Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migration guide v0.2.x to v0.3.0 #547

Merged
merged 17 commits into from
Oct 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- [panic! and assert!](./panic.md)
- [Printers](./printers.md)
- [Encoding](./encoding.md)
- [Migrating from `v0.2.x` to `v0.3.0`](./migration-02-03.md)
- [Design & impl details](./design.md)
- [Interning](./interning.md)
- [Dealing with duplicates](./duplicates.md)
Expand Down
311 changes: 311 additions & 0 deletions book/src/migration-02-03.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
# Migrating from `v0.2.x` to `v0.3.0`

This guide covers how to upgrade a library or application using `defmt v0.2.x` to version `v0.3.0`.

## `Cargo.toml`

Update the version of `defmt` to `"0.3"` (or `"0.3.0"`, which is equivalent).

Additionally please remove the `defmt-*` cargo features from your `[features]` section.

```diff
[dependencies]

- defmt = "0.2"
+ defmt = "0.3"

[features]
default = [
"other-feature",
- "defmt-default",
- "dependency-a/defmt-trace",
]

other-feature = []
Urhengulas marked this conversation as resolved.
Show resolved Hide resolved

- defmt-default = []
- defmt-trace = []
- defmt-debug = []
- defmt-info = []
- defmt-warn = []
- defmt-error = []
```

## Set the log-level with `DEFMT_LOG`

Setting the log-level via cargo features is superseded by the new `DEFMT_LOG` environment variable.
Urhengulas marked this conversation as resolved.
Show resolved Hide resolved

To log everything on the `INFO` level and above, run your application like following:

```console
$ DEFMT_LOG=info cargo run
```

