Linux HID分析-程序员宅基地

技术标签: hid  usb  io  linux  设备  Linux子系统  



Linux Hid设备调研
一.HID I/O传输驱动程序
                     ===========================

HID子系统独立于底层传输驱动程序,开始仅支持USB,但其它规格采用HID设计提供了新的传输驱动,内核至少支持USB、蓝牙、I2C和用户空间I/O驱动程序。
1) HID 总线
==========
HID子系统作为一个总线设计,任何I/O子系统都可以提供HID设备,并将其与HID总线进行注册。HID core然后加载通用设备驱动在它上面。传输驱动负责原始数据传输和设备设置/管理,HID core负责report解析、report解释和用户空间AIP。设备特征和quirks由所有层根据quicks处理。


传输驱动实例:
  I/O: USB, I2C, Bluetooth-l2cap
  Transport: USB-HID, I2C-HID, BT-HIDP


1.1) 设备设置
-----------------

I / O驱动程序通常为传输驱动程序提供热插拔检测或设备枚举API。运输驱动使用I/O驱动来找到任何合适的HID设备,传输驱动分配HID设备对象并使用HID core注册。传输驱动程序不需要注册HID core,HID core从来不知道哪些传输程序可用,并且不感兴趣,它只对设备感兴趣。

传输驱动在每个设备上附加一个常量“struct hid_ll_driver”对象,一旦设备注册到HID core,HID core就会使用该结构提供的回调与设备进行通信。

传输驱动负责检测设备故障和是否插入,只要注册了,无论任何的故障,HID core都会对其操作;一旦传输驱动程序检测到拔出或故障,他们必须HID core注销,HID core将停止使用提供的回调。

1.2) 传输驱动要求
----------------------------------
本文档中的术语“异步”和“同步”描述了关于确认的传输行为。异步通道不得执行任何同步操作,如等待确认或验证。 通常,在异步通道上运行的HID调用必须在原子上下文中正常运行。
另一方面,同步信道可以由传输驱动器以任何他们喜欢的方式实现。 它们可能与异步通道相同,但也可以以阻塞方式提供确认报告,故障自动重传等。 如果异步通道需要这样的功能,传输驱动程序必须通过自己的工作线程实现。

HID core要求传输驱动遵循给定的设计,传输驱动程序必须为每个HID设备提供两个双向I/O通道,这些通道在硬件本身不一定是双向的,传输驱动可能只提供4个单向通道,或者它可以在一个物理通道上复用所有四个,然而,在本文中,我们将它们描述为两个双向通道,因为它们具有多个属性。

 - Interrupt Channel (intr):
   Intr通道用于一步的reports数据,在此通道上不发送管理命令或数据确认,任何未经请求的传入或传出report数据都必须在此通道上发送,并且远程方不能被确认。 设备通常会在此通道上发送其输入事件,传出的事件通常不会通过intr发送,除非需要高吞吐量。
 - Control Channel (ctrl):
   ctrl通道用于同步请求和设备管理,未经请求的数据输入事件不得在此通道上发送,通常被忽略。相反,设备仅仅在此通道上发送管理事件或回复主机的请求。
控制通道以直接阻塞的方式访问设备,而不依赖与intr通道的任何事件,传出报告通常通过同步SET_REPORT请求在ctrl通道上发送。

设备和HID core之间的通信主要是通过HID report完成的。report可以是三种类型之一:

 - INPUT Report: Input reports provide data from device to host. This
   data may include button events, axis events, battery status or more. This
   data is generated by the device and sent to the host with or without
   requiring explicit requests. Devices can choose to send data continuously or
   only on change.
 - OUTPUT Report: Output reports change device states. They are sent from host
   to device and may include LED requests, rumble requests or more. Output
   reports are never sent from device to host, but a host can retrieve their
   current state.
   Hosts may choose to send output reports either continuously or only on
   change.
 - FEATURE Report: Feature reports are used for specific static device features
   and never reported spontaneously. A host can read and/or write them to access
   data like battery-state or device-settings.
   Feature reports are never sent without requests. A host must explicitly set
   or retrieve a feature report. This also means, feature reports are never sent
   on the intr channel as this channel is asynchronous.

INPUT and OUTPUT reports can be sent as pure data reports on the intr channel.
For INPUT reports this is the usual operational mode. But for OUTPUT reports,
this is rarely done as OUTPUT reports are normally quite scarce. But devices are
free to make excessive use of asynchronous OUTPUT reports (for instance, custom
HID audio speakers make great use of it).

