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() 只能发送一个事件。 挂起的数据将被忽略。 如果您想在单个 syscall 中处理多个事件,请使用带有 readv()/writev() 的向量化 I/O。 “type” 字段定义有效负载。 对于每种类型,联合“u”中都有一个有效的负载结构(除了空有效负载)。 此有效负载包含管理和/或设备数据。
您应该做的第一件事是发送一个 UHID_CREATE2 事件。 这将注册设备。 UHID 将以 UHID_START 事件响应。 您现在可以开始向 UHID 发送数据和从 UHID 读取数据。 但是,除非 UHID 发送 UHID_OPEN 事件,否则内部连接的 HID 设备驱动程序没有附加用户。 也就是说,除非您收到 UHID_OPEN 事件,否则您可能会使您的设备进入睡眠状态。 如果您收到 UHID_OPEN 事件,您应该启动 I/O。 如果最后一个用户关闭了 HID 设备,您将收到一个 UHID_CLOSE 事件。 这之后可能会再次出现 UHID_OPEN 事件,等等。 不需要在用户空间执行引用计数。 也就是说,您永远不会在没有 UHID_CLOSE 事件的情况下收到多个 UHID_OPEN 事件。 HID 子系统为您执行 ref 计数。 您也可以决定忽略 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 以注册新设备。 如果您 close() 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
如果内核驱动程序想在控制通道上执行 GET_REPORT 请求(如 HID 规范中所述),则会发送此事件。 报告类型和报告编号在有效负载中可用。 内核序列化 GET_REPORT 请求,因此永远不会有两个并行。 但是,如果您未能使用 UHID_GET_REPORT_REPLY 响应,则请求可能会静默超时。 一旦您读取了一个 GET_REPORT 请求,您应该将其转发到 HID 设备并记住有效负载中的“id”字段。 一旦您的 HID 设备响应 GET_REPORT(或者如果它失败),您必须向内核发送一个 UHID_GET_REPORT_REPLY,其“id”与请求中的完全相同。 如果请求已经超时,内核将静默忽略响应。“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>