Udostępnij za pośrednictwem


Praca z dyskami NVMe

Dotyczy:

  • Windows 10
  • Windows Server 2016

Dowiedz się, jak pracować z szybkimi urządzeniami NVMe z aplikacji systemu Windows. Dostęp do urządzeń jest możliwy za pomocą StorNVMe.sys, sterownik wbudowany po raz pierwszy wprowadzono w systemach Windows Server 2012 R2 i Windows 8.1. Jest również dostępny dla urządzeń Windows 7 za pośrednictwem poprawki KB. W systemie Windows 10 wprowadzono kilka nowych funkcji, w tym mechanizm przekazywania poleceń NVMe dostosowanych do specyficznych wymagań dostawców oraz aktualizacje istniejących poleceń IOCTL.

Ten temat zawiera omówienie interfejsów API ogólnego użycia, których można użyć do uzyskiwania dostępu do dysków NVMe w systemie Windows 10. Opisano w nim również:

Interfejsy API do pracy z dyskami NVMe

Do uzyskiwania dostępu do dysków NVMe w systemie Windows 10 można użyć następujących interfejsów API ogólnego użycia. Te interfejsy API można znaleźć w winioctl.h w aplikacjach działających w trybie użytkownika, a ntddstor.h w sterownikach działających w trybie jądra. Aby uzyskać więcej informacji o plikach nagłówków, zobacz Pliki nagłówka.

  • IOCTL_STORAGE_PROTOCOL_COMMAND: użyj tego polecenia IOCTL ze strukturą STORAGE_PROTOCOL_COMMAND, aby wydać polecenia NVMe. Ta funkcja IOCTL umożliwia przekazywanie NVMe i obsługuje dziennik efektów poleceń w NVMe. Można go używać z komendami charakterystycznymi dla dostawcy. Aby uzyskać więcej informacji, zobacz mechanizm transmisji .

  • STORAGE_PROTOCOL_COMMAND: ta struktura buforu wejściowego zawiera pole ReturnStatus, które może służyć do raportowania następujących wartości stanu.

    • 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
    • STATUS_PROTOKOŁU_MAGAZYNOWANIA_PRZEPEŁNIENIE_DANYCH
    • STORAGE_PROTOCOL_STATUS_INSUFFICIENT_RESOURCES
    • STORAGE_PROTOCOL_STATUS_NOT_SUPPORTED
  • IOCTL_STORAGE_QUERY_PROPERTY: użyj tego IOCTL wraz ze strukturą STORAGE_PROPERTY_QUERY, aby pobrać informacje o urządzeniu. Aby uzyskać więcej informacji, zobacz zapytania specyficzne dla protokołu i zapytania dotyczące temperatury.

  • STORAGE_PROPERTY_QUERY: ta struktura zawiera pola PropertyId i AdditionalParameters w celu określenia danych do odpytowania. W pliku PropertyId użyj wyliczenia STORAGE_PROPERTY_ID, aby określić typ danych. Użyj pola AdditionalParameters, aby określić więcej szczegółów w zależności od typu danych. W przypadku danych specyficznych dla protokołu użyj struktury STORAGE_PROTOCOL_SPECIFIC_DATA w polu AdditionalParameters. W przypadku danych temperatury użyj struktury STORAGE_TEMPERATURE_INFO w polu AdditionalParameters.

  • STORAGE_PROPERTY_ID : Wyliczenie zawiera nowe wartości, które umożliwiają IOCTL_STORAGE_QUERY_PROPERTY pobieranie informacji o temperaturze i specyficznych dla protokołu.

    • StorageAdapterProtocolSpecificProperty: Jeśli ProtocolType = ProtocolTypeNvme oraz DataType = NVMeDataTypeLogPage, wywołujący powinien zażądać segmentów danych o rozmiarze 512 bajtów.
    • WłaściwośćDotyczącaProtokołuUrządzeniaMagazynującego

    Użyj jednego z tych identyfikatorów właściwości specyficznych dla protokołu w połączeniu z STORAGE_PROTOCOL_SPECIFIC_DATA, aby pobrać dane specyficzne dla protokołu w strukturze STORAGE_PROTOCOL_DATA_DESCRIPTOR.

    • StorageAdapterTemperatureProperty
    • StorageDeviceTemperatureProperty

    Użyj jednego z tych identyfikatorów właściwości temperatury, aby pobrać dane temperatury w strukturze STORAGE_TEMPERATURE_DATA_DESCRIPTOR.

  • STORAGE_PROTOCOL_SPECIFIC_DATA: Pobierz dane specyficzne dla NVMe, gdy ta struktura jest używana w polu AdditionalParameters w STORAGE_PROPERTY_QUERY i określana jest wartość wyliczeniowa STORAGE_PROTOCOL_NVME_DATA_TYPE. Użyj jednej z następujących wartości STORAGE_PROTOCOL_NVME_DATA_TYPE w polu DataType struktury STORAGE_PROTOCOL_SPECIFIC_DATA:

    • Użyj NVMeDataTypeIdentify, aby uzyskać dane identyfikacyjne kontrolera lub przestrzeni nazw.
    • Użyj NVMeDataTypeLogPage, aby uzyskać strony logów (w tym dane SMART i dane zdrowotne).
    • Użyj NVMeDataTypeFeature, aby uzyskać funkcje dysku NVMe.
  • STORAGE_TEMPERATURE_INFO: ta struktura służy do przechowywania określonych danych dotyczących temperatury. Jest używany w STORAGE_TEMPERATURE_DATA_DESCRIPTOR do zwracania wyników zapytania o temperaturę.

  • IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD: użyj tej IOCTL ze strukturą STORAGE_TEMPERATURE_THRESHOLD, aby ustawić progi temperatury. Aby uzyskać więcej informacji, zobacz Polecenia zmieniające zachowanie.

  • STORAGE_TEMPERATURE_THRESHOLD: ta struktura jest używana jako bufor wejściowy w celu określenia progu temperatury. Pole OverThreshold (wartość logiczna) określa, czy pole Threshold jest wartością przekraczającą próg, czy nie (w przeciwnym razie jest to wartość poniżej progu).

Mechanizm przekazywania

Polecenia, które nie są zdefiniowane w specyfikacji NVMe, są najtrudniejsze dla systemu operacyjnego hosta do obsługi — host nie ma wglądu w skutki, jakie polecenia mogą mieć na urządzenie docelowe, odsłoniętą infrastrukturę (przestrzenie nazw/rozmiary bloków) oraz jego zachowanie.

Aby lepiej przesyłać takie polecenia specyficzne dla urządzenia przez stos pamięci systemu Windows, nowy mechanizm przepustowy umożliwia potokowanie poleceń specyficznych dla dostawcy. Ten rurociąg przechodni pomoże również w opracowywaniu narzędzi do zarządzania i testowania. Jednak ten mechanizm przekazywania wymaga użycia dziennika efektów działań. Ponadto StoreNVMe.sys wymaga, aby wszystkie polecenia, a nie tylko przekazywane polecenia, zostały opisane w dzienniku efektów poleceń.

Ważny

StorNVMe.sys i Storport.sys będą blokować dowolne polecenie na urządzeniu, jeśli nie zostało to opisane w dzienniku efektów poleceń.

 

Obsługa dziennika efektów poleceń

Dziennik efektów poleceń (zgodnie z opisem w sekcji Polecenia obsługiwane i efekty, sekcja 5.10.1.5 specyfikacji NVMe 1.2) umożliwia opis efektów poleceń specyficznych dla dostawcy wraz ze specyfikacją poleceń zdefiniowanych przez specyfikację. Ułatwia to zarówno obsługę walidacji poleceń, jak i optymalizację zachowania poleceń, a w związku z tym należy zaimplementować dla całego zestawu poleceń, które obsługuje urządzenie. Poniższe warunki opisują wynik wysyłania polecenia na podstawie wpisu dziennika efektów polecenia.

W przypadku dowolnego określonego polecenia opisanego w dzienniku efektów poleceń...

