Linux---应用层获取usb设备描述信息&通过endpoint地址数据通讯

文章目录

🌈应用层获取USB设备信息总体思路

应用层可以打开USB设备的节点,读取包括USB设备的配置,端口,端点等信息。具体来说对于USB设备均存在对应的VID PID,其中VID指的是Vendor ID(厂商识别码),用于唯一标识USB设备的制造商。PID指的是Product ID(产品识别码),用于唯一标识USB设备的产品型号。通过VID和PID,可以确定一个具体的USB设备,因为每个设备的VID和PID是唯一的,可以帮助系统识别和与设备进行交互。


如果上位机正常识别USB设备,我们可以通过对应节点信息/sys/bus/usb/devices按照文件目录去搜索,如果目录中idVendor和idProduct与我们要搜索的一致,可以判定为我们需要找的设备节点。如下图1-4目录中的idVendor和idProduct与我们要找寻的一致:

c 复制代码
root@Vostro:/sys/bus/usb/devices/1-4# ls -al
total 0
drwxr-xr-x 12 root root     0 Aug 31 20:55 .
drwxr-xr-x  8 root root     0 Aug 10 11:55 ..
drwxr-xr-x  6 root root     0 Aug 31 20:55 1-4:1.0
drwxr-xr-x  6 root root     0 Aug 31 20:55 1-4:1.1
drwxr-xr-x  7 root root     0 Aug 31 20:55 1-4:1.2
drwxr-xr-x  7 root root     0 Aug 31 20:55 1-4:1.3
drwxr-xr-x  7 root root     0 Aug 31 20:55 1-4:1.4
drwxr-xr-x  7 root root     0 Aug 31 20:55 1-4:1.5
drwxr-xr-x  7 root root     0 Aug 31 20:55 1-4:1.6
-rw-r--r--  1 root root  4096 Aug 31 20:55 authorized
-rw-r--r--  1 root root  4096 Aug 31 20:55 avoid_reset_quirk
-r--r--r--  1 root root  4096 Aug 31 20:55 bcdDevice
-rw-r--r--  1 root root  4096 Aug 31 20:55 bConfigurationValue
-r--r--r--  1 root root  4096 Aug 31 20:55 bDeviceClass
-r--r--r--  1 root root  4096 Aug 31 20:55 bDeviceProtocol
-r--r--r--  1 root root  4096 Aug 31 20:55 bDeviceSubClass
-r--r--r--  1 root root  4096 Aug 31 20:55 bmAttributes
-r--r--r--  1 root root  4096 Aug 31 20:55 bMaxPacketSize0
-r--r--r--  1 root root  4096 Aug 31 20:55 bMaxPower
-r--r--r--  1 root root  4096 Aug 31 20:55 bNumConfigurations
-r--r--r--  1 root root  4096 Aug 31 20:55 bNumInterfaces
-r--r--r--  1 root root  4096 Aug 31 20:55 busnum
-r--r--r--  1 root root  4096 Aug 31 20:55 configuration
-r--r--r--  1 root root 65553 Aug 31 20:55 descriptors
-r--r--r--  1 root root  4096 Aug 31 20:55 dev
-r--r--r--  1 root root  4096 Aug 31 20:55 devnum
-r--r--r--  1 root root  4096 Aug 31 20:55 devpath
lrwxrwxrwx  1 root root     0 Aug 31 20:55 driver -> ../../../../../bus/usb/drivers/usb
drwxr-xr-x  3 root root     0 Aug 31 20:55 ep_00
lrwxrwxrwx  1 root root     0 Aug 31 20:55 firmware_node -> ../../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:4c/device:4d/device:51
-r--r--r--  1 root root  4096 Aug 31 20:55 idProduct
-r--r--r--  1 root root  4096 Aug 31 20:55 idVendor
-r--r--r--  1 root root  4096 Aug 31 20:55 ltm_capable
-r--r--r--  1 root root  4096 Aug 31 20:55 manufacturer
-r--r--r--  1 root root  4096 Aug 31 20:55 maxchild
drwxr-xr-x  2 root root     0 Aug 31 20:55 physical_location
lrwxrwxrwx  1 root root     0 Aug 31 20:55 port -> ../1-0:1.0/usb1-port4
drwxr-xr-x  2 root root     0 Aug 31 20:55 power
-r--r--r--  1 root root  4096 Aug 31 20:55 product
-r--r--r--  1 root root  4096 Aug 31 20:55 quirks
-r--r--r--  1 root root  4096 Aug 31 20:55 removable
--w-------  1 root root  4096 Aug 31 20:55 remove
-r--r--r--  1 root root  4096 Aug 31 20:55 rx_lanes
-r--r--r--  1 root root  4096 Aug 31 20:55 serial
-r--r--r--  1 root root  4096 Aug 31 20:55 speed
lrwxrwxrwx  1 root root     0 Aug 31 20:55 subsystem -> ../../../../../bus/usb
-r--r--r--  1 root root  4096 Aug 31 20:55 tx_lanes
-rw-r--r--  1 root root  4096 Aug 31 20:55 uevent
-r--r--r--  1 root root  4096 Aug 31 20:55 urbnum
-r--r--r--  1 root root  4096 Aug 31 20:55 version

