GK SDK 源码库: XMIPCLinuxV100R005C00SPC030 (kernel/tools/open_source excluded)

This commit is contained in:
lai
2026-09-06 03:52:57 +08:00
commit b1928b41c0
21813 changed files with 4413081 additions and 0 deletions
@@ -0,0 +1,123 @@
config EFI_LOADER
bool "Support running UEFI applications"
depends on OF_LIBFDT && ( \
ARM && (SYS_CPU = arm1136 || \
SYS_CPU = arm1176 || \
SYS_CPU = armv7 || \
SYS_CPU = armv8) || \
X86 || RISCV || SANDBOX)
# We need EFI_STUB_64BIT to be set on x86_64 with EFI_STUB
depends on !EFI_STUB || !X86_64 || EFI_STUB_64BIT
# We need EFI_STUB_32BIT to be set on x86_32 with EFI_STUB
depends on !EFI_STUB || !X86 || X86_64 || EFI_STUB_32BIT
default y if !ARM || SYS_CPU = armv7 || SYS_CPU = armv8
select LIB_UUID
select HAVE_BLOCK_DEVICE
select REGEX
imply CFB_CONSOLE_ANSI
help
Select this option if you want to run UEFI applications (like GNU
GRUB or iPXE) on top of U-Boot. If this option is enabled, U-Boot
will expose the UEFI API to a loaded application, enabling it to
reuse U-Boot's device drivers.
if EFI_LOADER
config EFI_GET_TIME
bool "GetTime() runtime service"
depends on DM_RTC
default y
help
Provide the GetTime() runtime service at boottime. This service
can be used by an EFI application to read the real time clock.
config EFI_SET_TIME
bool "SetTime() runtime service"
depends on EFI_GET_TIME
default n
help
Provide the SetTime() runtime service at boottime. This service
can be used by an EFI application to adjust the real time clock.
config EFI_DEVICE_PATH_TO_TEXT
bool "Device path to text protocol"
default y
help
The device path to text protocol converts device nodes and paths to
human readable strings.
config EFI_LOADER_HII
bool "HII protocols"
default y
help
The Human Interface Infrastructure is a complicated framework that
allows UEFI applications to draw fancy menus and hook strings using
a translation framework.
U-Boot implements enough of its features to be able to run the UEFI
Shell, but not more than that.
config EFI_UNICODE_COLLATION_PROTOCOL2
bool "Unicode collation protocol"
default y
help
The Unicode collation protocol is used for lexical comparisons. It is
required to run the UEFI shell.
if EFI_UNICODE_COLLATION_PROTOCOL2
config EFI_UNICODE_CAPITALIZATION
bool "Support Unicode capitalization"
default y
help
Select this option to enable correct handling of the capitalization of
Unicode codepoints in the range 0x0000-0xffff. If this option is not
set, only the the correct handling of the letters of the codepage
used by the FAT file system is ensured.
config EFI_UNICODE_COLLATION_PROTOCOL
bool "Deprecated version of the Unicode collation protocol"
default n
help
In EFI 1.10 a version of the Unicode collation protocol using ISO
639-2 language codes existed. This protocol is not part of the UEFI
specification any longer. Unfortunately it is required to run the
UEFI Self Certification Test (SCT) II, version 2.6, 2017.
Choose this option for testing only. It is bound to be removed.
endif
config EFI_LOADER_BOUNCE_BUFFER
bool "EFI Applications use bounce buffers for DMA operations"
depends on ARM64
default n
help
Some hardware does not support DMA to full 64bit addresses. For this
hardware we can create a bounce buffer so that payloads don't have to
worry about platform details.
config EFI_PLATFORM_LANG_CODES
string "Language codes supported by firmware"
default "en-US"
help
This value is used to initialize the PlatformLangCodes variable. Its
value is a semicolon (;) separated list of language codes in native
RFC 4646 format, e.g. "en-US;de-DE". The first language code is used
to initialize the PlatformLang variable.
config EFI_HAVE_RUNTIME_RESET
# bool "Reset runtime service is available"
bool
default y
depends on ARCH_BCM283X || FSL_LAYERSCAPE || PSCI_RESET || SYSRESET_X86
config EFI_GRUB_ARM32_WORKAROUND
bool "Workaround for GRUB on 32bit ARM"
default y
depends on ARM && !ARM64
help
GRUB prior to version 2.04 requires U-Boot to disable caches. This
workaround currently is also needed on systems with caches that
cannot be managed via CP15.
endif
@@ -0,0 +1,44 @@
# SPDX-License-Identifier: GPL-2.0+
#
# (C) Copyright 2016 Alexander Graf
#
# This file only gets included with CONFIG_EFI_LOADER set, so all
# object inclusion implicitly depends on it
asflags-y += -DHOST_ARCH="$(HOST_ARCH)"
ccflags-y += -DHOST_ARCH="$(HOST_ARCH)"
CFLAGS_efi_boottime.o += \
-DFW_VERSION="0x$(VERSION)" \
-DFW_PATCHLEVEL="0x$(PATCHLEVEL)"
CFLAGS_helloworld.o := $(CFLAGS_EFI) -Os -ffreestanding
CFLAGS_REMOVE_helloworld.o := $(CFLAGS_NON_EFI)
ifneq ($(CONFIG_CMD_BOOTEFI_HELLO_COMPILE),)
always += helloworld.efi
endif
obj-$(CONFIG_CMD_BOOTEFI_HELLO) += helloworld_efi.o
obj-y += efi_bootmgr.o
obj-y += efi_boottime.o
obj-y += efi_console.o
obj-y += efi_device_path.o
obj-$(CONFIG_EFI_DEVICE_PATH_TO_TEXT) += efi_device_path_to_text.o
obj-y += efi_device_path_utilities.o
obj-y += efi_file.o
obj-$(CONFIG_EFI_LOADER_HII) += efi_hii.o efi_hii_config.o
obj-y += efi_image_loader.o
obj-y += efi_memory.o
obj-y += efi_root_node.o
obj-y += efi_runtime.o
obj-y += efi_setup.o
obj-$(CONFIG_EFI_UNICODE_COLLATION_PROTOCOL2) += efi_unicode_collation.o
obj-y += efi_variable.o
obj-y += efi_watchdog.o
obj-$(CONFIG_LCD) += efi_gop.o
obj-$(CONFIG_DM_VIDEO) += efi_gop.o
obj-$(CONFIG_PARTITIONS) += efi_disk.o
obj-$(CONFIG_NET) += efi_net.o
obj-$(CONFIG_GENERATE_ACPI_TABLE) += efi_acpi.o
obj-$(CONFIG_GENERATE_SMBIOS_TABLE) += efi_smbios.o
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* EFI application ACPI tables support
*
* Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
*/
#include <common.h>
#include <efi_loader.h>
#include <asm/acpi_table.h>
static const efi_guid_t acpi_guid = EFI_ACPI_TABLE_GUID;
/*
* Install the ACPI table as a configuration table.
*
* @return status code
*/
efi_status_t efi_acpi_register(void)
{
/* Map within the low 32 bits, to allow for 32bit ACPI tables */
u64 acpi = U32_MAX;
efi_status_t ret;
/* Reserve 64kiB page for ACPI */
ret = efi_allocate_pages(EFI_ALLOCATE_MAX_ADDRESS,
EFI_RUNTIME_SERVICES_DATA, 16, &acpi);
if (ret != EFI_SUCCESS)
return ret;
/*
* Generate ACPI tables - we know that efi_allocate_pages() returns
* a 4k-aligned address, so it is safe to assume that
* write_acpi_tables() will write the table at that address.
*/
assert(!(acpi & 0xf));
write_acpi_tables(acpi);
/* And expose them to our EFI payload */
return efi_install_configuration_table(&acpi_guid,
(void *)(uintptr_t)acpi);
}
@@ -0,0 +1,282 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* EFI boot manager
*
* Copyright (c) 2017 Rob Clark
*/
#include <common.h>
#include <charset.h>
#include <malloc.h>
#include <efi_loader.h>
#include <asm/unaligned.h>
static const struct efi_boot_services *bs;
static const struct efi_runtime_services *rs;
/*
* bootmgr implements the logic of trying to find a payload to boot
* based on the BootOrder + BootXXXX variables, and then loading it.
*
* TODO detecting a special key held (f9?) and displaying a boot menu
* like you would get on a PC would be clever.
*
* TODO if we had a way to write and persist variables after the OS
* has started, we'd also want to check OsIndications to see if we
* should do normal or recovery boot.
*/
/**
* efi_deserialize_load_option() - parse serialized data
*
* Parse serialized data describing a load option and transform it to the
* efi_load_option structure.
*
* @lo: pointer to target
* @data: serialized data
*/
void efi_deserialize_load_option(struct efi_load_option *lo, u8 *data)
{
lo->attributes = get_unaligned_le32(data);
data += sizeof(u32);
lo->file_path_length = get_unaligned_le16(data);
data += sizeof(u16);
/* FIXME */
lo->label = (u16 *)data;
data += (u16_strlen(lo->label) + 1) * sizeof(u16);
/* FIXME */
lo->file_path = (struct efi_device_path *)data;
data += lo->file_path_length;
lo->optional_data = data;
}
/**
* efi_serialize_load_option() - serialize load option
*
* Serialize efi_load_option structure into byte stream for BootXXXX.
*
* @data: buffer for serialized data
* @lo: load option
* Return: size of allocated buffer
*/
unsigned long efi_serialize_load_option(struct efi_load_option *lo, u8 **data)
{
unsigned long label_len;
unsigned long size;
u8 *p;
label_len = (u16_strlen(lo->label) + 1) * sizeof(u16);
/* total size */
size = sizeof(lo->attributes);
size += sizeof(lo->file_path_length);
size += label_len;
size += lo->file_path_length;
if (lo->optional_data)
size += (utf8_utf16_strlen((const char *)lo->optional_data)
+ 1) * sizeof(u16);
p = malloc(size);
if (!p)
return 0;
/* copy data */
*data = p;
memcpy(p, &lo->attributes, sizeof(lo->attributes));
p += sizeof(lo->attributes);
memcpy(p, &lo->file_path_length, sizeof(lo->file_path_length));
p += sizeof(lo->file_path_length);
memcpy(p, lo->label, label_len);
p += label_len;
memcpy(p, lo->file_path, lo->file_path_length);
p += lo->file_path_length;
if (lo->optional_data) {
utf8_utf16_strcpy((u16 **)&p, (const char *)lo->optional_data);
p += sizeof(u16); /* size of trailing \0 */
}
return size;
}
/**
* get_var() - get UEFI variable
*
* It is the caller's duty to free the returned buffer.
*
* @name: name of variable
* @vendor: vendor GUID of variable
* @size: size of allocated buffer
* Return: buffer with variable data or NULL
*/
static void *get_var(u16 *name, const efi_guid_t *vendor,
efi_uintn_t *size)
{
efi_guid_t *v = (efi_guid_t *)vendor;
efi_status_t ret;
void *buf = NULL;
*size = 0;
EFI_CALL(ret = rs->get_variable(name, v, NULL, size, buf));
if (ret == EFI_BUFFER_TOO_SMALL) {
buf = malloc(*size);
EFI_CALL(ret = rs->get_variable(name, v, NULL, size, buf));
}
if (ret != EFI_SUCCESS) {
free(buf);
*size = 0;
return NULL;
}
return buf;
}
/**
* try_load_entry() - try to load image for boot option
*
* Attempt to load load-option number 'n', returning device_path and file_path
* if successful. This checks that the EFI_LOAD_OPTION is active (enabled)
* and that the specified file to boot exists.
*
* @n: number of the boot option, e.g. 0x0a13 for Boot0A13
* @handle: on return handle for the newly installed image
* Return: status code
*/
static efi_status_t try_load_entry(u16 n, efi_handle_t *handle)
{
struct efi_load_option lo;
u16 varname[] = L"Boot0000";
u16 hexmap[] = L"0123456789ABCDEF";
void *load_option;
efi_uintn_t size;
efi_status_t ret;
varname[4] = hexmap[(n & 0xf000) >> 12];
varname[5] = hexmap[(n & 0x0f00) >> 8];
varname[6] = hexmap[(n & 0x00f0) >> 4];
varname[7] = hexmap[(n & 0x000f) >> 0];
load_option = get_var(varname, &efi_global_variable_guid, &size);
if (!load_option)
return EFI_LOAD_ERROR;
efi_deserialize_load_option(&lo, load_option);
if (lo.attributes & LOAD_OPTION_ACTIVE) {
u32 attributes;
debug("%s: trying to load \"%ls\" from %pD\n",
__func__, lo.label, lo.file_path);
ret = EFI_CALL(efi_load_image(true, efi_root, lo.file_path,
NULL, 0, handle));
if (ret != EFI_SUCCESS) {
printf("Loading from Boot%04X '%ls' failed\n", n,
lo.label);
goto error;
}
attributes = EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS;
size = sizeof(n);
ret = EFI_CALL(efi_set_variable(
L"BootCurrent",
(efi_guid_t *)&efi_global_variable_guid,
attributes, size, &n));
if (ret != EFI_SUCCESS) {
if (EFI_CALL(efi_unload_image(*handle))
!= EFI_SUCCESS)
printf("Unloading image failed\n");
goto error;
}
printf("Booting: %ls\n", lo.label);
} else {
ret = EFI_LOAD_ERROR;
}
error:
free(load_option);
return ret;
}
/**
* efi_bootmgr_load() - try to load from BootNext or BootOrder
*
* Attempt to load from BootNext or in the order specified by BootOrder
* EFI variable, the available load-options, finding and returning
* the first one that can be loaded successfully.
*
* @handle: on return handle for the newly installed image
* Return: status code
*/
efi_status_t efi_bootmgr_load(efi_handle_t *handle)
{
u16 bootnext, *bootorder;
efi_uintn_t size;
int i, num;
efi_status_t ret;
bs = systab.boottime;
rs = systab.runtime;
/* BootNext */
bootnext = 0;
size = sizeof(bootnext);
ret = EFI_CALL(efi_get_variable(L"BootNext",
(efi_guid_t *)&efi_global_variable_guid,
NULL, &size, &bootnext));
if (ret == EFI_SUCCESS || ret == EFI_BUFFER_TOO_SMALL) {
/* BootNext does exist here */
if (ret == EFI_BUFFER_TOO_SMALL || size != sizeof(u16))
printf("BootNext must be 16-bit integer\n");
/* delete BootNext */
ret = EFI_CALL(efi_set_variable(
L"BootNext",
(efi_guid_t *)&efi_global_variable_guid,
EFI_VARIABLE_NON_VOLATILE, 0,
&bootnext));
/* load BootNext */
if (ret == EFI_SUCCESS) {
if (size == sizeof(u16)) {
ret = try_load_entry(bootnext, handle);
if (ret == EFI_SUCCESS)
return ret;
printf("Loading from BootNext failed, falling back to BootOrder\n");
}
} else {
printf("Deleting BootNext failed\n");
}
}
/* BootOrder */
bootorder = get_var(L"BootOrder", &efi_global_variable_guid, &size);
if (!bootorder) {
printf("BootOrder not defined\n");
ret = EFI_NOT_FOUND;
goto error;
}
num = size / sizeof(uint16_t);
for (i = 0; i < num; i++) {
debug("%s: trying to load Boot%04X\n", __func__, bootorder[i]);
ret = try_load_entry(bootorder[i], handle);
if (ret == EFI_SUCCESS)
break;
}
free(bootorder);
error:
return ret;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,369 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* EFI device path interface
*
* Copyright (c) 2017 Heinrich Schuchardt
*/
#include <common.h>
#include <efi_loader.h>
#define MAC_OUTPUT_LEN 22
#define UNKNOWN_OUTPUT_LEN 23
#define MAX_NODE_LEN 512
#define MAX_PATH_LEN 1024
const efi_guid_t efi_guid_device_path_to_text_protocol =
EFI_DEVICE_PATH_TO_TEXT_PROTOCOL_GUID;
/**
* efi_str_to_u16() - convert ASCII string to UTF-16
*
* A u16 buffer is allocated from pool. The ASCII string is copied to the u16
* buffer.
*
* @str: ASCII string
* Return: UTF-16 string. NULL if out of memory.
*/
static u16 *efi_str_to_u16(char *str)
{
efi_uintn_t len;
u16 *out, *dst;
efi_status_t ret;
len = sizeof(u16) * (utf8_utf16_strlen(str) + 1);
ret = efi_allocate_pool(EFI_ALLOCATE_ANY_PAGES, len, (void **)&out);
if (ret != EFI_SUCCESS)
return NULL;
dst = out;
utf8_utf16_strcpy(&dst, str);
return out;
}
static char *dp_unknown(char *s, struct efi_device_path *dp)
{
s += sprintf(s, "UNKNOWN(%04x,%04x)", dp->type, dp->sub_type);
return s;
}
static char *dp_hardware(char *s, struct efi_device_path *dp)
{
switch (dp->sub_type) {
case DEVICE_PATH_SUB_TYPE_MEMORY: {
struct efi_device_path_memory *mdp =
(struct efi_device_path_memory *)dp;
s += sprintf(s, "MemoryMapped(0x%x,0x%llx,0x%llx)",
mdp->memory_type,
mdp->start_address,
mdp->end_address);
break;
}
case DEVICE_PATH_SUB_TYPE_VENDOR: {
int i, n;
struct efi_device_path_vendor *vdp =
(struct efi_device_path_vendor *)dp;
s += sprintf(s, "VenHw(%pUl", &vdp->guid);
n = (int)vdp->dp.length - sizeof(struct efi_device_path_vendor);
if (n > 0) {
s += sprintf(s, ",");
for (i = 0; i < n; ++i)
s += sprintf(s, "%02x", vdp->vendor_data[i]);
}
s += sprintf(s, ")");
break;
}
default:
s = dp_unknown(s, dp);
break;
}
return s;
}
static char *dp_acpi(char *s, struct efi_device_path *dp)
{
switch (dp->sub_type) {
case DEVICE_PATH_SUB_TYPE_ACPI_DEVICE: {
struct efi_device_path_acpi_path *adp =
(struct efi_device_path_acpi_path *)dp;
s += sprintf(s, "Acpi(PNP%04X,%d)", EISA_PNP_NUM(adp->hid),
adp->uid);
break;
}
default:
s = dp_unknown(s, dp);
break;
}
return s;
}
static char *dp_msging(char *s, struct efi_device_path *dp)
{
switch (dp->sub_type) {
case DEVICE_PATH_SUB_TYPE_MSG_ATAPI: {
struct efi_device_path_atapi *ide =
(struct efi_device_path_atapi *)dp;
s += sprintf(s, "Ata(%d,%d,%d)", ide->primary_secondary,
ide->slave_master, ide->logical_unit_number);
break;
}
case DEVICE_PATH_SUB_TYPE_MSG_SCSI: {
struct efi_device_path_scsi *ide =
(struct efi_device_path_scsi *)dp;
s += sprintf(s, "Scsi(%u,%u)", ide->target_id,
ide->logical_unit_number);
break;
}
case DEVICE_PATH_SUB_TYPE_MSG_USB: {
struct efi_device_path_usb *udp =
(struct efi_device_path_usb *)dp;
s += sprintf(s, "USB(0x%x,0x%x)", udp->parent_port_number,
udp->usb_interface);
break;
}
case DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR: {
int i, n = sizeof(struct efi_mac_addr);
struct efi_device_path_mac_addr *mdp =
(struct efi_device_path_mac_addr *)dp;
if (mdp->if_type <= 1)
n = 6;
s += sprintf(s, "MAC(");
for (i = 0; i < n; ++i)
s += sprintf(s, "%02x", mdp->mac.addr[i]);
s += sprintf(s, ",%u)", mdp->if_type);
break;
}
case DEVICE_PATH_SUB_TYPE_MSG_USB_CLASS: {
struct efi_device_path_usb_class *ucdp =
(struct efi_device_path_usb_class *)dp;
s += sprintf(s, "UsbClass(0x%x,0x%x,0x%x,0x%x,0x%x)",
ucdp->vendor_id, ucdp->product_id,
ucdp->device_class, ucdp->device_subclass,
ucdp->device_protocol);
break;
}
case DEVICE_PATH_SUB_TYPE_MSG_NVME: {
struct efi_device_path_nvme *ndp =
(struct efi_device_path_nvme *)dp;
u32 ns_id;
int i;
memcpy(&ns_id, &ndp->ns_id, sizeof(ns_id));
s += sprintf(s, "NVMe(0x%x,", ns_id);
for (i = 0; i < sizeof(ndp->eui64); ++i)
s += sprintf(s, "%s%02x", i ? "-" : "",
ndp->eui64[i]);
s += sprintf(s, ")");
break;
}
case DEVICE_PATH_SUB_TYPE_MSG_SD:
case DEVICE_PATH_SUB_TYPE_MSG_MMC: {
const char *typename =
(dp->sub_type == DEVICE_PATH_SUB_TYPE_MSG_SD) ?
"SD" : "eMMC";
struct efi_device_path_sd_mmc_path *sddp =
(struct efi_device_path_sd_mmc_path *)dp;
s += sprintf(s, "%s(%u)", typename, sddp->slot_number);
break;
}
default:
s = dp_unknown(s, dp);
break;
}
return s;
}
/*
* Convert a media device path node to text.
*
* @s output buffer
* @dp device path node
* @return next unused buffer address
*/
static char *dp_media(char *s, struct efi_device_path *dp)
{
switch (dp->sub_type) {
case DEVICE_PATH_SUB_TYPE_HARD_DRIVE_PATH: {
struct efi_device_path_hard_drive_path *hddp =
(struct efi_device_path_hard_drive_path *)dp;
void *sig = hddp->partition_signature;
u64 start;
u64 end;
/* Copy from packed structure to aligned memory */
memcpy(&start, &hddp->partition_start, sizeof(start));
memcpy(&end, &hddp->partition_end, sizeof(end));
switch (hddp->signature_type) {
case SIG_TYPE_MBR: {
u32 signature;
memcpy(&signature, sig, sizeof(signature));
s += sprintf(
s, "HD(%d,MBR,0x%08x,0x%llx,0x%llx)",
hddp->partition_number, signature, start, end);
break;
}
case SIG_TYPE_GUID:
s += sprintf(
s, "HD(%d,GPT,%pUl,0x%llx,0x%llx)",
hddp->partition_number, sig, start, end);
break;
default:
s += sprintf(
s, "HD(%d,0x%02x,0,0x%llx,0x%llx)",
hddp->partition_number, hddp->partmap_type,
start, end);
break;
}
break;
}
case DEVICE_PATH_SUB_TYPE_CDROM_PATH: {
struct efi_device_path_cdrom_path *cddp =
(struct efi_device_path_cdrom_path *)dp;
s += sprintf(s, "CDROM(%u,0x%llx,0x%llx)", cddp->boot_entry,
cddp->partition_start, cddp->partition_size);
break;
}
case DEVICE_PATH_SUB_TYPE_FILE_PATH: {
struct efi_device_path_file_path *fp =
(struct efi_device_path_file_path *)dp;
int slen = (dp->length - sizeof(*dp)) / 2;
if (slen > MAX_NODE_LEN - 2)
slen = MAX_NODE_LEN - 2;
s += sprintf(s, "%-.*ls", slen, fp->str);
break;
}
default:
s = dp_unknown(s, dp);
break;
}
return s;
}
/*
* Converts a single node to a char string.
*
* @buffer output buffer
* @dp device path or node
* @return end of string
*/
static char *efi_convert_single_device_node_to_text(
char *buffer,
struct efi_device_path *dp)
{
char *str = buffer;
switch (dp->type) {
case DEVICE_PATH_TYPE_HARDWARE_DEVICE:
str = dp_hardware(str, dp);
break;
case DEVICE_PATH_TYPE_ACPI_DEVICE:
str = dp_acpi(str, dp);
break;
case DEVICE_PATH_TYPE_MESSAGING_DEVICE:
str = dp_msging(str, dp);
break;
case DEVICE_PATH_TYPE_MEDIA_DEVICE:
str = dp_media(str, dp);
break;
case DEVICE_PATH_TYPE_END:
break;
default:
str = dp_unknown(str, dp);
}
*str = '\0';
return str;
}
/*
* This function implements the ConvertDeviceNodeToText service of the
* EFI_DEVICE_PATH_TO_TEXT_PROTOCOL.
* See the Unified Extensible Firmware Interface (UEFI) specification
* for details.
*
* device_node device node to be converted
* display_only true if the shorter text representation shall be used
* allow_shortcuts true if shortcut forms may be used
* @return text representation of the device path
* NULL if out of memory of device_path is NULL
*/
static uint16_t EFIAPI *efi_convert_device_node_to_text(
struct efi_device_path *device_node,
bool display_only,
bool allow_shortcuts)
{
char str[MAX_NODE_LEN];
uint16_t *text = NULL;
EFI_ENTRY("%p, %d, %d", device_node, display_only, allow_shortcuts);
if (!device_node)
goto out;
efi_convert_single_device_node_to_text(str, device_node);
text = efi_str_to_u16(str);
out:
EFI_EXIT(EFI_SUCCESS);
return text;
}
/*
* This function implements the ConvertDevicePathToText service of the
* EFI_DEVICE_PATH_TO_TEXT_PROTOCOL.
* See the Unified Extensible Firmware Interface (UEFI) specification
* for details.
*
* device_path device path to be converted
* display_only true if the shorter text representation shall be used
* allow_shortcuts true if shortcut forms may be used
* @return text representation of the device path
* NULL if out of memory of device_path is NULL
*/
static uint16_t EFIAPI *efi_convert_device_path_to_text(
struct efi_device_path *device_path,
bool display_only,
bool allow_shortcuts)
{
uint16_t *text = NULL;
char buffer[MAX_PATH_LEN];
char *str = buffer;
EFI_ENTRY("%p, %d, %d", device_path, display_only, allow_shortcuts);
if (!device_path)
goto out;
while (device_path &&
str + MAX_NODE_LEN < buffer + MAX_PATH_LEN) {
*str++ = '/';
str = efi_convert_single_device_node_to_text(str, device_path);
device_path = efi_dp_next(device_path);
}
text = efi_str_to_u16(buffer);
out:
EFI_EXIT(EFI_SUCCESS);
return text;
}
/* helper for debug prints.. efi_free_pool() the result. */
uint16_t *efi_dp_str(struct efi_device_path *dp)
{
return EFI_CALL(efi_convert_device_path_to_text(dp, true, true));
}
const struct efi_device_path_to_text_protocol efi_device_path_to_text = {
.convert_device_node_to_text = efi_convert_device_node_to_text,
.convert_device_path_to_text = efi_convert_device_path_to_text,
};
@@ -0,0 +1,199 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* EFI device path interface
*
* Copyright (c) 2017 Leif Lindholm
*/
#include <common.h>
#include <efi_loader.h>
const efi_guid_t efi_guid_device_path_utilities_protocol =
EFI_DEVICE_PATH_UTILITIES_PROTOCOL_GUID;
/*
* Get size of a device path.
*
* This function implements the GetDevicePathSize service of the device path
* utilities protocol. The device path length includes the end of path tag
* which may be an instance end.
*
* See the Unified Extensible Firmware Interface (UEFI) specification
* for details.
*
* @device_path device path
* @return size in bytes
*/
static efi_uintn_t EFIAPI get_device_path_size(
const struct efi_device_path *device_path)
{
efi_uintn_t sz = 0;
EFI_ENTRY("%pD", device_path);
/* size includes the END node: */
if (device_path)
sz = efi_dp_size(device_path) + sizeof(struct efi_device_path);
return EFI_EXIT(sz);
}
/*
* Duplicate a device path.
*
* This function implements the DuplicateDevicePath service of the device path
* utilities protocol.
*
* The UEFI spec does not indicate what happens to the end tag. We follow the
* EDK2 logic: In case the device path ends with an end of instance tag, the
* copy will also end with an end of instance tag.
*
* See the Unified Extensible Firmware Interface (UEFI) specification
* for details.
*
* @device_path device path
* @return copy of the device path
*/
static struct efi_device_path * EFIAPI duplicate_device_path(
const struct efi_device_path *device_path)
{
EFI_ENTRY("%pD", device_path);
return EFI_EXIT(efi_dp_dup(device_path));
}
/*
* Append device path.
*
* This function implements the AppendDevicePath service of the device path
* utilities protocol.
*
* See the Unified Extensible Firmware Interface (UEFI) specification
* for details.
*
* @src1 1st device path
* @src2 2nd device path
* @return concatenated device path
*/
static struct efi_device_path * EFIAPI append_device_path(
const struct efi_device_path *src1,
const struct efi_device_path *src2)
{
EFI_ENTRY("%pD, %pD", src1, src2);
return EFI_EXIT(efi_dp_append(src1, src2));
}
/*
* Append device path node.
*
* This function implements the AppendDeviceNode service of the device path
* utilities protocol.
*
* See the Unified Extensible Firmware Interface (UEFI) specification
* for details.
*
* @device_path device path
* @device_node device node
* @return concatenated device path
*/
static struct efi_device_path * EFIAPI append_device_node(
const struct efi_device_path *device_path,
const struct efi_device_path *device_node)
{
EFI_ENTRY("%pD, %p", device_path, device_node);
return EFI_EXIT(efi_dp_append_node(device_path, device_node));
}
/*
* Append device path instance.
*
* This function implements the AppendDevicePathInstance service of the device
* path utilities protocol.
*
* See the Unified Extensible Firmware Interface (UEFI) specification
* for details.
*
* @device_path 1st device path
* @device_path_instance 2nd device path
* @return concatenated device path
*/
static struct efi_device_path * EFIAPI append_device_path_instance(
const struct efi_device_path *device_path,
const struct efi_device_path *device_path_instance)
{
EFI_ENTRY("%pD, %pD", device_path, device_path_instance);
return EFI_EXIT(efi_dp_append_instance(device_path,
device_path_instance));
}
/*
* Get next device path instance.
*
* This function implements the GetNextDevicePathInstance service of the device
* path utilities protocol.
*
* See the Unified Extensible Firmware Interface (UEFI) specification
* for details.
*
* @device_path_instance next device path instance
* @device_path_instance_size size of the device path instance
* @return concatenated device path
*/
static struct efi_device_path * EFIAPI get_next_device_path_instance(
struct efi_device_path **device_path_instance,
efi_uintn_t *device_path_instance_size)
{
EFI_ENTRY("%pD, %p", device_path_instance, device_path_instance_size);
return EFI_EXIT(efi_dp_get_next_instance(device_path_instance,
device_path_instance_size));
}
/*
* Check if a device path contains more than one instance.
*
* This function implements the AppendDeviceNode service of the device path
* utilities protocol.
*
* See the Unified Extensible Firmware Interface (UEFI) specification
* for details.
*
* @device_path device path
* @device_node device node
* @return concatenated device path
*/
static bool EFIAPI is_device_path_multi_instance(
const struct efi_device_path *device_path)
{
EFI_ENTRY("%pD", device_path);
return EFI_EXIT(efi_dp_is_multi_instance(device_path));
}
/*
* Create device node.
*
* This function implements the CreateDeviceNode service of the device path
* utilities protocol.
*
* See the Unified Extensible Firmware Interface (UEFI) specification
* for details.
*
* @node_type node type
* @node_sub_type node sub type
* @node_length node length
* @return device path node
*/
static struct efi_device_path * EFIAPI create_device_node(
uint8_t node_type, uint8_t node_sub_type, uint16_t node_length)
{
EFI_ENTRY("%u, %u, %u", node_type, node_sub_type, node_length);
return EFI_EXIT(efi_dp_create_device_node(node_type, node_sub_type,
node_length));
}
const struct efi_device_path_utilities_protocol efi_device_path_utilities = {
.get_device_path_size = get_device_path_size,
.duplicate_device_path = duplicate_device_path,
.append_device_path = append_device_path,
.append_device_node = append_device_node,
.append_device_path_instance = append_device_path_instance,
.get_next_device_path_instance = get_next_device_path_instance,
.is_device_path_multi_instance = is_device_path_multi_instance,
.create_device_node = create_device_node,
};
@@ -0,0 +1,516 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* EFI application disk support
*
* Copyright (c) 2016 Alexander Graf
*/
#include <common.h>
#include <blk.h>
#include <dm.h>
#include <efi_loader.h>
#include <fs.h>
#include <part.h>
#include <malloc.h>
const efi_guid_t efi_block_io_guid = EFI_BLOCK_IO_PROTOCOL_GUID;
/**
* struct efi_disk_obj - EFI disk object
*
* @header: EFI object header
* @ops: EFI disk I/O protocol interface
* @ifname: interface name for block device
* @dev_index: device index of block device
* @media: block I/O media information
* @dp: device path to the block device
* @part: partition
* @volume: simple file system protocol of the partition
* @offset: offset into disk for simple partition
* @desc: internal block device descriptor
*/
struct efi_disk_obj {
struct efi_object header;
struct efi_block_io ops;
const char *ifname;
int dev_index;
struct efi_block_io_media media;
struct efi_device_path *dp;
unsigned int part;
struct efi_simple_file_system_protocol *volume;
lbaint_t offset;
struct blk_desc *desc;
};
/**
* efi_disk_reset() - reset block device
*
* This function implements the Reset service of the EFI_BLOCK_IO_PROTOCOL.
*
* As U-Boot's block devices do not have a reset function simply return
* EFI_SUCCESS.
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* @this: pointer to the BLOCK_IO_PROTOCOL
* @extended_verification: extended verification
* Return: status code
*/
static efi_status_t EFIAPI efi_disk_reset(struct efi_block_io *this,
char extended_verification)
{
EFI_ENTRY("%p, %x", this, extended_verification);
return EFI_EXIT(EFI_SUCCESS);
}
enum efi_disk_direction {
EFI_DISK_READ,
EFI_DISK_WRITE,
};
static efi_status_t efi_disk_rw_blocks(struct efi_block_io *this,
u32 media_id, u64 lba, unsigned long buffer_size,
void *buffer, enum efi_disk_direction direction)
{
struct efi_disk_obj *diskobj;
struct blk_desc *desc;
int blksz;
int blocks;
unsigned long n;
diskobj = container_of(this, struct efi_disk_obj, ops);
desc = (struct blk_desc *) diskobj->desc;
blksz = desc->blksz;
blocks = buffer_size / blksz;
lba += diskobj->offset;
EFI_PRINT("blocks=%x lba=%llx blksz=%x dir=%d\n",
blocks, lba, blksz, direction);
/* We only support full block access */
if (buffer_size & (blksz - 1))
return EFI_BAD_BUFFER_SIZE;
if (direction == EFI_DISK_READ)
n = blk_dread(desc, lba, blocks, buffer);
else
n = blk_dwrite(desc, lba, blocks, buffer);
/* We don't do interrupts, so check for timers cooperatively */
efi_timer_check();
EFI_PRINT("n=%lx blocks=%x\n", n, blocks);
if (n != blocks)
return EFI_DEVICE_ERROR;
return EFI_SUCCESS;
}
static efi_status_t EFIAPI efi_disk_read_blocks(struct efi_block_io *this,
u32 media_id, u64 lba, efi_uintn_t buffer_size,
void *buffer)
{
void *real_buffer = buffer;
efi_status_t r;
if (!this)
return EFI_INVALID_PARAMETER;
/* TODO: check for media changes */
if (media_id != this->media->media_id)
return EFI_MEDIA_CHANGED;
if (!this->media->media_present)
return EFI_NO_MEDIA;
/* media->io_align is a power of 2 */
if ((uintptr_t)buffer & (this->media->io_align - 1))
return EFI_INVALID_PARAMETER;
if (lba * this->media->block_size + buffer_size >
this->media->last_block * this->media->block_size)
return EFI_INVALID_PARAMETER;
#ifdef CONFIG_EFI_LOADER_BOUNCE_BUFFER
if (buffer_size > EFI_LOADER_BOUNCE_BUFFER_SIZE) {
r = efi_disk_read_blocks(this, media_id, lba,
EFI_LOADER_BOUNCE_BUFFER_SIZE, buffer);
if (r != EFI_SUCCESS)
return r;
return efi_disk_read_blocks(this, media_id, lba +
EFI_LOADER_BOUNCE_BUFFER_SIZE / this->media->block_size,
buffer_size - EFI_LOADER_BOUNCE_BUFFER_SIZE,
buffer + EFI_LOADER_BOUNCE_BUFFER_SIZE);
}
real_buffer = efi_bounce_buffer;
#endif
EFI_ENTRY("%p, %x, %llx, %zx, %p", this, media_id, lba,
buffer_size, buffer);
r = efi_disk_rw_blocks(this, media_id, lba, buffer_size, real_buffer,
EFI_DISK_READ);
/* Copy from bounce buffer to real buffer if necessary */
if ((r == EFI_SUCCESS) && (real_buffer != buffer))
memcpy(buffer, real_buffer, buffer_size);
return EFI_EXIT(r);
}
static efi_status_t EFIAPI efi_disk_write_blocks(struct efi_block_io *this,
u32 media_id, u64 lba, efi_uintn_t buffer_size,
void *buffer)
{
void *real_buffer = buffer;
efi_status_t r;
if (!this)
return EFI_INVALID_PARAMETER;
if (this->media->read_only)
return EFI_WRITE_PROTECTED;
/* TODO: check for media changes */
if (media_id != this->media->media_id)
return EFI_MEDIA_CHANGED;
if (!this->media->media_present)
return EFI_NO_MEDIA;
/* media->io_align is a power of 2 */
if ((uintptr_t)buffer & (this->media->io_align - 1))
return EFI_INVALID_PARAMETER;
if (lba * this->media->block_size + buffer_size >
this->media->last_block * this->media->block_size)
return EFI_INVALID_PARAMETER;
#ifdef CONFIG_EFI_LOADER_BOUNCE_BUFFER
if (buffer_size > EFI_LOADER_BOUNCE_BUFFER_SIZE) {
r = efi_disk_write_blocks(this, media_id, lba,
EFI_LOADER_BOUNCE_BUFFER_SIZE, buffer);
if (r != EFI_SUCCESS)
return r;
return efi_disk_write_blocks(this, media_id, lba +
EFI_LOADER_BOUNCE_BUFFER_SIZE / this->media->block_size,
buffer_size - EFI_LOADER_BOUNCE_BUFFER_SIZE,
buffer + EFI_LOADER_BOUNCE_BUFFER_SIZE);
}
real_buffer = efi_bounce_buffer;
#endif
EFI_ENTRY("%p, %x, %llx, %zx, %p", this, media_id, lba,
buffer_size, buffer);
/* Populate bounce buffer if necessary */
if (real_buffer != buffer)
memcpy(real_buffer, buffer, buffer_size);
r = efi_disk_rw_blocks(this, media_id, lba, buffer_size, real_buffer,
EFI_DISK_WRITE);
return EFI_EXIT(r);
}
static efi_status_t EFIAPI efi_disk_flush_blocks(struct efi_block_io *this)
{
/* We always write synchronously */
EFI_ENTRY("%p", this);
return EFI_EXIT(EFI_SUCCESS);
}
static const struct efi_block_io block_io_disk_template = {
.reset = &efi_disk_reset,
.read_blocks = &efi_disk_read_blocks,
.write_blocks = &efi_disk_write_blocks,
.flush_blocks = &efi_disk_flush_blocks,
};
/*
* Get the simple file system protocol for a file device path.
*
* The full path provided is split into device part and into a file
* part. The device part is used to find the handle on which the
* simple file system protocol is installed.
*
* @full_path device path including device and file
* @return simple file system protocol
*/
struct efi_simple_file_system_protocol *
efi_fs_from_path(struct efi_device_path *full_path)
{
struct efi_object *efiobj;
struct efi_handler *handler;
struct efi_device_path *device_path;
struct efi_device_path *file_path;
efi_status_t ret;
/* Split the path into a device part and a file part */
ret = efi_dp_split_file_path(full_path, &device_path, &file_path);
if (ret != EFI_SUCCESS)
return NULL;
efi_free_pool(file_path);
/* Get the EFI object for the partition */
efiobj = efi_dp_find_obj(device_path, NULL);
efi_free_pool(device_path);
if (!efiobj)
return NULL;
/* Find the simple file system protocol */
ret = efi_search_protocol(efiobj, &efi_simple_file_system_protocol_guid,
&handler);
if (ret != EFI_SUCCESS)
return NULL;
/* Return the simple file system protocol for the partition */
return handler->protocol_interface;
}
/**
* efi_fs_exists() - check if a partition bears a file system
*
* @desc: block device descriptor
* @part: partition number
* Return: 1 if a file system exists on the partition
* 0 otherwise
*/
static int efi_fs_exists(struct blk_desc *desc, int part)
{
if (fs_set_blk_dev_with_part(desc, part))
return 0;
if (fs_get_type() == FS_TYPE_ANY)
return 0;
fs_close();
return 1;
}
/*
* Create a handle for a partition or disk
*
* @parent parent handle
* @dp_parent parent device path
* @if_typename interface name for block device
* @desc internal block device
* @dev_index device index for block device
* @offset offset into disk for simple partitions
* @return disk object
*/
static efi_status_t efi_disk_add_dev(
efi_handle_t parent,
struct efi_device_path *dp_parent,
const char *if_typename,
struct blk_desc *desc,
int dev_index,
lbaint_t offset,
unsigned int part,
struct efi_disk_obj **disk)
{
struct efi_disk_obj *diskobj;
efi_status_t ret;
/* Don't add empty devices */
if (!desc->lba)
return EFI_NOT_READY;
diskobj = calloc(1, sizeof(*diskobj));
if (!diskobj)
return EFI_OUT_OF_RESOURCES;
/* Hook up to the device list */
efi_add_handle(&diskobj->header);
/* Fill in object data */
if (part) {
struct efi_device_path *node = efi_dp_part_node(desc, part);
diskobj->dp = efi_dp_append_node(dp_parent, node);
efi_free_pool(node);
} else {
diskobj->dp = efi_dp_from_part(desc, part);
}
diskobj->part = part;
ret = efi_add_protocol(&diskobj->header, &efi_block_io_guid,
&diskobj->ops);
if (ret != EFI_SUCCESS)
return ret;
ret = efi_add_protocol(&diskobj->header, &efi_guid_device_path,
diskobj->dp);
if (ret != EFI_SUCCESS)
return ret;
/* partitions or whole disk without partitions */
if ((part || desc->part_type == PART_TYPE_UNKNOWN) &&
efi_fs_exists(desc, part)) {
diskobj->volume = efi_simple_file_system(desc, part,
diskobj->dp);
ret = efi_add_protocol(&diskobj->header,
&efi_simple_file_system_protocol_guid,
diskobj->volume);
if (ret != EFI_SUCCESS)
return ret;
}
diskobj->ops = block_io_disk_template;
diskobj->ifname = if_typename;
diskobj->dev_index = dev_index;
diskobj->offset = offset;
diskobj->desc = desc;
/* Fill in EFI IO Media info (for read/write callbacks) */
diskobj->media.removable_media = desc->removable;
diskobj->media.media_present = 1;
/*
* MediaID is just an arbitrary counter.
* We have to change it if the medium is removed or changed.
*/
diskobj->media.media_id = 1;
diskobj->media.block_size = desc->blksz;
diskobj->media.io_align = desc->blksz;
diskobj->media.last_block = desc->lba - offset;
if (part != 0)
diskobj->media.logical_partition = 1;
diskobj->ops.media = &diskobj->media;
if (disk)
*disk = diskobj;
return EFI_SUCCESS;
}
/*
* Create handles and protocols for the partitions of a block device
*
* @parent handle of the parent disk
* @blk_desc block device
* @if_typename interface type
* @diskid device number
* @pdevname device name
* @return number of partitions created
*/
int efi_disk_create_partitions(efi_handle_t parent, struct blk_desc *desc,
const char *if_typename, int diskid,
const char *pdevname)
{
int disks = 0;
char devname[32] = { 0 }; /* dp->str is u16[32] long */
disk_partition_t info;
int part;
struct efi_device_path *dp = NULL;
efi_status_t ret;
struct efi_handler *handler;
/* Get the device path of the parent */
ret = efi_search_protocol(parent, &efi_guid_device_path, &handler);
if (ret == EFI_SUCCESS)
dp = handler->protocol_interface;
/* Add devices for each partition */
for (part = 1; part <= MAX_SEARCH_PARTITIONS; part++) {
if (part_get_info(desc, part, &info))
continue;
snprintf(devname, sizeof(devname), "%s:%d", pdevname,
part);
ret = efi_disk_add_dev(parent, dp, if_typename, desc, diskid,
info.start, part, NULL);
if (ret != EFI_SUCCESS) {
printf("Adding partition %s failed\n", pdevname);
continue;
}
disks++;
}
return disks;
}
/*
* U-Boot doesn't have a list of all online disk devices. So when running our
* EFI payload, we scan through all of the potentially available ones and
* store them in our object pool.
*
* TODO(sjg@chromium.org): Actually with CONFIG_BLK, U-Boot does have this.
* Consider converting the code to look up devices as needed. The EFI device
* could be a child of the UCLASS_BLK block device, perhaps.
*
* This gets called from do_bootefi_exec().
*/
efi_status_t efi_disk_register(void)
{
struct efi_disk_obj *disk;
int disks = 0;
efi_status_t ret;
#ifdef CONFIG_BLK
struct udevice *dev;
for (uclass_first_device_check(UCLASS_BLK, &dev); dev;
uclass_next_device_check(&dev)) {
struct blk_desc *desc = dev_get_uclass_platdata(dev);
const char *if_typename = blk_get_if_type_name(desc->if_type);
/* Add block device for the full device */
printf("Scanning disk %s...\n", dev->name);
ret = efi_disk_add_dev(NULL, NULL, if_typename,
desc, desc->devnum, 0, 0, &disk);
if (ret == EFI_NOT_READY) {
printf("Disk %s not ready\n", dev->name);
continue;
}
if (ret) {
printf("ERROR: failure to add disk device %s, r = %lu\n",
dev->name, ret & ~EFI_ERROR_MASK);
return ret;
}
disks++;
/* Partitions show up as block devices in EFI */
disks += efi_disk_create_partitions(
&disk->header, desc, if_typename,
desc->devnum, dev->name);
}
#else
int i, if_type;
/* Search for all available disk devices */
for (if_type = 0; if_type < IF_TYPE_COUNT; if_type++) {
const struct blk_driver *cur_drvr;
const char *if_typename;
cur_drvr = blk_driver_lookup_type(if_type);
if (!cur_drvr)
continue;
if_typename = cur_drvr->if_typename;
printf("Scanning disks on %s...\n", if_typename);
for (i = 0; i < 4; i++) {
struct blk_desc *desc;
char devname[32] = { 0 }; /* dp->str is u16[32] long */
desc = blk_get_devnum_by_type(if_type, i);
if (!desc)
continue;
if (desc->type == DEV_TYPE_UNKNOWN)
continue;
snprintf(devname, sizeof(devname), "%s%d",
if_typename, i);
/* Add block device for the full device */
ret = efi_disk_add_dev(NULL, NULL, if_typename, desc,
i, 0, 0, &disk);
if (ret == EFI_NOT_READY) {
printf("Disk %s not ready\n", devname);
continue;
}
if (ret) {
printf("ERROR: failure to add disk device %s, r = %lu\n",
devname, ret & ~EFI_ERROR_MASK);
return ret;
}
disks++;
/* Partitions show up as block devices in EFI */
disks += efi_disk_create_partitions
(&disk->header, desc,
if_typename, i, devname);
}
}
#endif
printf("Found %d disks\n", disks);
return EFI_SUCCESS;
}
@@ -0,0 +1,883 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* EFI_FILE_PROTOCOL
*
* Copyright (c) 2017 Rob Clark
*/
#include <common.h>
#include <charset.h>
#include <efi_loader.h>
#include <malloc.h>
#include <mapmem.h>
#include <fs.h>
/* GUID for file system information */
const efi_guid_t efi_file_system_info_guid = EFI_FILE_SYSTEM_INFO_GUID;
/* GUID to obtain the volume label */
const efi_guid_t efi_system_volume_label_id = EFI_FILE_SYSTEM_VOLUME_LABEL_ID;
struct file_system {
struct efi_simple_file_system_protocol base;
struct efi_device_path *dp;
struct blk_desc *desc;
int part;
};
#define to_fs(x) container_of(x, struct file_system, base)
struct file_handle {
struct efi_file_handle base;
struct file_system *fs;
loff_t offset; /* current file position/cursor */
int isdir;
u64 open_mode;
/* for reading a directory: */
struct fs_dir_stream *dirs;
struct fs_dirent *dent;
char path[0];
};
#define to_fh(x) container_of(x, struct file_handle, base)
static const struct efi_file_handle efi_file_handle_protocol;
static char *basename(struct file_handle *fh)
{
char *s = strrchr(fh->path, '/');
if (s)
return s + 1;
return fh->path;
}
static int set_blk_dev(struct file_handle *fh)
{
return fs_set_blk_dev_with_part(fh->fs->desc, fh->fs->part);
}
/**
* is_dir() - check if file handle points to directory
*
* We assume that set_blk_dev(fh) has been called already.
*
* @fh: file handle
* Return: true if file handle points to a directory
*/
static int is_dir(struct file_handle *fh)
{
struct fs_dir_stream *dirs;
dirs = fs_opendir(fh->path);
if (!dirs)
return 0;
fs_closedir(dirs);
return 1;
}
/*
* Normalize a path which may include either back or fwd slashes,
* double slashes, . or .. entries in the path, etc.
*/
static int sanitize_path(char *path)
{
char *p;
/* backslash to slash: */
p = path;
while ((p = strchr(p, '\\')))
*p++ = '/';
/* handle double-slashes: */
p = path;
while ((p = strstr(p, "//"))) {
char *src = p + 1;
memmove(p, src, strlen(src) + 1);
}
/* handle extra /.'s */
p = path;
while ((p = strstr(p, "/."))) {
/*
* You'd be tempted to do this *after* handling ".."s
* below to avoid having to check if "/." is start of
* a "/..", but that won't have the correct results..
* for example, "/foo/./../bar" would get resolved to
* "/foo/bar" if you did these two passes in the other
* order
*/
if (p[2] == '.') {
p += 2;
continue;
}
char *src = p + 2;
memmove(p, src, strlen(src) + 1);
}
/* handle extra /..'s: */
p = path;
while ((p = strstr(p, "/.."))) {
char *src = p + 3;
p--;
/* find beginning of previous path entry: */
while (true) {
if (p < path)
return -1;
if (*p == '/')
break;
p--;
}
memmove(p, src, strlen(src) + 1);
}
return 0;
}
/**
* efi_create_file() - create file or directory
*
* @fh: file handle
* @attributes: attributes for newly created file
* Returns: 0 for success
*/
static int efi_create_file(struct file_handle *fh, u64 attributes)
{
loff_t actwrite;
void *buffer = &actwrite;
if (attributes & EFI_FILE_DIRECTORY)
return fs_mkdir(fh->path);
else
return fs_write(fh->path, map_to_sysmem(buffer), 0, 0,
&actwrite);
}
/**
* file_open() - open a file handle
*
* @fs: file system
* @parent: directory relative to which the file is to be opened
* @file_name: path of the file to be opened. '\', '.', or '..' may
* be used as modifiers. A leading backslash indicates an
* absolute path.
* @open_mode: bit mask indicating the access mode (read, write,
* create)
* @attributes: attributes for newly created file
* Returns: handle to the opened file or NULL
*/
static struct efi_file_handle *file_open(struct file_system *fs,
struct file_handle *parent, u16 *file_name, u64 open_mode,
u64 attributes)
{
struct file_handle *fh;
char f0[MAX_UTF8_PER_UTF16] = {0};
int plen = 0;
int flen = 0;
if (file_name) {
utf16_to_utf8((u8 *)f0, file_name, 1);
flen = u16_strlen(file_name);
}
/* we could have a parent, but also an absolute path: */
if (f0[0] == '\\') {
plen = 0;
} else if (parent) {
plen = strlen(parent->path) + 1;
}
/* +2 is for null and '/' */
fh = calloc(1, sizeof(*fh) + plen + (flen * MAX_UTF8_PER_UTF16) + 2);
fh->open_mode = open_mode;
fh->base = efi_file_handle_protocol;
fh->fs = fs;
if (parent) {
char *p = fh->path;
int exists;
if (plen > 0) {
strcpy(p, parent->path);
p += plen - 1;
*p++ = '/';
}
utf16_to_utf8((u8 *)p, file_name, flen);
if (sanitize_path(fh->path))
goto error;
/* check if file exists: */
if (set_blk_dev(fh))
goto error;
exists = fs_exists(fh->path);
/* fs_exists() calls fs_close(), so open file system again */
if (set_blk_dev(fh))
goto error;
if (!exists) {
if (!(open_mode & EFI_FILE_MODE_CREATE) ||
efi_create_file(fh, attributes))
goto error;
if (set_blk_dev(fh))
goto error;
}
/* figure out if file is a directory: */
fh->isdir = is_dir(fh);
} else {
fh->isdir = 1;
strcpy(fh->path, "");
}
return &fh->base;
error:
free(fh);
return NULL;
}
static efi_status_t EFIAPI efi_file_open(struct efi_file_handle *file,
struct efi_file_handle **new_handle,
u16 *file_name, u64 open_mode, u64 attributes)
{
struct file_handle *fh = to_fh(file);
efi_status_t ret;
EFI_ENTRY("%p, %p, \"%ls\", %llx, %llu", file, new_handle,
file_name, open_mode, attributes);
/* Check parameters */
if (!file || !new_handle || !file_name) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
if (open_mode != EFI_FILE_MODE_READ &&
open_mode != (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE) &&
open_mode != (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE |
EFI_FILE_MODE_CREATE)) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
/*
* The UEFI spec requires that attributes are only set in create mode.
* The SCT does not care about this and sets EFI_FILE_DIRECTORY in
* read mode. EDK2 does not check that attributes are zero if not in
* create mode.
*
* So here we only check attributes in create mode and do not check
* that they are zero otherwise.
*/
if ((open_mode & EFI_FILE_MODE_CREATE) &&
(attributes & (EFI_FILE_READ_ONLY | ~EFI_FILE_VALID_ATTR))) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
/* Open file */
*new_handle = file_open(fh->fs, fh, file_name, open_mode, attributes);
if (*new_handle) {
EFI_PRINT("file handle %p\n", *new_handle);
ret = EFI_SUCCESS;
} else {
ret = EFI_NOT_FOUND;
}
out:
return EFI_EXIT(ret);
}
static efi_status_t file_close(struct file_handle *fh)
{
fs_closedir(fh->dirs);
free(fh);
return EFI_SUCCESS;
}
static efi_status_t EFIAPI efi_file_close(struct efi_file_handle *file)
{
struct file_handle *fh = to_fh(file);
EFI_ENTRY("%p", file);
return EFI_EXIT(file_close(fh));
}
static efi_status_t EFIAPI efi_file_delete(struct efi_file_handle *file)
{
struct file_handle *fh = to_fh(file);
efi_status_t ret = EFI_SUCCESS;
EFI_ENTRY("%p", file);
if (set_blk_dev(fh) || fs_unlink(fh->path))
ret = EFI_WARN_DELETE_FAILURE;
file_close(fh);
return EFI_EXIT(ret);
}
/**
* efi_get_file_size() - determine the size of a file
*
* @fh: file handle
* @file_size: pointer to receive file size
* Return: status code
*/
static efi_status_t efi_get_file_size(struct file_handle *fh,
loff_t *file_size)
{
if (set_blk_dev(fh))
return EFI_DEVICE_ERROR;
if (fs_size(fh->path, file_size))
return EFI_DEVICE_ERROR;
return EFI_SUCCESS;
}
static efi_status_t file_read(struct file_handle *fh, u64 *buffer_size,
void *buffer)
{
loff_t actread;
efi_status_t ret;
loff_t file_size;
ret = efi_get_file_size(fh, &file_size);
if (ret != EFI_SUCCESS)
return ret;
if (file_size < fh->offset) {
ret = EFI_DEVICE_ERROR;
return ret;
}
if (set_blk_dev(fh))
return EFI_DEVICE_ERROR;
if (fs_read(fh->path, map_to_sysmem(buffer), fh->offset,
*buffer_size, &actread))
return EFI_DEVICE_ERROR;
*buffer_size = actread;
fh->offset += actread;
return EFI_SUCCESS;
}
static efi_status_t dir_read(struct file_handle *fh, u64 *buffer_size,
void *buffer)
{
struct efi_file_info *info = buffer;
struct fs_dirent *dent;
u64 required_size;
u16 *dst;
if (set_blk_dev(fh))
return EFI_DEVICE_ERROR;
if (!fh->dirs) {
assert(fh->offset == 0);
fh->dirs = fs_opendir(fh->path);
if (!fh->dirs)
return EFI_DEVICE_ERROR;
fh->dent = NULL;
}
/*
* So this is a bit awkward. Since fs layer is stateful and we
* can't rewind an entry, in the EFI_BUFFER_TOO_SMALL case below
* we might have to return without consuming the dent.. so we
* have to stash it for next call.
*/
if (fh->dent) {
dent = fh->dent;
} else {
dent = fs_readdir(fh->dirs);
}
if (!dent) {
/* no more files in directory */
*buffer_size = 0;
return EFI_SUCCESS;
}
/* check buffer size: */
required_size = sizeof(*info) +
2 * (utf8_utf16_strlen(dent->name) + 1);
if (*buffer_size < required_size) {
*buffer_size = required_size;
fh->dent = dent;
return EFI_BUFFER_TOO_SMALL;
}
fh->dent = NULL;
*buffer_size = required_size;
memset(info, 0, required_size);
info->size = required_size;
info->file_size = dent->size;
info->physical_size = dent->size;
if (dent->type == FS_DT_DIR)
info->attribute |= EFI_FILE_DIRECTORY;
dst = info->file_name;
utf8_utf16_strcpy(&dst, dent->name);
fh->offset++;
return EFI_SUCCESS;
}
static efi_status_t EFIAPI efi_file_read(struct efi_file_handle *file,
efi_uintn_t *buffer_size, void *buffer)
{
struct file_handle *fh = to_fh(file);
efi_status_t ret = EFI_SUCCESS;
u64 bs;
EFI_ENTRY("%p, %p, %p", file, buffer_size, buffer);
if (!buffer_size || !buffer) {
ret = EFI_INVALID_PARAMETER;
goto error;
}
bs = *buffer_size;
if (fh->isdir)
ret = dir_read(fh, &bs, buffer);
else
ret = file_read(fh, &bs, buffer);
if (bs <= SIZE_MAX)
*buffer_size = bs;
else
*buffer_size = SIZE_MAX;
error:
return EFI_EXIT(ret);
}
/**
* efi_file_write() - write to file
*
* This function implements the Write() service of the EFI_FILE_PROTOCOL.
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* @file: file handle
* @buffer_size: number of bytes to write
* @buffer: buffer with the bytes to write
* Return: status code
*/
static efi_status_t EFIAPI efi_file_write(struct efi_file_handle *file,
efi_uintn_t *buffer_size,
void *buffer)
{
struct file_handle *fh = to_fh(file);
efi_status_t ret = EFI_SUCCESS;
loff_t actwrite;
EFI_ENTRY("%p, %p, %p", file, buffer_size, buffer);
if (!file || !buffer_size || !buffer) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
if (fh->isdir) {
ret = EFI_UNSUPPORTED;
goto out;
}
if (!(fh->open_mode & EFI_FILE_MODE_WRITE)) {
ret = EFI_ACCESS_DENIED;
goto out;
}
if (!*buffer_size)
goto out;
if (set_blk_dev(fh)) {
ret = EFI_DEVICE_ERROR;
goto out;
}
if (fs_write(fh->path, map_to_sysmem(buffer), fh->offset, *buffer_size,
&actwrite)) {
ret = EFI_DEVICE_ERROR;
goto out;
}
*buffer_size = actwrite;
fh->offset += actwrite;
out:
return EFI_EXIT(ret);
}
/**
* efi_file_getpos() - get current position in file
*
* This function implements the GetPosition service of the EFI file protocol.
* See the UEFI spec for details.
*
* @file: file handle
* @pos: pointer to file position
* Return: status code
*/
static efi_status_t EFIAPI efi_file_getpos(struct efi_file_handle *file,
u64 *pos)
{
efi_status_t ret = EFI_SUCCESS;
struct file_handle *fh = to_fh(file);
EFI_ENTRY("%p, %p", file, pos);
if (fh->isdir) {
ret = EFI_UNSUPPORTED;
goto out;
}
*pos = fh->offset;
out:
return EFI_EXIT(ret);
}
/**
* efi_file_setpos() - set current position in file
*
* This function implements the SetPosition service of the EFI file protocol.
* See the UEFI spec for details.
*
* @file: file handle
* @pos: new file position
* Return: status code
*/
static efi_status_t EFIAPI efi_file_setpos(struct efi_file_handle *file,
u64 pos)
{
struct file_handle *fh = to_fh(file);
efi_status_t ret = EFI_SUCCESS;
EFI_ENTRY("%p, %llu", file, pos);
if (fh->isdir) {
if (pos != 0) {
ret = EFI_UNSUPPORTED;
goto error;
}
fs_closedir(fh->dirs);
fh->dirs = NULL;
}
if (pos == ~0ULL) {
loff_t file_size;
ret = efi_get_file_size(fh, &file_size);
if (ret != EFI_SUCCESS)
goto error;
pos = file_size;
}
fh->offset = pos;
error:
return EFI_EXIT(ret);
}
static efi_status_t EFIAPI efi_file_getinfo(struct efi_file_handle *file,
const efi_guid_t *info_type,
efi_uintn_t *buffer_size,
void *buffer)
{
struct file_handle *fh = to_fh(file);
efi_status_t ret = EFI_SUCCESS;
u16 *dst;
EFI_ENTRY("%p, %pUl, %p, %p", file, info_type, buffer_size, buffer);
if (!file || !info_type || !buffer_size ||
(*buffer_size && !buffer)) {
ret = EFI_INVALID_PARAMETER;
goto error;
}
if (!guidcmp(info_type, &efi_file_info_guid)) {
struct efi_file_info *info = buffer;
char *filename = basename(fh);
unsigned int required_size;
loff_t file_size;
/* check buffer size: */
required_size = sizeof(*info) +
2 * (utf8_utf16_strlen(filename) + 1);
if (*buffer_size < required_size) {
*buffer_size = required_size;
ret = EFI_BUFFER_TOO_SMALL;
goto error;
}
ret = efi_get_file_size(fh, &file_size);
if (ret != EFI_SUCCESS)
goto error;
memset(info, 0, required_size);
info->size = required_size;
info->file_size = file_size;
info->physical_size = file_size;
if (fh->isdir)
info->attribute |= EFI_FILE_DIRECTORY;
dst = info->file_name;
utf8_utf16_strcpy(&dst, filename);
} else if (!guidcmp(info_type, &efi_file_system_info_guid)) {
struct efi_file_system_info *info = buffer;
disk_partition_t part;
efi_uintn_t required_size;
int r;
if (fh->fs->part >= 1)
r = part_get_info(fh->fs->desc, fh->fs->part, &part);
else
r = part_get_info_whole_disk(fh->fs->desc, &part);
if (r < 0) {
ret = EFI_DEVICE_ERROR;
goto error;
}
required_size = sizeof(*info) + 2;
if (*buffer_size < required_size) {
*buffer_size = required_size;
ret = EFI_BUFFER_TOO_SMALL;
goto error;
}
memset(info, 0, required_size);
info->size = required_size;
info->read_only = true;
info->volume_size = part.size * part.blksz;
info->free_space = 0;
info->block_size = part.blksz;
/*
* TODO: The volume label is not available in U-Boot.
*/
info->volume_label[0] = 0;
} else if (!guidcmp(info_type, &efi_system_volume_label_id)) {
if (*buffer_size < 2) {
*buffer_size = 2;
ret = EFI_BUFFER_TOO_SMALL;
goto error;
}
*(u16 *)buffer = 0;
} else {
ret = EFI_UNSUPPORTED;
}
error:
return EFI_EXIT(ret);
}
static efi_status_t EFIAPI efi_file_setinfo(struct efi_file_handle *file,
const efi_guid_t *info_type,
efi_uintn_t buffer_size,
void *buffer)
{
struct file_handle *fh = to_fh(file);
efi_status_t ret = EFI_UNSUPPORTED;
EFI_ENTRY("%p, %pUl, %zu, %p", file, info_type, buffer_size, buffer);
if (!guidcmp(info_type, &efi_file_info_guid)) {
struct efi_file_info *info = (struct efi_file_info *)buffer;
char *filename = basename(fh);
char *new_file_name, *pos;
loff_t file_size;
/* The buffer will always contain a file name. */
if (buffer_size < sizeof(struct efi_file_info) + 2 ||
buffer_size < info->size) {
ret = EFI_BAD_BUFFER_SIZE;
goto out;
}
/* We cannot change the directory attribute */
if (!fh->isdir != !(info->attribute & EFI_FILE_DIRECTORY)) {
ret = EFI_ACCESS_DENIED;
goto out;
}
/* Check for renaming */
new_file_name = malloc(utf16_utf8_strlen(info->file_name));
if (!new_file_name) {
ret = EFI_OUT_OF_RESOURCES;
goto out;
}
pos = new_file_name;
utf16_utf8_strcpy(&pos, info->file_name);
if (strcmp(new_file_name, filename)) {
/* TODO: we do not support renaming */
EFI_PRINT("Renaming not supported\n");
free(new_file_name);
ret = EFI_ACCESS_DENIED;
goto out;
}
free(new_file_name);
/* Check for truncation */
ret = efi_get_file_size(fh, &file_size);
if (ret != EFI_SUCCESS)
goto out;
if (file_size != info->file_size) {
/* TODO: we do not support truncation */
EFI_PRINT("Truncation not supported\n");
ret = EFI_ACCESS_DENIED;
goto out;
}
/*
* We do not care for the other attributes
* TODO: Support read only
*/
ret = EFI_SUCCESS;
} else {
/* TODO: We do not support changing the volume label */
ret = EFI_UNSUPPORTED;
}
out:
return EFI_EXIT(ret);
}
static efi_status_t EFIAPI efi_file_flush(struct efi_file_handle *file)
{
EFI_ENTRY("%p", file);
return EFI_EXIT(EFI_SUCCESS);
}
static efi_status_t EFIAPI efi_file_open_ex(struct efi_file_handle *file,
struct efi_file_handle **new_handle,
u16 *file_name, u64 open_mode, u64 attributes,
struct efi_file_io_token *token)
{
return EFI_UNSUPPORTED;
}
static efi_status_t EFIAPI efi_file_read_ex(struct efi_file_handle *file,
struct efi_file_io_token *token)
{
return EFI_UNSUPPORTED;
}
static efi_status_t EFIAPI efi_file_write_ex(struct efi_file_handle *file,
struct efi_file_io_token *token)
{
return EFI_UNSUPPORTED;
}
static efi_status_t EFIAPI efi_file_flush_ex(struct efi_file_handle *file,
struct efi_file_io_token *token)
{
return EFI_UNSUPPORTED;
}
static const struct efi_file_handle efi_file_handle_protocol = {
.rev = EFI_FILE_PROTOCOL_REVISION2,
.open = efi_file_open,
.close = efi_file_close,
.delete = efi_file_delete,
.read = efi_file_read,
.write = efi_file_write,
.getpos = efi_file_getpos,
.setpos = efi_file_setpos,
.getinfo = efi_file_getinfo,
.setinfo = efi_file_setinfo,
.flush = efi_file_flush,
.open_ex = efi_file_open_ex,
.read_ex = efi_file_read_ex,
.write_ex = efi_file_write_ex,
.flush_ex = efi_file_flush_ex,
};
/**
* efi_file_from_path() - open file via device path
*
* @fp: device path
* @return: EFI_FILE_PROTOCOL for the file or NULL
*/
struct efi_file_handle *efi_file_from_path(struct efi_device_path *fp)
{
struct efi_simple_file_system_protocol *v;
struct efi_file_handle *f;
efi_status_t ret;
v = efi_fs_from_path(fp);
if (!v)
return NULL;
EFI_CALL(ret = v->open_volume(v, &f));
if (ret != EFI_SUCCESS)
return NULL;
/* Skip over device-path nodes before the file path. */
while (fp && !EFI_DP_TYPE(fp, MEDIA_DEVICE, FILE_PATH))
fp = efi_dp_next(fp);
/*
* Step through the nodes of the directory path until the actual file
* node is reached which is the final node in the device path.
*/
while (fp) {
struct efi_device_path_file_path *fdp =
container_of(fp, struct efi_device_path_file_path, dp);
struct efi_file_handle *f2;
u16 *filename;
if (!EFI_DP_TYPE(fp, MEDIA_DEVICE, FILE_PATH)) {
printf("bad file path!\n");
f->close(f);
return NULL;
}
filename = u16_strdup(fdp->str);
if (!filename)
return NULL;
EFI_CALL(ret = f->open(f, &f2, filename,
EFI_FILE_MODE_READ, 0));
free(filename);
if (ret != EFI_SUCCESS)
return NULL;
fp = efi_dp_next(fp);
EFI_CALL(f->close(f));
f = f2;
}
return f;
}
static efi_status_t EFIAPI
efi_open_volume(struct efi_simple_file_system_protocol *this,
struct efi_file_handle **root)
{
struct file_system *fs = to_fs(this);
EFI_ENTRY("%p, %p", this, root);
*root = file_open(fs, NULL, NULL, 0, 0);
return EFI_EXIT(EFI_SUCCESS);
}
struct efi_simple_file_system_protocol *
efi_simple_file_system(struct blk_desc *desc, int part,
struct efi_device_path *dp)
{
struct file_system *fs;
fs = calloc(1, sizeof(*fs));
fs->base.rev = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION;
fs->base.open_volume = efi_open_volume;
fs->desc = desc;
fs->part = part;
fs->dp = dp;
return &fs->base;
}
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Library for freestanding binary
*
* Copyright 2019, Heinrich Schuchardt <xypron.glpk@gmx.de>
*
* GCC requires that freestanding programs provide memcpy(), memmove(),
* memset(), and memcmp().
*/
#include <common.h>
/**
* memcmp() - compare memory areas
*
* @s1: pointer to first area
* @s2: pointer to second area
* @n: number of bytes to compare
* Return: 0 if both memory areas are the same, otherwise the sign of the
* result value is the same as the sign of the difference between
* the first differing pair of bytes taken as u8.
*/
int memcmp(const void *s1, const void *s2, size_t n)
{
const u8 *pos1 = s1;
const u8 *pos2 = s2;
for (; n; --n) {
if (*pos1 != *pos2)
return *pos1 - *pos2;
++pos1;
++pos2;
}
return 0;
}
/**
* memcpy() - copy memory area
*
* @dest: destination buffer
* @src: source buffer
* @n: number of bytes to copy
* Return: pointer to destination buffer
*/
void *memmove(void *dest, const void *src, size_t n)
{
u8 *d = dest;
const u8 *s = src;
if (d >= s) {
for (; n; --n)
*d++ = *s++;
} else {
d += n;
s += n;
for (; n; --n)
*--d = *--s;
}
return dest;
}
/**
* memcpy() - copy memory area
*
* @dest: destination buffer
* @src: source buffer
* @n: number of bytes to copy
* Return: pointer to destination buffer
*/
void *memcpy(void *dest, const void *src, size_t n)
{
return memmove(dest, src, n);
}
/**
* memset() - fill memory with a constant byte
*
* @s: destination buffer
* @c: byte value
* @n: number of bytes to set
* Return: pointer to destination buffer
*/
void *memset(void *s, int c, size_t n)
{
u8 *d = s;
for (; n; --n)
*d++ = c;
return s;
}
@@ -0,0 +1,530 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* EFI application disk support
*
* Copyright (c) 2016 Alexander Graf
*/
#include <common.h>
#include <dm.h>
#include <efi_loader.h>
#include <lcd.h>
#include <malloc.h>
#include <video.h>
DECLARE_GLOBAL_DATA_PTR;
static const efi_guid_t efi_gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
/**
* struct efi_gop_obj - graphical output protocol object
*
* @header: EFI object header
* @ops: graphical output protocol interface
* @info: graphical output mode information
* @mode: graphical output mode
* @bpix: bits per pixel
* @fb: frame buffer
*/
struct efi_gop_obj {
struct efi_object header;
struct efi_gop ops;
struct efi_gop_mode_info info;
struct efi_gop_mode mode;
/* Fields we only have access to during init */
u32 bpix;
void *fb;
};
static efi_status_t EFIAPI gop_query_mode(struct efi_gop *this, u32 mode_number,
efi_uintn_t *size_of_info,
struct efi_gop_mode_info **info)
{
struct efi_gop_obj *gopobj;
efi_status_t ret = EFI_SUCCESS;
EFI_ENTRY("%p, %x, %p, %p", this, mode_number, size_of_info, info);
if (!this || !size_of_info || !info || mode_number) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
gopobj = container_of(this, struct efi_gop_obj, ops);
ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, sizeof(gopobj->info),
(void **)info);
if (ret != EFI_SUCCESS)
goto out;
*size_of_info = sizeof(gopobj->info);
memcpy(*info, &gopobj->info, sizeof(gopobj->info));
out:
return EFI_EXIT(ret);
}
static __always_inline struct efi_gop_pixel efi_vid16_to_blt_col(u16 vid)
{
struct efi_gop_pixel blt = {
.reserved = 0,
};
blt.blue = (vid & 0x1f) << 3;
vid >>= 5;
blt.green = (vid & 0x3f) << 2;
vid >>= 6;
blt.red = (vid & 0x1f) << 3;
return blt;
}
static __always_inline u16 efi_blt_col_to_vid16(struct efi_gop_pixel *blt)
{
return (u16)(blt->red >> 3) << 11 |
(u16)(blt->green >> 2) << 5 |
(u16)(blt->blue >> 3);
}
static __always_inline efi_status_t gop_blt_int(struct efi_gop *this,
struct efi_gop_pixel *bufferp,
u32 operation, efi_uintn_t sx,
efi_uintn_t sy, efi_uintn_t dx,
efi_uintn_t dy,
efi_uintn_t width,
efi_uintn_t height,
efi_uintn_t delta,
efi_uintn_t vid_bpp)
{
struct efi_gop_obj *gopobj = container_of(this, struct efi_gop_obj, ops);
efi_uintn_t i, j, linelen, slineoff = 0, dlineoff, swidth, dwidth;
u32 *fb32 = gopobj->fb;
u16 *fb16 = gopobj->fb;
struct efi_gop_pixel *buffer = __builtin_assume_aligned(bufferp, 4);
if (delta) {
/* Check for 4 byte alignment */
if (delta & 3)
return EFI_INVALID_PARAMETER;
linelen = delta >> 2;
} else {
linelen = width;
}
/* Check source rectangle */
switch (operation) {
case EFI_BLT_VIDEO_FILL:
break;
case EFI_BLT_BUFFER_TO_VIDEO:
if (sx + width > linelen)
return EFI_INVALID_PARAMETER;
break;
case EFI_BLT_VIDEO_TO_BLT_BUFFER:
case EFI_BLT_VIDEO_TO_VIDEO:
if (sx + width > gopobj->info.width ||
sy + height > gopobj->info.height)
return EFI_INVALID_PARAMETER;
break;
default:
return EFI_INVALID_PARAMETER;
}
/* Check destination rectangle */
switch (operation) {
case EFI_BLT_VIDEO_FILL:
case EFI_BLT_BUFFER_TO_VIDEO:
case EFI_BLT_VIDEO_TO_VIDEO:
if (dx + width > gopobj->info.width ||
dy + height > gopobj->info.height)
return EFI_INVALID_PARAMETER;
break;
case EFI_BLT_VIDEO_TO_BLT_BUFFER:
if (dx + width > linelen)
return EFI_INVALID_PARAMETER;
break;
}
/* Calculate line width */
switch (operation) {
case EFI_BLT_BUFFER_TO_VIDEO:
swidth = linelen;
break;
case EFI_BLT_VIDEO_TO_BLT_BUFFER:
case EFI_BLT_VIDEO_TO_VIDEO:
swidth = gopobj->info.width;
if (!vid_bpp)
return EFI_UNSUPPORTED;
break;
case EFI_BLT_VIDEO_FILL:
swidth = 0;
break;
}
switch (operation) {
case EFI_BLT_BUFFER_TO_VIDEO:
case EFI_BLT_VIDEO_FILL:
case EFI_BLT_VIDEO_TO_VIDEO:
dwidth = gopobj->info.width;
if (!vid_bpp)
return EFI_UNSUPPORTED;
break;
case EFI_BLT_VIDEO_TO_BLT_BUFFER:
dwidth = linelen;
break;
}
slineoff = swidth * sy;
dlineoff = dwidth * dy;
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
struct efi_gop_pixel pix;
/* Read source pixel */
switch (operation) {
case EFI_BLT_VIDEO_FILL:
pix = *buffer;
break;
case EFI_BLT_BUFFER_TO_VIDEO:
pix = buffer[slineoff + j + sx];
break;
case EFI_BLT_VIDEO_TO_BLT_BUFFER:
case EFI_BLT_VIDEO_TO_VIDEO:
if (vid_bpp == 32)
pix = *(struct efi_gop_pixel *)&fb32[
slineoff + j + sx];
else
pix = efi_vid16_to_blt_col(fb16[
slineoff + j + sx]);
break;
}
/* Write destination pixel */
switch (operation) {
case EFI_BLT_VIDEO_TO_BLT_BUFFER:
buffer[dlineoff + j + dx] = pix;
break;
case EFI_BLT_BUFFER_TO_VIDEO:
case EFI_BLT_VIDEO_FILL:
case EFI_BLT_VIDEO_TO_VIDEO:
if (vid_bpp == 32)
fb32[dlineoff + j + dx] = *(u32 *)&pix;
else
fb16[dlineoff + j + dx] =
efi_blt_col_to_vid16(&pix);
break;
}
}
slineoff += swidth;
dlineoff += dwidth;
}
return EFI_SUCCESS;
}
static efi_uintn_t gop_get_bpp(struct efi_gop *this)
{
struct efi_gop_obj *gopobj = container_of(this, struct efi_gop_obj, ops);
efi_uintn_t vid_bpp = 0;
switch (gopobj->bpix) {
#ifdef CONFIG_DM_VIDEO
case VIDEO_BPP32:
#else
case LCD_COLOR32:
#endif
vid_bpp = 32;
break;
#ifdef CONFIG_DM_VIDEO
case VIDEO_BPP16:
#else
case LCD_COLOR16:
#endif
vid_bpp = 16;
break;
}
return vid_bpp;
}
/*
* GCC can't optimize our BLT function well, but we need to make sure that
* our 2-dimensional loop gets executed very quickly, otherwise the system
* will feel slow.
*
* By manually putting all obvious branch targets into functions which call
* our generic BLT function with constants, the compiler can successfully
* optimize for speed.
*/
static efi_status_t gop_blt_video_fill(struct efi_gop *this,
struct efi_gop_pixel *buffer,
u32 foo, efi_uintn_t sx,
efi_uintn_t sy, efi_uintn_t dx,
efi_uintn_t dy, efi_uintn_t width,
efi_uintn_t height, efi_uintn_t delta,
efi_uintn_t vid_bpp)
{
return gop_blt_int(this, buffer, EFI_BLT_VIDEO_FILL, sx, sy, dx,
dy, width, height, delta, vid_bpp);
}
static efi_status_t gop_blt_buf_to_vid16(struct efi_gop *this,
struct efi_gop_pixel *buffer,
u32 foo, efi_uintn_t sx,
efi_uintn_t sy, efi_uintn_t dx,
efi_uintn_t dy, efi_uintn_t width,
efi_uintn_t height, efi_uintn_t delta)
{
return gop_blt_int(this, buffer, EFI_BLT_BUFFER_TO_VIDEO, sx, sy, dx,
dy, width, height, delta, 16);
}
static efi_status_t gop_blt_buf_to_vid32(struct efi_gop *this,
struct efi_gop_pixel *buffer,
u32 foo, efi_uintn_t sx,
efi_uintn_t sy, efi_uintn_t dx,
efi_uintn_t dy, efi_uintn_t width,
efi_uintn_t height, efi_uintn_t delta)
{
return gop_blt_int(this, buffer, EFI_BLT_BUFFER_TO_VIDEO, sx, sy, dx,
dy, width, height, delta, 32);
}
static efi_status_t gop_blt_vid_to_vid(struct efi_gop *this,
struct efi_gop_pixel *buffer,
u32 foo, efi_uintn_t sx,
efi_uintn_t sy, efi_uintn_t dx,
efi_uintn_t dy, efi_uintn_t width,
efi_uintn_t height, efi_uintn_t delta,
efi_uintn_t vid_bpp)
{
return gop_blt_int(this, buffer, EFI_BLT_VIDEO_TO_VIDEO, sx, sy, dx,
dy, width, height, delta, vid_bpp);
}
static efi_status_t gop_blt_vid_to_buf(struct efi_gop *this,
struct efi_gop_pixel *buffer,
u32 foo, efi_uintn_t sx,
efi_uintn_t sy, efi_uintn_t dx,
efi_uintn_t dy, efi_uintn_t width,
efi_uintn_t height, efi_uintn_t delta,
efi_uintn_t vid_bpp)
{
return gop_blt_int(this, buffer, EFI_BLT_VIDEO_TO_BLT_BUFFER, sx, sy,
dx, dy, width, height, delta, vid_bpp);
}
/**
* gop_set_mode() - set graphical output mode
*
* This function implements the SetMode() service.
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* @this: the graphical output protocol
* @mode_number: the mode to be set
* Return: status code
*/
static efi_status_t EFIAPI gop_set_mode(struct efi_gop *this, u32 mode_number)
{
struct efi_gop_obj *gopobj;
struct efi_gop_pixel buffer = {0, 0, 0, 0};
efi_uintn_t vid_bpp;
efi_status_t ret = EFI_SUCCESS;
EFI_ENTRY("%p, %x", this, mode_number);
if (!this) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
if (mode_number) {
ret = EFI_UNSUPPORTED;
goto out;
}
gopobj = container_of(this, struct efi_gop_obj, ops);
vid_bpp = gop_get_bpp(this);
ret = gop_blt_video_fill(this, &buffer, EFI_BLT_VIDEO_FILL, 0, 0, 0, 0,
gopobj->info.width, gopobj->info.height, 0,
vid_bpp);
out:
return EFI_EXIT(ret);
}
/*
* Copy rectangle.
*
* This function implements the Blt service of the EFI_GRAPHICS_OUTPUT_PROTOCOL.
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* @this: EFI_GRAPHICS_OUTPUT_PROTOCOL
* @buffer: pixel buffer
* @sx: source x-coordinate
* @sy: source y-coordinate
* @dx: destination x-coordinate
* @dy: destination y-coordinate
* @width: width of rectangle
* @height: height of rectangle
* @delta: length in bytes of a line in the pixel buffer (optional)
* @return: status code
*/
efi_status_t EFIAPI gop_blt(struct efi_gop *this, struct efi_gop_pixel *buffer,
u32 operation, efi_uintn_t sx,
efi_uintn_t sy, efi_uintn_t dx,
efi_uintn_t dy, efi_uintn_t width,
efi_uintn_t height, efi_uintn_t delta)
{
efi_status_t ret = EFI_INVALID_PARAMETER;
efi_uintn_t vid_bpp;
EFI_ENTRY("%p, %p, %u, %zu, %zu, %zu, %zu, %zu, %zu, %zu", this,
buffer, operation, sx, sy, dx, dy, width, height, delta);
vid_bpp = gop_get_bpp(this);
/* Allow for compiler optimization */
switch (operation) {
case EFI_BLT_VIDEO_FILL:
ret = gop_blt_video_fill(this, buffer, operation, sx, sy, dx,
dy, width, height, delta, vid_bpp);
break;
case EFI_BLT_BUFFER_TO_VIDEO:
/* This needs to be super-fast, so duplicate for 16/32bpp */
if (vid_bpp == 32)
ret = gop_blt_buf_to_vid32(this, buffer, operation, sx,
sy, dx, dy, width, height,
delta);
else
ret = gop_blt_buf_to_vid16(this, buffer, operation, sx,
sy, dx, dy, width, height,
delta);
break;
case EFI_BLT_VIDEO_TO_VIDEO:
ret = gop_blt_vid_to_vid(this, buffer, operation, sx, sy, dx,
dy, width, height, delta, vid_bpp);
break;
case EFI_BLT_VIDEO_TO_BLT_BUFFER:
ret = gop_blt_vid_to_buf(this, buffer, operation, sx, sy, dx,
dy, width, height, delta, vid_bpp);
break;
default:
ret = EFI_INVALID_PARAMETER;
}
if (ret != EFI_SUCCESS)
return EFI_EXIT(ret);
#ifdef CONFIG_DM_VIDEO
video_sync_all();
#else
lcd_sync();
#endif
return EFI_EXIT(EFI_SUCCESS);
}
/*
* Install graphical output protocol.
*
* If no supported video device exists this is not considered as an
* error.
*/
efi_status_t efi_gop_register(void)
{
struct efi_gop_obj *gopobj;
u32 bpix, col, row;
u64 fb_base, fb_size;
void *fb;
efi_status_t ret;
#ifdef CONFIG_DM_VIDEO
struct udevice *vdev;
struct video_priv *priv;
/* We only support a single video output device for now */
if (uclass_first_device(UCLASS_VIDEO, &vdev) || !vdev) {
debug("WARNING: No video device\n");
return EFI_SUCCESS;
}
priv = dev_get_uclass_priv(vdev);
bpix = priv->bpix;
col = video_get_xsize(vdev);
row = video_get_ysize(vdev);
fb_base = (uintptr_t)priv->fb;
fb_size = priv->fb_size;
fb = priv->fb;
#else
int line_len;
bpix = panel_info.vl_bpix;
col = panel_info.vl_col;
row = panel_info.vl_row;
fb_base = gd->fb_base;
fb_size = lcd_get_size(&line_len);
fb = (void*)gd->fb_base;
#endif
switch (bpix) {
#ifdef CONFIG_DM_VIDEO
case VIDEO_BPP16:
case VIDEO_BPP32:
#else
case LCD_COLOR32:
case LCD_COLOR16:
#endif
break;
default:
/* So far, we only work in 16 or 32 bit mode */
debug("WARNING: Unsupported video mode\n");
return EFI_SUCCESS;
}
gopobj = calloc(1, sizeof(*gopobj));
if (!gopobj) {
printf("ERROR: Out of memory\n");
return EFI_OUT_OF_RESOURCES;
}
/* Hook up to the device list */
efi_add_handle(&gopobj->header);
/* Fill in object data */
ret = efi_add_protocol(&gopobj->header, &efi_gop_guid,
&gopobj->ops);
if (ret != EFI_SUCCESS) {
printf("ERROR: Failure adding GOP protocol\n");
return ret;
}
gopobj->ops.query_mode = gop_query_mode;
gopobj->ops.set_mode = gop_set_mode;
gopobj->ops.blt = gop_blt;
gopobj->ops.mode = &gopobj->mode;
gopobj->mode.max_mode = 1;
gopobj->mode.info = &gopobj->info;
gopobj->mode.info_size = sizeof(gopobj->info);
gopobj->mode.fb_base = fb_base;
gopobj->mode.fb_size = fb_size;
gopobj->info.version = 0;
gopobj->info.width = col;
gopobj->info.height = row;
#ifdef CONFIG_DM_VIDEO
if (bpix == VIDEO_BPP32)
#else
if (bpix == LCD_COLOR32)
#endif
{
gopobj->info.pixel_format = EFI_GOT_BGRA8;
} else {
gopobj->info.pixel_format = EFI_GOT_BITMASK;
gopobj->info.pixel_bitmask[0] = 0xf800; /* red */
gopobj->info.pixel_bitmask[1] = 0x07e0; /* green */
gopobj->info.pixel_bitmask[2] = 0x001f; /* blue */
}
gopobj->info.pixels_per_scanline = col;
gopobj->bpix = bpix;
gopobj->fb = fb;
return EFI_SUCCESS;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,146 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* EFI Human Interface Infrastructure ... Configuration
*
* Copyright (c) 2017 Leif Lindholm
* Copyright (c) 2018 AKASHI Takahiro, Linaro Limited
*/
#include <common.h>
#include <efi_loader.h>
const efi_guid_t efi_guid_hii_config_routing_protocol
= EFI_HII_CONFIG_ROUTING_PROTOCOL_GUID;
const efi_guid_t efi_guid_hii_config_access_protocol
= EFI_HII_CONFIG_ACCESS_PROTOCOL_GUID;
/*
* EFI_HII_CONFIG_ROUTING_PROTOCOL
*/
static efi_status_t EFIAPI
extract_config(const struct efi_hii_config_routing_protocol *this,
const efi_string_t request,
efi_string_t *progress,
efi_string_t *results)
{
EFI_ENTRY("%p, \"%ls\", %p, %p", this, request, progress, results);
return EFI_EXIT(EFI_OUT_OF_RESOURCES);
}
static efi_status_t EFIAPI
export_config(const struct efi_hii_config_routing_protocol *this,
efi_string_t *results)
{
EFI_ENTRY("%p, %p", this, results);
return EFI_EXIT(EFI_OUT_OF_RESOURCES);
}
static efi_status_t EFIAPI
route_config(const struct efi_hii_config_routing_protocol *this,
const efi_string_t configuration,
efi_string_t *progress)
{
EFI_ENTRY("%p, \"%ls\", %p", this, configuration, progress);
return EFI_EXIT(EFI_OUT_OF_RESOURCES);
}
static efi_status_t EFIAPI
block_to_config(const struct efi_hii_config_routing_protocol *this,
const efi_string_t config_request,
const u8 *block,
const efi_uintn_t block_size,
efi_string_t *config,
efi_string_t *progress)
{
EFI_ENTRY("%p, \"%ls\", %p, %zu, %p, %p", this, config_request,
block, block_size, config, progress);
return EFI_EXIT(EFI_OUT_OF_RESOURCES);
}
static efi_status_t EFIAPI
config_to_block(const struct efi_hii_config_routing_protocol *this,
const efi_string_t config_resp,
const u8 *block,
const efi_uintn_t *block_size,
efi_string_t *progress)
{
EFI_ENTRY("%p, \"%ls\", %p, %p, %p", this, config_resp,
block, block_size, progress);
return EFI_EXIT(EFI_OUT_OF_RESOURCES);
}
static efi_status_t EFIAPI
get_alt_config(const struct efi_hii_config_routing_protocol *this,
const efi_string_t config_resp,
const efi_guid_t *guid,
const efi_string_t name,
const struct efi_device_path *device_path,
const efi_string_t alt_cfg_id,
efi_string_t *alt_cfg_resp)
{
EFI_ENTRY("%p, \"%ls\", %pUl, \"%ls\", %p, \"%ls\", %p",
this, config_resp, guid, name, device_path,
alt_cfg_id, alt_cfg_resp);
return EFI_EXIT(EFI_OUT_OF_RESOURCES);
}
/*
* EFI_HII_ACCESS_PROTOCOL
*/
efi_status_t EFIAPI
extract_config_access(const struct efi_hii_config_access_protocol *this,
const efi_string_t request,
efi_string_t *progress,
efi_string_t *results)
{
EFI_ENTRY("%p, \"%ls\", %p, %p", this, request, progress, results);
return EFI_EXIT(EFI_OUT_OF_RESOURCES);
};
efi_status_t EFIAPI
route_config_access(const struct efi_hii_config_access_protocol *this,
const efi_string_t configuration,
efi_string_t *progress)
{
EFI_ENTRY("%p, \"%ls\", %p", this, configuration, progress);
return EFI_EXIT(EFI_OUT_OF_RESOURCES);
};
efi_status_t EFIAPI
form_callback(const struct efi_hii_config_access_protocol *this,
efi_browser_action_t action,
efi_question_id_t question_id,
u8 type,
union efi_ifr_type_value *value,
efi_browser_action_request_t *action_request)
{
EFI_ENTRY("%p, 0x%zx, 0x%x, 0x%x, %p, %p", this, action,
question_id, type, value, action_request);
return EFI_EXIT(EFI_DEVICE_ERROR);
};
const struct efi_hii_config_routing_protocol efi_hii_config_routing = {
.extract_config = extract_config,
.export_config = export_config,
.route_config = route_config,
.block_to_config = block_to_config,
.config_to_block = config_to_block,
.get_alt_config = get_alt_config
};
const struct efi_hii_config_access_protocol efi_hii_config_access = {
.extract_config_access = extract_config_access,
.route_config_access = route_config_access,
.form_callback = form_callback
};
@@ -0,0 +1,344 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* EFI image loader
*
* based partly on wine code
*
* Copyright (c) 2016 Alexander Graf
*/
#include <common.h>
#include <cpu_func.h>
#include <efi_loader.h>
#include <pe.h>
const efi_guid_t efi_global_variable_guid = EFI_GLOBAL_VARIABLE_GUID;
const efi_guid_t efi_guid_device_path = EFI_DEVICE_PATH_PROTOCOL_GUID;
const efi_guid_t efi_guid_loaded_image = EFI_LOADED_IMAGE_PROTOCOL_GUID;
const efi_guid_t efi_guid_loaded_image_device_path =
EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID;
const efi_guid_t efi_simple_file_system_protocol_guid =
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
const efi_guid_t efi_file_info_guid = EFI_FILE_INFO_GUID;
static int machines[] = {
#if defined(__aarch64__)
IMAGE_FILE_MACHINE_ARM64,
#elif defined(__arm__)
IMAGE_FILE_MACHINE_ARM,
IMAGE_FILE_MACHINE_THUMB,
IMAGE_FILE_MACHINE_ARMNT,
#endif
#if defined(__x86_64__)
IMAGE_FILE_MACHINE_AMD64,
#elif defined(__i386__)
IMAGE_FILE_MACHINE_I386,
#endif
#if defined(__riscv) && (__riscv_xlen == 32)
IMAGE_FILE_MACHINE_RISCV32,
#endif
#if defined(__riscv) && (__riscv_xlen == 64)
IMAGE_FILE_MACHINE_RISCV64,
#endif
0 };
/**
* efi_print_image_info() - print information about a loaded image
*
* If the program counter is located within the image the offset to the base
* address is shown.
*
* @obj: EFI object
* @image: loaded image
* @pc: program counter (use NULL to suppress offset output)
* Return: status code
*/
static efi_status_t efi_print_image_info(struct efi_loaded_image_obj *obj,
struct efi_loaded_image *image,
void *pc)
{
printf("UEFI image");
printf(" [0x%p:0x%p]",
image->image_base, image->image_base + image->image_size - 1);
if (pc && pc >= image->image_base &&
pc < image->image_base + image->image_size)
printf(" pc=0x%zx", pc - image->image_base);
if (image->file_path)
printf(" '%pD'", image->file_path);
printf("\n");
return EFI_SUCCESS;
}
/**
* efi_print_image_infos() - print information about all loaded images
*
* @pc: program counter (use NULL to suppress offset output)
*/
void efi_print_image_infos(void *pc)
{
struct efi_object *efiobj;
struct efi_handler *handler;
list_for_each_entry(efiobj, &efi_obj_list, link) {
list_for_each_entry(handler, &efiobj->protocols, link) {
if (!guidcmp(handler->guid, &efi_guid_loaded_image)) {
efi_print_image_info(
(struct efi_loaded_image_obj *)efiobj,
handler->protocol_interface, pc);
}
}
}
}
/**
* efi_loader_relocate() - relocate UEFI binary
*
* @rel: pointer to the relocation table
* @rel_size: size of the relocation table in bytes
* @efi_reloc: actual load address of the image
* @pref_address: preferred load address of the image
* Return: status code
*/
static efi_status_t efi_loader_relocate(const IMAGE_BASE_RELOCATION *rel,
unsigned long rel_size, void *efi_reloc,
unsigned long pref_address)
{
unsigned long delta = (unsigned long)efi_reloc - pref_address;
const IMAGE_BASE_RELOCATION *end;
int i;
if (delta == 0)
return EFI_SUCCESS;
end = (const IMAGE_BASE_RELOCATION *)((const char *)rel + rel_size);
while (rel < end && rel->SizeOfBlock) {
const uint16_t *relocs = (const uint16_t *)(rel + 1);
i = (rel->SizeOfBlock - sizeof(*rel)) / sizeof(uint16_t);
while (i--) {
uint32_t offset = (uint32_t)(*relocs & 0xfff) +
rel->VirtualAddress;
int type = *relocs >> EFI_PAGE_SHIFT;
uint64_t *x64 = efi_reloc + offset;
uint32_t *x32 = efi_reloc + offset;
uint16_t *x16 = efi_reloc + offset;
switch (type) {
case IMAGE_REL_BASED_ABSOLUTE:
break;
case IMAGE_REL_BASED_HIGH:
*x16 += ((uint32_t)delta) >> 16;
break;
case IMAGE_REL_BASED_LOW:
*x16 += (uint16_t)delta;
break;
case IMAGE_REL_BASED_HIGHLOW:
*x32 += (uint32_t)delta;
break;
case IMAGE_REL_BASED_DIR64:
*x64 += (uint64_t)delta;
break;
#ifdef __riscv
case IMAGE_REL_BASED_RISCV_HI20:
*x32 = ((*x32 & 0xfffff000) + (uint32_t)delta) |
(*x32 & 0x00000fff);
break;
case IMAGE_REL_BASED_RISCV_LOW12I:
case IMAGE_REL_BASED_RISCV_LOW12S:
/* We know that we're 4k aligned */
if (delta & 0xfff) {
printf("Unsupported reloc offset\n");
return EFI_LOAD_ERROR;
}
break;
#endif
default:
printf("Unknown Relocation off %x type %x\n",
offset, type);
return EFI_LOAD_ERROR;
}
relocs++;
}
rel = (const IMAGE_BASE_RELOCATION *)relocs;
}
return EFI_SUCCESS;
}
void __weak invalidate_icache_all(void)
{
/* If the system doesn't support icache_all flush, cross our fingers */
}
/**
* efi_set_code_and_data_type() - determine the memory types to be used for code
* and data.
*
* @loaded_image_info: image descriptor
* @image_type: field Subsystem of the optional header for
* Windows specific field
*/
static void efi_set_code_and_data_type(
struct efi_loaded_image *loaded_image_info,
uint16_t image_type)
{
switch (image_type) {
case IMAGE_SUBSYSTEM_EFI_APPLICATION:
loaded_image_info->image_code_type = EFI_LOADER_CODE;
loaded_image_info->image_data_type = EFI_LOADER_DATA;
break;
case IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER:
loaded_image_info->image_code_type = EFI_BOOT_SERVICES_CODE;
loaded_image_info->image_data_type = EFI_BOOT_SERVICES_DATA;
break;
case IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER:
case IMAGE_SUBSYSTEM_EFI_ROM:
loaded_image_info->image_code_type = EFI_RUNTIME_SERVICES_CODE;
loaded_image_info->image_data_type = EFI_RUNTIME_SERVICES_DATA;
break;
default:
printf("%s: invalid image type: %u\n", __func__, image_type);
/* Let's assume it is an application */
loaded_image_info->image_code_type = EFI_LOADER_CODE;
loaded_image_info->image_data_type = EFI_LOADER_DATA;
break;
}
}
/**
* efi_load_pe() - relocate EFI binary
*
* This function loads all sections from a PE binary into a newly reserved
* piece of memory. On success the entry point is returned as handle->entry.
*
* @handle: loaded image handle
* @efi: pointer to the EFI binary
* @loaded_image_info: loaded image protocol
* Return: status code
*/
efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle, void *efi,
struct efi_loaded_image *loaded_image_info)
{
IMAGE_NT_HEADERS32 *nt;
IMAGE_DOS_HEADER *dos;
IMAGE_SECTION_HEADER *sections;
int num_sections;
void *efi_reloc;
int i;
const IMAGE_BASE_RELOCATION *rel;
unsigned long rel_size;
int rel_idx = IMAGE_DIRECTORY_ENTRY_BASERELOC;
uint64_t image_base;
unsigned long virt_size = 0;
int supported = 0;
dos = efi;
if (dos->e_magic != IMAGE_DOS_SIGNATURE) {
printf("%s: Invalid DOS Signature\n", __func__);
return EFI_LOAD_ERROR;
}
nt = (void *) ((char *)efi + dos->e_lfanew);
if (nt->Signature != IMAGE_NT_SIGNATURE) {
printf("%s: Invalid NT Signature\n", __func__);
return EFI_LOAD_ERROR;
}
for (i = 0; machines[i]; i++)
if (machines[i] == nt->FileHeader.Machine) {
supported = 1;
break;
}
if (!supported) {
printf("%s: Machine type 0x%04x is not supported\n",
__func__, nt->FileHeader.Machine);
return EFI_LOAD_ERROR;
}
/* Calculate upper virtual address boundary */
num_sections = nt->FileHeader.NumberOfSections;
sections = (void *)&nt->OptionalHeader +
nt->FileHeader.SizeOfOptionalHeader;
for (i = num_sections - 1; i >= 0; i--) {
IMAGE_SECTION_HEADER *sec = &sections[i];
virt_size = max_t(unsigned long, virt_size,
sec->VirtualAddress + sec->Misc.VirtualSize);
}
/* Read 32/64bit specific header bits */
if (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
IMAGE_NT_HEADERS64 *nt64 = (void *)nt;
IMAGE_OPTIONAL_HEADER64 *opt = &nt64->OptionalHeader;
image_base = opt->ImageBase;
efi_set_code_and_data_type(loaded_image_info, opt->Subsystem);
handle->image_type = opt->Subsystem;
efi_reloc = efi_alloc(virt_size,
loaded_image_info->image_code_type);
if (!efi_reloc) {
printf("%s: Could not allocate %lu bytes\n",
__func__, virt_size);
return EFI_OUT_OF_RESOURCES;
}
handle->entry = efi_reloc + opt->AddressOfEntryPoint;
rel_size = opt->DataDirectory[rel_idx].Size;
rel = efi_reloc + opt->DataDirectory[rel_idx].VirtualAddress;
virt_size = ALIGN(virt_size, opt->SectionAlignment);
} else if (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
IMAGE_OPTIONAL_HEADER32 *opt = &nt->OptionalHeader;
image_base = opt->ImageBase;
efi_set_code_and_data_type(loaded_image_info, opt->Subsystem);
handle->image_type = opt->Subsystem;
efi_reloc = efi_alloc(virt_size,
loaded_image_info->image_code_type);
if (!efi_reloc) {
printf("%s: Could not allocate %lu bytes\n",
__func__, virt_size);
return EFI_OUT_OF_RESOURCES;
}
handle->entry = efi_reloc + opt->AddressOfEntryPoint;
rel_size = opt->DataDirectory[rel_idx].Size;
rel = efi_reloc + opt->DataDirectory[rel_idx].VirtualAddress;
virt_size = ALIGN(virt_size, opt->SectionAlignment);
} else {
printf("%s: Invalid optional header magic %x\n", __func__,
nt->OptionalHeader.Magic);
return EFI_LOAD_ERROR;
}
/* Copy PE headers */
memcpy(efi_reloc, efi, sizeof(*dos) + sizeof(*nt)
+ nt->FileHeader.SizeOfOptionalHeader
+ num_sections * sizeof(IMAGE_SECTION_HEADER));
/* Load sections into RAM */
for (i = num_sections - 1; i >= 0; i--) {
IMAGE_SECTION_HEADER *sec = &sections[i];
memset(efi_reloc + sec->VirtualAddress, 0,
sec->Misc.VirtualSize);
memcpy(efi_reloc + sec->VirtualAddress,
efi + sec->PointerToRawData,
sec->SizeOfRawData);
}
/* Run through relocations */
if (efi_loader_relocate(rel, rel_size, efi_reloc,
(unsigned long)image_base) != EFI_SUCCESS) {
efi_free_pages((uintptr_t) efi_reloc,
(virt_size + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT);
return EFI_LOAD_ERROR;
}
/* Flush cache */
flush_cache((ulong)efi_reloc,
ALIGN(virt_size, EFI_CACHELINE_SIZE));
invalidate_icache_all();
/* Populate the loaded image interface bits */
loaded_image_info->image_base = efi_reloc;
loaded_image_info->image_size = virt_size;
return EFI_SUCCESS;
}
@@ -0,0 +1,788 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* EFI application memory management
*
* Copyright (c) 2016 Alexander Graf
*/
#include <common.h>
#include <efi_loader.h>
#include <init.h>
#include <malloc.h>
#include <mapmem.h>
#include <watchdog.h>
#include <linux/list_sort.h>
#include <linux/sizes.h>
DECLARE_GLOBAL_DATA_PTR;
/* Magic number identifying memory allocated from pool */
#define EFI_ALLOC_POOL_MAGIC 0x1fe67ddf6491caa2
efi_uintn_t efi_memory_map_key;
struct efi_mem_list {
struct list_head link;
struct efi_mem_desc desc;
};
#define EFI_CARVE_NO_OVERLAP -1
#define EFI_CARVE_LOOP_AGAIN -2
#define EFI_CARVE_OVERLAPS_NONRAM -3
/* This list contains all memory map items */
LIST_HEAD(efi_mem);
#ifdef CONFIG_EFI_LOADER_BOUNCE_BUFFER
void *efi_bounce_buffer;
#endif
/**
* struct efi_pool_allocation - memory block allocated from pool
*
* @num_pages: number of pages allocated
* @checksum: checksum
* @data: allocated pool memory
*
* U-Boot services each UEFI AllocatePool() request as a separate
* (multiple) page allocation. We have to track the number of pages
* to be able to free the correct amount later.
*
* The checksum calculated in function checksum() is used in FreePool() to avoid
* freeing memory not allocated by AllocatePool() and duplicate freeing.
*
* EFI requires 8 byte alignment for pool allocations, so we can
* prepend each allocation with these header fields.
*/
struct efi_pool_allocation {
u64 num_pages;
u64 checksum;
char data[] __aligned(ARCH_DMA_MINALIGN);
};
/**
* checksum() - calculate checksum for memory allocated from pool
*
* @alloc: allocation header
* Return: checksum, always non-zero
*/
static u64 checksum(struct efi_pool_allocation *alloc)
{
u64 addr = (uintptr_t)alloc;
u64 ret = (addr >> 32) ^ (addr << 32) ^ alloc->num_pages ^
EFI_ALLOC_POOL_MAGIC;
if (!ret)
++ret;
return ret;
}
/*
* Sorts the memory list from highest address to lowest address
*
* When allocating memory we should always start from the highest
* address chunk, so sort the memory list such that the first list
* iterator gets the highest address and goes lower from there.
*/
static int efi_mem_cmp(void *priv, struct list_head *a, struct list_head *b)
{
struct efi_mem_list *mema = list_entry(a, struct efi_mem_list, link);
struct efi_mem_list *memb = list_entry(b, struct efi_mem_list, link);
if (mema->desc.physical_start == memb->desc.physical_start)
return 0;
else if (mema->desc.physical_start < memb->desc.physical_start)
return 1;
else
return -1;
}
static uint64_t desc_get_end(struct efi_mem_desc *desc)
{
return desc->physical_start + (desc->num_pages << EFI_PAGE_SHIFT);
}
static void efi_mem_sort(void)
{
struct list_head *lhandle;
struct efi_mem_list *prevmem = NULL;
bool merge_again = true;
list_sort(NULL, &efi_mem, efi_mem_cmp);
/* Now merge entries that can be merged */
while (merge_again) {
merge_again = false;
list_for_each(lhandle, &efi_mem) {
struct efi_mem_list *lmem;
struct efi_mem_desc *prev = &prevmem->desc;
struct efi_mem_desc *cur;
uint64_t pages;
lmem = list_entry(lhandle, struct efi_mem_list, link);
if (!prevmem) {
prevmem = lmem;
continue;
}
cur = &lmem->desc;
if ((desc_get_end(cur) == prev->physical_start) &&
(prev->type == cur->type) &&
(prev->attribute == cur->attribute)) {
/* There is an existing map before, reuse it */
pages = cur->num_pages;
prev->num_pages += pages;
prev->physical_start -= pages << EFI_PAGE_SHIFT;
prev->virtual_start -= pages << EFI_PAGE_SHIFT;
list_del(&lmem->link);
free(lmem);
merge_again = true;
break;
}
prevmem = lmem;
}
}
}
/** efi_mem_carve_out - unmap memory region
*
* @map: memory map
* @carve_desc: memory region to unmap
* @overlap_only_ram: the carved out region may only overlap RAM
* Return Value: the number of overlapping pages which have been
* removed from the map,
* EFI_CARVE_NO_OVERLAP, if the regions don't overlap,
* EFI_CARVE_OVERLAPS_NONRAM, if the carve and map overlap,
* and the map contains anything but free ram
* (only when overlap_only_ram is true),
* EFI_CARVE_LOOP_AGAIN, if the mapping list should be
* traversed again, as it has been altered.
*
* Unmaps all memory occupied by the carve_desc region from the list entry
* pointed to by map.
*
* In case of EFI_CARVE_OVERLAPS_NONRAM it is the callers responsibility
* to re-add the already carved out pages to the mapping.
*/
static s64 efi_mem_carve_out(struct efi_mem_list *map,
struct efi_mem_desc *carve_desc,
bool overlap_only_ram)
{
struct efi_mem_list *newmap;
struct efi_mem_desc *map_desc = &map->desc;
uint64_t map_start = map_desc->physical_start;
uint64_t map_end = map_start + (map_desc->num_pages << EFI_PAGE_SHIFT);
uint64_t carve_start = carve_desc->physical_start;
uint64_t carve_end = carve_start +
(carve_desc->num_pages << EFI_PAGE_SHIFT);
/* check whether we're overlapping */
if ((carve_end <= map_start) || (carve_start >= map_end))
return EFI_CARVE_NO_OVERLAP;
/* We're overlapping with non-RAM, warn the caller if desired */
if (overlap_only_ram && (map_desc->type != EFI_CONVENTIONAL_MEMORY))
return EFI_CARVE_OVERLAPS_NONRAM;
/* Sanitize carve_start and carve_end to lie within our bounds */
carve_start = max(carve_start, map_start);
carve_end = min(carve_end, map_end);
/* Carving at the beginning of our map? Just move it! */
if (carve_start == map_start) {
if (map_end == carve_end) {
/* Full overlap, just remove map */
list_del(&map->link);
free(map);
} else {
map->desc.physical_start = carve_end;
map->desc.virtual_start = carve_end;
map->desc.num_pages = (map_end - carve_end)
>> EFI_PAGE_SHIFT;
}
return (carve_end - carve_start) >> EFI_PAGE_SHIFT;
}
/*
* Overlapping maps, just split the list map at carve_start,
* it will get moved or removed in the next iteration.
*
* [ map_desc |__carve_start__| newmap ]
*/
/* Create a new map from [ carve_start ... map_end ] */
newmap = calloc(1, sizeof(*newmap));
newmap->desc = map->desc;
newmap->desc.physical_start = carve_start;
newmap->desc.virtual_start = carve_start;
newmap->desc.num_pages = (map_end - carve_start) >> EFI_PAGE_SHIFT;
/* Insert before current entry (descending address order) */
list_add_tail(&newmap->link, &map->link);
/* Shrink the map to [ map_start ... carve_start ] */
map_desc->num_pages = (carve_start - map_start) >> EFI_PAGE_SHIFT;
return EFI_CARVE_LOOP_AGAIN;
}
/**
* efi_add_memory_map() - add memory area to the memory map
*
* @start: start address, must be a multiple of EFI_PAGE_SIZE
* @pages: number of pages to add
* @memory_type: type of memory added
* @overlap_only_ram: the memory area must overlap existing
* Return: status code
*/
efi_status_t efi_add_memory_map(uint64_t start, uint64_t pages, int memory_type,
bool overlap_only_ram)
{
struct list_head *lhandle;
struct efi_mem_list *newlist;
bool carve_again;
uint64_t carved_pages = 0;
struct efi_event *evt;
EFI_PRINT("%s: 0x%llx 0x%llx %d %s\n", __func__,
start, pages, memory_type, overlap_only_ram ? "yes" : "no");
if (memory_type >= EFI_MAX_MEMORY_TYPE)
return EFI_INVALID_PARAMETER;
if (!pages)
return EFI_SUCCESS;
++efi_memory_map_key;
newlist = calloc(1, sizeof(*newlist));
newlist->desc.type = memory_type;
newlist->desc.physical_start = start;
newlist->desc.virtual_start = start;
newlist->desc.num_pages = pages;
switch (memory_type) {
case EFI_RUNTIME_SERVICES_CODE:
case EFI_RUNTIME_SERVICES_DATA:
newlist->desc.attribute = EFI_MEMORY_WB | EFI_MEMORY_RUNTIME;
break;
case EFI_MMAP_IO:
newlist->desc.attribute = EFI_MEMORY_RUNTIME;
break;
default:
newlist->desc.attribute = EFI_MEMORY_WB;
break;
}
/* Add our new map */
do {
carve_again = false;
list_for_each(lhandle, &efi_mem) {
struct efi_mem_list *lmem;
s64 r;
lmem = list_entry(lhandle, struct efi_mem_list, link);
r = efi_mem_carve_out(lmem, &newlist->desc,
overlap_only_ram);
switch (r) {
case EFI_CARVE_OVERLAPS_NONRAM:
/*
* The user requested to only have RAM overlaps,
* but we hit a non-RAM region. Error out.
*/
return EFI_NO_MAPPING;
case EFI_CARVE_NO_OVERLAP:
/* Just ignore this list entry */
break;
case EFI_CARVE_LOOP_AGAIN:
/*
* We split an entry, but need to loop through
* the list again to actually carve it.
*/
carve_again = true;
break;
default:
/* We carved a number of pages */
carved_pages += r;
carve_again = true;
break;
}
if (carve_again) {
/* The list changed, we need to start over */
break;
}
}
} while (carve_again);
if (overlap_only_ram && (carved_pages != pages)) {
/*
* The payload wanted to have RAM overlaps, but we overlapped
* with an unallocated region. Error out.
*/
return EFI_NO_MAPPING;
}
/* Add our new map */
list_add_tail(&newlist->link, &efi_mem);
/* And make sure memory is listed in descending order */
efi_mem_sort();
/* Notify that the memory map was changed */
list_for_each_entry(evt, &efi_events, link) {
if (evt->group &&
!guidcmp(evt->group,
&efi_guid_event_group_memory_map_change)) {
efi_signal_event(evt);
break;
}
}
return EFI_SUCCESS;
}
/**
* efi_check_allocated() - validate address to be freed
*
* Check that the address is within allocated memory:
*
* * The address must be in a range of the memory map.
* * The address may not point to EFI_CONVENTIONAL_MEMORY.
*
* Page alignment is not checked as this is not a requirement of
* efi_free_pool().
*
* @addr: address of page to be freed
* @must_be_allocated: return success if the page is allocated
* Return: status code
*/
static efi_status_t efi_check_allocated(u64 addr, bool must_be_allocated)
{
struct efi_mem_list *item;
list_for_each_entry(item, &efi_mem, link) {
u64 start = item->desc.physical_start;
u64 end = start + (item->desc.num_pages << EFI_PAGE_SHIFT);
if (addr >= start && addr < end) {
if (must_be_allocated ^
(item->desc.type == EFI_CONVENTIONAL_MEMORY))
return EFI_SUCCESS;
else
return EFI_NOT_FOUND;
}
}
return EFI_NOT_FOUND;
}
static uint64_t efi_find_free_memory(uint64_t len, uint64_t max_addr)
{
struct list_head *lhandle;
/*
* Prealign input max address, so we simplify our matching
* logic below and can just reuse it as return pointer.
*/
max_addr &= ~EFI_PAGE_MASK;
list_for_each(lhandle, &efi_mem) {
struct efi_mem_list *lmem = list_entry(lhandle,
struct efi_mem_list, link);
struct efi_mem_desc *desc = &lmem->desc;
uint64_t desc_len = desc->num_pages << EFI_PAGE_SHIFT;
uint64_t desc_end = desc->physical_start + desc_len;
uint64_t curmax = min(max_addr, desc_end);
uint64_t ret = curmax - len;
/* We only take memory from free RAM */
if (desc->type != EFI_CONVENTIONAL_MEMORY)
continue;
/* Out of bounds for max_addr */
if ((ret + len) > max_addr)
continue;
/* Out of bounds for upper map limit */
if ((ret + len) > desc_end)
continue;
/* Out of bounds for lower map limit */
if (ret < desc->physical_start)
continue;
/* Return the highest address in this map within bounds */
return ret;
}
return 0;
}
/*
* Allocate memory pages.
*
* @type type of allocation to be performed
* @memory_type usage type of the allocated memory
* @pages number of pages to be allocated
* @memory allocated memory
* @return status code
*/
efi_status_t efi_allocate_pages(int type, int memory_type,
efi_uintn_t pages, uint64_t *memory)
{
u64 len = pages << EFI_PAGE_SHIFT;
efi_status_t ret;
uint64_t addr;
/* Check import parameters */
if (memory_type >= EFI_PERSISTENT_MEMORY_TYPE &&
memory_type <= 0x6FFFFFFF)
return EFI_INVALID_PARAMETER;
if (!memory)
return EFI_INVALID_PARAMETER;
switch (type) {
case EFI_ALLOCATE_ANY_PAGES:
/* Any page */
addr = efi_find_free_memory(len, -1ULL);
if (!addr)
return EFI_OUT_OF_RESOURCES;
break;
case EFI_ALLOCATE_MAX_ADDRESS:
/* Max address */
addr = efi_find_free_memory(len, *memory);
if (!addr)
return EFI_OUT_OF_RESOURCES;
break;
case EFI_ALLOCATE_ADDRESS:
/* Exact address, reserve it. The addr is already in *memory. */
ret = efi_check_allocated(*memory, false);
if (ret != EFI_SUCCESS)
return EFI_NOT_FOUND;
addr = *memory;
break;
default:
/* UEFI doesn't specify other allocation types */
return EFI_INVALID_PARAMETER;
}
/* Reserve that map in our memory maps */
if (efi_add_memory_map(addr, pages, memory_type, true) != EFI_SUCCESS)
/* Map would overlap, bail out */
return EFI_OUT_OF_RESOURCES;
*memory = addr;
return EFI_SUCCESS;
}
void *efi_alloc(uint64_t len, int memory_type)
{
uint64_t ret = 0;
uint64_t pages = efi_size_in_pages(len);
efi_status_t r;
r = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, memory_type, pages,
&ret);
if (r == EFI_SUCCESS)
return (void*)(uintptr_t)ret;
return NULL;
}
/**
* efi_free_pages() - free memory pages
*
* @memory: start of the memory area to be freed
* @pages: number of pages to be freed
* Return: status code
*/
efi_status_t efi_free_pages(uint64_t memory, efi_uintn_t pages)
{
efi_status_t ret;
ret = efi_check_allocated(memory, true);
if (ret != EFI_SUCCESS)
return ret;
/* Sanity check */
if (!memory || (memory & EFI_PAGE_MASK) || !pages) {
printf("%s: illegal free 0x%llx, 0x%zx\n", __func__,
memory, pages);
return EFI_INVALID_PARAMETER;
}
ret = efi_add_memory_map(memory, pages, EFI_CONVENTIONAL_MEMORY, false);
/* Merging of adjacent free regions is missing */
if (ret != EFI_SUCCESS)
return EFI_NOT_FOUND;
return ret;
}
/**
* efi_allocate_pool - allocate memory from pool
*
* @pool_type: type of the pool from which memory is to be allocated
* @size: number of bytes to be allocated
* @buffer: allocated memory
* Return: status code
*/
efi_status_t efi_allocate_pool(int pool_type, efi_uintn_t size, void **buffer)
{
efi_status_t r;
u64 addr;
struct efi_pool_allocation *alloc;
u64 num_pages = efi_size_in_pages(size +
sizeof(struct efi_pool_allocation));
if (!buffer)
return EFI_INVALID_PARAMETER;
if (size == 0) {
*buffer = NULL;
return EFI_SUCCESS;
}
r = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, pool_type, num_pages,
&addr);
if (r == EFI_SUCCESS) {
alloc = (struct efi_pool_allocation *)(uintptr_t)addr;
alloc->num_pages = num_pages;
alloc->checksum = checksum(alloc);
*buffer = alloc->data;
}
return r;
}
/**
* efi_free_pool() - free memory from pool
*
* @buffer: start of memory to be freed
* Return: status code
*/
efi_status_t efi_free_pool(void *buffer)
{
efi_status_t ret;
struct efi_pool_allocation *alloc;
if (!buffer)
return EFI_INVALID_PARAMETER;
ret = efi_check_allocated((uintptr_t)buffer, true);
if (ret != EFI_SUCCESS)
return ret;
alloc = container_of(buffer, struct efi_pool_allocation, data);
/* Check that this memory was allocated by efi_allocate_pool() */
if (((uintptr_t)alloc & EFI_PAGE_MASK) ||
alloc->checksum != checksum(alloc)) {
printf("%s: illegal free 0x%p\n", __func__, buffer);
return EFI_INVALID_PARAMETER;
}
/* Avoid double free */
alloc->checksum = 0;
ret = efi_free_pages((uintptr_t)alloc, alloc->num_pages);
return ret;
}
/*
* Get map describing memory usage.
*
* @memory_map_size on entry the size, in bytes, of the memory map buffer,
* on exit the size of the copied memory map
* @memory_map buffer to which the memory map is written
* @map_key key for the memory map
* @descriptor_size size of an individual memory descriptor
* @descriptor_version version number of the memory descriptor structure
* @return status code
*/
efi_status_t efi_get_memory_map(efi_uintn_t *memory_map_size,
struct efi_mem_desc *memory_map,
efi_uintn_t *map_key,
efi_uintn_t *descriptor_size,
uint32_t *descriptor_version)
{
efi_uintn_t map_size = 0;
int map_entries = 0;
struct list_head *lhandle;
efi_uintn_t provided_map_size;
if (!memory_map_size)
return EFI_INVALID_PARAMETER;
provided_map_size = *memory_map_size;
list_for_each(lhandle, &efi_mem)
map_entries++;
map_size = map_entries * sizeof(struct efi_mem_desc);
*memory_map_size = map_size;
if (provided_map_size < map_size)
return EFI_BUFFER_TOO_SMALL;
if (!memory_map)
return EFI_INVALID_PARAMETER;
if (descriptor_size)
*descriptor_size = sizeof(struct efi_mem_desc);
if (descriptor_version)
*descriptor_version = EFI_MEMORY_DESCRIPTOR_VERSION;
/* Copy list into array */
/* Return the list in ascending order */
memory_map = &memory_map[map_entries - 1];
list_for_each(lhandle, &efi_mem) {
struct efi_mem_list *lmem;
lmem = list_entry(lhandle, struct efi_mem_list, link);
*memory_map = lmem->desc;
memory_map--;
}
if (map_key)
*map_key = efi_memory_map_key;
return EFI_SUCCESS;
}
/**
* efi_add_conventional_memory_map() - add a RAM memory area to the map
*
* @ram_start: start address of a RAM memory area
* @ram_end: end address of a RAM memory area
* @ram_top: max address to be used as conventional memory
* Return: status code
*/
efi_status_t efi_add_conventional_memory_map(u64 ram_start, u64 ram_end,
u64 ram_top)
{
u64 pages;
/* Remove partial pages */
ram_end &= ~EFI_PAGE_MASK;
ram_start = (ram_start + EFI_PAGE_MASK) & ~EFI_PAGE_MASK;
if (ram_end <= ram_start) {
/* Invalid mapping */
return EFI_INVALID_PARAMETER;
}
pages = (ram_end - ram_start) >> EFI_PAGE_SHIFT;
efi_add_memory_map(ram_start, pages,
EFI_CONVENTIONAL_MEMORY, false);
/*
* Boards may indicate to the U-Boot memory core that they
* can not support memory above ram_top. Let's honor this
* in the efi_loader subsystem too by declaring any memory
* above ram_top as "already occupied by firmware".
*/
if (ram_top < ram_start) {
/* ram_top is before this region, reserve all */
efi_add_memory_map(ram_start, pages,
EFI_BOOT_SERVICES_DATA, true);
} else if ((ram_top >= ram_start) && (ram_top < ram_end)) {
/* ram_top is inside this region, reserve parts */
pages = (ram_end - ram_top) >> EFI_PAGE_SHIFT;
efi_add_memory_map(ram_top, pages,
EFI_BOOT_SERVICES_DATA, true);
}
return EFI_SUCCESS;
}
__weak void efi_add_known_memory(void)
{
u64 ram_top = board_get_usable_ram_top(0) & ~EFI_PAGE_MASK;
int i;
/*
* ram_top is just outside mapped memory. So use an offset of one for
* mapping the sandbox address.
*/
ram_top = (uintptr_t)map_sysmem(ram_top - 1, 0) + 1;
/* Fix for 32bit targets with ram_top at 4G */
if (!ram_top)
ram_top = 0x100000000ULL;
/* Add RAM */
for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
u64 ram_end, ram_start;
ram_start = (uintptr_t)map_sysmem(gd->bd->bi_dram[i].start, 0);
ram_end = ram_start + gd->bd->bi_dram[i].size;
efi_add_conventional_memory_map(ram_start, ram_end, ram_top);
}
}
/* Add memory regions for U-Boot's memory and for the runtime services code */
static void add_u_boot_and_runtime(void)
{
unsigned long runtime_start, runtime_end, runtime_pages;
unsigned long runtime_mask = EFI_PAGE_MASK;
unsigned long uboot_start, uboot_pages;
unsigned long uboot_stack_size = 16 * 1024 * 1024;
/* Add U-Boot */
uboot_start = ((uintptr_t)map_sysmem(gd->start_addr_sp, 0) -
uboot_stack_size) & ~EFI_PAGE_MASK;
uboot_pages = ((uintptr_t)map_sysmem(gd->ram_top - 1, 0) -
uboot_start + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT;
efi_add_memory_map(uboot_start, uboot_pages, EFI_LOADER_DATA, false);
#if defined(__aarch64__)
/*
* Runtime Services must be 64KiB aligned according to the
* "AArch64 Platforms" section in the UEFI spec (2.7+).
*/
runtime_mask = SZ_64K - 1;
#endif
/*
* Add Runtime Services. We mark surrounding boottime code as runtime as
* well to fulfill the runtime alignment constraints but avoid padding.
*/
runtime_start = (ulong)&__efi_runtime_start & ~runtime_mask;
runtime_end = (ulong)&__efi_runtime_stop;
runtime_end = (runtime_end + runtime_mask) & ~runtime_mask;
runtime_pages = (runtime_end - runtime_start) >> EFI_PAGE_SHIFT;
efi_add_memory_map(runtime_start, runtime_pages,
EFI_RUNTIME_SERVICES_CODE, false);
}
int efi_memory_init(void)
{
efi_add_known_memory();
add_u_boot_and_runtime();
#ifdef CONFIG_EFI_LOADER_BOUNCE_BUFFER
/* Request a 32bit 64MB bounce buffer region */
uint64_t efi_bounce_buffer_addr = 0xffffffff;
if (efi_allocate_pages(EFI_ALLOCATE_MAX_ADDRESS, EFI_LOADER_DATA,
(64 * 1024 * 1024) >> EFI_PAGE_SHIFT,
&efi_bounce_buffer_addr) != EFI_SUCCESS)
return -1;
efi_bounce_buffer = (void*)(uintptr_t)efi_bounce_buffer_addr;
#endif
return 0;
}
@@ -0,0 +1,946 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Simple network protocol
* PXE base code protocol
*
* Copyright (c) 2016 Alexander Graf
*
* The simple network protocol has the following statuses and services
* to move between them:
*
* Start(): EfiSimpleNetworkStopped -> EfiSimpleNetworkStarted
* Initialize(): EfiSimpleNetworkStarted -> EfiSimpleNetworkInitialized
* Shutdown(): EfiSimpleNetworkInitialized -> EfiSimpleNetworkStarted
* Stop(): EfiSimpleNetworkStarted -> EfiSimpleNetworkStopped
* Reset(): EfiSimpleNetworkInitialized -> EfiSimpleNetworkInitialized
*/
#include <common.h>
#include <efi_loader.h>
#include <malloc.h>
static const efi_guid_t efi_net_guid = EFI_SIMPLE_NETWORK_PROTOCOL_GUID;
static const efi_guid_t efi_pxe_base_code_protocol_guid =
EFI_PXE_BASE_CODE_PROTOCOL_GUID;
static struct efi_pxe_packet *dhcp_ack;
static bool new_rx_packet;
static void *new_tx_packet;
static void *transmit_buffer;
/*
* The notification function of this event is called in every timer cycle
* to check if a new network packet has been received.
*/
static struct efi_event *network_timer_event;
/*
* This event is signaled when a packet has been received.
*/
static struct efi_event *wait_for_packet;
/**
* struct efi_net_obj - EFI object representing a network interface
*
* @header: EFI object header
* @net: simple network protocol interface
* @net_mode: status of the network interface
* @pxe: PXE base code protocol interface
* @pxe_mode: status of the PXE base code protocol
*/
struct efi_net_obj {
struct efi_object header;
struct efi_simple_network net;
struct efi_simple_network_mode net_mode;
struct efi_pxe_base_code_protocol pxe;
struct efi_pxe_mode pxe_mode;
};
/*
* efi_net_start() - start the network interface
*
* This function implements the Start service of the
* EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface
* (UEFI) specification for details.
*
* @this: pointer to the protocol instance
* Return: status code
*/
static efi_status_t EFIAPI efi_net_start(struct efi_simple_network *this)
{
efi_status_t ret = EFI_SUCCESS;
EFI_ENTRY("%p", this);
/* Check parameters */
if (!this) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
if (this->mode->state != EFI_NETWORK_STOPPED) {
ret = EFI_ALREADY_STARTED;
} else {
this->int_status = 0;
wait_for_packet->is_signaled = false;
this->mode->state = EFI_NETWORK_STARTED;
}
out:
return EFI_EXIT(ret);
}
/*
* efi_net_stop() - stop the network interface
*
* This function implements the Stop service of the
* EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface
* (UEFI) specification for details.
*
* @this: pointer to the protocol instance
* Return: status code
*/
static efi_status_t EFIAPI efi_net_stop(struct efi_simple_network *this)
{
efi_status_t ret = EFI_SUCCESS;
EFI_ENTRY("%p", this);
/* Check parameters */
if (!this) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
if (this->mode->state == EFI_NETWORK_STOPPED) {
ret = EFI_NOT_STARTED;
} else {
/* Disable hardware and put it into the reset state */
eth_halt();
this->mode->state = EFI_NETWORK_STOPPED;
}
out:
return EFI_EXIT(ret);
}
/*
* efi_net_initialize() - initialize the network interface
*
* This function implements the Initialize service of the
* EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface
* (UEFI) specification for details.
*
* @this: pointer to the protocol instance
* @extra_rx: extra receive buffer to be allocated
* @extra_tx: extra transmit buffer to be allocated
* Return: status code
*/
static efi_status_t EFIAPI efi_net_initialize(struct efi_simple_network *this,
ulong extra_rx, ulong extra_tx)
{
int ret;
efi_status_t r = EFI_SUCCESS;
EFI_ENTRY("%p, %lx, %lx", this, extra_rx, extra_tx);
/* Check parameters */
if (!this) {
r = EFI_INVALID_PARAMETER;
goto out;
}
switch (this->mode->state) {
case EFI_NETWORK_INITIALIZED:
case EFI_NETWORK_STARTED:
break;
default:
r = EFI_NOT_STARTED;
goto out;
}
/* Setup packet buffers */
net_init();
/* Disable hardware and put it into the reset state */
eth_halt();
/* Set current device according to environment variables */
eth_set_current();
/* Get hardware ready for send and receive operations */
ret = eth_init();
if (ret < 0) {
eth_halt();
this->mode->state = EFI_NETWORK_STOPPED;
r = EFI_DEVICE_ERROR;
goto out;
} else {
this->int_status = 0;
wait_for_packet->is_signaled = false;
this->mode->state = EFI_NETWORK_INITIALIZED;
}
out:
return EFI_EXIT(r);
}
/*
* efi_net_reset() - reinitialize the network interface
*
* This function implements the Reset service of the
* EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface
* (UEFI) specification for details.
*
* @this: pointer to the protocol instance
* @extended_verification: execute exhaustive verification
* Return: status code
*/
static efi_status_t EFIAPI efi_net_reset(struct efi_simple_network *this,
int extended_verification)
{
efi_status_t ret;
EFI_ENTRY("%p, %x", this, extended_verification);
/* Check parameters */
if (!this) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
switch (this->mode->state) {
case EFI_NETWORK_INITIALIZED:
break;
case EFI_NETWORK_STOPPED:
ret = EFI_NOT_STARTED;
goto out;
default:
ret = EFI_DEVICE_ERROR;
goto out;
}
this->mode->state = EFI_NETWORK_STARTED;
ret = EFI_CALL(efi_net_initialize(this, 0, 0));
out:
return EFI_EXIT(ret);
}
/*
* efi_net_shutdown() - shut down the network interface
*
* This function implements the Shutdown service of the
* EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface
* (UEFI) specification for details.
*
* @this: pointer to the protocol instance
* Return: status code
*/
static efi_status_t EFIAPI efi_net_shutdown(struct efi_simple_network *this)
{
efi_status_t ret = EFI_SUCCESS;
EFI_ENTRY("%p", this);
/* Check parameters */
if (!this) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
switch (this->mode->state) {
case EFI_NETWORK_INITIALIZED:
break;
case EFI_NETWORK_STOPPED:
ret = EFI_NOT_STARTED;
goto out;
default:
ret = EFI_DEVICE_ERROR;
goto out;
}
eth_halt();
this->int_status = 0;
wait_for_packet->is_signaled = false;
this->mode->state = EFI_NETWORK_STARTED;
out:
return EFI_EXIT(ret);
}
/*
* efi_net_receive_filters() - mange multicast receive filters
*
* This function implements the ReceiveFilters service of the
* EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface
* (UEFI) specification for details.
*
* @this: pointer to the protocol instance
* @enable: bit mask of receive filters to enable
* @disable: bit mask of receive filters to disable
* @reset_mcast_filter: true resets contents of the filters
* @mcast_filter_count: number of hardware MAC addresses in the new filters list
* @mcast_filter: list of new filters
* Return: status code
*/
static efi_status_t EFIAPI efi_net_receive_filters
(struct efi_simple_network *this, u32 enable, u32 disable,
int reset_mcast_filter, ulong mcast_filter_count,
struct efi_mac_address *mcast_filter)
{
EFI_ENTRY("%p, %x, %x, %x, %lx, %p", this, enable, disable,
reset_mcast_filter, mcast_filter_count, mcast_filter);
return EFI_EXIT(EFI_UNSUPPORTED);
}
/*
* efi_net_station_address() - set the hardware MAC address
*
* This function implements the StationAddress service of the
* EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface
* (UEFI) specification for details.
*
* @this: pointer to the protocol instance
* @reset: if true reset the address to default
* @new_mac: new MAC address
* Return: status code
*/
static efi_status_t EFIAPI efi_net_station_address
(struct efi_simple_network *this, int reset,
struct efi_mac_address *new_mac)
{
EFI_ENTRY("%p, %x, %p", this, reset, new_mac);
return EFI_EXIT(EFI_UNSUPPORTED);
}
/*
* efi_net_statistics() - reset or collect statistics of the network interface
*
* This function implements the Statistics service of the
* EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface
* (UEFI) specification for details.
*
* @this: pointer to the protocol instance
* @reset: if true, the statistics are reset
* @stat_size: size of the statistics table
* @stat_table: table to receive the statistics
* Return: status code
*/
static efi_status_t EFIAPI efi_net_statistics(struct efi_simple_network *this,
int reset, ulong *stat_size,
void *stat_table)
{
EFI_ENTRY("%p, %x, %p, %p", this, reset, stat_size, stat_table);
return EFI_EXIT(EFI_UNSUPPORTED);
}
/*
* efi_net_mcastiptomac() - translate multicast IP address to MAC address
*
* This function implements the MCastIPtoMAC service of the
* EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface
* (UEFI) specification for details.
*
* @this: pointer to the protocol instance
* @ipv6: true if the IP address is an IPv6 address
* @ip: IP address
* @mac: MAC address
* Return: status code
*/
static efi_status_t EFIAPI efi_net_mcastiptomac(struct efi_simple_network *this,
int ipv6,
struct efi_ip_address *ip,
struct efi_mac_address *mac)
{
efi_status_t ret = EFI_SUCCESS;
EFI_ENTRY("%p, %x, %p, %p", this, ipv6, ip, mac);
if (!this || !ip || !mac) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
if (ipv6) {
ret = EFI_UNSUPPORTED;
goto out;
}
/* Multi-cast addresses are in the range 224.0.0.0 - 239.255.255.255 */
if ((ip->ip_addr[0] & 0xf0) != 0xe0) {
ret = EFI_INVALID_PARAMETER;
goto out;
};
switch (this->mode->state) {
case EFI_NETWORK_INITIALIZED:
case EFI_NETWORK_STARTED:
break;
default:
ret = EFI_NOT_STARTED;
goto out;
}
memset(mac, 0, sizeof(struct efi_mac_address));
/*
* Copy lower 23 bits of IPv4 multi-cast address
* RFC 1112, RFC 7042 2.1.1.
*/
mac->mac_addr[0] = 0x01;
mac->mac_addr[1] = 0x00;
mac->mac_addr[2] = 0x5E;
mac->mac_addr[3] = ip->ip_addr[1] & 0x7F;
mac->mac_addr[4] = ip->ip_addr[2];
mac->mac_addr[5] = ip->ip_addr[3];
out:
return EFI_EXIT(ret);
}
/**
* efi_net_nvdata() - read or write NVRAM
*
* This function implements the GetStatus service of the Simple Network
* Protocol. See the UEFI spec for details.
*
* @this: the instance of the Simple Network Protocol
* @read_write: true for read, false for write
* @offset: offset in NVRAM
* @buffer_size: size of buffer
* @buffer: buffer
* Return: status code
*/
static efi_status_t EFIAPI efi_net_nvdata(struct efi_simple_network *this,
int read_write, ulong offset,
ulong buffer_size, char *buffer)
{
EFI_ENTRY("%p, %x, %lx, %lx, %p", this, read_write, offset, buffer_size,
buffer);
return EFI_EXIT(EFI_UNSUPPORTED);
}
/**
* efi_net_get_status() - get interrupt status
*
* This function implements the GetStatus service of the Simple Network
* Protocol. See the UEFI spec for details.
*
* @this: the instance of the Simple Network Protocol
* @int_status: interface status
* @txbuf: transmission buffer
*/
static efi_status_t EFIAPI efi_net_get_status(struct efi_simple_network *this,
u32 *int_status, void **txbuf)
{
efi_status_t ret = EFI_SUCCESS;
EFI_ENTRY("%p, %p, %p", this, int_status, txbuf);
efi_timer_check();
/* Check parameters */
if (!this) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
switch (this->mode->state) {
case EFI_NETWORK_STOPPED:
ret = EFI_NOT_STARTED;
goto out;
case EFI_NETWORK_STARTED:
ret = EFI_DEVICE_ERROR;
goto out;
default:
break;
}
if (int_status) {
*int_status = this->int_status;
this->int_status = 0;
}
if (txbuf)
*txbuf = new_tx_packet;
new_tx_packet = NULL;
out:
return EFI_EXIT(ret);
}
/**
* efi_net_transmit() - transmit a packet
*
* This function implements the Transmit service of the Simple Network Protocol.
* See the UEFI spec for details.
*
* @this: the instance of the Simple Network Protocol
* @header_size: size of the media header
* @buffer_size: size of the buffer to receive the packet
* @buffer: buffer to receive the packet
* @src_addr: source hardware MAC address
* @dest_addr: destination hardware MAC address
* @protocol: type of header to build
* Return: status code
*/
static efi_status_t EFIAPI efi_net_transmit
(struct efi_simple_network *this, size_t header_size,
size_t buffer_size, void *buffer,
struct efi_mac_address *src_addr,
struct efi_mac_address *dest_addr, u16 *protocol)
{
efi_status_t ret = EFI_SUCCESS;
EFI_ENTRY("%p, %lu, %lu, %p, %p, %p, %p", this,
(unsigned long)header_size, (unsigned long)buffer_size,
buffer, src_addr, dest_addr, protocol);
efi_timer_check();
/* Check parameters */
if (!this || !buffer) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
/* We do not support jumbo packets */
if (buffer_size > PKTSIZE_ALIGN) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
/* At least the IP header has to fit into the buffer */
if (buffer_size < this->mode->media_header_size) {
ret = EFI_BUFFER_TOO_SMALL;
goto out;
}
/*
* TODO:
* Support VLANs. Use net_set_ether() for copying the header. Use a
* U_BOOT_ENV_CALLBACK to update the media header size.
*/
if (header_size) {
struct ethernet_hdr *header = buffer;
if (!dest_addr || !protocol ||
header_size != this->mode->media_header_size) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
if (!src_addr)
src_addr = &this->mode->current_address;
memcpy(header->et_dest, dest_addr, ARP_HLEN);
memcpy(header->et_src, src_addr, ARP_HLEN);
header->et_protlen = htons(*protocol);
}
switch (this->mode->state) {
case EFI_NETWORK_STOPPED:
ret = EFI_NOT_STARTED;
goto out;
case EFI_NETWORK_STARTED:
ret = EFI_DEVICE_ERROR;
goto out;
default:
break;
}
/* Ethernet packets always fit, just bounce */
memcpy(transmit_buffer, buffer, buffer_size);
net_send_packet(transmit_buffer, buffer_size);
new_tx_packet = buffer;
this->int_status |= EFI_SIMPLE_NETWORK_TRANSMIT_INTERRUPT;
out:
return EFI_EXIT(ret);
}
/**
* efi_net_receive() - receive a packet from a network interface
*
* This function implements the Receive service of the Simple Network Protocol.
* See the UEFI spec for details.
*
* @this: the instance of the Simple Network Protocol
* @header_size: size of the media header
* @buffer_size: size of the buffer to receive the packet
* @buffer: buffer to receive the packet
* @src_addr: source MAC address
* @dest_addr: destination MAC address
* @protocol: protocol
* Return: status code
*/
static efi_status_t EFIAPI efi_net_receive
(struct efi_simple_network *this, size_t *header_size,
size_t *buffer_size, void *buffer,
struct efi_mac_address *src_addr,
struct efi_mac_address *dest_addr, u16 *protocol)
{
efi_status_t ret = EFI_SUCCESS;
struct ethernet_hdr *eth_hdr;
size_t hdr_size = sizeof(struct ethernet_hdr);
u16 protlen;
EFI_ENTRY("%p, %p, %p, %p, %p, %p, %p", this, header_size,
buffer_size, buffer, src_addr, dest_addr, protocol);
/* Execute events */
efi_timer_check();
/* Check parameters */
if (!this || !buffer || !buffer_size) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
switch (this->mode->state) {
case EFI_NETWORK_STOPPED:
ret = EFI_NOT_STARTED;
goto out;
case EFI_NETWORK_STARTED:
ret = EFI_DEVICE_ERROR;
goto out;
default:
break;
}
if (!new_rx_packet) {
ret = EFI_NOT_READY;
goto out;
}
/* Fill export parameters */
eth_hdr = (struct ethernet_hdr *)net_rx_packet;
protlen = ntohs(eth_hdr->et_protlen);
if (protlen == 0x8100) {
hdr_size += 4;
protlen = ntohs(*(u16 *)&net_rx_packet[hdr_size - 2]);
}
if (header_size)
*header_size = hdr_size;
if (dest_addr)
memcpy(dest_addr, eth_hdr->et_dest, ARP_HLEN);
if (src_addr)
memcpy(src_addr, eth_hdr->et_src, ARP_HLEN);
if (protocol)
*protocol = protlen;
if (*buffer_size < net_rx_packet_len) {
/* Packet doesn't fit, try again with bigger buffer */
*buffer_size = net_rx_packet_len;
ret = EFI_BUFFER_TOO_SMALL;
goto out;
}
/* Copy packet */
memcpy(buffer, net_rx_packet, net_rx_packet_len);
*buffer_size = net_rx_packet_len;
new_rx_packet = 0;
this->int_status &= ~EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT;
out:
return EFI_EXIT(ret);
}
/**
* efi_net_set_dhcp_ack() - take note of a selected DHCP IP address
*
* This function is called by dhcp_handler().
*
* @pkt: packet received by dhcp_handler()
* @len: length of the packet received
*/
void efi_net_set_dhcp_ack(void *pkt, int len)
{
int maxsize = sizeof(*dhcp_ack);
if (!dhcp_ack)
dhcp_ack = malloc(maxsize);
memcpy(dhcp_ack, pkt, min(len, maxsize));
}
/**
* efi_net_push() - callback for received network packet
*
* This function is called when a network packet is received by eth_rx().
*
* @pkt: network packet
* @len: length
*/
static void efi_net_push(void *pkt, int len)
{
new_rx_packet = true;
}
/**
* efi_network_timer_notify() - check if a new network packet has been received
*
* This notification function is called in every timer cycle.
*
* @event: the event for which this notification function is registered
* @context: event context - not used in this function
*/
static void EFIAPI efi_network_timer_notify(struct efi_event *event,
void *context)
{
struct efi_simple_network *this = (struct efi_simple_network *)context;
EFI_ENTRY("%p, %p", event, context);
/*
* Some network drivers do not support calling eth_rx() before
* initialization.
*/
if (!this || this->mode->state != EFI_NETWORK_INITIALIZED)
goto out;
if (!new_rx_packet) {
push_packet = efi_net_push;
eth_rx();
push_packet = NULL;
if (new_rx_packet) {
/* Check that we at least received an Ethernet header */
if (net_rx_packet_len >=
sizeof(struct ethernet_hdr)) {
this->int_status |=
EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT;
wait_for_packet->is_signaled = true;
} else {
new_rx_packet = 0;
}
}
}
out:
EFI_EXIT(EFI_SUCCESS);
}
static efi_status_t EFIAPI efi_pxe_base_code_start(
struct efi_pxe_base_code_protocol *this,
u8 use_ipv6)
{
return EFI_UNSUPPORTED;
}
static efi_status_t EFIAPI efi_pxe_base_code_stop(
struct efi_pxe_base_code_protocol *this)
{
return EFI_UNSUPPORTED;
}
static efi_status_t EFIAPI efi_pxe_base_code_dhcp(
struct efi_pxe_base_code_protocol *this,
u8 sort_offers)
{
return EFI_UNSUPPORTED;
}
static efi_status_t EFIAPI efi_pxe_base_code_discover(
struct efi_pxe_base_code_protocol *this,
u16 type, u16 *layer, u8 bis,
struct efi_pxe_base_code_discover_info *info)
{
return EFI_UNSUPPORTED;
}
static efi_status_t EFIAPI efi_pxe_base_code_mtftp(
struct efi_pxe_base_code_protocol *this,
u32 operation, void *buffer_ptr,
u8 overwrite, efi_uintn_t *buffer_size,
struct efi_ip_address server_ip, char *filename,
struct efi_pxe_base_code_mtftp_info *info,
u8 dont_use_buffer)
{
return EFI_UNSUPPORTED;
}
static efi_status_t EFIAPI efi_pxe_base_code_udp_write(
struct efi_pxe_base_code_protocol *this,
u16 op_flags, struct efi_ip_address *dest_ip,
u16 *dest_port,
struct efi_ip_address *gateway_ip,
struct efi_ip_address *src_ip, u16 *src_port,
efi_uintn_t *header_size, void *header_ptr,
efi_uintn_t *buffer_size, void *buffer_ptr)
{
return EFI_UNSUPPORTED;
}
static efi_status_t EFIAPI efi_pxe_base_code_udp_read(
struct efi_pxe_base_code_protocol *this,
u16 op_flags, struct efi_ip_address *dest_ip,
u16 *dest_port, struct efi_ip_address *src_ip,
u16 *src_port, efi_uintn_t *header_size,
void *header_ptr, efi_uintn_t *buffer_size,
void *buffer_ptr)
{
return EFI_UNSUPPORTED;
}
static efi_status_t EFIAPI efi_pxe_base_code_set_ip_filter(
struct efi_pxe_base_code_protocol *this,
struct efi_pxe_base_code_filter *new_filter)
{
return EFI_UNSUPPORTED;
}
static efi_status_t EFIAPI efi_pxe_base_code_arp(
struct efi_pxe_base_code_protocol *this,
struct efi_ip_address *ip_addr,
struct efi_mac_address *mac_addr)
{
return EFI_UNSUPPORTED;
}
static efi_status_t EFIAPI efi_pxe_base_code_set_parameters(
struct efi_pxe_base_code_protocol *this,
u8 *new_auto_arp, u8 *new_send_guid,
u8 *new_ttl, u8 *new_tos,
u8 *new_make_callback)
{
return EFI_UNSUPPORTED;
}
static efi_status_t EFIAPI efi_pxe_base_code_set_station_ip(
struct efi_pxe_base_code_protocol *this,
struct efi_ip_address *new_station_ip,
struct efi_ip_address *new_subnet_mask)
{
return EFI_UNSUPPORTED;
}
static efi_status_t EFIAPI efi_pxe_base_code_set_packets(
struct efi_pxe_base_code_protocol *this,
u8 *new_dhcp_discover_valid,
u8 *new_dhcp_ack_received,
u8 *new_proxy_offer_received,
u8 *new_pxe_discover_valid,
u8 *new_pxe_reply_received,
u8 *new_pxe_bis_reply_received,
EFI_PXE_BASE_CODE_PACKET *new_dchp_discover,
EFI_PXE_BASE_CODE_PACKET *new_dhcp_acc,
EFI_PXE_BASE_CODE_PACKET *new_proxy_offer,
EFI_PXE_BASE_CODE_PACKET *new_pxe_discover,
EFI_PXE_BASE_CODE_PACKET *new_pxe_reply,
EFI_PXE_BASE_CODE_PACKET *new_pxe_bis_reply)
{
return EFI_UNSUPPORTED;
}
/**
* efi_net_register() - register the simple network protocol
*
* This gets called from do_bootefi_exec().
*/
efi_status_t efi_net_register(void)
{
struct efi_net_obj *netobj = NULL;
efi_status_t r;
if (!eth_get_dev()) {
/* No network device active, don't expose any */
return EFI_SUCCESS;
}
/* We only expose the "active" network device, so one is enough */
netobj = calloc(1, sizeof(*netobj));
if (!netobj)
goto out_of_resources;
/* Allocate an aligned transmit buffer */
transmit_buffer = calloc(1, PKTSIZE_ALIGN + PKTALIGN);
if (!transmit_buffer)
goto out_of_resources;
transmit_buffer = (void *)ALIGN((uintptr_t)transmit_buffer, PKTALIGN);
/* Hook net up to the device list */
efi_add_handle(&netobj->header);
/* Fill in object data */
r = efi_add_protocol(&netobj->header, &efi_net_guid,
&netobj->net);
if (r != EFI_SUCCESS)
goto failure_to_add_protocol;
r = efi_add_protocol(&netobj->header, &efi_guid_device_path,
efi_dp_from_eth());
if (r != EFI_SUCCESS)
goto failure_to_add_protocol;
r = efi_add_protocol(&netobj->header, &efi_pxe_base_code_protocol_guid,
&netobj->pxe);
if (r != EFI_SUCCESS)
goto failure_to_add_protocol;
netobj->net.revision = EFI_SIMPLE_NETWORK_PROTOCOL_REVISION;
netobj->net.start = efi_net_start;
netobj->net.stop = efi_net_stop;
netobj->net.initialize = efi_net_initialize;
netobj->net.reset = efi_net_reset;
netobj->net.shutdown = efi_net_shutdown;
netobj->net.receive_filters = efi_net_receive_filters;
netobj->net.station_address = efi_net_station_address;
netobj->net.statistics = efi_net_statistics;
netobj->net.mcastiptomac = efi_net_mcastiptomac;
netobj->net.nvdata = efi_net_nvdata;
netobj->net.get_status = efi_net_get_status;
netobj->net.transmit = efi_net_transmit;
netobj->net.receive = efi_net_receive;
netobj->net.mode = &netobj->net_mode;
netobj->net_mode.state = EFI_NETWORK_STOPPED;
memcpy(netobj->net_mode.current_address.mac_addr, eth_get_ethaddr(), 6);
netobj->net_mode.hwaddr_size = ARP_HLEN;
netobj->net_mode.media_header_size = ETHER_HDR_SIZE;
netobj->net_mode.max_packet_size = PKTSIZE;
netobj->net_mode.if_type = ARP_ETHER;
netobj->pxe.revision = EFI_PXE_BASE_CODE_PROTOCOL_REVISION;
netobj->pxe.start = efi_pxe_base_code_start;
netobj->pxe.stop = efi_pxe_base_code_stop;
netobj->pxe.dhcp = efi_pxe_base_code_dhcp;
netobj->pxe.discover = efi_pxe_base_code_discover;
netobj->pxe.mtftp = efi_pxe_base_code_mtftp;
netobj->pxe.udp_write = efi_pxe_base_code_udp_write;
netobj->pxe.udp_read = efi_pxe_base_code_udp_read;
netobj->pxe.set_ip_filter = efi_pxe_base_code_set_ip_filter;
netobj->pxe.arp = efi_pxe_base_code_arp;
netobj->pxe.set_parameters = efi_pxe_base_code_set_parameters;
netobj->pxe.set_station_ip = efi_pxe_base_code_set_station_ip;
netobj->pxe.set_packets = efi_pxe_base_code_set_packets;
netobj->pxe.mode = &netobj->pxe_mode;
if (dhcp_ack)
netobj->pxe_mode.dhcp_ack = *dhcp_ack;
/*
* Create WaitForPacket event.
*/
r = efi_create_event(EVT_NOTIFY_WAIT, TPL_CALLBACK,
efi_network_timer_notify, NULL, NULL,
&wait_for_packet);
if (r != EFI_SUCCESS) {
printf("ERROR: Failed to register network event\n");
return r;
}
netobj->net.wait_for_packet = wait_for_packet;
/*
* Create a timer event.
*
* The notification function is used to check if a new network packet
* has been received.
*
* iPXE is running at TPL_CALLBACK most of the time. Use a higher TPL.
*/
r = efi_create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_NOTIFY,
efi_network_timer_notify, &netobj->net, NULL,
&network_timer_event);
if (r != EFI_SUCCESS) {
printf("ERROR: Failed to register network event\n");
return r;
}
/* Network is time critical, create event in every timer cycle */
r = efi_set_timer(network_timer_event, EFI_TIMER_PERIODIC, 0);
if (r != EFI_SUCCESS) {
printf("ERROR: Failed to set network timer\n");
return r;
}
return EFI_SUCCESS;
failure_to_add_protocol:
printf("ERROR: Failure to add protocol\n");
return r;
out_of_resources:
free(netobj);
/* free(transmit_buffer) not needed yet */
printf("ERROR: Out of memory\n");
return EFI_OUT_OF_RESOURCES;
}
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Root node for system services
*
* Copyright (c) 2018 Heinrich Schuchardt
*/
#include <common.h>
#include <malloc.h>
#include <efi_loader.h>
const efi_guid_t efi_u_boot_guid = U_BOOT_GUID;
efi_handle_t efi_root = NULL;
struct efi_root_dp {
struct efi_device_path_vendor vendor;
struct efi_device_path end;
} __packed;
/**
* efi_root_node_register() - create root node
*
* Create the root node on which we install all protocols that are
* not related to a loaded image or a driver.
*
* Return: status code
*/
efi_status_t efi_root_node_register(void)
{
efi_status_t ret;
struct efi_root_dp *dp;
/* Create device path protocol */
dp = calloc(1, sizeof(*dp));
if (!dp)
return EFI_OUT_OF_RESOURCES;
/* Fill vendor node */
dp->vendor.dp.type = DEVICE_PATH_TYPE_HARDWARE_DEVICE;
dp->vendor.dp.sub_type = DEVICE_PATH_SUB_TYPE_VENDOR;
dp->vendor.dp.length = sizeof(struct efi_device_path_vendor);
dp->vendor.guid = efi_u_boot_guid;
/* Fill end node */
dp->end.type = DEVICE_PATH_TYPE_END;
dp->end.sub_type = DEVICE_PATH_SUB_TYPE_END;
dp->end.length = sizeof(struct efi_device_path);
/* Create root node and install protocols */
ret = EFI_CALL(efi_install_multiple_protocol_interfaces
(&efi_root,
/* Device path protocol */
&efi_guid_device_path, dp,
#if CONFIG_IS_ENABLED(EFI_DEVICE_PATH_TO_TEXT)
/* Device path to text protocol */
&efi_guid_device_path_to_text_protocol,
(void *)&efi_device_path_to_text,
#endif
/* Device path utilities protocol */
&efi_guid_device_path_utilities_protocol,
(void *)&efi_device_path_utilities,
#if CONFIG_IS_ENABLED(EFI_UNICODE_COLLATION_PROTOCOL2)
#if CONFIG_IS_ENABLED(EFI_UNICODE_COLLATION_PROTOCOL)
/* Deprecated Unicode collation protocol */
&efi_guid_unicode_collation_protocol,
(void *)&efi_unicode_collation_protocol,
#endif
/* Current Unicode collation protocol */
&efi_guid_unicode_collation_protocol2,
(void *)&efi_unicode_collation_protocol2,
#endif
#if CONFIG_IS_ENABLED(EFI_LOADER_HII)
/* HII string protocol */
&efi_guid_hii_string_protocol,
(void *)&efi_hii_string,
/* HII database protocol */
&efi_guid_hii_database_protocol,
(void *)&efi_hii_database,
/* HII configuration routing protocol */
&efi_guid_hii_config_routing_protocol,
(void *)&efi_hii_config_routing,
#endif
NULL));
efi_root->type = EFI_OBJECT_TYPE_U_BOOT_FIRMWARE;
return ret;
}
@@ -0,0 +1,883 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* EFI application runtime services
*
* Copyright (c) 2016 Alexander Graf
*/
#include <common.h>
#include <command.h>
#include <cpu_func.h>
#include <dm.h>
#include <elf.h>
#include <efi_loader.h>
#include <rtc.h>
#include <u-boot/crc.h>
/* For manual relocation support */
DECLARE_GLOBAL_DATA_PTR;
struct efi_runtime_mmio_list {
struct list_head link;
void **ptr;
u64 paddr;
u64 len;
};
/* This list contains all runtime available mmio regions */
LIST_HEAD(efi_runtime_mmio);
static efi_status_t __efi_runtime EFIAPI efi_unimplemented(void);
/*
* TODO(sjg@chromium.org): These defines and structures should come from the ELF
* header for each architecture (or a generic header) rather than being repeated
* here.
*/
#if defined(__aarch64__)
#define R_RELATIVE R_AARCH64_RELATIVE
#define R_MASK 0xffffffffULL
#define IS_RELA 1
#elif defined(__arm__)
#define R_RELATIVE R_ARM_RELATIVE
#define R_MASK 0xffULL
#elif defined(__i386__)
#define R_RELATIVE R_386_RELATIVE
#define R_MASK 0xffULL
#elif defined(__x86_64__)
#define R_RELATIVE R_X86_64_RELATIVE
#define R_MASK 0xffffffffULL
#define IS_RELA 1
#elif defined(__riscv)
#define R_RELATIVE R_RISCV_RELATIVE
#define R_MASK 0xffULL
#define IS_RELA 1
struct dyn_sym {
ulong foo1;
ulong addr;
u32 foo2;
u32 foo3;
};
#if (__riscv_xlen == 32)
#define R_ABSOLUTE R_RISCV_32
#define SYM_INDEX 8
#elif (__riscv_xlen == 64)
#define R_ABSOLUTE R_RISCV_64
#define SYM_INDEX 32
#else
#error unknown riscv target
#endif
#else
#error Need to add relocation awareness
#endif
struct elf_rel {
ulong *offset;
ulong info;
};
struct elf_rela {
ulong *offset;
ulong info;
long addend;
};
static __efi_runtime_data struct efi_mem_desc *efi_virtmap;
static __efi_runtime_data efi_uintn_t efi_descriptor_count;
static __efi_runtime_data efi_uintn_t efi_descriptor_size;
/*
* EFI runtime code lives in two stages. In the first stage, U-Boot and an EFI
* payload are running concurrently at the same time. In this mode, we can
* handle a good number of runtime callbacks
*/
efi_status_t efi_init_runtime_supported(void)
{
u16 efi_runtime_services_supported =
EFI_RT_SUPPORTED_SET_VIRTUAL_ADDRESS_MAP |
EFI_RT_SUPPORTED_CONVERT_POINTER;
/*
* This value must be synced with efi_runtime_detach_list
* as well as efi_runtime_services.
*/
#ifdef CONFIG_EFI_HAVE_RUNTIME_RESET
efi_runtime_services_supported |= EFI_RT_SUPPORTED_RESET_SYSTEM;
#endif
return EFI_CALL(efi_set_variable(L"RuntimeServicesSupported",
&efi_global_variable_guid,
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
sizeof(efi_runtime_services_supported),
&efi_runtime_services_supported));
}
/**
* efi_update_table_header_crc32() - Update crc32 in table header
*
* @table: EFI table
*/
void __efi_runtime efi_update_table_header_crc32(struct efi_table_hdr *table)
{
table->crc32 = 0;
table->crc32 = crc32(0, (const unsigned char *)table,
table->headersize);
}
/**
* efi_reset_system_boottime() - reset system at boot time
*
* This function implements the ResetSystem() runtime service before
* SetVirtualAddressMap() is called.
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* @reset_type: type of reset to perform
* @reset_status: status code for the reset
* @data_size: size of reset_data
* @reset_data: information about the reset
*/
static void EFIAPI efi_reset_system_boottime(
enum efi_reset_type reset_type,
efi_status_t reset_status,
unsigned long data_size, void *reset_data)
{
struct efi_event *evt;
EFI_ENTRY("%d %lx %lx %p", reset_type, reset_status, data_size,
reset_data);
/* Notify reset */
list_for_each_entry(evt, &efi_events, link) {
if (evt->group &&
!guidcmp(evt->group,
&efi_guid_event_group_reset_system)) {
efi_signal_event(evt);
break;
}
}
switch (reset_type) {
case EFI_RESET_COLD:
case EFI_RESET_WARM:
case EFI_RESET_PLATFORM_SPECIFIC:
do_reset(NULL, 0, 0, NULL);
break;
case EFI_RESET_SHUTDOWN:
#ifdef CONFIG_CMD_POWEROFF
do_poweroff(NULL, 0, 0, NULL);
#endif
break;
}
while (1) { }
}
/**
* efi_get_time_boottime() - get current time at boot time
*
* This function implements the GetTime runtime service before
* SetVirtualAddressMap() is called.
*
* See the Unified Extensible Firmware Interface (UEFI) specification
* for details.
*
* @time: pointer to structure to receive current time
* @capabilities: pointer to structure to receive RTC properties
* Returns: status code
*/
static efi_status_t EFIAPI efi_get_time_boottime(
struct efi_time *time,
struct efi_time_cap *capabilities)
{
#ifdef CONFIG_EFI_GET_TIME
efi_status_t ret = EFI_SUCCESS;
struct rtc_time tm;
struct udevice *dev;
EFI_ENTRY("%p %p", time, capabilities);
if (!time) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
if (uclass_get_device(UCLASS_RTC, 0, &dev) ||
dm_rtc_get(dev, &tm)) {
ret = EFI_UNSUPPORTED;
goto out;
}
if (dm_rtc_get(dev, &tm)) {
ret = EFI_DEVICE_ERROR;
goto out;
}
memset(time, 0, sizeof(*time));
time->year = tm.tm_year;
time->month = tm.tm_mon;
time->day = tm.tm_mday;
time->hour = tm.tm_hour;
time->minute = tm.tm_min;
time->second = tm.tm_sec;
if (tm.tm_isdst)
time->daylight =
EFI_TIME_ADJUST_DAYLIGHT | EFI_TIME_IN_DAYLIGHT;
time->timezone = EFI_UNSPECIFIED_TIMEZONE;
if (capabilities) {
/* Set reasonable dummy values */
capabilities->resolution = 1; /* 1 Hz */
capabilities->accuracy = 100000000; /* 100 ppm */
capabilities->sets_to_zero = false;
}
out:
return EFI_EXIT(ret);
#else
EFI_ENTRY("%p %p", time, capabilities);
return EFI_EXIT(EFI_UNSUPPORTED);
#endif
}
#ifdef CONFIG_EFI_SET_TIME
/**
* efi_validate_time() - checks if timestamp is valid
*
* @time: timestamp to validate
* Returns: 0 if timestamp is valid, 1 otherwise
*/
static int efi_validate_time(struct efi_time *time)
{
return (!time ||
time->year < 1900 || time->year > 9999 ||
!time->month || time->month > 12 || !time->day ||
time->day > rtc_month_days(time->month - 1, time->year) ||
time->hour > 23 || time->minute > 59 || time->second > 59 ||
time->nanosecond > 999999999 ||
time->daylight &
~(EFI_TIME_IN_DAYLIGHT | EFI_TIME_ADJUST_DAYLIGHT) ||
((time->timezone < -1440 || time->timezone > 1440) &&
time->timezone != EFI_UNSPECIFIED_TIMEZONE));
}
#endif
/**
* efi_set_time_boottime() - set current time
*
* This function implements the SetTime() runtime service before
* SetVirtualAddressMap() is called.
*
* See the Unified Extensible Firmware Interface (UEFI) specification
* for details.
*
* @time: pointer to structure to with current time
* Returns: status code
*/
static efi_status_t EFIAPI efi_set_time_boottime(struct efi_time *time)
{
#ifdef CONFIG_EFI_SET_TIME
efi_status_t ret = EFI_SUCCESS;
struct rtc_time tm;
struct udevice *dev;
EFI_ENTRY("%p", time);
if (efi_validate_time(time)) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
if (uclass_get_device(UCLASS_RTC, 0, &dev)) {
ret = EFI_UNSUPPORTED;
goto out;
}
memset(&tm, 0, sizeof(tm));
tm.tm_year = time->year;
tm.tm_mon = time->month;
tm.tm_mday = time->day;
tm.tm_hour = time->hour;
tm.tm_min = time->minute;
tm.tm_sec = time->second;
tm.tm_isdst = time->daylight ==
(EFI_TIME_ADJUST_DAYLIGHT | EFI_TIME_IN_DAYLIGHT);
/* Calculate day of week */
rtc_calc_weekday(&tm);
if (dm_rtc_set(dev, &tm))
ret = EFI_DEVICE_ERROR;
out:
return EFI_EXIT(ret);
#else
EFI_ENTRY("%p", time);
return EFI_EXIT(EFI_UNSUPPORTED);
#endif
}
/**
* efi_reset_system() - reset system
*
* This function implements the ResetSystem() runtime service after
* SetVirtualAddressMap() is called. It only executes an endless loop.
* Boards may override the helpers below to implement reset functionality.
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* @reset_type: type of reset to perform
* @reset_status: status code for the reset
* @data_size: size of reset_data
* @reset_data: information about the reset
*/
void __weak __efi_runtime EFIAPI efi_reset_system(
enum efi_reset_type reset_type,
efi_status_t reset_status,
unsigned long data_size, void *reset_data)
{
/* Nothing we can do */
while (1) { }
}
/**
* efi_reset_system_init() - initialize the reset driver
*
* Boards may override this function to initialize the reset driver.
*/
efi_status_t __weak efi_reset_system_init(void)
{
return EFI_SUCCESS;
}
/**
* efi_get_time() - get current time
*
* This function implements the GetTime runtime service after
* SetVirtualAddressMap() is called. As the U-Boot driver are not available
* anymore only an error code is returned.
*
* See the Unified Extensible Firmware Interface (UEFI) specification
* for details.
*
* @time: pointer to structure to receive current time
* @capabilities: pointer to structure to receive RTC properties
* Returns: status code
*/
efi_status_t __weak __efi_runtime EFIAPI efi_get_time(
struct efi_time *time,
struct efi_time_cap *capabilities)
{
return EFI_UNSUPPORTED;
}
/**
* efi_set_time() - set current time
*
* This function implements the SetTime runtime service after
* SetVirtualAddressMap() is called. As the U-Boot driver are not available
* anymore only an error code is returned.
*
* See the Unified Extensible Firmware Interface (UEFI) specification
* for details.
*
* @time: pointer to structure to with current time
* Returns: status code
*/
efi_status_t __weak __efi_runtime EFIAPI efi_set_time(struct efi_time *time)
{
return EFI_UNSUPPORTED;
}
/**
* efi_is_runtime_service_pointer() - check if pointer points to runtime table
*
* @p: pointer to check
* Return: true if the pointer points to a service function pointer in the
* runtime table
*/
static bool efi_is_runtime_service_pointer(void *p)
{
return (p >= (void *)&efi_runtime_services.get_time &&
p <= (void *)&efi_runtime_services.query_variable_info) ||
p == (void *)&efi_events.prev ||
p == (void *)&efi_events.next;
}
/**
* efi_runtime_detach() - detach unimplemented runtime functions
*/
void efi_runtime_detach(void)
{
efi_runtime_services.reset_system = efi_reset_system;
efi_runtime_services.get_time = efi_get_time;
efi_runtime_services.set_time = efi_set_time;
/* Update CRC32 */
efi_update_table_header_crc32(&efi_runtime_services.hdr);
}
/**
* efi_set_virtual_address_map_runtime() - change from physical to virtual
* mapping
*
* This function implements the SetVirtualAddressMap() runtime service after
* it is first called.
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* @memory_map_size: size of the virtual map
* @descriptor_size: size of an entry in the map
* @descriptor_version: version of the map entries
* @virtmap: virtual address mapping information
* Return: status code EFI_UNSUPPORTED
*/
static __efi_runtime efi_status_t EFIAPI efi_set_virtual_address_map_runtime(
efi_uintn_t memory_map_size,
efi_uintn_t descriptor_size,
uint32_t descriptor_version,
struct efi_mem_desc *virtmap)
{
return EFI_UNSUPPORTED;
}
/**
* efi_convert_pointer_runtime() - convert from physical to virtual pointer
*
* This function implements the ConvertPointer() runtime service after
* the first call to SetVirtualAddressMap().
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* @debug_disposition: indicates if pointer may be converted to NULL
* @address: pointer to be converted
* Return: status code EFI_UNSUPPORTED
*/
static __efi_runtime efi_status_t EFIAPI efi_convert_pointer_runtime(
efi_uintn_t debug_disposition, void **address)
{
return EFI_UNSUPPORTED;
}
/**
* efi_convert_pointer_runtime() - convert from physical to virtual pointer
*
* This function implements the ConvertPointer() runtime service until
* the first call to SetVirtualAddressMap().
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* @debug_disposition: indicates if pointer may be converted to NULL
* @address: pointer to be converted
* Return: status code EFI_UNSUPPORTED
*/
static __efi_runtime efi_status_t EFIAPI efi_convert_pointer(
efi_uintn_t debug_disposition, void **address)
{
efi_physical_addr_t addr = (uintptr_t)*address;
efi_uintn_t i;
efi_status_t ret = EFI_NOT_FOUND;
EFI_ENTRY("%zu %p", debug_disposition, address);
if (!efi_virtmap) {
ret = EFI_UNSUPPORTED;
goto out;
}
if (!address) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
for (i = 0; i < efi_descriptor_count; i++) {
struct efi_mem_desc *map = (void *)efi_virtmap +
(efi_descriptor_size * i);
if (addr >= map->physical_start &&
(addr < map->physical_start
+ (map->num_pages << EFI_PAGE_SHIFT))) {
*address = (void *)(uintptr_t)
(addr + map->virtual_start -
map->physical_start);
ret = EFI_SUCCESS;
break;
}
}
out:
return EFI_EXIT(ret);
}
static __efi_runtime void efi_relocate_runtime_table(ulong offset)
{
ulong patchoff;
void **pos;
/* Relocate the runtime services pointers */
patchoff = offset - gd->relocaddr;
for (pos = (void **)&efi_runtime_services.get_time;
pos <= (void **)&efi_runtime_services.query_variable_info; ++pos) {
if (*pos)
*pos += patchoff;
}
/*
* The entry for SetVirtualAddress() must point to a physical address.
* After the first execution the service must return EFI_UNSUPPORTED.
*/
efi_runtime_services.set_virtual_address_map =
&efi_set_virtual_address_map_runtime;
/*
* The entry for ConvertPointer() must point to a physical address.
* The service is not usable after SetVirtualAddress().
*/
efi_runtime_services.convert_pointer = &efi_convert_pointer_runtime;
/*
* TODO: Update UEFI variable RuntimeServicesSupported removing flags
* EFI_RT_SUPPORTED_SET_VIRTUAL_ADDRESS_MAP and
* EFI_RT_SUPPORTED_CONVERT_POINTER as required by the UEFI spec 2.8.
*/
/* Update CRC32 */
efi_update_table_header_crc32(&efi_runtime_services.hdr);
}
/* Relocate EFI runtime to uboot_reloc_base = offset */
void efi_runtime_relocate(ulong offset, struct efi_mem_desc *map)
{
#ifdef IS_RELA
struct elf_rela *rel = (void*)&__efi_runtime_rel_start;
#else
struct elf_rel *rel = (void*)&__efi_runtime_rel_start;
static ulong lastoff = CONFIG_SYS_TEXT_BASE;
#endif
debug("%s: Relocating to offset=%lx\n", __func__, offset);
for (; (ulong)rel < (ulong)&__efi_runtime_rel_stop; rel++) {
ulong base = CONFIG_SYS_TEXT_BASE;
ulong *p;
ulong newaddr;
p = (void*)((ulong)rel->offset - base) + gd->relocaddr;
/*
* The runtime services table is updated in
* efi_relocate_runtime_table()
*/
if (map && efi_is_runtime_service_pointer(p))
continue;
debug("%s: rel->info=%#lx *p=%#lx rel->offset=%p\n", __func__,
rel->info, *p, rel->offset);
switch (rel->info & R_MASK) {
case R_RELATIVE:
#ifdef IS_RELA
newaddr = rel->addend + offset - CONFIG_SYS_TEXT_BASE;
#else
newaddr = *p - lastoff + offset;
#endif
break;
#ifdef R_ABSOLUTE
case R_ABSOLUTE: {
ulong symidx = rel->info >> SYM_INDEX;
extern struct dyn_sym __dyn_sym_start[];
newaddr = __dyn_sym_start[symidx].addr + offset;
#ifdef IS_RELA
newaddr -= CONFIG_SYS_TEXT_BASE;
#endif
break;
}
#endif
default:
printf("%s: Unknown relocation type %llx\n",
__func__, rel->info & R_MASK);
continue;
}
/* Check if the relocation is inside bounds */
if (map && ((newaddr < map->virtual_start) ||
newaddr > (map->virtual_start +
(map->num_pages << EFI_PAGE_SHIFT)))) {
printf("%s: Relocation at %p is out of range (%lx)\n",
__func__, p, newaddr);
continue;
}
debug("%s: Setting %p to %lx\n", __func__, p, newaddr);
*p = newaddr;
flush_dcache_range((ulong)p & ~(EFI_CACHELINE_SIZE - 1),
ALIGN((ulong)&p[1], EFI_CACHELINE_SIZE));
}
#ifndef IS_RELA
lastoff = offset;
#endif
invalidate_icache_all();
}
/**
* efi_set_virtual_address_map() - change from physical to virtual mapping
*
* This function implements the SetVirtualAddressMap() runtime service.
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* @memory_map_size: size of the virtual map
* @descriptor_size: size of an entry in the map
* @descriptor_version: version of the map entries
* @virtmap: virtual address mapping information
* Return: status code
*/
static efi_status_t EFIAPI efi_set_virtual_address_map(
efi_uintn_t memory_map_size,
efi_uintn_t descriptor_size,
uint32_t descriptor_version,
struct efi_mem_desc *virtmap)
{
efi_uintn_t n = memory_map_size / descriptor_size;
efi_uintn_t i;
efi_status_t ret = EFI_INVALID_PARAMETER;
int rt_code_sections = 0;
struct efi_event *event;
EFI_ENTRY("%zx %zx %x %p", memory_map_size, descriptor_size,
descriptor_version, virtmap);
if (descriptor_version != EFI_MEMORY_DESCRIPTOR_VERSION ||
descriptor_size < sizeof(struct efi_mem_desc))
goto out;
efi_virtmap = virtmap;
efi_descriptor_size = descriptor_size;
efi_descriptor_count = n;
/*
* TODO:
* Further down we are cheating. While really we should implement
* SetVirtualAddressMap() events and ConvertPointer() to allow
* dynamically loaded drivers to expose runtime services, we don't
* today.
*
* So let's ensure we see exactly one single runtime section, as
* that is the built-in one. If we see more (or less), someone must
* have tried adding or removing to that which we don't support yet.
* In that case, let's better fail rather than expose broken runtime
* services.
*/
for (i = 0; i < n; i++) {
struct efi_mem_desc *map = (void*)virtmap +
(descriptor_size * i);
if (map->type == EFI_RUNTIME_SERVICES_CODE)
rt_code_sections++;
}
if (rt_code_sections != 1) {
/*
* We expose exactly one single runtime code section, so
* something is definitely going wrong.
*/
goto out;
}
/* Notify EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE */
list_for_each_entry(event, &efi_events, link) {
if (event->notify_function)
EFI_CALL_VOID(event->notify_function(
event, event->notify_context));
}
/* Rebind mmio pointers */
for (i = 0; i < n; i++) {
struct efi_mem_desc *map = (void*)virtmap +
(descriptor_size * i);
struct list_head *lhandle;
efi_physical_addr_t map_start = map->physical_start;
efi_physical_addr_t map_len = map->num_pages << EFI_PAGE_SHIFT;
efi_physical_addr_t map_end = map_start + map_len;
u64 off = map->virtual_start - map_start;
/* Adjust all mmio pointers in this region */
list_for_each(lhandle, &efi_runtime_mmio) {
struct efi_runtime_mmio_list *lmmio;
lmmio = list_entry(lhandle,
struct efi_runtime_mmio_list,
link);
if ((map_start <= lmmio->paddr) &&
(map_end >= lmmio->paddr)) {
uintptr_t new_addr = lmmio->paddr + off;
*lmmio->ptr = (void *)new_addr;
}
}
if ((map_start <= (uintptr_t)systab.tables) &&
(map_end >= (uintptr_t)systab.tables)) {
char *ptr = (char *)systab.tables;
ptr += off;
systab.tables = (struct efi_configuration_table *)ptr;
}
}
/* Relocate the runtime. See TODO above */
for (i = 0; i < n; i++) {
struct efi_mem_desc *map;
map = (void*)virtmap + (descriptor_size * i);
if (map->type == EFI_RUNTIME_SERVICES_CODE) {
ulong new_offset = map->virtual_start -
map->physical_start + gd->relocaddr;
efi_relocate_runtime_table(new_offset);
efi_runtime_relocate(new_offset, map);
ret = EFI_SUCCESS;
goto out;
}
}
out:
return EFI_EXIT(ret);
}
/**
* efi_add_runtime_mmio() - add memory-mapped IO region
*
* This function adds a memory-mapped IO region to the memory map to make it
* available at runtime.
*
* @mmio_ptr: pointer to a pointer to the start of the memory-mapped
* IO region
* @len: size of the memory-mapped IO region
* Returns: status code
*/
efi_status_t efi_add_runtime_mmio(void *mmio_ptr, u64 len)
{
struct efi_runtime_mmio_list *newmmio;
u64 pages = (len + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT;
uint64_t addr = *(uintptr_t *)mmio_ptr;
efi_status_t ret;
ret = efi_add_memory_map(addr, pages, EFI_MMAP_IO, false);
if (ret != EFI_SUCCESS)
return EFI_OUT_OF_RESOURCES;
newmmio = calloc(1, sizeof(*newmmio));
if (!newmmio)
return EFI_OUT_OF_RESOURCES;
newmmio->ptr = mmio_ptr;
newmmio->paddr = *(uintptr_t *)mmio_ptr;
newmmio->len = len;
list_add_tail(&newmmio->link, &efi_runtime_mmio);
return EFI_SUCCESS;
}
/*
* In the second stage, U-Boot has disappeared. To isolate our runtime code
* that at this point still exists from the rest, we put it into a special
* section.
*
* !!WARNING!!
*
* This means that we can not rely on any code outside of this file in any
* function or variable below this line.
*
* Please keep everything fully self-contained and annotated with
* __efi_runtime and __efi_runtime_data markers.
*/
/*
* Relocate the EFI runtime stub to a different place. We need to call this
* the first time we expose the runtime interface to a user and on set virtual
* address map calls.
*/
/**
* efi_unimplemented() - replacement function, returns EFI_UNSUPPORTED
*
* This function is used after SetVirtualAddressMap() is called as replacement
* for services that are not available anymore due to constraints of the U-Boot
* implementation.
*
* Return: EFI_UNSUPPORTED
*/
static efi_status_t __efi_runtime EFIAPI efi_unimplemented(void)
{
return EFI_UNSUPPORTED;
}
/**
* efi_update_capsule() - process information from operating system
*
* This function implements the UpdateCapsule() runtime service.
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* @capsule_header_array: pointer to array of virtual pointers
* @capsule_count: number of pointers in capsule_header_array
* @scatter_gather_list: pointer to arry of physical pointers
* Returns: status code
*/
efi_status_t __efi_runtime EFIAPI efi_update_capsule(
struct efi_capsule_header **capsule_header_array,
efi_uintn_t capsule_count,
u64 scatter_gather_list)
{
return EFI_UNSUPPORTED;
}
/**
* efi_query_capsule_caps() - check if capsule is supported
*
* This function implements the QueryCapsuleCapabilities() runtime service.
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* @capsule_header_array: pointer to array of virtual pointers
* @capsule_count: number of pointers in capsule_header_array
* @maximum_capsule_size: maximum capsule size
* @reset_type: type of reset needed for capsule update
* Returns: status code
*/
efi_status_t __efi_runtime EFIAPI efi_query_capsule_caps(
struct efi_capsule_header **capsule_header_array,
efi_uintn_t capsule_count,
u64 *maximum_capsule_size,
u32 *reset_type)
{
return EFI_UNSUPPORTED;
}
struct efi_runtime_services __efi_runtime_data efi_runtime_services = {
.hdr = {
.signature = EFI_RUNTIME_SERVICES_SIGNATURE,
.revision = EFI_SPECIFICATION_VERSION,
.headersize = sizeof(struct efi_runtime_services),
},
.get_time = &efi_get_time_boottime,
.set_time = &efi_set_time_boottime,
.get_wakeup_time = (void *)&efi_unimplemented,
.set_wakeup_time = (void *)&efi_unimplemented,
.set_virtual_address_map = &efi_set_virtual_address_map,
.convert_pointer = efi_convert_pointer,
.get_variable = efi_get_variable,
.get_next_variable_name = efi_get_next_variable_name,
.set_variable = efi_set_variable,
.get_next_high_mono_count = (void *)&efi_unimplemented,
.reset_system = &efi_reset_system_boottime,
.update_capsule = efi_update_capsule,
.query_capsule_caps = efi_query_capsule_caps,
.query_variable_info = efi_query_variable_info,
};
@@ -0,0 +1,185 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* EFI setup code
*
* Copyright (c) 2016-2018 Alexander Graf et al.
*/
#include <common.h>
#include <bootm.h>
#include <efi_loader.h>
#define OBJ_LIST_NOT_INITIALIZED 1
static efi_status_t efi_obj_list_initialized = OBJ_LIST_NOT_INITIALIZED;
/*
* Allow unaligned memory access.
*
* This routine is overridden by architectures providing this feature.
*/
void __weak allow_unaligned(void)
{
}
/**
* efi_init_platform_lang() - define supported languages
*
* Set the PlatformLangCodes and PlatformLang variables.
*
* Return: status code
*/
static efi_status_t efi_init_platform_lang(void)
{
efi_status_t ret;
efi_uintn_t data_size = 0;
char *lang = CONFIG_EFI_PLATFORM_LANG_CODES;
char *pos;
/*
* Variable PlatformLangCodes defines the language codes that the
* machine can support.
*/
ret = EFI_CALL(efi_set_variable(L"PlatformLangCodes",
&efi_global_variable_guid,
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
sizeof(CONFIG_EFI_PLATFORM_LANG_CODES),
CONFIG_EFI_PLATFORM_LANG_CODES));
if (ret != EFI_SUCCESS)
goto out;
/*
* Variable PlatformLang defines the language that the machine has been
* configured for.
*/
ret = EFI_CALL(efi_get_variable(L"PlatformLang",
&efi_global_variable_guid,
NULL, &data_size, &pos));
if (ret == EFI_BUFFER_TOO_SMALL) {
/* The variable is already set. Do not change it. */
ret = EFI_SUCCESS;
goto out;
}
/*
* The list of supported languages is semicolon separated. Use the first
* language to initialize PlatformLang.
*/
pos = strchr(lang, ';');
if (pos)
*pos = 0;
ret = EFI_CALL(efi_set_variable(L"PlatformLang",
&efi_global_variable_guid,
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
1 + strlen(lang), lang));
out:
if (ret != EFI_SUCCESS)
printf("EFI: cannot initialize platform language settings\n");
return ret;
}
/**
* efi_init_obj_list() - Initialize and populate EFI object list
*
* Return: status code
*/
efi_status_t efi_init_obj_list(void)
{
u64 os_indications_supported = 0; /* None */
efi_status_t ret = EFI_SUCCESS;
/* Initialize once only */
if (efi_obj_list_initialized != OBJ_LIST_NOT_INITIALIZED)
return efi_obj_list_initialized;
/* Allow unaligned memory access */
allow_unaligned();
/* On ARM switch from EL3 or secure mode to EL2 or non-secure mode */
switch_to_non_secure_mode();
/* Initialize variable services */
ret = efi_init_variables();
if (ret != EFI_SUCCESS)
goto out;
/* Define supported languages */
ret = efi_init_platform_lang();
if (ret != EFI_SUCCESS)
goto out;
/* Indicate supported features */
ret = EFI_CALL(efi_set_variable(L"OsIndicationsSupported",
&efi_global_variable_guid,
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
sizeof(os_indications_supported),
&os_indications_supported));
if (ret != EFI_SUCCESS)
goto out;
/* Indicate supported runtime services */
ret = efi_init_runtime_supported();
if (ret != EFI_SUCCESS)
goto out;
/* Initialize system table */
ret = efi_initialize_system_table();
if (ret != EFI_SUCCESS)
goto out;
/* Initialize root node */
ret = efi_root_node_register();
if (ret != EFI_SUCCESS)
goto out;
/* Initialize EFI driver uclass */
ret = efi_driver_init();
if (ret != EFI_SUCCESS)
goto out;
ret = efi_console_register();
if (ret != EFI_SUCCESS)
goto out;
#ifdef CONFIG_PARTITIONS
ret = efi_disk_register();
if (ret != EFI_SUCCESS)
goto out;
#endif
#if defined(CONFIG_LCD) || defined(CONFIG_DM_VIDEO)
ret = efi_gop_register();
if (ret != EFI_SUCCESS)
goto out;
#endif
#ifdef CONFIG_NET
ret = efi_net_register();
if (ret != EFI_SUCCESS)
goto out;
#endif
#ifdef CONFIG_GENERATE_ACPI_TABLE
ret = efi_acpi_register();
if (ret != EFI_SUCCESS)
goto out;
#endif
#ifdef CONFIG_GENERATE_SMBIOS_TABLE
ret = efi_smbios_register();
if (ret != EFI_SUCCESS)
goto out;
#endif
ret = efi_watchdog_register();
if (ret != EFI_SUCCESS)
goto out;
/* Initialize EFI runtime services */
ret = efi_reset_system_init();
if (ret != EFI_SUCCESS)
goto out;
out:
efi_obj_list_initialized = ret;
return ret;
}
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* EFI application tables support
*
* Copyright (c) 2016 Alexander Graf
*/
#include <common.h>
#include <efi_loader.h>
#include <mapmem.h>
#include <smbios.h>
static const efi_guid_t smbios_guid = SMBIOS_TABLE_GUID;
/*
* Install the SMBIOS table as a configuration table.
*
* @return status code
*/
efi_status_t efi_smbios_register(void)
{
/* Map within the low 32 bits, to allow for 32bit SMBIOS tables */
u64 dmi_addr = U32_MAX;
efi_status_t ret;
void *dmi;
/* Reserve 4kiB page for SMBIOS */
ret = efi_allocate_pages(EFI_ALLOCATE_MAX_ADDRESS,
EFI_RUNTIME_SERVICES_DATA, 1, &dmi_addr);
if (ret != EFI_SUCCESS) {
/* Could not find space in lowmem, use highmem instead */
ret = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES,
EFI_RUNTIME_SERVICES_DATA, 1,
&dmi_addr);
if (ret != EFI_SUCCESS)
return ret;
}
/*
* Generate SMBIOS tables - we know that efi_allocate_pages() returns
* a 4k-aligned address, so it is safe to assume that
* write_smbios_table() will write the table at that address.
*
* Note that on sandbox, efi_allocate_pages() unfortunately returns a
* pointer even though it uses a uint64_t type. Convert it.
*/
assert(!(dmi_addr & 0xf));
dmi = (void *)(uintptr_t)dmi_addr;
write_smbios_table(map_to_sysmem(dmi));
/* And expose them to our EFI payload */
return efi_install_configuration_table(&smbios_guid, dmi);
}
@@ -0,0 +1,368 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* EFI Unicode collation protocol
*
* Copyright (c) 2018 Heinrich Schuchardt <xypron.glpk@gmx.de>
*/
#include <common.h>
#include <charset.h>
#include <cp1250.h>
#include <cp437.h>
#include <efi_loader.h>
/* Characters that may not be used in FAT 8.3 file names */
static const char illegal[] = "+,<=>:;\"/\\|?*[]\x7f";
/*
* EDK2 assumes codepage 1250 when creating FAT 8.3 file names.
* Linux defaults to codepage 437 for FAT 8.3 file names.
*/
#if CONFIG_FAT_DEFAULT_CODEPAGE == 1250
/* Unicode code points for code page 1250 characters 0x80 - 0xff */
static const u16 codepage[] = CP1250;
#else
/* Unicode code points for code page 437 characters 0x80 - 0xff */
static const u16 codepage[] = CP437;
#endif
/* GUID of the EFI_UNICODE_COLLATION_PROTOCOL2 */
const efi_guid_t efi_guid_unicode_collation_protocol2 =
EFI_UNICODE_COLLATION_PROTOCOL2_GUID;
/**
* efi_stri_coll() - compare utf-16 strings case-insenitively
*
* @this: unicode collation protocol instance
* @s1: first string
* @s2: second string
*
* This function implements the StriColl() service of the
* EFI_UNICODE_COLLATION_PROTOCOL.
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* Return: 0: s1 == s2, > 0: s1 > s2, < 0: s1 < s2
*/
static efi_intn_t EFIAPI efi_stri_coll(
struct efi_unicode_collation_protocol *this, u16 *s1, u16 *s2)
{
s32 c1, c2;
efi_intn_t ret = 0;
EFI_ENTRY("%p, %ls, %ls", this, s1, s2);
for (; *s1 | *s2; ++s1, ++s2) {
c1 = utf_to_upper(*s1);
c2 = utf_to_upper(*s2);
if (c1 < c2) {
ret = -1;
goto out;
} else if (c1 > c2) {
ret = 1;
goto out;
}
}
out:
EFI_EXIT(EFI_SUCCESS);
return ret;
}
/**
* next_lower() - get next codepoint converted to lower case
*
* @string: pointer to u16 string, on return advanced by one codepoint
* Return: first codepoint of string converted to lower case
*/
static s32 next_lower(const u16 **string)
{
return utf_to_lower(utf16_get(string));
}
/**
* metai_match() - compare utf-16 string with a pattern string case-insenitively
*
* @string: string to compare
* @pattern: pattern string
*
* The pattern string may use these:
* - * matches >= 0 characters
* - ? matches 1 character
* - [<char1><char2>...<charN>] match any character in the set
* - [<char1>-<char2>] matches any character in the range
*
* This function is called my efi_metai_match().
*
* For '*' pattern searches this function calls itself recursively.
* Performance-wise this is suboptimal, especially for multiple '*' wildcards.
* But it results in simple code.
*
* Return: true if the string is matched.
*/
static bool metai_match(const u16 *string, const u16 *pattern)
{
s32 first, s, p;
for (; *string && *pattern;) {
const u16 *string_old = string;
s = next_lower(&string);
p = next_lower(&pattern);
switch (p) {
case '*':
/* Match 0 or more characters */
for (;; s = next_lower(&string)) {
if (metai_match(string_old, pattern))
return true;
if (!s)
return false;
string_old = string;
}
case '?':
/* Match any one character */
break;
case '[':
/* Match any character in the set */
p = next_lower(&pattern);
first = p;
if (first == ']')
/* Empty set */
return false;
p = next_lower(&pattern);
if (p == '-') {
/* Range */
p = next_lower(&pattern);
if (s < first || s > p)
return false;
p = next_lower(&pattern);
if (p != ']')
return false;
} else {
/* Set */
bool hit = false;
if (s == first)
hit = true;
for (; p && p != ']';
p = next_lower(&pattern)) {
if (p == s)
hit = true;
}
if (!hit || p != ']')
return false;
}
break;
default:
/* Match one character */
if (p != s)
return false;
}
}
if (!*pattern && !*string)
return true;
return false;
}
/**
* efi_metai_match() - compare utf-16 string with a pattern string
* case-insenitively
*
* @this: unicode collation protocol instance
* @s: string to compare
* @p: pattern string
*
* The pattern string may use these:
* - * matches >= 0 characters
* - ? matches 1 character
* - [<char1><char2>...<charN>] match any character in the set
* - [<char1>-<char2>] matches any character in the range
*
* This function implements the MetaMatch() service of the
* EFI_UNICODE_COLLATION_PROTOCOL.
*
* Return: true if the string is matched.
*/
static bool EFIAPI efi_metai_match(struct efi_unicode_collation_protocol *this,
const u16 *string, const u16 *pattern)
{
bool ret;
EFI_ENTRY("%p, %ls, %ls", this, string, pattern);
ret = metai_match(string, pattern);
EFI_EXIT(EFI_SUCCESS);
return ret;
}
/**
* efi_str_lwr() - convert to lower case
*
* @this: unicode collation protocol instance
* @string: string to convert
* @p: pattern string
*
* The conversion is done in place. As long as upper and lower letters use the
* same number of words this does not pose a problem.
*
* This function implements the StrLwr() service of the
* EFI_UNICODE_COLLATION_PROTOCOL.
*/
static void EFIAPI efi_str_lwr(struct efi_unicode_collation_protocol *this,
u16 *string)
{
EFI_ENTRY("%p, %ls", this, string);
for (; *string; ++string)
*string = utf_to_lower(*string);
EFI_EXIT(EFI_SUCCESS);
}
/**
* efi_str_upr() - convert to upper case
*
* @this: unicode collation protocol instance
* @string: string to convert
* @p: pattern string
*
* The conversion is done in place. As long as upper and lower letters use the
* same number of words this does not pose a problem.
*
* This function implements the StrUpr() service of the
* EFI_UNICODE_COLLATION_PROTOCOL.
*/
static void EFIAPI efi_str_upr(struct efi_unicode_collation_protocol *this,
u16 *string)
{
EFI_ENTRY("%p, %ls", this, string);
for (; *string; ++string)
*string = utf_to_upper(*string);
EFI_EXIT(EFI_SUCCESS);
}
/**
* efi_fat_to_str() - convert an 8.3 file name from an OEM codepage to Unicode
*
* @this: unicode collation protocol instance
* @fat_size: size of the string to convert
* @fat: string to convert
* @string: converted string
*
* This function implements the FatToStr() service of the
* EFI_UNICODE_COLLATION_PROTOCOL.
*/
static void EFIAPI efi_fat_to_str(struct efi_unicode_collation_protocol *this,
efi_uintn_t fat_size, char *fat, u16 *string)
{
efi_uintn_t i;
u16 c;
EFI_ENTRY("%p, %zu, %s, %p", this, fat_size, fat, string);
for (i = 0; i < fat_size; ++i) {
c = (unsigned char)fat[i];
if (c > 0x80)
c = codepage[i - 0x80];
string[i] = c;
if (!c)
break;
}
string[i] = 0;
EFI_EXIT(EFI_SUCCESS);
}
/**
* efi_fat_to_str() - convert a utf-16 string to legal characters for a FAT
* file name in an OEM code page
*
* @this: unicode collation protocol instance
* @string: Unicode string to convert
* @fat_size: size of the target buffer
* @fat: converted string
*
* This function implements the StrToFat() service of the
* EFI_UNICODE_COLLATION_PROTOCOL.
*
* Return: true if an illegal character was substituted by '_'.
*/
static bool EFIAPI efi_str_to_fat(struct efi_unicode_collation_protocol *this,
const u16 *string, efi_uintn_t fat_size,
char *fat)
{
efi_uintn_t i;
s32 c;
bool ret = false;
EFI_ENTRY("%p, %ls, %zu, %p", this, string, fat_size, fat);
for (i = 0; i < fat_size;) {
c = utf16_get(&string);
switch (c) {
/* Ignore period and space */
case '.':
case ' ':
continue;
case 0:
break;
}
c = utf_to_upper(c);
if (c >= 0x80) {
int j;
/* Look for codepage translation */
for (j = 0; j < 0x80; ++j) {
if (c == codepage[j]) {
c = j + 0x80;
break;
}
}
if (j >= 0x80) {
c = '_';
ret = true;
}
} else if (c && (c < 0x20 || strchr(illegal, c))) {
c = '_';
ret = true;
}
fat[i] = c;
if (!c)
break;
++i;
}
EFI_EXIT(EFI_SUCCESS);
return ret;
}
const struct efi_unicode_collation_protocol efi_unicode_collation_protocol2 = {
.stri_coll = efi_stri_coll,
.metai_match = efi_metai_match,
.str_lwr = efi_str_lwr,
.str_upr = efi_str_upr,
.fat_to_str = efi_fat_to_str,
.str_to_fat = efi_str_to_fat,
.supported_languages = "en",
};
/*
* In EFI 1.10 a version of the Unicode collation protocol using ISO 639-2
* language codes existed. This protocol is not part of the UEFI specification
* any longer. Unfortunately it is required to run the UEFI Self Certification
* Test (SCT) II, version 2.6, 2017. So we implement it here for the sole
* purpose of running the SCT. It can be removed when a compliant SCT is
* available.
*/
#if CONFIG_IS_ENABLED(EFI_UNICODE_COLLATION_PROTOCOL)
/* GUID of the EFI_UNICODE_COLLATION_PROTOCOL */
const efi_guid_t efi_guid_unicode_collation_protocol =
EFI_UNICODE_COLLATION_PROTOCOL_GUID;
const struct efi_unicode_collation_protocol efi_unicode_collation_protocol = {
.stri_coll = efi_stri_coll,
.metai_match = efi_metai_match,
.str_lwr = efi_str_lwr,
.str_upr = efi_str_upr,
.fat_to_str = efi_fat_to_str,
.str_to_fat = efi_str_to_fat,
/* ISO 639-2 language code */
.supported_languages = "eng",
};
#endif
@@ -0,0 +1,643 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* EFI utils
*
* Copyright (c) 2017 Rob Clark
*/
#include <common.h>
#include <efi_loader.h>
#include <env_internal.h>
#include <hexdump.h>
#include <malloc.h>
#include <search.h>
#include <u-boot/crc.h>
#define READ_ONLY BIT(31)
/*
* Mapping between EFI variables and u-boot variables:
*
* efi_$guid_$varname = {attributes}(type)value
*
* For example:
*
* efi_8be4df61-93ca-11d2-aa0d-00e098032b8c_OsIndicationsSupported=
* "{ro,boot,run}(blob)0000000000000000"
* efi_8be4df61-93ca-11d2-aa0d-00e098032b8c_BootOrder=
* "(blob)00010000"
*
* The attributes are a comma separated list of these possible
* attributes:
*
* + ro - read-only
* + boot - boot-services access
* + run - runtime access
*
* NOTE: with current implementation, no variables are available after
* ExitBootServices, and all are persisted (if possible).
*
* If not specified, the attributes default to "{boot}".
*
* The required type is one of:
*
* + utf8 - raw utf8 string
* + blob - arbitrary length hex string
*
* Maybe a utf16 type would be useful to for a string value to be auto
* converted to utf16?
*/
#define PREFIX_LEN (strlen("efi_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_"))
/**
* efi_to_native() - convert the UEFI variable name and vendor GUID to U-Boot
* variable name
*
* The U-Boot variable name is a concatenation of prefix 'efi', the hexstring
* encoded vendor GUID, and the UTF-8 encoded UEFI variable name separated by
* underscores, e.g. 'efi_8be4df61-93ca-11d2-aa0d-00e098032b8c_BootOrder'.
*
* @native: pointer to pointer to U-Boot variable name
* @variable_name: UEFI variable name
* @vendor: vendor GUID
* Return: status code
*/
static efi_status_t efi_to_native(char **native, const u16 *variable_name,
const efi_guid_t *vendor)
{
size_t len;
char *pos;
len = PREFIX_LEN + utf16_utf8_strlen(variable_name) + 1;
*native = malloc(len);
if (!*native)
return EFI_OUT_OF_RESOURCES;
pos = *native;
pos += sprintf(pos, "efi_%pUl_", vendor);
utf16_utf8_strcpy(&pos, variable_name);
return EFI_SUCCESS;
}
/**
* prefix() - skip over prefix
*
* Skip over a prefix string.
*
* @str: string with prefix
* @prefix: prefix string
* Return: string without prefix, or NULL if prefix not found
*/
static const char *prefix(const char *str, const char *prefix)
{
size_t n = strlen(prefix);
if (!strncmp(prefix, str, n))
return str + n;
return NULL;
}
/**
* parse_attr() - decode attributes part of variable value
*
* Convert the string encoded attributes of a UEFI variable to a bit mask.
* TODO: Several attributes are not supported.
*
* @str: value of U-Boot variable
* @attrp: pointer to UEFI attributes
* Return: pointer to remainder of U-Boot variable value
*/
static const char *parse_attr(const char *str, u32 *attrp)
{
u32 attr = 0;
char sep = '{';
if (*str != '{') {
*attrp = EFI_VARIABLE_BOOTSERVICE_ACCESS;
return str;
}
while (*str == sep) {
const char *s;
str++;
if ((s = prefix(str, "ro"))) {
attr |= READ_ONLY;
} else if ((s = prefix(str, "nv"))) {
attr |= EFI_VARIABLE_NON_VOLATILE;
} else if ((s = prefix(str, "boot"))) {
attr |= EFI_VARIABLE_BOOTSERVICE_ACCESS;
} else if ((s = prefix(str, "run"))) {
attr |= EFI_VARIABLE_RUNTIME_ACCESS;
} else {
printf("invalid attribute: %s\n", str);
break;
}
str = s;
sep = ',';
}
str++;
*attrp = attr;
return str;
}
/**
* efi_get_variable() - retrieve value of a UEFI variable
*
* This function implements the GetVariable runtime service.
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* @variable_name: name of the variable
* @vendor: vendor GUID
* @attributes: attributes of the variable
* @data_size: size of the buffer to which the variable value is copied
* @data: buffer to which the variable value is copied
* Return: status code
*/
efi_status_t EFIAPI efi_get_variable(u16 *variable_name,
const efi_guid_t *vendor, u32 *attributes,
efi_uintn_t *data_size, void *data)
{
char *native_name;
efi_status_t ret;
unsigned long in_size;
const char *val, *s;
u32 attr;
EFI_ENTRY("\"%ls\" %pUl %p %p %p", variable_name, vendor, attributes,
data_size, data);
if (!variable_name || !vendor || !data_size)
return EFI_EXIT(EFI_INVALID_PARAMETER);
ret = efi_to_native(&native_name, variable_name, vendor);
if (ret)
return EFI_EXIT(ret);
EFI_PRINT("get '%s'\n", native_name);
val = env_get(native_name);
free(native_name);
if (!val)
return EFI_EXIT(EFI_NOT_FOUND);
val = parse_attr(val, &attr);
in_size = *data_size;
if ((s = prefix(val, "(blob)"))) {
size_t len = strlen(s);
/* number of hexadecimal digits must be even */
if (len & 1)
return EFI_EXIT(EFI_DEVICE_ERROR);
/* two characters per byte: */
len /= 2;
*data_size = len;
if (in_size < len) {
ret = EFI_BUFFER_TOO_SMALL;
goto out;
}
if (!data)
return EFI_EXIT(EFI_INVALID_PARAMETER);
if (hex2bin(data, s, len))
return EFI_EXIT(EFI_DEVICE_ERROR);
EFI_PRINT("got value: \"%s\"\n", s);
} else if ((s = prefix(val, "(utf8)"))) {
unsigned len = strlen(s) + 1;
*data_size = len;
if (in_size < len) {
ret = EFI_BUFFER_TOO_SMALL;
goto out;
}
if (!data)
return EFI_EXIT(EFI_INVALID_PARAMETER);
memcpy(data, s, len);
((char *)data)[len] = '\0';
EFI_PRINT("got value: \"%s\"\n", (char *)data);
} else {
EFI_PRINT("invalid value: '%s'\n", val);
return EFI_EXIT(EFI_DEVICE_ERROR);
}
out:
if (attributes)
*attributes = attr & EFI_VARIABLE_MASK;
return EFI_EXIT(ret);
}
static char *efi_variables_list;
static char *efi_cur_variable;
/**
* parse_uboot_variable() - parse a u-boot variable and get uefi-related
* information
* @variable: whole data of u-boot variable (ie. name=value)
* @variable_name_size: size of variable_name buffer in byte
* @variable_name: name of uefi variable in u16, null-terminated
* @vendor: vendor's guid
* @attributes: attributes
*
* A uefi variable is encoded into a u-boot variable as described above.
* This function parses such a u-boot variable and retrieve uefi-related
* information into respective parameters. In return, variable_name_size
* is the size of variable name including NULL.
*
* Return: EFI_SUCCESS if parsing is OK, EFI_NOT_FOUND when
* the entire variable list has been returned,
* otherwise non-zero status code
*/
static efi_status_t parse_uboot_variable(char *variable,
efi_uintn_t *variable_name_size,
u16 *variable_name,
const efi_guid_t *vendor,
u32 *attributes)
{
char *guid, *name, *end, c;
unsigned long name_len;
u16 *p;
guid = strchr(variable, '_');
if (!guid)
return EFI_INVALID_PARAMETER;
guid++;
name = strchr(guid, '_');
if (!name)
return EFI_INVALID_PARAMETER;
name++;
end = strchr(name, '=');
if (!end)
return EFI_INVALID_PARAMETER;
name_len = end - name;
if (*variable_name_size < (name_len + 1)) {
*variable_name_size = name_len + 1;
return EFI_BUFFER_TOO_SMALL;
}
end++; /* point to value */
/* variable name */
p = variable_name;
utf8_utf16_strncpy(&p, name, name_len);
variable_name[name_len] = 0;
*variable_name_size = name_len + 1;
/* guid */
c = *(name - 1);
*(name - 1) = '\0'; /* guid need be null-terminated here */
uuid_str_to_bin(guid, (unsigned char *)vendor, UUID_STR_FORMAT_GUID);
*(name - 1) = c;
/* attributes */
parse_attr(end, attributes);
return EFI_SUCCESS;
}
/**
* efi_get_next_variable_name() - enumerate the current variable names
*
* @variable_name_size: size of variable_name buffer in byte
* @variable_name: name of uefi variable's name in u16
* @vendor: vendor's guid
*
* This function implements the GetNextVariableName service.
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* Return: status code
*/
efi_status_t EFIAPI efi_get_next_variable_name(efi_uintn_t *variable_name_size,
u16 *variable_name,
const efi_guid_t *vendor)
{
char *native_name, *variable;
ssize_t name_len, list_len;
char regex[256];
char * const regexlist[] = {regex};
u32 attributes;
int i;
efi_status_t ret;
EFI_ENTRY("%p \"%ls\" %pUl", variable_name_size, variable_name, vendor);
if (!variable_name_size || !variable_name || !vendor)
return EFI_EXIT(EFI_INVALID_PARAMETER);
if (variable_name[0]) {
/* check null-terminated string */
for (i = 0; i < *variable_name_size; i++)
if (!variable_name[i])
break;
if (i >= *variable_name_size)
return EFI_EXIT(EFI_INVALID_PARAMETER);
/* search for the last-returned variable */
ret = efi_to_native(&native_name, variable_name, vendor);
if (ret)
return EFI_EXIT(ret);
name_len = strlen(native_name);
for (variable = efi_variables_list; variable && *variable;) {
if (!strncmp(variable, native_name, name_len) &&
variable[name_len] == '=')
break;
variable = strchr(variable, '\n');
if (variable)
variable++;
}
free(native_name);
if (!(variable && *variable))
return EFI_EXIT(EFI_INVALID_PARAMETER);
/* next variable */
variable = strchr(variable, '\n');
if (variable)
variable++;
if (!(variable && *variable))
return EFI_EXIT(EFI_NOT_FOUND);
} else {
/*
*new search: free a list used in the previous search
*/
free(efi_variables_list);
efi_variables_list = NULL;
efi_cur_variable = NULL;
snprintf(regex, 256, "efi_.*-.*-.*-.*-.*_.*");
list_len = hexport_r(&env_htab, '\n',
H_MATCH_REGEX | H_MATCH_KEY,
&efi_variables_list, 0, 1, regexlist);
/* 1 indicates that no match was found */
if (list_len <= 1)
return EFI_EXIT(EFI_NOT_FOUND);
variable = efi_variables_list;
}
ret = parse_uboot_variable(variable, variable_name_size, variable_name,
vendor, &attributes);
return EFI_EXIT(ret);
}
/**
* efi_set_variable() - set value of a UEFI variable
*
* This function implements the SetVariable runtime service.
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* @variable_name: name of the variable
* @vendor: vendor GUID
* @attributes: attributes of the variable
* @data_size: size of the buffer with the variable value
* @data: buffer with the variable value
* Return: status code
*/
efi_status_t EFIAPI efi_set_variable(u16 *variable_name,
const efi_guid_t *vendor, u32 attributes,
efi_uintn_t data_size, const void *data)
{
char *native_name = NULL, *val = NULL, *s;
const char *old_val;
size_t old_size;
efi_status_t ret = EFI_SUCCESS;
u32 attr;
EFI_ENTRY("\"%ls\" %pUl %x %zu %p", variable_name, vendor, attributes,
data_size, data);
if (!variable_name || !*variable_name || !vendor ||
((attributes & EFI_VARIABLE_RUNTIME_ACCESS) &&
!(attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS))) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
ret = efi_to_native(&native_name, variable_name, vendor);
if (ret)
goto out;
old_val = env_get(native_name);
if (old_val) {
old_val = parse_attr(old_val, &attr);
/* check read-only first */
if (attr & READ_ONLY) {
ret = EFI_WRITE_PROTECTED;
goto out;
}
if ((data_size == 0 &&
!(attributes & EFI_VARIABLE_APPEND_WRITE)) ||
!attributes) {
/* delete the variable: */
env_set(native_name, NULL);
ret = EFI_SUCCESS;
goto out;
}
/* attributes won't be changed */
if (attr != (attributes & ~EFI_VARIABLE_APPEND_WRITE)) {
ret = EFI_INVALID_PARAMETER;
goto out;
}
if (attributes & EFI_VARIABLE_APPEND_WRITE) {
if (!prefix(old_val, "(blob)")) {
ret = EFI_DEVICE_ERROR;
goto out;
}
old_size = strlen(old_val);
} else {
old_size = 0;
}
} else {
if (data_size == 0 || !attributes ||
(attributes & EFI_VARIABLE_APPEND_WRITE)) {
/*
* Trying to delete or to update a non-existent
* variable.
*/
ret = EFI_NOT_FOUND;
goto out;
}
old_size = 0;
}
val = malloc(old_size + 2 * data_size
+ strlen("{ro,run,boot,nv}(blob)") + 1);
if (!val) {
ret = EFI_OUT_OF_RESOURCES;
goto out;
}
s = val;
/* store attributes */
attributes &= (EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS);
s += sprintf(s, "{");
while (attributes) {
u32 attr = 1 << (ffs(attributes) - 1);
if (attr == EFI_VARIABLE_NON_VOLATILE)
s += sprintf(s, "nv");
else if (attr == EFI_VARIABLE_BOOTSERVICE_ACCESS)
s += sprintf(s, "boot");
else if (attr == EFI_VARIABLE_RUNTIME_ACCESS)
s += sprintf(s, "run");
attributes &= ~attr;
if (attributes)
s += sprintf(s, ",");
}
s += sprintf(s, "}");
if (old_size)
/* APPEND_WRITE */
s += sprintf(s, old_val);
else
s += sprintf(s, "(blob)");
/* store payload: */
s = bin2hex(s, data, data_size);
*s = '\0';
EFI_PRINT("setting: %s=%s\n", native_name, val);
if (env_set(native_name, val))
ret = EFI_DEVICE_ERROR;
out:
free(native_name);
free(val);
return EFI_EXIT(ret);
}
/**
* efi_query_variable_info() - get information about EFI variables
*
* This function implements the QueryVariableInfo() runtime service.
*
* See the Unified Extensible Firmware Interface (UEFI) specification for
* details.
*
* @attributes: bitmask to select variables to be
* queried
* @maximum_variable_storage_size: maximum size of storage area for the
* selected variable types
* @remaining_variable_storage_size: remaining size of storage are for the
* selected variable types
* @maximum_variable_size: maximum size of a variable of the
* selected type
* Returns: status code
*/
efi_status_t __efi_runtime EFIAPI efi_query_variable_info(
u32 attributes,
u64 *maximum_variable_storage_size,
u64 *remaining_variable_storage_size,
u64 *maximum_variable_size)
{
return EFI_UNSUPPORTED;
}
/**
* efi_get_variable_runtime() - runtime implementation of GetVariable()
*
* @variable_name: name of the variable
* @vendor: vendor GUID
* @attributes: attributes of the variable
* @data_size: size of the buffer to which the variable value is copied
* @data: buffer to which the variable value is copied
* Return: status code
*/
static efi_status_t __efi_runtime EFIAPI
efi_get_variable_runtime(u16 *variable_name, const efi_guid_t *vendor,
u32 *attributes, efi_uintn_t *data_size, void *data)
{
return EFI_UNSUPPORTED;
}
/**
* efi_get_next_variable_name_runtime() - runtime implementation of
* GetNextVariable()
*
* @variable_name_size: size of variable_name buffer in byte
* @variable_name: name of uefi variable's name in u16
* @vendor: vendor's guid
* Return: status code
*/
static efi_status_t __efi_runtime EFIAPI
efi_get_next_variable_name_runtime(efi_uintn_t *variable_name_size,
u16 *variable_name, const efi_guid_t *vendor)
{
return EFI_UNSUPPORTED;
}
/**
* efi_set_variable_runtime() - runtime implementation of SetVariable()
*
* @variable_name: name of the variable
* @vendor: vendor GUID
* @attributes: attributes of the variable
* @data_size: size of the buffer with the variable value
* @data: buffer with the variable value
* Return: status code
*/
static efi_status_t __efi_runtime EFIAPI
efi_set_variable_runtime(u16 *variable_name, const efi_guid_t *vendor,
u32 attributes, efi_uintn_t data_size,
const void *data)
{
return EFI_UNSUPPORTED;
}
/**
* efi_variables_boot_exit_notify() - notify ExitBootServices() is called
*/
void efi_variables_boot_exit_notify(void)
{
efi_runtime_services.get_variable = efi_get_variable_runtime;
efi_runtime_services.get_next_variable_name =
efi_get_next_variable_name_runtime;
efi_runtime_services.set_variable = efi_set_variable_runtime;
efi_update_table_header_crc32(&efi_runtime_services.hdr);
}
/**
* efi_init_variables() - initialize variable services
*
* Return: status code
*/
efi_status_t efi_init_variables(void)
{
return EFI_SUCCESS;
}
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* EFI watchdog
*
* Copyright (c) 2017 Heinrich Schuchardt
*/
#include <common.h>
#include <efi_loader.h>
/* Conversion factor from seconds to multiples of 100ns */
#define EFI_SECONDS_TO_100NS 10000000ULL
static struct efi_event *watchdog_timer_event;
/*
* Reset the system when the watchdog event is notified.
*
* @event: the watchdog event
* @context: not used
*/
static void EFIAPI efi_watchdog_timer_notify(struct efi_event *event,
void *context)
{
EFI_ENTRY("%p, %p", event, context);
printf("\nEFI: Watchdog timeout\n");
EFI_CALL_VOID(efi_runtime_services.reset_system(EFI_RESET_COLD,
EFI_SUCCESS, 0, NULL));
EFI_EXIT(EFI_UNSUPPORTED);
}
/*
* Reset the watchdog timer.
*
* This function is used by the SetWatchdogTimer service.
*
* @timeout: seconds before reset by watchdog
* @return: status code
*/
efi_status_t efi_set_watchdog(unsigned long timeout)
{
efi_status_t r;
if (timeout)
/* Reset watchdog */
r = efi_set_timer(watchdog_timer_event, EFI_TIMER_RELATIVE,
EFI_SECONDS_TO_100NS * timeout);
else
/* Deactivate watchdog */
r = efi_set_timer(watchdog_timer_event, EFI_TIMER_STOP, 0);
return r;
}
/*
* Initialize the EFI watchdog.
*
* This function is called by efi_init_obj_list()
*/
efi_status_t efi_watchdog_register(void)
{
efi_status_t r;
/*
* Create a timer event.
*/
r = efi_create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK,
efi_watchdog_timer_notify, NULL, NULL,
&watchdog_timer_event);
if (r != EFI_SUCCESS) {
printf("ERROR: Failed to register watchdog event\n");
return r;
}
/*
* The UEFI standard requires that the watchdog timer is set to five
* minutes when invoking an EFI boot option.
*
* Unified Extensible Firmware Interface (UEFI), version 2.7 Errata A
* 7.5. Miscellaneous Boot Services - EFI_BOOT_SERVICES.SetWatchdogTimer
*/
r = efi_set_watchdog(300);
if (r != EFI_SUCCESS) {
printf("ERROR: Failed to set watchdog timer\n");
return r;
}
return EFI_SUCCESS;
}
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* EFI hello world
*
* Copyright (c) 2016 Google, Inc
* Written by Simon Glass <sjg@chromium.org>
*
* This program demonstrates calling a boottime service.
* It writes a greeting and the load options to the console.
*/
#include <common.h>
#include <efi_api.h>
static const efi_guid_t loaded_image_guid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
static const efi_guid_t fdt_guid = EFI_FDT_GUID;
static const efi_guid_t acpi_guid = EFI_ACPI_TABLE_GUID;
static const efi_guid_t smbios_guid = SMBIOS_TABLE_GUID;
/**
* efi_main() - entry point of the EFI application.
*
* @handle: handle of the loaded image
* @systable: system table
* @return: status code
*/
efi_status_t EFIAPI efi_main(efi_handle_t handle,
struct efi_system_table *systable)
{
struct efi_simple_text_output_protocol *con_out = systable->con_out;
struct efi_boot_services *boottime = systable->boottime;
struct efi_loaded_image *loaded_image;
efi_status_t ret;
efi_uintn_t i;
u16 rev[] = L"0.0.0";
/* UEFI requires CR LF */
con_out->output_string(con_out, L"Hello, world!\r\n");
/* Print the revision number */
rev[0] = (systable->hdr.revision >> 16) + '0';
rev[4] = systable->hdr.revision & 0xffff;
for (; rev[4] >= 10;) {
rev[4] -= 10;
++rev[2];
}
/* Third digit is only to be shown if non-zero */
if (rev[4])
rev[4] += '0';
else
rev[3] = 0;
con_out->output_string(con_out, L"Running on UEFI ");
con_out->output_string(con_out, rev);
con_out->output_string(con_out, L"\r\n");
/* Get the loaded image protocol */
ret = boottime->handle_protocol(handle, &loaded_image_guid,
(void **)&loaded_image);
if (ret != EFI_SUCCESS) {
con_out->output_string
(con_out, L"Cannot open loaded image protocol\r\n");
goto out;
}
/* Find configuration tables */
for (i = 0; i < systable->nr_tables; ++i) {
if (!memcmp(&systable->tables[i].guid, &fdt_guid,
sizeof(efi_guid_t)))
con_out->output_string
(con_out, L"Have device tree\r\n");
if (!memcmp(&systable->tables[i].guid, &acpi_guid,
sizeof(efi_guid_t)))
con_out->output_string
(con_out, L"Have ACPI 2.0 table\r\n");
if (!memcmp(&systable->tables[i].guid, &smbios_guid,
sizeof(efi_guid_t)))
con_out->output_string
(con_out, L"Have SMBIOS table\r\n");
}
/* Output the load options */
con_out->output_string(con_out, L"Load options: ");
if (loaded_image->load_options_size && loaded_image->load_options)
con_out->output_string(con_out,
(u16 *)loaded_image->load_options);
else
con_out->output_string(con_out, L"<none>");
con_out->output_string(con_out, L"\r\n");
out:
boottime->exit(handle, ret, 0, NULL);
/* We should never arrive here */
return ret;
}