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

内核中提供了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操作设备的用户空间驱动

时间: 2024-10-08 20:21:37

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

Linux usb子系统(二):USB设备驱动usb-skeleton.c

usb驱动分为通过usbfs操作设备的用户空间驱动,内核空间的内核驱动.两者不能同时进行,否则容易引发对共享资源访问的问题,死锁!使用了内核驱动,就不能在usbfs里驱动该设备. 下面转载的一篇分析usb-skeleton.c文章. 初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结当然不可缺,更何况我决定为嵌入式卖命了.好,言归正传,我说一说这段时间的收获,跟大家分享一下Linux的驱动开发.但这次只先针对Linux的USB子系统作分析,因为周五研讨老板催

Linux usb子系统(一):子系统架构

摘自:http://www.360doc.com/content/15/0519/05/22854460_471598740.shtml 摘自:https://www.cnblogs.com/cslunatic/p/3726053.html Linux usb子系统(一):子系统架构 一.USB协议基础知识   前序:USB概念概述 USB1.0版本速度1.5Mbps(低速USB) USB1.1版本速度12Mbps(全速USB)  USB2.0版本速度480Mbps(高速USB). USB 分为

Linux时间子系统(三) 用户空间接口函数

一.前言 从应用程序的角度看,内核需要提供的和时间相关的服务有三种: 1.和系统时间相关的服务.例如,在向数据库写入一条记录的时候,需要记录操作时间(何年何月何日何时). 2.让进程睡眠一段时间 3.和timer相关的服务.在一段指定的时间过去后,kernel要alert用户进程 本文主要描述和时间子系统相关的用户空间接口函数知识. 二.和系统时间相关的服务 1.秒级别的时间函数:time和stime time和stime函数的定义如下: #include <time.h> time_t ti

Linux USB 驱动开发(五)—— USB驱动程序开发过程简单总结

设备驱动程序是操作系统内核和机器硬件之间的接口,由一组函数和一些私有数据组成,是应用程序和硬件设备之间的桥梁.在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作. 设备驱动程序是内核的一部分,主要完成以下功能:对设备的初始化和释放:把数据从内核传送到硬件设备和从硬件设备读取数据:读取应用程序数据传送给设备文件和回送应用程序请求的数据:检测和处理硬件设备出现的错误. 一. Linux USB子系统分析 在Linux系统中,USB主机驱动程序由3部分组成:US

linux usb枚举过程分析之守护进程及其唤醒【转】

转自:http://blog.csdn.net/xuelin273/article/details/38646765 usb热插拔,即usb设备可以实现即插即用,像U盘一样,插到电脑里就可以用,不用时可以直接拔除,这个动作不会影响USB设备使用性能. 在linx 系统中,usb热插拔由两部分方面共同实现,即内核空间和用户空间,内核由一个守护进程实现,用户空间由udev 程序实现.在内核空间里,有一个专门用于监控usb hub的状态的守护进程,守护进程通过等待队列实现,等待队列平时处理休眠状态,当

Linux 设备驱动之 UIO 用户态驱动优缺点分析

[摘要]linux用户态的设备驱动开发:并不是所有的设备驱动程序都要在内核编写,有些情况下,在用户空间编写驱动程序能够更好地解决遇到的问题.本文对用户态驱动优缺点进行分析. 1.用户空间驱动程序的优点 1.可以和整个C库链接. 2.在驱动中可以使用浮点数,在某些特殊的硬件中,可能需要使用浮点数,而linux内核并不提供浮点数的支持.如果能在用户态实现驱动,就可以轻松解决这一问题. 3.驱动问题不会导致整个系统挂起.内核态驱动的一些错误常常导致整个系统挂起. 4.用户态的驱动调试方便. 5.可以给

聊聊Linux用户态驱动设计

序言 设备驱动可以运行在内核态,也可以运行在用户态,用户态驱动的利弊网上有很多的讨论,而且有些还上升到政治性上,这里不再多做讨论.不管用户态驱动还是内核态驱动,他们都有各自的缺点.内核态驱动的问题是:系统调用开销大:学习曲线陡峭:接口稳定性差:调试困难:bug致命:编程语言选择受限:而用户态驱动面临的挑战是:如何中断处理:如何DMA:如何管理设备的依赖关系:无法使用内核服务等.对此,<User-Space Device Drivers in Linux: A First Look> 一文有较详

linux用户空间和内核空间

When a process running in user mode requests additional memory, pages are allocated from the list of free page frames maintained by the kernel. This list is typically populated using a page-replacement algorithm such as those discussed in Section 9.4

【转】linux 用户空间与内核空间——高端内存详解

摘要:Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数据可能不在内存中.用户空间的内存映射采用段页式,而内核空间有自己的规则:本文旨在探讨内核空间的地址映射. Linux内核地址空间划分 通常32位Linux内核虚拟地址空间划分0~3G为用户空间,3~4G为内核空间(注意,内核可以使用的线性地址只有1G).注意这里是32位内核地址空间划分,64位