fix concurrent accesses to sm_execctrl and sm_instr when sideset isn't optional (#448)

* fix concurrent accesses to sm_execctrl and sm_instr when sideset isn't optional
* review & document unsafe blocks
This commit is contained in:
Wilfried Chauveau 2022-09-16 17:19:54 +01:00 committed by GitHub
parent 322bba2cc0
commit 4ddad4e74c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 368 additions and 302 deletions

View file

@ -28,3 +28,4 @@ rp2040-hal = { path = "./rp2040-hal" }
[patch.crates-io] [patch.crates-io]
rp2040-hal = { path = "./rp2040-hal" } rp2040-hal = { path = "./rp2040-hal" }
i2c-pio = { git = "https://github.com/ithinuel/i2c-pio-rs", branch = "fix-pio-exec-instruction" }

View file

@ -34,7 +34,7 @@ use rp_pico::hal;
// Import pio crates // Import pio crates
use hal::pio::{PIOBuilder, Running, StateMachine, Tx, ValidStateMachine, SM0}; use hal::pio::{PIOBuilder, Running, StateMachine, Tx, ValidStateMachine, SM0};
use pio::{InstructionOperands, OutDestination}; use pio::{Instruction, InstructionOperands, OutDestination};
use pio_proc::pio_file; use pio_proc::pio_file;
/// Set pio pwm period /// Set pio pwm period
@ -53,20 +53,22 @@ fn pio_pwm_set_period<T: ValidStateMachine>(
let mut sm = sm.stop(); let mut sm = sm.stop();
tx.write(period); tx.write(period);
sm.exec_instruction( sm.exec_instruction(Instruction {
InstructionOperands::PULL { operands: InstructionOperands::PULL {
if_empty: false, if_empty: false,
block: false, block: false,
} },
.encode(), delay: 0,
); side_set: None,
sm.exec_instruction( });
InstructionOperands::OUT { sm.exec_instruction(Instruction {
operands: InstructionOperands::OUT {
destination: OutDestination::ISR, destination: OutDestination::ISR,
bit_count: 32, bit_count: 32,
} },
.encode(), delay: 0,
); side_set: None,
});
sm.start() sm.start()
} }

View file

