From d396edd49930d4651a9800de0c322946f0bc77e3 Mon Sep 17 00:00:00 2001 From: Shahriyar Jalayeri Date: Fri, 4 Oct 2024 18:42:19 +0300 Subject: [PATCH 1/2] add eve efi application Signed-off-by: Shahriyar Jalayeri --- pkg/dom0-ztools/Dockerfile | 22 +- pkg/dom0-ztools/efi/Makefile | 32 ++ pkg/dom0-ztools/efi/efi_run.sh | 11 + pkg/dom0-ztools/efi/main.c | 697 ++++++++++++++++++++++++++++++++ pkg/dom0-ztools/efi/startup.nsh | 3 + 5 files changed, 764 insertions(+), 1 deletion(-) create mode 100644 pkg/dom0-ztools/efi/Makefile create mode 100644 pkg/dom0-ztools/efi/efi_run.sh create mode 100644 pkg/dom0-ztools/efi/main.c create mode 100644 pkg/dom0-ztools/efi/startup.nsh diff --git a/pkg/dom0-ztools/Dockerfile b/pkg/dom0-ztools/Dockerfile index 23835d4974..61ba231d2f 100644 --- a/pkg/dom0-ztools/Dockerfile +++ b/pkg/dom0-ztools/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile-upstream:1.5.0-rc2-labs FROM lfedge/eve-alpine:82df60e43ab9f8c935584b8c7b4d0a4b0271d608 as zfs ENV BUILD_PKGS git patch ca-certificates util-linux build-base gettext-dev libtirpc-dev automake autoconf \ - libtool linux-headers attr-dev e2fsprogs-dev glib-dev openssl-dev util-linux-dev coreutils + libtool linux-headers attr-dev e2fsprogs-dev glib-dev openssl-dev util-linux-dev coreutils gnu-efi gnu-efi-dev mtools ENV PKGS ca-certificates util-linux libintl libuuid libtirpc libblkid libcrypto1.1 zlib RUN eve-alpine-deploy.sh @@ -42,6 +42,26 @@ RUN cp -r /tmp/zfs-out/* /out # Add directory for CDI files RUN mkdir -p /out/etc/cdi +# build dosfstools, the one that is in alpine (busybox) is too old +# and doesn't support creating small FAT filesystems. +WORKDIR /mkfs +ADD https://github.com/dosfstools/dosfstools.git#v4.2 dosfstools +RUN cd dosfstools && \ + ./autogen.sh && \ + ./configure --prefix=/mkfs/dosfstools/bin && \ + make && \ + make install +WORKDIR /efi +COPY efi/ . +RUN make all && \ + mkdir -p /out/etc/ovmf && \ + cp efi_run.sh /out/etc/ovmf/ && \ + chmod +x /out/etc/ovmf/efi_run.sh && \ + dd if=/dev/zero of=/out/etc/ovmf/eve_efi.img bs=1M count=1 && \ + /mkfs/dosfstools/bin/sbin/mkfs.fat /out/etc/ovmf/eve_efi.img && \ + mcopy -i /out/etc/ovmf/eve_efi.img startup.nsh :: && \ + mcopy -i /out/etc/ovmf/eve_efi.img efi_eveutil.efi :: + FROM scratch COPY --from=zfs /out/ / # hadolint ignore=DL3020 diff --git a/pkg/dom0-ztools/efi/Makefile b/pkg/dom0-ztools/efi/Makefile new file mode 100644 index 0000000000..13e6ce4c08 --- /dev/null +++ b/pkg/dom0-ztools/efi/Makefile @@ -0,0 +1,32 @@ +ARCH = $(shell uname -m | sed s,i[3456789]86,ia32,) +OBJS = main.o +TARGE_SO = efi_eveutil.so +TARGET_EFI = efi_eveutil.efi +EFIINC = /usr/include/efi +EFIINCS = -I$(EFIINC) -I$(EFIINC)/$(ARCH) -I$(EFIINC)/protocol +LIB = /usr/lib +EFILIB = /usr/lib +EFI_CRT_OBJS = $(EFILIB)/crt0-efi-$(ARCH).o +EFI_LDS = $(EFILIB)/elf_$(ARCH)_efi.lds + +CFLAGS = $(EFIINCS) -fno-stack-protector -fpic \ + -fshort-wchar -mno-red-zone -Wall -Werror +ifeq ($(ARCH),x86_64) + CFLAGS += -DEFI_FUNCTION_WRAPPER +endif + +LDFLAGS = -nostdlib -znocombreloc -T $(EFI_LDS) -shared \ + -Bsymbolic -L $(EFILIB) -L $(LIB) $(EFI_CRT_OBJS) + +all: $(TARGET_EFI) + +$(TARGE_SO): $(OBJS) + ld $(LDFLAGS) $(OBJS) -o $@ -lefi -lgnuefi + +clean: + rm -f $(OBJS) $(TARGE_EFI) $(TARGE_SO) + +%.efi: %.so + objcopy -j .text -j .sdata -j .data -j .dynamic \ + -j .dynsym -j .rel -j .rela -j .reloc \ + --target=efi-app-$(ARCH) $^ $@ \ No newline at end of file diff --git a/pkg/dom0-ztools/efi/efi_run.sh b/pkg/dom0-ztools/efi/efi_run.sh new file mode 100644 index 0000000000..67a35ab288 --- /dev/null +++ b/pkg/dom0-ztools/efi/efi_run.sh @@ -0,0 +1,11 @@ +#!/bin/sh +mkdir -p /run/kvm +cp /hostfs/etc/ovmf/eve_efi.img /run/kvm/eve_efi.img +/usr/lib/xen/bin/qemu-system-x86_64 \ + -machine "type=pc-q35-3.1" \ + -drive "if=pflash,unit=0,format=raw,readonly=on,file=/usr/lib/xen/boot/OVMF_CODE.fd" \ + -drive "if=pflash,unit=1,format=raw,readonly=off,file=${1}" \ + -drive "file=/run/kvm/eve_efi.img,format=raw" \ + -smbios "type=11,value=${2}" \ + -net none \ + -nographic \ No newline at end of file diff --git a/pkg/dom0-ztools/efi/main.c b/pkg/dom0-ztools/efi/main.c new file mode 100644 index 0000000000..48cf023e58 --- /dev/null +++ b/pkg/dom0-ztools/efi/main.c @@ -0,0 +1,697 @@ +// Copyright (c) 2024 Zededa, Inc. +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#define SEC_TO_USEC(value) ((value) * 1000 * 1000) +#define SMBIOS_INSTANCE_FROM_THIS(this) CR(this, SMBIOS_INSTANCE, Smbios, SMBIOS_INSTANCE_SIGNATURE) +#define SMBIOS_ENTRY_FROM_LINK(link) CR(link, EFI_SMBIOS_ENTRY, Link, EFI_SMBIOS_ENTRY_SIGNATURE) +#define SMBIOS_INSTANCE_SIGNATURE SIGNATURE_32('S', 'B', 'i', 's') + +#define EFI_SMBIOS_RECORD_HEADER_VERSION 0x0100 +#define SMBIOS_HANDLE_PI_RESERVED 0xFFFE +#define SMBIOS_TYPE_OEM_STRINGS 11 + +#define VarLastTriedBootIndex L"LastTriedBootIndex" +#define VarPlatformConfig L"PlatformConfig" +#define CONFIG_DELIMITER L':' +#define EVE_FML_RESOUTION L"eve.fml.resolution:*" +#define EVE_TRY_ALL_BOOT L"eve.try.all.boot.options:*" + +EFI_GUID gOvmfPlatformConfigGuid = + {0x7235c51c, 0x0c80, 0x4cab, {0x87, 0xac, 0x3b, 0x08, 0x4a, 0x63, 0x04, 0xb1}}; + +EFI_GUID gEfiShellParametersProtocolGuid = + {0x752f3136, 0x4e16, 0x4fdc, {0xa2, 0x2a, 0xe5, 0xf4, 0x68, 0x12, 0xf4, 0xca}}; + +EFI_GUID gEfiSmbiosProtocolGuid = + {0x3583ff6, 0xcb36, 0x4940, {0x94, 0x7e, 0xb9, 0xb3, 0x9f, 0x4a, 0xfa, 0xf7}}; + +// +// Randmyly generated GUID, rolled a dice! +// +EFI_GUID gEfiCustomNextBootIndexGuid = + {0xdeadbeef, 0x9abc, 0xdef0, {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}}; + +UINT16 *gBootOrder; +UINTN gBootOrderSize; + +typedef enum +{ + EfiLockUninitialized = 0, + EfiLockReleased = 1, + EfiLockAcquired = 2 +} EFI_LOCK_STATE; + +typedef struct +{ + UINT32 HorizontalResolution; + UINT32 VerticalResolution; +} PLATFORM_CONFIG; + +typedef struct +{ + EFI_TPL Tpl; + EFI_TPL OwnerTpl; + EFI_LOCK_STATE Lock; +} EFI_LOCK; + +typedef struct EFI_SMBIOS_PROTOCOL +{ + VOID *Add; + VOID *UpdateString; + VOID *Remove; + VOID *GetNext; + UINT8 MajorVersion; + UINT8 MinorVersion; +} EFI_SMBIOS_PROTOCOL; + +typedef struct +{ + UINT32 Signature; + EFI_HANDLE Handle; + EFI_SMBIOS_PROTOCOL Smbios; + EFI_LOCK DataLock; + LIST_ENTRY DataListHead; + LIST_ENTRY AllocatedHandleListHead; +} SMBIOS_INSTANCE; + +typedef struct +{ + UINT16 Version; + UINT16 HeaderSize; + UINTN RecordSize; + EFI_HANDLE ProducerHandle; + UINTN NumberOfStrings; +} EFI_SMBIOS_RECORD_HEADER; + +typedef struct +{ + UINT32 Signature; + LIST_ENTRY Link; + EFI_SMBIOS_RECORD_HEADER *RecordHeader; + UINTN RecordSize; + BOOLEAN Smbios32BitTable; + BOOLEAN Smbios64BitTable; +} EFI_SMBIOS_ENTRY; + +typedef struct +{ + UINT8 Type; + UINT8 Length; + UINT16 Handle; +} SMBIOS_STRUCTURE; + +typedef struct +{ + SMBIOS_STRUCTURE Hdr; + UINT8 StringCount; +} SMBIOS_TABLE_TYPE11; + +typedef UINT8 EFI_SMBIOS_TYPE; +typedef UINT16 EFI_SMBIOS_HANDLE; +typedef SMBIOS_STRUCTURE EFI_SMBIOS_TABLE_HEADER; + +CHAR16 * +StrSplit( + CHAR16 *Str, + CHAR16 Delim) +{ + CHAR16 *StrPtr; + + StrPtr = Str; + if (StrPtr == NULL) + return NULL; + + while (*StrPtr != L'\0') + { + if ((CHAR16)(*StrPtr) == Delim) + return StrPtr + 1; + + StrPtr++; + } + + return NULL; +} + +EFI_STATUS +SetPlatformResolutions( + IN PLATFORM_CONFIG *PlatformConfig) +{ + EFI_STATUS Status; + EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput; + EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info; + UINTN SizeOfInfo, NumModes, ModeNum; + + Status = uefi_call_wrapper(BS->LocateProtocol, + 3, + &GraphicsOutputProtocol, + NULL, + (VOID **)&GraphicsOutput); + if (EFI_ERROR(Status)) + return Status; + + NumModes = GraphicsOutput->Mode->MaxMode; + for (ModeNum = 0; ModeNum < NumModes; ModeNum++) + { + Status = uefi_call_wrapper(GraphicsOutput->QueryMode, + 4, + GraphicsOutput, + ModeNum, + &SizeOfInfo, + &Info); + if (EFI_ERROR(Status)) + continue; + + // + // Make sure the provided resolution is one of the supported ones, + // before setting it. + // + if (PlatformConfig->HorizontalResolution == Info->HorizontalResolution && + PlatformConfig->VerticalResolution == Info->VerticalResolution) + { + Status = uefi_call_wrapper( + RT->SetVariable, + 5, + VarPlatformConfig, + &gOvmfPlatformConfigGuid, + EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, + sizeof *PlatformConfig, + PlatformConfig); + if (EFI_ERROR(Status)) + return Status; + + return EFI_SUCCESS; + } + } + + return EFI_NOT_FOUND; +} + +EFI_STATUS +SetNextBootOption( + IN UINT16 BootOptionNumber) +{ + EFI_STATUS Status; + + Status = uefi_call_wrapper( + RT->SetVariable, + 5, + VarBootNext, + &gEfiGlobalVariableGuid, + EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, + sizeof(BootOptionNumber), + &BootOptionNumber); + if (EFI_ERROR(Status)) + return Status; + + return EFI_SUCCESS; +} + +EFI_STATUS +ResetSystem( + EFI_RESET_TYPE ResetType) +{ + EFI_STATUS Status; + + Status = uefi_call_wrapper( + RT->ResetSystem, + 4, + ResetType, + EFI_SUCCESS, + 0, + NULL); + if (EFI_ERROR(Status)) + return Status; + + // + // We should never reach here. + // + return EFI_SUCCESS; +} + +EFI_STATUS +Sleep( + UINTN Seconds) +{ + return uefi_call_wrapper(BS->Stall, 1, SEC_TO_USEC(Seconds)); +} + +EFI_STATUS +GetBootOrder( + VOID) +{ + EFI_STATUS Status; + + gBootOrderSize = 0; + Status = uefi_call_wrapper( + RT->GetVariable, + 5, + VarBootOrder, + &gEfiGlobalVariableGuid, + NULL, + &gBootOrderSize, + NULL); + if (Status != EFI_BUFFER_TOO_SMALL) + return Status; + + gBootOrder = AllocateZeroPool(gBootOrderSize); + if (!gBootOrder) + return EFI_OUT_OF_RESOURCES; + + Status = uefi_call_wrapper( + RT->GetVariable, + 5, + VarBootOrder, + &gEfiGlobalVariableGuid, + NULL, + &gBootOrderSize, + gBootOrder); + if (EFI_ERROR(Status)) + { + FreePool(gBootOrder); + return Status; + } + + return EFI_SUCCESS; +} + +EFI_STATUS +IsValidBootOption( + UINT16 BootCurrent, + BOOLEAN *IsValid) +{ + EFI_STATUS Status; + UINTN Size = 0; + UINT8 *Data = NULL; + UINT8 *DataPtr = NULL; + CHAR16 *Description; + CHAR16 BootOptionName[32]; + *IsValid = TRUE; + + UnicodeSPrint(BootOptionName, + sizeof(BootOptionName) / sizeof(CHAR16), + L"Boot%04x", + BootCurrent); + + Status = uefi_call_wrapper( + RT->GetVariable, + 5, + BootOptionName, + &gEfiGlobalVariableGuid, + NULL, + &Size, + NULL); + if (Status != EFI_BUFFER_TOO_SMALL) + return Status; + + Data = AllocateZeroPool(Size); + if (!Data) + return EFI_OUT_OF_RESOURCES; + + Status = uefi_call_wrapper( + RT->GetVariable, + 5, + BootOptionName, + &gEfiGlobalVariableGuid, + NULL, + &Size, + Data); + if (EFI_ERROR(Status)) + { + FreePool(Data); + return Status; + } + + // + // Skip the attribute and pathsize and get to the description. + // + DataPtr = Data; + DataPtr += sizeof(UINT32) + sizeof(UINT16); + Description = (CHAR16 *)DataPtr; + + // + // UiApp or HTTP or PXE are not valid boot options. + // + if (MetaiMatch(Description, L"*UiApp*") || + MetaiMatch(Description, L"*HTTP*") || + MetaiMatch(Description, L"*PXE*")) + *IsValid = FALSE; + + FreePool(Data); + return EFI_SUCCESS; +} + +EFI_STATUS +GetLastTriedBootOption( + UINT16 *LastTriedBootOption) +{ + UINTN Size = sizeof(UINT16); + EFI_STATUS Status; + + Status = uefi_call_wrapper( + RT->GetVariable, + 5, + VarLastTriedBootIndex, + &gEfiCustomNextBootIndexGuid, + NULL, + &Size, + LastTriedBootOption); + + return Status; +} + +EFI_STATUS +SetLastTriedBootOption( + UINT16 LastTriedBootOption) +{ + UINTN Size = sizeof(UINT16); + EFI_STATUS Status; + + Status = uefi_call_wrapper( + RT->SetVariable, + 5, + VarLastTriedBootIndex, + &gEfiCustomNextBootIndexGuid, + EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS, + Size, + &LastTriedBootOption); + + return Status; +} + +EFI_STATUS +TryNextBoot( + VOID) +{ + EFI_STATUS Status; + UINT16 LastTriedBootOption, NextBoot = 0; + BOOLEAN IsValid = FALSE; + + Status = GetLastTriedBootOption(&LastTriedBootOption); + if (EFI_ERROR(Status)) + { + if (Status != EFI_NOT_FOUND) + { + return Status; + } + else + { + // + // Create and initialize the LastTriedBootIndex variable. + // + Status = SetLastTriedBootOption(0); + if (EFI_ERROR(Status)) + return Status; + } + } + + for (UINTN i = LastTriedBootOption + 1; i < gBootOrderSize / sizeof(UINT16); i++) + { + Status = IsValidBootOption(gBootOrder[i], &IsValid); + if (EFI_ERROR(Status)) + return Status; + + if (IsValid) + { + NextBoot = gBootOrder[i]; + LastTriedBootOption = i; + break; + } + } + + // + // No valid boot option left to try. + // + if (!IsValid) + return EFI_NOT_FOUND; + + Status = SetNextBootOption(NextBoot); + if (EFI_ERROR(Status)) + return Status; + + // TOOD: Debug remove + DbgPrint(D_ERROR, (CHAR8 *)"===== Booting to : %d\n", NextBoot); + + Status = SetLastTriedBootOption(LastTriedBootOption); + return Status; +} + + +// +// Calling smbios->GetNext() results in either crash or hang, +// so this one is more or less copy-pasted from EDKII's GetNext. +// +EFI_STATUS +SmbiosGetNext( + CONST EFI_SMBIOS_PROTOCOL *This, + OUT EFI_SMBIOS_HANDLE *SmbiosHandle, + EFI_SMBIOS_TABLE_HEADER **Record) +{ + BOOLEAN StartPointFound; + LIST_ENTRY *Link; + LIST_ENTRY *Head; + SMBIOS_INSTANCE *Private; + EFI_SMBIOS_ENTRY *SmbiosEntry; + EFI_SMBIOS_TABLE_HEADER *SmbiosTableHeader; + + if (SmbiosHandle == NULL) + return EFI_INVALID_PARAMETER; + + StartPointFound = FALSE; + Private = SMBIOS_INSTANCE_FROM_THIS(This); + Head = &Private->DataListHead; + for (Link = Head->Flink; Link != Head; Link = Link->Flink) + { + SmbiosEntry = SMBIOS_ENTRY_FROM_LINK(Link); + SmbiosTableHeader = (EFI_SMBIOS_TABLE_HEADER *)(SmbiosEntry->RecordHeader + 1); + + // + // If SmbiosHandle is 0xFFFE, the first matched SMBIOS record handle will be returned + // + if (*SmbiosHandle == SMBIOS_HANDLE_PI_RESERVED) + { + *SmbiosHandle = SmbiosTableHeader->Handle; + *Record = SmbiosTableHeader; + return EFI_SUCCESS; + } + + // + // Start this round search from the next SMBIOS handle + // + if (!StartPointFound && (*SmbiosHandle == SmbiosTableHeader->Handle)) + { + StartPointFound = TRUE; + continue; + } + + if (StartPointFound) + { + *SmbiosHandle = SmbiosTableHeader->Handle; + *Record = SmbiosTableHeader; + return EFI_SUCCESS; + } + } + + *SmbiosHandle = SMBIOS_HANDLE_PI_RESERVED; + return EFI_NOT_FOUND; +} + +CHAR8 * +GetSmbiosString( + SMBIOS_TABLE_TYPE11 *Record, + UINT8 Index) +{ + // + // The string arrays starts after the SMBIOS_TABLE_TYPE11 structure and + // strings are placed one after another with a NULL terminator, so we + // start by getting the first one and then move to the next one. + // + CHAR8 *StringArea = (CHAR8 *)(&Record->StringCount) + sizeof(Record->StringCount); + for (UINT8 i = 0; i < Index; i++) + StringArea += strlena(StringArea) + 1; + + return StringArea; +} + +EFI_STATUS +GetSmbiosConfigString( + CHAR16 *ConfigName, + CHAR16 **ConfigValue) +{ + EFI_STATUS Status; + EFI_SMBIOS_PROTOCOL *Smbios; + SMBIOS_STRUCTURE *Record; + SMBIOS_TABLE_TYPE11 *Type11Record; + UINT16 Handle; + *ConfigValue = NULL; + + Status = uefi_call_wrapper(BS->LocateProtocol, + 3, + &gEfiSmbiosProtocolGuid, + NULL, + (VOID **)&Smbios); + if (EFI_ERROR(Status)) + return Status; + + Handle = SMBIOS_HANDLE_PI_RESERVED; + while (SmbiosGetNext(Smbios, &Handle, &Record) != EFI_NOT_FOUND) + { + // + // We are only interested in OEM Strings. + // + if (Record->Type != SMBIOS_TYPE_OEM_STRINGS) + continue; + + // + // Interpret Record as SMBIOS_TABLE_TYPE11, we can still access the + // SMBIOS_STRUCTURE as it is part of the SMBIOS_TABLE_TYPE11. + // + Type11Record = (SMBIOS_TABLE_TYPE11 *)Record; + for (UINT8 i = 0; i < Type11Record->StringCount; i++) + { + CHAR8 *AsciiOemString = GetSmbiosString(Type11Record, i); + CHAR16 *UnicodeOemString = PoolPrint(L"%a", AsciiOemString); + if (MetaiMatch(UnicodeOemString, ConfigName)) + { + *ConfigValue = StrSplit(UnicodeOemString, CONFIG_DELIMITER); + if (*ConfigValue != NULL) + *ConfigValue = PoolPrint(L"%s", *ConfigValue); + + FreePool(UnicodeOemString); + return (*ConfigValue != NULL) ? EFI_SUCCESS : EFI_NOT_FOUND; + } + + FreePool(UnicodeOemString); + } + } + + return EFI_NOT_FOUND; +} + +EFI_STATUS +GetResolutionConfig( + UINT32 *HorizontalResolution, + UINT32 *VerticalResolution) +{ + EFI_STATUS Status; + CHAR16 *ConfigValue; + CHAR16 *Horizontal; + CHAR16 *Vertical; + + Status = GetSmbiosConfigString(EVE_FML_RESOUTION, &ConfigValue); + if (EFI_ERROR(Status)) + return Status; + + Vertical = StrSplit(ConfigValue, L'x'); + if (Vertical == NULL) + { + Status = EFI_NOT_FOUND; + goto _Return; + } + *VerticalResolution = Atoi(Vertical); + + ConfigValue[Vertical - ConfigValue - 1] = L'\0'; + Horizontal = ConfigValue; + if (*Horizontal == L'\0') + { + Status = EFI_NOT_FOUND; + goto _Return; + } + *HorizontalResolution = Atoi(ConfigValue); + +_Return: + FreePool(ConfigValue); + return EFI_SUCCESS; +} + +EFI_STATUS +GetBootConfig( + BOOLEAN *TryBootOptios) +{ + EFI_STATUS Status; + CHAR16 *ConfigValue; + *TryBootOptios = FALSE; + + Status = GetSmbiosConfigString(EVE_TRY_ALL_BOOT, &ConfigValue); + if (EFI_ERROR(Status)) + return Status; + + if (StriCmp(ConfigValue, L"true") == 0) + *TryBootOptios = TRUE; + + FreePool(ConfigValue); + return EFI_SUCCESS; +} + +EFI_STATUS +EFIAPI +efi_main( + EFI_HANDLE ImageHandle, + EFI_SYSTEM_TABLE *SystemTable) +{ + EFI_STATUS Status; + PLATFORM_CONFIG PlatformConfig = {0}; + BOOLEAN TryAllBootOptions = FALSE; + + InitializeLib(ImageHandle, SystemTable); + + // + // If config is present, set the resolution. + // + Status = GetResolutionConfig( + &PlatformConfig.HorizontalResolution, + &PlatformConfig.VerticalResolution); + if (!EFI_ERROR(Status)) + { + Status = SetPlatformResolutions(&PlatformConfig); + if (EFI_ERROR(Status)) + { + DbgPrint(D_ERROR, (CHAR8 *)"Error: Unable to set resolutions, Status : %r\n", Status); + + // + // shutdown the system to avoid booting to a wrong resolution and signal + // the usser that something went wrong. + // + ResetSystem(EfiResetShutdown); + } + } + + Status = GetBootConfig(&TryAllBootOptions); + if (!EFI_ERROR(Status)) + { + // + // If the boot-brute-force is not enabled, we are done here. + // + if (!TryAllBootOptions) + return EFI_SUCCESS; + + Status = GetBootOrder(); + if (EFI_ERROR(Status)) + { + DbgPrint(D_ERROR, (CHAR8 *)"Error: Unable to save BootOrder, Status : %r\n", Status); + ResetSystem(EfiResetShutdown); + } + + Status = TryNextBoot(); + if (EFI_ERROR(Status)) + { + DbgPrint(D_ERROR, (CHAR8 *)"Error: Unable to set next boot, Status : %r\n", Status); + + // + // We have tried all the boot options and none booted successfully, + // we are going to shutdown the system, so reset the LastTriedBootIndex + // to give the next explicit power on a chance to try again. + // + SetLastTriedBootOption(0); + ResetSystem(EfiResetShutdown); + } + + // + // Trigger a reset to apply BootNext + // + ResetSystem(EfiResetCold); + } + + return EFI_SUCCESS; +} \ No newline at end of file diff --git a/pkg/dom0-ztools/efi/startup.nsh b/pkg/dom0-ztools/efi/startup.nsh new file mode 100644 index 0000000000..2c6331d110 --- /dev/null +++ b/pkg/dom0-ztools/efi/startup.nsh @@ -0,0 +1,3 @@ +fs0: +efi_eveutil.efi +reset -s \ No newline at end of file From b460fe613bdb21b8aa194c02d7162870c6a082b4 Mon Sep 17 00:00:00 2001 From: Shahriyar Jalayeri Date: Fri, 4 Oct 2024 18:43:20 +0300 Subject: [PATCH 2/2] make it work Signed-off-by: Shahriyar Jalayeri --- pkg/dom0-ztools/efi/Makefile | 2 +- pkg/dom0-ztools/efi/efi_run.sh | 2 +- pkg/dom0-ztools/efi/main.c | 4 +- pkg/dom0-ztools/efi/startup.nsh | 2 +- pkg/pillar/cmd/domainmgr/domainmgr.go | 22 ++++- pkg/pillar/hypervisor/containerd.go | 5 +- pkg/pillar/hypervisor/kubevirt.go | 5 +- pkg/pillar/hypervisor/kvm.go | 129 ++++++++++++++++++++------ pkg/pillar/hypervisor/null.go | 6 +- pkg/pillar/hypervisor/xen.go | 6 +- pkg/pillar/types/domainmgrtypes.go | 16 +++- 11 files changed, 146 insertions(+), 53 deletions(-) diff --git a/pkg/dom0-ztools/efi/Makefile b/pkg/dom0-ztools/efi/Makefile index 13e6ce4c08..af3506ce07 100644 --- a/pkg/dom0-ztools/efi/Makefile +++ b/pkg/dom0-ztools/efi/Makefile @@ -29,4 +29,4 @@ clean: %.efi: %.so objcopy -j .text -j .sdata -j .data -j .dynamic \ -j .dynsym -j .rel -j .rela -j .reloc \ - --target=efi-app-$(ARCH) $^ $@ \ No newline at end of file + --target=efi-app-$(ARCH) $^ $@ diff --git a/pkg/dom0-ztools/efi/efi_run.sh b/pkg/dom0-ztools/efi/efi_run.sh index 67a35ab288..a5a62131a4 100644 --- a/pkg/dom0-ztools/efi/efi_run.sh +++ b/pkg/dom0-ztools/efi/efi_run.sh @@ -8,4 +8,4 @@ cp /hostfs/etc/ovmf/eve_efi.img /run/kvm/eve_efi.img -drive "file=/run/kvm/eve_efi.img,format=raw" \ -smbios "type=11,value=${2}" \ -net none \ - -nographic \ No newline at end of file + -nographic diff --git a/pkg/dom0-ztools/efi/main.c b/pkg/dom0-ztools/efi/main.c index 48cf023e58..672895410f 100644 --- a/pkg/dom0-ztools/efi/main.c +++ b/pkg/dom0-ztools/efi/main.c @@ -602,7 +602,7 @@ GetResolutionConfig( _Return: FreePool(ConfigValue); - return EFI_SUCCESS; + return Status; } EFI_STATUS @@ -694,4 +694,4 @@ efi_main( } return EFI_SUCCESS; -} \ No newline at end of file +} diff --git a/pkg/dom0-ztools/efi/startup.nsh b/pkg/dom0-ztools/efi/startup.nsh index 2c6331d110..b46a0bd962 100644 --- a/pkg/dom0-ztools/efi/startup.nsh +++ b/pkg/dom0-ztools/efi/startup.nsh @@ -1,3 +1,3 @@ fs0: efi_eveutil.efi -reset -s \ No newline at end of file +reset -s diff --git a/pkg/pillar/cmd/domainmgr/domainmgr.go b/pkg/pillar/cmd/domainmgr/domainmgr.go index fcfabd3e86..93fed3c28d 100644 --- a/pkg/pillar/cmd/domainmgr/domainmgr.go +++ b/pkg/pillar/cmd/domainmgr/domainmgr.go @@ -1119,14 +1119,21 @@ func maybeRetryBoot(ctx *domainContext, status *types.DomainStatus) { } defer file.Close() - if err := hyper.Task(status).VirtualTPMSetup(status.DomainName, agentName, ctx.ps, warningTime, errorTime); err != nil { + extra := types.ExtraArgs{ + AgentName: agentName, + Ps: ctx.ps, + WarnTime: warningTime, + ErrTime: errorTime, + } + + if err := hyper.Task(status).VirtualTPMSetup(status.DomainName, &extra); err != nil { log.Errorf("Failed to setup virtual TPM for %s: %s", status.DomainName, err) status.VirtualTPM = false } else { status.VirtualTPM = true } - if err := hyper.Task(status).Setup(*status, *config, ctx.assignableAdapters, nil, file); err != nil { + if err := hyper.Task(status).Setup(*status, *config, ctx.assignableAdapters, nil, file, &extra); err != nil { //it is retry, so omit error log.Errorf("Failed to create DomainStatus from %+v: %s", config, err) @@ -1671,7 +1678,14 @@ func doActivate(ctx *domainContext, config types.DomainConfig, } defer file.Close() - if err := hyper.Task(status).VirtualTPMSetup(status.DomainName, agentName, ctx.ps, warningTime, errorTime); err != nil { + extra := types.ExtraArgs{ + AgentName: agentName, + Ps: ctx.ps, + WarnTime: warningTime, + ErrTime: errorTime, + } + + if err := hyper.Task(status).VirtualTPMSetup(status.DomainName, &extra); err != nil { log.Errorf("Failed to setup virtual TPM for %s: %s", status.DomainName, err) status.VirtualTPM = false } else { @@ -1679,7 +1693,7 @@ func doActivate(ctx *domainContext, config types.DomainConfig, } globalConfig := agentlog.GetGlobalConfig(log, ctx.subGlobalConfig) - if err := hyper.Task(status).Setup(*status, config, ctx.assignableAdapters, globalConfig, file); err != nil { + if err := hyper.Task(status).Setup(*status, config, ctx.assignableAdapters, globalConfig, file, &extra); err != nil { log.Errorf("Failed to create DomainStatus from %+v: %s", config, err) status.SetErrorNow(err.Error()) diff --git a/pkg/pillar/hypervisor/containerd.go b/pkg/pillar/hypervisor/containerd.go index fc8fbcc7c2..e039e24323 100644 --- a/pkg/pillar/hypervisor/containerd.go +++ b/pkg/pillar/hypervisor/containerd.go @@ -11,7 +11,6 @@ import ( "time" "github.com/lf-edge/eve/pkg/pillar/containerd" - "github.com/lf-edge/eve/pkg/pillar/pubsub" "github.com/lf-edge/eve/pkg/pillar/types" "github.com/opencontainers/runtime-spec/specs-go" @@ -101,7 +100,7 @@ func (ctx ctrdContext) setupSpec(status *types.DomainStatus, config *types.Domai } func (ctx ctrdContext) Setup(status types.DomainStatus, config types.DomainConfig, - aa *types.AssignableAdapters, globalConfig *types.ConfigItemValueMap, file *os.File) error { + aa *types.AssignableAdapters, globalConfig *types.ConfigItemValueMap, file *os.File, extra *types.ExtraArgs) error { if status.OCIConfigDir == "" { return logError("failed to run domain %s: not based on an OCI image", status.DomainName) } @@ -327,7 +326,7 @@ func (ctx ctrdContext) GetDomsCPUMem() (map[string]types.DomainMetric, error) { return res, nil } -func (ctx ctrdContext) VirtualTPMSetup(domainName, agentName string, ps *pubsub.PubSub, warnTime, errTime time.Duration) error { +func (ctx ctrdContext) VirtualTPMSetup(domainName string, extra *types.ExtraArgs) error { return fmt.Errorf("not implemented") } diff --git a/pkg/pillar/hypervisor/kubevirt.go b/pkg/pillar/hypervisor/kubevirt.go index 4edb6bc5fb..f5de054f16 100644 --- a/pkg/pillar/hypervisor/kubevirt.go +++ b/pkg/pillar/hypervisor/kubevirt.go @@ -22,7 +22,6 @@ import ( "time" "github.com/lf-edge/eve/pkg/pillar/base" - "github.com/lf-edge/eve/pkg/pillar/pubsub" "github.com/lf-edge/eve/pkg/pillar/types" netattdefv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" @@ -185,7 +184,7 @@ func (ctx kubevirtContext) Task(status *types.DomainStatus) types.Task { // Use eve DomainConfig and DomainStatus and generate k3s VMI config or a Pod config func (ctx kubevirtContext) Setup(status types.DomainStatus, config types.DomainConfig, - aa *types.AssignableAdapters, globalConfig *types.ConfigItemValueMap, file *os.File) error { + aa *types.AssignableAdapters, globalConfig *types.ConfigItemValueMap, file *os.File, extra *types.ExtraArgs) error { diskStatusList := status.DiskStatusList domainName := status.DomainName @@ -1362,7 +1361,7 @@ func (ctx kubevirtContext) PCISameController(id1 string, id2 string) bool { return PCISameControllerGeneric(id1, id2) } -func (ctx kubevirtContext) VirtualTPMSetup(domainName, agentName string, ps *pubsub.PubSub, warnTime, errTime time.Duration) error { +func (ctx kubevirtContext) VirtualTPMSetup(domainName string, extra *types.ExtraArgs) error { return fmt.Errorf("not implemented") } diff --git a/pkg/pillar/hypervisor/kvm.go b/pkg/pillar/hypervisor/kvm.go index 8c4483fd15..c5286f3b08 100644 --- a/pkg/pillar/hypervisor/kvm.go +++ b/pkg/pillar/hypervisor/kvm.go @@ -4,9 +4,11 @@ package hypervisor import ( + "bytes" "fmt" "net" "os" + "os/exec" "path/filepath" "runtime" "strings" @@ -17,7 +19,6 @@ import ( zconfig "github.com/lf-edge/eve-api/go/config" "github.com/lf-edge/eve/pkg/pillar/agentlog" "github.com/lf-edge/eve/pkg/pillar/containerd" - "github.com/lf-edge/eve/pkg/pillar/pubsub" "github.com/lf-edge/eve/pkg/pillar/types" "github.com/lf-edge/eve/pkg/pillar/utils" fileutils "github.com/lf-edge/eve/pkg/pillar/utils/file" @@ -738,35 +739,18 @@ func getOVMFSettingsFilename(domainName string) (string, error) { return types.OVMFSettingsDir + "/" + domainUUID.String() + "_OVMF_VARS.fd", nil } -func prepareOVMFSettings(config types.DomainConfig, status types.DomainStatus, globalConfig *types.ConfigItemValueMap) error { +func prepareOVMFSettings(status types.DomainStatus) error { // Create the OVMF settings directory if it does not exist if err := os.MkdirAll(types.OVMFSettingsDir, 0755); err != nil { return logError("failed to create OVMF settings directory: %v", err) } - // Create a copy of the ovmf_vars.bin file in _ovmf_vars.bin + // Get the path for a copy of the ovmf_vars.bin file form of _OVMF_VARS.fd ovmfSettingsFile, err := getOVMFSettingsFilename(status.DomainName) if err != nil { return logError("failed to get OVMF settings file: %v", err) } - // Check if we need custom OVMF settings for the domain (the resolution) - fmlResolution := types.FmlResolutionUnset - if config.VirtualizationMode == types.FML { - // if we are not getting the resolution from the cloud-init, check the - // global config. - fmlResolution = status.FmlCustomResolution - if fmlResolution == types.FmlResolutionUnset { - if fmlResolution, err = getFmlCustomResolution(&status, globalConfig); err != nil { - return logError("failed to get custom resolution for domain %s: %v", status.DomainName, err) - } - } - } - // Find the necessary OVMF settings file - ovmfSettingsFileSrc := types.OVMFSettingsTemplate - if fmlResolution != types.FmlResolutionUnset { - ovmfSettingsFileSrc = types.CustomOVMFSettingsDir + "/OVMF_VARS_" + fmlResolution + ".fd" - } if _, err := os.Stat(ovmfSettingsFile); os.IsNotExist(err) { - if err := fileutils.CopyFile(ovmfSettingsFileSrc, ovmfSettingsFile); err != nil { + if err := fileutils.CopyFile(types.OVMFSettingsTemplate, ovmfSettingsFile); err != nil { return logError("failed to copy OVMF_VARS file: %v", err) } } @@ -790,7 +774,7 @@ func cleanupOVMFSettings(domainName string) error { // Setup sets up kvm func (ctx KvmContext) Setup(status types.DomainStatus, config types.DomainConfig, - aa *types.AssignableAdapters, globalConfig *types.ConfigItemValueMap, file *os.File) error { + aa *types.AssignableAdapters, globalConfig *types.ConfigItemValueMap, file *os.File, extra *types.ExtraArgs) error { diskStatusList := status.DiskStatusList domainName := status.DomainName @@ -814,9 +798,29 @@ func (ctx KvmContext) Setup(status types.DomainStatus, config types.DomainConfig // for ARM produces a single QEMU_EFI.fd file that contains both OVMF_VARS.fd // and OVMF_CODE.fd. if config.VirtualizationMode == types.FML && runtime.GOARCH == "amd64" { - if err := prepareOVMFSettings(config, status, globalConfig); err != nil { + // Copy the OVMF_VARS file and the eve_efi.img file to the domain directory + err := prepareOVMFSettings(status) + if err != nil { return logError("failed to setup OVMF settings for domain %s: %v", status.DomainName, err) } + + fmlResolution := status.FmlCustomResolution + if fmlResolution == types.FmlResolutionUnset { + if fmlResolution, err = getFmlCustomResolution(&status, globalConfig); err != nil { + return logError("failed to get custom resolution for domain %s: %v", status.DomainName, err) + } + } + if fmlResolution != types.FmlResolutionUnset { + cfg := []string{ + "eve.fml.resolution:" + fmlResolution, + } + + logError("setOvmfVariables before call") + err := ctx.setOvmfVariables(cfg, status.DomainName, extra) + if err != nil { + return logError("failed to set OVMF variables for domain %s: %v", status.DomainName, err) + } + } } // first lets build the domain config @@ -846,6 +850,14 @@ func (ctx KvmContext) Setup(status types.DomainStatus, config types.DomainConfig "-readconfig", file.Name(), "-pidfile", kvmStateDir+domainName+"/pid") + if config.VirtualizationMode == types.FML && runtime.GOARCH == "amd64" { + args = append(args, "-drive", "file=/run/kvm/eve_efi.img,format=raw,if=none,id=drive-eve-efi") + args = append(args, "-device", "virtio-blk-pci,drive=drive-eve-efi") + args = append(args, "-smbios", "type=11,value=eve.try.all.boot.options:true") + } + + logError("guest args: %v", args) + spec, err := ctx.setupSpec(&status, &config, status.OCIConfigDir) if err != nil { @@ -1330,9 +1342,9 @@ func getQmpListenerSocket(domainName string) string { } // VirtualTPMSetup launches a vTPM instance for the domain -func (ctx KvmContext) VirtualTPMSetup(domainName, agentName string, ps *pubsub.PubSub, warnTime, errTime time.Duration) error { - if ps != nil { - wk := utils.NewWatchdogKick(ps, agentName, warnTime, errTime) +func (ctx KvmContext) VirtualTPMSetup(domainName string, extra *types.ExtraArgs) error { + if extra.Ps != nil { + wk := utils.NewWatchdogKick(extra.Ps, extra.AgentName, extra.WarnTime, extra.ErrTime) domainUUID, _, _, err := types.DomainnameToUUID(domainName) if err != nil { return fmt.Errorf("failed to extract UUID from domain name (vTPM setup): %v", err) @@ -1429,3 +1441,68 @@ func requestVtpmTermination(id uuid.UUID) error { return nil } + +// This is janky and hacky, but it is OK for testing +func (ctx KvmContext) setOvmfVariables(config []string, domainName string, extra *types.ExtraArgs) error { + cmd := exec.Command("/hostfs/usr/bin/ctr", "--namespace", "services.linuxkit", "t", "ls") + var out bytes.Buffer + cmd.Stdout = &out + if err := cmd.Run(); err != nil { + return logError("setOvmfVariables error running ctr: %s\n", err) + } + output := out.String() + lines := strings.Split(output, "\n") + var xenToolsPID string + for _, line := range lines { + columns := strings.Fields(line) + if len(columns) > 2 && columns[0] == "xen-tools" { + xenToolsPID = columns[1] + break + } + } + if xenToolsPID != "" { + logError("setOvmfVariables The PID of xen-tools is: %s\n", xenToolsPID) + } else { + return logError("setOvmfVariables xen-tools not found.") + } + + ovmfSettingsFile, err := getOVMFSettingsFilename(domainName) + if err != nil { + return logError("setOvmfVariables failed to get OVMF settings file: %v", err) + } + + args := []string{"-F", "-a", "-t", xenToolsPID, + "/hostfs/etc/ovmf/efi_run.sh", + ovmfSettingsFile, + "eve.fml.resolution:1920x1080", + } + + logError("setOvmfVariables args: nsenter %v", args) + cmd = exec.Command("nsenter", args...) + if err := cmd.Start(); err != nil { + return logError("setOvmfVariables error starting nsenter: %s\n", err) + } + done := make(chan error, 1) + go func() { + done <- cmd.Wait() + }() + +waitLoop: + for { + select { + case err := <-done: + if err != nil { + logError("setOvmfVariables Process exited with error: %s\n", err) + } else { + logError("setOvmfVariables Process completed successfully.") + } + break waitLoop + default: + extra.Ps.StillRunning(extra.AgentName, extra.WarnTime, extra.ErrTime) + logError("setOvmfVariables still waiting.") + time.Sleep(1 * time.Second) + } + } + + return nil +} diff --git a/pkg/pillar/hypervisor/null.go b/pkg/pillar/hypervisor/null.go index 899c35ed27..c351986c35 100644 --- a/pkg/pillar/hypervisor/null.go +++ b/pkg/pillar/hypervisor/null.go @@ -6,9 +6,7 @@ package hypervisor import ( "fmt" "os" - "time" - "github.com/lf-edge/eve/pkg/pillar/pubsub" "github.com/lf-edge/eve/pkg/pillar/types" uuid "github.com/satori/go.uuid" @@ -69,7 +67,7 @@ func (ctx nullContext) Task(status *types.DomainStatus) types.Task { } func (ctx nullContext) Setup(types.DomainStatus, types.DomainConfig, - *types.AssignableAdapters, *types.ConfigItemValueMap, *os.File) error { + *types.AssignableAdapters, *types.ConfigItemValueMap, *os.File, *types.ExtraArgs) error { return nil } @@ -174,7 +172,7 @@ func (ctx nullContext) GetDomsCPUMem() (map[string]types.DomainMetric, error) { return nil, nil } -func (ctx nullContext) VirtualTPMSetup(domainName, agentName string, ps *pubsub.PubSub, warnTime, errTime time.Duration) error { +func (ctx nullContext) VirtualTPMSetup(domainName string, extra *types.ExtraArgs) error { return fmt.Errorf("not implemented") } diff --git a/pkg/pillar/hypervisor/xen.go b/pkg/pillar/hypervisor/xen.go index 729f0d7432..394d180c26 100644 --- a/pkg/pillar/hypervisor/xen.go +++ b/pkg/pillar/hypervisor/xen.go @@ -11,9 +11,7 @@ import ( "runtime" "strconv" "strings" - "time" - "github.com/lf-edge/eve/pkg/pillar/pubsub" "github.com/lf-edge/eve/pkg/pillar/types" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/mem" @@ -121,7 +119,7 @@ func (ctx xenContext) Task(status *types.DomainStatus) types.Task { } func (ctx xenContext) Setup(status types.DomainStatus, config types.DomainConfig, - aa *types.AssignableAdapters, globalConfig *types.ConfigItemValueMap, file *os.File) error { + aa *types.AssignableAdapters, globalConfig *types.ConfigItemValueMap, file *os.File, extra *types.ExtraArgs) error { // first lets build the domain config if err := ctx.CreateDomConfig(status.DomainName, config, status.DiskStatusList, aa, globalConfig, file); err != nil { @@ -877,7 +875,7 @@ func fallbackDomainMetric() map[string]types.DomainMetric { return dmList } -func (ctx xenContext) VirtualTPMSetup(domainName, agentName string, ps *pubsub.PubSub, warnTime, errTime time.Duration) error { +func (ctx xenContext) VirtualTPMSetup(domainName string, extra *types.ExtraArgs) error { return fmt.Errorf("not implemented") } diff --git a/pkg/pillar/types/domainmgrtypes.go b/pkg/pillar/types/domainmgrtypes.go index 3e7bd762cc..4937f0b3d5 100644 --- a/pkg/pillar/types/domainmgrtypes.go +++ b/pkg/pillar/types/domainmgrtypes.go @@ -261,6 +261,14 @@ type VmConfig struct { EnableVncShimVM bool } +// ExtraArgs contains the extra arguments for the domain +type ExtraArgs struct { + AgentName string + Ps *pubsub.PubSub + WarnTime time.Duration + ErrTime time.Duration +} + // VmMode is the type for the virtualization mode type VmMode uint8 @@ -276,10 +284,10 @@ const ( // Task represents any runnable entity on EVE type Task interface { Setup(DomainStatus, DomainConfig, *AssignableAdapters, - *ConfigItemValueMap, *os.File) error - VirtualTPMSetup(domainName, agentName string, ps *pubsub.PubSub, warnTime, errTime time.Duration) error - VirtualTPMTerminate(domainName string) error - VirtualTPMTeardown(domainName string) error + *ConfigItemValueMap, *os.File, *ExtraArgs) error + VirtualTPMSetup(string, *ExtraArgs) error + VirtualTPMTerminate(string) error + VirtualTPMTeardown(string) error Create(string, string, *DomainConfig) (int, error) Start(string) error Stop(string, bool) error