7.12.3. 测试指南

7.12.3.1. 测试方案介绍

在测试 USB 时,普通的做法是找一些 U 盘、鼠标、键盘 等外设来做一些测试,但是这些测试还是偏上层偏功能的。相比较 HC (USB Host Controller) 和 UDC (USB Device Controller) 按照USB协议提供的完整功能来说,这种测试验证时不充分的。

在 Linux Kernel 中对 HC/UDC 有一套专有的测试方案,在底层对 control/bulk/int/iso 几种 endpoint 进行针对性的功能和压力测试。

image0

上图的测试方案由几部分组成:

  • 1、Device 侧的 gadget zero 测试设备,提供了测试通道。

  • 2、Host 侧的 usbtest.ko 测试驱动,封装了 30 个 endpoint 层级的测试用例。

  • 3、Host 侧的 testusb 用户程序,用来调用 usbtest.ko 提供的测试用例。

7.12.3.2. Device 侧 gadget zero

提供测试需要的Device设备有很多种方式,例如可用使用专门的测试 Device 里面烧录专有的测试 Firmware。节约成本的方式还是使用 Linux gadget 功能来动态模拟 USB Device 设备。针对 USB 测试,Linux 专门提供了 gadget zero 设备。

7.12.3.2.1. Device 创建

gadget zero 的核心是创建一个 Composite Device ,其包含了两个 Configuration ,其中一个 Configuration 0 包含 SourceSink Function/Interface ,另一个 Configuration 1 包含 Loopback Function/Interface 。某一时刻只能选择使用一个 Configuration ,通常情况下使用 Configuration 0SourceSink 的功能。

gadget zero Device 由两种方式创建:

  • 1、通过 zero_driver 创建,只要把对应驱动文件 drivers\usb\gadget\legacy\zero.c 编译进内核即可。

  • 2、通过 functionfs 动态创建,这种方式更灵活,实例命令如下:

mount -t configfs none /sys/kernel/config
cd /sys/kernel/config/usb_gadget

mkdir g2
cd g2

echo "0x04e8" > idVendor
echo "0x2d01" > idProduct

mkdir configs/c.1
mkdir configs/c.2
mkdir functions/Loopback.0
mkdir functions/SourceSink.0

mkdir strings/0x409
mkdir configs/c.1/strings/0x409
mkdir configs/c.2/strings/0x409

echo "0x0525" > idVendor
echo "0xa4a0" > idProduct

echo "0123456789" > strings/0x409/serialnumber
echo "Samsung Inc." > strings/0x409/manufacturer
echo "Bar Gadget" > strings/0x409/product

echo "Conf 1" > configs/c.1/strings/0x409/configuration
echo "Conf 2" > configs/c.2/strings/0x409/configuration
echo 120 > configs/c.1/MaxPower

// SourceSink:驱动 set configuration 会选取 第一个 configuration
ln -s functions/Loopback.0 configs/c.2
ln -s functions/SourceSink.0 configs/c.1

echo 4100000.udc-controller > UDC

整个过程就是创建了一个 Vendor ID = 0x0525Product ID = 0xa4a0Composite Device ,在 Host 侧可以查看这个设备:

$ lsusb -s 1:3
Bus 001 Device 003: ID 0525:a4a0 Netchip Technology, Inc. Linux-USB "Gadget Zero"

$ lsusb -v -s 1:3

