Today I've returned to this community after a 7-year hiatus, with a renewed interest in low-level development, a Bachelor's and a Master's Degree, and plenty of field experience. A far cry from the slice of my life indicated by my post history.
Anyway, I'd like to see if any members of the community have experience with UEFI Driver development, specifically for booting their OS from a ramdisk.
I'm both stuck on a problem, and would like to prime the community for this really neat loader that I think will be valuable for making encrypted installation media without pricey hardware!
I have been working on a generic EFI binary which can decrypt and decompress a FAT filesystem from an ISO image into memory. This in-memory blob is a Virtual Disk.
I am targeting all operating systems which support NVDIMM ACPI details about the loaded ramdisk, so this is intended to be a generic way to encapsulate all compatible boot images (and possibly multiple sets thereof).
This is working, password entry and all, no problem: the loader creates the ramdisk, decrypts et al., attaches Block I/O protocols to the handle, and the firmware does the rest of the black magic (Disk I/O protocols, etc.).
The loader then 'jumps' to the boot entrypoint on the loaded ramdisk at /EFI/BOOT/BOOTX64.EFI when a certain canary file is detected on the target "drive" (i.e., ramdisk) during enumeration. This is done with LoadImage and StartImage, without ever exiting boot services.
The target operating system will boot, but immediately (on Linux) drops into an emergency shell. This is of course because it has no idea where to find the root filesystem it was launched from, which is still hanging around in RAM within the system memory map.
This is further complicated by the fact that the compressed FAT filesystem often will have other fragments the loaders need to reference-- think, for example, RedHat's "kickstart" system.
Everything must be done from UEFI to make this work, so things like editing /etc/fstab are too OS-specific.
Luckily, newer ACPI specs provide an opportunity to install ramdisks as pseudo-device objects. This can make enumerating OSes aware of the ramdisk from the UEFI space.
I have tried to add NFIT entries correctly pointing to the EfiRuntimeServicesData preserved by the second-stage GRUB loader (BOOTX64.EFI within the FAT disk image), but to no avail despite Linux's dmesg output indicating it "sees" an NFIT entry.
...
So my primary question is this: how do I make the host operating system properly aware of the NVDIMM devices (i.e., the ramdisk FAT filesystem) for mounting and executing?
The specification seems to indicate an NVDIMM "root device" is necessary under the _SB ACPI namespace, but I don't really know how to add that and using the full ACPI SDT driver from EDK2 is too much bloat.
Do I need to self-compile an ASL-to-AML file describing the Root Device, a la https://github.com/tianocore/edk2/blob/ ... amDisk.asl??
Any guidance here would be exceedingly valued!
Some sample code to follow...
Adding NFIT to ACPI (using GNU-EFI and inspired by the RamDiskDxe implementation in EDK2):
Code: Select all
NfitLen = sizeof(EFI_ACPI_6_1_NVDIMM_FIRMWARE_INTERFACE_TABLE) +
sizeof(EFI_ACPI_6_1_NFIT_SYSTEM_PHYSICAL_ADDRESS_RANGE_STRUCTURE);
Nfit = AllocateZeroPool(NfitLen);
if (NULL == Nfit)
return EFI_OUT_OF_RESOURCES;
SpaRange = (EFI_ACPI_6_1_NFIT_SYSTEM_PHYSICAL_ADDRESS_RANGE_STRUCTURE *)
((UINT8 *)Nfit + sizeof(EFI_ACPI_6_1_NVDIMM_FIRMWARE_INTERFACE_TABLE));
/* OEM ID shouldn't matter or need to be recognized. */
UINT8 PcdAcpiDefaultOemId[6] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
NfitHeader = (EFI_ACPI_DESCRIPTION_HEADER *)Nfit;
NfitHeader->Signature = EFI_ACPI_6_1_NVDIMM_FIRMWARE_INTERFACE_TABLE_STRUCTURE_SIGNATURE;
NfitHeader->Length = NfitLen;
NfitHeader->Revision = EFI_ACPI_6_1_NVDIMM_FIRMWARE_INTERFACE_TABLE_REVISION;
NfitHeader->OemRevision = 0x20240501; /* OEM Revision by date should be fine. */
NfitHeader->CreatorId = EFI_SIGNATURE_32('T', 'T', 'O', 'O'); /* Creator: 'TTOO' */
NfitHeader->CreatorRevision = 0x1; /* Should just be 1. */
NfitHeader->OemTableId = EFI_SIGNATURE_64('T', 'T', 'O', 'O', 'L', 'O', 'A', 'D');
CopyMem(NfitHeader->OemId, &PcdAcpiDefaultOemId[0], sizeof(NfitHeader->OemId));
/* Fill in the content of the SPA Range Structure. */
SpaRange->Type = EFI_ACPI_6_1_NFIT_SYSTEM_PHYSICAL_ADDRESS_RANGE_STRUCTURE_TYPE;
SpaRange->Length = sizeof (EFI_ACPI_6_1_NFIT_SYSTEM_PHYSICAL_ADDRESS_RANGE_STRUCTURE);
SpaRange->SystemPhysicalAddressRangeBase = PrivateData->StartingAddr;
SpaRange->SystemPhysicalAddressRangeLength = PrivateData->Size;
CopyMem(&SpaRange->AddressRangeTypeGUID, &PrivateData->TypeGuid, sizeof(EFI_GUID));
Checksum = CalculateCheckSum8((UINT8 *)Nfit, NfitHeader->Length);
NfitHeader->Checksum = Checksum;
/*
* Publish the NFIT to the ACPI table.
* Note, since the NFIT might be modified by other driver, therefore, we
* do not track the returning TableKey from the InstallAcpiTable().
*/
Status = uefi_call_wrapper(
mAcpiTableProtocol->InstallAcpiTable,
4,
mAcpiTableProtocol,
Nfit,
NfitHeader->Length,
&TableKey
);
if (EFI_ERROR(Status))
return Status;
FreePool(Nfit);
return EFI_SUCCESS;
Also, please do not specify alternative suggestions (like U-Boot, LUKS, GRUB chainloading, etc.).
I'm really set on making this something I've home-brewed, and I'm extremely close to having it functional.
If anything needs clarification, please let me know. Thanks for understanding and for any pointers in the right direction!
--
References:
- 1 - RamDiskDxe EDK2 Driver: https://github.com/tianocore/edk2/blob/ ... ver.c#L116
- 2 - ACPI Device Tree (Linux): https://docs.kernel.org/firmware-guide/ ... space.html
- 3 - NVDIMM Root Information: https://uefi.org/htmlspecs/ACPI_Spec_6_ ... mm-devices
- 4 - UEFI Driver Development: https://tianocore-docs.github.io/edk2-U ... -draft.pdf