[RFT] xHCI: cancel command queued in the command ring

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Software have to abort command ring and cancel command
when a command is failed or hang. Otherwise, the command
ring will hang and can't handle another commands. An
example of a command that may hang is the Address Device
Command, because waiting for a SET_ADDRESS request to be
acknowledged by a USB device is outside of the xHC's
ability to control.

According to xHCI spec section 4.6.1.1 and section 4.6.1.2,
after aborting a command on the command ring, xHC will
generate a command completion event with its completion code
set to Command Ring Stopped at least. If a command is
currently executing at the time of aborting a command, xHC
also generate a command completion event with its completion
code set to Command Abort. When the command ring is stopped,
software may remove, add, or rearrage Command Descriptors.

The RFT patch use to cancel command when the above situation
occur.

To cancel a command, software will initialize a command
descriptor for the cancel command, and add it into a
cancel_cd_list of xhci. When the command ring is stopped,
software will find the command trbs described by command
descriptors in cancel_cd_list and modify it to No Op
command. If software can't find the matched trbs, we can
think it had been finished.

Signed-off-by: Elric Fu <elricfu1@xxxxxxxxx>
---
 drivers/usb/host/xhci-mem.c  |    1 +
 drivers/usb/host/xhci-ring.c |  238 ++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/host/xhci.c      |   12 ++-
 drivers/usb/host/xhci.h      |   18 +++
 4 files changed, 266 insertions(+), 3 deletions(-)

diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index 383fc85..84e7245 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -2238,6 +2238,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
 	xhci->cmd_ring = xhci_ring_alloc(xhci, 1, true, false, flags);
 	if (!xhci->cmd_ring)
 		goto fail;
