aboutsummaryrefslogtreecommitdiff
path: root/drivers/char
diff options
context:
space:
mode:
authorAmit Shah <amit.shah@redhat.com>2009-12-21 21:49:30 +0530
committerRusty Russell <rusty@rustcorp.com.au>2010-02-24 14:22:54 +1030
commit2030fa496d74b49220308eaccf656e2338019cfd (patch)
treecf8d937551d8bfefadbbc5389caaf587f2d0b13b /drivers/char
parentfb08bd274df61967f40d49c4625fe6ed75a69ab5 (diff)
virtio: console: Add file operations to ports for open/read/write/poll
Allow guest userspace applications to open, read from, write to, poll the ports via the char dev interface. When a port gets opened, a notification is sent to the host via a control message indicating a connection has been established. Similarly, on closing of the port, a notification is sent indicating disconnection. Signed-off-by: Amit Shah <amit.shah@redhat.com> Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Diffstat (limited to 'drivers/char')
-rw-r--r--drivers/char/virtio_console.c164
1 files changed, 163 insertions, 1 deletions
diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
index 64ef476d6557..ece1546fbb20 100644
--- a/drivers/char/virtio_console.c
+++ b/drivers/char/virtio_console.c
@@ -19,11 +19,15 @@
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/err.h>
+#include <linux/fs.h>
#include <linux/init.h>
#include <linux/list.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
#include <linux/spinlock.h>
#include <linux/virtio.h>
#include <linux/virtio_console.h>
+#include <linux/wait.h>
#include <linux/workqueue.h>
#include "hvc_console.h"
@@ -163,8 +167,14 @@ struct port {
struct cdev cdev;
struct device *dev;
+ /* A waitqueue for poll() or blocking read operations */
+ wait_queue_head_t waitqueue;
+
/* The 'id' to identify the port with the Host */
u32 id;
+
+ /* Is the host device open */
+ bool host_connected;
};
/* This is the very early arch-specified put chars function. */
@@ -417,6 +427,146 @@ static ssize_t fill_readbuf(struct port *port, char *out_buf, size_t out_count,
return out_count;
}
+/* The condition that must be true for polling to end */
+static bool wait_is_over(struct port *port)
+{
+ return port_has_data(port) || !port->host_connected;
+}
+
+static ssize_t port_fops_read(struct file *filp, char __user *ubuf,
+ size_t count, loff_t *offp)
+{
+ struct port *port;
+ ssize_t ret;
+
+ port = filp->private_data;
+
+ if (!port_has_data(port)) {
+ /*
+ * If nothing's connected on the host just return 0 in
+ * case of list_empty; this tells the userspace app
+ * that there's no connection
+ */
+ if (!port->host_connected)
+ return 0;
+ if (filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ ret = wait_event_interruptible(port->waitqueue,
+ wait_is_over(port));
+ if (ret < 0)
+ return ret;
+ }
+ /*
+ * We could've received a disconnection message while we were
+ * waiting for more data.
+ *
+ * This check is not clubbed in the if() statement above as we
+ * might receive some data as well as the host could get
+ * disconnected after we got woken up from our wait. So we
+ * really want to give off whatever data we have and only then
+ * check for host_connected.
+ */
+ if (!port_has_data(port) && !port->host_connected)
+ return 0;
+
+ return fill_readbuf(port, ubuf, count, true);
+}
+
+static ssize_t port_fops_write(struct file *filp, const char __user *ubuf,
+ size_t count, loff_t *offp)
+{
+ struct port *port;
+ char *buf;
+ ssize_t ret;
+
+ port = filp->private_data;
+
+ count = min((size_t)(32 * 1024), count);
+
+ buf = kmalloc(count, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = copy_from_user(buf, ubuf, count);
+ if (ret) {
+ ret = -EFAULT;
+ goto free_buf;
+ }
+
+ ret = send_buf(port, buf, count);
+free_buf:
+ kfree(buf);
+ return ret;
+}
+
+static unsigned int port_fops_poll(struct file *filp, poll_table *wait)
+{
+ struct port *port;
+ unsigned int ret;
+
+ port = filp->private_data;
+ poll_wait(filp, &port->waitqueue, wait);
+
+ ret = 0;
+ if (port->inbuf)
+ ret |= POLLIN | POLLRDNORM;
+ if (port->host_connected)
+ ret |= POLLOUT;
+ if (!port->host_connected)
+ ret |= POLLHUP;
+
+ return ret;
+}
+
+static int port_fops_release(struct inode *inode, struct file *filp)
+{
+ struct port *port;
+
+ port = filp->private_data;
+
+ /* Notify host of port being closed */
+ send_control_msg(port, VIRTIO_CONSOLE_PORT_OPEN, 0);
+
+ return 0;
+}
+
+static int port_fops_open(struct inode *inode, struct file *filp)
+{
+ struct cdev *cdev = inode->i_cdev;
+ struct port *port;
+
+ port = container_of(cdev, struct port, cdev);
+ filp->private_data = port;
+
+ /*
+ * Don't allow opening of console port devices -- that's done
+ * via /dev/hvc
+ */
+ if (is_console_port(port))
+ return -ENXIO;
+
+ /* Notify host of port being opened */
+ send_control_msg(filp->private_data, VIRTIO_CONSOLE_PORT_OPEN, 1);
+
+ return 0;
+}
+
+/*
+ * The file operations that we support: programs in the guest can open
+ * a console device, read from it, write to it, poll for data and
+ * close it. The devices are at
+ * /dev/vport<device number>p<port number>
+ */
+static const struct file_operations port_fops = {
+ .owner = THIS_MODULE,
+ .open = port_fops_open,
+ .read = port_fops_read,
+ .write = port_fops_write,
+ .poll = port_fops_poll,
+ .release = port_fops_release,
+};
+
/*
* The put_chars() callback is pretty straightforward.
*
@@ -560,6 +710,9 @@ int init_port_console(struct port *port)
list_add_tail(&port->cons.list, &pdrvdata.consoles);
spin_unlock_irq(&pdrvdata_lock);
+ /* Notify host of port being opened */
+ send_control_msg(port, VIRTIO_CONSOLE_PORT_OPEN, 1);
+
return 0;
}
@@ -599,6 +752,10 @@ static void handle_control_message(struct ports_device *portdev,
port->cons.hvc->irq_requested = 1;
resize_console(port);
break;
+ case VIRTIO_CONSOLE_PORT_OPEN:
+ port->host_connected = cpkt->value;
+ wake_up_interruptible(&port->waitqueue);
+ break;
}
}
@@ -645,6 +802,8 @@ static void in_intr(struct virtqueue *vq)
spin_unlock_irqrestore(&port->inbuf_lock, flags);
+ wake_up_interruptible(&port->waitqueue);
+
if (is_console_port(port) && hvc_poll(port->cons.hvc))
hvc_kick();
}
@@ -697,10 +856,12 @@ static int add_port(struct ports_device *portdev, u32 id)
port->inbuf = NULL;
port->cons.hvc = NULL;
+ port->host_connected = false;
+
port->in_vq = portdev->in_vqs[port->id];
port->out_vq = portdev->out_vqs[port->id];
- cdev_init(&port->cdev, NULL);
+ cdev_init(&port->cdev, &port_fops);
devt = MKDEV(portdev->chr_major, id);
err = cdev_add(&port->cdev, devt, 1);
@@ -721,6 +882,7 @@ static int add_port(struct ports_device *portdev, u32 id)
}
spin_lock_init(&port->inbuf_lock);
+ init_waitqueue_head(&port->waitqueue);
inbuf = alloc_buf(PAGE_SIZE);
if (!inbuf) {