Partager via


Utilisation des lecteurs NVMe

S’applique à :

  • Windows 10
  • Windows Server 2016

Découvrez comment utiliser des appareils NVMe à haute vitesse à partir de votre application Windows. L’accès aux appareils est activé via StorNVMe.sys, le pilote intégré introduit pour la première fois dans Windows Server 2012 R2 et Windows 8.1. Il est également disponible pour les appareils Windows 7 par le biais d’un correctif KB. Dans Windows 10, plusieurs nouvelles fonctionnalités ont été introduites, notamment un mécanisme pass-through pour les commandes NVMe spécifiques au fournisseur et les mises à jour des IOCTL existants.

Cette rubrique fournit une vue d’ensemble des API à usage général que vous pouvez utiliser pour accéder aux lecteurs NVMe dans Windows 10. Il décrit également les éléments suivants :

API pour l’utilisation des lecteurs NVMe

Vous pouvez utiliser les API d’utilisation générale suivantes pour accéder aux lecteurs NVMe dans Windows 10. Ces API sont disponibles dans winioctl.h pour les applications en mode utilisateur et ntddstor.h pour les pilotes en mode noyau. Pour plus d’informations sur les fichiers d’en-tête, consultez Fichiers d’en-tête.

  • IOCTL_STORAGE_PROTOCOL_COMMAND : utilisez cette iocTL avec la structure de STORAGE_PROTOCOL_COMMAND pour émettre des commandes NVMe. Cette IOCTL permet la transmission NVMe et prend en charge le journal effets de commande dans NVMe. Vous pouvez l’utiliser avec des commandes spécifiques au fournisseur. Pour plus d’informations, consultez le mécanisme de transfert.

  • STORAGE_PROTOCOL_COMMAND : cette structure de mémoire tampon d’entrée inclut un champ ReturnStatus qui peut être utilisé pour signaler les valeurs d’état suivantes.

    • STORAGE_PROTOCOL_STATUS_PENDING
    • STORAGE_PROTOCOL_STATUS_SUCCESS
    • STORAGE_PROTOCOL_STATUS_ERROR
    • STORAGE_PROTOCOL_STATUS_INVALID_REQUEST
    • STORAGE_PROTOCOL_STATUS_NO_DEVICE
    • STORAGE_PROTOCOL_STATUS_BUSY
    • STORAGE_PROTOCOL_STATUS_DATA_OVERRUN
    • STORAGE_PROTOCOL_STATUS_RÉSSOURCES_INSUFFISANTES
    • STORAGE_PROTOCOL_STATUS_NOT_SUPPORTED
  • IOCTL_STORAGE_QUERY_PROPERTY : utilisez cette iocTL avec la structure STORAGE_PROPERTY_QUERY pour récupérer les informations de l’appareil. Pour plus d’informations, consultez requêtes spécifiques au protocole et requêtes Temperature.

  • STORAGE_PROPERTY_QUERY : cette structure inclut les champs PropertyId et AdditionalParameters pour spécifier les données à interroger. Dans le propertyId déposé, utilisez l’énumération STORAGE_PROPERTY_ID pour spécifier le type de données. Utilisez le champ AdditionalParameters pour spécifier plus de détails, en fonction du type de données. Pour les données spécifiques au protocole, utilisez la structure STORAGE_PROTOCOL_SPECIFIC_DATA dans le champ AdditionalParameters . Pour les données de température, utilisez la structure STORAGE_TEMPERATURE_INFO dans le champ AdditionalParameters .

  • STORAGE_PROPERTY_ID : cette énumération inclut de nouvelles valeurs qui permettent IOCTL_STORAGE_QUERY_PROPERTY de récupérer des informations de température et spécifiques au protocole.

    • StorageAdapterProtocolSpecificProperty : Si ProtocolType = ProtocolTypeNvme et DataType = NVMeDataTypeLogPage, les appelants doivent demander des blocs de données de 512 octets.

    • StorageDeviceProtocolSpecificProperty

      Utilisez l’un de ces ID de propriété spécifiques au protocole en combinaison avec STORAGE_PROTOCOL_SPECIFIC_DATA pour récupérer des données spécifiques au protocole dans la structure STORAGE_PROTOCOL_DATA_DESCRIPTOR .

      • StorageAdapterTemperatureProperty
      • StorageDeviceTemperatureProperty

      Utilisez l’un de ces ID de propriété de température pour récupérer les données de température dans la structure STORAGE_TEMPERATURE_DATA_DESCRIPTOR .

  • STORAGE_PROTOCOL_SPECIFIC_DATA : récupérez les données spécifiques à NVMe lorsque cette structure est utilisée pour le champ AdditionalParameters de STORAGE_PROPERTY_QUERY et qu’une valeur d’énumération STORAGE_PROTOCOL_NVME_DATA_TYPE est spécifiée. Utilisez l’une des valeurs STORAGE_PROTOCOL_NVME_DATA_TYPE suivantes dans le champ DataType de la structure STORAGE_PROTOCOL_SPECIFIC_DATA :

    • Utilisez NVMeDataTypeIdentify pour obtenir les données identifier le contrôleur ou identifier les données d’espace de noms.
    • Utilisez NVMeDataTypeLogPage pour obtenir des pages de journal (y compris les données SMART/health).
    • Utilisez NVMeDataTypeFeature pour obtenir des fonctionnalités du lecteur NVMe.
  • STORAGE_TEMPERATURE_INFO : cette structure est utilisée pour contenir des données de température spécifiques. Il est utilisé dans la STORAGE_TEMERATURE_DATA_DESCRIPTOR pour retourner les résultats d’une requête de température.

  • IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD : utilisez ce IOCTL avec la structure de STORAGE_TEMPERATURE_THRESHOLD pour définir des seuils de température. Pour plus d’informations, consultez Commandes de modification du comportement.

  • STORAGE_TEMPERATURE_THRESHOLD : cette structure est utilisée comme mémoire tampon d’entrée pour spécifier le seuil de température. Le champ OverThreshold (booléen) spécifie si le champ Seuil est la valeur de seuil supérieure ou non (sinon, il s’agit de la valeur de seuil inférieure).

Mécanisme pass-through

Les commandes qui ne sont pas définies dans la spécification NVMe sont les plus difficiles à gérer par le système d’exploitation hôte : l’hôte n’a aucun aperçu des effets que les commandes peuvent avoir sur l’appareil cible, l’infrastructure exposée (espaces de noms/tailles de bloc) et son comportement.

Pour mieux transmettre ces commandes spécifiques à l'appareil à travers le stack de stockage Windows, un nouveau mécanisme de passage direct permet aux commandes spécifiques au fournisseur d’être transférées. Ce tuyau de passage aidera également au développement d'outils de gestion et de tests. Toutefois, ce mécanisme pass-through nécessite l’utilisation du journal des effets de commande. En outre, StoreNVMe.sys nécessite que toutes les commandes, pas seulement les commandes de passage, soient décrites dans le journal des effets de commande.

Important

StorNVMe.sys et Storport.sys bloquent toute commande à un appareil s’il n’est pas décrit dans le journal des effets de commande.

Soutien pour le journal des effets de commande

Le journal des effets de commande (comme décrit dans les commandes prises en charge et les effets, section 5.10.1.5 de la spécification NVMe 1.2) permet de décrire les effets des commandes spécifiques au fournisseur, ainsi que des commandes définies par la spécification. Cela facilite la validation de la prise en charge des commandes ainsi que l’optimisation du comportement des commandes, et doit donc être implémentée pour l’ensemble des commandes prises en charge par l’appareil. Les conditions suivantes décrivent le résultat sur la façon dont la commande est envoyée en fonction de son entrée de journal des effets de commande.

Pour toute commande spécifique décrite dans le journal des effets de commande...

Alors que :

  • La commande prise en charge (CSUPP) est définie sur « 1 » indiquant que la commande est prise en charge par le contrôleur (Bit 01)

    Remarque

    Lorsque CSUPP est défini sur « 0 » (indiquant que la commande n’est pas prise en charge), la commande est bloquée

Et si l’un des éléments suivants est défini :

  • Changement de capacité du contrôleur (CCC) est défini sur « 1 » indiquant que la commande peut modifier les capacités du contrôleur (Bit 04)
  • Le Namespace Inventory Change (NIC) est défini sur ‘1’ pour indiquer que la commande peut modifier le nombre ou les capacités de plusieurs espaces de noms (Bit 03).
  • Namespace Capability Change (NCC) est défini sur « 1 », indiquant que la commande peut modifier les capacités d’un espace de noms unique (Bit 02)
  • La soumission et l’exécution de commandes (CSE) sont définies sur 001b ou 010b, ce qui signifie que la commande peut être envoyée lorsqu’il n’y a pas d’autre commande en attente dans le même espace de noms, et qu’une autre commande ne doit pas être soumise au même espace de noms ou à tout espace de noms tant que cette commande n’est pas terminée (Bits 18:16)

Ensuite , la commande est envoyée en tant que seule commande en attente à l’adaptateur.

Sinon si

  • La soumission et l’exécution de commandes (CSE) sont définies sur 001b, ce qui signifie que la commande peut être envoyée lorsqu’il n’existe aucune autre commande en attente dans le même espace de noms et qu’une autre commande ne doit pas être envoyée au même espace de noms tant que cette commande n’est pas terminée (Bits 18:16)

Ensuite , la commande est envoyée en tant que seule commande en attente à l’objet Numéro d’unité logique (LUN).

Sinon, la commande est envoyée avec d'autres commandes en cours sans restriction. Par exemple, si une commande spécifique au fournisseur est envoyée à l’appareil pour récupérer des informations statistiques qui ne sont pas définies par des spécifications, il ne doit pas y avoir de risque pour modifier le comportement ou la capacité de l’appareil d’exécuter des commandes d’E/S. Ces demandes pourraient être traitées en parallèle avec les E/S, et une pause et reprise ne seraient pas nécessaires.

Utilisation de IOCTL_STORAGE_PROTOCOL_COMMAND pour envoyer des commandes

La transmission directe peut être effectuée à l’aide de l’IOCTL_STORAGE_PROTOCOL_COMMAND, introduite dans Windows 10. Ce IOCTL a été conçu pour avoir un comportement similaire à celui des IOCTLs SCSI et ATA existants, afin d’envoyer une commande incorporée à l’appareil cible. Par le biais de cette IOCTL, un pass-through peut être envoyé à un dispositif de stockage, y compris un lecteur NVMe.

Par exemple, dans NVMe, le IOCTL autorise l’envoi des codes de commande suivants.

  • Commandes d’administration spécifiques au fournisseur (C0h – FFh)
  • Commandes NVMe spécifiques au fournisseur (80h – FFh)

Comme pour les autres IOCTL, utilisez DeviceIoControl pour envoyer l'IOCTL de passage direct. Le IOCTL est rempli à l’aide de la structure de tampon d'entrée STORAGE_PROTOCOL_COMMAND qui se trouve dans ntddstor.h. Remplissez le champ Commande avec la commande spécifique au fournisseur.

