Arbeiten mit NVMe-Laufwerken

Anwendungsbereich:

  • Windows 10
  • Windows Server 2016

Erfahren Sie, wie Sie mit NVMe-Hochgeschwindigkeitsgeräten aus Ihrer Windows arbeiten. Der Gerätezugriff wird über StorNVMe.sys aktiviert. Dabei handelt es sich um den in Windows Server 2012 R2 und Windows 8.1. Es ist auch für 7 Windows über einen KB-Hotfix verfügbar. In Windows 10 wurden mehrere neue Features eingeführt, einschließlich eines Pass-Through-Mechanismus für herstellerspezifische NVMe-Befehle und Updates vorhandener IOCTLs.

Dieses Thema bietet eine Übersicht über allgemeine APIs, die Sie für den Zugriff auf NVMe-Laufwerke in Windows 10. Außerdem wird Beschrieben:

APIs für die Arbeit mit NVMe-Laufwerken

Sie können die folgenden allgemeinen APIs verwenden, um auf NVMe-Laufwerke in Windows 10. 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 _ _ _ SPEICHERPROTOKOLLBEFEHL: Verwenden Sie diese IOCTL mit der SPEICHERPROTOKOLL-BEFEHLsstruktur, _ _ um NVMe-Befehle aus auszuführen. Diese IOCTL ermöglicht nvme-Pass-Through und unterstützt das Command Effects-Protokoll in NVMe. Sie können sie mit herstellerspezifischen Befehlen verwenden. Weitere Informationen finden Sie unter Pass-Through-Mechanismus.

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

    • _ _ SPEICHERPROTOKOLLSTATUS _ AUSSTEHEND
    • _ _ SPEICHERPROTOKOLLSTATUS _ ERFOLGREICH
    • _ _ _ SPEICHERPROTOKOLLSTATUSFEHLER
    • _ _ SPEICHERPROTOKOLLSTATUS _ UNGÜLTIGE _ ANFORDERUNG
    • _ _ SPEICHERPROTOKOLLSTATUS KEIN _ _ GERÄT
    • _ _ SPEICHERPROTOKOLLSTATUS _ AUSGELASTET
    • _ _ SPEICHERPROTOKOLLSTATUSDATEN _ _ ÜBERLAUF
    • _ _ SPEICHERPROTOKOLLSTATUS UNZUREICHENDE _ _ RESSOURCEN
    • _ _ SPEICHERPROTOKOLLSTATUS WIRD NICHT _ _ UNTERSTÜTZT
  • IOCTL _ STORAGE _ QUERY _ PROPERTY: Verwenden Sie diese IOCTL mit der STORAGE PROPERTY _ _ QUERY-Struktur, 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 daten anzugeben, die abgefragt werden sollen. Verwenden Sie im Feld PropertyId die STORAGE PROPERTY _ _ ID-Enumeration, um den Datentyp anzugeben. Verwenden Sie das Feld AdditionalParameters, um je nach Datentyp weitere Details anzugeben. Verwenden Sie für protokollspezifische Daten die STORAGE _ PROTOCOL SPECIFIC _ _ DATA-Struktur im Feld AdditionalParameters. Verwenden Sie für Temperaturdaten die STORAGE _ TEMPERATURE _ INFO-Struktur im Feld AdditionalParameters.

  • STORAGE _ PROPERTY _ ID : Diese Enumeration enthält neue Werte, mit denen IOCTL _ STORAGE QUERY _ _ PROPERTY protokollspezifische und Temperaturinformationen abrufen kann.

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

    Verwenden Sie eine dieser protokollspezifischen Eigenschaften-IDs in Kombination mit STORAGE _ PROTOCOL SPECIFIC _ _ DATA, um protokollspezifische Daten in der STORAGE PROTOCOL DATA _ _ _ DESCRIPTOR-Struktur abzurufen.

    • StorageAdapterTemperatureProperty
    • StorageDeviceTemperatureProperty

    Verwenden Sie eine dieser Temperatureigenschafts-IDs, um Temperaturdaten in der _ _ _ SPEICHERTEMPERATURDATEN-DESKRIPTORstruktur abzurufen.

  • STORAGE _ PROTOKOLLSPEZIFISCHE _ _ DATEN: 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-Aufzählwert _ angegeben wird. Verwenden Sie einen der folgenden STORAGE _ PROTOCOL _ NVME DATA _ _ TYPE-Werte im Feld DataType der STORAGE PROTOCOL SPECIFIC _ _ _ DATA-Struktur:

    • Verwenden Sie NVMeDataTypeIdentify, um Controllerdaten zu identifizieren oder Namespacedaten zu identifizieren.
    • Verwenden Sie NVMeDataTypeLogPage, um Protokollseiten (einschließlich SMART-/Integritätsdaten) zu erhalten.
    • Verwenden Sie NVMeDataTypeFeature, um Features des NVMe-Laufwerks zu erhalten.
  • STORAGE _ TEMPERATURE _ INFO : Diese Struktur wird verwendet, um bestimmte Temperaturdaten zu halten. Er wird im _ SPEICHER-TEMERATURE-DATENDESKRIPTOR _ _ verwendet, um die Ergebnisse einer Temperaturabfrage zurück zu geben.

  • IOCTL _ STORAGE _ SET _ TEMPERATURE _ THRESHOLD : Verwenden Sie diese IOCTL mit der _ _ SPEICHERTEMPERATURSCHWELLENstruktur, um Temperaturschwellenwerte zu setzen. Weitere Informationen finden Sie unter Befehle zum Ändern des Verhaltens.

  • STORAGE _ _TEMPERATURSCHWELLENWERT: Diese Struktur wird als Eingabepuffer verwendet, um den Temperaturschwellenwert anzugeben. Das OverThreshold-Feld (boolesch) gibt an, ob das Schwellenwertfeld der Über-Schwellenwert ist oder nicht (andernfalls ist es der unter dem Schwellenwert).

