Uso delle unità NVMe

Si applica a:

  • Windows 10
  • Windows Server 2016

Informazioni su come usare dispositivi NVMe ad alta velocità dall'applicazione Windows. L'accesso al dispositivo è abilitato tramite StorNVMe.sys, il driver in-box introdotto per la prima volta in Windows Server 2012 R2 e Windows 8.1. È disponibile anche per i dispositivi Windows 7 tramite una correzione a caldo kb. In Windows 10 sono state introdotte diverse nuove funzionalità, tra cui un meccanismo pass-through per i comandi NVMe specifici del fornitore e gli aggiornamenti a IOCTLs esistenti.

Questo argomento offre una panoramica delle API di uso generico che è possibile usare per accedere alle unità NVMe in Windows 10. Descrive anche:

API per l'uso delle unità NVMe

Puoi usare le API di utilizzo generico seguenti per accedere alle unità NVMe in Windows 10. Queste API sono disponibili in winioctl.h per le applicazioni in modalità utente e ntddstor.h per i driver in modalità kernel. Per altre informazioni sui file di intestazione, vedere File di intestazione.

  • IOCTL_STORAGE_PROTOCOL_COMMAND: usare questo IOCTL con la struttura STORAGE_PROTOCOL_COMMAND per eseguire comandi NVMe. Questo IOCTL abilita il pass-through NVMe e supporta il log degli effetti dei comandi in NVMe. È possibile usarlo con comandi specifici del fornitore. Per altre info, vedi Meccanismo pass-through.

  • STORAGE_PROTOCOL_COMMAND : questa struttura di buffer di input include un campo ReturnStatus che può essere usato per segnalare i valori di stato seguenti.

    • 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 : usare questo IOCTL con la struttura STORAGE_PROPERTY_QUERY per recuperare le informazioni sul dispositivo. Per altre informazioni, vedere Query specifiche del protocollo e Query sulla temperatura.

  • STORAGE_PROPERTY_QUERY : questa struttura include i campi PropertyId e AdditionalParameters per specificare i dati su cui eseguire query. Nell'oggetto PropertyId archiviato utilizzare l'enumerazione STORAGE_PROPERTY_ID per specificare il tipo di dati. Usare il campo AdditionalParameters per specificare altri dettagli, a seconda del tipo di dati. Per i dati specifici del protocollo, usare la struttura STORAGE_PROTOCOL_SPECIFIC_DATA nel campo AdditionalParameters . Per i dati relativi alla temperatura, usare la struttura STORAGE_TEMPERATURE_INFO nel campo AdditionalParameters .

  • STORAGE_PROPERTY_ID : questa enumerazione include nuovi valori che consentono IOCTL_STORAGE_QUERY_PROPERTY di recuperare informazioni specifiche del protocollo e sulla temperatura.

    • Archiviazione AdapterProtocolSpecificProperty: Se ProtocolType = ProtocolTypeNvme e DataType = NVMeDataTypeLogPage, i chiamanti devono richiedere blocchi di dati da 512 byte.
    • Archiviazione DeviceProtocolSpecificProperty

    Usare uno di questi ID di proprietà specifici del protocollo in combinazione con STORAGE_PROTOCOL_SPECIFIC_DATA per recuperare dati specifici del protocollo nella struttura STORAGE_PROTOCOL_DATA_DESCRIPTOR.

    • Archiviazione AdapterTemperatureProperty
    • Archiviazione DeviceTemperatureProperty

    Usare uno di questi ID di proprietà temperature per recuperare i dati relativi alla temperatura nella struttura STORAGE_TEMPERATURE_DATA_DESCRIPTOR.

  • STORAGE_PROTOCOL_SPECIFIC_DATA : recuperare dati specifici di NVMe quando questa struttura viene usata per il campo AdditionalParameters di STORAGE_PROPERTY_QUERY e viene specificato un valore di enumerazione STORAGE_PROTOCOL_NVME_DATA_TYPE. Utilizzare uno dei valori di STORAGE_PROTOCOL_NVME_DATA_TYPE seguenti nel campo DataType della struttura STORAGE_PROTOCOL_SPECIFIC_DATA:

    • Usare NVMeDataTypeIdentify per ottenere i dati del controller di identificazione o identificare i dati dello spazio dei nomi.
    • Usare NVMeDataTypeLogPage per ottenere pagine di log (inclusi i dati smart/integrità).
    • Usare NVMeDataTypeFeature per ottenere le funzionalità dell'unità NVMe.
  • STORAGE_TEMPERATURE_INFO : questa struttura viene usata per contenere dati di temperatura specifici. Viene usato nella STORAGE_TEMERATURE_DATA_DESCRIPTOR per restituire i risultati di una query sulla temperatura.

  • IOCTL_STORAGE_edizione StandardT_TEMPERATURE_THRESHOLD : usare questo IOCTL con la struttura STORAGE_TEMPERATURE_THRESHOLD per impostare le soglie di temperatura. Per altre info, vedi Modifica del comportamento dei comandi.

  • STORAGE_TEMPERATURE_THRESHOLD : questa struttura viene usata come buffer di input per specificare la soglia di temperatura. Il campo OverThreshold (boolean) specifica se il campo Soglia è il valore di soglia superiore o meno (in caso contrario, è il valore inferiore alla soglia).

