英特尔集成传感器 Hub (ISH)¶
传感器 Hub 能够将传感器轮询和算法处理卸载到专用的低功耗协处理器。这使得核心处理器可以更频繁地进入低功耗模式,从而延长电池寿命。
许多供应商提供符合 HID 传感器使用表标准的外部传感器 Hub。这些 Hub 可以在平板电脑、二合一可转换笔记本电脑和嵌入式产品中找到。Linux 自 Linux 3.9 版本以来就支持此功能。
英特尔® 从 Cherry Trail 开始引入了集成传感器 Hub 作为 SoC 的一部分,现在在多代 CPU 封装中都支持。许多商业设备已经搭载了集成传感器 Hub (ISH)。这些 ISH 也符合 HID 传感器规范,但不同之处在于通信使用的传输协议。当前的外部传感器 Hub 主要使用 HID over I2C 或 USB。但是 ISH 不使用 I2C 或 USB。
概述¶
与 usbhid 实现进行类比,ISH 遵循类似的模型进行高速通信
----------------- ----------------------
| USB HID | --> | ISH HID |
----------------- ----------------------
----------------- ----------------------
| USB protocol | --> | ISH Transport |
----------------- ----------------------
----------------- ----------------------
| EHCI/XHCI | --> | ISH IPC |
----------------- ----------------------
PCI PCI
----------------- ----------------------
|Host controller| --> | ISH processor |
----------------- ----------------------
USB Link
----------------- ----------------------
| USB End points| --> | ISH Clients |
----------------- ----------------------
就像 USB 协议提供设备枚举、链接管理和用户数据封装的方法一样,ISH 也提供类似的服务。但是它非常轻量级,专门用于管理和与固件中实现的 ISH 客户端应用程序进行通信。
ISH 允许在固件中执行多个传感器管理应用程序。就像 USB 端点一样,消息可以往返于客户端。作为枚举过程的一部分,会识别这些客户端。这些客户端可以是简单的 HID 传感器应用程序、传感器校准应用程序或传感器固件更新应用程序。
实现模型类似,就像 USB 总线一样,ISH 传输也被实现为总线。在 ISH 处理器中执行的每个客户端应用程序都在此总线上注册为设备。绑定每个设备的驱动程序(ISH HID 驱动程序)识别设备类型并向 HID 核心注册。
ISH 实现:框图¶
---------------------------
| User Space Applications |
---------------------------
----------------IIO ABI----------------
--------------------------
| IIO Sensor Drivers |
--------------------------
--------------------------
| IIO core |
--------------------------
--------------------------
| HID Sensor Hub MFD |
--------------------------
--------------------------
| HID Core |
--------------------------
--------------------------
| HID over ISH Client |
--------------------------
--------------------------
| ISH Transport (ISHTP) |
--------------------------
--------------------------
| IPC Drivers |
--------------------------
OS
---------------- PCI -----------------
Hardware + Firmware
----------------------------
| ISH Hardware/Firmware(FW) |
----------------------------
以上框中的高级处理¶
硬件接口¶
ISH 以“非 VGA 未分类 PCI 设备”的形式暴露给主机。PCI 产品和供应商 ID 会因不同代的处理器而异。因此,枚举驱动程序的源代码需要逐代更新。
处理器间通信 (IPC) 驱动程序¶
位置:drivers/hid/intel-ish-hid/ipc
IPC 消息使用内存映射 I/O。寄存器在 hw-ish-regs.h 中定义。
IPC/FW 消息类型¶
有两种类型的消息,一种用于管理链接,另一种用于往返于传输层的消息。
传输消息的 TX 和 RX¶
一组内存映射寄存器支持多字节消息 TX 和 RX(例如,IPC_REG_ISH2HOST_MSG、IPC_REG_HOST2ISH_MSG)。IPC 层维护内部队列以对消息进行排序,并按顺序将其发送到固件。可选地,调用者可以注册处理程序以获取完成通知。消息传递中使用门铃机制来触发主机和客户端固件侧的处理。当调用 ISH 中断处理程序时,主机驱动程序使用 ISH2HOST 门铃寄存器来确定中断是否用于 ISH。
每一侧都有 32 个 32 位消息寄存器和一个 32 位门铃。门铃寄存器具有以下格式
Bits 0..6: fragment length (7 bits are used)
Bits 10..13: encapsulated protocol
Bits 16..19: management command (for IPC management protocol)
Bit 31: doorbell trigger (signal H/W interrupt to the other side)
Other bits are reserved, should be 0.
传输层接口¶
为了抽象化 HW 级别 IPC 通信,注册了一组回调。传输层使用它们来发送和接收消息。有关回调,请参阅 struct ishtp_hw_ops。
ISH 传输层¶
位置:drivers/hid/intel-ish-hid/ishtp/
通用传输层¶
传输层是一种双向协议,它定义: - 一组用于启动、停止、连接、断开连接和流控制的命令(有关详细信息,请参阅 ishtp/hbm.h) - 一种避免缓冲区溢出的流控制机制
此协议类似于以下文档中描述的总线消息:http://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/dcmi-hi-1-0-spec.pdf “第 7 章:总线消息层”
连接和流控制机制¶
每个 FW 客户端和协议都由 UUID 标识。为了与 FW 客户端通信,必须使用连接请求和响应总线消息建立连接。如果成功,则一对 (host_client_id 和 fw_client_id) 将标识连接。
建立连接后,对等方会彼此独立发送流控制总线消息。每个对等方只有在之前收到流控制信用时才能发送消息。一旦发送了一条消息,在收到下一个流控制信用之前,它可能不会发送另一条消息。任何一方都可以发送断开连接请求总线消息来结束通信。如果发生重大 FW 重置,链接也会断开。
对等数据传输¶
对等数据传输可以在使用或不使用 DMA 的情况下发生。根据传感器带宽要求,可以通过在 intel_ishtp 下使用模块参数 ishtp_use_dma 来启用 DMA。
每一侧(主机和 FW)都独立管理其 DMA 传输内存。当来自主机或 FW 侧的 ISHTP 客户端想要发送某些内容时,它会决定是通过 IPC 还是通过 DMA 发送;对于每次传输,该决定都是独立的。当消息位于各自的主机缓冲区中时(主机客户端发送时为 TX,FW 客户端发送时为 RX),发送方会发送 DMA_XFER 消息。DMA 消息的接收者会回复 DMA_XFER_ACK,指示发送者该消息的内存区域可以重复使用。
DMA 初始化从主机发送 DMA_ALLOC_NOTIFY 总线消息(包括 RX 缓冲区)开始,FW 回复 DMA_ALLOC_NOTIFY_ACK。除了 DMA 地址通信之外,此序列还会检查功能:如果主机不支持 DMA,则它不会发送 DMA 分配,因此 FW 也无法发送 DMA;如果 FW 不支持 DMA,则它不会回复 DMA_ALLOC_NOTIFY_ACK,在这种情况下,主机将不使用 DMA 传输。在这里,ISH 充当总线主 DMA 控制器。因此,当主机发送 DMA_XFER 时,它是执行主机 -> ISH DMA 传输的请求;当 FW 发送 DMA_XFER 时,这意味着它已经完成了 DMA 并且该消息驻留在主机上。因此,DMA_XFER 和 DMA_XFER_ACK 充当所有权指示符。
在初始状态下,所有传出的内存都属于发送者(TX 到主机,RX 到 FW),DMA_XFER 将包含 ISHTP 消息的区域的所有权转移到接收方,DMA_XFER_ACK 将所有权返回给发送者。发送者不必等待先前的 DMA_XFER 被确认,只要其所有权中的剩余连续内存足够,就可以发送另一条消息。原则上,可以一次发送多个 DMA_XFER 和 DMA_XFER_ACK 消息(最多为 IPC MTU),从而允许中断限制。当前,如果 ISHTP 消息超过 3 个 IPC 片段,则 ISH FW 决定通过 DMA 发送,否则通过 IPC 发送。
环形缓冲区¶
当客户端启动连接时,会分配一个 RX 和 TX 缓冲区环。环的大小可以由客户端指定。HID 客户端分别将 TX 和 RX 缓冲区设置为 16 和 32。在客户端的发送请求中,要发送的数据被复制到发送环形缓冲区之一,并计划使用总线消息协议发送。需要这些缓冲区是因为 FW 可能尚未处理上一条消息,并且可能没有足够的流控制信用发送。在接收端也一样,需要流控制。
主机枚举¶
主机枚举总线命令允许发现 FW 中存在的客户端。可以有多个传感器客户端和用于校准功能的客户端。
为了简化实现并允许独立驱动程序处理每个客户端,此传输层利用了 Linux 总线驱动程序模型。每个客户端都在传输总线(ishtp 总线)上注册为设备。
消息枚举序列
主机发送 HOST_START_REQ_CMD,表示主机 ISHTP 层已启动。
FW 回复 HOST_START_RES_CMD
主机发送 HOST_ENUM_REQ_CMD(枚举 FW 客户端)
FW 回复 HOST_ENUM_RES_CMD,其中包含可用 FW 客户端 ID 的位图
对于该位图中找到的每个 FW ID,主机发送 HOST_CLIENT_PROPERTIES_REQ_CMD
FW 回复 HOST_CLIENT_PROPERTIES_RES_CMD。属性包括 UUID、最大 ISHTP 消息大小等。
一旦主机接收到最后一个发现的客户端的属性,它就会认为 ISHTP 设备已完全正常工作(并分配 DMA 缓冲区)
ISH 上的 HID 客户端¶
位置:drivers/hid/intel-ish-hid
ISHTP 客户端驱动程序负责
枚举 FW ISH 客户端下的 HID 设备
获取报告描述符
以 LL 驱动程序的形式向 HID 核心注册
处理获取/设置功能请求
获取输入报告
HID 传感器 Hub MFD 和 IIO 传感器驱动程序¶
这些驱动程序中的功能与外部传感器 Hub 相同。有关 IIO ABI 到用户空间的信息,请参阅 HID 传感器框架 以获取 HID 传感器文档/ABI/测试/sysfs-bus-iio。
端到端 HID 传输顺序图¶
HID-ISH-CLN ISHTP IPC HW
| | | |
| | |-----WAKE UP------------------>|
| | | |
| | |-----HOST READY--------------->|
| | | |
| | |<----MNG_RESET_NOTIFY_ACK----- |
| | | |
| |<----ISHTP_START------ | |
| | | |
| |<-----------------HOST_START_RES_CMD-------------------|
| | | |
| |------------------QUERY_SUBSCRIBER-------------------->|
| | | |
| |------------------HOST_ENUM_REQ_CMD------------------->|
| | | |
| |<-----------------HOST_ENUM_RES_CMD--------------------|
| | | |
| |------------------HOST_CLIENT_PROPERTIES_REQ_CMD------>|
| | | |
| |<-----------------HOST_CLIENT_PROPERTIES_RES_CMD-------|
| Create new device on in ishtp bus | |
| | | |
| |------------------HOST_CLIENT_PROPERTIES_REQ_CMD------>|
| | | |
| |<-----------------HOST_CLIENT_PROPERTIES_RES_CMD-------|
| Create new device on in ishtp bus | |
| | | |
| |--Repeat HOST_CLIENT_PROPERTIES_REQ_CMD-till last one--|
| | | |
probed()
|----ishtp_cl_connect--->|----------------- CLIENT_CONNECT_REQ_CMD-------------->|
| | | |
| |<----------------CLIENT_CONNECT_RES_CMD----------------|
| | | |
|register event callback | | |
| | | |
|ishtp_cl_send(
HOSTIF_DM_ENUM_DEVICES) |----------fill ishtp_msg_hdr struct write to HW----- >|
| | | |
| | |<-----IRQ(IPC_PROTOCOL_ISHTP---|
| | | |
|<--ENUM_DEVICE RSP------| | |
| | | |
for each enumerated device
|ishtp_cl_send(
HOSTIF_GET_HID_DESCRIPTOR|----------fill ishtp_msg_hdr struct write to HW----- >|
| | | |
...Response
| | | |
for each enumerated device
|ishtp_cl_send(
HOSTIF_GET_REPORT_DESCRIPTOR|--------------fill ishtp_msg_hdr struct write to HW-- >|
| | | |
| | | |
hid_allocate_device
| | | |
hid_add_device | | |
| | | |
从主机加载 ISH 固件流程¶
从 Lunar Lake 一代开始,ISH 固件已分为两个组件,以实现更好的空间优化和更高的灵活性。这些组件包括一个集成到 BIOS 中的引导加载程序,以及一个存储在操作系统文件系统中的主固件。
该过程的工作原理如下
最初,ISHTP 驱动程序会向 ISH 引导加载程序发送一个命令,即 HOST_START_REQ_CMD。作为响应,引导加载程序会发回一个 HOST_START_RES_CMD。此响应包含 ISHTP_SUPPORT_CAP_LOADER 位。随后,ISHTP 驱动程序会检查此位是否已设置。如果已设置,则从主机开始固件加载过程。
在此过程中,ISHTP 驱动程序首先调用
request_firmware()
函数,然后发送一个 LOADER_CMD_XFER_QUERY 命令。在收到来自引导加载程序的响应后,ISHTP 驱动程序会发送一个 LOADER_CMD_XFER_FRAGMENT 命令。在收到另一个响应后,ISHTP 驱动程序会发送一个 LOADER_CMD_START 命令。引导加载程序会响应,然后继续执行主固件。过程结束后,ISHTP 驱动程序会调用 release_firmware() 函数。
有关更多详细信息,请参阅下面提供的流程描述。
+---------------+ +-----------------+
| ISHTP Driver | | ISH Bootloader |
+---------------+ +-----------------+
| |
|~~~Send HOST_START_REQ_CMD~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>|
| |
|<--Send HOST_START_RES_CMD(Includes ISHTP_SUPPORT_CAP_LOADER bit)----|
| |
****************************************************************************************
* if ISHTP_SUPPORT_CAP_LOADER bit is set *
****************************************************************************************
| |
|~~~start loading firmware from host process~~~+ |
| | |
|<---------------------------------------------+ |
| |
--------------------------- |
| Call request_firmware() | |
--------------------------- |
| |
|~~~Send LOADER_CMD_XFER_QUERY~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>|
| |
|<--Send response-----------------------------------------------------|
| |
|~~~Send LOADER_CMD_XFER_FRAGMENT~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>|
| |
|<--Send response-----------------------------------------------------|
| |
|~~~Send LOADER_CMD_START~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>|
| |
|<--Send response-----------------------------------------------------|
| |
| |~~~Jump to Main Firmware~~~+
| | |
| |<--------------------------+
| |
--------------------------- |
| Call release_firmware() | |
--------------------------- |
| |
****************************************************************************************
* end if *
****************************************************************************************
| |
+---------------+ +-----------------+
| ISHTP Driver | | ISH Bootloader |
+---------------+ +-----------------+
供应商自定义固件加载¶
在 ISH 内部运行的固件可以由英特尔提供,也可以由供应商使用英特尔提供的固件开发工具包 (FDK) 开发。英特尔会将英特尔构建的固件上游到 linux-firmware.git
存储库,位于路径 intel/ish/
下。对于 Lunar Lake 平台,英特尔构建的 ISH 固件将命名为 ish_lnlm.bin
。希望上游其自定义固件的供应商应遵循以下固件文件命名指南。
固件文件名应使用以下模式之一:
ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_NAME_CRC32}_${PRODUCT_SKU_CRC32}.bin
ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_SKU_CRC32}.bin
ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_NAME_CRC32}.bin
ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}.bin
${intel_plat_gen}
表示英特尔平台世代(例如,Lunar Lake 为lnlm
),并且长度不得超过 8 个字符。${SYS_VENDOR_CRC32}
是来自 DMI 字段DMI_SYS_VENDOR
的sys_vendor
值的 CRC32 校验和。${PRODUCT_NAME_CRC32}
是来自 DMI 字段DMI_PRODUCT_NAME
的product_name
值的 CRC32 校验和。${PRODUCT_SKU_CRC32}
是来自 DMI 字段DMI_PRODUCT_SKU
的product_sku
值的 CRC32 校验和。
在系统启动期间,ISH Linux 驱动程序将尝试按以下顺序加载固件,优先加载具有更精确匹配模式的自定义固件。
intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_NAME_CRC32}_${PRODUCT_SKU_CRC32}.bin
intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_SKU_CRC32}.bin
intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_NAME_CRC32}.bin
intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}.bin
intel/ish/ish_${intel_plat_gen}.bin
驱动程序将加载第一个匹配的固件,并跳过其余的固件。如果找不到匹配的固件,它将按照指定的顺序继续查找下一个模式。如果所有搜索都失败,则将加载默认的英特尔固件,该固件在上述顺序中列在最后。
ISH 调试¶
为了调试 ISH,使用了事件跟踪机制。要启用调试日志
echo 1 > /sys/kernel/tracing/events/intel_ish/enable
cat /sys/kernel/tracing/trace
Lenovo Thinkpad Yoga 260 上的 ISH IIO sysfs 示例¶
root@otcpl-ThinkPad-Yoga-260:~# tree -l /sys/bus/iio/devices/
/sys/bus/iio/devices/
├── iio:device0 -> ../../../devices/0044:8086:22D8.0001/HID-SENSOR-200073.9.auto/iio:device0
│ ├── buffer
│ │ ├── enable
│ │ ├── length
│ │ └── watermark
...
│ ├── in_accel_hysteresis
│ ├── in_accel_offset
│ ├── in_accel_sampling_frequency
│ ├── in_accel_scale
│ ├── in_accel_x_raw
│ ├── in_accel_y_raw
│ ├── in_accel_z_raw
│ ├── name
│ ├── scan_elements
│ │ ├── in_accel_x_en
│ │ ├── in_accel_x_index
│ │ ├── in_accel_x_type
│ │ ├── in_accel_y_en
│ │ ├── in_accel_y_index
│ │ ├── in_accel_y_type
│ │ ├── in_accel_z_en
│ │ ├── in_accel_z_index
│ │ └── in_accel_z_type
...
│ │ ├── devices
│ │ │ │ ├── buffer
│ │ │ │ │ ├── enable
│ │ │ │ │ ├── length
│ │ │ │ │ └── watermark
│ │ │ │ ├── dev
│ │ │ │ ├── in_intensity_both_raw
│ │ │ │ ├── in_intensity_hysteresis
│ │ │ │ ├── in_intensity_offset
│ │ │ │ ├── in_intensity_sampling_frequency
│ │ │ │ ├── in_intensity_scale
│ │ │ │ ├── name
│ │ │ │ ├── scan_elements
│ │ │ │ │ ├── in_intensity_both_en
│ │ │ │ │ ├── in_intensity_both_index
│ │ │ │ │ └── in_intensity_both_type
│ │ │ │ ├── trigger
│ │ │ │ │ └── current_trigger
...
│ │ │ │ ├── buffer
│ │ │ │ │ ├── enable
│ │ │ │ │ ├── length
│ │ │ │ │ └── watermark
│ │ │ │ ├── dev
│ │ │ │ ├── in_magn_hysteresis
│ │ │ │ ├── in_magn_offset
│ │ │ │ ├── in_magn_sampling_frequency
│ │ │ │ ├── in_magn_scale
│ │ │ │ ├── in_magn_x_raw
│ │ │ │ ├── in_magn_y_raw
│ │ │ │ ├── in_magn_z_raw
│ │ │ │ ├── in_rot_from_north_magnetic_tilt_comp_raw
│ │ │ │ ├── in_rot_hysteresis
│ │ │ │ ├── in_rot_offset
│ │ │ │ ├── in_rot_sampling_frequency
│ │ │ │ ├── in_rot_scale
│ │ │ │ ├── name
...
│ │ │ │ ├── scan_elements
│ │ │ │ │ ├── in_magn_x_en
│ │ │ │ │ ├── in_magn_x_index
│ │ │ │ │ ├── in_magn_x_type
│ │ │ │ │ ├── in_magn_y_en
│ │ │ │ │ ├── in_magn_y_index
│ │ │ │ │ ├── in_magn_y_type
│ │ │ │ │ ├── in_magn_z_en
│ │ │ │ │ ├── in_magn_z_index
│ │ │ │ │ ├── in_magn_z_type
│ │ │ │ │ ├── in_rot_from_north_magnetic_tilt_comp_en
│ │ │ │ │ ├── in_rot_from_north_magnetic_tilt_comp_index
│ │ │ │ │ └── in_rot_from_north_magnetic_tilt_comp_type
│ │ │ │ ├── trigger
│ │ │ │ │ └── current_trigger
...
│ │ │ │ ├── buffer
│ │ │ │ │ ├── enable
│ │ │ │ │ ├── length
│ │ │ │ │ └── watermark
│ │ │ │ ├── dev
│ │ │ │ ├── in_anglvel_hysteresis
│ │ │ │ ├── in_anglvel_offset
│ │ │ │ ├── in_anglvel_sampling_frequency
│ │ │ │ ├── in_anglvel_scale
│ │ │ │ ├── in_anglvel_x_raw
│ │ │ │ ├── in_anglvel_y_raw
│ │ │ │ ├── in_anglvel_z_raw
│ │ │ │ ├── name
│ │ │ │ ├── scan_elements
│ │ │ │ │ ├── in_anglvel_x_en
│ │ │ │ │ ├── in_anglvel_x_index
│ │ │ │ │ ├── in_anglvel_x_type
│ │ │ │ │ ├── in_anglvel_y_en
│ │ │ │ │ ├── in_anglvel_y_index
│ │ │ │ │ ├── in_anglvel_y_type
│ │ │ │ │ ├── in_anglvel_z_en
│ │ │ │ │ ├── in_anglvel_z_index
│ │ │ │ │ └── in_anglvel_z_type
│ │ │ │ ├── trigger
│ │ │ │ │ └── current_trigger
...
│ │ │ │ ├── buffer
│ │ │ │ │ ├── enable
│ │ │ │ │ ├── length
│ │ │ │ │ └── watermark
│ │ │ │ ├── dev
│ │ │ │ ├── in_anglvel_hysteresis
│ │ │ │ ├── in_anglvel_offset
│ │ │ │ ├── in_anglvel_sampling_frequency
│ │ │ │ ├── in_anglvel_scale
│ │ │ │ ├── in_anglvel_x_raw
│ │ │ │ ├── in_anglvel_y_raw
│ │ │ │ ├── in_anglvel_z_raw
│ │ │ │ ├── name
│ │ │ │ ├── scan_elements
│ │ │ │ │ ├── in_anglvel_x_en
│ │ │ │ │ ├── in_anglvel_x_index
│ │ │ │ │ ├── in_anglvel_x_type
│ │ │ │ │ ├── in_anglvel_y_en
│ │ │ │ │ ├── in_anglvel_y_index
│ │ │ │ │ ├── in_anglvel_y_type
│ │ │ │ │ ├── in_anglvel_z_en
│ │ │ │ │ ├── in_anglvel_z_index
│ │ │ │ │ └── in_anglvel_z_type
│ │ │ │ ├── trigger
│ │ │ │ │ └── current_trigger
...