aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElric Fu <elricfu1@gmail.com>2012-06-27 16:55:43 +0800
committerBen Hutchings <ben@decadent.org.uk>2012-10-17 03:50:11 +0100
commitf3fe0babe0bef2d01701fcddcecc2ef3b3cd08af (patch)
treed9d075f8a4b735eb9dae334759f130788e05d31c
parent79c43665e46e20bf3b1029e0cb28e294b84ba6c0 (diff)
xHCI: handle command after aborting the command ring
commit b63f4053cc8aa22a98e3f9a97845afe6c15d0a0d upstream. 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. To cancel a command, software will initialize a command descriptor for the cancel command, and add it into a cancel_cmd_list of xhci. When the command ring is stopped, software will find the command trbs described by command descriptors in cancel_cmd_list and modify it to No Op command. If software can't find the matched trbs, we can think it had been finished. This patch should be backported to kernels as old as 3.0, that contain the commit 7ed603ecf8b68ab81f4c83097d3063d43ec73bb8 "xhci: Add an assertion to check for virt_dev=0 bug." That commit papers over a NULL pointer dereference, and this patch fixes the underlying issue that caused the NULL pointer dereference. Signed-off-by: Elric Fu <elricfu1@gmail.com> Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> Tested-by: Miroslav Sabljic <miroslav.sabljic@avl.com> [bwh: Backported to 3.2: inc_deq() needs an additional 'consumer' argument; Jonathan Nieder worked out that this should be false] Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
-rw-r--r--drivers/usb/host/xhci-ring.c171
1 files changed, 165 insertions, 6 deletions
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index dff89ae37dc..950aef8b82e 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -1154,6 +1154,20 @@ static void handle_reset_ep_completion(struct xhci_hcd *xhci,
}
}
+/* Complete the command and detele it from the devcie's command queue.
+ */
+static void xhci_complete_cmd_in_cmd_wait_list(struct xhci_hcd *xhci,
+ struct xhci_command *command, u32 status)
+{
+ command->status = status;
+ list_del(&command->cmd_list);
+ if (command->completion)
+ complete(command->completion);
+ else
+ xhci_free_command(xhci, command);
+}
+
+
/* Check to see if a command in the device's command queue matches this one.
* Signal the completion or free the command, and return 1. Return 0 if the
* completed command isn't at the head of the command list.
@@ -1172,15 +1186,144 @@ static int handle_cmd_in_cmd_wait_list(struct xhci_hcd *xhci,
if (xhci->cmd_ring->dequeue != command->command_trb)
return 0;
- command->status = GET_COMP_CODE(le32_to_cpu(event->status));
- list_del(&command->cmd_list);
- if (command->completion)
- complete(command->completion);
- else
- xhci_free_command(xhci, command);
+ xhci_complete_cmd_in_cmd_wait_list(xhci, command,
+ GET_COMP_CODE(le32_to_cpu(event->status)));
return 1;
}
+/*
+ * Finding the command trb need to be cancelled and modifying it to
+ * NO OP command. And if the command is in device's command wait
+ * list, finishing and freeing it.
+ *
+ * If we can't find the command trb, we think it had already been
+ * executed.
+ */
+static void xhci_cmd_to_noop(struct xhci_hcd *xhci, struct xhci_cd *cur_cd)
+{
+ struct xhci_segment *cur_seg;
+ union xhci_trb *cmd_trb;
+ u32 cycle_state;
+
+ if (xhci->cmd_ring->dequeue == xhci->cmd_ring->enqueue)
+ return;
+
+ /* find the current segment 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 the trb is link trb, continue */
+ if (TRB_TYPE_LINK_LE32(cmd_trb->generic.field[3]))
+ continue;
+
+ if (cur_cd->cmd_trb == cmd_trb) {
+
+ /* If the command in device's command list, we should
+ * finish it and free the command structure.
+ */
+ if (cur_cd->command)
+ xhci_complete_cmd_in_cmd_wait_list(xhci,
+ cur_cd->command, COMP_CMD_STOP);
+
+ /* 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 xhci_cancel_cmd_in_cd_list(struct xhci_hcd *xhci)
+{
+ struct xhci_cd *cur_cd, *next_cd;
+
+ if (list_empty(&xhci->cancel_cmd_list))
+ return;
+
+ list_for_each_entry_safe(cur_cd, next_cd,
+ &xhci->cancel_cmd_list, cancel_cmd_list) {
+ xhci_cmd_to_noop(xhci, cur_cd);
+ list_del(&cur_cd->cancel_cmd_list);
+ kfree(cur_cd);
+ }
+}
+
+/*
+ * traversing the cancel_cmd_list. If the command descriptor according
+ * to cmd_trb is found, the function free it and return 1, otherwise
+ * return 0.
+ */
+static int xhci_search_cmd_trb_in_cd_list(struct xhci_hcd *xhci,
+ union xhci_trb *cmd_trb)
+{
+ struct xhci_cd *cur_cd, *next_cd;
+
+ if (list_empty(&xhci->cancel_cmd_list))
+ return 0;
+
+ list_for_each_entry_safe(cur_cd, next_cd,
+ &xhci->cancel_cmd_list, cancel_cmd_list) {
+ if (cur_cd->cmd_trb == cmd_trb) {
+ if (cur_cd->command)
+ xhci_complete_cmd_in_cmd_wait_list(xhci,
+ cur_cd->command, COMP_CMD_STOP);
+ list_del(&cur_cd->cancel_cmd_list);
+ kfree(cur_cd);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * If the cmd_trb_comp_code is COMP_CMD_ABORT, we just check whether the
+ * trb pointed by the command ring dequeue pointer is the trb we want to
+ * cancel or not. And if the cmd_trb_comp_code is COMP_CMD_STOP, we will
+ * traverse the cancel_cmd_list to trun the all of the commands according
+ * to command descriptor to NO-OP trb.
+ */
+static int handle_stopped_cmd_ring(struct xhci_hcd *xhci,
+ int cmd_trb_comp_code)
+{
+ int cur_trb_is_good = 0;
+
+ /* Searching the cmd trb pointed by the command ring dequeue
+ * pointer in command descriptor list. If it is found, free it.
+ */
+ cur_trb_is_good = xhci_search_cmd_trb_in_cd_list(xhci,
+ xhci->cmd_ring->dequeue);
+
+ if (cmd_trb_comp_code == COMP_CMD_ABORT)
+ xhci->cmd_ring_state = CMD_RING_STATE_STOPPED;
+ else if (cmd_trb_comp_code == COMP_CMD_STOP) {
+ /* traversing the cancel_cmd_list and canceling
+ * the command according to command descriptor
+ */
+ xhci_cancel_cmd_in_cd_list(xhci);
+
+ xhci->cmd_ring_state = CMD_RING_STATE_RUNNING;
+ /*
+ * ring command ring doorbell again to restart the
+ * command ring
+ */
+ if (xhci->cmd_ring->dequeue != xhci->cmd_ring->enqueue)
+ xhci_ring_cmd_db(xhci);
+ }
+ return cur_trb_is_good;
+}
+
static void handle_cmd_completion(struct xhci_hcd *xhci,
struct xhci_event_cmd *event)
{
@@ -1206,6 +1349,22 @@ 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)) {
+ /* If the return value is 0, we think the trb pointed by
+ * command ring dequeue pointer is a good trb. The good
+ * trb means we don't want to cancel the trb, but it have
+ * been stopped by host. So we should handle it normally.
+ * Otherwise, driver should invoke inc_deq() and return.
+ */
+ if (handle_stopped_cmd_ring(xhci,
+ GET_COMP_CODE(le32_to_cpu(event->status)))) {
+ inc_deq(xhci, xhci->cmd_ring, false);
+ return;
+ }
+ }
+
switch (le32_to_cpu(xhci->cmd_ring->dequeue->generic.field[3])
& TRB_TYPE_BITMASK) {
case TRB_TYPE(TRB_ENABLE_SLOT):