From 4d97d9fe75e6627d60ef8855832e4713bbae4c61 Mon Sep 17 00:00:00 2001 From: Mathias Gottschlag Date: Tue, 28 Sep 2021 20:24:51 +0200 Subject: [PATCH] pio: Enable code sharing between SMs via objects for installed programs. Multiple state machines may want to execute the same program (e.g., two state machines are used to implement two I2C buses), in which code sharing saves space. --- rp2040-hal/src/pio.rs | 203 +++++++++++++++++++++++------------------- 1 file changed, 109 insertions(+), 94 deletions(-) diff --git a/rp2040-hal/src/pio.rs b/rp2040-hal/src/pio.rs index 1bf15da..d9d1d70 100644 --- a/rp2040-hal/src/pio.rs +++ b/rp2040-hal/src/pio.rs @@ -197,17 +197,21 @@ impl PIO

{ } } - fn add_program( + /// Allocates space in instruction memory and installs the program. + /// + /// The function returns a handle to the installed program that can be used to configure a + /// `StateMachine` via `PIOBuilder`. The program can be uninstalled to free instruction memory + /// via `uninstall()` once the state machine using the program has been uninitialized. + pub fn install( &mut self, - instructions: &[u16], - origin: Option, - side_set: pio::SideSet, - ) -> Option { - if let Some(offset) = self.find_offset_for_instructions(instructions, origin) { - for (i, instr) in instructions + p: &Program<{ pio::RP2040_MAX_PROGRAM_SIZE }>, + ) -> Result { + if let Some(offset) = self.find_offset_for_instructions(&p.code, p.origin) { + for (i, instr) in p + .code .iter() .map(|instr| { - let mut instr = pio::Instruction::decode(*instr, side_set).unwrap(); + let mut instr = pio::Instruction::decode(*instr, p.side_set).unwrap(); instr.operands = match instr.operands { pio::InstructionOperands::JMP { condition, address } => { @@ -222,7 +226,7 @@ impl PIO

{ _ => instr.operands, }; - instr.encode(side_set) + instr.encode(p.side_set) }) .enumerate() { @@ -230,9 +234,68 @@ impl PIO

{ } self.used_instruction_space = self.used_instruction_space | (((1 << p.code.len()) - 1) << offset); - Some(offset) + Ok(InstalledProgram { + offset: offset as u8, + length: p.code.len() as u8, + side_set: p.side_set, + wrap: p.wrap, + }) } else { - None + Err(InstallError::NoSpace) + } + } + + /// Removes the specified program from instruction memory, freeing the allocated space. + pub fn uninstall(&mut self, p: InstalledProgram) { + let instr_mask = (1 << p.length as u32) << p.offset as u32; + self.used_instruction_space = self.used_instruction_space & !instr_mask; + } +} + +/// Handle to a program that was placed in the PIO's instruction memory. +/// +/// Objects of this type can be reused for multiple state machines of the same PIO block to save +/// memory if multiple state machines are supposed to perform the same function (for example, if +/// one PIO block is used to implement multiple I2C busses). +/// +/// `PIO::uninstall(program)` can be used to free the space occupied by the program once it is no +/// longer used. +/// +/// TODO: Write an example? +/// +/// # Safety +/// +/// Objects of this type can outlive their `PIO` object. If the PIO block is reinitialized, the API +/// does not prevent the user from calling `uninstall()` when the PIO block does not actually hold +/// the program anymore. The user must therefore make sure that `uninstall()` is only called on the +/// PIO object which was used to install the program. +/// +/// TODO: Write an example? +#[derive(Debug)] +pub struct InstalledProgram { + offset: u8, + length: u8, + side_set: SideSet, + wrap: Wrap, +} + +impl InstalledProgram { + /// Clones this program handle so that it can be executed by two state machines at the same + /// time. + /// + /// # Safety + /// + /// This function is marked as unsafe because, once this function has been called, the + /// resulting handle can be used to call `PIO::uninstall()` while the program is still running. + /// + /// The user has to make sure to call `PIO::uninstall()` only once and only after all state + /// machines using the program have been uninitialized. + pub unsafe fn share(&self) -> InstalledProgram { + InstalledProgram { + offset: self.offset, + length: self.length, + side_set: self.side_set, + wrap: self.wrap, } } } @@ -643,18 +706,12 @@ impl ShiftDirection { /// Builder to deploy a fully configured PIO program on one of the state /// machines. #[derive(Debug)] -pub struct PIOBuilder<'a> { +pub struct PIOBuilder { /// Clock divisor. clock_divisor: f32, - /// Instructions of the program. - instructions: &'a [u16], - /// Origin where this program should be loaded. - instruction_origin: Option, - /// Wrapping behavior. - wrap: Wrap, - /// Side-set behavior. - side_set: SideSet, + /// Program location and configuration. + program: InstalledProgram, /// GPIO pin used by `jmp pin` instruction. jmp_pin: u8, @@ -697,17 +754,31 @@ pub struct PIOBuilder<'a> { out_base: u8, } -impl<'a> Default for PIOBuilder<'a> { - fn default() -> Self { +/// Buffer sharing configuration. +#[derive(Debug, Clone, Copy)] +pub enum Buffers { + /// No sharing. + RxTx, + /// The memory of the RX FIFO is given to the TX FIFO to double its depth. + OnlyTx, + /// The memory of the TX FIFO is given to the RX FIFO to double its depth. + OnlyRx, +} + +/// Errors that occurred during `PIO::install`. +#[derive(Debug)] +pub enum InstallError { + /// There was not enough space for the instructions on the selected PIO. + NoSpace, +} + +impl PIOBuilder { + /// Set config settings based on information from the given [`pio::Program`]. + /// Additional configuration may be needed in addition to this. + pub fn from_program(p: InstalledProgram) -> Self { PIOBuilder { clock_divisor: 1.0, - instructions: &[], - instruction_origin: None, - wrap: Wrap { - source: 31, - target: 0, - }, - side_set: SideSet::default(), + program: p, jmp_pin: 0, out_sticky: false, inline_out: None, @@ -727,47 +798,6 @@ impl<'a> Default for PIOBuilder<'a> { out_base: 0, } } -} - -/// Buffer sharing configuration. -#[derive(Debug, Clone, Copy)] -pub enum Buffers { - /// No sharing. - RxTx, - /// The memory of the RX FIFO is given to the TX FIFO to double its depth. - OnlyTx, - /// The memory of the TX FIFO is given to the RX FIFO to double its depth. - OnlyRx, -} - -/// Errors that occurred during `PIOBuilder::build`. -#[derive(Debug)] -pub enum BuildError { - /// There was not enough space for the instructions on the selected PIO. - NoSpace, -} - -impl<'a> PIOBuilder<'a> { - /// Set config settings based on information from the given [`pio::Program`]. - /// Additional configuration may be needed in addition to this. - pub fn with_program(mut self, p: &'a Program<{ pio::RP2040_MAX_PROGRAM_SIZE }>) -> Self { - self.instruction_origin = p.origin; - self.instructions(&p.code).wrap(p.wrap).side_set(p.side_set) - } - - /// Set the instructions of the program. - pub fn instructions(mut self, instructions: &'a [u16]) -> Self { - self.instructions = instructions; - self - } - - /// Set the wrap source and target. - /// - /// The program will automatically jump from the wrap bottom to the wrap top in 0 cycles. - pub fn wrap(mut self, wrap: Wrap) -> Self { - self.wrap = wrap; - self - } /// Set the pins asserted by `SET` instruction. /// @@ -817,12 +847,6 @@ impl<'a> PIOBuilder<'a> { self } - /// Set the side-set status. - pub fn side_set(mut self, side_set: SideSet) -> Self { - self.side_set = side_set; - self - } - /// Set buffer sharing. /// /// See [`Buffers`] for more information. @@ -905,27 +929,19 @@ impl<'a> PIOBuilder<'a> { } /// Build the config and deploy it to a StateMachine. - pub fn build( - self, - pio: &mut PIO

