mediatek_optical/receiver/
entry.rs1pub mod application;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct LegacyCandidate {
13 pub decoder: u32,
15 pub entry: u32,
17 pub end: u32,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct DualCandidate {
25 pub decoder: u32,
27 pub entry: u32,
29 pub stream: u32,
31}
32
33struct Window<'a> {
34 bytes: &'a [u8],
35 base: u32,
36}
37impl Window<'_> {
38 fn bytes(&self, address: u32, count: usize) -> Option<&[u8]> {
39 let start = usize::try_from(address.checked_sub(self.base)?).ok()?;
40 self.bytes.get(start..start.checked_add(count)?)
41 }
42 fn word(&self, address: u32) -> Option<u16> {
43 Some(u16::from_le_bytes(self.bytes(address, 2)?.try_into().ok()?))
44 }
45 fn literal(&self, address: u32) -> Option<(u16, u32)> {
46 let op = self.word(address)?;
47 if op & 0xf800 != 0x4800 {
48 return None;
49 }
50 let at = (address.checked_add(4)? & !3).checked_add(u32::from(op & 255) * 4)?;
51 Some((
52 (op >> 8) & 7,
53 u32::from_le_bytes(self.bytes(at, 4)?.try_into().ok()?),
54 ))
55 }
56 fn conditional(&self, address: u32, condition: u16) -> Option<u32> {
57 let op = self.word(address)?;
58 if op & 0xff00 != condition {
59 return None;
60 }
61 u32::try_from(i64::from(address) + 4 + i64::from(op as u8 as i8) * 2).ok()
62 }
63 fn branch(&self, address: u32) -> Option<u32> {
64 let op = self.word(address)?;
65 if op & 0xf800 != 0xe000 {
66 return None;
67 }
68 let delta = ((i32::from(op & 0x7ff) << 21) >> 20) as i64;
69 u32::try_from(i64::from(address) + 4 + delta).ok()
70 }
71}
72
73fn prologue(w: &Window<'_>, start: u32) -> Option<bool> {
77 for distance in [14, 16, 18] {
78 let Some(mut p) = start.checked_sub(distance) else {
79 continue;
80 };
81 let Some((0, cdb)) = w.literal(p) else {
82 continue;
83 };
84 if !(0x0200_0000..0x0201_0000).contains(&cdb) {
85 continue;
86 }
87 p += 2;
88 if w.word(p) != Some(0xb5f0) || w.word(p + 2) != Some(0x7882) {
89 continue;
90 }
91 p += 4;
92 if !matches!(w.word(p), Some(0xb085 | 0xb089)) {
93 continue;
94 }
95 p += 2;
96 let mut one = false;
97 let mut state = false;
98 while p < start.saturating_sub(4) {
99 if w.word(p) == Some(0x2701) && !one {
100 one = true;
101 p += 2;
102 } else if let Some((1, address)) = w.literal(p) {
103 if state || !(0x0200_0000..0x0201_0000).contains(&address) {
104 break;
105 }
106 state = true;
107 p += 2;
108 } else if state && w.word(p) == Some(0x390a) {
109 p += 2;
110 } else {
111 break;
112 }
113 }
114 if state
115 && p == start.checked_sub(4)?
116 && w.word(p) == Some(0x2a00)
117 && w.conditional(p + 2, 0xd100).is_some()
118 {
119 return Some(one);
120 }
121 }
122 None
123}
124
125fn candidate(w: &Window<'_>, start: u32) -> Option<LegacyCandidate> {
126 if w.bytes(start, 10)? != [0x42, 0x78, 0x12, 0x07, 0x12, 0x0f, 0x42, 0x70, 0x42, 0x78] {
128 return None;
129 }
130 let preloaded_one = prologue(w, start)?;
131 let mut p = start.checked_add(10)?;
132 let mut mmio = w.literal(p);
133 if mmio.is_some() {
134 p = p.checked_add(2)?;
135 }
136 if w.word(p)? != 0x2a01 {
137 return None;
138 }
139 let entry = w.conditional(p.checked_add(2)?, 0xd000)?;
140 p = entry;
141 if let Some(local) = w.literal(p) {
142 mmio = Some(local);
143 p = p.checked_add(2)?;
144 }
145 let (base, address) = mmio?;
146 if address != 0x0400_2040 || base == 0 {
147 return None;
148 }
149 let mut tmp = w.word(p)? & 7;
150 if tmp == 0 || tmp == base {
151 return None;
152 }
153 let load = 0x6800 | (6 << 6) | (base << 3) | tmp;
154 let clear = [
155 load,
156 0x0800 | (4 << 6) | (tmp << 3) | tmp,
157 (4 << 6) | (tmp << 3) | tmp,
158 0x6000 | (6 << 6) | (base << 3) | tmp,
159 ];
160 for op in clear {
161 if w.word(p)? != op {
162 return None;
163 }
164 p = p.checked_add(2)?;
165 }
166 for bit in 0..4u16 {
167 if bit == 3 {
168 tmp = 0;
169 }
170 if w.word(p)? != 0x7800 | (9 << 6) | tmp
171 || w.word(p.checked_add(2)?)? != ((31 - bit) << 6) | (tmp << 3) | tmp
172 {
173 return None;
174 }
175 let skip = w.conditional(p.checked_add(4)?, if bit == 0 { 0xd000 } else { 0xd500 })?;
176 p = p.checked_add(6)?;
177 if w.word(p)? != 0x6800 | (6 << 6) | (base << 3) | tmp {
178 return None;
179 }
180 p = p.checked_add(2)?;
181 let op = w.word(p)?;
182 let source = if op & 0xf800 == 0x2000 {
183 if op & 255 != 1 << bit {
184 return None;
185 }
186 p = p.checked_add(2)?;
187 (op >> 8) & 7
188 } else if bit == 0 && preloaded_one && base != 7 {
189 7
190 } else {
191 return None;
192 };
193 if source == tmp || source == base || (source == 0 && bit != 3) {
194 return None;
195 }
196 if w.word(p)? != 0x4300 | (source << 3) | tmp
197 || w.word(p.checked_add(2)?)? != 0x6000 | (6 << 6) | (base << 3) | tmp
198 {
199 return None;
200 }
201 p = p.checked_add(4)?;
202 if p != skip {
203 return None;
204 }
205 }
206 if w.word(p)? & 0xff80 != 0xb000 || w.word(p.checked_add(2)?)? & 0xff00 != 0xbd00 {
207 return None;
208 }
209 Some(LegacyCandidate {
210 decoder: start,
211 entry,
212 end: p.checked_add(4)?,
213 })
214}
215
216pub fn inspect_boot_legacy(bytes: &[u8], address: u32) -> Option<LegacyCandidate> {
221 let w = Window {
222 bytes,
223 base: address,
224 };
225 let mut found = None;
226 for index in (0..bytes.len().saturating_sub(9)).step_by(2) {
227 let at = address.checked_add(u32::try_from(index).ok()?)?;
228 if at & 1 != 0 {
229 continue;
230 }
231 if let Some(hit) = candidate(&w, at) {
232 if found.is_some() {
233 return None;
234 }
235 found = Some(hit);
236 }
237 }
238 found
239}
240
241const DUAL_LOCAL: &[u8] = b"\x00\x48\x85\x69\x2d\x09\x2d\x01\x85\x61\x4d\x7a\xed\x07\x10\xd1\x4d\x7a\xad\x07\x02\xd5\x85\x69\x2c\x43\x84\x61\x4c\x7a\x64\x07\x0c\xd4\x49\x7a\x09\x07\x02\xd5\x81\x69\x11\x43\x81\x61\x05\xb0\xf0\xbd\x85\x69\x01\x26\x35\x43\x85\x61\xe9\xe7\x84\x69\x23\x43\x83\x61\xee\xe7";
245const DUAL_PRELOADED: &[u8] = b"\xb0\x69\x00\x09\x00\x01\xb0\x61\x48\x7a\xc0\x07\x10\xd1\x48\x7a\x80\x07\x02\xd5\xb0\x69\x20\x43\xb0\x61\x48\x7a\x40\x07\x0b\xd4\x48\x7a\x00\x07\x02\xd5\xb0\x69\x10\x43\xb0\x61\x09\xb0\xf0\xbd\xb0\x69\x38\x43\xb0\x61\xea\xe7\xb0\x69\x18\x43\xb0\x61\xef\xe7";
246
247fn dual_candidate(w: &Window<'_>, start: u32) -> Option<DualCandidate> {
248 let (register, cdb) = w.literal(start)?;
249 if register != 1 || !(0x0200_0000..0x0201_0000).contains(&cdb) {
250 return None;
251 }
252 let mut p = start.checked_add(2)?;
253 for op in [0xb5f0, 0x7848] {
254 if w.word(p)? != op {
255 return None;
256 }
257 p = p.checked_add(2)?;
258 }
259 let stack = w.word(p)?;
260 if !matches!(stack, 0xb085 | 0xb089) {
261 return None;
262 }
263 p = p.checked_add(2)?;
264 for op in [0x0700, 0x0f00, 0x7048, 0x7848, 0x2208, 0x2304, 0x2402] {
265 if w.word(p)? != op {
266 return None;
267 }
268 p = p.checked_add(2)?;
269 }
270 let preloaded = stack == 0xb089;
271 if preloaded {
272 if w.word(p)? != 0x2701 || w.literal(p.checked_add(2)?)? != (6, 0x0400_2040) {
273 return None;
274 }
275 p = p.checked_add(4)?;
276 }
277 if w.word(p)? != 0x2801 {
278 return None;
279 }
280 let entry = w.conditional(p.checked_add(2)?, 0xd000)?;
281 p = p.checked_add(4)?;
282 let (register, state) = w.literal(p)?;
283 if register != 5 || !(0x0200_0000..0x0201_0000).contains(&state) {
284 return None;
285 }
286 p = p.checked_add(2)?;
287 if w.word(p)? != 0x3d0a || w.word(p.checked_add(2)?)? != 0x2802 {
288 return None;
289 }
290 p = p.checked_add(4)?;
291 if w.conditional(p, 0xd000)? != p.checked_add(6)? {
292 return None;
293 }
294 if w.word(p.checked_add(2)?)? != 0x2806 {
295 return None;
296 }
297 let reject = w.conditional(p.checked_add(4)?, 0xd100)?;
298 p = p.checked_add(6)?;
299 if w.word(p)? != 0x7848 || w.word(p.checked_add(2)?)? != 0x2806 {
300 return None;
301 }
302 let stream = w.conditional(p.checked_add(4)?, 0xd100)?;
303 p = p.checked_add(6)?;
304 if w.word(p)? != 0x7888
305 || w.word(p.checked_add(2)?)? != 0x2867
306 || w.conditional(p.checked_add(4)?, 0xd100)? != stream
307 {
308 return None;
309 }
310 p = p.checked_add(6)?;
311 if p != entry {
312 return None;
313 }
314 let template = if preloaded {
315 DUAL_PRELOADED
316 } else {
317 DUAL_LOCAL
318 };
319 if !preloaded && w.literal(entry)? != (0, 0x0400_2040) {
320 return None;
321 }
322 let body = w.bytes(entry, template.len())?;
323 let skip = if preloaded { 0 } else { 1 };
324 if body[skip..] != template[skip..]
325 || entry.checked_add(u32::try_from(template.len()).ok()?)? != stream
326 {
327 return None;
328 }
329 let mut error = w.branch(reject)?;
332 if preloaded {
333 if w.word(error)? != 0x712f {
334 return None;
335 } error = error.checked_add(2)?;
337 } else {
338 if w.word(error)? != 0x2601 || w.word(error.checked_add(2)?)? != 0x712e {
339 return None;
340 }
341 error = error.checked_add(4)?;
342 }
343 let return_at = entry.checked_add(if preloaded { 44 } else { 46 })?;
344 if w.branch(error)? != return_at {
345 return None;
346 }
347 Some(DualCandidate {
348 decoder: start,
349 entry,
350 stream,
351 })
352}
353
354pub fn inspect_boot_dual(bytes: &[u8], address: u32) -> Option<DualCandidate> {
358 let w = Window {
359 bytes,
360 base: address,
361 };
362 let mut found = None;
363 for index in (0..bytes.len()).step_by(2) {
364 let at = address.checked_add(u32::try_from(index).ok()?)?;
365 if at & 1 != 0 {
366 continue;
367 }
368 if let Some(hit) = dual_candidate(&w, at) {
369 if found.is_some() {
370 return None;
371 }
372 found = Some(hit);
373 }
374 }
375 found
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381
382 fn v9_window() -> std::vec::Vec<u8> {
385 let mut bytes = std::vec![0xff; 0x600];
386 for (address, hex) in [
387 (0x352a, "e548f0b5827889b00127e349002a72d142781207120f42704278e04e012a49d0022a01d0062af2d1"),
388 (0x35de, "b16909090901b161417ac90702d0b1693943b161417a890703d5b16902221143b161417a490703d5b16904221143b161407a000703d5b06908210843b06109b0f0bd"),
389 (0x38c0, "fc0d0002800d000240200004"),
390 ] {
391 for i in (0..hex.len()).step_by(2) {
392 bytes[address - 0x3400 + i / 2] = u8::from_str_radix(&hex[i..i+2], 16).unwrap();
393 }
394 }
395 bytes
396 }
397
398 #[test]
399 fn actual_v9_entry_has_fixed_operands_and_bounded_literals() {
400 let bytes = v9_window();
401 assert_eq!(
402 inspect_boot_legacy(&bytes, 0x3400),
403 Some(LegacyCandidate {
404 decoder: 0x353a,
405 entry: 0x35de,
406 end: 0x3620,
407 })
408 );
409 for length in 0..0x4cc {
410 assert!(inspect_boot_legacy(&bytes[..length], 0x3400).is_none());
411 }
412 for address in [
413 0x352e, 0x3532, 0x3536, 0x3546, 0x3548, 0x35de, 0x35e6, 0x35e8, 0x35ea, 0x35ee, 0x361a,
414 0x38c8,
415 ] {
416 let mut changed = bytes.clone();
417 changed[address - 0x3400] ^= 1;
418 assert!(
419 inspect_boot_legacy(&changed, 0x3400).is_none(),
420 "{address:#x}"
421 );
422 }
423 }
424
425 #[test]
426 fn ambiguity_alignment_and_address_overflow_never_select_a_recipe() {
427 let bytes = v9_window();
428 let mut doubled = bytes.clone();
429 doubled.extend_from_slice(&bytes);
430 assert!(inspect_boot_legacy(&doubled, 0x3400).is_none());
431 assert!(inspect_boot_legacy(&bytes, 0x3401).is_none());
432 assert!(inspect_boot_legacy(&bytes, u32::MAX - 16).is_none());
433 }
434
435 #[test]
436 fn both_real_dual_entry_compiler_forms_bind_modes_and_flag_paths() {
437 for (prefix, literal_at, literals, entry, body) in [
438 ("de49f0b5487885b00007000f4870487808220423022401280bd0d74d0a3d022801d0062879d14878062824d18878672821d1d348", 0x38bc,
439 "8a0d0002fc0d000240200004", 0x3576, DUAL_LOCAL),
440 ("dd49f0b5487889b00007000f487048780822042302240127d84e01280bd0d54d0a3d022801d006286cd14878062822d1887867281fd1", 0x38b8,
441 "8a0d0002fc0d000240200004", 0x357a, DUAL_PRELOADED),
442 ] {
443 let mut bytes = std::vec![0xff; 0x600];
444 for (address, hex) in [(0x3544, prefix), (literal_at, literals)] {
445 for i in (0..hex.len()).step_by(2) {
446 bytes[address - 0x3400 + i / 2] = u8::from_str_radix(&hex[i..i+2], 16).unwrap();
447 }
448 }
449 for (address, hex) in if entry == 0x3576 {
450 [(0x365e, "7ce0"), (0x375a, "01262e7121e7")]
451 } else {
452 [(0x3648, "dbe0"), (0x3802, "2f71cfe6")]
453 } {
454 for i in (0..hex.len()).step_by(2) {
455 bytes[address - 0x3400 + i / 2] = u8::from_str_radix(&hex[i..i+2], 16).unwrap();
456 }
457 }
458 bytes[entry-0x3400..entry-0x3400+body.len()].copy_from_slice(body);
459 if entry == 0x3576 { bytes[entry-0x3400] = 0xd3; }
460 assert_eq!(inspect_boot_dual(&bytes, 0x3400), Some(DualCandidate {
461 decoder: 0x3544, entry: entry as u32, stream: 0x35ba,
462 }));
463 assert!(inspect_boot_legacy(&bytes, 0x3400).is_none());
464 for address in 0x3554..0x35ba {
465 if address == entry { continue; }
468 let mut changed = bytes.clone(); changed[address-0x3400] ^= 1;
469 assert!(inspect_boot_dual(&changed, 0x3400).is_none(), "{entry:#x}: {address:#x}");
470 }
471 let mut doubled = bytes.clone(); doubled.extend_from_slice(&bytes);
472 assert!(inspect_boot_dual(&doubled, 0x3400).is_none());
473 assert!(inspect_boot_dual(&bytes, u32::MAX - 16).is_none());
474 }
475 }
476}