diff options
author | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2009-01-04 10:23:29 -0500 |
---|---|---|
committer | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2009-03-24 15:56:42 -0400 |
commit | fb4430367b0bbee2420132faf16c7c762a39c0bb (patch) | |
tree | d5def952cdb381863d7a2b7eb0e421d7757c7d4d /drivers/firewire/fw-cdev.c | |
parent | 632321ecd99bf85c982a75f8329b4ecbb95b3a8f (diff) |
firewire: cdev: reference-count client instances
The lifetime of struct client instances must be longer than the lifetime
of any client resource.
This fixes a possible race between fw_device_op_release and transaction
completions. It also prepares for new ioctls for isochronous resource
management which will involve delayed processing of client resources.
Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
Reviewed-by: David Moore <dcm@acm.org>
Diffstat (limited to 'drivers/firewire/fw-cdev.c')
-rw-r--r-- | drivers/firewire/fw-cdev.c | 55 |
1 files changed, 46 insertions, 9 deletions
diff --git a/drivers/firewire/fw-cdev.c b/drivers/firewire/fw-cdev.c index ead5c7b06ec1..81362c14ef44 100644 --- a/drivers/firewire/fw-cdev.c +++ b/drivers/firewire/fw-cdev.c | |||
@@ -20,6 +20,7 @@ | |||
20 | 20 | ||
21 | #include <linux/module.h> | 21 | #include <linux/module.h> |
22 | #include <linux/kernel.h> | 22 | #include <linux/kernel.h> |
23 | #include <linux/kref.h> | ||
23 | #include <linux/wait.h> | 24 | #include <linux/wait.h> |
24 | #include <linux/errno.h> | 25 | #include <linux/errno.h> |
25 | #include <linux/device.h> | 26 | #include <linux/device.h> |
@@ -94,8 +95,27 @@ struct client { | |||
94 | unsigned long vm_start; | 95 | unsigned long vm_start; |
95 | 96 | ||
96 | struct list_head link; | 97 | struct list_head link; |
98 | struct kref kref; | ||
97 | }; | 99 | }; |
98 | 100 | ||
101 | static inline void client_get(struct client *client) | ||
102 | { | ||
103 | kref_get(&client->kref); | ||
104 | } | ||
105 | |||
106 | static void client_release(struct kref *kref) | ||
107 | { | ||
108 | struct client *client = container_of(kref, struct client, kref); | ||
109 | |||
110 | fw_device_put(client->device); | ||
111 | kfree(client); | ||
112 | } | ||
113 | |||
114 | static void client_put(struct client *client) | ||
115 | { | ||
116 | kref_put(&client->kref, client_release); | ||
117 | } | ||
118 | |||
99 | static inline void __user *u64_to_uptr(__u64 value) | 119 | static inline void __user *u64_to_uptr(__u64 value) |
100 | { | 120 | { |
101 | return (void __user *)(unsigned long)value; | 121 | return (void __user *)(unsigned long)value; |
@@ -131,6 +151,7 @@ static int fw_device_op_open(struct inode *inode, struct file *file) | |||
131 | idr_init(&client->resource_idr); | 151 | idr_init(&client->resource_idr); |
132 | INIT_LIST_HEAD(&client->event_list); | 152 | INIT_LIST_HEAD(&client->event_list); |
133 | init_waitqueue_head(&client->wait); | 153 | init_waitqueue_head(&client->wait); |
154 | kref_init(&client->kref); | ||
134 | 155 | ||
135 | file->private_data = client; | 156 | file->private_data = client; |
136 | 157 | ||
@@ -326,6 +347,8 @@ static int add_client_resource(struct client *client, | |||
326 | else | 347 | else |
327 | ret = idr_get_new(&client->resource_idr, resource, | 348 | ret = idr_get_new(&client->resource_idr, resource, |
328 | &resource->handle); | 349 | &resource->handle); |
350 | if (ret >= 0) | ||
351 | client_get(client); | ||
329 | spin_unlock_irqrestore(&client->lock, flags); | 352 | spin_unlock_irqrestore(&client->lock, flags); |
330 | 353 | ||
331 | if (ret == -EAGAIN) | 354 | if (ret == -EAGAIN) |
@@ -358,6 +381,8 @@ static int release_client_resource(struct client *client, u32 handle, | |||
358 | else | 381 | else |
359 | r->release(client, r); | 382 | r->release(client, r); |
360 | 383 | ||
384 | client_put(client); | ||
385 | |||
361 | return 0; | 386 | return 0; |
362 | } | 387 | } |
363 | 388 | ||
@@ -385,11 +410,21 @@ static void complete_transaction(struct fw_card *card, int rcode, | |||
385 | 410 | ||
386 | spin_lock_irqsave(&client->lock, flags); | 411 | spin_lock_irqsave(&client->lock, flags); |
387 | /* | 412 | /* |
388 | * If called while in shutdown, the idr tree must be left untouched. | 413 | * 1. If called while in shutdown, the idr tree must be left untouched. |
389 | * The idr handle will be removed later. | 414 | * The idr handle will be removed and the client reference will be |
415 | * dropped later. | ||
416 | * 2. If the call chain was release_client_resource -> | ||
417 | * release_transaction -> complete_transaction (instead of a normal | ||
418 | * conclusion of the transaction), i.e. if this resource was already | ||
419 | * unregistered from the idr, the client reference will be dropped | ||
420 | * by release_client_resource and we must not drop it here. | ||
390 | */ | 421 | */ |
391 | if (!client->in_shutdown) | 422 | if (!client->in_shutdown && |
423 | idr_find(&client->resource_idr, response->resource.handle)) { | ||
392 | idr_remove(&client->resource_idr, response->resource.handle); | 424 | idr_remove(&client->resource_idr, response->resource.handle); |
425 | /* Drop the idr's reference */ | ||
426 | client_put(client); | ||
427 | } | ||
393 | spin_unlock_irqrestore(&client->lock, flags); | 428 | spin_unlock_irqrestore(&client->lock, flags); |
394 | 429 | ||
395 | r->type = FW_CDEV_EVENT_RESPONSE; | 430 | r->type = FW_CDEV_EVENT_RESPONSE; |
@@ -408,6 +443,9 @@ static void complete_transaction(struct fw_card *card, int rcode, | |||
408 | else | 443 | else |
409 | queue_event(client, &response->event, r, sizeof(*r) + r->length, | 444 | queue_event(client, &response->event, r, sizeof(*r) + r->length, |
410 | NULL, 0); | 445 | NULL, 0); |
446 | |||
447 | /* Drop the transaction callback's reference */ | ||
448 | client_put(client); | ||
411 | } | 449 | } |
412 | 450 | ||
413 | static int ioctl_send_request(struct client *client, void *buffer) | 451 | static int ioctl_send_request(struct client *client, void *buffer) |
@@ -459,6 +497,9 @@ static int ioctl_send_request(struct client *client, void *buffer) | |||
459 | if (ret < 0) | 497 | if (ret < 0) |
460 | goto failed; | 498 | goto failed; |
461 | 499 | ||
500 | /* Get a reference for the transaction callback */ | ||
501 | client_get(client); | ||
502 | |||
462 | fw_send_request(device->card, &response->transaction, | 503 | fw_send_request(device->card, &response->transaction, |
463 | request->tcode & 0x1f, | 504 | request->tcode & 0x1f, |
464 | device->node->node_id, | 505 | device->node->node_id, |
@@ -1044,6 +1085,7 @@ static int shutdown_resource(int id, void *p, void *data) | |||
1044 | struct client *client = data; | 1085 | struct client *client = data; |
1045 | 1086 | ||
1046 | r->release(client, r); | 1087 | r->release(client, r); |
1088 | client_put(client); | ||
1047 | 1089 | ||
1048 | return 0; | 1090 | return 0; |
1049 | } | 1091 | } |
@@ -1076,12 +1118,7 @@ static int fw_device_op_release(struct inode *inode, struct file *file) | |||
1076 | list_for_each_entry_safe(e, next_e, &client->event_list, link) | 1118 | list_for_each_entry_safe(e, next_e, &client->event_list, link) |
1077 | kfree(e); | 1119 | kfree(e); |
1078 | 1120 | ||
1079 | /* | 1121 | client_put(client); |
1080 | * FIXME: client should be reference-counted. It's extremely unlikely | ||
1081 | * but there may still be transactions being completed at this point. | ||
1082 | */ | ||
1083 | fw_device_put(client->device); | ||
1084 | kfree(client); | ||
1085 | 1122 | ||
1086 | return 0; | 1123 | return 0; |
1087 | } | 1124 | } |