Patch adds functionality responsible for handling CONNECT/DISCONNECT event. This event will be reported after attached/detached USB device to/from USB port. To complete this procedure usbssp_halt_endpoint function must to be implemented. This will be added in next patch. Signed-off-by: Pawel Laszczak <pawell@xxxxxxxxxxx> --- drivers/usb/usbssp/gadget-if.c | 16 +++ drivers/usb/usbssp/gadget-mem.c | 74 +++++++++++++ drivers/usb/usbssp/gadget-port.c | 92 ++++++++++++++++ drivers/usb/usbssp/gadget-ring.c | 12 ++ drivers/usb/usbssp/gadget.c | 182 ++++++++++++++++++++++++++++++- drivers/usb/usbssp/gadget.h | 17 +++ 6 files changed, 391 insertions(+), 2 deletions(-) diff --git a/drivers/usb/usbssp/gadget-if.c b/drivers/usb/usbssp/gadget-if.c index 28118c1a0250..9c236fc1149f 100644 --- a/drivers/usb/usbssp/gadget-if.c +++ b/drivers/usb/usbssp/gadget-if.c @@ -287,6 +287,16 @@ void usbssp_gadget_free_endpoint(struct usbssp_udc *usbssp_data) } } +static void usbssp_disconnect_gadget(struct usbssp_udc *usbssp_data) +{ + if (usbssp_data->gadget_driver && + usbssp_data->gadget_driver->disconnect) { + spin_unlock(&usbssp_data->irq_thread_lock); + usbssp_data->gadget_driver->disconnect(&usbssp_data->gadget); + spin_lock(&usbssp_data->irq_thread_lock); + } +} + void usbssp_suspend_gadget(struct usbssp_udc *usbssp_data) { if (usbssp_data->gadget_driver && usbssp_data->gadget_driver->suspend) { @@ -317,6 +327,12 @@ static void usbssp_reset_gadget(struct usbssp_udc *usbssp_data) spin_lock(&usbssp_data->lock); } } + +void usbssp_gadget_disconnect_interrupt(struct usbssp_udc *usbssp_data) +{ + usbssp_disconnect_gadget(usbssp_data); +} + void usbssp_gadget_reset_interrupt(struct usbssp_udc *usbssp_data) { usbssp_reset_gadget(usbssp_data); diff --git a/drivers/usb/usbssp/gadget-mem.c b/drivers/usb/usbssp/gadget-mem.c index 3e39db25f9ef..fd3c0557feef 100644 --- a/drivers/usb/usbssp/gadget-mem.c +++ b/drivers/usb/usbssp/gadget-mem.c @@ -645,6 +645,71 @@ void usbssp_free_priv_device(struct usbssp_udc *usbssp_data) usbssp_data->slot_id = 0; } + +int usbssp_alloc_priv_device(struct usbssp_udc *usbssp_data, gfp_t flags) +{ + struct usbssp_device *priv_dev; + + /* Slot ID 0 is reserved */ + if (usbssp_data->slot_id == 0) { + usbssp_warn(usbssp_data, "Bad Slot ID %d\n", + usbssp_data->slot_id); + return 0; + } + + priv_dev = &usbssp_data->devs; + + /* Allocate the (output) device context that will be + * used in the USBSSP. + */ + priv_dev->out_ctx = usbssp_alloc_container_ctx(usbssp_data, + USBSSP_CTX_TYPE_DEVICE, flags); + + if (!priv_dev->out_ctx) + goto fail; + + usbssp_dbg(usbssp_data, "Slot %d output ctx = 0x%llx (dma)\n", + usbssp_data->slot_id, + (unsigned long long)priv_dev->out_ctx->dma); + + /* Allocate the (input) device context for address device command */ + priv_dev->in_ctx = usbssp_alloc_container_ctx(usbssp_data, + USBSSP_CTX_TYPE_INPUT, flags); + + if (!priv_dev->in_ctx) + goto fail; + + usbssp_dbg(usbssp_data, "Slot %d input ctx = 0x%llx (dma)\n", + usbssp_data->slot_id, + (unsigned long long)priv_dev->in_ctx->dma); + + /* Allocate endpoint 0 ring */ + priv_dev->eps[0].ring = usbssp_ring_alloc(usbssp_data, 2, 1, + TYPE_CTRL, 0, flags); + if (!priv_dev->eps[0].ring) + goto fail; + + priv_dev->gadget = &usbssp_data->gadget; + + /* Point to output device context in dcbaa. */ + usbssp_data->dcbaa->dev_context_ptrs[usbssp_data->slot_id] = + cpu_to_le64(priv_dev->out_ctx->dma); + usbssp_dbg(usbssp_data, "Set slot id %d dcbaa entry %p to 0x%llx\n", + usbssp_data->slot_id, + &usbssp_data->dcbaa->dev_context_ptrs[usbssp_data->slot_id], + le64_to_cpu(usbssp_data->dcbaa->dev_context_ptrs[usbssp_data->slot_id])); + + trace_usbssp_alloc_priv_device(priv_dev); + return 1; +fail: + if (priv_dev->in_ctx) + usbssp_free_container_ctx(usbssp_data, priv_dev->in_ctx); + if (priv_dev->out_ctx) + usbssp_free_container_ctx(usbssp_data, priv_dev->out_ctx); + + return 0; +} + struct usbssp_command *usbssp_alloc_command(struct usbssp_udc *usbssp_data, bool allocate_completion, gfp_t mem_flags) @@ -756,6 +821,7 @@ void usbssp_mem_cleanup(struct usbssp_udc *usbssp_data) cancel_delayed_work_sync(&usbssp_data->cmd_timer); cancel_work_sync(&usbssp_data->bottom_irq); + destroy_workqueue(usbssp_data->bottom_irq_wq); /* Free the Event Ring Segment Table and the actual Event Ring */ usbssp_free_erst(usbssp_data, &usbssp_data->erst); @@ -1260,6 +1326,14 @@ int usbssp_mem_init(struct usbssp_udc *usbssp_data, gfp_t flags) usbssp_handle_command_timeout); init_completion(&usbssp_data->cmd_ring_stop_completion); + usbssp_data->bottom_irq_wq = + create_singlethread_workqueue(dev_name(usbssp_data->dev)); + + if (!usbssp_data->bottom_irq_wq) + goto fail; + + INIT_WORK(&usbssp_data->bottom_irq, usbssp_bottom_irq); + page_size = readl(&usbssp_data->op_regs->page_size); usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_init, "Supported page size register = 0x%x", page_size); diff --git a/drivers/usb/usbssp/gadget-port.c b/drivers/usb/usbssp/gadget-port.c index c9c8aae369ba..fc76139468d5 100644 --- a/drivers/usb/usbssp/gadget-port.c +++ b/drivers/usb/usbssp/gadget-port.c @@ -64,6 +64,98 @@ u32 usbssp_port_state_to_neutral(u32 state) /* Save read-only status and port state */ return (state & USBSSP_PORT_RO) | (state & USBSSP_PORT_RWS); } + +/* + * Stop device + * It issues stop endpoint command for EP 0 to 30. And wait the last command + * to complete. + */ +int usbssp_stop_device(struct usbssp_udc *usbssp_data, int suspend) +{ + struct usbssp_device *priv_dev; + struct usbssp_ep_ctx *ep_ctx; + int ret = 0; + int i; + + ret = 0; + priv_dev = &usbssp_data->devs; + + trace_usbssp_stop_device(priv_dev); + + if (usbssp_data->gadget.state < USB_STATE_ADDRESS) { + usbssp_dbg(usbssp_data, + "Device is not yet in USB_STATE_ADDRESS state\n"); + goto stop_ep0; + } + + for (i = LAST_EP_INDEX; i > 0; i--) { + if (priv_dev->eps[i].ring && priv_dev->eps[i].ring->dequeue) { + struct usbssp_command *command; + + if (priv_dev->eps[i].ep_state & EP_HALTED) { + usbssp_dbg(usbssp_data, + "ep_index %d is in halted state " + "- ep state: %x\n", + i, priv_dev->eps[i].ep_state); + usbssp_halt_endpoint(usbssp_data, + &priv_dev->eps[i], 0); + } + + ep_ctx = usbssp_get_ep_ctx(usbssp_data, + priv_dev->out_ctx, i); + + /* Check ep is running, required by AMD SNPS 3.1 xHC */ + if (GET_EP_CTX_STATE(ep_ctx) != EP_STATE_RUNNING) { + usbssp_dbg(usbssp_data, + "ep_index %d is already stopped.\n", i); + continue; + } + + if (priv_dev->eps[i].ep_state & EP_STOP_CMD_PENDING) { + usbssp_dbg(usbssp_data, + "Stop endpoint command is pending " + "for ep_index %d.\n", i); + continue; + } + + /*device was disconnected so endpoint should be disabled + * and transfer ring stopped. + */ + priv_dev->eps[i].ep_state |= EP_STOP_CMD_PENDING | + USBSSP_EP_DISABLE_PENDING; + + command = usbssp_alloc_command(usbssp_data, false, + GFP_ATOMIC); + if (!command) + return -ENOMEM; + + ret = usbssp_queue_stop_endpoint(usbssp_data, + command, i, suspend); + if (ret) { + usbssp_free_command(usbssp_data, command); + return ret; + } + } + } + +stop_ep0: + if (priv_dev->eps[0].ep_state & EP_HALTED) { + usbssp_dbg(usbssp_data, + "ep_index 0 is in halted state - ep state: %x\n", + priv_dev->eps[i].ep_state); + ret = usbssp_halt_endpoint(usbssp_data, &priv_dev->eps[0], 0); + } else { + /*device was disconnected so endpoint should be disabled + * and transfer ring stopped. + */ + priv_dev->eps[0].ep_state &= ~USBSSP_EP_ENABLED; + ret = usbssp_cmd_stop_ep(usbssp_data, &usbssp_data->gadget, + &priv_dev->eps[0]); + } + + return ret; +} + __le32 __iomem *usbssp_get_port_io_addr(struct usbssp_udc *usbssp_data) { if (usbssp_data->port_major_revision == 0x03) diff --git a/drivers/usb/usbssp/gadget-ring.c b/drivers/usb/usbssp/gadget-ring.c index 28b807fbbc64..32bf7a4cae34 100644 --- a/drivers/usb/usbssp/gadget-ring.c +++ b/drivers/usb/usbssp/gadget-ring.c @@ -217,6 +217,18 @@ static inline int room_on_ring(struct usbssp_udc *usbssp_data, return 1; } +/* Ring the device controller doorbell after placing a command on the ring */ +void usbssp_ring_cmd_db(struct usbssp_udc *usbssp_data) +{ + if (!(usbssp_data->cmd_ring_state & CMD_RING_STATE_RUNNING)) + return; + + usbssp_dbg(usbssp_data, "// Ding dong command ring!\n"); + writel(DB_VALUE_CMD, &usbssp_data->dba->doorbell[0]); + /* Flush PCI posted writes */ + readl(&usbssp_data->dba->doorbell[0]); +} + static bool usbssp_mod_cmd_timer(struct usbssp_udc *usbssp_data, unsigned long delay) { diff --git a/drivers/usb/usbssp/gadget.c b/drivers/usb/usbssp/gadget.c index 2ddb449765b6..6637fa010b39 100644 --- a/drivers/usb/usbssp/gadget.c +++ b/drivers/usb/usbssp/gadget.c @@ -23,6 +23,68 @@ #include "gadget-trace.h" #include "gadget.h" +void usbssp_bottom_irq(struct work_struct *work) +{ + struct usbssp_udc *usbssp_data = container_of(work, struct usbssp_udc, + bottom_irq); + + usbssp_dbg(usbssp_data, "===== Bottom IRQ handler start ====\n"); + + if (usbssp_data->usbssp_state & USBSSP_STATE_DYING) { + usbssp_err(usbssp_data, "Device controller dying\n"); + return; + } + + mutex_lock(&usbssp_data->mutex); + spin_lock_irqsave(&usbssp_data->irq_thread_lock, + usbssp_data->irq_thread_flag); + + if (usbssp_data->defered_event & EVENT_DEV_DISCONECTED) { + usbssp_dbg(usbssp_data, "Disconnecting device sequence\n"); + usbssp_data->defered_event &= ~EVENT_DEV_DISCONECTED; + usbssp_data->usbssp_state |= USBSSP_STATE_DISCONNECT_PENDING; + usbssp_stop_device(usbssp_data, 0); + + //time needed for disconnect + usbssp_gadget_disconnect_interrupt(usbssp_data); + usbssp_data->gadget.speed = USB_SPEED_UNKNOWN; + usb_gadget_set_state(&usbssp_data->gadget, USB_STATE_NOTATTACHED); + + usbssp_dbg(usbssp_data, "Wait for disconnect\n"); + + spin_unlock_irqrestore(&usbssp_data->irq_thread_lock, + usbssp_data->irq_thread_flag); + /*fixme: should be replaced by wait_for_completion*/ + msleep(200); + spin_lock_irqsave(&usbssp_data->irq_thread_lock, + usbssp_data->irq_thread_flag); + } + + if (usbssp_data->defered_event & EVENT_DEV_CONNECTED) { + usbssp_dbg(usbssp_data, "Connecting device sequence\n"); + if (usbssp_data->usbssp_state & USBSSP_STATE_DISCONNECT_PENDING) { + usbssp_free_dev(usbssp_data); + usbssp_data->usbssp_state &= ~USBSSP_STATE_DISCONNECT_PENDING; + } + + usbssp_data->defered_event &= ~EVENT_DEV_CONNECTED; + usbssp_alloc_dev(usbssp_data); + } + + if (usbssp_data->defered_event & EVENT_USB_RESET) { + /*TODO: implement handling of USB_RESET*/ + } + + /*handle setup packet*/ + if (usbssp_data->defered_event & EVENT_SETUP_PACKET) { + /*TODO: implement handling of SETUP packet*/ + } + + spin_unlock_irqrestore(&usbssp_data->irq_thread_lock, + usbssp_data->irq_thread_flag); + mutex_unlock(&usbssp_data->mutex); + usbssp_dbg(usbssp_data, "===== Bottom IRQ handler end ====\n"); +} /* * usbssp_handshake - spin reading dc until handshake completes or fails @@ -277,6 +339,123 @@ unsigned int usbssp_last_valid_endpoint(u32 added_ctxs) return fls(added_ctxs) - 1; } +int usbssp_halt_endpoint(struct usbssp_udc *usbssp_data, struct usbssp_ep *dep, + int value) +{ + /*TODO: implement this function*/ + return 0; +} + +/* + * 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 + * disabled. Free any DC data structures associated with that device. + */ +void usbssp_free_dev(struct usbssp_udc *usbssp_data) +{ + struct usbssp_device *priv_dev; + int i, ret; + struct usbssp_slot_ctx *slot_ctx; + + priv_dev = &usbssp_data->devs; + slot_ctx = usbssp_get_slot_ctx(usbssp_data, priv_dev->out_ctx); + trace_usbssp_free_dev(slot_ctx); + + for (i = 0; i < 31; ++i) + priv_dev->eps[i].ep_state &= ~EP_STOP_CMD_PENDING; + + ret = usbssp_disable_slot(usbssp_data); + if (ret) + usbssp_free_priv_device(usbssp_data); +} + +int usbssp_disable_slot(struct usbssp_udc *usbssp_data) +{ + struct usbssp_command *command; + u32 state; + int ret = 0; + + command = usbssp_alloc_command(usbssp_data, false, GFP_ATOMIC); + if (!command) + return -ENOMEM; + + /* Don't disable the slot if the device controller is dead. */ + state = readl(&usbssp_data->op_regs->status); + if (state == 0xffffffff || + (usbssp_data->usbssp_state & USBSSP_STATE_DYING) || + (usbssp_data->usbssp_state & USBSSP_STATE_HALTED)) { + kfree(command); + return -ENODEV; + } + + ret = usbssp_queue_slot_control(usbssp_data, command, TRB_DISABLE_SLOT); + if (ret) { + kfree(command); + return ret; + } + usbssp_ring_cmd_db(usbssp_data); + return ret; +} + +/* + * Returns 0 if the DC n out of device slots, the Enable Slot command + * timed out, or allocating memory failed. Returns 1 on success. + */ +int usbssp_alloc_dev(struct usbssp_udc *usbssp_data) +{ + int ret, slot_id; + struct usbssp_command *command; + struct usbssp_slot_ctx *slot_ctx; + + command = usbssp_alloc_command(usbssp_data, true, GFP_ATOMIC); + + if (!command) + return -ENOMEM; + + ret = usbssp_queue_slot_control(usbssp_data, command, TRB_ENABLE_SLOT); + + if (ret) { + usbssp_free_command(usbssp_data, command); + return ret; + } + + 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); + + slot_id = usbssp_data->slot_id; + + if (!slot_id || command->status != COMP_SUCCESS) { + usbssp_err(usbssp_data, + "Error while assigning device slot ID\n"); + usbssp_free_command(usbssp_data, command); + return 0; + } + + usbssp_free_command(usbssp_data, command); + + if (!usbssp_alloc_priv_device(usbssp_data, GFP_ATOMIC)) { + usbssp_warn(usbssp_data, + "Could not allocate usbssp_device data structures\n"); + goto disable_slot; + } + + slot_ctx = usbssp_get_slot_ctx(usbssp_data, usbssp_data->devs.out_ctx); + trace_usbssp_alloc_dev(slot_ctx); + + return 1; + +disable_slot: + ret = usbssp_disable_slot(usbssp_data); + if (ret) + usbssp_free_priv_device(usbssp_data); + + return 0; +} + int usbssp_gen_setup(struct usbssp_udc *usbssp_data) { int retval; @@ -424,7 +603,6 @@ int usbssp_gadget_exit(struct usbssp_udc *usbssp_data) usb_del_gadget_udc(&usbssp_data->gadget); usbssp_gadget_free_endpoint(usbssp_data); - /*TODO: add usbssp_stop implementation*/ - //usbssp_stop(usbssp_data); + usbssp_stop(usbssp_data); return ret; } diff --git a/drivers/usb/usbssp/gadget.h b/drivers/usb/usbssp/gadget.h index d0c9548a39ca..38e9b80faf2a 100644 --- a/drivers/usb/usbssp/gadget.h +++ b/drivers/usb/usbssp/gadget.h @@ -1679,6 +1679,8 @@ void usbssp_dbg_trace(struct usbssp_udc *usbssp_data, /* USBSSP memory management */ 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); unsigned int usbssp_last_valid_endpoint(u32 added_ctxs); int usbssp_ring_expansion(struct usbssp_udc *usbssp_data, struct usbssp_ring *ring, unsigned int num_trbs, gfp_t flags); @@ -1705,12 +1707,15 @@ int usbssp_handshake(void __iomem *ptr, u32 mask, u32 done, int usec); void usbssp_quiesce(struct usbssp_udc *usbssp_data); int usbssp_halt(struct usbssp_udc *usbssp_data); extern int usbssp_reset(struct usbssp_udc *usbssp_data); +int usbssp_disable_slot(struct usbssp_udc *usbssp_data); int usbssp_suspend(struct usbssp_udc *usbssp_data, bool do_wakeup); int usbssp_resume(struct usbssp_udc *usbssp_data, bool hibernated); 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); /* USBSSP ring, segment, TRB, and TD functions */ dma_addr_t usbssp_trb_virt_to_dma(struct usbssp_segment *seg, union usbssp_trb *trb); @@ -1718,6 +1723,12 @@ struct usbssp_segment *usbssp_trb_in_td(struct usbssp_udc *usbssp_data, struct usbssp_segment *start_seg, union usbssp_trb *start_trb, union usbssp_trb *end_trb, dma_addr_t suspect_dma, bool debug); +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_stop_endpoint(struct usbssp_udc *usbssp_data, + struct usbssp_command *cmd, + unsigned int ep_index, int suspend); void usbssp_handle_command_timeout(struct work_struct *work); void usbssp_cleanup_command_queue(struct usbssp_udc *usbssp_data); @@ -1744,6 +1755,12 @@ void usbssp_gadget_free_endpoint(struct usbssp_udc *usbssp_data); int usbssp_gadget_init_endpoint(struct usbssp_udc *usbssp_data); unsigned int usbssp_port_speed(unsigned int port_status); void usbssp_gadget_reset_interrupt(struct usbssp_udc *usbssp_data); +void usbssp_gadget_disconnect_interrupt(struct usbssp_udc *usbssp_data); +int usbssp_stop_device(struct usbssp_udc *usbssp_data, int suspend); +int usbssp_halt_endpoint(struct usbssp_udc *usbssp_data, + struct usbssp_ep *dep, int value); +int usbssp_cmd_stop_ep(struct usbssp_udc *usbssp_data, struct usb_gadget *g, + struct usbssp_ep *ep_priv); static inline char *usbssp_slot_state_string(u32 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