Linux usb 6. HC/UDC 測試

pwl999發表於2021-11-11

1. 背景介紹

在測試 USB 時,普通的做法是找一些 U 盤、滑鼠、鍵盤 等外設來做一些測試,但是這些測試還是偏上層偏功能的。相比較 HC (USB Host Controller) 和 UDC (USB Device Controller) 按照USB協議提供的完整功能來說,這種測試驗證時不充分的。

在 Linux Kernel 中對 HC/UDC 有一套專有的測試方案,在底層對 control/bulk/int/iso 幾種 endpoint 進行鍼對性的功能和壓力測試。

上圖的測試方案由幾部分組成:

  • 1、Device 側的 gadget zero 測試裝置,提供了測試通道。
  • 2、Host 側的 usbtest.ko 測試驅動,封裝了 30 個 endpoint 層級的測試用例。
  • 3、Host 側的 testusb 使用者程式,用來呼叫 usbtest.ko 提供的測試用例。

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 由兩種方式建立:

  • 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

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

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

相關文章