Skip to content
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

Transactional read/write via I2C_RDWR ioctl #50

Merged
merged 4 commits into from
Nov 23, 2019
Merged
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
79 changes: 79 additions & 0 deletions examples/pca9956b.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2018, Piers Finlayson <[email protected]>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/license/LICENSE-2.0> or the MIT license
// <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.extern crate i2cdev;

extern crate i2cdev;
extern crate docopt;

#[cfg(any(target_os = "linux", target_os = "android"))]
use i2cdev::core::{I2CTransfer, I2CMessage};
#[cfg(any(target_os = "linux", target_os = "android"))]
use i2cdev::linux::{LinuxI2CBus, LinuxI2CMessage};

use std::env::args;
use docopt::Docopt;

const USAGE: &str = "
Reads registers from a PCA9956B IC via Linux i2cdev.

Assumes the PCA9956B is using address 0x20.

Usage:
pca9956b <device>
pca9956b (-h | --help)
pca9956b --version

Options:
-h --help Show this help text.
--version Show version.
";

const ADDR: u16 = 0x20;

#[cfg(any(target_os = "linux", target_os = "android"))]
fn main() {
let args = Docopt::new(USAGE)
.and_then(|d| d.argv(args()).parse())
.unwrap_or_else(|e| e.exit());
let path = args.get_str("<device>");
let mut bus = match LinuxI2CBus::new(path) {
Ok(bus) => bus,
Err(_e) => {
println!("Error opening I2C Bus {} {}", path, _e);
return;
}
};
println!("Opened I2C Bus OK: {}", path);

// Build two I2C messages:
// 1) Write the MODE1 register address, with top bit indcating auto-
// increment should be enabled
// 2) Read 10 bytes from the current register onwards
let mut data = [0; 10];
let mut msgs = [
LinuxI2CMessage::write(ADDR, &[0b1000_0000]),
LinuxI2CMessage::read(ADDR, &mut data),
];

// Send the messages to the kernel to process
match bus.transfer(&mut msgs) {
Ok(rc) => println!("Successful transfer call: {} messages processed", rc),
Err(_e) => {
println!("Error reading/writing {}", _e);
return;
}
}

// Print the data read from the device. A recently reset PCA9956B should
// return:
// 0x8005000000000000ff00
let mut output = "Result: 0x".to_string();
for byte in &data {
output = format!("{}{:02x}", output, byte);
}
println!("{}", output);
}
30 changes: 30 additions & 0 deletions src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,33 @@ pub trait I2CDevice {
/// 1 to 31 bytes of data from it.
fn smbus_process_block(&mut self, register: u8, values: &[u8]) -> Result<Vec<u8>, Self::Error>;
}

/// Interface to an I2C Bus from an I2C Master
///
/// This is used when the client wants to interact directly with the bus
/// without specifying an I2C slave address up-front, either because it needs
/// to communicate with multiple addresses without creatings separate
/// I2CDevice objects, or because it wants to make used of the I2C_RDWR ioctl
/// which allows the client to send and transmit multiple sets I2C data in a
/// single operation, potentially to different I2C slave addresses.
///
/// Typical implementations will store state with references to the bus
/// in use. The trait is based on the Linux i2cdev interface.
pub trait I2CTransfer<'a> {
type Error: Error;
type Message: I2CMessage<'a>;

// Performs multiple serially chained I2C read/write transactions. On
// success the return code is the number of successfully executed
// transactions
fn transfer(&mut self, msgs: &'a mut [Self::Message]) -> Result<u32, Self::Error>;
}

/// Read/Write I2C message
pub trait I2CMessage<'a> {
/// Read data from device
fn read(slave_address: u16, data: &'a mut [u8]) -> Self;

/// Write data to device
fn write(slave_address: u16, data: &'a [u8]) -> Self;
}
56 changes: 26 additions & 30 deletions src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,23 @@ use std::mem;
use std::ptr;
use std::io::Cursor;
use std::os::unix::prelude::*;
use std::marker::PhantomData;
use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt};
use libc::c_int;

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! {
struct I2CFunctions: u32 {
Expand Down Expand Up @@ -163,23 +144,25 @@ pub struct i2c_smbus_ioctl_data {
}

/// This is the structure as used in the I2C_RDWR ioctl call
// see linux/i2c-dev.h
#[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,
}

mod ioctl {
use super::{I2C_SLAVE, I2C_SMBUS};
use super::{I2C_SLAVE, I2C_SMBUS, I2C_RDWR};
pub use super::i2c_smbus_ioctl_data;
pub use super::i2c_rdwr_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_rdwr, I2C_RDWR, i2c_rdwr_ioctl_data);
}


