summaryrefslogtreecommitdiff
path: root/drivers/char/virtio_console.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/virtio_console.c')
-rw-r--r--drivers/char/virtio_console.c559
1 files changed, 222 insertions, 337 deletions
diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
index ad843eb02ae7..088182e54deb 100644
--- a/drivers/char/virtio_console.c
+++ b/drivers/char/virtio_console.c
@@ -1,21 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2006, 2007, 2009 Rusty Russell, IBM Corporation
* Copyright (C) 2009, 2010, 2011 Red Hat, Inc.
* Copyright (C) 2009, 2010, 2011 Amit Shah <amit.shah@redhat.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/cdev.h>
#include <linux/debugfs.h>
@@ -26,6 +13,7 @@
#include <linux/fs.h>
#include <linux/splice.h>
#include <linux/pagemap.h>
+#include <linux/idr.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/poll.h>
@@ -38,9 +26,11 @@
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/dma-mapping.h>
+#include <linux/string_choices.h>
#include "../tty/hvc/hvc_console.h"
#define is_rproc_enabled IS_ENABLED(CONFIG_REMOTEPROC)
+#define VIRTCONS_MAX_PORTS 0x8000
/*
* This is a global struct for storing common data for all the devices
@@ -51,32 +41,22 @@
* across multiple devices and multiple ports per device.
*/
struct ports_driver_data {
- /* Used for registering chardevs */
- struct class *class;
-
/* Used for exporting per-port information to debugfs */
struct dentry *debugfs_dir;
/* List of all the devices we're handling */
struct list_head portdevs;
- /*
- * This is used to keep track of the number of hvc consoles
- * spawned by this driver. This number is given as the first
- * argument to hvc_alloc(). To correctly map an initial
- * console spawned via hvc_instantiate to the console being
- * hooked up via hvc_alloc, we need to pass the same vtermno.
- *
- * We also just assume the first console being initialised was
- * the first one that got used as the initial console.
- */
- unsigned int next_vtermno;
-
/* All the console devices handled by this driver */
struct list_head consoles;
};
+
static struct ports_driver_data pdrvdata;
+static const struct class port_class = {
+ .name = "virtio-ports",
+};
+
static DEFINE_SPINLOCK(pdrvdata_lock);
static DECLARE_COMPLETION(early_console_added);
@@ -101,6 +81,8 @@ struct console {
u32 vtermno;
};
+static DEFINE_IDA(vtermno_ida);
+
struct port_buffer {
char *buf;
@@ -125,7 +107,7 @@ struct port_buffer {
unsigned int sgpages;
/* sg is used if spages > 0. sg must be the last in is struct */
- struct scatterlist sg[0];
+ struct scatterlist sg[] __counted_by(sgpages);
};
/*
@@ -249,9 +231,6 @@ struct port {
bool guest_connected;
};
-/* This is the very early arch-specified put chars function. */
-static int (*early_put_chars)(u32, const char *, int);
-
static struct port *find_port_by_vtermno(u32 vtermno)
{
struct port *port;
@@ -422,7 +401,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;
@@ -433,8 +412,7 @@ static struct port_buffer *alloc_buf(struct virtqueue *vq, size_t buf_size,
* 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);
+ buf = kmalloc(struct_size(buf, sg, pages), GFP_KERNEL);
if (!buf)
goto fail;
@@ -445,19 +423,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.
- * The code here would have been less quirky if
- * DMA_MEMORY_INCLUDES_CHILDREN had been supported
- * in dma-coherent.c
+ * associated with the parent device:
+ * virtioY => remoteprocX#vdevYbuffer.
*/
- if (!vq->vdev->dev.parent || !vq->vdev->dev.parent->parent)
+ buf->dev = vdev->dev.parent;
+ if (!buf->dev)
goto free_buf;
- buf->dev = vq->vdev->dev.parent->parent;
/* Increase device refcnt to avoid freeing it */
get_device(buf->dev);
@@ -492,7 +467,7 @@ static struct port_buffer *get_inbuf(struct port *port)
buf = virtqueue_get_buf(port->in_vq, &len);
if (buf) {
- buf->len = len;
+ buf->len = min_t(size_t, len, buf->size);
buf->offset = 0;
port->stats.bytes_received += len;
}
@@ -676,7 +651,7 @@ done:
* Give out the data that's requested from the buffer that we have
* queued up.
*/
-static ssize_t fill_readbuf(struct port *port, char __user *out_buf,
+static ssize_t fill_readbuf(struct port *port, u8 __user *out_buf,
size_t out_count, bool to_user)
{
struct port_buffer *buf;
@@ -695,7 +670,7 @@ static ssize_t fill_readbuf(struct port *port, char __user *out_buf,
if (ret)
return -EFAULT;
} else {
- memcpy((__force char *)out_buf, buf->buf + buf->offset,
+ memcpy((__force u8 *)out_buf, buf->buf + buf->offset,
out_count);
}
@@ -841,7 +816,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;
@@ -888,7 +863,7 @@ static int pipe_to_sg(struct pipe_inode_info *pipe, struct pipe_buffer *buf,
return 0;
/* Try lock this page */
- if (pipe_buf_steal(pipe, buf) == 0) {
+ if (pipe_buf_try_steal(pipe, buf)) {
/* Get reference and unlock page for moving */
get_page(buf->page);
unlock_page(buf->page);
@@ -909,9 +884,9 @@ static int pipe_to_sg(struct pipe_inode_info *pipe, struct pipe_buffer *buf,
if (len + offset > PAGE_SIZE)
len = PAGE_SIZE - offset;
- src = kmap_atomic(buf->page);
+ src = kmap_local_page(buf->page);
memcpy(page_address(page) + offset, src + buf->offset, len);
- kunmap_atomic(src);
+ kunmap_local(src);
sg_set_page(&(sgl->sg[sgl->n]), page, len, offset);
}
@@ -936,6 +911,7 @@ static ssize_t port_fops_splice_write(struct pipe_inode_info *pipe,
.pos = *ppos,
.u.data = &sgl,
};
+ unsigned int occupancy;
/*
* Rproc_serial does not yet support splice. To support splice
@@ -946,21 +922,18 @@ static ssize_t port_fops_splice_write(struct pipe_inode_info *pipe,
if (is_rproc_serial(port->out_vq->vdev))
return -EINVAL;
- /*
- * pipe->nrbufs == 0 means there are no data to transfer,
- * so this returns just 0 for no data.
- */
pipe_lock(pipe);
- if (!pipe->nrbufs) {
- ret = 0;
+ ret = 0;
+ if (pipe_is_empty(pipe))
goto error_out;
- }
ret = wait_port_writable(port, filp->f_flags & O_NONBLOCK);
if (ret < 0)
goto error_out;
- buf = alloc_buf(port->out_vq, 0, pipe->nrbufs);
+ occupancy = pipe_buf_usage(pipe);
+ buf = alloc_buf(port->portdev->vdev, 0, occupancy);
+
if (!buf) {
ret = -ENOMEM;
goto error_out;
@@ -968,7 +941,7 @@ static ssize_t port_fops_splice_write(struct pipe_inode_info *pipe,
sgl.n = 0;
sgl.len = 0;
- sgl.size = pipe->nrbufs;
+ sgl.size = occupancy;
sgl.sg = buf->sg;
sg_init_table(sgl.sg, sgl.size);
ret = __splice_from_pipe(pipe, &sd, pipe_to_sg);
@@ -985,25 +958,25 @@ error_out:
return ret;
}
-static unsigned int port_fops_poll(struct file *filp, poll_table *wait)
+static __poll_t port_fops_poll(struct file *filp, poll_table *wait)
{
struct port *port;
- unsigned int ret;
+ __poll_t ret;
port = filp->private_data;
poll_wait(filp, &port->waitqueue, wait);
if (!port->guest_connected) {
/* Port got unplugged */
- return POLLHUP;
+ return EPOLLHUP;
}
ret = 0;
if (!will_read_block(port))
- ret |= POLLIN | POLLRDNORM;
+ ret |= EPOLLIN | EPOLLRDNORM;
if (!will_write_block(port))
- ret |= POLLOUT;
+ ret |= EPOLLOUT;
if (!port->host_connected)
- ret |= POLLHUP;
+ ret |= EPOLLHUP;
return ret;
}
@@ -1121,7 +1094,6 @@ static const struct file_operations port_fops = {
.poll = port_fops_poll,
.release = port_fops_release,
.fasync = port_fops_fasync,
- .llseek = no_llseek,
};
/*
@@ -1130,18 +1102,15 @@ static const struct file_operations port_fops = {
* We turn the characters into a scatter-gather list, add it to the
* output queue and then kick the Host. Then we sit here waiting for
* it to finish: inefficient in theory, but in practice
- * implementations will do it immediately (lguest's Launcher does).
+ * implementations will do it immediately.
*/
-static int put_chars(u32 vtermno, const char *buf, int count)
+static ssize_t put_chars(u32 vtermno, const u8 *buf, size_t count)
{
struct port *port;
struct scatterlist sg[1];
void *data;
int ret;
- if (unlikely(early_put_chars))
- return early_put_chars(vtermno, buf, count);
-
port = find_port_by_vtermno(vtermno);
if (!port)
return -EPIPE;
@@ -1163,14 +1132,10 @@ static int put_chars(u32 vtermno, const char *buf, int count)
* We call out to fill_readbuf that gets us the required data from the
* buffers that are queued up.
*/
-static int get_chars(u32 vtermno, char *buf, int count)
+static ssize_t get_chars(u32 vtermno, u8 *buf, size_t count)
{
struct port *port;
- /* If we've not set up the port yet, we have no input to give. */
- if (unlikely(early_put_chars))
- return 0;
-
port = find_port_by_vtermno(vtermno);
if (!port)
return -EPIPE;
@@ -1178,7 +1143,7 @@ static int get_chars(u32 vtermno, char *buf, int count)
/* If we don't have an input queue yet, we can't get input. */
BUG_ON(!port->in_vq);
- return fill_readbuf(port, (__force char __user *)buf, count, false);
+ return fill_readbuf(port, (__force u8 __user *)buf, count, false);
}
static void resize_console(struct port *port)
@@ -1226,21 +1191,6 @@ static const struct hv_ops hv_ops = {
.notifier_hangup = notifier_del_vio,
};
-/*
- * Console drivers are initialized very early so boot messages can go
- * out, so we do things slightly differently from the generic virtio
- * initialization of the net and block drivers.
- *
- * At this stage, the console is output-only. It's too early to set
- * up a virtqueue, so we let the drivers do some boutique early-output
- * thing.
- */
-int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int))
-{
- early_put_chars = put_chars;
- return hvc_instantiate(0, 0, &hv_ops);
-}
-
static int init_port_console(struct port *port)
{
int ret;
@@ -1262,29 +1212,25 @@ static int init_port_console(struct port *port)
* pointers. The final argument is the output buffer size: we
* can do any size, so we put PAGE_SIZE here.
*/
- port->cons.vtermno = pdrvdata.next_vtermno;
+ ret = ida_alloc_min(&vtermno_ida, 1, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+ port->cons.vtermno = ret;
port->cons.hvc = hvc_alloc(port->cons.vtermno, 0, &hv_ops, PAGE_SIZE);
if (IS_ERR(port->cons.hvc)) {
ret = PTR_ERR(port->cons.hvc);
dev_err(port->dev,
"error %d allocating hvc for port\n", ret);
port->cons.hvc = NULL;
+ ida_free(&vtermno_ida, port->cons.vtermno);
return ret;
}
spin_lock_irq(&pdrvdata_lock);
- pdrvdata.next_vtermno++;
list_add_tail(&port->cons.list, &pdrvdata.consoles);
spin_unlock_irq(&pdrvdata_lock);
port->guest_connected = true;
- /*
- * Start using the new console output if this is the first
- * console to come up.
- */
- if (early_put_chars)
- early_put_chars = NULL;
-
/* Notify host of port being opened */
send_control_msg(port, VIRTIO_CONSOLE_PORT_OPEN, 1);
@@ -1308,57 +1254,29 @@ static struct attribute *port_sysfs_entries[] = {
NULL
};
-static struct attribute_group port_attribute_group = {
+static const struct attribute_group port_attribute_group = {
.name = NULL, /* put in device directory */
.attrs = port_sysfs_entries,
};
-static ssize_t debugfs_read(struct file *filp, char __user *ubuf,
- size_t count, loff_t *offp)
+static int port_debugfs_show(struct seq_file *s, void *data)
{
- struct port *port;
- char *buf;
- ssize_t ret, out_offset, out_count;
-
- out_count = 1024;
- buf = kmalloc(out_count, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
+ struct port *port = s->private;
+
+ seq_printf(s, "name: %s\n", port->name ? port->name : "");
+ seq_printf(s, "guest_connected: %d\n", port->guest_connected);
+ seq_printf(s, "host_connected: %d\n", port->host_connected);
+ seq_printf(s, "outvq_full: %d\n", port->outvq_full);
+ seq_printf(s, "bytes_sent: %lu\n", port->stats.bytes_sent);
+ seq_printf(s, "bytes_received: %lu\n", port->stats.bytes_received);
+ seq_printf(s, "bytes_discarded: %lu\n", port->stats.bytes_discarded);
+ seq_printf(s, "is_console: %s\n", str_yes_no(is_console_port(port)));
+ seq_printf(s, "console_vtermno: %u\n", port->cons.vtermno);
- port = filp->private_data;
- out_offset = 0;
- out_offset += snprintf(buf + out_offset, out_count,
- "name: %s\n", port->name ? port->name : "");
- out_offset += snprintf(buf + out_offset, out_count - out_offset,
- "guest_connected: %d\n", port->guest_connected);
- out_offset += snprintf(buf + out_offset, out_count - out_offset,
- "host_connected: %d\n", port->host_connected);
- out_offset += snprintf(buf + out_offset, out_count - out_offset,
- "outvq_full: %d\n", port->outvq_full);
- out_offset += snprintf(buf + out_offset, out_count - out_offset,
- "bytes_sent: %lu\n", port->stats.bytes_sent);
- out_offset += snprintf(buf + out_offset, out_count - out_offset,
- "bytes_received: %lu\n",
- port->stats.bytes_received);
- out_offset += snprintf(buf + out_offset, out_count - out_offset,
- "bytes_discarded: %lu\n",
- port->stats.bytes_discarded);
- out_offset += snprintf(buf + out_offset, out_count - out_offset,
- "is_console: %s\n",
- is_console_port(port) ? "yes" : "no");
- out_offset += snprintf(buf + out_offset, out_count - out_offset,
- "console_vtermno: %u\n", port->cons.vtermno);
-
- ret = simple_read_from_buffer(ubuf, count, offp, buf, out_offset);
- kfree(buf);
- return ret;
+ return 0;
}
-static const struct file_operations port_debugfs_ops = {
- .owner = THIS_MODULE,
- .open = simple_open,
- .read = debugfs_read,
-};
+DEFINE_SHOW_ATTRIBUTE(port_debugfs);
static void set_console_size(struct port *port, u16 rows, u16 cols)
{
@@ -1369,24 +1287,24 @@ static void set_console_size(struct port *port, u16 rows, u16 cols)
port->cons.ws.ws_col = cols;
}
-static unsigned int fill_queue(struct virtqueue *vq, spinlock_t *lock)
+static int fill_queue(struct virtqueue *vq, spinlock_t *lock)
{
struct port_buffer *buf;
- unsigned int nr_added_bufs;
+ int nr_added_bufs;
int ret;
nr_added_bufs = 0;
do {
- buf = alloc_buf(vq, PAGE_SIZE, 0);
+ buf = alloc_buf(vq->vdev, PAGE_SIZE, 0);
if (!buf)
- break;
+ return -ENOMEM;
spin_lock_irq(lock);
ret = add_inbuf(vq, buf);
if (ret < 0) {
spin_unlock_irq(lock);
free_buf(buf, true);
- break;
+ return ret;
}
nr_added_bufs++;
spin_unlock_irq(lock);
@@ -1403,11 +1321,8 @@ static void send_sigio_to_port(struct port *port)
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;
port = kmalloc(sizeof(*port), GFP_KERNEL);
@@ -1426,6 +1341,7 @@ static int add_port(struct ports_device *portdev, u32 id)
port->async_queue = NULL;
port->cons.ws.ws_row = port->cons.ws.ws_col = 0;
+ port->cons.vtermno = 0;
port->host_connected = port->guest_connected = false;
port->stats = (struct port_stats) { 0 };
@@ -1450,7 +1366,7 @@ static int add_port(struct ports_device *portdev, u32 id)
"Error %d adding cdev for port %u\n", err, id);
goto free_cdev;
}
- port->dev = device_create(pdrvdata.class, &port->portdev->vdev->dev,
+ port->dev = device_create(&port_class, &port->portdev->vdev->dev,
devt, port, "vport%up%u",
port->portdev->vdev->index, id);
if (IS_ERR(port->dev)) {
@@ -1465,11 +1381,13 @@ static int add_port(struct ports_device *portdev, u32 id)
spin_lock_init(&port->outvq_lock);
init_waitqueue_head(&port->waitqueue);
- /* Fill the in_vq with buffers so the host can send us data. */
- nr_added_bufs = fill_queue(port->in_vq, &port->inbuf_lock);
- if (!nr_added_bufs) {
+ /* We can safely ignore ENOSPC because it means
+ * the queue already has buffers. Buffers are removed
+ * only by virtcons_remove(), not by unplug_port()
+ */
+ err = fill_queue(port->in_vq, &port->inbuf_lock);
+ if (err < 0 && err != -ENOSPC) {
dev_err(port->dev, "Error allocating inbufs\n");
- err = -ENOMEM;
goto free_device;
}
@@ -1501,25 +1419,18 @@ static int add_port(struct ports_device *portdev, u32 id)
*/
send_control_msg(port, VIRTIO_CONSOLE_PORT_READY, 1);
- if (pdrvdata.debugfs_dir) {
- /*
- * Finally, create the debugfs file that we can use to
- * inspect a port's state at any time
- */
- snprintf(debugfs_name, sizeof(debugfs_name), "vport%up%u",
- port->portdev->vdev->index, id);
- port->debugfs_file = debugfs_create_file(debugfs_name, 0444,
- pdrvdata.debugfs_dir,
- port,
- &port_debugfs_ops);
- }
+ /*
+ * Finally, create the debugfs file that we can use to
+ * inspect a port's state at any time
+ */
+ port->debugfs_file = debugfs_create_file(dev_name(port->dev), 0444,
+ pdrvdata.debugfs_dir,
+ port, &port_debugfs_fops);
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);
+ device_destroy(&port_class, port->dev->devt);
free_cdev:
cdev_del(port->cdev);
free_port:
@@ -1542,34 +1453,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);
}
/*
@@ -1601,6 +1492,7 @@ static void unplug_port(struct port *port)
list_del(&port->cons.list);
spin_unlock_irq(&pdrvdata_lock);
hvc_remove(port->cons.hvc);
+ ida_free(&vtermno_ida, port->cons.vtermno);
}
remove_port_data(port);
@@ -1613,7 +1505,7 @@ static void unplug_port(struct port *port)
port->portdev = NULL;
sysfs_remove_group(&port->dev->kobj, &port_attribute_group);
- device_destroy(pdrvdata.class, port->dev->devt);
+ device_destroy(&port_class, port->dev->devt);
cdev_del(port->cdev);
debugfs_remove(port->debugfs_file);
@@ -1684,8 +1576,8 @@ static void handle_control_message(struct virtio_device *vdev,
break;
case VIRTIO_CONSOLE_RESIZE: {
struct {
- __u16 rows;
- __u16 cols;
+ __virtio16 cols;
+ __virtio16 rows;
} size;
if (!is_console_port(port))
@@ -1693,7 +1585,8 @@ static void handle_control_message(struct virtio_device *vdev,
memcpy(&size, buf->buf + buf->offset + sizeof(*cpkt),
sizeof(size));
- set_console_size(port, size.rows, size.cols);
+ set_console_size(port, virtio16_to_cpu(vdev, size.rows),
+ virtio16_to_cpu(vdev, size.cols));
port->cons.hvc->irq_requested = 1;
resize_console(port);
@@ -1739,9 +1632,8 @@ static void handle_control_message(struct virtio_device *vdev,
"Not enough space to store port name\n");
break;
}
- strncpy(port->name, buf->buf + buf->offset + sizeof(*cpkt),
- name_size - 1);
- port->name[name_size - 1] = 0;
+ strscpy(port->name, buf->buf + buf->offset + sizeof(*cpkt),
+ name_size);
/*
* Since we only have one sysfs attribute, 'name',
@@ -1779,7 +1671,7 @@ static void control_work_handler(struct work_struct *work)
while ((buf = virtqueue_get_buf(vq, &len))) {
spin_unlock(&portdev->c_ivq_lock);
- buf->len = len;
+ buf->len = min_t(size_t, len, buf->size);
buf->offset = 0;
handle_control_message(vq->vdev, portdev, buf);
@@ -1794,13 +1686,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);
}
@@ -1811,8 +1714,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);
@@ -1896,8 +1801,7 @@ static void config_work_handler(struct work_struct *work)
static int init_vqs(struct ports_device *portdev)
{
- vq_callback_t **io_callbacks;
- char **io_names;
+ struct virtqueue_info *vqs_info;
struct virtqueue **vqs;
u32 i, j, nr_ports, nr_queues;
int err;
@@ -1905,15 +1809,13 @@ static int init_vqs(struct ports_device *portdev)
nr_ports = portdev->max_nr_ports;
nr_queues = use_multiport(portdev) ? (nr_ports + 1) * 2 : 2;
- vqs = kmalloc(nr_queues * sizeof(struct virtqueue *), GFP_KERNEL);
- io_callbacks = kmalloc(nr_queues * sizeof(vq_callback_t *), GFP_KERNEL);
- io_names = kmalloc(nr_queues * sizeof(char *), GFP_KERNEL);
- portdev->in_vqs = kmalloc(nr_ports * sizeof(struct virtqueue *),
- GFP_KERNEL);
- portdev->out_vqs = kmalloc(nr_ports * sizeof(struct virtqueue *),
- GFP_KERNEL);
- if (!vqs || !io_callbacks || !io_names || !portdev->in_vqs ||
- !portdev->out_vqs) {
+ vqs = kmalloc_array(nr_queues, sizeof(struct virtqueue *), GFP_KERNEL);
+ vqs_info = kcalloc(nr_queues, sizeof(*vqs_info), GFP_KERNEL);
+ portdev->in_vqs = kmalloc_array(nr_ports, sizeof(struct virtqueue *),
+ GFP_KERNEL);
+ portdev->out_vqs = kmalloc_array(nr_ports, sizeof(struct virtqueue *),
+ GFP_KERNEL);
+ if (!vqs || !vqs_info || !portdev->in_vqs || !portdev->out_vqs) {
err = -ENOMEM;
goto free;
}
@@ -1924,30 +1826,27 @@ static int init_vqs(struct ports_device *portdev)
* 0 before others.
*/
j = 0;
- io_callbacks[j] = in_intr;
- io_callbacks[j + 1] = out_intr;
- io_names[j] = "input";
- io_names[j + 1] = "output";
+ vqs_info[j].callback = in_intr;
+ vqs_info[j + 1].callback = out_intr;
+ vqs_info[j].name = "input";
+ vqs_info[j + 1].name = "output";
j += 2;
if (use_multiport(portdev)) {
- io_callbacks[j] = control_intr;
- io_callbacks[j + 1] = NULL;
- io_names[j] = "control-i";
- io_names[j + 1] = "control-o";
+ vqs_info[j].callback = control_intr;
+ vqs_info[j].name = "control-i";
+ vqs_info[j + 1].name = "control-o";
for (i = 1; i < nr_ports; i++) {
j += 2;
- io_callbacks[j] = in_intr;
- io_callbacks[j + 1] = out_intr;
- io_names[j] = "input";
- io_names[j + 1] = "output";
+ vqs_info[j].callback = in_intr;
+ vqs_info[j + 1].callback = out_intr;
+ vqs_info[j].name = "input";
+ vqs_info[j + 1].name = "output";
}
}
/* Find the queues. */
- err = virtio_find_vqs(portdev->vdev, nr_queues, vqs,
- io_callbacks,
- (const char **)io_names, NULL);
+ err = virtio_find_vqs(portdev->vdev, nr_queues, vqs, vqs_info, NULL);
if (err)
goto free;
@@ -1965,8 +1864,7 @@ static int init_vqs(struct ports_device *portdev)
portdev->out_vqs[i] = vqs[j + 1];
}
}
- kfree(io_names);
- kfree(io_callbacks);
+ kfree(vqs_info);
kfree(vqs);
return 0;
@@ -1974,8 +1872,7 @@ static int init_vqs(struct ports_device *portdev)
free:
kfree(portdev->out_vqs);
kfree(portdev->in_vqs);
- kfree(io_names);
- kfree(io_callbacks);
+ kfree(vqs_info);
kfree(vqs);
return err;
@@ -1987,24 +1884,62 @@ 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);
+ cond_resched();
+ }
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);
+ /* Device is going away, exit any polling for buffers */
+ virtio_break_device(vdev);
+ if (use_multiport(portdev))
+ flush_work(&portdev->control_work);
+ else
+ flush_work(&portdev->config_work);
+
+ /* Disable interrupts for vqs */
+ virtio_reset_device(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);
}
/*
@@ -2020,7 +1955,6 @@ static int virtcons_probe(struct virtio_device *vdev)
struct ports_device *portdev;
int err;
bool multiport;
- bool early = early_put_chars != NULL;
/* We only need a config space if features are offered */
if (!vdev->config->get &&
@@ -2031,9 +1965,6 @@ static int virtcons_probe(struct virtio_device *vdev)
return -EINVAL;
}
- /* Ensure to read early_put_chars now */
- barrier();
-
portdev = kmalloc(sizeof(*portdev), GFP_KERNEL);
if (!portdev) {
err = -ENOMEM;
@@ -2062,36 +1993,51 @@ static int virtcons_probe(struct virtio_device *vdev)
virtio_cread_feature(vdev, VIRTIO_CONSOLE_F_MULTIPORT,
struct virtio_console_config, max_nr_ports,
&portdev->max_nr_ports) == 0) {
+ if (portdev->max_nr_ports == 0 ||
+ portdev->max_nr_ports > VIRTCONS_MAX_PORTS) {
+ dev_err(&vdev->dev,
+ "Invalidate max_nr_ports %d",
+ portdev->max_nr_ports);
+ err = -EINVAL;
+ goto free;
+ }
multiport = true;
}
- err = init_vqs(portdev);
- if (err < 0) {
- dev_err(&vdev->dev, "Error %d initializing vqs\n", err);
- goto free_chrdev;
- }
-
spin_lock_init(&portdev->ports_lock);
INIT_LIST_HEAD(&portdev->ports);
-
- virtio_device_ready(portdev->vdev);
+ INIT_LIST_HEAD(&portdev->list);
INIT_WORK(&portdev->config_work, &config_work_handler);
INIT_WORK(&portdev->control_work, &control_work_handler);
if (multiport) {
- unsigned int nr_added_bufs;
-
spin_lock_init(&portdev->c_ivq_lock);
spin_lock_init(&portdev->c_ovq_lock);
+ }
+
+ err = init_vqs(portdev);
+ if (err < 0) {
+ dev_err(&vdev->dev, "Error %d initializing vqs\n", err);
+ goto free_chrdev;
+ }
+
+ virtio_device_ready(portdev->vdev);
- nr_added_bufs = fill_queue(portdev->c_ivq,
- &portdev->c_ivq_lock);
- if (!nr_added_bufs) {
+ if (multiport) {
+ err = fill_queue(portdev->c_ivq, &portdev->c_ivq_lock);
+ if (err < 0) {
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 err;
}
} else {
/*
@@ -2108,25 +2054,8 @@ static int virtcons_probe(struct virtio_device *vdev)
__send_control_msg(portdev, VIRTIO_CONSOLE_BAD_ID,
VIRTIO_CONSOLE_DEVICE_READY, 1);
- /*
- * If there was an early virtio console, assume that there are no
- * other consoles. We need to wait until the hvc_alloc matches the
- * hvc_instantiate, otherwise tty_open will complain, resulting in
- * a "Warning: unable to open an initial console" boot failure.
- * Without multiport this is done in add_port above. With multiport
- * this might take some host<->guest communication - thus we have to
- * wait.
- */
- if (multiport && early)
- wait_for_completion(&early_console_added);
-
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:
@@ -2135,61 +2064,26 @@ 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[] = {
+static const struct virtio_device_id id_table[] = {
{ VIRTIO_ID_CONSOLE, VIRTIO_DEV_ANY_ID },
{ 0 },
};
+MODULE_DEVICE_TABLE(virtio, id_table);
-static unsigned int features[] = {
+static const unsigned int features[] = {
VIRTIO_CONSOLE_F_SIZE,
VIRTIO_CONSOLE_F_MULTIPORT,
};
-static struct virtio_device_id rproc_serial_id_table[] = {
+static const struct virtio_device_id rproc_serial_id_table[] = {
#if IS_ENABLED(CONFIG_REMOTEPROC)
{ VIRTIO_ID_RPROC_SERIAL, VIRTIO_DEV_ANY_ID },
#endif
{ 0 },
};
+MODULE_DEVICE_TABLE(virtio, rproc_serial_id_table);
-static unsigned int rproc_serial_features[] = {
+static const unsigned int rproc_serial_features[] = {
};
#ifdef CONFIG_PM_SLEEP
@@ -2200,7 +2094,7 @@ static int virtcons_freeze(struct virtio_device *vdev)
portdev = vdev->priv;
- vdev->config->reset(vdev);
+ virtio_reset_device(vdev);
if (use_multiport(portdev))
virtqueue_disable_cb(portdev->c_ivq);
@@ -2212,7 +2106,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);
@@ -2270,7 +2163,6 @@ static struct virtio_driver virtio_console = {
.feature_table = features,
.feature_table_size = ARRAY_SIZE(features),
.driver.name = KBUILD_MODNAME,
- .driver.owner = THIS_MODULE,
.id_table = id_table,
.probe = virtcons_probe,
.remove = virtcons_remove,
@@ -2285,26 +2177,20 @@ static struct virtio_driver 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 virtio_console_init(void)
{
int err;
- pdrvdata.class = class_create(THIS_MODULE, "virtio-ports");
- if (IS_ERR(pdrvdata.class)) {
- err = PTR_ERR(pdrvdata.class);
- pr_err("Error %d creating virtio-ports class\n", err);
+ err = class_register(&port_class);
+ if (err)
return err;
- }
pdrvdata.debugfs_dir = debugfs_create_dir("virtio-ports", NULL);
- if (!pdrvdata.debugfs_dir)
- pr_warn("Error creating debugfs dir for virtio-ports\n");
INIT_LIST_HEAD(&pdrvdata.consoles);
INIT_LIST_HEAD(&pdrvdata.portdevs);
@@ -2324,23 +2210,22 @@ unregister:
unregister_virtio_driver(&virtio_console);
free:
debugfs_remove_recursive(pdrvdata.debugfs_dir);
- class_destroy(pdrvdata.class);
+ class_unregister(&port_class);
return err;
}
-static void __exit fini(void)
+static void __exit virtio_console_fini(void)
{
reclaim_dma_bufs();
unregister_virtio_driver(&virtio_console);
unregister_virtio_driver(&virtio_rproc_serial);
- class_destroy(pdrvdata.class);
+ class_unregister(&port_class);
debugfs_remove_recursive(pdrvdata.debugfs_dir);
}
-module_init(init);
-module_exit(fini);
+module_init(virtio_console_init);
+module_exit(virtio_console_fini);
-MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_DESCRIPTION("Virtio console driver");
MODULE_LICENSE("GPL");