Device Interfaces Windows : exposer un périphérique du kernel au user mode

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:

Device Interfaces Windows : exposer un périphérique du kernel au user mode

Post by Hydraxx »

Device Interfaces Windows : exposer un périphérique du kernel au user mode

Un driver doit souvent permettre à une application user mode de découvrir et d'ouvrir une instance de périphérique.

L'ancien réflexe consiste à créer un nom \Device\X puis un lien symbolique \DosDevices\X. Ce modèle reste possible pour certains drivers logiciels, mais pour un véritable périphérique PnP, Windows fournit le mécanisme des Device Interfaces.

Une device interface associe une instance de périphérique à une classe fonctionnelle identifiée par un GUID.

1. Device name vs Device Interface

Deux concepts différents :
  • Device name : nom d'objet dans l'Object Manager, par exemple \Device\MyDevice.
  • Device interface : chemin publié par PnP permettant aux applications de découvrir une instance appartenant à une classe d'interface.
Une application PnP ne devrait pas dépendre d'un nom d'instance hardcodé si plusieurs périphériques identiques peuvent exister.

2. GUID de classe d'interface

Une classe d'interface est identifiée par un GUID :

Code: Select all

DEFINE_GUID(GUID_DEVINTERFACE_MYDEVICE,
    0x12345678, 0x1234, 0x5678,
    0x90, 0xab, 0xcd, 0xef, 0x11, 0x22, 0x33, 0x44);
Ce GUID décrit une capacité/interface, pas une instance unique.

Plusieurs périphériques peuvent publier la même classe d'interface.

3. IoRegisterDeviceInterface

Le function driver enregistre une instance d'interface avec :

IoRegisterDeviceInterface

Signature logique :

Code: Select all

NTSTATUS IoRegisterDeviceInterface(
    PDEVICE_OBJECT PhysicalDeviceObject,
    const GUID* InterfaceClassGuid,
    PUNICODE_STRING ReferenceString,
    PUNICODE_STRING SymbolicLinkName
);
Le premier paramètre est le PDO, pas le FDO.

Exemple dans AddDevice :

Code: Select all

NTSTATUS status = IoRegisterDeviceInterface(
    PhysicalDeviceObject,
    &GUID_DEVINTERFACE_MYDEVICE,
    NULL,
    &ext->InterfaceName
);
Si l'appel réussit, InterfaceName contient une UNICODE_STRING allouée par le système représentant le chemin de l'interface.

4. Enregistrer ne signifie pas activer

Après IoRegisterDeviceInterface, l'interface existe mais n'est pas nécessairement utilisable par les applications.

Elle est activée avec :

IoSetDeviceInterfaceState

Code: Select all

status = IoSetDeviceInterfaceState(
    &ext->InterfaceName,
    TRUE
);
Une application ne peut ouvrir que les interfaces activées.

5. Quand activer l'interface ?

Pattern PnP normal :

Code: Select all

AddDevice
    |
IoRegisterDeviceInterface
    |
interface enregistrée mais désactivée
    |
IRP_MN_START_DEVICE réussi
    |
IoSetDeviceInterfaceState(..., TRUE)
Pourquoi ne pas l'activer immédiatement dans AddDevice ?

Parce que le périphérique n'a pas encore nécessairement reçu START_DEVICE et son hardware peut ne pas être prêt.

6. Quand désactiver l'interface ?

Lorsqu'un périphérique n'est plus accessible :

Code: Select all

IoSetDeviceInterfaceState(
    &ext->InterfaceName,
    FALSE
);
Cela est particulièrement important lors de :
  • IRP_MN_SURPRISE_REMOVAL
  • IRP_MN_REMOVE_DEVICE
Selon le modèle du driver, l'interface peut également être rendue indisponible pendant certaines transitions d'arrêt.

7. Libérer la chaîne retournée

IoRegisterDeviceInterface remplit une UNICODE_STRING dont le Buffer doit être libéré lorsqu'il n'est plus nécessaire.

Le driver peut utiliser :

RtlFreeUnicodeString

après avoir désactivé l'interface et terminé avec ce nom.

8. Côté user mode : SetupAPI

Une application peut rechercher toutes les interfaces actives d'une classe donnée avec SetupAPI.

Chaîne classique :

Code: Select all

SetupDiGetClassDevs
        |
SetupDiEnumDeviceInterfaces
        |
SetupDiGetDeviceInterfaceDetail
        |
DevicePath
        |
CreateFile
9. SetupDiGetClassDevs

Pour récupérer les périphériques présents exposant une classe d'interface :

Code: Select all

HDEVINFO h = SetupDiGetClassDevsW(
    &GUID_DEVINTERFACE_MYDEVICE,
    NULL,
    NULL,
    DIGCF_PRESENT | DIGCF_DEVICEINTERFACE
);
Flags importants :
  • DIGCF_PRESENT : uniquement les périphériques actuellement présents.
  • DIGCF_DEVICEINTERFACE : énumération d'interfaces de périphérique.
10. SetupDiEnumDeviceInterfaces

On énumère ensuite les interfaces :

Code: Select all

SP_DEVICE_INTERFACE_DATA ifData = {0};
ifData.cbSize = sizeof(ifData);