typedef struct _STORAGE_PROTOCOL_COMMAND {

    ULONG   Version;                        // STORAGE_PROTOCOL_STRUCTURE_VERSION
    ULONG   Length;                         // sizeof(STORAGE_PROTOCOL_COMMAND)

    STORAGE_PROTOCOL_TYPE  ProtocolType;
    ULONG   Flags;                          // Flags for the request

    ULONG   ReturnStatus;                   // return value
    ULONG   ErrorCode;                      // return value, optional

    ULONG   CommandLength;                  // non-zero value should be set by caller
    ULONG   ErrorInfoLength;                // optional, can be zero
    ULONG   DataToDeviceTransferLength;     // optional, can be zero. Used by WRITE type of request.
    ULONG   DataFromDeviceTransferLength;   // optional, can be zero. Used by READ type of request.

    ULONG   TimeOutValue;                   // in unit of seconds

    ULONG   ErrorInfoOffset;                // offsets need to be pointer aligned
    ULONG   DataToDeviceBufferOffset;       // offsets need to be pointer aligned
    ULONG   DataFromDeviceBufferOffset;     // offsets need to be pointer aligned

    ULONG   CommandSpecific;                // optional information passed along with Command.
    ULONG   Reserved0;

    ULONG   FixedProtocolReturnData;        // return data, optional. Some protocol, such as NVMe, may return a small amount data (DWORD0 from completion queue entry) without the need of separate device data transfer.
    ULONG   Reserved1[3];

    _Field_size_bytes_full_(CommandLength) UCHAR Command[ANYSIZE_ARRAY];

} STORAGE_PROTOCOL_COMMAND, *PSTORAGE_PROTOCOL_COMMAND;

La commande spécifique du fournisseur à envoyer doit être renseignée dans le champ mis en surbrillance ci-dessus. Notez à nouveau que le journal des effets de commande doit être implémenté pour les commandes directes. En particulier, ces commandes doivent être signalées comme prises en charge dans le journal des effets de commande (consultez la section précédente pour plus d’informations). Notez également que les champs PRP sont spécifiques au pilote, de sorte que les applications qui envoient des commandes peuvent les laisser comme 0.

Enfin, cet IOCTL de passage est destiné à envoyer des commandes spécifiques au fabricant. Pour envoyer d'autres commandes administrateur NVMe ou non spécifiques à un fournisseur, telles que 'Identify', cette IOCTL de passage direct ne doit pas être utilisée. Par exemple, IOCTL_STORAGE_QUERY_PROPERTY doit être utilisé pour identifier ou obtenir des pages de journal. Pour plus d’informations, consultez la section suivante, requêtes spécifiques au protocole.

Ne mettez pas à jour le microprogramme via le mécanisme pass-through

Les commandes de téléchargement et d’activation du microprogramme ne doivent pas être envoyées à l’aide d’une transmission directe. IOCTL_STORAGE_PROTOCOL_COMMAND ne doit être utilisé que pour les commandes spécifiques au fournisseur.

Utilisez plutôt les IOCTL de stockage généraux suivants (introduits dans Windows 10) pour empêcher les applications d'utiliser directement la version SCSI_miniport de l'IOCTL du microprogramme. Les pilotes de stockage traduisent le IOCTL en commande SCSI ou la version SCSI_miniport du IOCTL vers le miniport.

Ces IOCTL sont recommandés pour développer des outils de mise à niveau de microprogramme dans Windows 10 et Windows Server 2016 :

Pour obtenir des informations de stockage et mettre à jour le microprogramme, Windows prend également en charge les applets de commande PowerShell pour ce faire rapidement :

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

Retour d’erreurs par le biais du mécanisme pass-through

Comme pour les IOCTL pass-through SCSI et ATA, lorsqu’une commande/demande est envoyée au miniport ou au périphérique, l'IOCTL indique s'il a réussi ou non. Dans la structure STORAGE_PROTOCOL_COMMAND , le IOCTL retourne l’état par le biais du champ ReturnStatus .

Exemple : envoi d’une commande spécifique au fournisseur

Dans cet exemple, une commande arbitraire spécifique au fournisseur (0xFF) est envoyée via un lecteur NVMe. Le code suivant alloue une mémoire tampon, initialise une requête, puis envoie la commande à l’appareil via DeviceIoControl.

ZeroMemory(buffer, bufferLength);  
protocolCommand = (PSTORAGE_PROTOCOL_COMMAND)buffer;  

protocolCommand->Version = STORAGE_PROTOCOL_STRUCTURE_VERSION;  
protocolCommand->Length = sizeof(STORAGE_PROTOCOL_COMMAND);  
protocolCommand->ProtocolType = ProtocolTypeNvme;  
protocolCommand->Flags = STORAGE_PROTOCOL_COMMAND_FLAG_ADAPTER_REQUEST;  
protocolCommand->CommandLength = STORAGE_PROTOCOL_COMMAND_LENGTH_NVME;  
protocolCommand->ErrorInfoLength = sizeof(NVME_ERROR_INFO_LOG);  
protocolCommand->DataFromDeviceTransferLength = 4096;  
protocolCommand->TimeOutValue = 10;  
protocolCommand->ErrorInfoOffset = FIELD_OFFSET(STORAGE_PROTOCOL_COMMAND, Command) + STORAGE_PROTOCOL_COMMAND_LENGTH_NVME;  
protocolCommand->DataFromDeviceBufferOffset = protocolCommand->ErrorInfoOffset + protocolCommand->ErrorInfoLength;  
protocolCommand->CommandSpecific = STORAGE_PROTOCOL_SPECIFIC_NVME_ADMIN_COMMAND;  

