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鼠標):

root@dvr:/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的易用性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM