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