Share via


Utilisation de lecteurs NVMe

S’applique à :

  • Windows 10
  • Windows Server 2016

Découvrez comment utiliser des périphériques NVMe à haute vitesse depuis votre application Windows. L’accès aux périphériques 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 périphériques Windows 7 via un correctif de la base de connaissances. 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 contrôles IOCTL existants.

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

API pour l’utilisation de lecteurs NVMe

Vous pouvez utiliser les API à usage général suivantes pour accéder à des lecteurs NVMe dans Windows 10. Ces API se trouvent dans winioctl.h pour les applications en mode utilisateur, et dans 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 ce contrôle IOCTL avec la structure STORAGE_PROTOCOL_COMMAND pour émettre des commandes NVMe. Ce contrôle IOCTL active le pass-through NVMe et prend en charge le journal des effets de commande dans NVMe. Vous pouvez l’utiliser avec des commandes spécifiques au fournisseur. Pour plus d’informations, consultez Mécanisme du pass-through.

  • STORAGE_PROTOCOL_COMMAND : cette structure de mémoire tampon d’entrée comprend 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_INSUFFICIENT_RESOURCES
    • STORAGE_PROTOCOL_STATUS_NOT_SUPPORTED
  • IOCTL_STORAGE_QUERY_PROPERTY : utilisez ce contrôle IOCTL avec la structure STORAGE_PROPERTY_QUERY pour récupérer les informations du périphérique. Pour plus d’informations, consultez Requêtes spécifiques au protocole et Requêtes de température.

  • STORAGE_PROPERTY_QUERY : cette structure inclut les champs PropertyId et AdditionalParameters pour spécifier les données à interroger. Dans le champ PropertyId, 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 spécifiques au protocole et de température.

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

    Utilisez 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 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 des données spécifiques à NVMe quand 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 une des valeurs de STORAGE_PROTOCOL_NVME_DATA_TYPE suivantes dans le champ DataType de la structure STORAGE_PROTOCOL_SPECIFIC_DATA :

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

  • IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD : utilisez ce contrôle IOCTL avec la structure 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 Threshold est la valeur au-dessus du seuil ou non (sinon, c’est la valeur sous le seuil).

Mécanisme de pass-through

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

Pour mieux prendre en charge ces commandes spécifiques au périphérique via la pile de stockage Windows, un nouveau mécanisme de pass-through permet d’envoyer directement des commandes spécifiques au fournisseur. Ce canal de pass-through facilitera également le développement d’outils de gestion et de test. Cependant, ce mécanisme de pass-through nécessite l’utilisation du journal des effets des commandes. En outre, StoreNVMe.sys nécessite que toutes les commandes, et pas seulement les commandes pass-through, soient décrites dans le journal des effets des commandes.

Important

StorNVMe.sys et Storport.sys vont bloquer une commande adressée à un périphérique si elle n’est pas décrite dans le journal des effets des commandes.

 

Prise en charge du journal des effets des commandes

Le journal des effets des commandes (tel que décrit dans « Commands Supported and Effects », section 5.10.1.5 de Spécification de NVMe 1.2) permet de décrire les effets des commandes spécifiques au fournisseur ainsi que les commandes définies par la spécification. Ceci facilite à la fois la validation de la prise en charge des commandes et l’optimisation du comportement des commandes, et doit donc être implémenté pour l’ensemble des commandes prises en charge par le périphérique. Les conditions suivantes décrivent le résultat de l’envoi de la commande en fonction de son entrée dans le journal des effets des commandes.

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

