Search code examples
uefignu-efi

Most UEFI protocols are reported as "unsupported"


I'm writing an EFI executable on a SoC device (Up Board) to help us automate BIOS updates and PXE boots for installating our software on numerous devices.

The problem I have is that seemingly most of the protocols in the specification are "unsupported" on this platform, even basic file system tasks. The only one I've successfully used is the LOADED_IMAGE_PROTOCOL. I'm using gnu-efi to compile the code, basing my code to this example https://mjg59.dreamwidth.org/18773.html. Is there something I'm doing wrong, or is this simply the firmware absolutely not implementing any of the protocols?

The American Megatrends utility "AfuEfix64.efi" is capable of retrieving BIOS information, and the SoC BIOS updates are done using an Intel's EFI executable. Both are closed source, unfortunately. My initial idea would have been to write a script that parses the output from these executables, but I don't think the EFI shell has much features for this type of tasks, so I moved on to writing an EFI executable to perform this.

Minimal code showing this:

#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;
}

Here's the output for running it:

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:\>

And here's the Makefile I use to compile it:

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)

EDIT: Modified version that properly uses LocateProtocol() to find the protocols, and uses them to open and close a file.

#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\0";
    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;
}

Solution

  • Where and how to access a specific protocol is not statically defined by the UEFI specification, but something that needs to be discovered at runtime. While a bit arduous, it makes it possible to write applications/drivers portable across vastly different UEFI implementations that all conform to the specification.

    So you need to LocateProtocol() on each protocol you are intending to use before you can make use of HandleProtocol(). You get away with LOADED_IMAGE_PROTOCOL because that one is initialized with your ImageHandle referring to the instance of the currently executing image (your program).

    This is covered in Matthew's post under the section That covers how to use protocols attached to the image handle. How about protocols that are attached to other handles?.