Skip to content

[WIP] Allow sending multiple read/write operations in one transfer #45

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ https://www.kernel.org/doc/Documentation/i2c/dev-interface
libc = "0.2"
bitflags = "1"
byteorder = "1"
nix = "0.10"
nix = "0.11"

[dev-dependencies]
docopt = "0.8"
Expand Down
19 changes: 19 additions & 0 deletions src/core.rs
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use std::error::Error;
/// Linux i2cdev interface.
pub trait I2CDevice {
type Error: Error;
type I2CMessage;

/// Read data from the device to fill the provided slice
fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error>;
Expand Down Expand Up @@ -110,4 +111,22 @@ pub trait I2CDevice {
/// Select a register, send 1 to 31 bytes of data to it, and reads
/// 1 to 31 bytes of data from it.
fn smbus_process_block(&mut self, register: u8, values: &[u8]) -> Result<Vec<u8>, Self::Error>;

/// Send a batch of messages. Useful for when multiple buffers need to be
/// sent or recieved without an I2C `stop` flag.
fn transfer(&self, messages: &[Self::I2CMessage]) -> Result<(), Self::Error>;
}

/// Messages sent to / from an I2C device as a batch
pub trait I2CMessage {
type Flags;

/// Represents a read operation
fn read(&self, data: &[u8]) -> Self;

/// Represents a write operation
fn write(&self, data: &[u8]) -> Self;

/// Represents a custom operation or special opperands
fn custom(&self, data: &[u8], address: u16, flags: Self::Flags) -> Self;
}
66 changes: 31 additions & 35 deletions src/ffi.rs
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -18,37 +18,16 @@ use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt};

pub type I2CError = nix::Error;

bitflags! {
struct I2CMsgFlags: u16 {
/// this is a ten bit chip address
const I2C_M_TEN = 0x0010;
/// read data, from slave to master
const I2C_M_RD = 0x0001;
/// if I2C_FUNC_PROTOCOL_MANGLING
const I2C_M_STOP = 0x8000;
/// if I2C_FUNC_NOSTART
const I2C_M_NOSTART = 0x4000;
/// if I2C_FUNC_PROTOCOL_MANGLING
const I2C_M_REV_DIR_ADDR = 0x2000;
/// if I2C_FUNC_PROTOCOL_MANGLING
const I2C_M_IGNORE_NAK = 0x1000;
/// if I2C_FUNC_PROTOCOL_MANGLING
const I2C_M_NO_RD_ACK = 0x0800;
/// length will be first received byte
const I2C_M_RECV_LEN = 0x0400;
}
}