+	INIT_LIST_HEAD(&xhci->cancel_cd_list);
 	xhci_dbg(xhci, "Allocated command ring at %p\n", xhci->cmd_ring);
 	xhci_dbg(xhci, "First segment DMA is 0x%llx\n",
 			(unsigned long long)xhci->cmd_ring->first_seg->dma);
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index b62037b..d4d7f8d 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -292,12 +292,119 @@ static int room_on_ring(struct xhci_hcd *xhci, struct xhci_ring *ring,
 /* Ring the host controller doorbell after placing a command on the ring */
 void xhci_ring_cmd_db(struct xhci_hcd *xhci)
 {
+	if (!(xhci->cmd_ring_state & CMD_RING_STATE_RUNNING))
+		return;
+
 	xhci_dbg(xhci, "// Ding dong!\n");
 	xhci_writel(xhci, DB_VALUE_HOST, &xhci->dba->doorbell[0]);
 	/* Flush PCI posted writes */
 	xhci_readl(xhci, &xhci->dba->doorbell[0]);
 }
 
+int xhci_abort_cmd_ring(struct xhci_hcd *xhci)
+{
+	u64 temp_64;
+	int ret;
+
+	xhci_dbg(xhci, "Abort command ring\n");
+
+	if (!(xhci->cmd_ring_state & CMD_RING_STATE_RUNNING)) {
+		xhci_dbg(xhci, "The command ring isn't running, "
+				"Have the command ring been stopped?");
+		return -EINVAL;
+	}
+
+	temp_64 = xhci_read_64(xhci, &xhci->op_regs->cmd_ring);
+	if (!(temp_64 & CMD_RING_RUNNING)) {
+		xhci_dbg(xhci, "Command ring had been stopped\n");
+		return -EINVAL;
+	}
+	xhci->cmd_ring_state = CMD_RING_STATE_ABORTED;
+	xhci_write_64(xhci, temp_64 | CMD_RING_ABORT,
+			&xhci->op_regs->cmd_ring);
+
+	/* Section 4.6.1.2 of xHCI 1.0 spec says software should
+	 * time the completion od all xHCI commands, including
+	 * the Command Abort operation. If software doesn't see
+	 * CRR negated in a timely manner (e.g. longer than 5
+	 * seconds), then it should assume that the there are
+	 * larger problems with the xHC and assert HCRST.
+	 */
+	ret = handshake(xhci, &xhci->op_regs->cmd_ring,
+			CMD_RING_RUNNING, 0, 5 * 1000 * 1000);
+	if (ret < 0) {
+		xhci_err(xhci, "Stopped the command ring failed, "
+				"maybe the host is dead\n");
+		xhci->xhc_state |= XHCI_STATE_DYING;
+		xhci_quiesce(xhci);
+		xhci_halt(xhci);
+		return -ESHUTDOWN;
+	}
+
+	return 0;
+}
+
+static int queue_cd(struct xhci_hcd *xhci, int slot_id,
+		u32 cmd_type, int ep_index)
+{
+	struct xhci_cd *cd;
+
+	while (1) {
+		if (!(xhci->cmd_ring_state & CMD_RING_STATE_MODIFYING))
+			break;
+	}
+
+	cd = kzalloc(sizeof(struct xhci_cd), GFP_ATOMIC);
+	if (!cd)
+		return -ENOMEM;
+	INIT_LIST_HEAD(&cd->cancel_cd_list);
+
+	cd->slot_id = slot_id;
+	cd->cmd_type = cmd_type;
+	cd->ep_index = ep_index;
+	list_add_tail(&cd->cancel_cd_list, &xhci->cancel_cd_list);
+
+	return 0;
+}
+
+int xhci_cancel_cmd(struct xhci_hcd *xhci, int slot_id,
+		u32 cmd_type, int ep_index)
+{
+	int retval;
+	unsigned long flags;
+
+	spin_lock_irqsave(&xhci->lock, flags);
+
+	if (xhci->xhc_state & XHCI_STATE_DYING) {
+		xhci_warn(xhci, "Abort the command ring,"
+				" but the xHCI is dead.\n");
+		spin_unlock_irqrestore(&xhci->lock, flags);
+		return -ESHUTDOWN;
+	}
+
+	/* queue the cmd desriptor to cancelled_cd_list */
+	retval = queue_cd(xhci, slot_id, cmd_type, ep_index);
+	if (retval) {
+		spin_unlock_irqrestore(&xhci->lock, flags);
+		return retval;
+	}
+
+	/* abort command ring */
+	retval = xhci_abort_cmd_ring(xhci);
+	if (retval) {
+		xhci_err(xhci, "Abort command ring failed\n");
+		spin_unlock_irqrestore(&xhci->lock, flags);
+		if (retval == -ESHUTDOWN) {
+			usb_hc_died(xhci_to_hcd(xhci)->primary_hcd);
+			xhci_dbg(xhci, "xHCI host controller is dead.\n");
+		}
+		return retval;
+	}
+
+	spin_unlock_irqrestore(&xhci->lock, flags);
+	return 0;
+}
+
 void xhci_ring_ep_doorbell(struct xhci_hcd *xhci,
 		unsigned int slot_id,
 		unsigned int ep_index,
@@ -1050,6 +1157,130 @@ static int handle_cmd_in_cmd_wait_list(struct xhci_hcd *xhci,
 	return 1;
 }
 
+/*
+ * Find the command trb need to be cancelled and modify it to NO OP
+ * command.
+ *
+ * If we can't find the command trb, we think it had already been
+ * executed.
+ */
+static void cd_to_noop(struct xhci_hcd *xhci, struct xhci_cd *cur_cd)
+{
+	struct xhci_segment *cur_seg;
+	union xhci_trb *cmd_trb;
+	int slot_id;
+	u32 cmd_type;
+	u32 cycle_state;
+	int ep_index;
+
+	if (xhci->cmd_ring->dequeue == xhci->cmd_ring->enqueue)
+		return;
+
+	/* find the current segmene of command ring */
+	cur_seg = find_trb_seg(xhci->cmd_ring->first_seg,
+			xhci->cmd_ring->dequeue, &cycle_state);
+
+	/* find the command trb matched by CD from command ring */
+	for (cmd_trb = xhci->cmd_ring->dequeue;
+			cmd_trb != xhci->cmd_ring->enqueue;
+			next_trb(xhci, xhci->cmd_ring, &cur_seg, &cmd_trb)) {
+		if (TRB_TYPE_LINK_LE32(cmd_trb->generic.field[3]))
+			continue;
+
+		slot_id = TRB_TO_SLOT_ID(
+				le32_to_cpu(cmd_trb->generic.field[3]));
+		cmd_type = TRB_FIELD_TO_TYPE(
+				le32_to_cpu(cmd_trb->generic.field[3]));
+		if ((slot_id == cur_cd->slot_id) &&
+				(cmd_type == cur_cd->cmd_type)) {
+
+			/* The ep_index in the command desriptors of Stop
+			 * Endpoint command, Set TR Dequeue Pointer command
+			 * and Reset Endpoint command is vaild. so it should
+			 * be match the ep_index of cd.
+			 */
+			if ((cmd_type == TRB_STOP_RING) ||
+					(cmd_type == TRB_SET_DEQ) ||
+					(cmd_type == TRB_RESET_EP)) {
+				ep_index = TRB_TO_EP_INDEX(le32_to_cpu(
+						cmd_trb->generic.field[3]));
+				if (ep_index != cur_cd->ep_index)
+					continue;
+			}
+
+			/* get cycle state from the origin command trb */
+			cycle_state = le32_to_cpu(cmd_trb->generic.field[3])
+				& TRB_CYCLE;
+
+			/* modify the command trb to NO OP command */
+			cmd_trb->generic.field[0] = 0;
+			cmd_trb->generic.field[1] = 0;
+			cmd_trb->generic.field[2] = 0;
+			cmd_trb->generic.field[3] = cpu_to_le32(
+					TRB_TYPE(TRB_CMD_NOOP) | cycle_state);
+			break;
+		}
+	}
+}
+
+static void cancel_cd(struct xhci_hcd *xhci)
+{
+	struct xhci_cd *cur_cd;
+	struct list_head *entry;
+	struct list_head *next;
+
+	if (list_empty(&xhci->cancel_cd_list))
+		return;
+
+	list_for_each_safe(entry, next, &xhci->cancel_cd_list) {
+		cur_cd = list_entry(entry, struct xhci_cd, cancel_cd_list);
+		xhci_dbg(xhci, "Cancel command, slot %d, cmd type %d, "
+				"ep_index = %d\n", cur_cd->slot_id,
+				cur_cd->cmd_type, cur_cd->ep_index);
+		cd_to_noop(xhci, cur_cd);
+
+		/* Whatever we find the matched command trb, we need to
+		 * release the command descriptor.
+		 */
+		list_del(&cur_cd->cancel_cd_list);
+		kfree(cur_cd);
+	}
+}
+
+static void handle_stopped_cmd_ring(struct xhci_hcd *xhci,
+		struct xhci_event_cmd *event)
+{
+	struct xhci_virt_device *virt_dev;
+	int slot_id = TRB_TO_SLOT_ID(
+			le32_to_cpu(xhci->cmd_ring->dequeue->generic.field[3]));
+	int cmd_trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->status));
+
+	virt_dev = xhci->devs[slot_id];
+	if (virt_dev) {
+		/* if the command is in cmd_list, handle it. */
+		handle_cmd_in_cmd_wait_list(xhci, virt_dev, event);
+	}
+
+	/* advance the dequeue pointer to next cmd trb */
+	inc_deq(xhci, xhci->cmd_ring, false);
+
+	/* set the cmd ring state */
+	if (cmd_trb_comp_code != COMP_CMD_STOP) {
+		xhci->cmd_ring_state = CMD_RING_STATE_STOPPED;
+	} else {
+		xhci->cmd_ring_state = CMD_RING_STATE_MODIFYING;
+		cancel_cd(xhci);
+		xhci->cmd_ring_state = CMD_RING_STATE_RUNNING;
+
+		/*
+		 * ring command ring doorbell again to handle the waiting
+		 * command trbs due to aborting command ring
+		 */
+		if (xhci->cmd_ring->dequeue != xhci->cmd_ring->enqueue)
+			xhci_ring_cmd_db(xhci);
+	}
+}
+
 static void handle_cmd_completion(struct xhci_hcd *xhci,
 		struct xhci_event_cmd *event)
 {
@@ -1075,6 +1306,13 @@ static void handle_cmd_completion(struct xhci_hcd *xhci,
 		xhci->error_bitmask |= 1 << 5;
 		return;
 	}
+
+	if ((GET_COMP_CODE(le32_to_cpu(event->status)) == COMP_CMD_ABORT) ||
+		(GET_COMP_CODE(le32_to_cpu(event->status)) == COMP_CMD_STOP)) {
+		handle_stopped_cmd_ring(xhci, event);
+		return;
+	}
+
 	switch (le32_to_cpu(xhci->cmd_ring->dequeue->generic.field[3])
 		& TRB_TYPE_BITMASK) {
 	case TRB_TYPE(TRB_ENABLE_SLOT):
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index c939f5f..9434771 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -51,7 +51,7 @@ MODULE_PARM_DESC(link_quirk, "Don't clear the chain bit on a link TRB");
  * handshake done).  There are two failure modes:  "usec" have passed (major
  * hardware flakeout), or the register reads as all-ones (hardware removed).
  */
-static int handshake(struct xhci_hcd *xhci, void __iomem *ptr,
+int handshake(struct xhci_hcd *xhci, void __iomem *ptr,
 		      u32 mask, u32 done, int usec)
 {
 	u32	result;
@@ -104,8 +104,10 @@ int xhci_halt(struct xhci_hcd *xhci)
 
 	ret = handshake(xhci, &xhci->op_regs->status,
 			STS_HALT, STS_HALT, XHCI_MAX_HALT_USEC);
-	if (!ret)
+	if (!ret) {
 		xhci->xhc_state |= XHCI_STATE_HALTED;
+		xhci->cmd_ring_state = CMD_RING_STATE_STOPPED;
+	}
 	return ret;
 }
 
@@ -473,6 +475,7 @@ static int xhci_run_finished(struct xhci_hcd *xhci)
 		return -ENODEV;
 	}
 	xhci->shared_hcd->state = HC_STATE_RUNNING;
+	xhci->cmd_ring_state = CMD_RING_STATE_RUNNING;
 
 	if (xhci->quirks & XHCI_NEC_HOST)
 		xhci_ring_cmd_db(xhci);
@@ -3542,7 +3545,10 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev)
 	if (timeleft <= 0) {
 		xhci_warn(xhci, "%s while waiting for address device command\n",
 				timeleft == 0 ? "Timeout" : "Signal");
-		/* FIXME cancel the address device command */
+		/* cancel the address device command */
+		ret = xhci_cancel_cmd(xhci, udev->slot_id, TRB_ADDR_DEV, 0);
+		if (ret < 0)
+			return ret;
 		return -ETIME;
 	}
 
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 5b29d3c..8e59c42 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1247,6 +1247,14 @@ struct xhci_td {
 	union xhci_trb		*last_trb;
 };
 