@ -4,7 +4,7 @@ use crate::{
atomic_register_access::{write_bitmask_clear, write_bitmask_set}, atomic_register_access::{write_bitmask_clear, write_bitmask_set},
resets::SubsystemReset, resets::SubsystemReset,
}; };
use pio::{Program, SideSet, Wrap}; use pio::{Instruction, InstructionOperands, Program, SideSet, Wrap};
use rp2040_pac::{PIO0, PIO1}; use rp2040_pac::{PIO0, PIO1};
const PIO_INSTRUCTION_COUNT: usize = 32; const PIO_INSTRUCTION_COUNT: usize = 32;
@ -191,31 +191,37 @@ impl<P: PIOExt> PIO<P> {
p: &Program<{ pio::RP2040_MAX_PROGRAM_SIZE }>, p: &Program<{ pio::RP2040_MAX_PROGRAM_SIZE }>,
) -> Result<InstalledProgram<P>, InstallError> { ) -> Result<InstalledProgram<P>, InstallError> {
if let Some(offset) = self.find_offset_for_instructions(&p.code, p.origin) { if let Some(offset) = self.find_offset_for_instructions(&p.code, p.origin) {
for (i, instr) in p p.code
.code
.iter() .iter()
.cloned()
.map(|instr| { .map(|instr| {
let mut instr = pio::Instruction::decode(*instr, p.side_set).unwrap(); if let Some(Instruction {
operands: InstructionOperands::JMP { condition, address },
delay,
side_set,
}) = Instruction::decode(instr, p.side_set)
{
// JMP instruction. We need to apply offset here
let address = address + offset as u8;
assert!(
address < pio::RP2040_MAX_PROGRAM_SIZE as u8,
"Invalid JMP out of the program after offset addition"
);
instr.operands = match instr.operands { Instruction {
pio::InstructionOperands::JMP { condition, address } => { operands: InstructionOperands::JMP { condition, address },
// JMP instruction. We need to apply offset here delay,
let address = address + offset as u8; side_set,
assert!(
address < pio::RP2040_MAX_PROGRAM_SIZE as u8,
"Invalid JMP out of the program after offset addition"
);
pio::InstructionOperands::JMP { condition, address }
} }
_ => instr.operands, .encode(p.side_set)
}; } else {
instr
instr.encode(p.side_set) }
}) })
.enumerate() .enumerate()
{ .for_each(|(i, instr)| {
self.pio.instr_mem[i + offset].write(|w| unsafe { w.bits(instr as u32) }) self.pio.instr_mem[i + offset].write(|w| unsafe { w.instr_mem0().bits(instr) })
} });
self.used_instruction_space |= Self::instruction_mask(p.code.len()) << offset; self.used_instruction_space |= Self::instruction_mask(p.code.len()) << offset;
Ok(InstalledProgram { Ok(InstalledProgram {
offset: offset as u8, offset: offset as u8,
@ -470,28 +476,20 @@ impl<SM: ValidStateMachine> UninitStateMachine<SM> {
// Safety: The Send trait assumes this is the only write to sm_clkdiv // Safety: The Send trait assumes this is the only write to sm_clkdiv
fn set_clock_divisor(&self, int: u16, frac: u8) { fn set_clock_divisor(&self, int: u16, frac: u8) {
self.sm().sm_clkdiv.write(|w| { // Safety: This is the only write to this register
unsafe { unsafe {
w.int().bits(int); self.sm()
w.frac().bits(frac); .sm_clkdiv
} .write(|w| w.int().bits(int).frac().bits(frac));
}
w
});
} }
/// Execute the instruction immediately. unsafe fn sm(&self) -> &rp2040_pac::pio0::SM {
// Safety: The Send trait assumes this is the only write to sm_instr while uninitialized. The &*self.sm
// initialized `StateMachine` may also use this register. The `UnintStateMachine` is consumed
// by `PIOBuilder.build` to create `StateMachine`
fn exec_instruction(&mut self, instruction: u16) {
self.sm()
.sm_instr
.write(|w| unsafe { w.sm0_instr().bits(instruction) })
} }
fn sm(&self) -> &rp2040_pac::pio0::SM { unsafe fn pio(&self) -> &rp2040_pac::pio0::RegisterBlock {
unsafe { &*self.sm } &*self.block
} }
} }
@ -541,28 +539,92 @@ impl<SM: ValidStateMachine, State> StateMachine<SM, State> {
/// The address of the instruction currently being executed. /// The address of the instruction currently being executed.
pub fn instruction_address(&self) -> u32 { pub fn instruction_address(&self) -> u32 {
self.sm.sm().sm_addr.read().bits() // Safety: Read only access without side effect
unsafe { self.sm.sm().sm_addr.read().bits() }
} }
#[deprecated(note = "Renamed to exec_instruction")] #[deprecated(note = "Renamed to exec_instruction")]
///Execute the instruction immediately. /// Execute the instruction immediately.
pub fn set_instruction(&mut self, instruction: u16) { pub fn set_instruction(&mut self, instruction: u16) {
let instruction =
Instruction::decode(instruction, self.program.side_set).expect("Invalid instruction");
self.exec_instruction(instruction); self.exec_instruction(instruction);
} }
/// Execute the instruction immediately. /// Execute the instruction immediately.
/// ///
/// While this is allowed even when the state machine is running, the datasheet says: /// If an instruction written to INSTR stalls, it is stored in the same instruction latch used
/// > If EXEC instructions are used, instructions written to INSTR must not stall. /// by OUT EXEC and MOV EXEC, and will overwrite an in-progress instruction there. If EXEC
/// It's unclear what happens if this is violated. /// instructions are used, instructions written to INSTR must not stall.
pub fn exec_instruction(&mut self, instruction: u16) { pub fn exec_instruction(&mut self, instruction: Instruction) {
// TODO: clarify what happens if the instruction stalls. let instruction = instruction.encode(self.program.side_set);
self.sm.exec_instruction(instruction);
// Safety: all accesses to this register are controlled by this instance
unsafe {
self.sm
.sm()
.sm_instr
.write(|w| w.sm0_instr().bits(instruction))
}
} }
/// Check if the current instruction is stalled. /// Check if the current instruction is stalled.
pub fn stalled(&self) -> bool { pub fn stalled(&self) -> bool {
self.sm.sm().sm_execctrl.read().exec_stalled().bits() // Safety: read only access without side effect
unsafe { self.sm.sm().sm_execctrl.read().exec_stalled().bits() }
}
/// Drain Tx fifo.
pub fn drain_tx_fifo(&mut self) {
// According to the datasheet 3.5.4.2 Page 358:
//
// When autopull is enabled, the behaviour of 'PULL' is altered: it becomes a no-op
// if the OSR is full. This is to avoid a race condition against the system
// DMA. It behaves as a fence: either an autopull has already taken place, in which case
// the 'PULL' has no effect, or the program will stall on the 'PULL' until data becomes
// available in the FIFO.
// TODO: encode at compile time once pio 0.3.0 is out
const OUT: InstructionOperands = InstructionOperands::OUT {
destination: pio::OutDestination::NULL,
bit_count: 32,
};
const PULL: InstructionOperands = InstructionOperands::PULL {
if_empty: false,
block: false,
};
// Safety: all accesses to these registers are controlled by this instance
unsafe {
let sm = &self.sm.sm();
let sm_pinctrl = &sm.sm_pinctrl;
let sm_instr = &sm.sm_instr;
let fstat = &self.sm.pio().fstat;
let operands = if sm.sm_shiftctrl.read().autopull().bit_is_set() {
OUT
} else {
PULL
}
.encode();
// Safety: sm0_instr may be accessed from SM::exec_instruction.
let mut saved_sideset_count = 0;
sm_pinctrl.modify(|r, w| {
saved_sideset_count = r.sideset_count().bits();
w.sideset_count().bits(0)
});
let mask = 1 << SM::id();
// white tx fifo is not empty
while (fstat.read().txempty().bits() & mask) == 0 {
sm_instr.write(|w| w.sm0_instr().bits(operands))
}
if saved_sideset_count != 0 {
sm_pinctrl.modify(|_, w| w.sideset_count().bits(saved_sideset_count));
}
}
} }
} }
@ -590,14 +652,12 @@ impl<SM: ValidStateMachine> StateMachine<SM, Stopped> {
let int = divisor as u16; let int = divisor as u16;
let frac = ((divisor - int as f32) * 256.0) as u8; let frac = ((divisor - int as f32) * 256.0) as u8;
self.sm.sm().sm_clkdiv.write(|w| { self.sm.set_clock_divisor(int, frac);
unsafe { }
w.int().bits(int);
w.frac().bits(frac);
}
w /// Change the clock divider of a stopped state machine using a 16.8 fixed point value.
}); pub fn clock_divisor_fixed_point(&mut self, int: u16, frac: u8) {
self.sm.set_clock_divisor(int, frac);
} }
/// Sets the pin state for the specified pins. /// Sets the pin state for the specified pins.
@ -606,29 +666,50 @@ impl<SM: ValidStateMachine> StateMachine<SM, Stopped> {
/// other state machines of the same PIO block. /// other state machines of the same PIO block.
/// ///
/// The iterator's item are pairs of `(pin_number, pin_state)`. /// The iterator's item are pairs of `(pin_number, pin_state)`.
// Safety: this exclusively manages the SM resource and is created from the
// `UninitStateMachine` byt adding a program.
pub fn set_pins(&mut self, pins: impl IntoIterator<Item = (u8, PinState)>) { pub fn set_pins(&mut self, pins: impl IntoIterator<Item = (u8, PinState)>) {
let saved_ctrl = self.sm.sm().sm_pinctrl.read(); // TODO: turn those three into const once pio 0.3.0 is released
let saved_execctrl = self.sm.sm().sm_execctrl.read(); let set_high_instr = InstructionOperands::SET {
for (pin_num, pin_state) in pins { destination: pio::SetDestination::PINS,
self.sm data: 1,
.sm() }
.sm_pinctrl .encode();
.write(|w| unsafe { w.set_base().bits(pin_num).set_count().bits(1) }); let set_low_instr = InstructionOperands::SET {
self.exec_instruction( destination: pio::SetDestination::PINS,
pio::InstructionOperands::SET { data: 0,
destination: pio::SetDestination::PINS, }
data: if PinState::High == pin_state { 1 } else { 0 }, .encode();
}
.encode(), // Safety: all accesses to these registers are controlled by this instance
); unsafe {
let sm = self.sm.sm();
let sm_pinctrl = &sm.sm_pinctrl;
let sm_execctrl = &sm.sm_execctrl;
let sm_instr = &sm.sm_instr;
// sideset_count is implicitly set to 0 when the set_base/set_count are written (rather
// than modified)
let saved_pin_ctrl = sm_pinctrl.read().bits();
let mut saved_execctrl = 0;
sm_execctrl.modify(|r, w| {
saved_execctrl = r.bits();
w.out_sticky().clear_bit()
});
for (pin_num, pin_state) in pins {
sm_pinctrl.write(|w| w.set_base().bits(pin_num).set_count().bits(1));
let instruction = if pin_state == PinState::High {
set_high_instr
} else {
set_low_instr
};
sm_instr.write(|w| w.sm0_instr().bits(instruction))
}
sm_pinctrl.write(|w| w.bits(saved_pin_ctrl));
sm_execctrl.write(|w| w.bits(saved_execctrl));
} }
let sm = self.sm.sm();
sm.sm_pinctrl
.write(|w| unsafe { w.bits(saved_ctrl.bits()) });
sm.sm_execctrl
.write(|w| unsafe { w.bits(saved_execctrl.bits()) })
} }
/// Set pin directions. /// Set pin directions.
@ -637,33 +718,50 @@ impl<SM: ValidStateMachine> StateMachine<SM, Stopped> {
/// other state machines of the same PIO block. /// other state machines of the same PIO block.
/// ///
/// The iterator's item are pairs of `(pin_number, pin_dir)`. /// The iterator's item are pairs of `(pin_number, pin_dir)`.
// Safety: this exclusively manages the SM resource and is created from the
// `UninitStateMachine` byt adding a program.
pub fn set_pindirs(&mut self, pindirs: impl IntoIterator<Item = (u8, PinDir)>) { pub fn set_pindirs(&mut self, pindirs: impl IntoIterator<Item = (u8, PinDir)>) {
let saved_ctrl = self.sm.sm().sm_pinctrl.read(); // TODO: turn those three into const once pio 0.3.0 is released
let saved_execctrl = self.sm.sm().sm_execctrl.read(); let set_output_instr = InstructionOperands::SET {
self.sm destination: pio::SetDestination::PINDIRS,
.sm() data: 1,
.sm_execctrl }
.modify(|_, w| w.out_sticky().clear_bit()); .encode();
for (pinnum, pin_dir) in pindirs { let set_input_instr = InstructionOperands::SET {
self.sm destination: pio::SetDestination::PINDIRS,
.sm() data: 0,
.sm_pinctrl }
.write(|w| unsafe { w.set_base().bits(pinnum).set_count().bits(1) }); .encode();
self.exec_instruction(
pio::InstructionOperands::SET { // Safety: all accesses to these registers are controlled by this instance
destination: pio::SetDestination::PINDIRS, unsafe {
data: if PinDir::Output == pin_dir { 1 } else { 0 }, let sm = self.sm.sm();
} let sm_pinctrl = &sm.sm_pinctrl;
.encode(), let sm_execctrl = &sm.sm_execctrl;
); let sm_instr = &sm.sm_instr;
// sideset_count is implicitly set to 0 when the set_base/set_count are written (rather
// than modified)
let saved_pin_ctrl = sm_pinctrl.read().bits();
let mut saved_execctrl = 0;
sm_execctrl.modify(|r, w| {
saved_execctrl = r.bits();
w.out_sticky().clear_bit()
});
for (pin_num, pin_dir) in pindirs {
sm_pinctrl.write(|w| w.set_base().bits(pin_num).set_count().bits(1));
let instruction = if pin_dir == PinDir::Output {
set_output_instr
} else {
set_input_instr
};
sm_instr.write(|w| w.sm0_instr().bits(instruction))
}
sm_pinctrl.write(|w| w.bits(saved_pin_ctrl));
sm_execctrl.write(|w| w.bits(saved_execctrl));
} }
let sm = self.sm.sm();
sm.sm_pinctrl
.write(|w| unsafe { w.bits(saved_ctrl.bits()) });
sm.sm_execctrl
.write(|w| unsafe { w.bits(saved_execctrl.bits()) });
} }
} }
@ -1060,10 +1158,7 @@ impl<'sm, SM: ValidStateMachine> Drop for Synchronize<'sm, SM> {
// Restart the clocks of all state machines specified by the mask. // Restart the clocks of all state machines specified by the mask.
// Bits 11:8 of CTRL contain CLKDIV_RESTART. // Bits 11:8 of CTRL contain CLKDIV_RESTART.
let sm_mask = self.sm_mask << 8; let sm_mask = self.sm_mask << 8;
// Safety: We only use the atomic alias of the register. self.sm.sm.set_ctrl_bits(sm_mask);
unsafe {
write_bitmask_set((*self.sm.sm.block).ctrl.as_ptr(), sm_mask as u32);
}
} }
} }
@ -1084,16 +1179,37 @@ impl<SM: ValidStateMachine> StateMachine<SM, Running> {
pub fn restart(&mut self) { pub fn restart(&mut self) {
// pause the state machine // pause the state machine
self.sm.set_enabled(false); self.sm.set_enabled(false);
// revert it to its wrap target
self.sm.exec_instruction( // Safety: all accesses to these registers are controlled by this instance
pio::InstructionOperands::JMP { unsafe {
let sm = self.sm.sm();
let sm_pinctrl = &sm.sm_pinctrl;
let sm_instr = &sm.sm_instr;
// save exec_ctrl & make side_set optional
let mut saved_sideset_count = 0;
sm_pinctrl.modify(|r, w| {
saved_sideset_count = r.sideset_count().bits();
w.sideset_count().bits(0)
});
// revert it to its wrap target
let instruction = InstructionOperands::JMP {
condition: pio::JmpCondition::Always, condition: pio::JmpCondition::Always,
address: self.program.wrap_target(), address: self.program.wrap_target(),
} }
.encode(), .encode();
); sm_instr.write(|w| w.sm0_instr().bits(instruction));
// clear osr/isr
self.sm.restart(); // restore exec_ctrl
if saved_sideset_count != 0 {
sm_pinctrl.modify(|_, w| w.sideset_count().bits(saved_sideset_count));
}
// clear osr/isr
self.sm.restart();
}
// unpause the state machine // unpause the state machine
self.sm.set_enabled(true); self.sm.set_enabled(true);
} }
@ -1111,9 +1227,8 @@ unsafe impl<SM: ValidStateMachine + Send> Send for Rx<SM> {}
// Safety: `Rx` is marked Send so ensure all accesses remain atomic and no new concurrent accesses // Safety: `Rx` is marked Send so ensure all accesses remain atomic and no new concurrent accesses
// are added. // are added.
impl<SM: ValidStateMachine> Rx<SM> { impl<SM: ValidStateMachine> Rx<SM> {
fn register_block(&self) -> &pac::pio0::RegisterBlock { unsafe fn block(&self) -> &pac::pio0::RegisterBlock {
// Safety: The register is unique to this Rx instance. &*self.block
unsafe { &*self.block }
} }
/// Gets the FIFO's address. /// Gets the FIFO's address.
@ -1123,7 +1238,9 @@ impl<SM: ValidStateMachine> Rx<SM> {
/// NB: You are responsible for using the pointer correctly and not /// NB: You are responsible for using the pointer correctly and not
/// underflowing the buffer. /// underflowing the buffer.
pub fn fifo_address(&self) -> *const u32 { pub fn fifo_address(&self) -> *const u32 {
self.register_block().rxf[SM::id()].as_ptr() // Safety: returning the address is safe as such. The user is responsible for any
// dereference ops at that address.
unsafe { self.block().rxf[SM::id()].as_ptr() }
} }
/// Gets the FIFO's `DREQ` value. /// Gets the FIFO's `DREQ` value.
@ -1147,36 +1264,41 @@ impl<SM: ValidStateMachine> Rx<SM> {
} }
// Safety: The register is unique to this Rx instance. // Safety: The register is unique to this Rx instance.
Some(self.register_block().rxf[SM::id() as usize].read().bits()) Some(unsafe { core::ptr::read_volatile(self.fifo_address()) })
} }
/// Enable/Disable the autopush feature of the state machine. /// Enable/Disable the autopush feature of the state machine.
// Safety: This register is read by Rx, this is the only write. // Safety: This register is read by Rx, this is the only write.
pub fn enable_autopush(&mut self, enable: bool) { pub fn enable_autopush(&mut self, enable: bool) {
self.register_block().sm[SM::id()] // Safety: only instance reading/writing to autopush bit and no other write to this
.sm_shiftctrl // register
.modify(|_, w| w.autopush().bit(enable)) unsafe {
self.block().sm[SM::id()]
.sm_shiftctrl
.modify(|_, w| w.autopush().bit(enable))
}
} }
/// Indicate if the rx FIFO is empty /// Indicate if the rx FIFO is empty
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.register_block().fstat.read().rxempty().bits() & (1 << SM::id()) != 0 // Safety: Read only access without side effect
unsafe { self.block().fstat.read().rxempty().bits() & (1 << SM::id()) != 0 }
} }
/// Indicate if the rx FIFO is full /// Indicate if the rx FIFO is full
pub fn is_full(&self) -> bool { pub fn is_full(&self) -> bool {
self.register_block().fstat.read().rxfull().bits() & (1 << SM::id()) != 0 // Safety: Read only access without side effect
unsafe { self.block().fstat.read().rxfull().bits() & (1 << SM::id()) != 0 }
} }
/// Enable RX FIFO not empty interrupt. /// Enable RX FIFO not empty interrupt.
/// ///
/// This interrupt is raised when the RX FIFO is not empty, i.e. one could read more data from it. /// This interrupt is raised when the RX FIFO is not empty, i.e. one could read more data from it.
pub fn enable_rx_not_empty_interrupt(&self, id: PioIRQ) { pub fn enable_rx_not_empty_interrupt(&self, id: PioIRQ) {
// Safety: Atomic write to a single bit owned by this instance
unsafe { unsafe {
write_bitmask_set( write_bitmask_set(
self.register_block().sm_irq[id.to_index()] self.block().sm_irq[id.to_index()].irq_inte.as_ptr(),
.irq_inte
.as_ptr(),
1 << SM::id(), 1 << SM::id(),
); );
} }
@ -1184,11 +1306,10 @@ impl<SM: ValidStateMachine> Rx<SM> {
/// Disable RX FIFO not empty interrupt. /// Disable RX FIFO not empty interrupt.
pub fn disable_rx_not_empty_interrupt(&self, id: PioIRQ) { pub fn disable_rx_not_empty_interrupt(&self, id: PioIRQ) {
// Safety: Atomic write to a single bit owned by this instance
unsafe { unsafe {
write_bitmask_clear( write_bitmask_clear(
self.register_block().sm_irq[id.to_index()] self.block().sm_irq[id.to_index()].irq_inte.as_ptr(),
.irq_inte
.as_ptr(),
1 << SM::id(), 1 << SM::id(),
); );
} }
@ -1201,11 +1322,10 @@ impl<SM: ValidStateMachine> Rx<SM> {
} else { } else {
write_bitmask_clear write_bitmask_clear
}; };
// Safety: Atomic write to a single bit owned by this instance
unsafe { unsafe {
action( action(
self.register_block().sm_irq[id.to_index()] self.block().sm_irq[id.to_index()].irq_intf.as_ptr(),
.irq_intf
.as_ptr(),
1 << SM::id(), 1 << SM::id(),
); );
} }
@ -1224,9 +1344,21 @@ unsafe impl<SM: ValidStateMachine + Send> Send for Tx<SM> {}
// Safety: `Tx` is marked Send so ensure all accesses remain atomic and no new concurrent accesses // Safety: `Tx` is marked Send so ensure all accesses remain atomic and no new concurrent accesses
// are added. // are added.
impl<SM: ValidStateMachine> Tx<SM> { impl<SM: ValidStateMachine> Tx<SM> {
fn register_block(&self) -> &pac::pio0::RegisterBlock { unsafe fn block(&self) -> &pac::pio0::RegisterBlock {
// Safety: The register is unique to this Tx instance. &*self.block
unsafe { &*self.block } }
fn write_generic<T>(&mut self, value: T) -> bool {
if !self.is_full() {
// Safety: Only accessed by this instance (unless DMA is used).
unsafe {
let reg_ptr = self.fifo_address() as *mut T;
reg_ptr.write_volatile(value);
}
true
} else {
false
}
} }
/// Gets the FIFO's address. /// Gets the FIFO's address.
@ -1236,7 +1368,8 @@ impl<SM: ValidStateMachine> Tx<SM> {
/// NB: You are responsible for using the pointer correctly and not /// NB: You are responsible for using the pointer correctly and not
/// overflowing the buffer. /// overflowing the buffer.
pub fn fifo_address(&self) -> *const u32 { pub fn fifo_address(&self) -> *const u32 {
self.register_block().txf[SM::id()].as_ptr() // Safety: The only access to this register
unsafe { self.block().txf[SM::id()].as_ptr() }
} }
/// Gets the FIFO's `DREQ` value. /// Gets the FIFO's `DREQ` value.
@ -1255,19 +1388,7 @@ impl<SM: ValidStateMachine> Tx<SM> {
/// ///
/// Returns `true` if the value was written to FIFO, `false` otherwise. /// Returns `true` if the value was written to FIFO, `false` otherwise.
pub fn write(&mut self, value: u32) -> bool { pub fn write(&mut self, value: u32) -> bool {
// Safety: The register is never written by software. self.write_generic(value)
let is_full = self.is_full();
if is_full {
return false;
}
unsafe {
let reg_ptr = self.fifo_address() as *mut u32;
reg_ptr.write_volatile(value);
}
true
} }
/// Write a replicated u8 value to TX FIFO. /// Write a replicated u8 value to TX FIFO.
@ -1289,19 +1410,7 @@ impl<SM: ValidStateMachine> Tx<SM> {
/// ///
/// [section_2_1_4]: <https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#_narrow_io_register_writes> /// [section_2_1_4]: <https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#_narrow_io_register_writes>
pub fn write_u8_replicated(&mut self, value: u8) -> bool { pub fn write_u8_replicated(&mut self, value: u8) -> bool {
// Safety: The register is never written by software. self.write_generic(value)
let is_full = self.is_full();
if is_full {
return false;
}
unsafe {
let reg_ptr = self.fifo_address() as *mut u8;
reg_ptr.write_volatile(value);
}
true
} }
/// Write a replicated 16bit value to TX FIFO. /// Write a replicated 16bit value to TX FIFO.
@ -1323,19 +1432,7 @@ impl<SM: ValidStateMachine> Tx<SM> {
/// ///
/// [section_2_1_4]: <https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#_narrow_io_register_writes> /// [section_2_1_4]: <https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#_narrow_io_register_writes>
pub fn write_u16_replicated(&mut self, value: u16) -> bool { pub fn write_u16_replicated(&mut self, value: u16) -> bool {
// Safety: The register is never written by software. self.write_generic(value)
let is_full = self.is_full();
if is_full {
return false;
}
unsafe {
let reg_ptr = self.fifo_address() as *mut u16;
reg_ptr.write_volatile(value);
}
true
} }
/// Checks if the state machine has stalled on empty TX FIFO during a blocking PULL, or an OUT /// Checks if the state machine has stalled on empty TX FIFO during a blocking PULL, or an OUT
@ -1344,7 +1441,8 @@ impl<SM: ValidStateMachine> Tx<SM> {
/// **Note this is a sticky flag and may not reflect the current state of the machine.** /// **Note this is a sticky flag and may not reflect the current state of the machine.**
pub fn has_stalled(&self) -> bool { pub fn has_stalled(&self) -> bool {
let mask = 1 << SM::id(); let mask = 1 << SM::id();
self.register_block().fdebug.read().txstall().bits() & mask == mask // Safety: read-only access without side-effect
unsafe { self.block().fdebug.read().txstall().bits() & mask == mask }
} }
/// Clears the `tx_stalled` flag. /// Clears the `tx_stalled` flag.
@ -1352,66 +1450,31 @@ impl<SM: ValidStateMachine> Tx<SM> {
let mask = 1 << SM::id(); let mask = 1 << SM::id();
// Safety: These bits are WC, only the one corresponding to this SM is set. // Safety: These bits are WC, only the one corresponding to this SM is set.
self.register_block() unsafe {
.fdebug self.block().fdebug.write(|w| w.txstall().bits(mask));
.write(|w| unsafe { w.txstall().bits(mask) }); }
} }
/// Indicate if the tx FIFO is empty /// Indicate if the tx FIFO is empty
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.register_block().fstat.read().txempty().bits() & (1 << SM::id()) != 0 // Safety: read-only access without side-effect
unsafe { self.block().fstat.read().txempty().bits() & (1 << SM::id()) != 0 }
} }
/// Indicate if the tx FIFO is full /// Indicate if the tx FIFO is full
pub fn is_full(&self) -> bool { pub fn is_full(&self) -> bool {
self.register_block().fstat.read().txfull().bits() & (1 << SM::id()) != 0 // Safety: read-only access without side-effect
} unsafe { self.block().fstat.read().txfull().bits() & (1 << SM::id()) != 0 }
/// Drain Tx fifo.
pub fn drain_fifo(&mut self) {
// According to the datasheet 3.5.4.2 Page 358:
//
// When autopull is enabled, the behaviour of 'PULL' is altered: it becomes a no-op
// if the OSR is full. This is to avoid a race condition against the system
// DMA. It behaves as a fence: either an autopull has already taken place, in which case
// the 'PULL' has no effect, or the program will stall on the 'PULL' until data becomes
// available in the FIFO.
let instr = if self.register_block().sm[SM::id()]
.sm_shiftctrl
.read()
.autopull()
.bit_is_set()
{
pio::InstructionOperands::OUT {
destination: pio::OutDestination::NULL,
bit_count: 32,
}
} else {
pio::InstructionOperands::PULL {
if_empty: false,
block: false,
}
}
.encode();
// Safety: The only other place this register is written is
// `UninitStatemachine.exec_instruction`, `Tx` is only created after init.
let mask = 1 << SM::id();
while self.register_block().fstat.read().txempty().bits() & mask != mask {
self.register_block().sm[SM::id()]
.sm_instr
.write(|w| unsafe { w.sm0_instr().bits(instr) })
}
} }
/// Enable TX FIFO not full interrupt. /// Enable TX FIFO not full interrupt.
/// ///
/// This interrupt is raised when the TX FIFO is not full, i.e. one could push more data to it. /// This interrupt is raised when the TX FIFO is not full, i.e. one could push more data to it.
pub fn enable_tx_not_full_interrupt(&self, id: PioIRQ) { pub fn enable_tx_not_full_interrupt(&self, id: PioIRQ) {
// Safety: Atomic access to the register. Bit only modified by this Tx<SM>
unsafe { unsafe {
write_bitmask_set( write_bitmask_set(
self.register_block().sm_irq[id.to_index()] self.block().sm_irq[id.to_index()].irq_inte.as_ptr(),
.irq_inte
.as_ptr(),
1 << (SM::id() + 4), 1 << (SM::id() + 4),
); );
} }
@ -1419,11 +1482,10 @@ impl<SM: ValidStateMachine> Tx<SM> {
/// Disable TX FIFO not full interrupt. /// Disable TX FIFO not full interrupt.
pub fn disable_tx_not_full_interrupt(&self, id: PioIRQ) { pub fn disable_tx_not_full_interrupt(&self, id: PioIRQ) {
// Safety: Atomic access to the register. Bit only modified by this Tx<SM>
unsafe { unsafe {
write_bitmask_clear( write_bitmask_clear(
self.register_block().sm_irq[id.to_index()] self.block().sm_irq[id.to_index()].irq_inte.as_ptr(),
.irq_inte
.as_ptr(),
1 << (SM::id() + 4), 1 << (SM::id() + 4),
); );
} }
@ -1431,11 +1493,10 @@ impl<SM: ValidStateMachine> Tx<SM> {
/// Force TX FIFO not full interrupt. /// Force TX FIFO not full interrupt.
pub fn force_tx_not_full_interrupt(&self, id: PioIRQ) { pub fn force_tx_not_full_interrupt(&self, id: PioIRQ) {
// Safety: Atomic access to the register. Bit only modified by this Tx<SM>
unsafe { unsafe {
write_bitmask_set( write_bitmask_set(
self.register_block().sm_irq[id.to_index()] self.block().sm_irq[id.to_index()].irq_intf.as_ptr(),
.irq_intf
.as_ptr(),
1 << (SM::id() + 4), 1 << (SM::id() + 4),
); );
} }
@ -1463,6 +1524,7 @@ impl<'a, P: PIOExt, const IRQ: usize> Interrupt<'a, P, IRQ> {
/// does not correspond with the state machine index; any state machine can raise any one of the four interrupts. /// does not correspond with the state machine index; any state machine can raise any one of the four interrupts.
pub fn enable_sm_interrupt(&self, id: u8) { pub fn enable_sm_interrupt(&self, id: u8) {
assert!(id < 4, "invalid state machine interrupt number"); assert!(id < 4, "invalid state machine interrupt number");
// Safety: Atomic write to a single bit owned by this instance
unsafe { unsafe {
write_bitmask_set(self.irq().irq_inte.as_ptr(), 1 << (id + 8)); write_bitmask_set(self.irq().irq_inte.as_ptr(), 1 << (id + 8));
} }
@ -1473,6 +1535,7 @@ impl<'a, P: PIOExt, const IRQ: usize> Interrupt<'a, P, IRQ> {
/// See [`Self::enable_sm_interrupt`] for info about the index. /// See [`Self::enable_sm_interrupt`] for info about the index.
pub fn disable_sm_interrupt(&self, id: u8) { pub fn disable_sm_interrupt(&self, id: u8) {
assert!(id < 4, "invalid state machine interrupt number"); assert!(id < 4, "invalid state machine interrupt number");
// Safety: Atomic write to a single bit owned by this instance
unsafe { unsafe {
write_bitmask_clear(self.irq().irq_inte.as_ptr(), 1 << (id + 8)); write_bitmask_clear(self.irq().irq_inte.as_ptr(), 1 << (id + 8));
} }
@ -1487,6 +1550,7 @@ impl<'a, P: PIOExt, const IRQ: usize> Interrupt<'a, P, IRQ> {
/// See [`Self::enable_sm_interrupt`] for info about the index. /// See [`Self::enable_sm_interrupt`] for info about the index.
pub fn force_sm_interrupt(&self, id: u8, set: bool) { pub fn force_sm_interrupt(&self, id: u8, set: bool) {
assert!(id < 4, "invalid state machine interrupt number"); assert!(id < 4, "invalid state machine interrupt number");
// Safety: Atomic write to a single bit owned by this instance
unsafe { unsafe {
if set { if set {
write_bitmask_set(self.irq().irq_intf.as_ptr(), 1 << (id + 8)); write_bitmask_set(self.irq().irq_intf.as_ptr(), 1 << (id + 8));
@ -1506,6 +1570,7 @@ impl<'a, P: PIOExt, const IRQ: usize> Interrupt<'a, P, IRQ> {
)] )]
pub fn enable_tx_not_full_interrupt(&self, id: u8) { pub fn enable_tx_not_full_interrupt(&self, id: u8) {
assert!(id < 4, "invalid state machine interrupt number"); assert!(id < 4, "invalid state machine interrupt number");
// Safety: Atomic write to a single bit owned by this instance
unsafe { unsafe {
write_bitmask_set(self.irq().irq_inte.as_ptr(), 1 << (id + 4)); write_bitmask_set(self.irq().irq_inte.as_ptr(), 1 << (id + 4));
} }
@ -1520,6 +1585,7 @@ impl<'a, P: PIOExt, const IRQ: usize> Interrupt<'a, P, IRQ> {
)] )]
pub fn disable_tx_not_full_interrupt(&self, id: u8) { pub fn disable_tx_not_full_interrupt(&self, id: u8) {
assert!(id < 4, "invalid state machine interrupt number"); assert!(id < 4, "invalid state machine interrupt number");
// Safety: Atomic write to a single bit owned by this instance
unsafe { unsafe {
write_bitmask_clear(self.irq().irq_inte.as_ptr(), 1 << (id + 4)); write_bitmask_clear(self.irq().irq_inte.as_ptr(), 1 << (id + 4));
} }
@ -1534,6 +1600,7 @@ impl<'a, P: PIOExt, const IRQ: usize> Interrupt<'a, P, IRQ> {
)] )]
pub fn force_tx_not_full_interrupt(&self, id: u8) { pub fn force_tx_not_full_interrupt(&self, id: u8) {
assert!(id < 4, "invalid state machine interrupt number"); assert!(id < 4, "invalid state machine interrupt number");
// Safety: Atomic write to a single bit owned by this instance
unsafe { unsafe {
write_bitmask_set(self.irq().irq_intf.as_ptr(), 1 << (id + 4)); write_bitmask_set(self.irq().irq_intf.as_ptr(), 1 << (id + 4));
} }
@ -1549,6 +1616,7 @@ impl<'a, P: PIOExt, const IRQ: usize> Interrupt<'a, P, IRQ> {
)] )]
pub fn enable_rx_not_empty_interrupt(&self, id: u8) { pub fn enable_rx_not_empty_interrupt(&self, id: u8) {
assert!(id < 4, "invalid state machine interrupt number"); assert!(id < 4, "invalid state machine interrupt number");
// Safety: Atomic write to a single bit owned by this instance
unsafe { unsafe {
write_bitmask_set(self.irq().irq_inte.as_ptr(), 1 << id); write_bitmask_set(self.irq().irq_inte.as_ptr(), 1 << id);
} }
@ -1563,6 +1631,7 @@ impl<'a, P: PIOExt, const IRQ: usize> Interrupt<'a, P, IRQ> {
)] )]
pub fn disable_rx_not_empty_interrupt(&self, id: u8) { pub fn disable_rx_not_empty_interrupt(&self, id: u8) {
assert!(id < 4, "invalid state machine interrupt number"); assert!(id < 4, "invalid state machine interrupt number");
// Safety: Atomic write to a single bit owned by this instance
unsafe { unsafe {
write_bitmask_clear(self.irq().irq_inte.as_ptr(), 1 << id); write_bitmask_clear(self.irq().irq_inte.as_ptr(), 1 << id);
} }
@ -1577,6 +1646,7 @@ impl<'a, P: PIOExt, const IRQ: usize> Interrupt<'a, P, IRQ> {
)] )]
pub fn force_rx_not_empty_interrupt(&self, id: u8) { pub fn force_rx_not_empty_interrupt(&self, id: u8) {
assert!(id < 4, "invalid state machine interrupt number"); assert!(id < 4, "invalid state machine interrupt number");
// Safety: Atomic write to a single bit owned by this instance
unsafe { unsafe {
write_bitmask_set(self.irq().irq_intf.as_ptr(), 1 << id); write_bitmask_set(self.irq().irq_intf.as_ptr(), 1 << id);
} }
@ -1586,22 +1656,28 @@ impl<'a, P: PIOExt, const IRQ: usize> Interrupt<'a, P, IRQ> {
/// ///
/// This is the state of the interrupts without interrupt masking and forcing. /// This is the state of the interrupts without interrupt masking and forcing.
pub fn raw(&self) -> InterruptState { pub fn raw(&self) -> InterruptState {
InterruptState(self.register_block().intr.read().bits()) InterruptState(
// Safety: Read only access without side effect
unsafe { self.block().intr.read().bits() },
)
} }
/// Get the interrupt state. /// Get the interrupt state.
/// ///
/// This is the state of the interrupts after interrupt masking and forcing. /// This is the state of the interrupts after interrupt masking and forcing.
pub fn state(&self) -> InterruptState { pub fn state(&self) -> InterruptState {
InterruptState(self.irq().irq_ints.read().bits()) InterruptState(
// Safety: Read only access without side effect
unsafe { self.irq().irq_ints.read().bits() },
)
} }
fn register_block(&self) -> &rp2040_pac::pio0::RegisterBlock { unsafe fn block(&self) -> &rp2040_pac::pio0::RegisterBlock {
unsafe { &*self.block } &*self.block
} }
fn irq(&self) -> &rp2040_pac::pio0::SM_IRQ { unsafe fn irq(&self) -> &rp2040_pac::pio0::SM_IRQ {
&self.register_block().sm_irq[IRQ] &self.block().sm_irq[IRQ]
} }
} }
@ -1917,74 +1993,61 @@ impl<P: PIOExt> PIOBuilder<P> {
// Write all configuration bits // Write all configuration bits
sm.set_clock_divisor(self.clock_divisor.0, self.clock_divisor.1); sm.set_clock_divisor(self.clock_divisor.0, self.clock_divisor.1);
sm.sm().sm_execctrl.write(|w| { // Safety: Only instance owning the SM
w.side_en().bit(self.program.side_set.optional()); unsafe {
w.side_pindir().bit(self.program.side_set.pindirs()); sm.sm().sm_execctrl.write(|w| {
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); w.jmp_pin().bits(self.jmp_pin);
}
if let Some(inline_out) = self.inline_out { if let Some(inline_out) = self.inline_out {
w.inline_out_en().bit(true); w.inline_out_en().bit(true);
unsafe {
w.out_en_sel().bits(inline_out); w.out_en_sel().bits(inline_out);
} else {
w.inline_out_en().bit(false);
} }
} else {
w.inline_out_en().bit(false);
}
w.out_sticky().bit(self.out_sticky); w.out_sticky().bit(self.out_sticky);
unsafe {
w.wrap_top().bits(offset as u8 + self.program.wrap.source); w.wrap_top().bits(offset as u8 + self.program.wrap.source);
w.wrap_bottom() w.wrap_bottom()
.bits(offset as u8 + self.program.wrap.target); .bits(offset as u8 + self.program.wrap.target);
}
let n = match self.mov_status { let n = match self.mov_status {
MovStatusConfig::Tx(n) => { MovStatusConfig::Tx(n) => {
w.status_sel().bit(false); w.status_sel().bit(false);
n n
} }
MovStatusConfig::Rx(n) => { MovStatusConfig::Rx(n) => {
w.status_sel().bit(true); w.status_sel().bit(true);
n n
} }
}; };
unsafe { w.status_n().bits(n)
w.status_n().bits(n); });
}
w sm.sm().sm_shiftctrl.write(|w| {
}); let (fjoin_rx, fjoin_tx) = match self.fifo_join {
Buffers::RxTx => (false, false),
Buffers::OnlyTx => (false, true),
Buffers::OnlyRx => (true, false),
};
w.fjoin_rx().bit(fjoin_rx);
w.fjoin_tx().bit(fjoin_tx);
sm.sm().sm_shiftctrl.write(|w| {
let (fjoin_rx, fjoin_tx) = match self.fifo_join {
Buffers::RxTx => (false, false),
Buffers::OnlyTx => (false, true),
Buffers::OnlyRx => (true, false),
};
w.fjoin_rx().bit(fjoin_rx);
w.fjoin_tx().bit(fjoin_tx);
unsafe {
// TODO: Encode 32 as zero, and error on 0 // TODO: Encode 32 as zero, and error on 0
w.pull_thresh().bits(self.pull_threshold); w.pull_thresh().bits(self.pull_threshold);
w.push_thresh().bits(self.push_threshold); w.push_thresh().bits(self.push_threshold);
}
w.out_shiftdir().bit(self.out_shiftdir.bit()); w.out_shiftdir().bit(self.out_shiftdir.bit());
w.in_shiftdir().bit(self.in_shiftdir.bit()); w.in_shiftdir().bit(self.in_shiftdir.bit());
w.autopull().bit(self.autopull); w.autopull().bit(self.autopull);
w.autopush().bit(self.autopush); w.autopush().bit(self.autopush)
});
w sm.sm().sm_pinctrl.write(|w| {
});
sm.sm().sm_pinctrl.write(|w| {
unsafe {
w.sideset_count().bits(self.program.side_set.bits()); w.sideset_count().bits(self.program.side_set.bits());
w.set_count().bits(self.set_count); w.set_count().bits(self.set_count);
w.out_count().bits(self.out_count); w.out_count().bits(self.out_count);
@ -1992,11 +2055,9 @@ impl<P: PIOExt> PIOBuilder<P> {
w.in_base().bits(self.in_base); w.in_base().bits(self.in_base);
w.sideset_base().bits(self.side_set_base); w.sideset_base().bits(self.side_set_base);
w.set_base().bits(self.set_base); w.set_base().bits(self.set_base);
w.out_base().bits(self.out_base); w.out_base().bits(self.out_base)
} })
}
w
});
// Restart SM and its clock // Restart SM and its clock
sm.restart(); sm.restart();
@ -2004,13 +2065,15 @@ impl<P: PIOExt> PIOBuilder<P> {
// Set starting location by forcing the state machine to execute a jmp // Set starting location by forcing the state machine to execute a jmp
// to the beginning of the program we loaded in. // to the beginning of the program we loaded in.
sm.exec_instruction( let instr = InstructionOperands::JMP {
pio::InstructionOperands::JMP { condition: pio::JmpCondition::Always,
condition: pio::JmpCondition::Always, address: offset as u8,
address: offset as u8, }
} .encode();
.encode(), // Safety: Only instance owning the SM
); unsafe {
sm.sm().sm_instr.write(|w| w.sm0_instr().bits(instr));
}
let rx = Rx { let rx = Rx {
block: sm.block, block: sm.block,