___2017

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

一、listdevs 简介

listdevs 用于获取并显示系统当前的 USB 设备信息,包含:VID、PID、bus 编号、设备地址、端口号。

$ ./listdevs 
1d6b:0002 (bus 1, device 1)
0e0f:0002 (bus 2, device 3) path: 2
0e0f:0003 (bus 2, device 2) path: 1
1d6b:0001 (bus 2, device 1)

 

二、listdevs 入口

int main(void)
{
	libusb_device **devs;
	int r;
	ssize_t cnt;

	r = libusb_init(NULL);
	if (r < 0)
		return r;

	cnt = libusb_get_device_list(NULL, &devs);
	if (cnt < 0){
		libusb_exit(NULL);
		return (int) cnt;
	}

	print_devs(devs);
	libusb_free_device_list(devs, 1);

	libusb_exit(NULL);
	return 0;
}

 

main 函数很清晰简洁,这也说明 libusb 库的封装非常好。

骨架函数就三个:libusb_init() -> libusb_get_device_list() -> print_devs(),分别对应 libusb 初始化,获取 USB 设备列表,打印 USB 设备列表(信息)。

libusb 规定,在调用任何 libusb 功能函数之前都需要 libusb_init(),在分析 libusb_init() 之前先来看下 libusb_device{} 结构体:

struct libusb_device {
	/* lock protects refcnt, everything else is finalized at initialization
	 * time */
	usbi_mutex_t lock;
	int refcnt;

	struct libusb_context *ctx;
	struct libusb_device *parent_dev;

	uint8_t bus_number;
	uint8_t port_number;
	uint8_t device_address;
	enum libusb_speed speed;

	struct list_head list;
	unsigned long session_data;

	struct libusb_device_descriptor device_descriptor;
	int attached;
}

  

libusb_device{} 描述一个 USB 设备的信息:

lock/refcnt:引用计数,一旦有其他地方引用到该设备,refcnt 值加一;避免设备在其他模块内部使用的情况下被销毁。自动指针、动态内存管理里面广泛使用引用计数。

libusb_context:该设备所属的上下文,官方术语叫“libusb session”。通过在 libusb_init() 时指定互相独立的“libusb session”,进而允许你的程序独立的使用 libusb,而不会因为其他地方调用 libusb_exit() 而销毁自己正在使用的资源。“libusb session”使用 session_id 唯一标识。

parent_dev:当前设备所属的父设备(通常指控制器/Hub)。

bus_number/port_number/device_address/speed:这些是 USB 规范及内核驱动里的概念,speed 标识设备速率(低速,全速,高速,超高速),device_address 标识内核驱动枚举时为设备分配的地址,port_number 标识设备占用的 Hub 端口号,bus_number 标识设备所属的总线编号。

list:每一个“libusb session”有个 usb_devs 链表,usb_devs 链表上保存属于该 session 的 USB 设备。在构件完成一个设备之后,就把它加入到自己所属的 usb_devs 链表上。

session_data:保存 session_id。

device_descriptor:顾名思义,该设备的设备描述符。

attached:标识设备状态是 attached(已连接上主机)还是 detached(未连接主机)。

接下来就看下 libusb_init() 做了哪些事情。

 

三、libusb_init() 分析

 3.1 libusb_init() 注释

/** \ingroup libusb_lib
 * Initialize libusb. This function must be called before calling any other
 * libusb function.
 *
 * If you do not provide an output location for a context pointer, a default
 * context will be created. If there was already a default context, it will
 * be reused (and nothing will be initialized/reinitialized).
 *
 * \param context Optional output location for context pointer.
 * Only valid on return code 0.
 * \returns 0 on success, or a LIBUSB_ERROR code on failure
 * \see libusb_contexts
 */
int libusb_init(libusb_context **context) {}

libusb_init()初始化 libusb 内部资源,所以在使用 libusb 其他函数之前,必需优先调用该函数。
context:listdevs 这个demo中入参为 NULL,即使用默认的 libusb_context,即 usbi_default_context,在 3.5 小节对其进行创建。

3.2 struct timespec timestamp_origin

int libusb_init(libusb_context **context)
{
	if (!timestamp_origin.tv_sec)
		usbi_get_monotonic_time(&timestamp_origin);
}

调用 clock_gettime() 初始化 timestamp_origin 全局变量,timestamp_origin 用于 log 打印的时间戳显示。

3.3 又是引用计数

int libusb_init(libusb_context **context)
{
	if (!context && usbi_default_context) {
		usbi_dbg("reusing default context");
		default_context_refcnt++;
		usbi_mutex_static_unlock(&default_context_lock);
		return 0;
	}
}

 

3.4 log 设置

