Activer l’accès en mode utilisateur à GPIO, I2C et SPI

Dans Windows 10 et les versions ultérieures, les API sont fournies avec un accès direct en mode utilisateur à l’entrée/sortie à usage général (GPIO), au circuit inter-intégré (I2C), à l’interface périphérique de série (SPI) et à l’émetteur de récepteur asynchrone universel (UART). Les cartes de développement telles que Raspberry Pi 2 exposent un sous-ensemble de ces connexions, ce qui vous permet d’étendre un module de calcul de base avec des circuits personnalisés pour traiter une application particulière. Ces bus de bas niveau sont généralement partagés avec d’autres fonctions d’intégration critiques, avec seulement un sous-ensemble de broches GPIO et de bus exposés sur les en-têtes. Pour préserver la stabilité du système, il est nécessaire de spécifier les broches et les bus qui peuvent être modifiés en toute sécurité par les applications en mode utilisateur.

Ce document explique comment spécifier cette configuration dans Advanced Configuration and Power Interface (ACPI) et fournit des outils pour confirmer que la configuration a été correctement spécifiée.

Important

Ce document s’adresse aux développeurs Unified Extensible Firmware Interface (UEFI) et ACPI. Certaines connaissances sur l’ACPI, sur la création de la langue source de l’ACPI (ASL) et sur SpbCx/GpioClx sont supposées.

L’accès en mode utilisateur aux bus de bas niveau sur Windows est ajouté par les infrastructures GpioClx et SpbCx existantes. Un nouveau pilote appelé RhProxy, disponible sur Windows IoT Core et Windows Enterprise, expose les ressources GpioClx et SpbCx au mode utilisateur. Pour activer les API, un nœud d’appareil pour rhproxy doit être déclaré dans vos tables ACPI avec chacune des ressources GPIO et SPB qui doivent être exposées au mode utilisateur. Ce document décrit la création et la vérification de l’ASL.

ASL par exemple

Examinons la déclaration de nœud d’appareil rhproxy sur Raspberry Pi 2. Tout d’abord, créez la déclaration d’appareil ACPI dans l’étendue \_SB.

Device(RHPX)
{
    Name(_HID, "MSFT8000")
    Name(_CID, "MSFT8000")
    Name(_UID, 1)
}
  • _HID : ID matériel. Définissez-le sur un ID matériel spécifique au fournisseur.
  • _CID : ID compatible. Doit être « MSFT8000 ».
  • _UID : ID unique. Définissez-le sur 1.

Ensuite, nous déclarons chacune des ressources GPIO et SPB qui doivent être exposées au mode utilisateur. L’ordre dans lequel les ressources sont déclarées est important, car les index de ressources sont utilisés pour associer des propriétés aux ressources. S’il existe plusieurs bus I2C ou SPI exposés, le premier bus déclaré est considéré comme le bus « par défaut » pour ce type et sera l’instance retournée par les méthodes GetDefaultAsync() de Windows.Devices.I2c.I2cController et Windows.Devices.Spi.SpiController.

SPI

Raspberry Pi a deux bus SPI exposés. SPI0 a deux lignes de sélection de puce matérielle et SPI1 a une ligne de sélection de puce matérielle. Une déclaration de ressource SPISerialBus() est requise pour chaque ligne de sélection de puce pour chaque bus. Les deux déclarations de ressources SPISerialBus suivantes concernent les deux lignes de sélection de puce sur SPI0. Le champ DeviceSelection contient une valeur unique que le pilote interprète comme identificateur de ligne de sélection de puce matériel. La valeur exacte que vous avez placée dans le champ DeviceSelection dépend de la façon dont votre pilote interprète ce champ du descripteur de connexion ACPI.

Remarque

Cet article contient des références au terme esclave, un terme que Microsoft ne tolère pas, et dont l'utilisation a cessé dans les nouveaux produits et leur documentation. Lorsque le terme sera supprimé du logiciel, nous le supprimerons de cet article.

// 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

Comment les logiciels savent-ils que ces deux ressources doivent être associées au même bus ? Le mappage entre le nom convivial du bus et l’index de ressource est spécifié dans le DSD :

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

Cela crée un bus nommé « SPI0 » avec deux lignes de sélection de puce : les index de ressources 0 et 1. Plusieurs propriétés supplémentaires sont requises pour déclarer les fonctionnalités du bus SPI.

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

Les propriétés MinClockInHz et MaxClockInHz spécifient les vitesses d’horloge minimale et maximale prises en charge par le contrôleur. L’API empêche les utilisateurs de spécifier des valeurs en dehors de cette plage. La vitesse de l’horloge est passée à votre pilote SPB dans le champ _SPE du descripteur de connexion (section ACPI 6.4.3.8.2.2).

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

La propriété SupportedDataBitLengths répertorie les longueurs de bits de données prises en charge par le contrôleur. Plusieurs valeurs peuvent être spécifiées dans une liste séparée par des virgules. L’API empêche les utilisateurs de spécifier des valeurs en dehors de cette liste. La longueur du bit de données est transmise à votre pilote SPB dans le champ _LEN du descripteur de connexion (section ACPI 6.4.3.8.2.2).

Vous pouvez considérer ces déclarations de ressources comme des « modèles ». Certains des champs sont corrigés au démarrage du système, tandis que d’autres sont spécifiés dynamiquement au moment de l’exécution. Les champs suivants du descripteur SPISerialBus sont corrigés :

  • DeviceSelection
  • DeviceSelectionPolarity
  • WireMode
  • SlaveMode
  • ResourceSource

