1use super::{frame_with, indexed};
8use std::ops::Range;
9
10const FLASH_OP_READ: u8 = 0x44;
13
14pub const FLASH_PAGE_BASIC_PARAM: u16 = 0x0780;
16
17pub const FLASH_PAGES_PER_CHUNK: u16 = 4;
19
20const FLASH_OP_ERASE: u8 = 0x23;
21
22const FLASH_OP_WRITE: u8 = 0x85;
24
25pub const FLASH_PAGE_BYTES: usize = 256;
26
27pub const PARAM_BLOCK: u8 = 0x07;
29
30pub const FIRMWARE_BLOCKS: Range<u8> = 0x00..0x0b;
33
34pub const GOLDEN_BLOCK: u8 = 0x20;
36
37pub const SCREEN_RECORD_ADDR: u32 = 0x0007_f000;
40
41pub const SCREEN_RECORD_LEN: usize = 256;
42
43#[derive(Clone, Debug, PartialEq, Eq)]
46pub struct FlashMap {
47 pub param_block: u8,
49 pub firmware_blocks: Range<u8>,
51 pub golden_block: u8,
53 pub screen_record_addr: u32,
55}
56
57pub const E120: FlashMap = FlashMap {
59 param_block: PARAM_BLOCK,
60 firmware_blocks: FIRMWARE_BLOCKS,
61 golden_block: GOLDEN_BLOCK,
62 screen_record_addr: SCREEN_RECORD_ADDR,
63};
64
65pub const FLASH_REPLY_TYPE: [u8; 2] = [0x09, 0x01];
67
68pub const FLASH_CHUNK_BYTES: usize = 1024;
70
71#[must_use]
74pub fn read_flash(rcv_index: u16, page: u16) -> Vec<u8> {
75 paged(rcv_index, FLASH_OP_READ, page)
76}
77
78fn paged(rcv_index: u16, opcode: u8, page: u16) -> Vec<u8> {
80 frame_with([0x06, 0x00], 126, |p| {
81 indexed(p, rcv_index, opcode);
82 p[4] = 0x01;
83 p[5..7].copy_from_slice(&page.to_be_bytes());
84 })
85}
86
87#[must_use]
93pub fn set_program_writable(rcv_index: u16, writable: bool) -> Vec<u8> {
94 frame_with([0x23, 0x00], 126, |p| {
95 indexed(p, rcv_index, if writable { 0xff } else { 0x00 });
96 })
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
101pub enum WriteError {
102 ForbiddenBlock(u8),
103 WrongPageSize(usize),
104 ForbiddenAddress(u32),
105}
106
107impl std::fmt::Display for WriteError {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 match self {
110 Self::ForbiddenBlock(b) => write!(
111 f,
112 "refusing to touch flash block 0x{b:02x}; outside this write's allowlist"
113 ),
114 Self::WrongPageSize(n) => {
115 write!(f, "page payload is {n} bytes, must be {FLASH_PAGE_BYTES}")
116 }
117 Self::ForbiddenAddress(a) => write!(
118 f,
119 "refusing linear flash access at 0x{a:08x}; only the screen-size \
120 record may be reached this way"
121 ),
122 }
123 }
124}
125
126impl std::error::Error for WriteError {}
127
128impl FlashMap {
129 fn param_block(&self, block: u8) -> Result<u8, WriteError> {
130 if block == self.param_block {
131 Ok(block)
132 } else {
133 Err(WriteError::ForbiddenBlock(block))
134 }
135 }
136
137 fn firmware_block(&self, block: u8) -> Result<u8, WriteError> {
138 if self.firmware_blocks.contains(&block) {
139 Ok(block)
140 } else {
141 Err(WriteError::ForbiddenBlock(block))
142 }
143 }
144
145 pub fn erase_block(&self, rcv_index: u16, block: u8) -> Result<Vec<u8>, WriteError> {
150 Ok(erase_block_unchecked(rcv_index, self.param_block(block)?))
151 }
152
153 pub fn erase_firmware_block(&self, rcv_index: u16, block: u8) -> Result<Vec<u8>, WriteError> {
159 Ok(erase_block_unchecked(rcv_index, self.firmware_block(block)?))
160 }
161
162 pub fn write_firmware_page(&self, rcv_index: u16, block: u8, page: u8, data: &[u8]) -> Result<Vec<u8>, WriteError> {
168 Ok(write_page_unchecked(rcv_index, self.firmware_block(block)?, page, one_page(data)?))
169 }
170
171 pub fn write_page(&self, rcv_index: u16, block: u8, page: u8, data: &[u8]) -> Result<Vec<u8>, WriteError> {
177 Ok(write_page_unchecked(rcv_index, self.param_block(block)?, page, one_page(data)?))
178 }
179
180 pub fn write_screen_record(&self, rcv_index: u16, addr: u32, data: &[u8]) -> Result<Vec<u8>, WriteError> {
186 if data.len() != SCREEN_RECORD_LEN {
187 return Err(WriteError::WrongPageSize(data.len()));
188 }
189 if addr != self.screen_record_addr {
190 return Err(WriteError::ForbiddenAddress(addr));
191 }
192 Ok(linear(rcv_index, FLASH_OP_WRITE, addr, SCREEN_RECORD_LEN as u32, data, 4))
193 }
194
195 #[must_use]
197 pub fn read_screen_record(&self, rcv_index: u16) -> Vec<u8> {
198 read_flash_linear(rcv_index, self.screen_record_addr, SCREEN_RECORD_LEN as u32)
199 }
200}
201
202fn one_page(data: &[u8]) -> Result<&[u8], WriteError> {
203 if data.len() == FLASH_PAGE_BYTES {
204 Ok(data)
205 } else {
206 Err(WriteError::WrongPageSize(data.len()))
207 }
208}
209
210fn erase_block_unchecked(rcv_index: u16, block: u8) -> Vec<u8> {
211 paged(rcv_index, FLASH_OP_ERASE, u16::from(block) << 8)
212}
213
214fn write_page_unchecked(rcv_index: u16, block: u8, page: u8, data: &[u8]) -> Vec<u8> {
215 frame_with([0x06, 0x00], 8 + FLASH_PAGE_BYTES, |p| {
218 indexed(p, rcv_index, FLASH_OP_WRITE);
219 p[5] = block;
220 p[6] = page;
221 p[8..].copy_from_slice(data);
222 })
223}
224
225#[must_use]
227pub fn read_flash_linear(rcv_index: u16, addr: u32, len: u32) -> Vec<u8> {
228 linear(rcv_index, FLASH_OP_READ, addr, len, &[], 0)
229}
230
231fn linear(rcv_index: u16, opcode: u8, addr: u32, len: u32, data: &[u8], tail: usize) -> Vec<u8> {
234 frame_with([0x19, 0x00], 12 + data.len() + tail, |p| {
235 indexed(p, rcv_index, opcode);
236 p[4..8].copy_from_slice(&addr.to_be_bytes());
237 p[8..12].copy_from_slice(&len.to_be_bytes());
238 p[12..12 + data.len()].copy_from_slice(data);
239 })
240}
241
242pub fn flash_reply_data(eth_frame: &[u8]) -> Option<&[u8]> {
245 if eth_frame.len() < 15 || eth_frame[12..14] != FLASH_REPLY_TYPE {
246 return None;
247 }
248 let data = ð_frame[15..];
249 Some(&data[..data.len().min(FLASH_CHUNK_BYTES)])
250}
251
252#[cfg(test)]
253mod linear_tests {
254 use super::*;
255
256 #[test]
257 fn screen_record_write_matches_the_documented_layout() {
258 let data = vec![0xabu8; SCREEN_RECORD_LEN];
259 let f = E120.write_screen_record(0, SCREEN_RECORD_ADDR, &data).unwrap();
260 assert_eq!(&f[12..14], &[0x19, 0x00]);
261 assert_eq!(f[17], FLASH_OP_WRITE);
262 assert_eq!(&f[18..22], &SCREEN_RECORD_ADDR.to_be_bytes());
263 assert_eq!(&f[22..26], &(SCREEN_RECORD_LEN as u32).to_be_bytes());
264 assert_eq!(&f[26..26 + SCREEN_RECORD_LEN], &data[..]);
265 assert_eq!(f.len(), 286);
266 }
267
268 #[test]
269 fn linear_frames_refuse_every_address_but_the_screen_record() {
270 let data = vec![0u8; SCREEN_RECORD_LEN];
271 for addr in [
272 0x0000_0000,
273 0x0007_0000,
274 0x0007_efff,
275 0x0007_f001,
276 0x0008_0000,
277 0xffff_ffff,
278 ] {
279 assert_eq!(
280 E120.write_screen_record(0, addr, &data),
281 Err(WriteError::ForbiddenAddress(addr)),
282 "address 0x{addr:08x} must be refused"
283 );
284 }
285 }
286
287 #[test]
288 fn the_screen_record_address_is_allowed() {
289 let data = vec![0u8; SCREEN_RECORD_LEN];
290 assert!(E120.write_screen_record(0, SCREEN_RECORD_ADDR, &data).is_ok());
291 }
292
293 #[test]
294 fn a_wrong_length_payload_is_refused() {
295 assert_eq!(
296 E120.write_screen_record(0, SCREEN_RECORD_ADDR, &[0; 128]),
297 Err(WriteError::WrongPageSize(128))
298 );
299 }
300
301 #[test]
302 fn a_linear_read_carries_no_data() {
303 let f = read_flash_linear(0, SCREEN_RECORD_ADDR, SCREEN_RECORD_LEN as u32);
304 assert_eq!(&f[12..14], &[0x19, 0x00]);
305 assert_eq!(f[17], FLASH_OP_READ);
306 assert_eq!(&f[18..22], &SCREEN_RECORD_ADDR.to_be_bytes());
307 assert_eq!(&f[22..26], &256u32.to_be_bytes());
308 assert_eq!(f.len(), 26);
309 }
310}
311
312#[cfg(test)]
313mod firmware_tests {
314 use super::*;
315
316 #[test]
317 fn firmware_writes_are_confined_to_the_primary_image() {
318 let page = [0u8; FLASH_PAGE_BYTES];
319 for block in FIRMWARE_BLOCKS {
320 assert!(E120.erase_firmware_block(0, block).is_ok());
321 assert!(E120.write_firmware_page(0, block, 0, &page).is_ok());
322 }
323 for block in [GOLDEN_BLOCK, 0x0b, 0x0c, 0x21, 0xff] {
324 assert_eq!(
325 E120.erase_firmware_block(0, block),
326 Err(WriteError::ForbiddenBlock(block)),
327 "block 0x{block:02x} must be refused"
328 );
329 assert_eq!(
330 E120.write_firmware_page(0, block, 0, &page),
331 Err(WriteError::ForbiddenBlock(block))
332 );
333 }
334 }
335
336 #[test]
337 fn the_golden_bank_is_outside_the_writable_range() {
338 assert!(!FIRMWARE_BLOCKS.contains(&GOLDEN_BLOCK));
339 }
340
341 #[test]
342 fn the_parameter_helpers_still_refuse_firmware_blocks() {
343 assert_eq!(E120.erase_block(0, 0x00), Err(WriteError::ForbiddenBlock(0x00)));
344 assert_eq!(
345 E120.write_page(0, 0x00, 0, &[0u8; FLASH_PAGE_BYTES]),
346 Err(WriteError::ForbiddenBlock(0x00))
347 );
348 }
349}
350
351#[cfg(test)]
352mod writable_tests {
353 use super::*;
354
355 #[test]
356 fn unlock_uses_the_negated_flag() {
357 let f = set_program_writable(0, true);
358 assert_eq!(&f[12..14], &[0x23, 0x00]);
359 assert_eq!(f[17], 0xff, "enable is 0xff, not 0x01");
360 assert_eq!(f.len(), 140);
361 }
362
363 #[test]
364 fn relock_clears_it() {
365 assert_eq!(set_program_writable(0, false)[17], 0x00);
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372 use crate::{CARD_MAC, SENDER_MAC};
373
374 #[test]
375 fn read_frame_matches_the_documented_layout() {
376 let f = read_flash(0, FLASH_PAGE_BASIC_PARAM);
377 assert_eq!(f.len(), 140);
378 assert_eq!(&f[0..6], &CARD_MAC);
379 assert_eq!(&f[6..12], &SENDER_MAC);
380 assert_eq!(&f[12..14], &[0x06, 0x00]);
381 assert_eq!(f[17], FLASH_OP_READ);
382 assert_eq!(&f[19..21], &[0x07, 0x80]);
383 }
384
385 #[test]
386 fn a_read_frame_carries_no_data() {
387 let f = read_flash(0, FLASH_PAGE_BASIC_PARAM);
388 assert!(f[21..].iter().all(|&b| b == 0));
389 }
390
391 #[test]
392 fn writes_outside_the_parameter_block_are_refused() {
393 for block in [0x00, 0x01, 0x06, 0x08, 0xff] {
394 assert_eq!(
395 E120.erase_block(0, block),
396 Err(WriteError::ForbiddenBlock(block)),
397 "block 0x{block:02x} must be refused"
398 );
399 assert_eq!(
400 E120.write_page(0, block, 0, &[0; FLASH_PAGE_BYTES]),
401 Err(WriteError::ForbiddenBlock(block))
402 );
403 }
404 }
405
406 #[test]
407 fn the_parameter_block_is_allowed() {
408 assert!(E120.erase_block(0, PARAM_BLOCK).is_ok());
409 assert!(E120.write_page(0, PARAM_BLOCK, 0x80, &[0; FLASH_PAGE_BYTES]).is_ok());
410 }
411
412 #[test]
413 fn a_page_write_must_be_exactly_one_page() {
414 assert_eq!(
415 E120.write_page(0, PARAM_BLOCK, 0, &[0; 255]),
416 Err(WriteError::WrongPageSize(255))
417 );
418 assert_eq!(
419 E120.write_page(0, PARAM_BLOCK, 0, &[0; 257]),
420 Err(WriteError::WrongPageSize(257))
421 );
422 }
423
424 #[test]
425 fn write_frame_carries_the_page_data_at_the_documented_offset() {
426 let data: Vec<u8> = (0..=255u8).collect();
427 let f = E120.write_page(1, PARAM_BLOCK, 0x81, &data).unwrap();
428 assert_eq!(f.len(), 278);
429 assert_eq!(f[17], FLASH_OP_WRITE);
430 assert_eq!(f[18], 0x00, "flag byte");
431 assert_eq!(f[19], PARAM_BLOCK);
432 assert_eq!(f[20], 0x81);
433 assert_eq!(&f[22..], &data[..]);
434 }
435
436 #[test]
437 fn erase_frame_carries_the_block_in_the_page_high_byte() {
438 let f = E120.erase_block(0, PARAM_BLOCK).unwrap();
439 assert_eq!(f.len(), 140);
440 assert_eq!(f[17], FLASH_OP_ERASE);
441 assert_eq!(f[18], 0x01);
442 assert_eq!(&f[19..21], &[PARAM_BLOCK, 0x00]);
443 }
444}