diff --git a/example/firmware-ariel-os/Cargo.toml b/example/firmware-ariel-os/Cargo.toml new file mode 100644 index 0000000..5dc001e --- /dev/null +++ b/example/firmware-ariel-os/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "workbook-fw-ariel-os" +version = "0.1.0" +edition = "2021" + +[dependencies] +ariel-os = { path = "build/imports/ariel-os/src/ariel-os", features = [ + "override-usb-config", + "time", +] } +ariel-os-boards = { path = "build/imports/ariel-os/src/ariel-os-boards" } +embassy-sync = { version = "0.6.1", default-features = false } + +postcard-rpc = { version = "0.11", features = [ + "embassy-usb-0_4-server", + "defmt", +] } +postcard = { version = "1.0.10" } +postcard-schema = { version = "0.2.1", features = ["derive"] } + +workbook-icd = { path = "../workbook-icd" } +static_cell = "2.1.0" + +[patch.crates-io] +postcard-rpc = { path = "../../source/postcard-rpc" } diff --git a/example/firmware-ariel-os/laze-project.yml b/example/firmware-ariel-os/laze-project.yml new file mode 100644 index 0000000..aff15e9 --- /dev/null +++ b/example/firmware-ariel-os/laze-project.yml @@ -0,0 +1,10 @@ +imports: + - git: + url: https://github.com/ariel-os/ariel-os + commit: 4a1cf6786da07cc63154c5ddfd26b1487b285f7b + dldir: ariel-os + +apps: + - name: workbook-fw-ariel-os + selects: + - usb diff --git a/example/firmware-ariel-os/rust-toolchain.toml b/example/firmware-ariel-os/rust-toolchain.toml new file mode 100644 index 0000000..429e6ee --- /dev/null +++ b/example/firmware-ariel-os/rust-toolchain.toml @@ -0,0 +1,14 @@ +# this file is parsed with grep & sed for parsing out the actual toolchain +# from the "channel" line. Please keep that in mind when modifying. +[toolchain] +channel = "nightly-2025-02-25" +targets = [ + "thumbv6m-none-eabi", + "thumbv7m-none-eabi", + "thumbv7em-none-eabi", + "thumbv7em-none-eabihf", + "thumbv8m.main-none-eabi", + "riscv32imc-unknown-none-elf", + "riscv32imac-unknown-none-elf", +] +components = ["rust-src"] diff --git a/example/firmware-ariel-os/src/main.rs b/example/firmware-ariel-os/src/main.rs new file mode 100644 index 0000000..c49706b --- /dev/null +++ b/example/firmware-ariel-os/src/main.rs @@ -0,0 +1,132 @@ +#![no_std] +#![no_main] +#![feature(impl_trait_in_assoc_type)] +#![feature(used_with_arg)] + +use ariel_os::{ + asynch::Spawner, + debug::log::info, + reexports::embassy_usb, + time::{Duration, Timer}, + usb, +}; + +use postcard_rpc::{ + define_dispatch, + header::VarHeader, + server::{ + impls::embassy_usb_v0_4::{ + dispatch_impl::{WireRxBuf, WireRxImpl, WireSpawnImpl, WireStorageNoUsb, WireTxImpl}, + PacketBuffers, + }, + Dispatch, Server, + }, +}; +use static_cell::ConstStaticCell; +use workbook_icd::{PingEndpoint, ENDPOINT_LIST, TOPICS_IN_LIST, TOPICS_OUT_LIST}; + +pub struct Context; + +type AppMutex = embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +type AppDriver = usb::UsbDriver; +type AppStorage = WireStorageNoUsb; +type BufStorage = PacketBuffers<1024, 1024>; +type AppTx = WireTxImpl; +type AppRx = WireRxImpl; +type AppServer = Server; + +static PBUFS: ConstStaticCell = ConstStaticCell::new(BufStorage::new()); +static STORAGE: AppStorage = AppStorage::new(); + +/// Helper to get unique ID from flash +pub fn get_unique_id() -> Option { + // TODO + Some(12345678) +} + +#[ariel_os::config(usb)] +const USB_CONFIG: embassy_usb::Config = { + let mut config = embassy_usb::Config::new(0x16c0, 0x27DD); + config.manufacturer = Some("OneVariable"); + config.product = Some("ov-twin"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + config +}; + +define_dispatch! { + app: MyApp; + spawn_fn: spawn_fn; + tx_impl: AppTx; + spawn_impl: WireSpawnImpl; + context: Context; + + endpoints: { + list: ENDPOINT_LIST; + + | EndpointTy | kind | handler | + | ---------- | ---- | ------- | + | PingEndpoint | blocking | ping_handler | + }; + topics_in: { + list: TOPICS_IN_LIST; + + | TopicTy | kind | handler | + | ---------- | ---- | ------- | + }; + topics_out: { + list: TOPICS_OUT_LIST; + }; +} + +#[ariel_os::task(autostart, usb_builder_hook)] +async fn main() { + info!("Start"); + + let unique_id = get_unique_id().unwrap(); + info!("id: {=u64:016X}", unique_id); + + // USB/RPC INIT + let pbufs = PBUFS.take(); + let context = Context; + + // Create and inject the Postcard usb endpoint on the system USB builder. + let (tx_impl, rx_impl) = USB_BUILDER_HOOK + .with(|builder| STORAGE.init(builder, pbufs.tx_buf.as_mut_slice())) + .await; + + let spawner = Spawner::for_current_executor().await; + let dispatcher = MyApp::new(context, spawner.into()); + let vkk = dispatcher.min_key_len(); + let mut server: AppServer = Server::new( + tx_impl, + rx_impl, + pbufs.rx_buf.as_mut_slice(), + dispatcher, + vkk, + ); + + loop { + // Somehow at least on nrf52840dk, this is needed, otherwise the + // `ariel_os_embassy::init_task()` task starves. + Timer::after(Duration::from_millis(100)).await; + + // If the host disconnects, we'll return an error here. + // If this happens, just wait until the host reconnects + let _ = server.run().await; + } +} + +// --- + +fn ping_handler(_context: &mut Context, _header: VarHeader, rqst: u32) -> u32 { + info!("ping"); + rqst +} diff --git a/source/postcard-rpc/src/server/impls/embassy_usb_v0_4.rs b/source/postcard-rpc/src/server/impls/embassy_usb_v0_4.rs index a278692..b80b0f7 100644 --- a/source/postcard-rpc/src/server/impls/embassy_usb_v0_4.rs +++ b/source/postcard-rpc/src/server/impls/embassy_usb_v0_4.rs @@ -81,6 +81,12 @@ pub mod dispatch_impl { pub cell: StaticCell>>, } + /// A helper type for `static` storage of buffers and driver components + pub struct WireStorageNoUsb + 'static> { + /// WireTx/Sender static storage + pub cell: StaticCell>>, + } + impl< M: RawMutex + 'static, D: Driver<'static> + 'static, @@ -224,6 +230,103 @@ pub mod dispatch_impl { (builder, EUsbWireTx { inner: wtx }, EUsbWireRx { ep_out }) } } + + impl + 'static> WireStorageNoUsb { + /// Create a new, uninitialized static set of buffers + pub const fn new() -> Self { + Self { + cell: StaticCell::new(), + } + } + + /// Initialize the static storage, reporting as poststation compatible + /// + /// This must only be called once. + pub fn init_poststation( + &'static self, + builder: &mut Builder<'static, D>, + tx_buf: &'static mut [u8], + ) -> (WireTxImpl, WireRxImpl) { + // Register a poststation-compatible string handler + let hdlr = super::HDLR.take(); + builder.handler(hdlr); + + // Add the Microsoft OS Descriptor (MSOS/MOD) descriptor. + // We tell Windows that this entire device is compatible with the "WINUSB" feature, + // which causes it to use the built-in WinUSB driver automatically, which in turn + // can be used by libusb/rusb software without needing a custom driver or INF file. + // In principle you might want to call msos_feature() just on a specific function, + // if your device also has other functions that still use standard class drivers. + builder.msos_descriptor(windows_version::WIN8_1, 0); + builder.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + builder.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), + )); + + // Add a vendor-specific function (class 0xFF), and corresponding interface, + // that uses our custom handler. + let mut function = builder.function(0xFF, 0, 0); + let mut interface = function.interface(); + let stindx = interface.string(); + super::STINDX.store(stindx.0, core::sync::atomic::Ordering::Relaxed); + let mut alt = interface.alt_setting(0xFF, 0xCA, 0x7D, Some(stindx)); + let ep_out = alt.endpoint_bulk_out(64); + let ep_in = alt.endpoint_bulk_in(64); + drop(function); + + let wtx = self.cell.init(Mutex::new(EUsbWireTxInner { + ep_in, + log_seq: 0, + tx_buf, + pending_frame: false, + })); + + (EUsbWireTx { inner: wtx }, EUsbWireRx { ep_out }) + } + + /// Initialize the static storage. + /// + /// This must only be called once. + pub fn init( + &'static self, + builder: &mut Builder<'static, D>, + tx_buf: &'static mut [u8], + ) -> (WireTxImpl, WireRxImpl) { + // Add the Microsoft OS Descriptor (MSOS/MOD) descriptor. + // We tell Windows that this entire device is compatible with the "WINUSB" feature, + // which causes it to use the built-in WinUSB driver automatically, which in turn + // can be used by libusb/rusb software without needing a custom driver or INF file. + // In principle you might want to call msos_feature() just on a specific function, + // if your device also has other functions that still use standard class drivers. + + // TODO: increase ariel MSOS descriptors + // builder.msos_descriptor(windows_version::WIN8_1, 0); + // builder.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + // builder.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( + // "DeviceInterfaceGUIDs", + // msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), + // )); + + // Add a vendor-specific function (class 0xFF), and corresponding interface, + // that uses our custom handler. + let mut function = builder.function(0xFF, 0, 0); + let mut interface = function.interface(); + let mut alt = interface.alt_setting(0xFF, 0, 0, None); + let ep_out = alt.endpoint_bulk_out(64); + let ep_in = alt.endpoint_bulk_in(64); + drop(function); + + let wtx = self.cell.init(Mutex::new(EUsbWireTxInner { + ep_in, + log_seq: 0, + tx_buf, + pending_frame: false, + })); + + (EUsbWireTx { inner: wtx }, EUsbWireRx { ep_out }) + } + } } ////////////////////////////////////////////////////////////////////////////// diff --git a/source/postcard-rpc/src/server/mod.rs b/source/postcard-rpc/src/server/mod.rs index 5294538..9cf1bfe 100644 --- a/source/postcard-rpc/src/server/mod.rs +++ b/source/postcard-rpc/src/server/mod.rs @@ -73,6 +73,7 @@ pub trait WireTx { /// The base [`WireTx`] Error Kind #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum WireTxErrorKind { /// The connection has been closed, and is unlikely to succeed until @@ -118,6 +119,7 @@ pub trait WireRx { /// The base [`WireRx`] Error Kind #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum WireRxErrorKind { /// The connection has been closed, and is unlikely to succeed until @@ -374,6 +376,7 @@ where } /// A type representing the different errors [`Server::run()`] may return +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ServerError where Tx: WireTx,