Les champs suivants sont des espaces réservés pour les valeurs spécifiées par l’utilisateur au moment de l’exécution :

  • DataBitLength
  • ConnectionSpeed
  • ClockPolarity
  • ClockPhase

Étant donné que SPI1 ne contient qu’une seule ligne de sélection de puce, une seule ressource SPISerialBus() est déclarée :

// 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

La déclaration de nom convivial associée, qui est requise, est spécifiée dans le DSD et fait référence à l’index de cette déclaration de ressource.

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

Cela crée un bus nommé « SPI1 » et l’associe à l’index de ressource 2.

Configuration requise pour le pilote SPI

  • Doit utiliser SpbCx ou être compatible avec SpbCx
  • Doit avoir réussi les tests SPI MITT
  • Doit prendre en charge la vitesse d’horloge de 4 Mhz
  • Doit prendre en charge la longueur des données 8 bits
  • Doit prendre en charge tous les modes SPI : 0, 1, 2, 3

I2C

Ensuite, nous déclarons les ressources I2C. Raspberry Pi expose un seul bus I2C sur les broches 3 et 5.

// 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

La déclaration de nom convivial associée, qui est requise, est spécifiée dans le DSD :

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

Cela déclare un bus I2C portant le nom convivial « I2C1 » qui fait référence à l’index de ressource 3, qui est l’index de la ressource I2CSerialBus() que nous avons déclarée ci-dessus.

Les champs suivants du descripteur I2CSerialBus() sont fixes :

  • SlaveMode
  • ResourceSource

Les champs suivants sont des espaces réservés pour les valeurs spécifiées par l’utilisateur au moment de l’exécution.

  • SlaveAddress
  • ConnectionSpeed
  • AddressingMode

Configuration requise du pilote I2C

  • Doit utiliser SpbCx ou être compatible avec SpbCx
  • Doit avoir réussi les tests I2C MITT
  • Doit prendre en charge l’adressage 7 bits
  • Doit prendre en charge la vitesse d’horloge de 100 kHz
  • Doit prendre en charge la vitesse d’horloge de 400 kHz

GPIO

Ensuite, nous déclarons toutes les broches GPIO exposées au mode utilisateur. Nous vous proposons les conseils suivants pour déterminer les broches à exposer :

  • Déclarez toutes les broches sur les en-têtes exposés.
  • Déclarez des broches connectées à des fonctions d’intégration utiles telles que des boutons et des LED.
  • Ne déclarez pas les broches réservées aux fonctions système ou qui ne sont pas connectées à quoi que ce soit.

Le bloc suivant d’ASL déclare deux broches : GPIO4 et GPIO5. Les autres broches ne sont pas affichées ici par souci de concision. L’annexe C contient un exemple de script PowerShell qui peut être utilisé pour générer les ressources GPIO.

// 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 }

Les exigences suivantes doivent être observées lors de la déclaration des broches GPIO :

  • Seuls les contrôleurs GPIO mappés en mémoire sont pris en charge. Les contrôleurs GPIO interfacés sur I2C/SPI ne sont pas pris en charge. Le pilote du contrôleur est un contrôleur mappé en mémoire s’il définit l’indicateur MemoryMappedController dans la structure CLIENT_CONTROLLER_BASIC_INFORMATION en réponse au rappel CLIENT_QueryControllerBasicInformation.
  • Chaque broche nécessite à la fois une ressource GpioIO et GpioInt. La ressource GpioInt doit immédiatement suivre la ressource GpioIO et doit faire référence au même numéro de broche.
  • Les ressources GPIO doivent être classées par numéro de broche croissant.
  • Chaque ressource GpioIO et GpioInt doit contenir exactement un numéro de broche dans la liste des broches.
  • Le champ ShareType des deux descripteurs doit être Shared
  • Le champ EdgeLevel du descripteur GpioInt doit être Edge
  • Le champ ActiveLevel du descripteur GpioInt doit être ActiveBoth
  • Le champ PinConfig
    • Doit être identique dans les descripteurs GpioIO et GpioInt
    • Doit être PullUp, PullDown ou PullNone. Il ne peut pas être PullDefault.
    • La configuration pull doit correspondre à l’état de mise sous tension de la broche. Le fait de placer la broche dans le mode pull spécifié à partir de l’état de mise sous tension ne doit pas modifier l’état de la broche. Par exemple, si la feuille de données spécifie que la broche est fournie avec une résistance pull-up, spécifiez PinConfig en tant que PullUp.

Le code d’initialisation du microprogramme, UEFI et pilote ne doit pas changer l’état d’une broche à partir de son état de mise en tension pendant le démarrage. Seul l’utilisateur sait ce qui est attaché à une broche et, par conséquent, quelles transitions d’état sont sécurisées. L’état de mise sous tension de chaque broche doit être documenté afin que les utilisateurs puissent concevoir du matériel qui s’interface correctement avec une broche. Une broche ne doit pas changer d’état de façon inattendue pendant le démarrage.

Modes lecteur pris en charge

Si votre contrôleur GPIO prend en charge les résistances intégrées pull-up et pull-down en plus de l’entrée d’impédance élevée et de la sortie CMOS, vous devez spécifier cela avec la propriété SupportedDriveModes facultative.

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

La propriété SupportedDriveModes indique quels modes lecteur sont pris en charge par le contrôleur GPIO. Dans l’exemple ci-dessus, tous les modes lecteur suivants sont pris en charge. La propriété est un masque de bits des valeurs suivantes :