Meccanismo pass-through

I comandi non definiti nella specifica NVMe sono i più difficili da gestire per il sistema operativo host: l'host non ha informazioni dettagliate sugli effetti che i comandi possono avere nel dispositivo di destinazione, l'infrastruttura esposta (spazi dei nomi/dimensioni dei blocchi) e il relativo comportamento.

Per trasportare meglio questi comandi specifici del dispositivo tramite lo stack di archiviazione di Windows, un nuovo meccanismo pass-through consente l'invio tramite pipe di comandi specifici del fornitore. Questa pipe pass-through consente anche lo sviluppo di strumenti di gestione e test. Tuttavia, questo meccanismo pass-through richiede l'uso del log degli effetti del comando. Inoltre, StoreNVMe.sys richiede tutti i comandi, non solo i comandi pass-through, da descrivere nel log degli effetti del comando.

Importante

StorNVMe.sys e Storport.sys bloccano qualsiasi comando in un dispositivo se non è descritto nel log degli effetti del comando.

 

Supporto del log degli effetti del comando

Il log degli effetti del comando (come descritto in Comandi supportati ed effetti, sezione 5.10.1.5 della specifica NVMe 1.2) consente la descrizione degli effetti dei comandi specifici del fornitore insieme ai comandi definiti dalla specifica. Ciò facilita sia il supporto della convalida dei comandi che l'ottimizzazione del comportamento dei comandi e pertanto deve essere implementato per l'intero set di comandi supportato dal dispositivo. Le condizioni seguenti descrivono il risultato sulla modalità di invio del comando in base alla voce Log effetti comando.

Per qualsiasi comando specifico descritto nel log degli effetti del comando...

While:

  • Comando supportato (CSUPP) è impostato su '1' che indica che il comando è supportato dal controller (Bit 01)

    Nota

    Quando CSUPP è impostato su '0' (che indica che il comando non è supportato) il comando verrà bloccato

     

E se viene impostata una delle opzioni seguenti:

  • La modifica delle funzionalità del controller (CCC) è impostata su '1' che indica che il comando può modificare le funzionalità del controller (Bit 04)

  • La modifica dell'inventario dello spazio dei nomi (NIC) è impostata su '1' che indica che il comando può modificare il numero o le funzionalità per più spazi dei nomi (Bit 03)

  • La modifica della funzionalità dello spazio dei nomi (NCC) è impostata su '1' che indica che il comando può modificare le funzionalità di un singolo spazio dei nomi (Bit 02)

  • Invio ed esecuzione dei comandi (C edizione Standard) è impostato su 001b o 010b, che indica che il comando può essere inviato quando non è presente un altro comando in sospeso allo stesso o a uno spazio dei nomi e che un altro comando non deve essere inviato allo stesso o ad alcuno spazio dei nomi fino al completamento di questo comando (bit 18:16)

