-
Notifications
You must be signed in to change notification settings - Fork 40
feat: ariel-os example initial #97
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
base: main
Are you sure you want to change the base?
Changes from all commits
c04582a
0540bba
5dd2bed
8570a8f
6b84ebc
dc2e0f7
75645d7
e843365
d074cf1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<AppMutex, AppDriver>; | ||
type BufStorage = PacketBuffers<1024, 1024>; | ||
type AppTx = WireTxImpl<AppMutex, AppDriver>; | ||
type AppRx = WireRxImpl<AppDriver>; | ||
type AppServer = Server<AppTx, AppRx, WireRxBuf, MyApp>; | ||
|
||
static PBUFS: ConstStaticCell<BufStorage> = ConstStaticCell::new(BufStorage::new()); | ||
static STORAGE: AppStorage = AppStorage::new(); | ||
|
||
/// Helper to get unique ID from flash | ||
pub fn get_unique_id() -> Option<u64> { | ||
// TODO | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ariel has a device id in bytes form (I think platform specific length), this needs wiring up and convert to u64. |
||
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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should probably be dynamic and not const - it should use the serial number of the device, e.g. from flash memory or whatever |
||
|
||
// 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 | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a note that we should probably pick a different handler for the docs, I need to remove this from my examples too. As of today, |
||
}; | ||
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. | ||
kaspar030 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This stumped me. Somehow the other task (on the same executor) is starved without this delay here, and USB doesn't enumerate anymore. |
||
|
||
// 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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -81,6 +81,12 @@ pub mod dispatch_impl { | |
pub cell: StaticCell<Mutex<M, EUsbWireTxInner<D>>>, | ||
} | ||
|
||
/// A helper type for `static` storage of buffers and driver components | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this is A lot can be deduplicated here, also in the original implementation. Ideally they'd all share snippets. It took me some time to get an idea of what's postcard specific and what not. Also, the poststation variant has only some small differences, which are IMO hard to spot. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Or maybe the no-usb-state version could be a field of the original one. |
||
pub struct WireStorageNoUsb<M: RawMutex + 'static, D: Driver<'static> + 'static> { | ||
/// WireTx/Sender static storage | ||
pub cell: StaticCell<Mutex<M, EUsbWireTxInner<D>>>, | ||
} | ||
|
||
impl< | ||
M: RawMutex + 'static, | ||
D: Driver<'static> + 'static, | ||
|
@@ -224,6 +230,103 @@ pub mod dispatch_impl { | |
(builder, EUsbWireTx { inner: wtx }, EUsbWireRx { ep_out }) | ||
} | ||
} | ||
|
||
impl<M: RawMutex + 'static, D: Driver<'static> + 'static> WireStorageNoUsb<M, D> { | ||
/// 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<M, D>, WireRxImpl<D>) { | ||
// 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<M, D>, WireRxImpl<D>) { | ||
// 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO (Ariel defaults for MSOS are too small. Works without on Linux.) |
||
// 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 }) | ||
} | ||
} | ||
} | ||
|
||
////////////////////////////////////////////////////////////////////////////// | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On ariel, any application might run in isr, one-thread or multi-threaded mode, so I tend to default to the criticalsection mutex.