Freigeben über


Arbeiten mit NVMe-Laufwerken

Gilt für:

  • Windows 10
  • Windows Server 2016

Erfahren Sie, wie Sie mit NVMe-Geräten mit hoher Geschwindigkeit aus Ihrer Windows-Anwendung arbeiten. Der Gerätezugriff wird über StorNVMe.sys aktiviert, den in Windows Server 2012 R2 und Windows 8.1 eingeführten Inbox-Treiber. Es ist auch für Windows 7-Geräte über einen KB-Hot Fix verfügbar. In Windows 10 wurden mehrere neue Features eingeführt, darunter ein Pass-Through-Mechanismus für anbieterspezifische NVMe-Befehle und Updates für vorhandene IOCTLs.

Dieses Thema enthält eine Übersicht über allgemeine APIs, die Sie für den Zugriff auf NVMe-Laufwerke in Windows 10 verwenden können. Es beschreibt auch Folgendes:

APIs für das Arbeiten mit NVMe-Laufwerken

Sie können die folgenden allgemeinen APIs verwenden, 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 STORAGE_PROTOCOL_COMMAND Struktur, um NVMe-Befehle ausstellen zu können. Diese IOCTL aktiviert NVMe Pass-Through und unterstützt das Command Effects-Protokoll in NVMe. Sie können es mit anbieterspezifischen Befehlen verwenden. Weitere Informationen finden Sie im Pass-Through-Mechanismus.

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

    • SPEICHERPROTOKOLL_STATUS_AUSSTEHEND
    • STORAGE_PROTOCOL_STATUS_SUCCESS
    • STORAGE_PROTOCOL_STATUS_ERROR
    • STORAGE_PROTOCOL_STATUS_INVALID_REQUEST
    • SPEICHER_PROTOCOLSTATUS_KEIN_GERÄT
    • 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 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 in der abgelegten PropertyId die STORAGE_PROPERTY_ID-Aufzählung , 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 können.

    • StorageAdapterProtocolSpecificProperty: Wenn ProtocolType = ProtocolTypeNvme und DataType = NVMeDataTypeLogPage erfüllt sind, sollten Aufrufer 512 Byte große Datenblöcke 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
      • SpeichergerättemperaturEigenschaft

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

  • STORAGE_PROTOCOL_SPECIFIC_DATA : Abrufen von NVMe-spezifischen Daten, 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 STORAGE_PROTOCOL_NVME_DATA_TYPE Werte im Feld "Datentyp " der STORAGE_PROTOCOL_SPECIFIC_DATA Struktur:

    • Verwenden Sie NVMeDataTypeIdentify, um Daten des Verantwortlichen abzurufen oder Namespacedaten zu identifizieren.
    • 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 dient zum Halten bestimmter Temperaturdaten. Sie wird in der STORAGE_TEMERATURE_DATA_DESCRIPTOR verwendet, um die Ergebnisse einer Temperaturabfrage zurückzugeben.

  • IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD : Verwenden Sie diese IOCTL mit der STORAGE_TEMPERATURE_THRESHOLD Struktur, um Temperaturschwellenwerte festzulegen. Weitere Informationen finden Sie unter Verhaltensänderungsbefehle.

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

Pass-Through-Mechanismus

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

Um gerätespezifische Befehle besser über den Windows-Speicherstapel zu übertragen, ermöglicht ein neuer Pass-Through-Mechanismus die Weiterleitung von anbieterspezifischen Befehlen. Dieses Pass-Through-Rohr wird auch bei der Entwicklung von Management- und Testwerkzeugen helfen. Dieser Pass-Through-Mechanismus erfordert jedoch die Verwendung des Befehlseffekteprotokolls. Darüber hinaus erfordert StoreNVMe.sys, dass alle Befehle, nicht nur Pass-Through-Befehle, im Befehlseffektprotokoll beschrieben werden.

Von Bedeutung

StorNVMe.sys und Storport.sys blockieren jeden Befehl an ein Gerät, wenn dieser nicht im Protokoll der Befehlsauswirkungen beschrieben ist.

Unterstützung des Befehlseffektprotokolls

Das Befehlseffekteprotokoll (wie in Befehlen unterstützt und Effekte beschrieben, Abschnitt 5.10.1.5 der NVMe-Spezifikation 1.2) ermöglicht die Beschreibung der Auswirkungen anbieterspezifischer 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 basierend auf dem Eintrag "Command Effects Log" gesendet wird.

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

