大多数 UEFI 协议被报告为 "unsupported"

Most UEFI protocols are reported as "unsupported"

我正在 SoC 设备(Up Board)上编写 EFI 可执行文件,以帮助我们自动执行 BIOS 更新和 PXE 启动,以便在众多设备上安装我们的软件。

我遇到的问题是,似乎规范中的大多数协议都在这个平台上 "unsupported",甚至是基本的文件系统任务。我唯一成功使用的是 LOADED_IMAGE_PROTOCOL。我正在使用 gnu-efi 编译代码,将我的代码基于此示例 https://mjg59.dreamwidth.org/18773.html。是我做错了什么,还是固件完全没有实现任何协议?

American Megatrends 实用程序 "AfuEfix64.efi" 能够检索 BIOS 信息,SoC BIOS 更新是使用 Intel 的 EFI 可执行文件完成的。不幸的是,两者都是封闭源代码。我最初的想法是编写一个脚本来解析这些可执行文件的输出,但我认为 EFI shell 对此类任务没有太多功能,因此我继续编写 EFI 可执行文件以执行此操作。

显示此内容的最少代码:

#include <efi.h>
#include <efilib.h>
#include <x86_64/efibind.h>

// gnu-efi does not currently define firmware management
// https://raw.githubusercontent.com/tianocore/edk2/master/MdePkg/Include/Protocol/FirmwareManagement.h
#include "efifirmware.h"

// gnu-efi does not currently define this
#define EFI_LOAD_FILE2_PROTOCOL_GUID \
{ 0x4006c0c1, 0xfcb3, 0x403e, \
{ 0x99, 0x6d, 0x4a, 0x6c, 0x87, 0x24, 0xe0, 0x6d }}
typedef EFI_LOAD_FILE_PROTOCOL EFI_LOAD_FILE2_PROTOCOL;

void tryProtocol(EFI_GUID proto_guid, void** out, const CHAR16* name,
    EFI_HANDLE imageHandle, EFI_SYSTEM_TABLE* systemTable) {

    EFI_STATUS status;
    status = uefi_call_wrapper(systemTable->BootServices->HandleProtocol, 3,
        imageHandle, &proto_guid, out);

    if (EFI_ERROR(status)) {
        Print(L"HandleProtocol error for %s: %r\n", name, status);
    } else {
        Print(L"Protocol %s is supported\n", name);
    }
}

void tryProtocols(EFI_HANDLE imgh, EFI_SYSTEM_TABLE* syst) {
    EFI_LOADED_IMAGE* loaded_image = NULL;
    EFI_GUID guid_imgprot = LOADED_IMAGE_PROTOCOL;
    tryProtocol(guid_imgprot, (void**)&loaded_image,
        L"LOADED_IMAGE_PROTOCOL", imgh, syst);

    Print(L"Image base: %lx\n", loaded_image->ImageBase);
    Print(L"Image size: %lx\n", loaded_image->ImageSize);
    Print(L"Image file: %s\n", DevicePathToStr(loaded_image->FilePath));

    EFI_FIRMWARE_MANAGEMENT_PROTOCOL* fw_manage = NULL;
    EFI_GUID guid_fwman_prot = EFI_FIRMWARE_MANAGEMENT_PROTOCOL_GUID;
    tryProtocol(guid_fwman_prot, (void**)&fw_manage,
        L"FIRMWARE_MANAGEMENT_PROTOCOL", imgh, syst);

    EFI_LOAD_FILE_PROTOCOL* load_file = NULL;
    EFI_GUID guid_loadf_prot = EFI_LOAD_FILE_PROTOCOL_GUID;
    tryProtocol(guid_loadf_prot, (void**)&load_file,
        L"LOAD_FILE_PROTOCOL", imgh, syst);

    EFI_LOAD_FILE2_PROTOCOL* load_file2 = NULL;
    EFI_GUID guid_loadf2_prot = EFI_LOAD_FILE2_PROTOCOL_GUID;
    tryProtocol(guid_loadf2_prot, (void**)&load_file2,
        L"LOAD_FILE2_PROTOCOL", imgh, syst);

    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* simple_fs = NULL;
    EFI_GUID guid_simple_fs_prot = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
    tryProtocol(guid_simple_fs_prot, (void**)&simple_fs,
        L"SIMPLE_FILE_SYSTEM_PROTOCOL", imgh, syst);

    EFI_DISK_IO_PROTOCOL* disk = NULL;
    EFI_GUID guid_disk_io_prot = EFI_DISK_IO_PROTOCOL_GUID;
    tryProtocol(guid_disk_io_prot, (void**)&disk,
        L"DISK_IO_PROTOCOL", imgh, syst);

    EFI_BLOCK_IO_PROTOCOL* block = NULL;
    EFI_GUID guid_block_io_prot = EFI_BLOCK_IO_PROTOCOL_GUID;
    tryProtocol(guid_block_io_prot, (void**)&block,
        L"BLOCK_IO_PROTOCOL", imgh, syst);
}

