Arbeta med NVMe-enheter

gäller för:

  • Windows 10
  • Windows Server 2016

Lär dig hur du arbetar med nvme-enheter med hög hastighet från ditt Windows-program. Enhetsåtkomst aktiveras via StorNVMe.sys, den inkorgsdrivrutin som först introducerades i Windows Server 2012 R2 och Windows 8.1. Denna är också tillgänglig för Windows 7-enheter via en KB-snabbkorrigering. I Windows 10 introducerades flera nya funktioner, bland annat en direktströmningsmekanism för leverantörsspecifika NVMe-kommandon och uppdateringar av befintliga IOCTL:er.

Det här avsnittet innehåller en översikt över allmänna API:er som du kan använda för att komma åt NVMe-enheter i Windows 10. Den beskriver också:

API:er för att arbeta med NVMe-enheter

Du kan använda följande allmänna API:er för att komma åt NVMe-enheter i Windows 10. Dessa API:er finns i winioctl.h för användarlägesprogram och ntddstor.h för kernellägesdrivrutiner. Mer information om huvudfiler finns i Rubrikfiler.

  • IOCTL_STORAGE_PROTOCOL_COMMAND : Använd denna IOCTL med STORAGE_PROTOCOL_COMMAND-strukturen för att utfärda NVMe-kommandon. Denna IOCTL möjliggör NVMe-direktströmning och stöder loggen för kommandoeffekter i NVMe. Du kan använda den med leverantörsspecifika kommandon. Mer information finns i Genomföringsmekanism.

  • STORAGE_PROTOCOL_COMMAND : Den här indatabuffertstrukturen innehåller ett ReturnStatus-fält som kan användas för att rapportera följande statusvärden.

    • 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 : Använd denna IOCTL med STORAGE_PROPERTY_QUERY struktur för att hämta enhetsinformation. Mer information finns i Protokollspecifika frågor och Temperaturfrågor.

  • STORAGE_PROPERTY_QUERY : Den här strukturen innehåller fälten PropertyId och AdditionalParameters för att ange vilka data som ska efterfrågas. I egenskaps-ID-fältet använder du STORAGE_PROPERTY_ID-uppräkningen för att ange typen av data. Använd fältet AdditionalParameters för att ange mer information, beroende på typen av data. För protokollspecifika data använder du strukturen STORAGE_PROTOCOL_SPECIFIC_DATA i fältet AdditionalParameters. För temperaturdata använder du STORAGE_TEMPERATURE_INFO-strukturen i fältet AdditionalParameters.

  • STORAGE_PROPERTY_ID : Den här uppräkningen innehåller nya värden som gör att IOCTL_STORAGE_QUERY_PROPERTY kan hämta protokollspecifik information och temperaturinformation.

    • StorageAdapterProtocolSpecificProperty: Om ProtocolType = ProtocolTypeNvme och DataType = NVMeDataTypeLogPagebör anropare begära 512 byte-datasegment.

    • StorageDeviceProtocolSpecificProperty

      Använd ett av dessa protokollspecifika egenskaps-ID:er i kombination med STORAGE_PROTOCOL_SPECIFIC_DATA för att hämta protokollspecifika data i STORAGE_PROTOCOL_DATA_DESCRIPTOR struktur.

      • LagringsadapterTemperaturEgenskap
      • LagringsenhetTemperaturEgenskap

      Använd något av dessa temperaturegenskaps-ID:er för att hämta temperaturdata i STORAGE_TEMPERATURE_DATA_DESCRIPTOR struktur.

  • STORAGE_PROTOCOL_SPECIFIC_DATA : Hämta NVMe-specifika data när den här strukturen används för fältet AdditionalParametersi STORAGE_PROPERTY_QUERY och ett STORAGE_PROTOCOL_NVME_DATA_TYPE uppräkningsvärde anges. Använd något av följande STORAGE_PROTOCOL_NVME_DATA_TYPE värden i fältet DataType i STORAGE_PROTOCOL_SPECIFIC_DATA-strukturen :

    • Använd NVMeDataTypeIdentify för att hämta data för identifiera kontrollant eller identifiera namnområdesdata.
    • Använd NVMeDataTypeLogPage för att hämta loggsidor (inklusive SMART/hälsodata).
    • Använd NVMeDataTypeFeature för att hämta funktioner på NVMe-enheten.
  • STORAGE_TEMPERATURE_INFO : Den här strukturen används för att lagra specifika temperaturdata. Den används i STORAGE_TEMERATURE_DATA_DESCRIPTOR för att returnera resultatet av en temperaturfråga.

  • IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD : Använd denna IOCTL med STORAGE_TEMPERATURE_THRESHOLD struktur för att ange temperaturtrösklar. Mer information finns i Beteendeförändrande kommandon.

  • STORAGE_TEMPERATURE_THRESHOLD : Den här strukturen används som indatabuffert för att ange temperaturtröskeln. Fältet OverThreshold (booleskt värde) anger om tröskelvärdet är över tröskelvärdet eller inte (annars är det tröskelvärdet under).

