Skip to content

Commit

Permalink
Merge pull request #4 from ChocolateLoverRaj/more-commands
Browse files Browse the repository at this point in the history
More commands
  • Loading branch information
lleyton authored Oct 28, 2024
2 parents 434e422 + 10417b1 commit 94bfb87
Show file tree
Hide file tree
Showing 45 changed files with 4,110 additions and 323 deletions.
2,077 changes: 1,896 additions & 181 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions crosec/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,17 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
async-std = "1.12.0"
bytemuck = { version = "1.16.0", features = ["derive"] }
clap = { version = "4.5.6", optional = true }
nix = { version = "0.27.1", features = ["ioctl"] }
num = "0.4.3"
num-derive = "0.4.2"
num-traits = "0.2.18"
strum = "0.26.2"
strum_macros = "0.26.4"
thiserror = "1.0.57"
uom = "0.36.0"

[features]
clap = ["dep:clap"]
72 changes: 72 additions & 0 deletions crosec/src/battery.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use crate::commands::get_cmd_versions::{ec_cmd_get_cmd_versions, V1};
use crate::commands::CrosEcCmd;
use crate::read_mem_any::read_mem_any;
use crate::read_mem_string::read_mem_string;
use crate::{
EcCmdResult, EC_MEM_MAP_BATTERY_CAPACITY, EC_MEM_MAP_BATTERY_CYCLE_COUNT,
EC_MEM_MAP_BATTERY_DESIGN_CAPACITY, EC_MEM_MAP_BATTERY_DESIGN_VOLTAGE,
EC_MEM_MAP_BATTERY_FLAGS, EC_MEM_MAP_BATTERY_LAST_FULL_CHARGE_CAPACITY,
EC_MEM_MAP_BATTERY_MANUFACTURER, EC_MEM_MAP_BATTERY_MODEL, EC_MEM_MAP_BATTERY_RATE,
EC_MEM_MAP_BATTERY_SERIAL, EC_MEM_MAP_BATTERY_TYPE, EC_MEM_MAP_BATTERY_VERSION,
EC_MEM_MAP_BATTERY_VOLTAGE,
};
use std::fs::File;

#[derive(Debug, Clone)]
pub struct BatteryInfo {
pub oem_name: String,
pub model_number: String,
pub chemistry: String,
pub serial_number: String,
pub design_capacity: i32,
pub last_full_charge: i32,
pub design_output_voltage: i32,
pub cycle_count: i32,
pub present_voltage: i32,
pub present_current: i32,
pub remaining_capacity: i32,
pub flags: u8,
}

pub fn battery(file: &mut File) -> EcCmdResult<BatteryInfo> {
if ec_cmd_get_cmd_versions(file, CrosEcCmd::BatteryGetStatic)? & V1 != 0 {
panic!(
"Battery info needs to be gotten with the {:?} command",
CrosEcCmd::BatteryGetStatic
);
} else {
let battery_version = read_mem_any::<i8>(file, EC_MEM_MAP_BATTERY_VERSION).unwrap();
if battery_version < 1 {
panic!("Battery version {battery_version} is not supported");
}
let flags = read_mem_any::<u8>(file, EC_MEM_MAP_BATTERY_FLAGS).unwrap();
let oem_name = read_mem_string(file, EC_MEM_MAP_BATTERY_MANUFACTURER).unwrap();
let model_number = read_mem_string(file, EC_MEM_MAP_BATTERY_MODEL).unwrap();
let chemistry = read_mem_string(file, EC_MEM_MAP_BATTERY_TYPE).unwrap();
let serial_number = read_mem_string(file, EC_MEM_MAP_BATTERY_SERIAL).unwrap();
let design_capacity =
read_mem_any::<i32>(file, EC_MEM_MAP_BATTERY_DESIGN_CAPACITY).unwrap();
let last_full_charge =
read_mem_any::<i32>(file, EC_MEM_MAP_BATTERY_LAST_FULL_CHARGE_CAPACITY).unwrap();
let design_output_voltage =
read_mem_any::<i32>(file, EC_MEM_MAP_BATTERY_DESIGN_VOLTAGE).unwrap();
let cycle_count = read_mem_any::<i32>(file, EC_MEM_MAP_BATTERY_CYCLE_COUNT).unwrap();
let present_voltage = read_mem_any::<i32>(file, EC_MEM_MAP_BATTERY_VOLTAGE).unwrap();
let present_current = read_mem_any::<i32>(file, EC_MEM_MAP_BATTERY_RATE).unwrap();
let remaining_capacity = read_mem_any::<i32>(file, EC_MEM_MAP_BATTERY_CAPACITY).unwrap();
Ok(BatteryInfo {
flags,
oem_name,
model_number,
chemistry,
serial_number,
design_capacity,
last_full_charge,
design_output_voltage,
cycle_count,
present_voltage,
present_current,
remaining_capacity,
})
}
}
10 changes: 10 additions & 0 deletions crosec/src/commands/board_version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use std::fs::File;
use std::os::fd::AsRawFd;

