blob: c3e18906c14478167a04f897445257491b0413e0 [file] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Smart Battery SMBus config definition and methods to read the config through i2c bridge.
extern crate anyhow;
extern crate i2cdev;
use std::collections::HashMap;
use std::fmt;
use std::fmt::Formatter;
use std::fs::File;
use std::io::{Read, Write};
use anyhow::{bail, Result};
use log::debug;
use memoffset::offset_of;
use serde::{Deserialize, Serialize};
use crate::eeprom::SBRegisterValue::{BlockRegister, Reserved, WordRegister};
use i2cdev::core::*;
use i2cdev::linux::LinuxI2CDevice;
use zerocopy::AsBytes;
use zerocopy::FromBytes;
use sb_config::eeprom_layout;
use sb_config::eeprom_layout::*;
/// Smart Battery Register value types
#[derive(Default, Debug, Serialize, Deserialize)]
pub enum SBRegisterValue {
/// SB spec block data - can be 1 to 32 bytes.
BlockRegister(Vec<u8>),
/// SB spec word data.
WordRegister(u16),
#[default]
/// Unspecified in the SB spec.
Reserved,
}
/// SB Register
#[derive(Default, Debug, Serialize, Deserialize)]
pub struct SBRegister {
/// Name of the register - Just for debugging not sent to FW.
name: String,
/// Value read from the register.
pub(crate) value: SBRegisterValue,
}
/// All the registers in the spec.
#[derive(Default, Debug, Serialize, Deserialize)]
pub struct SBRegisterSet {
pub(crate) registers: HashMap<u8, SBRegister>,
}
impl fmt::Display for SBRegisterSet {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
writeln!(f)?;
let mut addresses: Vec<u8> = self.registers.keys().cloned().collect();
addresses.sort();
for addr in addresses {
let reg = self.registers.get(&addr).unwrap();
write!(f, " {addr:#5x} {:30} ", reg.name)?;
match &reg.value {
BlockRegister(v) => {
let s = std::str::from_utf8(v).unwrap_or("");
let s = s.replace(|c: char| !c.is_alphanumeric(), "");
write!(f, "{s:<32} {v:x?}")?;
}
WordRegister(v) => {
write!(f, "{:#x}", *v)?;
}
Reserved => {}
}
writeln!(f)?;
}
Ok(())
}
}
fn eeprom_layout_get_block_register_offset(address: u8) -> usize {
let m = HashMap::from([
(SB_MANUFACTURER_NAME as u8, 0),
(SB_DEVICE_NAME as u8, 1),
(SB_DEVICE_CHEMISTRY as u8, 2),
(SB_MANUFACTURER_DATA as u8, 3),
(SB_ALT_MANUFACTURER_ACCESS as u8, 4),
(SB_MANUFACTURE_INFO as u8, 5),
]);
let result: u8 = *m.get(&address).unwrap();
result as usize
}
fn eeprom_layout_get_word_register_offset(address: u8) -> usize {
address as _
}
impl SBRegisterSet {
pub fn new() -> SBRegisterSet {
SBRegisterSet {
registers: HashMap::from([
(SB_MANUFACTURER_ACCESS as u8, sb_u16("ManufacturerAccess")),
(SB_REMAINING_CAPACITY_ALARM as u8, sb_u16("RemCapAlarm")),
(SB_REMAINING_TIME_ALARM as u8, sb_u16("RemTimeAlarm")),
(SB_BATTERY_MODE as u8, sb_u16("BatteryMode")),
(SB_AT_RATE as u8, sb_u16("AtRate")),
(SB_AT_RATE_TIME_TO_FULL as u8, sb_u16("AtRateTimeToFull")),
(SB_AT_RATE_TIME_TO_EMPTY as u8, sb_u16("AtRateTimeToEmpty")),
(SB_AT_RATE_OK as u8, sb_u16("AtRateOK")),
(SB_TEMPERATURE as u8, sb_u16("Temperature")),
(SB_VOLTAGE as u8, sb_u16("Voltage")),
(SB_CURRENT as u8, sb_u16("Current")),
(SB_AVERAGE_CURRENT as u8, sb_u16("AverageCurrent")),
(SB_MAX_ERROR as u8, sb_u16("MaxError")),
(SB_RELATIVE_STATE_OF_CHARGE as u8, sb_u16("RelChargeState")),
(SB_ABSOLUTE_STATE_OF_CHARGE as u8, sb_u16("AbsChargeState")),
(SB_REMAINING_CAPACITY as u8, sb_u16("RemainingCapacity")),
(SB_FULL_CHARGE_CAPACITY as u8, sb_u16("FullChargeCapacity")),
(SB_RUN_TIME_TO_EMPTY as u8, sb_u16("RunTimeToEmpty")),
(SB_AVERAGE_TIME_TO_EMPTY as u8, sb_u16("AverageTimeToEmpty")),
(SB_AVERAGE_TIME_TO_FULL as u8, sb_u16("AverageTimeToFull")),
(SB_CHARGING_CURRENT as u8, sb_u16("ChargingCurrent")),
(SB_CHARGING_VOLTAGE as u8, sb_u16("ChargingVoltage")),
(SB_BATTERY_STATUS as u8, sb_u16("BatteryStatus")),
(SB_CYCLE_COUNT as u8, sb_u16("CycleCount")),
(SB_DESIGN_CAPACITY as u8, sb_u16("DesignCapacity")),
(SB_DESIGN_VOLTAGE as u8, sb_u16("DesignVoltage")),
(SB_SPECIFICATION_INFO as u8, sb_u16("SpecificationInfo")),
(SB_MANUFACTURE_DATE as u8, sb_u16("ManufactureDate")),
(SB_SERIAL_NUMBER as u8, sb_u16("SerialNumber")),
(SB_MANUFACTURER_NAME as u8, sb_string("ManufacturerName")),
(SB_DEVICE_NAME as u8, sb_string("DeviceName")),
(SB_DEVICE_CHEMISTRY as u8, sb_string("DeviceChemistry")),
(SB_MANUFACTURER_DATA as u8, sb_string("ManufacturerData")),
(SB_OPTIONAL_MFG_FUNC1 as u8, sb_u16("OptionalMfgFunction4")),
(SB_OPTIONAL_MFG_FUNC2 as u8, sb_u16("OptionalMfgFunction3")),
(SB_OPTIONAL_MFG_FUNC3 as u8, sb_u16("OptionalMfgFunction2")),
(SB_OPTIONAL_MFG_FUNC4 as u8, sb_u16("OptionalMfgFunction1")),
(SB_PACK_STATUS as u8, sb_u16("PackStatus")),
(SB_ALT_MANUFACTURER_ACCESS as u8, sb_string("AltMfrAccess")),
(SB_MANUFACTURE_INFO as u8, sb_string("ManufacturerInfo")),
]),
}
}
/// Create a new SBRegisterSet from eeprom_data
pub fn from_eeprom_data(eeprom_data: eeprom_layout::eeprom_data) -> SBRegisterSet {
let mut sb_config: SBRegisterSet = SBRegisterSet::new();
for (i, sb_reg) in sb_config.registers.iter_mut() {
let i = *i;
match &mut sb_reg.value {
BlockRegister(v) => {
let reg_offset = eeprom_layout_get_block_register_offset(i);
for j in 0..eeprom_data.block_registers[reg_offset].length as usize {
v.push(eeprom_data.block_registers[reg_offset].data[j]);
}
}
WordRegister(v) => {
let reg_offset = eeprom_layout_get_word_register_offset(i);
if eeprom_data.word_registers[reg_offset].present != 0 {
*v = eeprom_data.word_registers[reg_offset].data;
}
}
Reserved => {}
}
}
sb_config
}
}
/// Calculate CRC32 for given data bytes.
fn calculate_crc(data: &[u8]) -> u32 {
const CRC: crc::Crc<u32> = crc::Crc::<u32>::new(&crc::CRC_32_JAMCRC);
CRC.checksum(data)
}
/// Helper to create word register.
fn sb_u16(name: &str) -> SBRegister {
SBRegister {
name: name.to_string(),
value: WordRegister(0),
}
}
/// Helper to create empty block register.
fn sb_string(name: &str) -> SBRegister {
SBRegister {
name: name.to_string(),
value: BlockRegister(vec![]),
}
}
/// Read Smart Battery Registers over I2c and create SBRegisterSet
pub fn read_sb_registers(usb_i2c_bridge_address: u16) -> Result<SBRegisterSet> {
let smbus_address: u16 = 0xb;
let mut result: SBRegisterSet = SBRegisterSet::new();
let path = format!("/dev/i2c-{usb_i2c_bridge_address}");
let mut dev = match LinuxI2CDevice::new(path.clone(), smbus_address) {
Ok(v) => v,
Err(e) => {
let msg = format!("Failed to open USB i2c bridge {path}: {e}");
bail!(msg);
}
};
for (address, reg) in result.registers.iter_mut() {
let address = *address;
match reg.value {
BlockRegister(_) => {
debug!("Reading block data at {address:#x}");
let value = dev.smbus_read_block_data(address)?;
debug!("Read byte {}", value.len());
reg.value = BlockRegister(value);
}
WordRegister(_) => {
debug!("Reading word at {address:#x}");
let value = dev.smbus_read_word_data(address)?;
reg.value = WordRegister(value);
}
Reserved => {}
}
}
Ok(result)
}
/// Save the SBRegisterSet into a file.
pub fn write_eeprom_layout_to_file(
file_name: &String,
polarity: u8,
sb_config: &SBRegisterSet,
) -> Result<()> {
let mut f = File::create(file_name)?;
let mut r: eeprom_layout::eeprom_data = eeprom_data {
version: 1,
polarity,
manufactured_year: 24,
manufactured_week: 1,
serial_number: 1234,
crc: 0,
block_registers: Default::default(),
word_registers: [Default::default(); eeprom_layout::SB_TOTAL_WORD_REGISTERS as usize],
reserved_1: std::array::from_fn(|_| Default::default()),
reserved_2: std::array::from_fn(|_| Default::default()),
reserved_3: std::array::from_fn(|_| Default::default()),
};
for (i, sb_reg) in sb_config.registers.iter() {
match &sb_reg.value {
BlockRegister(v) => {
let block_reg_index = eeprom_layout_get_block_register_offset(*i);
for (j, value) in v.iter().enumerate() {
r.block_registers[block_reg_index].data[j] = *value;
}
r.block_registers[block_reg_index].length = v.len() as u8;
}
WordRegister(v) => {
r.word_registers[*i as usize] = eeprom_layout::sb_word_register {
present: 1,
data: *v,
};
}
Reserved => {}
}
}
let crc_start_offset = offset_of!(eeprom_layout::eeprom_data, reserved_1);
r.crc = calculate_crc(&r.as_bytes()[crc_start_offset..]);
f.write_all(r.as_bytes())?;
Ok(())
}
/// Read eeprom layout stroed in the given file and construct SBRegisterSet from it.
pub fn read_sb_registers_set_from_file(
file_name: &String,
) -> Result<(eeprom_layout::eeprom_data, SBRegisterSet)> {
let mut f = File::open(file_name)?;
let mut buf: [u8; 1024] = [0; 1024];
f.read_exact(&mut buf)?;
let data: eeprom_layout::eeprom_data = FromBytes::read_from(buf.as_slice()).unwrap();
let sb_config = SBRegisterSet::from_eeprom_data(data);
Ok((data, sb_config))
}