Поделиться через


Работа с дисками NVMe

Относится к:

  • Windows 10
  • Windows Server 2016

Узнайте, как работать с высокоскоростными устройствами NVMe из приложения Windows. Доступ к устройству включен с помощью StorNVMe.sys, встроенный драйвер впервые появился в Windows Server 2012 R2 и Windows 8.1. Он также доступен для устройств Windows 7 через обновление KB. В Windows 10 были введены несколько новых функций, включая механизм сквозной передачи для команд NVMe, специфичных для поставщиков, и обновления существующих IOCTLs.

В этом разделе представлен обзор api общего использования, которые можно использовать для доступа к дискам NVMe в Windows 10. Он также описывает следующее:

API для работы с дисками NVMe

Для доступа к дискам NVMe в Windows 10 можно использовать следующие api общего использования. Эти API можно найти в winioctl.h для приложений пользовательского режима и ntddstor.h для драйверов режима ядра. Дополнительные сведения о файлах заголовков см. в разделе "Файлы заголовков".

  • IOCTL_STORAGE_PROTOCOL_COMMAND . Используйте этот IOCTL со структурой STORAGE_PROTOCOL_COMMAND для выдачи команд NVMe. Этот протокол IOCTL включает сквозную передачу NVMe и поддерживает журнал эффектов команд в NVMe. Эту функцию можно использовать с командами, зависящими от поставщика. Дополнительные сведения см. в разделе "Сквозной механизм".

  • STORAGE_PROTOCOL_COMMAND : эта структура буфера ввода включает поле ReturnStatus , которое можно использовать для отчета о следующих значениях состояния.

    • STORAGE_PROTOCOL_STATUS_PENDING
    • STORAGE_PROTOCOL_STATUS_SUCCESS (Успешный статус протокола хранения)
    • ОШИБКА_СТАТУСА_ПРОТОКОЛА_ХРАНЕНИЯ
    • STORAGE_PROTOCOL_STATUS_INVALID_REQUEST
    • STORAGE_PROTOCOL_STATUS_NO_DEVICE (Ошибка: Нет устройства)
    • STORAGE_PROTOCOL_STATUS_BUSY
    • STORAGE_PROTOCOL_STATUS_DATA_OVERRUN
    • STORAGE_PROTOCOL_STATUS_INSUFFICIENT_RESOURCES
    • СТАТУС_ПРОТОКОЛА_ХРАНЕНИЯ_НЕ_ПОДДЕРЖИВАЕТСЯ
  • IOCTL_STORAGE_QUERY_PROPERTY . Используйте этот IOCTL со структурой STORAGE_PROPERTY_QUERY для получения сведений об устройстве. Дополнительные сведения см. в разделе "Запросы, относящиеся к протоколу " и "Температурные запросы".

  • STORAGE_PROPERTY_QUERY . Эта структура включает поля PropertyId и AdditionalParameters , чтобы указать данные для запроса. В файле PropertyId используйте перечисление STORAGE_PROPERTY_ID для указания типа данных. Используйте поле AdditionalParameters для указания дополнительных сведений в зависимости от типа данных. Для данных, относящихся к протоколу, используйте структуру STORAGE_PROTOCOL_SPECIFIC_DATA в поле AdditionalParameters . Для данных температуры используйте структуру STORAGE_TEMPERATURE_INFO в поле AdditionalParameters .

  • STORAGE_PROPERTY_ID . Это перечисление включает новые значения, позволяющие IOCTL_STORAGE_QUERY_PROPERTY получать сведения о конкретной протоколе и температуре.

    • StorageAdapterProtocolSpecificProperty: если ProtocolType = ProtocolTypeNvme и DataType = NVMeDataTypeLogPage, вызывающие программы должны запрашивать фрагменты данных размером 512 байт.

    • Свойство, специфичное для протокола устройства хранения

      Используйте один из этих идентификаторов свойств, относящихся к протоколу, в сочетании с STORAGE_PROTOCOL_SPECIFIC_DATA для получения данных, относящихся к протоколу, в структуре STORAGE_PROTOCOL_DATA_DESCRIPTOR .

      • StorageAdapterTemperatureProperty
      • СвойствоТемпературыУстройстваХранения

      Используйте один из этих идентификаторов свойств температуры для получения данных температуры в структуре STORAGE_TEMPERATURE_DATA_DESCRIPTOR .

  • STORAGE_PROTOCOL_SPECIFIC_DATA : Получение данных, относящихся к NVMe, если эта структура используется для поля AdditionalParametersSTORAGE_PROPERTY_QUERY при указании значения перечисления STORAGE_PROTOCOL_NVME_DATA_TYPE. Используйте одно из следующих STORAGE_PROTOCOL_NVME_DATA_TYPE значений в поле DataType структуры STORAGE_PROTOCOL_SPECIFIC_DATA :

    • Используйте NVMeDataTypeIdentify для получения данных контроллера идентификации или определения данных пространства имен.
    • Используйте NVMeDataTypeLogPage для получения страниц журналов (включая данные smart/health).
    • Используйте NVMeDataTypeFeature для получения функций диска NVMe.
  • STORAGE_TEMPERATURE_INFO : эта структура используется для хранения определенных данных температуры. Он используется в STORAGE_TEMERATURE_DATA_DESCRIPTOR для возврата результатов запроса температуры.

  • IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD . Используйте этот IOCTL со структурой STORAGE_TEMPERATURE_THRESHOLD для задания пороговых значений температуры. Дополнительные сведения см. в разделе "Изменение поведения".

  • STORAGE_TEMPERATURE_THRESHOLD : эта структура используется в качестве входного буфера для указания порогового значения температуры. Поле OverThreshold (логическое значение) указывает, является ли значение поля Порог превышающим пороговое значение или нет (в противном случае это значение ниже порога).

