All of lore.kernel.org
 help / color / mirror / Atom feed
From: Pawel Laszczak <pawell@cadence.com>
To: unlisted-recipients:; (no To-header on input)
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	<linux-usb@vger.kernel.org>, Felipe Balbi <balbi@kernel.org>,
	<linux-kernel@vger.kernel.org>, <ltyrala@cadence.com>,
	<adouglas@cadence.com>, <pawell@cadence.com>
Subject: [PATCH 14/31] usb: usbssp: added procedure handling command completion events.
Date: Thu, 19 Jul 2018 18:57:47 +0100	[thread overview]
Message-ID: <1532023084-28083-15-git-send-email-pawell@cadence.com> (raw)
In-Reply-To: <1532023084-28083-1-git-send-email-pawell@cadence.com>

This patch extends the usbssp_handle_event function with a new case,
that is responsible for servicing command completion events.

For this purpose, it adds handle_cmd_completion function which in turn
invokes some other functions depending of handling command type.

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/usbssp/gadget-mem.c  |  53 +++-
 drivers/usb/usbssp/gadget-ring.c | 510 +++++++++++++++++++++++++++++++
 drivers/usb/usbssp/gadget.c      |  11 +
 drivers/usb/usbssp/gadget.h      |  12 +
 4 files changed, 584 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/usbssp/gadget-mem.c b/drivers/usb/usbssp/gadget-mem.c
index fef83b6b6cf0..5708a0090ead 100644
--- a/drivers/usb/usbssp/gadget-mem.c
+++ b/drivers/usb/usbssp/gadget-mem.c
@@ -504,6 +504,38 @@ void usbssp_free_container_ctx(struct usbssp_udc *usbssp_data,
 	kfree(ctx);
 }
 
+struct usbssp_input_control_ctx *usbssp_get_input_control_ctx(
+					struct usbssp_container_ctx *ctx)
+{
+	if (ctx->type != USBSSP_CTX_TYPE_INPUT)
+		return NULL;
+
+	return (struct usbssp_input_control_ctx *)ctx->bytes;
+}
+
+struct usbssp_slot_ctx *usbssp_get_slot_ctx(struct usbssp_udc *usbssp_data,
+					    struct usbssp_container_ctx *ctx)
+{
+	if (ctx->type == USBSSP_CTX_TYPE_DEVICE)
+		return (struct usbssp_slot_ctx *)ctx->bytes;
+
+	return (struct usbssp_slot_ctx *) (ctx->bytes +
+		CTX_SIZE(usbssp_data->hcc_params));
+}
+
+struct usbssp_ep_ctx *usbssp_get_ep_ctx(struct usbssp_udc *usbssp_data,
+					struct usbssp_container_ctx *ctx,
+					unsigned int ep_index)
+{
+	/* increment ep index by offset of start of ep ctx array */
+	ep_index++;
+	if (ctx->type == USBSSP_CTX_TYPE_INPUT)
+		ep_index++;
+
+	return (struct usbssp_ep_ctx *) (ctx->bytes +
+		(ep_index * CTX_SIZE(usbssp_data->hcc_params)));
+}
+
 /***************** Streams structures manipulation *************************/
 static void usbssp_free_stream_ctx(struct usbssp_udc *usbssp_data,
 				   unsigned int num_stream_ctxs,
@@ -523,8 +555,25 @@ static void usbssp_free_stream_ctx(struct usbssp_udc *usbssp_data,
 				stream_ctx, dma);
 }
 
-/**
- * Frees all stream contexts associated with the endpoint,
+struct usbssp_ring *usbssp_stream_id_to_ring(struct usbssp_device *dev,
+					     unsigned int ep_index,
+					     unsigned int stream_id)
+{
+	struct usbssp_ep *ep = &dev->eps[ep_index];
+
+	if (stream_id == 0)
+		return ep->ring;
+
+	if (!ep->stream_info)
+		return NULL;
+
+	if (stream_id > ep->stream_info->num_streams)
+		return NULL;
+	return ep->stream_info->stream_rings[stream_id];
+}
+
+/*
+ *  Frees all stream contexts associated with the endpoint,
  *
  * Caller should fix the endpoint context streams fields.
  */
diff --git a/drivers/usb/usbssp/gadget-ring.c b/drivers/usb/usbssp/gadget-ring.c
index 6679d8ec7152..c704b939b48a 100644
--- a/drivers/usb/usbssp/gadget-ring.c
+++ b/drivers/usb/usbssp/gadget-ring.c
@@ -225,6 +225,107 @@ static bool usbssp_mod_cmd_timer(struct usbssp_udc *usbssp_data,
 	return 0;
 }
 
+void usbssp_ring_ep_doorbell(struct usbssp_udc *usbssp_data,
+			     unsigned int ep_index,
+			     unsigned int stream_id)
+{
+	__le32 __iomem *db_addr =
+			&usbssp_data->dba->doorbell[usbssp_data->slot_id];
+	struct usbssp_ep *ep = &usbssp_data->devs.eps[ep_index];
+	unsigned int ep_state = ep->ep_state;
+	unsigned int db_value;
+
+	/*
+	 * Don't ring the doorbell for this endpoint if there are pending
+	 * cancellations because we don't want to interrupt processing.
+	 * We don't want to restart any stream rings if there's a set dequeue
+	 * pointer command pending because the device can choose to start any
+	 * stream once the endpoint is on the HW schedule.
+	 * Also we don't want restart any endpoint if endpoint is halted or
+	 * disabled and also if endpoint disabling is pending.
+	 */
+	if ((ep_state & EP_STOP_CMD_PENDING) ||
+	    (ep_state & SET_DEQ_PENDING) ||
+	    (ep_state & EP_HALTED) ||
+	    !(ep_state & USBSSP_EP_ENABLED) ||
+	    (ep_state & USBSSP_EP_DISABLE_PENDING))
+		return;
+
+	if (ep_index == 0 && !usbssp_data->ep0_expect_in &&
+	    usbssp_data->ep0state == USBSSP_EP0_DATA_PHASE)
+		db_value = DB_VALUE_EP0_OUT(ep_index, stream_id);
+	else
+		db_value = DB_VALUE(ep_index, stream_id);
+
+	dev_dbg(usbssp_data->dev, "// Ding dong transfer ring for %s!"
+		" - [DB addr/DB val]: [%p/%08x]\n",
+		usbssp_data->devs.eps[ep_index].name, db_addr,
+		db_value);
+
+	writel(db_value, db_addr);
+	/*
+	 * The CPU has better things to do at this point than wait for a
+	 * write-posting flush. It'll get there soon enough.
+	 */
+}
+
+/* Ring the doorbell for any rings with pending USB requests */
+static void ring_doorbell_for_active_rings(struct usbssp_udc *usbssp_data,
+					   unsigned int ep_index)
+{
+	unsigned int stream_id;
+	struct usbssp_ep *ep;
+
+	ep = &usbssp_data->devs.eps[ep_index];
+
+	dev_dbg(usbssp_data->dev, "Ring all active ring for %s\n",
+		ep->name);
+
+	/* A ring has pending Request if its TD list is not empty */
+	if (!(ep->ep_state & EP_HAS_STREAMS)) {
+		if (ep->ring && !(list_empty(&ep->ring->td_list)))
+			usbssp_ring_ep_doorbell(usbssp_data, ep_index, 0);
+		return;
+	}
+
+	for (stream_id = 1; stream_id < ep->stream_info->num_streams;
+			stream_id++) {
+		struct usbssp_stream_info *stream_info = ep->stream_info;
+
+		if (!list_empty(&stream_info->stream_rings[stream_id]->td_list))
+			usbssp_ring_ep_doorbell(usbssp_data, ep_index,
+						stream_id);
+	}
+}
+
+/*
+ * When we get a command completion for a Stop Endpoint Command, we need to
+ * stop timer and clear EP_STOP_CMD_PENDING flag.
+ */
+static void usbssp_handle_cmd_stop_ep(struct usbssp_udc *usbssp_data,
+				      union usbssp_trb *trb,
+				      struct usbssp_event_cmd *event)
+{
+	unsigned int ep_index;
+	struct usbssp_ep *ep;
+	struct usbssp_ep_ctx *ep_ctx;
+	struct usbssp_device *priv_dev;
+
+	ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3]));
+	ep = &usbssp_data->devs.eps[ep_index];
+
+	dev_dbg(usbssp_data->dev,
+		"CMD stop endpoint completion for ep index: %d - %s\n",
+		ep_index, ep->name);
+
+
+	priv_dev = &usbssp_data->devs;
+	ep_ctx = usbssp_get_ep_ctx(usbssp_data, priv_dev->out_ctx, ep_index);
+	trace_usbssp_handle_cmd_stop_ep(ep_ctx);
+
+	ep->ep_state &= ~EP_STOP_CMD_PENDING;
+}
+
 static void usbssp_kill_ring_requests(struct usbssp_udc *usbssp_data,
 				      struct usbssp_ring *ring)
 {
@@ -300,6 +401,262 @@ void usbssp_udc_died(struct usbssp_udc *usbssp_data)
 		usbssp_kill_endpoint_request(usbssp_data, i);
 }
 
