DMA sous Windows : du buffer mémoire au périphérique

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:

DMA sous Windows : du buffer mémoire au périphérique

Post by Hydraxx »

DMA sous Windows : du buffer mémoire au périphérique

Introduction

Le Direct Memory Access permet à un périphérique de transférer des données vers ou depuis la mémoire sans obliger le CPU à copier chaque octet lui-même.

Le DMA est central dans les drivers de stockage, réseau, audio, vidéo et dans de nombreux périphériques PCI/PCIe.

1. CPU copy contre DMA

Sans DMA :

Code: Select all

Périphérique -> registre -> CPU -> RAM
Avec DMA :

Code: Select all

Périphérique <------> RAM
        ^
        |
       CPU configure le transfert
Le CPU prépare l'opération, puis le périphérique effectue le transfert.

2. Le problème des adresses

Le périphérique ne peut pas utiliser directement un pointeur virtuel comme :

Code: Select all

0x000001F123456000
Cette adresse n'a de sens que dans l'espace d'adressage du CPU/processus.

Windows doit traduire la mémoire en adresses utilisables par le périphérique.

Le DMA peut également passer par une IOMMU selon la plateforme.

3. DMA Adapter

Windows représente les capacités DMA du périphérique par un objet DMA adapter.

Le driver décrit d'abord son matériel avec :

DEVICE_DESCRIPTION

Champs historiquement importants :
  • Version
  • Master
  • ScatterGather
  • DemandMode
  • AutoInitialize
  • Dma32BitAddresses
  • Dma64BitAddresses
  • MaximumLength
Puis il obtient l'adapter :

Code: Select all

ULONG mapRegisters = 0;

PDMA_ADAPTER adapter = IoGetDmaAdapter(
    PhysicalDeviceObject,
    &deviceDescription,
    &mapRegisters
);
API importantes
  • IoGetDmaAdapter
  • AllocateAdapterChannel
  • MapTransfer
  • FlushAdapterBuffers
  • FreeMapRegisters
  • FreeAdapterChannel
  • GetScatterGatherList
  • PutScatterGatherList
  • AllocateCommonBuffer
  • FreeCommonBuffer
4. Rôle des map registers

Les map registers permettent au système de construire des adresses DMA exploitables par le périphérique.

Un gros buffer peut nécessiter plusieurs map registers.

Si l'opération est plus grande que ce que le système peut mapper en une fois, le transfert doit être réalisé par étapes.

5. Lien entre MDL et DMA

Le MDL décrit les pages du buffer.

Code: Select all

User buffer
    |
    v
MDL
    |
    v
pages physiques
    |
    v
DMA adapter
    |
    v
adresses utilisables par le périphérique
6. AllocateAdapterChannel

Historiquement, un driver WDM réserve des ressources DMA avec la routine AllocateAdapterChannel du DMA_OPERATIONS.

Conceptuellement :

Code: Select all

adapter->DmaOperations->AllocateAdapterChannel(
    adapter,
    DeviceObject,
    numberOfMapRegisters,
    AdapterControl,
    context
);
Lorsque les ressources deviennent disponibles, Windows appelle AdapterControl.

7. AdapterControl

La routine reçoit notamment un MapRegisterBase opaque.

Code: Select all

IO_ALLOCATION_ACTION AdapterControl(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PVOID MapRegisterBase,
    PVOID Context
);
Elle prépare le transfert puis retourne une valeur indiquant si les ressources DMA doivent être conservées ou libérées.

8. MapTransfer

MapTransfer traduit une portion du MDL en une adresse utilisable par le périphérique.

Code: Select all

PHYSICAL_ADDRESS deviceAddress;
ULONG length = bytesThisStage;

deviceAddress =
    adapter->DmaOperations->MapTransfer(
        adapter,
        Irp->MdlAddress,
        MapRegisterBase,
        currentVa,
        &length,
        writeToDevice
    );
Le driver programme ensuite les registres DMA du matériel avec l'adresse et la longueur obtenues.

9. Transfert en plusieurs étapes

Un transfert peut être découpé :

Code: Select all

IRP 1 Mo
 |
 +--> étape 1 : 64 Ko
 +--> étape 2 : 64 Ko
 +--> étape 3 : 64 Ko
 ...
Après chaque interruption de fin, le DPC peut préparer l'étape suivante.

10. FlushAdapterBuffers

Une fois une étape terminée, le driver utilise selon le modèle DMA :

Code: Select all

adapter->DmaOperations->FlushAdapterBuffers(...)
Cette étape permet au système de finaliser correctement le mapping et les éventuelles opérations de cohérence nécessaires.

11. Libération des ressources

Selon la stratégie :

Code: Select all

adapter->DmaOperations->FreeMapRegisters(...);
adapter->DmaOperations->FreeAdapterChannel(...);
Les ressources DMA sont limitées. Elles doivent être rendues dès qu'elles ne sont plus nécessaires.

12. Scatter/Gather

Un buffer virtuellement continu n'est pas nécessairement physiquement continu.

Exemple :

Code: Select all

