系统相关
首页 > 系统相关> > Linux usb 6. HC/UDC 测试

Linux usb 6. HC/UDC 测试

作者:互联网

目录

1. 背景介绍

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

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

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

2. Device (gadget zero)

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

2.1 gadget zero 创建

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

gadget zero 由两种方式创建:

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

2.2 SourceSink Function

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

具体提供了 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

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

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,
};

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: NOP\n" -
1 bulk Y - - - "TEST 1: write %d bytes %u times\n",
param->length, param->iterations
/* Simple non-queued bulk I/O tests */
2 bulk Y - - - "TEST 2: read %d bytes %u times\n",
param->length, param->iterations
-
3 bulk Y Y - - "TEST 3: write/%d 0..%d bytes %u times\n",
param->vary, param->length, param->iterations
-
4 bulk Y Y - - "TEST 4: read/%d 0..%d bytes %u times\n",
param->vary, param->length, param->iterations
-
5 bulk Y - Y - "TEST 5: write %d sglists %d entries of %d bytes\n",
param->iterations,param->sglen, param->length
/* Queued bulk I/O tests */
6 bulk Y - Y - "TEST 6: read %d sglists %d entries of %d bytes\n",
param->iterations,param->sglen, param->length
-
7 bulk Y Y Y - "TEST 7: write/%d %d sglists %d entries 0..%d bytes\n",
param->vary, param->iterations,param->sglen, param->length
-
8 bulk Y Y Y - "TEST 8: read/%d %d sglists %d entries 0..%d bytes\n",
param->vary, param->iterations,param->sglen, param->length
-
9 control Y - - - "TEST 9: ch9 (subset) control tests, %d times\n",
param->iterations
/* non-queued sanity tests for control (chapter 9 subset) */
10 control Y - Y - "TEST 10: queue %d control calls, %d times\n",
param->sglen, param->iterations)
/* queued control messaging */
11 bulk Y - - - "TEST 11: unlink %d reads of %d\n",
param->iterations, param->length
/* simple non-queued unlinks (ring with one urb) */
12 bulk Y - - - "TEST 12: unlink %d writes of %d\n",
param->iterations, param->length
-
13 control Y - - - "TEST 13: set/clear %d halts\n"
param->iterations
/* ep halt tests */
14 control Y Y - - "TEST 14: %d ep0out, %d..%d vary %d\n",
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 bytes\n",
param->iterations, param->sglen, param->length
/* iso write tests */
16 iso Y - Y - "TEST 16: read %d iso, %d entries of %d bytes\n",
param->iterations, param->sglen, param->length
/* iso read tests */
17 bulk Y - - Y "TEST 17: write odd addr %d bytes %u times core map\n"
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 map\n",
param->length, param->iterations
-
19 bulk Y - - Y "TEST 19: write odd addr %d bytes %u times premapped\n",
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 premapped\n",
param->length, param->iterations
-
21 control Y Y - Y "TEST 21: %d ep0out odd addr, %d..%d vary %d\n",
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 bytes\n",
param->iterations, param->sglen, param->length
/* unaligned iso tests */
23 iso Y - Y Y "TEST 23: read %d iso odd, %d entries of %d bytes\n",
param->iterations, param->sglen, param->length
-
24 bulk Y - Y - "TEST 24: unlink from %d queues of %d %d-byte writes\n",
param->iterations, param->sglen, param->length
/* unlink URBs from a bulk-OUT queue */
25 int Y - - - "TEST 25: write %d bytes %u times\n",
param->length, param->iterations
/* Simple non-queued interrupt I/O tests */
26 int Y - - - "TEST 26: read %d bytes %u times\n",
param->length, param->iterations
-
27 bulk Y - Y - "TEST 27: bulk write %dMbytes\n",
(param->iterations * param->sglen * param->length) / (1024 * 1024))
/* Performance test */
28 bulk Y - Y - "TEST 28: bulk read %dMbytes\n",
(param->iterations * param->sglen * param->length) / (1024 * 1024))
-
29 bulk Y - - - "TEST 29: Clear toggle between bulk writes %d times\n",
param->iterations
/* Test data Toggle/seq_nr clear between bulk out transfers */

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()

4. App (testusb)

因为通过 ioctl 可以调用 usbtest.ko 的 testcase,所以只要一个用户态的程序通过打开 /proc/bus/usb/devices/xxxx 对应 gadget zerousb interface device 的文件节点,就可以很方便的调用测试了。

USB Testing on Linux 有一个现成的工程,提供了 testusb.ctest.sh,但是因为适配的内核比较老,所以需要对 testusb.c 进行一些修改:

-       if ((c = open ("/proc/bus/usb/devices", O_RDONLY)) < 0) {
+       if ((c = open ("/sys/kernel/debug/usb/devices", O_RDONLY)) < 0) {
                fputs ("usbfs files are missing\n", stderr);
                return -1;
        }
 
        /* collect and list the test devices */
-       if (ftw ("/proc/bus/usb", find_testdev, 3) != 0) {
+       if (ftw ("/dev/bus/usb", find_testdev, 3) != 0) {
                fputs ("ftw failed; is usbfs missing?\n", stderr);
                return -1;
        }

简单编译:

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

参考资料

1.USB Testing on Linux
2.Linux USB测试
3.linux usb_gadget:设备控制器驱动测试
4.Linux-USB Gadget : Part 5: 测试 PXA UDC 驱动
5.Linux-USB Gadget : Part 4: 最简单的 gadget驱动:g_zero
6.Linux USB tests using Gadget Zero driver
7.USB/Linux USB Layers/Configfs Composite Gadget/Usage eq. to g zero.ko
8.usb/gadget: the start of the configfs interface

标签:usb,req,param,bulk,UDC,TEST,HC,ep
来源: https://www.cnblogs.com/pwl999/p/15540294.html