/* * Mach Operating System * Copyright (c) 1991,1990,1989,1988,1987 Carnegie Mellon University * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. * * Carnegie Mellon requests users of this software to return to * * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU * School of Computer Science * Carnegie Mellon University * Pittsburgh PA 15213-3890 * * any improvements or extensions that they make and grant Carnegie Mellon * the rights to redistribute these changes. */ /* * File: ipc_tt.c * Purpose: * Task and thread related IPC functions. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Routine: ipc_task_init * Purpose: * Initialize a task's IPC state. * * If non-null, some state will be inherited from the parent. * The parent must be appropriately initialized. * Conditions: * Nothing locked. */ void ipc_task_init( task_t task, task_t parent) { ipc_space_t space; ipc_port_t kport; kern_return_t kr; int i; kr = ipc_space_create(&space); if (kr != KERN_SUCCESS) panic("ipc_task_init"); kport = ipc_port_alloc_kernel(); if (kport == IP_NULL) panic("ipc_task_init"); itk_lock_init(task); task->itk_self = kport; task->itk_sself = ipc_port_make_send(kport); task->itk_space = space; if (parent == TASK_NULL) { task->itk_exception = IP_NULL; task->itk_bootstrap = IP_NULL; for (i = 0; i < TASK_PORT_REGISTER_MAX; i++) task->itk_registered[i] = IP_NULL; } else { itk_lock(parent); assert(parent->itk_self != IP_NULL); /* inherit registered ports */ for (i = 0; i < TASK_PORT_REGISTER_MAX; i++) task->itk_registered[i] = ipc_port_copy_send(parent->itk_registered[i]); /* inherit exception and bootstrap ports */ task->itk_exception = ipc_port_copy_send(parent->itk_exception); task->itk_bootstrap = ipc_port_copy_send(parent->itk_bootstrap); itk_unlock(parent); } } /* * Routine: ipc_task_enable * Purpose: * Enable a task for IPC access. * Conditions: * Nothing locked. */ void ipc_task_enable( task_t task) { ipc_port_t kport; itk_lock(task); kport = task->itk_self; if (kport != IP_NULL) ipc_kobject_set(kport, (ipc_kobject_t) task, IKOT_TASK); itk_unlock(task); } /* * Routine: ipc_task_disable * Purpose: * Disable IPC access to a task. * Conditions: * Nothing locked. */ void ipc_task_disable( task_t task) { ipc_port_t kport; itk_lock(task); kport = task->itk_self; if (kport != IP_NULL) ipc_kobject_set(kport, IKO_NULL, IKOT_NONE); itk_unlock(task); } /* * Routine: ipc_task_terminate * Purpose: * Clean up and destroy a task's IPC state. * Conditions: * Nothing locked. The task must be suspended. * (Or the current thread must be in the task.) */ void ipc_task_terminate( task_t task) { ipc_port_t kport; int i; itk_lock(task); kport = task->itk_self; if (kport == IP_NULL) { /* the task is already terminated (can this happen?) */ itk_unlock(task); return; } task->itk_self = IP_NULL; itk_unlock(task); /* release the naked send rights */ if (IP_VALID(task->itk_sself)) ipc_port_release_send(task->itk_sself); if (IP_VALID(task->itk_exception)) ipc_port_release_send(task->itk_exception); if (IP_VALID(task->itk_bootstrap)) ipc_port_release_send(task->itk_bootstrap); for (i = 0; i < TASK_PORT_REGISTER_MAX; i++) if (IP_VALID(task->itk_registered[i])) ipc_port_release_send(task->itk_registered[i]); /* destroy the space, leaving just a reference for it */ ipc_space_destroy(task->itk_space); /* destroy the kernel port */ ipc_port_dealloc_kernel(kport); } /* * Routine: ipc_thread_init * Purpose: * Initialize a thread's IPC state. * Conditions: * Nothing locked. */ void ipc_thread_init(thread_t thread) { ipc_port_t kport; kport = ipc_port_alloc_kernel(); if (kport == IP_NULL) panic("ipc_thread_init"); ipc_thread_links_init(thread); ipc_kmsg_queue_init(&thread->ith_messages); ith_lock_init(thread); thread->ith_self = kport; thread->ith_sself = ipc_port_make_send(kport); thread->ith_exception = IP_NULL; thread->ith_mig_reply = MACH_PORT_NULL; thread->ith_rpc_reply = IP_NULL; } /* * Routine: ipc_thread_enable * Purpose: * Enable a thread for IPC access. * Conditions: * Nothing locked. */ void ipc_thread_enable(thread_t thread) { ipc_port_t kport; ith_lock(thread); kport = thread->ith_self; if (kport != IP_NULL) ipc_kobject_set(kport, (ipc_kobject_t) thread, IKOT_THREAD); ith_unlock(thread); } /* * Routine: ipc_thread_disable * Purpose: * Disable IPC access to a thread. * Conditions: * Nothing locked. */ void ipc_thread_disable(thread_t thread) { ipc_port_t kport; ith_lock(thread); kport = thread->ith_self; if (kport != IP_NULL) ipc_kobject_set(kport, IKO_NULL, IKOT_NONE); ith_unlock(thread); } /* * Routine: ipc_thread_terminate * Purpose: * Clean up and destroy a thread's IPC state. * Conditions: * Nothing locked. The thread must be suspended. * (Or be the current thread.) */ void ipc_thread_terminate(thread_t thread) { ipc_port_t kport; ith_lock(thread); kport = thread->ith_self; if (kport == IP_NULL) { /* the thread is already terminated (can this happen?) */ ith_unlock(thread); return; } thread->ith_self = IP_NULL; ith_unlock(thread); assert(ipc_kmsg_queue_empty(&thread->ith_messages)); /* release the naked send rights */ if (IP_VALID(thread->ith_sself)) ipc_port_release_send(thread->ith_sself); if (IP_VALID(thread->ith_exception)) ipc_port_release_send(thread->ith_exception); /* destroy the kernel port */ ipc_port_dealloc_kernel(kport); } #if 0 /* * Routine: retrieve_task_self * Purpose: * Return a send right (possibly null/dead) * for the task's user-visible self port. * Conditions: * Nothing locked. */ ipc_port_t retrieve_task_self(task) task_t task; { ipc_port_t port; assert(task != TASK_NULL); itk_lock(task); if (task->itk_self != IP_NULL) port = ipc_port_copy_send(task->itk_sself); else port = IP_NULL; itk_unlock(task); return port; } /* * Routine: retrieve_thread_self * Purpose: * Return a send right (possibly null/dead) * for the thread's user-visible self port. * Conditions: * Nothing locked. */ ipc_port_t retrieve_thread_self(thread) thread_t thread; { ipc_port_t port; assert(thread != ITH_NULL); ith_lock(thread); if (thread->ith_self != IP_NULL) port = ipc_port_copy_send(thread->ith_sself); else port = IP_NULL; ith_unlock(thread); return port; } #endif /* 0 */ /* * Routine: retrieve_task_self_fast * Purpose: * Optimized version of retrieve_task_self, * that only works for the current task. * * Return a send right (possibly null/dead) * for the task's user-visible self port. * Conditions: * Nothing locked. */ ipc_port_t retrieve_task_self_fast( task_t task) { ipc_port_t port; assert(task == current_task()); itk_lock(task); assert(task->itk_self != IP_NULL); if ((port = task->itk_sself) == task->itk_self) { /* no interposing */ ip_lock(port); assert(ip_active(port)); ip_reference(port); port->ip_srights++; ip_unlock(port); } else port = ipc_port_copy_send(port); itk_unlock(task); return port; } /* * Routine: retrieve_thread_self_fast * Purpose: * Optimized version of retrieve_thread_self, * that only works for the current thread. * * Return a send right (possibly null/dead) * for the thread's user-visible self port. * Conditions: * Nothing locked. */ ipc_port_t retrieve_thread_self_fast(thread_t thread) { ipc_port_t port; assert(thread == current_thread()); ith_lock(thread); assert(thread->ith_self != IP_NULL); if ((port = thread->ith_sself) == thread->ith_self) { /* no interposing */ ip_lock(port); assert(ip_active(port)); ip_reference(port); port->ip_srights++; ip_unlock(port); } else port = ipc_port_copy_send(port); ith_unlock(thread); return port; } #if 0 /* * Routine: retrieve_task_exception * Purpose: * Return a send right (possibly null/dead) * for the task's exception port. * Conditions: * Nothing locked. */ ipc_port_t retrieve_task_exception(task) task_t task; { ipc_port_t port; assert(task != TASK_NULL); itk_lock(task); if (task->itk_self != IP_NULL) port = ipc_port_copy_send(task->itk_exception); else port = IP_NULL; itk_unlock(task); return port; } /* * Routine: retrieve_thread_exception * Purpose: * Return a send right (possibly null/dead) * for the thread's exception port. * Conditions: * Nothing locked. */ ipc_port_t retrieve_thread_exception(thread) thread_t thread; { ipc_port_t port; assert(thread != ITH_NULL); ith_lock(thread); if (thread->ith_self != IP_NULL) port = ipc_port_copy_send(thread->ith_exception); else port = IP_NULL; ith_unlock(thread); return port; } #endif /* 0 */ /* * Routine: mach_task_self [mach trap] * Purpose: * Give the caller send rights for his own task port. * Conditions: * Nothing locked. * Returns: * MACH_PORT_NULL if there are any resource failures * or other errors. */ mach_port_t mach_task_self(void) { task_t task = current_task(); ipc_port_t sright; sright = retrieve_task_self_fast(task); return ipc_port_copyout_send(sright, task->itk_space); } /* * Routine: mach_thread_self [mach trap] * Purpose: * Give the caller send rights for his own thread port. * Conditions: * Nothing locked. * Returns: * MACH_PORT_NULL if there are any resource failures * or other errors. */ mach_port_t mach_thread_self(void) { thread_t thread = current_thread(); task_t task = thread->task; ipc_port_t sright; sright = retrieve_thread_self_fast(thread); return ipc_port_copyout_send(sright, task->itk_space); } /* * Routine: mach_reply_port [mach trap] * Purpose: * Allocate a port for the caller. * Conditions: * Nothing locked. * Returns: * MACH_PORT_NULL if there are any resource failures * or other errors. */ mach_port_t mach_reply_port(void) { ipc_port_t port; mach_port_t name; kern_return_t kr; kr = ipc_port_alloc(current_task()->itk_space, &name, &port); if (kr == KERN_SUCCESS) ip_unlock(port); else name = MACH_PORT_NULL; return name; } /* * Routine: task_get_special_port [kernel call] * Purpose: * Clones a send right for one of the task's * special ports. * Conditions: * Nothing locked. * Returns: * KERN_SUCCESS Extracted a send right. * KERN_INVALID_ARGUMENT The task is null. * KERN_FAILURE The task/space is dead. * KERN_INVALID_ARGUMENT Invalid special port. */ kern_return_t task_get_special_port( task_t task, int which, ipc_port_t *portp) { ipc_port_t *whichp; ipc_port_t port; if (task == TASK_NULL) return KERN_INVALID_ARGUMENT; switch (which) { case TASK_KERNEL_PORT: whichp = &task->itk_sself; break; case TASK_EXCEPTION_PORT: whichp = &task->itk_exception; break; case TASK_BOOTSTRAP_PORT: whichp = &task->itk_bootstrap; break; default: return KERN_INVALID_ARGUMENT; } itk_lock(task); if (task->itk_self == IP_NULL) { itk_unlock(task); return KERN_FAILURE; } port = ipc_port_copy_send(*whichp); itk_unlock(task); *portp = port; return KERN_SUCCESS; } /* * Routine: task_set_special_port [kernel call] * Purpose: * Changes one of the task's special ports, * setting it to the supplied send right. * Conditions: * Nothing locked. If successful, consumes * the supplied send right. * Returns: * KERN_SUCCESS Changed the special port. * KERN_INVALID_ARGUMENT The task is null. * KERN_FAILURE The task/space is dead. * KERN_INVALID_ARGUMENT Invalid special port. */ kern_return_t task_set_special_port( task_t task, int which, const ipc_port_t port) { ipc_port_t *whichp; ipc_port_t old; if (task == TASK_NULL) return KERN_INVALID_ARGUMENT; switch (which) { case TASK_KERNEL_PORT: whichp = &task->itk_sself; break; case TASK_EXCEPTION_PORT: whichp = &task->itk_exception; break; case TASK_BOOTSTRAP_PORT: whichp = &task->itk_bootstrap; break; default: return KERN_INVALID_ARGUMENT; } itk_lock(task); if (task->itk_self == IP_NULL) { itk_unlock(task); return KERN_FAILURE; } old = *whichp; *whichp = port; itk_unlock(task); if (IP_VALID(old)) ipc_port_release_send(old); return KERN_SUCCESS; } /* * Routine: thread_get_special_port [kernel call] * Purpose: * Clones a send right for one of the thread's * special ports. * Conditions: * Nothing locked. * Returns: * KERN_SUCCESS Extracted a send right. * KERN_INVALID_ARGUMENT The thread is null. * KERN_FAILURE The thread is dead. * KERN_INVALID_ARGUMENT Invalid special port. */ kern_return_t thread_get_special_port( thread_t thread, int which, ipc_port_t *portp) { ipc_port_t *whichp; ipc_port_t port; if (thread == ITH_NULL) return KERN_INVALID_ARGUMENT; switch (which) { case THREAD_KERNEL_PORT: whichp = &thread->ith_sself; break; case THREAD_EXCEPTION_PORT: whichp = &thread->ith_exception; break; default: return KERN_INVALID_ARGUMENT; } ith_lock(thread); if (thread->ith_self == IP_NULL) { ith_unlock(thread); return KERN_FAILURE; } port = ipc_port_copy_send(*whichp); ith_unlock(thread); *portp = port; return KERN_SUCCESS; } /* * Routine: thread_set_special_port [kernel call] * Purpose: * Changes one of the thread's special ports, * setting it to the supplied send right. * Conditions: * Nothing locked. If successful, consumes * the supplied send right. * Returns: * KERN_SUCCESS Changed the special port. * KERN_INVALID_ARGUMENT The thread is null. * KERN_FAILURE The thread is dead. * KERN_INVALID_ARGUMENT Invalid special port. */ kern_return_t thread_set_special_port( thread_t thread, int which, ipc_port_t port) { ipc_port_t *whichp; ipc_port_t old; if (thread == ITH_NULL) return KERN_INVALID_ARGUMENT; switch (which) { case THREAD_KERNEL_PORT: whichp = &thread->ith_sself; break; case THREAD_EXCEPTION_PORT: whichp = &thread->ith_exception; break; default: return KERN_INVALID_ARGUMENT; } ith_lock(thread); if (thread->ith_self == IP_NULL) { ith_unlock(thread); return KERN_FAILURE; } old = *whichp; *whichp = port; ith_unlock(thread); if (IP_VALID(old)) ipc_port_release_send(old); return KERN_SUCCESS; } /* * Routine: mach_ports_register [kernel call] * Purpose: * Stash a handful of port send rights in the task. * Child tasks will inherit these rights, but they * must use mach_ports_lookup to acquire them. * * The rights are supplied in a (wired) kalloc'd segment. * Rights which aren't supplied are assumed to be null. * Conditions: * Nothing locked. If successful, consumes * the supplied rights and memory. * Returns: * KERN_SUCCESS Stashed the port rights. * KERN_INVALID_ARGUMENT The task is null. * KERN_INVALID_ARGUMENT The task is dead. * KERN_INVALID_ARGUMENT Too many port rights supplied. */ kern_return_t mach_ports_register( task_t task, mach_port_array_t memory, mach_msg_type_number_t portsCnt) { ipc_port_t ports[TASK_PORT_REGISTER_MAX]; unsigned i; if ((task == TASK_NULL) || (portsCnt > TASK_PORT_REGISTER_MAX)) return KERN_INVALID_ARGUMENT; /* * Pad the port rights with nulls. */ for (i = 0; i < portsCnt; i++) ports[i] = (ipc_port_t)memory[i]; for (; i < TASK_PORT_REGISTER_MAX; i++) ports[i] = IP_NULL; itk_lock(task); if (task->itk_self == IP_NULL) { itk_unlock(task); return KERN_INVALID_ARGUMENT; } /* * Replace the old send rights with the new. * Release the old rights after unlocking. */ for (i = 0; i < TASK_PORT_REGISTER_MAX; i++) { ipc_port_t old; old = task->itk_registered[i]; task->itk_registered[i] = ports[i]; ports[i] = old; } itk_unlock(task); for (i = 0; i < TASK_PORT_REGISTER_MAX; i++) if (IP_VALID(ports[i])) ipc_port_release_send(ports[i]); /* * Now that the operation is known to be successful, * we can free the memory. */ if (portsCnt != 0) kfree((vm_offset_t) memory, (vm_size_t) (portsCnt * sizeof(mach_port_t))); return KERN_SUCCESS; } /* * Routine: mach_ports_lookup [kernel call] * Purpose: * Retrieves (clones) the stashed port send rights. * Conditions: * Nothing locked. If successful, the caller gets * rights and memory. * Returns: * KERN_SUCCESS Retrieved the send rights. * KERN_INVALID_ARGUMENT The task is null. * KERN_INVALID_ARGUMENT The task is dead. * KERN_RESOURCE_SHORTAGE Couldn't allocate memory. */ kern_return_t mach_ports_lookup( task_t task, ipc_port_t **portsp, mach_msg_type_number_t *portsCnt) { vm_offset_t memory; vm_size_t size; ipc_port_t *ports; int i; if (task == TASK_NULL) return KERN_INVALID_ARGUMENT; size = (vm_size_t) (TASK_PORT_REGISTER_MAX * sizeof(ipc_port_t)); memory = kalloc(size); if (memory == 0) return KERN_RESOURCE_SHORTAGE; itk_lock(task); if (task->itk_self == IP_NULL) { itk_unlock(task); kfree(memory, size); return KERN_INVALID_ARGUMENT; } ports = (ipc_port_t *) memory; /* * Clone port rights. Because kalloc'd memory * is wired, we won't fault while holding the task lock. */ for (i = 0; i < TASK_PORT_REGISTER_MAX; i++) ports[i] = ipc_port_copy_send(task->itk_registered[i]); itk_unlock(task); *portsp = ports; *portsCnt = TASK_PORT_REGISTER_MAX; return KERN_SUCCESS; } /* * Routine: convert_port_to_task * Purpose: * Convert from a port to a task. * Doesn't consume the port ref; produces a task ref, * which may be null. * Conditions: * Nothing locked. */ task_t convert_port_to_task( ipc_port_t port) { task_t task = TASK_NULL; if (IP_VALID(port)) { ip_lock(port); if (ip_active(port) && (ip_kotype(port) == IKOT_TASK)) { task = (task_t) port->ip_kobject; task_reference(task); } ip_unlock(port); } return task; } /* * Routine: convert_port_to_space * Purpose: * Convert from a port to a space. * Doesn't consume the port ref; produces a space ref, * which may be null. * Conditions: * Nothing locked. */ ipc_space_t convert_port_to_space( ipc_port_t port) { ipc_space_t space = IS_NULL; if (IP_VALID(port)) { ip_lock(port); if (ip_active(port) && (ip_kotype(port) == IKOT_TASK)) { space = ((task_t) port->ip_kobject)->itk_space; is_reference(space); } ip_unlock(port); } return space; } /* * Routine: convert_port_to_map * Purpose: * Convert from a port to a map. * Doesn't consume the port ref; produces a map ref, * which may be null. * Conditions: * Nothing locked. */ vm_map_t convert_port_to_map(ipc_port_t port) { vm_map_t map = VM_MAP_NULL; if (IP_VALID(port)) { ip_lock(port); if (ip_active(port) && (ip_kotype(port) == IKOT_TASK)) { map = ((task_t) port->ip_kobject)->map; vm_map_reference(map); } ip_unlock(port); } return map; } /* * Routine: convert_port_to_thread * Purpose: * Convert from a port to a thread. * Doesn't consume the port ref; produces a thread ref, * which may be null. * Conditions: * Nothing locked. */ thread_t convert_port_to_thread(ipc_port_t port) { thread_t thread = THREAD_NULL; if (IP_VALID(port)) { ip_lock(port); if (ip_active(port) && (ip_kotype(port) == IKOT_THREAD)) { thread = (thread_t) port->ip_kobject; thread_reference(thread); } ip_unlock(port); } return thread; } /* * Routine: convert_task_to_port * Purpose: * Convert from a task to a port. * Consumes a task ref; produces a naked send right * which may be invalid. * Conditions: * Nothing locked. */ ipc_port_t convert_task_to_port(task_t task) { ipc_port_t port; itk_lock(task); if (task->itk_self != IP_NULL) port = ipc_port_make_send(task->itk_self); else port = IP_NULL; itk_unlock(task); task_deallocate(task); return port; } /* * Routine: convert_thread_to_port * Purpose: * Convert from a thread to a port. * Consumes a thread ref; produces a naked send right * which may be invalid. * Conditions: * Nothing locked. */ ipc_port_t convert_thread_to_port(thread_t thread) { ipc_port_t port; ith_lock(thread); if (thread->ith_self != IP_NULL) port = ipc_port_make_send(thread->ith_self); else port = IP_NULL; ith_unlock(thread); thread_deallocate(thread); return port; } /* * Routine: space_deallocate * Purpose: * Deallocate a space ref produced by convert_port_to_space. * Conditions: * Nothing locked. */ void space_deallocate(ipc_space_t space) { if (space != IS_NULL) is_release(space); }