Skip to content

Commit 5cd8c29

Browse files
committed
xtask: build algo crates, parse ELFs, emit probe-rs target YAMLs
1 parent f611e03 commit 5cd8c29

6 files changed

Lines changed: 1031 additions & 5 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[workspace]
22
resolver = "2"
3-
members = ["algos/*"]
3+
members = ["algos/*", "xtask"]
4+
default-members = ["xtask"]
45

56
[workspace.package]
67
edition = "2024"
@@ -17,10 +18,6 @@ ch32-metapac = { path = "../ch32-data/build/ch32-metapac", default-features = fa
1718
flash-algorithm = { version = "0.7", default-features = false, features = ["erase-chip", "panic-handler"] }
1819
panic-halt = "1"
1920

20-
[patch.crates-io]
21-
# Match the locally-installed probe-rs schema (crates.io 0.31.0 has diverged).
22-
probe-rs-target = { path = "../probe-rs/probe-rs-target" }
23-
2421
[profile.release]
2522
codegen-units = 1
2623
debug = 2

xtask/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "xtask"
3+
version = "0.1.0"
4+
edition.workspace = true
5+
license.workspace = true
6+
repository.workspace = true
7+
authors.workspace = true
8+
publish = false
9+
10+
[dependencies]
11+
anyhow = "1"
12+
goblin = "0.10"
13+
indexmap = "2"
14+
probe-rs-target = { git = "https://github.com/ch32-rs/probe-rs", rev = "8bfc5b87e356934ef0af6ad3ee8f3f6162089327" }
15+
scroll = "0.13"
16+
serde = { version = "1", features = ["derive"] }
17+
serde_json = "1"
18+
serde_yaml = "0.9"

xtask/src/algo.rs

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
use anyhow::{Context, Result, anyhow, bail};
2+
use probe_rs_target::{FlashProperties, RawFlashAlgorithm};
3+
use std::collections::HashMap;
4+
use std::path::Path;
5+
use std::process::Command;
6+
7+
pub struct AlgoBlob {
8+
/// Flash IP version (matches `FLASH.registers.version`).
9+
pub silicon: String,
10+
/// "usr" | "sys" | "ob".
11+
pub region_kind: String,
12+
pub template: RawFlashAlgorithm,
13+
}
14+
15+
pub fn build_all(workspace_root: &Path) -> Result<Vec<AlgoBlob>> {
16+
let algos_dir = workspace_root.join("algos");
17+
let mut out = Vec::new();
18+
19+
for entry in std::fs::read_dir(&algos_dir)? {
20+
let entry = entry?;
21+
let dir = entry.path();
22+
if !dir.is_dir() {
23+
continue;
24+
}
25+
let name = entry.file_name().to_string_lossy().to_string();
26+
if name == "common" {
27+
continue;
28+
}
29+
out.extend(build_crate(&dir, &name)?);
30+
}
31+
32+
if out.is_empty() {
33+
bail!("no algo crates found under algos/");
34+
}
35+
Ok(out)
36+
}
37+
38+
fn build_crate(crate_dir: &Path, silicon: &str) -> Result<Vec<AlgoBlob>> {
39+
eprintln!(" cargo build --release ({})", crate_dir.display());
40+
let status = Command::new("cargo")
41+
.arg("+nightly")
42+
.arg("build")
43+
.arg("--release")
44+
.current_dir(crate_dir)
45+
.status()
46+
.with_context(|| format!("failed to spawn cargo for {}", crate_dir.display()))?;
47+
if !status.success() {
48+
bail!("cargo build failed in {}", crate_dir.display());
49+
}
50+
51+
let triple = read_target_triple(crate_dir)?;
52+
let bin_dir = crate_dir.join("../../target").join(&triple).join("release");
53+
54+
let mut out = Vec::new();
55+
let bin_src = crate_dir.join("src/bin");
56+
for entry in
57+
std::fs::read_dir(&bin_src).with_context(|| format!("reading {}", bin_src.display()))?
58+
{
59+
let entry = entry?;
60+
let stem = entry
61+
.path()
62+
.file_stem()
63+
.and_then(|s| s.to_str())
64+
.map(|s| s.to_string())
65+
.ok_or_else(|| anyhow!("bad bin filename"))?;
66+
let elf_path = bin_dir.join(&stem);
67+
let elf = std::fs::read(&elf_path)
68+
.with_context(|| format!("reading ELF {}", elf_path.display()))?;
69+
let template =
70+
extract_algo(&elf).with_context(|| format!("parsing ELF {}", elf_path.display()))?;
71+
out.push(AlgoBlob {
72+
silicon: silicon.to_string(),
73+
region_kind: stem,
74+
template,
75+
});
76+
}
77+
Ok(out)
78+
}
79+
80+
fn read_target_triple(crate_dir: &Path) -> Result<String> {
81+
let cfg = crate_dir.join(".cargo/config.toml");
82+
let text =
83+
std::fs::read_to_string(&cfg).with_context(|| format!("reading {}", cfg.display()))?;
84+
for line in text.lines() {
85+
let l = line.trim();
86+
if let Some(rest) = l.strip_prefix("target") {
87+
let rest = rest.trim_start_matches([' ', '=']).trim();
88+
let raw = rest.trim_matches('"');
89+
if raw.is_empty() {
90+
continue;
91+
}
92+
// Custom JSON target specs build into a dir named after the
93+
// file stem, not the path literal.
94+
let triple = if raw.ends_with(".json") {
95+
Path::new(raw)
96+
.file_stem()
97+
.and_then(|s| s.to_str())
98+
.ok_or_else(|| anyhow!("bad target json path {raw} in {}", cfg.display()))?
99+
.to_string()
100+
} else {
101+
raw.to_string()
102+
};
103+
return Ok(triple);
104+
}
105+
}
106+
bail!("no `target = \"\"` in {}", cfg.display());
107+
}
108+
109+
// ELF extraction ported from probe-rs target-gen (binary-only dependency).
110+
111+
const CODE_SECTION_KEY: (&str, u32) = ("PrgCode", goblin::elf64::section_header::SHT_PROGBITS);
112+
const DATA_SECTION_KEY: (&str, u32) = ("PrgData", goblin::elf64::section_header::SHT_PROGBITS);
113+
const BSS_SECTION_KEY: (&str, u32) = ("PrgData", goblin::elf64::section_header::SHT_NOBITS);
114+
115+
struct Section {
116+
start: u32,
117+
length: u32,
118+
data: Vec<u8>,
119+
load_address: u32,
120+
}
121+
122+
struct AlgoElf {
123+
code: Section,
124+
data: Section,
125+
bss: Section,
126+
}
127+
128+
impl AlgoElf {
129+
fn parse(elf: &goblin::elf::Elf<'_>, buf: &[u8]) -> Result<Self> {
130+
let mut code = None;
131+
let mut data = None;
132+
let mut bss = None;
133+
for ph in &elf.program_headers {
134+
if ph.p_type != goblin::elf::program_header::PT_LOAD || ph.p_memsz == 0 {
135+
continue;
136+
}
137+
let seg = ph.p_offset..ph.p_offset + ph.p_memsz;
138+
for sh in &elf.section_headers {
139+
let r = sh.sh_offset..sh.sh_offset + sh.sh_size;
140+
if seg.start <= r.start && r.end <= seg.end {
141+
let bytes = if sh.sh_type == goblin::elf64::section_header::SHT_NOBITS {
142+
Vec::new()
143+
} else {
144+
buf[sh.sh_offset as usize..][..sh.sh_size as usize].to_vec()
145+
};
146+
let s = Section {
147+
start: sh.sh_addr as u32,
148+
length: sh.sh_size as u32,
149+
data: bytes,
150+
load_address: (ph.p_vaddr + sh.sh_offset - ph.p_offset) as u32,
151+
};
152+
match (&elf.shdr_strtab[sh.sh_name], sh.sh_type) {
153+
CODE_SECTION_KEY => code = Some(s),
154+
DATA_SECTION_KEY => data = Some(s),
155+
BSS_SECTION_KEY => bss = Some(s),
156+
_ => {}
157+
}
158+
}
159+
}
160+
}
161+
let code = code.ok_or_else(|| anyhow!("PrgCode section missing"))?;
162+
let data = data.unwrap_or_else(|| Section {
163+
start: code.start + code.length,
164+
length: 0,
165+
data: Vec::new(),
166+
load_address: code.load_address + code.length,
167+
});
168+
let bss = bss.unwrap_or_else(|| Section {
169+
start: data.start + data.length,
170+
length: 0,
171+
data: Vec::new(),
172+
load_address: data.load_address + data.length,
173+
});
174+
Ok(Self { code, data, bss })
175+
}
176+
177+
fn blob(&self) -> Vec<u8> {
178+
let mut b =
179+
Vec::with_capacity((self.code.length + self.data.length + self.bss.length) as usize);
180+
b.extend(&self.code.data);
181+
b.extend(&self.data.data);
182+
b.extend(std::iter::repeat_n(0u8, self.bss.length as usize));
183+
b
184+
}
185+
}
186+
187+
fn extract_algo(buf: &[u8]) -> Result<RawFlashAlgorithm> {
188+
let elf = goblin::elf::Elf::parse(buf)?;
189+
let parsed = AlgoElf::parse(&elf, buf)?;
190+
let dev = read_flash_device(&elf, buf)?;
191+
192+
let mut algo = RawFlashAlgorithm::default();
193+
let code_off = parsed.code.start as u64;
194+
let mut syms: HashMap<&str, u64> = HashMap::new();
195+
for s in elf.syms.iter() {
196+
let name = &elf.strtab[s.st_name];
197+
if !name.is_empty() {
198+
syms.insert(name, s.st_value);
199+
}
200+
}
201+
let need = |k: &str| -> Result<u64> {
202+
syms.get(k)
203+
.copied()
204+
.map(|v| v - code_off)
205+
.ok_or_else(|| anyhow!("ELF missing required symbol {k}"))
206+
};
207+
algo.pc_init = Some(need("Init")?);
208+
algo.pc_uninit = syms.get("UnInit").copied().map(|v| v - code_off);
209+
algo.pc_program_page = need("ProgramPage")?;
210+
algo.pc_erase_sector = need("EraseSector")?;
211+
algo.pc_erase_all = syms.get("EraseChip").copied().map(|v| v - code_off);
212+
213+
algo.instructions = parsed.blob();
214+
algo.load_address = Some(parsed.code.load_address as u64);
215+
algo.data_section_offset = (parsed.data.start - parsed.code.load_address) as u64;
216+
algo.big_endian = !elf.little_endian;
217+
218+
algo.flash_properties = FlashProperties {
219+
address_range: dev.start..(dev.start + dev.size),
220+
page_size: dev.page_size,
221+
erased_byte_value: dev.erased,
222+
program_page_timeout: dev.program_to,
223+
erase_sector_timeout: dev.erase_to,
224+
sectors: Vec::new(),
225+
};
226+
// probe-rs schema default (Rust default for u64 is 0).
227+
algo.rtt_poll_interval = 20;
228+
Ok(algo)
229+
}
230+
231+
struct FlashDevice {
232+
start: u64,
233+
size: u64,
234+
page_size: u32,
235+
erased: u8,
236+
program_to: u32,
237+
erase_to: u32,
238+
}
239+
240+
fn read_flash_device(elf: &goblin::elf::Elf<'_>, buf: &[u8]) -> Result<FlashDevice> {
241+
use scroll::Pread;
242+
let mut addr = None;
243+
for s in elf.syms.iter() {
244+
if &elf.strtab[s.st_name] == "FlashDevice" {
245+
addr = Some(s.st_value as u32);
246+
break;
247+
}
248+
}
249+
let addr = addr.ok_or_else(|| anyhow!("ELF missing FlashDevice symbol"))?;
250+
251+
let bytes = read_at(elf, buf, addr, 160)
252+
.ok_or_else(|| anyhow!("FlashDevice not in any LOAD segment"))?;
253+
Ok(FlashDevice {
254+
start: bytes.pread::<u32>(132).unwrap() as u64,
255+
size: bytes.pread::<u32>(136).unwrap() as u64,
256+
page_size: bytes.pread(140).unwrap(),
257+
erased: bytes.pread(148).unwrap(),
258+
program_to: bytes.pread(152).unwrap(),
259+
erase_to: bytes.pread(156).unwrap(),
260+
})
261+
}
262+
263+
fn read_at<'a>(
264+
elf: &goblin::elf::Elf<'_>,
265+
buf: &'a [u8],
266+
addr: u32,
267+
size: u32,
268+
) -> Option<&'a [u8]> {
269+
let want = addr as u64..(addr as u64 + size as u64);
270+
for ph in &elf.program_headers {
271+
let seg = ph.p_paddr..(ph.p_paddr + ph.p_memsz.min(ph.p_filesz));
272+
if seg.start <= want.start && want.end <= seg.end {
273+
let off = (ph.p_offset + addr as u64 - seg.start) as usize;
274+
return Some(&buf[off..][..size as usize]);
275+
}
276+
}
277+
None
278+
}

0 commit comments

Comments
 (0)