在找到对应目录后,读取该目录下面的uevent节点,会获取到该USB设备对应的主设备号,次设备号,设备名称、设备类型等相关信息,如下图:

c 复制代码
root@Vostro:/sys/bus/usb/devices/1-4# cat uevent 
MAJOR=189
MINOR=115
DEVNAME=bus/usb/001/116
DEVTYPE=usb_device
DRIVER=usb
PRODUCT=2cb7/1/100
TYPE=0/0/0
BUSNUM=001
DEVNUM=116

这里我们需要获取的是DEVNAME=bus/usb/001/116这个信息,该信息表示在/dev设备目录下/dev/bus/usb/001/116为该USB设备的节点,我们后续读取USB设备信息以及数据读写操作都是通过这个节点进行的,如下图我们可以看到/dev/bus/usb/001/目录下有多个USB设备,其中116为我们需要的设备节点:

c 复制代码
root@Vostro:/sys/bus/usb/devices/1-4# cd /dev/bus/usb/001/
root@Vostro:/dev/bus/usb/001# ls -al
total 0
drwxr-xr-x 2 root root      120 Sep  1 17:59 .
drwxr-xr-x 4 root root       80 Aug 10 11:55 ..
crw-rw-rw- 1 root root 189,   0 Aug 10 11:56 001
crw-rw-rw- 1 root root 189,   2 Aug 10 11:56 003
crw-rw-rw- 1 root root 189,   3 Aug 10 11:56 004
crw-rw-rw- 1 root root 189, 115 Aug 31 20:55 116

找到该节点后,通过应用层的open函数打开设备,通过read函数读取并解析设备的相关信息,即可获得USB设备的配置,端点,端口等信息。


🌈应用层代码实例

c 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <linux/usbdevice_fs.h>
#include <linux/usb/ch9.h>


#define BUF_SIZE 512
#define MAX_PATH_LEN 512
#define USB_DIR_BASE    "/sys/bus/usb/devices"


typedef struct
{
    int idVendor;
    int idProduct;
    int InterfaceNum;
    int usbdev;

    char portname[BUF_SIZE];
    char syspath[BUF_SIZE];
    char busname[BUF_SIZE];

    int bulk_ep_in;
    int bulk_ep_out;
    int wMaxPacketSize;
    int usb_need_zero_package;

    int (* write)(const void *handle, void *pbuf, int size, int portnum);
    int (* read)(const void *handle, void *pbuf, int size, int portnum);
} s_usbdev_t;

s_usbdev_t udev;

