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

Commit 8899c60d authored by Hemant Kumar's avatar Hemant Kumar
Browse files

usb: xhci-msm-hsic: Reset transfer ring upon bus suspend



HW TR dequeue pointer gets stuck to a No-Op TRB while aborting
ongoing transfer during function suspend. This is triggering
frequent ring expansion and eventually system goes out of
dma memory pool. Workaround this issue by resetting HW TR dequeue
pointer to the trb of first segment of the transfer ring. Also,
reset SW enqueue and dequeue pointers to the same trb.

CRs-Fixed: 582401
Change-Id: Ib739ce66efa11035db9a4e65c313308693b09159
Signed-off-by: default avatarHemant Kumar <hemantk@codeaurora.org>
parent bfd7e850
Loading
Loading
Loading
Loading
+78 −0
Original line number Diff line number Diff line
@@ -259,6 +259,82 @@ int xhci_find_slot_id_by_port(struct usb_hcd *hcd, struct xhci_hcd *xhci,
	return slot_id;
}

/*
 * For ep 0 to 30 it resets sw enq and deq pointers to the first seg trb of the
 * ring and issues set tr deq cmd to reset hw deq pointers to the first seg trb
 * of the ring. And wait for the last command to complete.
 */
static int xhci_reset_xfer_ring(struct xhci_hcd *xhci, int slot_id)
{
	struct xhci_virt_device *virt_dev;
	struct xhci_virt_ep *ep;
	struct xhci_dequeue_state deq_state;
	struct xhci_ep_ctx *ep_ctx;
	struct xhci_command *cmd;
	unsigned long flags;
	unsigned int ep_state;
	int i;
	int timeleft;
	int ret;

	virt_dev = xhci->devs[slot_id];
	cmd = xhci_alloc_command(xhci, false, true, GFP_NOIO);
	if (!cmd) {
		xhci_dbg(xhci, "Couldn't allocate command structure.\n");
		return -ENOMEM;
	}

	spin_lock_irqsave(&xhci->lock, flags);
	for (i = LAST_EP_INDEX; i >= 0; i--) {
		ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->out_ctx, i);
		ep_state = ep_ctx->ep_info & cpu_to_le32(EP_STATE_MASK);
		ep = &virt_dev->eps[i];
		if (ep->ring && ep->ring->dequeue
			&& ep_state == cpu_to_le32(EP_STATE_STOPPED)
			&& ep->ring->enqueue != ep->ring->dequeue) {
			ep->stopped_td = NULL;
			ep->stopped_trb = NULL;
			xhci_reinit_xfer_ring(ep->ring, 1);
			memset(&deq_state, 0, sizeof(deq_state));
			deq_state.new_deq_ptr = ep->ring->first_seg->trbs;
			deq_state.new_deq_seg = ep->ring->first_seg;
			deq_state.new_cycle_state = 0x1;
			cmd->command_trb =
				xhci_find_next_enqueue(xhci->cmd_ring);
			xhci_queue_new_dequeue_state(xhci,
				slot_id, i, ep->ring->stream_id,
				&deq_state);
		}
	}

	if (!cmd->command_trb) {
		spin_unlock_irqrestore(&xhci->lock, flags);
		goto command_cleanup;
	}

	list_add_tail(&cmd->cmd_list, &virt_dev->cmd_list);
	xhci_ring_cmd_db(xhci);
	spin_unlock_irqrestore(&xhci->lock, flags);

	/* Wait for last set tr deq command to finish */
	timeleft = wait_for_completion_interruptible_timeout(
			cmd->completion,
			XHCI_CMD_DEFAULT_TIMEOUT);
	if (timeleft <= 0) {
		xhci_warn(xhci, "%s while waiting for set tr deq command\n",
				timeleft == 0 ? "Timeout" : "Signal");
		spin_lock_irqsave(&xhci->lock, flags);
		if (cmd->cmd_list.next != LIST_POISON1)
			list_del(&cmd->cmd_list);
		spin_unlock_irqrestore(&xhci->lock, flags);
		ret = -ETIME;
	}