Plain reports must not be sent on the ctrl channel, though. Instead, the ctrl
channel provides synchronous GET/SET_REPORT requests. Plain reports are only
allowed on the intr channel and are the only means of data there.

 - GET_REPORT: A GET_REPORT request has a report ID as payload and is sent
   from host to device. The device must answer with a data report for the
   requested report ID on the ctrl channel as a synchronous acknowledgement.
   Only one GET_REPORT request can be pending for each device. This restriction
   is enforced by HID core as several transport drivers don't allow multiple
   simultaneous GET_REPORT requests.
   Note that data reports which are sent as answer to a GET_REPORT request are
   not handled as generic device events. That is, if a device does not operate
   in continuous data reporting mode, an answer to GET_REPORT does not replace
   the raw data report on the intr channel on state change.
   GET_REPORT is only used by custom HID device drivers to query device state.
   Normally, HID core caches any device state so this request is not necessary
   on devices that follow the HID specs except during device initialization to
   retrieve the current state.
   GET_REPORT requests can be sent for any of the 3 report types and shall
   return the current report state of the device. However, OUTPUT reports as
   payload may be blocked by the underlying transport driver if the
   specification does not allow them.
 - SET_REPORT: A SET_REPORT request has a report ID plus data as payload. It is
   sent from host to device and a device must update it's current report state
   according to the given data. Any of the 3 report types can be used. However,
   INPUT reports as payload might be blocked by the underlying transport driver
   if the specification does not allow them.
   A device must answer with a synchronous acknowledgement. However, HID core
   does not require transport drivers to forward this acknowledgement to HID
   core.
   Same as for GET_REPORT, only one SET_REPORT can be pending at a time. This
   restriction is enforced by HID core as some transport drivers do not support
   multiple synchronous SET_REPORT requests.

Other ctrl-channel requests are supported by USB-HID but are not available
(or deprecated) in most other transport level specifications:

 - GET/SET_IDLE: Only used by USB-HID and I2C-HID.
 - GET/SET_PROTOCOL: Not used by HID core.
 - RESET: Used by I2C-HID, not hooked up in HID core.
 - SET_POWER: Used by I2C-HID, not hooked up in HID core.

2) HID API
==========
2.1) 初始化
-------------------

传输驱动程序通常使用以下步骤向HID core注册新设备:
 struct hid_device *hid;
 int ret;

 hid = hid_allocate_device();
 if (IS_ERR(hid)) {
  ret = PTR_ERR(hid);
  goto err_<...>;
 }

 strlcpy(hid->name, <device-name-src>, 127);
 strlcpy(hid->phys, <device-phys-src>, 63);
 strlcpy(hid->uniq, <device-uniq-src>, 63);

 hid->ll_driver = &custom_ll_driver;
 hid->bus = <device-bus>;
 hid->vendor = <device-vendor>;
 hid->product = <device-product>;
 hid->version = <device-version>;
 hid->country = <device-country>;
 hid->dev.parent = <pointer-to-parent-device>;
 hid->driver_data = <transport-driver-data-field>;

 ret = hid_add_device(hid);
 if (ret)
  goto err_<...>;

一旦hid_add_device()被调用,HID core可以使用custom_ll_driver中提供的回调。

注销设备:

 hid_destroy_device(hid);

一旦hid_destory_device()返回,HID core将不可以再使用任何驱动回调。

2.2) hid_ll_driver operations
-----------------------------

有效的HID回调:
 - int (*start) (struct hid_device *hdev)
    HID驱动程序想要使用设备,就会调用。传输驱动程序可以选择在此回调中设置其设备。 然而,一般来说,设备在传输驱动程序将其注册到HID核心之前已经建立,因此这主要仅用于USB HID。

 - void (*stop) (struct hid_device *hdev)
    一旦使用完一个设备,就从HID设备驱动程序调用,传输驱动程序可以释放任何缓冲区并将设备取消初始化。但是,如果在设备上加载了另一个HID设备驱动程序,则可能再次调用->start().
   
 - int (*open) (struct hid_device *hdev)
如果对数据reports感兴趣,就从HID设备驱动程序调用。
通常,当用户空间没有打开任何输入API / etc时,设备驱动程序是
 对设备数据和传输驱动程序不感兴趣,可以将设备置于睡眠状态。
但是,一旦 - > open()被调用,传输驱动程序必须准备好进行I / O。
->open()是为每一个要打开HID设备的客户端嵌套调用。
 - void (*close) (struct hid_device *hdev)
    在->open()之后在HID设备驱动调用,它们将不再对设备reports感兴趣,(通常是用户空间关闭驱动程序的任何输入设备)。传输驱动可以将设备置于睡眠状态并终止所有I/O.

 - int (*parse) (struct hid_device *hdev)
   在->start()之后调用,传输驱动必须从设备读取HID report描述符,并通过  hid_parse_report()告知HID core。

 - int (*power) (struct hid_device *hdev, int level)
    HID core调用给PM提示。
 
 - void (*request) (struct hid_device *hdev, struct hid_report *report,
                    int reqtype)
