From: Sjur Brændeland <sjur.brandel...@stericsson.com>

Add a simple serial connection driver called
VIRTIO_ID_RPROC_SERIAL (11) for communicating with a
remote processor in an asymmetric multi-processing
configuration.

This implementation reuses the existing virtio_console
implementation, and adds support for DMA allocation
of data buffers and disables use of tty console and
the virtio control queue.

Signed-off-by: Sjur Brændeland <sjur.brandel...@stericsson.com>
cc: Rusty Russell <ru...@rustcorp.com.au>
cc: Michael S. Tsirkin <m...@redhat.com>
cc: Amit Shah <amit.s...@redhat.com>
cc: Ohad Ben-Cohen <o...@wizery.com>
cc: Linus Walleij <linus.wall...@linaro.org>
cc: Arnd Bergmann <a...@arndb.de>
---
Changes since v4:
- New baseline
- Use name is_rproc_enabled
- Renamed list and spin-lock used for pending deletion of dma buffers
- Minor style fixes: indentation, removed brace


 drivers/char/virtio_console.c |  184 +++++++++++++++++++++++++++++++++++++----
 include/linux/virtio_ids.h    |    1 +
 2 files changed, 170 insertions(+), 15 deletions(-)

diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
index f4f7b04..faedd2c 100644
--- a/drivers/char/virtio_console.c
+++ b/drivers/char/virtio_console.c
@@ -37,8 +37,12 @@
 #include <linux/wait.h>
 #include <linux/workqueue.h>
 #include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/kconfig.h>
 #include "../tty/hvc/hvc_console.h"
 
+#define is_rproc_enabled IS_ENABLED(CONFIG_REMOTEPROC)
+
 /*
  * This is a global struct for storing common data for all the devices
  * this driver handles.
@@ -112,6 +116,15 @@ struct port_buffer {
        /* offset in the buf from which to consume data */
        size_t offset;
 
+       /* DMA address of buffer */
+       dma_addr_t dma;
+
+       /* Device we got DMA memory from */
+       struct device *dev;
+
+       /* List of pending dma buffers to free */
+       struct list_head list;
+
        /* If sgpages == 0 then buf is used, else sg is used */
        unsigned int sgpages;
 
@@ -330,6 +343,11 @@ static bool is_console_port(struct port *port)
        return false;
 }
 
+static bool is_rproc_serial(const struct virtio_device *vdev)
+{
+       return is_rproc_enabled && vdev->id.device == VIRTIO_ID_RPROC_SERIAL;
+}
+
 static inline bool use_multiport(struct ports_device *portdev)
 {
        /*
@@ -341,32 +359,84 @@ static inline bool use_multiport(struct ports_device 
*portdev)
        return portdev->vdev->features[0] & (1 << VIRTIO_CONSOLE_F_MULTIPORT);
 }
 
+static DEFINE_SPINLOCK(dma_bufs_lock);
+static LIST_HEAD(pending_free_dma_bufs);
+
 static void free_buf(struct port_buffer *buf)
 {
        int i;
+       unsigned long flags;
 
-       kfree(buf->buf);
+       if (!buf->dev)
+               kfree(buf->buf);
 
-       if (buf->sgpages)
+       if (buf->sgpages) {
                for (i = 0; i < buf->sgpages; i++) {
                        struct page *page = sg_page(&buf->sg[i]);
                        if (!page)
                                break;
                        put_page(page);
                }
+               return;
+       }
+
+       if (buf->dev && is_rproc_enabled) {
+
+               /* dma_free_coherent requires interrupts to be enabled. */
+               if (irqs_disabled()) {
+                       /* queue up dma-buffers to be freed later */
+                       spin_lock_irqsave(&dma_bufs_lock, flags);
+                       list_add_tail(&buf->list, &pending_free_dma_bufs);
+                       spin_unlock_irqrestore(&dma_bufs_lock, flags);
+                       return;
+               }
+               dma_free_coherent(buf->dev, buf->size, buf->buf, buf->dma);
+
+               /* Release device refcnt and allow it to be freed */
+               might_sleep();
+               put_device(buf->dev);
+       }
 
        kfree(buf);
 }
 
