启用 GPIO、I2C 和 SPI 的用户模式访问

在 Windows 10 及更高版本中,API 提供从用户模式直接访问常规用途输入/输出 (GPIO)、内部集成电路 (I2C)、串行外围接口 (SPI) 和通用异步接收器发射机 (UART)。 开发板(如 Raspberry Pi 2)将公开这些连接的子集,这些连接支持使用自定义电路扩展基本计算模块来处理特定应用程序。 通常,只需使用部分 GPIO 引脚和标头上公开的总线,即可与其他关键板载功能共享这些低级别总线。 若要保持系统稳定性,必须通过用户模式应用程序指定可供安全修改的引脚和总线。

本文档介绍如何在高级配置和电源接口 (ACPI) 中指定此配置,并提供工具来验证是否正确指定了配置。

重要

本文档的受众是统一可扩展固件接口 (UEFI) 和 ACPI 开发人员。 本文档假定其对 ACPI、ACPI 源语言 (ASL) 创作和 SpbCx/GpioClx 有一定的了解。

对 Windows 上低级别总线的用户模式访问通过现有 GpioClxSpbCx 框架实现。 在 Windows IoT 核心版和 Windows 企业版上可用的新驱动程序(称为 RhProxy)会向用户模式公开 GpioClxSpbCx 资源。 若要启用这些 API,必须在 ACPI 表(内含应向用户模式公开的每个 GPIO 和 SPB 资源)中声明用于 rhproxy 的设备节点。 本文档逐步讲解如何创作和验证 ASL。

ASL 示例

我们来演练 Raspberry Pi 2 上的 rhproxy 设备节点声明。 首先,在 \_SB 作用域中创建 ACPI 设备声明。

Device(RHPX)
{
    Name(_HID, "MSFT8000")
    Name(_CID, "MSFT8000")
    Name(_UID, 1)
}
  • _HID – 硬件 ID。将此 ID 设置为特定于供应商的硬件 ID。
  • –_CID – 兼容 ID。必须为“MSFT8000”。
  • _UID – 唯一 Id.设置为 1。

接下来,声明应向用户模式公开的每个 GPIO 和 SPB 资源。 声明资源的顺序很重要,因为资源索引用于将属性与资源相关联。 如果存在多条公开的 I2C 或 SPI 总线,声明的第一条总线被视为该类型的“默认”总线,并且会是 Windows.Devices.I2c.I2cControllerWindows.Devices.Spi.SpiControllerGetDefaultAsync() 方法返回的实例。

SPI

Raspberry Pi 有两个公开的 SPI 总线。 SPI0 有两个硬件芯片选择线,SPI1 有一个硬件芯片选择线。 每个总线的每个芯片选择行都需要一个 SPISerialBus() 资源声明。 以下两个 SPISerialBus 资源声明适用于 SPI0 上的两个芯片选择行。 DeviceSelection 字段包含一个唯一值,驱动程序将其解释为硬件芯片选择行标识符。 放入 DeviceSelection 字段的确切值取决于驱动程序如何解释 ACPI 连接描述符的此字段。

注意

本文包含对“从属”这一术语的引用,Microsoft 不容忍该术语,并且已停止在新产品和文档中使用该术语。 在从软件中删除该术语后,我们会将其从本文中删除。

// Index 0
SPISerialBus(              // SCKL - GPIO 11 - Pin 23
                           // MOSI - GPIO 10 - Pin 19
                           // MISO - GPIO 9  - Pin 21
                           // CE0  - GPIO 8  - Pin 24
    0,                     // Device selection (CE0)
    PolarityLow,           // Device selection polarity
    FourWireMode,          // wiremode
    0,                     // databit len: placeholder
    ControllerInitiated,   // slave mode
    0,                     // connection speed: placeholder
    ClockPolarityLow,      // clock polarity: placeholder
    ClockPhaseFirst,       // clock phase: placeholder
    "\\_SB.SPI0",          // ResourceSource: SPI bus controller name
    0,                     // ResourceSourceIndex
                           // Resource usage
    )                      // Vendor Data

// Index 1
SPISerialBus(              // SCKL - GPIO 11 - Pin 23
                           // MOSI - GPIO 10 - Pin 19
                           // MISO - GPIO 9  - Pin 21
                           // CE1  - GPIO 7  - Pin 26
    1,                     // Device selection (CE1)
    PolarityLow,           // Device selection polarity
    FourWireMode,          // wiremode
    0,                     // databit len: placeholder
    ControllerInitiated,   // slave mode
    0,                     // connection speed: placeholder
    ClockPolarityLow,      // clock polarity: placeholder
    ClockPhaseFirst,       // clock phase: placeholder
    "\\_SB.SPI0",          // ResourceSource: SPI bus controller name
    0,                     // ResourceSourceIndex
                           // Resource usage
    )                      // Vendor Data

软件如何知道这两个资源应该与同一总线相关联? 在 DSD 中指定了总线友好名称和资源索引之间的映射:

Package(2) { "bus-SPI-SPI0", Package() { 0, 1 }},

这会创建一个名为“SPI0”的总线,其中包含两条芯片选择线 - 资源索引 0 和 1。 还需要多个属性来声明 SPI 总线的功能。

Package(2) { "SPI0-MinClockInHz", 7629 },
Package(2) { "SPI0-MaxClockInHz", 125000000 },

MinClockInHz 和 MaxClockInHz 属性指定控制器支持的最小和最大时钟速度。 API 将阻止用户指定此范围之外的值。 时钟速度将传递到连接描述符 _SPE 字段中的 SPB 驱动程序 (ACPI section 6.4.3.8.2.2)。

Package(2) { "SPI0-SupportedDataBitLengths", Package() { 8 }},

SupportedDataBitLengths 属性列出了控制器支持的数据位长度。 可以在逗号分隔的列表中指定多个值。 API 将阻止用户指定此列表之外的值。 数据位长度将传递到连接描述符_LEN字段中的 SPB 驱动程序 (ACPI section 6.4.3.8.2.2)。

可以将这些资源声明视为“模板”。 某些字段在系统启动时是固定的,而另一些字段是在运行时动态指定的。 SPISerialBus 描述符的以下字段是固定的:

  • DeviceSelection
  • DeviceSelectionPolarity
  • WireMode
  • SlaveMode
  • ResourceSource

以下字段是用户在运行时指定的值的占位符:

  • DataBitLength
  • ConnectionSpeed
  • ClockPolarity
  • ClockPhase

由于 SPI1 仅包含单个芯片选择行,因此声明单个 SPISerialBus() 资源:

// Index 2
SPISerialBus(              // SCKL - GPIO 21 - Pin 40
                           // MOSI - GPIO 20 - Pin 38
                           // MISO - GPIO 19 - Pin 35
                           // CE1  - GPIO 17 - Pin 11
    1,                     // Device selection (CE1)
    PolarityLow,           // Device selection polarity
    FourWireMode,          // wiremode
    0,                     // databit len: placeholder
    ControllerInitiated,   // slave mode
    0,                     // connection speed: placeholder
    ClockPolarityLow,      // clock polarity: placeholder
    ClockPhaseFirst,       // clock phase: placeholder
    "\\_SB.SPI1",          // ResourceSource: SPI bus controller name
    0,                     // ResourceSourceIndex
                           // Resource usage
    )                      // Vendor Data

随附的友好名称声明(是必需的)在 DSD 中指定,并引用此资源声明的索引。

Package(2) { "bus-SPI-SPI1", Package() { 2 }},

这会创建名为“SPI1”的总线,并将其与资源索引 2 相关联。

SPI 驱动程序要求

  • 必须使用 SpbCx 或与 SpbCx 兼容
  • 必须已通过 MITT SPI 测试
  • 必须支持 4Mhz 时钟速度
  • 必须支持 8 位数据长度
  • 必须支持所有 SPI 模式:0、1、2、3

I2C

接下来,声明 I2C 资源。 Raspberry Pi 在引脚 3 和 5 上公开单个 I2C 总线。

