Compartir a través de


Trabajar con unidades NVMe

Se aplica a:

  • Windows 10
  • Windows Server 2016

Aprenda a trabajar con dispositivos NVMe de alta velocidad desde la aplicación Windows. El acceso al dispositivo se habilita a través de StorNVMe.sys, el primer controlador incorporado que se introdujo en Windows Server 2012 R2 y Windows 8.1. También está disponible para dispositivos Windows 7 a través de una corrección activa de KB. En Windows 10 se introdujeron varias características nuevas, incluido un mecanismo de paso a través para comandos NVMe específicos del proveedor y actualizaciones de ioCTLs existentes.

En este tema se proporciona información general sobre las API de uso general que puede usar para acceder a las unidades NVMe en Windows 10. También describe:

API para trabajar con unidades NVMe

Puede usar las siguientes API de uso general para acceder a las unidades NVMe en Windows 10. Estas API se pueden encontrar en winioctl.h para las aplicaciones en modo de usuario y ntddstor.h para los controladores de modo kernel. Para obtener más información sobre los archivos de encabezado, consulte Archivos de encabezado.

  • IOCTL_STORAGE_PROTOCOL_COMMAND: use este IOCTL con la estructura STORAGE_PROTOCOL_COMMAND para emitir comandos NVMe. Este IOCTL habilita el paso a través de NVMe y admite el registro Command Effects en NVMe. Puede usarlo con comandos específicos del proveedor. Para obtener más información, consulte Mecanismo de paso a través.

  • STORAGE_PROTOCOL_COMMAND: esta estructura de búfer de entrada incluye un campo ReturnStatus que se puede usar para notificar los siguientes valores de estado.

    • 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 con la estructura STORAGE_PROPERTY_QUERY para recuperar la información del dispositivo. Para obtener más información, consulte Consultas específicas del protocolo y Consultas de temperatura.

  • STORAGE_PROPERTY_QUERY: esta estructura incluye los campos PropertyId y AdditionalParameters para especificar los datos que se van a consultar. En el campo propertyId, use la enumeración STORAGE_PROPERTY_ID para especificar el tipo de datos. Use el campo AdditionalParameters para especificar más detalles, en función del tipo de datos. Para los datos específicos del protocolo, use la estructura STORAGE_PROTOCOL_SPECIFIC_DATA en el campo AdditionalParameters. Para los datos de temperatura, use la estructura STORAGE_TEMPERATURE_INFO en el campo AdditionalParameters.

  • STORAGE_PROPERTY_ID: esta enumeración incluye nuevos valores que permiten a IOCTL_STORAGE_QUERY_PROPERTY recuperar información específica del protocolo y la temperatura.

    • StorageAdapterProtocolSpecificProperty: si ProtocolType = ProtocolTypeNvme y DataType = NVMeDataTypeLogPage, los autores de las llamadas deben solicitar fragmentos de datos de 512 bytes.
    • StorageDeviceProtocolSpecificProperty

    Use uno de estos identificadores de propiedad específicos del protocolo en combinación con STORAGE_PROTOCOL_SPECIFIC_DATA para recuperar datos específicos del protocolo en la estructura STORAGE_PROTOCOL_DATA_DESCRIPTOR.

    • StorageAdapterTemperatureProperty
    • StorageDeviceTemperatureProperty

    Use uno de estos identificadores de propiedad de temperatura para recuperar los datos de temperatura en la estructura STORAGE_TEMPERATURE_DATA_DESCRIPTOR.

  • STORAGE_PROTOCOL_SPECIFIC_DATA: recupera datos específicos de NVMe cuando se usa esta estructura para el campo AdditionalParameters de STORAGE_PROPERTY_QUERY y se especifica un valor de enumeración STORAGE_PROTOCOL_NVME_DATA_TYPE. Use uno de los siguientes valores de STORAGE_PROTOCOL_NVME_DATA_TYPE en el campo DataType de la estructura STORAGE_PROTOCOL_SPECIFIC_DATA:

    • Use NVMeDataTypeIdentify para obtener los datos del controlador de identificación o para identificar los datos del espacio de nombres.
    • Use NVMeDataTypeLogPage para obtener páginas de registro (incluidos los datos SMART/health).
    • Use NVMeDataTypeFeature para obtener características de la unidad NVMe.
  • STORAGE_TEMPERATURE_INFO: esta estructura sirve para contener datos de temperatura específicos. Se usa en STORAGE_TEMERATURE_DATA_DESCRIPTOR para devolver los resultados de una consulta de temperatura.

  • IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD: use este IOCTL con la estructura STORAGE_TEMPERATURE_THRESHOLD para establecer umbrales de temperatura. Para obtener más información, consulte Comandos de cambio de comportamiento.

  • STORAGE_TEMPERATURE_THRESHOLD: esta estructura se usa como búfer de entrada para especificar el umbral de temperatura. El campo OverThreshold (booleano) especifica si el campo Umbral es el valor por encima del umbral o no (de lo contrario, es el valor por debajo del umbral).