Valeur de l’indicateur Mode lecteur Description
0x1 InputHighImpedance La broche prend en charge l’entrée d’impédance élevée, qui correspond à la valeur « PullNone » dans ACPI.
0x2 InputPullUp La broche prend en charge une résistance pull-up intégrée, qui correspond à la valeur « PullUp » dans ACPI.
0x4 InputPullDown La broche prend en charge une résistance pull-down intégrée, qui correspond à la valeur « PullDown » dans ACPI.
0x8 OutputCmos La broche prend en charge la génération de hauts forts et de faibles forts (par opposition au drain ouvert).

InputHighImpedance et OutputCmos sont pris en charge par presque tous les contrôleurs GPIO. Si la propriété SupportedDriveModes n’est pas spécifiée, il s’agit de la valeur par défaut.

Si un signal GPIO passe par un shifter de niveau avant d’atteindre un en-tête exposé, déclarez les modes lecteur pris en charge par le SOC, même si le mode lecteur ne serait pas observable sur l’en-tête externe. Par exemple, si une broche passe par un shifter de niveau bidirectionnel qui fait apparaître une broche comme un drain ouvert avec un pull-up résistif, vous n’observerez jamais un état d’impédance élevé sur l’en-tête exposé même si la broche est configurée comme une entrée d’impédance élevée. Vous devez toujours déclarer que la broche prend en charge l’entrée d’impédance élevée.

Numérotation des broches

Windows prend en charge deux schémas de numérotation de broche :

  • Numérotation de broche séquentielle : les utilisateurs voient des nombres tels que 0, 1, 2... jusqu’au nombre de broches exposées. 0 est la première ressource GpioIo déclarée dans ASL, 1 est la deuxième ressource GpioIo déclarée dans ASL, et ainsi de suite.
  • Numérotation de broche native : les utilisateurs voient les numéros de broche spécifiés dans les descripteurs GpioIo, par exemple 4, 5, 12, 13...
Package (2) { “GPIO-UseDescriptorPinNumbers”, 1 },

La propriété UseDescriptorPinNumbers indique à Windows d’utiliser la numérotation de broche native au lieu de la numérotation de broche séquentielle. Si la propriété UseDescriptorPinNumbers n’est pas spécifiée ou que sa valeur est égale à zéro, Windows utilise par défaut la numérotation de broche séquentielle.

Si la numérotation de broche native est utilisée, vous devez également spécifier la propriété PinCount.

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

La propriété PinCount doit correspondre à la valeur retournée par le biais de la propriété TotalPins dans le rappel CLIENT_QueryControllerBasicInformation du pilote GpioClx.

Choisissez le schéma de numérotation le plus compatible avec la documentation publiée existante pour votre carte. Par exemple, Raspberry Pi utilise la numérotation de broche native, car de nombreux diagrammes de broche existants utilisent les numéros de broche BCM2835. MinnowBoardMax utilise la numérotation séquentielle des broches, car il existe peu de diagrammes de broche existants, et la numérotation de broche séquentielle simplifie l’expérience du développeur, car seules 10 broches sont exposées sur plus de 200 broches. La décision d’utiliser la numérotation de broche séquentielle ou native doit viser à réduire la confusion des développeurs.

Configuration requise du pilote GPIO

  • Doit utiliser GpioClx
  • Doit être mappé sur la mémoire SOC
  • Doit utiliser la gestion des interruptions ActiveBoth émulée

UART

Si votre pilote UART utilise SerCx ou SerCx2, vous pouvez utiliser rhproxy pour exposer le pilote au mode utilisateur. Les pilotes UART qui créent une interface d’appareil de type GUID_DEVINTERFACE_COMPORT n’ont pas besoin d’utiliser rhproxy. Le pilote Serial.sys de boîte de réception est l’un de ces cas.

Pour exposer un UART de style SerCx au mode utilisateur, déclarez une ressource UARTSerialBus comme suit.

// 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
    ,
    ,
    ,
    )

Seul le champ ResourceSource est corrigé alors que tous les autres champs sont des espaces réservés pour les valeurs spécifiées lors de l’exécution par l’utilisateur.

La déclaration de nom convivial associée est la suivante :

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

Cela affecte le nom convivial « UART2 » au contrôleur, qui est l’identificateur que les utilisateurs utiliseront pour accéder au bus à partir du mode utilisateur.

Runtime de multiplexage de broches

Le multiplexage de broches est la capacité d’utiliser la même broche physique pour différentes fonctions. Plusieurs périphériques sur puce différents, tels qu’un contrôleur I2C, un contrôleur SPI et un contrôleur GPIO, peuvent être routés vers la même broche physique sur un SOC. Le bloc de multiplexage contrôle la fonction active sur la broche à tout moment. Traditionnellement, le microprogramme est chargé d’établir des attributions de fonction au démarrage, et cette affectation reste statique pendant la session de démarrage. Le runtime de multiplexage de broches ajoute la possibilité de reconfigurer les attributions de fonctions de broche au moment de l’exécution. Le fait de permettre aux utilisateurs de choisir la fonction d’une broche au moment de l’exécution accélère le développement en permettant aux utilisateurs de reconfigurer rapidement les broches d’une carte, et permet au matériel de prendre en charge une gamme d’applications plus large que ne le ferait une configuration statique.

Les utilisateurs utilisent la prise en charge du multiplexage pour GPIO, I2C, SPI et UART sans écrire de code supplémentaire. Lorsqu’un utilisateur ouvre un GPIO ou un bus à l’aide de OpenPin() ou de FromIdAsync(), les broches physiques sous-jacentes sont automatiquement multiplexées à la fonction demandée. Si les broches sont déjà utilisées par une autre fonction, l’appel OpenPin() ou FromIdAsync() échoue. Lorsque l’utilisateur ferme l’appareil en supprimant l’objet GpioPin, I2cDevice, SpiDevice ou SerialDevice, les broches sont libérées, ce qui leur permet d’être ouvertes ultérieurement pour une autre fonction.