Podczas:

  • Obsługiwane polecenie (CSUPP) ma wartość "1" oznaczającą, że polecenie jest obsługiwane przez kontroler (Bit 01)

    Notatka

    Gdy CSUPP ma wartość "0" (oznaczającą, że polecenie nie jest obsługiwane), polecenie zostanie zablokowane

     

A jeśli zostanie ustawiona dowolna z następujących wartości:

  • Zmiana możliwości kontrolera (CCC) jest ustawiona na wartość "1", co oznacza, że polecenie może zmienić możliwości kontrolera (Bit 04)

  • Zmiana inwentarza przestrzeni nazw (NIC) jest ustawiona na "1", co oznacza, że polecenie może zmieniać liczbę lub funkcje wielu przestrzeni nazw (Bit 03)

  • Zmiana możliwości przestrzeni nazw (NCC) jest ustawiona na wartość "1" oznaczającą, że polecenie może zmienić możliwości jednej przestrzeni nazw (Bit 02)

  • Ustawienie przesyłania i wykonywania poleceń (CSE) ma wartość 001b lub 010b, co oznacza, że polecenie może zostać przesłane, gdy nie ma żadnego oczekującego polecenia do tej samej przestrzeni nazw ani do dowolnej, oraz że inne polecenie nie powinno być przesyłane do tej samej lub dowolnej przestrzeni nazw, dopóki to polecenie nie zostanie ukończone (bity 18:16)

Następnie polecenie zostanie wysłane jako jedyne polecenie pozostające do wykonania dla adaptera.

inaczej, jeśli:

  • Wysyłanie i Wykonywanie Poleceń (CSE) jest ustawione na 001b, co oznacza, że polecenie może zostać przesłane, gdy nie ma żadnego innego oczekującego polecenia do tej samej przestrzeni nazw, i że inne polecenie nie powinno być przesyłane do tej samej przestrzeni nazw, dopóki to polecenie nie zostanie ukończone (Bity 18:16)

Następnie polecenie zostanie wysłane jako jedyne polecenie zaległe do obiektu Numer jednostki logicznej (LUN).

w przeciwnym raziepolecenie jest wysyłane z innymi poleceniami oczekującymi bez żadnych przeszkód. Jeśli na przykład do urządzenia jest wysyłane polecenie specyficzne dla dostawcy w celu pobrania informacji statystycznych, które nie są zdefiniowane przez specyfikację, nie powinno istnieć ryzyko zmiany zachowania urządzenia lub możliwości wykonywania poleceń we/wy. Takie żądania mogą być obsługiwane równolegle do operacji wejścia/wyjścia i wstrzymywanie-wznawianie nie byłoby konieczne.

Wysyłanie poleceń przy użyciu IOCTL_STORAGE_PROTOCOL_COMMAND

Przekazywanie można przeprowadzić przy użyciu IOCTL_STORAGE_PROTOCOL_COMMAND, wprowadzonego w systemie Windows 10. Ta funkcja IOCTL została zaprojektowana tak, aby zachowywała się podobnie do istniejących IOCTL SCSI i ATA, umożliwiając wysyłanie osadzonych poleceń do urządzenia docelowego. Za pośrednictwem tej biblioteki IOCTL można wysyłać przekazywanie do urządzenia magazynowego, w tym do dysku NVMe.

Na przykład w NVMe, IOCTL umożliwi wysyłanie następujących kodów poleceń.

  • Polecenia administratora specyficzne dla dostawcy (C0h – FFh)
  • Polecenia NVMe specyficzne dla producenta (80h – FFh)

Podobnie jak w przypadku wszystkich innych kodów IOCTL, użyj DeviceIoControl, aby przekazać polecenie IOCTL w dół. IOCTL jest wypełniane przy użyciu struktury bufora wejściowego STORAGE_PROTOCOL_COMMAND znalezionej w ntddstor.h. Wypełnij pole Command za pomocą polecenia specyficznego dla dostawcy.

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;

