libusb開發指南

libusb學習筆記

ubuntu版本:ubuntu-gnome-16.04-desktop-amd64,gnome版
libusb版本 :2016-10-01: v1.0.21
作者:wang baoli
E-mail: [email protected]

libusb學習網站:

website:http://libusb.info/
API:http://libusb.sourceforge.net/api-1.0/
download:https://github.com/libusb/libusb
mailing list:http://mailing-list.libusb.info
libusb test demo:https://github.com/crazybaoli/libusb-test

1. 編譯及安裝

下載libusb源碼,進入目錄,shell下依次執行下列命令。

1.1 執行:./configure

提示:configure: error: “udev support requested but libudev not installed”
解決:

sudo apt-get install libudev-dev

1.2 執行:make

提示:‘aclocal-1.14’ is missing on your system.
解決:

  1. sudo apt-get install automake
  2. sudo apt-get install libtool
  3. sudo autoreconf -ivf
  4. make

1.3 安裝:make install

執行:sudo make install
libusb-1.0.a 和 libusb-1.0.so 被安裝到 /usr/local/lib/ 目錄
libusb.h 被安裝到 /usr/local/include/libusb-1.0/ 目錄

注:在這以後可以直接執行:./configure && make && make install

2. 源碼學習

libusb採用的linux技術:sysfs、libudev、netlink、pipe、thread、hotplug

2.1 目錄分析

tests/

關於libusb的四個壓力測試,不涉USB打開操作及具體的數據傳輸。

android/

用於生成Android版本的libusb庫、test和examples。進入android/jni/,執行ndk_build即可。在android/README中有以下描述:

  1. Download the latest NDK from: http://developer.android.com/tools/sdk/ndk/index.html
  2. Extract the NDK.
  3. Open a shell and make sure there exist an NDK global variable, set to the directory where you extracted the NDK.
  4. Change directory to libusb’s “android/jni”
  5. Run “ndk-build”.
  6. The libusb library, examples and tests can then be found in:“android/libs/$ARCH”

doc/

用於生成軟件接口文檔。編譯完工程後,打開doc/doxygen.cfg,將PROJECT_LOGO = libusb.png修改爲PROJECT_LOGO = ,否則產生文檔時會提示 libusb.png不存在,修改完成後在doc/目錄下執行:doxygen doxygen.cfg即可生成html格式文檔,或者執行make docs。
注:Ubuntu需要提前安裝doxygen。

libusb/

libusb的核心代碼。
1)os/目錄是是平臺相關的代碼,支持:darwin、haiku、linux、windows、sunos、netbsd、openbsd等七種平臺,即Linux, OS X, Windows, Windows CE, Android, OpenBSD/NetBSD, Haiku。
2)libusb-1.0.def DLL中導出函數的聲明的一種方式:採用模塊定義(.def) 文件聲明,.def文件爲鏈接器提供了有關被鏈接程序的導出、屬性及其他方面的信息。
3)libusb-1.0.rc 用於windows,產生 .res文件。

msvc/

微軟VC編譯環境,目錄下均是windows平臺環境相關文件。

m4/

linux編譯相關。m4 是一種宏處理器,它掃描用戶輸入的文本並將其輸出,期間如果遇到宏就將其展開後輸出。

Xcode/

apple平臺相關文件。Xcode是蘋果的集成開發環境(IDE),開發者可用其構建適用於蘋果iPad、iPhone以及Mac設備的應用程序。在應用程序的創建、測試、優化以及提交至App Store的過程中,Xcode爲開發者提供了用以管理整個開發工作流的工具。

examples/

libusb的測試demo,進入目錄後執行make即可生成可執行文件進行測試。

1)getopt/

getopt現在已經是C函數庫的一部分,沒有編譯使用,刪除此目錄不會有影響。

2)hotplugtest.c

熱插拔測試demo

3)listdevs.c

獲取系統當前的usb設備列表,並打印出VID、PID、bus和device編號

4)testlibusb.c

打印usb設備列表的詳細信息:包括設備描述符、配置、接口、端點描述符

5)dpfp.c

一款指紋識別器的應用程序:URU4000B fingerprint scanner 應用程序,將採集到的指紋圖像保存爲文件。系統採用異步傳輸的方式,使用了control、interrupt、bulk三種傳輸方式。不僅使用了libusb_control_transfer等同步接口傳輸,也使用了libusb_submit_transfer的異步傳輸方式。