Механизм сквозной передачи

Команды, которые не определены в спецификации NVMe, являются самыми сложными для обработки операционной системой узла — узел не имеет сведений по поводу влияния, которое команды могут оказать на целевое устройство, предоставленную инфраструктуру (пространства имен и размеры блоков) и его поведение.

Для более эффективной передачи команд, специфичных для устройства, через стек хранилища Windows, новый механизм проходной передачи позволяет прокладывать команды, специфичные для поставщика. Этот сквозной канал также поможет в разработке средств управления и тестирования. Однако для этого сквозного механизма требуется использование журнала эффектов команд. Кроме того, StoreNVMe.sys требует, чтобы все команды, а не только сквозные команды, описывались в журнале эффектов команд.

Это важно

StorNVMe.sys и Storport.sys будет блокировать любую команду на устройстве, если она не описана в журнале эффектов команд.

Поддержание журнала эффектов командных операций

Журнал эффектов команд (как описано в пунктах "Поддерживаемые команды" и "Эффекты", раздел 5.10.1.5 спецификации NVMe 1.2) позволяет описывать эффекты команд, зависимых от поставщика, наряду с командами, определенными спецификацией. Это упрощает проверку поддержки команд, а также оптимизацию поведения команд, поэтому ее следует реализовать для всего набора команд, поддерживаемых устройством. Следующие условия описывают результат отправки команды на основе записи журнала эффектов команд.

Для любой конкретной команды, описанной в журнале эффектов команд...

Хотя:

  • Для поддерживаемых команд (CSUPP) задано значение 1, определяющее, что команда поддерживается контроллером (Bit 01)

    Замечание

    Если для CSUPP задано значение 0 (означает, что команда не поддерживается), команда будет заблокирована.

