Arbeiten mit NVMe-Laufwerken

Gilt für:

  • Windows 10
  • Windows Server 2016

Erfahren Sie, wie Sie mit NvMe-Hochgeschwindigkeitsgeräten aus Ihrer Windows-Anwendung arbeiten. Der Gerätezugriff wird über StorNVMe.sys aktiviert, dem im Lieferumfang erstmals in Windows Server 2012 R2 und Windows 8.1 eingeführten Treibers. Es ist auch für Windows 7-Geräte über einen KB-Hotfix verfügbar. In Windows 10 wurden mehrere neue Features eingeführt, darunter ein Passthrough-Mechanismus für herstellerspezifische NVMe-Befehle und Aktualisierungen vorhandener IOCTLs.

Dieses Thema bietet eine Übersicht über APIs für die allgemeine Verwendung, die Sie für den Zugriff auf NVMe-Laufwerke in Windows 10 verwenden können. Außerdem wird Folgendes beschrieben:

APIs zum Arbeiten mit NVMe-Laufwerken

Sie können die folgenden APIs für die allgemeine Verwendung nutzen, um auf NVMe-Laufwerke in Windows 10 zuzugreifen. Diese APIs finden Sie in winioctl.h für Benutzermodusanwendungen und ntddstor.h für Kernelmodustreiber. Weitere Informationen zu Headerdateien finden Sie unter Headerdateien.

  • IOCTL_STORAGE_PROTOCOL_COMMAND : Verwenden Sie diese IOCTL mit der Struktur STORAGE_PROTOCOL_COMMAND, um NVMe-Befehle auszuweisen. Dieses IOCTL aktiviert NVMe-Passthrough und unterstützt das Befehlseffekteprotokoll in NVMe. Sie können es mit herstellerspezifischen Befehlen verwenden. Weitere Informationen finden Sie unter Passthrough-Mechanismus.

  • STORAGE_PROTOCOL_COMMAND : Diese Eingabepufferstruktur enthält ein Feld ReturnStatus, das verwendet werden kann, um die folgenden Statuswerte zu melden.

    • 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 : Verwenden Sie diese IOCTL mit der Struktur STORAGE_PROPERTY_QUERY, um Geräteinformationen abzurufen. Weitere Informationen finden Sie unter Protokollspezifische Abfragen und Temperaturabfragen.

  • STORAGE_PROPERTY_QUERY : Diese Struktur enthält die Felder PropertyId und AdditionalParameters, um die abzufragenden Daten anzugeben. Verwenden Sie in der abgelegten PropertyId die Enumeration STORAGE_PROPERTY_ID, um den Typ der Daten anzugeben. Verwenden Sie das Feld AdditionalParameters, um je nach Datentyp weitere Details anzugeben. Verwenden Sie für protokollspezifische Daten die Struktur STORAGE_PROTOCOL_SPECIFIC_DATA im Feld AdditionalParameters. Verwenden Sie für Temperaturdaten die Struktur STORAGE_TEMPERATURE_INFO im Feld AdditionalParameters.

  • STORAGE_PROPERTY_ID : Diese Enumeration enthält neue Werte, mit denen IOCTL_STORAGE_QUERY_PROPERTY protokollspezifische und Temperaturinformationen abrufen können.

    • StorageAdapterProtocolSpecificProperty: Wenn ProtocolType = ProtocolTypeNvme und DataType = NVMeDataTypeLogPage, sollten Aufrufer 512 Byte-Datenblöcke anfordern.
    • StorageDeviceProtocolSpecificProperty

    Verwenden Sie eine dieser protokollspezifischen Eigenschaften-IDs in Kombination mit STORAGE_PROTOCOL_SPECIFIC_DATA, um protokollspezifische Daten in der Struktur STORAGE_PROTOCOL_DATA_DESCRIPTOR abzurufen.

    • StorageAdapterTemperatureProperty
    • StorageDeviceTemperatureProperty

    Verwenden Sie eine dieser Temperatureigenschaften-IDs, um Temperaturdaten in der Struktur STORAGE_TEMPERATURE_DATA_DESCRIPTOR abzurufen.

  • STORAGE_PROTOCOL_SPECIFIC_DATA : Rufen Sie NVMe-spezifische Daten ab, wenn diese Struktur für das Feld AdditionalParameters von STORAGE_PROPERTY_QUERY verwendet wird und ein STORAGE_PROTOCOL_NVME_DATA_TYPE Enumerationswert angegeben wird. Verwenden Sie einen der folgenden Werte STORAGE_PROTOCOL_NVME_DATA_TYPE im Feld DataType der Struktur STORAGE_PROTOCOL_SPECIFIC_DATA:

    • Verwenden Sie NVMeDataTypeIdentify, um Daten zum Identifizieren des Controllers oder zum Identifizieren von Namespacedaten abzurufen.
    • Verwenden Sie NVMeDataTypeLogPage, um Protokollseiten (einschließlich SMART/Health-Daten) abzurufen.
    • Verwenden Sie NVMeDataTypeFeature, um Features des NVMe-Laufwerks abzurufen.
  • STORAGE_TEMPERATURE_INFO : Diese Struktur wird verwendet, um bestimmte Temperaturdaten aufzunehmen. Sie wird im STORAGE_TEMPERATURE_DATA_DESCRIPTOR verwendet, um die Ergebnisse einer Temperaturabfrage zurückzugeben.

  • IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD : Verwenden Sie dieses IOCTL mit der Struktur STORAGE_TEMPERATURE_THRESHOLD, um Temperaturschwellenwerte festzulegen. Weitere Informationen finden Sie unter Verhalten ändernde Befehle.

  • STORAGE_TEMPERATURE_THRESHOLD : Diese Struktur wird als Eingabepuffer verwendet, um den Temperaturschwellenwert anzugeben. Das Feld OverThreshold (boolescher Wert) gibt an, ob das Feld Threshold über dem Schwellenwert ist oder nicht (andernfalls ist es der untere Schwellenwert).

