Ressources matérielles WDM : ports, MMIO, interruptions et ressources PnP

Ce cours dédiée au développement de drivers en kernel mode sans Framework

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 144
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

Ressources matérielles WDM : ports, MMIO, interruptions et ressources PnP

Post by Hydraxx »

Ressources matérielles WDM : ports, MMIO, interruptions et ressources PnP

Un driver de périphérique ne choisit normalement pas arbitrairement les adresses, IRQ ou canaux DMA qu'il va utiliser. Sous Windows PnP, le bus driver décrit les ressources possibles, le PnP Manager arbitre les conflits, puis le driver reçoit les ressources qui lui ont été attribuées lors de IRP_MN_START_DEVICE.

Ce cours explique comment lire ces ressources et comment les transformer en accès matériels utilisables par un driver WDM.

1. Qu'est-ce qu'une ressource matérielle ?

Une ressource est une portion du système attribuée à un périphérique.

Les types les plus fréquents sont :
  • CmResourceTypePort : plage de ports d'E/S.
  • CmResourceTypeMemory : plage de mémoire physique, souvent utilisée pour du MMIO.
  • CmResourceTypeInterrupt : ligne/vecteur d'interruption.
  • CmResourceTypeDma : ressource DMA sur les architectures/périphériques qui l'utilisent.
  • CmResourceTypeBusNumber : plage de numéros de bus.
  • CmResourceTypeDevicePrivate : données privées propres à certains bus.
2. Où les ressources arrivent-elles ?

Lors de IRP_MN_START_DEVICE :

Code: Select all

PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);

PCM_RESOURCE_LIST raw =
    stack->Parameters.StartDevice.AllocatedResources;

PCM_RESOURCE_LIST translated =
    stack->Parameters.StartDevice.AllocatedResourcesTranslated;
Deux listes sont fournies :
  • Raw resources : représentation relative au bus.
  • Translated resources : représentation traduite par le HAL/PnP Manager dans l'espace système exploitable par le driver.
En pratique, un function driver utilise généralement la liste traduite pour programmer le périphérique, tout en pouvant conserver la liste raw si le bus/protocole l'exige.

3. Structure CM_RESOURCE_LIST

La hiérarchie est :

Code: Select all

CM_RESOURCE_LIST
    |
    +-- CM_FULL_RESOURCE_DESCRIPTOR
            |
            +-- CM_PARTIAL_RESOURCE_LIST
                    |
                    +-- CM_PARTIAL_RESOURCE_DESCRIPTOR[0]
                    +-- CM_PARTIAL_RESOURCE_DESCRIPTOR[1]
                    +-- ...
Un CM_RESOURCE_LIST est une structure de taille variable.

Forme simplifiée :

Code: Select all

typedef struct _CM_RESOURCE_LIST
{
    ULONG Count;
    CM_FULL_RESOURCE_DESCRIPTOR List[1];
} CM_RESOURCE_LIST;
Pour un driver WDM, Count est normalement 1 pour la liste fournie à START_DEVICE.

4. CM_PARTIAL_RESOURCE_DESCRIPTOR

Chaque ressource réelle est décrite par un CM_PARTIAL_RESOURCE_DESCRIPTOR.

Le champ Type détermine l'union à lire :

Code: Select all

switch (desc->Type)
{
    case CmResourceTypeMemory:
        // desc->u.Memory.Start
        // desc->u.Memory.Length
        break;

    case CmResourceTypePort:
        // desc->u.Port.Start
        // desc->u.Port.Length
        break;

    case CmResourceTypeInterrupt:
        // desc->u.Interrupt.Level
        // desc->u.Interrupt.Vector
        // desc->u.Interrupt.Affinity
        break;

    case CmResourceTypeDma:
        // desc->u.Dma.Channel
        // desc->u.Dma.Port
        break;
}
Il faut toujours respecter le type et les Flags du descriptor avant d'interpréter les champs de l'union.

5. Parcourir la liste de ressources

Exemple :

Code: Select all

VOID ParseResources(PCM_RESOURCE_LIST Resources)
{
    if (Resources == NULL || Resources->Count == 0)
        return;

    PCM_FULL_RESOURCE_DESCRIPTOR full = &Resources->List[0];
    PCM_PARTIAL_RESOURCE_LIST partial = &full->PartialResourceList;

    for (ULONG i = 0; i < partial->Count; ++i)
    {
        PCM_PARTIAL_RESOURCE_DESCRIPTOR d = &partial->PartialDescriptors[i];

        switch (d->Type)
        {
            case CmResourceTypeMemory:
                DbgPrint("MEM start=%llx length=%lu\n",
                    d->u.Memory.Start.QuadPart,
                    d->u.Memory.Length);
                break;

            case CmResourceTypePort:
                DbgPrint("PORT start=%llx length=%lu\n",
                    d->u.Port.Start.QuadPart,
                    d->u.Port.Length);
                break;

            case CmResourceTypeInterrupt:
                DbgPrint("IRQ vector=%lu level=%lu\n",
                    d->u.Interrupt.Vector,
                    d->u.Interrupt.Level);
                break;
        }
    }
}
6. MMIO : Memory-Mapped I/O

