内核中提供了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鼠标):
[email protected]:/proc/bus/usb# cat devices
T: 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=256ms
T: 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=256ms
T: 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=10ms
T: 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 <stdio.h> #include <stdlib.h> #include <sys/ioctl.h> #include <linux/usbdevice_fs.h> #include <dirent.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #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本身已经自带了相关的驱动,我们只要操作设备文件便可以完成对设备大部分的操作,而另外一些设备,诸如自己设计的硬件产品,这些驱动就需要我们驱动工程师开发出相关的驱动了。内核驱动有它的优点,然而内核驱动在某些情况下会遇到如下的一些问题:
1 当使用我们产品的客户有2.4内核的平台,同时也有2.6内核的平台,我们要设计的驱动是要兼容两个平台的,就连makefile 我们都要写两个。
2 当我们要把linux移植到嵌入平台上,你会发现原先linux自带的驱动移过去还挺大的,我的内核当然是越小越好拉,这样有必要么。这还不是最郁闷的地方,如果嵌入平台是客户的,客户要购买你的产品,你突然发现客户设备里的系统和你的环境不一样,它没有你要的驱动了,你的程序运行不了,你会先想:“没关系,我写个内核驱动加载一下不就行了“。却发现客户连insmod加载模块的工具都没移植,那时你就看看老天,说声我怎么那么倒霉啊,客户可不想你动他花了n时间移植的内核哦
3 花了些功夫写了个新产品的驱动,挺有成就感啊,代码质量也是相当的有水准啊。正当你沉醉在你的代码中时,客服不断的邮件来了,“客户需要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); }
读写设备和操作设备
假设我们的设备使用控制传输方式,至于批处理传输和中断传输限于篇幅这里不介绍
我们这里定义三个函数,Device_Write, Device_Read, Device_Report
Device_Report 功能:发送接收函数
Device_Write 功能:写数据
Device_Read 功能:读数据
Device_Write和Device_Read调用Device_Report发送写的信息和读的信息,开发者根据发送的命令协议来设计,我们这里只简单实现发送数据的函数。
假设我们要给设备发送72字节的数据,头8个字节是报告头,是我们定义的和设备相关的规则,后64位是数据。
HID驱动的实现(这里只是用代码来有助理解,代码是伪代码)
int Device_Report(int fd, unsigned char *buffer72) { int ret; /* 保存ioctl函数的返回值 */ int index; unsigned char send_data[72]; /* 发送的数据 */ unsigned char recv_data[72]; /* 接收的数据 */ struct hiddev_usage_ref uref; /* hid驱动定义的数据包 */ struct hiddev_report_info rinfo; /* hid驱动定义的 */ memset(send_data, 0, sizeof(send_data)); memset(recv_data, 0, sizeof(recv_data)); memcpy(send_data, buffer72, 72); /* 这在发送数据之前必须调用的,初始化设备 */ ret = ioctl(fd, HIDIOCINITREPORT, 0); if (ret != 0) { return NOT_OPENED_DEVICE;/* NOT_OPENED_DEVICE 属于自己定义宏 */ } /* HID设备每次传输一个字节的数据包 */ for(index = 0; index < 72; index++) { /* 设置发送数据的状态 */ uref.report_type = HID_REPORT_TYPE_FEATURE; uref.report_id = HID_REPORT_ID_FIRST; uref.usage_index = index; uref.field_index = 0; uref.value = send_data[index]; ioctl(fd, HIDIOCGUCODE, &uref); ret = ioctl(fd, HIDIOCSUSAGE, &uref); if (ret != 0) { return UNKNOWN_ERROR; } } /* 发送数据 */ rinfo.report_type = HID_REPORT_TYPE_FEATURE; rinfo.report_id = HID_REPORT_ID_FIRST; rinfo.num_fields = 1; ret=ioctl(fd, HIDIOCSREPORT, &rinfo); /* 发送数据 */ if (ret != 0) { return WRITE_REPORT; } /* 接受数据 */ ret = ioctl(fd, HIDIOCINITREPORT, 0); for (index = 0; index < 72; index++) { uref.report_type = HID_REPORT_TYPE_FEATURE; uref.report_id = HID_REPORT_ID_FIRST; uref.usage_index = index; uref.field_index = 0; ioctl(fd, HIDIOCGUCODE, &uref); ret = ioctl(fd, HIDIOCGUSAGE, &uref); if (ret != 0) { return UNKNOWN_ERROR; } recv_data[index] = uref.value; } memcpy(buffer72, recv_data, 72); return SUCCESS; }
libusb驱动的实现
int Device_Report(int fd, unsigned char *buffer72) { /* 定义设备句柄 */ usb_dev_handle* Device_handle; /* save the data of send and receive */ unsigned char send_data[72]; unsigned char recv_data[72]; int send_len; int recv_len; /* 数据置空 */ memset(send_data, 0 , sizeof(send_data)); memset(recv_data, 0 , sizeof(recv_data)); /* 这里的g_list是全局的数据变量,里面可以存储相关设备的所需信息, 当然我们 也可以从函数形参中传输进来,设备的信息在打开设备时初始化, 我们将在后面的总结中详细描述一下 */ Device_handle = (usb_dev_handle*)(g_list[fd].device_handle); if (Device_handle == NULL) { return NOT_OPENED_DEVICE; } /* 这个函数前面已经说过,在操作设备前是必须调用的, 0是指用默认的设备 */ usb_claim_interface(Device_handle, 0); /* 发送数据,所用到的宏定义在usb.h可以找到,我列出来大家看一下 #define USB_ENDPOINT_OUT (0x00) #define USB_TYPE_CLASS (0x01 << 5) #define USB_RECIP_INTERFACE (0x01) #define HID_REPORT_SET (0x09) */ send_len = usb_control_msg(Device_handle, USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE, HID_REPORT_SET, 0x300, 0, send_data, 72, USB_TIMEOUT); /* 发送数据有错误 */ if (send_len < 0) { return WRITE_REPORT; } if (send_len != 72) { return send_len; } /* 接受数据 #define USB_ENDPOINT_IN (0x80) #define USB_TYPE_CLASS (0x01 << 5) #define USB_RECIP_INTERFACE (0x01) #define HID_REPORT_GET (0x01) */ recv_len = usb_control_msg(Device_handle, USB_ENDPOINT_IN + USB_TYPE_CLASS + USB_RECIP_INTERFACE, HID_REPORT_GET, 0x300, 0, recv_data, 72, USB_TIMEOUT); if (recv_len < 0) { printf("failed to retrieve report from USB device!/n"); return READ_REPORT; } if (recv_len != 72) { return recv_len; } /* 和usb_claim_interface对应 */ usb_release_interface(RY2_handle, 0); memcpy(buffer72, recv_data, 72); return SUCCESS; }
关闭设备
假设我们定义的关闭设备的函数名是Device_Close()
HID驱动实现
/* 使用hid驱动关闭设备 */ int Device_Close() { int handle; handle = open("hiddev0", O_RDONLY); /* 传统HID驱动调用,通过close()设备文件就可 */ close( handle ); }
libusb驱动实现
/* 使用libusb关闭驱动 */ int Device_Close() { /* LIBUSB 驱动打开设备,这里写的是伪代码,不保证代码有用 */ struct usb_device * udev; usb_dev_handle * device_handle; device_handle = usb_open(udev); /* libusb库使用usb_close关闭程序 */ usb_close(device_handle); }
libusb 的驱动框架
前面我们看了些主要的libusb函数的使用,这里我们把前面的内容归纳下:
一般的驱动应该都包含如下接口:
Device_Find(); /* 寻找设备接口 */
Device_Open(); /* 打开设备接口 */
Device_Write(); /* 写设备接口 */
Device_Read(); /* 读设备接口 */
Device_Close(); /* 关闭设备接口 */
具体代码如下:
#include <usb.h> /* usb.h这个头文件是要包括的,里面包含了必须要用到的数据结构 */ /* 我们将一个设备的属性用一个结构体来概括 */ typedef struct { struct usb_device* udev; usb_dev_handle* device_handle; /* 这里可以添加设备的其他属性,这里只列出每个设备要用到的属性 */ } device_descript; /* 用来设置传输数据的时间延迟 */ #define USB_TIMEOUT 10000 /* 厂家ID 和产品 ID */ #define VENDOR_ID 0xffff #define PRODUCT_ID 0xffff /* 这里定义数组来保存设备的相关属性,DEVICE_MINOR可以设置能够同 时操作的设备数量,用全局变量的目的在于方便保存属性 */ #define DEVICE_MINOR 16 int g_num; device_descript g_list[ DEVICE_MINOR ]; /* 我们写个设备先找到设备,并把相关信息保存在 g_list 中 */ int Device_Find() { struct usb_bus *bus; struct usb_device *dev; g_num = 0; usb_find_busses(); usb_find_devices(); /* 寻找设备 */ for (bus = usb_busses; bus; bus = bus->next) { for (dev = bus->devices; dev; dev = dev->next) { if(dev->descriptor.idVendor==VENDOR_ID && dev->descriptor.idProduct == PRODUCT_ID) { /* 保存设备信息 */ if (g_num < DEVICE_MINOR) { g_list[g_num].udev = dev; g_num ++; } } } } return g_num; } /* 找到设备后,我们根据信息打开设备 */ int Device_Open() { /* 根据情况打开你所需要操作的设备,这里我们仅列出伪代码 */ if(g_list[g_num].udev != NULL) { g_list[g_num].device_handle = usb_open(g_list[g_num].udev); } } /* 下面就是操作设备的函数了,我们就不列出来拉,大家可以参考上面的介绍 */ int DeviceWite(int handle) { /* 填写相关代码,具体查看设备协议*/ } int DeviceOpen(int handle) { /* 填写相关代码,具体查看设备协议 */ } /* 最后不要忘记关闭设备 */ void Device_close(int handle) { /* 调用usb_close */ }
到此,使用libusb进行驱动开发介绍完了,通过对库所提供的API的使用可以体会到libusb的易用性。
Linux usb子系统(三):通过usbfs操作设备的用户空间驱动