Genomströmningsmekanism

Kommandon som inte definieras i NVMe-specifikationen är de svåraste för värdoperativsystemet att hantera – värden har ingen insikt i vilka effekter kommandona kan ha på målenheten, den exponerade infrastrukturen (namnrymder/blockstorlekar) och dess beteende.

För att bättre kunna utföra sådana enhetsspecifika kommandon via Windows-lagringsstacken gör en ny genomströmningsmekanism att leverantörsspecifika kommandon kan dirigeras. Detta genomströmningsrör kommer också att underlätta utvecklingen av hanterings- och testverktyg. Den här genomströmningsmekanismen kräver dock användning av kommandoeffektsloggen. Dessutom kräver StoreNVMe.sys att alla kommandon, inte bara direktkommandon, ska beskrivas i kommandoeffektsloggen.

Viktigt!

StorNVMe.sys och Storport.sys blockerar alla kommandon till en enhet om det inte beskrivs i kommandoeffektsloggen.

Stöd för kommandoeffektloggen

Kommandoeffektloggen (som beskrivs i Kommandon som stöds och effekter, avsnitt 5.10.1.5 i NVMe Specification 1.2) tillåter beskrivningen av effekterna av leverantörsspecifika kommandon tillsammans med specifikationsdefinierade kommandon. Detta underlättar både validering av kommandostöd och optimering av kommandobeteende och bör därför implementeras för hela uppsättningen kommandon som enheten stöder. Följande villkor beskriver resultatet av hur kommandot skickas baserat på dess post i kommandoeffektsloggen.

För alla specifika kommandon som beskrivs i kommandoeffektloggen...

Medan:

  • Kommandot som stöds (CSUPP) är inställt på "1" som anger att kommandot stöds av kontrollanten (Bit 01)

    Anmärkning

    När CSUPP är inställt på "0" (vilket betyder att kommandot inte stöds) blockeras kommandot

Och om något av följande anges:

  • CCC (Controller Capability Change) är inställt på "1" vilket betyder att kommandot kan ändra kontrollantfunktioner (Bit 04)
  • Namnområdeslagerändring (NIC) är inställt på "1" vilket betyder att kommandot kan ändra antalet eller funktionerna för flera namnområden (Bit 03)
  • NCC (Namespace Capability Change) är inställt på "1" vilket betyder att kommandot kan ändra funktionerna i ett enda namnområde (Bit 02)
  • Kommandoöverföring och körning (CSE) är inställt på 001b eller 010b, vilket betyder att kommandot kan skickas när det inte finns något annat utestående kommando till samma eller något namnområde, och att ett annat kommando inte ska skickas till samma eller något namnområde förrän det här kommandot har slutförts (Bitar 18:16)

Sedan skickas kommandot som det enda kvarstående kommandot till adaptern.