use crate::commands::CrosEcCmd;
use crate::ec_command::ec_command_bytemuck;
use crate::EcCmdResult;

pub fn ec_cmd_board_version(file: &mut File) -> EcCmdResult<u32> {
ec_command_bytemuck(CrosEcCmd::GetBoardVersion, 0, &(), file.as_raw_fd())
}
76 changes: 76 additions & 0 deletions crosec/src/commands/charge_control.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use crate::commands::get_cmd_versions::{ec_cmd_get_cmd_versions, V2};
use crate::commands::CrosEcCmd;
use crate::ec_command::ec_command_bytemuck;
use crate::EcCmdResult;
use bytemuck::{Pod, Zeroable};
use std::os::fd::AsRawFd;

#[repr(C)]
#[derive(Pod, Zeroable, Copy, Clone, Default, Debug)]
pub struct Sustainer {
pub min_percent: i8,
pub max_percent: i8,
}

#[derive(Debug)]
pub enum ChargeControl {
Normal(Option<Sustainer>),
Idle,
Discharge,
}

pub fn supports_get_and_sustainer<File: AsRawFd>(file: &mut File) -> EcCmdResult<bool> {
let versions = ec_cmd_get_cmd_versions(file, CrosEcCmd::ChargeControl)?;
Ok(versions & V2 != 0)
}

#[repr(C)]
#[derive(Pod, Zeroable, Copy, Clone)]
pub struct EcParamsChargeControl {
mode: u32,
command: u8,
reserved: u8,
sustain: Sustainer,
}

const CHARGE_CONTROL_MODE_SET: u8 = 0;
// const CHARGE_CONTROL_MODE_GET: u8 = 1;

const CHARGE_CONTROL_COMMAND_NORMAL: u32 = 0;
const CHARGE_CONTROL_COMMAND_IDLE: u32 = 1;
const CHARGE_CONTROL_COMMAND_DISCHARGE: u32 = 2;

pub fn get_charge_control<File: AsRawFd>(_file: &mut File) -> EcCmdResult<ChargeControl> {
panic!("Not implemented yet");
}

pub fn set_charge_control<File: AsRawFd>(
file: &mut File,
charge_control: ChargeControl,
) -> EcCmdResult<()> {
ec_command_bytemuck(
CrosEcCmd::ChargeControl,
{
let version = ec_cmd_get_cmd_versions(file, CrosEcCmd::ChargeControl)?;
Ok(if version & V2 != 0 { 2 } else { 1 })
}?,
&EcParamsChargeControl {
command: CHARGE_CONTROL_MODE_SET,
mode: match charge_control {
ChargeControl::Normal(_) => CHARGE_CONTROL_COMMAND_NORMAL,
ChargeControl::Idle => CHARGE_CONTROL_COMMAND_IDLE,
ChargeControl::Discharge => CHARGE_CONTROL_COMMAND_DISCHARGE,
},
reserved: Default::default(),
sustain: match charge_control {
ChargeControl::Normal(sustain) => sustain.unwrap_or(Sustainer {
min_percent: -1,
max_percent: -1,
}),
_ => Default::default(),
},
},
file.as_raw_fd(),
)?;
Ok(())
}
29 changes: 29 additions & 0 deletions crosec/src/commands/charge_current_limit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use std::os::fd::AsRawFd;

use bytemuck::{Pod, Zeroable};
use uom::si::{electric_current::milliampere, f32::ElectricCurrent};

use crate::{ec_command::ec_command_bytemuck, EcCmdResult};

use super::CrosEcCmd;

#[repr(C)]
#[derive(Pod, Zeroable, Copy, Clone)]
pub struct EcParamsChargeCurrentLimit {
limit: u32, // in mA
}

/// Limit the charging current. The EC command sends the charging current limit to the nearest mA.
pub fn set_charge_current_limit<File: AsRawFd>(
file: &mut File,
limit: ElectricCurrent,
) -> EcCmdResult<()> {
ec_command_bytemuck(
CrosEcCmd::ChargeCurrentLimit,
0,
&EcParamsChargeCurrentLimit {
limit: limit.get::<milliampere>() as u32,
},
file.as_raw_fd(),
)
}
115 changes: 115 additions & 0 deletions crosec/src/commands/fp_download.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use std::{os::fd::AsRawFd, thread::sleep, time::Duration};

use bytemuck::{bytes_of, Pod, Zeroable};

use crate::ec_command::ec_command_with_dynamic_output_size;

use super::{fp_info::EcResponseFpInfo, get_protocol_info::EcResponseGetProtocolInfo, CrosEcCmd};

