Files
retroSoC/scripts/build_hp_linux.py
T

238 lines
7.3 KiB
Python

#!/usr/bin/env python3
"""Build the pinned RV32 HP Buildroot, Linux, DTB, and OpenSBI images."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import shutil
import subprocess
from pathlib import Path
LAYOUT = {
"fw_jump.bin": (0x38000000, 512 * 1024),
"retrosoc_hp.dtb": (0x38080000, 64 * 1024),
"Image": (0x38400000, 12 * 1024 * 1024),
"rootfs.cpio.gz": (0x39000000, 8 * 1024 * 1024),
}
REQUIRED_LINUX_CONFIG = (
"CONFIG_BINFMT_ELF",
"CONFIG_BINFMT_SCRIPT",
"CONFIG_PRINTK",
"CONFIG_TTY",
"CONFIG_HVC_RISCV_SBI",
"CONFIG_SERIAL_EARLYCON_RISCV_SBI",
)
def command(arguments: list[str], cwd: Path) -> None:
environment = dict(os.environ)
environment.pop("CONFIG", None)
environment.pop("MAKEFLAGS", None)
environment.pop("MAKEOVERRIDES", None)
environment.pop("MFLAGS", None)
subprocess.run(arguments, cwd=cwd, check=True, env=environment)
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def require_source(path: Path, name: str) -> Path:
resolved = path.resolve()
if not (resolved / ".git").exists():
raise FileNotFoundError(f"missing locked {name} source: {resolved}; run setup-hp-linux")
return resolved
def source_revision(path: Path) -> str:
return subprocess.check_output(
["git", "-C", str(path), "rev-parse", "HEAD"], text=True
).strip()
def find_toolchain(buildroot_output: Path) -> str:
matches = sorted((buildroot_output / "host/bin").glob("riscv32*-linux-*-gcc"))
if len(matches) != 1:
raise RuntimeError(f"expected one RV32 Linux GCC in {buildroot_output / 'host/bin'}")
return str(matches[0])[: -len("gcc")]
def validate_linux_config(config: Path) -> None:
values: dict[str, str] = {}
for line in config.read_text(encoding="utf-8").splitlines():
if line.startswith("CONFIG_") and "=" in line:
symbol, value = line.split("=", 1)
values[symbol] = value
elif line.startswith("# CONFIG_") and line.endswith(" is not set"):
symbol = line[len("# ") : -len(" is not set")]
values[symbol] = "n"
invalid = [
f"{symbol}={values.get(symbol, 'missing')}"
for symbol in REQUIRED_LINUX_CONFIG
if values.get(symbol) != "y"
]
if invalid:
raise RuntimeError(
"invalid HP Linux effective configuration: " + ", ".join(invalid)
)
def build(args: argparse.Namespace) -> None:
root = args.root.resolve()
output = args.output.resolve()
output.mkdir(parents=True, exist_ok=True)
buildroot = require_source(args.buildroot, "Buildroot")
linux = require_source(args.linux, "Linux")
opensbi = require_source(args.opensbi, "OpenSBI")
external = root / "app/ports/linux"
buildroot_output = output / "buildroot"
command(
["make", f"O={buildroot_output}", f"BR2_EXTERNAL={external}", "retrosoc_hp_defconfig"],
buildroot,
)
command(["make", f"O={buildroot_output}", f"BR2_EXTERNAL={external}", f"-j{args.jobs}"], buildroot)
cross_compile = find_toolchain(buildroot_output)
linux_output = output / "linux"
command(
["make", f"O={linux_output}", "ARCH=riscv", f"CROSS_COMPILE={cross_compile}", "tinyconfig"],
linux,
)
command(
[
str(linux / "scripts/kconfig/merge_config.sh"),
"-m",
"-O",
str(linux_output),
str(linux_output / ".config"),
str(external / "linux/retrosoc_hp.config"),
],
linux,
)
command(
["make", f"O={linux_output}", "ARCH=riscv", f"CROSS_COMPILE={cross_compile}", "olddefconfig"],
linux,
)
validate_linux_config(linux_output / ".config")
command(
["make", f"O={linux_output}", "ARCH=riscv", f"CROSS_COMPILE={cross_compile}",
f"-j{args.jobs}", "Image"],
linux,
)
image_dir = output / "images"
image_dir.mkdir(parents=True, exist_ok=True)
rootfs_source = buildroot_output / "images/rootfs.cpio.gz"
if not rootfs_source.is_file():
raise FileNotFoundError(f"HP Linux build output is missing: {rootfs_source}")
initrd_end = LAYOUT["rootfs.cpio.gz"][0] + rootfs_source.stat().st_size
command(
[
"dtc",
"-I",
"dts",
"-O",
"dtb",
"-o",
str(image_dir / "retrosoc_hp.dtb"),
str(external / "linux/retrosoc_hp.dts"),
],
root,
)
command(
[
"fdtput",
"-t",
"x",
str(image_dir / "retrosoc_hp.dtb"),
"/chosen",
"linux,initrd-end",
f"0x{initrd_end:08x}",
],
root,
)
opensbi_output = output / "opensbi"
command(
[
"make",
f"O={opensbi_output}",
f"CROSS_COMPILE={cross_compile}",
"PLATFORM=retrosoc_hp",
f"PLATFORM_DIR={external / 'opensbi'}",
"PLATFORM_RISCV_XLEN=32",
"PLATFORM_RISCV_ABI=ilp32d",
"PLATFORM_RISCV_ISA=rv32imafdc_zicbom_zicsr_zifencei",
"FW_TEXT_START=0x38000000",
"FW_JUMP=y",
"FW_JUMP_ADDR=0x38400000",
"FW_JUMP_FDT_ADDR=0x38080000",
f"-j{args.jobs}",
],
opensbi,
)
copies = {
opensbi_output / "platform/retrosoc_hp/firmware/fw_jump.bin": image_dir / "fw_jump.bin",
linux_output / "arch/riscv/boot/Image": image_dir / "Image",
rootfs_source: image_dir / "rootfs.cpio.gz",
}
for source, destination in copies.items():
if not source.is_file():
raise FileNotFoundError(f"HP Linux build output is missing: {source}")
shutil.copy2(source, destination)
manifest: dict[str, object] = {
"schema_version": 1,
"boot_flow": "opensbi-fw_jump",
"hart_id": 1,
"sources": {
"buildroot": source_revision(buildroot),
"linux": source_revision(linux),
"opensbi": source_revision(opensbi),
},
"artifacts": {},
}
artifacts = manifest["artifacts"]
assert isinstance(artifacts, dict)
for name, (address, maximum) in LAYOUT.items():
path = image_dir / name
size = path.stat().st_size
if size > maximum:
raise ValueError(f"{name} is {size} bytes; maximum is {maximum}")
artifacts[name] = {
"address": f"0x{address:08X}",
"maximum_bytes": maximum,
"size_bytes": size,
"sha256": sha256(path),
}
(image_dir / "manifest.json").write_text(
json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8"
)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--root", type=Path, required=True)
parser.add_argument("--buildroot", type=Path, required=True)
parser.add_argument("--linux", type=Path, required=True)
parser.add_argument("--opensbi", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--jobs", type=int, default=1)
build(parser.parse_args())
if __name__ == "__main__":
main()