Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2030fa49 authored by Amit Shah's avatar Amit Shah Committed by Rusty Russell
Browse files

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: default avatarAmit Shah <amit.shah@redhat.com>
Signed-off-by: default avatarRusty Russell <rusty@rustcorp.com.au>
parent fb08bd27
Loading
Loading
Loading
Loading
+163 −1
Original line number Diff line number Diff line
@@ -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) {
+1 −0
Original line number Diff line number Diff line
@@ -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));