uf2 Binary Serialization and Cargo Subcommands
Topics:
- Binary serialization for bootloader protocols.
- Rust and WASM in a Chrome extension
- Elixer and Rust and OpenCL
- Command line argument processing
- Handy tools like clippy, cargo-expand, cargo
Binary Serialization via @jacobrosenthal
Im trying to simplify uploading code to the pygamer. Theres an existing uf2 converter and you can just manually copy the file to the disk, but its kind of a lot of lines of setup and libraries we need installed. Honestly it works fine, I just like the idea of learning some more about Rust while cleaning up the workflow. This is the current state of things
#only needed one time cargo install --git https://github.com/sajattack/uf2conv-rs cargo install cargo-binutils rustup component add llvm-tools-preview #each compile, objcopy actually already calls cargo under the hood here saving 1 line cargo objcopy --example ferris_img --release -- -O binary ferris_img.bin uf2conv-rs ferris_img.bin --base 0x4000 --output ferris_img.uf2 cp ferris_img.uf2 /Volumes/PYGAMERBOOT/
But its a lot of dependencies. Instead talking to the device over the HID protocol using the microsoft bootloader protocol called hf2 which adafruit uses heavily. My work is at uf2-rs where I use live upload over usb instead of a copy
cargo install --git https://github.com/jacobrosenthal/uf2 cargo install cargo-binutils rustup component add llvm-tools-preview cargo objcopy --example ferris_img --release -- -O binary ferris_img.bin uf2 flash -f ferris.bin 0x4000
My next step not yet done yet is to turn it into a cargo command using cargo-project reducing it into something like:
cargo install --git https://github.com/jacobrosenthal/uf2 cargo install cargo-binutils rustup component add llvm-tools-preview cargo uf2 --example ferris_img --release 0x4000
Ideally I can figure out how to convert the cargo elf file to a bin internally and remove the need for even llvm dependencies
cargo install --git https://github.com/jacobrosenthal/uf2 cargo uf2 --example ferris_img --release 0x4000
The big thing this library needs to solve is how binary serialization to turn this c style struct
pub(crate) struct Command { ///Command ID id: u32, ///arbitrary number set by the host, for example as sequence number. The response should repeat the tag. tag: u16, ///reserved bytes in the command should be sent as zero and ignored by the device _reserved0: u8, ///reserved bytes in the command should be sent as zero and ignored by the device _reserved1: u8, }
into a buffer to send over the wire. In std rust you might use the std cursor and The core available from_le_bytes to help us turn bytes into primitives, and not mess up indexing, but but I like to write no_std code so I use a library called scroll like this
//Packets are up to 64 bytes long let buffer = &mut [0_u8; 64]; let mut offset = 1; buffer.gwrite_with(cmd.id, &mut offset, LE)?; buffer.gwrite_with(cmd.tag, &mut offset, LE)?; buffer.gwrite_with(cmd._reserved0, &mut offset, LE)?; buffer.gwrite_with(cmd._reserved1, &mut offset, LE)?;
Other topics discussed:
Using +nightly
For those not in the know say someone says you need the nightly build of a cargo crate. So you have to rustup default nightly
first then cargo install blah-thing
and then rustup default stable
Instead you can add +nightly
onto most commands to do something in nightly for just one command so all that becomes cargo +nightly install blah-thing
cargo-asm to see what assembly language is generated
cargo new helloworld && cd helloworld
$ cargo asm helloworld::main --rust fn main() { push rbp mov rbp, rsp sub rsp, 48 lea rax, [rip, +, l___unnamed_2] mov qword, ptr, [rbp, -, 48], rax mov qword, ptr, [rbp, -, 40], 1 mov qword, ptr, [rbp, -, 32], 0 lea rax, [rip, +, l___unnamed_3] mov qword, ptr, [rbp, -, 16], rax mov qword, ptr, [rbp, -, 8], 0 lea rdi, [rbp, -, 48] println!("Hello, world!"); call std::io::stdio::_print } add rsp, 48 pop rbp ret
cargo-expand to expand macros to the console so you can see what you're generating.
You may have seen by now you can implement Debug on a type for free if underlying types support it by using the #[derive(Debug)]
attribute macro. Using cargo expand we can see what that did for us:
#[derive(Debug)] struct S; fn main() { }
#![feature(prelude_import)] #![no_std] #[prelude_import] use ::std::prelude::v1::*; #[macro_use] extern crate std as std; struct S; #[automatically_derived] #[allow(unused_qualifications)] impl ::core::fmt::Debug for S { fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { match *self { S => { let mut debug_trait_builder = f.debug_tuple("S"); debug_trait_builder.finish() } } } } fn main() { }
cargo clippy for more help after it compiles
The compiler makes sure things compile, clippy tries to tell you there might be a better way to do things. You can replace 'check' in your ide with 'clippy' or run it on the command line.
$ cargo clippy warning: casting u32 to f64 may become silently lossy if you later change the type --> src/bin.rs:166:51 | 166 | let padded_num_pages = (binary.len() as f64 / bininfo.flash_page_size as f64).ceil() as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `f64::from(bininfo.flash_page_size)` | = note: `#[warn(clippy::cast_lossless)]` on by default = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless Finished dev [unoptimized + debuginfo] target(s) in 12.93s $
Note you have it installed on nightly, it can also apply the suggestion automatically.
$ cargo +nightly fix -Z unstable-options --clippy $ cargo clippy Finished dev [unoptimized + debuginfo] target(s) in 0.57s $
There is a list of ALL the Clippy Lints which shows you all the rules in one place.
CLI Apps with clap.rs via @danielbank
Demo Repo
https://github.com/danielbank/next-prime-rs
Builder Pattern for Making a CLI
let matches = App::new("next-prime") .version("1.0") .about("Calculate the next prime after a given integer") .author("Daniel Bank") .arg( Arg::with_name("number") .help("a number after which the next prime will occur") .index(1) .required(true), ) .get_matches();
Crates You Should Know
Learnings
Return Values
-
Forgot the return type (
-> u64
) -
I was trying to return using a statement:
n;
vs an expression:n
Statements are instructions that perform some action and do not return a value. Expressions evaluate to a resulting value.
error[E0308]: mismatched types
--> src/main.rs:4:26
|
4 | fn next_prime(x: u64) -> u64 {
| ---------- ^^^ expected u64, found ()
| |
| implicitly returns `()` as its body has no tail or `return` expression
...
7 | n;
| - help: consider removing this semicolon
|
= note: expected type `u64`
found type `()`