-
Notifications
You must be signed in to change notification settings - Fork 35
Overhauled API design #15
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
Conversation
Signed-off-by: Rahix <[email protected]>
This commit marks the start of a complete overhaul of this crate. Here, only a minimal subset of the functionality has been re-implemented; the rest will follow in the next changesets. The major changes up to here are: - Redesign of the `BusMutex` trait. The lock function now hands out &mut references which makes the previous RefCell in the BusManager superfluous. Additionally, the generic parameter was moved into an associated type which makes use much easier. - Split out proxy types so each bus can have its own. This allows enforcing bus-specific invariants. For example, SPI proxies MUST NOT be shared across threads/tasks. Signed-off-by: Rahix <[email protected]>
Add a shared_bus::new_std!() macro for instanciating a bus-manager with 'static lifetime. Signed-off-by: Rahix <[email protected]>
Signed-off-by: Rahix <[email protected]>
Add a 'dummy' mutex type for sharing within a single thread/task. This mutex is `!Sync` to uphold this safety guarantee. Signed-off-by: Rahix <[email protected]>
Add back the SPI proxy. This time it upholds the required constraints to work around the race-condition caused by externally managed CS pins. To do this, a couple of changes have been added: - SPI proxies can only be acquired from BusManager's using the NullMutex. This, by definition, enforces single-threaded use. - The SpiProxy contains a marker that forces it !Send and !Sync. Signed-off-by: Rahix <[email protected]>
Signed-off-by: Rahix <[email protected]>
Implement BusMutex for the cortex-m mutex type and add a BusManagerCortexM type alias for convenience. Additionally, add a new_cortexm!() macro to make creating of a static bus manager easier. Signed-off-by: Rahix <[email protected]>
I need feedback on this from people who are using @ryankurte: Re #13, can you try using the Lines 94 to 117 in a0e52c3
Does this work for you? |
Documentation is still missing in this version, but as an example, this is how a bus could be shared between an interrupt and thread mode: #![no_std]
#![no_main]
// use panic_halt as _;
use panic_semihosting as _;
use stm32f3_discovery::stm32f3xx_hal;
use stm32f3_discovery::stm32f3xx_hal::interrupt;
use stm32f3_discovery::stm32f3xx_hal::prelude::*;
use stm32f3_discovery::stm32f3xx_hal::stm32;
use stm32f3_discovery::button;
use cortex_m_semihosting::hprintln;
type I2cBus = stm32f3xx_hal::i2c::I2c<
stm32::I2C1,
(
stm32f3xx_hal::gpio::gpiob::PB6<stm32f3xx_hal::gpio::AF4>,
stm32f3xx_hal::gpio::gpiob::PB7<stm32f3xx_hal::gpio::AF4>,
),
>;
static mut RIGHT: core::mem::MaybeUninit<
pcf857x::Pcf8574a<shared_bus::I2cProxy<shared_bus::CortexMMutex<I2cBus>>>,
> = core::mem::MaybeUninit::uninit();
#[cortex_m_rt::entry]
fn main() -> ! {
let device_periphs = stm32::Peripherals::take().unwrap();
let mut reset_and_clock_control = device_periphs.RCC.constrain();
let core_periphs = cortex_m::Peripherals::take().unwrap();
let mut flash = device_periphs.FLASH.constrain();
let clocks = reset_and_clock_control.cfgr.freeze(&mut flash.acr);
hprintln!("Initializing ...").unwrap();
// initialize I2C
let mut gpiob = device_periphs.GPIOB.split(&mut reset_and_clock_control.ahb);
let scl = gpiob.pb6.into_af4(&mut gpiob.moder, &mut gpiob.afrl);
let sda = gpiob.pb7.into_af4(&mut gpiob.moder, &mut gpiob.afrl);
let i2c = stm32f3xx_hal::i2c::I2c::i2c1(
device_periphs.I2C1,
(scl, sda),
100.khz(),
clocks,
&mut reset_and_clock_control.apb1,
);
// let bus = shared_bus::BusManagerSimple::new(i2c);
let bus = shared_bus::new_cortexm!(I2cBus = i2c).unwrap();
let mut lsm303dhlc = stm32f3_discovery::lsm303dlhc::Lsm303dlhc::new(bus.acquire_i2c()).unwrap();
let mut left = pcf857x::Pcf8574a::new(
bus.acquire_i2c(),
pcf857x::SlaveAddr::Alternative(false, false, true),
);
let mut right = pcf857x::Pcf8574a::new(bus.acquire_i2c(), pcf857x::SlaveAddr::Default);
left.set(0xff).unwrap();
right.set(0xff).unwrap();
unsafe {
RIGHT = core::mem::MaybeUninit::new(right);
}
cortex_m::asm::dmb();
button::interrupt::enable(
&device_periphs.EXTI,
&device_periphs.SYSCFG,
button::interrupt::TriggerMode::Rising,
);
hprintln!("Done!").unwrap();
loop {
let accel = lsm303dhlc.accel().unwrap();
left.set((accel.z >> 8) as u8).unwrap();
}
}
#[interrupt]
fn EXTI0() {
static mut COUNTER: u8 = 0;
//If we don't clear the interrupt to signal it's been serviced, it will continue to fire.
button::interrupt::clear();
let right = unsafe { &mut *RIGHT.as_mut_ptr() };
right.set(!*COUNTER).unwrap();
*COUNTER = COUNTER.wrapping_add(1);
} (the unsafe code is only needed for moving the device to interrupt context) |
Signed-off-by: Rahix <[email protected]>
Signed-off-by: Rahix <[email protected]>
Signed-off-by: Rahix <[email protected]>
Signed-off-by: Rahix <[email protected]>
Signed-off-by: Rahix <[email protected]>
I've released an alpha version of the current state! The documentation is here: https://docs.rs/shared-bus/0.2.0-alpha.1/shared_bus/ I'd be grateful for feedback whether this new design works well. |
I've tested |
hey looks good! i'll try have a go with it but, not entirely sure when this might be. is it worth documenting the mutex interactions for the listed example? i think because we're always within a |
@Rahix I really like this redesign - I think it's going to fit all of my use cases quite well, thanks for investing the time into this! I would also propose one additional type of mutex that sits in between the "NullMutex" and a full cirtical-section "CortexM Mutex" - specifically, in cases in RTIC where multiple peripherals on the same bus are contained in the same object (e.g. a containing struct) such that RTIC manages resource contention inherently, it would be nice to have a This is just a thought for inclusion into the library and I would be happy to PR it separately as well (or keep it entirely external and just use it in my internal projects, since we can impl custom external mutexes), but thought it would be worth mentioning. In these cases, we can rely on RTIC to do all the managing work and just want shared-bus to manage references and enforce usage constraints. Edit: I'm going to be incorporating the alpha release into a current project and will report back on success. Edit-2: I just tried out the cortex-m mutex and it worked gloriously. Will attempt working with my own custom mutex next. Edit-3: I have now tested using a custom mutex imp and it's suiting my needs as well. This is what I describe abovel: pub struct AtomicCheckMutex<BUS> {
bus: core::cell::UnsafeCell<BUS>,
busy: core::sync::atomic::AtomicBool,
}
impl<BUS> shared_bus::BusMutex for AtomicCheckMutex<BUS> {
type Bus = BUS;
fn create(v: BUS) -> Self {
Self {
bus: core::cell::UnsafeCell::new(v),
busy: core::sync::atomic::AtomicBool::from(false)
}
}
fn lock<R, F: FnOnce(&mut Self::Bus) -> R>(&self, f: F) -> R {
self.busy.compare_exchange(
false,
true,
core::sync::atomic::Ordering::SeqCst,
core::sync::atomic::Ordering::SeqCst)
.expect("Bus conflict");
let result = f(unsafe { &mut *self.bus.get() });
self.busy.store(false, core::sync::atomic::Ordering::SeqCst);
result
}
}
unsafe impl<BUS> Sync for AtomicCheckMutex<BUS> {}
type AtomicCheckManager<T> = shared_bus::BusManager<AtomicCheckMutex<T>>;
macro_rules! new_atomic_check_manager {
($bus_type:ty = $bus:expr) => {{
let m: Option<&'static mut _> = cortex_m::singleton!(
: AtomicCheckManager<$bus_type> =
AtomicCheckManager::new($bus)
);
m
}};
} |
Thanks a lot for the feedback everyone!
@ryankurte: I'm not quite sure what you mean? The mutex will only dead-lock when the I2C implementation locks up, so it's neither better nor worse than without
@ryan-summers: We can surely add such a mutex type as well! I'm all in for compatibility with the widest possible range of use-cases in upstream. Ideally, no one should need to maintain mutex-impls outside of |
it might be me missing the point too tbh, and this is somewhat irrelevant because this PR is a net improvement / shouldn't block this at all, but, what is the behaviour if the mutex is taken in the main task, then an interrupt occurs and the interrupt attempts to take the mutex? this cannot happen with the current implementation within for example, the |
I think the problem here is that "mutex" in this case is a bit of a misnomer. As opposed to a true mutex that is acquired and released to denote ownership of a resource, the "mutexes" in this crate simply ensure that pre-emption of the task using the bus is not possible by another task that is using the bus. That's why there's no formal acquire/release mechanism. |
This PR serves to track progress of the new implementation of
shared-bus
. See #14 for the goals that this archieves to implement.This version is also available for tesing on crates.io as
0.2.0-alpha.1
. The documentation is here: https://docs.rs/shared-bus/0.2.0-alpha.1/shared_bus/I'd be grateful for feedback whether this new design works well.