// Index 3
I2CSerialBus(              // Pin 3 (GPIO2, SDA1), 5 (GPIO3, SCL1)
    0xFFFF,                // SlaveAddress: placeholder
    ,                      // SlaveMode: default to ControllerInitiated
    0,                     // ConnectionSpeed: placeholder
    ,                      // Addressing Mode: placeholder
    "\\_SB.I2C1",          // ResourceSource: I2C bus controller name
    ,
    ,
    )                      // VendorData

DSD 中指定随附的友好名称声明(必需):

Package(2) { "bus-I2C-I2C1", Package() { 3 }},

这会声明一个 I2C 总线,其友好名称为“I2C1”,该总线引用资源索引 3,它是上面声明的 I2CSerialBus() 资源的索引。

I2CSerialBus() 描述符的以下字段是固定的:

  • SlaveMode
  • ResourceSource

以下字段是用户在运行时指定的值的占位符:

  • SlaveAddress
  • ConnectionSpeed
  • AddressingMode

I2C 驱动程序要求

  • 必须使用 SpbCx 或与 SpbCx 兼容
  • 必须已通过 MITT I2C 测试
  • 必须支持 7 位寻址
  • 必须支持 100kHz 时钟速度
  • 必须支持 400kHz 时钟速度

GPIO

接下来,声明向用户模式公开的所有 GPIO 引脚。 我们在确定要公开的引脚时提供以下指导:

  • 在公开的标头上声明所有引脚。
  • 声明连接到有用的载入函数(如按钮和 LED)的引脚。
  • 请勿声明为系统函数保留或未连接到任何内容的引脚。

以下 ASL 块声明两个引脚 - GPIO4 和 GPIO5。 为了简洁起见,此处未显示其他引脚。 附录 C 包含可用于生成 GPIO 资源的示例 powershell 脚本。

// Index 4 – GPIO 4
GpioIO(Shared, PullUp, , , , “\\_SB.GPI0”, , , , ) { 4 }
GpioInt(Edge, ActiveBoth, Shared, PullUp, 0, “\\_SB.GPI0”,) { 4 }

// Index 6 – GPIO 5
GpioIO(Shared, PullUp, , , , “\\_SB.GPI0”, , , , ) { 5 }
GpioInt(Edge, ActiveBoth, Shared, PullUp, 0, “\\_SB.GPI0”,) { 5 }

声明 GPIO 引脚时必须遵守以下要求:

  • 仅支持内存映射 GPIO 控制器。 不支持通过 I2C/SPI 进行接口的 GPIO 控制器。 如果控制器驱动程序在 CLIENT_CONTROLLER_BASIC_INFORMATION 结构中设置 MemoryMappedController 标志以响应 CLIENT_QueryControllerBasicInformation 回调,则控制器驱动程序是内存映射控制器。
  • 每个引脚都需要 GpioIO 和 GpioInt 资源。 GpioInt 资源必须紧跟 GpioIO 资源,并且必须引用相同的引脚号。
  • GPIO 资源必须通过增加引脚数进行排序。
  • 每个 GpioIO 和 GpioInt 资源都必须在引脚列表中仅包含一个引脚编号。
  • 两个描述符的 ShareType 字段必须共享
  • GpioInt 描述符的 EdgeLevel 字段必须是 Edge
  • GpioInt 描述符的 ActiveLevel 字段必须是 ActiveBoth
  • PinConfig 字段
    • GpioIO 和 GpioInt 描述符中必须相同
    • 必须是 PullUp、PullDown 或 PullNone 之一。 不能为 PullDefault。
    • 拉取配置必须与引脚的开机状态匹配。 从开机状态将引脚置于指定的拉取模式不得更改引脚的状态。 例如,如果数据表指定引脚附带了拉取,请将 PinConfig 指定为 PullUp。

固件、UEFI 和驱动程序初始化代码不应在启动期间从其开机状态更改引脚的状态。 只有用户知道附加到引脚的内容以及因此哪些状态转换是安全的。 必须记录每个引脚的开机状态,以便用户可以设计与引脚正确接口的硬件。 引脚在启动期间不得意外更改状态。

支持的驱动器模式

如果你的 GPIO 控制器支持内置拉取和下拉电流,除了高二进制输入和输出,还必须使用可选的 SupportedDriveModes 属性来指定它。

Package (2) { “GPIO-SupportedDriveModes”, 0xf },

SupportedDriveModes 属性指示 GPIO 控制器支持哪些驱动器模式。 在上面的示例中,支持以下所有驱动器模式。 该属性是以下值的位掩码:

标志值 驱动器模式 说明
0x1 InputHighImpedance 该引脚支持高二元输入,对应于 ACPI 中的“PullNone”值。
0x2 InputPullUp 该引脚支持内置拉取阀,对应于 ACPI 中的“PullUp”值。
0x4 InputPullDown 该引脚支持内置拉取阀,对应于 ACPI 中的“PullUp”值。
0x8 OutputCmos 引脚支持生成强高点和强低点(而不是打开排水)。

几乎所有 GPIO 控制器都支持 InputHighImpedance 和 OutputCmos。 如果未指定 SupportedDriveModes 属性,则这是默认值。

如果在到达公开标头之前,GPIO 信号经过级别移位器,请声明 SOC 支持的驱动器模式,即使外部标头上无法观察到驱动器模式也是如此。 例如,如果引脚经过双向级别移位器,使引脚显示为打开排水,并带有抗拒拉力,即使引脚配置为高引脚输入,也永远不会在公开标头上观察到高电压状态。 仍应声明引脚支持高二元输入。

引脚编号

Windows 支持两个引脚编号方案:

  • 顺序引脚编号 – 用户会看到数字,如 0、1、2...直到公开的最高引脚数。 0 是在 ASL 中声明的第一个 GpioIo 资源,1 是在 ASL 中声明的第二个 GpioIo 资源,依此说明。
  • 本机引脚编号 - 用户看到 GpioIo 描述符中指定的引脚编号,例如 4、5、12、13 …
Package (2) { “GPIO-UseDescriptorPinNumbers”, 1 },

UseDescriptorPinNumbers 属性指示 Windows 使用本机引脚编号,而不是顺序引脚编号。 如果未指定 UseDescriptorPinNumbers 属性或其值为零,则 Windows 将默认为顺序引脚编号。

如果使用本机引脚编号,则还必须指定 PinCount 属性。

Package (2) { “GPIO-PinCount”, 54 },

PinCount 属性应匹配 GpioClx 驱动程序的 CLIENT_QueryControllerBasicInformation 回调中通过 TotalPins 属性返回的值。

选择与开发板现有已发布文档最兼容的编号方案。 例如,Raspberry Pi 使用本机引脚编号,因为许多现有引脚图都使用 BCM2835 引脚编号。 MinnowBoardMax 使用顺序引脚编号,因为现有的引脚图示很少,并且顺序引脚编号简化了开发人员体验,因为只有 10 个引脚在超过 200 个引脚中公开。 使用顺序或本机引脚编号的决定应旨在减少开发人员混淆。

GPIO 驱动程序要求

  • 必须使用 GpioClx
  • 必须映射 SOC 内存
  • 必须使用模拟的 ActiveBoth 中断处理

UART

如果 UART 驱动程序使用 SerCxSerCx2,则可以使用 rhproxy 向用户模式公开该驱动程序。 创建类型 GUID_DEVINTERFACE_COMPORT 的设备接口的 UART 驱动程序不需要使用 rhproxy。 收件箱 Serial.sys 驱动程序是其中一种情况。

若要向用户模式公开 SerCx 样式的 UART,请按照如下方法声明 UARTSerialBus 资源。

// Index 2
UARTSerialBus(           // Pin 17, 19 of JP1, for SIO_UART2
    115200,                // InitialBaudRate: in bits ber second
    ,                      // BitsPerByte: default to 8 bits
    ,                      // StopBits: Defaults to one bit
    0xfc,                  // LinesInUse: 8 1-bit flags to declare line enabled
    ,                      // IsBigEndian: default to LittleEndian
    ,                      // Parity: Defaults to no parity
    ,                      // FlowControl: Defaults to no flow control
    32,                    // ReceiveBufferSize
    32,                    // TransmitBufferSize
    "\\_SB.URT2",          // ResourceSource: UART bus controller name
    ,
    ,
    ,
    )

