Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 977dcfdc authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman
Browse files

USB: OHCI: don't lose track of EDs when a controller dies



This patch fixes a bug in ohci-hcd.  When an URB is unlinked, the
corresponding Endpoint Descriptor is added to the ed_rm_list and taken
off the hardware schedule.  Once the ED is no longer visible to the
hardware, finish_unlinks() handles the URBs that were unlinked or have
completed.  If any URBs remain attached to the ED, the ED is added
back to the hardware schedule -- but only if the controller is
running.

This fails when a controller dies.  A non-empty ED does not get added
back to the hardware schedule and does not remain on the ed_rm_list;
ohci-hcd loses track of it.  The remaining URBs cannot be unlinked,
which causes the USB stack to hang.

The patch changes finish_unlinks() so that non-empty EDs remain on
the ed_rm_list if the controller isn't running.  This requires moving
some of the existing code around, to avoid modifying the ED's hardware
fields more than once.

Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
CC: <stable@vger.kernel.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 256dbcd8
Loading
Loading
Loading
Loading
+29 −17
Original line number Original line Diff line number Diff line
@@ -311,8 +311,7 @@ static void periodic_unlink (struct ohci_hcd *ohci, struct ed *ed)
 *  - ED_OPER: when there's any request queued, the ED gets rescheduled
 *  - ED_OPER: when there's any request queued, the ED gets rescheduled
 *    immediately.  HC should be working on them.
 *    immediately.  HC should be working on them.
 *
 *
 *  - ED_IDLE:  when there's no TD queue. there's no reason for the HC
 *  - ED_IDLE: when there's no TD queue or the HC isn't running.
 *    to care about this ED; safe to disable the endpoint.
 *
 *
 * When finish_unlinks() runs later, after SOF interrupt, it will often
 * When finish_unlinks() runs later, after SOF interrupt, it will often
 * complete one or more URB unlinks before making that state change.
 * complete one or more URB unlinks before making that state change.
@@ -954,6 +953,10 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
		int			completed, modified;
		int			completed, modified;
		__hc32			*prev;
		__hc32			*prev;


		/* Is this ED already invisible to the hardware? */
		if (ed->state == ED_IDLE)
			goto ed_idle;

		/* only take off EDs that the HC isn't using, accounting for
		/* only take off EDs that the HC isn't using, accounting for
		 * frame counter wraps and EDs with partially retired TDs
		 * frame counter wraps and EDs with partially retired TDs
		 */
		 */
@@ -983,12 +986,20 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
			}
			}
		}
		}


		/* ED's now officially unlinked, hc doesn't see */
		ed->state = ED_IDLE;
		if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT)
			ohci->eds_scheduled--;
		ed->hwHeadP &= ~cpu_to_hc32(ohci, ED_H);
		ed->hwNextED = 0;
		wmb();
		ed->hwINFO &= ~cpu_to_hc32(ohci, ED_SKIP | ED_DEQUEUE);
ed_idle:

		/* reentrancy:  if we drop the schedule lock, someone might
		/* reentrancy:  if we drop the schedule lock, someone might
		 * have modified this list.  normally it's just prepending
		 * have modified this list.  normally it's just prepending
		 * entries (which we'd ignore), but paranoia won't hurt.
		 * entries (which we'd ignore), but paranoia won't hurt.
		 */
		 */
		*last = ed->ed_next;
		ed->ed_next = NULL;
		modified = 0;
		modified = 0;


		/* unlink urbs as requested, but rescan the list after
		/* unlink urbs as requested, but rescan the list after
@@ -1046,19 +1057,20 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
		if (completed && !list_empty (&ed->td_list))
		if (completed && !list_empty (&ed->td_list))
			goto rescan_this;
			goto rescan_this;


		/* ED's now officially unlinked, hc doesn't see */
		/*
		ed->state = ED_IDLE;
		 * If no TDs are queued, take ED off the ed_rm_list.
		if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT)
		 * Otherwise, if the HC is running, reschedule.
			ohci->eds_scheduled--;
		 * If not, leave it on the list for further dequeues.
		ed->hwHeadP &= ~cpu_to_hc32(ohci, ED_H);
		 */
		ed->hwNextED = 0;
		if (list_empty(&ed->td_list)) {
		wmb ();
			*last = ed->ed_next;
		ed->hwINFO &= ~cpu_to_hc32 (ohci, ED_SKIP | ED_DEQUEUE);
			ed->ed_next = NULL;

		} else if (ohci->rh_state == OHCI_RH_RUNNING) {
		/* but if there's work queued, reschedule */
			*last = ed->ed_next;
		if (!list_empty (&ed->td_list)) {
			ed->ed_next = NULL;
			if (ohci->rh_state == OHCI_RH_RUNNING)
			ed_schedule(ohci, ed);
			ed_schedule(ohci, ed);
		} else {
			last = &ed->ed_next;
		}
		}


		if (modified)
		if (modified)