virtuel :
[A][B][C][D]

physique :
[A] page 0x1000
[B] page 0x9000
[C] page 0x3000
[D] page 0xF000
Le scatter/gather décrit cette fragmentation sous forme de segments.

Structures importantes
  • SCATTER_GATHER_LIST
  • SCATTER_GATHER_ELEMENT
Conceptuellement :

Code: Select all

SCATTER_GATHER_LIST
  element 0 -> Address + Length
  element 1 -> Address + Length
  element 2 -> Address + Length
13. GetScatterGatherList

Windows peut construire directement la liste :

Code: Select all

adapter->DmaOperations->GetScatterGatherList(
    adapter,
    DeviceObject,
    Irp->MdlAddress,
    currentVa,
    length,
    ExecutionRoutine,
    context,
    writeToDevice
);
La callback reçoit ensuite la liste et peut programmer le matériel.

14. PutScatterGatherList

À la fin :

Code: Select all

adapter->DmaOperations->PutScatterGatherList(
    adapter,
    scatterGatherList,
    writeToDevice
);
15. Pourquoi le scatter/gather est puissant

Sans scatter/gather, un driver peut devoir effectuer plusieurs petits mappings ou utiliser un buffer intermédiaire.

Avec scatter/gather, un périphérique capable de traiter une liste de descriptors peut transférer plusieurs fragments physiques comme une seule opération logique.

16. Common Buffer

Un Common Buffer est une zone mémoire allouée pour être accessible à la fois par le CPU et par le périphérique.

Code: Select all

CPU <----> Common Buffer <----> Device
Allocation :

Code: Select all

PHYSICAL_ADDRESS logicalAddress;

PVOID commonBuffer =
    adapter->DmaOperations->AllocateCommonBuffer(
        adapter,
        size,
        &logicalAddress,
        FALSE
    );
Libération :

Code: Select all

adapter->DmaOperations->FreeCommonBuffer(
    adapter,
    size,
    logicalAddress,
    commonBuffer,
    FALSE
);
17. À quoi sert un Common Buffer ?

Très utile pour :
  • Descripteurs DMA.
  • Ring buffers.
  • Queues matérielles.
  • Petites structures persistantes partagées CPU/périphérique.
18. Common Buffer et cohérence

Le paramètre de cache/cohérence dépend de l'architecture et du type de périphérique. Le driver ne doit pas supposer qu'un simple buffer RAM quelconque peut être utilisé directement par le matériel.

19. Bus-master DMA

Un périphérique bus-master peut initier lui-même les transactions DMA sur le bus après programmation par le driver.

Schéma :

Code: Select all

Driver
  |
  +--> prépare MDL / SG list
  +--> programme adresse
  +--> programme longueur
  +--> START
            |
            v
        périphérique
            |
            v
          DMA
            |
            v
       interruption
20. System DMA / Slave DMA

Les anciennes architectures pouvaient utiliser un contrôleur DMA système et des canaux dédiés.

Ce modèle reste utile pour comprendre l'histoire et certaines plateformes, mais le bus-master DMA est beaucoup plus représentatif des périphériques modernes PCI/PCIe.

21. Chaîne complète IRP -> DMA -> ISR -> DPC

Code: Select all

ReadFile / WriteFile
        |
        v
IRP_MJ_READ / WRITE
        |
        v
MDL
        |
        v
DMA adapter
        |
        v
MapTransfer / Scatter-Gather
        |
        v
programmation du périphérique
        |
        v
DMA
        |
        v
interruption
        |
        v
ISR
        |
        v
DPC
        |
        +--> FlushAdapterBuffers
        +--> étape suivante éventuelle
        +--> libération ressources
        |
        v
IoCompleteRequest
22. Erreurs fréquentes
  • Donner directement une adresse virtuelle CPU au matériel.
  • Oublier de tenir compte des limites de longueur du périphérique.
  • Supposer que les pages sont physiquement contiguës.
  • Ne pas libérer les scatter/gather lists ou les map registers.
  • Faire trop de travail dans l'ISR.
  • Compléter l'IRP avant la fin réelle du DMA.
  • Réutiliser un buffer alors que le périphérique l'utilise encore.
23. API historiques et Windows modernes

Le modèle conceptuel présenté ici vient du WDM classique. Certaines API historiques restent documentées mais les nouveaux drivers peuvent utiliser KMDF et les objets DMA WDF, qui automatisent une partie de la gestion.

Il reste néanmoins essentiel de comprendre les notions suivantes :
  • MDL
  • DMA adapter
  • mapping
  • scatter/gather
  • common buffer
  • interruptions
  • ISR
  • DPC
Ces concepts existent toujours derrière les abstractions modernes.

24. Résumé

Le DMA est le point où plusieurs mécanismes du kernel se rejoignent : mémoire virtuelle, pages physiques, MDL, PnP, MMIO, interruptions, ISR, DPC et synchronisation.

Comprendre cette chaîne donne une vision très proche de ce que fait réellement un driver matériel Windows.

Who is online

Users browsing this forum: No registered users and 0 guests