While:

  • Befehl Unterstützt (CSUPP) wird auf "1" festgelegt, um zu signalisieren, dass der Befehl vom Controller (Bit 01) unterstützt wird.

    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 Optionen festgelegt ist:

  • Die Änderung der Controllerfähigkeiten (Controller Capability Change, CCC) ist auf "1" gesetzt, was bedeutet, dass der Befehl die Controllerfähigkeiten ändern kann (Bit 04).
  • Die Änderung des Namespace-Inventars (Namespace Inventory Change, NIC) ist auf "1" festgelegt, was anzeigt, dass der Befehl die Anzahl oder die Fähigkeiten für mehrere Namespaces (Bit 03) ändern kann.
  • Namespace Capability Change (NCC) ist auf "1" festgelegt, was bedeutet, dass der Befehl die Funktionen eines einzelnen Namespaces (Bit 02) ändern kann.
  • Die Befehlsübermittlung und -ausführung (COMMAND Submission and Execution, 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 Namespace gesendet wird, und dass ein anderer Befehl nicht an denselben oder einen Namespace gesendet werden sollte, bis dieser Befehl abgeschlossen ist (Bits 18:16)

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

else if:

  • Die Befehlsübermittlung und -ausführung (CSE) ist auf 001b festgelegt, was bedeutet, dass der Befehl gesendet werden kann, wenn kein anderer ausstehender Befehl an denselben Namespace vorhanden ist und dass ein anderer Befehl erst dann an denselben Namespace gesendet werden sollte, wenn dieser Befehl abgeschlossen ist (Bits 18:16)

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

Andernfalls wird der Befehl zusammen mit anderen Befehlen gesendet, die ohne Verzögerung ausgeführt werden. 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 Funktion 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 erforderlich.

Verwenden von IOCTL_STORAGE_PROTOCOL_COMMAND zum Senden von Befehlen

Pass-through kann mit dem in Windows 10 eingeführten IOCTL_STORAGE_PROTOCOL_COMMAND durchgeführt werden. Diese IOCTL wurde so konzipiert, dass sie ein ähnliches Verhalten wie die vorhandenen SCSI- und ATA-Pass-Through-IOCTLs aufweist, 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.

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

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

Wie bei allen anderen IOCTLs verwenden Sie DeviceIoControl , um die Pass-Through-IOCTL nach unten zu senden. Die IOCTL wird mit der STORAGE_PROTOCOL_COMMAND Eingabepufferstruktur aufgefüllt, die in ntddstor.h gefunden wurde. Füllen Sie das Feld "Befehl" mit dem anbieterspezifischen 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 ausgefüllt werden. Beachten Sie bitte noch einmal, dass das Protokoll der Befehlswirkungen für Pass-Through-Kommandos implementiert werden muss. Insbesondere müssen diese Befehle im Befehlseffektprotokoll als unterstützt gemeldet werden (weitere Informationen finden Sie im vorherigen Abschnitt). Beachten Sie außerdem, dass PRP-Felder treiberspezifisch sind, damit Anwendungen, die Befehle senden, diese als 0belassen können.

Schließlich ist diese Pass-Through-IOCTL für das Senden von herstellerspezifischen Befehlen vorgesehen. Um andere Administrator- oder nicht anbieterspezifische NVMe-Befehle wie "Identifizieren" zu senden, sollte diese Pass-Through-IOCTL nicht verwendet werden. Beispielsweise sollten 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 Pass-Through-Mechanismus

Firmwaredownload- und Aktivierungsbefehle sollten nicht mithilfe von Pass-Through gesendet werden. IOCTL_STORAGE_PROTOCOL_COMMAND sollten nur für anbieterspezifische Befehle verwendet werden.

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

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

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

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

Zurückgeben von Fehlern über den Pass-Through-Mechanismus

Ähnlich wie bei SCSI- und ATA-Pass-Through-IOCTLs meldet der IOCTL zurück, ob ein Befehl oder eine Anforderung erfolgreich an den Miniport oder das Gerät gesendet wurde. 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) über Pass-Through an ein NVMe-Laufwerk gesendet. Der folgende Code weist einen Puffer zu, initialisiert eine Abfrage und sendet dann den Befehl ü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 erwarten wir protocolCommand->ReturnStatus == STORAGE_PROTOCOL_STATUS_SUCCESS, dass der Befehl auf dem Gerät erfolgreich ausgeführt wurde.

