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>
This commit is contained in:
Родитель
fb08bd274d
Коммит
2030fa496d
|
@ -19,11 +19,15 @@
|
||||||
#include <linux/cdev.h>
|
#include <linux/cdev.h>
|
||||||
#include <linux/device.h>
|
#include <linux/device.h>
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
|
#include <linux/fs.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/list.h>
|
#include <linux/list.h>
|
||||||
|
#include <linux/poll.h>
|
||||||
|
#include <linux/sched.h>
|
||||||
#include <linux/spinlock.h>
|
#include <linux/spinlock.h>
|
||||||
#include <linux/virtio.h>
|
#include <linux/virtio.h>
|
||||||
#include <linux/virtio_console.h>
|
#include <linux/virtio_console.h>
|
||||||
|
#include <linux/wait.h>
|
||||||
#include <linux/workqueue.h>
|
#include <linux/workqueue.h>
|
||||||
#include "hvc_console.h"
|
#include "hvc_console.h"
|
||||||
|
|
||||||
|
@ -163,8 +167,14 @@ struct port {
|
||||||
struct cdev cdev;
|
struct cdev cdev;
|
||||||
struct device *dev;
|
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 */
|
/* The 'id' to identify the port with the Host */
|
||||||
u32 id;
|
u32 id;
|
||||||
|
|
||||||
|
/* Is the host device open */
|
||||||
|
bool host_connected;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* This is the very early arch-specified put chars function. */
|
/* 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;
|
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.
|
* 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);
|
list_add_tail(&port->cons.list, &pdrvdata.consoles);
|
||||||
spin_unlock_irq(&pdrvdata_lock);
|
spin_unlock_irq(&pdrvdata_lock);
|
||||||
|
|
||||||
|
/* Notify host of port being opened */
|
||||||
|
send_control_msg(port, VIRTIO_CONSOLE_PORT_OPEN, 1);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -599,6 +752,10 @@ static void handle_control_message(struct ports_device *portdev,
|
||||||
port->cons.hvc->irq_requested = 1;
|
port->cons.hvc->irq_requested = 1;
|
||||||
resize_console(port);
|
resize_console(port);
|
||||||
break;
|
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);
|
spin_unlock_irqrestore(&port->inbuf_lock, flags);
|
||||||
|
|
||||||
|
wake_up_interruptible(&port->waitqueue);
|
||||||
|
|
||||||
if (is_console_port(port) && hvc_poll(port->cons.hvc))
|
if (is_console_port(port) && hvc_poll(port->cons.hvc))
|
||||||
hvc_kick();
|
hvc_kick();
|
||||||
}
|
}
|
||||||
|
@ -697,10 +856,12 @@ static int add_port(struct ports_device *portdev, u32 id)
|
||||||
port->inbuf = NULL;
|
port->inbuf = NULL;
|
||||||
port->cons.hvc = NULL;
|
port->cons.hvc = NULL;
|
||||||
|
|
||||||
|
port->host_connected = false;
|
||||||
|
|
||||||
port->in_vq = portdev->in_vqs[port->id];
|
port->in_vq = portdev->in_vqs[port->id];
|
||||||
port->out_vq = portdev->out_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);
|
devt = MKDEV(portdev->chr_major, id);
|
||||||
err = cdev_add(&port->cdev, devt, 1);
|
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);
|
spin_lock_init(&port->inbuf_lock);
|
||||||
|
init_waitqueue_head(&port->waitqueue);
|
||||||
|
|
||||||
inbuf = alloc_buf(PAGE_SIZE);
|
inbuf = alloc_buf(PAGE_SIZE);
|
||||||
if (!inbuf) {
|
if (!inbuf) {
|
||||||
|
|
|
@ -39,6 +39,7 @@ struct virtio_console_control {
|
||||||
#define VIRTIO_CONSOLE_PORT_READY 0
|
#define VIRTIO_CONSOLE_PORT_READY 0
|
||||||
#define VIRTIO_CONSOLE_CONSOLE_PORT 1
|
#define VIRTIO_CONSOLE_CONSOLE_PORT 1
|
||||||
#define VIRTIO_CONSOLE_RESIZE 2
|
#define VIRTIO_CONSOLE_RESIZE 2
|
||||||
|
#define VIRTIO_CONSOLE_PORT_OPEN 3
|
||||||
|
|
||||||
#ifdef __KERNEL__
|
#ifdef __KERNEL__
|
||||||
int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int));
|
int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int));
|
||||||
|
|
Загрузка…
Ссылка в новой задаче