Polecenie specyficzne dla dostawcy, które ma zostać wysłane, powinno zostać wypełnione w wyróżnionym polu powyżej. Należy jeszcze raz zauważyć, że dziennik efektów poleceń musi być zaimplementowany dla poleceń przekazywania. W szczególności te polecenia muszą być zgłaszane jako obsługiwane w dzienniku efektów poleceń (zobacz poprzednią sekcję, aby uzyskać więcej informacji). Należy również pamiętać, że pola PRP są specyficzne dla sterownika, dlatego aplikacje wysyłające polecenia mogą pozostawić je jako 0.

Na koniec ta pass-through IOCTL jest przeznaczona do wysyłania poleceń specyficznych dla dostawcy. Aby wysłać inne polecenia administracyjne NVMe lub polecenia niezwiązane z dostawcą, takie jak Identyfikowanie, nie należy używać tego polecenia przepuszczającego IOCTL. Na przykład IOCTL_STORAGE_QUERY_PROPERTY powinien być używany do identyfikowania lub pobierania stron dziennika. Aby uzyskać więcej informacji, zobacz następną sekcję Zapytania specyficzne dla protokołu.

Nie aktualizuj oprogramowania układowego za pośrednictwem mechanizmu przejściowego

Polecenia pobierania i aktywacji oprogramowania układowego nie powinny być wysyłane przy użyciu przekazywania. IOCTL_STORAGE_PROTOCOL_COMMAND należy używać tylko dla poleceń specyficznych dla dostawcy.

Zamiast tego należy użyć następujących ogólnych poleceń IOCTL magazynu (wprowadzonych w systemie Windows 10), aby uniknąć sytuacji, w której aplikacje bezpośrednio używają wersji IOCTL oprogramowania układowego SCSI_miniport. Sterowniki magazynu przetłumaczą IOCTL na polecenie SCSI lub wersję IOCTL SCSI_miniport dla miniportu.

Te IOCTL są zalecane do tworzenia narzędzi do aktualizacji oprogramowania układowego w systemach operacyjnych Windows 10 i Windows Server 2016:

Aby uzyskać informacje o magazynie i zaktualizować oprogramowanie układowe, system Windows obsługuje również polecenia cmdlet programu PowerShell do szybkiego wykonania tych czynności.

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

Notatka

Aby zaktualizować oprogramowanie układowe na urządzeniu NVMe w systemie Windows 8.1, użyj IOCTL_SCSI_MINIPORT_FIRMWARE. Ten IOCTL nie został wprowadzony do systemu Windows 7. Aby uzyskać więcej informacji, zobacz Uaktualnianie oprogramowania układowego dla urządzenia NVMe w systemie Windows 8.1.

 

Zwracanie błędów przez mechanizm przekazywania

Podobnie jak w przypadku przekazywania IOCTL dla SCSI i ATA, gdy polecenie/żądanie jest wysyłane do miniportu lub urządzenia, funkcja IOCTL zwraca wartość, czy operacja zakończyła się pomyślnie czy nie. W strukturze STORAGE_PROTOCOL_COMMAND IOCTL zwraca stan za pomocą pola ReturnStatus.

Przykład: wysyłanie polecenia specyficznego dla dostawcy

W tym przykładzie arbitralne polecenie specyficzne dla dostawcy (0xFF) jest wysyłane przekazaniem do dysku NVMe. Poniższy kod przydziela bufor, inicjuje zapytanie, a następnie wysyła polecenie do urządzenia za pomocą funkcji 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 
                             );  

W tym przykładzie oczekujemy, że otrzymamy protocolCommand->ReturnStatus == STORAGE_PROTOCOL_STATUS_SUCCESS, jeśli polecenie się powiodło do urządzenia.

Zapytania specyficzne dla protokołu

System Windows 8.1 wprowadził IOCTL_STORAGE_QUERY_PROPERTY do pobierania danych. W systemie Windows 10 rozszerzenie IOCTL zostało ulepszone w celu obsługi często żądanych funkcji NVMe, takich jak Pobierz strony logów, Pobierz funkcjei Identyfikacja. Umożliwia to pobieranie informacji specyficznych dla NVMe na cele monitorowania i inwentaryzacji.

