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.
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);
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
);
Exemple dans AddDevice :
Code: Select all
NTSTATUS status = IoRegisterDeviceInterface(
PhysicalDeviceObject,
&GUID_DEVINTERFACE_MYDEVICE,
NULL,
&ext->InterfaceName
);
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
);
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)
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
);
- IRP_MN_SURPRISE_REMOVAL
- IRP_MN_REMOVE_DEVICE
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
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
);
- DIGCF_PRESENT : uniquement les périphériques actuellement présents.
- DIGCF_DEVICEINTERFACE : énumération d'interfaces de périphérique.
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
);
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
);
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
);
Code: Select all
detail->DevicePath
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
);
- ReadFile / WriteFile si le driver les expose ;
- DeviceIoControl pour les IOCTL ;
- I/O overlapped si le driver et l'application supportent l'asynchronisme.
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
Code: Select all
PDO
|
IoRegisterDeviceInterface(GUID)
|
IoSetDeviceInterfaceState(TRUE)
|
SetupAPI énumère l'interface
|
CreateFile(DevicePath)
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.
- 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.
- 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.
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