И если задано одно из следующих элементов:

  • Изменение возможностей контроллера (CCC) имеет значение "1", обозначающее, что команда может изменить возможности контроллера (bit 04)
  • Изменение инвентаризации пространства имен (NIC) имеет значение "1", обозначающее, что команда может изменить число или возможности для нескольких пространств имен (бит 03)
  • Изменение возможностей пространства имен (NCC) имеет значение "1", обозначающее, что команда может изменить возможности одного пространства имен (Bit 02)
  • Для отправки и выполнения команд (CSE) задано значение 001b или 010b, что означает, что команда может быть отправлена, если в то же или любое пространство имен отсутствует другая команда, а другая команда не должна быть отправлена в то же или любое пространство имен, пока эта команда не будет завершена (Bits 18:16)

Затем команда будет отправлена как единственная незавершённая команда для адаптера.

В противном случае:

  • Для отправки и выполнения команд (CSE) задано значение 001b, что означает, что команда может быть отправлена, если в том же пространстве имен отсутствует другая команда, и что другая команда не должна быть отправлена в то же пространство имен, пока эта команда не будет завершена (Биты 18:16)

Затем команда будет отправлена как единственная невыполненная команда объекту с логическим номером устройства (LUN).

В противном случае команда отправляется с другими командами без ограничений. Например, если команда, связанная с поставщиком, отправляется на устройство для получения статистических сведений, не определенных спецификацией, не может возникнуть риск изменения поведения или возможностей устройства для выполнения команд ввода-вывода. Такие запросы можно обслуживать параллельно с вводом-выводом, и приостановка-возобновление не потребуется.

Использование IOCTL_STORAGE_PROTOCOL_COMMAND для отправки команд

Сквозная передача может выполняться с помощью IOCTL_STORAGE_PROTOCOL_COMMAND, представленной в Windows 10. Этот IOCTL был разработан для того, чтобы иметь схожее поведение с существующими IOCTL SCSI и сквозными IOCTL ATA, чтобы отправить встроенную команду на целевое устройство. С помощью этого протокола IOCTL сквозная передача может быть отправлена на устройство хранения, включая диск NVMe.

Например, в NVMe IOCTL позволит отправлять следующие коды команд.

  • Команды администратора конкретного поставщика (C0h — FFh)
  • Команды NVMe для конкретного поставщика (80h — FFh)

Как и во всех остальных ioCTLs, используйте DeviceIoControl для отправки сквозного IOCTL вниз. IOCTL заполняется при помощи структуры входного буфера STORAGE_PROTOCOL_COMMAND, найденной в ntddstor.h. Заполните поле "Команда " командой для конкретного поставщика.

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;

Команда поставщика, требуемая для отправки, должна быть заполнена в выделенном поле выше. Еще раз обратите внимание, что журнал командных эффектов должен быть реализован для сквозных команд. В частности, эти команды должны быть сообщены как поддерживаемые в журнале эффектов команд (дополнительные сведения см. в предыдущем разделе). Кроме того, обратите внимание, что поля PRP зависят от драйвера, поэтому приложения, отправляющие команды, могут оставить их в виде 0.

Наконец, этот сквозной протокол IOCTL предназначен для отправки команд, относящихся к поставщику. Для отправки других административных команд NVMe, не относящихся к конкретному поставщику, таких как Identify, нельзя использовать команду pass-through IOCTL. Например, IOCTL_STORAGE_QUERY_PROPERTY следует использовать для идентификации или получения страниц журналов. Дополнительные сведения см. в следующем разделе: запросы, относящиеся к протоколу.

Не обновляйте встроенное ПО через механизм сквозной передачи

Команды загрузки и активации встроенного ПО не должны отправляться с помощью сквозной передачи. IOCTL_STORAGE_PROTOCOL_COMMAND следует использовать только для команд, относящихся к поставщику.

Вместо этого используйте следующие общие команды ввода-вывода (IOCTL) для хранилища, представленные в Windows 10, чтобы предотвратить прямое использование приложениями версии IOCTL встроенного ПО SCSI_miniport. Драйверы хранилища преобразуют IOCTL в команду SCSI или SCSI_miniport версию IOCTL в мини-порт.