int strStartsWith(const char *str, const char *match_str)
{
    for ( ; *str != '\0' && *match_str != '\0'; str++, match_str++) {
        if (*str != *match_str) {
            return 0;
        }
    }
    return *match_str == '\0';
}

static int get_usbsys_val(const char *sys_filename, int base)
{
    char buff[64] = {0};
    int ret_val = -1;

    int fd = open(sys_filename, O_RDONLY);
    if (fd < 0) {
        return -1;
    }

    if (read(fd, buff, sizeof(buff)) <= 0) {
        printf("read:%s failed\n", sys_filename);
    }
    else {
        ret_val = strtoul(buff, NULL, base);
    }
    close(fd);

    return ret_val;
}

static int get_busname_by_uevent(const char *uevent, char *busname)
{
    FILE *fp = NULL;
    char line[BUF_SIZE] = {0};
    int MAJOR = 0, MINOR = 0;
    char DEVTYPE[64] = {0}, PRODUCT[64] = {0};

    fp = fopen(uevent, "r");
    if (fp == NULL) {
        printf("fopen %s failed, errno:%d(%s)\n", uevent, errno, strerror(errno));
        return -1;
    }

    while (fgets(line, sizeof(line), fp))
    {
        if (line[strlen(line) - 1] == '\n' || line[strlen(line) - 1] == '\r') {
            line[strlen(line) - 1] = 0;
        }

        if (strStartsWith(line, "MAJOR="))
        {
            MAJOR = atoi(&line[strlen("MAJOR=")]);
        }
        else if (strStartsWith(line, "MINOR="))
        {
            MINOR = atoi(&line[strlen("MINOR=")]);
        }
        else if (strStartsWith(line, "DEVICE="))
        {
            strncpy(busname, &line[strlen("DEVICE=")], MAX_PATH_LEN);
        }
        else if (strStartsWith(line, "DEVNAME="))
        {
            strncpy(busname, &line[strlen("DEVNAME=")], MAX_PATH_LEN);
        }
        else if (strStartsWith(line, "DEVTYPE="))
        {
            strncpy(DEVTYPE, &line[strlen("DEVTYPE=")], sizeof(DEVTYPE));
        }
        else if (strStartsWith(line, "PRODUCT="))
        {
            strncpy(PRODUCT, &line[strlen("PRODUCT=")], sizeof(PRODUCT));
        }
    }
    fclose(fp);

    if (MAJOR != 189  || MINOR == 0 || busname[0] == 0
        || DEVTYPE[0] == 0 || PRODUCT[0] == 0
        || strStartsWith(DEVTYPE, "usb_device") == 0) {
        return -1;
    }

    return 0;
}


