Trabalhar com unidades NVMe

Aplica-se a:

  • Windows 10
  • Windows Server 2016

Saiba como trabalhar com os dispositivos NVMe de alta velocidade do aplicativo Windows. O acesso ao dispositivo é habilitado por meio do StorNVMe.sys, o driver nativo apresentado pela primeira vez no Windows Server 2012 R2 e Windows 8.1. Ele também está disponível para dispositivos Windows 7 por meio de um hotfix de KB. No Windows 10, vários recursos novos foram apresentados, 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 APIs de uso geral a seguir 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 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 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_DATA_OVERRUN
    • 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 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. Na PropertyId arquivada, 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 for ProtocolType = ProtocolTypeNvme e DataType = NVMeDataTypeLogPage, os chamadores devem solicitar 512 partes de bytes de dados.
    • StorageDeviceProtocolSpecificProperty

    Use uma dessas IDs de propriedade específicas do protocolo combinadas à STORAGE_PROTOCOL_SPECIFIC_DATA para recuperar dados específicos do protocolo na estrutura STORAGE_PROTOCOL_DATA_DESCRIPTOR.

    • StorageAdapterTemperatureProperty
    • 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 de NVMe quando essa estrutura for usada no 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 obter dados do Controlador de identificação ou dados do Namespace de identificação.
    • Use NVMeDataTypeLogPage para obter páginas de log (incluindo dados INTELIGENTES/integridade).
    • Use NVMeDataTypeFeature para obter recursos da unidade NVMe.
  • STORAGE_TEMPERATURE_INFO : essa estrutura é usada para conter dados de temperatura específicos. Ela é usada na 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 Threshold é o valor acima do limite ou não (caso contrário, ele é o valor abaixo do limite).

Mecanismo de passagem

Comandos que não são definidos na especificação NVMe são os mais difíceis para o sistema operacional host manipular – o host não sabe os efeitos que os comandos podem ter no dispositivo de destino,n a infraestrutura exposta (tamanhos de namespaces/bloco) e no comportamento deles.

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 redirecionados. Esse pipe de passagem também ajudará no desenvolvimento de ferramentas de gerenciamento e de teste. No entanto, esse mecanismo de passagem exige o uso do Log de efeitos de comando. Além disso, StoreNVMe.sys exige 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ão todos os comando para um dispositivo se eles não forem descritos no Log de efeitos de comando.

 

Suporte ao Log 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 1.2 de NVMe) permite a descrição dos efeitos de comandos específicos do fornecedor junto aos comandos definidos por especificação. Isso facilita a validação do suporte de comando e a otimização do comportamento de comando, portanto, ele deve ser implementado em 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.

Em todo comando específico descrito no Log de efeitos de comando...

Quando:

  • O Comando com suporte (CSUPP) estiver definido como ''1'' significando que o comando é compatível com o controlador (Bit 01)

    Observação

    Quando CSUPP estiver definido como ''0'' (significando que não há suporte para o comando) o comando será bloqueado

     

E se um dos seguintes itens estiver definido:

  • A CCC (Alteração de capacidade do controlador) estiver definida como ''1'' significando que o comando poderá alterar as funcionalidades do controlador (Bit 04)

  • A NIC (Alteração de estoque de namespace) estiver definida como ''1'' significando que o comando poderá alterar o número ou as funcionalidades para vários namespaces (Bit 03)

  • A NCC (Alteração de funcionalidade de namespace) estiver definida como ''1'' significando que o comando poderá alterar as funcionalidades de um único namespace (Bit 02)

  • O CSE (Envio e execução de comando) estiver definido como 001b ou 010b, significando que o comando poderá ser enviado quando não houver outro comando pendente para o mesmo ou qualquer namespace e que outro comando não deverá 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.

Senão:

  • O CSE (Envio e execução de comando) estiver definido como 001b, significando que o comando poderá ser enviado quando não houver outro comando pendente para o mesmo namespace e que outro comando não deverá 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 de LUN (número de unidade lógica).