+static void update_ring_for_set_deq_completion(struct usbssp_udc *usbssp_data,
+					       struct usbssp_device *dev,
+					       struct usbssp_ring *ep_ring,
+					       unsigned int ep_index)
+{
+	union usbssp_trb *dequeue_temp;
+	int num_trbs_free_temp;
+	bool revert = false;
+
+	num_trbs_free_temp = ep_ring->num_trbs_free;
+	dequeue_temp = ep_ring->dequeue;
+
+	if (trb_is_link(ep_ring->dequeue)) {
+		ep_ring->deq_seg = ep_ring->deq_seg->next;
+		ep_ring->dequeue = ep_ring->deq_seg->trbs;
+	}
+
+	while (ep_ring->dequeue != dev->eps[ep_index].queued_deq_ptr) {
+		/* We have more usable TRBs */
+		ep_ring->num_trbs_free++;
+		ep_ring->dequeue++;
+		if (trb_is_link(ep_ring->dequeue)) {
+			if (ep_ring->dequeue ==
+					dev->eps[ep_index].queued_deq_ptr)
+				break;
+			ep_ring->deq_seg = ep_ring->deq_seg->next;
+			ep_ring->dequeue = ep_ring->deq_seg->trbs;
+		}
+		if (ep_ring->dequeue == dequeue_temp) {
+			revert = true;
+			break;
+		}
+	}
+
+	if (revert) {
+		dev_dbg(usbssp_data->dev, "Unable to find new dequeue pointer\n");
+		ep_ring->num_trbs_free = num_trbs_free_temp;
+	}
+}
+
+/*
+ * When we get a completion for a Set Transfer Ring Dequeue Pointer command,
+ * we need to clear the set deq pending flag in the endpoint ring state, so that
+ * the TD queueing code can ring the doorbell again. We also need to ring the
+ * endpoint doorbell to restart the ring
+ */
+static void usbssp_handle_cmd_set_deq(struct usbssp_udc *usbssp_data,
+				      union usbssp_trb *trb, u32 cmd_comp_code)
+{
+	unsigned int ep_index;
+	unsigned int stream_id;
+	struct usbssp_ring *ep_ring;
+	struct usbssp_device *dev;
+	struct usbssp_ep *ep;
+	struct usbssp_ep_ctx *ep_ctx;
+	struct usbssp_slot_ctx *slot_ctx;
+
+	ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3]));
+	stream_id = TRB_TO_STREAM_ID(le32_to_cpu(trb->generic.field[2]));
+	dev = &usbssp_data->devs;
+	ep = &dev->eps[ep_index];
+
+	ep_ring = usbssp_stream_id_to_ring(dev, ep_index, stream_id);
+	if (!ep_ring) {
+		dev_warn(usbssp_data->dev,
+			"WARN Set TR deq ptr command for freed stream ID %u\n",
+			stream_id);
+		goto cleanup;
+	}
+
+	ep_ctx = usbssp_get_ep_ctx(usbssp_data, dev->out_ctx, ep_index);
+	slot_ctx = usbssp_get_slot_ctx(usbssp_data, dev->out_ctx);
+	trace_usbssp_handle_cmd_set_deq(slot_ctx);
+	trace_usbssp_handle_cmd_set_deq_ep(ep_ctx);
+
+	if (cmd_comp_code != COMP_SUCCESS) {
+		unsigned int ep_state;
+		unsigned int slot_state;
+
+		switch (cmd_comp_code) {
+		case COMP_TRB_ERROR:
+			dev_warn(usbssp_data->dev,
+				"WARN Set TR Deq Ptr cmd invalid because of "
+				"stream ID configuration\n");
+			break;
+		case COMP_CONTEXT_STATE_ERROR:
+			dev_warn(usbssp_data->dev, "WARN Set TR Deq Ptr cmd "
+				"failed due to incorrect slot or ep state.\n");
+			ep_state = GET_EP_CTX_STATE(ep_ctx);
+			slot_state = le32_to_cpu(slot_ctx->dev_state);
+			slot_state = GET_SLOT_STATE(slot_state);
+			usbssp_dbg_trace(usbssp_data,
+					trace_usbssp_dbg_cancel_request,
+					"Slot state = %u, EP state = %u",
+					slot_state, ep_state);
+			break;
+		case COMP_SLOT_NOT_ENABLED_ERROR:
+			dev_warn(usbssp_data->dev,
+					"WARN Set TR Deq Ptr cmd failed because"
+					" slot %u was not enabled.\n",
+					usbssp_data->slot_id);
+			break;
+		default:
+			dev_warn(usbssp_data->dev, "WARN Set TR Deq Ptr cmd with"
+					" unknown completion code of %u.\n",
+					cmd_comp_code);
+			break;
+		}
+
+	} else {
+		u64 deq;
+		/* deq ptr is written to the stream ctx for streams */
+		if (ep->ep_state & EP_HAS_STREAMS) {
+			struct usbssp_stream_ctx *ctx =
+				&ep->stream_info->stream_ctx_array[stream_id];
+			deq = le64_to_cpu(ctx->stream_ring) & SCTX_DEQ_MASK;
+		} else {
+			deq = le64_to_cpu(ep_ctx->deq) & ~EP_CTX_CYCLE_MASK;
+		}
+		usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_cancel_request,
+			"Successful Set TR Deq Ptr cmd, deq = @%08llx", deq);
+		if (usbssp_trb_virt_to_dma(ep->queued_deq_seg,
+		    ep->queued_deq_ptr) == deq) {
+
+			/*
+			 * Update the ring's dequeue segment and dequeue pointer
+			 * to reflect the new position.
+			 */
+			update_ring_for_set_deq_completion(usbssp_data, dev,
+				ep_ring, ep_index);
+		} else {
+			dev_warn(usbssp_data->dev,
+				"Mismatch between completed Set TR Deq "
+				"Ptr command & DC internal state.\n");
+			dev_warn(usbssp_data->dev,
+				"ep deq seg = %p, deq ptr = %p\n",
+				ep->queued_deq_seg, ep->queued_deq_ptr);
+		}
+	}
+
+cleanup:
+	dev->eps[ep_index].ep_state &= ~SET_DEQ_PENDING;
+	dev->eps[ep_index].queued_deq_seg = NULL;
+	dev->eps[ep_index].queued_deq_ptr = NULL;
+	/* Restart any rings with pending requests */
+	ring_doorbell_for_active_rings(usbssp_data, ep_index);
+}
+
+
+static void usbssp_handle_cmd_reset_ep(struct usbssp_udc *usbssp_data,
+				       union usbssp_trb *trb,
+				       u32 cmd_comp_code)
+{
+	struct usbssp_ep *dep;
+	struct usbssp_ep_ctx *ep_ctx;
+	unsigned int ep_index;
+
+	ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3]));
+	ep_ctx = usbssp_get_ep_ctx(usbssp_data, usbssp_data->devs.out_ctx,
+			ep_index);
+	trace_usbssp_handle_cmd_reset_ep(ep_ctx);
+
+	/*
+	 * This command will only fail if the endpoint wasn't halted,
+	 * but we don't care.
+	 */
+	usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_reset_ep,
+			"Ignoring reset ep completion code of %u",
+			cmd_comp_code);
+
+	dep = &usbssp_data->devs.eps[ep_index];
+
+	/* Clear our internal halted state */
+	dep->ep_state &= ~EP_HALTED;
+
+	ring_doorbell_for_active_rings(usbssp_data, ep_index);
+}
+
+static void usbssp_handle_cmd_enable_slot(struct usbssp_udc *usbssp_data,
+					  int slot_id,
+					  struct usbssp_command *command,
+					  u32 cmd_comp_code)
+{
+	if (cmd_comp_code == COMP_SUCCESS) {
+		dev_dbg(usbssp_data->dev,
+			"CMD enable slot complition successfully "
+			"- slto id: %d\n", slot_id);
+		usbssp_data->slot_id = slot_id;
+	} else {
+		dev_dbg(usbssp_data->dev, "CMD enable slot complition failed\n");
+		usbssp_data->slot_id = 0;
+	}
+}
+
+static void usbssp_handle_cmd_disable_slot(struct usbssp_udc *usbssp_data)
+{
+	struct usbssp_device *dev_priv;
+	struct usbssp_slot_ctx *slot_ctx;
+
+	dev_dbg(usbssp_data->dev, "CMD disable slot complition\n");
+
+	dev_priv = &usbssp_data->devs;
+	if (!dev_priv)
+		return;
+
+	usbssp_data->slot_id = 0;
+	slot_ctx = usbssp_get_slot_ctx(usbssp_data, dev_priv->out_ctx);
+	trace_usbssp_handle_cmd_disable_slot(slot_ctx);
+}
+
+static void usbssp_handle_cmd_config_ep(struct usbssp_udc *usbssp_data,
+					struct usbssp_event_cmd *event,
+					u32 cmd_comp_code)
+{
+	struct usbssp_device *priv_dev;
+	struct usbssp_input_control_ctx *ctrl_ctx;
+	struct usbssp_ep_ctx *ep_ctx;
+	unsigned int ep_index;
+	u32 add_flags, drop_flags;
+
+	/*
+	 * Configure endpoint commands can come, becaouse device
+	 * receive USB_SET_CONFIGURATION or SET_INTERFACE request,
+	 * or because the HW needed an extra configure endpoint
+	 * command after a reset or disconnect event.
+	 */
+	priv_dev = &usbssp_data->devs;
+	ctrl_ctx = usbssp_get_input_control_ctx(priv_dev->in_ctx);
+	if (!ctrl_ctx) {
+		dev_warn(usbssp_data->dev,
+			"Could not get input context, bad type.\n");
+		return;
+	}
+
+	add_flags = le32_to_cpu(ctrl_ctx->add_flags);
+	drop_flags = le32_to_cpu(ctrl_ctx->drop_flags);
+	/* Input ctx add_flags are the endpoint index plus one */
+	ep_index = usbssp_last_valid_endpoint(add_flags) - 1;
+
+	ep_ctx = usbssp_get_ep_ctx(usbssp_data, priv_dev->out_ctx, ep_index);
+	trace_usbssp_handle_cmd_config_ep(ep_ctx);
+}
+
+static void usbssp_handle_cmd_reset_dev(struct usbssp_udc *usbssp_data,
+					struct usbssp_event_cmd *event)
+{
+	struct usbssp_device *dev_priv;
+	struct usbssp_slot_ctx *slot_ctx;
+
+	dev_priv = &usbssp_data->devs;
+	slot_ctx = usbssp_get_slot_ctx(usbssp_data, dev_priv->out_ctx);
+	trace_usbssp_handle_cmd_reset_dev(slot_ctx);
+	dev_dbg(usbssp_data->dev, "Completed reset device command.\n");
+	if (!usbssp_data->devs.gadget)
+		dev_warn(usbssp_data->dev, "Reset device command completion\n");
+}
 
 void usbssp_handle_command_timeout(struct work_struct *work)
 {
@@ -327,6 +684,156 @@ void usbssp_cleanup_command_queue(struct usbssp_udc *usbssp_data)
 		usbssp_complete_del_and_free_cmd(cur_cmd, COMP_COMMAND_ABORTED);
 }
 
