UHID - HID子系统的用户空间 I/O 驱动支持

UHID 允许用户空间实现 HID 传输驱动程序。请参阅 HID I/O 传输驱动程序 以了解 HID 传输驱动程序的介绍。本文档在很大程度上依赖于此处声明的定义。

使用 UHID,用户空间传输驱动程序可以为连接到用户空间控制总线的每个设备创建内核 hid 设备。UHID API 定义了从内核到用户空间以及反之亦然提供的 I/O 事件。

在 ./samples/uhid/uhid-example.c 中有一个用户空间应用程序示例。

UHID API

UHID 通过字符杂项设备访问。次要号码是动态分配的,因此您需要依赖 udev(或类似工具)来创建设备节点。默认情况下,这是 /dev/uhid。

如果您的 HID I/O 驱动程序检测到新设备,并且您想在 HID 子系统中注册此设备,则需要为您要注册的每个设备打开一次 /dev/uhid。所有后续通信都是通过 read() 或 write() “struct uhid_event” 对象完成的。通过设置 O_NONBLOCK 支持非阻塞操作。

struct uhid_event {
      __u32 type;
      union {
              struct uhid_create2_req create2;
              struct uhid_output_req output;
              struct uhid_input2_req input2;
              ...
      } u;
};

“type” 字段包含事件的 ID。根据 ID 发送不同的有效负载。您不得将单个事件拆分到多个 read() 或多个 write() 中。单个事件必须始终作为一个整体发送。此外,每个 read() 或 write() 只能发送一个事件。挂起的数据将被忽略。如果您想在单个系统调用中处理多个事件,则可以使用带有 readv()/writev() 的向量 I/O。“type” 字段定义了有效负载。对于每种类型,在联合“u”中都有一个有效负载结构可用(空有效负载除外)。此有效负载包含管理和/或设备数据。

您应该做的第一件事是发送一个 UHID_CREATE2 事件。这将注册设备。UHID 将响应一个 UHID_START 事件。您现在可以开始向 UHID 发送数据并从中读取数据。但是,除非 UHID 发送 UHID_OPEN 事件,否则内部附加的 HID 设备驱动程序没有用户附加。也就是说,除非收到 UHID_OPEN 事件,否则您可能会使您的设备休眠。如果您收到 UHID_OPEN 事件,则应开始 I/O。如果最后一个用户关闭 HID 设备,您将收到一个 UHID_CLOSE 事件。这可能会再次被 UHID_OPEN 事件跟随,依此类推。无需在用户空间执行引用计数。也就是说,您永远不会在没有 UHID_CLOSE 事件的情况下收到多个 UHID_OPEN 事件。HID 子系统为您执行引用计数。您也可以决定忽略 UHID_OPEN/UHID_CLOSE。即使设备可能没有用户,也允许 I/O。

如果您想在中断通道上将数据发送到 HID 子系统,则发送带有原始数据有效负载的 HID_INPUT2 事件。如果内核想在中断通道上将数据发送到设备,您将读取一个 UHID_OUTPUT 事件。控制通道上的数据请求目前仅限于 GET_REPORT 和 SET_REPORT(到目前为止,没有定义控制通道上的其他数据报告)。这些请求始终是同步的。这意味着,内核发送 UHID_GET_REPORT 和 UHID_SET_REPORT 事件,并要求您在控制通道上将其转发到设备。一旦设备响应,您必须通过 UHID_GET_REPORT_REPLY 和 UHID_SET_REPORT_REPLY 将响应转发到内核。内核在此类往返期间会阻止内部驱动程序执行(在硬编码时间段后超时)。

如果您的设备断开连接,您应发送一个 UHID_DESTROY 事件。这将注销设备。您现在可以再次发送 UHID_CREATE2 来注册一个新设备。如果您关闭() fd,则该设备会自动注销并在内部销毁。

write()

write() 允许您修改设备的状态并将输入数据馈送到内核。内核将立即解析事件,如果不支持事件 ID,它将返回 -EOPNOTSUPP。如果有效负载无效,则返回 -EINVAL,否则,返回读取的数据量并且请求已成功处理。O_NONBLOCK 不会影响 write(),因为写操作始终以非阻塞方式立即处理。但是,未来的请求可能会使用 O_NONBLOCK。

UHID_CREATE2

这将创建内部 HID 设备。在将此事件发送到内核之前,不可能进行 I/O。有效负载的类型为 struct uhid_create2_req,并包含有关您的设备的信息。您现在可以开始 I/O。

UHID_DESTROY