6)dpfp_threaded.c

與dpfp.c功能一致,代碼也大部分相同,唯一不同在於dpfp.c將libusb_handle_events 放在 main loop中,而dpfp_threaded.c 將libusb_handle_events 放在一個線程當中。

7)sam3u_benchmark.c

Atmel SAM3U isochronous(等時傳輸)性能測試。程序不斷接收來自SAM3U iso端點的數據,當按下CTRL-C時,計算花費時間和傳輸的總數據量。

8)xusb.c

一個綜合的USB測試程序,包括:HID設備(xbox、PS3和Joystick)、Mass Storage,涉及中斷、批量和控制傳輸。其中Mass Storage可以使用普通的U盤進行測試,只需修改VID和PID即可,可以實現的功能有:讀取描述符、查詢U盤信息、讀取U盤容量、讀取U盤數據(因爲沒有使用文件系統,讀取出來的數據是原始二進制數據)。
關於Mass Storage中涉及的SCSI命令,參考: USB Mass Stroage - SCSI指令格式詳解。

9)fxload.c和ezusb.c

EZ-USB的固件下載程序,可實現下載固件(image)到Cypress EZ-USB microcontrollers,ezusb系列芯片使用端點0和廠商特定命令將數據寫到片上SRAM,並且也支持寫數據到CPUCS register或者eeprom。
程序使用控制傳輸方式進行指令和數據的傳輸,libusb_control_transfer()的形參bmRequestType使用LIBUSB_REQUEST_TYPE_VENDOR(廠商自定義請求)。程序支持五種下載類型(Target type): an21, fx, fx2, fx2lp, fx3,支持四種固件(image)類型:“Intel HEX”, “Cypress 8051 IIC”, “Cypress 8051 BIX”, “Cypress IMG format”。

10)other

ChangeLog:代碼修改日誌。2008-05-25: v0.9.0 release,目前最新版2016-10-01: v1.0.21
INSTALL:編譯、安裝方法。編譯器選項,如: ./configure CC=c99 CFLAGS=-g LIBS=-lposix
PORTING:移植libusb到其他未支持平臺的方法。

注:

  1. Atmel SAM3U:基於ARM Cortex M3內核的MCU,支持usb high speed。
  2. 關於ezusb的介紹:
    http://www.linux-usb.org/ezusb/
    http://www.cypress.com/
    EZ-USB FX是CYPRESS公司出品的一種帶有USB功能的8051兼容系列,封裝採用PQFP。這一系列芯片的最大不同之處在於使用不同的方式存儲固件,EZ-USB FX可以在一個串行EEPROM中存儲固件,也可以在主機上存儲固件。當設備連接主機後,這些固件通過USB總線傳輸到芯片中。這樣做最大的好處就是固件容易升級,不需要替換芯片或使用特殊的程序,只要在主機上更新固件即可。
    CY7C61083A是一款FX2LP芯片,支持full/high speed,應用:MP3、讀卡器、照相機等等

2.2 權限問題

當open USB時需要提供root權限,這同打開串口一樣,對底層硬件操作都需要root權限。

2.3 函數調用圖

如果能繪製出函數調用關係圖會更有利於分析代碼。可以採用callfraph,但有些具有特殊返回值的函數不能被識別,並且不能跨文件尋找調用關係(可能是我沒有正確的使用)。可以直接採用dot語言手動繪圖。

2.4 log

2.4.1 修改log輸出

libusb 日誌默認輸出到stderr,如果我們想輸出libusb 的log至syslog,有以下兩種方法:
方法1:./configure --enable-system-log
修改結果會反饋在./config.h中,增加了USE_SYSTEM_LOGGING_FACILITY宏
查看syslog:cat /var/log/syslog

Nov 22 09:51:07 ubuntu libusb-test: libusb: error [_get_usbfs_fd] libusb couldn’t open USB device /dev/bus/usb/002/018: Permission denied.

方法2:./configure CFLAGS=-DUSE_SYSTEM_LOGGING_FACILITY=1
或者:CFLAGS=-DUSE_SYSTEM_LOGGING_FACILITY=1 ./configure
修改結果反饋在./Makefile文件中,使CFLAGS = -DUSE_SYSTEM_LOGGING_FACILITY=1,這會覆蓋原來的cflags 。
當然,也可在執行./configure後,直接修改Makefile中的cflags選項。
推薦採用方法1.
注:可以使用 ./configur --help 來獲取–enable-system-log類似的選項