Bus 001 Device 003: ID 0525:a4a0 Netchip Technology, Inc. Linux-USB "Gadget Zero"
Couldn't open device, some information will be missing
Device Descriptor:
bLength                18
bDescriptorType         1
bcdUSB               2.00
bDeviceClass            0
bDeviceSubClass         0
bDeviceProtocol         0
bMaxPacketSize0        64
idVendor           0x0525 Netchip Technology, Inc.
idProduct          0xa4a0 Linux-USB "Gadget Zero"
bcdDevice            5.10
iManufacturer           1
iProduct                2
iSerial                 3
bNumConfigurations      2
Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength       0x0045
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          4
    bmAttributes         0x80
    (Bus Powered)
    MaxPower              120mA
    Interface Descriptor:
    bLength                 9
    bDescriptorType         4
    bInterfaceNumber        0
    bAlternateSetting       0
    bNumEndpoints           2
    bInterfaceClass       255 Vendor Specific Class
    bInterfaceSubClass      0
    bInterfaceProtocol      0
    iInterface              0
    Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            2
        Transfer Type            Bulk
        Synch Type               None
        Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
    Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x01  EP 1 OUT
        bmAttributes            2
        Transfer Type            Bulk
        Synch Type               None
        Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
    Interface Descriptor:
    bLength                 9
    bDescriptorType         4
    bInterfaceNumber        0
    bAlternateSetting       1
    bNumEndpoints           4
    bInterfaceClass       255 Vendor Specific Class
    bInterfaceSubClass      0
    bInterfaceProtocol      0
    iInterface              0
    Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            2
        Transfer Type            Bulk
        Synch Type               None
        Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
    Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x01  EP 1 OUT
        bmAttributes            2
        Transfer Type            Bulk
        Synch Type               None
        Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
    Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x82  EP 2 IN
        bmAttributes            1
        Transfer Type            Isochronous
        Synch Type               None
        Usage Type               Data
        wMaxPacketSize     0x0400  1x 1024 bytes
        bInterval               4
    Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x02  EP 2 OUT
        bmAttributes            1
        Transfer Type            Isochronous
        Synch Type               None
        Usage Type               Data
        wMaxPacketSize     0x0400  1x 1024 bytes
        bInterval               4
Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength       0x0020
    bNumInterfaces          1
    bConfigurationValue     2
    iConfiguration          5
    bmAttributes         0x80
    (Bus Powered)
    MaxPower                2mA
    Interface Descriptor:
    bLength                 9
    bDescriptorType         4
    bInterfaceNumber        0
    bAlternateSetting       0
    bNumEndpoints           2
    bInterfaceClass       255 Vendor Specific Class
    bInterfaceSubClass      0
    bInterfaceProtocol      0
    iInterface              6
    Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            2
        Transfer Type            Bulk
        Synch Type               None
        Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
    Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x01  EP 1 OUT
        bmAttributes            2
        Transfer Type            Bulk
        Synch Type               None
        Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0

7.12.3.2.2. SourceSink Function

SourceSink Function 的主要功能是提供了一组 USB 测试 endpoint,其中:

  • Sink。sinks bulk packets OUT to the peripheral。意思是把数据从 Host 引流到 Device,即 OUT 方向。

  • Source。sources them IN to the host。意思是把从 Device 发送数据到 Device,即 IN 方向。

具体提供了 4 组 测试 endpoint:

Endpoint

Type

Direction

Descript|

in_ep

bulk

IN

Source 发送数据到 Host,
注意这数据是 Device 主动生成的

out_ep

bulk

OUT

Sink 接收 Host 的数据

iso_in_ep

iso

IN

Source 发送数据到 Host

iso_out_ep

iso

OUT

Sink 接收 Host 的数据

主要流程如下:

drivers\usb\gadget\function\f_sourcesink.c:

sourcesink_bind():

static int
sourcesink_bind(struct usb_configuration *c, struct usb_function *f)
{

    /* (1) 从 gadget 中分配 2 个 bulk endpoint */
    /* allocate bulk endpoints */
    ss->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_source_desc);

    ss->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_sink_desc);

    /* (2) 如果支持ISO,再从 gadget 中分配 2 个 iso endpoint */
    /* allocate iso endpoints */
    ss->iso_in_ep = usb_ep_autoconfig(cdev->gadget, &fs_iso_source_desc);
    if (!ss->iso_in_ep)
        goto no_iso;

    ss->iso_out_ep = usb_ep_autoconfig(cdev->gadget, &fs_iso_sink_desc);
    if (!ss->iso_out_ep) {

}

