diff options
| -rw-r--r-- | drivers/char/virtio_console.c | 164 | ||||
| -rw-r--r-- | include/linux/virtio_console.h | 1 | 
2 files changed, 164 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) { diff --git a/include/linux/virtio_console.h b/include/linux/virtio_console.h index f4d183b5b493..bd0e2a596f93 100644 --- a/include/linux/virtio_console.h +++ b/include/linux/virtio_console.h @@ -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)); | 
