diff options
author | Jay Fenlason <fenlason@redhat.com> | 2008-12-21 10:47:17 -0500 |
---|---|---|
committer | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2009-03-24 15:56:38 -0400 |
commit | 45ee3199eb3e4233b755a9bb353a0527a4c58b5f (patch) | |
tree | 4fa2ab6e54af7cb6aceab1cea1b5676836644841 /drivers/firewire | |
parent | 97811e347310766030a648fdf0e407b2c91a39c1 (diff) |
firewire: cdev: use an idr rather than a linked list for resources
The current code uses a linked list and a counter for storing
resources and the corresponding handle numbers. By changing to an idr
we can be safe from counter wrap-around giving two resources the same
handle.
Furthermore, the deallocation ioctls now check whether the resource to
be freed is of the intended type.
Signed-off-by: Jay Fenlason <fenlason@redhat.com>
Some rework by Stefan R:
- The idr API documentation says we get an ID within 0...0x7fffffff.
Hence we can rest assured that idr handles fit into cdev handles.
- Fix some races. Add a client->in_shutdown flag for this purpose.
- Add allocation retry to add_client_resource().
- It is possible to use idr_for_each() in fw_device_op_release().
- Fix ioctl_send_response() regression.
- Small style changes.
Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
Diffstat (limited to 'drivers/firewire')
-rw-r--r-- | drivers/firewire/fw-cdev.c | 165 |
1 files changed, 114 insertions, 51 deletions
diff --git a/drivers/firewire/fw-cdev.c b/drivers/firewire/fw-cdev.c index 4dd66c1a36da..094aee5c8bcf 100644 --- a/drivers/firewire/fw-cdev.c +++ b/drivers/firewire/fw-cdev.c | |||
@@ -41,10 +41,12 @@ | |||
41 | #include "fw-device.h" | 41 | #include "fw-device.h" |
42 | 42 | ||
43 | struct client; | 43 | struct client; |
44 | struct client_resource; | ||
45 | typedef void (*client_resource_release_fn_t)(struct client *, | ||
46 | struct client_resource *); | ||
44 | struct client_resource { | 47 | struct client_resource { |
45 | struct list_head link; | 48 | client_resource_release_fn_t release; |
46 | void (*release)(struct client *client, struct client_resource *r); | 49 | int handle; |
47 | u32 handle; | ||
48 | }; | 50 | }; |
49 | 51 | ||
50 | /* | 52 | /* |
@@ -78,9 +80,10 @@ struct iso_interrupt { | |||
78 | struct client { | 80 | struct client { |
79 | u32 version; | 81 | u32 version; |
80 | struct fw_device *device; | 82 | struct fw_device *device; |
83 | |||
81 | spinlock_t lock; | 84 | spinlock_t lock; |
82 | u32 resource_handle; | 85 | bool in_shutdown; |
83 | struct list_head resource_list; | 86 | struct idr resource_idr; |
84 | struct list_head event_list; | 87 | struct list_head event_list; |
85 | wait_queue_head_t wait; | 88 | wait_queue_head_t wait; |
86 | u64 bus_reset_closure; | 89 | u64 bus_reset_closure; |
@@ -126,9 +129,9 @@ static int fw_device_op_open(struct inode *inode, struct file *file) | |||
126 | } | 129 | } |
127 | 130 | ||
128 | client->device = device; | 131 | client->device = device; |
129 | INIT_LIST_HEAD(&client->event_list); | ||
130 | INIT_LIST_HEAD(&client->resource_list); | ||
131 | spin_lock_init(&client->lock); | 132 | spin_lock_init(&client->lock); |
133 | idr_init(&client->resource_idr); | ||
134 | INIT_LIST_HEAD(&client->event_list); | ||
132 | init_waitqueue_head(&client->wait); | 135 | init_waitqueue_head(&client->wait); |
133 | 136 | ||
134 | file->private_data = client; | 137 | file->private_data = client; |
@@ -151,7 +154,10 @@ static void queue_event(struct client *client, struct event *event, | |||
151 | event->v[1].size = size1; | 154 | event->v[1].size = size1; |
152 | 155 | ||
153 | spin_lock_irqsave(&client->lock, flags); | 156 | spin_lock_irqsave(&client->lock, flags); |
154 | list_add_tail(&event->link, &client->event_list); | 157 | if (client->in_shutdown) |
158 | kfree(event); | ||
159 | else | ||
160 | list_add_tail(&event->link, &client->event_list); | ||
155 | spin_unlock_irqrestore(&client->lock, flags); | 161 | spin_unlock_irqrestore(&client->lock, flags); |
156 | 162 | ||
157 | wake_up_interruptible(&client->wait); | 163 | wake_up_interruptible(&client->wait); |
@@ -310,34 +316,49 @@ static int ioctl_get_info(struct client *client, void *buffer) | |||
310 | return 0; | 316 | return 0; |
311 | } | 317 | } |
312 | 318 | ||
313 | static void | 319 | static int |
314 | add_client_resource(struct client *client, struct client_resource *resource) | 320 | add_client_resource(struct client *client, struct client_resource *resource, |
321 | gfp_t gfp_mask) | ||
315 | { | 322 | { |
316 | unsigned long flags; | 323 | unsigned long flags; |
324 | int ret; | ||
325 | |||
326 | retry: | ||
327 | if (idr_pre_get(&client->resource_idr, gfp_mask) == 0) | ||
328 | return -ENOMEM; | ||
317 | 329 | ||
318 | spin_lock_irqsave(&client->lock, flags); | 330 | spin_lock_irqsave(&client->lock, flags); |
319 | list_add_tail(&resource->link, &client->resource_list); | 331 | if (client->in_shutdown) |
320 | resource->handle = client->resource_handle++; | 332 | ret = -ECANCELED; |
333 | else | ||
334 | ret = idr_get_new(&client->resource_idr, resource, | ||
335 | &resource->handle); | ||
321 | spin_unlock_irqrestore(&client->lock, flags); | 336 | spin_unlock_irqrestore(&client->lock, flags); |
337 | |||
338 | if (ret == -EAGAIN) | ||
339 | goto retry; | ||
340 | |||
341 | return ret < 0 ? ret : 0; | ||
322 | } | 342 | } |
323 | 343 | ||
324 | static int | 344 | static int |
325 | release_client_resource(struct client *client, u32 handle, | 345 | release_client_resource(struct client *client, u32 handle, |
346 | client_resource_release_fn_t release, | ||
326 | struct client_resource **resource) | 347 | struct client_resource **resource) |
327 | { | 348 | { |
328 | struct client_resource *r; | 349 | struct client_resource *r; |
329 | unsigned long flags; | 350 | unsigned long flags; |
330 | 351 | ||
331 | spin_lock_irqsave(&client->lock, flags); | 352 | spin_lock_irqsave(&client->lock, flags); |
332 | list_for_each_entry(r, &client->resource_list, link) { | 353 | if (client->in_shutdown) |
333 | if (r->handle == handle) { | 354 | r = NULL; |
334 | list_del(&r->link); | 355 | else |
335 | break; | 356 | r = idr_find(&client->resource_idr, handle); |
336 | } | 357 | if (r && r->release == release) |
337 | } | 358 | idr_remove(&client->resource_idr, handle); |
338 | spin_unlock_irqrestore(&client->lock, flags); | 359 | spin_unlock_irqrestore(&client->lock, flags); |
339 | 360 | ||
340 | if (&r->link == &client->resource_list) | 361 | if (!(r && r->release == release)) |
341 | return -EINVAL; | 362 | return -EINVAL; |
342 | 363 | ||
343 | if (resource) | 364 | if (resource) |
@@ -372,7 +393,12 @@ complete_transaction(struct fw_card *card, int rcode, | |||
372 | memcpy(r->data, payload, r->length); | 393 | memcpy(r->data, payload, r->length); |
373 | 394 | ||
374 | spin_lock_irqsave(&client->lock, flags); | 395 | spin_lock_irqsave(&client->lock, flags); |
375 | list_del(&response->resource.link); | 396 | /* |
397 | * If called while in shutdown, the idr tree must be left untouched. | ||
398 | * The idr handle will be removed later. | ||
399 | */ | ||
400 | if (!client->in_shutdown) | ||
401 | idr_remove(&client->resource_idr, response->resource.handle); | ||
376 | spin_unlock_irqrestore(&client->lock, flags); | 402 | spin_unlock_irqrestore(&client->lock, flags); |
377 | 403 | ||
378 | r->type = FW_CDEV_EVENT_RESPONSE; | 404 | r->type = FW_CDEV_EVENT_RESPONSE; |
@@ -416,7 +442,7 @@ static int ioctl_send_request(struct client *client, void *buffer) | |||
416 | copy_from_user(response->response.data, | 442 | copy_from_user(response->response.data, |
417 | u64_to_uptr(request->data), request->length)) { | 443 | u64_to_uptr(request->data), request->length)) { |
418 | ret = -EFAULT; | 444 | ret = -EFAULT; |
419 | goto err; | 445 | goto failed; |
420 | } | 446 | } |
421 | 447 | ||
422 | switch (request->tcode) { | 448 | switch (request->tcode) { |
@@ -434,11 +460,13 @@ static int ioctl_send_request(struct client *client, void *buffer) | |||
434 | break; | 460 | break; |
435 | default: | 461 | default: |
436 | ret = -EINVAL; | 462 | ret = -EINVAL; |
437 | goto err; | 463 | goto failed; |
438 | } | 464 | } |
439 | 465 | ||
440 | response->resource.release = release_transaction; | 466 | response->resource.release = release_transaction; |
441 | add_client_resource(client, &response->resource); | 467 | ret = add_client_resource(client, &response->resource, GFP_KERNEL); |
468 | if (ret < 0) | ||
469 | goto failed; | ||
442 | 470 | ||
443 | fw_send_request(device->card, &response->transaction, | 471 | fw_send_request(device->card, &response->transaction, |
444 | request->tcode & 0x1f, | 472 | request->tcode & 0x1f, |
@@ -453,7 +481,7 @@ static int ioctl_send_request(struct client *client, void *buffer) | |||
453 | return sizeof(request) + request->length; | 481 | return sizeof(request) + request->length; |
454 | else | 482 | else |
455 | return sizeof(request); | 483 | return sizeof(request); |
456 | err: | 484 | failed: |
457 | kfree(response); | 485 | kfree(response); |
458 | 486 | ||
459 | return ret; | 487 | return ret; |
@@ -500,22 +528,21 @@ handle_request(struct fw_card *card, struct fw_request *r, | |||
500 | struct request *request; | 528 | struct request *request; |
501 | struct request_event *e; | 529 | struct request_event *e; |
502 | struct client *client = handler->client; | 530 | struct client *client = handler->client; |
531 | int ret; | ||
503 | 532 | ||
504 | request = kmalloc(sizeof(*request), GFP_ATOMIC); | 533 | request = kmalloc(sizeof(*request), GFP_ATOMIC); |
505 | e = kmalloc(sizeof(*e), GFP_ATOMIC); | 534 | e = kmalloc(sizeof(*e), GFP_ATOMIC); |
506 | if (request == NULL || e == NULL) { | 535 | if (request == NULL || e == NULL) |
507 | kfree(request); | 536 | goto failed; |
508 | kfree(e); | ||
509 | fw_send_response(card, r, RCODE_CONFLICT_ERROR); | ||
510 | return; | ||
511 | } | ||
512 | 537 | ||
513 | request->request = r; | 538 | request->request = r; |
514 | request->data = payload; | 539 | request->data = payload; |
515 | request->length = length; | 540 | request->length = length; |
516 | 541 | ||
517 | request->resource.release = release_request; | 542 | request->resource.release = release_request; |
518 | add_client_resource(client, &request->resource); | 543 | ret = add_client_resource(client, &request->resource, GFP_ATOMIC); |
544 | if (ret < 0) | ||
545 | goto failed; | ||
519 | 546 | ||
520 | e->request.type = FW_CDEV_EVENT_REQUEST; | 547 | e->request.type = FW_CDEV_EVENT_REQUEST; |
521 | e->request.tcode = tcode; | 548 | e->request.tcode = tcode; |
@@ -526,6 +553,12 @@ handle_request(struct fw_card *card, struct fw_request *r, | |||
526 | 553 | ||
527 | queue_event(client, &e->event, | 554 | queue_event(client, &e->event, |
528 | &e->request, sizeof(e->request), payload, length); | 555 | &e->request, sizeof(e->request), payload, length); |
556 | return; | ||
557 | |||
558 | failed: | ||
559 | kfree(request); | ||
560 | kfree(e); | ||
561 | fw_send_response(card, r, RCODE_CONFLICT_ERROR); | ||
529 | } | 562 | } |
530 | 563 | ||
531 | static void | 564 | static void |
@@ -544,6 +577,7 @@ static int ioctl_allocate(struct client *client, void *buffer) | |||
544 | struct fw_cdev_allocate *request = buffer; | 577 | struct fw_cdev_allocate *request = buffer; |
545 | struct address_handler *handler; | 578 | struct address_handler *handler; |
546 | struct fw_address_region region; | 579 | struct fw_address_region region; |
580 | int ret; | ||
547 | 581 | ||
548 | handler = kmalloc(sizeof(*handler), GFP_KERNEL); | 582 | handler = kmalloc(sizeof(*handler), GFP_KERNEL); |
549 | if (handler == NULL) | 583 | if (handler == NULL) |
@@ -563,7 +597,11 @@ static int ioctl_allocate(struct client *client, void *buffer) | |||
563 | } | 597 | } |
564 | 598 | ||
565 | handler->resource.release = release_address_handler; | 599 | handler->resource.release = release_address_handler; |
566 | add_client_resource(client, &handler->resource); | 600 | ret = add_client_resource(client, &handler->resource, GFP_KERNEL); |
601 | if (ret < 0) { | ||
602 | release_address_handler(client, &handler->resource); | ||
603 | return ret; | ||
604 | } | ||
567 | request->handle = handler->resource.handle; | 605 | request->handle = handler->resource.handle; |
568 | 606 | ||
569 | return 0; | 607 | return 0; |
@@ -573,7 +611,8 @@ static int ioctl_deallocate(struct client *client, void *buffer) | |||
573 | { | 611 | { |
574 | struct fw_cdev_deallocate *request = buffer; | 612 | struct fw_cdev_deallocate *request = buffer; |
575 | 613 | ||
576 | return release_client_resource(client, request->handle, NULL); | 614 | return release_client_resource(client, request->handle, |
615 | release_address_handler, NULL); | ||
577 | } | 616 | } |
578 | 617 | ||
579 | static int ioctl_send_response(struct client *client, void *buffer) | 618 | static int ioctl_send_response(struct client *client, void *buffer) |
@@ -582,8 +621,10 @@ static int ioctl_send_response(struct client *client, void *buffer) | |||
582 | struct client_resource *resource; | 621 | struct client_resource *resource; |
583 | struct request *r; | 622 | struct request *r; |
584 | 623 | ||
585 | if (release_client_resource(client, request->handle, &resource) < 0) | 624 | if (release_client_resource(client, request->handle, |
625 | release_request, &resource) < 0) | ||
586 | return -EINVAL; | 626 | return -EINVAL; |
627 | |||
587 | r = container_of(resource, struct request, resource); | 628 | r = container_of(resource, struct request, resource); |
588 | if (request->length < r->length) | 629 | if (request->length < r->length) |
589 | r->length = request->length; | 630 | r->length = request->length; |
@@ -626,7 +667,7 @@ static int ioctl_add_descriptor(struct client *client, void *buffer) | |||
626 | { | 667 | { |
627 | struct fw_cdev_add_descriptor *request = buffer; | 668 | struct fw_cdev_add_descriptor *request = buffer; |
628 | struct descriptor *descriptor; | 669 | struct descriptor *descriptor; |
629 | int retval; | 670 | int ret; |
630 | 671 | ||
631 | if (request->length > 256) | 672 | if (request->length > 256) |
632 | return -EINVAL; | 673 | return -EINVAL; |
@@ -638,8 +679,8 @@ static int ioctl_add_descriptor(struct client *client, void *buffer) | |||
638 | 679 | ||
639 | if (copy_from_user(descriptor->data, | 680 | if (copy_from_user(descriptor->data, |
640 | u64_to_uptr(request->data), request->length * 4)) { | 681 | u64_to_uptr(request->data), request->length * 4)) { |
641 | kfree(descriptor); | 682 | ret = -EFAULT; |
642 | return -EFAULT; | 683 | goto failed; |
643 | } | 684 | } |
644 | 685 | ||
645 | descriptor->d.length = request->length; | 686 | descriptor->d.length = request->length; |
@@ -647,24 +688,31 @@ static int ioctl_add_descriptor(struct client *client, void *buffer) | |||
647 | descriptor->d.key = request->key; | 688 | descriptor->d.key = request->key; |
648 | descriptor->d.data = descriptor->data; | 689 | descriptor->d.data = descriptor->data; |
649 | 690 | ||
650 | retval = fw_core_add_descriptor(&descriptor->d); | 691 | ret = fw_core_add_descriptor(&descriptor->d); |
651 | if (retval < 0) { | 692 | if (ret < 0) |
652 | kfree(descriptor); | 693 | goto failed; |
653 | return retval; | ||
654 | } | ||
655 | 694 | ||
656 | descriptor->resource.release = release_descriptor; | 695 | descriptor->resource.release = release_descriptor; |
657 | add_client_resource(client, &descriptor->resource); | 696 | ret = add_client_resource(client, &descriptor->resource, GFP_KERNEL); |
697 | if (ret < 0) { | ||
698 | fw_core_remove_descriptor(&descriptor->d); | ||
699 | goto failed; | ||
700 | } | ||
658 | request->handle = descriptor->resource.handle; | 701 | request->handle = descriptor->resource.handle; |
659 | 702 | ||
660 | return 0; | 703 | return 0; |
704 | failed: | ||
705 | kfree(descriptor); | ||
706 | |||
707 | return ret; | ||
661 | } | 708 | } |
662 | 709 | ||
663 | static int ioctl_remove_descriptor(struct client *client, void *buffer) | 710 | static int ioctl_remove_descriptor(struct client *client, void *buffer) |
664 | { | 711 | { |
665 | struct fw_cdev_remove_descriptor *request = buffer; | 712 | struct fw_cdev_remove_descriptor *request = buffer; |
666 | 713 | ||
667 | return release_client_resource(client, request->handle, NULL); | 714 | return release_client_resource(client, request->handle, |
715 | release_descriptor, NULL); | ||
668 | } | 716 | } |
669 | 717 | ||
670 | static void | 718 | static void |
@@ -1003,11 +1051,21 @@ static int fw_device_op_mmap(struct file *file, struct vm_area_struct *vma) | |||
1003 | return retval; | 1051 | return retval; |
1004 | } | 1052 | } |
1005 | 1053 | ||
1054 | static int shutdown_resource(int id, void *p, void *data) | ||
1055 | { | ||
1056 | struct client_resource *r = p; | ||
1057 | struct client *client = data; | ||
1058 | |||
1059 | r->release(client, r); | ||
1060 | |||
1061 | return 0; | ||
1062 | } | ||
1063 | |||
1006 | static int fw_device_op_release(struct inode *inode, struct file *file) | 1064 | static int fw_device_op_release(struct inode *inode, struct file *file) |
1007 | { | 1065 | { |
1008 | struct client *client = file->private_data; | 1066 | struct client *client = file->private_data; |
1009 | struct event *e, *next_e; | 1067 | struct event *e, *next_e; |
1010 | struct client_resource *r, *next_r; | 1068 | unsigned long flags; |
1011 | 1069 | ||
1012 | mutex_lock(&client->device->client_list_mutex); | 1070 | mutex_lock(&client->device->client_list_mutex); |
1013 | list_del(&client->link); | 1071 | list_del(&client->link); |
@@ -1019,17 +1077,22 @@ static int fw_device_op_release(struct inode *inode, struct file *file) | |||
1019 | if (client->iso_context) | 1077 | if (client->iso_context) |
1020 | fw_iso_context_destroy(client->iso_context); | 1078 | fw_iso_context_destroy(client->iso_context); |
1021 | 1079 | ||
1022 | list_for_each_entry_safe(r, next_r, &client->resource_list, link) | 1080 | /* Freeze client->resource_idr and client->event_list */ |
1023 | r->release(client, r); | 1081 | spin_lock_irqsave(&client->lock, flags); |
1082 | client->in_shutdown = true; | ||
1083 | spin_unlock_irqrestore(&client->lock, flags); | ||
1024 | 1084 | ||
1025 | /* | 1085 | idr_for_each(&client->resource_idr, shutdown_resource, client); |
1026 | * FIXME: We should wait for the async tasklets to stop | 1086 | idr_remove_all(&client->resource_idr); |
1027 | * running before freeing the memory. | 1087 | idr_destroy(&client->resource_idr); |
1028 | */ | ||
1029 | 1088 | ||
1030 | list_for_each_entry_safe(e, next_e, &client->event_list, link) | 1089 | list_for_each_entry_safe(e, next_e, &client->event_list, link) |
1031 | kfree(e); | 1090 | kfree(e); |
1032 | 1091 | ||
1092 | /* | ||
1093 | * FIXME: client should be reference-counted. It's extremely unlikely | ||
1094 | * but there may still be transactions being completed at this point. | ||
1095 | */ | ||
1033 | fw_device_put(client->device); | 1096 | fw_device_put(client->device); |
1034 | kfree(client); | 1097 | kfree(client); |
1035 | 1098 | ||