Protokollspezifische Abfragen

Windows 8.1 hat 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 das Abrufen von NVMe-spezifischen Informationen für Überwachungs- und Bestandszwecke.

Der Eingabepuffer für die IOCTL, STORAGE_PROPERTY_QUERY (von Windows 10 und höher) 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 zum Abrufen von NVMe-protokollspezifischen Informationen im STORAGE_PROTOCOL_DATA_DESCRIPTOR verwenden, konfigurieren Sie die STORAGE_PROPERTY_QUERY-Struktur wie folgt:

  • Weisen Sie einen Puffer zu, der sowohl eine STORAGE_PROPERTY_QUERY als auch eine STORAGE_PROTOCOL_SPECIFIC_DATA Struktur enthält.
  • Legen Sie das PropertyID- Feld auf StorageAdapterProtocolSpecificProperty oder StorageDeviceProtocolSpecificProperty für eine Controller- oder Geräte-/Namespaceanforderung fest.
  • Legen Sie das feld QueryType auf PropertyStandardQuery-fest.
  • Füllen Sie die STORAGE_PROTOCOL_SPECIFIC_DATA Struktur mit den gewünschten Werten aus. Der Anfang des STORAGE_PROTOCOL_SPECIFIC_DATA ist das AdditionalParameters Feld von STORAGE_PROPERTY_QUERY.

Die STORAGE_PROTOCOL_SPECIFIC_DATA-Struktur (von Windows 10 und höher) 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 STORAGE_PROTOCOL_SPECIFIC_DATA Struktur wie folgt:

  • Legen Sie das feld ProtocolType auf ProtocolTypeNVMe-fest.
  • Legen Sie das DataType Feld auf einen Enumerationswert fest, der durch STORAGE_PROTOCOL_NVME_DATA_TYPEdefiniert wird:
    • Verwenden Sie NVMeDataTypeIdentify, um Daten des Verantwortlichen abzurufen oder Namespacedaten zu identifizieren.
    • 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 mit anderen E/A auf dem NVMe-Laufwerk abgerufen werden.

Von Bedeutung

Für eine IOCTL_STORAGE_QUERY_PROPERTY, die eine STORAGE_PROPERTY_ID vom Typ StorageAdapterProtocolSpecificProperty verwendet und deren STORAGE_PROTOCOL_SPECIFIC_DATA- oder STORAGE_PROTOCOL_SPECIFIC_DATA_EXT-Struktur auf ProtocolType=ProtocolTypeNvme und DataType=NVMeDataTypeLogPage setzt, legen Sie das Mitglied ProtocolDataLength derselben Struktur auf einen Mindestwert von 512 (Bytes) fest.

Die folgenden Beispiele veranschaulichen NVMe-protokollspezifische Abfragen.

Beispiel: NVMe-Abfrage identifizieren

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

Von Bedeutung

Für eine IOCTL_STORAGE_QUERY_PROPERTY, die eine STORAGE_PROPERTY_ID vom Typ StorageAdapterProtocolSpecificProperty verwendet und deren STORAGE_PROTOCOL_SPECIFIC_DATA- oder STORAGE_PROTOCOL_SPECIFIC_DATA_EXT-Struktur auf ProtocolType=ProtocolTypeNvme und DataType=NVMeDataTypeLogPage setzt, legen Sie das Mitglied ProtocolDataLength 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 von STORAGE_PROTOCOL_SPECIFIC_DATA enthält. In diesem Beispiel wird derselbe Puffer für die Eingabe und Ausgabe aus der Eigenschaftenabfrage verwendet. Aus diesem Grund weist der zugeordnete Puffer eine Größe von "FIELD_OFFSET(STORAGE_PROPERTY_QUERY, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE" auf. Obwohl separate Puffer sowohl für Die Eingabe als auch für die Ausgabe zugewiesen werden können, empfehlen wir die Verwendung eines einzelnen Puffers zum Abfragen von NVMe-bezogenen Informationen.

identifyControllerData->NN ist die Anzahl der Namespaces (NN). Windows erkennt einen Namespace als physisches Laufwerk.

Beispiel: NvMe Get Log Pages-Abfrage

