|
@@ -422,7 +422,7 @@ static void reclaim_dma_bufs(void)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-static struct port_buffer *alloc_buf(struct virtqueue *vq, size_t buf_size,
|
|
|
+static struct port_buffer *alloc_buf(struct virtio_device *vdev, size_t buf_size,
|
|
|
int pages)
|
|
|
{
|
|
|
struct port_buffer *buf;
|
|
@@ -445,16 +445,16 @@ static struct port_buffer *alloc_buf(struct virtqueue *vq, size_t buf_size,
|
|
|
return buf;
|
|
|
}
|
|
|
|
|
|
- if (is_rproc_serial(vq->vdev)) {
|
|
|
+ if (is_rproc_serial(vdev)) {
|
|
|
/*
|
|
|
* Allocate DMA memory from ancestor. When a virtio
|
|
|
* device is created by remoteproc, the DMA memory is
|
|
|
* associated with the grandparent device:
|
|
|
* vdev => rproc => platform-dev.
|
|
|
*/
|
|
|
- if (!vq->vdev->dev.parent || !vq->vdev->dev.parent->parent)
|
|
|
+ if (!vdev->dev.parent || !vdev->dev.parent->parent)
|
|
|
goto free_buf;
|
|
|
- buf->dev = vq->vdev->dev.parent->parent;
|
|
|
+ buf->dev = vdev->dev.parent->parent;
|
|
|
|
|
|
/* Increase device refcnt to avoid freeing it */
|
|
|
get_device(buf->dev);
|
|
@@ -838,7 +838,7 @@ static ssize_t port_fops_write(struct file *filp, const char __user *ubuf,
|
|
|
|
|
|
count = min((size_t)(32 * 1024), count);
|
|
|
|
|
|
- buf = alloc_buf(port->out_vq, count, 0);
|
|
|
+ buf = alloc_buf(port->portdev->vdev, count, 0);
|
|
|
if (!buf)
|
|
|
return -ENOMEM;
|
|
|
|
|
@@ -957,7 +957,7 @@ static ssize_t port_fops_splice_write(struct pipe_inode_info *pipe,
|
|
|
if (ret < 0)
|
|
|
goto error_out;
|
|
|
|
|
|
- buf = alloc_buf(port->out_vq, 0, pipe->nrbufs);
|
|
|
+ buf = alloc_buf(port->portdev->vdev, 0, pipe->nrbufs);
|
|
|
if (!buf) {
|
|
|
ret = -ENOMEM;
|
|
|
goto error_out;
|
|
@@ -1374,7 +1374,7 @@ static unsigned int fill_queue(struct virtqueue *vq, spinlock_t *lock)
|
|
|
|
|
|
nr_added_bufs = 0;
|
|
|
do {
|
|
|
- buf = alloc_buf(vq, PAGE_SIZE, 0);
|
|
|
+ buf = alloc_buf(vq->vdev, PAGE_SIZE, 0);
|
|
|
if (!buf)
|
|
|
break;
|
|
|
|
|
@@ -1402,7 +1402,6 @@ static int add_port(struct ports_device *portdev, u32 id)
|
|
|
{
|
|
|
char debugfs_name[16];
|
|
|
struct port *port;
|
|
|
- struct port_buffer *buf;
|
|
|
dev_t devt;
|
|
|
unsigned int nr_added_bufs;
|
|
|
int err;
|
|
@@ -1513,8 +1512,6 @@ static int add_port(struct ports_device *portdev, u32 id)
|
|
|
return 0;
|
|
|
|
|
|
free_inbufs:
|
|
|
- while ((buf = virtqueue_detach_unused_buf(port->in_vq)))
|
|
|
- free_buf(buf, true);
|
|
|
free_device:
|
|
|
device_destroy(pdrvdata.class, port->dev->devt);
|
|
|
free_cdev:
|
|
@@ -1539,34 +1536,14 @@ static void remove_port(struct kref *kref)
|
|
|
|
|
|
static void remove_port_data(struct port *port)
|
|
|
{
|
|
|
- struct port_buffer *buf;
|
|
|
-
|
|
|
spin_lock_irq(&port->inbuf_lock);
|
|
|
/* Remove unused data this port might have received. */
|
|
|
discard_port_data(port);
|
|
|
spin_unlock_irq(&port->inbuf_lock);
|
|
|
|
|
|
- /* Remove buffers we queued up for the Host to send us data in. */
|
|
|
- do {
|
|
|
- spin_lock_irq(&port->inbuf_lock);
|
|
|
- buf = virtqueue_detach_unused_buf(port->in_vq);
|
|
|
- spin_unlock_irq(&port->inbuf_lock);
|
|
|
- if (buf)
|
|
|
- free_buf(buf, true);
|
|
|
- } while (buf);
|
|
|
-
|
|
|
spin_lock_irq(&port->outvq_lock);
|
|
|
reclaim_consumed_buffers(port);
|
|
|
spin_unlock_irq(&port->outvq_lock);
|
|
|
-
|
|
|
- /* Free pending buffers from the out-queue. */
|
|
|
- do {
|
|
|
- spin_lock_irq(&port->outvq_lock);
|
|
|
- buf = virtqueue_detach_unused_buf(port->out_vq);
|
|
|
- spin_unlock_irq(&port->outvq_lock);
|
|
|
- if (buf)
|
|
|
- free_buf(buf, true);
|
|
|
- } while (buf);
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -1791,13 +1768,24 @@ static void control_work_handler(struct work_struct *work)
|
|
|
spin_unlock(&portdev->c_ivq_lock);
|
|
|
}
|
|
|
|
|
|
+static void flush_bufs(struct virtqueue *vq, bool can_sleep)
|
|
|
+{
|
|
|
+ struct port_buffer *buf;
|
|
|
+ unsigned int len;
|
|
|
+
|
|
|
+ while ((buf = virtqueue_get_buf(vq, &len)))
|
|
|
+ free_buf(buf, can_sleep);
|
|
|
+}
|
|
|
+
|
|
|
static void out_intr(struct virtqueue *vq)
|
|
|
{
|
|
|
struct port *port;
|
|
|
|
|
|
port = find_port_by_vq(vq->vdev->priv, vq);
|
|
|
- if (!port)
|
|
|
+ if (!port) {
|
|
|
+ flush_bufs(vq, false);
|
|
|
return;
|
|
|
+ }
|
|
|
|
|
|
wake_up_interruptible(&port->waitqueue);
|
|
|
}
|
|
@@ -1808,8 +1796,10 @@ static void in_intr(struct virtqueue *vq)
|
|
|
unsigned long flags;
|
|
|
|
|
|
port = find_port_by_vq(vq->vdev->priv, vq);
|
|
|
- if (!port)
|
|
|
+ if (!port) {
|
|
|
+ flush_bufs(vq, false);
|
|
|
return;
|
|
|
+ }
|
|
|
|
|
|
spin_lock_irqsave(&port->inbuf_lock, flags);
|
|
|
port->inbuf = get_inbuf(port);
|
|
@@ -1984,24 +1974,54 @@ static const struct file_operations portdev_fops = {
|
|
|
|
|
|
static void remove_vqs(struct ports_device *portdev)
|
|
|
{
|
|
|
+ struct virtqueue *vq;
|
|
|
+
|
|
|
+ virtio_device_for_each_vq(portdev->vdev, vq) {
|
|
|
+ struct port_buffer *buf;
|
|
|
+
|
|
|
+ flush_bufs(vq, true);
|
|
|
+ while ((buf = virtqueue_detach_unused_buf(vq)))
|
|
|
+ free_buf(buf, true);
|
|
|
+ }
|
|
|
portdev->vdev->config->del_vqs(portdev->vdev);
|
|
|
kfree(portdev->in_vqs);
|
|
|
kfree(portdev->out_vqs);
|
|
|
}
|
|
|
|
|
|
-static void remove_controlq_data(struct ports_device *portdev)
|
|
|
+static void virtcons_remove(struct virtio_device *vdev)
|
|
|
{
|
|
|
- struct port_buffer *buf;
|
|
|
- unsigned int len;
|
|
|
+ struct ports_device *portdev;
|
|
|
+ struct port *port, *port2;
|
|
|
|
|
|
- if (!use_multiport(portdev))
|
|
|
- return;
|
|
|
+ portdev = vdev->priv;
|
|
|
|
|
|
- while ((buf = virtqueue_get_buf(portdev->c_ivq, &len)))
|
|
|
- free_buf(buf, true);
|
|
|
+ spin_lock_irq(&pdrvdata_lock);
|
|
|
+ list_del(&portdev->list);
|
|
|
+ spin_unlock_irq(&pdrvdata_lock);
|
|
|
|
|
|
- while ((buf = virtqueue_detach_unused_buf(portdev->c_ivq)))
|
|
|
- free_buf(buf, true);
|
|
|
+ /* Disable interrupts for vqs */
|
|
|
+ vdev->config->reset(vdev);
|
|
|
+ /* Finish up work that's lined up */
|
|
|
+ if (use_multiport(portdev))
|
|
|
+ cancel_work_sync(&portdev->control_work);
|
|
|
+ else
|
|
|
+ cancel_work_sync(&portdev->config_work);
|
|
|
+
|
|
|
+ list_for_each_entry_safe(port, port2, &portdev->ports, list)
|
|
|
+ unplug_port(port);
|
|
|
+
|
|
|
+ unregister_chrdev(portdev->chr_major, "virtio-portsdev");
|
|
|
+
|
|
|
+ /*
|
|
|
+ * When yanking out a device, we immediately lose the
|
|
|
+ * (device-side) queues. So there's no point in keeping the
|
|
|
+ * guest side around till we drop our final reference. This
|
|
|
+ * also means that any ports which are in an open state will
|
|
|
+ * have to just stop using the port, as the vqs are going
|
|
|
+ * away.
|
|
|
+ */
|
|
|
+ remove_vqs(portdev);
|
|
|
+ kfree(portdev);
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -2070,6 +2090,7 @@ static int virtcons_probe(struct virtio_device *vdev)
|
|
|
|
|
|
spin_lock_init(&portdev->ports_lock);
|
|
|
INIT_LIST_HEAD(&portdev->ports);
|
|
|
+ INIT_LIST_HEAD(&portdev->list);
|
|
|
|
|
|
virtio_device_ready(portdev->vdev);
|
|
|
|
|
@@ -2087,8 +2108,15 @@ static int virtcons_probe(struct virtio_device *vdev)
|
|
|
if (!nr_added_bufs) {
|
|
|
dev_err(&vdev->dev,
|
|
|
"Error allocating buffers for control queue\n");
|
|
|
- err = -ENOMEM;
|
|
|
- goto free_vqs;
|
|
|
+ /*
|
|
|
+ * The host might want to notify mgmt sw about device
|
|
|
+ * add failure.
|
|
|
+ */
|
|
|
+ __send_control_msg(portdev, VIRTIO_CONSOLE_BAD_ID,
|
|
|
+ VIRTIO_CONSOLE_DEVICE_READY, 0);
|
|
|
+ /* Device was functional: we need full cleanup. */
|
|
|
+ virtcons_remove(vdev);
|
|
|
+ return -ENOMEM;
|
|
|
}
|
|
|
} else {
|
|
|
/*
|
|
@@ -2119,11 +2147,6 @@ static int virtcons_probe(struct virtio_device *vdev)
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
-free_vqs:
|
|
|
- /* The host might want to notify mgmt sw about device add failure */
|
|
|
- __send_control_msg(portdev, VIRTIO_CONSOLE_BAD_ID,
|
|
|
- VIRTIO_CONSOLE_DEVICE_READY, 0);
|
|
|
- remove_vqs(portdev);
|
|
|
free_chrdev:
|
|
|
unregister_chrdev(portdev->chr_major, "virtio-portsdev");
|
|
|
free:
|
|
@@ -2132,43 +2155,6 @@ fail:
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
-static void virtcons_remove(struct virtio_device *vdev)
|
|
|
-{
|
|
|
- struct ports_device *portdev;
|
|
|
- struct port *port, *port2;
|
|
|
-
|
|
|
- portdev = vdev->priv;
|
|
|
-
|
|
|
- spin_lock_irq(&pdrvdata_lock);
|
|
|
- list_del(&portdev->list);
|
|
|
- spin_unlock_irq(&pdrvdata_lock);
|
|
|
-
|
|
|
- /* Disable interrupts for vqs */
|
|
|
- vdev->config->reset(vdev);
|
|
|
- /* Finish up work that's lined up */
|
|
|
- if (use_multiport(portdev))
|
|
|
- cancel_work_sync(&portdev->control_work);
|
|
|
- else
|
|
|
- cancel_work_sync(&portdev->config_work);
|
|
|
-
|
|
|
- list_for_each_entry_safe(port, port2, &portdev->ports, list)
|
|
|
- unplug_port(port);
|
|
|
-
|
|
|
- unregister_chrdev(portdev->chr_major, "virtio-portsdev");
|
|
|
-
|
|
|
- /*
|
|
|
- * When yanking out a device, we immediately lose the
|
|
|
- * (device-side) queues. So there's no point in keeping the
|
|
|
- * guest side around till we drop our final reference. This
|
|
|
- * also means that any ports which are in an open state will
|
|
|
- * have to just stop using the port, as the vqs are going
|
|
|
- * away.
|
|
|
- */
|
|
|
- remove_controlq_data(portdev);
|
|
|
- remove_vqs(portdev);
|
|
|
- kfree(portdev);
|
|
|
-}
|
|
|
-
|
|
|
static struct virtio_device_id id_table[] = {
|
|
|
{ VIRTIO_ID_CONSOLE, VIRTIO_DEV_ANY_ID },
|
|
|
{ 0 },
|
|
@@ -2209,7 +2195,6 @@ static int virtcons_freeze(struct virtio_device *vdev)
|
|
|
*/
|
|
|
if (use_multiport(portdev))
|
|
|
virtqueue_disable_cb(portdev->c_ivq);
|
|
|
- remove_controlq_data(portdev);
|
|
|
|
|
|
list_for_each_entry(port, &portdev->ports, list) {
|
|
|
virtqueue_disable_cb(port->in_vq);
|