From 63d4ce27692a2e28f1bfb205befa392a2905529d Mon Sep 17 00:00:00 2001 From: David Brown Date: Thu, 10 Apr 2025 15:44:23 -0600 Subject: [PATCH 1/4] wip: rtio/i2c --- dt-rust.yaml | 33 +++++++ zephyr-sys/build.rs | 4 + zephyr-sys/wrapper.h | 9 ++ zephyr/src/device.rs | 1 + zephyr/src/device/gpio.rs | 6 +- zephyr/src/device/i2c.rs | 100 +++++++++++++++++++ zephyr/src/lib.rs | 2 + zephyr/src/rtio.rs | 195 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 349 insertions(+), 1 deletion(-) create mode 100644 zephyr/src/device/i2c.rs create mode 100644 zephyr/src/rtio.rs diff --git a/dt-rust.yaml b/dt-rust.yaml index c1390c70..c101dda6 100644 --- a/dt-rust.yaml +++ b/dt-rust.yaml @@ -27,6 +27,22 @@ raw: !Phandle gpios device: crate::device::gpio::GpioPin +# Hook up the gpio-keys as gpio pins as well +- name: gpio-keys + rules: + - type: compatible + value: + names: + - gpio-keys + level: 1 + actions: + - type: instance + value: + raw: + type: phandle + value: gpios + device: crate::device::gpio::GpioPin + # Flash controllers don't have any particular property to identify them, so we need a list of # compatible values that should match. - name: flash-controller @@ -36,6 +52,8 @@ - "nordic,nrf52-flash-controller" - "nordic,nrf51-flash-controller" - "raspberrypi,pico-flash-controller" + - "st,stm32g4-flash-controller" + - "st,stm32l5-flash-controller" - "zephyr,sim-flash" level: 0 actions: @@ -64,6 +82,21 @@ - !Reg device: "crate::device::flash::FlashPartition" +# I2C. +- name: i2c + rules: + - type: compatible + value: + names: + - "snps,designware-i2c" + level: 0 + actions: + - type: instance + value: + raw: + type: myself + device: crate::device::i2c::I2C + # Generate a pseudo node that matches all of the labels across the tree with their nodes. - name: labels rules: !Root diff --git a/zephyr-sys/build.rs b/zephyr-sys/build.rs index fd1e7117..2f50cb9b 100644 --- a/zephyr-sys/build.rs +++ b/zephyr-sys/build.rs @@ -76,9 +76,13 @@ fn main() -> Result<()> { .derive_copy(false) .allowlist_function("k_.*") .allowlist_function("gpio_.*") + .allowlist_function("i2c_.*") .allowlist_function("flash_.*") .allowlist_function("zr_.*") + .allowlist_function("mpsc_.*") + .allowlist_function("rtio.*") .allowlist_item("GPIO_.*") + .allowlist_item("I2C_.*") .allowlist_item("FLASH_.*") .allowlist_item("Z_.*") .allowlist_item("ZR_.*") diff --git a/zephyr-sys/wrapper.h b/zephyr-sys/wrapper.h index 69bab654..95f0fd28 100644 --- a/zephyr-sys/wrapper.h +++ b/zephyr-sys/wrapper.h @@ -43,6 +43,9 @@ extern int errno; #include #include #include +#include +#include +#include /* * bindgen will only output #defined constants that resolve to simple numbers. These are some @@ -63,6 +66,12 @@ 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 +const uint8_t ZR_I2C_MSG_WRITE = I2C_MSG_WRITE; +const uint8_t ZR_I2C_MSG_READ = I2C_MSG_READ; +const uint8_t ZR_I2C_MSG_STOP = I2C_MSG_STOP; + +const uint16_t ZR_RTIO_SQE_NO_RESPONSE = RTIO_SQE_NO_RESPONSE; + /* * Zephyr's irq_lock() and irq_unlock() are macros not inline functions, so we need some inlines to * access them. diff --git a/zephyr/src/device.rs b/zephyr/src/device.rs index 0bd80ac3..5ad60768 100644 --- a/zephyr/src/device.rs +++ b/zephyr/src/device.rs @@ -12,6 +12,7 @@ use crate::sync::atomic::{AtomicBool, Ordering}; pub mod flash; pub mod gpio; +pub mod i2c; // Allow dead code, because it isn't required for a given build to have any devices. /// Device uniqueness. diff --git a/zephyr/src/device/gpio.rs b/zephyr/src/device/gpio.rs index f0e52c8a..dfbea072 100644 --- a/zephyr/src/device/gpio.rs +++ b/zephyr/src/device/gpio.rs @@ -35,7 +35,10 @@ mod async_io { ZR_GPIO_INT_MODE_DISABLE_ONLY, }; - use crate::sync::atomic::{AtomicBool, AtomicU32}; + use crate::{ + printkln, + sync::atomic::{AtomicBool, AtomicU32}, + }; use super::{GpioPin, GpioToken}; @@ -112,6 +115,7 @@ mod async_io { cb: *mut gpio_callback, mut pins: gpio_port_pins_t, ) { + printkln!("GPIO callback: {}", pins); let data = unsafe { cb.cast::() .sub(mem::offset_of!(Self, callback)) diff --git a/zephyr/src/device/i2c.rs b/zephyr/src/device/i2c.rs new file mode 100644 index 00000000..654bf044 --- /dev/null +++ b/zephyr/src/device/i2c.rs @@ -0,0 +1,100 @@ +//! Zpehyr I2C interface + +use core::{ffi::c_int, marker::PhantomData}; + +use crate::{error::to_result, printkln, raw}; + +use super::{NoStatic, Unique}; + +/// A single I2C controller. +pub struct I2C { + /// The underlying device itself. + #[allow(dead_code)] + pub(crate) device: *const raw::device, +} + +unsafe impl Send for I2C {} + +impl I2C { + /// Constructor, used by the devicetree generated code. + #[allow(dead_code)] + pub(crate) unsafe fn new( + unique: &Unique, + _data: &'static NoStatic, + device: *const raw::device, + ) -> Option { + if !unique.once() { + return None; + } + Some(I2C { device }) + } + + /// Do a write/read. + pub fn write_read(&mut self, write: &[u8], read: &mut [u8]) -> crate::Result { + let mut msg = [ + raw::i2c_msg { + buf: write.as_ptr() as *mut _, + len: write.len() as u32, + flags: raw::ZR_I2C_MSG_WRITE, + }, + raw::i2c_msg { + buf: read.as_mut_ptr(), + len: read.len() as u32, + flags: raw::ZR_I2C_MSG_READ | raw::ZR_I2C_MSG_STOP, + }, + ]; + let res = unsafe { to_result(raw::i2c_transfer(self.device, msg.as_mut_ptr(), 2, 0x42)) }; + + printkln!("res: {} {}", msg[1].len, msg[1].flags); + + res + } + + /// Add an i2c operation to the RTIO. + /// + /// TODO: Unclear how to indicate that the buffers must live long enough for the submittion. + /// As it is, this is actually completely unsound. + pub fn rtio_write_read(&mut self, write: &[u8], read: &mut [u8]) -> crate::Result<()> { + let _msg = [ + raw::i2c_msg { + buf: write.as_ptr() as *mut _, + len: write.len() as u32, + flags: raw::ZR_I2C_MSG_WRITE, + }, + raw::i2c_msg { + buf: read.as_mut_ptr(), + len: read.len() as u32, + flags: raw::ZR_I2C_MSG_READ | raw::ZR_I2C_MSG_STOP, + }, + ]; + + todo!() + } +} + +/// An i2c transaction. +pub struct ReadWrite<'a> { + _phantom: PhantomData<&'a ()>, + msgs: [raw::i2c_msg; 2], +} + +impl<'a> ReadWrite<'a> { + /// Construct a new read/write transaction. + pub fn new(write: &'a [u8], read: &'a mut [u8]) -> Self { + Self { + _phantom: PhantomData, + msgs: [ + raw::i2c_msg { + buf: write.as_ptr() as *mut _, + len: write.len() as u32, + flags: raw::ZR_I2C_MSG_WRITE, + }, + raw::i2c_msg { + buf: read.as_mut_ptr(), + len: read.len() as u32, + flags: raw::ZR_I2C_MSG_READ | raw::ZR_I2C_MSG_STOP, + }, + ], + } + } +} diff --git a/zephyr/src/lib.rs b/zephyr/src/lib.rs index cccdfaf6..fcc39651 100644 --- a/zephyr/src/lib.rs +++ b/zephyr/src/lib.rs @@ -74,6 +74,8 @@ pub mod embassy; pub mod error; pub mod logging; pub mod object; +#[cfg(CONFIG_RTIO)] +pub mod rtio; #[cfg(CONFIG_RUST_ALLOC)] pub mod simpletls; pub mod sync; diff --git a/zephyr/src/rtio.rs b/zephyr/src/rtio.rs new file mode 100644 index 00000000..c4000251 --- /dev/null +++ b/zephyr/src/rtio.rs @@ -0,0 +1,195 @@ +//! Interface to Zephyr 'rtio' infrastructure. + +use core::ffi::c_void; + +use crate::error::to_result_void; +use crate::object::{ObjectInit, ZephyrObject}; +use crate::raw; + +/// The underlying structure, holding the rtio, it's semaphores, and pools. +/// +/// Note that putting these together in a single struct makes this "pleasant" to use from Rust, but +/// does make the end result incompatible with userspace. +#[repr(C)] +pub struct RtioData { + /// The overall rtio struct. + rtio: raw::rtio, + /// Sempahore used for the submission queue. + #[cfg(CONFIG_RTIO_SUBMIT_SEM)] + submit_sem: raw::k_sem, + /// Semaphore used for the consumption queue. + #[cfg(CONFIG_RTIO_CONSUME_SEM)] + consume_sem: raw::k_sem, + /// The SQE items. + sqe_pool_items: [raw::rtio_iodev_sqe; SQE_SZ], + /// The SQE pool itself. + sqe_pool: raw::rtio_sqe_pool, + /// The CQE items. + cqe_pool_items: [raw::rtio_cqe; CQE_SZ], + /// The pool of CQEs. + cqe_pool: raw::rtio_cqe_pool, +} + +/// Init based reference to the the underlying rtio object. +/// +/// Note that this declaration will _not_ support userspace currently, as the object will not be +/// placed in an iterable linker section. Also, the linker sevction will not work as the +/// ZephyrObject will have an attached atomic used to ensure proper initialization. +pub struct RtioObject( + pub(crate) ZephyrObject>, +); + +unsafe impl Sync for RtioObject {} + +impl RtioObject { + /// Construct a new RTIO pool. + /// + /// Create a new RTIO object. These objects generally need to be statically allocated. + pub const fn new() -> Self { + let this = >>::new_raw(); + RtioObject(this) + } + + /// Acquire a submission object. + pub fn sqe_acquire(&'static self) -> Option { + let this = unsafe { self.0.get() }; + + let ptr = unsafe { raw::rtio_sqe_acquire(&raw mut (*this).rtio) }; + + if ptr.is_null() { + None + } else { + Some(Sqe { item: ptr }) + } + } + + /// Submit the work. + pub fn submit(&'static self, wait: usize) -> crate::Result<()> { + let this = unsafe { self.0.get() }; + + unsafe { to_result_void(raw::rtio_submit(&raw mut (*this).rtio, wait as u32)) } + } + + /// Consume a single completion. + /// + /// Will return the completion if available. If returned, it will be released upon drop. + pub fn cqe_consume(&'static self) -> Option { + let this = unsafe { self.0.get() }; + + let ptr = unsafe { raw::rtio_cqe_consume(&raw mut (*this).rtio) }; + + if ptr.is_null() { + None + } else { + Some(Cqe { + item: ptr, + rtio: unsafe { &raw mut (*this).rtio }, + }) + } + } +} + +impl ObjectInit> + for ZephyrObject> +{ + fn init(item: *mut RtioData) { + #[cfg(CONFIG_RTIO_SUBMIT_SEM)] + unsafe { + raw::k_sem_init(&raw mut (*item).submit_sem, 0, raw::K_SEM_MAX_LIMIT); + (*item).rtio.submit_sem = &raw mut (*item).submit_sem; + (*item).rtio.submit_count = 0; + } + #[cfg(CONFIG_RTIO_CONSUME_SEM)] + unsafe { + raw::k_sem_init(&raw mut (*item).consume_sem, 0, raw::K_SEM_MAX_LIMIT); + (*item).rtio.consume_sem = &raw mut (*item).consume_sem; + } + unsafe { + // TODO: Zephyr atomic init? + (*item).rtio.cq_count = 0; + (*item).rtio.xcqcnt = 0; + + // Set up the sqe pool. + raw::mpsc_init(&raw mut (*item).sqe_pool.free_q); + (*item).sqe_pool.pool_size = SQE_SZ as u16; + (*item).sqe_pool.pool_free = SQE_SZ as u16; + (*item).sqe_pool.pool = (*item).sqe_pool_items.as_mut_ptr(); + + for p in &mut (*item).sqe_pool_items { + raw::mpsc_push(&raw mut (*item).sqe_pool.free_q, &raw mut p.q); + } + + // Set up the cqe pool + raw::mpsc_init(&raw mut (*item).cqe_pool.free_q); + (*item).cqe_pool.pool_size = CQE_SZ as u16; + (*item).cqe_pool.pool_free = CQE_SZ as u16; + (*item).cqe_pool.pool = (*item).cqe_pool_items.as_mut_ptr(); + + for p in &mut (*item).cqe_pool_items { + raw::mpsc_push(&raw mut (*item).cqe_pool.free_q, &raw mut p.q); + } + + (*item).rtio.sqe_pool = &raw mut (*item).sqe_pool; + (*item).rtio.cqe_pool = &raw mut (*item).cqe_pool; + + raw::mpsc_init(&raw mut (*item).rtio.sq); + raw::mpsc_init(&raw mut (*item).rtio.cq); + } + } +} + +/// A single Sqe. +/// +/// TODO: How to bind the lifetime to the Rtio meaningfully, even though it is all static. +pub struct Sqe { + item: *mut raw::rtio_sqe, +} + +impl Sqe { + /// Configure this SQE as a callback. + pub fn prep_callback( + &mut self, + callback: raw::rtio_callback_t, + arg0: *mut c_void, + userdata: *mut c_void, + ) { + unsafe { + raw::rtio_sqe_prep_callback(self.item, callback, arg0, userdata); + } + } + + /// Configure this SQE as a nop. + pub fn prep_nop(&mut self, dev: *mut raw::rtio_iodev, userdata: *mut c_void) { + unsafe { + raw::rtio_sqe_prep_nop(self.item, dev, userdata); + } + } + + /// Add flags. + pub fn or_flags(&mut self, flags: u16) { + unsafe { + (*self.item).flags |= flags; + } + } +} + +/// A single Cqe. +pub struct Cqe { + item: *mut raw::rtio_cqe, + rtio: *mut raw::rtio, +} + +impl Cqe { + /// Retrieve the result of this operation. + pub fn result(&self) -> i32 { + unsafe { (*self.item).result } + } +} + +impl Drop for Cqe { + fn drop(&mut self) { + unsafe { + raw::rtio_cqe_release(self.rtio, self.item); + } + } +} From b27712102dc6330290b22c088bebb269c5341607 Mon Sep 17 00:00:00 2001 From: David Brown Date: Thu, 10 Apr 2025 15:55:25 -0600 Subject: [PATCH 2/4] wip: rtio/i2c --- docgen/prj.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docgen/prj.conf b/docgen/prj.conf index 4c55eb98..b23ff323 100644 --- a/docgen/prj.conf +++ b/docgen/prj.conf @@ -7,3 +7,6 @@ CONFIG_GPIO=y CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT=y CONFIG_PRINTK=y CONFIG_POLL=y +CONFIG_I2C=y +CONFIG_I2C_RTIO=y +CONFIG_RTIO=y From 8b947717df2902e965841d0bdac2679796fcf53d Mon Sep 17 00:00:00 2001 From: David Brown Date: Thu, 10 Apr 2025 16:00:42 -0600 Subject: [PATCH 3/4] wip: Drivers hacks for debugging Make some small changes to try testing this on stm32. This change can be discarded. Signed-off-by: David Brown --- tests/drivers/gpio-async/prj.conf | 6 +++++- tests/drivers/gpio-async/src/lib.rs | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/drivers/gpio-async/prj.conf b/tests/drivers/gpio-async/prj.conf index f9a269ba..4ac5fcd2 100644 --- a/tests/drivers/gpio-async/prj.conf +++ b/tests/drivers/gpio-async/prj.conf @@ -13,4 +13,8 @@ CONFIG_RUST_ALLOC=y CONFIG_GPIO=y CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT=y -CONFIG_LOG_BACKEND_RTT=n +# CONFIG_LOG_BACKEND_RTT=n + +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 index ebccc5ff..076d8c3e 100644 --- a/tests/drivers/gpio-async/src/lib.rs +++ b/tests/drivers/gpio-async/src/lib.rs @@ -35,13 +35,18 @@ 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 row0 = zephyr::devicetree::aliases::sw0::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); } From 1d3547dc106023233fb8de125b3753c8398e9a0a Mon Sep 17 00:00:00 2001 From: David Brown Date: Thu, 17 Apr 2025 12:02:58 -0600 Subject: [PATCH 4/4] i2c: Use new dt-yaml syntax Upgrade the DT entries to use the new syntax. Signed-off-by: David Brown --- dt-rust.yaml | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/dt-rust.yaml b/dt-rust.yaml index c101dda6..4209e7af 100644 --- a/dt-rust.yaml +++ b/dt-rust.yaml @@ -30,17 +30,13 @@ # Hook up the gpio-keys as gpio pins as well - name: gpio-keys rules: - - type: compatible - value: + - !Compatible names: - - gpio-keys + - gpio-keys level: 1 actions: - - type: instance - value: - raw: - type: phandle - value: gpios + - !Instance + raw: !Phandle gpios device: crate::device::gpio::GpioPin # Flash controllers don't have any particular property to identify them, so we need a list of @@ -85,17 +81,14 @@ # I2C. - name: i2c rules: - - type: compatible - value: - names: - - "snps,designware-i2c" - level: 0 + - !Compatible + names: + - "snps,designware-i2c" + level: 0 actions: - - type: instance - value: - raw: - type: myself - device: crate::device::i2c::I2C + - !Instance + raw: !Myself + device: crate::device::i2c::I2C # Generate a pseudo node that matches all of the labels across the tree with their nodes. - name: labels