Annars om:

  • Kommandoöverföring och körning (CSE) är inställt på 001b, vilket betyder att kommandot kan skickas när det inte finns något annat utestående kommando till samma namnområde och att ett annat kommando inte ska skickas till samma namnområde förrän kommandot är klart (Bits 18:16)

Sedan skickas kommandot som det enda kvarstående kommandot till LUN-objektet (Logical Unit Number).

Annars skickas kommandot med andra kommandon som är utestående utan hämning. Om ett leverantörsspecifikt kommando till exempel skickas till enheten för att hämta statistisk information som inte är specifikationsdefinierad bör det inte finnas någon risk för att ändra enhetens beteende eller förmåga att köra I/O-kommandon. Sådana begäranden kan hanteras parallellt med I/O och det skulle inte vara nödvändigt att pausa och återuppta.

Använda IOCTL_STORAGE_PROTOCOL_COMMAND för att skicka kommandon

Genomströmning kan utföras med hjälp av IOCTL_STORAGE_PROTOCOL_COMMAND, som introducerades i Windows 10. Denna IOCTL har utformats för att ha ett liknande beteende som befintliga SCSI- och ATA-direkt-IOCTL:er för att skicka ett inbäddat kommando till målenheten. Via denna IOCTL kan direktströmning skickas till en lagringsenhet, inklusive en NVMe-enhet.

I NVMe tillåter till exempel IOCTL att följande kommandokoder skickas ned.

  • Leverantörsspecifika administratörskommandon (C0h – FFh)
  • Leverantörsspecifika NVMe-kommandon (80h – FFh)

Precis som med alla andra IOCTL:er använder du DeviceIoControl för att skicka direkt-IOCTL nedåt. IOCTL fylls i med hjälp av den STORAGE_PROTOCOL_COMMAND indatabuffertstrukturen som finns i ntddstor.h. Fyll i fältet Kommando med det leverantörsspecifika kommandot.

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;

Det leverantörsspecifika kommando som ska skickas ska fyllas i i det markerade fältet ovan. Observera återigen att kommandoeffektloggen måste implementeras för direktkommandon. I synnerhet måste dessa kommandon rapporteras som stöd i kommandoeffektloggen (se föregående avsnitt för mer information). Observera också att PRP-fält är drivrutinsspecifika, vilket innebär att program som skickar kommandon kan lämna dem som 0.

Slutligen är denna pass-through IOCTL avsedd för att skicka leverantörsspecifika kommandon. Om du vill skicka andra administratörs- eller icke-leverantörsspecifika NVMe-kommandon, till exempel Identifiera, ska denna direkt-IOCTL inte användas. Till exempel bör IOCTL_STORAGE_QUERY_PROPERTY användas för Identifiera eller Hämta loggsidor. Mer information finns i nästa avsnitt, Protokollspecifika frågor.

Uppdatera inte inbyggd programvara via direktströmningsmekanismen

Kommandon för nedladdning och aktivering av firmware ska inte skickas via genomströmning. IOCTL_STORAGE_PROTOCOL_COMMAND ska endast användas för leverantörsspecifika kommandon.

Använd istället följande allmänna lagrings-IOCTLs (introducerade i Windows 10) för att undvika att applikationer direkt använder SCSI_miniport-versionen av IOCTL för inbyggd programvara. Lagringsdrivrutiner översätter IOCTL till antingen ett SCSI-kommando eller den SCSI_miniport versionen av IOCTL till miniporten.

Dessa IOCTL:er rekommenderas för utveckling av uppgraderingsverktyg för inbyggd programvara i Windows 10 och Windows Server 2016:

För att få lagringsinformation och uppdatera inbyggd programvara har Windows även stöd för PowerShell-cmdletar för att göra detta snabbt:

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

Returnera fel via genomströmningsmekanismen

