Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Относится к:
- 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. Он также описывает следующее:
- Как отправить команду NVMe, специфичную для поставщика, через сквозную передачу
- Отправка команды "Идентификация", "Получение функций" или "Получить страницы журналов " на диск NVMe
- Получение сведений о температуре с диска NVMe
- Как выполнять команды изменения поведения, такие как установка пороговых значений температуры
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 хранилища для приложений в пользовательском режиме. |