603 lines
20 KiB
Rust
603 lines
20 KiB
Rust
// SPDX-License-Identifier: MIT
|
|
|
|
use clap::{Arg, App, AppSettings, SubCommand};
|
|
use ectool::{
|
|
Access,
|
|
AccessHid,
|
|
AccessLpcLinux,
|
|
AccessLpcSim,
|
|
Ec,
|
|
Error,
|
|
Firmware,
|
|
StdTimeout,
|
|
Spi,
|
|
SpiRom,
|
|
SpiTarget,
|
|
};
|
|
use hidapi::HidApi;
|
|
use std::{
|
|
fmt::Display,
|
|
fs,
|
|
process,
|
|
str::{self, FromStr},
|
|
time::Duration,
|
|
thread,
|
|
};
|
|
|
|
unsafe fn console(ec: &mut Ec<Box<dyn Access>>) -> Result<(), Error> {
|
|
//TODO: driver support for reading debug region?
|
|
let access = ec.access();
|
|
|
|
let mut head = access.read_debug(0)? as usize;
|
|
loop {
|
|
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 = access.read_debug(head as u8)?;
|
|
print!("{}", c as char);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe fn flash_read<S: Spi>(spi: &mut SpiRom<S, StdTimeout>, rom: &mut [u8], sector_size: usize) -> Result<(), Error> {
|
|
let mut address = 0;
|
|
while address < rom.len() {
|
|
eprint!("\rSPI Read {}K", address / 1024);
|
|
let next_address = address + sector_size;
|
|
let count = spi.read_at(address as u32, &mut rom[address..next_address])?;
|
|
if count != sector_size {
|
|
eprintln!("\ncount {} did not match sector size {}", count, sector_size);
|
|
return Err(Error::Verify);
|
|
}
|
|
address = next_address;
|
|
}
|
|
eprintln!("\rSPI Read {}K", address / 1024);
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn flash_inner(ec: &mut Ec<Box<dyn Access>>, firmware: &Firmware, target: SpiTarget, scratch: bool) -> Result<(), Error> {
|
|
let rom_size = 128 * 1024;
|
|
|
|
let mut new_rom = firmware.data.to_vec();
|
|
while new_rom.len() < rom_size {
|
|
new_rom.push(0xFF);
|
|
}
|
|
|
|
let mut spi_bus = ec.spi(target, scratch)?;
|
|
let mut spi = SpiRom::new(
|
|
&mut spi_bus,
|
|
StdTimeout::new(Duration::new(1, 0))
|
|
);
|
|
let sector_size = spi.sector_size();
|
|
|
|
let mut rom = vec![0xFF; rom_size];
|
|
flash_read(&mut spi, &mut rom, sector_size)?;
|
|
|
|
eprintln!("Saving ROM to backup.rom");
|
|
fs::write("backup.rom", &rom).map_err(|_| Error::Verify)?;
|
|
|
|
// Program chip, sector by sector
|
|
//TODO: write signature last
|
|
{
|
|
let mut address = 0;
|
|
while address < rom_size {
|
|
eprint!("\rSPI Write {}K", address / 1024);
|
|
|
|
let next_address = address + sector_size;
|
|
|
|
let mut matches = true;
|
|
let mut erased = true;
|
|
let mut new_erased = true;
|
|
for i in address..next_address {
|
|
if rom[i] != new_rom[i] {
|
|
matches = false;
|
|
}
|
|
if rom[i] != 0xFF {
|
|
erased = false;
|
|
}
|
|
if new_rom[i] != 0xFF {
|
|
new_erased = false;
|
|
}
|
|
}
|
|
|
|
if ! matches {
|
|
if ! erased {
|
|
spi.erase_sector(address as u32)?;
|
|
}
|
|
if ! new_erased {
|
|
let count = spi.write_at(address as u32, &new_rom[address..next_address])?;
|
|
if count != sector_size {
|
|
eprintln!("\nWrite count {} did not match sector size {}", count, sector_size);
|
|
return Err(Error::Verify);
|
|
}
|
|
}
|
|
}
|
|
|
|
address = next_address;
|
|
}
|
|
eprintln!("\rSPI Write {}K", address / 1024);
|
|
|
|
// Verify chip write
|
|
flash_read(&mut spi, &mut rom, sector_size)?;
|
|
for i in 0..rom.len() {
|
|
if rom[i] != new_rom[i] {
|
|
eprintln!("Failed to program: {:X} is {:X} instead of {:X}", i, rom[i], new_rom[i]);
|
|
return Err(Error::Verify);
|
|
}
|
|
}
|
|
}
|
|
|
|
eprintln!("Successfully programmed SPI ROM");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn flash(ec: &mut Ec<Box<dyn Access>>, path: &str, target: SpiTarget) -> Result<(), Error> {
|
|
let scratch = true;
|
|
|
|
//TODO: remove unwraps
|
|
let firmware_data = fs::read(path).unwrap();
|
|
let firmware = Firmware::new(&firmware_data).unwrap();
|
|
println!("file board: {:?}", str::from_utf8(firmware.board));
|
|
println!("file version: {:?}", str::from_utf8(firmware.version));
|
|
|
|
let data_size = ec.access().data_size();
|
|
|
|
{
|
|
let mut data = vec![0; data_size];
|
|
let size = ec.board(&mut data)?;
|
|
|
|
let ec_board = &data[..size];
|
|
println!("ec board: {:?}", str::from_utf8(ec_board));
|
|
|
|
if ec_board != firmware.board {
|
|
panic!("file board does not match ec board");
|
|
}
|
|
}
|
|
|
|
{
|
|
let mut data = vec![0; data_size];
|
|
let size = ec.version(&mut data)?;
|
|
|
|
let ec_version = &data[..size];
|
|
println!("ec version: {:?}", str::from_utf8(ec_version));
|
|
}
|
|
|
|
if scratch {
|
|
// 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(ec, &firmware, target, scratch);
|
|
eprintln!("Result: {:X?}", res);
|
|
|
|
eprintln!("Sync");
|
|
let _ = process::Command::new("sync").status();
|
|
|
|
if scratch {
|
|
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(ec: &mut Ec<Box<dyn Access>>) -> Result<(), Error> {
|
|
let data_size = ec.access().data_size();
|
|
|
|
{
|
|
print!("board: ");
|
|
let mut data = vec![0; data_size];
|
|
let size = ec.board(&mut data)?;
|
|
for &b in data[..size].iter() {
|
|
print!("{}", b as char);
|
|
}
|
|
println!();
|
|
}
|
|
|
|
{
|
|
print!("version: ");
|
|
let mut data = vec![0; data_size];
|
|
let size = ec.version(&mut data)?;
|
|
for &b in data[..size].iter() {
|
|
print!("{}", b as char);
|
|
}
|
|
println!();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn matrix(ec: &mut Ec<Box<dyn Access>>) -> Result<(), Error> {
|
|
let data_size = ec.access().data_size();
|
|
|
|
let mut data = vec![0; data_size];
|
|
ec.matrix_get(&mut data)?;
|
|
let rows = *data.get(0).unwrap_or(&0);
|
|
let cols = *data.get(1).unwrap_or(&0);
|
|
let mut byte = 2;
|
|
let mut bit = 0;
|
|
for _row in 0..rows {
|
|
for _col in 0..cols {
|
|
if (data.get(byte).unwrap_or(&0) & (1 << bit)) != 0 {
|
|
print!("#");
|
|
} else {
|
|
print!("-");
|
|
}
|
|
|
|
bit += 1;
|
|
if bit >= 8 {
|
|
byte += 1;
|
|
bit = 0;
|
|
}
|
|
}
|
|
println!();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn print(ec: &mut Ec<Box<dyn Access>>, message: &[u8]) -> Result<(), Error> {
|
|
ec.print(message)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn fan_get(ec: &mut Ec<Box<dyn Access>>, index: u8) -> Result<(), Error> {
|
|
let duty = ec.fan_get(index)?;
|
|
println!("{}", duty);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn fan_set(ec: &mut Ec<Box<dyn Access>>, index: u8, duty: u8) -> Result<(), Error> {
|
|
ec.fan_set(index, duty)
|
|
}
|
|
|
|
unsafe fn keymap_get(ec: &mut Ec<Box<dyn Access>>, layer: u8, output: u8, input: u8) -> Result<(), Error> {
|
|
let value = ec.keymap_get(layer, output, input)?;
|
|
println!("{:04X}", value);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn keymap_set(ec: &mut Ec<Box<dyn Access>>, layer: u8, output: u8, input: u8, value: u16) -> Result<(), Error> {
|
|
ec.keymap_set(layer, output, input, value)
|
|
}
|
|
|
|
fn validate_from_str<T: FromStr>(s: String) -> Result<(), String>
|
|
where T::Err: Display {
|
|
s.parse::<T>()
|
|
.and(Ok(()))
|
|
.map_err(|err| format!("{}", err))
|
|
}
|
|
|
|
fn parse_color(s: &str) -> Result<(u8, u8, u8), String> {
|
|
let r = u8::from_str_radix(&s[0..2], 16);
|
|
let g = u8::from_str_radix(&s[2..4], 16);
|
|
let b = u8::from_str_radix(&s[4..6], 16);
|
|
match (r, g, b) {
|
|
(Ok(r), Ok(g), Ok(b)) if s.len() == 6 => Ok((r, g, b)),
|
|
_ => Err(format!("Invalid color '{}'", s)),
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
let matches = App::new("system76_ectool")
|
|
.setting(AppSettings::SubcommandRequired)
|
|
.arg(Arg::with_name("access")
|
|
.long("access")
|
|
.possible_values(&["lpc-linux", "lpc-sim", "hid"])
|
|
.default_value("lpc-linux")
|
|
)
|
|
.subcommand(SubCommand::with_name("console"))
|
|
.subcommand(SubCommand::with_name("fan")
|
|
.arg(Arg::with_name("index")
|
|
.validator(validate_from_str::<u8>)
|
|
.required(true)
|
|
)
|
|
.arg(Arg::with_name("duty")
|
|
.validator(validate_from_str::<u8>)
|
|
)
|
|
)
|
|
.subcommand(SubCommand::with_name("flash")
|
|
.arg(Arg::with_name("path")
|
|
.required(true)
|
|
)
|
|
)
|
|
.subcommand(SubCommand::with_name("flash_backup")
|
|
.arg(Arg::with_name("path")
|
|
.required(true)
|
|
)
|
|
)
|
|
.subcommand(SubCommand::with_name("info"))
|
|
.subcommand(SubCommand::with_name("keymap")
|
|
.arg(Arg::with_name("layer")
|
|
.validator(validate_from_str::<u8>)
|
|
.required(true)
|
|
)
|
|
.arg(Arg::with_name("output")
|
|
.validator(validate_from_str::<u8>)
|
|
.required(true)
|
|
)
|
|
.arg(Arg::with_name("input")
|
|
.validator(validate_from_str::<u8>)
|
|
.required(true)
|
|
)
|
|
.arg(Arg::with_name("value"))
|
|
)
|
|
.subcommand(SubCommand::with_name("led_color")
|
|
.arg(Arg::with_name("index")
|
|
.validator(validate_from_str::<u8>)
|
|
.required(true)
|
|
)
|
|
.arg(Arg::with_name("value")
|
|
.validator(|x| parse_color(&x).and(Ok(())))
|
|
)
|
|
)
|
|
.subcommand(SubCommand::with_name("led_value")
|
|
.arg(Arg::with_name("index")
|
|
.validator(validate_from_str::<u8>)
|
|
.required(true)
|
|
)
|
|
.arg(Arg::with_name("value")
|
|
.validator(validate_from_str::<u8>)
|
|
)
|
|
)
|
|
.subcommand(SubCommand::with_name("led_mode")
|
|
.arg(Arg::with_name("layer")
|
|
.validator(validate_from_str::<u8>)
|
|
.required(true)
|
|
)
|
|
.arg(Arg::with_name("mode")
|
|
.validator(validate_from_str::<u8>)
|
|
.requires("speed")
|
|
)
|
|
.arg(Arg::with_name("speed")
|
|
.validator(validate_from_str::<u8>)
|
|
)
|
|
)
|
|
.subcommand(SubCommand::with_name("led_save"))
|
|
.subcommand(SubCommand::with_name("matrix"))
|
|
.subcommand(SubCommand::with_name("print")
|
|
.arg(Arg::with_name("message")
|
|
.required(true)
|
|
.multiple(true)
|
|
)
|
|
)
|
|
.get_matches();
|
|
|
|
let get_ec = || -> Result<_, Error> {
|
|
unsafe {
|
|
match matches.value_of("access").unwrap() {
|
|
"lpc-linux" => {
|
|
let access = AccessLpcLinux::new(Duration::new(1, 0))?;
|
|
Ok(Ec::new(access)?.into_dyn())
|
|
},
|
|
"lpc-sim" => {
|
|
let access = AccessLpcSim::new(Duration::new(1, 0))?;
|
|
Ok(Ec::new(access)?.into_dyn())
|
|
},
|
|
"hid" => {
|
|
let api = HidApi::new()?;
|
|
for info in api.device_list() {
|
|
match (info.vendor_id(), info.product_id(), info.interface_number()) {
|
|
// System76 launch_1
|
|
(0x3384, 0x0001, 1) => {
|
|
let device = info.open_device(&api)?;
|
|
let access = AccessHid::new(device, 10, 100)?;
|
|
return Ok(Ec::new(access)?.into_dyn());
|
|
}
|
|
_ => {},
|
|
}
|
|
}
|
|
Err(hidapi::HidError::OpenHidDeviceError.into())
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
};
|
|
let mut ec = match get_ec() {
|
|
Ok(ec) => ec,
|
|
Err(err) => {
|
|
eprintln!("failed to connect to EC: {:X?}", err);
|
|
process::exit(1);
|
|
}
|
|
};
|
|
|
|
match matches.subcommand() {
|
|
("console", Some(_sub_m)) => match unsafe { console(&mut ec) } {
|
|
Ok(()) => (),
|
|
Err(err) => {
|
|
eprintln!("failed to read console: {:X?}", err);
|
|
process::exit(1);
|
|
},
|
|
},
|
|
("fan", Some(sub_m)) => {
|
|
let index = sub_m.value_of("index").unwrap().parse::<u8>().unwrap();
|
|
let duty_opt = sub_m.value_of("duty").map(|x| x.parse::<u8>().unwrap());
|
|
match duty_opt {
|
|
Some(duty) => match unsafe { fan_set(&mut ec, index, duty) } {
|
|
Ok(()) => (),
|
|
Err(err) => {
|
|
eprintln!("failed to set fan {} to {}: {:X?}", index, duty, err);
|
|
process::exit(1);
|
|
},
|
|
},
|
|
None => match unsafe { fan_get(&mut ec, index) } {
|
|
Ok(()) => (),
|
|
Err(err) => {
|
|
eprintln!("failed to get fan {}: {:X?}", index, err);
|
|
process::exit(1);
|
|
},
|
|
},
|
|
}
|
|
},
|
|
("flash", Some(sub_m)) => {
|
|
let path = sub_m.value_of("path").unwrap();
|
|
match unsafe { flash(&mut ec, &path, SpiTarget::Main) } {
|
|
Ok(()) => (),
|
|
Err(err) => {
|
|
eprintln!("failed to flash '{}': {:X?}", path, err);
|
|
process::exit(1);
|
|
},
|
|
}
|
|
},
|
|
("flash_backup", Some(sub_m)) => {
|
|
let path = sub_m.value_of("path").unwrap();
|
|
match unsafe { flash(&mut ec, &path, SpiTarget::Backup) } {
|
|
Ok(()) => (),
|
|
Err(err) => {
|
|
eprintln!("failed to flash '{}': {:X?}", path, err);
|
|
process::exit(1);
|
|
},
|
|
}
|
|
},
|
|
("info", Some(_sub_m)) => match unsafe { info(&mut ec) } {
|
|
Ok(()) => (),
|
|
Err(err) => {
|
|
eprintln!("failed to read info: {:X?}", err);
|
|
process::exit(1);
|
|
},
|
|
},
|
|
("keymap", Some(sub_m)) => {
|
|
let layer = sub_m.value_of("layer").unwrap().parse::<u8>().unwrap();
|
|
let output = sub_m.value_of("output").unwrap().parse::<u8>().unwrap();
|
|
let input = sub_m.value_of("input").unwrap().parse::<u8>().unwrap();
|
|
match sub_m.value_of("value") {
|
|
Some(value_str) => match u16::from_str_radix(value_str.trim_start_matches("0x"), 16) {
|
|
Ok(value) => match unsafe { keymap_set(&mut ec, layer, output, input, value) } {
|
|
Ok(()) => (),
|
|
Err(err) => {
|
|
eprintln!("failed to set keymap {}, {}, {} to {}: {:X?}", layer, output, input, value, err);
|
|
process::exit(1);
|
|
},
|
|
},
|
|
Err(err) => {
|
|
eprintln!("failed to parse value: '{}': {}", value_str, err);
|
|
process::exit(1);
|
|
}
|
|
},
|
|
None => match unsafe { keymap_get(&mut ec, layer, output, input) } {
|
|
Ok(()) => (),
|
|
Err(err) => {
|
|
eprintln!("failed to get keymap {}, {}, {}: {:X?}", layer, output, input, err);
|
|
process::exit(1);
|
|
},
|
|
},
|
|
}
|
|
},
|
|
("led_color", Some(sub_m)) => {
|
|
let index = sub_m.value_of("index").unwrap().parse::<u8>().unwrap();
|
|
let value = sub_m.value_of("value");
|
|
if let Some(value) = value {
|
|
let (r, g, b) = parse_color(value).unwrap();
|
|
match unsafe { ec.led_set_color(index, r, g, b) } {
|
|
Ok(()) => (),
|
|
Err(err) => {
|
|
eprintln!("failed to set color {}: {:X?}", value, err);
|
|
process::exit(1);
|
|
},
|
|
}
|
|
} else {
|
|
match unsafe { ec.led_get_color(index) } {
|
|
Ok((r, g, b)) => println!("{:02x}{:02x}{:02x}", r, g, b),
|
|
Err(err) => {
|
|
eprintln!("failed to get color: {:X?}", err);
|
|
process::exit(1);
|
|
},
|
|
}
|
|
}
|
|
},
|
|
("led_value", Some(sub_m)) => {
|
|
let index = sub_m.value_of("index").unwrap().parse::<u8>().unwrap();
|
|
let value = sub_m.value_of("value").map(|x| x.parse::<u8>().unwrap());
|
|
if let Some(value) = value {
|
|
match unsafe { ec.led_set_value(index, value) } {
|
|
Ok(()) => (),
|
|
Err(err) => {
|
|
eprintln!("failed to set value {}: {:X?}", value, err);
|
|
process::exit(1);
|
|
},
|
|
}
|
|
} else {
|
|
match unsafe { ec.led_get_value(index) } {
|
|
Ok((value, max)) => {
|
|
println!("value: {}", value);
|
|
println!("max: {}", max);
|
|
},
|
|
Err(err) => {
|
|
eprintln!("failed to get value: {:X?}", err);
|
|
process::exit(1);
|
|
},
|
|
}
|
|
}
|
|
},
|
|
("led_mode", Some(sub_m)) => {
|
|
let layer = sub_m.value_of("layer").unwrap().parse::<u8>().unwrap();
|
|
let mode = sub_m.value_of("mode").map(|x| x.parse::<u8>().unwrap());
|
|
let speed = sub_m.value_of("speed").map(|x| x.parse::<u8>().unwrap());
|
|
if let (Some(mode), Some(speed)) = (mode, speed) {
|
|
match unsafe { ec.led_set_mode(layer, mode, speed) } {
|
|
Ok(()) => (),
|
|
Err(err) => {
|
|
eprintln!("failed to set layer {} mode {} at speed {}: {:X?}", layer, mode, speed, err);
|
|
process::exit(1);
|
|
},
|
|
}
|
|
} else {
|
|
match unsafe { ec.led_get_mode(layer) } {
|
|
Ok((mode, speed)) => {
|
|
println!("mode: {}", mode);
|
|
println!("speed: {}", speed);
|
|
},
|
|
Err(err) => {
|
|
eprintln!("failed to get mode for layer {}: {:X?}", layer, err);
|
|
process::exit(1);
|
|
},
|
|
}
|
|
}
|
|
},
|
|
("led_save", Some(_sub_m)) => match unsafe { ec.led_save() } {
|
|
Ok(()) => (),
|
|
Err(err) => {
|
|
eprintln!("failed to save LED settings: {:X?}", err);
|
|
process::exit(1);
|
|
},
|
|
},
|
|
("matrix", Some(_sub_m)) => match unsafe { matrix(&mut ec) } {
|
|
Ok(()) => (),
|
|
Err(err) => {
|
|
eprintln!("failed to read matrix: {:X?}", err);
|
|
process::exit(1);
|
|
},
|
|
},
|
|
("print", Some(sub_m)) => for arg in sub_m.values_of("message").unwrap() {
|
|
let mut arg = arg.to_owned();
|
|
arg.push('\n');
|
|
match unsafe { print(&mut ec, &arg.as_bytes()) } {
|
|
Ok(()) => (),
|
|
Err(err) => {
|
|
eprintln!("failed to print '{}': {:X?}", arg, err);
|
|
process::exit(1);
|
|
},
|
|
}
|
|
},
|
|
_ => unreachable!()
|
|
}
|
|
}
|