I likhet med SCSI- och ATA-pass-through-IOCTLs returnerar IOCTL om ett kommando eller en begäran som skickas till miniporten eller enheten lyckades eller inte. I den STORAGE_PROTOCOL_COMMAND strukturen returnerar IOCTL statusen via fältet ReturnStatus .

Exempel: skicka ett leverantörsspecifikt kommando

I det här exemplet skickas ett godtyckligt leverantörsspecifikt kommando (0xFF) via direktströmning till en NVMe-enhet. Följande kod allokerar en buffert, initierar en fråga och skickar sedan kommandot till enheten via DeviceIoControl.

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

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

command = (PNVME_COMMAND)protocolCommand->Command;  

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

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

I det här exemplet förväntar vi oss protocolCommand->ReturnStatus == STORAGE_PROTOCOL_STATUS_SUCCESS om kommandot lyckades på enheten.

Protokollspecifika frågor

Windows 8.1 introducerade IOCTL_STORAGE_QUERY_PROPERTY för datahämtning. I Windows 10 förbättrades IOCTL för att stödja vanliga NVMe-funktioner som Hämta loggsidor, Hämta funktioner och Identifiera. Detta möjliggör hämtning av NVMe-specifik information för övervaknings- och inventeringsändamål.

Indatabufferten för IOCTL , STORAGE_PROPERTY_QUERY (från Windows 10 och senare) visas här:

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

När du använder IOCTL_STORAGE_QUERY_PROPERTY för att hämta NVMe-protokollspecifik information i STORAGE_PROTOCOL_DATA_DESCRIPTOR konfigurerar du STORAGE_PROPERTY_QUERY-strukturen på följande sätt:

Den STORAGE_PROTOCOL_SPECIFIC_DATA strukturen (från Windows 10 och senare) visas här:

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;

Om du vill ange en typ av NVMe-protokollspecifik information konfigurerar du STORAGE_PROTOCOL_SPECIFIC_DATA-strukturen på följande sätt:

  • Ange fältet ProtocolType till ProtocolTypeNVMe.
  • Ange fältet DataType till ett uppräkningsvärde som definierats av STORAGE_PROTOCOL_NVME_DATA_TYPE:
    • Använd NVMeDataTypeIdentify för att hämta data för identifiera kontrollant eller identifiera namnområdesdata.
    • Använd NVMeDataTypeLogPage för att hämta loggsidor (inklusive SMART/hälsodata).
    • Använd NVMeDataTypeFeature för att hämta funktioner på NVMe-enheten.

När ProtocolTypeNVMe används som ProtocolType kan frågor om protokollspecifik information hämtas parallellt med andra I/O på NVMe-enheten.

Viktigt!

För en IOCTL_STORAGE_QUERY_PROPERTY som använder en STORAGE_PROPERTY_ID av typen StorageAdapterProtocolSpecificProperty, och vars STORAGE_PROTOCOL_SPECIFIC_DATA eller STORAGE_PROTOCOL_SPECIFIC_DATA_EXT-struktur har angetts till ProtocolType=ProtocolTypeNvme och DataType=NVMeDataTypeLogPage, ska medlemmen ProtocolDataLength i samma struktur ställas in på ett minimivärde av 512 byte.

Följande exempel visar NVMe-protokollspecifika frågor.

Exempel: NVMe Identifiera-query

I det här exemplet skickas begäran Identifiera till en NVMe-enhet. Följande kod initierar frågedatastrukturen och skickar sedan kommandot till enheten via DeviceIoControl.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

protocolData = &protocolDataDescr->ProtocolSpecificData;

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

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

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

Viktigt!

För en IOCTL_STORAGE_QUERY_PROPERTY som använder en STORAGE_PROPERTY_ID av typen StorageAdapterProtocolSpecificProperty, och vars STORAGE_PROTOCOL_SPECIFIC_DATA eller STORAGE_PROTOCOL_SPECIFIC_DATA_EXT-struktur har angetts till ProtocolType=ProtocolTypeNvme och DataType=NVMeDataTypeLogPage, ska medlemmen ProtocolDataLength i samma struktur ställas in på ett minimivärde av 512 byte.