Caso contrário, o comando será enviado com outros comandos pendentes sem inibição. Por exemplo, se um comando específico do fornecedor for enviado ao dispositivo para recuperar informações estatísticas que não sã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 à E/S e nenhuma pausa-retomada seria necessária.

Usar IOCTL_STORAGE_PROTOCOL_COMMAND para enviar comandos

A passagem pode ser realizada usando o IOCTL_STORAGE_PROTOCOL_COMMAND, apresentado no Windows 10. Esse IOCTL foi criado para ter um comportamento semelhante aos IOCTLs de passagem existentes do SCSI e do ATA, para enviar um comando incorporado para o dispositivo de destino. Por meio desse IOCTL, a passagem pode ser enviada para um dispositivo de armazenamento, incluindo uma unidade NVMe.

Por exemplo, em 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)

Assim como acontece com todos os outros IOCTLs, use DeviceIoControl para enviar o IOCTL de passagem. O IOCTL é preenchido usando a estrutura STORAGE_PROTOCOL_COMMAND de buffer de entrada 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-se enviar deve ser preenchido no campo realçado acima. Observe novamente que o Log de efeitos de comando deve ser implementado para comandos de passagem. Em especial, 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 de 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 específicos de administrador ou de não fornecedor, como Identify, esse IOCTL de passagem não deve ser usado. Por exemplo, IOCTL_STORAGE_QUERY_PROPERTY deve ser usado para Identify ou Get Log Pages. Para obter mais informações, consulte a próxima seção, Consultas específicas do protocolo.

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

Os comandos de download e ativação do firmware não devem ser enviados usando passagem. IOCTL_STORAGE_PROTOCOL_COMMAND deve ser usado somente para comandos específicos do fornecedor.

Em vez disso, use os IOCTLs de armazenamento geral a seguir (apresentados no Windows 10) para evitar obter aplicativos usando diretamente a versão SCSI_miniport do IOCTL Firmware. Os drivers de armazenamento converterão o IOCTL para um comando SCSI ou a versão SCSI_miniport do IOCTL para a miniporta.

Esses IOCTLs são recomendados para desenvolver ferramentas de atualização de firmware no Windows 10 e 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 com rapidez:

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

Observação

Para atualizar o firmware em NVMe no Windows 8.1, use IOCTL_SCSI_MINIPORT_FIRMWARE. Não foi realizado backport desse IOCTL para o Windows 7. Para obter mais informações, consulte Atualização de firmware para um dispositivo NVMe no Windows 8.1.

 

Retornar erros por meio do mecanismo de passagem

Semelhante aos IOCTLs de passagem de SCSI e ATA, quando um comando/solicitação é enviado para a miniporta ou dispositivo, o IOCTL retorna se foi obteve êxito 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, protocolCommand->ReturnStatus == STORAGE_PROTOCOL_STATUS_SUCCESS é o esperado caso o comando tenha obtido êxito no dispositivo.

Consultas específicas do protocolo

O Windows 8.1 apresentou o IOCTL_STORAGE_QUERY_PROPERTY para recuperação de dados. No Windows 10, o IOCTL foi aprimorado para dar suporte aos recursos de NVMe mais solicitados, como Obter páginas de log, Obter recursos e Identificação. Isso permite a recuperação de informações específicas de NVMe para fins de monitoramento e estoque.

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 o IOCTL_STORAGE_QUERY_PROPERTY para recuperar informações específicas do protocolo NVMe na 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 uma STORAGE_PROTOCOL_SPECIFIC_DATA.

  • Defina o campo PropertyID como StorageAdapterProtocolSpecificProperty ou StorageDeviceProtocolSpecificProperty para um controlador ou solicitação de dispositivo/namespace, respectivamente.

  • Defina o campo QueryType como PropertyStandardQuery.

  • Preencha a estrutura STORAGE_PROTOCOL_SPECIFIC_DATA com os valores desejados. O início da STORAGE_PROTOCOL_SPECIFIC_DATA é o campo AdditionalParameters da STORAGE_PROPERTY_QUERY.

