libusb(3)hotplugtest 實現分析


一、hotplugtest 簡介

hotplugtest 用於監聽系統中 USB 設備的 attached(插入)和 detached(拔出),使用示例:

$ ./hotplugtest 0x067b 0x2303
Device detached // 插入設備
Device attached: 067b:2303 // 拔下設備

 

二、hotplugtest 入口

examples/hotplugtest.c
int main(int argc, char *argv[]) { libusb_hotplug_callback_handle hp[2]; int product_id, vendor_id, class_id; int rc; vendor_id = (argc > 1) ? (int)strtol (argv[1], NULL, 0) : 0x045a; product_id = (argc > 2) ? (int)strtol (argv[2], NULL, 0) : 0x5005; class_id = (argc > 3) ? (int)strtol (argv[3], NULL, 0) : LIBUSB_HOTPLUG_MATCH_ANY; rc = libusb_init(NULL); if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { printf("Hotplug capabilities are not supported on this platform\n"); libusb_exit(NULL); return EXIT_FAILURE; } rc = libusb_hotplug_register_callback(NULL, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, 0, vendor_id, product_id, class_id, hotplug_callback, NULL, &hp[0]); if (LIBUSB_SUCCESS != rc) { fprintf(stderr, "Error registering callback 0\n"); libusb_exit (NULL); return EXIT_FAILURE; } rc = libusb_hotplug_register_callback(NULL, LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, 0, vendor_id, product_id, class_id, hotplug_callback_detach, NULL, &hp[1]); if (LIBUSB_SUCCESS != rc) { fprintf(stderr, "Error registering callback 1\n"); libusb_exit(NULL); return EXIT_FAILURE; } while (done < 2) { rc = libusb_handle_events(NULL); if (rc < 0) printf("libusb_handle_events() failed: %s\n", libusb_error_name(rc)); } if (handle) { libusb_close(handle); } libusb_exit(NULL); return EXIT_SUCCESS; }

main 函數依舊清晰簡潔。這里看出:

  • hotplugtest 用法:hotplugtest vendor_id product_id class_id
  • libusb 庫內部資源初始化:libusb_init()
  • 監聽事件注冊:libusb_hotplug_register_callback()
  • 事件監聽:libusb_handle_events(),等待事件到來

libusb_init() 已經在《libusb(2)listdevs 實現分析》花大篇幅分析過,這里重點看后面兩個關鍵地方。


三、監聽事件注冊

3.1 注冊

在 libusb_hotplug_register_callback() 內部,主要就是初始化 struct libusb_hotplug_callback {},只提下其中一個分支:

int libusb_hotplug_register_callback(...)
{
	if ((flags & LIBUSB_HOTPLUG_ENUMERATE) && (events & LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED)) {
		ssize_t i, len;
		struct libusb_device **devs;

		len = libusb_get_device_list(ctx, &devs);
		if (len < 0) {
			libusb_hotplug_deregister_callback(ctx,
							new_callback->handle);
			return (int)len;
		}

		for (i = 0; i < len; i++) {
			usbi_hotplug_match_cb(ctx, devs[i],
					LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED,
					new_callback);
		}

		libusb_free_device_list(devs, 1);
	}
}

libusb_init() 初始化過程中會枚舉系統中已存在的設備;后續調用到 libusb_hotplug_register_callback() 的時候,如果 flag 傳參 LIBUSB_HOTPLUG_ENUMERATE,且注冊的是 attached 事件,則會遍歷枚舉出的設備鏈表,並對各個設備調用 cb_fn()。

3.2 attached callback

int hotplug_callback(libusb_context *ctx, libusb_device *dev, libusb_hotplug_event event, void *user_data)
{
	struct libusb_device_descriptor desc;
	int rc;

	/* 設備在 callback 之前已經完成創建,這里直接獲取設備描述符 */
	rc = libusb_get_device_descriptor(dev, &desc);
	if (LIBUSB_SUCCESS != rc) {
		fprintf (stderr, "Error getting device descriptor\n");
	}

	printf ("Device attached: %04x:%04x\n", desc.idVendor, desc.idProduct);

	/* 關閉前一個打開的設備 */
	if (handle) {
		libusb_close(handle);
		handle = NULL;
	}

	/* 打開當前設備 */
	rc = libusb_open(dev, &handle);

	done++;

	return 0;
}

hotplug_callback() 值得關注就一個點,libusb_open() 打開設備是如何操作的?

/** libusb_dev
 * Open a device and obtain a device handle. A handle allows you to perform
 * I/O on the device in question.
 *
 * Internally, this function adds a reference to the device and makes it
 * available to you through libusb_get_device(). This reference is removed
 * during libusb_close().
 */