Mecanismo de paso a través

Los comandos que no están definidos en la especificación NVMe son los más difíciles de controlar para el sistema operativo host: el host no tiene información sobre los efectos que pueden tener los comandos en el dispositivo de destino, la infraestructura expuesta (espacios de nombres o tamaños de bloque) y su comportamiento.

Para mejorar la transferencia de estos comandos específicos del dispositivo a través de la pila de almacenamiento de Windows, un nuevo mecanismo de paso a través permite canalizar comandos específicos del proveedor. Esta canalización de paso a través también ayudará a desarrollar herramientas de administración y pruebas. Sin embargo, este mecanismo de paso a través requiere el uso del registro de efectos de comandos. Además, StoreNVMe.sys requiere que todos los comandos, no solo los comandos de paso a través, se describan en el registro Command Effects.

Importante

StorNVMe.sys y Storport.sys bloquearán cualquier comando en un dispositivo si no se describe en el registro Command Effects.

 

Compatibilidad con el registro Command Effects

El registro Command Effects (como se describe en Comandos admitidos y efectos, sección 5.10.1.5 de Especificación de NVMe 1.2) permite la descripción de los efectos de los comandos específicos del proveedor junto con comandos definidos por la especificación. Esto facilita tanto la validación de compatibilidad de comandos como la optimización del comportamiento de los comandos y, por tanto, se debe implementar para todo el conjunto de comandos que admite el dispositivo. Las condiciones siguientes describen el resultado sobre cómo se envía el comando en función de su entrada en el registro Command Effects.

Para cualquier comando específico descrito en el registro Command Effects...

Mientras:

  • El comando compatible (CSUPP) se establece en "1", lo que significa que el controlador admite el comando (bit 01)

    Nota:

    Cuando CSUPP se establece en "0" (que indica que no se admite el comando), se bloqueará el comando.

     

Y si se establece alguna de las siguientes opciones:

  • El cambio de funcionalidades del controlador (CCC) se establece en "1", que indica que el comando puede cambiar las funcionalidades del controlador (bit 04)

  • El cambio de inventario de espacios de nombres (NIC) se establece en "1", que significa que el comando puede cambiar el número o las funcionalidades de varios espacios de nombres (bit 03)

  • El cambio de funcionalidades del espacio de nombres (NCC) se establece en "1", que indica que el comando puede cambiar las funcionalidades de un único espacio de nombres (bit 02)

  • El envío y la ejecución de comandos (CSE) se establece en 001b o 010b, que significa que el comando se puede enviar cuando no hay ningún otro comando pendiente en el mismo espacio de nombres o en ningún espacio de nombres, y que no se debe enviar otro comando al mismo espacio de nombres o a ningún espacio de nombres hasta que se haya completado este comando (bits 18:16)

A continuación, el comando se enviará como el único comando pendiente para el adaptador.

O si:

  • El envío y la ejecución de comandos (CSE) se establece en 001b, que significa que el comando se puede enviar cuando no hay ningún otro comando pendiente en el mismo espacio de nombres, y que no se debe enviar otro comando al mismo espacio de nombres hasta que se haya completado este comando (bits 18:16)

A continuación, el comando se enviará como el único comando pendiente al objeto Número de unidad lógica (LUN).

