1pub mod anti_void;
8pub mod data_swap;
9pub mod module_pos;
10pub mod scan_table;
11
12use crate::record01::View;
13use crate::spec::Generated;
14use crate::Rcvbp;
15use anyhow::{bail, Context, Result};
16use panelspec::PanelSpec;
17pub use receivers::BootImage;
18
19pub const IMAGE_LEN: usize = 0x1_0000;
20
21pub const BASIC_PACK_OFFSET: usize = 0x0000;
24pub const DATA_SWAP_OFFSET: usize = 0x0500;
25pub const MODULE_POS_OFFSET: usize = 0x0600;
26pub const CHIP_PAGE_OFFSET: usize = 0x0900;
27pub const VOID_LINE_OFFSET: usize = 0x1000;
29pub const VOID_LINE_COLUMNS_OFFSET: usize = 0x1400;
30pub const ANTI_VOID_OFFSET: usize = 0x1800;
31pub const MAPPING_OFFSET: usize = 0x3000;
32pub const SCAN_TABLE_OFFSET: usize = 0x6000;
33pub const RCVBP_OFFSET: usize = 0x8000;
34
35const ZERO_REGIONS: [(usize, usize); 6] = [
39 (0x0100, 0x0500),
40 (0x0A00, 0x0C00),
41 (0x0C00, 0x0D00),
42 (0x1000, 0x1800),
43 (0x6800, 0x7000),
44 (0x7000, 0x8000),
45];
46
47pub struct Block7Builder {
48 map: BootImage,
49 img: Vec<u8>,
50 notes: Vec<String>,
51}
52
53impl Block7Builder {
54 #[must_use]
57 pub fn erased(map: &BootImage) -> Self {
58 Self {
59 map: map.clone(),
60 img: vec![0xFF; IMAGE_LEN],
61 notes: Vec::new(),
62 }
63 }
64
65 pub fn from_generated(map: &BootImage, spec: &PanelSpec, g: &Generated) -> Result<Self> {
73 let rec01 = &g.rcvbp.record_01().context("generated config has no record 0x01")?.payload;
74 let mut b = Self::erased(map);
75 b.zero_regions();
76 b.basic_pack(&g.basic_pack)?;
77 b.data_swap_from(rec01)?;
78 b.module_positions_from(rec01)?;
79 b.anti_void_lines();
80 if spec.mapping.gate_phantom_positions {
81 b.void_line_columns(spec.module.width, spec.module.width * 2);
82 }
83 b.mapping_from(&g.rcvbp)?;
84 b.scan_table_from(rec01, spec.card_scan_len())?;
85 Ok(b)
86 }
87
88 fn place(&mut self, at: usize, bytes: &[u8], note: impl Into<String>) {
89 self.img[at..at + bytes.len()].copy_from_slice(bytes);
90 self.notes.push(note.into());
91 }
92
93 pub fn zero_regions(&mut self) {
95 for (lo, hi) in ZERO_REGIONS {
96 self.img[lo..hi].fill(0);
97 }
98 self.notes
99 .push("0x100/0xA00/0xC00/0x1000/0x6800/0x7000: zeros (builders gated off)".into());
100 }
101
102 pub fn void_line_columns(&mut self, from: u16, to: u16) {
107 let at = self.map.void_line_columns;
108 self.img[at + usize::from(from)..at + usize::from(to)].fill(0xFF);
109 self.notes.push(format!(
110 "0x1400: void-line column table, positions {from}..{to} displaced off the chain"
111 ));
112 }
113
114 pub fn basic_pack(&mut self, body: &[u8]) -> Result<()> {
119 if body.len() != 0x100 {
120 bail!("basic pack body is {} bytes, need 256", body.len());
121 }
122 self.place(self.map.basic_pack, body, "page 0x00: basic-parameter pack");
123 Ok(())
124 }
125
126 pub fn chip_registers_from(&mut self, cfg: &Rcvbp) -> Result<()> {
132 let Some(rec) = cfg.find_by_id(0x84) else {
133 return Ok(());
134 };
135 if rec.payload.len() != 0x100 {
136 bail!("record 0x84 is {} bytes, need 256", rec.payload.len());
137 }
138 self.place(self.map.chip_page, &rec.payload, "page 0x09: chip registers");
139 Ok(())
140 }
141
142 pub fn data_swap_from(&mut self, rec01: &[u8]) -> Result<()> {
147 let body = data_swap::body(View::new(rec01)?);
148 self.place(self.map.data_swap, &body, "0x500: data-swap (lane map + deseam 1.0 x3)");
149 Ok(())
150 }
151
152 pub fn module_positions_from(&mut self, rec01: &[u8]) -> Result<()> {
157 let (region, note) = module_pos::region(View::new(rec01)?)?;
158 self.place(self.map.module_positions, ®ion, note);
159 Ok(())
160 }
161
162 pub fn anti_void_lines(&mut self) {
164 let region = anti_void::region();
165 self.place(self.map.anti_void, ®ion, "0x1800: anti-void-line counters");
166 }
167
168 pub fn mapping_from(&mut self, cfg: &Rcvbp) -> Result<()> {
174 let rec = record(cfg, 0x03)?;
175 let body = &rec[2..];
176 if !body.len().is_multiple_of(3) {
177 bail!("mapping record body is {} bytes, not a multiple of 3", body.len());
178 }
179 let len = self.map.mapping_len();
180 if body.len() > len {
181 bail!("mapping record ({} entries) exceeds the card's {} entries", body.len() / 3, self.map.map_entries);
182 }
183 let dst = &mut self.img[self.map.mapping..self.map.mapping + len];
184 dst.fill(0);
185 let (dst3, _) = dst.as_chunks_mut::<3>();
186 let (src3, _) = body.as_chunks::<3>();
187 for (d, e) in dst3.iter_mut().zip(src3) {
188 d.copy_from_slice(&[e[0], e[2], e[1]]);
189 }
190 let note = if body.len() < len {
191 format!(
192 "pages 0x30-0x5f: mapping ({} entries, zero-padded — padding UNVERIFIED)",
193 body.len() / 3
194 )
195 } else {
196 format!("pages 0x30-0x5f: mapping ({} entries)", body.len() / 3)
197 };
198 self.notes.push(note);
199 Ok(())
200 }
201
202 pub fn scan_table_from(&mut self, rec01: &[u8], card_scan_len: u16) -> Result<()> {
207 let table = scan_table::body(View::new(rec01)?, card_scan_len)?;
208 self.place(self.map.scan_table, &table, "0x6000: scan table (bit-time solver)");
209 Ok(())
210 }
211
212 pub fn rcvbp(&mut self, file: &[u8]) -> Result<()> {
217 let max = self.map.rcvbp_max;
218 if file.len() > max {
219 bail!("rcvbp is {} bytes; the vendor clamps at {max}", file.len());
220 }
221 let at = self.map.rcvbp;
222 self.img[at..at + 4].copy_from_slice(&(file.len() as u32).to_le_bytes());
223 self.img[at + 4..at + 4 + file.len()].copy_from_slice(file);
224 self.img[at + 4 + file.len()..].fill(0xFF);
225 self.notes.push(format!("+0x8000: embedded .rcvbp ({} bytes)", file.len()));
226 Ok(())
227 }
228
229 #[must_use]
231 pub fn finish(self) -> Block7 {
232 let changed_pages = self
233 .img
234 .as_chunks::<0x100>()
235 .0
236 .iter()
237 .enumerate()
238 .filter(|(_, page)| page.iter().any(|&b| b != 0xFF))
239 .map(|(i, _)| i as u8)
240 .collect();
241 Block7 {
242 image: self.img,
243 notes: self.notes,
244 changed_pages,
245 }
246 }
247}
248
249pub fn compile(map: &BootImage, spec: &PanelSpec, g: &Generated) -> Result<Block7> {
256 let mut b = Block7Builder::from_generated(map, spec, g)?;
257 if spec.boot.arm_at_boot {
258 b.chip_registers_from(&g.rcvbp)?;
259 }
260 b.rcvbp(&g.rcvbp.to_file_bytes()?)?;
261 Ok(b.finish())
262}
263
264#[derive(Debug, Clone, PartialEq, Eq)]
266pub struct Block7 {
267 pub image: Vec<u8>,
268 pub notes: Vec<String>,
270 pub changed_pages: Vec<u8>,
272}
273
274fn record(cfg: &Rcvbp, id: u8) -> Result<&[u8]> {
275 cfg.find_by_id(id)
276 .map(|r| r.payload.as_slice())
277 .with_context(|| format!("config has no record 0x{id:02x}"))
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn the_e120_model_carries_the_pinned_offsets() {
286 let m = &receivers::by_name("E120").unwrap().memory.boot_image;
287 assert_eq!(
288 [m.basic_pack, m.data_swap, m.module_positions, m.chip_page, m.void_line, m.void_line_columns, m.anti_void, m.mapping, m.scan_table, m.rcvbp],
289 [BASIC_PACK_OFFSET, DATA_SWAP_OFFSET, MODULE_POS_OFFSET, CHIP_PAGE_OFFSET, VOID_LINE_OFFSET, VOID_LINE_COLUMNS_OFFSET, ANTI_VOID_OFFSET, MAPPING_OFFSET, SCAN_TABLE_OFFSET, RCVBP_OFFSET]
290 );
291 assert_eq!((m.map_entries, m.rcvbp_max), (4096, 0x6FFC));
292 }
293}