Эти IOCTLs рекомендуется использовать для разработки инструментов обновления встроенного ПО в Windows 10 и Windows Server 2016.

Для получения сведений о хранилище и обновлении встроенного ПО Windows также поддерживает командлеты PowerShell для быстрого выполнения этих действий:

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

Замечание

Чтобы обновить встроенное ПО в NVMe, используйте IOCTL_SCSI_MINIPORT_FIRMWARE. Дополнительные сведения см. в статье об обновлении встроенного ПО для устройства NVMe.

Возврат ошибок через механизм сквозной передачи

Аналогично SCSI и сквозным IOCTL ATA, когда команда/запрос отправляется минипорту или устройству, возвращается результат о том, была ли операция успешной или нет. В структуре STORAGE_PROTOCOL_COMMAND IOCTL возвращает состояние через поле ReturnStatus .

Пример: отправка команды для конкретного поставщика

В этом примере произвольная команда для конкретного поставщика (0xFF) передается через сквозный диск NVMe. Следующий код выделяет буфер, инициализирует запрос, а затем отправляет команду вниз на устройство через 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 
                            );  

В этом примере мы ожидаем protocolCommand->ReturnStatus == STORAGE_PROTOCOL_STATUS_SUCCESS, если команда успешно выполнена на устройстве.

Запросы, относящиеся к протоколу

Windows 8.1 представила IOCTL_STORAGE_QUERY_PROPERTY для извлечения данных. В Windows 10 IOCTL было улучшено для поддержки часто запрашиваемых функций NVMe, таких как получение страниц журналов, получение характеристик и идентификация. Это позволяет получить конкретную информацию NVMe для мониторинга и инвентаризации.

Входной буфер для IOCTL STORAGE_PROPERTY_QUERY (из Windows 10 и более поздних версий) показан здесь:

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

При использовании IOCTL_STORAGE_QUERY_PROPERTY для получения сведений о протоколе NVMe в STORAGE_PROTOCOL_DATA_DESCRIPTOR настройте структуру STORAGE_PROPERTY_QUERY следующим образом:

  • Выделите буфер, который может содержать как STORAGE_PROPERTY_QUERY, так и структуру STORAGE_PROTOCOL_SPECIFIC_DATA.
  • Задайте для поля PropertyID значение StorageAdapterProtocolSpecificProperty или StorageDeviceProtocolSpecificProperty для запроса контроллера или пространства имен соответственно.
  • Задайте для поля QueryType значение PropertyStandardQuery.
  • Заполните структуру STORAGE_PROTOCOL_SPECIFIC_DATA нужными значениями. Начало STORAGE_PROTOCOL_SPECIFIC_DATA — это поле AdditionalParametersSTORAGE_PROPERTY_QUERY.

Здесь показана структура STORAGE_PROTOCOL_SPECIFIC_DATA (из 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;

Чтобы указать тип сведений о протоколе NVMe, настройте структуру STORAGE_PROTOCOL_SPECIFIC_DATA следующим образом:

  • Задайте для поля ProtocolType значение ProtocolTypeNVMe.
  • Задайте для поля DataType значение перечисления, определенное STORAGE_PROTOCOL_NVME_DATA_TYPE:
    • Используйте NVMeDataTypeIdentify для получения данных контроллера идентификации или определения данных пространства имен.
    • Используйте NVMeDataTypeLogPage для получения страниц журналов (включая данные smart/health).
    • Используйте NVMeDataTypeFeature для получения функций диска NVMe.

Если ПротоколTypeNVMe используется в качестве протокола, запросы к сведениям, зависящим от протокола, можно получить параллельно с другими ввода-выводами на диске NVMe.

Это важно

Для IOCTL_STORAGE_QUERY_PROPERTY, использующего STORAGE_PROPERTY_IDStorageAdapterProtocolSpecificProperty, и когда структура STORAGE_PROTOCOL_SPECIFIC_DATA или STORAGE_PROTOCOL_SPECIFIC_DATA_EXT установлена на ProtocolType=ProtocolTypeNvme и DataType=NVMeDataTypeLogPage, установите для элемента ProtocolDataLength значение не менее 512 (байт).

В следующих примерах показаны запросы NVMe, относящиеся к протоколу.

Пример: запрос Identify NVMe

В этом примере запрос на идентификацию отправляется на диск NVMe. Следующий код инициализирует структуру данных запроса, а затем отправляет команду вниз на устройство с помощью 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"));
    }
}