For more details how to configure the log-level using `DEFMT_LOG` see the [user docs](./filtering.md#defmt_log).
Urhengulas marked this conversation as resolved.
Show resolved Hide resolved

This new mechanism is modelled to be similar to the well-known `RUST_LOG` and now also supports log-configuration down to the module-level!

## Upgrade display hints

> 💡 Easily change the display hints, using the search-and-replace feature of your editor. *([vs code](https://code.visualstudio.com/docs/editor/codebasics#_search-and-replace))*

### Rename display hint `µs` to `us`

Due to ambiguity in-between `µ` (micro sign) and `μ` (small mu), the display hint for microseconds changed to be `us`.

Therefore you likely need to update your timestamp definition.

```diff
- defmt::timestamp!("{=u32:µs}", {
+ defmt::timestamp!("{=u32:us}", {
// ...
});
```

As well as all other logging calls where you were using `µs`.

```diff
- defmt::info!("{=u8:µs}", time)
+ defmt::info!("{=u8:us}", time)
```

### Drop `u24` type hint

The `u24` type hint got dropped, cause it was confusing users and complicated internal parser logic.

Therefore replace it with `u32` in all logging calls.

```diff
- defmt::info!("{=u24}", 42);
+ defmt::info!("{=u32}", 42);
```

## Adapt manual `trait Logger` implementations

The `Logger` trait has seen a couple of big changes, for one the function signatures of a few methods have changed, the previous `Write` trait got removed while its `write` method is part of `Logger` now and the method `flush` was added.

> 💡 If you are using one of our loggers, `defmt-rtt` and `defmt-itm`, no action is required!

Let's see what a new implementation of the `Logger` in crate `defmt-itm` looks like compared to the previous implementation in version `0.2`. The following abbreviated example code shows how the `Logger` worked before.

```rust,ignore
#[defmt::global_logger]
struct Logger;

static TAKEN: AtomicBool = AtomicBool::new(false);

unsafe impl defmt::Logger for Logger {
fn acquire() -> Option<NonNull<dyn defmt::Write>> {
// disable interrupts

if !TAKEN.load(Ordering::Relaxed) {
// acquire the lock
TAKEN.store(true, Ordering::Relaxed);
Some(NonNull::from(&Logger as &dyn defmt::Write))
} else {
None
}
}

unsafe fn release(_: NonNull<dyn defmt::Write>) {
// release the lock
TAKEN.store(false, Ordering::Relaxed);

// re-enable interrupts
}
}

impl defmt::Write for Logger {
fn write(&mut self, bytes: &[u8]) {
unsafe { itm::write_all(&mut (*ITM::ptr()).stim[0], bytes) }
}
}
```

And here is how it conceptually works now:

```rust
# extern crate defmt;
# use std::sync::atomic::{AtomicBool,Ordering};

#[defmt::global_logger]
struct Logger;

static TAKEN: AtomicBool = AtomicBool::new(false);
static mut ENCODER: defmt::Encoder = defmt::Encoder::new();

unsafe impl defmt::Logger for Logger {
fn acquire() {
// disable interrupts

if TAKEN.load(Ordering::Relaxed) {
panic!("defmt logger taken reentrantly")
}

// acquire the lock
TAKEN.store(true, Ordering::Relaxed);

unsafe { ENCODER.start_frame(do_write) }
}

unsafe fn flush() {
// Omitted. We will come to this in a second.
}

unsafe fn release() {
ENCODER.end_frame(do_write);

// release the lock
TAKEN.store(false, Ordering::Relaxed);

// re-enable interrupts
}

unsafe fn write(bytes: &[u8]) {
ENCODER.write(bytes, do_write);
}
}

fn do_write(bytes: &[u8]) {
unsafe { itm::write_all(&mut (*ITM::ptr()).stim[0], bytes) }
}

# // mock cortex_m-crate
# mod itm {
# pub unsafe fn write_all(a: &mut u8, b: &[u8]) {}
# }
# struct ITM { stim: [u8; 1] }
# impl ITM {
# fn ptr() -> *mut ITM { &mut ITM { stim: [0] } as *mut _ }
# }
```

Let us go through the changes step-by-step:
- Drop `trait Write`:
- Extract the `fn write` from the `trait Write` and name it `fn do_write`.
- Remove the `impl defmt::Write for Logger`.
- Remove the the first argument of `&mut self` from `fn do_write`.
- Add a new `static mut ENCODER: defmt::Encoder = defmt::Encoder::new()` outside the `impl defmt::Logger for Logger`-block.
- Adapt `fn acquire`:
- Remove the return type `Option<NonNull<dyn defmt::Write>>` from `fn acquire`.
- Replace all `return None` with an explicit `panic!`, with a descriptive error message.
- Call `unsafe { ENCODER.start_frame(do_write) }`, after you acquired the lock.
- Adapt `fn release`:
- Call `ENCODER.end_frame(do_write);`, before releasing the lock.
- Add new method `unsafe fn write` to `impl defmt::Logger for Logger`:
```rust,noplayground
# extern crate defmt;
# static mut ENCODER: defmt::Encoder = defmt::Encoder::new();
# fn do_write(bytes: &[u8]) {}
unsafe fn write(bytes: &[u8]) {
ENCODER.write(bytes, do_write);
}
```

And that is already it!

### Flush

One final thing is left before your custom `trait Logger` implementation works again: You need to implement `fn flush`.

This functionality is what gets used when calling `defmt::flush`, [whose docs say](https://docs.rs/defmt/*/defmt/fn.flush.html):
> Block until host has read all pending data.
>
> The flush operation will not fail, but might not succeed in flushing all pending data. It is implemented as a “best effort” operation.
Urhengulas marked this conversation as resolved.
Show resolved Hide resolved

The idea is to ensure that _all data_ is read by the host. Take `defmt-rtt` as an example:

```rust
# extern crate defmt;
# use std::sync::atomic::{AtomicUsize,Ordering};

# #[defmt::global_logger]
# struct Logger;

# static READ: AtomicUsize = AtomicUsize::new(0);
# static WRITE: AtomicUsize = AtomicUsize::new(0);

unsafe impl defmt::Logger for Logger {
fn acquire() {
// ...
}

unsafe fn flush() {
// busy wait, until the read- catches up with the write-pointer
let read = || READ.load(Ordering::Relaxed);
let write = || WRITE.load(Ordering::Relaxed);
while read() != write() {}
}

unsafe fn release() {
// ...
}

unsafe fn write(bytes: &[u8]) {
// ...
}
}
```

If your transport doesn't allow to ensure that _all data_ got read, `flush` should at least flush _as much data as possible_. Take `defmt-itm` as an example for this:

```rust
# extern crate defmt;

# #[defmt::global_logger]
# struct Logger;

unsafe impl defmt::Logger for Logger {
fn acquire() {
// ...
}

unsafe fn flush() {
// wait for the queue to be able to accept more data
while !stim_0().is_fifo_ready() {}

// delay "a bit" to drain the queue
// This is a heuristic and might be too short in reality.
// Please open an issue if it is!
asm::delay(100);
}

unsafe fn release() {
// ...
}

unsafe fn write(bytes: &[u8]) {
// ...
}
}

# // mock cortex_m-crate
# struct STIM0;
# impl STIM0 {
# fn is_fifo_ready(&self) -> bool { true }
# }
# fn stim_0() -> STIM0 { STIM0 }
# mod asm {
# pub fn delay(cycles: usize) {}
# }
```

`defmt::flush` can be used before a hard-reset of the device, where you would loose data if you do not flush.

Since you are the expert of your transport, implement the method now!

## Unified `probe-run` backtrace options

The new `--backtrace` and `--backtrace-limit` of `probe-run` should simplify the configuration.

```console
cargo run --bin panic --backtrace=always --backtrace-limit=5
```

Using `--backtrace` you can now specify if you want to have a backtrace `never`, `always` or only in case of an exception (the `auto` option, which is the default). For the latter two options you can specify the maximum backtrace length, which defaults to `50` and can be set to unlimited with `0`.

See [the `probe-run`-README](https://github.com/knurling-rs/probe-run#backtrace-options) for all the options.

## Congrats! 🎉

If you followed all the steps in this guide, your application should work flawlessly again and make use of all the internal and user-facing improvements shipped in this release!
16 changes: 8 additions & 8 deletions book/src/primitives.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

The following **primitive types** are available:

| type hint | name |
| :------------------- | :---------------------------------- |
| `=bool` | boolean |
| `={i,u}{8,16,32,64}` | standard integer types |
| `=f{32, 64}` | 32-bit / 64-bit floating point type |
| `=[u8; N]` | byte array |
| `=[u8]` | byte slice |
| `=str` | string slice |
| type hint | name |
| :----------------------- | :---------------------------------- |
| `=bool` | boolean |
| `={i,u}{8,16,32,64,128}` | standard integer types |
| `=f{32, 64}` | 32-bit / 64-bit floating point type |
| `=[u8; N]` | byte array |
| `=[u8]` | byte slice |
| `=str` | string slice |

They can be used like this:

Expand Down
9 changes: 8 additions & 1 deletion xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,14 @@ fn test_book() {
do_test(|| run_command("cargo", &["clean"], None, &[]), "book");

do_test(
|| run_command("cargo", &["build", "--features", "unstable-test"], None, &[]),
|| {
run_command(
"cargo",
&["build", "-p", "defmt", "--features", "unstable-test"],
None,
&[],
)
},
"book",
);

Expand Down