VMM: Sync halting GPCs and interrupt injection
[akaros.git] / user / iplib / epoll.c
index e6dc322..87fdae5 100644 (file)
  *     exec and flags in struct user_fd.
  *     - EPOLL_CTL_MOD is just a DEL then an ADD.  There might be races associated
  *     with that.
- *     - If you close a tracked FD without removing it from the epoll set, the
- *     kernel will turn off the FD tap.  You may still have an epoll event that was
- *     concurrently sent.  Likewise, that FD may be used again by your program, and
- *     if you add *that* one to another epoll set before removing it from the
- *     current one, weird things may happen (like having two epoll ctlrs turning on
- *     and off taps).
  *     - epoll_pwait is probably racy.
- *     - Using spin locks instead of mutexes during syscalls that could block.  The
- *     process won't deadlock, but it will busy wait on something like an RPC,
- *     depending on the device being tapped.
  *     - You can't dup an epoll fd (same as other user FDs).
+ *     - If you add a BSD socket FD to an epoll set before calling listen(), you'll
+ *     only epoll on the data (which is inactive) instead of on the accept().
+ *     - If you add the same BSD socket listener to multiple epoll sets, you will
+ *     likely fail.  This is in addition to being able to tap only one FD at a
+ *     time.
  * */
 
 #include <sys/epoll.h>
 #include <parlib/event.h>
 #include <parlib/ceq.h>
 #include <parlib/uthread.h>
-#include <parlib/spinlock.h>
 #include <parlib/timing.h>
 #include <sys/user_fd.h>
+#include <sys/close_cb.h>
 #include <stdio.h>
 #include <errno.h>
+#include <unistd.h>
+#include <malloc.h>
+#include <sys/queue.h>
+#include <sys/plan9_helpers.h>
 
 /* Sanity check, so we can ID our own FDs */
 #define EPOLL_UFD_MAGIC                0xe9011
 
 struct epoll_ctlr {
+       TAILQ_ENTRY(epoll_ctlr)         link;
        struct event_queue                      *ceq_evq;
        struct ceq                                      *ceq;   /* convenience pointer */
        unsigned int                            size;
-       struct spin_pdr_lock            lock;
+       uth_mutex_t                                     mtx;
        struct user_fd                          ufd;
 };
 
+TAILQ_HEAD(epoll_ctlrs, epoll_ctlr);
+static struct epoll_ctlrs all_ctlrs = TAILQ_HEAD_INITIALIZER(all_ctlrs);
+static uth_mutex_t ctlrs_mtx;
+
 /* There's some bookkeeping we need to maintain on every FD.  Right now, the FD
  * is the index into the CEQ event array, so we can just hook this into the user
  * data blob in the ceq_event.
@@ -150,15 +155,19 @@ static struct event_queue *ep_get_alarm_evq(void)
  * some sort of user deferred destruction. (TODO). */
 static void ep_put_ceq_evq(struct event_queue *ceq_evq)
 {
+#if 0 /* TODO: EVQ/INDIR Cleanup */
        ceq_cleanup(&ceq_evq->ev_mbox->ceq);
        evq_remove_wakeup_ctlr(ceq_evq);
        put_eventq_raw(ceq_evq);
+#endif
 }
 
 static void ep_put_alarm_evq(struct event_queue *alarm_evq)
 {
+#if 0 /* TODO: EVQ/INDIR Cleanup */
        evq_remove_wakeup_ctlr(alarm_evq);
        put_eventq(alarm_evq);
+#endif
 }
 
 static void epoll_close(struct user_fd *ufd)
@@ -193,6 +202,11 @@ static void epoll_close(struct user_fd *ufd)
        } while (nr_done < nr_tap_req);
        free(tap_reqs);
        ep_put_ceq_evq(ep->ceq_evq);
+       uth_mutex_lock(ctlrs_mtx);
+       TAILQ_REMOVE(&all_ctlrs, ep, link);
+       uth_mutex_unlock(ctlrs_mtx);
+       uth_mutex_free(ep->mtx);
+       free(ep);
 }
 
 static int init_ep_ctlr(struct epoll_ctlr *ep, int size)
@@ -202,17 +216,40 @@ static int init_ep_ctlr(struct epoll_ctlr *ep, int size)
        if (size == 1)
                size = 128;
        ep->size = ceq_size;
-       spin_pdr_init(&ep->lock);
+       ep->mtx = uth_mutex_alloc();
        ep->ufd.magic = EPOLL_UFD_MAGIC;
        ep->ufd.close = epoll_close;
        ep->ceq_evq = ep_get_ceq_evq(ceq_size);
        return 0;
 }
 
