Linux 如何在USB vhci中启动USB批量传输
编辑:重新编排这个问题,因为我已经设法让基础知识发挥作用,但仍然遇到问题。 我正试图模拟一个USB设备(条形码扫描器),以便使用进行测试,但我遇到了一些问题 给出一些上下文:该设备是一个CDC抽象调制解调器,客户端(一个java程序)使用AT命令通过串行线与之通信 基本上,我已经启动并运行了我的设备,它正确地注册了自己,我能够接收来自客户端的命令并响应客户端 主要问题似乎是,一旦设备启动或从主机接收到大容量传输,它就会触发正在进行的大容量和中断传输流(大量,我的usbmon日志在几秒钟内增长到100 MB) 首先是在启动时,它一直(主要)大量输出传输,直到我收到SET_CONTROL_LINE_STATE请求,然后它们停止。然后,当客户端发送命令时(通过串行设备发送AT命令),它将再次启动 我猜这是因为我没有正确地回应一些转移,但我不知道它是什么 我一直在比较我的设备的usbmon输出和真实设备的输出,但到目前为止,我还无法检测到任何差异,来解释为什么我的模拟设备的行为是这样的,而真实设备的行为不是这样的 我基本上是从libusb_vhci/examples/virtual_device2.c中的示例代码开始的,并对其进行了修改以模拟实际设备。首先是设备描述符:Linux 如何在USB vhci中启动USB批量传输,linux,usb,libusb,Linux,Usb,Libusb,编辑:重新编排这个问题,因为我已经设法让基础知识发挥作用,但仍然遇到问题。 我正试图模拟一个USB设备(条形码扫描器),以便使用进行测试,但我遇到了一些问题 给出一些上下文:该设备是一个CDC抽象调制解调器,客户端(一个java程序)使用AT命令通过串行线与之通信 基本上,我已经启动并运行了我的设备,它正确地注册了自己,我能够接收来自客户端的命令并响应客户端 主要问题似乎是,一旦设备启动或从主机接收到大容量传输,它就会触发正在进行的大容量和中断传输流(大量,我的usbmon日志在几秒钟内增长到1
const uint8_t dev_desc[] = {
/* Device Descriptor */
0x12, //bLength 18
0x01, //bDescriptorType 1
0x00, 0x02, //bcdUSB 2.00
0x02, //bDeviceClass 2 Communications
0x00, //bDeviceSubClass 0
0x00, //bDeviceProtocol 0
0x40, //bMaxPacketSize0 64
0x5a, 0x06, //idVendor 065a
0x02, 0xa0, //idProduct a002
0x00, 0x01, //bcdDevice 1.00
0x00, //iManufacturer 0
0x01, //iProduct 1
0x00, //iSerial 0
0x01 //bNumConfigurations 1
};
const uint8_t conf_desc[] = {
/* Configuration Descriptor */
0x09, //bLength 9
0x02, //bDescriptorType 2
0x43, 0x00, //wTotalLength 67 ??
0x02, //bNumInterfaces 2
0x01, //bConfigurationValue 1
0x00, //iConfiguration 0
0x80, //bmAttributes (Bus Powered) 0x80
250, //MaxPower 500mA
/* Interface Descriptor 0 */
0x09, //bLength 9
0x04, //bDescriptorType 4
0x00, //bInterfaceNumber 0
0x00, //bAlternateSetting 0
0x01, //bNumEndpoints 1
0x02, //bInterfaceClass 2 Communications
0x02, //bInterfaceSubClass 2 Abstract (modem)
0x00, //bInterfaceProtocol 0 None
0x00, //iInterface 0
/* CDC Header */
0x05, //bLength 7
0x24, //bDescriptorType 5
0x00, //bEndpointAddress 0x01 EP 1 OUT
0x10, //bcdCDC 1.10
0x01, //"
/* CDC Call Management */
0x05, //bLength 3
0x24, //CDC_CS_INTERFACE
0x01, //CDC_CALL_MANAGEMENT
0x01, //bmCapabilities 0x01
0x00, //bDataInterface 0
/* CDC ACM */
0x04, //bLength 2
0x24, //CDC_CS_INTERFACE
0x02, //CDC_ABSTRACT_CONTROL_MANAGEMENT
0x02, //bmCapabilities 0x02
/* CDC Union */
0x05, //bLength 3
0x24, //CDC_CS_INTERFACE
0x06, //CDC_UNION
0x00, //bMasterInterface 0
0x01, //bSlaveInterface 1
/* Endpoint Descriptor */
0x07, //bLength 7
0x05, //bDescriptorType 5
0x83, //bEndpointAddress 0x83 EP 3 IN
0x03, //bmAttributes 3
0x40, 0x00, //wMaxPacketSize 0x0040 1x 64 bytes
0x0a, //bInterval 10
/* Interface Descriptor 1 */
0x09, //bLength 9
0x04, //bDescriptorType 4
0x01, //bInterfaceNumber 1
0x00, //bAlternateSetting 0
0x02, //bNumEndpoints 2
0x0a, //bInterfaceClass 10 CDC Data
0x00, //bInterfaceSubClass 0
0x00, //bInterfaceProtocol 0
0x00, //iInterface 0
/* Endpoint Descriptor */
0x07, //bLength 7
0x05, //bDescriptorType 5
0x01, //bEndpointAddress 0x01 EP 1 OUT
0x02, //bmAttributes 2
0x40, 0x00, //wMaxPacketSize 0x0040 1x 64 bytes
0x00, //bInterval 0
/* Endpoint Descriptor */
0x07, //bLength 7
0x05, //bDescriptorType 5
0x82, //bEndpointAddress 0x82 EP 2 IN
0x02, //bmAttributes 2
0x40,0x00, //wMaxPacketSize 0x0040 1x 64 bytes
0x00 //bInterval 0
};
const uint8_t str0_desc[] = {
0x04, //bLength 4
0x03, //bDescriptorType 3
0x09, 0x04 //bLanguage 0409 US
};
const uint8_t *str1_desc =
(uint8_t *)"\x36\x03O\0p\0t\0i\0c\0o\0n\0 \0U\0S\0B\00\0B\0a\0r\0c\0o\0d\0e\0 \0R\0e\0a\0d\0e\0r";
main函数与示例中的相同,但process_urb()函数是主要更改的。控制部分基本上完好无损,但我添加了一些附加设置数据包的处理:
uint8_t rt = urb->bmRequestType;
uint8_t r = urb->bRequest;
if(rt == 0x00 && r == URB_RQ_SET_CONFIGURATION)
{
devlog("URB_RQ_SET_CONFIGURATION\n");
urb->status = USB_VHCI_STATUS_SUCCESS;
}
else if(rt == 0x00 && r == URB_RQ_SET_INTERFACE)
{
devlog("URB_RQ_SET_INTERFACE\n");
urb->status = USB_VHCI_STATUS_SUCCESS;
}
else if (rt == 0x21 && r == 0x20)
{
devlog("URB_CDC_SET_LINE_CODING\n");
urb->status = USB_VHCI_STATUS_SUCCESS;
}
else if (rt == 0x21 && r == 0x22)
{
devlog("URB_CDC_SET_CONTROL_LINE_STATE\n");
urb->status = USB_VHCI_STATUS_SUCCESS;
}
else if(rt == 0x80 && r == URB_RQ_GET_DESCRIPTOR)
{
int l = urb->wLength;
uint8_t *buffer = urb->buffer;
devlog("GET_DESCRIPTOR ");
switch(urb->wValue >> 8)
{
case 0:
puts("WTF_DESCRIPTOR");
urb->status = USB_VHCI_STATUS_SUCCESS;
break;
case 1:
puts("DEV_DESC");
if(dev_desc[0] < l) l = dev_desc[0];
memcpy(buffer, dev_desc, l);
urb->buffer_actual = l;
urb->status = USB_VHCI_STATUS_SUCCESS;
break;
case 2:
puts("CONF_DESC");
if(conf_desc[2] < l) l = conf_desc[2];
memcpy(buffer, conf_desc, l);
urb->buffer_actual = l;
urb->status = USB_VHCI_STATUS_SUCCESS;
break;
case 3:
devlog(" Reading string %d\n", urb->wValue & 0xff);
switch(urb->wValue & 0xff)
{
case 0:
if(str0_desc[0] < l) l = str0_desc[0];
memcpy(buffer, str0_desc, l);
urb->buffer_actual = l;
urb->status = USB_VHCI_STATUS_SUCCESS;
break;
case 1:
if(str1_desc[0] < l) l = str1_desc[0];
memcpy(buffer, str1_desc, l);
urb->buffer_actual = l;
urb->status = USB_VHCI_STATUS_SUCCESS;
break;
default:
devlog(" Trying to read unknown string: %d\n",urb->wValue & 0xff);
urb->status = USB_VHCI_STATUS_STALL;
break;
}
break;
default:
devlog(" UNKNOWN: wValue=%d (%d)\n",urb->wValue, urb->wValue >> 8);
urb->status = USB_VHCI_STATUS_STALL;
break;
}
}
else
{
devlog("OTHER bmRequestType %x bRequest %x\n", rt, r);
urb->status = USB_VHCI_STATUS_STALL;
}
以下是我在设备启动时获得的usbmon日志的一个片段:
ffff880510727900 266671312 S Bi:5:002:2 -115 128 <
ffff880510727f00 266671315 C Bi:5:002:2 0 0
ffff880510727f00 266671316 S Bi:5:002:2 -115 128 <
ffff880510727cc0 266671319 C Ii:5:002:3 0:8 0
ffff880510727cc0 266671321 S Ii:5:002:3 -115:8 64 <
ffff880514d80900 266671323 S Co:5:002:0 s 21 22 0000 0000 0000 0
ffff880510727780 266671324 C Bi:5:002:2 0 0
ffff880510727780 266671325 S Bi:5:002:2 -115 128 <
ffff8805101096c0 266671329 C Bi:5:002:2 0 0
ffff8805101096c0 266671333 S Bi:5:002:2 -115 128 <
ffff8805107273c0 266671339 C Bi:5:002:2 0 0
ffff8805107273c0 266671344 S Bi:5:002:2 -115 128 <
ffff880510109b40 266671348 C Bi:5:002:2 0 0
ffff880510109b40 266671350 S Bi:5:002:2 -115 128 <
ffff880510109000 266671354 C Bi:5:002:2 0 0
ffff880510109000 266671357 S Bi:5:002:2 -115 128 <
ffff880510727d80 266671360 C Bi:5:002:2 0 0
ffff880510727d80 266671361 S Bi:5:002:2 -115 128 <
ffff880510109a80 266671363 C Bi:5:002:2 0 0
ffff880510109c00 266671370 C Bi:5:002:2 0 0
...
ffff880510727900 266671312 S Bi:5:002:2-115 128<
FFFF8805107207F00 266671315 C Bi:5:002:2 0
FFFF8805107207F00 266671316 S Bi:5:002:2-115 128<
ffff880510727cc0 266671319 C Ii:5:002:30:80
ffff880510727cc0 266671321 S Ii:5:002:3-115:8 64<
ffff880514d80900 266671323 S Co:5:002:0 S 21 22 0000 0
FFFF8805107280266671324 C Bi:5:002:20
FFFF8805107280266671325S Bi:5:002:2-115 128<
ffff8805101096c0 266671329 C Bi:5:002:20
ffff8805101096c0 266671333 S Bi:5:002:2-115 128<
ffff8805107273c0 266671339 C Bi:5:002:20
ffff8805107273c0 266671344 S Bi:5:002:2-115 128<
ffff880510109b40 266671348 C Bi:5:002:20
ffff880510109b40 266671350 S Bi:5:002:2-115 128<
ffff880510109000 266671354 C Bi:5:002:20
ffff880510109000 266671357 S Bi:5:002:2-115 128<
ffff880510727d80 266671360 C Bi:5:002:20
ffff880510727d80 266671361 S Bi:5:002:2-115 128<
ffff880510109a80 266671363 C Bi:5:002:20
ffff880510109c00 266671370 C Bi:5:002:20
...
所以,这就是我被困的地方。我有一个几乎可以正常工作的设备,但是大量的传输基本上阻塞了我的系统,使它变得毫无用处。任何帮助或信息将不胜感激 我现在似乎已经能够解决我的大部分问题,而问题确实是我对事件的反应不正确 在对实际设备的usbmon输出进行了更详细的分析之后,我注意到它用-enoint响应多余的中断传输,而我用0响应(即成功)。进一步深入研究usb vhci代码发现,该错误代码对应于usb_vhci_STATUS_Cancelled,一旦我开始对此做出响应,我的设备中的行为与真实设备中的行为相同。基本上,我将其添加到流程图的非控制部分:
/* if we have a INTERRUPT transfer */
if (usb_vhci_is_int(urb->type)) {
urb->status = USB_VHCI_STATUS_CANCELED;
return;
}
不过,我还没有完全脱离困境。我注意到同样的事情似乎也适用于批量转账;在启动过程中(安装完成后立即停止),我得到了大量的数据,但对于真实设备来说,情况似乎并非如此,而真实设备再次对这些(多余的)传输做出响应,使用-enoint。我试过这样做,但似乎效果很好。额外的传输确实会停止,其行为与实际设备一样,但不幸的是,这也会导致我的设备无法将数据发送回客户端。我在处理代码时修改了我的批量,如下所示:
/* if we have a BULK IN transfer */
if (usb_vhci_is_bulk(urb->type) && usb_vhci_is_in(urb->epadr)) {
if (last_command) {
// send version
memcpy(urb->buffer, VERSION_STR, strlen(VERSION_STR));
urb->buffer_actual = strlen(VERSION_STR);
last_command = 0;
urb->status = USB_VHCI_STATUS_SUCCESS;
} else {
urb->status = USB_VHCI_STATUS_CANCELED;
}
return;
}
我认为这应该是可行的,也就是说,如果我在上一次批量输出传输中收到一个命令,我应该能够使用in传输进行响应(就像我一直在做的那样),如果没有命令,我就使用-enoint进行响应。由于某种原因,这不起作用,但我不知道为什么
关于来自真实设备的跟踪,我注意到了另一件事:虽然它确实使用-enoint响应这些批量传输,但它们在收到请求后10秒(!)多时间内发送响应!我不知道这是怎么回事,但如果有人有什么想法,我将不胜感激。谢谢你的帖子。我刚刚开始做类似的工作,必须将VHCI代码移植到内核4.15,处理来自主机的所有USB请求,并在内核空间而不是用户空间模拟虚拟设备。您是否能够使用libusb_vhci代码处理所有USB请求?我想知道我的计划是否可行。@luke:我已经有一段时间没做了,但据我记忆所及,我可以处理所有的请求。
/* if we have a BULK IN transfer */
if (usb_vhci_is_bulk(urb->type) && usb_vhci_is_in(urb->epadr)) {
if (last_command) {
// send version
memcpy(urb->buffer, VERSION_STR, strlen(VERSION_STR));
urb->buffer_actual = strlen(VERSION_STR);
last_command = 0;
urb->status = USB_VHCI_STATUS_SUCCESS;
} else {
urb->status = USB_VHCI_STATUS_CANCELED;
}
return;
}