1use anyhow::{bail, Context, Result};
8use serde::Deserialize;
9use std::collections::BTreeMap;
10use std::path::Path;
11
12#[derive(Debug, Clone, Deserialize)]
13#[serde(deny_unknown_fields)]
14pub struct ChipLibrary {
15 pub name: String,
16 #[serde(default)]
19 pub status: crate::Status,
20 #[serde(default)]
22 pub vendor: Option<String>,
23 #[serde(default)]
25 pub datasheet: Option<String>,
26 pub family_id: u16,
27 pub sub_id: Option<u16>,
28 pub serial_clock: u16,
30 pub chip_control: [u8; 20],
33 #[serde(default)]
36 pub order: Vec<u8>,
37 #[serde(default, deserialize_with = "hex_keys")]
39 pub registers: BTreeMap<u8, [u8; 3]>,
40 pub chip_custom: Option<[u8; 16]>,
44 pub chip_custom_scan_patch: Option<ScanPatch>,
47 pub chip_custom_ex: Option<[u8; 4]>,
49 #[serde(default = "default_true")]
52 pub emit_record_84: bool,
53 pub gray_bits: Option<u8>,
56 #[serde(default, deserialize_with = "record01_offsets")]
59 pub record01_overrides: BTreeMap<usize, u8>,
60}
61
62fn hex_keys<'de, D, K, V>(d: D) -> std::result::Result<BTreeMap<K, V>, D::Error>
65where
66 D: serde::Deserializer<'de>,
67 K: HexKey,
68 V: serde::Deserialize<'de>,
69{
70 let raw: BTreeMap<String, V> = serde::Deserialize::deserialize(d)?;
71 let mut out = BTreeMap::new();
72 for (key, value) in raw {
73 let k = K::parse_hex(&key).map_err(serde::de::Error::custom)?;
74 if out.insert(k, value).is_some() {
75 return Err(serde::de::Error::custom(format!(
76 "key {key} given twice (spelled differently)"
77 )));
78 }
79 }
80 Ok(out)
81}
82
83pub(crate) fn record01_offsets<'de, D>(d: D) -> std::result::Result<BTreeMap<usize, u8>, D::Error>
85where
86 D: serde::Deserializer<'de>,
87{
88 let map: BTreeMap<usize, u8> = hex_keys(d)?;
89 if let Some(at) = map.keys().find(|&&at| at >= crate::RECORD01_LEN) {
90 return Err(serde::de::Error::custom(format!(
91 "record01_overrides offset {at:#05x} is past the record"
92 )));
93 }
94 Ok(map)
95}
96
97pub(crate) fn hex_offsets<S>(map: &BTreeMap<usize, u8>, s: S) -> std::result::Result<S::Ok, S::Error>
99where
100 S: serde::Serializer,
101{
102 s.collect_map(map.iter().map(|(at, v)| (format!("{at:#05X}"), v)))
103}
104
105pub(crate) trait HexKey: Ord {
106 fn parse_hex(s: &str) -> Result<Self>
107 where
108 Self: Sized;
109}
110
111impl HexKey for u8 {
112 fn parse_hex(s: &str) -> Result<Self> {
113 Self::from_str_radix(s.trim_start_matches("0x"), 16)
114 .with_context(|| format!("bad register id {s:?}"))
115 }
116}
117
118impl HexKey for usize {
119 fn parse_hex(s: &str) -> Result<Self> {
120 Self::from_str_radix(s.trim_start_matches("0x"), 16)
121 .with_context(|| format!("bad offset {s:?}"))
122 }
123}
124
125#[derive(Debug, Clone, Deserialize)]
126#[serde(deny_unknown_fields)]
127pub struct ScanPatch {
128 pub bytes: Vec<usize>,
129 pub mask: u8,
130 pub base: u8,
131}
132
133const fn default_true() -> bool {
134 true
135}
136
137impl ChipLibrary {
138 pub fn load(path: impl AsRef<Path>) -> Result<Self> {
141 let path = path.as_ref();
142 let text =
143 std::fs::read_to_string(path).with_context(|| format!("read {}", path.display()))?;
144 Self::parse(&text).with_context(|| format!("parse {}", path.display()))
145 }
146
147 pub fn parse(text: &str) -> Result<Self> {
152 Ok(toml::from_str(text)?)
153 }
154
155 fn reg(&self, id: u8) -> Result<[u8; 3]> {
156 self.registers
157 .get(&id)
158 .copied()
159 .with_context(|| format!("{}: no values for register {id:#04x}", self.name))
160 }
161
162 pub fn record_84(&self, scan: u8) -> Result<Option<[u8; 256]>> {
169 if !self.emit_record_84 || self.order.is_empty() {
170 return Ok(None);
171 }
172 if self.order.len() * 4 > 256 {
173 bail!(
174 "{}: {} registers do not fit a 256-byte record",
175 self.name,
176 self.order.len()
177 );
178 }
179 let mut out = [0u8; 256];
180 let (quads, _) = out.as_chunks_mut::<4>();
182 for (quad, ®) in quads.iter_mut().zip(&self.order) {
183 let rgb = if reg == 0x02 {
184 [scan.wrapping_sub(1) & 0x3F; 3]
185 } else {
186 self.reg(reg)?
187 };
188 quad[0] = reg;
189 quad[1..].copy_from_slice(&rgb);
190 }
191 Ok(Some(out))
192 }
193
194 #[must_use]
197 pub fn chip_custom_block(&self, scan: u8) -> Option<[u8; 16]> {
198 let mut block = self.chip_custom?;
199 if let Some(p) = &self.chip_custom_scan_patch {
200 for &i in &p.bytes {
201 if let Some(b) = block.get_mut(i) {
202 *b = p.base | (scan.wrapping_sub(1) & p.mask);
203 }
204 }
205 }
206 Some(block)
207 }
208
209 pub fn gray_bits(&self) -> Result<u8> {
216 if let Some(g) = self.gray_bits {
217 return Ok(g);
218 }
219 let r07 = self.reg(0x07)?[0];
220 let r03 = self.reg(0x03)?[0];
221 let g = 128u32 << ((r07 >> 3) & 3);
222 let m = if r03 < 0x40 { 64 } else { 32 };
223 Ok(match m * g {
224 x if x < 0x1000 => 12,
225 x if x < 0x2000 => 13,
226 x if x < 0x4000 => 14,
227 x if x < 0x8000 => 15,
228 _ => 16,
229 })
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 fn lib() -> ChipLibrary {
238 toml::from_str(
239 r#"
240 name = "t"
241 family_id = 1
242 serial_clock = 15
243 chip_control = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
244 order = [0x02, 0x03, 0x07, 0xf0]
245 [registers]
246 0x02 = [0x3f, 0x3f, 0x3f]
247 0x03 = [0x3f, 0x3f, 0x3f]
248 0x07 = [0x04, 0x04, 0x04]
249 0xf0 = [4, 5, 6]
250 "#,
251 )
252 .unwrap()
253 }
254
255 #[test]
256 fn quads_land_in_order_with_scan_patch_and_zero_fill() {
257 let r = lib().record_84(16).unwrap().unwrap();
258 assert_eq!(&r[..4], &[0x02, 15, 15, 15], "reg 0x02 = scan - 1");
259 assert_eq!(&r[12..16], &[0xf0, 4, 5, 6]);
260 assert!(r[16..].iter().all(|&b| b == 0));
261 }
262
263 #[test]
264 fn hex_keys_are_typed_and_range_checked_at_load() {
265 let lib: ChipLibrary = toml::from_str(
266 r#"
267 name = "t"
268 family_id = 1
269 serial_clock = 15
270 chip_control = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
271 [registers]
272 0x2 = [1, 2, 3]
273 [record01_overrides]
274 "0x02F" = 1
275 "0x2fb" = 2
276 "#,
277 )
278 .unwrap();
279 assert_eq!(lib.registers.get(&2), Some(&[1, 2, 3]));
280 assert_eq!(
281 lib.record01_overrides.iter().collect::<Vec<_>>(),
282 vec![(&0x2F, &1), (&0x2FB, &2)]
283 );
284
285 let past: std::result::Result<ChipLibrary, _> = toml::from_str(
286 "name = \"t\"\nfamily_id = 1\nserial_clock = 1\nchip_control = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]\n[record01_overrides]\n\"0x2FC\" = 1\n",
287 );
288 assert!(past.unwrap_err().to_string().contains("past the record"));
289 let twice: std::result::Result<ChipLibrary, _> = toml::from_str(
290 "name = \"t\"\nfamily_id = 1\nserial_clock = 1\nchip_control = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]\n[record01_overrides]\n\"0x02F\" = 1\n\"0x2F\" = 0\n",
291 );
292 assert!(twice.unwrap_err().to_string().contains("twice"));
293 }
294
295 #[test]
296 fn gray_bits_follow_the_vendor_formula() {
297 assert_eq!(lib().gray_bits().unwrap(), 14); }
299}