diff --git a/Cargo.toml b/Cargo.toml index 2e9e143..9652e99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ exclude = [ "broker", + "factory", "persister", "sphinx-key", ] diff --git a/factory/.cargo/config.toml b/factory/.cargo/config.toml new file mode 100644 index 0000000..33f5399 --- /dev/null +++ b/factory/.cargo/config.toml @@ -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" } diff --git a/factory/Cargo.toml b/factory/Cargo.toml new file mode 100644 index 0000000..1da361e --- /dev/null +++ b/factory/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "sphinx-key-factory" +version = "0.1.0" +authors = ["decentclock "] +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" diff --git a/factory/README.md b/factory/README.md new file mode 100644 index 0000000..3990a9e --- /dev/null +++ b/factory/README.md @@ -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` diff --git a/factory/build.rs b/factory/build.rs new file mode 100644 index 0000000..4dd5e1f --- /dev/null +++ b/factory/build.rs @@ -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") +} diff --git a/factory/rust-toolchain.toml b/factory/rust-toolchain.toml new file mode 100644 index 0000000..271800c --- /dev/null +++ b/factory/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file diff --git a/factory/sdkconfig.defaults b/factory/sdkconfig.defaults new file mode 100644 index 0000000..3a2aa72 --- /dev/null +++ b/factory/sdkconfig.defaults @@ -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 diff --git a/factory/src/main.rs b/factory/src/main.rs new file mode 100644 index 0000000..2a6d7d8 --- /dev/null +++ b/factory/src/main.rs @@ -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() }; +} diff --git a/factory/src/ota.rs b/factory/src/ota.rs new file mode 100644 index 0000000..db603fa --- /dev/null +++ b/factory/src/ota.rs @@ -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..."); +} diff --git a/factory/src/sdcard.rs b/factory/src/sdcard.rs new file mode 100644 index 0000000..f242438 --- /dev/null +++ b/factory/src/sdcard.rs @@ -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(()) +} diff --git a/factory/table.csv b/factory/table.csv new file mode 100644 index 0000000..8133218 --- /dev/null +++ b/factory/table.csv @@ -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,