command = (PNVME_COMMAND)protocolCommand->Command;  

command->CDW0.OPC = 0xFF;  
command->u.GENERAL.CDW10 = 0xto_fill_in;  
command->u.GENERAL.CDW12 = 0xto_fill_in;  
command->u.GENERAL.CDW13 = 0xto_fill_in;  

//  
// Send request down.  
//  
result = DeviceIoControl(DeviceList[DeviceIndex].Handle,  
                            IOCTL_STORAGE_PROTOCOL_COMMAND,  
                            buffer,  
                            bufferLength,  
                            buffer,  
                            bufferLength,  
                            &returnedLength,  
                            NULL 
                            );  

Dans cet exemple, nous nous attendons à protocolCommand->ReturnStatus == STORAGE_PROTOCOL_STATUS_SUCCESS ce que la commande réussisse pour l’appareil.

Requêtes spécifiques au protocole

Windows 8.1 a introduit IOCTL_STORAGE_QUERY_PROPERTY pour la récupération des données. Dans Windows 10, l'IOCTL a été amélioré pour prendre en charge les fonctionnalités NVMe couramment demandées, telles que Obtenir des Pages de Journal, Obtenir des Fonctionnalités et Identifier. Cela permet la récupération d’informations spécifiques NVMe à des fins de surveillance et d’inventaire.

La mémoire tampon d’entrée pour le IOCTL, STORAGE_PROPERTY_QUERY (à partir de Windows 10 et versions ultérieures) s’affiche ici :

typedef struct _STORAGE_PROPERTY_QUERY {
    STORAGE_PROPERTY_ID PropertyId;
    STORAGE_QUERY_TYPE QueryType;
    UCHAR  AdditionalParameters[1];
} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;

Lorsque vous utilisez IOCTL_STORAGE_QUERY_PROPERTY pour récupérer des informations spécifiques au protocole NVMe dans le STORAGE_PROTOCOL_DATA_DESCRIPTOR, configurez la structure STORAGE_PROPERTY_QUERY comme suit :

  • Allouez une mémoire tampon qui peut contenir à la fois une STORAGE_PROPERTY_QUERY et une structure STORAGE_PROTOCOL_SPECIFIC_DATA.
  • Définissez le champ PropertyID sur StorageAdapterProtocolSpecificProperty ou StorageDeviceProtocolSpecificProperty pour une requête de contrôleur ou d’espace de noms, respectivement.
  • Définissez le champ QueryType sur PropertyStandardQuery.
  • Remplissez la structure STORAGE_PROTOCOL_SPECIFIC_DATA avec les valeurs souhaitées. Le début de la STORAGE_PROTOCOL_SPECIFIC_DATA est le champ AdditionalParameters de STORAGE_PROPERTY_QUERY.

La structure STORAGE_PROTOCOL_SPECIFIC_DATA (à partir de Windows 10 et versions ultérieures) s’affiche ici :

typedef struct _STORAGE_PROTOCOL_SPECIFIC_DATA {

    STORAGE_PROTOCOL_TYPE ProtocolType;
    ULONG   DataType;                 

    ULONG   ProtocolDataRequestValue;
    ULONG   ProtocolDataRequestSubValue;

    ULONG   ProtocolDataOffset;         
    ULONG   ProtocolDataLength;

    ULONG   FixedProtocolReturnData;   
    ULONG   Reserved[3];

} STORAGE_PROTOCOL_SPECIFIC_DATA, *PSTORAGE_PROTOCOL_SPECIFIC_DATA;

Pour spécifier un type d’informations spécifiques au protocole NVMe, configurez la structure STORAGE_PROTOCOL_SPECIFIC_DATA comme suit :

  • Définissez le champ ProtocolType sur ProtocolTypeNVMe.
  • Définissez le champ DataType sur une valeur d’énumération définie par STORAGE_PROTOCOL_NVME_DATA_TYPE:
    • Utilisez NVMeDataTypeIdentify pour obtenir les données identifier le contrôleur ou identifier les données d’espace de noms.
    • Utilisez NVMeDataTypeLogPage pour obtenir des pages de journal (y compris les données SMART/health).
    • Utilisez NVMeDataTypeFeature pour obtenir des fonctionnalités du lecteur NVMe.

Lorsque ProtocolTypeNVMe est utilisé comme ProtocolType, les requêtes pour des informations spécifiques au protocole peuvent être récupérées en parallèle avec d’autres E/S sur le lecteur NVMe.

Important

Pour un IOCTL_STORAGE_QUERY_PROPERTY qui utilise un STORAGE_PROPERTY_ID de StorageAdapterProtocolSpecificProperty, et dont la structure STORAGE_PROTOCOL_SPECIFIC_DATA ou STORAGE_PROTOCOL_SPECIFIC_DATA_EXT est définie à ProtocolType=ProtocolTypeNvme et DataType=NVMeDataTypeLogPage, définissez le membre ProtocolDataLength de cette même structure à une valeur minimale de 512 (octets).

Les exemples suivants illustrent les requêtes spécifiques au protocole NVMe.

Exemple : requête NVMe Identifier

Dans cet exemple, la requête Identifier est envoyée à un lecteur NVMe. Le code suivant initialise la structure de données de requête, puis envoie la commande à l’appareil via DeviceIoControl.

