Skip to content

Commit

Permalink
Merge #367
Browse files Browse the repository at this point in the history
367: GPIO driver r=hudson-ayers a=alexandruradovici

This PR implements an initial version of the GPIO driver.

# Roadmap:
 - [x] implement pin controls (input / output)
 - [x] ~implement iterator~
 - [x] implement interrupts
 
# Iterator Implementation
I am not quite sure how to implement the iterator in such a way as to prevent its multiple instantiation. The previous version of libtock was using the driver factory idea and used mutability and lifetimes to prevent this. As the current driver model is using associated functions instead of methods, this does not seem possible. 

```rust
impl<S: Syscalls> Gpio<S> {
    pub fn gpios() -> Gpios<'static, S> {
        let num_gpios = S::command(DRIVER_ID, GPIO_COUNT, 0, 0)
            .get_success_u32()
            .unwrap_or_default() as usize;
        Gpios {
            num_gpios,
            current_gpio: 0,
            _syscalls: &PhantomData,
        }
    }
}
```

One idea is to use runtime checks and a mutable static, but this (I think) implies unsafe code. Any suggestions are very much welcome.

# Usage Examples

## Output

```rust
let gpios = Gpio::gpios();
let output_pin: Output<...> = gpios.nth(i)?.into();
output_pin.set();
```

## Input
```rust
let gpios = Gpio::gpios();
let mut input_pin: Input<PullUp> = gpios.nth(i)?.into();
input_pin.read();
```


Co-authored-by: Alexandru Radovici <[email protected]>
Co-authored-by: Alexandru Radovici <[email protected]>
  • Loading branch information
3 people authored Mar 11, 2022
2 parents 9248bbf + 3fb46cd commit d575835
Show file tree
Hide file tree
Showing 7 changed files with 1,237 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ debug = true
[workspace]
exclude = ["tock"]
members = [
"apis/gpio",
"apis/buttons",
"apis/leds",
"apis/low_level_debug",
Expand Down
14 changes: 14 additions & 0 deletions apis/gpio/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "libtock_gpio"
version = "0.1.0"
authors = ["Tock Project Developers <[email protected]>"]
license = "MIT/Apache-2.0"
edition = "2018"
repository = "https://www.github.com/tock/libtock-rs"
description = "libtock gpio driver"

[dependencies]
libtock_platform = { path = "../../platform" }

[dev-dependencies]
libtock_unittest = { path = "../../unittest" }
255 changes: 255 additions & 0 deletions apis/gpio/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
#![no_std]

use core::marker::PhantomData;

use libtock_platform::{
share::Handle, subscribe::OneId, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall,
};

/// The Gpio driver
///
/// # Example
/// ```ignore
/// use libtock2::Gpios;
///
/// // Turn on led 0
/// let pin = Gpios::get_pin(0)?;
///
/// ```

#[derive(Copy, Clone, PartialEq, Debug)]
pub enum GpioState {
Low = 0,
High = 1,
}

pub enum PinInterruptEdge {
Either = 0,
Rising = 1,
Falling = 2,
}

pub enum Error {
Invalid,
Failed,
}

pub trait Pull {
const MODE: u32;
}

pub struct PullUp;
impl Pull for PullUp {
const MODE: u32 = 1;
}

pub struct PullDown;
impl Pull for PullDown {
const MODE: u32 = 2;
}

pub struct PullNone;
impl Pull for PullNone {
const MODE: u32 = 0;
}

pub struct Gpio<S: Syscalls>(S);

impl<S: Syscalls> Gpio<S> {
/// Run a check against the gpio capsule to ensure it is present.
///
/// Returns true` if the driver was present. This does not necessarily mean
/// that the driver is working, as it may still fail to allocate grant
/// memory.
pub fn count() -> Result<u32, ErrorCode> {
S::command(DRIVER_NUM, GPIO_COUNT, 0, 0).to_result()
}

pub fn get_pin(pin: u32) -> Result<Pin<S>, ErrorCode> {
Self::disable(pin)?;
Ok(Pin {
pin_number: pin,
_syscalls: PhantomData,
})
}

/// Register an interrupt listener
///
/// There can be only one single listener registered at a time.
/// Each time this function is used, it will replace the
/// previously registered listener.
pub fn register_listener<'share, F: Fn(u32, GpioState)>(
listener: &'share GpioInterruptListener<F>,
subscribe: Handle<Subscribe<'share, S, DRIVER_NUM, 0>>,
) -> Result<(), ErrorCode> {
S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener)
}

/// Unregister the interrupt listener
///
/// This function may be used even if there was no
/// previously registered listener.
pub fn unregister_listener() {
S::unsubscribe(DRIVER_NUM, 0)
}
}

/// A wrapper around a closure to be registered and called when
/// a gpio interrupt occurs.
///
/// ```ignore
/// let listener = GpioInterruptListener(|gpio, interrupt_edge| {
/// // make use of the button's state
/// });
/// ```
pub struct GpioInterruptListener<F: Fn(u32, GpioState)>(pub F);

impl<F: Fn(u32, GpioState)> Upcall<OneId<DRIVER_NUM, 0>> for GpioInterruptListener<F> {
fn upcall(&self, gpio_index: u32, value: u32, _arg2: u32) {
self.0(gpio_index, value.into())
}
}