在ctrl通道发送一个HID请求,“report”包含应发送的报告和“reqtype”请求类型, 请求类型可以是HID_REQ_SET_REPORT或HID_REQ_GET_REPORT.这个回调是可选的。 如果没有提供,HID core将按照HID规范组装原始报告,并通过 - > raw_request()回调发送它。

 - int (*wait) (struct hid_device *hdev)
    在再次调用->request()之前有HID core调用,如果一次只允许一个请求,传输驱动程序可以使用它来等待任何待处理的请求完成。

 - int (*raw_request) (struct hid_device *hdev, unsigned char reportnum,
                       __u8 *buf, size_t count, unsigned char rtype,
                       int reqtype)
    与 - > request()相同,但以报告为原始缓冲区。该请求应同步。传输驱动程序不能使用 - > wait()来完成这些请求。 此请求是强制性的,并且如果缺少,HID core将拒绝该设备。

 - int (*output_report) (struct hid_device *hdev, __u8 *buf, size_t len)
    通过intr通道发送原始输出报告。由一些需要高吞吐量的HID设备驱动程序用于内部通道的传出请求。 这不能导致SET_REPORT调用! 这必须在内部通道上实现为异步输出报告!

 - int (*idle) (struct hid_device *hdev, int report, int idle, int reqtype)
   执行SET / GET_IDLE请求。

2.3) Data Path
--------------

传输驱动负责从I/O设备读取数据。它们必须自己处理与I/O有关的状态跟踪。HID core不实现协议握手或其它管理命令,这些命令由给定的HID传输规范来执行。
从设备读取的每个原始数据包都必须通过HID core进行hid_input_report().必须指定通道类型(intr或ctrl)并报告类型(输入/输出/功能)。在正常情况下,只有输入报告通过此API提供。

还必须通过API提供对GET_REPORT请求的响应->request()。对->raw_request()的响应是同步的,必须被传输驱动程序拦截,而不会传递给hid_input_report()。对SET_REPORT请求的确认不是HID core所关心的。

----------------------------------------------------
David Herrmann <[email protected]>写于2013年


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

UHID允许用户空间实现HID传输驱动程序,HID传输驱动程序的定义可以查看第一部分。

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

在 ./samples/uhid/uhid-example.c有一个用户空间应用的例子。

UHID API
------------

通过字符混杂设备访问UHID,minor-number被动态的分配,因此需要依靠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()/write()向量I/O.
“tupe”字段定义了有效载荷,对于每个type,在union“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子系统执行重新计数。

如果要将中断通道上的数据发送到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:
  This creates the internal HID device. No I/O is possible until you send this
  event to the kernel. The payload is of type struct uhid_create2_req and
  contains information about your device. You can start I/O now.

  UHID_DESTROY:
  This destroys the internal HID device. No further I/O will be accepted. There
  may still be pending messages that you can receive with read() but no further
  UHID_INPUT events can be sent to the kernel.
  You can create a new device by sending UHID_CREATE2 again. There is no need to
  reopen the character device.

  UHID_INPUT2:
  You must send UHID_CREATE2 before sending input to the kernel! This event
  contains a data-payload. This is the raw data that you read from your device
  on the interrupt channel. The kernel will parse the HID reports.

  UHID_GET_REPORT_REPLY:
  If you receive a UHID_GET_REPORT request you must answer with this request.
  You  must copy the "id" field from the request into the answer. Set the "err"
  field to 0 if no error occurred or to EIO if an I/O error occurred.
  If "err" is 0 then you should fill the buffer of the answer with the results
  of the GET_REPORT request and set "size" correspondingly.

  UHID_SET_REPORT_REPLY:
  This is the SET_REPORT equivalent of UHID_GET_REPORT_REPLY. Unlike GET_REPORT,
  SET_REPORT never returns a data buffer, therefore, it's sufficient to set the
  "id" and "err" fields correctly.

