Linux usb子系统(三):通过usbfs操作设备的用户空间驱动

2019-07-13 08:52发布

内核中提供了USB设备文件系统(usbdevfs,Linux 2.6改为usbfs,即USB文件系统),它和/proc类似,都是动态产生的。通过在/etc/fstab文件中添加如下一行:
none /proc/bus/usb usbfs defaults
或者输入命令:
mount -t usbfs none /proc/bus/usb
可以实现USB设备文件系统的挂载。一个典型的/proc/bus/usb/devices文件的结构如下(运行的arm Linux 2.6.37内核上的机器上插入了一个usb鼠标):root@dvr:/proc/bus/usb# cat devicesT: Bus=02 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 1 Spd=480 MxCh= 1
B: Alloc= 0/800 us ( 0%), #Int= 0, #Iso= 0
D: Ver= 2.00 Cls=09(hub ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1
P: Vendor=1d6b ProdID=0002 Rev= 2.06
S: Manufacturer=Linux 2.6.37+ musb-hcd
S: Product=MUSB HDRC host driver
S: SerialNumber=musb-hdrc.1
C:* #Ifs= 1 Cfg#= 1 Atr=e0 MxPwr= 0mA
I:* If#= 0 Alt= 0 #EPs= 1 Cls=09(hub ) Sub=00 Prot=00 Driver=hub
E: Ad=81(I) Atr=03(Int.) MxPS= 4 Ivl=256msT: Bus=02 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=480 MxCh= 4
D: Ver= 2.00 Cls=09(hub ) Sub=00 Prot=01 MxPS=64 #Cfgs= 1
P: Vendor=1a40 ProdID=0101 Rev= 1.11
S: Product=USB 2.0 Hub
C:* #Ifs= 1 Cfg#= 1 Atr=e0 MxPwr=100mA
I:* If#= 0 Alt= 0 #EPs= 1 Cls=09(hub ) Sub=00 Prot=00 Driver=hub
E: Ad=81(I) Atr=03(Int.) MxPS= 1 Ivl=256msT: Bus=02 Lev=02 Prnt=02 Port=01 Cnt=01 Dev#= 3 Spd=1.5 MxCh= 0
D: Ver= 1.10 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1
P: Vendor=1c4f ProdID=0003 Rev= 1.10
S: Manufacturer=SIGMACHIP
S: Product=Usb Mouse
C:* #Ifs= 1 Cfg#= 1 Atr=a0 MxPwr= 98mA
I:* If#= 0 Alt= 0 #EPs= 1 Cls=03(HID ) Sub=01 Prot=02 Driver=usbhid
E: Ad=81(I) Atr=03(Int.) MxPS= 4 Ivl=10msT: Bus=01 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 1 Spd=480 MxCh= 1
B: Alloc= 0/800 us ( 0%), #Int= 0, #Iso= 0
D: Ver= 2.00 Cls=09(hub ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1
P: Vendor=1d6b ProdID=0002 Rev= 2.06
S: Manufacturer=Linux 2.6.37+ musb-hcd
S: Product=MUSB HDRC host driver
S: SerialNumber=musb-hdrc.0
C:* #Ifs= 1 Cfg#= 1 Atr=e0 MxPwr= 0mA
I:* If#= 0 Alt= 0 #EPs= 1 Cls=09(hub ) Sub=00 Prot=00 Driver=hub
E: Ad=81(I) Atr=03(Int.) MxPS= 4 Ivl=256ms通过分析usbfs中记录的信息,可以得到系统中USB完整的信息,例如,usbview可以以图形化的方式显示系统中的USB设备。当然,在编译Linux内核时,应该包括“USB device filesystem”。usbfs动态跟踪总线上插入和移除的设备,通过它可以查看系统中USB设备的信息,包括拓扑、带宽、设备描述符信息、产品ID、字符串描述符、配置描述符、接口描述符、端点描述符等。参考TF32A09芯片厂商提供的USB通信层。本层提供应用程序与安全模块间进行USB数据通信的底层实现,屏蔽了调用内核usbdevfs接口的细节和部分系统限制。本层实现了四个主要功能:搜索系统中的安全模块并完成USB通信前的准备工作、向安全模块读写数据、关闭模块、获取CBW地址等。其中读写数据函数是USB通信的核心功能,后续的所有通信都是调用这两个函数完成的。除非是非常了解USB通信和安全模块私有通信指令的细节并需要直接与安全模块进行通信,否则是完全不需要直接调用这一层的通信函数的。本层的usb数据通信函数仅适用于与安全模块之间的通信,并不是通用的USB通信实现,不适用于其他USB设备。下面是通信层源码:复制代码/* * 此文件仅用于同方USB加密模块设备访问程序,供客户使用,不建议客户修改 * * 作者:宇浩然 * 时间:2012.04.21 * */ #include #include #include #include #include #include <string.h> #include #include #include "tf09usb.h" //此结构为内部使用,仅在搜索设备时读取pid vid时使用 struct usb_device_descriptor { unsigned char bLength; unsigned char bDescriptorType; unsigned short int bcdUSB; unsigned char bDeviceClass; unsigned char bDeviceSubClass; unsigned char bDeviceProtocol; unsigned char bMaxPacketSize0; unsigned short int idVendor; unsigned short int idProduct; unsigned short int bcdDevice; unsigned char iManufacturer; unsigned char iProduct; unsigned char iSerialNumber; unsigned char bNumConfigurations; } __attribute__ ((packed)); const static char * usbpath[] = {"/dev/bus/usb", "/proc/bus/usb"}; //usb devfs可能存在的位置 tf09_device *tf09Device = NULL; //定义CBW初始化常量,用于不同USB设备初始化时的复制 const CBW cbw_init = {{'U','S','B','C'}, {'T','F','0','9'}, //固定不变,任何时候都不进行读写操作 {0,0,0,0}, //数据长度,每次使用前都必须设定一次,以小端方式存储的32位整数,目前只用低两位 0, //数据方向,每次使用前都必须设定一次, 0,16, 0xd0, //固定不变,任何时候都不进行读写操作 {2, 1, 5, 0}, //与协议相关的指令标识,每次使用前必须设定 {0,{{0,0,0,0,0}},{{0,0,0}},0,0} //与协议指令相关的参数信息,每次使用前必须设定一次 }; /******************************************************************************* 功能:搜索系统中符合参数的所有USB设备,同时完成通信前的所有准备工作。 注意: 作者:宇浩然 时间:2012.04.21 参数:idVendor即设备的VID, idProduct即设备的PID 返回值:NULL失败 *******************************************************************************/ tf09_device * tf09_find_device(short int idVendor, short int idProduct) { DIR * usb_dir, *bus_dir; struct dirent *bus, *dev; char buspath[TF09_PATH_MAX], devpath[TF09_PATH_MAX]; struct usb_device_descriptor dev_des; int fd, usb_index; //查找usb设备的总线位置 tf09_close(); //若之前已经查找过设备,则关闭所有设备,此函数可以确保tf09Device=NULL for(usb_index=0;usb_index<(sizeof(usbpath)/sizeof(char*));usb_index++) //搜索并打开总线所在目录 { usb_dir = opendir(usbpath[usb_index]); if (NULL != usb_dir) break; } if(NULL == usb_dir) return tf09Device; while(NULL != (bus=readdir(usb_dir))) //读取usb devfs下的每一项,即bus { if(!strchr("1234567890", bus->d_name[0])) //bus肯定以数字开头,其实全部都是数字 continue; snprintf(buspath, TF09_PATH_MAX, "%s/%s", usbpath[usb_index], bus->d_name); bus_dir = opendir(buspath); if(NULL==bus_dir) continue; while(NULL!=(dev=readdir(bus_dir))) //读取总线目录下的每一项,即usb设备 { if(!strchr("1234567890", dev->d_name[0])) continue; snprintf(devpath, TF09_PATH_MAX, "%s/%s", buspath, dev->d_name); if((fd = open(devpath, O_RDWR))<0) continue; if(read(fd, (void *)(&dev_des), sizeof(dev_des)) > 0 && dev_des.idVendor==idVendor && dev_des.idProduct==idProduct) //客户需要的设备 { tf09_device *tmp = (tf09_device*)malloc(sizeof(tf09_device)); tmp->fd = fd; if(0 == tf09_init(tmp)){ tmp->next = tf09Device; tf09Device = tmp; //将新设备添加到tf09Device单向链表中 }else{ close(fd); free(tmp); //通信前的初始化工作失败,关闭设备,并释放设备内存 } }else{ close(fd); } //已经打开的句柄另行处理,不需要在此关闭 } closedir(bus_dir); } closedir(usb_dir); return tf09Device; } //给设备赋值 //ad int tf09_set_device(const tf09_device * dev) { //tf09Device = NULL; tf09Device=dev; tf09_init(tf09Device); } /******************************************************************************* 功能:在指定的USB设备上进行bulk写操作,无16KB限制 注意: 作者:宇浩然 时间:2012.04.21 参数:dev为设备指针,data为数据缓冲地址,size为缓冲区大小,timeout为时间 返回值: <0失败;其他为实际写入的字符数 *******************************************************************************/ int tf09_bulk_write(const tf09_device * dev,const void *data, int size, int timeout) { if (NULL == dev) dev = tf09Device; if (NULL == dev || dev->fd <= 0 || NULL == data) return -1; return usb_bulk(dev->fd, 1, (void *)data, size, timeout); } /******************************************************************************* 功能:在指定的USB设备上进行bulk读操作,无16KB限制 注意: 作者:宇浩然 时间:2012.04.21 参数:dev为设备指针,data为数据缓冲地址,size计划读取的字符数,timeout为时间 返回值: <0失败;其他为实际读取的字符数 *******************************************************************************/ int tf09_bulk_read(const tf09_device * dev, void *data, int size, int timeout) { if (NULL == dev) dev = tf09Device; if (NULL == dev || dev->fd <= 0 || NULL == data) return -1; return usb_bulk(dev->fd, 129, (void *)data, size, timeout); } /******************************************************************************* 功能:关闭所有的USB设备,并将tf09Device置空 注意:关闭设备后,若要再次使用,需要重新执行tf09_finddevice函数 作者:宇浩然 时间:2012.04.21 参数:无 返回值:无 *******************************************************************************/ void tf09_close(void) { int i = 0; // while( (NULL != tf09Device) && (i < TF09_USB_DEVICE_MAX) ) // { // tf09_device* devtmp = tf09Device; // tf09Device = tf09Device->next; // // tf09_release_interface(devtmp); //释放usb设备的interface // // ioctl(devtmp->fd, USBDEVFS_RESET, NULL); // // close(devtmp->fd); // // free(devtmp); // // i++; // } tf09Device = NULL; } void tf09_close_delete(void) { int i = 0; while( (NULL != tf09Device) && (i < TF09_USB_DEVICE_MAX) ) { tf09_device* devtmp = tf09Device; tf09Device = tf09Device->next; tf09_release_interface(devtmp); //释放usb设备的interface ioctl(devtmp->fd, USBDEVFS_RESET, NULL); close(devtmp->fd); free(devtmp); i++; } tf09Device = NULL; } /******************************************************************************* 功能:初始化指定的USB设备完成通信前的准备工作,该设备已经打开 注意:此函数不需要客户调用 作者:宇浩然 时间:2012.04.21 参数:dev为设备指针 返回值:0成功 -1失败 *******************************************************************************/ static int tf09_init(tf09_device * dev) { if(0==tf09_detach_driver(dev)) { ioctl(dev->fd, USBDEVFS_RESETEP, NULL); //附加驱动卸载后,将设备重置一次 } unsigned char* pa = (unsigned char*)&(dev->cbw); unsigned char* pb = (unsigned char*)&cbw_init; int i=0; for(i=0;i<sizeof(CBW);i++) pa[i] = pb[i]; return tf09_claim_interface(dev); } /******************************************************************************* 功能:从指定的USB设备上卸载内核驱动 注意:固定的卸载附加在interface 0上的驱动;此函数不需要客户调用 作者:宇浩然 时间:2012.04.21 参数:dev为设备指针 返回值:0成功 其他失败*********** *******************************************************************************/ static int tf09_detach_driver(tf09_device * dev) { struct usbdevfs_ioctl comm = {0, USBDEVFS_DISCONNECT, NULL}; return ioctl(dev->fd, USBDEVFS_IOCTL, &comm); } /******************************************************************************* 功能:在指定的USB设备上进行bulk读写操作;此函数屏蔽了内核16KB的限制 注意:用户不应该直接调用此函数 作者:宇浩然 时间:2011.12.26 参数:fd为设备handl,ep为端点号(1或129),data为数据缓冲地址,size为缓冲区大小,timeout为时间 返回值:<0失败;其他为实际读或写的字符数 *******************************************************************************/ static int usb_bulk(int fd, int ep, void* data, int size, int timeout) { int ret, currentsize, alreadysize=0; struct usbdevfs_bulktransfer bulk; bulk.ep = ep; bulk.timeout = timeout; while (alreadysize < size) { currentsize = size-alreadysize; if (currentsize > TF09_MAX_USB_SIZE) currentsize = TF09_MAX_USB_SIZE; bulk.len = currentsize; bulk.data = data; ret = ioctl(fd, USBDEVFS_BULK, &bulk); TF09_CHECK(ret, alreadysize); alreadysize += ret; if( 129 == ep && ret < currentsize) //读取数据时,实际读取数据未达到指定长度 break; data = (char*)data + ret; } return alreadysize; } /******************************************************************************* 功能:claim指定的USB设备的interface 注意:仅claim interface 0;此函数不需要客户调用 作者:宇浩然 时间:2012.04.21 参数:dev为设备指针 返回值:0为成功,<0失败 *******************************************************************************/ static int tf09_claim_interface(tf09_device * dev) { unsigned int interface = 0; return ioctl(dev->fd, USBDEVFS_CLAIMINTERFACE, &interface); } /******************************************************************************* 功能:与claim相反的操作 注意:仅interface 0;此函数不需要客户调用 作者:宇浩然 时间:2012.04.21 参数:dev为设备指针 返回值:0为成功,<0失败 *******************************************************************************/ static int tf09_release_interface(tf09_device * dev) { unsigned int interface = 0; return ioctl(dev->fd, USBDEVFS_RELEASEINTERFACE, &interface); } /******************************************************************************* 功能:返回指定USB设备对应的CBW指针 注意: 作者:宇浩然 时间:2012.05.25 参数:dev:设备,若为空,则使用默认设备; 返回值: CBW指针,若为NULL,则失败 *******************************************************************************/ CBW* tf09_get_cbw(const tf09_device* dev) { return (CBW*)(dev ? &(dev->cbw) : (tf09Device ? &(tf09Device->cbw) : NULL)); } /******************************************************************************* 功能:返回指定USB设备对应的协议参数指针 注意: 作者:宇浩然 时间:2012.05.25 参数:dev:设备,若为空,则使用默认设备; 返回值: 协议参数指针,若为NULL,则失败 *******************************************************************************/ tf09_comm_para * tf09_get_comm_para(const tf09_device* dev) { CBW* cbw = tf09_get_cbw((tf09_device*)dev); return cbw ? &(cbw->secPara) : NULL; } /******************************************************************************* 功能:回指定USB设备对应的协议参数指针,同时进行初始化 注意: 作者:宇浩然 时间:2012.04.22 参数:dev:设备,若为空,则使用默认设备; 返回值: 无 *******************************************************************************/ void tf09_comm_para_init(tf09_comm_para* secPara) { unsigned int i; for(i=0;i<6;i++) ((unsigned char *)secPara)[i] = 0xff; for(;i<sizeof(tf09_comm_para);i++) ((unsigned char *)secPara)[i] = 0; }复制代码基于Libusb无驱设计libusb是基于用户空间的usb库。libusb 设计了一系列的外部API 为应用程序所调用,通过这些API应用程序可以操作硬件,从libusb的源代码可以看出,这些API 调用了内核的底层接口,和kernel driver中所用到的函数所实现的功能差不多,只是libusb更加接近USB 规范。使得libusb的使用也比开发内核驱动相对容易的多。对于内核驱动的大部分设备,诸如带usb接口的hid设备,linux本身已经自带了相关的驱动,我们只要操作设备文件便可以完成对设备大部分的操作,而另外一些设备,诸如自己设计的硬件产品,这些驱动就需要我们驱动工程师开发出相关的驱动了。内核驱动有它的优点,然而内核驱动在某些情况下会遇到如下的一些问题:当使用我们产品的客户有2.4内核的平台,同时也有2.6内核的平台,我们要设计的驱动是要兼容两个平台的,就连makefile 我们都要写两个。当我们要把linux移植到嵌入平台上,你会发现原先linux自带的驱动移过去还挺大的,我的内核当然是越小越好拉,这样有必要么。这还不是最郁闷的地方,如果嵌入平台是客户的,客户要购买你的产品,你突然发现客户设备里的系统和你的环境不一样,它没有你要的驱动了,你的程序运行不了,你会先想:“没关系,我写个内核驱动加载一下不就行了“。却发现客户连insmod加载模块的工具都没移植,那时你就看看老天,说声我怎么那么倒霉啊,客户可不想你动他花了n时间移植的内核哦花了些功夫写了个新产品的驱动,挺有成就感啊,代码质量也是相当的有水准啊。正当你沉醉在你的代码中时,客服不断的邮件来了,“客户需要2.6.5内核的驱动,config文件我已经发你了”“客户需要双核的 2.6.18-smp 的驱动”“客户的平台是自己定制的是2.6.12-xxx “   你恨不得把驱动的源代码给客户,这样省得编译了。你的一部分工作时间编译内核,定制驱动有问题产生必然会有想办法解决问题的人, libusb的出现给我们带来了某些方便,即节约了我们的时间,也降低了公司的成本。所以在一些情况下,就可以考虑使用libusb的无驱设计了。用libusb之前你的linux系统必须装有usb文件系统,这里还介绍了使用hiddev设备文件来访问设备,目的在于不仅可以比较出usb的易用性,还提供了一个转化成libusb驱动的案例。find 设备任何驱动第一步首先是寻找到要操作的设备,我们先来看看HID驱动是怎样寻找到设备的。我们假设寻找设备的函数Device_Find(注:代码只是为了方便解说,不保证代码的健全)复制代码/* 我们简单看一下使用hid驱动寻找设备的实现,然后在看一下libusb是如何寻找设备的 */ int Device_Find( ) { char dir_str[100]; /* 这个变量我们用来保存设备文件的目录路径 */ char hiddev[100]; /* 这个变量用来保存设备文件的全路径 */ DIR dir; /* 申请的字符串数组清空,这个编程习惯要养成 */ memset (dir_str, 0 , sizeof(dir_str)); memset (hiddev, 0 , sizeof(hiddev)); /* hiddev 的设备描述符不在/dev/usb/hid下面,就在/dev/usb下面 这里我们使用opendir函数来检验目录的有效性 打开目录返回的值保存在变量dir里,dir前面有声明 */ dir = opendir("/dev/usb/hid"); if (dir){ /* 程序运行到这里,说明存在 /dev/usb/hid 路径的目录 */ sprintf(dir_str,"/dev/usb/hid/"); closedir(dir); } else { /* 如果不存在hid目录,那么设备文件就在/dev/usb下 */ sprintf(dir_str,"/dev/usb/"); } /* DEVICE_MINOR 是指设备数,HID一般是16个 */ for(i = 0; i < DEVICE_MINOR; i++) { /* 获得全路径的设备文件名,一般hid设备文件名是hiddev0 到 hiddev16 */ sprintf(hiddev, "%shiddev%d", dir_str,i); /* 打开设备文件,获得文件句柄 */ fd = open(hiddev, O_RDWR); if (fd > 0) { /* 操作设备获得设备信息 */ ioctl(fd, HIDIOCGDEVINFO, &info); /* VENDOR_ID 和 PRODUCT_ID 是标识usb设备厂家和产品ID, 驱动都需要这两个参数来寻找设备,到此我们寻找到了设备 */ if(info.vendor== VENDOR_ID && info.product== PRODUCT_ID) { /* 这里添加设备的初始化代码 */ device_num++; /* 找到的设备数 */ } close(fd); } } return device_num; /* 返回寻找的设备数量 */ }复制代码我们再来看libusb是如何来寻找和初始化设备 复制代码int Device_Find() { struct usb_bus *busses; int device_num = 0; device_num = 0; /* 记录设备数量 */ usb_init(); /* 初始化 */ usb_find_busses(); /* 寻找系统上的usb总线 */ usb_find_devices(); /* 寻找usb总线上的usb设备 */ /* 获得系统总线链表的句柄 */ busses = usb_get_busses(); struct usb_bus *bus; /* 遍历总线 */ for (bus = busses; bus; bus = bus->next) { struct usb_device *dev; /* 遍历总线上的设备 */ for (dev = bus->devices; dev; dev = dev->next) { /* 寻找到相关设备, */ if ((dev->descriptor.idVendor == VENDOR_ID) && (dev->descriptor.idProduct == PRODUCT_ID)) { /* 这里添加设备的初始化代码 */ device_num++; /* 找到的设备数 */ } } } return device_num; /* 返回设备数量 */ }复制代码注:在新版本的libusb中,usb_get_busses就可以不用了 ,这个函数是返回系统上的usb总线链表句柄 这里我们直接用usb_busses变量,这个变量在usb.h中被定义为外部变量所以可以直接写成这样:复制代码struct usb_bus *bus; for (bus = usb_busses; bus; bus = bus->next) { struct usb_device *dev; for (dev = bus->devices; dev; dev = dev->next) { /* 这里添加设备的初始化代码 */ } }复制代码打开设备 假设我们定义的打开设备的函数名是device_open,HID驱动实现复制代码/* 使用hid驱动打开设备 */ int Device_Open() { int handle; /* 传统HID驱动调用,通过open打开设备文件就可 */ handle = open("hiddev0", O_RDONLY); }复制代码libusb驱动实现复制代码/* 使用libusb打开驱动 */ int Device_Open() { /* LIBUSB 驱动打开设备,这里写的是伪代码,不保证代码有用 */ struct usb_device * udev; usb_dev_handle * device_handle; /* 当找到设备后,通过usb_open打开设备,这里的函数就相当open函数 */ device_handle = usb_open(udev); }复制代码读写设备和操作设备假设我们的设备使用控制传输方式,至于批处理传输和中断传输限于篇幅这里不介绍我们这里定义三个函数,D