Passthrough-Mechanismus

Befehle, die nicht in der NVMe-Spezifikation definiert sind, sind für das Hostbetriebssystem am schwierigsten zu verarbeiten. Der Host hat keinen Einblick in die Auswirkungen der Befehle auf das Zielgerät, die verfügbar gemachte Infrastruktur (Namespaces/Blockgrößen) und dessen Verhalten.

Um solche gerätespezifischen Befehle besser durch den Windows-Speicherstapel zu übertragen, können durch einen neuen Passthrough-Mechanismus herstellerspezifische Befehle weitergeleitet werden. Dieses Passthrough-Pipe wird auch bei der Entwicklung von Management- und Testtools helfen. Für diesen Passthrough-Mechanismus ist jedoch die Verwendung des Befehlseffektprotokolls erforderlich. Darüber hinaus erfordert StoreNVMe.sys, dass alle Befehle, nicht nur Passthroughbefehle, im Befehlseffektprotokoll beschrieben werden müssen.

Wichtig

StorNVMe.sys und Storport.sys blockieren jeden Befehl für ein Gerät, wenn er nicht im Befehlseffektprotokoll beschrieben wird.

 

Unterstützung des Befehlseffektprotokolls

Das Befehlseffektprotokoll (wie unter Unterstützte Befehle und Effekte, Abschnitt 5.10.1.5 der NVMe-Spezifikation 1.2 beschrieben) ermöglicht die Beschreibung der Auswirkungen herstellerspezifischer Befehle zusammen mit spezifikationsdefinierte Befehle. Dies erleichtert sowohl die Überprüfung der Befehlsunterstützung als auch die Optimierung des Befehlsverhaltens und sollte daher für den gesamten Satz von Befehlen implementiert werden, die das Gerät unterstützt. Die folgenden Bedingungen beschreiben das Ergebnis, wie der Befehl gesendet wird, basierend auf dem Eintrag Befehlseffekteprotokoll.

Für einen bestimmten Befehl, der im Befehlseffektprotokoll beschrieben wird...

Während:

  • Command Supported (CSUPP) ist auf "1" festgelegt, was bedeutet, dass der Befehl vom Controller unterstützt wird (Bit 01)

    Hinweis

    Wenn CSUPP auf "0" festgelegt ist (was bedeutet, dass der Befehl nicht unterstützt wird), wird der Befehl blockiert

     