Pass-Through-Mechanismus

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

Um solche gerätespezifischen Befehle besser über den Windows-Speicherstapel zu übertragen, ermöglicht ein neuer Pass-Through-Mechanismus das Übergeben anbieterspezifischer Befehle. Diese Pass-Through-Pipe wird auch bei der Entwicklung von Verwaltungs- und Testtools helfen. Dieser Pass-Through-Mechanismus erfordert jedoch die Verwendung des Command Effects-Protokolls. Darüber hinaus StoreNVMe.sys alle Befehle, nicht nur Pass-Through-Befehle, im Befehlseffektprotokoll beschrieben werden.

Wichtig

StorNVMe.sys und Storport.sys blockiert jeden Befehl auf einem Gerät, wenn er nicht im Befehlseffektprotokoll beschrieben wird.

Unterstützen des Befehlseffektprotokolls

Das Befehlseffektprotokoll (wie unter Unterstützte Befehle und Effekte, Abschnitt 5.10.1.5 der NVMe-Spezifikation 1.2beschrieben) ermöglicht die Beschreibung der Auswirkungen herstellerspezifischer Befehle zusammen mit spezifikationsdefinierten Befehlen. Dies erleichtert sowohl die Validierung 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 basierend auf seinem Command Effects Log-Eintrag gesendet wird.

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

While:

  • 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 eine der folgenden Bedingungen festgelegt ist:

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

  • Die Änderung des Namespacebestands (NIC) ist auf "1" festgelegt, was bedeutet, dass der Befehl die Anzahl oder Funktionen für mehrere Namespaces ändern kann (Bit 03).

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

  • Die Befehlsübermittlung und -ausführung (Command Submission and Execution, CSE) ist auf 001b oder 010b festgelegt. Dies bedeutet, dass der Befehl übermittelt werden kann, wenn kein anderer ausstehender Befehl für denselben oder einen namespace verfügbar ist, und dass ein anderer Befehl erst dann an denselben oder einen namespace übermittelt werden soll, wenn dieser Befehl abgeschlossen ist (Bits 18:16).

Anschließend wird der Befehl als einziger ausstehender Befehl an den Adapter gesendet.

Ander denn, wenn:

  • Die Befehlsübermittlung und -ausführung (Command Submission and Execution, CSE) ist auf 001b festgelegt. Dies bedeutet, dass der Befehl übermittelt werden kann, wenn kein anderer ausstehender Befehl für denselben Namespace verfügbar ist, und dass ein anderer Befehl erst dann an denselben Namespace übermittelt werden sollte, wenn dieser Befehl abgeschlossen ist (Bits 18:16).