只有 ResourceSource 字段是固定的,而所有其他字段都是用户在运行时指定的值的占位符。

随附的友好名称声明为:

Package(2) { "bus-UART-UART2", Package() { 2 }},

这会将友好名称“UART2”分配给控制器,该名称是用户用于从用户模式访问总线的标识符。

运行时引脚复用

引脚复用可用于不同函数的相同物理引脚。 多个不同的芯片上外围设备(例如 I2C 控制器、SPI 控制器和 GPIO 控制器)可能会路由到 SOC 上的同一物理引脚。 复用函数控制在任意给定时间在引脚上处于活动状态的函数。 传统上,固件负责在启动时建立函数分配,并且此分配通过启动会话保持静态。 运行时引脚复用增加了在运行时重新配置引脚函数分配的功能。 使用户能够快速重新配置开发板的引脚,以允许用户在运行时加速开发时选择引脚的功能,并使硬件能够支持比静态配置更广泛的应用程序范围。

用户无需编写任何其他代码即可使用对 GPIO、I2C、SPI 和 UART 的复用支持。 当用户使用 OpenPin() 或 FromIdAsync() 打开 GPIO 或总线时,基础物理引脚会自动复用到请求的函数。 如果引脚已被其他函数使用,则 OpenPin() 或 FromIdAsync() 调用将失败。 当用户通过释放 GpioPin、I2cDevice、、SpiDevice 或 SerialDevice 对象关闭设备时,将释放引脚,以便稍后打开其他函数。

Windows 包含对 GpioClx、SpbCx 和 SerCx 框架中的引脚复用的内置支持。 这些框架协同工作,在访问 GPIO 引脚或总线时自动将引脚切换到正确的函数。 对引脚的访问是仲裁的,以防止多个客户端之间的冲突。 除了此内置支持之外,引脚复用的接口和协议是常规用途,可以扩展以支持其他设备和方案。

本文档首先介绍了引脚复用所涉及的基础接口和协议,然后介绍如何向 GpioClx、SpbCx 和 SerCx 控制器驱动程序添加对引脚复用的支持。

引脚复用体系结构

本部分介绍引脚复用所涉及的基础接口和协议。 了解基础协议不一定需要支持 GpioClx/SpbCx/SerCx 驱动程序的引脚复用。 有关如何支持 GpioCls/SpbCx/SerCx 驱动程序的引脚复用的详细信息,请参阅在 GpioClx 客户端驱动程序中实现引脚复用支持,以及在 SpbCx 和 SerCx 控制器驱动程序中使用复用支持。

引脚复用是通过多个组件的合作完成的。

  • 引脚复用服务器 – 这些是控制引脚复用控制块的驱动程序。 引脚复用服务器通过请求保留复用资源(通过 IRP_MJ_CREATE 请求)和请求切换引脚的功能(通过 *IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS 请求),从客户端接收引脚复用请求。 引脚复用服务器通常是 GPIO 驱动程序,因为复用块有时是 GPIO 块的一部分。 即使复用块是单独的外围设备,GPIO 驱动程序也是放置复用功能的逻辑位置。
  • 引脚复用客户端 – 这些是使用引脚复用的驱动程序。 引脚复用客户端从 ACPI 固件接收引脚复用资源。 引脚复用资源是一种连接资源,由资源中心管理。 引脚复用客户端通过打开资源的句柄来保留引脚复用资源。 若要影响硬件更改,客户端必须通过发送 IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS 请求来提交配置。 客户端通过关闭句柄释放引脚复用资源,此时复用配置还原为其默认状态。
  • ACPI 固件 – 使用 MsftFunctionConfig() 资源指定复用配置。 MsftFunctionConfig 资源表示客户端需要哪些引脚(其中复用配置)。 MsftFunctionConfig 资源包含函数号、拉取配置和引脚编号列表。 MsftFunctionConfig 资源用于将复用客户端固定为硬件资源,这些资源由驱动程序在其 PrepareHardware 回调中接收,类似于 GPIO 和 SPB 连接资源。 客户端接收可用于打开资源的句柄的资源中心 ID。

必须将 /MsftInternal 命令行开关传递给 asl.exe 以编译包含 MsftFunctionConfig() 描述符的 ASL 文件,因为这些描述符当前正在 ACPI 工作委员会审查。 例如:asl.exe /MsftInternal dsdt.asl

引脚复用中涉及的操作序列如下所示。

Pin muxing client server interaction

  1. 客户端在其 EvtDevicePrepareHardware() 回调中从 ACPI 固件接收 MsftFunctionConfig 资源。
  2. 客户端使用资源中心帮助程序函数 RESOURCE_HUB_CREATE_PATH_FROM_ID() 从资源 ID 创建路径,然后打开路径句柄(使用 ZwCreateFile()、IoGetDeviceObjectPointer() 或 WdfIoTargetOpen())。
  3. 服务器使用资源中心帮助程序函数 RESOURCE_HUB_ID_FROM_FILE_NAME() 从文件路径中提取资源中心 ID,然后查询资源中心以获取资源描述符。
  4. 服务器对描述符中的每个引脚执行共享仲裁,并完成 IRP_MJ_CREATE 请求。
  5. 客户端在收到的句柄上发出 IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS 请求。
  6. 为了响应 IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS,服务器通过在每个引脚上激活指定的函数来执行硬件复用操作。
  7. 客户端将继续执行依赖于所请求的引脚复用配置的操作。
  8. 当客户端不再需要复用引脚时,它会关闭句柄。
  9. 为了响应正在关闭的句柄,服务器还原引脚回到其初始状态。

引脚复用客户端的协议说明

本部分介绍客户端如何使用引脚复用功能。 这不适用于 SerCx 控制器驱动程序和 SpbCx 控制器驱动程序,因为框架代表控制器驱动程序实现此协议。

正在分析资源

WDF 驱动程序在其 EvtDevicePrepareHardware() 例程中接收 MsftFunctionConfig() 资源。 MsftFunctionConfig 资源可以通过以下字段进行标识:

CM_PARTIAL_RESOURCE_DESCRIPTOR::Type = CmResourceTypeConnection
CM_PARTIAL_RESOURCE_DESCRIPTOR::u.Connection.Class = CM_RESOURCE_CONNECTION_CLASS_FUNCTION_CONFIG
CM_PARTIAL_RESOURCE_DESCRIPTOR::u.Connection.Type = CM_RESOURCE_CONNECTION_TYPE_FUNCTION_CONFIG

EvtDevicePrepareHardware() 例程可能会提取 MsftFunctionConfig 资源,如下所示:

EVT_WDF_DEVICE_PREPARE_HARDWARE evtDevicePrepareHardware;

_Use_decl_annotations_
NTSTATUS
evtDevicePrepareHardware (
    WDFDEVICE WdfDevice,
    WDFCMRESLIST ResourcesTranslated
    )
{
    PAGED_CODE();

    LARGE_INTEGER connectionId;
    ULONG functionConfigCount = 0;

    const ULONG resourceCount = WdfCmResourceListGetCount(ResourcesTranslated);
    for (ULONG index = 0; index < resourceCount; ++index) {
        const CM_PARTIAL_RESOURCE_DESCRIPTOR* resDescPtr =
            WdfCmResourceListGetDescriptor(ResourcesTranslated, index);

        switch (resDescPtr->Type) {
        case CmResourceTypeConnection:
            switch (resDescPtr->u.Connection.Class) {
            case CM_RESOURCE_CONNECTION_CLASS_FUNCTION_CONFIG:
                switch (resDescPtr->u.Connection.Type) {
                case CM_RESOURCE_CONNECTION_TYPE_FUNCTION_CONFIG:
                    switch (functionConfigCount) {
                    case 0:
                        // save the connection ID
                        connectionId.LowPart = resDescPtr->u.Connection.IdLowPart;
                        connectionId.HighPart = resDescPtr->u.Connection.IdHighPart;
                        break;
                    } // switch (functionConfigCount)
                    ++functionConfigCount;
                    break; // CM_RESOURCE_CONNECTION_TYPE_FUNCTION_CONFIG

                } // switch (resDescPtr->u.Connection.Type)
                break; // CM_RESOURCE_CONNECTION_CLASS_FUNCTION_CONFIG
            } // switch (resDescPtr->u.Connection.Class)
            break;
        } // switch
    } // for (resource list)

    if (functionConfigCount < 1) {
        return STATUS_INVALID_DEVICE_CONFIGURATION;
    }
    // TODO: save connectionId in the device context for later use

    return STATUS_SUCCESS;
}

