xref: /openbmc/qemu/net/vhost-user.c (revision 53c7c924)
1 /*
2  * vhost-user.c
3  *
4  * Copyright (c) 2013 Virtual Open Systems Sarl.
5  *
6  * This work is licensed under the terms of the GNU GPL, version 2 or later.
7  * See the COPYING file in the top-level directory.
8  *
9  */
10 
11 #include "qemu/osdep.h"
12 #include "clients.h"
13 #include "net/vhost_net.h"
14 #include "net/vhost-user.h"
15 #include "hw/virtio/vhost-user.h"
16 #include "chardev/char-fe.h"
17 #include "qapi/error.h"
18 #include "qapi/qapi-commands-net.h"
19 #include "qemu/config-file.h"
20 #include "qemu/error-report.h"
21 #include "qemu/option.h"
22 #include "trace.h"
23 
24 typedef struct NetVhostUserState {
25     NetClientState nc;
26     CharBackend chr; /* only queue index 0 */
27     VhostUserState *vhost_user;
28     VHostNetState *vhost_net;
29     guint watch;
30     uint64_t acked_features;
31     bool started;
32 } NetVhostUserState;
33 
vhost_user_get_vhost_net(NetClientState * nc)34 VHostNetState *vhost_user_get_vhost_net(NetClientState *nc)
35 {
36     NetVhostUserState *s = DO_UPCAST(NetVhostUserState, nc, nc);
37     assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
38     return s->vhost_net;
39 }
40 
vhost_user_get_acked_features(NetClientState * nc)41 uint64_t vhost_user_get_acked_features(NetClientState *nc)
42 {
43     NetVhostUserState *s = DO_UPCAST(NetVhostUserState, nc, nc);
44     assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
45     return s->acked_features;
46 }
47 
vhost_user_save_acked_features(NetClientState * nc)48 void vhost_user_save_acked_features(NetClientState *nc)
49 {
50     NetVhostUserState *s;
51 
52     s = DO_UPCAST(NetVhostUserState, nc, nc);
53     if (s->vhost_net) {
54         uint64_t features = vhost_net_get_acked_features(s->vhost_net);
55         if (features) {
56             s->acked_features = features;
57         }
58     }
59 }
60 
vhost_user_stop(int queues,NetClientState * ncs[])61 static void vhost_user_stop(int queues, NetClientState *ncs[])
62 {
63     int i;
64     NetVhostUserState *s;
65 
66     for (i = 0; i < queues; i++) {
67         assert(ncs[i]->info->type == NET_CLIENT_DRIVER_VHOST_USER);
68 
69         s = DO_UPCAST(NetVhostUserState, nc, ncs[i]);
70 
71         if (s->vhost_net) {
72             vhost_user_save_acked_features(ncs[i]);
73             vhost_net_cleanup(s->vhost_net);
74         }
75     }
76 }
77 
vhost_user_start(int queues,NetClientState * ncs[],VhostUserState * be)78 static int vhost_user_start(int queues, NetClientState *ncs[],
79                             VhostUserState *be)
80 {
81     VhostNetOptions options;
82     struct vhost_net *net = NULL;
83     NetVhostUserState *s;
84     int max_queues;
85     int i;
86 
87     options.backend_type = VHOST_BACKEND_TYPE_USER;
88 
89     for (i = 0; i < queues; i++) {
90         assert(ncs[i]->info->type == NET_CLIENT_DRIVER_VHOST_USER);
91 
92         s = DO_UPCAST(NetVhostUserState, nc, ncs[i]);
93 
94         options.net_backend = ncs[i];
95         options.opaque      = be;
96         options.busyloop_timeout = 0;
97         options.nvqs = 2;
98         net = vhost_net_init(&options);
99         if (!net) {
100             error_report("failed to init vhost_net for queue %d", i);
101             goto err;
102         }
103 
104         if (i == 0) {
105             max_queues = vhost_net_get_max_queues(net);
106             if (queues > max_queues) {
107                 error_report("you are asking more queues than supported: %d",
108                              max_queues);
109                 goto err;
110             }
111         }
112 
113         if (s->vhost_net) {
114             vhost_net_cleanup(s->vhost_net);
115             g_free(s->vhost_net);
116         }
117         s->vhost_net = net;
118     }
119 
120     return 0;
121 
122 err:
123     if (net) {
124         vhost_net_cleanup(net);
125         g_free(net);
126     }
127     vhost_user_stop(i, ncs);
128     return -1;
129 }
130 
vhost_user_receive(NetClientState * nc,const uint8_t * buf,size_t size)131 static ssize_t vhost_user_receive(NetClientState *nc, const uint8_t *buf,
132                                   size_t size)
133 {
134     /* In case of RARP (message size is 60) notify backup to send a fake RARP.
135        This fake RARP will be sent by backend only for guest
136        without GUEST_ANNOUNCE capability.
137      */
138     if (size == 60) {
139         NetVhostUserState *s = DO_UPCAST(NetVhostUserState, nc, nc);
140         int r;
141         static int display_rarp_failure = 1;
142         char mac_addr[6];
143 
144         /* extract guest mac address from the RARP message */
145         memcpy(mac_addr, &buf[6], 6);
146 
147         r = vhost_net_notify_migration_done(s->vhost_net, mac_addr);
148 
149         if ((r != 0) && (display_rarp_failure)) {
150             fprintf(stderr,
151                     "Vhost user backend fails to broadcast fake RARP\n");
152             fflush(stderr);
153             display_rarp_failure = 0;
154         }
155     }
156 
157     return size;
158 }
159 
net_vhost_user_cleanup(NetClientState * nc)160 static void net_vhost_user_cleanup(NetClientState *nc)
161 {
162     NetVhostUserState *s = DO_UPCAST(NetVhostUserState, nc, nc);
163 
164     if (s->vhost_net) {
165         vhost_net_cleanup(s->vhost_net);
166         g_free(s->vhost_net);
167         s->vhost_net = NULL;
168     }
169     if (nc->queue_index == 0) {
170         if (s->watch) {
171             g_source_remove(s->watch);
172             s->watch = 0;
173         }
174         qemu_chr_fe_deinit(&s->chr, true);
175         if (s->vhost_user) {
176             vhost_user_cleanup(s->vhost_user);
177             g_free(s->vhost_user);
178             s->vhost_user = NULL;
179         }
180     }
181 
182     qemu_purge_queued_packets(nc);
183 }
184 
vhost_user_set_vnet_endianness(NetClientState * nc,bool enable)185 static int vhost_user_set_vnet_endianness(NetClientState *nc,
186                                           bool enable)
187 {
188     /* Nothing to do.  If the server supports
189      * VHOST_USER_PROTOCOL_F_CROSS_ENDIAN, it will get the
190      * vnet header endianness from there.  If it doesn't, negotiation
191      * fails.
192      */
193     return 0;
194 }
195 
vhost_user_has_vnet_hdr(NetClientState * nc)196 static bool vhost_user_has_vnet_hdr(NetClientState *nc)
197 {
198     assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
199 
200     return true;
201 }
202 
vhost_user_has_ufo(NetClientState * nc)203 static bool vhost_user_has_ufo(NetClientState *nc)
204 {
205     assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
206 
207     return true;
208 }
209 
vhost_user_check_peer_type(NetClientState * nc,ObjectClass * oc,Error ** errp)210 static bool vhost_user_check_peer_type(NetClientState *nc, ObjectClass *oc,
211                                        Error **errp)
212 {
213     const char *driver = object_class_get_name(oc);
214 
215     if (!g_str_has_prefix(driver, "virtio-net-")) {
216         error_setg(errp, "vhost-user requires frontend driver virtio-net-*");
217         return false;
218     }
219 
220     return true;
221 }
222 
223 static NetClientInfo net_vhost_user_info = {
224         .type = NET_CLIENT_DRIVER_VHOST_USER,
225         .size = sizeof(NetVhostUserState),
226         .receive = vhost_user_receive,
227         .cleanup = net_vhost_user_cleanup,
228         .has_vnet_hdr = vhost_user_has_vnet_hdr,
229         .has_ufo = vhost_user_has_ufo,
230         .set_vnet_be = vhost_user_set_vnet_endianness,
231         .set_vnet_le = vhost_user_set_vnet_endianness,
232         .check_peer_type = vhost_user_check_peer_type,
233 };
234 
net_vhost_user_watch(void * do_not_use,GIOCondition cond,void * opaque)235 static gboolean net_vhost_user_watch(void *do_not_use, GIOCondition cond,
236                                      void *opaque)
237 {
238     NetVhostUserState *s = opaque;
239 
240     qemu_chr_fe_disconnect(&s->chr);
241 
242     return G_SOURCE_CONTINUE;
243 }
244 
245 static void net_vhost_user_event(void *opaque, QEMUChrEvent event);
246 
chr_closed_bh(void * opaque)247 static void chr_closed_bh(void *opaque)
248 {
249     const char *name = opaque;
250     NetClientState *ncs[MAX_QUEUE_NUM];
251     NetVhostUserState *s;
252     Error *err = NULL;
253     int queues, i;
254 
255     queues = qemu_find_net_clients_except(name, ncs,
256                                           NET_CLIENT_DRIVER_NIC,
257                                           MAX_QUEUE_NUM);
258     assert(queues < MAX_QUEUE_NUM);
259 
260     s = DO_UPCAST(NetVhostUserState, nc, ncs[0]);
261 
262     for (i = queues -1; i >= 0; i--) {
263         vhost_user_save_acked_features(ncs[i]);
264     }
265 
266     qmp_set_link(name, false, &err);
267 
268     qemu_chr_fe_set_handlers(&s->chr, NULL, NULL, net_vhost_user_event,
269                              NULL, opaque, NULL, true);
270 
271     if (err) {
272         error_report_err(err);
273     }
274 }
275 
net_vhost_user_event(void * opaque,QEMUChrEvent event)276 static void net_vhost_user_event(void *opaque, QEMUChrEvent event)
277 {
278     const char *name = opaque;
279     NetClientState *ncs[MAX_QUEUE_NUM];
280     NetVhostUserState *s;
281     Chardev *chr;
282     Error *err = NULL;
283     int queues;
284 
285     queues = qemu_find_net_clients_except(name, ncs,
286                                           NET_CLIENT_DRIVER_NIC,
287                                           MAX_QUEUE_NUM);
288     assert(queues < MAX_QUEUE_NUM);
289 
290     s = DO_UPCAST(NetVhostUserState, nc, ncs[0]);
291     chr = qemu_chr_fe_get_driver(&s->chr);
292     trace_vhost_user_event(chr->label, event);
293     switch (event) {
294     case CHR_EVENT_OPENED:
295         if (vhost_user_start(queues, ncs, s->vhost_user) < 0) {
296             qemu_chr_fe_disconnect(&s->chr);
297             return;
298         }
299         s->watch = qemu_chr_fe_add_watch(&s->chr, G_IO_HUP,
300                                          net_vhost_user_watch, s);
301         qmp_set_link(name, true, &err);
302         s->started = true;
303         break;
304     case CHR_EVENT_CLOSED:
305         /* a close event may happen during a read/write, but vhost
306          * code assumes the vhost_dev remains setup, so delay the
307          * stop & clear to idle.
308          * FIXME: better handle failure in vhost code, remove bh
309          */
310         if (s->watch) {
311             AioContext *ctx = qemu_get_current_aio_context();
312 
313             g_source_remove(s->watch);
314             s->watch = 0;
315             qemu_chr_fe_set_handlers(&s->chr, NULL, NULL, NULL, NULL,
316                                      NULL, NULL, false);
317 
318             aio_bh_schedule_oneshot(ctx, chr_closed_bh, opaque);
319         }
320         break;
321     case CHR_EVENT_BREAK:
322     case CHR_EVENT_MUX_IN:
323     case CHR_EVENT_MUX_OUT:
324         /* Ignore */
325         break;
326     }
327 
328     if (err) {
329         error_report_err(err);
330     }
331 }
332 
net_vhost_user_init(NetClientState * peer,const char * device,const char * name,Chardev * chr,int queues)333 static int net_vhost_user_init(NetClientState *peer, const char *device,
334                                const char *name, Chardev *chr,
335                                int queues)
336 {
337     Error *err = NULL;
338     NetClientState *nc, *nc0 = NULL;
339     NetVhostUserState *s = NULL;
340     VhostUserState *user;
341     int i;
342 
343     assert(name);
344     assert(queues > 0);
345 
346     user = g_new0(struct VhostUserState, 1);
347     for (i = 0; i < queues; i++) {
348         nc = qemu_new_net_client(&net_vhost_user_info, peer, device, name);
349         qemu_set_info_str(nc, "vhost-user%d to %s", i, chr->label);
350         nc->queue_index = i;
351         if (!nc0) {
352             nc0 = nc;
353             s = DO_UPCAST(NetVhostUserState, nc, nc);
354             if (!qemu_chr_fe_init(&s->chr, chr, &err) ||
355                 !vhost_user_init(user, &s->chr, &err)) {
356                 error_report_err(err);
357                 goto err;
358             }
359         }
360         s = DO_UPCAST(NetVhostUserState, nc, nc);
361         s->vhost_user = user;
362     }
363 
364     s = DO_UPCAST(NetVhostUserState, nc, nc0);
365     do {
366         if (qemu_chr_fe_wait_connected(&s->chr, &err) < 0) {
367             error_report_err(err);
368             goto err;
369         }
370         qemu_chr_fe_set_handlers(&s->chr, NULL, NULL,
371                                  net_vhost_user_event, NULL, nc0->name, NULL,
372                                  true);
373     } while (!s->started);
374 
375     assert(s->vhost_net);
376 
377     return 0;
378 
379 err:
380     if (user) {
381         vhost_user_cleanup(user);
382         g_free(user);
383         if (s) {
384             s->vhost_user = NULL;
385         }
386     }
387     if (nc0) {
388         qemu_del_net_client(nc0);
389     }
390 
391     return -1;
392 }
393 
net_vhost_claim_chardev(const NetdevVhostUserOptions * opts,Error ** errp)394 static Chardev *net_vhost_claim_chardev(
395     const NetdevVhostUserOptions *opts, Error **errp)
396 {
397     Chardev *chr = qemu_chr_find(opts->chardev);
398 
399     if (chr == NULL) {
400         error_setg(errp, "chardev \"%s\" not found", opts->chardev);
401         return NULL;
402     }
403 
404     if (!qemu_chr_has_feature(chr, QEMU_CHAR_FEATURE_RECONNECTABLE)) {
405         error_setg(errp, "chardev \"%s\" is not reconnectable",
406                    opts->chardev);
407         return NULL;
408     }
409     if (!qemu_chr_has_feature(chr, QEMU_CHAR_FEATURE_FD_PASS)) {
410         error_setg(errp, "chardev \"%s\" does not support FD passing",
411                    opts->chardev);
412         return NULL;
413     }
414 
415     return chr;
416 }
417 
net_init_vhost_user(const Netdev * netdev,const char * name,NetClientState * peer,Error ** errp)418 int net_init_vhost_user(const Netdev *netdev, const char *name,
419                         NetClientState *peer, Error **errp)
420 {
421     int queues;
422     const NetdevVhostUserOptions *vhost_user_opts;
423     Chardev *chr;
424 
425     assert(netdev->type == NET_CLIENT_DRIVER_VHOST_USER);
426     vhost_user_opts = &netdev->u.vhost_user;
427 
428     chr = net_vhost_claim_chardev(vhost_user_opts, errp);
429     if (!chr) {
430         return -1;
431     }
432 
433     queues = vhost_user_opts->has_queues ? vhost_user_opts->queues : 1;
434     if (queues < 1 || queues > MAX_QUEUE_NUM) {
435         error_setg(errp,
436                    "vhost-user number of queues must be in range [1, %d]",
437                    MAX_QUEUE_NUM);
438         return -1;
439     }
440 
441     return net_vhost_user_init(peer, "vhost_user", name, chr, queues);
442 }
443