struct usb_gadget       *gadget;
        struct usb_composite_dev *cdev;
        struct fsg_dev          *fsg, *new_fsg;
+       wait_queue_head_t       io_wait;
        wait_queue_head_t       fsg_wait;
 
        /* filesem protects: backing files in use */
        struct rw_semaphore     filesem;
 
-       /* lock protects: state, all the req_busy's */
+       /* lock protects: state and thread_task */
        spinlock_t              lock;
 
        struct usb_ep           *ep0;           /* Copy of gadget->ep0 */
        unsigned int            running:1;
        unsigned int            sysfs:1;
 
-       int                     thread_wakeup_needed;
        struct completion       thread_notifier;
        struct task_struct      *thread_task;
 
 
 /* These routines may be called in process context or in_irq */
 
-/* Caller must hold fsg->lock */
-static void wakeup_thread(struct fsg_common *common)
-{
-       smp_wmb();      /* ensure the write of bh->state is complete */
-       /* Tell the main thread that something has happened */
-       common->thread_wakeup_needed = 1;
-       if (common->thread_task)
-               wake_up_process(common->thread_task);
-}
-
 static void raise_exception(struct fsg_common *common, enum fsg_state new_state)
 {
        unsigned long           flags;
        if (req->status == -ECONNRESET)         /* Request was cancelled */
                usb_ep_fifo_flush(ep);
 
-       /* Hold the lock while we update the request and buffer states */
-       smp_wmb();
-       spin_lock(&common->lock);
-       bh->inreq_busy = 0;
-       bh->state = BUF_STATE_EMPTY;
-       wakeup_thread(common);
-       spin_unlock(&common->lock);
+       /* Synchronize with the smp_load_acquire() in sleep_thread() */
+       smp_store_release(&bh->state, BUF_STATE_EMPTY);
+       wake_up(&common->io_wait);
 }
 
 static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req)
        if (req->status == -ECONNRESET)         /* Request was cancelled */
                usb_ep_fifo_flush(ep);
 
-       /* Hold the lock while we update the request and buffer states */
-       smp_wmb();
-       spin_lock(&common->lock);
-       bh->outreq_busy = 0;
-       bh->state = BUF_STATE_FULL;
-       wakeup_thread(common);
-       spin_unlock(&common->lock);
+       /* Synchronize with the smp_load_acquire() in sleep_thread() */
+       smp_store_release(&bh->state, BUF_STATE_FULL);
+       wake_up(&common->io_wait);
 }
 
 static int _fsg_common_get_max_lun(struct fsg_common *common)
 /* All the following routines run in process context */
 
 /* Use this for bulk or interrupt transfers, not ep0 */
-static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep,
-                          struct usb_request *req, int *pbusy,
-                          enum fsg_buffer_state *state)
+static int start_transfer(struct fsg_dev *fsg, struct usb_ep *ep,
+                          struct usb_request *req)
 {
        int     rc;
 
        if (ep == fsg->bulk_in)
                dump_msg(fsg, "bulk-in", req->buf, req->length);
 
-       spin_lock_irq(&fsg->common->lock);
-       *pbusy = 1;
-       *state = BUF_STATE_BUSY;
-       spin_unlock_irq(&fsg->common->lock);
-
        rc = usb_ep_queue(ep, req, GFP_KERNEL);
-       if (rc == 0)
-               return;  /* All good, we're done */
-
-       *pbusy = 0;
-       *state = BUF_STATE_EMPTY;
+       if (rc) {
 
-       /* We can't do much more than wait for a reset */
+               /* We can't do much more than wait for a reset */
+               req->status = rc;
 
-       /*
-        * Note: currently the net2280 driver fails zero-length
-        * submissions if DMA is enabled.
-        */
-       if (rc != -ESHUTDOWN && !(rc == -EOPNOTSUPP && req->length == 0))
-               WARNING(fsg, "error in submission: %s --> %d\n", ep->name, rc);
+               /*
+                * Note: currently the net2280 driver fails zero-length
+                * submissions if DMA is enabled.
+                */
+               if (rc != -ESHUTDOWN &&
+                               !(rc == -EOPNOTSUPP && req->length == 0))
+                       WARNING(fsg, "error in submission: %s --> %d\n",
+                                       ep->name, rc);
+       }
+       return rc;
 }
 
 static bool start_in_transfer(struct fsg_common *common, struct fsg_buffhd *bh)
 {
        if (!fsg_is_set(common))
                return false;
-       start_transfer(common->fsg, common->fsg->bulk_in,
-                      bh->inreq, &bh->inreq_busy, &bh->state);
+       bh->state = BUF_STATE_SENDING;
+       if (start_transfer(common->fsg, common->fsg->bulk_in, bh->inreq))
+               bh->state = BUF_STATE_EMPTY;
        return true;
 }
 
 {
        if (!fsg_is_set(common))
                return false;
-       start_transfer(common->fsg, common->fsg->bulk_out,
-                      bh->outreq, &bh->outreq_busy, &bh->state);
+       bh->state = BUF_STATE_RECEIVING;
+       if (start_transfer(common->fsg, common->fsg->bulk_out, bh->outreq))
+               bh->state = BUF_STATE_FULL;
        return true;
 }
 