De lo contrario, el comando se enviará con otros comandos pendientes sin inhibición. Por ejemplo, si se envía un comando específico del proveedor al dispositivo para recuperar información estadística que no está definida por la especificación, no debe haber riesgo de cambiar el comportamiento o la capacidad del dispositivo para ejecutar comandos de E/S. Estas solicitudes podrían atenderse en paralelo a E/S y no sería necesario reanudar la pausa.

Uso de IOCTL_STORAGE_PROTOCOL_COMMAND para enviar comandos

El paso a través se puede realizar mediante el comando IOCTL_STORAGE_PROTOCOL_COMMAND, introducido en Windows 10. Este IOCTL se diseñó para tener un comportamiento similar al de los IOCTL de paso a través de SCSI y ATA existentes, para enviar un comando incrustado al dispositivo de destino. Con este IOCTL, el paso a través se puede enviar a un dispositivo de almacenamiento, así como a una unidad NVMe.

Por ejemplo, en NVMe, el IOCTL permitirá enviar los siguientes códigos de comando.

  • Comandos de administración específicos del proveedor (C0h – FFh)
  • Comandos de NVMe específicos del proveedor (80h – FFh)

Al igual que con los demás IOCTL, use DeviceIoControl para enviar hacia abajo el IOCTL de paso a través. El IOCTL se rellena mediante la estructura de búfer de entrada STORAGE_PROTOCOL_COMMAND que se encuentra en ntddstor.h. Rellene el campo Comando con el comando específico del proveedor.

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;

El comando específico del proveedor que se desea enviar debe rellenarse en el campo resaltado anterior. Tenga en cuenta de nuevo que el registro Command Effects debe implementarse para los comandos de paso a través. En concreto, estos comandos deben notificarse como compatibles con el registro Command Effects (consulte la sección anterior para obtener más información). Tenga en cuenta también que los campos PRP son específicos del controlador, por lo que las aplicaciones que envían comandos pueden dejarlos en 0.

Por último, este IOCTL de paso a través está pensado para enviar comandos específicos del proveedor. Para enviar otros comandos NVMe específicos del administrador o que no son de proveedor, como Identify, no se debe usar este IOCTL de paso a través. Por ejemplo, se debe usar IOCTL_STORAGE_QUERY_PROPERTY para Identify o Get Log Pages. Para obtener más información, consulte la sección siguiente, Consultas específicas del protocolo.

No actualice el firmware con el mecanismo de paso a través

Los comandos de descarga y activación de firmware no deben enviarse mediante el paso a través. IOCTL_STORAGE_PROTOCOL_COMMAND solo se debe usar para comandos específicos del proveedor.

En su lugar, use los siguientes IOCTL de almacenamiento general (introducidos en Windows 10) para evitar que las aplicaciones usen directamente la versión SCSI_miniport del IOCTL de firmware. Los controladores de almacenamiento traducirán el IOCTL a un comando SCSI o a la versión SCSI_miniport del IOCTL al minipuerto.

Estos IOCTL se recomiendan para desarrollar herramientas de actualización de firmware en Windows 10 y Windows Server 2016:

Para obtener información de almacenamiento y actualización de firmware, Windows también admite cmdlets de PowerShell para hacerlo rápidamente:

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

Nota:

Para actualizar el firmware en NVMe en Windows 8.1, use IOCTL_SCSI_MINIPORT_FIRMWARE. Este IOCTL no se devolvió a Windows 7. Para obtener más información, consulte Actualización de firmware para un dispositivo NVMe en Windows 8.1.

 

Devolver errores con el mecanismo de paso a través

De forma similar a los IOCTL de paso a través de SCSI y ATA, cuando se envía un comando o solicitud al minipuerto o dispositivo, el IOCTL devuelve si se realizó correctamente o no. En la estructura STORAGE_PROTOCOL_COMMAND, el IOCTL devuelve el estado a través del campo ReturnStatus.

Ejemplo: envío de un comando específico del proveedor

