|
@@ -37,8 +37,12 @@
|
|
#include <linux/wait.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/module.h>
|
|
#include <linux/module.h>
|
|
|
|
+#include <linux/dma-mapping.h>
|
|
|
|
+#include <linux/kconfig.h>
|
|
#include "../tty/hvc/hvc_console.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 is a global struct for storing common data for all the devices
|
|
* this driver handles.
|
|
* this driver handles.
|
|
@@ -111,6 +115,21 @@ struct port_buffer {
|
|
size_t len;
|
|
size_t len;
|
|
/* offset in the buf from which to consume data */
|
|
/* offset in the buf from which to consume data */
|
|
size_t offset;
|
|
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 */
|
|
|
|
+ unsigned int sgpages;
|
|
|
|
+
|
|
|
|
+ /* sg is used if spages > 0. sg must be the last in is struct */
|
|
|
|
+ struct scatterlist sg[0];
|
|
};
|
|
};
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -325,6 +344,11 @@ static bool is_console_port(struct port *port)
|
|
return false;
|
|
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)
|
|
static inline bool use_multiport(struct ports_device *portdev)
|
|
{
|
|
{
|
|
/*
|
|
/*
|
|
@@ -336,20 +360,110 @@ static inline bool use_multiport(struct ports_device *portdev)
|
|
return portdev->vdev->features[0] & (1 << VIRTIO_CONSOLE_F_MULTIPORT);
|
|
return portdev->vdev->features[0] & (1 << VIRTIO_CONSOLE_F_MULTIPORT);
|
|
}
|
|
}
|
|
|
|
|
|
-static void free_buf(struct port_buffer *buf)
|
|
|
|
|
|
+static DEFINE_SPINLOCK(dma_bufs_lock);
|
|
|
|
+static LIST_HEAD(pending_free_dma_bufs);
|
|
|
|
+
|
|
|
|
+static void free_buf(struct port_buffer *buf, bool can_sleep)
|
|
{
|
|
{
|
|
- kfree(buf->buf);
|
|
|
|
|
|
+ unsigned int i;
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < buf->sgpages; i++) {
|
|
|
|
+ struct page *page = sg_page(&buf->sg[i]);
|
|
|
|
+ if (!page)
|
|
|
|
+ break;
|
|
|
|
+ put_page(page);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!buf->dev) {
|
|
|
|
+ kfree(buf->buf);
|
|
|
|
+ } else if (is_rproc_enabled) {
|
|
|
|
+ unsigned long flags;
|
|
|
|
+
|
|
|
|
+ /* dma_free_coherent requires interrupts to be enabled. */
|
|
|
|
+ if (!can_sleep) {
|
|
|
|
+ /* 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 */
|
|
|
|
+ put_device(buf->dev);
|
|
|
|
+ }
|
|
|
|
+
|
|
kfree(buf);
|
|
kfree(buf);
|
|
}
|
|
}
|
|
|
|
|
|
-static struct port_buffer *alloc_buf(size_t buf_size)
|
|
|
|
|
|
+static void reclaim_dma_bufs(void)
|
|
|
|
+{
|
|
|
|
+ unsigned long flags;
|
|
|
|
+ struct port_buffer *buf, *tmp;
|
|
|
|
+ LIST_HEAD(tmp_list);
|
|
|
|
+
|
|
|
|
+ if (list_empty(&pending_free_dma_bufs))
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ /* 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, true);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static struct port_buffer *alloc_buf(struct virtqueue *vq, size_t buf_size,
|
|
|
|
+ int pages)
|
|
{
|
|
{
|
|
struct port_buffer *buf;
|
|
struct port_buffer *buf;
|
|
|
|
|
|
- buf = kmalloc(sizeof(*buf), GFP_KERNEL);
|
|
|
|
|
|
+ reclaim_dma_bufs();
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Allocate buffer and the sg list. The sg list array is allocated
|
|
|
|
+ * directly after the port_buffer struct.
|
|
|
|
+ */
|
|
|
|
+ buf = kmalloc(sizeof(*buf) + sizeof(struct scatterlist) * pages,
|
|
|
|
+ GFP_KERNEL);
|
|
if (!buf)
|
|
if (!buf)
|
|
goto fail;
|
|
goto fail;
|
|
- buf->buf = kzalloc(buf_size, GFP_KERNEL);
|
|
|
|
|
|
+
|
|
|
|
+ buf->sgpages = pages;
|
|
|
|
+ if (pages > 0) {
|
|
|
|
+ buf->dev = NULL;
|
|
|
|
+ buf->buf = NULL;
|
|
|
|
+ return buf;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 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_KERNEL);
|
|
|
|
+ } else {
|
|
|
|
+ buf->dev = NULL;
|
|
|
|
+ buf->buf = kmalloc(buf_size, GFP_KERNEL);
|
|
|
|
+ }
|
|
|
|
+
|
|
if (!buf->buf)
|
|
if (!buf->buf)
|
|
goto free_buf;
|
|
goto free_buf;
|
|
buf->len = 0;
|
|
buf->len = 0;
|
|
@@ -396,6 +510,8 @@ static int add_inbuf(struct virtqueue *vq, struct port_buffer *buf)
|
|
|
|
|
|
ret = virtqueue_add_buf(vq, sg, 0, 1, buf, GFP_ATOMIC);
|
|
ret = virtqueue_add_buf(vq, sg, 0, 1, buf, GFP_ATOMIC);
|
|
virtqueue_kick(vq);
|
|
virtqueue_kick(vq);
|
|
|
|
+ if (!ret)
|
|
|
|
+ ret = vq->num_free;
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -416,7 +532,7 @@ static void discard_port_data(struct port *port)
|
|
port->stats.bytes_discarded += buf->len - buf->offset;
|
|
port->stats.bytes_discarded += buf->len - buf->offset;
|
|
if (add_inbuf(port->in_vq, buf) < 0) {
|
|
if (add_inbuf(port->in_vq, buf) < 0) {
|
|
err++;
|
|
err++;
|
|
- free_buf(buf);
|
|
|
|
|
|
+ free_buf(buf, false);
|
|
}
|
|
}
|
|
port->inbuf = NULL;
|
|
port->inbuf = NULL;
|
|
buf = get_inbuf(port);
|
|
buf = get_inbuf(port);
|
|
@@ -459,7 +575,7 @@ static ssize_t __send_control_msg(struct ports_device *portdev, u32 port_id,
|
|
vq = portdev->c_ovq;
|
|
vq = portdev->c_ovq;
|
|
|
|
|
|
sg_init_one(sg, &cpkt, sizeof(cpkt));
|
|
sg_init_one(sg, &cpkt, sizeof(cpkt));
|
|
- if (virtqueue_add_buf(vq, sg, 1, 0, &cpkt, GFP_ATOMIC) >= 0) {
|
|
|
|
|
|
+ if (virtqueue_add_buf(vq, sg, 1, 0, &cpkt, GFP_ATOMIC) == 0) {
|
|
virtqueue_kick(vq);
|
|
virtqueue_kick(vq);
|
|
while (!virtqueue_get_buf(vq, &len))
|
|
while (!virtqueue_get_buf(vq, &len))
|
|
cpu_relax();
|
|
cpu_relax();
|
|
@@ -476,55 +592,29 @@ static ssize_t send_control_msg(struct port *port, unsigned int event,
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
-struct buffer_token {
|
|
|
|
- union {
|
|
|
|
- void *buf;
|
|
|
|
- struct scatterlist *sg;
|
|
|
|
- } u;
|
|
|
|
- /* If sgpages == 0 then buf is used, else sg is used */
|
|
|
|
- unsigned int sgpages;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-static void reclaim_sg_pages(struct scatterlist *sg, unsigned int nrpages)
|
|
|
|
-{
|
|
|
|
- int i;
|
|
|
|
- struct page *page;
|
|
|
|
-
|
|
|
|
- for (i = 0; i < nrpages; i++) {
|
|
|
|
- page = sg_page(&sg[i]);
|
|
|
|
- if (!page)
|
|
|
|
- break;
|
|
|
|
- put_page(page);
|
|
|
|
- }
|
|
|
|
- kfree(sg);
|
|
|
|
-}
|
|
|
|
|
|
|
|
/* Callers must take the port->outvq_lock */
|
|
/* Callers must take the port->outvq_lock */
|
|
static void reclaim_consumed_buffers(struct port *port)
|
|
static void reclaim_consumed_buffers(struct port *port)
|
|
{
|
|
{
|
|
- struct buffer_token *tok;
|
|
|
|
|
|
+ struct port_buffer *buf;
|
|
unsigned int len;
|
|
unsigned int len;
|
|
|
|
|
|
if (!port->portdev) {
|
|
if (!port->portdev) {
|
|
/* Device has been unplugged. vqs are already gone. */
|
|
/* Device has been unplugged. vqs are already gone. */
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
- while ((tok = virtqueue_get_buf(port->out_vq, &len))) {
|
|
|
|
- if (tok->sgpages)
|
|
|
|
- reclaim_sg_pages(tok->u.sg, tok->sgpages);
|
|
|
|
- else
|
|
|
|
- kfree(tok->u.buf);
|
|
|
|
- kfree(tok);
|
|
|
|
|
|
+ while ((buf = virtqueue_get_buf(port->out_vq, &len))) {
|
|
|
|
+ free_buf(buf, false);
|
|
port->outvq_full = false;
|
|
port->outvq_full = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static ssize_t __send_to_port(struct port *port, struct scatterlist *sg,
|
|
static ssize_t __send_to_port(struct port *port, struct scatterlist *sg,
|
|
int nents, size_t in_count,
|
|
int nents, size_t in_count,
|
|
- struct buffer_token *tok, bool nonblock)
|
|
|
|
|
|
+ void *data, bool nonblock)
|
|
{
|
|
{
|
|
struct virtqueue *out_vq;
|
|
struct virtqueue *out_vq;
|
|
- ssize_t ret;
|
|
|
|
|
|
+ int err;
|
|
unsigned long flags;
|
|
unsigned long flags;
|
|
unsigned int len;
|
|
unsigned int len;
|
|
|
|
|
|
@@ -534,17 +624,17 @@ static ssize_t __send_to_port(struct port *port, struct scatterlist *sg,
|
|
|
|
|
|
reclaim_consumed_buffers(port);
|
|
reclaim_consumed_buffers(port);
|
|
|
|
|
|
- ret = virtqueue_add_buf(out_vq, sg, nents, 0, tok, GFP_ATOMIC);
|
|
|
|
|
|
+ err = virtqueue_add_buf(out_vq, sg, nents, 0, data, GFP_ATOMIC);
|
|
|
|
|
|
/* Tell Host to go! */
|
|
/* Tell Host to go! */
|
|
virtqueue_kick(out_vq);
|
|
virtqueue_kick(out_vq);
|
|
|
|
|
|
- if (ret < 0) {
|
|
|
|
|
|
+ if (err) {
|
|
in_count = 0;
|
|
in_count = 0;
|
|
goto done;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
|
|
- if (ret == 0)
|
|
|
|
|
|
+ if (out_vq->num_free == 0)
|
|
port->outvq_full = true;
|
|
port->outvq_full = true;
|
|
|
|
|
|
if (nonblock)
|
|
if (nonblock)
|
|
@@ -572,37 +662,6 @@ done:
|
|
return in_count;
|
|
return in_count;
|
|
}
|
|
}
|
|
|
|
|
|
-static ssize_t send_buf(struct port *port, void *in_buf, size_t in_count,
|
|
|
|
- bool nonblock)
|
|
|
|
-{
|
|
|
|
- struct scatterlist sg[1];
|
|
|
|
- struct buffer_token *tok;
|
|
|
|
-
|
|
|
|
- tok = kmalloc(sizeof(*tok), GFP_ATOMIC);
|
|
|
|
- if (!tok)
|
|
|
|
- return -ENOMEM;
|
|
|
|
- tok->sgpages = 0;
|
|
|
|
- tok->u.buf = in_buf;
|
|
|
|
-
|
|
|
|
- sg_init_one(sg, in_buf, in_count);
|
|
|
|
-
|
|
|
|
- return __send_to_port(port, sg, 1, in_count, tok, nonblock);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-static ssize_t send_pages(struct port *port, struct scatterlist *sg, int nents,
|
|
|
|
- size_t in_count, bool nonblock)
|
|
|
|
-{
|
|
|
|
- struct buffer_token *tok;
|
|
|
|
-
|
|
|
|
- tok = kmalloc(sizeof(*tok), GFP_ATOMIC);
|
|
|
|
- if (!tok)
|
|
|
|
- return -ENOMEM;
|
|
|
|
- tok->sgpages = nents;
|
|
|
|
- tok->u.sg = sg;
|
|
|
|
-
|
|
|
|
- return __send_to_port(port, sg, nents, in_count, tok, nonblock);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
/*
|
|
/*
|
|
* Give out the data that's requested from the buffer that we have
|
|
* Give out the data that's requested from the buffer that we have
|
|
* queued up.
|
|
* queued up.
|
|
@@ -748,9 +807,10 @@ static ssize_t port_fops_write(struct file *filp, const char __user *ubuf,
|
|
size_t count, loff_t *offp)
|
|
size_t count, loff_t *offp)
|
|
{
|
|
{
|
|
struct port *port;
|
|
struct port *port;
|
|
- char *buf;
|
|
|
|
|
|
+ struct port_buffer *buf;
|
|
ssize_t ret;
|
|
ssize_t ret;
|
|
bool nonblock;
|
|
bool nonblock;
|
|
|
|
+ struct scatterlist sg[1];
|
|
|
|
|
|
/* Userspace could be out to fool us */
|
|
/* Userspace could be out to fool us */
|
|
if (!count)
|
|
if (!count)
|
|
@@ -766,11 +826,11 @@ static ssize_t port_fops_write(struct file *filp, const char __user *ubuf,
|
|
|
|
|
|
count = min((size_t)(32 * 1024), count);
|
|
count = min((size_t)(32 * 1024), count);
|
|
|
|
|
|
- buf = kmalloc(count, GFP_KERNEL);
|
|
|
|
|
|
+ buf = alloc_buf(port->out_vq, count, 0);
|
|
if (!buf)
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
return -ENOMEM;
|
|
|
|
|
|
- ret = copy_from_user(buf, ubuf, count);
|
|
|
|
|
|
+ ret = copy_from_user(buf->buf, ubuf, count);
|
|
if (ret) {
|
|
if (ret) {
|
|
ret = -EFAULT;
|
|
ret = -EFAULT;
|
|
goto free_buf;
|
|
goto free_buf;
|
|
@@ -784,13 +844,14 @@ static ssize_t port_fops_write(struct file *filp, const char __user *ubuf,
|
|
* through to the host.
|
|
* through to the host.
|
|
*/
|
|
*/
|
|
nonblock = true;
|
|
nonblock = true;
|
|
- ret = send_buf(port, buf, count, nonblock);
|
|
|
|
|
|
+ sg_init_one(sg, buf->buf, count);
|
|
|
|
+ ret = __send_to_port(port, sg, 1, count, buf, nonblock);
|
|
|
|
|
|
if (nonblock && ret > 0)
|
|
if (nonblock && ret > 0)
|
|
goto out;
|
|
goto out;
|
|
|
|
|
|
free_buf:
|
|
free_buf:
|
|
- kfree(buf);
|
|
|
|
|
|
+ free_buf(buf, true);
|
|
out:
|
|
out:
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
@@ -856,6 +917,7 @@ static ssize_t port_fops_splice_write(struct pipe_inode_info *pipe,
|
|
struct port *port = filp->private_data;
|
|
struct port *port = filp->private_data;
|
|
struct sg_list sgl;
|
|
struct sg_list sgl;
|
|
ssize_t ret;
|
|
ssize_t ret;
|
|
|
|
+ struct port_buffer *buf;
|
|
struct splice_desc sd = {
|
|
struct splice_desc sd = {
|
|
.total_len = len,
|
|
.total_len = len,
|
|
.flags = flags,
|
|
.flags = flags,
|
|
@@ -863,22 +925,34 @@ static ssize_t port_fops_splice_write(struct pipe_inode_info *pipe,
|
|
.u.data = &sgl,
|
|
.u.data = &sgl,
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+ /*
|
|
|
|
+ * Rproc_serial does not yet support splice. To support splice
|
|
|
|
+ * pipe_to_sg() must allocate dma-buffers and copy content from
|
|
|
|
+ * regular pages to dma pages. And alloc_buf and free_buf must
|
|
|
|
+ * support allocating and freeing such a list of dma-buffers.
|
|
|
|
+ */
|
|
|
|
+ if (is_rproc_serial(port->out_vq->vdev))
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
ret = wait_port_writable(port, filp->f_flags & O_NONBLOCK);
|
|
ret = wait_port_writable(port, filp->f_flags & O_NONBLOCK);
|
|
if (ret < 0)
|
|
if (ret < 0)
|
|
return ret;
|
|
return ret;
|
|
|
|
|
|
|
|
+ buf = alloc_buf(port->out_vq, 0, pipe->nrbufs);
|
|
|
|
+ if (!buf)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+
|
|
sgl.n = 0;
|
|
sgl.n = 0;
|
|
sgl.len = 0;
|
|
sgl.len = 0;
|
|
sgl.size = pipe->nrbufs;
|
|
sgl.size = pipe->nrbufs;
|
|
- sgl.sg = kmalloc(sizeof(struct scatterlist) * sgl.size, GFP_KERNEL);
|
|
|
|
- if (unlikely(!sgl.sg))
|
|
|
|
- return -ENOMEM;
|
|
|
|
-
|
|
|
|
|
|
+ sgl.sg = buf->sg;
|
|
sg_init_table(sgl.sg, sgl.size);
|
|
sg_init_table(sgl.sg, sgl.size);
|
|
ret = __splice_from_pipe(pipe, &sd, pipe_to_sg);
|
|
ret = __splice_from_pipe(pipe, &sd, pipe_to_sg);
|
|
if (likely(ret > 0))
|
|
if (likely(ret > 0))
|
|
- ret = send_pages(port, sgl.sg, sgl.n, sgl.len, true);
|
|
|
|
|
|
+ ret = __send_to_port(port, buf->sg, sgl.n, sgl.len, buf, true);
|
|
|
|
|
|
|
|
+ if (unlikely(ret <= 0))
|
|
|
|
+ free_buf(buf, true);
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -927,6 +1001,7 @@ static int port_fops_release(struct inode *inode, struct file *filp)
|
|
reclaim_consumed_buffers(port);
|
|
reclaim_consumed_buffers(port);
|
|
spin_unlock_irq(&port->outvq_lock);
|
|
spin_unlock_irq(&port->outvq_lock);
|
|
|
|
|
|
|
|
+ reclaim_dma_bufs();
|
|
/*
|
|
/*
|
|
* Locks aren't necessary here as a port can't be opened after
|
|
* 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
|
|
* unplug, and if a port isn't unplugged, a kref would already
|
|
@@ -1031,6 +1106,7 @@ static const struct file_operations port_fops = {
|
|
static int put_chars(u32 vtermno, const char *buf, int count)
|
|
static int put_chars(u32 vtermno, const char *buf, int count)
|
|
{
|
|
{
|
|
struct port *port;
|
|
struct port *port;
|
|
|
|
+ struct scatterlist sg[1];
|
|
|
|
|
|
if (unlikely(early_put_chars))
|
|
if (unlikely(early_put_chars))
|
|
return early_put_chars(vtermno, buf, count);
|
|
return early_put_chars(vtermno, buf, count);
|
|
@@ -1039,7 +1115,8 @@ static int put_chars(u32 vtermno, const char *buf, int count)
|
|
if (!port)
|
|
if (!port)
|
|
return -EPIPE;
|
|
return -EPIPE;
|
|
|
|
|
|
- return send_buf(port, (void *)buf, count, false);
|
|
|
|
|
|
+ sg_init_one(sg, buf, count);
|
|
|
|
+ return __send_to_port(port, sg, 1, count, (void *)buf, false);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -1076,7 +1153,10 @@ static void resize_console(struct port *port)
|
|
return;
|
|
return;
|
|
|
|
|
|
vdev = port->portdev->vdev;
|
|
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);
|
|
hvc_resize(port->cons.hvc, port->cons.ws);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1260,7 +1340,7 @@ static unsigned int fill_queue(struct virtqueue *vq, spinlock_t *lock)
|
|
|
|
|
|
nr_added_bufs = 0;
|
|
nr_added_bufs = 0;
|
|
do {
|
|
do {
|
|
- buf = alloc_buf(PAGE_SIZE);
|
|
|
|
|
|
+ buf = alloc_buf(vq, PAGE_SIZE, 0);
|
|
if (!buf)
|
|
if (!buf)
|
|
break;
|
|
break;
|
|
|
|
|
|
@@ -1268,7 +1348,7 @@ static unsigned int fill_queue(struct virtqueue *vq, spinlock_t *lock)
|
|
ret = add_inbuf(vq, buf);
|
|
ret = add_inbuf(vq, buf);
|
|
if (ret < 0) {
|
|
if (ret < 0) {
|
|
spin_unlock_irq(lock);
|
|
spin_unlock_irq(lock);
|
|
- free_buf(buf);
|
|
|
|
|
|
+ free_buf(buf, true);
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
nr_added_bufs++;
|
|
nr_added_bufs++;
|
|
@@ -1356,10 +1436,18 @@ static int add_port(struct ports_device *portdev, u32 id)
|
|
goto free_device;
|
|
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);
|
|
err = init_port_console(port);
|
|
if (err)
|
|
if (err)
|
|
goto free_inbufs;
|
|
goto free_inbufs;
|
|
@@ -1392,7 +1480,7 @@ static int add_port(struct ports_device *portdev, u32 id)
|
|
|
|
|
|
free_inbufs:
|
|
free_inbufs:
|
|
while ((buf = virtqueue_detach_unused_buf(port->in_vq)))
|
|
while ((buf = virtqueue_detach_unused_buf(port->in_vq)))
|
|
- free_buf(buf);
|
|
|
|
|
|
+ free_buf(buf, true);
|
|
free_device:
|
|
free_device:
|
|
device_destroy(pdrvdata.class, port->dev->devt);
|
|
device_destroy(pdrvdata.class, port->dev->devt);
|
|
free_cdev:
|
|
free_cdev:
|
|
@@ -1434,7 +1522,11 @@ static void remove_port_data(struct port *port)
|
|
|
|
|
|
/* Remove buffers we queued up for the Host to send us data in. */
|
|
/* Remove buffers we queued up for the Host to send us data in. */
|
|
while ((buf = virtqueue_detach_unused_buf(port->in_vq)))
|
|
while ((buf = virtqueue_detach_unused_buf(port->in_vq)))
|
|
- free_buf(buf);
|
|
|
|
|
|
+ free_buf(buf, true);
|
|
|
|
+
|
|
|
|
+ /* Free pending buffers from the out-queue. */
|
|
|
|
+ while ((buf = virtqueue_detach_unused_buf(port->out_vq)))
|
|
|
|
+ free_buf(buf, true);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -1636,7 +1728,7 @@ static void control_work_handler(struct work_struct *work)
|
|
if (add_inbuf(portdev->c_ivq, buf) < 0) {
|
|
if (add_inbuf(portdev->c_ivq, buf) < 0) {
|
|
dev_warn(&portdev->vdev->dev,
|
|
dev_warn(&portdev->vdev->dev,
|
|
"Error adding buffer to queue\n");
|
|
"Error adding buffer to queue\n");
|
|
- free_buf(buf);
|
|
|
|
|
|
+ free_buf(buf, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
spin_unlock(&portdev->cvq_lock);
|
|
spin_unlock(&portdev->cvq_lock);
|
|
@@ -1832,10 +1924,10 @@ static void remove_controlq_data(struct ports_device *portdev)
|
|
return;
|
|
return;
|
|
|
|
|
|
while ((buf = virtqueue_get_buf(portdev->c_ivq, &len)))
|
|
while ((buf = virtqueue_get_buf(portdev->c_ivq, &len)))
|
|
- free_buf(buf);
|
|
|
|
|
|
+ free_buf(buf, true);
|
|
|
|
|
|
while ((buf = virtqueue_detach_unused_buf(portdev->c_ivq)))
|
|
while ((buf = virtqueue_detach_unused_buf(portdev->c_ivq)))
|
|
- free_buf(buf);
|
|
|
|
|
|
+ free_buf(buf, true);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -1882,11 +1974,15 @@ static int virtcons_probe(struct virtio_device *vdev)
|
|
|
|
|
|
multiport = false;
|
|
multiport = false;
|
|
portdev->config.max_nr_ports = 1;
|
|
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;
|
|
multiport = true;
|
|
|
|
+ }
|
|
|
|
|
|
err = init_vqs(portdev);
|
|
err = init_vqs(portdev);
|
|
if (err < 0) {
|
|
if (err < 0) {
|
|
@@ -1996,6 +2092,16 @@ static unsigned int features[] = {
|
|
VIRTIO_CONSOLE_F_MULTIPORT,
|
|
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
|
|
#ifdef CONFIG_PM
|
|
static int virtcons_freeze(struct virtio_device *vdev)
|
|
static int virtcons_freeze(struct virtio_device *vdev)
|
|
{
|
|
{
|
|
@@ -2080,6 +2186,20 @@ static struct virtio_driver virtio_console = {
|
|
#endif
|
|
#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)
|
|
static int __init init(void)
|
|
{
|
|
{
|
|
int err;
|
|
int err;
|
|
@@ -2104,7 +2224,15 @@ static int __init init(void)
|
|
pr_err("Error %d registering virtio driver\n", err);
|
|
pr_err("Error %d registering virtio driver\n", err);
|
|
goto free;
|
|
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;
|
|
return 0;
|
|
|
|
+unregister:
|
|
|
|
+ unregister_virtio_driver(&virtio_console);
|
|
free:
|
|
free:
|
|
if (pdrvdata.debugfs_dir)
|
|
if (pdrvdata.debugfs_dir)
|
|
debugfs_remove_recursive(pdrvdata.debugfs_dir);
|
|
debugfs_remove_recursive(pdrvdata.debugfs_dir);
|
|
@@ -2114,7 +2242,10 @@ free:
|
|
|
|
|
|
static void __exit fini(void)
|
|
static void __exit fini(void)
|
|
{
|
|
{
|
|
|
|
+ reclaim_dma_bufs();
|
|
|
|
+
|
|
unregister_virtio_driver(&virtio_console);
|
|
unregister_virtio_driver(&virtio_console);
|
|
|
|
+ unregister_virtio_driver(&virtio_rproc_serial);
|
|
|
|
|
|
class_destroy(pdrvdata.class);
|
|
class_destroy(pdrvdata.class);
|
|
if (pdrvdata.debugfs_dir)
|
|
if (pdrvdata.debugfs_dir)
|