read()
------
read()返回一个请求的输出报告,

  UHID_START:
  This is sent when the HID device is started. Consider this as an answer to
  UHID_CREATE2. This is always the first event that is sent. Note that this
  event might not be available immediately after write(UHID_CREATE2) returns.
  Device drivers might required delayed setups.
  This event contains a payload of type uhid_start_req. The "dev_flags" field
  describes special behaviors of a device. The following flags are defined:
      UHID_DEV_NUMBERED_FEATURE_REPORTS:
      UHID_DEV_NUMBERED_OUTPUT_REPORTS:
      UHID_DEV_NUMBERED_INPUT_REPORTS:
          Each of these flags defines whether a given report-type uses numbered
          reports. If numbered reports are used for a type, all messages from
          the kernel already have the report-number as prefix. Otherwise, no
          prefix is added by the kernel.
          For messages sent by user-space to the kernel, you must adjust the
          prefixes according to these flags.

  UHID_STOP:
  This is sent when the HID device is stopped. Consider this as an answer to
  UHID_DESTROY.
  If you didn't destroy your device via UHID_DESTROY, but the kernel sends an
  UHID_STOP event, this should usually be ignored. It means that the kernel
  reloaded/changed the device driver loaded on your HID device (or some other
  maintenance actions happened).
  You can usually ignored any UHID_STOP events safely.

  UHID_OPEN:
  This is sent when the HID device is opened. That is, the data that the HID
  device provides is read by some other process. You may ignore this event but
  it is useful for power-management. As long as you haven't received this event
  there is actually no other process that reads your data so there is no need to
  send UHID_INPUT2 events to the kernel.

  UHID_CLOSE:
  This is sent when there are no more processes which read the HID data. It is
  the counterpart of UHID_OPEN and you may as well ignore this event.

  UHID_OUTPUT:
  This is sent if the HID device driver wants to send raw data to the I/O
  device on the interrupt channel. You should read the payload and forward it to
  the device. The payload is of type "struct uhid_data_req".
  This may be received even though you haven't received UHID_OPEN, yet.

  UHID_GET_REPORT:
  This event is sent if the kernel driver wants to perform a GET_REPORT request
  on the control channeld as described in the HID specs. The report-type and
  report-number are available in the payload.
  The kernel serializes GET_REPORT requests so there will never be two in
  parallel. However, if you fail to respond with a UHID_GET_REPORT_REPLY, the
  request might silently time out.
  Once you read a GET_REPORT request, you shall forward it to the hid device and
  remember the "id" field in the payload. Once your hid device responds to the
  GET_REPORT (or if it fails), you must send a UHID_GET_REPORT_REPLY to the
  kernel with the exact same "id" as in the request. If the request already
  timed out, the kernel will ignore the response silently. The "id" field is
  never re-used, so conflicts cannot happen.

  UHID_SET_REPORT:
  This is the SET_REPORT equivalent of UHID_GET_REPORT. On receipt, you shall
  send a SET_REPORT request to your hid device. Once it replies, you must tell
  the kernel about it via UHID_SET_REPORT_REPLY.
  The same restrictions as for UHID_GET_REPORT apply.

----------------------------------------------------
Written 2012, David Herrmann <[email protected]>
结合内核源码分析原理:在内核drivers/hid/下有一个uhid.c
/*
 * User-space I/O driver support for HID subsystem
 * Copyright (c) 2012 David Herrmann
 */
uhid在内核中本身是一个misc杂项设备驱动(对一些字符设备的封装),它对应的设备文件是/dev/uhid,用户空间就是访问的它,然后内核中uhid misc驱动再与hid子系统交互。其实就是可以间接的在用户空间实现了hid传输驱动的职能。



三.内核中驱动实现重要的宏:
标准实现:
static int __init sony_init(void)
{
         dbg_hid("Sony:%s\n", __func__);
 
         return hid_register_driver(&sony_driver);
}

static void __exit sony_exit(void)
{
         dbg_hid("Sony:%s\n", __func__);

         hid_unregister_driver(&sony_driver);
         ida_destroy(&sony_device_id_allocator);
}
 module_init(sony_init);
 module_exit(sony_exit);

简洁实现:
只有一条 module_xxx_driver(starct hid_driver)
宏:
模块头文件
#define module_hid_driver(__hid_driver) \
         module_driver(__hid_driver, hid_register_driver, \    //hid_register_driver为总线上函数                                                                                                   
                        hid_unregister_driver)
device总头文件
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \                                                                                                                                                                                
         return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
         __unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);


四.Care and feeding of your Human Interface Devices
介绍
除了正常的输入类型的HID设备之外,USB还将HID协议用于不是真正的人机交互但具有相似种类的通信需求的东西。两个很大的例子是电源设备(尤其是不间断电源)和监控高端显示器的控制。
The two big examples for this are power devices (especially uninterruptable power
supplies) and monitor control on higher end monitors.

为了支持这些不同的要求,Linux USB系统向两个独立的接口提供HID事件:

*input子系统将HID事件转换为普通输入设备接口(如键盘,鼠标和操纵杆)和规范化的事件接口 - 请参阅Documentation / input / input.txt

* hiddev接口,提供相当原始的HID事件

由设备产生的HID事件的数据流是类似的
下列 :
 usb.c ---> hid-core.c  ----> hid-input.c ----> [keyboard/mouse/joystick/event]
                         |
                         |
                          --> hiddev.c ----> POWER / MONITOR CONTROL

此外,其他子系统(除了USB)可能会将事件提供给输入子系统,但这些对HID设备接口没有任何影响。

使用HID设备接口

Hiddev接口是一个使用普通usb主设备号以及从设备号96到111的char类型接口,因此需要一下命令:
mknod /dev/usb/hiddev0 c 180 96
mknod /dev/usb/hiddev1 c 180 97
mknod /dev/usb/hiddev2 c 180 98
mknod /dev/usb/hiddev3 c 180 99
mknod /dev/usb/hiddev4 c 180 100
mknod /dev/usb/hiddev5 c 180 101
mknod /dev/usb/hiddev6 c 180 102
mknod /dev/usb/hiddev7 c 180 103
mknod /dev/usb/hiddev8 c 180 104
mknod /dev/usb/hiddev9 c 180 105
mknod /dev/usb/hiddev10 c 180 106
mknod /dev/usb/hiddev11 c 180 107
mknod /dev/usb/hiddev12 c 180 108
mknod /dev/usb/hiddev13 c 180 109
mknod /dev/usb/hiddev14 c 180 110
mknod /dev/usb/hiddev15 c 180 111

