diff --git a/Cargo.toml b/Cargo.toml index 808c4349..4bb23d2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ libtock_buzzer = {path = "apis/buzzer"} libtock_console = { path = "apis/console" } libtock_debug_panic = { path = "panic_handlers/debug_panic" } libtock_gpio = { path = "apis/gpio" } +libtock_i2c_master_slave = { path = "apis/i2c_master_slave" } libtock_leds = { path = "apis/leds" } libtock_low_level_debug = { path = "apis/low_level_debug" } libtock_ninedof = { path = "apis/ninedof" } @@ -58,6 +59,7 @@ members = [ "apis/buzzer", "apis/console", "apis/gpio", + "apis/i2c_master_slave", "apis/leds", "apis/low_level_debug", "apis/ninedof", diff --git a/apis/i2c_master_slave/Cargo.toml b/apis/i2c_master_slave/Cargo.toml new file mode 100644 index 00000000..e2333f84 --- /dev/null +++ b/apis/i2c_master_slave/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "libtock_i2c_master_slave" +version = "0.1.0" +authors = [ + "Tock Project Developers ", + "Wilfred Mallawa ", +] +license = "Apache-2.0 OR MIT" +edition = "2021" +repository = "https://www.github.com/tock/libtock-rs" +rust-version.workspace = true +description = "libtock I2C master-slave driver" + +[dependencies] +libtock_platform = { path = "../../platform" } + diff --git a/apis/i2c_master_slave/src/lib.rs b/apis/i2c_master_slave/src/lib.rs new file mode 100644 index 00000000..c2d3c8c4 --- /dev/null +++ b/apis/i2c_master_slave/src/lib.rs @@ -0,0 +1,436 @@ +#![no_std] + +use core::cell::Cell; +use libtock_platform as platform; +use libtock_platform::allow_ro::AllowRo; +use libtock_platform::allow_rw::AllowRw; +use libtock_platform::share; +use libtock_platform::subscribe::Subscribe; +use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; + +pub struct I2CMasterSlave(S, C); + +impl I2CMasterSlave { + /// # Summary + /// + /// Perform an I2C write to the slave device on @addr. + /// + /// # Parameter + /// + /// * `addr`: Slave device address + /// * `buf`: Storage buffer, this should be bigger than @len + /// * `len`: Number of bytes to write from @buf + /// + /// # Returns + /// On success: Returns Ok(()), @len bytes were written from @buf. + /// On failure: Err(ErrorCode), with failure ErrorCode. + pub fn i2c_master_slave_write_sync( + addr: u16, + buffer: &[u8], + len: u16, + ) -> Result<(), ErrorCode> { + // We could write just the buffer length, but this may lead to + // ambiguities for the caller. So Err out early. + if len as usize > buffer.len() { + return Err(ErrorCode::NoMem); + } + let called: Cell> = Cell::new(None); + // The kernel will split this argument into upper length and lower address. + let cmd_arg0: u32 = (len as u32) << 16 | addr as u32; + share::scope::< + ( + AllowRo<_, DRIVER_NUM, { ro_allow::MASTER_TX }>, + Subscribe<_, DRIVER_NUM, { subscribe::MASTER_WRITE }>, + ), + _, + _, + >(|handle| { + let (allow_ro, subscribe) = handle.split(); + + S::allow_ro::(allow_ro, buffer)?; + + S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::MASTER_WRITE }>(subscribe, &called)?; + + S::command(DRIVER_NUM, i2c_master_slave_cmd::MASTER_WRITE, cmd_arg0, 0).to_result()?; + + loop { + S::yield_wait(); + if let Some((r0, status, _r1)) = called.get() { + // Kernel uses a different cmd number for this... + assert_eq!(r0, 0); + return match status { + 0 => Ok(()), + e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), + }; + } + } + }) + } + + /// # Summary + /// + /// Perform an I2C read from the the slave device with the slave address of @addr. + /// + /// # Parameter + /// + /// * `addr`: Slave device address + /// * `buf`: Storage buffer, this should be bigger than @len + /// * `len`: Number of bytes to read into @buf + /// + /// # Returns + /// On success: Returns Ok(()) with @bytes_received valid. + /// On failure: Err(ErrorCode), Failure ErrorCode and @bytes_received is invalid. + /// + /// Note: @bytes_received is the first return tuple index (valid only on success). + pub fn i2c_master_slave_read_sync( + addr: u16, + buf: &mut [u8], + len: u16, + ) -> (usize, Result<(), ErrorCode>) { + if len as usize > buf.len() { + return (0, Err(ErrorCode::NoMem)); + } + // This is the total amount of bytes read if the operation was a success. + // Otherwise, it is invalid. + let mut bytes_received: usize = core::cmp::min(buf.len(), len as usize); + let called: Cell> = Cell::new(None); + // The kernel will split this argument into upper length and lower address. + let cmd_arg0: u32 = (len as u32) << 16 | addr as u32; + let r = share::scope::< + ( + AllowRw<_, DRIVER_NUM, { rw_allow::MASTER_RX }>, + Subscribe<_, DRIVER_NUM, { subscribe::MASTER_READ }>, + ), + _, + _, + >(|handle| { + let (allow_rw, subscribe) = handle.split(); + S::allow_rw::(allow_rw, buf)?; + S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::MASTER_READ }>(subscribe, &called)?; + // When this fails, `called` is guaranteed unmodified, + // because upcalls are never processed until we call `yield`. + S::command(DRIVER_NUM, i2c_master_slave_cmd::MASTER_READ, cmd_arg0, 0).to_result()?; + + loop { + S::yield_wait(); + if let Some((r0, _read_len, status)) = called.get() { + // TODO: The kernel I2C api does not currently return the read_len, so this + // will be invalid. We should keep track, likely assume the transfer was + // done if no error. See: tock@capsules/core/src/i2c_master_slave_driver.rs:129 + // see: https://github.com/tock/tock/issues/3735 + // Kernel uses a different cmd number for this... + assert_eq!(r0, 1); + return match status { + 0 => Ok(()), + e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), + }; + } + } + }); + // If the operation failed, make bytes received zero so that the caller isn't confused in case + // the error is not handled properly. That is, in case of an error, we cannot guarantee the + // number of bytes received. + if r.is_err() { + bytes_received = 0; + } + (bytes_received, r) + } + + /// # Summary + /// + /// Perform an I2C write followed by a read. + /// + /// Note: The kernel uses the TX buffer for both actions, such that if you request a + /// a read that exceeds the buffer length of @w_buf, the read will be + /// limited to the capacity of @w_buf. This API will detect such a case + /// and error to avoid ambiguities until we have a better solution in the kernel. + /// + /// # Parameter + /// + /// * `addr`: Slave device address + /// * `w_buf`: Write buffer + /// * `r_buf`: Read buffer + /// * `w_len`: Number of bytes to write from @w_buf + /// * `r_len`: Number of bytes to read into @r_buf + /// + /// # Returns + /// On success: Returns Ok(()) with @bytes_received valid. + /// On failure: Err(ErrorCode), Failure ErrorCode and @bytes_received is invalid. + /// + /// Note: @bytes_received is the first return tuple index (valid only on success). + pub fn i2c_master_slave_write_read_sync( + addr: u16, + w_buf: &mut [u8], + r_buf: &mut [u8], + w_len: u16, + r_len: u16, + ) -> (usize, Result<(), ErrorCode>) { + if w_len as usize > w_buf.len() || r_len as usize > r_buf.len() { + return (0, Err(ErrorCode::NoMem)); + } + // TODO: Kernel uses the TX Buffer to perform both RX/TX for a write_read, so if + // the @w_buff is smaller than @r_len. The subsequent read will stop prematurely. + // So let's error here until that is addressed. + if r_len as usize > w_buf.len() { + return (0, Err(ErrorCode::NoMem)); + } + // This is the total amount of bytes read if the operation was a success. + // Otherwise, it is invalid. + let mut bytes_received: usize = core::cmp::min(r_buf.len(), r_len as usize); + let called: Cell> = Cell::new(None); + + let cmd_arg0: u32 = (w_len as u32) << 16 | (r_len as u32) << 8 | addr as u32; + + let r = share::scope::< + ( + AllowRw<_, DRIVER_NUM, { rw_allow::MASTER_RX }>, + AllowRo<_, DRIVER_NUM, { ro_allow::MASTER_TX }>, + Subscribe<_, DRIVER_NUM, { subscribe::MASTER_WRITE_READ }>, + ), + _, + _, + >(|handle| { + let (allow_rw, allow_ro, subscribe) = handle.split(); + S::allow_rw::(allow_rw, r_buf)?; + S::allow_ro::(allow_ro, w_buf)?; + S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::MASTER_WRITE_READ }>( + subscribe, &called, + )?; + // When this fails, `called` is guaranteed unmodified, + // because upcalls are never processed until we call `yield`. + S::command( + DRIVER_NUM, + i2c_master_slave_cmd::MASTER_WRITE_READ, + cmd_arg0, + 0, + ) + .to_result()?; + + loop { + S::yield_wait(); + if let Some((r0, _read_len, status)) = called.get() { + // TODO: The kernel I2C api does not currently return the read_len, so this + // will be invalid. We should keep track, likely assume the transfer was + // done if no error. See: tock@capsules/core/src/i2c_master_slave_driver.rs:129 + // see: https://github.com/tock/tock/issues/3735 + assert_eq!(r0, i2c_master_slave_cmd::MASTER_WRITE_READ); + return match status { + 0 => Ok(()), + e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), + }; + } + } + }); + // If the operation failed, make bytes received zero so that the caller isn't confused in case + // the error is not handled properly. That is, in case of an error, we cannot guarantee the + // number of bytes received. + if r.is_err() { + bytes_received = 0; + } + (bytes_received, r) + } + + /// # Summary + /// + /// Set the slave address for this device for slave mode operation. The IP should respond + /// to @addr. + /// + /// # Parameter + /// + /// * `addr`: Slave device address to set + /// + /// # Returns + /// On success: Returns Ok(()) + /// On failure: Err(ErrorCode) + pub fn i2c_master_slave_set_slave_address(addr: u8) -> Result<(), ErrorCode> { + // We do not count the R/W bit as part of the address, so the + // valid range is 0x00-0x7f + if addr > 0x7f { + return Err(ErrorCode::Invalid); + } + S::command( + DRIVER_NUM, + i2c_master_slave_cmd::SLAVE_SET_ADDR, + addr as u32, + 0, + ) + .to_result() + } + + /// # Summary + /// + /// Expect a write from master into the buffer pointed by @buf. This function is + /// synchronous and returns only when the operation has completed. + /// + /// TODO: Add async support + /// + /// Note: As we do not know the size of data to be sent from a master device, + /// it is suggested to allocated a large buffer to accommodate bigger transfers. + /// + /// # Parameter + /// + /// * `buf`: Buffer into which to copy data from master + /// + /// # Returns + /// On success: Returns (bytes_read, Ok(())) + /// On failure: (0, Err(ErrorCode)) + pub fn i2c_master_slave_write_recv_sync(buf: &mut [u8]) -> (usize, Result<(), ErrorCode>) { + let called: Cell> = Cell::new(None); + let mut bytes_recvd_ret: u32 = 0; + let r = share::scope::< + ( + AllowRw<_, DRIVER_NUM, { rw_allow::SLAVE_RX }>, + Subscribe<_, DRIVER_NUM, { subscribe::SLAVE_WRITE_RECV }>, + ), + _, + _, + >(|handle| { + let (allow_rw, subscribe) = handle.split(); + S::allow_rw::(allow_rw, buf)?; + S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::SLAVE_READ }>(subscribe, &called)?; + + S::command(DRIVER_NUM, i2c_master_slave_cmd::SLAVE_START_LISTEN, 0, 0).to_result()?; + + loop { + S::yield_wait(); + if let Some((r0, bytes_recvd, status)) = called.get() { + // TODO: Ensure we are returning from the correct upcall and not from an unexpected `read_expect` + // Everything in this module subscribes to `0`. Which can be problematic from an async context. + assert_eq!(r0, i2c_master_slave_cmd::SLAVE_START_LISTEN); + return match status { + 0 => { + bytes_recvd_ret = bytes_recvd; + Ok(()) + } + e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), + }; + } + } + }); + (bytes_recvd_ret as usize, r) + } + + /// # Summary + /// + /// Expect a write from master into the buffer pointed by @buf. This function is + /// synchronous and returns only when the operation has completed. + /// + /// TODO: Add async support + /// + /// # Parameter + /// + /// * `buf`: Buffer from which to transfer data from + /// * `len`: max number of bytes from buffer to transfer + /// + /// # Returns + /// On success: Returns (bytes_sent, Ok(())) + /// On failure: (0, Err(ErrorCode)) + pub fn i2c_master_slave_read_send_sync( + buf: &[u8], + len: usize, + ) -> (usize, Result<(), ErrorCode>) { + if len > buf.len() { + return (0, Err(ErrorCode::Invalid)); + } + let called: Cell> = Cell::new(None); + let mut bytes_sent_ret: u32 = 0; + let r = share::scope::< + ( + AllowRo<_, DRIVER_NUM, { ro_allow::SLAVE_TX }>, + Subscribe<_, DRIVER_NUM, { subscribe::SLAVE_READ_SEND }>, + ), + _, + _, + >(|handle| { + let (allow_ro, subscribe) = handle.split(); + S::allow_ro::(allow_ro, buf)?; + S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::SLAVE_READ }>(subscribe, &called)?; + + S::command( + DRIVER_NUM, + i2c_master_slave_cmd::SLAVE_READ_SEND, + len as u32, + 0, + ) + .to_result()?; + + loop { + S::yield_wait(); + if let Some((r0, bytes_sent, status)) = called.get() { + // TODO: Ensure we are returning from the correct upcall and not from an unexpected `read_expect` + // Everything in this module subscribes to `0`. Which can be problematic from an async context. + assert_eq!(r0, i2c_master_slave_cmd::SLAVE_READ_SEND); + return match status { + 0 => { + bytes_sent_ret = bytes_sent; + Ok(()) + } + e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), + }; + } + } + }); + (bytes_sent_ret as usize, r) + } +} + +/// System call configuration trait for `I2CMaster`. +pub trait Config: + platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config +{ +} +impl + Config for T +{ +} + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- +const DRIVER_NUM: u32 = 0x20006; + +#[allow(unused)] +mod subscribe { + // TODO: It seems like only 0 is supported by the i2c_master_slave capsule currently + // would be nice to improve this. + pub const MASTER_WRITE: u32 = 0; + pub const MASTER_WRITE_READ: u32 = 0; + pub const MASTER_READ: u32 = 0; + pub const SLAVE_READ: u32 = 0; + pub const SLAVE_WRITE_RECV: u32 = 0; + pub const SLAVE_READ_SEND: u32 = 0; +} + +/// Ids for read-only allow buffers +#[allow(unused)] +mod ro_allow { + pub const MASTER_TX: u32 = 0; + pub const SLAVE_TX: u32 = 2; + /// The number of allow buffers the kernel stores for this grant + pub const COUNT: u8 = 3; +} + +/// Ids for read-write allow buffers +#[allow(unused)] +mod rw_allow { + pub const MASTER_RX: u32 = 1; + pub const SLAVE_RX: u32 = 3; +} + +#[allow(unused)] +mod i2c_buffers { + pub const MASTER_WRITE: u32 = 0; + pub const MASTER_READ: u32 = 1; + pub const SLAVE_READ: u32 = 2; + pub const SLAVE_WRITE: u32 = 3; +} + +#[allow(unused)] +mod i2c_master_slave_cmd { + pub const MASTER_WRITE: u32 = 1; + pub const MASTER_READ: u32 = 2; + pub const SLAVE_START_LISTEN: u32 = 3; + pub const SLAVE_READ_SEND: u32 = 4; + pub const SLAVE_SET_ADDR: u32 = 6; + pub const MASTER_WRITE_READ: u32 = 7; +} diff --git a/examples/i2c_master_write_read.rs b/examples/i2c_master_write_read.rs new file mode 100644 index 00000000..08035cc2 --- /dev/null +++ b/examples/i2c_master_write_read.rs @@ -0,0 +1,93 @@ +//! This sample demonstrates setting up the i2c ip (assuming board has support) +//! for master mode. In the event loop, we write some bytes to the target, then +//! attempt to read some bytes from the target. +//! +//! This sample is tested with `i2c_slave_send_recv.rs` sample running on the +//! slave device. That sample uses the synchronous slave api, so the order of operations +//! is important to ensure we don't cause the slave to stretch clocks if it hasn't setup +//! send buffers in time. + +#![no_main] +#![no_std] +use core::fmt::Write; +use libtock::alarm::{Alarm, Milliseconds}; +use libtock::console::Console; +use libtock::i2c_master_slave::I2CMasterSlave; +use libtock::runtime::{set_main, stack_size}; + +set_main! {main} +stack_size! {0x400} + +pub const SLAVE_DEVICE_ADDR: u16 = 0x69; + +fn main() { + let addr = SLAVE_DEVICE_ADDR; + // 7-bit addressing + assert!(addr <= 0x7f); + let mut tx_buf: [u8; 4] = [0; 4]; + // Write 4 bytes to the slave + let tx_len = 4; + let mut rx_buf: [u8; 2] = [0; 2]; + // Attempt to read 2 bytes from the slave + let rx_len = 2; + + writeln!(Console::writer(), "i2c-master: write-read sample\r").unwrap(); + writeln!( + Console::writer(), + "i2c-master: slave address 0x{:x}!\r", + addr + ) + .unwrap(); + + let mut i: u32 = 0; + loop { + writeln!( + Console::writer(), + "i2c-master: write-read operation {:?}\r", + i + ) + .unwrap(); + + // Change up the data in tx-buffer + tx_buf[0] = tx_buf[0].wrapping_add(2); + tx_buf[1] = tx_buf[1].wrapping_add(4); + tx_buf[2] = tx_buf[2].wrapping_add(6); + tx_buf[3] = tx_buf[3].wrapping_add(8); + + if let Err(why) = I2CMasterSlave::i2c_master_slave_write_sync(addr, &tx_buf, tx_len) { + writeln!( + Console::writer(), + "i2c-master: write operation failed {:?}", + why + ) + .unwrap(); + } else { + // This sample target the i2c_slave_send_recv.rs sample, which is synchronous. + // so allow some time for it to setup 'send' buffer. + Alarm::sleep_for(Milliseconds(200)).unwrap(); + + let r = I2CMasterSlave::i2c_master_slave_read_sync(addr, &mut rx_buf, rx_len); + match r.1 { + Ok(()) => { + writeln!( + Console::writer(), + "{:} bytes read from slave | data received (0h): {:x?}\r\n", + r.0, + rx_buf + ) + .unwrap(); + } + Err(why) => { + writeln!( + Console::writer(), + "i2c-master: read operation failed {:?}", + why + ) + .unwrap(); + } + } + i += 1; + } + Alarm::sleep_for(Milliseconds(1000)).unwrap(); + } +} diff --git a/examples/i2c_slave_send_recv.rs b/examples/i2c_slave_send_recv.rs new file mode 100644 index 00000000..f0a1ec82 --- /dev/null +++ b/examples/i2c_slave_send_recv.rs @@ -0,0 +1,84 @@ +//! This sample demonstrates setting up the i2c ip (assuming board has support) +//! for target mode. In the event loop, we first expect the master to write some data +//! then we setup a response packet. +//! +//! NOTE: The device (based on hwip) may stretch clocks by holding the SCL line low if the master attempts to +//! read data before we have setup the read data buffers. +//! +//! This sample is tested with `i2c_master_write_read.rs` sample running on the +//! master device. + +#![no_main] +#![no_std] +use core::fmt::Write; +use libtock::console::Console; +use libtock::i2c_master_slave::I2CMasterSlave; +use libtock::runtime::{set_main, stack_size}; + +set_main! {main} +stack_size! {0x400} +pub const SLAVE_DEVICE_ADDR: u8 = 0x69; +fn main() { + let mut rx_buf: [u8; 8] = [0; 8]; + let mut tx_buf: [u8; 8] = [0; 8]; + let addr: u8 = SLAVE_DEVICE_ADDR; + // 7-bit addressing + assert!(addr <= 0x7f); + + writeln!(Console::writer(), "i2c-slave: setting up\r").unwrap(); + writeln!(Console::writer(), "i2c-slave: address 0x{:x}!\r", addr).unwrap(); + + I2CMasterSlave::i2c_master_slave_set_slave_address(addr).expect("i2c-target: Failed to listen"); + let mut i: u32 = 0; + loop { + writeln!(Console::writer(), "i2c-slave: operation {:?}\r", i).unwrap(); + + // Expect a write, if the master reads here, the IP may stretch clocks! + let r = I2CMasterSlave::i2c_master_slave_write_recv_sync(&mut rx_buf); + + if let Err(why) = r.1 { + writeln!( + Console::writer(), + "i2c-slave: error to receiving data {:?}\r", + why + ) + .unwrap(); + } else { + writeln!( + Console::writer(), + "{:} bytes received from master | buf: {:x?}\r", + r.0, + rx_buf + ) + .unwrap(); + + // Note: The master should allow a little delay when communicating with this slave + // as we are doing everything synchronously. + // Expect a 2 byte read by master and let's keep changing the values + tx_buf[0] = tx_buf[0].wrapping_add(1); + tx_buf[1] = tx_buf[1].wrapping_add(5); + let r = I2CMasterSlave::i2c_master_slave_read_send_sync(&tx_buf, tx_buf.len()); + + match r.1 { + Ok(()) => { + writeln!( + Console::writer(), + "{:} bytes read by master | data sent: {:x?}\r", + r.0, + tx_buf + ) + .unwrap(); + i += 1; + } + Err(why) => { + writeln!( + Console::writer(), + "i2c-slave: error setting up read_send {:?}\r", + why + ) + .unwrap(); + } + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 5d6f32e0..2da42507 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,10 @@ pub mod gpio { PullDown, PullNone, PullUp, }; } +pub mod i2c_master_slave { + use libtock_i2c_master_slave as i2c_master_slave; + pub type I2CMasterSlave = i2c_master_slave::I2CMasterSlave; +} pub mod leds { use libtock_leds as leds; pub type Leds = leds::Leds;