/*
|
# Copyright (c) 2014-2018, Linaro Limited. All rights reserved.
|
#
|
# SPDX-License-Identifier: BSD-2-Clause-Patent
|
#
|
*/
|
#include <RdkBootManagerLib.h>
|
|
/* See sparse_format.h in AOSP */
|
#define SPARSE_HEADER_MAGIC 0xed26ff3a
|
#define CHUNK_TYPE_RAW 0xCAC1
|
#define CHUNK_TYPE_FILL 0xCAC2
|
#define CHUNK_TYPE_DONT_CARE 0xCAC3
|
#define CHUNK_TYPE_CRC32 0xCAC4
|
|
#define PARTITION_NAME_MAX_LENGTH 72/2
|
|
#define FLASH_DEVICE_PATH_SIZE(DevPath) ( GetDevicePathSize (DevPath) - \
|
sizeof (EFI_DEVICE_PATH_PROTOCOL))
|
|
#define IS_ALPHA(Char) (((Char) <= L'z' && (Char) >= L'a') || \
|
((Char) <= L'Z' && (Char) >= L'Z'))
|
|
typedef struct _DISKIO_PARTITION_LIST {
|
LIST_ENTRY Link;
|
CHAR16 PartitionName[PARTITION_NAME_MAX_LENGTH];
|
EFI_HANDLE PartitionHandle;
|
} DISKIO_PARTITION_LIST;
|
|
typedef struct _SPARSE_HEADER {
|
UINT32 Magic;
|
UINT16 MajorVersion;
|
UINT16 MinorVersion;
|
UINT16 FileHeaderSize;
|
UINT16 ChunkHeaderSize;
|
UINT32 BlockSize;
|
UINT32 TotalBlocks;
|
UINT32 TotalChunks;
|
UINT32 ImageChecksum;
|
} SPARSE_HEADER;
|
|
typedef struct _CHUNK_HEADER {
|
UINT16 ChunkType;
|
UINT16 Reserved1;
|
UINT32 ChunkSize;
|
UINT32 TotalSize;
|
} CHUNK_HEADER;
|
|
STATIC LIST_ENTRY mPartitionListHead;
|
STATIC EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *mTextOut;
|
|
/*
|
* Helper to free the partition list
|
*/
|
STATIC
|
VOID
|
FreePartitionList (
|
VOID
|
)
|
{
|
DISKIO_PARTITION_LIST *Entry;
|
DISKIO_PARTITION_LIST *NextEntry;
|
|
Entry = (DISKIO_PARTITION_LIST *)GetFirstNode (&mPartitionListHead);
|
while (!IsNull (&mPartitionListHead, &Entry->Link)) {
|
NextEntry = (DISKIO_PARTITION_LIST *) GetNextNode (\
|
&mPartitionListHead, &Entry->Link);
|
|
RemoveEntryList (&Entry->Link);
|
FreePool (Entry);
|
|
Entry = NextEntry;
|
}
|
}
|
|
/*
|
* lists the available Block Io and adds handle of given dev path
|
*/
|
STATIC
|
EFI_STATUS
|
ListBlockIos (
|
IN CHAR16 *PartitionName
|
)
|
{
|
EFI_STATUS Status;
|
EFI_HANDLE *AllHandles;
|
EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *DevPathToText;
|
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
|
UINTN LoopIndex;
|
UINTN NumHandles;
|
UINT16 *DeviceFullPath;
|
DISKIO_PARTITION_LIST *Entry;
|
RETURN_STATUS RetStatus;
|
|
InitializeListHead (&mPartitionListHead);
|
|
Status = gBS->LocateProtocol (
|
&gEfiDevicePathToTextProtocolGuid,
|
NULL,
|
(VOID **) &DevPathToText
|
);
|
ASSERT_EFI_ERROR (Status);
|
|
// Get every Block IO protocol instance installed in the system
|
Status = gBS->LocateHandleBuffer (
|
ByProtocol,
|
&gEfiBlockIoProtocolGuid,
|
NULL,
|
&NumHandles,
|
&AllHandles
|
);
|
ASSERT_EFI_ERROR (Status);
|
DEBUG ((DEBUG_INFO, "Block IO: %d handles \n", NumHandles));
|
|
// Get HTTP driver handle from AllHandles
|
for (LoopIndex = 0; LoopIndex < NumHandles; LoopIndex++) {
|
// Get the device path for the handle
|
Status = gBS->OpenProtocol (
|
AllHandles[LoopIndex],
|
&gEfiDevicePathProtocolGuid,
|
(VOID **) &DevicePath,
|
gImageHandle,
|
NULL,
|
EFI_OPEN_PROTOCOL_GET_PROTOCOL
|
);
|
|
DeviceFullPath = DevPathToText->ConvertDevicePathToText (
|
DevicePath,
|
FALSE,
|
TRUE
|
);
|
|
DEBUG((DEBUG_INFO,"Handle[%d] is %p, fullpath %s\n", \
|
LoopIndex, AllHandles[LoopIndex], DeviceFullPath));
|
|
if (StrCmp (PartitionName, DeviceFullPath) == 0) {
|
DEBUG((DEBUG_INFO, "rootfs partition path matched\n"));
|
//
|
// Add the partition handle to the list
|
//
|
// Create entry
|
Entry = AllocatePool (sizeof (DISKIO_PARTITION_LIST));
|
if (Entry == NULL) {
|
Status = EFI_OUT_OF_RESOURCES;
|
goto Exit;
|
}
|
|
// Copy handle and partition name
|
Entry->PartitionHandle = AllHandles[LoopIndex];
|
RetStatus = StrnCpyS (
|
Entry->PartitionName,
|
PARTITION_NAME_MAX_LENGTH,
|
PartitionName,
|
PARTITION_NAME_MAX_LENGTH - 1
|
);
|
ASSERT_RETURN_ERROR (RetStatus);
|
InsertTailList (&mPartitionListHead, &Entry->Link);
|
break;
|
}
|
}
|
FreePool (AllHandles);
|
ASSERT (LoopIndex < NumHandles);
|
Exit:
|
return Status;
|
}
|
|
STATIC
|
EFI_STATUS
|
OpenPartition (
|
IN CHAR8 *PartitionName,
|
IN VOID *Image,
|
IN UINTN Size,
|
OUT EFI_BLOCK_IO_PROTOCOL **BlockIo,
|
OUT EFI_DISK_IO_PROTOCOL **DiskIo
|
)
|
{
|
EFI_STATUS Status;
|
UINTN PartitionSize;
|
DISKIO_PARTITION_LIST *Entry;
|
SPARSE_HEADER *SparseHeader;
|
UINT16 UnicodePartitionName[100];
|
RETURN_STATUS RetStatus;
|
|
RetStatus = AsciiStrToUnicodeStrS (PartitionName, UnicodePartitionName,
|
sizeof (UnicodePartitionName));
|
if (RETURN_ERROR (RetStatus)) {
|
return EFI_OUT_OF_RESOURCES;
|
}
|
DEBUG((DEBUG_INFO, "Unicode partition name %s\n", UnicodePartitionName));
|
|
Status = ListBlockIos (UnicodePartitionName);
|
ASSERT_EFI_ERROR (Status);
|
|
Entry = (DISKIO_PARTITION_LIST *)GetFirstNode (&(mPartitionListHead));
|
ASSERT (Entry != NULL);
|
|
Status = gBS->OpenProtocol (
|
Entry->PartitionHandle,
|
&gEfiBlockIoProtocolGuid,
|
(VOID **) BlockIo,
|
gImageHandle,
|
NULL,
|
EFI_OPEN_PROTOCOL_GET_PROTOCOL
|
);
|
|
if (EFI_ERROR (Status)) {
|
DEBUG ((DEBUG_ERROR, "Unable to open Block IO protocol: %r\n", Status));
|
Status = EFI_NOT_FOUND;
|
goto Exit;
|
}
|
|
SparseHeader = (SPARSE_HEADER *)Image;
|
|
if (SparseHeader->Magic == SPARSE_HEADER_MAGIC) {
|
DEBUG ((DEBUG_INFO, \
|
"Sparse Magic: 0x%x Major: %d Minor: %d fhs: %d chs: %d bs: %d tbs: %d tcs: %d checksum: %d \n", \
|
SparseHeader->Magic, SparseHeader->MajorVersion, \
|
SparseHeader->MinorVersion, SparseHeader->FileHeaderSize, \
|
SparseHeader->ChunkHeaderSize, SparseHeader->BlockSize, \
|
SparseHeader->TotalBlocks, \
|
SparseHeader->TotalChunks, SparseHeader->ImageChecksum));
|
|
if (SparseHeader->MajorVersion != 1) {
|
DEBUG ((DEBUG_ERROR, "Sparse image version %d.%d not supported.\n",
|
SparseHeader->MajorVersion, SparseHeader->MinorVersion));
|
Status = EFI_INVALID_PARAMETER;
|
goto Exit;
|
}
|
|
Size = SparseHeader->BlockSize * SparseHeader->TotalBlocks;
|
}
|
|
// Check image will fit on device
|
PartitionSize = (BlockIo[0]->Media->LastBlock + 1) * BlockIo[0]->Media->BlockSize;
|
if (PartitionSize < Size) {
|
DEBUG ((DEBUG_ERROR, "Partition not big enough.\n"));
|
DEBUG ((DEBUG_ERROR, \
|
"Partition Size:\t%ld\nImage Size:\t%ld\n", PartitionSize, Size));
|
|
Status = EFI_VOLUME_FULL;
|
goto Exit;
|
}
|
|
Status = gBS->OpenProtocol (
|
Entry->PartitionHandle,
|
&gEfiDiskIoProtocolGuid,
|
(VOID **) DiskIo,
|
gImageHandle,
|
NULL,
|
EFI_OPEN_PROTOCOL_GET_PROTOCOL
|
);
|
|
Exit:
|
FreePartitionList ();
|
return Status;
|
}
|
|
EFI_STATUS
|
PartitionRead (
|
IN CHAR8 *PartitionName,
|
IN VOID *Image,
|
IN UINTN Size
|
)
|
{
|
EFI_STATUS Status;
|
EFI_BLOCK_IO_PROTOCOL *BlockIo;
|
EFI_DISK_IO_PROTOCOL *DiskIo;
|
UINT32 MediaId;
|
|
Status = OpenPartition (PartitionName, Image, Size, &BlockIo, &DiskIo);
|
if (EFI_ERROR (Status)) {
|
goto Exit;
|
}
|
|
MediaId = BlockIo->Media->MediaId;
|
|
Status = DiskIo->ReadDisk (DiskIo, MediaId, 0, Size, Image);
|
if (EFI_ERROR (Status)) {
|
goto Exit;
|
}
|
|
BlockIo->FlushBlocks(BlockIo);
|
|
Exit:
|
return Status;
|
}
|
|
EFI_STATUS
|
PartitionWrite (
|
IN CHAR8 *PartitionName,
|
IN VOID *Image,
|
IN UINTN Size
|
)
|
{
|
EFI_STATUS Status;
|
EFI_BLOCK_IO_PROTOCOL *BlockIo;
|
EFI_DISK_IO_PROTOCOL *DiskIo;
|
UINT32 MediaId;
|
SPARSE_HEADER *SparseHeader;
|
CHUNK_HEADER *ChunkHeader;
|
UINT32 Chunk;
|
UINTN Offset;
|
|
Status = OpenPartition (PartitionName, Image, Size, &BlockIo, &DiskIo);
|
if (EFI_ERROR (Status)) {
|
goto Exit;
|
}
|
|
Offset = 0;
|
MediaId = BlockIo->Media->MediaId;
|
SparseHeader = (SPARSE_HEADER *)Image;
|
|
if (SparseHeader->Magic == SPARSE_HEADER_MAGIC) {
|
CHAR16 OutputString[64];
|
UINTN ChunkPrintDensity =
|
SparseHeader->TotalChunks > 1600 ? SparseHeader->TotalChunks / 200 : 32;
|
|
Image += SparseHeader->FileHeaderSize;
|
for (Chunk = 0; Chunk < SparseHeader->TotalChunks; Chunk++) {
|
UINTN WriteSize;
|
ChunkHeader = (CHUNK_HEADER *)Image;
|
|
// Show progress. Don't do it for every packet as outputting text
|
// might be time consuming. ChunkPrintDensity is calculated to
|
// provide an update every half percent change for large
|
// downloads.
|
if (Chunk % ChunkPrintDensity == 0) {
|
UnicodeSPrint(OutputString, sizeof(OutputString),
|
L"\r%5d / %5d chunks written (%d%%)", Chunk,
|
SparseHeader->TotalChunks,
|
(Chunk * 100) / SparseHeader->TotalChunks);
|
mTextOut->OutputString(mTextOut, OutputString);
|
}
|
|
DEBUG ((DEBUG_INFO, "Chunk #%d - Type: 0x%x Size: %d TotalSize: %d Offset %d\n",
|
(Chunk+1), ChunkHeader->ChunkType, ChunkHeader->ChunkSize,
|
ChunkHeader->TotalSize, Offset));
|
Image += sizeof(CHUNK_HEADER);
|
WriteSize = (SparseHeader->BlockSize) * ChunkHeader->ChunkSize;
|
switch (ChunkHeader->ChunkType) {
|
case CHUNK_TYPE_RAW:
|
DEBUG ((DEBUG_INFO, "Writing %d at Offset %d\n", WriteSize, Offset));
|
Status = DiskIo->WriteDisk (DiskIo, MediaId, Offset, WriteSize, Image);
|
if (EFI_ERROR (Status)) {
|
goto Exit;
|
}
|
Image += WriteSize;
|
break;
|
case CHUNK_TYPE_DONT_CARE:
|
break;
|
case CHUNK_TYPE_CRC32:
|
break;
|
default:
|
DEBUG ((DEBUG_ERROR, "Unknown Chunk Type: 0x%x", ChunkHeader->ChunkType));
|
Status = EFI_PROTOCOL_ERROR;
|
goto Exit;
|
}
|
Offset += WriteSize;
|
}
|
|
UnicodeSPrint (OutputString, sizeof (OutputString),
|
L"\r%5d / %5d chunks written (100%%)\r\n",
|
SparseHeader->TotalChunks, SparseHeader->TotalChunks);
|
mTextOut->OutputString(mTextOut, OutputString);
|
|
} else {
|
|
Status = DiskIo->WriteDisk (DiskIo, MediaId, 0, Size, Image);
|
if (EFI_ERROR (Status)) {
|
goto Exit;
|
}
|
}
|
|
BlockIo->FlushBlocks (BlockIo);
|
|
Exit:
|
return Status;
|
}
|