github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/boot/linux/load_linux_amd64.go (about)

     1  // Copyright 2022 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package linux
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  
    14  	"github.com/mvdan/u-root-coreutils/pkg/boot/bzimage"
    15  	"github.com/mvdan/u-root-coreutils/pkg/boot/kexec"
    16  	"github.com/mvdan/u-root-coreutils/pkg/boot/purgatory"
    17  	"github.com/mvdan/u-root-coreutils/pkg/uio"
    18  )
    19  
    20  const (
    21  	bootParams = "/sys/kernel/boot_params/data"
    22  )
    23  
    24  // KexecLoad loads a bzImage-formated Linux kernel file as the to-be-kexeced
    25  // kernel with the given ramfs file and cmdline string.
    26  //
    27  // It uses the kexec_load system call.
    28  func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error {
    29  	bzimage.Debug = Debug
    30  
    31  	// A collection of vars used for processing the kernel for kexec
    32  	var err error
    33  	// bzimage is the deserialized bzImage from the kernel
    34  	// io.ReaderAt.
    35  	var bzimg bzimage.BzImage
    36  	// kmem is a struct holding kexec segments.
    37  	//
    38  	// It has routines to work with physical memory
    39  	// ranges.
    40  	var kmem *kexec.Memory
    41  	// TODO(10000TB): construct default params in go.
    42  	//
    43  	// boot_params directory is x86 specific. So for now, following code only
    44  	// works on x86.
    45  	// https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-kernel-boot_params
    46  	bp, err := ioutil.ReadFile("/sys/kernel/boot_params/data")
    47  	if err != nil {
    48  		return fmt.Errorf("reading boot_param data: %w", err)
    49  	}
    50  	var lp = &bzimage.LinuxParams{}
    51  	if err := lp.UnmarshalBinary(bp); err != nil {
    52  		return fmt.Errorf("unmarshaling header: %w", err)
    53  	}
    54  
    55  	kb, err := uio.ReadAll(kernel)
    56  	if err != nil {
    57  		return fmt.Errorf("reading Linux kernel into memory: %w", err)
    58  	}
    59  	if err := bzimg.UnmarshalBinary(kb); err != nil {
    60  		return fmt.Errorf("parsing bzImage Linux kernel: %w", err)
    61  	}
    62  
    63  	if len(bzimg.KernelCode) < 1024 {
    64  		return fmt.Errorf("kernel code size smaller than 1024 bytes: %d", len(bzimg.KernelCode))
    65  	}
    66  
    67  	kelf, err := bzimg.ELF()
    68  	if err != nil {
    69  		return fmt.Errorf("getting ELF from bzImage: %w", err)
    70  	}
    71  	kernelEntry := uintptr(kelf.Entry)
    72  	Debug("kernelEntry: %v", kernelEntry)
    73  
    74  	// Prepare segments.
    75  	kmem = &kexec.Memory{}
    76  	Debug("Try parsing memory map...")
    77  	// TODO(10000TB): refactor this call into initialization of
    78  	// kexec.Memory, as it does not depend on specific boot.
    79  	if err := kmem.ParseMemoryMap(); err != nil {
    80  		return fmt.Errorf("parse memory map: %v", err)
    81  	}
    82  
    83  	var relocatableKernel bool
    84  	if bzimg.Header.Protocolversion < 0x0205 {
    85  		return fmt.Errorf("bzImage boot protocol earlier thatn 2.05 is not supported currently: %v", bzimg.Header.Protocolversion)
    86  	}
    87  	relocatableKernel = bzimg.Header.RelocatableKernel != 0
    88  	// Only protected mode is currently supported.
    89  	// In protected mode, kernel need be relocatable, or it will need to fall
    90  	// to real mode executing.
    91  	if !relocatableKernel {
    92  		return errors.New("non-relocateable Kernels are not supported")
    93  	}
    94  	if _, err := kmem.LoadElfSegments(bytes.NewReader(bzimg.KernelCode)); err != nil {
    95  		return fmt.Errorf("loading kernel ELF segments: %w", err)
    96  	}
    97  
    98  	var ramfsRange kexec.Range
    99  	if ramfs != nil {
   100  		ramfsContents, err := ioutil.ReadAll(ramfs)
   101  		if err != nil {
   102  			return fmt.Errorf("unable to read initramfs: %w", err)
   103  		}
   104  		if ramfsRange, err = kmem.AddKexecSegment(ramfsContents); err != nil {
   105  			return fmt.Errorf("add initramfs segment: %w", err)
   106  		}
   107  		Debug("Added %d byte initramfs at %s", len(ramfsContents), ramfsRange)
   108  		lp.Initrdstart = uint32(ramfsRange.Start)
   109  		lp.Initrdsize = uint32(ramfsRange.Size)
   110  	}
   111  
   112  	Debug("Kernel cmdline to append: %s", cmdline)
   113  	if len(cmdline) > 0 {
   114  		var cmdlineRange kexec.Range
   115  		Debug("Add cmdline: %s", cmdline)
   116  
   117  		// Cmdline must be null-terminated.
   118  		cmdlineBytes := []byte(cmdline + "\x00")
   119  		if cmdlineRange, err = kmem.AddKexecSegment(cmdlineBytes); err != nil {
   120  			return fmt.Errorf("add cmdline segment: %v", err)
   121  		}
   122  		Debug("Added %d byte of cmdline at %s", len(cmdlineBytes), cmdlineRange)
   123  		lp.CLPtr = uint32(cmdlineRange.Start)      // 2.02+
   124  		lp.CmdLineSize = uint32(cmdlineRange.Size) // 2.06+
   125  	}
   126  
   127  	// The kernel is a bzImage kernel if the protocol >= 2.00 and the 0x01
   128  	// bit (LOAD_HIGH) in the loadflags field is set.
   129  	// TODO(10000TB): check on loadflags.
   130  	linuxParam, err := lp.MarshalBinary()
   131  	if err != nil {
   132  		return fmt.Errorf("re-marshaling header: %w", err)
   133  	}
   134  
   135  	// TODO(10000TB): Free mem hole start end aligns by
   136  	// max(16, pagesize).
   137  	//
   138  	// Push alignment logic to kexec memory functions, e.g. a similar
   139  	// function to FindSpace.
   140  	setupRange, err := kmem.AddPhysSegment(
   141  		linuxParam,
   142  		kexec.RangeFromInterval(
   143  			uintptr(0x90000),
   144  			uintptr(len(linuxParam)),
   145  		),
   146  		// TODO(10000TB): evaluate if we need to provide  option to
   147  		// reserve from end.
   148  		//
   149  		// Our go code defaults to pick up a mem block of requested
   150  		// size from beginning, e.g.
   151  		//
   152  		//   [Range.Start, Range.Start+memsz)
   153  		//
   154  		// Kexec userspace use the range from end, e.g.
   155  		//
   156  		//   [Range.end-memsz+1, Range.end)
   157  		//
   158  	)
   159  	if err != nil {
   160  		return fmt.Errorf("add real mode data and cmdline: %v", err)
   161  	}
   162  
   163  	Debug("Loaded real mode data and cmdline at: %v", setupRange)
   164  
   165  	// Verify purgatory loads higher than the parameters.
   166  	// TODO(10000TB): if rel_addr < setupRange.Start then return error.
   167  
   168  	// Load purgatory.
   169  	purgatoryEntry, err := purgatory.Load(kmem, kernelEntry, setupRange.Start)
   170  	if err != nil {
   171  		return err
   172  	}
   173  	Debug("purgatory entry: %v", purgatoryEntry)
   174  
   175  	// Load it.
   176  	if err := kexec.Load(purgatoryEntry, kmem.Segments, 0); err != nil {
   177  		return fmt.Errorf("kexec load(%v, %v, %d): %w", purgatoryEntry, kmem.Segments, 0, err)
   178  	}
   179  	return nil
   180  }