Trabalhar com unidades NVMe
Aplica-se a:
- Windows 10
- Windows Server 2016
Saiba como trabalhar com dispositivos NVMe de alta velocidade a partir da sua aplicação Windows. O acesso ao dispositivo é ativado através do StorNVMe.sys, o driver in-box introduzido pela primeira vez no Windows Server 2012 R2 e no Windows 8.1. Também está disponível para dispositivos Windows 7 através de um hot fix KB. No Windows 10, vários novos recursos foram introduzidos, incluindo um mecanismo de passagem para comandos NVMe específicos do fornecedor e atualizações para IOCTLs existentes.
Este tópico fornece uma visão geral das APIs de uso geral que você pode usar para acessar unidades NVMe no Windows 10. Também descreve:
- Como enviar um comando NVMe específico do fornecedor com passagem
- Como enviar um comando de Identificar, Obter Recursos ou Obter Páginas de Log para a unidade NVMe
- Como obter informações sobre a temperatura de uma unidade NVMe
- Como executar comandos de alteração de comportamento, como definir limites de temperatura
APIs para trabalhar com unidades NVMe
Você pode usar as seguintes APIs de uso geral para acessar unidades NVMe no Windows 10. Essas APIs podem ser encontradas em winioctl.h para aplicativos de modo de usuário e ntddstor.h para drivers de modo kernel. Para obter mais informações sobre arquivos de cabeçalho, consulte Arquivos de cabeçalho.
IOCTL_STORAGE_PROTOCOL_COMMAND : Use este IOCTL com a estrutura STORAGE_PROTOCOL_COMMAND para emitir comandos NVMe. Este IOCTL permite a passagem direta do NVMe e suporta o registo de efeitos de comando no NVMe. Você pode usá-lo com comandos específicos do fornecedor. Para obter mais informações, consulte o mecanismo de repasse.
STORAGE_PROTOCOL_COMMAND : Esta estrutura de buffer de entrada inclui um campo ReturnStatus que pode ser usado para relatar os seguintes valores de status.
- ESTADO_DO_PROTOCOLO_DE_ARMAZENAMENTO_PENDENTE
- STORAGE_PROTOCOL_STATUS_SUCCESS
- STORAGE_PROTOCOL_STATUS_ERROR
- STORAGE_PROTOCOL_STATUS_INVALID_REQUEST
- STATUS_PROT_POR_ARMAZENAMENTO_SEM_DISPOSITIVO
- STORAGE_PROTOCOL_STATUS_BUSY
- ESTADO_DO_PROTOCOLO_DE_ARMAZENAMENTO_EXCESSO_DE_DADOS
- STORAGE_PROTOCOL_STATUS_INSUFFICIENT_RESOURCES
- STORAGE_PROTOCOL_STATUS_NOT_SUPPORTED
IOCTL_STORAGE_QUERY_PROPERTY : Use este IOCTL com a estrutura STORAGE_PROPERTY_QUERY para recuperar informações do dispositivo. Para obter mais informações, consulte Perguntas Específicas do Protocolo e Perguntas de Temperatura.
STORAGE_PROPERTY_QUERY : Esta estrutura inclui os campos PropertyId e AdditionalParameters para especificar os dados a serem consultados. No campo PropertyId, use a enumeração STORAGE_PROPERTY_ID para especificar o tipo de dados. Use o campo AdditionalParameters para especificar mais detalhes, dependendo do tipo de dados. Para dados específicos do protocolo, use a estrutura STORAGE_PROTOCOL_SPECIFIC_DATA no campo AdditionalParameters. Para dados de temperatura, use a estrutura STORAGE_TEMPERATURE_INFO no campo Parâmetros Adicionais.
STORAGE_PROPERTY_ID : Esta enumeração inclui novos valores que permitem que IOCTL_STORAGE_QUERY_PROPERTY recupere informações específicas do protocolo e de temperatura.
- StorageAdapterProtocolSpecificProperty: Se ProtocolType = ProtocolTypeNvme e DataType = NVMeDataTypeLogPage, os chamadores devem solicitar blocos de dados de 512 bytes.
- PropriedadeEspecíficaDoProtocoloDoDispositivoDeArmazenamento
Use uma dessas IDs de propriedade específicas do protocolo em combinação com STORAGE_PROTOCOL_SPECIFIC_DATA para recuperar dados específicos do protocolo na estrutura STORAGE_PROTOCOL_DATA_DESCRIPTOR.
- StorageAdapterTemperatureProperty
- PropriedadeTemperaturaDispositivoArmazenamento
Use um desses IDs de propriedade de temperatura para recuperar dados de temperatura na estrutura STORAGE_TEMPERATURE_DATA_DESCRIPTOR.
STORAGE_PROTOCOL_SPECIFIC_DATA : Recupere dados específicos do NVMe quando essa estrutura é usada para o AdditionalParameters campo de STORAGE_PROPERTY_QUERY e um valor de enum STORAGE_PROTOCOL_NVME_DATA_TYPE é especificado. Use um dos seguintes valores de STORAGE_PROTOCOL_NVME_DATA_TYPE no campo DataType da estrutura STORAGE_PROTOCOL_SPECIFIC_DATA:
- Utilize NVMeDataTypeIdentify para obter os dados do Identify Controller ou do Identify Namespace.
- Use NVMeDataTypeLogPage para obter páginas de log (incluindo dados SMART/health).
- Use NVMeDataTypeFeature para obter funcionalidades da unidade NVMe.
STORAGE_TEMPERATURE_INFO : Esta estrutura é usada para armazenar dados de temperatura específicos. Ele é usado no STORAGE_TEMERATURE_DATA_DESCRIPTOR para retornar os resultados de uma consulta de temperatura.
IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD : Use este IOCTL com a estrutura STORAGE_TEMPERATURE_THRESHOLD para definir limites de temperatura. Para obter mais informações, consulte Comandos de alteração de comportamento.
STORAGE_TEMPERATURE_THRESHOLD : Esta estrutura é usada como um buffer de entrada para especificar o limite de temperatura. O campo OverThreshold (booleano) especifica se o campo Threshold é o valor acima do limite ou não (caso contrário, é o valor abaixo do limite).
Mecanismo de repasse
Os comandos que não estão definidos na especificação NVMe são os mais difíceis para o sistema operacional host manipular – o host não tem nenhuma visão sobre os efeitos que os comandos podem ter no dispositivo de destino, na infraestrutura exposta (namespaces/tamanhos de bloco) e seu comportamento.
Para transportar melhor esses comandos específicos do dispositivo através da pilha de armazenamento do Windows, um novo mecanismo de passagem permite que comandos específicos do fornecedor sejam canalizados. Este tubo de passagem também ajudará no desenvolvimento de ferramentas de gestão e teste. No entanto, esse mecanismo de passagem requer o uso do log de efeitos de comando. Além disso, StoreNVMe.sys requer que todos os comandos, não apenas os comandos de passagem, sejam descritos no Log de Efeitos de Comando.
Importante
StorNVMe.sys e Storport.sys bloquearão qualquer comando para um dispositivo se ele não estiver descrito no Log de Efeitos de Comando.
Dando suporte ao log de efeitos de comando
O Command Effects Log (conforme descrito em Commands Supported and Effects, seção 5.10.1.5 do NVMe Specification 1.2) permite a descrição dos efeitos de comandos específicos do fornecedor juntamente com comandos definidos por especificação. Isso facilita tanto a validação do suporte ao comando quanto a otimização do comportamento do comando e, portanto, deve ser implementado para todo o conjunto de comandos suportados pelo dispositivo. As condições a seguir descrevem o resultado de como o comando é enviado com base em sua entrada Command Effects Log.
Para qualquer comando específico descrito no Command Effects Log...
Enquanto:
Command Supported (CSUPP) é definido como '1' significando que o comando é suportado pelo controlador (Bit 01)
Observação
Quando CSUPP é definido como '0' (significando que o comando não é suportado), o comando será bloqueado
E se alguma das seguintes opções estiver definida:
A Alteração da Capacidade do Controlador (CCC) está definida como '1', o que significa que o comando pode alterar as capacidades do controlador (Bit 04)
Namespace Inventory Change (NIC) é definido como '1', significando que o comando pode alterar o número ou os recursos para vários namespaces (Bit 03)
Namespace Capability Change (NCC) é definido como '1', significando que o comando pode alterar os recursos de um único namespace (Bit 02)
Command Submission and Execution (CSE) é definido como 001b ou 010b, significando que o comando pode ser enviado quando não há outro comando pendente para o mesmo ou qualquer namespace, e que outro comando não deve ser enviado para o mesmo ou qualquer namespace até que este comando seja concluído (Bits 18:16)
Em seguida, o comando será enviado como o único comando pendente para o adaptador.
Caso contrário, se:
- Command Submission and Execution (CSE) é definido como 001b, significando que o comando pode ser enviado quando não há outro comando pendente para o mesmo namespace e que outro comando não deve ser enviado para o mesmo namespace até que esse comando seja concluído (Bits 18:16)
Em seguida, o comando será enviado como o único comando pendente para o objeto Logical Unit Number (LUN).
Caso contrário,, o comando é enviado com outros comandos pendentes sem inibição. Por exemplo, se um comando específico do fornecedor for enviado para o dispositivo para recuperar informações estatísticas que não estão definidas por especificações, não deve haver risco de alterar o comportamento do dispositivo ou a capacidade de executar comandos de E/S. Tais solicitações poderiam ser atendidas em paralelo à E/S e nenhuma pausa-retomada seria necessária.
Usando IOCTL_STORAGE_PROTOCOL_COMMAND para enviar comandos
A transmissão pode ser realizada usando o IOCTL_STORAGE_PROTOCOL_COMMAND, introduzido no Windows 10. Este IOCTL foi projetado para ter um comportamento semelhante aos IOCTLs de passagem SCSI e ATA existentes, para enviar um comando incorporado para o dispositivo de destino. Através deste IOCTL, a passagem pode ser enviada para um dispositivo de armazenamento, incluindo uma unidade NVMe.
Por exemplo, no NVMe, o IOCTL permitirá o envio dos seguintes códigos de comando.
- Comandos de administração específicos do fornecedor (C0h – FFh)
- Comandos NVMe específicos do fornecedor (80h – FFh)
Tal como acontece com todas as outras IOCTLs, Use DeviceIoControl para enviar a IOCTL de passagem para baixo. O IOCTL é preenchido usando a estrutura de buffer de entrada STORAGE_PROTOCOL_COMMAND encontrada em ntddstor.h. Preencha o campo Comando com o comando específico do fornecedor.
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;
O comando específico do fornecedor que deseja ser enviado deve ser preenchido no campo destacado acima. Observe novamente que o Log de Efeitos de Comando deve ser implementado para comandos de passagem. Em particular, esses comandos precisam ser relatados como suportados no Log de Efeitos de Comando (consulte a seção anterior para obter mais informações). Observe também que os campos PRP são específicos do driver, portanto, os aplicativos que enviam comandos podem deixá-los como 0.
Finalmente, este IOCTL de passagem destina-se ao envio de comandos específicos do fornecedor. Para enviar outros comandos NVMe específicos de administradores ou não fornecedores, como Identify, essa IOCTL de passagem não deve ser usada. Por exemplo, IOCTL_STORAGE_QUERY_PROPERTY deve ser usado para Identificar ou Obter Páginas de Log. Para saber mais, veja a próxima seção, Consultas específicas do protocolo.
Não atualize o firmware através do mecanismo de passagem
Os comandos de download e ativação do firmware não devem ser enviados usando o pass-through. IOCTL_STORAGE_PROTOCOL_COMMAND só deve ser usado para comandos específicos do fornecedor.
Em vez disso, utilize os seguintes IOCTLs de armazenamento geral (introduzidos no Windows 10) para evitar que as aplicações usem diretamente a versão SCSI_miniport do IOCTL de firmware. Os drivers de armazenamento traduzirão o IOCTL para um comando SCSI ou a versão SCSI_miniport do IOCTL para a miniporta.
Estas IOCTLs são recomendadas para desenvolver ferramentas de atualização de firmware no Windows 10 e no Windows Server 2016:
Para obter informações de armazenamento e atualizar o firmware, o Windows também oferece suporte a cmdlets do PowerShell para fazer isso rapidamente:
Get-StorageFirmwareInfo
Update-StorageFirmware
Observação
Para atualizar o firmware em uma NVMe no Windows 8.1, use o IOCTL_SCSI_MINIPORT_FIRMWARE. Este IOCTL não foi retroportado para o Windows 7. Para obter mais informações, consulte atualizando firmware para um dispositivo NVMe no Windows 8.1.
Retornando erros através do mecanismo de passagem
Semelhante aos IOCTLs de passagem SCSI e ATA, quando um comando/solicitação é enviado para o miniport ou dispositivo, o IOCTL retorna se foi bem-sucedido ou não. Na estrutura STORAGE_PROTOCOL_COMMAND, o IOCTL retorna o status por meio do campo ReturnStatus.
Exemplo: enviar um comando específico do fornecedor
Neste exemplo, um comando arbitrário específico do fornecedor (0xFF) é enviado via passagem para uma unidade NVMe. O código a seguir aloca um buffer, inicializa uma consulta e, em seguida, envia o comando para o dispositivo via 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
);
Neste exemplo, esperamos protocolCommand->ReturnStatus == STORAGE_PROTOCOL_STATUS_SUCCESS
se o comando for executado com sucesso no dispositivo.
Consultas específicas do protocolo
O Windows 8.1 introduziu IOCTL_STORAGE_QUERY_PROPERTY para recuperação de dados. No Windows 10, o IOCTL foi aprimorado para oferecer suporte a recursos NVMe comumente solicitados, como Obter páginas de log, Obter recursose Identificar. Isso permite a recuperação de informações específicas do NVMe para fins de monitoramento e inventário.
O buffer de entrada para o IOCTL, STORAGE_PROPERTY_QUERY (do Windows 10) é mostrado aqui.
typedef struct _STORAGE_PROPERTY_QUERY {
STORAGE_PROPERTY_ID PropertyId;
STORAGE_QUERY_TYPE QueryType;
UCHAR AdditionalParameters[1];
} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;
Ao usar IOCTL_STORAGE_QUERY_PROPERTY para recuperar informações específicas do protocolo NVMe no STORAGE_PROTOCOL_DATA_DESCRIPTOR, configure a estrutura STORAGE_PROPERTY_QUERY da seguinte maneira:
Aloque um buffer que possa conter uma estrutura STORAGE_PROPERTY_QUERY e STORAGE_PROTOCOL_SPECIFIC_DATA.
Defina o campo PropertyID como StorageAdapterProtocolSpecificProperty ou StorageDeviceProtocolSpecificProperty para uma solicitação de controlador ou dispositivo/namespace, respectivamente.
Defina o campo QueryType como PropertyStandardQuery.
Preencha a estrutura STORAGE_PROTOCOL_SPECIFIC_DATA com os valores desejados. O início do STORAGE_PROTOCOL_SPECIFIC_DATA é o campo AdditionalParameters de STORAGE_PROPERTY_QUERY.
A estrutura STORAGE_PROTOCOL_SPECIFIC_DATA (do Windows 10) é mostrada aqui.
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;
Para especificar um tipo de informações específicas do protocolo NVMe, configure a estrutura STORAGE_PROTOCOL_SPECIFIC_DATA da seguinte maneira:
Defina o campo ProtocolType como ProtocolTypeNVMe.
Defina o campo DataType como um valor de enumeração definido por STORAGE_PROTOCOL_NVME_DATA_TYPE:
- Use NVMeDataTypeIdentify para obter dados do Identify Controller ou Identify Namespace.
- Utilize NVMeDataTypeLogPage para obter páginas de log (incluindo dados SMART/saúde).
- Use NVMeDataTypeFeature para obter características da unidade NVMe.
Quando ProtocolTypeNVMe é usado como o ProtocolType, as consultas para obter informações específicas do protocolo podem ser recuperadas em paralelo com outras operações de E/S na drive NVMe.
Importante
Para um IOCTL_STORAGE_QUERY_PROPERTY que utiliza um STORAGE_PROPERTY_ID de StorageAdapterProtocolSpecificProperty, e cuja estrutura STORAGE_PROTOCOL_SPECIFIC_DATA ou STORAGE_PROTOCOL_SPECIFIC_DATA_EXT está configurada para ProtocolType=ProtocolTypeNvme
e DataType=NVMeDataTypeLogPage
, defina o membro ProtocolDataLength dessa mesma estrutura para um valor mínimo de 512 bytes.
Os exemplos a seguir demonstram consultas específicas do protocolo NVMe.
Exemplo: consulta NVMe Identify
Neste exemplo, a solicitação Identificar é enviada para uma unidade NVMe. O código a seguir inicializa a estrutura de dados de consulta e, em seguida, envia o comando para baixo para o dispositivo via 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"));
}
}
Importante
Para um IOCTL_STORAGE_QUERY_PROPERTY que usa uma STORAGE_PROPERTY_ID de StorageAdapterProtocolSpecificPropertye cuja estrutura STORAGE_PROTOCOL_SPECIFIC_DATA ou STORAGE_PROTOCOL_SPECIFIC_DATA_EXT está definida como ProtocolType=ProtocolTypeNvme
e DataType=NVMeDataTypeLogPage
, defina o membro ProtocolDataLength dessa mesma estrutura para um valor mínimo de 512 (bytes).
Observe que o chamador precisa alocar um único buffer contendo STORAGE_PROPERTY_QUERY e o tamanho do STORAGE_PROTOCOL_SPECIFIC_DATA. Neste exemplo, ele está usando o mesmo buffer para entrada e saída da consulta de propriedade. É por isso que o buffer que foi alocado tem um tamanho de "FIELD_OFFSET(STORAGE_PROPERTY_QUERY, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE". Embora buffers separados possam ser alocados para entrada e saída, recomendamos o uso de um único buffer para consultar informações relacionadas ao NVMe.
identifyControllerData->NN é o número de namespaces (NN). O Windows detecta um namespace como um disco físico.
Exemplo: consulta NVMe Get Log Pages
Neste exemplo, baseado no anterior, a solicitação Obter Páginas de Log é enviada para uma unidade NVMe. O código a seguir prepara a estrutura de dados de consulta e, em seguida, envia o comando para o dispositivo via 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"));
}
Os chamadores podem usar uma STORAGE_PROPERTY_ID de StorageAdapterProtocolSpecificPropertye cuja estrutura de STORAGE_PROTOCOL_SPECIFIC_DATA ou STORAGE_PROTOCOL_SPECIFIC_DATA_EXT está definida para ProtocolDataRequestValue=VENDOR_SPECIFIC_LOG_PAGE_IDENTIFIER
para solicitar blocos de 512 bytes de dados específicos do fornecedor.
Exemplo: consulta NVMe Get Features
Neste exemplo, com base no anterior, a solicitação Obter recursos é enviada para uma unidade NVMe. O código a seguir prepara a estrutura de dados de consulta e, em seguida, envia o comando para o dispositivo via 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"));
}
Conjunto específico do protocolo
A partir do Windows 10 19H1, o IOCTL_STORAGE_SET_PROPERTY foi aprimorado para suportar a definição de funcionalidades NVMe.
O buffer de entrada para o IOCTL_STORAGE_SET_PROPERTY é mostrado aqui:
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;
Ao usar IOCTL_STORAGE_SET_PROPERTY para definir o recurso NVMe, configure a estrutura STORAGE_PROPERTY_SET da seguinte maneira:
- Alocar um buffer que possa conter tanto uma estrutura STORAGE_PROPERTY_SET quanto uma estrutura STORAGE_PROTOCOL_SPECIFIC_DATA_EXT.
- Defina o campo PropertyID como StorageAdapterProtocolSpecificProperty ou StorageDeviceProtocolSpecificProperty para uma solicitação de controlador ou dispositivo/namespace, respectivamente.
- Preencha a estrutura STORAGE_PROTOCOL_SPECIFIC_DATA_EXT com os valores desejados. O início do STORAGE_PROTOCOL_SPECIFIC_DATA_EXT é o campo AdditionalParameters de STORAGE_PROPERTY_SET.
A estrutura STORAGE_PROTOCOL_SPECIFIC_DATA_EXT é mostrada aqui.
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;
Para especificar um tipo de recurso NVMe a ser definido, configure a estrutura STORAGE_PROTOCOL_SPECIFIC_DATA_EXT da seguinte maneira:
- Defina o campo ProtocolType como ProtocolTypeNvme;
- Defina o campo DataType para o valor de enumeração NVMeDataTypeFeature definido por STORAGE_PROTOCOL_NVME_DATA_TYPE;
Os exemplos a seguir demonstram o conjunto de recursos NVMe.
Exemplo: Definir Funcionalidades NVMe
Neste exemplo, a solicitação Definir recursos é enviada para uma unidade NVMe. O código a seguir prepara a estrutura de dados definida e, em seguida, envia o comando para baixo para o dispositivo via 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
);
Consultas de temperatura
No Windows 10, IOCTL_STORAGE_QUERY_PROPERTY também pode ser usado para consultar dados de temperatura de dispositivos NVMe.
Para recuperar informações de temperatura de uma unidade NVMe no STORAGE_TEMPERATURE_DATA_DESCRIPTOR, configure a estrutura STORAGE_PROPERTY_QUERY da seguinte maneira:
Aloque um buffer que possa conter uma estrutura STORAGE_PROPERTY_QUERY.
Defina o campo PropertyID como StorageAdapterTemperatureProperty ou StorageDeviceTemperatureProperty para uma solicitação de controlador ou dispositivo/namespace, respectivamente.
Defina o campo QueryType como PropertyStandardQuery .
A estrutura STORAGE_TEMPERATURE_INFO (do Windows 10) é mostrada aqui.
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;
Comandos de alteração de comportamento
Comandos que manipulam atributos do dispositivo ou potencialmente afetam o comportamento do dispositivo são mais difíceis para o sistema operacional lidar. Se os atributos do dispositivo forem alterados em tempo de execução enquanto a E/S está sendo processada, problemas de sincronização ou integridade de dados podem surgir se não forem tratados corretamente.
O comando NVMe Set-Features é um bom exemplo de um comando de mudança de comportamento. Permite a alteração do mecanismo de arbitragem e a fixação de limiares de temperatura. Para garantir que os dados em voo não estejam em risco quando comandos de conjunto que afetam o comportamento forem enviados, o Windows pausará todas as E/S para o dispositivo NVMe, drenará filas e liberará buffers. Uma vez que o comando set tenha sido executado com êxito, a E/S é retomada (se possível). Se a E/S não puder ser retomada, poderá ser necessária uma reposição do dispositivo.
Definição de limiares de temperatura
O Windows 10 introduziu o IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD, um IOCTL para obter e definir limites de temperatura. Você também pode usá-lo para obter a temperatura atual do dispositivo. O buffer de entrada/saída para este IOCTL é a estrutura STORAGE_TEMPERATURE_INFO, da seção de código anterior.
Exemplo: Definir temperatura acima do limiar
Neste exemplo, a temperatura acima do limiar de uma unidade NVMe é definida. O código a seguir prepara o comando e, em seguida, envia-o para o dispositivo via 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
);
Definindo recursos específicos do fornecedor
Sem o Command Effects Log, o driver não tem conhecimento das ramificações do comando. É por isso que o Command Effects Log é necessário. Isto ajuda o sistema operativo a determinar se um comando é de alto impacto e se pode ser enviado em paralelo com outros comandos para o disco.
O Log de Efeitos de Comando ainda não é granular o suficiente para abranger comandos Set-Features específicos do fornecedor. Por esse motivo, ainda não é possível enviar comandos Set-Features específicos do fornecedor. No entanto, é possível usar o mecanismo de passagem, discutido anteriormente, para enviar comandos específicos do fornecedor. Para mais informações, consulte mecanismo de repasse.
Arquivos de cabeçalho
Os seguintes arquivos são relevantes para o desenvolvimento NVMe. Esses arquivos estão incluídos com o Microsoft Windows Software Development Kit (SDK).
Arquivo de cabeçalho | Descrição |
---|---|
ntddstor.h | Define constantes e tipos para acessar os drivers de classe de armazenamento a partir do modo kernel. |
nvme.h | Para outras estruturas de dados relacionadas ao NVMe. |
winioctl.h | Para definições gerais de IOCTL do Win32, incluindo APIs de armazenamento para aplicativos de modo de usuário. |