真样就可以将hiddev兼容的用户空间程序指向设备的正确接口,并且它们都可以正常工作。
前提是假设有一个hiddev兼容的用户空间程序,当然,如果需要写一个,请继续阅读。
THE HIDDEV API
该描述应与HID规范一起阅读,可从http://www.usb.org  http://www.linux-usb.org获得。

hiddev API使用一个read()接口和一组ioctl()调用。

HID设备使用称为“report”的数据包与主机交换数据,每个报告用“fields”分开,每一个都可以有一个或多个“usages”。 在HID core中,这些“usages”中每一个都具有单个带符号的32位值。

read():
这是一个事件接口,当HID设备的状态改变时,它执行一个中断传输,这个中断传输包含一个带有变化了的值得report,hid-core.c模块解析报告,并在report中将单个的含有usages的变化返回到hiddev.c。在其基本模式下,hiddev将使用struct hiddev_event向调用者提供这些单个的usage改变值。
       struct hiddev_event {
           unsigned hid;
           signed int value;
       };
结构体包含更改状态的HID使用情况标识符以及更改的值,需要注意,结构在<linux / hiddev.h>中定义,以及一些其他有用的#define和结构。可以使用下面描述的HIDIOCSFLAG ioctl()来修改read()函数的行为。


ioctl():
该控制接口,有下面一些实现:

HIDIOCGVERSION - int (read)
从hiddev驱动程序中获取版本信息。
HIDIOCAPPLICATION - (none)
该ioctl调用返回与hid设备关联的HID应用usage,ioctl()的第三个参数指定要获取的应用程序索引。当设备有多个应用程序集合时,这是非常有用的。如果索引无效(大于或等于此设备的应用程序集合数),则ioctl将返回-1。可以从hiddev_devinfo结构的num_applications字段中预先了解设备有多少应用程序集合。

HIDIOCGCOLLECTIONINFO - struct hiddev_collection_info (read/write)
这返回上述信息的超集,不仅提供应用程序集合,还提供设备所有的集合。 它还返回集合在层次结构中的层次。 用户通过索引hiddev_collection_info结构体
字段设置为应返回的索引。 ioctl填充其他字段。 如果索引大于上一个集合索引,则ioctl返回-1并将errno设置为-EINVAL。

HIDIOCGDEVINFO - struct hiddev_devinfo (read)
获取hiddev_devinfo结构体数据;

HIDIOCGSTRING - struct hiddev_string_descriptor (read/write)
从设备获取字符串描述符,必须填写“index”字段来指示应该返回哪个描述符。

HIDIOCINITREPORT - (none)
Instructs the kernel to retrieve all input and feature report values
from the device. At this point, all the usage structures will contain
current values for the device, and will maintain it as the device
changes.  Note that the use of this ioctl is unnecessary in general,
since later kernels automatically initialize the reports from the
device at attach time.

HIDIOCGNAME - string (variable length)
Gets the device name

HIDIOCGREPORT - struct hiddev_report_info (write)
指示内核从设备获取功能或输入report,以便有选择地更新usage结构(与INITREPORT相反)。

HIDIOCSREPORT - struct hiddev_report_info (write)
指示内核向设备发送report,该report可以由用户通过HIDIOCSUSAGE调用后填写,以便在将report全部发送到设备之前填写report中的各个usage值。

HIDIOCGREPORTINFO - struct hiddev_report_info (read/write)
Fills in a hiddev_report_info structure for the user. The report is
looked up by type (input, output or feature) and id, so these fields
must be filled in by the user. The ID can be absolute -- the actual
report id as reported by the device -- or relative --
HID_REPORT_ID_FIRST for the first report, and (HID_REPORT_ID_NEXT |
report_id) for the next report after report_id. Without a-priori
information about report ids, the right way to use this ioctl is to
use the relative IDs above to enumerate the valid IDs. The ioctl
returns non-zero when there is no more next ID. The real report ID is
filled into the returned hiddev_report_info structure.

HIDIOCGFIELDINFO - struct hiddev_field_info (read/write)
Returns the field information associated with a report in a
hiddev_field_info structure. The user must fill in report_id and
report_type in this structure, as above. The field_index should also
be filled in, which should be a number from 0 and maxfield-1, as
returned from a previous HIDIOCGREPORTINFO call.

HIDIOCGUCODE - struct hiddev_usage_ref (read/write)
Returns the usage_code in a hiddev_usage_ref structure, given that
given its report type, report id, field index, and index within the
field have already been filled into the structure.

HIDIOCGUSAGE - struct hiddev_usage_ref (read/write)
Returns the value of a usage in a hiddev_usage_ref structure. The
usage to be retrieved can be specified as above, or the user can
choose to fill in the report_type field and specify the report_id as
HID_REPORT_ID_UNKNOWN. In this case, the hiddev_usage_ref will be
filled in with the report and field information associated with this
usage if it is found.

