前面我们在https://mp.weixin.qq.com/s/s3uC-SHaVcXWAZK1sK6EZw?token=6568576&lang=zh_CN
《WSL2中配置支持UVC》一文中,分享了使用usbipd绑定USB到WSL中,并重新编译WSL2内核以支持UVC。基于此,我们能随意修改内核重新编译加载新的内核运行,这样我们就可以方便的在PC上进行USB相关的调试了。上一篇分享了调试UVC,这一篇再来分享一个调试CDC的案例。
案例的问题是一个CDC设备在Windows下能够工作,在Linux下不能工作,下面就来分享调试过程。
老规矩,第一步是找到对应的驱动的位置打开调试信息,增加自己的调试信息跟踪数据和执行流。CDC的驱动代码位于drivers/usb/class/cdc-acm.c。
我们把最开头的
#undef DEBUG
#undef VERBOSE_DEBUG
改为
#define DEBUG
#define VERBOSE_DEBUG
再按照前文介绍的,重新编译内核并替换。
编译内核
sudo make -j$(nproc) KCONFIG_CONFIG=.config && sudo make modules_install -j$(nproc) && sudo make install -j$(nproc)
复制内核到windows下,复制到自己的WSL的安装目录即可。
sudo cp vmlinux /mnt/d/WSL/Ubuntu2204-221101/Ubuntu_2204.1.7.0_x64
修改windows下用户目录下的.wslconfig文件(以下去windows下操作也可以)
nano /mnt/c/Users/qinyu/.wslconfig
添加如下内容(ctrl+o是保存,ctrl+x是退出)
[wsl2]
kernel=D:\\WSL\\Ubuntu2204-221101\\Ubuntu_2204.1.7.0_x64\\vmlinux
关闭所有WSL终端和程序
wsl --shutdown
重新打开WSL终端
uname -r -a查看编译时间是否更新,如果没有可以重启下电脑试一下。
sudo dmesg -c清除信息
usbipd bind -b x-x
usbipd attach -w -b x-x 链接设备到WSL
此时WSL中dmesg查看
[ 447.668696] vhci_hcd vhci_hcd.0: pdev(0) rhport(0) sockfd(3)
[ 447.670050] vhci_hcd vhci_hcd.0: devid(196610) speed(3) speed_str(high-speed)
[ 447.672172] vhci_hcd vhci_hcd.0: Device attached
[ 448.020519] usb 1-1: new high-speed USB device number 2 using vhci_hcd
[ 448.170543] usb 1-1: SetAddress Request (2) to port 0
[ 448.208695] usb 1-1: New USB device found, idVendor=1993, idProduct=0101, bcdDevice= 1.00
[ 448.209585] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 448.210382] usb 1-1: Product: xxxxSpecificDevice
[ 448.210935] usb 1-1: Manufacturer: xxxx Team.
[ 448.211560] usb 1-1: SerialNumber: 0.1
[ 448.213988] cdc_acm: probe of 1-1:1.0 failed with error -22
qinyunti@qinyunti:~$
先找到驱动入口
acm_probe
添加打印信息看是否执行到这里
我们看到执行到了这里
如果没有打印可以尝试修改printk的debug输出等级。echo 8 > /proc/sys/kernel/printk,
上面打印信息可以看到,后面打印了错误
那么我们就继续添加打印信息,跟踪到底是哪里开始出错的。
static int acm_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_cdc_union_desc *union_header = NULL;
struct usb_cdc_call_mgmt_descriptor *cmgmd = NULL;
unsigned char *buffer = intf->altsetting->extra;
int buflen = intf->altsetting->extralen;
struct usb_interface *control_interface;
struct usb_interface *data_interface;
struct usb_endpoint_descriptor *epctrl = NULL;
struct usb_endpoint_descriptor *epread = NULL;
struct usb_endpoint_descriptor *epwrite = NULL;
struct usb_device *usb_dev = interface_to_usbdev(intf);
struct usb_cdc_parsed_header h;
struct acm *acm;
int minor;
int ctrlsize, readsize;
u8 *buf;
int call_intf_num = -1;
int data_intf_num = -1;
unsigned long quirks;
int num_rx_buf;
int i;
int combined_interfaces = 0;
struct device *tty_dev;
int rv = -ENOMEM;
int res;
dev_dbg(&intf->dev,"acm_probe ...\n");
/* normal quirks */
quirks = (unsigned long)id->driver_info;
if (quirks == IGNORE_DEVICE){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
return -ENODEV;
}
memset(&h, 0x00, sizeof(struct usb_cdc_parsed_header));
num_rx_buf = (quirks == SINGLE_RX_URB) ? 1 : ACM_NR;
/* handle quirks deadly to normal probing*/
if (quirks == NO_UNION_NORMAL) {
data_interface = usb_ifnum_to_if(usb_dev, 1);
control_interface = usb_ifnum_to_if(usb_dev, 0);
/* we would crash */
if (!data_interface || !control_interface){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
return -ENODEV;
}
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto skip_normal_probe;
}
/* normal probing*/
if (!buffer) {
dev_err(&intf->dev, "Weird descriptor references\n");
return -EINVAL;
}
if (!buflen) {
if (intf->cur_altsetting->endpoint &&
intf->cur_altsetting->endpoint->extralen &&
intf->cur_altsetting->endpoint->extra) {
dev_dbg(&intf->dev,
"Seeking extra descriptors on endpoint\n");
buflen = intf->cur_altsetting->endpoint->extralen;
buffer = intf->cur_altsetting->endpoint->extra;
} else {
dev_err(&intf->dev,
"Zero length descriptor references\n");
return -EINVAL;
}
}
cdc_parse_cdc_header(&h, intf, buffer, buflen);
union_header = h.usb_cdc_union_desc;
cmgmd = h.usb_cdc_call_mgmt_descriptor;
if (cmgmd){
call_intf_num = cmgmd->bDataInterface;
dev_dbg(&intf->dev,"%s %d call_intf_num=%d\n",__FILE__,__LINE__,call_intf_num);
}
if (!union_header) {
if (intf->cur_altsetting->desc.bNumEndpoints == 3) {
dev_dbg(&intf->dev, "No union descriptor, assuming single interface\n");
combined_interfaces = 1;
control_interface = data_interface = intf;
goto look_for_collapsed_interface;
} else if (call_intf_num > 0) {
dev_dbg(&intf->dev, "No union descriptor, using call management descriptor\n");
data_intf_num = call_intf_num;
data_interface = usb_ifnum_to_if(usb_dev, data_intf_num);
control_interface = intf;
} else {
dev_dbg(&intf->dev, "No union descriptor, giving up\n");
return -ENODEV;
}
} else {
int class = -1;
data_intf_num = union_header->bSlaveInterface0;
control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0);
data_interface = usb_ifnum_to_if(usb_dev, data_intf_num);
if (control_interface){
class = control_interface->cur_altsetting->desc.bInterfaceClass;
dev_dbg(&intf->dev,"%s %d class=%d\n",__FILE__,__LINE__,class);
}
if (class != USB_CLASS_COMM && class != USB_CLASS_CDC_DATA) {
dev_dbg(&intf->dev, "Broken union descriptor, assuming single interface\n");
combined_interfaces = 1;
control_interface = data_interface = intf;
goto look_for_collapsed_interface;
}
}
if (!control_interface || !data_interface) {
dev_dbg(&intf->dev, "no interfaces\n");
return -ENODEV;
}
if (data_intf_num != call_intf_num)
dev_dbg(&intf->dev, "Separate call control interface. That is not fully supported.\n");
if (control_interface == data_interface) {
/* some broken devices designed for windows work this way */
dev_warn(&intf->dev,"Control and data interfaces are not separated!\n");
combined_interfaces = 1;
/* a popular other OS doesn't use it */
quirks |= NO_CAP_LINE;
if (data_interface->cur_altsetting->desc.bNumEndpoints != 3) {
dev_err(&intf->dev, "This needs exactly 3 endpoints\n");
return -EINVAL;
}
look_for_collapsed_interface:
res = usb_find_common_endpoints(data_interface->cur_altsetting,
&epread, &epwrite, &epctrl, NULL);
if (res)
return res;
goto made_compressed_probe;
}
skip_normal_probe:
/*workaround for switched interfaces */
if (data_interface->cur_altsetting->desc.bInterfaceClass != USB_CLASS_CDC_DATA) {
if (control_interface->cur_altsetting->desc.bInterfaceClass == USB_CLASS_CDC_DATA) {
dev_dbg(&intf->dev,
"Your device has switched interfaces.\n");
swap(control_interface, data_interface);
} else {
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
return -EINVAL;
}
}
/* Accept probe requests only for the control interface */
if (!combined_interfaces && intf != control_interface){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
return -ENODEV;
}
if (data_interface->cur_altsetting->desc.bNumEndpoints < 2 ||
control_interface->cur_altsetting->desc.bNumEndpoints == 0){
dev_dbg(&intf->dev,"%s %d %d %d\n",__FILE__,__LINE__,data_interface->cur_altsetting->desc.bNumEndpoints,control_interface->cur_altsetting->desc.bNumEndpoints);
return -EINVAL;
}
epctrl = &control_interface->cur_altsetting->endpoint[0].desc;
epread = &data_interface->cur_altsetting->endpoint[0].desc;
epwrite = &data_interface->cur_altsetting->endpoint[1].desc;
/* workaround for switched endpoints */
if (!usb_endpoint_dir_in(epread)) {
/* descriptors are swapped */
dev_dbg(&intf->dev,
"The data interface has switched endpoints\n");
swap(epread, epwrite);
}
made_compressed_probe:
dev_dbg(&intf->dev, "interfaces are valid\n");
acm = kzalloc(sizeof(struct acm), GFP_KERNEL);
if (!acm){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
return -ENOMEM;
}
tty_port_init(&acm->port);
acm->port.ops = &acm_port_ops;
ctrlsize = usb_endpoint_maxp(epctrl);
readsize = usb_endpoint_maxp(epread) *
(quirks == SINGLE_RX_URB ? 1 : 2);
acm->combined_interfaces = combined_interfaces;
acm->writesize = usb_endpoint_maxp(epwrite) * 20;
acm->control = control_interface;
acm->data = data_interface;
usb_get_intf(acm->control); /* undone in destruct() */
minor = acm_alloc_minor(acm);
if (minor < 0) {
acm->minor = ACM_MINOR_INVALID;
dev_dbg(&intf->dev,"%s %d minor=%d\n",__FILE__,__LINE__,minor);
goto err_put_port;
}
acm->minor = minor;
acm->dev = usb_dev;
if (h.usb_cdc_acm_descriptor)
acm->ctrl_caps = h.usb_cdc_acm_descriptor->bmCapabilities;
if (quirks & NO_CAP_LINE)
acm->ctrl_caps &= ~USB_CDC_CAP_LINE;
acm->ctrlsize = ctrlsize;
acm->readsize = readsize;
acm->rx_buflimit = num_rx_buf;
INIT_DELAYED_WORK(&acm->dwork, acm_softint);
init_waitqueue_head(&acm->wioctl);
spin_lock_init(&acm->write_lock);
spin_lock_init(&acm->read_lock);
mutex_init(&acm->mutex);
if (usb_endpoint_xfer_int(epread)) {
acm->bInterval = epread->bInterval;
acm->in = usb_rcvintpipe(usb_dev, epread->bEndpointAddress);
} else {
acm->in = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress);
}
if (usb_endpoint_xfer_int(epwrite))
acm->out = usb_sndintpipe(usb_dev, epwrite->bEndpointAddress);
else
acm->out = usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress);
init_usb_anchor(&acm->delayed);
acm->quirks = quirks;
buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma);
if (!buf)
goto err_put_port;
acm->ctrl_buffer = buf;
if (acm_write_buffers_alloc(acm) < 0){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto err_free_ctrl_buffer;
}
acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL);
if (!acm->ctrlurb){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto err_free_write_buffers;
}
for (i = 0; i < num_rx_buf; i++) {
struct acm_rb *rb = &(acm->read_buffers[i]);
struct urb *urb;
rb->base = usb_alloc_coherent(acm->dev, readsize, GFP_KERNEL,
&rb->dma);
if (!rb->base){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto err_free_read_urbs;
}
rb->index = i;
rb->instance = acm;
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto err_free_read_urbs;
}
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
urb->transfer_dma = rb->dma;
if (usb_endpoint_xfer_int(epread))
usb_fill_int_urb(urb, acm->dev, acm->in, rb->base,
acm->readsize,
acm_read_bulk_callback, rb,
acm->bInterval);
else
usb_fill_bulk_urb(urb, acm->dev, acm->in, rb->base,
acm->readsize,
acm_read_bulk_callback, rb);
acm->read_urbs[i] = urb;
__set_bit(i, &acm->read_urbs_free);
}
for (i = 0; i < ACM_NW; i++) {
struct acm_wb *snd = &(acm->wb[i]);
snd->urb = usb_alloc_urb(0, GFP_KERNEL);
if (!snd->urb)
goto err_free_write_urbs;
if (usb_endpoint_xfer_int(epwrite))
usb_fill_int_urb(snd->urb, usb_dev, acm->out,
NULL, acm->writesize, acm_write_bulk, snd, epwrite->bInterval);
else
usb_fill_bulk_urb(snd->urb, usb_dev, acm->out,
NULL, acm->writesize, acm_write_bulk, snd);
snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
if (quirks & SEND_ZERO_PACKET)
snd->urb->transfer_flags |= URB_ZERO_PACKET;
snd->instance = acm;
}
usb_set_intfdata(intf, acm);
i = device_create_file(&intf->dev, &dev_attr_bmCapabilities);
if (i < 0){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto err_free_write_urbs;
}
if (h.usb_cdc_country_functional_desc) { /* export the country data */
struct usb_cdc_country_functional_desc * cfd =
h.usb_cdc_country_functional_desc;
acm->country_codes = kmalloc(cfd->bLength - 4, GFP_KERNEL);
if (!acm->country_codes){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto skip_countries;
}
acm->country_code_size = cfd->bLength - 4;
memcpy(acm->country_codes, (u8 *)&cfd->wCountyCode0,
cfd->bLength - 4);
acm->country_rel_date = cfd->iCountryCodeRelDate;
i = device_create_file(&intf->dev, &dev_attr_wCountryCodes);
if (i < 0) {
kfree(acm->country_codes);
acm->country_codes = NULL;
acm->country_code_size = 0;
goto skip_countries;
}
i = device_create_file(&intf->dev,
&dev_attr_iCountryCodeRelDate);
if (i < 0) {
device_remove_file(&intf->dev, &dev_attr_wCountryCodes);
kfree(acm->country_codes);
acm->country_codes = NULL;
acm->country_code_size = 0;
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto skip_countries;
}
}
skip_countries:
usb_fill_int_urb(acm->ctrlurb, usb_dev,
usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress),
acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm,
/* works around buggy devices */
epctrl->bInterval ? epctrl->bInterval : 16);
acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
acm->ctrlurb->transfer_dma = acm->ctrl_dma;
acm->notification_buffer = NULL;
acm->nb_index = 0;
acm->nb_size = 0;
acm->line.dwDTERate = cpu_to_le32(9600);
acm->line.bDataBits = 8;
acm_set_line(acm, &acm->line);
if (!acm->combined_interfaces) {
rv = usb_driver_claim_interface(&acm_driver, data_interface, acm);
if (rv){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto err_remove_files;
}
}
tty_dev = tty_port_register_device(&acm->port, acm_tty_driver, minor,
&control_interface->dev);
if (IS_ERR(tty_dev)) {
rv = PTR_ERR(tty_dev);
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto err_release_data_interface;
}
if (quirks & CLEAR_HALT_CONDITIONS) {
usb_clear_halt(usb_dev, acm->in);
usb_clear_halt(usb_dev, acm->out);
}
dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor);
return 0;
err_release_data_interface:
if (!acm->combined_interfaces) {
/* Clear driver data so that disconnect() returns early. */
usb_set_intfdata(data_interface, NULL);
usb_driver_release_interface(&acm_driver, data_interface);
}
err_remove_files:
if (acm->country_codes) {
device_remove_file(&acm->control->dev,
&dev_attr_wCountryCodes);
device_remove_file(&acm->control->dev,
&dev_attr_iCountryCodeRelDate);
}
device_remove_file(&acm->control->dev, &dev_attr_bmCapabilities);
err_free_write_urbs:
for (i = 0; i < ACM_NW; i++)
usb_free_urb(acm->wb[i].urb);
err_free_read_urbs:
for (i = 0; i < num_rx_buf; i++)
usb_free_urb(acm->read_urbs[i]);
acm_read_buffers_free(acm);
usb_free_urb(acm->ctrlurb);
err_free_write_buffers:
acm_write_buffers_free(acm);
err_free_ctrl_buffer:
usb_free_coherent(usb_dev, ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);
err_put_port:
tty_port_put(&acm->port);
return rv;
}
此时再来看打印信息
[ 16.623759] vhci_hcd vhci_hcd.0: pdev(0) rhport(0) sockfd(3)
[ 16.624646] vhci_hcd vhci_hcd.0: devid(196610) speed(3) speed_str(high-speed)
[ 16.625829] vhci_hcd vhci_hcd.0: Device attached
[ 16.970553] usb 1-1: new high-speed USB device number 2 using vhci_hcd
[ 17.120556] usb 1-1: SetAddress Request (2) to port 0
[ 17.157661] usb 1-1: New USB device found, idVendor=1993, idProduct=0101, bcdDevice= 1.00
[ 17.158611] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 17.159421] usb 1-1: Product: xxxxSpecificDevice
[ 17.159979] usb 1-1: Manufacturer: xxxx Team.
[ 17.160426] usb 1-1: SerialNumber: 0.1
[ 17.162352] cdc_acm 1-1:1.0: acm_probe ...
[ 17.162842] cdc_acm 1-1:1.0: drivers/usb/class/cdc-acm.c 1233 call_intf_num=1
[ 17.163594] cdc_acm 1-1:1.0: drivers/usb/class/cdc-acm.c 1260 class=2
[ 17.164254] cdc_acm 1-1:1.0: drivers/usb/class/cdc-acm.c 1320 2 0
[ 17.164876] cdc_acm: probe of 1-1:1.0 failed with error -22
qinyunti:~$
可以看到正是如下位置,EINVAL返回值就是22
此时data_interface->cur_altsetting->desc.bNumEndpoints为0
control_interface->cur_altsetting->desc.bNumEndpoints为0,也就是控制接口没有端点。
if (data_interface->cur_altsetting->desc.bNumEndpoints < 2 ||
control_interface->cur_altsetting->desc.bNumEndpoints == 0){
dev_dbg(&intf->dev,"%s %d %d %d\n",__FILE__,__LINE__,data_interface->cur_altsetting->desc.bNumEndpoints,control_interface->cur_altsetting->desc.bNumEndpoints);
return -EINVAL;
}
如果可以修改内核,简单粗暴,直接修改如下
将控制接口端点相关的处理加上条件判断,有控制接口端点才处理,否则不处理
static int acm_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_cdc_union_desc *union_header = NULL;
struct usb_cdc_call_mgmt_descriptor *cmgmd = NULL;
unsigned char *buffer = intf->altsetting->extra;
int buflen = intf->altsetting->extralen;
struct usb_interface *control_interface;
struct usb_interface *data_interface;
struct usb_endpoint_descriptor *epctrl = NULL;
struct usb_endpoint_descriptor *epread = NULL;
struct usb_endpoint_descriptor *epwrite = NULL;
struct usb_device *usb_dev = interface_to_usbdev(intf);
struct usb_cdc_parsed_header h;
struct acm *acm;
int minor;
int ctrlsize, readsize;
u8 *buf;
int call_intf_num = -1;
int data_intf_num = -1;
unsigned long quirks;
int num_rx_buf;
int i;
int combined_interfaces = 0;
struct device *tty_dev;
int rv = -ENOMEM;
int res;
dev_dbg(&intf->dev,"acm_probe ...\n");
/* normal quirks */
quirks = (unsigned long)id->driver_info;
if (quirks == IGNORE_DEVICE){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
return -ENODEV;
}
memset(&h, 0x00, sizeof(struct usb_cdc_parsed_header));
num_rx_buf = (quirks == SINGLE_RX_URB) ? 1 : ACM_NR;
/* handle quirks deadly to normal probing*/
if (quirks == NO_UNION_NORMAL) {
data_interface = usb_ifnum_to_if(usb_dev, 1);
control_interface = usb_ifnum_to_if(usb_dev, 0);
/* we would crash */
if (!data_interface || !control_interface){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
return -ENODEV;
}
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto skip_normal_probe;
}
/* normal probing*/
if (!buffer) {
dev_err(&intf->dev, "Weird descriptor references\n");
return -EINVAL;
}
if (!buflen) {
if (intf->cur_altsetting->endpoint &&
intf->cur_altsetting->endpoint->extralen &&
intf->cur_altsetting->endpoint->extra) {
dev_dbg(&intf->dev,
"Seeking extra descriptors on endpoint\n");
buflen = intf->cur_altsetting->endpoint->extralen;
buffer = intf->cur_altsetting->endpoint->extra;
} else {
dev_err(&intf->dev,
"Zero length descriptor references\n");
return -EINVAL;
}
}
cdc_parse_cdc_header(&h, intf, buffer, buflen);
union_header = h.usb_cdc_union_desc;
cmgmd = h.usb_cdc_call_mgmt_descriptor;
if (cmgmd){
call_intf_num = cmgmd->bDataInterface;
dev_dbg(&intf->dev,"%s %d call_intf_num=%d\n",__FILE__,__LINE__,call_intf_num);
}
if (!union_header) {
if (intf->cur_altsetting->desc.bNumEndpoints == 3) {
dev_dbg(&intf->dev, "No union descriptor, assuming single interface\n");
combined_interfaces = 1;
control_interface = data_interface = intf;
goto look_for_collapsed_interface;
} else if (call_intf_num > 0) {
dev_dbg(&intf->dev, "No union descriptor, using call management descriptor\n");
data_intf_num = call_intf_num;
data_interface = usb_ifnum_to_if(usb_dev, data_intf_num);
control_interface = intf;
} else {
dev_dbg(&intf->dev, "No union descriptor, giving up\n");
return -ENODEV;
}
} else {
int class = -1;
data_intf_num = union_header->bSlaveInterface0;
control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0);
data_interface = usb_ifnum_to_if(usb_dev, data_intf_num);
if (control_interface){
class = control_interface->cur_altsetting->desc.bInterfaceClass;
dev_dbg(&intf->dev,"%s %d class=%d\n",__FILE__,__LINE__,class);
}
if (class != USB_CLASS_COMM && class != USB_CLASS_CDC_DATA) {
dev_dbg(&intf->dev, "Broken union descriptor, assuming single interface\n");
combined_interfaces = 1;
control_interface = data_interface = intf;
goto look_for_collapsed_interface;
}
}
if (!control_interface || !data_interface) {
dev_dbg(&intf->dev, "no interfaces\n");
return -ENODEV;
}
if (data_intf_num != call_intf_num)
dev_dbg(&intf->dev, "Separate call control interface. That is not fully supported.\n");
if (control_interface == data_interface) {
/* some broken devices designed for windows work this way */
dev_warn(&intf->dev,"Control and data interfaces are not separated!\n");
combined_interfaces = 1;
/* a popular other OS doesn't use it */
quirks |= NO_CAP_LINE;
if (data_interface->cur_altsetting->desc.bNumEndpoints != 3) {
dev_err(&intf->dev, "This needs exactly 3 endpoints\n");
return -EINVAL;
}
look_for_collapsed_interface:
res = usb_find_common_endpoints(data_interface->cur_altsetting,
&epread, &epwrite, &epctrl, NULL);
if (res)
return res;
goto made_compressed_probe;
}
skip_normal_probe:
/*workaround for switched interfaces */
if (data_interface->cur_altsetting->desc.bInterfaceClass != USB_CLASS_CDC_DATA) {
if (control_interface->cur_altsetting->desc.bInterfaceClass == USB_CLASS_CDC_DATA) {
dev_dbg(&intf->dev,
"Your device has switched interfaces.\n");
swap(control_interface, data_interface);
} else {
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
return -EINVAL;
}
}
/* Accept probe requests only for the control interface */
if (!combined_interfaces && intf != control_interface){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
return -ENODEV;
}
if (data_interface->cur_altsetting->desc.bNumEndpoints < 2){
dev_dbg(&intf->dev,"%s %d %d %d\n",__FILE__,__LINE__,data_interface->cur_altsetting->desc.bNumEndpoints,control_interface->cur_altsetting->desc.bNumEndpoints);
return -EINVAL;
}
if( control_interface->cur_altsetting->desc.bNumEndpoints != 0){
epctrl = &control_interface->cur_altsetting->endpoint[0].desc;
}
epread = &data_interface->cur_altsetting->endpoint[0].desc;
epwrite = &data_interface->cur_altsetting->endpoint[1].desc;
/* workaround for switched endpoints */
if (!usb_endpoint_dir_in(epread)) {
/* descriptors are swapped */
dev_dbg(&intf->dev,
"The data interface has switched endpoints\n");
swap(epread, epwrite);
}
made_compressed_probe:
dev_dbg(&intf->dev, "interfaces are valid\n");
acm = kzalloc(sizeof(struct acm), GFP_KERNEL);
if (!acm){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
return -ENOMEM;
}
tty_port_init(&acm->port);
acm->port.ops = &acm_port_ops;
if(epctrl != NULL){
ctrlsize = usb_endpoint_maxp(epctrl);
}
readsize = usb_endpoint_maxp(epread) *
(quirks == SINGLE_RX_URB ? 1 : 2);
acm->combined_interfaces = combined_interfaces;
acm->writesize = usb_endpoint_maxp(epwrite) * 20;
acm->control = control_interface;
acm->data = data_interface;
usb_get_intf(acm->control); /* undone in destruct() */
minor = acm_alloc_minor(acm);
if (minor < 0) {
acm->minor = ACM_MINOR_INVALID;
dev_dbg(&intf->dev,"%s %d minor=%d\n",__FILE__,__LINE__,minor);
goto err_put_port;
}
acm->minor = minor;
acm->dev = usb_dev;
if (h.usb_cdc_acm_descriptor)
acm->ctrl_caps = h.usb_cdc_acm_descriptor->bmCapabilities;
if (quirks & NO_CAP_LINE)
acm->ctrl_caps &= ~USB_CDC_CAP_LINE;
acm->ctrlsize = ctrlsize;
acm->readsize = readsize;
acm->rx_buflimit = num_rx_buf;
INIT_DELAYED_WORK(&acm->dwork, acm_softint);
init_waitqueue_head(&acm->wioctl);
spin_lock_init(&acm->write_lock);
spin_lock_init(&acm->read_lock);
mutex_init(&acm->mutex);
if (usb_endpoint_xfer_int(epread)) {
acm->bInterval = epread->bInterval;
acm->in = usb_rcvintpipe(usb_dev, epread->bEndpointAddress);
} else {
acm->in = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress);
}
if (usb_endpoint_xfer_int(epwrite))
acm->out = usb_sndintpipe(usb_dev, epwrite->bEndpointAddress);
else
acm->out = usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress);
init_usb_anchor(&acm->delayed);
acm->quirks = quirks;
if(epctrl != NULL){
buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma);
if (!buf)
goto err_put_port;
acm->ctrl_buffer = buf;
}
if (acm_write_buffers_alloc(acm) < 0){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto err_free_ctrl_buffer;
}
if(epctrl != NULL){
acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL);
if (!acm->ctrlurb){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto err_free_write_buffers;
}
}
for (i = 0; i < num_rx_buf; i++) {
struct acm_rb *rb = &(acm->read_buffers[i]);
struct urb *urb;
rb->base = usb_alloc_coherent(acm->dev, readsize, GFP_KERNEL,
&rb->dma);
if (!rb->base){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto err_free_read_urbs;
}
rb->index = i;
rb->instance = acm;
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto err_free_read_urbs;
}
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
urb->transfer_dma = rb->dma;
if (usb_endpoint_xfer_int(epread))
usb_fill_int_urb(urb, acm->dev, acm->in, rb->base,
acm->readsize,
acm_read_bulk_callback, rb,
acm->bInterval);
else
usb_fill_bulk_urb(urb, acm->dev, acm->in, rb->base,
acm->readsize,
acm_read_bulk_callback, rb);
acm->read_urbs[i] = urb;
__set_bit(i, &acm->read_urbs_free);
}
for (i = 0; i < ACM_NW; i++) {
struct acm_wb *snd = &(acm->wb[i]);
snd->urb = usb_alloc_urb(0, GFP_KERNEL);
if (!snd->urb)
goto err_free_write_urbs;
if (usb_endpoint_xfer_int(epwrite))
usb_fill_int_urb(snd->urb, usb_dev, acm->out,
NULL, acm->writesize, acm_write_bulk, snd, epwrite->bInterval);
else
usb_fill_bulk_urb(snd->urb, usb_dev, acm->out,
NULL, acm->writesize, acm_write_bulk, snd);
snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
if (quirks & SEND_ZERO_PACKET)
snd->urb->transfer_flags |= URB_ZERO_PACKET;
snd->instance = acm;
}
usb_set_intfdata(intf, acm);
i = device_create_file(&intf->dev, &dev_attr_bmCapabilities);
if (i < 0){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto err_free_write_urbs;
}
if (h.usb_cdc_country_functional_desc) { /* export the country data */
struct usb_cdc_country_functional_desc * cfd =
h.usb_cdc_country_functional_desc;
acm->country_codes = kmalloc(cfd->bLength - 4, GFP_KERNEL);
if (!acm->country_codes){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto skip_countries;
}
acm->country_code_size = cfd->bLength - 4;
memcpy(acm->country_codes, (u8 *)&cfd->wCountyCode0,
cfd->bLength - 4);
acm->country_rel_date = cfd->iCountryCodeRelDate;
i = device_create_file(&intf->dev, &dev_attr_wCountryCodes);
if (i < 0) {
kfree(acm->country_codes);
acm->country_codes = NULL;
acm->country_code_size = 0;
goto skip_countries;
}
i = device_create_file(&intf->dev,
&dev_attr_iCountryCodeRelDate);
if (i < 0) {
device_remove_file(&intf->dev, &dev_attr_wCountryCodes);
kfree(acm->country_codes);
acm->country_codes = NULL;
acm->country_code_size = 0;
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto skip_countries;
}
}
skip_countries:
if(epctrl != NULL){
usb_fill_int_urb(acm->ctrlurb, usb_dev,
usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress),
acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm,
/* works around buggy devices */
epctrl->bInterval ? epctrl->bInterval : 16);
acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
acm->ctrlurb->transfer_dma = acm->ctrl_dma;
}
acm->notification_buffer = NULL;
acm->nb_index = 0;
acm->nb_size = 0;
acm->line.dwDTERate = cpu_to_le32(9600);
acm->line.bDataBits = 8;
acm_set_line(acm, &acm->line);
if (!acm->combined_interfaces) {
rv = usb_driver_claim_interface(&acm_driver, data_interface, acm);
if (rv){
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto err_remove_files;
}
}
tty_dev = tty_port_register_device(&acm->port, acm_tty_driver, minor,
&control_interface->dev);
if (IS_ERR(tty_dev)) {
rv = PTR_ERR(tty_dev);
dev_dbg(&intf->dev,"%s %d\n",__FILE__,__LINE__);
goto err_release_data_interface;
}
if (quirks & CLEAR_HALT_CONDITIONS) {
usb_clear_halt(usb_dev, acm->in);
usb_clear_halt(usb_dev, acm->out);
}
dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor);
return 0;
err_release_data_interface:
if (!acm->combined_interfaces) {
/* Clear driver data so that disconnect() returns early. */
usb_set_intfdata(data_interface, NULL);
usb_driver_release_interface(&acm_driver, data_interface);
}
err_remove_files:
if (acm->country_codes) {
device_remove_file(&acm->control->dev,
&dev_attr_wCountryCodes);
device_remove_file(&acm->control->dev,
&dev_attr_iCountryCodeRelDate);
}
device_remove_file(&acm->control->dev, &dev_attr_bmCapabilities);
err_free_write_urbs:
for (i = 0; i < ACM_NW; i++)
usb_free_urb(acm->wb[i].urb);
err_free_read_urbs:
for (i = 0; i < num_rx_buf; i++)
usb_free_urb(acm->read_urbs[i]);
acm_read_buffers_free(acm);
usb_free_urb(acm->ctrlurb);
err_free_write_buffers:
acm_write_buffers_free(acm);
err_free_ctrl_buffer:
usb_free_coherent(usb_dev, ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);
err_put_port:
tty_port_put(&acm->port);
return rv;
}
修改后看到成功枚举了tty设备。
[ 16.706981] vhci_hcd vhci_hcd.0: pdev(0) rhport(0) sockfd(3)
[ 16.711093] vhci_hcd vhci_hcd.0: devid(196610) speed(3) speed_str(high-speed)
[ 16.712732] vhci_hcd vhci_hcd.0: Device attached
[ 17.060874] usb 1-1: new high-speed USB device number 2 using vhci_hcd
[ 17.210837] usb 1-1: SetAddress Request (2) to port 0
[ 17.249544] usb 1-1: New USB device found, idVendor=1993, idProduct=0101, bcdDevice= 1.00
[ 17.250420] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 17.251202] usb 1-1: Product: xxxxSpecificDevice
[ 17.251676] usb 1-1: Manufacturer: xxxx Team.
[ 17.252108] usb 1-1: SerialNumber: 0.1
[ 17.254065] cdc_acm 1-1:1.0: acm_probe ...
[ 17.254541] cdc_acm 1-1:1.0: drivers/usb/class/cdc-acm.c 1233 call_intf_num=1
[ 17.255145] cdc_acm 1-1:1.0: drivers/usb/class/cdc-acm.c 1260 class=2
[ 17.255641] cdc_acm 1-1:1.0: interfaces are valid
[ 17.257047] cdc_acm 1-1:1.0: acm_ctrl_msg - rq 0x20, val 0x0, len 0x7, result 7
[ 17.257922] cdc_acm 1-1:1.0: ttyACM0: USB ACM device
qinyunti@qinyunti:~$
qinyunti@qinyunti:~$ ls /dev/ttyACM0
/dev/ttyACM0
测试读写
此时会打印以下错误,找到错误的地方
[ 49.533354] hv_balloon: Max. dynamic memory size: 7618 MB
[ 750.950166] mini_init (145): drop_caches: 1
[ 1707.885065] cdc_acm 1-1:1.0: acm_port_activate - usb_submit_urb(ctrl irq) failed
qinyunti@qinyunti:~$
同样要加上判断
static int acm_port_activate(struct tty_port *port, struct tty_struct *tty)
{
struct acm *acm = container_of(port, struct acm, port);
int retval = -ENODEV;
int i;
mutex_lock(&acm->mutex);
if (acm->disconnected)
goto disconnected;
retval = usb_autopm_get_interface(acm->control);
if (retval)
goto error_get_interface;
/*
* FIXME: Why do we need this? Allocating 64K of physically contiguous
* memory is really nasty...
*/
set_bit(TTY_NO_WRITE_SPLIT, &tty->flags);
acm->control->needs_remote_wakeup = 1;
if(acm->ctrlurb){
acm->ctrlurb->dev = acm->dev;
retval = usb_submit_urb(acm->ctrlurb, GFP_KERNEL);
if (retval) {
dev_err(&acm->control->dev,
"%s - usb_submit_urb(ctrl irq) failed\n", __func__);
goto error_submit_urb;
}
}
acm_tty_set_termios(tty, NULL);
/*
* Unthrottle device in case the TTY was closed while throttled.
*/
clear_bit(ACM_THROTTLED, &acm->flags);
retval = acm_submit_read_urbs(acm, GFP_KERNEL);
if (retval)
goto error_submit_read_urbs;
usb_autopm_put_interface(acm->control);
mutex_unlock(&acm->mutex);
return 0;
error_submit_read_urbs:
for (i = 0; i < acm->rx_buflimit; i++)
usb_kill_urb(acm->read_urbs[i]);
usb_kill_urb(acm->ctrlurb);
error_submit_urb:
usb_autopm_put_interface(acm->control);
error_get_interface:
disconnected:
mutex_unlock(&acm->mutex);
return usb_translate_errors(retval);
}
使用minicom测试
sudo apt-get install minicom
sudo minicom -s 配置
sudo minicom -D /dev/ttyACM0 打开
设备那边收到数据原样返回,可以看到读写正常了。
Ctrl+A,再按Z进入界面,Q退出。
如果内核无法替换,则可以参考cdc_acm.c修改,单独再实现一个驱动,加载该驱动即可。
复制修改后的cdc_acm.c和cdc_acm.h
nano Makefile添加
CURDIR = $(shell pwd)
KVERS = $(shell uname -r)
obj-m := cdc-acm.o
all:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
ctrl+o保存
Ctrl+x退出
make编译
qinyunti@qinyunti:~/cdc_acm$ sudo insmod cdc-acm.ko
[sudo] password for qinyunti:
insmod: ERROR: could not insert module cdc-acm.ko: Device or resource busy
提示设备busy是因为设备号被占用了
cat /proc/devices可以看到
166 ttyACM
我们修改
还是报错,看dmesg是已经加载了对应的驱动
[ 42.783891] cdc_acm: loading out-of-tree module taints kernel.
[ 42.815334] Error: Driver 'cdc_acm' is already registered, aborting...
[ 49.595978] hv_balloon: Max. dynamic memory size: 7618 MB
[ 217.087763] Error: Driver 'cdc_acm' is already registered, aborting...
qinyunti@qinyunti:~/cdc_acm$
我们给驱动改个名字
cdc_acm改为cdc_usb
此时看到出现了设备
qinyunti@qinyunti:~/cdc_acm$ ls /dev/ttyUSB0
/dev/ttyUSB0
使用minicom测试串口OK
sudo minicom -D /dev/ttyUSB0
如果设备可以修改,则修改设备控制接口增加一个中断端点,并响应对应的请求即可。
而对于一些已经固化的驱动比如bootrom中的程序可能就改不了了,只能改PC端的驱动了。
以上主要是分享基于WSL2如何修改内核CDC相关代码,增加调试信息去进行调试,问题本身不是很重要,重点是分享分析过程。而分享的案例本身的问题是CDC设备没有提供控制接口的中断端点,Linux下驱动必须要求有,而Windows下没有也可以,这也看出Windows下兼容性容错性上做的更好。
解决方案根据具体情况而定,这也是嵌入式开发中经常会遇到的情况,兼容性问题,各种填坑问题等,往往解决一个问题可能需要山路十八弯才能解决,然而解决一个这样的问题却是价值的体现,这也是嵌入式开发中能力价值的体现。比如上面如果设备端驱动改不了了则只能改内核驱动,内核驱动改不了则只能加载自定义的驱动。