mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
vhost,virtio: fixes, features, cleanups
This adds CPR support for vhost. Plus some fixes, notably for a host of races we had when callback fire around device reset. Signed-off-by: Michael S. Tsirkin <mst@redhat.com> -----BEGIN PGP SIGNATURE----- iQFDBAABCgAtFiEEXQn9CHHI+FuUyooNKB8NuNKNVGkFAmrXRLEPHG1zdEByZWRo YXQuY29tAAoJECgfDbjSjVRpknAIAL7bxTwzZSTv5VbySGOCVDPZ4mysxRsKYbRT BTkDdYG+L4gvfwOMCuD2s3PIksNZwNvHCxYM9tsAOBQ4XgqOLW/GF07TifRMWUVz KFvl2wyEBuQb5ewKLgsASohHZ/2Bl+4qaL6RAuBN6KPQo5wYRCHlJEdh2IugWyIv /wUwBaO8u4pNHionj840TQcjjseYwnPBSG7V8aTRNXKcmiIQ7qqAx/88c7/2tdse FTSv7Y67LR02jVkiNTwJu0uFEGBNfzT3E47F/TPA53mJ53MLbW5Lej9kdlElKEQk 19XOYulcLdoPhuMyTJ7/pX1Zf7BsYydTAYBaNerb8w4+9NPXEqk= =xkKp -----END PGP SIGNATURE----- Merge tag 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mst/vhost Pull vhost,virtio updates from Michael Tsirkin: "This adds CPR support for vhost. Plus some fixes, notably for a host of races we had when callback fire around device reset" * tag 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mst/vhost: tools/virtio: add missing device-id/virtio.h wrapper header vhost/vsock: add VHOST_RESET_OWNER ioctl vhost: synchronize with RCU readers when freeing workers vhost/vsock: re-scan TX virtqueue on device start vhost/vsock: suppress EHOSTUNREACH fast-fail during CPR pause vhost/vsock: split out vhost_vsock_drop_backends helper virtio_pci: drop callback sync on reset virtio_pci_modern: move avq cleanup from reset to del_vqs virtio: synchronize callbacks after device reset virtio_vdpa: implement synchronize_cbs() platform/mellanox: mlxbf-tmfifo: implement synchronize_cbs() um: virtio_uml: implement synchronize_cbs() remoteproc: implement synchronize_cbs() for virtio devices virtio_ccw: always take irq_lock in the classic interrupt handler virtio_ccw: fix synchronize_cbs() after interrupt fallback
This commit is contained in:
commit
9f2516f45b
|
|
@ -20,6 +20,7 @@
|
|||
*
|
||||
* Based on Virtio MMIO driver by Pawel Moll, copyright 2011-2014, ARM Ltd.
|
||||
*/
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
|
@ -869,6 +870,14 @@ static void vu_reset(struct virtio_device *vdev)
|
|||
vu_dev->status = 0;
|
||||
}
|
||||
|
||||
static void vu_synchronize_cbs(struct virtio_device *vdev)
|
||||
{
|
||||
struct virtio_uml_device *vu_dev = to_virtio_uml_device(vdev);
|
||||
|
||||
if (vu_dev->irq != UM_IRQ_ALLOC)
|
||||
synchronize_irq(vu_dev->irq);
|
||||
}
|
||||
|
||||
static void vu_del_vq(struct virtqueue *vq)
|
||||
{
|
||||
struct virtio_uml_vq_info *info = vq->priv;
|
||||
|
|
@ -1121,6 +1130,7 @@ static const struct virtio_config_ops virtio_uml_config_ops = {
|
|||
.reset = vu_reset,
|
||||
.find_vqs = vu_find_vqs,
|
||||
.del_vqs = vu_del_vqs,
|
||||
.synchronize_cbs = vu_synchronize_cbs,
|
||||
.get_features = vu_get_features,
|
||||
.finalize_features = vu_finalize_features,
|
||||
.bus_name = vu_bus_name,
|
||||
|
|
|
|||
|
|
@ -1135,6 +1135,20 @@ static void mlxbf_tmfifo_virtio_reset(struct virtio_device *vdev)
|
|||
tm_vdev->status = 0;
|
||||
}
|
||||
|
||||
/* Synchronize with callbacks running in the FIFO work item. */
|
||||
static void mlxbf_tmfifo_virtio_synchronize_cbs(struct virtio_device *vdev)
|
||||
{
|
||||
struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev);
|
||||
struct mlxbf_tmfifo *fifo = tm_vdev->vrings[0].fifo;
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(fifo->spin_lock); i++) {
|
||||
spin_lock_irqsave(&fifo->spin_lock[i], flags);
|
||||
spin_unlock_irqrestore(&fifo->spin_lock[i], flags);
|
||||
}
|
||||
}
|
||||
|
||||
/* Read the value of a configuration field. */
|
||||
static void mlxbf_tmfifo_virtio_get(struct virtio_device *vdev,
|
||||
unsigned int offset,
|
||||
|
|
@ -1179,6 +1193,7 @@ static const struct virtio_config_ops mlxbf_tmfifo_virtio_config_ops = {
|
|||
.find_vqs = mlxbf_tmfifo_virtio_find_vqs,
|
||||
.del_vqs = mlxbf_tmfifo_virtio_del_vqs,
|
||||
.reset = mlxbf_tmfifo_virtio_reset,
|
||||
.synchronize_cbs = mlxbf_tmfifo_virtio_synchronize_cbs,
|
||||
.set_status = mlxbf_tmfifo_virtio_set_status,
|
||||
.get_status = mlxbf_tmfifo_virtio_get_status,
|
||||
.get = mlxbf_tmfifo_virtio_get,
|
||||
|
|
|
|||
|
|
@ -2410,6 +2410,7 @@ static void rproc_type_release(struct device *dev)
|
|||
|
||||
dev_info(&rproc->dev, "releasing %s\n", rproc->name);
|
||||
|
||||
cleanup_srcu_struct(&rproc->vq_srcu);
|
||||
idr_destroy(&rproc->notifyids);
|
||||
|
||||
if (rproc->index >= 0)
|
||||
|
|
@ -2507,6 +2508,11 @@ struct rproc *rproc_alloc(struct device *dev, const char *name,
|
|||
if (!rproc)
|
||||
return NULL;
|
||||
|
||||
if (init_srcu_struct(&rproc->vq_srcu)) {
|
||||
kfree(rproc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rproc->priv = &rproc[1];
|
||||
rproc->auto_boot = true;
|
||||
rproc->elf_class = ELFCLASSNONE;
|
||||
|
|
@ -2571,6 +2577,9 @@ EXPORT_SYMBOL(rproc_alloc);
|
|||
*
|
||||
* If no one holds any reference to rproc anymore, then its refcount would
|
||||
* now drop to zero, and it would be freed.
|
||||
*
|
||||
* Context: Any context, but the last reference must not be dropped from
|
||||
* atomic context.
|
||||
*/
|
||||
void rproc_free(struct rproc *rproc)
|
||||
{
|
||||
|
|
@ -2586,6 +2595,9 @@ EXPORT_SYMBOL(rproc_free);
|
|||
*
|
||||
* If no one holds any reference to rproc anymore, then its refcount would
|
||||
* now drop to zero, and it would be freed.
|
||||
*
|
||||
* Context: Any context, but the last reference must not be dropped from
|
||||
* atomic context.
|
||||
*/
|
||||
void rproc_put(struct rproc *rproc)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include <linux/err.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/srcu.h>
|
||||
|
||||
#include "remoteproc_internal.h"
|
||||
|
||||
|
|
@ -88,15 +89,23 @@ static bool rproc_virtio_notify(struct virtqueue *vq)
|
|||
*/
|
||||
irqreturn_t rproc_vq_interrupt(struct rproc *rproc, int notifyid)
|
||||
{
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
struct rproc_vring *rvring;
|
||||
struct virtqueue *vq;
|
||||
int idx;
|
||||
|
||||
dev_dbg(&rproc->dev, "vq index %d is interrupted\n", notifyid);
|
||||
|
||||
rvring = idr_find(&rproc->notifyids, notifyid);
|
||||
if (!rvring || !rvring->vq)
|
||||
return IRQ_NONE;
|
||||
idx = srcu_read_lock(&rproc->vq_srcu);
|
||||
|
||||
return vring_interrupt(0, rvring->vq);
|
||||
rvring = idr_find(&rproc->notifyids, notifyid);
|
||||
vq = rvring ? READ_ONCE(rvring->vq) : NULL;
|
||||
if (vq)
|
||||
ret = vring_interrupt(0, vq);
|
||||
|
||||
srcu_read_unlock(&rproc->vq_srcu, idx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(rproc_vq_interrupt);
|
||||
|
||||
|
|
@ -153,7 +162,7 @@ static struct virtqueue *rp_find_vq(struct virtio_device *vdev,
|
|||
|
||||
vq->num_max = num;
|
||||
|
||||
rvring->vq = vq;
|
||||
WRITE_ONCE(rvring->vq, vq);
|
||||
vq->priv = rvring;
|
||||
|
||||
/* Update vring in resource table */
|
||||
|
|
@ -165,14 +174,20 @@ static struct virtqueue *rp_find_vq(struct virtio_device *vdev,
|
|||
|
||||
static void __rproc_virtio_del_vqs(struct virtio_device *vdev)
|
||||
{
|
||||
struct rproc *rproc = vdev_to_rproc(vdev);
|
||||
struct virtqueue *vq, *n;
|
||||
struct rproc_vring *rvring;
|
||||
|
||||
list_for_each_entry_safe(vq, n, &vdev->vqs, list) {
|
||||
list_for_each_entry(vq, &vdev->vqs, list) {
|
||||
rvring = vq->priv;
|
||||
rvring->vq = NULL;
|
||||
vring_del_virtqueue(vq);
|
||||
WRITE_ONCE(rvring->vq, NULL);
|
||||
}
|
||||
|
||||
/* Synchronize with rproc_vq_interrupt() callers that found a queue. */
|
||||
synchronize_srcu(&rproc->vq_srcu);
|
||||
|
||||
list_for_each_entry_safe(vq, n, &vdev->vqs, list)
|
||||
vring_del_virtqueue(vq);
|
||||
}
|
||||
|
||||
static void rproc_virtio_del_vqs(struct virtio_device *vdev)
|
||||
|
|
@ -242,6 +257,13 @@ static void rproc_virtio_reset(struct virtio_device *vdev)
|
|||
dev_dbg(&vdev->dev, "reset !\n");
|
||||
}
|
||||
|
||||
static void rproc_virtio_synchronize_cbs(struct virtio_device *vdev)
|
||||
{
|
||||
struct rproc *rproc = vdev_to_rproc(vdev);
|
||||
|
||||
synchronize_srcu(&rproc->vq_srcu);
|
||||
}
|
||||
|
||||
/* provide the vdev features as retrieved from the firmware */
|
||||
static u64 rproc_virtio_get_features(struct virtio_device *vdev)
|
||||
{
|
||||
|
|
@ -330,6 +352,7 @@ static const struct virtio_config_ops rproc_virtio_config_ops = {
|
|||
.find_vqs = rproc_virtio_find_vqs,
|
||||
.del_vqs = rproc_virtio_del_vqs,
|
||||
.reset = rproc_virtio_reset,
|
||||
.synchronize_cbs = rproc_virtio_synchronize_cbs,
|
||||
.set_status = rproc_virtio_set_status,
|
||||
.get_status = rproc_virtio_get_status,
|
||||
.get = rproc_virtio_get,
|
||||
|
|
|
|||
|
|
@ -1062,7 +1062,7 @@ static void virtio_ccw_synchronize_cbs(struct virtio_device *vdev)
|
|||
struct virtio_ccw_device *vcdev = to_vc_device(vdev);
|
||||
struct airq_info *info = vcdev->airq_info;
|
||||
|
||||
if (info) {
|
||||
if (vcdev->is_thinint && info) {
|
||||
/*
|
||||
* This device uses adapter interrupts: synchronize with
|
||||
* vring_interrupt() called by virtio_airq_handler()
|
||||
|
|
@ -1204,13 +1204,11 @@ static void virtio_ccw_int_handler(struct ccw_device *cdev,
|
|||
vcdev->err = -EIO;
|
||||
}
|
||||
virtio_ccw_check_activity(vcdev, activity);
|
||||
#ifdef CONFIG_VIRTIO_HARDEN_NOTIFICATION
|
||||
/*
|
||||
* Paired with virtio_ccw_synchronize_cbs() and interrupts are
|
||||
* disabled here.
|
||||
*/
|
||||
read_lock(&vcdev->irq_lock);
|
||||
#endif
|
||||
for_each_set_bit(i, indicators(vcdev),
|
||||
sizeof(*indicators(vcdev)) * BITS_PER_BYTE) {
|
||||
/* The bit clear must happen before the vring kick. */
|
||||
|
|
@ -1219,9 +1217,7 @@ static void virtio_ccw_int_handler(struct ccw_device *cdev,
|
|||
vq = virtio_ccw_vq_by_ind(vcdev, i);
|
||||
vring_interrupt(0, vq);
|
||||
}
|
||||
#ifdef CONFIG_VIRTIO_HARDEN_NOTIFICATION
|
||||
read_unlock(&vcdev->irq_lock);
|
||||
#endif
|
||||
if (test_bit(0, indicators2(vcdev))) {
|
||||
virtio_config_changed(&vcdev->vdev);
|
||||
clear_bit(0, indicators2(vcdev));
|
||||
|
|
|
|||
|
|
@ -1452,7 +1452,7 @@ static int vhost_net_release(struct inode *inode, struct file *f)
|
|||
vhost_net_stop(n, &tx_sock, &rx_sock);
|
||||
vhost_net_flush(n);
|
||||
vhost_dev_stop(&n->dev);
|
||||
vhost_dev_cleanup(&n->dev);
|
||||
vhost_dev_cleanup(&n->dev, false);
|
||||
vhost_net_vq_reset(n);
|
||||
if (tx_sock)
|
||||
sockfd_put(tx_sock);
|
||||
|
|
@ -1661,7 +1661,7 @@ static long vhost_net_reset_owner(struct vhost_net *n)
|
|||
vhost_net_stop(n, &tx_sock, &rx_sock);
|
||||
vhost_net_flush(n);
|
||||
vhost_dev_stop(&n->dev);
|
||||
vhost_dev_reset_owner(&n->dev, umem);
|
||||
vhost_dev_reset_owner(&n->dev, umem, false);
|
||||
vhost_net_vq_reset(n);
|
||||
done:
|
||||
mutex_unlock(&n->dev.mutex);
|
||||
|
|
|
|||
|
|
@ -2390,7 +2390,7 @@ static int vhost_scsi_release(struct inode *inode, struct file *f)
|
|||
mutex_unlock(&vs->dev.mutex);
|
||||
vhost_scsi_clear_endpoint(vs, &t);
|
||||
vhost_dev_stop(&vs->dev);
|
||||
vhost_dev_cleanup(&vs->dev);
|
||||
vhost_dev_cleanup(&vs->dev, false);
|
||||
kfree(vs->dev.vqs);
|
||||
kvfree(vs->vqs);
|
||||
kfree(vs->old_inflight);
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ static int vhost_test_release(struct inode *inode, struct file *f)
|
|||
vhost_test_stop(n, &private);
|
||||
vhost_test_flush(n);
|
||||
vhost_dev_stop(&n->dev);
|
||||
vhost_dev_cleanup(&n->dev);
|
||||
vhost_dev_cleanup(&n->dev, false);
|
||||
kfree(n->dev.vqs);
|
||||
kfree(n);
|
||||
return 0;
|
||||
|
|
@ -238,7 +238,7 @@ static long vhost_test_reset_owner(struct vhost_test *n)
|
|||
vhost_test_stop(n, &priv);
|
||||
vhost_test_flush(n);
|
||||
vhost_dev_stop(&n->dev);
|
||||
vhost_dev_reset_owner(&n->dev, umem);
|
||||
vhost_dev_reset_owner(&n->dev, umem, false);
|
||||
done:
|
||||
mutex_unlock(&n->dev.mutex);
|
||||
return err;
|
||||
|
|
|
|||
|
|
@ -948,7 +948,7 @@ static long vhost_vdpa_unlocked_ioctl(struct file *filep,
|
|||
case VHOST_SET_OWNER:
|
||||
r = vhost_vdpa_bind_mm(v);
|
||||
if (r)
|
||||
vhost_dev_reset_owner(d, NULL);
|
||||
vhost_dev_reset_owner(d, NULL, false);
|
||||
break;
|
||||
}
|
||||
out:
|
||||
|
|
@ -1453,7 +1453,7 @@ static void vhost_vdpa_cleanup(struct vhost_vdpa *v)
|
|||
}
|
||||
|
||||
vhost_vdpa_free_domain(v);
|
||||
vhost_dev_cleanup(&v->vdev);
|
||||
vhost_dev_cleanup(&v->vdev, false);
|
||||
kfree(v->vdev.vqs);
|
||||
v->vdev.vqs = NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -731,7 +731,7 @@ static void vhost_worker_destroy(struct vhost_dev *dev,
|
|||
kfree(worker);
|
||||
}
|
||||
|
||||
static void vhost_workers_free(struct vhost_dev *dev)
|
||||
static void vhost_workers_free(struct vhost_dev *dev, bool sync)
|
||||
{
|
||||
struct vhost_worker *worker;
|
||||
unsigned long i;
|
||||
|
|
@ -741,6 +741,21 @@ static void vhost_workers_free(struct vhost_dev *dev)
|
|||
|
||||
for (i = 0; i < dev->nvqs; i++)
|
||||
rcu_assign_pointer(dev->vqs[i]->worker, NULL);
|
||||
|
||||
/*
|
||||
* This path can be reached by lockless work queuers. In this case
|
||||
* vhost_vq_work_queue() reads vq->worker under rcu_read_lock(), so a
|
||||
* RCU reader that fetched a worker before we cleared the pointers above
|
||||
* may still be queueing work on it. Wait for those readers to finish,
|
||||
* then flush so any work they queued runs (clearing VHOST_WORK_QUEUED)
|
||||
* before the workers are freed. Notably, that is the case for the
|
||||
* vsock RESET_OWNER path.
|
||||
*/
|
||||
if (sync) {
|
||||
synchronize_rcu();
|
||||
vhost_dev_flush(dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* Free the default worker we created and cleanup workers userspace
|
||||
* created but couldn't clean up (it forgot or crashed).
|
||||
|
|
@ -1163,11 +1178,12 @@ struct vhost_iotlb *vhost_dev_reset_owner_prepare(void)
|
|||
EXPORT_SYMBOL_GPL(vhost_dev_reset_owner_prepare);
|
||||
|
||||
/* Caller should have device mutex */
|
||||
void vhost_dev_reset_owner(struct vhost_dev *dev, struct vhost_iotlb *umem)
|
||||
void vhost_dev_reset_owner(struct vhost_dev *dev, struct vhost_iotlb *umem,
|
||||
bool sync)
|
||||
{
|
||||
int i;
|
||||
|
||||
vhost_dev_cleanup(dev);
|
||||
vhost_dev_cleanup(dev, sync);
|
||||
|
||||
dev->fork_owner = fork_from_owner_default;
|
||||
dev->umem = umem;
|
||||
|
|
@ -1227,7 +1243,7 @@ void vhost_clear_msg(struct vhost_dev *dev)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(vhost_clear_msg);
|
||||
|
||||
void vhost_dev_cleanup(struct vhost_dev *dev)
|
||||
void vhost_dev_cleanup(struct vhost_dev *dev, bool sync)
|
||||
{
|
||||
int i;
|
||||
|
||||
|
|
@ -1251,7 +1267,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_workers_free(dev);
|
||||
vhost_workers_free(dev, sync);
|
||||
vhost_detach_mm(dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vhost_dev_cleanup);
|
||||
|
|
|
|||
|
|
@ -219,8 +219,9 @@ long vhost_dev_set_owner(struct vhost_dev *dev);
|
|||
bool vhost_dev_has_owner(struct vhost_dev *dev);
|
||||
long vhost_dev_check_owner(struct vhost_dev *);
|
||||
struct vhost_iotlb *vhost_dev_reset_owner_prepare(void);
|
||||
void vhost_dev_reset_owner(struct vhost_dev *dev, struct vhost_iotlb *iotlb);
|
||||
void vhost_dev_cleanup(struct vhost_dev *);
|
||||
void vhost_dev_reset_owner(struct vhost_dev *dev, struct vhost_iotlb *iotlb,
|
||||
bool sync);
|
||||
void vhost_dev_cleanup(struct vhost_dev *dev, bool sync);
|
||||
void vhost_dev_stop(struct vhost_dev *);
|
||||
long vhost_dev_ioctl(struct vhost_dev *, unsigned int ioctl, void __user *argp);
|
||||
long vhost_vring_ioctl(struct vhost_dev *d, unsigned int ioctl, void __user *argp);
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ struct vhost_vsock {
|
|||
|
||||
u32 guest_cid;
|
||||
bool seqpacket_allow;
|
||||
bool ever_started; /* set on first SET_RUNNING(1); never cleared */
|
||||
};
|
||||
|
||||
static u32 vhost_transport_get_local_cid(void)
|
||||
|
|
@ -302,17 +303,12 @@ vhost_transport_send_pkt(struct sk_buff *skb, struct net *net)
|
|||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Fast-fail if the guest hasn't enabled the RX vq yet. Queuing the packet
|
||||
* and making the caller wait is pointless: even if the guest manages to init
|
||||
* within the timeout, it'll immediately reply with RST, because there's no
|
||||
* listener on the port yet.
|
||||
*
|
||||
* vhost_vq_get_backend() without vq->mutex is acceptable here: locking
|
||||
* the mutex would be too expensive in this hot path, and we already have
|
||||
* all the outcomes covered: if the backend becomes NULL right after the check,
|
||||
* vhost_transport_do_send_pkt() will check it under the mutex anyway.
|
||||
/* Fast-fail until the guest first enables the device (SET_RUNNING(1)).
|
||||
* Before that there is no listener, so queuing is pointless.
|
||||
* 'ever_started' is never cleared, so once we're up we keep queuing
|
||||
* across later stop / CPR-pause windows.
|
||||
*/
|
||||
if (unlikely(!data_race(vhost_vq_get_backend(&vsock->vqs[VSOCK_VQ_RX])))) {
|
||||
if (unlikely(!READ_ONCE(vsock->ever_started))) {
|
||||
rcu_read_unlock();
|
||||
kfree_skb(skb);
|
||||
return -EHOSTUNREACH;
|
||||
|
|
@ -640,11 +636,23 @@ static int vhost_vsock_start(struct vhost_vsock *vsock)
|
|||
mutex_unlock(&vq->mutex);
|
||||
}
|
||||
|
||||
/* Set 'ever_started' flag on the first start; never cleared, so send_pkt
|
||||
* keeps queuing (instead of fast-failing) on later stop / CPR pauses.
|
||||
*/
|
||||
WRITE_ONCE(vsock->ever_started, true);
|
||||
|
||||
/* Some packets may have been queued before the device was started,
|
||||
* let's kick the send worker to send them.
|
||||
*/
|
||||
vhost_vq_work_queue(&vsock->vqs[VSOCK_VQ_RX], &vsock->send_pkt_work);
|
||||
|
||||
/* The guest may have added TX buffers while the device was stopped
|
||||
* (e.g. across VHOST_RESET_OWNER) and their kicks got consumed by
|
||||
* the NULL-backend window. Re-scan the TX VQ, mirroring the RX
|
||||
* send-worker kick above.
|
||||
*/
|
||||
vhost_poll_queue(&vsock->vqs[VSOCK_VQ_TX].poll);
|
||||
|
||||
mutex_unlock(&vsock->dev.mutex);
|
||||
return 0;
|
||||
|
||||
|
|
@ -664,9 +672,24 @@ static int vhost_vsock_start(struct vhost_vsock *vsock)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void vhost_vsock_drop_backends(struct vhost_vsock *vsock)
|
||||
{
|
||||
struct vhost_virtqueue *vq;
|
||||
size_t i;
|
||||
|
||||
lockdep_assert_held(&vsock->dev.mutex);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(vsock->vqs); i++) {
|
||||
vq = &vsock->vqs[i];
|
||||
|
||||
mutex_lock(&vq->mutex);
|
||||
vhost_vq_set_backend(vq, NULL);
|
||||
mutex_unlock(&vq->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
static int vhost_vsock_stop(struct vhost_vsock *vsock, bool check_owner)
|
||||
{
|
||||
size_t i;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&vsock->dev.mutex);
|
||||
|
|
@ -677,14 +700,7 @@ static int vhost_vsock_stop(struct vhost_vsock *vsock, bool check_owner)
|
|||
goto err;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(vsock->vqs); i++) {
|
||||
struct vhost_virtqueue *vq = &vsock->vqs[i];
|
||||
|
||||
mutex_lock(&vq->mutex);
|
||||
vhost_vq_set_backend(vq, NULL);
|
||||
mutex_unlock(&vq->mutex);
|
||||
}
|
||||
|
||||
vhost_vsock_drop_backends(vsock);
|
||||
err:
|
||||
mutex_unlock(&vsock->dev.mutex);
|
||||
return ret;
|
||||
|
|
@ -720,6 +736,7 @@ static int vhost_vsock_dev_open(struct inode *inode, struct file *file)
|
|||
|
||||
vsock->guest_cid = 0; /* no CID assigned yet */
|
||||
vsock->seqpacket_allow = false;
|
||||
vsock->ever_started = false;
|
||||
|
||||
atomic_set(&vsock->queued_replies, 0);
|
||||
|
||||
|
|
@ -807,7 +824,7 @@ static int vhost_vsock_dev_release(struct inode *inode, struct file *file)
|
|||
|
||||
virtio_vsock_skb_queue_purge(&vsock->send_pkt_queue);
|
||||
|
||||
vhost_dev_cleanup(&vsock->dev);
|
||||
vhost_dev_cleanup(&vsock->dev, false);
|
||||
put_net_track(vsock->net, &vsock->ns_tracker);
|
||||
kfree(vsock->dev.vqs);
|
||||
vhost_vsock_free(vsock);
|
||||
|
|
@ -888,6 +905,36 @@ static int vhost_vsock_set_features(struct vhost_vsock *vsock, u64 features)
|
|||
return -EFAULT;
|
||||
}
|
||||
|
||||
static long vhost_vsock_reset_owner(struct vhost_vsock *vsock)
|
||||
{
|
||||
struct vhost_iotlb *umem;
|
||||
long err;
|
||||
|
||||
mutex_lock(&vsock->dev.mutex);
|
||||
err = vhost_dev_check_owner(&vsock->dev);
|
||||
if (err)
|
||||
goto done;
|
||||
umem = vhost_dev_reset_owner_prepare();
|
||||
if (!umem) {
|
||||
err = -ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
vhost_vsock_drop_backends(vsock);
|
||||
vhost_vsock_flush(vsock);
|
||||
vhost_dev_stop(&vsock->dev);
|
||||
|
||||
/*
|
||||
* vsock keeps the CID hashed across RESET_OWNER, so a lockless sender
|
||||
* can still reach the device while its workers are freed. Pass
|
||||
* sync=true so vhost_workers_free() fences RCU readers and flushes
|
||||
* before freeing them.
|
||||
*/
|
||||
vhost_dev_reset_owner(&vsock->dev, umem, true);
|
||||
done:
|
||||
mutex_unlock(&vsock->dev.mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
static long vhost_vsock_dev_ioctl(struct file *f, unsigned int ioctl,
|
||||
unsigned long arg)
|
||||
{
|
||||
|
|
@ -931,6 +978,8 @@ static long vhost_vsock_dev_ioctl(struct file *f, unsigned int ioctl,
|
|||
return -EOPNOTSUPP;
|
||||
vhost_set_backend_features(&vsock->dev, features);
|
||||
return 0;
|
||||
case VHOST_RESET_OWNER:
|
||||
return vhost_vsock_reset_owner(vsock);
|
||||
default:
|
||||
mutex_lock(&vsock->dev.mutex);
|
||||
r = vhost_dev_ioctl(&vsock->dev, ioctl, argp);
|
||||
|
|
|
|||
|
|
@ -264,6 +264,8 @@ void virtio_reset_device(struct virtio_device *dev)
|
|||
#endif
|
||||
|
||||
dev->config->reset(dev);
|
||||
/* Flush pending VQ/configuration callbacks. */
|
||||
virtio_synchronize_cbs(dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(virtio_reset_device);
|
||||
|
||||
|
|
|
|||
|
|
@ -98,8 +98,6 @@ static void vp_reset(struct virtio_device *vdev)
|
|||
/* Flush out the status write, and flush in device writes,
|
||||
* including MSi-X interrupts, if any. */
|
||||
vp_legacy_get_status(&vp_dev->ldev);
|
||||
/* Flush pending VQ/configuration callbacks. */
|
||||
vp_synchronize_vectors(vdev);
|
||||
}
|
||||
|
||||
static u16 vp_config_vector(struct virtio_pci_device *vp_dev, u16 vector)
|
||||
|
|
|
|||
|
|
@ -364,6 +364,12 @@ static void vp_modern_avq_cleanup(struct virtio_device *vdev)
|
|||
}
|
||||
}
|
||||
|
||||
static void vp_modern_del_vqs(struct virtio_device *vdev)
|
||||
{
|
||||
vp_modern_avq_cleanup(vdev);
|
||||
vp_del_vqs(vdev);
|
||||
}
|
||||
|
||||
static void vp_transport_features(struct virtio_device *vdev, u64 features)
|
||||
{
|
||||
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
|
||||
|
|
@ -557,11 +563,6 @@ static void vp_reset(struct virtio_device *vdev)
|
|||
*/
|
||||
while (vp_modern_get_status(mdev))
|
||||
msleep(1);
|
||||
|
||||
vp_modern_avq_cleanup(vdev);
|
||||
|
||||
/* Flush pending VQ/configuration callbacks. */
|
||||
vp_synchronize_vectors(vdev);
|
||||
}
|
||||
|
||||
static int vp_active_vq(struct virtqueue *vq, u16 msix_vec)
|
||||
|
|
@ -1232,7 +1233,7 @@ static const struct virtio_config_ops virtio_pci_config_nodev_ops = {
|
|||
.set_status = vp_set_status,
|
||||
.reset = vp_reset,
|
||||
.find_vqs = vp_modern_find_vqs,
|
||||
.del_vqs = vp_del_vqs,
|
||||
.del_vqs = vp_modern_del_vqs,
|
||||
.synchronize_cbs = vp_synchronize_vectors,
|
||||
.get_extended_features = vp_get_features,
|
||||
.finalize_features = vp_finalize_features,
|
||||
|
|
@ -1252,7 +1253,7 @@ static const struct virtio_config_ops virtio_pci_config_ops = {
|
|||
.set_status = vp_set_status,
|
||||
.reset = vp_reset,
|
||||
.find_vqs = vp_modern_find_vqs,
|
||||
.del_vqs = vp_del_vqs,
|
||||
.del_vqs = vp_modern_del_vqs,
|
||||
.synchronize_cbs = vp_synchronize_vectors,
|
||||
.get_extended_features = vp_get_features,
|
||||
.finalize_features = vp_finalize_features,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/srcu.h>
|
||||
#include <linux/uuid.h>
|
||||
#include <linux/group_cpus.h>
|
||||
#include <linux/virtio.h>
|
||||
|
|
@ -27,6 +28,7 @@
|
|||
struct virtio_vdpa_device {
|
||||
struct virtio_device vdev;
|
||||
struct vdpa_device *vdpa;
|
||||
struct srcu_struct cb_srcu;
|
||||
u64 features;
|
||||
};
|
||||
|
||||
|
|
@ -114,8 +116,11 @@ static bool virtio_vdpa_notify_with_data(struct virtqueue *vq)
|
|||
static irqreturn_t virtio_vdpa_config_cb(void *private)
|
||||
{
|
||||
struct virtio_vdpa_device *vd_dev = private;
|
||||
int idx;
|
||||
|
||||
idx = srcu_read_lock(&vd_dev->cb_srcu);
|
||||
virtio_config_changed(&vd_dev->vdev);
|
||||
srcu_read_unlock(&vd_dev->cb_srcu, idx);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
|
@ -123,8 +128,22 @@ static irqreturn_t virtio_vdpa_config_cb(void *private)
|
|||
static irqreturn_t virtio_vdpa_virtqueue_cb(void *private)
|
||||
{
|
||||
struct virtqueue *vq = private;
|
||||
struct virtio_vdpa_device *vd_dev = to_virtio_vdpa_device(vq->vdev);
|
||||
irqreturn_t ret;
|
||||
int idx;
|
||||
|
||||
return vring_interrupt(0, vq);
|
||||
idx = srcu_read_lock(&vd_dev->cb_srcu);
|
||||
ret = vring_interrupt(0, vq);
|
||||
srcu_read_unlock(&vd_dev->cb_srcu, idx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void virtio_vdpa_synchronize_cbs(struct virtio_device *vdev)
|
||||
{
|
||||
struct virtio_vdpa_device *vd_dev = to_virtio_vdpa_device(vdev);
|
||||
|
||||
synchronize_srcu(&vd_dev->cb_srcu);
|
||||
}
|
||||
|
||||
static struct virtqueue *
|
||||
|
|
@ -440,6 +459,7 @@ static const struct virtio_config_ops virtio_vdpa_config_ops = {
|
|||
.reset = virtio_vdpa_reset,
|
||||
.find_vqs = virtio_vdpa_find_vqs,
|
||||
.del_vqs = virtio_vdpa_del_vqs,
|
||||
.synchronize_cbs = virtio_vdpa_synchronize_cbs,
|
||||
.get_features = virtio_vdpa_get_features,
|
||||
.finalize_features = virtio_vdpa_finalize_features,
|
||||
.bus_name = virtio_vdpa_bus_name,
|
||||
|
|
@ -454,6 +474,7 @@ static void virtio_vdpa_release_dev(struct device *_d)
|
|||
struct virtio_vdpa_device *vd_dev =
|
||||
container_of(vdev, struct virtio_vdpa_device, vdev);
|
||||
|
||||
cleanup_srcu_struct(&vd_dev->cb_srcu);
|
||||
kfree(vd_dev);
|
||||
}
|
||||
|
||||
|
|
@ -467,6 +488,11 @@ static int virtio_vdpa_probe(struct vdpa_device *vdpa)
|
|||
if (!vd_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
if (init_srcu_struct(&vd_dev->cb_srcu)) {
|
||||
kfree(vd_dev);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
vd_dev->vdev.dev.parent = vdpa->map ? &vdpa->dev :
|
||||
vdpa_get_map(vdpa).dma_dev;
|
||||
vd_dev->vdev.dev.release = virtio_vdpa_release_dev;
|
||||
|
|
@ -489,10 +515,12 @@ static int virtio_vdpa_probe(struct vdpa_device *vdpa)
|
|||
return 0;
|
||||
|
||||
err:
|
||||
if (reg_dev)
|
||||
if (reg_dev) {
|
||||
put_device(&vd_dev->vdev.dev);
|
||||
else
|
||||
} else {
|
||||
cleanup_srcu_struct(&vd_dev->cb_srcu);
|
||||
kfree(vd_dev);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <linux/cdev.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/srcu.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/rsc_table.h>
|
||||
|
||||
|
|
@ -228,6 +229,7 @@ enum rproc_features {
|
|||
* @mappings: list of iommu mappings we initiated, needed on shutdown
|
||||
* @bootaddr: address of first instruction to boot rproc with (optional)
|
||||
* @rvdevs: list of remote virtio devices
|
||||
* @vq_srcu: SRCU domain for the virtqueue callbacks of @rvdevs
|
||||
* @subdevs: list of subdevices, to following the running state
|
||||
* @notifyids: idr for dynamically assigning rproc-wide unique notify ids
|
||||
* @index: index of this rproc device
|
||||
|
|
@ -274,6 +276,7 @@ struct rproc {
|
|||
struct list_head mappings;
|
||||
u64 bootaddr;
|
||||
struct list_head rvdevs;
|
||||
struct srcu_struct vq_srcu;
|
||||
struct list_head subdevs;
|
||||
struct idr notifyids;
|
||||
int index;
|
||||
|
|
|
|||
2
tools/virtio/linux/device-id/virtio.h
Normal file
2
tools/virtio/linux/device-id/virtio.h
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#include <../../include/linux/device-id/virtio.h>
|
||||
Loading…
Reference in New Issue
Block a user