For an xhci host's downstream port, Link Validation System (LVS) is a
hardware which acts as device and used to run link layer tests. Most of
the link layer tests only expects link to be in U0. If a host sends a
setup packet, it is NAKed by LVS. However, there are some tests which
ask host to send some control command like Get Descriptor etc.

This patch adds a sysfs node to add a quirk flag to prevent enumeration
of a device, and then to call a driver for this device which can further
add options to generate test case specific traffic.

Without this patch many Lecroy Link Layer tests such as TD.7.02 fail.

Before a lecroy LVS device is connected to the root hub n, do following:

echo 1 >  /sys/bus/usb/devices/usbn/avoid_enum_quirk

Now connect LVS device and run Link Layer tests from Lecroy USB
Compliance Suite.

To get back into normal working situation (to connect a general usb device
to root hub n), do following:

echo 0 >  /sys/bus/usb/devices/usbn/avoid_enum_quirk

Signed-off-by: Pratyush Anand <pratyush.an...@st.com>
---
 Documentation/ABI/testing/sysfs-bus-usb |  10 +++
 drivers/usb/core/hub.c                  | 105 +++++++++++++++++++++++++++++++-
 drivers/usb/core/hub.h                  |   2 +
 drivers/usb/core/sysfs.c                |  34 +++++++++++
 drivers/usb/core/usb.c                  |   1 -
 include/linux/usb/quirks.h              |   3 +
 6 files changed, 151 insertions(+), 4 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-bus-usb 
b/Documentation/ABI/testing/sysfs-bus-usb
index f093e59..d7f6bfd 100644
--- a/Documentation/ABI/testing/sysfs-bus-usb
+++ b/Documentation/ABI/testing/sysfs-bus-usb
@@ -236,3 +236,13 @@ Description:
                This attribute is to expose these information to user space.
                The file will read "hotplug", "wired" and "not used" if the
                information is available, and "unknown" otherwise.
+
+What:          /sys/bus/usb/device/.../avoid_enum_quirk
+Date:          March 2014
+Contact:       Pratyush Anand <pratyush.an...@st.com>
+Description:
+               Only root hub allows to write. Writing to
+               other device will return error. Writing 1 to this file tells the
+               kernel that device connected to this root hub must not
+               be enumerated. Ports of this root hub will be used to
+               conncet Link Layer Validation System device.
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 5edcfb2..124b5c8 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -27,6 +27,7 @@
 #include <linux/freezer.h>
 #include <linux/random.h>
 #include <linux/pm_qos.h>
+#include <linux/platform_device.h>
 
 #include <asm/uaccess.h>
 #include <asm/byteorder.h>
@@ -4656,6 +4657,98 @@ static int hub_handle_remote_wakeup(struct usb_hub *hub, 
unsigned int port,
        return connect_change;
 }
 
+static void usb_destroy_lvs_device(struct usb_hub *hub, int port1)
+{
+       struct usb_device *hdev = hub->hdev;
+       struct platform_device  *lvstestdev = hub->ports[port1 -1]->lvstestdev;
+       struct usb_device *udev = hub->ports[port1 - 1]->child;
+
+       if (!lvstestdev) {
+               dev_err(&hdev->dev, "No Platform device at port %d\n", port1);
+               return;
+       }
+
+       platform_device_unregister(lvstestdev);
+
+       kfree(udev);
+
+       hub->ports[port1 -1]->child = NULL;
+       hub->ports[port1 -1]->lvstestdev = NULL;
+       dev_info(&hdev->dev, "LVS device destroyed\n");
+}
+
+static int usb_create_lvs_device(struct usb_hub *hub, int port1)
+{
+       struct usb_device *hdev = hub->hdev;
+       struct usb_device *udev;
+       struct platform_device  *lvstestdev = hub->ports[port1 -1]->lvstestdev;
+       int ret;
+
+       if (lvstestdev) {
+               dev_err(&hdev->dev,
+                       "Already a platform device at port %d\n", port1);
+               usb_destroy_lvs_device(hub, port1);
+       }
+
+       udev = usb_alloc_dev(hdev, hdev->bus, port1);
+       if (!udev) {
+               dev_err (&hdev->dev, "couldn't allocate port %d usb_device\n",
+                               port1);
+               return -ENOMEM;
+       }
+
+       if (hub_is_superspeed(hdev)) {
+               udev->speed = USB_SPEED_SUPER;
+               udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512);
+               usb_set_device_state(udev, USB_STATE_DEFAULT);
+               ret = hub_enable_device(udev);
+               if (ret < 0) {
+                       dev_err(&hdev->dev, "Failed to enable %d\n", ret);
+                       goto free_udev;
+               }
+       } else {
+               dev_err(&hdev->dev, "Host not supported\n");
+               ret = -EINVAL;
+               goto free_udev;
+       }
+
+       lvstestdev = platform_device_alloc("lvstestdev", PLATFORM_DEVID_AUTO);
+       if (!lvstestdev) {
+               dev_err(&hdev->dev, "couldn't allocate lvstestdev device\n");
+               ret = -ENOMEM;
+               goto free_udev;
+       }
+
+       /*
+        * udev will be copied to platform_data and then memory of udev
+        * will be released.
+        */
+       ret = platform_device_add_data(lvstestdev, &udev, sizeof(udev));
+       if (ret) {
+               dev_err(&hdev->dev, "couldn't add udev data\n");
+               goto free_lvstestdev;
+       }
+
+       ret = platform_device_add(lvstestdev);
+       if (ret) {
+               dev_err(&hdev->dev, "failed to register lvstestdev device\n");
+               goto free_lvstestdev;
+       }
+
+       hub->ports[port1 -1]->lvstestdev = lvstestdev;
+       hub->ports[port1 -1]->child = udev;
+
+       dev_info(&hdev->dev, "LVS device created\n");
+       return 0;
+
+free_lvstestdev:
+       platform_device_put(lvstestdev);
+free_udev:
+       kfree(udev);
+
+       return ret;
+}
+
 static void hub_events(void)
 {
        struct list_head *tmp;
@@ -4862,9 +4955,15 @@ static void hub_events(void)
                                }
                        }
 