SetupDiEnumDeviceInterfaces(
    h,
    NULL,
    &GUID_DEVINTERFACE_MYDEVICE,
    index,
    &ifData
);
Chaque appel récupère une instance différente.

11. SetupDiGetDeviceInterfaceDetail

Cette fonction renvoie le chemin ouvrable.

Elle s'utilise généralement en deux appels :

Code: Select all

DWORD required = 0;
SetupDiGetDeviceInterfaceDetailW(
    h,
    &ifData,
    NULL,
    0,
    &required,
    NULL
);
Puis allocation du buffer et second appel :

Code: Select all

PSP_DEVICE_INTERFACE_DETAIL_DATA_W detail =
    (PSP_DEVICE_INTERFACE_DETAIL_DATA_W)malloc(required);

detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);

SetupDiGetDeviceInterfaceDetailW(
    h,
    &ifData,
    detail,
    required,
    NULL,
    NULL
);
Le champ :

Code: Select all

detail->DevicePath
contient le chemin à fournir à CreateFileW.

12. Ouvrir le périphérique avec CreateFile

Exemple :

Code: Select all

HANDLE hDevice = CreateFileW(
    detail->DevicePath,
    GENERIC_READ | GENERIC_WRITE,
    0,
    NULL,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    NULL
);
Une fois le handle obtenu, l'application communique avec le driver via :
  • ReadFile / WriteFile si le driver les expose ;
  • DeviceIoControl pour les IOCTL ;
  • I/O overlapped si le driver et l'application supportent l'asynchronisme.
13. Nettoyage SetupAPI

Le set retourné par SetupDiGetClassDevs doit être libéré avec :

SetupDiDestroyDeviceInfoList

14. IoGetDeviceInterfaces côté kernel

Un driver kernel peut lui aussi rechercher des interfaces d'une classe avec :

IoGetDeviceInterfaces

Cette routine peut retourner une multi-string contenant les noms symboliques des interfaces correspondant au GUID demandé.

Elle est utile lorsqu'un driver doit découvrir d'autres devices sans passer par une application user mode.

15. ReferenceString

IoRegisterDeviceInterface accepte un paramètre optionnel ReferenceString.

Il permet de distinguer plusieurs interfaces logiques appartenant au même PDO et à la même classe.

Dans la majorité des drivers simples, NULL suffit.

Il ne faut pas utiliser des séparateurs de chemin dans cette chaîne.

16. Device Interface vs lien \DosDevices

Modèle traditionnel :

Code: Select all

\Device\MyDriver
      |
\DosDevices\MyDriver
      |
\\.\MyDriver
Modèle PnP :

Code: Select all

PDO
 |
IoRegisterDeviceInterface(GUID)
 |
IoSetDeviceInterfaceState(TRUE)
 |
SetupAPI énumère l'interface
 |
CreateFile(DevicePath)
Pour un périphérique PnP pouvant exister en plusieurs exemplaires, le second modèle est beaucoup plus naturel.

17. Sécurité

Une device interface n'accorde pas automatiquement des droits illimités aux applications.

L'ouverture est toujours soumise au modèle de sécurité du device object / device setup.

Le driver doit également valider strictement tous les buffers et IOCTL reçus. La présence d'un GUID d'interface n'est pas une frontière de sécurité.

18. API essentielles

Kernel mode :
  • IoRegisterDeviceInterface : enregistre une instance d'une classe d'interface pour un PDO.
  • IoSetDeviceInterfaceState : active ou désactive l'interface.
  • IoGetDeviceInterfaces : récupère les interfaces existantes d'une classe depuis le kernel.
  • RtlFreeUnicodeString : libère certaines UNICODE_STRING allouées par le système lorsqu'elles ne sont plus nécessaires.
User mode :
  • SetupDiGetClassDevsW : crée un set d'informations sur les devices/interfaces recherchés.
  • SetupDiEnumDeviceInterfaces : énumère les interfaces.
  • SetupDiGetDeviceInterfaceDetailW : récupère le DevicePath.
  • SetupDiDestroyDeviceInfoList : libère le HDEVINFO.
  • CreateFileW : ouvre l'interface.
  • DeviceIoControl : envoie des IOCTL au driver.
19. Erreurs fréquentes
  • Passer le FDO à IoRegisterDeviceInterface au lieu du PDO.
  • Activer l'interface dans AddDevice avant que START_DEVICE ait réussi.
  • Oublier de la désactiver lors d'un surprise removal/remove.
  • Hardcoder le DevicePath retourné par SetupAPI.
  • Supposer qu'une classe d'interface ne contient qu'une instance.
  • Confondre GUID de classe d'interface et identité unique du périphérique.
  • Confondre le chemin d'interface PnP avec un simple lien \DosDevices.
20. Résumé

Le modèle complet est :

Code: Select all

Kernel
------
AddDevice
   |
IoRegisterDeviceInterface(PDO, GUID)
   |
START_DEVICE réussi
   |
IoSetDeviceInterfaceState(TRUE)

User mode
---------
SetupDiGetClassDevs
   |
SetupDiEnumDeviceInterfaces
   |
SetupDiGetDeviceInterfaceDetail
   |
CreateFile
   |
DeviceIoControl / ReadFile / WriteFile
Les device interfaces permettent donc de relier proprement le monde PnP kernel à la découverte dynamique côté user mode.

Who is online

Users browsing this forum: No registered users and 1 guest