pcitool/misc/
pnp.rs

1/*!
2# Plug and Play Resource Data Types
3
4Plug and Play resource data fully describes all resource requirements of a Plug and Play ISA card as well as
5resource programmability and interdependencies. Plug and Play resource data is supplied as a series of
6“tagged” data structures.
7*/
8
9use heterob::{
10    bit_numbering::Lsb,
11    endianness::{Le, LeBytesTryInto},
12    Seq, P2, P3, U8,
13};
14
15/// An iterator over Plug and Play resources
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct PlugAndPlayResource<'a> {
18    data: &'a [u8],
19}
20
21impl<'a> PlugAndPlayResource<'a> {
22    pub fn new(data: &'a [u8]) -> Self {
23        Self { data }
24    }
25}
26
27impl<'a> Iterator for PlugAndPlayResource<'a> {
28    type Item = Resource<'a>;
29
30    fn next(&mut self) -> Option<Self::Item> {
31        let (first, data) = self.data.split_first()?;
32        let Lsb((large_item_name, is_large_item)) = P2::<_, 7, 1>(*first).into();
33        let Lsb((small_item_len, small_item_name)) = P2::<u8, 3, 4>(large_item_name).into();
34        let _: u8 = small_item_len;
35        let (resource, tail) = if is_large_item {
36            let Seq {
37                head: large_item_len,
38                tail,
39            } = data.le_bytes_try_into().ok()?;
40            if tail.len() < large_item_len as usize {
41                return None;
42            }
43            let (data, tail) = tail.split_at(large_item_len as usize);
44            let item = match large_item_name {
45                0x01 => LargeItem::MemoryRangeDescriptor,
46                0x02 => LargeItem::IdentifierStringAnsi(std::str::from_utf8(data).ok()?),
47                0x03 => LargeItem::IdentifierStringUnicode(std::str::from_utf8(data).ok()?),
48                0x04 => LargeItem::VendorDefined,
49                0x05 => LargeItem::MemoryRangeDescriptor32bit,
50                0x06 => LargeItem::FixedLocationMemoryRangeDescriptor32bit,
51                0x10 => LargeItem::VitalProductDataRo(VitalProductDataRo::new(data)),
52                0x11 => LargeItem::VitalProductDataRw(VitalProductDataRw::new(data)),
53                v => LargeItem::Reserved(v),
54            };
55            (
56                Resource::Large(Large {
57                    item,
58                    length: large_item_len,
59                }),
60                tail,
61            )
62        } else {
63            if data.len() < small_item_len as usize {
64                return None;
65            }
66            let (_data, tail) = data.split_at(small_item_len as usize);
67            let item = match small_item_name {
68                0x01 => SmallItem::PlugAndPlayVersionNumber,
69                0x02 => SmallItem::LogicalDeviceId,
70                0x03 => SmallItem::CompatibleDeviceId,
71                0x04 => SmallItem::IrqFormat,
72                0x05 => SmallItem::DmaFormat,
73                0x06 => SmallItem::StartDependentFunction,
74                0x07 => SmallItem::EndDependentFunction,
75                0x08 => SmallItem::IoPortDescriptor,
76                0x09 => SmallItem::FixedLocationIoPortDescriptor,
77                0x0E => SmallItem::VendorDefined,
78                0x0F => SmallItem::End,
79                v => SmallItem::Reserved(v),
80            };
81            (
82                Resource::Small(Small {
83                    item,
84                    length: small_item_len,
85                }),
86                tail,
87            )
88        };
89        self.data = tail;
90        Some(resource)
91    }
92}
93
94/// To minimize the amount of storage needed on Plug and Play ISA cards two different
95/// data types are supported. These are called small items and large items.
96#[derive(Debug, Clone, PartialEq, Eq)]
97pub enum Resource<'a> {
98    Small(Small),
99    Large(Large<'a>),
100}
101
102/// Small Resource Data Type
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct Small {
105    pub item: SmallItem,
106    pub length: u8,
107}
108
109/// Small information items
110#[derive(Debug, Clone, PartialEq, Eq)]
111pub enum SmallItem {
112    /// Plug and Play version number
113    PlugAndPlayVersionNumber,
114    /// Logical device ID
115    LogicalDeviceId,
116    /// Compatible device ID
117    CompatibleDeviceId,
118    /// IRQ format
119    IrqFormat,
120    /// DMA format
121    DmaFormat,
122    /// Start dependent Function
123    StartDependentFunction,
124    /// End dependent Function
125    EndDependentFunction,
126    /// I/O port descriptor
127    IoPortDescriptor,
128    /// Fixed location I/O port descriptor
129    FixedLocationIoPortDescriptor,
130    /// Reserved
131    Reserved(u8),
132    /// Vendor defined
133    VendorDefined,
134    /// End tag
135    End,
136}
137
138impl SmallItem {
139    pub fn value(&self) -> u8 {
140        match self {
141            SmallItem::PlugAndPlayVersionNumber => 0x01,
142            SmallItem::LogicalDeviceId => 0x02,
143            SmallItem::CompatibleDeviceId => 0x03,
144            SmallItem::IrqFormat => 0x04,
145            SmallItem::DmaFormat => 0x05,
146            SmallItem::StartDependentFunction => 0x06,
147            SmallItem::EndDependentFunction => 0x07,
148            SmallItem::IoPortDescriptor => 0x08,
149            SmallItem::FixedLocationIoPortDescriptor => 0x09,
150            SmallItem::VendorDefined => 0x0E,
151            SmallItem::End => 0x0F,
152            SmallItem::Reserved(v) => *v,
153        }
154    }
155}
156
157/// Large Resource Data Type
158#[derive(Debug, Clone, PartialEq, Eq)]
159pub struct Large<'a> {
160    pub item: LargeItem<'a>,
161    pub length: u16,
162}
163
164/// Large information items
165#[derive(Debug, Clone, PartialEq, Eq)]
166pub enum LargeItem<'a> {
167    /// Memory range descriptor
168    MemoryRangeDescriptor,
169    /// Identifier string (ANSI)
170    IdentifierStringAnsi(&'a str),
171    /// Identifier string (Unicode)
172    IdentifierStringUnicode(&'a str),
173    /// Vendor defined
174    VendorDefined,
175    /// 32-bit memory range descriptor
176    MemoryRangeDescriptor32bit,
177    /// 32-bit fixed location memory range descriptor
178    FixedLocationMemoryRangeDescriptor32bit,
179    /// Read-only Vital Product Data
180    VitalProductDataRo(VitalProductDataRo<'a>),
181    /// Read-write Vital Product Data
182    VitalProductDataRw(VitalProductDataRw<'a>),
183    /// Reserved
184    Reserved(u8),
185}
186
187impl<'a> LargeItem<'a> {
188    pub fn value(&self) -> u8 {
189        match self {
190            LargeItem::MemoryRangeDescriptor => 0x01,
191            LargeItem::IdentifierStringAnsi(_) => 0x02,
192            LargeItem::IdentifierStringUnicode(_) => 0x03,
193            LargeItem::VendorDefined => 0x04,
194            LargeItem::MemoryRangeDescriptor32bit => 0x05,
195            LargeItem::FixedLocationMemoryRangeDescriptor32bit => 0x06,
196            LargeItem::VitalProductDataRo(_) => 0x10,
197            LargeItem::VitalProductDataRw(_) => 0x11,
198            LargeItem::Reserved(v) => *v,
199        }
200    }
201}
202
203/// An iterator over read-only VPD resources
204#[derive(Debug, Clone, PartialEq, Eq)]
205pub struct VitalProductDataRo<'a> {
206    data: &'a [u8],
207}
208
209impl<'a> VitalProductDataRo<'a> {
210    pub fn new(data: &'a [u8]) -> Self {
211        Self { data }
212    }
213}
214
215impl<'a> Iterator for VitalProductDataRo<'a> {
216    type Item = VpdRoResource<'a>;
217
218    fn next(&mut self) -> Option<Self::Item> {
219        let Seq {
220            head: Le((U8(k0), U8(k1), U8(len))),
221            tail,
222        } = P3(self.data).try_into().ok()?;
223        if tail.len() < len {
224            return None;
225        }
226        let (data, tail) = tail.split_at(len);
227        self.data = tail;
228        let result = match (k0, k1) {
229            ('P', 'N') => VpdRoResource::PartNumber(std::str::from_utf8(data).ok()?),
230            ('E', 'C') => VpdRoResource::EngineeringChange(std::str::from_utf8(data).ok()?),
231            ('F', 'G') => VpdRoResource::FabricGeography(std::str::from_utf8(data).ok()?),
232            ('L', 'C') => VpdRoResource::Location(std::str::from_utf8(data).ok()?),
233            ('M', 'N') => VpdRoResource::ManufactureId(std::str::from_utf8(data).ok()?),
234            ('P', 'G') => VpdRoResource::PciGeography(std::str::from_utf8(data).ok()?),
235            ('S', 'N') => VpdRoResource::SerialNumber(std::str::from_utf8(data).ok()?),
236            ('V', x) => VpdRoResource::VendorSpecific(x, std::str::from_utf8(data).ok()?),
237            ('C', 'P') => {
238                let Seq {
239                    head: Le((cap_id, bar_index, bar_offset)),
240                    ..
241                } = P3(data).try_into().ok()?;
242                VpdRoResource::ExtendedCapability {
243                    cap_id,
244                    bar_index,
245                    bar_offset,
246                }
247            }
248            ('R', 'V') => {
249                let Seq {
250                    head: checksum,
251                    tail: reserved,
252                } = data.le_bytes_try_into().ok()?;
253                VpdRoResource::ChecksumAndReserved { checksum, reserved }
254            }
255            (k0, k1) => VpdRoResource::Unknown {
256                k0: k0 as u8,
257                k1: k1 as u8,
258                len: len as u8,
259                data,
260            },
261        };
262        Some(result)
263    }
264}
265/// VPD read-only Fields
266#[derive(Debug, Clone, PartialEq, Eq)]
267pub enum VpdRoResource<'a> {
268    /// PN Add-in Card Part Number
269    PartNumber(&'a str),
270    /// EC Engineering Change Level of the Add-in Card
271    EngineeringChange(&'a str),
272    /// FG Fabric Geography
273    FabricGeography(&'a str),
274    /// LC Location
275    Location(&'a str),
276    /// MN Manufacture ID
277    ManufactureId(&'a str),
278    /// PG PCI Geography
279    PciGeography(&'a str),
280    /// SN Serial Number
281    SerialNumber(&'a str),
282    /// Vx Vendor Specific
283    VendorSpecific(char, &'a str),
284    /// CP Extended Capability
285    ExtendedCapability {
286        cap_id: u8,
287        bar_index: u8,
288        bar_offset: u16,
289    },
290    /// RV Checksum and Reserved
291    ChecksumAndReserved { checksum: u8, reserved: &'a [u8] },
292    /// Unknown
293    Unknown {
294        k0: u8,
295        k1: u8,
296        len: u8,
297        data: &'a [u8],
298    },
299}
300
301/// An iterator over read-only VPD resources
302#[derive(Debug, Clone, PartialEq, Eq)]
303pub struct VitalProductDataRw<'a> {
304    data: &'a [u8],
305}
306
307impl<'a> VitalProductDataRw<'a> {
308    pub fn new(data: &'a [u8]) -> Self {
309        Self { data }
310    }
311}
312
313impl<'a> Iterator for VitalProductDataRw<'a> {
314    type Item = VpdRwResource<'a>;
315
316    fn next(&mut self) -> Option<Self::Item> {
317        let Seq {
318            head: Le((U8(k0), U8(k1), U8(len))),
319            tail,
320        } = P3(self.data).try_into().ok()?;
321        if tail.len() < len {
322            return None;
323        }
324        let (data, tail) = tail.split_at(len);
325        self.data = tail;
326        let result = match (k0, k1) {
327            ('V', x) => VpdRwResource::VendorSpecific(x, std::str::from_utf8(data).ok()?),
328            ('Y', 'A') => VpdRwResource::AssetTagIdentifier(std::str::from_utf8(data).ok()?),
329            ('Y', x) => VpdRwResource::SystemSpecific(x, data),
330            _ => VpdRwResource::RemainingRwArea(data),
331        };
332        Some(result)
333    }
334}
335
336/// VPD read-write fields
337#[derive(Debug, Clone, PartialEq, Eq)]
338pub enum VpdRwResource<'a> {
339    /// Vx Vendor Specific
340    VendorSpecific(char, &'a str),
341    /// Yx System Specific
342    SystemSpecific(char, &'a [u8]),
343    /// YA Asset Tag Identifier
344    AssetTagIdentifier(&'a str),
345    /// RW Remaining Read/Write Area
346    RemainingRwArea(&'a [u8]),
347}
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352    use pretty_assertions::assert_eq;
353
354    #[test]
355    fn vital_product_data_ro() {
356        let data = [
357            b"PN",
358            [0x08].as_slice(),
359            b"6181682A",
360            b"EC",
361            [0x0A].as_slice(),
362            b"4950262536",
363            b"SN",
364            [0x08].as_slice(),
365            b"00000194",
366            b"MN",
367            [0x04].as_slice(),
368            b"1037",
369            b"RV",
370            [0x2C].as_slice(),
371            [0xFF].as_slice(), // Checksum
372            [0x00; 128 - 85].as_slice(),
373        ]
374        .concat();
375
376        let sample_vpd_r = vec![
377            VpdRoResource::PartNumber("6181682A"),
378            VpdRoResource::EngineeringChange("4950262536"),
379            VpdRoResource::SerialNumber("00000194"),
380            VpdRoResource::ManufactureId("1037"),
381            VpdRoResource::ChecksumAndReserved {
382                checksum: 0xFF,
383                reserved: &[0x00; 0x2c - 1],
384            },
385        ];
386        let result_vpd_r = VitalProductDataRo::new(&data);
387        assert_eq!(sample_vpd_r, result_vpd_r.collect::<Vec<_>>());
388    }
389
390    #[test]
391    fn vital_product_data_rw() {
392        let data = [
393            b"V1",
394            [0x05].as_slice(),
395            b"65A01",
396            b"Y1",
397            [0x0D].as_slice(),
398            b"Error Code 26",
399            b"RW",
400            [0x61].as_slice(),
401            [0x00; 255 - 158].as_slice(),
402        ]
403        .concat();
404
405        let sample_vpd_w = vec![
406            VpdRwResource::VendorSpecific('1', "65A01"),
407            VpdRwResource::SystemSpecific('1', b"Error Code 26"),
408            VpdRwResource::RemainingRwArea(&[0; 0x61]),
409        ];
410        let result_vpd_w = VitalProductDataRw::new(&data);
411        assert_eq!(sample_vpd_w, result_vpd_w.clone().collect::<Vec<_>>());
412    }
413
414    #[test]
415    fn plug_and_play_resource() {
416        let data = [
417            [0x82].as_slice(),
418            &0x0021u16.to_le_bytes(),
419            b"ABCD Super-Fast Widget Controller",
420            [0x90].as_slice(),
421            &0x0059u16.to_le_bytes(),
422            b"PN",
423            [0x08].as_slice(),
424            b"6181682A",
425            b"EC",
426            [0x0A].as_slice(),
427            b"4950262536",
428            b"SN",
429            [0x08].as_slice(),
430            b"00000194",
431            b"MN",
432            [0x04].as_slice(),
433            b"1037",
434            b"RV",
435            [0x2C].as_slice(),
436            [0xFF].as_slice(), // Checksum
437            [0x00; 128 - 85].as_slice(),
438            [0x91].as_slice(),
439            &0x007Cu16.to_le_bytes(),
440            b"V1",
441            [0x05].as_slice(),
442            b"65A01",
443            b"Y1",
444            [0x0D].as_slice(),
445            b"Error Code 26",
446            b"RW",
447            [0x61].as_slice(),
448            [0x00; 255 - 158].as_slice(),
449            [0x78].as_slice(),
450        ]
451        .concat();
452
453        let sample = vec![
454            Resource::Large(Large {
455                item: LargeItem::IdentifierStringAnsi("ABCD Super-Fast Widget Controller"),
456                length: 0x0021,
457            }),
458            Resource::Large(Large {
459                item: LargeItem::VitalProductDataRo(VitalProductDataRo::new(&data[39..128])),
460                length: 0x0059,
461            }),
462            Resource::Large(Large {
463                item: LargeItem::VitalProductDataRw(VitalProductDataRw::new(&data[131..255])),
464                length: 0x007C,
465            }),
466            Resource::Small(Small {
467                item: SmallItem::End,
468                length: 0x00,
469            }),
470        ];
471        let result = PlugAndPlayResource::new(&data);
472        assert_eq!(sample, result.clone().collect::<Vec<_>>(), "{:x?}", result);
473    }
474}