+/* command descriptor */
+struct xhci_cd {
+	struct list_head        cancel_cd_list;
+	int                     slot_id;
+	u32                     cmd_type;
+	int			ep_index;
+};
+
 struct xhci_dequeue_state {
 	struct xhci_segment *new_deq_seg;
 	union xhci_trb *new_deq_ptr;
@@ -1394,6 +1402,12 @@ struct xhci_hcd {
 	/* data structures */
 	struct xhci_device_context_array *dcbaa;
 	struct xhci_ring	*cmd_ring;
+	unsigned int		cmd_ring_state;
+#define CMD_RING_STATE_RUNNING		(1 << 0)
+#define CMD_RING_STATE_ABORTED		(1 << 1)
+#define CMD_RING_STATE_MODIFYING	(1 << 2)
+#define CMD_RING_STATE_STOPPED		(1 << 3)
+	struct list_head        cancel_cd_list;
 	unsigned int		cmd_ring_reserved_trbs;
 	struct xhci_ring	*event_ring;
 	struct xhci_erst	erst;
@@ -1651,6 +1665,8 @@ static inline void xhci_unregister_pci(void) {}
 
 /* xHCI host controller glue */
 typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *);
+int handshake(struct xhci_hcd *xhci, void __iomem *ptr,
+		      u32 mask, u32 done, int usec);
 void xhci_quiesce(struct xhci_hcd *xhci);
 int xhci_halt(struct xhci_hcd *xhci);
 int xhci_reset(struct xhci_hcd *xhci);
@@ -1741,6 +1757,8 @@ void xhci_queue_config_ep_quirk(struct xhci_hcd *xhci,
 		unsigned int slot_id, unsigned int ep_index,
 		struct xhci_dequeue_state *deq_state);
 void xhci_stop_endpoint_command_watchdog(unsigned long arg);
+int xhci_cancel_cmd(struct xhci_hcd *xhci, int slot_id,
+		u32 cmd_type, int ep_index);
 void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, unsigned int slot_id,
 		unsigned int ep_index, unsigned int stream_id);
 
-- 
1.7.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux