Compartilhar via


Trabalhando com unidades NVMe

aplica-se a:

  • Windows 10
  • Windows Server 2016

Saiba como trabalhar com dispositivos NVMe de alta velocidade do aplicativo Windows. O acesso ao dispositivo é habilitado por meio de StorNVMe.sys, o driver integrado introduzido pela primeira vez no Windows Server 2012 R2 e no Windows 8.1. Ele também está disponível para dispositivos Windows 7 por meio de um hotfix 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. Ele também descreve:

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. Esse IOCTL habilita a passagem do NVMe e dá suporte ao log 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 passagem.

  • STORAGE_PROTOCOL_COMMAND : essa estrutura de buffer de entrada inclui um campo ReturnStatus que pode ser usado para relatar os seguintes valores de status.

    • 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_DADOS_EXCEDIDOS
    • STORAGE_PROTOCOL_STATUS_RECURSOS_INSUFICIENTES
    • STORAGE_PROTOCOL_STATUS_NOT_SUPPORTED (Protocolo de Armazenamento: Status Não Suportado)
  • 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 consultas específicas do protocolo e consultas de temperatura.

  • STORAGE_PROPERTY_QUERY : essa 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 AdditionalParameters .

  • STORAGE_PROPERTY_ID : essa enumeração inclui novos valores que permitem que IOCTL_STORAGE_QUERY_PROPERTY recuperem informações de temperatura e específicas do protocolo.

    • StorageAdapterProtocolSpecificProperty: se ProtocolType = ProtocolTypeNvme e DataType = NVMeDataTypeLogPage, os chamadores devem solicitar blocos de dados de 512 bytes.

    • StorageDeviceProtocolSpecificProperty

      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 .

      • PropriedadeTemperaturaDoAdaptadorDeArmazenamento
      • StorageDeviceTemperatureProperty

      Use uma dessas 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 for usada para o campo AdditionalParameters de STORAGE_PROPERTY_QUERY e um valor de enumeração STORAGE_PROTOCOL_NVME_DATA_TYPE for especificado. Use um dos seguintes valores STORAGE_PROTOCOL_NVME_DATA_TYPE no campo DataType da estrutura STORAGE_PROTOCOL_SPECIFIC_DATA:

    • Use NVMeDataTypeIdentify para identificar dados do controlador ou identificar dados do namespace.
    • Use NVMeDataTypeLogPage para obter páginas de log (incluindo dados SMART/health).
    • Use NVMeDataTypeFeature para obter recursos da unidade NVMe.
  • STORAGE_TEMPERATURE_INFO : essa estrutura é usada para conter 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 : essa estrutura é usada como um buffer de entrada para especificar o limite de temperatura. O campo OverThreshold (booliano) especifica se o campo Limite é 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 informações sobre os efeitos que os comandos podem ter no dispositivo de destino, na infraestrutura exposta (namespaces/tamanhos de bloco) e em seu comportamento.

Para carregar melhor esses comandos específicos do dispositivo por meio da pilha de armazenamento do Windows, um novo mecanismo de passagem permite que comandos específicos do fornecedor sejam canalizados. Esse tubo de passagem também ajudará no desenvolvimento de ferramentas de gerenciamento e teste. No entanto, esse mecanismo de passagem requer o uso do Registro de Efeitos de Comando. Além disso, StoreNVMe.sys requer que todos os comandos, não apenas comandos de passagem, sejam descritos no Log de Efeitos de Comando.

Importante

StorNVMe.sys e Storport.sys bloqueará qualquer comando para um dispositivo se ele não for descrito no Log de Efeitos de Comando.

Suporte ao Registro de Efeitos de Comando

O Log de Efeitos de Comando (conforme descrito em Comandos com suporte e efeitos, seção 5.10.1.5 da Especificação NVMe 1.2) permite a descrição dos efeitos dos comandos específicos do fornecedor, juntamente com comandos definidos por especificação. Isso facilita tanto a validação de suporte de comando quanto a otimização de comportamento do comando e, portanto, deve ser implementada para todo o conjunto de comandos que o dispositivo dá suporte. As condições a seguir descrevem o resultado de como o comando é enviado com base na entrada do Log de Efeitos de Comando.

Para qualquer comando específico descrito no Log de Efeitos de Comando...

Enquanto:

  • O comando com suporte (CSUPP) é definido como '1' significando que o comando tem suporte pelo controlador (Bit 01)

    Observação

    Quando o CSUPP for definido como '0' (significando que o comando não tem suporte) o comando será bloqueado

E se qualquer um dos seguintes itens estiver definido:

  • A CCC (Alteração de Capacidade do Controlador) está definida como '1' significando que o comando pode alterar as funcionalidades do controlador (Bit 04)
  • NIC (Namespace Inventory Change, Alteração de Inventário de Namespace) está definida como '1', significando que o comando pode alterar o número ou capacidades para vários namespaces (Bit 03)
  • A NCC (Alteração de Funcionalidade de Namespace) está definida como '1' significando que o comando pode alterar os recursos de um único namespace (Bit 02)
  • O CSE (Envio e Execução de Comando) é definido como 001b ou 010b, significando que o comando pode ser enviado quando não houver nenhum outro comando pendente no mesmo namespace ou em qualquer namespace e que outro comando não deve ser enviado para o mesmo ou qualquer namespace até que esse 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:

  • O CSE (Envio e Execução de Comando) está definido como 001b, significando que o comando pode ser enviado quando não houver outro comando pendente no 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 LUN (objeto Número de Unidade Lógica).

