Medius - Rust LibraryUpdate

Update

Replace either chip's firmware over the open connection

Write a new image with stage_firmware and commit it with activate_firmware; update_firmware does both for one chip, and abort_update throws a transfer away. Read what each chip is running with firmware_info. Nothing reboots into ROM download and no second port is involved; the wire is UPDATE.

Update a...Write itWrite and commit it
single chipstage_firmwareupdate_firmware
both chipsstage_firmware twicethen activate_firmware once
  stage_firmware(Host, ..)   --> host image into the host chip's spare slot
  stage_firmware(Device, ..) --> device image into the device chip's spare slot
  activate_firmware()        --> commit both, host chip reboots first
EXAMPLE
use medius::{Device, UpdateTarget};

let device = Device::find()?;
let host = std::fs::read("medius_host.bin")?;
let dev = std::fs::read("medius_device.bin")?;

// The host chip first: its image travels through the device chip.
device.stage_firmware(UpdateTarget::Host, &host, &mut |p| {
    println!("host {}%", p.percent());
})?;
device.stage_firmware(UpdateTarget::Device, &dev, &mut |p| {
    println!("device {}%", p.percent());
})?;
device.activate_firmware()?;

stage_firmware

Write an image into a chip's spare slot, without booting it
fn stage_firmware(&self, target: UpdateTarget, image: &[u8], progress: &mut dyn FnMut(UpdateProgress)) -> Result<u32>

Blocks

Blocks for the whole transfer, a few seconds per chip. The clone disconnects from the game PC for the duration and comes back afterwards. Waits for both chips to confirm the image they booted before it starts, because a chip on probation refuses to open another update and returns Error::Update with ON_PROBATION.

ParameterTypeDescription
targetUpdateTargetWhich chip to write: Device or Host.
image&[u8]The whole .bin. Larger than slot_size is refused with TOO_BIG before a byte is sent.
progress&mut dyn FnMut(UpdateProgress)Called once per acknowledged window, not once per chunk.

Returns the number of bytes the box wrote. A box on the single-app layout answers NO_SLOT and needs one flash over ROM download first.

A staged image is inert. Nothing boots it until activate_firmware, so a power cut in between brings the running firmware back.

EXAMPLE
use medius::{Device, UpdateTarget};

let device = Device::find()?;
let image = std::fs::read("medius_device.bin")?;

let written = device.stage_firmware(UpdateTarget::Device, &image, &mut |p| {
    print!("\r{}%", p.percent());          // one call per acknowledged window
})?;
println!("\nstaged {written} bytes, not yet booted");

activate_firmware

Commit every staged image and boot into it
fn activate_firmware(&self) -> Result<()>

Blocks

Commits every staged image and reboots into it, host chip first, then reconnects. Takes tens of seconds if the host chip is involved. With nothing staged it returns Error::Update with NOTHING_STAGED.

A chip that boots an image which cannot run is reverted by the bootloader without anyone asking, so a bad image costs a reboot rather than a box. See rollback.

A refusal stops at the host chip and leaves the device image staged and armed, so the next call would commit it alone and put the two chips on different versions. Either retry the whole update or abort_update each staged target first.

EXAMPLE
device.activate_firmware()?;
let fw = device.firmware_info()?;
println!("now on ota_{} ({})", fw.device.slot, fw.device.state);

abort_update

Throw a staged or in-flight transfer away
fn abort_update(&self, target: UpdateTarget) -> Result<()>

Blocks

Drops whatever is staged or in flight for one target. The clone comes back without a reboot, and the running slot is untouched. A session left alone times out on the box after ten seconds, so this is a courtesy rather than a requirement.

ParameterTypeDescription
targetUpdateTargetWhich chip to clear: Device or Host.

Sent while an activate is waiting on the host chip, it abandons that wait and disarms both chips whichever target it names.

EXAMPLE
use medius::UpdateTarget;

if let Err(e) = device.activate_firmware() {
    device.abort_update(UpdateTarget::Host)?;    // disarm what the refusal left behind
    device.abort_update(UpdateTarget::Device)?;
    return Err(e);
}

update_firmware

Stage one image and activate it in a single call
fn update_firmware(&self, target: UpdateTarget, image: &[u8], progress: &mut dyn FnMut(UpdateProgress)) -> Result<()>

Blocks

stage_firmware followed by activate_firmware, for the one-chip case. Use the two calls separately to update both chips together. If the activate refuses, the staged image is cleared before the error is returned.

ParameterTypeDescription
targetUpdateTargetWhich chip to write: Device or Host.
image&[u8]The whole .bin.
progress&mut dyn FnMut(UpdateProgress)Called once per acknowledged window.
EXAMPLE
use medius::{Device, UpdateTarget};

let device = Device::find()?;
let image = std::fs::read("medius_device.bin")?;
device.update_firmware(UpdateTarget::Device, &image, &mut |p| {
    print!("\r{}%", p.percent());
})?;

On AsyncDevice

The same calls, awaitable
async fn stage_firmware(&self, target: UpdateTarget, image: &[u8], progress: &mut dyn FnMut(UpdateProgress)) -> Result<u32>
async fn activate_firmware(&self) -> Result<()>
async fn abort_update(&self, target: UpdateTarget) -> Result<()>
async fn update_firmware(&self, target: UpdateTarget, image: &[u8], progress: &mut dyn FnMut(UpdateProgress)) -> Result<()>

Blocks

cargo add medius --features async

Each one runs the synchronous transfer on its own thread and resolves when it finishes, so the crate stays runtime-agnostic. They are not cancellable: dropping the future does not stop a transfer the box has already begun.

EXAMPLE
use futures::executor::block_on;
use medius::{Device, UpdateTarget};

let device = Device::find()?.into_async();
let image = std::fs::read("medius_device.bin")?;
block_on(device.update_firmware(UpdateTarget::Device, &image, &mut |_| {}))?;