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

Commit 9ffca948 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "usb: xhci: Add support for SINGLE_STEP_SET_FEATURE test of EHSET"

parents 4766387d 6c6008d2
Loading
Loading
Loading
Loading
+173 −3
Original line number Original line Diff line number Diff line
@@ -20,7 +20,7 @@
 * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
 */



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


@@ -702,6 +702,149 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd,
	return status;
	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)
		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,
int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
		u16 wIndex, char *buf, u16 wLength)
		u16 wIndex, char *buf, u16 wLength)
{
{
@@ -716,6 +859,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
	u16 link_state = 0;
	u16 link_state = 0;
	u16 wake_mask = 0;
	u16 wake_mask = 0;
	u16 timeout = 0;
	u16 timeout = 0;
	u16 test_mode = 0;


	max_ports = xhci_get_ports(hcd, &port_array);
	max_ports = xhci_get_ports(hcd, &port_array);
	bus_state = &xhci->bus_state[hcd_index(hcd)];
	bus_state = &xhci->bus_state[hcd_index(hcd)];
@@ -789,8 +933,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
			link_state = (wIndex & 0xff00) >> 3;
			link_state = (wIndex & 0xff00) >> 3;
		if (wValue == USB_PORT_FEAT_REMOTE_WAKE_MASK)
		if (wValue == USB_PORT_FEAT_REMOTE_WAKE_MASK)
			wake_mask = wIndex & 0xff00;
			wake_mask = wIndex & 0xff00;
		/* The MSB of wIndex is the U1/U2 timeout */
		/* The MSB of wIndex is the U1/U2 timeout OR TEST mode*/
		timeout = (wIndex & 0xff00) >> 8;
		test_mode = timeout = (wIndex & 0xff00) >> 8;
		wIndex &= 0xff;
		wIndex &= 0xff;
		if (!wIndex || wIndex > max_ports)
		if (!wIndex || wIndex > max_ports)
			goto error;
			goto error;
@@ -964,6 +1108,32 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
			temp |= PORT_U2_TIMEOUT(timeout);
			temp |= PORT_U2_TIMEOUT(timeout);
			writel(temp, port_array[wIndex] + PORTPMSC);
			writel(temp, port_array[wIndex] + PORTPMSC);
			break;
			break;
		case USB_PORT_FEAT_TEST:
			slot_id = xhci_find_slot_id_by_port(hcd, xhci,
							wIndex + 1);
			if (test_mode && test_mode <= 5) {
				/* unlock to execute stop endpoint commands */
				spin_unlock_irqrestore(&xhci->lock, flags);
				xhci_stop_device(xhci, slot_id, 1);
				spin_lock_irqsave(&xhci->lock, flags);
				xhci_halt(xhci);

				temp = readl_relaxed(port_array[wIndex] +
								PORTPMSC);
				temp |= test_mode << 28;
				writel_relaxed(temp, port_array[wIndex] +
								PORTPMSC);
				/* to make sure above write goes through */
				mb();
			} else if (test_mode == 6) {
				spin_unlock_irqrestore(&xhci->lock, flags);
				retval = xhci_ehset_single_step_set_feature(hcd,
									wIndex);
				spin_lock_irqsave(&xhci->lock, flags);
			} else {
				goto error;
			}
			break;
		default:
		default:
			goto error;
			goto error;
		}
		}
+144 −0
Original line number Original line Diff line number Diff line
@@ -3451,6 +3451,150 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
	return 0;
	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;
	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] = kzalloc(sizeof(struct xhci_td), GFP_ATOMIC);
	if (!td) {
		kfree(urb_priv);
		return -ENOMEM;
	}

	urb_priv->length = 1;
	urb_priv->td_cnt = 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(td);
		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 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;

		length_field = TRB_LEN(urb->transfer_buffer_length) |
			xhci_td_remainder(urb->transfer_buffer_length) |
			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;
}

static int count_isoc_trbs_needed(struct xhci_hcd *xhci,
static int count_isoc_trbs_needed(struct xhci_hcd *xhci,
		struct urb *urb, int i)
		struct urb *urb, int i)
{
{
+4 −0
Original line number Original line Diff line number Diff line
@@ -1873,4 +1873,8 @@ struct xhci_input_control_ctx *xhci_get_input_control_ctx(struct xhci_hcd *xhci,
struct xhci_slot_ctx *xhci_get_slot_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx);
struct xhci_slot_ctx *xhci_get_slot_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx);
struct xhci_ep_ctx *xhci_get_ep_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx, unsigned int ep_index);
struct xhci_ep_ctx *xhci_get_ep_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx, unsigned int ep_index);


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

#endif /* __LINUX_XHCI_HCD_H */
#endif /* __LINUX_XHCI_HCD_H */