Caso contrário, o comando é enviado junto com outros comandos pendentes, sem impedimentos. Por exemplo, se um comando específico do fornecedor for enviado ao dispositivo para recuperar informações estatísticas que não estão definidas por especificação, não haverá risco de alterar o comportamento ou a capacidade do dispositivo de executar comandos de E/S. Essas solicitações poderiam ser atendidas em paralelo com a E/S e nenhuma pausa e retomada seria necessária.

Usando IOCTL_STORAGE_PROTOCOL_COMMAND para enviar comandos

A passagem pode ser realizada usando o IOCTL_STORAGE_PROTOCOL_COMMAND, introduzido no Windows 10. Este IOCTL foi projetado para ter um comportamento semelhante aos IOCTLs pass-through existentes do SCSI e do ATA, para enviar um comando embutido para o dispositivo de destino. Por meio desse IOCTL, o comando de passagem pode ser enviado para um dispositivo de armazenamento, incluindo uma unidade NVMe.

Por exemplo, no NVMe, o IOCTL permitirá o envio dos códigos de comando a seguir.

  • Comandos de administrador específicos do fornecedor (C0h – FFh)
  • Comandos NVMe específicos do fornecedor (80h – FFh)

Como em todos os outros IOCTLs, use DeviceIoControl para enviar o IOCTL de passagem para o nível inferior. O IOCTL é preenchido usando a estrutura de buffer de entrada do 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 desejado para ser enviado deve ser preenchido no campo realçado acima. Lembre-se novamente de que o Log de Efeitos de Comando deve ser implementado para comandos pass-through. Em particular, esses comandos precisam ser relatados com suporte 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 comandos de envio de aplicativos podem deixá-los como 0.

Por fim, esse IOCTL de passagem destina-se ao envio de comandos específicos do fornecedor. Para enviar outros comandos NVMe de administração ou não específicos do fornecedor, como Identificar, este IOCTL de passagem não deve ser usado. Por exemplo, IOCTL_STORAGE_QUERY_PROPERTY deve ser usado para identificar ou obter páginas de log. Para obter mais informações, consulte a próxima seção, consultas específicas do protocolo.

Não atualize o firmware por meio do mecanismo de passagem

Os comandos de download e ativação do firmware não devem ser enviados usando pass-through. IOCTL_STORAGE_PROTOCOL_COMMAND só deve ser usado para comandos específicos do fornecedor.

Em vez disso, use os seguintes IOCTLs de armazenamento geral (introduzidos no Windows 10) para evitar que aplicativos usem diretamente a versão SCSI_miniport do Firmware IOCTL. Os drivers de armazenamento traduzirão o IOCTL para um comando SCSI ou a versão SCSI_miniport do IOCTL para o miniporto.

Esses IOCTLs são recomendados 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 dá suporte a cmdlets do PowerShell para fazer isso rapidamente:

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

Retornando erros por meio do mecanismo de passagem

Semelhante às IOCTLs de passagem de SCSI e ATA, quando um comando/solicitação é enviado para o miniporto 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 por meio de passagem para uma unidade NVMe. O código a seguir aloca um buffer, inicializa uma consulta e envia o comando para o dispositivo por meio de 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 tiver êxito 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 dar suporte a recursos de NVMe geralmente solicitados, como Obter Páginas de Log, Obter Recursos e 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 e posterior) é 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 de STORAGE_PROPERTY_QUERY da seguinte maneira:

  • Aloque um buffer que possa conter um STORAGE_PROPERTY_QUERY e uma estrutura de STORAGE_PROTOCOL_SPECIFIC_DATA.
  • Defina o campo PropertyID para StorageAdapterProtocolSpecificProperty ou StorageDeviceProtocolSpecificProperty para uma solicitação de dispositivo/namespace ou controlador, 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 de STORAGE_PROTOCOL_SPECIFIC_DATA (do Windows 10 e posterior) é 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 para ProtocolTypeNVMe.
  • Defina o campo DataType como um valor de enumeração definido por STORAGE_PROTOCOL_NVME_DATA_TYPE:
    • Use NVMeDataTypeIdentify para identificar dados do controlador ou identificar dados do namespace.
    • Use NVMeDataTypeLogPage para obter páginas de log (incluindo dados SMART/health).
    • Use NVMeDataTypeFeature para obter recursos da unidade NVMe.

Quando ProtocolTypeNVMe é usado como ProtocolType, as consultas para informações específicas do protocolo podem ser recuperadas em paralelo com outras E/S na unidade NVMe.

Importante

