qemu/net/vhost-user.c
Changchun Ouyang b931bfbf04 vhost-user: add multiple queue support
This patch is initially based a patch from Nikolay Nikolaev.

This patch adds vhost-user multiple queue support, by creating a nc
and vhost_net pair for each queue.

Qemu exits if find that the backend can't support the number of requested
queues (by providing queues=# option). The max number is queried by a
new message, VHOST_USER_GET_QUEUE_NUM, and is sent only when protocol
feature VHOST_USER_PROTOCOL_F_MQ is present first.

The max queue check is done at vhost-user initiation stage. We initiate
one queue first, which, in the meantime, also gets the max_queues the
backend supports.

In older version, it was reported that some messages are sent more times
than necessary. Here we came an agreement with Michael that we could
categorize vhost user messages to 2 types: non-vring specific messages,
which should be sent only once, and vring specific messages, which should
be sent per queue.

Here I introduced a helper function vhost_user_one_time_request(), which
lists following messages as non-vring specific messages:

        VHOST_USER_SET_OWNER
        VHOST_USER_RESET_DEVICE
        VHOST_USER_SET_MEM_TABLE
        VHOST_USER_GET_QUEUE_NUM

For above messages, we simply ignore them when they are not sent the first
time.

Signed-off-by: Nikolay Nikolaev <n.nikolaev@virtualopensystems.com>
Signed-off-by: Changchun Ouyang <changchun.ouyang@intel.com>
Signed-off-by: Yuanhan Liu <yuanhan.liu@linux.intel.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Yuanhan Liu <yuanhan.liu@linux.intel.com>
Reviewed-by: Jason Wang <jasowang@redhat.com>
Tested-by: Marcel Apfelbaum <marcel@redhat.com>
2015-09-24 16:27:53 +03:00

294 lines
7.5 KiB
C

/*
* vhost-user.c
*
* Copyright (c) 2013 Virtual Open Systems Sarl.
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*
*/
#include "clients.h"
#include "net/vhost_net.h"
#include "net/vhost-user.h"
#include "sysemu/char.h"
#include "qemu/config-file.h"
#include "qemu/error-report.h"
#include "qmp-commands.h"
typedef struct VhostUserState {
NetClientState nc;
CharDriverState *chr;
VHostNetState *vhost_net;
} VhostUserState;
typedef struct VhostUserChardevProps {
bool is_socket;
bool is_unix;
bool is_server;
} VhostUserChardevProps;
VHostNetState *vhost_user_get_vhost_net(NetClientState *nc)
{
VhostUserState *s = DO_UPCAST(VhostUserState, nc, nc);
assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
return s->vhost_net;
}
static int vhost_user_running(VhostUserState *s)
{
return (s->vhost_net) ? 1 : 0;
}
static void vhost_user_stop(int queues, NetClientState *ncs[])
{
VhostUserState *s;
int i;
for (i = 0; i < queues; i++) {
assert (ncs[i]->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
s = DO_UPCAST(VhostUserState, nc, ncs[i]);
if (!vhost_user_running(s)) {
continue;
}
if (s->vhost_net) {
vhost_net_cleanup(s->vhost_net);
s->vhost_net = NULL;
}
}
}
static int vhost_user_start(int queues, NetClientState *ncs[])
{
VhostNetOptions options;
VhostUserState *s;
int max_queues;
int i;
options.backend_type = VHOST_BACKEND_TYPE_USER;
for (i = 0; i < queues; i++) {
assert (ncs[i]->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
s = DO_UPCAST(VhostUserState, nc, ncs[i]);
if (vhost_user_running(s)) {
continue;
}
options.net_backend = ncs[i];
options.opaque = s->chr;
s->vhost_net = vhost_net_init(&options);
if (!s->vhost_net) {
error_report("failed to init vhost_net for queue %d\n", i);
goto err;
}
if (i == 0) {
max_queues = vhost_net_get_max_queues(s->vhost_net);
if (queues > max_queues) {
error_report("you are asking more queues than "
"supported: %d\n", max_queues);
goto err;
}
}
}
return 0;
err:
vhost_user_stop(i + 1, ncs);
return -1;
}
static void vhost_user_cleanup(NetClientState *nc)
{
VhostUserState *s = DO_UPCAST(VhostUserState, nc, nc);
if (s->vhost_net) {
vhost_net_cleanup(s->vhost_net);
s->vhost_net = NULL;
}
qemu_purge_queued_packets(nc);
}
static bool vhost_user_has_vnet_hdr(NetClientState *nc)
{
assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
return true;
}
static bool vhost_user_has_ufo(NetClientState *nc)
{
assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
return true;
}
static NetClientInfo net_vhost_user_info = {
.type = NET_CLIENT_OPTIONS_KIND_VHOST_USER,
.size = sizeof(VhostUserState),
.cleanup = vhost_user_cleanup,
.has_vnet_hdr = vhost_user_has_vnet_hdr,
.has_ufo = vhost_user_has_ufo,
};
static void net_vhost_user_event(void *opaque, int event)
{
const char *name = opaque;
NetClientState *ncs[MAX_QUEUE_NUM];
VhostUserState *s;
Error *err = NULL;
int queues;
queues = qemu_find_net_clients_except(name, ncs,
NET_CLIENT_OPTIONS_KIND_NIC,
MAX_QUEUE_NUM);
s = DO_UPCAST(VhostUserState, nc, ncs[0]);
switch (event) {
case CHR_EVENT_OPENED:
if (vhost_user_start(queues, ncs) < 0) {
exit(1);
}
qmp_set_link(name, true, &err);
error_report("chardev \"%s\" went up", s->chr->label);
break;
case CHR_EVENT_CLOSED:
qmp_set_link(name, true, &err);
vhost_user_stop(queues, ncs);
error_report("chardev \"%s\" went down", s->chr->label);
break;
}
if (err) {
error_report_err(err);
}
}
static int net_vhost_user_init(NetClientState *peer, const char *device,
const char *name, CharDriverState *chr,
int queues)
{
NetClientState *nc;
VhostUserState *s;
int i;
for (i = 0; i < queues; i++) {
nc = qemu_new_net_client(&net_vhost_user_info, peer, device, name);
snprintf(nc->info_str, sizeof(nc->info_str), "vhost-user%d to %s",
i, chr->label);
/* We don't provide a receive callback */
nc->receive_disabled = 1;
nc->queue_index = i;
s = DO_UPCAST(VhostUserState, nc, nc);
s->chr = chr;
}
qemu_chr_add_handlers(chr, NULL, NULL, net_vhost_user_event, (void*)name);
return 0;
}
static int net_vhost_chardev_opts(void *opaque,
const char *name, const char *value,
Error **errp)
{
VhostUserChardevProps *props = opaque;
if (strcmp(name, "backend") == 0 && strcmp(value, "socket") == 0) {
props->is_socket = true;
} else if (strcmp(name, "path") == 0) {
props->is_unix = true;
} else if (strcmp(name, "server") == 0) {
props->is_server = true;
} else {
error_setg(errp,
"vhost-user does not support a chardev with option %s=%s",
name, value);
return -1;
}
return 0;
}
static CharDriverState *net_vhost_parse_chardev(
const NetdevVhostUserOptions *opts, Error **errp)
{
CharDriverState *chr = qemu_chr_find(opts->chardev);
VhostUserChardevProps props;
if (chr == NULL) {
error_setg(errp, "chardev \"%s\" not found", opts->chardev);
return NULL;
}
/* inspect chardev opts */
memset(&props, 0, sizeof(props));
if (qemu_opt_foreach(chr->opts, net_vhost_chardev_opts, &props, errp)) {
return NULL;
}
if (!props.is_socket || !props.is_unix) {
error_setg(errp, "chardev \"%s\" is not a unix socket",
opts->chardev);
return NULL;
}
qemu_chr_fe_claim_no_fail(chr);
return chr;
}
static int net_vhost_check_net(void *opaque, QemuOpts *opts, Error **errp)
{
const char *name = opaque;
const char *driver, *netdev;
const char virtio_name[] = "virtio-net-";
driver = qemu_opt_get(opts, "driver");
netdev = qemu_opt_get(opts, "netdev");
if (!driver || !netdev) {
return 0;
}
if (strcmp(netdev, name) == 0 &&
strncmp(driver, virtio_name, strlen(virtio_name)) != 0) {
error_setg(errp, "vhost-user requires frontend driver virtio-net-*");
return -1;
}
return 0;
}
int net_init_vhost_user(const NetClientOptions *opts, const char *name,
NetClientState *peer, Error **errp)
{
int queues;
const NetdevVhostUserOptions *vhost_user_opts;
CharDriverState *chr;
assert(opts->kind == NET_CLIENT_OPTIONS_KIND_VHOST_USER);
vhost_user_opts = opts->vhost_user;
chr = net_vhost_parse_chardev(vhost_user_opts, errp);
if (!chr) {
return -1;
}
/* verify net frontend */
if (qemu_opts_foreach(qemu_find_opts("device"), net_vhost_check_net,
(char *)name, errp)) {
return -1;
}
queues = vhost_user_opts->has_queues ? vhost_user_opts->queues : 1;
return net_vhost_user_init(peer, "vhost_user", name, chr, queues);
}