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


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

Относится к:

  • Windows 10
  • Windows Server 2016

Узнайте, как работать с высокоскоростными устройствами NVMe из приложения Windows. Доступ к устройству включен через StorNVMe.sys, встроенный драйвер впервые появился в Windows Server 2012 R2 и Windows 8.1. Он также доступен для устройств Windows 7 с помощью горячего исправления КБ. В 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_ERROR
    • STORAGE_PROTOCOL_STATUS_INVALID_REQUEST
    • STORAGE_PROTOCOL_STATUS_NO_DEVICE
    • STORAGE_PROTOCOL_STATUS_BUSY
    • STORAGE_PROTOCOL_STATUS_DATA_OVERRUN
    • STORAGE_PROTOCOL_STATUS_INSUFFICIENT_RESOURCES
    • STORAGE_PROTOCOL_STATUS_NOT_SUPPORTED
  • IOCTL_STORAGE_QUERY_PROPERTY. Используйте этот 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 получать сведения о конкретной протоколе и температуре.

    • служба хранилища AdapterProtocolSpecificProperty: If ProtocolType = ProtocolTypeNvme и DataType = NVMeDataTypeLogPage, вызывающие элементы должны запрашивать 512 байтовых блоков данных.
    • служба хранилища DeviceProtocolSpecificProperty

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

    • служба хранилища AdapterTemperatureProperty
    • служба хранилища DeviceTemperatureProperty

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

  • STORAGE_PROTOCOL_SPECIFIC_DATA. Получение данных, относящихся к NVMe, если эта структура используется для поля "Дополнительныеparameters" STORAGE_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 был разработан для того, чтобы иметь аналогичное поведение, как существующие SCSI и сквозные ioCTLs 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 для других администраторов или сторонних поставщиков, таких как Идентификация, этот сквозной протокол IOCTL не должен использоваться. Например, IOCTL_STORAGE_QUERY_PROPERTY следует использовать для идентификации или получения страниц журналов. Дополнительные сведения см. в следующем разделе: запросы, относящиеся к протоколу.

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

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

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

Эти ioCTLs рекомендуются для разработки средств обновления встроенного ПО в Windows 10 и Windows Server 2016:

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

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

Примечание.

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

 

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

Аналогично SCSI и сквозным ioCTLs ATA, когда команда/запрос отправляется минипорту или устройству, IOCTL возвращается, если это было успешно или нет. В структуре 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 значение служба хранилища AdapterProtocolSpecificProperty или служба хранилища DeviceProtocolSpecificProperty для запроса контроллера или пространства имен соответственно.

  • Задайте для поля QueryType значение PropertyStandardQuery.

  • Заполните структуру STORAGE_PROTOCOL_SPECIFIC_DATA нужными значениями. Начало STORAGE_PROTOCOL_SPECIFIC_DATA — это поле "Дополнительныеparameters" STORAGE_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_ID служба хранилища AdapterProtocolSpecificProperty, а для структуры STORAGE_PROTOCOL_SPECIFIC_DATA или STORAGE_PROTOCOL_SPECIFIC_DATA_EXT задано ProtocolType=ProtocolTypeNvme значение и DataType=NVMeDataTypeLogPageзадайте для элемента ProtocolDataLength той же структуры минимальное значение 512 (байт).

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

Пример: запрос на определение 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_ID служба хранилища AdapterProtocolSpecificProperty, а для структуры STORAGE_PROTOCOL_SPECIFIC_DATA или STORAGE_PROTOCOL_SPECIFIC_DATA_EXT задано ProtocolType=ProtocolTypeNvme значение и DataType=NVMeDataTypeLogPageзадайте для элемента ProtocolDataLength той же структуры минимальное значение 512 (байт).

Обратите внимание, что вызывающий объект должен выделить один буфер, содержащий STORAGE_PROPERTY_QUERY и размер STORAGE_PROTOCOL_SPECIFIC_DATA. В этом примере используется тот же буфер для ввода и вывода из запроса свойства. Именно поэтому выделенный буфер имеет размер "FIELD_OFFSET(STORAGE_PROPERTY_QUERY, ДополнительныеParameters) + 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_ID служба хранилища AdapterProtocolSpecificProperty, а структура 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 значение служба хранилища AdapterProtocolSpecificProperty или служба хранилища DeviceProtocolSpecificProperty для запроса контроллера или устройства или пространства имен соответственно.
  • Заполните структуру STORAGE_PROTOCOL_SPECIFIC_DATA_EXT нужными значениями. Начало STORAGE_PROTOCOL_SPECIFIC_DATA_EXT — это поле "Дополнительныеparameters" 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 значение служба хранилища AdapterTemperatureProperty или служба хранилища DeviceTemperatureProperty для запроса контроллера или пространства имен соответственно.

  • Задайте для поля 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, очереди очистки и буферы очистки. После успешного выполнения команды набора операций ввода-вывода возобновляется (если это возможно). Если не удается возобновить ввод-вывод, может потребоваться сброс устройства.

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

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

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

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

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

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

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

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