保留和提交资源

当客户端想要复用引脚时,它会保留并提交 MsftFunctionConfig 资源。 以下示例演示客户端如何保留和提交 MsftFunctionConfig 资源。

_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS AcquireFunctionConfigResource (
    WDFDEVICE WdfDevice,
    LARGE_INTEGER ConnectionId,
    _Out_ WDFIOTARGET* ResourceHandlePtr
    )
{
    PAGED_CODE();

    //
    // Form the resource path from the connection ID
    //
    DECLARE_UNICODE_STRING_SIZE(resourcePath, RESOURCE_HUB_PATH_CHARS);
    NTSTATUS status = RESOURCE_HUB_CREATE_PATH_FROM_ID(
            &resourcePath,
            ConnectionId.LowPart,
            ConnectionId.HighPart);
    if (!NT_SUCCESS(status)) {
        return status;
    }

    //
    // Create a WDFIOTARGET
    //
    WDFIOTARGET resourceHandle;
    status = WdfIoTargetCreate(WdfDevice, WDF_NO_ATTRIBUTES, &resourceHandle);
    if (!NT_SUCCESS(status)) {
        return status;
    }

    //
    // Reserve the resource by opening a WDFIOTARGET to the resource
    //
    WDF_IO_TARGET_OPEN_PARAMS openParams;
    WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME(
        &openParams,
        &resourcePath,
        FILE_GENERIC_READ | FILE_GENERIC_WRITE);

    status = WdfIoTargetOpen(resourceHandle, &openParams);
    if (!NT_SUCCESS(status)) {
        return status;
    }
    //
    // Commit the resource
    //
    status = WdfIoTargetSendIoctlSynchronously(
            resourceHandle,
            WDF_NO_HANDLE,      // WdfRequest
            IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS,
            nullptr,            // InputBuffer
            nullptr,            // OutputBuffer
            nullptr,            // RequestOptions
            nullptr);           // BytesReturned

    if (!NT_SUCCESS(status)) {
        WdfIoTargetClose(resourceHandle);
        return status;
    }

    //
    // Pins were successfully muxed, return the handle to the caller
    //
    *ResourceHandlePtr = resourceHandle;
    return STATUS_SUCCESS;
}

驱动程序应在其中一个上下文区域中存储 WDFIOTARGET,以便稍后可以关闭它。 当驱动程序准备好释放复用配置时,如果想要重复使用 WDFIOTARGET,则应通过调用 WdfObjectDelete() 或 WdfIoTargetClose() 关闭资源句柄。

    WdfObjectDelete(resourceHandle);

当客户端关闭其资源句柄时,引脚将复用回其初始状态,现在可以由其他客户端获取。

引脚复用服务器的协议说明

本部分介绍引脚复用服务器如何向客户端公开其功能。 这不适用于 GpioClx 微型端口驱动程序,因为框架代表客户端驱动程序实现此协议。 有关如何在 GpioClx 客户端驱动程序中支持引脚复用的详细信息,请参阅在 GpioClx 客户端驱动程序中实现复用支持。

处理 IRP_MJ_CREATE 请求

客户端在想要保留引脚复用资源时打开资源的句柄。 引脚复用服务器通过重新分析操作从资源中心接收 IRP_MJ_CREATE 请求。 IRP_MJ_CREATE 请求的尾随路径组件包含资源中心 ID,它是十六进制格式的 64 位整数。 服务器应使用 reshub.h 中的 RESOURCE_HUB_ID_FROM_FILE_NAME() 从文件名中提取资源中心 ID,并将 IOCTL_RH_QUERY_CONNECTION_PROPERTIES 发送到资源中心以获取 MsftFunctionConfig() 描述符。

服务器应验证描述符,并从描述符中提取共享模式和引脚列表。 然后,它应对引脚执行共享仲裁,如果成功,请在完成请求之前将引脚标记为保留。

如果共享仲裁成功,则共享仲裁会在整个引脚列表中成功。 每个引脚应按如下所示进行仲裁:

  • 如果引脚尚未保留,则共享仲裁成功。
  • 如果引脚已保留为独占,则共享仲裁将失败。
  • 如果引脚已保留为共享,
    • 并且传入请求是共享的,共享仲裁成功。
    • 并且传入请求是独占的,共享仲裁失败。

如果共享仲裁失败,应使用 STATUS_GPIO_INCOMPATIBLE_CONNECT_MODE 完成请求。 如果共享仲裁成功,则应使用 STATUS_SUCCESS 完成请求。

请注意,应从 MsftFunctionConfig 描述符而不是从 IrpSp->Parameters.Create.ShareAccess 中获取传入请求的共享模式。

处理 IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS 请求

客户端通过打开句柄成功保留 MsftFunctionConfig 资源后,可以发送 IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS 以请求服务器执行实际的硬件复用操作。 当服务器收到 IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS 时,对于引脚列表中的每个引脚,它应该

  • 将 PNP_FUNCTION_CONFIG_DESCRIPTOR 结构的 PinConfiguration 成员中指定的拉取模式设置为硬件。
  • 将引脚复用到由 PNP_FUNCTION_CONFIG_DESCRIPTOR 结构的 FunctionNumber 成员指定的函数。

然后,服务器应使用 STATUS_SUCCESS 完成请求。

FunctionNumber 的含义由服务器定义,据了解,MsftFunctionConfig 描述符是使用服务器如何解释此字段的知识创作的。

请记住,当句柄关闭时,服务器必须还原收到 IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS 时它们所处于的配置的引脚,因此服务器可能需要先保存引脚的状态,然后再对其进行修改。

处理 IRP_MJ_CLO 标准版请求

当客户端不再需要复用资源时,它会关闭其句柄。 当服务器收到 IRP_MJ_CLOSE 请求时,它应将引脚还原到收到 IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS 时它们处于的状态。 如果客户端从未发送 IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS,则无需执行任何操作。 然后,服务器应将引脚标记为与共享仲裁相关的可用,并使用 STATUS_SUCCESS 完成请求。 请务必将 IRP_MJ_CLOSE 处理与 IRP_MJ_CREATE 处理正确同步。

ACPI 表的创作指南

本部分介绍如何向客户端驱动程序提供复用资源。 请注意,需要 Microsoft ASL 编译器内部版本 14327 或更高版本来编译包含 MsftFunctionConfig() 资源的表。 MsftFunctionConfig() 资源提供给引脚复用客户端作为硬件资源。 应将 MsftFunctionConfig() 资源提供给需要更改引脚复用的驱动程序,这些驱动程序通常是 SPB 和串行控制器驱动程序;但不应将该资源提供给 SPB 和串行外设驱动程序,因为该控制器驱动程序将处理复用配置。 MsftFunctionConfig() ACPI 宏的定义如下:

  MsftFunctionConfig(Shared/Exclusive
                PinPullConfig,
                FunctionNumber,
                ResourceSource,
                ResourceSourceIndex,
                ResourceConsumer/ResourceProducer,
                VendorData) { Pin List }

  • 共享/独占 – 如果为独占,则一次可由单个客户端获取此引脚。 如果共享,则多个共享客户端可以获取资源。 始终将此设置为独占,因为允许多个未协调的客户端访问可变资源可能会导致数据争用以及无法预测的结果。
  • PinPullConfig – 其中一个
    • PullDefault – 使用 SOC 定义的开机默认拉取配置
    • PullUp - 启用拉取阀
    • PullDown - 启用下拉阀
    • PullNone – 禁用所有拉取阀
  • FunctionNumber – 要编程到复用函数中的函数号。
  • ResourceSource – 引脚复用服务器的 ACPI 命名空间路径
  • ResourceSourceIndex – 将此设置为 0
  • ResourceConsumer/ResourceProducer – 将此设置为 ResourceConsumer
  • VendorData – 可选的二进制数据,其含义由引脚复用服务器定义。 这通常应留空
  • 引脚列表 – 配置应用的引脚编号的逗号分隔列表。 当引脚复用服务器是 GpioClx 驱动程序时,这些是 GPIO 引脚编号,其含义与 GpioIo 描述符中的引脚号相同。