#[repr(C)]
#[derive(Pod, Zeroable, Clone, Copy)]
struct EcParamsFpFrame {
/// The offset contains the template index or FP_FRAME_INDEX_RAW_IMAGE
/// in the high nibble, and the real offset within the frame in
/// FP_FRAME_OFFSET_MASK.
offset: u32,
size: u32,
}

const FP_FRAME_INDEX_RAW_IMAGE: u32 = 0;

/// This can be changed. `3` is what the ChromiumOS ectool uses.
const MAX_ATTEMPTS: usize = 3;

pub enum DownloadType {
/// (aka `FP_FRAME_INDEX_SIMPLE_IMAGE`) for the a single grayscale image
SimpleImage,
/// (aka `FP_FRAME_INDEX_RAW_IMAGE`) for the full vendor raw finger image.
RawImage,
Template(usize),
}

/// Downloads a frame buffer from the FPMCU.
/// The downloaded data might be either the finger image or a finger template.
pub fn fp_download<File: AsRawFd>(
file: &mut File,
fp_info: &EcResponseFpInfo,
protocol_info: &EcResponseGetProtocolInfo,
download_type: &DownloadType,
) -> Vec<u8> {
let (size, index) = match download_type {
DownloadType::SimpleImage => (fp_info.get_simple_image_size(), FP_FRAME_INDEX_RAW_IMAGE),
DownloadType::RawImage => (fp_info.frame_size as usize, FP_FRAME_INDEX_RAW_IMAGE),
DownloadType::Template(template_index) => {
(fp_info.template_size as usize, *template_index as u32 + 1)
}
};
// The template may be (and probably is) bigger than the max output size, so we need to download it in chunks
let number_of_chunks = size.div_ceil(protocol_info.max_ec_output_size());
let mut chunks = Vec::<Vec<u8>>::with_capacity(number_of_chunks);
for chunk_index in 0..number_of_chunks {
let bytes_read = chunk_index * protocol_info.max_ec_output_size();
let remaining_bytes = size - bytes_read;
let current_chunk_size = remaining_bytes.min(protocol_info.max_ec_output_size());
let mut attempt = 0;
loop {
let result = ec_command_with_dynamic_output_size(
CrosEcCmd::FpFrame,
0,
bytes_of(&EcParamsFpFrame {
offset: (index << 28) + (bytes_read as u32),
size: current_chunk_size as u32,
}),
current_chunk_size,
file.as_raw_fd(),
);
if let Ok(chunk) = result {
chunks.push(chunk);
break;
} else {
attempt += 1;
if attempt == MAX_ATTEMPTS {
panic!("Could not successfully get the fp frame in {MAX_ATTEMPTS} attempts");
}
// Using micros and not millis to be more like original `usleep(100000)` from ChromiumOS's ectool
sleep(Duration::from_micros(100_000));
}
}
}
chunks.concat()
}

/// A safe wrapper around the actual template so you don't try to upload arbitrary data
pub struct FpTemplate {
vec: Vec<u8>,
}

impl From<FpTemplate> for Vec<u8> {
fn from(value: FpTemplate) -> Self {
value.vec
}
}

impl FpTemplate {
pub fn buffer(&self) -> &Vec<u8> {
&self.vec
}

/// Make sure your buffer is actually a compatible fp template
/// # Safety
/// Make sure you're uploading a template from the same FPMCU with the same version and the same seed set.
pub unsafe fn from_vec_unchecked(vec: Vec<u8>) -> Self {
FpTemplate { vec }
}
}

pub fn fp_download_template<File: AsRawFd>(
file: &mut File,
fp_info: &EcResponseFpInfo,
protocol_info: &EcResponseGetProtocolInfo,
index: usize,
) -> FpTemplate {
FpTemplate {
vec: fp_download(file, fp_info, protocol_info, &DownloadType::Template(index)),
}
}
23 changes: 23 additions & 0 deletions crosec/src/commands/fp_get_encryption_status.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use crate::commands::CrosEcCmd;
use crate::ec_command::ec_command_bytemuck;
use crate::EcCmdResult;
use bytemuck::{Pod, Zeroable};
use std::os::fd::AsRawFd;

#[repr(u32)]
pub enum FpEncryptionStatus {
SeedSet = 0b1,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct EcResponseFpGetEncryptionStatus {
pub valid_flags: u32,
pub status: u32,
}

pub fn fp_get_encryption_status<File: AsRawFd>(
file: &mut File,
) -> EcCmdResult<EcResponseFpGetEncryptionStatus> {
ec_command_bytemuck(CrosEcCmd::FpGetEncryptionStatus, 0, &(), file.as_raw_fd())
}
Loading

0 comments on commit 94bfb87

Please sign in to comment.