Windows contient la prise en charge intégrée du multiplexage de broches dans les infrastructures GpioClx, SpbCx et SerCx. Ces infrastructures fonctionnent ensemble pour basculer automatiquement une broche vers la fonction correcte lorsqu’une broche ou un bus GPIO est accessible. L’accès aux broches est arbitré pour éviter les conflits entre plusieurs clients. Outre cette prise en charge intégrée, les interfaces et les protocoles pour le multiplexage de broches sont à usage général et peuvent être étendus pour prendre en charge d’autres appareils et scénarios.

Ce document décrit d’abord les interfaces et protocoles sous-jacents impliqués dans le multiplexage de broches, puis explique comment ajouter la prise en charge du multiplexage de broches aux pilotes de contrôleur GpioClx, SpbCx et SerCx.

Architecture du multiplexage de broches

Cette section décrit les interfaces et protocoles sous-jacents impliqués dans le multiplexage de broches. La connaissance des protocoles sous-jacents n’est pas forcément nécessaire pour prendre en charge le multiplexage de broches avec les pilotes GpioClx/SpbCx/SerCx. Pour plus d’informations sur la prise en charge du multiplexage de broches avec les pilotes GpioCls/SpbCx/SerCx, consultez Implémentation de la prise en charge du multiplexage de broches dans les pilotes clients GpioClx et Consommation de la prise en charge du multiplexage de broches dans les pilotes de contrôleur SpbCx et SerCx.

Le multiplexage de broches est réalisé par la coopération de plusieurs composants.

  • Serveurs de multiplexage de broches : il s’agit de pilotes qui contrôlent le bloc de contrôle du multiplexage de broches. Les serveurs de multiplexage de broches reçoivent des demandes de multiplexage de broches provenant de clients via des requêtes pour réserver des ressources de multiplexage (via les requêtes IRP_MJ_CREATE) et des requêtes pour changer la fonction d’une broche (via les requêtes *IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS-). Le serveur de multiplexage de broches est généralement le pilote GPIO, car le bloc de multiplexage fait parfois partie du bloc GPIO. Même si le bloc de multiplexage est un périphérique distinct, le pilote GPIO est un emplacement logique où mettre la fonctionnalité de multiplexage.
  • Clients de multiplexage de broches : il s’agit de pilotes qui consomment le multiplexage de broches. Les clients de multiplexage de broches reçoivent des ressources de multiplexage de broches provenant du microprogramme ACPI. Les ressources de multiplexage de broches sont un type de ressource de connexion et sont gérées par le hub de ressources. Les clients de multiplexage de broches réservent des ressources de multiplexage de broches en ouvrant une poignée à la ressource. Pour effectuer une modification matérielle, les clients doivent valider la configuration en envoyant une requête IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS. Les clients libèrent les ressources de multiplexage de broches en fermant la poignée, ce qui ramène la configuration de multiplexage à son état par défaut.
  • Microprogramme ACPI : spécifie la configuration de multiplexage avec les ressources MsftFunctionConfig(). Les ressources MsftFunctionConfig indiquent les broches, dans quelle configuration de multiplexage, sont requises par un client. Les ressources MsftFunctionConfig contiennent le numéro de fonction, la configuration pull et la liste des numéros de broche. Les ressources MsftFunctionConfig sont fournies aux clients du multiplexage de broches en tant que ressources matérielles, qui sont reçues par les pilotes dans leur rappel PrepareHardware de la même façon que les ressources de connexion GPIO et SPB. Les clients reçoivent un ID de hub de ressources qui peut être utilisé pour ouvrir une poignée à la ressource.

Vous devez passer le commutateur de ligne de commande /MsftInternal sur asl.exe pour compiler des fichiers ASL contenant des descripteurs MsftFunctionConfig(), car ces descripteurs sont actuellement en cours d’examen par le comité de travail ACPI. Par exemple : asl.exe /MsftInternal dsdt.asl

La séquence d’opérations impliquées dans le multiplexage de broches est illustrée ci-dessous.

Pin muxing client server interaction

  1. Le client reçoit les ressources MsftFunctionConfig du microprogramme ACPI dans son rappel EvtDevicePrepareHardware().
  2. Le client utilise la fonction RESOURCE_HUB_CREATE_PATH_FROM_ID() d’assistance du hub de ressources pour créer un chemin à partir de l’ID de ressource, puis ouvre une poignée vers le chemin (à l’aide de ZwCreateFile(), IoGetDeviceObjectPointer() ou WdfIoTargetOpen()).
  3. Le serveur extrait l’ID du hub de ressources à partir du chemin d’accès au fichier à l’aide des fonctions RESOURCE_HUB_ID_FROM_FILE_NAME() d’assistance du hub de ressources, puis interroge le hub de ressources pour obtenir le descripteur de ressource.
  4. Le serveur effectue un arbitrage de partage pour chaque broche dans le descripteur et termine la requête IRP_MJ_CREATE.
  5. Le client émet une requête IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS sur la poignée reçue.
  6. En réponse à IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS, le serveur effectue l’opération de multiplexage matériel en rendant la fonction spécifiée active sur chaque broche.
  7. Le client poursuit avec les opérations qui dépendent de la configuration du multiplexage de broches demandée.
  8. Lorsque le client n’a plus besoin que les broches soient multiplexées, il ferme la poignée.
  9. En réponse à la fermeture de la poignée, le serveur rétablit l’état initial des broches.

