]> git.saurik.com Git - apple/xnu.git/blobdiff - osfmk/ipc/ipc_pset.c
xnu-2782.10.72.tar.gz
[apple/xnu.git] / osfmk / ipc / ipc_pset.c
index bf805af557164400d6e348d41c3fbf0cd1c54faa..533ee3f08a67453003d642e7566d171bca6dfb65 100644 (file)
 #include <ipc/ipc_right.h>
 #include <ipc/ipc_space.h>
 #include <ipc/ipc_port.h>
-#include <ipc/ipc_print.h>
 
 #include <kern/kern_types.h>
 #include <kern/spl.h>
+
+#include <vm/vm_map.h>
+
 /*
  *     Routine:        ipc_pset_alloc
  *     Purpose:
@@ -105,10 +107,11 @@ ipc_pset_alloc(
                              &name, (ipc_object_t *) &pset);
        if (kr != KERN_SUCCESS)
                return kr;
-       /* pset is locked */
+       /* pset and space are locked */
 
        pset->ips_local_name = name;
        ipc_mqueue_init(&pset->ips_messages, TRUE /* set */);
+       is_write_unlock(space);
 
        *namep = name;
        *psetp = pset;
@@ -183,15 +186,16 @@ ipc_pset_member(
 
 kern_return_t
 ipc_pset_add(
-       ipc_pset_t      pset,
-       ipc_port_t      port)
+       ipc_pset_t        pset,
+       ipc_port_t        port,
+       wait_queue_link_t wql)
 {
        kern_return_t kr;
 
        assert(ips_active(pset));
        assert(ip_active(port));
        
-       kr = ipc_mqueue_add(&port->ip_messages, &pset->ips_messages);
+       kr = ipc_mqueue_add(&port->ip_messages, &pset->ips_messages, wql);
 
        if (kr == KERN_SUCCESS)
                port->ip_pset_count++;
@@ -213,8 +217,9 @@ ipc_pset_add(
 
 kern_return_t
 ipc_pset_remove(
-       ipc_pset_t      pset,
-       ipc_port_t      port)
+       ipc_pset_t        pset,
+       ipc_port_t        port,
+       wait_queue_link_t *wqlp)
 {
        kern_return_t kr;
 
@@ -223,7 +228,7 @@ ipc_pset_remove(
        if (port->ip_pset_count == 0)
                return KERN_NOT_IN_SET;
 
-       kr = ipc_mqueue_remove(&port->ip_messages, &pset->ips_messages);
+       kr = ipc_mqueue_remove(&port->ip_messages, &pset->ips_messages, wqlp);
 
        if (kr == KERN_SUCCESS)
                port->ip_pset_count--;
@@ -241,7 +246,8 @@ ipc_pset_remove(
 
 kern_return_t
 ipc_pset_remove_from_all(
-       ipc_port_t      port)
+       ipc_port_t      port,
+       queue_t         links)
 {
        assert(ip_active(port));
        
@@ -251,7 +257,7 @@ ipc_pset_remove_from_all(
        /* 
         * Remove the port's mqueue from all sets
         */
-       ipc_mqueue_remove_from_all(&port->ip_messages);
+       ipc_mqueue_remove_from_all(&port->ip_messages, links);
        port->ip_pset_count = 0;
        return KERN_SUCCESS;
 }
@@ -272,6 +278,11 @@ 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));
 
@@ -280,67 +291,271 @@ ipc_pset_destroy(
        /*
         * remove all the member message queues
         */
-       ipc_mqueue_remove_all(&pset->ips_messages);
+       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);
 
-       /* XXXX Perhaps ought to verify ips_thread_pool is empty */
+       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);
+       }
 
-       ips_release(pset);      /* consume the ref our caller gave us */
-       ips_check_unlock(pset);
 }
 
