About a year ago a new test method was developed for running the USB Hi-Speed Host Electrical tests during compliance testing. The new method was designed for embedded devices with OTG or Host ports. Prior to this the test method required that Windows test software be run on the test target. The new method allows the firmware in the test target to set it up for different test cases based on the Vendor and Product ID of the attached test device. Attached is a patch that shows how I think this support could be added to the USB core directory and the EHCI driver. The changes to the USB core would check newly attached devices to see if they have the Vendor ID set aside for this test method. If so and it's running in Hi-Speed mode then the HCD entry point for sending Root Hub device requests is called to cause the HC Driver to run a test based on the Product ID. This would provide a frame work for adding support for this test method to other Hi-Speed Host Controller Drivers. The patch compiles cleanly but has not yet been tested at all. Before I put more work into it I'd like to know if anyone else is interested in it. Also if anyone has any comments about how I coded it please let me know.
Best Regards, Craig Nadler
diff -Nru a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c --- a/drivers/usb/core/hub.c 2006-03-18 22:34:51.000000000 -0500 +++ b/drivers/usb/core/hub.c 2006-03-20 00:55:50.000000000 -0500 @@ -76,6 +76,15 @@ "first one fails"); +/* Semaphore used for Hi-Speed Host Electrical tests */ +static DECLARE_MUTEX(ehset_sem); +static struct workqueue_struct *EHSET_workqueue = NULL; +static u8 EHSET_in_progress = 0; + +static int hub_suspend(struct usb_interface *, pm_message_t); +static int hub_resume(struct usb_interface *); + + #ifdef DEBUG static inline char *portspeed (int portstatus) { @@ -1264,6 +1273,127 @@ #endif +static void EHSET_tests(void *data) +{ + struct usb_device *udev = (struct usb_device *)data; + struct usb_hcd *hcd = udev->bus->hcpriv; + struct usb_device *root = udev->parent; + struct usb_hub *hub = hdev_to_hub(root); + struct usb_interface *intf = to_usb_interface(hub->intfdev); + int status; + u8 port = udev->portnum; + + dev_info(&udev->dev, "running EHSET Test %x\n", + udev->descriptor.idProduct); + + switch (udev->descriptor.idProduct) { + case EHSET_TEST_SE0_NAK: + if (hub_suspend(intf, PMSG_ON)) + dev_err(&udev->dev, "hub_suspend: Failed"); + status = hcd->driver->hub_control(hcd, SetPortFeature, + USB_PORT_FEAT_TEST, + (USB_PORT_TEST_SE0_NAK<<8)|port, + NULL, 0); + if (status) + dev_err(&udev->dev, "SetPortFeature Failed\n"); + /* This test runs until the host is powered off */ + break; + case EHSET_TEST_J: + if (hub_suspend(intf, PMSG_ON)) + dev_err(&udev->dev, "hub_suspend: Failed"); + status = hcd->driver->hub_control(hcd, SetPortFeature, + USB_PORT_FEAT_TEST, + (USB_PORT_TEST_J<<8)|port, + NULL, 0); + if (status) + dev_err(&udev->dev, "SetPortFeature Failed\n"); + /* This test runs until the host is powered off */ + break; + case EHSET_TEST_K: + if (hub_suspend(intf, PMSG_ON)) + dev_err(&udev->dev, "hub_suspend: Failed"); + status = hcd->driver->hub_control(hcd, SetPortFeature, + USB_PORT_FEAT_TEST, + (USB_PORT_TEST_K<<8)|port, + NULL, 0); + if (status) + dev_err(&udev->dev, "SetPortFeature Failed\n"); + /* This test runs until the host is powered off */ + break; + case EHSET_TEST_PACKET: + status = hcd->driver->hub_control(hcd, SetPortFeature, + USB_PORT_FEAT_TEST, + (USB_PORT_TEST_PACKET<<8)|port, + NULL, 0); + if (status) + dev_err(&udev->dev, "SetPortFeature Failed\n"); + down(&ehset_sem); + EHSET_in_progress = 0; + up(&ehset_sem); + break; + /* Note the FORCE ENABLE test is no longer used in the EHSET spec. */ + case EHSET_TEST_FORCE_ENABLE: + status = hcd->driver->hub_control(hcd, SetPortFeature, + USB_PORT_FEAT_TEST, + (USB_PORT_TEST_FORCE_ENABLE<<8)|port, + NULL, 0); + if (status) + dev_err(&udev->dev, "SetPortFeature Failed\n"); + down(&ehset_sem); + EHSET_in_progress = 0; + up(&ehset_sem); + break; + case EHSET_HS_HOST_PORT_SUSPEND_RESUME: + /* Sleep for 15 seconds */ + schedule_timeout(msecs_to_jiffies(15000)); + + if (hub_suspend(intf, PMSG_ON)) + dev_err(&udev->dev, "hub_suspend: Failed"); + + /* Sleep for 15 seconds */ + schedule_timeout(msecs_to_jiffies(15000)); + + if (hub_resume(intf)) + dev_err(&udev->dev, "hub_suspend: Failed"); + + down(&ehset_sem); + EHSET_in_progress = 0; + up(&ehset_sem); + break; + case EHSET_SINGLE_STEP_GET_DEV_DESC: + schedule_timeout(msecs_to_jiffies(15000)); + + status = hcd->driver->hub_control(hcd, SetPortFeature, + USB_PORT_FEAT_TEST, + ((EHSET_SINGLE_STEP_GET_DEV_DESC&0xFF)<<8)|port, + (char *)udev, 0); + if (status) + dev_err(&udev->dev, "SetPortFeature Failed\n"); + down(&ehset_sem); + EHSET_in_progress = 0; + up(&ehset_sem); + break; + case EHSET_SINGLE_STEP_SET_FEATURE: + status = hcd->driver->hub_control(hcd, SetPortFeature, + USB_PORT_FEAT_TEST, + ((EHSET_SINGLE_STEP_SET_FEATURE&0xFF)<<8)|port, + (char *)udev, 0); + if (status) + dev_err(&udev->dev, "SetPortFeature Failed\n"); + down(&ehset_sem); + EHSET_in_progress = 0; + up(&ehset_sem); + break; + default: + dev_err(&udev->dev, "EHSET: Unsupported test mode %x\n", + udev->descriptor.idProduct); + down(&ehset_sem); + EHSET_in_progress = 0; + up(&ehset_sem); + } +} + + #ifdef CONFIG_USB_OTG #include "otg_whitelist.h" #endif @@ -1315,6 +1445,37 @@ show_string(udev, "Manufacturer", udev->manufacturer); show_string(udev, "SerialNumber", udev->serial); + if (udev->speed == USB_SPEED_HIGH && + udev->parent == udev->bus->root_hub && + udev->descriptor.idVendor == 0x1A0A) + { + static struct work_struct EHSET_work; + + if (!EHSET_workqueue) { + EHSET_workqueue = create_singlethread_workqueue( + "USB EHSET"); + if (!EHSET_workqueue) { + dev_err(&udev->dev, "Failed to create a " + "workqueue for USB EHSET\n"); + } + } + + if (EHSET_workqueue) { + if (down_trylock(&ehset_sem) == 0) { + if (!EHSET_in_progress) { + EHSET_in_progress = 1; + up(&ehset_sem); + INIT_WORK(&EHSET_work, EHSET_tests, + udev); + queue_work(EHSET_workqueue, + &EHSET_work); + } else { + up(&ehset_sem); + } + } + } + } + #ifdef CONFIG_USB_OTG /* * OTG-aware devices on OTG-capable root hubs may be able to use SRP, @@ -2928,6 +3089,10 @@ * individual hub resources. -greg */ usb_deregister(&hub_driver); + + if (EHSET_workqueue) + destroy_workqueue(EHSET_workqueue); + } /* usb_hub_cleanup() */ static int config_descriptors_changed(struct usb_device *udev) Files a/drivers/usb/core/.hub.c.swp and b/drivers/usb/core/.hub.c.swp differ diff -Nru a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h --- a/drivers/usb/core/hub.h 2006-03-18 22:34:51.000000000 -0500 +++ b/drivers/usb/core/hub.h 2006-03-18 23:19:06.000000000 -0500 @@ -55,6 +55,35 @@ #define USB_PORT_FEAT_TEST 21 #define USB_PORT_FEAT_INDICATOR 22 + +/* + * Hub Port Test Mode Selector Codes + * See USB 2.0 spec Table 11-24 + */ + +#define USB_PORT_TEST_J 0x01 +#define USB_PORT_TEST_K 0x02 +#define USB_PORT_TEST_SE0_NAK 0x03 +#define USB_PORT_TEST_PACKET 0x04 +#define USB_PORT_TEST_FORCE_ENABLE 0x05 + + +/* + * Product IDs used to trigger USB Hi-Speed Host Electrical Tests + * on the root hub. See USB 2.0 spec 7.1.20 and the + * Embedded High-speed Host Electrical Test Procedure. + */ +#define EHSET_TEST_SE0_NAK 0x0101 +#define EHSET_TEST_J 0x0102 +#define EHSET_TEST_K 0x0103 +#define EHSET_TEST_PACKET 0x0104 +/* Note that the FORCE ENABLE test is no longer used in the EHSET spec. */ +#define EHSET_TEST_FORCE_ENABLE 0x0105 +#define EHSET_HS_HOST_PORT_SUSPEND_RESUME 0x0106 +#define EHSET_SINGLE_STEP_GET_DEV_DESC 0x0107 +#define EHSET_SINGLE_STEP_SET_FEATURE 0x0108 + + /* * Hub Status and Hub Change results * See USB 2.0 spec Table 11-19 and Table 11-20 diff -Nru a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h --- a/drivers/usb/host/ehci.h 2006-01-02 22:21:10.000000000 -0500 +++ b/drivers/usb/host/ehci.h 2006-03-18 22:59:26.000000000 -0500 @@ -245,7 +245,11 @@ #define PORT_WKOC_E (1<<22) /* wake on overcurrent (enable) */ #define PORT_WKDISC_E (1<<21) /* wake on disconnect (enable) */ #define PORT_WKCONN_E (1<<20) /* wake on connect (enable) */ -/* 19:16 for port testing */ +#define PORT_TEST_J (1<<16) +#define PORT_TEST_K (2<<16) +#define PORT_TEST_SE0_NAK (3<<16) +#define PORT_TEST_PACKET (4<<16) +#define PORT_TEST_FORCE_ENABLE (5<<16) #define PORT_LED_OFF (0<<14) #define PORT_LED_AMBER (1<<14) #define PORT_LED_GREEN (2<<14) diff -Nru a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c --- a/drivers/usb/host/ehci-hub.c 2006-03-18 22:34:51.000000000 -0500 +++ b/drivers/usb/host/ehci-hub.c 2006-03-20 00:53:18.000000000 -0500 @@ -304,6 +304,23 @@ /*-------------------------------------------------------------------------*/ +static struct list_head * qh_urb_transaction ( + struct ehci_hcd *ehci, + struct urb *urb, + struct list_head *head, + gfp_t flags); + +static int submit_async ( + struct ehci_hcd *ehci, + struct usb_host_endpoint *ep, + struct urb *urb, + struct list_head *qtd_list, + gfp_t mem_flags); + +static inline void ehci_qtd_free ( + struct ehci_hcd *ehci, + struct ehci_qtd *qtd); + #define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) static int ehci_hub_control ( @@ -555,6 +572,139 @@ } writel (temp, &ehci->regs->port_status [wIndex]); break; + case USB_PORT_FEAT_TEST: + switch ((wIndex>>8)&0xFF) { + case USB_PORT_TEST_J: + writel (temp|PORT_TEST_J, + &ehci->regs->port_status + [wIndex&0xFF]); + break; + case USB_PORT_TEST_K: + writel (temp|PORT_TEST_K, + &ehci->regs->port_status + [wIndex&0xFF]); + break; + case USB_PORT_TEST_SE0_NAK: + writel (temp|PORT_TEST_SE0_NAK, + &ehci->regs->port_status + [wIndex&0xFF]); + break; + case USB_PORT_TEST_PACKET: + writel (temp|PORT_TEST_PACKET, + &ehci->regs->port_status + [wIndex&0xFF]); + break; + case USB_PORT_TEST_FORCE_ENABLE: + writel (temp|PORT_TEST_FORCE_ENABLE, + &ehci->regs->port_status + [wIndex&0xFF]); + break; + /* case EHSET_HS_HOST_PORT_SUSPEND_RESUME is handled + * in the USB core not in the HCD driver. + */ + case EHSET_SINGLE_STEP_GET_DEV_DESC&0xFF: + /* The USB core slept for 15 seconds before + * calling us so we don't have to. + */ + case EHSET_SINGLE_STEP_SET_FEATURE&0xFF: + { + struct list_head qtd_list; + struct list_head setup_list; + struct list_head data_list; + struct usb_device *dev; + struct ehci_qtd *qtd; + struct urb urb; + struct usb_ctrlrequest setup_packet; + char data_buffer[USB_DT_DEVICE_SIZE]; + + if (!buf) { + ehci_err (ehci, "No usb_device pointer" + " found\n"); + goto error; + } + dev = (struct usb_device *)buf; + setup_packet.bRequestType = + USB_REQ_GET_DESCRIPTOR; + setup_packet.bRequest = USB_DIR_IN; + setup_packet.wValue = (USB_DT_DEVICE << 8); + setup_packet.wIndex = 0; + setup_packet.wLength = USB_DT_DEVICE_SIZE; + + INIT_LIST_HEAD (&qtd_list); + INIT_LIST_HEAD (&setup_list); + INIT_LIST_HEAD (&data_list); + urb.transfer_buffer_length = + USB_DT_DEVICE_SIZE; + urb.dev = dev; + urb.pipe = usb_rcvctrlpipe(dev, 0); + urb.setup_packet = (char *)&setup_packet; + urb.transfer_buffer = data_buffer; + urb.transfer_flags = URB_HCD_DRIVER_TEST; + urb.setup_dma = dma_map_single ( + hcd->self.controller, + urb.setup_packet, + sizeof (struct usb_ctrlrequest), + DMA_TO_DEVICE); + urb.transfer_dma = dma_map_single ( + hcd->self.controller, + urb.transfer_buffer, + sizeof (struct usb_ctrlrequest), + DMA_TO_DEVICE); + if (!urb.setup_dma || !urb.transfer_dma) { + ehci_err (ehci, "dma_map_single Failed" + "\n"); + goto error; + } + + if (!qh_urb_transaction (ehci, &urb, &qtd_list, + GFP_ATOMIC)) + { + ehci_err (ehci, "qh_urb_transaction " + "Failed\n"); + goto error; + } + + qtd = container_of (qtd_list.next, + struct ehci_qtd, qtd_list); + list_del_init (&qtd->qtd_list); + list_add (&qtd->qtd_list, &setup_list); + qtd = container_of (qtd_list.next, + struct ehci_qtd, qtd_list); + list_del_init (&qtd->qtd_list); + list_add (&qtd->qtd_list, &data_list); + if (submit_async (ehci, &dev->ep0, NULL, + &setup_list, GFP_ATOMIC)) + { + ehci_err (ehci, "Failed to queue up " + "qtds\n"); + } + + if ((EHSET_SINGLE_STEP_SET_FEATURE&0xFF) == + ((wIndex>>8)&0xFF)) + { + spin_unlock_irqrestore (&ehci->lock, + flags); + schedule_timeout(msecs_to_jiffies( + 15000)); + spin_lock_irqsave (&ehci->lock, flags); + } + + if (submit_async (ehci, &dev->ep0, NULL, + &data_list, GFP_ATOMIC)) + { + ehci_err (ehci, "Failed to queue up " + "qtds\n"); + } + qtd = container_of (qtd_list.next, + struct ehci_qtd, qtd_list); + list_del_init (&qtd->qtd_list); + ehci_qtd_free (ehci, qtd); + break; + } + default: + goto error; + } + break; default: goto error; } diff -Nru a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c --- a/drivers/usb/host/ehci-q.c 2006-03-18 22:34:51.000000000 -0500 +++ b/drivers/usb/host/ehci-q.c 2006-03-20 00:52:16.000000000 -0500 @@ -218,6 +218,9 @@ __releases(ehci->lock) __acquires(ehci->lock) { + if (likely (urb->transfer_flags == URB_HCD_DRIVER_TEST)) + return; + if (likely (urb->hcpriv != NULL)) { struct ehci_qh *qh = (struct ehci_qh *) urb->hcpriv; diff -Nru a/include/linux/usb.h b/include/linux/usb.h --- a/include/linux/usb.h 2006-03-18 22:34:53.000000000 -0500 +++ b/include/linux/usb.h 2006-03-20 00:50:26.000000000 -0500 @@ -655,6 +655,7 @@ #define URB_ZERO_PACKET 0x0040 /* Finish bulk OUT with short packet */ #define URB_NO_INTERRUPT 0x0080 /* HINT: no non-error interrupt * needed */ +#define URB_HCD_DRIVER_TEST 0xFFFF /* Do NOT hand back or free this URB. */ struct usb_iso_packet_descriptor { unsigned int offset;