Und, wenn einer der folgenden Punkte festgelegt ist:

  • Controllerfunktionsänderung (Controller Capability Change, CCC) ist auf "1" festgelegt, was bedeutet, dass der Befehl die Controllerfunktionen ändern kann (Bit 04)

  • Namespace Inventory Change (NIC) ist auf "1" festgelegt, was bedeutet, dass der Befehl die Anzahl oder die Funktionen für mehrere Namespaces ändern kann (Bit 03)

  • Namespacefunktionsänderung (Namespace Capability Change, NCC) ist auf "1" festgelegt, was bedeutet, dass der Befehl die Funktionen eines einzelnen Namespace ändern kann (Bit 02)

  • Befehlsübermittlung und -ausführung (CSE) ist auf 001b oder 010b festgelegt, was bedeutet, dass der Befehl möglicherweise übermittelt wird, wenn kein anderer ausstehender Befehl an denselben oder einen anderen Namespace vorhanden ist, und dass ein anderer Befehl nicht an denselben oder einen beliebigen Namespace übermittelt werden sollte, bis dieser Befehl abgeschlossen ist (Bits 18:16)

Dann wird der Befehl als einziger ausstehender Befehl an den Adapter gesendet.

Sonst:

  • Befehlsübermittlung und -ausführung (Command Submission and Execution, CSE) ist auf 001b festgelegt, was bedeutet, dass der Befehl möglicherweise übermittelt wird, wenn kein anderer ausstehender Befehl an denselben Namespace vorhanden ist, und dass ein anderer Befehl nicht an denselben Namespace übermittelt werden sollte, bis dieser Befehl abgeschlossen ist (Bits 18:16)

Dann wird der Befehl als einziger ausstehender Befehl an das LUN -Objekt (Logical Unit Number) gesendet.

Andernfalls wird der Befehl gesendet, wenn andere Befehle ohne Hemmnis ausstehen. Wenn beispielsweise ein anbieterspezifischer Befehl an das Gerät gesendet wird, um statistische Informationen abzurufen, die nicht spezifikationsdefiniert sind, sollte kein Risiko bestehen, das Verhalten oder die Fähigkeit des Geräts zum Ausführen von E/A-Befehlen zu ändern. Solche Anforderungen könnten parallel zu E/A gewartet werden, und es wäre kein Anhalten und Fortsetzen erforderlich.

Verwenden von IOCTL_STORAGE_PROTOCOL_COMMAND zum Senden von Befehlen

Passthrough kann mit dem in Windows 10 eingeführten IOCTL_STORAGE_PROTOCOL_COMMAND durchgeführt werden. Diese IOCTL wurde entwickelt, um ein ähnliches Verhalten wie die vorhandenen SCSI- und ATA-Passthrough-IOCTLs zu haben, um einen eingebetteten Befehl an das Zielgerät zu senden. Über dieses IOCTL kann Passthrough an ein Speichergerät gesendet werden, einschließlich eines NVMe-Laufwerks.

In NVMe lässt die IOCTL beispielsweise das Senden der folgenden Befehlscodes zu.

  • Herstellerspezifische Admin-Befehle (C0h – FFh)
  • Herstellerspezifische NVMe-Befehle (80h – FFh)

Wie bei allen anderen IOCTLs können Sie DeviceIoControl verwenden, um die Passthrough-IOCTL nach unten zu senden. Die IOCTL wird mithilfe der Eingabepufferstruktur STORAGE_PROTOCOL_COMMAND in ntddstor.h aufgefüllt. Füllen Sie das Feld Command mit dem herstellerspezifischen Befehl auf.

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;

Der herstellerspezifische Befehl, der gesendet werden soll, sollte im hervorgehobenen Feld oben aufgefüllt werden. Beachten Sie erneut, dass das Befehlseffektprotokoll für Passthrough-Befehle implementiert werden muss. Insbesondere müssen diese Befehle im Befehlseffektprotokoll als unterstützt gemeldet werden (weitere Informationen finden Sie im vorherigen Abschnitt). Beachten Sie auch, dass PRP-Felder treiberspezifisch sind, sodass Anwendungen, die Befehle senden, sie als 0 belassen können.

