blob: 4d05037d5efecf644c0b57c19b1252e4f51cff31 [file] [log] [blame]
Lorenz Brun5486a9c2022-09-12 16:49:30 +00001//go:build amd64 || arm64 || riscv64
2// +build amd64 arm64 riscv64
3
4// Package kexec allows executing subsequent kernels from Linux userspace.
5package kexec
6
7import (
Lorenz Brun0123a1c2022-10-24 18:41:48 +00008 "bufio"
9 "errors"
Lorenz Brun5486a9c2022-09-12 16:49:30 +000010 "fmt"
Lorenz Brun0123a1c2022-10-24 18:41:48 +000011 "io"
Lorenz Brun5486a9c2022-09-12 16:49:30 +000012 "os"
13 "runtime"
Lorenz Brun0123a1c2022-10-24 18:41:48 +000014 "strings"
Lorenz Brun5486a9c2022-09-12 16:49:30 +000015
16 "golang.org/x/sys/unix"
17)
18
19// FileLoad loads the given kernel as the new kernel with the given initramfs
Lorenz Brun0123a1c2022-10-24 18:41:48 +000020// and cmdline. It also performs auxiliary work like adding the ACPI RSDP
21// physical address to command line if using EFI. The kernel can be started by
22// calling unix.Reboot(unix.LINUX_REBOOT_CMD_KEXEC).
23// The underlying syscall is only available on x86_64, arm64 and riscv.
Lorenz Brun5486a9c2022-09-12 16:49:30 +000024// Parts of this function are taken from u-root's kexec package.
25func FileLoad(kernel, initramfs *os.File, cmdline string) error {
Lorenz Brun0123a1c2022-10-24 18:41:48 +000026 passedCmdline := cmdline
27 systab, err := os.Open("/sys/firmware/efi/systab")
28 if os.IsNotExist(err) {
29 // No EFI, nothing to do
30 } else if err != nil {
31 return fmt.Errorf("unable to open EFI systab: %w", err)
32 } else {
33 s := bufio.NewScanner(systab)
34 for s.Scan() {
35 if errors.Is(s.Err(), io.EOF) {
36 // We have no RSDP, no need to pass it
37 break
38 }
39 if err != nil {
40 return fmt.Errorf("failed to read EFI systab: %w", err)
41 }
42 parts := strings.SplitN(s.Text(), "=", 2)
43 // There are two ACPI RDSP revisions, 1.0 and 2.0.
44 // Linux guarantees that the 2.0 always comes before the
45 // 1.0 so just matching and breaking is good enough.
46 if parts[0] == "ACPI20" || parts[0] == "ACPI" {
47 // Technically this could be passed through as parsing a hexa-
48 // decimal address and printing it back does nothing, but in
49 // case unexpected values show up this could cause very hard-
50 // to-debug crashes when the new kernel boots.
51 var acpiRsdp int64
52 if _, err := fmt.Sscanf(parts[1], "0x%x", &acpiRsdp); err != nil {
53 return fmt.Errorf("failed to parse EFI systab ACP RSDP address: %w", err)
54 }
55 passedCmdline += fmt.Sprintf(" acpi_rsdp=0x%x", acpiRsdp)
56 break
57 }
58 }
59 }
60
Lorenz Brun5486a9c2022-09-12 16:49:30 +000061 var flags int
62 var initramfsfd int
63 if initramfs != nil {
64 initramfsfd = int(initramfs.Fd())
65 } else {
66 flags |= unix.KEXEC_FILE_NO_INITRAMFS
67 }
68
Lorenz Brun0123a1c2022-10-24 18:41:48 +000069 if err := unix.KexecFileLoad(int(kernel.Fd()), initramfsfd, passedCmdline, flags); err != nil {
Lorenz Brun5486a9c2022-09-12 16:49:30 +000070 return fmt.Errorf("SYS_kexec_file_load(%d, %d, %s, %x) = %v", kernel.Fd(), initramfsfd, cmdline, flags, err)
71 }
72 runtime.KeepAlive(kernel)
73 runtime.KeepAlive(initramfs)
74 return nil
75}