Para um IOCTL_STORAGE_QUERY_PROPERTY que usa uma STORAGE_PROPERTY_ID de StorageAdapterProtocolSpecificProperty e cuja estrutura STORAGE_PROTOCOL_SPECIFIC_DATA ou STORAGE_PROTOCOL_SPECIFIC_DATA_EXT está definida nos valores representados por 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 de Identificação NVMe

Neste exemplo, a solicitação Identifique é enviada para uma unidade NVMe. O código a seguir inicializa a estrutura de dados de consulta e envia o comando para o dispositivo por meio de 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 StorageAdapterProtocolSpecificProperty e cuja estrutura STORAGE_PROTOCOL_SPECIFIC_DATA ou STORAGE_PROTOCOL_SPECIFIC_DATA_EXT está definida nos valores representados por 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 de 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 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 usar um único buffer para consultar informações relacionadas ao NVMe.

identifyControllerData->NN é o Número de Namespaces (NN). Windows reconhece um namespace como um drive físico.

Exemplo: consulta NVMe Get Log Pages

Neste exemplo, com base na 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 envia o comando para o dispositivo por meio de 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 um STORAGE_PROPERTY_ID de StorageAdapterProtocolSpecificProperty e cuja STORAGE_PROTOCOL_SPECIFIC_DATA ou STORAGE_PROTOCOL_SPECIFIC_DATA_EXT está configurada como 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 na anterior, a solicitação Obter Recursos é enviada para uma unidade NVMe. O código a seguir prepara a estrutura de dados de consulta e envia o comando para o dispositivo por meio de 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

Observe que, no Windows 10 19H1 e posterior, o IOCTL_STORAGE_SET_PROPERTY foi aprimorado para dar suporte a recursos de conjunto de NVMe.

O buffer de entrada do 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 pode conter um STORAGE_PROPERTY_SET e uma estrutura STORAGE_PROTOCOL_SPECIFIC_DATA_EXT ;
  • Defina o campo PropertyID para StorageAdapterProtocolSpecificProperty ou StorageDeviceProtocolSpecificProperty para uma solicitação de dispositivo/namespace ou controlador, 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 como o valor de enumeração NVMeDataTypeFeature definido por STORAGE_PROTOCOL_NVME_DATA_TYPE;

Os exemplos a seguir demonstram o conjunto de recursos NVMe.

Exemplo: Configurar Recursos do NVMe

Neste exemplo, a solicitação Definir Recursos é enviada para uma unidade NVMe. O código a seguir prepara a estrutura de dados do conjunto e envia o comando para o dispositivo por meio de 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 e posterior, IOCTL_STORAGE_QUERY_PROPERTY também podem ser usados 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 dispositivo/namespace ou controlador, respectivamente.
  • Defina o campo QueryType como PropertyStandardQuery .

A estrutura de STORAGE_TEMPERATURE_INFO (disponível no Windows 10 e posterior) é 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 de dispositivo ou potencialmente afetam o comportamento do dispositivo são mais difíceis de lidar com o sistema operacional. Se os atributos do dispositivo forem alterados em tempo de execução enquanto a E/S estiver sendo processada, problemas de sincronização ou integridade de dados poderão surgir se não forem tratados corretamente.

O comando NVMe Set-Features é um bom exemplo de um comando de alteração de comportamento. Ele permite a alteração do mecanismo de arbitragem e a configuração dos limites de temperatura. Para garantir que os dados in-flight não estejam em risco quando os comandos de conjunto que afetam o comportamento forem enviados para baixo, o Windows pausará todas as E/S para o dispositivo NVMe, esvaziará filas e liberará buffers. Depois que o comando set for executado com êxito, a E/S será retomada (se possível). Se a E/S não puder ser retomada, uma reinicialização do dispositivo poderá ser necessária.

Definindo limites de temperatura

O Windows 10 introduziu 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 esse IOCTL é a estrutura STORAGE_TEMPERATURE_INFO , da seção de código anterior.

Exemplo: definindo a temperatura acima do limite

Neste exemplo, é definida a temperatura limite para uma unidade NVMe. O código a seguir prepara o comando e o envia para o dispositivo por meio de 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  
                            );

Configurando recursos específicos do fornecedor

Sem o Log de Efeitos de Comando, o driver não tem conhecimento das ramificações do comando. É por isso que o Log de Efeitos de Comando é necessário. Isso ajuda o sistema operacional a determinar se um comando tem alto impacto e se ele pode ser enviado em paralelo com outros comandos para a unidade.

O Log de Efeitos de Comando ainda não é granular o suficiente para abranger comandos Set-Features específicos do fabricante. 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 obter mais informações, consulte o mecanismo de passagem.

Arquivos de cabeçalho

Os arquivos a seguir são relevantes para o desenvolvimento do NVMe. Esses arquivos são incluídos no SDK (Microsoft Windows Software Development Kit).

Arquivo de cabeçalho Descrição
ntddstor.h Define constantes e tipos para acessar os drivers de classe de armazenamento do modo kernel.
nvme.h Para outras estruturas de dados relacionadas ao NVMe.
winioctl.h Para definições gerais do Win32 IOCTL, incluindo APIs de armazenamento para aplicativos de modo de usuário.

Especificação NVMe 1.2