diff --git a/crates/sbd-gen-schema/src/lib.rs b/crates/sbd-gen-schema/src/lib.rs index e4dbaa9..f972ff1 100644 --- a/crates/sbd-gen-schema/src/lib.rs +++ b/crates/sbd-gen-schema/src/lib.rs @@ -88,6 +88,27 @@ impl Target { false } } + + /// Returns true if there are any UARTs listed for this board. + #[must_use] + pub fn has_uarts(&self) -> bool { + if let Some(uarts) = &self.uarts { + !uarts.is_empty() + } else { + false + } + } + + /// Returns true if there are any UARTs listed for this board that have the + /// [`Uart::host_facing`] property. + #[must_use] + pub fn has_host_facing_uart(&self) -> bool { + if let Some(uarts) = &self.uarts { + uarts.iter().any(|u| u.host_facing) + } else { + false + } + } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -164,6 +185,49 @@ pub struct Uart { pub tx_pin: String, pub cts_pin: Option, pub rts_pin: Option, + /// Peripheral device names, any of which is fundamentally available to serve this connection + /// as the peripheral that takes control of the TX and RX pins. + /// + /// # Usage + /// + /// All items in the list are peripheral names of the MCU for which the UART interface is + /// implemented. For example, on EFM32, a pin combination might be configurable either using + /// `LEUART0` or `USART1`, in which case those are given as values. + /// + /// On some OSes and platforms (e. g., at the time of writing, in Ariel OS on nRF devices), + /// using that device name might entail using companion peripherals that are statically + /// selected (e. g. `UARTE0` being bundled with `TIMER4`, `PPI_CH14`, `PPI_CH15` and + /// `PPI_GROUP5`). This is an implementation detail of the OS; the name in this list is still + /// only the name of the one peripheral that performs the UART functionality. + /// + /// # Future development + /// + /// When future versions of `sbd` or the OSes consuming this file learn to process per-MCU + /// information, this field might go away. Instead, the possible peripherals might be deduced + /// purely from the MCU's peripheral mapping and the `*_pin` values. + /// + /// When multiple UARTs are in use in an application and their possible peripherals overlap, + /// deciding which of the choices to take is a [hard problem]. When none of the peripherals are + /// available, the OS's mechanism of choosing a peripheral may need enhancing: For example, + /// Ariel OS (at the time of writing) only selects the first peripheral. Future versions might + /// pick the first one that has not previously been taken, and ideally, a static choice would + /// be made at build time solving the satisfiability problem. + /// + /// When no peripheral is given, or all are used for other purposes, the OS may fall back to + /// bit-banging operation; currently, they do not. + /// + /// [hard problem]: https://en.wikipedia.org/wiki/Boolean_satisfiability_problem + #[serde(default)] + pub possible_peripherals: Vec, + + /// Set if the board supports using it with a host system (e.g. the build host), and this UART + /// would typically face that system. + /// + /// For example, this is set on boards with built-in programmers on UARTs that are exposed by + /// the programmer as USB serial devices. Typical applications querying this are tools that + /// report debug or measurement data. + #[serde(default)] + pub host_facing: bool, } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] diff --git a/crates/sbd-gen/src/ariel.rs b/crates/sbd-gen/src/ariel.rs index abb8701..01bc332 100644 --- a/crates/sbd-gen/src/ariel.rs +++ b/crates/sbd-gen/src/ariel.rs @@ -17,7 +17,7 @@ use crate::{ }; use sbd_gen_schema::{ - Button, Led, PinLevel, Quirk, SbdFile, SetPinOp, Target, common::StringOrVecString, + Button, Led, PinLevel, Quirk, SbdFile, SetPinOp, Target, Uart, common::StringOrVecString, }; #[derive(argh::FromArgs, Debug)] @@ -167,6 +167,9 @@ pub fn render_ariel_board_crate(sbd: &SbdFile) -> FileMap { if target.has_buttons() { target_builder.provides.insert("has_buttons".into()); } + if target.has_host_facing_uart() { + target_builder.provides.insert("has_host_facing_uart".into()); + } if let Some(swi) = target.ariel.swi { target_builder.provides.insert("has_swi".into()); @@ -291,7 +294,7 @@ fn render_pins(target: &Target) -> String { pins.push_str("pub mod pins {\n"); - if target.has_leds() || target.has_buttons() { + if target.has_leds() || target.has_buttons() || target.has_uarts() { pins.push_str("use ariel_os_hal::hal::peripherals;\n\n"); if let Some(leds) = target.leds.as_ref() { pins.push_str(&render_led_pins(leds)); @@ -299,6 +302,9 @@ fn render_pins(target: &Target) -> String { if let Some(buttons) = target.buttons.as_ref() { pins.push_str(&render_button_pins(buttons)); } + if let Some(uarts) = target.uarts.as_ref() { + pins.push_str(&render_uarts(uarts)); + } } pins.push_str("}\n"); @@ -333,3 +339,74 @@ fn render_button_pins(buttons: &[Button]) -> String { buttons_rs } + +fn render_uarts(uarts: &[Uart]) -> String { + let mut code = String::new(); + + code.push_str("ariel_os_hal::define_uarts![\n"); + + for (uart_number, uart) in uarts.iter().enumerate() { + let name = uart.name.as_ref().map_or_else( + || format!("_unnamed_uart_{uart_number}").into(), + std::borrow::Cow::from, + ); + let Some(device) = uart.possible_peripherals.first() else { + eprintln!( + "warning: No peripheral defined for UART, making it unusable in Ariel output." + ); + eprintln!("Affected UART: {uart:?}"); + continue; + }; + if uart.possible_peripherals.len() > 1 + { + eprintln!( + "warning: Multiple hardware devices are available, but Ariel OS does not process any but the first." + ); + eprintln!("Affected UART: {uart:?}"); + } + // Deferring to a macro so that any actual logic in there is handled in the OS where it + // belongs; this merely processes the data into a format usable there. + writeln!( + code, + "{{ name: {}, device: {}, tx: {}, rx: {}, host_facing: {} }},", + name, device, uart.tx_pin, uart.rx_pin, uart.host_facing + ) + .unwrap(); + } + + code.push_str("];\n"); + + code +} + +#[test] +fn test_render_uarts() { + let rendered = render_uarts(&[ + Uart { + name: Some("CON0".to_string()), + rx_pin: "PA08".to_owned(), + tx_pin: "PC99".to_owned(), + cts_pin: None, + rts_pin: None, + possible_peripherals: vec!["UART2".to_owned(), "LEUART0".to_owned()], + host_facing: false, + }, + Uart { + name: Some("VCOM".to_string()), + rx_pin: "P0_04".to_owned(), + tx_pin: "P1_23".to_owned(), + cts_pin: Some("P7.89".to_owned()), + rts_pin: Some("D5".to_owned()), + possible_peripherals: vec!["UART1".to_owned(), "LEUART0".to_owned()], + host_facing: true, + }, + ]); + assert_eq!( + rendered, + "ariel_os_hal::define_uarts![ +{ name: CON0, device: UART2, tx: PC99, rx: PA08, host_facing: false }, +{ name: VCOM, device: UART1, tx: P1_23, rx: P0_04, host_facing: true }, +]; +" + ); +} diff --git a/crates/sbd-gen/src/riot.rs b/crates/sbd-gen/src/riot.rs index 86dfd5d..7c66974 100644 --- a/crates/sbd-gen/src/riot.rs +++ b/crates/sbd-gen/src/riot.rs @@ -231,7 +231,7 @@ fn generate_riot_target(sbd: &SbdFile, target: &Target) -> Result { println!( "warning: {}: no peripheral found for UART {}", target.name, - uart.name.as_ref().map_or_else(|| "unnamed", |s| s) + uart.name.as_deref().unwrap_or("unnamed") ); } }