基于WSL2调试CDC设备-Windows下正常Linux下不工作案例分享

原创 嵌入式Lee 2025-01-09 16:04

.前言

前面我们在https://mp.weixin.qq.com/s/s3uC-SHaVcXWAZK1sK6EZw?token=6568576&lang=zh_CN

WSL2中配置支持UVC》一文中,分享了使用usbipd绑定USBWSL中,并重新编译WSL2内核以支持UVC。基于此,我们能随意修改内核重新编译加载新的内核运行,这样我们就可以方便的在PC上进行USB相关的调试了。上一篇分享了调试UVC,这一篇再来分享一个调试CDC的案例。

案例的问题是一个CDC设备在Windows下能够工作,Linux下不能工作,下面就来分享调试过程。

.使能CDC调试信息

老规矩,第一步是找到对应的驱动的位置打开调试信息,增加自己的调试信息跟踪数据和执行流。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

此时WSLdmesg查看

[  447.668696] vhci_hcd vhci_hcd.0pdev(0rhport(0sockfd(3)[  447.670050] vhci_hcd vhci_hcd.0devid(196610speed(3speed_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 -22qinyunti@qinyunti:~$

先找到驱动入口

acm_probe

添加打印信息看是否执行到这里

我们看到执行到了这里

如果没有打印可以尝试修改printkdebug输出等级。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, 0x00sizeof(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.0pdev(0rhport(0sockfd(3)[   16.624646] vhci_hcd vhci_hcd.0devid(196610speed(3speed_str(high-speed)[   16.625829] vhci_hcd vhci_hcd.0Device attached[   16.970553] usb 1-1new high-speed USB device number 2 using vhci_hcd[   17.120556] usb 1-1SetAddress Request (2) to port 0[   17.157661] usb 1-1New USB device found, idVendor=1993, idProduct=0101, bcdDevice= 1.00[   17.158611] usb 1-1New USB device stringsMfr=1Product=2SerialNumber=3[   17.159421] usb 1-1Product: xxxxSpecificDevice[   17.159979] usb 1-1Manufacturer: xxxx Team.[   17.160426] usb 1-1SerialNumber0.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.164876cdc_acm: probe of 1-1:1.0 failed with error -22qinyunti@qinyunti:~$

可以看到正是如下位置,EINVAL返回值就是22

此时data_interface->cur_altsetting->desc.bNumEndpoints0

control_interface->cur_altsetting->desc.bNumEndpoints0,也就是控制接口没有端点。

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

四. 解决方法

4.1修改内核驱动

如果可以修改内核,简单粗暴,直接修改如下

将控制接口端点相关的处理加上条件判断,有控制接口端点才处理,否则不处理

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, 0x00sizeof(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.0Device attached[   17.060874] usb 1-1: new high-speed USB device number 2 using vhci_hcd[   17.210837] usb 1-1SetAddress Request (2) to port 0[   17.249544] usb 1-1New USB device found, idVendor=1993, idProduct=0101, bcdDevice= 1.00[   17.250420] usb 1-1New USB device strings: Mfr=1Product=2SerialNumber=3[   17.251202] usb 1-1Product: xxxxSpecificDevice[   17.251676] usb 1-1Manufacturer: xxxx Team.[   17.252108] usb 1-1SerialNumber0.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.0ttyACM0: USB ACM deviceqinyunti@qinyunti:~$ qinyunti@qinyunti:~ls /dev/ttyACM0/dev/ttyACM0

测试读写

此时会打印以下错误,找到错误的地方

[   49.533354hv_balloon: Max. dynamic memory size: 7618 MB[  750.950166] mini_init (145): drop_caches: 11707.885065] cdc_acm 1-1:1.0: acm_port_activate - usb_submit_urb(ctrl irq) failedqinyunti@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退出。

4.2使用自定义驱动

如果内核无法替换,则可以参考cdc_acm.c修改,单独再实现一个驱动,加载该驱动即可。

复制修改后的cdc_acm.ccdc_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.783891cdc_acm: loading out-of-tree module taints kernel.[   42.815334ErrorDriver 'cdc_acm' is already registered, aborting...[   49.595978hv_balloon: Max. dynamic memory size: 7618 MB[  217.087763ErrorDriver '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

4.3修改CDC设备驱动

如果设备可以修改,则修改设备控制接口增加一个中断端点,并响应对应的请求即可。

而对于一些已经固化的驱动比如bootrom中的程序可能就改不了了,只能改PC端的驱动了。

.总结

以上主要是分享基于WSL2如何修改内核CDC相关代码,增加调试信息去进行调试,问题本身不是很重要,重点是分享分析过程。而分享的案例本身的问题是CDC设备没有提供控制接口的中断端点,Linux下驱动必须要求有,而Windows下没有也可以,这也看出Windows下兼容性容错性上做的更好。

解决方案根据具体情况而定,这也是嵌入式开发中经常会遇到的情况,兼容性问题,各种填坑问题等,往往解决一个问题可能需要山路十八弯才能解决,然而解决一个这样的问题却是价值的体现,这也是嵌入式开发中能力价值的体现。比如上面如果设备端驱动改不了了则只能改内核驱动,内核驱动改不了则只能加载自定义的驱动。









评论
  • 故障现象一辆2017款东风风神AX7车,搭载DFMA14T发动机,累计行驶里程约为13.7万km。该车冷起动后怠速运转正常,热机后怠速运转不稳,组合仪表上的发动机转速表指针上下轻微抖动。 故障诊断 用故障检测仪检测,发动机控制单元中无故障代码存储;读取发动机数据流,发现进气歧管绝对压力波动明显,有时能达到69 kPa,明显偏高,推断可能的原因有:进气系统漏气;进气歧管绝对压力传感器信号失真;发动机机械故障。首先从节气门处打烟雾,没有发现进气管周围有漏气的地方;接着拔下进气管上的两个真空
    虹科Pico汽车示波器 2025-01-08 16:51 108浏览
  • 1月7日-10日,2025年国际消费电子产品展览会(CES 2025)盛大举行,广和通发布Fibocom AI Stack,赋智千行百业端侧应用。Fibocom AI Stack提供集高性能模组、AI工具链、高性能推理引擎、海量模型、支持与服务一体化的端侧AI解决方案,帮助智能设备快速实现AI能力商用。为适应不同端侧场景的应用,AI Stack具备海量端侧AI模型及行业端侧模型,基于不同等级算力的芯片平台或模组,Fibocom AI Stack可将TensorFlow、PyTorch、ONNX、
    物吾悟小通 2025-01-08 18:17 66浏览
  • 在当前人工智能(AI)与物联网(IoT)的快速发展趋势下,各行各业的数字转型与自动化进程正以惊人的速度持续进行。如今企业在设计与营运技术系统时所面临的挑战不仅是技术本身,更包含硬件设施、第三方软件及配件等复杂的外部因素。然而这些系统往往讲究更精密的设计与高稳定性,哪怕是任何一个小小的问题,都可能对整体业务运作造成严重影响。 POS应用环境与客户需求以本次分享的客户个案为例,该客户是一家全球领先的信息技术服务与数字解决方案提供商,遭遇到一个由他们所开发的POS机(Point of Sal
    百佳泰测试实验室 2025-01-09 17:35 75浏览
  • 在过去十年中,自动驾驶和高级驾驶辅助系统(AD/ADAS)软件与硬件的快速发展对多传感器数据采集的设计需求提出了更高的要求。然而,目前仍缺乏能够高质量集成多传感器数据采集的解决方案。康谋ADTF正是应运而生,它提供了一个广受认可和广泛引用的软件框架,包含模块化的标准化应用程序和工具,旨在为ADAS功能的开发提供一站式体验。一、ADTF的关键之处!无论是奥迪、大众、宝马还是梅赛德斯-奔驰:他们都依赖我们不断发展的ADTF来开发智能驾驶辅助解决方案,直至实现自动驾驶的目标。从新功能的最初构思到批量生
    康谋 2025-01-09 10:04 69浏览
  • 「他明明跟我同梯进来,为什么就是升得比我快?」许多人都有这样的疑问:明明就战绩也不比隔壁同事差,升迁之路却比别人苦。其实,之间的差异就在于「领导力」。並非必须当管理者才需要「领导力」,而是散发领导力特质的人,才更容易被晓明。许多领导力和特质,都可以通过努力和学习获得,因此就算不是天生的领导者,也能成为一个具备领导魅力的人,进而被老板看见,向你伸出升迁的橘子枝。领导力是什么?领导力是一种能力或特质,甚至可以说是一种「影响力」。好的领导者通常具备影响和鼓励他人的能力,并导引他们朝着共同的目标和愿景前
    优思学院 2025-01-08 14:54 93浏览
  • 在智能网联汽车中,各种通信技术如2G/3G/4G/5G、GNSS(全球导航卫星系统)、V2X(车联网通信)等在行业内被广泛使用。这些技术让汽车能够实现紧急呼叫、在线娱乐、导航等多种功能。EMC测试就是为了确保在复杂电磁环境下,汽车的通信系统仍然可以正常工作,保护驾乘者的安全。参考《QCT-基于LTE-V2X直连通信的车载信息交互系统技术要求及试验方法-1》标准10.5电磁兼容试验方法,下面将会从整车功能层面为大家解读V2X整车电磁兼容试验的过程。测试过程揭秘1. 设备准备为了进行电磁兼容试验,技
    北汇信息 2025-01-09 11:24 79浏览
  •  在全球能源结构加速向清洁、可再生方向转型的今天,风力发电作为一种绿色能源,已成为各国新能源发展的重要组成部分。然而,风力发电系统在复杂的环境中长时间运行,对系统的安全性、稳定性和抗干扰能力提出了极高要求。光耦(光电耦合器)作为一种电气隔离与信号传输器件,凭借其优秀的隔离保护性能和信号传输能力,已成为风力发电系统中不可或缺的关键组件。 风力发电系统对隔离与控制的需求风力发电系统中,包括发电机、变流器、变压器和控制系统等多个部分,通常工作在高压、大功率的环境中。光耦在这里扮演了
    晶台光耦 2025-01-08 16:03 86浏览
  • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
    hai.qin_651820742 2025-01-07 14:52 121浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2025-01-09 09:58 51浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球中空长航时无人机产值达到9009百万美元,2024-2030年期间年复合增长率CAGR为8.0%。 环洋市场咨询机构出版了的【全球中空长航时无人机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球中空长航时无人机总体规模,包括产量、产值、消费量、主要生产地区、主要生产商及市场份额,同时分析中空长航时无人机市场主要驱动因素、阻碍因素、市场机遇、挑战、新产品发布等。报告从中空长航时
    GIRtina 2025-01-09 10:35 71浏览
  • HDMI 2.2 规格将至,开启视听新境界2025年1月6日,HDMI Forum, Inc. 宣布即将发布HDMI规范2.2版本。新HDMI规范为规模庞大的 HDMI 生态系统带来更多选择,为创建、分发和体验理想的终端用户效果提供更先进的解决方案。新技术为电视、电影和游戏工作室等内容制作商在当前和未来提供更高质量的选择,同时实现多种分发平台。96Gbps的更高带宽和新一代 HDMI 固定比率速率传输(Fixed Rate Link)技术为各种设备应用提供更优质的音频和视频。终端用户显示器能以最
    百佳泰测试实验室 2025-01-09 17:33 81浏览
  • 职场是人生的重要战场,既是谋生之地,也是实现个人价值的平台。然而,有些思维方式却会悄无声息地拖住你的后腿,让你原地踏步甚至退步。今天,我们就来聊聊职场中最忌讳的五种思维方式,看看自己有没有中招。1. 固步自封的思维在职场中,最可怕的事情莫过于自满于现状,拒绝学习和改变。世界在不断变化,行业的趋势、技术的革新都在要求我们与时俱进。如果你总觉得自己的方法最优,或者害怕尝试新事物,那就很容易被淘汰。与其等待机会找上门,不如主动出击,保持学习和探索的心态。加入优思学院,可以帮助你快速提升自己,与行业前沿
    优思学院 2025-01-09 15:48 65浏览
  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
    知白 2025-01-07 15:02 158浏览
  • 一个真正的质量工程师(QE)必须将一件产品设计的“意图”与系统的可制造性、可服务性以及资源在现实中实现设计和产品的能力结合起来。所以,可以说,这确实是一种工程学科。我们常开玩笑说,质量工程师是工程领域里的「侦探」、「警察」或「律师」,守护神是"墨菲”,信奉的哲学就是「墨菲定律」。(注:墨菲定律是一种启发性原则,常被表述为:任何可能出错的事情最终都会出错。)做质量工程师的,有时会不受欢迎,也会被忽视,甚至可能遭遇主动或被动的阻碍,而一旦出了问题,责任往往就落在质量工程师的头上。虽然质量工程师并不负
    优思学院 2025-01-09 11:48 92浏览
  • 本文介绍编译Android13 ROOT权限固件的方法,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。关闭selinux修改此文件("+"号为修改内容)device/rockchip/common/BoardConfig.mkBOARD_BOOT_HEADER_VERSION ?= 2BOARD_MKBOOTIMG_ARGS :=BOARD_PREBUILT_DTB
    Industio_触觉智能 2025-01-08 00:06 111浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