boolean_t mach_msg_interrupt(thread_t thread) { ipc_mqueue_t mqueue; assert((thread->swap_func == (void (*)()) mach_msg_continue) || (thread->swap_func == (void (*)()) mach_msg_receive_continue)); mqueue = thread->ith_mqueue; imq_lock(mqueue); if (thread->ith_state != MACH_RCV_IN_PROGRESS) { /* * The thread is no longer waiting for a message. * It may have a message sitting in ith_kmsg. * We can't clean this up. */ imq_unlock(mqueue); return FALSE; } ipc_thread_rmqueue(&mqueue->imq_threads, thread); imq_unlock(mqueue); ipc_object_release(thread->ith_object); thread_set_syscall_return(thread, MACH_RCV_INTERRUPTED); thread->swap_func = thread_exception_return; return TRUE; }
void ipc_pset_add( ipc_pset_t pset, ipc_port_t port) { assert(ips_active(pset)); assert(ip_active(port)); assert(port->ip_pset == IPS_NULL); port->ip_pset = pset; port->ip_cur_target = &pset->ips_target; ips_reference(pset); imq_lock(&port->ip_messages); imq_lock(&pset->ips_messages); /* move messages from port's queue to the port set's queue */ ipc_mqueue_move(&pset->ips_messages, &port->ip_messages, port); imq_unlock(&pset->ips_messages); assert(ipc_kmsg_queue_empty(&port->ip_messages.imq_messages)); /* wake up threads waiting to receive from the port */ ipc_mqueue_changed(&port->ip_messages, MACH_RCV_PORT_CHANGED); assert(ipc_thread_queue_empty(&port->ip_messages.imq_threads)); imq_unlock(&port->ip_messages); }
void ipc_pset_destroy( ipc_pset_t pset) { spl_t s; assert(ips_active(pset)); pset->ips_object.io_bits &= ~IO_BITS_ACTIVE; /* * remove all the member message queues * AND remove this message queue from any containing sets */ ipc_mqueue_remove_all(&pset->ips_messages); /* * Set all waiters on the portset running to * discover the change. */ s = splsched(); imq_lock(&pset->ips_messages); ipc_mqueue_changed(&pset->ips_messages); imq_unlock(&pset->ips_messages); splx(s); ipc_mqueue_deinit(&pset->ips_messages); ips_unlock(pset); ips_release(pset); /* consume the ref our caller gave us */ }
void ipc_pset_destroy( ipc_pset_t pset) { spl_t s; assert(ips_active(pset)); pset->ips_object.io_bits &= ~IO_BITS_ACTIVE; /* * remove all the member message queues */ ipc_mqueue_remove_all(&pset->ips_messages); s = splsched(); imq_lock(&pset->ips_messages); ipc_mqueue_changed(&pset->ips_messages); imq_unlock(&pset->ips_messages); splx(s); /* XXXX Perhaps ought to verify ips_thread_pool is empty */ ips_release(pset); /* consume the ref our caller gave us */ ips_check_unlock(pset); }
/* * Routine: ipc_mqueue_peek * Purpose: * Peek at a message queue to see if it has any messages * (in it or contained message queues for a set). * * Conditions: * Locks may be held by callers, so this routine cannot block. * Caller holds reference on the message queue. */ unsigned ipc_mqueue_peek(ipc_mqueue_t mq) { wait_queue_link_t wql; queue_t q; spl_t s; if (!imq_is_set(mq)) return (ipc_kmsg_queue_first(&mq->imq_messages) != IKM_NULL); /* * Don't block trying to get the lock. */ s = splsched(); imq_lock(mq); /* * peek at the contained port message queues, return as soon as * we spot a message on one of the message queues linked on the * prepost list. */ q = &mq->imq_preposts; queue_iterate(q, wql, wait_queue_link_t, wql_preposts) { ipc_mqueue_t port_mq = (ipc_mqueue_t)wql->wql_queue; ipc_kmsg_queue_t kmsgs = &port_mq->imq_messages; if (ipc_kmsg_queue_first(kmsgs) != IKM_NULL) { imq_unlock(mq); splx(s); return 1; } }
/* Called when an ACKMESSAGE packet is received. <name> indicates * the flipc name of the port holding the messages to be acknowledged. * <msg_count> indicates the number of messages being acked for this node:port. */ static void flipc_cmd_ack(flipc_ack_msg_t fmsg, mach_node_t node __unused, uint32_t flags __unused) { unsigned int msg_count = fmsg->msg_count; thread_t thread = current_thread(); boolean_t kick = FALSE; flipc_port_t fport = (flipc_port_t)mnl_obj_lookup(fmsg->mnl.object); ipc_port_t lport = fport->lport; ip_lock(lport); ipc_mqueue_t lport_mq = &lport->ip_messages; imq_lock(lport_mq); assert(fport->peek_count >= msg_count); // Can't ack what we haven't peeked! while (msg_count--) { ipc_mqueue_select_on_thread(lport_mq, IMQ_NULL, 0, 0, thread); fport->peek_count--; kick |= ipc_kmsg_delayed_destroy(thread->ith_kmsg); } imq_unlock(lport_mq); ip_unlock(lport); if (kick) ipc_kmsg_reap_delayed(); }
void ipc_port_clear_receiver( ipc_port_t port, queue_t links) { spl_t s; assert(ip_active(port)); /* * pull ourselves from any sets. */ if (port->ip_pset_count != 0) { ipc_pset_remove_from_all(port, links); assert(port->ip_pset_count == 0); } /* * Send anyone waiting on the port's queue directly away. * Also clear the mscount and seqno. */ s = splsched(); imq_lock(&port->ip_messages); ipc_mqueue_changed(&port->ip_messages); ipc_port_set_mscount(port, 0); port->ip_messages.imq_seqno = 0; port->ip_context = port->ip_guarded = port->ip_strict_guard = 0; imq_unlock(&port->ip_messages); splx(s); }
/* * Routine: ipc_mqueue_peek * Purpose: * Peek at a (non-set) message queue to see if it has a message * matching the sequence number provided (if zero, then the * first message in the queue) and return vital info about the * message. * * Conditions: * Locks may be held by callers, so this routine cannot block. * Caller holds reference on the message queue. */ unsigned ipc_mqueue_peek(ipc_mqueue_t mq, mach_port_seqno_t *seqnop, mach_msg_size_t *msg_sizep, mach_msg_id_t *msg_idp, mach_msg_max_trailer_t *msg_trailerp) { ipc_kmsg_queue_t kmsgq; ipc_kmsg_t kmsg; mach_port_seqno_t seqno, msgoff; int res = 0; spl_t s; assert(!imq_is_set(mq)); s = splsched(); imq_lock(mq); seqno = (seqnop != NULL) ? seqno = *seqnop : 0; if (seqno == 0) { seqno = mq->imq_seqno; msgoff = 0; } else if (seqno >= mq->imq_seqno && seqno < mq->imq_seqno + mq->imq_msgcount) { msgoff = seqno - mq->imq_seqno; } else goto out; /* look for the message that would match that seqno */ kmsgq = &mq->imq_messages; kmsg = ipc_kmsg_queue_first(kmsgq); while (msgoff-- && kmsg != IKM_NULL) { kmsg = ipc_kmsg_queue_next(kmsgq, kmsg); } if (kmsg == IKM_NULL) goto out; /* found one - return the requested info */ if (seqnop != NULL) *seqnop = seqno; if (msg_sizep != NULL) *msg_sizep = kmsg->ikm_header->msgh_size; if (msg_idp != NULL) *msg_idp = kmsg->ikm_header->msgh_id; if (msg_trailerp != NULL) memcpy(msg_trailerp, (mach_msg_max_trailer_t *)((vm_offset_t)kmsg->ikm_header + round_msg(kmsg->ikm_header->msgh_size)), sizeof(mach_msg_max_trailer_t)); res = 1; out: imq_unlock(mq); splx(s); return res; }
void ipc_pset_remove( ipc_pset_t pset, ipc_port_t port) { assert(ip_active(port)); assert(port->ip_pset == pset); port->ip_pset = IPS_NULL; port->ip_cur_target = &port->ip_target; ips_release(pset); imq_lock(&port->ip_messages); imq_lock(&pset->ips_messages); /* move messages from port set's queue to the port's queue */ ipc_mqueue_move(&port->ip_messages, &pset->ips_messages, port); imq_unlock(&pset->ips_messages); imq_unlock(&port->ip_messages); }
void ipc_pset_destroy( ipc_pset_t pset) { assert(ips_active(pset)); pset->ips_object.io_bits &= ~IO_BITS_ACTIVE; imq_lock(&pset->ips_messages); ipc_mqueue_changed(&pset->ips_messages, MACH_RCV_PORT_DIED); imq_unlock(&pset->ips_messages); /* Common destruction for the IPC target. */ ipc_target_terminate(&pset->ips_target); ips_release(pset); /* consume the ref our caller gave us */ ips_check_unlock(pset); }
void ipc_pset_destroy( ipc_pset_t pset) { spl_t s; queue_head_t link_data; queue_t links = &link_data; wait_queue_link_t wql; queue_init(links); assert(ips_active(pset)); pset->ips_object.io_bits &= ~IO_BITS_ACTIVE; /* * remove all the member message queues */ ipc_mqueue_remove_all(&pset->ips_messages, links); /* * Set all waiters on the portset running to * discover the change. */ s = splsched(); imq_lock(&pset->ips_messages); ipc_mqueue_changed(&pset->ips_messages); imq_unlock(&pset->ips_messages); splx(s); ips_unlock(pset); ips_release(pset); /* consume the ref our caller gave us */ while(!queue_empty(links)) { wql = (wait_queue_link_t) dequeue(links); wait_queue_link_free(wql); } }
wait_result_t ipc_mqueue_receive_on_thread( ipc_mqueue_t mqueue, mach_msg_option_t option, mach_msg_size_t max_size, mach_msg_timeout_t rcv_timeout, int interruptible, thread_t thread) { ipc_kmsg_queue_t kmsgs; wait_result_t wresult; uint64_t deadline; spl_t s; s = splsched(); imq_lock(mqueue); if (imq_is_set(mqueue)) { queue_t q; q = &mqueue->imq_preposts; /* * If we are waiting on a portset mqueue, we need to see if * any of the member ports have work for us. Ports that * have (or recently had) messages will be linked in the * prepost queue for the portset. By holding the portset's * mqueue lock during the search, we tie up any attempts by * mqueue_deliver or portset membership changes that may * cross our path. */ search_set: while(!queue_empty(q)) { wait_queue_link_t wql; ipc_mqueue_t port_mq; queue_remove_first(q, wql, wait_queue_link_t, wql_preposts); assert(!wql_is_preposted(wql)); /* * This is a lock order violation, so we have to do it * "softly," putting the link back on the prepost list * if it fails (at the tail is fine since the order of * handling messages from different sources in a set is * not guaranteed and we'd like to skip to the next source * if one is available). */ port_mq = (ipc_mqueue_t)wql->wql_queue; if (!imq_lock_try(port_mq)) { queue_enter(q, wql, wait_queue_link_t, wql_preposts); imq_unlock(mqueue); splx(s); mutex_pause(0); s = splsched(); imq_lock(mqueue); goto search_set; /* start again at beginning - SMP */ } /* * If there are no messages on this queue, just skip it * (we already removed the link from the set's prepost queue). */ kmsgs = &port_mq->imq_messages; if (ipc_kmsg_queue_first(kmsgs) == IKM_NULL) { imq_unlock(port_mq); continue; } /* * There are messages, so reinsert the link back * at the tail of the preposted queue (for fairness) * while we still have the portset mqueue locked. */ queue_enter(q, wql, wait_queue_link_t, wql_preposts); imq_unlock(mqueue); /* * Continue on to handling the message with just * the port mqueue locked. */ ipc_mqueue_select_on_thread(port_mq, option, max_size, thread); imq_unlock(port_mq); splx(s); return THREAD_NOT_WAITING; } } else { /* * Receive on a single port. Just try to get the messages. */ kmsgs = &mqueue->imq_messages; if (ipc_kmsg_queue_first(kmsgs) != IKM_NULL) { ipc_mqueue_select_on_thread(mqueue, option, max_size, thread); imq_unlock(mqueue); splx(s); return THREAD_NOT_WAITING; } } /* * Looks like we'll have to block. The mqueue we will * block on (whether the set's or the local port's) is * still locked. */ if (option & MACH_RCV_TIMEOUT) { if (rcv_timeout == 0) { imq_unlock(mqueue); splx(s); thread->ith_state = MACH_RCV_TIMED_OUT; return THREAD_NOT_WAITING; } } thread_lock(thread); thread->ith_state = MACH_RCV_IN_PROGRESS; thread->ith_option = option; thread->ith_msize = max_size; if (option & MACH_RCV_TIMEOUT) clock_interval_to_deadline(rcv_timeout, 1000*NSEC_PER_USEC, &deadline); else deadline = 0; wresult = wait_queue_assert_wait64_locked(&mqueue->imq_wait_queue, IPC_MQUEUE_RECEIVE, interruptible, TIMEOUT_URGENCY_USER_NORMAL, deadline, 0, thread); /* preposts should be detected above, not here */ if (wresult == THREAD_AWAKENED) panic("ipc_mqueue_receive_on_thread: sleep walking"); thread_unlock(thread); imq_unlock(mqueue); splx(s); return wresult; }
mach_msg_return_t ipc_mqueue_receive( ipc_mqueue_t mqueue, mach_msg_option_t option, mach_msg_size_t max_size, mach_msg_timeout_t time_out, boolean_t resume, void (*continuation)(void), ipc_kmsg_t *kmsgp, mach_port_seqno_t *seqnop) { ipc_port_t port; ipc_kmsg_t kmsg; mach_port_seqno_t seqno; { ipc_kmsg_queue_t kmsgs = &mqueue->imq_messages; ipc_thread_t self = current_thread(); if (resume) goto after_thread_block; for (;;) { kmsg = ipc_kmsg_queue_first(kmsgs); if (kmsg != IKM_NULL) { /* check space requirements */ if (kmsg->ikm_header.msgh_size > max_size) { * (mach_msg_size_t *) kmsgp = kmsg->ikm_header.msgh_size; imq_unlock(mqueue); return MACH_RCV_TOO_LARGE; } ipc_kmsg_rmqueue_first_macro(kmsgs, kmsg); port = (ipc_port_t) kmsg->ikm_header.msgh_remote_port; seqno = port->ip_seqno++; break; } /* must block waiting for a message */ if (option & MACH_RCV_TIMEOUT) { if (time_out == 0) { imq_unlock(mqueue); return MACH_RCV_TIMED_OUT; } thread_will_wait_with_timeout(self, time_out); } else thread_will_wait(self); ipc_thread_enqueue_macro(&mqueue->imq_threads, self); self->ith_state = MACH_RCV_IN_PROGRESS; self->ith_msize = max_size; imq_unlock(mqueue); if (continuation != (void (*)(void)) 0) { counter(c_ipc_mqueue_receive_block_user++); } else { counter(c_ipc_mqueue_receive_block_kernel++); } thread_block(continuation); after_thread_block: imq_lock(mqueue); /* why did we wake up? */ if (self->ith_state == MACH_MSG_SUCCESS) { /* pick up the message that was handed to us */ kmsg = self->ith_kmsg; seqno = self->ith_seqno; port = (ipc_port_t) kmsg->ikm_header.msgh_remote_port; break; } switch (self->ith_state) { case MACH_RCV_TOO_LARGE: /* pick up size of the too-large message */ * (mach_msg_size_t *) kmsgp = self->ith_msize; /* fall-through */ case MACH_RCV_PORT_DIED: case MACH_RCV_PORT_CHANGED: /* something bad happened to the port/set */ imq_unlock(mqueue); return self->ith_state; case MACH_RCV_IN_PROGRESS: /* * Awakened for other than IPC completion. * Remove ourselves from the waiting queue, * then check the wakeup cause. */ ipc_thread_rmqueue(&mqueue->imq_threads, self); switch (self->ith_wait_result) { case THREAD_INTERRUPTED: /* receive was interrupted - give up */ imq_unlock(mqueue); return MACH_RCV_INTERRUPTED; case THREAD_TIMED_OUT: /* timeout expired */ assert(option & MACH_RCV_TIMEOUT); time_out = 0; break; case THREAD_RESTART: default: #if MACH_ASSERT assert(!"ipc_mqueue_receive"); #else panic("ipc_mqueue_receive"); #endif } break; default: #if MACH_ASSERT assert(!"ipc_mqueue_receive: strange ith_state"); #else panic("ipc_mqueue_receive: strange ith_state"); #endif } } /* we have a kmsg; unlock the msg queue */ imq_unlock(mqueue); assert(kmsg->ikm_header.msgh_size <= max_size); } { ipc_marequest_t marequest; marequest = kmsg->ikm_marequest; if (marequest != IMAR_NULL) { ipc_marequest_destroy(marequest); kmsg->ikm_marequest = IMAR_NULL; } assert((kmsg->ikm_header.msgh_bits & MACH_MSGH_BITS_CIRCULAR) == 0); assert(port == (ipc_port_t) kmsg->ikm_header.msgh_remote_port); ip_lock(port); if (ip_active(port)) { ipc_thread_queue_t senders; ipc_thread_t sender; assert(port->ip_msgcount > 0); port->ip_msgcount--; senders = &port->ip_blocked; sender = ipc_thread_queue_first(senders); if ((sender != ITH_NULL) && (port->ip_msgcount < port->ip_qlimit)) { ipc_thread_rmqueue(senders, sender); sender->ith_state = MACH_MSG_SUCCESS; thread_go(sender); } } ip_unlock(port); } current_task()->messages_received++; *kmsgp = kmsg; *seqnop = seqno; return MACH_MSG_SUCCESS; }
/* * Routine: ipc_mqueue_add * Purpose: * Associate the portset's mqueue with the port's mqueue. * This has to be done so that posting the port will wakeup * a portset waiter. If there are waiters on the portset * mqueue and messages on the port mqueue, try to match them * up now. * Conditions: * May block. */ kern_return_t ipc_mqueue_add( ipc_mqueue_t port_mqueue, ipc_mqueue_t set_mqueue, wait_queue_link_t wql) { wait_queue_t port_waitq = &port_mqueue->imq_wait_queue; wait_queue_set_t set_waitq = &set_mqueue->imq_set_queue; ipc_kmsg_queue_t kmsgq; ipc_kmsg_t kmsg, next; kern_return_t kr; spl_t s; kr = wait_queue_link_noalloc(port_waitq, set_waitq, wql); if (kr != KERN_SUCCESS) return kr; /* * Now that the set has been added to the port, there may be * messages queued on the port and threads waiting on the set * waitq. Lets get them together. */ s = splsched(); imq_lock(port_mqueue); kmsgq = &port_mqueue->imq_messages; for (kmsg = ipc_kmsg_queue_first(kmsgq); kmsg != IKM_NULL; kmsg = next) { next = ipc_kmsg_queue_next(kmsgq, kmsg); for (;;) { thread_t th; mach_msg_size_t msize; th = wait_queue_wakeup64_identity_locked( port_waitq, IPC_MQUEUE_RECEIVE, THREAD_AWAKENED, FALSE); /* waitq/mqueue still locked, thread locked */ if (th == THREAD_NULL) goto leave; /* * If the receiver waited with a facility not directly * related to Mach messaging, then it isn't prepared to get * handed the message directly. Just set it running, and * go look for another thread that can. */ if (th->ith_state != MACH_RCV_IN_PROGRESS) { thread_unlock(th); continue; } /* * Found a receiver. see if they can handle the message * correctly (the message is not too large for them, or * they didn't care to be informed that the message was * too large). If they can't handle it, take them off * the list and let them go back and figure it out and * just move onto the next. */ msize = ipc_kmsg_copyout_size(kmsg, th->map); if (th->ith_msize < (msize + REQUESTED_TRAILER_SIZE(thread_is_64bit(th), th->ith_option))) { th->ith_state = MACH_RCV_TOO_LARGE; th->ith_msize = msize; if (th->ith_option & MACH_RCV_LARGE) { /* * let him go without message */ th->ith_receiver_name = port_mqueue->imq_receiver_name; th->ith_kmsg = IKM_NULL; th->ith_seqno = 0; thread_unlock(th); continue; /* find another thread */ } } else { th->ith_state = MACH_MSG_SUCCESS; } /* * This thread is going to take this message, * so give it to him. */ ipc_kmsg_rmqueue(kmsgq, kmsg); ipc_mqueue_release_msgcount(port_mqueue); th->ith_kmsg = kmsg; th->ith_seqno = port_mqueue->imq_seqno++; thread_unlock(th); break; /* go to next message */ } } leave: imq_unlock(port_mqueue); splx(s); return KERN_SUCCESS; }
void exception_raise( ipc_port_t dest_port, ipc_port_t thread_port, ipc_port_t task_port, integer_t _exception, integer_t code, integer_t subcode) { ipc_thread_t self = current_thread(); ipc_thread_t receiver; ipc_port_t reply_port; ipc_mqueue_t dest_mqueue; ipc_mqueue_t reply_mqueue; ipc_kmsg_t kmsg; mach_msg_return_t mr; assert(IP_VALID(dest_port)); /* * We will eventually need a message buffer. * Grab the buffer now, while nothing is locked. * This buffer will get handed to the exception server, * and it will give the buffer back with its reply. */ kmsg = ikm_cache(); if (kmsg != IKM_NULL) { ikm_cache() = IKM_NULL; ikm_check_initialized(kmsg, IKM_SAVED_KMSG_SIZE); } else { kmsg = ikm_alloc(IKM_SAVED_MSG_SIZE); if (kmsg == IKM_NULL) panic("exception_raise"); ikm_init(kmsg, IKM_SAVED_MSG_SIZE); } /* * We need a reply port for the RPC. * Check first for a cached port. */ ith_lock(self); assert(self->ith_self != IP_NULL); reply_port = self->ith_rpc_reply; if (reply_port == IP_NULL) { ith_unlock(self); reply_port = ipc_port_alloc_reply(); ith_lock(self); if ((reply_port == IP_NULL) || (self->ith_rpc_reply != IP_NULL)) panic("exception_raise"); self->ith_rpc_reply = reply_port; } ip_lock(reply_port); assert(ip_active(reply_port)); ith_unlock(self); /* * Make a naked send-once right for the reply port, * to hand to the exception server. * Make an extra reference for the reply port, * to receive on. This protects us against * mach_msg_abort_rpc. */ reply_port->ip_sorights++; ip_reference(reply_port); ip_reference(reply_port); self->ith_port = reply_port; reply_mqueue = &reply_port->ip_messages; imq_lock(reply_mqueue); assert(ipc_kmsg_queue_empty(&reply_mqueue->imq_messages)); ip_unlock(reply_port); /* * Make sure we can queue to the destination port. */ if (!ip_lock_try(dest_port)) { imq_unlock(reply_mqueue); goto slow_exception_raise; } if (!ip_active(dest_port) || (dest_port->ip_receiver == ipc_space_kernel)) { imq_unlock(reply_mqueue); ip_unlock(dest_port); goto slow_exception_raise; } /* * Find the destination message queue. */ { ipc_pset_t dest_pset; dest_pset = dest_port->ip_pset; if (dest_pset == IPS_NULL) dest_mqueue = &dest_port->ip_messages; else dest_mqueue = &dest_pset->ips_messages; } if (!imq_lock_try(dest_mqueue)) { imq_unlock(reply_mqueue); ip_unlock(dest_port); goto slow_exception_raise; } /* * Safe to unlock dest_port, because we hold * dest_mqueue locked. We never bother changing * dest_port->ip_msgcount. */ ip_unlock(dest_port); receiver = ipc_thread_queue_first(&dest_mqueue->imq_threads); if ((receiver == ITH_NULL) || !((receiver->swap_func == (void (*)()) mach_msg_continue) || ((receiver->swap_func == (void (*)()) mach_msg_receive_continue) && (sizeof(struct mach_exception) <= receiver->ith_msize) && ((receiver->ith_option & MACH_RCV_NOTIFY) == 0))) || !thread_handoff(self, exception_raise_continue, receiver)) { imq_unlock(reply_mqueue); imq_unlock(dest_mqueue); goto slow_exception_raise; } counter(c_exception_raise_block++); assert(current_thread() == receiver); /* * We need to finish preparing self for its * time asleep in reply_mqueue. self is left * holding the extra ref for reply_port. */ ipc_thread_enqueue_macro(&reply_mqueue->imq_threads, self); self->ith_state = MACH_RCV_IN_PROGRESS; self->ith_msize = MACH_MSG_SIZE_MAX; imq_unlock(reply_mqueue); /* * Finish extracting receiver from dest_mqueue. */ ipc_thread_rmqueue_first_macro( &dest_mqueue->imq_threads, receiver); imq_unlock(dest_mqueue); /* * Release the receiver's reference for his object. */ { ipc_object_t object = receiver->ith_object; io_lock(object); io_release(object); io_check_unlock(object); } { struct mach_exception *exc = (struct mach_exception *) &kmsg->ikm_header; ipc_space_t space = receiver->task->itk_space; /* * We are running as the receiver now. We hold * the following resources, which must be consumed: * kmsg, send-once right for reply_port * send rights for dest_port, thread_port, task_port * Synthesize a kmsg for copyout to the receiver. */ exc->Head.msgh_bits = (MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PORT_SEND) | MACH_MSGH_BITS_COMPLEX); exc->Head.msgh_size = sizeof *exc; /* exc->Head.msgh_remote_port later */ /* exc->Head.msgh_local_port later */ exc->Head.msgh_seqno = 0; exc->Head.msgh_id = MACH_EXCEPTION_ID; exc->threadType = exc_port_proto; /* exc->thread later */ exc->taskType = exc_port_proto; /* exc->task later */ exc->exceptionType = exc_code_proto; exc->exception = _exception; exc->codeType = exc_code_proto; exc->code = code; exc->subcodeType = exc_code_proto; exc->subcode = subcode; /* * Check that the receiver can handle the message. */ if (receiver->ith_rcv_size < sizeof(struct mach_exception)) { /* * ipc_kmsg_destroy is a handy way to consume * the resources we hold, but it requires setup. */ exc->Head.msgh_bits = (MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND, MACH_MSG_TYPE_PORT_SEND_ONCE) | MACH_MSGH_BITS_COMPLEX); exc->Head.msgh_remote_port = (mach_port_t) dest_port; exc->Head.msgh_local_port = (mach_port_t) reply_port; exc->thread = (mach_port_t) thread_port; exc->task = (mach_port_t) task_port; ipc_kmsg_destroy(kmsg); thread_syscall_return(MACH_RCV_TOO_LARGE); /*NOTREACHED*/ } is_write_lock(space); assert(space->is_active); /* * To do an atomic copyout, need simultaneous * locks on both ports and the space. */ ip_lock(dest_port); if (!ip_active(dest_port) || !ip_lock_try(reply_port)) { abort_copyout: ip_unlock(dest_port); is_write_unlock(space); /* * Oh well, we have to do the header the slow way. * First make it look like it's in-transit. */ exc->Head.msgh_bits = (MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND, MACH_MSG_TYPE_PORT_SEND_ONCE) | MACH_MSGH_BITS_COMPLEX); exc->Head.msgh_remote_port = (mach_port_t) dest_port; exc->Head.msgh_local_port = (mach_port_t) reply_port; mr = ipc_kmsg_copyout_header(&exc->Head, space, MACH_PORT_NULL); if (mr == MACH_MSG_SUCCESS) goto copyout_body; /* * Ack! Prepare for ipc_kmsg_copyout_dest. * It will consume thread_port and task_port. */ exc->thread = (mach_port_t) thread_port; exc->task = (mach_port_t) task_port; ipc_kmsg_copyout_dest(kmsg, space); (void) ipc_kmsg_put(receiver->ith_msg, kmsg, sizeof(mach_msg_header_t)); thread_syscall_return(mr); /*NOTREACHED*/ } if (!ip_active(reply_port)) { ip_unlock(reply_port); goto abort_copyout; } assert(reply_port->ip_sorights > 0); ip_unlock(reply_port); { kern_return_t kr; ipc_entry_t entry; kr = ipc_entry_get (space, &exc->Head.msgh_remote_port, &entry); if (kr) goto abort_copyout; { mach_port_gen_t gen; assert((entry->ie_bits &~ IE_BITS_GEN_MASK) == 0); gen = entry->ie_bits + IE_BITS_GEN_ONE; /* optimized ipc_right_copyout */ entry->ie_bits = gen | (MACH_PORT_TYPE_SEND_ONCE | 1); } entry->ie_object = (ipc_object_t) reply_port; is_write_unlock(space); } /* optimized ipc_object_copyout_dest */ assert(dest_port->ip_srights > 0); ip_release(dest_port); exc->Head.msgh_local_port = ((dest_port->ip_receiver == space) ? dest_port->ip_receiver_name : MACH_PORT_NULL); if ((--dest_port->ip_srights == 0) && (dest_port->ip_nsrequest != IP_NULL)) { ipc_port_t nsrequest; mach_port_mscount_t mscount; /* a rather rare case */ nsrequest = dest_port->ip_nsrequest; mscount = dest_port->ip_mscount; dest_port->ip_nsrequest = IP_NULL; ip_unlock(dest_port); ipc_notify_no_senders(nsrequest, mscount); } else ip_unlock(dest_port); copyout_body: /* * Optimized version of ipc_kmsg_copyout_body, * to handle the two ports in the body. */ mr = (ipc_kmsg_copyout_object(space, (ipc_object_t) thread_port, MACH_MSG_TYPE_PORT_SEND, &exc->thread) | ipc_kmsg_copyout_object(space, (ipc_object_t) task_port, MACH_MSG_TYPE_PORT_SEND, &exc->task)); if (mr != MACH_MSG_SUCCESS) { (void) ipc_kmsg_put(receiver->ith_msg, kmsg, kmsg->ikm_header.msgh_size); thread_syscall_return(mr | MACH_RCV_BODY_ERROR); /*NOTREACHED*/ } } /* * Optimized version of ipc_kmsg_put. * We must check ikm_cache after copyoutmsg. */ ikm_check_initialized(kmsg, kmsg->ikm_size); assert(kmsg->ikm_size == IKM_SAVED_KMSG_SIZE); if (copyoutmsg(&kmsg->ikm_header, receiver->ith_msg, sizeof(struct mach_exception)) || (ikm_cache() != IKM_NULL)) { mr = ipc_kmsg_put(receiver->ith_msg, kmsg, kmsg->ikm_header.msgh_size); thread_syscall_return(mr); /*NOTREACHED*/ } ikm_cache() = kmsg; thread_syscall_return(MACH_MSG_SUCCESS); /*NOTREACHED*/ #ifndef __GNUC__ return; /* help for the compiler */ #endif slow_exception_raise: { struct mach_exception *exc = (struct mach_exception *) &kmsg->ikm_header; ipc_kmsg_t reply_kmsg; mach_port_seqno_t reply_seqno; exception_raise_misses++; /* * We hold the following resources, which must be consumed: * kmsg, send-once right and ref for reply_port * send rights for dest_port, thread_port, task_port * Synthesize a kmsg to send. */ exc->Head.msgh_bits = (MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND, MACH_MSG_TYPE_PORT_SEND_ONCE) | MACH_MSGH_BITS_COMPLEX); exc->Head.msgh_size = sizeof *exc; exc->Head.msgh_remote_port = (mach_port_t) dest_port; exc->Head.msgh_local_port = (mach_port_t) reply_port; exc->Head.msgh_seqno = 0; exc->Head.msgh_id = MACH_EXCEPTION_ID; exc->threadType = exc_port_proto; exc->thread = (mach_port_t) thread_port; exc->taskType = exc_port_proto; exc->task = (mach_port_t) task_port; exc->exceptionType = exc_code_proto; exc->exception = _exception; exc->codeType = exc_code_proto; exc->code = code; exc->subcodeType = exc_code_proto; exc->subcode = subcode; ipc_mqueue_send_always(kmsg); /* * We are left with a ref for reply_port, * which we use to receive the reply message. */ ip_lock(reply_port); if (!ip_active(reply_port)) { ip_unlock(reply_port); exception_raise_continue_slow(MACH_RCV_PORT_DIED, IKM_NULL, /*dummy*/0); /*NOTREACHED*/ } imq_lock(reply_mqueue); ip_unlock(reply_port); mr = ipc_mqueue_receive(reply_mqueue, MACH_MSG_OPTION_NONE, MACH_MSG_SIZE_MAX, MACH_MSG_TIMEOUT_NONE, FALSE, exception_raise_continue, &reply_kmsg, &reply_seqno); /* reply_mqueue is unlocked */ exception_raise_continue_slow(mr, reply_kmsg, reply_seqno); /*NOTREACHED*/ } }
kern_return_t mach_port_get_attributes( ipc_space_t space, mach_port_name_t name, int flavor, mach_port_info_t info, mach_msg_type_number_t *count) { ipc_port_t port; kern_return_t kr; if (space == IS_NULL) return KERN_INVALID_TASK; switch (flavor) { case MACH_PORT_LIMITS_INFO: { mach_port_limits_t *lp = (mach_port_limits_t *)info; if (*count < MACH_PORT_LIMITS_INFO_COUNT) return KERN_FAILURE; if (!MACH_PORT_VALID(name)) { *count = 0; break; } kr = ipc_port_translate_receive(space, name, &port); if (kr != KERN_SUCCESS) return kr; /* port is locked and active */ lp->mpl_qlimit = port->ip_messages.imq_qlimit; *count = MACH_PORT_LIMITS_INFO_COUNT; ip_unlock(port); break; } case MACH_PORT_RECEIVE_STATUS: { mach_port_status_t *statusp = (mach_port_status_t *)info; spl_t s; if (*count < MACH_PORT_RECEIVE_STATUS_COUNT) return KERN_FAILURE; if (!MACH_PORT_VALID(name)) return KERN_INVALID_RIGHT; kr = ipc_port_translate_receive(space, name, &port); if (kr != KERN_SUCCESS) return kr; /* port is locked and active */ statusp->mps_pset = port->ip_pset_count; s = splsched(); imq_lock(&port->ip_messages); statusp->mps_seqno = port->ip_messages.imq_seqno; statusp->mps_qlimit = port->ip_messages.imq_qlimit; statusp->mps_msgcount = port->ip_messages.imq_msgcount; imq_unlock(&port->ip_messages); splx(s); statusp->mps_mscount = port->ip_mscount; statusp->mps_sorights = port->ip_sorights; statusp->mps_srights = port->ip_srights > 0; statusp->mps_pdrequest = port->ip_pdrequest != IP_NULL; statusp->mps_nsrequest = port->ip_nsrequest != IP_NULL; statusp->mps_flags = 0; *count = MACH_PORT_RECEIVE_STATUS_COUNT; ip_unlock(port); break; } case MACH_PORT_DNREQUESTS_SIZE: { ipc_port_request_t table; if (*count < MACH_PORT_DNREQUESTS_SIZE_COUNT) return KERN_FAILURE; if (!MACH_PORT_VALID(name)) { *(int *)info = 0; break; } kr = ipc_port_translate_receive(space, name, &port); if (kr != KERN_SUCCESS) return kr; /* port is locked and active */ table = port->ip_dnrequests; if (table == IPR_NULL) *(int *)info = 0; else *(int *)info = table->ipr_size->its_size; *count = MACH_PORT_DNREQUESTS_SIZE_COUNT; ip_unlock(port); break; } default: return KERN_INVALID_ARGUMENT; /*NOTREACHED*/ } return KERN_SUCCESS; }
/* * Routine: ipc_mqueue_send * Purpose: * Send a message to a message queue. The message holds a reference * for the destination port for this message queue in the * msgh_remote_port field. * * If unsuccessful, the caller still has possession of * the message and must do something with it. If successful, * the message is queued, given to a receiver, or destroyed. * Conditions: * mqueue is locked. * Returns: * MACH_MSG_SUCCESS The message was accepted. * MACH_SEND_TIMED_OUT Caller still has message. * MACH_SEND_INTERRUPTED Caller still has message. */ mach_msg_return_t ipc_mqueue_send( ipc_mqueue_t mqueue, ipc_kmsg_t kmsg, mach_msg_option_t option, mach_msg_timeout_t send_timeout, spl_t s) { int wresult; /* * Don't block if: * 1) We're under the queue limit. * 2) Caller used the MACH_SEND_ALWAYS internal option. * 3) Message is sent to a send-once right. */ if (!imq_full(mqueue) || (!imq_full_kernel(mqueue) && ((option & MACH_SEND_ALWAYS) || (MACH_MSGH_BITS_REMOTE(kmsg->ikm_header->msgh_bits) == MACH_MSG_TYPE_PORT_SEND_ONCE)))) { mqueue->imq_msgcount++; assert(mqueue->imq_msgcount > 0); imq_unlock(mqueue); splx(s); } else { thread_t cur_thread = current_thread(); uint64_t deadline; /* * We have to wait for space to be granted to us. */ if ((option & MACH_SEND_TIMEOUT) && (send_timeout == 0)) { imq_unlock(mqueue); splx(s); return MACH_SEND_TIMED_OUT; } if (imq_full_kernel(mqueue)) { imq_unlock(mqueue); splx(s); return MACH_SEND_NO_BUFFER; } mqueue->imq_fullwaiters = TRUE; thread_lock(cur_thread); if (option & MACH_SEND_TIMEOUT) clock_interval_to_deadline(send_timeout, 1000*NSEC_PER_USEC, &deadline); else deadline = 0; wresult = wait_queue_assert_wait64_locked( &mqueue->imq_wait_queue, IPC_MQUEUE_FULL, THREAD_ABORTSAFE, TIMEOUT_URGENCY_USER_NORMAL, deadline, 0, cur_thread); thread_unlock(cur_thread); imq_unlock(mqueue); splx(s); if (wresult == THREAD_WAITING) { wresult = thread_block(THREAD_CONTINUE_NULL); counter(c_ipc_mqueue_send_block++); } switch (wresult) { case THREAD_TIMED_OUT: assert(option & MACH_SEND_TIMEOUT); return MACH_SEND_TIMED_OUT; case THREAD_AWAKENED: /* we can proceed - inherited msgcount from waker */ assert(mqueue->imq_msgcount > 0); break; case THREAD_INTERRUPTED: return MACH_SEND_INTERRUPTED; case THREAD_RESTART: /* mqueue is being destroyed */ return MACH_SEND_INVALID_DEST; default: panic("ipc_mqueue_send"); } } ipc_mqueue_post(mqueue, kmsg); return MACH_MSG_SUCCESS; }
/* * Routine: ipc_mqueue_post * Purpose: * Post a message to a waiting receiver or enqueue it. If a * receiver is waiting, we can release our reserved space in * the message queue. * * Conditions: * If we need to queue, our space in the message queue is reserved. */ void ipc_mqueue_post( register ipc_mqueue_t mqueue, register ipc_kmsg_t kmsg) { spl_t s; /* * While the msg queue is locked, we have control of the * kmsg, so the ref in it for the port is still good. * * Check for a receiver for the message. */ s = splsched(); imq_lock(mqueue); for (;;) { wait_queue_t waitq = &mqueue->imq_wait_queue; thread_t receiver; mach_msg_size_t msize; receiver = wait_queue_wakeup64_identity_locked( waitq, IPC_MQUEUE_RECEIVE, THREAD_AWAKENED, FALSE); /* waitq still locked, thread locked */ if (receiver == THREAD_NULL) { /* * no receivers; queue kmsg */ assert(mqueue->imq_msgcount > 0); ipc_kmsg_enqueue_macro(&mqueue->imq_messages, kmsg); break; } /* * If the receiver waited with a facility not directly * related to Mach messaging, then it isn't prepared to get * handed the message directly. Just set it running, and * go look for another thread that can. */ if (receiver->ith_state != MACH_RCV_IN_PROGRESS) { thread_unlock(receiver); continue; } /* * We found a waiting thread. * If the message is too large or the scatter list is too small * the thread we wake up will get that as its status. */ msize = ipc_kmsg_copyout_size(kmsg, receiver->map); if (receiver->ith_msize < (msize + REQUESTED_TRAILER_SIZE(thread_is_64bit(receiver), receiver->ith_option))) { receiver->ith_msize = msize; receiver->ith_state = MACH_RCV_TOO_LARGE; } else { receiver->ith_state = MACH_MSG_SUCCESS; } /* * If there is no problem with the upcoming receive, or the * receiver thread didn't specifically ask for special too * large error condition, go ahead and select it anyway. */ if ((receiver->ith_state == MACH_MSG_SUCCESS) || !(receiver->ith_option & MACH_RCV_LARGE)) { receiver->ith_kmsg = kmsg; receiver->ith_seqno = mqueue->imq_seqno++; thread_unlock(receiver); /* we didn't need our reserved spot in the queue */ ipc_mqueue_release_msgcount(mqueue); break; } /* * Otherwise, this thread needs to be released to run * and handle its error without getting the message. We * need to go back and pick another one. */ receiver->ith_receiver_name = mqueue->imq_receiver_name; receiver->ith_kmsg = IKM_NULL; receiver->ith_seqno = 0; thread_unlock(receiver); } imq_unlock(mqueue); splx(s); current_task()->messages_sent++; return; }
mach_msg_return_t mach_msg_trap( mach_msg_header_t *msg, mach_msg_option_t option, mach_msg_size_t send_size, mach_msg_size_t rcv_size, mach_port_t rcv_name, mach_msg_timeout_t time_out, mach_port_t notify) { mach_msg_return_t mr; /* first check for common cases */ if (option == (MACH_SEND_MSG|MACH_RCV_MSG)) { ipc_thread_t self = current_thread(); ipc_space_t space = self->task->itk_space; ipc_kmsg_t kmsg; ipc_port_t dest_port; ipc_object_t rcv_object; ipc_mqueue_t rcv_mqueue; mach_msg_size_t reply_size; /* * This case is divided into ten sections, each * with a label. There are five optimized * sections and six unoptimized sections, which * do the same thing but handle all possible * cases and are slower. * * The five sections for an RPC are * 1) Get request message into a buffer. * (fast_get or slow_get) * 2) Copyin request message and rcv_name. * (fast_copyin or slow_copyin) * 3) Enqueue request and dequeue reply. * (fast_send_receive or * slow_send and slow_receive) * 4) Copyout reply message. * (fast_copyout or slow_copyout) * 5) Put reply message to user's buffer. * (fast_put or slow_put) * * Keep the locking hierarchy firmly in mind. * (First spaces, then ports, then port sets, * then message queues.) Only a non-blocking * attempt can be made to acquire locks out of * order, or acquire two locks on the same level. * Acquiring two locks on the same level will * fail if the objects are really the same, * unless simple locking is disabled. This is OK, * because then the extra unlock does nothing. * * There are two major reasons these RPCs can't use * ipc_thread_switch, and use slow_send/slow_receive: * 1) Kernel RPCs. * 2) Servers fall behind clients, so * client doesn't find a blocked server thread and * server finds waiting messages and can't block. */ /* fast_get: */ /* * optimized ipc_kmsg_get * * No locks, references, or messages held. * We must clear ikm_cache before copyinmsg. */ if ((send_size > IKM_SAVED_MSG_SIZE) || (send_size < sizeof(mach_msg_header_t)) || (send_size & 3) || ((kmsg = ikm_cache()) == IKM_NULL)) goto slow_get; ikm_cache() = IKM_NULL; ikm_check_initialized(kmsg, IKM_SAVED_KMSG_SIZE); if (copyinmsg(msg, &kmsg->ikm_header, send_size)) { ikm_free(kmsg); goto slow_get; } kmsg->ikm_header.msgh_size = send_size; fast_copyin: /* * optimized ipc_kmsg_copyin/ipc_mqueue_copyin * * We have the request message data in kmsg. * Must still do copyin, send, receive, etc. * * If the message isn't simple, we can't combine * ipc_kmsg_copyin_header and ipc_mqueue_copyin, * because copyin of the message body might * affect rcv_name. */ switch (kmsg->ikm_header.msgh_bits) { case MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE): { ipc_entry_t table; ipc_entry_num_t size; ipc_port_t reply_port; /* sending a request message */ { mach_port_index_t index; mach_port_gen_t gen; { mach_port_t reply_name = kmsg->ikm_header.msgh_local_port; if (reply_name != rcv_name) goto slow_copyin; /* optimized ipc_entry_lookup of reply_name */ index = MACH_PORT_INDEX(reply_name); gen = MACH_PORT_GEN(reply_name); } is_read_lock(space); assert(space->is_active); size = space->is_table_size; table = space->is_table; if (index >= size) goto abort_request_copyin; { ipc_entry_t entry; ipc_entry_bits_t bits; entry = &table[index]; bits = entry->ie_bits; /* check generation number and type bit */ if ((bits & (IE_BITS_GEN_MASK| MACH_PORT_TYPE_RECEIVE)) != (gen | MACH_PORT_TYPE_RECEIVE)) goto abort_request_copyin; reply_port = (ipc_port_t) entry->ie_object; assert(reply_port != IP_NULL); } } /* optimized ipc_entry_lookup of dest_name */ { mach_port_index_t index; mach_port_gen_t gen; { mach_port_t dest_name = kmsg->ikm_header.msgh_remote_port; index = MACH_PORT_INDEX(dest_name); gen = MACH_PORT_GEN(dest_name); } if (index >= size) goto abort_request_copyin; { ipc_entry_t entry; ipc_entry_bits_t bits; entry = &table[index]; bits = entry->ie_bits; /* check generation number and type bit */ if ((bits & (IE_BITS_GEN_MASK|MACH_PORT_TYPE_SEND)) != (gen | MACH_PORT_TYPE_SEND)) goto abort_request_copyin; assert(IE_BITS_UREFS(bits) > 0); dest_port = (ipc_port_t) entry->ie_object; assert(dest_port != IP_NULL); } } /* * To do an atomic copyin, need simultaneous * locks on both ports and the space. If * dest_port == reply_port, and simple locking is * enabled, then we will abort. Otherwise it's * OK to unlock twice. */ ip_lock(dest_port); if (!ip_active(dest_port) || !ip_lock_try(reply_port)) { ip_unlock(dest_port); goto abort_request_copyin; } is_read_unlock(space); assert(dest_port->ip_srights > 0); dest_port->ip_srights++; ip_reference(dest_port); assert(ip_active(reply_port)); assert(reply_port->ip_receiver_name == kmsg->ikm_header.msgh_local_port); assert(reply_port->ip_receiver == space); reply_port->ip_sorights++; ip_reference(reply_port); kmsg->ikm_header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND, MACH_MSG_TYPE_PORT_SEND_ONCE); kmsg->ikm_header.msgh_remote_port = (mach_port_t) dest_port; kmsg->ikm_header.msgh_local_port = (mach_port_t) reply_port; /* make sure we can queue to the destination */ if (dest_port->ip_receiver == ipc_space_kernel) { /* * The kernel server has a reference to * the reply port, which it hands back * to us in the reply message. We do * not need to keep another reference to * it. */ ip_unlock(reply_port); assert(ip_active(dest_port)); ip_unlock(dest_port); goto kernel_send; } if (dest_port->ip_msgcount >= dest_port->ip_qlimit) goto abort_request_send_receive; /* optimized ipc_mqueue_copyin */ if (reply_port->ip_pset != IPS_NULL) goto abort_request_send_receive; rcv_object = (ipc_object_t) reply_port; io_reference(rcv_object); rcv_mqueue = &reply_port->ip_messages; imq_lock(rcv_mqueue); io_unlock(rcv_object); goto fast_send_receive; abort_request_copyin: is_read_unlock(space); goto slow_copyin; abort_request_send_receive: ip_unlock(dest_port); ip_unlock(reply_port); goto slow_send; } case MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0): { ipc_entry_num_t size; ipc_entry_t table; /* sending a reply message */ { mach_port_t reply_name = kmsg->ikm_header.msgh_local_port; if (reply_name != MACH_PORT_NULL) goto slow_copyin; } is_write_lock(space); assert(space->is_active); /* optimized ipc_entry_lookup */ size = space->is_table_size; table = space->is_table; { ipc_entry_t entry; mach_port_gen_t gen; mach_port_index_t index; { mach_port_t dest_name = kmsg->ikm_header.msgh_remote_port; index = MACH_PORT_INDEX(dest_name); gen = MACH_PORT_GEN(dest_name); } if (index >= size) goto abort_reply_dest_copyin; entry = &table[index]; /* check generation, collision bit, and type bit */ if ((entry->ie_bits & (IE_BITS_GEN_MASK| IE_BITS_COLLISION| MACH_PORT_TYPE_SEND_ONCE)) != (gen | MACH_PORT_TYPE_SEND_ONCE)) goto abort_reply_dest_copyin; /* optimized ipc_right_copyin */ assert(IE_BITS_TYPE(entry->ie_bits) == MACH_PORT_TYPE_SEND_ONCE); assert(IE_BITS_UREFS(entry->ie_bits) == 1); assert((entry->ie_bits & IE_BITS_MAREQUEST) == 0); if (entry->ie_request != 0) goto abort_reply_dest_copyin; dest_port = (ipc_port_t) entry->ie_object; assert(dest_port != IP_NULL); ip_lock(dest_port); if (!ip_active(dest_port)) { ip_unlock(dest_port); goto abort_reply_dest_copyin; } assert(dest_port->ip_sorights > 0); /* optimized ipc_entry_dealloc */ entry->ie_next = table->ie_next; table->ie_next = index; entry->ie_bits = gen; entry->ie_object = IO_NULL; } kmsg->ikm_header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, 0); kmsg->ikm_header.msgh_remote_port = (mach_port_t) dest_port; /* make sure we can queue to the destination */ assert(dest_port->ip_receiver != ipc_space_kernel); /* optimized ipc_entry_lookup/ipc_mqueue_copyin */ { ipc_entry_t entry; ipc_entry_bits_t bits; { mach_port_index_t index; mach_port_gen_t gen; index = MACH_PORT_INDEX(rcv_name); gen = MACH_PORT_GEN(rcv_name); if (index >= size) goto abort_reply_rcv_copyin; entry = &table[index]; bits = entry->ie_bits; /* check generation number */ if ((bits & IE_BITS_GEN_MASK) != gen) goto abort_reply_rcv_copyin; } /* check type bits; looking for receive or set */ if (bits & MACH_PORT_TYPE_PORT_SET) { ipc_pset_t rcv_pset; rcv_pset = (ipc_pset_t) entry->ie_object; assert(rcv_pset != IPS_NULL); ips_lock(rcv_pset); assert(ips_active(rcv_pset)); rcv_object = (ipc_object_t) rcv_pset; rcv_mqueue = &rcv_pset->ips_messages; } else if (bits & MACH_PORT_TYPE_RECEIVE) { ipc_port_t rcv_port; rcv_port = (ipc_port_t) entry->ie_object; assert(rcv_port != IP_NULL); if (!ip_lock_try(rcv_port)) goto abort_reply_rcv_copyin; assert(ip_active(rcv_port)); if (rcv_port->ip_pset != IPS_NULL) { ip_unlock(rcv_port); goto abort_reply_rcv_copyin; } rcv_object = (ipc_object_t) rcv_port; rcv_mqueue = &rcv_port->ip_messages; } else goto abort_reply_rcv_copyin; } is_write_unlock(space); io_reference(rcv_object); imq_lock(rcv_mqueue); io_unlock(rcv_object); goto fast_send_receive; abort_reply_dest_copyin: is_write_unlock(space); goto slow_copyin; abort_reply_rcv_copyin: ip_unlock(dest_port); is_write_unlock(space); goto slow_send; } default: goto slow_copyin; } /*NOTREACHED*/ fast_send_receive: /* * optimized ipc_mqueue_send/ipc_mqueue_receive * * Finished get/copyin of kmsg and copyin of rcv_name. * space is unlocked, dest_port is locked, * we can queue kmsg to dest_port, * rcv_mqueue is locked, rcv_object holds a ref, * if rcv_object is a port it isn't in a port set * * Note that if simple locking is turned off, * then we could have dest_mqueue == rcv_mqueue * and not abort when we try to lock dest_mqueue. */ assert(ip_active(dest_port)); assert(dest_port->ip_receiver != ipc_space_kernel); assert((dest_port->ip_msgcount < dest_port->ip_qlimit) || (MACH_MSGH_BITS_REMOTE(kmsg->ikm_header.msgh_bits) == MACH_MSG_TYPE_PORT_SEND_ONCE)); assert((kmsg->ikm_header.msgh_bits & MACH_MSGH_BITS_CIRCULAR) == 0); { ipc_mqueue_t dest_mqueue; ipc_thread_t receiver; { ipc_pset_t dest_pset; dest_pset = dest_port->ip_pset; if (dest_pset == IPS_NULL) dest_mqueue = &dest_port->ip_messages; else dest_mqueue = &dest_pset->ips_messages; } if (!imq_lock_try(dest_mqueue)) { abort_send_receive: ip_unlock(dest_port); imq_unlock(rcv_mqueue); ipc_object_release(rcv_object); goto slow_send; } receiver = ipc_thread_queue_first(&dest_mqueue->imq_threads); if ((receiver == ITH_NULL) || (ipc_kmsg_queue_first(&rcv_mqueue->imq_messages) != IKM_NULL)) { imq_unlock(dest_mqueue); goto abort_send_receive; } /* * There is a receiver thread waiting, and * there is no reply message for us to pick up. * We have hope of hand-off, so save state. */ self->ith_msg = msg; self->ith_rcv_size = rcv_size; self->ith_object = rcv_object; self->ith_mqueue = rcv_mqueue; if ((receiver->swap_func == (void (*)()) mach_msg_continue) && thread_handoff(self, mach_msg_continue, receiver)) { assert(current_thread() == receiver); /* * We can use the optimized receive code, * because the receiver is using no options. */ } else if ((receiver->swap_func == (void (*)()) exception_raise_continue) && thread_handoff(self, mach_msg_continue, receiver)) { counter(c_mach_msg_trap_block_exc++); assert(current_thread() == receiver); /* * We are a reply message coming back through * the optimized exception-handling path. * Finish with rcv_mqueue and dest_mqueue, * and then jump to exception code with * dest_port still locked. We don't bother * with a sequence number in this case. */ ipc_thread_enqueue_macro( &rcv_mqueue->imq_threads, self); self->ith_state = MACH_RCV_IN_PROGRESS; self->ith_msize = MACH_MSG_SIZE_MAX; imq_unlock(rcv_mqueue); ipc_thread_rmqueue_first_macro( &dest_mqueue->imq_threads, receiver); imq_unlock(dest_mqueue); exception_raise_continue_fast(dest_port, kmsg); /*NOTREACHED*/ return MACH_MSG_SUCCESS; } else if ((send_size <= receiver->ith_msize) && thread_handoff(self, mach_msg_continue, receiver)) { assert(current_thread() == receiver); if ((receiver->swap_func == (void (*)()) mach_msg_receive_continue) && ((receiver->ith_option & MACH_RCV_NOTIFY) == 0)) { /* * We can still use the optimized code. */ } else { counter(c_mach_msg_trap_block_slow++); /* * We are running as the receiver, * but we can't use the optimized code. * Finish send/receive processing. */ dest_port->ip_msgcount++; ip_unlock(dest_port); ipc_thread_enqueue_macro( &rcv_mqueue->imq_threads, self); self->ith_state = MACH_RCV_IN_PROGRESS; self->ith_msize = MACH_MSG_SIZE_MAX; imq_unlock(rcv_mqueue); ipc_thread_rmqueue_first_macro( &dest_mqueue->imq_threads, receiver); receiver->ith_state = MACH_MSG_SUCCESS; receiver->ith_kmsg = kmsg; receiver->ith_seqno = dest_port->ip_seqno++; imq_unlock(dest_mqueue); /* * Call the receiver's continuation. */ receiver->wait_result = THREAD_AWAKENED; (*receiver->swap_func)(); /*NOTREACHED*/ return MACH_MSG_SUCCESS; } } else { /* * The receiver can't accept the message, * or we can't switch to the receiver. */ imq_unlock(dest_mqueue); goto abort_send_receive; } counter(c_mach_msg_trap_block_fast++); /* * Safe to unlock dest_port now that we are * committed to this path, because we hold * dest_mqueue locked. We never bother changing * dest_port->ip_msgcount. */ ip_unlock(dest_port); /* * We need to finish preparing self for its * time asleep in rcv_mqueue. */ ipc_thread_enqueue_macro(&rcv_mqueue->imq_threads, self); self->ith_state = MACH_RCV_IN_PROGRESS; self->ith_msize = MACH_MSG_SIZE_MAX; imq_unlock(rcv_mqueue); /* * Finish extracting receiver from dest_mqueue. */ ipc_thread_rmqueue_first_macro( &dest_mqueue->imq_threads, receiver); kmsg->ikm_header.msgh_seqno = dest_port->ip_seqno++; imq_unlock(dest_mqueue); /* * We don't have to do any post-dequeue processing of * the message. We never incremented ip_msgcount, we * know it has no msg-accepted request, and blocked * senders aren't a worry because we found the port * with a receiver waiting. */ self = receiver; space = self->task->itk_space; msg = self->ith_msg; rcv_size = self->ith_rcv_size; rcv_object = self->ith_object; /* inline ipc_object_release */ io_lock(rcv_object); io_release(rcv_object); io_check_unlock(rcv_object); } fast_copyout: /* * Nothing locked and no references held, except * we have kmsg with msgh_seqno filled in. Must * still check against rcv_size and do * ipc_kmsg_copyout/ipc_kmsg_put. */ assert((ipc_port_t) kmsg->ikm_header.msgh_remote_port == dest_port); reply_size = kmsg->ikm_header.msgh_size; if (rcv_size < reply_size) goto slow_copyout; /* optimized ipc_kmsg_copyout/ipc_kmsg_copyout_header */ switch (kmsg->ikm_header.msgh_bits) { case MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND, MACH_MSG_TYPE_PORT_SEND_ONCE): { ipc_port_t reply_port = (ipc_port_t) kmsg->ikm_header.msgh_local_port; mach_port_t dest_name, reply_name; /* receiving a request message */ if (!IP_VALID(reply_port)) goto slow_copyout; is_write_lock(space); assert(space->is_active); /* * To do an atomic copyout, need simultaneous * locks on both ports and the space. If * dest_port == reply_port, and simple locking is * enabled, then we will abort. Otherwise it's * OK to unlock twice. */ ip_lock(dest_port); if (!ip_active(dest_port) || !ip_lock_try(reply_port)) goto abort_request_copyout; if (!ip_active(reply_port)) { ip_unlock(reply_port); goto abort_request_copyout; } assert(reply_port->ip_sorights > 0); ip_unlock(reply_port); { ipc_entry_t table; ipc_entry_t entry; mach_port_index_t index; /* optimized ipc_entry_get */ table = space->is_table; index = table->ie_next; if (index == 0) goto abort_request_copyout; entry = &table[index]; table->ie_next = entry->ie_next; entry->ie_request = 0; { mach_port_gen_t gen; assert((entry->ie_bits &~ IE_BITS_GEN_MASK) == 0); gen = entry->ie_bits + IE_BITS_GEN_ONE; reply_name = MACH_PORT_MAKE(index, gen); /* optimized ipc_right_copyout */ entry->ie_bits = gen | (MACH_PORT_TYPE_SEND_ONCE | 1); } assert(MACH_PORT_VALID(reply_name)); entry->ie_object = (ipc_object_t) reply_port; is_write_unlock(space); } /* optimized ipc_object_copyout_dest */ assert(dest_port->ip_srights > 0); ip_release(dest_port); if (dest_port->ip_receiver == space) dest_name = dest_port->ip_receiver_name; else dest_name = MACH_PORT_NULL; if ((--dest_port->ip_srights == 0) && (dest_port->ip_nsrequest != IP_NULL)) { ipc_port_t nsrequest; mach_port_mscount_t mscount; /* a rather rare case */ nsrequest = dest_port->ip_nsrequest; mscount = dest_port->ip_mscount; dest_port->ip_nsrequest = IP_NULL; ip_unlock(dest_port); ipc_notify_no_senders(nsrequest, mscount); } else ip_unlock(dest_port); if (! ipc_port_flag_protected_payload(dest_port)) { kmsg->ikm_header.msgh_bits = MACH_MSGH_BITS( MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PORT_SEND); kmsg->ikm_header.msgh_local_port = dest_name; } else { kmsg->ikm_header.msgh_bits = MACH_MSGH_BITS( MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PROTECTED_PAYLOAD); kmsg->ikm_header.msgh_protected_payload = dest_port->ip_protected_payload; } kmsg->ikm_header.msgh_remote_port = reply_name; goto fast_put; abort_request_copyout: ip_unlock(dest_port); is_write_unlock(space); goto slow_copyout; } case MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, 0): { mach_port_t dest_name; /* receiving a reply message */ ip_lock(dest_port); if (!ip_active(dest_port)) goto slow_copyout; /* optimized ipc_object_copyout_dest */ assert(dest_port->ip_sorights > 0); if (dest_port->ip_receiver == space) { ip_release(dest_port); dest_port->ip_sorights--; dest_name = dest_port->ip_receiver_name; ip_unlock(dest_port); } else { ip_unlock(dest_port); ipc_notify_send_once(dest_port); dest_name = MACH_PORT_NULL; } if (! ipc_port_flag_protected_payload(dest_port)) { kmsg->ikm_header.msgh_bits = MACH_MSGH_BITS( 0, MACH_MSG_TYPE_PORT_SEND_ONCE); kmsg->ikm_header.msgh_local_port = dest_name; } else { kmsg->ikm_header.msgh_bits = MACH_MSGH_BITS( 0, MACH_MSG_TYPE_PROTECTED_PAYLOAD); kmsg->ikm_header.msgh_protected_payload = dest_port->ip_protected_payload; } kmsg->ikm_header.msgh_remote_port = MACH_PORT_NULL; goto fast_put; } case MACH_MSGH_BITS_COMPLEX| MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, 0): { mach_port_t dest_name; /* receiving a complex reply message */ ip_lock(dest_port); if (!ip_active(dest_port)) goto slow_copyout; /* optimized ipc_object_copyout_dest */ assert(dest_port->ip_sorights > 0); if (dest_port->ip_receiver == space) { ip_release(dest_port); dest_port->ip_sorights--; dest_name = dest_port->ip_receiver_name; ip_unlock(dest_port); } else { ip_unlock(dest_port); ipc_notify_send_once(dest_port); dest_name = MACH_PORT_NULL; } if (! ipc_port_flag_protected_payload(dest_port)) { kmsg->ikm_header.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS( 0, MACH_MSG_TYPE_PORT_SEND_ONCE); kmsg->ikm_header.msgh_local_port = dest_name; } else { kmsg->ikm_header.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS( 0, MACH_MSG_TYPE_PROTECTED_PAYLOAD); kmsg->ikm_header.msgh_protected_payload = dest_port->ip_protected_payload; } kmsg->ikm_header.msgh_remote_port = MACH_PORT_NULL; mr = ipc_kmsg_copyout_body( (vm_offset_t) (&kmsg->ikm_header + 1), (vm_offset_t) &kmsg->ikm_header + kmsg->ikm_header.msgh_size, space, current_map()); if (mr != MACH_MSG_SUCCESS) { (void) ipc_kmsg_put(msg, kmsg, kmsg->ikm_header.msgh_size); return mr | MACH_RCV_BODY_ERROR; } goto fast_put; } default: goto slow_copyout; } /*NOTREACHED*/ fast_put: /* * We have the reply message data in kmsg, * and the reply message size in reply_size. * Just need to copy it out to the user and free kmsg. * We must check ikm_cache after copyoutmsg. */ ikm_check_initialized(kmsg, kmsg->ikm_size); if ((kmsg->ikm_size != IKM_SAVED_KMSG_SIZE) || copyoutmsg(&kmsg->ikm_header, msg, reply_size) || (ikm_cache() != IKM_NULL)) goto slow_put; ikm_cache() = kmsg; thread_syscall_return(MACH_MSG_SUCCESS); /*NOTREACHED*/ return MACH_MSG_SUCCESS; /* help for the compiler */ /* * The slow path has a few non-register temporary * variables used only for call-by-reference. */ { ipc_kmsg_t temp_kmsg; mach_port_seqno_t temp_seqno; ipc_object_t temp_rcv_object; ipc_mqueue_t temp_rcv_mqueue; slow_get: /* * No locks, references, or messages held. * Still have to get the request, send it, * receive reply, etc. */ mr = ipc_kmsg_get(msg, send_size, &temp_kmsg); if (mr != MACH_MSG_SUCCESS) { thread_syscall_return(mr); /*NOTREACHED*/ } kmsg = temp_kmsg; /* try to get back on optimized path */ goto fast_copyin; slow_copyin: /* * We have the message data in kmsg, but * we still need to copyin, send it, * receive a reply, and do copyout. */ mr = ipc_kmsg_copyin(kmsg, space, current_map(), MACH_PORT_NULL); if (mr != MACH_MSG_SUCCESS) { ikm_free(kmsg); thread_syscall_return(mr); /*NOTREACHED*/ } /* try to get back on optimized path */ if (kmsg->ikm_header.msgh_bits & MACH_MSGH_BITS_CIRCULAR) goto slow_send; dest_port = (ipc_port_t) kmsg->ikm_header.msgh_remote_port; assert(IP_VALID(dest_port)); ip_lock(dest_port); if (dest_port->ip_receiver == ipc_space_kernel) { assert(ip_active(dest_port)); ip_unlock(dest_port); goto kernel_send; } if (ip_active(dest_port) && ((dest_port->ip_msgcount < dest_port->ip_qlimit) || (MACH_MSGH_BITS_REMOTE(kmsg->ikm_header.msgh_bits) == MACH_MSG_TYPE_PORT_SEND_ONCE))) { /* * Try an optimized ipc_mqueue_copyin. * It will work if this is a request message. */ ipc_port_t reply_port; reply_port = (ipc_port_t) kmsg->ikm_header.msgh_local_port; if (IP_VALID(reply_port)) { if (ip_lock_try(reply_port)) { if (ip_active(reply_port) && reply_port->ip_receiver == space && reply_port->ip_receiver_name == rcv_name && reply_port->ip_pset == IPS_NULL) { /* Grab a reference to the reply port. */ rcv_object = (ipc_object_t) reply_port; io_reference(rcv_object); rcv_mqueue = &reply_port->ip_messages; imq_lock(rcv_mqueue); io_unlock(rcv_object); goto fast_send_receive; } ip_unlock(reply_port); } } } ip_unlock(dest_port); goto slow_send; kernel_send: /* * Special case: send message to kernel services. * The request message has been copied into the * kmsg. Nothing is locked. */ { ipc_port_t reply_port; /* * Perform the kernel function. */ kmsg = ipc_kobject_server(kmsg); if (kmsg == IKM_NULL) { /* * No reply. Take the * slow receive path. */ goto slow_get_rcv_port; } /* * Check that: * the reply port is alive * we hold the receive right * the name has not changed. * the port is not in a set * If any of these are not true, * we cannot directly receive the reply * message. */ reply_port = (ipc_port_t) kmsg->ikm_header.msgh_remote_port; ip_lock(reply_port); if ((!ip_active(reply_port)) || (reply_port->ip_receiver != space) || (reply_port->ip_receiver_name != rcv_name) || (reply_port->ip_pset != IPS_NULL)) { ip_unlock(reply_port); ipc_mqueue_send_always(kmsg); goto slow_get_rcv_port; } rcv_mqueue = &reply_port->ip_messages; imq_lock(rcv_mqueue); /* keep port locked, and don`t change ref count yet */ /* * If there are messages on the port * or other threads waiting for a message, * we cannot directly receive the reply. */ if ((ipc_thread_queue_first(&rcv_mqueue->imq_threads) != ITH_NULL) || (ipc_kmsg_queue_first(&rcv_mqueue->imq_messages) != IKM_NULL)) { imq_unlock(rcv_mqueue); ip_unlock(reply_port); ipc_mqueue_send_always(kmsg); goto slow_get_rcv_port; } /* * We can directly receive this reply. * Since the kernel reply never blocks, * it holds no message_accepted request. * Since there were no messages queued * on the reply port, there should be * no threads blocked waiting to send. */ assert(kmsg->ikm_marequest == IMAR_NULL); assert(ipc_thread_queue_first(&reply_port->ip_blocked) == ITH_NULL); dest_port = reply_port; kmsg->ikm_header.msgh_seqno = dest_port->ip_seqno++; imq_unlock(rcv_mqueue); /* * inline ipc_object_release. * Port is still locked. * Reference count was not incremented. */ ip_check_unlock(reply_port); /* copy out the kernel reply */ goto fast_copyout; } slow_send: /* * Nothing is locked. We have acquired kmsg, but * we still need to send it and receive a reply. */ mr = ipc_mqueue_send(kmsg, MACH_MSG_OPTION_NONE, MACH_MSG_TIMEOUT_NONE); if (mr != MACH_MSG_SUCCESS) { mr |= ipc_kmsg_copyout_pseudo(kmsg, space, current_map()); assert(kmsg->ikm_marequest == IMAR_NULL); (void) ipc_kmsg_put(msg, kmsg, kmsg->ikm_header.msgh_size); thread_syscall_return(mr); /*NOTREACHED*/ } slow_get_rcv_port: /* * We have sent the message. Copy in the receive port. */ mr = ipc_mqueue_copyin(space, rcv_name, &temp_rcv_mqueue, &temp_rcv_object); if (mr != MACH_MSG_SUCCESS) { thread_syscall_return(mr); /*NOTREACHED*/ } rcv_mqueue = temp_rcv_mqueue; rcv_object = temp_rcv_object; /* hold ref for rcv_object; rcv_mqueue is locked */ /* slow_receive: */ /* * Now we have sent the request and copied in rcv_name, * so rcv_mqueue is locked and hold ref for rcv_object. * Just receive a reply and try to get back to fast path. * * ipc_mqueue_receive may not return, because if we block * then our kernel stack may be discarded. So we save * state here for mach_msg_continue to pick up. */ self->ith_msg = msg; self->ith_rcv_size = rcv_size; self->ith_object = rcv_object; self->ith_mqueue = rcv_mqueue; mr = ipc_mqueue_receive(rcv_mqueue, MACH_MSG_OPTION_NONE, MACH_MSG_SIZE_MAX, MACH_MSG_TIMEOUT_NONE, FALSE, mach_msg_continue, &temp_kmsg, &temp_seqno); /* rcv_mqueue is unlocked */ ipc_object_release(rcv_object); if (mr != MACH_MSG_SUCCESS) { thread_syscall_return(mr); /*NOTREACHED*/ } (kmsg = temp_kmsg)->ikm_header.msgh_seqno = temp_seqno; dest_port = (ipc_port_t) kmsg->ikm_header.msgh_remote_port; goto fast_copyout; slow_copyout: /* * Nothing locked and no references held, except * we have kmsg with msgh_seqno filled in. Must * still check against rcv_size and do * ipc_kmsg_copyout/ipc_kmsg_put. */ reply_size = kmsg->ikm_header.msgh_size; if (rcv_size < reply_size) { ipc_kmsg_copyout_dest(kmsg, space); (void) ipc_kmsg_put(msg, kmsg, sizeof *msg); thread_syscall_return(MACH_RCV_TOO_LARGE); /*NOTREACHED*/ } mr = ipc_kmsg_copyout(kmsg, space, current_map(), MACH_PORT_NULL); if (mr != MACH_MSG_SUCCESS) { if ((mr &~ MACH_MSG_MASK) == MACH_RCV_BODY_ERROR) { (void) ipc_kmsg_put(msg, kmsg, kmsg->ikm_header.msgh_size); } else { ipc_kmsg_copyout_dest(kmsg, space); (void) ipc_kmsg_put(msg, kmsg, sizeof *msg); } thread_syscall_return(mr); /*NOTREACHED*/ } /* try to get back on optimized path */ goto fast_put; slow_put: mr = ipc_kmsg_put(msg, kmsg, reply_size); thread_syscall_return(mr); /*NOTREACHED*/ } } else if (option == MACH_SEND_MSG) { ipc_space_t space = current_space(); vm_map_t map = current_map(); ipc_kmsg_t kmsg; mr = ipc_kmsg_get(msg, send_size, &kmsg); if (mr != MACH_MSG_SUCCESS) return mr; mr = ipc_kmsg_copyin(kmsg, space, map, MACH_PORT_NULL); if (mr != MACH_MSG_SUCCESS) { ikm_free(kmsg); return mr; } mr = ipc_mqueue_send(kmsg, MACH_MSG_OPTION_NONE, MACH_MSG_TIMEOUT_NONE); if (mr != MACH_MSG_SUCCESS) { mr |= ipc_kmsg_copyout_pseudo(kmsg, space, map); assert(kmsg->ikm_marequest == IMAR_NULL); (void) ipc_kmsg_put(msg, kmsg, kmsg->ikm_header.msgh_size); } return mr; } else if (option == MACH_RCV_MSG) { ipc_thread_t self = current_thread(); ipc_space_t space = current_space(); vm_map_t map = current_map(); ipc_object_t object; ipc_mqueue_t mqueue; ipc_kmsg_t kmsg; mach_port_seqno_t seqno; mr = ipc_mqueue_copyin(space, rcv_name, &mqueue, &object); if (mr != MACH_MSG_SUCCESS) return mr; /* hold ref for object; mqueue is locked */ /* * ipc_mqueue_receive may not return, because if we block * then our kernel stack may be discarded. So we save * state here for mach_msg_continue to pick up. */ self->ith_msg = msg; self->ith_rcv_size = rcv_size; self->ith_object = object; self->ith_mqueue = mqueue; mr = ipc_mqueue_receive(mqueue, MACH_MSG_OPTION_NONE, MACH_MSG_SIZE_MAX, MACH_MSG_TIMEOUT_NONE, FALSE, mach_msg_continue, &kmsg, &seqno); /* mqueue is unlocked */ ipc_object_release(object); if (mr != MACH_MSG_SUCCESS) return mr; kmsg->ikm_header.msgh_seqno = seqno; if (rcv_size < kmsg->ikm_header.msgh_size) { ipc_kmsg_copyout_dest(kmsg, space); (void) ipc_kmsg_put(msg, kmsg, sizeof *msg); return MACH_RCV_TOO_LARGE; } mr = ipc_kmsg_copyout(kmsg, space, map, MACH_PORT_NULL); if (mr != MACH_MSG_SUCCESS) { if ((mr &~ MACH_MSG_MASK) == MACH_RCV_BODY_ERROR) { (void) ipc_kmsg_put(msg, kmsg, kmsg->ikm_header.msgh_size); } else { ipc_kmsg_copyout_dest(kmsg, space); (void) ipc_kmsg_put(msg, kmsg, sizeof *msg); } return mr; } return ipc_kmsg_put(msg, kmsg, kmsg->ikm_header.msgh_size); } else if (option == MACH_MSG_OPTION_NONE) { /* * We can measure the "null mach_msg_trap" * (syscall entry and thread_syscall_return exit) * with this path. */ thread_syscall_return(MACH_MSG_SUCCESS); /*NOTREACHED*/ } if (option & MACH_SEND_MSG) { mr = mach_msg_send(msg, option, send_size, time_out, notify); if (mr != MACH_MSG_SUCCESS) return mr; } if (option & MACH_RCV_MSG) { mr = mach_msg_receive(msg, option, rcv_size, rcv_name, time_out, notify); if (mr != MACH_MSG_SUCCESS) return mr; } return MACH_MSG_SUCCESS; }
mach_msg_return_t ipc_mqueue_send( ipc_kmsg_t kmsg, mach_msg_option_t option, mach_msg_timeout_t time_out) { ipc_port_t port; port = (ipc_port_t) kmsg->ikm_header.msgh_remote_port; assert(IP_VALID(port)); ip_lock(port); if (port->ip_receiver == ipc_space_kernel) { ipc_kmsg_t reply; /* * We can check ip_receiver == ipc_space_kernel * before checking that the port is active because * ipc_port_dealloc_kernel clears ip_receiver * before destroying a kernel port. */ assert(ip_active(port)); ip_unlock(port); reply = ipc_kobject_server(kmsg); if (reply != IKM_NULL) ipc_mqueue_send_always(reply); return MACH_MSG_SUCCESS; } for (;;) { ipc_thread_t self; /* * Can't deliver to a dead port. * However, we can pretend it got sent * and was then immediately destroyed. */ if (!ip_active(port)) { /* * We can't let ipc_kmsg_destroy deallocate * the port right, because we might end up * in an infinite loop trying to deliver * a send-once notification. */ ip_release(port); ip_check_unlock(port); kmsg->ikm_header.msgh_remote_port = MACH_PORT_NULL; ipc_kmsg_destroy(kmsg); return MACH_MSG_SUCCESS; } /* * Don't block if: * 1) We're under the queue limit. * 2) Caller used the MACH_SEND_ALWAYS internal option. * 3) Message is sent to a send-once right. */ if ((port->ip_msgcount < port->ip_qlimit) || (option & MACH_SEND_ALWAYS) || (MACH_MSGH_BITS_REMOTE(kmsg->ikm_header.msgh_bits) == MACH_MSG_TYPE_PORT_SEND_ONCE)) break; /* must block waiting for queue to clear */ self = current_thread(); if (option & MACH_SEND_TIMEOUT) { if (time_out == 0) { ip_unlock(port); return MACH_SEND_TIMED_OUT; } thread_will_wait_with_timeout(self, time_out); } else thread_will_wait(self); ipc_thread_enqueue(&port->ip_blocked, self); self->ith_state = MACH_SEND_IN_PROGRESS; ip_unlock(port); counter(c_ipc_mqueue_send_block++); thread_block((void (*)(void)) 0); ip_lock(port); /* why did we wake up? */ if (self->ith_state == MACH_MSG_SUCCESS) continue; assert(self->ith_state == MACH_SEND_IN_PROGRESS); /* take ourselves off blocked queue */ ipc_thread_rmqueue(&port->ip_blocked, self); /* * Thread wakeup-reason field tells us why * the wait was interrupted. */ switch (self->ith_wait_result) { case THREAD_INTERRUPTED: /* send was interrupted - give up */ ip_unlock(port); return MACH_SEND_INTERRUPTED; case THREAD_TIMED_OUT: /* timeout expired */ assert(option & MACH_SEND_TIMEOUT); time_out = 0; break; case THREAD_RESTART: default: #if MACH_ASSERT assert(!"ipc_mqueue_send"); #else panic("ipc_mqueue_send"); #endif } } if (kmsg->ikm_header.msgh_bits & MACH_MSGH_BITS_CIRCULAR) { ip_unlock(port); /* don't allow the creation of a circular loop */ ipc_kmsg_destroy(kmsg); return MACH_MSG_SUCCESS; } { ipc_mqueue_t mqueue; ipc_pset_t pset; ipc_thread_t receiver; ipc_thread_queue_t receivers; port->ip_msgcount++; assert(port->ip_msgcount > 0); pset = port->ip_pset; if (pset == IPS_NULL) mqueue = &port->ip_messages; else mqueue = &pset->ips_messages; imq_lock(mqueue); receivers = &mqueue->imq_threads; /* * Can unlock the port now that the msg queue is locked * and we know the port is active. While the msg queue * is locked, we have control of the kmsg, so the ref in * it for the port is still good. If the msg queue is in * a set (dead or alive), then we're OK because the port * is still a member of the set and the set won't go away * until the port is taken out, which tries to lock the * set's msg queue to remove the port's msgs. */ ip_unlock(port); /* check for a receiver for the message */ for (;;) { receiver = ipc_thread_queue_first(receivers); if (receiver == ITH_NULL) { /* no receivers; queue kmsg */ ipc_kmsg_enqueue_macro(&mqueue->imq_messages, kmsg); imq_unlock(mqueue); break; } ipc_thread_rmqueue_first_macro(receivers, receiver); assert(ipc_kmsg_queue_empty(&mqueue->imq_messages)); if (kmsg->ikm_header.msgh_size <= receiver->ith_msize) { /* got a successful receiver */ receiver->ith_state = MACH_MSG_SUCCESS; receiver->ith_kmsg = kmsg; receiver->ith_seqno = port->ip_seqno++; imq_unlock(mqueue); thread_go(receiver); break; } receiver->ith_state = MACH_RCV_TOO_LARGE; receiver->ith_msize = kmsg->ikm_header.msgh_size; thread_go(receiver); } } current_task()->messages_sent++; return MACH_MSG_SUCCESS; }