Description du protocole pour les clients du multiplexage de broches

Cette section décrit comment un client consomme la fonctionnalité de multiplexage de broches. Cela ne s’applique pas aux pilotes de contrôleur SerCx et SpbCx car les infrastructures implémentent ce protocole pour le compte des pilotes de contrôleur.

Analyse des ressources

Un pilote WDF reçoit des ressources MsftFunctionConfig() dans sa routine EvtDevicePrepareHardware(). Les ressources MsftFunctionConfig peuvent être identifiées par les champs suivants :

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

Une routine EvtDevicePrepareHardware() peut extraire des ressources MsftFunctionConfig comme suit :

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;
}

Réserver et valider des ressources

Lorsqu’un client souhaite multiplexer des broches, il réserve et valide la ressource MsftFunctionConfig. L’exemple suivant montre comment un client peut réserver et valider des ressources 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;
}

Le pilote doit stocker le WDFIOTARGET dans l’une de ses zones de contexte afin de pouvoir la fermer ultérieurement. Lorsque le pilote est prêt à libérer la configuration de multiplexage, il doit fermer la poignée de ressource en appelant WdfObjectDelete() ou WdfIoTargetClose() si vous envisagez de réutiliser le WDFIOTARGET.

    WdfObjectDelete(resourceHandle);

Lorsque le client ferme sa poignée de ressource, les broches sont multiplexées à leur état initial et peuvent maintenant être acquises par un autre client.

Description du protocole pour les serveurs de multiplexage de broches

Cette section décrit comment un serveur de multiplexage de broches expose ses fonctionnalités aux clients. Cela ne s’applique pas aux pilotes miniport GpioClx, car l’infrastructure implémente ce protocole pour le compte des pilotes clients. Pour plus d’informations sur la prise en charge du multiplexage de broches dans les pilotes clients GpioClx, consultez Implémentation de la prise en charge du multiplexage dans les pilotes clients GpioClx.

Gestion des requêtes IRP_MJ_CREATE

Les clients ouvrent une poignée vers une ressource lorsqu’ils souhaitent réserver une ressource de multiplexage de broches. Un serveur de multiplexage de broches reçoit les requêtes IRP_MJ_CREATE par le biais d’une opération d’analyse à partir du hub de ressources. Le composant de chemin de fin de la requête IRP_MJ_CREATE contient l’ID du hub de ressources, qui est un entier 64 bits au format hexadécimal. Le serveur doit extraire l’ID du hub de ressources du nom de fichier à l’aide de RESOURCE_HUB_ID_FROM_FILE_NAME() du reshub.h et envoyer IOCTL_RH_QUERY_CONNECTION_PROPERTIES au hub de ressources pour obtenir le descripteur MsftFunctionConfig().

Le serveur doit valider le descripteur et extraire le mode de partage et la liste de broches du descripteur. Il doit ensuite effectuer l’arbitrage de partage pour les broches, et s’il réussit, marquer les broches comme réservées avant de compléter la requête.

L’arbitrage de partage réussit globalement si l’arbitrage de partage réussit pour chaque broche dans la liste des broches. Chaque broche doit être arbitrée comme suit :

  • Si la broche n’est pas déjà réservée, l’arbitrage de partage réussit.
  • Si la broche est déjà réservée comme exclusive, l’arbitrage de partage échoue.
  • Si la broche est déjà réservée en tant que partage,
    • et que la requête entrante est partagée, l’arbitrage de partage réussit.
    • et que la requête entrante est exclusive, l’arbitrage de partage échoue.

Si l’arbitrage de partage échoue, la requête doit être effectuée avec STATUS_GPIO_INCOMPATIBLE_CONNECT_MODE. Si l’arbitrage de partage réussit, la requête doit être effectuée avec STATUS_SUCCESS.

Le mode de partage de la requête entrante doit être récupéré à partir du descripteur MsftFunctionConfig, et non IrpSp->Parameters.Create.ShareAccess.

Gestion des requêtes IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS

Une fois que le client a correctement réservé une ressource MsftFunctionConfig en ouvrant une poignée, il peut envoyer IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS pour demander au serveur d’effectuer l’opération de multiplexage matérielle réelle. Lorsque le serveur reçoit IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS, pour chaque broche de la liste des broches, il doit

  • Définir le mode pull spécifié dans le membre PinConfiguration de la structure PNP_FUNCTION_CONFIG_DESCRIPTOR dans le matériel.
  • Multiplexer la broche à la fonction spécifiée par le membre FunctionNumber de la structure PNP_FUNCTION_CONFIG_DESCRIPTOR.

Le serveur doit ensuite terminer la requête avec STATUS_SUCCESS.

La signification de FunctionNumber est définie par le serveur, et il est entendu que le descripteur MsftFunctionConfig a été créé en connaissance de la manière dont le serveur interprète ce champ.

N’oubliez pas que lorsque la poignée est fermée, le serveur doit rétablir les broches dans la configuration dans laquelle elles se trouvaient lorsque IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS a été reçu, de sorte que le serveur peut avoir besoin d’enregistrer l’état des broches avant de les modifier.

Gestion des requêtes IRP_MJ_CLOSE