En este ejemplo se envía un comando arbitrario específico del proveedor (0xFF) mediante paso a través a una unidad NVMe. El código siguiente asigna un búfer, inicializa una consulta y, a continuación, envía el comando al dispositivo mediante 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 
                             );  

En este ejemplo, se prevé protocolCommand->ReturnStatus == STORAGE_PROTOCOL_STATUS_SUCCESS si el comando se realizó correctamente en el dispositivo.

Consultas específicas del protocolo

Windows 8.1 introdujo IOCTL_STORAGE_QUERY_PROPERTY para la recuperación de datos. En Windows 10, el IOCTL se ha mejorado para admitir características de NVMe comúnmente solicitadas, como Get Log Pages, Get Features o Identify. Esto permite recuperar información específica de NVMe para fines de supervisión e inventario.

Aquí se muestra el búfer de entrada del IOCTL, STORAGE_PROPERTY_QUERY (de Windows 10).

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

Al usar IOCTL_STORAGE_QUERY_PROPERTY para recuperar información específica del protocolo NVMe en STORAGE_PROTOCOL_DATA_DESCRIPTOR, configure la estructura STORAGE_PROPERTY_QUERY del siguiente modo:

  • Asigne un búfer que pueda contener una STORAGE_PROPERTY_QUERY y una estructura STORAGE_PROTOCOL_SPECIFIC_DATA.

  • Establezca el campo PropertyID en StorageAdapterProtocolSpecificProperty o en StorageDeviceProtocolSpecificProperty para una solicitud de controlador o espacio de nombres, respectivamente.

  • Establezca el campo QueryType en PropertyStandardQuery.

  • Rellene la estructura STORAGE_PROTOCOL_SPECIFIC_DATA con los valores deseados. El inicio de STORAGE_PROTOCOL_SPECIFIC_DATA es el campo AdditionalParameters de STORAGE_PROPERTY_QUERY.

Aquí se muestra la estructura STORAGE_PROTOCOL_SPECIFIC_DATA (de Windows 10).

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 un tipo de información específica del protocolo NVMe, configure la estructura STORAGE_PROTOCOL_SPECIFIC_DATA de la siguiente manera:

  • Establezca el campo ProtocolType en ProtocolTypeNVMe.

  • Establezca el campo DataType en un valor de enumeración definido por STORAGE_PROTOCOL_NVME_DATA_TYPE:

    • Use NVMeDataTypeIdentify para obtener los datos del controlador de identificación o para identificar los datos del espacio de nombres.
    • Use NVMeDataTypeLogPage para obtener páginas de registro (incluidos los datos SMART/health).
    • Use NVMeDataTypeFeature para obtener características de la unidad NVMe.

Cuando ProtocolTypeNVMe se usa como ProtocolType, las consultas de información específica del protocolo se pueden recuperar en paralelo con otra E/S en la unidad NVMe.

Importante

Para una IOCTL_STORAGE_QUERY_PROPERTY que usa un STORAGE_PROPERTY_ID de StorageAdapterProtocolSpecificProperty, y cuya estructura STORAGE_PROTOCOL_SPECIFIC_DATA o STORAGE_PROTOCOL_SPECIFIC_DATA_EXT está establecida en ProtocolType=ProtocolTypeNvme y DataType=NVMeDataTypeLogPage, establezca el miembro ProtocolDataLength de esa misma estructura en un valor mínimo de 512 (bytes).

En los ejemplos siguientes se muestran consultas específicas del protocolo NVMe.

Ejemplo: consulta Identify de NVMe

En este ejemplo, la solicitud Identify se envía a una unidad NVMe. El siguiente código inicializa la estructura de datos de consulta y, a continuación, envía el comando al dispositivo a través 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 una IOCTL_STORAGE_QUERY_PROPERTY que usa un STORAGE_PROPERTY_ID de StorageAdapterProtocolSpecificProperty, y cuya estructura STORAGE_PROTOCOL_SPECIFIC_DATA o STORAGE_PROTOCOL_SPECIFIC_DATA_EXT está establecida en ProtocolType=ProtocolTypeNvme y DataType=NVMeDataTypeLogPage, establezca el miembro ProtocolDataLength de esa misma estructura en un valor mínimo de 512 (bytes).

