xref: /openbmc/qemu/util/fdmon-epoll.c (revision 7d87775f)
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 /*
3  * epoll(7) file descriptor monitoring
4  */
5 
6 #include "qemu/osdep.h"
7 #include <sys/epoll.h>
8 #include "qemu/lockcnt.h"
9 #include "qemu/rcu_queue.h"
10 #include "aio-posix.h"
11 
12 /* The fd number threshold to switch to epoll */
13 #define EPOLL_ENABLE_THRESHOLD 64
14 
15 void fdmon_epoll_disable(AioContext *ctx)
16 {
17     if (ctx->epollfd >= 0) {
18         close(ctx->epollfd);
19         ctx->epollfd = -1;
20     }
21 
22     /* Switch back */
23     ctx->fdmon_ops = &fdmon_poll_ops;
24 }
25 
26 static inline int epoll_events_from_pfd(int pfd_events)
27 {
28     return (pfd_events & G_IO_IN ? EPOLLIN : 0) |
29            (pfd_events & G_IO_OUT ? EPOLLOUT : 0) |
30            (pfd_events & G_IO_HUP ? EPOLLHUP : 0) |
31            (pfd_events & G_IO_ERR ? EPOLLERR : 0);
32 }
33 
34 static void fdmon_epoll_update(AioContext *ctx,
35                                AioHandler *old_node,
36                                AioHandler *new_node)
37 {
38     struct epoll_event event = {
39         .data.ptr = new_node,
40         .events = new_node ? epoll_events_from_pfd(new_node->pfd.events) : 0,
41     };
42     int r;
43 
44     if (!new_node) {
45         r = epoll_ctl(ctx->epollfd, EPOLL_CTL_DEL, old_node->pfd.fd, &event);
46     } else if (!old_node) {
47         r = epoll_ctl(ctx->epollfd, EPOLL_CTL_ADD, new_node->pfd.fd, &event);
48     } else {
49         r = epoll_ctl(ctx->epollfd, EPOLL_CTL_MOD, new_node->pfd.fd, &event);
50     }
51 
52     if (r) {
53         fdmon_epoll_disable(ctx);
54     }
55 }
56 
57 static int fdmon_epoll_wait(AioContext *ctx, AioHandlerList *ready_list,
58                             int64_t timeout)
59 {
60     GPollFD pfd = {
61         .fd = ctx->epollfd,
62         .events = G_IO_IN | G_IO_OUT | G_IO_HUP | G_IO_ERR,
63     };
64     AioHandler *node;
65     int i, ret = 0;
66     struct epoll_event events[128];
67 
68     if (timeout > 0) {
69         ret = qemu_poll_ns(&pfd, 1, timeout);
70         if (ret > 0) {
71             timeout = 0;
72         }
73     }
74     if (timeout <= 0 || ret > 0) {
75         ret = epoll_wait(ctx->epollfd, events,
76                          ARRAY_SIZE(events),
77                          timeout);
78         if (ret <= 0) {
79             goto out;
80         }
81         for (i = 0; i < ret; i++) {
82             int ev = events[i].events;
83             int revents = (ev & EPOLLIN ? G_IO_IN : 0) |
84                           (ev & EPOLLOUT ? G_IO_OUT : 0) |
85                           (ev & EPOLLHUP ? G_IO_HUP : 0) |
86                           (ev & EPOLLERR ? G_IO_ERR : 0);
87 
88             node = events[i].data.ptr;
89             aio_add_ready_handler(ready_list, node, revents);
90         }
91     }
92 out:
93     return ret;
94 }
95 
96 static const FDMonOps fdmon_epoll_ops = {
97     .update = fdmon_epoll_update,
98     .wait = fdmon_epoll_wait,
99     .need_wait = aio_poll_disabled,
100 };
101 
102 static bool fdmon_epoll_try_enable(AioContext *ctx)
103 {
104     AioHandler *node;
105     struct epoll_event event;
106 
107     QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
108         int r;
109         if (QLIST_IS_INSERTED(node, node_deleted) || !node->pfd.events) {
110             continue;
111         }
112         event.events = epoll_events_from_pfd(node->pfd.events);
113         event.data.ptr = node;
114         r = epoll_ctl(ctx->epollfd, EPOLL_CTL_ADD, node->pfd.fd, &event);
115         if (r) {
116             return false;
117         }
118     }
119 
120     ctx->fdmon_ops = &fdmon_epoll_ops;
121     return true;
122 }
123 
124 bool fdmon_epoll_try_upgrade(AioContext *ctx, unsigned npfd)
125 {
126     bool ok;
127 
128     if (ctx->epollfd < 0) {
129         return false;
130     }
131 
132     if (npfd < EPOLL_ENABLE_THRESHOLD) {
133         return false;
134     }
135 
136     /* The list must not change while we add fds to epoll */
137     if (!qemu_lockcnt_dec_if_lock(&ctx->list_lock)) {
138         return false;
139     }
140 
141     ok = fdmon_epoll_try_enable(ctx);
142 
143     qemu_lockcnt_inc_and_unlock(&ctx->list_lock);
144 
145     if (!ok) {
146         fdmon_epoll_disable(ctx);
147     }
148     return ok;
149 }
150 
151 void fdmon_epoll_setup(AioContext *ctx)
152 {
153     ctx->epollfd = epoll_create1(EPOLL_CLOEXEC);
154     if (ctx->epollfd == -1) {
155         fprintf(stderr, "Failed to create epoll instance: %s", strerror(errno));
156     }
157 }
158