From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1MEkhN-00041c-1o for qemu-devel@nongnu.org; Thu, 11 Jun 2009 09:52:09 -0400 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1MEkhG-0003u3-2p for qemu-devel@nongnu.org; Thu, 11 Jun 2009 09:52:06 -0400 Received: from [199.232.76.173] (port=39691 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1MEkhF-0003tc-O4 for qemu-devel@nongnu.org; Thu, 11 Jun 2009 09:52:01 -0400 Received: from mx2.redhat.com ([66.187.237.31]:58041) by monty-python.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1MEkhE-0007h0-VT for qemu-devel@nongnu.org; Thu, 11 Jun 2009 09:52:01 -0400 Date: Thu, 11 Jun 2009 19:21:55 +0530 From: Amit Shah Subject: Re: [Qemu-devel] [PATCH] virtio-serial: PCI device for simple host <-> guest communication Message-ID: <20090611135155.GA13426@amit-x200.pnq.redhat.com> References: <1244565768-9103-1-git-send-email-amit.shah@redhat.com> <1244565768-9103-2-git-send-email-amit.shah@redhat.com> <1244565768-9103-3-git-send-email-amit.shah@redhat.com> <1244565768-9103-4-git-send-email-amit.shah@redhat.com> <20090609181714.GB11485@amd.home.annexia.org> <4A2EB079.6020909@codemonkey.ws> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <4A2EB079.6020909@codemonkey.ws> List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: Anthony Liguori Cc: "Richard W.M. Jones" , qemu-devel@nongnu.org On (Tue) Jun 09 2009 [13:56:57], Anthony Liguori wrote: > Richard W.M. Jones wrote: >> On Tue, Jun 09, 2009 at 10:12:48PM +0530, Amit Shah wrote: >> [...] >> >> Are you going to post the virtio driver for the Linux kernel later? >> > > Yeah, I'd like to see the Linux driver before drawing conclusions. Pasting here a slightly older version of the patch. I'm actively working on it; a few things might have changed but the basic idea remains the same. Amit diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 735bbe2..a023346 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -666,6 +666,12 @@ config VIRTIO_CONSOLE help Virtio console for use with lguest and other hypervisors. +config VIRTIO_SERIAL + tristate "Virtio serial" + select VIRTIO + select VIRTIO_RING + help + Virtio serial device driver for simple guest and host communication config HVCS tristate "IBM Hypervisor Virtual Console Server support" diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 9caf5b5..e5bee21 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -53,6 +53,7 @@ obj-$(CONFIG_HVC_XEN) += hvc_xen.o obj-$(CONFIG_HVC_IUCV) += hvc_iucv.o obj-$(CONFIG_HVC_UDBG) += hvc_udbg.o obj-$(CONFIG_VIRTIO_CONSOLE) += virtio_console.o +obj-$(CONFIG_VIRTIO_SERIAL) += virtio_serial.o obj-$(CONFIG_RAW_DRIVER) += raw.o obj-$(CONFIG_SGI_SNSC) += snsc.o snsc_event.o obj-$(CONFIG_MSPEC) += mspec.o diff --git a/drivers/char/virtio_serial.c b/drivers/char/virtio_serial.c new file mode 100644 index 0000000..0a5e90a --- /dev/null +++ b/drivers/char/virtio_serial.c @@ -0,0 +1,531 @@ +/* + * VirtIO Serial driver + * + * This is paravirtualised serial guest<->host communication channel + * for relaying short messages and events in either direction. + * + * One PCI device can have multiple serial ports so that different + * applications can communicate without polluting the PCI device space + * in the guest. + * + * Copyright (C) 2009, Red Hat, Inc. + * + * Author(s): Amit Shah + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +struct virtio_serial_struct { + struct virtio_device *vdev; + struct virtqueue *ctrl_vq; + struct virtio_serial_port *ports; + u8 nr_devs; /* number of devices we can open */ +}; + +struct virtio_serial_port { + struct virtio_serial_struct *virtserial; + struct virtqueue *in_vq, *out_vq; + + /* The name given to this channel, if any. NOT zero-terminated */ + char *name; + + /* Each port associates with a separate char device */ + dev_t dev; + struct cdev cdev; + +#define RECV_BUFFERS_PER_PORT 1 + char *pages[RECV_BUFFERS_PER_PORT]; + struct scatterlist *recv_sg; + unsigned int read_len; + char *read_buf; +}; + +static struct virtio_serial_struct virtserial; + +/* This should become per-port data */ +static DECLARE_COMPLETION(have_data); + +static int major = 60; /* from the experimental range */ + +static struct virtio_serial_port *get_port_from_idx(u8 idx) +{ + if (idx > virtserial.nr_devs || idx < 0) + return NULL; + + return &virtserial.ports[idx]; +} + +static int get_id_from_vq(struct virtqueue *vq) +{ + int i; + + for (i = 0; i < virtserial.nr_devs; i++) { + if (virtserial.ports[i].in_vq == vq + || virtserial.ports[i].out_vq == vq) + return i; + } + return -1; +} + +static struct virtio_serial_port *get_port_from_vq(struct virtqueue *vq) +{ + int idx; + + idx = get_id_from_vq(vq); + if (idx < 0) + return NULL; + return &virtserial.ports[idx]; +} + +static void receive_data(struct virtqueue *vq) +{ + struct virtio_serial_port *port; + + port = get_port_from_vq(vq); + + /* We can get spurious callbacks, e.g. shared IRQs + virtio_pci. */ + port->read_buf = vq->vq_ops->get_buf(vq, &port->read_len); + if (!port->read_buf) + return; + + complete(&have_data); +} + +static int receive_control_response(struct virtqueue *vq) +{ + int ret; + char *data; + uint8_t idx; + uint32_t key; + unsigned int len; + struct virtio_serial_port *port; + + /* We can get spurious callbacks, e.g. shared IRQs + virtio_pci. */ + data = vq->vq_ops->get_buf(vq, &len); + if (!data) + return 0; + + memcpy(&key, data, 4); + ret = -EINVAL; + switch (key) { + case VIRTIO_SERIAL_GET_PORT_NAME: + if (len < 6) { + /* Not enough data for this ioctl: + * key (4 bytes) + idx (1 byte) + port name + */ + /* XXX: return something else? */ + ret = -EIO; + break; + } + memcpy(&idx, data + 4, 1); + + port = get_port_from_idx(idx); + if (!port) { + ret = -EINVAL; + break; + } + len -= 5; + if (len > VIRTIO_SERIAL_NAME_MAX_LEN) + len = VIRTIO_SERIAL_NAME_MAX_LEN; + + /* If the port is getting renamed */ + kfree(port->name); + + port->name = kzalloc(len, GFP_KERNEL); + if (!port->name) { + ret = -ENOMEM; + break; + } + memcpy(port->name, data + 5, len); + ret = len; + break; + } + return ret; +} + +static int request_control_info(u32 key, u8 idx) +{ + int ret; + char *vbuf, *obuf; + struct scatterlist sg[2]; + + vbuf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!vbuf) + return -ENOMEM; + obuf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!obuf) + return -ENOMEM; + + memcpy(vbuf, &key, 4); + memcpy(vbuf + 4, &idx, 1); + + sg_init_table(sg, 2); + sg_set_buf(&sg[0], vbuf, PAGE_SIZE); + sg_set_buf(&sg[1], obuf, PAGE_SIZE); + + if (virtserial.ctrl_vq->vq_ops->add_buf(virtserial.ctrl_vq, sg, 1, 1, + obuf) == 0) { + /* Tell Host to go! */ + virtserial.ctrl_vq->vq_ops->kick(virtserial.ctrl_vq); + + /* Chill out until it's done with the buffer. */ + ret = receive_control_response(virtserial.ctrl_vq); + while (ret == 0) { + cpu_relax(); + ret = receive_control_response(virtserial.ctrl_vq); + } + } + kfree(vbuf); + kfree(obuf); + + return ret; +} + + +static ssize_t virtserial_read(struct file *filp, char __user *ubuf, + size_t count, loff_t *offp) +{ + struct virtqueue *in_vq; + struct virtio_serial_port *port; + ssize_t ret; + + port = filp->private_data; + in_vq = port->in_vq; + + if (port->read_len == 0) + return 0; + + if (count > port->read_len) + count = port->read_len; + + ret = copy_to_user(ubuf, port->read_buf, count); + + /* Return the number of bytes actually copied */ + ret = count - ret; + + port->read_buf += ret; + port->read_len -= ret; + + if (!port->read_len) { + /* FIXME: Assuming only one page per port */ + in_vq->vq_ops->add_buf(in_vq, port->recv_sg, 0, 1, + port->pages[0]); + /* FIXME: it's a bug if add_buf didn't succeed */ + in_vq->vq_ops->kick(in_vq); + } + return ret; +} + +static ssize_t virtserial_write(struct file *filp, const char __user *ubuf, + size_t count, loff_t *offp) +{ + ssize_t ret; + char *vbuf; + unsigned int len, size; + struct virtqueue *out_vq; + struct scatterlist sg[1]; + struct virtio_serial_port *port; + + size = min(count, PAGE_SIZE); + vbuf = kmalloc(size, GFP_KERNEL); + if (!vbuf) + return -EFBIG; + ret = copy_from_user(vbuf, ubuf, size); + + /* Return the number of bytes actually written */ + ret = size - ret; + + port = filp->private_data; + out_vq = port->out_vq; + + sg_init_one(sg, vbuf, ret); + + /* add_buf wants a token to identify this buffer: we hand it any + * non-NULL pointer, since there's only ever one buffer. + */ + if (out_vq->vq_ops->add_buf(out_vq, sg, 1, 0, (void *)1) == 0) { + /* Tell Host to go! */ + out_vq->vq_ops->kick(out_vq); + /* Chill out until it's done with the buffer. */ + while (!out_vq->vq_ops->get_buf(out_vq, &len)) + cpu_relax(); + } + kfree(vbuf); + + /* We're expected to return the amount of data we wrote */ + return ret; +} + +static long virtserial_ioctl(struct file *filp, unsigned int ioctl, + unsigned long arg) +{ + int ret; + struct virtio_serial_port *port; + struct virtio_serial_port_name *port_name; + + port = filp->private_data; + + ret = -EINVAL; + switch (ioctl) { + case VIRTIO_SERIAL_IOCTL_GET_PORT_NAME: + port_name = (struct virtio_serial_port_name *)arg; + + if (!port->name) { + ret = request_control_info(VIRTIO_SERIAL_GET_PORT_NAME, + get_id_from_vq(port->in_vq)); + if (ret < 0) { + /* Of IOCTL error return codes, only + * this one comes close to what has + * happened: either out of memory or + * the virtio-serial backend didn't + * have the associated name for the + * port + */ + ret = -EFAULT; + break; + } + } + ret = copy_to_user(port_name->name, + port->name, VIRTIO_SERIAL_NAME_MAX_LEN); + if (ret < 0) { + ret = -EINVAL; + break; + } + if (ret > 0) { + /* Do something? -- There is still data to be + * copied to userspace + */ + ret = 0; + } + break; + } + return ret; +} + +static int virtserial_release(struct inode *inode, struct file *filp) +{ + printk(KERN_NOTICE "%s\n", __func__); + return 0; +} + +static int virtserial_open(struct inode *inode, struct file *filp) +{ + struct cdev *cdev = inode->i_cdev; + struct virtio_serial_port *port; + + port = container_of(cdev, struct virtio_serial_port, + cdev); + + filp->private_data = port; + return 0; +} + +static unsigned int virtserial_poll(struct file *filp, poll_table *wait) +{ + printk(KERN_NOTICE "%s\n", __func__); + return 0; +} + +static const struct file_operations virtserial_fops = { + .owner = THIS_MODULE, + .open = virtserial_open, + .read = virtserial_read, + .write = virtserial_write, + .compat_ioctl = virtserial_ioctl, + .unlocked_ioctl = virtserial_ioctl, + .poll = virtserial_poll, + .release = virtserial_release, +}; + +static int port_setup_recv_buffers(struct virtio_serial_port *port) +{ + int ret; + int i, nr_buffers = RECV_BUFFERS_PER_PORT; + struct virtqueue *vq = port->in_vq; + + ret = -ENOMEM; + port->recv_sg = kzalloc(sizeof(struct scatterlist) * nr_buffers, + GFP_KERNEL); + if (!port->recv_sg) + return ret; + + sg_init_table(port->recv_sg, nr_buffers); + + for (i = 0; i < nr_buffers; i++) { + port->pages[i] = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!port->pages[i]) + goto free_pages; + + sg_set_buf(&port->recv_sg[i], port->pages[i], PAGE_SIZE); + ret = vq->vq_ops->add_buf(vq, port->recv_sg, 0, 1, + port->pages[i]); + if (ret < 0) + goto free_pages; + } + vq->vq_ops->kick(vq); + + return 0; + +free_pages: + for (; i >= 0; i--) + kfree(port->pages[i]); + kfree(port->recv_sg); + + return ret; +} + +static int virtserial_probe(struct virtio_device *vdev) +{ + int i, ret; + struct virtio_serial_port *port; + struct virtio_serial_config virtsercfg; + + vdev->config->get(vdev, offsetof(struct virtio_serial_config, nr_ports), + &virtsercfg.nr_ports, sizeof(virtsercfg.nr_ports)); + + virtserial.vdev = vdev; + virtserial.nr_devs = virtsercfg.nr_ports; + virtserial.ports = kzalloc(sizeof(struct virtio_serial_port) + * virtserial.nr_devs, GFP_KERNEL); + if (!virtserial.ports) { + ret = -ENOMEM; + goto fail; + } + + virtserial.ctrl_vq = vdev->config->find_vq(vdev, 0, NULL); + for (i = 0; i < virtserial.nr_devs; i++) { + port = &virtserial.ports[i]; + + port->in_vq = vdev->config->find_vq(vdev, (i * 2) + 1, + receive_data); + if (IS_ERR(port->in_vq)) { + ret = PTR_ERR(port->in_vq); + goto fail; + } + port->out_vq = vdev->config->find_vq(vdev, (i * 2) + 2, NULL); + if (IS_ERR(port->out_vq)) { + ret = PTR_ERR(port->out_vq); + goto free_in_vq; + } + cdev_init(&port->cdev, &virtserial_fops); + port->dev = MKDEV(major, i); + + /* XXX Do this for each port or do it once? */ + ret = register_chrdev_region(port->dev, 1, "virtio-serial"); + if (ret < 0) { + printk(KERN_ERR "%s: can't register chrdev region\n", + __func__); + goto free_out_vq; + } + ret = cdev_add(&port->cdev, port->dev, 1); + if (ret < 0) { + printk(KERN_ERR "%s: can't add cdev\n", __func__); + goto free_cdev_region; + } + + /* Finally set up some receive buffers */ + ret = port_setup_recv_buffers(port); + if (ret < 0) + printk(KERN_NOTICE "%s: err for setup_recv_buff\n", __func__); + } + return 0; + +/* Rehaul this */ +free_cdev_region: + unregister_chrdev_region(port->dev, 1); +free_out_vq: + vdev->config->del_vq(port->out_vq); +free_in_vq: + vdev->config->del_vq(port->in_vq); +fail: + return ret; +} + + +static void virtserial_remove(struct virtio_device *vdev) +{ + int i, j; + + vdev->config->del_vq(virtserial.ctrl_vq); + + for (i = 0; i < virtserial.nr_devs; i++) { + struct virtio_serial_port *port; + + port = &virtserial.ports[i]; + if (port->dev) { + /* FIXME: Take a long, hard look at this */ + unregister_chrdev_region(port->dev, 1); + cdev_del(&port->cdev); + + vdev->config->del_vq(port->out_vq); + vdev->config->del_vq(port->in_vq); + + kfree(port->name); + kfree(port->recv_sg); + for (j = 0; j < RECV_BUFFERS_PER_PORT; j++) + kfree(port->pages[j]); + } + } +} + +static void virtserial_apply_config(struct virtio_device *vdev) +{ + struct virtio_serial_config virtserconf; + + vdev->config->get(vdev, offsetof(struct virtio_serial_config, nr_ports), + &virtserconf.nr_ports, sizeof(virtserconf.nr_ports)); + printk(KERN_NOTICE "%s: %u ports found\n", __func__, virtserconf.nr_ports); +} + + +static struct virtio_device_id id_table[] = { + { VIRTIO_ID_SERIAL, VIRTIO_DEV_ANY_ID }, + { 0 }, +}; + +static struct virtio_driver virtio_serial = { + // .feature_table = features, + // .feature_table_size = ARRAY_SIZE(features), + .driver.name = KBUILD_MODNAME, + .driver.owner = THIS_MODULE, + .id_table = id_table, + .probe = virtserial_probe, + .remove = virtserial_remove, + .config_changed = virtserial_apply_config, +}; + +static int __init init(void) +{ + return register_virtio_driver(&virtio_serial); +} + +static void __exit fini(void) +{ + unregister_virtio_driver(&virtio_serial); +} +module_init(init); +module_exit(fini); + +MODULE_DEVICE_TABLE(virtio, id_table); +MODULE_DESCRIPTION("Virtio serial driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/virtio_serial.h b/include/linux/virtio_serial.h new file mode 100644 index 0000000..5528b11 --- /dev/null +++ b/include/linux/virtio_serial.h @@ -0,0 +1,34 @@ +#ifndef _LINUX_VIRTIO_SERIAL_H +#define _LINUX_VIRTIO_SERIAL_H +#include +#include + +/* Guest kernel - Host interface */ + +/* The ID for virtio serial */ +#define VIRTIO_ID_SERIAL 7 + +struct virtio_serial_config { + __u8 nr_ports; + __u16 status; +} __attribute__((packed)); + + +#define VIRTIO_SERIAL_NAME_MAX_LEN 30 + +/* Some defines for the control channel key */ +#define VIRTIO_SERIAL_GET_PORT_NAME 1 + +/* Guest kernel - Guest userspace interface */ + +/* IOCTL-related */ +#define VIRTIO_SERIAL_IO 0xAF + +struct virtio_serial_port_name { + char name[VIRTIO_SERIAL_NAME_MAX_LEN]; +}; + +#define VIRTIO_SERIAL_IOCTL_GET_PORT_NAME _IOWR(VIRTIO_SERIAL_IO, 0x00, \ + struct virtio_serial_port_name) + +#endif /* _LINUX_VIRTIO_SERIAL_H */