Initial commit

Code dump with a few minor improvements. Let's develop here in the open
and keep register maps in a private repo.

Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
This commit is contained in:
Casey Connolly
2026-04-02 23:27:13 +02:00
commit 69cfc3cd4a
17 changed files with 4004 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
target/
src/html/
Generated
+1203
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "regwatch"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = { version = "1.0.71", features = ["backtrace"] }
chrono = "0.4.26"
clap = { version = "4.1.6", features = ["derive"] }
clap-num = "1.0.2"
colored = "2.0.4"
env_logger = "0.10.0"
log = { version = "0.4.19", features = ["serde", "std"] }
logos = "0.13.0"
num-traits = "0.2.16"
parking_lot = { version = "0.12.1", features = ["arc_lock"] }
prettytable = "0.10.0"
range-overlap = "0.0.1"
requestty = "0.5.0"
schemars = "0.8.15"
serde = { version = "1.0.164", features = ["derive"] }
serde_json = "1.0.97"
term_size = "0.3.2"
+101
View File
@@ -0,0 +1,101 @@
# Interactive register decoding for Qualcom SoCs
This is a collection of tools for parsing the .FLAT and .per files which are
commonly included in Qualcomm chipcode releases. These files include reasonably
comprehensive register maps for the main SoC as well as co-processors and PMICs.
However, they are intended to be used with proprietary tools like Trace32,
having a much higher barrier of entry.
![example output](images/example.jpg)
## Usage
### regdump
The regdump tool implements register decoding for a given JSON register map. It
can be used to decode a single register with the `decode` command, however due
to the massive size of the register maps this can be quite slow (on my machine
is takes ~750ms to load the SDM845 register map).
Instead, it can be run with no arguments to enter an interactive prompt, in this
mode you're prompted for address/value pairs until you quit, this is much faster as the JSON parsing only has to be done once.
```txt
Run with no command for interactive mode. This is recommended
to avoid having to reload large JSON files on every invocation
Usage: regdump [OPTIONS] --json <JSON> [COMMAND]
Commands:
decode Decode an address/value pair
parse Parse a file of address/value pairs each on their own line
print Print a list of registers and modules matching the given substring
codegen Create a header that can be used to decode registers at runtime (e.g. in U-Boot/Linux) for debugging
help Print this message or the help of the given subcommand(s)
Options:
-j, --json <JSON> Path to JSON register description (generate with reg2json)
-d, --debug Enable debug logs
-w, --max-width <MAX_WIDTH> Override max table width in characters
-h, --help Print help
-V, --version Print version
```
#### Codegen
Regdump codegen can be used to generate C code for runtime-decoding of register
values to assist with driver development. The generated code is written to
stdout. At runtime just call `regdump_decode(u64 addr, u32 val)` or
`regdump_read_decode(u64 addr)`.
Due to the size of the register maps, it isn't suitable to include all possible
registers in the output, so the list of peripherals to include must be specified
on the cmdline.
Some configuration can be adjusted by changing the macros at the top of the
generated output, for example to use the correct print function for the
platform.
Example usage:
```sh
regdump --json socs/example.json codegen DEMO_BLOCK_REGS > example_regdump.c
```
### reg2json
reg2json is a tool for converting the `.FLAT` and `.per` file formats into a
standard json representation that can be used by other tools in this repo. The schema for the
resulting JSON files can be found in
[schemas/hwio_u64.json](/schemas/hwio_u64.json).
> **WARNING:** .FLAT file support is known to have bugs, it may miss registers!
```txt
Usage: reg2json [OPTIONS] --out <OUT>
Options:
-f, --flat <FLAT> Path to FLAT file address map
-e, --per <PER> Path to TRACE32 PER file address map
-o, --out <OUT> Path to save JSON file as
-d, --debug Enable debug logs
-h, --help Print help
-V, --version Print version
```
## Finding HWIO register maps
The .per files which document the register maps for a given SoC can usually be
found in a chipcode release. Do `find . -name "*.per"` and feed it to
`reg2json`.
## Cross Compiling
Install [cross](https://github.com/cross-rs/cross)
```sh
# Build with glibc
cross build --target aarch64-unknown-linux-gnu --release
# OR musl
cross build --target aarch64-unknown-linux-musl --release
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Executable
BIN
View File
Binary file not shown.
+146
View File
@@ -0,0 +1,146 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "HWIO register map",
"type": "object",
"required": [
"name",
"peripherals"
],
"properties": {
"name": {
"type": "string"
},
"peripherals": {
"type": "array",
"items": {
"$ref": "#/definitions/Peripheral_for_uint64"
}
}
},
"definitions": {
"FieldValue_for_uint64": {
"type": "object",
"required": [
"applied",
"name",
"value"
],
"properties": {
"applied": {
"type": "boolean"
},
"name": {
"type": "string"
},
"value": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
}
},
"Field_for_uint64": {
"type": "object",
"required": [
"mask",
"name",
"values"
],
"properties": {
"mask": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"name": {
"type": "string"
},
"values": {
"type": "array",
"items": {
"$ref": "#/definitions/FieldValue_for_uint64"
}
}
}
},
"Peripheral_for_uint64": {
"type": "object",
"required": [
"addr",
"name",
"registers"
],
"properties": {
"addr": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"name": {
"type": "string"
},
"registers": {
"type": "array",
"items": {
"$ref": "#/definitions/Register_for_uint64"
}
}
}
},
"RegisterPerm": {
"type": "string",
"enum": [
"Read",
"Write",
"ReadWrite"
]
},
"Register_for_uint64": {
"type": "object",
"required": [
"addr",
"fields",
"name",
"offset",
"perm",
"reset_value",
"width"
],
"properties": {
"addr": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"fields": {
"type": "array",
"items": {
"$ref": "#/definitions/Field_for_uint64"
}
},
"name": {
"type": "string"
},
"offset": {
"description": "Offset from base",
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"perm": {
"$ref": "#/definitions/RegisterPerm"
},
"reset_value": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"width": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
}
}
}
}
+112
View File
@@ -0,0 +1,112 @@
{
"name": "example",
"peripherals": [
{
"name": "DEMO_BLOCK_REGS",
"addr": 1048576,
"end": 1048612,
"registers": [
{
"name": "DEMO_REGXY",
"addr": 1048576,
"offset": 0,
"width": 0,
"reset_value": 0,
"perm": "Read",
"fields": [
{
"name": "Y",
"mask": 16711680,
"values": []
},
{
"name": "X",
"mask": 65535,
"values": []
}
]
},
{
"name": "DEMO_BLOCK_ID",
"addr": 1048580,
"offset": 4,
"width": 0,
"reset_value": 0,
"perm": "Read",
"fields": [
{
"name": "ID",
"mask": 4294967295,
"values": []
}
]
},
{
"name": "DEMO_BLOCK_CFG",
"addr": 1048608,
"offset": 32,
"width": 0,
"reset_value": 0,
"perm": "Read",
"fields": [
{
"name": "FIELD0",
"mask": 4290772992,
"values": []
},
{
"name": "ADDRSPACE",
"mask": 4128768,
"values": []
},
{
"name": "CFG1",
"mask": 49152,
"values": []
},
{
"name": "CFG2",
"mask": 14336,
"values": []
},
{
"name": "ERRCODE",
"mask": 1792,
"values": []
},
{
"name": "RSV0",
"mask": 128,
"values": []
},
{
"name": "OPCODE",
"mask": 112,
"values": []
},
{
"name": "DEVICE",
"mask": 8,
"values": []
},
{
"name": "SECURE",
"mask": 4,
"values": []
},
{
"name": "ERRSTATUS",
"mask": 2,
"values": []
},
{
"name": "ENABLE",
"mask": 1,
"values": []
}
]
}
]
}
]
}
+49
View File
@@ -0,0 +1,49 @@
config 16. 8.
width 6.
tree "DEMO_BLOCK"
base ad:0x0100000
tree "DEMO_BLOCK_REGS"
tree "DEMO_REGXY"
group.long 0x0--0x3 "DEMO_REGXY (at 0x0100000, Read)"
line.long 0x0 "VALUE"
textline "BITS"
textline ""
hexmask.long.byte 0x0 0x10--0x17 1 " [23:16] Y = "
textline ""
hexmask.long.word 0x0 0x0--0xf 1 " [15: 0] X = "
tree.end
tree "DEMO_BLOCK_ID"
group.long 0x4--0x7 "DEMO_BLOCK_ID (at 0x0100004, Read)"
line.long 0x0 "VALUE"
textline "BITS"
textline ""
hexmask.long.long 0x0 0x0--0x1f 1 " [31: 0] ID = "
tree.end
tree "DEMO_BLOCK_CFG"
group.long 0x20--0x23 "DEMO_BLOCK_CFG (at 0x0100020, Read)"
line.long 0x0 "VALUE"
textline "BITS"
textline ""
hexmask.long.word 0x0 0x16--0x1f 1 " [31:22] FIELD0 = "
textline ""
hexmask.long.byte 0x0 0x10--0x15 1 " [21:16] ADDRSPACE = "
textline ""
bitfld.long 0x0 0xe--0xf " [15:14] CFG1 = " "0, 1, 2, 3"
textline ""
bitfld.long 0x0 0xb--0xd " [13:11] CFG2 = " "0, 1, 2, 3, 4, 5, 6, 7"
textline ""
bitfld.long 0x0 0x8--0xa " [10: 8] ERRCODE = " "0, 1, 2, 3, 4, 5, 6, 7"
textline ""
bitfld.long 0x0 0x7--0x7 " [ 7] RSV0 = " "0, 1"
textline ""
bitfld.long 0x0 0x4--0x6 " [ 6: 4] OPCODE = " "0, 1, 2, 3, 4, 5, 6, 7"
textline ""
bitfld.long 0x0 0x3--0x3 " [ 3] DEVICE = " "0, 1"
textline ""
bitfld.long 0x0 0x2--0x2 " [ 2] SECURE = " "0, 1"
textline ""
bitfld.long 0x0 0x1--0x1 " [ 1] ERRSTATUS = " "0, 1"
textline ""
bitfld.long 0x0 0x0--0x0 " [ 0] ENABLE = " "0, 1"
tree.end
tree.end
+27
View File
@@ -0,0 +1,27 @@
use anyhow::Result;
use clap::Parser;
use regwatch::parsers::Hwio;
use schemars::schema_for;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Args {
#[arg(short, long, help = "Path to output schema")]
pub out: PathBuf,
}
fn main() -> Result<()> {
let args = Args::parse();
let mut schema = schema_for!(Hwio<u64>);
let metadata = schema.schema.metadata();
metadata.title = Some("HWIO".into());
let mut file = File::create(args.out)?;
serde_json::to_writer_pretty(&mut file, &schema)?;
file.flush()?;
Ok(())
}
+56
View File
@@ -0,0 +1,56 @@
use anyhow::{bail, Result};
use clap::Parser;
use regwatch::parsers::flat::{FlatParser};
use std::io::Write;
use std::path::PathBuf;
use regwatch::parsers::per::PerParser;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Args {
#[arg(short, long, help = "Path to FLAT file address map")]
pub flat: Option<PathBuf>,
#[arg(short = 'e', long, help = "Path to TRACE32 PER file address map")]
pub per: Option<PathBuf>,
#[arg(short, long, help = "Path to save JSON file as")]
pub out: PathBuf,
#[arg(short = 'd', long, help = "Enable debug logs", required = false)]
pub debug: bool
}
fn main() -> Result<()> {
let args = Args::parse();
setup_logging(args.debug);
if let Some(p) = args.flat {
FlatParser::new().parse(p)?.save_to_json(&args.out)
} else if let Some(p) = args.per {
PerParser::new().parse(p)?.save_to_json(&args.out)
} else {
bail!("Must specify either --flat or --per");
}
}
fn setup_logging(debug: bool) {
if debug {
// #[cfg(debug_assertions)]
::std::env::set_var("RUST_LOG", "trace");
} else {
//#[cfg(not(debug_assertions))]
::std::env::set_var("RUST_LOG", "info");
}
env_logger::Builder::from_default_env()
.format(|buf, record| {
let style = buf.default_level_style(record.level());
writeln!(
buf,
"{:<10}:{} [{}] {}",
record.file().unwrap_or("unknown"), record.line().unwrap_or(0),
style.value(record.level()),
record.args()
)
})
.init();
}
+711
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
pub mod parsers;
pub mod regmap;
+706
View File
File diff suppressed because it is too large Load Diff
+271
View File
@@ -0,0 +1,271 @@
use anyhow::{bail, Result};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::{
fmt::{Display, LowerHex},
fs::File,
io::{Read, Write},
ops::Range,
path::PathBuf,
};
use num_traits::{Zero, PrimInt};
use std::fmt::Debug;
pub mod flat;
pub mod per;
pub trait RegisterWidth: Zero + std::cmp::PartialEq + PrimInt + Serialize + Default + LowerHex + Into<u64> + Debug { }
impl RegisterWidth for u8 { }
impl RegisterWidth for u16 { }
impl RegisterWidth for u32 { }
impl RegisterWidth for u64 { }
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct Hwio<W> where W: RegisterWidth {
pub name: String,
pub peripherals: Vec<Peripheral<W>>,
#[serde(skip)]
pub range: Range<u64>,
#[serde(skip)]
pub addrs: Vec<u64>,
#[serde(skip)]
pub changed_at: u64,
#[serde(skip)]
pub used_modules: Vec<Peripheral<W>>,
}
impl<'de, W> Hwio<W> where W: RegisterWidth + DeserializeOwned {
pub fn load_from_json(path: &PathBuf) -> Result<Self> {
let mut file =
File::open(path).map_err(|e| anyhow::anyhow!("Failed to open file: {}", e))?;
let mut contents = vec![];
file.read_to_end(&mut contents)?;
let flat: Hwio<W> = serde_json::from_slice(&contents)?;
Ok(flat)
}
}
impl<W> Hwio<W> where W: RegisterWidth {
pub fn apply_values(&mut self, values: &Vec<(u64, W)>) -> Result<()> {
if values.is_empty() {
bail!("No values to apply");
}
self.addrs = values.iter().map(|v| v.0).collect::<Vec<u64>>();
//log::trace!("{:?}", values);
self.range = values[0].0..values[values.len() - 1].0;
// Modules that overlap with the range
self.used_modules = self
.peripherals
.iter_mut()
.filter(|m| {
(m.addr <= self.addrs[0] && m.end > self.addrs[0])
|| (m.addr <= *self.addrs.last().unwrap()
&& m.end > *self.addrs.last().unwrap())
})
.map(|m| m.clone())
.collect::<Vec<Peripheral<W>>>();
//log::trace!("Range: {:?}", self.range);
// log::trace!("Found {:?} registers", regs.size_hint());
// if regs.size_hint().0 == 0 {
// bail!("No registers found for range {:#x}-{:#x}", self.range.start, self.range.end);
// }
for (addr, val) in values {
//log::trace!("Value: {:#x} = {:#x}", addr, val);
let reg = match self
.used_modules
.iter_mut()
.find_map(|m| m.registers.iter_mut().find(|r| r.addr == *addr))
{
Some(r) => r,
None => {
//log::warn!("No register found for address {:#x}", addr);
continue;
}
};
let val: W = *val;
if reg.value != val {
reg.changed_at = chrono::Utc::now().timestamp_millis() as u64;
}
reg.value = val;
//log::trace!("Register: {}", reg.name);
for field in reg.fields.iter_mut() {
field.apply_value(reg.value);
}
}
self.changed_at = chrono::Utc::now().timestamp_millis() as u64;
Ok(())
}
pub fn save_to_json(&self, path: &PathBuf) -> Result<()> {
let mut file = File::create(path)?;
serde_json::to_writer(&mut file, self)?;
file.flush()?;
Ok(())
}
pub fn find_peripheral(&self, name: &str) -> Option<&Peripheral<W>> {
self.peripherals.iter().find(|p| p.name == name)
}
pub fn find_register(&self, addr: u64) -> Option<&Register<W>> {
self.peripherals.iter().filter(|p| {
log::debug!("Checking peripheral {}: {:#x}", p.name, p.addr);
p.addr <= addr && match p.registers.last() {
Some(r) => r.addr >= addr,
None => false,
}
}).find_map(|p| {
p.find_register_by_addr(addr)
})
}
}
impl<W> Display for Hwio<W> where W: RegisterWidth {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let now = chrono::Utc::now().timestamp_millis() as u64;
writeln!(f, "delta T: {}ms", now - self.changed_at)?;
for module in self.used_modules.iter() {
writeln!(f, "module {}", module.name)?;
for reg in module.registers.iter() {
if !self.addrs.contains(&reg.addr) {
continue;
}
// let (fmt_start, fmt_end) = if false && now - reg.changed_at > 5000 {
// ("\u{001b}[7m", "\u{001b}[0m")
// } else {
// ("", "")
// };
writeln!(
f,
" {:>#06x} {:#04x} | {:2} ({:#04x}) {:<24}",
reg.addr, reg.value, reg.perm, reg.reset_value, reg.name
)?;
for field in reg.fields.iter() {
if !field.values.is_empty() && !field.values.iter().any(|v| v.applied) {
// There ought to be a value defined for 0x0!
log::warn!(
"No value applied for field {} in reg {:#>6x} {}",
field.name,
reg.addr,
reg.name
);
}
write!(
f,
" [{}:{}] {:>#04x} | {:<32}",
7 - field.mask.leading_zeros(),
field.mask.trailing_zeros(),
field.prepare(reg.value),
field.name
)?;
match field.values.iter().find(|v| v.applied) {
Some(v) => write!(f, ": {}", v.name)?,
None => (),
}
writeln!(f)?;
}
writeln!(f)?;
}
}
Ok(())
}
}
#[derive(Debug, Default, PartialEq, Serialize, Deserialize, Clone, JsonSchema)]
pub enum RegisterPerm {
#[default]
Read,
Write,
ReadWrite,
}
impl Display for RegisterPerm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RegisterPerm::Read => write!(f, "R"),
RegisterPerm::Write => write!(f, "W"),
RegisterPerm::ReadWrite => write!(f, "RW"),
}
}
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone, JsonSchema)]
pub struct FieldValue<W> where W: RegisterWidth {
pub name: String,
pub value: W,
// Mask
applied: bool,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default, JsonSchema)]
pub struct Field<W> where W: RegisterWidth {
pub name: String,
pub mask: W,
pub values: Vec<FieldValue<W>>,
}
impl<W> Field<W> where W: RegisterWidth + {
fn apply_value(&mut self, value: W) {
let value: W = self.prepare(value);
for v in &mut self.values {
v.applied = v.value == value;
}
}
fn prepare(&self, value: W) -> W {
(value & self.mask) >> self.mask.trailing_zeros().try_into().unwrap()
}
}
#[derive(Debug, PartialEq, Default, Serialize, Deserialize, Clone, JsonSchema)]
pub struct Register<W> where W: RegisterWidth {
pub name: String,
// Absolute address
pub addr: u64,
/// Offset from base
pub offset: u64,
pub width: u32,
reset_value: W,
// For printing....
#[serde(skip)]
pub value: W,
#[serde(skip)]
pub changed_at: u64,
pub perm: RegisterPerm,
pub fields: Vec<Field<W>>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default, JsonSchema)]
pub struct Peripheral<W> where W: RegisterWidth {
pub name: String,
pub addr: u64,
// #[serde(skip)]
pub end: u64,
#[serde(skip)]
pub apre: String,
pub registers: Vec<Register<W>>,
}
impl<W> Peripheral<W> where W: RegisterWidth {
pub fn new(name: String, addr: u64) -> Peripheral<W> {
Peripheral {
name,
addr,
end: 0,
apre: String::new(),
registers: vec![],
}
}
pub fn find_register_by_name(&self, name: &str) -> Option<&Register<W>> {
self.registers.iter().find(|r| r.name == name)
}
pub fn find_register_by_addr(&self, addr: u64) -> Option<&Register<W>> {
log::trace!("Finding register {:#x}", addr);
self.registers.iter().find(|r| r.addr == addr)
}
}
+440
View File
@@ -0,0 +1,440 @@
use anyhow::{bail, Result, anyhow};
use logos::{Lexer, Logos};
use std::{
fs::File,
io::Read,
path::PathBuf,
};
use super::{Peripheral, Hwio, Register, Field, RegisterPerm};
type TreeField = Field<u64>;
type TreePeripheral = Peripheral<u64>;
type TreeRegister = Register<u64>;
// #[derive(Debug, PartialEq)]
// struct TreePeripheral {
// name: String,
// subtrees: Vec<Register<u32>>,
// }
// #[derive(Debug, PartialEq)]
// struct TreePeripheral {
// base: u64,
// name: String,
// registers: Vec<Register<u32>>,
// }
#[derive(Debug, PartialEq, Default)]
struct ParserState {
trees: Vec<TreePeripheral>,
in_copy: bool,
}
#[derive(Debug, Default)]
pub struct PerParser {
state: ParserState,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PerError {
#[default]
UnexpectedToken,
}
#[derive(Debug, PartialEq)]
enum TreeState {
Start,
End,
}
#[derive(Logos, Debug, PartialEq)]
#[logos(extras = PerParser)]
#[logos(error = PerError)]
#[logos(subpattern xdigit = r"[0-9a-fA-F]")]
#[logos(skip r#"[\s\t\n\f",]+"#)]
enum Token {
#[regex(r"tree(\.end)?", callback = |lex| if lex.slice().contains("end") { TreeState::End } else { TreeState::Start })]
Tree(TreeState),
#[token("base")]
Base,
#[token("\"")]
Quote, // The Label token also eats the quotes
#[token("copy")]
Copy,
#[regex(r"(Read/Write|Read|Write)( \(Command\))?", |lex| {
match lex.slice().to_string().replace(" (Command)", "").as_str() {
"Read" => Some(RegisterPerm::Read),
"Read/Write" => Some(RegisterPerm::ReadWrite),
"Write" => Some(RegisterPerm::Write),
_ => None,
}
})]
Perm(RegisterPerm),
#[regex(r"(ad:|at )?0x(?&xdigit)+", |lex| {
let s = lex.slice();
if s.contains(":") || s.contains("at") {
u64::from_str_radix(&s[5..], 16).unwrap()
} else {
u64::from_str_radix(&s[2..], 16).unwrap()
}
})]
Addr(u64),
#[regex(r"group\.(long|quad)")]
Group,
#[regex(r"(hexmask|bitfld)(\.(byte|word|long|quad))+ 0x0")]
Field,
#[regex(r"0x(?&xdigit)+--0x(?&xdigit)+", |lex| {
let s = lex.slice();
let mut split = s.split("--");
let end = split.next().unwrap();
let end = u128::from_str_radix(&end[2..], 16).unwrap();
if end > 64 {
// Technically the 0xXX--0xXX syntax is a range and not a bitmask
// but we only care about it in the context where it's used as a bitmask
// soo...
0 as u64
} else {
let start = split.next().unwrap();
let start = u128::from_str_radix(&start[2..], 16).unwrap();
let mask: u128 = (1 << (start - end + 1)) - 1;
(mask << end) as u64
}
})]
BitMask(u64),
#[regex(r"\[\s*[0-9]+(:\s*[0-9]+)?\]", |lex| {
let s = &lex.slice()[1..lex.slice().len()-1].trim();
if s.contains(':') {
let mut split = s.split(':');
let start = split.next().unwrap().trim().parse::<u128>().unwrap();
let end = split.next().unwrap().trim().parse::<u128>().unwrap();
let mask: u128 = (1 << (start - end + 1)) - 1;
mask as u64
} else {
log::trace!("BitMaskLabel: {} {:?}", s, lex.span());
let t = s.parse::<u128>();
if !t.is_ok() {
print_parse_error(None, lex);
}
1 << t.unwrap()
}
})]
BitMaskLabel(u64), // The type of bitmask used in the label string
#[token("(")]
LParan,
#[token(")")]
RParan,
#[token("=")]
Equals,
#[regex(r#"(line.(long|quad)|textline "\w*")"#)]
Stuff,
#[regex(r"(config|width)( \d+\.)+")]
Preamble,
#[regex(r"\d+")]
Digit,
#[regex(r#"[a-zA-Z_][a-zA-Z0-9_]*"#, priority = 1, callback = |lex| lex.slice().to_string())]
//#[regex(r#""?[\w_]+"#, priority = 2, callback = |_| "nyaa".to_string())]
Label(String),
}
macro_rules! token {
($lex:ident) => {
match $lex.next() {
None => bail!("No tokens"),
Some(tok) => {
let tok = tok.map_err(|e| anyhow!("{:?}", e))?;
log::trace!("TOKEN {:?}", tok);
tok
},
}
};
}
macro_rules! token_exp {
($lex:ident, $exp:pat => $result:expr) => {
{
log::trace!("Expecting {:?}", stringify!($exp));
match token!($lex) {
$exp => {
//log::trace!("Matched {:?}", $lex.slice());
$result
},
tok => {
//log::trace!("Expected {:?}, got {:?}", stringify!($exp), tok);
bail!("Unexpected token {:?}", tok);
},
}
}
};
($lex:ident, $($exp:pat),+) => {
$(
token_exp!($lex, $exp => ());
)*
};
($lex:ident, $($exp:pat => $result:expr$(,)?),+) => {
log::trace!("Expecting {:?}", stringify!($($exp),*));
match token!($lex) {
$($exp => {
//log::trace!("Matched {:?}", $lex.slice());
$result
},)+
tok => {
log::trace!("Unexpected {:?}", tok);
bail!("Unexpected token {:?}", tok);
}
}
};
}
fn parse_tree_register(lex: &mut Lexer<Token>, tree: &mut TreePeripheral) -> Result<()> {
let mut reg: TreeRegister = Default::default();
//token_exp!(lex, Token::Tree(TreeState::Start));
reg.name = token_exp!(lex, Token::Label(name) => name);
// Match e.g. group.long 0x0--0x3 "APCS_GICNOC_OBS_ID_COREID (at 0x0f11b000, Read)"
// up to... ^ here
loop {
token_exp! { lex,
Token::Group => {},
Token::BitMask(bytes) => {
//log::error!("Bytes: {:#b} {:?}", bytes, reg);
reg.width = bytes.leading_ones(); // Width in bits
break;
},
/* Sometimes for a bunch of similar registers there's an additional tree
* level where the first one is defined and the others are all "copy" */
Token::Tree(TreeState::Start) => {
lex.extras.state.in_copy = true;
log::info!("NESTING!\n");
parse_tree_register(lex, tree)?;
},
Token::Tree(TreeState::End) => {
if lex.extras.state.in_copy {
lex.extras.state.in_copy = false;
return Ok(()); // We're done here
}
}
};
}
token_exp!(lex, Token::Label(_), Token::LParan); // Duplicate of above label
reg.addr = token_exp!(lex, Token::Addr(a) => a);
reg.offset = reg.addr - tree.addr;
reg.perm = token_exp!(lex, Token::Perm(p) => p);
token_exp!(lex, Token::RParan);
'outer: loop {
let mut field: TreeField = Default::default();
loop {
token_exp! { lex,
Token::Copy => {
if !lex.extras.state.in_copy {
bail!("Unexpected copy token");
}
//log::debug!("Copy");
// AAAAAAAAA
reg.fields = tree.registers.last().unwrap().fields.clone();
},
Token::Stuff => {},
Token::Label(_) => {},
Token::Field => {},
Token::Addr(_) => {},
Token::Tree(TreeState::End) => {
log::debug!("Tree end at start of field parse");
break 'outer;
},
Token::Equals => {},
Token::BitMask(x) => {
field.mask = x;
break;
}
}
}
loop {
token_exp! { lex,
Token::Digit => {},
Token::BitMaskLabel(_) => { break; }
};
}
let mut done: bool = false;
loop {
token_exp! { lex,
Token::Equals => {},
Token::Digit => {},
Token::LParan => {},
Token::RParan => {},
Token::Label(l) => {
field.name = l;
},
Token::Stuff => break,
Token::Tree(TreeState::End) => {
done = true;
break;
},
}
}
if !done && field.name.is_empty() {
bail!("Missing field name!");
}
field.values = vec![]; // TODO: Parse values
//log::debug!("Parsed field: {:?}", field);
reg.fields.push(field);
if done {
break;
}
}
//log::debug!("Parsed register: {:?}", reg);
tree.registers.push(reg);
Ok(())
}
// Layer 2, a register block
// e.g. GICD/GICR
fn parse_tree_peripherals(lex: &mut Lexer<Token>) -> Result<()> {
while let Some(tok) = lex.next() {
match tok {
Ok(Token::Base) => {},
Ok(Token::Tree(TreeState::End)) => break,
Ok(_) => continue,
Err(e) => bail!("{:?}", e),
}
// loop {
// while let tok = Some(tok) {
// }
// token_exp!(lex,
// Token::Base => break,
// Token::Tree(TreeState::End) => break 'outer,
// );
// }
let base = token_exp!(lex, Token::Addr(a) => a);
token_exp!(lex, Token::Tree(TreeState::Start));
let label = token_exp!(lex, Token::Label(name) => name);
let mut tree = TreePeripheral::new(label, base);
while lex.peekable().next_if_eq(&Ok(Token::Tree(TreeState::Start))).is_some() {
parse_tree_register(lex, &mut tree)?;
}
if let Some(last_reg) = tree.registers.last() {
tree.end = last_reg.addr + 4;
}
//log::info!("Parsed tree: {:?}", tree);
lex.extras.state.trees.push(tree);
}
//log::trace!("current slice {:?}", lex.slice());
Ok(())
}
// Layer 1
fn parse_root(lex: &mut Lexer<Token>) -> Result<()> {
'toploop: while match lex.next() {
Some(tok) => {
match tok {
Ok(Token::Preamble) => continue 'toploop,
Ok(Token::Tree(TreeState::Start)) => continue 'toploop,
Ok(Token::Label(_)) => true,
Ok(Token::Tree(TreeState::End)) => false,
Ok(_) => continue 'toploop,
Err(e) => bail!("{:?}", e),
}
},
None => return Ok(()),
} {
parse_tree_peripherals(lex)?;
}
Ok(())
}
fn print_parse_error(path: Option<&PathBuf>, lexer: &Lexer<Token>) {
let mut linecount = 1;
let mut charcount = 0;
let mut linechars = 0;
for b in lexer.source().chars() {
if charcount == lexer.span().end {
break;
}
if b == '\n' {
linecount += 1;
linechars = 0;
}
linechars += 1;
charcount += 1;
}
match path {
Some(p) => log::error!("Failed to parse {}:{}:{} (symbol {:?})", p.display(), linecount, linechars, lexer.slice()),
None => log::error!("Failed to parse {}:{}:{} (symbol {:?})", "unknown ", linecount, linechars, lexer.slice()),
}
}
impl PerParser {
pub fn new() -> Self {
PerParser { state: ParserState::default() }
}
pub fn parse(self, path: PathBuf) -> Result<Hwio<u64>> {
let mut file = File::open(&path)?;
let mut buf = String::new();
file.read_to_string(&mut buf)?;
let mut lexer = Token::lexer_with_extras(&buf, self);
log::info!("Parsing {}", path.as_os_str().display());
match parse_root(&mut lexer) {
Ok(_) => (),
Err(e) => {
// yeah this is awful
log::error!("Parser error {}", e);
print_parse_error(Some(&path), &lexer);
if buf.len() - lexer.span().end > 10 {
return Err(e);
}
log::info!("less than 10 bytes away from the end of the file... close enough?");
}
}
log::info!("Sorting peripherals");
lexer.extras.state.trees.sort_by_key(|p| p.addr);
log::info!("Done sorting peripherals");
let hwio: Hwio<u64> = Hwio {
name:path.file_stem().unwrap().to_str().unwrap().to_string(),
peripherals: lexer.extras.state.trees,
range: 0..0,
addrs: vec![],
changed_at: 0,
used_modules: vec![],
};
Ok(hwio)
}
}
+152
View File
@@ -0,0 +1,152 @@
use std::{
fs::File,
io::{Read, Write},
path::PathBuf,
};
use anyhow::{bail, Result};
use num_traits::Num;
use crate::parsers::RegisterWidth;
pub struct Regmap {
path: PathBuf,
name: String,
only_regs: Option<Vec<u16>>,
buf: String,
}
impl Regmap {
pub fn new(
path: PathBuf,
start_addr: u16,
length: u16,
only_regs: Option<Vec<u16>>,
) -> Result<Self> {
let r = Self {
name: format!(
"{}-{:02x}-{:02x}",
path.file_name().unwrap().to_str().unwrap(),
start_addr,
length
),
path,
only_regs: match only_regs {
Some(mut r) => {
r.sort();
Some(r)
}
None => None,
},
buf: String::with_capacity(4096),
};
r.set_addr(start_addr as u32)?;
r.set_length(length as u32)?;
Ok(r)
}
fn read_prop(&self, prop: &str) -> Result<u64> {
let mut path = self.path.clone();
path.push(prop);
//log::trace!("Reading {}", path.display());
let mut file = File::open(path)?;
let mut buf = String::new();
file.read_to_string(&mut buf)?;
//log::trace!("Read: {}", buf);
let buf = buf.trim_start_matches("0x");
u64::from_str_radix(buf.trim(), 16).map_err(|e| e.into())
}
fn write_prop(&self, prop: &str, val: u32, file: Option<File>) -> Result<File> {
let mut file = match file {
None => {
let mut path = self.path.clone();
path.push(prop);
if !path.exists() {
bail!("{} does not exist", path.display());
}
File::create(path)?
}
Some(f) => f,
};
file.write_all(val.to_string().as_bytes())?;
Ok(file)
}
pub fn set_addr(&self, addr: u32) -> Result<()> {
self.write_prop("address", addr, None)?;
Ok(())
}
pub fn set_length(&self, length: u32) -> Result<()> {
self.write_prop("count", length, None)?;
Ok(())
}
pub fn addr(&self) -> Result<u64> {
self.read_prop("address")
}
pub fn length(&self) -> Result<u64> {
self.read_prop("count")
}
pub fn name(&self) -> &str {
&self.name
}
pub fn read<W>(&mut self, regs: &mut Vec<(u64, W)>) -> Result<()> where W: RegisterWidth, <W as Num>::FromStrRadixErr: std::fmt::Debug {
let mut path = self.path.clone();
path.push("data");
let mut file = File::open(path)?;
let mut addr = self.addr()?;
let mut retry = 0;
'outer: loop {
retry += 1;
self.buf.clear();
file.read_to_string(&mut self.buf)?;
let mut lines = self.buf.lines();
loop {
let line = match lines.next() {
Some(l) => {
retry = 0;
l
}
None => {
if retry > 3 {
break 'outer;
}
std::thread::sleep(std::time::Duration::from_millis(2));
continue 'outer;
}
};
//log::trace!("Line: {}", line);
if line.contains("XX") {
addr += 1;
continue;
}
let ndx = if let Some(ndx) = line.find(':') {
ndx
} else {
log::debug!("Couldn't parse line: {}", line);
continue;
};
let (_addr, val) = line.split_at(ndx);
let _addr = u64::from_str_radix(_addr, 16)?;
if addr != _addr {
log::debug!("Address mismatch: {} != {}", addr, _addr);
}
//log::trace!("{}: {:08x}", addr, val);
if let Some(only) = self.only_regs.as_ref() {
if only.binary_search(&(_addr as u16)).is_err() {
addr += 1;
continue;
}
}
let val = val.trim_start_matches(':').trim();
let val = W::from_str_radix(val, 16).unwrap();
regs.push((_addr, val));
addr += 1;
}
}
Ok(())
}
}