+static void handle_cmd_completion(struct usbssp_udc *usbssp_data,
+				  struct usbssp_event_cmd *event)
+{
+	int slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags));
+	u64 cmd_dma;
+	dma_addr_t cmd_dequeue_dma;
+	u32 cmd_comp_code;
+	union usbssp_trb *cmd_trb;
+	struct usbssp_command *cmd;
+	u32 cmd_type;
+
+	cmd_dma = le64_to_cpu(event->cmd_trb);
+	cmd_trb = usbssp_data->cmd_ring->dequeue;
+
+	trace_usbssp_handle_command(usbssp_data->cmd_ring, &cmd_trb->generic);
+
+	cmd_dequeue_dma = usbssp_trb_virt_to_dma(usbssp_data->cmd_ring->deq_seg,
+			cmd_trb);
+
+	/*
+	 * Check whether the completion event is for our internal kept
+	 * command.
+	 */
+	if (!cmd_dequeue_dma || cmd_dma != (u64)cmd_dequeue_dma) {
+		dev_warn(usbssp_data->dev,
+			"ERROR mismatched command completion event\n");
+		return;
+	}
+
+	cmd = list_entry(usbssp_data->cmd_list.next, struct usbssp_command,
+			cmd_list);
+
+	cancel_delayed_work(&usbssp_data->cmd_timer);
+
+	cmd_comp_code = GET_COMP_CODE(le32_to_cpu(event->status));
+
+	/* If CMD ring stopped we own the trbs between enqueue and dequeue */
+	if (cmd_comp_code == COMP_COMMAND_RING_STOPPED) {
+		complete_all(&usbssp_data->cmd_ring_stop_completion);
+		return;
+	}
+
+	if (cmd->command_trb != usbssp_data->cmd_ring->dequeue) {
+		dev_err(usbssp_data->dev,
+			 "Command completion event does not match command\n");
+		return;
+	}
+
+	/*
+	 * device aborted the command ring, check if the current command was
+	 * supposed to be aborted, otherwise continue normally.
+	 * The command ring is stopped now, but the DC will issue a Command
+	 * Ring Stopped event which will cause us to restart it.
+	 */
+	if (cmd_comp_code == COMP_COMMAND_ABORTED) {
+		usbssp_data->cmd_ring_state = CMD_RING_STATE_STOPPED;
+
+		if (cmd->status == COMP_COMMAND_ABORTED) {
+			if (usbssp_data->current_cmd == cmd)
+				usbssp_data->current_cmd = NULL;
+			goto event_handled;
+		}
+	}
+
+	cmd_type = TRB_FIELD_TO_TYPE(le32_to_cpu(cmd_trb->generic.field[3]));
+	switch (cmd_type) {
+	case TRB_ENABLE_SLOT:
+		usbssp_handle_cmd_enable_slot(usbssp_data, slot_id,
+					cmd, cmd_comp_code);
+		break;
+	case TRB_DISABLE_SLOT:
+		usbssp_handle_cmd_disable_slot(usbssp_data);
+		break;
+	case TRB_CONFIG_EP:
+		if (!cmd->completion)
+			usbssp_handle_cmd_config_ep(usbssp_data, event,
+						cmd_comp_code);
+		break;
+	case TRB_EVAL_CONTEXT:
+		break;
+	case TRB_ADDR_DEV: {
+		struct usbssp_slot_ctx *slot_ctx;
+
+		slot_ctx = usbssp_get_slot_ctx(usbssp_data,
+					usbssp_data->devs.out_ctx);
+		trace_usbssp_handle_cmd_addr_dev(slot_ctx);
+		break;
+	}
+	case TRB_STOP_RING:
+		WARN_ON(slot_id != TRB_TO_SLOT_ID(
+				le32_to_cpu(cmd_trb->generic.field[3])));
+		usbssp_handle_cmd_stop_ep(usbssp_data, cmd_trb, event);
+		break;
+	case TRB_SET_DEQ:
+		WARN_ON(slot_id != TRB_TO_SLOT_ID(
+				le32_to_cpu(cmd_trb->generic.field[3])));
+		usbssp_handle_cmd_set_deq(usbssp_data, cmd_trb, cmd_comp_code);
+		break;
+	case TRB_CMD_NOOP:
+		/* Is this an aborted command turned to NO-OP? */
+		if (cmd->status == COMP_COMMAND_RING_STOPPED)
+			cmd_comp_code = COMP_COMMAND_RING_STOPPED;
+		break;
+	case TRB_HALT_ENDPOINT:
+		if (cmd->status == COMP_COMMAND_RING_STOPPED)
+			cmd_comp_code = COMP_COMMAND_RING_STOPPED;
+		break;
+	case TRB_FLUSH_ENDPOINT:
+		if (cmd->status == COMP_COMMAND_RING_STOPPED)
+			cmd_comp_code = COMP_COMMAND_RING_STOPPED;
+		break;
+	case TRB_RESET_EP:
+		WARN_ON(slot_id != TRB_TO_SLOT_ID(
+				le32_to_cpu(cmd_trb->generic.field[3])));
+		usbssp_handle_cmd_reset_ep(usbssp_data, cmd_trb, cmd_comp_code);
+		break;
+	case TRB_RESET_DEV:
+		/*
+		 * SLOT_ID field in reset device cmd completion event TRB is 0.
+		 * Use the SLOT_ID from the command TRB instead.
+		 */
+		slot_id = TRB_TO_SLOT_ID(
+				le32_to_cpu(cmd_trb->generic.field[3]));
+
+		WARN_ON(slot_id != 0);
+		usbssp_handle_cmd_reset_dev(usbssp_data, event);
+		break;
+	case TRB_FORCE_HEADER:
+		break;
+	default:
+		/* Skip over unknown commands on the event ring */
+		dev_info(usbssp_data->dev, "INFO unknown command type %d\n",
+			cmd_type);
+		break;
+	}
+
+	/* restart timer if this wasn't the last command */
+	if (!list_is_singular(&usbssp_data->cmd_list)) {
+		usbssp_data->current_cmd = list_first_entry(&cmd->cmd_list,
+					struct usbssp_command, cmd_list);
+		usbssp_mod_cmd_timer(usbssp_data, USBSSP_CMD_DEFAULT_TIMEOUT);
+	} else if (usbssp_data->current_cmd == cmd) {
+		usbssp_data->current_cmd = NULL;
+	}
+
+event_handled:
+	usbssp_complete_del_and_free_cmd(cmd, cmd_comp_code);
+	inc_deq(usbssp_data, usbssp_data->cmd_ring);
+}
+
 static void handle_port_status(struct usbssp_udc *usbssp_data,
 			       union usbssp_trb *event)
 {
@@ -596,6 +1103,9 @@ int usbssp_handle_event(struct usbssp_udc *usbssp_data)
 	rmb();
 
 	switch ((le32_to_cpu(event->event_cmd.flags) & TRB_TYPE_BITMASK)) {
+	case TRB_TYPE(TRB_COMPLETION):
+		handle_cmd_completion(usbssp_data, &event->event_cmd);
+		break;
 	case TRB_TYPE(TRB_PORT_STATUS):
 		handle_port_status(usbssp_data, event);
 		update_ptrs = 0;
diff --git a/drivers/usb/usbssp/gadget.c b/drivers/usb/usbssp/gadget.c
index 2996b1d3baf7..6b3dc973c0d9 100644
--- a/drivers/usb/usbssp/gadget.c
+++ b/drivers/usb/usbssp/gadget.c
@@ -262,6 +262,17 @@ int usbssp_resume(struct usbssp_udc *usbssp_data, bool hibernated)
 
 #endif	/* CONFIG_PM */
 
+/* Compute the last valid endpoint context index. Basically, this is the
+ * endpoint index plus one. For slot contexts with more than valid endpoint,
+ * we find the most significant bit set in the added contexts flags.
+ * e.g. ep 1 IN (with epnum 0x81) => added_ctxs = 0b1000
+ * fls(0b1000) = 4, but the endpoint context index is 3, so subtract one.
+ */
+unsigned int usbssp_last_valid_endpoint(u32 added_ctxs)
+{
+	return fls(added_ctxs) - 1;
+}
+
 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 33eccffc885d..145371eee47d 100644
--- a/drivers/usb/usbssp/gadget.h
+++ b/drivers/usb/usbssp/gadget.h
@@ -1685,9 +1685,13 @@ 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);
+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);
+struct usbssp_ring *usbssp_stream_id_to_ring(struct usbssp_device *dev,
+					unsigned int ep_index,
+					unsigned int stream_id);
 
 struct usbssp_command *usbssp_alloc_command(struct usbssp_udc *usbssp_data,
 					bool allocate_completion,
@@ -1732,6 +1736,14 @@ void usbssp_set_link_state(struct usbssp_udc *usbssp_data,
 void usbssp_test_and_clear_bit(struct usbssp_udc *usbssp_data,
 			__le32 __iomem *port_regs, u32 port_bit);
 
+/* USBSSP DC contexts */
+struct usbssp_input_control_ctx *usbssp_get_input_control_ctx(
+					struct usbssp_container_ctx *ctx);
+struct usbssp_slot_ctx *usbssp_get_slot_ctx(struct usbssp_udc *usbssp_data,
+					struct usbssp_container_ctx *ctx);
+struct usbssp_ep_ctx *usbssp_get_ep_ctx(struct usbssp_udc *usbssp_data,
+					struct usbssp_container_ctx *ctx,
+					unsigned int ep_index);
 /* USBSSP gadget interface*/
 void usbssp_suspend_gadget(struct usbssp_udc *usbssp_data);
 void usbssp_resume_gadget(struct usbssp_udc *usbssp_data);
-- 
2.17.1


WARNING: multiple messages have this Message-ID (diff)
From: Pawel Laszczak <pawell@cadence.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	linux-usb@vger.kernel.org, Felipe Balbi <balbi@kernel.org>,
	linux-kernel@vger.kernel.org, ltyrala@cadence.com,
	adouglas@cadence.com, pawell@cadence.com
Subject: [14/31] usb: usbssp: added procedure handling command completion events.
Date: Thu, 19 Jul 2018 18:57:47 +0100	[thread overview]
Message-ID: <1532023084-28083-15-git-send-email-pawell@cadence.com> (raw)

This patch extends the usbssp_handle_event function with a new case,
that is responsible for servicing command completion events.

For this purpose, it adds handle_cmd_completion function which in turn
invokes some other functions depending of handling command type.

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/usbssp/gadget-mem.c  |  53 +++-
 drivers/usb/usbssp/gadget-ring.c | 510 +++++++++++++++++++++++++++++++
 drivers/usb/usbssp/gadget.c      |  11 +
 drivers/usb/usbssp/gadget.h      |  12 +
 4 files changed, 584 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/usbssp/gadget-mem.c b/drivers/usb/usbssp/gadget-mem.c
index fef83b6b6cf0..5708a0090ead 100644
--- a/drivers/usb/usbssp/gadget-mem.c
+++ b/drivers/usb/usbssp/gadget-mem.c
@@ -504,6 +504,38 @@ void usbssp_free_container_ctx(struct usbssp_udc *usbssp_data,
 	kfree(ctx);
 }
 
+struct usbssp_input_control_ctx *usbssp_get_input_control_ctx(
+					struct usbssp_container_ctx *ctx)
+{
+	if (ctx->type != USBSSP_CTX_TYPE_INPUT)
+		return NULL;
+
+	return (struct usbssp_input_control_ctx *)ctx->bytes;
+}
+
+struct usbssp_slot_ctx *usbssp_get_slot_ctx(struct usbssp_udc *usbssp_data,
+					    struct usbssp_container_ctx *ctx)
+{
+	if (ctx->type == USBSSP_CTX_TYPE_DEVICE)
+		return (struct usbssp_slot_ctx *)ctx->bytes;
+
+	return (struct usbssp_slot_ctx *) (ctx->bytes +
+		CTX_SIZE(usbssp_data->hcc_params));
+}
+
+struct usbssp_ep_ctx *usbssp_get_ep_ctx(struct usbssp_udc *usbssp_data,
+					struct usbssp_container_ctx *ctx,
+					unsigned int ep_index)
+{
+	/* increment ep index by offset of start of ep ctx array */
+	ep_index++;
+	if (ctx->type == USBSSP_CTX_TYPE_INPUT)
+		ep_index++;
+
+	return (struct usbssp_ep_ctx *) (ctx->bytes +
+		(ep_index * CTX_SIZE(usbssp_data->hcc_params)));
+}
+
 /***************** Streams structures manipulation *************************/
 static void usbssp_free_stream_ctx(struct usbssp_udc *usbssp_data,
 				   unsigned int num_stream_ctxs,
@@ -523,8 +555,25 @@ static void usbssp_free_stream_ctx(struct usbssp_udc *usbssp_data,
 				stream_ctx, dma);
 }
 
-/**
- * Frees all stream contexts associated with the endpoint,
+struct usbssp_ring *usbssp_stream_id_to_ring(struct usbssp_device *dev,
+					     unsigned int ep_index,
+					     unsigned int stream_id)
+{
+	struct usbssp_ep *ep = &dev->eps[ep_index];
+
+	if (stream_id == 0)
+		return ep->ring;
+
+	if (!ep->stream_info)
+		return NULL;
+
+	if (stream_id > ep->stream_info->num_streams)
+		return NULL;
+	return ep->stream_info->stream_rings[stream_id];
+}
+
+/*
+ *  Frees all stream contexts associated with the endpoint,
  *
  * Caller should fix the endpoint context streams fields.
  */
diff --git a/drivers/usb/usbssp/gadget-ring.c b/drivers/usb/usbssp/gadget-ring.c
index 6679d8ec7152..c704b939b48a 100644
--- a/drivers/usb/usbssp/gadget-ring.c
+++ b/drivers/usb/usbssp/gadget-ring.c
@@ -225,6 +225,107 @@ static bool usbssp_mod_cmd_timer(struct usbssp_udc *usbssp_data,
 	return 0;
 }
 
+void usbssp_ring_ep_doorbell(struct usbssp_udc *usbssp_data,
+			     unsigned int ep_index,
+			     unsigned int stream_id)
+{
+	__le32 __iomem *db_addr =
+			&usbssp_data->dba->doorbell[usbssp_data->slot_id];
+	struct usbssp_ep *ep = &usbssp_data->devs.eps[ep_index];
+	unsigned int ep_state = ep->ep_state;
+	unsigned int db_value;
+
+	/*
+	 * Don't ring the doorbell for this endpoint if there are pending
+	 * cancellations because we don't want to interrupt processing.
+	 * We don't want to restart any stream rings if there's a set dequeue
+	 * pointer command pending because the device can choose to start any
+	 * stream once the endpoint is on the HW schedule.
+	 * Also we don't want restart any endpoint if endpoint is halted or
+	 * disabled and also if endpoint disabling is pending.
+	 */
+	if ((ep_state & EP_STOP_CMD_PENDING) ||
+	    (ep_state & SET_DEQ_PENDING) ||
+	    (ep_state & EP_HALTED) ||
+	    !(ep_state & USBSSP_EP_ENABLED) ||
+	    (ep_state & USBSSP_EP_DISABLE_PENDING))
+		return;
+
+	if (ep_index == 0 && !usbssp_data->ep0_expect_in &&
+	    usbssp_data->ep0state == USBSSP_EP0_DATA_PHASE)
+		db_value = DB_VALUE_EP0_OUT(ep_index, stream_id);
+	else
+		db_value = DB_VALUE(ep_index, stream_id);
+
+	dev_dbg(usbssp_data->dev, "// Ding dong transfer ring for %s!"
+		" - [DB addr/DB val]: [%p/%08x]\n",
+		usbssp_data->devs.eps[ep_index].name, db_addr,
+		db_value);
+
+	writel(db_value, db_addr);
+	/*
+	 * The CPU has better things to do at this point than wait for a
+	 * write-posting flush. It'll get there soon enough.
+	 */
+}
+
+/* Ring the doorbell for any rings with pending USB requests */
+static void ring_doorbell_for_active_rings(struct usbssp_udc *usbssp_data,
+					   unsigned int ep_index)
+{
+	unsigned int stream_id;
+	struct usbssp_ep *ep;
+
+	ep = &usbssp_data->devs.eps[ep_index];
+
+	dev_dbg(usbssp_data->dev, "Ring all active ring for %s\n",
+		ep->name);
+
+	/* A ring has pending Request if its TD list is not empty */
+	if (!(ep->ep_state & EP_HAS_STREAMS)) {
+		if (ep->ring && !(list_empty(&ep->ring->td_list)))
+			usbssp_ring_ep_doorbell(usbssp_data, ep_index, 0);
+		return;
+	}
+
+	for (stream_id = 1; stream_id < ep->stream_info->num_streams;
+			stream_id++) {
+		struct usbssp_stream_info *stream_info = ep->stream_info;
+
+		if (!list_empty(&stream_info->stream_rings[stream_id]->td_list))
+			usbssp_ring_ep_doorbell(usbssp_data, ep_index,
+						stream_id);
+	}
+}
+
+/*
+ * When we get a command completion for a Stop Endpoint Command, we need to
+ * stop timer and clear EP_STOP_CMD_PENDING flag.
+ */
+static void usbssp_handle_cmd_stop_ep(struct usbssp_udc *usbssp_data,
+				      union usbssp_trb *trb,
+				      struct usbssp_event_cmd *event)
+{
+	unsigned int ep_index;
+	struct usbssp_ep *ep;
+	struct usbssp_ep_ctx *ep_ctx;
+	struct usbssp_device *priv_dev;
+
+	ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3]));
+	ep = &usbssp_data->devs.eps[ep_index];
+
+	dev_dbg(usbssp_data->dev,
+		"CMD stop endpoint completion for ep index: %d - %s\n",
+		ep_index, ep->name);
+
+
+	priv_dev = &usbssp_data->devs;
+	ep_ctx = usbssp_get_ep_ctx(usbssp_data, priv_dev->out_ctx, ep_index);
+	trace_usbssp_handle_cmd_stop_ep(ep_ctx);
+
+	ep->ep_state &= ~EP_STOP_CMD_PENDING;
+}
+
 static void usbssp_kill_ring_requests(struct usbssp_udc *usbssp_data,
 				      struct usbssp_ring *ring)
 {
@@ -300,6 +401,262 @@ void usbssp_udc_died(struct usbssp_udc *usbssp_data)
 		usbssp_kill_endpoint_request(usbssp_data, i);
 }
 