BOOL    result;
PVOID   buffer = NULL;
ULONG   bufferLength = 0;
ULONG   returnedLength = 0;

PSTORAGE_PROPERTY_QUERY query = NULL;
PSTORAGE_PROTOCOL_SPECIFIC_DATA protocolData = NULL;
PSTORAGE_PROTOCOL_DATA_DESCRIPTOR protocolDataDescr = NULL;

//
// Allocate buffer for use.
//
bufferLength = FIELD_OFFSET(STORAGE_PROPERTY_QUERY, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE;
buffer = malloc(bufferLength);

if (buffer == NULL) {
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: allocate buffer failed, exit.\n"));
    goto exit;
}

//
// Initialize query data structure to get Identify Controller Data.
//
ZeroMemory(buffer, bufferLength);

query = (PSTORAGE_PROPERTY_QUERY)buffer;
protocolDataDescr = (PSTORAGE_PROTOCOL_DATA_DESCRIPTOR)buffer;
protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;

query->PropertyId = StorageAdapterProtocolSpecificProperty;
query->QueryType = PropertyStandardQuery;

protocolData->ProtocolType = ProtocolTypeNvme;
protocolData->DataType = NVMeDataTypeIdentify;
protocolData->ProtocolDataRequestValue = NVME_IDENTIFY_CNS_CONTROLLER;
protocolData->ProtocolDataRequestSubValue = 0;
protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);
protocolData->ProtocolDataLength = NVME_MAX_LOG_SIZE;

//
// Send request down.
//
result = DeviceIoControl(DeviceList[Index].Handle,
                            IOCTL_STORAGE_QUERY_PROPERTY,
                            buffer,
                            bufferLength,
                            buffer,
                            bufferLength,
                            &returnedLength,
                            NULL
                            );

ZeroMemory(buffer, bufferLength);
query = (PSTORAGE_PROPERTY_QUERY)buffer;  
protocolDataDescr = (PSTORAGE_PROTOCOL_DATA_DESCRIPTOR)buffer;  
protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;  

query->PropertyId = StorageDeviceProtocolSpecificProperty;  
query->QueryType = PropertyStandardQuery;  

protocolData->ProtocolType = ProtocolTypeNvme;  
protocolData->DataType = NVMeDataTypeLogPage;  
protocolData->ProtocolDataRequestValue = NVME_LOG_PAGE_HEALTH_INFO;  
protocolData->ProtocolDataRequestSubValue = 0;  
protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);  
protocolData->ProtocolDataLength = sizeof(NVME_HEALTH_INFO_LOG);  

//  
// Send request down.  
//  
result = DeviceIoControl(DeviceList[Index].Handle,  
                            IOCTL_STORAGE_QUERY_PROPERTY,  
                            buffer,  
                            bufferLength,  
                            buffer, 
                            bufferLength,  
                            &returnedLength,  
                            NULL  
                            );  

//
// Validate the returned data.
//
if ((protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)) ||
    (protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR))) {
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: Get Identify Controller Data - data descriptor header not valid.\n"));
    return;
}

protocolData = &protocolDataDescr->ProtocolSpecificData;

if ((protocolData->ProtocolDataOffset < sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA)) ||
    (protocolData->ProtocolDataLength < NVME_MAX_LOG_SIZE)) {
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: Get Identify Controller Data - ProtocolData Offset/Length not valid.\n"));
    goto exit;
}

//
// Identify Controller Data 
//
{
    PNVME_IDENTIFY_CONTROLLER_DATA identifyControllerData = (PNVME_IDENTIFY_CONTROLLER_DATA)((PCHAR)protocolData + protocolData->ProtocolDataOffset);

    if ((identifyControllerData->VID == 0) ||
        (identifyControllerData->NN == 0)) {
        _tprintf(_T("DeviceNVMeQueryProtocolDataTest: Identify Controller Data not valid.\n"));
        goto exit;
    } else {
        _tprintf(_T("DeviceNVMeQueryProtocolDataTest: ***Identify Controller Data succeeded***.\n"));
    }
}

Important

Pour un IOCTL_STORAGE_QUERY_PROPERTY qui utilise un STORAGE_PROPERTY_ID de StorageAdapterProtocolSpecificProperty, et dont la structure STORAGE_PROTOCOL_SPECIFIC_DATA ou STORAGE_PROTOCOL_SPECIFIC_DATA_EXT est définie à ProtocolType=ProtocolTypeNvme et DataType=NVMeDataTypeLogPage, définissez le membre ProtocolDataLength de cette même structure à une valeur minimale de 512 (octets).