impl From<u32> for GpioState {
fn from(original: u32) -> GpioState {
match original {
0 => GpioState::Low,
_ => GpioState::High,
}
}
}

pub struct Pin<S: Syscalls> {
pin_number: u32,
_syscalls: PhantomData<S>,
}

impl<S: Syscalls> Pin<S> {
pub fn make_output(&mut self) -> Result<OutputPin<S>, ErrorCode> {
Gpio::<S>::enable_gpio_output(self.pin_number)?;
Ok(OutputPin { pin: self })
}

pub fn make_input<P: Pull>(&self) -> Result<InputPin<S, P>, ErrorCode> {
Gpio::<S>::enable_gpio_input(self.pin_number, P::MODE)?;
Ok(InputPin {
pin: self,
_pull: PhantomData,
})
}
}

pub struct OutputPin<'a, S: Syscalls> {
pin: &'a Pin<S>,
}

impl<'a, S: Syscalls> OutputPin<'a, S> {
pub fn toggle(&mut self) -> Result<(), ErrorCode> {
Gpio::<S>::toggle(self.pin.pin_number)
}
pub fn set(&mut self) -> Result<(), ErrorCode> {
Gpio::<S>::write(self.pin.pin_number, GpioState::High)
}
pub fn clear(&mut self) -> Result<(), ErrorCode> {
Gpio::<S>::write(self.pin.pin_number, GpioState::Low)
}
}

pub struct InputPin<'a, S: Syscalls, P: Pull> {
pin: &'a Pin<S>,
_pull: PhantomData<P>,
}

impl<'a, S: Syscalls, P: Pull> InputPin<'a, S, P> {
pub fn read(&self) -> Result<GpioState, ErrorCode> {
Gpio::<S>::read(self.pin.pin_number)
}

pub fn enable_interrupts(&self, edge: PinInterruptEdge) -> Result<(), ErrorCode> {
Gpio::<S>::enable_interrupts(self.pin.pin_number, edge)
}

pub fn disable_interrupts(&self) -> Result<(), ErrorCode> {
Gpio::<S>::disable_interrupts(self.pin.pin_number)
}
}

impl<S: Syscalls> Drop for OutputPin<'_, S> {
fn drop(&mut self) {
let _ = Gpio::<S>::disable(self.pin.pin_number);
}
}

impl<S: Syscalls, P: Pull> Drop for InputPin<'_, S, P> {
fn drop(&mut self) {
let _ = Gpio::<S>::disable(self.pin.pin_number);
}
}

// -----------------------------------------------------------------------------
// Implementation details below
// -----------------------------------------------------------------------------

impl<S: Syscalls> Gpio<S> {
fn enable_gpio_output(pin: u32) -> Result<(), ErrorCode> {
S::command(DRIVER_NUM, GPIO_ENABLE_OUTPUT, pin, 0).to_result()
}

fn enable_gpio_input(pin: u32, mode: u32) -> Result<(), ErrorCode> {
S::command(DRIVER_NUM, GPIO_ENABLE_INPUT, pin, mode).to_result()
}

fn write(pin: u32, state: GpioState) -> Result<(), ErrorCode> {
let action = match state {
GpioState::Low => GPIO_CLEAR,
_ => GPIO_SET,
};
S::command(DRIVER_NUM, action, pin, 0).to_result()
}

fn read(pin: u32) -> Result<GpioState, ErrorCode> {
let pin_state: u32 = S::command(DRIVER_NUM, GPIO_READ_INPUT, pin, 0).to_result()?;
Ok(pin_state.into())
}

fn toggle(pin: u32) -> Result<(), ErrorCode> {
S::command(DRIVER_NUM, GPIO_TOGGLE, pin, 0).to_result()
}

fn disable(pin: u32) -> Result<(), ErrorCode> {
S::command(DRIVER_NUM, GPIO_DISABLE, pin, 0).to_result()
}

fn enable_interrupts(pin: u32, edge: PinInterruptEdge) -> Result<(), ErrorCode> {
S::command(DRIVER_NUM, GPIO_ENABLE_INTERRUPTS, pin, edge as u32).to_result()
}

fn disable_interrupts(pin: u32) -> Result<(), ErrorCode> {
S::command(DRIVER_NUM, GPIO_DISABLE_INTERRUPTS, pin, 0).to_result()
}
}

#[cfg(test)]
mod tests;

// -----------------------------------------------------------------------------
// Driver number and command IDs
// -----------------------------------------------------------------------------

const DRIVER_NUM: u32 = 4;

// Command IDs
const GPIO_COUNT: u32 = 0;

const GPIO_ENABLE_OUTPUT: u32 = 1;
const GPIO_SET: u32 = 2;
const GPIO_CLEAR: u32 = 3;
const GPIO_TOGGLE: u32 = 4;

const GPIO_ENABLE_INPUT: u32 = 5;
const GPIO_READ_INPUT: u32 = 6;

const GPIO_ENABLE_INTERRUPTS: u32 = 7;
const GPIO_DISABLE_INTERRUPTS: u32 = 8;

const GPIO_DISABLE: u32 = 9;
Loading

0 comments on commit d575835

Please sign in to comment.