Il comando verrà quindi inviato come unico comando in sospeso all'adapter.

Else if:

  • Invio ed esecuzione dei comandi (C edizione Standard) è impostato su 001b, che indica che il comando può essere inviato quando non è presente un altro comando in sospeso allo stesso spazio dei nomi e che un altro comando non deve essere inviato allo stesso spazio dei nomi finché questo comando non viene completato (bit 18:16)

Il comando verrà quindi inviato come unico comando in sospeso all'oggetto Numero unità logica (LUN).

In caso contrario, il comando viene inviato con altri comandi in sospeso senza inibire. Ad esempio, se un comando specifico del fornitore viene inviato al dispositivo per recuperare informazioni statistiche non definite da specifiche, non dovrebbe esserci alcun rischio di modificare il comportamento o la funzionalità del dispositivo per eseguire i comandi di I/O. Tali richieste potrebbero essere gestite in parallelo all'I/O e non sarebbe necessario sospendere la ripresa.

Uso di IOCTL_STORAGE_PROTOCOL_COMMAND per inviare comandi

Il pass-through può essere eseguito usando il IOCTL_STORAGE_PROTOCOL_COMMAND, introdotto in Windows 10. Questo IOCTL è stato progettato per avere un comportamento simile a quello degli IOCTL SCSI e ATA esistenti, per inviare un comando incorporato al dispositivo di destinazione. Tramite questo IOCTL, il pass-through può essere inviato a un dispositivo di archiviazione, inclusa un'unità NVMe.

Ad esempio, in NVMe, IOCTL consentirà l'invio dei codici di comando seguenti.

  • Comandi Amministrazione specifici del fornitore (C0h - FFh)
  • Comandi NVMe specifici del fornitore (80h - FFh)

Come per tutti gli altri IOCTLs, usare DeviceIoControl per inviare il valore IOCTL pass-through. L'IOCTL viene popolato usando la struttura del buffer di input STORAGE_PROTOCOL_COMMAND presente in ntddstor.h. Popolare il campo Comando con il comando specifico del fornitore.

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;

Il comando specifico del fornitore da inviare deve essere popolato nel campo evidenziato sopra. Si noti di nuovo che il log degli effetti del comando deve essere implementato per i comandi pass-through. In particolare, questi comandi devono essere segnalati come supportati nel log degli effetti del comando (vedere la sezione precedente per altre informazioni). Si noti anche che i campi PRP sono specifici del driver, pertanto le applicazioni che inviano comandi possono lasciarli come 0.

Infine, questo IOCTL pass-through è destinato all'invio di comandi specifici del fornitore. Per inviare altri comandi NVMe specifici dell'amministratore o non del fornitore, ad esempio Identificazione, non è consigliabile usare questo IOCTL pass-through. Ad esempio, IOCTL_STORAGE_QUERY_PROPERTY deve essere usato per identificare o ottenere pagine di log. Per altre informazioni, vedere la sezione successiva Query specifiche del protocollo.

Non aggiornare il firmware tramite il meccanismo pass-through

I comandi di download e attivazione del firmware non devono essere inviati tramite pass-through. IOCTL_STORAGE_PROTOCOL_COMMAND devono essere usati solo per i comandi specifici del fornitore.

Usare invece i IOCTL di archiviazione generali seguenti (introdotti in Windows 10) per evitare che le applicazioni usino direttamente la versione SCSI_miniport di Firmware IOCTL. Archiviazione driver convertono IOCTL in un comando SCSI o nella versione SCSI_miniport di IOCTL nel miniport.

Questi IOCTLs sono consigliati per lo sviluppo di strumenti di aggiornamento del firmware in Windows 10 e Windows Server 2016:

Per ottenere informazioni di archiviazione e aggiornare il firmware, Windows supporta anche i cmdlet di PowerShell per eseguire questa operazione rapidamente:

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

Nota

