mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
238 lines
7.3 KiB
Python
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()
|