Beaucoup de périphériques modernes exposent leurs registres via une plage de mémoire physique.

Le CPU accède alors à des registres matériels comme à de la mémoire, mais cette mémoire n'est pas de la RAM normale.

Chaîne typique :

Code: Select all

CM_RESOURCE_DESCRIPTOR
        |
Physical Address + Length
        |
MmMapIoSpaceEx
        |
Virtual Address kernel
        |
READ_REGISTER_xxx / WRITE_REGISTER_xxx
Exemple :

Code: Select all

PHYSICAL_ADDRESS pa = desc->u.Memory.Start;
ULONG length = desc->u.Memory.Length;

PVOID regs = MmMapIoSpaceEx(
    pa,
    length,
    PAGE_READWRITE | PAGE_NOCACHE
);

if (regs == NULL)
    return STATUS_INSUFFICIENT_RESOURCES;
MmMapIoSpaceEx mappe une plage physique d'I/O dans l'espace virtuel du noyau avec les protections indiquées.

Lors de STOP/REMOVE :

Code: Select all

MmUnmapIoSpace(regs, length);
Ne jamais utiliser directement un pointeur physique comme un pointeur virtuel.

7. Lire et écrire des registres MMIO

Pour les registres mémoire-mappés, utiliser les primitives prévues :
  • READ_REGISTER_UCHAR
  • READ_REGISTER_USHORT
  • READ_REGISTER_ULONG
  • READ_REGISTER_BUFFER_UCHAR/USHORT/ULONG
  • WRITE_REGISTER_UCHAR
  • WRITE_REGISTER_USHORT
  • WRITE_REGISTER_ULONG
  • WRITE_REGISTER_BUFFER_UCHAR/USHORT/ULONG
Exemple :

Code: Select all

volatile ULONG* control = (volatile ULONG*)((PUCHAR)regs + 0x20);

ULONG value = READ_REGISTER_ULONG(control);
WRITE_REGISTER_ULONG(control, value | 0x1);
L'intérêt de ces primitives est de respecter les contraintes d'accès aux registres d'I/O imposées par la plateforme.

8. Port I/O

Sur certaines architectures/périphériques, une ressource CmResourceTypePort correspond à un véritable espace de ports d'E/S.

Les primitives classiques sont :
  • READ_PORT_UCHAR / USHORT / ULONG
  • WRITE_PORT_UCHAR / USHORT / ULONG
  • READ_PORT_BUFFER_*
  • WRITE_PORT_BUFFER_*
Exemple conceptuel :

Code: Select all

PUCHAR port = (PUCHAR)(ULONG_PTR)desc->u.Port.Start.QuadPart;
UCHAR status = READ_PORT_UCHAR(port);
WRITE_PORT_UCHAR(port, 0x01);
Attention : il faut interpréter correctement les Flags du resource descriptor. Selon la ressource et l'architecture, une plage peut devoir être traitée comme port I/O ou comme ressource memory-mapped.

9. Interruptions

Une ressource CmResourceTypeInterrupt contient les informations nécessaires à la connexion de l'interruption.

Les champs peuvent inclure :
  • Vector
  • Level
  • Affinity
  • Flags indiquant notamment le mode/partage selon la ressource
L'API moderne générale côté WDM est :

IoConnectInterruptEx

Elle permet d'enregistrer une ISR en utilisant les informations d'interruption fournies par PnP.

La déconnexion se fait avec :

IoDisconnectInterruptEx

La logique détaillée ISR/DPC appartient au cours Interruptions/DPC.

10. Ressources raw et translated

Pourquoi deux listes ?

Un bus peut exprimer une adresse dans son propre espace. Le système traduit cette adresse vers une adresse exploitable au niveau CPU/système.

Exemple conceptuel :

Code: Select all

Adresse bus          : 0x00002000
       |
Bus driver / HAL / PnP
       v
Adresse système      : 0xF7800000
Le driver ne doit donc pas supposer que les deux listes sont identiques.

11. Requirements vs Assigned Resources

Il faut distinguer :
  • Resource requirements : ce que le périphérique PEUT ou DOIT recevoir.
  • Assigned resources : ce que le PnP Manager a réellement choisi.
Les requirements utilisent notamment :

Code: Select all

IO_RESOURCE_REQUIREMENTS_LIST
    |
    +-- IO_RESOURCE_LIST
            |
            +-- IO_RESOURCE_DESCRIPTOR[]