+static void update_ring_for_set_deq_completion(struct usbssp_udc *usbssp_data,
+					       struct usbssp_device *dev,
+					       struct usbssp_ring *ep_ring,
+					       unsigned int ep_index)
+{
+	union usbssp_trb *dequeue_temp;
+	int num_trbs_free_temp;
+	bool revert = false;
+
+	num_trbs_free_temp = ep_ring->num_trbs_free;
+	dequeue_temp = ep_ring->dequeue;
+
+	if (trb_is_link(ep_ring->dequeue)) {
+		ep_ring->deq_seg = ep_ring->deq_seg->next;
+		ep_ring->dequeue = ep_ring->deq_seg->trbs;
+	}
+
+	while (ep_ring->dequeue != dev->eps[ep_index].queued_deq_ptr) {
+		/* We have more usable TRBs */
+		ep_ring->num_trbs_free++;
+		ep_ring->dequeue++;
+		if (trb_is_link(ep_ring->dequeue)) {
+			if (ep_ring->dequeue ==
+					dev->eps[ep_index].queued_deq_ptr)
+				break;
+			ep_ring->deq_seg = ep_ring->deq_seg->next;
+			ep_ring->dequeue = ep_ring->deq_seg->trbs;
+		}
+		if (ep_ring->dequeue == dequeue_temp) {
+			revert = true;
+			break;
+		}
+	}
+
+	if (revert) {
+		dev_dbg(usbssp_data->dev, "Unable to find new dequeue pointer\n");
+		ep_ring->num_trbs_free = num_trbs_free_temp;
+	}
+}
+
+/*
+ * When we get a completion for a Set Transfer Ring Dequeue Pointer command,
+ * we need to clear the set deq pending flag in the endpoint ring state, so that
+ * the TD queueing code can ring the doorbell again. We also need to ring the
+ * endpoint doorbell to restart the ring
+ */
+static void usbssp_handle_cmd_set_deq(struct usbssp_udc *usbssp_data,
+				      union usbssp_trb *trb, u32 cmd_comp_code)
+{
+	unsigned int ep_index;
+	unsigned int stream_id;
+	struct usbssp_ring *ep_ring;
+	struct usbssp_device *dev;
+	struct usbssp_ep *ep;
+	struct usbssp_ep_ctx *ep_ctx;
+	struct usbssp_slot_ctx *slot_ctx;
+
+	ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3]));
+	stream_id = TRB_TO_STREAM_ID(le32_to_cpu(trb->generic.field[2]));
+	dev = &usbssp_data->devs;
+	ep = &dev->eps[ep_index];
+
+	ep_ring = usbssp_stream_id_to_ring(dev, ep_index, stream_id);
+	if (!ep_ring) {
+		dev_warn(usbssp_data->dev,
+			"WARN Set TR deq ptr command for freed stream ID %u\n",
+			stream_id);
+		goto cleanup;
+	}
+
+	ep_ctx = usbssp_get_ep_ctx(usbssp_data, dev->out_ctx, ep_index);
+	slot_ctx = usbssp_get_slot_ctx(usbssp_data, dev->out_ctx);
+	trace_usbssp_handle_cmd_set_deq(slot_ctx);
+	trace_usbssp_handle_cmd_set_deq_ep(ep_ctx);
+
+	if (cmd_comp_code != COMP_SUCCESS) {
+		unsigned int ep_state;
+		unsigned int slot_state;
+
+		switch (cmd_comp_code) {
+		case COMP_TRB_ERROR:
+			dev_warn(usbssp_data->dev,
+				"WARN Set TR Deq Ptr cmd invalid because of "
+				"stream ID configuration\n");
+			break;
+		case COMP_CONTEXT_STATE_ERROR:
+			dev_warn(usbssp_data->dev, "WARN Set TR Deq Ptr cmd "
+				"failed due to incorrect slot or ep state.\n");
+			ep_state = GET_EP_CTX_STATE(ep_ctx);
+			slot_state = le32_to_cpu(slot_ctx->dev_state);
+			slot_state = GET_SLOT_STATE(slot_state);
+			usbssp_dbg_trace(usbssp_data,
+					trace_usbssp_dbg_cancel_request,
+					"Slot state = %u, EP state = %u",
+					slot_state, ep_state);
+			break;
+		case COMP_SLOT_NOT_ENABLED_ERROR:
+			dev_warn(usbssp_data->dev,
+					"WARN Set TR Deq Ptr cmd failed because"
+					" slot %u was not enabled.\n",
+					usbssp_data->slot_id);
+			break;
+		default:
+			dev_warn(usbssp_data->dev, "WARN Set TR Deq Ptr cmd with"
+					" unknown completion code of %u.\n",
+					cmd_comp_code);
+			break;
+		}
+
+	} else {
+		u64 deq;
+		/* deq ptr is written to the stream ctx for streams */
+		if (ep->ep_state & EP_HAS_STREAMS) {
+			struct usbssp_stream_ctx *ctx =
+				&ep->stream_info->stream_ctx_array[stream_id];
+			deq = le64_to_cpu(ctx->stream_ring) & SCTX_DEQ_MASK;
+		} else {
+			deq = le64_to_cpu(ep_ctx->deq) & ~EP_CTX_CYCLE_MASK;
+		}
+		usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_cancel_request,
+			"Successful Set TR Deq Ptr cmd, deq = @%08llx", deq);
+		if (usbssp_trb_virt_to_dma(ep->queued_deq_seg,
+		    ep->queued_deq_ptr) == deq) {
+
+			/*
+			 * Update the ring's dequeue segment and dequeue pointer
+			 * to reflect the new position.
+			 */
+			update_ring_for_set_deq_completion(usbssp_data, dev,
+				ep_ring, ep_index);
+		} else {
+			dev_warn(usbssp_data->dev,
+				"Mismatch between completed Set TR Deq "
+				"Ptr command & DC internal state.\n");
+			dev_warn(usbssp_data->dev,
+				"ep deq seg = %p, deq ptr = %p\n",
+				ep->queued_deq_seg, ep->queued_deq_ptr);
+		}
+	}
+
+cleanup:
+	dev->eps[ep_index].ep_state &= ~SET_DEQ_PENDING;
+	dev->eps[ep_index].queued_deq_seg = NULL;
+	dev->eps[ep_index].queued_deq_ptr = NULL;
+	/* Restart any rings with pending requests */
+	ring_doorbell_for_active_rings(usbssp_data, ep_index);
+}
+
+
+static void usbssp_handle_cmd_reset_ep(struct usbssp_udc *usbssp_data,
+				       union usbssp_trb *trb,
+				       u32 cmd_comp_code)
+{
+	struct usbssp_ep *dep;
+	struct usbssp_ep_ctx *ep_ctx;
+	unsigned int ep_index;
+
+	ep_index = TRB_TO_EP_INDEX(le32_to_cpu(trb->generic.field[3]));
+	ep_ctx = usbssp_get_ep_ctx(usbssp_data, usbssp_data->devs.out_ctx,
+			ep_index);
+	trace_usbssp_handle_cmd_reset_ep(ep_ctx);
+
+	/*
+	 * This command will only fail if the endpoint wasn't halted,
+	 * but we don't care.
+	 */
+	usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_reset_ep,
+			"Ignoring reset ep completion code of %u",
+			cmd_comp_code);
+
+	dep = &usbssp_data->devs.eps[ep_index];
+
+	/* Clear our internal halted state */
+	dep->ep_state &= ~EP_HALTED;
+
+	ring_doorbell_for_active_rings(usbssp_data, ep_index);
+}
+
+static void usbssp_handle_cmd_enable_slot(struct usbssp_udc *usbssp_data,
+					  int slot_id,
+					  struct usbssp_command *command,
+					  u32 cmd_comp_code)
+{
+	if (cmd_comp_code == COMP_SUCCESS) {
+		dev_dbg(usbssp_data->dev,
+			"CMD enable slot complition successfully "
+			"- slto id: %d\n", slot_id);
+		usbssp_data->slot_id = slot_id;
+	} else {
+		dev_dbg(usbssp_data->dev, "CMD enable slot complition failed\n");
+		usbssp_data->slot_id = 0;
+	}
+}
+
+static void usbssp_handle_cmd_disable_slot(struct usbssp_udc *usbssp_data)
+{
+	struct usbssp_device *dev_priv;
+	struct usbssp_slot_ctx *slot_ctx;
+
+	dev_dbg(usbssp_data->dev, "CMD disable slot complition\n");
+
+	dev_priv = &usbssp_data->devs;
+	if (!dev_priv)
+		return;
+
+	usbssp_data->slot_id = 0;
+	slot_ctx = usbssp_get_slot_ctx(usbssp_data, dev_priv->out_ctx);
+	trace_usbssp_handle_cmd_disable_slot(slot_ctx);
+}
+
+static void usbssp_handle_cmd_config_ep(struct usbssp_udc *usbssp_data,
+					struct usbssp_event_cmd *event,
+					u32 cmd_comp_code)
+{
+	struct usbssp_device *priv_dev;
+	struct usbssp_input_control_ctx *ctrl_ctx;
+	struct usbssp_ep_ctx *ep_ctx;
+	unsigned int ep_index;
+	u32 add_flags, drop_flags;
+
+	/*
+	 * Configure endpoint commands can come, becaouse device
+	 * receive USB_SET_CONFIGURATION or SET_INTERFACE request,
+	 * or because the HW needed an extra configure endpoint
+	 * command after a reset or disconnect event.
+	 */
+	priv_dev = &usbssp_data->devs;
+	ctrl_ctx = usbssp_get_input_control_ctx(priv_dev->in_ctx);
+	if (!ctrl_ctx) {
+		dev_warn(usbssp_data->dev,
+			"Could not get input context, bad type.\n");
+		return;
+	}
+
+	add_flags = le32_to_cpu(ctrl_ctx->add_flags);
+	drop_flags = le32_to_cpu(ctrl_ctx->drop_flags);
+	/* Input ctx add_flags are the endpoint index plus one */
+	ep_index = usbssp_last_valid_endpoint(add_flags) - 1;
+
+	ep_ctx = usbssp_get_ep_ctx(usbssp_data, priv_dev->out_ctx, ep_index);
+	trace_usbssp_handle_cmd_config_ep(ep_ctx);
+}
+
+static void usbssp_handle_cmd_reset_dev(struct usbssp_udc *usbssp_data,
+					struct usbssp_event_cmd *event)
+{
+	struct usbssp_device *dev_priv;
+	struct usbssp_slot_ctx *slot_ctx;
+
+	dev_priv = &usbssp_data->devs;
+	slot_ctx = usbssp_get_slot_ctx(usbssp_data, dev_priv->out_ctx);
+	trace_usbssp_handle_cmd_reset_dev(slot_ctx);
+	dev_dbg(usbssp_data->dev, "Completed reset device command.\n");
+	if (!usbssp_data->devs.gadget)
+		dev_warn(usbssp_data->dev, "Reset device command completion\n");
+}
 
 void usbssp_handle_command_timeout(struct work_struct *work)
 {
@@ -327,6 +684,156 @@ void usbssp_cleanup_command_queue(struct usbssp_udc *usbssp_data)
 		usbssp_complete_del_and_free_cmd(cur_cmd, COMP_COMMAND_ABORTED);
 }
 