int usb_dev_open(int usb_vid, int usb_pid)
{
    char devdesc[MAX_PATH_LEN*2] = {0};
    char devname[MAX_PATH_LEN+128] = {0};
    size_t desc_length = 0, len = 0;
    int bInterfaceNumber = 0;

    DIR *usbdir = NULL;
    struct dirent *dent = NULL;
    int idVendor = 0, idProduct = 0;
    int bNumInterfaces = 0, bConfigurationValue = 0;
    char sys_filename[MAX_PATH_LEN] = {0};

    usbdir = opendir("/sys/bus/usb/devices");
    if (usbdir == NULL) {
        return -1;
    }

    while ((dent = readdir(usbdir)) != NULL)
    {
        if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) {
            continue;
        }

        snprintf(sys_filename, sizeof(sys_filename), "%s/%s/idVendor", USB_DIR_BASE, dent->d_name);
        if ((idVendor = get_usbsys_val(sys_filename, 16)) <= 0) {
            continue;
        }

        snprintf(sys_filename, sizeof(sys_filename), "%s/%s/idProduct", USB_DIR_BASE, dent->d_name);
        if ((idProduct = get_usbsys_val(sys_filename, 16)) <= 0) {
            continue;
        }

        snprintf(sys_filename, sizeof(sys_filename), "%s/%s/bConfigurationValue", USB_DIR_BASE, dent->d_name);
        if ((bConfigurationValue = get_usbsys_val(sys_filename, 10)) <= 0) {
            continue;
        }

        snprintf(sys_filename, sizeof(sys_filename), "%s/%s/bNumInterfaces", USB_DIR_BASE, dent->d_name);
        if ((bNumInterfaces = get_usbsys_val(sys_filename, 10)) <= 0) {
            continue;
        }

        if((idVendor == usb_vid)&&(idProduct == usb_pid))
        {
            udev.idVendor = idVendor;
            udev.idProduct = idProduct;
            printf("----------------------------------\n");
            printf("idVendor: %04x\n", udev.idVendor);
            printf("idProduct: %04x\n", udev.idProduct);
            printf("bNumInterfaces: %d\n", bNumInterfaces);
            printf("bConfigurationValue: %d\n", bConfigurationValue);
            snprintf(sys_filename, sizeof(sys_filename), "%s/%s/uevent", USB_DIR_BASE, dent->d_name);
            get_busname_by_uevent(sys_filename, udev.busname);
            printf("busname: %s\n", udev.busname);
            printf("----------------------------------\n");
            break;
        }
        usleep(10000);
    }

    if (usbdir) {
        closedir(usbdir);
        usbdir = NULL;
    }


    snprintf(devname, sizeof(devname), "/dev/%s", udev.busname);

    if (access(devname, F_OK | R_OK| W_OK)) {
        printf("access %s failed, errno:%d(%s)\n", devname, errno, strerror(errno));
        return -1;
    }

    udev.usbdev = open(devname, O_RDWR | O_NOCTTY);
    if (udev.usbdev < 0) {
        printf("open %s failed, errno:%d(%s)\n", devname, errno, strerror(errno));
        return -1;
    }
    printf("[%s] OK.\n", devname);

    desc_length = read(udev.usbdev, devdesc, sizeof(devdesc));
	printf("desc_length is %zu,read length is %zu\r\n", sizeof(devdesc), desc_length);
    for (len=0; len<desc_length;)
    {
        struct usb_descriptor_header *h = (struct usb_descriptor_header *)(devdesc + len);

        if (h->bLength == sizeof(struct usb_device_descriptor) && h->bDescriptorType == USB_DT_DEVICE)
        {
             struct usb_device_descriptor *device = (struct usb_device_descriptor *)h;
             printf("P: idVendor: %04x idProduct:%04x\n", device->idVendor, device->idProduct);
        }
        else if (h->bLength == sizeof(struct usb_config_descriptor) && h->bDescriptorType == USB_DT_CONFIG)
        {
             struct usb_config_descriptor *config = (struct usb_config_descriptor *)h;
             printf("C: bNumInterfaces: %d\n", config->bNumInterfaces);
        }
        else if (h->bLength == sizeof(struct usb_interface_descriptor) && h->bDescriptorType == USB_DT_INTERFACE)
        {
            struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)h;

             printf("I: If#= %d Alt= %d #EPs= %d Cls=%02x Sub=%02x Prot=%02x\n",
                 interface->bInterfaceNumber, interface->bAlternateSetting, interface->bNumEndpoints,
                 interface->bInterfaceClass, interface->bInterfaceSubClass, interface->bInterfaceProtocol);
            bInterfaceNumber = interface->bInterfaceNumber;
        }
        else if (h->bLength == USB_DT_ENDPOINT_SIZE && h->bDescriptorType == USB_DT_ENDPOINT)
        {
            struct usb_endpoint_descriptor *endpoint = (struct usb_endpoint_descriptor *)h;

                if ( (endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK)
                {
                    if (endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) {
                        udev.bulk_ep_in = endpoint->bEndpointAddress;
                        printf("bulk_ep_in:0x%02X\n", udev.bulk_ep_in);
                    } else {
                        udev.bulk_ep_out = endpoint->bEndpointAddress;
                        printf("bulk_ep_out:0x%02X\n", udev.bulk_ep_out);
                    }
                    udev.wMaxPacketSize = endpoint->wMaxPacketSize;
                    printf("wMaxPacketSize:%d\n", endpoint->wMaxPacketSize);
                }
        }
        len += h->bLength;
    }

    return 0;
}