, - sm: &mut StateMachine

, - ) -> Result<(), BuildError> { - let offset = - match pio.add_program(self.instructions, self.instruction_origin, self.side_set) { - Some(o) => o, - None => return Err(BuildError::NoSpace), - }; + pub fn build(self, sm: &mut StateMachine

) { + // TODO: Currently, the program is just lost and can never be uninstalled again. + let offset = self.program.offset; // Stop the SM - // TODO: This should probably do before we write the program source code sm.set_enabled(false); // Write all configuration bits sm.set_clock_divisor(self.clock_divisor); sm.sm().sm_execctrl.write(|w| { - w.side_en().bit(self.side_set.optional()); - w.side_pindir().bit(self.side_set.pindirs()); + w.side_en().bit(self.program.side_set.optional()); + w.side_pindir().bit(self.program.side_set.pindirs()); unsafe { w.jmp_pin().bits(self.jmp_pin); @@ -943,8 +959,9 @@ impl<'a> PIOBuilder<'a> { w.out_sticky().bit(self.out_sticky); unsafe { - w.wrap_top().bits(offset as u8 + self.wrap.source); - w.wrap_bottom().bits(offset as u8 + self.wrap.target); + w.wrap_top().bits(offset as u8 + self.program.wrap.source); + w.wrap_bottom() + .bits(offset as u8 + self.program.wrap.target); } let n = match self.mov_status { @@ -990,7 +1007,7 @@ impl<'a> PIOBuilder<'a> { sm.sm().sm_pinctrl.write(|w| { unsafe { - w.sideset_count().bits(self.side_set.bits()); + w.sideset_count().bits(self.program.side_set.bits()); w.set_count().bits(self.set_count); w.out_count().bits(self.out_count); @@ -1019,7 +1036,5 @@ impl<'a> PIOBuilder<'a> { // Enable SM sm.set_enabled(true); - - Ok(()) } }