Skip to content

Commit

Permalink
virtio: console: Add file operations to ports for open/read/write/poll
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Amit Shah authored and Rusty Russell committed Feb 24, 2010
1 parent fb08bd2 commit 2030fa4
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 1 deletion.
164 changes: 163 additions & 1 deletion drivers/char/virtio_console.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions include/linux/virtio_console.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ struct virtio_console_control {
#define VIRTIO_CONSOLE_PORT_READY 0
#define VIRTIO_CONSOLE_CONSOLE_PORT 1
#define VIRTIO_CONSOLE_RESIZE 2
#define VIRTIO_CONSOLE_PORT_OPEN 3

#ifdef __KERNEL__
int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int));
Expand Down

0 comments on commit 2030fa4

Please sign in to comment.