int libusb_init(libusb_context **context)
{
	ctx->debug = get_env_debug_level();
	if (ctx->debug != LIBUSB_LOG_LEVEL_NONE)
		ctx->debug_fixed = 1;
}

既然是分析源码,我们自然要把 log 输出都打印出来,所以修改 get_env_debug_level() 函数,使其返回 LIBUSB_LOG_LEVEL_DEBUG。

3.5 创建 usbi_default_context

int libusb_init(libusb_context **context)
{
	/* default context should be initialized before calling usbi_dbg */
	if (!usbi_default_context) {
		usbi_default_context = ctx;
		default_context_refcnt++;
		usbi_dbg("created default context");
	}
	
	usbi_dbg("libusb v%u.%u.%u.%u%s", libusb_version_internal.major, libusb_version_internal.minor,
		libusb_version_internal.micro, libusb_version_internal.nano, libusb_version_internal.rc);
}

struct libusb_version:libusb 的版本号定义很规范!这里值得学习~

3.6 锁/链表初始化

int libusb_init(libusb_context **context)
{
	usbi_mutex_init(&ctx->usb_devs_lock);
	usbi_mutex_init(&ctx->open_devs_lock);
	usbi_mutex_init(&ctx->hotplug_cbs_lock);
	list_init(&ctx->usb_devs);
	list_init(&ctx->open_devs);
	list_init(&ctx->hotplug_cbs);
	ctx->next_hotplug_cb_handle = 1;
}

这里提一句 libusb 的链表:struct list_head { struct list_head *prev, *next; }; 自然,这是从 Linux 内核移植过来的,其精妙无需赘言,至今见过好多开源项目使用该链表。

3.7 libusb_context 链表

int libusb_init(libusb_context **context)
{
	if (first_init) {
		first_init = 0;
		list_init(&active_contexts_list);
	}
	list_add (&ctx->list, &active_contexts_list);
}

active_contexts_list 是用来管理 libusb_context 链表,源于先前提到的“libusb session”思想。

3.8 重头戏来也:op_init()

int libusb_init(libusb_context **context)
{
	if (usbi_backend.init) {
		r = usbi_backend.init(ctx);
		if (r)
			goto err_free_ctx;
	}
}

usbi_backend.init 指向 op_init() 函数:

int op_init(struct libusb_context *ctx)
{
	/* 通过 uname() 系统调用获取内核版本号 */
	if (get_kernel_version(ctx, &kversion) < 0) {}

	/* 检查系统版本是否满足 libusb 的要求 */
	if (!kernel_version_ge(&kversion, 2, 6, 32)) {}

	/* 查找 usbfs_path,最终结果为 "/dev/bus/usb" */
	usbfs_path = find_usbfs_path();

	/* 同步传输包限制大小,同步传输时用到 */
	if (!max_iso_packet_len) {
		if (kernel_version_ge(&kversion, 5, 2, 0))
			max_iso_packet_len = 98304;
		else if (kernel_version_ge(&kversion, 3, 10, 0))
			max_iso_packet_len = 49152;
		else
			max_iso_packet_len = 8192;
	}

	/* 检查 sysfs 是否正常 */
	if (sysfs_available == -1) {
		struct statfs statfsbuf;

		r = statfs(SYSFS_MOUNT_PATH, &statfsbuf);
		if (r == 0 && statfsbuf.f_type == SYSFS_MAGIC) {
			usbi_dbg("sysfs is available");
			sysfs_available = 1;
		} else {
			usbi_warn(ctx, "sysfs not mounted");
			sysfs_available = 0;
		}
	}

	if (init_count == 0) {
		/* 启动热插拔监听线程,后续讲到 hotplugtest 这个 demo 的时候再谈 */
		r = linux_start_event_monitor();
	}
	if (r == LIBUSB_SUCCESS) {
		/* 扫描系统中的 USB 设备,这里是 listdevs 功能的核心实现 */
		r = linux_scan_devices(ctx);
		if (r == LIBUSB_SUCCESS)
			init_count++;
		else if (init_count == 0)
			linux_stop_event_monitor();
	} else {
		usbi_err(ctx, "error starting hotplug event monitor");
	}

	return r;
}

  

3.9 系统中 USB 设备的扫描

没有使用 libudev,所以 linux_scan_devices() 调用流程为:linux_scan_devices() -> linux_default_scan_devices() -> sysfs_get_device_list()

3.9.1 sysfs_get_device_list()

