Faster flashing with SMFI (#32)
* WIP: support for new flashing API * Add SPI flashing support to tool * Add timeouts when flashing with ectool * Test SPI reading * Use chunks for SPI commands * Sanity test of flash size * Read rom in sectors * Relocate memmap region, remove PMC3 * Use ectool to flash * Remove debugging of spi command * Fix flashing over smfi
This commit is contained in:
150
tool/src/ec.rs
150
tool/src/ec.rs
@ -2,6 +2,7 @@ use hwio::{Io, Pio};
|
||||
|
||||
use crate::{
|
||||
Error,
|
||||
Spi,
|
||||
SuperIo,
|
||||
Timeout,
|
||||
timeout
|
||||
@ -9,13 +10,20 @@ use crate::{
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum EcCmd {
|
||||
pub enum Cmd {
|
||||
None = 0,
|
||||
Probe = 1,
|
||||
Board = 2,
|
||||
Version = 3,
|
||||
Debug = 4,
|
||||
Spi = 5,
|
||||
Reset = 6,
|
||||
}
|
||||
|
||||
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 struct Ec<T: Timeout> {
|
||||
cmd: u16,
|
||||
dbg: u16,
|
||||
@ -24,8 +32,8 @@ pub struct Ec<T: Timeout> {
|
||||
|
||||
impl<T: Timeout> Ec<T> {
|
||||
/// Probes for a compatible EC
|
||||
pub unsafe fn new(primary: bool, timeout: T) -> Result<Self, Error> {
|
||||
let mut sio = SuperIo::new(if primary { 0x2E } else { 0x4E });
|
||||
pub unsafe fn new(timeout: T) -> Result<Self, Error> {
|
||||
let mut sio = SuperIo::new(0x2E);
|
||||
|
||||
let id =
|
||||
(sio.read(0x20) as u16) << 8 |
|
||||
@ -37,8 +45,8 @@ impl<T: Timeout> Ec<T> {
|
||||
}
|
||||
|
||||
let mut ec = Ec {
|
||||
cmd: if primary { 0xC00 } else { 0xE00 },
|
||||
dbg: if primary { 0xD00 } else { 0xF00 },
|
||||
cmd: 0xE00,
|
||||
dbg: 0xF00,
|
||||
timeout,
|
||||
};
|
||||
|
||||
@ -68,44 +76,39 @@ impl<T: Timeout> Ec<T> {
|
||||
).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);
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Finish an EC command
|
||||
pub unsafe fn command_finish(&mut self) -> Result<(), Error> {
|
||||
if self.can_command() {
|
||||
match self.read(1) {
|
||||
0 => Ok(()),
|
||||
err => Err(Error::Protocol(err)),
|
||||
}
|
||||
} 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 (start and finish)
|
||||
pub unsafe fn command(&mut self, cmd: EcCmd) -> Result<(), Error> {
|
||||
self.timeout.reset();
|
||||
|
||||
timeout!(self.timeout, self.command_start(cmd))?;
|
||||
timeout!(self.timeout, self.command_finish())
|
||||
/// 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) {
|
||||
0 => Ok(()),
|
||||
err => Err(Error::Protocol(err)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Probe for EC
|
||||
pub unsafe fn probe(&mut self) -> Result<u8, Error> {
|
||||
self.command(EcCmd::Probe)?;
|
||||
self.command(Cmd::Probe)?;
|
||||
let signature = (
|
||||
self.read(2),
|
||||
self.read(3)
|
||||
@ -120,7 +123,7 @@ impl<T: Timeout> Ec<T> {
|
||||
|
||||
/// Read board from EC
|
||||
pub unsafe fn board(&mut self, data: &mut [u8]) -> Result<usize, Error> {
|
||||
self.command(EcCmd::Board)?;
|
||||
self.command(Cmd::Board)?;
|
||||
let mut i = 0;
|
||||
while i < data.len() && (i + 2) < 256 {
|
||||
data[i] = self.read((i + 2) as u8);
|
||||
@ -134,7 +137,7 @@ impl<T: Timeout> Ec<T> {
|
||||
|
||||
/// Read version from EC
|
||||
pub unsafe fn version(&mut self, data: &mut [u8]) -> Result<usize, Error> {
|
||||
self.command(EcCmd::Version)?;
|
||||
self.command(Cmd::Version)?;
|
||||
let mut i = 0;
|
||||
while i < data.len() && (i + 2) < 256 {
|
||||
data[i] = self.read((i + 2) as u8);
|
||||
@ -145,4 +148,85 @@ impl<T: Timeout> Ec<T> {
|
||||
}
|
||||
Ok(i)
|
||||
}
|
||||
|
||||
pub unsafe fn spi(&mut self, scratch: bool) -> Result<EcSpi<T>, Error> {
|
||||
let mut spi = EcSpi {
|
||||
ec: self,
|
||||
scratch,
|
||||
};
|
||||
spi.reset()?;
|
||||
Ok(spi)
|
||||
}
|
||||
|
||||
pub unsafe fn reset(&mut self) -> Result<(), Error> {
|
||||
self.command(Cmd::Reset)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EcSpi<'a, T: Timeout> {
|
||||
ec: &'a mut Ec<T>,
|
||||
scratch: bool,
|
||||
}
|
||||
|
||||
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 =
|
||||
CMD_SPI_FLAG_DISABLE |
|
||||
if self.scratch { CMD_SPI_FLAG_SCRATCH } else { 0 };
|
||||
|
||||
self.ec.write(2, flags);
|
||||
self.ec.write(3, 0);
|
||||
self.ec.command(Cmd::Spi)?;
|
||||
assert_eq!(self.ec.read(3), 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// SPI read
|
||||
unsafe fn read(&mut self, data: &mut [u8]) -> Result<usize, Error> {
|
||||
let flags =
|
||||
CMD_SPI_FLAG_READ |
|
||||
if self.scratch { CMD_SPI_FLAG_SCRATCH } else { 0 };
|
||||
|
||||
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)?;
|
||||
assert_eq!(self.ec.read(3), chunk.len() as u8);
|
||||
|
||||
for i in 0..chunk.len() {
|
||||
chunk[i] = self.ec.read(i as u8 + 4);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(data.len())
|
||||
}
|
||||
|
||||
/// SPI write
|
||||
unsafe fn write(&mut self, data: &[u8]) -> Result<usize, Error> {
|
||||
let flags =
|
||||
if self.scratch { CMD_SPI_FLAG_SCRATCH } else { 0 };
|
||||
|
||||
for chunk in data.chunks(256 - 4) {
|
||||
for i in 0..chunk.len() {
|
||||
self.ec.write(i as u8 + 4, chunk[i]);
|
||||
}
|
||||
|
||||
self.ec.write(2, flags);
|
||||
self.ec.write(3, chunk.len() as u8);
|
||||
self.ec.command(Cmd::Spi)?;
|
||||
assert_eq!(self.ec.read(3), chunk.len() as u8);
|
||||
}
|
||||
|
||||
Ok(data.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Timeout> Drop for EcSpi<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = self.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Parameter,
|
||||
Protocol(u8),
|
||||
Signature((u8, u8)),
|
||||
SuperIoId(u16),
|
||||
Timeout,
|
||||
Verify,
|
||||
WouldBlock,
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,9 @@ mod legacy;
|
||||
pub use self::pmc::Pmc;
|
||||
mod pmc;
|
||||
|
||||
pub use self::spi::{Spi, SpiRom};
|
||||
mod spi;
|
||||
|
||||
pub use self::super_io::SuperIo;
|
||||
mod super_io;
|
||||
|
||||
|
119
tool/src/main.rs
119
tool/src/main.rs
@ -2,6 +2,7 @@ use ectool::{
|
||||
Ec,
|
||||
Error,
|
||||
Firmware,
|
||||
SpiRom,
|
||||
Timeout,
|
||||
};
|
||||
use std::{
|
||||
@ -53,7 +54,6 @@ unsafe fn console() -> Result<(), Error> {
|
||||
iopl();
|
||||
|
||||
let mut ec = Ec::new(
|
||||
true,
|
||||
StdTimeout::new(Duration::new(1, 0)),
|
||||
)?;
|
||||
|
||||
@ -73,6 +73,98 @@ unsafe fn console() -> Result<(), Error> {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn flash_inner(ec: &mut Ec<StdTimeout>, firmware: &Firmware) -> Result<(), Error> {
|
||||
let rom_size = 128 * 1024;
|
||||
let sector_size = 1024;
|
||||
|
||||
let mut spi_bus = ec.spi(true)?;
|
||||
let mut spi = SpiRom::new(
|
||||
&mut spi_bus,
|
||||
StdTimeout::new(Duration::new(1, 0))
|
||||
);
|
||||
|
||||
// Read entire ROM
|
||||
let mut rom = vec![0; rom_size];
|
||||
eprintln!("SPI read");
|
||||
spi.read_at(0, &mut rom)?;
|
||||
|
||||
eprintln!("Saving ROM to backup.rom");
|
||||
fs::write("backup.rom", &rom).map_err(|_| Error::Verify)?;
|
||||
|
||||
let mut matches = true;
|
||||
for i in 0..rom.len() {
|
||||
if &rom[i] != firmware.data.get(i).unwrap_or(&0xFF) {
|
||||
matches = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if matches {
|
||||
eprintln!("ROM matches specified firmware");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
{
|
||||
// Chip erase
|
||||
// eprintln!("SPI chip erase");
|
||||
// spi.erase_chip()?;
|
||||
|
||||
// Sector erase
|
||||
let mut address = 0;
|
||||
while address < rom_size {
|
||||
let mut erased = true;
|
||||
for &b in &rom[address..address + sector_size] {
|
||||
if b != 0xFF {
|
||||
erased =false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if erased {
|
||||
eprintln!("SPI sector already erased {:06X}", address);
|
||||
address += sector_size;
|
||||
} else {
|
||||
eprintln!("SPI sector erase {:06X}", address);
|
||||
address += spi.erase_sector(address as u32)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Read entire ROM
|
||||
eprintln!("SPI read");
|
||||
spi.read_at(0, &mut rom)?;
|
||||
}
|
||||
|
||||
// Verify chip erase
|
||||
for i in 0..rom.len() {
|
||||
if rom[i] != 0xFF {
|
||||
eprintln!("Failed to erase: {:X} is {:X} instead of {:X}", i, rom[i], 0xFF);
|
||||
return Err(Error::Verify);
|
||||
}
|
||||
}
|
||||
|
||||
// Program
|
||||
{
|
||||
eprintln!("SPI AAI word program");
|
||||
spi.write_at(0, &firmware.data)?;
|
||||
|
||||
// Read entire ROM
|
||||
eprintln!("SPI read");
|
||||
spi.read_at(0, &mut rom)?;
|
||||
}
|
||||
|
||||
// Verify program
|
||||
for i in 0..rom.len() {
|
||||
if &rom[i] != firmware.data.get(i).unwrap_or(&0xFF) {
|
||||
eprintln!("Failed to program: {:X} is {:X} instead of {:X}", i, rom[i], firmware.data[i]);
|
||||
return Err(Error::Verify);
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("Successfully programmed SPI ROM");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn flash(path: &str) -> Result<(), Error> {
|
||||
//TODO: remove unwraps
|
||||
let firmware_data = fs::read(path).unwrap();
|
||||
@ -83,7 +175,6 @@ unsafe fn flash(path: &str) -> Result<(), Error> {
|
||||
iopl();
|
||||
|
||||
let mut ec = Ec::new(
|
||||
true,
|
||||
StdTimeout::new(Duration::new(1, 0)),
|
||||
)?;
|
||||
|
||||
@ -107,14 +198,34 @@ unsafe fn flash(path: &str) -> Result<(), Error> {
|
||||
println!("ec version: {:?}", str::from_utf8(ec_version));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// Wait for any key releases
|
||||
eprintln!("Waiting 5 seconds for all keys to be released");
|
||||
thread::sleep(Duration::new(5, 0));
|
||||
|
||||
eprintln!("Sync");
|
||||
let _ = process::Command::new("sync").status();
|
||||
|
||||
let res = flash_inner(&mut ec, &firmware);
|
||||
eprintln!("Result: {:X?}", res);
|
||||
|
||||
eprintln!("Sync");
|
||||
let _ = process::Command::new("sync").status();
|
||||
|
||||
eprintln!("System will shut off in 5 seconds");
|
||||
thread::sleep(Duration::new(5, 0));
|
||||
|
||||
eprintln!("Sync");
|
||||
let _ = process::Command::new("sync").status();
|
||||
|
||||
ec.reset()?;
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
unsafe fn info() -> Result<(), Error> {
|
||||
iopl();
|
||||
|
||||
let mut ec = Ec::new(
|
||||
true,
|
||||
StdTimeout::new(Duration::new(1, 0)),
|
||||
)?;
|
||||
|
||||
|
165
tool/src/spi.rs
Normal file
165
tool/src/spi.rs
Normal file
@ -0,0 +1,165 @@
|
||||
use crate::{
|
||||
Error,
|
||||
Timeout,
|
||||
};
|
||||
|
||||
pub trait Spi {
|
||||
unsafe fn reset(&mut self) -> Result<(), Error>;
|
||||
unsafe fn read(&mut self, data: &mut [u8]) -> Result<usize, Error>;
|
||||
unsafe fn write(&mut self, data: &[u8]) -> Result<usize, Error>;
|
||||
}
|
||||
|
||||
|
||||
pub struct SpiRom<'a, S: Spi, T: Timeout> {
|
||||
spi: &'a mut S,
|
||||
timeout: T,
|
||||
}
|
||||
|
||||
impl<'a, S: Spi, T: Timeout> SpiRom<'a, S, T> {
|
||||
pub fn new(spi: &'a mut S, timeout: T) -> Self {
|
||||
Self {
|
||||
spi,
|
||||
timeout,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn status(&mut self) -> Result<u8, Error> {
|
||||
let mut status = [0];
|
||||
|
||||
self.spi.reset()?;
|
||||
self.spi.write(&[0x05])?;
|
||||
self.spi.read(&mut status)?;
|
||||
|
||||
Ok(status[0])
|
||||
}
|
||||
|
||||
pub unsafe fn status_wait(&mut self, mask: u8, value: u8) -> Result<(), Error> {
|
||||
self.timeout.reset();
|
||||
while self.timeout.running() {
|
||||
if self.status()? & mask == value {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(Error::Timeout)
|
||||
}
|
||||
|
||||
pub unsafe fn write_disable(&mut self) -> Result<(), Error> {
|
||||
self.spi.reset()?;
|
||||
self.spi.write(&[0x04])?;
|
||||
|
||||
// Poll status for busy unset and write enable unset
|
||||
self.status_wait(3, 0)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub unsafe fn write_enable(&mut self) -> Result<(), Error> {
|
||||
self.spi.reset()?;
|
||||
self.spi.write(&[0x06])?;
|
||||
|
||||
// Poll status for busy unset and write enable set
|
||||
self.status_wait(3, 2)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub unsafe fn erase_chip(&mut self) -> Result<(), Error> {
|
||||
self.write_enable()?;
|
||||
|
||||
self.spi.reset()?;
|
||||
self.spi.write(&[0x60])?;
|
||||
|
||||
// Poll status for busy unset
|
||||
self.status_wait(1, 0)?;
|
||||
|
||||
self.write_disable()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub unsafe fn erase_sector(&mut self, address: u32) -> Result<usize, Error> {
|
||||
if (address & 0xFF00_0000) > 0 {
|
||||
return Err(Error::Parameter);
|
||||
}
|
||||
|
||||
self.write_enable()?;
|
||||
|
||||
self.spi.reset()?;
|
||||
self.spi.write(&[
|
||||
0xD7,
|
||||
(address >> 16) as u8,
|
||||
(address >> 8) as u8,
|
||||
address as u8,
|
||||
])?;
|
||||
|
||||
// Poll status for busy unset
|
||||
self.status_wait(1, 0)?;
|
||||
|
||||
self.write_disable()?;
|
||||
|
||||
//TODO: dynamically figure out this value
|
||||
Ok(1024)
|
||||
}
|
||||
|
||||
pub unsafe fn read_at(&mut self, address: u32, data: &mut [u8]) -> Result<usize, Error> {
|
||||
if (address & 0xFF00_0000) > 0 {
|
||||
return Err(Error::Parameter);
|
||||
}
|
||||
|
||||
self.spi.reset()?;
|
||||
self.spi.write(&[
|
||||
0x0B,
|
||||
(address >> 16) as u8,
|
||||
(address >> 8) as u8,
|
||||
address as u8,
|
||||
0,
|
||||
])?;
|
||||
self.spi.read(data)
|
||||
}
|
||||
|
||||
pub unsafe fn write_at(&mut self, address: u32, data: &[u8]) -> Result<usize, Error> {
|
||||
if (address & 0xFF00_0000) > 0 {
|
||||
return Err(Error::Parameter);
|
||||
}
|
||||
|
||||
self.write_enable()?;
|
||||
|
||||
for (i, word) in data.chunks(2).enumerate() {
|
||||
let low = *word.get(0).unwrap_or(&0xFF);
|
||||
let high = *word.get(1).unwrap_or(&0xFF);
|
||||
|
||||
self.spi.reset()?;
|
||||
if i == 0 {
|
||||
self.spi.write(&[
|
||||
0xAD,
|
||||
(address >> 16) as u8,
|
||||
(address >> 8) as u8,
|
||||
address as u8,
|
||||
low,
|
||||
high
|
||||
])?;
|
||||
} else {
|
||||
self.spi.write(&[
|
||||
0xAD,
|
||||
low,
|
||||
high
|
||||
])?;
|
||||
}
|
||||
|
||||
// Poll status for busy unset
|
||||
self.status_wait(1, 0)?;
|
||||
}
|
||||
|
||||
self.write_disable()?;
|
||||
|
||||
Ok(data.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: Spi, T: Timeout> Drop for SpiRom<'a, S, T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = self.write_disable();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user