Sdílet prostřednictvím


Práce s jednotkami NVMe

platí pro:

  • Windows 10
  • Windows Server 2016

Naučte se pracovat s vysokorychlostními zařízeními NVMe z aplikace pro Windows. Přístup zařízení je umožněn pomocí StorNVMe.sys, integrovaného ovladače poprvé zavedeného v systému Windows Server 2012 R2 a Windows 8.1. Je také k dispozici pro zařízení s Windows 7 prostřednictvím opravy hotfix KB. Ve Windows 10 bylo zavedeno několik nových funkcí, včetně mechanismu průchodu pro příkazy NVMe specifické pro dodavatele a aktualizace stávajících IOCTLs.

Toto téma obsahuje přehled rozhraní API pro obecné použití, která můžete použít pro přístup k jednotkám NVMe ve Windows 10. Popisuje také:

Rozhraní API pro práci s NVMe disky

Pro přístup k jednotkám NVMe ve Windows 10 můžete použít následující obecná rozhraní API. Tato rozhraní API najdete v winioctl.h pro aplikace v uživatelském režimu a ntddstor.h pro ovladače režimu jádra. Další informace o souborech hlaviček naleznete v tématu Soubory hlaviček.

  • IOCTL_STORAGE_PROTOCOL_COMMAND: K vydávání příkazů NVMe použijte tuto strukturu IOCTL se strukturou STORAGE_PROTOCOL_COMMAND. Tato hodnota IOCTL umožňuje průchozí nvMe a podporuje protokol Command Effects v NVMe. Můžete ho použít s příkazy specifickými pro dodavatele. Další informace naleznete v sekci Předávací mechanismus.

  • STORAGE_PROTOCOL_COMMAND: Tato struktura vstupní vyrovnávací paměti obsahuje pole ReturnStatus, které lze použít k hlášení následujících hodnot stavu.

    • STORAGE_PROTOCOL_STATUS_PENDING
    • STORAGE_PROTOCOL_STATUS_SUCCESS
    • STORAGE_PROTOCOL_STATUS_ERROR
    • STATUS_PROTOKOLU_UKLÁDÁNÍ_NEPLATNÝ_POŽADAVEK
    • STATUS_PROTOKOLU_UKLÁDÁNÍ_BEZ_ZAŘÍZENÍ
    • STAV_PROTOKOLU_UKLÁDÁNÍ_ZANEPRÁZDNĚN
    • STATUS_PROTOKOLU_UKLÁDÁNÍ_PŘEKROČENÍ_DAT
    • STAV_PROTOKOLU_UKLÁDÁNÍ_NEDOSTATEČNÉ_ZDROJE
    • STAV PROTOKOLU NENÍ PODPOROVÁN
  • IOCTL_STORAGE_QUERY_PROPERTY: Pomocí této hodnoty IOCTL se strukturou STORAGE_PROPERTY_QUERY načtěte informace o zařízení. Další informace naleznete v tématech dotazy týkající se protokolu a dotazy k teplotě.

  • STORAGE_PROPERTY_QUERY: Tato struktura zahrnuje pole PropertyId a AdditionalParameters pole k určení dat, která se mají dotazovat. V poli PropertyId použijte výčet STORAGE_PROPERTY_ID k určení typu dat. Pomocí pole AdditionalParameters zadejte další podrobnosti v závislosti na typu dat. Pro data specifická pro protokol použijte strukturu STORAGE_PROTOCOL_SPECIFIC_DATA v poli AdditionalParameters. Pro data o teplotě použijte strukturu STORAGE_TEMPERATURE_INFO v poli AdditionalParameters.

  • STORAGE_PROPERTY_ID: Tento výčet obsahuje nové hodnoty, které umožňují IOCTL_STORAGE_QUERY_PROPERTY načíst informace o konkrétní protokol a teplotě.

    • StorageAdapterProtocolSpecificProperty: Pokud ProtocolType = ProtocolTypeNvme a DataType = NVMeDataTypeLogPage, volající by měli požádat o 512 bajtových bloků dat.
    • VlastnostProtokoluÚložištěZařízení

    Pomocí jednoho z těchto ID vlastností specifických pro protokol v kombinaci s STORAGE_PROTOCOL_SPECIFIC_DATA načtěte data specifická pro protokoly ve struktuře STORAGE_PROTOCOL_DATA_DESCRIPTOR.

    • VlastnostTeplotyAdaptéruÚložiště
    • StorageDeviceTemperatureProperty

    Pomocí jednoho z těchto ID vlastností teploty načtěte data o teplotě ve struktuře STORAGE_TEMPERATURE_DATA_DESCRIPTOR.

  • STORAGE_PROTOCOL_SPECIFIC_DATA: Načtěte data specifická pro NVMe, když je tato struktura použita pro pole AdditionalParameters struktury STORAGE_PROPERTY_QUERY a je zadána hodnota výčtu STORAGE_PROTOCOL_NVME_DATA_TYPE. Použijte jednu z následujících hodnot STORAGE_PROTOCOL_NVME_DATA_TYPE v poli DataType struktury STORAGE_PROTOCOL_SPECIFIC_DATA:

    • Pomocí NVMeDataTypeIdentify získejte data Identify Controller nebo data Identify Namespace.
    • Pomocí NVMeDataTypeLogPage můžete získat stránky protokolu (včetně dat SMART/health).
    • Pomocí NVMeDataTypeFeature získejte funkce jednotky NVMe.
  • STORAGE_TEMPERATURE_INFO: Tato struktura se používá k uchovávání konkrétních dat o teplotě. Používá se v STORAGE_TEMERATURE_DATA_DESCRIPTOR k vrácení výsledků dotazu na teplotu.

  • IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD: Tuto hodnotu IOCTL použijte se strukturou STORAGE_TEMPERATURE_THRESHOLD k nastavení prahových hodnot teploty. Další informace najdete v tématu Chování při změně příkazů.

  • STORAGE_TEMPERATURE_THRESHOLD: Tato struktura se používá jako vstupní vyrovnávací paměť k určení prahové hodnoty teploty. Pole OverThreshold (boolean) určuje, jestli je pole Prahová hodnota nad prahovou hodnotou (jinak se jedná o hodnotu pod prahovou hodnotou).

Propustný mechanismus

Příkazy, které nejsou definované ve specifikaci NVMe, jsou pro hostitelský operační systém nejobtížnější – hostitel nemá přehled o efektech, které mohou mít příkazy na cílovém zařízení, vystavenou infrastrukturu (obory názvů/velikosti bloků) a jeho chování.

Aby bylo možné tyto příkazy specifické pro zařízení přenášet prostřednictvím zásobníku úložiště Windows, nový průchozí mechanismus umožňuje předat příkazy specifické pro dodavatele. Tento průchozí kanál také pomůže při vývoji nástrojů pro správu a testování. Tento průchozí mechanismus však vyžaduje použití protokolu efektů příkazů. Kromě toho StoreNVMe.sys vyžaduje, aby všechny příkazy, nejen předávací příkazy, byly popsány v protokolu efektů příkazů.

Důležitý

StorNVMe.sys a Storport.sys zablokují na zařízení všechny příkazy, pokud nejsou popsány v protokolu efektů příkazů.

 

Podpora záznamu efektů příkazů

Protokol efektů příkazů (jak je popsáno v části Podporované příkazy a efekty, oddíl 5.10.1.5 specifikace NVMe Specification 1.2) umožňuje popis účinků příkazů specifických pro dodavatele spolu se specifikacemi definovanými příkazy. To usnadňuje ověřování i optimalizaci chování příkazů, a proto by se mělo implementovat pro celou sadu příkazů, které zařízení podporuje. Následující podmínky popisují výsledek odeslání příkazu na základě položky protokolu efektů příkazů.

Pro každý konkrétní příkaz popsaný v protokolu efektů příkazů...

Zatímco:

  • Podporovaný příkaz (CSUPP) je nastavený na 1, což znamená, že tento příkaz podporuje kontroler (bit 01).

    Poznámka

    Pokud je csUPP nastaven na hodnotu 0 (značí, že příkaz není podporovaný), příkaz se zablokuje.

     

A pokud je nastavena některá z následujících možností:

  • Změna schopností kontroleru (CCC) je nastavená na 1, což znamená, že příkaz může změnit možnosti kontroleru (bit 04)

  • Změna inventáře oboru názvů (NIC) je nastavená na 1, což znamená, že příkaz může změnit číslo nebo možnosti pro více oborů názvů (bit 03)

  • Změna schopností oboru názvů (NCC) je nastavená na 1, což znamená, že příkaz může změnit schopnosti jednoho oboru názvů (Bit 02).

  • Odesílání a spouštění příkazů (CSE) je nastaveno na 001b nebo 010b, což znamená, že příkaz může být odeslán, pokud není žádný jiný nevyřízený příkaz do stejného nebo jakéhokoli jiného oboru názvů a že jiný příkaz by neměl být odeslán do stejného nebo jiného oboru názvů, dokud nebude tento příkaz dokončen (Bity 18:16).

Poté se příkaz odešle jako jediný nevyřízený příkaz pro adaptér.

Else, pokud:

  • Odeslání a spuštění příkazu (CSE) je nastaveno na 001b, což znamená, že příkaz může být odeslán, když není žádný jiný nevyřízený příkaz do stejného prostoru jmen, a že jiný příkaz by neměl být odeslán do stejného prostoru jmen, dokud tento příkaz nebude dokončen (Bits 18:16)

Pak se příkaz odešle jako jediný příkaz, který zbývá k objektu logické jednotky (LUN).

Jinak, příkaz se odešle s jinými příkazy, které nejsou vynikající bez inhibice. Pokud se například do zařízení odešle příkaz specifický pro dodavatele, který načte statistické informace, které nejsou definované specifikací, nemělo by dojít ke změně chování nebo schopnosti zařízení spouštět vstupně-výstupní příkazy. Tyto žádosti by mohly být zpracovávány paralelně s operacemi I/O, aniž by bylo nutné pozastavit a obnovit jejich provádění.

Použití IOCTL_STORAGE_PROTOCOL_COMMAND k odesílání příkazů

Průchozí lze provádět pomocí IOCTL_STORAGE_PROTOCOL_COMMAND, zavedené ve Windows 10. Tento IOCTL byl navržen tak, aby měl podobné chování jako stávající přenosové IOCTLs SCSI a ATA, a umožnil tak odeslání vloženého příkazu do cílového zařízení. Prostřednictvím této hodnoty IOCTL lze odesílat příkazy úložnému zařízení, včetně jednotky NVMe.

Například v NVMe umožní IOCTL odesílat následující kódy příkazů.

  • Příkazy pro správu specifické pro dodavatele (C0h – FFh)
  • Příkazy NVMe specifické pro dodavatele (80h – FFh)

Stejně jako u všech ostatních IOCTLs použijte DeviceIoControl k odeslání procházejícího IOCTL dolů. IOCTL je naplněn pomocí struktury vstupní vyrovnávací paměti STORAGE_PROTOCOL_COMMAND, která se nachází v ntddstor.h. Do pole Command zadejte příkaz specifický pro dodavatele.

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;

Příkaz specifický pro dodavatele, který se má odeslat, by se měl zadat do zvýrazněného pole výše. Znovu si všimněte, že protokol efektů příkazů musí být implementován pro předávací příkazy. Konkrétně je potřeba tyto příkazy hlásit jako podporované v protokolu efektů příkazů (další informace najdete v předchozí části). Všimněte si také, že pole PRP jsou specifická pro konkrétní ovladače, takže aplikace, které odesílají příkazy, je mohou ponechat jako 0.

Nakonec je tato průchozí IOCTL určena pro odesílání příkazů specifických pro dodavatele. Pokud chcete odeslat administrativní příkazy NVMe nebo příkazy, které nejsou specifické pro dodavatele, například Identifikovat, neměl by se použít tento průchozí IOCTL. Například IOCTL_STORAGE_QUERY_PROPERTY by měl být použit pro identifikaci nebo získání stránek protokolu. Další informace najdete v další části dotazy specifické pro protokol.

Neaktualizovat firmware prostřednictvím průchozího mechanismu

Příkazy pro stahování firmwaru a aktivační příkazy by se neměly posílat pomocí předávacího příkazu. IOCTL_STORAGE_PROTOCOL_COMMAND by se měly používat jenom pro příkazy specifické pro dodavatele.

Místo toho použijte následující obecné IOCTL rozhraní úložiště, které byly zavedeny s Windows 10, a tím se vyhněte používání SCSI_miniport IOCTL firmwaru přímo v aplikacích. Ovladače úložiště přeloží IOCTL buď na příkaz SCSI, nebo na miniportovou verzi IOCTL pro miniport.

Pro vývoj nástrojů pro upgrade firmwaru ve Windows 10 a Windows Server 2016 se doporučují následující IOCTLs:

