factory: init, writes upgrades from sd card to flash and sets up ESP to boot newly written app

This commit is contained in:
decentclock
2022-09-19 11:43:11 -04:00
parent 79fea3092d
commit f59aa3de57
11 changed files with 323 additions and 0 deletions

View File

@@ -8,6 +8,7 @@ members = [
exclude = [
"broker",
"factory",
"persister",
"sphinx-key",
]

View File

@@ -0,0 +1,34 @@
[build]
# Uncomment the relevant target for your chip here (ESP32, ESP32-S2, ESP32-S3 or ESP32-C3)
#target = "xtensa-esp32-espidf"
#target = "xtensa-esp32s2-espidf"
#target = "xtensa-esp32s3-espidf"
target = "riscv32imc-esp-espidf"
[target.xtensa-esp32-espidf]
linker = "ldproxy"
[target.xtensa-esp32s2-espidf]
linker = "ldproxy"
[target.xtensa-esp32s3-espidf]
linker = "ldproxy"
[target.riscv32imc-esp-espidf]
linker = "ldproxy"
# Future - necessary for the experimental "native build" of esp-idf-sys with ESP32C3
# See also https://github.com/ivmarkov/embuild/issues/16
rustflags = ["-C", "default-linker-libraries"]
[unstable]
build-std = ["std", "panic_abort"]
build-std-features = ["panic_immediate_abort"] # Required for older ESP-IDF versions without a realpath implementation
[env]
# Note: these variables are not used when using pio builder
# Enables the esp-idf-sys "native" build feature (`cargo build --features native`) to build against ESP-IDF stable (v4.4)
ESP_IDF_VERSION = { value = "release/v4.4" }
# Enables the esp-idf-sys "native" build feature (`cargo build --features native`) to build against ESP-IDF master (mainline)
#ESP_IDF_VERSION = { value = "master" }

26
factory/Cargo.toml Normal file
View File

@@ -0,0 +1,26 @@
[package]
name = "sphinx-key-factory"
version = "0.1.0"
authors = ["decentclock <decentclock.5uh2k@slmail.me>"]
edition = "2021"
[features]
pio = ["esp-idf-sys/pio"]
[dependencies]
esp-idf-sys = { version = "0.31.8", features = ["binstart"] }
esp-idf-svc = { version = "0.42.3", features = ["experimental", "alloc"] }
esp-idf-hal = "0.38.1"
embedded-hal = "0.2.7"
embedded-svc = "0.22.1"
anyhow = { version = "1.0.65", features = ["backtrace"] }
rand = "0.8.5"
log = "0.4.17"
bitflags = "1.3.2"
[build-dependencies]
embuild = "0.29"
anyhow = "1"
[package.metadata.espflash]
partition_table = "table.csv"

14
factory/README.md Normal file
View File

@@ -0,0 +1,14 @@
# Sphinx Key Factory App
The main function of this app is to write any `update.bin` files from the sd card to the flash of the ESP, and configure the ESP so that on the next boot, it boots the freshly written app.
## Background Reading
- Partition Tables: https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-guides/partition-tables.html
- Over-the-Air Updates: https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/system/ota.html
## Flashing factory and sphinx-key
- First flash the factory app here using the usual `espflash` command, but add the `--partition-table` flag and point it to `table.csv` here. See `espflash -h` for more info.
- Then use `esptool.py` to flash the sphinx-key binary at offset `0xc0000`.
- Finally use this command to tell the ESP to boot the sphinx-key binary first ( there is no update to write to the ESP yet, so we don't boot the factory app ): `otatool.py switch_ota_partition --slot 0`

5
factory/build.rs Normal file
View File

@@ -0,0 +1,5 @@
// Necessary because of this issue: https://github.com/rust-lang/cargo/issues/9641
fn main() -> anyhow::Result<()> {
embuild::build::CfgArgs::output_propagated("ESP_IDF")?;
embuild::build::LinkArgs::output_propagated("ESP_IDF")
}

View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

View File

@@ -0,0 +1,11 @@
# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K)
CONFIG_ESP_MAIN_TASK_STACK_SIZE=64000
# CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
# Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default).
# This allows to use 1 ms granuality for thread sleeps (10 ms by default).
#CONFIG_FREERTOS_HZ=1000
# Workaround for https://github.com/espressif/esp-idf/issues/7631
#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n
#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n

34
factory/src/main.rs Normal file
View File

@@ -0,0 +1,34 @@
mod ota;
mod sdcard;
use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported
use log::{error, info, warn};
use ota::{run_sdcard_ota_update, set_boot_main_app, UPDATE_BIN_PATH};
use std::path::Path;
use std::thread;
use std::time::Duration;
fn main() {
// Temporary. Will disappear once ESP-IDF 4.4 is released, but for now it is necessary to call this function once,
// or else some patches to the runtime implemented by esp-idf-sys might not link properly.
esp_idf_svc::log::EspLogger::initialize_default();
esp_idf_sys::link_patches();
thread::sleep(Duration::from_secs(10));
info!("Hello, world! Mounting sd card...");
sdcard::mount_sd_card();
info!("SD card mounted! Checking for update...");
if let Ok(true) = Path::new(UPDATE_BIN_PATH).try_exists() {
info!("Found update.bin file! Launching the update process...");
while let Err(e) = run_sdcard_ota_update() {
error!("OTA update failed: {}", e.to_string());
error!("Trying again...");
thread::sleep(Duration::from_secs(5));
}
info!("OTA update complete!");
} else {
warn!("Update file not found! Setting up main app boot...");
set_boot_main_app();
}
info!("Restarting ESP, booting the main app...");
unsafe { esp_idf_sys::esp_restart() };
}

51
factory/src/ota.rs Normal file
View File

@@ -0,0 +1,51 @@
use anyhow::Result;
use embedded_svc::io::Write;
use embedded_svc::ota::Ota;
use embedded_svc::ota::OtaUpdate;
use esp_idf_svc::ota::EspOta;
use esp_idf_sys::{esp, esp_ota_get_next_update_partition, esp_ota_set_boot_partition};
use log::info;
use std::fs::File;
use std::io::BufReader;
use std::io::Read;
use std::ptr;
pub const UPDATE_BIN_PATH: &str = "/sdcard/update.bin";
const BUFFER_LEN: usize = 1024;
pub fn run_sdcard_ota_update() -> Result<()> {
let f = File::open(UPDATE_BIN_PATH)?;
let mut reader = BufReader::with_capacity(BUFFER_LEN, f);
let mut ota = EspOta::new()?;
let mut ota = ota.initiate_update()?;
let mut buf = [0_u8; BUFFER_LEN];
let mut read_tot: usize = 0;
let mut write_tot: usize = 0;
let mut i = 0;
loop {
let r = reader.read(&mut buf)?;
if r == 0 {
break;
}
let w = ota.write(&buf[..r])?;
read_tot += r;
write_tot += w;
i += 1;
if i % 20 == 0 {
info!("Cumulative bytes read: {}", read_tot);
info!("Cumulative bytes written: {}", write_tot);
}
}
info!("TOTAL read: {}", read_tot);
info!("TOTAL write: {}", write_tot);
ota.complete()?;
Ok(())
}
pub fn set_boot_main_app() {
let partition = unsafe { esp_ota_get_next_update_partition(ptr::null()) };
esp!(unsafe { esp_ota_set_boot_partition(partition) })
.expect("Couldn't set next boot partition...");
}

138
factory/src/sdcard.rs Normal file
View File

@@ -0,0 +1,138 @@
use bitflags::bitflags;
use esp_idf_sys::c_types::c_char;
use esp_idf_sys::{
esp, esp_vfs_fat_sdmmc_mount_config_t, esp_vfs_fat_sdspi_mount, gpio_num_t, sdmmc_card_t,
sdmmc_host_t, sdspi_device_config_t, spi_bus_config_t, spi_bus_initialize, spi_host_device_t,
spi_host_device_t_SPI2_HOST,
};
use std::ptr;
use std::thread;
use std::time::Duration;
const C_MOUNT_POINT: &'static [u8] = b"/sdcard\0";
const SPI_HOST_SLOT: spi_host_device_t = spi_host_device_t_SPI2_HOST;
const SPI_GPIO_MOSI: gpio_num_t = 7;
const SPI_GPIO_CLK: gpio_num_t = 6;
const SPI_GPIO_MISO: gpio_num_t = 2;
const SPI_GPIO_CS: gpio_num_t = 10;
bitflags! {
struct SDMMCHostFlag: u32 {
/// host supports 1-line SD and MMC protocol
const BIT1 = 1 << 0;
/// host supports 4-line SD and MMC protocol
const BIT4 = 1 << 1;
/// host supports 8-line MMC protocol
const BIT8 = 1 << 2;
/// host supports SPI protocol
const SPI = 1 << 3;
/// host supports DDR mode for SD/MMC
const DDR = 1 << 4;
/// host `deinit` function called with the slot argument
const DEINIT_ARG = 1 << 5;
}
}
#[allow(dead_code)]
enum SDMMCFreq {
/// SD/MMC Default speed (limited by clock divider)
Default = 20000,
/// SD High speed (limited by clock divider)
HighSPeed = 40000,
/// SD/MMC probing speed
Probing = 400,
/// MMC 52MHz speed
_52M = 52000,
/// MMC 26MHz speed
_26M = 26000,
}
#[allow(unused)]
pub fn mount_sd_card() {
while let Err(e) = setup() {
println!("Failed to mount sd card. Make sure it is connected, trying again...");
thread::sleep(Duration::from_secs(5));
}
}
fn setup() -> anyhow::Result<()> {
let mount_config = esp_vfs_fat_sdmmc_mount_config_t {
format_if_mount_failed: false,
max_files: 5,
allocation_unit_size: 16 * 1024,
};
let mut card: *mut sdmmc_card_t = ptr::null_mut();
let bus_cfg = spi_bus_config_t {
__bindgen_anon_1: esp_idf_sys::spi_bus_config_t__bindgen_ty_1 {
mosi_io_num: SPI_GPIO_MOSI,
},
__bindgen_anon_2: esp_idf_sys::spi_bus_config_t__bindgen_ty_2 {
miso_io_num: SPI_GPIO_MISO,
},
sclk_io_num: SPI_GPIO_CLK,
__bindgen_anon_3: esp_idf_sys::spi_bus_config_t__bindgen_ty_3 { quadwp_io_num: -1 },
__bindgen_anon_4: esp_idf_sys::spi_bus_config_t__bindgen_ty_4 { quadhd_io_num: -1 },
data4_io_num: -1,
data5_io_num: -1,
data6_io_num: -1,
data7_io_num: -1,
max_transfer_sz: 4000,
flags: 0,
intr_flags: 0,
};
if let Err(error) = esp!(unsafe {
spi_bus_initialize(
SPI_HOST_SLOT as u32,
&bus_cfg,
esp_idf_sys::spi_common_dma_t_SPI_DMA_CH_AUTO,
)
}) {
if error.code() != 259 {
return Err(anyhow::Error::new(error));
}
}
println!("Initialized SPI BUS!");
let slot_config = sdspi_device_config_t {
host_id: SPI_HOST_SLOT,
gpio_cs: SPI_GPIO_CS,
gpio_cd: -1,
gpio_wp: -1,
gpio_int: -1,
};
let host = sdmmc_host_t {
flags: (SDMMCHostFlag::SPI | SDMMCHostFlag::DEINIT_ARG).bits, //SDMMC_HOST_FLAG_SPI | SDMMC_HOST_FLAG_DEINIT_ARG,
slot: SPI_HOST_SLOT as i32,
max_freq_khz: SDMMCFreq::Default as i32, //SDMMC_FREQ_DEFAULT,
io_voltage: 3.3f32,
init: Some(esp_idf_sys::sdspi_host_init),
set_bus_width: None,
get_bus_width: None,
set_bus_ddr_mode: None,
set_card_clk: Some(esp_idf_sys::sdspi_host_set_card_clk),
do_transaction: Some(esp_idf_sys::sdspi_host_do_transaction),
__bindgen_anon_1: esp_idf_sys::sdmmc_host_t__bindgen_ty_1 {
deinit_p: Some(esp_idf_sys::sdspi_host_remove_device),
},
io_int_enable: Some(esp_idf_sys::sdspi_host_io_int_enable),
io_int_wait: Some(esp_idf_sys::sdspi_host_io_int_wait),
command_timeout_ms: 0,
};
esp!(unsafe {
esp_vfs_fat_sdspi_mount(
C_MOUNT_POINT.as_ptr() as *const c_char,
&host,
&slot_config,
&mount_config,
&mut card as *mut *mut sdmmc_card_t,
)
})?;
Ok(())
}

7
factory/table.csv Normal file
View File

@@ -0,0 +1,7 @@
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x4000,
otadata, data, ota, 0xd000, 0x2000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1200K,
ota_0, app, ota_0, 0x140000, 2800K,
1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 nvs, data, nvs, 0x9000, 0x4000,
4 otadata, data, ota, 0xd000, 0x2000,
5 phy_init, data, phy, 0xf000, 0x1000,
6 factory, app, factory, 0x10000, 1200K,
7 ota_0, app, ota_0, 0x140000, 2800K,