-#include <mach_kdb.h>
-#if    MACH_KDB
+/* Kqueue EVFILT_MACHPORT support */
+
+#include <sys/errno.h>
+
+static int      filt_machportattach(struct knote *kn);
+static void    filt_machportdetach(struct knote *kn);
+static int     filt_machport(struct knote *kn, long hint);
+static void     filt_machporttouch(struct knote *kn, struct kevent64_s *kev, long type);
+static unsigned filt_machportpeek(struct knote *kn);
+struct filterops machport_filtops = {
+        .f_attach = filt_machportattach,
+        .f_detach = filt_machportdetach,
+        .f_event = filt_machport,
+        .f_touch = filt_machporttouch,
+       .f_peek = filt_machportpeek,
+};
+
+static int
+filt_machportattach(
+        struct knote *kn)
+{
+        mach_port_name_t        name = (mach_port_name_t)kn->kn_kevent.ident;
+       wait_queue_link_t       wql = wait_queue_link_allocate();
+        ipc_pset_t              pset = IPS_NULL;
+        int                     result = ENOSYS;
+        kern_return_t           kr;
+
+        kr = ipc_object_translate(current_space(), name,
+                                  MACH_PORT_RIGHT_PORT_SET,
+                                  (ipc_object_t *)&pset);
+        if (kr != KERN_SUCCESS) {
+               wait_queue_link_free(wql);
+                return (kr == KERN_INVALID_NAME ? ENOENT : ENOTSUP);
+        }
+        /* We've got a lock on pset */
+
+       /* 
+        * Bind the portset wait queue directly to knote/kqueue.
+        * This allows us to just use wait_queue foo to effect a wakeup,
+        * rather than having to call knote() from the Mach code on each
+        * message.
+        */
+       result = knote_link_wait_queue(kn, &pset->ips_messages.imq_wait_queue, wql);
+       if (result == 0) {
+               /* keep a reference for the knote */
+               kn->kn_ptr.p_pset = pset; 
+               ips_reference(pset);
+               ips_unlock(pset);
+               return 0;
+       }
 
-#include <ddb/db_output.h>
+       ips_unlock(pset);
+       wait_queue_link_free(wql);
+       return result;
+}
 
