The following patches apply over linus's tree and the user_worker patchset here: https://lore.kernel.org/virtualization/20211129194707.5863-1-michael.christie at oracle.com/T/#t which allows us to check the vhost owner thread's RLIMITs, and they are built over Andrey's flush cleanups: https://lore.kernel.org/virtualization/20211207024510.23292-1-michael.christie at oracle.com/T/#t The patches allow us to support multiple vhost workers per device. The design is a modified version of Stefan's original idea where userspace has the kernel create a worker and we pass back the pid. In this version, V4, instead of passing the pid between user/kernel space we use a worker_id which is just an integer managed by the vhost driver and we allow userspace to create and free workers and then attach them to virtqueues at setup time or while IO is running. All review comments from the past reviews should be handled. If I didn't reply to a review comment, I agreed with the comment and should have handled it in this posting. Let me know if I missed one. Results: -------- fio jobs 1 2 4 8 12 16 ---------------------------------------------------------- 1 worker 84k 492k 510k - - - worker per vq 184k 380k 744k 1422k 2256k 2434k Notes: 0. This used a simple fio command: fio --filename=/dev/sdb --direct=1 --rw=randrw --bs=4k \ --ioengine=libaio --iodepth=128 --numjobs=$JOBS_ABOVE and I used a VM with 16 vCPUs and 16 virtqueues. 1. The patches were tested with emulate_pr=0 and these patches: https://lore.kernel.org/all/yq1tuhge4bg.fsf at ca-mkp.ca.oracle.com/t/ which are in mkp's scsi branches for the next kernel. They fix the perf issues where IOPs dropped at 12 vqs/jobs. 2. Because we have a hard limit of 1024 cmds, if the num jobs * iodepth was greater than 1024, I would decrease iodepth. So 12 jobs used 85 cmds, and 16 used 64. 3. The perf issue above at 2 jobs is because when we only have 1 worker we execute more cmds per vhost_work due to all vqs funneling to one worker. This results in less context switches and better batching without having to tweak any settings. I'm working on patches to add back batching during lio completion and do polling on the submission side. We will still want the threading patches, because if we batch at the fio level plus use the vhost theading patches, we can see a big boost like below. So hopefully doing it at the kernel will allow apps to just work without having to be smart like fio. fio using io_uring and batching with the iodepth_batch* settings: fio jobs 1 2 4 8 12 16 ------------------------------------------------------------- 1 worker 494k 520k - - - - worker per vq 496k 878k 1542k 2436k 2304k 2590k V5: - Rebase against user_worker patchset. - Rebase against flush patchset. - Redo vhost-scsi tmf flush handling so it doesn't access vq->worker. V4: - fix vhost-sock VSOCK_VQ_RX use. - name functions called directly by ioctl cmd's to match the ioctl cmd. - break up VHOST_SET_VRING_WORKER into a new, free and attach cmd. - document worker lifetime, and cgroup, namespace, mm, rlimit inheritance, make it clear we currently only support sharing within the device. - add support to attach workers while IO is running. - instead of passing a pid_t of the kernel thread, pass a int allocated by the vhost layer with an idr. V3: - fully convert vhost code to use vq based APIs instead of leaving it half per dev and half per vq. - rebase against kernel worker API. - Drop delayed worker creation. We always create the default worker at VHOST_SET_OWNER time. Userspace can create and bind workers after that. V2: - change loop that we take a refcount to the worker in - replaced pid == -1 with define. - fixed tabbing/spacing coding style issue - use hash instead of list to lookup workers. - I dropped the patch that added an ioctl cmd to get a vq's worker's pid. I saw we might do a generic netlink interface instead.
Mike Christie
2021-Dec-07 02:51 UTC
[PATCH V5 01/12] vhost: add vhost_worker pointer to vhost_virtqueue
This patchset allows userspace to map vqs to different workers. This patch adds a worker pointer to the vq so we can store that info. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- drivers/vhost/vhost.c | 24 +++++++++++++----------- drivers/vhost/vhost.h | 1 + 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 17e5956e7424..a314f050413a 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -488,6 +488,7 @@ void vhost_dev_init(struct vhost_dev *dev, vq->log = NULL; vq->indirect = NULL; vq->heads = NULL; + vq->worker = NULL; vq->dev = dev; mutex_init(&vq->mutex); vhost_vq_reset(dev, vq); @@ -566,15 +567,14 @@ static void vhost_worker_free(struct vhost_dev *dev) kfree(worker); } -static int vhost_worker_create(struct vhost_dev *dev) +static struct vhost_worker *vhost_worker_create(struct vhost_dev *dev) { struct vhost_worker *worker; struct task_struct *task; - int ret; worker = kzalloc(sizeof(*worker), GFP_KERNEL_ACCOUNT); if (!worker) - return -ENOMEM; + return NULL; dev->worker = worker; worker->kcov_handle = kcov_common_handle(); @@ -586,25 +586,24 @@ static int vhost_worker_create(struct vhost_dev *dev) */ task = user_worker_create(vhost_worker, worker, NUMA_NO_NODE, CLONE_FS, USER_WORKER_NO_FILES | USER_WORKER_SIG_IGN); - if (IS_ERR(task)) { - ret = PTR_ERR(task); + if (IS_ERR(task)) goto free_worker; - } worker->task = task; user_worker_start(task, "vhost-%d", current->pid); - return 0; + return worker; free_worker: kfree(worker); dev->worker = NULL; - return ret; + return NULL; } /* Caller should have device mutex */ long vhost_dev_set_owner(struct vhost_dev *dev) { - int err; + struct vhost_worker *worker; + int err, i; /* Is there an owner already? */ if (vhost_dev_has_owner(dev)) { @@ -615,9 +614,12 @@ long vhost_dev_set_owner(struct vhost_dev *dev) vhost_attach_mm(dev); if (dev->use_worker) { - err = vhost_worker_create(dev); - if (err) + worker = vhost_worker_create(dev); + if (!worker) goto err_worker; + + for (i = 0; i < dev->nvqs; i++) + dev->vqs[i]->worker = worker; } err = vhost_dev_alloc_iovecs(dev); diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h index 67b23e178812..31d074724fe8 100644 --- a/drivers/vhost/vhost.h +++ b/drivers/vhost/vhost.h @@ -79,6 +79,7 @@ struct vhost_vring_call { /* The virtqueue structure describes a queue attached to a device. */ struct vhost_virtqueue { struct vhost_dev *dev; + struct vhost_worker *worker; /* The actual ring of buffers. */ struct mutex mutex; -- 2.25.1
Mike Christie
2021-Dec-07 02:51 UTC
[PATCH V5 02/12] vhost, vhost-net: add helper to check if vq has work
This adds a helper to check if a vq has work pending and converts vhost-net to use it. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- drivers/vhost/net.c | 2 +- drivers/vhost/vhost.c | 6 +++--- drivers/vhost/vhost.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index b1feb5e0571e..f225cb9dcb10 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -543,7 +543,7 @@ static void vhost_net_busy_poll(struct vhost_net *net, endtime = busy_clock() + busyloop_timeout; while (vhost_can_busy_poll(endtime)) { - if (vhost_has_work(&net->dev)) { + if (vhost_vq_has_work(vq)) { *busyloop_intr = true; break; } diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index a314f050413a..2fe7c2e9f6ad 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -261,11 +261,11 @@ void vhost_work_queue(struct vhost_dev *dev, struct vhost_work *work) EXPORT_SYMBOL_GPL(vhost_work_queue); /* A lockless hint for busy polling code to exit the loop */ -bool vhost_has_work(struct vhost_dev *dev) +bool vhost_vq_has_work(struct vhost_virtqueue *vq) { - return dev->worker && !llist_empty(&dev->worker->work_list); + return vq->worker && !llist_empty(&vq->worker->work_list); } -EXPORT_SYMBOL_GPL(vhost_has_work); +EXPORT_SYMBOL_GPL(vhost_vq_has_work); void vhost_poll_queue(struct vhost_poll *poll) { diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h index 31d074724fe8..f0f2fb334b13 100644 --- a/drivers/vhost/vhost.h +++ b/drivers/vhost/vhost.h @@ -50,7 +50,6 @@ struct vhost_poll { void vhost_work_init(struct vhost_work *work, vhost_work_fn_t fn); void vhost_work_queue(struct vhost_dev *dev, struct vhost_work *work); -bool vhost_has_work(struct vhost_dev *dev); void vhost_poll_init(struct vhost_poll *poll, vhost_work_fn_t fn, __poll_t mask, struct vhost_dev *dev); @@ -199,6 +198,7 @@ int vhost_get_vq_desc(struct vhost_virtqueue *, struct vhost_log *log, unsigned int *log_num); void vhost_discard_vq_desc(struct vhost_virtqueue *, int n); +bool vhost_vq_has_work(struct vhost_virtqueue *vq); bool vhost_vq_is_setup(struct vhost_virtqueue *vq); int vhost_vq_init_access(struct vhost_virtqueue *); int vhost_add_used(struct vhost_virtqueue *, unsigned int head, int len); -- 2.25.1
Mike Christie
2021-Dec-07 02:51 UTC
[PATCH V5 03/12] vhost: take worker or vq instead of dev for queueing
This patch has the core work queueing function take a worker for when we support multiple workers. It also adds a helper that takes a vq during queueing so modules can control which vq/worker to queue work on. This temp leaves vhost_work_queue. It will be removed when the drivers are converted in the next patches. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- drivers/vhost/vhost.c | 44 +++++++++++++++++++++++++++---------------- drivers/vhost/vhost.h | 1 + 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 2fe7c2e9f6ad..86192e1658af 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -230,6 +230,34 @@ void vhost_poll_stop(struct vhost_poll *poll) } EXPORT_SYMBOL_GPL(vhost_poll_stop); +static void vhost_work_queue_on(struct vhost_worker *worker, + struct vhost_work *work) +{ + if (!worker) + return; + + if (!test_and_set_bit(VHOST_WORK_QUEUED, &work->flags)) { + /* We can only add the work to the list after we're + * sure it was not in the list. + * test_and_set_bit() implies a memory barrier. + */ + llist_add(&work->node, &worker->work_list); + wake_up_process(worker->task); + } +} + +void vhost_work_queue(struct vhost_dev *dev, struct vhost_work *work) +{ + vhost_work_queue_on(dev->worker, work); +} +EXPORT_SYMBOL_GPL(vhost_work_queue); + +void vhost_vq_work_queue(struct vhost_virtqueue *vq, struct vhost_work *work) +{ + vhost_work_queue_on(vq->worker, work); +} +EXPORT_SYMBOL_GPL(vhost_vq_work_queue); + void vhost_work_dev_flush(struct vhost_dev *dev) { struct vhost_flush_struct flush; @@ -244,22 +272,6 @@ void vhost_work_dev_flush(struct vhost_dev *dev) } EXPORT_SYMBOL_GPL(vhost_work_dev_flush); -void vhost_work_queue(struct vhost_dev *dev, struct vhost_work *work) -{ - if (!dev->worker) - return; - - if (!test_and_set_bit(VHOST_WORK_QUEUED, &work->flags)) { - /* We can only add the work to the list after we're - * sure it was not in the list. - * test_and_set_bit() implies a memory barrier. - */ - llist_add(&work->node, &dev->worker->work_list); - wake_up_process(dev->worker->task); - } -} -EXPORT_SYMBOL_GPL(vhost_work_queue); - /* A lockless hint for busy polling code to exit the loop */ bool vhost_vq_has_work(struct vhost_virtqueue *vq) { diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h index f0f2fb334b13..f5251cf902ce 100644 --- a/drivers/vhost/vhost.h +++ b/drivers/vhost/vhost.h @@ -198,6 +198,7 @@ int vhost_get_vq_desc(struct vhost_virtqueue *, struct vhost_log *log, unsigned int *log_num); void vhost_discard_vq_desc(struct vhost_virtqueue *, int n); +void vhost_vq_work_queue(struct vhost_virtqueue *vq, struct vhost_work *work); bool vhost_vq_has_work(struct vhost_virtqueue *vq); bool vhost_vq_is_setup(struct vhost_virtqueue *vq); int vhost_vq_init_access(struct vhost_virtqueue *); -- 2.25.1
Mike Christie
2021-Dec-07 02:51 UTC
[PATCH V5 04/12] vhost: take worker or vq instead of dev for flushing
This patch has the core work flush function take a worker for when we support multiple workers. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- drivers/vhost/vhost.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 86192e1658af..e5f5acc7e648 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -246,6 +246,20 @@ static void vhost_work_queue_on(struct vhost_worker *worker, } } +static void vhost_work_flush_on(struct vhost_worker *worker) +{ + struct vhost_flush_struct flush; + + if (!worker) + return; + + init_completion(&flush.wait_event); + vhost_work_init(&flush.work, vhost_flush_work); + + vhost_work_queue_on(worker, &flush.work); + wait_for_completion(&flush.wait_event); +} + void vhost_work_queue(struct vhost_dev *dev, struct vhost_work *work) { vhost_work_queue_on(dev->worker, work); @@ -260,15 +274,7 @@ EXPORT_SYMBOL_GPL(vhost_vq_work_queue); void vhost_work_dev_flush(struct vhost_dev *dev) { - struct vhost_flush_struct flush; - - if (dev->worker) { - init_completion(&flush.wait_event); - vhost_work_init(&flush.work, vhost_flush_work); - - vhost_work_queue(dev, &flush.work); - wait_for_completion(&flush.wait_event); - } + vhost_work_flush_on(dev->worker); } EXPORT_SYMBOL_GPL(vhost_work_dev_flush); -- 2.25.1
Mike Christie
2021-Dec-07 02:51 UTC
[PATCH V5 05/12] vhost: convert poll work to be vq based
This has the drivers pass in their poll to vq mapping and then converts the core poll code to use the vq based helpers. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- drivers/vhost/net.c | 6 ++++-- drivers/vhost/vhost.c | 8 +++++--- drivers/vhost/vhost.h | 4 +++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index f225cb9dcb10..6e2b1670ae51 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -1339,8 +1339,10 @@ static int vhost_net_open(struct inode *inode, struct file *f) VHOST_NET_PKT_WEIGHT, VHOST_NET_WEIGHT, true, NULL); - vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, EPOLLOUT, dev); - vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, EPOLLIN, dev); + vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, EPOLLOUT, dev, + vqs[VHOST_NET_VQ_TX]); + vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, EPOLLIN, dev, + vqs[VHOST_NET_VQ_RX]); f->private_data = n; n->page_frag.page = NULL; diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index e5f5acc7e648..e9f78f0d72cb 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -186,13 +186,15 @@ EXPORT_SYMBOL_GPL(vhost_work_init); /* Init poll structure */ void vhost_poll_init(struct vhost_poll *poll, vhost_work_fn_t fn, - __poll_t mask, struct vhost_dev *dev) + __poll_t mask, struct vhost_dev *dev, + struct vhost_virtqueue *vq) { init_waitqueue_func_entry(&poll->wait, vhost_poll_wakeup); init_poll_funcptr(&poll->table, vhost_poll_func); poll->mask = mask; poll->dev = dev; poll->wqh = NULL; + poll->vq = vq; vhost_work_init(&poll->work, fn); } @@ -287,7 +289,7 @@ EXPORT_SYMBOL_GPL(vhost_vq_has_work); void vhost_poll_queue(struct vhost_poll *poll) { - vhost_work_queue(poll->dev, &poll->work); + vhost_vq_work_queue(poll->vq, &poll->work); } EXPORT_SYMBOL_GPL(vhost_poll_queue); @@ -512,7 +514,7 @@ void vhost_dev_init(struct vhost_dev *dev, vhost_vq_reset(dev, vq); if (vq->handle_kick) vhost_poll_init(&vq->poll, vq->handle_kick, - EPOLLIN, dev); + EPOLLIN, dev, vq); } } EXPORT_SYMBOL_GPL(vhost_dev_init); diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h index f5251cf902ce..58032429bd05 100644 --- a/drivers/vhost/vhost.h +++ b/drivers/vhost/vhost.h @@ -46,13 +46,15 @@ struct vhost_poll { struct vhost_work work; __poll_t mask; struct vhost_dev *dev; + struct vhost_virtqueue *vq; }; void vhost_work_init(struct vhost_work *work, vhost_work_fn_t fn); void vhost_work_queue(struct vhost_dev *dev, struct vhost_work *work); void vhost_poll_init(struct vhost_poll *poll, vhost_work_fn_t fn, - __poll_t mask, struct vhost_dev *dev); + __poll_t mask, struct vhost_dev *dev, + struct vhost_virtqueue *vq); int vhost_poll_start(struct vhost_poll *poll, struct file *file); void vhost_poll_stop(struct vhost_poll *poll); void vhost_poll_queue(struct vhost_poll *poll); -- 2.25.1
Mike Christie
2021-Dec-07 02:51 UTC
[PATCH V5 06/12] vhost-sock: convert to vhost_vq_work_queue
Convert from vhost_work_queue to vhost_vq_work_queue. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- drivers/vhost/vsock.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c index 1f38160b249d..068ccdbd3bcd 100644 --- a/drivers/vhost/vsock.c +++ b/drivers/vhost/vsock.c @@ -300,7 +300,7 @@ vhost_transport_send_pkt(struct virtio_vsock_pkt *pkt) list_add_tail(&pkt->list, &vsock->send_pkt_list); spin_unlock_bh(&vsock->send_pkt_list_lock); - vhost_work_queue(&vsock->dev, &vsock->send_pkt_work); + vhost_vq_work_queue(&vsock->vqs[VSOCK_VQ_RX], &vsock->send_pkt_work); rcu_read_unlock(); return len; @@ -608,7 +608,7 @@ static int vhost_vsock_start(struct vhost_vsock *vsock) /* Some packets may have been queued before the device was started, * let's kick the send worker to send them. */ - vhost_work_queue(&vsock->dev, &vsock->send_pkt_work); + vhost_vq_work_queue(&vsock->vqs[VSOCK_VQ_RX], &vsock->send_pkt_work); mutex_unlock(&vsock->dev.mutex); return 0; -- 2.25.1
Mike Christie
2021-Dec-07 02:51 UTC
[PATCH V5 07/12] vhost-scsi: make SCSI cmd completion per vq
This patch separates the scsi cmd completion code paths so we can complete cmds based on their vq instead of having all cmds complete on the same worker/CPU. This will be useful with the next patches that allow us to create mulitple worker threads and bind them to different vqs, so we can have completions running on different threads/CPUs. Signed-off-by: Mike Christie <michael.christie at oracle.com> Reviewed-by: Stefan Hajnoczi <stefanha at redhat.com> --- drivers/vhost/scsi.c | 48 +++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 94535c813ef7..b2592e927316 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -164,6 +164,7 @@ enum { struct vhost_scsi_virtqueue { struct vhost_virtqueue vq; + struct vhost_scsi *vs; /* * Reference counting for inflight reqs, used for flush operation. At * each time, one reference tracks new commands submitted, while we @@ -178,6 +179,9 @@ struct vhost_scsi_virtqueue { struct vhost_scsi_cmd *scsi_cmds; struct sbitmap scsi_tags; int max_cmds; + + struct vhost_work completion_work; + struct llist_head completion_list; }; struct vhost_scsi { @@ -188,9 +192,6 @@ struct vhost_scsi { struct vhost_dev dev; struct vhost_scsi_virtqueue vqs[VHOST_SCSI_MAX_VQ]; - struct vhost_work vs_completion_work; /* cmd completion work item */ - struct llist_head vs_completion_list; /* cmd completion queue */ - struct vhost_work vs_event_work; /* evt injection work item */ struct llist_head vs_event_list; /* evt injection queue */ @@ -365,10 +366,11 @@ static void vhost_scsi_release_cmd(struct se_cmd *se_cmd) } else { struct vhost_scsi_cmd *cmd = container_of(se_cmd, struct vhost_scsi_cmd, tvc_se_cmd); - struct vhost_scsi *vs = cmd->tvc_vhost; + struct vhost_scsi_virtqueue *svq = container_of(cmd->tvc_vq, + struct vhost_scsi_virtqueue, vq); - llist_add(&cmd->tvc_completion_list, &vs->vs_completion_list); - vhost_work_queue(&vs->dev, &vs->vs_completion_work); + llist_add(&cmd->tvc_completion_list, &svq->completion_list); + vhost_vq_work_queue(&svq->vq, &svq->completion_work); } } @@ -531,18 +533,17 @@ static void vhost_scsi_evt_work(struct vhost_work *work) */ static void vhost_scsi_complete_cmd_work(struct vhost_work *work) { - struct vhost_scsi *vs = container_of(work, struct vhost_scsi, - vs_completion_work); - DECLARE_BITMAP(signal, VHOST_SCSI_MAX_VQ); + struct vhost_scsi_virtqueue *svq = container_of(work, + struct vhost_scsi_virtqueue, completion_work); struct virtio_scsi_cmd_resp v_rsp; struct vhost_scsi_cmd *cmd, *t; struct llist_node *llnode; struct se_cmd *se_cmd; struct iov_iter iov_iter; - int ret, vq; + bool signal = false; + int ret; - bitmap_zero(signal, VHOST_SCSI_MAX_VQ); - llnode = llist_del_all(&vs->vs_completion_list); + llnode = llist_del_all(&svq->completion_list); llist_for_each_entry_safe(cmd, t, llnode, tvc_completion_list) { se_cmd = &cmd->tvc_se_cmd; @@ -562,21 +563,16 @@ static void vhost_scsi_complete_cmd_work(struct vhost_work *work) cmd->tvc_in_iovs, sizeof(v_rsp)); ret = copy_to_iter(&v_rsp, sizeof(v_rsp), &iov_iter); if (likely(ret == sizeof(v_rsp))) { - struct vhost_scsi_virtqueue *q; + signal = true; vhost_add_used(cmd->tvc_vq, cmd->tvc_vq_desc, 0); - q = container_of(cmd->tvc_vq, struct vhost_scsi_virtqueue, vq); - vq = q - vs->vqs; - __set_bit(vq, signal); } else pr_err("Faulted on virtio_scsi_cmd_resp\n"); vhost_scsi_release_cmd_res(se_cmd); } - vq = -1; - while ((vq = find_next_bit(signal, VHOST_SCSI_MAX_VQ, vq + 1)) - < VHOST_SCSI_MAX_VQ) - vhost_signal(&vs->dev, &vs->vqs[vq].vq); + if (signal) + vhost_signal(&svq->vs->dev, &svq->vq); } static struct vhost_scsi_cmd * @@ -1776,6 +1772,7 @@ static int vhost_scsi_set_features(struct vhost_scsi *vs, u64 features) static int vhost_scsi_open(struct inode *inode, struct file *f) { + struct vhost_scsi_virtqueue *svq; struct vhost_scsi *vs; struct vhost_virtqueue **vqs; int r = -ENOMEM, i; @@ -1788,7 +1785,6 @@ static int vhost_scsi_open(struct inode *inode, struct file *f) if (!vqs) goto err_vqs; - vhost_work_init(&vs->vs_completion_work, vhost_scsi_complete_cmd_work); vhost_work_init(&vs->vs_event_work, vhost_scsi_evt_work); vs->vs_events_nr = 0; @@ -1799,8 +1795,14 @@ static int vhost_scsi_open(struct inode *inode, struct file *f) vs->vqs[VHOST_SCSI_VQ_CTL].vq.handle_kick = vhost_scsi_ctl_handle_kick; vs->vqs[VHOST_SCSI_VQ_EVT].vq.handle_kick = vhost_scsi_evt_handle_kick; for (i = VHOST_SCSI_VQ_IO; i < VHOST_SCSI_MAX_VQ; i++) { - vqs[i] = &vs->vqs[i].vq; - vs->vqs[i].vq.handle_kick = vhost_scsi_handle_kick; + svq = &vs->vqs[i]; + + vqs[i] = &svq->vq; + svq->vs = vs; + init_llist_head(&svq->completion_list); + vhost_work_init(&svq->completion_work, + vhost_scsi_complete_cmd_work); + svq->vq.handle_kick = vhost_scsi_handle_kick; } vhost_dev_init(&vs->dev, vqs, VHOST_SCSI_MAX_VQ, UIO_MAXIOV, VHOST_SCSI_WEIGHT, 0, true, NULL); -- 2.25.1
Mike Christie
2021-Dec-07 02:51 UTC
[PATCH V5 08/12] vhost-scsi: convert to vhost_vq_work_queue
Convert from vhost_work_queue to vhost_vq_work_queue. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- drivers/vhost/scsi.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index b2592e927316..93c6ad1246eb 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -361,8 +361,9 @@ static void vhost_scsi_release_cmd(struct se_cmd *se_cmd) if (se_cmd->se_cmd_flags & SCF_SCSI_TMR_CDB) { struct vhost_scsi_tmf *tmf = container_of(se_cmd, struct vhost_scsi_tmf, se_cmd); + struct vhost_virtqueue *vq = &tmf->svq->vq; - vhost_work_queue(&tmf->vhost->dev, &tmf->vwork); + vhost_vq_work_queue(vq, &tmf->vwork); } else { struct vhost_scsi_cmd *cmd = container_of(se_cmd, struct vhost_scsi_cmd, tvc_se_cmd); @@ -1360,11 +1361,9 @@ static void vhost_scsi_ctl_handle_kick(struct vhost_work *work) } static void -vhost_scsi_send_evt(struct vhost_scsi *vs, - struct vhost_scsi_tpg *tpg, - struct se_lun *lun, - u32 event, - u32 reason) +vhost_scsi_send_evt(struct vhost_scsi *vs, struct vhost_virtqueue *vq, + struct vhost_scsi_tpg *tpg, struct se_lun *lun, + u32 event, u32 reason) { struct vhost_scsi_evt *evt; @@ -1386,7 +1385,7 @@ vhost_scsi_send_evt(struct vhost_scsi *vs, } llist_add(&evt->list, &vs->vs_event_list); - vhost_work_queue(&vs->dev, &vs->vs_event_work); + vhost_vq_work_queue(vq, &vs->vs_event_work); } static void vhost_scsi_evt_handle_kick(struct vhost_work *work) @@ -1400,7 +1399,8 @@ static void vhost_scsi_evt_handle_kick(struct vhost_work *work) goto out; if (vs->vs_events_missed) - vhost_scsi_send_evt(vs, NULL, NULL, VIRTIO_SCSI_T_NO_EVENT, 0); + vhost_scsi_send_evt(vs, vq, NULL, NULL, VIRTIO_SCSI_T_NO_EVENT, + 0); out: mutex_unlock(&vq->mutex); } @@ -1965,8 +1965,8 @@ vhost_scsi_do_plug(struct vhost_scsi_tpg *tpg, vq = &vs->vqs[VHOST_SCSI_VQ_EVT].vq; mutex_lock(&vq->mutex); if (vhost_has_feature(vq, VIRTIO_SCSI_F_HOTPLUG)) - vhost_scsi_send_evt(vs, tpg, lun, - VIRTIO_SCSI_T_TRANSPORT_RESET, reason); + vhost_scsi_send_evt(vs, vq, tpg, lun, + VIRTIO_SCSI_T_TRANSPORT_RESET, reason); mutex_unlock(&vq->mutex); mutex_unlock(&vs->dev.mutex); } -- 2.25.1
vhost_work_queue is no longer used. Each driver is using the poll or vq based queueing, so remove vhost_work_queue. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- drivers/vhost/vhost.c | 6 ------ drivers/vhost/vhost.h | 1 - 2 files changed, 7 deletions(-) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index e9f78f0d72cb..050b8007db8b 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -262,12 +262,6 @@ static void vhost_work_flush_on(struct vhost_worker *worker) wait_for_completion(&flush.wait_event); } -void vhost_work_queue(struct vhost_dev *dev, struct vhost_work *work) -{ - vhost_work_queue_on(dev->worker, work); -} -EXPORT_SYMBOL_GPL(vhost_work_queue); - void vhost_vq_work_queue(struct vhost_virtqueue *vq, struct vhost_work *work) { vhost_work_queue_on(vq->worker, work); diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h index 58032429bd05..4423b2420c90 100644 --- a/drivers/vhost/vhost.h +++ b/drivers/vhost/vhost.h @@ -50,7 +50,6 @@ struct vhost_poll { }; void vhost_work_init(struct vhost_work *work, vhost_work_fn_t fn); -void vhost_work_queue(struct vhost_dev *dev, struct vhost_work *work); void vhost_poll_init(struct vhost_poll *poll, vhost_work_fn_t fn, __poll_t mask, struct vhost_dev *dev, -- 2.25.1
Mike Christie
2021-Dec-07 02:51 UTC
[PATCH V5 10/12] vhost-scsi: flush IO vqs then send TMF rsp
With one worker we will always send the scsi cmd responses then send the TMF rsp, because LIO will always complete the scsi cmds first then call into us to send the TMF response. With multiple workers, the IO vq workers could be running while the TMF/ctl vq worker is so this has us do a flush before completing the TMF to make sure cmds are completed when it's work is later queued and run. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- drivers/vhost/scsi.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index 93c6ad1246eb..33e3ff4c1f38 100644 --- a/drivers/vhost/scsi.c +++ b/drivers/vhost/scsi.c @@ -413,7 +413,13 @@ static void vhost_scsi_queue_tm_rsp(struct se_cmd *se_cmd) { struct vhost_scsi_tmf *tmf = container_of(se_cmd, struct vhost_scsi_tmf, se_cmd); - + /* + * LIO will complete the cmds this TMF has cleaned up, then call + * this function. If we have vqs that do not share a worker with the + * ctl vq, then those cmds/works could still be completing. Do a + * flush here to make sure when the tmf work runs the cmds are done. + */ + vhost_work_dev_flush(&tmf->vhost->dev); tmf->scsi_resp = se_cmd->se_tmr_req->response; transport_generic_free_cmd(&tmf->se_cmd, 0); } -- 2.25.1
Mike Christie
2021-Dec-07 02:51 UTC
[PATCH V5 11/12] vhost: allow userspace to create workers
For vhost-scsi with 3 vqs and a workload like that tries to use those vqs like: fio --filename=/dev/sdb --direct=1 --rw=randrw --bs=4k \ --ioengine=libaio --iodepth=128 --numjobs=3 the single vhost worker thread will become a bottlneck. To better utilize virtqueues and available CPUs, this patch allows userspace to create workers and bind them to vqs. You can have N workers per dev and also share N workers with M vqs. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- drivers/vhost/vhost.c | 164 ++++++++++++++++++++++++++++--- drivers/vhost/vhost.h | 4 +- include/uapi/linux/vhost.h | 22 +++++ include/uapi/linux/vhost_types.h | 15 +++ 4 files changed, 188 insertions(+), 17 deletions(-) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 050b8007db8b..1af43b5d1dbd 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -248,18 +248,16 @@ static void vhost_work_queue_on(struct vhost_worker *worker, } } -static void vhost_work_flush_on(struct vhost_worker *worker) +static int vhost_workers_idr_flush_iter(int id, void *worker, void *dev) { struct vhost_flush_struct flush; - if (!worker) - return; - init_completion(&flush.wait_event); vhost_work_init(&flush.work, vhost_flush_work); vhost_work_queue_on(worker, &flush.work); wait_for_completion(&flush.wait_event); + return 0; } void vhost_vq_work_queue(struct vhost_virtqueue *vq, struct vhost_work *work) @@ -268,9 +266,16 @@ void vhost_vq_work_queue(struct vhost_virtqueue *vq, struct vhost_work *work) } EXPORT_SYMBOL_GPL(vhost_vq_work_queue); +/** + * vhost_work_dev_flush - flush vhost work in device + * @dev: vhost dev to flush + * + * This must be called with the device mutex or from the device's release + * function when workers cannot be swapped. + */ void vhost_work_dev_flush(struct vhost_dev *dev) { - vhost_work_flush_on(dev->worker); + idr_for_each(&dev->worker_idr, vhost_workers_idr_flush_iter, dev); } EXPORT_SYMBOL_GPL(vhost_work_dev_flush); @@ -485,7 +490,6 @@ void vhost_dev_init(struct vhost_dev *dev, dev->umem = NULL; dev->iotlb = NULL; dev->mm = NULL; - dev->worker = NULL; dev->iov_limit = iov_limit; dev->weight = weight; dev->byte_weight = byte_weight; @@ -495,6 +499,7 @@ void vhost_dev_init(struct vhost_dev *dev, INIT_LIST_HEAD(&dev->read_list); INIT_LIST_HEAD(&dev->pending_list); spin_lock_init(&dev->iotlb_lock); + idr_init(&dev->worker_idr); for (i = 0; i < dev->nvqs; ++i) { @@ -568,31 +573,59 @@ static void vhost_worker_stop(struct vhost_worker *worker) wait_for_completion(worker->exit_done); } -static void vhost_worker_free(struct vhost_dev *dev) +static void vhost_worker_put(struct vhost_dev *dev, struct vhost_worker *worker) { - struct vhost_worker *worker = dev->worker; - if (!worker) return; - dev->worker = NULL; + if (!refcount_dec_and_test(&worker->refcount)) + return; + WARN_ON(!llist_empty(&worker->work_list)); vhost_worker_stop(worker); + idr_remove(&dev->worker_idr, worker->id); kfree(worker); } +static void vhost_vq_detach_worker(struct vhost_virtqueue *vq) +{ + if (vq->worker) + vhost_worker_put(vq->dev, vq->worker); + vq->worker = NULL; +} + +static int vhost_workers_idr_iter(int id, void *worker, void *dev) +{ + vhost_worker_put(dev, worker); + return 0; +} + +static void vhost_workers_free(struct vhost_dev *dev) +{ + int i; + + if (!dev->use_worker) + return; + + for (i = 0; i < dev->nvqs; i++) + vhost_vq_detach_worker(dev->vqs[i]); + + idr_for_each(&dev->worker_idr, vhost_workers_idr_iter, dev); +} + static struct vhost_worker *vhost_worker_create(struct vhost_dev *dev) { struct vhost_worker *worker; struct task_struct *task; + int id; worker = kzalloc(sizeof(*worker), GFP_KERNEL_ACCOUNT); if (!worker) return NULL; - dev->worker = worker; worker->kcov_handle = kcov_common_handle(); init_llist_head(&worker->work_list); + refcount_set(&worker->refcount, 1); /* * vhost used to use the kthread API which ignores all signals by @@ -605,14 +638,88 @@ static struct vhost_worker *vhost_worker_create(struct vhost_dev *dev) worker->task = task; user_worker_start(task, "vhost-%d", current->pid); + + /* idr accesses are done under the vhost_dev mutex */ + id = idr_alloc(&dev->worker_idr, worker, 0, INT_MAX, GFP_KERNEL); + if (id < 0) + goto stop_worker; + worker->id = id; + return worker; +stop_worker: + vhost_worker_stop(worker); free_worker: kfree(worker); - dev->worker = NULL; return NULL; } +/* Caller must have device mutex */ +static int vhost_vq_attach_worker(struct vhost_virtqueue *vq, + struct vhost_vring_worker *info) +{ + struct vhost_dev *dev = vq->dev; + struct vhost_worker *worker; + + if (!dev->use_worker) + return -EINVAL; + + /* We don't support setting a worker on an active vq */ + if (vq->private_data) + return -EBUSY; + + worker = idr_find(&dev->worker_idr, info->worker_id); + if (!worker) + return -ENODEV; + + refcount_inc(&worker->refcount); + + vhost_vq_detach_worker(vq); + vq->worker = worker; + return 0; +} + +/* Caller must have device mutex */ +static int vhost_new_worker(struct vhost_dev *dev, + struct vhost_worker_state *info) +{ + struct vhost_worker *worker; + + if (!dev->use_worker) + return -EINVAL; + + worker = vhost_worker_create(dev); + if (!worker) + return -ENOMEM; + + info->worker_id = worker->id; + return 0; +} + +/* Caller must have device mutex */ +static int vhost_free_worker(struct vhost_dev *dev, + struct vhost_worker_state *info) +{ + struct vhost_worker *worker; + + if (!dev->use_worker) + return -EINVAL; + + worker = idr_find(&dev->worker_idr, info->worker_id); + if (!worker) + return -ENODEV; + + /* + * We can free the worker if there are no attached vqs and we only + * have the refcount from the initial creation. + */ + if (refcount_read(&worker->refcount) != 1) + return -EBUSY; + + vhost_worker_put(dev, worker); + return 0; +} + /* Caller should have device mutex */ long vhost_dev_set_owner(struct vhost_dev *dev) { @@ -632,8 +739,10 @@ long vhost_dev_set_owner(struct vhost_dev *dev) if (!worker) goto err_worker; - for (i = 0; i < dev->nvqs; i++) + for (i = 0; i < dev->nvqs; i++) { + refcount_inc(&worker->refcount); dev->vqs[i]->worker = worker; + } } err = vhost_dev_alloc_iovecs(dev); @@ -642,7 +751,7 @@ long vhost_dev_set_owner(struct vhost_dev *dev) return 0; err_iovecs: - vhost_worker_free(dev); + vhost_workers_free(dev); err_worker: vhost_detach_mm(dev); err_mm: @@ -734,7 +843,7 @@ void vhost_dev_cleanup(struct vhost_dev *dev) dev->iotlb = NULL; vhost_clear_msg(dev); wake_up_interruptible_poll(&dev->wait, EPOLLIN | EPOLLRDNORM); - vhost_worker_free(dev); + vhost_workers_free(dev); vhost_detach_mm(dev); } EXPORT_SYMBOL_GPL(vhost_dev_cleanup); @@ -1604,6 +1713,7 @@ long vhost_vring_ioctl(struct vhost_dev *d, unsigned int ioctl, void __user *arg struct eventfd_ctx *ctx = NULL; u32 __user *idxp = argp; struct vhost_virtqueue *vq; + struct vhost_vring_worker w; struct vhost_vring_state s; struct vhost_vring_file f; u32 idx; @@ -1711,7 +1821,16 @@ long vhost_vring_ioctl(struct vhost_dev *d, unsigned int ioctl, void __user *arg if (copy_to_user(argp, &s, sizeof(s))) r = -EFAULT; break; - default: + case VHOST_ATTACH_VRING_WORKER: + if (copy_from_user(&w, argp, sizeof(w))) { + r = -EFAULT; + break; + } + r = vhost_vq_attach_worker(vq, &w); + if (!r && copy_to_user(argp, &w, sizeof(w))) + r = -EFAULT; + break; +default: r = -ENOIOCTLCMD; } @@ -1764,6 +1883,7 @@ EXPORT_SYMBOL_GPL(vhost_init_device_iotlb); /* Caller must have device mutex */ long vhost_dev_ioctl(struct vhost_dev *d, unsigned int ioctl, void __user *argp) { + struct vhost_worker_state w; struct eventfd_ctx *ctx; u64 p; long r; @@ -1824,6 +1944,18 @@ long vhost_dev_ioctl(struct vhost_dev *d, unsigned int ioctl, void __user *argp) if (ctx) eventfd_ctx_put(ctx); break; + case VHOST_NEW_WORKER: + r = vhost_new_worker(d, &w); + if (!r && copy_to_user(argp, &w, sizeof(w))) + r = -EFAULT; + break; + case VHOST_FREE_WORKER: + if (copy_from_user(&w, argp, sizeof(w))) { + r = -EFAULT; + break; + } + r = vhost_free_worker(d, &w); + break; default: r = -ENOIOCTLCMD; break; diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h index 4423b2420c90..1738388fa02d 100644 --- a/drivers/vhost/vhost.h +++ b/drivers/vhost/vhost.h @@ -35,6 +35,8 @@ struct vhost_worker { struct llist_head work_list; u64 kcov_handle; unsigned long flags; + refcount_t refcount; + int id; }; /* Poll a file (eventfd or socket) */ @@ -160,7 +162,6 @@ struct vhost_dev { struct vhost_virtqueue **vqs; int nvqs; struct eventfd_ctx *log_ctx; - struct vhost_worker *worker; struct vhost_iotlb *umem; struct vhost_iotlb *iotlb; spinlock_t iotlb_lock; @@ -170,6 +171,7 @@ struct vhost_dev { int iov_limit; int weight; int byte_weight; + struct idr worker_idr; bool use_worker; int (*msg_handler)(struct vhost_dev *dev, struct vhost_iotlb_msg *msg); diff --git a/include/uapi/linux/vhost.h b/include/uapi/linux/vhost.h index c998860d7bbc..117ea92b3925 100644 --- a/include/uapi/linux/vhost.h +++ b/include/uapi/linux/vhost.h @@ -45,6 +45,23 @@ #define VHOST_SET_LOG_BASE _IOW(VHOST_VIRTIO, 0x04, __u64) /* Specify an eventfd file descriptor to signal on log write. */ #define VHOST_SET_LOG_FD _IOW(VHOST_VIRTIO, 0x07, int) +/* By default, a device gets one vhost_worker that its virtqueues share. This + * command allows the owner of the device to create an additional vhost_worker + * for the device. It can later be bound to 1 or more of its virtqueues using + * the VHOST_ATTACH_VRING_WORKER command. + * + * This must be called after VHOST_SET_OWNER and the caller must be the owner + * of the device. The new thread will inherit caller's cgroups and namespaces, + * and will share the caller's memory space. The new thread will also be + * counted against the caller's RLIMIT_NPROC value. + */ +#define VHOST_NEW_WORKER _IOW(VHOST_VIRTIO, 0x8, struct vhost_worker_state) +/* Free a worker created with VHOST_NEW_WORKER if it's not attached to any + * virtqueue. If userspace is not able to call this for workers its created, + * the kernel will free all the device's workers when the device is closed and + * the last reference to the device has been released. + */ +#define VHOST_FREE_WORKER _IOR(VHOST_VIRTIO, 0x9, struct vhost_worker_state) /* Ring setup. */ /* Set number of descriptors in ring. This parameter can not @@ -70,6 +87,11 @@ #define VHOST_VRING_BIG_ENDIAN 1 #define VHOST_SET_VRING_ENDIAN _IOW(VHOST_VIRTIO, 0x13, struct vhost_vring_state) #define VHOST_GET_VRING_ENDIAN _IOW(VHOST_VIRTIO, 0x14, struct vhost_vring_state) +/* Attach a vhost_worker created with VHOST_NEW_WORKER to one of the device's + * virtqueues. This must be done before the virtqueue is active. + */ +#define VHOST_ATTACH_VRING_WORKER _IOR(VHOST_VIRTIO, 0x15, \ + struct vhost_vring_worker) /* The following ioctls use eventfd file descriptors to signal and poll * for events. */ diff --git a/include/uapi/linux/vhost_types.h b/include/uapi/linux/vhost_types.h index f7f6a3a28977..d38a023e494f 100644 --- a/include/uapi/linux/vhost_types.h +++ b/include/uapi/linux/vhost_types.h @@ -47,6 +47,21 @@ struct vhost_vring_addr { __u64 log_guest_addr; }; +struct vhost_worker_state { + /* + * For VHOST_NEW_WORKER the kernel will return the new vhost_worker id. + * For VHOST_FREE_WORKER this must be set to the id of the vhost_worker + * to free. + */ + int worker_id; +}; + +struct vhost_vring_worker { + unsigned int index; + /* The id of the vhost_worker returned from VHOST_NEW_WORKER */ + int worker_id; +}; + /* no alignment requirement */ struct vhost_iotlb_msg { __u64 iova; -- 2.25.1
Mike Christie
2021-Dec-07 02:51 UTC
[PATCH V5 12/12] vhost: allow worker attachment after initial setup
This patch allows userspace to change the vq to worker mapping while it's in use so tools can do this setup post device creation if needed. Signed-off-by: Mike Christie <michael.christie at oracle.com> --- drivers/vhost/vhost.c | 76 +++++++++++++++++++++++++++----------- drivers/vhost/vhost.h | 2 +- include/uapi/linux/vhost.h | 2 +- 3 files changed, 57 insertions(+), 23 deletions(-) diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c index 1af43b5d1dbd..f702df0ce33f 100644 --- a/drivers/vhost/vhost.c +++ b/drivers/vhost/vhost.c @@ -232,12 +232,9 @@ void vhost_poll_stop(struct vhost_poll *poll) } EXPORT_SYMBOL_GPL(vhost_poll_stop); -static void vhost_work_queue_on(struct vhost_worker *worker, - struct vhost_work *work) +static void vhost_worker_work_queue(struct vhost_worker *worker, + struct vhost_work *work) { - if (!worker) - return; - if (!test_and_set_bit(VHOST_WORK_QUEUED, &work->flags)) { /* We can only add the work to the list after we're * sure it was not in the list. @@ -248,21 +245,32 @@ static void vhost_work_queue_on(struct vhost_worker *worker, } } -static int vhost_workers_idr_flush_iter(int id, void *worker, void *dev) +static void vhost_worker_flush(struct vhost_worker *worker) { struct vhost_flush_struct flush; init_completion(&flush.wait_event); vhost_work_init(&flush.work, vhost_flush_work); - vhost_work_queue_on(worker, &flush.work); + vhost_worker_work_queue(worker, &flush.work); wait_for_completion(&flush.wait_event); +} + +static int vhost_workers_idr_flush_iter(int id, void *worker, void *dev) +{ + vhost_worker_flush(worker); return 0; } void vhost_vq_work_queue(struct vhost_virtqueue *vq, struct vhost_work *work) { - vhost_work_queue_on(vq->worker, work); + struct vhost_worker *worker; + + rcu_read_lock(); + worker = rcu_dereference(vq->worker); + if (worker) + vhost_worker_work_queue(worker, work); + rcu_read_unlock(); } EXPORT_SYMBOL_GPL(vhost_vq_work_queue); @@ -282,7 +290,16 @@ EXPORT_SYMBOL_GPL(vhost_work_dev_flush); /* A lockless hint for busy polling code to exit the loop */ bool vhost_vq_has_work(struct vhost_virtqueue *vq) { - return vq->worker && !llist_empty(&vq->worker->work_list); + struct vhost_worker *worker; + bool has_work = false; + + rcu_read_lock(); + worker = rcu_dereference(vq->worker); + if (worker && !llist_empty(&worker->work_list)) + has_work = true; + rcu_read_unlock(); + + return has_work; } EXPORT_SYMBOL_GPL(vhost_vq_has_work); @@ -507,7 +524,7 @@ void vhost_dev_init(struct vhost_dev *dev, vq->log = NULL; vq->indirect = NULL; vq->heads = NULL; - vq->worker = NULL; + rcu_assign_pointer(vq->worker, NULL); vq->dev = dev; mutex_init(&vq->mutex); vhost_vq_reset(dev, vq); @@ -587,11 +604,30 @@ static void vhost_worker_put(struct vhost_dev *dev, struct vhost_worker *worker) kfree(worker); } -static void vhost_vq_detach_worker(struct vhost_virtqueue *vq) +static void vhost_vq_swap_worker(struct vhost_virtqueue *vq, + struct vhost_worker *new_worker, bool flush) { - if (vq->worker) - vhost_worker_put(vq->dev, vq->worker); - vq->worker = NULL; + struct vhost_worker *old_worker; + + old_worker = rcu_dereference_check(vq->worker, + lockdep_is_held(&vq->dev->mutex)); + rcu_assign_pointer(vq->worker, new_worker); + + if (!old_worker) + return; + + if (flush) { + /* + * For dev cleanup we won't have work running, but for the + * dynamic attach case we might so make sure we see the new + * worker and there is no work in the old worker when we + * return. + */ + synchronize_rcu(); + vhost_worker_flush(old_worker); + } + + vhost_worker_put(vq->dev, old_worker); } static int vhost_workers_idr_iter(int id, void *worker, void *dev) @@ -608,7 +644,7 @@ static void vhost_workers_free(struct vhost_dev *dev) return; for (i = 0; i < dev->nvqs; i++) - vhost_vq_detach_worker(dev->vqs[i]); + vhost_vq_swap_worker(dev->vqs[i], NULL, false); idr_for_each(&dev->worker_idr, vhost_workers_idr_iter, dev); } @@ -664,18 +700,13 @@ static int vhost_vq_attach_worker(struct vhost_virtqueue *vq, if (!dev->use_worker) return -EINVAL; - /* We don't support setting a worker on an active vq */ - if (vq->private_data) - return -EBUSY; - worker = idr_find(&dev->worker_idr, info->worker_id); if (!worker) return -ENODEV; refcount_inc(&worker->refcount); - vhost_vq_detach_worker(vq); - vq->worker = worker; + vhost_vq_swap_worker(vq, worker, true); return 0; } @@ -1826,7 +1857,10 @@ long vhost_vring_ioctl(struct vhost_dev *d, unsigned int ioctl, void __user *arg r = -EFAULT; break; } + /* We might flush worker, so we can't hold the vq mutex. */ + mutex_unlock(&vq->mutex); r = vhost_vq_attach_worker(vq, &w); + mutex_lock(&vq->mutex); if (!r && copy_to_user(argp, &w, sizeof(w))) r = -EFAULT; break; diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h index 1738388fa02d..7903ac6bc92d 100644 --- a/drivers/vhost/vhost.h +++ b/drivers/vhost/vhost.h @@ -81,7 +81,7 @@ struct vhost_vring_call { /* The virtqueue structure describes a queue attached to a device. */ struct vhost_virtqueue { struct vhost_dev *dev; - struct vhost_worker *worker; + struct vhost_worker __rcu *worker; /* The actual ring of buffers. */ struct mutex mutex; diff --git a/include/uapi/linux/vhost.h b/include/uapi/linux/vhost.h index 117ea92b3925..e0221c8ce877 100644 --- a/include/uapi/linux/vhost.h +++ b/include/uapi/linux/vhost.h @@ -88,7 +88,7 @@ #define VHOST_SET_VRING_ENDIAN _IOW(VHOST_VIRTIO, 0x13, struct vhost_vring_state) #define VHOST_GET_VRING_ENDIAN _IOW(VHOST_VIRTIO, 0x14, struct vhost_vring_state) /* Attach a vhost_worker created with VHOST_NEW_WORKER to one of the device's - * virtqueues. This must be done before the virtqueue is active. + * virtqueues. */ #define VHOST_ATTACH_VRING_WORKER _IOR(VHOST_VIRTIO, 0x15, \ struct vhost_vring_worker) -- 2.25.1
On Tue, Dec 7, 2021 at 10:51 AM Mike Christie <michael.christie at oracle.com> wrote:> > The following patches apply over linus's tree and the user_worker > patchset here: > > https://lore.kernel.org/virtualization/20211129194707.5863-1-michael.christie at oracle.com/T/#tIt looks to me it gets some acks, maybe we need to nudge the maintainer to merge that? This may simplify the review. Thanks