X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fusb%2Fstorage%2Fusb.c;h=ad22739bee825601b122b5c533e718790eeda3d3;hb=9bf4aaab3e101692164d49b7ca357651eb691cb6;hp=6342822656821ef50398b3c1070d079544487aa2;hpb=db216c3d5e4c040e557a50f8f5d35d5c415e8c1c;p=linux-2.6.git diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c index 634282265..ad22739be 100644 --- a/drivers/usb/storage/usb.c +++ b/drivers/usb/storage/usb.c @@ -211,7 +211,7 @@ static struct us_unusual_dev us_unusual_dev_list[] = { .useTransport = US_PR_BULK}, /* Terminating entry */ - { 0 } + { NULL } }; struct usb_driver usb_storage_driver = { @@ -266,6 +266,7 @@ void fill_inquiry_response(struct us_data *us, unsigned char *data, static int usb_stor_control_thread(void * __us) { struct us_data *us = (struct us_data *)__us; + struct Scsi_Host *host = us->host; lock_kernel(); @@ -283,19 +284,21 @@ static int usb_stor_control_thread(void * __us) complete(&(us->notify)); for(;;) { - struct Scsi_Host *host; US_DEBUGP("*** thread sleeping.\n"); if(down_interruptible(&us->sema)) break; US_DEBUGP("*** thread awakened.\n"); + /* lock the device pointers */ + down(&(us->dev_semaphore)); + /* if us->srb is NULL, we are being asked to exit */ if (us->srb == NULL) { US_DEBUGP("-- exit command received\n"); + up(&(us->dev_semaphore)); break; } - host = us->srb->device->host; /* lock access to the state */ scsi_lock(host); @@ -306,23 +309,20 @@ static int usb_stor_control_thread(void * __us) goto SkipForAbort; } - /* set the state and release the lock */ - us->sm_state = US_STATE_RUNNING; - scsi_unlock(host); - - /* lock the device pointers */ - down(&(us->dev_semaphore)); - /* don't do anything if we are disconnecting */ if (test_bit(US_FLIDX_DISCONNECTING, &us->flags)) { US_DEBUGP("No command during disconnect\n"); - us->srb->result = DID_BAD_TARGET << 16; + goto SkipForDisconnect; } + /* set the state and release the lock */ + us->sm_state = US_STATE_RUNNING; + scsi_unlock(host); + /* reject the command if the direction indicator * is UNKNOWN */ - else if (us->srb->sc_data_direction == SCSI_DATA_UNKNOWN) { + if (us->srb->sc_data_direction == SCSI_DATA_UNKNOWN) { US_DEBUGP("UNKNOWN data direction\n"); us->srb->result = DID_ERROR << 16; } @@ -362,9 +362,6 @@ static int usb_stor_control_thread(void * __us) us->proto_handler(us->srb, us); } - /* unlock the device pointers */ - up(&(us->dev_semaphore)); - /* lock access to the state */ scsi_lock(host); @@ -374,7 +371,7 @@ static int usb_stor_control_thread(void * __us) us->srb->result); us->srb->scsi_done(us->srb); } else { - SkipForAbort: +SkipForAbort: US_DEBUGP("scsi command aborted\n"); } @@ -387,9 +384,13 @@ static int usb_stor_control_thread(void * __us) complete(&(us->notify)); /* empty the queue, reset the state, and release the lock */ +SkipForDisconnect: us->srb = NULL; us->sm_state = US_STATE_IDLE; scsi_unlock(host); + + /* unlock the device pointers */ + up(&(us->dev_semaphore)); } /* for (;;) */ /* notify the exit routine that we're actually exiting now @@ -423,10 +424,8 @@ static int associate_dev(struct us_data *us, struct usb_interface *intf) us->pusb_intf = intf; us->ifnum = intf->cur_altsetting->desc.bInterfaceNumber; - /* Store our private data in the interface and increment the - * device's reference count */ + /* Store our private data in the interface */ usb_set_intfdata(intf, us); - usb_get_dev(us->pusb_dev); /* Allocate the device-related DMA-mapped buffers */ us->cr = usb_buffer_alloc(us->pusb_dev, sizeof(*us->cr), @@ -754,8 +753,14 @@ static int usb_stor_acquire_resources(struct us_data *us) down(&us->dev_semaphore); /* For bulk-only devices, determine the max LUN value */ - if (us->protocol == US_PR_BULK) - us->max_lun = usb_stor_Bulk_max_lun(us); + if (us->protocol == US_PR_BULK) { + p = usb_stor_Bulk_max_lun(us); + if (p < 0) { + up(&us->dev_semaphore); + return p; + } + us->max_lun = p; + } /* Just before we start our control thread, initialize * the device if it needs initialization */ @@ -764,19 +769,6 @@ static int usb_stor_acquire_resources(struct us_data *us) up(&us->dev_semaphore); - /* Start up our control thread */ - us->sm_state = US_STATE_IDLE; - p = kernel_thread(usb_stor_control_thread, us, CLONE_VM); - if (p < 0) { - printk(KERN_WARNING USB_STORAGE - "Unable to start control thread\n"); - return p; - } - us->pid = p; - - /* Wait for the thread to start */ - wait_for_completion(&(us->notify)); - /* * Since this is a new device, we need to register a SCSI * host definition with the higher SCSI layers. @@ -784,69 +776,61 @@ static int usb_stor_acquire_resources(struct us_data *us) us->host = scsi_host_alloc(&usb_stor_host_template, sizeof(us)); if (!us->host) { printk(KERN_WARNING USB_STORAGE - "Unable to register the scsi host\n"); + "Unable to allocate the scsi host\n"); return -EBUSY; } /* Set the hostdata to prepare for scanning */ us->host->hostdata[0] = (unsigned long) us; - return 0; -} - -/* Dissociate from the USB device */ -static void dissociate_dev(struct us_data *us) -{ - US_DEBUGP("-- %s\n", __FUNCTION__); - down(&us->dev_semaphore); - - /* Free the device-related DMA-mapped buffers */ - if (us->cr) { - usb_buffer_free(us->pusb_dev, sizeof(*us->cr), us->cr, - us->cr_dma); - us->cr = NULL; - } - if (us->iobuf) { - usb_buffer_free(us->pusb_dev, US_IOBUF_SIZE, us->iobuf, - us->iobuf_dma); - us->iobuf = NULL; + /* Start up our control thread */ + us->sm_state = US_STATE_IDLE; + p = kernel_thread(usb_stor_control_thread, us, CLONE_VM); + if (p < 0) { + printk(KERN_WARNING USB_STORAGE + "Unable to start control thread\n"); + return p; } + us->pid = p; - /* Remove our private data from the interface and decrement the - * device's reference count */ - usb_set_intfdata(us->pusb_intf, NULL); - usb_put_dev(us->pusb_dev); + /* Wait for the thread to start */ + wait_for_completion(&(us->notify)); - us->pusb_dev = NULL; - us->pusb_intf = NULL; - up(&us->dev_semaphore); + return 0; } -/* Release all our static and dynamic resources */ +/* Release all our dynamic resources */ void usb_stor_release_resources(struct us_data *us) { - /* - * The host must already have been removed - * and dissociate_dev() must have been called. - */ - - /* Finish the SCSI host removal sequence */ - if (us->host) { - us->host->hostdata[0] = 0; - scsi_host_put(us->host); - } + US_DEBUGP("-- %s\n", __FUNCTION__); - /* Kill the control thread - * - * Enqueue the command, wake up the thread, and wait for - * notification that it has exited. + /* Kill the control thread. The SCSI host must already have been + * removed so it won't try to queue any more commands. */ if (us->pid) { + + /* Wait for the thread to be idle */ + down(&us->dev_semaphore); US_DEBUGP("-- sending exit command to thread\n"); BUG_ON(us->sm_state != US_STATE_IDLE); + + /* If the SCSI midlayer queued a final command just before + * scsi_remove_host() was called, us->srb might not be + * NULL. We can overwrite it safely, because the midlayer + * will not wait for the command to finish. Also the + * control thread will already have been awakened. + * That's okay, an extra up() on us->sema won't hurt. + * + * Enqueue the command, wake up the thread, and wait for + * notification that it has exited. + */ + scsi_lock(us->host); us->srb = NULL; - up(&(us->sema)); - wait_for_completion(&(us->notify)); + scsi_unlock(us->host); + up(&us->dev_semaphore); + + up(&us->sema); + wait_for_completion(&us->notify); } /* Call the destructor routine, if it exists */ @@ -855,15 +839,36 @@ void usb_stor_release_resources(struct us_data *us) us->extra_destructor(us->extra); } + /* Finish the host removal sequence */ + if (us->host) + scsi_host_put(us->host); + /* Free the extra data and the URB */ if (us->extra) kfree(us->extra); if (us->current_urb) usb_free_urb(us->current_urb); +} + +/* Dissociate from the USB device */ +static void dissociate_dev(struct us_data *us) +{ + US_DEBUGP("-- %s\n", __FUNCTION__); + + /* Free the device-related DMA-mapped buffers */ + if (us->cr) + usb_buffer_free(us->pusb_dev, sizeof(*us->cr), us->cr, + us->cr_dma); + if (us->iobuf) + usb_buffer_free(us->pusb_dev, US_IOBUF_SIZE, us->iobuf, + us->iobuf_dma); + + /* Remove our private data from the interface */ + usb_set_intfdata(us->pusb_intf, NULL); + /* Free the structure itself */ kfree(us); - US_DEBUGP("-- %s finished\n", __FUNCTION__); } /* Probe to see if we can drive a newly-connected USB device */ @@ -889,6 +894,7 @@ static int storage_probe(struct usb_interface *intf, init_MUTEX(&(us->dev_semaphore)); init_MUTEX_LOCKED(&(us->sema)); init_completion(&(us->notify)); + init_waitqueue_head(&us->dev_reset_wait); /* Associate the us_data structure with the USB device */ result = associate_dev(us, intf); @@ -959,8 +965,8 @@ static int storage_probe(struct usb_interface *intf, /* We come here if there are any problems */ BadDevice: US_DEBUGP("storage_probe() failed\n"); - dissociate_dev(us); usb_stor_release_resources(us); + dissociate_dev(us); return result; } @@ -971,20 +977,20 @@ static void storage_disconnect(struct usb_interface *intf) US_DEBUGP("storage_disconnect() called\n"); - /* Prevent new USB transfers and stop the current command */ + /* Prevent new USB transfers, stop the current command, and + * interrupt a device-reset delay */ set_bit(US_FLIDX_DISCONNECTING, &us->flags); usb_stor_stop_transport(us); + wake_up(&us->dev_reset_wait); - /* Dissociate from the USB device */ - dissociate_dev(us); - + /* Wait for the current command to finish, then remove the host */ + down(&us->dev_semaphore); + up(&us->dev_semaphore); scsi_remove_host(us->host); - /* TODO: somehow, wait for the device to - * be 'idle' (tasklet completion) */ - - /* Release all our other resources */ + /* Wait for everything to become idle and release all our resources */ usb_stor_release_resources(us); + dissociate_dev(us); } /*********************************************************************** @@ -1017,47 +1023,6 @@ static void __exit usb_stor_exit(void) */ US_DEBUGP("-- calling usb_deregister()\n"); usb_deregister(&usb_storage_driver) ; - -#if 0 - /* While there are still virtual hosts, unregister them - * Note that it's important to do this completely before removing - * the structures because of possible races with the /proc - * interface - */ - for (next = us_list; next; next = next->next) { - US_DEBUGP("-- calling scsi_unregister_host()\n"); - scsi_unregister_host(&usb_stor_host_template); - } - - /* While there are still structures, free them. Note that we are - * now race-free, since these structures can no longer be accessed - * from either the SCSI command layer or the /proc interface - */ - while (us_list) { - /* keep track of where the next one is */ - next = us_list->next; - - /* If there's extra data in the us_data structure then - * free that first */ - if (us_list->extra) { - /* call the destructor routine, if it exists */ - if (us_list->extra_destructor) { - US_DEBUGP("-- calling extra_destructor()\n"); - us_list->extra_destructor(us_list->extra); - } - - /* destroy the extra data */ - US_DEBUGP("-- freeing the data structure\n"); - kfree(us_list->extra); - } - - /* free the structure itself */ - kfree (us_list); - - /* advance the list pointer */ - us_list = next; - } -#endif } module_init(usb_stor_init);