sourcesink_set_alt() → enable_source_sink() → usb_ep_enable()/source_sink_start_ep():
// 启动上述 endpoint

→ source_sink_complete():
// urb 的 complete() 函数,urb 发送/接收完成后,重新挂载 urb

还支持一些参数调整:

# ls functions/SourceSink.0/
bulk_buflen     iso_qlen        isoc_maxburst   isoc_mult
bulk_qlen       isoc_interval   isoc_maxpacket  pattern

7.12.3.2.3. Loopback Function

Loopback Function 提供的功能更为简单,它分配了两个 bulk endpoint,所做的就是把 out_ep 接收到的数据 转发到 in_ep

主要流程如下:

drivers\usb\gadget\function\f_loopback.c:

loopback_bind():

static int loopback_bind(struct usb_configuration *c, struct usb_function *f)
{
    /* (1) 从 gadget 中分配 2 个 bulk endpoint */
    /* allocate endpoints */
    loop->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_source_desc);

    loop->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_loop_sink_desc);
}

loopback_set_alt() → enable_loopback() → alloc_requests():

static int alloc_requests(struct usb_composite_dev *cdev,
            struct f_loopback *loop)
{

    for (i = 0; i < loop->qlen && result == 0; i++) {
        result = -ENOMEM;

        in_req = usb_ep_alloc_request(loop->in_ep, GFP_ATOMIC);
        if (!in_req)
            goto fail;

        out_req = lb_alloc_ep_req(loop->out_ep, loop->buflen);
        if (!out_req)
            goto fail_in;

        in_req->complete = loopback_complete;
        out_req->complete = loopback_complete;

        in_req->buf = out_req->buf;
        /* length will be set in complete routine */
        in_req->context = out_req;
        out_req->context = in_req;

        /* (2) 先启动 OUT endpoint */
        result = usb_ep_queue(loop->out_ep, out_req, GFP_ATOMIC);
        if (result) {
            ERROR(cdev, "%s queue req --> %d\n",
                    loop->out_ep->name, result);
            goto fail_out;
        }
    }

}

