diff --git a/rust-toolchain b/rust-toolchain index 2cf3e8f..40973da 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2019-11-04 +nightly-2020-07-27 diff --git a/src/board/system76/common/scratch/scratch.mk b/src/board/system76/common/scratch/scratch.mk index 34fdc98..6ec36dd 100644 --- a/src/board/system76/common/scratch/scratch.mk +++ b/src/board/system76/common/scratch/scratch.mk @@ -17,7 +17,6 @@ SCRATCH_CFLAGS+=-I$(SCRATCH_DIR)/include -D__SCRATCH__ # Add minimal source from other directories SCRATCH_SRC+=\ - $(COMMON_DIR)/version.c \ $(SYSTEM76_COMMON_DIR)/smfi.c SCRATCH_BUILD=$(BUILD)/scratch diff --git a/src/board/system76/common/smfi.c b/src/board/system76/common/smfi.c index 752e445..b087e2b 100644 --- a/src/board/system76/common/smfi.c +++ b/src/board/system76/common/smfi.c @@ -1,4 +1,17 @@ // SPDX-License-Identifier: GPL-3.0-only +// +// This defines a protocol for clients on the AP (application processor) to +// communicate with the EC. The protocol is polled, and uses semaphores to +// ensure correct sequencing. +// +// The client should check that the SMFI_CMD_CMD register is set to CMD_NONE, +// indicating that the EC is waiting for a command. The client should set the +// SMFI_CMD_DATA region as necessary for the command they wish to issue. They +// should then set the SMFI_CMD_CMD byte to indicate to the EC what command to +// execute. The EC will execute the command, and then set SMFI_CMD_RES to the +// correct result. It will finally set SMFI_CMD_CMD to CMD_NONE, to indicate +// the command is complete and the result is available. The client should only +// read the SMFI_CMD_RES value when SMFI_CMD_CMD is set to CMD_NONE. #include #include @@ -29,6 +42,14 @@ volatile uint8_t __xdata __at(0x105D) HRAMW0AAS; volatile uint8_t __xdata __at(0x105E) HRAMW1AAS; // Flash control register 3 volatile uint8_t __xdata __at(0x1063) FLHCTRL3; +// Host RAM window 2 base address +volatile uint8_t __xdata __at(0x1076) HRAMW2BA; +// Host RAM window 3 base address +volatile uint8_t __xdata __at(0x1077) HRAMW3BA; +// Host RAM window 2 access allow size +volatile uint8_t __xdata __at(0x1078) HRAMW2AAS; +// Host RAM window 3 access allow size +volatile uint8_t __xdata __at(0x1079) HRAMW3AAS; // EC indirect flash access volatile uint8_t __xdata __at(0x103B) ECINDAR0; @@ -37,12 +58,135 @@ volatile uint8_t __xdata __at(0x103D) ECINDAR2; volatile uint8_t __xdata __at(0x103E) ECINDAR3; volatile uint8_t __xdata __at(0x103F) ECINDDR; +// Command region - allows client to send commands to EC +#define SMFI_CMD_CMD 0x00 +#define SMFI_CMD_RES 0x01 +#define SMFI_CMD_DATA 0x02 static volatile uint8_t __xdata __at(0xE00) smfi_cmd[256]; + +// Debug region - ring buffer of EC firmware prints +#define SMFI_DBG_TAIL 0x00 static volatile uint8_t __xdata __at(0xF00) smfi_dbg[256]; +#if !defined(__SCRATCH__) +void smfi_init(void) { + int i; + + // Clear command region + for (i = (SMFI_CMD_CMD + 1); i < ARRAY_SIZE(smfi_cmd); i++) { + smfi_cmd[i] = 0x00; + } + // Clear host command last + smfi_cmd[SMFI_CMD_CMD] = 0x00; + + // Clear debug region + for (i = (SMFI_DBG_TAIL + 1); i < ARRAY_SIZE(smfi_dbg); i++) { + smfi_dbg[i] = 0x00; + } + // Clear tail last + smfi_dbg[SMFI_DBG_TAIL] = 0x00; + + // H2RAM window 0 address 0xE00 - 0xEFF, read/write + HRAMW0BA = 0xE0; + HRAMW0AAS = 0x04; + + // H2RAM window 1 address 0xF00 - 0xFFF, read-only + HRAMW1BA = 0xF0; + HRAMW1AAS = 0x34; + + // Enable H2RAM window 0 and 1 using LPC I/O + HRAMWC |= (1 << 4) | (1 << 1) | (1 << 0); + + // Enable backup ROM access + FLHCTRL3 |= (1 << 3); +} + +static enum Result cmd_print(void) { + uint8_t flags = smfi_cmd[SMFI_CMD_DATA]; + uint8_t len = smfi_cmd[SMFI_CMD_DATA + 1]; + + uint8_t i; + for (i = 0; (i < len) && ((i + SMFI_CMD_DATA + 2) < ARRAY_SIZE(smfi_cmd)); i++) { + putchar(smfi_cmd[i + SMFI_CMD_DATA + 2]); + } + + smfi_cmd[SMFI_CMD_DATA + 1] = i; + + return RES_OK; +} + +static enum Result cmd_fan_get(void) { + switch (smfi_cmd[SMFI_CMD_DATA]) { + case 0: + // Get duty of fan 0 + smfi_cmd[SMFI_CMD_DATA + 1] = DCR2; + return RES_OK; + case 1: + // Get duty of fan 1 + //TODO: only allow on platforms like addw2 + smfi_cmd[SMFI_CMD_DATA + 1] = DCR4; + return RES_OK; + } + + // Failed if fan not found + return RES_ERR; +} + +static enum Result cmd_fan_set(void) { + switch (smfi_cmd[SMFI_CMD_DATA]) { + case 0: + // Set duty cycle of fan 0 + DCR2 = smfi_cmd[SMFI_CMD_DATA + 1]; + return RES_OK; + case 1: + // Set duty cycle of fan 1 + //TODO: only allow on platforms like addw2 + DCR4 = smfi_cmd[SMFI_CMD_DATA + 1]; + return RES_OK; + } + + // Failed if fan not found + return RES_ERR; +} + +static enum Result cmd_keymap_get(void) { + int layer = smfi_cmd[SMFI_CMD_DATA]; + int output = smfi_cmd[SMFI_CMD_DATA + 1]; + int input = smfi_cmd[SMFI_CMD_DATA + 2]; + + if (layer < KM_LAY && output < KM_OUT && input < KM_IN) { + uint16_t key = KEYMAP[layer][output][input]; + smfi_cmd[SMFI_CMD_DATA + 3] = (uint8_t)key; + smfi_cmd[SMFI_CMD_DATA + 4] = (uint8_t)(key >> 8); + return RES_OK; + } + + // Failed if keyboard mapping not found + return RES_ERR; +} + +static enum Result cmd_keymap_set(void) { + int layer = smfi_cmd[SMFI_CMD_DATA]; + int output = smfi_cmd[SMFI_CMD_DATA + 1]; + int input = smfi_cmd[SMFI_CMD_DATA + 2]; + + if (layer < KM_LAY && output < KM_OUT && input < KM_IN) { + uint16_t key = + ((uint16_t)smfi_cmd[SMFI_CMD_DATA + 3]) | + (((uint16_t)smfi_cmd[SMFI_CMD_DATA + 4]) << 8); + KEYMAP[layer][output][input] = key; + return RES_OK; + } + + // Failed if keyboard mapping not found + return RES_ERR; +} +#endif // !defined(__SCRATCH__) + +#if defined(__SCRATCH__) static enum Result cmd_spi_scratch(void) __critical { - uint8_t flags = smfi_cmd[2]; - uint8_t len = smfi_cmd[3]; + uint8_t flags = smfi_cmd[SMFI_CMD_DATA]; + uint8_t len = smfi_cmd[SMFI_CMD_DATA + 1]; // Enable chip if (flags & CMD_SPI_FLAG_BACKUP) { @@ -56,16 +200,16 @@ static enum Result cmd_spi_scratch(void) __critical { // Read or write len bytes uint8_t i; - for (i = 0; (i < len) && ((i + 4) < ARRAY_SIZE(smfi_cmd)); i++) { + for (i = 0; (i < len) && ((i + SMFI_CMD_DATA + 2) < ARRAY_SIZE(smfi_cmd)); i++) { if (flags & CMD_SPI_FLAG_READ) { - smfi_cmd[i + 4] = ECINDDR; + smfi_cmd[i + SMFI_CMD_DATA + 2] = ECINDDR; } else { - ECINDDR = smfi_cmd[i + 4]; + ECINDDR = smfi_cmd[i + SMFI_CMD_DATA + 2]; } } // Set actually read/written count - smfi_cmd[3] = i; + smfi_cmd[SMFI_CMD_DATA + 1] = i; if (flags & CMD_SPI_FLAG_DISABLE) { // Disable chip @@ -75,65 +219,19 @@ static enum Result cmd_spi_scratch(void) __critical { return RES_OK; } - -void smfi_init(void) { - int i; - - // Clear command region - for (i = 1; i < ARRAY_SIZE(smfi_cmd); i++) { - smfi_cmd[i] = 0x00; - } - // Clear host command last - smfi_cmd[0] = 0x00; - - // Clear debug region - for (i = 1; i < ARRAY_SIZE(smfi_dbg); i++) { - smfi_dbg[i] = 0x00; - } - // Clear tail last - smfi_dbg[0] = 0x00; - - - // H2RAM window 0 address 0xE00 - 0xEFF, read/write - HRAMW0BA = 0xE0; - HRAMW0AAS = 0x04; - - // H2RAM window 1 address 0xF00 - 0xFFF, read/write - HRAMW1BA = 0xF0; - HRAMW1AAS = 0x04; - - // Enable H2RAM window 0 and 1 using LPC I/O - HRAMWC |= 0x13; - - // Enable backup ROM access - FLHCTRL3 |= (1 << 3); -} - -static enum Result cmd_print(void) { - uint8_t flags = smfi_cmd[2]; - uint8_t len = smfi_cmd[3]; - - uint8_t i; - for (i = 0; (i < len) && ((i + 4) < ARRAY_SIZE(smfi_cmd)); i++) { - putchar(smfi_cmd[i + 4]); - } - - smfi_cmd[3] = i; - - return RES_OK; -} +#endif // defined(__SCRATCH__) static enum Result cmd_spi(void) { -#ifdef __SCRATCH__ +#if defined(__SCRATCH__) return cmd_spi_scratch(); -#else - if (smfi_cmd[2] & CMD_SPI_FLAG_SCRATCH) { +#else // defined(__SCRATCH__) + if (smfi_cmd[SMFI_CMD_DATA] & CMD_SPI_FLAG_SCRATCH) { scratch_trampoline(); } // Cannot use follow mode unless running from scratch rom return RES_ERR; -#endif +#endif // defined(__SCRATCH__) } static enum Result cmd_reset(void) { @@ -145,75 +243,6 @@ static enum Result cmd_reset(void) { return RES_ERR; } -#ifndef __SCRATCH__ -static enum Result cmd_fan_get(void) { - switch (smfi_cmd[2]) { - case 0: - // Get duty of fan 0 - smfi_cmd[3] = DCR2; - return RES_OK; - case 1: - // Get duty of fan 1 - //TODO: only allow on platforms like addw2 - smfi_cmd[3] = DCR4; - return RES_OK; - } - - // Failed if fan not found - return RES_ERR; -} - -static enum Result cmd_fan_set(void) { - switch (smfi_cmd[2]) { - case 0: - // Set duty cycle of fan 0 - DCR2 = smfi_cmd[3]; - return RES_OK; - case 1: - // Set duty cycle of fan 1 - //TODO: only allow on platforms like addw2 - DCR4 = smfi_cmd[3]; - return RES_OK; - } - - // Failed if fan not found - return RES_ERR; -} - -static enum Result cmd_keymap_get(void) { - int layer = smfi_cmd[2]; - int output = smfi_cmd[3]; - int input = smfi_cmd[4]; - - if (layer < KM_LAY && output < KM_OUT && input < KM_IN) { - uint16_t key = KEYMAP[layer][output][input]; - smfi_cmd[5] = (uint8_t)key; - smfi_cmd[6] = (uint8_t)(key >> 8); - return RES_OK; - } - - // Failed if keyboard mapping not found - return RES_ERR; -} - -static enum Result cmd_keymap_set(void) { - int layer = smfi_cmd[2]; - int output = smfi_cmd[3]; - int input = smfi_cmd[4]; - - if (layer < KM_LAY && output < KM_OUT && input < KM_IN) { - uint16_t key = - ((uint16_t)smfi_cmd[5]) | - (((uint16_t)smfi_cmd[6]) << 8); - KEYMAP[layer][output][input] = key; - return RES_OK; - } - - // Failed if keyboard mapping not found - return RES_ERR; -} -#endif - // Set a watchdog timer of 10 seconds void smfi_watchdog(void) { ET1CNTLLR = 0xFF; @@ -222,72 +251,73 @@ void smfi_watchdog(void) { } void smfi_event(void) { - if (smfi_cmd[0]) { -#ifdef __SCRATCH__ + if (smfi_cmd[SMFI_CMD_CMD]) { +#if defined(__SCRATCH__) // If in scratch ROM, restart watchdog timer when command received smfi_watchdog(); #endif - switch (smfi_cmd[0]) { + switch (smfi_cmd[SMFI_CMD_CMD]) { +#if !defined(__SCRATCH__) case CMD_PROBE: // Signature - smfi_cmd[2] = 0x76; - smfi_cmd[3] = 0xEC; + smfi_cmd[SMFI_CMD_DATA + 0] = 0x76; + smfi_cmd[SMFI_CMD_DATA + 1] = 0xEC; // Version - smfi_cmd[4] = 0x01; + smfi_cmd[SMFI_CMD_DATA + 2] = 0x01; + //TODO: bitmask of implemented commands? // Always successful - smfi_cmd[1] = RES_OK; + smfi_cmd[SMFI_CMD_RES] = RES_OK; break; case CMD_BOARD: - strncpy(&smfi_cmd[2], board(), ARRAY_SIZE(smfi_cmd) - 2); + strncpy(&smfi_cmd[SMFI_CMD_DATA], board(), ARRAY_SIZE(smfi_cmd) - SMFI_CMD_DATA); // Always successful - smfi_cmd[1] = RES_OK; + smfi_cmd[SMFI_CMD_RES] = RES_OK; break; case CMD_VERSION: - strncpy(&smfi_cmd[2], version(), ARRAY_SIZE(smfi_cmd) - 2); + strncpy(&smfi_cmd[SMFI_CMD_DATA], version(), ARRAY_SIZE(smfi_cmd) - SMFI_CMD_DATA); // Always successful - smfi_cmd[1] = RES_OK; + smfi_cmd[SMFI_CMD_RES] = RES_OK; break; case CMD_PRINT: - smfi_cmd[1] = cmd_print(); + smfi_cmd[SMFI_CMD_RES] = cmd_print(); break; - case CMD_SPI: - smfi_cmd[1] = cmd_spi(); - break; - case CMD_RESET: - smfi_cmd[1] = cmd_reset(); - break; -#ifndef __SCRATCH__ case CMD_FAN_GET: - smfi_cmd[1] = cmd_fan_get(); + smfi_cmd[SMFI_CMD_RES] = cmd_fan_get(); break; case CMD_FAN_SET: - smfi_cmd[1] = cmd_fan_set(); + smfi_cmd[SMFI_CMD_RES] = cmd_fan_set(); break; case CMD_KEYMAP_GET: - smfi_cmd[1] = cmd_keymap_get(); + smfi_cmd[SMFI_CMD_RES] = cmd_keymap_get(); break; case CMD_KEYMAP_SET: - smfi_cmd[1] = cmd_keymap_set(); + smfi_cmd[SMFI_CMD_RES] = cmd_keymap_set(); + break; +#endif // !defined(__SCRATCH__) + case CMD_SPI: + smfi_cmd[SMFI_CMD_RES] = cmd_spi(); + break; + case CMD_RESET: + smfi_cmd[SMFI_CMD_RES] = cmd_reset(); break; -#endif // __SCRATCH__ default: // Command not found - smfi_cmd[1] = RES_ERR; + smfi_cmd[SMFI_CMD_RES] = RES_ERR; break; } // Mark command as finished - smfi_cmd[0] = CMD_NONE; + smfi_cmd[SMFI_CMD_CMD] = CMD_NONE; } } void smfi_debug(unsigned char byte) { - int tail = (int)smfi_dbg[0]; + int tail = (int)smfi_dbg[SMFI_DBG_TAIL]; tail++; if (tail >= ARRAY_SIZE(smfi_dbg)) { - tail = 1; + tail = SMFI_DBG_TAIL + 1; } smfi_dbg[tail] = byte; - smfi_dbg[0] = (uint8_t)tail; + smfi_dbg[SMFI_DBG_TAIL] = (uint8_t)tail; } diff --git a/tool/Cargo.lock b/tool/Cargo.lock index 077ee4c..c5eba23 100644 --- a/tool/Cargo.lock +++ b/tool/Cargo.lock @@ -5,9 +5,14 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "libc" +version = "0.2.77" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "redox_hwio" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -17,9 +22,11 @@ dependencies = [ name = "system76_ectool" version = "0.1.3" dependencies = [ - "redox_hwio 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_hwio 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [metadata] "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum redox_hwio 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "847d8b48be152acb4b75dbc37d873ac54899389691f5de3358603c889883b25d" +"checksum libc 0.2.77 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" +"checksum redox_hwio 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "41aa2c4c67329a04106644cad336238aa5adecfd73d06fb10339d472ce6d8070" diff --git a/tool/Cargo.toml b/tool/Cargo.toml index e23f69a..8fbb577 100644 --- a/tool/Cargo.toml +++ b/tool/Cargo.toml @@ -10,10 +10,15 @@ repository = "https://github.com/system76/ec" [lib] name = "ectool" +[[bin]] +name = "system76_ectool" +required-features = ["std"] + [dependencies] -redox_hwio = "0.1.1" +libc = { version = "0.2", optional = true } +redox_hwio = "0.1.3" [features] default = ["std"] stable = ["redox_hwio/stable"] -std = [] +std = ["libc"] diff --git a/tool/src/access/lpc/direct.rs b/tool/src/access/lpc/direct.rs new file mode 100644 index 0000000..c474bdc --- /dev/null +++ b/tool/src/access/lpc/direct.rs @@ -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 { + cmd: u16, + dbg: u16, + timeout: T, +} + +impl AccessLpcDirect { + /// Checks that Super I/O ID matches and then returns access object + pub unsafe fn new(timeout: T) -> Result { + // 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::::new( + self.cmd + (addr as u16) + ).read() + } + + /// Write to the command space + unsafe fn write_cmd(&mut self, addr: u8, data: u8) { + Pio::::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::::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 Access for AccessLpcDirect { + unsafe fn command(&mut self, cmd: u8, data: &mut [u8]) -> Result { + // 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 + } +} diff --git a/tool/src/access/lpc/linux.rs b/tool/src/access/lpc/linux.rs new file mode 100644 index 0000000..3b2aefb --- /dev/null +++ b/tool/src/access/lpc/linux.rs @@ -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 { + 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 { + 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 { + // 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 { + 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 { + 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 { + // 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 + } +} diff --git a/tool/src/access/lpc/mod.rs b/tool/src/access/lpc/mod.rs new file mode 100644 index 0000000..3f94965 --- /dev/null +++ b/tool/src/access/lpc/mod.rs @@ -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; diff --git a/tool/src/access/mod.rs b/tool/src/access/mod.rs new file mode 100644 index 0000000..b57c4b3 --- /dev/null +++ b/tool/src/access/mod.rs @@ -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; + + /// The maximum size that can be provided for the data argument + fn data_size(&self) -> usize; +} diff --git a/tool/src/ec.rs b/tool/src/ec.rs index 455a31e..f8515c7 100644 --- a/tool/src/ec.rs +++ b/tool/src/ec.rs @@ -1,18 +1,14 @@ -use hwio::{Io, Pio}; - use crate::{ + Access, Error, Spi, SpiTarget, - SuperIo, - Timeout, - timeout }; #[derive(Clone, Copy, Debug)] #[repr(u8)] -pub enum Cmd { - None = 0, +enum Cmd { + // None = 0, Probe = 1, Board = 2, Version = 3, @@ -25,88 +21,45 @@ pub enum Cmd { KeymapSet = 10, } -pub const CMD_SPI_FLAG_READ: u8 = 1 << 0; -pub const CMD_SPI_FLAG_DISABLE: u8 = 1 << 1; -pub const CMD_SPI_FLAG_SCRATCH: u8 = 1 << 2; -pub const CMD_SPI_FLAG_BACKUP: u8 = 1 << 3; +const CMD_SPI_FLAG_READ: u8 = 1 << 0; +const CMD_SPI_FLAG_DISABLE: u8 = 1 << 1; +const CMD_SPI_FLAG_SCRATCH: u8 = 1 << 2; +const CMD_SPI_FLAG_BACKUP: u8 = 1 << 3; -pub struct Ec { - cmd: u16, - dbg: u16, - timeout: T, +/// Run EC commands using a provided access method +pub struct Ec { + access: A, + version: u8, } -impl Ec { +impl Ec { /// Probes for a compatible EC - pub unsafe fn new(timeout: T) -> Result { - 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)), - } - + pub unsafe fn new(access: A) -> Result { + // Create EC struct with provided access method and timeout let mut ec = Ec { - cmd: 0xE00, - dbg: 0xF00, - timeout, + access, + version: 0, }; - ec.probe()?; + // Read version of protocol + ec.version = ec.probe()?; + + // Make sure protocol version is supported + match ec.version { + 1 => (), + _ => return Err(Error::Version(ec.version)), + } Ok(ec) } - /// Read from the command space - pub unsafe fn read(&mut self, addr: u8) -> u8 { - Pio::::new( - self.cmd + (addr as u16) - ).read() + /// Unsafe access to access + pub unsafe fn access(&mut self) -> &mut A { + &mut self.access } - /// 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 Ok if a command can be sent - unsafe fn command_check(&mut self) -> Result<(), Error> { - if self.read(0) == Cmd::None as u8 { - Ok(()) - } else { - Err(Error::WouldBlock) - } - } - - /// Wait until a command can be sent - unsafe fn command_wait(&mut self) -> Result<(), Error> { - self.timeout.reset(); - timeout!(self.timeout, self.command_check()) - } - - /// Run an EC command - pub unsafe fn command(&mut self, cmd: Cmd) -> Result<(), Error> { - // All previous commands should be finished - self.command_check()?; - // Write command byte - self.write(0, cmd as u8); - // Wait for command to finish - self.command_wait()?; - // Read response byte and test for error - match self.read(1) { + unsafe fn command(&mut self, cmd: Cmd, data: &mut [u8]) -> Result<(), Error> { + match self.access.command(cmd as u8, data)? { 0 => Ok(()), err => Err(Error::Protocol(err)), } @@ -114,13 +67,11 @@ impl Ec { /// Probe for EC pub unsafe fn probe(&mut self) -> Result { - self.command(Cmd::Probe)?; - let signature = ( - self.read(2), - self.read(3) - ); + let mut data = [0; 3]; + self.command(Cmd::Probe, &mut data)?; + let signature = (data[0], data[1]); if signature == (0x76, 0xEC) { - let version = self.read(4); + let version = data[2]; Ok(version) } else { Err(Error::Signature(signature)) @@ -129,10 +80,9 @@ impl Ec { /// Read board from EC pub unsafe fn board(&mut self, data: &mut [u8]) -> Result { - self.command(Cmd::Board)?; + self.command(Cmd::Board, data)?; let mut i = 0; - while i < data.len() && (i + 2) < 256 { - data[i] = self.read((i + 2) as u8); + while i < data.len() { if data[i] == 0 { break; } @@ -143,10 +93,9 @@ impl Ec { /// Read version from EC pub unsafe fn version(&mut self, data: &mut [u8]) -> Result { - self.command(Cmd::Version)?; + self.command(Cmd::Version, data)?; let mut i = 0; - while i < data.len() && (i + 2) < 256 { - data[i] = self.read((i + 2) as u8); + while i < data.len() { if data[i] == 0 { break; } @@ -155,24 +104,27 @@ impl Ec { Ok(i) } + /// Print data to EC console pub unsafe fn print(&mut self, data: &[u8]) -> Result { + //TODO: use self.access.data_size() let flags = 0; for chunk in data.chunks(256 - 4) { + let mut data = [0; 256 - 2]; + data[0] = flags; + data[1] = chunk.len() as u8; for i in 0..chunk.len() { - self.write(i as u8 + 4, chunk[i]); + data[i + 2] = chunk[i]; } - - self.write(2, flags); - self.write(3, chunk.len() as u8); - self.command(Cmd::Print)?; - if self.read(3) != chunk.len() as u8 { + self.command(Cmd::Print, &mut data)?; + if data[1] != chunk.len() as u8 { return Err(Error::Verify); } } Ok(data.len()) } - pub unsafe fn spi(&mut self, target: SpiTarget, scratch: bool) -> Result, Error> { + /// Access EC SPI bus + pub unsafe fn spi(&mut self, target: SpiTarget, scratch: bool) -> Result, Error> { let mut spi = EcSpi { ec: self, target, @@ -182,50 +134,66 @@ impl Ec { Ok(spi) } + /// Reset EC. Will also power off computer. pub unsafe fn reset(&mut self) -> Result<(), Error> { - self.command(Cmd::Reset) + self.command(Cmd::Reset, &mut []) } + /// Read fan duty cycle by fan index pub unsafe fn fan_get(&mut self, index: u8) -> Result { - self.write(2, index); - self.command(Cmd::FanGet)?; - Ok(self.read(3)) + let mut data = [ + index, + 0 + ]; + self.command(Cmd::FanGet, &mut data)?; + Ok(data[1]) } + /// Set fan duty cycle by fan index pub unsafe fn fan_set(&mut self, index: u8, duty: u8) -> Result<(), Error> { - self.write(2, index); - self.write(3, duty); - self.command(Cmd::FanSet) + let mut data = [ + index, + duty + ]; + self.command(Cmd::FanSet, &mut data) } + /// Read keymap data by layout, output pin, and input pin pub unsafe fn keymap_get(&mut self, layer: u8, output: u8, input: u8) -> Result { - self.write(2, layer); - self.write(3, output); - self.write(4, input); - self.command(Cmd::KeymapGet)?; + let mut data = [ + layer, + output, + input, + 0, + 0 + ]; + self.command(Cmd::KeymapGet, &mut data)?; Ok( - (self.read(5) as u16) | - ((self.read(6) as u16) << 8) + (data[3] as u16) | + ((data[4] as u16) << 8) ) } + /// Set keymap data by layout, output pin, and input pin pub unsafe fn keymap_set(&mut self, layer: u8, output: u8, input: u8, value: u16) -> Result<(), Error> { - self.write(2, layer); - self.write(3, output); - self.write(4, input); - self.write(5, value as u8); - self.write(6, (value >> 8) as u8); - self.command(Cmd::KeymapSet) + let mut data = [ + layer, + output, + input, + value as u8, + (value >> 8) as u8 + ]; + self.command(Cmd::KeymapSet, &mut data) } } -pub struct EcSpi<'a, T: Timeout> { - ec: &'a mut Ec, +pub struct EcSpi<'a, A: Access> { + ec: &'a mut Ec, target: SpiTarget, scratch: bool, } -impl<'a, T: Timeout> EcSpi<'a, T> { +impl<'a, A: Access> EcSpi<'a, A> { fn flags(&self, read: bool, disable: bool) -> u8 { let mut flags = 0; @@ -252,7 +220,7 @@ impl<'a, T: Timeout> EcSpi<'a, T> { } } -impl<'a, T: Timeout> Spi for EcSpi<'a, T> { +impl<'a, A: Access> Spi for EcSpi<'a, A> { fn target(&self) -> SpiTarget { self.target } @@ -260,10 +228,12 @@ impl<'a, T: Timeout> Spi for EcSpi<'a, T> { /// Disable SPI chip, must be done before and after a transaction unsafe fn reset(&mut self) -> Result<(), Error> { let flags = self.flags(false, true); - self.ec.write(2, flags); - self.ec.write(3, 0); - self.ec.command(Cmd::Spi)?; - if self.ec.read(3) != 0 { + let mut data = [ + flags, + 0, + ]; + self.ec.command(Cmd::Spi, &mut data)?; + if data[1] != 0 { return Err(Error::Verify); } Ok(()) @@ -271,17 +241,18 @@ impl<'a, T: Timeout> Spi for EcSpi<'a, T> { /// SPI read unsafe fn read(&mut self, data: &mut [u8]) -> Result { + //TODO: use self.access.data_size() let flags = self.flags(true, false); for chunk in data.chunks_mut(256 - 4) { - self.ec.write(2, flags); - self.ec.write(3, chunk.len() as u8); - self.ec.command(Cmd::Spi)?; - if self.ec.read(3) != chunk.len() as u8 { + let mut data = [0; 256 - 2]; + data[0] = flags; + data[1] = chunk.len() as u8; + self.ec.command(Cmd::Spi, &mut data)?; + if data[1] != chunk.len() as u8 { return Err(Error::Verify); } - for i in 0..chunk.len() { - chunk[i] = self.ec.read(i as u8 + 4); + chunk[i] = data[i + 2]; } } Ok(data.len()) @@ -289,16 +260,17 @@ impl<'a, T: Timeout> Spi for EcSpi<'a, T> { /// SPI write unsafe fn write(&mut self, data: &[u8]) -> Result { + //TODO: use self.access.data_size() let flags = self.flags(false, false); for chunk in data.chunks(256 - 4) { + let mut data = [0; 256 - 2]; + data[0] = flags; + data[1] = chunk.len() as u8; for i in 0..chunk.len() { - self.ec.write(i as u8 + 4, chunk[i]); + data[i + 2] = chunk[i]; } - - self.ec.write(2, flags); - self.ec.write(3, chunk.len() as u8); - self.ec.command(Cmd::Spi)?; - if self.ec.read(3) != chunk.len() as u8 { + self.ec.command(Cmd::Spi, &mut data)?; + if data[1] != chunk.len() as u8 { return Err(Error::Verify); } } @@ -306,7 +278,7 @@ impl<'a, T: Timeout> Spi for EcSpi<'a, T> { } } -impl<'a, T: Timeout> Drop for EcSpi<'a, T> { +impl<'a, A: Access> Drop for EcSpi<'a, A> { fn drop(&mut self) { unsafe { let _ = self.reset(); diff --git a/tool/src/error.rs b/tool/src/error.rs index fdfbd64..bbcff47 100644 --- a/tool/src/error.rs +++ b/tool/src/error.rs @@ -1,11 +1,25 @@ +/// Errors returned by operations #[derive(Debug)] pub enum Error { + /// Data length is too large + DataLength(usize), + /// A parameter was invalid Parameter, + /// EC protocol returned an error result Protocol(u8), + /// EC protocol signature did not match Signature((u8, u8)), + /// Super I/O ID did not match SuperIoId(u16), + /// Blocking operation timed out Timeout, + /// Unexpected data from EC Verify, + /// EC protocol version is unsupported + Version(u8), + /// Indicates that a blocking operation would block WouldBlock, + /// Encountered a std::io::Error + #[cfg(feature = "std")] + Io(std::io::Error) } - diff --git a/tool/src/firmware.rs b/tool/src/firmware.rs index 7b91b14..ba5c28c 100644 --- a/tool/src/firmware.rs +++ b/tool/src/firmware.rs @@ -1,3 +1,4 @@ +/// Parses firmware information from a firmware ROM pub struct Firmware<'a> { pub board: &'a [u8], pub version: &'a [u8], @@ -36,6 +37,7 @@ fn firmware_str<'a>(data: &'a [u8], key: &[u8]) -> Option<&'a [u8]> { } impl<'a> Firmware<'a> { + /// Parses firmware board and version, and then returns firmware object pub fn new(data: &'a [u8]) -> Option { let board = firmware_str(data, b"76EC_BOARD=")?; let version = firmware_str(data, b"76EC_VERSION=")?; diff --git a/tool/src/legacy.rs b/tool/src/legacy.rs index e0d91df..ed6cb8c 100644 --- a/tool/src/legacy.rs +++ b/tool/src/legacy.rs @@ -5,12 +5,13 @@ use crate::{ Timeout, }; +/// Run some EC commands on previous proprietary firmware pub struct EcLegacy { pub pmc: Pmc, } impl EcLegacy { - /// Probes for a compatible EC + /// Probes for EC using direct hardware access. Unsafe due to no mutual exclusion pub unsafe fn new(primary: bool, timeout: T) -> Result { let mut sio = SuperIo::new(if primary { 0x2E } else { 0x4E }); @@ -30,6 +31,7 @@ impl EcLegacy { }) } + /// Read the EC firmware project, which uniquely identifies the board pub unsafe fn project(&mut self, data: &mut [u8]) -> Result { let mut i = 0; self.pmc.command(0x92)?; @@ -43,6 +45,7 @@ impl EcLegacy { Ok(i) } + /// Read the EC firmware version pub unsafe fn version(&mut self, data: &mut [u8]) -> Result { // Prepend `1.` to version string let mut i = 0; diff --git a/tool/src/lib.rs b/tool/src/lib.rs index 974a728..ffc2fd4 100644 --- a/tool/src/lib.rs +++ b/tool/src/lib.rs @@ -1,5 +1,8 @@ #![cfg_attr(not(feature = "std"), no_std)] +pub use self::access::*; +mod access; + pub use self::ec::Ec; mod ec; diff --git a/tool/src/main.rs b/tool/src/main.rs index d29186e..b90bb73 100644 --- a/tool/src/main.rs +++ b/tool/src/main.rs @@ -1,4 +1,6 @@ use ectool::{ + Access, + AccessLpcLinux, Ec, Error, Firmware, @@ -10,41 +12,32 @@ use ectool::{ use std::{ env, fs, - io, process, str::{self, FromStr}, time::Duration, thread, }; -unsafe fn iopl() { - extern { - fn iopl(level: isize) -> isize; - } - - if iopl(3) < 0 { - eprintln!("failed to get I/O permission: {}", io::Error::last_os_error()); - process::exit(1); - } +unsafe fn ec() -> Result, Error> { + let access = AccessLpcLinux::new(Duration::new(1, 0))?; + Ec::new(access) } unsafe fn console() -> Result<(), Error> { - iopl(); + //TODO: driver support for reading debug region? + let mut ec = ec()?; + let access = ec.access(); - let mut ec = Ec::new( - StdTimeout::new(Duration::new(1, 0)), - )?; - - let mut head = ec.debug(0) as usize; + let mut head = access.read_debug(0)? as usize; loop { - let tail = ec.debug(0) as usize; + let tail = access.read_debug(0)? as usize; if tail == 0 || head == tail { thread::sleep(Duration::from_millis(1)); } else { while head != tail { head += 1; if head >= 256 { head = 1; } - let c = ec.debug(head as u8); + let c = access.read_debug(head as u8)?; print!("{}", c as char); } } @@ -67,7 +60,7 @@ unsafe fn flash_read(spi: &mut SpiRom, rom: &mut [u8], se Ok(()) } -unsafe fn flash_inner(ec: &mut Ec, firmware: &Firmware, target: SpiTarget, scratch: bool) -> Result<(), Error> { +unsafe fn flash_inner(ec: &mut Ec, firmware: &Firmware, target: SpiTarget, scratch: bool) -> Result<(), Error> { let rom_size = 128 * 1024; let mut new_rom = firmware.data.to_vec(); @@ -153,14 +146,11 @@ unsafe fn flash(path: &str, target: SpiTarget) -> Result<(), Error> { println!("file board: {:?}", str::from_utf8(firmware.board)); println!("file version: {:?}", str::from_utf8(firmware.version)); - iopl(); - - let mut ec = Ec::new( - StdTimeout::new(Duration::new(1, 0)), - )?; + let mut ec = ec()?; + let data_size = ec.access().data_size(); { - let mut data = [0; 256]; + let mut data = vec![0; data_size]; let size = ec.board(&mut data)?; let ec_board = &data[..size]; @@ -172,7 +162,7 @@ unsafe fn flash(path: &str, target: SpiTarget) -> Result<(), Error> { } { - let mut data = [0; 256]; + let mut data = vec![0; data_size]; let size = ec.version(&mut data)?; let ec_version = &data[..size]; @@ -208,15 +198,12 @@ unsafe fn flash(path: &str, target: SpiTarget) -> Result<(), Error> { } unsafe fn info() -> Result<(), Error> { - iopl(); - - let mut ec = Ec::new( - StdTimeout::new(Duration::new(1, 0)), - )?; + let mut ec = ec()?; + let data_size = ec.access().data_size(); { print!("board: "); - let mut data = [0; 256]; + let mut data = vec![0; data_size]; let size = ec.board(&mut data)?; for &b in data[..size].iter() { print!("{}", b as char); @@ -226,7 +213,7 @@ unsafe fn info() -> Result<(), Error> { { print!("version: "); - let mut data = [0; 256]; + let mut data = vec![0; data_size]; let size = ec.version(&mut data)?; for &b in data[..size].iter() { print!("{}", b as char); @@ -238,11 +225,7 @@ unsafe fn info() -> Result<(), Error> { } unsafe fn print(message: &[u8]) -> Result<(), Error> { - iopl(); - - let mut ec = Ec::new( - StdTimeout::new(Duration::new(1, 0)), - )?; + let mut ec = ec()?; ec.print(message)?; @@ -250,11 +233,7 @@ unsafe fn print(message: &[u8]) -> Result<(), Error> { } unsafe fn fan_get(index: u8) -> Result<(), Error> { - iopl(); - - let mut ec = Ec::new( - StdTimeout::new(Duration::new(1, 0)), - )?; + let mut ec = ec()?; let duty = ec.fan_get(index)?; println!("{}", duty); @@ -263,21 +242,13 @@ unsafe fn fan_get(index: u8) -> Result<(), Error> { } unsafe fn fan_set(index: u8, duty: u8) -> Result<(), Error> { - iopl(); - - let mut ec = Ec::new( - StdTimeout::new(Duration::new(1, 0)), - )?; + let mut ec = ec()?; ec.fan_set(index, duty) } unsafe fn keymap_get(layer: u8, output: u8, input: u8) -> Result<(), Error> { - iopl(); - - let mut ec = Ec::new( - StdTimeout::new(Duration::new(1, 0)), - )?; + let mut ec = ec()?; let value = ec.keymap_get(layer, output, input)?; println!("{:04X}", value); @@ -286,11 +257,7 @@ unsafe fn keymap_get(layer: u8, output: u8, input: u8) -> Result<(), Error> { } unsafe fn keymap_set(layer: u8, output: u8, input: u8, value: u16) -> Result<(), Error> { - iopl(); - - let mut ec = Ec::new( - StdTimeout::new(Duration::new(1, 0)), - )?; + let mut ec = ec()?; ec.keymap_set(layer, output, input, value) } diff --git a/tool/src/pmc.rs b/tool/src/pmc.rs index 1d755fc..880e401 100644 --- a/tool/src/pmc.rs +++ b/tool/src/pmc.rs @@ -6,6 +6,7 @@ use crate::{ timeout }; +/// Standard ACPI EC interface pub struct Pmc { data: Pio, cmd: Pio, @@ -13,9 +14,9 @@ pub struct Pmc { } impl Pmc { - /// Create a new PMC instance. `base` identifies the data port. The command - /// port will be `base + 4` - pub fn new(base: u16, timeout: T) -> Self { + /// Create a new ACPI EC instance using direct hardware access. `base` identifies the data + /// port. The command port will be `base + 4`. Unsafe due to no mutual exclusion + pub unsafe fn new(base: u16, timeout: T) -> Self { Self { data: Pio::new(base), cmd: Pio::new(base + 4), @@ -62,6 +63,7 @@ impl Pmc { } } + /// Read from the ACPI region, at a specific address pub unsafe fn acpi_read(&mut self, addr: u8) -> Result { self.timeout.reset(); diff --git a/tool/src/spi.rs b/tool/src/spi.rs index 4e114fd..5dfa15a 100644 --- a/tool/src/spi.rs +++ b/tool/src/spi.rs @@ -3,25 +3,38 @@ use crate::{ Timeout, }; +/// SPI bus transactions pub trait Spi { + /// Return the target of the SPI bus fn target(&self) -> SpiTarget; + + /// Reset the SPI bus unsafe fn reset(&mut self) -> Result<(), Error>; + + /// Read data from the SPI bus unsafe fn read(&mut self, data: &mut [u8]) -> Result; + + /// Write data to the SPI bus unsafe fn write(&mut self, data: &[u8]) -> Result; } +/// Target which will receive SPI commands #[derive(Clone, Copy)] pub enum SpiTarget { + /// The ROM normally used by the EC Main, + /// The ROM used by the EC should the main ROM be invalid Backup, } +/// SPI ROM transactions pub struct SpiRom<'a, S: Spi, T: Timeout> { spi: &'a mut S, timeout: T, } impl<'a, S: Spi, T: Timeout> SpiRom<'a, S, T> { + /// Create a SPI ROM using the specified SPI bus and timeout pub fn new(spi: &'a mut S, timeout: T) -> Self { Self { spi, @@ -29,13 +42,16 @@ impl<'a, S: Spi, T: Timeout> SpiRom<'a, S, T> { } } + /// Get sector size in bytes pub fn sector_size(&self) -> usize { + //TODO: can this be determined automatically? match self.spi.target() { SpiTarget::Main => 1024, SpiTarget::Backup => 4096, } } + /// Read the status register pub unsafe fn status(&mut self) -> Result { let mut status = [0]; @@ -46,6 +62,7 @@ impl<'a, S: Spi, T: Timeout> SpiRom<'a, S, T> { Ok(status[0]) } + /// Wait for the status register to have `mask` bits set to `value` pub unsafe fn status_wait(&mut self, mask: u8, value: u8) -> Result<(), Error> { self.timeout.reset(); while self.timeout.running() { @@ -56,6 +73,7 @@ impl<'a, S: Spi, T: Timeout> SpiRom<'a, S, T> { Err(Error::Timeout) } + /// Disable writes pub unsafe fn write_disable(&mut self) -> Result<(), Error> { self.spi.reset()?; self.spi.write(&[0x04])?; @@ -66,6 +84,7 @@ impl<'a, S: Spi, T: Timeout> SpiRom<'a, S, T> { Ok(()) } + /// Enable writes pub unsafe fn write_enable(&mut self) -> Result<(), Error> { self.spi.reset()?; self.spi.write(&[0x06])?; @@ -76,6 +95,7 @@ impl<'a, S: Spi, T: Timeout> SpiRom<'a, S, T> { Ok(()) } + /// Erase a sector with the specified address pub unsafe fn erase_sector(&mut self, address: u32) -> Result<(), Error> { if (address & 0xFF00_0000) > 0 { return Err(Error::Parameter); @@ -104,6 +124,7 @@ impl<'a, S: Spi, T: Timeout> SpiRom<'a, S, T> { Ok(()) } + /// Read at a specific address pub unsafe fn read_at(&mut self, address: u32, data: &mut [u8]) -> Result { if (address & 0xFF00_0000) > 0 { return Err(Error::Parameter); @@ -120,6 +141,7 @@ impl<'a, S: Spi, T: Timeout> SpiRom<'a, S, T> { self.spi.read(data) } + /// Write at a specific address pub unsafe fn write_at(&mut self, address: u32, data: &[u8]) -> Result { if (address & 0xFF00_0000) > 0 { return Err(Error::Parameter); @@ -127,6 +149,7 @@ impl<'a, S: Spi, T: Timeout> SpiRom<'a, S, T> { self.write_enable()?; + //TODO: automatically detect write command match self.spi.target() { SpiTarget::Main => for (i, word) in data.chunks(2).enumerate() { let low = *word.get(0).unwrap_or(&0xFF); diff --git a/tool/src/super_io.rs b/tool/src/super_io.rs index 8543434..aa441be 100644 --- a/tool/src/super_io.rs +++ b/tool/src/super_io.rs @@ -1,25 +1,28 @@ use hwio::{Io, Pio}; +/// Super I/O interface provided by LPC ECs 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 { + /// Create a new SuperIo using direct hardware access. `base` identifies the address port. The + /// data port will be `base + 1`. Unsafe due to no mutual exclusion + pub unsafe fn new(base: u16) -> Self { Self { addr: Pio::new(base), data: Pio::new(base + 1), } } + /// Read a Super I/O register pub unsafe fn read(&mut self, addr: u8) -> u8 { self.addr.write(addr); self.data.read() } + /// Write a Super I/O register 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 index c62fbac..48e8663 100644 --- a/tool/src/timeout.rs +++ b/tool/src/timeout.rs @@ -24,11 +24,16 @@ macro_rules! timeout { }}; } +/// Timeout for use in blocking operations pub trait Timeout { + /// Reset the timeout to its initial state fn reset(&mut self); + + /// Check if timeout is still running fn running(&self) -> bool; } +/// Timeout implemented using std::time #[cfg(feature = "std")] pub struct StdTimeout { instant: Instant, @@ -37,6 +42,7 @@ pub struct StdTimeout { #[cfg(feature = "std")] impl StdTimeout { + /// Create a timeout with the specified duration pub fn new(duration: Duration) -> Self { StdTimeout { instant: Instant::now(),