HIDIOCSUSAGE - struct hiddev_usage_ref (write)
Sets the value of a usage in an output report.  The user fills in
the hiddev_usage_ref structure as above, but additionally fills in
the value field.

HIDIOGCOLLECTIONINDEX - struct hiddev_usage_ref (write)
Returns the collection index associated with this usage.  This
indicates where in the collection hierarchy this usage sits.

HIDIOCGFLAG - int (read)
HIDIOCSFLAG - int (write)
These operations respectively inspect and replace the mode flags
that influence the read() call above.  The flags are as follows:

    HIDDEV_FLAG_UREF - read() calls will now return
        struct hiddev_usage_ref instead of struct hiddev_event.
        This is a larger structure, but in situations where the
        device has more than one usage in its reports with the
        same usage code, this mode serves to resolve such
        ambiguity.

    HIDDEV_FLAG_REPORT - This flag can only be used in conjunction
        with HIDDEV_FLAG_UREF.  With this flag set, when the device
        sends a report, a struct hiddev_usage_ref will be returned
        to read() filled in with the report_type and report_id, but
        with field_index set to FIELD_INDEX_NONE.  This serves as
        additional notification when the device has sent a report.


五.HIDRAW - 原始的访问USB和BT hid设备
     ==================================================================
介绍
hidraw驱动程序为USB和蓝牙人机接口设备(HID)提供了一个原始接口。 它与hiddev不同之处在于发送和接收的报告不会被HID解析器解析,而是发送到设备并从设备接收未修改。

如果用户空间应用程序知道如何与硬件设备通信,并且能够手动构建HID report,则应使用Hidraw。 当为自定义HID设备创建用户空间驱动程序时,通常是这种情况。

Hidraw还可用于与non-conformant的HID设备进行通信,HID设备以与其报告描述符不一致的方式发送和接收数据。因为hiddev解析通过它发送和接收的报告,检查它们对设备的报告描述符,这种与这些不一致的设备的通信是不可能使用hiddev。除了为non-conformant设备编写自定义内核驱动程序,Hidraw是唯一的替代方案。

hidraw的一个好处是用户空间应用程序的使用与底层硬件类型无关。 目前,Hidraw已经实现了USB和蓝牙。 将来,随着开发使用HID规范的新硬件总线类型,hidraw将被扩展以增加对这些新总线类型的支持。

Hidraw使用一个动态的主设备号,这意味着应该依赖udev来创建hidraw设备节点。 Udev通常会直接在/ dev下创建设备节点(例如:/ dev / hidraw0)。 由于这个位置是分布式的并且依赖于udev 规则,应用程序应该使用libudev来定位连接到系统的hidraw设备。 有一个关于libudev的教程,其工作示例如下:
http://www.signal11.us/oss/udev/


The HIDRAW API
---------------

read()
-------
read() will read a queued report received from the HID device. On USB
devices, the reports read using read() are the reports sent from the device
on the INTERRUPT IN endpoint.  By default, read() will block until there is
a report available to be read.  read() can be made non-blocking, by passing
the O_NONBLOCK flag to open(), or by setting the O_NONBLOCK flag using
fcntl().

On a device which uses numbered reports, the first byte of the returned data
will be the report number; the report data follows, beginning in the second
byte.  For devices which do not use numbered reports, the report data
will begin at the first byte.

write()
--------
The write() function will write a report to the device. For USB devices, if
the device has an INTERRUPT OUT endpoint, the report will be sent on that
endpoint. If it does not, the report will be sent over the control endpoint,
using a SET_REPORT transfer.

The first byte of the buffer passed to write() should be set to the report
number.  If the device does not use numbered reports, the first byte should
be set to 0. The report data itself should begin at the second byte.

ioctl()
--------
Hidraw supports the following ioctls:

HIDIOCGRDESCSIZE: Get Report Descriptor Size
This ioctl will get the size of the device's report descriptor.

HIDIOCGRDESC: Get Report Descriptor
This ioctl returns the device's report descriptor using a
hidraw_report_descriptor struct.  Make sure to set the size field of the
hidraw_report_descriptor struct to the size returned from HIDIOCGRDESCSIZE.

HIDIOCGRAWINFO: Get Raw Info
This ioctl will return a hidraw_devinfo struct containing the bus type, the
vendor ID (VID), and product ID (PID) of the device. The bus type can be one
of:
 BUS_USB
 BUS_HIL
 BUS_BLUETOOTH
 BUS_VIRTUAL
which are defined in linux/input.h.

HIDIOCGRAWNAME(len): Get Raw Name
This ioctl returns a string containing the vendor and product strings of
the device.  The returned string is Unicode, UTF-8 encoded.

HIDIOCGRAWPHYS(len): Get Physical Address
This ioctl returns a string representing the physical address of the device.
For USB devices, the string contains the physical path to the device (the
USB controller, hubs, ports, etc).  For Bluetooth devices, the string
contains the hardware (MAC) address of the device.

