英特尔集成传感器 Hub (ISH)¶
传感器 Hub 能够将传感器轮询和算法处理卸载到专用的低功耗协处理器。 这使得核心处理器能够更频繁地进入低功耗模式,从而延长电池续航时间。
有许多供应商提供符合 HID 传感器使用表的外部传感器 Hub。 这些设备可以在平板电脑、二合一可转换笔记本电脑和嵌入式产品中找到。 Linux 自 Linux 3.9 以来就支持这一点。
Intel® 推出了集成传感器 Hub,作为从 Cherry Trail 开始的 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 通信,注册了一组回调。 传输层使用它们来发送和接收消息。 请参阅结构 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 发送; 对于每次传输,该决策都是独立的。 当消息位于相应的宿主缓冲区中时,发送方会发送 DMA_XFER 消息(主机客户端发送时为 TX,FW 客户端发送时为 RX)。 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 缓冲区)
HID over ISH 客户端¶
位置:drivers/hid/intel-ish-hid
ISHTP 客户端驱动程序负责
枚举 FW ISH 客户端下的 HID 设备
获取报告描述符
将自身注册为 HID 核心的 LL 驱动程序
处理 Get/Set 功能请求
获取输入报告
HID 传感器 Hub MFD 和 IIO 传感器驱动程序¶
这些驱动程序中的功能与外部传感器 Hub 相同。 请参阅 HID 传感器框架,了解 HID 传感器 ABI 文件测试/sysfs-bus-iio,了解 IIO ABI 到用户空间。
端到端 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 内部运行的固件可以由 Intel 提供,也可以由供应商使用 Intel 提供的固件开发套件 (FDK) 开发。 Intel 会将 Intel 构建的固件上传到 linux-firmware.git
存储库,该存储库位于路径 intel/ish/
下。 对于 Lunar Lake 平台,Intel 构建的 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}
表示 Intel 平台代(例如,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
驱动程序将加载第一个匹配的固件并跳过其余固件。 如果找不到匹配的固件,它将按指定顺序继续执行下一个模式。 如果所有搜索都失败,则将加载默认的 Intel 固件(在上面的顺序中最后列出)。
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
...