Lorsqu’un client n’a plus besoin d’une ressource de multiplexage, il ferme sa poignée. Lorsqu’un serveur reçoit une requête IRP_MJ_CLOSE, il doit rétablir les broches à l’état dans lequel elles se trouvaient quand IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS a été reçu. Si le client n’a jamais envoyé de IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS, aucune action n’est nécessaire. Le serveur doit ensuite marquer les broches comme disponibles en ce qui concerne l’arbitrage de partage, et terminer la requête avec STATUS_SUCCESS. Veillez à synchroniser correctement la gestion de IRP_MJ_CLOSE avec la gestion de IRP_MJ_CREATE.

Instructions de création pour les tables ACPI

Cette section explique comment fournir des ressources de multiplexage aux pilotes clients. Notez que vous aurez besoin du compilateur Microsoft ASL build 14327 ou version ultérieure pour compiler des tables contenant des ressources MsftFunctionConfig(). Les ressources MsftFunctionConfig() sont fournies aux clients du multiplexage de broches en tant que ressources matérielles. Les ressources MsftFunctionConfig() doivent être fournies aux pilotes qui requièrent des modifications de multiplexage de broches, qui sont généralement des pilotes de contrôleur série et SPB, mais ne doivent pas être fournies aux pilotes de périphériques série ou SPB, dans la mesure où le pilote de contrôleur gère la configuration du multiplexage. La macro ACPI MsftFunctionConfig() est définie comme suit :

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

  • Partagée/exclusive : si elle est exclusive, cette broche peut être acquise par un seul client à la fois. Si elle est partagée, plusieurs clients partagés peuvent acquérir la ressource. Définissez-la toujours sur exclusive, car permettre à plusieurs clients non coordonnés d’accéder à une ressource mutable peut entraîner des courses aux données et donc des résultats imprévisibles.
  • PinPullConfig : l’un des
    • PullDefault : utiliser la configuration pull par défaut de mise en tension définie par SOC
    • PullUp : activer la résistance pull-up
    • PullDown : activer la résistance pull-down
    • PullNone : désactiver toutes les résistances pull
  • FunctionNumber : numéro de fonction à programmer dans le multiplexage.
  • ResourceSource : chemin d’espace de noms ACPI du serveur de multiplexage de broches
  • ResourceSourceIndex : définir cette valeur sur 0
  • ResourceConsumer/ResourceProducer : définir cette valeur sur ResourceConsumer
  • VendorData : données binaires facultatives dont la signification est définie par le serveur de multiplexage de broches. Cette valeur doit généralement être laissée vide
  • Liste des broches : liste séparée par des virgules de numéros de broche auxquels la configuration s’applique. Lorsque le serveur de multiplexage de broches est un pilote GpioClx, ce sont des numéros de broche GPIO et ils ont la même signification que les numéros de broche dans un descripteur GpioIo.

L’exemple suivant montre comment fournir une ressource MsftFunctionConfig() à un pilote de contrôleur I2C.

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)
    }
}

Outre la mémoire et les ressources d’interruption généralement requises par un pilote de contrôleur, une ressource MsftFunctionConfig() est également spécifiée. Cette ressource permet au pilote de contrôleur I2C de placer les broches 2 et 3 (gérées par le nœud d’appareil au niveau de \_SB. GPIO0) dans la fonction 4 avec la résistance pull-up activée.

Prise en charge du multiplexage dans les pilotes clients GpioClx

GpioClx intègre la prise en charge du multiplexage de broches. Les pilotes miniport GpioClx (également appelés « pilotes clients GpioClx ») pilotent le matériel du contrôleur GPIO. À compter de la build 14327 de Windows 10, les pilotes miniport GpioClx peuvent ajouter la prise en charge du multiplexage de broches en implémentant deux nouvelles DDI :

  • CLIENT_ConnectFunctionConfigPins : appelée par GpioClx pour ordonner au pilote miniport d’appliquer la configuration de multiplexage spécifiée.
  • CLIENT_DisconnectFunctionConfigPins : appelée par GpioClx pour ordonner au pilote miniport de rétablir la configuration du multiplexage.

Consultez les Fonctions de rappel d’événements GpioClx pour obtenir une description de ces routines.

En plus de ces deux nouvelles DDI, les DDI existantes doivent être auditées pour la compatibilité avec le multiplexage de broches :

  • CLIENT_ConnectIoPins/CLIENT_ConnectInterrupt : CLIENT_ConnectIoPins est appelée par GpioClx pour ordonner au pilote miniport de configurer une broche définie pour l’entrée ou la sortie GPIO. GPIO s’exclue mutuellement avec MsftFunctionConfig, ce qui signifie qu’une broche ne sera jamais connectée pour GPIO et MsftFunctionConfig en même temps. Étant donné que la fonction par défaut d’une broche n’est pas obligatoire pour être GPIO, il est possible qu’une broche ne soit pas nécessairement multiplexée à GPIO lorsque ConnectIoPins est appelé. ConnectIoPins est nécessaire pour effectuer toutes les opérations nécessaires pour préparer la broche pour les E/S GPIO, y compris les opérations de multiplexage. CLIENT_ConnectInterrupt doit se comporter de la même façon, car les interruptions peuvent être considérées comme un cas particulier d’entrée GPIO.
  • CLIENT_DisconnectIoPins/CLIENT_DisconnectInterrupt : ces routines doivent retourner les broches à l’état dans lequel elles se trouvaient quand CLIENT_ConnectIoPins/CLIENT_ConnectInterrupt a été appelée, sauf si l’indicateur PreserveConfiguration est spécifié. En plus de rétablir la direction des broches à leur état par défaut, le miniport doit également rétablir l’état de multiplexage de chaque broche à l’état où il était au moment où la routine _Connect a été appelée.