Pro získání informací o úložišti a aktualizaci firmwaru systém Windows také podporuje rutiny PowerShellu pro rychlé provedení těchto kroků:

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

Poznámka

Pokud chcete aktualizovat firmware nvMe ve Windows 8.1, použijte IOCTL_SCSI_MINIPORT_FIRMWARE. Tento IOCTL nebyl zpětně převeden na Windows 7. Další informace naleznete v tématu Aktualizace firmwaru pro zařízení NVMe ve Windows 8.1.

 

Vrácení chyb prostřednictvím předávacího mechanismu

Podobně jako u IOCTLs typu průchozí SCSI a ATA, když je příkaz nebo požadavek odeslán do miniportu nebo zařízení, IOCTL se vrátí s informací, zda byla operace úspěšná nebo neúspěšná. Ve struktuře STORAGE_PROTOCOL_COMMAND vrací IOCTL stav prostřednictvím pole ReturnStatus.

Příklad: Odeslání příkazu specifického pro dodavatele

V tomto příkladu se odešle libovolný příkaz specifický pro dodavatele (0xFF) přímo na jednotku NVMe. Následující kód přidělí vyrovnávací paměť, inicializuje dotaz a poté odešle příkaz směrem k zařízení prostřednictvím 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 
                             );  

V tomto příkladu očekáváme protocolCommand->ReturnStatus == STORAGE_PROTOCOL_STATUS_SUCCESS, pokud se příkaz k zařízení zdařil.

Dotazy specifické pro protokoly

Windows 8.1 zavedl IOCTL_STORAGE_QUERY_PROPERTY pro načítání dat. Ve Windows 10 byla podpora pro IOCTL rozšířena, aby zahrnovala běžně požadované funkce NVMe, jako například Získání stránek protokolu, Získání funkcía Identifikace. To umožňuje načtení konkrétních informací NVMe pro účely monitorování a inventáře.

Zde je vstupní vyrovnávací paměť pro IOCTL, STORAGE_PROPERTY_QUERY (z Windows 10).

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

Při použití IOCTL_STORAGE_QUERY_PROPERTY k načtení informací specifických pro protokol NVMe v STORAGE_PROTOCOL_DATA_DESCRIPTORnakonfigurujte strukturu STORAGE_PROPERTY_QUERY následujícím způsobem:

  • Přidělte vyrovnávací paměť, která může obsahovat jak strukturu STORAGE_PROPERTY_QUERY, tak strukturu STORAGE_PROTOCOL_SPECIFIC_DATA.

  • Nastavte pole PropertyID na StorageAdapterProtocolSpecificProperty nebo StorageDeviceProtocolSpecificProperty pro požadavek kontroleru nebo zařízení/oboru názvů.

  • Nastavte pole QueryType na PropertyStandardQuery.

  • Vyplňte STORAGE_PROTOCOL_SPECIFIC_DATA strukturu požadovanými hodnotami. Začátek STORAGE_PROTOCOL_SPECIFIC_DATA je pole AdditionalParameters ve struktuře STORAGE_PROPERTY_QUERY.

Tady je vidět struktura STORAGE_PROTOCOL_SPECIFIC_DATA (z Windows 10).

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;

Chcete-li zadat typ informací specifických pro protokol NVMe, nakonfigurujte strukturu STORAGE_PROTOCOL_SPECIFIC_DATA následujícím způsobem:

  • Nastavte pole ProtocolType na ProtocolTypeNVMe.

  • Nastavte pole DataType na hodnotu výčtu definovanou STORAGE_PROTOCOL_NVME_DATA_TYPE:

    • Pomocí NVMeDataTypeIdentify získejte data identifikace kontroléru nebo data identifikace oboru názvů.
    • Pomocí NVMeDataTypeLogPage můžete získat stránky protokolu (včetně údajů SMART/ o stavu).
    • Pomocí NVMeDataTypeFeature získejte funkce jednotky NVMe.

Pokud se ProtocolTypeNVMe používá jako ProtocolType, je možné dotazy na informace specifické pro protokol načítat paralelně s jinými vstupně-výstupními operacemi na NVMe jednotce.

Důležitý

Pro IOCTL_STORAGE_QUERY_PROPERTY, která používá STORAGE_PROPERTY_IDStorageAdapterProtocolSpecificPropertya jejíž STORAGE_PROTOCOL_SPECIFIC_DATA nebo STORAGE_PROTOCOL_SPECIFIC_DATA_EXT struktura je nastavena na ProtocolType=ProtocolTypeNvme a DataType=NVMeDataTypeLogPage, nastavte člen ProtocolDataLength stejné struktury na minimální hodnotu 512 (bajtů).

