Commit 225785ae authored by Alan Stern's avatar Alan Stern Committed by Felipe Balbi

USB: f_mass_storage: improve memory barriers and synchronization

This patch reworks the way f_mass_storage.c handles memory barriers
and synchronization:

	The driver now uses a wait_queue instead of doing its own
	task-state manipulations (even though only one task will ever
	use the wait_queue).

	The thread_wakeup_needed variable is removed.  It was only a
	source of trouble; although it was what the driver tested to
	see whether it should wake up, what we really wanted to see
	was whether a USB transfer had completed.

	All the explicit memory barriers scattered throughout the
	driver are replaced by a few calls to smp_load_acquire() and
	smp_store_release().

	The inreq_busy and outreq_busy fields are removed.  In their
	place, the driver keeps track of the current I/O direction by
	splitting BUF_STATE_BUSY into two states: BUF_STATE_SENDING
	and BUF_STATE_RECEIVING.

	The buffer states are no longer protected by a lock.  Mutual
	exclusion isn't needed; the state is changed only by the
	driver's main thread when it owns the buffer, and only by the
	request completion routine when the gadget core owns the buffer.

	The do_write() and throw_away_data() routines were reorganized
	to make efficient use of the new sleeping mechanism.  This
	resulted in the removal of one indentation level in those
	routines, making the patch appear to be more more complicated
	than it really is.

	In a few places, the driver allowed itself to be frozen although
	it really shouldn't have (in the middle of executing a SCSI
	command).  Those places have been fixed.

	The logic in the exception handler for aborting transfers and
	waiting for them to stop has been simplified.
