+++ /dev/null
-From 9044ad57b60b0556d42b6f8aa218a68865e810a4 Mon Sep 17 00:00:00 2001
-From: Mathias Nyman <mathias.nyman@linux.intel.com>
-Date: Thu, 5 Sep 2024 17:32:49 +0300
-Subject: xhci: dbc: Fix STALL transfer event handling
-MIME-Version: 1.0
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-
-From: Mathias Nyman <mathias.nyman@linux.intel.com>
-
-commit 9044ad57b60b0556d42b6f8aa218a68865e810a4 upstream.
-
-Don't flush all pending DbC data requests when an endpoint halts.
-
-An endpoint may halt and xHC DbC triggers a STALL error event if there's
-an issue with a bulk data transfer. The transfer should restart once xHC
-DbC receives a ClearFeature(ENDPOINT_HALT) request from the host.
-
-Once xHC DbC restarts it will start from the TRB pointed to by dequeue
-field in the endpoint context, which might be the same TRB we got the
-STALL event for. Turn the TRB to a no-op in this case to make sure xHC
-DbC doesn't reuse and tries to retransmit this same TRB after we already
-handled it, and gave its corresponding data request back.
-
-Other STALL events might be completely bogus.
-Lukasz Bartosik discovered that xHC DbC might issue spurious STALL events
-if hosts sends a ClearFeature(ENDPOINT_HALT) request to non-halted
-endpoints even without any active bulk transfers.
-
-Assume STALL event is spurious if it reports 0 bytes transferred, and
-the endpoint stopped on the STALLED TRB.
-Don't give back the data request corresponding to the TRB in this case.
-
-The halted status is per endpoint. Track it with a per endpoint flag
-instead of the driver invented DbC wide DS_STALLED state.
-DbC remains in DbC-Configured state even if endpoints halt. There is no
-Stalled state in the DbC Port state Machine (xhci section 7.6.6)
-
-Reported-by: Łukasz Bartosik <ukaszb@chromium.org>
-Closes: https://lore.kernel.org/linux-usb/20240725074857.623299-1-ukaszb@chromium.org/
-Tested-by: Łukasz Bartosik <ukaszb@chromium.org>
-Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
-Link: https://lore.kernel.org/r/20240905143300.1959279-2-mathias.nyman@linux.intel.com
-Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
----
- drivers/usb/host/xhci-dbgcap.c | 132 +++++++++++++++++++++++++----------------
- drivers/usb/host/xhci-dbgcap.h | 2
- 2 files changed, 83 insertions(+), 51 deletions(-)
-
---- a/drivers/usb/host/xhci-dbgcap.c
-+++ b/drivers/usb/host/xhci-dbgcap.c
-@@ -158,16 +158,18 @@ static void xhci_dbc_giveback(struct dbc
- spin_lock(&dbc->lock);
- }
-
--static void xhci_dbc_flush_single_request(struct dbc_request *req)
-+static void trb_to_noop(union xhci_trb *trb)
- {
-- union xhci_trb *trb = req->trb;
--
- trb->generic.field[0] = 0;
- trb->generic.field[1] = 0;
- trb->generic.field[2] = 0;
- trb->generic.field[3] &= cpu_to_le32(TRB_CYCLE);
- trb->generic.field[3] |= cpu_to_le32(TRB_TYPE(TRB_TR_NOOP));
-+}
-
-+static void xhci_dbc_flush_single_request(struct dbc_request *req)
-+{
-+ trb_to_noop(req->trb);
- xhci_dbc_giveback(req, -ESHUTDOWN);
- }
-
-@@ -637,7 +639,6 @@ static void xhci_dbc_stop(struct xhci_db
- case DS_DISABLED:
- return;
- case DS_CONFIGURED:
-- case DS_STALLED:
- if (dbc->driver->disconnect)
- dbc->driver->disconnect(dbc);
- break;
-@@ -658,6 +659,23 @@ static void xhci_dbc_stop(struct xhci_db
- }
-
- static void
-+handle_ep_halt_changes(struct xhci_dbc *dbc, struct dbc_ep *dep, bool halted)
-+{
-+ if (halted) {
-+ dev_info(dbc->dev, "DbC Endpoint halted\n");
-+ dep->halted = 1;
-+
-+ } else if (dep->halted) {
-+ dev_info(dbc->dev, "DbC Endpoint halt cleared\n");
-+ dep->halted = 0;
-+
-+ if (!list_empty(&dep->list_pending))
-+ writel(DBC_DOOR_BELL_TARGET(dep->direction),
-+ &dbc->regs->doorbell);
-+ }
-+}
-+
-+static void
- dbc_handle_port_status(struct xhci_dbc *dbc, union xhci_trb *event)
- {
- u32 portsc;
-@@ -685,6 +703,7 @@ static void dbc_handle_xfer_event(struct
- struct xhci_ring *ring;
- int ep_id;
- int status;
-+ struct xhci_ep_ctx *ep_ctx;
- u32 comp_code;
- size_t remain_length;
- struct dbc_request *req = NULL, *r;
-@@ -694,8 +713,30 @@ static void dbc_handle_xfer_event(struct
- ep_id = TRB_TO_EP_ID(le32_to_cpu(event->generic.field[3]));
- dep = (ep_id == EPID_OUT) ?
- get_out_ep(dbc) : get_in_ep(dbc);
-+ ep_ctx = (ep_id == EPID_OUT) ?
-+ dbc_bulkout_ctx(dbc) : dbc_bulkin_ctx(dbc);
- ring = dep->ring;
-
-+ /* Match the pending request: */
-+ list_for_each_entry(r, &dep->list_pending, list_pending) {
-+ if (r->trb_dma == event->trans_event.buffer) {
-+ req = r;
-+ break;
-+ }
-+ if (r->status == -COMP_STALL_ERROR) {
-+ dev_warn(dbc->dev, "Give back stale stalled req\n");
-+ ring->num_trbs_free++;
-+ xhci_dbc_giveback(r, 0);
-+ }
-+ }
-+
-+ if (!req) {
-+ dev_warn(dbc->dev, "no matched request\n");
-+ return;
-+ }
-+
-+ trace_xhci_dbc_handle_transfer(ring, &req->trb->generic);
-+
- switch (comp_code) {
- case COMP_SUCCESS:
- remain_length = 0;
-@@ -706,31 +747,49 @@ static void dbc_handle_xfer_event(struct
- case COMP_TRB_ERROR:
- case COMP_BABBLE_DETECTED_ERROR:
- case COMP_USB_TRANSACTION_ERROR:
-- case COMP_STALL_ERROR:
- dev_warn(dbc->dev, "tx error %d detected\n", comp_code);
- status = -comp_code;
- break;
-+ case COMP_STALL_ERROR:
-+ dev_warn(dbc->dev, "Stall error at bulk TRB %llx, remaining %zu, ep deq %llx\n",
-+ event->trans_event.buffer, remain_length, ep_ctx->deq);
-+ status = 0;
-+ dep->halted = 1;
-+
-+ /*
-+ * xHC DbC may trigger a STALL bulk xfer event when host sends a
-+ * ClearFeature(ENDPOINT_HALT) request even if there wasn't an
-+ * active bulk transfer.
-+ *
-+ * Don't give back this transfer request as hardware will later
-+ * start processing TRBs starting from this 'STALLED' TRB,
-+ * causing TRBs and requests to be out of sync.
-+ *
-+ * If STALL event shows some bytes were transferred then assume
-+ * it's an actual transfer issue and give back the request.
-+ * In this case mark the TRB as No-Op to avoid hw from using the
-+ * TRB again.
-+ */
-+
-+ if ((ep_ctx->deq & ~TRB_CYCLE) == event->trans_event.buffer) {
-+ dev_dbg(dbc->dev, "Ep stopped on Stalled TRB\n");
-+ if (remain_length == req->length) {
-+ dev_dbg(dbc->dev, "Spurious stall event, keep req\n");
-+ req->status = -COMP_STALL_ERROR;
-+ req->actual = 0;
-+ return;
-+ }
-+ dev_dbg(dbc->dev, "Give back stalled req, but turn TRB to No-op\n");
-+ trb_to_noop(req->trb);
-+ }
-+ break;
-+
- default:
- dev_err(dbc->dev, "unknown tx error %d\n", comp_code);
- status = -comp_code;
- break;
- }
-
-- /* Match the pending request: */
-- list_for_each_entry(r, &dep->list_pending, list_pending) {
-- if (r->trb_dma == event->trans_event.buffer) {
-- req = r;
-- break;
-- }
-- }
--
-- if (!req) {
-- dev_warn(dbc->dev, "no matched request\n");
-- return;
-- }
--
-- trace_xhci_dbc_handle_transfer(ring, &req->trb->generic);
--
- ring->num_trbs_free++;
- req->actual = req->length - remain_length;
- xhci_dbc_giveback(req, status);
-@@ -750,7 +809,6 @@ static void inc_evt_deq(struct xhci_ring
- static enum evtreturn xhci_dbc_do_handle_events(struct xhci_dbc *dbc)
- {
- dma_addr_t deq;
-- struct dbc_ep *dep;
- union xhci_trb *evt;
- u32 ctrl, portsc;
- bool update_erdp = false;
-@@ -802,43 +860,17 @@ static enum evtreturn xhci_dbc_do_handle
- return EVT_DISC;
- }
-
-- /* Handle endpoint stall event: */
-+ /* Check and handle changes in endpoint halt status */
- ctrl = readl(&dbc->regs->control);
-- if ((ctrl & DBC_CTRL_HALT_IN_TR) ||
-- (ctrl & DBC_CTRL_HALT_OUT_TR)) {
-- dev_info(dbc->dev, "DbC Endpoint stall\n");
-- dbc->state = DS_STALLED;
--
-- if (ctrl & DBC_CTRL_HALT_IN_TR) {
-- dep = get_in_ep(dbc);
-- xhci_dbc_flush_endpoint_requests(dep);
-- }
--
-- if (ctrl & DBC_CTRL_HALT_OUT_TR) {
-- dep = get_out_ep(dbc);
-- xhci_dbc_flush_endpoint_requests(dep);
-- }
--
-- return EVT_DONE;
-- }
-+ handle_ep_halt_changes(dbc, get_in_ep(dbc), ctrl & DBC_CTRL_HALT_IN_TR);
-+ handle_ep_halt_changes(dbc, get_out_ep(dbc), ctrl & DBC_CTRL_HALT_OUT_TR);
-
- /* Clear DbC run change bit: */
- if (ctrl & DBC_CTRL_DBC_RUN_CHANGE) {
- writel(ctrl, &dbc->regs->control);
- ctrl = readl(&dbc->regs->control);
- }
--
- break;
-- case DS_STALLED:
-- ctrl = readl(&dbc->regs->control);
-- if (!(ctrl & DBC_CTRL_HALT_IN_TR) &&
-- !(ctrl & DBC_CTRL_HALT_OUT_TR) &&
-- (ctrl & DBC_CTRL_DBC_RUN)) {
-- dbc->state = DS_CONFIGURED;
-- break;
-- }
--
-- return EVT_DONE;
- default:
- dev_err(dbc->dev, "Unknown DbC state %d\n", dbc->state);
- break;
---- a/drivers/usb/host/xhci-dbgcap.h
-+++ b/drivers/usb/host/xhci-dbgcap.h
-@@ -81,7 +81,6 @@ enum dbc_state {
- DS_ENABLED,
- DS_CONNECTED,
- DS_CONFIGURED,
-- DS_STALLED,
- };
-
- struct dbc_ep {
-@@ -89,6 +88,7 @@ struct dbc_ep {
- struct list_head list_pending;
- struct xhci_ring *ring;
- unsigned int direction:1;
-+ unsigned int halted:1;
- };
-
- #define DBC_QUEUE_SIZE 16