alcyone/BOOT/ENVIRON/LIB/EFI/efiinit.c

749 lines
20 KiB
C

/*++
Copyright (c) 2024, Quinn Stephens.
Provided under the BSD 3-Clause license.
Module Name:
efiinit.c
Abstract:
Provides EFI initialization utilities.
--*/
#include <ntrtl.h>
#include <string.h>
#include <wchar.h>
#include "efilib.h"
UCHAR EfiInitScratch[2048];
const EFI_GUID EfiLoadedImageProtocol = LOADED_IMAGE_PROTOCOL;
const EFI_GUID EfiDevicePathProtocol = DEVICE_PATH_PROTOCOL;
NTSTATUS
EfiInitpAppendPathString (
IN PWCHAR Destination,
IN ULONG BufferSize,
IN PWCHAR Source,
IN ULONG SourceSize,
IN OUT PULONG BufferUsed
)
/*++
Routine Description:
Appends a soure path to a destination path.
Arguments:
Destination - Path to append Source to.
BufferSize - Maximum number of bytes to append to Destination.
Source - Source path to append to Destination.
SourceSize - Size of Source, in bytes.
BufferUsed - Pointer to a ULONG recieving the number of bytes appended in.
Return Value:
STATUS_SUCCESS if successful,
STATUS_INVALID_PARAMETER if Destination is not valid,
STATUS_BUFFER_TOO_SMALL if BufferSize is too small.
--*/
{
ULONG Position;
//
// Verify that Source uses wide characters.
//
if (SourceSize % sizeof(WCHAR) != 0) {
return STATUS_INVALID_PARAMETER;
}
//
// Remove NULL terminator.
//
if (SourceSize >= sizeof(WCHAR)) {
Position = (SourceSize / sizeof(WCHAR)) - 1;
if (Source[Position] == UNICODE_NULL) {
SourceSize -= sizeof(UNICODE_NULL);
}
}
//
// Remove leading separator.
//
if (SourceSize >= sizeof(WCHAR)) {
if (Source[0] == L'\\') {
Source++;
SourceSize -= sizeof(WCHAR);
}
}
//
// Remove trailing separator.
//
if (SourceSize >= sizeof(WCHAR)) {
Position = (SourceSize / sizeof(WCHAR)) - 1;
if (Source[Position] == L'\\') {
SourceSize -= sizeof(WCHAR);
}
}
//
// Check if Source is empty.
//
if (SourceSize == 0) {
*BufferUsed = 0;
return STATUS_SUCCESS;
}
//
// Make sure the buffer is large enough.
//
if (BufferSize < SourceSize + sizeof(WCHAR)) {
return STATUS_BUFFER_TOO_SMALL;
}
//
// Append separator and Source to Destination.
//
Destination[0] = L'\\';
RtlCopyMemory(Destination + 1, Source, SourceSize);
*BufferUsed = SourceSize + sizeof(WCHAR);
return STATUS_SUCCESS;
}
EFI_DEVICE_PATH *
EfiInitpGetDeviceNode (
IN EFI_DEVICE_PATH *DevicePath
)
/*++
Routine Description:
Searches an EFI device path for the last device path node
before a file path node.
Arguments:
DevicePath - EFI_DEVICE_PATH *.
Return Value:
EFI_DEVICE_PATH *.
--*/
{
EFI_DEVICE_PATH *Node;
if (IsDevicePathEndType(DevicePath)) {
return DevicePath;
}
Node = NextDevicePathNode(DevicePath);
while (!IsDevicePathEndType(Node)) {
if (DevicePathType(Node) == MEDIA_DEVICE_PATH && DevicePathSubType(Node) == MEDIA_FILEPATH_DP) {
break;
}
DevicePath = Node;
Node = NextDevicePathNode(Node);
}
return DevicePath;
}
NTSTATUS
EfiInitTranslateDevicePath (
IN EFI_DEVICE_PATH *EfiDevicePath,
IN OUT PBOOT_DEVICE BootDevice,
IN ULONG BufferSize
)
/*++
Routine Description:
Translates an EFI_DEVICE_PATH into a BOOT_DEVICE.
Arguments:
EfiDevicePath - Path to be translated.
BootDevice - Pointer to a buffer that recieves the device.
BufferSize - Amount of available bytes in the buffer.
Return Value:
STATUS_SUCCESS if successful.
STATUS_INVALID_PARAMETER if the buffer is too small.
STATUS_UNSUCCESSFUL if the path could not be translated.
--*/
{
EFI_DEVICE_PATH *DeviceNode;
MEMMAP_DEVICE_PATH *MemmapNode;
HARDDRIVE_DEVICE_PATH *HarddriveNode;
PBOOT_BLOCK_IDENTIFIER BlockDevice;
if (BufferSize < sizeof(BOOT_DEVICE)) {
return STATUS_INVALID_PARAMETER;
}
BootDevice->Size = sizeof(BOOT_DEVICE);
//
// Memory map devices are treated as ramdisks.
//
if (DevicePathType(EfiDevicePath) == HARDWARE_DEVICE_PATH && DevicePathSubType(EfiDevicePath) == HW_MEMMAP_DP) {
MemmapNode = (MEMMAP_DEVICE_PATH *)EfiDevicePath;
BlockDevice = &BootDevice->Block;
BootDevice->Type = BOOT_DEVICE_TYPE_BLOCK;
BlockDevice->Type = BOOT_BLOCK_DEVICE_TYPE_RAMDISK;
BlockDevice->Ramdisk.ImageBase.QuadPart = MemmapNode->StartingAddress;
BlockDevice->Ramdisk.ImageSize = MemmapNode->EndingAddress - MemmapNode->StartingAddress;
BlockDevice->Ramdisk.ImageOffset = 0;
return STATUS_SUCCESS;
}
//
// TODO: Support more devices than just HDD/CD-ROM and ramdisks.
//
DeviceNode = EfiInitpGetDeviceNode(EfiDevicePath);
if (DevicePathType(DeviceNode) != MEDIA_DEVICE_PATH) {
return STATUS_UNSUCCESSFUL;
}
switch (DevicePathSubType(DeviceNode)) {
case MEDIA_HARDDRIVE_DP:
HarddriveNode = (HARDDRIVE_DEVICE_PATH *)DeviceNode;
//
// MBR disks still use the old partition struct.
//
if (HarddriveNode->SignatureType != SIGNATURE_TYPE_MBR) {
BlockDevice = &BootDevice->PartitionEx.Parent;
BootDevice->Type = BOOT_DEVICE_TYPE_PARTITION_EX;
} else {
BlockDevice = &BootDevice->Partition.Parent;
BootDevice->Type = BOOT_DEVICE_TYPE_PARTITION;
}
BlockDevice->Type = BOOT_BLOCK_DEVICE_TYPE_HARDDRIVE;
switch (HarddriveNode->SignatureType) {
case SIGNATURE_TYPE_MBR:
BlockDevice->Harddrive.PartitionType = BOOT_HARDDRIVE_PARTITION_TYPE_MBR;
BlockDevice->Harddrive.Mbr.Signature = *((ULONG *)HarddriveNode->Signature);
BootDevice->Partition.Mbr.PartitionNumber = HarddriveNode->PartitionNumber;
break;
case SIGNATURE_TYPE_GUID:
BootDevice->Attributes |= BOOT_DEVICE_ATTRIBUTE_NO_PARENT_SIGNATURE;
BlockDevice->Harddrive.PartitionType = BOOT_HARDDRIVE_PARTITION_TYPE_GPT;
RtlCopyMemory(&BootDevice->PartitionEx.Gpt.PartitionIdentifier, &HarddriveNode->Signature, sizeof(HarddriveNode->Signature));
break;
default:
BlockDevice->Harddrive.PartitionType = BOOT_HARDDRIVE_PARTITION_TYPE_RAW;
BlockDevice->Harddrive.Raw.DriveNumber = 0;
}
break;
case MEDIA_CDROM_DP:
BootDevice->Type = BOOT_DEVICE_TYPE_BLOCK;
BootDevice->Block.Type = BOOT_BLOCK_DEVICE_TYPE_CDROM;
BootDevice->Block.Cdrom.DriveNumber = 0;
break;
default:
return STATUS_UNSUCCESSFUL;
}
return STATUS_SUCCESS;
}
NTSTATUS
EfiInitpConvertEfiDevicePath (
IN EFI_DEVICE_PATH *EfiDevicePath,
IN BCDE_DATA_TYPE OptionType,
IN OUT PBOOT_ENTRY_OPTION Option,
IN ULONG BufferSize
)
/*++
Routine Description:
Converts an EFI device path into option format.
Arguments:
EfiDevicePath - Path to be converted.
OptionType - The data type to be assigned to Option->Type.
Option - Pointer to a buffer that recieves the option.
BufferSize - The amount of available bytes in the buffer.
Return Value:
STATUS_SUCCESS if successful.
STATUS_INVALID_PARAMETER if the buffer is too small.
Any status code returned by EfiInitTranslateDevicePath().
--*/
{
NTSTATUS Status;
PBCDE_DEVICE DeviceElement;
if (BufferSize < sizeof(BOOT_ENTRY_OPTION) + FIELD_OFFSET(BCDE_DEVICE, Device)) {
return STATUS_INVALID_PARAMETER;
}
RtlZeroMemory(Option, sizeof(BOOT_ENTRY_OPTION));
DeviceElement = (PBCDE_DEVICE)((PUCHAR)Option + sizeof(BOOT_ENTRY_OPTION));
Status = EfiInitTranslateDevicePath(
EfiDevicePath,
&DeviceElement->Device,
BufferSize - (sizeof(BOOT_ENTRY_OPTION) + FIELD_OFFSET(BCDE_DEVICE, Device))
);
if (!NT_SUCCESS(Status)) {
return Status;
}
Option->Type = OptionType;
Option->DataOffset = sizeof(BOOT_ENTRY_OPTION);
Option->DataSize = FIELD_OFFSET(BCDE_DEVICE, Device) + DeviceElement->Device.Size;
return STATUS_SUCCESS;
}
NTSTATUS
EfiInitpConvertEfiFilePath (
IN EFI_DEVICE_PATH *EfiFilePath,
IN BCDE_DATA_TYPE OptionType,
IN OUT PBOOT_ENTRY_OPTION Option,
IN ULONG BufferSize
)
/*++
Routine Description:
Converts an EFI file path into option format.
Arguments:
EfiFilePath - Path to be converted.
OptionType - The data type to be assigned to Option->Type.
Option - Pointer to a buffer that recieves the option.
BufferSize - The amount of available bytes in the buffer.
Return Value:
STATUS_SUCCESS if successful.
STATUS_INVALID_PARAMETER if the buffer is too small.
--*/
{
NTSTATUS Status;
EFI_DEVICE_PATH *Node;
PWCHAR PathStart, Position;
ULONG BufferRemaining, Length, Appended;
if (BufferSize < sizeof(BOOT_ENTRY_OPTION)) {
return STATUS_INVALID_PARAMETER;
}
RtlZeroMemory(Option, sizeof(BOOT_ENTRY_OPTION));
Option->Type = OptionType;
Option->DataOffset = sizeof(BOOT_ENTRY_OPTION);
//
// Loop through nodes and add one at a time.
//
Option->DataSize = 0;
BufferRemaining = BufferSize - sizeof(BOOT_ENTRY_OPTION);
Node = EfiFilePath;
PathStart = (PWCHAR)((PUCHAR)Option + Option->DataOffset);
Position = PathStart;
while (!IsDevicePathEndType(Node)) {
//
// Ignore non-filepath nodes.
//
if (DevicePathType(Node) != MEDIA_DEVICE_PATH || DevicePathSubType(Node) != MEDIA_FILEPATH_DP) {
Node = NextDevicePathNode(Node);
continue;
}
//
// Find the length of this path.
//
Status = RtlULongSub(DevicePathNodeLength(Node), FIELD_OFFSET(FILEPATH_DEVICE_PATH, PathName), &Length);
if (!NT_SUCCESS(Status)) {
return Status;
}
//
// Append this path to the path string.
//
Status = EfiInitpAppendPathString(Position, BufferRemaining, &((FILEPATH_DEVICE_PATH *)Node)->PathName[0], Length, &Appended);
if (!NT_SUCCESS(Status)) {
return Status;
}
//
// Update counters & position.
//
Option->DataSize += Appended;
BufferRemaining -= Appended;
Position = (PWCHAR)((PUCHAR)Position + Appended);
Node = NextDevicePathNode(Node);
}
//
// NULL-terminate path string.
//
if (BufferRemaining < sizeof(UNICODE_NULL)) {
return STATUS_INVALID_PARAMETER;
}
*Position = L'\0';
Option->DataSize += sizeof(UNICODE_NULL);
//
// The option is invalid if the path is empty.
//
if (Position == PathStart) {
Option->IsInvalid = TRUE;
}
return STATUS_SUCCESS;
}
VOID
EfiInitpCreateApplicationEntry (
IN EFI_SYSTEM_TABLE *SystemTable,
IN OUT PBOOT_INIT_APPLICATION_ENTRY Entry,
IN ULONG BufferSize,
IN EFI_DEVICE_PATH *EfiDevicePath,
IN EFI_DEVICE_PATH *EfiFilePath,
IN PWCHAR LoadOptions,
IN ULONG LoadOptionsSize,
IN ULONG Flags,
OUT PULONG BufferUsed,
OUT PBOOT_DEVICE *BootDevice
)
/*++
Routine Description:
Creates an application entry for the boot application.
Arguments:
SystemTable - Pointer to the EFI system table.
Entry - Pointer to a buffer that recieves the entry.
BufferSize - The amount of available bytes in the buffer.
EfiDevicePath - The application's device path.
EfiFilePath - The application's file path.
LoadOptions - Firmware load options string.
LoadOptionsSize - Size in bytes of the string pointed to by LoadOptions.
Flags - Unused.
BufferUsed - Pointer to a ULONG that recieves the buffer space used by this routine.
BootDevice - Pointer to a PBOOT_DEVICE that recieves the device the application was loaded from.
Return Value:
None.
--*/
{
NTSTATUS Status;
ULONG BufferRemaining, OptionsSize, Size;
PWCHAR BcdOptionString;
BOOLEAN BcdIdentifierSet;
UNICODE_STRING UnicodeString;
PBOOT_ENTRY_OPTION Option, PrevOption;
PBCDE_DEVICE BootDeviceElement;
(VOID)SystemTable;
(VOID)Flags;
*BufferUsed = 0;
*BootDevice = NULL;
OptionsSize = 0;
BcdIdentifierSet = FALSE;
BufferRemaining = BufferSize;
if (BufferRemaining < sizeof(BOOT_INIT_APPLICATION_ENTRY)) {
return;
}
RtlZeroMemory(Entry, sizeof(BOOT_INIT_APPLICATION_ENTRY));
Entry->Signature = BOOT_INIT_APPLICATION_ENTRY_SIGNATURE;
BufferRemaining -= FIELD_OFFSET(BOOT_INIT_APPLICATION_ENTRY, Options);
//
// Terminate load options.
//
LoadOptionsSize /= sizeof(WCHAR);
if (LoadOptionsSize != 0 && wcsnlen(LoadOptions, LoadOptionsSize) == LoadOptionsSize) {
LoadOptions[LoadOptionsSize - 1] = L'\0';
}
//
// Parse BCD GUID option if present.
//
if (LoadOptions != NULL && (BcdOptionString = wcsstr(LoadOptions, L"BCDOBJECT=")) != NULL) {
RtlInitUnicodeString(&UnicodeString, (PWCHAR)((PUCHAR)BcdOptionString + sizeof(L"BCDOBJECT=") - sizeof(UNICODE_NULL)));
if (NT_SUCCESS(RtlGUIDFromString(&UnicodeString, &Entry->BcdIdentifier))) {
BcdIdentifierSet = TRUE;
}
}
if (!BcdIdentifierSet) {
Entry->Attributes |= BOOT_APPLICATION_ENTRY_NO_BCD_IDENTIFIER;
}
//
// Convert the EFI device path into a boot device option.
//
Option = &Entry->Options;
Status = EfiInitpConvertEfiDevicePath(EfiDevicePath, BCDE_DATA_TYPE_APPLICATION_DEVICE, Option, BufferRemaining);
if (!NT_SUCCESS(Status)) {
Option->IsInvalid = TRUE;
goto exit;
}
BootDeviceElement = (PBCDE_DEVICE)((PUCHAR)Option + Option->DataOffset);
*BootDevice = &BootDeviceElement->Device;
Size = BlGetBootOptionSize(Option);
OptionsSize += Size;
BufferRemaining -= Size;
//
// Convert the EFI file path into a boot file path option.
// TODO: Support UDP/PXE boot.
//
PrevOption = Option;
Option = (PBOOT_ENTRY_OPTION)((PUCHAR)&Entry->Options + OptionsSize);
Status = EfiInitpConvertEfiFilePath(EfiFilePath, BCDE_DATA_TYPE_APPLICATION_PATH, Option, BufferRemaining);
if (!NT_SUCCESS(Status)) {
goto exit;
}
PrevOption->NextOptionOffset = (PUCHAR)Option - (PUCHAR)&Entry->Options;
Size = BlGetBootOptionSize(Option);
OptionsSize += Size;
BufferRemaining -= Size;
//
// TODO: Parse additional options from LoadOptions.
//
PrevOption = Option;
Option = (PBOOT_ENTRY_OPTION)((PUCHAR)&Entry->Options + OptionsSize);
// Status = Unknown(LoadOptions, &Entry->Options, RemainingSize, &OptionsSize, &PrevOption, &Size);
if (!NT_SUCCESS(Status)) {
goto exit;
}
exit:
*BufferUsed = BufferSize - BufferRemaining;
}
PBOOT_APPLICATION_PARAMETERS
EfiInitCreateInputParametersEx (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable,
IN ULONG Flags
)
/*++
Routine Description:
Creates the input parameter structure for the boot application.
Arguments:
ImageHandle - EFI handle for the boot application image.
SystemTable - Pointer to the EFI system table.
Flags - Unused.
Return Value:
Pointer to parameter structure if successful.
NULL on failure.
--*/
{
EFI_STATUS Status;
ULONG ScratchUsed;
ULONG ApplicationEntrySize;
EFI_PHYSICAL_ADDRESS BadPageAddress;
EFI_LOADED_IMAGE *LoadedImage;
EFI_DEVICE_PATH *DevicePath;
PBOOT_APPLICATION_PARAMETERS InputParameters;
PBOOT_MEMORY_INFO MemoryInfo;
PMEMORY_DESCRIPTOR MemoryDescriptor;
PBOOT_DEVICE BootDevice;
PBOOT_FIRMWARE_DATA FirmwareData;
PBOOT_RETURN_DATA ReturnData;
ScratchUsed = 0;
//
// Page 0x102 may be broken on some machines.
// It is mapped here so that it does not get used.
//
BadPageAddress = 0x102 << PAGE_SHIFT;
SystemTable->BootServices->AllocatePages(AllocateAddress, EfiLoaderData, 1, &BadPageAddress);
Status = SystemTable->BootServices->HandleProtocol(
ImageHandle,
(EFI_GUID*)&EfiLoadedImageProtocol,
(VOID**)&LoadedImage
);
if (Status != EFI_SUCCESS) {
return NULL;
}
Status = SystemTable->BootServices->HandleProtocol(
LoadedImage->DeviceHandle,
(EFI_GUID*)&EfiDevicePathProtocol,
(VOID**)&DevicePath
);
if (Status != EFI_SUCCESS) {
return NULL;
}
InputParameters = (PBOOT_APPLICATION_PARAMETERS)(&EfiInitScratch[ScratchUsed]);
ScratchUsed += sizeof(BOOT_APPLICATION_PARAMETERS);
InputParameters->Signature = BOOT_APPLICATION_PARAMETERS_SIGNATURE;
InputParameters->Version = BOOT_APPLICATION_PARAMETERS_VERSION;
InputParameters->MachineType = BOOT_MACHINE_TYPE;
InputParameters->TranslationType = TRANSLATION_TYPE_NONE;
InputParameters->ImageBase = LoadedImage->ImageBase;
InputParameters->ImageSize = LoadedImage->ImageSize;
InputParameters->MemoryInfoOffset = ScratchUsed;
MemoryInfo = (PBOOT_MEMORY_INFO)(&EfiInitScratch[ScratchUsed]);
ScratchUsed += sizeof(BOOT_MEMORY_INFO);
MemoryInfo->Version = BOOT_MEMORY_INFO_VERSION;
MemoryInfo->MdlOffset = sizeof(BOOT_MEMORY_INFO);
MemoryInfo->DescriptorCount = 1;
MemoryInfo->DescriptorSize = sizeof(MEMORY_DESCRIPTOR);
MemoryInfo->FirstPageOffset = FIELD_OFFSET(MEMORY_DESCRIPTOR, FirstPage);
MemoryDescriptor = (PMEMORY_DESCRIPTOR)(&EfiInitScratch[ScratchUsed]);
ScratchUsed += sizeof(MEMORY_DESCRIPTOR);
MemoryDescriptor->FirstPage = (UINTN)InputParameters->ImageBase >> PAGE_SHIFT;
MemoryDescriptor->PageCount = ALIGN_UP(InputParameters->ImageSize, PAGE_SIZE) >> PAGE_SHIFT;
MemoryDescriptor->Attributes = MEMORY_ATTRIBUTE_WB;
MemoryDescriptor->Type = MEMORY_TYPE_BOOT_APPLICATION;
InputParameters->ApplicationEntryOffset = ScratchUsed;
EfiInitpCreateApplicationEntry(
SystemTable,
(PBOOT_INIT_APPLICATION_ENTRY)(&EfiInitScratch[ScratchUsed]),
sizeof(EfiInitScratch) - ScratchUsed,
DevicePath,
LoadedImage->FilePath,
LoadedImage->LoadOptions,
LoadedImage->LoadOptionsSize,
Flags,
&ApplicationEntrySize,
&BootDevice
);
ScratchUsed += ApplicationEntrySize;
InputParameters->BootDeviceOffset = ScratchUsed;
if (BootDevice != NULL) {
RtlCopyMemory(&EfiInitScratch[ScratchUsed], BootDevice, BootDevice->Size);
ScratchUsed += BootDevice->Size;
} else {
RtlZeroMemory(&EfiInitScratch[ScratchUsed], sizeof(BOOT_DEVICE));
ScratchUsed += sizeof(BOOT_DEVICE);
}
InputParameters->FirmwareDataOffset = ScratchUsed;
FirmwareData = (PBOOT_FIRMWARE_DATA)(&EfiInitScratch[ScratchUsed]);
ScratchUsed += sizeof(BOOT_FIRMWARE_DATA);
RtlZeroMemory(FirmwareData, sizeof(BOOT_FIRMWARE_DATA));
FirmwareData->Version = BOOT_FIRMWARE_DATA_VERSION;
FirmwareData->ImageHandle = ImageHandle;
FirmwareData->SystemTable = SystemTable;
#if defined(__i386__) || defined(__x86_64__)
asm volatile("mov %%cr3, %0" :"=r"(FirmwareData->Cr3));
#endif
BlpArchGetDescriptorTableContext(&FirmwareData->DescriptorTableContext);
InputParameters->ReturnDataOffset = ScratchUsed;
ReturnData = (PBOOT_RETURN_DATA)(&EfiInitScratch[ScratchUsed]);
ScratchUsed += sizeof(BOOT_RETURN_DATA);
ReturnData->Version = BOOT_RETURN_DATA_VERSION;
InputParameters->Size = ScratchUsed;
if (InputParameters->Size > sizeof(EfiInitScratch)) {
return NULL;
}
return InputParameters;
}
PBOOT_APPLICATION_PARAMETERS
EfiInitCreateInputParameters (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
/*++
Routine Description:
Creates the input parameter structure for the boot application.
Arguments:
ImageHandle - EFI handle for the boot application image.
SystemTable - Pointer to the EFI system table.
Return Value:
Pointer to parameter structure if successful.
NULL on failure.
--*/
{
return EfiInitCreateInputParametersEx(ImageHandle, SystemTable, 0);
}