Následující příklady ukazují dotazy specifické pro protokol NVMe.

Příklad: Identifikace dotazu NVMe

V tomto příkladu se požadavek na identifikaci odešle na jednotku NVMe. Následující kód inicializuje strukturu dat dotazu a pak odešle příkaz dolů do zařízení prostřednictvím 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"));
        }
    }

  

Důležitý

Pro IOCTL_STORAGE_QUERY_PROPERTY, která používá STORAGE_PROPERTY_IDStorageAdapterProtocolSpecificPropertya jejíž STORAGE_PROTOCOL_SPECIFIC_DATA nebo STORAGE_PROTOCOL_SPECIFIC_DATA_EXT struktura je nastavena na ProtocolType=ProtocolTypeNvme a DataType=NVMeDataTypeLogPage, nastavte člen ProtocolDataLength stejné struktury na minimální hodnotu 512 (bajtů).

Všimněte si, že volající musí přidělit jednu vyrovnávací paměť obsahující STORAGE_PROPERTY_QUERY a velikost STORAGE_PROTOCOL_SPECIFIC_DATA. V tomto příkladu se používá stejná vyrovnávací paměť pro vstup i výstup při dotazu na vlastnost. Proto má přidělená vyrovnávací paměť velikost "FIELD_OFFSET(STORAGE_PROPERTY_QUERY; AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE". I když se pro vstup i výstup dají přidělit samostatné vyrovnávací paměti, doporučujeme použít k dotazování informací souvisejících s NVMe jednu vyrovnávací paměť.

identifyControllerData->NN je počet oborů názvů (NN). Systém Windows rozpozná obor názvů jako fyzický disk.

Příklad: Dotaz na stránky protokolu NVMe

Na základě předchozího příkladu je v tomto příkladu na jednotku NVMe odeslán požadavek Získat stránky protokolu. Následující kód připraví datovou strukturu dotazu a pak odešle příkaz do zařízení přes 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"));
    }

Volající můžou použít STORAGE_PROPERTY_IDStorageAdapterProtocolSpecificProperty, a jejichž struktura STORAGE_PROTOCOL_SPECIFIC_DATA nebo STORAGE_PROTOCOL_SPECIFIC_DATA_EXT je nastavena na ProtocolDataRequestValue=VENDOR_SPECIFIC_LOG_PAGE_IDENTIFIER k vyžádání 512 bajtových bloků dat specifických pro dodavatele.

Příklad: Dotaz na získání funkcí NVMe

V tomto příkladu, vycházejícím z předchozího, je požadavek Získat funkce odeslán na jednotku NVMe. Následující kód připraví datovou strukturu dotazu a pak odešle příkaz do zařízení přes 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"));
    }

Sada specifická pro protokol

Od Windows 10 19H1 byl příkaz IOCTL_STORAGE_SET_PROPERTY vylepšen tak, aby podporoval funkci Set Features pro NVMe.

Vstupní vyrovnávací paměť pro IOCTL_STORAGE_SET_PROPERTY se zobrazí tady:

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;

Při použití IOCTL_STORAGE_SET_PROPERTY k nastavení funkce NVMe nakonfigurujte strukturu STORAGE_PROPERTY_SET následujícím způsobem:

  • Přidělte vyrovnávací paměť, která může obsahovat jak STORAGE_PROPERTY_SET, tak strukturu STORAGE_PROTOCOL_SPECIFIC_DATA_EXT.
  • Nastavte pole PropertyID na StorageAdapterProtocolSpecificProperty nebo StorageDeviceProtocolSpecificProperty pro požadavek řadiče nebo zařízení či jmenného prostoru.
  • Vyplňte STORAGE_PROTOCOL_SPECIFIC_DATA_EXT strukturu požadovanými hodnotami. Začátek STORAGE_PROTOCOL_SPECIFIC_DATA_EXT je pole AdditionalParameters STORAGE_PROPERTY_SET.

Zde je znázorněna struktura STORAGE_PROTOCOL_SPECIFIC_DATA_EXT.

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;