Schließlich ist diese Passthrough-IOCTL für das Senden von anbieterspezifischen Befehlen vorgesehen. Zum Senden anderer administrator- oder nicht herstellerspezifischer NVMe-Befehle wie Identifizieren sollte diese Passthrough-IOCTL nicht verwendet werden. Beispielsweise sollte IOCTL_STORAGE_QUERY_PROPERTY zum Identifizieren oder Abrufen von Protokollseiten verwendet werden. Weitere Informationen finden Sie im nächsten Abschnitt protokollspezifische Abfragen.

Aktualisieren Sie die Firmware nicht über den Passthrough-Mechanismus

Firmwaredownload- und Aktivierungsbefehle sollten nicht per Passthrough gesendet werden. IOCTL_STORAGE_PROTOCOL_COMMAND sollte nur für anbieterspezifische Befehle verwendet werden.

Verwenden Sie stattdessen die folgenden allgemeinen Speicher-IOCTLs (eingeführt in Windows 10), um zu vermeiden, dass Anwendungen direkt die SCSI_miniport Version der Firmware-IOCTL verwenden. Speichertreiber übersetzen die IOCTL entweder in einen SCSI-Befehl oder in die SCSI_miniport Version der IOCTL in den Miniport.

Diese IOCTLs werden für die Entwicklung von Firmwareupgradetools in Windows 10 und Windows Server 2016 empfohlen:

Zum Abrufen von Speicherinformationen und Aktualisieren der Firmware unterstützt Windows auch PowerShell-Cmdlets, um dies schnell zu tun:

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

Hinweis

Um Firmware von NVMe in Windows 8.1 zu aktualisieren, verwenden Sie IOCTL_SCSI_MINIPORT_FIRMWARE. Diese IOCTL wurde nicht auf Windows 7 zurückportiert. Weitere Informationen finden Sie unter Aktualisieren der Firmware für ein NVMe-Gerät in Windows 8.1.

 

Zurückgeben von Fehlern über den Passthrough-Mechanismus

Ähnlich wie bei SCSI- und ATA-Passthrough-IOCTLs gibt die IOCTL zurück, wenn ein Befehl/eine Anforderung an den Miniport oder das Gerät gesendet wird, ob sie erfolgreich war oder nicht. In der Struktur STORAGE_PROTOCOL_COMMAND gibt die IOCTL den Status über das Feld ReturnStatus zurück.

Beispiel: Senden eines herstellerspezifischen Befehls

In diesem Beispiel wird ein beliebiger herstellerspezifischer Befehl (0xFF) per Passthrough an ein NVMe-Laufwerk gesendet. Der folgende Code ordnet einen Puffer zu, initialisiert eine Abfrage und sendet den Befehl dann über DeviceIoControl an das Gerät.

    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 
                             );  

In diesem Beispiel wird protocolCommand->ReturnStatus == STORAGE_PROTOCOL_STATUS_SUCCESS erwartet, wenn der Befehl erfolgreich für das Gerät ausgeführt wurde.

Protokollspezifische Abfragen

Windows 8.1 wurde IOCTL_STORAGE_QUERY_PROPERTY für den Datenabruf eingeführt. In Windows 10 wurde die IOCTL erweitert, um häufig angeforderte NVMe-Features wie Protokollseiten abrufen, Features abrufen und Identifizieren zu unterstützen. Dies ermöglicht den Abruf von NVMe-spezifischen Informationen zu Überwachungs- und Bestandszwecken.

Der Eingabepuffer für die IOCTL, STORAGE_PROPERTY_QUERY (von Windows 10) ist hier dargestellt.

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