+static void handle_cmd_completion(struct usbssp_udc *usbssp_data,
+				  struct usbssp_event_cmd *event)
+{
+	int slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags));
+	u64 cmd_dma;
+	dma_addr_t cmd_dequeue_dma;
+	u32 cmd_comp_code;
+	union usbssp_trb *cmd_trb;
+	struct usbssp_command *cmd;
+	u32 cmd_type;
+
+	cmd_dma = le64_to_cpu(event->cmd_trb);
+	cmd_trb = usbssp_data->cmd_ring->dequeue;
+
+	trace_usbssp_handle_command(usbssp_data->cmd_ring, &cmd_trb->generic);
+
+	cmd_dequeue_dma = usbssp_trb_virt_to_dma(usbssp_data->cmd_ring->deq_seg,
+			cmd_trb);
+
+	/*
+	 * Check whether the completion event is for our internal kept
+	 * command.
+	 */
+	if (!cmd_dequeue_dma || cmd_dma != (u64)cmd_dequeue_dma) {
+		dev_warn(usbssp_data->dev,
+			"ERROR mismatched command completion event\n");
+		return;
+	}
+
+	cmd = list_entry(usbssp_data->cmd_list.next, struct usbssp_command,
+			cmd_list);
+
+	cancel_delayed_work(&usbssp_data->cmd_timer);
+
+	cmd_comp_code = GET_COMP_CODE(le32_to_cpu(event->status));
+
+	/* If CMD ring stopped we own the trbs between enqueue and dequeue */
+	if (cmd_comp_code == COMP_COMMAND_RING_STOPPED) {
+		complete_all(&usbssp_data->cmd_ring_stop_completion);
+		return;
+	}
+
+	if (cmd->command_trb != usbssp_data->cmd_ring->dequeue) {
+		dev_err(usbssp_data->dev,
+			 "Command completion event does not match command\n");
+		return;
+	}
+
+	/*
+	 * device aborted the command ring, check if the current command was
+	 * supposed to be aborted, otherwise continue normally.
+	 * The command ring is stopped now, but the DC will issue a Command
+	 * Ring Stopped event which will cause us to restart it.
+	 */
+	if (cmd_comp_code == COMP_COMMAND_ABORTED) {
+		usbssp_data->cmd_ring_state = CMD_RING_STATE_STOPPED;
+
+		if (cmd->status == COMP_COMMAND_ABORTED) {
+			if (usbssp_data->current_cmd == cmd)
+				usbssp_data->current_cmd = NULL;
+			goto event_handled;
+		}
+	}
+
+	cmd_type = TRB_FIELD_TO_TYPE(le32_to_cpu(cmd_trb->generic.field[3]));
+	switch (cmd_type) {
+	case TRB_ENABLE_SLOT:
+		usbssp_handle_cmd_enable_slot(usbssp_data, slot_id,
+					cmd, cmd_comp_code);
+		break;
+	case TRB_DISABLE_SLOT:
+		usbssp_handle_cmd_disable_slot(usbssp_data);
+		break;
+	case TRB_CONFIG_EP:
+		if (!cmd->completion)
+			usbssp_handle_cmd_config_ep(usbssp_data, event,
+						cmd_comp_code);
+		break;
+	case TRB_EVAL_CONTEXT:
+		break;
+	case TRB_ADDR_DEV: {
+		struct usbssp_slot_ctx *slot_ctx;
+
+		slot_ctx = usbssp_get_slot_ctx(usbssp_data,
+					usbssp_data->devs.out_ctx);
+		trace_usbssp_handle_cmd_addr_dev(slot_ctx);
+		break;
+	}
+	case TRB_STOP_RING:
+		WARN_ON(slot_id != TRB_TO_SLOT_ID(
+				le32_to_cpu(cmd_trb->generic.field[3])));
+		usbssp_handle_cmd_stop_ep(usbssp_data, cmd_trb, event);
+		break;
+	case TRB_SET_DEQ:
+		WARN_ON(slot_id != TRB_TO_SLOT_ID(
+				le32_to_cpu(cmd_trb->generic.field[3])));
+		usbssp_handle_cmd_set_deq(usbssp_data, cmd_trb, cmd_comp_code);
+		break;
+	case TRB_CMD_NOOP:
+		/* Is this an aborted command turned to NO-OP? */
+		if (cmd->status == COMP_COMMAND_RING_STOPPED)
+			cmd_comp_code = COMP_COMMAND_RING_STOPPED;
+		break;
+	case TRB_HALT_ENDPOINT:
+		if (cmd->status == COMP_COMMAND_RING_STOPPED)
+			cmd_comp_code = COMP_COMMAND_RING_STOPPED;
+		break;
+	case TRB_FLUSH_ENDPOINT:
+		if (cmd->status == COMP_COMMAND_RING_STOPPED)
+			cmd_comp_code = COMP_COMMAND_RING_STOPPED;
+		break;
+	case TRB_RESET_EP:
+		WARN_ON(slot_id != TRB_TO_SLOT_ID(
+				le32_to_cpu(cmd_trb->generic.field[3])));
+		usbssp_handle_cmd_reset_ep(usbssp_data, cmd_trb, cmd_comp_code);
+		break;
+	case TRB_RESET_DEV:
+		/*
+		 * SLOT_ID field in reset device cmd completion event TRB is 0.
+		 * Use the SLOT_ID from the command TRB instead.
+		 */
+		slot_id = TRB_TO_SLOT_ID(
+				le32_to_cpu(cmd_trb->generic.field[3]));
+
+		WARN_ON(slot_id != 0);
+		usbssp_handle_cmd_reset_dev(usbssp_data, event);
+		break;
+	case TRB_FORCE_HEADER:
+		break;
+	default:
+		/* Skip over unknown commands on the event ring */
+		dev_info(usbssp_data->dev, "INFO unknown command type %d\n",
+			cmd_type);
+		break;
+	}
+
+	/* restart timer if this wasn't the last command */
+	if (!list_is_singular(&usbssp_data->cmd_list)) {
+		usbssp_data->current_cmd = list_first_entry(&cmd->cmd_list,
+					struct usbssp_command, cmd_list);
+		usbssp_mod_cmd_timer(usbssp_data, USBSSP_CMD_DEFAULT_TIMEOUT);
+	} else if (usbssp_data->current_cmd == cmd) {
+		usbssp_data->current_cmd = NULL;
+	}
+
+event_handled:
+	usbssp_complete_del_and_free_cmd(cmd, cmd_comp_code);
+	inc_deq(usbssp_data, usbssp_data->cmd_ring);
+}
+
 static void handle_port_status(struct usbssp_udc *usbssp_data,
 			       union usbssp_trb *event)
 {
@@ -596,6 +1103,9 @@ int usbssp_handle_event(struct usbssp_udc *usbssp_data)
 	rmb();
 
 	switch ((le32_to_cpu(event->event_cmd.flags) & TRB_TYPE_BITMASK)) {
+	case TRB_TYPE(TRB_COMPLETION):
+		handle_cmd_completion(usbssp_data, &event->event_cmd);
+		break;
 	case TRB_TYPE(TRB_PORT_STATUS):
 		handle_port_status(usbssp_data, event);
 		update_ptrs = 0;
diff --git a/drivers/usb/usbssp/gadget.c b/drivers/usb/usbssp/gadget.c
index 2996b1d3baf7..6b3dc973c0d9 100644
--- a/drivers/usb/usbssp/gadget.c
+++ b/drivers/usb/usbssp/gadget.c
@@ -262,6 +262,17 @@ int usbssp_resume(struct usbssp_udc *usbssp_data, bool hibernated)
 
 #endif	/* CONFIG_PM */
 
+/* Compute the last valid endpoint context index. Basically, this is the
+ * endpoint index plus one. For slot contexts with more than valid endpoint,
+ * we find the most significant bit set in the added contexts flags.
+ * e.g. ep 1 IN (with epnum 0x81) => added_ctxs = 0b1000
+ * fls(0b1000) = 4, but the endpoint context index is 3, so subtract one.
+ */
+unsigned int usbssp_last_valid_endpoint(u32 added_ctxs)
+{
+	return fls(added_ctxs) - 1;
+}
+
 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 33eccffc885d..145371eee47d 100644
--- a/drivers/usb/usbssp/gadget.h
+++ b/drivers/usb/usbssp/gadget.h
@@ -1685,9 +1685,13 @@ 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);
+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);
+struct usbssp_ring *usbssp_stream_id_to_ring(struct usbssp_device *dev,
+					unsigned int ep_index,
+					unsigned int stream_id);
 
 struct usbssp_command *usbssp_alloc_command(struct usbssp_udc *usbssp_data,
 					bool allocate_completion,
@@ -1732,6 +1736,14 @@ void usbssp_set_link_state(struct usbssp_udc *usbssp_data,
 void usbssp_test_and_clear_bit(struct usbssp_udc *usbssp_data,
 			__le32 __iomem *port_regs, u32 port_bit);
 
+/* USBSSP DC contexts */
+struct usbssp_input_control_ctx *usbssp_get_input_control_ctx(
+					struct usbssp_container_ctx *ctx);
+struct usbssp_slot_ctx *usbssp_get_slot_ctx(struct usbssp_udc *usbssp_data,
+					struct usbssp_container_ctx *ctx);
+struct usbssp_ep_ctx *usbssp_get_ep_ctx(struct usbssp_udc *usbssp_data,
+					struct usbssp_container_ctx *ctx,
+					unsigned int ep_index);
 /* USBSSP gadget interface*/
 void usbssp_suspend_gadget(struct usbssp_udc *usbssp_data);
 void usbssp_resume_gadget(struct usbssp_udc *usbssp_data);

  parent reply	other threads:[~2018-07-19 18:02 UTC|newest]

Thread overview: 102+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-07-19 17:57 [PATCH 00/31] Introduced new Cadence USBSSP DRD Driver Pawel Laszczak
2018-07-19 17:57 ` [PATCH 01/31] usb: usbssp: Defined register maps and other useful structures Pawel Laszczak
2018-07-19 17:57   ` [01/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 02/31] usb: usbssp: Added some decoding functions Pawel Laszczak
2018-07-19 17:57   ` [02/31] " Pawel Laszczak
2018-09-10 18:18   ` [PATCH 02/31] " Greg Kroah-Hartman
2018-09-10 18:18     ` [02/31] " Greg Kroah-Hartman
2018-09-11  5:48     ` [PATCH 02/31] " Felipe Balbi
2018-09-11  5:48       ` [02/31] " Felipe Balbi
2018-09-11  8:12       ` [PATCH 02/31] " Greg Kroah-Hartman
2018-09-11  8:12         ` [02/31] " Greg Kroah-Hartman
2018-09-11  9:01         ` [PATCH 02/31] " Pawel Laszczak
2018-09-11  9:01           ` [02/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 03/31] usb: usbssp: Add trace events used in driver Pawel Laszczak
2018-07-19 17:57   ` [03/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 04/31] usb: usbssp: Added USBSSP platform driver Pawel Laszczak
2018-07-19 17:57   ` [04/31] " Pawel Laszczak
2018-08-01 12:24   ` [PATCH 04/31] " Roger Quadros
2018-08-01 12:24     ` [04/31] " Roger Quadros
2018-08-02  6:25     ` [PATCH 04/31] " Pawel Laszczak
2018-08-02  6:25       ` [04/31] " Pawel Laszczak
2018-08-02 13:28       ` [PATCH 04/31] " Roger Quadros
2018-08-02 13:28         ` [04/31] " Roger Quadros
2018-07-19 17:57 ` [PATCH 05/31] usb: usbssp: Added first part of initialization sequence Pawel Laszczak
2018-07-19 17:57   ` [05/31] " Pawel Laszczak
2018-08-03 10:17   ` [PATCH 05/31] " Roger Quadros
2018-08-03 10:17     ` [05/31] " Roger Quadros
2018-08-06  8:57     ` [PATCH 05/31] " Pawel Laszczak
2018-08-06  8:57       ` [05/31] " Pawel Laszczak
2018-08-06 10:33       ` [PATCH 05/31] " Roger Quadros
2018-08-06 10:33         ` [05/31] " Roger Quadros
2018-08-06 12:03         ` [PATCH 05/31] " Pawel Laszczak
2018-08-06 12:03           ` [05/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 06/31] usb: usbssp: added template functions used by upper layer Pawel Laszczak
2018-07-19 17:57   ` [06/31] " Pawel Laszczak
2018-08-03 10:42   ` [PATCH 06/31] " Roger Quadros
2018-08-03 10:42     ` [06/31] " Roger Quadros
2018-08-04  6:37     ` [PATCH 06/31] " Pawel Laszczak
2018-08-04  6:37       ` [06/31] " Pawel Laszczak
2018-08-06  8:57       ` [PATCH 06/31] " Roger Quadros
2018-08-06  8:57         ` [06/31] " Roger Quadros
2018-08-06 11:40         ` [PATCH 06/31] " Pawel Laszczak
2018-08-06 11:40           ` [06/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 07/31] usb: usbssp: Initialization - added usbssp_mem_init Pawel Laszczak
2018-07-19 17:57   ` [07/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 08/31] usb: usbssp: Added ring and segment handling functions Pawel Laszczak
2018-07-19 17:57   ` [08/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 09/31] usb: usbssp: add implementation of usbssp_mem_cleanup Pawel Laszczak
2018-07-19 17:57   ` [09/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 10/31] usb: usbssp: added usbssp_trb_in_td function Pawel Laszczak
2018-07-19 17:57   ` [10/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 11/31] usb: usbssp: added function for stopping driver Pawel Laszczak
2018-07-19 17:57   ` [11/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 12/31] usb: usbssp: added functions for queuing commands Pawel Laszczak
2018-07-19 17:57   ` [12/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 13/31] usb: usbssp: addec procedure for handlin Port Status Change events Pawel Laszczak
2018-07-19 17:57   ` [13/31] " Pawel Laszczak
2018-07-19 17:57 ` Pawel Laszczak [this message]
2018-07-19 17:57   ` [14/31] usb: usbssp: added procedure handling command completion events Pawel Laszczak
2018-07-19 17:57 ` [PATCH 15/31] usb: usbssp: added device controller error, transfer and SETUP completion event Pawel Laszczak
2018-07-19 17:57   ` [15/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 16/31] usb: usbssp: added connect/disconnect procedures Pawel Laszczak
2018-07-19 17:57   ` [16/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 17/31] usb: usbssp: added implementation of usbssp_halt_endpoint function Pawel Laszczak
2018-07-19 17:57   ` [17/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 18/31] usb: usbssp: added handling of Port Reset event Pawel Laszczak
2018-07-19 17:57   ` [18/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 19/31] usb: usbssp: added support for USB enumeration process Pawel Laszczak
2018-07-19 17:57   ` [19/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 20/31] usb: usbssp: added queuing procedure for control transfer Pawel Laszczak
2018-07-19 17:57   ` [20/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 21/31] usb: usbssp: added queuing procedure for BULK and INT transfer Pawel Laszczak
2018-07-19 17:57   ` [21/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 22/31] usb: usbssp: added procedure removing request from transfer ring Pawel Laszczak
2018-07-19 17:57   ` [22/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 23/31] usb: usbssp: added implementation of transfer events Pawel Laszczak
2018-07-19 17:57   ` [23/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 24/31] usb: usbssp: added detecting command timeout Pawel Laszczak
2018-07-19 17:57   ` [24/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 25/31] usb: usbssp: added implementation of usbssp interface Pawel Laszczak
2018-07-19 17:57   ` [25/31] " Pawel Laszczak
2018-07-19 17:57 ` [PATCH 26/31] usb: usbssp: added endpoint configuration functionality Pawel Laszczak
2018-07-19 17:57   ` [26/31] " Pawel Laszczak
2018-07-19 17:58 ` [PATCH 27/31] usb: usbssp: implements usbssp_gadget_ep_enable function Pawel Laszczak
2018-07-19 17:58   ` [27/31] " Pawel Laszczak
2018-07-19 17:58 ` [PATCH 28/31] usb: usbssp: implemented usbssp_gadget_ep_disable function Pawel Laszczak
2018-07-19 17:58   ` [28/31] " Pawel Laszczak
2018-07-19 17:58 ` [PATCH 29/31] usb: usbssp: added support for LPM Pawel Laszczak
2018-07-19 17:58   ` [29/31] " Pawel Laszczak
2018-07-19 17:58 ` [PATCH 30/31] usb: usbssp: added support for TEST_MODE Pawel Laszczak
2018-07-19 17:58   ` [30/31] " Pawel Laszczak
2018-07-19 17:58 ` [PATCH 31/31] usb: usbssp: add pci to platform driver wrapper Pawel Laszczak
2018-07-19 17:58   ` [31/31] " Pawel Laszczak
2018-08-01 11:27 ` [PATCH 00/31] Introduced new Cadence USBSSP DRD Driver Roger Quadros
2018-08-02  4:26   ` Pawel Laszczak
2018-08-02 13:24     ` Roger Quadros
2018-09-10 18:21     ` Greg Kroah-Hartman
2018-08-17 21:05 ` Bin Liu
2018-08-21 14:50   ` Pawel Laszczak
2018-09-10 18:20     ` Greg Kroah-Hartman
2018-09-10 18:16 ` Greg Kroah-Hartman
  -- strict thread matches above, loose matches on Subject: below --
2018-07-12  5:46 Pawel Laszczak
2018-07-12  5:47 ` [PATCH 14/31] usb: usbssp: added procedure handling command completion events Pawel Laszczak

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1532023084-28083-15-git-send-email-pawell@cadence.com \
    --to=pawell@cadence.com \
    --cc=adouglas@cadence.com \
    --cc=balbi@kernel.org \
    --cc=gregkh@linuxfoundation.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=ltyrala@cadence.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.