use crate::ddc_trait::DdcControl;
use crate::error::DdcError;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::os::unix::io::AsRawFd;
use std::sync::Mutex;
use std::thread;
use std::time::Duration;
pub struct LinuxBackend {
pub path: String,
lock: Mutex<()>,
}
impl LinuxBackend {
const DDC_I2C_ADDR: u16 = 0x37;
const HOST_ADDRESS: u8 = 0x51;
const WRITE_DELAY: Duration = Duration::from_millis(50);
const RETRY_ATTEMPTS: usize = 3;
pub fn new(path: String) -> Self {
Self {
path,
lock: Mutex::new(()),
}
}
fn open_device(&self) -> Result<File, DdcError> {
let file = OpenOptions::new()
.read(true)
.write(true)
.open(&self.path)
.map_err(|e| match e.kind() {
std::io::ErrorKind::PermissionDenied => DdcError::AccessDenied,
_ => DdcError::BackendNotAvailable {
details: e.to_string(),
},
})?;
unsafe {
const I2C_SLAVE: libc::c_ulong = 0x0703;
if libc::ioctl(
file.as_raw_fd(),
I2C_SLAVE,
Self::DDC_I2C_ADDR as libc::c_ulong,
) < 0
{
return Err(DdcError::CommunicationFailed {
reason: format!("I2C_SLAVE ioctl failed for {}", self.path),
});
}
}
Ok(file)
}
#[inline(always)]
fn calculate_checksum(&self, data: &[u8]) -> u8 {
let sum: u8 = data.iter().fold(0u8, |a, b| a.wrapping_add(*b));
(256u16 - sum as u16).wrapping_rem(256) as u8
}
}
impl DdcControl for LinuxBackend {
fn get_vcp_feature(&self, code: u8) -> Result<(u32, u32), DdcError> {
let _guard = self
.lock
.lock()
.map_err(|_| DdcError::CommunicationFailed {
reason: "Mutex lock poisoned in linux backend (get_vcp_feature)".to_string(),
})?;
let mut file = self.open_device()?;
let mut request = [Self::HOST_ADDRESS, 0x82, 0x01, code, 0x00];
request[4] = self.calculate_checksum(&request[..4]);
for i in 0..Self::RETRY_ATTEMPTS {
if file.write_all(&request).is_ok() {
thread::sleep(Self::WRITE_DELAY);
let mut response = [0u8; 11];
if file.read_exact(&mut response).is_ok() {
if response[4] == code {
let max = ((response[6] as u32) << 8) | (response[7] as u32);
let cur = ((response[8] as u32) << 8) | (response[9] as u32);
return Ok((cur, max));
}
}
}
thread::sleep(Duration::from_millis(20 * (i as u64 + 1)));
}
Err(DdcError::CommunicationFailed {
reason: format!("I2C read timeout on {}", self.path),
})
}
fn set_vcp_feature(&self, code: u8, value: u32) -> Result<(), DdcError> {
let _guard = self
.lock
.lock()
.map_err(|_| DdcError::CommunicationFailed {
reason: "Mutex lock poisoned in linux backend (set_vcp_feature)".to_string(),
})?;
let mut file = self.open_device()?;
let mut request = [
Self::HOST_ADDRESS,
0x84,
0x03,
code,
(value >> 8) as u8,
value as u8,
0x00,
];
request[6] = self.calculate_checksum(&request[..6]);
file.write_all(&request)
.map_err(|e| DdcError::CommunicationFailed {
reason: format!("I2C write error: {}", e),
})?;
thread::sleep(Self::WRITE_DELAY);
Ok(())
}
}