Par exemple, supposons que la configuration de multiplexage par défaut d’une broche est UART et que la broche peut également être utilisée comme GPIO. Quand CLIENT_ConnectIoPins est appelée pour connecter la broche pour GPIO, elle doit multiplexer la broche à GPIO et, dans CLIENT_DisconnectIoPins, elle doit multiplexer la broche à UART. En général, les routines Disconnect doivent annuler les opérations effectuées par les routines Connect.

Prise en charge du multiplexage dans les pilotes de contrôleur SpbCx et SerCx

À compter de la build 14327 de Windows 10, les infrastructures SpbCx et SerCx contiennent une prise en charge intégrée du multiplexage de broches qui permet aux pilotes de contrôleur SpbCx et SerCx d’être des clients de multiplexage de broches sans aucune modification du code apportée aux pilotes de contrôleur eux-mêmes. Par extension, tout pilote de périphérique SpbCx/SerCx qui se connecte à un pilote de contrôleur SpbCx/SerCx prenant en charge le multiplexage déclenche l’activité de multiplexage de broches.

Le diagramme suivant montre les dépendances entre chacun de ces composants. Comme vous pouvez le voir, le multiplexage de broches introduit une dépendance des pilotes de contrôleur SerCx et SpbCx au pilote GPIO, qui est généralement responsable du multiplexage.

Pin muxing dependency

Au moment de l’initialisation de l’appareil, les infrastructures SpbCx et SerCx analysent toutes les ressources MsftFunctionConfig() fournies en tant que ressources matérielles à l’appareil. SpbCx/SerCx acquiert et libère les ressources de multiplexage de broche à la demande.

SpbCx applique la configuration de multiplexage de broche dans son gestionnaire IRP_MJ_CREATE, juste avant d’appeler le rappel EvtSpbTargetConnect() du pilote client. Si la configuration du multiplexage n’a pas pu être appliquée, le rappel EvtSpbTargetConnect() du pilote du contrôleur n’est pas appelé. Par conséquent, un pilote de contrôleur SPB peut supposer que les broches sont multiplexées à la fonction SPB au moment de l’appel de EvtSpbTargetConnect().

SpbCx rétablit la configuration de multiplexage de broche dans son gestionnaire IRP_MJ_CLOSE, juste après l’appel du rappel EvtSpbTargetDisconnect() du pilote de contrôleur. Le résultat est que les broches sont multiplexées à la fonction SPB chaque fois qu’un pilote périphérique ouvre une poignée au pilote du contrôleur SPB et ne sont plus multiplexées lorsque le pilote périphérique ferme sa poignée.

SerCx se comporte de la même façon. SerCx acquiert toutes les ressources MsftFunctionConfig() dans son gestionnaire IRP_MJ_CREATE juste avant d’appeler le rappel EvtSerCx2FileOpen() du pilote de contrôleur, et libère toutes les ressources dans son gestionnaire IRP_MJ_CLOSE, juste après l’appel du rappel EvtSerCx2FileClose du pilote de contrôleur.

L’implication du multiplexage de broches dynamique pour les pilotes de contrôleur SerCx et SpbCx est qu’ils doivent être en mesure de tolérer que les broches soient multiplexées loin de la fonction SPB/UART à certains moments. Les pilotes de contrôleur doivent supposer que les broches ne seront pas multiplexées tant que EvtSpbTargetConnect() ou EvtSerCx2FileOpen() n’est pas appelé. Les broches ne sont pas forcément multiplexées à la fonction SPB/UART pendant les rappels suivants. La liste suivante n’est pas complète, mais représente les routines PNP les plus courantes implémentées par les pilotes de contrôleur.

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

Vérification

Lorsque vous êtes prêt à tester rhproxy, il est utile d’utiliser la procédure pas à pas suivante.

  1. Vérifier que chaque pilote du contrôleur SpbCx, GpioClx et SerCx se charge et fonctionne correctement
  2. Vérifiez que rhproxy est présent sur le système. Certaines éditions et builds de Windows ne l’ont pas.
  3. Compiler et charger votre nœud rhproxy à l’aide de ACPITABL.dat
  4. Vérifier que le nœud de l’appareil rhproxy existe
  5. Vérifier que rhproxy se charge et démarre
  6. Vérifier que les appareils attendus sont exposés au mode utilisateur
  7. Vérifier que vous pouvez interagir avec chaque appareil à partir de la ligne de commande
  8. Vérifier que vous pouvez interagir avec chaque appareil à partir d’une application UWP
  9. Exécuter des tests HLK

Vérifier les pilotes du contrôleur

Étant donné que rhproxy expose d’autres appareils sur le système au mode utilisateur, il fonctionne uniquement si ces appareils fonctionnent déjà. La première étape consiste à vérifier que ces appareils, les contrôleurs I2C, SPI, GPIO que vous souhaitez exposer, fonctionnent déjà.

À l’invite de commandes, exécutez

devcon status *

Examinez la sortie et vérifiez que tous les appareils d’intérêt sont démarrés. Si un appareil a un code problématique, vous devez déterminer pourquoi cet appareil ne se charge pas. Tous les appareils doivent avoir été activés lors de la mise à jour initiale de la plateforme. La résolution des problèmes des pilotes du contrôleur SpbCx, GpioClx ou SerCx dépasse la portée de ce document.

Vérifier que rhproxy est présent sur le système

Vérifiez que le service rhproxy est présent sur le système.

reg query HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\rhproxy

Si la clé reg n’est pas présente, rhproxy n’existe pas sur votre système. Rhproxy est présent sur toutes les builds d’IoT Core et Windows Enterprise build 15063 et versions ultérieures.

Compiler et charger ASL avec ACPITABL.dat

