Refactor SMFI interface and ectool
This commit is contained in:
committed by
Jeremy Soller
parent
39e2586c50
commit
eff4caa752
106
tool/src/access/lpc/direct.rs
Normal file
106
tool/src/access/lpc/direct.rs
Normal file
@ -0,0 +1,106 @@
|
||||
use hwio::{Io, Pio};
|
||||
|
||||
use crate::{
|
||||
Access,
|
||||
Error,
|
||||
SuperIo,
|
||||
Timeout,
|
||||
timeout,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Use direct hardware access. Unsafe due to not having mutual exclusion
|
||||
pub struct AccessLpcDirect<T: Timeout> {
|
||||
cmd: u16,
|
||||
dbg: u16,
|
||||
timeout: T,
|
||||
}
|
||||
|
||||
impl<T: Timeout> AccessLpcDirect<T> {
|
||||
/// Checks that Super I/O ID matches and then returns access object
|
||||
pub unsafe fn new(timeout: T) -> Result<Self, Error> {
|
||||
// Make sure EC ID matches
|
||||
let mut sio = SuperIo::new(0x2E);
|
||||
let id =
|
||||
(sio.read(0x20) as u16) << 8 |
|
||||
(sio.read(0x21) as u16);
|
||||
match id {
|
||||
0x5570 | 0x8587 => (),
|
||||
_ => return Err(Error::SuperIoId(id)),
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
cmd: SMFI_CMD_BASE,
|
||||
dbg: SMFI_DBG_BASE,
|
||||
timeout,
|
||||
})
|
||||
}
|
||||
|
||||
/// Read from the command space
|
||||
unsafe fn read_cmd(&mut self, addr: u8) -> u8 {
|
||||
Pio::<u8>::new(
|
||||
self.cmd + (addr as u16)
|
||||
).read()
|
||||
}
|
||||
|
||||
/// Write to the command space
|
||||
unsafe fn write_cmd(&mut self, addr: u8, data: u8) {
|
||||
Pio::<u8>::new(
|
||||
self.cmd + (addr as u16)
|
||||
).write(data)
|
||||
}
|
||||
|
||||
/// Read from the debug space
|
||||
//TODO: better public interface
|
||||
pub unsafe fn read_debug(&mut self, addr: u8) -> u8 {
|
||||
Pio::<u8>::new(
|
||||
self.dbg + (addr as u16)
|
||||
).read()
|
||||
}
|
||||
|
||||
/// Returns Ok if a command can be sent
|
||||
unsafe fn command_check(&mut self) -> Result<(), Error> {
|
||||
if self.read_cmd(SMFI_CMD_CMD) == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::WouldBlock)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Timeout> Access for AccessLpcDirect<T> {
|
||||
unsafe fn command(&mut self, cmd: u8, data: &mut [u8]) -> Result<u8, Error> {
|
||||
// Test data length
|
||||
if data.len() > self.data_size() {
|
||||
return Err(Error::DataLength(data.len()));
|
||||
}
|
||||
|
||||
// All previous commands should be finished
|
||||
self.command_check()?;
|
||||
|
||||
// Write data bytes, index should be valid due to length test above
|
||||
for i in 0..data.len() {
|
||||
self.write_cmd(i as u8 + SMFI_CMD_DATA, data[i]);
|
||||
}
|
||||
|
||||
// Write command byte, which starts command
|
||||
self.write_cmd(SMFI_CMD_CMD, cmd as u8);
|
||||
|
||||
// Wait for command to finish with timeout
|
||||
self.timeout.reset();
|
||||
timeout!(self.timeout, self.command_check())?;
|
||||
|
||||
// Read data bytes, index should be valid due to length test above
|
||||
for i in 0..data.len() {
|
||||
data[i] = self.read_cmd(i as u8 + SMFI_CMD_DATA);
|
||||
}
|
||||
|
||||
// Return response byte
|
||||
Ok(self.read_cmd(SMFI_CMD_RES))
|
||||
}
|
||||
|
||||
fn data_size(&self) -> usize {
|
||||
SMFI_CMD_SIZE - SMFI_CMD_DATA as usize
|
||||
}
|
||||
}
|
183
tool/src/access/lpc/linux.rs
Normal file
183
tool/src/access/lpc/linux.rs
Normal file
@ -0,0 +1,183 @@
|
||||
use std::{
|
||||
fs,
|
||||
io::{
|
||||
self,
|
||||
Read,
|
||||
Write,
|
||||
Seek,
|
||||
SeekFrom,
|
||||
},
|
||||
os::unix::io::AsRawFd,
|
||||
path::Path,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
Access,
|
||||
Error,
|
||||
StdTimeout,
|
||||
Timeout,
|
||||
timeout,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
struct PortLock {
|
||||
start: u16,
|
||||
len: u16,
|
||||
file: fs::File,
|
||||
}
|
||||
|
||||
impl PortLock {
|
||||
pub fn new(start: u16, end: u16) -> io::Result<Self> {
|
||||
if end < start {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"PortLock::new: end < start"
|
||||
));
|
||||
}
|
||||
let len = (end - start) + 1;
|
||||
|
||||
let file = fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open("/dev/port")?;
|
||||
|
||||
let mut flock = libc::flock {
|
||||
l_type: libc::F_WRLCK as _,
|
||||
l_whence: libc::SEEK_SET as _,
|
||||
l_start: start as _,
|
||||
l_len: len as _,
|
||||
l_pid: 0,
|
||||
};
|
||||
|
||||
if unsafe { libc::fcntl(file.as_raw_fd(), libc::F_SETLK, &mut flock) < 0 } {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
start,
|
||||
len,
|
||||
file,
|
||||
})
|
||||
}
|
||||
|
||||
fn seek(&mut self, offset: u16) -> io::Result<()> {
|
||||
if offset >= self.len {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"PortLock::seek: offset >= len"
|
||||
));
|
||||
}
|
||||
let port = self.start + offset;
|
||||
let pos = self.file.seek(SeekFrom::Start(port as u64))?;
|
||||
if pos != port as u64 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::UnexpectedEof,
|
||||
"PortLock::seek: failed to seek to port"
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read(&mut self, offset: u16) -> io::Result<u8> {
|
||||
self.seek(offset)?;
|
||||
let mut data = [0];
|
||||
self.file.read_exact(&mut data)?;
|
||||
Ok(data[0])
|
||||
}
|
||||
|
||||
pub fn write(&mut self, offset: u16, value: u8) -> io::Result<()> {
|
||||
self.seek(offset)?;
|
||||
self.file.write_all(&[value])
|
||||
}
|
||||
}
|
||||
|
||||
/// Use /dev/port access with file locking
|
||||
pub struct AccessLpcLinux {
|
||||
cmd: PortLock,
|
||||
dbg: PortLock,
|
||||
timeout: StdTimeout,
|
||||
}
|
||||
|
||||
impl AccessLpcLinux {
|
||||
/// Locks ports and then returns access object
|
||||
pub unsafe fn new(timeout: Duration) -> Result<Self, Error> {
|
||||
// TODO: is there a better way to probe before running a command?
|
||||
if ! Path::new("/sys/bus/acpi/devices/17761776:00").is_dir() {
|
||||
return Err(Error::Io(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"Failed to find System76 ACPI device",
|
||||
)));
|
||||
}
|
||||
|
||||
let cmd = PortLock::new(SMFI_CMD_BASE, SMFI_CMD_BASE + SMFI_CMD_SIZE as u16 - 1).map_err(Error::Io)?;
|
||||
let dbg = PortLock::new(SMFI_DBG_BASE, SMFI_DBG_BASE + SMFI_DBG_SIZE as u16 - 1).map_err(Error::Io)?;
|
||||
Ok(Self {
|
||||
cmd,
|
||||
dbg,
|
||||
timeout: StdTimeout::new(timeout),
|
||||
})
|
||||
}
|
||||
|
||||
/// Read from the command space
|
||||
unsafe fn read_cmd(&mut self, addr: u8) -> Result<u8, Error> {
|
||||
self.cmd.read(addr as u16).map_err(Error::Io)
|
||||
}
|
||||
|
||||
/// Write to the command space
|
||||
unsafe fn write_cmd(&mut self, addr: u8, data: u8) -> Result<(), Error> {
|
||||
self.cmd.write(addr as u16, data).map_err(Error::Io)
|
||||
}
|
||||
|
||||
/// Read from the debug space
|
||||
//TODO: better public interface
|
||||
pub unsafe fn read_debug(&mut self, addr: u8) -> Result<u8, Error> {
|
||||
self.dbg.read(addr as u16).map_err(Error::Io)
|
||||
}
|
||||
|
||||
/// Returns Ok if a command can be sent
|
||||
unsafe fn command_check(&mut self) -> Result<(), Error> {
|
||||
if self.read_cmd(SMFI_CMD_CMD)? == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::WouldBlock)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Access for AccessLpcLinux {
|
||||
unsafe fn command(&mut self, cmd: u8, data: &mut [u8]) -> Result<u8, Error> {
|
||||
// Test data length
|
||||
if data.len() > self.data_size() {
|
||||
return Err(Error::DataLength(data.len()));
|
||||
}
|
||||
|
||||
// All previous commands should be finished
|
||||
self.command_check()?;
|
||||
|
||||
// Write data bytes, index should be valid due to length test above
|
||||
for i in 0..data.len() {
|
||||
self.write_cmd(i as u8 + SMFI_CMD_DATA, data[i])?;
|
||||
}
|
||||
|
||||
// Write command byte, which starts command
|
||||
self.write_cmd(SMFI_CMD_CMD, cmd as u8)?;
|
||||
|
||||
// Wait for command to finish with timeout
|
||||
self.timeout.reset();
|
||||
timeout!(self.timeout, self.command_check())?;
|
||||
|
||||
// Read data bytes, index should be valid due to length test above
|
||||
for i in 0..data.len() {
|
||||
data[i] = self.read_cmd(i as u8 + SMFI_CMD_DATA)?;
|
||||
}
|
||||
|
||||
// Return response byte
|
||||
self.read_cmd(SMFI_CMD_RES)
|
||||
}
|
||||
|
||||
fn data_size(&self) -> usize {
|
||||
SMFI_CMD_SIZE - SMFI_CMD_DATA as usize
|
||||
}
|
||||
}
|
17
tool/src/access/lpc/mod.rs
Normal file
17
tool/src/access/lpc/mod.rs
Normal file
@ -0,0 +1,17 @@
|
||||
pub(crate) const SMFI_CMD_BASE: u16 = 0xE00;
|
||||
pub(crate) const SMFI_CMD_SIZE: usize = 0x100;
|
||||
|
||||
pub(crate) const SMFI_DBG_BASE: u16 = 0xF00;
|
||||
pub(crate) const SMFI_DBG_SIZE: usize = 0x100;
|
||||
|
||||
pub(crate) const SMFI_CMD_CMD: u8 = 0x00;
|
||||
pub(crate) const SMFI_CMD_RES: u8 = 0x01;
|
||||
pub(crate) const SMFI_CMD_DATA: u8 = 0x02;
|
||||
|
||||
pub use self::direct::AccessLpcDirect;
|
||||
mod direct;
|
||||
|
||||
#[cfg(all(feature = "std", target_os = "linux"))]
|
||||
pub use self::linux::AccessLpcLinux;
|
||||
#[cfg(all(feature = "std", target_os = "linux"))]
|
||||
mod linux;
|
13
tool/src/access/mod.rs
Normal file
13
tool/src/access/mod.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use crate::Error;
|
||||
|
||||
pub use self::lpc::*;
|
||||
mod lpc;
|
||||
|
||||
/// Access method for running an EC command
|
||||
pub trait Access {
|
||||
/// Sends a command using the access method. Only internal use is recommended
|
||||
unsafe fn command(&mut self, cmd: u8, data: &mut [u8]) -> Result<u8, Error>;
|
||||
|
||||
/// The maximum size that can be provided for the data argument
|
||||
fn data_size(&self) -> usize;
|
||||
}
|
Reference in New Issue
Block a user