In diesem Beispiel, basierend auf dem vorherigen, wird die Anforderung " Protokollseiten abrufen" an ein NVMe-Laufwerk gesendet. Der folgende Code bereitet die Abfragedatenstruktur vor und sendet dann den Befehl ü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 StorageAdapterProtocolSpecificProperty verwenden, wobei die STORAGE_PROTOCOL_SPECIFIC_DATA oder STORAGE_PROTOCOL_SPECIFIC_DATA_EXT Struktur auf ProtocolDataRequestValue=VENDOR_SPECIFIC_LOG_PAGE_IDENTIFIER gesetzt ist, um 512-Byte-Blöcke von anbieterspezifischen Daten anzufordern.

Beispiel: NVMe Get Features-Abfrage

In diesem Beispiel, basierend auf dem vorherigen, wird die Anforderung " Features abrufen " an ein NVMe-Laufwerk gesendet. Der folgende Code bereitet die Abfragedatenstruktur vor und sendet dann den Befehl ü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

Beachten Sie, dass in Windows 10 19H1 und höher die IOCTL_STORAGE_SET_PROPERTY erweitert wurde, 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 STORAGE_PROPERTY_SET Struktur wie folgt:

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

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

  • Legen Sie das ProtocolType-Feld auf "ProtocolTypeNvme" fest.
  • Legen Sie das DataType-Feld auf den Enumerationswert NVMeDataTypeFeature fest, das von STORAGE_PROTOCOL_NVME_DATA_TYPE definiert wird;

Die folgenden Beispiele veranschaulichen den NVMe-Funktionssatz.

Beispiel: NVMe-Feature-Einstellungen

In diesem Beispiel wird die Anforderung "Features festlegen" an ein NVMe-Laufwerk gesendet. Der folgende Code bereitet die Setdatenstruktur 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 und höher können IOCTL_STORAGE_QUERY_PROPERTY auch zum Abfragen von Temperaturdaten von NVMe-Geräten verwendet werden.

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

  • Weisen Sie einen Puffer zu, der eine STORAGE_PROPERTY_QUERY Struktur enthält.
  • Legen Sie das PropertyID-Feld auf "StorageAdapterTemperatureProperty " oder "StorageDeviceTemperatureProperty " für einen Controller oder eine Geräte-/Namespaceanforderung fest.
  • Legen Sie das feld QueryType auf PropertyStandardQuery-fest.

Die STORAGE_TEMPERATURE_INFO Struktur (in Windows 10 und höher verfügbar) 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;

Verhaltensänderungsbefehle

Befehle, die Geräteattribute bearbeiten oder das Geräteverhalten beeinträchtigen, 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 Befehl " NVMe-Set-Features" ist ein gutes Beispiel für einen Verhaltensänderungsbefehl. Es ermöglicht die Änderung des Schiedsmechanismus und die Festlegung von Temperaturschwellenwerten. Um sicherzustellen, dass in-flight Daten nicht gefährdet sind, wenn verhaltensbeeinflussende Set-Befehle gesendet werden, hält Windows alle E/A-Vorgänge zum NVMe-Gerät an, entwässert Warteschlangen und leert Puffer. Sobald der Setbefehl erfolgreich ausgeführt wurde, wird E/A (sofern möglich) fortgesetzt. Wenn E/A nicht fortgesetzt werden kann, kann eine Geräterücksetzung erforderlich sein.

Festlegen von Temperaturschwellenwerten

Windows 10 hat IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD eingeführt, ein IOCTL zum Abrufen und Festlegen von Temperaturschwellenwerten. Sie können es 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 Überschwellentemperatur

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 von herstellerspezifischen Features

Ohne das Befehlseffektprotokoll verfügt der Treiber nicht über die Auswirkungen des Befehls. Aus diesem Grund ist das Befehlseffekteprotokoll erforderlich. Es hilft dem Betriebssystem zu ermitteln, ob ein Befehl hohe Auswirkungen hat und ob er parallel zu anderen Befehlen an das Laufwerk gesendet werden kann.

Das Command Effects Log ist noch nicht präzise genug, um herstellerspezifische Set-Features-Befehle einzuschließen. Aus diesem Grund ist es noch nicht möglich, anbieterspezifische Set-Features-Befehle zu senden. Es ist jedoch möglich, den zuvor erläuterten Pass-Through-Mechanismus zum Senden von anbieterspezifischen Befehlen zu verwenden. Weitere Informationen finden Sie im 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.

Header-Datei 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.

NVMe-Spezifikation 1.2