| From b63f4053cc8aa22a98e3f9a97845afe6c15d0a0d Mon Sep 17 00:00:00 2001 |
| From: Elric Fu <elricfu1@gmail.com> |
| Date: Wed, 27 Jun 2012 16:55:43 +0800 |
| Subject: xHCI: handle command after aborting the command ring |
| |
| From: Elric Fu <elricfu1@gmail.com> |
| |
| 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. |
| |
| Note from Sarah: The TRB_TYPE_LINK_LE32 macro is not in the 3.0 stable |
| kernel, so I added it to this patch. |
| |
| 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> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| |
| --- |
| drivers/usb/host/xhci-ring.c | 171 +++++++++++++++++++++++++++++++++++++++++-- |
| drivers/usb/host/xhci.h | 3 |
| 2 files changed, 168 insertions(+), 6 deletions(-) |
| |
| --- a/drivers/usb/host/xhci-ring.c |
| +++ b/drivers/usb/host/xhci-ring.c |
| @@ -1157,6 +1157,20 @@ static void handle_reset_ep_completion(s |
| } |
| } |
| |
| +/* 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. |
| @@ -1175,15 +1189,144 @@ static int handle_cmd_in_cmd_wait_list(s |
| 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) |
| { |
| @@ -1209,6 +1352,22 @@ static void handle_cmd_completion(struct |
| 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): |
| --- a/drivers/usb/host/xhci.h |
| +++ b/drivers/usb/host/xhci.h |
| @@ -1070,6 +1070,9 @@ union xhci_trb { |
| #define TRB_MFINDEX_WRAP 39 |
| /* TRB IDs 40-47 reserved, 48-63 is vendor-defined */ |
| |
| +#define TRB_TYPE_LINK_LE32(x) (((x) & cpu_to_le32(TRB_TYPE_BITMASK)) == \ |
| + cpu_to_le32(TRB_TYPE(TRB_LINK))) |
| + |
| /* Nec vendor-specific command completion event. */ |
| #define TRB_NEC_CMD_COMP 48 |
| /* Get NEC firmware revision. */ |