Notez que l’appelant doit allouer une mémoire tampon unique contenant STORAGE_PROPERTY_QUERY et la taille de STORAGE_PROTOCOL_SPECIFIC_DATA. Dans cet exemple, il utilise la même mémoire tampon pour l’entrée et la sortie de la requête de propriété. C’est pourquoi la mémoire tampon allouée a une taille de « FIELD_OFFSET(STORAGE_PROPERTY_QUERY, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE ». Bien que des mémoires tampons distinctes puissent être allouées pour l’entrée et la sortie, nous vous recommandons d’utiliser une mémoire tampon unique pour interroger les informations relatives à NVMe.

identifierControllerData->NN est le nombre d’espaces de noms (NN). Windows détecte un namespace comme lecteur physique.

Exemple : requête Get Log Pages NVMe

Dans cet exemple, en fonction de la précédente, la demande Get Log Pages est envoyée à un lecteur NVMe. Le code suivant prépare la structure des données de requête, puis envoie la commande à l’appareil via DeviceIoControl.

ZeroMemory(buffer, bufferLength);  

query = (PSTORAGE_PROPERTY_QUERY)buffer;  
protocolDataDescr = (PSTORAGE_PROTOCOL_DATA_DESCRIPTOR)buffer;  
protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;  

query->PropertyId = StorageDeviceProtocolSpecificProperty;  
query->QueryType = PropertyStandardQuery;  

protocolData->ProtocolType = ProtocolTypeNvme;  
protocolData->DataType = NVMeDataTypeLogPage;  
protocolData->ProtocolDataRequestValue = NVME_LOG_PAGE_HEALTH_INFO;  
protocolData->ProtocolDataRequestSubValue = 0;  // This will be passed as the lower 32 bit of log page offset if controller supports extended data for the Get Log Page.
protocolData->ProtocolDataRequestSubValue2 = 0; // This will be passed as the higher 32 bit of log page offset if controller supports extended data for the Get Log Page.
protocolData->ProtocolDataRequestSubValue3 = 0; // This will be passed as Log Specific Identifier in CDW11.
protocolData->ProtocolDataRequestSubValue4 = 0; // This will map to STORAGE_PROTOCOL_DATA_SUBVALUE_GET_LOG_PAGE definition, then user can pass Retain Asynchronous Event, Log Specific Field.

protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);  
protocolData->ProtocolDataLength = sizeof(NVME_HEALTH_INFO_LOG);  

//  
// Send request down.  
//  
result = DeviceIoControl(DeviceList[Index].Handle,  
                            IOCTL_STORAGE_QUERY_PROPERTY,  
                            buffer,  
                            bufferLength,  
                            buffer, 
                            bufferLength,  
                            &returnedLength,  
                            NULL  
                            );  

if (!result || (returnedLength == 0)) {
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log failed. Error Code %d.\n"), GetLastError());
    goto exit;
}

//
// Validate the returned data.
//
if ((protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)) ||
    (protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR))) {
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log - data descriptor header not valid.\n"));
    return;
}

protocolData = &protocolDataDescr->ProtocolSpecificData;

if ((protocolData->ProtocolDataOffset < sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA)) ||
    (protocolData->ProtocolDataLength < sizeof(NVME_HEALTH_INFO_LOG))) {
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log - ProtocolData Offset/Length not valid.\n"));
    goto exit;
}

//
// SMART/Health Information Log Data 
//
{
    PNVME_HEALTH_INFO_LOG smartInfo = (PNVME_HEALTH_INFO_LOG)((PCHAR)protocolData + protocolData->ProtocolDataOffset);

    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log Data - Temperature %d.\n"), ((ULONG)smartInfo->Temperature[1] << 8 | smartInfo->Temperature[0]) - 273);

    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: ***SMART/Health Information Log succeeded***.\n"));
}

Les appelants peuvent utiliser un STORAGE_PROPERTY_ID de StorageAdapterProtocolSpecificProperty et dont la structure STORAGE_PROTOCOL_SPECIFIC_DATA ou STORAGE_PROTOCOL_SPECIFIC_DATA_EXT est définie pour ProtocolDataRequestValue=VENDOR_SPECIFIC_LOG_PAGE_IDENTIFIER demander 512 octets de données spécifiques au fournisseur.

Exemple : requête NvMe Get Features

Dans cet exemple, en fonction de la précédente, la demande Get Features est envoyée à un lecteur NVMe. Le code suivant prépare la structure des données de requête, puis envoie la commande à l’appareil via DeviceIoControl.

//  
// Initialize query data structure to Volatile Cache feature.  
//  

ZeroMemory(buffer, bufferLength);  


query = (PSTORAGE_PROPERTY_QUERY)buffer;  
protocolDataDescr = (PSTORAGE_PROTOCOL_DATA_DESCRIPTOR)buffer;  
protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;  

query->PropertyId = StorageDeviceProtocolSpecificProperty;  
query->QueryType = PropertyStandardQuery;  

protocolData->ProtocolType = ProtocolTypeNvme;  
protocolData->DataType = NVMeDataTypeFeature;  
protocolData->ProtocolDataRequestValue = NVME_FEATURE_VOLATILE_WRITE_CACHE;  
protocolData->ProtocolDataRequestSubValue = 0;  
protocolData->ProtocolDataOffset = 0;  
protocolData->ProtocolDataLength = 0;  

//  
// Send request down.  
//  

result = DeviceIoControl(DeviceList[Index].Handle,  
                            IOCTL_STORAGE_QUERY_PROPERTY,  
                            buffer,  
                            bufferLength,  
                            buffer,  
                            bufferLength,  
                            &returnedLength,  
                            NULL  
                            );  

if (!result || (returnedLength == 0)) {  
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: Get Feature - Volatile Cache failed. Error Code %d.\n"), GetLastError());  
    goto exit;  
}  

//  
// Validate the returned data.  
//  

if ((protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)) ||  
    (protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR))) {  
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: Get Feature - Volatile Cache  - data descriptor header not valid.\n"));  
    return;                                           
}  

//
// Volatile Cache 
//
{
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: Get Feature - Volatile Cache - %x.\n"), protocolDataDescr->ProtocolSpecificData.FixedProtocolReturnData);

    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: ***Get Feature - Volatile Cache succeeded***.\n"));
}

Ensemble spécifique au protocole

Notez que dans Windows 10 19H1 et versions ultérieures, le IOCTL_STORAGE_SET_PROPERTY a été amélioré pour prendre en charge les fonctionnalités nvMe Set.

