diff mbox series

[v2,3/4] xhci-ring.c: Add poll pending state to properly abort transactions

Message ID 20200914023921.119630-4-jason.wessel@windriver.com
State New
Delegated to: Bin Meng
Headers show
Series xhci fixes for rpi4 | expand

Commit Message

Jason Wessel Sept. 14, 2020, 2:39 a.m. UTC
Both xhci_ctrl_tx() and xhci_bulk_tx() can be called synchronously by
other drivers such as the usb storage or network, while the keyboard
driver exclusively uses the polling mode.

The reason the abort needs to happen is for the case when a keyboard
poll was issue but there was no response packet.  If another driver
such as the usb mass storage is called, it could receive the response
from the keyboard because only a single TRB queue is used.

Any pending polling transactions must be aborted before switching
modes to avoid corrupting the state of the controller and the driver
which expects a series of commands and responses from a specific
device.

Signed-off-by: Jason Wessel <jason.wessel@windriver.com>
---
 drivers/usb/host/xhci-ring.c | 80 ++++++++++++++++++++++++++++--------
 include/usb/xhci.h           |  5 +++
 2 files changed, 69 insertions(+), 16 deletions(-)
diff mbox series

Patch

diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 607d4f715e..00a0491771 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -549,19 +549,8 @@  static void record_transfer_result(struct usb_device *udev,
 	}
 }
 
-/**** Bulk and Control transfer methods ****/
-/**
- * Queues up the BULK Request
- *
- * @param udev		pointer to the USB device structure
- * @param pipe		contains the DIR_IN or OUT , devnum
- * @param length	length of the buffer
- * @param buffer	buffer to be read/written based on the request
- * @param nonblock	when true do not block waiting for response
- * @return returns 0 if successful else -1 on failure
- */
-int xhci_bulk_tx(struct usb_device *udev, unsigned long pipe,
-			int length, void *buffer, bool nonblock)
+static int _xhci_bulk_tx_queue(struct usb_device *udev, unsigned long pipe,
+			       int length, void *buffer)
 {
 	int num_trbs = 0;
 	struct xhci_generic_trb *start_trb;
@@ -575,7 +564,6 @@  int xhci_bulk_tx(struct usb_device *udev, unsigned long pipe,
 	struct xhci_virt_device *virt_dev;
 	struct xhci_ep_ctx *ep_ctx;
 	struct xhci_ring *ring;		/* EP transfer ring */
-	union xhci_trb *event;
 
 	int running_total, trb_buff_len;
 	unsigned int total_packet_count;
@@ -719,20 +707,73 @@  int xhci_bulk_tx(struct usb_device *udev, unsigned long pipe,
 	} while (running_total < length);
 
 	giveback_first_trb(udev, ep_index, start_cycle, start_trb);
+	return 0;
+}
+
+/**** Bulk and Control transfer methods ****/
+/**
+ * Queues up the BULK Request
+ *
+ * @param udev		pointer to the USB device structure
+ * @param pipe		contains the DIR_IN or OUT , devnum
+ * @param length	length of the buffer
+ * @param buffer	buffer to be read/written based on the request
+ * @param nonblock	when true do not block waiting for response
+ * @return returns 0 if successful else -1 on failure
+ */
+int xhci_bulk_tx(struct usb_device *udev, unsigned long pipe,
+		 int length, void *buffer, bool nonblock)
+{
+	u32 field;
+	int ret;
+	union xhci_trb *event;
+	struct xhci_ctrl *ctrl = xhci_get_ctrl(udev);
+	int ep_index = usb_pipe_ep_index(pipe);
 
+	if (ctrl->poll_pend) {
+		/*
+		 * Abort a pending poll operation if it should have
+		 * timed out, or if this is a different buffer from a
+		 * separate request
+		 */
+		if (get_timer(ctrl->bulk_tx_poll_ts) > XHCI_TIMEOUT ||
+		    ctrl->last_bulk_tx_buf != buffer || ctrl->poll_last_udev != udev ||
+		    ep_index != ctrl->poll_last_ep_index) {
+			abort_td(ctrl->poll_last_udev, ctrl->poll_last_ep_index);
+			ctrl->poll_last_udev->status = USB_ST_NAK_REC;  /* closest thing to a timeout */
+			ctrl->poll_last_udev->act_len = 0;
+			ctrl->poll_pend = false;
+		}
+	} /* No else here because poll_pend might have changed above */
+	if (!ctrl->poll_pend) {
+		ctrl->last_bulk_tx_buf = buffer;
+		ret = _xhci_bulk_tx_queue(udev, pipe, length, buffer);
+		if (ret)
+			return ret;
+	}
 	event = xhci_wait_for_event(ctrl, TRB_TRANSFER, nonblock);
 	if (!event) {
-		if (nonblock)
+		if (nonblock) {
+			if (!ctrl->poll_pend) {
+				/* Start the timer */
+				ctrl->bulk_tx_poll_ts = get_timer(0);
+				ctrl->poll_last_udev = udev;
+				ctrl->poll_last_ep_index = ep_index;
+				ctrl->poll_pend = true;
+			}
 			return -EINVAL;
+		}
 		debug("XHCI bulk transfer timed out, aborting...\n");
 		abort_td(udev, ep_index);
 		udev->status = USB_ST_NAK_REC;  /* closest thing to a timeout */
 		udev->act_len = 0;
+		ctrl->poll_pend = false;
 		return -ETIMEDOUT;
 	}
+	ctrl->poll_pend = false;
 	field = le32_to_cpu(event->trans_event.flags);
 
-	BUG_ON(TRB_TO_SLOT_ID(field) != slot_id);
+	BUG_ON(TRB_TO_SLOT_ID(field) != udev->slot_id);
 	BUG_ON(TRB_TO_EP_INDEX(field) != ep_index);
 	BUG_ON(*(void **)(uintptr_t)le64_to_cpu(event->trans_event.buffer) -
 		buffer > (size_t)length);
@@ -779,6 +820,13 @@  int xhci_ctrl_tx(struct usb_device *udev, unsigned long pipe,
 		le16_to_cpu(req->value), le16_to_cpu(req->value),
 		le16_to_cpu(req->index));
 
+	if (ctrl->poll_pend) {
+		abort_td(ctrl->poll_last_udev, ctrl->poll_last_ep_index);
+		ctrl->poll_last_udev->status = USB_ST_NAK_REC;  /* closest thing to a timeout */
+		ctrl->poll_last_udev->act_len = 0;
+		ctrl->poll_pend = false;
+	}
+
 	ep_index = usb_pipe_ep_index(pipe);
 
 	ep_ring = virt_dev->eps[ep_index].ring;
diff --git a/include/usb/xhci.h b/include/usb/xhci.h
index 73db77fc02..72082b3775 100644
--- a/include/usb/xhci.h
+++ b/include/usb/xhci.h
@@ -1227,6 +1227,11 @@  struct xhci_ctrl {
 	struct xhci_scratchpad *scratchpad;
 	struct xhci_virt_device *devs[MAX_HC_SLOTS];
 	int rootdev;
+	bool poll_pend; /* Pending state for polling for an event for a udev */
+	unsigned long bulk_tx_poll_ts;
+	void *last_bulk_tx_buf;
+	struct usb_device *poll_last_udev;
+	int poll_last_ep_index;
 };
 
 unsigned long trb_addr(struct xhci_segment *seg, union xhci_trb *trb);