Merge #131

131: Better subscribe API r=Woyten a=Woyten

This PR improves the usability of the `subscribe` API. The current API has the following problems:

- It is impossible to implement `SubscribableCallback` for different `FnMut` traits. This is the reason why the odd `WithCallback` helper objects were introduced.
- `SubscribableCallback` is crate private by accident. As a result, it is hard to reason what types actually implement `SubscribableCallback`

Besides that, the following APIs have been made more consistent:
- LEDs
- Buttons
- GPIO
- Temperature

Co-authored-by: Woyten <woyten.tielesch@online.de>
diff --git a/.travis.yml b/.travis.yml
index d3547db..651ad97 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,7 +18,7 @@
 install:
   - rustup target add thumbv7em-none-eabi
   - rustup target add riscv32imc-unknown-none-elf
-  - rustup component add rustfmt-preview
+  - rustup component add rustfmt
   - rustup component add clippy
 
 script:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d15206f..0ab5997 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,23 +2,41 @@
 
 ## 0.2.0 (WIP)
 
+### Comprehensive Changes
+
 - Many functions are asynchronous
-  - To make create an `async` main function you can use the attribute `#[libtock::main]`
+  - To create an `async` main function you can use the attribute `#[libtock::main]`
   - To retrieve the value of an asynchronous `value`, use `value.await`
   - This is only possible within an `async fn`, so either
     - Make the caller `fn` of `.await` an `async fn`
     - Not recommended: Use `core::executor::block_on(value)` to retrieve the `value`
+- Most API functions, including `main()`, return a `Result<T, TockError>`
+- All drivers can exclusively be retrieved by `retrieve_drivers` which returns a `Drivers` singleton. Drivers can be shared between different tasks only if it is safe to do so.
+
+### Changed APIs
+
+- The basic APIs have been made consistent. They are initialized via driver factories and no longer require a `WithCallback` object, s.t. the callback subscription is more intuitive. The affected APIs are:
+  - LEDs
+  - Buttons
+  - GPIO
+  - Temperature
+  - ADC (partially)
+- The timer API now supports concurrent sleep operations
+
+### Syscalls
+
+- `syscalls::subscribe` is actually usable
 - `syscalls::yieldk_for` is no longer available
   - Yielding manually is discouraged as it conflicts with Rust's safety guarantees. If you need to wait for a condition, use `futures::wait_until` and `.await`.
 - `syscalls::yieldk` has become `unsafe` for the same reason
-- Commands are no longer `unsafe`
+- `syscalls::command` is no longer `unsafe`
 - The low-level syscalls have been moved to `syscalls::raw`
   - `syscalls::subscribe_ptr` becomes `syscalls::raw::subscribe`
   - `syscalls::allow_ptr` becomes `syscalls::raw::allow`
+
+### Miscellaneous
+
 - Targets without support for atomics can be built
-- Most API functions, including `main()`, return a `Result<T, TockError>`
-- The library now supports parallel timers
-- all drivers can exclusively be retrieved by `retrieve_drivers` which returns a `Drivers`-singleton. Drivers can be shared between different tasks only if it is safe to do so.
 
 ## a8bb4fa9be504517d5533511fd8e607ea61f1750 (0.1.0)
 
diff --git a/examples/adc.rs b/examples/adc.rs
index ef100a3..711176e 100644
--- a/examples/adc.rs
+++ b/examples/adc.rs
@@ -3,28 +3,24 @@
 use core::fmt::Write;
 use libtock::result::TockResult;
 use libtock::timer::Duration;
-use libtock::Drivers;
 
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let Drivers {
-        console_driver,
-        timer_context,
-        adc_driver,
-        ..
-    } = libtock::retrieve_drivers()?;
+    let mut drivers = libtock::retrieve_drivers()?;
 
-    let mut driver = timer_context.create_timer_driver();
-    let timer_driver = driver.activate()?;
-    let mut console = console_driver.create_console();
-    let mut with_callback = adc_driver.with_callback(|channel: usize, value: usize| {
+    let adc_driver = drivers.adc.init_driver()?;
+    let mut timer_driver = drivers.timer.create_timer_driver();
+    let timer_driver = timer_driver.activate()?;
+    let mut console = drivers.console.create_console();
+
+    let mut callback = |channel, value| {
         writeln!(console, "channel: {}, value: {}", channel, value).unwrap();
-    });
+    };
 
-    let adc = with_callback.init()?;
+    let _subscription = adc_driver.subscribe(&mut callback)?;
 
     loop {
-        adc.sample(0)?;
+        adc_driver.sample(0)?;
         timer_driver.sleep(Duration::from_ms(2000)).await?;
     }
 }
diff --git a/examples/adc_buffer.rs b/examples/adc_buffer.rs
index b948b93..86f94b2 100644
--- a/examples/adc_buffer.rs
+++ b/examples/adc_buffer.rs
@@ -4,31 +4,29 @@
 use libtock::adc::AdcBuffer;
 use libtock::result::TockResult;
 use libtock::syscalls;
-use libtock::Drivers;
 
 #[libtock::main]
 /// Reads a 128 byte sample into a buffer and prints the first value to the console.
 async fn main() -> TockResult<()> {
-    let Drivers {
-        console_driver,
-        adc_driver,
-        ..
-    } = libtock::retrieve_drivers()?;
-    let mut console = console_driver.create_console();
+    let mut drivers = libtock::retrieve_drivers()?;
+
+    let adc_driver = drivers.adc.init_driver()?;
+    let mut console = drivers.console.create_console();
+
     let mut adc_buffer = AdcBuffer::default();
     let mut temp_buffer = [0; libtock::adc::BUFFER_SIZE];
 
-    let adc_buffer = libtock::adc::Adc::init_buffer(&mut adc_buffer)?;
+    let adc_buffer = adc_driver.init_buffer(&mut adc_buffer)?;
 
-    let mut with_callback = adc_driver.with_callback(|_, _| {
+    let mut callback = |_, _| {
         adc_buffer.read_bytes(&mut temp_buffer[..]);
         writeln!(console, "First sample in buffer: {}", temp_buffer[0]).unwrap();
-    });
+    };
 
-    let adc = with_callback.init()?;
+    let _subscription = adc_driver.subscribe(&mut callback)?;
 
     loop {
-        adc.sample_continuous_buffered(0, 128)?;
+        adc_driver.sample_continuous_buffered(0, 128)?;
         unsafe { syscalls::raw::yieldk() };
     }
 }
diff --git a/examples/alloc_error.rs b/examples/alloc_error.rs
new file mode 100644
index 0000000..ec42b5a
--- /dev/null
+++ b/examples/alloc_error.rs
@@ -0,0 +1,16 @@
+// Triggers the out-of-memory handler. Should make all LEDs cycle.
+
+#![no_std]
+
+extern crate alloc;
+
+use alloc::vec::Vec;
+use libtock::result::TockResult;
+
+#[libtock::main]
+fn main() -> TockResult<()> {
+    let mut vec = Vec::new();
+    loop {
+        vec.push(0);
+    }
+}
diff --git a/examples/ble_scanning.rs b/examples/ble_scanning.rs
index a00ed46..bbbe76a 100644
--- a/examples/ble_scanning.rs
+++ b/examples/ble_scanning.rs
@@ -6,7 +6,6 @@
 use libtock::simple_ble;
 use libtock::simple_ble::BleCallback;
 use libtock::simple_ble::BleScanningDriver;
-use libtock::Drivers;
 use serde::Deserialize;
 
 #[derive(Deserialize)]
@@ -17,17 +16,13 @@
 
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let Drivers {
-        led_driver_factory,
-        mut ble_scanning_driver,
-        ..
-    } = libtock::retrieve_drivers()?;
+    let mut drivers = libtock::retrieve_drivers()?;
 
-    let led_driver = led_driver_factory.create_driver()?;
+    let leds_driver = drivers.leds.init_driver()?;
 
     let mut shared_buffer = BleScanningDriver::create_scan_buffer();
     let mut my_buffer = BleScanningDriver::create_scan_buffer();
-    let shared_memory = ble_scanning_driver.share_memory(&mut shared_buffer)?;
+    let shared_memory = drivers.ble_scanning.share_memory(&mut shared_buffer)?;
 
     let mut callback = BleCallback::new(|_: usize, _: usize| {
         shared_memory.read_bytes(&mut my_buffer[..]);
@@ -35,13 +30,14 @@
             .and_then(|service_data| ble_parser::extract_for_service([91, 79], service_data))
             .and_then(|payload| corepack::from_bytes::<LedCommand>(&payload).ok())
             .and_then(|msg| {
-                led_driver
+                leds_driver
                     .get(msg.nr as usize)
-                    .map(|led| led.set_state(msg.st))
+                    .map(|led| led.set(msg.st))
+                    .into()
             });
     });
 
-    let _subscription = ble_scanning_driver.start(&mut callback)?;
+    let _subscription = drivers.ble_scanning.start(&mut callback)?;
 
     future::pending().await
 }
diff --git a/examples/blink.rs b/examples/blink.rs
index aa9b64c..fd3b1e5 100644
--- a/examples/blink.rs
+++ b/examples/blink.rs
@@ -2,26 +2,21 @@
 
 use libtock::result::TockResult;
 use libtock::timer::Duration;
-use libtock::Drivers;
 
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let Drivers {
-        led_driver_factory,
-        timer_context,
-        ..
-    } = libtock::retrieve_drivers()?;
+    let mut drivers = libtock::retrieve_drivers()?;
 
-    let mut driver = timer_context.create_timer_driver();
-    let timer_driver = driver.activate()?;
-    let led_driver = led_driver_factory.create_driver()?;
+    let leds_driver = drivers.leds.init_driver()?;
+    let mut timer_driver = drivers.timer.create_timer_driver();
+    let timer_driver = timer_driver.activate()?;
 
     // Blink the LEDs in a binary count pattern and scale
     // to the number of LEDs on the board.
     let mut count: usize = 0;
     loop {
-        for led in led_driver.all() {
-            let i = led.number();
+        for led in leds_driver.leds() {
+            let i = led.led_num();
             if count & (1 << i) == (1 << i) {
                 led.on()?;
             } else {
diff --git a/examples/blink_random.rs b/examples/blink_random.rs
index 8734693..93a263d 100644
--- a/examples/blink_random.rs
+++ b/examples/blink_random.rs
@@ -1,45 +1,37 @@
 #![no_std]
 
-use libtock::led::LedDriver;
+use libtock::leds::LedsDriver;
 use libtock::result::TockResult;
 use libtock::timer::Duration;
-use libtock::Drivers;
 
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let Drivers {
-        timer_context,
-        mut rng_driver,
-        led_driver_factory,
-        ..
-    } = libtock::retrieve_drivers()?;
+    let mut drivers = libtock::retrieve_drivers()?;
 
-    let led_driver = led_driver_factory.create_driver()?;
+    let leds_driver = drivers.leds.init_driver()?;
+    let mut timer_driver = drivers.timer.create_timer_driver();
+    let timer_driver = timer_driver.activate()?;
 
-    let mut driver = timer_context.create_timer_driver();
-    let timer_driver = driver.activate()?;
-
-    let num_leds = led_driver.count()?;
     // blink_nibble assumes 4 leds.
-    assert_eq!(num_leds, 4);
+    assert_eq!(leds_driver.num_leds(), 4);
 
     let mut buf = [0; 64];
     loop {
-        rng_driver.fill_buffer(&mut buf).await?;
+        drivers.rng.fill_buffer(&mut buf).await?;
 
         for &x in buf.iter() {
-            blink_nibble(x, &led_driver)?;
+            blink_nibble(&leds_driver, x)?;
             timer_driver.sleep(Duration::from_ms(100)).await?;
-            blink_nibble(x >> 4, &led_driver)?;
+            blink_nibble(&leds_driver, x >> 4)?;
             timer_driver.sleep(Duration::from_ms(100)).await?;
         }
     }
 }
 
 // Takes the 4 least-significant bits of x, and turn the 4 leds on/off accordingly.
-fn blink_nibble(x: u8, led_driver: &LedDriver) -> TockResult<()> {
+fn blink_nibble(leds_driver: &LedsDriver, x: u8) -> TockResult<()> {
     for i in 0..4 {
-        let led = led_driver.get(i).unwrap();
+        let led = leds_driver.get(i)?;
         if (x >> i) & 1 != 0 {
             led.on()?;
         } else {
diff --git a/examples/button_leds.rs b/examples/button_leds.rs
index ce6565f..4a48f2f 100644
--- a/examples/button_leds.rs
+++ b/examples/button_leds.rs
@@ -3,29 +3,23 @@
 use futures::future;
 use libtock::buttons::ButtonState;
 use libtock::result::TockResult;
-use libtock::Drivers;
 
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let Drivers {
-        led_driver_factory,
-        button_driver,
-        ..
-    } = libtock::retrieve_drivers()?;
+    let mut drivers = libtock::retrieve_drivers()?;
 
-    let led_driver = led_driver_factory.create_driver()?;
+    let buttons_driver = drivers.buttons.init_driver()?;
+    let leds_driver = drivers.leds.init_driver()?;
 
-    let mut with_callback = button_driver.with_callback(|button_num: usize, state| {
-        match state {
-            ButtonState::Pressed => led_driver.get(button_num).unwrap().toggle().ok().unwrap(),
-            ButtonState::Released => (),
-        };
-    });
+    let mut callback = |button_num, state| {
+        if let (ButtonState::Pressed, Ok(led)) = (state, leds_driver.get(button_num)) {
+            led.toggle().ok().unwrap();
+        }
+    };
 
-    let mut buttons = with_callback.init()?;
-
-    for mut button in &mut buttons {
-        button.enable()?;
+    let _subscription = buttons_driver.subscribe(&mut callback)?;
+    for button in buttons_driver.buttons() {
+        button.enable_interrupt()?;
     }
 
     future::pending().await
diff --git a/examples/button_read.rs b/examples/button_read.rs
index 2ce209f..c3f9aa6 100644
--- a/examples/button_read.rs
+++ b/examples/button_read.rs
@@ -1,34 +1,27 @@
 #![no_std]
 
 use core::fmt::Write;
-use libtock::buttons::ButtonState;
 use libtock::result::TockResult;
 use libtock::timer::Duration;
-use libtock::Drivers;
 
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let Drivers {
-        console_driver,
-        timer_context,
-        button_driver,
-        ..
-    } = libtock::retrieve_drivers()?;
-    let mut console = console_driver.create_console();
-    let mut with_callback = button_driver.with_callback(|_, _| {});
-    let mut buttons = with_callback.init()?;
-    let mut button = buttons.iter_mut().next().unwrap();
-    let button = button.enable()?;
+    let mut drivers = libtock::retrieve_drivers()?;
 
-    let mut driver = timer_context.create_timer_driver();
-    let timer_driver = driver.activate()?;
+    let buttons_driver = drivers.buttons.init_driver()?;
+    let mut timer_driver = drivers.timer.create_timer_driver();
+    let timer_driver = timer_driver.activate()?;
+    let mut console = drivers.console.create_console();
 
     loop {
-        match button.read()? {
-            ButtonState::Pressed => writeln!(console, "pressed"),
-            ButtonState::Released => writeln!(console, "released"),
-        }?;
-
+        for button in buttons_driver.buttons() {
+            writeln!(
+                console,
+                "button {}: {:?}",
+                button.button_num(),
+                button.read()?
+            )?;
+        }
         timer_driver.sleep(Duration::from_ms(500)).await?;
     }
 }
diff --git a/examples/button_subscribe.rs b/examples/button_subscribe.rs
index 268e6f0..d06a0e9 100644
--- a/examples/button_subscribe.rs
+++ b/examples/button_subscribe.rs
@@ -1,39 +1,41 @@
 #![no_std]
 
+use core::cell::Cell;
 use core::fmt::Write;
-use futures::future;
 use libtock::buttons::ButtonState;
 use libtock::result::TockResult;
-use libtock::Drivers;
+use libtock::timer::Duration;
 
-// FIXME: Hangs up when buttons are pressed rapidly. Yielding in callback leads to stack overflow.
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let Drivers {
-        console_driver,
-        button_driver,
-        ..
-    } = libtock::retrieve_drivers()?;
-    let mut console = console_driver.create_console();
-    let mut with_callback = button_driver.with_callback(|button_num: usize, state| {
-        writeln!(
-            console,
-            "Button: {} - State: {}",
-            button_num,
-            match state {
-                ButtonState::Pressed => "pressed",
-                ButtonState::Released => "released",
-            }
-        )
-        .ok()
-        .unwrap();
-    });
+    let mut drivers = libtock::retrieve_drivers()?;
 
-    let mut buttons = with_callback.init()?;
+    let buttons_driver = drivers.buttons.init_driver()?;
+    let mut timer_driver = drivers.timer.create_timer_driver();
+    let timer_driver = timer_driver.activate()?;
+    let mut console = drivers.console.create_console();
 
-    for mut button in &mut buttons {
-        button.enable()?;
+    let pressed_count = Cell::new(0usize);
+    let released_count = Cell::new(0usize);
+
+    let mut callback = |_button_num, state| match state {
+        ButtonState::Pressed => pressed_count.set(pressed_count.get() + 1),
+        ButtonState::Released => released_count.set(released_count.get() + 1),
+    };
+
+    let _subscription = buttons_driver.subscribe(&mut callback)?;
+
+    for button in buttons_driver.buttons() {
+        button.enable_interrupt()?;
     }
 
-    future::pending().await
+    loop {
+        writeln!(
+            console,
+            "pressed: {}, released: {}",
+            pressed_count.get(),
+            released_count.get()
+        )?;
+        timer_driver.sleep(Duration::from_ms(500)).await?;
+    }
 }
diff --git a/examples/gpio.rs b/examples/gpio.rs
index ee42d69..9241e74 100644
--- a/examples/gpio.rs
+++ b/examples/gpio.rs
@@ -2,25 +2,22 @@
 
 use libtock::result::TockResult;
 use libtock::timer::Duration;
-use libtock::Drivers;
 
 // Example works on P0.03
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let Drivers {
-        timer_context,
-        gpio_driver,
-        ..
-    } = libtock::retrieve_drivers()?;
-    let pin = gpio_driver.pin(0)?;
-    let pin = pin.open_for_write()?;
-    let mut driver = timer_context.create_timer_driver();
-    let timer_driver = driver.activate()?;
+    let mut drivers = libtock::retrieve_drivers()?;
 
+    let mut gpio_driver = drivers.gpio.init_driver()?;
+    let mut timer_driver = drivers.timer.create_timer_driver();
+    let timer_driver = timer_driver.activate()?;
+
+    let mut gpio = gpio_driver.gpios().next().unwrap();
+    let gpio_out = gpio.enable_output()?;
     loop {
-        pin.set_high()?;
+        gpio_out.set_high()?;
         timer_driver.sleep(Duration::from_ms(500)).await?;
-        pin.set_low()?;
+        gpio_out.set_low()?;
         timer_driver.sleep(Duration::from_ms(500)).await?;
     }
 }
diff --git a/examples/gpio_read.rs b/examples/gpio_read.rs
index 5e3d403..a29662e 100644
--- a/examples/gpio_read.rs
+++ b/examples/gpio_read.rs
@@ -1,32 +1,24 @@
 #![no_std]
 
 use core::fmt::Write;
-use libtock::gpio::InputMode;
+use libtock::gpio::ResistorMode;
 use libtock::result::TockResult;
 use libtock::timer::Duration;
-use libtock::Drivers;
 
 // example works on p0.03
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let Drivers {
-        console_driver,
-        timer_context,
-        gpio_driver,
-        ..
-    } = libtock::retrieve_drivers()?;
-    let mut console = console_driver.create_console();
-    let pin = gpio_driver.pin(0)?;
-    let pin = pin.open_for_read(None, InputMode::PullDown)?;
-    let mut driver = timer_context.create_timer_driver();
-    let timer_driver = driver.activate()?;
+    let mut drivers = libtock::retrieve_drivers()?;
 
+    let mut gpio_driver = drivers.gpio.init_driver()?;
+    let mut timer_driver = drivers.timer.create_timer_driver();
+    let timer_driver = timer_driver.activate()?;
+    let mut console = drivers.console.create_console();
+
+    let mut gpio = gpio_driver.gpios().next().unwrap();
+    let gpio_in = gpio.enable_input(ResistorMode::PullDown)?;
     loop {
-        if pin.read() {
-            writeln!(console, "true")?;
-        } else {
-            writeln!(console, "false")?;
-        }
+        writeln!(console, "{:?}", gpio_in.read()?)?;
         timer_driver.sleep(Duration::from_ms(500)).await?;
     }
 }
diff --git a/examples/hardware_test.rs b/examples/hardware_test.rs
index db2dd34..2f7028c 100644
--- a/examples/hardware_test.rs
+++ b/examples/hardware_test.rs
@@ -8,13 +8,13 @@
 use core::fmt::Write;
 use futures::future;
 use libtock::console::Console;
-use libtock::gpio::GpioPinRead;
-use libtock::gpio::GpioPinWrite;
-use libtock::gpio::InputMode;
+use libtock::gpio::GpioRead;
+use libtock::gpio::GpioState;
+use libtock::gpio::GpioWrite;
+use libtock::gpio::ResistorMode;
 use libtock::result::TockResult;
 use libtock::timer::DriverContext;
 use libtock::timer::Duration;
-use libtock::Drivers;
 
 static mut STATIC: usize = 0;
 
@@ -36,18 +36,16 @@
 
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let Drivers {
-        console_driver,
-        gpio_driver,
-        mut timer_context,
-        ..
-    } = libtock::retrieve_drivers()?;
-    let mut gpio_iter = gpio_driver.all_pins()?;
-    let mut console = console_driver.create_console();
-    let pin_in = gpio_iter.next().unwrap();
-    let pin_out = gpio_iter.next().unwrap();
-    let pin_in = pin_in.open_for_read(None, InputMode::PullDown)?;
-    let mut pin_out = pin_out.open_for_write()?;
+    let mut drivers = libtock::retrieve_drivers()?;
+
+    let mut gpio_driver = drivers.gpio.init_driver()?;
+    let mut console = drivers.console.create_console();
+
+    let mut gpios = gpio_driver.gpios();
+    let mut pin_in = gpios.next().unwrap();
+    let pin_in = pin_in.enable_input(ResistorMode::PullDown)?;
+    let mut pin_out = gpios.next().unwrap();
+    let mut pin_out = pin_out.enable_output()?;
 
     writeln!(console, "[test-results]")?;
 
@@ -59,7 +57,7 @@
 
     test_trait_objects(&mut console, &pin_in, &mut pin_out)?;
 
-    test_callbacks_and_wait_forever(&mut console, &mut timer_context).await
+    test_callbacks_and_wait_forever(&mut console, &mut drivers.timer).await
 }
 
 fn test_heap(console: &mut Console) {
@@ -78,27 +76,26 @@
 /// trait_obj_value_string = string
 fn test_trait_objects(
     console: &mut Console,
-    pin_in: &GpioPinRead,
-    pin_out: &mut GpioPinWrite,
+    pin_in: &GpioRead,
+    pin_out: &mut GpioWrite,
 ) -> TockResult<()> {
     pin_out.set_high()?;
 
     let string = String::from("string");
 
-    let x = if pin_in.read() {
-        &1usize as &dyn MyTrait
-    } else {
-        &string as &dyn MyTrait
+    let x = match pin_in.read()? {
+        GpioState::Low => &string as &dyn MyTrait,
+        GpioState::High => &1usize as &dyn MyTrait,
     };
 
-    let y = if !pin_in.read() {
-        &1usize as &dyn MyTrait
-    } else {
-        &string as &dyn MyTrait
+    let y = match pin_in.read()? {
+        GpioState::Low => &1usize as &dyn MyTrait,
+        GpioState::High => &string as &dyn MyTrait,
     };
 
     x.do_something_with_a_console(console);
     y.do_something_with_a_console(console);
+
     Ok(())
 }
 
@@ -109,10 +106,15 @@
 }
 
 /// needs P0.03 and P0.04 to be connected
-fn test_gpio(console: &mut Console, pin_in: &GpioPinRead, pin_out: &mut GpioPinWrite) {
+fn test_gpio(console: &mut Console, pin_in: &GpioRead, pin_out: &mut GpioWrite) {
     pin_out.set_high().ok().unwrap();
 
-    writeln!(console, "gpio_works = {}", pin_in.read()).unwrap();
+    writeln!(
+        console,
+        "gpio_works = {}",
+        pin_in.read().ok() == Some(GpioState::High)
+    )
+    .unwrap();
 }
 
 async fn test_callbacks_and_wait_forever(
diff --git a/examples/panic.rs b/examples/panic.rs
index 6b476e3..5abc01a 100644
--- a/examples/panic.rs
+++ b/examples/panic.rs
@@ -1,9 +1,10 @@
+// Triggers the panic handler. Should make all LEDs flash.
+
 #![no_std]
 
 use libtock::result::TockResult;
 
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let _ = libtock::LibTock {};
     panic!("Bye world!");
 }
diff --git a/examples/sensors.rs b/examples/sensors.rs
index 4e72c8c..7ea87c7 100644
--- a/examples/sensors.rs
+++ b/examples/sensors.rs
@@ -2,33 +2,37 @@
 
 use core::fmt::Write;
 use libtock::result::TockResult;
-use libtock::sensors::*;
+use libtock::sensors::Sensor;
 use libtock::timer::Duration;
-use libtock::Drivers;
 
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let Drivers {
-        console_driver,
-        timer_context,
-        mut temperature_sensor,
-        mut humidity_sensor,
-        mut ambient_light_sensor,
-        mut ninedof_driver,
-        ..
-    } = libtock::retrieve_drivers()?;
-    let mut console = console_driver.create_console();
-    let mut driver = timer_context.create_timer_driver();
-    let timer_driver = driver.activate()?;
+    let mut drivers = libtock::retrieve_drivers()?;
+
+    let mut timer_driver = drivers.timer.create_timer_driver();
+    let timer_driver = timer_driver.activate()?;
+    let mut console = drivers.console.create_console();
 
     loop {
-        writeln!(console, "Humidity:    {}\n", humidity_sensor.read()?)?;
-        writeln!(console, "Temperature: {}\n", temperature_sensor.read()?)?;
-        writeln!(console, "Light:       {}\n", ambient_light_sensor.read()?)?;
+        writeln!(
+            console,
+            "Humidity:    {}\n",
+            drivers.humidity_sensor.read()?
+        )?;
+        writeln!(
+            console,
+            "Temperature: {}\n",
+            drivers.temperature_sensor.read()?
+        )?;
+        writeln!(
+            console,
+            "Light:       {}\n",
+            drivers.ambient_light_sensor.read()?
+        )?;
         writeln!(
             console,
             "Accel:       {}\n",
-            ninedof_driver.read_acceleration()?
+            drivers.ninedof.read_acceleration()?
         )?;
         timer_driver.sleep(Duration::from_ms(500)).await?;
     }
diff --git a/examples/seven_segment.rs b/examples/seven_segment.rs
index 4249157..88b0700 100644
--- a/examples/seven_segment.rs
+++ b/examples/seven_segment.rs
@@ -3,7 +3,6 @@
 use libtock::electronics::ShiftRegister;
 use libtock::result::TockResult;
 use libtock::timer::Duration;
-use libtock::Drivers;
 
 fn number_to_bits(n: u8) -> [bool; 8] {
     match n {
@@ -24,18 +23,19 @@
 // Example works on a shift register on P0.03, P0.04, P0.28
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let Drivers {
-        timer_context,
-        gpio_driver,
-        ..
-    } = libtock::retrieve_drivers()?;
-    let mut shift_register = ShiftRegister::new(
-        gpio_driver.pin(0)?.open_for_write()?,
-        gpio_driver.pin(1)?.open_for_write()?,
-        gpio_driver.pin(2)?.open_for_write()?,
-    );
+    let mut drivers = libtock::retrieve_drivers()?;
 
-    let mut driver = timer_context.create_timer_driver();
+    let mut gpio_driver = drivers.gpio.init_driver()?;
+    let mut gpios = gpio_driver.gpios();
+    let mut gpio0 = gpios.next().unwrap();
+    let gpio0 = gpio0.enable_output()?;
+    let mut gpio1 = gpios.next().unwrap();
+    let gpio1 = gpio1.enable_output()?;
+    let mut gpio2 = gpios.next().unwrap();
+    let gpio2 = gpio2.enable_output()?;
+    let mut shift_register = ShiftRegister::new(&gpio0, &gpio1, &gpio2);
+
+    let mut driver = drivers.timer.create_timer_driver();
     let timer_driver = driver.activate()?;
 
     let mut i = 0;
diff --git a/examples/simple_ble.rs b/examples/simple_ble.rs
index e312ef5..23543ed 100644
--- a/examples/simple_ble.rs
+++ b/examples/simple_ble.rs
@@ -5,7 +5,6 @@
 use libtock::result::TockResult;
 use libtock::simple_ble::BleAdvertisingDriver;
 use libtock::timer::Duration;
-use libtock::Drivers;
 use serde::Serialize;
 
 #[derive(Serialize)]
@@ -16,16 +15,13 @@
 
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let Drivers {
-        led_driver_factory,
-        timer_context,
-        mut ble_advertising_driver,
-        ..
-    } = libtock::retrieve_drivers()?;
+    let mut drivers = libtock::retrieve_drivers()?;
 
-    let led_driver = led_driver_factory.create_driver()?;
+    let leds_driver = drivers.leds.init_driver()?;
+    let mut timer_driver = drivers.timer.create_timer_driver();
+    let timer_driver = timer_driver.activate()?;
 
-    let led = led_driver.get(0).unwrap();
+    let led = leds_driver.leds().next().unwrap();
 
     let uuid: [u8; 2] = [0x00, 0x18];
 
@@ -33,6 +29,7 @@
 
     let mut buffer = BleAdvertisingDriver::create_advertising_buffer();
     let mut gap_payload = BlePayload::default();
+
     gap_payload
         .add_flag(ble_composer::flags::LE_GENERAL_DISCOVERABLE)
         .unwrap();
@@ -44,12 +41,12 @@
     gap_payload
         .add(ble_composer::gap_types::COMPLETE_LOCAL_NAME, b"Tock!")
         .unwrap();
+
     gap_payload.add_service_payload([91, 79], &payload).unwrap();
 
-    let _handle = ble_advertising_driver.initialize(100, &gap_payload, &mut buffer);
-
-    let mut driver = timer_context.create_timer_driver();
-    let timer_driver = driver.activate()?;
+    let _handle = drivers
+        .ble_advertising
+        .initialize(100, &gap_payload, &mut buffer);
 
     loop {
         led.on()?;
diff --git a/examples/temperature.rs b/examples/temperature.rs
index 9198f1b..7428187 100644
--- a/examples/temperature.rs
+++ b/examples/temperature.rs
@@ -2,16 +2,20 @@
 
 use core::fmt::Write;
 use libtock::result::TockResult;
-use libtock::Drivers;
+use libtock::timer::Duration;
 
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let Drivers {
-        console_driver,
-        mut temperature_driver,
-        ..
-    } = libtock::retrieve_drivers()?;
-    let mut console = console_driver.create_console();
-    let temperature = temperature_driver.measure_temperature().await?;
-    writeln!(console, "Temperature: {}", temperature).map_err(Into::into)
+    let mut drivers = libtock::retrieve_drivers()?;
+
+    let mut temperature_driver = drivers.temperature.init_driver()?;
+    let mut timer_driver = drivers.timer.create_timer_driver();
+    let timer_driver = timer_driver.activate()?;
+    let mut console = drivers.console.create_console();
+
+    loop {
+        let temperature = temperature_driver.measure_temperature().await?;
+        writeln!(console, "Temperature: {}", temperature)?;
+        timer_driver.sleep(Duration::from_ms(1000)).await?;
+    }
 }
diff --git a/examples/timer.rs b/examples/timer.rs
index 9ed0112..493f0b9 100644
--- a/examples/timer.rs
+++ b/examples/timer.rs
@@ -8,25 +8,21 @@
 use libtock::result::TockResult;
 use libtock::timer::DriverContext;
 use libtock::timer::Duration;
-use libtock::Drivers;
 
 const DELAY_MS: usize = 500;
 
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let Drivers {
-        mut timer_context,
-        console_driver,
-        ..
-    } = libtock::retrieve_drivers()?;
-    let mut console = console_driver.create_console();
+    let mut drivers = libtock::retrieve_drivers()?;
+
+    let mut console = drivers.console.create_console();
 
     let mut previous_ticks = None;
 
     for i in 0.. {
-        print_now(&mut console, &mut timer_context, &mut previous_ticks, i)?;
-        let mut driver = timer_context.create_timer_driver();
-        let timer_driver = driver.activate()?;
+        print_now(&mut console, &mut drivers.timer, &mut previous_ticks, i)?;
+        let mut timer_driver = drivers.timer.create_timer_driver();
+        let timer_driver = timer_driver.activate()?;
 
         timer_driver.sleep(Duration::from_ms(DELAY_MS)).await?;
     }
diff --git a/examples/timer_parallel.rs b/examples/timer_parallel.rs
index 389727a..66ddac2 100644
--- a/examples/timer_parallel.rs
+++ b/examples/timer_parallel.rs
@@ -1,16 +1,15 @@
 #![no_std]
 
 use futures::future;
-use libtock::led::Led;
+use libtock::leds::Led;
 use libtock::result::TockResult;
 use libtock::timer::Duration;
 use libtock::timer::ParallelSleepDriver;
-use libtock::Drivers;
 
-async fn blink<'a>(
-    timer_driver: &'a ParallelSleepDriver<'a>,
+async fn blink(
+    timer_driver: &ParallelSleepDriver<'_>,
     duration: Duration<usize>,
-    led: &'a mut Led<'a>,
+    led: Led<'_>,
 ) -> TockResult<()> {
     loop {
         led.toggle()?;
@@ -21,24 +20,17 @@
 
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let Drivers {
-        led_driver_factory,
-        timer_context,
-        ..
-    } = libtock::retrieve_drivers()?;
-    let led_driver = led_driver_factory.create_driver()?;
+    let mut drivers = libtock::retrieve_drivers()?;
 
-    let mut led_iter = led_driver.all();
-    let mut led_1 = led_iter.next().unwrap();
-    let mut led_2 = led_iter.next().unwrap();
-    let mut led_3 = led_iter.next().unwrap();
+    let leds_driver = drivers.leds.init_driver()?;
+    let mut timer_driver = drivers.timer.create_timer_driver();
+    let timer_driver = timer_driver.activate()?;
 
-    let mut driver = timer_context.create_timer_driver();
-    let timer_driver = driver.activate()?;
+    let mut leds = leds_driver.leds();
 
-    let fut_1 = blink(&timer_driver, Duration::from_ms(500), &mut led_1);
-    let fut_2 = blink(&timer_driver, Duration::from_ms(333), &mut led_2);
-    let fut_3 = blink(&timer_driver, Duration::from_ms(250), &mut led_3);
+    let fut_1 = blink(&timer_driver, Duration::from_ms(500), leds.next().unwrap());
+    let fut_2 = blink(&timer_driver, Duration::from_ms(333), leds.next().unwrap());
+    let fut_3 = blink(&timer_driver, Duration::from_ms(250), leds.next().unwrap());
 
     future::try_join3(fut_1, fut_2, fut_3).await?;
     Ok(())
diff --git a/examples/timer_subscribe.rs b/examples/timer_subscribe.rs
index f7e158c..5c69be1 100644
--- a/examples/timer_subscribe.rs
+++ b/examples/timer_subscribe.rs
@@ -4,17 +4,14 @@
 use futures::future;
 use libtock::result::TockResult;
 use libtock::timer::Duration;
-use libtock::Drivers;
 
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    let Drivers {
-        console_driver,
-        mut timer_context,
-        ..
-    } = libtock::retrieve_drivers()?;
-    let mut console = console_driver.create_console();
-    let mut with_callback = timer_context.with_callback(|_, _| {
+    let mut drivers = libtock::retrieve_drivers()?;
+
+    let mut console = drivers.console.create_console();
+
+    let mut with_callback = drivers.timer.with_callback(|_, _| {
         writeln!(
             console,
             "This line is printed 2 seconds after the start of the program.",
diff --git a/src/adc.rs b/src/adc.rs
index 7a18cce..7d351df 100644
--- a/src/adc.rs
+++ b/src/adc.rs
@@ -1,9 +1,11 @@
-use crate::callback::{CallbackSubscription, SubscribableCallback};
+use crate::callback::CallbackSubscription;
+use crate::callback::Consumer;
 use crate::result::TockResult;
 use crate::shared_memory::SharedMemory;
 use crate::syscalls;
+use core::marker::PhantomData;
 
-pub const DRIVER_NUM: usize = 0x0005;
+pub const DRIVER_NUMBER: usize = 0x0005;
 pub const BUFFER_SIZE: usize = 128;
 
 mod command_nr {
@@ -25,11 +27,16 @@
 }
 
 #[non_exhaustive]
-pub struct AdcDriver;
+pub struct AdcDriverFactory;
 
-impl AdcDriver {
-    pub fn with_callback<CB>(self, callback: CB) -> WithCallback<CB> {
-        WithCallback { callback }
+impl AdcDriverFactory {
+    pub fn init_driver(&mut self) -> TockResult<Adc> {
+        let adc = Adc {
+            // num_channels
+            num_channels: syscalls::command(DRIVER_NUMBER, command_nr::COUNT, 0, 0)?,
+            lifetime: PhantomData,
+        };
+        Ok(adc)
     }
 }
 
@@ -47,65 +54,61 @@
 }
 
 pub struct Adc<'a> {
-    count: usize,
-    #[allow(dead_code)] // Used in drop
-    subscription: CallbackSubscription<'a>,
+    num_channels: usize,
+    lifetime: PhantomData<&'a ()>,
 }
 
-pub struct WithCallback<CB> {
-    callback: CB,
-}
+struct AdcEventConsumer;
 
-impl<CB: FnMut(usize, usize)> SubscribableCallback for WithCallback<CB> {
-    fn call_rust(&mut self, _: usize, channel: usize, value: usize) {
-        (self.callback)(channel, value);
-    }
-}
-
-impl<'a, CB> WithCallback<CB>
-where
-    Self: SubscribableCallback,
-{
-    pub fn init(&mut self) -> TockResult<Adc> {
-        let adc = Adc {
-            count: syscalls::command(DRIVER_NUM, command_nr::COUNT, 0, 0)?,
-            subscription: syscalls::subscribe(DRIVER_NUM, subscribe_nr::SUBSCRIBE_CALLBACK, self)?,
-        };
-        Ok(adc)
+impl<CB: FnMut(usize, usize)> Consumer<CB> for AdcEventConsumer {
+    fn consume(data: &mut CB, _: usize, channel: usize, value: usize) {
+        data(channel, value);
     }
 }
 
 impl<'a> Adc<'a> {
-    pub fn init_buffer(buffer: &'a mut AdcBuffer) -> TockResult<SharedMemory> {
-        syscalls::allow(DRIVER_NUM, allow_nr::BUFFER, &mut buffer.buffer).map_err(Into::into)
+    pub fn init_buffer(&self, buffer: &'a mut AdcBuffer) -> TockResult<SharedMemory> {
+        syscalls::allow(DRIVER_NUMBER, allow_nr::BUFFER, &mut buffer.buffer).map_err(Into::into)
     }
 
-    pub fn init_alt_buffer(alt_buffer: &'a mut AdcBuffer) -> TockResult<SharedMemory> {
-        syscalls::allow(DRIVER_NUM, allow_nr::BUFFER_ALT, &mut alt_buffer.buffer)
+    pub fn init_alt_buffer(&self, alt_buffer: &'a mut AdcBuffer) -> TockResult<SharedMemory> {
+        syscalls::allow(DRIVER_NUMBER, allow_nr::BUFFER_ALT, &mut alt_buffer.buffer)
             .map_err(Into::into)
     }
 
     /// Return the number of available channels
     pub fn count(&self) -> usize {
-        self.count
+        self.num_channels
+    }
+
+    pub fn subscribe<CB: FnMut(usize, usize)>(
+        &self,
+        callback: &'a mut CB,
+    ) -> TockResult<CallbackSubscription> {
+        syscalls::subscribe::<AdcEventConsumer, _>(
+            DRIVER_NUMBER,
+            subscribe_nr::SUBSCRIBE_CALLBACK,
+            callback,
+        )
+        .map_err(Into::into)
     }
 
     /// Start a single sample of channel
     pub fn sample(&self, channel: usize) -> TockResult<()> {
-        syscalls::command(DRIVER_NUM, command_nr::START, channel, 0)?;
+        syscalls::command(DRIVER_NUMBER, command_nr::START, channel, 0)?;
         Ok(())
     }
 
     /// Start continuous sampling of channel
     pub fn sample_continuous(&self, channel: usize) -> TockResult<()> {
-        syscalls::command(DRIVER_NUM, command_nr::START_REPEAT, channel, 0)?;
+        syscalls::command(DRIVER_NUMBER, command_nr::START_REPEAT, channel, 0)?;
         Ok(())
     }
 
     /// Start continuous sampling to first buffer
     pub fn sample_continuous_buffered(&self, channel: usize, frequency: usize) -> TockResult<()> {
         syscalls::command(
-            DRIVER_NUM,
+            DRIVER_NUMBER,
             command_nr::START_REPEAT_BUFFER,
             channel,
             frequency,
@@ -120,7 +123,7 @@
         frequency: usize,
     ) -> TockResult<()> {
         syscalls::command(
-            DRIVER_NUM,
+            DRIVER_NUMBER,
             command_nr::START_REPEAT_BUFFER_ALT,
             channel,
             frequency,
@@ -130,7 +133,7 @@
 
     /// Stop any started sampling operation
     pub fn stop(&self) -> TockResult<()> {
-        syscalls::command(DRIVER_NUM, command_nr::STOP, 0, 0)?;
+        syscalls::command(DRIVER_NUMBER, command_nr::STOP, 0, 0)?;
         Ok(())
     }
 }
diff --git a/src/buttons.rs b/src/buttons.rs
index 7004cd8..aac5cb2 100644
--- a/src/buttons.rs
+++ b/src/buttons.rs
@@ -1,5 +1,7 @@
 use crate::callback::CallbackSubscription;
-use crate::callback::SubscribableCallback;
+use crate::callback::Consumer;
+use crate::result::OtherError;
+use crate::result::OutOfRangeError;
 use crate::result::TockResult;
 use crate::syscalls;
 use core::marker::PhantomData;
@@ -18,53 +20,93 @@
 }
 
 #[non_exhaustive]
-pub struct ButtonDriver;
+pub struct ButtonsDriverFactory;
 
-impl ButtonDriver {
-    pub fn with_callback<CB>(self, callback: CB) -> WithCallback<CB> {
-        WithCallback { callback }
-    }
-}
-
-pub struct WithCallback<CB> {
-    callback: CB,
-}
-
-impl<CB: FnMut(usize, ButtonState)> SubscribableCallback for WithCallback<CB> {
-    fn call_rust(&mut self, button_num: usize, state: usize, _: usize) {
-        (self.callback)(button_num, state.into());
-    }
-}
-
-impl<CB> WithCallback<CB>
-where
-    Self: SubscribableCallback,
-{
-    pub fn init(&mut self) -> TockResult<Buttons> {
-        let buttons = Buttons {
-            count: syscalls::command(DRIVER_NUMBER, command_nr::COUNT, 0, 0)?,
-            subscription: syscalls::subscribe(
-                DRIVER_NUMBER,
-                subscribe_nr::SUBSCRIBE_CALLBACK,
-                self,
-            )?,
+impl ButtonsDriverFactory {
+    pub fn init_driver(&mut self) -> TockResult<ButtonsDriver> {
+        let buttons_driver = ButtonsDriver {
+            num_buttons: syscalls::command(DRIVER_NUMBER, command_nr::COUNT, 0, 0)?,
+            lifetime: PhantomData,
         };
-        Ok(buttons)
+        Ok(buttons_driver)
+    }
+}
+
+pub struct ButtonsDriver<'a> {
+    num_buttons: usize,
+    lifetime: PhantomData<&'a ()>,
+}
+
+impl<'a> ButtonsDriver<'a> {
+    pub fn num_buttons(&self) -> usize {
+        self.num_buttons
+    }
+
+    /// Returns the button at 0-based index `button_num`
+    pub fn get(&self, button_num: usize) -> Result<Button, OutOfRangeError> {
+        if button_num < self.num_buttons {
+            Ok(Button {
+                button_num,
+                lifetime: PhantomData,
+            })
+        } else {
+            Err(OutOfRangeError)
+        }
+    }
+
+    pub fn buttons(&self) -> Buttons {
+        Buttons {
+            num_buttons: self.num_buttons,
+            curr_button: 0,
+            lifetime: PhantomData,
+        }
+    }
+
+    pub fn subscribe<CB: Fn(usize, ButtonState)>(
+        &self,
+        callback: &'a mut CB,
+    ) -> TockResult<CallbackSubscription> {
+        syscalls::subscribe::<ButtonsEventConsumer, _>(
+            DRIVER_NUMBER,
+            subscribe_nr::SUBSCRIBE_CALLBACK,
+            callback,
+        )
+        .map_err(Into::into)
+    }
+}
+
+struct ButtonsEventConsumer;
+
+impl<CB: Fn(usize, ButtonState)> Consumer<CB> for ButtonsEventConsumer {
+    fn consume(callback: &mut CB, button_num: usize, button_state: usize, _: usize) {
+        let button_state = match button_state {
+            0 => ButtonState::Released,
+            1 => ButtonState::Pressed,
+            _ => return,
+        };
+        callback(button_num, button_state);
     }
 }
 
 pub struct Buttons<'a> {
-    count: usize,
-    #[allow(dead_code)] // Used in drop
-    subscription: CallbackSubscription<'a>,
+    num_buttons: usize,
+    curr_button: usize,
+    lifetime: PhantomData<&'a ()>,
 }
 
-impl<'a> Buttons<'a> {
-    pub fn iter_mut(&mut self) -> ButtonIter {
-        ButtonIter {
-            curr_button: 0,
-            button_count: self.count,
-            _lifetime: Default::default(),
+impl<'a> Iterator for Buttons<'a> {
+    type Item = Button<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.curr_button < self.num_buttons {
+            let item = Button {
+                button_num: self.curr_button,
+                lifetime: PhantomData,
+            };
+            self.curr_button += 1;
+            Some(item)
+        } else {
+            None
         }
     }
 }
@@ -75,65 +117,45 @@
     Released,
 }
 
-impl From<usize> for ButtonState {
-    fn from(state: usize) -> ButtonState {
-        match state {
-            0 => ButtonState::Released,
-            1 => ButtonState::Pressed,
-            _ => unreachable!(),
+impl From<ButtonState> for bool {
+    fn from(button_state: ButtonState) -> Self {
+        match button_state {
+            ButtonState::Released => false,
+            ButtonState::Pressed => true,
         }
     }
 }
 
-impl<'a, 'b> IntoIterator for &'b mut Buttons<'a> {
-    type Item = ButtonHandle<'b>;
-    type IntoIter = ButtonIter<'b>;
-
-    fn into_iter(self) -> Self::IntoIter {
-        self.iter_mut()
-    }
-}
-
-pub struct ButtonIter<'a> {
-    curr_button: usize,
-    button_count: usize,
-    _lifetime: PhantomData<&'a ()>,
-}
-
-impl<'a> Iterator for ButtonIter<'a> {
-    type Item = ButtonHandle<'a>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.curr_button < self.button_count {
-            let item = ButtonHandle {
-                button_num: self.curr_button,
-                _lifetime: Default::default(),
-            };
-            self.curr_button += 1;
-            Some(item)
-        } else {
-            None
-        }
-    }
-}
-
-pub struct ButtonHandle<'a> {
+pub struct Button<'a> {
     button_num: usize,
-    _lifetime: PhantomData<&'a ()>,
+    lifetime: PhantomData<&'a ()>,
 }
 
-impl<'a> ButtonHandle<'a> {
-    pub fn enable(&mut self) -> TockResult<Button> {
+impl<'a> Button<'a> {
+    pub fn button_num(&self) -> usize {
+        self.button_num
+    }
+
+    pub fn read(&self) -> TockResult<ButtonState> {
+        let button_state = syscalls::command(DRIVER_NUMBER, command_nr::READ, self.button_num, 0)?;
+        match button_state {
+            0 => Ok(ButtonState::Released),
+            1 => Ok(ButtonState::Pressed),
+            _ => Err(OtherError::ButtonsDriverInvalidState.into()),
+        }
+    }
+
+    pub fn enable_interrupt(&self) -> TockResult<()> {
         syscalls::command(
             DRIVER_NUMBER,
             command_nr::ENABLE_INTERRUPT,
             self.button_num,
             0,
         )?;
-        Ok(Button { handle: self })
+        Ok(())
     }
 
-    pub fn disable(&mut self) -> TockResult<()> {
+    pub fn disable_interrupt(&self) -> TockResult<()> {
         syscalls::command(
             DRIVER_NUMBER,
             command_nr::DISABLE_INTERRUPT,
@@ -143,15 +165,3 @@
         Ok(())
     }
 }
-
-pub struct Button<'a> {
-    handle: &'a ButtonHandle<'a>,
-}
-
-impl<'a> Button<'a> {
-    pub fn read(&self) -> TockResult<ButtonState> {
-        syscalls::command(DRIVER_NUMBER, command_nr::READ, self.handle.button_num, 0)
-            .map(ButtonState::from)
-            .map_err(Into::into)
-    }
-}
diff --git a/src/callback.rs b/src/callback.rs
index 8149b32..053806f 100644
--- a/src/callback.rs
+++ b/src/callback.rs
@@ -2,13 +2,39 @@
 use core::marker::PhantomData;
 use core::ptr;
 
-pub trait SubscribableCallback {
-    fn call_rust(&mut self, arg1: usize, arg2: usize, arg3: usize);
+pub trait Consumer<T> {
+    fn consume(data: &mut T, arg1: usize, arg2: usize, arg3: usize);
 }
 
-impl<F: FnMut(usize, usize, usize)> SubscribableCallback for F {
-    fn call_rust(&mut self, arg1: usize, arg2: usize, arg3: usize) {
-        self(arg1, arg2, arg3)
+pub struct Identity3Consumer;
+
+impl<CB: FnMut(usize, usize, usize)> Consumer<CB> for Identity3Consumer {
+    fn consume(data: &mut CB, arg1: usize, arg2: usize, arg3: usize) {
+        data(arg1, arg2, arg3);
+    }
+}
+
+pub struct Identity2Consumer;
+
+impl<CB: FnMut(usize, usize)> Consumer<CB> for Identity2Consumer {
+    fn consume(data: &mut CB, arg1: usize, arg2: usize, _: usize) {
+        data(arg1, arg2);
+    }
+}
+
+pub struct Identity1Consumer;
+
+impl<CB: FnMut(usize)> Consumer<CB> for Identity1Consumer {
+    fn consume(data: &mut CB, arg1: usize, _: usize, _: usize) {
+        data(arg1);
+    }
+}
+
+pub struct Identity0Consumer;
+
+impl<CB: FnMut()> Consumer<CB> for Identity0Consumer {
+    fn consume(data: &mut CB, _: usize, _: usize, _: usize) {
+        data();
     }
 }
 
@@ -16,15 +42,15 @@
 pub struct CallbackSubscription<'a> {
     driver_number: usize,
     subscribe_number: usize,
-    _lifetime: PhantomData<&'a ()>,
+    lifetime: PhantomData<&'a ()>,
 }
 
 impl<'a> CallbackSubscription<'a> {
-    pub fn new(driver_number: usize, subscribe_number: usize) -> CallbackSubscription<'a> {
+    pub(crate) fn new(driver_number: usize, subscribe_number: usize) -> CallbackSubscription<'a> {
         CallbackSubscription {
             driver_number,
             subscribe_number,
-            _lifetime: Default::default(),
+            lifetime: PhantomData,
         }
     }
 }
diff --git a/src/console.rs b/src/console.rs
index b4f2c51..ee6f50a 100644
--- a/src/console.rs
+++ b/src/console.rs
@@ -1,3 +1,4 @@
+use crate::callback::Identity0Consumer;
 use crate::futures;
 use crate::result::TockResult;
 use crate::syscalls;
@@ -56,8 +57,8 @@
         )?;
 
         let is_written = Cell::new(false);
-        let mut is_written_alarm = |_, _, _| is_written.set(true);
-        let subscription = syscalls::subscribe(
+        let mut is_written_alarm = || is_written.set(true);
+        let subscription = syscalls::subscribe::<Identity0Consumer, _>(
             DRIVER_NUMBER,
             subscribe_nr::SET_ALARM,
             &mut is_written_alarm,
diff --git a/src/debug/mod.rs b/src/debug/mod.rs
index 1e55bb3..4596aaa 100644
--- a/src/debug/mod.rs
+++ b/src/debug/mod.rs
@@ -1,23 +1,23 @@
 //! Heapless debugging functions for Tock troubleshooting
 
 mod low_level_debug;
-use crate::drivers::Drivers;
-pub use low_level_debug::*;
 
-use crate::retrieve_drivers_unsafe;
+use crate::drivers;
+
+pub use low_level_debug::*;
 
 pub fn println() {
     let buffer = [b'\n'];
-    let Drivers { console_driver, .. } = unsafe { retrieve_drivers_unsafe() };
-    let mut console = console_driver.create_console();
+    let drivers = unsafe { drivers::retrieve_drivers_unsafe() };
+    let mut console = drivers.console.create_console();
     let _ = console.write(&buffer);
 }
 
 pub fn print_as_hex(value: usize) {
     let mut buffer = [b'\n'; 11];
     write_as_hex(&mut buffer, value);
-    let Drivers { console_driver, .. } = unsafe { retrieve_drivers_unsafe() };
-    let mut console = console_driver.create_console();
+    let drivers = unsafe { drivers::retrieve_drivers_unsafe() };
+    let mut console = drivers.console.create_console();
     let _ = console.write(buffer);
 }
 
@@ -29,8 +29,8 @@
     let mut buffer = [b'\n'; 15];
     buffer[0..4].clone_from_slice(b"SP: ");
     write_as_hex(&mut buffer[4..15], stack_pointer);
-    let Drivers { console_driver, .. } = unsafe { retrieve_drivers_unsafe() };
-    let mut console = console_driver.create_console();
+    let drivers = unsafe { drivers::retrieve_drivers_unsafe() };
+    let mut console = drivers.console.create_console();
     let _ = console.write(buffer);
 }
 
@@ -54,8 +54,8 @@
         }
     }
     buffer[27] = b'\n';
-    let Drivers { console_driver, .. } = retrieve_drivers_unsafe();
-    let mut console = console_driver.create_console();
+    let drivers = drivers::retrieve_drivers_unsafe();
+    let mut console = drivers.console.create_console();
     let _ = console.write(&buffer);
 }
 
diff --git a/src/drivers.rs b/src/drivers.rs
index 814f61b..a8e1e98 100644
--- a/src/drivers.rs
+++ b/src/drivers.rs
@@ -1,8 +1,8 @@
-use crate::adc::AdcDriver;
-use crate::buttons::ButtonDriver;
+use crate::adc::AdcDriverFactory;
+use crate::buttons::ButtonsDriverFactory;
 use crate::console::ConsoleDriver;
-use crate::gpio::GpioDriver;
-use crate::led::LedDriverFactory;
+use crate::gpio::GpioDriverFactory;
+use crate::leds::LedsDriverFactory;
 use crate::result::OtherError;
 use crate::result::TockError;
 use crate::result::TockResult;
@@ -13,27 +13,27 @@
 use crate::sensors::TemperatureSensor;
 use crate::simple_ble::BleAdvertisingDriver;
 use crate::simple_ble::BleScanningDriver;
-use crate::temperature::TemperatureDriver;
+use crate::temperature::TemperatureDriverFactory;
 use crate::timer::DriverContext;
 use core::cell::Cell;
 
 /// Struct containing all drivers constructible through [retrieve_drivers()]
 #[non_exhaustive]
 pub struct Drivers {
-    pub console_driver: ConsoleDriver,
-    pub led_driver_factory: LedDriverFactory,
-    pub timer_context: DriverContext,
-    pub gpio_driver: GpioDriver,
-    pub temperature_driver: TemperatureDriver,
-    pub button_driver: ButtonDriver,
-    pub adc_driver: AdcDriver,
-    pub rng_driver: RngDriver,
-    pub ble_advertising_driver: BleAdvertisingDriver,
-    pub ble_scanning_driver: BleScanningDriver,
+    pub console: ConsoleDriver,
+    pub leds: LedsDriverFactory,
+    pub timer: DriverContext,
+    pub gpio: GpioDriverFactory,
+    pub temperature: TemperatureDriverFactory,
+    pub buttons: ButtonsDriverFactory,
+    pub adc: AdcDriverFactory,
+    pub rng: RngDriver,
+    pub ble_advertising: BleAdvertisingDriver,
+    pub ble_scanning: BleScanningDriver,
     pub ambient_light_sensor: AmbientLightSensor,
     pub temperature_sensor: TemperatureSensor,
     pub humidity_sensor: HumiditySensor,
-    pub ninedof_driver: NinedofDriver,
+    pub ninedof: NinedofDriver,
 }
 
 /// Retrieve [Drivers] struct. Returns struct only once.
@@ -56,31 +56,30 @@
 
 #[allow(clippy::declare_interior_mutable_const)]
 const DRIVERS: Drivers = Drivers {
-    adc_driver: AdcDriver,
-    ble_advertising_driver: BleAdvertisingDriver,
-    ble_scanning_driver: BleScanningDriver,
-    button_driver: ButtonDriver,
-    console_driver: ConsoleDriver,
-    led_driver_factory: LedDriverFactory,
-    timer_context: DriverContext {
+    adc: AdcDriverFactory,
+    ble_advertising: BleAdvertisingDriver,
+    ble_scanning: BleScanningDriver,
+    buttons: ButtonsDriverFactory,
+    console: ConsoleDriver,
+    leds: LedsDriverFactory,
+    timer: DriverContext {
         active_timer: Cell::new(None),
     },
-    gpio_driver: GpioDriver,
-    temperature_driver: TemperatureDriver,
-    rng_driver: RngDriver,
+    gpio: GpioDriverFactory,
+    temperature: TemperatureDriverFactory,
+    rng: RngDriver,
     ambient_light_sensor: AmbientLightSensor,
     temperature_sensor: TemperatureSensor,
     humidity_sensor: HumiditySensor,
-    ninedof_driver: NinedofDriver,
+    ninedof: NinedofDriver,
 };
 
 static mut DRIVERS_SINGLETON: Option<Drivers> = Some(DRIVERS);
 
 #[cfg(test)]
 mod test {
-    use super::DRIVERS;
-    use super::DRIVERS_SINGLETON;
-    use crate::retrieve_drivers;
+    use super::*;
+
     #[test]
     pub fn can_be_retrieved_once() {
         reset_drivers_singleton();
diff --git a/src/electronics/shift_register.rs b/src/electronics/shift_register.rs
index 10eac0d..0e7c63e 100644
--- a/src/electronics/shift_register.rs
+++ b/src/electronics/shift_register.rs
@@ -1,17 +1,17 @@
-use crate::gpio::GpioPinWrite;
+use crate::gpio::GpioWrite;
 use crate::result::TockResult;
 
 pub struct ShiftRegister<'a> {
-    data_pin: GpioPinWrite<'a>,
-    clock_pin: GpioPinWrite<'a>,
-    latch_pin: GpioPinWrite<'a>,
+    data_pin: &'a GpioWrite<'a>,
+    clock_pin: &'a GpioWrite<'a>,
+    latch_pin: &'a GpioWrite<'a>,
 }
 
 impl<'a> ShiftRegister<'a> {
     pub fn new(
-        data_pin: GpioPinWrite<'a>,
-        clock_pin: GpioPinWrite<'a>,
-        latch_pin: GpioPinWrite<'a>,
+        data_pin: &'a GpioWrite<'a>,
+        clock_pin: &'a GpioWrite<'a>,
+        latch_pin: &'a GpioWrite<'a>,
     ) -> ShiftRegister<'a> {
         ShiftRegister {
             data_pin,
diff --git a/src/entry_point/mod.rs b/src/entry_point/mod.rs
index 6d299dd..cc5f402 100644
--- a/src/entry_point/mod.rs
+++ b/src/entry_point/mod.rs
@@ -85,7 +85,7 @@
 /// into the rustc-generated main(). This cannot use mutable global variables or
 /// global references to globals until it is done setting up the data segment.
 #[no_mangle]
-pub unsafe extern "C" fn rust_start(app_start: usize, stacktop: usize, app_heap_break: usize) -> ! {
+unsafe extern "C" fn rust_start(app_start: usize, stacktop: usize, app_heap_break: usize) -> ! {
     extern "C" {
         // This function is created internally by `rustc`. See
         // `src/lang_items.rs` for more details.
@@ -146,12 +146,9 @@
 use core::ptr::NonNull;
 use linked_list_allocator::Heap;
 
-#[global_allocator]
-static ALLOCATOR: TockAllocator = TockAllocator;
-
 static mut HEAP: Heap = Heap::empty();
 
-struct TockAllocator;
+pub struct TockAllocator;
 
 unsafe impl GlobalAlloc for TockAllocator {
     unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
diff --git a/src/gpio.rs b/src/gpio.rs
index 6f87e44..e69d5ee 100644
--- a/src/gpio.rs
+++ b/src/gpio.rs
@@ -1,12 +1,14 @@
+use crate::callback::CallbackSubscription;
+use crate::callback::Consumer;
 use crate::result::OtherError;
-use crate::result::TockError;
 use crate::result::TockResult;
 use crate::syscalls;
 use core::marker::PhantomData;
 
 const DRIVER_NUMBER: usize = 0x00004;
+
 mod command_nr {
-    pub const NUMBER_PINS: usize = 0;
+    pub const COUNT: usize = 0;
     pub const ENABLE_OUTPUT: usize = 1;
     pub const SET_HIGH: usize = 2;
     pub const SET_LOW: usize = 3;
@@ -23,50 +25,101 @@
 }
 
 #[non_exhaustive]
-pub struct GpioDriver;
+pub struct GpioDriverFactory;
 
-impl GpioDriver {
-    pub fn all_pins<'a>(&'a self) -> TockResult<GpioIter<'a>> {
-        let number = self.number_of_pins()?;
-        Ok(GpioIter {
-            curr_gpio: 0,
-            gpio_count: number,
-            phantom: PhantomData,
-        })
+impl GpioDriverFactory {
+    pub fn init_driver(&mut self) -> TockResult<GpioDriver> {
+        let driver = GpioDriver {
+            num_gpios: syscalls::command(DRIVER_NUMBER, command_nr::COUNT, 0, 0)?,
+            lifetime: PhantomData,
+        };
+        Ok(driver)
+    }
+}
+
+pub struct GpioDriver<'a> {
+    num_gpios: usize,
+    lifetime: PhantomData<&'a ()>,
+}
+
+impl<'a> GpioDriver<'a> {
+    pub fn num_gpios(&self) -> usize {
+        self.num_gpios
     }
 
-    pub fn pin<'a>(&'a self, pin: usize) -> TockResult<GpioPinUnitialized<'a>> {
-        let number = self.number_of_pins()?;
-        if pin < number {
-            Ok(GpioPinUnitialized {
-                number: pin,
-                phantom: PhantomData,
-            })
-        } else {
-            Err(TockError::Other(OtherError::NotEnoughGpioPins))
+    pub fn gpios(&mut self) -> Gpios {
+        Gpios {
+            num_gpios: self.num_gpios(),
+            curr_gpio: 0,
+            lifetime: PhantomData,
         }
     }
 
-    fn number_of_pins(&self) -> TockResult<usize> {
-        syscalls::command(DRIVER_NUMBER, command_nr::NUMBER_PINS, 0, 0).map_err(Into::into)
+    pub fn subscribe<CB: Fn(usize, GpioState)>(
+        &self,
+        callback: &'a mut CB,
+    ) -> TockResult<CallbackSubscription> {
+        syscalls::subscribe::<GpioEventConsumer, _>(
+            DRIVER_NUMBER,
+            subscribe_nr::SUBSCRIBE_CALLBACK,
+            callback,
+        )
+        .map_err(Into::into)
     }
 }
 
-#[derive(Copy, Clone)]
-pub struct GpioIter<'a> {
-    curr_gpio: usize,
-    gpio_count: usize,
-    phantom: PhantomData<&'a mut ()>,
+struct GpioEventConsumer;
+
+impl<CB: Fn(usize, GpioState)> Consumer<CB> for GpioEventConsumer {
+    fn consume(callback: &mut CB, gpio_num: usize, gpio_state: usize, _: usize) {
+        let gpio_state = match gpio_state {
+            0 => GpioState::Low,
+            1 => GpioState::High,
+            _ => return,
+        };
+        callback(gpio_num, gpio_state);
+    }
 }
 
-impl<'a> Iterator for GpioIter<'a> {
-    type Item = GpioPinUnitialized<'a>;
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum GpioState {
+    Low,
+    High,
+}
+
+impl From<GpioState> for bool {
+    fn from(gpio_state: GpioState) -> Self {
+        match gpio_state {
+            GpioState::Low => false,
+            GpioState::High => true,
+        }
+    }
+}
+
+impl From<bool> for GpioState {
+    fn from(from_value: bool) -> Self {
+        if from_value {
+            GpioState::Low
+        } else {
+            GpioState::High
+        }
+    }
+}
+
+pub struct Gpios<'a> {
+    num_gpios: usize,
+    curr_gpio: usize,
+    lifetime: PhantomData<&'a ()>,
+}
+
+impl<'a> Iterator for Gpios<'a> {
+    type Item = Gpio<'a>;
 
     fn next(&mut self) -> Option<Self::Item> {
-        if self.curr_gpio < self.gpio_count {
-            let item = GpioPinUnitialized {
-                number: self.curr_gpio,
-                phantom: PhantomData,
+        if self.curr_gpio < self.num_gpios {
+            let item = Gpio {
+                gpio_num: self.curr_gpio,
+                lifetime: PhantomData,
             };
             self.curr_gpio += 1;
             Some(item)
@@ -76,142 +129,131 @@
     }
 }
 
-pub enum InputMode {
-    PullUp,
-    PullDown,
-    PullNone,
+pub struct Gpio<'a> {
+    gpio_num: usize,
+    lifetime: PhantomData<&'a ()>,
 }
 
-pub enum IrqMode {
-    EitherEdge,
-    RisingEdge,
-    FallingEdge,
-}
-
-impl InputMode {
-    fn to_num(&self) -> usize {
-        match self {
-            InputMode::PullNone => 0,
-            InputMode::PullUp => 1,
-            InputMode::PullDown => 2,
-        }
-    }
-}
-
-impl IrqMode {
-    fn to_num(&self) -> usize {
-        match self {
-            IrqMode::EitherEdge => 0,
-            IrqMode::RisingEdge => 1,
-            IrqMode::FallingEdge => 2,
-        }
-    }
-}
-
-pub struct GpioPinUnitialized<'a> {
-    number: usize,
-    phantom: PhantomData<&'a mut ()>,
-}
-
-pub struct GpioPinWrite<'a> {
-    number: usize,
-    phantom: PhantomData<&'a mut ()>,
-}
-
-pub struct GpioPinRead<'a> {
-    number: usize,
-    phantom: PhantomData<&'a mut ()>,
-}
-
-impl<'a> GpioPinUnitialized<'a> {
-    pub fn open_for_write(self) -> TockResult<GpioPinWrite<'a>> {
-        syscalls::command(DRIVER_NUMBER, command_nr::ENABLE_OUTPUT, self.number, 0)?;
-        Ok(GpioPinWrite {
-            number: self.number,
-            phantom: PhantomData,
-        })
+impl<'a> Gpio<'a> {
+    pub fn enable_output(&mut self) -> TockResult<GpioWrite> {
+        syscalls::command(DRIVER_NUMBER, command_nr::ENABLE_OUTPUT, self.gpio_num, 0)?;
+        let gpio_write = GpioWrite {
+            gpio_num: self.gpio_num,
+            lifetime: PhantomData,
+        };
+        Ok(gpio_write)
     }
 
-    pub fn open_for_read(
-        self,
-        callback: Option<(extern "C" fn(usize, usize, usize, usize), IrqMode)>,
-        input_mode: InputMode,
-    ) -> TockResult<GpioPinRead<'a>> {
-        let (callback, irq_mode) = callback.unwrap_or((noop_callback, IrqMode::EitherEdge));
-        self.enable_input(input_mode)
-            .and_then(|pin| pin.subscribe_callback(callback))
-            .and_then(move |pin| pin.enable_callback(irq_mode))
-    }
-
-    fn subscribe_callback(
-        self,
-        callback: extern "C" fn(usize, usize, usize, usize),
-    ) -> TockResult<GpioPinUnitialized<'a>> {
-        syscalls::subscribe_fn(
-            DRIVER_NUMBER,
-            subscribe_nr::SUBSCRIBE_CALLBACK,
-            callback,
-            self.number,
-        )?;
-        Ok(self)
-    }
-
-    fn enable_input(self, mode: InputMode) -> TockResult<GpioPinUnitialized<'a>> {
+    pub fn enable_input(&mut self, resistor_mode: ResistorMode) -> TockResult<GpioRead> {
         syscalls::command(
             DRIVER_NUMBER,
             command_nr::ENABLE_INPUT,
-            self.number,
-            mode.to_num(),
+            self.gpio_num,
+            resistor_mode as usize,
         )?;
-        Ok(self)
+        let gpio_read = GpioRead {
+            gpio_num: self.gpio_num,
+            lifetime: PhantomData,
+        };
+        Ok(gpio_read)
+    }
+}
+
+pub struct GpioWrite<'a> {
+    gpio_num: usize,
+    lifetime: PhantomData<&'a ()>,
+}
+
+impl<'a> GpioWrite<'a> {
+    pub fn gpio_num(&self) -> usize {
+        self.gpio_num
     }
 
-    fn enable_callback(self, irq_mode: IrqMode) -> TockResult<GpioPinRead<'a>> {
+    pub fn set(&self, state: impl Into<GpioState>) -> TockResult<()> {
+        match state.into() {
+            GpioState::Low => self.set_low(),
+            GpioState::High => self.set_high(),
+        }
+    }
+
+    pub fn set_low(&self) -> TockResult<()> {
+        syscalls::command(DRIVER_NUMBER, command_nr::SET_LOW, self.gpio_num, 0)?;
+        Ok(())
+    }
+
+    pub fn set_high(&self) -> TockResult<()> {
+        syscalls::command(DRIVER_NUMBER, command_nr::SET_HIGH, self.gpio_num, 0)?;
+        Ok(())
+    }
+
+    pub fn toggle(&self) -> TockResult<()> {
+        syscalls::command(DRIVER_NUMBER, command_nr::TOGGLE, self.gpio_num, 0)?;
+        Ok(())
+    }
+}
+
+impl<'a> Drop for GpioWrite<'a> {
+    fn drop(&mut self) {
+        let _ = syscalls::command(DRIVER_NUMBER, command_nr::DISABLE, self.gpio_num, 0);
+    }
+}
+
+pub struct GpioRead<'a> {
+    gpio_num: usize,
+    lifetime: PhantomData<&'a ()>,
+}
+
+impl<'a> GpioRead<'a> {
+    pub fn gpio_num(&self) -> usize {
+        self.gpio_num
+    }
+
+    pub fn read(&self) -> TockResult<GpioState> {
+        let button_state = syscalls::command(DRIVER_NUMBER, command_nr::READ, self.gpio_num, 0)?;
+        match button_state {
+            0 => Ok(GpioState::Low),
+            1 => Ok(GpioState::High),
+            _ => Err(OtherError::GpioDriverInvalidState.into()),
+        }
+    }
+
+    pub fn enable_interrupt(&self, trigger_type: TriggerType) -> TockResult<()> {
         syscalls::command(
             DRIVER_NUMBER,
             command_nr::ENABLE_INTERRUPT,
-            self.number,
-            irq_mode.to_num(),
+            self.gpio_num,
+            trigger_type as usize,
         )?;
-        Ok(GpioPinRead {
-            number: self.number,
-            phantom: PhantomData,
-        })
+        Ok(())
     }
-}
 
-impl<'a> GpioPinWrite<'a> {
-    pub fn set_low(&self) -> TockResult<()> {
-        syscalls::command(DRIVER_NUMBER, command_nr::SET_LOW, self.number, 0)?;
-        Ok(())
-    }
-    pub fn set_high(&self) -> TockResult<()> {
-        syscalls::command(DRIVER_NUMBER, command_nr::SET_HIGH, self.number, 0)?;
-        Ok(())
-    }
-    pub fn toggle(&self) -> TockResult<()> {
-        syscalls::command(DRIVER_NUMBER, command_nr::TOGGLE, self.number, 0)?;
+    pub fn disable_interrupt(&self, trigger_type: TriggerType) -> TockResult<()> {
+        syscalls::command(
+            DRIVER_NUMBER,
+            command_nr::DISABLE_INTERRUPT,
+            self.gpio_num,
+            trigger_type as usize,
+        )?;
         Ok(())
     }
 }
 
-impl<'a> GpioPinRead<'a> {
-    pub fn read(&'a self) -> bool {
-        syscalls::command(DRIVER_NUMBER, command_nr::READ, self.number, 0).ok() == Some(1)
-    }
-}
-
-impl<'a> Drop for GpioPinWrite<'a> {
+impl<'a> Drop for GpioRead<'a> {
     fn drop(&mut self) {
-        let _ = syscalls::command(DRIVER_NUMBER, command_nr::DISABLE, self.number, 0);
+        let _ = syscalls::command(DRIVER_NUMBER, command_nr::DISABLE, self.gpio_num, 0);
     }
 }
 
-impl<'a> Drop for GpioPinRead<'a> {
-    fn drop(&mut self) {
-        let _ = syscalls::command(DRIVER_NUMBER, command_nr::DISABLE_INTERRUPT, self.number, 0);
-        let _ = syscalls::command(DRIVER_NUMBER, command_nr::DISABLE, self.number, 0);
-    }
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum ResistorMode {
+    PullNone = 0,
+    PullUp = 1,
+    PullDown = 2,
 }
 
-extern "C" fn noop_callback(_: usize, _: usize, _: usize, _: usize) {}
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum TriggerType {
+    EitherEdge = 0,
+    RisingEdge = 1,
+    FallingEdge = 2,
+}
diff --git a/src/lang_items.rs b/src/lang_items.rs
index 6ff7529..69ddf85 100644
--- a/src/lang_items.rs
+++ b/src/lang_items.rs
@@ -18,10 +18,12 @@
 //! `rustc_main`. That's covered by the `_start` function in the root of this
 //! crate.
 
-use crate::led::LedDriver;
+use crate::drivers;
+use crate::entry_point::TockAllocator;
+use crate::leds::LedsDriver;
+use crate::result::TockResult;
 use crate::timer::Duration;
 use crate::timer::ParallelSleepDriver;
-use crate::Drivers;
 use core::alloc::Layout;
 use core::executor;
 use core::panic::PanicInfo;
@@ -48,63 +50,63 @@
 
     // Flash all LEDs (if available).
     executor::block_on(async {
-        let Drivers {
-            led_driver_factory,
-            timer_context,
-            ..
-        } = crate::retrieve_drivers_unsafe();
-        let mut driver = timer_context.create_timer_driver();
-        let timer_driver = driver.activate().ok();
-        let led_driver = led_driver_factory.create_driver().ok();
-        if let (Some(ref led_driver), Some(ref timer_driver)) = (led_driver, timer_driver) {
-            blink_all_leds(timer_driver, led_driver).await;
+        let mut drivers = drivers::retrieve_drivers_unsafe();
+
+        let leds_driver = drivers.leds.init_driver();
+        let mut timer_driver = drivers.timer.create_timer_driver();
+        let timer_driver = timer_driver.activate();
+
+        if let (Ok(leds_driver), Ok(timer_driver)) = (leds_driver, timer_driver) {
+            let _ = blink_all_leds(&leds_driver, &timer_driver).await;
         }
         loop {}
-    });
-    // Never type is not supported for T in Future
-    unreachable!()
+    })
 }
 
-async fn blink_all_leds(timer_driver: &ParallelSleepDriver<'_>, led_driver: &LedDriver) {
+async fn blink_all_leds(
+    leds_driver: &LedsDriver<'_>,
+    timer_driver: &ParallelSleepDriver<'_>,
+) -> TockResult<()> {
     loop {
-        for led in led_driver.all() {
-            let _ = led.on();
+        for led in leds_driver.leds() {
+            led.on()?;
         }
-        let _ = timer_driver.sleep(Duration::from_ms(100)).await;
-        for led in led_driver.all() {
-            let _ = led.off();
+        timer_driver.sleep(Duration::from_ms(100)).await?;
+        for led in leds_driver.leds() {
+            led.off()?;
         }
-        let _ = timer_driver.sleep(Duration::from_ms(100)).await;
+        timer_driver.sleep(Duration::from_ms(100)).await?;
     }
 }
 
+#[global_allocator]
+static ALLOCATOR: TockAllocator = TockAllocator;
+
 #[alloc_error_handler]
 unsafe fn alloc_error_handler(_: Layout) -> ! {
     executor::block_on(async {
-        let Drivers {
-            led_driver_factory,
-            timer_context,
-            ..
-        } = crate::retrieve_drivers_unsafe();
-        let mut driver = timer_context.create_timer_driver();
-        let timer_driver = driver.activate().ok();
-        let led_driver = led_driver_factory.create_driver().ok();
+        let mut drivers = drivers::retrieve_drivers_unsafe();
 
-        if let (Some(led_driver), Some(timer_driver)) = (led_driver, timer_driver) {
-            cycle_all_leds(&timer_driver, &led_driver).await;
+        let leds_driver = drivers.leds.init_driver();
+        let mut timer_driver = drivers.timer.create_timer_driver();
+        let timer_driver = timer_driver.activate();
+
+        if let (Ok(leds_driver), Ok(timer_driver)) = (leds_driver, timer_driver) {
+            let _ = cycle_all_leds(&leds_driver, &timer_driver).await;
         }
         loop {}
-    });
-    // Never type is not supported for T in Future
-    unreachable!()
+    })
 }
 
-async fn cycle_all_leds(timer_driver: &ParallelSleepDriver<'_>, led_driver: &LedDriver) {
+async fn cycle_all_leds(
+    leds_driver: &LedsDriver<'_>,
+    timer_driver: &ParallelSleepDriver<'_>,
+) -> TockResult<()> {
     loop {
-        for led in led_driver.all() {
-            let _ = led.on();
-            let _ = timer_driver.sleep(Duration::from_ms(100)).await;
-            let _ = led.off();
+        for led in leds_driver.leds() {
+            led.on()?;
+            timer_driver.sleep(Duration::from_ms(100)).await?;
+            led.off()?;
         }
     }
 }
diff --git a/src/led.rs b/src/led.rs
deleted file mode 100644
index 6e69944..0000000
--- a/src/led.rs
+++ /dev/null
@@ -1,111 +0,0 @@
-use crate::result::TockResult;
-use crate::syscalls::command;
-use core::marker::PhantomData;
-
-const DRIVER_NUMBER: usize = 0x00002;
-
-mod command_nr {
-    pub const COUNT: usize = 0;
-    pub const ON: usize = 1;
-    pub const OFF: usize = 2;
-    pub const TOGGLE: usize = 3;
-}
-
-#[non_exhaustive]
-pub struct LedDriverFactory;
-
-impl LedDriverFactory {
-    pub fn create_driver(self) -> TockResult<LedDriver> {
-        command(DRIVER_NUMBER, command_nr::COUNT, 0, 0)?;
-        Ok(LedDriver)
-    }
-}
-
-#[non_exhaustive]
-pub struct LedDriver;
-
-pub struct Led<'a> {
-    led_num: usize,
-    phantom: PhantomData<&'a mut ()>,
-}
-
-impl LedDriver {
-    pub fn get(&self, led_num: usize) -> Option<Led> {
-        if led_num < self.count().ok().unwrap() {
-            Some(Led {
-                led_num,
-                phantom: PhantomData,
-            })
-        } else {
-            None
-        }
-    }
-
-    pub fn count(&self) -> TockResult<usize> {
-        command(DRIVER_NUMBER, command_nr::COUNT, 0, 0).map_err(Into::into)
-    }
-
-    pub fn all(&self) -> LedIter {
-        LedIter {
-            curr_led: 0,
-            led_count: self.count().unwrap_or(0),
-            phantom: PhantomData,
-        }
-    }
-}
-
-/// Returns an iterator over all available LEDs. If the LED driver is not
-/// present, the iterator will be empty.
-
-impl<'a> Led<'a> {
-    pub fn set_state(&self, state: bool) -> TockResult<()> {
-        if state {
-            self.on()
-        } else {
-            self.off()
-        }
-    }
-
-    pub fn on(&self) -> TockResult<()> {
-        command(DRIVER_NUMBER, command_nr::ON, self.led_num, 0)?;
-        Ok(())
-    }
-
-    pub fn off(&self) -> TockResult<()> {
-        command(DRIVER_NUMBER, command_nr::OFF, self.led_num, 0)?;
-        Ok(())
-    }
-
-    pub fn toggle(&self) -> TockResult<()> {
-        command(DRIVER_NUMBER, command_nr::TOGGLE, self.led_num, 0)?;
-        Ok(())
-    }
-
-    pub fn number(&self) -> usize {
-        self.led_num
-    }
-}
-
-#[derive(Copy, Clone)]
-pub struct LedIter<'a> {
-    curr_led: usize,
-    led_count: usize,
-    phantom: PhantomData<&'a mut ()>,
-}
-
-impl<'a> Iterator for LedIter<'a> {
-    type Item = Led<'a>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.curr_led < self.led_count {
-            let item = Led {
-                led_num: self.curr_led,
-                phantom: PhantomData,
-            };
-            self.curr_led += 1;
-            Some(item)
-        } else {
-            None
-        }
-    }
-}
diff --git a/src/leds.rs b/src/leds.rs
new file mode 100644
index 0000000..e0f420c
--- /dev/null
+++ b/src/leds.rs
@@ -0,0 +1,129 @@
+use crate::result::OutOfRangeError;
+use crate::result::TockResult;
+use crate::syscalls::command;
+use core::marker::PhantomData;
+
+const DRIVER_NUMBER: usize = 0x00002;
+
+mod command_nr {
+    pub const COUNT: usize = 0;
+    pub const ON: usize = 1;
+    pub const OFF: usize = 2;
+    pub const TOGGLE: usize = 3;
+}
+
+#[non_exhaustive]
+pub struct LedsDriverFactory;
+
+impl LedsDriverFactory {
+    pub fn init_driver(&mut self) -> TockResult<LedsDriver> {
+        let driver = LedsDriver {
+            num_leds: command(DRIVER_NUMBER, command_nr::COUNT, 0, 0)?,
+            lifetime: PhantomData,
+        };
+        Ok(driver)
+    }
+}
+
+pub struct LedsDriver<'a> {
+    num_leds: usize,
+    lifetime: PhantomData<&'a ()>,
+}
+
+impl<'a> LedsDriver<'a> {
+    pub fn num_leds(&self) -> usize {
+        self.num_leds
+    }
+
+    pub fn leds(&self) -> Leds {
+        Leds {
+            num_leds: self.num_leds,
+            curr_led: 0,
+            lifetime: PhantomData,
+        }
+    }
+
+    /// Returns the led at 0-based index `led_num`
+    pub fn get(&self, led_num: usize) -> Result<Led, OutOfRangeError> {
+        if led_num < self.num_leds {
+            Ok(Led {
+                led_num,
+                lifetime: PhantomData,
+            })
+        } else {
+            Err(OutOfRangeError)
+        }
+    }
+}
+
+pub struct Leds<'a> {
+    num_leds: usize,
+    curr_led: usize,
+    lifetime: PhantomData<&'a ()>,
+}
+
+impl<'a> Iterator for Leds<'a> {
+    type Item = Led<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.curr_led < self.num_leds {
+            let item = Led {
+                led_num: self.curr_led,
+                lifetime: PhantomData,
+            };
+            self.curr_led += 1;
+            Some(item)
+        } else {
+            None
+        }
+    }
+}
+
+pub struct Led<'a> {
+    led_num: usize,
+    lifetime: PhantomData<&'a ()>,
+}
+
+impl<'a> Led<'a> {
+    pub fn led_num(&self) -> usize {
+        self.led_num
+    }
+
+    pub fn set(&self, state: impl Into<LedState>) -> TockResult<()> {
+        match state.into() {
+            LedState::On => self.on(),
+            LedState::Off => self.off(),
+        }
+    }
+
+    pub fn on(&self) -> TockResult<()> {
+        command(DRIVER_NUMBER, command_nr::ON, self.led_num, 0)?;
+        Ok(())
+    }
+
+    pub fn off(&self) -> TockResult<()> {
+        command(DRIVER_NUMBER, command_nr::OFF, self.led_num, 0)?;
+        Ok(())
+    }
+
+    pub fn toggle(&self) -> TockResult<()> {
+        command(DRIVER_NUMBER, command_nr::TOGGLE, self.led_num, 0)?;
+        Ok(())
+    }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum LedState {
+    On,
+    Off,
+}
+
+impl From<bool> for LedState {
+    fn from(from_value: bool) -> Self {
+        if from_value {
+            LedState::On
+        } else {
+            LedState::Off
+        }
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 1c1ecf0..e7d9d63 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,41 +1,32 @@
 #![feature(asm, alloc_error_handler, lang_items, naked_functions)]
 #![cfg_attr(any(target_arch = "arm", target_arch = "riscv32"), no_std)]
 
-mod callback;
+mod entry_point;
+#[cfg(any(target_arch = "arm", target_arch = "riscv32"))]
+mod lang_items;
 
 pub mod adc;
 pub mod ble_composer;
 pub mod ble_parser;
 pub mod buttons;
+pub mod callback;
 pub mod console;
 pub mod debug;
+pub mod drivers;
 pub mod electronics;
 pub mod futures;
 pub mod gpio;
-pub mod led;
+pub mod leds;
 pub mod memop;
 pub mod result;
 pub mod rng;
 pub mod sensors;
 pub mod shared_memory;
 pub mod simple_ble;
+pub mod syscalls;
 pub mod temperature;
 pub mod timer;
 pub mod unwind_symbols;
 
-#[cfg(any(target_arch = "arm", target_arch = "riscv32"))]
-pub mod entry_point;
-
-#[cfg(any(target_arch = "arm", target_arch = "riscv32"))]
-mod lang_items;
-
-pub mod syscalls;
-
+pub use drivers::retrieve_drivers;
 pub use libtock_codegen::main;
-
-pub(crate) mod drivers;
-pub use drivers::*;
-
-/// Dummy structure to force importing the panic_handler and other no_std elements when nothing else
-/// is imported.
-pub struct LibTock;
diff --git a/src/result.rs b/src/result.rs
index f3929ca..9dc4f16 100644
--- a/src/result.rs
+++ b/src/result.rs
@@ -67,10 +67,12 @@
 
 #[derive(Copy, Clone)]
 pub enum OtherError {
+    ButtonsDriverInvalidState,
+    GpioDriverInvalidState,
     TimerDriverDurationOutOfRange,
     TimerDriverErroneousClockFrequency,
     DriverAlreadyTaken,
-    NotEnoughGpioPins,
+    OutOfRangeError,
 }
 
 impl From<OtherError> for TockError {
@@ -79,6 +81,14 @@
     }
 }
 
+pub struct OutOfRangeError;
+
+impl From<OutOfRangeError> for TockError {
+    fn from(_other: OutOfRangeError) -> Self {
+        TockError::Other(OtherError::OutOfRangeError)
+    }
+}
+
 pub const SUCCESS: isize = 0;
 pub const FAIL: isize = -1;
 pub const EBUSY: isize = -2;
diff --git a/src/rng.rs b/src/rng.rs
index e04500f..039fa77 100644
--- a/src/rng.rs
+++ b/src/rng.rs
@@ -1,3 +1,4 @@
+use crate::callback::Identity0Consumer;
 use crate::futures;
 use crate::result::TockResult;
 use crate::syscalls;
@@ -26,8 +27,8 @@
         let buf_len = buf.len();
         let shared_memory = syscalls::allow(DRIVER_NUMBER, allow_nr::SHARE_BUFFER, buf)?;
         let is_filled = Cell::new(false);
-        let mut is_filled_alarm = |_, _, _| is_filled.set(true);
-        let subscription = syscalls::subscribe(
+        let mut is_filled_alarm = || is_filled.set(true);
+        let subscription = syscalls::subscribe::<Identity0Consumer, _>(
             DRIVER_NUMBER,
             subscribe_nr::BUFFER_FILLED,
             &mut is_filled_alarm,
diff --git a/src/simple_ble.rs b/src/simple_ble.rs
index f700385..2876bce 100644
--- a/src/simple_ble.rs
+++ b/src/simple_ble.rs
@@ -1,6 +1,6 @@
 use crate::ble_composer::BlePayload;
 use crate::callback::CallbackSubscription;
-use crate::callback::SubscribableCallback;
+use crate::callback::Consumer;
 use crate::result::TockResult;
 use crate::shared_memory::SharedMemory;
 use crate::syscalls;
@@ -79,9 +79,9 @@
     }
 }
 
-impl<CB: FnMut(usize, usize)> SubscribableCallback for BleCallback<CB> {
-    fn call_rust(&mut self, arg1: usize, arg2: usize, _: usize) {
-        (self.callback)(arg1, arg2);
+impl<CB: FnMut(usize, usize)> Consumer<BleCallback<CB>> for BleCallback<CB> {
+    fn consume(data: &mut BleCallback<CB>, arg1: usize, arg2: usize, _: usize) {
+        (data.callback)(arg1, arg2);
     }
 }
 
@@ -100,15 +100,15 @@
         syscalls::allow(DRIVER_NUMBER, allow_nr::ALLOW_SCAN_BUFFER, scan_buffer).map_err(Into::into)
     }
 
-    pub fn start<'a, CB>(
+    pub fn start<'a, CB: FnMut(usize, usize)>(
         &'a mut self,
         callback: &'a mut BleCallback<CB>,
-    ) -> TockResult<CallbackSubscription>
-    where
-        BleCallback<CB>: SubscribableCallback,
-    {
-        let subscription =
-            syscalls::subscribe(DRIVER_NUMBER, subscribe_nr::BLE_PASSIVE_SCAN_SUB, callback)?;
+    ) -> TockResult<CallbackSubscription> {
+        let subscription = syscalls::subscribe::<BleCallback<CB>, _>(
+            DRIVER_NUMBER,
+            subscribe_nr::BLE_PASSIVE_SCAN_SUB,
+            callback,
+        )?;
         syscalls::command(DRIVER_NUMBER, command_nr::PASSIVE_SCAN, 1, 0)?;
         Ok(subscription)
     }
diff --git a/src/syscalls/mod.rs b/src/syscalls/mod.rs
index 4cbef0f..c0289e5 100644
--- a/src/syscalls/mod.rs
+++ b/src/syscalls/mod.rs
@@ -7,7 +7,7 @@
 mod platform;
 
 use crate::callback::CallbackSubscription;
-use crate::callback::SubscribableCallback;
+use crate::callback::Consumer;
 use crate::result::AllowError;
 use crate::result::CommandError;
 use crate::result::SubscribeError;
@@ -33,26 +33,26 @@
     }
 }
 
-pub fn subscribe<CB: SubscribableCallback>(
+pub fn subscribe<C: Consumer<T>, T>(
     driver_number: usize,
     subscribe_number: usize,
-    callback: &mut CB,
+    payload: &mut T,
 ) -> Result<CallbackSubscription, SubscribeError> {
-    extern "C" fn c_callback<CB: SubscribableCallback>(
+    extern "C" fn c_callback<T, C: Consumer<T>>(
         arg1: usize,
         arg2: usize,
         arg3: usize,
         data: usize,
     ) {
-        let callback = unsafe { &mut *(data as *mut CB) };
-        callback.call_rust(arg1, arg2, arg3);
+        let payload = unsafe { &mut *(data as *mut T) };
+        C::consume(payload, arg1, arg2, arg3);
     }
 
     subscribe_fn(
         driver_number,
         subscribe_number,
-        c_callback::<CB>,
-        callback as *mut CB as usize,
+        c_callback::<T, C>,
+        payload as *mut _ as usize,
     )
     .map(|_| CallbackSubscription::new(driver_number, subscribe_number))
 }
diff --git a/src/temperature.rs b/src/temperature.rs
index 4420791..1f1dcb1 100644
--- a/src/temperature.rs
+++ b/src/temperature.rs
@@ -1,14 +1,18 @@
+use crate::callback::Identity1Consumer;
 use crate::futures;
 use crate::result::TockError;
+use crate::result::TockResult;
 use crate::syscalls;
 use core::cell::Cell;
 use core::fmt;
 use core::fmt::Display;
+use core::marker::PhantomData;
 use core::mem;
 
 const DRIVER_NUMBER: usize = 0x60000;
 
 mod command_nr {
+    pub const IS_DRIVER_AVAILABLE: usize = 0;
     pub const START_MEASUREMENT: usize = 1;
 }
 
@@ -17,25 +21,40 @@
 }
 
 #[non_exhaustive]
-pub struct TemperatureDriver;
+pub struct TemperatureDriverFactory;
 
-impl TemperatureDriver {
+impl TemperatureDriverFactory {
+    pub fn init_driver(&mut self) -> TockResult<TemperatureDriver> {
+        syscalls::command(DRIVER_NUMBER, command_nr::IS_DRIVER_AVAILABLE, 0, 0)?;
+        let driver = TemperatureDriver {
+            lifetime: PhantomData,
+        };
+        Ok(driver)
+    }
+}
+
+pub struct TemperatureDriver<'a> {
+    lifetime: PhantomData<&'a ()>,
+}
+
+impl<'a> TemperatureDriver<'a> {
     pub async fn measure_temperature(&mut self) -> Result<Temperature, TockError> {
-        let temperature = Cell::<Option<isize>>::new(None);
-        let mut callback = |arg1, _, _| temperature.set(Some(arg1 as isize));
-        let subscription = syscalls::subscribe(
+        let temperature = Cell::new(None);
+        let mut callback = |centi_celsius| temperature.set(Some(centi_celsius as isize));
+        let subscription = syscalls::subscribe::<Identity1Consumer, _>(
             DRIVER_NUMBER,
             subscribe_nr::SUBSCRIBE_CALLBACK,
             &mut callback,
         )?;
         syscalls::command(DRIVER_NUMBER, command_nr::START_MEASUREMENT, 0, 0)?;
-        let temperatur = Temperature {
+        let result = Temperature {
             centi_celsius: futures::wait_for_value(|| temperature.get()).await,
         };
         mem::drop(subscription);
-        Ok(temperatur)
+        Ok(result)
     }
 }
+
 #[derive(Copy, Clone)]
 pub struct Temperature {
     centi_celsius: isize,
diff --git a/src/timer.rs b/src/timer.rs
index 80adc1a..946c6e5 100644
--- a/src/timer.rs
+++ b/src/timer.rs
@@ -1,7 +1,7 @@
 //! Async timer driver. Can be used for (non-busy)  sleeping.
 
 use crate::callback::CallbackSubscription;
-use crate::callback::SubscribableCallback;
+use crate::callback::Consumer;
 use crate::futures;
 use crate::result::OtherError;
 use crate::result::TockError;
@@ -33,22 +33,21 @@
     phantom: PhantomData<&'a mut ()>,
 }
 
-impl<CB: FnMut(ClockValue, Alarm)> SubscribableCallback for WithCallback<'_, CB> {
-    fn call_rust(&mut self, clock_value: usize, alarm_id: usize, _: usize) {
-        (self.callback)(
+struct TimerEventConsumer;
+
+impl<CB: FnMut(ClockValue, Alarm)> Consumer<WithCallback<'_, CB>> for TimerEventConsumer {
+    fn consume(data: &mut WithCallback<CB>, clock_value: usize, alarm_id: usize, _: usize) {
+        (data.callback)(
             ClockValue {
                 num_ticks: clock_value as isize,
-                clock_frequency: self.clock_frequency,
+                clock_frequency: data.clock_frequency,
             },
             Alarm { alarm_id },
         );
     }
 }
 
-impl<'a, CB> WithCallback<'a, CB>
-where
-    Self: SubscribableCallback,
-{
+impl<'a, CB: FnMut(ClockValue, Alarm)> WithCallback<'a, CB> {
     pub fn init(&'a mut self) -> TockResult<Timer<'a>> {
         let num_notifications =
             syscalls::command(DRIVER_NUMBER, command_nr::IS_DRIVER_AVAILABLE, 0, 0)?;
@@ -64,8 +63,11 @@
             hz: clock_frequency,
         };
 
-        let subscription =
-            syscalls::subscribe(DRIVER_NUMBER, subscribe_nr::SUBSCRIBE_CALLBACK, self)?;
+        let subscription = syscalls::subscribe::<TimerEventConsumer, _>(
+            DRIVER_NUMBER,
+            subscribe_nr::SUBSCRIBE_CALLBACK,
+            self,
+        )?;
 
         Ok(Timer {
             num_notifications,
@@ -286,12 +288,10 @@
 /// Context for the time driver.
 /// You can create a context as follows:
 /// ```no_run
-/// # use libtock::timer::DriverContext;
 /// # use libtock::result::TockResult;
-/// # use libtock::Drivers;
-/// # #[libtock::main]
-/// # async fn main() -> TockResult<()> {
-///  let Drivers {  timer_context, .. } = libtock::retrieve_drivers()?;
+/// # async fn doc() -> TockResult<()> {
+/// let mut drivers = libtock::retrieve_drivers()?;
+/// let mut timer_context = drivers.timer;
 /// # Ok(())
 /// # }
 /// ```
@@ -302,10 +302,10 @@
 
 impl DriverContext {
     /// Create a driver timer from a context.
-    pub fn create_timer_driver(&self) -> TimerDriver<'_> {
+    pub fn create_timer_driver(&mut self) -> TimerDriver {
         TimerDriver {
             callback: Callback,
-            context: &self,
+            context: self,
         }
     }
 
@@ -320,14 +320,12 @@
 
 /// Timer driver instance. You can create a TimerDriver from a DriverContext as follows:
 /// ```no_run
-/// # use libtock::timer::DriverContext;
 /// # use libtock::result::TockResult;
-/// # use libtock::Drivers;
-/// # #[libtock::main]
-/// # async fn main() -> TockResult<()> {
-/// # let Drivers {  timer_context,.. } = libtock::retrieve_drivers()?;
-/// # let mut driver = timer_context.create_timer_driver();
-/// let timer_driver = driver.activate()?;
+/// # async fn doc() -> TockResult<()> {
+/// # let mut drivers = libtock::retrieve_drivers()?;
+/// # let mut timer_context = drivers.timer;
+/// let mut timer_driver = timer_context.create_timer_driver();
+/// let timer_driver = timer_driver.activate()?;
 /// # Ok(())
 /// # }
 /// ```
@@ -338,23 +336,22 @@
 
 struct Callback;
 
-impl SubscribableCallback for Callback {
-    fn call_rust(&mut self, _: usize, _: usize, _: usize) {}
+struct ParallelTimerConsumer;
+
+impl<'a> Consumer<Callback> for ParallelTimerConsumer {
+    fn consume(_: &mut Callback, _: usize, _: usize, _: usize) {}
 }
 
 /// Activated time driver. Updates current time in the context and manages
 /// active alarms.
 /// Example usage (sleep for 1 second):
 /// ```no_run
-/// # use libtock::timer::DriverContext;
 /// # use libtock::result::TockResult;
 /// # use libtock::timer::Duration;
-/// # use libtock::Drivers;
-/// # #[libtock::main]
-/// # async fn main() -> TockResult<()> {
-/// # let Drivers {  timer_context,.. } = libtock::retrieve_drivers()?;
-/// # let mut driver = timer_context.create_timer_driver();
-/// let timer_driver = driver.activate()?;
+/// # async fn doc() -> TockResult<()> {
+/// # let mut drivers = libtock::retrieve_drivers()?;
+/// # let mut timer_driver = drivers.timer.create_timer_driver();
+/// let timer_driver = timer_driver.activate()?;
 /// timer_driver.sleep(Duration::from_ms(1000)).await?;
 /// # Ok(())
 /// # }
@@ -368,7 +365,7 @@
     /// Activate the timer driver, will return a ParallelSleepDriver which
     /// can used to sleep.
     pub fn activate(&'a mut self) -> TockResult<ParallelSleepDriver<'a>> {
-        let subscription = syscalls::subscribe(
+        let subscription = syscalls::subscribe::<ParallelTimerConsumer, _>(
             DRIVER_NUMBER,
             subscribe_nr::SUBSCRIBE_CALLBACK,
             &mut self.callback,
diff --git a/src/unwind_symbols.rs b/src/unwind_symbols.rs
index 98debbe..4215626 100644
--- a/src/unwind_symbols.rs
+++ b/src/unwind_symbols.rs
@@ -1,3 +1,5 @@
+#![doc(hidden)]
+
 // The stack unwinding ABI in ARM is specified as part of EABI. Although
 // libunwind has not been ported to Tock OS (and likely will not be), LLVM still
 // assumes some of the symbols are present. This causes linking errors
@@ -6,6 +8,7 @@
 // functions; for example, the Linux Kernel does so in arch/arm/kernel/unwind.c.
 // We do so here as well. The addition of these symbols to libtock-rs was
 // discussed at https://groups.google.com/forum/#!topic/tock-dev/eov8fJmskLk.
+
 #[cfg(target_arch = "arm")]
 #[no_mangle]
 pub extern "C" fn __aeabi_unwind_cpp_pr0() {}