Bufor wejściowy dla IOCTL, STORAGE_PROPERTY_QUERY (z systemu Windows 10) jest pokazany tutaj.

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

W przypadku używania IOCTL_STORAGE_QUERY_PROPERTY do pobierania informacji specyficznych dla protokołu NVMe w STORAGE_PROTOCOL_DATA_DESCRIPTORskonfiguruj strukturę STORAGE_PROPERTY_QUERY w następujący sposób:

  • Przydziel bufor, który może zawierać zarówno STORAGE_PROPERTY_QUERY, jak i strukturę STORAGE_PROTOCOL_SPECIFIC_DATA.

  • Ustaw pole PropertyID na wartość StorageAdapterProtocolSpecificProperty lub StorageDeviceProtocolSpecificProperty odpowiednio dla żądania kontrolera lub urządzenia/przestrzeni nazw.

  • Ustaw pole `QueryType` na `PropertyStandardQuery`.

  • Wypełnij strukturę STORAGE_PROTOCOL_SPECIFIC_DATA żądanymi wartościami. Początek STORAGE_PROTOCOL_SPECIFIC_DATA jest polem AdditionalParametersSTORAGE_PROPERTY_QUERY.

Struktura STORAGE_PROTOCOL_SPECIFIC_DATA (z systemu Windows 10) jest pokazana tutaj.

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;

Aby określić typ informacji specyficznych dla protokołu NVMe, skonfiguruj strukturę STORAGE_PROTOCOL_SPECIFIC_DATA w następujący sposób:

  • Ustaw pole ProtocolType na wartość ProtocolTypeNVMe.

  • Ustaw pole DataType na wartość wyliczenia zdefiniowaną przez STORAGE_PROTOCOL_NVME_DATA_TYPE:

    • Użyj NVMeDataTypeIdentify, aby uzyskać dane identyfikujące kontroler lub dane identyfikujące przestrzeń nazw.
    • Użyj NVMeDataTypeLogPage, aby uzyskać strony dziennika (w tym dane SMART/health).
    • Użyj NVMeDataTypeFeature, aby uzyskać funkcje dysku NVMe.

Gdy ProtocolTypeNVMe jest używany jako ProtocolType, zapytania dotyczące informacji specyficznych dla protokołu można pobrać równolegle z innymi operacjami we/wy na dysku NVMe.

Ważny

W przypadku IOCTL_STORAGE_QUERY_PROPERTY, który używa STORAGE_PROPERTY_IDStorageAdapterProtocolSpecificProperty, a którego struktura STORAGE_PROTOCOL_SPECIFIC_DATA lub STORAGE_PROTOCOL_SPECIFIC_DATA_EXT jest skonfigurowana na ProtocolType=ProtocolTypeNvme i DataType=NVMeDataTypeLogPage, ustaw element członkowski ProtocolDataLength tej samej struktury na minimalną wartość 512 (bajtów).

W poniższych przykładach pokazano zapytania specyficzne dla protokołu NVMe.

Przykład: Zapytanie identyfikowania nvme

W tym przykładzie żądanie Identify jest wysyłane do dysku NVMe. Poniższy kod inicjuje strukturę danych zapytania, a następnie wysyła polecenie do urządzenia za pomocą funkcji 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"));
        }
    }

  

Ważny

W przypadku IOCTL_STORAGE_QUERY_PROPERTY, który używa STORAGE_PROPERTY_IDStorageAdapterProtocolSpecificProperty, i którego struktura STORAGE_PROTOCOL_SPECIFIC_DATA lub STORAGE_PROTOCOL_SPECIFIC_DATA_EXT jest ustawiona na ProtocolType=ProtocolTypeNvme i DataType=NVMeDataTypeLogPage, ustaw element członkowski ProtocolDataLength tej samej struktury na minimalną wartość 512 (bajtów).