-static int sleep_thread(struct fsg_common *common, bool can_freeze)
+static int sleep_thread(struct fsg_common *common, bool can_freeze,
+               struct fsg_buffhd *bh)
 {
-       int     rc = 0;
+       int     rc;
 
-       /* Wait until a signal arrives or we are woken up */
-       for (;;) {
-               if (can_freeze)
-                       try_to_freeze();
-               set_current_state(TASK_INTERRUPTIBLE);
-               if (signal_pending(current)) {
-                       rc = -EINTR;
-                       break;
-               }
-               if (common->thread_wakeup_needed)
-                       break;
-               schedule();
-       }
-       __set_current_state(TASK_RUNNING);
-       common->thread_wakeup_needed = 0;
-       smp_rmb();      /* ensure the latest bh->state is visible */
-       return rc;
+       /* Wait until a signal arrives or bh is no longer busy */
+       if (can_freeze)
+               /*
+                * synchronize with the smp_store_release(&bh->state) in
+                * bulk_in_complete() or bulk_out_complete()
+                */
+               rc = wait_event_freezable(common->io_wait,
+                               bh && smp_load_acquire(&bh->state) >=
+                                       BUF_STATE_EMPTY);
+       else
+               rc = wait_event_interruptible(common->io_wait,
+                               bh && smp_load_acquire(&bh->state) >=
+                                       BUF_STATE_EMPTY);
+       return rc ? -EINTR : 0;
 }
 
 
 
                /* Wait for the next buffer to become available */
                bh = common->next_buffhd_to_fill;
-               while (bh->state != BUF_STATE_EMPTY) {
-                       rc = sleep_thread(common, false);
-                       if (rc)
-                               return rc;
-               }
+               rc = sleep_thread(common, false, bh);
+               if (rc)
+                       return rc;
 
                /*
                 * If we were asked to read past the end of file,
                bh = common->next_buffhd_to_drain;
                if (bh->state == BUF_STATE_EMPTY && !get_some_more)
                        break;                  /* We stopped early */
