Patch adds functionality used during resetting USB port. During this process driver must finish all queued transfers, and enter the controller to default state. Signed-off-by: Pawel Laszczak <pawell@xxxxxxxxxxx> --- drivers/usb/usbssp/gadget-dbg.c | 9 + drivers/usb/usbssp/gadget-mem.c | 100 +++++++++++ drivers/usb/usbssp/gadget.c | 304 +++++++++++++++++++++++++++++++- drivers/usb/usbssp/gadget.h | 12 ++ 4 files changed, 424 insertions(+), 1 deletion(-) diff --git a/drivers/usb/usbssp/gadget-dbg.c b/drivers/usb/usbssp/gadget-dbg.c index 88e4e8d38e4b..cb744e26ab39 100644 --- a/drivers/usb/usbssp/gadget-dbg.c +++ b/drivers/usb/usbssp/gadget-dbg.c @@ -12,6 +12,15 @@ #include "gadget.h" +char *usbssp_get_slot_state(struct usbssp_udc *usbssp_data, + struct usbssp_container_ctx *ctx) +{ + struct usbssp_slot_ctx *slot_ctx = usbssp_get_slot_ctx(usbssp_data, ctx); + int state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)); + + return usbssp_slot_state_string(state); +} + void usbssp_dbg_trace(struct usbssp_udc *usbssp_data, void (*trace)(struct va_format *), const char *fmt, ...) diff --git a/drivers/usb/usbssp/gadget-mem.c b/drivers/usb/usbssp/gadget-mem.c index fd3c0557feef..60c78d1d7f11 100644 --- a/drivers/usb/usbssp/gadget-mem.c +++ b/drivers/usb/usbssp/gadget-mem.c @@ -710,6 +710,106 @@ int usbssp_alloc_priv_device(struct usbssp_udc *usbssp_data, gfp_t flags) return 0; } +void usbssp_copy_ep0_dequeue_into_input_ctx(struct usbssp_udc *usbssp_data) +{ + struct usbssp_device *priv_dev; + struct usbssp_ep_ctx *ep0_ctx; + struct usbssp_ring *ep_ring; + + priv_dev = &usbssp_data->devs; + ep0_ctx = usbssp_get_ep_ctx(usbssp_data, priv_dev->in_ctx, 0); + ep_ring = priv_dev->eps[0].ring; + /* + * We don't keep track of the dequeue pointer very well after a + * Set TR dequeue pointer, so we're setting the dequeue pointer of the + * device to our enqueue pointer. This should only be called after a + * configured device has reset, so all control transfers should have + * been completed or cancelled before the reset. + */ + ep0_ctx->deq = cpu_to_le64(usbssp_trb_virt_to_dma(ep_ring->enq_seg, + ep_ring->enqueue) | ep_ring->cycle_state); +} + +/* Setup an DC private device for a Set Address command */ +int usbssp_setup_addressable_priv_dev(struct usbssp_udc *usbssp_data) +{ + struct usbssp_device *dev_priv; + struct usbssp_ep_ctx *ep0_ctx; + struct usbssp_slot_ctx *slot_ctx; + u32 max_packets; + + dev_priv = &usbssp_data->devs; + /* Slot ID 0 is reserved */ + if (usbssp_data->slot_id == 0 || !dev_priv->gadget) { + usbssp_warn(usbssp_data, + "Slot ID %d is not assigned to this device\n", + usbssp_data->slot_id); + return -EINVAL; + } + + ep0_ctx = usbssp_get_ep_ctx(usbssp_data, dev_priv->in_ctx, 0); + slot_ctx = usbssp_get_slot_ctx(usbssp_data, dev_priv->in_ctx); + + /* 3) Only the control endpoint is valid - one endpoint context */ + slot_ctx->dev_info |= cpu_to_le32(LAST_CTX(1) /*| udev->route*/); + + switch (dev_priv->gadget->speed) { + case USB_SPEED_SUPER_PLUS: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_SSP); + max_packets = MAX_PACKET(512); + break; + case USB_SPEED_SUPER: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_SS); + max_packets = MAX_PACKET(512); + break; + case USB_SPEED_HIGH: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_HS); + max_packets = MAX_PACKET(64); + break; + case USB_SPEED_FULL: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_FS); + max_packets = MAX_PACKET(64); + break; + case USB_SPEED_LOW: + slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_LS); + max_packets = MAX_PACKET(8); + break; + case USB_SPEED_WIRELESS: + usbssp_dbg(usbssp_data, + "USBSSP doesn't support wireless speeds\n"); + return -EINVAL; + default: + /* Speed was not set , this shouldn't happen. */ + return -EINVAL; + } + + if (!usbssp_data->devs.port_num) + return -EINVAL; + + slot_ctx->dev_info2 |= + cpu_to_le32(ROOT_DEV_PORT(usbssp_data->devs.port_num)); + slot_ctx->dev_state |= (usbssp_data->device_address & DEV_ADDR_MASK); + + ep0_ctx->tx_info = EP_AVG_TRB_LENGTH(0x8); + /*cpu_to_le32(EP_MAX_ESIT_PAYLOAD_LO(max_esit_payload) |*/ + + /* Step 4 - ring already allocated */ + /* Step 5 */ + ep0_ctx->ep_info2 = cpu_to_le32(EP_TYPE(CTRL_EP)); + + /* EP 0 can handle "burst" sizes of 1, so Max Burst Size field is 0 */ + ep0_ctx->ep_info2 |= cpu_to_le32(MAX_BURST(0) | ERROR_COUNT(3) | + max_packets); + + ep0_ctx->deq = cpu_to_le64(dev_priv->eps[0].ring->first_seg->dma | + dev_priv->eps[0].ring->cycle_state); + + trace_usbssp_setup_addressable_priv_device(dev_priv); + /* Steps 7 and 8 were done in usbssp_alloc_priv_device() */ + + return 0; +} + struct usbssp_command *usbssp_alloc_command(struct usbssp_udc *usbssp_data, bool allocate_completion, gfp_t mem_flags) diff --git a/drivers/usb/usbssp/gadget.c b/drivers/usb/usbssp/gadget.c index 0ae4e1c07035..f198d7e308c6 100644 --- a/drivers/usb/usbssp/gadget.c +++ b/drivers/usb/usbssp/gadget.c @@ -72,7 +72,27 @@ void usbssp_bottom_irq(struct work_struct *work) } if (usbssp_data->defered_event & EVENT_USB_RESET) { - /*TODO: implement handling of USB_RESET*/ + __le32 __iomem *port_regs; + u32 temp; + + usbssp_dbg(usbssp_data, "Beginning USB reset device sequence\n"); + + /*Reset Device Command*/ + usbssp_data->defered_event &= ~EVENT_USB_RESET; + usbssp_reset_device(usbssp_data); + usbssp_data->devs.eps[0].ep_state |= USBSSP_EP_ENABLED; + usbssp_data->defered_event &= ~EVENT_DEV_CONNECTED; + + usbssp_enable_device(usbssp_data); + if ((usbssp_data->gadget.speed == USB_SPEED_SUPER) || + (usbssp_data->gadget.speed == USB_SPEED_SUPER_PLUS)) { + usbssp_dbg(usbssp_data, "Set U1/U2 enable\n"); + port_regs = usbssp_get_port_io_addr(usbssp_data); + temp = readl(port_regs+PORTPMSC); + temp &= ~(PORT_U1_TIMEOUT_MASK | PORT_U2_TIMEOUT_MASK); + temp |= PORT_U1_TIMEOUT(1) | PORT_U2_TIMEOUT(1); + writel(temp, port_regs+PORTPMSC); + } } /*handle setup packet*/ @@ -495,6 +515,107 @@ int usbssp_halt_endpoint(struct usbssp_udc *usbssp_data, struct usbssp_ep *dep, return 0; } +/* + * This submits a Reset Device Command, which will set the device state to 0, + * set the device address to 0, and disable all the endpoints except the default + * control endpoint. The USB core should come back and call + * usbssp_address_device(), and then re-set up the configuration. + * + * Wait for the Reset Device command to finish. Remove all structures + * associated with the endpoints that were disabled. Clear the input device + * structure? Reset the control endpoint 0 max packet size? + */ +int usbssp_reset_device(struct usbssp_udc *usbssp_data) +{ + struct usbssp_device *dev_priv; + struct usbssp_command *reset_device_cmd; + struct usbssp_slot_ctx *slot_ctx; + int slot_state; + int ret = 0; + + ret = usbssp_check_args(usbssp_data, NULL, 0, false, __func__); + if (ret <= 0) + return ret; + + dev_priv = &usbssp_data->devs; + + /* If device is not setup, there is no point in resetting it */ + slot_ctx = usbssp_get_slot_ctx(usbssp_data, dev_priv->out_ctx); + slot_state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)); + pr_info("usbssp_reset_deviceslot_stated\n"); + if (slot_state == SLOT_STATE_DISABLED || + slot_state == SLOT_STATE_ENABLED || + slot_state == SLOT_STATE_DEFAULT) { + usbssp_dbg(usbssp_data, + "Slot in DISABLED/ENABLED state - reset not allowed\n"); + return 0; + } + + trace_usbssp_reset_device(slot_ctx); + + usbssp_dbg(usbssp_data, "Resetting device with slot ID %u\n", + usbssp_data->slot_id); + /* Allocate the command structure that holds the struct completion. + */ + reset_device_cmd = usbssp_alloc_command(usbssp_data, true, GFP_ATOMIC); + + if (!reset_device_cmd) { + usbssp_dbg(usbssp_data, + "Couldn't allocate command structure.\n"); + return -ENOMEM; + } + + /* Attempt to submit the Reset Device command to the command ring */ + ret = usbssp_queue_reset_device(usbssp_data, reset_device_cmd); + if (ret) { + usbssp_dbg(usbssp_data, + "FIXME: allocate a command ring segment\n"); + goto command_cleanup; + } + usbssp_ring_cmd_db(usbssp_data); + + spin_unlock_irqrestore(&usbssp_data->irq_thread_lock, + usbssp_data->irq_thread_flag); + + /* Wait for the Reset Device command to finish */ + wait_for_completion(reset_device_cmd->completion); + spin_lock_irqsave(&usbssp_data->irq_thread_lock, + usbssp_data->irq_thread_flag); + + /* The Reset Device command can't fail, according to spec, + * unless we tried to reset a slot ID that wasn't enabled, + * or the device wasn't in the addressed or configured state. + */ + ret = reset_device_cmd->status; + switch (ret) { + case COMP_COMMAND_ABORTED: + case COMP_COMMAND_RING_STOPPED: + usbssp_warn(usbssp_data, + "Timeout waiting for reset device command\n"); + ret = -ETIME; + goto command_cleanup; + case COMP_SLOT_NOT_ENABLED_ERROR: /*completion code for bad slot ID */ + case COMP_CONTEXT_STATE_ERROR: /* completion code for same thing */ + usbssp_dbg(usbssp_data, "Not freeing device rings.\n"); + ret = 0; + goto command_cleanup; + case COMP_SUCCESS: + usbssp_dbg(usbssp_data, "Successful reset device command.\n"); + break; + default: + usbssp_warn(usbssp_data, "Unknown completion code %u for " + "reset device command.\n", ret); + ret = -EINVAL; + goto command_cleanup; + } + + ret = 0; + +command_cleanup: + usbssp_free_command(usbssp_data, reset_device_cmd); + return ret; +} + /* * At this point, the struct usb_device is about to go away, the device has * disconnected, and all traffic has been stopped and the endpoints have been @@ -605,6 +726,187 @@ int usbssp_alloc_dev(struct usbssp_udc *usbssp_data) return 0; } +/* + * Issue an Address Device command + */ +static int usbssp_setup_device(struct usbssp_udc *usbssp_data, + enum usbssp_setup_dev setup) +{ + const char *act = setup == SETUP_CONTEXT_ONLY ? "context" : "address"; + struct usbssp_device *dev_priv; + int ret = 0; + struct usbssp_slot_ctx *slot_ctx; + struct usbssp_input_control_ctx *ctrl_ctx; + u64 temp_64; + struct usbssp_command *command = NULL; + int dev_state = 0; + int slot_id = usbssp_data->slot_id; + + if (usbssp_data->usbssp_state) {/* dying, removing or halted */ + ret = -ESHUTDOWN; + goto out; + } + + if (!slot_id) { + usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_address, + "Bad Slot ID %d", slot_id); + ret = -EINVAL; + goto out; + } + + dev_priv = &usbssp_data->devs; + + slot_ctx = usbssp_get_slot_ctx(usbssp_data, dev_priv->out_ctx); + trace_usbssp_setup_device_slot(slot_ctx); + + dev_state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state)); + + if (setup == SETUP_CONTEXT_ONLY) { + if (dev_state == SLOT_STATE_DEFAULT) { + usbssp_dbg(usbssp_data, + "Slot already in default state\n"); + goto out; + } + } + + command = usbssp_alloc_command(usbssp_data, true, GFP_ATOMIC); + if (!command) { + ret = -ENOMEM; + goto out; + } + + command->in_ctx = dev_priv->in_ctx; + + slot_ctx = usbssp_get_slot_ctx(usbssp_data, dev_priv->in_ctx); + ctrl_ctx = usbssp_get_input_control_ctx(dev_priv->in_ctx); + + if (!ctrl_ctx) { + usbssp_warn(usbssp_data, + "%s: Could not get input context, bad type.\n", + __func__); + ret = -EINVAL; + goto out; + } + + /* + * If this is the first Set Address (BSR=0) or driver trays + * transition to Default (BSR=1) since device plug-in or + * priv device reallocation after a resume with an USBSSP power loss, + * then set up the slot context or update device address in slot + * context. + */ + if (!slot_ctx->dev_info || dev_state == SLOT_STATE_DEFAULT) + usbssp_setup_addressable_priv_dev(usbssp_data); + + if (dev_state == SLOT_STATE_DEFAULT) + usbssp_copy_ep0_dequeue_into_input_ctx(usbssp_data); + + ctrl_ctx->add_flags = cpu_to_le32(SLOT_FLAG | EP0_FLAG); + ctrl_ctx->drop_flags = 0; + + trace_usbssp_address_ctx(usbssp_data, dev_priv->in_ctx, + le32_to_cpu(slot_ctx->dev_info) >> 27); + + ret = usbssp_queue_address_device(usbssp_data, command, + dev_priv->in_ctx->dma, setup); + + if (ret) { + usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_address, + "Prabably command ring segment is full"); + goto out; + } + + usbssp_ring_cmd_db(usbssp_data); + + spin_unlock_irqrestore(&usbssp_data->irq_thread_lock, + usbssp_data->irq_thread_flag); + wait_for_completion(command->completion); + spin_lock_irqsave(&usbssp_data->irq_thread_lock, + usbssp_data->irq_thread_flag); + + switch (command->status) { + case COMP_COMMAND_ABORTED: + case COMP_COMMAND_RING_STOPPED: + usbssp_warn(usbssp_data, + "Timeout while waiting for setup device command\n"); + ret = -ETIME; + break; + case COMP_CONTEXT_STATE_ERROR: + case COMP_SLOT_NOT_ENABLED_ERROR: + usbssp_err(usbssp_data, + "Setup ERROR: setup %s command for slot %d.\n", + act, slot_id); + ret = -EINVAL; + break; + case COMP_INCOMPATIBLE_DEVICE_ERROR: + dev_warn(usbssp_data->dev, + "ERROR: Incompatible device for setup %s command\n", + act); + ret = -ENODEV; + break; + case COMP_SUCCESS: + usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_address, + "Successful setup %s command", act); + break; + default: + usbssp_err(usbssp_data, + "ERROR: unexpected setup %s command completion code 0x%x.\n", + act, command->status); + + trace_usbssp_address_ctx(usbssp_data, dev_priv->out_ctx, 1); + ret = -EINVAL; + break; + } + + if (ret) + goto out; + + temp_64 = usbssp_read_64(usbssp_data, &usbssp_data->op_regs->dcbaa_ptr); + usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_address, + "Op regs DCBAA ptr = %#016llx", temp_64); + usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_address, + "Slot ID %d dcbaa entry @%p = %#016llx", + slot_id, &usbssp_data->dcbaa->dev_context_ptrs[slot_id], + (unsigned long long) + le64_to_cpu(usbssp_data->dcbaa->dev_context_ptrs[slot_id])); + usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_address, + "Output Context DMA address = %#08llx", + (unsigned long long)dev_priv->out_ctx->dma); + + trace_usbssp_address_ctx(usbssp_data, dev_priv->in_ctx, + le32_to_cpu(slot_ctx->dev_info) >> 27); + + slot_ctx = usbssp_get_slot_ctx(usbssp_data, dev_priv->out_ctx); + trace_usbssp_address_ctx(usbssp_data, dev_priv->out_ctx, + le32_to_cpu(slot_ctx->dev_info) >> 27); + /* Zero the input context control for later use */ + ctrl_ctx->add_flags = 0; + ctrl_ctx->drop_flags = 0; + + usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_address, + "Internal device address = %d", + le32_to_cpu(slot_ctx->dev_state) & DEV_ADDR_MASK); + + if (setup == SETUP_CONTEXT_ADDRESS) + usbssp_status_stage(usbssp_data); +out: + if (command) { + kfree(command->completion); + kfree(command); + } + return ret; +} + +int usbssp_address_device(struct usbssp_udc *usbssp_data) +{ + return usbssp_setup_device(usbssp_data, SETUP_CONTEXT_ADDRESS); +} + +int usbssp_enable_device(struct usbssp_udc *usbssp_data) +{ + return usbssp_setup_device(usbssp_data, SETUP_CONTEXT_ONLY); +} + int usbssp_gen_setup(struct usbssp_udc *usbssp_data) { int retval; diff --git a/drivers/usb/usbssp/gadget.h b/drivers/usb/usbssp/gadget.h index 45b568d33723..5f653f3caabd 100644 --- a/drivers/usb/usbssp/gadget.h +++ b/drivers/usb/usbssp/gadget.h @@ -1673,6 +1673,8 @@ static inline void usbssp_write_64(struct usbssp_udc *usbssp_data, } /* USBSSP memory management */ +char *usbssp_get_slot_state(struct usbssp_udc *usbssp_data, + struct usbssp_container_ctx *ctx); void usbssp_dbg_trace(struct usbssp_udc *usbssp_data, void (*trace)(struct va_format *), const char *fmt, ...); @@ -1681,6 +1683,8 @@ void usbssp_mem_cleanup(struct usbssp_udc *usbssp_data); int usbssp_mem_init(struct usbssp_udc *usbssp_data, gfp_t flags); void usbssp_free_priv_device(struct usbssp_udc *usbssp_data); int usbssp_alloc_priv_device(struct usbssp_udc *usbssp_data, gfp_t flags); +int usbssp_setup_addressable_priv_dev(struct usbssp_udc *usbssp_data); +void usbssp_copy_ep0_dequeue_into_input_ctx(struct usbssp_udc *usbssp_data); unsigned int usbssp_get_endpoint_index(const struct usb_endpoint_descriptor *desc); unsigned int usbssp_last_valid_endpoint(u32 added_ctxs); int usbssp_ring_expansion(struct usbssp_udc *usbssp_data, @@ -1717,6 +1721,8 @@ irqreturn_t usbssp_irq(int irq, void *priv); int usbssp_alloc_dev(struct usbssp_udc *usbssp_data); void usbssp_free_dev(struct usbssp_udc *usbssp_data); +int usbssp_address_device(struct usbssp_udc *usbssp_data); +int usbssp_enable_device(struct usbssp_udc *usbssp_data); /* USBSSP ring, segment, TRB, and TD functions */ dma_addr_t usbssp_trb_virt_to_dma(struct usbssp_segment *seg, union usbssp_trb *trb); @@ -1730,6 +1736,9 @@ int usbssp_is_vendor_info_code(struct usbssp_udc *usbssp_data, void usbssp_ring_cmd_db(struct usbssp_udc *usbssp_data); int usbssp_queue_slot_control(struct usbssp_udc *usbssp_data, struct usbssp_command *cmd, u32 trb_type); +int usbssp_queue_address_device(struct usbssp_udc *usbssp_data, + struct usbssp_command *cmd, + dma_addr_t in_ctx_ptr, enum usbssp_setup_dev setup); int usbssp_queue_stop_endpoint(struct usbssp_udc *usbssp_data, struct usbssp_command *cmd, unsigned int ep_index, int suspend); @@ -1744,6 +1753,8 @@ void usbssp_cleanup_halted_endpoint(struct usbssp_udc *usbssp_data, int usbssp_queue_halt_endpoint(struct usbssp_udc *usbssp_data, struct usbssp_command *cmd, unsigned int ep_index); +int usbssp_queue_reset_device(struct usbssp_udc *usbssp_data, + struct usbssp_command *cmd); void usbssp_handle_command_timeout(struct work_struct *work); void usbssp_cleanup_command_queue(struct usbssp_udc *usbssp_data); @@ -1778,6 +1789,7 @@ int usbssp_cmd_stop_ep(struct usbssp_udc *usbssp_data, struct usb_gadget *g, struct usbssp_ep *ep_priv); int usbssp_status_stage(struct usbssp_udc *usbssp_data); +int usbssp_reset_device(struct usbssp_udc *usbssp_data); static inline char *usbssp_slot_state_string(u32 state) { switch (state) { -- 2.17.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