USB: Add LVS Test device driver

OTG3 and EH Compliance Plan 1.0 talks about Super Speed OTG Verification
system (SS-OVS) which consists of an excersizer and analyzer.

USB Compliance Suite from Lecroy or Ellisys can act as such SS-OVS for
Link Layer Validation (LVS).

Some modifications are needed for an embedded Linux USB host to pass all
these tests.  Most of these tests require just Link to be in U0. They do
not work with default Linux USB stack since, default stack does port
reset and then starts sending setup packet, which is not expected by
Link Layer Validation (LVS) device of Lecroy Compliance Suit.  Then,
There are many Link Layer Tests which need host to generate specific
traffic.

This patch supports specific traffic generation cases. As of now all the
host Lecroy Link Layer-USBIF tests (except TD7.26) passes
with this patch for single run using  Lecroy USB Compliance Suite
Version 1.98 Build 239 and Lecroy USB Protocol Analyzer version 4.80
Build 1603. Therefore patch seems to be a good candidate for inclusion.
Further modification can be done on top of it.

lvstest driver will not bind to any device by default. It can bind
manually to a super speed USB host controller root hub. Therefore, regular
hub driver must be unbound before this driver is bound. For example, if
2-0:1.0 is the xhci root hub, then execute following to unbind hub driver.

 echo 2-0:1.0 > /sys/bus/usb/drivers/hub/unbind

Then write Linux Foundation's vendor ID which is used by root hubs and
SS root hub's device ID into new_id file. Writing IDs into new_id file
will also bind the lvs driver with any available SS root hub interfaces.

 echo "1D6B 3" > /sys/bus/usb/drivers/lvs/new_id

Now connect LVS device with root hub port.

Test case specific traffic can be generated as follows whenever needed:

1. To issue "Get Device descriptor" command for TD.7.06:
 echo  > /sys/bus/usb/devices/2-0\:1.0/get_dev_desc

2. To set U1 timeout to 127 for TD.7.18
 echo 127 > /sys/bus/usb/devices/2-0\:1.0/u1_timeout

3. To set U2 timeout to 0 for TD.7.18
 echo 0 > /sys/bus/usb/devices/2-0\:1.0/u2_timeout

4. To issue "Hot Reset" for TD.7.29
 echo  > /sys/bus/usb/devices/2-0\:1.0/hot_reset

5. To issue "U3 Entry" for TD.7.35
 echo  > /sys/bus/usb/devices/2-0\:1.0/u3_entry

6. To issue "U3 Exit" for TD.7.36
 echo  > /sys/bus/usb/devices/2-0\:1.0/u3_exit

Signed-off-by: Pratyush Anand <pratyush.anand@st.com>
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Pratyush Anand 2014-07-14 19:27:49 +05:30 committed by Greg Kroah-Hartman
parent caa67a5ec8
commit ce21bfe603
4 changed files with 515 additions and 0 deletions

View File

@ -0,0 +1,47 @@
Link Layer Validation Device is a standard device for testing of Super
Speed Link Layer tests. These nodes are available in sysfs only when lvs
driver is bound with root hub device.
What: /sys/bus/usb/devices/.../get_dev_desc
Date: March 2014
Contact: Pratyush Anand <pratyush.anand@st.com>
Description:
Write to this node to issue "Get Device Descriptor"
for Link Layer Validation device. It is needed for TD.7.06.
What: /sys/bus/usb/devices/.../u1_timeout
Date: March 2014
Contact: Pratyush Anand <pratyush.anand@st.com>
Description:
Set "U1 timeout" for the downstream port where Link Layer
Validation device is connected. Timeout value must be between 0
and 127. It is needed for TD.7.18, TD.7.19, TD.7.20 and TD.7.21.
What: /sys/bus/usb/devices/.../u2_timeout
Date: March 2014
Contact: Pratyush Anand <pratyush.anand@st.com>
Description:
Set "U2 timeout" for the downstream port where Link Layer
Validation device is connected. Timeout value must be between 0
and 127. It is needed for TD.7.18, TD.7.19, TD.7.20 and TD.7.21.
What: /sys/bus/usb/devices/.../hot_reset
Date: March 2014
Contact: Pratyush Anand <pratyush.anand@st.com>
Description:
Write to this node to issue "Reset" for Link Layer Validation
device. It is needed for TD.7.29, TD.7.31, TD.7.34 and TD.7.35.
What: /sys/bus/usb/devices/.../u3_entry
Date: March 2014
Contact: Pratyush Anand <pratyush.anand@st.com>
Description:
Write to this node to issue "U3 entry" for Link Layer
Validation device. It is needed for TD.7.35 and TD.7.36.
What: /sys/bus/usb/devices/.../u3_exit
Date: March 2014
Contact: Pratyush Anand <pratyush.anand@st.com>
Description:
Write to this node to issue "U3 exit" for Link Layer
Validation device. It is needed for TD.7.36.