Per aggiornare il firmware in NVMe in Windows 8.1, usare IOCTL_SCSI_MINIPORT_FIRMWARE. Questo IOCTL non è stato sottoposto a backporting in Windows 7. Per altre informazioni, vedere Aggiornamento del firmware per un dispositivo NVMe in Windows 8.1.

 

Restituzione di errori tramite il meccanismo pass-through

Analogamente a IOCTL SCSI e ATA, quando un comando o una richiesta viene inviata al miniport o al dispositivo, L'IOCTL restituisce se ha avuto esito positivo o negativo. Nella struttura STORAGE_PROTOCOL_COMMAND, IOCTL restituisce lo stato tramite il campo ReturnStatus.

Esempio: invio di un comando specifico del fornitore

In questo esempio, un comando arbitrario specifico del fornitore (0xFF) viene inviato tramite pass-through a un'unità NVMe. Il codice seguente alloca un buffer, inizializza una query e quindi invia il comando al dispositivo tramite 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 
                             );  

In questo esempio, ci aspettiamo protocolCommand->ReturnStatus == STORAGE_PROTOCOL_STATUS_SUCCESS se il comando è riuscito al dispositivo.

Query specifiche del protocollo

Windows 8.1 ha introdotto IOCTL_STORAGE_QUERY_PROPERTY per il recupero dei dati. In Windows 10, L'IOCTL è stato migliorato per supportare le funzionalità NVMe richieste comunemente, ad esempio Get Log Pages, Get Features e Identify. Ciò consente il recupero di informazioni specifiche NVMe a scopo di monitoraggio e inventario.

Il buffer di input per IOCTL, STORAGE_PROPERTY_QUERY (da Windows 10) è illustrato qui.

typedef struct _STORAGE_PROPERTY_QUERY {
    STORAGE_PROPERTY_ID PropertyId;
    STORAGE_QUERY_TYPE QueryType;
    UCHAR  AdditionalParameters[1];
} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;

Quando si usa IOCTL_STORAGE_QUERY_PROPERTY per recuperare informazioni specifiche del protocollo NVMe nel STORAGE_PROTOCOL_DATA_DESCRIPTOR, configurare la struttura STORAGE_PROPERTY_QUERY come indicato di seguito:

La struttura STORAGE_PROTOCOL_SPECIFIC_DATA (da Windows 10) è illustrata qui.

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;

Per specificare un tipo di informazioni specifiche del protocollo NVMe, configurare la struttura STORAGE_PROTOCOL_SPECIFIC_DATA come indicato di seguito:

  • Impostare il campo ProtocolType su ProtocolTypeNVMe.

  • Impostare il campo DataType su un valore di enumerazione definito da STORAGE_PROTOCOL_NVME_DATA_TYPE:

    • Usare NVMeDataTypeIdentify per ottenere i dati del controller di identificazione o identificare i dati dello spazio dei nomi.
    • Usare NVMeDataTypeLogPage per ottenere pagine di log (inclusi i dati smart/integrità).
    • Usare NVMeDataTypeFeature per ottenere le funzionalità dell'unità NVMe.

Quando ProtocolTypeNVMe viene usato come ProtocolType, le query per informazioni specifiche del protocollo possono essere recuperate in parallelo con altre operazioni di I/O nell'unità NVMe.

Importante

Per un IOCTL_STORAGE_QUERY_PROPERTY che usa un STORAGE_PROPERTY_ID di Archiviazione AdapterProtocolSpecificProperty e la cui struttura STORAGE_PROTOCOL_SPECIFIC_DATA o STORAGE_PROTOCOL_SPECIFIC_DATA_EXT è impostata su ProtocolType=ProtocolTypeNvme e DataType=NVMeDataTypeLogPage, impostare il membro ProtocolDataLength della stessa struttura su un valore minimo di 512 (byte).

Gli esempi seguenti illustrano query specifiche del protocollo NVMe.

Esempio: query NVMe Identify

In questo esempio la richiesta Di identificazione viene inviata a un'unità NVMe. Il codice seguente inizializza la struttura dei dati di query e quindi invia il comando al dispositivo tramite 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