以下示例演示如何向 I2C 控制器驱动程序提供 MsftFunctionConfig() 资源。

Device(I2C1)
{
    Name(_HID, "BCM2841")
    Name(_CID, "BCMI2C")
    Name(_UID, 0x1)
    Method(_STA)
    {
        Return(0xf)
    }
    Method(_CRS, 0x0, NotSerialized)
    {
        Name(RBUF, ResourceTemplate()
        {
            Memory32Fixed(ReadWrite, 0x3F804000, 0x20)
            Interrupt(ResourceConsumer, Level, ActiveHigh, Shared) { 0x55 }
            MsftFunctionConfig(Exclusive, PullUp, 4, "\\_SB.GPI0", 0, ResourceConsumer, ) { 2, 3 }
        })
        Return(RBUF)
    }
}

除了控制器驱动程序通常需要的内存和中断资源外,还指定了资源 MsftFunctionConfig()。 此资源允许 I2C 控制器驱动程序将引脚 2 和 3(由 \_SB.GPIO0 处的设备节点进行管理)置于已启用上拉电阻器的功能 4 中。

支持 GpioClx 客户端驱动程序中的复用支持

GpioClx 具有对引脚复用的内置支持。 GpioClx 微型端口驱动程序(也称为“GpioClx 客户端驱动程序”),驱动 GPIO 控制器硬件。 从 Windows 10 内部版本 14327 开始,GpioClx 微型端口驱动程序可以通过实现两个新的 DDI 来添加对引脚复用的支持:

  • CLIENT_ConnectFunctionConfigPins – 由 GpioClx 调用,以命令微型端口驱动程序应用指定的复用配置。
  • CLIENT_DisconnectFunctionConfigPins – 由 GpioClx 调用,以命令微型端口驱动程序应用指定的复用配置。

有关这些例程的说明,请参阅 GpioClx 事件回调函数

除了这两个新的 DDI 之外,还应审核现有的 DDI,以便实现引脚复用兼容性:

  • CLIENT_ConnectIoPins/CLIENT_ConnectInterrupt – CLIENT_ConnectIoPins 由 GpioClx 调用,以命令微型端口驱动程序为 GPIO 输入或输出配置设置的引脚。 GPIO 与 MsftFunctionConfig 互斥,这意味着永远不会同时为 GPIO 和 MsftFunctionConfig 连接引脚。 由于引脚的默认函数不需要为 GPIO,因此在调用 ConnectIoPins 时,引脚不一定复用到 GPIO。 ConnectIoPins 需要执行所有必要的操作,使引脚为 GPIO IO 做好准备,包括复用操作。 CLIENT_ConnectInterrupt 的行为应该类似,因为中断可以视为 GPIO 输入的特殊情况。
  • CLIENT_DisconnectIoPins/CLIENT_DisconnectInterrupt – 除非指定 PreserveConfiguration 标志,否则这些例程应将引脚返回到调用 CLIENT_ConnectIoPins/CLIENT_ConnectInterrupt 时的状态。 除了将引脚方向还原到其默认状态之外,微型端口还应将每个引脚的复用状态还原到调用 _Connect 例程时处于的状态。

例如,假设引脚的默认复用配置为 UART,并且引脚也可以用作 GPIO。 当调用 CLIENT_ConnectIoPins 以连接 GPIO 的引脚时,它应将引脚复用到 GPIO,并在 CLIENT_DisconnectIoPins 中将引脚复用回 UART。 一般情况下,Disconnect 例程应撤消 Connect 例程执行的操作。

支持 SpbCx 和 SerCx 控制器驱动程序中的复用

从 Windows 10 内部版本 14327 开始,SpbCxSerCx 框架包含对引脚复用的内置支持,使 SpbCxSerCx 控制器驱动程序能够成为引脚复用客户端,而无需对控制器驱动程序本身进行任何代码更改。 通过扩展,连接到已启用复用的 SpbCx/SerCx 控制器驱动程序的任何 SpbCx/SerCx 外围驱动程序都将触发引脚复用活动。

下图显示了每个组件之间的依赖关系。 可以看到,引脚复用会将 SerCx 和 SpbCx 控制器驱动程序的依赖项引入到 GPIO 驱动程序,该驱动程序通常负责复用。

Pin muxing dependency

在设备初始化时,SpbCxSerCx 框架将分析作为硬件资源提供给设备的所有 MsftFunctionConfig() 资源。 然后,SpbCx/SerCx 按需获取并释放引脚复用资源。

SpbCx 仅在调用客户端驱动程序的 EvtSpbTargetConnect() 回调之前,在其 IRP_MJ_CREATE 处理程序中应用引脚复用配置。 如果无法应用复用配置,则不会调用控制器驱动程序的 EvtSpbTargetConnect() 回调。 因此,SPB 控制器驱动程序可能假定在调用 SPB 函数时 EvtSpbTargetConnect() 将引脚复用到 SPB 函数。

SpbCx 仅在调用控制器驱动程序的 EvtSpbTargetDisconnect() 回调之后,在其 IRP_MJ_CLOSE 处理程序中恢复引脚复用配置。 结果是,每当外围驱动程序打开 SPB 控制器驱动程序的句柄时,引脚将复用到 SPB 函数,并在外围驱动程序关闭其句柄时被复用。

SerCx 具有类似的行为。 SerCx 仅在调用控制器驱动程序的 EvtSerCx2FileOpen() 回调之前,在其 IRP_MJ_CREATE 处理程序中获取所有 MsftFunctionConfig() 资源;仅在调用控制器驱动程序的 EvtSerCx2FileClose 回调之后,在其 IRP_MJ_CLOSE 处理程序中释放所有资源。

SerCxSpbCx 控制器驱动程序的动态引脚复用的含义是,它们必须能够容忍某些时间从 SPB/UART 函数中复用的引脚。 控制器驱动程序需要假定在调用 EvtSpbTargetConnect()EvtSerCx2FileOpen() 之前不会复用引脚。 在以下回调期间,不需要将引脚复用到 SPB/UART 函数。 以下不是完整列表,而是表示控制器驱动程序实现的最常见 PNP 例程。

  • DriverEntry
  • EvtDriverDeviceAdd
  • EvtDevicePrepareHardware/EvtDeviceReleaseHardware
  • EvtDeviceD0Entry/EvtDeviceD0Exit

验证

准备好测试 rhproxy 后,使用以下分步过程会很有帮助。

  1. 验证 SpbCxGpioClxSerCx 控制器驱动程序是否已正确加载和运行
  2. 验证系统上是否存在 rhproxy。 某些版本的 Windows 没有它。
  3. 使用 ACPITABL.dat 编译并加载 rhproxy 节点
  4. 验证 rhproxy 设备节点是否存在
  5. 验证 rhproxy 是否正在加载和启动
  6. 验证是否向用户模式公开了预期设备
  7. 验证是否可以从命令行与每个设备交互
  8. 验证是否可以从 UWP 应用与每个设备交互
  9. 运行 HLK 测试

验证控制器驱动程序

由于 rhproxy 将系统上的其他设备向用户模式公开,因此它仅在这些设备运行时工作。 第一步是验证这些设备(要公开的 I2C、SPI、GPIO 控制器)是否已正常工作。