int libusb_open(libusb_device *dev,	libusb_device_handle **dev_handle)
{
	struct libusb_context *ctx = DEVICE_CTX(dev);
	struct libusb_device_handle *_dev_handle;
	size_t priv_size = usbi_backend.device_handle_priv_size;
	int r;

	/* 設備狀態不對,無法處理 */
	if (!dev->attached) {
		return LIBUSB_ERROR_NO_DEVICE;
	}

	/* 創建 libusb_device_handle */
	_dev_handle = calloc(1, PTR_ALIGN(sizeof(*_dev_handle)) + priv_size);

	/* 把上面創建的 libusb_device_handle 和設備進行關聯 */
	_dev_handle->dev = libusb_ref_device(dev);

	/* 打開類似 /dev/bus/usb/001/001 的設備節點,獲取設備節點 fd,
	 * 然后將其加入到事件監聽列表里。
	 */
	r = usbi_backend.open(_dev_handle);

	/* 加入到 open handles 狀態設備鏈表 */
	list_add(&_dev_handle->list, &ctx->open_devs);
	*dev_handle = _dev_handle;

	return 0;
}

 

3.3 detached callback

hotplug_callback_detach() 需要關注的是 libusb_close()。

 

四、事件監聽

接觸一件新事物,首先需要提取它的骨架,脈絡。
對於 hotplug 來說,其實現脈絡是:

  • 內核監測到設備的狀態改變,通過 netlink 通知給用戶態的應用;
  • netlink 就是 socket 編程;
  • 解析 netlink 數據;
  • 通知 hotplug 模塊;
  • 調用用戶回調函數。

4.1 netlink

netlink 介紹參見:《linux netlink通信機制

netlink 注冊流程:op_init() -> linux_start_event_monitor() -> linux_netlink_start_event_monitor()

int linux_netlink_start_event_monitor(void)
{
	/* netlink socket 編程,不表 */
	ret = usbi_create_event(&netlink_control_event);

	ret = pthread_create(&libusb_linux_event_thread, NULL, linux_netlink_event_thread_main, NULL);
	return LIBUSB_SUCCESS;
}

usbi_create_event():創建一個 netlink 的事件控制描述符,用於后續事件的等待及通知。使用的是 linux 的 eventfd()。
eventfd() 的機制是:創建一個 eventfd,等待事件一方讀 fd,通知一方寫 fd。

linux_netlink_event_thread_main():netlink 監控線程,使用的是 poll() 機制。
poll() 監控了兩個文件描述符,一個是 usbi_create_event() 創建的,用於通知線程退出;
一個是 netlink socket 描述符,不用說是接受內核的事件通知。

4.2 netlink 數據解析

代碼是需求的實現。單純看代碼總會不明所以,有了詳細的需求,看代碼事半功倍。
這里,如果我們知道 netlink 的消息格式,代碼也就瞄一眼就瞬間明了。

以下是拔下 USB 轉串口的 netlink 數據:

remove@/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.0/ttyUSB0/tty/ttyUSB0
ACTION=remove
DEVPATH=/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.0/ttyUSB0/tty/ttyUSB0
SUBSYSTEM=tty
MAJOR=188
MINOR=0
DEVNAME=ttyUSB0
SEQNUM=643000

remove@/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.0/ttyUSB0
ACTION=remove
DEVPATH=/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.0/ttyUSB0
SUBSYSTEM=usb-serial
SEQNUM=643100


remove@/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.0
ACTION=remove
DEVPATH=/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.0
SUBSYSTEM=usb
DEVTYPE=usb_interface
PRODUCT=67b/2303/30000TYPE=0/0/0
INTERFACE=255/0/0
MODALIAS=usb:v067Bp2303d0300dc00dsc00dp00icFFisc00ip00in00
SEQNUM=643200

remove@/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1
ACTION=remove
DEVPATH=/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1
SUBSYSTEM=usb
MAJOR=189
MINOR=137
DEVNAME=bus/usb/002/010
DEVTYPE=usb_device
PRODUCT=67b/2303/300
TYPE=0/0/0
BUSNUM=002
DEVNUM=010
SEQNUM=643300

:這里的換行是為了方便分析,實際為字符串結束符'\0',也即0。

數據解析完后,通知 hotplug 模塊:

void usbi_connect_device(struct libusb_device *dev)
{
	struct libusb_context *ctx = DEVICE_CTX(dev);
	dev->attached = 1;

	/* 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);
	}
}

 

4.3 hotplug 模塊處理

4.3.1 attached 處理

attached 設備走 linux_hotplug_enumerate()。還記得有一個全局鏈表 active_contexts_lock,上面掛着所有的 libusb_context{},這里遍歷之,就把新設備加入到所有 libusb_context 里面。

遍歷過程調用的是 linux_enumerate_device() -> usbi_connect_device(),前面分析過,但是關於 hotplug 的分支掠過了,現在來看下:

通知有 LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED 事件到了。usbi_hotplug_notification() 里面完成:

void usbi_hotplug_notification(struct libusb_context *ctx, struct libusb_device *dev, libusb_hotplug_event event)
{
	/* 1. 構建 hotplug 消息 libusb_hotplug_message{} */
	struct libusb_hotplug_message *message = calloc(1, sizeof(*message));
	unsigned int event_flags;

	/* 消息包含產生消息的設備,消息類型 */
	message->event = event;
	message->device = dev;

	/* Take the event data lock and add this message to the list.
	 * Only signal an event if there are no prior pending events. */
	usbi_mutex_lock(&ctx->event_data_lock);
	event_flags = ctx->event_flags;
	/* 2. 新消息需要處理標志 */
	ctx->event_flags |= USBI_EVENT_HOTPLUG_MSG_PENDING;
	/* 3. 把該消息鏈接到 libusb_context{} 消息鏈表上 */
	list_add_tail(&message->list, &ctx->hotplug_msgs);
	
	/* 是否存在 pending 事件 */
	if (!event_flags)
		usbi_signal_event(&ctx->event);
	usbi_mutex_unlock(&ctx->event_data_lock);
}

