virtio: console: Check if port is valid in resize_console
[safe/jmp/linux-2.6] / drivers / char / virtio_console.c
index 9f20fda..44288ce 100644 (file)
@@ -17,6 +17,7 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 #include <linux/cdev.h>
+#include <linux/debugfs.h>
 #include <linux/device.h>
 #include <linux/err.h>
 #include <linux/fs.h>
@@ -43,6 +44,9 @@ struct ports_driver_data {
        /* Used for registering chardevs */
        struct class *class;
 
+       /* Used for exporting per-port information to debugfs */
+       struct dentry *debugfs_dir;
+
        /* Number of devices this driver is handling */
        unsigned int index;
 
@@ -158,6 +162,9 @@ struct port {
        /* The IO vqs for this port */
        struct virtqueue *in_vq, *out_vq;
 
+       /* File in the debugfs directory that exposes this port's information */
+       struct dentry *debugfs_file;
+
        /*
         * The entries in this struct will be valid if this port is
         * hooked up to an hvc console
@@ -323,6 +330,7 @@ static void discard_port_data(struct port *port)
        struct port_buffer *buf;
        struct virtqueue *vq;
        unsigned int len;
+       int ret;
 
        vq = port->in_vq;
        if (port->inbuf)
@@ -330,16 +338,18 @@ static void discard_port_data(struct port *port)
        else
                buf = vq->vq_ops->get_buf(vq, &len);
 
-       if (!buf)
-               return;
-
-       if (add_inbuf(vq, buf) < 0) {
-               buf->len = buf->offset = 0;
-               dev_warn(port->dev, "Error adding buffer back to vq\n");
-               return;
+       ret = 0;
+       while (buf) {
+               if (add_inbuf(vq, buf) < 0) {
+                       ret++;
+                       free_buf(buf);
+               }
+               buf = vq->vq_ops->get_buf(vq, &len);
        }
-
        port->inbuf = NULL;
+       if (ret)
+               dev_warn(port->dev, "Errors adding %d buffers back to vq\n",
+                        ret);
 }
 
 static bool port_has_data(struct port *port)
@@ -347,12 +357,19 @@ static bool port_has_data(struct port *port)
        unsigned long flags;
        bool ret;
 
-       ret = false;
        spin_lock_irqsave(&port->inbuf_lock, flags);
-       if (port->inbuf)
+       if (port->inbuf) {
+               ret = true;
+               goto out;
+       }
+       port->inbuf = get_inbuf(port);
+       if (port->inbuf) {
                ret = true;
+               goto out;
+       }
+       ret = false;
+out:
        spin_unlock_irqrestore(&port->inbuf_lock, flags);
-
        return ret;
 }
 
@@ -362,7 +379,7 @@ static ssize_t send_control_msg(struct port *port, unsigned int event,
        struct scatterlist sg[1];
        struct virtio_console_control cpkt;
        struct virtqueue *vq;
-       int len;
+       unsigned int len;
 
        if (!use_multiport(port->portdev))
                return 0;
@@ -664,6 +681,10 @@ static void resize_console(struct port *port)
        struct virtio_device *vdev;
        struct winsize ws;
 
+       /* The port could have been hot-unplugged */
+       if (!port)
+               return;
+
        vdev = port->portdev->vdev;
        if (virtio_has_feature(vdev, VIRTIO_CONSOLE_F_SIZE)) {
                vdev->config->get(vdev,
@@ -746,6 +767,8 @@ int init_port_console(struct port *port)
        port->cons.hvc = hvc_alloc(port->cons.vtermno, 0, &hv_ops, PAGE_SIZE);
        if (IS_ERR(port->cons.hvc)) {
                ret = PTR_ERR(port->cons.hvc);
+               dev_err(port->dev,
+                       "error %d allocating hvc for port\n", ret);
                port->cons.hvc = NULL;
                return ret;
        }
@@ -783,9 +806,54 @@ static struct attribute_group port_attribute_group = {
        .attrs = port_sysfs_entries,
 };
 
+static int debugfs_open(struct inode *inode, struct file *filp)
+{
+       filp->private_data = inode->i_private;
+       return 0;
+}
+
+static ssize_t debugfs_read(struct file *filp, char __user *ubuf,
+                           size_t count, loff_t *offp)
+{
+       struct port *port;
+       char *buf;
+       ssize_t ret, out_offset, out_count;
+
+       out_count = 1024;
+       buf = kmalloc(out_count, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       port = filp->private_data;
+       out_offset = 0;
+       out_offset += snprintf(buf + out_offset, out_count,
+                              "name: %s\n", port->name ? port->name : "");
+       out_offset += snprintf(buf + out_offset, out_count - out_offset,
+                              "guest_connected: %d\n", port->guest_connected);
+       out_offset += snprintf(buf + out_offset, out_count - out_offset,
+                              "host_connected: %d\n", port->host_connected);
+       out_offset += snprintf(buf + out_offset, out_count - out_offset,
+                              "is_console: %s\n",
+                              is_console_port(port) ? "yes" : "no");
+       out_offset += snprintf(buf + out_offset, out_count - out_offset,
+                              "console_vtermno: %u\n", port->cons.vtermno);
+
+       ret = simple_read_from_buffer(ubuf, count, offp, buf, out_offset);
+       kfree(buf);
+       return ret;
+}
+
+static const struct file_operations port_debugfs_ops = {
+       .owner = THIS_MODULE,
+       .open  = debugfs_open,
+       .read  = debugfs_read,
+};
+
 /* Remove all port-specific data. */
 static int remove_port(struct port *port)
 {
+       struct port_buffer *buf;
+
        spin_lock_irq(&port->portdev->ports_lock);
        list_del(&port->list);
        spin_unlock_irq(&port->portdev->ports_lock);
@@ -799,16 +867,21 @@ static int remove_port(struct port *port)
        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);
 
+       /* Remove unused data this port might have received. */
        discard_port_data(port);
+
+       /* Remove buffers we queued up for the Host to send us data in. */
+       while ((buf = port->in_vq->vq_ops->detach_unused_buf(port->in_vq)))
+               free_buf(buf);
+
        kfree(port->name);
 
+       debugfs_remove(port->debugfs_file);
+
        kfree(port);
        return 0;
 }
@@ -878,11 +951,18 @@ static void handle_control_message(struct ports_device *portdev,
                 */
                err = sysfs_create_group(&port->dev->kobj,
                                         &port_attribute_group);
-               if (err)
+               if (err) {
                        dev_err(port->dev,
                                "Error %d creating sysfs device attributes\n",
                                err);
-
+               } else {
+                       /*
+                        * Generate a udev event so that appropriate
+                        * symlinks can be created based on udev
+                        * rules.
+                        */
+                       kobject_uevent(&port->dev->kobj, KOBJ_CHANGE);
+               }
                break;
        case VIRTIO_CONSOLE_PORT_REMOVE:
                /*
@@ -952,7 +1032,8 @@ static void in_intr(struct virtqueue *vq)
                return;
 
        spin_lock_irqsave(&port->inbuf_lock, flags);
-       port->inbuf = get_inbuf(port);
+       if (!port->inbuf)
+               port->inbuf = get_inbuf(port);
 
        /*
         * Don't queue up data when port is closed.  This condition
@@ -998,11 +1079,13 @@ static void config_intr(struct virtio_device *vdev)
        resize_console(find_port_by_id(portdev, 0));
 }
 
-static void fill_queue(struct virtqueue *vq, spinlock_t *lock)
+static unsigned int fill_queue(struct virtqueue *vq, spinlock_t *lock)
 {
        struct port_buffer *buf;
+       unsigned int nr_added_bufs;
        int ret;
 
+       nr_added_bufs = 0;
        do {
                buf = alloc_buf(PAGE_SIZE);
                if (!buf)
@@ -1015,15 +1098,20 @@ static void fill_queue(struct virtqueue *vq, spinlock_t *lock)
                        free_buf(buf);
                        break;
                }
+               nr_added_bufs++;
                spin_unlock_irq(lock);
        } while (ret > 0);
+
+       return nr_added_bufs;
 }
 
 static int add_port(struct ports_device *portdev, u32 id)
 {
+       char debugfs_name[16];
        struct port *port;
-       struct port_buffer *inbuf;
+       struct port_buffer *buf;
        dev_t devt;
+       unsigned int nr_added_bufs;
        int err;
 
        port = kmalloc(sizeof(*port), GFP_KERNEL);
@@ -1067,22 +1155,21 @@ 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) {
+       /* Fill the in_vq with buffers so the host can send us data. */
+       nr_added_bufs = fill_queue(port->in_vq, &port->inbuf_lock);
+       if (!nr_added_bufs) {
+               dev_err(port->dev, "Error allocating inbufs\n");
                err = -ENOMEM;
                goto free_device;
        }
 
-       /* Register the input buffer the first time. */
-       add_inbuf(port->in_vq, 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;
+                       goto free_inbufs;
        }
 
        spin_lock_irq(&portdev->ports_lock);
@@ -1096,10 +1183,23 @@ static int add_port(struct ports_device *portdev, u32 id)
         */
        send_control_msg(port, VIRTIO_CONSOLE_PORT_READY, 1);
 
+       if (pdrvdata.debugfs_dir) {
+               /*
+                * Finally, create the debugfs file that we can use to
+                * inspect a port's state at any time
+                */
+               sprintf(debugfs_name, "vport%up%u",
+                       port->portdev->drv_index, id);
+               port->debugfs_file = debugfs_create_file(debugfs_name, 0444,
+                                                        pdrvdata.debugfs_dir,
+                                                        port,
+                                                        &port_debugfs_ops);
+       }
        return 0;
 
-free_inbuf:
-       free_buf(inbuf);
+free_inbufs:
+       while ((buf = port->in_vq->vq_ops->detach_unused_buf(port->in_vq)))
+               free_buf(buf);
 free_device:
        device_destroy(pdrvdata.class, port->dev->devt);
 free_cdev:
@@ -1354,11 +1454,19 @@ static int __devinit virtcons_probe(struct virtio_device *vdev)
        INIT_LIST_HEAD(&portdev->ports);
 
        if (multiport) {
+               unsigned int nr_added_bufs;
+
                spin_lock_init(&portdev->cvq_lock);
                INIT_WORK(&portdev->control_work, &control_work_handler);
                INIT_WORK(&portdev->config_work, &config_work_handler);
 
-               fill_queue(portdev->c_ivq, &portdev->cvq_lock);
+               nr_added_bufs = fill_queue(portdev->c_ivq, &portdev->cvq_lock);
+               if (!nr_added_bufs) {
+                       dev_err(&vdev->dev,
+                               "Error allocating buffers for control queue\n");
+                       err = -ENOMEM;
+                       goto free_vqs;
+               }
        }
 
        for (i = 0; i < portdev->config.nr_ports; i++)
@@ -1368,6 +1476,10 @@ static int __devinit virtcons_probe(struct virtio_device *vdev)
        early_put_chars = NULL;
        return 0;
 
+free_vqs:
+       vdev->config->del_vqs(vdev);
+       kfree(portdev->in_vqs);
+       kfree(portdev->out_vqs);
 free_chrdev:
        unregister_chrdev(portdev->chr_major, "virtio-portsdev");
 free:
@@ -1376,6 +1488,36 @@ fail:
        return err;
 }
 
+static void virtcons_remove(struct virtio_device *vdev)
+{
+       struct ports_device *portdev;
+       struct port *port, *port2;
+       struct port_buffer *buf;
+       unsigned int len;
+
+       portdev = vdev->priv;
+
+       cancel_work_sync(&portdev->control_work);
+       cancel_work_sync(&portdev->config_work);
+
+       list_for_each_entry_safe(port, port2, &portdev->ports, list)
+               remove_port(port);
+
+       unregister_chrdev(portdev->chr_major, "virtio-portsdev");
+
+       while ((buf = portdev->c_ivq->vq_ops->get_buf(portdev->c_ivq, &len)))
+               free_buf(buf);
+
+       while ((buf = portdev->c_ivq->vq_ops->detach_unused_buf(portdev->c_ivq)))
+               free_buf(buf);
+
+       vdev->config->del_vqs(vdev);
+       kfree(portdev->in_vqs);
+       kfree(portdev->out_vqs);
+
+       kfree(portdev);
+}
+
 static struct virtio_device_id id_table[] = {
        { VIRTIO_ID_CONSOLE, VIRTIO_DEV_ANY_ID },
        { 0 },
@@ -1393,6 +1535,7 @@ static struct virtio_driver virtio_console = {
        .driver.owner = THIS_MODULE,
        .id_table =     id_table,
        .probe =        virtcons_probe,
+       .remove =       virtcons_remove,
        .config_changed = config_intr,
 };
 
@@ -1406,11 +1549,27 @@ static int __init init(void)
                pr_err("Error %d creating virtio-ports class\n", err);
                return err;
        }
+
+       pdrvdata.debugfs_dir = debugfs_create_dir("virtio-ports", NULL);
+       if (!pdrvdata.debugfs_dir) {
+               pr_warning("Error %ld creating debugfs dir for virtio-ports\n",
+                          PTR_ERR(pdrvdata.debugfs_dir));
+       }
        INIT_LIST_HEAD(&pdrvdata.consoles);
 
        return register_virtio_driver(&virtio_console);
 }
+
+static void __exit fini(void)
+{
+       unregister_virtio_driver(&virtio_console);
+
+       class_destroy(pdrvdata.class);
+       if (pdrvdata.debugfs_dir)
+               debugfs_remove_recursive(pdrvdata.debugfs_dir);
+}
 module_init(init);
+module_exit(fini);
 
 MODULE_DEVICE_TABLE(virtio, id_table);
 MODULE_DESCRIPTION("Virtio console driver");