Wenn Sie IOCTL_STORAGE_QUERY_PROPERTY verwenden, um NVMe-protokollspezifische Informationen im STORAGE_PROTOCOL_DATA_DESCRIPTOR abzurufen, konfigurieren Sie die Struktur STORAGE_PROPERTY_QUERY wie folgt:

  • Weisen Sie einen Puffer zu, der sowohl eine STORAGE_PROPERTY_QUERY als auch eine StrukturSTORAGE_PROTOCOL_SPECIFIC_DATA enthalten kann.

  • Legen Sie das Feld PropertyID für eine Controller- bzw. Geräte-/Namespaceanforderung auf StorageAdapterProtocolSpecificProperty oder StorageDeviceProtocolSpecificProperty fest.

  • Legen Sie das Feld QueryType auf PropertyStandardQuery fest.

  • Füllen Sie die Struktur STORAGE_PROTOCOL_SPECIFIC_DATA mit den gewünschten Werten. Der Anfang der STORAGE_PROTOCOL_SPECIFIC_DATA ist das Feld AdditionalParameters von STORAGE_PROPERTY_QUERY.

Die Struktur STORAGE_PROTOCOL_SPECIFIC_DATA (aus Windows 10) wird hier gezeigt.

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;

Um einen Typ von NVMe-protokollspezifischen Informationen anzugeben, konfigurieren Sie die Struktur STORAGE_PROTOCOL_SPECIFIC_DATA wie folgt:

  • Legen Sie das Feld ProtocolType auf ProtocolTypeNVMe fest.

  • Legen Sie das Feld DataType auf einen durch STORAGE_PROTOCOL_NVME_DATA_TYPE definierten Enumerationswert fest:

    • Verwenden Sie NVMeDataTypeIdentify, um Daten zum Identifizieren des Controllers oder zum Identifizieren von Namespacedaten abzurufen.
    • Verwenden Sie NVMeDataTypeLogPage, um Protokollseiten (einschließlich SMART/Health-Daten) abzurufen.
    • Verwenden Sie NVMeDataTypeFeature, um Features des NVMe-Laufwerks abzurufen.

Wenn ProtocolTypeNVMe als ProtocolType verwendet wird, können Abfragen für protokollspezifische Informationen parallel zu anderen E/A-Vorgängen auf dem NVMe-Laufwerk abgerufen werden.

Wichtig

Legen Sie für eine IOCTL_STORAGE_QUERY_PROPERTY, die eine STORAGE_PROPERTY_ID von storageAdapterProtocolSpecificProperty verwendet und dessen Struktur STORAGE_PROTOCOL_SPECIFIC_DATA oder STORAGE_PROTOCOL_SPECIFIC_DATA_EXT auf ProtocolType=ProtocolTypeNvme und DataType=NVMeDataTypeLogPage festgelegt ist, den ProtocolDataLength-Member derselben Struktur auf einen Mindestwert von 512 (Bytes) fest.

Die folgenden Beispiele veranschaulichen NVMe-protokollspezifische Abfragen.

Beispiel: NVMe Identify-Abfrage

In diesem Beispiel wird die Anforderung Identify an ein NVMe-Laufwerk gesendet. Der folgende Code initialisiert eine Abfrage der Datenstruktur und sendet den Befehl dann über DeviceIoControl an das Gerät.

    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"));
        }
    }

  

Wichtig

Legen Sie für eine IOCTL_STORAGE_QUERY_PROPERTY, die eine STORAGE_PROPERTY_ID von storageAdapterProtocolSpecificProperty verwendet und dessen Struktur STORAGE_PROTOCOL_SPECIFIC_DATA oder STORAGE_PROTOCOL_SPECIFIC_DATA_EXT auf ProtocolType=ProtocolTypeNvme und DataType=NVMeDataTypeLogPage festgelegt ist, den ProtocolDataLength-Member derselben Struktur auf einen Mindestwert von 512 (Bytes) fest.

Beachten Sie, dass der Aufrufer einen einzelnen Puffer zuordnen muss, der STORAGE_PROPERTY_QUERY und die Größe der STORAGE_PROTOCOL_SPECIFIC_DATA enthält. In diesem Beispiel wird derselbe Puffer für Eingabe und Ausgabe aus der Eigenschaftenabfrage verwendet. Aus diesem Grund hat der zugeordnete Puffer die Größe "FIELD_OFFSET(STORAGE_PROPERTY_QUERY, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE". Obwohl separate Puffer sowohl für die Eingabe als auch für die Ausgabe zugeordnet werden können, wird empfohlen, einen einzelnen Puffer zu verwenden, um NVMe-bezogene Informationen abzufragen.

