diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2007-05-04 11:55:11 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2007-07-12 19:29:46 -0400 |
commit | e8054854221d9d51e381c64d365404f4c1c30f50 (patch) | |
tree | f7efd45bce5842dde7986aa700dab09548e8c7d4 /drivers/usb | |
parent | 06b84e8adcad8280d76a7c71e772c5cddba96d85 (diff) |
USB: make hub driver's release more robust
This revised patch (as893c) improves the method used by the hub driver
to release its private data structure. The current code is non-robust,
relying on a memory region not getting reused by another driver after
it has been freed. The patch adds a reference count to the structure,
resolving the question of when to release it.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/core/hub.c | 59 |
1 files changed, 31 insertions, 28 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 9464eb504ae6..77a6627b18d2 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c | |||
@@ -34,6 +34,7 @@ | |||
34 | struct usb_hub { | 34 | struct usb_hub { |
35 | struct device *intfdev; /* the "interface" device */ | 35 | struct device *intfdev; /* the "interface" device */ |
36 | struct usb_device *hdev; | 36 | struct usb_device *hdev; |
37 | struct kref kref; | ||
37 | struct urb *urb; /* for interrupt polling pipe */ | 38 | struct urb *urb; /* for interrupt polling pipe */ |
38 | 39 | ||
39 | /* buffer for urb ... with extra space in case of babble */ | 40 | /* buffer for urb ... with extra space in case of babble */ |
@@ -66,6 +67,7 @@ struct usb_hub { | |||
66 | unsigned limited_power:1; | 67 | unsigned limited_power:1; |
67 | unsigned quiescing:1; | 68 | unsigned quiescing:1; |
68 | unsigned activating:1; | 69 | unsigned activating:1; |
70 | unsigned disconnected:1; | ||
69 | 71 | ||
70 | unsigned has_indicators:1; | 72 | unsigned has_indicators:1; |
71 | u8 indicator[USB_MAXCHILDREN]; | 73 | u8 indicator[USB_MAXCHILDREN]; |
@@ -321,7 +323,7 @@ static void kick_khubd(struct usb_hub *hub) | |||
321 | to_usb_interface(hub->intfdev)->pm_usage_cnt = 1; | 323 | to_usb_interface(hub->intfdev)->pm_usage_cnt = 1; |
322 | 324 | ||
323 | spin_lock_irqsave(&hub_event_lock, flags); | 325 | spin_lock_irqsave(&hub_event_lock, flags); |
324 | if (list_empty(&hub->event_list)) { | 326 | if (!hub->disconnected & list_empty(&hub->event_list)) { |
325 | list_add_tail(&hub->event_list, &hub_event_list); | 327 | list_add_tail(&hub->event_list, &hub_event_list); |
326 | wake_up(&khubd_wait); | 328 | wake_up(&khubd_wait); |
327 | } | 329 | } |
@@ -330,6 +332,7 @@ static void kick_khubd(struct usb_hub *hub) | |||
330 | 332 | ||
331 | void usb_kick_khubd(struct usb_device *hdev) | 333 | void usb_kick_khubd(struct usb_device *hdev) |
332 | { | 334 | { |
335 | /* FIXME: What if hdev isn't bound to the hub driver? */ | ||
333 | kick_khubd(hdev_to_hub(hdev)); | 336 | kick_khubd(hdev_to_hub(hdev)); |
334 | } | 337 | } |
335 | 338 | ||
@@ -845,43 +848,42 @@ fail: | |||
845 | return ret; | 848 | return ret; |
846 | } | 849 | } |
847 | 850 | ||
851 | static void hub_release(struct kref *kref) | ||
852 | { | ||
853 | struct usb_hub *hub = container_of(kref, struct usb_hub, kref); | ||
854 | |||
855 | usb_put_intf(to_usb_interface(hub->intfdev)); | ||
856 | kfree(hub); | ||
857 | } | ||
858 | |||
848 | static unsigned highspeed_hubs; | 859 | static unsigned highspeed_hubs; |
849 | 860 | ||
850 | static void hub_disconnect(struct usb_interface *intf) | 861 | static void hub_disconnect(struct usb_interface *intf) |
851 | { | 862 | { |
852 | struct usb_hub *hub = usb_get_intfdata (intf); | 863 | struct usb_hub *hub = usb_get_intfdata (intf); |
853 | struct usb_device *hdev; | 864 | |
865 | /* Take the hub off the event list and don't let it be added again */ | ||
866 | spin_lock_irq(&hub_event_lock); | ||
867 | list_del_init(&hub->event_list); | ||
868 | hub->disconnected = 1; | ||
869 | spin_unlock_irq(&hub_event_lock); | ||
854 | 870 | ||
855 | /* Disconnect all children and quiesce the hub */ | 871 | /* Disconnect all children and quiesce the hub */ |
856 | hub->error = 0; | 872 | hub->error = 0; |
857 | hub_pre_reset(intf); | 873 | hub_pre_reset(intf); |
858 | 874 | ||
859 | usb_set_intfdata (intf, NULL); | 875 | usb_set_intfdata (intf, NULL); |
860 | hdev = hub->hdev; | ||
861 | 876 | ||
862 | if (hdev->speed == USB_SPEED_HIGH) | 877 | if (hub->hdev->speed == USB_SPEED_HIGH) |
863 | highspeed_hubs--; | 878 | highspeed_hubs--; |
864 | 879 | ||
865 | usb_free_urb(hub->urb); | 880 | usb_free_urb(hub->urb); |
866 | hub->urb = NULL; | ||
867 | |||
868 | spin_lock_irq(&hub_event_lock); | ||
869 | list_del_init(&hub->event_list); | ||
870 | spin_unlock_irq(&hub_event_lock); | ||
871 | |||
872 | kfree(hub->descriptor); | 881 | kfree(hub->descriptor); |
873 | hub->descriptor = NULL; | ||
874 | |||
875 | kfree(hub->status); | 882 | kfree(hub->status); |
876 | hub->status = NULL; | 883 | usb_buffer_free(hub->hdev, sizeof(*hub->buffer), hub->buffer, |
877 | 884 | hub->buffer_dma); | |
878 | if (hub->buffer) { | ||
879 | usb_buffer_free(hdev, sizeof(*hub->buffer), hub->buffer, | ||
880 | hub->buffer_dma); | ||
881 | hub->buffer = NULL; | ||
882 | } | ||
883 | 885 | ||
884 | kfree(hub); | 886 | kref_put(&hub->kref, hub_release); |
885 | } | 887 | } |
886 | 888 | ||
887 | static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) | 889 | static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) |
@@ -929,10 +931,12 @@ descriptor_error: | |||
929 | return -ENOMEM; | 931 | return -ENOMEM; |
930 | } | 932 | } |
931 | 933 | ||
934 | kref_init(&hub->kref); | ||
932 | INIT_LIST_HEAD(&hub->event_list); | 935 | INIT_LIST_HEAD(&hub->event_list); |
933 | hub->intfdev = &intf->dev; | 936 | hub->intfdev = &intf->dev; |
934 | hub->hdev = hdev; | 937 | hub->hdev = hdev; |
935 | INIT_DELAYED_WORK(&hub->leds, led_work); | 938 | INIT_DELAYED_WORK(&hub->leds, led_work); |
939 | usb_get_intf(intf); | ||
936 | 940 | ||
937 | usb_set_intfdata (intf, hub); | 941 | usb_set_intfdata (intf, hub); |
938 | intf->needs_remote_wakeup = 1; | 942 | intf->needs_remote_wakeup = 1; |
@@ -2534,10 +2538,12 @@ static void hub_events(void) | |||
2534 | list_del_init(tmp); | 2538 | list_del_init(tmp); |
2535 | 2539 | ||
2536 | hub = list_entry(tmp, struct usb_hub, event_list); | 2540 | hub = list_entry(tmp, struct usb_hub, event_list); |
2537 | hdev = hub->hdev; | 2541 | kref_get(&hub->kref); |
2538 | intf = to_usb_interface(hub->intfdev); | 2542 | spin_unlock_irq(&hub_event_lock); |
2539 | hub_dev = &intf->dev; | ||
2540 | 2543 | ||
2544 | hdev = hub->hdev; | ||
2545 | hub_dev = hub->intfdev; | ||
2546 | intf = to_usb_interface(hub_dev); | ||
2541 | dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n", | 2547 | dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n", |
2542 | hdev->state, hub->descriptor | 2548 | hdev->state, hub->descriptor |
2543 | ? hub->descriptor->bNbrPorts | 2549 | ? hub->descriptor->bNbrPorts |
@@ -2546,13 +2552,10 @@ static void hub_events(void) | |||
2546 | (u16) hub->change_bits[0], | 2552 | (u16) hub->change_bits[0], |
2547 | (u16) hub->event_bits[0]); | 2553 | (u16) hub->event_bits[0]); |
2548 | 2554 | ||
2549 | usb_get_intf(intf); | ||
2550 | spin_unlock_irq(&hub_event_lock); | ||
2551 | |||
2552 | /* Lock the device, then check to see if we were | 2555 | /* Lock the device, then check to see if we were |
2553 | * disconnected while waiting for the lock to succeed. */ | 2556 | * disconnected while waiting for the lock to succeed. */ |
2554 | usb_lock_device(hdev); | 2557 | usb_lock_device(hdev); |
2555 | if (hub != usb_get_intfdata(intf)) | 2558 | if (unlikely(hub->disconnected)) |
2556 | goto loop; | 2559 | goto loop; |
2557 | 2560 | ||
2558 | /* If the hub has died, clean up after it */ | 2561 | /* If the hub has died, clean up after it */ |
@@ -2715,7 +2718,7 @@ loop_autopm: | |||
2715 | usb_autopm_enable(intf); | 2718 | usb_autopm_enable(intf); |
2716 | loop: | 2719 | loop: |
2717 | usb_unlock_device(hdev); | 2720 | usb_unlock_device(hdev); |
2718 | usb_put_intf(intf); | 2721 | kref_put(&hub->kref, hub_release); |
2719 | 2722 | ||
2720 | } /* end while (1) */ | 2723 | } /* end while (1) */ |
2721 | } | 2724 | } |