Skip to main content

bao1x_api/
bio.rs

1//! BIO API
2//!
3//! For the first draft of this API, we're going to take an approach where all machines
4//! and pins are explicitly managed. That is: we don't try and be clever and try to imply any
5//! allocations. The developer has to correctly identify which engine to place their code on,
6//! and which I/Os, if any, the thing uses.
7
8// Other notes:
9//
10// clock divider equation:
11// output frequency = fclk / (div_int + div_frac / 256)
12//
13
14use core::num::NonZeroU32;
15
16use bitbybit::bitfield;
17use num_traits::ToPrimitive;
18use xous::MemoryRange;
19
20use crate::IoxPort;
21
22pub const BIO_SERVER_NAME: &'static str = "_BIO server_";
23
24/// A platform-neutral API for the BIO. Intended to be usable in both baremetal and `std` environments
25pub trait BioApi<'a> {
26    /// Initializes a core with the given `code`, loading into `offset`, with a given
27    /// core configuration. This does not start the core running: that needs to be
28    /// done with a separate `set_core_state` call.
29    ///
30    /// Returns a `u32` which is the frequency in Hz of the actual quantum interval that
31    /// the core is running at. It's `None` if an external pin is configured as the quantum source.
32    fn init_core(
33        &mut self,
34        core: BioCore,
35        code: &[u8],
36        offset: usize,
37        config: CoreConfig,
38    ) -> Result<Option<u32>, BioError>;
39
40    /// Releases the core. As a side effect, the core is stopped.
41    fn de_init_core(&mut self, core: BioCore) -> Result<(), BioError>;
42
43    /// Updates the frequency of the cores, used to calculate dividers etc.
44    /// Returns the previous frequency used by the system. Setting this
45    /// allows the sleep/wake clock manager to do a best-effort rescaling of the
46    /// clock dividers for each core as DVFS is applied for power savings.
47    ///
48    /// This API call does not actually *set* the frequency of the BIO complex -
49    /// that is handled by the clock manager. This merely informs the BIO driver
50    /// that the clocks may have changed.
51    fn update_bio_freq(&mut self, freq: u32) -> u32;
52
53    /// Returns the frequency, in Hz, of the incoming clock to the BIO cores.
54    fn get_bio_freq(&self) -> u32;
55
56    /// Returns the currently running frequency of a given BIO core. This API
57    /// exists because `update_bio_freq()` call will result in the dividers being
58    /// adjusted in an attempt to maintain the target quantum; however, there will
59    /// often be some error between what was requested and the actual achievable
60    /// frequency of the quantum interval.
61    fn get_core_freq(&self, core: BioCore) -> Option<u32>;
62
63    /// The index of `which` corresponds to each of the cores, 0-3.
64    fn set_core_state(&mut self, which: [CoreRunSetting; 4]) -> Result<(), BioError>;
65
66    /// Returns a `usize` which should be turned into a CSR as *mut u32 by the caller
67    /// this can then be dereferenced using UTRA abstractions to access the following
68    /// registers:
69    ///   - SFR_FLEVEL
70    ///   - SFR_TXF#
71    ///   - SFR_RXF#
72    ///   - SFR_EVENT_SET
73    ///   - SFR_EVENT_CLR
74    ///   - SFR_EVENT_STATUS
75    ///
76    /// The handle can only access one of the FIFOs, but it has access to all the other
77    /// registers regardless of the FIFO.
78    ///
79    /// The handle has a `Drop` implementation that releases it when it goes out of scope.
80    ///
81    /// Safety: this has to be wrapped in an object that derives a CSR that also tracks
82    /// the lifetime of this object, to prevent `Drop` from being called at the wrong time.
83    ///
84    /// Returns `None` if no more handles are available
85    unsafe fn get_core_handle(&self, fifo: Fifo) -> Result<Option<CoreHandle>, BioError>;
86
87    /// This call sets up the BIO's IRQ routing. It doesn't actually claim the IRQ
88    /// or install the handler - that's up to the caller to do with Xous API calls.
89    fn setup_irq_config(&mut self, config: IrqConfig) -> Result<(), BioError>;
90
91    /// Allows BIO cores to DMA to/from the windows specified in `windows`. DMA filtering is
92    /// on by default with no windows allowed.
93    fn setup_dma_windows(&mut self, windows: DmaFilterWindows) -> Result<(), BioError>;
94
95    /// Sets up the BIO I/O configuration
96    fn setup_io_config(&mut self, config: IoConfig) -> Result<(), BioError>;
97
98    /// Sets up a FIFO event trigger
99    fn setup_fifo_event_triggers(&mut self, config: FifoEventConfig) -> Result<(), BioError>;
100
101    /// Returns a version code for the underlying hardware.
102    fn get_version(&self) -> u32;
103}
104
105#[macro_export]
106/// This macro takes three identifiers and assembly code:
107///   - name of the function to call to retrieve the assembled code
108///   - a unique identifier that serves as label name for the start of the code
109///   - a unique identifier that serves as label name for the end of the code
110///   - a comma separated list of strings that form the assembly itself
111///
112///   *** The comma separated list must *not* end in a comma. ***
113///
114///   The macro is unable to derive names of functions or identifiers for labels
115///   due to the partially hygienic macro rules of Rust, so you have to come
116///   up with a list of unique names by yourself.
117macro_rules! bio_code {
118    ($fn_name:ident, $name_start:ident, $name_end:ident, $($item:expr),*) => {
119        pub fn $fn_name() -> &'static [u8] {
120            extern "C" {
121                static $name_start: *const u8;
122                static $name_end: *const u8;
123            }
124            // skip the first 4 bytes, as they contain the loading offset
125            unsafe { core::slice::from_raw_parts($name_start.add(4), ($name_end as usize) - ($name_start as usize) - 4)}
126        }
127
128        core::arch::global_asm!(
129            ".align 4",
130            concat!(".globl ", stringify!($name_start)),
131            concat!(stringify!($name_start), ":"),
132            ".word .",
133            $($item),*
134            , ".align 4",
135            concat!(".globl ", stringify!($name_end)),
136            concat!(stringify!($name_end), ":"),
137            ".word .",
138        );
139    };
140}
141
142#[derive(Debug, Copy, Clone, num_derive::FromPrimitive, num_derive::ToPrimitive)]
143pub enum BioOp {
144    InitCore,
145    DeInitCore,
146    UpdateBioFreq,
147    GetCoreFreq,
148    GetBioFreq,
149    CoreState,
150    GetCoreHandle,
151    ReleaseCoreHandle,
152    IrqConfig,
153    DmaWindows,
154    IoConfig,
155    FifoEventTriggers,
156    GetVersion,
157
158    // Resource management opcodes
159    ClaimResources,
160    ReleaseResources,
161    ResourceAvailability,
162    CheckResources,
163    CheckResourcesBatch,
164    ClaimDynamicPin,
165    ReleaseDynamicPin,
166
167    InvalidCall,
168}
169
170#[derive(Debug, Copy, Clone)]
171#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
172// align this so it can be passed as a memory message
173#[repr(align(4096))]
174pub struct CoreInitRkyv {
175    pub core: BioCore,
176    pub offset: usize,
177    pub actual_freq: Option<u32>,
178    pub config: CoreConfig,
179    pub code: [u8; 4096],
180    pub result: BioError,
181}
182
183#[repr(usize)]
184#[derive(Copy, Clone, Debug, PartialEq, Eq)]
185#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
186pub enum BioCore {
187    Core0 = 0,
188    Core1 = 1,
189    Core2 = 2,
190    Core3 = 3,
191}
192
193impl From<usize> for BioCore {
194    fn from(value: usize) -> Self {
195        match value {
196            0 => BioCore::Core0,
197            1 => BioCore::Core1,
198            2 => BioCore::Core2,
199            3 => BioCore::Core3,
200            _ => panic!("Invalid BioCore value: {}", value),
201        }
202    }
203}
204
205#[repr(usize)]
206#[derive(Copy, Clone, Debug, PartialEq, Eq)]
207#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
208pub enum CoreRunSetting {
209    Unchanged = 0,
210    Start = 1,
211    Stop = 2,
212}
213
214impl From<usize> for CoreRunSetting {
215    fn from(value: usize) -> Self {
216        match value {
217            0 => CoreRunSetting::Unchanged,
218            1 => CoreRunSetting::Start,
219            2 => CoreRunSetting::Stop,
220            _ => panic!("Invalid CoreRunSetting: {}", value),
221        }
222    }
223}
224
225#[derive(Debug, Copy, Clone)]
226#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
227pub enum BioError {
228    /// Uninitialized
229    Uninit,
230    /// No error
231    None,
232    /// specified core is not valid
233    InvalidCore,
234    /// program can't fit in memory, for one reason or another
235    Oom,
236    /// no more machines available
237    NoFreeMachines,
238    /// resource is already in use
239    ResourceInUse,
240    /// Loaded code did not match, first error at argument
241    CodeCheck(usize),
242    /// Catch-all for programming bugs that shouldn't happen
243    InternalError,
244}
245
246impl From<xous::Error> for BioError {
247    fn from(error: xous::Error) -> Self {
248        match error {
249            xous::Error::OutOfMemory => BioError::Oom,
250            xous::Error::ServerNotFound => BioError::InvalidCore,
251            xous::Error::ServerExists => BioError::NoFreeMachines,
252            // Handle unmapped cases with a panic
253            _ => panic!("Cannot convert Error::{:?} to BioError", error),
254        }
255    }
256}
257
258#[derive(Debug, Clone, Copy)]
259#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
260pub struct BioPin {
261    pin_number: u8,
262}
263impl BioPin {
264    pub fn pin_number(&self) -> u8 { self.pin_number }
265
266    pub fn new(pin: u8) -> Self {
267        if pin < 32 { Self { pin_number: pin } } else { panic!("Pin value out of range") }
268    }
269}
270
271#[derive(Debug, Copy, Clone)]
272#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
273pub enum ClockMode {
274    /// Fixed divider - (int, frac)
275    FixedDivider(u16, u8),
276    /// Target frequency - fractional allowed. Attempts to adjust to target based on
277    /// changing CPU clock. Fractional component means the "average" frequency is achieved
278    /// by occasionally skipping clocks. This means there is jitter in the edge timing.
279    TargetFreqFrac(u32),
280    /// Target frequency - integer dividers only allowed. The absolute error of the
281    /// frequency may be larger, but the jitter is smaller. Attempts to adjust to the
282    /// target based on changing CPU clock.
283    TargetFreqInt(u32),
284    /// Use external pin as quantum source
285    ExternalPin(BioPin),
286}
287
288#[derive(Debug, Copy, Clone)]
289#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
290pub struct CoreConfig {
291    pub clock_mode: ClockMode,
292}
293
294#[derive(Debug, Copy, Clone, Eq, PartialEq)]
295#[repr(usize)]
296#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
297pub enum IoConfigMode {
298    /// values in the structure will overwrite everything
299    Overwrite = 0,
300    /// only values that are `1` will set the corresponding bit
301    SetOnly = 1,
302    /// only values that are `1` will clear the corresponding bit
303    ClearOnly = 2,
304}
305
306impl From<usize> for IoConfigMode {
307    fn from(value: usize) -> Self {
308        match value {
309            0 => IoConfigMode::Overwrite,
310            1 => IoConfigMode::SetOnly,
311            2 => IoConfigMode::ClearOnly,
312            _ => unimplemented!(),
313        }
314    }
315}
316
317#[derive(Debug, Copy, Clone)]
318#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
319pub struct IoConfig {
320    /// See definition of IoConfigMode enum
321    pub mode: IoConfigMode,
322    /// bits set to `1` here are mapped to the BIO instead of the GPIO
323    pub mapped: u32,
324    /// bits set to `1` in the u32 corresponding to BIO inputs passed in as raw, unsynchronized
325    /// values. This can lead to instability, but reduces latency.
326    pub sync_bypass: u32,
327    /// bits set to `1` in the u32 corresponding to BIO outputs that have the OE inverted
328    pub oe_inv: u32,
329    /// bits set to `1` in the u32 corresponding to BIO outputs that have the output value inverted
330    /// compared to the value written into the register
331    pub o_inv: u32,
332    /// bits set to `1` in the u32 corresponding to BIO inputs that have the input value inverted
333    /// before being passed into the register accessed by the core
334    pub i_inv: u32,
335    /// When specified all GPIO inputs are aligned to the divided clock of the specified core
336    pub snap_inputs: Option<BioCore>,
337    /// When specified all GPIO outputs are aligned to the divided clock of the specified core
338    pub snap_outputs: Option<BioCore>,
339}
340impl Default for IoConfig {
341    fn default() -> Self {
342        Self {
343            mode: IoConfigMode::Overwrite,
344            mapped: 0,
345            sync_bypass: 0,
346            oe_inv: 0,
347            o_inv: 0,
348            i_inv: 0,
349            snap_inputs: None,
350            snap_outputs: None,
351        }
352    }
353}
354
355#[bitfield(u8)]
356#[derive(Debug)]
357#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
358pub struct TriggerSlot {
359    #[bits(1..=7)]
360    _reserved: arbitrary_int::u7,
361    #[bit(0, rw)]
362    trigger_slot: arbitrary_int::u1,
363}
364
365#[bitfield(u8)]
366#[derive(Debug)]
367#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
368pub struct FifoLevel {
369    #[bits(3..=7)]
370    _reserved: arbitrary_int::u5,
371    #[bits(0..=2, rw)]
372    level: arbitrary_int::u3,
373}
374
375/// The event register is divided into 24 code-settable event bits + 8 FIFO event bits
376#[derive(Debug)]
377#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
378pub struct FifoEventConfig {
379    // for which FIFO we're configuring its event triggers
380    pub which: Fifo,
381    // there are up to two trigger slots per FIFO, specify 0 or 1 here.
382    pub trigger_slot: TriggerSlot,
383    // the level used for the triggers. Any number from 0-7.
384    pub level: FifoLevel,
385    // when set, the trigger condition happens compared to the level above
386    pub trigger_less_than: bool,
387    pub trigger_greater_than: bool,
388    pub trigger_equal_to: bool,
389}
390
391#[derive(Debug, Copy, Clone, Eq, PartialEq)]
392#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
393#[repr(usize)]
394pub enum Fifo {
395    Fifo0 = 0,
396    Fifo1 = 1,
397    Fifo2 = 2,
398    Fifo3 = 3,
399}
400
401impl Fifo {
402    pub fn to_usize_checked(self) -> usize {
403        let discriminant = self as usize;
404        assert!(discriminant <= 3, "Invalid discriminant");
405        discriminant
406    }
407}
408
409#[derive(Debug, Copy, Clone)]
410#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
411#[repr(usize)]
412pub enum Irq {
413    Irq0 = 0,
414    Irq1 = 1,
415    Irq2 = 2,
416    Irq3 = 3,
417}
418
419impl Irq {
420    pub fn to_usize_checked(self) -> usize {
421        let discriminant = self as usize;
422        assert!(discriminant <= 3, "Invalid discriminant");
423        discriminant
424    }
425}
426
427#[bitfield(u32)]
428#[derive(Debug)]
429#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
430pub struct IrqMask {
431    #[bit(31, rw)]
432    fifo3_trigger1: bool,
433    #[bit(30, rw)]
434    fifo3_trigger0: bool,
435    #[bit(29, rw)]
436    fifo2_trigger1: bool,
437    #[bit(28, rw)]
438    fifo2_trigger0: bool,
439    #[bit(27, rw)]
440    fifo1_trigger1: bool,
441    #[bit(26, rw)]
442    fifo1_trigger0: bool,
443    #[bit(25, rw)]
444    fifo0_trigger1: bool,
445    #[bit(24, rw)]
446    fifo0_trigger0: bool,
447    #[bits(0..=23, rw)]
448    software: arbitrary_int::u24,
449}
450
451#[derive(Debug, Copy, Clone)]
452#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
453pub struct IrqConfig {
454    /// Specifies which of the four BIO IRQ lines are being configured
455    pub which: Irq,
456    pub edge_triggered: bool,
457    /// Bits set to 1 here will cause an interrupt to be passed up when they are set in the
458    /// aggregated BIO event state.
459    pub mask: IrqMask,
460}
461
462/// Defines an accessible window by DMA from BIO cores. It's a slice starting from `base` going for `bounds`
463/// bytes.
464#[derive(Debug, Copy, Clone)]
465#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
466pub struct DmaWindow {
467    pub base: u32,
468    pub bounds: NonZeroU32,
469}
470
471/// Structure that defines all of the windows allowed by the system
472#[derive(Debug, Copy, Clone)]
473#[cfg_attr(feature = "std", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
474pub struct DmaFilterWindows {
475    pub windows: [Option<DmaWindow>; 4],
476}
477
478pub struct CoreHandle {
479    conn: xous::CID,
480    handle: usize,
481    fifo: arbitrary_int::u2,
482}
483
484impl CoreHandle {
485    pub fn new(conn: xous::CID, handle: usize, fifo: arbitrary_int::u2) -> Self {
486        Self { conn, handle, fifo }
487    }
488
489    /// safety: this needs to be wrapped in a hardware-level CSR object that tracks the lifetime of
490    /// the underlying pointer handle.
491    pub unsafe fn handle(&self) -> usize { self.handle }
492}
493
494impl Drop for CoreHandle {
495    fn drop(&mut self) {
496        // safety: handle was allocated by the OS and is thus safe to re-create as a range
497        // the length of the range (one page) is set by the hardware implementation and never changes
498        xous::unmap_memory(unsafe { MemoryRange::new(self.handle, 4096).unwrap() }).unwrap();
499        xous::send_message(
500            self.conn,
501            xous::Message::new_blocking_scalar(
502                BioOp::ReleaseCoreHandle.to_usize().unwrap(),
503                self.fifo.value() as usize,
504                0,
505                0,
506                0,
507            ),
508        )
509        .unwrap();
510    }
511}
512
513pub fn bio_bit_to_port_and_pin(bit: arbitrary_int::u5) -> (IoxPort, u8) {
514    match bit.value() {
515        // For bao1x the ports should be in correct order
516        0 => (IoxPort::PB, 0),
517        1 => (IoxPort::PB, 1),
518        2 => (IoxPort::PB, 2),
519        3 => (IoxPort::PB, 3),
520        4 => (IoxPort::PB, 4),
521        5 => (IoxPort::PB, 5),
522        6 => (IoxPort::PB, 6),
523        7 => (IoxPort::PB, 7),
524        8 => (IoxPort::PB, 8),
525        9 => (IoxPort::PB, 9),
526        10 => (IoxPort::PB, 10),
527        11 => (IoxPort::PB, 11),
528        12 => (IoxPort::PB, 12),
529        13 => (IoxPort::PB, 13),
530        14 => (IoxPort::PB, 14),
531        15 => (IoxPort::PB, 15),
532        // Port C
533        16 => (IoxPort::PC, 0),
534        17 => (IoxPort::PC, 1),
535        18 => (IoxPort::PC, 2),
536        19 => (IoxPort::PC, 3),
537        20 => (IoxPort::PC, 4),
538        21 => (IoxPort::PC, 5),
539        22 => (IoxPort::PC, 6),
540        23 => (IoxPort::PC, 7),
541        24 => (IoxPort::PC, 8),
542        25 => (IoxPort::PC, 9),
543        26 => (IoxPort::PC, 10),
544        27 => (IoxPort::PC, 11),
545        28 => (IoxPort::PC, 12),
546        29 => (IoxPort::PC, 13),
547        30 => (IoxPort::PC, 14),
548        31 => (IoxPort::PC, 15),
549        _ => unreachable!(),
550    }
551}
552
553pub fn port_and_pin_to_bio_bit(port: IoxPort, pin: u8) -> Option<arbitrary_int::u5> {
554    match port {
555        IoxPort::PA => None,
556        IoxPort::PB => {
557            if pin >= 16 {
558                None
559            } else {
560                let bio_bit = 15 - pin;
561                Some(arbitrary_int::u5::new(bio_bit))
562            }
563        }
564        IoxPort::PC => {
565            if pin < 16 {
566                let bio_bit = pin + 16;
567                Some(arbitrary_int::u5::new(bio_bit))
568            } else {
569                None
570            }
571        }
572        _ => None,
573    }
574}