Anschließend wird der Befehl als einziger ausstehender Befehl für das Logical Unit Number-Objekt (LUN) gesendet.

Andernfalls wird der Befehl mit anderen befehlslos ausstehenden Befehlen gesendet. Wenn beispielsweise ein herstellerspezifischer Befehl an das Gerät gesendet wird, um statistische Informationen abzurufen, die nicht spezifikationsdefiniert sind, sollte es kein Risiko geben, 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-Anforderungen ausgeführt werden, und es wäre kein Anhalten und Fortsetzen erforderlich.

Verwenden des IOCTL _ STORAGE _ _ PROTOCOL-BEFEHLs zum Senden von Befehlen

Pass-Through kann mithilfe des IOCTL _ STORAGE PROTOCOL _ _ COMMANDdurchgeführt werden, der in Windows 10. Diese IOCTL wurde entwickelt, um ein ähnliches Verhalten wie die vorhandenen SCSI- und ATA-Pass-Through-IOCTLs zu haben, um einen eingebetteten Befehl an das Zielgerät zu senden. Über diese IOCTL kann pass-through an ein Speichergerät gesendet werden, einschließlich eines NVMe-Laufwerks.

Beispielsweise lässt die IOCTL in NVMe das Senden der folgenden Befehlscodes zu.

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

Verwenden Sie wie bei allen anderen IOCTLs DeviceIoControl, um die Pass-Through-IOCTL nach unten zu senden. Die IOCTL wird mithilfe der Eingabepufferstruktur STORAGE _ PROTOCOL _ COMMAND in ntddstor.h aufgefüllt. Füllen Sie das Feld Befehl 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 anbieterspezifische Befehl, der gesendet werden soll, sollte im oben hervorgehobenen Feld aufgefüllt werden. Beachten Sie erneut, dass das Befehlseffektprotokoll für Pass-Through-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, daher können Anwendungen, die Befehle senden, diese als 0 (0) beenden.

Schließlich ist diese Pass-Through-IOCTL für das Senden anbieterspezifischer Befehle vorgesehen. Diese Pass-Through-IOCTL sollte nicht verwendet werden, um andere administrator- oder herstellerspezifische NVMe-Befehle wie z. B. Identify zu senden. Beispielsweise sollte IOCTL _ STORAGE QUERY _ _ PROPERTY zum Identifizieren oder Abfragen von Protokollseiten verwendet werden. Weitere Informationen finden Sie im nächsten Abschnitt Protokollspezifische Abfragen.

Firmware nicht über den Pass-Through-Mechanismus aktualisieren

Firmwaredownload- und Aktivierungsbefehle sollten nicht per Pass-Through 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-Miniportversion der _ Firmware-IOCTL verwenden. Storage-Treiber übersetzt die IOCTL entweder in einen SCSI-Befehl oder die SCSI-Miniportversion von _ IOCTL in den Miniport.

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

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

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

Hinweis

Verwenden Sie IOCTL SCSI MINIPORT FIRMWARE, um die Firmware auf NVMe in Windows 8.1 _ _ _ aktualisieren. 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 Pass-Through-Mechanismus

Ähnlich wie bei SCSI- und ATA-Pass-Through-IOCTLs gibt IOCTL zurück, wenn ein Befehl/eine Anforderung an den Miniport oder das Gerät gesendet wird, wenn er erfolgreich war oder nicht. In der STORAGE _ PROTOCOL _ COMMAND-Struktur gibt die IOCTL den Status über das Feld ReturnStatus zurück.

Beispiel: Senden eines anbieterspezifischen Befehls

In diesem Beispiel wird ein beliebiger anbieterspezifischer Befehl (0xFF) per Pass-Through an ein NVMe-Laufwerk gesendet. Der folgende Code weist 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 erwartet, protocolCommand->ReturnStatus == STORAGE_PROTOCOL_STATUS_SUCCESS dass der Befehl erfolgreich auf dem Gerät ausgeführt wurde.

Protokollspezifische Abfragen

Windows 8.1 IOCTL STORAGE _ _ QUERY _ PROPERTY für den Datenabruf eingeführt. In Windows 10 wurde IOCTL erweitert, um häufig angeforderte NVMe-Features wie Get Log Pages, Get Features und Identify zu unterstützen. Dies ermöglicht das Abrufen von NVMe-spezifischen Informationen zu Überwachungs- und Inventurzwecken.

