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

[RSDK-6253] Update a device's image while retaining credentials data #204

Merged
merged 30 commits into from
May 20, 2024

Conversation

mattjperez
Copy link
Member

@mattjperez mattjperez commented May 13, 2024

tested with local app image on running device:
cargo r -p micro-rdk-installer -- update-app-image --binary-path target/xtensa-esp32-espidf/esp32-server.bin

The update-app-image command does the following in sequence:

  • read the partition table of a currently running device
  • download/read the new/selected image into memory
  • compare the partition tables between the old app image and new one
  • if they do not match, prompt user to re-build and flash device from scratch
  • overwrite the running device's app image with the new one

Modifications are restricted to just the factory partition, leaving pre-existing NVS data intact.

// let mut flasher = connect(&args.connect_args, &config, false, false).unwrap();
flasher
.read_flash(nvs_offset, nvs_size, 0x1000, 64, nvs_path.clone())
.map_err(Error::EspFlashError)?;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stuck here for some reason. keep getting a (CorruptData(4096, 4039), where 4096 is the read block size, and 4039 is the size of the chunk that was read.

This step is for retrieving the actual NVS data and not just partition info.
Trying to read the full image at the start (before parsing the running partition table) fails with this error as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have not checked the stub. new changes made this part unnecessary

@mattjperez mattjperez marked this pull request as ready for review May 15, 2024 16:28
@mattjperez mattjperez requested a review from a team as a code owner May 15, 2024 16:28
Copy link
Member

@gvaradarajan gvaradarajan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, waiting for resolution of the two comments in this review and for me to give it a quick test

if old_ptable != new_ptable {
log::error!("partition tables do not match!");
log::error!("rebuild and flash micro-rdk from scratch");
return Err(Error::BinaryEditError(64));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why 64? I suspect this isn't an error regarding binary size specifically (although to be fair BinaryEditError is not a good name for that error). If so, could you make a new variant in the error enum for this (and rename BinaryEditError)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right, that's residual and was the closest thing I could find at the time. nice catch!

log::info!("Connecting...");
let mut flasher = connect(
&args.monitor_args.connect_args,
&monitor_args.clone().unwrap().connect_args,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see we're assuming monitor_args is always some flash_args.monitor is true. Instead of assuming, could we check that we're in a valid state regarding these two settings (and return an error if we're not)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, it might not be necessary anymore for MonitorArgs to be Option. checking nowa

At the time when I was still determining the fields for AppImageArgs, if two re-exported commands (FlashArgs, WriteArgs, MonitorArgs, etc) had duplicate fields due to nesting, a runtime panic would happen.
This was a workaround for that, but is no longer necessary.

.map_err(Error::FileError)?;

let file_len = app_file_new.metadata().map_err(Error::FileError)?.len();
if file_len < PARTITION_TABLE_SIZE.into() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks weird to me shouldn't it be PARTITION_TABLE_SIZE+PARTITION_TABLE_ADDR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right, changed.

@mattjperez mattjperez requested a review from npmenard May 16, 2024 17:16
Copy link
Member

@gvaradarajan gvaradarajan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

fn flash(args: WriteFlashArgs, config: &Config, app_path: PathBuf) -> Result<(), Error> {
fn flash(
flash_args: FlashArgs,
monitor_args: MonitorArgs,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we pass MonitorArgs now? what's the upside? how does the new invocation looks like?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

many of espflash's method's, such as Flasher::connect, take these structs as parameters. Sadly, because the structs are all annotated as non-exhaustive, they can't just be initialized directly within our functions (ex let m_args = MonitorArgs{..}).

This means that the only way we can create these input arguments is via clap.
cargo-espflash highlights this in that it also passes all there structs directly or with a thin wrapper.
In addition, we have to be mindful about what espflash Commands we wrap in our own because the flattened fields of all inner commands must be unique, which sadly isn't always the case. espflash as a library isn't very ergonomic.

The new invocation is cargo r -p micro-rdk-installer -- update-app-image --bootloader target/xtensa-esp32-espidf/esp32-server.bin, with the breaking change being the --bootloader flag to specify the binary. Bumping espflash to 3.0 was a breaking change so it seemed an opportune moment to also have breaking change for the installer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feels counterintuitive to use bootloader to pass an app image via a bootloader flag we should add a bin or app-bin arg.

@mattjperez mattjperez requested a review from npmenard May 17, 2024 14:06
micro-rdk-installer/Cargo.toml Outdated Show resolved Hide resolved
Comment on lines 384 to 385
0x1000,
64,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the origin of these values? Why are they correct? When, if ever, might they need to be updated? Let's add some comments or declare some named constants.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they're default values used in espflash. will add comments about it

micro-rdk-installer/src/main.rs Outdated Show resolved Hide resolved
.map_err(|e| Error::PartitionTableError(e.to_string()))?;

log::info!("Retrieving new image");
let tmp_new = tempfile::Builder::new()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar question re why not just tempfile::tempfile

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to above, except it's how our download_micro_rdk_release uses paths and not just something that takes Read.

.prefix("micro-rdk-bin")
.tempdir()
.map_err(Error::FileError)?;
let app_path_new = match &args.flash_args.bootloader {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why bootloader here? Not saying that it is wrong, just a little surprising.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just how espflash's clap structs have things named. Not a huge fan of it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed it to binary_path

micro-rdk-installer/src/main.rs Outdated Show resolved Hide resolved
micro-rdk-installer/src/main.rs Outdated Show resolved Hide resolved
micro-rdk-installer/src/main.rs Outdated Show resolved Hide resolved
micro-rdk-installer/src/main.rs Outdated Show resolved Hide resolved
micro-rdk-installer/src/main.rs Outdated Show resolved Hide resolved
@mattjperez mattjperez requested a review from acmorrow May 17, 2024 18:40
Copy link
Member

@acmorrow acmorrow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code is looking pretty good, though I think there is a related temp file API that would let you avoid needing to come up with names of your own but still gives you paths.

I did note that it appears that the partition table offset can be adjusted via sdkconfig. I'm not sure what we ought to do about that. I'd be fine with the answer being "nothing" or "ticket it".

@@ -24,6 +26,14 @@ use secrecy::Secret;
use serde::Deserialize;
use tokio::runtime::Runtime;

const PARTITION_TABLE_ADDR: u32 = 0x8000;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An option would keep a list of possible values we use and iterate through it when trying to extract the partition table.

Currently, even espflash hardcodes it.

A perfect solution would involve fetching/initializing the sdkconfig, parsing it, using that info when extracting; which may not match if the sdkconfig changed this default address between the old and new app images.

Either way, it's an optimization at best and new feature at worst. Better left to another ticket if we want to look closer at this. I vote leaving it as-is for now and make a ticket when it becomes necessary. This will error at that time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with going forward with the hard-coded value give that espflash does the same.

micro-rdk-installer/src/main.rs Outdated Show resolved Hide resolved
@mattjperez mattjperez requested a review from acmorrow May 17, 2024 20:56
Copy link
Member

@acmorrow acmorrow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This LGTM though I share @npmenard's concerns about the use of the term bootloader.

@@ -24,6 +26,14 @@ use secrecy::Secret;
use serde::Deserialize;
use tokio::runtime::Runtime;

const PARTITION_TABLE_ADDR: u32 = 0x8000;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with going forward with the hard-coded value give that espflash does the same.

@mattjperez mattjperez merged commit 012a41f into viamrobotics:main May 20, 2024
5 checks passed
@mattjperez mattjperez deleted the installer-write-appimage branch May 20, 2024 17:53
@npmenard
Copy link
Member

I can't invoke write-flash anymore seems like it's expecting --elf and --log-format or is another argment mandatory now? Seems like it's the case since c317551
also passing --monitor with update-app-image doesn't open a serial monitor after flashing is done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants