diff --git a/docgen/Cargo.toml b/docgen/Cargo.toml index 3e384c26..69ade174 100644 --- a/docgen/Cargo.toml +++ b/docgen/Cargo.toml @@ -13,4 +13,4 @@ license = "Apache-2.0 or MIT" crate-type = ["staticlib"] [dependencies] -zephyr = "0.1.0" +zephyr = { version = "0.1.0", features = ["executor-zephyr", "async-drivers"] } diff --git a/docgen/prj.conf b/docgen/prj.conf index 6ffe361e..4c55eb98 100644 --- a/docgen/prj.conf +++ b/docgen/prj.conf @@ -4,5 +4,6 @@ CONFIG_RUST=y CONFIG_RUST_ALLOC=y CONFIG_GPIO=y +CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT=y CONFIG_PRINTK=y CONFIG_POLL=y diff --git a/dt-rust.yaml b/dt-rust.yaml index 878913db..8eb8db92 100644 --- a/dt-rust.yaml +++ b/dt-rust.yaml @@ -15,6 +15,7 @@ raw: type: myself device: crate::device::gpio::Gpio + static_type: crate::device::gpio::GpioStatic # The gpio-leds node will have #children nodes describing each led. We'll match on the parent # having this compatible property. The nodes themselves are built out of the properties associated diff --git a/tests/drivers/gpio-async/CMakeLists.txt b/tests/drivers/gpio-async/CMakeLists.txt new file mode 100644 index 00000000..3a7f7ab5 --- /dev/null +++ b/tests/drivers/gpio-async/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(gpio_async) +rust_cargo_application() diff --git a/tests/drivers/gpio-async/Cargo.toml b/tests/drivers/gpio-async/Cargo.toml new file mode 100644 index 00000000..ed59653b --- /dev/null +++ b/tests/drivers/gpio-async/Cargo.toml @@ -0,0 +1,45 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +[package] +# This must be rustapp for now. +name = "rustapp" +version = "0.1.0" +edition = "2021" +description = "Test async gpios" +license = "Apache-2.0 or MIT" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +zephyr = { version = "0.1.0", features = ["time-driver", "executor-zephyr", "async-drivers"] } +log = "0.4.22" +static_cell = "2.1" +heapless = "0.8" + +[dependencies.embassy-executor] +version = "0.7.0" +features = [ + "log", + "task-arena-size-2048", +] + +[dependencies.embassy-futures] +version = "0.1.1" + +[dependencies.embassy-sync] +version = "0.6.2" + +[dependencies.embassy-time] +version = "0.4.0" +features = ["tick-hz-10_000"] + +[dependencies.critical-section] +version = "1.2" + +[profile.dev] +opt-level = 1 + +[profile.release] +debug = true diff --git a/tests/drivers/gpio-async/boards/pimoroni_tiny_2040.overlay b/tests/drivers/gpio-async/boards/pimoroni_tiny_2040.overlay new file mode 100644 index 00000000..6b33d86d --- /dev/null +++ b/tests/drivers/gpio-async/boards/pimoroni_tiny_2040.overlay @@ -0,0 +1,13 @@ +/ { + gpio-leds { + compatible = "gpio-leds"; + col0: col0 { + gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>; + label = "Column 0"; + }; + row0: row0 { + gpios = <&gpio0 0 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; + label = "Row 0"; + }; + }; +}; diff --git a/tests/drivers/gpio-async/boards/rpi_pico.overlay b/tests/drivers/gpio-async/boards/rpi_pico.overlay new file mode 100644 index 00000000..6b33d86d --- /dev/null +++ b/tests/drivers/gpio-async/boards/rpi_pico.overlay @@ -0,0 +1,13 @@ +/ { + gpio-leds { + compatible = "gpio-leds"; + col0: col0 { + gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>; + label = "Column 0"; + }; + row0: row0 { + gpios = <&gpio0 0 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; + label = "Row 0"; + }; + }; +}; diff --git a/tests/drivers/gpio-async/pimoroni_tiny_2040.conf b/tests/drivers/gpio-async/pimoroni_tiny_2040.conf new file mode 100644 index 00000000..94c0843a --- /dev/null +++ b/tests/drivers/gpio-async/pimoroni_tiny_2040.conf @@ -0,0 +1,7 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +# This board doesn't have a serial console, so use RTT. +CONFIG_UART_CONSOLE=n +CONFIG_RTT_CONSOLE=y +CONFIG_USE_SEGGER_RTT=y diff --git a/tests/drivers/gpio-async/prj.conf b/tests/drivers/gpio-async/prj.conf new file mode 100644 index 00000000..f9a269ba --- /dev/null +++ b/tests/drivers/gpio-async/prj.conf @@ -0,0 +1,16 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_DEBUG=y + +# The default 1k stack isn't large enough for rust string formatting with logging. +CONFIG_MAIN_STACK_SIZE=4096 + +CONFIG_RUST=y + +CONFIG_RUST_ALLOC=y +# CONFIG_LOG=y +CONFIG_GPIO=y +CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT=y + +CONFIG_LOG_BACKEND_RTT=n diff --git a/tests/drivers/gpio-async/rpi_pico.conf b/tests/drivers/gpio-async/rpi_pico.conf new file mode 100644 index 00000000..94c0843a --- /dev/null +++ b/tests/drivers/gpio-async/rpi_pico.conf @@ -0,0 +1,7 @@ +# Copyright (c) 2024 Linaro LTD +# SPDX-License-Identifier: Apache-2.0 + +# This board doesn't have a serial console, so use RTT. +CONFIG_UART_CONSOLE=n +CONFIG_RTT_CONSOLE=y +CONFIG_USE_SEGGER_RTT=y diff --git a/tests/drivers/gpio-async/src/lib.rs b/tests/drivers/gpio-async/src/lib.rs new file mode 100644 index 00000000..ebccc5ff --- /dev/null +++ b/tests/drivers/gpio-async/src/lib.rs @@ -0,0 +1,77 @@ +// Copyright (c) 2024 Linaro LTD +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +extern crate alloc; + +use embassy_time::{Duration, Ticker}; +use zephyr::{ + device::gpio::{GpioPin, GpioToken}, + embassy::Executor, + raw::{GPIO_INPUT, GPIO_OUTPUT_ACTIVE, GPIO_PULL_DOWN}, +}; + +use embassy_executor::Spawner; +use log::info; +use static_cell::StaticCell; + +static EXECUTOR_MAIN: StaticCell = StaticCell::new(); + +#[no_mangle] +extern "C" fn rust_main() { + unsafe { + zephyr::set_logger().unwrap(); + } + + let executor = EXECUTOR_MAIN.init(Executor::new()); + executor.run(|spawner| { + spawner.spawn(main(spawner)).unwrap(); + }) +} + +#[embassy_executor::task] +async fn main(spawner: Spawner) { + info!("Hello world"); + let _ = spawner; + + let mut col0 = zephyr::devicetree::labels::col0::get_instance().unwrap(); + let mut row0 = zephyr::devicetree::labels::row0::get_instance().unwrap(); + let mut gpio_token = unsafe { zephyr::device::gpio::GpioToken::get_instance().unwrap() }; + + unsafe { + col0.configure(&mut gpio_token, GPIO_OUTPUT_ACTIVE); + col0.set(&mut gpio_token, true); + row0.configure(&mut gpio_token, GPIO_INPUT | GPIO_PULL_DOWN); + } + + loop { + unsafe { row0.wait_for_high(&mut gpio_token).await }; + // Simple debounce, Wait for 20 consecutive high samples. + debounce(&mut row0, &mut gpio_token, true).await; + info!("Pressed"); + unsafe { row0.wait_for_low(&mut gpio_token).await }; + debounce(&mut row0, &mut gpio_token, false).await; + info!("Released"); + } +} + +/// Simple debounce. Scan the gpio periodically, and return when we have 20 consecutive samples of +/// the intended value. +async fn debounce(pin: &mut GpioPin, gpio_token: &mut GpioToken, level: bool) { + let mut count = 0; + let mut ticker = Ticker::every(Duration::from_millis(1)); + loop { + ticker.next().await; + + if unsafe { pin.get(gpio_token) } == level { + count += 1; + + if count >= 20 { + return; + } + } else { + count = 0; + } + } +} diff --git a/tests/drivers/gpio-async/testcase.yaml b/tests/drivers/gpio-async/testcase.yaml new file mode 100644 index 00000000..cd915ba0 --- /dev/null +++ b/tests/drivers/gpio-async/testcase.yaml @@ -0,0 +1,13 @@ +common: + filter: CONFIG_RUST_SUPPORTED + platform_allow: + - rpi_pico +tests: + test.gpio-async: + harness: console + harness_config: + type: one_line + regex: + # This doesn't actually happen, as this test requires specific hardware and buttons to be + # pressed. + - "All tests passed" diff --git a/zephyr-build/src/devicetree/augment.rs b/zephyr-build/src/devicetree/augment.rs index 55f150db..3b781da8 100644 --- a/zephyr-build/src/devicetree/augment.rs +++ b/zephyr-build/src/devicetree/augment.rs @@ -130,6 +130,8 @@ pub enum Action { /// The name of the full path (within the zephyr-sys crate) for the wrapper node for this /// device. device: String, + /// Full path to a type if this node needs a static associated with each instance. + static_type: Option, }, /// Generate all of the labels as its own node. Labels, @@ -138,7 +140,11 @@ pub enum Action { impl Action { fn generate(&self, _name: &Ident, node: &Node, tree: &DeviceTree) -> TokenStream { match self { - Action::Instance { raw, device } => raw.generate(node, device), + Action::Instance { + raw, + device, + static_type, + } => raw.generate(node, device, static_type.as_deref()), Action::Labels => { let nodes = tree.labels.iter().map(|(k, v)| { let name = dt_to_lower_id(k); @@ -181,8 +187,9 @@ pub enum RawInfo { } impl RawInfo { - fn generate(&self, node: &Node, device: &str) -> TokenStream { + fn generate(&self, node: &Node, device: &str, static_type: Option<&str>) -> TokenStream { let device_id = str_to_path(device); + let static_type = str_to_path(static_type.unwrap_or("crate::device::NoStatic")); match self { Self::Myself => { let ord = node.ord; @@ -192,12 +199,17 @@ impl RawInfo { pub unsafe fn get_instance_raw() -> *const crate::raw::device { &crate::raw::#rawdev } + #[allow(dead_code)] + pub(crate) unsafe fn get_static_raw() -> &'static #static_type { + &STATIC + } static UNIQUE: crate::device::Unique = crate::device::Unique::new(); + static STATIC: #static_type = #static_type::new(); pub fn get_instance() -> Option<#device_id> { unsafe { let device = get_instance_raw(); - #device_id::new(&UNIQUE, device) + #device_id::new(&UNIQUE, &STATIC, device) } } } @@ -220,10 +232,12 @@ impl RawInfo { quote! { static UNIQUE: crate::device::Unique = crate::device::Unique::new(); + static STATIC: #static_type = #static_type::new(); pub fn get_instance() -> Option<#device_id> { unsafe { let device = #target_route :: get_instance_raw(); - #device_id::new(&UNIQUE, device, #(#args),*) + let device_static = #target_route :: get_static_raw(); + #device_id::new(&UNIQUE, &STATIC, device, device_static, #(#args),*) } } } @@ -239,10 +253,11 @@ impl RawInfo { quote! { static UNIQUE: crate::device::Unique = crate::device::Unique::new(); + static STATIC: #static_type = #static_type::new(); pub fn get_instance() -> Option<#device_id> { unsafe { let device = #path :: get_instance_raw(); - #device_id::new(&UNIQUE, device, #(#get_args),*) + #device_id::new(&UNIQUE, &STATIC, device, #(#get_args),*) } } } diff --git a/zephyr-sys/wrapper.h b/zephyr-sys/wrapper.h index 7d964bb8..98bb9571 100644 --- a/zephyr-sys/wrapper.h +++ b/zephyr-sys/wrapper.h @@ -56,3 +56,8 @@ const uintptr_t ZR_STACK_RESERVED = K_KERNEL_STACK_RESERVED; const uint32_t ZR_POLL_TYPE_SEM_AVAILABLE = K_POLL_TYPE_SEM_AVAILABLE; const uint32_t ZR_POLL_TYPE_SIGNAL = K_POLL_TYPE_SIGNAL; const uint32_t ZR_POLL_TYPE_DATA_AVAILABLE = K_POLL_TYPE_DATA_AVAILABLE; + +#ifdef CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT +const uint32_t ZR_GPIO_INT_MODE_DISABLE_ONLY = GPIO_INT_MODE_DISABLE_ONLY; +const uint32_t ZR_GPIO_INT_MODE_ENABLE_ONLY = GPIO_INT_MODE_ENABLE_ONLY; +#endif diff --git a/zephyr/Cargo.toml b/zephyr/Cargo.toml index 2e340907..bb70549c 100644 --- a/zephyr/Cargo.toml +++ b/zephyr/Cargo.toml @@ -81,3 +81,8 @@ time-driver = [ executor-zephyr = [ "dep:embassy-executor", ] + +# Enables async support in various drivers. +async-drivers = [ + "dep:embassy-sync", +] diff --git a/zephyr/src/device.rs b/zephyr/src/device.rs index 5206d720..0bd80ac3 100644 --- a/zephyr/src/device.rs +++ b/zephyr/src/device.rs @@ -41,3 +41,13 @@ impl Unique { !self.0.fetch_or(true, Ordering::AcqRel) } } + +/// For devices that don't need any associated static data, This NoStatic type will take no space +/// and generate no code, and has the const constructor needed for the type. +pub(crate) struct NoStatic; + +impl NoStatic { + pub(crate) const fn new() -> Self { + Self + } +} diff --git a/zephyr/src/device/flash.rs b/zephyr/src/device/flash.rs index 5d76b422..3372c5a1 100644 --- a/zephyr/src/device/flash.rs +++ b/zephyr/src/device/flash.rs @@ -3,7 +3,7 @@ // Note that currently, the flash partition shares the controller, so the underlying operations // are not actually safe. Need to rethink how to manage this. -use super::Unique; +use super::{NoStatic, Unique}; use crate::raw; /// A flash controller @@ -22,6 +22,7 @@ impl FlashController { #[allow(dead_code)] pub(crate) unsafe fn new( unique: &Unique, + _static: &NoStatic, device: *const raw::device, ) -> Option { if !unique.once() { @@ -50,6 +51,7 @@ impl FlashPartition { #[allow(dead_code)] pub(crate) unsafe fn new( unique: &Unique, + _static: &NoStatic, device: *const raw::device, offset: u32, size: u32, diff --git a/zephyr/src/device/gpio.rs b/zephyr/src/device/gpio.rs index 14a562e0..f0e52c8a 100644 --- a/zephyr/src/device/gpio.rs +++ b/zephyr/src/device/gpio.rs @@ -7,9 +7,244 @@ //! pervasively throughout Zephyr device drivers. As such, most of the calls in this module are //! unsafe. -use super::Unique; +use core::ffi::c_int; + +use super::{NoStatic, Unique}; use crate::raw; +#[cfg(feature = "async-drivers")] +mod async_io { + //! Async operations for gpio drivers. + //! + //! For now, we make an assumption that a gpio controller can contain up to 32 gpios, which is + //! the largest number currently used, although this might change with 64-bit targest in the + //! future. + + use core::{ + cell::UnsafeCell, + future::Future, + mem, + sync::atomic::Ordering, + task::{Poll, Waker}, + }; + + use embassy_sync::waitqueue::AtomicWaker; + use zephyr_sys::{ + device, gpio_add_callback, gpio_callback, gpio_init_callback, gpio_pin_interrupt_configure, + gpio_pin_interrupt_configure_dt, gpio_port_pins_t, GPIO_INT_LEVEL_HIGH, GPIO_INT_LEVEL_LOW, + ZR_GPIO_INT_MODE_DISABLE_ONLY, + }; + + use crate::sync::atomic::{AtomicBool, AtomicU32}; + + use super::{GpioPin, GpioToken}; + + pub(crate) struct GpioStatic { + /// The wakers for each of the gpios. + wakers: [AtomicWaker; 32], + /// Indicates when an interrupt has fired. Used to definitively indicate the event has + /// happened, so we can wake. + fired: AtomicU32, + /// Have we been initialized? + installed: AtomicBool, + /// The data for the callback itself. + callback: UnsafeCell, + } + + unsafe impl Sync for GpioStatic {} + + impl GpioStatic { + pub(crate) const fn new() -> Self { + Self { + wakers: [const { AtomicWaker::new() }; 32], + fired: AtomicU32::new(0), + installed: AtomicBool::new(false), + // SAFETY: `installed` will tell us this need to be installed. + callback: unsafe { mem::zeroed() }, + } + } + + /// Ensure that the callback has been installed. + pub(super) fn fast_install(&self, port: *const device) { + if !self.installed.load(Ordering::Acquire) { + self.install(port); + } + } + + fn install(&self, port: *const device) { + critical_section::with(|_| { + if !self.installed.load(Ordering::Acquire) { + let cb = self.callback.get(); + // SAFETY: We're in a critical section, so there should be no concurrent use, + // and there should not be any calls from the driver. + unsafe { + gpio_init_callback(cb, Some(Self::callback_handler), 0); + gpio_add_callback(port, cb); + } + + self.installed.store(true, Ordering::Release); + } + }) + } + + /// Register (replacing) a given callback. + pub(super) fn register(&self, pin: u8, waker: &Waker) { + self.wakers[pin as usize].register(waker); + + // SAFETY: Inherently unsafe, due to how the Zephyr API is defined. + // The API appears to assume coherent memory, which although untrue, probably is "close + // enough" on the supported targets. + // The main issue is to ensure that any race is resolved in the direction of getting the + // callback more than needed, rather than missing. In the context here, ensure the + // waker is registered (which does use an atomic), before enabling the pin in the + // callback structure. + // + // If it seems that wakes are getting missed, it might be the case that this needs some + // kind of memory barrier. + let cb = self.callback.get(); + unsafe { + (*cb).pin_mask |= 1 << pin; + } + } + + extern "C" fn callback_handler( + port: *const device, + cb: *mut gpio_callback, + mut pins: gpio_port_pins_t, + ) { + let data = unsafe { + cb.cast::() + .sub(mem::offset_of!(Self, callback)) + .cast::() + }; + + // For each pin we are informed of. + while pins > 0 { + let pin = pins.trailing_zeros(); + + pins &= !(1 << pin); + + // SAFETY: Handling this correctly is a bit tricky, especially with the + // un-coordinated 'pin-mask' value. + // + // For level-triggered interrupts, not disabling this will result in an interrupt + // storm. + unsafe { + // Disable the actual interrupt from the controller. + gpio_pin_interrupt_configure(port, pin as u8, ZR_GPIO_INT_MODE_DISABLE_ONLY); + + // Remove the callback bit. Unclear if this is actually useful. + (*cb).pin_mask &= !(1 << pin); + + // Indicate that we have fired. + // AcqRel is sufficient for ordering across a single atomic. + (*data).fired.fetch_or(1 << pin, Ordering::AcqRel); + + // After the interrupt is off, wake the handler. + (*data).wakers[pin as usize].wake(); + } + } + } + + /// Check if we have fired for a given pin. Clears the status. + pub(crate) fn has_fired(&self, pin: u8) -> bool { + let value = self.fired.fetch_and(!(1 << pin), Ordering::AcqRel); + value & (1 << pin) != 0 + } + } + + impl GpioPin { + /// Asynchronously wait for a gpio pin to become high. + /// + /// # Safety + /// + /// The `_token` enforces single use of gpios. Note that this makes it impossible to wait for + /// more than one GPIO. + /// + pub unsafe fn wait_for_high( + &mut self, + _token: &mut GpioToken, + ) -> impl Future + use<'_> { + GpioWait::new(self, 1) + } + + /// Asynchronously wait for a gpio pin to become low. + /// + /// # Safety + /// + /// The `_token` enforces single use of gpios. Note that this makes it impossible to wait + /// for more than one GPIO. + pub unsafe fn wait_for_low( + &mut self, + _token: &mut GpioToken, + ) -> impl Future + use<'_> { + GpioWait::new(self, 0) + } + } + + /// A future that waits for a gpio to become high. + pub struct GpioWait<'a> { + pin: &'a mut GpioPin, + level: u8, + } + + impl<'a> GpioWait<'a> { + fn new(pin: &'a mut GpioPin, level: u8) -> Self { + Self { pin, level } + } + } + + impl<'a> Future for GpioWait<'a> { + type Output = (); + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + self.pin.data.fast_install(self.pin.pin.port); + + // Early detection of the event. Also clears. + // This should be non-racy as long as only one task at a time waits on the gpio. + if self.pin.data.has_fired(self.pin.pin.pin) { + return Poll::Ready(()); + } + + self.pin.data.register(self.pin.pin.pin, cx.waker()); + + let mode = match self.level { + 0 => GPIO_INT_LEVEL_LOW, + 1 => GPIO_INT_LEVEL_HIGH, + _ => unreachable!(), + }; + + unsafe { + gpio_pin_interrupt_configure_dt(&self.pin.pin, mode); + + // Before sleeping, check if it fired, to avoid having to pend if it already + // happened. + if self.pin.data.has_fired(self.pin.pin.pin) { + return Poll::Ready(()); + } + } + + Poll::Pending + } + } +} + +#[cfg(not(feature = "async-drivers"))] +mod async_io { + pub(crate) struct GpioStatic; + + impl GpioStatic { + pub(crate) const fn new() -> Self { + Self + } + } +} + +pub(crate) use async_io::*; + /// Global instance to help make gpio in Rust slightly safer. /// /// # Safety @@ -45,6 +280,8 @@ pub struct Gpio { /// The underlying device itself. #[allow(dead_code)] pub(crate) device: *const raw::device, + /// Our associated data, used for callbacks. + pub(crate) data: &'static GpioStatic, } // SAFETY: Gpio's can be shared with other threads. Safety is maintained by the Token. @@ -55,11 +292,15 @@ impl Gpio { /// /// TODO: Guarantee single instancing. #[allow(dead_code)] - pub(crate) unsafe fn new(unique: &Unique, device: *const raw::device) -> Option { + pub(crate) unsafe fn new( + unique: &Unique, + data: &'static GpioStatic, + device: *const raw::device, + ) -> Option { if !unique.once() { return None; } - Some(Gpio { device }) + Some(Gpio { device, data }) } /// Verify that the device is ready for use. At a minimum, this means the device has been @@ -77,6 +318,7 @@ impl Gpio { #[allow(dead_code)] pub struct GpioPin { pub(crate) pin: raw::gpio_dt_spec, + pub(crate) data: &'static GpioStatic, } // SAFETY: GpioPin's can be shared with other threads. Safety is maintained by the Token. @@ -87,7 +329,9 @@ impl GpioPin { #[allow(dead_code)] pub(crate) unsafe fn new( unique: &Unique, + _static: &NoStatic, device: *const raw::device, + device_static: &'static GpioStatic, pin: u32, dt_flags: u32, ) -> Option { @@ -100,6 +344,7 @@ impl GpioPin { pin: pin as raw::gpio_pin_t, dt_flags: dt_flags as raw::gpio_dt_flags_t, }, + data: device_static, }) } @@ -113,6 +358,7 @@ impl GpioPin { pub fn get_gpio(&self) -> Gpio { Gpio { device: self.pin.port, + data: self.data, } } @@ -147,4 +393,18 @@ impl GpioPin { raw::gpio_pin_toggle_dt(&self.pin); } } + + /// Set the logical level of the pin. + pub unsafe fn set(&mut self, _token: &mut GpioToken, value: bool) { + raw::gpio_pin_set_dt(&self.pin, value as c_int); + } + + /// Read the logical level of the pin. + pub unsafe fn get(&mut self, _token: &mut GpioToken) -> bool { + match raw::gpio_pin_get_dt(&self.pin) { + 0 => false, + 1 => true, + _ => panic!("TODO: Handle gpio get error"), + } + } }