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 
#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)的描述符

你可能感兴趣的:(USB,Linux,Linux,Application)