这将销毁内部 HID 设备。将不再接受进一步的 I/O。可能仍有挂起的消息可以通过 read() 接收,但不能将进一步的 UHID_INPUT 事件发送到内核。您可以通过再次发送 UHID_CREATE2 来创建一个新设备。无需重新打开字符设备。

UHID_INPUT2

在向内核发送输入之前,必须发送 UHID_CREATE2!此事件包含数据有效负载。这是您从中断通道上的设备读取的原始数据。内核将解析 HID 报告。

UHID_GET_REPORT_REPLY

如果您收到一个 UHID_GET_REPORT 请求,您必须使用此请求进行回复。您必须将请求中的 “id” 字段复制到答案中。如果没有错误发生,则将 “err” 字段设置为 0,如果发生 I/O 错误,则将其设置为 EIO。如果 “err” 为 0,则应使用 GET_REPORT 请求的结果填充答案的缓冲区,并相应地设置 “size”。

UHID_SET_REPORT_REPLY

这是 UHID_GET_REPORT_REPLY 的 SET_REPORT 等效项。与 GET_REPORT 不同,SET_REPORT 永远不会返回数据缓冲区,因此,正确设置 “id” 和 “err” 字段就足够了。

read()

read() 将返回一个排队的输出报告。不需要对它们进行任何反应,但您应该根据您的需要来处理它们。

UHID_START

这是在启动 HID 设备时发送的。将其视为对 UHID_CREATE2 的回答。这始终是发送的第一个事件。请注意,此事件可能不会在 write(UHID_CREATE2) 返回后立即可用。设备驱动程序可能需要延迟设置。此事件包含一个类型为 uhid_start_req 的有效负载。“dev_flags” 字段描述设备的特殊行为。定义了以下标志

  • UHID_DEV_NUMBERED_FEATURE_REPORTS

  • UHID_DEV_NUMBERED_OUTPUT_REPORTS

  • UHID_DEV_NUMBERED_INPUT_REPORTS

    每个标志都定义给定报告类型是否使用编号报告。如果对某种类型使用编号报告,则来自内核的所有消息都已将报告编号作为前缀。否则,内核不会添加前缀。对于用户空间发送到内核的消息,您必须根据这些标志调整前缀。

UHID_STOP

这是在停止 HID 设备时发送的。将其视为对 UHID_DESTROY 的回答。

如果您没有通过 UHID_DESTROY 销毁您的设备,但内核发送了一个 UHID_STOP 事件,则通常应忽略它。这意味着内核重新加载/更改了加载到您的 HID 设备上的设备驱动程序(或发生了一些其他维护操作)。

您通常可以安全地忽略任何 UHID_STOP 事件。

UHID_OPEN

这是在打开 HID 设备时发送的。也就是说,HID 设备提供的数据被其他进程读取。您可以忽略此事件,但它对电源管理很有用。只要您尚未收到此事件,实际上就没有其他进程读取您的数据,因此无需向内核发送 UHID_INPUT2 事件。

UHID_CLOSE

当不再有进程读取 HID 数据时,会发送此消息。它是 UHID_OPEN 的对应项,您也可以忽略此事件。

UHID_OUTPUT

如果 HID 设备驱动程序想在中断通道上向 I/O 设备发送原始数据,则会发送此消息。您应读取有效负载并将其转发到设备。有效负载的类型为 “struct uhid_output_req”。即使您尚未收到 UHID_OPEN,也可能会收到此消息。

UHID_GET_REPORT

如果内核驱动程序想如 HID 规范中所述在控制通道上执行 GET_REPORT 请求,则会发送此事件。报告类型和报告编号在有效负载中可用。内核序列化 GET_REPORT 请求,因此永远不会有两个并行请求。但是,如果您未能使用 UHID_GET_REPORT_REPLY 进行响应,则请求可能会静默超时。一旦您读取一个 GET_REPORT 请求,您应该将其转发到 HID 设备并记住有效负载中的 “id” 字段。一旦您的 HID 设备响应 GET_REPORT(或者如果它失败),您必须将一个具有与请求中完全相同的 “id” 的 UHID_GET_REPORT_REPLY 发送到内核。如果请求已经超时,内核将静默忽略该响应。“id” 字段永远不会被重用,因此不会发生冲突。

UHID_SET_REPORT

这是 UHID_GET_REPORT 的 SET_REPORT 等效项。收到后,您应向您的 HID 设备发送一个 SET_REPORT 请求。一旦它回复,您必须通过 UHID_SET_REPORT_REPLY 将其告知内核。与 UHID_GET_REPORT 相同的限制适用。


编写于 2012 年,David Herrmann <dh.herrmann@gmail.com>