Большинство протоколов UEFI сообщается как неподдерживаемые

Я пишу исполняемый файл EFI на устройстве SoC (Up Board), чтобы помочь нам автоматизировать обновления BIOS и загрузку PXE для установки нашего программного обеспечения на множество устройств.

У меня проблема в том, что, по-видимому, большинство протоколов в спецификации «не поддерживаются» на этой платформе, даже основные задачи файловой системы. Единственный, который я успешно использовал, — это LOADED_IMAGE_PROTOCOL. Я использую gnu-efi для компиляции кода, основываясь на этом примере https://mjg59.dreamwidth.org/18773.html. Я что-то не так делаю, или это просто прошивка абсолютно не реализует ни один из протоколов?

Утилита American Megatrends «AfuEfix64.efi» способна получать информацию о BIOS, а обновления SoC BIOS выполняются с использованием исполняемого файла Intel EFI. Оба с закрытым исходным кодом, к сожалению. Моя первоначальная идея состояла в том, чтобы написать сценарий, который анализирует вывод этих исполняемых файлов, но я не думаю, что оболочка EFI имеет много возможностей для задач такого типа, поэтому я перешел к написанию исполняемого файла 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\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;
}

person SirDifferential    schedule 12.04.2018    source источник


Ответы (1)


Где и как получить доступ к конкретному протоколу, не определяется статически спецификацией UEFI, это необходимо выяснить во время выполнения. Хотя это немного сложно, это позволяет писать приложения/драйверы, переносимые на самые разные реализации UEFI, которые соответствуют спецификации.

Поэтому вам нужно использовать LocateProtocol() для каждого протокола, который вы собираетесь использовать, прежде чем вы сможете использовать HandleProtocol(). Вам сойдет с рук LOADED_IMAGE_PROTOCOL, потому что он инициализируется вашим ImageHandle, ссылающимся на экземпляр текущего исполняемого изображения (вашей программы).

Это описано в сообщении Мэтью в разделе О том, как использовать протоколы, прикрепленные к дескриптору изображения. Как насчет протоколов, прикрепленных к другим дескрипторам?.

person unixsmurf    schedule 13.04.2018
comment
Это было оно. Спецификация также говорит, что HandleProtocol больше не следует использовать, поскольку OpenProtocol выполняет свою работу. Однако, когда я изменил свой вызов на LocateProtocol, я смог просто использовать его, не вызывая OpenProtocol. Попытка использовать OpenProtocol привела к ошибкам Invalid parameter, и в спецификации было немного неясно, каким должен быть дескриптор в вызове — интерфейс, возвращаемый LocateProtocol? Что-то из LocateHandle? А, ну теперь работает. - person SirDifferential; 13.04.2018