devices/
fw_cfg.rs

1// Copyright 2023 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! fw_cfg device implementing QEMU's Firmware Configuration interface
6//! <https://www.qemu.org/docs/master/specs/fw_cfg.html>
7
8use std::collections::HashSet;
9use std::fs;
10use std::path::PathBuf;
11
12#[cfg(windows)]
13use base::error;
14use serde::Deserialize;
15use serde::Serialize;
16use serde_keyvalue::FromKeyValues;
17use thiserror::Error as ThisError;
18
19use crate::BusAccessInfo;
20use crate::BusDevice;
21use crate::DeviceId;
22use crate::Suspendable;
23
24pub const FW_CFG_BASE_PORT: u64 = 0x510;
25pub const FW_CFG_WIDTH: u64 = 0x4;
26// For the 16-bit selector, the 2nd highest-order bit represents whether the data port will be read
27// or written to. Because this has been deprecrated by Qemu, this bit is useless. The highest order
28// bit represents whether the selected configuration item is arch-specific. Therefore, only the
29// lower 14 bits are used for indexing and we mask the two highest bits off with
30// FW_CFG_SELECTOR_SELECT_MASK. 16384 = 2^14.
31pub const FW_CFG_MAX_FILE_SLOTS: usize = 16384 - FW_CFG_FILE_FIRST;
32const FW_CFG_FILE_FIRST: usize = 0x0020;
33const FW_CFG_SELECTOR_PORT_OFFSET: u64 = 0x0;
34const FW_CFG_DATA_PORT_OFFSET: u64 = 0x1;
35const FW_CFG_SELECTOR_RW_MASK: u16 = 0x2000;
36const FW_CFG_SELECTOR_ARCH_MASK: u16 = 0x4000;
37const FW_CFG_SELECTOR_SELECT_MASK: u16 = 0xbfff;
38const FW_CFG_SIGNATURE: [u8; 4] = [b'Q', b'E', b'M', b'U'];
39const FW_CFG_REVISION: [u8; 4] = [0, 0, 0, 1];
40const FW_CFG_SIGNATURE_SELECTOR: u16 = 0x0000;
41const FW_CFG_REVISION_SELECTOR: u16 = 0x0001;
42const FW_CFG_FILE_DIR_SELECTOR: u16 = 0x0019;
43// Code that uses fw_cfg expects to read a char[56] for filenames
44const FW_CFG_FILENAME_SIZE: usize = 56;
45
46#[derive(ThisError, Debug)]
47pub enum Error {
48    #[error("Ran out of file slots")]
49    InsufficientFileSlots,
50
51    #[error("File already exists")]
52    FileAlreadyExists,
53
54    #[error("Data blob's size too large: overflowed u32")]
55    SizeOverflow,
56
57    #[error("too many entries: oveflows u16 selector")]
58    IndexOverflow,
59
60    #[error("Filename must be less than 55 characters long")]
61    FileNameTooLong,
62
63    #[error("Unable to open file {0} for fw_cfg: {1}")]
64    FileOpen(PathBuf, std::io::Error),
65
66    #[error("fw_cfg parameters must have exactly one of string or path")]
67    StringOrPathRequired,
68}
69
70pub type Result<T> = std::result::Result<T, Error>;
71
72#[derive(Clone, Debug, Deserialize, Serialize, FromKeyValues, PartialEq, Eq)]
73#[serde(deny_unknown_fields, rename_all = "kebab-case")]
74pub struct FwCfgParameters {
75    pub name: String,
76    pub string: Option<String>,
77    pub path: Option<PathBuf>,
78}
79
80#[derive(PartialEq)]
81pub enum FwCfgItemType {
82    GenericItem,
83    ArchSpecificItem,
84    FileDir,
85    Signature,
86    RevisionVector,
87}
88
89impl FwCfgItemType {
90    fn value(&self) -> usize {
91        match self {
92            FwCfgItemType::ArchSpecificItem => 1,
93            _ => 0,
94        }
95    }
96}
97
98// Contains metadata about the entries stored in fw_cfg.
99// FwCfgFile is exposed to the the guest
100// so that the guest may search for the entry
101// with the desired filename and obtain its 16-bit-wide select
102// key to write to the control register
103struct FwCfgFile {
104    pub size: u32,
105    pub select: u16,
106    pub name: String,
107}
108
109// Contains the actual data. The data is represented as an
110// array of u8 to conviently pass
111// a data item byte-by-byte when read() is called
112// on that item
113#[derive(Clone)]
114struct FwCfgEntry {
115    pub allow_write: bool,
116    pub data: Vec<u8>,
117}
118
119// Device exposed to the rest of crosvm. Contains state information in addition to arrays of
120// FwCfgEntry and FwCfgFile. cur_entry keeps the index of the currently selected entry. cur_offset
121// keeps the byte offset within cur_entry. Storing cur_offset is neccessary because the data IO port
122// is only 8 bits wide, so a call to read() will only retrieve one 8 bit chunk of data at a time.
123// cur_offset allows for a data item larger than 8 bits to be read through multiple calls to read(),
124// maintaining the position of the current read and incrementing across calls to read().
125pub struct FwCfgDevice {
126    file_slots: usize,
127    // entries[0] holds generic fw_cfg items in addition to special items (file dir, signature, and
128    // revision vector). entries[1] holds arch-specific items.
129    entries: [Vec<FwCfgEntry>; 2],
130    files: Vec<FwCfgFile>,
131    cur_item_type: FwCfgItemType,
132    cur_entry: u16,
133    cur_offset: usize,
134    file_names: HashSet<String>,
135}
136
137impl FwCfgDevice {
138    pub fn new(file_slots: usize, fw_cfg_parameters: Vec<FwCfgParameters>) -> Result<FwCfgDevice> {
139        let mut device = FwCfgDevice {
140            file_slots,
141            entries: [
142                vec![
143                    FwCfgEntry {
144                        allow_write: false,
145                        data: vec![]
146                    };
147                    FW_CFG_FILE_FIRST
148                ],
149                Vec::new(),
150            ],
151            files: Vec::new(),
152            cur_item_type: FwCfgItemType::GenericItem,
153            cur_entry: 0,
154            cur_offset: 0,
155            file_names: HashSet::new(),
156        };
157
158        for param in fw_cfg_parameters {
159            let data = match (&param.string, &param.path) {
160                (Some(string), None) => string.as_bytes().to_vec(),
161                (None, Some(path)) => {
162                    fs::read(path).map_err(|e| Error::FileOpen(path.clone(), e))?
163                }
164                _ => return Err(Error::StringOrPathRequired),
165            };
166
167            // The file added from the command line will be a generic item. QEMU does not
168            // give users the option to specify whether the user-specified blob is
169            // arch-specific, so we won't either.
170            device.add_file(&param.name, data, FwCfgItemType::GenericItem)?
171        }
172
173        device.add_bytes(FW_CFG_SIGNATURE.to_vec(), FwCfgItemType::Signature);
174        device.add_bytes(FW_CFG_REVISION.to_vec(), FwCfgItemType::RevisionVector);
175
176        Ok(device)
177    }
178
179    /// Adds a file to the device.
180    ///
181    /// # Arguments
182    ///
183    /// - `filename`: Name of file. Must be valid a Unix-style filename
184    /// - `data`: File data as bytes
185    pub fn add_file(
186        &mut self,
187        filename: &str,
188        data: Vec<u8>,
189        item_type: FwCfgItemType,
190    ) -> Result<()> {
191        // Adds a data blob to the device under the name filename. This entails creating an
192        // FwCfgEntry and its associated FwCfgFile and adding them to FwCfgDevice.
193
194        if self.files.len() >= FW_CFG_MAX_FILE_SLOTS || self.files.len() >= self.file_slots {
195            return Err(Error::InsufficientFileSlots);
196        }
197
198        if filename.len() > FW_CFG_FILENAME_SIZE - 1 {
199            return Err(Error::FileNameTooLong);
200        }
201
202        // No need to worry about endianess in this function. We will deal with this in read(). We
203        // are only using FwCfgFile internally.
204        let index = self.entries[item_type.value()].len();
205
206        if self.file_names.contains(filename) {
207            return Err(Error::FileAlreadyExists);
208        }
209
210        // Since the size field of an entry is stored as a u32, the largest file that can be stored
211        // in the device is 2^32 - 1 ~ 4GB
212        let size: u32 = data.len().try_into().map_err(|_| Error::SizeOverflow)?;
213
214        let mut select: u16 = (index).try_into().map_err(|_| Error::IndexOverflow)?;
215
216        if item_type == FwCfgItemType::ArchSpecificItem {
217            select |= FW_CFG_SELECTOR_ARCH_MASK;
218        }
219
220        let new_file = FwCfgFile {
221            size,
222            select,
223            name: filename.to_owned(),
224        };
225
226        self.add_bytes(data, item_type);
227        self.files.push(new_file);
228        self.file_names.insert(filename.to_string());
229        // We need to update the file_dir entry every time we insert a new file.
230        self.update_file_dir_entry();
231
232        Ok(())
233    }
234
235    fn add_bytes(&mut self, data: Vec<u8>, item_type: FwCfgItemType) {
236        // Add a FwCfgEntry to FwCfgDevice's entries array
237
238        let new_entry = FwCfgEntry {
239            allow_write: false,
240            data,
241        };
242
243        match item_type {
244            FwCfgItemType::GenericItem | FwCfgItemType::ArchSpecificItem => {
245                self.entries[item_type.value()].push(new_entry)
246            }
247            FwCfgItemType::FileDir => {
248                self.entries[item_type.value()][FW_CFG_FILE_DIR_SELECTOR as usize] = new_entry
249            }
250            FwCfgItemType::Signature => {
251                self.entries[item_type.value()][FW_CFG_SIGNATURE_SELECTOR as usize] = new_entry
252            }
253            FwCfgItemType::RevisionVector => {
254                self.entries[item_type.value()][FW_CFG_REVISION_SELECTOR as usize] = new_entry
255            }
256        }
257    }
258
259    fn update_file_dir_entry(&mut self) {
260        let mut raw_file_dir: Vec<u8> = Vec::new();
261        // casting to u32 should not be problematic. insert_file() assures that there can be no
262        // more than 2^14 items in the device.
263        let files_dir_count = self.files.len() as u32;
264        raw_file_dir.extend_from_slice(&files_dir_count.to_be_bytes());
265
266        for file in &self.files {
267            raw_file_dir.extend_from_slice(&file.size.to_be_bytes());
268            raw_file_dir.extend_from_slice(&file.select.to_be_bytes());
269            // The caller expects a "reserved" field to be present on each FwCfgFile. Since
270            // we always set the field to zero, we don't bother to store it on FwCfgDevice and
271            // return zero unconditionally.
272            raw_file_dir.extend_from_slice(&[0, 0]);
273            raw_file_dir.extend_from_slice(file.name.as_bytes());
274            // Padding for c-style char[]
275            raw_file_dir.extend(std::iter::repeat_n(
276                0,
277                FW_CFG_FILENAME_SIZE - file.name.len(),
278            ));
279        }
280
281        self.add_bytes(raw_file_dir, FwCfgItemType::FileDir);
282    }
283}
284
285// We implement two 8-bit registers: a Selector(Control) Register and a Data Register
286impl BusDevice for FwCfgDevice {
287    fn device_id(&self) -> DeviceId {
288        super::CrosvmDeviceId::FwCfg.into()
289    }
290
291    fn debug_label(&self) -> String {
292        "FwCfg".to_owned()
293    }
294
295    // Read a byte from the FwCfgDevice. The byte read is based on the current state of the device.
296    fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
297        if data.len() != 1 {
298            return;
299        }
300
301        // Attemping to read anything other than the data port is a NOP
302        if info.offset == FW_CFG_DATA_PORT_OFFSET {
303            let entries_index = self.cur_entry as usize;
304            // If the caller attempts to read bytes past the current entry, read returns
305            // zero
306            if self.cur_offset
307                >= self.entries[self.cur_item_type.value()][entries_index]
308                    .data
309                    .len()
310            {
311                data[0] = 0x00;
312                return;
313            }
314            data[0] = self.entries[self.cur_item_type.value()][entries_index].data[self.cur_offset];
315            self.cur_offset += 1;
316        }
317    }
318
319    // Write to the FwCfgDevice. Used to set the select register.
320    fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
321        // Attempting to write to any port other than the data port is a NOP
322        if info.offset == FW_CFG_SELECTOR_PORT_OFFSET {
323            if data.len() != 2 {
324                return;
325            }
326
327            let Ok(selector) = data.try_into().map(u16::from_le_bytes) else {
328                return;
329            };
330
331            self.cur_offset = 0;
332
333            match selector {
334                FW_CFG_FILE_DIR_SELECTOR => {
335                    self.cur_entry = FW_CFG_FILE_DIR_SELECTOR;
336                }
337                FW_CFG_REVISION_SELECTOR => {
338                    self.cur_entry = FW_CFG_REVISION_SELECTOR;
339                }
340                FW_CFG_SIGNATURE_SELECTOR => {
341                    self.cur_entry = FW_CFG_SIGNATURE_SELECTOR;
342                }
343                _ => {
344                    let entries_index = selector as usize;
345
346                    // Checks if the 15th bit is set. The bit indicates whether the fw_cfg item
347                    // selected is archetecture specific.
348                    if (FW_CFG_SELECTOR_ARCH_MASK & selector) > 0 {
349                        self.cur_item_type = FwCfgItemType::ArchSpecificItem;
350                    } else {
351                        self.cur_item_type = FwCfgItemType::GenericItem;
352                    }
353
354                    // Check if the selector key is valid.
355                    if self.entries[self.cur_item_type.value()].len() <= entries_index {
356                        return;
357                    }
358
359                    // Checks if the 14th bit is set. The bit indicates whether the fw_cfg item
360                    // selected is going to be written to or only read via the data port. Since
361                    // writes to the data port have been deprecated as of Qemu v2.4, we don't
362                    // support them either. This code is only included for clarity.
363                    self.entries[self.cur_item_type.value()][entries_index].allow_write =
364                        (FW_CFG_SELECTOR_RW_MASK & selector) > 0;
365
366                    // Checks if the 15th bit is set. The bit indicates whether the fw_cfg item
367                    // selected is archetecture specific.
368                    if (FW_CFG_SELECTOR_ARCH_MASK & selector) > 0 {
369                        self.cur_item_type = FwCfgItemType::ArchSpecificItem;
370                    } else {
371                        self.cur_item_type = FwCfgItemType::GenericItem;
372                    }
373
374                    // Only the lower 14 bits are used for actual indexing. The 14th bit
375                    // determines whether the data item will be written to or only read
376                    // from the data port. The 15th bit determines whether the selected
377                    // configuration item is architecture specific. Therefore, we mask the 14th
378                    // and 15th bit off.
379                    self.cur_entry = selector & FW_CFG_SELECTOR_SELECT_MASK;
380                }
381            }
382        }
383    }
384}
385
386impl Suspendable for FwCfgDevice {
387    fn sleep(&mut self) -> anyhow::Result<()> {
388        Ok(())
389    }
390
391    fn wake(&mut self) -> anyhow::Result<()> {
392        Ok(())
393    }
394}
395
396#[cfg(test)]
397mod tests {
398    use serde_keyvalue::*;
399
400    use super::*;
401    use crate::FW_CFG_BASE_PORT;
402
403    const MAGIC_BYTE: u8 = 111;
404    const MAGIC_BYTE_ALT: u8 = 222;
405    const FILENAME: &str = "/test/device/crosvmval";
406    const FILENAMES: [&str; 6] = [
407        "test/hello.txt",
408        "user/data/mydata.txt",
409        "bruschetta/user/foo",
410        "valid_unix/me/home/dir/back.txt",
411        "/dev/null",
412        "google/unix/sys/maple.txt",
413    ];
414
415    fn default_params() -> Vec<FwCfgParameters> {
416        Vec::new()
417    }
418
419    fn get_contents() -> [Vec<u8>; 6] {
420        [
421            b"CROSVM".to_vec(),
422            b"GOOGLE".to_vec(),
423            b"FWCONFIG".to_vec(),
424            b"PIZZA".to_vec(),
425            b"CHROMEOS".to_vec(),
426            b"42".to_vec(),
427        ]
428    }
429
430    fn make_device(
431        filenames: &[&str],
432        contents: &[Vec<u8>],
433        params: &[FwCfgParameters],
434        file_slots: &usize,
435    ) -> Result<FwCfgDevice> {
436        let mut device = FwCfgDevice::new(*file_slots, params.to_owned())?;
437        let count = filenames.len();
438
439        for i in 0..count {
440            device.add_file(
441                filenames[i],
442                contents[i].clone(),
443                FwCfgItemType::GenericItem,
444            )?;
445        }
446
447        Ok(device)
448    }
449
450    fn from_fw_cfg_arg(options: &str) -> std::result::Result<FwCfgParameters, ParseError> {
451        from_key_values(options)
452    }
453
454    fn read_u32(device: &mut FwCfgDevice, bai: BusAccessInfo, data: &mut [u8]) -> u32 {
455        let mut bytes: [u8; 4] = [0, 0, 0, 0];
456        device.read(bai, data);
457        bytes[0] = data[0];
458        device.read(bai, data);
459        bytes[1] = data[0];
460        device.read(bai, data);
461        bytes[2] = data[0];
462        device.read(bai, data);
463        bytes[3] = data[0];
464
465        u32::from_be_bytes(bytes)
466    }
467
468    fn read_u16(device: &mut FwCfgDevice, bai: BusAccessInfo, data: &mut [u8]) -> u16 {
469        let mut bytes: [u8; 2] = [0, 0];
470        device.read(bai, data);
471        bytes[0] = data[0];
472        device.read(bai, data);
473        bytes[1] = data[0];
474
475        u16::from_be_bytes(bytes)
476    }
477
478    fn read_u8(device: &mut FwCfgDevice, bai: BusAccessInfo, data: &mut [u8]) -> u8 {
479        let mut bytes: [u8; 1] = [0];
480        device.read(bai, data);
481        bytes[0] = data[0];
482        u8::from_be_bytes(bytes)
483    }
484
485    fn read_char_56(device: &mut FwCfgDevice, bai: BusAccessInfo, data: &mut [u8]) -> String {
486        let mut c: char = read_u8(device, bai, data) as char;
487        let mut count = 1; //start at 1 b/c called read_u8 above
488        let mut name: String = String::new();
489        while c != '\0' {
490            name.push(c);
491            c = read_u8(device, bai, data) as char;
492            count += 1;
493        }
494        while count < FW_CFG_FILENAME_SIZE {
495            read_u8(device, bai, data);
496            count += 1;
497        }
498        name
499    }
500
501    fn get_entry(
502        device: &mut FwCfgDevice,
503        mut bai: BusAccessInfo,
504        size: usize,
505        selector: u16,
506    ) -> Vec<u8> {
507        let mut data: Vec<u8> = vec![0];
508        let mut blob: Vec<u8> = Vec::new();
509
510        bai.address = FW_CFG_BASE_PORT;
511        bai.offset = FW_CFG_SELECTOR_PORT_OFFSET;
512        let selector: [u8; 2] = selector.to_le_bytes();
513        device.write(bai, &selector);
514
515        bai.offset = FW_CFG_DATA_PORT_OFFSET;
516
517        for _i in 0..size {
518            read_u8(device, bai, &mut data[..]);
519            blob.push(data[0]);
520        }
521
522        blob
523    }
524
525    fn assert_read_entries(filenames: &[&str], device: &mut FwCfgDevice, bai: BusAccessInfo) {
526        let data_len = device.entries[0][0 + FW_CFG_FILE_FIRST].data.len();
527        assert_eq!(
528            get_entry(device, bai, data_len, (0 + FW_CFG_FILE_FIRST) as u16),
529            device.entries[0][0 + FW_CFG_FILE_FIRST].data
530        );
531
532        for i in (FW_CFG_FILE_FIRST + 1)..filenames.len() {
533            let data_len = device.entries[0][i].data.len();
534            assert_eq!(
535                get_entry(device, bai, data_len, (i + FW_CFG_FILE_FIRST) as u16),
536                device.entries[0][i].data
537            );
538        }
539    }
540
541    fn assert_read_file_dir(
542        filenames: &[&str],
543        contents: &[Vec<u8>],
544        device: &mut FwCfgDevice,
545        bai: BusAccessInfo,
546    ) {
547        let mut data: Vec<u8> = vec![0];
548        let file_count = read_u32(device, bai, &mut data[..]);
549        assert_eq!(file_count, filenames.len() as u32);
550
551        for i in 0..filenames.len() {
552            let file_size = read_u32(device, bai, &mut data[..]);
553            assert_eq!(file_size, contents[i].len() as u32);
554
555            let file_select = read_u16(device, bai, &mut data[..]);
556            assert_eq!(file_select - (FW_CFG_FILE_FIRST as u16), i as u16);
557
558            let file_reserved = read_u16(device, bai, &mut data[..]);
559            assert_eq!(file_reserved, 0);
560
561            let file_name = read_char_56(device, bai, &mut data[..]);
562            assert_eq!(file_name, FILENAMES[i]);
563        }
564    }
565    fn setup_read(
566        filenames: &[&str],
567        contents: &[Vec<u8>],
568        selector: u16,
569    ) -> (FwCfgDevice, BusAccessInfo) {
570        let mut device = make_device(
571            filenames,
572            contents,
573            &default_params(),
574            &(filenames.len() + 5),
575        )
576        .unwrap();
577        let mut bai = BusAccessInfo {
578            offset: FW_CFG_SELECTOR_PORT_OFFSET,
579            address: FW_CFG_BASE_PORT,
580            id: 0,
581        };
582        let selector: [u8; 2] = selector.to_le_bytes();
583        device.write(bai, &selector);
584        bai.offset = FW_CFG_DATA_PORT_OFFSET;
585
586        (device, bai)
587    }
588
589    #[test]
590    // Attempt to build FwCfgParams from key value pairs
591    fn params_from_key_values() {
592        from_fw_cfg_arg("").expect_err("parsing empty string should fail");
593
594        let params = from_fw_cfg_arg("name=foo,path=/path/to/input").unwrap();
595        assert_eq!(
596            params,
597            FwCfgParameters {
598                name: "foo".into(),
599                path: Some("/path/to/input".into()),
600                string: None,
601            }
602        );
603
604        let params = from_fw_cfg_arg("name=bar,string=testdata").unwrap();
605        assert_eq!(
606            params,
607            FwCfgParameters {
608                name: "bar".into(),
609                string: Some("testdata".into()),
610                path: None,
611            }
612        );
613    }
614
615    #[test]
616    // Try to cause underflow by using a selector less than FW_CFG_FILE_FIRST but not one of the
617    // special selectors
618    fn attempt_underflow_read() {
619        let (_device, _bai) = setup_read(
620            &FILENAMES,
621            &get_contents(),
622            (FW_CFG_FILE_FIRST - 0x05) as u16,
623        );
624    }
625
626    #[test]
627    // Write a simple one byte file and confirm that an entry is properly created
628    fn write_one_byte_file() {
629        let mut fw_cfg = FwCfgDevice::new(100, default_params()).unwrap();
630        let data = vec![MAGIC_BYTE];
631        fw_cfg
632            .add_file(FILENAME, data, FwCfgItemType::GenericItem)
633            .expect("File insert failed");
634        let ind = fw_cfg.entries[0].len();
635        assert_eq!(
636            ind,
637            FW_CFG_FILE_FIRST + 1,
638            "Insertion into fw_cfg failed: Index is wrong. expected {}, got {},
639                 ",
640            FW_CFG_FILE_FIRST + 1,
641            ind
642        );
643        assert_eq!(
644            fw_cfg.entries[0][ind - 1].data,
645            vec![MAGIC_BYTE],
646            "Insertion failed: unexpected fw_cfg entry values"
647        );
648    }
649
650    #[test]
651    // Write a simple four byte file and confirm that an entry is properly created
652    fn write_four_byte_file() {
653        let mut fw_cfg = FwCfgDevice::new(100, default_params()).unwrap();
654        let data = vec![MAGIC_BYTE, MAGIC_BYTE_ALT, MAGIC_BYTE, MAGIC_BYTE_ALT];
655        fw_cfg
656            .add_file(FILENAME, data, FwCfgItemType::GenericItem)
657            .expect("File insert failed");
658        let ind = fw_cfg.entries[0].len();
659        assert_eq!(
660            ind,
661            FW_CFG_FILE_FIRST + 1,
662            "Insertion into fw_cfg failed: Index is wrong. expected {}, got {}",
663            FW_CFG_FILE_FIRST + 1,
664            ind
665        );
666        assert_eq!(
667            fw_cfg.entries[0][ind - 1].data,
668            vec![MAGIC_BYTE, MAGIC_BYTE_ALT, MAGIC_BYTE, MAGIC_BYTE_ALT],
669            "Insertion failed: unexpected fw_cfg entry values"
670        );
671    }
672
673    #[test]
674    #[should_panic]
675    // Attempt to add a file to an fw_cfg device w/ no fileslots and assert that nothing gets
676    // inserted
677    fn write_file_one_slot_expect_nop() {
678        let mut fw_cfg = FwCfgDevice::new(0, default_params()).unwrap();
679        let data = vec![MAGIC_BYTE];
680        fw_cfg
681            .add_file(FILENAME, data, FwCfgItemType::GenericItem)
682            .expect("File insert failed");
683    }
684
685    #[test]
686    #[should_panic]
687    // Attempt to add two files to an fw_cfg w/ only one fileslot and assert only first insert
688    // succeeds.
689    fn write_two_files_no_slots_expect_nop_on_second() {
690        let mut fw_cfg = FwCfgDevice::new(1, default_params()).unwrap();
691        let data = vec![MAGIC_BYTE];
692        let data2 = vec![MAGIC_BYTE_ALT];
693        fw_cfg
694            .add_file(FILENAME, data, FwCfgItemType::GenericItem)
695            .expect("File insert failed");
696        assert_eq!(
697            fw_cfg.entries[0].len(),
698            1,
699            "Insertion into fw_cfg failed: Expected {} elements, got {}",
700            1,
701            fw_cfg.entries[0].len()
702        );
703        fw_cfg
704            .add_file(FILENAME, data2, FwCfgItemType::GenericItem)
705            .expect("File insert failed");
706    }
707
708    #[test]
709    // Attempt to read a FwCfgDevice's signature
710    fn read_fw_cfg_signature() {
711        let mut data: Vec<u8> = vec![0];
712        let (mut device, bai) = setup_read(&FILENAMES, &get_contents(), FW_CFG_SIGNATURE_SELECTOR);
713        // To logically compare the revison vector to FW_CFG_REVISION byte-by-byte, we must use
714        // to_be_bytes() since we are comparing byte arrays, not integers.
715        let signature = read_u32(&mut device, bai, &mut data[..]).to_be_bytes();
716        assert_eq!(signature, FW_CFG_SIGNATURE);
717    }
718
719    #[test]
720    // Attempt to read a FwCfgDevice's revision bit vector
721    fn read_fw_cfg_revision() {
722        let mut data: Vec<u8> = vec![0];
723        let (mut device, bai) = setup_read(&FILENAMES, &get_contents(), FW_CFG_REVISION_SELECTOR);
724        // To logically compare the revison vector to FW_CFG_REVISION byte-by-byte, we must use
725        // to_be_bytes() since we are comparing byte arrays, not integers.
726        let revision = read_u32(&mut device, bai, &mut data[..]).to_be_bytes();
727        assert_eq!(revision, FW_CFG_REVISION);
728    }
729
730    #[test]
731    // Attempt to read a FwCfgDevice's file directory
732    fn read_file_dir() {
733        let contents = get_contents();
734        let (mut device, bai) = setup_read(&FILENAMES, &contents, FW_CFG_FILE_DIR_SELECTOR);
735        assert_read_file_dir(&FILENAMES, &contents, &mut device, bai);
736    }
737
738    #[test]
739    // Attempt to read all of a FwCfgDevice's entries
740    fn read_fw_cfg_entries() {
741        let contents = get_contents();
742        let (mut device, bai) = setup_read(&FILENAMES, &contents, (0 + FW_CFG_FILE_FIRST) as u16);
743
744        assert_read_entries(&FILENAMES, &mut device, bai);
745    }
746
747    #[test]
748    // Attempt to read revision, file dir, and entries in random order to make
749    // sure that proper state is maintained.
750    fn read_whole_device() {
751        let contents = get_contents();
752        let mut data: Vec<u8> = vec![0];
753
754        let (mut device, mut bai) = setup_read(&FILENAMES, &contents, FW_CFG_REVISION_SELECTOR);
755
756        let revision = read_u32(&mut device, bai, &mut data[..]).to_be_bytes();
757        assert_eq!(revision, FW_CFG_REVISION);
758
759        let i = FILENAMES.len() - 1;
760        let data_len = device.entries[0][i].data.len();
761        assert_eq!(
762            get_entry(&mut device, bai, data_len, (i + FW_CFG_FILE_FIRST) as u16),
763            device.entries[0][i].data
764        );
765
766        bai.address = FW_CFG_BASE_PORT;
767        bai.offset = FW_CFG_SELECTOR_PORT_OFFSET;
768        device.write(bai, &FW_CFG_FILE_DIR_SELECTOR.to_le_bytes());
769        bai.offset = FW_CFG_DATA_PORT_OFFSET;
770
771        assert_read_file_dir(&FILENAMES, &contents, &mut device, bai);
772
773        let data_len = device.entries[0][FW_CFG_FILE_FIRST + 0].data.len();
774        assert_eq!(
775            get_entry(&mut device, bai, data_len, (0 + FW_CFG_FILE_FIRST) as u16),
776            device.entries[0][FW_CFG_FILE_FIRST + 0].data
777        );
778    }
779
780    #[test]
781    // Assert that the device maintains proper state after reads of random length.
782    fn read_incorrect_bytes() {
783        let contents = get_contents();
784        let mut data: Vec<u8> = vec![0];
785        let (mut device, mut bai) =
786            setup_read(&FILENAMES, &contents, (0 + FW_CFG_FILE_FIRST) as u16);
787
788        for _i in 1..1000 {
789            let _random_bytes = read_u32(&mut device, bai, &mut data[..]);
790        }
791
792        bai.address = FW_CFG_BASE_PORT;
793        device.write(bai, &FW_CFG_FILE_DIR_SELECTOR.to_le_bytes());
794        bai.offset = FW_CFG_DATA_PORT_OFFSET;
795
796        for _i in 1..10000 {
797            let mut data: Vec<u8> = vec![0];
798            let _random_bytes = read_u32(&mut device, bai, &mut data[..]);
799        }
800
801        bai.address = FW_CFG_BASE_PORT;
802        bai.offset = FW_CFG_SELECTOR_PORT_OFFSET;
803        device.write(bai, &FW_CFG_FILE_DIR_SELECTOR.to_le_bytes());
804        bai.offset = FW_CFG_DATA_PORT_OFFSET;
805
806        assert_read_file_dir(&FILENAMES, &contents, &mut device, bai);
807
808        let i = FILENAMES.len() - 1;
809
810        bai.address = FW_CFG_BASE_PORT;
811        bai.offset = FW_CFG_SELECTOR_PORT_OFFSET;
812        device.write(bai, &(FW_CFG_FILE_FIRST + i).to_le_bytes());
813        bai.offset = FW_CFG_DATA_PORT_OFFSET;
814
815        for _i in 1..1000 {
816            let _random_bytes = read_u32(&mut device, bai, &mut data[..]);
817        }
818
819        assert_read_entries(&FILENAMES, &mut device, bai);
820    }
821}