From 51c533fdce75fbdec7de28a05a675a553d209bcb Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Mon, 28 Aug 2017 18:54:30 +0200 Subject: efi_loader: bootefi hello should use loadaddr Command 'bootefi hello' currently uses CONFIG_SYS_LOAD_ADDR as loading address. qemu machines have by default 128 MiB RAM. CONFIG_SYS_LOAD_ADDR for x86 is 0x20000000 (512 MiB). This causes 'bootefi hello' to fail. We should use the environment variable loadaddr if available. It defaults to 0x1000000 (16 MiB) on qemu_x86. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- cmd/bootefi.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/bootefi.c b/cmd/bootefi.c index 3196d86040..c65c619d63 100644 --- a/cmd/bootefi.c +++ b/cmd/bootefi.c @@ -299,7 +299,11 @@ static int do_bootefi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) if (!strcmp(argv[1], "hello")) { ulong size = __efi_hello_world_end - __efi_hello_world_begin; - addr = CONFIG_SYS_LOAD_ADDR; + saddr = env_get("loadaddr"); + if (saddr) + addr = simple_strtoul(saddr, NULL, 16); + else + addr = CONFIG_SYS_LOAD_ADDR; memcpy((char *)addr, __efi_hello_world_begin, size); } else #endif -- cgit v1.2.3 From a80a232e965acc08d5323b2acc55da855adcf4f3 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Sat, 26 Aug 2017 22:33:13 +0200 Subject: efi_disk: efi_disk_rw_blocks is not an API function There is no need to use attribute EFIAPI for efi_disk_rw_blocks. It is not an API function. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- lib/efi_loader/efi_disk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c index ed06485e33..4e78ade2ed 100644 --- a/lib/efi_loader/efi_disk.c +++ b/lib/efi_loader/efi_disk.c @@ -47,7 +47,7 @@ enum efi_disk_direction { EFI_DISK_WRITE, }; -static efi_status_t EFIAPI efi_disk_rw_blocks(struct efi_block_io *this, +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) { -- cgit v1.2.3 From da94073b42cad349879ae5741e0b0fb3ac59a067 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 25 Aug 2017 19:53:14 +0200 Subject: efi_loader: call __efi_exit_check in efi_exit The calls to __efi_entry_check and __efi_exit_check have to match. If DEBUG is defined, panic() will be called otherwise. If debugging is activated some Travis CI builds fail due to an assertion in EFI_CALL without the patch. Signed-off-by: Heinrich Schuchardt Reviewed-by: Rob Clark Signed-off-by: Alexander Graf --- lib/efi_loader/efi_boottime.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 43f32385fa..ea953dca82 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -793,6 +793,8 @@ static efi_status_t EFIAPI efi_exit(efi_handle_t image_handle, EFI_ENTRY("%p, %ld, %ld, %p", image_handle, exit_status, exit_data_size, exit_data); + __efi_exit_check(); + loaded_image_info->exit_status = exit_status; longjmp(&loaded_image_info->exit_jmp, 1); -- cgit v1.2.3 From ae0bd3a983023aeb48b4ab3f417e5a62f8f72c37 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 18 Aug 2017 17:45:16 +0200 Subject: efi_loader: write protocol GUID in OpenProtocol To understand what is happening in OpenProtocol or LocateProtocol it is necessary to know the protocol interface GUID. Let's write a debug message. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- include/efi_loader.h | 8 ++++++++ lib/efi_loader/efi_boottime.c | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/include/efi_loader.h b/include/efi_loader.h index 1179234f68..46d684f6df 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -17,6 +17,7 @@ int __efi_entry_check(void); int __efi_exit_check(void); +const char *__efi_nesting(void); const char *__efi_nesting_inc(void); const char *__efi_nesting_dec(void); @@ -51,6 +52,13 @@ const char *__efi_nesting_dec(void); debug("%sEFI: Return From: %s\n", __efi_nesting_dec(), #exp); \ } while(0) +/* + * Write GUID + */ +#define EFI_PRINT_GUID(txt, guid) ({ \ + debug("%sEFI: %s %pUl\n", __efi_nesting(), txt, guid); \ + }) + extern struct efi_runtime_services efi_runtime_services; extern struct efi_system_table systab; diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index ea953dca82..99c5a6ee0c 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -109,6 +109,11 @@ static const char *indent_string(int level) return &indent[max - level]; } +const char *__efi_nesting(void) +{ + return indent_string(nesting_level); +} + const char *__efi_nesting_inc(void) { return indent_string(nesting_level++); @@ -1021,6 +1026,8 @@ static efi_status_t EFIAPI efi_locate_protocol(efi_guid_t *protocol, if (!protocol || !protocol_interface) return EFI_EXIT(EFI_INVALID_PARAMETER); + EFI_PRINT_GUID("protocol", protocol); + list_for_each(lhandle, &efi_obj_list) { struct efi_object *efiobj; @@ -1134,6 +1141,8 @@ static efi_status_t EFIAPI efi_open_protocol( goto out; } + EFI_PRINT_GUID("protocol", protocol); + switch (attributes) { case EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL: case EFI_OPEN_PROTOCOL_GET_PROTOCOL: -- cgit v1.2.3 From a148920e12cb9aa09589634966737c0fe88de2cc Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Sun, 3 Sep 2017 14:14:17 +0200 Subject: efi_loader: Fix efi_exit gd clobbering Commit f494950b (efi_loader: call __efi_exit_check in efi_exit) added a call to __efi_exit_check inside efi_exit to account for the fact that we're exiting the efi_exit function via a longjmp call. However, __efi_exit_check also swizzles gd to the application gd while the longjmp will put us back into EFI context, so we need the efi (u-boot) gd. This patch fixes that up by explicitly setting gd back to efi_gd before doing the longjmp. It also adds a few comments on why it does that. Signed-off-by: Alexander Graf --- lib/efi_loader/efi_boottime.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 99c5a6ee0c..90e9ead7b2 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -798,8 +798,15 @@ static efi_status_t EFIAPI efi_exit(efi_handle_t image_handle, EFI_ENTRY("%p, %ld, %ld, %p", image_handle, exit_status, exit_data_size, exit_data); + /* Make sure entry/exit counts for EFI world cross-overs match */ __efi_exit_check(); + /* + * But longjmp out with the U-Boot gd, not the application's, as + * the other end is a setjmp call inside EFI context. + */ + efi_restore_gd(); + loaded_image_info->exit_status = exit_status; longjmp(&loaded_image_info->exit_jmp, 1); -- cgit v1.2.3 From d6507e6fd9e0a6f1a8dd28c18cd320c1f861269e Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Sun, 3 Sep 2017 08:17:46 +0200 Subject: scripts/Makefile.lib: remove overridden target $(obj)/helloworld.so: The target $(obj)/helloworld.so: exists twice in Makefile.lib. If you add an echo command to each of the two recipes you get warnings like: scripts/Makefile.lib:383: warning: overriding recipe for target 'drivers/power/battery/helloworld.so' scripts/Makefile.lib:379: warning: ignoring old recipe for target 'drivers/power/battery/helloworld.so' This patch removes the obsolete target. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- scripts/Makefile.lib | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib index 2a7ed70cf2..7bf6ef12a0 100644 --- a/scripts/Makefile.lib +++ b/scripts/Makefile.lib @@ -392,8 +392,6 @@ cmd_efi_ld = $(LD) -nostdlib -znocombreloc -T $(EFI_LDS_PATH) -shared \ EFI_LDS_PATH = $(srctree)/arch/$(ARCH)/lib/$(EFI_LDS) -$(obj)/helloworld.so: $(EFI_LDS_PATH) - $(obj)/helloworld.so: $(obj)/helloworld.o arch/$(ARCH)/lib/$(EFI_CRT0) \ arch/$(ARCH)/lib/$(EFI_RELOC) $(call cmd,efi_ld) -- cgit v1.2.3 From 5e44489bc19dad344e0019f4a6926aa5f29b4456 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Tue, 5 Sep 2017 03:19:37 +0200 Subject: efi_loader: rename __efi_hello_world_* In scripts/Makefile.lib we build section including helloworld.efi. This allows to load the EFI binary with command 'bootefi hello'. scripts/Makefile.lib contains explicit references to strings containing helloworld and hello_world. This makes it impossible to generalize the coding to accomodate additional built in EFI binaries. Let us rename the variables __efi_hello_world_* to __efi_helloworld_*. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- cmd/bootefi.c | 4 ++-- include/asm-generic/sections.h | 4 ++-- scripts/Makefile.lib | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/bootefi.c b/cmd/bootefi.c index c65c619d63..ffd50ba159 100644 --- a/cmd/bootefi.c +++ b/cmd/bootefi.c @@ -297,14 +297,14 @@ static int do_bootefi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) return CMD_RET_USAGE; #ifdef CONFIG_CMD_BOOTEFI_HELLO if (!strcmp(argv[1], "hello")) { - ulong size = __efi_hello_world_end - __efi_hello_world_begin; + ulong size = __efi_helloworld_end - __efi_helloworld_begin; saddr = env_get("loadaddr"); if (saddr) addr = simple_strtoul(saddr, NULL, 16); else addr = CONFIG_SYS_LOAD_ADDR; - memcpy((char *)addr, __efi_hello_world_begin, size); + memcpy((char *)addr, __efi_helloworld_begin, size); } else #endif { diff --git a/include/asm-generic/sections.h b/include/asm-generic/sections.h index daf021b647..b6535705a5 100644 --- a/include/asm-generic/sections.h +++ b/include/asm-generic/sections.h @@ -22,8 +22,8 @@ extern char __kprobes_text_start[], __kprobes_text_end[]; extern char __entry_text_start[], __entry_text_end[]; extern char __initdata_begin[], __initdata_end[]; extern char __start_rodata[], __end_rodata[]; -extern char __efi_hello_world_begin[]; -extern char __efi_hello_world_end[]; +extern char __efi_helloworld_begin[]; +extern char __efi_helloworld_end[]; /* Start and end of .ctors section - used for constructor calls. */ extern char __ctors_start[], __ctors_end[]; diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib index 7bf6ef12a0..320e6c0ef0 100644 --- a/scripts/Makefile.lib +++ b/scripts/Makefile.lib @@ -367,11 +367,11 @@ cmd_S_efi= \ ( \ echo '.section .rodata.efi.init,"a"'; \ echo '.balign 16'; \ - echo '.global __efi_hello_world_begin'; \ - echo '__efi_hello_world_begin:'; \ + echo '.global __efi_helloworld_begin'; \ + echo '__efi_helloworld_begin:'; \ echo '.incbin "$<" '; \ - echo '__efi_hello_world_end:'; \ - echo '.global __efi_hello_world_end'; \ + echo '__efi_helloworld_end:'; \ + echo '.global __efi_helloworld_end'; \ echo '.balign 16'; \ ) > $@ -- cgit v1.2.3 From b0763108eacfc27c9f60d2ebfdf9d4328cf0bc38 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Tue, 5 Sep 2017 16:07:17 +0200 Subject: scripts/Makefile.lib: generalize building built in EFI app Replace all occurences of helloworld by generalized forms. This allows us to build additional EFI applications that are included into the U-Boot binary without loading scripts/Makefile.lib with specific filenames. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- scripts/Makefile.lib | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib index 320e6c0ef0..861a3dc3f1 100644 --- a/scripts/Makefile.lib +++ b/scripts/Makefile.lib @@ -359,20 +359,22 @@ cmd_S_ttf= \ $(obj)/%.S: $(src)/%.ttf $(call cmd,S_ttf) -# EFI Hello World application +# EFI applications +# A Makefile target *.efi is built as EFI application. +# A Makefile target *_efi.S wraps *.efi as built-in EFI application. # --------------------------------------------------------------------------- # Generate an assembly file to wrap the EFI app -cmd_S_efi= \ -( \ - echo '.section .rodata.efi.init,"a"'; \ - echo '.balign 16'; \ - echo '.global __efi_helloworld_begin'; \ - echo '__efi_helloworld_begin:'; \ - echo '.incbin "$<" '; \ - echo '__efi_helloworld_end:'; \ - echo '.global __efi_helloworld_end'; \ - echo '.balign 16'; \ +cmd_S_efi= \ +( \ + echo '.section .rodata.$*.init,"a"'; \ + echo '.balign 16'; \ + echo '.global __efi_$*_begin'; \ + echo '__efi_$*_begin:'; \ + echo '.incbin "$<" '; \ + echo '__efi_$*_end:'; \ + echo '.global __efi_$*_end'; \ + echo '.balign 16'; \ ) > $@ $(obj)/%_efi.S: $(obj)/%.efi @@ -383,7 +385,7 @@ cmd_efi_objcopy = $(OBJCOPY) -j .header -j .text -j .sdata -j .data -j \ .dynamic -j .dynsym -j .rel* -j .rela* -j .reloc \ $(if $(EFI_TARGET),$(EFI_TARGET),-O binary) $^ $@ -$(obj)/%.efi: $(obj)/%.so +$(obj)/%.efi: $(obj)/%_efi.so $(call cmd,efi_objcopy) quiet_cmd_efi_ld = LD $@ @@ -392,7 +394,7 @@ cmd_efi_ld = $(LD) -nostdlib -znocombreloc -T $(EFI_LDS_PATH) -shared \ EFI_LDS_PATH = $(srctree)/arch/$(ARCH)/lib/$(EFI_LDS) -$(obj)/helloworld.so: $(obj)/helloworld.o arch/$(ARCH)/lib/$(EFI_CRT0) \ +$(obj)/%_efi.so: $(obj)/%.o arch/$(ARCH)/lib/$(EFI_CRT0) \ arch/$(ARCH)/lib/$(EFI_RELOC) $(call cmd,efi_ld) -- cgit v1.2.3 From c96967e6d7cd2fefbbc8cfcf9de6b5ad2eccb4e2 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Tue, 5 Sep 2017 03:19:39 +0200 Subject: efi_loader: usage of always in Makefile Variable always should only be appended but not overwritten by lib/efi_loader/Makefile. Remove variable efiprogs which is not otherwise used. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- lib/efi_loader/Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile index 30bf343a36..0cfdd3bfc9 100644 --- a/lib/efi_loader/Makefile +++ b/lib/efi_loader/Makefile @@ -10,8 +10,9 @@ CFLAGS_helloworld.o := $(CFLAGS_EFI) CFLAGS_REMOVE_helloworld.o := $(CFLAGS_NON_EFI) -efiprogs-$(CONFIG_CMD_BOOTEFI_HELLO_COMPILE) += helloworld.efi -always := $(efiprogs-y) +ifneq ($(CONFIG_CMD_BOOTEFI_HELLO_COMPILE),) +always += helloworld.efi +endif obj-$(CONFIG_CMD_BOOTEFI_HELLO) += helloworld_efi.o obj-y += efi_image_loader.o efi_boottime.o efi_runtime.o efi_console.o -- cgit v1.2.3 From eefa0a647d34aaa09ceeb1781d5d81a4994d8b6a Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Thu, 6 Jul 2017 10:14:03 +0100 Subject: EFI: find EFI system partition by legacy MBR partition type The UEFI spec allows an EFI system partition (ESP, with the bootloader or kernel EFI apps on it) to reside on a disk using a "legacy" MBR partitioning scheme. But in contrast to actual legacy disks the ESP is not marked as "bootable" using bit 7 in byte 0 of the legacy partition entry, but is instead using partition *type* 0xef (in contrast to 0x0b or 0x0c for a normal FAT partition). The EFI spec isn't 100% clear on this, but it even seems to discourage the use of the bootable flag for ESPs. Also it seems that some EFI implementations (EDK2?) even seem to ignore partitions marked as bootable (probably since they believe they contain legacy boot code). The Debian installer [1] (*not* mini.iso), for instance, contains such an MBR, where none of the two partitions are marked bootable, but the ESP has clearly type 0xef. Now U-Boot cannot find the ESP on such a disk (USB flash drive) and fails to load the EFI grub and thus the installer. Since it all boils down to the distro bootcmds eventually calling "part list -bootable" to find potential boot partitions, it seems logical to just add this "partition type is 0xef" condition to the is_bootable() implementation. This allows the bog standard arm64 Debian-testing installer to boot from an USB pen drive on Allwinner A64 boards (Pine64, BananaPi-M64). (Ubuntu and other distribution installers don't have a legacy MBR, so U-Boot falls back to El Torito there). [1] https://cdimage.debian.org/cdimage/daily-builds/daily/arch-latest/arm64/iso-cd/ Signed-off-by: Andre Przywara Reviewed-by: Alexander Graf Signed-off-by: Alexander Graf --- disk/part_dos.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disk/part_dos.c b/disk/part_dos.c index 7ede15ec26..7aff73d6f8 100644 --- a/disk/part_dos.c +++ b/disk/part_dos.c @@ -44,7 +44,7 @@ static inline int is_extended(int part_type) static inline int is_bootable(dos_partition_t *p) { - return p->boot_ind == 0x80; + return (p->sys_ind == 0xef) || (p->boot_ind == 0x80); } static void print_one_part(dos_partition_t *p, lbaint_t ext_part_sector, -- cgit v1.2.3 From 591cf2e1614572552aa952717eb40927aaa471c3 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Mon, 18 Sep 2017 22:11:34 +0200 Subject: efi_loader: avoid obscure constants in efi_runtime.c We should use the predefined constants EFI_PAGE_SHIFT and EFI_PAGE_MASK where applicable. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- lib/efi_loader/efi_runtime.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/efi_loader/efi_runtime.c b/lib/efi_loader/efi_runtime.c index ad7f3754bd..47b03763c8 100644 --- a/lib/efi_loader/efi_runtime.c +++ b/lib/efi_loader/efi_runtime.c @@ -243,7 +243,8 @@ void efi_runtime_relocate(ulong offset, struct efi_mem_desc *map) /* Check if the relocation is inside bounds */ if (map && ((newaddr < map->virtual_start) || - newaddr > (map->virtual_start + (map->num_pages << 12)))) { + newaddr > (map->virtual_start + + (map->num_pages << EFI_PAGE_SHIFT)))) { if (!efi_runtime_tobedetached(p)) printf("U-Boot EFI: Relocation at %p is out of " "range (%lx)\n", p, newaddr); @@ -269,7 +270,8 @@ static efi_status_t EFIAPI efi_set_virtual_address_map( uint32_t descriptor_version, struct efi_mem_desc *virtmap) { - ulong runtime_start = (ulong)&__efi_runtime_start & ~0xfffULL; + ulong runtime_start = (ulong)&__efi_runtime_start & + ~(ulong)EFI_PAGE_MASK; int n = memory_map_size / descriptor_size; int i; -- cgit v1.2.3 From 29f1a3670e25d4778c1a066ae2a90186e6ce8e95 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Mon, 18 Sep 2017 07:54:50 +0200 Subject: efi_loader: fix typo in include/efi.h Fix typo in teh EFI_BOOT_SERVICES_CODE description. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- include/efi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/efi.h b/include/efi.h index 02b78b31b1..1bc22ccdf8 100644 --- a/include/efi.h +++ b/include/efi.h @@ -116,7 +116,7 @@ enum efi_mem_type { /* The code portions of a loaded Boot Services Driver */ EFI_BOOT_SERVICES_CODE, /* - * The data portions of a loaded Boot Serves Driver and + * The data portions of a loaded Boot Services Driver and * the default data allocation type used by a Boot Services * Driver to allocate pool memory. */ -- cgit v1.2.3 From ea630ce9eae4858a2108bd019a6ef22e2bc1b3f6 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 15 Sep 2017 10:06:10 +0200 Subject: efi_loader: allow return value in EFI_CALL Macro EFI_CALL was introduced to call an UEFI function. Unfortunately it does not support return values. Most UEFI functions have a return value. So let's rename EFI_CALL to EFI_CALL_VOID and introduce a new EFI_CALL macro that supports return values. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- include/efi_loader.h | 17 +++++++++++++++-- lib/efi_loader/efi_boottime.c | 3 ++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/include/efi_loader.h b/include/efi_loader.h index 46d684f6df..f27192555e 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -42,9 +42,22 @@ const char *__efi_nesting_dec(void); }) /* - * Callback into UEFI world from u-boot: + * Call non-void UEFI function from u-boot and retrieve return value: */ -#define EFI_CALL(exp) do { \ +#define EFI_CALL(exp) ({ \ + debug("%sEFI: Call: %s\n", __efi_nesting_inc(), #exp); \ + assert(__efi_exit_check()); \ + typeof(exp) _r = exp; \ + assert(__efi_entry_check()); \ + debug("%sEFI: %lu returned by %s\n", __efi_nesting_dec(), \ + (unsigned long)((uintptr_t)_r & ~EFI_ERROR_MASK), #exp); \ + _r; \ +}) + +/* + * Call void UEFI function from u-boot: + */ +#define EFI_CALL_VOID(exp) do { \ debug("%sEFI: Call: %s\n", __efi_nesting_inc(), #exp); \ assert(__efi_exit_check()); \ exp; \ diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 90e9ead7b2..2c9379a8ae 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -163,7 +163,8 @@ void efi_signal_event(struct efi_event *event) return; event->signaled = 1; if (event->type & EVT_NOTIFY_SIGNAL) { - EFI_CALL(event->notify_function(event, event->notify_context)); + EFI_CALL_VOID(event->notify_function(event, + event->notify_context)); } } -- cgit v1.2.3 From 623b3a579765f8e05723bd1eff6f8c7e56d33922 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 15 Sep 2017 10:06:11 +0200 Subject: efi_selftest: provide an EFI selftest application A testing framework for the EFI API is provided. It can be executed with the 'bootefi selftest' command. It is coded in a way that at a later stage we may turn it into a standalone EFI application. The current build system does not allow this yet. All tests use a driver model and are run in three phases: setup, execute, teardown. A test may be setup and executed at boottime, it may be setup at boottime and executed at runtime, or it may be setup and executed at runtime. After executing all tests the system is reset. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- MAINTAINERS | 5 +- cmd/Kconfig | 2 + cmd/bootefi.c | 25 +++- include/efi_loader.h | 9 ++ include/efi_selftest.h | 91 +++++++++++++ lib/Makefile | 1 + lib/efi_selftest/Kconfig | 7 + lib/efi_selftest/Makefile | 17 +++ lib/efi_selftest/efi_selftest.c | 219 ++++++++++++++++++++++++++++++++ lib/efi_selftest/efi_selftest_console.c | 187 +++++++++++++++++++++++++++ 10 files changed, 558 insertions(+), 5 deletions(-) create mode 100644 include/efi_selftest.h create mode 100644 lib/efi_selftest/Kconfig create mode 100644 lib/efi_selftest/Makefile create mode 100644 lib/efi_selftest/efi_selftest.c create mode 100644 lib/efi_selftest/efi_selftest_console.c diff --git a/MAINTAINERS b/MAINTAINERS index 04acf2b89d..0b7b2bbeb2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -259,8 +259,9 @@ EFI PAYLOAD M: Alexander Graf S: Maintained T: git git://github.com/agraf/u-boot.git -F: include/efi_loader.h -F: lib/efi_loader/ +F: include/efi* +F: lib/efi* +F: test/py/tests/test_efi* F: cmd/bootefi.c FLATTENED DEVICE TREE diff --git a/cmd/Kconfig b/cmd/Kconfig index d6d130edfa..3ef9b16b08 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -222,6 +222,8 @@ config CMD_BOOTEFI_HELLO for testing that EFI is working at a basic level, and for bringing up EFI support on a new architecture. +source lib/efi_selftest/Kconfig + config CMD_BOOTMENU bool "bootmenu" select MENU diff --git a/cmd/bootefi.c b/cmd/bootefi.c index ffd50ba159..788f869479 100644 --- a/cmd/bootefi.c +++ b/cmd/bootefi.c @@ -285,7 +285,6 @@ static unsigned long do_bootefi_exec(void *efi, void *fdt) return efi_do_enter(&loaded_image_info, &systab, entry); } - /* Interpreter command to boot an arbitrary EFI image from memory */ static int do_bootefi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { @@ -306,6 +305,22 @@ static int do_bootefi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) addr = CONFIG_SYS_LOAD_ADDR; memcpy((char *)addr, __efi_helloworld_begin, size); } else +#endif +#ifdef CONFIG_CMD_BOOTEFI_SELFTEST + if (!strcmp(argv[1], "selftest")) { + /* + * gd lives in a fixed register which may get clobbered while we + * execute the payload. So save it here and restore it on every + * callback entry + */ + efi_save_gd(); + /* Initialize and populate EFI object list */ + if (!efi_obj_list_initalized) + efi_init_obj_list(); + loaded_image_info.device_handle = bootefi_device_path; + loaded_image_info.file_path = bootefi_image_path; + return efi_selftest(&loaded_image_info, &systab); + } else #endif { saddr = argv[1]; @@ -336,8 +351,12 @@ static char bootefi_help_text[] = " If specified, the device tree located at gets\n" " exposed as EFI configuration table.\n" #ifdef CONFIG_CMD_BOOTEFI_HELLO - "hello\n" - " - boot a sample Hello World application stored within U-Boot" + "bootefi hello\n" + " - boot a sample Hello World application stored within U-Boot\n" +#endif +#ifdef CONFIG_CMD_BOOTEFI_SELFTEST + "bootefi selftest\n" + " - boot an EFI selftest application stored within U-Boot\n" #endif ; #endif diff --git a/include/efi_loader.h b/include/efi_loader.h index f27192555e..f74b33d589 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -254,6 +254,15 @@ efi_status_t __efi_runtime EFIAPI efi_get_time( struct efi_time_cap *capabilities); void efi_get_time_init(void); +#ifdef CONFIG_CMD_BOOTEFI_SELFTEST +/* + * Entry point for the tests of the EFI API. + * It is called by 'bootefi selftest' + */ +efi_status_t EFIAPI efi_selftest(efi_handle_t image_handle, + struct efi_system_table *systab); +#endif + #else /* defined(EFI_LOADER) && !defined(CONFIG_SPL_BUILD) */ /* Without CONFIG_EFI_LOADER we don't have a runtime section, stub it out */ diff --git a/include/efi_selftest.h b/include/efi_selftest.h new file mode 100644 index 0000000000..76304a2b2a --- /dev/null +++ b/include/efi_selftest.h @@ -0,0 +1,91 @@ +/* + * EFI application loader + * + * Copyright (c) 2017 Heinrich Schuchardt + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _EFI_SELFTEST_H +#define _EFI_SELFTEST_H + +#include +#include +#include +#include + +/* + * Prints an error message. + * + * @... format string followed by fields to print + */ +#define efi_st_error(...) \ + efi_st_printf("%s(%u):\nERROR: ", __FILE__, __LINE__); \ + efi_st_printf(__VA_ARGS__) \ + +/* + * A test may be setup and executed at boottime, + * it may be setup at boottime and executed at runtime, + * or it may be setup and executed at runtime. + */ +enum efi_test_phase { + EFI_EXECUTE_BEFORE_BOOTTIME_EXIT = 1, + EFI_SETUP_BEFORE_BOOTTIME_EXIT, + EFI_SETUP_AFTER_BOOTTIME_EXIT, +}; + +extern struct efi_simple_text_output_protocol *con_out; +extern struct efi_simple_input_interface *con_in; + +/* + * Exit the boot services. + * + * The size of the memory map is determined. + * Pool memory is allocated to copy the memory map. + * The memory amp is copied and the map key is obtained. + * The map key is used to exit the boot services. + */ +void efi_st_exit_boot_services(void); + +/* + * Print a pointer to an u16 string + * + * @pointer: pointer + * @buf: pointer to buffer address + * on return position of terminating zero word + */ +void efi_st_printf(const char *fmt, ...) + __attribute__ ((format (__printf__, 1, 2))); + +/* + * Reads an Unicode character from the input device. + * + * @return: Unicode character + */ +u16 efi_st_get_key(void); + +/** + * struct efi_unit_test - EFI unit test + * + * An efi_unit_test provides a interface to an EFI unit test. + * + * @name: name of unit test + * @phase: specifies when setup and execute are executed + * @setup: set up the unit test + * @teardown: tear down the unit test + * @execute: execute the unit test + */ +struct efi_unit_test { + const char *name; + const enum efi_test_phase phase; + int (*setup)(const efi_handle_t handle, + const struct efi_system_table *systable); + int (*execute)(void); + int (*teardown)(void); +}; + +/* Declare a new EFI unit test */ +#define EFI_UNIT_TEST(__name) \ + ll_entry_declare(struct efi_unit_test, __name, efi_unit_test) + +#endif /* _EFI_SELFTEST_H */ diff --git a/lib/Makefile b/lib/Makefile index faf4538fb5..8e1c9d1bb7 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -9,6 +9,7 @@ ifndef CONFIG_SPL_BUILD obj-$(CONFIG_EFI) += efi/ obj-$(CONFIG_EFI_LOADER) += efi_loader/ +obj-$(CONFIG_EFI_LOADER) += efi_selftest/ obj-$(CONFIG_LZMA) += lzma/ obj-$(CONFIG_LZO) += lzo/ obj-$(CONFIG_BZIP2) += bzip2/ diff --git a/lib/efi_selftest/Kconfig b/lib/efi_selftest/Kconfig new file mode 100644 index 0000000000..3b5f3a1230 --- /dev/null +++ b/lib/efi_selftest/Kconfig @@ -0,0 +1,7 @@ +config CMD_BOOTEFI_SELFTEST + bool "Allow booting an EFI efi_selftest" + depends on CMD_BOOTEFI + help + This adds an EFI test application to U-Boot that can be executed + with the 'bootefi selftest' command. It provides extended tests of + the EFI API implementation. diff --git a/lib/efi_selftest/Makefile b/lib/efi_selftest/Makefile new file mode 100644 index 0000000000..34f5ff1838 --- /dev/null +++ b/lib/efi_selftest/Makefile @@ -0,0 +1,17 @@ +: +# (C) Copyright 2017, Heinrich Schuchardt +# +# SPDX-License-Identifier: GPL-2.0+ +# + +# This file only gets included with CONFIG_EFI_LOADER set, so all +# object inclusion implicitly depends on it + +CFLAGS_efi_selftest.o := $(CFLAGS_EFI) +CFLAGS_REMOVE_efi_selftest.o := $(CFLAGS_NON_EFI) +CFLAGS_efi_selftest_console.o := $(CFLAGS_EFI) +CFLAGS_REMOVE_efi_selftest_console.o := $(CFLAGS_NON_EFI) + +obj-$(CONFIG_CMD_BOOTEFI_SELFTEST) += \ +efi_selftest.o \ +efi_selftest_console.o diff --git a/lib/efi_selftest/efi_selftest.c b/lib/efi_selftest/efi_selftest.c new file mode 100644 index 0000000000..efec832e98 --- /dev/null +++ b/lib/efi_selftest/efi_selftest.c @@ -0,0 +1,219 @@ +/* + * EFI efi_selftest + * + * Copyright (c) 2017 Heinrich Schuchardt + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include + +static const struct efi_system_table *systable; +static const struct efi_boot_services *boottime; +static const struct efi_runtime_services *runtime; +static efi_handle_t handle; +static u16 reset_message[] = L"Selftest completed"; + +/* + * Exit the boot services. + * + * The size of the memory map is determined. + * Pool memory is allocated to copy the memory map. + * The memory amp is copied and the map key is obtained. + * The map key is used to exit the boot services. + */ +void efi_st_exit_boot_services(void) +{ + unsigned long map_size = 0; + unsigned long map_key; + unsigned long desc_size; + u32 desc_version; + efi_status_t ret; + struct efi_mem_desc *memory_map; + + ret = boottime->get_memory_map(&map_size, NULL, &map_key, &desc_size, + &desc_version); + if (ret != EFI_BUFFER_TOO_SMALL) { + efi_st_printf("ERROR: GetMemoryMap did not return " + "EFI_BUFFER_TOO_SMALL\n"); + return; + } + /* Allocate extra space for newly allocated memory */ + map_size += sizeof(struct efi_mem_desc); + ret = boottime->allocate_pool(EFI_BOOT_SERVICES_DATA, map_size, + (void **)&memory_map); + if (ret != EFI_SUCCESS) { + efi_st_printf("ERROR: AllocatePool did not return " + "EFI_SUCCESS\n"); + return; + } + ret = boottime->get_memory_map(&map_size, memory_map, &map_key, + &desc_size, &desc_version); + if (ret != EFI_SUCCESS) { + efi_st_printf("ERROR: GetMemoryMap did not return " + "EFI_SUCCESS\n"); + return; + } + ret = boottime->exit_boot_services(handle, map_key); + if (ret != EFI_SUCCESS) { + efi_st_printf("ERROR: ExitBootServices did not return " + "EFI_SUCCESS\n"); + return; + } + efi_st_printf("\nBoot services terminated\n"); +} + +/* + * Set up a test. + * + * @test the test to be executed + * @failures counter that will be incremented if a failure occurs + */ +static int setup(struct efi_unit_test *test, unsigned int *failures) +{ + int ret; + + if (!test->setup) + return 0; + efi_st_printf("\nSetting up '%s'\n", test->name); + ret = test->setup(handle, systable); + if (ret) { + efi_st_printf("ERROR: Setting up '%s' failed\n", test->name); + ++*failures; + } else { + efi_st_printf("Setting up '%s' succeeded\n", test->name); + } + return ret; +} + +/* + * Execute a test. + * + * @test the test to be executed + * @failures counter that will be incremented if a failure occurs + */ +static int execute(struct efi_unit_test *test, unsigned int *failures) +{ + int ret; + + if (!test->execute) + return 0; + efi_st_printf("\nExecuting '%s'\n", test->name); + ret = test->execute(); + if (ret) { + efi_st_printf("ERROR: Executing '%s' failed\n", test->name); + ++*failures; + } else { + efi_st_printf("Executing '%s' succeeded\n", test->name); + } + return ret; +} + +/* + * Tear down a test. + * + * @test the test to be torn down + * @failures counter that will be incremented if a failure occurs + */ +static int teardown(struct efi_unit_test *test, unsigned int *failures) +{ + int ret; + + if (!test->teardown) + return 0; + efi_st_printf("\nTearing down '%s'\n", test->name); + ret = test->teardown(); + if (ret) { + efi_st_printf("ERROR: Tearing down '%s' failed\n", test->name); + ++*failures; + } else { + efi_st_printf("Tearing down '%s' succeeded\n", test->name); + } + return ret; +} + +/* + * Execute selftest of the EFI API + * + * This is the main entry point of the EFI selftest application. + * + * All tests use a driver model and are run in three phases: + * setup, execute, teardown. + * + * A test may be setup and executed at boottime, + * it may be setup at boottime and executed at runtime, + * or it may be setup and executed at runtime. + * + * After executing all tests the system is reset. + * + * @image_handle: handle of the loaded EFI image + * @systab: EFI system table + */ +efi_status_t EFIAPI efi_selftest(efi_handle_t image_handle, + struct efi_system_table *systab) +{ + struct efi_unit_test *test; + unsigned int failures = 0; + + systable = systab; + boottime = systable->boottime; + runtime = systable->runtime; + handle = image_handle; + con_out = systable->con_out; + con_in = systable->con_in; + + efi_st_printf("\nTesting EFI API implementation\n"); + + efi_st_printf("\nNumber of tests to execute: %u\n", + ll_entry_count(struct efi_unit_test, efi_unit_test)); + + /* Execute boottime tests */ + for (test = ll_entry_start(struct efi_unit_test, efi_unit_test); + test < ll_entry_end(struct efi_unit_test, efi_unit_test); ++test) { + if (test->phase == EFI_EXECUTE_BEFORE_BOOTTIME_EXIT) { + setup(test, &failures); + execute(test, &failures); + teardown(test, &failures); + } + } + + /* Execute mixed tests */ + for (test = ll_entry_start(struct efi_unit_test, efi_unit_test); + test < ll_entry_end(struct efi_unit_test, efi_unit_test); ++test) { + if (test->phase == EFI_SETUP_BEFORE_BOOTTIME_EXIT) + setup(test, &failures); + } + + efi_st_exit_boot_services(); + + for (test = ll_entry_start(struct efi_unit_test, efi_unit_test); + test < ll_entry_end(struct efi_unit_test, efi_unit_test); ++test) { + if (test->phase == EFI_SETUP_BEFORE_BOOTTIME_EXIT) { + execute(test, &failures); + teardown(test, &failures); + } + } + + /* Execute runtime tests */ + for (test = ll_entry_start(struct efi_unit_test, efi_unit_test); + test < ll_entry_end(struct efi_unit_test, efi_unit_test); ++test) { + if (test->phase == EFI_SETUP_AFTER_BOOTTIME_EXIT) { + setup(test, &failures); + execute(test, &failures); + teardown(test, &failures); + } + } + + /* Give feedback */ + efi_st_printf("\nSummary: %u failures\n\n", failures); + + /* Reset system */ + efi_st_printf("Preparing for reset. Press any key.\n"); + efi_st_get_key(); + runtime->reset_system(EFI_RESET_WARM, EFI_NOT_READY, + sizeof(reset_message), reset_message); + efi_st_printf("\nERROR: reset failed.\n"); + + return EFI_UNSUPPORTED; +} diff --git a/lib/efi_selftest/efi_selftest_console.c b/lib/efi_selftest/efi_selftest_console.c new file mode 100644 index 0000000000..7b5b724a61 --- /dev/null +++ b/lib/efi_selftest/efi_selftest_console.c @@ -0,0 +1,187 @@ +/* + * EFI efi_selftest + * + * Copyright (c) 2017 Heinrich Schuchardt + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include + +struct efi_simple_text_output_protocol *con_out; +struct efi_simple_input_interface *con_in; + +/* + * Print a pointer to an u16 string + * + * @pointer: pointer + * @buf: pointer to buffer address + * on return position of terminating zero word + */ +static void pointer(void *pointer, u16 **buf) +{ + int i; + u16 c; + uintptr_t p = (uintptr_t)pointer; + u16 *pos = *buf; + + for (i = 8 * sizeof(p) - 4; i >= 0; i -= 4) { + c = (p >> i) & 0x0f; + c += '0'; + if (c > '9') + c += 'a' - '9' - 1; + *pos++ = c; + } + *pos = 0; + *buf = pos; +} + +/* + * Print an unsigned 32bit value as decimal number to an u16 string + * + * @value: value to be printed + * @buf: pointer to buffer address + * on return position of terminating zero word + */ +static void uint2dec(u32 value, u16 **buf) +{ + u16 *pos = *buf; + int i; + u16 c; + u64 f; + + /* + * Increment by .5 and multiply with + * (2 << 60) / 1,000,000,000 = 0x44B82FA0.9B5A52CC + * to move the first digit to bit 60-63. + */ + f = 0x225C17D0; + f += (0x9B5A52DULL * value) >> 28; + f += 0x44B82FA0ULL * value; + + for (i = 0; i < 10; ++i) { + /* Write current digit */ + c = f >> 60; + if (c || pos != *buf) + *pos++ = c + '0'; + /* Eliminate current digit */ + f &= 0xfffffffffffffff; + /* Get next digit */ + f *= 0xaULL; + } + if (pos == *buf) + *pos++ = '0'; + *pos = 0; + *buf = pos; +} + +/* + * Print a signed 32bit value as decimal number to an u16 string + * + * @value: value to be printed + * @buf: pointer to buffer address + * on return position of terminating zero word + */ +static void int2dec(s32 value, u16 **buf) +{ + u32 u; + u16 *pos = *buf; + + if (value < 0) { + *pos++ = '-'; + u = -value; + } else { + u = value; + } + uint2dec(u, &pos); + *buf = pos; +} + +/* + * Print a formatted string to the EFI console + * + * @fmt: format string + * @...: optional arguments + */ +void efi_st_printf(const char *fmt, ...) +{ + va_list args; + u16 buf[160]; + const char *c; + u16 *pos = buf; + const char *s; + + va_start(args, fmt); + + c = fmt; + for (; *c; ++c) { + switch (*c) { + case '\\': + ++c; + switch (*c) { + case '\0': + --c; + break; + case 'n': + *pos++ = '\n'; + break; + case 'r': + *pos++ = '\r'; + break; + case 't': + *pos++ = '\t'; + break; + default: + *pos++ = *c; + } + break; + case '%': + ++c; + switch (*c) { + case '\0': + --c; + break; + case 'd': + int2dec(va_arg(args, s32), &pos); + break; + case 'p': + pointer(va_arg(args, void*), &pos); + break; + case 's': + s = va_arg(args, const char *); + for (; *s; ++s) + *pos++ = *s; + break; + case 'u': + uint2dec(va_arg(args, u32), &pos); + break; + default: + break; + } + break; + default: + *pos++ = *c; + } + } + va_end(args); + *pos = 0; + con_out->output_string(con_out, buf); +} + +/* + * Reads an Unicode character from the input device. + * + * @return: Unicode character + */ +u16 efi_st_get_key(void) +{ + struct efi_input_key input_key; + efi_status_t ret; + + /* Wait for next key */ + do { + ret = con_in->read_key_stroke(con_in, &input_key); + } while (ret == EFI_NOT_READY); + return input_key.unicode_char; +} -- cgit v1.2.3 From 38b1b79021418709bcc55504d1fec31d8a365f56 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 15 Sep 2017 10:06:12 +0200 Subject: test/py: add a test calling the EFI selftest A Python test script is provided that runs the EFI selftest if CONFIG_CMD_EFI_SELFTEST=y. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- test/py/tests/test_efi_selftest.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 test/py/tests/test_efi_selftest.py diff --git a/test/py/tests/test_efi_selftest.py b/test/py/tests/test_efi_selftest.py new file mode 100644 index 0000000000..76e282a6c7 --- /dev/null +++ b/test/py/tests/test_efi_selftest.py @@ -0,0 +1,25 @@ +# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. +# Copyright (c) 2017, Heinrich Schuchardt +# +# SPDX-License-Identifier: GPL-2.0 + +# Test efi API implementation + +import pytest +import u_boot_utils + +@pytest.mark.buildconfigspec('cmd_bootefi_selftest') +def test_efi_selftest(u_boot_console): + """ + Run bootefi selftest + """ + + u_boot_console.run_command(cmd='bootefi selftest', wait_for_prompt=False) + m = u_boot_console.p.expect(['Summary: 0 failures', 'Press any key']) + if m != 0: + raise Exception('Failures occured during the EFI selftest') + u_boot_console.run_command(cmd='', wait_for_echo=False, wait_for_prompt=False); + m = u_boot_console.p.expect(['resetting', 'U-Boot']) + if m != 0: + raise Exception('Reset failed during the EFI selftest') + u_boot_console.restart_uboot(); -- cgit v1.2.3 From ca62a4f53e82d4877e3b5fb7d7cd5c1ecc485e32 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 15 Sep 2017 10:06:13 +0200 Subject: efi_loader: implement queueing of the notification function For the correct implementation of the task priority level (TPL) calling the notification function must be queued. Add a status field 'queued' to events. In function efi_signal_event set status queued if a notification function exists and reset it after we have called the function. A later patch will add a check of the TPL here. In efi_create_event and efi_close_event unset the queued status. In function efi_wait_for_event and efi_check_event queue the notification function. In efi_timer_check call the efi_notify_event if the status queued is set. For all timer events set status signaled. In efi_console_timer_notify set the signaled state of the WaitForKey event. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- include/efi_loader.h | 4 +++- lib/efi_loader/efi_boottime.c | 40 ++++++++++++++++++++++++++++++---------- lib/efi_loader/efi_console.c | 4 +++- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/include/efi_loader.h b/include/efi_loader.h index f74b33d589..25398ba40c 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -131,7 +131,8 @@ struct efi_object { * @nofify_function: Function to call when the event is triggered * @notify_context: Data to be passed to the notify function * @trigger_type: Type of timer, see efi_set_timer - * @signaled: The notify function was already called + * @queued: The notification functionis queued + * @signaled: The event occured */ struct efi_event { uint32_t type; @@ -141,6 +142,7 @@ struct efi_event { u64 trigger_next; u64 trigger_time; enum efi_timer_delay trigger_type; + int queued; int signaled; }; diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 2c9379a8ae..408b4a9097 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -159,13 +159,13 @@ static u64 efi_div10(u64 a) void efi_signal_event(struct efi_event *event) { - if (event->signaled) - return; - event->signaled = 1; - if (event->type & EVT_NOTIFY_SIGNAL) { + if (event->notify_function) { + event->queued = 1; + /* Put missing TPL check here */ EFI_CALL_VOID(event->notify_function(event, event->notify_context)); } + event->queued = 0; } static efi_status_t efi_unsupported(const char *funcname) @@ -276,6 +276,7 @@ efi_status_t efi_create_event(uint32_t type, UINTN notify_tpl, efi_events[i].notify_context = notify_context; /* Disable timers on bootup */ efi_events[i].trigger_next = -1ULL; + efi_events[i].queued = 0; efi_events[i].signaled = 0; *event = &efi_events[i]; return EFI_SUCCESS; @@ -307,16 +308,25 @@ void efi_timer_check(void) u64 now = timer_get_us(); for (i = 0; i < ARRAY_SIZE(efi_events); ++i) { - if (!efi_events[i].type || - !(efi_events[i].type & EVT_TIMER) || - efi_events[i].trigger_type == EFI_TIMER_STOP || + if (!efi_events[i].type) + continue; + if (efi_events[i].queued) + efi_signal_event(&efi_events[i]); + if (!(efi_events[i].type & EVT_TIMER) || now < efi_events[i].trigger_next) continue; - if (efi_events[i].trigger_type == EFI_TIMER_PERIODIC) { + switch (efi_events[i].trigger_type) { + case EFI_TIMER_RELATIVE: + efi_events[i].trigger_type = EFI_TIMER_STOP; + break; + case EFI_TIMER_PERIODIC: efi_events[i].trigger_next += efi_events[i].trigger_time; - efi_events[i].signaled = 0; + break; + default: + continue; } + efi_events[i].signaled = 1; efi_signal_event(&efi_events[i]); } WATCHDOG_RESET(); @@ -377,6 +387,7 @@ static efi_status_t EFIAPI efi_wait_for_event(unsigned long num_events, /* Check parameters */ if (!num_events || !event) return EFI_EXIT(EFI_INVALID_PARAMETER); + /* Put missing TPL check here */ for (i = 0; i < num_events; ++i) { for (j = 0; j < ARRAY_SIZE(efi_events); ++j) { if (event[i] == &efi_events[j]) @@ -386,6 +397,8 @@ static efi_status_t EFIAPI efi_wait_for_event(unsigned long num_events, known_event: if (!event[i]->type || event[i]->type & EVT_NOTIFY_SIGNAL) return EFI_EXIT(EFI_INVALID_PARAMETER); + if (!event[i]->signaled) + efi_signal_event(event[i]); } /* Wait for signal */ @@ -418,7 +431,11 @@ static efi_status_t EFIAPI efi_signal_event_ext(struct efi_event *event) for (i = 0; i < ARRAY_SIZE(efi_events); ++i) { if (event != &efi_events[i]) continue; - efi_signal_event(event); + if (event->signaled) + break; + event->signaled = 1; + if (event->type & EVT_NOTIFY_SIGNAL) + efi_signal_event(event); break; } return EFI_EXIT(EFI_SUCCESS); @@ -433,6 +450,7 @@ static efi_status_t EFIAPI efi_close_event(struct efi_event *event) if (event == &efi_events[i]) { event->type = 0; event->trigger_next = -1ULL; + event->queued = 0; event->signaled = 0; return EFI_EXIT(EFI_SUCCESS); } @@ -451,6 +469,8 @@ static efi_status_t EFIAPI efi_check_event(struct efi_event *event) continue; if (!event->type || event->type & EVT_NOTIFY_SIGNAL) break; + if (!event->signaled) + efi_signal_event(event); if (event->signaled) return EFI_EXIT(EFI_SUCCESS); return EFI_EXIT(EFI_NOT_READY); diff --git a/lib/efi_loader/efi_console.c b/lib/efi_loader/efi_console.c index 3fc82b8726..65c07fdf37 100644 --- a/lib/efi_loader/efi_console.c +++ b/lib/efi_loader/efi_console.c @@ -426,8 +426,10 @@ static void EFIAPI efi_console_timer_notify(struct efi_event *event, void *context) { EFI_ENTRY("%p, %p", event, context); - if (tstc()) + if (tstc()) { + efi_con_in.wait_for_key->signaled = 1; efi_signal_event(efi_con_in.wait_for_key); + } EFI_EXIT(EFI_SUCCESS); } -- cgit v1.2.3 From 2eca84523f4ffdea0f4b29c187188dde205cfcbd Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 15 Sep 2017 10:06:14 +0200 Subject: efi_loader: efi_set_timer: reset signaled state We should be able to call efi_set_timer repeatedly. So let us reset the signaled state here. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- lib/efi_loader/efi_boottime.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 408b4a9097..73793cd986 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -363,6 +363,7 @@ efi_status_t efi_set_timer(struct efi_event *event, enum efi_timer_delay type, } event->trigger_type = type; event->trigger_time = trigger_time; + event->signaled = 0; return EFI_SUCCESS; } return EFI_INVALID_PARAMETER; -- cgit v1.2.3 From bd126692da45ace7c5bfecc782443d042545ea77 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 15 Sep 2017 10:06:15 +0200 Subject: efi_selftest: provide unit test for event services This unit test uses timer events to check the implementation of the following boottime services: CreateEvent, CloseEvent, WaitForEvent, CheckEvent, SetTimer Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- lib/efi_selftest/Makefile | 5 +- lib/efi_selftest/efi_selftest_events.c | 195 +++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 lib/efi_selftest/efi_selftest_events.c diff --git a/lib/efi_selftest/Makefile b/lib/efi_selftest/Makefile index 34f5ff1838..a71c8bf937 100644 --- a/lib/efi_selftest/Makefile +++ b/lib/efi_selftest/Makefile @@ -11,7 +11,10 @@ CFLAGS_efi_selftest.o := $(CFLAGS_EFI) CFLAGS_REMOVE_efi_selftest.o := $(CFLAGS_NON_EFI) CFLAGS_efi_selftest_console.o := $(CFLAGS_EFI) CFLAGS_REMOVE_efi_selftest_console.o := $(CFLAGS_NON_EFI) +CFLAGS_efi_selftest_events.o := $(CFLAGS_EFI) +CFLAGS_REMOVE_efi_selftest_events.o := $(CFLAGS_NON_EFI) obj-$(CONFIG_CMD_BOOTEFI_SELFTEST) += \ efi_selftest.o \ -efi_selftest_console.o +efi_selftest_console.o \ +efi_selftest_events.o diff --git a/lib/efi_selftest/efi_selftest_events.c b/lib/efi_selftest/efi_selftest_events.c new file mode 100644 index 0000000000..c4f66952b9 --- /dev/null +++ b/lib/efi_selftest/efi_selftest_events.c @@ -0,0 +1,195 @@ +/* + * efi_selftest_events + * + * Copyright (c) 2017 Heinrich Schuchardt + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This unit test uses timer events to check the implementation + * of the following boottime services: + * CreateEvent, CloseEvent, WaitForEvent, CheckEvent, SetTimer. + */ + +#include + +static struct efi_event *event_notify; +static struct efi_event *event_wait; +static unsigned int counter; +static struct efi_boot_services *boottime; + +/* + * Notification function, increments a counter. + * + * @event notified event + * @context pointer to the counter + */ +static void EFIAPI notify(struct efi_event *event, void *context) +{ + if (!context) + return; + ++*(unsigned int *)context; +} + +/* + * Setup unit test. + * + * Create two timer events. + * One with EVT_NOTIFY_SIGNAL, the other with EVT_NOTIFY_WAIT. + * + * @handle: handle of the loaded image + * @systable: system table + */ +static int setup(const efi_handle_t handle, + const struct efi_system_table *systable) +{ + efi_status_t ret; + + boottime = systable->boottime; + + ret = boottime->create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL, + TPL_CALLBACK, notify, (void *)&counter, + &event_notify); + if (ret != EFI_SUCCESS) { + efi_st_error("could not create event\n"); + return 1; + } + ret = boottime->create_event(EVT_TIMER | EVT_NOTIFY_WAIT, + TPL_CALLBACK, notify, NULL, &event_wait); + if (ret != EFI_SUCCESS) { + efi_st_error("could not create event\n"); + return 1; + } + return 0; +} + +/* + * Tear down unit test. + * + * Close the events created in setup. + */ +static int teardown(void) +{ + efi_status_t ret; + + if (event_notify) { + ret = boottime->close_event(event_notify); + event_notify = NULL; + if (ret != EFI_SUCCESS) { + efi_st_error("could not close event\n"); + return 1; + } + } + if (event_wait) { + ret = boottime->close_event(event_wait); + event_wait = NULL; + if (ret != EFI_SUCCESS) { + efi_st_error("could not close event\n"); + return 1; + } + } + return 0; +} + +/* + * Execute unit test. + * + * Run a 10 ms periodic timer and check that it is called 10 times + * while waiting for 100 ms single shot timer. + * + * Run a 100 ms single shot timer and check that it is called once + * while waiting for 100 ms periodic timer for two periods. + */ +static int execute(void) +{ + unsigned long index; + efi_status_t ret; + + /* Set 10 ms timer */ + counter = 0; + ret = boottime->set_timer(event_notify, EFI_TIMER_PERIODIC, 100000); + if (ret != EFI_SUCCESS) { + efi_st_error("Could not set timer\n"); + return 1; + } + /* Set 100 ms timer */ + ret = boottime->set_timer(event_wait, EFI_TIMER_RELATIVE, 1000000); + if (ret != EFI_SUCCESS) { + efi_st_error("Could not set timer\n"); + return 1; + } + + index = 5; + ret = boottime->wait_for_event(1, &event_wait, &index); + if (ret != EFI_SUCCESS) { + efi_st_error("Could not wait for event\n"); + return 1; + } + ret = boottime->check_event(event_wait); + if (ret != EFI_NOT_READY) { + efi_st_error("Signaled state was not cleared.\n"); + efi_st_printf("ret = %u\n", (unsigned int)ret); + return 1; + } + if (index != 0) { + efi_st_error("WaitForEvent returned wrong index\n"); + return 1; + } + efi_st_printf("Counter periodic: %u\n", counter); + if (counter < 8 || counter > 12) { + efi_st_error("Incorrect timing of events\n"); + return 1; + } + ret = boottime->set_timer(event_notify, EFI_TIMER_STOP, 0); + if (index != 0) { + efi_st_error("Could not cancel timer\n"); + return 1; + } + /* Set 10 ms timer */ + counter = 0; + ret = boottime->set_timer(event_notify, EFI_TIMER_RELATIVE, 100000); + if (index != 0) { + efi_st_error("Could not set timer\n"); + return 1; + } + /* Set 100 ms timer */ + ret = boottime->set_timer(event_wait, EFI_TIMER_PERIODIC, 1000000); + if (index != 0) { + efi_st_error("Could not set timer\n"); + return 1; + } + ret = boottime->wait_for_event(1, &event_wait, &index); + if (ret != EFI_SUCCESS) { + efi_st_error("Could not wait for event\n"); + return 1; + } + efi_st_printf("Counter single shot: %u\n", counter); + if (counter != 1) { + efi_st_error("Single shot timer failed\n"); + return 1; + } + ret = boottime->wait_for_event(1, &event_wait, &index); + if (ret != EFI_SUCCESS) { + efi_st_error("Could not wait for event\n"); + return 1; + } + efi_st_printf("Stopped counter: %u\n", counter); + if (counter != 1) { + efi_st_error("Stopped timer fired\n"); + return 1; + } + ret = boottime->set_timer(event_wait, EFI_TIMER_STOP, 0); + if (index != 0) { + efi_st_error("Could not cancel timer\n"); + return 1; + } + + return 0; +} + +EFI_UNIT_TEST(events) = { + .name = "event services", + .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT, + .setup = setup, + .execute = execute, + .teardown = teardown, +}; -- cgit v1.2.3 From 32f4b2f8c455b70cea65f5ba63346a6efc9d4e1e Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 15 Sep 2017 10:06:16 +0200 Subject: efi_loader: implement task priority level (TPL) Define variable holding tpl. Implement RaiseTPL and RestoreTPL. Implement TPL check in efi_signal_event. Implement TPL check in efi_wait_for_event. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- lib/efi_loader/efi_boottime.c | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 73793cd986..7c92a68bf4 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -18,6 +18,9 @@ DECLARE_GLOBAL_DATA_PTR; +/* Task priority level */ +static UINTN efi_tpl = TPL_APPLICATION; + /* This list contains all the EFI objects our payload has access to */ LIST_HEAD(efi_obj_list); @@ -161,7 +164,9 @@ void efi_signal_event(struct efi_event *event) { if (event->notify_function) { event->queued = 1; - /* Put missing TPL check here */ + /* Check TPL */ + if (efi_tpl >= event->notify_tpl) + return; EFI_CALL_VOID(event->notify_function(event, event->notify_context)); } @@ -176,14 +181,31 @@ static efi_status_t efi_unsupported(const char *funcname) static unsigned long EFIAPI efi_raise_tpl(UINTN new_tpl) { + UINTN old_tpl = efi_tpl; + EFI_ENTRY("0x%zx", new_tpl); - return EFI_EXIT(0); + + if (new_tpl < efi_tpl) + debug("WARNING: new_tpl < current_tpl in %s\n", __func__); + efi_tpl = new_tpl; + if (efi_tpl > TPL_HIGH_LEVEL) + efi_tpl = TPL_HIGH_LEVEL; + + EFI_EXIT(EFI_SUCCESS); + return old_tpl; } static void EFIAPI efi_restore_tpl(UINTN old_tpl) { EFI_ENTRY("0x%zx", old_tpl); - efi_unsupported(__func__); + + if (old_tpl > efi_tpl) + debug("WARNING: old_tpl > current_tpl in %s\n", __func__); + efi_tpl = old_tpl; + if (efi_tpl > TPL_HIGH_LEVEL) + efi_tpl = TPL_HIGH_LEVEL; + + EFI_EXIT(EFI_SUCCESS); } static efi_status_t EFIAPI efi_allocate_pages_ext(int type, int memory_type, @@ -388,7 +410,9 @@ static efi_status_t EFIAPI efi_wait_for_event(unsigned long num_events, /* Check parameters */ if (!num_events || !event) return EFI_EXIT(EFI_INVALID_PARAMETER); - /* Put missing TPL check here */ + /* Check TPL */ + if (efi_tpl != TPL_APPLICATION) + return EFI_EXIT(EFI_UNSUPPORTED); for (i = 0; i < num_events; ++i) { for (j = 0; j < ARRAY_SIZE(efi_events); ++j) { if (event[i] == &efi_events[j]) -- cgit v1.2.3 From 1835f6ea7133263dbb51977a420b84b41234b5f1 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 15 Sep 2017 10:06:17 +0200 Subject: efi_selftest: test task priority levels Run a 10 ms periodic timer and check that it is called 10 times while waiting for 100 ms single shot timer. Raise the TPL level to the level of the 10 ms timer and observe that the notification function is not called again. Lower the TPL level and check that the queued notification function is called. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- lib/efi_selftest/Makefile | 5 +- lib/efi_selftest/efi_selftest_tpl.c | 214 ++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 lib/efi_selftest/efi_selftest_tpl.c diff --git a/lib/efi_selftest/Makefile b/lib/efi_selftest/Makefile index a71c8bf937..ddf304e1fa 100644 --- a/lib/efi_selftest/Makefile +++ b/lib/efi_selftest/Makefile @@ -13,8 +13,11 @@ CFLAGS_efi_selftest_console.o := $(CFLAGS_EFI) CFLAGS_REMOVE_efi_selftest_console.o := $(CFLAGS_NON_EFI) CFLAGS_efi_selftest_events.o := $(CFLAGS_EFI) CFLAGS_REMOVE_efi_selftest_events.o := $(CFLAGS_NON_EFI) +CFLAGS_efi_selftest_tpl.o := $(CFLAGS_EFI) +CFLAGS_REMOVE_efi_selftest_tpl.o := $(CFLAGS_NON_EFI) obj-$(CONFIG_CMD_BOOTEFI_SELFTEST) += \ efi_selftest.o \ efi_selftest_console.o \ -efi_selftest_events.o +efi_selftest_events.o \ +efi_selftest_tpl.o diff --git a/lib/efi_selftest/efi_selftest_tpl.c b/lib/efi_selftest/efi_selftest_tpl.c new file mode 100644 index 0000000000..90ace0f51e --- /dev/null +++ b/lib/efi_selftest/efi_selftest_tpl.c @@ -0,0 +1,214 @@ +/* + * efi_selftest_events + * + * Copyright (c) 2017 Heinrich Schuchardt + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This unit test uses timer events to check the handling of + * task priority levels. + */ + +#include + +static struct efi_event *event_notify; +static struct efi_event *event_wait; +static unsigned int counter; +static struct efi_boot_services *boottime; + +/* + * Notification function, increments a counter. + * + * @event notified event + * @context pointer to the counter + */ +static void EFIAPI notify(struct efi_event *event, void *context) +{ + if (!context) + return; + ++*(unsigned int *)context; +} + +/* + * Setup unit test. + * + * Create two timer events. + * One with EVT_NOTIFY_SIGNAL, the other with EVT_NOTIFY_WAIT. + * + * @handle: handle of the loaded image + * @systable: system table + */ +static int setup(const efi_handle_t handle, + const struct efi_system_table *systable) +{ + efi_status_t ret; + + boottime = systable->boottime; + + ret = boottime->create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL, + TPL_CALLBACK, notify, (void *)&counter, + &event_notify); + if (ret != EFI_SUCCESS) { + efi_st_error("could not create event\n"); + return 1; + } + ret = boottime->create_event(EVT_TIMER | EVT_NOTIFY_WAIT, + TPL_HIGH_LEVEL, notify, NULL, &event_wait); + if (ret != EFI_SUCCESS) { + efi_st_error("could not create event\n"); + return 1; + } + return 0; +} + +/* + * Tear down unit test. + * + * Close the events created in setup. + */ +static int teardown(void) +{ + efi_status_t ret; + + if (event_notify) { + ret = boottime->close_event(event_notify); + event_notify = NULL; + if (ret != EFI_SUCCESS) { + efi_st_error("could not close event\n"); + return 1; + } + } + if (event_wait) { + ret = boottime->close_event(event_wait); + event_wait = NULL; + if (ret != EFI_SUCCESS) { + efi_st_error("could not close event\n"); + return 1; + } + } + boottime->restore_tpl(TPL_APPLICATION); + return 0; +} + +/* + * Execute unit test. + * + * Run a 10 ms periodic timer and check that it is called 10 times + * while waiting for 100 ms single shot timer. + * + * Raise the TPL level to the level of the 10 ms timer and observe + * that the notification function is not called again. + * + * Lower the TPL level and check that the queued notification + * function is called. + */ +static int execute(void) +{ + unsigned long index; + efi_status_t ret; + UINTN old_tpl; + + /* Set 10 ms timer */ + counter = 0; + ret = boottime->set_timer(event_notify, EFI_TIMER_PERIODIC, 100000); + if (ret != EFI_SUCCESS) { + efi_st_error("Could not set timer\n"); + return 1; + } + /* Set 100 ms timer */ + ret = boottime->set_timer(event_wait, EFI_TIMER_RELATIVE, 1000000); + if (ret != EFI_SUCCESS) { + efi_st_error("Could not set timer\n"); + return 1; + } + index = 5; + ret = boottime->wait_for_event(1, &event_wait, &index); + if (ret != EFI_SUCCESS) { + efi_st_error("Could not wait for event\n"); + return 1; + } + ret = boottime->check_event(event_wait); + if (ret != EFI_NOT_READY) { + efi_st_error("Signaled state was not cleared.\n"); + efi_st_printf("ret = %u\n", (unsigned int)ret); + return 1; + } + if (index != 0) { + efi_st_error("WaitForEvent returned wrong index\n"); + return 1; + } + efi_st_printf("Counter with TPL level TPL_APPLICATION: %u\n", counter); + if (counter < 8 || counter > 12) { + efi_st_error("Incorrect timing of events\n"); + return 1; + } + ret = boottime->set_timer(event_notify, EFI_TIMER_STOP, 0); + if (index != 0) { + efi_st_error("Could not cancel timer\n"); + return 1; + } + /* Raise TPL level */ + old_tpl = boottime->raise_tpl(TPL_CALLBACK); + if (old_tpl != TPL_APPLICATION) { + efi_st_error("Initial TPL level was not TPL_APPLICATION"); + return 1; + } + /* Set 10 ms timer */ + counter = 0; + ret = boottime->set_timer(event_notify, EFI_TIMER_PERIODIC, 100000); + if (index != 0) { + efi_st_error("Could not set timer\n"); + return 1; + } + /* Set 100 ms timer */ + ret = boottime->set_timer(event_wait, EFI_TIMER_RELATIVE, 1000000); + if (ret != EFI_SUCCESS) { + efi_st_error("Could not set timer\n"); + return 1; + } + do { + ret = boottime->check_event(event_wait); + } while (ret == EFI_NOT_READY); + if (ret != EFI_SUCCESS) { + efi_st_error("Could not check event\n"); + return 1; + } + efi_st_printf("Counter with TPL level TPL_CALLBACK: %u\n", counter); + if (counter != 0) { + efi_st_error("Suppressed timer fired\n"); + return 1; + } + /* Set 1 ms timer */ + ret = boottime->set_timer(event_wait, EFI_TIMER_RELATIVE, 1000); + if (ret != EFI_SUCCESS) { + efi_st_error("Could not set timer\n"); + return 1; + } + /* Restore the old TPL level */ + boottime->restore_tpl(TPL_APPLICATION); + ret = boottime->wait_for_event(1, &event_wait, &index); + if (ret != EFI_SUCCESS) { + efi_st_error("Could not wait for event\n"); + return 1; + } + efi_st_printf("Counter with TPL level TPL_APPLICATION: %u\n", counter); + if (counter < 1) { + efi_st_error("Queued timer event did not fire\n"); + return 1; + } + ret = boottime->set_timer(event_wait, EFI_TIMER_STOP, 0); + if (index != 0) { + efi_st_error("Could not cancel timer\n"); + return 1; + } + + return 0; +} + +EFI_UNIT_TEST(tpl) = { + .name = "task priority levels", + .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT, + .setup = setup, + .execute = execute, + .teardown = teardown, +}; -- cgit v1.2.3 From 152a263c86c344fb255edf039071b8746a7fe0ff Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 15 Sep 2017 10:06:18 +0200 Subject: efi_loader: notify when ExitBootServices is invoked All events of type EVT_SIGNAL_EXIT_BOOT_SERVICES have to be notified when ExitBootServices is invoked. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- lib/efi_loader/efi_boottime.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 7c92a68bf4..0d234146d7 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -900,8 +900,19 @@ static void efi_exit_caches(void) static efi_status_t EFIAPI efi_exit_boot_services(void *image_handle, unsigned long map_key) { + int i; + EFI_ENTRY("%p, %ld", image_handle, map_key); + /* Notify that ExitBootServices is invoked. */ + for (i = 0; i < ARRAY_SIZE(efi_events); ++i) { + if (efi_events[i].type != EVT_SIGNAL_EXIT_BOOT_SERVICES) + continue; + efi_signal_event(&efi_events[i]); + } + /* Make sure that notification functions are not called anymore */ + efi_tpl = TPL_HIGH_LEVEL; + board_quiesce_devices(); /* Fix up caches for EFI payloads if necessary */ -- cgit v1.2.3 From 0923876d297b046010df390277115aed2b3537e9 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 15 Sep 2017 10:06:19 +0200 Subject: efi_selftest: check notification of ExitBootServices Check that the notification function of an EVT_SIGNAL_EXIT_BOOT_SERVICES event is called exactly once. Signed-off-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- lib/efi_selftest/Makefile | 3 + lib/efi_selftest/efi_selftest_exitbootservices.c | 106 +++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 lib/efi_selftest/efi_selftest_exitbootservices.c diff --git a/lib/efi_selftest/Makefile b/lib/efi_selftest/Makefile index ddf304e1fa..30f1960933 100644 --- a/lib/efi_selftest/Makefile +++ b/lib/efi_selftest/Makefile @@ -13,6 +13,8 @@ CFLAGS_efi_selftest_console.o := $(CFLAGS_EFI) CFLAGS_REMOVE_efi_selftest_console.o := $(CFLAGS_NON_EFI) CFLAGS_efi_selftest_events.o := $(CFLAGS_EFI) CFLAGS_REMOVE_efi_selftest_events.o := $(CFLAGS_NON_EFI) +CFLAGS_efi_selftest_exitbootservices.o := $(CFLAGS_EFI) +CFLAGS_REMOVE_efi_selftest_exitbootservices.o := $(CFLAGS_NON_EFI) CFLAGS_efi_selftest_tpl.o := $(CFLAGS_EFI) CFLAGS_REMOVE_efi_selftest_tpl.o := $(CFLAGS_NON_EFI) @@ -20,4 +22,5 @@ obj-$(CONFIG_CMD_BOOTEFI_SELFTEST) += \ efi_selftest.o \ efi_selftest_console.o \ efi_selftest_events.o \ +efi_selftest_exitbootservices.o \ efi_selftest_tpl.o diff --git a/lib/efi_selftest/efi_selftest_exitbootservices.c b/lib/efi_selftest/efi_selftest_exitbootservices.c new file mode 100644 index 0000000000..60271e6180 --- /dev/null +++ b/lib/efi_selftest/efi_selftest_exitbootservices.c @@ -0,0 +1,106 @@ +/* + * efi_selftest_events + * + * Copyright (c) 2017 Heinrich Schuchardt + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This unit test checks that the notification function of an + * EVT_SIGNAL_EXIT_BOOT_SERVICES event is called exactly once. + */ + +#include + +static struct efi_boot_services *boottime; +static struct efi_event *event_notify; +static unsigned int counter; + +/* + * Notification function, increments a counter. + * + * @event notified event + * @context pointer to the counter + */ +static void EFIAPI notify(struct efi_event *event, void *context) +{ + if (!context) + return; + ++*(unsigned int *)context; +} + +/* + * Setup unit test. + * + * Create an EVT_SIGNAL_EXIT_BOOT_SERVICES event. + * + * @handle: handle of the loaded image + * @systable: system table + */ +static int setup(const efi_handle_t handle, + const struct efi_system_table *systable) +{ + efi_status_t ret; + + boottime = systable->boottime; + + counter = 0; + ret = boottime->create_event(EVT_SIGNAL_EXIT_BOOT_SERVICES, + TPL_CALLBACK, notify, (void *)&counter, + &event_notify); + if (ret != EFI_SUCCESS) { + efi_st_error("could not create event\n"); + return 1; + } + return 0; +} + +/* + * Tear down unit test. + * + * Close the event created in setup. + */ +static int teardown(void) +{ + efi_status_t ret; + + if (event_notify) { + ret = boottime->close_event(event_notify); + event_notify = NULL; + if (ret != EFI_SUCCESS) { + efi_st_error("could not close event\n"); + return 1; + } + } + return 0; +} + +/* + * Execute unit test. + * + * Check that the notification function of the EVT_SIGNAL_EXIT_BOOT_SERVICES + * event has been called. + * + * Call ExitBootServices again and check that the notification function is + * not called again. + */ +static int execute(void) +{ + if (counter != 1) { + efi_st_error("ExitBootServices was not notified"); + return 1; + } + efi_st_exit_boot_services(); + if (counter != 1) { + efi_st_error("ExitBootServices was notified twice"); + return 1; + } + return 0; +} + +EFI_UNIT_TEST(exitbootservices) = { + .name = "ExitBootServices", + .phase = EFI_SETUP_BEFORE_BOOTTIME_EXIT, + .setup = setup, + .execute = execute, + .teardown = teardown, +}; -- cgit v1.2.3 From 0d6ab32e3712fe7bc2e3aaf3d93e0d5910264df3 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:24 -0400 Subject: part: move efi_guid_t Prep work for next patch. Signed-off-by: Rob Clark Reviewed-by: Heinrich Schuchardt Signed-off-by: Alexander Graf --- include/efi.h | 4 ++++ include/part.h | 3 ++- include/part_efi.h | 4 ---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/efi.h b/include/efi.h index 1bc22ccdf8..63cbdb61ec 100644 --- a/include/efi.h +++ b/include/efi.h @@ -28,6 +28,10 @@ struct efi_device_path; +typedef struct { + u8 b[16]; +} efi_guid_t; + #define EFI_BITS_PER_LONG BITS_PER_LONG /* diff --git a/include/part.h b/include/part.h index 86117a7ce5..b2e820ef8a 100644 --- a/include/part.h +++ b/include/part.h @@ -280,8 +280,9 @@ struct part_driver { #define U_BOOT_PART_TYPE(__name) \ ll_entry_declare(struct part_driver, __name, part_driver) -#if CONFIG_IS_ENABLED(EFI_PARTITION) #include + +#if CONFIG_IS_ENABLED(EFI_PARTITION) /* disk/part_efi.c */ /** * write_gpt_table() - Write the GUID Partition Table to disk diff --git a/include/part_efi.h b/include/part_efi.h index 317c044795..31e6bc6e14 100644 --- a/include/part_efi.h +++ b/include/part_efi.h @@ -58,10 +58,6 @@ /* linux/include/efi.h */ typedef u16 efi_char16_t; -typedef struct { - u8 b[16]; -} efi_guid_t; - /* based on linux/include/genhd.h */ struct partition { u8 boot_ind; /* 0x80 - active */ -- cgit v1.2.3 From ff98cb90514d9b787ddc097c203ac8db2941efe1 Mon Sep 17 00:00:00 2001 From: Peter Jones Date: Wed, 13 Sep 2017 18:05:25 -0400 Subject: part: extract MBR signature from partitions EFI client programs need the signature information from the partition table to determine the disk a partition is on, so we need to fill that in here. Signed-off-by: Peter Jones [separated from efi_loader part, and fixed build-errors for non- CONFIG_EFI_PARTITION case] Signed-off-by: Rob Clark Signed-off-by: Alexander Graf --- disk/part_dos.c | 12 +++++++++--- disk/part_efi.c | 20 ++++++++++++++++++++ include/blk.h | 18 ++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/disk/part_dos.c b/disk/part_dos.c index 7aff73d6f8..1a36be0446 100644 --- a/disk/part_dos.c +++ b/disk/part_dos.c @@ -89,14 +89,20 @@ static int test_block_type(unsigned char *buffer) static int part_test_dos(struct blk_desc *dev_desc) { - ALLOC_CACHE_ALIGN_BUFFER(unsigned char, buffer, dev_desc->blksz); + ALLOC_CACHE_ALIGN_BUFFER(legacy_mbr, mbr, dev_desc->blksz); - if (blk_dread(dev_desc, 0, 1, (ulong *)buffer) != 1) + if (blk_dread(dev_desc, 0, 1, (ulong *)mbr) != 1) return -1; - if (test_block_type(buffer) != DOS_MBR) + if (test_block_type((unsigned char *)mbr) != DOS_MBR) return -1; + if (dev_desc->sig_type == SIG_TYPE_NONE && + mbr->unique_mbr_signature != 0) { + dev_desc->sig_type = SIG_TYPE_MBR; + dev_desc->mbr_sig = mbr->unique_mbr_signature; + } + return 0; } diff --git a/disk/part_efi.c b/disk/part_efi.c index 2973d52f6a..208bb14ee8 100644 --- a/disk/part_efi.c +++ b/disk/part_efi.c @@ -923,11 +923,19 @@ static int is_pmbr_valid(legacy_mbr * mbr) static int is_gpt_valid(struct blk_desc *dev_desc, u64 lba, gpt_header *pgpt_head, gpt_entry **pgpt_pte) { + ALLOC_CACHE_ALIGN_BUFFER(legacy_mbr, mbr, dev_desc->blksz); + if (!dev_desc || !pgpt_head) { printf("%s: Invalid Argument(s)\n", __func__); return 0; } + /* Read MBR Header from device */ + if (blk_dread(dev_desc, 0, 1, (ulong *)mbr) != 1) { + printf("*** ERROR: Can't read MBR header ***\n"); + return 0; + } + /* Read GPT Header from device */ if (blk_dread(dev_desc, (lbaint_t)lba, 1, pgpt_head) != 1) { printf("*** ERROR: Can't read GPT header ***\n"); @@ -937,6 +945,18 @@ static int is_gpt_valid(struct blk_desc *dev_desc, u64 lba, if (validate_gpt_header(pgpt_head, (lbaint_t)lba, dev_desc->lba)) return 0; + if (dev_desc->sig_type == SIG_TYPE_NONE) { + efi_guid_t empty = {}; + if (memcmp(&pgpt_head->disk_guid, &empty, sizeof(empty))) { + dev_desc->sig_type = SIG_TYPE_GUID; + memcpy(&dev_desc->guid_sig, &pgpt_head->disk_guid, + sizeof(empty)); + } else if (mbr->unique_mbr_signature != 0) { + dev_desc->sig_type = SIG_TYPE_MBR; + dev_desc->mbr_sig = mbr->unique_mbr_signature; + } + } + /* Read and allocate Partition Table Entries */ *pgpt_pte = alloc_read_gpt_entries(dev_desc, pgpt_head); if (*pgpt_pte == NULL) { diff --git a/include/blk.h b/include/blk.h index 1965812a9d..41b4d7efa8 100644 --- a/include/blk.h +++ b/include/blk.h @@ -8,6 +8,8 @@ #ifndef BLK_H #define BLK_H +#include + #ifdef CONFIG_SYS_64BIT_LBA typedef uint64_t lbaint_t; #define LBAFlength "ll" @@ -40,6 +42,17 @@ enum if_type { #define BLK_PRD_SIZE 20 #define BLK_REV_SIZE 8 +/* + * Identifies the partition table type (ie. MBR vs GPT GUID) signature + */ +enum sig_type { + SIG_TYPE_NONE, + SIG_TYPE_MBR, + SIG_TYPE_GUID, + + SIG_TYPE_COUNT /* Number of signature types */ +}; + /* * With driver model (CONFIG_BLK) this is uclass platform data, accessible * with dev_get_uclass_platdata(dev) @@ -67,6 +80,11 @@ struct blk_desc { char vendor[BLK_VEN_SIZE + 1]; /* device vendor string */ char product[BLK_PRD_SIZE + 1]; /* device product number */ char revision[BLK_REV_SIZE + 1]; /* firmware revision */ + enum sig_type sig_type; /* Partition table signature type */ + union { + uint32_t mbr_sig; /* MBR integer signature */ + efi_guid_t guid_sig; /* GPT GUID Signature */ + }; #if CONFIG_IS_ENABLED(BLK) /* * For now we have a few functions which take struct blk_desc as a -- cgit v1.2.3 From a8606ef0757b722e33c98858c0e4cc6a07b41867 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:26 -0400 Subject: efi: add some missing __packed All of the device-path related structures should be packed. UEFI defines the device-path as a byte-aligned data structure. Signed-off-by: Rob Clark Signed-off-by: Alexander Graf --- include/efi_api.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/efi_api.h b/include/efi_api.h index ec1b321e8e..175341348e 100644 --- a/include/efi_api.h +++ b/include/efi_api.h @@ -284,11 +284,11 @@ struct efi_device_path { u8 type; u8 sub_type; u16 length; -}; +} __packed; struct efi_mac_addr { u8 addr[32]; -}; +} __packed; #define DEVICE_PATH_TYPE_MESSAGING_DEVICE 0x03 # define DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR 0x0b @@ -297,7 +297,7 @@ struct efi_device_path_mac_addr { struct efi_device_path dp; struct efi_mac_addr mac; u8 if_type; -}; +} __packed; #define DEVICE_PATH_TYPE_MEDIA_DEVICE 0x04 # define DEVICE_PATH_SUB_TYPE_FILE_PATH 0x04 @@ -305,7 +305,7 @@ struct efi_device_path_mac_addr { struct efi_device_path_file_path { struct efi_device_path dp; u16 str[32]; -}; +} __packed; #define BLOCK_IO_GUID \ EFI_GUID(0x964e5b21, 0x6459, 0x11d2, \ -- cgit v1.2.3 From c80214ce1f39a9b9da32dbe941ff83051b03c080 Mon Sep 17 00:00:00 2001 From: Peter Jones Date: Wed, 13 Sep 2017 18:05:27 -0400 Subject: efi: add some more device path structures Signed-off-by: Peter Jones Signed-off-by: Rob Clark Signed-off-by: Alexander Graf --- include/efi_api.h | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/include/efi_api.h b/include/efi_api.h index 175341348e..b761cf4822 100644 --- a/include/efi_api.h +++ b/include/efi_api.h @@ -290,8 +290,38 @@ struct efi_mac_addr { u8 addr[32]; } __packed; +#define DEVICE_PATH_TYPE_HARDWARE_DEVICE 0x01 +# define DEVICE_PATH_SUB_TYPE_VENDOR 0x04 + +struct efi_device_path_vendor { + struct efi_device_path dp; + efi_guid_t guid; + u8 vendor_data[]; +} __packed; + +#define DEVICE_PATH_TYPE_ACPI_DEVICE 0x02 +# define DEVICE_PATH_SUB_TYPE_ACPI_DEVICE 0x01 + +#define EFI_PNP_ID(ID) (u32)(((ID) << 16) | 0x41D0) +#define EISA_PNP_ID(ID) EFI_PNP_ID(ID) + +struct efi_device_path_acpi_path { + struct efi_device_path dp; + u32 hid; + u32 uid; +} __packed; + #define DEVICE_PATH_TYPE_MESSAGING_DEVICE 0x03 +# define DEVICE_PATH_SUB_TYPE_MSG_USB 0x05 # define DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR 0x0b +# define DEVICE_PATH_SUB_TYPE_MSG_SD 0x1a +# define DEVICE_PATH_SUB_TYPE_MSG_MMC 0x1d + +struct efi_device_path_usb { + struct efi_device_path dp; + u8 parent_port_number; + u8 usb_interface; +} __packed; struct efi_device_path_mac_addr { struct efi_device_path dp; @@ -299,9 +329,33 @@ struct efi_device_path_mac_addr { u8 if_type; } __packed; +struct efi_device_path_sd_mmc_path { + struct efi_device_path dp; + u8 slot_number; +} __packed; + #define DEVICE_PATH_TYPE_MEDIA_DEVICE 0x04 +# define DEVICE_PATH_SUB_TYPE_HARD_DRIVE_PATH 0x01 +# define DEVICE_PATH_SUB_TYPE_CDROM_PATH 0x02 # define DEVICE_PATH_SUB_TYPE_FILE_PATH 0x04 +struct efi_device_path_hard_drive_path { + struct efi_device_path dp; + u32 partition_number; + u64 partition_start; + u64 partition_end; + u8 partition_signature[16]; + u8 partmap_type; + u8 signature_type; +} __packed; + +struct efi_device_path_cdrom_path { + struct efi_device_path dp; + u32 boot_entry; + u64 partition_start; + u64 partition_end; +} __packed; + struct efi_device_path_file_path { struct efi_device_path dp; u16 str[32]; -- cgit v1.2.3 From b66c60dde9d48889b93694326d40f7e5208cff25 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:28 -0400 Subject: efi_loader: add device-path utils Helpers to construct device-paths from devices, partitions, files, and for parsing and manipulating device-paths. For non-legacy devices, this will use u-boot's device-model to construct device-paths which include bus hierarchy to construct device-paths. For legacy devices we still fake it, but slightly more convincingly. Signed-off-by: Rob Clark Signed-off-by: Alexander Graf --- include/efi_api.h | 10 + include/efi_loader.h | 26 ++ lib/efi_loader/Makefile | 2 +- lib/efi_loader/efi_boottime.c | 13 +- lib/efi_loader/efi_device_path.c | 563 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 611 insertions(+), 3 deletions(-) create mode 100644 lib/efi_loader/efi_device_path.c diff --git a/include/efi_api.h b/include/efi_api.h index b761cf4822..4e27c82129 100644 --- a/include/efi_api.h +++ b/include/efi_api.h @@ -314,6 +314,7 @@ struct efi_device_path_acpi_path { #define DEVICE_PATH_TYPE_MESSAGING_DEVICE 0x03 # define DEVICE_PATH_SUB_TYPE_MSG_USB 0x05 # define DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR 0x0b +# define DEVICE_PATH_SUB_TYPE_MSG_USB_CLASS 0x0f # define DEVICE_PATH_SUB_TYPE_MSG_SD 0x1a # define DEVICE_PATH_SUB_TYPE_MSG_MMC 0x1d @@ -329,6 +330,15 @@ struct efi_device_path_mac_addr { u8 if_type; } __packed; +struct efi_device_path_usb_class { + struct efi_device_path dp; + u16 vendor_id; + u16 product_id; + u8 device_class; + u8 device_subclass; + u8 device_protocol; +} __packed; + struct efi_device_path_sd_mmc_path { struct efi_device_path dp; u8 slot_number; diff --git a/include/efi_loader.h b/include/efi_loader.h index 25398ba40c..29701af319 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -220,6 +220,32 @@ extern void *efi_bounce_buffer; #define EFI_LOADER_BOUNCE_BUFFER_SIZE (64 * 1024 * 1024) #endif + +struct efi_device_path *efi_dp_next(const struct efi_device_path *dp); +int efi_dp_match(struct efi_device_path *a, struct efi_device_path *b); +struct efi_object *efi_dp_find_obj(struct efi_device_path *dp, + struct efi_device_path **rem); +unsigned efi_dp_size(const struct efi_device_path *dp); +struct efi_device_path *efi_dp_dup(const struct efi_device_path *dp); +struct efi_device_path *efi_dp_append(const struct efi_device_path *dp1, + const struct efi_device_path *dp2); +struct efi_device_path *efi_dp_append_node(const struct efi_device_path *dp, + const struct efi_device_path *node); + + +struct efi_device_path *efi_dp_from_dev(struct udevice *dev); +struct efi_device_path *efi_dp_from_part(struct blk_desc *desc, int part); +struct efi_device_path *efi_dp_from_file(struct blk_desc *desc, int part, + const char *path); +struct efi_device_path *efi_dp_from_eth(void); +void efi_dp_split_file_path(struct efi_device_path *full_path, + struct efi_device_path **device_path, + struct efi_device_path **file_path); + +#define EFI_DP_TYPE(_dp, _type, _subtype) \ + (((_dp)->type == DEVICE_PATH_TYPE_##_type) && \ + ((_dp)->sub_type == DEVICE_PATH_SUB_TYPE_##_subtype)) + /* Convert strings from normal C strings to uEFI strings */ static inline void ascii2unicode(u16 *unicode, const char *ascii) { diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile index 0cfdd3bfc9..e68b0dd9a5 100644 --- a/lib/efi_loader/Makefile +++ b/lib/efi_loader/Makefile @@ -16,7 +16,7 @@ endif obj-$(CONFIG_CMD_BOOTEFI_HELLO) += helloworld_efi.o obj-y += efi_image_loader.o efi_boottime.o efi_runtime.o efi_console.o -obj-y += efi_memory.o efi_device_path_to_text.o +obj-y += efi_memory.o efi_device_path_to_text.o efi_device_path.o obj-$(CONFIG_LCD) += efi_gop.o obj-$(CONFIG_DM_VIDEO) += efi_gop.o obj-$(CONFIG_PARTITIONS) += efi_disk.o diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 0d234146d7..67a045a2ec 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -716,8 +716,17 @@ static efi_status_t EFIAPI efi_locate_device_path(efi_guid_t *protocol, struct efi_device_path **device_path, efi_handle_t *device) { - EFI_ENTRY("%p, %p, %p", protocol, device_path, device); - return EFI_EXIT(EFI_NOT_FOUND); + struct efi_object *efiobj; + + EFI_ENTRY("%pUl, %p, %p", protocol, device_path, device); + + efiobj = efi_dp_find_obj(*device_path, device_path); + if (!efiobj) + return EFI_EXIT(EFI_NOT_FOUND); + + *device = efiobj->handle; + + return EFI_EXIT(EFI_SUCCESS); } /* Collapses configuration table entries, removing index i */ diff --git a/lib/efi_loader/efi_device_path.c b/lib/efi_loader/efi_device_path.c new file mode 100644 index 0000000000..5d5c3b3464 --- /dev/null +++ b/lib/efi_loader/efi_device_path.c @@ -0,0 +1,563 @@ +/* + * EFI device path from u-boot device-model mapping + * + * (C) Copyright 2017 Rob Clark + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* template END node: */ +static const struct efi_device_path END = { + .type = DEVICE_PATH_TYPE_END, + .sub_type = DEVICE_PATH_SUB_TYPE_END, + .length = sizeof(END), +}; + +#define U_BOOT_GUID \ + EFI_GUID(0xe61d73b9, 0xa384, 0x4acc, \ + 0xae, 0xab, 0x82, 0xe8, 0x28, 0xf3, 0x62, 0x8b) + +/* template ROOT node: */ +static const struct efi_device_path_vendor ROOT = { + .dp = { + .type = DEVICE_PATH_TYPE_HARDWARE_DEVICE, + .sub_type = DEVICE_PATH_SUB_TYPE_VENDOR, + .length = sizeof(ROOT), + }, + .guid = U_BOOT_GUID, +}; + +static void *dp_alloc(size_t sz) +{ + void *buf; + + if (efi_allocate_pool(EFI_ALLOCATE_ANY_PAGES, sz, &buf) != EFI_SUCCESS) + return NULL; + + return buf; +} + +/* + * Iterate to next block in device-path, terminating (returning NULL) + * at /End* node. + */ +struct efi_device_path *efi_dp_next(const struct efi_device_path *dp) +{ + if (dp == NULL) + return NULL; + if (dp->type == DEVICE_PATH_TYPE_END) + return NULL; + dp = ((void *)dp) + dp->length; + if (dp->type == DEVICE_PATH_TYPE_END) + return NULL; + return (struct efi_device_path *)dp; +} + +/* + * Compare two device-paths, stopping when the shorter of the two hits + * an End* node. This is useful to, for example, compare a device-path + * representing a device with one representing a file on the device, or + * a device with a parent device. + */ +int efi_dp_match(struct efi_device_path *a, struct efi_device_path *b) +{ + while (1) { + int ret; + + ret = memcmp(&a->length, &b->length, sizeof(a->length)); + if (ret) + return ret; + + ret = memcmp(a, b, a->length); + if (ret) + return ret; + + a = efi_dp_next(a); + b = efi_dp_next(b); + + if (!a || !b) + return 0; + } +} + + +/* + * See UEFI spec (section 3.1.2, about short-form device-paths.. + * tl;dr: we can have a device-path that starts with a USB WWID + * or USB Class node, and a few other cases which don't encode + * the full device path with bus hierarchy: + * + * - MESSAGING:USB_WWID + * - MESSAGING:USB_CLASS + * - MEDIA:FILE_PATH + * - MEDIA:HARD_DRIVE + * - MESSAGING:URI + */ +static struct efi_device_path *shorten_path(struct efi_device_path *dp) +{ + while (dp) { + /* + * TODO: Add MESSAGING:USB_WWID and MESSAGING:URI.. + * in practice fallback.efi just uses MEDIA:HARD_DRIVE + * so not sure when we would see these other cases. + */ + if (EFI_DP_TYPE(dp, MESSAGING_DEVICE, MSG_USB_CLASS) || + EFI_DP_TYPE(dp, MEDIA_DEVICE, HARD_DRIVE_PATH) || + EFI_DP_TYPE(dp, MEDIA_DEVICE, FILE_PATH)) + return dp; + + dp = efi_dp_next(dp); + } + + return dp; +} + +static struct efi_object *find_obj(struct efi_device_path *dp, bool short_path, + struct efi_device_path **rem) +{ + struct efi_object *efiobj; + + list_for_each_entry(efiobj, &efi_obj_list, link) { + int i; + + for (i = 0; i < ARRAY_SIZE(efiobj->protocols); i++) { + struct efi_handler *handler = &efiobj->protocols[i]; + struct efi_device_path *obj_dp; + + if (!handler->guid) + break; + + if (guidcmp(handler->guid, &efi_guid_device_path)) + continue; + + obj_dp = handler->protocol_interface; + + do { + if (efi_dp_match(dp, obj_dp) == 0) { + if (rem) { + *rem = ((void *)dp) + + efi_dp_size(obj_dp); + } + return efiobj; + } + + obj_dp = shorten_path(efi_dp_next(obj_dp)); + } while (short_path && obj_dp); + } + } + + return NULL; +} + + +/* + * Find an efiobj from device-path, if 'rem' is not NULL, returns the + * remaining part of the device path after the matched object. + */ +struct efi_object *efi_dp_find_obj(struct efi_device_path *dp, + struct efi_device_path **rem) +{ + struct efi_object *efiobj; + + efiobj = find_obj(dp, false, rem); + + if (!efiobj) + efiobj = find_obj(dp, true, rem); + + return efiobj; +} + +/* return size not including End node: */ +unsigned efi_dp_size(const struct efi_device_path *dp) +{ + unsigned sz = 0; + + while (dp) { + sz += dp->length; + dp = efi_dp_next(dp); + } + + return sz; +} + +struct efi_device_path *efi_dp_dup(const struct efi_device_path *dp) +{ + struct efi_device_path *ndp; + unsigned sz = efi_dp_size(dp) + sizeof(END); + + if (!dp) + return NULL; + + ndp = dp_alloc(sz); + memcpy(ndp, dp, sz); + + return ndp; +} + +struct efi_device_path *efi_dp_append(const struct efi_device_path *dp1, + const struct efi_device_path *dp2) +{ + struct efi_device_path *ret; + + if (!dp1) { + ret = efi_dp_dup(dp2); + } else if (!dp2) { + ret = efi_dp_dup(dp1); + } else { + /* both dp1 and dp2 are non-null */ + unsigned sz1 = efi_dp_size(dp1); + unsigned sz2 = efi_dp_size(dp2); + void *p = dp_alloc(sz1 + sz2 + sizeof(END)); + memcpy(p, dp1, sz1); + memcpy(p + sz1, dp2, sz2); + memcpy(p + sz1 + sz2, &END, sizeof(END)); + ret = p; + } + + return ret; +} + +struct efi_device_path *efi_dp_append_node(const struct efi_device_path *dp, + const struct efi_device_path *node) +{ + struct efi_device_path *ret; + + if (!node && !dp) { + ret = efi_dp_dup(&END); + } else if (!node) { + ret = efi_dp_dup(dp); + } else if (!dp) { + unsigned sz = node->length; + void *p = dp_alloc(sz + sizeof(END)); + memcpy(p, node, sz); + memcpy(p + sz, &END, sizeof(END)); + ret = p; + } else { + /* both dp and node are non-null */ + unsigned sz = efi_dp_size(dp); + void *p = dp_alloc(sz + node->length + sizeof(END)); + memcpy(p, dp, sz); + memcpy(p + sz, node, node->length); + memcpy(p + sz + node->length, &END, sizeof(END)); + ret = p; + } + + return ret; +} + +#ifdef CONFIG_DM +/* size of device-path not including END node for device and all parents + * up to the root device. + */ +static unsigned dp_size(struct udevice *dev) +{ + if (!dev || !dev->driver) + return sizeof(ROOT); + + switch (dev->driver->id) { + case UCLASS_ROOT: + case UCLASS_SIMPLE_BUS: + /* stop traversing parents at this point: */ + return sizeof(ROOT); + case UCLASS_MMC: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_sd_mmc_path); + case UCLASS_MASS_STORAGE: + case UCLASS_USB_HUB: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_usb_class); + default: + /* just skip over unknown classes: */ + return dp_size(dev->parent); + } +} + +static void *dp_fill(void *buf, struct udevice *dev) +{ + if (!dev || !dev->driver) + return buf; + + switch (dev->driver->id) { + case UCLASS_ROOT: + case UCLASS_SIMPLE_BUS: { + /* stop traversing parents at this point: */ + struct efi_device_path_vendor *vdp = buf; + *vdp = ROOT; + return &vdp[1]; + } +#if defined(CONFIG_DM_MMC) && defined(CONFIG_MMC) + case UCLASS_MMC: { + struct efi_device_path_sd_mmc_path *sddp = + dp_fill(buf, dev->parent); + struct mmc *mmc = mmc_get_mmc_dev(dev); + struct blk_desc *desc = mmc_get_blk_desc(mmc); + + sddp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + sddp->dp.sub_type = (desc->if_type == IF_TYPE_MMC) ? + DEVICE_PATH_SUB_TYPE_MSG_MMC : + DEVICE_PATH_SUB_TYPE_MSG_SD; + sddp->dp.length = sizeof(*sddp); + sddp->slot_number = dev->seq; + + return &sddp[1]; + } +#endif + case UCLASS_MASS_STORAGE: + case UCLASS_USB_HUB: { + struct efi_device_path_usb_class *udp = + dp_fill(buf, dev->parent); + struct usb_device *udev = dev_get_parent_priv(dev); + struct usb_device_descriptor *desc = &udev->descriptor; + + udp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + udp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_USB_CLASS; + udp->dp.length = sizeof(*udp); + udp->vendor_id = desc->idVendor; + udp->product_id = desc->idProduct; + udp->device_class = desc->bDeviceClass; + udp->device_subclass = desc->bDeviceSubClass; + udp->device_protocol = desc->bDeviceProtocol; + + return &udp[1]; + } + default: + debug("unhandled device class: %s (%u)\n", + dev->name, dev->driver->id); + return dp_fill(buf, dev->parent); + } +} + +/* Construct a device-path from a device: */ +struct efi_device_path *efi_dp_from_dev(struct udevice *dev) +{ + void *buf, *start; + + start = buf = dp_alloc(dp_size(dev) + sizeof(END)); + buf = dp_fill(buf, dev); + *((struct efi_device_path *)buf) = END; + + return start; +} +#endif + +static unsigned dp_part_size(struct blk_desc *desc, int part) +{ + unsigned dpsize; + +#ifdef CONFIG_BLK + dpsize = dp_size(desc->bdev->parent); +#else + dpsize = sizeof(ROOT) + sizeof(struct efi_device_path_usb); +#endif + + if (part == 0) /* the actual disk, not a partition */ + return dpsize; + + if (desc->part_type == PART_TYPE_ISO) + dpsize += sizeof(struct efi_device_path_cdrom_path); + else + dpsize += sizeof(struct efi_device_path_hard_drive_path); + + return dpsize; +} + +static void *dp_part_fill(void *buf, struct blk_desc *desc, int part) +{ + disk_partition_t info; + +#ifdef CONFIG_BLK + buf = dp_fill(buf, desc->bdev->parent); +#else + /* + * We *could* make a more accurate path, by looking at if_type + * and handling all the different cases like we do for non- + * legacy (ie CONFIG_BLK=y) case. But most important thing + * is just to have a unique device-path for if_type+devnum. + * So map things to a fictional USB device: + */ + struct efi_device_path_usb *udp; + + memcpy(buf, &ROOT, sizeof(ROOT)); + buf += sizeof(ROOT); + + udp = buf; + udp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + udp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_USB; + udp->dp.length = sizeof(*udp); + udp->parent_port_number = desc->if_type; + udp->usb_interface = desc->devnum; + buf = &udp[1]; +#endif + + if (part == 0) /* the actual disk, not a partition */ + return buf; + + part_get_info(desc, part, &info); + + if (desc->part_type == PART_TYPE_ISO) { + struct efi_device_path_cdrom_path *cddp = buf; + + cddp->boot_entry = part - 1; + cddp->dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; + cddp->dp.sub_type = DEVICE_PATH_SUB_TYPE_CDROM_PATH; + cddp->dp.length = sizeof(*cddp); + cddp->partition_start = info.start; + cddp->partition_end = info.size; + + buf = &cddp[1]; + } else { + struct efi_device_path_hard_drive_path *hddp = buf; + + hddp->dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; + hddp->dp.sub_type = DEVICE_PATH_SUB_TYPE_HARD_DRIVE_PATH; + hddp->dp.length = sizeof(*hddp); + hddp->partition_number = part - 1; + hddp->partition_start = info.start; + hddp->partition_end = info.size; + if (desc->part_type == PART_TYPE_EFI) + hddp->partmap_type = 2; + else + hddp->partmap_type = 1; + hddp->signature_type = desc->sig_type; + if (hddp->signature_type != 0) + memcpy(hddp->partition_signature, &desc->guid_sig, + sizeof(hddp->partition_signature)); + + buf = &hddp[1]; + } + + return buf; +} + + +/* Construct a device-path from a partition on a blk device: */ +struct efi_device_path *efi_dp_from_part(struct blk_desc *desc, int part) +{ + void *buf, *start; + + start = buf = dp_alloc(dp_part_size(desc, part) + sizeof(END)); + + buf = dp_part_fill(buf, desc, part); + + *((struct efi_device_path *)buf) = END; + + return start; +} + +/* convert path to an UEFI style path (ie. DOS style backslashes and utf16) */ +static void path_to_uefi(u16 *uefi, const char *path) +{ + while (*path) { + char c = *(path++); + if (c == '/') + c = '\\'; + *(uefi++) = c; + } + *uefi = '\0'; +} + +/* + * If desc is NULL, this creates a path with only the file component, + * otherwise it creates a full path with both device and file components + */ +struct efi_device_path *efi_dp_from_file(struct blk_desc *desc, int part, + const char *path) +{ + struct efi_device_path_file_path *fp; + void *buf, *start; + unsigned dpsize = 0, fpsize; + + if (desc) + dpsize = dp_part_size(desc, part); + + fpsize = sizeof(struct efi_device_path) + 2 * (strlen(path) + 1); + dpsize += fpsize; + + start = buf = dp_alloc(dpsize + sizeof(END)); + + if (desc) + buf = dp_part_fill(buf, desc, part); + + /* add file-path: */ + fp = buf; + fp->dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; + fp->dp.sub_type = DEVICE_PATH_SUB_TYPE_FILE_PATH; + fp->dp.length = fpsize; + path_to_uefi(fp->str, path); + buf += fpsize; + + *((struct efi_device_path *)buf) = END; + + return start; +} + +#ifdef CONFIG_NET +struct efi_device_path *efi_dp_from_eth(void) +{ + struct efi_device_path_mac_addr *ndp; + void *buf, *start; + unsigned dpsize = 0; + + assert(eth_get_dev()); + +#ifdef CONFIG_DM_ETH + dpsize += dp_size(eth_get_dev()); +#else + dpsize += sizeof(ROOT); +#endif + dpsize += sizeof(*ndp); + + start = buf = dp_alloc(dpsize + sizeof(END)); + +#ifdef CONFIG_DM_ETH + buf = dp_fill(buf, eth_get_dev()); +#else + memcpy(buf, &ROOT, sizeof(ROOT)); + buf += sizeof(ROOT); +#endif + + ndp = buf; + ndp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + ndp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR; + ndp->dp.length = sizeof(*ndp); + memcpy(ndp->mac.addr, eth_get_ethaddr(), ARP_HLEN); + buf = &ndp[1]; + + *((struct efi_device_path *)buf) = END; + + return start; +} +#endif + +/* + * Helper to split a full device path (containing both device and file + * parts) into it's constituent parts. + */ +void efi_dp_split_file_path(struct efi_device_path *full_path, + struct efi_device_path **device_path, + struct efi_device_path **file_path) +{ + struct efi_device_path *p, *dp, *fp; + + dp = efi_dp_dup(full_path); + p = dp; + while (!EFI_DP_TYPE(p, MEDIA_DEVICE, FILE_PATH)) + p = efi_dp_next(p); + fp = efi_dp_dup(p); + + p->type = DEVICE_PATH_TYPE_END; + p->sub_type = DEVICE_PATH_SUB_TYPE_END; + p->length = sizeof(*p); + + *device_path = dp; + *file_path = fp; +} -- cgit v1.2.3 From 9309a1b76ce4f18fe1d9fe48f2b1356ebc58b62f Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:29 -0400 Subject: efi_loader: drop redundant efi_device_path_protocol This is really the same thing as the efi_device_path struct. Signed-off-by: Rob Clark Signed-off-by: Alexander Graf --- include/efi_api.h | 12 ++---------- lib/efi_loader/efi_device_path_to_text.c | 13 ++++++++----- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/include/efi_api.h b/include/efi_api.h index 4e27c82129..ac58fd58de 100644 --- a/include/efi_api.h +++ b/include/efi_api.h @@ -487,22 +487,14 @@ struct efi_console_control_protocol EFI_GUID(0x8b843e20, 0x8132, 0x4852, \ 0x90, 0xcc, 0x55, 0x1a, 0x4e, 0x4a, 0x7f, 0x1c) -struct efi_device_path_protocol -{ - uint8_t type; - uint8_t sub_type; - uint16_t length; - uint8_t data[]; -}; - struct efi_device_path_to_text_protocol { uint16_t *(EFIAPI *convert_device_node_to_text)( - struct efi_device_path_protocol *device_node, + struct efi_device_path *device_node, bool display_only, bool allow_shortcuts); uint16_t *(EFIAPI *convert_device_path_to_text)( - struct efi_device_path_protocol *device_path, + struct efi_device_path *device_path, bool display_only, bool allow_shortcuts); }; diff --git a/lib/efi_loader/efi_device_path_to_text.c b/lib/efi_loader/efi_device_path_to_text.c index 4b2f43f0c8..f9d071ac50 100644 --- a/lib/efi_loader/efi_device_path_to_text.c +++ b/lib/efi_loader/efi_device_path_to_text.c @@ -16,7 +16,7 @@ const efi_guid_t efi_guid_device_path_to_text_protocol = EFI_DEVICE_PATH_TO_TEXT_PROTOCOL_GUID; static uint16_t *efi_convert_device_node_to_text( - struct efi_device_path_protocol *device_node, + struct efi_device_path *device_node, bool display_only, bool allow_shortcuts) { @@ -55,15 +55,18 @@ static uint16_t *efi_convert_device_node_to_text( break; case DEVICE_PATH_TYPE_MEDIA_DEVICE: switch (device_node->sub_type) { - case DEVICE_PATH_SUB_TYPE_FILE_PATH: + case DEVICE_PATH_SUB_TYPE_FILE_PATH: { + struct efi_device_path_file_path *fp = + (struct efi_device_path_file_path *)device_node; buffer_size = device_node->length - 4; r = efi_allocate_pool(EFI_ALLOCATE_ANY_PAGES, buffer_size, (void **) &buffer); if (r != EFI_SUCCESS) return NULL; - memcpy(buffer, device_node->data, buffer_size); + memcpy(buffer, fp->str, buffer_size); break; } + } break; } @@ -89,7 +92,7 @@ static uint16_t *efi_convert_device_node_to_text( } static uint16_t EFIAPI *efi_convert_device_node_to_text_ext( - struct efi_device_path_protocol *device_node, + struct efi_device_path *device_node, bool display_only, bool allow_shortcuts) { @@ -105,7 +108,7 @@ static uint16_t EFIAPI *efi_convert_device_node_to_text_ext( } static uint16_t EFIAPI *efi_convert_device_path_to_text( - struct efi_device_path_protocol *device_path, + struct efi_device_path *device_path, bool display_only, bool allow_shortcuts) { -- cgit v1.2.3 From adae4313cdd891e5ab76c407134b69bd825220e4 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:30 -0400 Subject: efi_loader: flesh out device-path to text It needs to handle more device-path node types, and also multiple levels of path hierarchy. To simplify this, initially construct utf8 string to a temporary buffer, and then allocate the real utf16 buffer that is returned. This should be mostly for debugging or at least not critical- path so an extra copy won't hurt, and is saner than the alternative. Signed-off-by: Rob Clark Signed-off-by: Alexander Graf --- include/efi_api.h | 1 + include/efi_loader.h | 2 + lib/efi_loader/efi_device_path_to_text.c | 241 +++++++++++++++++++++++-------- 3 files changed, 181 insertions(+), 63 deletions(-) diff --git a/include/efi_api.h b/include/efi_api.h index ac58fd58de..0c36122107 100644 --- a/include/efi_api.h +++ b/include/efi_api.h @@ -304,6 +304,7 @@ struct efi_device_path_vendor { #define EFI_PNP_ID(ID) (u32)(((ID) << 16) | 0x41D0) #define EISA_PNP_ID(ID) EFI_PNP_ID(ID) +#define EISA_PNP_NUM(ID) ((ID) >> 16) struct efi_device_path_acpi_path { struct efi_device_path dp; diff --git a/include/efi_loader.h b/include/efi_loader.h index 29701af319..784257bc1f 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -80,6 +80,8 @@ extern struct efi_simple_input_interface efi_con_in; extern const struct efi_console_control_protocol efi_console_control; extern const struct efi_device_path_to_text_protocol efi_device_path_to_text; +uint16_t *efi_dp_str(struct efi_device_path *dp); + extern const efi_guid_t efi_guid_console_control; extern const efi_guid_t efi_guid_device_path; extern const efi_guid_t efi_guid_loaded_image; diff --git a/lib/efi_loader/efi_device_path_to_text.c b/lib/efi_loader/efi_device_path_to_text.c index f9d071ac50..1a5ef3919b 100644 --- a/lib/efi_loader/efi_device_path_to_text.c +++ b/lib/efi_loader/efi_device_path_to_text.c @@ -15,82 +15,197 @@ const efi_guid_t efi_guid_device_path_to_text_protocol = EFI_DEVICE_PATH_TO_TEXT_PROTOCOL_GUID; -static uint16_t *efi_convert_device_node_to_text( - struct efi_device_path *device_node, - bool display_only, - bool allow_shortcuts) +static char *dp_unknown(char *s, struct efi_device_path *dp) { - unsigned long buffer_size; - efi_status_t r; - uint16_t *buffer = NULL; - int i; + s += sprintf(s, "/UNKNOWN(%04x,%04x)", dp->type, dp->sub_type); + return s; +} - switch (device_node->type) { - case DEVICE_PATH_TYPE_END: - return NULL; - case DEVICE_PATH_TYPE_MESSAGING_DEVICE: - switch (device_node->sub_type) { - case DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR: { - struct efi_device_path_mac_addr *dp = - (struct efi_device_path_mac_addr *)device_node; - - if (dp->if_type != 0 && dp->if_type != 1) - break; - r = efi_allocate_pool(EFI_ALLOCATE_ANY_PAGES, - 2 * MAC_OUTPUT_LEN, - (void **)&buffer); - if (r != EFI_SUCCESS) - return NULL; - sprintf((char *)buffer, - "MAC(%02x%02x%02x%02x%02x%02x,0x%1x)", - dp->mac.addr[0], dp->mac.addr[1], - dp->mac.addr[2], dp->mac.addr[3], - dp->mac.addr[4], dp->mac.addr[5], - dp->if_type); - for (i = MAC_OUTPUT_LEN - 1; i >= 0; --i) - buffer[i] = ((uint8_t *)buffer)[i]; +static char *dp_hardware(char *s, struct efi_device_path *dp) +{ + switch (dp->sub_type) { + case DEVICE_PATH_SUB_TYPE_VENDOR: { + struct efi_device_path_vendor *vdp = + (struct efi_device_path_vendor *)dp; + s += sprintf(s, "/VenHw(%pUl)", &vdp->guid); + 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", EISA_PNP_NUM(adp->hid)); + if (adp->uid) + s += sprintf(s, ",%d", adp->uid); + s += sprintf(s, ")"); + 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_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: { + struct efi_device_path_mac_addr *mdp = + (struct efi_device_path_mac_addr *)dp; + + if (mdp->if_type != 0 && mdp->if_type != 1) break; - } - } + + s += sprintf(s, "/MAC(%02x%02x%02x%02x%02x%02x,0x%1x)", + mdp->mac.addr[0], mdp->mac.addr[1], + mdp->mac.addr[2], mdp->mac.addr[3], + mdp->mac.addr[4], mdp->mac.addr[5], + 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(%x,%x,%x,%x,%x)", + ucdp->vendor_id, ucdp->product_id, + ucdp->device_class, ucdp->device_subclass, + ucdp->device_protocol); + + 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) ? + "SDCard" : "MMC"; + struct efi_device_path_sd_mmc_path *sddp = + (struct efi_device_path_sd_mmc_path *)dp; + s += sprintf(s, "/%s(Slot%u)", typename, sddp->slot_number); + break; + } + default: + s = dp_unknown(s, dp); break; - case DEVICE_PATH_TYPE_MEDIA_DEVICE: - switch (device_node->sub_type) { - case DEVICE_PATH_SUB_TYPE_FILE_PATH: { - struct efi_device_path_file_path *fp = - (struct efi_device_path_file_path *)device_node; - buffer_size = device_node->length - 4; - r = efi_allocate_pool(EFI_ALLOCATE_ANY_PAGES, - buffer_size, (void **) &buffer); - if (r != EFI_SUCCESS) - return NULL; - memcpy(buffer, fp->str, buffer_size); + } + return s; +} + +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; + + switch (hddp->signature_type) { + case SIG_TYPE_MBR: + s += sprintf(s, "/HD(Part%d,Sig%08x)", + hddp->partition_number, + *(uint32_t *)sig); break; + case SIG_TYPE_GUID: + s += sprintf(s, "/HD(Part%d,Sig%pUl)", + hddp->partition_number, sig); + default: + s += sprintf(s, "/HD(Part%d,MBRType=%02x,SigType=%02x)", + hddp->partition_number, hddp->partmap_type, + hddp->signature_type); } - } + + 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(0x%x)", cddp->boot_entry); + 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; + s += sprintf(s, "/%-*ls", slen, fp->str); + break; + } + default: + s = dp_unknown(s, dp); break; } + return s; +} - /* - * For all node types that we do not yet support return - * 'UNKNOWN(type,subtype)'. - */ - if (!buffer) { - r = efi_allocate_pool(EFI_ALLOCATE_ANY_PAGES, - 2 * UNKNOWN_OUTPUT_LEN, - (void **)&buffer); - if (r != EFI_SUCCESS) - return NULL; - sprintf((char *)buffer, - "UNKNOWN(%04x,%04x)", - device_node->type, - device_node->sub_type); - for (i = UNKNOWN_OUTPUT_LEN - 1; i >= 0; --i) - buffer[i] = ((uint8_t *)buffer)[i]; +static uint16_t *efi_convert_device_node_to_text( + struct efi_device_path *dp, + bool display_only, + bool allow_shortcuts) +{ + unsigned long len; + efi_status_t r; + char buf[512]; /* this ought be be big enough for worst case */ + char *str = buf; + uint16_t *out; + + while (dp) { + 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; + default: + str = dp_unknown(str, dp); + } + + dp = efi_dp_next(dp); } - return buffer; + *str++ = '\0'; + + len = str - buf; + r = efi_allocate_pool(EFI_ALLOCATE_ANY_PAGES, 2 * len, (void **)&out); + if (r != EFI_SUCCESS) + return NULL; + + ascii2unicode(out, buf); + out[len - 1] = 0; + + return out; } +/* helper for debug prints.. efi_free_pool() the result. */ +uint16_t *efi_dp_str(struct efi_device_path *dp) +{ + return efi_convert_device_node_to_text(dp, true, true); +} + + static uint16_t EFIAPI *efi_convert_device_node_to_text_ext( struct efi_device_path *device_node, bool display_only, -- cgit v1.2.3 From 884bcf6f65c414dce3b3d2a91e2c9eba0e5e08f8 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:31 -0400 Subject: efi_loader: use proper device-paths for partitions Also, create disk objects for the disk itself, in addition to the partitions. (UEFI terminology is a bit confusing, a "disk" object is really a partition.) This helps grub properly identify the boot device since it is trying to match up partition "disk" object with it's parent device. Now instead of seeing devices like: /File(sdhci@07864000.blk)/EndEntire /File(usb_mass_storage.lun0)/EndEntire You see: /ACPI(133741d0,0)/UnknownMessaging(1d)/EndEntire /ACPI(133741d0,0)/UnknownMessaging(1d)/HD(0,800,64000,dd904a8c00000000,1,1)/EndEntire /ACPI(133741d0,0)/UnknownMessaging(1d)/HD(1,64800,200000,dd904a8c00000000,1,1)/EndEntire /ACPI(133741d0,0)/UnknownMessaging(1d)/HD(2,264800,19a000,dd904a8c00000000,1,1)/EndEntire /ACPI(133741d0,0)/USB(0,0)/USB(0,0)/USB(0,0)/EndEntire /ACPI(133741d0,0)/USB(0,0)/USB(0,0)/USB(0,0)/HD(0,800,60000,38ca680200000000,1,1)/EndEntire /ACPI(133741d0,0)/USB(0,0)/USB(0,0)/USB(0,0)/HD(1,61000,155000,38ca680200000000,1,1)/EndEntire /ACPI(133741d0,0)/USB(0,0)/USB(0,0)/USB(0,0)/HD(2,20fa800,1bbf8800,38ca680200000000,1,1)/EndEntire /ACPI(133741d0,0)/USB(0,0)/USB(0,0)/USB(0,0)/HD(3,1b6800,1f44000,38ca680200000000,1,1)/EndEntire This is on a board with single USB disk and single sd-card. The UnknownMessaging(1d) node in the device-path is the MMC device, but grub_efi_print_device_path() hasn't been updated yet for some of the newer device-path sub-types. This patch is inspired by a patch originally from Peter Jones, but re-worked to use efi_device_path, so it doesn't much resemble the original. Signed-off-by: Rob Clark [agraf: s/unsigned/unsigned int/] Signed-off-by: Alexander Graf --- lib/efi_loader/efi_disk.c | 54 +++++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c index 4e78ade2ed..c815711e57 100644 --- a/lib/efi_loader/efi_disk.c +++ b/lib/efi_loader/efi_disk.c @@ -28,11 +28,13 @@ struct efi_disk_obj { /* EFI Interface Media descriptor struct, referenced by ops */ struct efi_block_io_media media; /* EFI device path to this block device */ - struct efi_device_path_file_path *dp; + struct efi_device_path *dp; + /* partition # */ + unsigned int part; /* Offset into disk for simple partitions */ lbaint_t offset; /* Internal block device */ - const struct blk_desc *desc; + struct blk_desc *desc; }; static efi_status_t EFIAPI efi_disk_reset(struct efi_block_io *this, @@ -172,26 +174,26 @@ static const struct efi_block_io block_io_disk_template = { static void efi_disk_add_dev(const char *name, const char *if_typename, - const struct blk_desc *desc, + struct blk_desc *desc, int dev_index, - lbaint_t offset) + lbaint_t offset, + unsigned int part) { struct efi_disk_obj *diskobj; - struct efi_device_path_file_path *dp; - int objlen = sizeof(*diskobj) + (sizeof(*dp) * 2); /* Don't add empty devices */ if (!desc->lba) return; - diskobj = calloc(1, objlen); + diskobj = calloc(1, sizeof(*diskobj)); /* Fill in object data */ - dp = (void *)&diskobj[1]; + diskobj->dp = efi_dp_from_part(desc, part); + diskobj->part = part; diskobj->parent.protocols[0].guid = &efi_block_io_guid; diskobj->parent.protocols[0].protocol_interface = &diskobj->ops; diskobj->parent.protocols[1].guid = &efi_guid_device_path; - diskobj->parent.protocols[1].protocol_interface = dp; + diskobj->parent.protocols[1].protocol_interface = diskobj->dp; diskobj->parent.handle = diskobj; diskobj->ops = block_io_disk_template; diskobj->ifname = if_typename; @@ -207,17 +209,6 @@ static void efi_disk_add_dev(const char *name, diskobj->media.last_block = desc->lba - offset; diskobj->ops.media = &diskobj->media; - /* Fill in device path */ - diskobj->dp = dp; - dp[0].dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; - dp[0].dp.sub_type = DEVICE_PATH_SUB_TYPE_FILE_PATH; - dp[0].dp.length = sizeof(*dp); - ascii2unicode(dp[0].str, name); - - dp[1].dp.type = DEVICE_PATH_TYPE_END; - dp[1].dp.sub_type = DEVICE_PATH_SUB_TYPE_END; - dp[1].dp.length = sizeof(*dp); - /* Hook up to the device list */ list_add_tail(&diskobj->parent.link, &efi_obj_list); } @@ -236,14 +227,18 @@ static int efi_disk_create_eltorito(struct blk_desc *desc, if (desc->part_type != PART_TYPE_ISO) return 0; + /* and devices for each partition: */ while (!part_get_info(desc, part, &info)) { snprintf(devname, sizeof(devname), "%s:%d", pdevname, part); efi_disk_add_dev(devname, if_typename, desc, diskid, - info.start); + info.start, part); part++; disks++; } + + /* ... and add block device: */ + efi_disk_add_dev(devname, if_typename, desc, diskid, 0, 0); #endif return disks; @@ -271,9 +266,22 @@ int efi_disk_register(void) uclass_next_device_check(&dev)) { struct blk_desc *desc = dev_get_uclass_platdata(dev); const char *if_typename = dev->driver->name; + disk_partition_t info; + int part = 1; printf("Scanning disk %s...\n", dev->name); - efi_disk_add_dev(dev->name, if_typename, desc, desc->devnum, 0); + + /* add devices for each partition: */ + while (!part_get_info(desc, part, &info)) { + efi_disk_add_dev(dev->name, if_typename, desc, + desc->devnum, 0, part); + part++; + } + + /* ... and add block device: */ + efi_disk_add_dev(dev->name, if_typename, desc, + desc->devnum, 0, 0); + disks++; /* @@ -309,7 +317,7 @@ int efi_disk_register(void) snprintf(devname, sizeof(devname), "%s%d", if_typename, i); - efi_disk_add_dev(devname, if_typename, desc, i, 0); + efi_disk_add_dev(devname, if_typename, desc, i, 0, 0); disks++; /* -- cgit v1.2.3 From e15fc33548acda1b975bad172dc19033382dc211 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:32 -0400 Subject: efi_loader: use proper device-paths for net Signed-off-by: Rob Clark Signed-off-by: Alexander Graf --- lib/efi_loader/efi_net.c | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/lib/efi_loader/efi_net.c b/lib/efi_loader/efi_net.c index 0b949d86e8..aa0618fd3a 100644 --- a/lib/efi_loader/efi_net.c +++ b/lib/efi_loader/efi_net.c @@ -26,9 +26,6 @@ struct efi_net_obj { /* EFI Interface callback struct for network */ struct efi_simple_network net; struct efi_simple_network_mode net_mode; - /* Device path to the network adapter */ - struct efi_device_path_mac_addr dp_mac; - struct efi_device_path_file_path dp_end; /* PXE struct to transmit dhcp data */ struct efi_pxe pxe; struct efi_pxe_mode pxe_mode; @@ -213,16 +210,6 @@ void efi_net_set_dhcp_ack(void *pkt, int len) int efi_net_register(void **handle) { struct efi_net_obj *netobj; - struct efi_device_path_mac_addr dp_net = { - .dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE, - .dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR, - .dp.length = sizeof(dp_net), - }; - struct efi_device_path_file_path dp_end = { - .dp.type = DEVICE_PATH_TYPE_END, - .dp.sub_type = DEVICE_PATH_SUB_TYPE_END, - .dp.length = sizeof(dp_end), - }; if (!eth_get_dev()) { /* No eth device active, don't expose any */ @@ -236,7 +223,8 @@ int efi_net_register(void **handle) netobj->parent.protocols[0].guid = &efi_net_guid; netobj->parent.protocols[0].protocol_interface = &netobj->net; netobj->parent.protocols[1].guid = &efi_guid_device_path; - netobj->parent.protocols[1].protocol_interface = &netobj->dp_mac; + netobj->parent.protocols[1].protocol_interface = + efi_dp_from_eth(); netobj->parent.protocols[2].guid = &efi_pxe_guid; netobj->parent.protocols[2].protocol_interface = &netobj->pxe; netobj->parent.handle = &netobj->net; @@ -255,9 +243,6 @@ int efi_net_register(void **handle) netobj->net.receive = efi_net_receive; netobj->net.mode = &netobj->net_mode; netobj->net_mode.state = EFI_NETWORK_STARTED; - netobj->dp_mac = dp_net; - netobj->dp_end = dp_end; - memcpy(netobj->dp_mac.mac.addr, eth_get_ethaddr(), 6); memcpy(netobj->net_mode.current_address.mac_addr, eth_get_ethaddr(), 6); netobj->net_mode.max_packet_size = PKTSIZE; -- cgit v1.2.3 From 95c5553ea268144056c4bafc318b9e8b5c096a6c Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:33 -0400 Subject: efi_loader: refactor boot device and loaded_image handling Get rid of the hacky fake boot-device and duplicate device-path constructing (which needs to match what efi_disk and efi_net do). Instead convert over to use efi_device_path helpers to construct device-paths, and use that to look up the actual boot device. Also, extract out a helper to plug things in properly to the loaded_image. In a following patch we'll want to re-use this in efi_load_image() to handle the case of loading an image from a file_path. Signed-off-by: Rob Clark Signed-off-by: Alexander Graf --- cmd/bootefi.c | 201 +++++++++++++----------------------------- include/efi_loader.h | 5 +- lib/efi_loader/efi_boottime.c | 36 ++++++++ lib/efi_loader/efi_net.c | 5 +- 4 files changed, 100 insertions(+), 147 deletions(-) diff --git a/cmd/bootefi.c b/cmd/bootefi.c index 788f869479..c1ef1b542b 100644 --- a/cmd/bootefi.c +++ b/cmd/bootefi.c @@ -22,97 +22,14 @@ DECLARE_GLOBAL_DATA_PTR; static uint8_t efi_obj_list_initalized; -/* - * When booting using the "bootefi" command, we don't know which - * physical device the file came from. So we create a pseudo-device - * called "bootefi" with the device path /bootefi. - * - * In addition to the originating device we also declare the file path - * of "bootefi" based loads to be /bootefi. - */ -static struct efi_device_path_file_path bootefi_image_path[] = { - { - .dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE, - .dp.sub_type = DEVICE_PATH_SUB_TYPE_FILE_PATH, - .dp.length = sizeof(bootefi_image_path[0]), - .str = { 'b','o','o','t','e','f','i' }, - }, { - .dp.type = DEVICE_PATH_TYPE_END, - .dp.sub_type = DEVICE_PATH_SUB_TYPE_END, - .dp.length = sizeof(bootefi_image_path[0]), - } -}; - -static struct efi_device_path_file_path bootefi_device_path[] = { - { - .dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE, - .dp.sub_type = DEVICE_PATH_SUB_TYPE_FILE_PATH, - .dp.length = sizeof(bootefi_image_path[0]), - .str = { 'b','o','o','t','e','f','i' }, - }, { - .dp.type = DEVICE_PATH_TYPE_END, - .dp.sub_type = DEVICE_PATH_SUB_TYPE_END, - .dp.length = sizeof(bootefi_image_path[0]), - } -}; - -/* The EFI loaded_image interface for the image executed via "bootefi" */ -static struct efi_loaded_image loaded_image_info = { - .device_handle = bootefi_device_path, - .file_path = bootefi_image_path, -}; - -/* The EFI object struct for the image executed via "bootefi" */ -static struct efi_object loaded_image_info_obj = { - .handle = &loaded_image_info, - .protocols = { - { - /* - * When asking for the loaded_image interface, just - * return handle which points to loaded_image_info - */ - .guid = &efi_guid_loaded_image, - .protocol_interface = &loaded_image_info, - }, - { - /* - * When asking for the device path interface, return - * bootefi_device_path - */ - .guid = &efi_guid_device_path, - .protocol_interface = bootefi_device_path, - }, - { - .guid = &efi_guid_console_control, - .protocol_interface = (void *) &efi_console_control - }, - { - .guid = &efi_guid_device_path_to_text_protocol, - .protocol_interface = (void *) &efi_device_path_to_text - }, - }, -}; - -/* The EFI object struct for the device the "bootefi" image was loaded from */ -static struct efi_object bootefi_device_obj = { - .handle = bootefi_device_path, - .protocols = { - { - /* When asking for the device path interface, return - * bootefi_device_path */ - .guid = &efi_guid_device_path, - .protocol_interface = bootefi_device_path - } - }, -}; +static struct efi_device_path *bootefi_image_path; +static struct efi_device_path *bootefi_device_path; /* Initialize and populate EFI object list */ static void efi_init_obj_list(void) { efi_obj_list_initalized = 1; - list_add_tail(&loaded_image_info_obj.link, &efi_obj_list); - list_add_tail(&bootefi_device_obj.link, &efi_obj_list); efi_console_register(); #ifdef CONFIG_PARTITIONS efi_disk_register(); @@ -121,13 +38,7 @@ static void efi_init_obj_list(void) efi_gop_register(); #endif #ifdef CONFIG_NET - void *nethandle = loaded_image_info.device_handle; - efi_net_register(&nethandle); - - if (!memcmp(bootefi_device_path[0].str, "N\0e\0t", 6)) - loaded_image_info.device_handle = nethandle; - else - loaded_image_info.device_handle = bootefi_device_path; + efi_net_register(); #endif #ifdef CONFIG_GENERATE_SMBIOS_TABLE efi_smbios_register(); @@ -210,14 +121,27 @@ static unsigned long efi_run_in_el2(asmlinkage ulong (*entry)( * Load an EFI payload into a newly allocated piece of memory, register all * EFI objects it would want to access and jump to it. */ -static unsigned long do_bootefi_exec(void *efi, void *fdt) +static unsigned long do_bootefi_exec(void *efi, void *fdt, + struct efi_device_path *device_path, + struct efi_device_path *image_path) { + struct efi_loaded_image loaded_image_info = {}; + struct efi_object loaded_image_info_obj = {}; + ulong ret; + ulong (*entry)(void *image_handle, struct efi_system_table *st) asmlinkage; ulong fdt_pages, fdt_size, fdt_start, fdt_end; const efi_guid_t fdt_guid = EFI_FDT_GUID; bootm_headers_t img = { 0 }; + /* Initialize and populate EFI object list */ + if (!efi_obj_list_initalized) + efi_init_obj_list(); + + efi_setup_loaded_image(&loaded_image_info, &loaded_image_info_obj, + device_path, image_path); + /* * gd lives in a fixed register which may get clobbered while we execute * the payload. So save it here and restore it on every callback entry @@ -252,18 +176,18 @@ static unsigned long do_bootefi_exec(void *efi, void *fdt) /* Load the EFI payload */ entry = efi_load_pe(efi, &loaded_image_info); - if (!entry) - return -ENOENT; - - /* Initialize and populate EFI object list */ - if (!efi_obj_list_initalized) - efi_init_obj_list(); + if (!entry) { + ret = -ENOENT; + goto exit; + } /* Call our payload! */ debug("%s:%d Jumping to 0x%lx\n", __func__, __LINE__, (long)entry); if (setjmp(&loaded_image_info.exit_jmp)) { - return loaded_image_info.exit_status; + ret = loaded_image_info.exit_status; + EFI_EXIT(ret); + goto exit; } #ifdef CONFIG_ARM64 @@ -282,7 +206,13 @@ static unsigned long do_bootefi_exec(void *efi, void *fdt) } #endif - return efi_do_enter(&loaded_image_info, &systab, entry); + ret = efi_do_enter(&loaded_image_info, &systab, entry); + +exit: + /* image has returned, loaded-image obj goes *poof*: */ + list_del(&loaded_image_info_obj.link); + + return ret; } /* Interpreter command to boot an arbitrary EFI image from memory */ @@ -334,7 +264,8 @@ static int do_bootefi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) } printf("## Starting EFI application at %08lx ...\n", addr); - r = do_bootefi_exec((void *)addr, (void*)fdt_addr); + r = do_bootefi_exec((void *)addr, (void *)fdt_addr, + bootefi_device_path, bootefi_image_path); printf("## Application terminated, r = %lu\n", r & ~EFI_ERROR_MASK); @@ -367,58 +298,44 @@ U_BOOT_CMD( bootefi_help_text ); -void efi_set_bootdev(const char *dev, const char *devnr, const char *path) +static int parse_partnum(const char *devnr) { - __maybe_unused struct blk_desc *desc; - char devname[32] = { 0 }; /* dp->str is u16[32] long */ - char *colon, *s; - -#if defined(CONFIG_BLK) || CONFIG_IS_ENABLED(ISO_PARTITION) - desc = blk_get_dev(dev, simple_strtol(devnr, NULL, 10)); -#endif - -#ifdef CONFIG_BLK - if (desc) { - snprintf(devname, sizeof(devname), "%s", desc->bdev->name); - } else -#endif - - { - /* Assemble the condensed device name we use in efi_disk.c */ - snprintf(devname, sizeof(devname), "%s%s", dev, devnr); + const char *str = strchr(devnr, ':'); + if (str) { + str++; + return simple_strtoul(str, NULL, 16); } + return 0; +} - colon = strchr(devname, ':'); - -#if CONFIG_IS_ENABLED(ISO_PARTITION) - /* For ISOs we create partition block devices */ - if (desc && (desc->type != DEV_TYPE_UNKNOWN) && - (desc->part_type == PART_TYPE_ISO)) { - if (!colon) - snprintf(devname, sizeof(devname), "%s:1", devname); +void efi_set_bootdev(const char *dev, const char *devnr, const char *path) +{ + char filename[32] = { 0 }; /* dp->str is u16[32] long */ + char *s; - colon = NULL; - } -#endif + if (strcmp(dev, "Net")) { + struct blk_desc *desc; + int part; - if (colon) - *colon = '\0'; + desc = blk_get_dev(dev, simple_strtol(devnr, NULL, 10)); + part = parse_partnum(devnr); - /* Patch bootefi_device_path to the target device */ - memset(bootefi_device_path[0].str, 0, sizeof(bootefi_device_path[0].str)); - ascii2unicode(bootefi_device_path[0].str, devname); + bootefi_device_path = efi_dp_from_part(desc, part); + } else { +#ifdef CONFIG_NET + bootefi_device_path = efi_dp_from_eth(); +#endif + } - /* Patch bootefi_image_path to the target file path */ - memset(bootefi_image_path[0].str, 0, sizeof(bootefi_image_path[0].str)); if (strcmp(dev, "Net")) { /* Add leading / to fs paths, because they're absolute */ - snprintf(devname, sizeof(devname), "/%s", path); + snprintf(filename, sizeof(filename), "/%s", path); } else { - snprintf(devname, sizeof(devname), "%s", path); + snprintf(filename, sizeof(filename), "%s", path); } /* DOS style file path: */ - s = devname; + s = filename; while ((s = strchr(s, '/'))) *s++ = '\\'; - ascii2unicode(bootefi_image_path[0].str, devname); + bootefi_image_path = efi_dp_from_file(NULL, 0, filename); } diff --git a/include/efi_loader.h b/include/efi_loader.h index 784257bc1f..8752bcf010 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -159,7 +159,7 @@ int efi_disk_register(void); /* Called by bootefi to make GOP (graphical) interface available */ int efi_gop_register(void); /* Called by bootefi to make the network interface available */ -int efi_net_register(void **handle); +int efi_net_register(void); /* Called by bootefi to make SMBIOS tables available */ void efi_smbios_register(void); @@ -216,6 +216,9 @@ uint64_t efi_add_memory_map(uint64_t start, uint64_t pages, int memory_type, int efi_memory_init(void); /* Adds new or overrides configuration table entry to the system table */ efi_status_t efi_install_configuration_table(const efi_guid_t *guid, void *table); +void efi_setup_loaded_image(struct efi_loaded_image *info, struct efi_object *obj, + struct efi_device_path *device_path, + struct efi_device_path *file_path); #ifdef CONFIG_EFI_LOADER_BOUNCE_BUFFER extern void *efi_bounce_buffer; diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 67a045a2ec..38ae80b116 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -777,6 +777,42 @@ static efi_status_t EFIAPI efi_install_configuration_table_ext(efi_guid_t *guid, return EFI_EXIT(efi_install_configuration_table(guid, table)); } +/* Initialize a loaded_image_info + loaded_image_info object with correct + * protocols, boot-device, etc. + */ +void efi_setup_loaded_image(struct efi_loaded_image *info, struct efi_object *obj, + struct efi_device_path *device_path, + struct efi_device_path *file_path) +{ + obj->handle = info; + + /* + * When asking for the device path interface, return + * bootefi_device_path + */ + obj->protocols[0].guid = &efi_guid_device_path; + obj->protocols[0].protocol_interface = device_path; + + /* + * When asking for the loaded_image interface, just + * return handle which points to loaded_image_info + */ + obj->protocols[1].guid = &efi_guid_loaded_image; + obj->protocols[1].protocol_interface = info; + + obj->protocols[2].guid = &efi_guid_console_control; + obj->protocols[2].protocol_interface = (void *)&efi_console_control; + + obj->protocols[3].guid = &efi_guid_device_path_to_text_protocol; + obj->protocols[3].protocol_interface = + (void *)&efi_device_path_to_text; + + info->file_path = file_path; + info->device_handle = efi_dp_find_obj(device_path, NULL); + + list_add_tail(&obj->link, &efi_obj_list); +} + static efi_status_t EFIAPI efi_load_image(bool boot_policy, efi_handle_t parent_image, struct efi_device_path *file_path, diff --git a/lib/efi_loader/efi_net.c b/lib/efi_loader/efi_net.c index aa0618fd3a..91f1e4a69e 100644 --- a/lib/efi_loader/efi_net.c +++ b/lib/efi_loader/efi_net.c @@ -207,7 +207,7 @@ void efi_net_set_dhcp_ack(void *pkt, int len) } /* This gets called from do_bootefi_exec(). */ -int efi_net_register(void **handle) +int efi_net_register(void) { struct efi_net_obj *netobj; @@ -253,8 +253,5 @@ int efi_net_register(void **handle) /* Hook net up to the device list */ list_add_tail(&netobj->parent.link, &efi_obj_list); - if (handle) - *handle = &netobj->net; - return 0; } -- cgit v1.2.3 From 2a92080d8c4463f2ddbf4e3110c25ab55ef6ca3f Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:34 -0400 Subject: efi_loader: add file/filesys support fallback.efi (and probably other things) use UEFI's simple-file-system protocol and file support to search for OS's to boot. Signed-off-by: Rob Clark [agraf: whitespace fixes, unsigned fixes] Signed-off-by: Alexander Graf --- include/efi.h | 2 + include/efi_api.h | 65 +++++ include/efi_loader.h | 13 + lib/efi_loader/Makefile | 1 + lib/efi_loader/efi_disk.c | 32 +++ lib/efi_loader/efi_file.c | 560 ++++++++++++++++++++++++++++++++++++++ lib/efi_loader/efi_image_loader.c | 3 + 7 files changed, 676 insertions(+) create mode 100644 lib/efi_loader/efi_file.c diff --git a/include/efi.h b/include/efi.h index 63cbdb61ec..a432c892cc 100644 --- a/include/efi.h +++ b/include/efi.h @@ -81,6 +81,8 @@ typedef struct { #define EFI_IP_ADDRESS_CONFLICT (EFI_ERROR_MASK | 34) #define EFI_HTTP_ERROR (EFI_ERROR_MASK | 35) +#define EFI_WARN_DELETE_FAILURE 2 + typedef unsigned long efi_status_t; typedef u64 efi_physical_addr_t; typedef u64 efi_virtual_addr_t; diff --git a/include/efi_api.h b/include/efi_api.h index 0c36122107..1aae96355f 100644 --- a/include/efi_api.h +++ b/include/efi_api.h @@ -666,4 +666,69 @@ struct efi_pxe { struct efi_pxe_mode *mode; }; +#define EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID \ + EFI_GUID(0x964e5b22, 0x6459, 0x11d2, \ + 0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b) +#define EFI_FILE_PROTOCOL_REVISION 0x00010000 + +struct efi_file_handle { + u64 rev; + efi_status_t (EFIAPI *open)(struct efi_file_handle *file, + struct efi_file_handle **new_handle, + s16 *file_name, u64 open_mode, u64 attributes); + efi_status_t (EFIAPI *close)(struct efi_file_handle *file); + efi_status_t (EFIAPI *delete)(struct efi_file_handle *file); + efi_status_t (EFIAPI *read)(struct efi_file_handle *file, + u64 *buffer_size, void *buffer); + efi_status_t (EFIAPI *write)(struct efi_file_handle *file, + u64 *buffer_size, void *buffer); + efi_status_t (EFIAPI *getpos)(struct efi_file_handle *file, + u64 *pos); + efi_status_t (EFIAPI *setpos)(struct efi_file_handle *file, + u64 pos); + efi_status_t (EFIAPI *getinfo)(struct efi_file_handle *file, + efi_guid_t *info_type, u64 *buffer_size, void *buffer); + efi_status_t (EFIAPI *setinfo)(struct efi_file_handle *file, + efi_guid_t *info_type, u64 buffer_size, void *buffer); + efi_status_t (EFIAPI *flush)(struct efi_file_handle *file); +}; + +#define EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID \ + EFI_GUID(0x964e5b22, 0x6459, 0x11d2, \ + 0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b) +#define EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION 0x00010000 + +struct efi_simple_file_system_protocol { + u64 rev; + efi_status_t (EFIAPI *open_volume)(struct efi_simple_file_system_protocol *this, + struct efi_file_handle **root); +}; + +#define EFI_FILE_INFO_GUID \ + EFI_GUID(0x9576e92, 0x6d3f, 0x11d2, \ + 0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b) + +#define EFI_FILE_MODE_READ 0x0000000000000001 +#define EFI_FILE_MODE_WRITE 0x0000000000000002 +#define EFI_FILE_MODE_CREATE 0x8000000000000000 + +#define EFI_FILE_READ_ONLY 0x0000000000000001 +#define EFI_FILE_HIDDEN 0x0000000000000002 +#define EFI_FILE_SYSTEM 0x0000000000000004 +#define EFI_FILE_RESERVED 0x0000000000000008 +#define EFI_FILE_DIRECTORY 0x0000000000000010 +#define EFI_FILE_ARCHIVE 0x0000000000000020 +#define EFI_FILE_VALID_ATTR 0x0000000000000037 + +struct efi_file_info { + u64 size; + u64 file_size; + u64 physical_size; + struct efi_time create_time; + struct efi_time last_access_time; + struct efi_time modification_time; + u64 attribute; + s16 file_name[0]; +}; + #endif diff --git a/include/efi_loader.h b/include/efi_loader.h index 8752bcf010..9cb06440a6 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -86,6 +86,8 @@ extern const efi_guid_t efi_guid_console_control; extern const efi_guid_t efi_guid_device_path; extern const efi_guid_t efi_guid_loaded_image; extern const efi_guid_t efi_guid_device_path_to_text_protocol; +extern const efi_guid_t efi_simple_file_system_protocol_guid; +extern const efi_guid_t efi_file_info_guid; extern unsigned int __efi_runtime_start, __efi_runtime_stop; extern unsigned int __efi_runtime_rel_start, __efi_runtime_rel_stop; @@ -163,6 +165,9 @@ int efi_net_register(void); /* Called by bootefi to make SMBIOS tables available */ void efi_smbios_register(void); +struct efi_simple_file_system_protocol * +efi_fs_from_path(struct efi_device_path *fp); + /* Called by networking code to memorize the dhcp ack package */ void efi_net_set_dhcp_ack(void *pkt, int len); @@ -191,6 +196,14 @@ efi_status_t efi_set_timer(struct efi_event *event, enum efi_timer_delay type, /* Call this to signal an event */ void efi_signal_event(struct efi_event *event); +/* open file system: */ +struct efi_simple_file_system_protocol *efi_simple_file_system( + struct blk_desc *desc, int part, struct efi_device_path *dp); + +/* open file from device-path: */ +struct efi_file_handle *efi_file_from_path(struct efi_device_path *fp); + + /* Generic EFI memory allocator, call this to get memory */ void *efi_alloc(uint64_t len, int memory_type); /* More specific EFI memory allocator, called by EFI payloads */ diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile index e68b0dd9a5..00919ceda0 100644 --- a/lib/efi_loader/Makefile +++ b/lib/efi_loader/Makefile @@ -17,6 +17,7 @@ endif obj-$(CONFIG_CMD_BOOTEFI_HELLO) += helloworld_efi.o obj-y += efi_image_loader.o efi_boottime.o efi_runtime.o efi_console.o obj-y += efi_memory.o efi_device_path_to_text.o efi_device_path.o +obj-y += efi_file.o obj-$(CONFIG_LCD) += efi_gop.o obj-$(CONFIG_DM_VIDEO) += efi_gop.o obj-$(CONFIG_PARTITIONS) += efi_disk.o diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c index c815711e57..eb9ce772d1 100644 --- a/lib/efi_loader/efi_disk.c +++ b/lib/efi_loader/efi_disk.c @@ -31,6 +31,8 @@ struct efi_disk_obj { struct efi_device_path *dp; /* partition # */ unsigned int part; + /* handle to filesys proto (for partition objects) */ + struct efi_simple_file_system_protocol *volume; /* Offset into disk for simple partitions */ lbaint_t offset; /* Internal block device */ @@ -172,6 +174,28 @@ static const struct efi_block_io block_io_disk_template = { .flush_blocks = &efi_disk_flush_blocks, }; +/* + * Find filesystem from a device-path. The passed in path 'p' probably + * contains one or more /File(name) nodes, so the comparison stops at + * the first /File() node, and returns the pointer to that via 'rp'. + * This is mostly intended to be a helper to map a device-path to an + * efi_file_handle object. + */ +struct efi_simple_file_system_protocol * +efi_fs_from_path(struct efi_device_path *fp) +{ + struct efi_object *efiobj; + struct efi_disk_obj *diskobj; + + efiobj = efi_dp_find_obj(fp, NULL); + if (!efiobj) + return NULL; + + diskobj = container_of(efiobj, struct efi_disk_obj, parent); + + return diskobj->volume; +} + static void efi_disk_add_dev(const char *name, const char *if_typename, struct blk_desc *desc, @@ -194,6 +218,14 @@ static void efi_disk_add_dev(const char *name, diskobj->parent.protocols[0].protocol_interface = &diskobj->ops; diskobj->parent.protocols[1].guid = &efi_guid_device_path; diskobj->parent.protocols[1].protocol_interface = diskobj->dp; + if (part >= 1) { + diskobj->volume = efi_simple_file_system(desc, part, + diskobj->dp); + diskobj->parent.protocols[2].guid = + &efi_simple_file_system_protocol_guid; + diskobj->parent.protocols[2].protocol_interface = + diskobj->volume; + } diskobj->parent.handle = diskobj; diskobj->ops = block_io_disk_template; diskobj->ifname = if_typename; diff --git a/lib/efi_loader/efi_file.c b/lib/efi_loader/efi_file.c new file mode 100644 index 0000000000..52a4e7438e --- /dev/null +++ b/lib/efi_loader/efi_file.c @@ -0,0 +1,560 @@ +/* + * EFI utils + * + * Copyright (c) 2017 Rob Clark + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include + +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; + + /* 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); +} + +static int is_dir(struct file_handle *fh) +{ + struct fs_dir_stream *dirs; + + set_blk_dev(fh); + 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; +} + +/* NOTE: despite what you would expect, 'file_name' is actually a path. + * With windoze style backlashes, ofc. + */ +static struct efi_file_handle *file_open(struct file_system *fs, + struct file_handle *parent, s16 *file_name, u64 mode) +{ + 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, (u16 *)file_name, 1); + flen = utf16_strlen((u16 *)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->base = efi_file_handle_protocol; + fh->fs = fs; + + if (parent) { + char *p = fh->path; + + if (plen > 0) { + strcpy(p, parent->path); + p += plen - 1; + *p++ = '/'; + } + + utf16_to_utf8((u8 *)p, (u16 *)file_name, flen); + + if (sanitize_path(fh->path)) + goto error; + + /* check if file exists: */ + if (set_blk_dev(fh)) + goto error; + + if (!((mode & EFI_FILE_MODE_CREATE) || fs_exists(fh->path))) + 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, + s16 *file_name, u64 open_mode, u64 attributes) +{ + struct file_handle *fh = to_fh(file); + + EFI_ENTRY("%p, %p, \"%ls\", %llx, %llu", file, new_handle, file_name, + open_mode, attributes); + + *new_handle = file_open(fh->fs, fh, file_name, open_mode); + if (!*new_handle) + return EFI_EXIT(EFI_NOT_FOUND); + + return EFI_EXIT(EFI_SUCCESS); +} + +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_ENTRY("%p", file); + file_close(fh); + return EFI_EXIT(EFI_WARN_DELETE_FAILURE); +} + +static efi_status_t file_read(struct file_handle *fh, u64 *buffer_size, + void *buffer) +{ + loff_t actread; + + if (fs_read(fh->path, (ulong)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; + unsigned int required_size; + + if (!fh->dirs) { + assert(fh->offset == 0); + fh->dirs = fs_opendir(fh->path); + if (!fh->dirs) + return EFI_DEVICE_ERROR; + } + + /* + * 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; + fh->dent = NULL; + } else { + dent = fs_readdir(fh->dirs); + } + + + if (!dent) { + /* no more files in directory: */ + /* workaround shim.efi bug/quirk.. as find_boot_csv() + * loops through directory contents, it initially calls + * read w/ zero length buffer to find out how much mem + * to allocate for the EFI_FILE_INFO, then allocates, + * and then calls a 2nd time. If we return size of + * zero the first time, it happily passes that to + * AllocateZeroPool(), and when that returns NULL it + * thinks it is EFI_OUT_OF_RESOURCES. So on first + * call return a non-zero size: + */ + if (*buffer_size == 0) + *buffer_size = sizeof(*info); + else + *buffer_size = 0; + return EFI_SUCCESS; + } + + /* check buffer size: */ + required_size = sizeof(*info) + 2 * (strlen(dent->name) + 1); + if (*buffer_size < required_size) { + *buffer_size = required_size; + fh->dent = dent; + return EFI_BUFFER_TOO_SMALL; + } + + *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; + + ascii2unicode((u16 *)info->file_name, dent->name); + + fh->offset++; + + return EFI_SUCCESS; +} + +static efi_status_t EFIAPI efi_file_read(struct efi_file_handle *file, + u64 *buffer_size, void *buffer) +{ + struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %p, %p", file, buffer_size, buffer); + + if (set_blk_dev(fh)) { + ret = EFI_DEVICE_ERROR; + goto error; + } + + if (fh->isdir) + ret = dir_read(fh, buffer_size, buffer); + else + ret = file_read(fh, buffer_size, buffer); + +error: + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI efi_file_write(struct efi_file_handle *file, + u64 *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 (set_blk_dev(fh)) { + ret = EFI_DEVICE_ERROR; + goto error; + } + + if (fs_write(fh->path, (ulong)buffer, fh->offset, *buffer_size, + &actwrite)) { + ret = EFI_DEVICE_ERROR; + goto error; + } + + *buffer_size = actwrite; + fh->offset += actwrite; + +error: + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI efi_file_getpos(struct efi_file_handle *file, + u64 *pos) +{ + struct file_handle *fh = to_fh(file); + EFI_ENTRY("%p, %p", file, pos); + *pos = fh->offset; + return EFI_EXIT(EFI_SUCCESS); +} + +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; + + if (set_blk_dev(fh)) { + ret = EFI_DEVICE_ERROR; + goto error; + } + + if (fs_size(fh->path, &file_size)) { + ret = EFI_DEVICE_ERROR; + 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, + efi_guid_t *info_type, u64 *buffer_size, void *buffer) +{ + struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %p, %p, %p", file, info_type, buffer_size, buffer); + + 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 * (strlen(filename) + 1); + if (*buffer_size < required_size) { + *buffer_size = required_size; + ret = EFI_BUFFER_TOO_SMALL; + goto error; + } + + if (set_blk_dev(fh)) { + ret = EFI_DEVICE_ERROR; + goto error; + } + + if (fs_size(fh->path, &file_size)) { + ret = EFI_DEVICE_ERROR; + 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; + + ascii2unicode((u16 *)info->file_name, filename); + } else { + ret = EFI_UNSUPPORTED; + } + +error: + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI efi_file_setinfo(struct efi_file_handle *file, + efi_guid_t *info_type, u64 buffer_size, void *buffer) +{ + EFI_ENTRY("%p, %p, %llu, %p", file, info_type, buffer_size, buffer); + return EFI_EXIT(EFI_UNSUPPORTED); +} + +static efi_status_t EFIAPI efi_file_flush(struct efi_file_handle *file) +{ + EFI_ENTRY("%p", file); + return EFI_EXIT(EFI_SUCCESS); +} + +static const struct efi_file_handle efi_file_handle_protocol = { + .rev = EFI_FILE_PROTOCOL_REVISION, + .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, +}; + +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); + + while (fp) { + struct efi_device_path_file_path *fdp = + container_of(fp, struct efi_device_path_file_path, dp); + struct efi_file_handle *f2; + + if (!EFI_DP_TYPE(fp, MEDIA_DEVICE, FILE_PATH)) { + printf("bad file path!\n"); + f->close(f); + return NULL; + } + + EFI_CALL(ret = f->open(f, &f2, (s16 *)fdp->str, + EFI_FILE_MODE_READ, 0)); + 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); + + 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; +} diff --git a/lib/efi_loader/efi_image_loader.c b/lib/efi_loader/efi_image_loader.c index f961407f50..469acae082 100644 --- a/lib/efi_loader/efi_image_loader.c +++ b/lib/efi_loader/efi_image_loader.c @@ -17,6 +17,9 @@ DECLARE_GLOBAL_DATA_PTR; const efi_guid_t efi_guid_device_path = DEVICE_PATH_GUID; const efi_guid_t efi_guid_loaded_image = LOADED_IMAGE_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 efi_status_t efi_loader_relocate(const IMAGE_BASE_RELOCATION *rel, unsigned long rel_size, void *efi_reloc) -- cgit v1.2.3 From 838ee4b4798629ed88254e431cf17d4955b793bf Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:35 -0400 Subject: efi_loader: support load_image() from a file-path Previously we only supported the case when the EFI application loaded the image into memory for us. But fallback.efi does not do this. Signed-off-by: Rob Clark Signed-off-by: Alexander Graf --- lib/efi_loader/efi_boottime.c | 85 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 38ae80b116..f01b80598c 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -813,6 +813,47 @@ void efi_setup_loaded_image(struct efi_loaded_image *info, struct efi_object *ob list_add_tail(&obj->link, &efi_obj_list); } +static efi_status_t load_image_from_path(struct efi_device_path *file_path, + void **buffer) +{ + struct efi_file_info *info = NULL; + struct efi_file_handle *f; + static efi_status_t ret; + uint64_t bs; + + f = efi_file_from_path(file_path); + if (!f) + return EFI_DEVICE_ERROR; + + bs = 0; + EFI_CALL(ret = f->getinfo(f, (efi_guid_t *)&efi_file_info_guid, + &bs, info)); + if (ret == EFI_BUFFER_TOO_SMALL) { + info = malloc(bs); + EFI_CALL(ret = f->getinfo(f, (efi_guid_t *)&efi_file_info_guid, + &bs, info)); + } + if (ret != EFI_SUCCESS) + goto error; + + ret = efi_allocate_pool(EFI_LOADER_DATA, info->file_size, buffer); + if (ret) + goto error; + + EFI_CALL(ret = f->read(f, &info->file_size, *buffer)); + +error: + free(info); + EFI_CALL(f->close(f)); + + if (ret != EFI_SUCCESS) { + efi_free_pool(*buffer); + *buffer = NULL; + } + + return ret; +} + static efi_status_t EFIAPI efi_load_image(bool boot_policy, efi_handle_t parent_image, struct efi_device_path *file_path, @@ -820,25 +861,40 @@ static efi_status_t EFIAPI efi_load_image(bool boot_policy, unsigned long source_size, efi_handle_t *image_handle) { - static struct efi_object loaded_image_info_obj = { - .protocols = { - { - .guid = &efi_guid_loaded_image, - }, - }, - }; struct efi_loaded_image *info; struct efi_object *obj; EFI_ENTRY("%d, %p, %p, %p, %ld, %p", boot_policy, parent_image, file_path, source_buffer, source_size, image_handle); - info = malloc(sizeof(*info)); - loaded_image_info_obj.protocols[0].protocol_interface = info; - obj = malloc(sizeof(loaded_image_info_obj)); - memset(info, 0, sizeof(*info)); - memcpy(obj, &loaded_image_info_obj, sizeof(loaded_image_info_obj)); - obj->handle = info; - info->file_path = file_path; + + info = calloc(1, sizeof(*info)); + obj = calloc(1, sizeof(*obj)); + + if (!source_buffer) { + struct efi_device_path *dp, *fp; + efi_status_t ret; + + ret = load_image_from_path(file_path, &source_buffer); + if (ret != EFI_SUCCESS) { + free(info); + free(obj); + return EFI_EXIT(ret); + } + + /* + * split file_path which contains both the device and + * file parts: + */ + efi_dp_split_file_path(file_path, &dp, &fp); + + efi_setup_loaded_image(info, obj, dp, fp); + } else { + /* In this case, file_path is the "device" path, ie. + * something like a HARDWARE_DEVICE:MEMORY_MAPPED + */ + efi_setup_loaded_image(info, obj, file_path, NULL); + } + info->reserved = efi_load_pe(source_buffer, info); if (!info->reserved) { free(info); @@ -847,7 +903,6 @@ static efi_status_t EFIAPI efi_load_image(bool boot_policy, } *image_handle = info; - list_add_tail(&obj->link, &efi_obj_list); return EFI_EXIT(EFI_SUCCESS); } -- cgit v1.2.3 From 946160f334ddaf2dd0dfd4bc72485f9ce39dc2cd Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:36 -0400 Subject: efi_loader: make pool allocations cacheline aligned This avoids printf() spam about file reads (such as loading an image) into unaligned buffers (and the associated memcpy()). And generally seems like a good idea. Signed-off-by: Rob Clark [agraf: use __aligned] Signed-off-by: Alexander Graf --- lib/efi_loader/efi_memory.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c index 9e079f1fa3..d47759e08e 100644 --- a/lib/efi_loader/efi_memory.c +++ b/lib/efi_loader/efi_memory.c @@ -43,7 +43,7 @@ void *efi_bounce_buffer; */ struct efi_pool_allocation { u64 num_pages; - char data[]; + char data[] __aligned(ARCH_DMA_MINALIGN); }; /* @@ -356,7 +356,8 @@ efi_status_t efi_allocate_pool(int pool_type, unsigned long size, { efi_status_t r; efi_physical_addr_t t; - u64 num_pages = (size + sizeof(u64) + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT; + u64 num_pages = (size + sizeof(struct efi_pool_allocation) + + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT; if (size == 0) { *buffer = NULL; -- cgit v1.2.3 From ad644e7c18238026fecc647f62584d87d2c5b0a3 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:37 -0400 Subject: efi_loader: efi variable support Add EFI variable support, mapping to u-boot environment variables. Variables are pretty important for setting up boot order, among other things. If the board supports saveenv, then it will be called in ExitBootServices() to persist variables set by the efi payload. (For example, fallback.efi configuring BootOrder and BootXXXX load-option variables.) Variables are *not* currently exposed at runtime, post ExitBootServices. On boards without a dedicated device for storage, which the loaded OS is not trying to also use, this is rather tricky. One idea, at least for boards that can persist RAM across reboot, is to keep a "journal" of modified variables in RAM, and then turn halt into a reboot into u-boot, plus store variables, plus halt. Whatever the solution, it likely involves some per-board support. 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 Signed-off-by: Rob Clark Signed-off-by: Alexander Graf --- cmd/bootefi.c | 4 + include/efi.h | 19 +++ include/efi_loader.h | 10 ++ lib/efi_loader/Makefile | 2 +- lib/efi_loader/efi_boottime.c | 6 + lib/efi_loader/efi_runtime.c | 17 ++- lib/efi_loader/efi_variable.c | 335 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 388 insertions(+), 5 deletions(-) create mode 100644 lib/efi_loader/efi_variable.c diff --git a/cmd/bootefi.c b/cmd/bootefi.c index c1ef1b542b..9f844737e5 100644 --- a/cmd/bootefi.c +++ b/cmd/bootefi.c @@ -181,6 +181,10 @@ static unsigned long do_bootefi_exec(void *efi, void *fdt, goto exit; } + /* we don't support much: */ + env_set("efi_8be4df61-93ca-11d2-aa0d-00e098032b8c_OsIndicationsSupported", + "{ro,boot}(blob)0000000000000000"); + /* Call our payload! */ debug("%s:%d Jumping to 0x%lx\n", __func__, __LINE__, (long)entry); diff --git a/include/efi.h b/include/efi.h index a432c892cc..dc8edc8743 100644 --- a/include/efi.h +++ b/include/efi.h @@ -324,6 +324,25 @@ extern char image_base[]; /* Start and end of U-Boot image (for payload) */ extern char _binary_u_boot_bin_start[], _binary_u_boot_bin_end[]; +/* + * Variable Attributes + */ +#define EFI_VARIABLE_NON_VOLATILE 0x0000000000000001 +#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x0000000000000002 +#define EFI_VARIABLE_RUNTIME_ACCESS 0x0000000000000004 +#define EFI_VARIABLE_HARDWARE_ERROR_RECORD 0x0000000000000008 +#define EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS 0x0000000000000010 +#define EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS 0x0000000000000020 +#define EFI_VARIABLE_APPEND_WRITE 0x0000000000000040 + +#define EFI_VARIABLE_MASK (EFI_VARIABLE_NON_VOLATILE | \ + EFI_VARIABLE_BOOTSERVICE_ACCESS | \ + EFI_VARIABLE_RUNTIME_ACCESS | \ + EFI_VARIABLE_HARDWARE_ERROR_RECORD | \ + EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS | \ + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | \ + EFI_VARIABLE_APPEND_WRITE) + /** * efi_get_sys_table() - Get access to the main EFI system table * diff --git a/include/efi_loader.h b/include/efi_loader.h index 9cb06440a6..d8ea870eb7 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -309,6 +309,16 @@ efi_status_t EFIAPI efi_selftest(efi_handle_t image_handle, struct efi_system_table *systab); #endif +efi_status_t EFIAPI efi_get_variable(s16 *variable_name, + efi_guid_t *vendor, u32 *attributes, + unsigned long *data_size, void *data); +efi_status_t EFIAPI efi_get_next_variable( + unsigned long *variable_name_size, + s16 *variable_name, efi_guid_t *vendor); +efi_status_t EFIAPI efi_set_variable(s16 *variable_name, + efi_guid_t *vendor, u32 attributes, + unsigned long data_size, void *data); + #else /* defined(EFI_LOADER) && !defined(CONFIG_SPL_BUILD) */ /* Without CONFIG_EFI_LOADER we don't have a runtime section, stub it out */ diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile index 00919ceda0..301bf7b507 100644 --- a/lib/efi_loader/Makefile +++ b/lib/efi_loader/Makefile @@ -17,7 +17,7 @@ endif obj-$(CONFIG_CMD_BOOTEFI_HELLO) += helloworld_efi.o obj-y += efi_image_loader.o efi_boottime.o efi_runtime.o efi_console.o obj-y += efi_memory.o efi_device_path_to_text.o efi_device_path.o -obj-y += efi_file.o +obj-y += efi_file.o efi_variable.o obj-$(CONFIG_LCD) += efi_gop.o obj-$(CONFIG_DM_VIDEO) += efi_gop.o obj-$(CONFIG_PARTITIONS) += efi_disk.o diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index f01b80598c..90f0051484 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -1013,6 +1014,11 @@ static efi_status_t EFIAPI efi_exit_boot_services(void *image_handle, /* Make sure that notification functions are not called anymore */ efi_tpl = TPL_HIGH_LEVEL; +#if defined(CONFIG_CMD_SAVEENV) && !defined(CONFIG_ENV_IS_NOWHERE) + /* save any EFI variables that have been written: */ + env_save(); +#endif + board_quiesce_devices(); /* Fix up caches for EFI payloads if necessary */ diff --git a/lib/efi_loader/efi_runtime.c b/lib/efi_loader/efi_runtime.c index 47b03763c8..8104e08c46 100644 --- a/lib/efi_loader/efi_runtime.c +++ b/lib/efi_loader/efi_runtime.c @@ -184,7 +184,16 @@ static const struct efi_runtime_detach_list_struct efi_runtime_detach_list[] = { /* Clean up system table */ .ptr = &systab.boottime, .patchto = NULL, - }, + }, { + .ptr = &efi_runtime_services.get_variable, + .patchto = &efi_device_error, + }, { + .ptr = &efi_runtime_services.get_next_variable, + .patchto = &efi_device_error, + }, { + .ptr = &efi_runtime_services.set_variable, + .patchto = &efi_device_error, + } }; static bool efi_runtime_tobedetached(void *p) @@ -384,9 +393,9 @@ struct efi_runtime_services __efi_runtime_data efi_runtime_services = { .set_wakeup_time = (void *)&efi_unimplemented, .set_virtual_address_map = &efi_set_virtual_address_map, .convert_pointer = (void *)&efi_invalid_parameter, - .get_variable = (void *)&efi_device_error, - .get_next_variable = (void *)&efi_device_error, - .set_variable = (void *)&efi_device_error, + .get_variable = efi_get_variable, + .get_next_variable = efi_get_next_variable, + .set_variable = efi_set_variable, .get_next_high_mono_count = (void *)&efi_device_error, .reset_system = &efi_reset_system_boottime, }; diff --git a/lib/efi_loader/efi_variable.c b/lib/efi_loader/efi_variable.c new file mode 100644 index 0000000000..5569b3d3f0 --- /dev/null +++ b/lib/efi_loader/efi_variable.c @@ -0,0 +1,335 @@ +/* + * EFI utils + * + * Copyright (c) 2017 Rob Clark + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include + +#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 MAX_VAR_NAME 31 +#define MAX_NATIVE_VAR_NAME \ + (strlen("efi_xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx_") + \ + (MAX_VAR_NAME * MAX_UTF8_PER_UTF16)) + +static int hex(unsigned char ch) +{ + if (ch >= 'a' && ch <= 'f') + return ch-'a'+10; + if (ch >= '0' && ch <= '9') + return ch-'0'; + if (ch >= 'A' && ch <= 'F') + return ch-'A'+10; + return -1; +} + +static const char *hex2mem(u8 *mem, const char *hexstr, int count) +{ + memset(mem, 0, count/2); + + do { + int nibble; + + *mem = 0; + + if (!count || !*hexstr) + break; + + nibble = hex(*hexstr); + if (nibble < 0) + break; + + *mem = nibble; + count--; + hexstr++; + + if (!count || !*hexstr) + break; + + nibble = hex(*hexstr); + if (nibble < 0) + break; + + *mem = (*mem << 4) | nibble; + count--; + hexstr++; + mem++; + + } while (1); + + if (*hexstr) + return hexstr; + + return NULL; +} + +static char *mem2hex(char *hexstr, const u8 *mem, int count) +{ + static const char hexchars[] = "0123456789abcdef"; + + while (count-- > 0) { + u8 ch = *mem++; + *hexstr++ = hexchars[ch >> 4]; + *hexstr++ = hexchars[ch & 0xf]; + } + + return hexstr; +} + +static efi_status_t efi_to_native(char *native, s16 *variable_name, + efi_guid_t *vendor) +{ + size_t len; + + len = utf16_strlen((u16 *)variable_name); + if (len >= MAX_VAR_NAME) + return EFI_DEVICE_ERROR; + + native += sprintf(native, "efi_%pUl_", vendor); + native = (char *)utf16_to_utf8((u8 *)native, (u16 *)variable_name, len); + *native = '\0'; + + return EFI_SUCCESS; +} + +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 attributes part of variable value, if present: */ +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, "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; +} + +/* http://wiki.phoenix.com/wiki/index.php/EFI_RUNTIME_SERVICES#GetVariable.28.29 */ +efi_status_t EFIAPI efi_get_variable(s16 *variable_name, + efi_guid_t *vendor, u32 *attributes, + unsigned long *data_size, void *data) +{ + char native_name[MAX_NATIVE_VAR_NAME + 1]; + efi_status_t ret; + unsigned long in_size; + const char *val, *s; + u32 attr; + + EFI_ENTRY("%p %p %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); + + debug("%s: get '%s'\n", __func__, native_name); + + val = env_get(native_name); + if (!val) + return EFI_EXIT(EFI_NOT_FOUND); + + val = parse_attr(val, &attr); + + in_size = *data_size; + + if ((s = prefix(val, "(blob)"))) { + unsigned len = strlen(s); + + /* two characters per byte: */ + len = DIV_ROUND_UP(len, 2); + *data_size = len; + + if (in_size < len) + return EFI_EXIT(EFI_BUFFER_TOO_SMALL); + + if (!data) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + if (hex2mem(data, s, len * 2)) + return EFI_EXIT(EFI_DEVICE_ERROR); + + debug("%s: got value: \"%s\"\n", __func__, s); + } else if ((s = prefix(val, "(utf8)"))) { + unsigned len = strlen(s) + 1; + + *data_size = len; + + if (in_size < len) + return EFI_EXIT(EFI_BUFFER_TOO_SMALL); + + if (!data) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + memcpy(data, s, len); + ((char *)data)[len] = '\0'; + + debug("%s: got value: \"%s\"\n", __func__, (char *)data); + } else { + debug("%s: invalid value: '%s'\n", __func__, val); + return EFI_EXIT(EFI_DEVICE_ERROR); + } + + if (attributes) + *attributes = attr & EFI_VARIABLE_MASK; + + return EFI_EXIT(EFI_SUCCESS); +} + +/* http://wiki.phoenix.com/wiki/index.php/EFI_RUNTIME_SERVICES#GetNextVariableName.28.29 */ +efi_status_t EFIAPI efi_get_next_variable( + unsigned long *variable_name_size, + s16 *variable_name, efi_guid_t *vendor) +{ + EFI_ENTRY("%p %p %p", variable_name_size, variable_name, vendor); + + return EFI_EXIT(EFI_DEVICE_ERROR); +} + +/* http://wiki.phoenix.com/wiki/index.php/EFI_RUNTIME_SERVICES#SetVariable.28.29 */ +efi_status_t EFIAPI efi_set_variable(s16 *variable_name, + efi_guid_t *vendor, u32 attributes, + unsigned long data_size, void *data) +{ + char native_name[MAX_NATIVE_VAR_NAME + 1]; + efi_status_t ret = EFI_SUCCESS; + char *val, *s; + u32 attr; + + EFI_ENTRY("%p %p %x %lu %p", variable_name, vendor, attributes, + data_size, data); + + if (!variable_name || !vendor) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + ret = efi_to_native(native_name, variable_name, vendor); + if (ret) + return EFI_EXIT(ret); + +#define ACCESS_ATTR (EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS) + + if ((data_size == 0) || !(attributes & ACCESS_ATTR)) { + /* delete the variable: */ + env_set(native_name, NULL); + return EFI_EXIT(EFI_SUCCESS); + } + + val = env_get(native_name); + if (val) { + parse_attr(val, &attr); + + if (attr & READ_ONLY) + return EFI_EXIT(EFI_WRITE_PROTECTED); + } + + val = malloc(2 * data_size + strlen("{ro,run,boot}(blob)") + 1); + if (!val) + return EFI_EXIT(EFI_OUT_OF_RESOURCES); + + s = val; + + /* store attributes: */ + attributes &= (EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS); + s += sprintf(s, "{"); + while (attributes) { + u32 attr = 1 << (ffs(attributes) - 1); + + 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, "}"); + + /* store payload: */ + s += sprintf(s, "(blob)"); + s = mem2hex(s, data, data_size); + *s = '\0'; + + debug("%s: setting: %s=%s\n", __func__, native_name, val); + + if (env_set(native_name, val)) + ret = EFI_DEVICE_ERROR; + + free(val); + + return EFI_EXIT(ret); +} -- cgit v1.2.3 From 9975fe96b697b6d64bde3f77cd08612fec812b47 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:38 -0400 Subject: efi_loader: add bootmgr Similar to a "real" UEFI implementation, the bootmgr looks at the BootOrder and BootXXXX variables to try to find an EFI payload to load and boot. This is added as a sub-command of bootefi. The idea is that the distro bootcmd would first try loading a payload via the bootmgr, and then if that fails (ie. first boot or corrupted EFI variables) it would fallback to loading bootaa64.efi. (Which would then load fallback.efi which would look for \EFI\*\boot.csv and populate BootOrder and BootXXXX based on what it found.) Signed-off-by: Rob Clark Signed-off-by: Alexander Graf --- cmd/bootefi.c | 49 ++++++++++- include/config_distro_bootcmd.h | 5 ++ include/efi_api.h | 4 + include/efi_loader.h | 6 ++ lib/efi_loader/Makefile | 2 +- lib/efi_loader/efi_bootmgr.c | 180 ++++++++++++++++++++++++++++++++++++++ lib/efi_loader/efi_boottime.c | 6 +- lib/efi_loader/efi_image_loader.c | 1 + 8 files changed, 247 insertions(+), 6 deletions(-) create mode 100644 lib/efi_loader/efi_bootmgr.c diff --git a/cmd/bootefi.c b/cmd/bootefi.c index 9f844737e5..1e2dbcc4a4 100644 --- a/cmd/bootefi.c +++ b/cmd/bootefi.c @@ -219,6 +219,37 @@ exit: return ret; } +static int do_bootefi_bootmgr_exec(unsigned long fdt_addr) +{ + struct efi_device_path *device_path, *file_path; + void *addr; + efi_status_t r; + + /* Initialize and populate EFI object list */ + if (!efi_obj_list_initalized) + efi_init_obj_list(); + + /* + * gd lives in a fixed register which may get clobbered while we execute + * the payload. So save it here and restore it on every callback entry + */ + efi_save_gd(); + + addr = efi_bootmgr_load(&device_path, &file_path); + if (!addr) + return 1; + + printf("## Starting EFI application at %p ...\n", addr); + r = do_bootefi_exec(addr, (void *)fdt_addr, device_path, file_path); + printf("## Application terminated, r = %lu\n", + r & ~EFI_ERROR_MASK); + + if (r != EFI_SUCCESS) + return 1; + + return 0; +} + /* Interpreter command to boot an arbitrary EFI image from memory */ static int do_bootefi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { @@ -256,7 +287,14 @@ static int do_bootefi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) return efi_selftest(&loaded_image_info, &systab); } else #endif - { + if (!strcmp(argv[1], "bootmgr")) { + unsigned long fdt_addr = 0; + + if (argc > 2) + fdt_addr = simple_strtoul(argv[2], NULL, 16); + + return do_bootefi_bootmgr_exec(fdt_addr); + } else { saddr = argv[1]; addr = simple_strtoul(saddr, NULL, 16); @@ -293,7 +331,11 @@ static char bootefi_help_text[] = "bootefi selftest\n" " - boot an EFI selftest application stored within U-Boot\n" #endif - ; + "bootmgr [fdt addr]\n" + " - load and boot EFI payload based on BootOrder/BootXXXX variables.\n" + "\n" + " If specified, the device tree located at gets\n" + " exposed as EFI configuration table.\n"; #endif U_BOOT_CMD( @@ -331,6 +373,9 @@ void efi_set_bootdev(const char *dev, const char *devnr, const char *path) #endif } + if (!path) + return; + if (strcmp(dev, "Net")) { /* Add leading / to fs paths, because they're absolute */ snprintf(filename, sizeof(filename), "/%s", path); diff --git a/include/config_distro_bootcmd.h b/include/config_distro_bootcmd.h index 9ed6b9892c..e0d0034ed3 100644 --- a/include/config_distro_bootcmd.h +++ b/include/config_distro_bootcmd.h @@ -112,6 +112,11 @@ #define BOOTENV_SHARED_EFI \ "boot_efi_binary=" \ + "if fdt addr ${fdt_addr_r}; then " \ + "bootefi bootmgr ${fdt_addr_r};" \ + "else " \ + "bootefi bootmgr ${fdtcontroladdr};" \ + "fi;" \ "load ${devtype} ${devnum}:${distro_bootpart} " \ "${kernel_addr_r} efi/boot/"BOOTEFI_NAME"; " \ "if fdt addr ${fdt_addr_r}; then " \ diff --git a/include/efi_api.h b/include/efi_api.h index 1aae96355f..d0aefa8221 100644 --- a/include/efi_api.h +++ b/include/efi_api.h @@ -211,6 +211,10 @@ struct efi_runtime_services { EFI_GUID(0x00000000, 0x0000, 0x0000, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) +#define EFI_GLOBAL_VARIABLE_GUID \ + EFI_GUID(0x8be4df61, 0x93ca, 0x11d2, 0xaa, 0x0d, \ + 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c) + #define LOADED_IMAGE_PROTOCOL_GUID \ EFI_GUID(0x5b1b31a1, 0x9562, 0x11d2, 0x8e, 0x3f, \ 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b) diff --git a/include/efi_loader.h b/include/efi_loader.h index d8ea870eb7..2f081f8996 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -82,6 +82,7 @@ extern const struct efi_device_path_to_text_protocol efi_device_path_to_text; uint16_t *efi_dp_str(struct efi_device_path *dp); +extern const efi_guid_t efi_global_variable_guid; extern const efi_guid_t efi_guid_console_control; extern const efi_guid_t efi_guid_device_path; extern const efi_guid_t efi_guid_loaded_image; @@ -232,6 +233,8 @@ efi_status_t efi_install_configuration_table(const efi_guid_t *guid, void *table void efi_setup_loaded_image(struct efi_loaded_image *info, struct efi_object *obj, struct efi_device_path *device_path, struct efi_device_path *file_path); +efi_status_t efi_load_image_from_path(struct efi_device_path *file_path, + void **buffer); #ifdef CONFIG_EFI_LOADER_BOUNCE_BUFFER extern void *efi_bounce_buffer; @@ -319,6 +322,9 @@ efi_status_t EFIAPI efi_set_variable(s16 *variable_name, efi_guid_t *vendor, u32 attributes, unsigned long data_size, void *data); +void *efi_bootmgr_load(struct efi_device_path **device_path, + struct efi_device_path **file_path); + #else /* defined(EFI_LOADER) && !defined(CONFIG_SPL_BUILD) */ /* Without CONFIG_EFI_LOADER we don't have a runtime section, stub it out */ diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile index 301bf7b507..ddb978f650 100644 --- a/lib/efi_loader/Makefile +++ b/lib/efi_loader/Makefile @@ -17,7 +17,7 @@ endif obj-$(CONFIG_CMD_BOOTEFI_HELLO) += helloworld_efi.o obj-y += efi_image_loader.o efi_boottime.o efi_runtime.o efi_console.o obj-y += efi_memory.o efi_device_path_to_text.o efi_device_path.o -obj-y += efi_file.o efi_variable.o +obj-y += efi_file.o efi_variable.o efi_bootmgr.o obj-$(CONFIG_LCD) += efi_gop.o obj-$(CONFIG_DM_VIDEO) += efi_gop.o obj-$(CONFIG_PARTITIONS) += efi_disk.o diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c new file mode 100644 index 0000000000..857d88a879 --- /dev/null +++ b/lib/efi_loader/efi_bootmgr.c @@ -0,0 +1,180 @@ +/* + * EFI utils + * + * Copyright (c) 2017 Rob Clark + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include + +static const struct efi_boot_services *bs; +static const struct efi_runtime_services *rs; + +#define LOAD_OPTION_ACTIVE 0x00000001 +#define LOAD_OPTION_FORCE_RECONNECT 0x00000002 +#define LOAD_OPTION_HIDDEN 0x00000008 + +/* + * 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. + */ + + +/* + * See section 3.1.3 in the v2.7 UEFI spec for more details on + * the layout of EFI_LOAD_OPTION. In short it is: + * + * typedef struct _EFI_LOAD_OPTION { + * UINT32 Attributes; + * UINT16 FilePathListLength; + * // CHAR16 Description[]; <-- variable length, NULL terminated + * // EFI_DEVICE_PATH_PROTOCOL FilePathList[]; <-- FilePathListLength bytes + * // UINT8 OptionalData[]; + * } EFI_LOAD_OPTION; + */ +struct load_option { + u32 attributes; + u16 file_path_length; + u16 *label; + struct efi_device_path *file_path; + u8 *optional_data; +}; + +/* parse an EFI_LOAD_OPTION, as described above */ +static void parse_load_option(struct load_option *lo, void *ptr) +{ + lo->attributes = *(u32 *)ptr; + ptr += sizeof(u32); + + lo->file_path_length = *(u16 *)ptr; + ptr += sizeof(u16); + + lo->label = ptr; + ptr += (utf16_strlen(lo->label) + 1) * 2; + + lo->file_path = ptr; + ptr += lo->file_path_length; + + lo->optional_data = ptr; +} + +/* free() the result */ +static void *get_var(u16 *name, const efi_guid_t *vendor, + unsigned long *size) +{ + efi_guid_t *v = (efi_guid_t *)vendor; + efi_status_t ret; + void *buf = NULL; + + *size = 0; + EFI_CALL(ret = rs->get_variable((s16 *)name, v, NULL, size, buf)); + if (ret == EFI_BUFFER_TOO_SMALL) { + buf = malloc(*size); + EFI_CALL(ret = rs->get_variable((s16 *)name, v, NULL, size, buf)); + } + + if (ret != EFI_SUCCESS) { + free(buf); + *size = 0; + return NULL; + } + + return buf; +} + +/* + * 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. + */ +static void *try_load_entry(uint16_t n, struct efi_device_path **device_path, + struct efi_device_path **file_path) +{ + struct load_option lo; + u16 varname[] = L"Boot0000"; + u16 hexmap[] = L"0123456789ABCDEF"; + void *load_option, *image = NULL; + unsigned long size; + + 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 NULL; + + parse_load_option(&lo, load_option); + + if (lo.attributes & LOAD_OPTION_ACTIVE) { + efi_status_t ret; + u16 *str = NULL; + + debug("%s: trying to load \"%ls\" from: %ls\n", __func__, + lo.label, (str = efi_dp_str(lo.file_path))); + efi_free_pool(str); + + ret = efi_load_image_from_path(lo.file_path, &image); + + if (ret != EFI_SUCCESS) + goto error; + + printf("Booting: %ls\n", lo.label); + efi_dp_split_file_path(lo.file_path, device_path, file_path); + } + +error: + free(load_option); + + return image; +} + +/* + * Attempt to load, in the order specified by BootOrder EFI variable, the + * available load-options, finding and returning the first one that can + * be loaded successfully. + */ +void *efi_bootmgr_load(struct efi_device_path **device_path, + struct efi_device_path **file_path) +{ + uint16_t *bootorder; + unsigned long size; + void *image = NULL; + int i, num; + + __efi_entry_check(); + + bs = systab.boottime; + rs = systab.runtime; + + bootorder = get_var(L"BootOrder", &efi_global_variable_guid, &size); + if (!bootorder) + goto error; + + num = size / sizeof(uint16_t); + for (i = 0; i < num; i++) { + debug("%s: trying to load Boot%04X\n", __func__, bootorder[i]); + image = try_load_entry(bootorder[i], device_path, file_path); + if (image) + break; + } + + free(bootorder); + +error: + __efi_exit_check(); + + return image; +} diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 90f0051484..f97b0ca4ab 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -814,8 +814,8 @@ void efi_setup_loaded_image(struct efi_loaded_image *info, struct efi_object *ob list_add_tail(&obj->link, &efi_obj_list); } -static efi_status_t load_image_from_path(struct efi_device_path *file_path, - void **buffer) +efi_status_t efi_load_image_from_path(struct efi_device_path *file_path, + void **buffer) { struct efi_file_info *info = NULL; struct efi_file_handle *f; @@ -875,7 +875,7 @@ static efi_status_t EFIAPI efi_load_image(bool boot_policy, struct efi_device_path *dp, *fp; efi_status_t ret; - ret = load_image_from_path(file_path, &source_buffer); + ret = efi_load_image_from_path(file_path, &source_buffer); if (ret != EFI_SUCCESS) { free(info); free(obj); diff --git a/lib/efi_loader/efi_image_loader.c b/lib/efi_loader/efi_image_loader.c index 469acae082..242e6a504b 100644 --- a/lib/efi_loader/efi_image_loader.c +++ b/lib/efi_loader/efi_image_loader.c @@ -15,6 +15,7 @@ DECLARE_GLOBAL_DATA_PTR; +const efi_guid_t efi_global_variable_guid = EFI_GLOBAL_VARIABLE_GUID; const efi_guid_t efi_guid_device_path = DEVICE_PATH_GUID; const efi_guid_t efi_guid_loaded_image = LOADED_IMAGE_GUID; const efi_guid_t efi_simple_file_system_protocol_guid = -- cgit v1.2.3 From 61b7e22479880f57140dd74879996c83577aa63d Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:39 -0400 Subject: efi_loader: file_path should be variable length Signed-off-by: Rob Clark Signed-off-by: Alexander Graf --- include/efi_api.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/efi_api.h b/include/efi_api.h index d0aefa8221..604c5b7ec4 100644 --- a/include/efi_api.h +++ b/include/efi_api.h @@ -373,7 +373,7 @@ struct efi_device_path_cdrom_path { struct efi_device_path_file_path { struct efi_device_path dp; - u16 str[32]; + u16 str[]; } __packed; #define BLOCK_IO_GUID \ -- cgit v1.2.3 From 4657a3f1acb2f5f725b13d0f4f9e41cee1dcdc8d Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:40 -0400 Subject: efi_loader: set loaded image code/data type properly These should be set according to the image type. Shell.efi and SCT.efi use these fields to determine what sort of image they are loading. Signed-off-by: Rob Clark Signed-off-by: Alexander Graf --- include/pe.h | 6 ++++++ lib/efi_loader/efi_image_loader.c | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/include/pe.h b/include/pe.h index deb35a0ea4..4ef3e92efa 100644 --- a/include/pe.h +++ b/include/pe.h @@ -62,6 +62,12 @@ typedef struct _IMAGE_DATA_DIRECTORY { #define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 +/* PE32+ Subsystem type for EFI images */ +#define IMAGE_SUBSYSTEM_EFI_APPLICATION 10 +#define IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER 11 +#define IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER 12 +#define IMAGE_SUBSYSTEM_SAL_RUNTIME_DRIVER 13 + typedef struct _IMAGE_OPTIONAL_HEADER64 { uint16_t Magic; /* 0x20b */ uint8_t MajorLinkerVersion; diff --git a/lib/efi_loader/efi_image_loader.c b/lib/efi_loader/efi_image_loader.c index 242e6a504b..af29cc4f04 100644 --- a/lib/efi_loader/efi_image_loader.c +++ b/lib/efi_loader/efi_image_loader.c @@ -94,6 +94,7 @@ void *efi_load_pe(void *efi, struct efi_loaded_image *loaded_image_info) unsigned long virt_size = 0; bool can_run_nt64 = true; bool can_run_nt32 = true; + uint16_t image_type; #if defined(CONFIG_ARM64) can_run_nt32 = false; @@ -139,6 +140,7 @@ void *efi_load_pe(void *efi, struct efi_loaded_image *loaded_image_info) entry = efi_reloc + opt->AddressOfEntryPoint; rel_size = opt->DataDirectory[rel_idx].Size; rel = efi_reloc + opt->DataDirectory[rel_idx].VirtualAddress; + image_type = opt->Subsystem; } else if (can_run_nt32 && (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)) { IMAGE_OPTIONAL_HEADER32 *opt = &nt->OptionalHeader; @@ -152,12 +154,32 @@ void *efi_load_pe(void *efi, struct efi_loaded_image *loaded_image_info) entry = efi_reloc + opt->AddressOfEntryPoint; rel_size = opt->DataDirectory[rel_idx].Size; rel = efi_reloc + opt->DataDirectory[rel_idx].VirtualAddress; + image_type = opt->Subsystem; } else { printf("%s: Invalid optional header magic %x\n", __func__, nt->OptionalHeader.Magic); return NULL; } + 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_SAL_RUNTIME_DRIVER: + 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); + break; + } + /* Load sections into RAM */ for (i = num_sections - 1; i >= 0; i--) { IMAGE_SECTION_HEADER *sec = §ions[i]; -- cgit v1.2.3 From 778e6af8a6e78f8754aaf5d9b80a51a267da10c7 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:41 -0400 Subject: efi_loader: print GUIDs Utilize printf GUID support to print GUIDs. Signed-off-by: Rob Clark Signed-off-by: Alexander Graf --- lib/efi_loader/efi_boottime.c | 22 +++++++++++----------- lib/efi_loader/efi_variable.c | 6 +++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index f97b0ca4ab..9e741c3cf3 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -565,7 +565,7 @@ static efi_status_t EFIAPI efi_install_protocol_interface_ext(void **handle, efi_guid_t *protocol, int protocol_interface_type, void *protocol_interface) { - EFI_ENTRY("%p, %p, %d, %p", handle, protocol, protocol_interface_type, + EFI_ENTRY("%p, %pUl, %d, %p", handle, protocol, protocol_interface_type, protocol_interface); return EFI_EXIT(efi_install_protocol_interface(handle, protocol, @@ -577,7 +577,7 @@ static efi_status_t EFIAPI efi_reinstall_protocol_interface(void *handle, efi_guid_t *protocol, void *old_interface, void *new_interface) { - EFI_ENTRY("%p, %p, %p, %p", handle, protocol, old_interface, + EFI_ENTRY("%p, %pUl, %p, %p", handle, protocol, old_interface, new_interface); return EFI_EXIT(EFI_ACCESS_DENIED); } @@ -626,7 +626,7 @@ out: static efi_status_t EFIAPI efi_uninstall_protocol_interface_ext(void *handle, efi_guid_t *protocol, void *protocol_interface) { - EFI_ENTRY("%p, %p, %p", handle, protocol, protocol_interface); + EFI_ENTRY("%p, %pUl, %p", handle, protocol, protocol_interface); return EFI_EXIT(efi_uninstall_protocol_interface(handle, protocol, protocol_interface)); @@ -636,7 +636,7 @@ static efi_status_t EFIAPI efi_register_protocol_notify(efi_guid_t *protocol, struct efi_event *event, void **registration) { - EFI_ENTRY("%p, %p, %p", protocol, event, registration); + EFI_ENTRY("%pUl, %p, %p", protocol, event, registration); return EFI_EXIT(EFI_OUT_OF_RESOURCES); } @@ -706,7 +706,7 @@ static efi_status_t EFIAPI efi_locate_handle_ext( efi_guid_t *protocol, void *search_key, unsigned long *buffer_size, efi_handle_t *buffer) { - EFI_ENTRY("%d, %p, %p, %p, %p", search_type, protocol, search_key, + EFI_ENTRY("%d, %pUl, %p, %p, %p", search_type, protocol, search_key, buffer_size, buffer); return EFI_EXIT(efi_locate_handle(search_type, protocol, search_key, @@ -774,7 +774,7 @@ efi_status_t efi_install_configuration_table(const efi_guid_t *guid, void *table static efi_status_t EFIAPI efi_install_configuration_table_ext(efi_guid_t *guid, void *table) { - EFI_ENTRY("%p, %p", guid, table); + EFI_ENTRY("%pUl, %p", guid, table); return EFI_EXIT(efi_install_configuration_table(guid, table)); } @@ -1083,7 +1083,7 @@ static efi_status_t EFIAPI efi_close_protocol(void *handle, void *agent_handle, void *controller_handle) { - EFI_ENTRY("%p, %p, %p, %p", handle, protocol, agent_handle, + EFI_ENTRY("%p, %pUl, %p, %p", handle, protocol, agent_handle, controller_handle); return EFI_EXIT(EFI_NOT_FOUND); } @@ -1093,7 +1093,7 @@ static efi_status_t EFIAPI efi_open_protocol_information(efi_handle_t handle, struct efi_open_protocol_info_entry **entry_buffer, unsigned long *entry_count) { - EFI_ENTRY("%p, %p, %p, %p", handle, protocol, entry_buffer, + EFI_ENTRY("%p, %pUl, %p, %p", handle, protocol, entry_buffer, entry_count); return EFI_EXIT(EFI_NOT_FOUND); } @@ -1159,7 +1159,7 @@ static efi_status_t EFIAPI efi_locate_handle_buffer( efi_status_t r; unsigned long buffer_size = 0; - EFI_ENTRY("%d, %p, %p, %p, %p", search_type, protocol, search_key, + EFI_ENTRY("%d, %pUl, %p, %p, %p", search_type, protocol, search_key, no_handles, buffer); if (!no_handles || !buffer) { @@ -1191,7 +1191,7 @@ static efi_status_t EFIAPI efi_locate_protocol(efi_guid_t *protocol, struct list_head *lhandle; int i; - EFI_ENTRY("%p, %p, %p", protocol, registration, protocol_interface); + EFI_ENTRY("%pUl, %p, %p", protocol, registration, protocol_interface); if (!protocol || !protocol_interface) return EFI_EXIT(EFI_INVALID_PARAMETER); @@ -1301,7 +1301,7 @@ static efi_status_t EFIAPI efi_open_protocol( int i; efi_status_t r = EFI_INVALID_PARAMETER; - EFI_ENTRY("%p, %p, %p, %p, %p, 0x%x", handle, protocol, + EFI_ENTRY("%p, %pUl, %p, %p, %p, 0x%x", handle, protocol, protocol_interface, agent_handle, controller_handle, attributes); diff --git a/lib/efi_loader/efi_variable.c b/lib/efi_loader/efi_variable.c index 5569b3d3f0..6c177da3a6 100644 --- a/lib/efi_loader/efi_variable.c +++ b/lib/efi_loader/efi_variable.c @@ -187,7 +187,7 @@ efi_status_t EFIAPI efi_get_variable(s16 *variable_name, const char *val, *s; u32 attr; - EFI_ENTRY("%p %p %p %p %p", variable_name, vendor, attributes, + EFI_ENTRY("\"%ls\" %pUl %p %p %p", variable_name, vendor, attributes, data_size, data); if (!variable_name || !vendor || !data_size) @@ -255,7 +255,7 @@ efi_status_t EFIAPI efi_get_next_variable( unsigned long *variable_name_size, s16 *variable_name, efi_guid_t *vendor) { - EFI_ENTRY("%p %p %p", variable_name_size, variable_name, vendor); + EFI_ENTRY("%p \"%ls\" %pUl", variable_name_size, variable_name, vendor); return EFI_EXIT(EFI_DEVICE_ERROR); } @@ -270,7 +270,7 @@ efi_status_t EFIAPI efi_set_variable(s16 *variable_name, char *val, *s; u32 attr; - EFI_ENTRY("%p %p %x %lu %p", variable_name, vendor, attributes, + EFI_ENTRY("\"%ls\" %pUl %x %lu %p", variable_name, vendor, attributes, data_size, data); if (!variable_name || !vendor) -- cgit v1.2.3 From 71cc25c35c34945babbd94935a0e9c4f4ee942b2 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:42 -0400 Subject: efi_loader: split out escape sequence based size query We need to do something different for vidconsole, since it cannot respond to the query on stdin. Prep work for next patch. Signed-off-by: Rob Clark Signed-off-by: Alexander Graf --- lib/efi_loader/efi_console.c | 53 +++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/lib/efi_loader/efi_console.c b/lib/efi_loader/efi_console.c index 65c07fdf37..6502081706 100644 --- a/lib/efi_loader/efi_console.c +++ b/lib/efi_loader/efi_console.c @@ -186,6 +186,34 @@ static bool cout_mode_matches(struct cout_mode *mode, int rows, int cols) return (mode->rows == rows) && (mode->columns == cols); } +static int query_console_serial(int *rows, int *cols) +{ + /* Ask the terminal about its size */ + int n[3]; + u64 timeout; + + /* Empty input buffer */ + while (tstc()) + getc(); + + printf(ESC"[18t"); + + /* Check if we have a terminal that understands */ + timeout = timer_get_us() + 1000000; + while (!tstc()) + if (timer_get_us() > timeout) + return -1; + + /* Read {depth,rows,cols} */ + if (term_read_reply(n, 3, 't')) + return -1; + + *cols = n[2]; + *rows = n[1]; + + return 0; +} + static efi_status_t EFIAPI efi_cout_query_mode( struct efi_simple_text_output_protocol *this, unsigned long mode_number, unsigned long *columns, @@ -194,33 +222,12 @@ static efi_status_t EFIAPI efi_cout_query_mode( EFI_ENTRY("%p, %ld, %p, %p", this, mode_number, columns, rows); if (!console_size_queried) { - /* Ask the terminal about its size */ - int n[3]; - int cols; - int rows; - u64 timeout; + int rows, cols; console_size_queried = true; - /* Empty input buffer */ - while (tstc()) - getc(); - - printf(ESC"[18t"); - - /* Check if we have a terminal that understands */ - timeout = timer_get_us() + 1000000; - while (!tstc()) - if (timer_get_us() > timeout) - goto out; - - /* Read {depth,rows,cols} */ - if (term_read_reply(n, 3, 't')) { + if (query_console_serial(&rows, &cols)) goto out; - } - - cols = n[2]; - rows = n[1]; /* Test if we can have Mode 1 */ if (cols >= 80 && rows >= 50) { -- cgit v1.2.3 From a18c5a8382284037ea8fa4742736a65317476d23 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:43 -0400 Subject: efi_loader: Correctly figure out size for vidconsole If stdout is vidconsole, we cannot rely on ANSI escape sequences to query the size, as vidconsole cannot reply on stdin. Instead special- case this if stdout is vidconsole. Signed-off-by: Rob Clark Signed-off-by: Alexander Graf --- lib/efi_loader/efi_console.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/efi_loader/efi_console.c b/lib/efi_loader/efi_console.c index 6502081706..d596c58241 100644 --- a/lib/efi_loader/efi_console.c +++ b/lib/efi_loader/efi_console.c @@ -8,7 +8,10 @@ #include #include +#include #include +#include +#include static bool console_size_queried; @@ -222,12 +225,23 @@ static efi_status_t EFIAPI efi_cout_query_mode( EFI_ENTRY("%p, %ld, %p, %p", this, mode_number, columns, rows); if (!console_size_queried) { + const char *stdout_name = env_get("stdout"); int rows, cols; console_size_queried = true; - if (query_console_serial(&rows, &cols)) + if (stdout_name && !strcmp(stdout_name, "vidconsole") && + IS_ENABLED(CONFIG_DM_VIDEO)) { + struct stdio_dev *stdout_dev = + stdio_get_by_name("vidconsole"); + struct udevice *dev = stdout_dev->priv; + struct vidconsole_priv *priv = + dev_get_uclass_priv(dev); + rows = priv->rows; + cols = priv->cols; + } else if (query_console_serial(&rows, &cols)) { goto out; + } /* Test if we can have Mode 1 */ if (cols >= 80 && rows >= 50) { -- cgit v1.2.3 From 3a45bc7faa5f93513369fc4447db2c379c19b2ca Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 13 Sep 2017 18:05:44 -0400 Subject: efi_loader: Some console improvements for vidconsole 1) use fputs() to reduce cache flushes from once-per-char to once-per-string 2) handle \r, \t, and \b in addition to just \n for tracking cursor position 3) cursor row/col are zero based, not one based Signed-off-by: Rob Clark [agraf: s/unsigned/unsigned int/] Signed-off-by: Alexander Graf --- include/efi_api.h | 6 +++-- lib/efi_loader/efi_console.c | 58 ++++++++++++++++++++++++++------------------ 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/include/efi_api.h b/include/efi_api.h index 604c5b7ec4..c3b9032a48 100644 --- a/include/efi_api.h +++ b/include/efi_api.h @@ -29,6 +29,8 @@ enum efi_timer_delay { }; #define UINTN size_t +typedef long INTN; +typedef uint16_t *efi_string_t; #define EVT_TIMER 0x80000000 #define EVT_RUNTIME 0x40000000 @@ -427,10 +429,10 @@ struct efi_simple_text_output_protocol { void *reset; efi_status_t (EFIAPI *output_string)( struct efi_simple_text_output_protocol *this, - const unsigned short *str); + const efi_string_t str); efi_status_t (EFIAPI *test_string)( struct efi_simple_text_output_protocol *this, - const unsigned short *str); + const efi_string_t str); efi_status_t(EFIAPI *query_mode)( struct efi_simple_text_output_protocol *this, unsigned long mode_number, unsigned long *columns, diff --git a/lib/efi_loader/efi_console.c b/lib/efi_loader/efi_console.c index d596c58241..fd5398d61d 100644 --- a/lib/efi_loader/efi_console.c +++ b/lib/efi_loader/efi_console.c @@ -140,34 +140,46 @@ static efi_status_t EFIAPI efi_cout_reset( return EFI_EXIT(EFI_UNSUPPORTED); } -static void print_unicode_in_utf8(u16 c) -{ - char utf8[MAX_UTF8_PER_UTF16] = { 0 }; - utf16_to_utf8((u8 *)utf8, &c, 1); - puts(utf8); -} - static efi_status_t EFIAPI efi_cout_output_string( struct efi_simple_text_output_protocol *this, - const unsigned short *string) + const efi_string_t string) { - struct cout_mode *mode; - u16 ch; + struct simple_text_output_mode *con = &efi_con_mode; + struct cout_mode *mode = &efi_cout_modes[con->mode]; - mode = &efi_cout_modes[efi_con_mode.mode]; EFI_ENTRY("%p, %p", this, string); - for (;(ch = *string); string++) { - print_unicode_in_utf8(ch); - efi_con_mode.cursor_column++; - if (ch == '\n') { - efi_con_mode.cursor_column = 1; - efi_con_mode.cursor_row++; - } else if (efi_con_mode.cursor_column > mode->columns) { - efi_con_mode.cursor_column = 1; - efi_con_mode.cursor_row++; + + unsigned int n16 = utf16_strlen(string); + char buf[MAX_UTF8_PER_UTF16 * n16 + 1]; + char *p; + + *utf16_to_utf8((u8 *)buf, string, n16) = '\0'; + + fputs(stdout, buf); + + for (p = buf; *p; p++) { + switch (*p) { + case '\r': /* carriage-return */ + con->cursor_column = 0; + break; + case '\n': /* newline */ + con->cursor_column = 0; + con->cursor_row++; + break; + case '\t': /* tab, assume 8 char align */ + break; + case '\b': /* backspace */ + con->cursor_column = max(0, con->cursor_column - 1); + break; + default: + con->cursor_column++; + break; + } + if (con->cursor_column >= mode->columns) { + con->cursor_column = 0; + con->cursor_row++; } - if (efi_con_mode.cursor_row > mode->rows) - efi_con_mode.cursor_row = mode->rows; + con->cursor_row = min(con->cursor_row, (s32)mode->rows - 1); } return EFI_EXIT(EFI_SUCCESS); @@ -175,7 +187,7 @@ static efi_status_t EFIAPI efi_cout_output_string( static efi_status_t EFIAPI efi_cout_test_string( struct efi_simple_text_output_protocol *this, - const unsigned short *string) + const efi_string_t string) { EFI_ENTRY("%p, %p", this, string); return EFI_EXIT(EFI_SUCCESS); -- cgit v1.2.3 From 7aca68ca06b8a96cb71f690e24f990281e402be8 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Wed, 20 Sep 2017 22:54:58 +0200 Subject: efi_loader: reenable selftest ad503ffe9c6 efi_loader: refactor boot device and loaded_image handling leads to an error when building with CONFIG_CMD_BOOTEFI_SELFTEST=y This patch fixes the problem. Fixes: ad503ffe9c6 efi_loader: refactor boot device and loaded_image handling Signed-off-by: Heinrich Schuchardt Reviewed-by: Rob Clark Signed-off-by: Alexander Graf --- cmd/bootefi.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/bootefi.c b/cmd/bootefi.c index 1e2dbcc4a4..e0a657323f 100644 --- a/cmd/bootefi.c +++ b/cmd/bootefi.c @@ -273,6 +273,12 @@ static int do_bootefi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) #endif #ifdef CONFIG_CMD_BOOTEFI_SELFTEST if (!strcmp(argv[1], "selftest")) { + struct efi_loaded_image loaded_image_info = {}; + struct efi_object loaded_image_info_obj = {}; + + efi_setup_loaded_image(&loaded_image_info, + &loaded_image_info_obj, + bootefi_device_path, bootefi_image_path); /* * gd lives in a fixed register which may get clobbered while we * execute the payload. So save it here and restore it on every @@ -282,8 +288,6 @@ static int do_bootefi(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) /* Initialize and populate EFI object list */ if (!efi_obj_list_initalized) efi_init_obj_list(); - loaded_image_info.device_handle = bootefi_device_path; - loaded_image_info.file_path = bootefi_image_path; return efi_selftest(&loaded_image_info, &systab); } else #endif -- cgit v1.2.3 From 7dd5d44753969167a6059405635bdb8b9d961fa1 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Wed, 20 Sep 2017 18:23:42 -0400 Subject: efi_loader: fix efi_exit efi_exit() already restores gd, so we shouldn't EFI_EXIT() on the otherside of the longjmp(). Signed-off-by: Rob Clark Signed-off-by: Alexander Graf --- cmd/bootefi.c | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/bootefi.c b/cmd/bootefi.c index e0a657323f..b7087e3da8 100644 --- a/cmd/bootefi.c +++ b/cmd/bootefi.c @@ -190,7 +190,6 @@ static unsigned long do_bootefi_exec(void *efi, void *fdt, if (setjmp(&loaded_image_info.exit_jmp)) { ret = loaded_image_info.exit_status; - EFI_EXIT(ret); goto exit; } -- cgit v1.2.3