int main(int argc, char * * argv)
{
    int vid_hex,pid_hex;
    if(argc !=3 )
    {
        printf("Usage: usb_read_device_info USB_VID USB_PID \r\n");
        return -1;
    }

    sscanf(argv[1], "%x", &vid_hex);
    sscanf(argv[2], "%x", &pid_hex);
    printf("USB VID %4x, PID is %4x\r\n", vid_hex, pid_hex);
    usb_dev_open(vid_hex,pid_hex);
}

🌈实例测试

插入一个VID PID为2cb7 0001的USB设备,运行上面的测试程序(编译过程省略),main函数入口参数传入VID 2CB7及 PID 0001运行如下:

c 复制代码
book@Vostro:~/Joy/Test$ sudo ./USB_read_device_info 2cb7 0001
USB VID 2cb7, PID is    1
----------------------------------
idVendor: 2cb7
idProduct: 0001
bNumInterfaces: 7
bConfigurationValue: 1
busname: bus/usb/001/116
----------------------------------
[/dev/bus/usb/001/116] OK.
desc_length is 1024,read length is 221
P: idVendor: 2cb7 idProduct:0001
C: bNumInterfaces: 7
I: If#= 0 Alt= 0 #EPs= 1 Cls=02 Sub=06 Prot=00
I: If#= 1 Alt= 0 #EPs= 0 Cls=0a Sub=00 Prot=00
I: If#= 1 Alt= 1 #EPs= 2 Cls=0a Sub=00 Prot=00
bulk_ep_in:0x81
wMaxPacketSize:512
bulk_ep_out:0x01
wMaxPacketSize:512
I: If#= 2 Alt= 0 #EPs= 2 Cls=ff Sub=ff Prot=ff
bulk_ep_in:0x82
wMaxPacketSize:512
bulk_ep_out:0x02
wMaxPacketSize:512
I: If#= 3 Alt= 0 #EPs= 2 Cls=ff Sub=ff Prot=ff
bulk_ep_in:0x83
wMaxPacketSize:512
bulk_ep_out:0x03
wMaxPacketSize:512
I: If#= 4 Alt= 0 #EPs= 2 Cls=ff Sub=ff Prot=ff
bulk_ep_in:0x84
wMaxPacketSize:512
bulk_ep_out:0x04
wMaxPacketSize:512
I: If#= 5 Alt= 0 #EPs= 2 Cls=ff Sub=ff Prot=ff
bulk_ep_in:0x85
wMaxPacketSize:512
bulk_ep_out:0x05
wMaxPacketSize:512
I: If#= 6 Alt= 0 #EPs= 2 Cls=ff Sub=42 Prot=01
bulk_ep_in:0x86
wMaxPacketSize:512
bulk_ep_out:0x06
wMaxPacketSize:512

可以看到通过解析read函数读取的相关信息,打印出了该USB设备的configuration、interface、endpoint等信息。

🌈应用层通过endpoint进行数据读写

使用USB的endpoint进行数据读写,需要了解每个endpoint的地址,并且endpoint为单向的,对于USB设备每interface下面可以有多个endpoint,这些endpoint可以是输入,也可以是输出,下面实例的设备节点USB_DEV_PATH 是由上面应用程序获取,通讯具体的endpoint地址也是由上面应用程序获取,这里直接写死对应数值以便测试:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <linux/usbdevice_fs.h>

// USB设备节点路径,可通过上部分应用层实例代码获取某VID PID 对应的usb设备节点
#define USB_DEV_PATH "/dev/bus/usb/001/018"  

