diff --git a/protocol/usb/dfu/lib.rs b/protocol/usb/dfu/lib.rs index 3958d0a6..ba6ed914 100644 --- a/protocol/usb/dfu/lib.rs +++ b/protocol/usb/dfu/lib.rs @@ -212,9 +212,9 @@ where return; } if let Some(data) = self.buffer.get(self.transfer_offset..self.transfer_total) { - let n = driver.transfer_in_unaligned(0, data, true); + let zlp = self.transfer_total < self.config.transfer_size as usize; + let n = driver.transfer_in_unaligned(0, data, zlp); self.transfer_offset += n; - if self.transfer_offset == self.transfer_total { if self.transfer_total < self.config.transfer_size as usize { self.state = DfuState::DfuIdle; diff --git a/target/earlgrey/firmware/transport/BUILD.bazel b/target/earlgrey/firmware/transport/BUILD.bazel index c74631c7..26ed19c0 100644 --- a/target/earlgrey/firmware/transport/BUILD.bazel +++ b/target/earlgrey/firmware/transport/BUILD.bazel @@ -65,6 +65,7 @@ rust_process( "//target/earlgrey/util", "//util/error", "//util/ipc", + "//util/types", "//util/zfmt", "@pigweed//pw_kernel/userspace", "@pigweed//pw_status/rust:pw_status", @@ -85,10 +86,14 @@ rust_process( tags = ["kernel"], visibility = ["//visibility:public"], deps = [ + "//drivers/flash:spi_flash", "//hal/blocking/flash", + "//hal/blocking/flash:driver", "//services/flash:server", "//target/earlgrey/drivers:eflash_driver", + "//target/earlgrey/drivers:spi_host", "//target/earlgrey/registers:flash_ctrl_core", + "//target/earlgrey/registers:spi_host", "//target/earlgrey/util", "//util/error", "//util/ipc", @@ -96,6 +101,7 @@ rust_process( "//util/zfmt", "@pigweed//pw_kernel/userspace", "@pigweed//pw_status/rust:pw_status", + "@rust_crates//:embedded-hal", "@zfmt//zfmt", ], ) diff --git a/target/earlgrey/firmware/transport/dfu.rs b/target/earlgrey/firmware/transport/dfu.rs index 5fcee7fe..348e8814 100644 --- a/target/earlgrey/firmware/transport/dfu.rs +++ b/target/earlgrey/firmware/transport/dfu.rs @@ -18,6 +18,7 @@ use hal_flash::{Flash, FlashAddress}; use services_flash_client::FlashIpcClient; use util_error::ErrorCode; use util_ipc::IpcChannel; +use util_types::PowerOf2Usize; use zerocopy::FromBytes; use protocol_usb_dfu::{DfuHandler, DfuStatus}; @@ -111,6 +112,12 @@ pub const DFU_CDI0_CERT: hal_usb::StringDescriptorRef = pub const DFU_CDI1_CERT: hal_usb::StringDescriptorRef = hal_usb::string_descriptor!("CDI1 Certificate").as_ref(); +pub const DFU_ALT_FIRMWARE: u8 = 0; +pub const DFU_ALT_UDS_CERT: u8 = 1; +pub const DFU_ALT_CDI0_CERT: u8 = 2; +pub const DFU_ALT_CDI1_CERT: u8 = 3; +pub const DFU_ALT_SPI_EEPROM0: u8 = 4; + /// Retrieves a certificate from the info partition in flash. /// /// # Arguments @@ -247,21 +254,31 @@ impl FwUpdate { /// DFU handler for Earlgrey, managing firmware updates and certificate uploads. pub struct EarlgreyDfuHandler { flash: FlashIpcClient, + spi_flash: FlashIpcClient, + spi_flash_total_size: u32, + spi_flash_page_size: PowerOf2Usize, sysmgr: SysmgrClient, update: FwUpdate, + alt_setting: Option, } impl EarlgreyDfuHandler { /// Creates a new DFU handler. pub fn new( flash: FlashIpcClient, + mut spi_flash: FlashIpcClient, sysmgr: SysmgrClient, info: &BootInfo, ) -> Result { + let (spi_flash_total_size, spi_flash_page_size, _) = spi_flash.geometry()?; Ok(EarlgreyDfuHandler { flash, + spi_flash, + spi_flash_total_size: spi_flash_total_size.get() as u32, + spi_flash_page_size, sysmgr, update: FwUpdate::new(info)?, + alt_setting: None, }) } @@ -394,8 +411,40 @@ impl EarlgreyDfuHandler { } Ok(()) } -} + fn flash_spi_eeprom0_block(&mut self, block_num: u32, data: &[u8]) -> Result<(), DfuStatus> { + let address = block_num * FLASH_BLOCK_SIZE as u32; + if address >= self.spi_flash_total_size { + return Err(DfuStatus::ErrAddress); + } + if (address as usize) % self.spi_flash_page_size.get() == 0 { + self.spi_flash + .erase(FlashAddress::new(address), self.spi_flash_page_size) + .map_err(|_| DfuStatus::ErrErase)?; + } + self.spi_flash + .program(FlashAddress::new(address), data) + .map_err(|_| DfuStatus::ErrProg)?; + Ok(()) + } + + fn read_spi_eeprom0_block( + &mut self, + block_num: u32, + data: &mut [u8], + ) -> Result { + let address = block_num * FLASH_BLOCK_SIZE as u32; + let total_bytes = self.spi_flash_total_size; + if address >= total_bytes { + return Ok(0); + } + let read_len = core::cmp::min(data.len(), (total_bytes - address) as usize); + self.spi_flash + .read(FlashAddress::new(address), &mut data[..read_len]) + .map_err(|_| DfuStatus::ErrUnknown)?; + Ok(read_len) + } +} impl DfuHandler for EarlgreyDfuHandler { /// Handles a DFU download (DNLOAD) request. /// @@ -407,8 +456,11 @@ impl DfuHandler for EarlgreyDfuHandler { block: block_num, len: data.len() as u32, }); - if alt == 0 { + self.alt_setting = Some(alt); + if alt == DFU_ALT_FIRMWARE { self.flash_fw_block(block_num as u32, data) + } else if alt == DFU_ALT_SPI_EEPROM0 { + self.flash_spi_eeprom0_block(block_num as u32, data) } else { Err(DfuStatus::ErrFile) } @@ -424,8 +476,12 @@ impl DfuHandler for EarlgreyDfuHandler { block: block_num, len: data.len() as u32, }); + self.alt_setting = Some(alt); match alt { - 1 | 2 | 3 => get_certificate(&mut self.flash, alt - 1, data), + DFU_ALT_UDS_CERT | DFU_ALT_CDI0_CERT | DFU_ALT_CDI1_CERT => { + get_certificate(&mut self.flash, alt - DFU_ALT_UDS_CERT, data) + } + DFU_ALT_SPI_EEPROM0 => self.read_spi_eeprom0_block(block_num as u32, data), _ => Err(DfuStatus::ErrFile), } } @@ -436,6 +492,9 @@ impl DfuHandler for EarlgreyDfuHandler { /// slot and requests a reboot. fn manifest(&mut self) -> Result<(), DfuStatus> { util_zfmt::info!(DfuManifest); + if self.alt_setting == Some(DFU_ALT_SPI_EEPROM0) { + return Ok(()); + } if self.update.state == FwUpdateState::Done || self.update.state == FwUpdateState::Application || self.update.state == FwUpdateState::RomExt diff --git a/target/earlgrey/firmware/transport/flash_server.rs b/target/earlgrey/firmware/transport/flash_server.rs index 0918274c..668b5cac 100644 --- a/target/earlgrey/firmware/transport/flash_server.rs +++ b/target/earlgrey/firmware/transport/flash_server.rs @@ -10,14 +10,29 @@ use userspace::time::Instant; use userspace::{process_entry, syscall}; use util_error::{AsStatus, ErrorCode}; use util_zfmt::messages::{ProcessExit, ProcessStart}; +use zfmt::Zfmt; use earlgrey_util::EarlgreyFlashAddress; use eflash_driver::{EmbeddedFlash, Permission}; use hal_flash::{BlockingFlash, FlashAddress}; use services_flash_server::FlashIpcServer; +use spi_flash::SpiFlash; +use spi_host::SpiHost0; use util_ipc::IpcHandle; use util_types::Blocking; +#[derive(Zfmt)] +#[zfmt(format = "SPI Host init failed: {code:08x}")] +struct SpiHostInitFailed { + code: u32, +} + +#[derive(Zfmt)] +#[zfmt(format = "SPI Flash init failed: {code:08x}")] +struct SpiFlashInitFailed { + code: u32, +} + struct FlashCtrlInterrupt; impl Blocking for FlashCtrlInterrupt { @@ -38,28 +53,70 @@ impl Blocking for FlashCtrlInterrupt { } fn flash_server() -> Result<(), ErrorCode> { - let mut driver = + let mut eflash_driver = EmbeddedFlash::new_with_interrupts(unsafe { flash_ctrl_core::FlashCtrl::new() }); - driver.set_default_permission(Permission::FULL_ACCESS); + eflash_driver.set_default_permission(Permission::FULL_ACCESS); for i in 5..9 { - driver.set_info_permission(FlashAddress::info(0, i, 0), Permission::FULL_ACCESS)?; - driver.set_info_permission(FlashAddress::info(1, i, 0), Permission::FULL_ACCESS)?; + eflash_driver.set_info_permission(FlashAddress::info(0, i, 0), Permission::FULL_ACCESS)?; + eflash_driver.set_info_permission(FlashAddress::info(1, i, 0), Permission::FULL_ACCESS)?; } - let flash = BlockingFlash { - driver, + let eflash = BlockingFlash { + driver: eflash_driver, blocking: FlashCtrlInterrupt, }; - let mut flash_server = FlashIpcServer::new(flash); + let mut eflash_server = FlashIpcServer::new(eflash); + + let mut spi_host = unsafe { + // SAFETY: we have exclusive access to the spi_host0 peripheral. + earlgrey_spi_host::SpiHost::new(spi_host::RegisterBlock::new(SpiHost0::PTR)) + }; + if let Err(e) = spi_host.init(&earlgrey_spi_host::SpiConfig::DEFAULT_SPI0) { + let code = u32::from(ErrorCode::from(e)); + util_zfmt::error!(SpiHostInitFailed { code }); + return Err(ErrorCode::from(e)); + } + + let mut spi_flash = SpiFlash::new(spi_host); + if let Err(e) = spi_flash.init() { + util_zfmt::error!(SpiFlashInitFailed { code: u32::from(e) }); + return Err(e); + } + let mut spi_flash_server = FlashIpcServer::new(spi_flash); + + syscall::wait_group_add( + handle::FLASH_WAIT_GROUP, + handle::EFLASH_SERVICE, + syscall::Signals::READABLE, + handle::EFLASH_SERVICE as usize, + ) + .map_err(ErrorCode::kernel_error)?; + + syscall::wait_group_add( + handle::FLASH_WAIT_GROUP, + handle::SPI_FLASH_SERVICE, + syscall::Signals::READABLE, + handle::SPI_FLASH_SERVICE as usize, + ) + .map_err(ErrorCode::kernel_error)?; + let mut buf = [0u8; 2064]; - let ipc = IpcHandle::new(handle::FLASH_SERVICE); + let eflash_ipc = IpcHandle::new(handle::EFLASH_SERVICE); + let spi_flash_ipc = IpcHandle::new(handle::SPI_FLASH_SERVICE); + loop { - syscall::object_wait( - handle::FLASH_SERVICE, + let wait_result = syscall::object_wait( + handle::FLASH_WAIT_GROUP, syscall::Signals::READABLE, Instant::MAX, ) .map_err(ErrorCode::kernel_error)?; - flash_server.handle_one(&ipc, &mut buf)?; + + let channel = wait_result.user_data as u32; + if channel == handle::EFLASH_SERVICE { + eflash_server.handle_one(&eflash_ipc, &mut buf)?; + } else if channel == handle::SPI_FLASH_SERVICE { + spi_flash_server.handle_one(&spi_flash_ipc, &mut buf)?; + } } } diff --git a/target/earlgrey/firmware/transport/system.json5 b/target/earlgrey/firmware/transport/system.json5 index 5373985c..a4459ed6 100644 --- a/target/earlgrey/firmware/transport/system.json5 +++ b/target/earlgrey/firmware/transport/system.json5 @@ -142,9 +142,17 @@ handler_object_name: "logger_flash" }, { - name: "flash_service", + name: "eflash_service", type: "channel_handler" }, + { + name: "spi_flash_service", + type: "channel_handler" + }, + { + name: "flash_wait_group", + type: "wait_group" + }, { name: "flash_interrupts", type: "interrupt", @@ -169,6 +177,12 @@ type: "device", start_address: 0x41000000, size_bytes: 0x200 + }, + { + name: "spi_host0", + type: "device", + start_address: 0x40300000, + size_bytes: 0x1000 } ] }, @@ -215,7 +229,13 @@ name: "flash_usb", type: "channel_initiator", handler_process: "flash_server", - handler_object_name: "flash_service" + handler_object_name: "eflash_service" + }, + { + name: "spi_flash_usb", + type: "channel_initiator", + handler_process: "flash_server", + handler_object_name: "spi_flash_service" }, { name: "sysmgr_usb", diff --git a/target/earlgrey/firmware/transport/tests/dfu/BUILD.bazel b/target/earlgrey/firmware/transport/tests/dfu/BUILD.bazel index 354b9cc6..93929236 100644 --- a/target/earlgrey/firmware/transport/tests/dfu/BUILD.bazel +++ b/target/earlgrey/firmware/transport/tests/dfu/BUILD.bazel @@ -101,3 +101,59 @@ opentitan_test( test_cmd = "--logging=info --expect-reboot --expect-app --firmware=target/earlgrey/firmware/transport/tests/dfu/bootinfo_simple.app_prod_0.signed.bin", test_harness = ":host_usb_dfu_owner_transfer", ) + +opentitan_rust_binary( + name = "host_usb_dfu_spi_flash", + srcs = ["host_usb_dfu_spi_flash.rs"], + edition = "2024", + rustc_flags = [ + "-C", + "link-arg=-Wl,--allow-shlib-undefined", + ], + deps = [ + "//target/earlgrey/testutil", + "//third_party/lowrisc_opentitan:opentitanlib", + "//third_party/lowrisc_opentitan:usb_test_helper", + "@ot_crate_index//:anyhow", + "@ot_crate_index//:clap", + "@ot_crate_index//:log", + ], +) + +opentitan_test( + name = "dfu_spi_flash_hyper310_test", + timeout = "eternal", + clear_bitstream = True, + ecdsa_key = FPGA_ECDSA_KEY, + environment = "//target/earlgrey/env:hyper310", + interface = "hyper310", + tags = [ + "hardware", + "hyper310", + ], + target = "//target/earlgrey/firmware/transport:transport_firmware", + target_data = [ + ":bootinfo_signed_simple", + ], + test_cmd = "--logging=info --firmware=target/earlgrey/firmware/transport/tests/dfu/bootinfo_simple.app_prod_0.signed.bin", + test_harness = ":host_usb_dfu_spi_flash", +) + +opentitan_test( + name = "dfu_spi_flash_hyper340_test", + timeout = "eternal", + clear_bitstream = True, + ecdsa_key = FPGA_ECDSA_KEY, + environment = "//target/earlgrey/env:hyper340", + interface = "hyper340", + tags = [ + "hardware", + "hyper340", + ], + target = "//target/earlgrey/firmware/transport:transport_firmware", + target_data = [ + ":bootinfo_signed_simple", + ], + test_cmd = "--logging=info --firmware=target/earlgrey/firmware/transport/tests/dfu/bootinfo_simple.app_prod_0.signed.bin", + test_harness = ":host_usb_dfu_spi_flash", +) diff --git a/target/earlgrey/firmware/transport/tests/dfu/host_usb_dfu_spi_flash.rs b/target/earlgrey/firmware/transport/tests/dfu/host_usb_dfu_spi_flash.rs new file mode 100644 index 00000000..117a43e4 --- /dev/null +++ b/target/earlgrey/firmware/transport/tests/dfu/host_usb_dfu_spi_flash.rs @@ -0,0 +1,151 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::{bail, Context, Result}; +use clap::Parser; +use std::time::Duration; + +use earlgrey_testutil::{ + get_dfu_transfer_size, print_uart, sequence_dfu_download, sequence_dfu_upload, DfuClient, +}; +use opentitanlib::app::TransportWrapper; +use opentitanlib::io::uart::Uart; +use opentitanlib::test_utils::init::InitializeTest; +use opentitanlib::uart::console::UartConsole; +use usb::UsbOpts; + +#[derive(Parser, Debug)] +struct CmdArgs { + #[command(flatten)] + init: InitializeTest, + + #[command(flatten)] + usb: UsbOpts, + + #[arg( + long, + default_value = "target/earlgrey/firmware/transport/tests/dfu/bootinfo_simple.app_prod_0.signed.bin" + )] + firmware: String, +} + +fn run_dfu_spi_flash_test_inner( + transport: &TransportWrapper, + usb: &UsbOpts, + firmware_path: &str, + uart: &dyn Uart, +) -> Result<()> { + log::info!("Resetting target..."); + transport.reset(opentitanlib::app::UartRx::Clear)?; + + log::info!("waiting for Maize Welcome on console..."); + let _ = UartConsole::wait_for( + uart, + r"Welcome to Maize on Earlgrey Transport Firmware!", + Duration::from_secs(10), + )?; + + usb.apply_strappings(transport, true)?; + if usb.vbus_control_available() { + usb.enable_vbus(transport, true)?; + } + if usb.vbus_sense_available() { + if !usb.vbus_present(transport)? { + bail!("OT USB does not appear to be connected to a host (VBUS not detected)"); + } + } + + let usb_vid = usb.vid; + let usb_pid = usb.pid; + + log::info!( + "waiting for DFU device (VID={:04x}, PID={:04x})...", + usb_vid, + usb_pid + ); + let device = transport + .usb()? + .device_by_id_with_timeout(usb_vid, usb_pid, None, Duration::from_secs(10)) + .context("DFU device not found")?; + + log::info!("Claiming DFU interface..."); + let interface_num = 2; + device.claim_interface(interface_num)?; + + let transfer_size = get_dfu_transfer_size(&*device, interface_num)?; + log::info!("DFU Transfer Size (Block Size): {} bytes", transfer_size); + + // Set Alt setting 4 (SPI EEPROM 0) + log::info!("Setting USB DFU Alt setting to 4 (SPI EEPROM 0)..."); + device.set_alternate_setting(interface_num, 4)?; + + let dfu = DfuClient::new(&*device, interface_num); + + log::info!("Reading payload from '{}'...", firmware_path); + let test_data = std::fs::read(firmware_path)?; + + log::info!("Sequencing DFU Download (expect_reboot = false)..."); + sequence_dfu_download(&dfu, uart, &test_data, transfer_size, false)?; + + log::info!("Sequencing DFU Upload to read back payload..."); + let uploaded_data = sequence_dfu_upload(&dfu, test_data.len(), transfer_size)?; + + log::info!("Verifying integrity of uploaded data..."); + if uploaded_data != test_data { + log::error!("Data mismatch!"); + log::error!( + "Original len: {}, Uploaded len: {}", + test_data.len(), + uploaded_data.len() + ); + log::error!( + "Original (first 16 bytes): {:02x?}", + &test_data[..std::cmp::min(16, test_data.len())] + ); + log::error!( + "Uploaded (first 16 bytes): {:02x?}", + &uploaded_data[..std::cmp::min(16, uploaded_data.len())] + ); + if let Some(mismatch_idx) = test_data + .iter() + .zip(uploaded_data.iter()) + .position(|(a, b)| a != b) + { + log::error!( + "First mismatch at index {}: expected {:02x}, got {:02x}", + mismatch_idx, + test_data[mismatch_idx], + uploaded_data[mismatch_idx] + ); + } else { + log::error!("No mismatch found within zipped range (vectors have different lengths)."); + } + let _ = device.release_interface(interface_num); + bail!("Data mismatch! Uploaded data does not match the downloaded payload."); + } + log::info!("✅ Integrity verification passed (hashes/bytes match)!"); + + let _ = device.release_interface(interface_num); + log::info!("Test Execution Finished Successfully!"); + Ok(()) +} + +fn run_dfu_spi_flash_test( + transport: &TransportWrapper, + usb: &UsbOpts, + firmware_path: &str, +) -> Result<()> { + let uart = transport.uart("console")?; + let res = run_dfu_spi_flash_test_inner(transport, usb, firmware_path, &*uart); + print_uart(&*uart); + res +} + +fn main() -> Result<()> { + let args = CmdArgs::parse(); + args.init.init_logging(); + + let transport = args.init.init_target()?; + run_dfu_spi_flash_test(&transport, &args.usb, &args.firmware)?; + Ok(()) +} diff --git a/target/earlgrey/firmware/transport/usbmgr.rs b/target/earlgrey/firmware/transport/usbmgr.rs index bc76db32..d08cd110 100644 --- a/target/earlgrey/firmware/transport/usbmgr.rs +++ b/target/earlgrey/firmware/transport/usbmgr.rs @@ -29,7 +29,11 @@ use usb_driver::UsbConfig; use usb_stack::{DescriptorSource, UsbAction, UsbClass}; mod dfu; -use dfu::{EarlgreyDfuHandler, DFU_CDI0_CERT, DFU_CDI1_CERT, DFU_FIRMWARE, DFU_UDS_CERT}; +use dfu::{ + EarlgreyDfuHandler, DFU_ALT_CDI0_CERT, DFU_ALT_CDI1_CERT, DFU_ALT_FIRMWARE, + DFU_ALT_SPI_EEPROM0, DFU_ALT_UDS_CERT, DFU_CDI0_CERT, DFU_CDI1_CERT, DFU_FIRMWARE, + DFU_UDS_CERT, +}; use earlgrey_sysmgr_client::SysmgrClient; use protocol_usb_cdc_acm::{CdcAcm, CdcAcmBuilder}; use protocol_usb_dfu::{DfuBuilder, DfuClass}; @@ -50,6 +54,7 @@ const DFU_FIRMWARE_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(6); const DFU_UDS_CERT_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(7); const DFU_CDI0_CERT_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(8); const DFU_CDI1_CERT_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(9); +const DFU_SPI_EEPROM_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(10); // The serial number size is 2 bytes (USB descriptor header) + 32 bytes of // serial number * (2 for hex encoding) * (2 bytes per UTF16 character). @@ -57,7 +62,7 @@ const USB_SERIAL_SIZE: usize = 2 + 32 * 2 * 2; const DFU_BUILDER: DfuBuilder = DfuBuilder::new( 2, // interface_num (2, after CDC-ACM's 0 and 1) - 4, // alt_settings + 5, // alt_settings 2048, // transfer_size ); @@ -94,12 +99,13 @@ const CONFIG_DESC: ConfigDescriptor = ConfigDescriptor { &CDC_BUILDER.comm_endpoints(), ), CDC_BUILDER.data_interface(USB_CDC_DATA_HANDLE, &CDC_BUILDER.data_endpoints()), - DFU_BUILDER.interface(0, DFU_FIRMWARE_HANDLE, &[]), - DFU_BUILDER.interface(1, DFU_UDS_CERT_HANDLE, &[]), - DFU_BUILDER.interface(2, DFU_CDI0_CERT_HANDLE, &[]), + DFU_BUILDER.interface(DFU_ALT_FIRMWARE, DFU_FIRMWARE_HANDLE, &[]), + DFU_BUILDER.interface(DFU_ALT_UDS_CERT, DFU_UDS_CERT_HANDLE, &[]), + DFU_BUILDER.interface(DFU_ALT_CDI0_CERT, DFU_CDI0_CERT_HANDLE, &[]), + DFU_BUILDER.interface(DFU_ALT_CDI1_CERT, DFU_CDI1_CERT_HANDLE, &[]), DFU_BUILDER.interface( - 3, - DFU_CDI1_CERT_HANDLE, + DFU_ALT_SPI_EEPROM0, + DFU_SPI_EEPROM_HANDLE, &[DFU_BUILDER.functional_descriptor()], ), ], @@ -119,6 +125,8 @@ const USB_COMM: hal_usb::StringDescriptorRef = hal_usb::string_descriptor!("CDC Comm Interface").as_ref(); const USB_DATA: hal_usb::StringDescriptorRef = hal_usb::string_descriptor!("CDC Data Interface").as_ref(); +const DFU_SPI_EEPROM: hal_usb::StringDescriptorRef = + hal_usb::string_descriptor!("SPI EEPROM 0").as_ref(); /// Implements `DescriptorSource` to provide USB descriptors. /// @@ -160,6 +168,8 @@ impl DescriptorSource for MyDescriptors<'_> { Some(DFU_CDI0_CERT) } else if h == DFU_CDI1_CERT_HANDLE.0 { Some(DFU_CDI1_CERT) + } else if h == DFU_SPI_EEPROM_HANDLE.0 { + Some(DFU_SPI_EEPROM) } else { None } @@ -229,7 +239,8 @@ fn handle_usb() -> Result<(), ErrorCode> { const USB_CONFIG: UsbConfig = UsbConfig::new(&CDC_BUILDER.eps().0, &CDC_BUILDER.eps().1); let flash = FlashIpcClient::new(IpcHandle::new(handle::FLASH_USB))?; - let dfu_handler = EarlgreyDfuHandler::new(flash, sysmgr, &boot_info)?; + let spi_flash = FlashIpcClient::new(IpcHandle::new(handle::SPI_FLASH_USB))?; + let dfu_handler = EarlgreyDfuHandler::new(flash, spi_flash, sysmgr, &boot_info)?; let mut dfu = DfuClass::<_, 2048>::new(DFU_BUILDER, dfu_handler); let mut usb = usb_driver::Usb::new(unsafe { usbdev::Usbdev::new() }, USB_CONFIG); diff --git a/target/earlgrey/testutil/lib.rs b/target/earlgrey/testutil/lib.rs index 95a0d155..26e82ba6 100644 --- a/target/earlgrey/testutil/lib.rs +++ b/target/earlgrey/testutil/lib.rs @@ -165,3 +165,43 @@ pub fn sequence_dfu_download( // Removed print_uart(uart) to preserve telemetry for the test harness. Ok(()) } + +pub fn sequence_dfu_upload( + dfu: &DfuClient, + expected_len: usize, + transfer_size: u16, +) -> Result> { + // Ensure we start from a clean state + let status = dfu.get_status()?; + if status.state() == DfuState::Error { + log::info!("Clearing DFU error status..."); + dfu.clear_status()?; + } + + let mut uploaded_data = Vec::new(); + let mut block_num = 0; + let mut buf = vec![0u8; transfer_size as usize]; + while uploaded_data.len() < expected_len { + let n = dfu.upload(block_num, &mut buf)?; + log::info!("Uploaded block {block_num}, size {n}..."); + if n == 0 { + log::warn!("Upload returned 0 bytes early at block {block_num}"); + break; + } + let chunk_len = std::cmp::min(n, expected_len - uploaded_data.len()); + uploaded_data.extend_from_slice(&buf[..chunk_len]); + block_num += 1; + } + + let status = dfu.get_status()?; + if status.state() == DfuState::UpLoadIdle { + dfu.abort()?; + } else if status.state() != DfuState::Idle { + bail!( + "DFU upload finished in unexpected state: {:?}", + status.state() + ); + } + + Ok(uploaded_data) +}