Linux驱动学习day28(USB驱动,libusb操作)
一、USB设备的枚举过程
状态切换流程图
当我们将一个USB设备插入PC中,host会给USB设备上电,上电之后发出reset信号,在此过程中检测设备的速率类型(低速、全速或高速),这时候设备进入到默认状态(Default State),一个属于默认状态的USB设备其地址为0,以后这个PC机会使用地址0与这个USB设备通信,会访问地址为0的设备,访问其端点0,将新地址发送给USB设备,之后就工作于address模式,PC会使用新地址于之通信。PC配置USB设备,使其工作于某一种配置下(Configuration State),这就是设备的枚举过程。
USB设备插入之后,PC主要做下面几件事:1、获取设备的设备描述符,2、发送新地址给USB设备,3、再次获取设备描述符,4、获取配置描述符,5、设置configuration。
二、LibUSB
可以通过libusb访问USB设备,不需要USB设备端的驱动程序,需要移除原来的驱动程序。然后就可以直接访问USB控制器的驱动程序,使用open/read/write/ioctl/close这些接囗来打开设备、收发数据、关闭设备。libusb封装了更好用的函数,这些函数的使用可以分为5个步骤:
初始化、打开设备、移除原驱动/认领接口、传输、关闭设备。
2.1 libUSB库使用流程
1、打开设备:(libusb_init ==> libusb_get_device_list ==>libusb_get_device_descriptor/libusb_get_config_descriptor(得到所有的描述符) ==> libusb_open ==> libusb_free_config_descriptor ==> libusb_free_device_list)
2、移除原有的驱动程序/认领接口(逻辑上的认领,就是说我已经移除了USB设备驱动,之后使用libusb来操作USB设备)
libusb_set_auto_detach_kernel_driver==>libusb_claim_interface(设置标志位,认领清除驱动)
libusb_detach_kernel_driver==>libusb_claim_interface(直接清除设备驱动)
3、认领接口之后可以发起传输
同步传输:libusb_control_transfer / libusb_bulk_transfer / libusb_interrupt_transfer 启动和等待
异步传输(更灵活,可以启动多个传输,一次性等待这些传输有没有完成):libusb_alloc_transfer(分配一个传输结构体) ==>libusb_fill_control_transfer \ libusb fill bulk transfer \ libusb fill interrupt transfer \ libusb fill iso transfer(填充传输结构体,填充完之后才可以启动传输) ==> libusb_submit_transfer(启动传输) ==> libusb_handle_events_timeout_completed / libusb_handle_events_timeout / libusb_handle_events_completed / libusb_handle_events 等待所有的传输结果
异步传输除了分配设置构造传输libusb_transfer结构体之外,还需要设置一个回调函数,当传输结束之后,该回调函数会被调用。(回调函数是核心)
4、传输完成之后关闭设备
libusb_free_transfer ==> libusb_release_interface ==> libusb_close ==> libusb_exit
2.2 同步传输和异步传输的区别
USB数据传输分为两个步骤,对于读数据,先给设备发出读数据的请求,一段时间后数据返回;对于写数据,先发送数据给设备,一段时间后得到回应。
同步接口的核心在于把上述两个步骤放在一个函数里面。比如想去读取一个USB键盘的数据,给键盘发出读请求后,如果用户一直没有按下键盘,那么读函数会一直等待。
异步接口的核心在于把上述两个步骤分开:使用一个非阻塞的函数启动传输,它会立刻返回;提供一个回调函数用来处理返回结果。
对于多端点的设备,使用同步传输无法同一时刻在同一个线程中访问多个端点,必须等待上一个传输结束之后才能进行下一次传输,或者启动多个线程。使用异步传输,可以先设置好多个传输结构体和传输端点,然后使用libusb_submit_transfer一次性提交所有结构体,同时启动多个传输。
2.3 使用libusb读取鼠标数据
可以从接口描述符得到其设备是否是鼠标,鼠标的描述符如下:
在usb设备里面,所有的输入输出都是站在主机的角度来判断。
主要流程:找到设备的设备描述符,在设备描述符中找到配置描述符,再找到接口描述符,看看接口描述符的接口类是不是3,子类是不是1,protocol协议是不是2(1是键盘),有没有中断端点。
2.3.1 鼠标上报的数据信息
第0个字节的第1位代表鼠标左键按下,等等。
内核源码的定义:
2.3.2 同步读取代码
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libusb.h>int main(int argc , char * argv[])
{int err , i , interface_num , endpoint , ep , transferred;int find_mouse = 0;int num_dev;libusb_device *dev, **dev_list; struct libusb_config_descriptor *config_desc;struct libusb_device_handle *dev_handle = NULL;/* libusb_init */err = lisusb_init(NULL);if (err < 0) {fprintf(stderr, "failed to initialise libusb %d - %s\n", err, libusb_strerror(err));exit(1);}/* get_device_list */if((num_dev = libusb_get_device_list(NULL, &dev_list)) < 0 ){fprintf(stderr, "failed to libusb_get_device_list libusb %d - %s\n", err, libusb_strerror(err));exit(1);}/* for each device get config descriptor */for(i = 0 ; i < num_dev ; i++){dev = dev_list[i];err = libusb_get_config_descriptor(dev, 0, &config_desc);if (err) {fprintf(stderr, "could not get configuration descriptor\n");continue;}fprintf(stdout, "libusb_get_config_descriptor() ok\n");/* parse interface descriptor, find usb mouse */ for(interface_num = 0 ; interface_num < config_desc->bNumInterfaces ; i++){const struct libusb_interface_descriptor *intf_desc = &config_desc->interface[interface].altsetting[0];// interface_num = intf_desc->bInterfaceNumber;if (intf_desc->bInterfaceClass != 3 || intf_desc->bInterfaceProtocol != 2)continue;else{/* find usb mouse interrupt endpoint */for(ep = 0 ; endpoint < intf_desc->bNumEndpoints ; ep++){if ((intf_desc->endpoint[ep].bmAttributes & 3) == LIBUSB_TRANSFER_TYPE_INTERRUPT ||(intf_desc->endpoint[ep].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN) {/* find input eps interrupt */endpoint = intf_desc->endpoint[ep].bEndpointAddress;find_mouse = 1;break;}}}if(find_mouse)break;}if(find_mouse)break;}/* open device */if(find_mouse){err = libusb_open(dev, &dev_handle);if (err) {fprintf(stderr, "could not get configuration descriptor\n");continue;}}/* free device list */libusb_free_device_list(devs, 1);/* claim interface */libusb_set_auto_detach_kernel_driver(dev_handle, 1); err = libusb_claim_interface(dev_handle, interface_num);if (err) {fprintf(stderr, "could not libusb_claim_interface()\n");continue;}/* libusb_interrupt_transfer */while(1){/* set libusb_transfer struct *//* handle , interrupt endpoint , data buffer , size , really size , timeout ms*/err = libusb_interrupt_transfer(dev_handle, endpoint, buffer, 16, &transferred, 5000);if (!err) {/* parser data */printf("%04d datas: ", count++);for (int i = 0; i < transferred; i++){printf("%02x ", buffer[i]);}printf("\n");} else if (err == LIBUSB_ERROR_TIMEOUT){fprintf(stderr, "libusb_interrupt_transfer timout\n");} else {fprintf(stderr, "libusb_interrupt_transfer err : %d\n", err);exit(1);}}/* libusb close */libusb_release_interface(dev_handle, interface_num);libusb_close(dev_handle);libusb_exit(NULL);}
2.4 上机实验(交叉编译)
在开发板上进行实验需要交叉编译libusb库
2.4.1 交叉编译阶段
解压库文件
sudo apt-get install libtool
cd libusb
./autogen.sh
./configure --host=gcc前缀
make and make install
2.4.2 安装库、头文件到工具链里
gcc xxx.c -o xxx -v 可以找到头文件
gcc xxx.c -o xxx lusb -v 可以找到在哪里寻找相关的库