Należy pamiętać, że obiekt wywołujący musi przydzielić pojedynczy bufor zawierający STORAGE_PROPERTY_QUERY i rozmiar STORAGE_PROTOCOL_SPECIFIC_DATA. W tym przykładzie jest używany ten sam bufor dla danych wejściowych i wyjściowych z zapytania właściwości. Dlatego przydzielony bufor ma rozmiar "FIELD_OFFSET(STORAGE_PROPERTY_QUERY, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE". Mimo że można przydzielić oddzielne bufory zarówno dla danych wejściowych, jak i wyjściowych, zalecamy użycie pojedynczego buforu do zapytań o informacje związane z NVMe.

identifyControllerData—>NN to liczba przestrzeni nazw (NN). System Windows rozpoznaje przestrzeń nazw jako fizyczny dysk.

Przykład: zapytanie NVMe Get Log Pages

W tym przykładzie, opartym na poprzednim, żądanie Pobierz strony dziennika jest wysyłane do dysku NVMe. Poniższy kod przygotowuje strukturę danych zapytania, a następnie wysyła polecenie do urządzenia za pomocą funkcji 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"));
    }

Osoby wywołujące mogą używać STORAGE_PROPERTY_IDStorageAdapterProtocolSpecificPropertyi których STORAGE_PROTOCOL_SPECIFIC_DATA lub STORAGE_PROTOCOL_SPECIFIC_DATA_EXT struktura jest ustawiona na ProtocolDataRequestValue=VENDOR_SPECIFIC_LOG_PAGE_IDENTIFIER, aby zażądać 512 bajtowych fragmentów danych specyficznych dla dostawcy.

Przykład: Zapytanie "Get Features" w NVMe

W tym przykładzie, na podstawie poprzedniego przykładu, żądanie Pobierz funkcje jest wysyłane do dysku NVMe. Poniższy kod przygotowuje strukturę danych zapytania, a następnie wysyła polecenie do urządzenia za pomocą funkcji 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"));
    }

Zestaw specyficzny dla protokołu

W systemie Windows 10 19H1 IOCTL_STORAGE_SET_PROPERTY została rozszerzona o obsługę ustalania funkcyjności NVMe.

Bufor wejściowy dla IOCTL_STORAGE_SET_PROPERTY jest pokazany tutaj:

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;

W przypadku używania IOCTL_STORAGE_SET_PROPERTY do ustawiania funkcji NVMe skonfiguruj strukturę STORAGE_PROPERTY_SET w następujący sposób:

  • Przydziel bufor, który może zawierać zarówno STORAGE_PROPERTY_SET, jak i strukturę STORAGE_PROTOCOL_SPECIFIC_DATA_EXT;
  • Ustaw pole PropertyID na StorageAdapterProtocolSpecificProperty lub StorageDeviceProtocolSpecificProperty dla żądania kontrolera lub odpowiednio urządzenia/przestrzeni nazw.
  • Wypełnij strukturę STORAGE_PROTOCOL_SPECIFIC_DATA_EXT żądanymi wartościami. Początek STORAGE_PROTOCOL_SPECIFIC_DATA_EXT to pole AdditionalParameters STORAGE_PROPERTY_SET.

Struktura STORAGE_PROTOCOL_SPECIFIC_DATA_EXT jest pokazana tutaj.

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;

Aby określić typ funkcji NVMe do ustawienia, skonfiguruj strukturę STORAGE_PROTOCOL_SPECIFIC_DATA_EXT w następujący sposób:

  • Ustaw pole ProtocolType na ProtocolTypeNvme;
  • Ustaw pole DataType na wartość wyliczenia NVMeDataTypeFeature zdefiniowaną przez STORAGE_PROTOCOL_NVME_DATA_TYPE;

W poniższych przykładach pokazano zestaw funkcji NVMe.

Przykład: Ustawienia funkcji NVMe

W tym przykładzie żądanie Ustaw funkcje jest wysyłane do dysku NVMe. Poniższy kod przygotowuje strukturę danych typu set, a następnie wysyła polecenie do urządzenia za pomocą funkcji 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
            );

Zapytania dotyczące temperatury