Tant que :

  • Command Supported (CSUPP) est défini sur « 1 », signifiant que la commande est prise en charge par le contrôleur (Bit 01)

    Remarque

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

     

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

  • CCC (Controller Capability Change) est défini sur « 1 », signifiant que la commande peut modifier les capacités du contrôleur (Bit 04)

  • NIC (Namespace Inventory Change) est défini sur « 1 », signifiant que la commande peut modifier le nombre ou les capacités pour plusieurs espaces de noms (Bit 03)

  • NCC (Namespace Capability Change) est défini sur « 1 », signifiant que la commande peut modifier les capacités d’un même espace de noms (Bit 02)

  • CSE (Command Submission and Execution) est défini sur 001b ou 010b, signifiant que la commande peut être envoyée quand il n’existe aucune autre commande en suspens dans le même espace de noms ou dans n’importe quel autre espace de noms, et qu’une autre commande ne doit pas être envoyée au même espace de noms ou à n’importe quel autre espace de noms tant que cette commande n’est pas terminée (Bits 18:16)

Alors, la commande est envoyée comme seule commande en attente à l’adaptateur.

Sinon, si :

  • CSE (Command Submission and Execution) est défini sur 001b, signifiant que la commande peut être envoyée quand il n’existe aucune autre commande en suspens 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)

Alors la commande sera envoyée comme seule commande en attente à l’objet LUN (Logical Unit Number).

Sinon, la commande est envoyée avec d’autres commandes en attente sans interdiction. Par exemple, si une commande spécifique au fournisseur est envoyée au périphérique pour récupérer des informations statistiques qui ne sont pas définies par les spécifications, il ne doit pas y avoir de risque de modifier le comportement ou la capacité du périphérique à exécuter des commandes d’E/S. Ces demandes peuvent être traitée en parallèle aux E/S et aucune pause-reprise n’est nécessaire.

Utilisation de IOCTL_STORAGE_PROTOCOL_COMMAND pour envoyer des commandes

Le pass-through peut être effectué en utilisant IOCTL_STORAGE_PROTOCOL_COMMAND, qui a été introduit dans Windows 10. Ce contrôle IOCTL a été conçu pour avoir un comportement similaire à celui des contrôles IOCTL pass-through SCSI et ATA existants, pour envoyer une commande incorporée au périphérique cible. Via ce contrôle IOCTL, le pass-through peut être envoyé à un périphérique de stockage, y compris à un lecteur NVMe.

Par exemple, dans NVMe, le contrô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 toutes les autres contrôles IOCTL, utilisez DeviceIoControl pour envoyer le contrôle IOCTL pass-through. Le contrôle IOCTL est renseigné en utilisant la structure de mémoire tampon d’entrée STORAGE_PROTOCOL_COMMAND qui se trouve dans ntddstor.h. Renseignez le champ Command 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 au fournisseur que vous souhaitez envoyer doit être renseignée dans le champ en évidence ci-dessus. Notez à nouveau que le journal des effets des commandes doit être implémenté pour les commandes pass-through. En particulier, ces commandes doivent être indiquées comme étant prises en charge dans le journal des effets des commandes (voir la section précédente pour plus d’informations). Notez aussi que les champs PRP sont spécifiques au pilote, de sorte que les applications qui envoient des commandes peuvent les laisser à 0.

Enfin, ce contrôle IOCTL pass-through est destiné à l’envoi de commandes spécifiques au fournisseur. Pour envoyer d’autres commandes NVMe d’administration ou non spécifiques au fournisseur, comme Identifier, ce contrôle IOCTL pass-through ne doit pas être utilisé. Par exemple, IOCTL_STORAGE_QUERY_PROPERTY doit être utilisé pour Identifier et 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 en utilisant le pass-through. IOCTL_STORAGE_PROTOCOL_COMMAND doit être utilisé seulement pour les commandes spécifiques au fournisseur.

Utilisez à la place les IOCTL de stockage généraux suivants (introduits dans Windows 10) pour éviter que les applications utilisent directement la version SCSI_miniport de l’IOCTL du microprogramme. Les pilotes de stockage traduisent le contrôle IOCTL en commande SCSI ou en version SCSI_miniport du contrôle IOCTL pour le miniport.

Ces contrôles IOCTL sont recommandés pour le développement d’outils de mise à niveau du microprogramme dans Windows 10 et Windows Server 2016 :

Pour obtenir des informations sur le stockage et mettre à jour le microprogramme, Windows prend également en charge les applets de commande PowerShell pour effectuer cette opération rapidement :

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

