Partilhar via


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:

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.