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