View File

@ -248,3 +248,10 @@ config USB_HSIC_USB3503
select REGMAP_I2C
help
This option enables support for SMSC USB3503 HSIC to USB 2.0 Driver.
config USB_LINK_LAYER_TEST
tristate "USB Link Layer Test driver"
help
This driver is for generating specific traffic for Super Speed Link
Layer Test Device. Say Y only when you want to conduct USB Super Speed
Link Layer Test for host controllers.

View File

@ -27,3 +27,4 @@ obj-$(CONFIG_USB_YUREX) += yurex.o
obj-$(CONFIG_USB_HSIC_USB3503) += usb3503.o
obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/
obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o

460
drivers/usb/misc/lvstest.c Normal file
View File

@ -0,0 +1,460 @@
/*
* drivers/usb/misc/lvstest.c
*
* Test pattern generation for Link Layer Validation System Tests
*
* Copyright (C) 2014 ST Microelectronics
* Pratyush Anand <pratyush.anand@st.com>
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/usb/ch11.h>
#include <linux/usb/hcd.h>
#include <linux/usb/phy.h>
struct lvs_rh {
/* root hub interface */
struct usb_interface *intf;
/* if lvs device connected */
bool present;
/* port no at which lvs device is present */
int portnum;
/* urb buffer */
u8 buffer[8];
/* class descriptor */
struct usb_hub_descriptor descriptor;
/* urb for polling interrupt pipe */
struct urb *urb;
/* LVS RH work queue */
struct workqueue_struct *rh_queue;
/* LVH RH work */
struct work_struct rh_work;
/* RH port status */
struct usb_port_status port_status;
};
static struct usb_device *create_lvs_device(struct usb_interface *intf)
{
struct usb_device *udev, *hdev;
struct usb_hcd *hcd;
struct lvs_rh *lvs = usb_get_intfdata(intf);
if (!lvs->present) {
dev_err(&intf->dev, "No LVS device is present\n");
return NULL;
}
hdev = interface_to_usbdev(intf);
hcd = bus_to_hcd(hdev->bus);
udev = usb_alloc_dev(hdev, hdev->bus, lvs->portnum);
if (!udev) {
dev_err(&intf->dev, "Could not allocate lvs udev\n");
return NULL;
}
udev->speed = USB_SPEED_SUPER;
udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512);
usb_set_device_state(udev, USB_STATE_DEFAULT);
if (hcd->driver->enable_device) {
if (hcd->driver->enable_device(hcd, udev) < 0) {
dev_err(&intf->dev, "Failed to enable\n");
usb_put_dev(udev);
return NULL;
}
}
return udev;
}
static void destroy_lvs_device(struct usb_device *udev)
{
struct usb_device *hdev = udev->parent;
struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
if (hcd->driver->free_dev)
hcd->driver->free_dev(hcd, udev);
usb_put_dev(udev);
}
static int lvs_rh_clear_port_feature(struct usb_device *hdev,
int port1, int feature)
{
return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
USB_REQ_CLEAR_FEATURE, USB_RT_PORT, feature, port1,
NULL, 0, 1000);
}
static int lvs_rh_set_port_feature(struct usb_device *hdev,
int port1, int feature)
{
return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
USB_REQ_SET_FEATURE, USB_RT_PORT, feature, port1,
NULL, 0, 1000);
}
static ssize_t u3_entry_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct usb_interface *intf = to_usb_interface(dev);
struct usb_device *hdev = interface_to_usbdev(intf);
struct lvs_rh *lvs = usb_get_intfdata(intf);
struct usb_device *udev;
int ret;
udev = create_lvs_device(intf);
if (!udev) {
dev_err(dev, "failed to create lvs device\n");
return -ENOMEM;
}
ret = lvs_rh_set_port_feature(hdev, lvs->portnum,
USB_PORT_FEAT_SUSPEND);
if (ret < 0)
dev_err(dev, "can't issue U3 entry %d\n", ret);
destroy_lvs_device(udev);
if (ret < 0)
return ret;
return count;
}
static DEVICE_ATTR_WO(u3_entry);
static ssize_t u3_exit_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct usb_interface *intf = to_usb_interface(dev);
struct usb_device *hdev = interface_to_usbdev(intf);
struct lvs_rh *lvs = usb_get_intfdata(intf);
struct usb_device *udev;
int ret;
udev = create_lvs_device(intf);
if (!udev) {
dev_err(dev, "failed to create lvs device\n");
return -ENOMEM;
}
ret = lvs_rh_clear_port_feature(hdev, lvs->portnum,
USB_PORT_FEAT_SUSPEND);
if (ret < 0)
dev_err(dev, "can't issue U3 exit %d\n", ret);
destroy_lvs_device(udev);
if (ret < 0)
return ret;
return count;
}
static DEVICE_ATTR_WO(u3_exit);
static ssize_t hot_reset_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct usb_interface *intf = to_usb_interface(dev);
struct usb_device *hdev = interface_to_usbdev(intf);
struct lvs_rh *lvs = usb_get_intfdata(intf);
int ret;
ret = lvs_rh_set_port_feature(hdev, lvs->portnum,
USB_PORT_FEAT_RESET);
if (ret < 0) {
dev_err(dev, "can't issue hot reset %d\n", ret);
return ret;
}
return count;
}
static DEVICE_ATTR_WO(hot_reset);
static ssize_t u2_timeout_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct usb_interface *intf = to_usb_interface(dev);
struct usb_device *hdev = interface_to_usbdev(intf);
struct lvs_rh *lvs = usb_get_intfdata(intf);
unsigned long val;
int ret;
ret = kstrtoul(buf, 10, &val);
if (ret < 0) {
dev_err(dev, "couldn't parse string %d\n", ret);
return ret;
}
if (val < 0 || val > 127)
return -EINVAL;
ret = lvs_rh_set_port_feature(hdev, lvs->portnum | (val << 8),
USB_PORT_FEAT_U2_TIMEOUT);
if (ret < 0) {
dev_err(dev, "Error %d while setting U2 timeout %ld\n", ret, val);
return ret;
}
return count;
}
static DEVICE_ATTR_WO(u2_timeout);
static ssize_t u1_timeout_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct usb_interface *intf = to_usb_interface(dev);
struct usb_device *hdev = interface_to_usbdev(intf);
struct lvs_rh *lvs = usb_get_intfdata(intf);
unsigned long val;
int ret;
ret = kstrtoul(buf, 10, &val);
if (ret < 0) {
dev_err(dev, "couldn't parse string %d\n", ret);
return ret;
}
if (val < 0 || val > 127)
return -EINVAL;
ret = lvs_rh_set_port_feature(hdev, lvs->portnum | (val << 8),
USB_PORT_FEAT_U1_TIMEOUT);
if (ret < 0) {
dev_err(dev, "Error %d while setting U1 timeout %ld\n", ret, val);
return ret;
}
return count;
}
static DEVICE_ATTR_WO(u1_timeout);
static ssize_t get_dev_desc_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct usb_interface *intf = to_usb_interface(dev);
struct usb_device *udev;
struct usb_device_descriptor *descriptor;
int ret;
descriptor = kmalloc(sizeof(*descriptor), GFP_KERNEL);
if (!descriptor) {
dev_err(dev, "failed to allocate descriptor memory\n");
return -ENOMEM;
}
udev = create_lvs_device(intf);
if (!udev) {
dev_err(dev, "failed to create lvs device\n");
ret = -ENOMEM;
goto free_desc;
}
ret = usb_control_msg(udev, (PIPE_CONTROL << 30) | USB_DIR_IN,
USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, USB_DT_DEVICE << 8,
0, descriptor, sizeof(*descriptor),
USB_CTRL_GET_TIMEOUT);
if (ret < 0)
dev_err(dev, "can't read device descriptor %d\n", ret);
destroy_lvs_device(udev);
free_desc:
kfree(descriptor);
if (ret < 0)
return ret;
return count;
}
static DEVICE_ATTR_WO(get_dev_desc);
static struct attribute *lvs_attributes[] = {
&dev_attr_get_dev_desc.attr,
&dev_attr_u1_timeout.attr,
&dev_attr_u2_timeout.attr,
&dev_attr_hot_reset.attr,
&dev_attr_u3_entry.attr,
&dev_attr_u3_exit.attr,
NULL
};
static const struct attribute_group lvs_attr_group = {
.attrs = lvs_attributes,
};
static void lvs_rh_work(struct work_struct *work)
{
struct lvs_rh *lvs = container_of(work, struct lvs_rh, rh_work);
struct usb_interface *intf = lvs->intf;
struct usb_device *hdev = interface_to_usbdev(intf);
struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
struct usb_hub_descriptor *descriptor = &lvs->descriptor;
struct usb_port_status *port_status = &lvs->port_status;
int i, ret = 0;
u16 portchange;
/* Examine each root port */
for (i = 1; i <= descriptor->bNbrPorts; i++) {
ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, i,
port_status, sizeof(*port_status), 1000);
if (ret < 4)
continue;
portchange = port_status->wPortChange;
if (portchange & USB_PORT_STAT_C_LINK_STATE)
lvs_rh_clear_port_feature(hdev, i,
USB_PORT_FEAT_C_PORT_LINK_STATE);
if (portchange & USB_PORT_STAT_C_ENABLE)
lvs_rh_clear_port_feature(hdev, i,
USB_PORT_FEAT_C_ENABLE);
if (portchange & USB_PORT_STAT_C_RESET)
lvs_rh_clear_port_feature(hdev, i,
USB_PORT_FEAT_C_RESET);
if (portchange & USB_PORT_STAT_C_BH_RESET)
lvs_rh_clear_port_feature(hdev, i,
USB_PORT_FEAT_C_BH_PORT_RESET);
if (portchange & USB_PORT_STAT_C_CONNECTION) {
lvs_rh_clear_port_feature(hdev, i,
USB_PORT_FEAT_C_CONNECTION);
if (port_status->wPortStatus &
USB_PORT_STAT_CONNECTION) {
lvs->present = true;
lvs->portnum = i;
if (hcd->phy)
usb_phy_notify_connect(hcd->phy,
USB_SPEED_SUPER);
} else {
lvs->present = false;
if (hcd->phy)
usb_phy_notify_disconnect(hcd->phy,
USB_SPEED_SUPER);
}
break;
}
}
ret = usb_submit_urb(lvs->urb, GFP_KERNEL);
if (ret != 0 && ret != -ENODEV && ret != -EPERM)
dev_err(&intf->dev, "urb resubmit error %d\n", ret);
}
static void lvs_rh_irq(struct urb *urb)
{
struct lvs_rh *lvs = urb->context;
queue_work(lvs->rh_queue, &lvs->rh_work);
}
static int lvs_rh_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *hdev;
struct usb_host_interface *desc;
struct usb_endpoint_descriptor *endpoint;
struct lvs_rh *lvs;
unsigned int pipe;
int ret, maxp;
hdev = interface_to_usbdev(intf);
desc = intf->cur_altsetting;
endpoint = &desc->endpoint[0].desc;
/* valid only for SS root hub */
if (hdev->descriptor.bDeviceProtocol != USB_HUB_PR_SS || hdev->parent) {
dev_err(&intf->dev, "Bind LVS driver with SS root Hub only\n");
return -EINVAL;
}
lvs = devm_kzalloc(&intf->dev, sizeof(*lvs), GFP_KERNEL);
if (!lvs)
return -ENOMEM;
lvs->intf = intf;
usb_set_intfdata(intf, lvs);
/* how many number of ports this root hub has */
ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB,
USB_DT_SS_HUB << 8, 0, &lvs->descriptor,
USB_DT_SS_HUB_SIZE, USB_CTRL_GET_TIMEOUT);
if (ret < (USB_DT_HUB_NONVAR_SIZE + 2)) {
dev_err(&hdev->dev, "wrong root hub descriptor read %d\n", ret);
return ret;
}
/* submit urb to poll interrupt endpoint */
lvs->urb = usb_alloc_urb(0, GFP_KERNEL);
if (!lvs->urb) {
dev_err(&intf->dev, "couldn't allocate lvs urb\n");
return -ENOMEM;
}
lvs->rh_queue = create_singlethread_workqueue("lvs_rh_queue");
if (!lvs->rh_queue) {
dev_err(&intf->dev, "couldn't create workqueue\n");
ret = -ENOMEM;
goto free_urb;
}
INIT_WORK(&lvs->rh_work, lvs_rh_work);
ret = sysfs_create_group(&intf->dev.kobj, &lvs_attr_group);
if (ret < 0) {
dev_err(&intf->dev, "Failed to create sysfs node %d\n", ret);
goto destroy_queue;
}
pipe = usb_rcvintpipe(hdev, endpoint->bEndpointAddress);
maxp = usb_maxpacket(hdev, pipe, usb_pipeout(pipe));
usb_fill_int_urb(lvs->urb, hdev, pipe, &lvs->buffer[0], maxp,
lvs_rh_irq, lvs, endpoint->bInterval);
ret = usb_submit_urb(lvs->urb, GFP_KERNEL);
if (ret < 0) {
dev_err(&intf->dev, "couldn't submit lvs urb %d\n", ret);
goto sysfs_remove;
}
return ret;
sysfs_remove:
sysfs_remove_group(&intf->dev.kobj, &lvs_attr_group);
destroy_queue:
destroy_workqueue(lvs->rh_queue);
free_urb:
usb_free_urb(lvs->urb);
return ret;
}
static void lvs_rh_disconnect(struct usb_interface *intf)
{
struct lvs_rh *lvs = usb_get_intfdata(intf);
sysfs_remove_group(&intf->dev.kobj, &lvs_attr_group);
destroy_workqueue(lvs->rh_queue);
usb_free_urb(lvs->urb);
}
static struct usb_driver lvs_driver = {
.name = "lvs",
.probe = lvs_rh_probe,
.disconnect = lvs_rh_disconnect,
};
module_usb_driver(lvs_driver);
MODULE_DESCRIPTION("Link Layer Validation System Driver");
MODULE_LICENSE("GPL");