Linux USB开发:libusb开发指南
置顶 2018年10月07日 20:18:44 crazy_baoli 阅读数 6123更多
分类专栏: Linux USB Linux Application USB开发
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u012247418/article/details/82960889
libusb学习笔记
ubuntu版本:ubuntu-gnome-16.04-desktop-amd64,gnome版
libusb版本 :2016-10-01: v1.0.21
作者:wang baoli
E-mail: baoliw@foxmail.com
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.
解决:
- sudo apt-get install automake
- sudo apt-get install libtool
- sudo autoreconf -ivf
- 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 目录分析
关于libusb的四个压力测试,不涉USB打开操作及具体的数据传输。
用于生成Android版本的libusb库、test和examples。进入android/jni/,执行ndk_build即可。在android/README中有以下描述:
- Download the latest NDK from: http://developer.android.com/tools/sdk/ndk/index.html
- Extract the NDK.
- Open a shell and make sure there exist an NDK global variable, set to the directory where you extracted the NDK.
- Change directory to libusb’s “android/jni”
- Run “ndk-build”.
- The libusb library, examples and tests can then be found in:“android/libs/$ARCH”
用于生成软件接口文档。编译完工程后,打开doc/doxygen.cfg,将PROJECT_LOGO = libusb.png修改为PROJECT_LOGO = ,否则产生文档时会提示 libusb.png不存在,修改完成后在doc/目录下执行:doxygen doxygen.cfg即可生成html格式文档,或者执行make docs。
注:Ubuntu需要提前安装doxygen。
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文件。
微软VC编译环境,目录下均是windows平台环境相关文件。
linux编译相关。m4 是一种宏处理器,它扫描用户输入的文本并将其输出,期间如果遇到宏就将其展开后输出。
apple平台相关文件。Xcode是苹果的集成开发环境(IDE),开发者可用其构建适用于苹果iPad、iPhone以及Mac设备的应用程序。在应用程序的创建、测试、优化以及提交至App Store的过程中,Xcode为开发者提供了用以管理整个开发工作流的工具。
libusb的测试demo,进入目录后执行make即可生成可执行文件进行测试。
getopt现在已经是C函数库的一部分,没有编译使用,删除此目录不会有影响。
热插拔测试demo
获取系统当前的usb设备列表,并打印出VID、PID、bus和device编号
打印usb设备列表的详细信息:包括设备描述符、配置、接口、端点描述符
一款指纹识别器的应用程序:URU4000B fingerprint scanner 应用程序,将采集到的指纹图像保存为文件。系统采用异步传输的方式,使用了control、interrupt、bulk三种传输方式。不仅使用了libusb_control_transfer等同步接口传输,也使用了libusb_submit_transfer的异步传输方式。
与dpfp.c功能一致,代码也大部分相同,唯一不同在于dpfp.c将libusb_handle_events 放在 main loop中,而dpfp_threaded.c 将libusb_handle_events 放在一个线程当中。
Atmel SAM3U isochronous(等时传输)性能测试。程序不断接收来自SAM3U iso端点的数据,当按下CTRL-C时,计算花费时间和传输的总数据量。
一个综合的USB测试程序,包括:HID设备(xbox、PS3和Joystick)、Mass Storage,涉及中断、批量和控制传输。其中Mass Storage可以使用普通的U盘进行测试,只需修改VID和PID即可,可以实现的功能有:读取描述符、查询U盘信息、读取U盘容量、读取U盘数据(因为没有使用文件系统,读取出来的数据是原始二进制数据)。
关于Mass Storage中涉及的SCSI命令,参考: USB Mass Stroage - SCSI指令格式详解。
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”。
ChangeLog:代码修改日志。2008-05-25: v0.9.0 release,目前最新版2016-10-01: v1.0.21
INSTALL:编译、安装方法。编译器选项,如: ./configure CC=c99 CFLAGS=-g LIBS=-lposix
PORTING:移植libusb到其他未支持平台的方法。
注:
- Atmel SAM3U:基于ARM Cortex M3内核的MCU,支持usb high speed。
- 关于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
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类似的选项
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;
};
- 1
- 2
- 3
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链表(这里主要指添加设备)。
- 调用libusb_init初始化时,libusb调用linux_scan_devices获取系统当前所有usb设备,并将其添加到ctx->usb_devs链表。
- 在hotplug监听线程中,如果有设备插入,便将其添加到ctx->usb_devs链表。
- 用户调用libusb_get_device_list时,再一次查看是否有新设备插入,如果有便将其添加到ctx->usb_devs链表。
2.11 控制传输
用户可以使用libusb_control_transfer() 进行控制传输。
控制传输既可以在系统枚举阶段进行,也可以在打开USB设备后进行传输,如在xusb.c中便有很多地方用到了控制传输:获取HID设备的报告描述符等。
2.12 头文件说明
_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
为了在C++代码支持libusb库,在头文件中可见以下代码:
#ifdef __cplusplus
extern "C" {
#endif
// 代码
#ifdef __cplusplus
}
#endif
#if, #elif, #else, #endif
#if defined()和#if !defined()
2.13 其它
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)的描述符