Alors que les ressources attribuées dans START_DEVICE utilisent :

Code: Select all

CM_RESOURCE_LIST
    |
    +-- CM_PARTIAL_RESOURCE_DESCRIPTOR[]
Ne pas confondre les deux familles de structures.

12. IRP_MN_FILTER_RESOURCE_REQUIREMENTS

Cette requête est envoyée pour permettre au function driver d'ajuster les exigences de ressources avant l'allocation finale.

Le pointeur principal est retourné dans :

Code: Select all

Irp->IoStatus.Information
Il pointe vers une IO_RESOURCE_REQUIREMENTS_LIST.

Le driver peut par exemple :
  • Vérifier qu'une ressource requise existe.
  • Restreindre une plage acceptable.
  • Ajouter/modifier une alternative lorsque le bus le permet.
S'il change la taille totale de la structure, il doit créer une nouvelle liste de taille suffisante et retourner ce nouveau buffer correctement.

Important : l'ordre des ressources et les contraintes du bus doivent être respectés. Modifier arbitrairement une requirements list peut rendre l'allocation impossible ou incohérente.

13. Cycle de vie d'un mapping hardware

Un mapping n'est pas permanent.

Séquence correcte :

Code: Select all

IRP_MN_START_DEVICE
    |
    +--> lire ressources
    +--> MmMapIoSpaceEx
    +--> configurer hardware
    |
    v
I/O normales
    |
STOP / REMOVE / SURPRISE REMOVAL
    |
    +--> arrêter hardware
    +--> déconnecter interruptions
    +--> MmUnmapIoSpace
Le driver ne doit jamais continuer à utiliser le mapping après avoir décidé que le matériel n'est plus accessible.

14. API et structures essentielles
  • CM_RESOURCE_LIST : liste des ressources attribuées.
  • CM_FULL_RESOURCE_DESCRIPTOR : descripteur complet d'une liste.
  • CM_PARTIAL_RESOURCE_LIST : ensemble de ressources partielles.
  • CM_PARTIAL_RESOURCE_DESCRIPTOR : une ressource attribuée.
  • IO_RESOURCE_REQUIREMENTS_LIST : liste de configurations de ressources possibles.
  • IO_RESOURCE_LIST : une configuration alternative.
  • IO_RESOURCE_DESCRIPTOR : une exigence de ressource.
  • MmMapIoSpaceEx : mappe une ressource physique MMIO.
  • MmUnmapIoSpace : détruit le mapping.
  • READ_REGISTER_* / WRITE_REGISTER_* : accès aux registres MMIO.
  • READ_PORT_* / WRITE_PORT_* : accès à l'espace de ports.
  • IoConnectInterruptEx : connecte une ISR.
  • IoDisconnectInterruptEx : déconnecte l'interruption.
  • IoGetCurrentIrpStackLocation : accès aux Parameters.StartDevice.
15. APIs legacy à connaître sans les utiliser comme modèle moderne

Certains vieux ouvrages WDM montrent des mécanismes historiques comme IoReportResourceForDetection ou des modèles d'allocation datant des périphériques non-PnP.

Pour un driver matériel PnP moderne, le modèle normal est :

Code: Select all

Bus driver décrit les besoins
        |
PnP Manager arbitre
        |
IRP_MN_START_DEVICE
        |
CM_RESOURCE_LIST attribuée au driver
Les APIs legacy restent utiles pour comprendre l'histoire de WDM, mais ne doivent pas remplacer le modèle PnP moderne sans raison précise.

16. Erreurs fréquentes
  • Confondre IO_RESOURCE_REQUIREMENTS_LIST et CM_RESOURCE_LIST.
  • Utiliser les raw resources à la place des translated resources sans raison.
  • Caster une adresse physique en pointeur virtuel.
  • Lire les registres matériels avec un simple pointeur sans respecter les primitives REGISTER/PORT adaptées.
  • Oublier MmUnmapIoSpace lors de l'arrêt ou de la suppression.
  • Continuer à accéder au hardware après SURPRISE_REMOVAL.
  • Hardcoder une adresse MMIO ou une IRQ qui doit être fournie par PnP.
  • Ignorer les Flags de CM_PARTIAL_RESOURCE_DESCRIPTOR.
17. Résumé

La chaîne complète est :

Code: Select all

Resource requirements
        |
PnP arbitration
        |
IRP_MN_START_DEVICE
        |
CM_RESOURCE_LIST raw + translated
        |
Parse descriptors
        |
MMIO / PORT / INTERRUPT / DMA
        |
Driver opérationnel
Pour un function driver, la règle la plus importante est simple : le hardware ne doit être configuré qu'à partir des ressources réellement attribuées par le système.

Who is online

Users browsing this forum: No registered users and 1 guest