Skip to content

Commit a1738ee

Browse files
author
Jorge Aparicio
committed
initial commit
0 parents  commit a1738ee

File tree

9 files changed

+378
-0
lines changed

9 files changed

+378
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.rs.bk
2+
Cargo.lock
3+
target
4+
tests/

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "cmsis-svd"]
2+
path = cmsis-svd
3+
url = git://github.com/posborne/cmsis-svd.git

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
authors = ["Jorge Aparicio <[email protected]>"]
3+
name = "svd"
4+
version = "0.1.0"
5+
6+
[dependencies]
7+
xmltree = "0.3.2"

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[![Build Status][status]](https://travis-ci.org/japaric/svd)
2+
3+
[status]: https://travis-ci.org/japaric/svd.svg?branch=master
4+
5+
# `svd`
6+
7+
> A WIP CMSIS-SVD file parser
8+
9+
## License
10+
11+
Licensed under either of
12+
13+
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
14+
http://www.apache.org/licenses/LICENSE-2.0)
15+
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
16+
17+
at your option.
18+
19+
### Contribution
20+
21+
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the
22+
work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
23+
additional terms or conditions.

cmsis-svd

Submodule cmsis-svd added at 9ce0610

generate-tests.sh

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
elementIn() {
6+
local e
7+
for e in "${@:2}"; do
8+
[[ "$e" == "$1" ]] && return 0
9+
done
10+
return 1
11+
}
12+
13+
main() {
14+
local device tests_dir=$(pwd)/tests/
15+
local blacklist=(
16+
# These SVD files have some registers with a `resetValue` bigger than the register itself
17+
Toshiba/M365
18+
Toshiba/M367
19+
Toshiba/M368
20+
Toshiba/M369
21+
Toshiba/M36B
22+
)
23+
24+
rm -f tests/*.rs
25+
26+
local vendor_dir
27+
for vendor_dir in $(echo cmsis-svd/data/*); do
28+
local vendor=$(basename $vendor_dir)
29+
cat >"$tests_dir/$vendor.rs" <<EOF
30+
#![allow(non_snake_case)]
31+
32+
extern crate svd;
33+
34+
use svd::Device;
35+
EOF
36+
37+
local device_path
38+
for device_path in $(find $vendor_dir/* -name '*.svd'); do
39+
local device=$(basename $device_path)
40+
device=${device%.svd}
41+
42+
if elementIn "$vendor/$device" "${blacklist[@]}"; then
43+
continue
44+
fi
45+
46+
device=${device//./_}
47+
48+
cat >>"$tests_dir/$vendor.rs" <<EOF
49+
#[test]
50+
fn $device() {
51+
let xml = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/$device_path"));
52+
53+
Device::parse(xml);
54+
}
55+
EOF
56+
done
57+
done
58+
}
59+
60+
main

src/bin/parse.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
extern crate svd;
2+
3+
use std::env;
4+
use std::fs::File;
5+
use std::io::Read;
6+
7+
use svd::Device;
8+
9+
fn main() {
10+
let xml = &mut String::new();
11+
File::open(env::args_os().skip(1).next().unwrap()).unwrap().read_to_string(xml).unwrap();
12+
13+
println!("{:#?}", Device::parse(xml));
14+
}

src/lib.rs

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
//! WIP CMSIS-SVD file parser
2+
//!
3+
//! Early stage. Not much documentation at the moment but here's some starting code:
4+
//!
5+
//! ``` ignore
6+
//! extern crate svd;
7+
//!
8+
//! use svd::Device;
9+
//!
10+
//! fn main() {
11+
//! println!("{:?}", Device::parse(include_str!("/path/to/file.svd")));
12+
//! }
13+
//! ```
14+
//!
15+
//! # References
16+
//!
17+
//! - [SVD Schema file](https://www.keil.com/pack/doc/CMSIS/SVD/html/group__schema__1__2__gr.html)
18+
//! - [SVD file database](https://github.com/posborne/cmsis-svd/tree/master/data)
19+
//! - [Sample SVD file](https://www.keil.com/pack/doc/CMSIS/SVD/html/svd__example_pg.html)
20+
21+
#![deny(warnings)]
22+
23+
extern crate xmltree;
24+
25+
use xmltree::Element;
26+
27+
macro_rules! try {
28+
($e:expr) => {
29+
$e.expect(concat!(file!(), ":", line!(), " ", stringify!($e)))
30+
}
31+
}
32+
33+
mod parse;
34+
35+
trait ElementExt {
36+
fn get_child_text<K>(&self, k: K) -> Option<String> where String: PartialEq<K>;
37+
fn debug(&self);
38+
}
39+
40+
impl ElementExt for Element {
41+
fn get_child_text<K>(&self, k: K) -> Option<String>
42+
where String: PartialEq<K>
43+
{
44+
self.get_child(k).map(|c| try!(c.text.clone()))
45+
}
46+
47+
fn debug(&self) {
48+
println!("<{}>", self.name);
49+
for c in &self.children {
50+
println!("{}: {:?}", c.name, c.text)
51+
}
52+
println!("</{}>", self.name);
53+
}
54+
}
55+
56+
#[derive(Debug)]
57+
pub struct Device {
58+
pub name: String,
59+
pub peripherals: Vec<Peripheral>,
60+
pub defaults: Defaults,
61+
}
62+
63+
impl Device {
64+
/// Parses a SVD file
65+
///
66+
/// # Panics
67+
///
68+
/// If the input is an invalid SVD file (yay, no error handling)
69+
pub fn parse(svd: &str) -> Device {
70+
let tree = &try!(Element::parse(svd.as_bytes()));
71+
72+
Device {
73+
name: try!(tree.get_child_text("name")),
74+
peripherals: try!(tree.get_child("peripherals"))
75+
.children
76+
.iter()
77+
.map(Peripheral::parse)
78+
.collect(),
79+
defaults: Defaults::parse(tree),
80+
}
81+
}
82+
}
83+
84+
#[derive(Debug)]
85+
pub struct Peripheral {
86+
pub name: String,
87+
pub description: Option<String>,
88+
pub base_address: u32,
89+
pub interrupt: Option<Interrupt>,
90+
pub registers: Option<Vec<Register>>,
91+
}
92+
93+
impl Peripheral {
94+
fn parse(tree: &Element) -> Peripheral {
95+
assert_eq!(tree.name, "peripheral");
96+
97+
Peripheral {
98+
name: try!(tree.get_child_text("name")),
99+
description: tree.get_child_text("description"),
100+
base_address: try!(parse::u32(try!(tree.get_child("baseAddress")))),
101+
interrupt: tree.get_child("interrupt").map(Interrupt::parse),
102+
registers: tree.get_child("registers").map(|rs| {
103+
rs.children
104+
.iter()
105+
.filter_map(Register::parse)
106+
.collect()
107+
}),
108+
}
109+
}
110+
}
111+
112+
#[derive(Debug)]
113+
pub struct Interrupt {
114+
pub name: String,
115+
pub description: Option<String>,
116+
pub value: u32,
117+
}
118+
119+
impl Interrupt {
120+
fn parse(tree: &Element) -> Interrupt {
121+
Interrupt {
122+
name: try!(tree.get_child_text("name")),
123+
description: tree.get_child_text("description"),
124+
value: try!(parse::u32(try!(tree.get_child("value")))),
125+
}
126+
}
127+
}
128+
129+
#[derive(Debug)]
130+
pub struct Register {
131+
pub name: String,
132+
pub description: String,
133+
pub address_offset: u32,
134+
pub size: Option<u32>,
135+
pub access: Option<Access>,
136+
pub reset_value: Option<u32>,
137+
pub reset_mask: Option<u32>,
138+
pub fields: Option<Vec<Field>>,
139+
}
140+
141+
impl Register {
142+
// TODO handle "clusters", return `Register` not an `Option`
143+
fn parse(tree: &Element) -> Option<Register> {
144+
if tree.name == "cluster" {
145+
return None;
146+
}
147+
148+
assert_eq!(tree.name, "register");
149+
150+
Some(Register {
151+
name: try!(tree.get_child_text("name")),
152+
description: try!(tree.get_child_text("description")),
153+
address_offset: try!(parse::u32(try!(tree.get_child("addressOffset")))),
154+
size: tree.get_child("size").map(|t| try!(parse::u32(t))),
155+
access: tree.get_child("access").map(Access::parse),
156+
reset_value: tree.get_child("resetValue").map(|t| try!(parse::u32(t))),
157+
reset_mask: tree.get_child("resetMask").map(|t| try!(parse::u32(t))),
158+
fields: tree.get_child("fields")
159+
.map(|fs| fs.children.iter().map(Field::parse).collect()),
160+
})
161+
}
162+
}
163+
164+
#[derive(Debug)]
165+
pub enum Access {
166+
ReadOnly,
167+
ReadWrite,
168+
ReadWriteOnce,
169+
WriteOnly,
170+
}
171+
172+
impl Access {
173+
fn parse(tree: &Element) -> Access {
174+
let text = try!(tree.text.as_ref());
175+
176+
match &text[..] {
177+
"read-only" => Access::ReadOnly,
178+
"read-write" => Access::ReadWrite,
179+
"read-writeOnce" => Access::ReadWriteOnce,
180+
"write-only" => Access::WriteOnly,
181+
_ => panic!("unknown access variant: {}", text),
182+
}
183+
}
184+
}
185+
186+
#[derive(Debug)]
187+
pub struct Field {
188+
pub name: String,
189+
pub description: Option<String>,
190+
pub bit_range: BitRange,
191+
}
192+
193+
impl Field {
194+
fn parse(tree: &Element) -> Field {
195+
assert_eq!(tree.name, "field");
196+
197+
Field {
198+
name: try!(tree.get_child_text("name")),
199+
description: tree.get_child_text("description"),
200+
bit_range: BitRange::parse(tree),
201+
}
202+
}
203+
}
204+
205+
#[derive(Debug)]
206+
pub struct BitRange {
207+
pub offset: u32,
208+
pub width: u32,
209+
}
210+
211+
impl BitRange {
212+
fn parse(tree: &Element) -> BitRange {
213+
let (start, end) = if let Some(range) = tree.get_child("bitRange") {
214+
let text = try!(range.text.as_ref());
215+
216+
assert!(text.starts_with('['));
217+
assert!(text.ends_with(']'));
218+
219+
let mut parts = text[1..text.len() - 1].split(':');
220+
221+
(try!(try!(parts.next()).parse()), try!(try!(parts.next()).parse()))
222+
} else if let (Some(lsb), Some(msb)) = (tree.get_child_text("lsb"),
223+
tree.get_child_text("msb")) {
224+
(try!(lsb.parse()), try!(msb.parse::<u32>()))
225+
} else {
226+
return BitRange {
227+
offset: try!(try!(tree.get_child_text("bitOffset")).parse()),
228+
width: try!(try!(tree.get_child_text("bitWidth")).parse()),
229+
};
230+
};
231+
232+
BitRange {
233+
offset: start,
234+
width: end - start + 1,
235+
}
236+
}
237+
}
238+
239+
/// Register default properties
240+
#[derive(Debug)]
241+
pub struct Defaults {
242+
pub size: Option<u32>,
243+
pub reset_value: Option<u32>,
244+
pub reset_mask: Option<u32>,
245+
}
246+
247+
impl Defaults {
248+
fn parse(tree: &Element) -> Defaults {
249+
Defaults {
250+
size: tree.get_child("size").map(|t| try!(parse::u32(t))),
251+
reset_value: tree.get_child("resetValue").map(|t| try!(parse::u32(t))),
252+
reset_mask: tree.get_child("resetMask").map(|t| try!(parse::u32(t))),
253+
}
254+
}
255+
}

src/parse.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use xmltree::Element;
2+
3+
pub fn u32(tree: &Element) -> Option<u32> {
4+
let text = try!(tree.text.as_ref());
5+
6+
if text.starts_with("0x") || text.starts_with("0X") {
7+
u32::from_str_radix(&text["0x".len()..], 16).ok()
8+
} else {
9+
text.parse().ok()
10+
}
11+
}

0 commit comments

Comments
 (0)