identifyControllerData>-NN ist Number of Namespaces (Anzahl an Namespaces, NN). Windows erkennt einen Namespace als physisches Laufwerk.

Beispiel: Abfrage NVMe Get Log Pages

In diesem Beispiel wird basierend auf dem vorherigen Beispiel die Anforderung Get Log Pages an ein NVMe-Laufwerk gesendet. Der folgende Code bereitet eine Abfrage der Datenstruktur vor und sendet den Befehl dann über DeviceIoControl an das Gerät.

    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"));
    }

Aufrufer können eine STORAGE_PROPERTY_ID von StorageAdapterProtocolSpecificPropertyverwenden, deren STORAGE_PROTOCOL_SPECIFIC_DATA- oder STORAGE_PROTOCOL_SPECIFIC_DATA_EXT-Struktur auf ProtocolDataRequestValue=VENDOR_SPECIFIC_LOG_PAGE_IDENTIFIER festgelegt ist, sodass 512 Byteblöcke von anbieterspezifischen Daten angefordert werden.

Beispiel: Abfrage NVMe Get Features

In diesem Beispiel wird basierend auf dem vorherigen Beispiel die Anforderung Get Features an ein NVMe-Laufwerk gesendet. Der folgende Code bereitet eine Abfrage der Datenstruktur vor und sendet den Befehl dann über DeviceIoControl an das Gerät.

    //  
    // 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"));
    }

Protokollspezifischer Satz

Ab Windows 10 19H1 wurde die IOCTL_STORAGE_SET_PROPERTY erweitert, um NVMe-Set-Features zu unterstützen.

Der Eingabepuffer für die IOCTL_STORAGE_SET_PROPERTY wird hier gezeigt:

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;

Wenn Sie IOCTL_STORAGE_SET_PROPERTY zum Festlegen des NVMe-Features verwenden, konfigurieren Sie die Struktur STORAGE_PROPERTY_SET wie folgt:

  • Weisen Sie einen Puffer zu, der sowohl eine STORAGE_PROPERTY_SET als auch eine Struktur STORAGE_PROTOCOL_SPECIFIC_DATA_EXT enthalten kann;
  • Legen Sie das Feld PropertyID für eine Controller- bzw. Geräte-/Namespaceanforderung auf StorageAdapterProtocolSpecificProperty oder StorageDeviceProtocolSpecificProperty fest.
  • Füllen Sie die Struktur STORAGE_PROTOCOL_SPECIFIC_DATA_EXT mit den gewünschten Werten. Der Anfang der STORAGE_PROTOCOL_SPECIFIC_DATA_EXT ist das Feld AdditionalParameters von STORAGE_PROPERTY_SET.

Die Struktur STORAGE_PROTOCOL_SPECIFIC_DATA_EXT wird hier gezeigt.

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;

Um einen festzulegenden NvMe-Featuretyp anzugeben, konfigurieren Sie die Struktur STORAGE_PROTOCOL_SPECIFIC_DATA_EXT wie folgt:

  • Legen Sie das Feld ProtocolType auf ProtocolTypeNvme fest;
  • Legen Sie das Feld DataType auf den durch STORAGE_PROTOCOL_NVME_DATA_TYPE definierten Enumerationswert NVMeDataTypeFeature fest;

Die folgenden Beispiele veranschaulichen den NVMe-Featuresatz.

Beispiel: NVMe-Set-Features

In diesem Beispiel wird die Anforderung Set Features an ein NVMe-Laufwerk gesendet. Der folgende Code bereitet eine Abfrage der festgelegte Datenstruktur vor und sendet den Befehl dann über DeviceIoControl an das Gerät.

            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
            );

Temperaturabfragen

In Windows 10 kann IOCTL_STORAGE_QUERY_PROPERTY auch verwendet werden, um Temperaturdaten von NVMe-Geräten abzufragen.

