m/p/kexec: add auxiliary EFI handling
This makes kexec do auxiliary handling related to EFI. If a system is
booted using EFI and has an ACPI Root System Description Pointer (RDSP)
i.e. just about every modern system, the pointer to that structure needs
to be passed to the kexec'ed kernel on its command line.
Otherwise various EFI-related functionality breaks, like accessing
EFI variables which causes a kernel crash.
Logically I think this functionality belongs to kexec as callers
shouldn't need to be aware of kexec specifics and every caller
potentially running on EFI needs this functionality.
Change-Id: Iec7ad4c3c0a7e5c31d738d307ff0e10aac02ab11
Reviewed-on: https://review.monogon.dev/c/monogon/+/960
Tested-by: Jenkins CI
Reviewed-by: Mateusz Zalega <mateusz@monogon.tech>
diff --git a/metropolis/pkg/kexec/kexec.go b/metropolis/pkg/kexec/kexec.go
index dc330ec..4d05037 100644
--- a/metropolis/pkg/kexec/kexec.go
+++ b/metropolis/pkg/kexec/kexec.go
@@ -5,19 +5,59 @@
package kexec
import (
+ "bufio"
+ "errors"
"fmt"
+ "io"
"os"
"runtime"
+ "strings"
"golang.org/x/sys/unix"
)
// FileLoad loads the given kernel as the new kernel with the given initramfs
-// and cmdline. The kernel can be started by calling
-// unix.Reboot(unix.LINUX_REBOOT_CMD_KEXEC). The underlying syscall is only
-// available on x86_64, arm64 and riscv.
+// and cmdline. It also performs auxiliary work like adding the ACPI RSDP
+// physical address to command line if using EFI. The kernel can be started by
+// calling unix.Reboot(unix.LINUX_REBOOT_CMD_KEXEC).
+// The underlying syscall is only available on x86_64, arm64 and riscv.
// Parts of this function are taken from u-root's kexec package.
func FileLoad(kernel, initramfs *os.File, cmdline string) error {
+ passedCmdline := cmdline
+ systab, err := os.Open("/sys/firmware/efi/systab")
+ if os.IsNotExist(err) {
+ // No EFI, nothing to do
+ } else if err != nil {
+ return fmt.Errorf("unable to open EFI systab: %w", err)
+ } else {
+ s := bufio.NewScanner(systab)
+ for s.Scan() {
+ if errors.Is(s.Err(), io.EOF) {
+ // We have no RSDP, no need to pass it
+ break
+ }
+ if err != nil {
+ return fmt.Errorf("failed to read EFI systab: %w", err)
+ }
+ parts := strings.SplitN(s.Text(), "=", 2)
+ // There are two ACPI RDSP revisions, 1.0 and 2.0.
+ // Linux guarantees that the 2.0 always comes before the
+ // 1.0 so just matching and breaking is good enough.
+ if parts[0] == "ACPI20" || parts[0] == "ACPI" {
+ // Technically this could be passed through as parsing a hexa-
+ // decimal address and printing it back does nothing, but in
+ // case unexpected values show up this could cause very hard-
+ // to-debug crashes when the new kernel boots.
+ var acpiRsdp int64
+ if _, err := fmt.Sscanf(parts[1], "0x%x", &acpiRsdp); err != nil {
+ return fmt.Errorf("failed to parse EFI systab ACP RSDP address: %w", err)
+ }
+ passedCmdline += fmt.Sprintf(" acpi_rsdp=0x%x", acpiRsdp)
+ break
+ }
+ }
+ }
+
var flags int
var initramfsfd int
if initramfs != nil {
@@ -26,7 +66,7 @@
flags |= unix.KEXEC_FILE_NO_INITRAMFS
}
- if err := unix.KexecFileLoad(int(kernel.Fd()), initramfsfd, cmdline, flags); err != nil {
+ if err := unix.KexecFileLoad(int(kernel.Fd()), initramfsfd, passedCmdline, flags); err != nil {
return fmt.Errorf("SYS_kexec_file_load(%d, %d, %s, %x) = %v", kernel.Fd(), initramfsfd, cmdline, flags, err)
}
runtime.KeepAlive(kernel)