-                       if (connect_change)
-                               hub_port_connect_change(hub, i,
-                                               portstatus, portchange);
+                       if (connect_change) {
+                               if (!(hdev->quirks & USB_QUIRK_ENUM))
+                                       hub_port_connect_change(hub, i,
+                                                       portstatus, portchange);
+                               else if (portstatus & USB_PORT_STAT_ENABLE)
+                                       usb_create_lvs_device(hub, i);
+                               else
+                                       usb_destroy_lvs_device(hub, i);
+                       }
                } /* end for i */
 
                /* deal with hub status changes */
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index f608b39..fa25f62 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -85,6 +85,7 @@ struct usb_hub {
  * @portnum: port index num based one
  * @power_is_on: port's power state
  * @did_runtime_put: port has done pm_runtime_put().
+ * @lvs_data: LVS platform device attached to this port
  */
 struct usb_port {
        struct usb_device *child;
@@ -94,6 +95,7 @@ struct usb_port {
        u8 portnum;
        unsigned power_is_on:1;
        unsigned did_runtime_put:1;
+       void *lvstestdev;
 };
 
 #define to_usb_port(_dev) \
diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c
index aa38db4..0a4a5ab 100644
--- a/drivers/usb/core/sysfs.c
+++ b/drivers/usb/core/sysfs.c
@@ -234,6 +234,39 @@ static DEVICE_ATTR(avoid_reset_quirk, S_IRUGO | S_IWUSR,
                show_avoid_reset_quirk, set_avoid_reset_quirk);
 
 static ssize_t
+show_avoid_enum_quirk(struct device *dev, struct device_attribute *attr, char 
*buf)
+{
+       struct usb_device *udev;
+
+       udev = to_usb_device(dev);
+       return sprintf(buf, "%d\n", !!(udev->quirks & USB_QUIRK_ENUM));
+}
+
+static ssize_t
+set_avoid_enum_quirk(struct device *dev, struct device_attribute *attr,
+               const char *buf, size_t count)
+{
+       struct usb_device       *udev = to_usb_device(dev);
+       int                     val;
+
+       if (udev->parent)
+               return -EINVAL;
+
+       if (sscanf(buf, "%d", &val) != 1 || val < 0 || val > 1)
+               return -EINVAL;
+       usb_lock_device(udev);
+       if (val)
+               udev->quirks |= USB_QUIRK_ENUM;
+       else
+               udev->quirks &= ~USB_QUIRK_ENUM;
+       usb_unlock_device(udev);
+       return count;
+}
+
+static DEVICE_ATTR(avoid_enum_quirk, S_IRUGO | S_IWUSR,
+               show_avoid_enum_quirk, set_avoid_enum_quirk);
+
+static ssize_t
 show_urbnum(struct device *dev, struct device_attribute *attr, char *buf)
 {
        struct usb_device *udev;
@@ -668,6 +701,7 @@ static struct attribute *dev_attrs[] = {
        &dev_attr_maxchild.attr,
        &dev_attr_quirks.attr,
        &dev_attr_avoid_reset_quirk.attr,
+       &dev_attr_avoid_enum_quirk.attr,
        &dev_attr_authorized.attr,
        &dev_attr_remove.attr,
        &dev_attr_removable.attr,
diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
index b10da72..d708baa 100644
--- a/drivers/usb/core/usb.c
+++ b/drivers/usb/core/usb.c
@@ -345,7 +345,6 @@ static unsigned usb_bus_is_wusb(struct usb_bus *bus)
        return hcd->wireless;
 }
 
-
 /**
  * usb_alloc_dev - usb device constructor (usbcore-internal)
  * @parent: hub to which device is connected; null to allocate a root hub
diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h
index 52f944d..2a1ac98 100644
--- a/include/linux/usb/quirks.h
+++ b/include/linux/usb/quirks.h
@@ -30,4 +30,7 @@
    descriptor */
 #define USB_QUIRK_DELAY_INIT           0x00000040
 
+/* device can't be ennumerated. This is a Lecroy Link Layer Validation System 
*/
+#define USB_QUIRK_ENUM                 0x00000080
+
 #endif /* __LINUX_USB_QUIRKS_H */
-- 
1.8.1.2

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to