2.4.2 設置debug level

using libusb_set_debug() or the LIBUSB_DEBUG environment variable

除了可以使用libusb_set_debug()函數,也可以通過環境變量LIBUSB_DEBUG 來設置debug level。

2.5 open

USB設備文件對應路徑爲:“/dev/bus/usb/xxx/xxx”,使用了udev文件系統。
libusb通過打開設備文件:/dev/bus/usb/bus編號/device地址 來打開USB,stm32 USB device 對應/dev/bus/usb/002/020。可以使用lsusb來查看bus和device地址。
libusb優先使用udev文件系統打開usb設備,其次選擇usbfs文件系統:/proc/bus/usb/來打開usb設備。Linux2.6採用了usbfs文件系統:/proc/bus/usb,在Ubuntu16.4上沒有usbfs。
分析代碼可知:

static const char *find_usbfs_path(void)
{
	const char *path = "/dev/bus/usb";
	const char *ret = NULL;

	if (check_usb_vfs(path)) {
		ret = path;
	} else {
		path = "/proc/bus/usb";
		if (check_usb_vfs(path))
			ret = path;
	}

	/* look for /dev/usbdev*.* if the normal places fail */
	if (ret == NULL) {
		struct dirent *entry;
		DIR *dir;

		path = "/dev";
		dir = opendir(path);
		if (dir != NULL) {
			while ((entry = readdir(dir)) != NULL) {
				if (_is_usbdev_entry(entry, NULL, NULL)) {
					/* found one; that's enough */
					ret = path;
					usbdev_names = 1;
					break;
				}
			}
			closedir(dir);
		}
	}


#if defined(USE_UDEV)
	if (ret == NULL)
		ret = "/dev/bus/usb";
#endif

	if (ret != NULL)
		usbi_dbg("found usbfs at %s", ret);

	return ret;
}

stm32設備在linux sysfs文件系統的路徑: /sys/bus/usb/devices/2-2.1,由內核向用戶空間導出設備的數據結構及屬性,可以修改和訪問。libusb中有大量通過sysfs來獲得usb設備屬性的用法,如獲取usb 速度:
speed = (DEVICE_CTX(dev), sysfs_dir, "speed");
我們也可以使用 cat /sys/bus/usb/devices/2-2.1/speed 來獲取usb速度。
由於目錄/sys/bus/usb/devices/經常被使用,在libusb源碼中有以下宏定義:
#define SYSFS_DEVICE_PATH "/sys/bus/usb/devices"

2.6 結構體亂序初始化

linux結構體可以採用亂序初始化,即用成員變量前加(.)符號,如定義linux_usbfs_backend 結構體變量時就採用了這種方法:

const struct usbi_os_backend linux_usbfs_backend = {
	.name = "Linux usbfs",
	.caps = 
	.init = op_init,
	.exit = op_exit,
	.get_device_list = NULL,
  .....
}

亂序初始化是C99標準新加的,比較直觀的一種初始化方式。相比順序初始化而言,亂序初始化就如其名,成員可以不按照順序初始化,而且可以只初始化部分成員,擴展性較好。linux內核中採用這種方式初始化struct。

2.7 數據傳輸

libusb的數據傳輸通過向內核提交URB來實現:
ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb);
而非使用read、write等讀寫函數。

2.8 hotplug

libusb的熱插拔事件有兩種:arrived 、left。
hotplug事件的底層支持機制:udev或者netlink,udev的熱插拔機制是基於netlink實現。
libusb對於產生的熱插拔消息在handle_events()中進行處理,然後調用用戶註冊的回調函數。
libusb內部專門開闢了一個線程來監聽是否有USB設備插拔,並通過netlink或是udev兩種方式來實現監聽hotplug。優先採取udev方式,當系統不支持udev時,便採用netlink方式。其實udev的熱插拔機制也是基於netlink實現。
實現原理:
當libusb在熱插拔監聽線程(linux_udev_event_thread_main or linux_netlink_event_thread_main)中接收到內核的hotplug消息,libusb首先將消息添加到ctx->hotplug_msgs中,然後在通過管道(ctx->event_pipe)將hotplug事件發送出去。在用戶創建的monitor線程裏,調用libusb_handle_events()進行事件處理,具體做法是調用poll監聽管道,一旦ctx->event_pipe[0]可讀,便讀取hotplug_msgs,經過usbi_hotplug_match_cb()函數判斷VID、PID以及device class符合後再調用用戶的回調函數。
注:同一個進程中也可以使用管道進行通信。

