___2017

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
  328 随笔 :: 18 文章 :: 15 评论 :: 18万 阅读

 


一、hotplugtest 简介

hotplugtest 用于监听系统中 USB 设备的 attached(插入)和 detached(拔出),使用示例:

1
2
3
$ ./hotplugtest 0x067b 0x2303
Device detached // 插入设备
Device attached: 067b:2303 // 拔下设备

 

二、hotplugtest 入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
examples/hotplugtest.c<br>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 {},只提下其中一个分支:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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() 打开设备是如何操作的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/** 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()

1
2
3
4
5
6
7
8
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 数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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 模块:

1
2
3
4
5
6
7
8
9
10
11
12
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() 里面完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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() 完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/* 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()。

posted on   yin'xiang  阅读(2444)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示