Per un IOCTL_STORAGE_QUERY_PROPERTY che usa un STORAGE_PROPERTY_ID di Archiviazione AdapterProtocolSpecificProperty e la cui struttura STORAGE_PROTOCOL_SPECIFIC_DATA o STORAGE_PROTOCOL_SPECIFIC_DATA_EXT è impostata su ProtocolType=ProtocolTypeNvme e DataType=NVMeDataTypeLogPage, impostare il membro ProtocolDataLength della stessa struttura su un valore minimo di 512 (byte).

Si noti che il chiamante deve allocare un singolo buffer contenente STORAGE_PROPERTY_QUERY e le dimensioni di STORAGE_PROTOCOL_SPECIFIC_DATA. In questo esempio viene usato lo stesso buffer per l'input e l'output della query della proprietà. Ecco perché il buffer allocato ha una dimensione "FIELD_OFFedizione Standard T(STORAGE_PROPERTY_QUERY, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE". Anche se è possibile allocare buffer separati sia per l'input che per l'output, è consigliabile usare un singolo buffer per eseguire query sulle informazioni correlate a NVMe.

identifyControllerData-NN> è Number of Namespaces (NN). Windows rileva uno spazio dei nomi come unità fisica.

Esempio: query NVMe Get Log Pages

In questo esempio, in base a quello precedente, la richiesta Get Log Pages viene inviata a un'unità NVMe. Il codice seguente prepara la struttura dei dati di query e quindi invia il comando al dispositivo tramite 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"));
    }

I chiamanti possono usare un STORAGE_PROPERTY_ID di Archiviazione AdapterProtocolSpecificProperty e la cui struttura STORAGE_PROTOCOL_SPECIFIC_DATA o STORAGE_PROTOCOL_SPECIFIC_DATA_EXT è impostata su per ProtocolDataRequestValue=VENDOR_SPECIFIC_LOG_PAGE_IDENTIFIER richiedere blocchi di byte di 512 byte di dati specifici del fornitore.

Esempio: query NVMe Get Features

In questo esempio, in base a quello precedente, la richiesta Get Features viene inviata a un'unità NVMe. Il codice seguente prepara la struttura dei dati di query e quindi invia il comando al dispositivo tramite 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"));
    }

Set specifico del protocollo

Da Windows 10 19H1, il IOCTL_STORAGE_edizione StandardT_PROPERTY è stato migliorato per supportare le funzionalità del set NVMe.

Il buffer di input per il IOCTL_STORAGE_edizione StandardT_PROPERTY è illustrato di seguito:

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;

Quando si usa IOCTL_STORAGE_edizione StandardT_PROPERTY per impostare la funzionalità NVMe, configurare la struttura STORAGE_PROPERTY_edizione Standard T come indicato di seguito:

  • Allocare un buffer che può contenere sia un STORAGE_PROPERTY_edizione Standard T che una struttura STORAGE_PROTOCOL_SPECIFIC_DATA_EXT;
  • Impostare il campo PropertyID su Archiviazione AdapterProtocolSpecificProperty o Archiviazione DeviceProtocolSpecificProperty rispettivamente per una richiesta controller o dispositivo/spazio dei nomi.
  • Compilare la struttura STORAGE_PROTOCOL_SPECIFIC_DATA_EXT con i valori desiderati. L'inizio della STORAGE_PROTOCOL_SPECIFIC_DATA_EXT è il campo AdditionalParameters di STORAGE_PROPERTY_edizione Standard T.

La struttura STORAGE_PROTOCOL_SPECIFIC_DATA_EXT è illustrata qui.

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;

Per specificare un tipo di funzionalità NVMe da impostare, configurare la struttura STORAGE_PROTOCOL_SPECIFIC_DATA_EXT come indicato di seguito:

  • Impostare il campo ProtocolType su ProtocolTypeNvme;
  • Impostare il campo DataType sul valore di enumerazione NVMeDataTypeFeature definito da STORAGE_PROTOCOL_NVME_DATA_TYPE;

Gli esempi seguenti illustrano il set di funzionalità NVMe.

Esempio: Funzionalità del set NVMe