Remarque

Pour mettre à jour le microprogramme sur NVMe dans Windows 8.1, utilisez IOCTL_SCSI_MINIPORT_FIRMWARE. Ce contrôle IOCTL n’a pas été rétroporté sur Windows 7. Pour plus d’informations, consultez Mise à niveau du microprogramme pour un périphérique NVMe dans Windows 8.1.

 

Retourner des erreurs via le mécanisme de pass-through

À l’instar des contrôles IOCTL pass-through SCSI et ATA, quand une commande/demande est envoyée au miniport ou au périphérique, le contrôle IOCTL retourne un code indiquant si elle a réussi ou non. Dans la structure STORAGE_PROTOCOL_COMMAND, le contrôle IOCTL retourne l’état via le 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 pass-through à un lecteur NVMe. Le code suivant alloue une mémoire tampon, initialise une requête, puis envoie la commande au périphérique 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 attendons protocolCommand->ReturnStatus == STORAGE_PROTOCOL_STATUS_SUCCESS si la commande a réussi sur le périphérique.

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, le contrôle IOCTL a été amélioré pour prendre en charge les fonctionnalités NVMe fréquemment demandées, comme Obtenir des pages de journal, Obtenir les fonctionnalités et Identifier. Ceci permet de récupérer des informations spécifiques de NVMe à des fins de surveillance et d’inventaire.

La mémoire tampon d’entrée pour le contrôle IOCTL, STORAGE_PROPERTY_QUERY (à compter de Windows 10) est illustrée ici.

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

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

  • Allouez une mémoire tampon qui peut contenir à la fois une structure STORAGE_PROPERTY_QUERY et une structure STORAGE_PROTOCOL_SPECIFIC_DATA.

  • Définissez le champ PropertyID sur StorageAdapterProtocolSpecificProperty ou StorageDeviceProtocolSpecificProperty pour une demande de contrôleur ou de périphérique/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 STORAGE_PROTOCOL_SPECIFIC_DATA est le champ AdditionalParameters de STORAGE_PROPERTY_QUERY.

La structure STORAGE_PROTOCOL_SPECIFIC_DATA (à compter de Windows 10) est illustrée 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 d’identification du contrôleur ou d’identification de l’espace de noms.
    • Utilisez NVMeDataTypeLogPage pour obtenir des pages de journal (y compris les données SMART/d’intégrité).
    • Utilisez NVMeDataTypeFeature pour obtenir les fonctionnalités du lecteur NVMe.

Quand ProtocolTypeNVMe est utilisé comme ProtocolType, les requêtes pour obtenir 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 une 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 sur ProtocolType=ProtocolTypeNvme et DataType=NVMeDataTypeLogPage, définissez le membre ProtocolDataLength de cette même structure sur une valeur minimale de 512 (octets).

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

Exemple : Requête d’identification NVMe

Dans cet exemple, la demande Identifier est envoyée à un lecteur NVMe. Le code suivant initialise la structure de données de la requête, puis envoie la commande au périphérique 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 une 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 sur ProtocolType=ProtocolTypeNvme et DataType=NVMeDataTypeLogPage, définissez le membre ProtocolDataLength de cette même structure sur une valeur minimale de 512 (octets).

Notez que l’appelant doit allouer une seule mémoire tampon 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 qui a été 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 à la fois pour l’entrée et la sortie, nous vous recommandons d’utiliser une seule mémoire tampon pour interroger les informations relatives à NVMe.

identifyControllerData->NN est le nombre d’espaces de noms (NN). Windows détecte un espace de noms en tant que lecteur physique.

Exemple : Requête Obtenir des pages de journal NVMe

Dans cet exemple, basé sur le précédent, la requête Obtenir des pages de journal est envoyée à un lecteur NVMe. Le code suivant prépare la structure de données de la requête, puis envoie la commande au périphérique 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 ouSTORAGE_PROTOCOL_SPECIFIC_DATA_EXT est définie sur ProtocolDataRequestValue=VENDOR_SPECIFIC_LOG_PAGE_IDENTIFIER pour interroger des blocs de 512 octets de données spécifiques au fournisseur.