2.9 LIST

libusb實現了循環雙向鏈表,並且只有前向和後向指針,無數據成員,實現方法上也很獨特。應用時作爲其它數據結構的成員,可通過list_entry宏來獲得這個數據結構指針。

struct list_head {
	struct list_head *prev, *next;
};

void list_init(struct list_head *entry)
初始化一個鏈表

void list_add_tail(struct list_head *entry, struct list_head *head)
list_add_tail和list_add都是形成雙向循環鏈表,只是實現上有一點不同而已。
將節點entry添加到鏈表head的尾部,使head->prev指向entry,(head->next固定指向了鏈表中的第二個節點)

void list_add(struct list_head *entry, struct list_head *head)
將節點entry添加到鏈表head的尾部,使head->next指向entry,(head->prev固定指向了鏈表中的第二個節點)

void list_del(struct list_head *entry)
刪除一個鏈表

list_empty(entry)
判斷鏈表是否爲空

list_entry(ptr, type, member)
取得包含ptr所指結構體的對象的指針:返回type類型指針,這個type類型指針指向的對象包含這個節點。

ptr:list_head 結構體指針
type:包含member成員的數據類型
member:type數據類型裏的成員,member爲list_head類型

list_for_each_entry(pos, head, member, type)
遍歷head鏈表中的每個節點(entry),pos指向每次遍歷的結果。pos爲type類型結構體指針,這個結構體包含list_head 結構體成員。

pos:一個包含member成員的結構體指針
head:list head
member:pos指針指向的結構體裏的list_head 結構體成員
type:pos的數據類型

2.10 獲取USB設備列表

用戶使用ssize_t libusb_get_device_list(libusb_context *ctx, libusb_device ***list)函數即可獲得系統當前所有的USB設備。
libusb將接入的usb設備都保存在ctx->usb_devs鏈表中,libusb_get_device_list函數便是通過它來取得設備列表。
libusb通過三種途徑來維護ctx->usb_devs鏈表(這裏主要指添加設備)。

  1. 調用libusb_init初始化時,libusb調用linux_scan_devices獲取系統當前所有usb設備,並將其添加到ctx->usb_devs鏈表。
  2. 在hotplug監聽線程中,如果有設備插入,便將其添加到ctx->usb_devs鏈表。
  3. 用戶調用libusb_get_device_list時,再一次查看是否有新設備插入,如果有便將其添加到ctx->usb_devs鏈表。

2.11 控制傳輸

用戶可以使用libusb_control_transfer() 進行控制傳輸。
控制傳輸既可以在系統枚舉階段進行,也可以在打開USB設備後進行傳輸,如在xusb.c中便有很多地方用到了控制傳輸:獲取HID設備的報告描述符等。

2.12 頭文件說明

(1)宏定義_MSC_VER

_MSC_VER是微軟的預編譯控制,由於vc++不支持stdbool.h,所以有某些頭文件有以下代碼以便支持bool變量。

#if !defined(_MSC_VER)
#include <stdbool.h>
#else
#define __attribute__(x)
#if !defined(bool)
#define bool int
#endif
#if !defined(true)
#define true (1 == 1)
#endif
#if !defined(false)
#define false (!true)
#endif
#if defined(_PREFAST_)
#pragma warning(disable:28193)
#endif
#endif

(2) C++支持

爲了在C++代碼支持libusb庫,在頭文件中可見以下代碼:

#ifdef  __cplusplus
extern "C" {
#endif

// 代碼

#ifdef  __cplusplus
}
#endif

(3)條件編譯的使用

#if, #elif, #else, #endif
#if defined()和#if !defined()

2.13 其它

宏定義_WIN32

VC 有 3 個預處理常量,分別是 _WIN32、_WIN64、WIN32,WIN32和_WIN32 可以用來判斷是否 Windows 系統(對於跨平臺程序),而 _WIN64 用來判斷編譯環境是 x86 還是 x64。

3. libusb測試demo

github:https://github.com/crazybaoli/libusb-test

  • 支持bulk/interrupt endpoint 數據讀寫
  • 支持hotplug
  • 支持命令行參數
  • 支持快捷發送數據
  • 支持將收到的數據保存爲文件
  • 支持lsusb功能,可列出系統所有usb設備
  • 支持打印顯示特定usb設備(VID:PID)的描述符
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章