Skip to content

Commit

Permalink
USB: xhci: Change how xHCI commands are handled.
Browse files Browse the repository at this point in the history
Some commands to the xHCI hardware cannot be allowed to fail due to out of
memory issues or the command ring being full.

Add a way to reserve a TRB on the command ring, and make all command
queueing functions indicate whether they are using a reserved TRB.

Add a way to pre-allocate all the memory a command might need.  A command
needs an input context, a variable to store the status, and (optionally) a
completion for the caller to wait on.  Change all code that assumes the
input device context, status, and completion for a command is stored in
the xhci virtual USB device structure (xhci_virt_device).

Store pending completions in a FIFO in xhci_virt_device.  Make the event
handler for a configure endpoint command check to see whether a pending
command in the list has completed.  We need to use separate input device
contexts for some configure endpoint commands, since multiple drivers can
submit requests at the same time that require a configure endpoint
command.

Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
  • Loading branch information
Sarah Sharp authored and Greg Kroah-Hartman committed Sep 23, 2009
1 parent 5270b95 commit 913a8a3
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 53 deletions.
82 changes: 52 additions & 30 deletions drivers/usb/host/xhci-hcd.c
Original file line number Diff line number Diff line change
Expand Up @@ -612,8 +612,8 @@ int xhci_check_args(struct usb_hcd *hcd, struct usb_device *udev,
}

static int xhci_configure_endpoint(struct xhci_hcd *xhci,
struct usb_device *udev, struct xhci_virt_device *virt_dev,
bool ctx_change);
struct usb_device *udev, struct xhci_command *command,
bool ctx_change, bool must_succeed);

