diff --git a/cargo.sh b/cargo.sh index e085b7bc8..8b3b3cfc6 100755 --- a/cargo.sh +++ b/cargo.sh @@ -34,6 +34,12 @@ cd firmware-binaries; fb_res="$?" cd .. +cd host-binaries; + printf "host-binaries " >&2; + run cargo "$@"; + hb_res="$?" +cd .. + if [[ fs_res -ne 0 ]]; then echo "firmware-support failure!" fi @@ -42,7 +48,10 @@ if [[ fb_res -ne 0 ]]; then echo "firmware-binaries failure!" fi +if [[ hb_res -ne 0 ]]; then + echo "host-binaries failure!" +fi -if [[ fs_res -ne 0 ]] || [[ fb_res -ne 0 ]] ; then +if [[ fs_res -ne 0 ]] || [[ fb_res -ne 0 ]] || [[ hb_res -ne 0 ]]; then exit 1 fi diff --git a/host-binaries/.cargo/config.toml b/host-binaries/.cargo/config.toml new file mode 100644 index 000000000..c47bcfab3 --- /dev/null +++ b/host-binaries/.cargo/config.toml @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2022 Google LLC +# +# SPDX-License-Identifier: CC0-1.0 + +[build] +target-dir = "../_build/cargo/host-binaries" diff --git a/host-binaries/Cargo.lock b/host-binaries/Cargo.lock new file mode 100644 index 000000000..45f3588af --- /dev/null +++ b/host-binaries/Cargo.lock @@ -0,0 +1,154 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memmap-generate" +version = "0.1.0" +dependencies = [ + "derivative", + "heck", + "proc-macro2", + "quote", + "serde", + "serde_json", + "smallvec", +] + +[[package]] +name = "memmap-markdown" +version = "0.1.0" +dependencies = [ + "heck", + "memmap-generate", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" diff --git a/host-binaries/Cargo.lock.license b/host-binaries/Cargo.lock.license new file mode 100644 index 000000000..8af336081 --- /dev/null +++ b/host-binaries/Cargo.lock.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 Google LLC + +SPDX-License-Identifier: CC0-1.0 diff --git a/host-binaries/Cargo.toml b/host-binaries/Cargo.toml new file mode 100644 index 000000000..b3bf250d9 --- /dev/null +++ b/host-binaries/Cargo.toml @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2022 Google LLC +# +# SPDX-License-Identifier: CC0-1.0 + +[workspace] +members = [ + "memmap-markdown" +] + +resolver = "2" diff --git a/host-binaries/memmap-markdown/Cargo.toml b/host-binaries/memmap-markdown/Cargo.toml new file mode 100644 index 000000000..34276ffde --- /dev/null +++ b/host-binaries/memmap-markdown/Cargo.toml @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2024 Google LLC +# +# SPDX-License-Identifier: CC0-1.0 + +[package] +name = "memmap-markdown" +version = "0.1.0" +edition = "2024" + +[dependencies] +memmap-generate = { path = "../../firmware-support/memmap-generate" } +heck = "0.5.0" diff --git a/host-binaries/memmap-markdown/src/main.rs b/host-binaries/memmap-markdown/src/main.rs new file mode 100644 index 000000000..f3bed22a6 --- /dev/null +++ b/host-binaries/memmap-markdown/src/main.rs @@ -0,0 +1,573 @@ +// SPDX-FileCopyrightText: 2025 Google LLC +// +// SPDX-License-Identifier: Apache-2.0 + +use std::{collections::BTreeMap, fs::File, path::Path}; + +use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; +use memmap_generate::{ + input_language::{self as mm_inp}, + ir::{ + input_to_ir::IrInputMapping, + types::{ + DeviceDescription, HalHandles, IrCtx, PathComp, TreeElem, TreeElemType, + TypeConstructor, TypeDefinition, TypeDescription, TypeRef, + }, + }, + storage::{Handle, HandleRange}, +}; +use std::fmt::Write; + +struct GenContext<'a> { + types_path: &'a str, + devices_path: &'a str, +} + +fn main() { + use std::io::Write; + let memmap_dir = memmap_generate::build_utils::memmap_dir(); + let memory_maps = read_memory_maps(&memmap_dir); + + let out_dir = memmap_dir.join("../markdown-docs"); + + _ = std::fs::remove_dir_all(&out_dir); + + std::fs::create_dir_all(&out_dir).unwrap(); + + let mut ctx = IrCtx::new(); + + let mut hals = vec![]; + let mut hal_names = vec![]; + + let mut input_mapping = IrInputMapping::default(); + for (name, memmap) in memory_maps.iter() { + let hal_handles = ctx.add_memory_map_desc(&mut input_mapping, memmap); + hals.push(hal_handles); + hal_names.push(name.clone()); + } + + for (name, handles) in hal_names.into_iter().zip(hals) { + let hal_dir = out_dir.join(name.to_snake_case()); + std::fs::create_dir(&hal_dir).unwrap(); + + let mut instance_file = File::create(hal_dir.join("instances.md")).unwrap(); + let gen_ctx = GenContext { + types_path: "./types/", + devices_path: "./devices/", + }; + + writeln!(instance_file, "# HAL {name}\n").unwrap(); + let instance_table = generate_hal_instances(&gen_ctx, &ctx, &handles); + writeln!(instance_file, "{instance_table}\n").unwrap(); + + writeln!(instance_file, "\n## Types\n").unwrap(); + + let types_dir = hal_dir.join("types"); + std::fs::create_dir(&types_dir).unwrap(); + + for ty in handles.types.handles() { + let desc = &ctx.type_descs[ty]; + let name = &ctx.type_names[desc.name]; + + if ctx.type_primitives.contains(&ty) { + continue; + } + + if ctx.type_tuples.contains(&ty) { + continue; + } + + let gen_ctx = GenContext { + types_path: "./", + devices_path: "../devices/", + }; + + let mut ty_file = + File::create(types_dir.join(format!("{}.md", name.base.to_upper_camel_case()))) + .unwrap(); + + writeln!(ty_file, "### {}\n", name.base.to_upper_camel_case()).unwrap(); + let type_desc = generate_type_definition(&gen_ctx, &ctx, desc); + writeln!(ty_file, "{type_desc}\n").unwrap(); + + writeln!( + instance_file, + "- [`{}`](types/{}.md)", + name.base.to_upper_camel_case(), + name.base.to_upper_camel_case() + ) + .unwrap(); + } + + let devices_dir = hal_dir.join("devices"); + std::fs::create_dir(&devices_dir).unwrap(); + writeln!(instance_file, "\n## Devices\n").unwrap(); + + for dev in handles.devices.handles() { + let desc = &ctx.device_descs[dev]; + let name = &ctx.identifiers[desc.name]; + let mut dev_file = + File::create(devices_dir.join(format!("{}.md", name.to_upper_camel_case()))) + .unwrap(); + let gen_ctx = GenContext { + types_path: "../types/", + devices_path: "./", + }; + + writeln!(dev_file, "### {}", name.to_upper_camel_case()).unwrap(); + let device_table = generate_device(&gen_ctx, &ctx, desc); + writeln!(dev_file, "{device_table}").unwrap(); + + writeln!( + instance_file, + "- [`{}`](devices/{}.md)", + name.to_upper_camel_case(), + name.to_upper_camel_case() + ) + .unwrap(); + } + } +} + +fn generate_type_definition(gen_ctx: &GenContext, ctx: &IrCtx, desc: &TypeDescription) -> String { + let mut output = String::new(); + + let name = &ctx.type_names[desc.name]; + let args = desc.param_names; + + let mut args_def = String::new(); + + if args.len > 0 { + args_def.push('<'); + + for (i, arg) in args.handles().enumerate() { + let name = ctx.identifiers[arg].to_shouty_snake_case(); + + if i > 0 { + args_def.push_str(", "); + } + + args_def.push_str(&format!("`{name}`")); + + if ctx.type_param_nats.contains(&arg) { + args_def.push_str(": number"); + } else { + args_def.push_str(": type"); + } + } + + args_def.push('>'); + } + + fn is_fieldless(ctx: &IrCtx, cons: HandleRange) -> bool { + ctx.type_constructors[cons] + .iter() + .all(|con| con.field_types.len == 0) + } + + match &desc.definition { + TypeDefinition::DataType { + names, + constructors, + } if constructors.len == 1 => { + writeln!(output, "Data type\n").unwrap(); + + let con_name = names.start; + let constructor = constructors.start; + + let con_name = ctx.identifiers[con_name].to_upper_camel_case(); + write!( + output, + "`newtype` `{}`{} = `{con_name}`", + name.base.to_upper_camel_case(), + args_def + ) + .unwrap(); + let cons = &ctx.type_constructors[constructor]; + if let Some(names) = cons.field_names { + // named + writeln!(output, " {{\n").unwrap(); + for (ty, name) in cons.field_types.handles().zip(names.handles()) { + let name = ctx.identifiers[name].to_snake_case(); + writeln!( + output, + " `{}`: {},\n", + name, + generate_type_name(gen_ctx, ctx, ty, 1) + ) + .unwrap(); + } + writeln!(output, "}}\n").unwrap(); + } else { + // nameless + for ty in cons.field_types.handles() { + write!(output, " {}", generate_type_name(gen_ctx, ctx, ty, 1)).unwrap(); + } + writeln!(output, "\n").unwrap(); + } + } + TypeDefinition::DataType { + names, + constructors, + } if is_fieldless(ctx, *constructors) => { + // "enum" like ADT + writeln!(output, "Enum\n").unwrap(); + + writeln!( + output, + "`enum` `{}`{}\n", + name.base.to_upper_camel_case(), + args_def + ) + .unwrap(); + + for (i, name) in names.handles().enumerate() { + let name = ctx.identifiers[name].to_upper_camel_case(); + if i == 0 { + write!(output, " = ").unwrap(); + } else { + write!(output, " | ").unwrap(); + } + writeln!(output, "`{name}`\n").unwrap(); + } + } + TypeDefinition::DataType { + names, + constructors, + } => { + // "SoP" ADT + writeln!(output, "Data type\n").unwrap(); + + writeln!( + output, + "`data` `{}`{}\n", + name.base.to_upper_camel_case(), + args_def + ) + .unwrap(); + + for (i, (name, con)) in names.handles().zip(constructors.handles()).enumerate() { + let con_name = ctx.identifiers[name].to_upper_camel_case(); + if i == 0 { + write!(output, " = ").unwrap(); + } else { + write!(output, " | ").unwrap(); + } + write!(output, "`{con_name}`",).unwrap(); + let cons = &ctx.type_constructors[con]; + if let Some(names) = cons.field_names { + // named + writeln!(output, " {{\n").unwrap(); + for (ty, name) in cons.field_types.handles().zip(names.handles()) { + let name = ctx.identifiers[name].to_snake_case(); + writeln!( + output, + "  `{}`: {},\n", + name, + generate_type_name(gen_ctx, ctx, ty, 1) + ) + .unwrap(); + } + writeln!(output, " }}\n").unwrap(); + } else { + // nameless + for ty in cons.field_types.handles() { + write!(output, " {}", generate_type_name(gen_ctx, ctx, ty, 1)).unwrap(); + } + writeln!(output, "\n").unwrap(); + } + } + } + TypeDefinition::Newtype { + name: con_name, + constructor, + } => { + writeln!(output, "Newtype\n").unwrap(); + + let con_name = ctx.identifiers[*con_name].to_upper_camel_case(); + write!( + output, + "`newtype {}`{} = `{con_name}`", + name.base.to_upper_camel_case(), + args_def + ) + .unwrap(); + let cons = &ctx.type_constructors[*constructor]; + if let Some(names) = cons.field_names { + // named + writeln!(output, " {{\n").unwrap(); + for (ty, name) in cons.field_types.handles().zip(names.handles()) { + let name = ctx.identifiers[name].to_snake_case(); + writeln!( + output, + " {}: {},\n", + name, + generate_type_name(gen_ctx, ctx, ty, 1) + ) + .unwrap(); + } + writeln!(output, "}}\n").unwrap(); + } else { + // nameless + for ty in cons.field_types.handles() { + write!(output, " {}", generate_type_name(gen_ctx, ctx, ty, 1)).unwrap(); + } + writeln!(output, "\n").unwrap(); + } + } + TypeDefinition::Builtin(_builtin_type) => { + panic!("Builtin types should not have type descriptions generated about them.") + } + TypeDefinition::Synonym(handle) => { + writeln!(output, "Type synonym\n",).unwrap(); + + writeln!( + output, + "`type {}`{}` = `{}", + name.base.to_upper_camel_case(), + args_def, + generate_type_name(gen_ctx, ctx, *handle, 0) + ) + .unwrap(); + } + } + + output +} + +fn generate_hal_instances(gen_ctx: &GenContext, ctx: &IrCtx, handles: &HalHandles) -> String { + let mut instances = vec![]; + for handle in handles.tree_elem_range.handles() { + let ty = &ctx.tree_elem_types[handle.cast()]; + let device_name = if let TreeElemType::DeviceInstance { device_name } = ty { + &ctx.identifiers[*device_name] + } else { + continue; + }; + let elem = &ctx.tree_elems[handle]; + instances.push((device_name, elem, handle)); + } + + instances.sort_by(|(_, a, _), (_, b, _)| a.absolute_addr.cmp(&b.absolute_addr)); + + let unnameds = instance_unnameds(ctx, &instances); + + let mut output = String::new(); + + writeln!( + output, + "| Absolute Address | Instance Name | Device Name | Tags |" + ) + .unwrap(); + writeln!( + output, + "|------------------|---------------|-------------|------|" + ) + .unwrap(); + + for (device_name, elem, _handle) in instances { + let instance_name = instance_name(ctx, &unnameds, device_name, elem.path); + let device_name = device_name.to_upper_camel_case(); + let tags = &ctx.tags[elem.tags]; + let tags = tags + .iter() + .map(|t| format!("`{t}`")) + .collect::>() + .join(", "); + writeln!( + output, + "| 0x{:X} | `{}` | [`{}`]({}{}.md) | {} |", + elem.absolute_addr, instance_name, device_name, gen_ctx.devices_path, device_name, tags, + ) + .unwrap(); + } + + output +} + +fn generate_type_name( + gen_ctx: &GenContext, + ctx: &IrCtx, + ty: Handle, + depth: usize, +) -> String { + let with_brace = |s| { + if depth > 0 { format!("({s})") } else { s } + }; + match &ctx.type_refs[ty] { + TypeRef::BitVector(handle) => with_brace(format!( + "`BitVector` {}", + generate_type_name(gen_ctx, ctx, *handle, depth + 1) + )), + TypeRef::Unsigned(handle) => with_brace(format!( + "`Unsigned` {}", + generate_type_name(gen_ctx, ctx, *handle, depth + 1) + )), + TypeRef::Signed(handle) => with_brace(format!( + "`Signed` {}", + generate_type_name(gen_ctx, ctx, *handle, depth + 1) + )), + TypeRef::Index(handle) => with_brace(format!( + "`Index` {}", + generate_type_name(gen_ctx, ctx, *handle, depth + 1) + )), + TypeRef::Bool => "`bool`".to_string(), + TypeRef::Float => "`float`".to_string(), + TypeRef::Double => "`double`".to_string(), + TypeRef::Vector(len, inner) => with_brace({ + format!( + "`Vec` {} {}", + generate_type_name(gen_ctx, ctx, *len, depth + 1), + generate_type_name(gen_ctx, ctx, *inner, depth + 1) + ) + }), + TypeRef::Tuple(handle_range) if handle_range.len == 0 => "`unit`".to_string(), + TypeRef::Tuple(handle_range) => { + let mut s = "(".to_string(); + for (i, handle) in handle_range.handles().enumerate() { + if i > 0 { + s.push_str(", "); + } + s.push_str(&generate_type_name(gen_ctx, ctx, handle, 0)); + } + s.push(')'); + s + } + TypeRef::Variable(handle) => ctx.identifiers[*handle].to_shouty_snake_case(), + TypeRef::Nat(n) => format!("{n}"), + TypeRef::Reference { name, args } => { + let ty_name = &ctx.type_names[*name]; + let base_name = ty_name.base.to_upper_camel_case(); + let link = format!("{}{base_name}.md", gen_ctx.types_path); + let mut output = format!("[`{base_name}`]({link})"); + if args.len > 0 { + output.push('<'); + for (i, arg) in args.handles().enumerate() { + if i > 0 { + output.push_str(", "); + } + output.push_str(&generate_type_name(gen_ctx, ctx, arg, 0)); + } + output.push('>'); + } + output + } + } +} + +fn generate_device(gen_ctx: &GenContext, ctx: &IrCtx, desc: &DeviceDescription) -> String { + let mut output = String::new(); + + writeln!( + output, + "| Offset | Size | Name | Access | Type | Description | Tags |" + ) + .unwrap(); + writeln!( + output, + "|--------|------|------|--------|------|-------------|------|" + ) + .unwrap(); + + for reg in desc.registers.handles() { + let reg_desc = &ctx.registers[reg]; + let name = ctx.identifiers[reg_desc.name].to_snake_case(); + let tags = &ctx.tags[reg_desc.tags]; + let tags = tags + .iter() + .map(|t| format!("`{t}`")) + .collect::>() + .join(", "); + + writeln!( + output, + "| 0x{:X} | 0x{:X}({}) | `{}` | {:?} | {} | {} | {} |", + reg_desc.address, + reg_desc.size, + reg_desc.size, + name, + reg_desc.access, + generate_type_name(gen_ctx, ctx, reg_desc.type_ref, 0), + reg_desc.description, + tags + ) + .unwrap(); + } + + output +} + +fn instance_unnameds( + ctx: &IrCtx, + instances: &[(&String, &TreeElem, Handle)], +) -> BTreeMap { + let mut map = BTreeMap::new(); + + for (device_name, elem, _) in instances { + let unnameds: &mut usize = map.entry(device_name.to_string()).or_default(); + match ctx.paths[elem.path].last() { + Some(PathComp::Named { loc: _, name: _ }) => {} + Some(PathComp::Unnamed(_)) => *unnameds += 1, + None => panic!(), + } + } + + map +} + +fn instance_name( + ctx: &IrCtx, + instance_unnameds: &BTreeMap, + device_name: &str, + path: Handle, +) -> String { + let path = &ctx.paths[path]; + + match path.last() { + Some(PathComp::Named { loc: _, name }) => name.clone(), + Some(PathComp::Unnamed(n)) => { + if instance_unnameds[device_name] > 1 { + format!("{device_name}_{n}").to_snake_case() + } else { + device_name.to_snake_case() + } + } + None => panic!("all paths should have at least one element"), + } +} + +fn read_memory_maps( + dir: &Path, +) -> BTreeMap { + let mut memory_maps = BTreeMap::new(); + + for dir in dir.read_dir().unwrap() { + let dir = dir.unwrap(); + if dir.path().is_dir() { + continue; + } + + let path = dir.path(); + + let Some(extension) = path.extension() else { + continue; + }; + + if extension != "json" { + continue; + } + + let Some(name) = path.file_stem() else { + continue; + }; + let src = std::fs::read_to_string(&path).unwrap(); + + let desc = mm_inp::parse(&src).unwrap(); + + let hal_name = name.to_str().unwrap(); + + memory_maps.insert(hal_name.to_string(), desc); + } + + memory_maps +}