W systemie Windows 10 IOCTL_STORAGE_QUERY_PROPERTY mogą być również używane do wykonywania zapytań dotyczących danych temperatury z urządzeń NVMe.

Aby pobrać informacje o temperaturze z dysku NVMe w STORAGE_TEMPERATURE_DATA_DESCRIPTOR, skonfiguruj strukturę STORAGE_PROPERTY_QUERY w następujący sposób:

  • Przydziel bufor, który może zawierać STORAGE_PROPERTY_QUERY strukturę.

  • Ustaw pole PropertyID na StorageAdapterTemperatureProperty lub StorageDeviceTemperatureProperty, odpowiednio dla żądania kontrolera lub urządzenia/przestrzeni nazw.

  • Ustaw pole QueryType na PropertyStandardQuery.

Struktura STORAGE_TEMPERATURE_INFO (z systemu Windows 10) jest wyświetlana tutaj.

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;

Polecenia zmieniające zachowanie

Polecenia, które manipulują atrybutami urządzenia lub potencjalnie wpływają na zachowanie urządzenia, są trudniejsze do obsługi systemu operacyjnego. Jeśli atrybuty urządzenia zmieniają się w czasie działania podczas przetwarzania operacji wejścia/wyjścia, synchronizacja lub problemy z integralnością danych mogą wystąpić, jeśli nie są odpowiednio zarządzane.

Polecenie NVMe Set-Features jest dobrym przykładem polecenia zmieniającego zachowanie. Umożliwia to zmianę mechanizmu arbitrażowego i ustawienie progów temperatury. Aby upewnić się, że dane przetwarzane w czasie rzeczywistym nie są zagrożone w przypadku wysyłania poleceń konfiguracyjnych mających wpływ na zachowanie, system Windows wstrzyma wszystkie operacje we/wy na urządzeniu NVMe, opróżni kolejki i opłuka buforów. Po pomyślnym wykonaniu polecenia set operacje we/wy są wznawiane (jeśli to możliwe). Jeśli nie można wznowić operacji we/wy, może być wymagane zresetowanie urządzenia.

Ustawianie progów temperatury

System Windows 10 wprowadził IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD, IOCTL służący do uzyskiwania i ustawiania progów temperatury. Możesz również użyć go, aby uzyskać bieżącą temperaturę urządzenia. Bufor wejściowy/wyjściowy dla IOCTL to struktura STORAGE_TEMPERATURE_INFO z wcześniejszej sekcji kodu.

Przykład: ustawianie temperatury przekroczenia progu

W tym przykładzie ustawiono temperaturę przekroczenia progu dysku NVMe. Poniższy kod przygotowuje polecenie, a następnie wysyła go do urządzenia za pośrednictwem 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  
                             ); 

Ustawianie funkcji specyficznych dla dostawcy

Bez dziennika efektów poleceń sterownik nie ma wiedzy na temat konsekwencji polecenia. Dlatego jest wymagany dziennik efektów poleceń. Pomaga systemowi operacyjnemu określić, czy polecenie ma duży wpływ i czy można go wysłać równolegle z innymi poleceniami na dysk.

Dziennik efektów poleceń nie jest jeszcze wystarczająco szczegółowy, aby obejmować polecenia set-features specyficzne dla dostawcy. Z tego powodu nie można jeszcze wysyłać poleceń Set-Features specyficznych dla dostawcy. Można jednak użyć omówionego wcześniej mechanizmu przekazywania w celu wysyłania poleceń specyficznych dla dostawcy. Aby uzyskać więcej informacji, zobacz mechanizm transferowy.

Pliki nagłówka

Następujące pliki są istotne dla programowania nvme. Te pliki są dołączone do zestawu Microsoft Windows Software Development Kit (SDK).

Plik nagłówka Opis
ntddstor.h Definiuje stałe i typy na potrzeby uzyskiwania dostępu do sterowników klasy pamięci masowej z trybu jądra.
nvme.h Dla innych struktur danych związanych z NVMe.
winioctl.h Ogólne definicje IOCTL Win32, w tym API magazynowania dla aplikacji działających w trybie użytkownika.