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