Der Eingabepuffer für IOCTL, STORAGE _ PROPERTY _ QUERY (von Windows 10) wird hier gezeigt.

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 protokollspezifische NVMe-Informationen im SPEICHERPROTOKOLL-DATENDESKRIPTOR _ _ _abzurufen, konfigurieren Sie die STORAGE PROPERTY _ _ QUERY-Struktur wie folgt:

Die _ _ SPEICHERPROTOKOLLSPEZIFISCHE _ DATENstruktur (aus Windows 10) ist hier dargestellt.

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 protokollspezifischen NVMe-Informationen anzugeben, konfigurieren Sie die _ SPEICHERPROTOKOLLSPEZIFISCHE _ _ DATENstruktur wie folgt:

  • Legen Sie das Feld ProtocolType auf ProtocolTypeNVMe fest.

  • Legen Sie das Feld DataType auf einen Enumerationswert fest, der durch STORAGE PROTOCOL _ _ NVME DATA TYPE definiert _ _ wird:

    • Verwenden Sie NVMeDataTypeIdentify, um Controllerdaten zu identifizieren oder Namespacedaten zu identifizieren.
    • Verwenden Sie NVMeDataTypeLogPage, um Protokollseiten (einschließlich SMART-/Integritätsdaten) zu erhalten.
    • Verwenden Sie NVMeDataTypeFeature, um Features des NVMe-Laufwerks zu erhalten.

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

Wichtig

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

In den folgenden Beispielen werden NVMe-protokollspezifische Abfragen veranschaulicht.

Beispiel: NVMe-Abfrage "Identify"

In diesem Beispiel wird die Identify-Anforderung an ein NVMe-Laufwerk gesendet. Der folgende Code initialisiert die Abfragedatenstruktur und sendet dann den Befehl ü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 einen IOCTL_STORAGE_QUERY_PROPERTY, der eine STORAGE_PROPERTY_ID von StorageAdapterProtocolSpecificPropertyverwendet und dessen STORAGE_PROTOCOL_SPECIFIC_DATA- oder STORAGE_PROTOCOL_SPECIFIC_DATA_EXT-Struktur auf und festgelegt ist, den ProtocolDataLength-Member derselben Struktur auf einen Mindestwert von ProtocolType=ProtocolTypeNvme DataType=NVMeDataTypeLogPage 512 (Bytes) fest.

Beachten Sie, dass der Aufrufer einen einzelnen Puffer zuordnen muss, der STORAGE PROPERTY QUERY und die Größe der _ _ _ _ SPEICHERPROTOKOLLSPEZIFISCHEN _ DATEN enthält. In diesem Beispiel wird der gleiche Puffer für die Eingabe und Ausgabe 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önnten, wird empfohlen, einen einzelnen Puffer zum Abfragen von NVMe-bezogenen Informationen zu verwenden.

Beispiel: NVMe Get Log Pages-Abfrage

In diesem Beispiel wird basierend auf dem vorherigen die Anforderung Protokollseiten erhalten an ein NVMe-Laufwerk gesendet. Der folgende Code bereitet die Abfragedatenstruktur 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"));
    }

Beispiel: NVMe-Abfrage "Features erhalten"

In diesem Beispiel wird basierend auf dem vorherigen die Anforderung "Features erhalten" an ein NVMe-Laufwerk gesendet. Der folgende Code bereitet die Abfragedatenstruktur 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 ist hier dargestellt:

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 NVMe-Feature verwenden, konfigurieren Sie die STORAGE_PROPERTY_SET wie folgt:

  • Ordnen Sie einen Puffer zu, der sowohl eine STORAGE_PROPERTY_SET als auch eine STORAGE_PROTOCOL_SPECIFIC_DATA_EXT enthält.
  • Legen Sie das Feld PropertyID auf StorageAdapterProtocolSpecificProperty bzw. StorageDeviceProtocolSpecificProperty für eine Controller- bzw. Geräte-/Namespaceanforderung fest.
  • Füllen Sie STORAGE_PROTOCOL_SPECIFIC_DATA_EXT struktur mit den gewünschten Werten aus. Der Anfang des STORAGE_PROTOCOL_SPECIFIC_DATA_EXT ist das Feld AdditionalParameters der STORAGE_PROPERTY_SET.