在命令提示符处,运行

devcon status *

查看输出并验证是否启动了所有感兴趣的设备。 如果设备有问题代码,则需要排查设备未加载的原因。 所有设备都应在初始平台启动期间启用。 排查 SpbCxGpioClxSerCx 控制器驱动程序的问题超出了本文档的范围。

验证系统上是否存在 rhproxy

验证系统上是否存在 rhproxy 服务。

reg query HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\rhproxy

如果 reg 键不存在,则系统上不存在 rhproxy。 Rhproxy 存在于 IoT Core 和 Windows Enterprise 内部版本 15063 及更高版本的所有版本中。

使用 ACPITABL.dat 编译和加载 ASL

现在你已编写了 rhproxy ASL 节点,可以编译并加载它了。 可以将 rhproxy 节点编译为可追加到系统 ACPI 表的独立 AML 文件。 或者,如果有权访问系统的 ACPI 源,可以直接将 rhproxy 节点插入平台的 ACPI 表。 但是,在初始启动期间,使用 ACPITABL.dat 可能更容易。

  1. 创建名为 yourboard.asl 的文件,并将 RHPX 设备节点置于 DefinitionBlock 中:

    DefinitionBlock ("ACPITABL.dat", "SSDT", 1, "MSFT", "RHPROXY", 1)
    {
        Scope (\_SB)
        {
            Device(RHPX)
            {
            ...
            }
        }
    }
    
  2. C:\Program Files (x86)\Windows Kits\10\Tools\x64\ACPIVerify 中下载 WDK 并找到 asl.exe

  3. 运行以下命令以生成 ACPITABL.dat:

    asl.exe yourboard.asl
    
  4. 将生成的 ACPITABL.dat 文件复制到受测系统上的 c:\windows\system32。

  5. 在受测试的系统上启用 testsigning :

    bcdedit /set testsigning on
    
  6. 重新启动受测试的系统。 系统将向系统固件表追加 ACPITABL.dat 中定义的 ACPI 表。

验证 rhproxy 设备节点是否存在

运行以下命令来枚举 rhproxy 设备节点。

devcon status *msft8000

devcon 的输出应指示设备存在。 如果设备节点不存在,则 ACPI 表未成功添加到系统。

验证 rhproxy 是否正在加载和启动

检查 rhproxy 的状态:

devcon status *msft8000

如果输出指示已启动 rhproxy,则 rhproxy 已加载并成功启动。 如果看到问题代码,则需要进行调查。 一些常见问题代码包括:

  • 问题 51 - CM_PROB_WAITING_ON_DEPENDENCY - 系统未启动 rhproxy,因为其中一个依赖项无法加载。 这意味着传递给 rhproxy 的资源指向无效的 ACPI 节点或目标设备未启动。 首先,双重检查所有设备都成功运行(请参阅上面的“验证控制器驱动程序”)。 然后,仔细检查 ASL,确保所有资源路径(例如,\_SB.I2C1)正确无误并指向 DSDT 中的有效节点。
  • 问题 10 - CM_PROB_FAILED_START - Rhproxy 无法启动,很可能是因为资源分析问题。 遍历 ASL 并仔细检查 DSD 中的资源索引,并验证是否在增加引脚编号顺序中指定了 GPIO 资源。

验证是否向用户模式公开了预期设备

现在 rhproxy 已经运行,它应该已经创建了可由用户模式访问的设备接口。 我们将使用多个命令行工具来枚举设备并查看它们是否存在。

克隆 https://github.com/ms-iot/samples 存储库并生成 GpioTestToolI2cTestToolSpiTestToolMincomm 示例。 将工具复制到受测设备,并使用以下命令枚举设备。

I2cTestTool.exe -list
SpiTestTool.exe -list
GpioTestTool.exe -list
MinComm.exe -list

应会看到你的设备和友好名称列出。 如果未看到正确的设备和友好名称,请检查 ASL。

验证命令行上的每个设备

下一步是使用命令行工具打开设备并与之交互。

I2CTestTool 示例:

I2cTestTool.exe 0x55 I2C1
> write {1 2 3}
> read 3
> writeread {1 2 3} 3

SpiTestTool 示例:

SpiTestTool.exe -n SPI1
> write {1 2 3}
> read 3

GpioTestTool 示例:

GpioTestTool.exe 12
> setdrivemode output
> write 0
> write 1
> setdrivemode input
> read
> interrupt on
> interrupt off

MinComm(串行)示例。 在运行之前,连接 Rx 到 Tx:

MinComm "\\?\ACPI#FSCL0007#3#{86e0d1e0-8089-11d0-9ce4-08003e301f73}\0000000000000006"
(type characters and see them echoed back)

验证 UWP 应用中的每个设备

使用以下示例验证设备是否适用于 UWP。

运行 HLK 测试

下载 Hardware Lab Kit (HLK)。 以下测试可用:

在 HLK 管理器中选择 rhproxy 设备节点时,将自动选择适用的测试。

在 HLK 管理器中,选择“资源中心代理设备”:

Screenshot of the Windows Hardware Lab Kit showing the Selection tab with the Resource Hub proxy device option selected.

然后单击“测试”选项卡,然后选择“I2C WinRT”、“Gpio WinRT”和“Spi WinRT”测试。

Screenshot of the Windows Hardware Lab Kit showing the Tests tab with the G P I O Win R T Functional and Stress Tests option selected.

单击“运行所选项”。 可通过右键单击测试并单击“测试说明”来获取有关每个测试的更多文档。

资源

附录

附录 A - Raspberry Pi ASL 列表

另请参阅 Raspberry Pi 2 和 3 引脚映射

