Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 | #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0+ """ Script to build an EFI thing suitable for booting with QEMU, possibly running it also. UEFI binaries for QEMU used for testing this script: OVMF-pure-efi.i386.fd at https://drive.google.com/file/d/1jWzOAZfQqMmS2_dAK2G518GhIgj9r2RY/view?usp=sharing OVMF-pure-efi.x64.fd at https://drive.google.com/file/d/1c39YI9QtpByGQ4V0UNNQtGqttEzS-eFV/view?usp=sharing Use ~/.build-efi to configure the various paths used by this script. When --bootcmd is specified, a uboot.env file is created on the EFI partition containing the boot command. U-Boot needs to be configured to import this file on startup, for example by adding to CONFIG_PREBOOT or the default bootcmd: load ${devtype} ${devnum}:${distro_bootpart} ${loadaddr} uboot.env; \ env import -t ${loadaddr} ${filesize} """ from argparse import ArgumentParser import os from pathlib import Path import shutil import sys import tempfile import build_helper # pylint: disable=C0413 from u_boot_pylib import command from u_boot_pylib import tools from u_boot_pylib import tout def parse_args(): """Parse the program arguments Return: Namespace object """ parser = ArgumentParser( epilog='Script for running U-Boot as an EFI app/payload') build_helper.add_common_args(parser) parser.add_argument('-g', '--debug', action='store_true', help="Run QEMU with gdb") parser.add_argument('--write-kernel', action='store_true', help='Add a kernel to the disk image') parser.add_argument('-O', '--old', action='store_true', help='Use old EFI app build (before 32/64 split)') parser.add_argument('-p', '--payload', action='store_true', help='Package up the payload instead of the app') parser.add_argument('-P', '--partition', action='store_true', help='Create a partition table') parser.add_argument('--spice', action='store_true', help='Enable SPICE for clipboard sharing') parser.add_argument('-N', '--net', action='store_true', help='Enable networking (with SSH forwarding on port 2222)') parser.add_argument('-v', '--verbose', action='store_true', help='Show executed commands') parser.add_argument('--include-dir', help='Directory containing additional files to include in the image') args = parser.parse_args() return args class BuildEfi: """Class to collect together the various bits of state while running""" def __init__(self, args): self.helper = build_helper.Helper(args) self.helper.read_settings() self.img_fname = self.helper.get_setting('efi_image_file', 'efi.img') self.img = None self.build_topdir = self.helper.get_setting("build_dir", '/tmp') self.build_dir = None self.args = args self.imagedir = Path(self.helper.get_setting('image_dir', '~/dev')) def run_qemu(self, serial_only): """Run QEMU Args: serial_only (bool): True to run without a display """ extra = [] efi_dir = self.helper.get_setting('efi_dir') if self.args.arch == 'arm': qemu_arch = 'aarch64' extra += ['--machine', 'virt', '-cpu', 'max'] bios = os.path.join(efi_dir, 'OVMF-pure-efi.aarch64.fd.64m') var_store = os.path.join(efi_dir, 'varstore.img') extra += [ '-drive', f'if=pflash,format=raw,file={bios},readonly=on', '-drive', f'if=pflash,format=raw,file={var_store}' ] extra += ['-drive', f'if=virtio,file={self.img},format=raw,id=hd0'] elif self.args.arch == 'riscv': qemu_arch = 'riscv64' extra += ['--machine', 'virt'] bios = os.path.join(efi_dir, 'RISCV_VIRT_CODE.fd') if not os.path.exists(bios): bios = '/usr/share/qemu-efi-riscv64/RISCV_VIRT_CODE.fd' var_store = os.path.join(efi_dir, 'RISCV_VIRT_VARS.fd') if not os.path.exists(var_store): sys_var = '/usr/share/qemu-efi-riscv64/RISCV_VIRT_VARS.fd' shutil.copy(sys_var, var_store) extra += [ '-drive', f'if=pflash,format=raw,file={bios},readonly=on', '-drive', f'if=pflash,format=raw,file={var_store}' ] extra += ['-drive', f'if=virtio,file={self.img},format=raw,id=hd0'] else: # x86 if self.helper.bitness == 64: qemu_arch = 'x86_64' bios = 'OVMF-release-x64.fd' else: qemu_arch = 'i386' bios = 'OVMF-pure-efi.i386.fd' bios = os.path.join(efi_dir, bios) var_store = os.path.join(efi_dir, 'OVMF_VARS_4M.fd') extra += [ '-drive', f'if=pflash,format=raw,file={bios},readonly=on', '-drive', f'if=pflash,format=raw,file={var_store}' ] extra += ['-drive', f'id=disk,file={self.img},if=none,format=raw'] extra += ['-device', 'ahci,id=ahci'] extra += ['-device', 'ide-hd,drive=disk,bus=ahci.0'] qemu = f'qemu-system-{qemu_arch}' if serial_only: extra += ['-display', 'none', '-serial', 'mon:stdio'] serial_msg = ' (Ctrl-a x to quit)' else: if self.args.arch in ('arm', 'riscv'): extra += ['-device', 'virtio-gpu-pci'] extra += ['-device', 'qemu-xhci', '-device', 'usb-kbd', '-device', 'usb-tablet'] extra += ['-display', 'default,show-cursor=on'] else: # x86 extra += ['-device', 'qemu-xhci', '-device', 'usb-kbd', '-device', 'usb-mouse'] # This uses QEMU's GTK clipboard integration with SPICE vdagent if self.args.spice: extra += ['-device', 'virtio-serial-pci'] extra += ['-chardev', 'qemu-vdagent,id=spicechannel0,name=vdagent,clipboard=on'] extra += ['-device', 'virtserialport,chardev=spicechannel0,name=com.redhat.spice.0'] extra += ['-serial', 'mon:stdio'] serial_msg = '' if self.args.kvm: extra.extend(['-enable-kvm', '-cpu', 'host']) print(f'Running {qemu}{serial_msg}') # Use 512MB since U-Boot EFI likes to have 256MB to play with if self.args.os or self.args.disk: mem = '4G' extra.extend(['-smp', '4']) else: mem = '1G' if self.args.debug: extra.extend(['-s', '-S']) cmd = [qemu] cmd += '-m', mem if self.args.net: cmd += '-netdev', 'user,id=net0,hostfwd=tcp::2222-:22' cmd += '-device', 'virtio-net-pci,netdev=net0' else: cmd += '-nic', 'none' cmd += extra self.helper.add_qemu_args(self.args, cmd, base_hd=1) tout.info(' '.join(cmd)) sys.stdout.flush() command.run(*cmd) def setup_files(self, build, build_type, dst): """Set up files in the staging area Args: build (str): Name of build being packaged, e.g. 'efi-x86_app32' build_type (str): Build type ('app' or 'payload') dst (str): Destination directory """ print(f'Packaging {build}') if self.args.custom: dirname, fname = os.path.split(self.args.custom) if not dirname: dirname = '.' else: fname = f'u-boot-{build_type}.efi' dirname = f'{self.build_dir}/' tools.write_file(f'{dst}/startup.nsh', f'fs0:{fname}', binary=False) shutil.copy(f'{dirname}/{fname}', dst) # Copy additional files from include directory if specified if self.args.include_dir: include_path = Path(self.args.include_dir) if include_path.exists() and include_path.is_dir(): print(f'Including files from {include_path}') for item in include_path.iterdir(): if item.is_file(): print(f' Copying {item.name}') shutil.copy(str(item), dst) elif item.is_dir(): dest_dir = Path(dst) / item.name print(f' Copying directory {item.name}') shutil.copytree(str(item), str(dest_dir)) else: print(f'Warning: Include directory {include_path} does not exist or is not a directory') # Write U-Boot environment file if bootcmd is specified if self.args.bootcmd: # Check if mkenvimage is available (local build or system-wide) mkenvimage = 'tools/mkenvimage' if not os.path.exists(mkenvimage): mkenvimage = 'mkenvimage' if not shutil.which(mkenvimage): tout.error('Please install u-boot-tools package:') tout.error(' sudo apt install u-boot-tools') raise FileNotFoundError('mkenvimage not found') # Create text environment file env_content = f'bootcmd={self.args.bootcmd}\n' with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as outf: outf.write(env_content) env_fname = outf.name try: # Convert to binary format with CRC using mkenvimage command.run(mkenvimage, '-s', '0x1000', '-o', f'{dst}/uboot.env', env_fname) print(f'Created uboot.env with bootcmd: {self.args.bootcmd}') finally: os.unlink(env_fname) def do_build(self, build): """Build U-Boot for the selected board""" extra = ['-a', '~CONSOLE_PAGER'] if self.args.no_pager else [] res = command.run_one('buildman', '-w', '-o', self.build_dir, *extra, '--board', build, '-I', raise_on_error=False) if res.return_code and res.return_code != 101: # Allow warnings raise ValueError( f'buildman exited with {res.return_code}: {res.combined}') def start(self): """This does all the work""" args = self.args if self.args.arch == 'arm': arch = 'arm' elif self.args.arch == 'riscv': arch = 'riscv' else: arch = 'x86' build_type = 'payload' if args.payload else 'app' build = f'efi-{arch}_{build_type}{self.helper.bitness}' if args.build_dir: self.build_dir = args.build_dir self.img = f'{self.build_dir}/{self.img_fname}' else: self.build_dir = f'{self.build_topdir}/{build}' self.img = self.img_fname if not args.no_build: self.do_build(build) if args.old and self.helper.bitness == 32: build = f'efi-{arch}_{build_type}' with self.helper.make_disk(self.img, fs_type='vfat', use_part=args.partition) as dirpath: self.setup_files(build, build_type, dirpath) if self.args.write_kernel: bzimage = self.helper.get_setting('bzimage_file', 'bzImage') command.run('cp', bzimage, f'{dirpath}/vmlinuz') if args.run: self.run_qemu(args.serial_only) def main(): """Parse arguments and start the program""" args = parse_args() tout.init(tout.INFO if args.verbose else tout.WARNING) qemu = BuildEfi(args) qemu.start() if __name__ == '__main__': main() |