EFI_STATUS
EFIAPI
efi_main (EFI_HANDLE imageHandle, EFI_SYSTEM_TABLE* systemTable) {
    InitializeLib(imageHandle, systemTable);
    Print(L"Image loaded\n");

    tryProtocols(imageHandle, systemTable);

    return EFI_SUCCESS;
}

这是 运行 它的输出:

EFI Shell version 2.40 [5.11]
Current running mode 1.1.2
Device mapping table
  fs0  :HardDisk - Alias hd6b blk0
        PciRoot(0x0)/Pci(0x10,0x0)/Ctrl(0x0)/HD(1,GPT,2DCDDADD-8F3A-4A77-94A9-010A8C700BB8,0x800,0x100000)
  fs1  :Removable HardDisk - Alias hd9g0a0b blk1
        PciRoot(0x0)/Pci(0x14,0x0)/USB(0x6,0x0)/USB(0x0,0x0)/HD(1,MBR,0x528E6A1F,0x800,0x1CDE800)
  blk0 :HardDisk - Alias hd6b fs0
        PciRoot(0x0)/Pci(0x10,0x0)/Ctrl(0x0)/HD(1,GPT,2DCDDADD-8F3A-4A77-94A9-010A8C700BB8,0x800,0x100000)
  blk1 :Removable HardDisk - Alias hd9g0a0b fs1
        PciRoot(0x0)/Pci(0x14,0x0)/USB(0x6,0x0)/USB(0x0,0x0)/HD(1,MBR,0x528E6A1F,0x800,0x1CDE800)
  blk2 :HardDisk - Alias (null)
        PciRoot(0x0)/Pci(0x10,0x0)/Ctrl(0x0)/HD(2,GPT,8AC0F94E-3CA2-4C03-BE00-3A69721CC391,0x100800,0x1C1F7DF)
  blk3 :BlockDevice - Alias (null)
        PciRoot(0x0)/Pci(0x10,0x0)/Ctrl(0x0)
  blk4 :BlockDevice - Alias (null)
        PciRoot(0x0)/Pci(0x10,0x0)/Ctrl(0x1)
  blk5 :BlockDevice - Alias (null)
        PciRoot(0x0)/Pci(0x10,0x0)/Ctrl(0x2)
  blk6 :Removable BlockDevice - Alias (null)
        PciRoot(0x0)/Pci(0x14,0x0)/USB(0x6,0x0)/USB(0x0,0x0)

Press ESC in 4 seconds to skip startup.nsh, any other key to continue.

fs1:\> tryprotocols.efi
Image loaded
Protocol LOADED_IMAGE_PROTOCOL is supported
Image base: 55BA6000
Image size: F000
Image file: \/tryprotocols.efi
HandleProtocol error for FIRMWARE_MANAGEMENT_PROTOCOL: Unsupported
HandleProtocol error for LOAD_FILE_PROTOCOL: Unsupported
HandleProtocol error for LOAD_FILE2_PROTOCOL: Unsupported
HandleProtocol error for SIMPLE_FILE_SYSTEM_PROTOCOL: Unsupported
HandleProtocol error for DISK_IO_PROTOCOL: Unsupported
HandleProtocol error for BLOCK_IO_PROTOCOL: Unsupported

fs1:\>

这是我用来编译它的 Makefile:

ARCH=x86_64
OBJS=tryprotocols.o
TARGET=tryprotocols.efi
TARGET_SO=$(TARGET:.efi=.so)
GNUEFIDIR=/home/gekko/gnu-efi-3.0.8
EFIINC=$(GNUEFIDIR)/inc
EFIINCS=-I$(EFIINC) -I$(EFIINC)/$(ARCH) -I$(EFIINC)/protocol
LIB=$(GNUEFIDIR)/$(ARCH)/lib
EFILIB=$(GNUEFIDIR)/gnuefi
EFI_CRT_OBJS=$(GNUEFIDIR)/$(ARCH)/gnuefi/crt0-efi-$(ARCH).o
EFI_LDS=$(EFILIB)/elf_$(ARCH)_efi.lds

CFLAGS=$(EFIINCS) -fno-stack-protector -fpic \
    -fshort-wchar -mno-red-zone -Wall

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)

