michael.hudson@canonical.com edf0acbeac
Add Python boot configuration package
Add architecture-specific boot configurators that translate the
debian-cd boot shell scripts (boot-amd64, boot-arm64, boot-ppc64el,
boot-riscv64, boot-s390x) into Python.

The package uses a class hierarchy:
- BaseBootConfigurator: abstract base with common functionality
- GrubBootConfigurator: shared GRUB config generation
- UEFIBootConfigurator: UEFI-specific shim/ESP handling
- Architecture classes: AMD64, ARM64, PPC64EL, RISCV64, S390X

A factory function make_boot_configurator_for_arch() creates the
appropriate configurator for each architecture.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 10:47:13 +13:00

224 lines
6.9 KiB
Python

"""RISC-V 64-bit architecture boot configuration."""
import pathlib
import shutil
from .grub import GrubBootConfigurator
def copy_unsigned_monolithic_grub_to_boot_tree(
grub_dir: pathlib.Path, efi_suffix: str, grub_target: str, boot_tree: pathlib.Path
) -> None:
efi_boot_dir = boot_tree.joinpath("EFI", "boot")
efi_boot_dir.mkdir(parents=True, exist_ok=True)
shutil.copy(
grub_dir.joinpath(
"usr",
"lib",
"grub",
f"{grub_target}-efi",
"monolithic",
f"gcd{efi_suffix}.efi",
),
efi_boot_dir.joinpath(f"boot{efi_suffix}.efi"),
)
grub_boot_dir = boot_tree.joinpath("boot", "grub", f"{grub_target}-efi")
grub_boot_dir.mkdir(parents=True, exist_ok=True)
src_grub_dir = grub_dir.joinpath("usr", "lib", "grub", f"{grub_target}-efi")
for mod_file in src_grub_dir.glob("*.mod"):
shutil.copy(mod_file, grub_boot_dir)
for lst_file in src_grub_dir.glob("*.lst"):
shutil.copy(lst_file, grub_boot_dir)
class RISCV64BootConfigurator(GrubBootConfigurator):
"""Boot setup for RISC-V 64-bit architecture."""
def mkisofs_opts(self) -> list[str | pathlib.Path]:
"""Return mkisofs options for RISC-V64."""
efi_img = self.scratch.joinpath("efi.img")
return [
"-joliet",
"on",
"-compliance",
"joliet_long_names",
"--append_partition",
"2",
"0xef",
efi_img,
"-boot_image",
"any",
"partition_offset=10240",
"-boot_image",
"any",
"partition_cyl_align=all",
"-boot_image",
"any",
"efi_path=--interval:appended_partition_2:all::",
"-boot_image",
"any",
"appended_part_as=gpt",
"-boot_image",
"any",
"cat_path=/boot/boot.cat",
"-fs",
"64m",
]
def extract_files(self) -> None:
"""Download and extract bootloader packages for RISC-V64."""
self.logger.log("extracting RISC-V64 boot files")
u_boot_dir = self.scratch.joinpath("u-boot-sifive")
# Download and extract bootloader packages
self.download_and_extract_package("grub2-common", self.grub_dir)
self.download_and_extract_package("grub-efi-riscv64-bin", self.grub_dir)
self.download_and_extract_package("grub-efi-riscv64-unsigned", self.grub_dir)
self.download_and_extract_package("u-boot-sifive", u_boot_dir)
# Add GRUB to tree
self.setup_grub_common_files()
copy_unsigned_monolithic_grub_to_boot_tree(
self.grub_dir, "riscv64", "riscv64", self.boot_tree
)
# Extract DTBs to tree
self.logger.log("extracting device tree files")
kernel_layer = self.scratch.joinpath("kernel-layer")
squashfs_path = self.iso_root.joinpath(
"casper", "ubuntu-server-minimal.squashfs"
)
# Extract device tree firmware from squashfs
self.logger.run(
[
"unsquashfs",
"-no-xattrs",
"-d",
kernel_layer,
squashfs_path,
"usr/lib/firmware",
],
check=True,
)
# Copy DTBs if they exist
dtb_dir = self.boot_tree.joinpath("dtb")
dtb_dir.mkdir(parents=True, exist_ok=True)
firmware_dir = kernel_layer.joinpath("usr", "lib", "firmware")
device_tree_files = list(firmware_dir.glob("*/device-tree/*"))
if device_tree_files:
for dtb_file in device_tree_files:
if dtb_file.is_file():
shutil.copy(dtb_file, dtb_dir)
# Clean up kernel layer
shutil.rmtree(kernel_layer)
# Copy tree contents to live-media rootfs
self.logger.run(["cp", "-aT", self.boot_tree, self.iso_root], check=True)
# Create ESP image with GRUB and dtbs
efi_img = self.scratch.joinpath("efi.img")
self.logger.run(
["mkfs.msdos", "-n", "ESP", "-C", "-v", efi_img, "32768"], check=True
)
# Add EFI files to ESP
efi_dir = self.boot_tree.joinpath("EFI")
self.logger.run(["mcopy", "-s", "-i", efi_img, efi_dir, "::/."], check=True)
# Add DTBs to ESP
self.logger.run(["mcopy", "-s", "-i", efi_img, dtb_dir, "::/."], check=True)
def generate_grub_config(self) -> None:
"""Generate grub.cfg for RISC-V64."""
grub_dir = self.iso_root.joinpath("boot", "grub")
grub_dir.mkdir(parents=True, exist_ok=True)
grub_cfg = grub_dir.joinpath("grub.cfg")
# Write GRUB header (without loadfont for RISC-V)
self.write_grub_header(grub_cfg, include_loadfont=False)
# Main menu entry
with grub_cfg.open("a") as f:
f.write(
f"""menuentry "Try or Install {self.humanproject}" {{
\tset gfxpayload=keep
\tlinux\t/casper/vmlinux efi=debug sysctl.kernel.watchdog_thresh=60 ---
\tinitrd\t/casper/initrd
}}
"""
)
# HWE kernel option if available
self.write_hwe_menu_entry(
grub_cfg,
"vmlinux",
"---",
extra_params="efi=debug sysctl.kernel.watchdog_thresh=60 ",
)
def post_process_iso(self, iso_path: pathlib.Path) -> None:
"""Add GPT partitions with U-Boot for SiFive Unmatched board.
The SiFive Unmatched board needs a GPT table containing U-Boot in
order to boot. U-Boot does not currently support booting from a CD,
so the GPT table also contains an entry pointing to the ESP so that
U-Boot can find it.
"""
u_boot_dir = self.scratch.joinpath(
"u-boot-sifive", "usr", "lib", "u-boot", "sifive_unmatched"
)
self.logger.run(
[
"sgdisk",
iso_path,
"--set-alignment=2",
"-d",
"1",
"-n",
"1:2082:10273",
"-c",
"1:loader2",
"-t",
"1:2E54B353-1271-4842-806F-E436D6AF6985",
"-n",
"3:10274:12321",
"-c",
"3:loader1",
"-t",
"3:5B193300-FC78-40CD-8002-E86C45580B47",
"-c",
"2:ESP",
"-r=2:3",
],
)
self.logger.run(
[
"dd",
f"if={u_boot_dir / 'u-boot.itb'}",
f"of={iso_path}",
"bs=512",
"seek=2082",
"conv=notrunc",
],
)
self.logger.run(
[
"dd",
f"if={u_boot_dir / 'u-boot-spl.bin'}",
f"of={iso_path}",
"bs=512",
"seek=10274",
"conv=notrunc",
],
)