Tested-by: default avatarThinh Nguyen <thinhn@synopsys.com>
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarFelipe Balbi <felipe.balbi@linux.intel.com>
parent 78db441d
...@@ -260,12 +260,13 @@ struct fsg_common { ...@@ -260,12 +260,13 @@ struct fsg_common {
struct usb_gadget *gadget; struct usb_gadget *gadget;
struct usb_composite_dev *cdev; struct usb_composite_dev *cdev;
struct fsg_dev *fsg, *new_fsg; struct fsg_dev *fsg, *new_fsg;
wait_queue_head_t io_wait;
wait_queue_head_t fsg_wait; wait_queue_head_t fsg_wait;
/* filesem protects: backing files in use */ /* filesem protects: backing files in use */
struct rw_semaphore filesem; struct rw_semaphore filesem;
/* lock protects: state, all the req_busy's */ /* lock protects: state and thread_task */
spinlock_t lock; spinlock_t lock;
struct usb_ep *ep0; /* Copy of gadget->ep0 */ struct usb_ep *ep0; /* Copy of gadget->ep0 */
...@@ -303,7 +304,6 @@ struct fsg_common { ...@@ -303,7 +304,6 @@ struct fsg_common {
unsigned int running:1; unsigned int running:1;
unsigned int sysfs:1; unsigned int sysfs:1;
int thread_wakeup_needed;
struct completion thread_notifier; struct completion thread_notifier;
struct task_struct *thread_task; struct task_struct *thread_task;
...@@ -393,16 +393,6 @@ static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep) ...@@ -393,16 +393,6 @@ static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep)
/* These routines may be called in process context or in_irq */ /* 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) static void raise_exception(struct fsg_common *common, enum fsg_state new_state)
{ {
unsigned long flags; unsigned long flags;
...@@ -456,13 +446,9 @@ static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req) ...@@ -456,13 +446,9 @@ static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req)
if (req->status == -ECONNRESET) /* Request was cancelled */ if (req->status == -ECONNRESET) /* Request was cancelled */
usb_ep_fifo_flush(ep); usb_ep_fifo_flush(ep);
/* Hold the lock while we update the request and buffer states */ /* Synchronize with the smp_load_acquire() in sleep_thread() */
smp_wmb(); smp_store_release(&bh->state, BUF_STATE_EMPTY);
spin_lock(&common->lock); wake_up(&common->io_wait);
bh->inreq_busy = 0;
bh->state = BUF_STATE_EMPTY;
wakeup_thread(common);
spin_unlock(&common->lock);
} }
static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req) static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req)
...@@ -477,13 +463,9 @@ static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req) ...@@ -477,13 +463,9 @@ static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req)
if (req->status == -ECONNRESET) /* Request was cancelled */ if (req->status == -ECONNRESET) /* Request was cancelled */
usb_ep_fifo_flush(ep); usb_ep_fifo_flush(ep);
/* Hold the lock while we update the request and buffer states */ /* Synchronize with the smp_load_acquire() in sleep_thread() */
smp_wmb(); smp_store_release(&bh->state, BUF_STATE_FULL);
spin_lock(&common->lock); wake_up(&common->io_wait);
bh->outreq_busy = 0;
bh->state = BUF_STATE_FULL;
wakeup_thread(common);
spin_unlock(&common->lock);
} }
static int _fsg_common_get_max_lun(struct fsg_common *common) static int _fsg_common_get_max_lun(struct fsg_common *common)
...@@ -559,43 +541,39 @@ static int fsg_setup(struct usb_function *f, ...@@ -559,43 +541,39 @@ static int fsg_setup(struct usb_function *f,
/* All the following routines run in process context */ /* All the following routines run in process context */
/* Use this for bulk or interrupt transfers, not ep0 */ /* Use this for bulk or interrupt transfers, not ep0 */
static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep, static int start_transfer(struct fsg_dev *fsg, struct usb_ep *ep,
struct usb_request *req, int *pbusy, struct usb_request *req)
enum fsg_buffer_state *state)
{ {
int rc; int rc;
if (ep == fsg->bulk_in) if (ep == fsg->bulk_in)
dump_msg(fsg, "bulk-in", req->buf, req->length); 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); rc = usb_ep_queue(ep, req, GFP_KERNEL);
if (rc == 0) if (rc) {
return; /* All good, we're done */
*pbusy = 0;
*state = BUF_STATE_EMPTY;
/* 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 * Note: currently the net2280 driver fails zero-length
* submissions if DMA is enabled. * submissions if DMA is enabled.
*/ */
if (rc != -ESHUTDOWN && !(rc == -EOPNOTSUPP && req->length == 0)) if (rc != -ESHUTDOWN &&
WARNING(fsg, "error in submission: %s --> %d\n", ep->name, rc); !(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) static bool start_in_transfer(struct fsg_common *common, struct fsg_buffhd *bh)
{ {
if (!fsg_is_set(common)) if (!fsg_is_set(common))
return false; return false;
start_transfer(common->fsg, common->fsg->bulk_in, bh->state = BUF_STATE_SENDING;
bh->inreq, &bh->inreq_busy, &bh->state); if (start_transfer(common->fsg, common->fsg->bulk_in, bh->inreq))
bh->state = BUF_STATE_EMPTY;
return true; return true;
} }
...@@ -603,32 +581,31 @@ static bool start_out_transfer(struct fsg_common *common, struct fsg_buffhd *bh) ...@@ -603,32 +581,31 @@ static bool start_out_transfer(struct fsg_common *common, struct fsg_buffhd *bh)
{ {
if (!fsg_is_set(common)) if (!fsg_is_set(common))
return false; return false;
start_transfer(common->fsg, common->fsg->bulk_out, bh->state = BUF_STATE_RECEIVING;
bh->outreq, &bh->outreq_busy, &bh->state); if (start_transfer(common->fsg, common->fsg->bulk_out, bh->outreq))
bh->state = BUF_STATE_FULL;
return true; 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 */ /* Wait until a signal arrives or bh is no longer busy */
for (;;) {
if (can_freeze) if (can_freeze)
try_to_freeze(); /*
set_current_state(TASK_INTERRUPTIBLE); * synchronize with the smp_store_release(&bh->state) in
if (signal_pending(current)) { * bulk_in_complete() or bulk_out_complete()
rc = -EINTR; */
break; rc = wait_event_freezable(common->io_wait,
} bh && smp_load_acquire(&bh->state) >=
if (common->thread_wakeup_needed) BUF_STATE_EMPTY);
break; else
schedule(); rc = wait_event_interruptible(common->io_wait,
} bh && smp_load_acquire(&bh->state) >=
__set_current_state(TASK_RUNNING); BUF_STATE_EMPTY);
common->thread_wakeup_needed = 0; return rc ? -EINTR : 0;
smp_rmb(); /* ensure the latest bh->state is visible */
return rc;
} }
...@@ -688,11 +665,9 @@ static int do_read(struct fsg_common *common) ...@@ -688,11 +665,9 @@ static int do_read(struct fsg_common *common)
/* Wait for the next buffer to become available */ /* Wait for the next buffer to become available */
bh = common->next_buffhd_to_fill; bh = common->next_buffhd_to_fill;
while (bh->state != BUF_STATE_EMPTY) { rc = sleep_thread(common, false, bh);
rc = sleep_thread(common, false);
if (rc) if (rc)
return rc; return rc;
}
/* /*
* If we were asked to read past the end of file, * If we were asked to read past the end of file,
...@@ -869,8 +844,12 @@ static int do_write(struct fsg_common *common) ...@@ -869,8 +844,12 @@ static int do_write(struct fsg_common *common)
bh = common->next_buffhd_to_drain; bh = common->next_buffhd_to_drain;
if (bh->state == BUF_STATE_EMPTY && !get_some_more) if (bh->state == BUF_STATE_EMPTY && !get_some_more)
break; /* We stopped early */ break; /* We stopped early */
if (bh->state == BUF_STATE_FULL) {
smp_rmb(); /* Wait for the data to be received */
rc = sleep_thread(common, false, bh);
if (rc)
return rc;
common->next_buffhd_to_drain = bh->next; common->next_buffhd_to_drain = bh->next;
bh->state = BUF_STATE_EMPTY; bh->state = BUF_STATE_EMPTY;
...@@ -885,14 +864,14 @@ static int do_write(struct fsg_common *common) ...@@ -885,14 +864,14 @@ static int do_write(struct fsg_common *common)
amount = bh->outreq->actual; amount = bh->outreq->actual;
if (curlun->file_length - file_offset < amount) { if (curlun->file_length - file_offset < amount) {
LERROR(curlun, LERROR(curlun, "write %u @ %llu beyond end %llu\n",
"write %u @ %llu beyond end %llu\n",
amount, (unsigned long long)file_offset, amount, (unsigned long long)file_offset,
(unsigned long long)curlun->file_length); (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 /*
* Don't accept excess data. The spec doesn't say
* what to do in this case. We'll ignore the error. * what to do in this case. We'll ignore the error.
*/ */
amount = min(amount, bh->bulk_out_intended_length); amount = min(amount, bh->bulk_out_intended_length);
...@@ -904,8 +883,7 @@ static int do_write(struct fsg_common *common) ...@@ -904,8 +883,7 @@ static int do_write(struct fsg_common *common)
/* Perform the write */ /* Perform the write */
file_offset_tmp = file_offset; file_offset_tmp = file_offset;
nwritten = vfs_write(curlun->filp, nwritten = vfs_write(curlun->filp, (char __user *)bh->buf,
(char __user *)bh->buf,
amount, &file_offset_tmp); amount, &file_offset_tmp);
VLDBG(curlun, "file write %u @ %llu -> %d\n", amount, VLDBG(curlun, "file write %u @ %llu -> %d\n", amount,
(unsigned long long)file_offset, (int)nwritten); (unsigned long long)file_offset, (int)nwritten);
...@@ -914,11 +892,11 @@ static int do_write(struct fsg_common *common) ...@@ -914,11 +892,11 @@ static int do_write(struct fsg_common *common)
if (nwritten < 0) { if (nwritten < 0) {
LDBG(curlun, "error in file write: %d\n", LDBG(curlun, "error in file write: %d\n",
(int)nwritten); (int) nwritten);
nwritten = 0; nwritten = 0;
} else if (nwritten < amount) { } else if (nwritten < amount) {
LDBG(curlun, "partial file write: %d/%u\n", LDBG(curlun, "partial file write: %d/%u\n",
(int)nwritten, amount); (int) nwritten, amount);
nwritten = round_down(nwritten, curlun->blksize); nwritten = round_down(nwritten, curlun->blksize);
} }
file_offset += nwritten; file_offset += nwritten;
...@@ -940,13 +918,6 @@ static int do_write(struct fsg_common *common) ...@@ -940,13 +918,6 @@ static int do_write(struct fsg_common *common)
common->short_packet_received = 1; common->short_packet_received = 1;
break; break;
} }
continue;
}
/* Wait for something to happen */
rc = sleep_thread(common, false);
if (rc)
return rc;
} }
return -EIO; /* No default reply */ return -EIO; /* No default reply */
...@@ -1471,7 +1442,7 @@ static int wedge_bulk_in_endpoint(struct fsg_dev *fsg) ...@@ -1471,7 +1442,7 @@ static int wedge_bulk_in_endpoint(struct fsg_dev *fsg)
static int throw_away_data(struct fsg_common *common) static int throw_away_data(struct fsg_common *common)
{ {
struct fsg_buffhd *bh; struct fsg_buffhd *bh, *bh2;
u32 amount; u32 amount;
int rc; int rc;
...@@ -1479,26 +1450,10 @@ static int throw_away_data(struct fsg_common *common) ...@@ -1479,26 +1450,10 @@ static int throw_away_data(struct fsg_common *common)
bh->state != BUF_STATE_EMPTY || common->usb_amount_left > 0; bh->state != BUF_STATE_EMPTY || common->usb_amount_left > 0;
bh = common->next_buffhd_to_drain) { 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 */ /* Try to submit another request if we need one */
bh = common->next_buffhd_to_fill; bh2 = common->next_buffhd_to_fill;
if (bh->state == BUF_STATE_EMPTY if (bh2->state == BUF_STATE_EMPTY &&
&& common->usb_amount_left > 0) { common->usb_amount_left > 0) {
amount = min(common->usb_amount_left, FSG_BUFLEN); amount = min(common->usb_amount_left, FSG_BUFLEN);
/* /*
...@@ -1506,19 +1461,30 @@ static int throw_away_data(struct fsg_common *common) ...@@ -1506,19 +1461,30 @@ static int throw_away_data(struct fsg_common *common)
* equal to the buffer size, which is divisible by * equal to the buffer size, which is divisible by
* the bulk-out maxpacket size. * the bulk-out maxpacket size.
*/ */
set_bulk_out_req_length(common, bh, amount); set_bulk_out_req_length(common, bh2, amount);
if (!start_out_transfer(common, bh)) if (!start_out_transfer(common, bh2))
/* Dunno what to do if common->fsg is NULL */ /* Dunno what to do if common->fsg is NULL */
return -EIO; return -EIO;
common->next_buffhd_to_fill = bh->next; common->next_buffhd_to_fill = bh2->next;
common->usb_amount_left -= amount; common->usb_amount_left -= amount;
continue; continue;
} }
/* Otherwise wait for something to happen */ /* Wait for the data to be received */
rc = sleep_thread(common, true); rc = sleep_thread(common, false, bh);
if (rc) if (rc)
return 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; return 0;
} }
...@@ -1636,11 +1602,9 @@ static void send_status(struct fsg_common *common) ...@@ -1636,11 +1602,9 @@ static void send_status(struct fsg_common *common)
/* Wait for the next buffer to become available */ /* Wait for the next buffer to become available */
bh = common->next_buffhd_to_fill; bh = common->next_buffhd_to_fill;
while (bh->state != BUF_STATE_EMPTY) { rc = sleep_thread(common, false, bh);
rc = sleep_thread(common, true);
if (rc) if (rc)
return; return;
}
if (curlun) { if (curlun) {
sd = curlun->sense_data; sd = curlun->sense_data;
...@@ -1839,11 +1803,10 @@ static int do_scsi_command(struct fsg_common *common) ...@@ -1839,11 +1803,10 @@ static int do_scsi_command(struct fsg_common *common)
/* Wait for the next buffer to become available for data or status */ /* Wait for the next buffer to become available for data or status */
bh = common->next_buffhd_to_fill; bh = common->next_buffhd_to_fill;
common->next_buffhd_to_drain = bh; common->next_buffhd_to_drain = bh;
while (bh->state != BUF_STATE_EMPTY) { rc = sleep_thread(common, false, bh);
rc = sleep_thread(common, true);
if (rc) if (rc)
return rc; return rc;
}
common->phase_error = 0; common->phase_error = 0;
common->short_packet_received = 0; common->short_packet_received = 0;
...@@ -2186,11 +2149,9 @@ static int get_next_command(struct fsg_common *common) ...@@ -2186,11 +2149,9 @@ static int get_next_command(struct fsg_common *common)
/* Wait for the next buffer to become available */ /* Wait for the next buffer to become available */
bh = common->next_buffhd_to_fill; bh = common->next_buffhd_to_fill;
while (bh->state != BUF_STATE_EMPTY) { rc = sleep_thread(common, true, bh);
rc = sleep_thread(common, true);
if (rc) if (rc)
return rc; return rc;
}
/* Queue a request to read a Bulk-only CBW */ /* Queue a request to read a Bulk-only CBW */
set_bulk_out_req_length(common, bh, US_BULK_CB_WRAP_LEN); set_bulk_out_req_length(common, bh, US_BULK_CB_WRAP_LEN);
...@@ -2205,12 +2166,10 @@ static int get_next_command(struct fsg_common *common) ...@@ -2205,12 +2166,10 @@ static int get_next_command(struct fsg_common *common)
*/ */
/* Wait for the CBW to arrive */ /* Wait for the CBW to arrive */
while (bh->state != BUF_STATE_FULL) { rc = sleep_thread(common, true, bh);
rc = sleep_thread(common, true);
if (rc) if (rc)
return rc; return rc;
}
smp_rmb();
rc = fsg_is_set(common) ? received_cbw(common->fsg, bh) : -EIO; rc = fsg_is_set(common) ? received_cbw(common->fsg, bh) : -EIO;
bh->state = BUF_STATE_EMPTY; bh->state = BUF_STATE_EMPTY;
...@@ -2374,23 +2333,14 @@ static void handle_exception(struct fsg_common *common) ...@@ -2374,23 +2333,14 @@ static void handle_exception(struct fsg_common *common)
if (likely(common->fsg)) { if (likely(common->fsg)) {
for (i = 0; i < common->fsg_num_buffers; ++i) { for (i = 0; i < common->fsg_num_buffers; ++i) {
bh = &common->buffhds[i]; bh = &common->buffhds[i];
if (bh->inreq_busy) if (bh->state == BUF_STATE_SENDING)
usb_ep_dequeue(common->fsg->bulk_in, bh->inreq); 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, usb_ep_dequeue(common->fsg->bulk_out,
bh->outreq); bh->outreq);
}
/* Wait until everything is idle */ /* Wait for a transfer to become idle */
for (;;) { if (sleep_thread(common, false, bh))
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))
return; return;
} }
...@@ -2518,7 +2468,7 @@ static int fsg_main_thread(void *common_) ...@@ -2518,7 +2468,7 @@ static int fsg_main_thread(void *common_)
} }
if (!common->running) { if (!common->running) {
sleep_thread(common, true); sleep_thread(common, true, NULL);
continue; continue;
} }
...@@ -2648,6 +2598,7 @@ static struct fsg_common *fsg_common_setup(struct fsg_common *common) ...@@ -2648,6 +2598,7 @@ static struct fsg_common *fsg_common_setup(struct fsg_common *common)
spin_lock_init(&common->lock); spin_lock_init(&common->lock);
kref_init(&common->ref); kref_init(&common->ref);
init_completion(&common->thread_notifier); init_completion(&common->thread_notifier);
init_waitqueue_head(&common->io_wait);
init_waitqueue_head(&common->fsg_wait); init_waitqueue_head(&common->fsg_wait);
common->state = FSG_STATE_TERMINATED; common->state = FSG_STATE_TERMINATED;
memset(common->luns, 0, sizeof(common->luns)); memset(common->luns, 0, sizeof(common->luns));
......
...@@ -133,9 +133,10 @@ static inline bool fsg_lun_is_open(struct fsg_lun *curlun) ...@@ -133,9 +133,10 @@ static inline bool fsg_lun_is_open(struct fsg_lun *curlun)
#define FSG_MAX_LUNS 16 #define FSG_MAX_LUNS 16
enum fsg_buffer_state { enum fsg_buffer_state {
BUF_STATE_SENDING = -2,
BUF_STATE_RECEIVING,
BUF_STATE_EMPTY = 0, BUF_STATE_EMPTY = 0,
BUF_STATE_FULL, BUF_STATE_FULL
BUF_STATE_BUSY
}; };
struct fsg_buffhd { struct fsg_buffhd {
...@@ -151,9 +152,7 @@ struct fsg_buffhd { ...@@ -151,9 +152,7 @@ struct fsg_buffhd {
unsigned int bulk_out_intended_length; unsigned int bulk_out_intended_length;
struct usb_request *inreq; struct usb_request *inreq;
int inreq_busy;
struct usb_request *outreq; struct usb_request *outreq;
int outreq_busy;
}; };
enum fsg_state { enum fsg_state {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment