|
@@ -16,6 +16,7 @@
|
|
#include <asm/efi.h>
|
|
#include <asm/efi.h>
|
|
#include <asm/uv/uv.h>
|
|
#include <asm/uv/uv.h>
|
|
#include <asm/cpu_device_id.h>
|
|
#include <asm/cpu_device_id.h>
|
|
|
|
+#include <asm/reboot.h>
|
|
|
|
|
|
#define EFI_MIN_RESERVE 5120
|
|
#define EFI_MIN_RESERVE 5120
|
|
|
|
|
|
@@ -654,3 +655,80 @@ int efi_capsule_setup_info(struct capsule_info *cap_info, void *kbuff,
|
|
}
|
|
}
|
|
|
|
|
|
#endif
|
|
#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;
|
|
|
|
+}
|