command_cleanup:
	xhci_free_command(xhci, cmd);
	return ret;
}

/*
 * Stop device
 * It issues stop endpoint command for EP 0 to 30. And wait the last command
@@ -1257,6 +1333,8 @@ int xhci_bus_suspend(struct usb_hcd *hcd)
					port_index + 1);
			if (slot_id) {
				spin_unlock_irqrestore(&xhci->lock, flags);
				if (xhci->quirks & XHCI_TR_DEQ_RESET_QUIRK)
					xhci_reset_xfer_ring(xhci, slot_id);
				xhci_stop_device(xhci, slot_id, 1);
				spin_lock_irqsave(&xhci->lock, flags);
			}
+17 −0
Original line number Diff line number Diff line
@@ -320,6 +320,23 @@ static void xhci_reinit_cached_ring(struct xhci_hcd *xhci,
	INIT_LIST_HEAD(&ring->td_list);
}

/* Zero an endpoint ring (except for link TRBs clear only cycle bit) and move
 * the enqueue and dequeue pointers to the beginning of the ring.
 */
void xhci_reinit_xfer_ring(struct xhci_ring *ring, unsigned int cycle_state)
{
	struct xhci_segment	*seg = ring->first_seg;

	do {
		memset(seg->trbs, 0,
				sizeof(union xhci_trb)*(TRBS_PER_SEGMENT - 1));
		seg->trbs[TRBS_PER_SEGMENT - 1].link.control &= ~TRB_CYCLE;
		seg = seg->next;
	} while (seg != ring->first_seg);

	xhci_initialize_ring_info(ring, cycle_state);
}

/*
 * Expand an existing ring.
 * Look for a cached ring or allocate a new ring which has same segment numbers
+7 −0
Original line number Diff line number Diff line
@@ -467,6 +467,13 @@ static void mxhci_hsic_plat_quirks(struct device *dev, struct xhci_hcd *xhci)
	if (mxhci->wakeup_irq)
		xhci->quirks |= XHCI_NO_SELECTIVE_SUSPEND;

	/*
	 * Observing hw tr deq pointer getting stuck to a noop trb
	 * when aborting transfer during suspend. Reset tr deq pointer
	 * to start of the first seg of the xfer ring.
	 */
	xhci->quirks |= XHCI_TR_DEQ_RESET_QUIRK;

	if (!pdata)
		return;
	if (pdata->vendor == SYNOPSIS_DWC3_VENDOR &&
+3 −0
Original line number Diff line number Diff line
@@ -1146,6 +1146,9 @@ static void handle_set_deq_completion(struct xhci_hcd *xhci,
	dev->eps[ep_index].ep_state &= ~SET_DEQ_PENDING;
	dev->eps[ep_index].queued_deq_seg = NULL;
	dev->eps[ep_index].queued_deq_ptr = NULL;

	handle_cmd_in_cmd_wait_list(xhci, dev, event);

	/* Restart any rings with pending URBs */
	ring_doorbell_for_active_rings(xhci, slot_id, ep_index);
}
+2 −0
Original line number Diff line number Diff line
@@ -1553,6 +1553,7 @@ struct xhci_hcd {
 */
#define XHCI_TR_DEQ_ERR_QUIRK	(1 << 19)
#define XHCI_NO_SELECTIVE_SUSPEND (1 << 20)
#define XHCI_TR_DEQ_RESET_QUIRK   (1 << 21)
/*
 * The DWC_usb3 controller has an internal bus interval counter for tracking the
 * microframes. The following is the expected behavior of the counter: If all of
@@ -1749,6 +1750,7 @@ void xhci_slot_copy(struct xhci_hcd *xhci,
int xhci_endpoint_init(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev,
		struct usb_device *udev, struct usb_host_endpoint *ep,
		gfp_t mem_flags);
void xhci_reinit_xfer_ring(struct xhci_ring *ring, unsigned int cycle_state);
void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring);
int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring,
				unsigned int num_trbs, gfp_t flags);