Actualización del firmware de un dispositivo NVMe
Las actualizaciones del firmware en un dispositivo de almacenamiento NVMe se emiten al controlador de miniport para ese dispositivo. Los comandos de función para obtener información de firmware, descargar y activar imágenes de firmware se emiten al miniporte.
Proceso de actualización de firmware
Los dispositivos NVMe certificados para Windows son capaces de actualizar su firmware mientras el dispositivo está en funcionamiento. El firmware se actualiza mediante la solicitud de IOCTL_SCSI_MINIPORT que contiene los datos de control de firmware asociados con formato en un SRB. El proceso de actualización implica:
Recopile la información de ranura de firmware para determinar dónde colocar la actualización. Hay algunas consideraciones para decidir dónde colocar la actualización de firmware, como:
- ¿Cuántas ranuras están disponibles?
- ¿Cuántas ranuras pueden contener una actualización? Algunas ranuras son de solo lectura o contienen imágenes que deben conservarse si se desea volver a una imagen anterior.
- ¿Qué ranura contiene la imagen de firmware activa actual (el firmware en ejecución)?
Para actualizar el dispositivo, se elige una ranura que se puede escribir y no está activa actualmente. Todos los datos de imagen existentes en la ranura seleccionada se sobrescriben cuando se completa la actualización.
Descargue la nueva imagen de firmware para una ranura seleccionada. Dependiendo del tamaño de la imagen, la descarga se produce en una sola operación de transferencia o en transferencias sucesivas de varias partes de la imagen. Una parte de una imagen está limitada por min(Tamaño máximo de transferencia del controlador, 512 KB).
Para hacer que la imagen descargada sea la imagen de firmware activa, su número de ranura se asigna para que sea la ranura en la que se descargó. A continuación, la ranura de firmware activa se cambia de la ranura usada actualmente a la ranura asignada a la imagen descargada. Dependiendo del tipo de descarga y de los cambios en la imagen de firmware, podría ser necesario reiniciar el sistema. El controlador NVMe determina si se necesita un reinicio.
Solicitudes de control de firmware de miniport
Cada comando de función se establece en una estructura de FIRMWARE_REQUEST_BLOCK que se incluye con un SRB_IO_CONTROL en el búfer de una solicitud de IOCTL_SCSI_MINIPORT. El miembro ControlCode de SRB_IO_CONTROL se establece en IOCTL_SCSI_MINIPORT_FIRMWARE para indicar una operación de firmware de miniport. Cada comando de función tiene una estructura de información relacionada ubicada después del FIRMWARE_REQUEST_BLOCK. En la tabla siguiente se enumeran cada comando de función y las estructuras incluidas en el búfer del sistema para IOCTL_SCSI_MINIPORT.
Función | Datos de entrada | Datos de salida |
---|---|---|
FIRMWARE_FUNCTION_GET_INFO | SRB_IO_CONTROL + FIRMWARE_REQUEST_BLOCK | SRB_IO_CONTROL + FIRMWARE_REQUEST_BLOCK + STORAGE_FIRMWARE_SLOT_INFO |
FIRMWARE_FUNCTION_DOWNLOAD | SRB_IO_CONTROL + FIRMWARE_REQUEST_BLOCK + STORAGE_FIRMWARE_DOWNLOAD | SRB_IO_CONTROL |
FIRMWARE_FUNCTION_ACTIVATE | SRB_IO_CONTROL + FIRMWARE_REQUEST_BLOCK + STORAGE_FIRMWARE_ACTIVATE | SRB_IO_CONTROL |
Las funciones de firmware y las estructuras asociadas se definen en ntddscsi.h.
Información de ranura de firmware
Las imágenes de firmware se mantienen en el dispositivo en ubicaciones denominadas ranuras. Es necesario encontrar una ranura disponible para que la imagen de firmware resida cuando la imagen de firmware se activa después de una descarga. Para buscar una ranura disponible, una utilidad de actualización puede enviar una consulta de información al dispositivo para recibir los descriptores de información de ranura. La siguiente función de ejemplo muestra cómo recuperar la información de todas las ranuras de firmware en un dispositivo NVMe seleccionado.
// A device list item structure for an adapter
typedef struct _DEVICE_LIST {
HANDLE Handle;
STORAGE_ADAPTER_DESCRIPTOR AdapterDescriptor;
} DEVICE_LIST, *PDEVICE_LIST;
BOOL
DeviceGetFirmwareInfo(
_In_ PDEVICE_LIST DeviceList,
_In_ DWORD Index,
_Inout_ PUCHAR Buffer,
_In_ DWORD BufferLength,
_In_ BOOLEAN DisplayResult
)
/*++
Routine Description:
Retrieve the firmware and firmware slot information from NVMe controller.
Arguments:
DeviceList – a pointer to device array that contains disks information.
Index – the index of NVMe device in DeviceList array.
Buffer – a buffer for input and output.
BufferLength – the size of the buffer.
DisplayResult – print information on screen or not.
Return Value:
BOOLEAN
--*/
{
BOOL result;
ULONG returnedLength;
ULONG firmwareInfoOffset;
PSRB_IO_CONTROL srbControl;
PFIRMWARE_REQUEST_BLOCK firmwareRequest;
PSTORAGE_FIRMWARE_INFO firmwareInfo;
srbControl = (PSRB_IO_CONTROL)Buffer;
firmwareRequest = (PFIRMWARE_REQUEST_BLOCK)(srbControl + 1);
//
// The STORAGE_FIRMWARE_INFO is located after SRB_IO_CONTROL and FIRMWARE_REQUEST_BLOCK
//
firmwareInfoOffset = ((sizeof(SRB_IO_CONTROL) + sizeof(FIRMWARE_REQUEST_BLOCK) - 1) / sizeof(PVOID) + 1) * sizeof(PVOID);
//
// Setup the SRB control with the firmware ioctl control info
//
srbControl->HeaderLength = sizeof(SRB_IO_CONTROL);
srbControl->ControlCode = IOCTL_SCSI_MINIPORT_FIRMWARE;
RtlMoveMemory(srbControl->Signature, IOCTL_MINIPORT_SIGNATURE_FIRMWARE, 8);
srbControl->Timeout = 30;
srbControl->Length = BufferLength - sizeof(SRB_IO_CONTROL);
//
// Set firmware request fields for FIRMWARE_FUNCTION_GET_INFO. This request is to the controller so
// FIRMWARE_REQUEST_FLAG_CONTROLLER is set in the flags
//
firmwareRequest->Version = FIRMWARE_REQUEST_BLOCK_STRUCTURE_VERSION;
firmwareRequest->Size = sizeof(FIRMWARE_REQUEST_BLOCK);
firmwareRequest->Function = FIRMWARE_FUNCTION_GET_INFO;
firmwareRequest->Flags = FIRMWARE_REQUEST_FLAG_CONTROLLER;
firmwareRequest->DataBufferOffset = firmwareInfoOffset;
firmwareRequest->DataBufferLength = BufferLength - firmwareInfoOffset;
//
// Send the request to get the device firmware info
//
result = DeviceIoControl(DeviceList[Index].Handle,
IOCTL_SCSI_MINIPORT,
Buffer,
BufferLength,
Buffer,
BufferLength,
&returnedLength,
NULL
);
//
// Format and display the firmware info
//
if (DisplayResult) {
if (!result) {
_tprintf(_T("\t Get Firmware Information Failed: 0x%X\n"), GetLastError());
} else {
UCHAR i;
TCHAR revision[16] = {0};
firmwareInfo = (PSTORAGE_FIRMWARE_INFO)((PUCHAR)srbControl + firmwareRequest->DataBufferOffset);
_tprintf(_T("\t ----Firmware Information----\n"));
_tprintf(_T("\t Support upgrade command: %s\n"), firmwareInfo->UpgradeSupport ? _T("Yes") : _T("No"));
_tprintf(_T("\t Slot Count: %d\n"), firmwareInfo->SlotCount);
_tprintf(_T("\t Current Active Slot: %d\n"), firmwareInfo->ActiveSlot);
if (firmwareInfo->PendingActivateSlot == STORAGE_FIRMWARE_INFO_INVALID_SLOT) {
_tprintf(_T("\t Pending Active Slot: %s\n\n"), _T("No"));
} else {
_tprintf(_T("\t Pending Active Slot: %d\n\n"), firmwareInfo->PendingActivateSlot);
}
for (i = 0; i < firmwareInfo->SlotCount; i++) {
RtlCopyMemory(revision, &firmwareInfo->Slot[i].Revision.AsUlonglong, 8);
_tprintf(_T("\t\t Slot Number: %d\n"), firmwareInfo->Slot[i].SlotNumber);
_tprintf(_T("\t\t Slot Read Only: %s\n"), firmwareInfo->Slot[i].ReadOnly ? _T("Yes") : _T("No"));
_tprintf(_T("\t\t Revision: %s\n"), revision);
_tprintf(_T("\n"));
}
}
_tprintf(_T("\n"));
}
return result;
}
La información de ranura se devuelve en una matriz de estructuras de STORAGE_FIRMWARE_SLOT_INFO . Cada estructura indica el estado de activación y la disponibilidad de la ranura de firmware. Las condiciones de disponibilidad son:
- El miembro ReadOnly se establece en 0.
- La ranura no es la ranura activa indicada por el número de ranura en el miembro ActiveSlot de STORAGE_FIRMWARE_INFO.
- El miembro PendingActiveSlot de STORAGE_FIRMWARE_INFO se establece en STORAGE_FIRMWARE_INFO_INVALID_SLOT.
- El miembro PendingActiveSlot de STORAGE_FIRMWARE_INFO no está establecido en la ranura deseada.
Además, si el estado de la ranura cumple las condiciones de disponibilidad, pero la cadena de información contiene datos de revisión válidos que son bytes distintos de cero, la ranura contiene una imagen de firmware válida, pero se puede reemplazar. Todos los ceros de la cadena de información indican una ranura vacía.
Ejemplo: actualización de firmware: selección de ranuras, descarga y activación
Una utilidad de actualización realiza los tres pasos mencionados anteriormente para actualizar el firmware en el controlador. Por ejemplo, la siguiente rutina de actualización contiene código para cada paso del proceso. El paso de detección de ranuras, que se muestra en el ejemplo DeviceGetFirmwareInfo , lo llama la rutina de actualización para seleccionar una ranura disponible. Los pasos de descarga y activación de la imagen se muestran directamente después de la selección de ranuras. Dentro de cada paso, se muestra el uso del comando de función correspondiente.
Durante el paso de descarga, se lee un archivo de imagen de firmware en un búfer asignado y el contenido del búfer se transfiere al controlador. Si el archivo de imagen de firmware es mayor que el tamaño del búfer, el archivo de imagen se lee varias veces con una parte de la imagen transferida cada vez hasta que se lee todo el archivo.
Después de completar la descarga de la imagen de firmware, el paso de activación requiere dos acciones desde el controlador. En primer lugar, la ranura seleccionada se asigna a la imagen de firmware y, en segundo lugar, la ranura seleccionada se establece como ranura activa.
VOID
DeviceFirmwareUpgrade(
_In_ PDEVICE_LIST DeviceList,
_In_ DWORD Index,
_In_ TCHAR* FileName
)
/*++
Routine Description:
Performs a firmware upgrade to the NVMe controller. The an available firmware
slot is selected, the firmware is downloaded to the controller from an image
file, and the new firmware is activated.
Arguments:
DeviceList – a pointer to device array that contains disks information.
Index – the index of NVMe device in DeviceList array.
FileName – the name of the firmware upgrade image file.
Return Value:
None
--*/
{
BOOL result;
PUCHAR buffer = NULL;
ULONG bufferSize;
ULONG firmwareStructureOffset;
ULONG imageBufferLength;
PSRB_IO_CONTROL srbControl;
PFIRMWARE_REQUEST_BLOCK firmwareRequest;
PSTORAGE_FIRMWARE_INFO firmwareInfo;
PSTORAGE_FIRMWARE_DOWNLOAD firmwareDownload;
PSTORAGE_FIRMWARE_ACTIVATE firmwareActivate;
ULONG slotNumber;
ULONG returnedLength;
ULONG i;
HANDLE fileHandle = NULL;
ULONG imageOffset;
ULONG readLength;
BOOLEAN moreToDownload;
//
// The STORAGE_FIRMWARE_INFO is located after SRB_IO_CONTROL and FIRMWARE_RESQUEST_BLOCK
//
firmwareStructureOffset = ((sizeof(SRB_IO_CONTROL) + sizeof(FIRMWARE_REQUEST_BLOCK) - 1) / sizeof(PVOID) + 1) * sizeof(PVOID);
//
// The Max Transfer Length limits the part of buffer that may need to transfer to controller, not the whole buffer.
//
bufferSize = min(DeviceList[Index].AdapterDescriptor.MaximumTransferLength, 2 * 1024 * 1024);
bufferSize += firmwareStructureOffset;
bufferSize += FIELD_OFFSET(STORAGE_FIRMWARE_DOWNLOAD, ImageBuffer);
buffer = (PUCHAR)malloc(bufferSize);
if (buffer == NULL) {
_tprintf(_T("\t FirmwareUpgrade - Allocate buffer failed: 0x%X\n"), GetLastError());
return;
}
//
// calculate the space available for the firmware image portion of the buffer allocation
//
imageBufferLength = bufferSize - firmwareStructureOffset - sizeof(STORAGE_FIRMWARE_DOWNLOAD);
RtlZeroMemory(buffer, bufferSize);
// ---------------------------------------------------------------------------
// ( 1 ) SELECT A SUITABLE FIRMWARE SLOT
// ---------------------------------------------------------------------------
//
// Get firmware slot information data.
//
result = DeviceGetFirmwareInfo(DeviceList, Index, buffer, bufferSize, FALSE);
if (result == FALSE) {
_tprintf(_T("\t FirmwareUpgrade: Get Firmware Information Failed: 0x%X\n"), GetLastError());
goto Exit;
}
//
// Set the request structure pointers
//
srbControl = (PSRB_IO_CONTROL)buffer;
firmwareRequest = (PFIRMWARE_REQUEST_BLOCK)(srbControl + 1);
firmwareInfo = (PSTORAGE_FIRMWARE_INFO)((PUCHAR)srbControl + firmwareRequest->DataBufferOffset);
if (srbControl->ReturnCode != FIRMWARE_STATUS_SUCCESS) {
_tprintf(_T("\t FirmwareUpgrade - get firmware info failed. srbControl->ReturnCode %d.\n"), srbControl->ReturnCode);
goto Exit;
}
//
// SelectFind the first writable slot.
//
slotNumber = (ULONG)-1;
if (firmwareInfo->UpgradeSupport) {
for (i = 0; i < firmwareInfo->SlotCount; i++) {
if (firmwareInfo->Slot[i].ReadOnly == FALSE) {
slotNumber = firmwareInfo->Slot[i].SlotNumber;
break;
}
}
}
//
// If no writable slot is found, bypass downloading and activation
//
if (slotNumber == (ULONG)-1) {
_tprintf(_T("\t FirmwareUpgrade - No writable Firmware slot.\n"));
goto Exit;
}
// ---------------------------------------------------------------------------
// ( 2 ) DOWNLOAD THE FIRMWARE IMAGE TO THE CONTROLLER
// ---------------------------------------------------------------------------
//
// initialize image length and offset
//
imageBufferLength = (imageBufferLength / sizeof(PVOID)) * sizeof(PVOID);
imageOffset = 0;
readLength = 0;
moreToDownload = TRUE;
//
// Open image file and download it to controller.
//
if (FileName == NULL) {
_tprintf(_T("\t FirmwareUpgrade - No firmware file specified.\n"));
goto Exit;
}
fileHandle = CreateFile(FileName, // file to open
GENERIC_READ, // open for reading
FILE_SHARE_READ, // share for reading
NULL, // default security
OPEN_EXISTING, // existing file only
FILE_ATTRIBUTE_NORMAL, // normal file
NULL); // no attr. template
if (fileHandle == INVALID_HANDLE_VALUE) {
_tprintf(_T("\t FirmwareUpgrade - unable to open file \"%s\" for read.\n"), FileName);
goto Exit;
}
//
// Read and download the firmware from the image file into image buffer length portions. Send the
// image portion to the controller.
//
while (moreToDownload) {
RtlZeroMemory(buffer, bufferSize);
//
// Setup the SRB control with the firmware ioctl control info
//
srbControl->HeaderLength = sizeof(SRB_IO_CONTROL);
srbControl->ControlCode = IOCTL_SCSI_MINIPORT_FIRMWARE;
RtlMoveMemory(srbControl->Signature, IOCTL_MINIPORT_SIGNATURE_FIRMWARE, 8);
srbControl->Timeout = 30;
srbControl->Length = bufferSize - sizeof(SRB_IO_CONTROL);
//
// Set firmware request fields for FIRMWARE_FUNCTION_DOWNLOAD. This request is to the controller so
// FIRMWARE_REQUEST_FLAG_CONTROLLER is set in the flags
//
firmwareRequest->Version = FIRMWARE_REQUEST_BLOCK_STRUCTURE_VERSION;
firmwareRequest->Size = sizeof(FIRMWARE_REQUEST_BLOCK);
firmwareRequest->Function = FIRMWARE_FUNCTION_DOWNLOAD;
firmwareRequest->Flags = FIRMWARE_REQUEST_FLAG_CONTROLLER;
firmwareRequest->DataBufferOffset = firmwareStructureOffset;
firmwareRequest->DataBufferLength = bufferSize - firmwareStructureOffset;
//
// Initialize the firmware data buffer pointer to the proper position after the request structure
//
firmwareDownload = (PSTORAGE_FIRMWARE_DOWNLOAD)((PUCHAR)srbControl + firmwareRequest->DataBufferOffset);
if (ReadFile(fileHandle, firmwareDownload->ImageBuffer, imageBufferLength, &readLength, NULL) == FALSE) {
_tprintf(_T("\t FirmwareUpgrade - Read firmware file failed.\n"));
goto Exit;
}
if (readLength == 0) {
moreToDownload = FALSE;
break;
}
if ((readLength % sizeof(ULONG)) != 0) {
_tprintf(_T("\t FirmwareUpgrade - Read firmware file failed.\n"));
}
//
// Set the download parameters and adjust the offset for this portion of the firmware image
//
firmwareDownload->Version = 1;
firmwareDownload->Size = sizeof(STORAGE_FIRMWARE_DOWNLOAD);
firmwareDownload->Offset = imageOffset;
firmwareDownload->BufferSize = readLength;
//
// download this portion of firmware to the device
//
result = DeviceIoControl(DeviceList[Index].Handle,
IOCTL_SCSI_MINIPORT,
buffer,
bufferSize,
buffer,
bufferSize,
&returnedLength,
NULL
);
if (result == FALSE) {
_tprintf(_T("\t FirmwareUpgrade - IOCTL - firmware download failed. 0x%X.\n"), GetLastError());
goto Exit;
}
if (srbControl->ReturnCode != FIRMWARE_STATUS_SUCCESS) {
_tprintf(_T("\t FirmwareUpgrade - firmware download failed. srbControl->ReturnCode %d.\n"), srbControl->ReturnCode);
goto Exit;
}
//
// Update Image Offset for next iteration.
//
imageOffset += readLength;
}
// ---------------------------------------------------------------------------
// ( 3 ) ACTIVATE THE FIRMWARE SLOT ASSIGNED TO THE UPGRADE
// ---------------------------------------------------------------------------
//
// Activate the newly downloaded image with the assigned slot.
//
RtlZeroMemory(buffer, bufferSize);
//
// Setup the SRB control with the firmware ioctl control info
//
srbControl->HeaderLength = sizeof(SRB_IO_CONTROL);
srbControl->ControlCode = IOCTL_SCSI_MINIPORT_FIRMWARE;
RtlMoveMemory(srbControl->Signature, IOCTL_MINIPORT_SIGNATURE_FIRMWARE, 8);
srbControl->Timeout = 30;
srbControl->Length = bufferSize - sizeof(SRB_IO_CONTROL);
//
// Set firmware request fields for FIRMWARE_FUNCTION_ACTIVATE. This request is to the controller so
// FIRMWARE_REQUEST_FLAG_CONTROLLER is set in the flags
//
firmwareRequest->Version = FIRMWARE_REQUEST_BLOCK_STRUCTURE_VERSION;
firmwareRequest->Size = sizeof(FIRMWARE_REQUEST_BLOCK);
firmwareRequest->Function = FIRMWARE_FUNCTION_ACTIVATE;
firmwareRequest->Flags = FIRMWARE_REQUEST_FLAG_CONTROLLER;
firmwareRequest->DataBufferOffset = firmwareStructureOffset;
firmwareRequest->DataBufferLength = bufferSize - firmwareStructureOffset;
//
// Initialize the firmware activation structure pointer to the proper position after the request structure
//
firmwareActivate = (PSTORAGE_FIRMWARE_ACTIVATE)((PUCHAR)srbControl + firmwareRequest->DataBufferOffset);
//
// Set the activation parameters with the available slot selected
//
firmwareActivate->Version = 1;
firmwareActivate->Size = sizeof(STORAGE_FIRMWARE_ACTIVATE);
firmwareActivate->SlotToActivate = (UCHAR)slotNumber;
//
// Send the activation request
//
result = DeviceIoControl(DeviceList[Index].Handle,
IOCTL_SCSI_MINIPORT,
buffer,
bufferSize,
buffer,
bufferSize,
&returnedLength,
NULL
);
if (result == FALSE) {
_tprintf(_T("\t FirmwareUpgrade - IOCTL - firmware activate failed. 0x%X.\n"), GetLastError());
goto Exit;
}
//
// Display status result from firmware activation
//
switch (srbControl->ReturnCode) {
case FIRMWARE_STATUS_SUCCESS:
_tprintf(_T("\t FirmwareUpgrade - firmware activate succeeded.\n"));
break;
case FIRMWARE_STATUS_POWER_CYCLE_REQUIRED:
_tprintf(_T("\t FirmwareUpgrade - firmware activate succeeded. PLEASE REBOOT COMPUTER.\n"));
break;
case FIRMWARE_STATUS_ILLEGAL_REQUEST:
case FIRMWARE_STATUS_INVALID_PARAMETER:
case FIRMWARE_STATUS_INPUT_BUFFER_TOO_BIG:
_tprintf(_T("\t FirmwareUpgrade - firmware activate parameter error. srbControl->ReturnCode %d.\n"), srbControl->ReturnCode);
break;
case FIRMWARE_STATUS_INVALID_SLOT:
_tprintf(_T("\t FirmwareUpgrade - firmware activate, slot number invalid.\n"));
break;
case FIRMWARE_STATUS_INVALID_IMAGE:
_tprintf(_T("\t FirmwareUpgrade - firmware activate, invalid firmware image.\n"));
break;
case FIRMWARE_STATUS_ERROR:
case FIRMWARE_STATUS_CONTROLLER_ERROR:
_tprintf(_T("\t FirmwareUpgrade - firmware activate, error returned.\n"));
break;
default:
_tprintf(_T("\t FirmwareUpgrade - firmware activate, unexpected error. srbControl->ReturnCode %d.\n"), srbControl->ReturnCode);
break;
}
Exit:
if (fileHandle != NULL) {
CloseHandle(fileHandle);
}
if (buffer != NULL) {
free(buffer);
}
return;
}
Nota:
No se admite la descarga simultánea de varias imágenes de firmware. Una sola descarga de firmware siempre va seguida de una sola activación de firmware.
Una imagen de firmware ya residente en una ranura se puede reactivar mediante solo el comando activate function con el número de ranura correspondiente.
El código de control de IOCTL_SCSI_MINIPORT_FIRMWARE para el control de E/SRB está disponible a partir de Windows 8.1.