From a23ea92474e558b071d3e43d961ec767c31faebd Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 18 Jan 2010 19:14:55 +0530 Subject: virtio: console: comment cleanup Remove old lguest-style comments. [Amit: - wingify comments acc. to kernel style - indent comments ] Signed-off-by: Rusty Russell Signed-off-by: Amit Shah Signed-off-by: Rusty Russell --- include/linux/virtio_console.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'include/linux/virtio_console.h') diff --git a/include/linux/virtio_console.h b/include/linux/virtio_console.h index fe885174cc1f..9e0da40beae0 100644 --- a/include/linux/virtio_console.h +++ b/include/linux/virtio_console.h @@ -3,8 +3,10 @@ #include #include #include -/* This header, excluding the #ifdef __KERNEL__ part, is BSD licensed so - * anyone can use the definitions to implement compatible drivers/servers. */ +/* + * This header, excluding the #ifdef __KERNEL__ part, is BSD licensed so + * anyone can use the definitions to implement compatible drivers/servers. + */ /* Feature bits */ #define VIRTIO_CONSOLE_F_SIZE 0 /* Does host provide console size? */ -- cgit v1.2.3 From 17634ba25544d60af1968982929150efad755032 Mon Sep 17 00:00:00 2001 From: Amit Shah Date: Mon, 21 Dec 2009 21:03:25 +0530 Subject: virtio: console: Add a new MULTIPORT feature, support for generic ports This commit adds a new feature, MULTIPORT. If the host supports this feature as well, the config space has the number of ports defined for that device. New ports are spawned according to this information. The config space also has the maximum number of ports that can be spawned for a particular device. This is useful in initializing the appropriate number of virtqueues in advance, as ports might be hot-plugged in later. Using this feature, generic ports can be created which are not tied to hvc consoles. We also open up a private channel between the host and the guest via which some "control" messages are exchanged for the ports, like whether the port being spawned is a console port, resizing the console window, etc. Next commits will add support for hotplugging and presenting char devices in /dev/ for bi-directional guest-host communication. Signed-off-by: Amit Shah Signed-off-by: Rusty Russell --- drivers/char/virtio_console.c | 392 +++++++++++++++++++++++++++++++++++------ include/linux/virtio_console.h | 21 +++ 2 files changed, 357 insertions(+), 56 deletions(-) (limited to 'include/linux/virtio_console.h') diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index d01051060be3..a70f2b3a9e64 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2006, 2007, 2009 Rusty Russell, IBM Corporation + * Copyright (C) 2009, 2010 Red Hat, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,6 +22,7 @@ #include #include #include +#include #include "hvc_console.h" /* @@ -69,17 +71,6 @@ struct console { u32 vtermno; }; -/* - * This is a per-device struct that stores data common to all the - * ports for that device (vdev->priv). - */ -struct ports_device { - /* Array of per-port IO virtqueues */ - struct virtqueue **in_vqs, **out_vqs; - - struct virtio_device *vdev; -}; - struct port_buffer { char *buf; @@ -92,8 +83,46 @@ struct port_buffer { size_t offset; }; +/* + * This is a per-device struct that stores data common to all the + * ports for that device (vdev->priv). + */ +struct ports_device { + /* + * Workqueue handlers where we process deferred work after + * notification + */ + struct work_struct control_work; + + struct list_head ports; + + /* To protect the list of ports */ + spinlock_t ports_lock; + + /* To protect the vq operations for the control channel */ + spinlock_t cvq_lock; + + /* The current config space is stored here */ + struct virtio_console_config config; + + /* The virtio device we're associated with */ + struct virtio_device *vdev; + + /* + * A couple of virtqueues for the control channel: one for + * guest->host transfers, one for host->guest transfers + */ + struct virtqueue *c_ivq, *c_ovq; + + /* Array of per-port IO virtqueues */ + struct virtqueue **in_vqs, **out_vqs; +}; + /* This struct holds the per-port data */ struct port { + /* Next port in the list, head is in the ports_device */ + struct list_head list; + /* Pointer to the parent virtio_console device */ struct ports_device *portdev; @@ -115,6 +144,9 @@ struct port { * hooked up to an hvc console */ struct console cons; + + /* The 'id' to identify the port with the Host */ + u32 id; }; /* This is the very early arch-specified put chars function. */ @@ -139,25 +171,56 @@ out: return port; } +static struct port *find_port_by_id(struct ports_device *portdev, u32 id) +{ + struct port *port; + unsigned long flags; + + spin_lock_irqsave(&portdev->ports_lock, flags); + list_for_each_entry(port, &portdev->ports, list) + if (port->id == id) + goto out; + port = NULL; +out: + spin_unlock_irqrestore(&portdev->ports_lock, flags); + + return port; +} + static struct port *find_port_by_vq(struct ports_device *portdev, struct virtqueue *vq) { struct port *port; - struct console *cons; unsigned long flags; - spin_lock_irqsave(&pdrvdata_lock, flags); - list_for_each_entry(cons, &pdrvdata.consoles, list) { - port = container_of(cons, struct port, cons); + spin_lock_irqsave(&portdev->ports_lock, flags); + list_for_each_entry(port, &portdev->ports, list) if (port->in_vq == vq || port->out_vq == vq) goto out; - } port = NULL; out: - spin_unlock_irqrestore(&pdrvdata_lock, flags); + spin_unlock_irqrestore(&portdev->ports_lock, flags); return port; } +static bool is_console_port(struct port *port) +{ + if (port->cons.hvc) + return true; + return false; +} + +static inline bool use_multiport(struct ports_device *portdev) +{ + /* + * This condition can be true when put_chars is called from + * early_init + */ + if (!portdev->vdev) + return 0; + return portdev->vdev->features[0] & (1 << VIRTIO_CONSOLE_F_MULTIPORT); +} + static void free_buf(struct port_buffer *buf) { kfree(buf->buf); @@ -233,6 +296,32 @@ static bool port_has_data(struct port *port) return ret; } +static ssize_t send_control_msg(struct port *port, unsigned int event, + unsigned int value) +{ + struct scatterlist sg[1]; + struct virtio_console_control cpkt; + struct virtqueue *vq; + int len; + + if (!use_multiport(port->portdev)) + return 0; + + cpkt.id = port->id; + cpkt.event = event; + cpkt.value = value; + + vq = port->portdev->c_ovq; + + sg_init_one(sg, &cpkt, sizeof(cpkt)); + if (vq->vq_ops->add_buf(vq, sg, 1, 0, &cpkt) >= 0) { + vq->vq_ops->kick(vq); + while (!vq->vq_ops->get_buf(vq, &len)) + cpu_relax(); + } + return 0; +} + static ssize_t send_buf(struct port *port, void *in_buf, size_t in_count) { struct scatterlist sg[1]; @@ -387,24 +476,7 @@ static void notifier_del_vio(struct hvc_struct *hp, int data) hp->irq_requested = 0; } -static void hvc_handle_input(struct virtqueue *vq) -{ - struct port *port; - unsigned long flags; - - port = find_port_by_vq(vq->vdev->priv, vq); - if (!port) - return; - - spin_lock_irqsave(&port->inbuf_lock, flags); - port->inbuf = get_inbuf(port); - spin_unlock_irqrestore(&port->inbuf_lock, flags); - - if (hvc_poll(port->cons.hvc)) - hvc_kick(); -} - -/* The operations for the console. */ +/* The operations for console ports. */ static const struct hv_ops hv_ops = { .get_chars = get_chars, .put_chars = put_chars, @@ -428,7 +500,7 @@ int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int)) return hvc_instantiate(0, 0, &hv_ops); } -int __devinit init_port_console(struct port *port) +int init_port_console(struct port *port) { int ret; @@ -465,7 +537,122 @@ int __devinit init_port_console(struct port *port) return 0; } -static int __devinit add_port(struct ports_device *portdev) +/* Any private messages that the Host and Guest want to share */ +static void handle_control_message(struct ports_device *portdev, + struct port_buffer *buf) +{ + struct virtio_console_control *cpkt; + struct port *port; + + cpkt = (struct virtio_console_control *)(buf->buf + buf->offset); + + port = find_port_by_id(portdev, cpkt->id); + if (!port) { + /* No valid header at start of buffer. Drop it. */ + dev_dbg(&portdev->vdev->dev, + "Invalid index %u in control packet\n", cpkt->id); + return; + } + + switch (cpkt->event) { + case VIRTIO_CONSOLE_CONSOLE_PORT: + if (!cpkt->value) + break; + if (is_console_port(port)) + break; + + init_port_console(port); + /* + * Could remove the port here in case init fails - but + * have to notify the host first. + */ + break; + case VIRTIO_CONSOLE_RESIZE: + if (!is_console_port(port)) + break; + port->cons.hvc->irq_requested = 1; + resize_console(port); + break; + } +} + +static void control_work_handler(struct work_struct *work) +{ + struct ports_device *portdev; + struct virtqueue *vq; + struct port_buffer *buf; + unsigned int len; + + portdev = container_of(work, struct ports_device, control_work); + vq = portdev->c_ivq; + + spin_lock(&portdev->cvq_lock); + while ((buf = vq->vq_ops->get_buf(vq, &len))) { + spin_unlock(&portdev->cvq_lock); + + buf->len = len; + buf->offset = 0; + + handle_control_message(portdev, buf); + + spin_lock(&portdev->cvq_lock); + if (add_inbuf(portdev->c_ivq, buf) < 0) { + dev_warn(&portdev->vdev->dev, + "Error adding buffer to queue\n"); + free_buf(buf); + } + } + spin_unlock(&portdev->cvq_lock); +} + +static void in_intr(struct virtqueue *vq) +{ + struct port *port; + unsigned long flags; + + port = find_port_by_vq(vq->vdev->priv, vq); + if (!port) + return; + + spin_lock_irqsave(&port->inbuf_lock, flags); + port->inbuf = get_inbuf(port); + + spin_unlock_irqrestore(&port->inbuf_lock, flags); + + if (is_console_port(port) && hvc_poll(port->cons.hvc)) + hvc_kick(); +} + +static void control_intr(struct virtqueue *vq) +{ + struct ports_device *portdev; + + portdev = vq->vdev->priv; + schedule_work(&portdev->control_work); +} + +static void fill_queue(struct virtqueue *vq, spinlock_t *lock) +{ + struct port_buffer *buf; + int ret; + + do { + buf = alloc_buf(PAGE_SIZE); + if (!buf) + break; + + spin_lock_irq(lock); + ret = add_inbuf(vq, buf); + if (ret < 0) { + spin_unlock_irq(lock); + free_buf(buf); + break; + } + spin_unlock_irq(lock); + } while (ret > 0); +} + +static int add_port(struct ports_device *portdev, u32 id) { struct port *port; struct port_buffer *inbuf; @@ -478,11 +665,13 @@ static int __devinit add_port(struct ports_device *portdev) } port->portdev = portdev; + port->id = id; port->inbuf = NULL; + port->cons.hvc = NULL; - port->in_vq = portdev->in_vqs[0]; - port->out_vq = portdev->out_vqs[0]; + port->in_vq = portdev->in_vqs[port->id]; + port->out_vq = portdev->out_vqs[port->id]; spin_lock_init(&port->inbuf_lock); @@ -495,9 +684,25 @@ static int __devinit add_port(struct ports_device *portdev) /* Register the input buffer the first time. */ add_inbuf(port->in_vq, inbuf); - err = init_port_console(port); - if (err) - goto free_inbuf; + /* + * If we're not using multiport support, this has to be a console port + */ + if (!use_multiport(port->portdev)) { + err = init_port_console(port); + if (err) + goto free_inbuf; + } + + spin_lock_irq(&portdev->ports_lock); + list_add_tail(&port->list, &port->portdev->ports); + spin_unlock_irq(&portdev->ports_lock); + + /* + * Tell the Host we're set so that it can send us various + * configuration parameters for this port (eg, port name, + * caching, whether this is a console port, etc.) + */ + send_control_msg(port, VIRTIO_CONSOLE_PORT_READY, 1); return 0; @@ -514,12 +719,11 @@ static int init_vqs(struct ports_device *portdev) vq_callback_t **io_callbacks; char **io_names; struct virtqueue **vqs; - u32 nr_ports, nr_queues; + u32 i, j, nr_ports, nr_queues; int err; - /* We currently only have one port and two queues for that port */ - nr_ports = 1; - nr_queues = 2; + nr_ports = portdev->config.max_nr_ports; + nr_queues = use_multiport(portdev) ? (nr_ports + 1) * 2 : 2; vqs = kmalloc(nr_queues * sizeof(struct virtqueue *), GFP_KERNEL); if (!vqs) { @@ -549,11 +753,32 @@ static int init_vqs(struct ports_device *portdev) goto free_invqs; } - io_callbacks[0] = hvc_handle_input; - io_callbacks[1] = NULL; - io_names[0] = "input"; - io_names[1] = "output"; - + /* + * For backward compat (newer host but older guest), the host + * spawns a console port first and also inits the vqs for port + * 0 before others. + */ + j = 0; + io_callbacks[j] = in_intr; + io_callbacks[j + 1] = NULL; + io_names[j] = "input"; + io_names[j + 1] = "output"; + j += 2; + + if (use_multiport(portdev)) { + io_callbacks[j] = control_intr; + io_callbacks[j + 1] = NULL; + io_names[j] = "control-i"; + io_names[j + 1] = "control-o"; + + for (i = 1; i < nr_ports; i++) { + j += 2; + io_callbacks[j] = in_intr; + io_callbacks[j + 1] = NULL; + io_names[j] = "input"; + io_names[j + 1] = "output"; + } + } /* Find the queues. */ err = portdev->vdev->config->find_vqs(portdev->vdev, nr_queues, vqs, io_callbacks, @@ -561,9 +786,20 @@ static int init_vqs(struct ports_device *portdev) if (err) goto free_outvqs; + j = 0; portdev->in_vqs[0] = vqs[0]; portdev->out_vqs[0] = vqs[1]; - + j += 2; + if (use_multiport(portdev)) { + portdev->c_ivq = vqs[j]; + portdev->c_ovq = vqs[j + 1]; + + for (i = 1; i < nr_ports; i++) { + j += 2; + portdev->in_vqs[i] = vqs[j]; + portdev->out_vqs[i] = vqs[j + 1]; + } + } kfree(io_callbacks); kfree(io_names); kfree(vqs); @@ -587,11 +823,17 @@ fail: /* * Once we're further in boot, we get probed like any other virtio * device. + * + * If the host also supports multiple console ports, we check the + * config space to see how many ports the host has spawned. We + * initialize each port found. */ static int __devinit virtcons_probe(struct virtio_device *vdev) { struct ports_device *portdev; + u32 i; int err; + bool multiport; portdev = kmalloc(sizeof(*portdev), GFP_KERNEL); if (!portdev) { @@ -603,16 +845,53 @@ static int __devinit virtcons_probe(struct virtio_device *vdev) portdev->vdev = vdev; vdev->priv = portdev; + multiport = false; + portdev->config.nr_ports = 1; + portdev->config.max_nr_ports = 1; + if (virtio_has_feature(vdev, VIRTIO_CONSOLE_F_MULTIPORT)) { + multiport = true; + vdev->features[0] |= 1 << VIRTIO_CONSOLE_F_MULTIPORT; + + vdev->config->get(vdev, offsetof(struct virtio_console_config, + nr_ports), + &portdev->config.nr_ports, + sizeof(portdev->config.nr_ports)); + vdev->config->get(vdev, offsetof(struct virtio_console_config, + max_nr_ports), + &portdev->config.max_nr_ports, + sizeof(portdev->config.max_nr_ports)); + if (portdev->config.nr_ports > portdev->config.max_nr_ports) { + dev_warn(&vdev->dev, + "More ports (%u) specified than allowed (%u). Will init %u ports.", + portdev->config.nr_ports, + portdev->config.max_nr_ports, + portdev->config.max_nr_ports); + + portdev->config.nr_ports = portdev->config.max_nr_ports; + } + } + + /* Let the Host know we support multiple ports.*/ + vdev->config->finalize_features(vdev); + err = init_vqs(portdev); if (err < 0) { dev_err(&vdev->dev, "Error %d initializing vqs\n", err); goto free; } - /* We only have one port. */ - err = add_port(portdev); - if (err) - goto free_vqs; + spin_lock_init(&portdev->ports_lock); + INIT_LIST_HEAD(&portdev->ports); + + if (multiport) { + spin_lock_init(&portdev->cvq_lock); + INIT_WORK(&portdev->control_work, &control_work_handler); + + fill_queue(portdev->c_ivq, &portdev->cvq_lock); + } + + for (i = 0; i < portdev->config.nr_ports; i++) + add_port(portdev, i); /* Start using the new console output. */ early_put_chars = NULL; @@ -635,6 +914,7 @@ static struct virtio_device_id id_table[] = { static unsigned int features[] = { VIRTIO_CONSOLE_F_SIZE, + VIRTIO_CONSOLE_F_MULTIPORT, }; static struct virtio_driver virtio_console = { diff --git a/include/linux/virtio_console.h b/include/linux/virtio_console.h index 9e0da40beae0..f4d183b5b493 100644 --- a/include/linux/virtio_console.h +++ b/include/linux/virtio_console.h @@ -6,18 +6,39 @@ /* * This header, excluding the #ifdef __KERNEL__ part, is BSD licensed so * anyone can use the definitions to implement compatible drivers/servers. + * + * Copyright (C) Red Hat, Inc., 2009, 2010 */ /* Feature bits */ #define VIRTIO_CONSOLE_F_SIZE 0 /* Does host provide console size? */ +#define VIRTIO_CONSOLE_F_MULTIPORT 1 /* Does host provide multiple ports? */ struct virtio_console_config { /* colums of the screens */ __u16 cols; /* rows of the screens */ __u16 rows; + /* max. number of ports this device can hold */ + __u32 max_nr_ports; + /* number of ports added so far */ + __u32 nr_ports; } __attribute__((packed)); +/* + * A message that's passed between the Host and the Guest for a + * particular port. + */ +struct virtio_console_control { + __u32 id; /* Port number */ + __u16 event; /* The kind of control event (see below) */ + __u16 value; /* Extra information for the key */ +}; + +/* Some events for control messages */ +#define VIRTIO_CONSOLE_PORT_READY 0 +#define VIRTIO_CONSOLE_CONSOLE_PORT 1 +#define VIRTIO_CONSOLE_RESIZE 2 #ifdef __KERNEL__ int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int)); -- cgit v1.2.3 From 2030fa496d74b49220308eaccf656e2338019cfd Mon Sep 17 00:00:00 2001 From: Amit Shah Date: Mon, 21 Dec 2009 21:49:30 +0530 Subject: 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 Signed-off-by: Rusty Russell --- drivers/char/virtio_console.c | 164 ++++++++++++++++++++++++++++++++++++++++- include/linux/virtio_console.h | 1 + 2 files changed, 164 insertions(+), 1 deletion(-) (limited to 'include/linux/virtio_console.h') 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 #include #include +#include #include #include +#include +#include #include #include #include +#include #include #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/vportp + */ +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)); -- cgit v1.2.3 From 431edb8a8bca71008fefceadf53b9315ef7196ec Mon Sep 17 00:00:00 2001 From: Amit Shah Date: Mon, 21 Dec 2009 21:57:40 +0530 Subject: virtio: console: Register with sysfs and create a 'name' attribute for ports The host can set a name for ports so that they're easily discoverable instead of going by the /dev/vportNpn naming. This attribute will be placed in /sys/class/virtio-ports/vportNpn/name. udev scripts can then create symlinks to the port using the name. Signed-off-by: Amit Shah Signed-off-by: Rusty Russell --- drivers/char/virtio_console.c | 57 ++++++++++++++++++++++++++++++++++++++++++ include/linux/virtio_console.h | 1 + 2 files changed, 58 insertions(+) (limited to 'include/linux/virtio_console.h') diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index 5e3503a31312..99d182b132c4 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -170,6 +170,9 @@ struct port { /* A waitqueue for poll() or blocking read operations */ wait_queue_head_t waitqueue; + /* The 'name' of the port that we expose via sysfs properties */ + char *name; + /* The 'id' to identify the port with the Host */ u32 id; @@ -732,12 +735,36 @@ int init_port_console(struct port *port) return 0; } +static ssize_t show_port_name(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + struct port *port; + + port = dev_get_drvdata(dev); + + return sprintf(buffer, "%s\n", port->name); +} + +static DEVICE_ATTR(name, S_IRUGO, show_port_name, NULL); + +static struct attribute *port_sysfs_entries[] = { + &dev_attr_name.attr, + NULL +}; + +static struct attribute_group port_attribute_group = { + .name = NULL, /* put in device directory */ + .attrs = port_sysfs_entries, +}; + /* Any private messages that the Host and Guest want to share */ static void handle_control_message(struct ports_device *portdev, struct port_buffer *buf) { struct virtio_console_control *cpkt; struct port *port; + size_t name_size; + int err; cpkt = (struct virtio_console_control *)(buf->buf + buf->offset); @@ -772,6 +799,35 @@ static void handle_control_message(struct ports_device *portdev, port->host_connected = cpkt->value; wake_up_interruptible(&port->waitqueue); break; + case VIRTIO_CONSOLE_PORT_NAME: + /* + * Skip the size of the header and the cpkt to get the size + * of the name that was sent + */ + name_size = buf->len - buf->offset - sizeof(*cpkt) + 1; + + port->name = kmalloc(name_size, GFP_KERNEL); + if (!port->name) { + dev_err(port->dev, + "Not enough space to store port name\n"); + break; + } + strncpy(port->name, buf->buf + buf->offset + sizeof(*cpkt), + name_size - 1); + port->name[name_size - 1] = 0; + + /* + * Since we only have one sysfs attribute, 'name', + * create it only if we have a name for the port. + */ + err = sysfs_create_group(&port->dev->kobj, + &port_attribute_group); + if (err) + dev_err(port->dev, + "Error %d creating sysfs device attributes\n", + err); + + break; } } @@ -869,6 +925,7 @@ static int add_port(struct ports_device *portdev, u32 id) port->portdev = portdev; port->id = id; + port->name = NULL; port->inbuf = NULL; port->cons.hvc = NULL; diff --git a/include/linux/virtio_console.h b/include/linux/virtio_console.h index bd0e2a596f93..1ebf007812a8 100644 --- a/include/linux/virtio_console.h +++ b/include/linux/virtio_console.h @@ -40,6 +40,7 @@ struct virtio_console_control { #define VIRTIO_CONSOLE_CONSOLE_PORT 1 #define VIRTIO_CONSOLE_RESIZE 2 #define VIRTIO_CONSOLE_PORT_OPEN 3 +#define VIRTIO_CONSOLE_PORT_NAME 4 #ifdef __KERNEL__ int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int)); -- cgit v1.2.3 From 1f7aa42d166cd104b0700d61efe2064178a3f6da Mon Sep 17 00:00:00 2001 From: Amit Shah Date: Mon, 21 Dec 2009 22:27:31 +0530 Subject: virtio: console: Add ability to hot-unplug ports Remove port data; deregister from the hvc core if it's a console port. Signed-off-by: Amit Shah Signed-off-by: Rusty Russell --- drivers/char/virtio_console.c | 65 ++++++++++++++++++++++++++++++++++++++++-- include/linux/virtio_console.h | 1 + 2 files changed, 64 insertions(+), 2 deletions(-) (limited to 'include/linux/virtio_console.h') diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index 7c53f58c87ba..9f20fda9c56f 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -783,6 +783,36 @@ static struct attribute_group port_attribute_group = { .attrs = port_sysfs_entries, }; +/* Remove all port-specific data. */ +static int remove_port(struct port *port) +{ + spin_lock_irq(&port->portdev->ports_lock); + list_del(&port->list); + spin_unlock_irq(&port->portdev->ports_lock); + + if (is_console_port(port)) { + spin_lock_irq(&pdrvdata_lock); + list_del(&port->cons.list); + spin_unlock_irq(&pdrvdata_lock); + hvc_remove(port->cons.hvc); + } + if (port->guest_connected) + send_control_msg(port, VIRTIO_CONSOLE_PORT_OPEN, 0); + + while (port->in_vq->vq_ops->detach_unused_buf(port->in_vq)) + ; + + sysfs_remove_group(&port->dev->kobj, &port_attribute_group); + device_destroy(pdrvdata.class, port->dev->devt); + cdev_del(&port->cdev); + + discard_port_data(port); + kfree(port->name); + + kfree(port); + return 0; +} + /* Any private messages that the Host and Guest want to share */ static void handle_control_message(struct ports_device *portdev, struct port_buffer *buf) @@ -854,6 +884,32 @@ static void handle_control_message(struct ports_device *portdev, err); break; + case VIRTIO_CONSOLE_PORT_REMOVE: + /* + * Hot unplug the port. We don't decrement nr_ports + * since we don't want to deal with extra complexities + * of using the lowest-available port id: We can just + * pick up the nr_ports number as the id and not have + * userspace send it to us. This helps us in two + * ways: + * + * - We don't need to have a 'port_id' field in the + * config space when a port is hot-added. This is a + * good thing as we might queue up multiple hotplug + * requests issued in our workqueue. + * + * - Another way to deal with this would have been to + * use a bitmap of the active ports and select the + * lowest non-active port from that map. That + * bloats the already tight config space and we + * would end up artificially limiting the + * max. number of ports to sizeof(bitmap). Right + * now we can support 2^32 ports (as the port id is + * stored in a u32 type). + * + */ + remove_port(port); + break; } } @@ -1078,12 +1134,17 @@ static void config_work_handler(struct work_struct *work) /* * Port 0 got hot-added. Since we already did all the * other initialisation for it, just tell the Host - * that the port is ready. + * that the port is ready if we find the port. In + * case the port was hot-removed earlier, we call + * add_port to add the port. */ struct port *port; port = find_port_by_id(portdev, 0); - send_control_msg(port, VIRTIO_CONSOLE_PORT_READY, 1); + if (!port) + add_port(portdev, 0); + else + send_control_msg(port, VIRTIO_CONSOLE_PORT_READY, 1); return; } if (virtconconf.nr_ports > portdev->config.max_nr_ports) { diff --git a/include/linux/virtio_console.h b/include/linux/virtio_console.h index 1ebf007812a8..ae4f039515b4 100644 --- a/include/linux/virtio_console.h +++ b/include/linux/virtio_console.h @@ -41,6 +41,7 @@ struct virtio_console_control { #define VIRTIO_CONSOLE_RESIZE 2 #define VIRTIO_CONSOLE_PORT_OPEN 3 #define VIRTIO_CONSOLE_PORT_NAME 4 +#define VIRTIO_CONSOLE_PORT_REMOVE 5 #ifdef __KERNEL__ int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int)); -- cgit v1.2.3