github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/boot/linux/load_linux_arm64.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  	"encoding/binary"
    10  	"fmt"
    11  	"os"
    12  	"syscall"
    13  
    14  	"github.com/mvdan/u-root-coreutils/pkg/boot/image"
    15  	"github.com/mvdan/u-root-coreutils/pkg/boot/kexec"
    16  	"github.com/mvdan/u-root-coreutils/pkg/dt"
    17  	"github.com/mvdan/u-root-coreutils/pkg/uio"
    18  	"golang.org/x/sys/unix"
    19  )
    20  
    21  const (
    22  	kernelAlignSize = 1 << 21 // 2 MB.
    23  )
    24  
    25  func mmap(f *os.File) (data []byte, ummap func() error, err error) {
    26  	s, err := f.Stat()
    27  	if err != nil {
    28  		return nil, nil, fmt.Errorf("stat error: %w", err)
    29  	}
    30  	if s.Size() == 0 {
    31  		return nil, nil, fmt.Errorf("cannot mmap zero-len file")
    32  	}
    33  	d, err := unix.Mmap(int(f.Fd()), 0, int(s.Size()), syscall.PROT_READ, syscall.MAP_PRIVATE)
    34  	if err != nil {
    35  		return nil, nil, fmt.Errorf("mmap failed: %w", err)
    36  	}
    37  
    38  	ummap = func() error {
    39  		return unix.Munmap(d)
    40  	}
    41  
    42  	return d, ummap, nil
    43  }
    44  
    45  // sanitizeFDT cleanups boot param properties from chosen node of the given FDT.
    46  func sanitizeFDT(fdt *dt.FDT) (*dt.Node, error) {
    47  	// Clear old entries in case we've already been through kexec to get
    48  	// to this instance of runtime.
    49  	chosen, _ := fdt.NodeByName("chosen")
    50  	if chosen == nil {
    51  		return nil, fmt.Errorf("no /chosen node in device tree")
    52  	}
    53  	for _, property := range []string{"linux,elfcorehdr", "linux,usable-memory-range", "kaslr-seed", "rng-seed", "linux,initrd-start", "linux,initrd-end"} {
    54  		chosen.RemoveProperty(property)
    55  	}
    56  
    57  	return chosen, nil
    58  }
    59  
    60  // KexecLoad loads arm64 Image, with the given ramfs and kernel cmdline.
    61  func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error {
    62  	var err error
    63  	// kmem is a struct holding kexec segments.
    64  	//
    65  	// It has routines to work with physical memory
    66  	// ranges.
    67  	var kmem *kexec.Memory
    68  	var kernelRange, ramfsRange, dtbRange, trampolineRange kexec.Range
    69  
    70  	fdt, err := dt.LoadFDT(opts.DTB)
    71  	if err != nil {
    72  		return fmt.Errorf("loadFDT(%s) = %v", opts.DTB, err)
    73  	}
    74  	Debug("Loaded FDT: %s", fdt)
    75  
    76  	chosen, err := sanitizeFDT(fdt)
    77  	if err != nil {
    78  		return fmt.Errorf("sanitizeFDT(%v) = %v", fdt, err)
    79  	}
    80  	Debug("FDT after sanitization: %s", fdt)
    81  
    82  	// Prepare segments.
    83  	kmem = &kexec.Memory{}
    84  	Debug("Try parsing memory map...")
    85  	if err := kmem.ParseMemoryMapFromFDT(fdt); err != nil {
    86  		return fmt.Errorf("ParseMemoryMapFromFDT(%v): %v", fdt, err)
    87  	}
    88  	Debug("Mem map: \n%+v", kmem.Phys)
    89  
    90  	// Load kernel.
    91  	var kernelBuf []byte
    92  	if opts.MmapKernel {
    93  		Debug("Mmapping kernel to virtual buffer...")
    94  		var cleanup func() error
    95  		kernelBuf, cleanup, err = mmap(kernel)
    96  		if err != nil {
    97  			return fmt.Errorf("mmap kernel: %v", err)
    98  		}
    99  		defer func() {
   100  			if err = cleanup(); err != nil {
   101  				Debug("Ummap kernel failed: %v", err)
   102  			}
   103  		}()
   104  	} else {
   105  		Debug("Read kernel from file ...")
   106  		kernelBuf, err = uio.ReadAll(kernel)
   107  		if err != nil {
   108  			return fmt.Errorf("read kernel from file: %v", err)
   109  		}
   110  	}
   111  
   112  	kImage, err := image.ParseFromBytes(kernelBuf)
   113  	if err != nil {
   114  		return fmt.Errorf("parse arm64 Image from bytes: %v", err)
   115  	}
   116  
   117  	if kernelRange, err = kmem.AddKexecSegmentExplicit(kernelBuf, uint(kImage.Header.ImageSize+kImage.Header.TextOffset), uint(kImage.Header.TextOffset), kernelAlignSize); err != nil {
   118  		return fmt.Errorf("add kernel segment: %v", err)
   119  	}
   120  
   121  	Debug("Added %d byte (size %d) kernel at %s", len(kernelBuf), kImage.Header.ImageSize, kernelRange)
   122  
   123  	var ramfsBuf []byte
   124  	if ramfs != nil {
   125  		if opts.MmapRamfs {
   126  			Debug("Mmap ramfs file to virtual buffer...")
   127  			var cleanup func() error
   128  			ramfsBuf, cleanup, err = mmap(ramfs)
   129  			if err != nil {
   130  				return fmt.Errorf("mmap ramfs: %v", err)
   131  			}
   132  			defer func() {
   133  				if err = cleanup(); err != nil {
   134  					Debug("Ummap ramfs failed: %v", err)
   135  				}
   136  			}()
   137  		} else {
   138  			Debug("Read ramfs from file...")
   139  			ramfsBuf, err = uio.ReadAll(ramfs)
   140  			if err != nil {
   141  				return fmt.Errorf("read ramfs from file: %v", err)
   142  			}
   143  		}
   144  	}
   145  
   146  	// NOTE(10000TB): This need be placed after kernel by convention.
   147  	if ramfsRange, err = kmem.AddKexecSegment(ramfsBuf); err != nil {
   148  		return fmt.Errorf("add initramfs segment: %v", err)
   149  	}
   150  	Debug("Added %d byte initramfs at %s", len(ramfsBuf), ramfsRange)
   151  
   152  	ramfsStart := make([]byte, 8)
   153  	binary.BigEndian.PutUint64(ramfsStart, uint64(ramfsRange.Start))
   154  	chosen.UpdateProperty("linux,initrd-start", ramfsStart)
   155  	ramfsEnd := make([]byte, 8)
   156  	binary.BigEndian.PutUint64(ramfsEnd, uint64(ramfsRange.Start)+uint64(ramfsRange.Size))
   157  	chosen.UpdateProperty("linux,initrd-end", ramfsEnd)
   158  
   159  	Debug("Kernel cmdline to append: %s", cmdline)
   160  	if len(cmdline) > 0 {
   161  		cmdlineBuf := append([]byte(cmdline), byte(0))
   162  		chosen.UpdateProperty("bootargs", cmdlineBuf)
   163  	} else {
   164  		chosen.RemoveProperty("bootargs")
   165  	}
   166  
   167  	dtbBuffer := &bytes.Buffer{}
   168  	_, err = fdt.Write(dtbBuffer)
   169  	if err != nil {
   170  		return fmt.Errorf("flattening device tree: %v", err)
   171  	}
   172  	dtbBuf := dtbBuffer.Bytes()
   173  	if dtbRange, err = kmem.AddKexecSegment(dtbBuf); err != nil {
   174  		return fmt.Errorf("add device tree segment: %w", err)
   175  	}
   176  	Debug("Added %d byte device tree at %s", len(dtbBuf), dtbRange)
   177  
   178  	// Trampoline.
   179  	//
   180  	// We need a trampoline to pass the DTB to the kernel; because
   181  	// we'll use this code as our entry point, it also needs to know
   182  	// the real entry point to kernel.
   183  	//
   184  	// TODO(10000TB): this assumes a little endian kernel, support
   185  	// big endian if needed per flag.
   186  	kernelEntry := kernelRange.Start
   187  	dtbBase := dtbRange.Start
   188  
   189  	var trampoline [10]uint32
   190  	// Instruction encoding per
   191  	// "Arm Architecture Reference Manual Armv8, for Armv8-A architecture
   192  	// profile" [ ARM DDI 0487E.a (ID070919) ]
   193  	trampoline[0] = 0x580000c4 // ldr x4, #0x18 (PC relative: trampoline[6 and 7])
   194  	trampoline[1] = 0x580000e0 // ldr x0, #0x1c (PC relative: trampoline[8 and 9])
   195  	// Zero out x1, x2, x3
   196  	trampoline[2] = 0xaa1f03e1 // mov x1, xzr
   197  	trampoline[3] = 0xaa1f03e2 // mov x2, xzr
   198  	trampoline[4] = 0xaa1f03e3 // mov x3, xzr
   199  	// Branch register / Jump to instruction from x4.
   200  	trampoline[5] = 0xd61f0080 // br  x4
   201  
   202  	trampoline[6] = uint32(uint64(kernelEntry) & 0xffffffff)
   203  	trampoline[7] = uint32(uint64(kernelEntry) >> 32)
   204  	trampoline[8] = uint32(uint64(dtbBase) & 0xffffffff)
   205  	trampoline[9] = uint32(uint64(dtbBase) >> 32)
   206  
   207  	trampolineBuffer := new(bytes.Buffer)
   208  	err = binary.Write(trampolineBuffer, binary.LittleEndian, trampoline)
   209  	if err != nil {
   210  		return fmt.Errorf("make trampoline: %v", err)
   211  	}
   212  	Debug("trampoline bytes %x", trampolineBuffer.Bytes())
   213  	trampolineRange, err = kmem.AddKexecSegment(trampolineBuffer.Bytes())
   214  	if err != nil {
   215  		return fmt.Errorf("add trampoline segment: %v", err)
   216  	}
   217  	Debug("Added %d byte trampoline at %s", len(trampolineBuffer.Bytes()), trampolineRange)
   218  
   219  	/* Load it */
   220  	entry := trampolineRange.Start
   221  	Debug("Entry: %#x", entry)
   222  	if err = kexec.Load(entry, kmem.Segments, 0); err != nil {
   223  		return fmt.Errorf("kexec Load(%v, %v, %d) = %v", entry, kmem.Segments, 0, err)
   224  	}
   225  
   226  	return nil
   227  }