In questo esempio la richiesta Imposta funzionalità viene inviata a un'unità NVMe. Il codice seguente prepara la struttura dei dati del set e quindi invia il comando al dispositivo tramite 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
            );

Query sulla temperatura

In Windows 10 è anche possibile usare IOCTL_STORAGE_QUERY_PROPERTY per eseguire query sui dati relativi alla temperatura dai dispositivi NVMe.

Per recuperare informazioni sulla temperatura da un'unità NVMe nella STORAGE_TEMPERATURE_DATA_DESCRIPTOR, configurare la struttura STORAGE_PROPERTY_QUERY come indicato di seguito:

  • Allocare un buffer che può contenere una struttura STORAGE_PROPERTY_QUERY.

  • Impostare il campo PropertyID su Archiviazione AdapterTemperatureProperty o Archiviazione DeviceTemperatureProperty rispettivamente per una richiesta controller o dispositivo/spazio dei nomi.

  • Impostare il campo QueryType su PropertyStandardQuery.

La struttura STORAGE_TEMPERATURE_INFO (da Windows 10) è illustrata qui.

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;

Comandi di modifica del comportamento

I comandi che modificano gli attributi del dispositivo o influiscono potenzialmente sul comportamento del dispositivo sono più difficili da gestire per il sistema operativo. Se gli attributi del dispositivo cambiano in fase di esecuzione durante l'elaborazione dell'I/O, possono verificarsi problemi di sincronizzazione o integrità dei dati se non gestiti correttamente.

Il comando NVMe Set-Features è un buon esempio di comando di modifica del comportamento. Consente la modifica del meccanismo di arbitrato e l'impostazione delle soglie di temperatura. Per assicurarsi che i dati in anteprima non siano a rischio quando i comandi dei set che influiscono sul comportamento vengono inviati inattivi, Windows sospende tutte le operazioni di I/O sul dispositivo NVMe, svuota le code e scarica i buffer. Dopo che il comando set è stato eseguito correttamente, l'I/O viene ripreso (se possibile). Se non è possibile riprendere l'I/O, potrebbe essere necessaria una reimpostazione del dispositivo.

Impostazione delle soglie di temperatura

Windows 10 ha introdotto IOCTL_STORAGE_edizione StandardT_TEMPERATURE_THRESHOLD, un IOCTL per ottenere e impostare soglie di temperatura. È anche possibile usarlo per ottenere la temperatura corrente del dispositivo. Il buffer di input/output per questo IOCTL è la struttura STORAGE_TEMPERATURE_INFO , della sezione del codice precedente.

Esempio: Impostazione della temperatura di soglia eccessiva

In questo esempio viene impostata una temperatura superiore alla soglia di un'unità NVMe. Il codice seguente prepara il comando e quindi lo invia al dispositivo tramite 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  
                             ); 

Impostazione delle funzionalità specifiche del fornitore

Senza il log degli effetti del comando, il driver non ha alcuna conoscenza delle ramificazioni del comando. Questo è il motivo per cui è necessario il log degli effetti dei comandi. Aiuta il sistema operativo a determinare se un comando ha un impatto elevato e se può essere inviato in parallelo con altri comandi all'unità.

Il log degli effetti del comando non è ancora abbastanza granulare per includere i comandi set-features specifici del fornitore. Per questo motivo, non è ancora possibile inviare comandi Set-Features specifici del fornitore. Tuttavia, è possibile usare il meccanismo pass-through, descritto in precedenza, per inviare comandi specifici del fornitore. Per altre info, vedi Meccanismo pass-through.

File di intestazione

I file seguenti sono rilevanti per lo sviluppo NVMe. Questi file sono inclusi in Microsoft Windows Software Development Kit (SDK).

File di intestazione Descrizione
ntddstor.h Definisce costanti e tipi per l'accesso ai driver della classe di archiviazione dalla modalità kernel.
nvme.h Per altre strutture di dati correlate a NVMe.
winioctl.h Per le definizioni IOCTL Win32 complessive, incluse le API di archiviazione per le applicazioni in modalità utente.