Chcete-li zadat typ funkce NVMe, která se má nastavit, nakonfigurujte strukturu STORAGE_PROTOCOL_SPECIFIC_DATA_EXT následujícím způsobem:

  • Nastavte pole ProtocolType na ProtocolTypeNvme;
  • Nastavte pole DataType na hodnotu výčtu NVMeDataTypeFeature definovanou STORAGE_PROTOCOL_NVME_DATA_TYPE;

Následující příklady ukazují sadu funkcí NVMe.

Příklad: Funkce sady NVMe

V tomto příkladu se požadavek Nastavit funkce odešle na jednotku NVMe. Následující kód připraví datovou strukturu sady a pak odešle příkaz do zařízení přes 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
            );

Dotazy na teplotu

Ve Windows 10 je možné IOCTL_STORAGE_QUERY_PROPERTY použít také k dotazování dat o teplotě ze zařízení NVMe.

Chcete-li načíst informace o teplotě z jednotky NVMe v STORAGE_TEMPERATURE_DATA_DESCRIPTOR, nakonfigurujte strukturu STORAGE_PROPERTY_QUERY následujícím způsobem:

  • Přidělte vyrovnávací paměť, která může obsahovat strukturu STORAGE_PROPERTY_QUERY.

  • Nastavte pole PropertyID na StorageAdapterTemperatureProperty nebo StorageDeviceTemperatureProperty pro požadavek kontroleru nebo zařízení/oboru názvů.

  • Nastavte pole QueryType na VlastnostStandardQuery.

Tady je znázorněna struktura STORAGE_TEMPERATURE_INFO (z Windows 10).

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;

Příkazy měnící chování

Příkazy, které pracují s atributy zařízení nebo potenciálně ovlivňují chování zařízení, jsou pro operační systém obtížnější. Pokud se atributy zařízení během zpracování vstupně-výstupních operací mění, může dojít k problémům se synchronizací nebo integritou dat, pokud nejsou správně zpracovány.

Příkaz NVMe Set-Features je dobrým příkladem příkazu, který mění chování. Umožňuje změnu rozhodčího mechanismu a nastavení prahových hodnot teploty. Aby se zajistilo, že při odesílání příkazů sady ovlivňujících chování nejsou ohrožená data v letu, systém Windows pozastaví všechny vstupně-výstupní operace do zařízení NVMe, vyprázdní fronty a vyprázdní vyrovnávací paměti. Jakmile se příkaz set úspěšně spustí, vstupně-výstupní operace se obnoví (pokud je to možné). Pokud vstupně-výstupní operace nejde obnovit, může se vyžadovat resetování zařízení.

Nastavení prahových hodnot teploty

Windows 10 zavedl IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD, IOCTL pro získání a nastavení prahových hodnot teploty. Můžete ho také použít k získání aktuální teploty zařízení. Vstupní/výstupní vyrovnávací paměť pro toto IOCTL je struktura STORAGE_TEMPERATURE_INFO z předchozího úseku kódu.

Příklad: Nastavení nadpráhové teploty

V tomto příkladu je nastavena teplota nadprahová pro jednotku NVMe. Následující kód připraví příkaz a pak ho odešle do zařízení přes 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  
                             ); 

Nastavení funkcí specifických pro dodavatele

Bez protokolu efektů příkazů nemá ovladač žádné znalosti o dopadech příkazu. To je důvod, proč je vyžadován protokol efektů příkazů. Pomáhá operačnímu systému určit, jestli má příkaz velký dopad a jestli se dá odeslat paralelně s jinými příkazy na jednotku.

Protokol efektů příkazů zatím není dostatečně podrobný, aby zahrnoval příkazy Set-Features specifické pro dodavatele. Z tohoto důvodu zatím není možné odesílat příkazy set-features specifické pro dodavatele. K odeslání příkazů specifických pro dodavatele je však možné použít předávací mechanismus, který jsme probírali dříve. Pro více informací si prohlédněte mechanismus předávání.

Soubory hlaviček

Následující soubory jsou relevantní pro vývoj NVMe. Tyto soubory jsou součástí sady Microsoft Windows Software Development Kit (SDK).

Hlavičkový soubor Popis
ntddstor.h Definuje konstanty a typy pro přístup k ovladačům třídy úložiště z režimu jádra.
nvme.h Pro další datové struktury související s NVMe.
winioctl.h Pro obecné definice IOCTL Win32, včetně rozhraní API pro úložiště pro aplikace v uživatelském režimu.