La mémoire tampon d’entrée de l’IOCTL_STORAGE_SET_PROPERTY est indiquée ici :

typedef struct _STORAGE_PROPERTY_SET {

    //
    // ID of the property being retrieved
    //

    STORAGE_PROPERTY_ID PropertyId;

    //
    // Flags indicating the type of set property being performed
    //

    STORAGE_SET_TYPE SetType;

    //
    // Space for additional parameters if necessary
    //

    UCHAR AdditionalParameters[1];

} STORAGE_PROPERTY_SET, *PSTORAGE_PROPERTY_SET;

Lorsque vous utilisez IOCTL_STORAGE_SET_PROPERTY pour définir la fonctionnalité NVMe, configurez la structure STORAGE_PROPERTY_SET comme suit :

  • Allouez une mémoire tampon qui peut contenir à la fois un STORAGE_PROPERTY_SET et une STORAGE_PROTOCOL_SPECIFIC_DATA_EXT structure;
  • Définissez le champ PropertyID sur StorageAdapterProtocolSpecificProperty ou StorageDeviceProtocolSpecificProperty pour une requête de contrôleur ou d’espace de noms, respectivement.
  • Remplissez la structure STORAGE_PROTOCOL_SPECIFIC_DATA_EXT avec les valeurs souhaitées. Le point de départ de la STORAGE_PROTOCOL_SPECIFIC_DATA_EXT est le champ AdditionalParameters de STORAGE_PROPERTY_SET.

La structure STORAGE_PROTOCOL_SPECIFIC_DATA_EXT est illustrée ici.

typedef struct _STORAGE_PROTOCOL_SPECIFIC_DATA_EXT {

    STORAGE_PROTOCOL_TYPE ProtocolType;
    ULONG   DataType;                   // The value will be protocol specific, as defined in STORAGE_PROTOCOL_NVME_DATA_TYPE or STORAGE_PROTOCOL_ATA_DATA_TYPE.

    ULONG   ProtocolDataValue;
    ULONG   ProtocolDataSubValue;      // Data sub request value

    ULONG   ProtocolDataOffset;         // The offset of data buffer is from beginning of this data structure.
    ULONG   ProtocolDataLength;

    ULONG   FixedProtocolReturnData;
    ULONG   ProtocolDataSubValue2;     // First additional data sub request value

    ULONG   ProtocolDataSubValue3;     // Second additional data sub request value
    ULONG   ProtocolDataSubValue4;     // Third additional data sub request value

    ULONG   ProtocolDataSubValue5;     // Fourth additional data sub request value
    ULONG   Reserved[5];
} STORAGE_PROTOCOL_SPECIFIC_DATA_EXT, *PSTORAGE_PROTOCOL_SPECIFIC_DATA_EXT;

Pour spécifier un type de fonctionnalité NVMe à définir, configurez la structure STORAGE_PROTOCOL_SPECIFIC_DATA_EXT comme suit :

  • Définissez le champ ProtocolType sur ProtocolTypeNvme ;
  • Définissez le champ DataType sur la valeur d’énumération NVMeDataTypeFeature définie par STORAGE_PROTOCOL_NVME_DATA_TYPE ;

Les exemples suivants illustrent l’ensemble de fonctionnalités NVMe.

Exemple : Fonctionnalités NVMe Set

Dans cet exemple, la demande Set Features est envoyée à un lecteur NVMe. Le code suivant prépare la structure de données de l'ensemble, puis envoie la commande au périphérique via DeviceIoControl.

PSTORAGE_PROPERTY_SET                   setProperty = NULL;
PSTORAGE_PROTOCOL_SPECIFIC_DATA_EXT     protocolData = NULL;
PSTORAGE_PROTOCOL_DATA_DESCRIPTOR_EXT   protocolDataDescr = NULL;

//
// Allocate buffer for use.
//
bufferLength = FIELD_OFFSET(STORAGE_PROPERTY_SET, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA_EXT);
bufferLength += NVME_MAX_LOG_SIZE;

buffer = new UCHAR[bufferLength];

//
// Initialize query data structure to get the desired log page.
//
ZeroMemory(buffer, bufferLength);

setProperty = (PSTORAGE_PROPERTY_SET)buffer;

setProperty->PropertyId = StorageAdapterProtocolSpecificProperty;
setProperty->SetType = PropertyStandardSet;

protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA_EXT)setProperty->AdditionalParameters;

protocolData->ProtocolType = ProtocolTypeNvme;
protocolData->DataType = NVMeDataTypeFeature;
protocolData->ProtocolDataValue = NVME_FEATURE_HOST_CONTROLLED_THERMAL_MANAGEMENT;

protocolData->ProtocolDataSubValue = 0; // This will pass to CDW11.
protocolData->ProtocolDataSubValue2 = 0; // This will pass to CDW12.
protocolData->ProtocolDataSubValue3 = 0; // This will pass to CDW13.
protocolData->ProtocolDataSubValue4 = 0; // This will pass to CDW14.
protocolData->ProtocolDataSubValue5 = 0; // This will pass to CDW15.

protocolData->ProtocolDataOffset = 0;
protocolData->ProtocolDataLength = 0;

//
// Send request down.
//
result = DeviceIoControl(m_deviceHandle,
                            IOCTL_STORAGE_SET_PROPERTY,
                            buffer,
                            bufferLength,
                            buffer,
                            bufferLength,
                            &returnedLength,
                            NULL
);

Requêtes de température

