Skip to content

Commit

Permalink
Merge branch 'efi-core-for-linus' of git://git.kernel.org/pub/scm/lin…
Browse files Browse the repository at this point in the history
…ux/kernel/git/tip/tip

Pull EFI updates from Ingo Molnar:
 "The main changes are:

   - Add support for enlisting the help of the EFI firmware to create
     memory reservations that persist across kexec.

   - Add page fault handling to the runtime services support code on x86
     so we can more gracefully recover from buggy EFI firmware.

   - Fix command line handling on x86 for the boot path that omits the
     stub's PE/COFF entry point.

   - Other assorted fixes and updates"

* 'efi-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  x86: boot: Fix EFI stub alignment
  efi/x86: Call efi_parse_options() from efi_main()
  efi/x86: earlyprintk - Add 64bit efi fb address support
  efi/x86: drop task_lock() from efi_switch_mm()
  efi/x86: Handle page faults occurring while running EFI runtime services
  efi: Make efi_rts_work accessible to efi page fault handler
  efi/efi_test: add exporting ResetSystem runtime service
  efi/libstub: arm: support building with clang
  efi: add API to reserve memory persistently across kexec reboot
  efi/arm: libstub: add a root memreserve config table
  efi: honour memory reservations passed via a linux specific config table
  • Loading branch information
torvalds committed Oct 23, 2018
2 parents cee1352 + fa70f0d commit de3fbb2
Show file tree
Hide file tree
Showing 14 changed files with 307 additions and 54 deletions.
10 changes: 10 additions & 0 deletions arch/x86/boot/compressed/eboot.c
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,7 @@ efi_main(struct efi_config *c, struct boot_params *boot_params)
struct desc_struct *desc;
void *handle;
efi_system_table_t *_table;
unsigned long cmdline_paddr;

efi_early = c;

Expand All @@ -755,6 +756,15 @@ efi_main(struct efi_config *c, struct boot_params *boot_params)
else
setup_boot_services32(efi_early);

/*
* make_boot_params() may have been called before efi_main(), in which
* case this is the second time we parse the cmdline. This is ok,
* parsing the cmdline multiple times does not have side-effects.
*/
cmdline_paddr = ((u64)hdr->cmd_line_ptr |
((u64)boot_params->ext_cmd_line_ptr << 32));
efi_parse_options((char *)cmdline_paddr);

/*
* If the boot loader gave us a value for secure_boot then we use that,
* otherwise we ask the BIOS.
Expand Down
7 changes: 7 additions & 0 deletions arch/x86/boot/tools/build.c
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,13 @@ int main(int argc, char ** argv)
die("Unable to mmap '%s': %m", argv[2]);
/* Number of 16-byte paragraphs, including space for a 4-byte CRC */
sys_size = (sz + 15 + 4) / 16;
#ifdef CONFIG_EFI_STUB
/*
* COFF requires minimum 32-byte alignment of sections, and
* adding a signature is problematic without that alignment.
*/
sys_size = (sys_size + 1) & ~1;
#endif

/* Patch the setup code with the appropriate size parameters */
buf[0x1f1] = setup_sectors-1;
Expand Down
1 change: 1 addition & 0 deletions arch/x86/include/asm/efi.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ extern void __init efi_apply_memmap_quirks(void);
extern int __init efi_reuse_config(u64 tables, int nr_tables);
extern void efi_delete_dummy_variable(void);
extern void efi_switch_mm(struct mm_struct *mm);
extern void efi_recover_from_page_fault(unsigned long phys_addr);

struct efi_setup_data {
u64 fw_vendor;
Expand Down
9 changes: 9 additions & 0 deletions arch/x86/mm/fault.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <linux/prefetch.h> /* prefetchw */
#include <linux/context_tracking.h> /* exception_enter(), ... */
#include <linux/uaccess.h> /* faulthandler_disabled() */
#include <linux/efi.h> /* efi_recover_from_page_fault()*/
#include <linux/mm_types.h>

#include <asm/cpufeature.h> /* boot_cpu_has, ... */
Expand All @@ -25,6 +26,7 @@
#include <asm/vsyscall.h> /* emulate_vsyscall */
#include <asm/vm86.h> /* struct vm86 */
#include <asm/mmu_context.h> /* vma_pkey() */
#include <asm/efi.h> /* efi_recover_from_page_fault()*/

#define CREATE_TRACE_POINTS
#include <asm/trace/exceptions.h>
Expand Down Expand Up @@ -788,6 +790,13 @@ no_context(struct pt_regs *regs, unsigned long error_code,
if (is_errata93(regs, address))
return;

/*
* Buggy firmware could access regions which might page fault, try to
* recover from such faults.
*/
if (IS_ENABLED(CONFIG_EFI))
efi_recover_from_page_fault(address);

/*
* Oops. The kernel tried to access some bad page. We'll have to
* terminate things with extreme prejudice:
Expand Down
8 changes: 6 additions & 2 deletions arch/x86/platform/efi/early_printk.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ static bool early_efi_keep;
*/
static __init int early_efi_map_fb(void)
{
unsigned long base, size;
u64 base, size;

if (!early_efi_keep)
return 0;

base = boot_params.screen_info.lfb_base;
if (boot_params.screen_info.capabilities & VIDEO_CAPABILITY_64BIT_BASE)
base |= (u64)boot_params.screen_info.ext_lfb_base << 32;
size = boot_params.screen_info.lfb_size;
efi_fb = ioremap(base, size);

Expand All @@ -46,9 +48,11 @@ early_initcall(early_efi_map_fb);
*/
static __ref void *early_efi_map(unsigned long start, unsigned long len)
{
unsigned long base;
u64 base;

base = boot_params.screen_info.lfb_base;
if (boot_params.screen_info.capabilities & VIDEO_CAPABILITY_64BIT_BASE)
base |= (u64)boot_params.screen_info.ext_lfb_base << 32;

if (efi_fb)
return (efi_fb + start);
Expand Down
10 changes: 4 additions & 6 deletions arch/x86/platform/efi/efi_64.c
Original file line number Diff line number Diff line change
Expand Up @@ -619,18 +619,16 @@ void __init efi_dump_pagetable(void)

/*
* Makes the calling thread switch to/from efi_mm context. Can be used
* for SetVirtualAddressMap() i.e. current->active_mm == init_mm as well
* as during efi runtime calls i.e current->active_mm == current_mm.
* We are not mm_dropping()/mm_grabbing() any mm, because we are not
* losing/creating any references.
* in a kernel thread and user context. Preemption needs to remain disabled
* while the EFI-mm is borrowed. mmgrab()/mmdrop() is not used because the mm
* can not change under us.
* It should be ensured that there are no concurent calls to this function.
*/
void efi_switch_mm(struct mm_struct *mm)
{
task_lock(current);
efi_scratch.prev_mm = current->active_mm;
current->active_mm = mm;
switch_mm(efi_scratch.prev_mm, mm, NULL);
task_unlock(current);
}

#ifdef CONFIG_EFI_MIXED
Expand Down
78 changes: 78 additions & 0 deletions arch/x86/platform/efi/quirks.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <asm/efi.h>
#include <asm/uv/uv.h>
#include <asm/cpu_device_id.h>
#include <asm/reboot.h>

#define EFI_MIN_RESERVE 5120

Expand Down Expand Up @@ -654,3 +655,80 @@ int efi_capsule_setup_info(struct capsule_info *cap_info, void *kbuff,
}

#endif

/*
* If any access by any efi runtime service causes a page fault, then,
* 1. If it's efi_reset_system(), reboot through BIOS.
* 2. If any other efi runtime service, then
* a. Return error status to the efi caller process.
* b. Disable EFI Runtime Services forever and
* c. Freeze efi_rts_wq and schedule new process.
*
* @return: Returns, if the page fault is not handled. This function
* will never return if the page fault is handled successfully.
*/
void efi_recover_from_page_fault(unsigned long phys_addr)
{
if (!IS_ENABLED(CONFIG_X86_64))
return;

/*
* Make sure that an efi runtime service caused the page fault.
* "efi_mm" cannot be used to check if the page fault had occurred
* in the firmware context because efi=old_map doesn't use efi_pgd.
*/
if (efi_rts_work.efi_rts_id == NONE)
return;

/*
* Address range 0x0000 - 0x0fff is always mapped in the efi_pgd, so
* page faulting on these addresses isn't expected.
*/
if (phys_addr >= 0x0000 && phys_addr <= 0x0fff)
return;

/*
* Print stack trace as it might be useful to know which EFI Runtime
* Service is buggy.
*/
WARN(1, FW_BUG "Page fault caused by firmware at PA: 0x%lx\n",
phys_addr);

/*
* Buggy efi_reset_system() is handled differently from other EFI
* Runtime Services as it doesn't use efi_rts_wq. Although,
* native_machine_emergency_restart() says that machine_real_restart()
* could fail, it's better not to compilcate this fault handler
* because this case occurs *very* rarely and hence could be improved
* on a need by basis.
*/
if (efi_rts_work.efi_rts_id == RESET_SYSTEM) {
pr_info("efi_reset_system() buggy! Reboot through BIOS\n");
machine_real_restart(MRR_BIOS);
return;
}

/*
* Before calling EFI Runtime Service, the kernel has switched the
* calling process to efi_mm. Hence, switch back to task_mm.
*/
arch_efi_call_virt_teardown();

/* Signal error status to the efi caller process */
efi_rts_work.status = EFI_ABORTED;
complete(&efi_rts_work.efi_rts_comp);

clear_bit(EFI_RUNTIME_SERVICES, &efi.flags);
pr_info("Froze efi_rts_wq and disabled EFI Runtime Services\n");

/*
* Call schedule() in an infinite loop, so that any spurious wake ups
* will never run efi_rts_wq again.
*/
for (;;) {
set_current_state(TASK_IDLE);
schedule();
}

return;
}
59 changes: 58 additions & 1 deletion drivers/firmware/efi/efi.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ struct efi __read_mostly efi = {
.properties_table = EFI_INVALID_TABLE_ADDR,
.mem_attr_table = EFI_INVALID_TABLE_ADDR,
.rng_seed = EFI_INVALID_TABLE_ADDR,
.tpm_log = EFI_INVALID_TABLE_ADDR
.tpm_log = EFI_INVALID_TABLE_ADDR,
.mem_reserve = EFI_INVALID_TABLE_ADDR,
};
EXPORT_SYMBOL(efi);

Expand Down Expand Up @@ -484,6 +485,7 @@ static __initdata efi_config_table_type_t common_tables[] = {
{EFI_MEMORY_ATTRIBUTES_TABLE_GUID, "MEMATTR", &efi.mem_attr_table},
{LINUX_EFI_RANDOM_SEED_TABLE_GUID, "RNG", &efi.rng_seed},
{LINUX_EFI_TPM_EVENT_LOG_GUID, "TPMEventLog", &efi.tpm_log},
{LINUX_EFI_MEMRESERVE_TABLE_GUID, "MEMRESERVE", &efi.mem_reserve},
{NULL_GUID, NULL, NULL},
};

Expand Down Expand Up @@ -591,6 +593,29 @@ int __init efi_config_parse_tables(void *config_tables, int count, int sz,
early_memunmap(tbl, sizeof(*tbl));
}

if (efi.mem_reserve != EFI_INVALID_TABLE_ADDR) {
unsigned long prsv = efi.mem_reserve;

while (prsv) {
struct linux_efi_memreserve *rsv;

/* reserve the entry itself */
memblock_reserve(prsv, sizeof(*rsv));

rsv = early_memremap(prsv, sizeof(*rsv));
if (rsv == NULL) {
pr_err("Could not map UEFI memreserve entry!\n");
return -ENOMEM;
}

if (rsv->size)
memblock_reserve(rsv->base, rsv->size);

prsv = rsv->next;
early_memunmap(rsv, sizeof(*rsv));
}
}

return 0;
}

Expand Down Expand Up @@ -937,6 +962,38 @@ bool efi_is_table_address(unsigned long phys_addr)
return false;
}

static DEFINE_SPINLOCK(efi_mem_reserve_persistent_lock);

int efi_mem_reserve_persistent(phys_addr_t addr, u64 size)
{
struct linux_efi_memreserve *rsv, *parent;

if (efi.mem_reserve == EFI_INVALID_TABLE_ADDR)
return -ENODEV;

rsv = kmalloc(sizeof(*rsv), GFP_KERNEL);
if (!rsv)
return -ENOMEM;

parent = memremap(efi.mem_reserve, sizeof(*rsv), MEMREMAP_WB);
if (!parent) {
kfree(rsv);
return -ENOMEM;
}

rsv->base = addr;
rsv->size = size;

spin_lock(&efi_mem_reserve_persistent_lock);
rsv->next = parent->next;
parent->next = __pa(rsv);
spin_unlock(&efi_mem_reserve_persistent_lock);

memunmap(parent);

return 0;
}

#ifdef CONFIG_KEXEC
static int update_efi_random_seed(struct notifier_block *nb,
unsigned long code, void *unused)
Expand Down
3 changes: 2 additions & 1 deletion drivers/firmware/efi/libstub/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ cflags-$(CONFIG_X86) += -m$(BITS) -D__KERNEL__ -O2 \
cflags-$(CONFIG_ARM64) := $(subst -pg,,$(KBUILD_CFLAGS)) -fpie \
$(DISABLE_STACKLEAK_PLUGIN)
cflags-$(CONFIG_ARM) := $(subst -pg,,$(KBUILD_CFLAGS)) \
-fno-builtin -fpic -mno-single-pic-base
-fno-builtin -fpic \
$(call cc-option,-mno-single-pic-base)

cflags-$(CONFIG_EFI_ARMSTUB) += -I$(srctree)/scripts/dtc/libfdt

Expand Down
27 changes: 27 additions & 0 deletions drivers/firmware/efi/libstub/arm-stub.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,31 @@ static struct screen_info *setup_graphics(efi_system_table_t *sys_table_arg)
return si;
}

void install_memreserve_table(efi_system_table_t *sys_table_arg)
{
struct linux_efi_memreserve *rsv;
efi_guid_t memreserve_table_guid = LINUX_EFI_MEMRESERVE_TABLE_GUID;
efi_status_t status;

status = efi_call_early(allocate_pool, EFI_LOADER_DATA, sizeof(*rsv),
(void **)&rsv);
if (status != EFI_SUCCESS) {
pr_efi_err(sys_table_arg, "Failed to allocate memreserve entry!\n");
return;
}

rsv->next = 0;
rsv->base = 0;
rsv->size = 0;

status = efi_call_early(install_configuration_table,
&memreserve_table_guid,
rsv);
if (status != EFI_SUCCESS)
pr_efi_err(sys_table_arg, "Failed to install memreserve config table!\n");
}


/*
* This function handles the architcture specific differences between arm and
* arm64 regarding where the kernel image must be loaded and any memory that
Expand Down Expand Up @@ -235,6 +260,8 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table,
}
}

install_memreserve_table(sys_table);

new_fdt_addr = fdt_addr;
status = allocate_new_fdt_and_exit_boot(sys_table, handle,
&new_fdt_addr, efi_get_max_fdt_addr(dram_base),
Expand Down
Loading

0 comments on commit de3fbb2

Please sign in to comment.