-               if (bh->state == BUF_STATE_FULL) {
-                       smp_rmb();
-                       common->next_buffhd_to_drain = bh->next;
-                       bh->state = BUF_STATE_EMPTY;
-
-                       /* Did something go wrong with the transfer? */
-                       if (bh->outreq->status != 0) {
-                               curlun->sense_data = SS_COMMUNICATION_FAILURE;
-                               curlun->sense_data_info =
+
+               /* Wait for the data to be received */
+               rc = sleep_thread(common, false, bh);
+               if (rc)
+                       return rc;
+
+               common->next_buffhd_to_drain = bh->next;
+               bh->state = BUF_STATE_EMPTY;
+
+               /* Did something go wrong with the transfer? */
+               if (bh->outreq->status != 0) {
+                       curlun->sense_data = SS_COMMUNICATION_FAILURE;
+                       curlun->sense_data_info =
                                        file_offset >> curlun->blkbits;
-                               curlun->info_valid = 1;
-                               break;
-                       }
+                       curlun->info_valid = 1;
+                       break;
+               }
 
-                       amount = bh->outreq->actual;
-                       if (curlun->file_length - file_offset < amount) {
-                               LERROR(curlun,
-                                      "write %u @ %llu beyond end %llu\n",
+               amount = bh->outreq->actual;
+               if (curlun->file_length - file_offset < amount) {
+                       LERROR(curlun, "write %u @ %llu beyond end %llu\n",
                                       amount, (unsigned long long)file_offset,
                                       (unsigned long long)curlun->file_length);
-                               amount = curlun->file_length - file_offset;
-                       }
+                       amount = curlun->file_length - file_offset;
+               }
 
-                       /* Don't accept excess data.  The spec doesn't say
-                        * what to do in this case.  We'll ignore the error.
-                        */
-                       amount = min(amount, bh->bulk_out_intended_length);
-
-                       /* Don't write a partial block */
-                       amount = round_down(amount, curlun->blksize);
-                       if (amount == 0)
-                               goto empty_write;
-
-                       /* Perform the write */
-                       file_offset_tmp = file_offset;
-                       nwritten = vfs_write(curlun->filp,
-                                            (char __user *)bh->buf,
-                                            amount, &file_offset_tmp);
-                       VLDBG(curlun, "file write %u @ %llu -> %d\n", amount,
-                             (unsigned long long)file_offset, (int)nwritten);
-                       if (signal_pending(current))
-                               return -EINTR;          /* Interrupted! */
-
-                       if (nwritten < 0) {
-                               LDBG(curlun, "error in file write: %d\n",
-                                    (int)nwritten);
-                               nwritten = 0;
-                       } else if (nwritten < amount) {
-                               LDBG(curlun, "partial file write: %d/%u\n",
-                                    (int)nwritten, amount);
-                               nwritten = round_down(nwritten, curlun->blksize);
-                       }
-                       file_offset += nwritten;
-                       amount_left_to_write -= nwritten;
-                       common->residue -= nwritten;
+               /*
+                * Don't accept excess data.  The spec doesn't say
+                * what to do in this case.  We'll ignore the error.
+                */
+               amount = min(amount, bh->bulk_out_intended_length);
 
-                       /* If an error occurred, report it and its position */
-                       if (nwritten < amount) {
-                               curlun->sense_data = SS_WRITE_ERROR;
-                               curlun->sense_data_info =
+               /* Don't write a partial block */
+               amount = round_down(amount, curlun->blksize);
+               if (amount == 0)
+                       goto empty_write;
+
+               /* Perform the write */
+               file_offset_tmp = file_offset;
+               nwritten = vfs_write(curlun->filp, (char __user *)bh->buf,
+                               amount, &file_offset_tmp);
+               VLDBG(curlun, "file write %u @ %llu -> %d\n", amount,
+                               (unsigned long long)file_offset, (int)nwritten);
+               if (signal_pending(current))
+                       return -EINTR;          /* Interrupted! */
+
+               if (nwritten < 0) {
+                       LDBG(curlun, "error in file write: %d\n",
+                                       (int) nwritten);
+                       nwritten = 0;
+               } else if (nwritten < amount) {
+                       LDBG(curlun, "partial file write: %d/%u\n",
+                                       (int) nwritten, amount);
+                       nwritten = round_down(nwritten, curlun->blksize);
+               }
+               file_offset += nwritten;
+               amount_left_to_write -= nwritten;
+               common->residue -= nwritten;
+
+               /* If an error occurred, report it and its position */
+               if (nwritten < amount) {
+                       curlun->sense_data = SS_WRITE_ERROR;
+                       curlun->sense_data_info =
                                        file_offset >> curlun->blkbits;
-                               curlun->info_valid = 1;
-                               break;
-                       }
+                       curlun->info_valid = 1;
+                       break;
+               }
 
  empty_write:
-                       /* Did the host decide to stop early? */
-                       if (bh->outreq->actual < bh->bulk_out_intended_length) {
-                               common->short_packet_received = 1;
-                               break;
-                       }
-                       continue;
+               /* Did the host decide to stop early? */
+               if (bh->outreq->actual < bh->bulk_out_intended_length) {
+                       common->short_packet_received = 1;
+                       break;
                }
-
-               /* Wait for something to happen */
-               rc = sleep_thread(common, false);
-               if (rc)
-                       return rc;
        }
 
        return -EIO;            /* No default reply */
 
 static int throw_away_data(struct fsg_common *common)
 {
-       struct fsg_buffhd       *bh;
+       struct fsg_buffhd       *bh, *bh2;
        u32                     amount;
        int                     rc;
 
             bh->state != BUF_STATE_EMPTY || common->usb_amount_left > 0;
             bh = common->next_buffhd_to_drain) {
 
-               /* Throw away the data in a filled buffer */
-               if (bh->state == BUF_STATE_FULL) {
-                       smp_rmb();
-                       bh->state = BUF_STATE_EMPTY;
-                       common->next_buffhd_to_drain = bh->next;
-
-                       /* A short packet or an error ends everything */
-                       if (bh->outreq->actual < bh->bulk_out_intended_length ||
-                           bh->outreq->status != 0) {
-                               raise_exception(common,
-                                               FSG_STATE_ABORT_BULK_OUT);
-                               return -EINTR;
-                       }
-                       continue;
-               }
-
                /* Try to submit another request if we need one */
-               bh = common->next_buffhd_to_fill;
-               if (bh->state == BUF_STATE_EMPTY
-                && common->usb_amount_left > 0) {
+               bh2 = common->next_buffhd_to_fill;
+               if (bh2->state == BUF_STATE_EMPTY &&
+                               common->usb_amount_left > 0) {
                        amount = min(common->usb_amount_left, FSG_BUFLEN);
 
                        /*
                         * equal to the buffer size, which is divisible by
                         * the bulk-out maxpacket size.
                         */
-                       set_bulk_out_req_length(common, bh, amount);
-                       if (!start_out_transfer(common, bh))
+                       set_bulk_out_req_length(common, bh2, amount);
+                       if (!start_out_transfer(common, bh2))
                                /* Dunno what to do if common->fsg is NULL */
                                return -EIO;
-                       common->next_buffhd_to_fill = bh->next;
+                       common->next_buffhd_to_fill = bh2->next;
                        common->usb_amount_left -= amount;
                        continue;
                }
 
-               /* Otherwise wait for something to happen */
-               rc = sleep_thread(common, true);
+               /* Wait for the data to be received */
+               rc = sleep_thread(common, false, bh);
                if (rc)
                        return rc;
+
+               /* Throw away the data in a filled buffer */
+               bh->state = BUF_STATE_EMPTY;
+               common->next_buffhd_to_drain = bh->next;
+
+               /* A short packet or an error ends everything */
+               if (bh->outreq->actual < bh->bulk_out_intended_length ||
+                               bh->outreq->status != 0) {
+                       raise_exception(common, FSG_STATE_ABORT_BULK_OUT);
+                       return -EINTR;
+               }
        }
        return 0;
 }
 
        /* Wait for the next buffer to become available */
        bh = common->next_buffhd_to_fill;
-       while (bh->state != BUF_STATE_EMPTY) {
-               rc = sleep_thread(common, true);
-               if (rc)
-                       return;
-       }
+       rc = sleep_thread(common, false, bh);
+       if (rc)
+               return;
 
        if (curlun) {
                sd = curlun->sense_data;
        /* Wait for the next buffer to become available for data or status */
        bh = common->next_buffhd_to_fill;
        common->next_buffhd_to_drain = bh;
-       while (bh->state != BUF_STATE_EMPTY) {
-               rc = sleep_thread(common, true);
-               if (rc)
-                       return rc;
-       }
+       rc = sleep_thread(common, false, bh);
+       if (rc)
+               return rc;
+
        common->phase_error = 0;
        common->short_packet_received = 0;
 
 
        /* Wait for the next buffer to become available */
        bh = common->next_buffhd_to_fill;
-       while (bh->state != BUF_STATE_EMPTY) {
-               rc = sleep_thread(common, true);
-               if (rc)
-                       return rc;
-       }
+       rc = sleep_thread(common, true, bh);
+       if (rc)
+               return rc;
 
        /* Queue a request to read a Bulk-only CBW */
        set_bulk_out_req_length(common, bh, US_BULK_CB_WRAP_LEN);
         */
 
        /* Wait for the CBW to arrive */
-       while (bh->state != BUF_STATE_FULL) {
-               rc = sleep_thread(common, true);
-               if (rc)
-                       return rc;
-       }
-       smp_rmb();
+       rc = sleep_thread(common, true, bh);
+       if (rc)
+               return rc;
+
        rc = fsg_is_set(common) ? received_cbw(common->fsg, bh) : -EIO;
        bh->state = BUF_STATE_EMPTY;
 
        if (likely(common->fsg)) {
                for (i = 0; i < common->fsg_num_buffers; ++i) {
                        bh = &common->buffhds[i];
-                       if (bh->inreq_busy)
+                       if (bh->state == BUF_STATE_SENDING)
                                usb_ep_dequeue(common->fsg->bulk_in, bh->inreq);
-                       if (bh->outreq_busy)
+                       if (bh->state == BUF_STATE_RECEIVING)
                                usb_ep_dequeue(common->fsg->bulk_out,
                                               bh->outreq);
-               }
 
-               /* Wait until everything is idle */
-               for (;;) {
-                       int num_active = 0;
-                       for (i = 0; i < common->fsg_num_buffers; ++i) {
-                               bh = &common->buffhds[i];
-                               num_active += bh->inreq_busy + bh->outreq_busy;
-                       }
-                       if (num_active == 0)
-                               break;
-                       if (sleep_thread(common, true))
+                       /* Wait for a transfer to become idle */
+                       if (sleep_thread(common, false, bh))
                                return;
                }
 
                }
 
                if (!common->running) {
-                       sleep_thread(common, true);
+                       sleep_thread(common, true, NULL);
                        continue;
                }
 
        spin_lock_init(&common->lock);
        kref_init(&common->ref);
        init_completion(&common->thread_notifier);
+       init_waitqueue_head(&common->io_wait);
        init_waitqueue_head(&common->fsg_wait);
        common->state = FSG_STATE_TERMINATED;
        memset(common->luns, 0, sizeof(common->luns));