mirror of
https://github.com/linux-msm/decodepurr.git
synced 2026-08-13 13:14:57 -07:00
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:
@@ -0,0 +1,2 @@
|
||||
target/
|
||||
src/html/
|
||||
Generated
+1203
File diff suppressed because it is too large
Load Diff
+25
@@ -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"
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
## 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
Binary file not shown.
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
|
||||
pub mod parsers;
|
||||
pub mod regmap;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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(®.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)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user