HIDIOCSFEATURE(len): Send a Feature Report
This ioctl will send a feature report to the device.  Per the HID
specification, feature reports are always sent using the control endpoint.
Set the first byte of the supplied buffer to the report number.  For devices
which do not use numbered reports, set the first byte to 0. The report data
begins in the second byte. Make sure to set len accordingly, to one more
than the length of the report (to account for the report number).

HIDIOCGFEATURE(len): Get a Feature Report
This ioctl will request a feature report from the device using the control
endpoint.  The first byte of the supplied buffer should be set to the report
number of the requested report.  For devices which do not use numbered
reports, set the first byte to 0.  The report will be returned starting at
the first byte of the buffer (ie: the report number is not returned).

Example
---------
In samples/, find hid-example.c, which shows examples of read(), write(),
and all the ioctls for hidraw.  The code may be used by anyone for any
purpose, and can serve as a starting point for developing applications using
hidraw.

Document by:
 Alan Ott <[email protected]>, Signal 11 Software

六.HID core
介绍
HID core本身以module的形式存在,在初始化函数hid_init中核心实现是调用了
ret = bus_register(&hid_bus_type);
... ...
ret = hidraw_init(); //与hidraw相关
这里与驱动的实现明显的区别。

Hid驱动初始化时调用的注册函数
int __hid_register_driver(struct hid_driver *hdrv, struct module *owner,
  const char *mod_name)
{
 int ret;
 hdrv->driver.name = hdrv->name;
 hdrv->driver.bus = &hid_bus_type;  //总线类型
 hdrv->driver.owner = owner;
 hdrv->driver.mod_name = mod_name;

 INIT_LIST_HEAD(&hdrv->dyn_list);
 spin_lock_init(&hdrv->dyn_lock);

 ret = driver_register(&hdrv->driver);  //驱动注册
 if (ret)
  return ret;

 ret = driver_create_file(&hdrv->driver, &driver_attr_new_id); 
 if (ret)
  driver_unregister(&hdrv->driver);

 return ret;
}

Usbhid传输驱动
usbhid也以module的形式存在,在它的初始化函数hid_init中,核心执行了下列操作
 retval = usbhid_quirks_init(quirks_param);
    ... ...
 retval = usb_register(&hid_driver);
先看usb_register函数,usbhid core将自己注册到了usb总线,函数实现在usb core,首先指定bus type为usb_bus_type,然后调用了driver_register注册。

上面结构图提到他与hid core是有关系的,查看usbhid注册自己的结构体:
static struct usb_driver hid_driver = {
 .name =  "usbhid",
 .probe = usbhid_probe,
 .disconnect = usbhid_disconnect,
#ifdef CONFIG_PM
 .suspend = hid_suspend,
 .resume = hid_resume,
 .reset_resume = hid_reset_resume,
#endif
 .pre_reset = hid_pre_reset,
 .post_reset = hid_post_reset,
 .id_table = hid_usb_ids,
 .supports_autosuspend = 1,
};
其中有一个函数指针usbhid_probe,如果该函数被调用(一般是usb core),在该函数里调用了ret = hid_add_device(hid);此时就传入了一些usbhid实现的回调给hid core,通过这些回调他们交互。
具体实现:
hid->ll_driver = &usb_hid_driver;
//包括的操作,上面传输驱动部分介绍了
static struct hid_ll_driver usb_hid_driver = {
 .parse = usbhid_parse,
 .start = usbhid_start,
 .stop = usbhid_stop,
 .open = usbhid_open,
 .close = usbhid_close,
 .power = usbhid_power,
 .request = usbhid_request,
 .wait = usbhid_wait_io,
 .raw_request = usbhid_raw_request,
 .output_report = usbhid_output_report,
 .idle = usbhid_idle,
};

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u012759483/article/details/70791912

智能推荐

class和struct的区别-程序员宅基地

文章浏览阅读101次。4.class可以有⽆参的构造函数,struct不可以,必须是有参的构造函数,⽽且在有参的构造函数必须初始。2.Struct适⽤于作为经常使⽤的⼀些数据组合成的新类型,表示诸如点、矩形等主要⽤来存储数据的轻量。1.Class⽐较适合⼤的和复杂的数据,表现抽象和多级别的对象层次时。2.class允许继承、被继承,struct不允许,只能继承接⼝。3.Struct有性能优势,Class有⾯向对象的扩展优势。3.class可以初始化变量,struct不可以。1.class是引⽤类型,struct是值类型。

android使用json后闪退,应用闪退问题:从json信息的解析开始就会闪退-程序员宅基地

