Skip to content

Commit 7ef9feb

Browse files
committed
feat: add workload-tohex functionality with API and event steps for converting ELF files to hex
1 parent 67b3f33 commit 7ef9feb

6 files changed

Lines changed: 193 additions & 3 deletions

File tree

api/steps/kernel/01_build_event.step.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
config = {
2222
"name": "kernel-build",
23-
"description": "build RISC-V kernel + rootfs for Pegasus via bb-tests/workloads/lib/kernel",
23+
"description": "build RISC-V kernel + rootfs for image via bb-tests/workloads/lib/kernel",
2424
"flows": ["kernel"],
2525
"triggers": [queue("kernel.build")],
2626
"enqueues": [],
@@ -66,8 +66,8 @@ async def handler(input_data: dict, ctx: FlowContext) -> None:
6666
return
6767

6868
# Convert bin to hex for P2E memory backdoor
69-
bin_file = os.path.join(output_dir, "pegasus-bin")
70-
hex_file = os.path.join(output_dir, "pegasus.hex")
69+
bin_file = os.path.join(output_dir, "image.bin")
70+
hex_file = os.path.join(output_dir, "image.hex")
7171

7272
if os.path.exists(bin_file):
7373
ctx.logger.info(f"Converting {bin_file} to Verilog hex format...")
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from motia import ApiRequest, ApiResponse, FlowContext, api
2+
3+
from utils.path import get_buckyball_path
4+
5+
config = {
6+
"name": "workload-tohex-api",
7+
"description": "convert elf to hex",
8+
"flows": ["workload"],
9+
"triggers": [api("POST", "/workload/tohex")],
10+
"enqueues": ["workload.tohex"],
11+
}
12+
13+
14+
async def handler(request: ApiRequest, ctx: FlowContext) -> ApiResponse:
15+
bbdir = get_buckyball_path()
16+
body = request.body or {}
17+
data = {}
18+
await ctx.enqueue({"topic": "workload.tohex", "data": {**data, "_trace_id": ctx.trace_id}})
19+
return ApiResponse(status=202, body={"trace_id": ctx.trace_id})
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import os
2+
import subprocess
3+
import sys
4+
from pathlib import Path
5+
6+
from motia import FlowContext, queue
7+
8+
# Add the utils directory to the Python path
9+
utils_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
10+
if utils_path not in sys.path:
11+
sys.path.insert(0, utils_path)
12+
13+
from utils.path import get_buckyball_path
14+
from utils.stream_run import stream_run_logger
15+
from utils.event_common import check_result, get_origin_trace_id
16+
17+
config = {
18+
"name": "workload-tohex",
19+
"description": "convert all -baremetal ELF files under bb-tests/output/workloads/src to hex",
20+
"flows": ["workload"],
21+
"triggers": [queue("workload.tohex")],
22+
"enqueues": [],
23+
}
24+
25+
26+
async def handler(input_data: dict, ctx: FlowContext) -> None:
27+
origin_tid = get_origin_trace_id(input_data, ctx)
28+
bbdir = get_buckyball_path()
29+
script = f"{bbdir}/bbdev/api/steps/workload/scripts/elf2hex.py"
30+
search_root = Path(f"{bbdir}/bb-tests/output/workloads/src")
31+
32+
ctx.logger.info("Search root", {"path": str(search_root)})
33+
34+
if not search_root.is_dir():
35+
ctx.logger.error(
36+
"Workload output directory not found",
37+
{"path": str(search_root)},
38+
)
39+
await check_result(ctx, 1, continue_run=False, trace_id=origin_tid)
40+
return
41+
42+
ctx.logger.info("Search root is a directory", {"path": str(search_root)})
43+
44+
elf_files = sorted(p for p in search_root.rglob("*-baremetal") if p.is_file())
45+
46+
ctx.logger.info("ELF files found", {"count": len(elf_files)})
47+
48+
if not elf_files:
49+
ctx.logger.warn(
50+
"No -baremetal ELF files found",
51+
{"search_root": str(search_root)},
52+
)
53+
await check_result(ctx, 0, continue_run=False, trace_id=origin_tid)
54+
return
55+
56+
ctx.logger.info(
57+
"Converting ELF files to hex",
58+
{"count": len(elf_files), "search_root": str(search_root)},
59+
)
60+
61+
overall_rc = 0
62+
for elf in elf_files:
63+
command = f"python3 {script} {elf}"
64+
ctx.logger.info("Executing to-hex command", {"command": command})
65+
result = stream_run_logger(
66+
cmd=command,
67+
logger=ctx.logger,
68+
cwd=str(elf.parent),
69+
executable="bash",
70+
stdout_prefix=f"to-hex {elf.name}",
71+
stderr_prefix=f"to-hex {elf.name}",
72+
)
73+
if result.returncode != 0:
74+
overall_rc = result.returncode
75+
ctx.logger.error(
76+
"elf2hex failed",
77+
{"elf": str(elf), "returncode": result.returncode},
78+
)
79+
80+
# ==================================================================================
81+
# Return conversion result
82+
# ==================================================================================
83+
success_result, failure_result = await check_result(
84+
ctx, overall_rc, continue_run=False, trace_id=origin_tid)
85+
86+
# ==================================================================================
87+
# finish workflow
88+
# ==================================================================================
89+
return
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/env python3
2+
"""ELF -> hex converter for Buckyball P2E DDR loading.
3+
4+
Output format:
5+
@0
6+
XX
7+
XX
8+
...
9+
where XX is one byte per line (uppercase hex).
10+
11+
The hex file represents bytes starting at DDR offset 0, which maps to
12+
CPU address 0x80000000. All LOAD segments are concatenated by physical
13+
address, with gaps padded by zeros.
14+
"""
15+
16+
import argparse
17+
import subprocess
18+
import sys
19+
from pathlib import Path
20+
21+
22+
def elf_to_bin(elf_path: Path, bin_path: Path, objcopy: str) -> None:
23+
subprocess.run(
24+
[objcopy, "-O", "binary", str(elf_path), str(bin_path)],
25+
check=True,
26+
)
27+
28+
29+
def bin_to_hex(bin_path: Path, hex_path: Path) -> int:
30+
data = bin_path.read_bytes()
31+
lines = ["@0"]
32+
lines.extend(f"{b:02X}" for b in data)
33+
hex_path.write_text("\n".join(lines) + "\n")
34+
return len(data)
35+
36+
37+
def main() -> int:
38+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
39+
parser.add_argument("elf", type=Path, help="Input ELF file")
40+
parser.add_argument(
41+
"hex",
42+
type=Path,
43+
nargs="?",
44+
help="Output hex file (default: <elf>.hex)",
45+
)
46+
parser.add_argument(
47+
"--objcopy",
48+
default="riscv64-unknown-elf-objcopy",
49+
help="objcopy binary (default: riscv64-unknown-elf-objcopy)",
50+
)
51+
args = parser.parse_args()
52+
53+
if not args.elf.is_file():
54+
print(f"error: ELF not found: {args.elf}", file=sys.stderr)
55+
return 1
56+
57+
hex_path = args.hex or args.elf.with_suffix(".hex")
58+
bin_path = hex_path.with_suffix(".bin")
59+
60+
try:
61+
elf_to_bin(args.elf, bin_path, args.objcopy)
62+
except FileNotFoundError:
63+
print(f"error: objcopy not found: {args.objcopy}", file=sys.stderr)
64+
print("hint: run inside `nix develop` shell", file=sys.stderr)
65+
return 1
66+
except subprocess.CalledProcessError as e:
67+
print(f"error: objcopy failed: {e}", file=sys.stderr)
68+
return 1
69+
70+
n = bin_to_hex(bin_path, hex_path)
71+
bin_path.unlink()
72+
73+
print(f"wrote {hex_path} ({n} bytes, {n + 1} lines)")
74+
return 0
75+
76+
77+
if __name__ == "__main__":
78+
sys.exit(main())
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from _api_test_helper import run_bbdev_case
2+
3+
run_bbdev_case("bbdev workload --tohex")

bbdev

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ WORKFLOW_COMMANDS = {
4242
},
4343
"workload": {
4444
"build": "Build workload.",
45+
"tohex": "Convert all -baremetal ELF files under bb-tests/output/workloads/src to hex.",
4546
},
4647
"kernel": {
4748
"build": "Build RISC-V kernel + rootfs for Pegasus (cmake kernel-build).",

0 commit comments

Comments
 (0)