summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/char/virtio_console.c65
-rw-r--r--include/linux/virtio_console.h1
2 files changed, 64 insertions, 2 deletions
diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
index 7c53f58c87b..9f20fda9c56 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 1ebf007812a..ae4f039515b 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));