DefinitionBlock ("ACPITABL.dat", "SSDT", 1, "MSFT", "RHPROXY", 1)
{

    Scope (\_SB)
    {
        //
        // RHProxy Device Node to enable WinRT API
        //
        Device(RHPX)
        {
            Name(_HID, "MSFT8000")
            Name(_CID, "MSFT8000")
            Name(_UID, 1)

            Name(_CRS, ResourceTemplate()
            {
                // Index 0
                SPISerialBus(              // SCKL - GPIO 11 - Pin 23
                                           // MOSI - GPIO 10 - Pin 19
                                           // MISO - GPIO 9  - Pin 21
                                           // CE0  - GPIO 8  - Pin 24
                    0,                     // Device selection (CE0)
                    PolarityLow,           // Device selection polarity
                    FourWireMode,          // wiremode
                    0,                     // databit len: placeholder
                    ControllerInitiated,   // slave mode
                    0,                     // connection speed: placeholder
                    ClockPolarityLow,      // clock polarity: placeholder
                    ClockPhaseFirst,       // clock phase: placeholder
                    "\\_SB.SPI0",          // ResourceSource: SPI bus controller name
                    0,                     // ResourceSourceIndex
                                           // Resource usage
                    )                      // Vendor Data

                // Index 1
                SPISerialBus(              // SCKL - GPIO 11 - Pin 23
                                           // MOSI - GPIO 10 - Pin 19
                                           // MISO - GPIO 9  - Pin 21
                                           // CE1  - GPIO 7  - Pin 26
                    1,                     // Device selection (CE1)
                    PolarityLow,           // Device selection polarity
                    FourWireMode,          // wiremode
                    0,                     // databit len: placeholder
                    ControllerInitiated,   // slave mode
                    0,                     // connection speed: placeholder
                    ClockPolarityLow,      // clock polarity: placeholder
                    ClockPhaseFirst,       // clock phase: placeholder
                    "\\_SB.SPI0",          // ResourceSource: SPI bus controller name
                    0,                     // ResourceSourceIndex
                                           // Resource usage
                    )                      // Vendor Data

                // Index 2
                SPISerialBus(              // SCKL - GPIO 21 - Pin 40
                                           // MOSI - GPIO 20 - Pin 38
                                           // MISO - GPIO 19 - Pin 35
                                           // CE1  - GPIO 17 - Pin 11
                    1,                     // Device selection (CE1)
                    PolarityLow,           // Device selection polarity
                    FourWireMode,          // wiremode
                    0,                     // databit len: placeholder
                    ControllerInitiated,   // slave mode
                    0,                     // connection speed: placeholder
                    ClockPolarityLow,      // clock polarity: placeholder
                    ClockPhaseFirst,       // clock phase: placeholder
                    "\\_SB.SPI1",          // ResourceSource: SPI bus controller name
                    0,                     // ResourceSourceIndex
                                           // Resource usage
                    )                      // Vendor Data
                // Index 3
                I2CSerialBus(              // Pin 3 (GPIO2, SDA1), 5 (GPIO3, SCL1)
                    0xFFFF,                // SlaveAddress: placeholder
                    ,                      // SlaveMode: default to ControllerInitiated
                    0,                     // ConnectionSpeed: placeholder
                    ,                      // Addressing Mode: placeholder
                    "\\_SB.I2C1",          // ResourceSource: I2C bus controller name
                    ,
                    ,
                    )                      // VendorData

                // Index 4 - GPIO 4 -
                GpioIO(Shared, PullUp, , , , "\\_SB.GPI0", , , , ) { 4 }
                GpioInt(Edge, ActiveBoth, Shared, PullUp, 0, "\\_SB.GPI0",) { 4 }
                // Index 6 - GPIO 5 -
                GpioIO(Shared, PullUp, , , , "\\_SB.GPI0", , , , ) { 5 }
                GpioInt(Edge, ActiveBoth, Shared, PullUp, 0, "\\_SB.GPI0",) { 5 }
                // Index 8 - GPIO 6 -
                GpioIO(Shared, PullUp, , , , "\\_SB.GPI0", , , , ) { 6 }
                GpioInt(Edge, ActiveBoth, Shared, PullUp, 0, "\\_SB.GPI0",) { 6 }
                // Index 10 - GPIO 12 -
                GpioIO(Shared, PullDown, , , , "\\_SB.GPI0", , , , ) { 12 }
                GpioInt(Edge, ActiveBoth, Shared, PullDown, 0, "\\_SB.GPI0",) { 12 }
                // Index 12 - GPIO 13 -
                GpioIO(Shared, PullDown, , , , "\\_SB.GPI0", , , , ) { 13 }
                GpioInt(Edge, ActiveBoth, Shared, PullDown, 0, "\\_SB.GPI0",) { 13 }
                // Index 14 - GPIO 16 -
                GpioIO(Shared, PullDown, , , , "\\_SB.GPI0", , , , ) { 16 }
                GpioInt(Edge, ActiveBoth, Shared, PullDown, 0, "\\_SB.GPI0",) { 16 }
                // Index 16 - GPIO 18 -
                GpioIO(Shared, PullDown, , , , "\\_SB.GPI0", , , , ) { 18 }
                GpioInt(Edge, ActiveBoth, Shared, PullDown, 0, "\\_SB.GPI0",) { 18 }
                // Index 18 - GPIO 22 -
                GpioIO(Shared, PullDown, , , , "\\_SB.GPI0", , , , ) { 22 }
                GpioInt(Edge, ActiveBoth, Shared, PullDown, 0, "\\_SB.GPI0",) { 22 }
                // Index 20 - GPIO 23 -
                GpioIO(Shared, PullDown, , , , "\\_SB.GPI0", , , , ) { 23 }
                GpioInt(Edge, ActiveBoth, Shared, PullDown, 0, "\\_SB.GPI0",) { 23 }
                // Index 22 - GPIO 24 -
                GpioIO(Shared, PullDown, , , , "\\_SB.GPI0", , , , ) { 24 }
                GpioInt(Edge, ActiveBoth, Shared, PullDown, 0, "\\_SB.GPI0",) { 24 }
                // Index 24 - GPIO 25 -
                GpioIO(Shared, PullDown, , , , "\\_SB.GPI0", , , , ) { 25 }
                GpioInt(Edge, ActiveBoth, Shared, PullDown, 0, "\\_SB.GPI0",) { 25 }
                // Index 26 - GPIO 26 -
                GpioIO(Shared, PullDown, , , , "\\_SB.GPI0", , , , ) { 26 }
                GpioInt(Edge, ActiveBoth, Shared, PullDown, 0, "\\_SB.GPI0",) { 26 }
                // Index 28 - GPIO 27 -
                GpioIO(Shared, PullDown, , , , "\\_SB.GPI0", , , , ) { 27 }
                GpioInt(Edge, ActiveBoth, Shared, PullDown, 0, "\\_SB.GPI0",) { 27 }
                // Index 30 - GPIO 35 -
                GpioIO(Shared, PullUp, , , , "\\_SB.GPI0", , , , ) { 35 }
                GpioInt(Edge, ActiveBoth, Shared, PullUp, 0, "\\_SB.GPI0",) { 35 }
                // Index 32 - GPIO 47 -
                GpioIO(Shared, PullUp, , , , "\\_SB.GPI0", , , , ) { 47 }
                GpioInt(Edge, ActiveBoth, Shared, PullUp, 0, "\\_SB.GPI0",) { 47 }
            })

            Name(_DSD, Package()
            {
                ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
                Package()
                {
                    // Reference http://www.raspberrypi.org/documentation/hardware/raspberrypi/spi/README.md
                    // SPI 0
                    Package(2) { "bus-SPI-SPI0", Package() { 0, 1 }},                       // Index 0 & 1
                    Package(2) { "SPI0-MinClockInHz", 7629 },                               // 7629 Hz
                    Package(2) { "SPI0-MaxClockInHz", 125000000 },                          // 125 MHz
                    Package(2) { "SPI0-SupportedDataBitLengths", Package() { 8 }},          // Data Bit Length
                    // SPI 1
                    Package(2) { "bus-SPI-SPI1", Package() { 2 }},                          // Index 2
                    Package(2) { "SPI1-MinClockInHz", 30518 },                              // 30518 Hz
                    Package(2) { "SPI1-MaxClockInHz", 125000000 },                          // 125 MHz
                    Package(2) { "SPI1-SupportedDataBitLengths", Package() { 8 }},          // Data Bit Length
                    // I2C1
                    Package(2) { "bus-I2C-I2C1", Package() { 3 }},
                    // GPIO Pin Count and supported drive modes
                    Package (2) { "GPIO-PinCount", 54 },
                    Package (2) { "GPIO-UseDescriptorPinNumbers", 1 },
                    Package (2) { "GPIO-SupportedDriveModes", 0xf },                        // InputHighImpedance, InputPullUp, InputPullDown, OutputCmos
                }
            })
        }
    }
}

附录 B - MinnowBoardMax ASL 一览

另请参阅 MinnowBoard Max 引脚映射

