diff options
author | Amit Shah <amit.shah@redhat.com> | 2010-09-02 09:08:29 -0400 |
---|---|---|
committer | Rusty Russell <rusty@rustcorp.com.au> | 2010-10-21 03:14:03 -0400 |
commit | b353a6b821627053f82b4e7b907e824cb7a6879c (patch) | |
tree | a863bec4278a403bd63336b4f01b45794a987805 /drivers/char/virtio_console.c | |
parent | d22a69892bd8f29e3096f6f54c2c00d8aec2e796 (diff) |
virtio: console: Add reference counting for port struct
When a port got hot-unplugged, when a port was open, any file operation
after the unplugging resulted in a crash. This is fixed by ref-counting
the port structure, and releasing it only when the file is closed.
This splits the unplug operation in two parts: first marks the port
as unavailable, removes all the buffers in the vqs and removes the port
from the per-device list of ports. The second stage, invoked when all
references drop to zero, releases the chardev and frees all other memory.
Signed-off-by: Amit Shah <amit.shah@redhat.com>
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Diffstat (limited to 'drivers/char/virtio_console.c')
-rw-r--r-- | drivers/char/virtio_console.c | 80 |
1 files changed, 64 insertions, 16 deletions
diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index 8a9c140d19be..288701ccbf7a 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c | |||
@@ -187,6 +187,9 @@ struct port { | |||
187 | struct cdev *cdev; | 187 | struct cdev *cdev; |
188 | struct device *dev; | 188 | struct device *dev; |
189 | 189 | ||
190 | /* Reference-counting to handle port hot-unplugs and file operations */ | ||
191 | struct kref kref; | ||
192 | |||
190 | /* A waitqueue for poll() or blocking read operations */ | 193 | /* A waitqueue for poll() or blocking read operations */ |
191 | wait_queue_head_t waitqueue; | 194 | wait_queue_head_t waitqueue; |
192 | 195 | ||
@@ -725,6 +728,8 @@ static unsigned int port_fops_poll(struct file *filp, poll_table *wait) | |||
725 | return ret; | 728 | return ret; |
726 | } | 729 | } |
727 | 730 | ||
731 | static void remove_port(struct kref *kref); | ||
732 | |||
728 | static int port_fops_release(struct inode *inode, struct file *filp) | 733 | static int port_fops_release(struct inode *inode, struct file *filp) |
729 | { | 734 | { |
730 | struct port *port; | 735 | struct port *port; |
@@ -745,6 +750,16 @@ static int port_fops_release(struct inode *inode, struct file *filp) | |||
745 | reclaim_consumed_buffers(port); | 750 | reclaim_consumed_buffers(port); |
746 | spin_unlock_irq(&port->outvq_lock); | 751 | spin_unlock_irq(&port->outvq_lock); |
747 | 752 | ||
753 | /* | ||
754 | * Locks aren't necessary here as a port can't be opened after | ||
755 | * unplug, and if a port isn't unplugged, a kref would already | ||
756 | * exist for the port. Plus, taking ports_lock here would | ||
757 | * create a dependency on other locks taken by functions | ||
758 | * inside remove_port if we're the last holder of the port, | ||
759 | * creating many problems. | ||
760 | */ | ||
761 | kref_put(&port->kref, remove_port); | ||
762 | |||
748 | return 0; | 763 | return 0; |
749 | } | 764 | } |
750 | 765 | ||
@@ -757,6 +772,11 @@ static int port_fops_open(struct inode *inode, struct file *filp) | |||
757 | port = find_port_by_devt(cdev->dev); | 772 | port = find_port_by_devt(cdev->dev); |
758 | filp->private_data = port; | 773 | filp->private_data = port; |
759 | 774 | ||
775 | /* Prevent against a port getting hot-unplugged at the same time */ | ||
776 | spin_lock_irq(&port->portdev->ports_lock); | ||
777 | kref_get(&port->kref); | ||
778 | spin_unlock_irq(&port->portdev->ports_lock); | ||
779 | |||
760 | /* | 780 | /* |
761 | * Don't allow opening of console port devices -- that's done | 781 | * Don't allow opening of console port devices -- that's done |
762 | * via /dev/hvc | 782 | * via /dev/hvc |
@@ -791,6 +811,7 @@ static int port_fops_open(struct inode *inode, struct file *filp) | |||
791 | 811 | ||
792 | return 0; | 812 | return 0; |
793 | out: | 813 | out: |
814 | kref_put(&port->kref, remove_port); | ||
794 | return ret; | 815 | return ret; |
795 | } | 816 | } |
796 | 817 | ||
@@ -1079,6 +1100,7 @@ static int add_port(struct ports_device *portdev, u32 id) | |||
1079 | err = -ENOMEM; | 1100 | err = -ENOMEM; |
1080 | goto fail; | 1101 | goto fail; |
1081 | } | 1102 | } |
1103 | kref_init(&port->kref); | ||
1082 | 1104 | ||
1083 | port->portdev = portdev; | 1105 | port->portdev = portdev; |
1084 | port->id = id; | 1106 | port->id = id; |
@@ -1183,22 +1205,43 @@ fail: | |||
1183 | return err; | 1205 | return err; |
1184 | } | 1206 | } |
1185 | 1207 | ||
1186 | /* Remove all port-specific data. */ | 1208 | /* No users remain, remove all port-specific data. */ |
1187 | static void remove_port(struct port *port) | 1209 | static void remove_port(struct kref *kref) |
1210 | { | ||
1211 | struct port *port; | ||
1212 | |||
1213 | port = container_of(kref, struct port, kref); | ||
1214 | |||
1215 | sysfs_remove_group(&port->dev->kobj, &port_attribute_group); | ||
1216 | device_destroy(pdrvdata.class, port->dev->devt); | ||
1217 | cdev_del(port->cdev); | ||
1218 | |||
1219 | kfree(port->name); | ||
1220 | |||
1221 | debugfs_remove(port->debugfs_file); | ||
1222 | |||
1223 | kfree(port); | ||
1224 | } | ||
1225 | |||
1226 | /* | ||
1227 | * Port got unplugged. Remove port from portdev's list and drop the | ||
1228 | * kref reference. If no userspace has this port opened, it will | ||
1229 | * result in immediate removal the port. | ||
1230 | */ | ||
1231 | static void unplug_port(struct port *port) | ||
1188 | { | 1232 | { |
1189 | struct port_buffer *buf; | 1233 | struct port_buffer *buf; |
1190 | 1234 | ||
1235 | spin_lock_irq(&port->portdev->ports_lock); | ||
1236 | list_del(&port->list); | ||
1237 | spin_unlock_irq(&port->portdev->ports_lock); | ||
1238 | |||
1191 | if (port->guest_connected) { | 1239 | if (port->guest_connected) { |
1192 | port->guest_connected = false; | 1240 | port->guest_connected = false; |
1193 | port->host_connected = false; | 1241 | port->host_connected = false; |
1194 | wake_up_interruptible(&port->waitqueue); | 1242 | wake_up_interruptible(&port->waitqueue); |
1195 | send_control_msg(port, VIRTIO_CONSOLE_PORT_OPEN, 0); | ||
1196 | } | 1243 | } |
1197 | 1244 | ||
1198 | spin_lock_irq(&port->portdev->ports_lock); | ||
1199 | list_del(&port->list); | ||
1200 | spin_unlock_irq(&port->portdev->ports_lock); | ||
1201 | |||
1202 | if (is_console_port(port)) { | 1245 | if (is_console_port(port)) { |
1203 | spin_lock_irq(&pdrvdata_lock); | 1246 | spin_lock_irq(&pdrvdata_lock); |
1204 | list_del(&port->cons.list); | 1247 | list_del(&port->cons.list); |
@@ -1216,9 +1259,6 @@ static void remove_port(struct port *port) | |||
1216 | hvc_remove(port->cons.hvc); | 1259 | hvc_remove(port->cons.hvc); |
1217 | #endif | 1260 | #endif |
1218 | } | 1261 | } |
1219 | sysfs_remove_group(&port->dev->kobj, &port_attribute_group); | ||
1220 | device_destroy(pdrvdata.class, port->dev->devt); | ||
1221 | cdev_del(port->cdev); | ||
1222 | 1262 | ||
1223 | /* Remove unused data this port might have received. */ | 1263 | /* Remove unused data this port might have received. */ |
1224 | discard_port_data(port); | 1264 | discard_port_data(port); |
@@ -1229,11 +1269,19 @@ static void remove_port(struct port *port) | |||
1229 | while ((buf = virtqueue_detach_unused_buf(port->in_vq))) | 1269 | while ((buf = virtqueue_detach_unused_buf(port->in_vq))) |
1230 | free_buf(buf); | 1270 | free_buf(buf); |
1231 | 1271 | ||
1232 | kfree(port->name); | 1272 | /* |
1233 | 1273 | * We should just assume the device itself has gone off -- | |
1234 | debugfs_remove(port->debugfs_file); | 1274 | * else a close on an open port later will try to send out a |
1275 | * control message. | ||
1276 | */ | ||
1277 | port->portdev = NULL; | ||
1235 | 1278 | ||
1236 | kfree(port); | 1279 | /* |
1280 | * Locks around here are not necessary - a port can't be | ||
1281 | * opened after we removed the port struct from ports_list | ||
1282 | * above. | ||
1283 | */ | ||
1284 | kref_put(&port->kref, remove_port); | ||
1237 | } | 1285 | } |
1238 | 1286 | ||
1239 | /* Any private messages that the Host and Guest want to share */ | 1287 | /* Any private messages that the Host and Guest want to share */ |
@@ -1272,7 +1320,7 @@ static void handle_control_message(struct ports_device *portdev, | |||
1272 | add_port(portdev, cpkt->id); | 1320 | add_port(portdev, cpkt->id); |
1273 | break; | 1321 | break; |
1274 | case VIRTIO_CONSOLE_PORT_REMOVE: | 1322 | case VIRTIO_CONSOLE_PORT_REMOVE: |
1275 | remove_port(port); | 1323 | unplug_port(port); |
1276 | break; | 1324 | break; |
1277 | case VIRTIO_CONSOLE_CONSOLE_PORT: | 1325 | case VIRTIO_CONSOLE_CONSOLE_PORT: |
1278 | if (!cpkt->value) | 1326 | if (!cpkt->value) |
@@ -1686,7 +1734,7 @@ static void virtcons_remove(struct virtio_device *vdev) | |||
1686 | cancel_work_sync(&portdev->control_work); | 1734 | cancel_work_sync(&portdev->control_work); |
1687 | 1735 | ||
1688 | list_for_each_entry_safe(port, port2, &portdev->ports, list) | 1736 | list_for_each_entry_safe(port, port2, &portdev->ports, list) |
1689 | remove_port(port); | 1737 | unplug_port(port); |
1690 | 1738 | ||
1691 | unregister_chrdev(portdev->chr_major, "virtio-portsdev"); | 1739 | unregister_chrdev(portdev->chr_major, "virtio-portsdev"); |
1692 | 1740 | ||