static void loopback_complete(struct usb_ep *ep, struct usb_request *req)
{
    struct f_loopback       *loop = ep->driver_data;
    struct usb_composite_dev *cdev = loop->function.config->cdev;
    int                     status = req->status;

    switch (status) {
    case 0:                         /* normal completion? */
        if (ep == loop->out_ep) {
            /*
            * We received some data from the host so let's
            * queue it so host can read the from our in ep
            */
            struct usb_request *in_req = req->context;

            in_req->zero = (req->actual < req->length);
            in_req->length = req->actual;
            ep = loop->in_ep;
            req = in_req;
        } else {
            /*
            * We have just looped back a bunch of data
            * to host. Now let's wait for some more data.
            */
            req = req->context;
            ep = loop->out_ep;
        }

        /* (3) 环回的关键:
                OUT endpoint 接收到的数据 转发到 IN endpoint
                IN endpoint 数据发送完成后 req 重新挂载到 OUT endpoint
        */
        /* queue the buffer back to host or for next bunch of data */
        status = usb_ep_queue(ep, req, GFP_ATOMIC);

}

也支持一些参数调整:

# ls functions/Loopback.0/
bulk_buflen  qlen

7.12.3.3. Host 侧 usbtest.ko

在 Host 侧的 usbtest.ko 它就是一个标准的 usb interface driver。它根据 Vendor ID = 0x0525Product ID = 0xa4a0 适配上一节 Composite Device 中的 SourceSink Interface 或者 Loopback Interface

static const struct usb_device_id id_table[] = {

    /* "Gadget Zero" firmware runs under Linux */
    { USB_DEVICE(0x0525, 0xa4a0),
        .driver_info = (unsigned long) &gz_info,
    },

}
MODULE_DEVICE_TABLE(usb, id_table);

static struct usb_driver usbtest_driver = {
    .name =         "usbtest",
    .id_table =     id_table,
    .probe =        usbtest_probe,
    .unlocked_ioctl = usbtest_ioctl,
    .disconnect =   usbtest_disconnect,
    .suspend =      usbtest_suspend,
    .resume =       usbtest_resume,
};

7.12.3.3.1. TestCase

其在 SourceSink Interface 提供的 4 个测试 endpoint、或者 Loopback Interface 提供的 2 个测试 endpoint + Composite Device 本身的 ep0 control endpoint 基础之上,提供了 30 个 testcase:

drivers\usb\misc\usbtest.c:

usbtest_do_ioctl()

index

type

iterations

vary

sglen

unaligned

testcase

descript

0

nop

“TEST 0: NOPn”

1

bulk

Y

“TEST 1: write %d bytes %u timesn”,
param->length, param->iterations

Simple non-queued bulk I/O tests

2

bulk

Y

“TEST 2: read %d bytes %u timesn”,
param->length, param->iterations

3

bulk

Y

Y

“TEST 3: write/%d 0..%d bytes %u timesn”,
param->vary, param->length, param->iterations

4

bulk

Y

Y

“TEST 4: read/%d 0..%d bytes %u timesn”,
param->vary, param->length, param->iterations

5

bulk

Y

Y

“TEST 5: write %d sglists %d entries of %d bytesn”,
param->iterations,param->sglen, param->length

Queued bulk I/O tests

6

bulk

Y

Y

“TEST 6: read %d sglists %d entries of %d bytesn”,
param->iterations,param->sglen, param->length

7

bulk

Y

Y

Y

“TEST 7: write/%d %d sglists %d entries 0..%d bytesn”,
param->vary, param->iterations,param->sglen, param->length

8

bulk

Y

Y

Y

“TEST 8: read/%d %d sglists %d entries 0..%d bytesn”,
param->vary, param->iterations,param->sglen, param->length

9

control

Y

“TEST 9: ch9 (subset) control tests, %d timesn”,
param->iterations

non-queued sanity tests for control (chapter 9 subset)

10

control

Y

Y

“TEST 10: queue %d control calls, %d timesn”,
param->sglen, param->iterations)

queued control messaging

11

bulk

Y

“TEST 11: unlink %d reads of %dn”,
param->iterations, param->length

simple non-queued unlinks (ring with one urb)

12

bulk

Y

“TEST 12: unlink %d writes of %dn”,
param->iterations, param->length

13

control

Y

“TEST 13: set/clear %d haltsn”
param->iterations

ep halt tests

14

control

Y

Y

“TEST 14: %d ep0out, %d..%d vary %dn”,
param->iterations,realworld ? 1 : 0, param->length,param->vary

control write tests

15

iso

Y

Y

“TEST 15: write %d iso, %d entries of %d bytesn”,
param->iterations, param->sglen, param->length

iso write tests

16

iso

Y

Y

“TEST 16: read %d iso, %d entries of %d bytesn”,
param->iterations, param->sglen, param->length

iso read tests

17

bulk

Y

Y

“TEST 17: write odd addr %d bytes %u times core mapn”
param->length, param->iterations

Tests for bulk I/O using DMA mapping by core and odd address

18

bulk

Y

Y

“TEST 18: read odd addr %d bytes %u times core mapn”,
param->length, param->iterations

19

bulk

Y

Y

“TEST 19: write odd addr %d bytes %u times premappedn”,
param->length, param->iterations

Tests for bulk I/O using premapped coherent buffer and odd address

20

bulk

Y

Y

“TEST 20: read odd addr %d bytes %u times premappedn”,
param->length, param->iterations

21

control

Y

Y

Y

“TEST 21: %d ep0out odd addr, %d..%d vary %dn”,
param->iterations,realworld ? 1 : 0, param->length, param->vary