$(TARGET_SO): $(OBJS)
    ld $(LDFLAGS) $(OBJS) -o $@ -lefi -lgnuefi

%.efi: %.so
    objcopy -j .text -j .sdata -j .data -j .dynamic \
    -j .dynsym  -j .rel -j .rela -j .reloc \
    --target=efi-app-$(ARCH) $^ $@

clean:
    rm -f *.o
    rm -f $(TARGET)
    rm -f $(TARGET_SO)

编辑:正确使用 LocateProtocol() 查找协议并使用它们打开和关闭文件的修改版本。

#include <efi.h>
#include <efilib.h>
#include <x86_64/efibind.h>

BOOLEAN tryProtocol(EFI_GUID proto_guid, void** out, const CHAR16* name,
    EFI_HANDLE imageHandle, EFI_SYSTEM_TABLE* systemTable) {

    *out = NULL;
    EFI_STATUS status;
    EFI_HANDLE interface = NULL;

    status = uefi_call_wrapper(systemTable->BootServices->LocateProtocol, 3,
        &proto_guid, NULL, &interface);

    if (EFI_ERROR(status)) {
        Print(L"LocateProtocol error for %s: %r\n", name, status);
        return FALSE;
    }

    Print(L"Locate protocol address: %s, %x\n", name, interface);
    *out = interface;

    return TRUE;
}
EFI_STATUS
EFIAPI
efi_main (EFI_HANDLE imageHandle, EFI_SYSTEM_TABLE* systemTable) {
    InitializeLib(imageHandle, systemTable);
    Print(L"Image loaded\n");

    EFI_LOADED_IMAGE* loaded_image = NULL;
    EFI_GUID guid_imgprot = LOADED_IMAGE_PROTOCOL;

    if (tryProtocol(guid_imgprot, (void**)&loaded_image,
        L"LOADED_IMAGE_PROTOCOL", imageHandle, systemTable) != TRUE) {
        Print(L"Missing required protocol. Aborting\n");
        return EFI_SUCCESS;
    }

    Print(L"Image base: %lx\n", loaded_image->ImageBase);
    Print(L"Image size: %lx\n", loaded_image->ImageSize);
    Print(L"Image file: %s\n", DevicePathToStr(loaded_image->FilePath));

    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* simple_fs = NULL;
    EFI_GUID guid_simple_fs_prot = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;

    if (tryProtocol(guid_simple_fs_prot, (void**)&simple_fs,
        L"EFI_SIMPLE_FILE_SYSTEM_PROTOCOL", imageHandle, systemTable) != TRUE) {
        Print(L"Missing required protocol. Aborting\n");
        return EFI_SUCCESS;
    }

    EFI_FILE_PROTOCOL* vol_proto = NULL;
    EFI_STATUS status;

    Print(L"dereffing\n");
    Print(L"address of OpenVolume: %x\n", simple_fs->OpenVolume);

    status = uefi_call_wrapper(simple_fs->OpenVolume, 2, simple_fs, &vol_proto);

    if (EFI_ERROR(status)) {
        Print(L"Error opening volume: %r\n", status);
        return EFI_SUCCESS;
    }

    Print(L"SIMPLE_FILE_SYSTEM volume opened\n");

    EFI_FILE_PROTOCOL* f;
    CHAR16 fname[10] = L"foo.txt[=14=]";
    UINT64 openmode = EFI_FILE_MODE_READ;
    UINT64 attr = 0;
    status = uefi_call_wrapper(vol_proto->Open, 5, vol_proto, &f, fname, openmode, attr);

    if (EFI_ERROR(status)) {
        Print(L"Error opening file: %r\n", status);
        return EFI_SUCCESS;
    }

    Print(L"opened file %s\n", fname);

    // Spec says can only return EFI_SUCCESS
    status = uefi_call_wrapper(vol_proto->Close, 1, f);

    Print(L"Closed file\n");

    return EFI_SUCCESS;
}

UEFI 规范并未静态定义访问特定协议的位置和方式,而是需要在运行时发现的内容。虽然有点费力,但它使得编写 applications/drivers 可移植到完全符合规范的完全不同的 UEFI 实现成为可能。

因此,在使用 HandleProtocol() 之前,您需要在您打算使用的每个协议上找到 LocateProtocol()。 你逃脱了 LOADED_IMAGE_PROTOCOL 因为那个是用你的 ImageHandle 初始化的,引用当前正在执行的图像(你的程序)的实例。

这在 Matthew 的 post 部分 下进行了介绍,该部分介绍了如何使用附加到图像句柄的协议。附加到其他句柄的协议怎么样?