diff mbox

[16/31] usb: usbssp: added connect/disconnect procedures.

Message ID 1531374448-26532-17-git-send-email-pawell@cadence.com (mailing list archive)
State New, archived
Headers show

Commit Message

Pawel Laszczak July 12, 2018, 5:47 a.m. UTC
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@cadence.com>
---
 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 mbox

Patch

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)
 {