Exemple : Requête Obtenir des fonctionnalités NVMe

Dans cet exemple, basé sur le précédent, la demande Obtenir des fonctionnalités est envoyée à un lecteur NVMe. Le code suivant prépare la structure de données de la requête, puis envoie la commande au périphérique 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

À compter de Windows 10 19H1, IOCTL_STORAGE_SET_PROPERTY a été amélioré pour prendre en charge la définition de fonctionnalités NVMe.

La mémoire tampon d’entrée pour IOCTL_STORAGE_SET_PROPERTY est illustré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;

Lors de l’utilisation de 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 une structure STORAGE_PROPERTY_SET et une structure STORAGE_PROTOCOL_SPECIFIC_DATA_EXT.
  • Définissez le champ PropertyID sur StorageAdapterProtocolSpecificProperty ou StorageDeviceProtocolSpecificProperty pour une demande de contrôleur ou de périphérique/espace de noms, respectivement.
  • Remplissez la structure STORAGE_PROTOCOL_SPECIFIC_DATA_EXT avec les valeurs souhaitées. Le début de 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 la définition de fonctionnalités NVMe.

Exemple : Définir des fonctionnalités NVMe

Dans cet exemple, la demande Définir des fonctionnalités est envoyée à un lecteur NVMe. Le code suivant prépare la structure de données de la définition, 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, IOCTL_STORAGE_QUERY_PROPERTY peut aussi être utilisé pour interroger des données de température auprès de périphériques NVMe.

Pour récupérer des informations de température auprès 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 demande de contrôleur ou de périphérique/espace de noms, respectivement.

  • Définissez le champ QueryType sur PropertyStandardQuery.

La structure STORAGE_TEMPERATURE_INFO (à compter de Windows 10) est illustrée 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 changement de comportement

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

La commande NVMe Set-Features est un bon exemple de commande de changement de comportement. Elle permet de changer le mécanisme d’arbitrage et de définir des seuils de température. Pour garantir que les données en cours de traitement ne sont pas exposées à un risque lors de l’envoi de commandes de définition affectant le comportement, Windows met en pause toutes les E/S sur le périphérique NVMe, et vide les files d’attente et les mémoires tampons. Une fois la commande de définition exécutée avec succès, les E/S reprennent (si c’est possible). Si les E/S ne peuvent pas reprendre, une réinitialisation du périphérique peut être nécessaire.

Définition de seuils de température

Windows 10 a introduit IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD, un contrôle IOCTL pour obtenir et définir des seuils de température. Vous pouvez aussi l’utiliser pour obtenir la température actuelle du périphérique. La mémoire tampon d’entrée/sortie de ce contrôle IOCTL est la structure STORAGE_TEMPERATURE_INFO de la section de code précédente.

Exemple : Définition de la température de dépassement du seuil

Dans cet exemple, la température de dépassement du seuil d’un lecteur NVMe est définie. Le code suivant prépare la commande, puis l’envoie au périphérique 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 de fonctionnalités propres au fournisseur

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

Le journal des effets des commandes n’est pas encore suffisamment détaillé pour englober les commandes Set-Features spécifiques au fournisseur. Pour cette raison, il n’est pas encore possible d’envoyer des commandes Set-Features spécifiques au fournisseur. Cependant, il est possible d’utiliser le mécanisme pass-through, décrit précédemment, pour envoyer des commandes spécifiques au fournisseur. Pour plus d’informations, consultez Mécanisme du pass-through.

Fichiers d’en-tête

Les fichiers suivants sont pertinents pour le développement relatif à NVMe. Ces fichiers sont inclus dans le SDK Microsoft Windows.

Fichier d’en-tête Description
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 d’autres structures de données liées à NVMe.
winioctl.h Pour les définitions de contrôles IOCTL Win32 globales, y compris les API de stockage pour les applications en mode utilisateur.