mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
- Added build_hp_rtthread.py for building the RT-Thread kernel with RV64 support. - Updated build_hp_smoke.py to target RV64 architecture and modified output filenames. - Modified generate_vexiiriscv.py to reflect RV64 configuration and memory management. - Created hp_tools.py for shared utilities related to RV64 builds. - Enhanced package_hp_boot.py to package RV64 workloads and updated versioning. - Implemented run_hp_sim.py for executing and validating HP workloads with specific markers. - Developed setup_hp_rtthread.py for installing RT-Thread source and environment setup. - Updated GenerateRetroSocHp.scala to generate RV64 application cores. - Enhanced tests for HP boot bundles, platform specifications, and tool utilities to accommodate RV64 changes. - Adjusted test cases to validate new workload types and ensure compatibility with RV64 architecture.
318 lines
12 KiB
Python
318 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""Build pinned RV64 Linux images with a small, static acceptance initramfs."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from scripts.hp_tools import require_revision, require_rv64_elf
|
|
except ModuleNotFoundError:
|
|
from hp_tools import require_revision, require_rv64_elf
|
|
|
|
LAYOUT = {
|
|
"fw_jump.bin": (0x38000000, 512 * 1024),
|
|
"retrosoc_hp.dtb": (0x38080000, 64 * 1024),
|
|
"Image": (0x38400000, 12 * 1024 * 1024),
|
|
"rootfs.cpio": (0x39000000, 8 * 1024 * 1024),
|
|
}
|
|
|
|
REQUIRED_LINUX_CONFIG = (
|
|
"CONFIG_ARCH_RV64I",
|
|
"CONFIG_64BIT",
|
|
"CONFIG_MMU",
|
|
"CONFIG_OF_RESERVED_MEM",
|
|
"CONFIG_BINFMT_ELF",
|
|
"CONFIG_BINFMT_SCRIPT",
|
|
"CONFIG_PRINTK",
|
|
"CONFIG_TTY",
|
|
"CONFIG_HVC_RISCV_SBI",
|
|
"CONFIG_SERIAL_EARLYCON_RISCV_SBI",
|
|
"CONFIG_PROC_FS",
|
|
"CONFIG_SYSFS",
|
|
"CONFIG_SHMEM",
|
|
"CONFIG_TMPFS",
|
|
"CONFIG_DEVTMPFS",
|
|
"CONFIG_DEVMEM",
|
|
)
|
|
|
|
|
|
def command(arguments: list[str], cwd: Path, extra_env: dict[str, str] | None = None) -> None:
|
|
environment = dict(os.environ)
|
|
environment.pop("CONFIG", None)
|
|
environment.pop("MAKEFLAGS", None)
|
|
environment.pop("MAKEOVERRIDES", None)
|
|
environment.pop("MFLAGS", None)
|
|
environment.update(extra_env or {})
|
|
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("riscv64*-linux-*-gcc"))
|
|
if len(matches) != 1:
|
|
raise RuntimeError(f"expected one RV64 Linux GCC in {buildroot_output / 'host/bin'}")
|
|
return str(matches[0])[: -len("gcc")]
|
|
|
|
|
|
def minimal_busybox(buildroot: Path, output: Path, external: Path, jobs: int) -> str:
|
|
"""Expand an allowlist using BusyBox's own allnoconfig, not default-y oldconfig."""
|
|
make = ["make", f"O={output}", f"BR2_EXTERNAL={external}", f"-j{jobs}"]
|
|
command([*make, "busybox-configure"], buildroot)
|
|
sources = [path for path in (output / "build").glob("busybox-*")
|
|
if (path / "scripts/kconfig/conf").is_file()]
|
|
if len(sources) != 1:
|
|
raise RuntimeError("expected one configured locked BusyBox source")
|
|
source = sources[0]
|
|
config_dir = output.parent / "busybox-config"
|
|
(config_dir / "include").mkdir(parents=True, exist_ok=True)
|
|
command([str(source / "scripts/kconfig/conf"), "-n", str(source / "Config.in")], config_dir,
|
|
{"srctree": str(source), "KCONFIG_NOTIMESTAMP": "1",
|
|
"KCONFIG_ALLCONFIG": str(external / "busybox/retrosoc_hp.config")})
|
|
# This BusyBox conf implementation resets booleans even when ALLCONFIG
|
|
# specified y. Overlay our explicit choices after its all-no expansion;
|
|
# Buildroot's ordinary oldconfig then resolves the real dependencies.
|
|
selected = dict(line.split("=", 1) for line in
|
|
(external / "busybox/retrosoc_hp.config").read_text().splitlines()
|
|
if line.startswith("CONFIG_") and "=" in line)
|
|
config = config_dir / ".config"
|
|
lines = []
|
|
for line in config.read_text().splitlines():
|
|
key = line[2:].split(" ", 1)[0] if line.startswith("# CONFIG_") else line.split("=", 1)[0]
|
|
lines.append(f"{key}={selected.pop(key)}" if key in selected else line)
|
|
lines.extend(f"{key}={value}" for key, value in selected.items())
|
|
config.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
config_arg = f"BUSYBOX_CONFIG_FILE={config_dir / '.config'}"
|
|
command([*make, config_arg, "busybox-reconfigure"], buildroot)
|
|
return config_arg
|
|
|
|
|
|
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")
|
|
for name, path in (("buildroot_hp", buildroot), ("linux_hp", linux), ("opensbi_hp", opensbi)):
|
|
if path != require_revision(root, name).resolve():
|
|
raise ValueError(f"{name} must use its locked source directory")
|
|
external = root / "app/ports/linux"
|
|
|
|
buildroot_output = output / "buildroot"
|
|
command(
|
|
["make", f"O={buildroot_output}", f"BR2_EXTERNAL={external}", "retrosoc_hp_defconfig"],
|
|
buildroot,
|
|
)
|
|
busybox_config = minimal_busybox(buildroot, buildroot_output, external, args.jobs)
|
|
command(["make", f"O={buildroot_output}", f"BR2_EXTERNAL={external}",
|
|
busybox_config, f"-j{args.jobs}"], buildroot)
|
|
busybox_sources = sorted((buildroot_output / "build").glob("busybox-*/.config"))
|
|
if len(busybox_sources) != 1:
|
|
raise RuntimeError("missing effective BusyBox configuration")
|
|
busybox_effective = busybox_sources[0].read_text().splitlines()
|
|
for symbol in ("STATIC", "BUSYBOX", "ASH", "MOUNT", "TEST", "ECHO", "CAT", "TRUE"):
|
|
if f"CONFIG_{symbol}=y" not in busybox_effective:
|
|
raise RuntimeError(f"HP BusyBox requires CONFIG_{symbol}=y")
|
|
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)
|
|
require_rv64_elf(linux_output / "vmlinux")
|
|
busybox = buildroot_output / "target/bin/busybox"
|
|
require_rv64_elf(busybox)
|
|
if "INTERP" in subprocess.check_output([cross_compile + "readelf", "-l", str(busybox)], text=True):
|
|
raise ValueError("HP acceptance BusyBox must be statically linked")
|
|
helper = output / "hp-ready"
|
|
command([cross_compile + "gcc", "-Os", "-static", "-Wall", "-Wextra", "-Werror",
|
|
"-o", str(helper), str(external / "hp_ready.c")], root)
|
|
require_rv64_elf(helper)
|
|
archive_spec = output / "initramfs.list"
|
|
entries = [f"dir /{name} 755 0 0" for name in ("bin", "dev", "proc", "sys", "tmp")]
|
|
entries += [f"file /bin/busybox {busybox} 755 0 0",
|
|
f"file /bin/hp-ready {helper} 755 0 0", f"file /init {external / 'init'} 755 0 0",
|
|
"nod /dev/console 600 0 0 c 5 1", "nod /dev/mem 600 0 0 c 1 1"]
|
|
entries += [f"slink /bin/{name} busybox 777 0 0" for name in
|
|
("sh", "mount", "mkdir", "echo", "test", "cat", "sleep")]
|
|
archive_spec.write_text("\n".join(entries) + "\n", encoding="utf-8")
|
|
rootfs_source = image_dir / "rootfs.cpio"
|
|
with rootfs_source.open("wb") as archive:
|
|
subprocess.run([str(linux_output / "usr/gen_init_cpio"), "-t", "0", str(archive_spec)],
|
|
stdout=archive, check=True)
|
|
initrd_end = LAYOUT["rootfs.cpio"][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=64",
|
|
"PLATFORM_RISCV_ABI=lp64d",
|
|
"PLATFORM_RISCV_ISA=rv64imafdc_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",
|
|
}
|
|
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)
|
|
|
|
require_rv64_elf(opensbi_output / "platform/retrosoc_hp/firmware/fw_jump.elf")
|
|
manifest: dict[str, object] = {
|
|
"schema_version": 1,
|
|
"xlen": 64,
|
|
"abi": "lp64d",
|
|
"initramfs": "minimal-static-uncompressed",
|
|
"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()
|