Die STORAGE_PROTOCOL_SPECIFIC_DATA_EXT ist hier dargestellt.

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 STORAGE_PROTOCOL_SPECIFIC_DATA_EXT wie folgt:

  • Legen Sie das Feld ProtocolType auf ProtocolTypeNvme fest.
  • Legen Sie das Feld DataType auf den Enumerationswert NVMeDataTypeFeature fest, der von STORAGE_PROTOCOL_NVME_DATA_TYPE;

In den folgenden Beispielen wird der NVMe-Featuresatz veranschaulicht.

Beispiel: NVMe-Satzfeatures

In diesem Beispiel wird die Anforderung Features festlegen an ein NVMe-Laufwerk gesendet. Der folgende Code bereitet die Set-Datenstruktur vor und sendet dann den Befehl ü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 zum Abfragen von Temperaturdaten von NVMe-Geräten verwendet werden.

Um Temperaturinformationen von einem NVMe-Laufwerk im _ _ SPEICHERTEMPERATURDATEN-DESKRIPTOR _abzurufen, konfigurieren Sie die STORAGE _ PROPERTY _ QUERY-Struktur wie folgt:

  • Ordnen Sie einen Puffer zu, der eine STORAGE _ PROPERTY _ QUERY-Struktur enthalten kann.

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

  • Legen Sie das Feld QueryType auf PropertyStandardQuery fest.

Die _ _ SPEICHERTEMPERATUR-INFO-Struktur (von 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 zum Ändern des Verhaltens

Befehle, die Geräteattribute bearbeiten oder sich potenziell auf das Geräteverhalten auswirken, sind für das Betriebssystem schwieriger zu behandeln. Wenn sich Geräteattribute zur Laufzeit ändern, während E/A verarbeitet wird, können Synchronisierungs- oder Datenintegritätsprobleme auftreten, wenn sie nicht ordnungsgemäß behandelt werden.

Der NVMe-Befehl Set-Features ist ein gutes Beispiel für einen Befehl zum Ändern des Verhaltens. Sie ermöglicht die Änderung des Vermittlungsmechanismus und die Festlegung von Temperaturschwellenwerte. Um sicherzustellen, dass während des Flugs ausgeführte Daten nicht gefährdet sind, wenn befehle, die sich auf das Verhalten beeinflussen, gesendet werden, hält Windows alle E/A-Daten auf dem NVMe-Gerät an, entleert Warteschlangen und leert Puffer. Nachdem der set-Befehl erfolgreich ausgeführt wurde, wird die E/A (sofern möglich) fortgesetzt. Wenn E/A nicht fortgesetzt werden kann, ist möglicherweise eine Gerätezurücksetzung erforderlich.

Festlegen von Temperaturschwellenwert

Windows 10 IOCTL _ STORAGE _ SET TEMPERATURE _ _ THRESHOLD, eine IOCTL zum Abrufen und Festlegen von Temperaturschwellenwerte. Sie können sie auch verwenden, um die aktuelle Temperatur des Geräts zu erhalten. Der Eingabe-/Ausgabepuffer für diese IOCTL ist die STORAGE _ TEMPERATURE _ INFO-Struktur aus dem vorherigen Codeabschnitt.

Beispiel: Festlegen der Temperatur über dem Schwellenwert

In diesem Beispiel wird die Überschwellentemperatur eines NVMe-Laufwerks festgelegt. Der folgende Code bereitet den Befehl 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 herstellerspezifischer Features

Ohne das Befehlseffektprotokoll hat der Treiber keine Kenntnis über die Auswirkungen des Befehls. Aus diesem Grund ist das Command Effects-Protokoll erforderlich. Damit kann das Betriebssystem ermitteln, ob ein Befehl eine hohe Auswirkung hat und 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, anbieterspezifische Set-Features-Befehle zu senden. Es ist jedoch möglich, den weiter oben erläuterten Pass-Through-Mechanismus zu verwenden, um anbieterspezifische Befehle zu senden. Weitere Informationen finden Sie unter Pass-Through-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 allgemeine Win32-IOCTL-Definitionen, einschließlich Speicher-APIs für Benutzermodusanwendungen.