| 1 | /* | 
|---|
| 2 |  * thread.c -  implementation of thread operations (user & kernel) | 
|---|
| 3 |  * | 
|---|
| 4 |  * Author  Ghassan Almaless (2008,2009,2010,2011,2012) | 
|---|
| 5 |  *         Alain Greiner (2016,2017) | 
|---|
| 6 |  * | 
|---|
| 7 |  * Copyright (c) UPMC Sorbonne Universites | 
|---|
| 8 |  * | 
|---|
| 9 |  * This file is part of ALMOS-MKH. | 
|---|
| 10 |  * | 
|---|
| 11 |  * ALMOS-MKH is free software; you can redistribute it and/or modify it | 
|---|
| 12 |  * under the terms of the GNU General Public License as published by | 
|---|
| 13 |  * the Free Software Foundation; version 2.0 of the License. | 
|---|
| 14 |  * | 
|---|
| 15 |  * ALMOS-MKH is distributed in the hope that it will be useful, but | 
|---|
| 16 |  * WITHOUT ANY WARRANTY; without even the implied warranty of | 
|---|
| 17 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
|---|
| 18 |  * General Public License for more details. | 
|---|
| 19 |  * | 
|---|
| 20 |  * You should have received a copy of the GNU General Public License | 
|---|
| 21 |  * along with ALMOS-MKH; if not, write to the Free Software Foundation, | 
|---|
| 22 |  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | 
|---|
| 23 |  */ | 
|---|
| 24 |  | 
|---|
| 25 | #include <kernel_config.h> | 
|---|
| 26 | #include <hal_types.h> | 
|---|
| 27 | #include <hal_context.h> | 
|---|
| 28 | #include <hal_irqmask.h> | 
|---|
| 29 | #include <hal_special.h> | 
|---|
| 30 | #include <hal_remote.h> | 
|---|
| 31 | #include <memcpy.h> | 
|---|
| 32 | #include <printk.h> | 
|---|
| 33 | #include <cluster.h> | 
|---|
| 34 | #include <process.h> | 
|---|
| 35 | #include <scheduler.h> | 
|---|
| 36 | #include <dev_pic.h> | 
|---|
| 37 | #include <core.h> | 
|---|
| 38 | #include <list.h> | 
|---|
| 39 | #include <xlist.h> | 
|---|
| 40 | #include <page.h> | 
|---|
| 41 | #include <kmem.h> | 
|---|
| 42 | #include <ppm.h> | 
|---|
| 43 | #include <thread.h> | 
|---|
| 44 |  | 
|---|
| 45 | ////////////////////////////////////////////////////////////////////////////////////// | 
|---|
| 46 | // Extern global variables | 
|---|
| 47 | ////////////////////////////////////////////////////////////////////////////////////// | 
|---|
| 48 |  | 
|---|
| 49 | extern process_t      process_zero; | 
|---|
| 50 |  | 
|---|
| 51 | ////////////////////////////////////////////////////////////////////////////////////// | 
|---|
| 52 | // This function returns a printable string for the thread type. | 
|---|
| 53 | ////////////////////////////////////////////////////////////////////////////////////// | 
|---|
| 54 | char * thread_type_str( uint32_t type ) | 
|---|
| 55 | { | 
|---|
| 56 |     if     ( type == THREAD_USER   ) return "USR"; | 
|---|
| 57 |     else if( type == THREAD_RPC    ) return "RPC"; | 
|---|
| 58 |     else if( type == THREAD_DEV    ) return "DEV"; | 
|---|
| 59 |     else if( type == THREAD_KERNEL ) return "KER"; | 
|---|
| 60 |     else if( type == THREAD_IDLE   ) return "IDL"; | 
|---|
| 61 |     else                             return "undefined"; | 
|---|
| 62 | } | 
|---|
| 63 |  | 
|---|
| 64 | ///////////////////////////////////////////////////////////////////////////////////// | 
|---|
| 65 | // This static function allocates physical memory for a thread descriptor. | 
|---|
| 66 | // It can be called by the three functions: | 
|---|
| 67 | // - thread_user_create() | 
|---|
| 68 | // - thread_user_fork() | 
|---|
| 69 | // - thread_kernel_create() | 
|---|
| 70 | ///////////////////////////////////////////////////////////////////////////////////// | 
|---|
| 71 | // @ return pointer on thread descriptor if success / return NULL if failure. | 
|---|
| 72 | ///////////////////////////////////////////////////////////////////////////////////// | 
|---|
| 73 | static thread_t * thread_alloc() | 
|---|
| 74 | { | 
|---|
| 75 |         page_t       * page;   // pointer on page descriptor containing thread descriptor | 
|---|
| 76 |         kmem_req_t     req;    // kmem request | 
|---|
| 77 |  | 
|---|
| 78 |         // allocates memory for thread descriptor + kernel stack | 
|---|
| 79 |         req.type  = KMEM_PAGE; | 
|---|
| 80 |         req.size  = CONFIG_THREAD_DESC_ORDER; | 
|---|
| 81 |         req.flags = AF_KERNEL | AF_ZERO; | 
|---|
| 82 |         page      = kmem_alloc( &req ); | 
|---|
| 83 |  | 
|---|
| 84 |         if( page == NULL ) return NULL; | 
|---|
| 85 |  | 
|---|
| 86 |     // return pointer on new thread descriptor | 
|---|
| 87 |     xptr_t base_xp = ppm_page2base( XPTR(local_cxy , page ) ); | 
|---|
| 88 |     return (thread_t *)GET_PTR( base_xp ); | 
|---|
| 89 |  | 
|---|
| 90 | }  // end thread_alloc() | 
|---|
| 91 |    | 
|---|
| 92 |  | 
|---|
| 93 | ///////////////////////////////////////////////////////////////////////////////////// | 
|---|
| 94 | // This static function releases the physical memory for a thread descriptor. | 
|---|
| 95 | // It is called by the three functions: | 
|---|
| 96 | // - thread_user_create() | 
|---|
| 97 | // - thread_user_fork() | 
|---|
| 98 | // - thread_kernel_create() | 
|---|
| 99 | ///////////////////////////////////////////////////////////////////////////////////// | 
|---|
| 100 | // @ thread  : pointer on thread descriptor. | 
|---|
| 101 | ///////////////////////////////////////////////////////////////////////////////////// | 
|---|
| 102 | static void thread_release( thread_t * thread ) | 
|---|
| 103 | { | 
|---|
| 104 |     kmem_req_t   req; | 
|---|
| 105 |  | 
|---|
| 106 |     xptr_t base_xp = ppm_base2page( XPTR(local_cxy , thread ) ); | 
|---|
| 107 |  | 
|---|
| 108 |     req.type  = KMEM_PAGE; | 
|---|
| 109 |     req.ptr   = GET_PTR( base_xp ); | 
|---|
| 110 |     kmem_free( &req ); | 
|---|
| 111 | } | 
|---|
| 112 |  | 
|---|
| 113 | ///////////////////////////////////////////////////////////////////////////////////// | 
|---|
| 114 | // This static function initializes a thread descriptor (kernel or user). | 
|---|
| 115 | // It can be called by the four functions: | 
|---|
| 116 | // - thread_user_create() | 
|---|
| 117 | // - thread_user_fork() | 
|---|
| 118 | // - thread_kernel_create() | 
|---|
| 119 | // - thread_user_init() | 
|---|
| 120 | ///////////////////////////////////////////////////////////////////////////////////// | 
|---|
| 121 | // @ thread       : pointer on thread descriptor | 
|---|
| 122 | // @ process      : pointer on process descriptor. | 
|---|
| 123 | // @ type         : thread type. | 
|---|
| 124 | // @ func         : pointer on thread entry function. | 
|---|
| 125 | // @ args         : pointer on thread entry function arguments. | 
|---|
| 126 | // @ core_lid     : target core local index. | 
|---|
| 127 | // @ u_stack_base : stack base (user thread only) | 
|---|
| 128 | // @ u_stack_size : stack base (user thread only) | 
|---|
| 129 | ///////////////////////////////////////////////////////////////////////////////////// | 
|---|
| 130 | static error_t thread_init( thread_t      * thread, | 
|---|
| 131 |                             process_t     * process, | 
|---|
| 132 |                             thread_type_t   type, | 
|---|
| 133 |                             void          * func, | 
|---|
| 134 |                             void          * args, | 
|---|
| 135 |                             lid_t           core_lid, | 
|---|
| 136 |                             intptr_t        u_stack_base, | 
|---|
| 137 |                             uint32_t        u_stack_size ) | 
|---|
| 138 | { | 
|---|
| 139 |     error_t        error; | 
|---|
| 140 |     trdid_t        trdid;      // allocated thread identifier | 
|---|
| 141 |  | 
|---|
| 142 |         cluster_t    * local_cluster = LOCAL_CLUSTER; | 
|---|
| 143 |  | 
|---|
| 144 |     // register new thread in process descriptor, and get a TRDID | 
|---|
| 145 |     spinlock_lock( &process->th_lock ); | 
|---|
| 146 |     error = process_register_thread( process, thread , &trdid ); | 
|---|
| 147 |     spinlock_unlock( &process->th_lock ); | 
|---|
| 148 |  | 
|---|
| 149 |     if( error ) | 
|---|
| 150 |     { | 
|---|
| 151 |         printk("\n[ERROR] in %s : cannot get TRDID\n", __FUNCTION__ ); | 
|---|
| 152 |         return EINVAL; | 
|---|
| 153 |     } | 
|---|
| 154 |  | 
|---|
| 155 |         // Initialize new thread descriptor | 
|---|
| 156 |     thread->trdid           = trdid; | 
|---|
| 157 |         thread->type            = type; | 
|---|
| 158 |     thread->quantum         = 0;            // TODO | 
|---|
| 159 |     thread->ticks_nr        = 0;            // TODO | 
|---|
| 160 |     thread->time_last_check = 0; | 
|---|
| 161 |         thread->core            = &local_cluster->core_tbl[core_lid]; | 
|---|
| 162 |         thread->process         = process; | 
|---|
| 163 |  | 
|---|
| 164 |     thread->local_locks     = 0; | 
|---|
| 165 |     list_root_init( &thread->locks_root ); | 
|---|
| 166 |  | 
|---|
| 167 |     thread->remote_locks    = 0; | 
|---|
| 168 |     xlist_root_init( XPTR( local_cxy , &thread->xlocks_root ) ); | 
|---|
| 169 |  | 
|---|
| 170 |     thread->u_stack_base    = u_stack_base; | 
|---|
| 171 |     thread->u_stack_size    = u_stack_size; | 
|---|
| 172 |     thread->k_stack_base    = (intptr_t)thread; | 
|---|
| 173 |     thread->k_stack_size    = CONFIG_THREAD_DESC_SIZE; | 
|---|
| 174 |  | 
|---|
| 175 |     thread->entry_func      = func;         // thread entry point | 
|---|
| 176 |     thread->entry_args      = args;         // thread function arguments | 
|---|
| 177 |     thread->flags           = 0;            // all flags reset | 
|---|
| 178 |     thread->signals         = 0;            // no pending signal | 
|---|
| 179 |     thread->errno           = 0;            // no error detected | 
|---|
| 180 |     thread->fork_user       = 0;            // no fork required | 
|---|
| 181 |     thread->fork_cxy        = 0; | 
|---|
| 182 |  | 
|---|
| 183 |     // thread blocked | 
|---|
| 184 |     thread->blocked = THREAD_BLOCKED_GLOBAL; | 
|---|
| 185 |  | 
|---|
| 186 |     // reset children list | 
|---|
| 187 |     xlist_root_init( XPTR( local_cxy , &thread->children_root ) ); | 
|---|
| 188 |     thread->children_nr = 0; | 
|---|
| 189 |  | 
|---|
| 190 |     // reset sched list and brothers list | 
|---|
| 191 |     list_entry_init( &thread->sched_list ); | 
|---|
| 192 |     xlist_entry_init( XPTR( local_cxy , &thread->brothers_list ) ); | 
|---|
| 193 |  | 
|---|
| 194 |     // reset thread info | 
|---|
| 195 |     memset( &thread->info , 0 , sizeof(thread_info_t) ); | 
|---|
| 196 |  | 
|---|
| 197 |     // initialise signature | 
|---|
| 198 |         thread->signature = THREAD_SIGNATURE; | 
|---|
| 199 |  | 
|---|
| 200 |     // update local DQDT | 
|---|
| 201 |     dqdt_local_update_threads( 1 ); | 
|---|
| 202 |  | 
|---|
| 203 |     // register new thread in core scheduler | 
|---|
| 204 |     sched_register_thread( thread->core , thread ); | 
|---|
| 205 |  | 
|---|
| 206 |         return 0; | 
|---|
| 207 |  | 
|---|
| 208 | } // end thread_init() | 
|---|
| 209 |  | 
|---|
| 210 | ///////////////////////////////////////////////////////// | 
|---|
| 211 | error_t thread_user_create( pid_t             pid, | 
|---|
| 212 |                             void            * start_func, | 
|---|
| 213 |                             void            * start_arg, | 
|---|
| 214 |                             pthread_attr_t  * attr, | 
|---|
| 215 |                             thread_t       ** new_thread ) | 
|---|
| 216 | { | 
|---|
| 217 |     error_t        error; | 
|---|
| 218 |         thread_t     * thread;       // pointer on created thread descriptor | 
|---|
| 219 |     process_t    * process;      // pointer to local process descriptor | 
|---|
| 220 |     lid_t          core_lid;     // selected core local index | 
|---|
| 221 |     vseg_t       * vseg;         // stack vseg | 
|---|
| 222 |  | 
|---|
| 223 |     thread_dmsg("\n[INFO] %s : enters for process %x\n", __FUNCTION__ , pid ); | 
|---|
| 224 |  | 
|---|
| 225 |     // get process descriptor local copy | 
|---|
| 226 |     process = process_get_local_copy( pid ); | 
|---|
| 227 |  | 
|---|
| 228 |     if( process == NULL ) | 
|---|
| 229 |     { | 
|---|
| 230 |                 printk("\n[ERROR] in %s : cannot get process descriptor %x\n", | 
|---|
| 231 |                __FUNCTION__ , pid ); | 
|---|
| 232 |         return ENOMEM; | 
|---|
| 233 |     } | 
|---|
| 234 |  | 
|---|
| 235 |     // select a target core in local cluster | 
|---|
| 236 |     if( attr->attributes & PT_ATTR_CORE_DEFINED ) core_lid = attr->lid; | 
|---|
| 237 |     else                                          core_lid = cluster_select_local_core(); | 
|---|
| 238 |  | 
|---|
| 239 |     // check core local index | 
|---|
| 240 |     if( core_lid >= LOCAL_CLUSTER->cores_nr ) | 
|---|
| 241 |     { | 
|---|
| 242 |             printk("\n[ERROR] in %s : illegal core index attribute = %d\n", | 
|---|
| 243 |                __FUNCTION__ , core_lid ); | 
|---|
| 244 |  | 
|---|
| 245 |         return EINVAL; | 
|---|
| 246 |     } | 
|---|
| 247 |  | 
|---|
| 248 |     // allocate a stack from local VMM | 
|---|
| 249 |     vseg = vmm_create_vseg( process, 0 , 0 , VSEG_TYPE_STACK ); | 
|---|
| 250 |  | 
|---|
| 251 |     if( vseg == NULL ) | 
|---|
| 252 |     { | 
|---|
| 253 |             printk("\n[ERROR] in %s : cannot create stack vseg\n", __FUNCTION__ ); | 
|---|
| 254 |                 return ENOMEM; | 
|---|
| 255 |     } | 
|---|
| 256 |  | 
|---|
| 257 |     // allocate memory for thread descriptor | 
|---|
| 258 |     thread = thread_alloc(); | 
|---|
| 259 |  | 
|---|
| 260 |     if( thread == NULL ) | 
|---|
| 261 |     { | 
|---|
| 262 |             printk("\n[ERROR] in %s : cannot create new thread\n", __FUNCTION__ ); | 
|---|
| 263 |         vmm_remove_vseg( vseg ); | 
|---|
| 264 |         return ENOMEM; | 
|---|
| 265 |     } | 
|---|
| 266 |  | 
|---|
| 267 |     // initialize thread descriptor | 
|---|
| 268 |     error = thread_init( thread, | 
|---|
| 269 |                          process, | 
|---|
| 270 |                          THREAD_USER, | 
|---|
| 271 |                          start_func, | 
|---|
| 272 |                          start_arg, | 
|---|
| 273 |                          core_lid, | 
|---|
| 274 |                          vseg->min, | 
|---|
| 275 |                          vseg->max - vseg->min ); | 
|---|
| 276 |  | 
|---|
| 277 |     if( error ) | 
|---|
| 278 |     { | 
|---|
| 279 |             printk("\n[ERROR] in %s : cannot initialize new thread\n", __FUNCTION__ ); | 
|---|
| 280 |         vmm_remove_vseg( vseg ); | 
|---|
| 281 |         thread_release( thread ); | 
|---|
| 282 |         return EINVAL; | 
|---|
| 283 |     } | 
|---|
| 284 |  | 
|---|
| 285 |     // set LOADABLE flag | 
|---|
| 286 |     thread->flags = THREAD_FLAG_LOADABLE; | 
|---|
| 287 |  | 
|---|
| 288 |     // set DETACHED flag if required | 
|---|
| 289 |     if( attr->attributes & PT_ATTR_DETACH ) thread->flags |= THREAD_FLAG_DETACHED; | 
|---|
| 290 |  | 
|---|
| 291 |     // allocate & initialize CPU context | 
|---|
| 292 |         error = hal_cpu_context_create( thread ); | 
|---|
| 293 |  | 
|---|
| 294 |     if( error ) | 
|---|
| 295 |     { | 
|---|
| 296 |             printk("\n[ERROR] in %s : cannot create CPU context\n", __FUNCTION__ ); | 
|---|
| 297 |         vmm_remove_vseg( vseg ); | 
|---|
| 298 |         thread_release( thread ); | 
|---|
| 299 |         return ENOMEM; | 
|---|
| 300 |     } | 
|---|
| 301 |  | 
|---|
| 302 |     // allocate & initialize FPU context | 
|---|
| 303 |     error = hal_fpu_context_create( thread ); | 
|---|
| 304 |  | 
|---|
| 305 |     if( error ) | 
|---|
| 306 |     { | 
|---|
| 307 |             printk("\n[ERROR] in %s : cannot create FPU context\n", __FUNCTION__ ); | 
|---|
| 308 |         vmm_remove_vseg( vseg ); | 
|---|
| 309 |         thread_release( thread ); | 
|---|
| 310 |         return ENOMEM; | 
|---|
| 311 |     } | 
|---|
| 312 |  | 
|---|
| 313 |     thread_dmsg("\n[INFO] %s : exit / trdid = %x / process %x / core = %d\n", | 
|---|
| 314 |                 __FUNCTION__ , thread->trdid , process->pid , core_lid ); | 
|---|
| 315 |  | 
|---|
| 316 |     *new_thread = thread; | 
|---|
| 317 |         return 0; | 
|---|
| 318 |  | 
|---|
| 319 | }  // end thread_user_create() | 
|---|
| 320 |  | 
|---|
| 321 | ////////////////////////////////////////////// | 
|---|
| 322 | error_t thread_user_fork( process_t * process, | 
|---|
| 323 |                           thread_t ** new_thread ) | 
|---|
| 324 | { | 
|---|
| 325 |     error_t        error; | 
|---|
| 326 |         thread_t     * thread;       // pointer on new thread descriptor | 
|---|
| 327 |     lid_t          core_lid;     // selected core local index | 
|---|
| 328 |         vseg_t       * vseg;         // stack vseg | 
|---|
| 329 |  | 
|---|
| 330 |     thread_dmsg("\n[INFO] %s : enters\n", __FUNCTION__ ); | 
|---|
| 331 |  | 
|---|
| 332 |     // allocate a stack from local VMM | 
|---|
| 333 |     vseg = vmm_create_vseg( process, 0 , 0 , VSEG_TYPE_STACK ); | 
|---|
| 334 |  | 
|---|
| 335 |     if( vseg == NULL ) | 
|---|
| 336 |     { | 
|---|
| 337 |             printk("\n[ERROR] in %s : cannot create stack vseg\n", __FUNCTION__ ); | 
|---|
| 338 |                 return ENOMEM; | 
|---|
| 339 |     } | 
|---|
| 340 |  | 
|---|
| 341 |     // select a target core in local cluster | 
|---|
| 342 |     core_lid = cluster_select_local_core(); | 
|---|
| 343 |  | 
|---|
| 344 |     // get pointer on calling thread descriptor | 
|---|
| 345 |     thread_t * this = CURRENT_THREAD; | 
|---|
| 346 |  | 
|---|
| 347 |     // allocate memory for new thread descriptor | 
|---|
| 348 |     thread = thread_alloc(); | 
|---|
| 349 |  | 
|---|
| 350 |     if( thread == NULL ) | 
|---|
| 351 |     { | 
|---|
| 352 |         printk("\n[ERROR] in %s : cannot allocate new thread\n", __FUNCTION__ ); | 
|---|
| 353 |         vmm_remove_vseg( vseg ); | 
|---|
| 354 |         return ENOMEM; | 
|---|
| 355 |     } | 
|---|
| 356 |  | 
|---|
| 357 |     // initialize thread descriptor | 
|---|
| 358 |     error = thread_init( thread, | 
|---|
| 359 |                          process, | 
|---|
| 360 |                          THREAD_USER, | 
|---|
| 361 |                          this->entry_func, | 
|---|
| 362 |                          this->entry_args, | 
|---|
| 363 |                          core_lid, | 
|---|
| 364 |                          vseg->min, | 
|---|
| 365 |                          vseg->max - vseg->min ); | 
|---|
| 366 |  | 
|---|
| 367 |     if( error ) | 
|---|
| 368 |     { | 
|---|
| 369 |             printk("\n[ERROR] in %s : cannot initialize new thread\n", __FUNCTION__ ); | 
|---|
| 370 |         vmm_remove_vseg( vseg ); | 
|---|
| 371 |         thread_release( thread ); | 
|---|
| 372 |         return EINVAL; | 
|---|
| 373 |     } | 
|---|
| 374 |  | 
|---|
| 375 |     // set ATTACHED flag if set in this thread | 
|---|
| 376 |     if( this->flags & THREAD_FLAG_DETACHED ) thread->flags = THREAD_FLAG_DETACHED; | 
|---|
| 377 |  | 
|---|
| 378 |     // allocate & initialize CPU context from calling thread | 
|---|
| 379 |         error = hal_cpu_context_copy( thread , this ); | 
|---|
| 380 |  | 
|---|
| 381 |     if( error ) | 
|---|
| 382 |     { | 
|---|
| 383 |             printk("\n[ERROR] in %s : cannot create CPU context\n", __FUNCTION__ ); | 
|---|
| 384 |         vmm_remove_vseg( vseg ); | 
|---|
| 385 |         thread_release( thread ); | 
|---|
| 386 |         return ENOMEM; | 
|---|
| 387 |     } | 
|---|
| 388 |  | 
|---|
| 389 |     // allocate & initialize FPU context from calling thread | 
|---|
| 390 |         error = hal_fpu_context_copy( thread , this ); | 
|---|
| 391 |  | 
|---|
| 392 |     if( error ) | 
|---|
| 393 |     { | 
|---|
| 394 |             printk("\n[ERROR] in %s : cannot create CPU context\n", __FUNCTION__ ); | 
|---|
| 395 |         vmm_remove_vseg( vseg ); | 
|---|
| 396 |         thread_release( thread ); | 
|---|
| 397 |         return ENOMEM; | 
|---|
| 398 |     } | 
|---|
| 399 |  | 
|---|
| 400 |     thread_dmsg("\n[INFO] %s : exit / thread %x for process %x on core %d in cluster %x\n", | 
|---|
| 401 |                  __FUNCTION__, thread->trdid, process->pid, core_lid, local_cxy ); | 
|---|
| 402 |  | 
|---|
| 403 |     *new_thread = thread; | 
|---|
| 404 |         return 0; | 
|---|
| 405 |  | 
|---|
| 406 | }  // end thread_user_fork() | 
|---|
| 407 |  | 
|---|
| 408 | ///////////////////////////////////////////////////////// | 
|---|
| 409 | error_t thread_kernel_create( thread_t     ** new_thread, | 
|---|
| 410 |                               thread_type_t   type, | 
|---|
| 411 |                               void          * func, | 
|---|
| 412 |                               void          * args, | 
|---|
| 413 |                                               lid_t           core_lid ) | 
|---|
| 414 | { | 
|---|
| 415 |     error_t        error; | 
|---|
| 416 |         thread_t     * thread;       // pointer on new thread descriptor | 
|---|
| 417 |  | 
|---|
| 418 |     thread_dmsg("\n[INFO] %s : enter / for type %s on core[%x,%d] / cycle %d\n", | 
|---|
| 419 |     __FUNCTION__ , thread_type_str( type ) , local_cxy , core_lid , hal_time_stamp() ); | 
|---|
| 420 |  | 
|---|
| 421 |     assert( ( (type == THREAD_KERNEL) || (type == THREAD_RPC) || | 
|---|
| 422 |               (type == THREAD_IDLE)   || (type == THREAD_DEV) ) , | 
|---|
| 423 |               __FUNCTION__ , "illegal thread type" ); | 
|---|
| 424 |  | 
|---|
| 425 |     assert( (core_lid < LOCAL_CLUSTER->cores_nr) , | 
|---|
| 426 |             __FUNCTION__ , "illegal core_lid" ); | 
|---|
| 427 |  | 
|---|
| 428 |     // allocate memory for new thread descriptor | 
|---|
| 429 |     thread = thread_alloc(); | 
|---|
| 430 |  | 
|---|
| 431 |     if( thread == NULL ) return ENOMEM; | 
|---|
| 432 |  | 
|---|
| 433 |     // initialize thread descriptor | 
|---|
| 434 |     error = thread_init( thread, | 
|---|
| 435 |                          &process_zero, | 
|---|
| 436 |                          type, | 
|---|
| 437 |                          func, | 
|---|
| 438 |                          args, | 
|---|
| 439 |                          core_lid, | 
|---|
| 440 |                          0 , 0 );  // no user stack for a kernel thread | 
|---|
| 441 |  | 
|---|
| 442 |     if( error ) // release allocated memory for thread descriptor | 
|---|
| 443 |     { | 
|---|
| 444 |         thread_release( thread ); | 
|---|
| 445 |         return EINVAL; | 
|---|
| 446 |     } | 
|---|
| 447 |  | 
|---|
| 448 |     // allocate & initialize CPU context | 
|---|
| 449 |         hal_cpu_context_create( thread ); | 
|---|
| 450 |  | 
|---|
| 451 |     thread_dmsg("\n[INFO] %s : exit / trdid = %x / type = %s / core = [%x,%d] / cycle %d\n", | 
|---|
| 452 |     __FUNCTION__ , thread->trdid , thread_type_str(type) ,  | 
|---|
| 453 |     local_cxy , core_lid , hal_time_stamp() ); | 
|---|
| 454 |  | 
|---|
| 455 |     *new_thread = thread; | 
|---|
| 456 |         return 0; | 
|---|
| 457 |  | 
|---|
| 458 | } // end thread_kernel_create() | 
|---|
| 459 |  | 
|---|
| 460 | /////////////////////////////////////////////////// | 
|---|
| 461 | error_t thread_kernel_init( thread_t      * thread, | 
|---|
| 462 |                             thread_type_t   type, | 
|---|
| 463 |                             void          * func, | 
|---|
| 464 |                             void          * args, | 
|---|
| 465 |                                             lid_t           core_lid ) | 
|---|
| 466 | { | 
|---|
| 467 |     assert( ( (type == THREAD_KERNEL) || (type == THREAD_RPC) || | 
|---|
| 468 |               (type == THREAD_IDLE)   || (type == THREAD_DEV) ) , | 
|---|
| 469 |               __FUNCTION__ , "illegal thread type" ); | 
|---|
| 470 |  | 
|---|
| 471 |     if( core_lid >= LOCAL_CLUSTER->cores_nr ) | 
|---|
| 472 |     { | 
|---|
| 473 |         printk("\n[PANIC] in %s : illegal core_lid / cores = %d / lid = %d / cxy = %x\n", | 
|---|
| 474 |                __FUNCTION__ , LOCAL_CLUSTER->cores_nr , core_lid , local_cxy ); | 
|---|
| 475 |         hal_core_sleep(); | 
|---|
| 476 |     } | 
|---|
| 477 |  | 
|---|
| 478 |     error_t  error = thread_init( thread, | 
|---|
| 479 |                                   &process_zero, | 
|---|
| 480 |                                   type, | 
|---|
| 481 |                                   func, | 
|---|
| 482 |                                   args, | 
|---|
| 483 |                                   core_lid, | 
|---|
| 484 |                                   0 , 0 );   // no user stack for a kernel thread | 
|---|
| 485 |  | 
|---|
| 486 |     // allocate & initialize CPU context if success | 
|---|
| 487 |     if( error == 0 ) hal_cpu_context_create( thread ); | 
|---|
| 488 |  | 
|---|
| 489 |     return error; | 
|---|
| 490 | } | 
|---|
| 491 |  | 
|---|
| 492 | /////////////////////////////////////////////////////////////////////////////////////// | 
|---|
| 493 | // TODO: check that all memory dynamically allocated during thread execution | 
|---|
| 494 | // has been released, using a cache of mmap and malloc requests. [AG] | 
|---|
| 495 | /////////////////////////////////////////////////////////////////////////////////////// | 
|---|
| 496 | void thread_destroy( thread_t * thread ) | 
|---|
| 497 | { | 
|---|
| 498 |         uint32_t     tm_start; | 
|---|
| 499 |         uint32_t     tm_end; | 
|---|
| 500 |     reg_t        state; | 
|---|
| 501 |  | 
|---|
| 502 |     process_t  * process    = thread->process; | 
|---|
| 503 |     core_t     * core       = thread->core; | 
|---|
| 504 |  | 
|---|
| 505 |     thread_dmsg("\n[INFO] %s : enters for thread %x in process %x / type = %s\n", | 
|---|
| 506 |                 __FUNCTION__ , thread->trdid , process->pid , thread_type_str( thread->type ) ); | 
|---|
| 507 |  | 
|---|
| 508 |     assert( (thread->children_nr == 0) , __FUNCTION__ , "still attached children" ); | 
|---|
| 509 |  | 
|---|
| 510 |     assert( (thread->local_locks == 0) , __FUNCTION__ , "all local locks not released" ); | 
|---|
| 511 |  | 
|---|
| 512 |     assert( (thread->remote_locks == 0) , __FUNCTION__ , "all remote locks not released" ); | 
|---|
| 513 |  | 
|---|
| 514 |         tm_start = hal_get_cycles(); | 
|---|
| 515 |  | 
|---|
| 516 |     // update intrumentation values | 
|---|
| 517 |     uint32_t pgfaults = thread->info.pgfault_nr; | 
|---|
| 518 |     uint32_t u_errors = thread->info.u_err_nr; | 
|---|
| 519 |     uint32_t m_errors = thread->info.m_err_nr; | 
|---|
| 520 |  | 
|---|
| 521 |         process->vmm.pgfault_nr += pgfaults; | 
|---|
| 522 |         process->vmm.u_err_nr   += u_errors; | 
|---|
| 523 |         process->vmm.m_err_nr   += m_errors; | 
|---|
| 524 |  | 
|---|
| 525 |     // release memory allocated for CPU context and FPU context | 
|---|
| 526 |         hal_cpu_context_destroy( thread ); | 
|---|
| 527 |         hal_fpu_context_destroy( thread ); | 
|---|
| 528 |          | 
|---|
| 529 |     // release FPU if required | 
|---|
| 530 |     // TODO This should be done before calling thread_destroy() | 
|---|
| 531 |         hal_disable_irq( &state ); | 
|---|
| 532 |         if( core->fpu_owner == thread ) | 
|---|
| 533 |         { | 
|---|
| 534 |                 core->fpu_owner = NULL; | 
|---|
| 535 |                 hal_fpu_disable(); | 
|---|
| 536 |         } | 
|---|
| 537 |         hal_restore_irq( state ); | 
|---|
| 538 |  | 
|---|
| 539 |     // remove thread from process th_tbl[] | 
|---|
| 540 |     // TODO This should be done before calling thread_destroy() | 
|---|
| 541 |     ltid_t ltid = LTID_FROM_TRDID( thread->trdid ); | 
|---|
| 542 |  | 
|---|
| 543 |         spinlock_lock( &process->th_lock ); | 
|---|
| 544 |         process->th_tbl[ltid] = XPTR_NULL; | 
|---|
| 545 |         process->th_nr--; | 
|---|
| 546 |         spinlock_unlock( &process->th_lock ); | 
|---|
| 547 |          | 
|---|
| 548 |     // update local DQDT | 
|---|
| 549 |     dqdt_local_update_threads( -1 ); | 
|---|
| 550 |  | 
|---|
| 551 |     // invalidate thread descriptor | 
|---|
| 552 |         thread->signature = 0; | 
|---|
| 553 |  | 
|---|
| 554 |     // release memory for thread descriptor | 
|---|
| 555 |     thread_release( thread ); | 
|---|
| 556 |  | 
|---|
| 557 |         tm_end = hal_get_cycles(); | 
|---|
| 558 |  | 
|---|
| 559 |         thread_dmsg("\n[INFO] %s : exit for thread %x in process %x / duration = %d\n", | 
|---|
| 560 |                        __FUNCTION__, thread->trdid , process->pid , tm_end - tm_start ); | 
|---|
| 561 | } | 
|---|
| 562 |  | 
|---|
| 563 | ///////////////////////////////////////////////// | 
|---|
| 564 | void thread_child_parent_link( xptr_t  xp_parent, | 
|---|
| 565 |                                xptr_t  xp_child ) | 
|---|
| 566 | { | 
|---|
| 567 |     // get extended pointers on children list root | 
|---|
| 568 |     cxy_t      parent_cxy = GET_CXY( xp_parent ); | 
|---|
| 569 |     thread_t * parent_ptr = (thread_t *)GET_PTR( xp_parent ); | 
|---|
| 570 |     xptr_t     root       = XPTR( parent_cxy , &parent_ptr->children_root ); | 
|---|
| 571 |  | 
|---|
| 572 |     // get extended pointer on children list entry | 
|---|
| 573 |     cxy_t      child_cxy  = GET_CXY( xp_child ); | 
|---|
| 574 |     thread_t * child_ptr  = (thread_t *)GET_PTR( xp_child ); | 
|---|
| 575 |     xptr_t     entry      = XPTR( child_cxy , &child_ptr->brothers_list ); | 
|---|
| 576 |  | 
|---|
| 577 |     // set the link | 
|---|
| 578 |     xlist_add_first( root , entry ); | 
|---|
| 579 |     hal_remote_atomic_add( XPTR( parent_cxy , &parent_ptr->children_nr ) , 1 ); | 
|---|
| 580 | } | 
|---|
| 581 |  | 
|---|
| 582 | /////////////////////////////////////////////////// | 
|---|
| 583 | void thread_child_parent_unlink( xptr_t  xp_parent, | 
|---|
| 584 |                                  xptr_t  xp_child ) | 
|---|
| 585 | { | 
|---|
| 586 |     // get extended pointer on children list lock | 
|---|
| 587 |     cxy_t      parent_cxy = GET_CXY( xp_parent ); | 
|---|
| 588 |     thread_t * parent_ptr = (thread_t *)GET_PTR( xp_parent ); | 
|---|
| 589 |     xptr_t     lock       = XPTR( parent_cxy , &parent_ptr->children_lock ); | 
|---|
| 590 |  | 
|---|
| 591 |     // get extended pointer on children list entry | 
|---|
| 592 |     cxy_t      child_cxy  = GET_CXY( xp_child ); | 
|---|
| 593 |     thread_t * child_ptr  = (thread_t *)GET_PTR( xp_child ); | 
|---|
| 594 |     xptr_t     entry      = XPTR( child_cxy , &child_ptr->brothers_list ); | 
|---|
| 595 |  | 
|---|
| 596 |     // get the lock | 
|---|
| 597 |     remote_spinlock_lock( lock ); | 
|---|
| 598 |  | 
|---|
| 599 |     // remove the link | 
|---|
| 600 |     xlist_unlink( entry ); | 
|---|
| 601 |     hal_remote_atomic_add( XPTR( parent_cxy , &parent_ptr->children_nr ) , -1 ); | 
|---|
| 602 |  | 
|---|
| 603 |     // release the lock | 
|---|
| 604 |     remote_spinlock_unlock( lock ); | 
|---|
| 605 | } | 
|---|
| 606 |  | 
|---|
| 607 | ///////////////////////////////////////////////// | 
|---|
| 608 | inline void thread_set_signal( thread_t * thread, | 
|---|
| 609 |                                uint32_t   mask ) | 
|---|
| 610 | { | 
|---|
| 611 |     hal_atomic_or( &thread->signals , mask ); | 
|---|
| 612 | } | 
|---|
| 613 |  | 
|---|
| 614 | /////////////////////////////////////////////////// | 
|---|
| 615 | inline void thread_reset_signal( thread_t * thread, | 
|---|
| 616 |                                  uint32_t   mask ) | 
|---|
| 617 | { | 
|---|
| 618 |     hal_atomic_and( &thread->signals , ~mask ); | 
|---|
| 619 | } | 
|---|
| 620 |  | 
|---|
| 621 | ////////////////////////////////// | 
|---|
| 622 | inline bool_t thread_is_joinable() | 
|---|
| 623 | { | 
|---|
| 624 |     thread_t * this = CURRENT_THREAD; | 
|---|
| 625 |     return( (this->brothers_list.next != XPTR_NULL) && | 
|---|
| 626 |             (this->brothers_list.pred != XPTR_NULL) ); | 
|---|
| 627 | } | 
|---|
| 628 |  | 
|---|
| 629 | ////////////////////////////////// | 
|---|
| 630 | inline bool_t thread_is_runnable() | 
|---|
| 631 | { | 
|---|
| 632 |     thread_t * this = CURRENT_THREAD; | 
|---|
| 633 |     return( this->blocked == 0 ); | 
|---|
| 634 | } | 
|---|
| 635 |  | 
|---|
| 636 | //////////////////////////////// | 
|---|
| 637 | inline bool_t thread_can_yield() | 
|---|
| 638 | { | 
|---|
| 639 |     thread_t * this = CURRENT_THREAD; | 
|---|
| 640 |     return (this->local_locks == 0) && (this->remote_locks == 0); | 
|---|
| 641 | } | 
|---|
| 642 |  | 
|---|
| 643 | ///////////////////////// | 
|---|
| 644 | void thread_check_sched() | 
|---|
| 645 | { | 
|---|
| 646 |     thread_t * this = CURRENT_THREAD; | 
|---|
| 647 |  | 
|---|
| 648 |         if( (this->local_locks == 0) &&  | 
|---|
| 649 |         (this->remote_locks == 0) && | 
|---|
| 650 |         (this->flags & THREAD_FLAG_SCHED) )  | 
|---|
| 651 |     { | 
|---|
| 652 |         this->flags &= ~THREAD_FLAG_SCHED; | 
|---|
| 653 |         sched_yield( NULL ); | 
|---|
| 654 |     } | 
|---|
| 655 | } | 
|---|
| 656 |  | 
|---|
| 657 | ///////////////////// | 
|---|
| 658 | error_t thread_exit() | 
|---|
| 659 | { | 
|---|
| 660 |     reg_t      sr_save; | 
|---|
| 661 |  | 
|---|
| 662 |         thread_t * this = CURRENT_THREAD; | 
|---|
| 663 |  | 
|---|
| 664 |     // test if this thread can be descheduled | 
|---|
| 665 |         if( !thread_can_yield() ) | 
|---|
| 666 |         { | 
|---|
| 667 |         printk("ERROR in %s : thread %x in process %x on core %d in cluster %x\n" | 
|---|
| 668 |                " did not released all locks\n", | 
|---|
| 669 |                __FUNCTION__ , this->trdid , this->process->pid , | 
|---|
| 670 |                CURRENT_CORE->lid , local_cxy ); | 
|---|
| 671 |         return EINVAL; | 
|---|
| 672 |     } | 
|---|
| 673 |  | 
|---|
| 674 |     if( this->flags & THREAD_FLAG_DETACHED ) | 
|---|
| 675 |     { | 
|---|
| 676 |         // if detached set signal and set blocking cause atomically | 
|---|
| 677 |         hal_disable_irq( &sr_save ); | 
|---|
| 678 |         thread_set_signal( this , THREAD_SIG_KILL ); | 
|---|
| 679 |         thread_block( this , THREAD_BLOCKED_EXIT ); | 
|---|
| 680 |         hal_restore_irq( sr_save ); | 
|---|
| 681 |     } | 
|---|
| 682 |     else | 
|---|
| 683 |     { | 
|---|
| 684 |         // if attached, set blocking cause | 
|---|
| 685 |         thread_block( this , THREAD_BLOCKED_EXIT ); | 
|---|
| 686 |     } | 
|---|
| 687 |  | 
|---|
| 688 |     // deschedule | 
|---|
| 689 |     sched_yield( NULL ); | 
|---|
| 690 |     return 0; | 
|---|
| 691 | } | 
|---|
| 692 |  | 
|---|
| 693 | ///////////////////////////////////// | 
|---|
| 694 | void thread_block( thread_t * thread, | 
|---|
| 695 |                    uint32_t   cause ) | 
|---|
| 696 | { | 
|---|
| 697 |     // set blocking cause | 
|---|
| 698 |     hal_atomic_or( &thread->blocked , cause ); | 
|---|
| 699 | } | 
|---|
| 700 |  | 
|---|
| 701 | //////////////////////////////////// | 
|---|
| 702 | void thread_unblock( xptr_t   thread, | 
|---|
| 703 |                     uint32_t cause ) | 
|---|
| 704 | { | 
|---|
| 705 |     // get thread cluster and local pointer | 
|---|
| 706 |     cxy_t      cxy = GET_CXY( thread ); | 
|---|
| 707 |     thread_t * ptr = (thread_t *)GET_PTR( thread ); | 
|---|
| 708 |  | 
|---|
| 709 |     // reset blocking cause | 
|---|
| 710 |     hal_remote_atomic_and( XPTR( cxy , &ptr->blocked ) , ~cause ); | 
|---|
| 711 | } | 
|---|
| 712 |  | 
|---|
| 713 | ///////////////////////////////////// | 
|---|
| 714 | void thread_kill( thread_t * target ) | 
|---|
| 715 | { | 
|---|
| 716 |     // set SIG_KILL signal in target thread descriptor | 
|---|
| 717 |     thread_set_signal( target , THREAD_SIG_KILL ); | 
|---|
| 718 |  | 
|---|
| 719 |     // set the global blocked bit in target thread descriptor. | 
|---|
| 720 |     thread_block( target , THREAD_BLOCKED_GLOBAL ); | 
|---|
| 721 |  | 
|---|
| 722 |     // send an IPI to schedule the target thread core. | 
|---|
| 723 |     dev_pic_send_ipi( local_cxy , target->core->lid ); | 
|---|
| 724 | } | 
|---|
| 725 |  | 
|---|
| 726 | /////////////////////// | 
|---|
| 727 | void thread_idle_func() | 
|---|
| 728 | { | 
|---|
| 729 | #if CONFIG_IDLE_DEBUG | 
|---|
| 730 |     lid_t  lid = CURRENT_CORE->lid; | 
|---|
| 731 | #endif | 
|---|
| 732 |  | 
|---|
| 733 |     while( 1 ) | 
|---|
| 734 |     { | 
|---|
| 735 |         idle_dmsg("\n[INFO] %s : core[%x][%d] goes to sleep at cycle %d\n", | 
|---|
| 736 |                     __FUNCTION__ , local_cxy , lid , hal_get_cycles() ); | 
|---|
| 737 |  | 
|---|
| 738 |         // force core to sleeping state | 
|---|
| 739 |         hal_core_sleep(); | 
|---|
| 740 |  | 
|---|
| 741 |         idle_dmsg("\n[INFO] %s : core[%x][%d] wake up at cycle %d\n", | 
|---|
| 742 |                     __FUNCTION__ , local_cxy , lid , hal_get_cycles() ); | 
|---|
| 743 |  | 
|---|
| 744 |         // force scheduling | 
|---|
| 745 |         sched_yield( NULL ); | 
|---|
| 746 |    } | 
|---|
| 747 | } | 
|---|
| 748 |  | 
|---|
| 749 | ///////////////////////////////////////////////// | 
|---|
| 750 | void thread_user_time_update( thread_t * thread ) | 
|---|
| 751 | { | 
|---|
| 752 |     // TODO | 
|---|
| 753 |     // printk("\n[WARNING] function %s not implemented\n", __FUNCTION__ ); | 
|---|
| 754 | } | 
|---|
| 755 |  | 
|---|
| 756 | /////////////////////////////////////////////////// | 
|---|
| 757 | void thread_kernel_time_update( thread_t * thread ) | 
|---|
| 758 | { | 
|---|
| 759 |     // TODO | 
|---|
| 760 |     // printk("\n[WARNING] function %s not implemented\n", __FUNCTION__ ); | 
|---|
| 761 | } | 
|---|
| 762 |  | 
|---|
| 763 | //////////////////////////////////////////////// | 
|---|
| 764 | void thread_signals_handle( thread_t * thread ) | 
|---|
| 765 | { | 
|---|
| 766 |     // TODO | 
|---|
| 767 |     // printk("\n[WARNING] function %s not implemented\n", __FUNCTION__ ); | 
|---|
| 768 | } | 
|---|
| 769 |  | 
|---|
| 770 | ///////////////////////////////////// | 
|---|
| 771 | xptr_t thread_get_xptr( pid_t    pid, | 
|---|
| 772 |                         trdid_t  trdid ) | 
|---|
| 773 | { | 
|---|
| 774 |     cxy_t         target_cxy;          // target thread cluster identifier | 
|---|
| 775 |     ltid_t        target_thread_ltid;  // target thread local index | 
|---|
| 776 |     thread_t    * target_thread_ptr;   // target thread local pointer | 
|---|
| 777 |     xptr_t        target_process_xp;   // extended pointer on target process descriptor | 
|---|
| 778 |     process_t   * target_process_ptr;  // local pointer on target process descriptor | 
|---|
| 779 |     pid_t         target_process_pid;  // target process identifier | 
|---|
| 780 |     xlist_entry_t root;                // root of list of process in target cluster | 
|---|
| 781 |     xptr_t        lock_xp;             // extended pointer on lock protecting  this list | 
|---|
| 782 |  | 
|---|
| 783 |     // get target cluster identifier and local thread identifier | 
|---|
| 784 |     target_cxy         = CXY_FROM_TRDID( trdid ); | 
|---|
| 785 |     target_thread_ltid = LTID_FROM_TRDID( trdid ); | 
|---|
| 786 |  | 
|---|
| 787 |     // get root of list of process descriptors in target cluster | 
|---|
| 788 |     hal_remote_memcpy( XPTR( local_cxy  , &root ), | 
|---|
| 789 |                        XPTR( target_cxy , &LOCAL_CLUSTER->pmgr.local_root ), | 
|---|
| 790 |                        sizeof(xlist_entry_t) ); | 
|---|
| 791 |  | 
|---|
| 792 |     // get extended pointer on lock protecting the list of processes | 
|---|
| 793 |     lock_xp = XPTR( target_cxy , &LOCAL_CLUSTER->pmgr.local_lock ); | 
|---|
| 794 |  | 
|---|
| 795 |     // take the lock protecting the list of processes in target cluster | 
|---|
| 796 |     remote_spinlock_lock( lock_xp ); | 
|---|
| 797 |  | 
|---|
| 798 |     // loop on list of process in target cluster to find the PID process | 
|---|
| 799 |     xptr_t  iter; | 
|---|
| 800 |     bool_t  found = false; | 
|---|
| 801 |     XLIST_FOREACH( XPTR( target_cxy , &LOCAL_CLUSTER->pmgr.local_root ) , iter ) | 
|---|
| 802 |     { | 
|---|
| 803 |         target_process_xp  = XLIST_ELEMENT( iter , process_t , local_list ); | 
|---|
| 804 |         target_process_ptr = (process_t *)GET_PTR( target_process_xp ); | 
|---|
| 805 |         target_process_pid = hal_remote_lw( XPTR( target_cxy , &target_process_ptr->pid ) ); | 
|---|
| 806 |         if( target_process_pid == pid ) | 
|---|
| 807 |         { | 
|---|
| 808 |             found = true; | 
|---|
| 809 |             break; | 
|---|
| 810 |         } | 
|---|
| 811 |     } | 
|---|
| 812 |  | 
|---|
| 813 |     // release the lock protecting the list of processes in target cluster | 
|---|
| 814 |     remote_spinlock_unlock( lock_xp ); | 
|---|
| 815 |  | 
|---|
| 816 |     // check target thread found | 
|---|
| 817 |     if( found == false ) | 
|---|
| 818 |     { | 
|---|
| 819 |         return XPTR_NULL; | 
|---|
| 820 |     } | 
|---|
| 821 |  | 
|---|
| 822 |     // get target thread local pointer | 
|---|
| 823 |     xptr_t xp = XPTR( target_cxy , &target_process_ptr->th_tbl[target_thread_ltid] ); | 
|---|
| 824 |     target_thread_ptr = (thread_t *)hal_remote_lpt( xp ); | 
|---|
| 825 |  | 
|---|
| 826 |     if( target_thread_ptr == NULL ) | 
|---|
| 827 |     { | 
|---|
| 828 |         return XPTR_NULL; | 
|---|
| 829 |     } | 
|---|
| 830 |  | 
|---|
| 831 |     return XPTR( target_cxy , target_thread_ptr ); | 
|---|
| 832 | } | 
|---|
| 833 |  | 
|---|