pub fn i2c_set_slave_address(fd: RawFd, slave_address: u16) -> Result<(), nix::Error> {
unsafe {
ioctl::set_i2c_slave_address(fd, slave_address as i32)?;
Expand Down Expand Up @@ -429,3 +412,16 @@ 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: &mut [i2c_msg]) -> Result<u32, I2CError> {
let i2c_data = i2c_rdwr_ioctl_data {
msgs: values.as_mut_ptr(),
nmsgs: values.len() as u32,
};

unsafe {
let n = ioctl::i2c_rdwr(fd, &i2c_data)?;
Ok(n as u32)
}
}
27 changes: 27 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,33 @@
//! }
//! }
//! ```
//!
//! ```rust,no_run
//! extern crate i2cdev;
//!
//! use std::thread;
//! use std::time::Duration;
//!
//! use i2cdev::core::*;
//! use i2cdev::linux::{LinuxI2CBus, LinuxI2CError, LinuxI2CMessage};
//!
//! const SLAVE_ADDR: u16 = 0x57;
//!
//! fn write_read_transaction() -> Result<(), LinuxI2CError> {
//! let mut dev = LinuxI2CBus::new("/dev/i2c-1")?;
//!
//! let mut read_data = [0; 2];
//! let mut msgs = [
//! LinuxI2CMessage::write(SLAVE_ADDR, &[0x01]),
//! LinuxI2CMessage::read(SLAVE_ADDR, &mut read_data)
//! ];
//! dev.transfer(&mut msgs)?;
//! thread::sleep(Duration::from_millis(100));
//!
//! println!("Reading: {:?}", read_data);
//! Ok(())
//! }
//! ```

#![crate_name = "i2cdev"]
#![crate_type = "lib"]
Expand Down
99 changes: 98 additions & 1 deletion src/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// except according to those terms.

use ffi;
use core::I2CDevice;
use core::{I2CDevice, I2CTransfer};
use std::error::Error;
use std::path::Path;
use std::fs::File;
Expand All @@ -18,11 +18,18 @@ use std::fs::OpenOptions;
use std::io::prelude::*;
use std::os::unix::prelude::*;

// Expose these core structs from this module
pub use core::I2CMessage;

pub struct LinuxI2CDevice {
devfile: File,
slave_address: u16,
}

pub struct LinuxI2CBus {
devfile: File,
}

#[derive(Debug)]
pub enum LinuxI2CError {
Nix(nix::Error),
Expand Down Expand Up @@ -88,6 +95,12 @@ impl AsRawFd for LinuxI2CDevice {
}
}

impl AsRawFd for LinuxI2CBus {
fn as_raw_fd(&self) -> RawFd {
self.devfile.as_raw_fd()
}
}

impl LinuxI2CDevice {
/// Create a new I2CDevice for the specified path
pub fn new<P: AsRef<Path>>(path: P,
Expand Down Expand Up @@ -221,3 +234,87 @@ impl I2CDevice for LinuxI2CDevice {
ffi::i2c_smbus_process_call_block(self.as_raw_fd(), register, values).map_err(From::from)
}
}

impl LinuxI2CBus {
/// Create a new LinuxI2CBus for the specified path
pub fn new<P: AsRef<Path>>(path: P)
-> Result<LinuxI2CBus, LinuxI2CError> {
let file = OpenOptions::new()
.read(true)
.write(true)
.open(path)?;
let bus = LinuxI2CBus {
devfile: file,
};
Ok(bus)
}
}

/// Linux I2C message
pub type LinuxI2CMessage<'a> = ffi::i2c_msg;

impl<'a> I2CTransfer<'a> for LinuxI2CBus {
type Error = LinuxI2CError;
type Message = LinuxI2CMessage<'a>;

/// Issue the provided sequence of I2C transactions
fn transfer(&mut self, msgs: &'a mut [Self::Message]) -> Result<u32, LinuxI2CError> {
ffi::i2c_rdwr(self.as_raw_fd(), msgs).map_err(From::from)
}
}

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 I2CMessageFlags: u16 {
/// Use ten bit addressing on this message
const TEN_BIT_ADDRESS = 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 NO_START = 0x4000;
/// If you need to invert a 'read' command bit to a 'write'
const INVERT_COMMAND = 0x2000;
/// Force this message to ignore I2C negative acknowlegements
const IGNORE_NACK = 0x1000;
/// Force message to ignore acknowledgement
const IGNORE_ACK = 0x0800;
/// Allow the client to specify how many bytes it will send
const USE_RECEIVE_LENGTH = 0x0400;
}
}

impl<'a> I2CMessage<'a> for LinuxI2CMessage<'a> {
fn read(slave_address: u16, data: &'a mut [u8]) -> LinuxI2CMessage {
Self {
addr: slave_address,
flags: I2CMessageFlags::READ.bits(),
len: data.len() as u16,
buf: data.as_ptr(),
}
}

fn write(slave_address: u16, data: &'a [u8]) -> LinuxI2CMessage {
Self {
addr: slave_address,
flags: I2CMessageFlags::empty().bits(),
len: data.len() as u16,
buf: data.as_ptr(),
}
}
}

impl<'a> LinuxI2CMessage<'a> {
pub fn with_flags(self, flags: I2CMessageFlags) -> Self {
Self {
addr: self.addr,
flags: flags.bits(),
len: self.len,
buf: self.buf,
}
}
}