A estrutura da 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 Controlador de identificação ou dados do Namespace de identificação.
    • Use NVMeDataTypeLogPage para obter páginas de log (incluindo dados INTELIGENTES/integridade).
    • 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 é definida como ProtocolType=ProtocolTypeNvme e DataType=NVMeDataTypeLogPage, defina o membro ProtocolDataLength dessa mesma estrutura com um valor mínimo de 512 (bytes).

Os exemplos a seguir demonstram consultas específicas do protocolo NVMe.

Exemplo: consulta Identificação de NVMe

Neste exemplo, a solicitação de Identificação é enviada para uma unidade NVMe. O código a seguir inicializa a estrutura de dados da 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 é definida como ProtocolType=ProtocolTypeNvme e DataType=NVMeDataTypeLogPage, defina o membro ProtocolDataLength dessa mesma estrutura com um valor mínimo de 512 (bytes).

Observe que o chamador precisa alocar um buffer único 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, é recomendável usar um buffer único para consultar informações relacionadas ao NVMe.

identifyControllerData->NN é Número de namespaces (NN). O Windows detecta um namespace como uma unidade física.

Exemplo: consulta Obter páginas de log NVMe

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 da 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 StorageAdapterProtocolSpecificPropertye cuja estrutura de STORAGE_PROTOCOL_SPECIFIC_DATA ou STORAGE_PROTOCOL_SPECIFIC_DATA_EXT esteja definida como ProtocolDataRequestValue=VENDOR_SPECIFIC_LOG_PAGE_IDENTIFIERpara solicitar 512 partes de bytes de dados específicos do fornecedor.

Exemplo: consulta Obter recursos NVMe

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 da 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

A partir do Windows 10 19H1, o IOCTL_STORAGE_SET_PROPERTY foi aprimorado para dar suporte aos recursos de conjunto 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:

  • Aloque um buffer que possa conter uma estrutura STORAGE_PROPERTY_SET e uma STORAGE_PROTOCOL_SPECIFIC_DATA_EXT;
  • Defina o campo PropertyID como StorageAdapterProtocolSpecificProperty ou StorageDeviceProtocolSpecificProperty para um controlador ou solicitação de dispositivo/namespace, respectivamente.
  • Preencha a estrutura STORAGE_PROTOCOL_SPECIFIC_DATA_EXT com os valores desejados. O início da STORAGE_PROTOCOL_SPECIFIC_DATA_EXT é o campo AdditionalParameters da 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: recursos de conjunto NVMe

Neste exemplo, a solicitação Definir recursos é enviada para uma unidade NVMe. O código a seguir prepara a estrutura de dados de 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, o 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 um controlador ou solicitação de dispositivo/namespace, respectivamente.

  • Defina o campo QueryType como PropertyStandardQuery.

A estrutura de 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

Os comandos que manipulam atributos de 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 no 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 Set-Features NVMe é 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 em andamento não fiquem em risco quando os comandos do conjunto que afetam o comportamento são enviados, o Windows pausa toda a E/S para o dispositivo NVMe, esvazia filas e libera 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, poderá ser necessária a redefinição do dispositivo.

Definir limites de temperatura

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

Exemplo: definir temperatura acima do limite

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

Configurar recursos específicos do fornecedor

Sem o Log de efeitos de comando, o driver não sabe quais são as consequências do comando. Por esse motivo o Log de efeitos de comando é necessário. Ele ajuda o sistema operacional a determinar se um comando é de 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 fornecedor. Por isso, 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 Mecanismo de passagem.

Arquivos de cabeçalho

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

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 de IOCTL do Win32, incluindo APIs de armazenamento para aplicativos de modo de usuário.