usb2642_i2c/
lib.rs

1//! A Rust library for interfacing with the USB2642 I2C bus using the linux sg3 SCSI interface.
2//!
3//!
4//! ## Usage example
5//!
6//! ```rust
7//! use usb2642_i2c::{USB2642I2C, I2CAddress};
8//!
9//! const I2C_ADDRESS: I2CAddress = 0x12;
10//!
11//! if let Ok(mut usb2642) = USB2642I2C::open("/dev/sg0") {
12//!   // Write-Only
13//!   let mut write_data = [0x01, 0x02];
14//!   usb2642.write(I2C_ADDRESS, &mut write_data).unwrap();
15//!
16//!   // Write-Read
17//!   let write_data = [0x02];
18//!   let read_data = usb2642.write_read(I2C_ADDRESS, &write_data, 1).unwrap();
19//! }
20//! ```
21
22#[macro_use]
23extern crate nix;
24
25use std::{
26    fs::OpenOptions,
27    io::{Error, ErrorKind, Result},
28    os::unix::io::{IntoRawFd, RawFd},
29};
30
31use nix::{libc::ioctl, sys::ioctl::ioctl_num_type};
32use num_derive::ToPrimitive;
33use num_traits::ToPrimitive;
34
35pub type I2CAddress = u8;
36
37pub const SG_INTERFACE_ID_ORIG: u8 = b'S';
38
39pub const SG_IO: u32 = 0x2285;
40
41#[derive(ToPrimitive)]
42pub enum DataTransferDirection {
43    ToDev = -2,
44    FromDev = -3,
45}
46
47pub const USB2642_SCSI_OPCODE: u8 = 0xcf;
48pub const USB2642_I2C_WRITE_STREAM: u8 = 0x23;
49pub const USB2642_I2C_WRITE_READ_STREAM: u8 = 0x22;
50
51pub trait USB2642I2CCommand {}
52
53#[derive(Debug, Default)]
54#[repr(C)]
55pub struct USB2642I2CWriteReadCommand {
56    scsi_vendor_command: u8,
57    scsi_vendor_action_write_read_i2c: u8,
58    i2c_write_slave_address: u8,
59    i2c_read_slave_address: u8,
60    i2c_read_data_phase_length_high: u8,
61    i2c_read_data_phase_length_low: u8,
62    i2c_write_phase_length: u8,
63    i2c_write_phase_payload: [u8; 9],
64}
65
66impl USB2642I2CWriteReadCommand {
67    pub fn new(i2c_addr: u8, write_data: &[u8], read_len: usize) -> Result<Self> {
68        if read_len > 9 {
69            return Err(Error::new(ErrorKind::InvalidInput, "read_len > 9 bytes"));
70        } else if write_data.len() > 9 {
71            return Err(Error::new(
72                ErrorKind::InvalidInput,
73                "write_data.len() > 9 bytes",
74            ));
75        }
76
77        let i2c_write_addr = i2c_addr << 1;
78        let i2c_read_addr = i2c_write_addr + 1;
79
80        let mut s = Self {
81            scsi_vendor_command: USB2642_SCSI_OPCODE,
82            scsi_vendor_action_write_read_i2c: USB2642_I2C_WRITE_READ_STREAM,
83            i2c_write_slave_address: i2c_write_addr,
84            i2c_read_slave_address: i2c_read_addr,
85            i2c_read_data_phase_length_high: ((read_len >> 8) & 0xff) as u8,
86            i2c_read_data_phase_length_low: (read_len & 0xff) as u8,
87            i2c_write_phase_length: write_data.len() as u8,
88            i2c_write_phase_payload: Default::default(),
89        };
90
91        for (i, b) in (&write_data[..write_data.len()]).iter().enumerate() {
92            s.i2c_write_phase_payload[i] = *b;
93        }
94
95        Ok(s)
96    }
97}
98
99impl USB2642I2CCommand for USB2642I2CWriteReadCommand {}
100
101#[derive(Debug, Default)]
102#[repr(C)]
103pub struct USB2642I2CWriteCommand {
104    scsi_vendor_command: u8,
105    scsi_vendor_action_write_i2c: u8,
106    i2c_slave_address: u8,
107    i2c_unused: u8,
108    i2c_data_phase_length_high: u8,
109    i2c_data_phase_length_low: u8,
110    i2c_command_phase_length: u8,
111    i2c_command_phase_payload: [u8; 9],
112}
113
114impl USB2642I2CWriteCommand {
115    pub fn new(i2c_addr: u8, write_data: &[u8]) -> Result<Self> {
116        if write_data.len() > 9 {
117            return Err(Error::new(
118                ErrorKind::InvalidInput,
119                "write_data.len() > 9 bytes",
120            ));
121        }
122
123        let i2c_write_addr = i2c_addr << 1;
124
125        let mut s = Self {
126            scsi_vendor_command: USB2642_SCSI_OPCODE,
127            scsi_vendor_action_write_i2c: USB2642_I2C_WRITE_STREAM,
128            i2c_slave_address: i2c_write_addr,
129            i2c_unused: 0,
130            i2c_data_phase_length_high: 0u8,
131            i2c_data_phase_length_low: 0u8,
132            i2c_command_phase_length: write_data.len() as u8,
133            i2c_command_phase_payload: Default::default(),
134        };
135
136        for (i, b) in (&write_data[..write_data.len()]).iter().enumerate() {
137            s.i2c_command_phase_payload[i] = *b;
138        }
139
140        Ok(s)
141    }
142}
143
144impl USB2642I2CCommand for USB2642I2CWriteCommand {}
145
146#[derive(Debug)]
147#[repr(C)]
148pub struct SgIoHdr<CMD: USB2642I2CCommand> {
149    /// [i] 'S' for SCSI generic (required)
150    interface_id: i32,
151    /// [i] data transfer direction
152    dxfer_direction: i32,
153    /// [i] SCSI command length
154    cmd_len: u8,
155    /// [i] max length to write to sbp
156    mx_sb_len: u8,
157    /// [i] 0 implies no scatter gather
158    iovec_count: u16,
159    /// [i] byte count of data transfer
160    dxfer_len: u32,
161    /// [i], [*io] points to data transfer memory or scatter gather list
162    dxferp: *mut u8,
163    /// [i], [*i] points to command to perform
164    cmdp: *mut CMD,
165    /// [i], [*o] points to sense_buffer memory
166    sbp: *mut u8,
167    /// [i] MAX_UINT->no timeout (unit: millisec)
168    timeout: u32,
169    /// [i] 0 -> default, see SG_FLAG...
170    flags: u32,
171    /// [i->o] unused internally (normally)
172    pack_id: i32,
173    /// [i->o] unused internally
174    usr_ptr: *const u8,
175    /// [o] scsi status
176    status: u8,
177    /// [o] shifted, masked scsi status
178    masked_status: u8,
179    /// [o] messaging level data (optional)
180    msg_status: u8,
181    /// [o] byte count actually written to sbp
182    sb_len_wr: u8,
183    /// [o] errors from host adapter
184    host_status: u16,
185    /// [o] errors from software driver
186    driver_status: u16,
187    /// [o] dxfer_len - actual_transferred
188    resid: i32,
189    /// [o] time taken by cmd (unit: millisec)
190    duration: u32,
191    /// [o] auxiliary information
192    info: u32,
193}
194
195impl<CMD: USB2642I2CCommand> SgIoHdr<CMD> {
196    pub fn new(
197        mut command: CMD,
198        sg_dxfer: DataTransferDirection,
199        data_buffer: *mut u8,
200        data_len: usize,
201    ) -> Self {
202        let mut sense = [0u8; 64];
203        Self {
204            interface_id: 'S' as i32,
205            dxfer_direction: sg_dxfer.to_i32().unwrap(),
206            cmd_len: std::mem::size_of::<CMD>() as u8,
207            mx_sb_len: sense.len() as u8,
208            iovec_count: 0,
209            dxfer_len: data_len as u32,
210            dxferp: data_buffer,
211            cmdp: &mut command,
212            sbp: sense.as_mut_ptr(),
213            timeout: 3000,
214            flags: 0,
215            pack_id: 0,
216            usr_ptr: std::ptr::null(),
217            status: 0,
218            masked_status: 0,
219            msg_status: 0,
220            sb_len_wr: 0,
221            host_status: 0,
222            driver_status: 0,
223            resid: 0,
224            duration: 0,
225            info: 0,
226        }
227    }
228}
229
230pub struct USB2642I2C {
231    sg_fd: RawFd,
232}
233
234impl USB2642I2C {
235    pub fn open<S: Into<String>>(sg_dev: S) -> Result<Self> {
236        let sg_fd = OpenOptions::new()
237            .read(true)
238            .write(true)
239            .open(sg_dev.into())?;
240        Ok(Self {
241            sg_fd: sg_fd.into_raw_fd(),
242        })
243    }
244
245    fn sg_ioctl<CMD: USB2642I2CCommand>(&self, sg_io_hdr: &SgIoHdr<CMD>) -> Result<()> {
246        if let Err(e) =
247            unsafe { convert_ioctl_res!(ioctl(self.sg_fd, SG_IO as ioctl_num_type, sg_io_hdr)) }
248        {
249            return Err(Error::new(ErrorKind::Other, e));
250        }
251        Ok(())
252    }
253
254    pub fn write(&mut self, i2c_addr: I2CAddress, data: &mut [u8]) -> Result<()> {
255        let command = USB2642I2CWriteCommand::new(i2c_addr, data)?;
256        let sgio = SgIoHdr::new(
257            command,
258            DataTransferDirection::ToDev,
259            std::ptr::null_mut(),
260            0,
261        );
262        self.sg_ioctl(&sgio)
263    }
264
265    pub fn write_read(
266        &mut self,
267        i2c_addr: I2CAddress,
268        data: &[u8],
269        read_len: usize,
270    ) -> Result<Vec<u8>> {
271        let command = USB2642I2CWriteReadCommand::new(i2c_addr, data, read_len)?;
272
273        let mut out_buffer = [0u8; 9];
274
275        let sgio = SgIoHdr::new(
276            command,
277            DataTransferDirection::FromDev,
278            out_buffer.as_mut_ptr(),
279            read_len,
280        );
281
282        self.sg_ioctl(&sgio)?;
283
284        Ok((&out_buffer[..read_len]).to_vec())
285    }
286}