int sysfs_get_device_list(struct libusb_context *ctx)
{
	DIR *devices = opendir("/sys/bus/usb/devices");

	while ((entry = readdir(devices))) {
		if ((!isdigit(entry->d_name[0]) && strncmp(entry->d_name, "usb", 3))
		    || strchr(entry->d_name, ':'))
			continue;

		num_devices++;

		if (sysfs_scan_device(ctx, entry->d_name)) {
			usbi_dbg("failed to enumerate dir entry %s", entry->d_name);
			continue;
		}

		num_enumerated++;
	}

	closedir(devices);

	/* successful if at least one device was enumerated or no devices were found */
	if (num_enumerated || !num_devices)
		return LIBUSB_SUCCESS;
	else
		return LIBUSB_ERROR_IO;
}

来看下 /sys/bus/usb/devices 目录里面都有什么:

USB 设备在 sysfs 中的表示:(host/roothub)-port : configuration.(interface/endpoint)
例如,1-3:1.0 表示:Hub 编号为 1,使用 3 号端口,配置为 1,端点为 0。
所以遍历 devices 时就过滤掉了":"式的目录。

int sysfs_scan_device(struct libusb_context *ctx, const char *devname)
{
	uint8_t busnum, devaddr;
	int ret;

	ret = linux_get_device_address(ctx, 0, &busnum, &devaddr, NULL, devname, -1);
	if (ret != LIBUSB_SUCCESS)
		return ret;

	return linux_enumerate_device(ctx, busnum, devaddr, devname);
}

3.9.2 linux_get_device_address()

int linux_get_device_address(struct libusb_context *ctx, int detached,
	uint8_t *busnum, uint8_t *devaddr, const char *dev_node,
	const char *sys_name, int fd)
{
	int sysfs_val;
	int r;

	r = read_sysfs_attr(ctx, sys_name, "busnum", UINT8_MAX, &sysfs_val);
	if (r < 0)
		return r;
	*busnum = (uint8_t)sysfs_val;

	r = read_sysfs_attr(ctx, sys_name, "devnum", UINT8_MAX, &sysfs_val);
	if (r < 0)
		return r;
	*devaddr = (uint8_t)sysfs_val;
	return LIBUSB_SUCCESS;
}

得益于 sysfs 的便捷,需要获取什么信息直接读取对应的文件就可以了。
比如,如果当前遍历 usb1 目录,那么读 usb1/busnum 文件就得到可 bus 编号。

3.9.3 linux_enumerate_device()

int linux_enumerate_device(struct libusb_context *ctx,
	uint8_t busnum, uint8_t devaddr, const char *sysfs_dir)
{
	unsigned long session_id;
	struct libusb_device *dev;
	int r;

	session_id = busnum << 8 | devaddr;
	dev = usbi_get_device_by_session_id(ctx, session_id);
	if (dev) {
		/* 设备已经存在,又是引用计数 */
		usbi_dbg("session_id %lu already exists", session_id);
		libusb_unref_device(dev);
		return LIBUSB_SUCCESS;
	}

	/* 为该设备分配内存 */
	dev = usbi_alloc_device(ctx, session_id);

	/* 设备信息初始化,这里重点关注描述符的信息获取 */
	r = initialize_device(dev, busnum, devaddr, sysfs_dir, -1);
	
	/* 根据 initialize_device() 读取的描述符信息做进一步的合法性检查 */
	r = usbi_sanitize_device(dev);

	r = linux_get_parent_info(dev, sysfs_dir);

	usbi_connect_device(dev);

	return r;
}

linux_get_parent_info() 父设备信息获取流程:

  • 如果当前目录是 usbX,说明位于 roothub 下,没有父设备,跳过后续流程;
  • 如果当前目录是 x-y,说明有父设备,且父设备为 roothub,名称为 "usbx";
  • 如果当前目录是 x-y:a.b,说明有父设备,名称为 "x-y:a";
  • 然后在 libusb_context 上遍历,如果有对应父设备名称的设备,说明父设备已经枚举过;否则就需要调用 sysfs_scan_device() 进行一次设备扫描。

3.9.4 usbi_connect_device()

void usbi_connect_device(struct libusb_device *dev)
{
	list_add(&dev->list, &dev->ctx->usb_devs);

	/* Signal that an event has occurred for this device if we support hotplug AND
	 * the hotplug message list is ready. This prevents an event from getting raised
	 * during initial enumeration. */
	if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) && dev->ctx->hotplug_msgs.next) {
		usbi_hotplug_notification(ctx, dev, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED);
	}
}

对于 listdevs 来说,关键信息就这个 list_add(),把设备加入到 libusb_context 的设备链表上。从这里也可以看出,libusb_context 是一个贯穿始终的东东。

 

四、USB 描述符的获取

USB 描述符的知识参阅:果壳中的USB(5)USB描述符
描述符信息在 initialize_device() 读取,下述代码段只表现描述符相关:

int initialize_device(struct libusb_device *dev, uint8_t busnum,
	uint8_t devaddr, const char *sysfs_dir, int wrapped_fd)
{
	struct linux_device_priv *priv = usbi_get_device_priv(dev);
	struct libusb_context *ctx = DEVICE_CTX(dev);
	size_t alloc_len;
	int fd, r;
	ssize_t nb;

	/* 1. 打开文件:/sys/bus/usb/devices/usb1/descriptors */
	fd = open_sysfs_attr(ctx, sysfs_dir, "descriptors");
	alloc_len = 0;
	/* 2. 读取文件内容 */
	do {
		const size_t desc_read_length = 256;
		uint8_t *read_ptr;

		alloc_len += desc_read_length;
		priv->descriptors = usbi_reallocf(priv->descriptors, alloc_len);

		read_ptr = (uint8_t *)priv->descriptors + priv->descriptors_len;

		nb = read(fd, read_ptr, desc_read_length);
		priv->descriptors_len += (size_t)nb;
	} while (priv->descriptors_len == alloc_len);

	close(fd);

	/* 每一个描述符都有大小规定,这些信息可以参阅内核的 include/uapi/linux/usb/ch9.h,
	 * 此文件相当于 USB 规范第九章的代码化
	 */
	if (priv->descriptors_len < LIBUSB_DT_DEVICE_SIZE) {
		usbi_err(ctx, "short descriptor read (%zu)", priv->descriptors_len);
		return LIBUSB_ERROR_IO;
	}

	/* 3. 配置描述符解析 */
	r = parse_config_descriptors(dev);

	/* 4. 得到设备描述符 */
	memcpy(&dev->device_descriptor, priv->descriptors, LIBUSB_DT_DEVICE_SIZE);

	if (sysfs_dir) {
		/* sysfs descriptors are in bus-endian format */
		usbi_localize_device_descriptor(&dev->device_descriptor);
		return LIBUSB_SUCCESS;
	}

	/* 5. 获取当前设备使用的配置描述符 */
	if (wrapped_fd < 0)
		/* 5.1 打开文件节点:/dev/bus/usb/001/001 */
		fd = get_usbfs_fd(dev, O_RDWR, 1);
	else
		fd = wrapped_fd;
	if (fd < 0) {
		/* cannot send a control message to determine the active
		 * config. just assume the first one is active. */
		usbi_warn(ctx, "Missing rw usbfs access; cannot determine "
			       "active configuration descriptor");
		if (priv->config_descriptors)
			priv->active_config = priv->config_descriptors[0].desc->bConfigurationValue;
		else
			priv->active_config = 0; /* No config dt */

		return LIBUSB_SUCCESS;
	}

	/* 5.2 通过控制传输,向设备发送 GET_CONFIGURATION 命令获取配置描述符 */
	r = usbfs_get_active_config(dev, fd);
	if (fd != wrapped_fd)
		close(fd);

	return r;
}

 

4.1 配置描述符解析:parse_config_descriptors()

在分析 parse_config_descriptors() 之前,我们人肉解析一边:

# hexdump -C /sys/bus/usb/devices/usb1/descriptors
00000000  12 01 00 02 09 00 01 40  6b 1d 02 00 15 04 03 02  |.......@k.......|
00000010  01 01 09 02 19 00 01 01  00 e0 00 09 04 00 00 01  |................|
00000020  09 00 00 00 07 05 81 03  04 00 0c                 |...........|

第一字节:0x12,bLength 指示描述符长度。
第二字节:0x01,bDescriptionType 指示描述符类型。
于是就得到了一个设备描述符:
12 01 00 02 09 00 01 40 6b 1d 02 00 15 04 03 02 01 01

同样的分析方法,解析出一个配置描述符:
09 02 19 00 01 01 00 e0 00

一个接口描述符:
09 04 00 00 01 09 00 00 00

一个端点描述符:
07 05 81 03 04 00 0c

经过以上步骤,parse_config_descriptors() 也就无需分析了。

4.2 获取当前设备使用的配置描述符

这里实际就一个 ioctl 调用,不表。

可以看出,这部分代码需要内核支持 usbfs:
usbfs:This lets devices provide ways to expose information to user space regardless of where they do (or don't) show up otherwise in the filesystem.

 

五、打印 USB 设备列表

print_devs(),无需赘述。

 

六、listdevs 分析总结

经过整体功能的源码分析,发现功能实现并没有实际操作系统中的 USB 设备(4.2 需要usbfs支持,不考虑),仅仅通过读取、解析操作系统暴露给应用的文件就拿到了所需信息。

posted on 2020-12-22 19:37  yin'xiang  阅读(1519)  评论(1编辑  收藏  举报