文章浏览阅读586次。想实现的功能是点击顶部按钮之后按关键字进行搜索,已经可以从服务器收到反馈的json信息,但从json信息的解析开始就会闪退,加载listview也不知道行不行public abstract class loadlistview{public ListView plv;public String js;public int listlength;public int listvisit;public..._rton转json为什么会闪退

如何使用wordnet词典,得到英文句子的同义句_get_synonyms wordnet-程序员宅基地

文章浏览阅读219次。如何使用wordnet词典,得到英文句子的同义句_get_synonyms wordnet

系统项目报表导出功能开发_积木报表 多线程-程序员宅基地

文章浏览阅读521次。系统项目报表导出 导出任务队列表 + 定时扫描 + 多线程_积木报表 多线程

ajax 如何从服务器上获取数据?_ajax 获取http数据-程序员宅基地

文章浏览阅读1.1k次,点赞9次,收藏9次。使用AJAX技术的好处之一是它能够提供更好的用户体验,因为它允许在不重新加载整个页面的情况下更新网页的某一部分。另外,AJAX还使得开发人员能够创建更复杂、更动态的Web应用程序,因为它们可以在后台与服务器进行通信,而不需要打断用户的浏览体验。在Web开发中,AJAX(Asynchronous JavaScript and XML)是一种常用的技术,用于在不重新加载整个页面的情况下,从服务器获取数据并更新网页的某一部分。使用AJAX,你可以创建异步请求,从而提供更快的响应和更好的用户体验。_ajax 获取http数据

Linux图形终端与字符终端-程序员宅基地

文章浏览阅读2.8k次。登录退出、修改密码、关机重启_字符终端

随便推点

Python与Arduino绘制超声波雷达扫描_超声波扫描建模 python库-程序员宅基地

文章浏览阅读3.8k次,点赞3次,收藏51次。前段时间看到一位发烧友制作的超声波雷达扫描神器,用到了Arduino和Processing,可惜啊,我不会Processing更看不懂人家的程序,咋办呢?嘿嘿,所以我就换了个思路解决,因为我会一点Python啊,那就动手吧!在做这个案例之前先要搞明白一个问题:怎么将Arduino通过超声波检测到的距离反馈到Python端?这个嘛,我首先想到了串行通信接口。没错!就是串口。只要Arduino将数据发送给COM口,然后Python能从COM口读取到这个数据就可以啦!我先写了一个测试程序试了一下,OK!搞定_超声波扫描建模 python库

凯撒加密方法介绍及实例说明-程序员宅基地

文章浏览阅读4.2k次。端—端加密指信息由发送端自动加密,并且由TCP/IP进行数据包封装,然后作为不可阅读和不可识别的数据穿过互联网,当这些信息到达目的地,将被自动重组、解密,而成为可读的数据。不可逆加密算法的特征是加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,只有重新输入明文,并再次经过同样不可逆的加密算法处理,得到相同的加密密文并被系统重新识别后,才能真正解密。2.使用时,加密者查找明文字母表中需要加密的消息中的每一个字母所在位置,并且写下密文字母表中对应的字母。_凯撒加密

工控协议--cip--协议解析基本记录_cip协议embedded_service_error-程序员宅基地

文章浏览阅读5.7k次。CIP报文解析常用到的几个字段:普通类型服务类型:[0x00], CIP对象:[0x02 Message Router], ioi segments:[XX]PCCC(带cmd和func)服务类型:[0x00], CIP对象:[0x02 Message Router], cmd:[0x101], fnc:[0x101]..._cip协议embedded_service_error

如何在vs2019及以后版本(如vs2022)上添加 添加ActiveX控件中的MFC类_vs添加mfc库-程序员宅基地

文章浏览阅读2.4k次,点赞9次,收藏13次。有时候我们在MFC项目开发过程中,需要用到一些微软已经提供的功能,如VC++使用EXCEL功能,这时候我们就能直接通过VS2019到如EXCEL.EXE方式,生成对应的OLE头文件,然后直接使用功能,那么,我们上篇文章中介绍了vs2017及以前的版本如何来添加。但由于微软某些方面考虑,这种方式已被放弃。从上图中可以看出,这一功能,在从vs2017版本15.9开始,后续版本已经删除了此功能。那么我们如果仍需要此功能,我们如何在新版本中添加呢。_vs添加mfc库

frame_size (1536) was not respected for a non-last frame_frame_size (1024) was not respected for a non-last-程序员宅基地

文章浏览阅读785次。用ac3编码,执行编码函数时报错入如下:[ac3 @ 0x7fed7800f200] frame_size (1536) was not respected for anon-last frame (avcodec_encode_audio2)用ac3编码时每次送入编码器的音频采样数应该是1536个采样,不然就会报上述错误。这个数字并非刻意固定,而是跟ac3内部的编码算法原理相关。全网找不到,国内音视频之路还有很长的路,音视频人一起加油吧~......_frame_size (1024) was not respected for a non-last frame

Android移动应用开发入门_在安卓移动应用开发中要在活动类文件中声迷你一个复选框变量-程序员宅基地

文章浏览阅读230次,点赞2次,收藏2次。创建Android应用程序一个项目里面可以有很多模块,而每一个模块就对应了一个应用程序。项目结构介绍_在安卓移动应用开发中要在活动类文件中声迷你一个复选框变量

推荐文章

热门文章

相关标签