Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 73bb68d5 authored by Jack Pham's avatar Jack Pham Committed by Gerrit - the friendly Code Review server
Browse files

usb: xhci: Add support for SINGLE_STEP_SET_FEATURE test of EHSET



The Embedded High-speed Host Electrical Test (EHSET) procedure defines
the SINGLE_STEP_SET_FEATURE test for an embedded USB Host port. Upon
activating this test mode, the SETUP stage of a GetDescriptor request
is sent and followed by a delay of 15 seconds before finishing with
the DATA and STATUS stages. The idea is that this delay will give the
test operator sufficient time to configure the oscilloscope to perform
a measurement of the response time delay between the latter two stages.

This test is not implemented by the EHCI/xHCI host controller itself but
can be implemented in software. Similar to commit 9841f37a ("usb:
ehci: Add support for SINGLE_STEP_SET_FEATURE test of EHSET"), this patch
adds such support to the xHCI driver.

Change-Id: I638ca552f6dae735147378f3e6f6068e0003094b
Signed-off-by: default avatarJack Pham <jackp@codeaurora.org>
parent 9bd05f8c
Loading
Loading
Loading
Loading
+153 −1
Original line number Diff line number Diff line
@@ -8,7 +8,7 @@
 * Some code borrowed from the Linux EHCI driver.
 */


#include <linux/gfp.h>
#include <linux/slab.h>
#include <asm/unaligned.h>

@@ -1008,6 +1008,151 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd,
	return status;
}

static void xhci_single_step_completion(struct urb *urb)
{
	struct completion *done = urb->context;

	complete(done);
}

/*
 * Allocate a URB and initialize the various fields of it.
 * This API is used by the single_step_set_feature test of
 * EHSET where IN packet of the GetDescriptor request is
 * sent 15secs after the SETUP packet.
 * Return NULL if failed.
 */
static struct urb *xhci_request_single_step_set_feature_urb(
		struct usb_device *udev,
		void *dr,
		void *buf,
		struct completion *done)
{
	struct urb *urb;
	struct usb_hcd *hcd = bus_to_hcd(udev->bus);
	struct usb_host_endpoint *ep;

	urb = usb_alloc_urb(0, GFP_KERNEL);
	if (!urb)
		return NULL;

	urb->pipe = usb_rcvctrlpipe(udev, 0);
	ep = udev->ep_in[usb_pipeendpoint(urb->pipe)];
	if (!ep) {
		usb_free_urb(urb);
		return NULL;
	}

	/*
	 * Initialize the various URB fields as these are used by the HCD
	 * driver to queue it and as well as when completion happens.
	 */
	urb->ep = ep;
	urb->dev = udev;
	urb->setup_packet = dr;
	urb->transfer_buffer = buf;
	urb->transfer_buffer_length = USB_DT_DEVICE_SIZE;
	urb->complete = xhci_single_step_completion;
	urb->status = -EINPROGRESS;
	urb->actual_length = 0;
	urb->transfer_flags = URB_DIR_IN;
	usb_get_urb(urb);
	atomic_inc(&urb->use_count);
	atomic_inc(&urb->dev->urbnum);
	usb_hcd_map_urb_for_dma(hcd, urb, GFP_KERNEL);
	urb->context = done;
	return urb;
}

/*
 * This function implements the USB_PORT_FEAT_TEST handling of the
 * SINGLE_STEP_SET_FEATURE test mode as defined in the Embedded
 * High-Speed Electrical Test (EHSET) specification. This simply
 * issues a GetDescriptor control transfer, with an inserted 15-second
 * delay after the end of the SETUP stage and before the IN token of
 * the DATA stage is set. The idea is that this gives the test operator
 * enough time to configure the oscilloscope to perform a measurement
 * of the response time between the DATA and ACK packets that follow.
 */