control write tests with unaligned buffer

22

iso

Y

Y

Y

“TEST 22: write %d iso odd, %d entries of %d bytesn”,
param->iterations, param->sglen, param->length

unaligned iso tests

23

iso

Y

Y

Y

“TEST 23: read %d iso odd, %d entries of %d bytesn”,
param->iterations, param->sglen, param->length

24

bulk

Y

Y

“TEST 24: unlink from %d queues of %d %d-byte writesn”,
param->iterations, param->sglen, param->length

unlink URBs from a bulk-OUT queue

25

int

Y

“TEST 25: write %d bytes %u timesn”,
param->length, param->iterations

Simple non-queued interrupt I/O tests

26

int

Y

“TEST 26: read %d bytes %u timesn”,
param->length, param->iterations

27

bulk

Y

Y

“TEST 27: bulk write %dMbytesn”,
(param->iterations * param->sglen * param->length) / (1024 * 1024))

Performance test

28

bulk

Y

Y

“TEST 28: bulk read %dMbytesn”,
(param->iterations * param->sglen * param->length) / (1024 * 1024))

29

bulk

Y

“TEST 29: Clear toggle between bulk writes %d timesn”,
param->iterations

Test data Toggle/seq_nr clear between bulk out transfers

7.12.3.3.2. Ioctl

usbtest.ko 以 ioctl 的形式向用户态提供对 testcase 的调用:

usbdev_file_operations → usbdev_ioctl() → usbdev_do_ioctl() → proc_ioctl_default() → proc_ioctl():

static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl)
{

    /*  (1) 找到对应的 usb interface device */
    else if (!(intf = usb_ifnum_to_if(ps->dev, ctl->ifno)))
        retval = -EINVAL;

    /* talk directly to the interface's driver */
    default:
        if (intf->dev.driver)
            /*  (2) 找到 usb interface device 对应的 driver  */
            driver = to_usb_driver(intf->dev.driver);
        if (driver == NULL || driver->unlocked_ioctl == NULL) {
            retval = -ENOTTY;
        } else {
            /* (3) 调用 driver 的 ioctl 函数 */
            retval = driver->unlocked_ioctl(intf, ctl->ioctl_code, buf);
            if (retval == -ENOIOCTLCMD)
                retval = -ENOTTY;
        }

}

↓

usbtest_ioctl() → usbtest_do_ioctl()

7.12.3.4. Host 侧 testusb

testusb 源码包含在 linux 内核当中, 路径为 linux-5.10\tools\usb\testusb.c 。可以通过 openwrt 编译,或者简单编译:

gcc -Wall -g -lpthread -o testusb testusb.c

就可以启动测试了:

$ sudo ./testusb -a
unknown speed       /dev/bus/usb/001/002
/dev/bus/usb/001/002 test 0,    0.000011 secs
/dev/bus/usb/001/002 test 1,    1.625031 secs
/dev/bus/usb/001/002 test 2 --> 110 (Connection timed out)
/dev/bus/usb/001/002 test 3,    1.639717 secs
/dev/bus/usb/001/002 test 4 --> 110 (Connection timed out)
/dev/bus/usb/001/002 test 5,    1.915198 secs
/dev/bus/usb/001/002 test 6 --> 110 (Connection timed out)
/dev/bus/usb/001/002 test 7,    1.928419 secs
/dev/bus/usb/001/002 test 8 --> 110 (Connection timed out)
/dev/bus/usb/001/002 test 9,   13.835084 secs

sudo ./testusb -a

sudo ./testusb -a -t1 -c1 -s512 -g32 -v32

sudo ./testusb -a -t29 -c1 -s512 -g32 -v32

// test 10 需要特别注意,容易挂死 host
sudo ./testusb -a -t10 -c1 -s512 -g5 -v32
// test 28 需要特别注意,容易挂死 host
sudo ./testusb -a -t28 -c1 -s512 -g32 -v32