Commit 0d8067f2 authored by P33M's avatar P33M Committed by popcornmix
Browse files

dwc_otg: fiq_fsm: Add non-periodic TT exclusivity constraints

Certain hub types do not discriminate between pipe direction (IN or OUT)
when considering non-periodic transfers. Therefore these hubs get confused
if multiple transfers are issued in different directions with the same
device address and endpoint number.

Constrain queuing non-periodic split transactions so they are performed
serially in such cases.

Related: https://github.com/raspberrypi/linux/issues/2024
parent 8c4dd350
Loading
Loading
Loading
Loading
+32 −0
Original line number Diff line number Diff line
@@ -191,6 +191,32 @@ static void notrace fiq_fsm_setup_csplit(struct fiq_state *st, int n)
	mb();
}

/**
 * fiq_fsm_restart_np_pending() - Restart a single non-periodic contended transfer
 * @st: Pointer to the channel's state
 * @num_channels: Total number of host channels
 * @orig_channel: Channel index of completed transfer
 *
 * In the case where an IN and OUT transfer are simultaneously scheduled to the
 * same device/EP, inadequate hub implementations will misbehave. Once the first
 * transfer is complete, a pending non-periodic split can then be issued.
 */
static void notrace fiq_fsm_restart_np_pending(struct fiq_state *st, int num_channels, int orig_channel)
{
	int i;
	int dev_addr = st->channel[orig_channel].hcchar_copy.b.devaddr;
	int ep_num = st->channel[orig_channel].hcchar_copy.b.epnum;
	for (i = 0; i < num_channels; i++) {
		if (st->channel[i].fsm == FIQ_NP_SSPLIT_PENDING &&
			st->channel[i].hcchar_copy.b.devaddr == dev_addr &&
			st->channel[i].hcchar_copy.b.epnum == ep_num) {
			st->channel[i].fsm = FIQ_NP_SSPLIT_STARTED;
			fiq_fsm_restart_channel(st, i, 0);
			break;
		}
	}
}

static inline int notrace fiq_get_xfer_len(struct fiq_state *st, int n)
{
	/* The xfersize register is a bit wonky. For IN transfers, it decrements by the packet size. */
@@ -870,6 +896,9 @@ static int notrace noinline fiq_fsm_do_hcintr(struct fiq_state *state, int num_c
				st->fsm = FIQ_NP_SPLIT_HS_ABORTED;
			}
		}
		if (st->fsm != FIQ_NP_IN_CSPLIT_RETRY) {
			fiq_fsm_restart_np_pending(state, num_channels, n);
		}
		break;

	case FIQ_NP_OUT_CSPLIT_RETRY:
@@ -919,6 +948,9 @@ static int notrace noinline fiq_fsm_do_hcintr(struct fiq_state *state, int num_c
				st->fsm = FIQ_NP_SPLIT_HS_ABORTED;
			}
		}
		if (st->fsm != FIQ_NP_OUT_CSPLIT_RETRY) {
			fiq_fsm_restart_np_pending(state, num_channels, n);
		}
		break;

	/* Periodic split states (except isoc out) */
+2 −0
Original line number Diff line number Diff line
@@ -178,6 +178,8 @@ enum fiq_fsm_state {
	/* Nonperiodic state groups */
	FIQ_NP_SSPLIT_STARTED = 1,
	FIQ_NP_SSPLIT_RETRY = 2,
	/* TT contention - working around hub bugs */
	FIQ_NP_SSPLIT_PENDING = 33,
	FIQ_NP_OUT_CSPLIT_RETRY = 3,
	FIQ_NP_IN_CSPLIT_RETRY = 4,
	FIQ_NP_SPLIT_DONE = 5,
+51 −2
Original line number Diff line number Diff line
@@ -1572,6 +1572,45 @@ int fiq_fsm_setup_periodic_dma(dwc_otg_hcd_t *hcd, struct fiq_channel_state *st,
	}
}

/**
 * fiq_fsm_np_tt_contended() - Avoid performing contended non-periodic transfers
 * @hcd: Pointer to the dwc_otg_hcd struct
 * @qh: Pointer to the endpoint's queue head
 *
 * Certain hub chips don't differentiate between IN and OUT non-periodic pipes
 * with the same endpoint number. If transfers get completed out of order
 * (disregarding the direction token) then the hub can lock up
 * or return erroneous responses.
 *
 * Returns 1 if initiating the transfer would cause contention, 0 otherwise.
 */
int fiq_fsm_np_tt_contended(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
{
	int i;
	struct fiq_channel_state *st;
	int dev_addr = qh->channel->dev_addr;
	int ep_num = qh->channel->ep_num;
	for (i = 0; i < hcd->core_if->core_params->host_channels; i++) {
		if (i == qh->channel->hc_num)
			continue;
		st = &hcd->fiq_state->channel[i];
		switch (st->fsm) {
		case FIQ_NP_SSPLIT_STARTED:
		case FIQ_NP_SSPLIT_RETRY:
		case FIQ_NP_SSPLIT_PENDING:
		case FIQ_NP_OUT_CSPLIT_RETRY:
		case FIQ_NP_IN_CSPLIT_RETRY:
			if (st->hcchar_copy.b.devaddr == dev_addr &&
				st->hcchar_copy.b.epnum == ep_num)
				return 1;
			break;
		default:
			break;
		}
	}
	return 0;
}

/*
 * Pushing a periodic request into the queue near the EOF1 point
 * in a microframe causes erroneous behaviour (frmovrun) interrupt.
@@ -1894,7 +1933,12 @@ int fiq_fsm_queue_split_transaction(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
	switch (hc->ep_type) {
		case UE_CONTROL:
		case UE_BULK:
			if (fiq_fsm_np_tt_contended(hcd, qh)) {
				st->fsm = FIQ_NP_SSPLIT_PENDING;
				start_immediate = 0;
			} else {
				st->fsm = FIQ_NP_SSPLIT_STARTED;
			}
			break;
		case UE_ISOCHRONOUS:
			if (hc->ep_is_in) {
@@ -1918,7 +1962,12 @@ int fiq_fsm_queue_split_transaction(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
			break;
		case UE_INTERRUPT:
			if (fiq_fsm_mask & 0x8) {
				if (fiq_fsm_np_tt_contended(hcd, qh)) {
					st->fsm = FIQ_NP_SSPLIT_PENDING;
					start_immediate = 0;
				} else {
					st->fsm = FIQ_NP_SSPLIT_STARTED;
				}
			} else if (start_immediate) {
					st->fsm = FIQ_PER_SSPLIT_STARTED;
			} else {