static int xhci_ehset_single_step_set_feature(struct usb_hcd *hcd, int port)
{
	int retval;
	struct usb_ctrlrequest *dr;
	struct urb *urb;
	struct usb_device *udev;
	struct xhci_hcd	*xhci = hcd_to_xhci(hcd);
	struct usb_device_descriptor *buf;
	unsigned long flags;
	DECLARE_COMPLETION_ONSTACK(done);

	/* Obtain udev of the rhub's child port */
	udev = usb_hub_find_child(hcd->self.root_hub, port);
	if (!udev) {
		xhci_err(xhci, "No device attached to the RootHub\n");
		return -ENODEV;
	}
	buf = kmalloc(USB_DT_DEVICE_SIZE, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
	if (!dr) {
		kfree(buf);
		return -ENOMEM;
	}

	/* Fill Setup packet for GetDescriptor */
	dr->bRequestType = USB_DIR_IN;
	dr->bRequest = USB_REQ_GET_DESCRIPTOR;
	dr->wValue = cpu_to_le16(USB_DT_DEVICE << 8);
	dr->wIndex = 0;
	dr->wLength = cpu_to_le16(USB_DT_DEVICE_SIZE);
	urb = xhci_request_single_step_set_feature_urb(udev, dr, buf, &done);
	if (!urb) {
		retval = -ENOMEM;
		goto cleanup;
	}

	/* Now complete just the SETUP stage */
	spin_lock_irqsave(&xhci->lock, flags);
	retval = xhci_submit_single_step_set_feature(hcd, urb, 1);
	spin_unlock_irqrestore(&xhci->lock, flags);
	if (retval)
		goto out1;

	if (!wait_for_completion_timeout(&done, msecs_to_jiffies(2000))) {
		usb_kill_urb(urb);
		retval = -ETIMEDOUT;
		xhci_err(xhci, "%s SETUP stage timed out on ep0\n", __func__);
		goto out1;
	}

	/* Sleep for 15 seconds; HC will send SOFs during this period */
	msleep(15 * 1000);

	/* Complete remaining DATA and status stages. Re-use same URB */
	urb->status = -EINPROGRESS;
	usb_get_urb(urb);
	atomic_inc(&urb->use_count);
	atomic_inc(&urb->dev->urbnum);

	spin_lock_irqsave(&xhci->lock, flags);
	retval = xhci_submit_single_step_set_feature(hcd, urb, 0);
	spin_unlock_irqrestore(&xhci->lock, flags);
	if (!retval && !wait_for_completion_timeout(&done,
						msecs_to_jiffies(2000))) {
		usb_kill_urb(urb);
		retval = -ETIMEDOUT;
		xhci_err(xhci, "%s IN stage timed out on ep0\n", __func__);
	}
out1:
	usb_free_urb(urb);
cleanup:
	kfree(dr);
	kfree(buf);
	return retval;
}

int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
		u16 wIndex, char *buf, u16 wLength)
{
@@ -1302,6 +1447,13 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
			/* 4.19.6 Port Test Modes (USB2 Test Mode) */
			if (hcd->speed != HCD_USB2)
				goto error;
			if (test_mode == 6) { /* TEST_SINGLE_STEP_SET_FEATURE */
				spin_unlock_irqrestore(&xhci->lock, flags);
				retval = xhci_ehset_single_step_set_feature(hcd,
									wIndex);
				spin_lock_irqsave(&xhci->lock, flags);
				break;
			}
			if (test_mode > TEST_FORCE_EN || test_mode < TEST_J)
				goto error;
			retval = xhci_enter_test_mode(xhci, test_mode, wIndex,
+144 −0
Original line number Diff line number Diff line
@@ -3387,6 +3387,150 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
	return 0;
}

/*
 * Variant of xhci_queue_ctrl_tx() used to implement EHSET
 * SINGLE_STEP_SET_FEATURE test mode. It differs in that the control
 * transfer is broken up so that the SETUP stage can happen and call
 * the URB's completion handler before the DATA/STATUS stages are
 * executed by the xHC hardware. This assumes the control transfer is a
 * GetDescriptor, with a DATA stage in the IN direction, and an OUT
 * STATUS stage.
 *
 * This function is called twice, usually with a 15-second delay in between.
 * - with is_setup==true, the SETUP stage for the control request
 *   (GetDescriptor) is queued in the TRB ring and sent to HW immediately
 * - with is_setup==false, the DATA and STATUS TRBs are queued and exceuted
 *
 * Caller must have locked xhci->lock
 */
int xhci_submit_single_step_set_feature(struct usb_hcd *hcd, struct urb *urb,
					int is_setup)
{
	struct xhci_hcd *xhci = hcd_to_xhci(hcd);
	struct xhci_ring *ep_ring;
	int num_trbs;
	int ret;
	unsigned int slot_id, ep_index;
	struct usb_ctrlrequest *setup;
	struct xhci_generic_trb *start_trb;
	int start_cycle;
	u32 field, length_field, remainder;
	struct urb_priv *urb_priv;
	struct xhci_td *td;

	ep_ring = xhci_urb_to_transfer_ring(xhci, urb);
	if (!ep_ring)
		return -EINVAL;

	/* Need buffer for data stage */
	if (urb->transfer_buffer_length <= 0)
		return -EINVAL;

	/*
	 * Need to copy setup packet into setup TRB, so we can't use the setup
	 * DMA address.
	 */
	if (!urb->setup_packet)
		return -EINVAL;
	setup = (struct usb_ctrlrequest *) urb->setup_packet;

	slot_id = urb->dev->slot_id;
	ep_index = xhci_get_endpoint_index(&urb->ep->desc);

	urb_priv = kzalloc(sizeof(struct urb_priv) +
				  sizeof(struct xhci_td *), GFP_ATOMIC);
	if (!urb_priv)
		return -ENOMEM;

	td = &urb_priv->td[0];
	urb_priv->num_tds = 1;
	urb_priv->num_tds_done = 0;
	urb->hcpriv = urb_priv;

	num_trbs = is_setup ? 1 : 2;

	ret = prepare_transfer(xhci, xhci->devs[slot_id],
			ep_index, urb->stream_id,
			num_trbs, urb, 0, GFP_ATOMIC);
	if (ret < 0) {
		kfree(urb_priv);
		return ret;
	}

	/*
	 * Don't give the first TRB to the hardware (by toggling the cycle bit)
	 * until we've finished creating all the other TRBs.  The ring's cycle
	 * state may change as we enqueue the other TRBs, so save it too.
	 */
	start_trb = &ep_ring->enqueue->generic;
	start_cycle = ep_ring->cycle_state;

	if (is_setup) {
		/* Queue only the setup TRB */
		field = TRB_IDT | TRB_IOC | TRB_TYPE(TRB_SETUP);
		if (start_cycle == 0)
			field |= 0x1;

		/* xHCI 1.0/1.1 6.4.1.2.1: Transfer Type field */
		if (xhci->hci_version >= 0x100) {
			if (setup->bRequestType & USB_DIR_IN)
				field |= TRB_TX_TYPE(TRB_DATA_IN);
			else
				field |= TRB_TX_TYPE(TRB_DATA_OUT);
		}

		/* Save the DMA address of the last TRB in the TD */
		td->last_trb = ep_ring->enqueue;

		queue_trb(xhci, ep_ring, false,
			  setup->bRequestType | setup->bRequest << 8 |
				le16_to_cpu(setup->wValue) << 16,
			  le16_to_cpu(setup->wIndex) |
				le16_to_cpu(setup->wLength) << 16,
			  TRB_LEN(8) | TRB_INTR_TARGET(0),
			  field);
	} else {
		/* Queue data TRB */
		field = TRB_ISP | TRB_TYPE(TRB_DATA);
		if (start_cycle == 0)
			field |= 0x1;
		if (setup->bRequestType & USB_DIR_IN)
			field |= TRB_DIR_IN;

		remainder = xhci_td_remainder(xhci, 0,
					   urb->transfer_buffer_length,
					   urb->transfer_buffer_length,
					   urb, 1);

		length_field = TRB_LEN(urb->transfer_buffer_length) |
			TRB_TD_SIZE(remainder) |
			TRB_INTR_TARGET(0);

		queue_trb(xhci, ep_ring, true,
			  lower_32_bits(urb->transfer_dma),
			  upper_32_bits(urb->transfer_dma),
			  length_field,
			  field);

		/* Save the DMA address of the last TRB in the TD */
		td->last_trb = ep_ring->enqueue;

		/* Queue status TRB */
		field = TRB_IOC | TRB_TYPE(TRB_STATUS);
		if (!(setup->bRequestType & USB_DIR_IN))
			field |= TRB_DIR_IN;

		queue_trb(xhci, ep_ring, false,
			  0,
			  0,
			  TRB_INTR_TARGET(0),
			  field | ep_ring->cycle_state);
	}

	giveback_first_trb(xhci, slot_id, ep_index, 0, start_cycle, start_trb);
	return 0;
}

/*
 * The transfer burst count field of the isochronous TRB defines the number of
 * bursts that are required to move all packets in this TD.  Only SuperSpeed
+4 −0
Original line number Diff line number Diff line
@@ -2607,4 +2607,8 @@ static inline const char *xhci_decode_ep_context(u32 info, u32 info2, u64 deq,
	return str;
}

/* EHSET */
int xhci_submit_single_step_set_feature(struct usb_hcd *hcd, struct urb *urb,
					int is_setup);

#endif /* __LINUX_XHCI_HCD_H */