#[repr(C)]
struct i2c_msg {
pub struct i2c_msg {
/// slave address
addr: u16,
pub(crate) addr: u16,
/// serialized I2CMsgFlags
flags: u16,
pub(crate) flags: u16,
/// msg length
len: u16,
pub(crate) len: u16,
/// pointer to msg data
buf: *mut u8,
pub(crate) buf: *const u8,
}

bitflags! {
Expand Down Expand Up @@ -147,7 +126,7 @@ const I2C_FUNCS: u16 = 0x0705;
const I2C_RDWR: u16 = 0x0707;
const I2C_PEC: u16 = 0x0708;
const I2C_SMBUS: u16 = 0x0720;
const I2C_RDRW_IOCTL_MAX_MSGS: u8 = 42;
const I2C_RDRW_IOCTL_MAX_MSGS: usize = 42;

/// This is the structure as used in the I2C_SMBUS ioctl call
#[repr(C)]
Expand All @@ -164,19 +143,21 @@ pub struct i2c_smbus_ioctl_data {

/// This is the structure as used in the I2C_RDWR ioctl call
#[repr(C)]
struct i2c_rdwr_ioctl_data {
pub struct i2c_rdwr_ioctl_data {
// struct i2c_msg __user *msgs;
msgs: *mut i2c_msg,
// __u32 nmsgs;
nmsgs: u32,
msgs: *const i2c_msg,
// __i32 nmsgs;
nmsgs: i32,
}

mod ioctl {
use super::{I2C_SLAVE, I2C_SMBUS};
pub use super::i2c_smbus_ioctl_data;
use super::{I2C_SLAVE, I2C_SMBUS, I2C_RDWR};
use super::i2c_smbus_ioctl_data;
use super::i2c_rdwr_ioctl_data;

ioctl!(bad write_int set_i2c_slave_address with I2C_SLAVE);
ioctl!(bad write_ptr i2c_smbus with I2C_SMBUS; i2c_smbus_ioctl_data);
ioctl_write_int_bad!(set_i2c_slave_address, I2C_SLAVE);
ioctl_write_ptr_bad!(i2c_smbus, I2C_SMBUS, i2c_smbus_ioctl_data);
ioctl_write_ptr_bad!(i2c_rdrw, I2C_RDWR, i2c_rdwr_ioctl_data);
}


Expand Down Expand Up @@ -429,3 +410,18 @@ pub fn i2c_smbus_process_call_block(fd: RawFd, register: u8, values: &[u8]) -> R
let count = data.block[0];
Ok((&data.block[1..(count + 1) as usize]).to_vec())
}

#[inline]
pub fn i2c_rdwr(fd: RawFd, values: &[i2c_msg]) -> Result<(), I2CError> {
// TODO: Find out if this is the best course of action or if we should let Linux throw the error
assert!(values.len() <= I2C_RDRW_IOCTL_MAX_MSGS);

let i2c_data = i2c_rdwr_ioctl_data {
msgs: values.as_ptr(),
nmsgs: values.len() as i32,
};

unsafe {
ioctl::i2c_rdrw(fd, &i2c_data).map(drop)
}
}
61 changes: 60 additions & 1 deletion src/linux.rs
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
// except according to those terms.

use ffi;
use core::I2CDevice;
use core::{
I2CDevice,
I2CMessage,
};
use std::error::Error;
use std::path::Path;
use std::fs::File;
Expand Down Expand Up @@ -125,6 +128,7 @@ impl LinuxI2CDevice {

impl I2CDevice for LinuxI2CDevice {
type Error = LinuxI2CError;
type I2CMessage = LinuxMessage;

/// Read data from the device to fill the provided slice
fn read(&mut self, data: &mut [u8]) -> Result<(), LinuxI2CError> {
Expand Down Expand Up @@ -220,4 +224,59 @@ impl I2CDevice for LinuxI2CDevice {
fn smbus_process_block(&mut self, register: u8, values: &[u8]) -> Result<Vec<u8>, LinuxI2CError> {
ffi::i2c_smbus_process_call_block(self.as_raw_fd(), register, values).map_err(From::from)
}

fn transfer(&self, messages: &[LinuxMessage]) -> Result<(), LinuxI2CError> {
ffi::i2c_rdwr(self.as_raw_fd(), messages).map_err(From::from)
}
}

type LinuxMessage = ffi::i2c_msg;

bitflags! {
/// Various flags used by the i2c_rdwr ioctl on Linux. For details, see
/// https://www.kernel.org/doc/Documentation/i2c/i2c-protocol
///
/// In general, these are for special cases and should not be needed
pub struct I2CMsgFlags: u16 {
/// Use ten bit addressing on this message
const TenBitAddress = 0x0010;
/// Read data, from slave to master
const Read = 0x0001;
/// Force an I2C stop condition on this message
const Stop = 0x8000;
/// Avoid sending an I2C start condition on this message
const NoStart = 0x4000;
/// If you need to invert a 'read' command bit to a 'write'
const InvertCommand = 0x2000;
/// Force this message to ignore I2C negative acknowlegements
const IgnoreNack = 0x1000;
/// Force message to ignore acknowledgement
const IgnoreAck = 0x0800;
/// Allow the client to specify how many bytes it will send
const UseRecieveLength = 0x0400;
}
}

impl I2CMessage for LinuxMessage {
type Flags = I2CMsgFlags;

fn custom(&self, data: &[u8], address: u16, flags: I2CMsgFlags) -> LinuxMessage {
// Convert from special type to u16
let flags = flags.bits();

Self {
addr: address,
flags,
len: data.len() as u16,
buf: data.as_ptr(),
}
}

fn read(&self, data: &[u8]) -> LinuxMessage {
self.custom(data, self.addr, I2CMsgFlags::Read)
}

fn write(&self, data: &[u8]) -> LinuxMessage {
self.custom(data, self.addr, I2CMsgFlags::empty())
}
}
28 changes: 27 additions & 1 deletion src/mock.rs
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use core::I2CDevice;
use core::{
I2CDevice,
I2CMessage,
};
use std::io;

pub type I2CResult<T> = io::Result<T>;
Expand Down Expand Up @@ -54,6 +57,24 @@ impl I2CRegisterMap {
}
}

pub struct MockMessage;

impl I2CMessage for MockMessage {
type Flags = ();

fn read(&self, _data: &[u8]) -> Self {
unimplemented!();
}

fn write(&self, _data: &[u8]) -> Self {
unimplemented!();
}

fn custom(&self, _data: &[u8], _address: u16, _flags: ()) -> Self {
unimplemented!();
}
}

pub struct MockI2CDevice {
pub regmap: I2CRegisterMap,
}
Expand All @@ -67,6 +88,7 @@ impl MockI2CDevice {

impl I2CDevice for MockI2CDevice {
type Error = io::Error;
type I2CMessage = MockMessage;

fn read(&mut self, data: &mut [u8]) -> I2CResult<()> {
self.regmap.read(data)
Expand Down Expand Up @@ -99,4 +121,8 @@ impl I2CDevice for MockI2CDevice {
fn smbus_write_i2c_block_data(&mut self, _register: u8, _values: &[u8]) -> I2CResult<()> {
unimplemented!()
}

fn transfer(&self, _messages: &[MockMessage]) -> Result<(), Self::Error> {
unimplemented!()
}
}