+static void reclaim_dma_bufs(void)
+{
+       unsigned long flags;
+       struct port_buffer *buf, *tmp;
+       LIST_HEAD(tmp_list);
+
+       WARN_ON(irqs_disabled());
+       if (list_empty(&pending_free_dma_bufs))
+               return;
+
+       BUG_ON(!is_rproc_enabled);
+
+       /* Create a copy of the pending_free_dma_bufs while holding the lock*/
+       spin_lock_irqsave(&dma_bufs_lock, flags);
+       list_cut_position(&tmp_list, &pending_free_dma_bufs,
+                         pending_free_dma_bufs.prev);
+       spin_unlock_irqrestore(&dma_bufs_lock, flags);
+
+       /* Release the dma buffers, without irqs enabled */
+       list_for_each_entry_safe(buf, tmp, &tmp_list, list) {
+               list_del(&buf->list);
+               free_buf(buf);
+       }
+}
+
 static struct port_buffer *alloc_buf(struct virtqueue *vq, size_t buf_size,
                                     int nrbufs)
 {
        struct port_buffer *buf;
        size_t alloc_size;
 
+       if (is_rproc_serial(vq->vdev) && !irqs_disabled())
+               reclaim_dma_bufs();
+
        /* Allocate buffer and the scatter list */
        alloc_size = sizeof(*buf) + sizeof(struct scatterlist) * nrbufs;
-       buf = kmalloc(alloc_size, GFP_ATOMIC);
+       buf = kzalloc(alloc_size, GFP_ATOMIC);
        if (!buf)
                goto fail;
 
@@ -374,11 +444,30 @@ static struct port_buffer *alloc_buf(struct virtqueue 
*vq, size_t buf_size,
        if (nrbufs > 0)
                return buf;
 
-       buf->buf = kmalloc(buf_size, GFP_ATOMIC);
+       if (is_rproc_serial(vq->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.
+                * The code here would have been less quirky if
+                * DMA_MEMORY_INCLUDES_CHILDREN had been supported
+                * in dma-coherent.c
+                */
+               if (!vq->vdev->dev.parent || !vq->vdev->dev.parent->parent)
+                       goto free_buf;
+               buf->dev = vq->vdev->dev.parent->parent;
+
+               /* Increase device refcnt to avoid freeing it*/
+               get_device(buf->dev);
+               buf->buf = dma_alloc_coherent(buf->dev, buf_size, &buf->dma,
+                                             GFP_ATOMIC);
+       } else {
+               buf->buf = kmalloc(buf_size, GFP_ATOMIC);
+       }
+
        if (!buf->buf)
                goto free_buf;
-       buf->len = 0;
-       buf->offset = 0;
        buf->size = buf_size;
 
        /* Prepare scatter buffer for sending */
@@ -838,6 +927,10 @@ static ssize_t port_fops_splice_write(struct 
pipe_inode_info *pipe,
                .u.data = &sgl,
        };
 
+       /* rproc_serial does not support splice */
+       if (is_rproc_serial(port->out_vq->vdev))
+               return -EINVAL;
+
        ret = wait_port_writable(port, filp->f_flags & O_NONBLOCK);
        if (ret < 0)
                return ret;
@@ -902,6 +995,8 @@ static int port_fops_release(struct inode *inode, struct 
file *filp)
        reclaim_consumed_buffers(port);
        spin_unlock_irq(&port->outvq_lock);
 
+       if (is_rproc_serial(port->portdev->vdev) && !irqs_disabled())
+               reclaim_dma_bufs();
        /*
         * Locks aren't necessary here as a port can't be opened after
         * unplug, and if a port isn't unplugged, a kref would already
@@ -1058,7 +1153,10 @@ static void resize_console(struct port *port)
                return;
 
        vdev = port->portdev->vdev;
-       if (virtio_has_feature(vdev, VIRTIO_CONSOLE_F_SIZE))
+
+       /* Don't test F_SIZE at all if we're rproc: not a valid feature! */
+       if (!is_rproc_serial(vdev) &&
+           virtio_has_feature(vdev, VIRTIO_CONSOLE_F_SIZE))
                hvc_resize(port->cons.hvc, port->cons.ws);
 }
 
@@ -1339,10 +1437,18 @@ static int add_port(struct ports_device *portdev, u32 
id)
                goto free_device;
        }
 
-       /*
-        * If we're not using multiport support, this has to be a console port
-        */
-       if (!use_multiport(port->portdev)) {
+       if (is_rproc_serial(port->portdev->vdev))
+               /*
+                * For rproc_serial assume remote processor is connected.
+                * rproc_serial does not want the console port, only
+                * the generic port implementation.
+                */
+               port->host_connected = true;
+       else if (!use_multiport(port->portdev)) {
+               /*
+                * If we're not using multiport support,
+                * this has to be a console port.
+                */
                err = init_port_console(port);
                if (err)
                        goto free_inbufs;
@@ -1418,6 +1524,15 @@ static void remove_port_data(struct port *port)
        /* Remove buffers we queued up for the Host to send us data in. */
        while ((buf = virtqueue_detach_unused_buf(port->in_vq)))
                free_buf(buf);
+
+       /*
+        * Remove buffers from out queue for rproc-serial. We cannot afford
+        * to leak any DMA mem, so reclaim this memory even if this might be
+        * racy for the remote processor.
+        */
+       if (is_rproc_serial(port->portdev->vdev))
+               while ((buf = virtqueue_detach_unused_buf(port->out_vq)))
+                       free_buf(buf);
 }
 
 /*
@@ -1865,11 +1980,15 @@ static int __devinit virtcons_probe(struct 
virtio_device *vdev)
 
        multiport = false;
        portdev->config.max_nr_ports = 1;
-       if (virtio_config_val(vdev, VIRTIO_CONSOLE_F_MULTIPORT,
-                             offsetof(struct virtio_console_config,
-                                      max_nr_ports),
-                             &portdev->config.max_nr_ports) == 0)
+
+       /* Don't test MULTIPORT at all if we're rproc: not a valid feature! */
+       if (!is_rproc_serial(vdev) &&
+           virtio_config_val(vdev, VIRTIO_CONSOLE_F_MULTIPORT,
+                                 offsetof(struct virtio_console_config,
+                                          max_nr_ports),
+                                 &portdev->config.max_nr_ports) == 0) {
                multiport = true;
+       }
 
        err = init_vqs(portdev);
        if (err < 0) {
@@ -1979,6 +2098,16 @@ static unsigned int features[] = {
        VIRTIO_CONSOLE_F_MULTIPORT,
 };
 
+static struct virtio_device_id rproc_serial_id_table[] = {
+#if IS_ENABLED(CONFIG_REMOTEPROC)
+       { VIRTIO_ID_RPROC_SERIAL, VIRTIO_DEV_ANY_ID },
+#endif
+       { 0 },
+};
+
+static unsigned int rproc_serial_features[] = {
+};
+
 #ifdef CONFIG_PM
 static int virtcons_freeze(struct virtio_device *vdev)
 {
@@ -2063,6 +2192,20 @@ static struct virtio_driver virtio_console = {
 #endif
 };
 
+/*
+ * virtio_rproc_serial refers to __devinit function which causes
+ * section mismatch warnings. So use __refdata to silence warnings.
+ */
+static struct virtio_driver __refdata virtio_rproc_serial = {
+       .feature_table = rproc_serial_features,
+       .feature_table_size = ARRAY_SIZE(rproc_serial_features),
+       .driver.name =  "virtio_rproc_serial",
+       .driver.owner = THIS_MODULE,
+       .id_table =     rproc_serial_id_table,
+       .probe =        virtcons_probe,
+       .remove =       virtcons_remove,
+};
+
 static int __init init(void)
 {
        int err;
@@ -2087,7 +2230,15 @@ static int __init init(void)
                pr_err("Error %d registering virtio driver\n", err);
                goto free;
        }
+       err = register_virtio_driver(&virtio_rproc_serial);
+       if (err < 0) {
+               pr_err("Error %d registering virtio rproc serial driver\n",
+                      err);
+               goto unregister;
+       }
        return 0;
+unregister:
+       unregister_virtio_driver(&virtio_console);
 free:
        if (pdrvdata.debugfs_dir)
                debugfs_remove_recursive(pdrvdata.debugfs_dir);
@@ -2097,7 +2248,10 @@ free:
 
 static void __exit fini(void)
 {
+       reclaim_dma_bufs();
+
        unregister_virtio_driver(&virtio_console);
+       unregister_virtio_driver(&virtio_rproc_serial);
 
        class_destroy(pdrvdata.class);
        if (pdrvdata.debugfs_dir)
diff --git a/include/linux/virtio_ids.h b/include/linux/virtio_ids.h
index 270fb22..cb28b52 100644
--- a/include/linux/virtio_ids.h
+++ b/include/linux/virtio_ids.h
@@ -37,5 +37,6 @@
 #define VIRTIO_ID_RPMSG                7 /* virtio remote processor messaging 
*/
 #define VIRTIO_ID_SCSI         8 /* virtio scsi */
 #define VIRTIO_ID_9P           9 /* 9p virtio console */
+#define VIRTIO_ID_RPROC_SERIAL 11 /* virtio remoteproc serial link */
 
 #endif /* _LINUX_VIRTIO_IDS_H */
-- 
1.7.5.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to