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
Code: Select all
Périphérique <------> RAM
^
|
CPU configure 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
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
Code: Select all
ULONG mapRegisters = 0;
PDMA_ADAPTER adapter = IoGetDmaAdapter(
PhysicalDeviceObject,
&deviceDescription,
&mapRegisters
);
- IoGetDmaAdapter
- AllocateAdapterChannel
- MapTransfer
- FlushAdapterBuffers
- FreeMapRegisters
- FreeAdapterChannel
- GetScatterGatherList
- PutScatterGatherList
- AllocateCommonBuffer
- FreeCommonBuffer
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
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
);
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
);
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
);
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
...
10. FlushAdapterBuffers
Une fois une étape terminée, le driver utilise selon le modèle DMA :
Code: Select all
adapter->DmaOperations->FlushAdapterBuffers(...)
11. Libération des ressources
Selon la stratégie :
Code: Select all
adapter->DmaOperations->FreeMapRegisters(...);
adapter->DmaOperations->FreeAdapterChannel(...);
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
Structures importantes
- SCATTER_GATHER_LIST
- SCATTER_GATHER_ELEMENT
Code: Select all
SCATTER_GATHER_LIST
element 0 -> Address + Length
element 1 -> Address + Length
element 2 -> Address + Length
Windows peut construire directement la liste :
Code: Select all
adapter->DmaOperations->GetScatterGatherList(
adapter,
DeviceObject,
Irp->MdlAddress,
currentVa,
length,
ExecutionRoutine,
context,
writeToDevice
);
14. PutScatterGatherList
À la fin :
Code: Select all
adapter->DmaOperations->PutScatterGatherList(
adapter,
scatterGatherList,
writeToDevice
);
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
Code: Select all
PHYSICAL_ADDRESS logicalAddress;
PVOID commonBuffer =
adapter->DmaOperations->AllocateCommonBuffer(
adapter,
size,
&logicalAddress,
FALSE
);
Code: Select all
adapter->DmaOperations->FreeCommonBuffer(
adapter,
size,
logicalAddress,
commonBuffer,
FALSE
);
Très utile pour :
- Descripteurs DMA.
- Ring buffers.
- Queues matérielles.
- Petites structures persistantes partagées CPU/périphérique.
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
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
- 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.
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
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.
