Skip to content
This repository has been archived by the owner on Jul 13, 2022. It is now read-only.

Commit

Permalink
xHCI: handle command after aborting the command ring
Browse files Browse the repository at this point in the history
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 7ed603e "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>
  • Loading branch information
Elric Fu authored and gregkh committed Oct 28, 2012
1 parent 4b360f4 commit 29b3f4e
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 6 deletions.
171 changes: 165 additions & 6 deletions drivers/usb/host/xhci-ring.c
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,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.
Expand All @@ -1175,15 +1189,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)
{
Expand All @@ -1209,6 +1352,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):
Expand Down
3 changes: 3 additions & 0 deletions drivers/usb/host/xhci.h
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down

0 comments on commit 29b3f4e

Please sign in to comment.