33// include/battery_smart.h
44use alloc:: vec:: Vec ;
55
6+ use sha1:: { Sha1 , Digest } ;
7+ use std:: thread;
8+ use std:: time:: Duration ;
9+
610use crate :: chromium_ec:: i2c_passthrough:: * ;
7- use crate :: chromium_ec:: { CrosEc , EcResult } ;
11+ use crate :: chromium_ec:: { CrosEc , EcResult , EcError } ;
812
913#[ repr( u16 ) ]
1014enum SmartBatReg {
@@ -21,6 +25,7 @@ enum SmartBatReg {
2125 CellVoltage2 = 0x3D ,
2226 CellVoltage3 = 0x3E ,
2327 CellVoltage4 = 0x3F ,
28+ Authenticate = 0x2F ,
2429}
2530
2631#[ repr( u16 ) ]
@@ -33,6 +38,8 @@ enum ManufReg {
3338 SafetyAlert = 0x50 ,
3439 SafetyStatus = 0x51 ,
3540 PFAlert = 0x52 ,
41+ PFStatus = 0x53 ,
42+ OperationStatus = 0x54 ,
3643 LifeTimeDataBlock1 = 0x60 ,
3744 LifeTimeDataBlock2 = 0x61 ,
3845 LifeTimeDataBlock3 = 0x62 ,
@@ -46,6 +53,27 @@ pub struct SmartBattery {
4653 i2c_addr : u16 ,
4754}
4855
56+ /// Calculates the HMAC using TI's specific nested SHA-1 method.
57+ /// Formula: SHA1( Key || SHA1( Key || Challenge ) )
58+ fn calculate_ti_hmac ( key : & [ u8 ; 16 ] , challenge : & [ u8 ; 20 ] ) -> [ u8 ; 20 ] {
59+ // 1. Inner Hash: SHA1( Key + Challenge )
60+ let mut inner_hasher = Sha1 :: new ( ) ;
61+ inner_hasher. update ( key) ;
62+ inner_hasher. update ( challenge) ;
63+ let inner_digest = inner_hasher. finalize ( ) ;
64+
65+ // 2. Outer Hash: SHA1( Key + Inner_Digest )
66+ let mut outer_hasher = Sha1 :: new ( ) ;
67+ outer_hasher. update ( key) ;
68+ outer_hasher. update ( inner_digest) ;
69+ let outer_digest = outer_hasher. finalize ( ) ;
70+
71+ // Convert GenericArray to standard [u8; 20]
72+ let mut result = [ 0u8 ; 20 ] ;
73+ result. copy_from_slice ( & outer_digest) ;
74+ result
75+ }
76+
4977impl SmartBattery {
5078 pub fn new ( ) -> Self {
5179 SmartBattery {
@@ -68,6 +96,15 @@ impl SmartBattery {
6896 Ok ( ( ) )
6997 }
7098
99+
100+ fn i2c_write ( & self , ec : & CrosEc , addr : u16 , data : & [ u8 ] ) -> EcResult < ( ) > {
101+ i2c_write ( ec, self . i2c_port , self . i2c_addr >> 1 , addr, data) ?;
102+ Ok ( ( ) )
103+ }
104+ fn i2c_read ( & self , ec : & CrosEc , addr : u16 , len : u16 ) -> EcResult < Vec < u8 > > {
105+ self . read_bytes ( ec, addr, len)
106+ }
107+
71108 fn read_bytes ( & self , ec : & CrosEc , addr : u16 , len : u16 ) -> EcResult < Vec < u8 > > {
72109 let i2c_response = i2c_read ( ec, self . i2c_port , self . i2c_addr >> 1 , addr, len + 1 ) ?;
73110 i2c_response. is_successful ( ) ?;
@@ -82,6 +119,7 @@ impl SmartBattery {
82119 i2c_response. data [ 1 ] ,
83120 ] ) )
84121 }
122+
85123 fn read_string ( & self , ec : & CrosEc , addr : u16 ) -> EcResult < String > {
86124 let i2c_response = i2c_read ( ec, self . i2c_port , self . i2c_addr >> 1 , addr, 32 ) ?;
87125 i2c_response. is_successful ( ) ?;
@@ -91,6 +129,61 @@ impl SmartBattery {
91129 Ok ( String :: from_utf8_lossy ( str_bytes) . to_string ( ) )
92130 }
93131
132+ pub fn authenticate_battery ( & self , ec : & CrosEc , auth_key : & [ u8 ; 16 ] ) -> EcResult < bool > {
133+ // 1. Generate a random 20-byte challenge
134+ // In production, use `rand::random()` to generate this.
135+ let challenge: [ u8 ; 20 ] = [
136+ 0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 , 0x08 ,
137+ 0x09 , 0x0A , 0x0B , 0x0C , 0x0D , 0x0E , 0x0F , 0x10 ,
138+ 0x11 , 0x12 , 0x13 , 0x14
139+ ] ;
140+
141+ println ! ( "Step 1: Sending Challenge..." ) ;
142+
143+ // SMBus Block Write format: [Command, Byte_Count, Data...]
144+ let mut write_buf = Vec :: new ( ) ;
145+ write_buf. push ( 20 ) ; // Byte Count (0x14)
146+ write_buf. extend_from_slice ( & challenge) ;
147+
148+ self . i2c_write ( ec, SmartBatReg :: Authenticate as u16 , & write_buf) ?;
149+
150+ // 2. Wait for the gauge to calculate (Datasheet says 250ms)
151+ println ! ( "Step 2: Waiting 250ms..." ) ;
152+ thread:: sleep ( Duration :: from_millis ( 250 ) ) ;
153+
154+ // 3. Calculate expected result locally while waiting
155+ let expected_response = calculate_ti_hmac ( & auth_key, & challenge) ;
156+
157+ // 4. Read Response
158+ // SMBus Block Read: Write Command -> Repeated Start -> Read [Len] + [Data]
159+ println ! ( "Step 3: Reading Response..." ) ;
160+
161+ // For block read, we usually write the command register first
162+ self . i2c_write ( ec, SmartBatReg :: Authenticate as u16 , & [ ] ) ?;
163+
164+ // Read 21 bytes (1 byte length + 20 bytes signature)
165+ // TODO: Read without writing register first
166+ let raw_response = self . i2c_read ( ec, 0x00 , 20 ) ?;
167+
168+ // 5. Parse and Compare
169+ if raw_response. len ( ) < 20 {
170+ return Err ( EcError :: DeviceError ( "Response too short" . to_string ( ) ) ) ;
171+ }
172+
173+ let device_response = & raw_response[ 1 ..20 ] ;
174+
175+ println ! ( "Expected: {:02X?}" , expected_response) ;
176+ println ! ( "Received: {:02X?}" , device_response) ;
177+
178+ if device_response == expected_response {
179+ println ! ( "SUCCESS: Battery is genuine." ) ;
180+ Ok ( true )
181+ } else {
182+ println ! ( "FAILURE: Signature mismatch." ) ;
183+ Ok ( false )
184+ }
185+ }
186+
94187 pub fn dump_data ( & self , ec : & CrosEc ) -> EcResult < ( ) > {
95188 // Check mode
96189 println ! (
@@ -157,6 +250,9 @@ impl SmartBattery {
157250 // Default key - does not work on our battery, it's changed during manufacturing!
158251 self . unseal ( ec, 0x0414 , 0x3672 ) . unwrap ( ) ;
159252
253+ // Dummy code. Do not push real authentication key!
254+ // self.authenticate_battery(ec, &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
255+
160256 // Need to unseal for access
161257 // SE [US] [FA]
162258 let soh = self . read_bytes ( ec, ManufReg :: Soh as u16 , 4 ) ?;
@@ -166,6 +262,10 @@ impl SmartBattery {
166262 u16 :: from_le_bytes( [ soh[ 2 ] , soh[ 3 ] ] ) / 100 ,
167263 u16 :: from_le_bytes( [ soh[ 2 ] , soh[ 3 ] ] ) % 100 ,
168264 ) ;
265+ println ! (
266+ "OperationStatus{:?}" ,
267+ self . read_i16( & ec, ManufReg :: OperationStatus as u16 ) ?
268+ ) ;
169269 println ! (
170270 "Safety Alert: {:?}" ,
171271 self . read_i16( & ec, ManufReg :: SafetyAlert as u16 ) ?
@@ -180,7 +280,7 @@ impl SmartBattery {
180280 ) ;
181281 println ! (
182282 "PFStatus: {:?}" ,
183- self . read_i16( & ec, ManufReg :: SafetyAlert as u16 ) ?
283+ self . read_i16( & ec, ManufReg :: PFStatus as u16 ) ?
184284 ) ;
185285 let lifetime1 = self . read_bytes ( ec, ManufReg :: LifeTimeDataBlock1 as u16 , 32 ) ?;
186286 println ! ( "LifeTime1" ) ;
0 commit comments