1use super::{frame_with, indexed};
9
10const SDRAM_TYPE: [u8; 2] = [0x1a, 0x00];
11
12const OP_DATA: u8 = 0x01;
13const OP_PROGRAM: u8 = 0x03;
14const OP_ERASE: u8 = 0x05;
15
16pub const CHUNK: usize = 1024;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum Partition {
22 Primary,
24 Golden,
26}
27
28impl Partition {
29 const fn selector(self) -> u8 {
30 match self {
31 Self::Primary => 0x04,
32 Self::Golden => 0x0d,
33 }
34 }
35}
36
37fn sdram_frame(sel: u16, op: u8, flag: u8, offset: u32, len: u32, data: &[u8]) -> Vec<u8> {
40 let body = data.len().max(CHUNK);
42 frame_with(SDRAM_TYPE, 12 + body, |p| {
43 indexed(p, sel, op);
44 p[4] = flag;
45 p[5..8].copy_from_slice(&offset.to_be_bytes()[1..]);
46 p[8..12].copy_from_slice(&len.to_be_bytes());
47 p[12..12 + data.len()].copy_from_slice(data);
48 })
49}
50
51#[must_use]
53pub fn sdram_chunk(sel: u16, offset: u32, data: &[u8]) -> Vec<u8> {
54 sdram_frame(sel, OP_DATA, 0x00, offset, data.len() as u32, data)
55}
56
57#[must_use]
59pub fn sdram_erase(sel: u16, partition: Partition, len: u32) -> Vec<u8> {
60 sdram_frame(sel, OP_ERASE, partition.selector(), 0, len, &[])
61}
62
63#[must_use]
65pub fn sdram_program(sel: u16, partition: Partition, len: u32) -> Vec<u8> {
66 sdram_frame(sel, OP_PROGRAM, partition.selector(), 0, len, &[])
67}
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub struct Descriptor {
72 pub start: u32,
74 pub image_len: u32,
76 pub file_len: u32,
78 pub flash_op_type: u8,
80 capabilities: u8,
81}
82
83impl Descriptor {
84 #[must_use]
86 pub const fn supports_sdram(self) -> bool {
87 self.capabilities & 0b0001 != 0
88 }
89
90 #[must_use]
92 pub const fn has_golden(self) -> bool {
93 self.capabilities & 0b0010 != 0
94 }
95
96 #[must_use]
98 pub const fn supports_select_part(self) -> bool {
99 self.capabilities & 0b0100 != 0
100 }
101
102 #[must_use]
104 pub const fn supports_golden_upgrade(self) -> bool {
105 self.capabilities & 0b1000 != 0
106 }
107
108 #[must_use]
110 pub const fn chunks(self) -> usize {
111 (self.image_len as usize).div_ceil(CHUNK)
112 }
113
114 #[must_use]
117 pub const fn first_poll_ms(self) -> u64 {
118 let blocks = (self.image_len as u64).div_ceil(0x10000);
119 let by_size = 150 * blocks;
120 if by_size > 1000 {
121 by_size
122 } else {
123 1000
124 }
125 }
126
127 #[must_use]
130 pub const fn estimated_ms(self) -> u64 {
131 let blocks = (self.image_len as u64).div_ceil(0x10000);
132 let pages = (self.image_len as u64).div_ceil(0x100);
133 500 * blocks + 3 * pages
134 }
135}
136
137#[must_use]
140pub fn parse_descriptor(eth_frame: &[u8]) -> Option<Descriptor> {
141 let p = eth_frame.get(12..)?;
142 if *p.first()? != 0x08 || p.get(1)? & 0b10 == 0 {
144 return None;
145 }
146 let byte = |i: usize| p.get(i).copied();
147 let image_len = u32::from(byte(0x18)?) << 16 | u32::from(byte(0x19)?) << 8;
148 let low = u32::from(byte(0x1b)?.wrapping_add(byte(0x1a)?.wrapping_mul(4)));
150 Some(Descriptor {
151 start: u32::from(byte(0x16)?) << 16 | u32::from(byte(0x17)?) << 8,
152 image_len,
153 file_len: image_len | (low & 0xff),
154 flash_op_type: byte(0x12)?,
155 capabilities: byte(0x13)?,
156 })
157}
158
159#[must_use]
162pub fn programming_finished(eth_frame: &[u8]) -> bool {
163 eth_frame
164 .get(12 + 0xc0)
165 .is_some_and(|status| status & 0b10 != 0)
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use crate::BROADCAST;
172
173 fn real_reply() -> Vec<u8> {
175 let mut f = vec![0u8; 12];
176 f.extend_from_slice(&[0x08, 0x02]);
177 f.extend_from_slice(&[
178 0x00, 0x1a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
179 0xff, 0x00, 0x36, 0x05, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x20, 0x00,
180 ]);
181 f.resize(1070, 0);
182 f
183 }
184
185 #[test]
186 fn the_real_reply_decodes_as_the_card_reported() {
187 let d = parse_descriptor(&real_reply()).expect("should decode");
188 assert_eq!(d.start, 0x0000_0000, "image starts at zero");
189 assert_eq!(d.image_len, 0x000b_0000, "720896 bytes programmed");
190 assert_eq!(d.file_len, 0x000b_0080, "721024 bytes of source file");
191 assert_eq!(d.flash_op_type, 0x36);
192 assert!(d.supports_sdram(), "this card stages via SDRAM");
193 assert!(!d.has_golden(), "and reports no golden bank");
194 assert!(d.supports_select_part());
195 assert!(!d.supports_golden_upgrade());
196 }
197
198 #[test]
199 fn the_image_needs_704_chunks() {
200 let d = parse_descriptor(&real_reply()).unwrap();
201 assert_eq!(d.chunks(), 704);
202 }
203
204 #[test]
205 fn timings_match_the_vendor_formulas() {
206 let d = parse_descriptor(&real_reply()).unwrap();
207 assert_eq!(d.first_poll_ms(), 1650);
209 assert_eq!(d.estimated_ms(), 500 * 11 + 3 * 2816);
210 }
211
212 #[test]
213 fn a_reply_of_the_wrong_type_is_rejected() {
214 let mut f = real_reply();
215 f[12] = 0x09;
216 assert!(parse_descriptor(&f).is_none());
217 }
218
219 #[test]
220 fn a_chunk_frame_matches_the_documented_layout() {
221 let data: Vec<u8> = (0..CHUNK).map(|i| i as u8).collect();
222 let f = sdram_chunk(BROADCAST, 0x000c_0400, &data);
223 assert_eq!(f.len(), 1050, "12 MAC + 2 type + 12 header + 1024 data");
224 assert_eq!(&f[12..14], &SDRAM_TYPE);
225 assert_eq!(&f[15..17], &BROADCAST.to_be_bytes());
226 assert_eq!(f[17], OP_DATA);
227 assert_eq!(f[18], 0x00);
228 assert_eq!(&f[19..22], &[0x0c, 0x04, 0x00], "24-bit big-endian offset");
229 assert_eq!(&f[22..26], &1024u32.to_be_bytes());
230 assert_eq!(&f[26..], &data[..]);
231 }
232
233 #[test]
234 fn erase_and_program_carry_the_partition_and_length() {
235 let len = 0x000b_0000;
236 let e = sdram_erase(BROADCAST, Partition::Primary, len);
237 assert_eq!(e[17], OP_ERASE);
238 assert_eq!(e[18], 0x04, "primary partition selector");
239 assert_eq!(&e[19..22], &[0, 0, 0], "no address on erase");
240 assert_eq!(&e[22..26], &len.to_be_bytes());
241
242 let p = sdram_program(BROADCAST, Partition::Primary, len);
243 assert_eq!(p[17], OP_PROGRAM);
244 assert_eq!(&p[18..], &e[18..]);
246 }
247
248 #[test]
249 fn the_golden_partition_uses_its_own_selector() {
250 let g = sdram_erase(BROADCAST, Partition::Golden, 0x1000);
251 assert_eq!(g[18], 0x0d);
252 }
253
254 #[test]
255 fn completion_is_read_from_the_status_bit() {
256 let mut f = vec![0u8; 12 + 0xc1];
257 assert!(!programming_finished(&f));
258 f[12 + 0xc0] = 0b10;
259 assert!(programming_finished(&f));
260 }
261}