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 }