-#define        printf  kdbprintf
+static void
+filt_machportdetach(
+        struct knote *kn)
+{
+        ipc_pset_t              pset = kn->kn_ptr.p_pset;
+       wait_queue_link_t       wql = WAIT_QUEUE_LINK_NULL;
 
-int
-ipc_list_count(
-       struct ipc_kmsg *base)
+       /*
+        * Unlink the portset wait queue from knote/kqueue,
+        * and release our reference on the portset.
+        */
+       ips_lock(pset);
+       (void)knote_unlink_wait_queue(kn, &pset->ips_messages.imq_wait_queue, &wql);
+       kn->kn_ptr.p_pset = IPS_NULL;
+       ips_unlock(pset);
+       ips_release(pset);
+       if (wql != WAIT_QUEUE_LINK_NULL)
+               wait_queue_link_free(wql);
+}
+
+static int
+filt_machport(
+        struct knote *kn,
+        __unused long hint)
 {
-       register int count = 0;
+        mach_port_name_t        name = (mach_port_name_t)kn->kn_kevent.ident;
+        ipc_pset_t              pset = IPS_NULL;
+       wait_result_t           wresult;
+       thread_t                self = current_thread();
+        kern_return_t           kr;
+       mach_msg_option_t       option;
+       mach_msg_size_t         size;
 
-       if (base) {
-               struct ipc_kmsg *kmsg = base;
+       /* never called from below */
+       assert(hint == 0);
 
-               ++count;
-               while (kmsg && kmsg->ikm_next != base
-                           && kmsg->ikm_next != IKM_BOGUS){
-                       kmsg = kmsg->ikm_next;
-                       ++count;
+       /*
+        * called from user context. Have to validate the
+        * name.  If it changed, we have an EOF situation.
+        */
+       kr = ipc_object_translate(current_space(), name,
+                                 MACH_PORT_RIGHT_PORT_SET,
+                                 (ipc_object_t *)&pset);
+       if (kr != KERN_SUCCESS || pset != kn->kn_ptr.p_pset || !ips_active(pset)) {
+               kn->kn_data = 0;
+               kn->kn_flags |= (EV_EOF | EV_ONESHOT);
+               if (pset != IPS_NULL) {
+                       ips_unlock(pset);
                }
+               return(1);
+        }
+
+       /* just use the reference from here on out */
+       ips_reference(pset);
+       ips_unlock(pset); 
+
+       /*
+        * Only honor supported receive options. If no options are
+        * provided, just force a MACH_RCV_TOO_LARGE to detect the
+        * name of the port and sizeof the waiting message.
+        */
+       option = kn->kn_sfflags & (MACH_RCV_MSG|MACH_RCV_LARGE|MACH_RCV_LARGE_IDENTITY|
+                                  MACH_RCV_TRAILER_MASK|MACH_RCV_VOUCHER);
+       if (option & MACH_RCV_MSG) {
+               self->ith_msg_addr = (mach_vm_address_t) kn->kn_ext[0];
+               size = (mach_msg_size_t)kn->kn_ext[1];
+       } else {
+               option = MACH_RCV_LARGE;
+               self->ith_msg_addr = 0;
+               size = 0;
        }
-       return(count);
+
+       /*
+        * Set up to receive a message or the notification of a
+        * too large message.  But never allow this call to wait.
+        * If the user provided aditional options, like trailer
+        * options, pass those through here.  But we don't support
+        * scatter lists through this interface.
+        */
+       self->ith_object = (ipc_object_t)pset;
+       self->ith_msize = size;
+       self->ith_option = option;
+       self->ith_scatter_list_size = 0;
+       self->ith_receiver_name = MACH_PORT_NULL;
+       self->ith_continuation = NULL;
+       option |= MACH_RCV_TIMEOUT; // never wait
+       self->ith_state = MACH_RCV_IN_PROGRESS;
+
+       wresult = ipc_mqueue_receive_on_thread(
+                       &pset->ips_messages,
+                       option,
+                       size, /* max_size */
+                       0, /* immediate timeout */
+                       THREAD_INTERRUPTIBLE,
+                       self);
+       assert(wresult == THREAD_NOT_WAITING);
+       assert(self->ith_state != MACH_RCV_IN_PROGRESS);
+
+       /*
+        * If we timed out, just release the reference on the
+        * portset and return zero.
+        */
+       if (self->ith_state == MACH_RCV_TIMED_OUT) {
+               ips_release(pset);
+               return 0;
+       }
+
+       /*
+        * If we weren't attempting to receive a message
+        * directly, we need to return the port name in
+        * the kevent structure.
+        */
+       if ((option & MACH_RCV_MSG) != MACH_RCV_MSG) {
+               assert(self->ith_state == MACH_RCV_TOO_LARGE);
+               assert(self->ith_kmsg == IKM_NULL);
+               kn->kn_data = self->ith_receiver_name;
+               ips_release(pset);
+               return 1;
+       }
+
+       /*
+        * Attempt to receive the message directly, returning
+        * the results in the fflags field.
+        */
+       assert(option & MACH_RCV_MSG);
+       kn->kn_ext[1] = self->ith_msize;
+       kn->kn_data = MACH_PORT_NULL;
+       kn->kn_fflags = mach_msg_receive_results();
+       /* kmsg and pset reference consumed */
+
+       /*
+        * if the user asked for the identity of ports containing a
+        * a too-large message, return it in the data field (as we
+        * do for messages we didn't try to receive).
+        */
+        if ((kn->kn_fflags == MACH_RCV_TOO_LARGE) &&
+           (option & MACH_RCV_LARGE_IDENTITY))
+           kn->kn_data = self->ith_receiver_name;
+
+       return 1;
+}
+
+static void
+filt_machporttouch(struct knote *kn, struct kevent64_s *kev, long type)
+{
+        switch (type) {
+        case EVENT_REGISTER:
+                kn->kn_sfflags = kev->fflags;
+                kn->kn_sdata = kev->data;
+               kn->kn_ext[0] = kev->ext[0];
+               kn->kn_ext[1] = kev->ext[1];
+                break;
+        case EVENT_PROCESS:
+                *kev = kn->kn_kevent;
+                if (kn->kn_flags & EV_CLEAR) {
+                       kn->kn_data = 0;
+                       kn->kn_fflags = 0;
+               }
+                break;
+        default:
+                panic("filt_machporttouch() - invalid type (%ld)", type);
+                break;
+        }
 }
 
 /*
- *     Routine:        ipc_pset_print
- *     Purpose:
- *             Pretty-print a port set for kdb.
+ * Peek to see if the portset associated with the knote has any
+ * events. This pre-hook is called when a filter uses the stay-
+ * on-queue mechanism (as the knote_link_wait_queue mechanism
+ * does).
+ *
+ * This is called with the kqueue that the knote belongs to still
+ * locked (thus holding a reference on the knote, but restricting
+ * also restricting our ability to take other locks).
+ *
+ * Just peek at the pre-post status of the portset's wait queue
+ * to determine if it has anything interesting.  We can do it
+ * without holding the lock, as it is just a snapshot in time
+ * (if this is used as part of really waiting for events, we
+ * will catch changes in this status when the event gets posted
+ * up to the knote's kqueue).
  */
-void
-ipc_pset_print(
-       ipc_pset_t      pset)
+static unsigned
+filt_machportpeek(struct knote *kn)
 {
-       printf("pset 0x%x\n", pset);
+        ipc_pset_t              pset = kn->kn_ptr.p_pset;
+       ipc_mqueue_t            set_mq = &pset->ips_messages;
 
-       db_indent += 2;
-
-       ipc_object_print(&pset->ips_object);
-       iprintf("local_name = 0x%x\n", pset->ips_local_name);
-       iprintf("%d kmsgs => 0x%x",
-               ipc_list_count(pset->ips_messages.imq_messages.ikmq_base),
-               pset->ips_messages.imq_messages.ikmq_base);
-       printf(",rcvrs queue= 0x%x\n", &pset->ips_messages.imq_wait_queue);
-
-       db_indent -=2;
+       return (ipc_mqueue_set_peek(set_mq));
 }
-
-#endif /* MACH_KDB */