+static void epoll_fd_closed(int fd)
+{
+       struct epoll_ctlr *ep;
+
+       /* Lockless peek, avoid locking for every close() */
+       if (TAILQ_EMPTY(&all_ctlrs))
+               return;
+       uth_mutex_lock(ctlrs_mtx);
+       TAILQ_FOREACH(ep, &all_ctlrs, link)
+               epoll_ctl(ep->ufd.fd, EPOLL_CTL_DEL, fd, 0);
+       uth_mutex_unlock(ctlrs_mtx);
+}
+
+static void epoll_init(void)
+{
+       static struct close_cb epoll_close_cb = {.func = epoll_fd_closed};
+
+       register_close_cb(&epoll_close_cb);
+       ctlrs_mtx = uth_mutex_alloc();
+}
+
 int epoll_create(int size)
 {
        int fd;
        struct epoll_ctlr *ep;
+
+       run_once(epoll_init());
        /* good thing the arg is a signed int... */
        if (size < 0) {
                errno = EINVAL;
@@ -227,6 +264,9 @@ int epoll_create(int size)
        fd = ufd_get_fd(&ep->ufd);
        if (fd < 0)
                free(ep);
+       uth_mutex_lock(ctlrs_mtx);
+       TAILQ_INSERT_TAIL(&all_ctlrs, ep, link);
+       uth_mutex_unlock(ctlrs_mtx);
        return fd;
 }
 
@@ -243,7 +283,7 @@ static int __epoll_ctl_add(struct epoll_ctlr *ep, int fd,
        struct ceq_event *ceq_ev;
        struct ep_fd_data *ep_fd;
        struct fd_tap_req tap_req = {0};
-       int ret, filter;
+       int ret, filter, sock_listen_fd;
 
        /* Only support ET.  Also, we just ignore EPOLLONESHOT.  That might work,
         * logically, just with spurious events firing. */
@@ -252,6 +292,16 @@ static int __epoll_ctl_add(struct epoll_ctlr *ep, int fd,
                werrstr("Epoll level-triggered not supported");
                return -1;
        }
+       /* The sockets-to-plan9 networking shims are a bit inconvenient.  The user
+        * asked us to epoll on an FD, but that FD is actually a Qdata FD.  We need
+        * to actually epoll on the listen_fd.
+        *
+        * As far as tracking the FD goes for epoll_wait() reporting, if the app
+        * wants to track the FD they think we are using, then they already passed
+        * that in event->data. */
+       sock_listen_fd = _sock_lookup_listen_fd(fd);
+       if (sock_listen_fd >= 0)
+               fd = sock_listen_fd;
        ceq_ev = ep_get_ceq_ev(ep, fd);
        if (!ceq_ev) {
                errno = ENOMEM;
@@ -288,8 +338,13 @@ static int __epoll_ctl_del(struct epoll_ctlr *ep, int fd,
        struct ceq_event *ceq_ev;
        struct ep_fd_data *ep_fd;
        struct fd_tap_req tap_req = {0};
-       int ret;
+       int ret, sock_listen_fd;
 
+       /* They could be asking to clear an epoll for a listener.  We need to remove
+        * the tap for the real FD we tapped */
+       sock_listen_fd = _sock_lookup_listen_fd(fd);
+       if (sock_listen_fd >= 0)
+               fd = sock_listen_fd;
        ceq_ev = ep_get_ceq_ev(ep, fd);
        if (!ceq_ev) {
                errno = ENOENT;
@@ -324,8 +379,7 @@ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
                werrstr("Epoll can't track User FDs");
                return -1;
        }
-       /* TODO: don't use a spinlock, use a mutex.  sys_tap_fds can block. */
-       spin_pdr_lock(&ep->lock);
+       uth_mutex_lock(ep->mtx);
        switch (op) {
                case (EPOLL_CTL_MOD):
                        /* In lieu of a proper MOD, just remove and readd.  The errors might
@@ -347,7 +401,7 @@ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
                        errno = EINVAL;
                        ret = -1;
        }
-       spin_pdr_unlock(&ep->lock);
+       uth_mutex_unlock(ep->mtx);
        return ret;
 }
 
@@ -388,14 +442,14 @@ static int __epoll_wait(struct epoll_ctlr *ep, struct epoll_event *events,
        /* Locking to protect get_ep_event_from_msg, specifically that the ep_fd
         * stored at ceq_ev->user_data does not get concurrently removed and
         * freed. */
-       spin_pdr_lock(&ep->lock);
+       uth_mutex_lock(ep->mtx);
        for (int i = 0; i < maxevents; i++) {
                if (uth_check_evqs(&msg, &which_evq, 1, ep->ceq_evq)) {
                        if (get_ep_event_from_msg(ep, &msg, &events[i]))
                                nr_ret++;
                }
        }
-       spin_pdr_unlock(&ep->lock);
+       uth_mutex_unlock(ep->mtx);
        if (nr_ret)
                return nr_ret;
        if (timeout == 0)
@@ -424,10 +478,10 @@ static int __epoll_wait(struct epoll_ctlr *ep, struct epoll_event *events,
        } else {
                uth_blockon_evqs(&msg, &which_evq, 1, ep->ceq_evq);
        }
-       spin_pdr_lock(&ep->lock);
+       uth_mutex_lock(ep->mtx);
        if (get_ep_event_from_msg(ep, &msg, &events[0]))
                nr_ret++;
-       spin_pdr_unlock(&ep->lock);
+       uth_mutex_unlock(ep->mtx);
        /* We might not have gotten one yet.  And regardless, there might be more
         * available.  Let's try again, with timeout == 0 to ensure no blocking.  We
         * use nr_ret (0 or 1 now) to adjust maxevents and events accordingly. */