Dans Windows 10 et versions ultérieures, IOCTL_STORAGE_QUERY_PROPERTY pouvez également être utilisé pour interroger des données de température à partir d’appareils NVMe.

Pour récupérer des informations de température à partir d’un lecteur NVMe dans le STORAGE_TEMPERATURE_DATA_DESCRIPTOR, configurez la structure STORAGE_PROPERTY_QUERY comme suit :

  • Allouez une mémoire tampon qui peut contenir une structure STORAGE_PROPERTY_QUERY .
  • Définissez le champ PropertyID sur StorageAdapterTemperatureProperty ou StorageDeviceTemperatureProperty pour une requête de contrôleur ou d’espace de noms, respectivement.
  • Définissez le champ QueryType sur PropertyStandardQuery.

La structure STORAGE_TEMPERATURE_INFO (disponible dans Windows 10 et versions ultérieures) s’affiche ici :

typedef struct _STORAGE_TEMPERATURE_INFO {

    USHORT  Index;                      // Starts from 0. Index 0 may indicate a composite value.
    SHORT   Temperature;                // Signed value; in Celsius.
    SHORT   OverThreshold;              // Signed value; in Celsius.
    SHORT   UnderThreshold;             // Signed value; in Celsius.

    BOOLEAN OverThresholdChangable;     // Can the threshold value being changed by using IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD.
    BOOLEAN UnderThresholdChangable;    // Can the threshold value being changed by using IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD.
    BOOLEAN EventGenerated;             // Indicates that notification will be generated when temperature cross threshold.
    UCHAR   Reserved0;
    ULONG   Reserved1;

} STORAGE_TEMPERATURE_INFO, *PSTORAGE_TEMPERATURE_INFO;

Commandes de modification du comportement

Les commandes qui manipulent des attributs d’appareil ou qui peuvent avoir un impact sur le comportement de l’appareil sont plus difficiles à gérer pour le système d’exploitation. Si les attributs d’appareil changent au moment de l’exécution pendant que les E/S sont traitées, les problèmes de synchronisation ou d’intégrité des données peuvent survenir s’ils ne sont pas gérés correctement.

La commande NVMe Set-Features est un bon exemple de commande de modification du comportement. Il permet de modifier le mécanisme d’arbitrage et la définition des seuils de température. Pour vous assurer que les données en transit ne sont pas exposées au risque lorsque les commandes de configuration affectant le comportement sont envoyées, Windows suspend toutes les E/S sur l’appareil NVMe, vide les files d’attente et les mémoires tampons. Une fois la commande set exécutée avec succès, les E/S sont reprises (si possible). Si les E/S ne peuvent pas être reprise, une réinitialisation de l’appareil peut être nécessaire.

Définition des seuils de température

Windows 10 a introduit IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD, un IOCTL pour obtenir et définir des seuils de température. Vous pouvez également l’utiliser pour obtenir la température actuelle de l’appareil. La mémoire tampon d’entrée/sortie de ce IOCTL est la structure STORAGE_TEMPERATURE_INFO , à partir de la section de code précédente.

Exemple : définition d’une température de dépassement de seuil

Dans cet exemple, la température de dépassement de seuil d’un lecteur NVMe est définie. Le code suivant prépare la commande, puis l’envoie à l’appareil via DeviceIoControl.

BOOL    result;  
ULONG   returnedLength = 0;  

STORAGE_TEMPERATURE_THRESHOLD setThreshold = {0};  

setThreshold.Version = sizeof(STORAGE_TEMPERATURE_THRESHOLD); 
setThreshold.Size = sizeof(STORAGE_TEMPERATURE_THRESHOLD);  
setThreshold.Flags = STORAGE_TEMPERATURE_THRESHOLD_FLAG_ADAPTER_REQUEST;  
setThreshold.Index = SensorIndex;  
setThreshold.Threshold = Threshold;  
setThreshold.OverThreshold = UpdateOverThreshold; 

//  
// Send request down.  
//  
result = DeviceIoControl(DeviceList[DeviceIndex].Handle,  
                            IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD,  
                            &setThreshold,  
                            sizeof(STORAGE_TEMPERATURE_THRESHOLD),  
                            NULL,  
                            0,  
                            &returnedLength,  
                            NULL  
                            );

Définition des fonctionnalités propres au fournisseur

Sans le journal des effets de commande, le pilote n’a aucune connaissance des ramifications de la commande. C’est pourquoi le journal des effets de commande est requis. Il aide le système d’exploitation à déterminer si une commande a un impact élevé et si elle peut être envoyée en parallèle avec d’autres commandes au lecteur.

Le journal des effets de commande n’est pas encore suffisamment granulaire pour englober les commandes Set-Features propres au fournisseur. Pour cette raison, il n’est pas encore possible d’envoyer des commandes Set-Features spécifiques au fournisseur. Toutefois, il est possible d’utiliser le mécanisme pass-through, abordé précédemment, pour envoyer des commandes spécifiques au fournisseur. Pour plus d’informations, consultez le mécanisme de transfert.

Fichiers d’en-tête

Les fichiers suivants sont pertinents pour le développement NVMe. Ces fichiers sont inclus dans le Kit de développement logiciel (SDK) Microsoft Windows.

Fichier d’en-tête Descriptif
ntddstor.h Définit des constantes et des types pour accéder aux pilotes de classe de stockage à partir du mode noyau.
nvme.h Pour les autres structures de données liées à NVMe.
winioctl.h Pour les définitions WIN32 IOCTL globales, notamment les API de stockage pour les applications en mode utilisateur.

NvMe Specification 1.2