diff options
author | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2010-07-22 05:56:38 -0400 |
---|---|---|
committer | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2010-07-27 05:04:11 -0400 |
commit | 424d66cedae8bebb00fdb917fc8430f7b8a655cf (patch) | |
tree | cd232df29974be404978d47a2257c9422272d304 /drivers/firewire/nosy.c | |
parent | b6d9c125e6610591c04ca9045f641e35ce1a9226 (diff) |
firewire: nosy: fix device shutdown with active client
Fix race between nosy_open() and remove_card() by replacing the
unprotected array of card pointers by a mutex-protected list of cards.
Make card instances reference-counted and let each client hold a
reference.
Notify clients about card removal via POLLHUP in poll()'s events
bitmap; also let read() fail with errno=ENODEV if the card was removed
and everything in the buffer was read.
Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
Diffstat (limited to 'drivers/firewire/nosy.c')
-rw-r--r-- | drivers/firewire/nosy.c | 111 |
1 files changed, 82 insertions, 29 deletions
diff --git a/drivers/firewire/nosy.c b/drivers/firewire/nosy.c index ccf9c461bd86..edd729aafeca 100644 --- a/drivers/firewire/nosy.c +++ b/drivers/firewire/nosy.c | |||
@@ -23,8 +23,10 @@ | |||
23 | #include <linux/interrupt.h> | 23 | #include <linux/interrupt.h> |
24 | #include <linux/io.h> | 24 | #include <linux/io.h> |
25 | #include <linux/kernel.h> | 25 | #include <linux/kernel.h> |
26 | #include <linux/kref.h> | ||
26 | #include <linux/miscdevice.h> | 27 | #include <linux/miscdevice.h> |
27 | #include <linux/module.h> | 28 | #include <linux/module.h> |
29 | #include <linux/mutex.h> | ||
28 | #include <linux/pci.h> | 30 | #include <linux/pci.h> |
29 | #include <linux/poll.h> | 31 | #include <linux/poll.h> |
30 | #include <linux/sched.h> /* required for linux/wait.h */ | 32 | #include <linux/sched.h> /* required for linux/wait.h */ |
@@ -104,8 +106,30 @@ struct pcilynx { | |||
104 | struct list_head client_list; | 106 | struct list_head client_list; |
105 | 107 | ||
106 | struct miscdevice misc; | 108 | struct miscdevice misc; |
109 | struct list_head link; | ||
110 | struct kref kref; | ||
107 | }; | 111 | }; |
108 | 112 | ||
113 | static inline struct pcilynx * | ||
114 | lynx_get(struct pcilynx *lynx) | ||
115 | { | ||
116 | kref_get(&lynx->kref); | ||
117 | |||
118 | return lynx; | ||
119 | } | ||
120 | |||
121 | static void | ||
122 | lynx_release(struct kref *kref) | ||
123 | { | ||
124 | kfree(container_of(kref, struct pcilynx, kref)); | ||
125 | } | ||
126 | |||
127 | static inline void | ||
128 | lynx_put(struct pcilynx *lynx) | ||
129 | { | ||
130 | kref_put(&lynx->kref, lynx_release); | ||
131 | } | ||
132 | |||
109 | struct client { | 133 | struct client { |
110 | struct pcilynx *lynx; | 134 | struct pcilynx *lynx; |
111 | u32 tcode_mask; | 135 | u32 tcode_mask; |
@@ -113,8 +137,8 @@ struct client { | |||
113 | struct list_head link; | 137 | struct list_head link; |
114 | }; | 138 | }; |
115 | 139 | ||
116 | #define MAX_MINORS 64 | 140 | static DEFINE_MUTEX(card_mutex); |
117 | static struct pcilynx *minors[MAX_MINORS]; | 141 | static LIST_HEAD(card_list); |
118 | 142 | ||
119 | static int | 143 | static int |
120 | packet_buffer_init(struct packet_buffer *buffer, size_t capacity) | 144 | packet_buffer_init(struct packet_buffer *buffer, size_t capacity) |
@@ -139,15 +163,20 @@ packet_buffer_destroy(struct packet_buffer *buffer) | |||
139 | } | 163 | } |
140 | 164 | ||
141 | static int | 165 | static int |
142 | packet_buffer_get(struct packet_buffer *buffer, void *data, size_t user_length) | 166 | packet_buffer_get(struct client *client, void *data, size_t user_length) |
143 | { | 167 | { |
168 | struct packet_buffer *buffer = &client->buffer; | ||
144 | size_t length; | 169 | size_t length; |
145 | char *end; | 170 | char *end; |
146 | 171 | ||
147 | if (wait_event_interruptible(buffer->wait, | 172 | if (wait_event_interruptible(buffer->wait, |
148 | atomic_read(&buffer->size) > 0)) | 173 | atomic_read(&buffer->size) > 0) || |
174 | list_empty(&client->lynx->link)) | ||
149 | return -ERESTARTSYS; | 175 | return -ERESTARTSYS; |
150 | 176 | ||
177 | if (atomic_read(&buffer->size) == 0) | ||
178 | return -ENODEV; | ||
179 | |||
151 | /* FIXME: Check length <= user_length. */ | 180 | /* FIXME: Check length <= user_length. */ |
152 | 181 | ||
153 | end = buffer->data + buffer->capacity; | 182 | end = buffer->data + buffer->capacity; |
@@ -265,39 +294,52 @@ nosy_open(struct inode *inode, struct file *file) | |||
265 | { | 294 | { |
266 | int minor = iminor(inode); | 295 | int minor = iminor(inode); |
267 | struct client *client; | 296 | struct client *client; |
268 | 297 | struct pcilynx *tmp, *lynx = NULL; | |
269 | if (minor > MAX_MINORS || minors[minor] == NULL) | 298 | |
299 | mutex_lock(&card_mutex); | ||
300 | list_for_each_entry(tmp, &card_list, link) | ||
301 | if (tmp->misc.minor == minor) { | ||
302 | lynx = lynx_get(tmp); | ||
303 | break; | ||
304 | } | ||
305 | mutex_unlock(&card_mutex); | ||
306 | if (lynx == NULL) | ||
270 | return -ENODEV; | 307 | return -ENODEV; |
271 | 308 | ||
272 | client = kmalloc(sizeof *client, GFP_KERNEL); | 309 | client = kmalloc(sizeof *client, GFP_KERNEL); |
273 | if (client == NULL) | 310 | if (client == NULL) |
274 | return -ENOMEM; | 311 | goto fail; |
275 | 312 | ||
276 | client->tcode_mask = ~0; | 313 | client->tcode_mask = ~0; |
277 | client->lynx = minors[minor]; | 314 | client->lynx = lynx; |
278 | INIT_LIST_HEAD(&client->link); | 315 | INIT_LIST_HEAD(&client->link); |
279 | 316 | ||
280 | if (packet_buffer_init(&client->buffer, 128 * 1024) < 0) { | 317 | if (packet_buffer_init(&client->buffer, 128 * 1024) < 0) |
281 | kfree(client); | 318 | goto fail; |
282 | return -ENOMEM; | ||
283 | } | ||
284 | 319 | ||
285 | file->private_data = client; | 320 | file->private_data = client; |
286 | 321 | ||
287 | return 0; | 322 | return 0; |
323 | fail: | ||
324 | kfree(client); | ||
325 | lynx_put(lynx); | ||
326 | |||
327 | return -ENOMEM; | ||
288 | } | 328 | } |
289 | 329 | ||
290 | static int | 330 | static int |
291 | nosy_release(struct inode *inode, struct file *file) | 331 | nosy_release(struct inode *inode, struct file *file) |
292 | { | 332 | { |
293 | struct client *client = file->private_data; | 333 | struct client *client = file->private_data; |
334 | struct pcilynx *lynx = client->lynx; | ||
294 | 335 | ||
295 | spin_lock_irq(&client->lynx->client_list_lock); | 336 | spin_lock_irq(&lynx->client_list_lock); |
296 | list_del_init(&client->link); | 337 | list_del_init(&client->link); |
297 | spin_unlock_irq(&client->lynx->client_list_lock); | 338 | spin_unlock_irq(&lynx->client_list_lock); |
298 | 339 | ||
299 | packet_buffer_destroy(&client->buffer); | 340 | packet_buffer_destroy(&client->buffer); |
300 | kfree(client); | 341 | kfree(client); |
342 | lynx_put(lynx); | ||
301 | 343 | ||
302 | return 0; | 344 | return 0; |
303 | } | 345 | } |
@@ -306,13 +348,17 @@ static unsigned int | |||
306 | nosy_poll(struct file *file, poll_table *pt) | 348 | nosy_poll(struct file *file, poll_table *pt) |
307 | { | 349 | { |
308 | struct client *client = file->private_data; | 350 | struct client *client = file->private_data; |
351 | unsigned int ret = 0; | ||
309 | 352 | ||
310 | poll_wait(file, &client->buffer.wait, pt); | 353 | poll_wait(file, &client->buffer.wait, pt); |
311 | 354 | ||
312 | if (atomic_read(&client->buffer.size) > 0) | 355 | if (atomic_read(&client->buffer.size) > 0) |
313 | return POLLIN | POLLRDNORM; | 356 | ret = POLLIN | POLLRDNORM; |
314 | else | 357 | |
315 | return 0; | 358 | if (list_empty(&client->lynx->link)) |
359 | ret |= POLLHUP; | ||
360 | |||
361 | return ret; | ||
316 | } | 362 | } |
317 | 363 | ||
318 | static ssize_t | 364 | static ssize_t |
@@ -320,7 +366,7 @@ nosy_read(struct file *file, char *buffer, size_t count, loff_t *offset) | |||
320 | { | 366 | { |
321 | struct client *client = file->private_data; | 367 | struct client *client = file->private_data; |
322 | 368 | ||
323 | return packet_buffer_get(&client->buffer, buffer, count); | 369 | return packet_buffer_get(client, buffer, count); |
324 | } | 370 | } |
325 | 371 | ||
326 | static long | 372 | static long |
@@ -479,16 +525,22 @@ irq_handler(int irq, void *device) | |||
479 | static void | 525 | static void |
480 | remove_card(struct pci_dev *dev) | 526 | remove_card(struct pci_dev *dev) |
481 | { | 527 | { |
482 | struct pcilynx *lynx; | 528 | struct pcilynx *lynx = pci_get_drvdata(dev); |
529 | struct client *client; | ||
483 | 530 | ||
484 | lynx = pci_get_drvdata(dev); | 531 | mutex_lock(&card_mutex); |
485 | if (!lynx) | 532 | list_del_init(&lynx->link); |
486 | return; | 533 | misc_deregister(&lynx->misc); |
487 | pci_set_drvdata(dev, NULL); | 534 | mutex_unlock(&card_mutex); |
488 | 535 | ||
489 | reg_write(lynx, PCI_INT_ENABLE, 0); | 536 | reg_write(lynx, PCI_INT_ENABLE, 0); |
490 | free_irq(lynx->pci_device->irq, lynx); | 537 | free_irq(lynx->pci_device->irq, lynx); |
491 | 538 | ||
539 | spin_lock_irq(&lynx->client_list_lock); | ||
540 | list_for_each_entry(client, &lynx->client_list, link) | ||
541 | wake_up_interruptible(&client->buffer.wait); | ||
542 | spin_unlock_irq(&lynx->client_list_lock); | ||
543 | |||
492 | pci_free_consistent(lynx->pci_device, sizeof(struct pcl), | 544 | pci_free_consistent(lynx->pci_device, sizeof(struct pcl), |
493 | lynx->rcv_start_pcl, lynx->rcv_start_pcl_bus); | 545 | lynx->rcv_start_pcl, lynx->rcv_start_pcl_bus); |
494 | pci_free_consistent(lynx->pci_device, sizeof(struct pcl), | 546 | pci_free_consistent(lynx->pci_device, sizeof(struct pcl), |
@@ -498,11 +550,7 @@ remove_card(struct pci_dev *dev) | |||
498 | 550 | ||
499 | iounmap(lynx->registers); | 551 | iounmap(lynx->registers); |
500 | pci_disable_device(dev); | 552 | pci_disable_device(dev); |
501 | 553 | lynx_put(lynx); | |
502 | minors[lynx->misc.minor] = NULL; | ||
503 | misc_deregister(&lynx->misc); | ||
504 | |||
505 | kfree(lynx); | ||
506 | } | 554 | } |
507 | 555 | ||
508 | #define RCV_BUFFER_SIZE (16 * 1024) | 556 | #define RCV_BUFFER_SIZE (16 * 1024) |
@@ -536,6 +584,7 @@ add_card(struct pci_dev *dev, const struct pci_device_id *unused) | |||
536 | 584 | ||
537 | spin_lock_init(&lynx->client_list_lock); | 585 | spin_lock_init(&lynx->client_list_lock); |
538 | INIT_LIST_HEAD(&lynx->client_list); | 586 | INIT_LIST_HEAD(&lynx->client_list); |
587 | kref_init(&lynx->kref); | ||
539 | 588 | ||
540 | lynx->registers = ioremap_nocache(pci_resource_start(dev, 0), | 589 | lynx->registers = ioremap_nocache(pci_resource_start(dev, 0), |
541 | PCILYNX_MAX_REGISTER); | 590 | PCILYNX_MAX_REGISTER); |
@@ -619,12 +668,16 @@ add_card(struct pci_dev *dev, const struct pci_device_id *unused) | |||
619 | lynx->misc.minor = MISC_DYNAMIC_MINOR; | 668 | lynx->misc.minor = MISC_DYNAMIC_MINOR; |
620 | lynx->misc.name = "nosy"; | 669 | lynx->misc.name = "nosy"; |
621 | lynx->misc.fops = &nosy_ops; | 670 | lynx->misc.fops = &nosy_ops; |
671 | |||
672 | mutex_lock(&card_mutex); | ||
622 | ret = misc_register(&lynx->misc); | 673 | ret = misc_register(&lynx->misc); |
623 | if (ret) { | 674 | if (ret) { |
624 | error("Failed to register misc char device\n"); | 675 | error("Failed to register misc char device\n"); |
676 | mutex_unlock(&card_mutex); | ||
625 | goto fail_free_irq; | 677 | goto fail_free_irq; |
626 | } | 678 | } |
627 | minors[lynx->misc.minor] = lynx; | 679 | list_add_tail(&lynx->link, &card_list); |
680 | mutex_unlock(&card_mutex); | ||
628 | 681 | ||
629 | notify("Initialized PCILynx IEEE1394 card, irq=%d\n", dev->irq); | 682 | notify("Initialized PCILynx IEEE1394 card, irq=%d\n", dev->irq); |
630 | 683 | ||