Skip to content

Commit 75da7c3

Browse files
Update qemu-run so we can publish it and use it in the training examples.
1 parent 79b777f commit 75da7c3

File tree

4 files changed

+161
-30
lines changed

4 files changed

+161
-30
lines changed

firmware/qemu/.cargo/config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ rrb = "-q run --target thumbv7m-none-eabi --release --bin"
55

66
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
77
# runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel"
8-
runner = "cargo -q run --manifest-path ../../qemu-run/Cargo.toml"
8+
runner = "cargo -q run --manifest-path ../../qemu-run/Cargo.toml -- --machine lm3s6965evb --verbose"
99

1010
rustflags = [
1111
# LLD (shipped with the Rust toolchain) is used as the default linker

qemu-run/Cargo.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
[package]
22
authors = ["The Knurling-rs developers"]
3+
categories = ["embedded"]
4+
description = "Runs qemu-system-arm but decodes defmt data sent to semihosting"
35
edition = "2021"
6+
keywords = ["knurling", "logging", "formatting", "qemu"]
47
license = "MIT OR Apache-2.0"
58
name = "qemu-run"
6-
publish = false
7-
version = "0.0.0"
9+
readme = "README.md"
10+
repository = "https://github.com/knurling-rs/defmt"
11+
version = "0.1.0"
812

913
[dependencies]
1014
anyhow = "1"
15+
clap = { version = "4.0", features = ["derive", "env"] }
1116
defmt-decoder = { version = "=1.0.0", path = "../decoder" }

qemu-run/README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# `qemu-run`
2+
3+
> Runs [`qemu-system-arm`] but decodes [`defmt`] data sent to semihosting
4+
5+
[`qemu-system-arm`]: https://www.qemu.org/docs/master/system/target-arm.html
6+
[`defmt`]: https://crates.io/crates/defmt
7+
8+
## Using
9+
10+
Set as your cargo runner, e.g. in your `.cargo/config.toml` file:
11+
12+
```toml
13+
[target.thumbv7em-none-eabihf]
14+
runner = "qemu-run -machine lm3s6965evb"
15+
```
16+
17+
It will execute `qemu-system-arm`, pass the given `-machine` argument, pass
18+
additional arguments to configure semihosting, and pipe semihosting data into
19+
`defmt-decoder` to be decoded and printed to the console.
20+
21+
## Support
22+
23+
`defmt-print` is part of the [Knurling] project, [Ferrous Systems]' effort at
24+
improving tooling used to develop for embedded systems.
25+
26+
If you think that our work is useful, consider sponsoring it via [GitHub
27+
Sponsors].
28+
29+
## License
30+
31+
Licensed under either of
32+
33+
- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or
34+
<http://www.apache.org/licenses/LICENSE-2.0>)
35+
36+
- MIT license ([LICENSE-MIT](../LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
37+
38+
at your option.
39+
40+
### Contribution
41+
42+
Unless you explicitly state otherwise, any contribution intentionally submitted
43+
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
44+
licensed as above, without any additional terms or conditions.
45+
46+
[Knurling]: https://knurling.ferrous-systems.com/
47+
[Ferrous Systems]: https://ferrous-systems.com/
48+
[GitHub Sponsors]: https://github.com/sponsors/knurling-rs

qemu-run/src/main.rs

Lines changed: 105 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
//! # qemu-run
2+
//!
13
//! An alternative to the [`probe-run`](https://github.com/knurling-rs/probe-run) printer,
24
//! used by [`defmt`](https://github.com/knurling-rs/defmt).
5+
//!
36
//! Parses data sent by QEMU over semihosting (ARM Cortex-M only).
4-
//! *Printers* are *host* programs that receive log data, format it and display it.
57
68
use std::{
79
env, fs,
@@ -10,9 +12,35 @@ use std::{
1012
};
1113

1214
use anyhow::{anyhow, bail};
15+
use clap::Parser;
1316
use defmt_decoder::{DecodeError, StreamDecoder, Table};
1417
use process::Child;
1518

19+
/// Run qemu-system-arm, takes defmt logs from semihosting output and prints them to stdout
20+
#[derive(clap::Parser, Clone)]
21+
#[command(name = "qemu-run")]
22+
struct Opts {
23+
/// The firmware running on the device being logged
24+
#[arg(required = false, conflicts_with("version"))]
25+
elf: Option<std::path::PathBuf>,
26+
27+
/// Specify the QEMU machine type
28+
#[arg(long, required = false)]
29+
machine: Option<String>,
30+
31+
/// Specify the QEMU CPU type
32+
#[arg(long, required = false)]
33+
cpu: Option<String>,
34+
35+
/// Print the version number, and quit
36+
#[arg(short = 'V', long)]
37+
version: bool,
38+
39+
/// Print the version number, and quit
40+
#[arg(short = 'v', long)]
41+
verbose: bool,
42+
}
43+
1644
fn main() -> Result<(), anyhow::Error> {
1745
notmain().map(|opt_code| {
1846
if let Some(code) = opt_code {
@@ -22,38 +50,69 @@ fn main() -> Result<(), anyhow::Error> {
2250
}
2351

2452
fn notmain() -> Result<Option<i32>, anyhow::Error> {
25-
let args = env::args().skip(1 /* program name */).collect::<Vec<_>>();
53+
let opts = Opts::parse();
54+
55+
if opts.version {
56+
return print_version();
57+
}
58+
59+
let Some(elf_path) = opts.elf else {
60+
bail!("ELF filename is required. Syntax: `qemu-run -machine <machine> <path-to-elf>`.");
61+
};
2662

27-
if args.len() != 1 {
28-
bail!("expected exactly one argument. Syntax: `qemu-run <path-to-elf>`");
63+
let Some(machine) = opts.machine else {
64+
bail!("Machine type is required. Syntax: `qemu-run -machine <machine> <path-to-elf>`.");
65+
};
66+
67+
if opts.verbose {
68+
eprintln!("QEMU machine is {:?}", machine);
69+
if let Some(cpu) = &opts.cpu {
70+
eprintln!("QEMU cpu is {:?}", cpu);
71+
}
2972
}
3073

31-
let path = &args[0];
32-
let bytes = fs::read(path)?;
74+
let bytes = fs::read(&elf_path)?;
3375

3476
let table = if env::var_os("QEMU_RUN_IGNORE_VERSION").is_some() {
3577
Table::parse_ignore_version(&bytes)
3678
} else {
3779
Table::parse(&bytes)
3880
};
39-
let table = table?.ok_or_else(|| anyhow!("`.defmt` section not found"))?;
81+
let table = match table {
82+
Ok(Some(table)) => table,
83+
Ok(None) => {
84+
bail!("Loaded ELF but did not find a .defmt section");
85+
}
86+
Err(e) => {
87+
bail!("Failed to load ELF: {:?}", e);
88+
}
89+
};
90+
91+
let mut command = Command::new("qemu-system-arm");
92+
command.args([
93+
"-machine",
94+
&machine,
95+
"-nographic",
96+
"-monitor",
97+
"none",
98+
"-semihosting-config",
99+
"enable=on,target=native",
100+
"-kernel",
101+
]);
102+
command.arg(elf_path);
103+
command.stdout(Stdio::piped());
104+
105+
if let Some(cpu) = &opts.cpu {
106+
command.arg("-cpu");
107+
command.arg(cpu);
108+
}
109+
110+
if opts.verbose {
111+
eprintln!("Running: {:?}", command);
112+
}
40113

41114
let mut child = KillOnDrop(
42-
Command::new("qemu-system-arm")
43-
.args([
44-
"-cpu",
45-
"cortex-m3",
46-
"-machine",
47-
"lm3s6965evb",
48-
"-nographic",
49-
"-monitor",
50-
"none",
51-
"-semihosting-config",
52-
"enable=on,target=native",
53-
"-kernel",
54-
])
55-
.arg(path)
56-
.stdout(Stdio::piped())
115+
command
57116
.spawn()
58117
.expect("Error running qemu-system-arm; perhaps you haven't installed it yet?"),
59118
);
@@ -71,24 +130,25 @@ fn notmain() -> Result<Option<i32>, anyhow::Error> {
71130
loop {
72131
let n = stdout.read(&mut readbuf)?;
73132
decoder.received(&readbuf[..n]);
74-
decode(&mut *decoder)?;
133+
decode_and_print(decoder.as_mut())?;
75134

76135
if let Some(status) = child.0.try_wait()? {
136+
// process finished - grab all remaining bytes and quit
77137
exit_code = status.code();
78-
79138
let mut data = Vec::new();
80139
stdout.read_to_end(&mut data)?;
81140
decoder.received(&data);
82-
decode(&mut *decoder)?;
83-
141+
decode_and_print(decoder.as_mut())?;
84142
break;
85143
}
86144
}
87145

146+
// pass back qemu exit code (if any)
88147
Ok(exit_code)
89148
}
90149

91-
fn decode(decoder: &mut dyn StreamDecoder) -> Result<(), DecodeError> {
150+
/// Pump the decoder and print any new frames
151+
fn decode_and_print(decoder: &mut dyn StreamDecoder) -> Result<(), DecodeError> {
92152
loop {
93153
match decoder.decode() {
94154
Ok(frame) => {
@@ -110,3 +170,21 @@ impl Drop for KillOnDrop {
110170
self.0.kill().ok();
111171
}
112172
}
173+
174+
/// Report version from Cargo.toml _(e.g. "0.1.4")_ and supported `defmt`-versions.
175+
///
176+
/// Used by `--version` flag.
177+
#[allow(clippy::unnecessary_wraps)]
178+
fn print_version() -> anyhow::Result<Option<i32>> {
179+
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
180+
let s = if defmt_decoder::DEFMT_VERSIONS.len() > 1 {
181+
"s"
182+
} else {
183+
""
184+
};
185+
println!(
186+
"supported defmt version{s}: {}",
187+
defmt_decoder::DEFMT_VERSIONS.join(", ")
188+
);
189+
Ok(Some(0))
190+
}

0 commit comments

Comments
 (0)