Maintenant que vous avez créé un nœud ASL rhproxy, il est temps de le compiler et de le charger. Vous pouvez compiler le nœud rhproxy dans un fichier AML autonome qui peut être ajouté aux tables ACPI système. Sinon, si vous avez accès aux sources ACPI de votre système, vous pouvez insérer le nœud rhproxy directement dans les tables ACPI de votre plateforme. Toutefois, lors de la mise à jour initiale, il peut être plus facile d’utiliser ACPITABL.dat.

  1. Créez un fichier nommé yourboard.asl et placez le nœud d’appareil RHPX dans un DefinitionBlock :

    DefinitionBlock ("ACPITABL.dat", "SSDT", 1, "MSFT", "RHPROXY", 1)
    {
        Scope (\_SB)
        {
            Device(RHPX)
            {
            ...
            }
        }
    }
    
  2. Téléchargez le WDK et recherchez asl.exe à l’adresse C:\Program Files (x86)\Windows Kits\10\Tools\x64\ACPIVerify

  3. Exécutez la commande suivante pour générer ACPITABL.dat :

    asl.exe yourboard.asl
    
  4. Copiez le fichier ACPITABL.dat obtenu dans c :\windows\system32 sur votre système sous test.

  5. Activez l’attribution de test sur votre système sous test :

    bcdedit /set testsigning on
    
  6. Redémarrez le système sous test. Le système ajoute les tables ACPI définies dans ACPITABL.dat aux tables du microprogramme système.

Vérifier que le nœud d’appareil rhproxy existe

Exécutez la commande suivante pour énumérer le nœud d’appareil rhproxy.

devcon status *msft8000

La sortie du devcon doit indiquer que l’appareil est présent. Si le nœud de l’appareil n’est pas présent, les tables ACPI n’ont pas été correctement ajoutées au système.

Vérifier que rhproxy se charge et démarre

Vérifiez l’état de rhproxy :

devcon status *msft8000

Si la sortie indique que rhproxy est démarré, rhproxy a chargé et démarré avec succès. Si vous voyez un code problématique, vous devez l’examiner. Voici quelques codes de problème courants :

  • Problème 51 : CM_PROB_WAITING_ON_DEPENDENCY : le système ne démarre pas rhproxy, car l’une des dépendances n’a pas pu être chargée. Cela signifie que les ressources ont été passées au point rhproxy vers des nœuds ACPI non valides ou que les appareils cibles ne démarrent pas. Tout d’abord, vérifiez que tous les appareils s’exécutent correctement (consultez « Vérifier les pilotes du contrôleur » ci-dessus). Ensuite, vérifiez votre ASL et vérifiez que tous vos chemins de ressources (par exemple, \_SB.I2C1) sont corrects et pointent vers des nœuds valides dans votre DSDT.
  • Problème 10 : CM_PROB_FAILED_START : rhproxy n’a pas pu démarrer, probablement en raison d’un problème d’analyse des ressources. Passez en revue votre ASL et vérifiez les index de ressources dans le DSD, puis vérifiez que les ressources GPIO sont spécifiées dans l’ordre croissant des numéros de broches.

Vérifier que les appareils attendus sont exposés au mode utilisateur

Maintenant que rhproxy est en cours d’exécution, il doit avoir créé des interfaces d’appareils accessibles en mode utilisateur. Nous allons utiliser plusieurs outils de ligne de commande pour énumérer les appareils et voir qu’ils sont présents.

Clonez le référentiel https://github.com/ms-iot/samples et générez les exemples GpioTestTool, I2cTestTool, SpiTestTool et Mincomm. Copiez les outils sur votre appareil sous test et utilisez les commandes suivantes pour énumérer les appareils.

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

Vous devez voir vos appareils et noms conviviaux répertoriés. Si vous ne voyez pas les appareils appropriés et les noms conviviaux, vérifiez votre ASL.

Vérifier chaque appareil sur la ligne de commande

L’étape suivante consiste à utiliser les outils de ligne de commande pour ouvrir et interagir avec les appareils.

Exemple I2CTestTool :

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

Exemple SpiTestTool :

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

Exemple GpioTestTool :

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

Exemple MinComm (série). Connectez Rx à Tx avant d’exécuter :

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

Vérifier chaque appareil à partir d’une application UWP

Utilisez les exemples suivants pour valider que les appareils fonctionnent à partir d’une application UWP.

Exécuter les tests HLK

Téléchargez le Hardware Lab Kit (HLK). Les tests suivants sont disponibles :

Lorsque vous sélectionnez le nœud d’appareil rhproxy dans le gestionnaire HLK, les tests applicables sont automatiquement sélectionnés.

Dans le gestionnaire HLK, sélectionnez « Appareil proxy du hub de ressources » :

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

Cliquez ensuite sur l’onglet Tests, puis sélectionnez les tests I2C WinRT, Gpio WinRT et 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.

Cliquez sur Exécuter la sélection. Une documentation supplémentaire sur chaque test est disponible en cliquant avec le bouton droit sur le test, puis en cliquant sur « Description du test ».

Ressources

Annexe

Annexe A : liste ASL Raspberry Pi

Consultez aussi Mappages de broches 3 et Raspberry Pi 2

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
                }
            })
        }
    }
}

Annexe B : liste ASL MinnowBoardMax

Voir aussi Mappages de broches Max MinnowBoard

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 }},
                }
            })
        }
    }
}

Annexe C : exemple de script PowerShell pour générer des ressources GPIO

Le script suivant peut être utilisé pour générer les déclarations de ressources GPIO pour Raspberry Pi :

$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;
}