diff --git a/tool/.gitignore b/tool/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/tool/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/tool/Cargo.lock b/tool/Cargo.lock new file mode 100644 index 0000000..646c9d5 --- /dev/null +++ b/tool/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "redox_hwio" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "system76_ectool" +version = "0.1.0" +dependencies = [ + "redox_hwio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum redox_hwio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c5c3085fd5054b1f2ad35668f7c60031e6a7cb05d82477f936ac700d24d4477" diff --git a/tool/Cargo.toml b/tool/Cargo.toml new file mode 100644 index 0000000..bdca982 --- /dev/null +++ b/tool/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "system76_ectool" +version = "0.1.0" +edition = "2018" +description = "System76 EC tool" +authors = ["Jeremy Soller "] +repository = "https://github.com/system76/ec" + +[lib] +name = "ectool" + +[dependencies] +redox_hwio = "0.1" diff --git a/tool/src/ec.rs b/tool/src/ec.rs new file mode 100644 index 0000000..6f540be --- /dev/null +++ b/tool/src/ec.rs @@ -0,0 +1,164 @@ +use hwio::{Io, Pio}; + +use crate::error::Error; +use crate::super_io::SuperIo; +use crate::timeout::Timeout; + +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +pub enum EcCmd { + None = 0, + Probe = 1, + Board = 2, + Version = 3, +} + +pub struct Ec { + cmd: u16, + dbg: u16, + timeout: T, +} + +impl Ec { + /// Probes for a compatible EC + pub unsafe fn new(primary: bool, timeout: T) -> Result { + let mut sio = SuperIo::new(if primary { 0x2E } else { 0x4E }); + + let id = + (sio.read(0x20) as u16) << 8 | + (sio.read(0x21) as u16); + + match id { + 0x5570 | 0x8587 => (), + _ => return Err(Error::SuperIoId(id)), + } + + let mut ec = Ec { + cmd: if primary { 0xC00 } else { 0xE00 }, + dbg: if primary { 0xD00 } else { 0xF00 }, + timeout, + }; + + ec.probe()?; + + Ok(ec) + } + + /// Read from the command space + pub unsafe fn read(&mut self, addr: u8) -> u8 { + Pio::::new( + self.cmd + (addr as u16) + ).read() + } + + /// Write to the command space + pub unsafe fn write(&mut self, addr: u8, data: u8) { + Pio::::new( + self.cmd + (addr as u16) + ).write(data) + } + + /// Read from the debug space + pub unsafe fn debug(&mut self, addr: u8) -> u8 { + Pio::::new( + self.dbg + (addr as u16) + ).read() + } + + /// Returns true if a command can be sent + pub unsafe fn can_command(&mut self) -> bool { + self.read(0) == EcCmd::None as u8 + } + + /// Start an EC command + pub unsafe fn command_start(&mut self, cmd: EcCmd) -> Result<(), Error> { + if self.can_command() { + self.write(0, cmd as u8); + Ok(()) + } else { + Err(Error::WouldBlock) + } + } + + /// Finish an EC command + pub unsafe fn command_finish(&mut self) -> Result<(), Error> { + if self.can_command() { + Err(Error::WouldBlock) + } else { + match self.read(1) { + 0 => Ok(()), + err => Err(Error::Protocol(err)), + } + } + } + + /// Run an EC command (start and finish) + pub unsafe fn command(&mut self, cmd: EcCmd) -> Result<(), Error> { + self.timeout.reset(); + + while self.timeout.running() { + match self.command_start(cmd) { + Ok(()) => break, + Err(err) => match err { + Error::WouldBlock => (), + _ => return Err(err) + }, + } + } + + while self.timeout.running() { + match self.command_finish() { + Ok(ok) => return Ok(ok), + Err(err) => match err { + Error::WouldBlock => (), + _ => return Err(err) + }, + } + } + + Err(Error::Timeout) + } + + /// Probe for EC + pub unsafe fn probe(&mut self) -> Result { + self.command(EcCmd::Probe)?; + let signature = ( + self.read(2), + self.read(3) + ); + if signature == (0x76, 0xEC) { + let version = self.read(4); + Ok(version) + } else { + Err(Error::Signature(signature)) + } + } + + /// Read board from EC + pub unsafe fn board(&mut self, data: &mut [u8]) -> Result { + self.command(EcCmd::Board)?; + let mut i = 0; + while i < data.len() && (i + 2) < 256 { + data[i] = self.read((i + 2) as u8); + if data[i] == 0 { + break; + } + i += 1; + } + Ok(i) + } + + /// Read version from EC + pub unsafe fn version(&mut self, data: &mut [u8]) -> Result { + self.command(EcCmd::Version)?; + let mut i = 0; + while i < data.len() && (i + 2) < 256 { + data[i] = self.read((i + 2) as u8); + if data[i] == 0 { + break; + } + i += 1; + } + Ok(i) + } +} diff --git a/tool/src/error.rs b/tool/src/error.rs new file mode 100644 index 0000000..2e76103 --- /dev/null +++ b/tool/src/error.rs @@ -0,0 +1,9 @@ +#[derive(Debug)] +pub enum Error { + Protocol(u8), + Signature((u8, u8)), + SuperIoId(u16), + Timeout, + WouldBlock, +} + diff --git a/tool/src/lib.rs b/tool/src/lib.rs new file mode 100644 index 0000000..a1433c7 --- /dev/null +++ b/tool/src/lib.rs @@ -0,0 +1,7 @@ +#![no_std] + +pub mod ec; +pub mod error; +pub mod pmc; +pub mod super_io; +pub mod timeout; diff --git a/tool/src/main.rs b/tool/src/main.rs new file mode 100644 index 0000000..adc1755 --- /dev/null +++ b/tool/src/main.rs @@ -0,0 +1,82 @@ +use ectool::{ + ec::Ec, + error::Error, + timeout::Timeout, +}; +use std::{ + io, + process, + time::{Duration, Instant}, +}; + +pub struct StdTimeout { + instant: Instant, + duration: Duration, +} + +impl StdTimeout { + pub fn new(duration: Duration) -> Self { + StdTimeout { + instant: Instant::now(), + duration + } + } +} + +impl Timeout for StdTimeout { + fn reset(&mut self) { + self.instant = Instant::now(); + } + + fn running(&self) -> bool { + self.instant.elapsed() < self.duration + } +} + +unsafe fn tool() -> Result<(), Error> { + let mut ec = Ec::new( + true, + StdTimeout::new(Duration::new(1, 0)), + )?; + + { + print!("board: "); + let mut data = [0; 256]; + let size = ec.board(&mut data)?; + for &b in data[..size].iter() { + print!("{}", b as char); + } + println!(); + } + + { + print!("version: "); + let mut data = [0; 256]; + let size = ec.version(&mut data)?; + for &b in data[..size].iter() { + print!("{}", b as char); + } + println!(); + } + + Ok(()) +} + +fn main() { + extern { + fn iopl(level: isize) -> isize; + } + + if unsafe { iopl(3) < 0 } { + eprintln!("failed to get I/O permission: {}", io::Error::last_os_error()); + process::exit(1); + } + + match unsafe { tool() } { + Ok(()) => (), + Err(err) => { + eprintln!("failed to run tool: {:X?}", err); + process::exit(1); + } + } +} diff --git a/tool/src/pmc.rs b/tool/src/pmc.rs new file mode 100644 index 0000000..743888e --- /dev/null +++ b/tool/src/pmc.rs @@ -0,0 +1,56 @@ +use hwio::{Io, Pio}; + +pub struct Pmc { + data: Pio, + cmd: Pio, +} + +impl Pmc { + /// Create a new PMC instance. `base` identifies the data port. The command + /// port will be `base + 4` + pub fn new(base: u16) -> Self { + Self { + data: Pio::new(base), + cmd: Pio::new(base + 4), + } + } + + /// Returns true if PMC can be read from + pub unsafe fn can_read(&mut self) -> bool { + self.cmd.read() & 1 == 1 + } + + /// Returns true if PMC can be written to + pub unsafe fn can_write(&mut self) -> bool { + self.cmd.read() & 2 == 0 + } + + /// Write a command to the PMC. Returns None if unable to write + pub unsafe fn command(&mut self, data: u8) -> Option<()> { + if self.can_write() { + self.cmd.write(data); + Some(()) + } else { + None + } + } + + /// Read data from the PMC. Returns None if unable to read + pub unsafe fn read(&mut self) -> Option { + if self.can_read() { + Some(self.data.read()) + } else { + None + } + } + + /// Write data to the PMC. Returns false if unable to write + pub unsafe fn write(&mut self, data: u8) -> Option<()> { + if self.can_write() { + self.data.write(data); + Some(()) + } else { + None + } + } +} diff --git a/tool/src/super_io.rs b/tool/src/super_io.rs new file mode 100644 index 0000000..8543434 --- /dev/null +++ b/tool/src/super_io.rs @@ -0,0 +1,27 @@ +use hwio::{Io, Pio}; + +pub struct SuperIo { + addr: Pio, + data: Pio, +} + +impl SuperIo { + /// Create a new SuperIo. `base` identifies the address port. The data port + /// will be `base + 1` + pub fn new(base: u16) -> Self { + Self { + addr: Pio::new(base), + data: Pio::new(base + 1), + } + } + + pub unsafe fn read(&mut self, addr: u8) -> u8 { + self.addr.write(addr); + self.data.read() + } + + pub unsafe fn write(&mut self, addr: u8, data: u8) { + self.addr.write(addr); + self.data.write(data); + } +} diff --git a/tool/src/timeout.rs b/tool/src/timeout.rs new file mode 100644 index 0000000..a4ce933 --- /dev/null +++ b/tool/src/timeout.rs @@ -0,0 +1,4 @@ +pub trait Timeout { + fn reset(&mut self); + fn running(&self) -> bool; +}