int usbfs_bulk_write(int fd, char *data, size_t dataLen)
{
	int ret;
	struct usbdevfs_urb urb_write;
	struct usbdevfs_urb *urb = NULL;
	
	memset(&urb_write, 0, sizeof(urb_write));
    urb_write.type = USBDEVFS_URB_TYPE_BULK;
    urb_write.endpoint = 0x05;
    urb_write.status = -1;
    urb_write.buffer = (void *)data;
    urb_write.buffer_length = dataLen;
    urb_write.usercontext = &urb_write;
    printf("endpoint is %d, buffer_length is %d\r\n", urb_write.endpoint, urb_write.buffer_length);
    do {
        ret = ioctl(fd, USBDEVFS_SUBMITURB, &urb_write);
    } while ((ret < 0) && (errno == EINTR));

    if (ret != 0) {
        printf("USBDEVFS_SUBMITURB failed, ret: %d, errno:%d(%s)\n", ret, errno, strerror(errno));
        return -1;
    }

    do {
        urb = NULL;
        ret = ioctl(fd, USBDEVFS_REAPURB, &urb);
    } while ((ret < 0) && (errno == EINTR));

    if (ret == 0 && urb && urb->status == 0 && urb->actual_length) {
        return urb->actual_length;
    }

    return -1;
}

int usbfs_bulk_read(int fd, char *data, size_t dataLen)
{
    int ret = -1;
    struct usbdevfs_bulktransfer bulk;

    bulk.ep = 0x85;
    bulk.len = dataLen;
    bulk.data = data;
    bulk.timeout = 3000;

    do {
        ret = ioctl(fd, USBDEVFS_BULK, &bulk);
    } while ((ret < 0) && (errno == EINTR));

    return ret;
}

int main() 
{
    // 准备要写入的数据
    unsigned char data[] = {0x01,0xaa,0xaa,0xaa,0x01,0x55,0x73,0x01,0x14,0x00,0x00,0x00,0x06,0x67,0xbb,0xbb,0x04,
                      0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x44,0x09,0x7e};
    size_t dataLen = sizeof(data);
	int ret;

    int fd = open(USB_DEV_PATH, O_RDWR | O_NOCTTY);  // 打开USB设备文件描述符
    if (fd == -1) {
        perror("Failed to open USB device");
        return -1;
    }
	
	ret = usbfs_bulk_write(fd, data, dataLen);
	printf("usbfs_bulk_write ret is %d\r\n", ret);
	
	ret = usbfs_bulk_read(fd, data, dataLen);
	printf("usbfs_bulk_read ret is %d\r\n", ret);
	
	return 0;
}

上述代码实际测试结果如下:

c 复制代码
root:/home/Joy/Test# ./usb_bulk_write_read
endpoint is 5, buffer_length is 35
usbfs_bulk_write ret is 35
usbfs_bulk_read ret is 10
相关推荐
醇氧7 分钟前
ab (Apache Bench)的使用
linux·学习·centos·apache
千城丶Y20 分钟前
GoogleCloud服务器的SSH连接配置
服务器·ssh·googlecloud
华纳云IDC服务商23 分钟前
怎么选择香港服务器的线路?解决方案
服务器·网络·香港服务器
sss-web122629 分钟前
4.远程访问及控制
运维·服务器
EasyCVR1 小时前
GA/T1400视图库平台EasyCVR视频融合平台HLS视频协议是什么?
服务器·网络·人工智能·音视频
moneyxjj1 小时前
Linux各种解压命令汇总
linux·运维·服务器
白白♛~1 小时前
网络管理之---3种网络模式配置
linux·服务器·网络
GOTXX1 小时前
NAT、代理服务与内网穿透技术全解析
linux·网络·人工智能·计算机网络·智能路由器
脱了格子衬衫2 小时前
使用源码编译安装 Tomcat
linux·tomcat
陈yanyu2 小时前
Linux - 弯路系列3:安装和编译libvirt-4.5.0及虚拟网卡virbr0(virbr0-nic)创建
linux·运维·服务器