usbi_hotplug_notification() 如函數名稱顯示的那樣,它做的是 notification 工作,只是設置了變量的值,並不作熱插拔的具體工作。

實際工作是在 main() 循環里的 libusb_handle_events() 完成。

libusb_handle_events() -> libusb_handle_events_timeout_completed()
/**
 * Handle any pending events.
 *
 * libusb determines "pending events" by checking if any timeouts have expired
 * and by checking the set of file descriptors for activity.
 *
 * If a zero timeval is passed, this function will handle any already-pending
 * events and then immediately return in non-blocking style.
 *
 * If a non-zero timeval is passed and no events are currently pending, this
 * function will block waiting for events to handle up until the specified
 * timeout. If an event arrives or a signal is raised, this function will
 * return early.
 *
 * If the parameter completed is not NULL then after obtaining the event
 * handling lock this function will return immediately if the integer
 * pointed to is not 0. This allows for race free waiting for the completion
 * of a specific transfer.
 *
 * \param ctx the context to operate on, or NULL for the default context
 * \param tv the maximum time to block waiting for events, or an all zero
 * timeval struct for non-blocking mode
 * \param completed pointer to completion integer to check, or NULL
 * \returns 0 on success
 * \returns LIBUSB_ERROR_INVALID_PARAM if timeval is invalid
 * \returns another LIBUSB_ERROR code on other failure
 */
int API_EXPORTED libusb_handle_events_timeout_completed(libusb_context *ctx,
	struct timeval *tv, int *completed)
{
	int r;
	struct timeval poll_timeout;

	if (libusb_try_lock_events(ctx) == 0) {
		if (completed == NULL || !*completed) {
			/* we obtained the event lock: do our own event handling */
			r = handle_events(ctx, &poll_timeout);
		}
		libusb_unlock_events(ctx);
		return r;
	}

	return 0;
}

libusb_try_lock_events() 拿到 ctx->events_lock 鎖后,調用 handle_events():

/* do the actual event handling. assumes that no other thread is concurrently
 * doing the same thing. */
static int handle_events(struct libusb_context *ctx, struct timeval *tv)
{
	struct usbi_reported_events reported_events;
	int r, timeout_ms;

	/* prevent attempts to recursively handle events (e.g. calling into
	 * libusb_handle_events() from within a hotplug or transfer callback) */
	if (usbi_handling_events(ctx))
		return LIBUSB_ERROR_BUSY;

	/* struct timeval{} 類型轉為 poll() 的 int 超時類型 */
	timeout_ms = (int)(tv->tv_sec * 1000) + (tv->tv_usec / 1000);
	/* round up to next millisecond */
	if (tv->tv_usec % 1000)
		timeout_ms++;

	reported_events.event_bits = 0;

	usbi_start_event_handling(ctx);

	/* 等待事件到來或超時 */
	r = usbi_wait_for_events(ctx, &reported_events, timeout_ms);
	if (r != LIBUSB_SUCCESS) {
		if (r == LIBUSB_ERROR_TIMEOUT) {
			handle_timeouts(ctx);
			r = LIBUSB_SUCCESS;
		}
		goto done;
	}

	if (reported_events.event_triggered) {
		r = handle_event_trigger(ctx);
		if (r) {
			/* return error code */
			goto done;
		}
	}

	if (!reported_events.num_ready)
		goto done;

	r = usbi_backend.handle_events(ctx, reported_events.event_data,
		reported_events.event_data_count, reported_events.num_ready);
	if (r)
		usbi_err(ctx, "backend handle_events failed with error %d", r);

done:
	usbi_end_event_handling(ctx);
	return r;
}

usbi_handling_events() 讀取線程局部存儲內容(TLS),數據格式是鍵值對,鍵為 ctx->event_handling_key,如果內容為 NULL 說明沒有事件正在被處理;否則直接返回 LIBUSB_ERROR_BUSY,開啟下一個循環。
usbi_start_event_handling() 設置鍵 ctx->event_handling_key 的值,標記有事件正在被處理。
usbi_wait_for_events() 等待事件到來或超時,使用的是 poll() 機制。
handle_event_trigger() 有事件到來,即 reported_events.event_triggered 被置位,則處理事件:handle_event_trigger() -> usbi_hotplug_match() -> usbi_hotplug_match_cb()。


免責聲明!

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



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