Tenga en cuenta que el autor de la llamada debe asignar un único búfer que contenga STORAGE_PROPERTY_QUERY y el tamaño de STORAGE_PROTOCOL_SPECIFIC_DATA. En este ejemplo se usa el mismo búfer para la entrada y salida de la consulta de propiedades. Por este motivo, el búfer asignado tiene un tamaño de "FIELD_OFFSET(STORAGE_PROPERTY_QUERY, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE". Aunque se podrían asignar búferes independientes tanto para la entrada como para la salida, se recomienda usar un único búfer para consultar la información relacionada con NVMe.

identifyControllerData->NN es el número de espacios de nombres (NN). Windows detecta un espacio de nombres como una unidad física.

Ejemplo: consulta Get Log Pages de NVMe

En este ejemplo, que se basa en el anterior, la solicitud Get Log Pages se envía a una unidad NVMe. El siguiente código prepara la estructura de datos de consulta y, a continuación, envía el comando al dispositivo a través 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"));
    }

Los autores de llamadas podrían usar un STORAGE_PROPERTY_ID de StorageAdapterProtocolSpecificProperty, y cuya estructura STORAGE_PROTOCOL_SPECIFIC_DATA o STORAGE_PROTOCOL_SPECIFIC_DATA_EXT esté establecida en ProtocolDataRequestValue=VENDOR_SPECIFIC_LOG_PAGE_IDENTIFIER para solicitar fragmentos de 512 bytes de datos específicos del proveedor.

Ejemplo: consulta Get Features de NVMe

En este ejemplo, que se basa en el anterior, la solicitud Get Features se envía a una unidad NVMe. El siguiente código prepara la estructura de datos de consulta y, a continuación, envía el comando al dispositivo a través 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 del protocolo

A partir de Windows 10 19H1, se ha mejorado IOCTL_STORAGE_SET_PROPERTY para admitir Set Features de NVMe.

Aquí se muestra el búfer de entrada de IOCTL_STORAGE_SET_PROPERTY:

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;

Al usar IOCTL_STORAGE_SET_PROPERTY para establecer la característica NVMe, configure la estructura STORAGE_PROPERTY_SET de la siguiente manera:

  • Asigne un búfer que pueda contener un STORAGE_PROPERTY_SET y una estructura STORAGE_PROTOCOL_SPECIFIC_DATA_EXT;
  • Establezca el campo PropertyID en StorageAdapterProtocolSpecificProperty o en StorageDeviceProtocolSpecificProperty para una solicitud de controlador o espacio de nombres, respectivamente.
  • Rellene la estructura STORAGE_PROTOCOL_SPECIFIC_DATA_EXT con los valores deseados. El inicio de STORAGE_PROTOCOL_SPECIFIC_DATA_EXT es el campo AdditionalParameters de STORAGE_PROPERTY_SET.

Aquí se muestra la estructura STORAGE_PROTOCOL_SPECIFIC_DATA_EXT.

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 un tipo de característica NVMe que se va a establecer, configure la estructura STORAGE_PROTOCOL_SPECIFIC_DATA_EXT de la siguiente manera:

  • Establezca el campo ProtocolType en ProtocolTypeNvme;
  • Establezca el campo DataType en el valor de enumeración NVMeDataTypeFeature definido por STORAGE_PROTOCOL_NVME_DATA_TYPE;

En los ejemplos siguientes se muestra el conjunto de características de NVMe.

Ejemplo: Set Features de NVMe

En este ejemplo, la solicitud Set Features se envía a una unidad NVMe. El siguiente código prepara la estructura de datos del conjunto y, a continuación, envía el comando al dispositivo a través 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

En Windows 10 también se puede usar IOCTL_STORAGE_QUERY_PROPERTY para consultar los datos de temperatura de los dispositivos NVMe.

Para recuperar información de temperatura de una unidad NVMe en STORAGE_TEMPERATURE_DATA_DESCRIPTOR, configure la estructura STORAGE_PROPERTY_QUERY de la siguiente manera:

  • Asigne un búfer que pueda contener una estructura STORAGE_PROPERTY_QUERY.

  • Establezca el campo PropertyID en StorageAdapterTemperatureProperty o en StorageDeviceTemperatureProperty para una solicitud de controlador o dispositivo o espacio de nombres, respectivamente.

  • Establezca el campo QueryType en PropertyStandardQuery.

