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 "clients.h" 12 #include "net/vhost_net.h" 13 #include "net/vhost-user.h" 14 #include "sysemu/char.h" 15 #include "qemu/config-file.h" 16 #include "qemu/error-report.h" 17 #include "qmp-commands.h" 18 19 typedef struct VhostUserState { 20 NetClientState nc; 21 CharDriverState *chr; 22 VHostNetState *vhost_net; 23 } VhostUserState; 24 25 typedef struct VhostUserChardevProps { 26 bool is_socket; 27 bool is_unix; 28 bool is_server; 29 } VhostUserChardevProps; 30 31 VHostNetState *vhost_user_get_vhost_net(NetClientState *nc) 32 { 33 VhostUserState *s = DO_UPCAST(VhostUserState, nc, nc); 34 assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER); 35 return s->vhost_net; 36 } 37 38 static int vhost_user_running(VhostUserState *s) 39 { 40 return (s->vhost_net) ? 1 : 0; 41 } 42 43 static void vhost_user_stop(int queues, NetClientState *ncs[]) 44 { 45 VhostUserState *s; 46 int i; 47 48 for (i = 0; i < queues; i++) { 49 assert (ncs[i]->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER); 50 51 s = DO_UPCAST(VhostUserState, nc, ncs[i]); 52 if (!vhost_user_running(s)) { 53 continue; 54 } 55 56 if (s->vhost_net) { 57 vhost_net_cleanup(s->vhost_net); 58 s->vhost_net = NULL; 59 } 60 } 61 } 62 63 static int vhost_user_start(int queues, NetClientState *ncs[]) 64 { 65 VhostNetOptions options; 66 VhostUserState *s; 67 int max_queues; 68 int i; 69 70 options.backend_type = VHOST_BACKEND_TYPE_USER; 71 72 for (i = 0; i < queues; i++) { 73 assert (ncs[i]->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER); 74 75 s = DO_UPCAST(VhostUserState, nc, ncs[i]); 76 if (vhost_user_running(s)) { 77 continue; 78 } 79 80 options.net_backend = ncs[i]; 81 options.opaque = s->chr; 82 s->vhost_net = vhost_net_init(&options); 83 if (!s->vhost_net) { 84 error_report("failed to init vhost_net for queue %d\n", i); 85 goto err; 86 } 87 88 if (i == 0) { 89 max_queues = vhost_net_get_max_queues(s->vhost_net); 90 if (queues > max_queues) { 91 error_report("you are asking more queues than " 92 "supported: %d\n", max_queues); 93 goto err; 94 } 95 } 96 } 97 98 return 0; 99 100 err: 101 vhost_user_stop(i + 1, ncs); 102 return -1; 103 } 104 105 static void vhost_user_cleanup(NetClientState *nc) 106 { 107 VhostUserState *s = DO_UPCAST(VhostUserState, nc, nc); 108 109 if (s->vhost_net) { 110 vhost_net_cleanup(s->vhost_net); 111 s->vhost_net = NULL; 112 } 113 114 qemu_purge_queued_packets(nc); 115 } 116 117 static bool vhost_user_has_vnet_hdr(NetClientState *nc) 118 { 119 assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER); 120 121 return true; 122 } 123 124 static bool vhost_user_has_ufo(NetClientState *nc) 125 { 126 assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER); 127 128 return true; 129 } 130 131 static NetClientInfo net_vhost_user_info = { 132 .type = NET_CLIENT_OPTIONS_KIND_VHOST_USER, 133 .size = sizeof(VhostUserState), 134 .cleanup = vhost_user_cleanup, 135 .has_vnet_hdr = vhost_user_has_vnet_hdr, 136 .has_ufo = vhost_user_has_ufo, 137 }; 138 139 static void net_vhost_user_event(void *opaque, int event) 140 { 141 const char *name = opaque; 142 NetClientState *ncs[MAX_QUEUE_NUM]; 143 VhostUserState *s; 144 Error *err = NULL; 145 int queues; 146 147 queues = qemu_find_net_clients_except(name, ncs, 148 NET_CLIENT_OPTIONS_KIND_NIC, 149 MAX_QUEUE_NUM); 150 s = DO_UPCAST(VhostUserState, nc, ncs[0]); 151 switch (event) { 152 case CHR_EVENT_OPENED: 153 if (vhost_user_start(queues, ncs) < 0) { 154 exit(1); 155 } 156 qmp_set_link(name, true, &err); 157 error_report("chardev \"%s\" went up", s->chr->label); 158 break; 159 case CHR_EVENT_CLOSED: 160 qmp_set_link(name, true, &err); 161 vhost_user_stop(queues, ncs); 162 error_report("chardev \"%s\" went down", s->chr->label); 163 break; 164 } 165 166 if (err) { 167 error_report_err(err); 168 } 169 } 170 171 static int net_vhost_user_init(NetClientState *peer, const char *device, 172 const char *name, CharDriverState *chr, 173 int queues) 174 { 175 NetClientState *nc; 176 VhostUserState *s; 177 int i; 178 179 for (i = 0; i < queues; i++) { 180 nc = qemu_new_net_client(&net_vhost_user_info, peer, device, name); 181 182 snprintf(nc->info_str, sizeof(nc->info_str), "vhost-user%d to %s", 183 i, chr->label); 184 185 /* We don't provide a receive callback */ 186 nc->receive_disabled = 1; 187 nc->queue_index = i; 188 189 s = DO_UPCAST(VhostUserState, nc, nc); 190 s->chr = chr; 191 } 192 193 qemu_chr_add_handlers(chr, NULL, NULL, net_vhost_user_event, (void*)name); 194 195 return 0; 196 } 197 198 static int net_vhost_chardev_opts(void *opaque, 199 const char *name, const char *value, 200 Error **errp) 201 { 202 VhostUserChardevProps *props = opaque; 203 204 if (strcmp(name, "backend") == 0 && strcmp(value, "socket") == 0) { 205 props->is_socket = true; 206 } else if (strcmp(name, "path") == 0) { 207 props->is_unix = true; 208 } else if (strcmp(name, "server") == 0) { 209 props->is_server = true; 210 } else { 211 error_setg(errp, 212 "vhost-user does not support a chardev with option %s=%s", 213 name, value); 214 return -1; 215 } 216 return 0; 217 } 218 219 static CharDriverState *net_vhost_parse_chardev( 220 const NetdevVhostUserOptions *opts, Error **errp) 221 { 222 CharDriverState *chr = qemu_chr_find(opts->chardev); 223 VhostUserChardevProps props; 224 225 if (chr == NULL) { 226 error_setg(errp, "chardev \"%s\" not found", opts->chardev); 227 return NULL; 228 } 229 230 /* inspect chardev opts */ 231 memset(&props, 0, sizeof(props)); 232 if (qemu_opt_foreach(chr->opts, net_vhost_chardev_opts, &props, errp)) { 233 return NULL; 234 } 235 236 if (!props.is_socket || !props.is_unix) { 237 error_setg(errp, "chardev \"%s\" is not a unix socket", 238 opts->chardev); 239 return NULL; 240 } 241 242 qemu_chr_fe_claim_no_fail(chr); 243 244 return chr; 245 } 246 247 static int net_vhost_check_net(void *opaque, QemuOpts *opts, Error **errp) 248 { 249 const char *name = opaque; 250 const char *driver, *netdev; 251 const char virtio_name[] = "virtio-net-"; 252 253 driver = qemu_opt_get(opts, "driver"); 254 netdev = qemu_opt_get(opts, "netdev"); 255 256 if (!driver || !netdev) { 257 return 0; 258 } 259 260 if (strcmp(netdev, name) == 0 && 261 strncmp(driver, virtio_name, strlen(virtio_name)) != 0) { 262 error_setg(errp, "vhost-user requires frontend driver virtio-net-*"); 263 return -1; 264 } 265 266 return 0; 267 } 268 269 int net_init_vhost_user(const NetClientOptions *opts, const char *name, 270 NetClientState *peer, Error **errp) 271 { 272 int queues; 273 const NetdevVhostUserOptions *vhost_user_opts; 274 CharDriverState *chr; 275 276 assert(opts->kind == NET_CLIENT_OPTIONS_KIND_VHOST_USER); 277 vhost_user_opts = opts->vhost_user; 278 279 chr = net_vhost_parse_chardev(vhost_user_opts, errp); 280 if (!chr) { 281 return -1; 282 } 283 284 /* verify net frontend */ 285 if (qemu_opts_foreach(qemu_find_opts("device"), net_vhost_check_net, 286 (char *)name, errp)) { 287 return -1; 288 } 289 290 queues = vhost_user_opts->has_queues ? vhost_user_opts->queues : 1; 291 292 return net_vhost_user_init(peer, "vhost_user", name, chr, queues); 293 } 294