Это важно

Для IOCTL_STORAGE_QUERY_PROPERTY, использующего STORAGE_PROPERTY_IDStorageAdapterProtocolSpecificProperty, где структура STORAGE_PROTOCOL_SPECIFIC_DATA или STORAGE_PROTOCOL_SPECIFIC_DATA_EXT задана ProtocolType=ProtocolTypeNvme, установите для элемента ProtocolDataLength значение не менее 512 (байт).

Обратите внимание, что вызывающий объект должен выделить один буфер, содержащий STORAGE_PROPERTY_QUERY и размер STORAGE_PROTOCOL_SPECIFIC_DATA. В этом примере используется тот же буфер для ввода и вывода из запроса свойства. Именно поэтому выделенный буфер имеет размер "FIELD_OFFSET(STORAGE_PROPERTY_QUERY, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE". Хотя отдельные буферы могут быть выделены для входных и выходных данных, рекомендуется использовать один буфер для запроса информации, связанной с NVMe.

identifyControllerData-NN> — количество пространств имен (NN). Windows обнаруживает пространство имен как физический диск.

Пример: запрос NVMe Get Log Pages

В этом примере, основанном на предыдущем, запрос Get Log Pages отправляется на накопитель NVMe. Следующий код подготавливает структуру данных запроса и отправляет команду вниз на устройство с помощью 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"));
}

Вызывающие могут использовать STORAGE_PROPERTY_IDStorageAdapterProtocolSpecificProperty, и чья структура STORAGE_PROTOCOL_SPECIFIC_DATA или STORAGE_PROTOCOL_SPECIFIC_DATA_EXT установлена ProtocolDataRequestValue=VENDOR_SPECIFIC_LOG_PAGE_IDENTIFIER, чтобы запрашивать 512-байтовые блоки данных, специфичных для поставщика.

Пример: запрос NVMe Get Features

На основе предыдущего примера, запрос на получение функций отправляется на диск NVMe. Следующий код подготавливает структуру данных запроса и отправляет команду вниз на устройство с помощью 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"));
}

Набор, зависящий от протокола

Обратите внимание, что в Windows 10 19H1 и более поздних версиях IOCTL_STORAGE_SET_PROPERTY был расширен для поддержки функций NVMe Set.

Входной буфер для IOCTL_STORAGE_SET_PROPERTY показан здесь:

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;

При использовании IOCTL_STORAGE_SET_PROPERTY для настройки функции NVMe настройте структуру STORAGE_PROPERTY_SET следующим образом:

  • Выделите буфер, который может содержать как STORAGE_PROPERTY_SET, так и структуру STORAGE_PROTOCOL_SPECIFIC_DATA_EXT.
  • Задайте для поля PropertyID значение StorageAdapterProtocolSpecificProperty или StorageDeviceProtocolSpecificProperty для запроса контроллера или пространства имен соответственно.
  • Заполните структуру STORAGE_PROTOCOL_SPECIFIC_DATA_EXT нужными значениями. Начало STORAGE_PROTOCOL_SPECIFIC_DATA_EXT — это поле Дополнительные параметрыSTORAGE_PROPERTY_SET.

Здесь показана структура 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;