DefinitionBlock ("ACPITABL.dat", "SSDT", 1, "MSFT", "RHPROXY", 1)
{
    Scope (\_SB)
    {
        Device(RHPX)
        {
            Name(_HID, "MSFT8000")
            Name(_CID, "MSFT8000")
            Name(_UID, 1)

            Name(_CRS, ResourceTemplate()
            {
                // Index 0
                SPISerialBus(            // Pin 5, 7, 9 , 11 of JP1 for SIO_SPI
                    1,                     // Device selection
                    PolarityLow,           // Device selection polarity
                    FourWireMode,          // wiremode
                    8,                     // databit len
                    ControllerInitiated,   // slave mode
                    8000000,               // Connection speed
                    ClockPolarityLow,      // Clock polarity
                    ClockPhaseSecond,      // clock phase
                    "\\_SB.SPI1",          // ResourceSource: SPI bus controller name
                    0,                     // ResourceSourceIndex
                    ResourceConsumer,      // Resource usage
                    JSPI,                  // DescriptorName: creates name for offset of resource descriptor
                    )                      // Vendor Data

                // Index 1
                I2CSerialBus(            // Pin 13, 15 of JP1, for SIO_I2C5 (signal)
                    0xFF,                  // SlaveAddress: bus address
                    ,                      // SlaveMode: default to ControllerInitiated
                    400000,                // ConnectionSpeed: in Hz
                    ,                      // Addressing Mode: default to 7 bit
                    "\\_SB.I2C6",          // ResourceSource: I2C bus controller name (For MinnowBoard Max, hardware I2C5(0-based) is reported as ACPI I2C6(1-based))
                    ,
                    ,
                    JI2C,                  // Descriptor Name: creates name for offset of resource descriptor
                    )                      // VendorData

                // Index 2
                UARTSerialBus(           // Pin 17, 19 of JP1, for SIO_UART2
                    115200,                // InitialBaudRate: in bits ber second
                    ,                      // BitsPerByte: default to 8 bits
                    ,                      // StopBits: Defaults to one bit
                    0xfc,                  // LinesInUse: 8 1-bit flags to declare line enabled
                    ,                      // IsBigEndian: default to LittleEndian
                    ,                      // Parity: Defaults to no parity
                    ,                      // FlowControl: Defaults to no flow control
                    32,                    // ReceiveBufferSize
                    32,                    // TransmitBufferSize
                    "\\_SB.URT2",          // ResourceSource: UART bus controller name
                    ,
                    ,
                    UAR2,                  // DescriptorName: creates name for offset of resource descriptor
                    )

                // Index 3
                GpioIo (Shared, PullNone, 0, 0, IoRestrictionNone, "\\_SB.GPO2",) {0}  // Pin 21 of JP1 (GPIO_S5[00])
                // Index 4
                GpioInt(Edge, ActiveBoth, SharedAndWake, PullNone, 0,"\\_SB.GPO2",) {0}

                // Index 5
                GpioIo (Shared, PullNone, 0, 0, IoRestrictionNone, "\\_SB.GPO2",) {1}  // Pin 23 of JP1 (GPIO_S5[01])
                // Index 6
                GpioInt(Edge, ActiveBoth, SharedAndWake, PullNone, 0,"\\_SB.GPO2",) {1}

                // Index 7
                GpioIo (Shared, PullNone, 0, 0, IoRestrictionNone, "\\_SB.GPO2",) {2}  // Pin 25 of JP1 (GPIO_S5[02])
                // Index 8
                GpioInt(Edge, ActiveBoth, SharedAndWake, PullNone, 0,"\\_SB.GPO2",) {2}

                // Index 9
                UARTSerialBus(           // Pin 6, 8, 10, 12 of JP1, for SIO_UART1
                    115200,                // InitialBaudRate: in bits ber second
                    ,                      // BitsPerByte: default to 8 bits
                    ,                      // StopBits: Defaults to one bit
                    0xfc,                  // LinesInUse: 8 1-bit flags to declare line enabled
                    ,                      // IsBigEndian: default to LittleEndian
                    ,                      // Parity: Defaults to no parity
                    FlowControlHardware,   // FlowControl: Defaults to no flow control
                    32,                    // ReceiveBufferSize
                    32,                    // TransmitBufferSize
                    "\\_SB.URT1",          // ResourceSource: UART bus controller name
                    ,
                    ,
                    UAR1,              // DescriptorName: creates name for offset of resource descriptor
                    )

                // Index 10
                GpioIo (Shared, PullNone, 0, 0, IoRestrictionNone, "\\_SB.GPO0",) {62}  // Pin 14 of JP1 (GPIO_SC[62])
                // Index 11
                GpioInt(Edge, ActiveBoth, SharedAndWake, PullNone, 0,"\\_SB.GPO0",) {62}

                // Index 12
                GpioIo (Shared, PullNone, 0, 0, IoRestrictionNone, "\\_SB.GPO0",) {63}  // Pin 16 of JP1 (GPIO_SC[63])
                // Index 13
                GpioInt(Edge, ActiveBoth, SharedAndWake, PullNone, 0,"\\_SB.GPO0",) {63}

                // Index 14
                GpioIo (Shared, PullNone, 0, 0, IoRestrictionNone, "\\_SB.GPO0",) {65}  // Pin 18 of JP1 (GPIO_SC[65])
                // Index 15
                GpioInt(Edge, ActiveBoth, SharedAndWake, PullNone, 0,"\\_SB.GPO0",) {65}

                // Index 16
                GpioIo (Shared, PullNone, 0, 0, IoRestrictionNone, "\\_SB.GPO0",) {64}  // Pin 20 of JP1 (GPIO_SC[64])
                // Index 17
                GpioInt(Edge, ActiveBoth, SharedAndWake, PullNone, 0,"\\_SB.GPO0",) {64}

                // Index 18
                GpioIo (Shared, PullNone, 0, 0, IoRestrictionNone, "\\_SB.GPO0",) {94}  // Pin 22 of JP1 (GPIO_SC[94])
                // Index 19
                GpioInt(Edge, ActiveBoth, SharedAndWake, PullNone, 0,"\\_SB.GPO0",) {94}

                // Index 20
                GpioIo (Shared, PullNone, 0, 0, IoRestrictionNone, "\\_SB.GPO0",) {95}  // Pin 24 of JP1 (GPIO_SC[95])
                // Index 21
                GpioInt(Edge, ActiveBoth, SharedAndWake, PullNone, 0,"\\_SB.GPO0",) {95}

                // Index 22
                GpioIo (Shared, PullNone, 0, 0, IoRestrictionNone, "\\_SB.GPO0",) {54}  // Pin 26 of JP1 (GPIO_SC[54])
                // Index 23
                GpioInt(Edge, ActiveBoth, SharedAndWake, PullNone, 0,"\\_SB.GPO0",) {54}
            })

            Name(_DSD, Package()
            {
                ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
                Package()
                {
                    // SPI Mapping
                    Package(2) { "bus-SPI-SPI0", Package() { 0 }},

                    Package(2) { "SPI0-MinClockInHz", 100000 },
                    Package(2) { "SPI0-MaxClockInHz", 15000000 },
                    // SupportedDataBitLengths takes a list of support data bit length
                    // Example : Package(2) { "SPI0-SupportedDataBitLengths", Package() { 8, 7, 16 }},
                    Package(2) { "SPI0-SupportedDataBitLengths", Package() { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 }},
                     // I2C Mapping
                    Package(2) { "bus-I2C-I2C5", Package() { 1 }},
                    // UART Mapping
                    Package(2) { "bus-UART-UART2", Package() { 2 }},
                    Package(2) { "bus-UART-UART1", Package() { 9 }},
                }
            })
        }
    }
}

附录 C - 用于生成 GPIO 资源的示例 Powershell 脚本

以下脚本可用于为 Raspberry Pi 生成 GPIO 资源声明:

$pins = @(
    @{PinNumber=4;PullConfig='PullUp'},
    @{PinNumber=5;PullConfig='PullUp'},
    @{PinNumber=6;PullConfig='PullUp'},
    @{PinNumber=12;PullConfig='PullDown'},
    @{PinNumber=13;PullConfig='PullDown'},
    @{PinNumber=16;PullConfig='PullDown'},
    @{PinNumber=18;PullConfig='PullDown'},
    @{PinNumber=22;PullConfig='PullDown'},
    @{PinNumber=23;PullConfig='PullDown'},
    @{PinNumber=24;PullConfig='PullDown'},
    @{PinNumber=25;PullConfig='PullDown'},
    @{PinNumber=26;PullConfig='PullDown'},
    @{PinNumber=27;PullConfig='PullDown'},
    @{PinNumber=35;PullConfig='PullUp'},
    @{PinNumber=47;PullConfig='PullUp'})

# generate the resources
$FIRST_RESOURCE_INDEX = 4
$resourceIndex = $FIRST_RESOURCE_INDEX
$pins | % {
    $a = @"
// Index $resourceIndex - GPIO $($_.PinNumber) - $($_.Name)
GpioIO(Shared, $($_.PullConfig), , , , "\\_SB.GPI0", , , , ) { $($_.PinNumber) }
GpioInt(Edge, ActiveBoth, Shared, $($_.PullConfig), 0, "\\_SB.GPI0",) { $($_.PinNumber) }
"@
    Write-Host $a
    $resourceIndex += 2;
}