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,21 @@ 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+
108+ fn read_bytes ( & self , ec : & CrosEc , addr : u16 , len : u16 ) -> EcResult < Vec < u8 > > {
109+ let i2c_response = i2c_read ( ec, self . i2c_port , self . i2c_addr >> 1 , addr, len) ?;
110+ i2c_response. is_successful ( ) ?;
111+ Ok ( i2c_response. data )
112+ }
113+
71114 fn read_bytes ( & self , ec : & CrosEc , addr : u16 , len : u16 ) -> EcResult < Vec < u8 > > {
72115 let i2c_response = i2c_read ( ec, self . i2c_port , self . i2c_addr >> 1 , addr, len + 1 ) ?;
73116 i2c_response. is_successful ( ) ?;
@@ -82,6 +125,7 @@ impl SmartBattery {
82125 i2c_response. data [ 1 ] ,
83126 ] ) )
84127 }
128+
85129 fn read_string ( & self , ec : & CrosEc , addr : u16 ) -> EcResult < String > {
86130 let i2c_response = i2c_read ( ec, self . i2c_port , self . i2c_addr >> 1 , addr, 32 ) ?;
87131 i2c_response. is_successful ( ) ?;
@@ -91,6 +135,67 @@ impl SmartBattery {
91135 Ok ( String :: from_utf8_lossy ( str_bytes) . to_string ( ) )
92136 }
93137
138+ pub fn authenticate_battery ( & self , ec : & CrosEc , auth_key : & [ u8 ; 16 ] ) -> EcResult < bool > {
139+ // 1. Generate a random 20-byte challenge
140+ // In production, use `rand::random()` to generate this.
141+ let challenge: [ u8 ; 20 ] = [
142+ 0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 , 0x08 ,
143+ 0x09 , 0x0A , 0x0B , 0x0C , 0x0D , 0x0E , 0x0F , 0x10 ,
144+ 0x11 , 0x12 , 0x13 , 0x14
145+ ] ;
146+
147+ println ! ( "Step 1: Sending Challenge..." ) ;
148+
149+ // SMBus Block Write format: [Command, Byte_Count, Data...]
150+ let mut write_buf = Vec :: new ( ) ;
151+ write_buf. push ( 20 ) ; // Byte Count (0x14)
152+ write_buf. extend_from_slice ( & challenge) ;
153+
154+ self . i2c_write ( ec, SmartBatReg :: Authenticate as u16 , & write_buf) ?;
155+
156+ // 2. Wait for the gauge to calculate (Datasheet says 250ms)
157+ println ! ( "Step 2: Waiting 250ms..." ) ;
158+ thread:: sleep ( Duration :: from_millis ( 250 ) ) ;
159+
160+ // 3. Calculate expected result locally while waiting
161+ let expected_response = calculate_ti_hmac ( & auth_key, & challenge) ;
162+
163+ // 4. Read Response
164+ // SMBus Block Read: Write Command -> Repeated Start -> Read [Len] + [Data]
165+ println ! ( "Step 3: Reading Response..." ) ;
166+
167+ // For block read, we usually write the command register first
168+ self . i2c_write ( ec, SmartBatReg :: Authenticate as u16 , & [ ] ) ?;
169+
170+ // Read 21 bytes (1 byte length + 20 bytes signature)
171+ // TODO: Read without writing register first
172+ let raw_response = self . i2c_read ( ec, 0x00 , 21 ) . i2c_data ?;
173+
174+ // 5. Parse and Compare
175+ if raw_response. len ( ) < 21 {
176+ return Err ( EcError :: DeviceError ( "Response too short" . to_string ( ) ) ) ;
177+ }
178+
179+ // The first byte in SMBus block read is the length (should be 20)
180+ let len_byte = raw_response[ 0 ] ;
181+ if len_byte != 20 {
182+ println ! ( "Warning: Device returned unexpected length: {}" , len_byte) ;
183+ }
184+
185+ let device_response = & raw_response[ 1 ..21 ] ;
186+
187+ println ! ( "Expected: {:02X?}" , expected_response) ;
188+ println ! ( "Received: {:02X?}" , device_response) ;
189+
190+ if device_response == expected_response {
191+ println ! ( "SUCCESS: Battery is genuine." ) ;
192+ Ok ( true )
193+ } else {
194+ println ! ( "FAILURE: Signature mismatch." ) ;
195+ Ok ( false )
196+ }
197+ }
198+
94199 pub fn dump_data ( & self , ec : & CrosEc ) -> EcResult < ( ) > {
95200 // Check mode
96201 println ! (
@@ -157,6 +262,14 @@ impl SmartBattery {
157262 // Default key - does not work on our battery, it's changed during manufacturing!
158263 self . unseal ( ec, 0x0414 , 0x3672 ) . unwrap ( ) ;
159264
265+ // Dummy code. Do not push real authentication key!
266+ self . authenticate_battery ( ec, & [ 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ] ) ;
267+
268+ println ! ( "Trying to unseal" ) ;
269+ // Try default key
270+ let default_unseal = & [ 0x0000 , 0x0000 ] ;
271+ self . unseal ( ec, default_unseal) ?;
272+
160273 // Need to unseal for access
161274 // SE [US] [FA]
162275 let soh = self . read_bytes ( ec, ManufReg :: Soh as u16 , 4 ) ?;
@@ -166,6 +279,10 @@ impl SmartBattery {
166279 u16 :: from_le_bytes( [ soh[ 2 ] , soh[ 3 ] ] ) / 100 ,
167280 u16 :: from_le_bytes( [ soh[ 2 ] , soh[ 3 ] ] ) % 100 ,
168281 ) ;
282+ println ! (
283+ "OperationStatus{:?}" ,
284+ self . read_i16( & ec, ManufReg :: OperationStatus as u16 ) ?
285+ ) ;
169286 println ! (
170287 "Safety Alert: {:?}" ,
171288 self . read_i16( & ec, ManufReg :: SafetyAlert as u16 ) ?
0 commit comments