Чтобы указать тип функции NVMe для установки, настройте структуру STORAGE_PROTOCOL_SPECIFIC_DATA_EXT следующим образом:

  • Установите поле ProtocolType на значение ProtocolTypeNvme;
  • Задайте для поля DataType значение перечисления NVMeDataTypeFeature, определяемое STORAGE_PROTOCOL_NVME_DATA_TYPE;

В следующих примерах демонстрируется набор компонентов NVMe.

Пример: набор функций NVMe

В этом примере запрос set Features отправляется на диск NVMe. Следующий код подготавливает структуру данных набора, а затем отправляет команду на устройство с помощью 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
);

Запросы температуры

В Windows 10 и более поздних версиях IOCTL_STORAGE_QUERY_PROPERTY также можно использовать для запроса данных температуры с устройств NVMe.

Чтобы получить сведения о температуре из диска NVMe в STORAGE_TEMPERATURE_DATA_DESCRIPTOR, настройте структуру STORAGE_PROPERTY_QUERY следующим образом:

  • Выделите буфер, который может содержать STORAGE_PROPERTY_QUERY структуру.
  • Задайте для поля PropertyID значение StorageAdapterTemperatureProperty или StorageDeviceTemperatureProperty для запроса контроллера или пространства имен соответственно.
  • Задайте для поля QueryType значение PropertyStandardQuery.

Здесь показана структура STORAGE_TEMPERATURE_INFO (доступна в 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;

Команды изменения поведения

Команды, которые управляют атрибутами устройства или потенциально влияют на его поведение, сложнее для обработки операционной системой. Если атрибуты устройства изменяются во время выполнения во время обработки ввода-вывода, могут возникнуть проблемы с синхронизацией или целостностью данных, если они не обрабатываются должным образом.

Команда NVMe Set-Features является хорошим примером команды изменения поведения. Он позволяет изменять механизм арбитража и устанавливать пороговые значения температуры. Чтобы гарантировать, что данные в процессе передачи не подвергаются риску при отправке команд набора параметров, влияющих на поведение, Windows приостанавливает все операции ввода-вывода на устройстве NVMe, выполняет очистку очередей и очистку буферов. После успешного выполнения команды set операции ввода-вывода возобновляются (если это возможно). Если не удается возобновить ввод-вывод, может потребоваться сброс устройства.

Установка пороговых значений температуры

Windows 10 представила IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD, IOCTL для получения и установки пороговых значений температуры. Вы также можете использовать его для получения текущей температуры устройства. Буфер входных и выходных данных для этого IOCTL — это структура STORAGE_TEMPERATURE_INFO из предыдущего раздела кода.

Пример. Настройка превышения пороговой температуры

В этом примере устанавливается пороговая температура диска NVMe. Следующий код подготавливает команду, а затем отправляет ее на устройство с помощью 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  
                            );

Настройка функций, относящихся к поставщику

Без журнала эффектов команд драйвер не знает о последствиях команды. Именно поэтому требуется журнал эффектов команд. Она помогает операционной системе определить, является ли команда высокоэффективной и может ли она быть отправлена параллельно с другими командами на накопитель.

Журнал эффектов команд еще недостаточно детализирован, чтобы включать команды Set-Features для конкретного поставщика. По этой причине пока невозможно отправить команды Set-Features для конкретного поставщика. Однако можно использовать механизм сквозной передачи, рассмотренный ранее, для отправки команд, относящихся к поставщику. Дополнительные сведения см. в разделе "Сквозный механизм".

Файлы заголовков

Следующие файлы относятся к разработке NVMe. Эти файлы включены в пакет СРЕДСТВ разработки программного обеспечения Microsoft Windows (SDK).

Файл заголовка Описание
ntddstor.h Определяет константы и типы для доступа к драйверам класса хранилища из режима ядра.
nvme.h Для других структур данных, связанных с NVMe.
winioctl.h Для общих определений IOCTL Win32, включая API хранилища для приложений в пользовательском режиме.

Спецификация NVMe 1.2