Um Temperaturinformationen von einem NVMe-Laufwerk im STORAGE_TEMPERATURE_DATA_DESCRIPTOR abzurufen, konfigurieren Sie die Struktur STORAGE_PROPERTY_QUERY wie folgt:

  • Ordnen Sie einen Puffer zu, der eine Struktur STORAGE_PROPERTY_QUERY enthalten kann.

  • Legen Sie das Feld PropertyID für eine Controller- bzw. Geräte-/Namespaceanforderung auf StorageAdapterTemperatureProperty oder StorageDeviceTemperatureProperty fest.

  • Legen Sie das Feld QueryType auf PropertyStandardQuery fest.

Die Struktur STORAGE_TEMPERATURE_INFO (aus Windows 10) wird hier gezeigt.

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;

Befehle zur Verhaltensänderung

Befehle, die Geräteattribute bearbeiten oder sich möglicherweise auf das Geräteverhalten auswirken, sind für das Betriebssystem schwieriger zu behandeln. Wenn sich Geräteattribute während der E/A-Verarbeitung zur Laufzeit ändern, können Probleme mit der Synchronisierung oder Datenintegrität auftreten, wenn sie nicht ordnungsgemäß behandelt werden.

Der NVMe-Befehl Set-Features ist ein gutes Beispiel für einen Verhaltensänderungsbefehl. Er ermöglicht die Änderung des Schiedsmechanismus und die Festlegung von Temperaturschwellenwerten. Um sicherzustellen, dass bereits ausgeführte Daten nicht gefährdet sind, wenn verhaltensbeeinflussbare Set-Befehle gesendet werden, hält Windows alle E/A-Vorgänge an das NVMe-Gerät an, löscht Warteschlangen und leert Puffer. Nachdem der Set-Befehl erfolgreich ausgeführt wurde, wird die E/A fortgesetzt (falls möglich). Wenn die E/A nicht fortgesetzt werden kann, ist möglicherweise eine Zurücksetzung des Geräts erforderlich.

Festlegen von Temperaturschwellenwerten

Mit Windows 10 wurde IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD eingeführt, eine IOCTL zum Abrufen und Festlegen von Temperaturschwellenwerten. Sie können es auch verwenden, um die aktuelle Temperatur des Geräts abzurufen. Der Eingabe-/Ausgabepuffer für diese IOCTL ist die Struktur STORAGE_TEMPERATURE_INFO aus dem vorherigen Codeabschnitt.

Beispiel: Festlegen einer Überschwellentemperatur

In diesem Beispiel wird die Überschwellentemperatur eines NVMe-Laufwerks festgelegt. Der folgende Code bereitet eine Abfrage des Befehls vor und sendet ihn dann über DeviceIoControl an das Gerät.

    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  
                             ); 

Festlegen von anbieterspezifischen Features

Ohne das Befehlseffektprotokoll hat der Treiber keine Kenntnis von den Auswirkungen des Befehls. Aus diesem Grund ist das Befehlseffekteprotokoll erforderlich. Es hilft dem Betriebssystem zu ermitteln, ob ein Befehl eine hohe Auswirkung hat und ob er parallel zu anderen Befehlen an das Laufwerk gesendet werden kann.

Das Befehlseffektprotokoll ist noch nicht präzise genug, um anbieterspezifische Set-Features-Befehle zu umfassen. Aus diesem Grund ist es noch nicht möglich, herstellerspezifische Set-Features-Befehle zu senden. Es ist jedoch möglich, den zuvor erläuterten Passthrough-Mechanismus zu verwenden, um herstellerspezifische Befehle zu senden. Weitere Informationen finden Sie unter Passthrough-Mechanismus.

Headerdateien

Die folgenden Dateien sind für die NVMe-Entwicklung relevant. Diese Dateien sind im Microsoft Windows Software Development Kit (SDK) enthalten.

Headerdatei Beschreibung
ntddstor.h Definiert Konstanten und Typen für den Zugriff auf die Speicherklassentreiber aus dem Kernelmodus.
nvme.h Für andere NVMe-bezogene Datenstrukturen.
winioctl.h Für alle Win32-IOCTL-Definitionen, einschließlich Speicher-APIs für Benutzermodusanwendungen.