Observera att anroparen måste allokera en enda buffert som innehåller STORAGE_PROPERTY_QUERY och storleken på STORAGE_PROTOCOL_SPECIFIC_DATA. I det här exemplet använder den samma buffert för indata och utdata från egenskapsfrågan. Det är därför man har allokerat en buffert med storleken "FIELD_OFFSET(STORAGE_PROPERTY_QUERY, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE". Även om separata buffertar kan allokeras för både indata och utdata rekommenderar vi att du använder en enda buffert för att fråga NVMe-relaterad information.

identifyControllerData-NN> är Antal namnområden (NN). Windows identifierar ett namnområde som en fysisk disk.

Exempel: NVMe Hämta loggsidor förfrågan

I det här exemplet, baserat på föregående, skickas begäran Hämta loggsidor till en NVMe-enhet. Följande kod förbereder frågedatastrukturen och skickar sedan kommandot till enheten via DeviceIoControl.

ZeroMemory(buffer, bufferLength);  

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

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

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

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

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

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

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

protocolData = &protocolDataDescr->ProtocolSpecificData;

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

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

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

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

Anropare kan använda en STORAGE_PROPERTY_ID av typen StorageAdapterProtocolSpecificProperty, och vars STORAGE_PROTOCOL_SPECIFIC_DATA eller STORAGE_PROTOCOL_SPECIFIC_DATA_EXT-struktur är inställd på att begära block om 512 byte av specifik leverantörsdata.

Exempel: NVMe fråga för att hämta funktioner

I det här exemplet, baserat på det tidigare, skickas begäran Hämta funktioner till en NVMe-enhet. Följande kod förbereder frågedatastrukturen och skickar sedan kommandot till enheten via DeviceIoControl.

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

ZeroMemory(buffer, bufferLength);  


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

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

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

//  
// Send request down.  
//  

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

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

//  
// Validate the returned data.  
//  

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

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

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

Protokollspecifik uppsättning

Observera att i Windows 10 19H1 och senare förbättrades IOCTL_STORAGE_SET_PROPERTY för att stödja NVMe-uppsättningsfunktioner.

Indatabufferten för IOCTL_STORAGE_SET_PROPERTY visas här:

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;

När du använder IOCTL_STORAGE_SET_PROPERTY för att ange NVMe-funktionen konfigurerar du STORAGE_PROPERTY_SET-strukturen på följande sätt:

  • Allokera en buffert som kan innehålla både en STORAGE_PROPERTY_SET och en STORAGE_PROTOCOL_SPECIFIC_DATA_EXT struktur.
  • Ange fältet PropertyID till StorageAdapterProtocolSpecificProperty eller StorageDeviceProtocolSpecificProperty för en kontrollant eller enhets-/namnområdesbegäran.
  • Fyll i STORAGE_PROTOCOL_SPECIFIC_DATA_EXT struktur med önskade värden. Början av STORAGE_PROTOCOL_SPECIFIC_DATA_EXT är AdditionalParameters-fältet i STORAGE_PROPERTY_SET.

Den STORAGE_PROTOCOL_SPECIFIC_DATA_EXT strukturen visas här.

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;

Om du vill ange en typ av NVMe-funktion som ska anges konfigurerar du den STORAGE_PROTOCOL_SPECIFIC_DATA_EXT strukturen på följande sätt:

  • Ange fältet ProtocolType till ProtocolTypeNvme;
  • Ange fältet DataType till uppräkningsvärdet NVMeDataTypeFeature som definierats av STORAGE_PROTOCOL_NVME_DATA_TYPE.

I följande exempel visas NVMe-funktionsuppsättningen.

Exempel: NVMe-uppsättningsfunktioner

I det här exemplet skickas begäran Ange funktioner till en NVMe-enhet. Följande kod förbereder datastrukturen för uppsättningen och skickar sedan kommandot till enheten via DeviceIoControl.

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

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