Aquí se muestra la estructura STORAGE_TEMPERATURE_INFO (de Windows 10).

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 cambio de comportamiento

Los comandos que manipulan atributos del dispositivo o que pueden afectar al comportamiento son más difíciles de gestionar para el sistema operativo. Si los atributos del dispositivo cambian en tiempo de ejecución mientras se procesa la E/S, pueden surgir problemas de sincronización o integridad de datos si no se controlan correctamente.

El comando Set-Features de NVMe es un buen ejemplo de un comando que cambia el comportamiento. Permite cambiar el mecanismo de arbitraje y la configuración de los umbrales de temperatura. Para asegurarse de que los datos en curso no estén en riesgo cuando se envíen comandos set que afectan al comportamiento, Windows pausará todas las E/S en el dispositivo NVMe, purgará las colas y vaciará los búferes. Una vez que el comando set se ha ejecutado correctamente, se reanuda la E/S (si es posible). Si no se puede reanudar la E/S, es posible que se requiera un restablecimiento del dispositivo.

Establecer umbrales de temperatura

Windows 10 introdujo IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD, un IOCTL para obtener y establecer umbrales de temperatura. También puede usarlo para obtener la temperatura actual del dispositivo. El búfer de entrada y salida de este IOCTL es la estructura STORAGE_TEMPERATURE_INFO, de la sección de código anterior.

Ejemplo: establecer una temperatura por encima del umbral

En este ejemplo se establece la temperatura por encima del umbral de una unidad NVMe. El siguiente código prepara el comando y, a continuación, lo envía al dispositivo a través de DeviceIoControl.

    BOOL    result;  
    ULONG   returnedLength = 0;  
    
    STORAGE_TEMPERATURE_THRESHOLD setThreshold = {0};  

    setThreshold.Version = sizeof(STORAGE_TEMPERATURE_THRESHOLD); 
    setThreshold.Size = sizeof(STORAGE_TEMPERATURE_THRESHOLD);  
    setThreshold.Flags = STORAGE_TEMPERATURE_THRESHOLD_FLAG_ADAPTER_REQUEST;  
    setThreshold.Index = SensorIndex;  
    setThreshold.Threshold = Threshold;  
    setThreshold.OverThreshold = UpdateOverThreshold; 

    //  
    // Send request down.  
    //  

    result = DeviceIoControl(DeviceList[DeviceIndex].Handle,  
                             IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD,  
                             &setThreshold,  
                             sizeof(STORAGE_TEMPERATURE_THRESHOLD),  
                             NULL,  
                             0,  
                             &returnedLength,  
                             NULL  
                             ); 

Establecer características específicas del proveedor

Sin el registro Command Effects, el controlador no tiene conocimiento de las ramificaciones del comando. Este es el motivo por el que se requiere el registro Command Effects. Ayuda al sistema operativo a determinar si un comando tiene un alto impacto y si se puede enviar en paralelo con otros comandos a la unidad.

El registro Command Effects aún no es lo suficientemente granular como para abarcar los comandos Set-Features específicos del proveedor. Por este motivo, aún no es posible enviar comandos Set-Features específicos del proveedor. Sin embargo, es posible usar el mecanismo de paso a través, descrito anteriormente, para enviar comandos específicos del proveedor. Para obtener más información, consulte Mecanismo de paso a través.

Archivos de encabezado

Los siguientes archivos son relevantes para el desarrollo de NVMe. Estos archivos se incluyen con el Kit de desarrollo de software (SDK) de Microsoft Windows.

Archivo de encabezado Descripción
ntddstor.h Define constantes y tipos para acceder a los controladores de clase de almacenamiento desde el modo kernel.
nvme.h Para otras estructuras de datos relacionadas con NVMe.
winioctl.h Para las definiciones generales de IOCTL de Win32, incluidas las API de almacenamiento para aplicaciones en modo de usuario.