/*
* Full speed devices may have a max packet size greater than 8 bytes, but the
Expand Down Expand Up @@ -645,7 +645,8 @@ static int xhci_check_maxpacket(struct xhci_hcd *xhci, unsigned int slot_id,
xhci_dbg(xhci, "Issuing evaluate context command.\n");

/* Set up the modified control endpoint 0 */
xhci_endpoint_copy(xhci, xhci->devs[slot_id], ep_index);
xhci_endpoint_copy(xhci, xhci->devs[slot_id]->in_ctx,
xhci->devs[slot_id]->out_ctx, ep_index);
in_ctx = xhci->devs[slot_id]->in_ctx;
ep_ctx = xhci_get_ep_ctx(xhci, in_ctx, ep_index);
ep_ctx->ep_info2 &= ~MAX_PACKET_MASK;
Expand All @@ -664,8 +665,8 @@ static int xhci_check_maxpacket(struct xhci_hcd *xhci, unsigned int slot_id,
xhci_dbg(xhci, "Slot %d output context\n", slot_id);
xhci_dbg_ctx(xhci, out_ctx, ep_index);

ret = xhci_configure_endpoint(xhci, urb->dev,
xhci->devs[slot_id], true);
ret = xhci_configure_endpoint(xhci, urb->dev, NULL,
true, false);

/* Clean up the input context for later use by bandwidth
* functions.
Expand Down Expand Up @@ -1038,11 +1039,11 @@ static void xhci_zero_in_ctx(struct xhci_hcd *xhci, struct xhci_virt_device *vir
}

static int xhci_configure_endpoint_result(struct xhci_hcd *xhci,
struct usb_device *udev, struct xhci_virt_device *virt_dev)
struct usb_device *udev, int *cmd_status)
{
int ret;

switch (virt_dev->cmd_status) {
switch (*cmd_status) {
case COMP_ENOMEM:
dev_warn(&udev->dev, "Not enough host controller resources "
"for new device state.\n");
Expand All @@ -1068,19 +1069,20 @@ static int xhci_configure_endpoint_result(struct xhci_hcd *xhci,
break;
default:
xhci_err(xhci, "ERROR: unexpected command completion "
"code 0x%x.\n", virt_dev->cmd_status);
"code 0x%x.\n", *cmd_status);
ret = -EINVAL;
break;
}
return ret;
}

static int xhci_evaluate_context_result(struct xhci_hcd *xhci,
struct usb_device *udev, struct xhci_virt_device *virt_dev)
struct usb_device *udev, int *cmd_status)
{
int ret;
struct xhci_virt_device *virt_dev = xhci->devs[udev->slot_id];

switch (virt_dev->cmd_status) {
switch (*cmd_status) {
case COMP_EINVAL:
dev_warn(&udev->dev, "WARN: xHCI driver setup invalid evaluate "
"context command.\n");
Expand All @@ -1101,7 +1103,7 @@ static int xhci_evaluate_context_result(struct xhci_hcd *xhci,
break;
default:
xhci_err(xhci, "ERROR: unexpected command completion "
"code 0x%x.\n", virt_dev->cmd_status);
"code 0x%x.\n", *cmd_status);
ret = -EINVAL;
break;
}
Expand All @@ -1112,19 +1114,37 @@ static int xhci_evaluate_context_result(struct xhci_hcd *xhci,
* and wait for it to finish.
*/
static int xhci_configure_endpoint(struct xhci_hcd *xhci,
struct usb_device *udev, struct xhci_virt_device *virt_dev,
bool ctx_change)
struct usb_device *udev,
struct xhci_command *command,
bool ctx_change, bool must_succeed)
{
int ret;
int timeleft;
unsigned long flags;
struct xhci_container_ctx *in_ctx;
struct completion *cmd_completion;
int *cmd_status;
struct xhci_virt_device *virt_dev;

spin_lock_irqsave(&xhci->lock, flags);
virt_dev = xhci->devs[udev->slot_id];
if (command) {
in_ctx = command->in_ctx;
cmd_completion = command->completion;
cmd_status = &command->status;
command->command_trb = xhci->cmd_ring->enqueue;
list_add_tail(&command->cmd_list, &virt_dev->cmd_list);
} else {
in_ctx = virt_dev->in_ctx;
cmd_completion = &virt_dev->cmd_completion;
cmd_status = &virt_dev->cmd_status;
}

if (!ctx_change)
ret = xhci_queue_configure_endpoint(xhci, virt_dev->in_ctx->dma,
udev->slot_id);
ret = xhci_queue_configure_endpoint(xhci, in_ctx->dma,
udev->slot_id, must_succeed);
else
ret = xhci_queue_evaluate_context(xhci, virt_dev->in_ctx->dma,
ret = xhci_queue_evaluate_context(xhci, in_ctx->dma,
udev->slot_id);
if (ret < 0) {
spin_unlock_irqrestore(&xhci->lock, flags);
Expand All @@ -1136,7 +1156,7 @@ static int xhci_configure_endpoint(struct xhci_hcd *xhci,

/* Wait for the configure endpoint command to complete */
timeleft = wait_for_completion_interruptible_timeout(
&virt_dev->cmd_completion,
cmd_completion,
USB_CTRL_SET_TIMEOUT);
if (timeleft <= 0) {
xhci_warn(xhci, "%s while waiting for %s command\n",
Expand All @@ -1149,8 +1169,8 @@ static int xhci_configure_endpoint(struct xhci_hcd *xhci,
}

if (!ctx_change)
return xhci_configure_endpoint_result(xhci, udev, virt_dev);
return xhci_evaluate_context_result(xhci, udev, virt_dev);
return xhci_configure_endpoint_result(xhci, udev, cmd_status);
return xhci_evaluate_context_result(xhci, udev, cmd_status);
}

/* Called after one or more calls to xhci_add_endpoint() or
Expand Down Expand Up @@ -1196,7 +1216,8 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev)
xhci_dbg_ctx(xhci, virt_dev->in_ctx,
LAST_CTX_TO_EP_NUM(slot_ctx->dev_info));

ret = xhci_configure_endpoint(xhci, udev, virt_dev, false);
ret = xhci_configure_endpoint(xhci, udev, NULL,
false, false);
if (ret) {
/* Callee should call reset_bandwidth() */
return ret;
Expand Down Expand Up @@ -1248,19 +1269,19 @@ void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev)
}

static void xhci_setup_input_ctx_for_config_ep(struct xhci_hcd *xhci,
unsigned int slot_id, u32 add_flags, u32 drop_flags)
struct xhci_container_ctx *in_ctx,
struct xhci_container_ctx *out_ctx,
u32 add_flags, u32 drop_flags)
{
struct xhci_input_control_ctx *ctrl_ctx;
ctrl_ctx = xhci_get_input_control_ctx(xhci,
xhci->devs[slot_id]->in_ctx);
ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx);
ctrl_ctx->add_flags = add_flags;
ctrl_ctx->drop_flags = drop_flags;
xhci_slot_copy(xhci, xhci->devs[slot_id]);
xhci_slot_copy(xhci, in_ctx, out_ctx);
ctrl_ctx->add_flags |= SLOT_FLAG;

xhci_dbg(xhci, "Slot ID %d Input Context:\n", slot_id);
xhci_dbg_ctx(xhci, xhci->devs[slot_id]->in_ctx,
xhci_last_valid_endpoint(add_flags));
xhci_dbg(xhci, "Input Context:\n");
xhci_dbg_ctx(xhci, in_ctx, xhci_last_valid_endpoint(add_flags));
}

void xhci_setup_input_ctx_for_quirk(struct xhci_hcd *xhci,
Expand All @@ -1272,7 +1293,8 @@ void xhci_setup_input_ctx_for_quirk(struct xhci_hcd *xhci,
u32 added_ctxs;
dma_addr_t addr;

xhci_endpoint_copy(xhci, xhci->devs[slot_id], ep_index);
xhci_endpoint_copy(xhci, xhci->devs[slot_id]->in_ctx,
xhci->devs[slot_id]->out_ctx, ep_index);
in_ctx = xhci->devs[slot_id]->in_ctx;
ep_ctx = xhci_get_ep_ctx(xhci, in_ctx, ep_index);
addr = xhci_trb_virt_to_dma(deq_state->new_deq_seg,
Expand All @@ -1288,8 +1310,8 @@ void xhci_setup_input_ctx_for_quirk(struct xhci_hcd *xhci,
ep_ctx->deq = addr | deq_state->new_cycle_state;

added_ctxs = xhci_get_endpoint_flag_from_index(ep_index);
xhci_setup_input_ctx_for_config_ep(xhci, slot_id,
added_ctxs, added_ctxs);
xhci_setup_input_ctx_for_config_ep(xhci, xhci->devs[slot_id]->in_ctx,
xhci->devs[slot_id]->out_ctx, added_ctxs, added_ctxs);
}

void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci,
Expand Down
55 changes: 49 additions & 6 deletions drivers/usb/host/xhci-mem.c
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id,
goto fail;

init_completion(&dev->cmd_completion);
INIT_LIST_HEAD(&dev->cmd_list);

/* Point to output device context in dcbaa. */
xhci->dcbaa->dev_context_ptrs[slot_id] = dev->out_ctx->dma;
Expand Down Expand Up @@ -624,13 +625,15 @@ void xhci_endpoint_zero(struct xhci_hcd *xhci,
* issue a configure endpoint command.
*/
void xhci_endpoint_copy(struct xhci_hcd *xhci,
struct xhci_virt_device *vdev, unsigned int ep_index)
struct xhci_container_ctx *in_ctx,
struct xhci_container_ctx *out_ctx,
unsigned int ep_index)
{
struct xhci_ep_ctx *out_ep_ctx;
struct xhci_ep_ctx *in_ep_ctx;

out_ep_ctx = xhci_get_ep_ctx(xhci, vdev->out_ctx, ep_index);
in_ep_ctx = xhci_get_ep_ctx(xhci, vdev->in_ctx, ep_index);
out_ep_ctx = xhci_get_ep_ctx(xhci, out_ctx, ep_index);
in_ep_ctx = xhci_get_ep_ctx(xhci, in_ctx, ep_index);

in_ep_ctx->ep_info = out_ep_ctx->ep_info;
in_ep_ctx->ep_info2 = out_ep_ctx->ep_info2;
Expand All @@ -643,13 +646,15 @@ void xhci_endpoint_copy(struct xhci_hcd *xhci,
* issue a configure endpoint command. Only the context entries field matters,
* but we'll copy the whole thing anyway.
*/
void xhci_slot_copy(struct xhci_hcd *xhci, struct xhci_virt_device *vdev)
void xhci_slot_copy(struct xhci_hcd *xhci,
struct xhci_container_ctx *in_ctx,
struct xhci_container_ctx *out_ctx)
{
struct xhci_slot_ctx *in_slot_ctx;
struct xhci_slot_ctx *out_slot_ctx;

in_slot_ctx = xhci_get_slot_ctx(xhci, vdev->in_ctx);
out_slot_ctx = xhci_get_slot_ctx(xhci, vdev->out_ctx);
in_slot_ctx = xhci_get_slot_ctx(xhci, in_ctx);
out_slot_ctx = xhci_get_slot_ctx(xhci, out_ctx);

in_slot_ctx->dev_info = out_slot_ctx->dev_info;
in_slot_ctx->dev_info2 = out_slot_ctx->dev_info2;
Expand Down Expand Up @@ -754,6 +759,44 @@ static void scratchpad_free(struct xhci_hcd *xhci)
xhci->scratchpad = NULL;
}

struct xhci_command *xhci_alloc_command(struct xhci_hcd *xhci,
bool allocate_completion, gfp_t mem_flags)
{
struct xhci_command *command;

command = kzalloc(sizeof(*command), mem_flags);
if (!command)
return NULL;

command->in_ctx =
xhci_alloc_container_ctx(xhci, XHCI_CTX_TYPE_INPUT, mem_flags);
if (!command->in_ctx)
return NULL;

if (allocate_completion) {
command->completion =
kzalloc(sizeof(struct completion), mem_flags);
if (!command->completion) {
xhci_free_container_ctx(xhci, command->in_ctx);
return NULL;
}
init_completion(command->completion);
}

command->status = 0;
INIT_LIST_HEAD(&command->cmd_list);
return command;
}

void xhci_free_command(struct xhci_hcd *xhci,
struct xhci_command *command)
{
xhci_free_container_ctx(xhci,
command->in_ctx);
kfree(command->completion);
kfree(command);
}

void xhci_mem_cleanup(struct xhci_hcd *xhci)
{
struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller);
Expand Down
Loading

0 comments on commit 913a8a3

Please sign in to comment.