buffer = new UCHAR[bufferLength];

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

setProperty = (PSTORAGE_PROPERTY_SET)buffer;

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

protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA_EXT)setProperty->AdditionalParameters;

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

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

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

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

Temperaturfrågor

I Windows 10 och senare kan IOCTL_STORAGE_QUERY_PROPERTY också användas för att köra frågor mot temperaturdata från NVMe-enheter.

Om du vill hämta temperaturinformation från en NVMe-enhet i STORAGE_TEMPERATURE_DATA_DESCRIPTOR konfigurerar du STORAGE_PROPERTY_QUERY-strukturen på följande sätt:

  • Allokera en buffert som kan innehålla en STORAGE_PROPERTY_QUERY struktur.
  • Ange fältet PropertyID till StorageAdapterTemperatureProperty eller StorageDeviceTemperatureProperty för en kontrollant eller enhets-/namnområdesbegäran.
  • Ange fältet QueryType till PropertyStandardQuery.

Den STORAGE_TEMPERATURE_INFO strukturen (finns i Windows 10 och senare) visas här:

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;

Beteendeförändrande kommandon

Kommandon som manipulerar enhetsattribut eller kan påverka enhetens beteende är svårare för operativsystemet att hantera. Om enhetsattribut ändras vid körning medan I/O bearbetas kan problem med synkronisering eller dataintegritet uppstå om de inte hanteras korrekt.

Kommandot NVMe Set-Features är ett bra exempel på ett beteendeförändrande kommando. Det gör det möjligt att ändra skiljedomsmekanismen och fastställa temperaturtrösklar. För att säkerställa att pågående dataöverföring inte är i fara när beteendepåverkande inställningskommandon skickas ned, pausar Windows alla I/O till NVMe-enheten, tömmer köer och rensar buffertar. När kommandot set har körts återupptas I/O (om möjligt). Om I/O inte kan återupptas kan en enhetsåterställning krävas.

Ställa in temperaturtröskelvärden

Windows 10 introducerade IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD, en IOCTL för att få och ställa in temperaturtröskelvärden. Du kan också använda den för att hämta enhetens aktuella temperatur. Indata-/utdatabufferten för denna IOCTL är STORAGE_TEMPERATURE_INFO-strukturen från föregående kodavsnitt.

Exempel: Ställa in övertröskeltemperatur

I det här exemplet anges en NVMe-enhets övertröskeltemperatur. Följande kod förbereder kommandot och skickar det sedan till enheten via DeviceIoControl.

BOOL    result;  
ULONG   returnedLength = 0;  

STORAGE_TEMPERATURE_THRESHOLD setThreshold = {0};  

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

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

Ange leverantörsspecifika funktioner

Utan kommandoeffektloggen har drivrutinen ingen kunskap om konsekvenserna av kommandot. Därför krävs loggen för kommandoeffekter. Det hjälper operativsystemet att avgöra om ett kommando har stor inverkan och om det kan skickas parallellt med andra kommandon till enheten.

Kommandoeffektloggen är ännu inte tillräckligt detaljerad för att omfatta leverantörsspecifika Set-Features-kommandon . Därför är det ännu inte möjligt att skicka leverantörsspecifika Set-Features-kommandon . Det är dock möjligt att använda direktmekanismen, som beskrevs tidigare, för att skicka leverantörsspecifika kommandon. Mer information finns i Genomföringsmekanism.

Rubrikfiler

Följande filer är relevanta för NVMe-utveckling. Dessa filer ingår i Microsoft Windows Software Development Kit (SDK).

Header-fil Beskrivning
ntddstor.h Definierar konstanter och typer för åtkomst till lagringsklassdrivrutiner från kernelläge.
nvme.h För andra NVMe-relaterade datastrukturer.
winioctl.h För övergripande Win32 IOCTL-definitioner, inklusive lagrings-API:er för användarlägesprogram.

NVMe-specifikation 1.2