Alioth Code Coverage

fw_cfg.rs22.54%

1// Copyright 2024 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15pub mod acpi;
16
17use std::ffi::CString;
18use std::fmt;
19use std::fs::File;
20use std::io::{ErrorKind, Read, Result, Seek, SeekFrom};
21use std::mem::{size_of, size_of_val};
22use std::os::unix::fs::FileExt;
23use std::path::Path;
24use std::sync::Arc;
25
26use alioth_macros::Layout;
27use bitfield::bitfield;
28use parking_lot::Mutex;
29use serde::de::{self, MapAccess, Visitor};
30use serde::{Deserialize, Deserializer};
31use serde_aco::Help;
32use zerocopy::{FromBytes, Immutable, IntoBytes};
33
34use crate::arch::layout::{
35 PORT_FW_CFG_DATA, PORT_FW_CFG_DMA_HI, PORT_FW_CFG_DMA_LO, PORT_FW_CFG_SELECTOR,
36};
37use crate::device::{self, MmioDev, Pause};
38use crate::firmware::acpi::AcpiTable;
39#[cfg(target_arch = "x86_64")]
40use crate::loader::linux::bootparams::{
41 BootE820Entry, BootParams, E820_ACPI, E820_PMEM, E820_RAM, E820_RESERVED,
42};
43use crate::mem;
44use crate::mem::emulated::{Action, Mmio};
45use crate::mem::mapped::RamBus;
46use crate::mem::{MemRegionEntry, MemRegionType};
47use crate::utils::endian::{Bu16, Bu32, Bu64, Lu16, Lu32, Lu64};
48
49use self::acpi::create_acpi_loader;
50
51pub const SELECTOR_WR: u16 = 1 << 14;
52
53pub const FW_CFG_SIGNATURE: u16 = 0x00;
54pub const FW_CFG_ID: u16 = 0x01;
55pub const FW_CFG_UUID: u16 = 0x02;
56pub const FW_CFG_RAM_SIZE: u16 = 0x03;
57pub const FW_CFG_NOGRAPHIC: u16 = 0x04;
58pub const FW_CFG_NB_CPUS: u16 = 0x05;
59pub const FW_CFG_MACHINE_ID: u16 = 0x06;
60pub const FW_CFG_KERNEL_ADDR: u16 = 0x07;
61pub const FW_CFG_KERNEL_SIZE: u16 = 0x08;
62pub const FW_CFG_KERNEL_CMDLINE: u16 = 0x09;
63pub const FW_CFG_INITRD_ADDR: u16 = 0x0a;
64pub const FW_CFG_INITRD_SIZE: u16 = 0x0b;
65pub const FW_CFG_BOOT_DEVICE: u16 = 0x0c;
66pub const FW_CFG_NUMA: u16 = 0x0d;
67pub const FW_CFG_BOOT_MENU: u16 = 0x0e;
68pub const FW_CFG_MAX_CPUS: u16 = 0x0f;
69pub const FW_CFG_KERNEL_ENTRY: u16 = 0x10;
70pub const FW_CFG_KERNEL_DATA: u16 = 0x11;
71pub const FW_CFG_INITRD_DATA: u16 = 0x12;
72pub const FW_CFG_CMDLINE_ADDR: u16 = 0x13;
73pub const FW_CFG_CMDLINE_SIZE: u16 = 0x14;
74pub const FW_CFG_CMDLINE_DATA: u16 = 0x15;
75pub const FW_CFG_SETUP_ADDR: u16 = 0x16;
76pub const FW_CFG_SETUP_SIZE: u16 = 0x17;
77pub const FW_CFG_SETUP_DATA: u16 = 0x18;
78pub const FW_CFG_FILE_DIR: u16 = 0x19;
79pub const FW_CFG_KNOWN_ITEMS: usize = 0x20;
80
81pub const FW_CFG_FILE_FIRST: u16 = 0x20;
82pub const FW_CFG_DMA_SIGNATURE: [u8; 8] = *b"QEMU CFG";
83pub const FW_CFG_FEATURE: [u8; 4] = [0b11, 0, 0, 0];
84
85pub const FILE_NAME_SIZE: usize = 56;
86
87fn create_file_name(name: &str) -> [u8; FILE_NAME_SIZE] {
88 let mut c_name = [0u8; FILE_NAME_SIZE];
89 let c_len = std::cmp::min(FILE_NAME_SIZE - 1, name.len());
90 c_name[0..c_len].copy_from_slice(&name.as_bytes()[0..c_len]);
91 c_name
92}
93
94#[derive(Debug)]
95pub enum FwCfgContent {
96 Bytes(Vec<u8>),
97 Slice(&'static [u8]),
98 File(u64, File),
99 Lu16(Lu16),
100 Lu32(Lu32),
101 Lu64(Lu64),
102}
103
104struct FwCfgContentAccess<'a> {
105 content: &'a FwCfgContent,
106 offset: u32,
107}
108
109impl Read for FwCfgContentAccess<'_> {
110 fn read(&mut self, buf: &mut [u8]) -> Result<usize> {12x
111 match self.content {12x
112 FwCfgContent::File(offset, f) => {2x
113 Seek::seek(&mut (&*f), SeekFrom::Start(offset + self.offset as u64))?;2x
114 Read::read(&mut (&*f), buf)2x
115 }
116 FwCfgContent::Bytes(b) => match b.get(self.offset as usize..) {2x
117 Some(mut s) => s.read(buf),1x
118 None => Err(ErrorKind::UnexpectedEof)?,1x
119 },
120 FwCfgContent::Slice(b) => match b.get(self.offset as usize..) {2x
121 Some(mut s) => s.read(buf),1x
122 None => Err(ErrorKind::UnexpectedEof)?,1x
123 },
124 FwCfgContent::Lu16(n) => match n.as_bytes().get(self.offset as usize..) {2x
125 Some(mut s) => s.read(buf),2x
126 None => Err(ErrorKind::UnexpectedEof)?,
127 },
128 FwCfgContent::Lu32(n) => match n.as_bytes().get(self.offset as usize..) {2x
129 Some(mut s) => s.read(buf),1x
130 None => Err(ErrorKind::UnexpectedEof)?,1x
131 },
132 FwCfgContent::Lu64(n) => match n.as_bytes().get(self.offset as usize..) {2x
133 Some(mut s) => s.read(buf),2x
134 None => Err(ErrorKind::UnexpectedEof)?,
135 },
136 }
137 }12x
138}
139
140impl Default for FwCfgContent {
141 fn default() -> Self {3x
142 FwCfgContent::Slice(&[])3x
143 }3x
144}
145
146impl FwCfgContent {
147 fn size(&self) -> Result<u32> {7x
148 let ret = match self {7x
149 FwCfgContent::Bytes(v) => v.len(),1x
150 FwCfgContent::File(offset, f) => (f.metadata()?.len() - offset) as usize,1x
151 FwCfgContent::Slice(s) => s.len(),2x
152 FwCfgContent::Lu16(n) => size_of_val(n),1x
153 FwCfgContent::Lu32(n) => size_of_val(n),1x
154 FwCfgContent::Lu64(n) => size_of_val(n),1x
155 };
156 u32::try_from(ret).map_err(|_| std::io::ErrorKind::InvalidInput.into())7x
157 }7x
158
159 fn access(&self, offset: u32) -> FwCfgContentAccess<'_> {12x
160 FwCfgContentAccess {12x
161 content: self,12x
162 offset,12x
163 }12x
164 }12x
165
166 fn read(&self, offset: u32) -> Option<u8> {8x
167 match self {8x
168 FwCfgContent::Bytes(b) => b.get(offset as usize).copied(),1x
169 FwCfgContent::Slice(s) => s.get(offset as usize).copied(),2x
170 FwCfgContent::File(o, f) => {2x
171 let mut buf = [0u8];2x
172 match f.read_exact_at(&mut buf, o + offset as u64) {2x
173 Ok(_) => Some(buf[0]),1x
174 Err(e) => {1x
175 log::error!("fw_cfg: reading {f:?}: {e:?}");1x
176 None1x
177 }
178 }
179 }
180 FwCfgContent::Lu16(n) => n.as_bytes().get(offset as usize).copied(),1x
181 FwCfgContent::Lu32(n) => n.as_bytes().get(offset as usize).copied(),1x
182 FwCfgContent::Lu64(n) => n.as_bytes().get(offset as usize).copied(),1x
183 }
184 }8x
185}
186
187#[derive(Debug, Default)]
188pub struct FwCfgItem {
189 pub name: String,
190 pub content: FwCfgContent,
191}
192
193/// https://www.qemu.org/docs/master/specs/fw_cfg.html
194#[derive(Debug)]
195pub struct FwCfg {
196 selector: u16,
197 data_offset: u32,
198 dma_address: u64,
199 items: Vec<FwCfgItem>, // 0x20 and above
200 known_items: [FwCfgContent; FW_CFG_KNOWN_ITEMS], // 0x0 to 0x19
201 memory: Arc<RamBus>,
202}
203
204#[repr(C)]
205#[derive(Debug, IntoBytes, FromBytes, Immutable, Layout)]
206struct FwCfgDmaAccess {
207 control: Bu32,
208 length: Bu32,
209 address: Bu64,
210}
211
212bitfield! {
213 struct AccessControl(u32);
214 impl Debug;
215 error, set_error: 0;
216 read, _: 1;
217 skip, _: 2;
218 select, _ : 3;
219 write, _ :4;
220 selector, _: 31, 16;
221}
222
223#[repr(C)]
224#[derive(Debug, IntoBytes, Immutable)]
225struct FwCfgFilesHeader {
226 count: Bu32,
227}
228
229#[repr(C)]
230#[derive(Debug, IntoBytes, Immutable)]
231struct FwCfgFile {
232 size: Bu32,
233 select: Bu16,
234 _reserved: u16,
235 name: [u8; FILE_NAME_SIZE],
236}
237
238impl FwCfg {
239 pub fn new(memory: Arc<RamBus>, items: Vec<FwCfgItem>) -> Result<Self> {
240 const DEFAULT_ITEM: FwCfgContent = FwCfgContent::Slice(&[]);
241 let mut known_items = [DEFAULT_ITEM; FW_CFG_KNOWN_ITEMS];
242 known_items[FW_CFG_SIGNATURE as usize] = FwCfgContent::Slice(&FW_CFG_DMA_SIGNATURE);
243 known_items[FW_CFG_ID as usize] = FwCfgContent::Slice(&FW_CFG_FEATURE);
244 let file_buf = Vec::from(FwCfgFilesHeader { count: 0.into() }.as_bytes());
245 known_items[FW_CFG_FILE_DIR as usize] = FwCfgContent::Bytes(file_buf);
246
247 let mut dev = Self {
248 selector: 0,
249 data_offset: 0,
250 dma_address: 0,
251 memory,
252 items: vec![],
253 known_items,
254 };
255 for item in items {
256 dev.add_item(item)?;
257 }
258 Ok(dev)
259 }
260
261 fn get_file_dir_mut(&mut self) -> &mut Vec<u8> {
262 let FwCfgContent::Bytes(file_buf) = &mut self.known_items[FW_CFG_FILE_DIR as usize] else {
263 unreachable!("fw_cfg: selector {FW_CFG_FILE_DIR:#x} should be FwCfgContent::Byte!")
264 };
265 file_buf
266 }
267
268 fn update_count(&mut self) {
269 let header = FwCfgFilesHeader {
270 count: (self.items.len() as u32).into(),
271 };
272 self.get_file_dir_mut()[0..4].copy_from_slice(header.as_bytes());
273 }
274
275 pub fn add_ram_size(&mut self, size: u64) {
276 self.known_items[FW_CFG_RAM_SIZE as usize] = FwCfgContent::Lu64(size.into());
277 }
278
279 pub fn add_cpu_count(&mut self, count: u16) {
280 self.known_items[FW_CFG_NB_CPUS as usize] = FwCfgContent::Lu16(count.into());
281 }
282
283 pub(crate) fn add_e820(&mut self, mem_regions: &[(u64, MemRegionEntry)]) -> Result<()> {
284 let mut bytes = vec![];
285 for (addr, region) in mem_regions.iter() {
286 let type_ = match region.type_ {
287 MemRegionType::Ram => E820_RAM,
288 MemRegionType::Reserved => E820_RESERVED,
289 MemRegionType::Acpi => E820_ACPI,
290 MemRegionType::Pmem => E820_PMEM,
291 MemRegionType::Hidden => continue,
292 };
293 let entry = BootE820Entry {
294 addr: *addr,
295 size: region.size,
296 type_,
297 };
298 bytes.extend_from_slice(entry.as_bytes());
299 }
300 let item = FwCfgItem {
301 name: "etc/e820".to_owned(),
302 content: FwCfgContent::Bytes(bytes),
303 };
304 self.add_item(item)
305 }
306
307 pub(crate) fn add_acpi(&mut self, acpi_table: AcpiTable) -> Result<()> {
308 let [table_loader, acpi_rsdp, apci_tables] = create_acpi_loader(acpi_table);
309 self.add_item(table_loader)?;
310 self.add_item(acpi_rsdp)?;
311 self.add_item(apci_tables)
312 }
313
314 pub fn add_kernel_data(&mut self, p: &Path) -> Result<()> {
315 let file = File::open(p)?;
316 let mut buffer = vec![0u8; size_of::<BootParams>()];
317 file.read_exact_at(&mut buffer, 0)?;
318 let bp = BootParams::mut_from_bytes(&mut buffer).unwrap();
319 if bp.hdr.setup_sects == 0 {
320 bp.hdr.setup_sects = 4;
321 }
322 bp.hdr.type_of_loader = 0xff;
323 let kernel_start = (bp.hdr.setup_sects as usize + 1) * 512;
324 self.known_items[FW_CFG_SETUP_SIZE as usize] =
325 FwCfgContent::Lu32((buffer.len() as u32).into());
326 self.known_items[FW_CFG_SETUP_DATA as usize] = FwCfgContent::Bytes(buffer);
327 self.known_items[FW_CFG_KERNEL_SIZE as usize] =
328 FwCfgContent::Lu32((file.metadata()?.len() as u32 - kernel_start as u32).into());
329 self.known_items[FW_CFG_KERNEL_DATA as usize] =
330 FwCfgContent::File(kernel_start as u64, file);
331 Ok(())
332 }
333
334 pub fn add_initramfs_data(&mut self, p: &Path) -> Result<()> {
335 let file = File::open(p)?;
336 let initramfs_size = file.metadata()?.len() as u32;
337 self.known_items[FW_CFG_INITRD_SIZE as usize] = FwCfgContent::Lu32(initramfs_size.into());
338 self.known_items[FW_CFG_INITRD_DATA as usize] = FwCfgContent::File(0, file);
339 Ok(())
340 }
341
342 pub fn add_kernel_cmdline(&mut self, s: CString) {
343 let bytes = s.into_bytes_with_nul();
344 self.known_items[FW_CFG_CMDLINE_SIZE as usize] =
345 FwCfgContent::Lu32((bytes.len() as u32).into());
346 self.known_items[FW_CFG_CMDLINE_DATA as usize] = FwCfgContent::Bytes(bytes);
347 }
348
349 pub fn add_item(&mut self, item: FwCfgItem) -> Result<()> {
350 let index = self.items.len();
351 let c_name = create_file_name(&item.name);
352 let size = item.content.size()?;
353 let cfg_file = FwCfgFile {
354 size: size.into(),
355 select: (FW_CFG_FILE_FIRST + index as u16).into(),
356 _reserved: 0,
357 name: c_name,
358 };
359 self.get_file_dir_mut()
360 .extend_from_slice(cfg_file.as_bytes());
361 self.items.push(item);
362 self.update_count();
363 Ok(())
364 }
365
366 fn dma_read_content(
367 &self,
368 content: &FwCfgContent,
369 offset: u32,
370 len: u32,
371 address: u64,
372 ) -> Result<u32> {
373 let content_size = content.size()?.saturating_sub(offset);
374 let op_size = std::cmp::min(content_size, len);
375 let r = self
376 .memory
377 .write_range(address, op_size as u64, content.access(offset));
378 match r {
379 Err(e) => {
380 log::error!("fw_cfg: dam read error: {e:x?}");
381 Err(ErrorKind::InvalidInput.into())
382 }
383 Ok(()) => Ok(op_size),
384 }
385 }
386
387 fn dma_read(&mut self, selector: u16, len: u32, address: u64) -> Result<()> {
388 let op_size = if let Some(content) = self.known_items.get(selector as usize) {
389 self.dma_read_content(content, self.data_offset, len, address)
390 } else if let Some(item) = self.items.get((selector - FW_CFG_FILE_FIRST) as usize) {
391 self.dma_read_content(&item.content, self.data_offset, len, address)
392 } else {
393 log::error!("fw_cfg: selector {selector:#x} does not exist.");
394 Err(ErrorKind::NotFound.into())
395 }?;
396 self.data_offset += op_size;
397 Ok(())
398 }
399
400 fn dma_write(&self, _selector: u16, _len: u32, _address: u64) -> Result<()> {
401 unimplemented!()
402 }
403
404 fn do_dma(&mut self) {
405 let dma_address = self.dma_address;
406 let dma_access: FwCfgDmaAccess = match self.memory.read_t(dma_address) {
407 Ok(access) => access,
408 Err(e) => {
409 log::error!("fw_cfg: invalid address of dma access {dma_address:#x}: {e:?}");
410 return;
411 }
412 };
413 let control = AccessControl(dma_access.control.into());
414 if control.select() {
415 self.selector = control.select() as u16;
416 }
417 let len = dma_access.length.to_ne();
418 let addr = dma_access.address.to_ne();
419 let ret = if control.read() {
420 self.dma_read(self.selector, len, addr)
421 } else if control.write() {
422 self.dma_write(self.selector, len, addr)
423 } else if control.skip() {
424 self.data_offset += len;
425 Ok(())
426 } else {
427 Err(ErrorKind::InvalidData.into())
428 };
429 let mut access_resp = AccessControl(0);
430 if let Err(e) = ret {
431 log::error!("fw_cfg: dma operation {dma_access:x?}: {e:x?}");
432 access_resp.set_error(true);
433 }
434 if let Err(e) = self.memory.write_t(
435 dma_address + FwCfgDmaAccess::OFFSET_CONTROL as u64,
436 &Bu32::from(access_resp.0),
437 ) {
438 log::error!("fw_cfg: finishing dma: {e:?}")
439 }
440 }
441
442 fn read_data(&mut self) -> u8 {
443 let ret = if let Some(content) = self.known_items.get(self.selector as usize) {
444 content.read(self.data_offset)
445 } else if let Some(item) = self.items.get((self.selector - FW_CFG_FILE_FIRST) as usize) {
446 item.content.read(self.data_offset)
447 } else {
448 log::error!("fw_cfg: selector {:#x} does not exist.", self.selector);
449 None
450 };
451 if let Some(val) = ret {
452 self.data_offset += 1;
453 val
454 } else {
455 0
456 }
457 }
458
459 fn write_data(&self, _val: u8) {
460 if self.selector & SELECTOR_WR != SELECTOR_WR {
461 log::error!("fw_cfg: data is read only");
462 return;
463 }
464 log::warn!("fw_cfg: write data no op.")
465 }
466}
467
468impl Mmio for Mutex<FwCfg> {
469 fn size(&self) -> u64 {
470 16
471 }
472
473 fn read(&self, offset: u64, size: u8) -> mem::Result<u64> {
474 let mut fw_cfg = self.lock();
475 let port = offset as u16 + PORT_FW_CFG_SELECTOR;
476 let ret = match (port, size) {
477 (PORT_FW_CFG_SELECTOR, _) => {
478 log::error!("fw_cfg: selector registeris write-only.");
479 0
480 }
481 (PORT_FW_CFG_DATA, 1) => fw_cfg.read_data() as u64,
482 (PORT_FW_CFG_DMA_HI, 4) => {
483 let addr = fw_cfg.dma_address;
484 let addr_hi = (addr >> 32) as u32;
485 addr_hi.to_be() as u64
486 }
487 (PORT_FW_CFG_DMA_LO, 4) => {
488 let addr = fw_cfg.dma_address;
489 let addr_lo = (addr & 0xffff_ffff) as u32;
490 addr_lo.to_be() as u64
491 }
492 _ => {
493 log::error!("fw_cfg: read unknown port {port:#x} with size {size}.");
494 0
495 }
496 };
497 Ok(ret)
498 }
499
500 fn write(&self, offset: u64, size: u8, val: u64) -> mem::Result<Action> {
501 let mut fw_cfg = self.lock();
502 let port = offset as u16 + PORT_FW_CFG_SELECTOR;
503 match (port, size) {
504 (PORT_FW_CFG_SELECTOR, 2) => {
505 fw_cfg.selector = val as u16;
506 fw_cfg.data_offset = 0;
507 }
508 (PORT_FW_CFG_DATA, 1) => fw_cfg.write_data(val as u8),
509 (PORT_FW_CFG_DMA_HI, 4) => {
510 fw_cfg.dma_address &= 0xffff_ffff;
511 fw_cfg.dma_address |= (u32::from_be(val as u32) as u64) << 32;
512 }
513 (PORT_FW_CFG_DMA_LO, 4) => {
514 fw_cfg.dma_address &= !0xffff_ffff;
515 fw_cfg.dma_address |= u32::from_be(val as u32) as u64;
516 fw_cfg.do_dma();
517 }
518 _ => log::error!(
519 "fw_cfg: write 0x{val:0width$x} to unknown port {port:#x}.",
520 width = 2 * size as usize,
521 ),
522 };
523 Ok(Action::None)
524 }
525}
526
527impl Pause for Mutex<FwCfg> {
528 fn pause(&self) -> device::Result<()> {
529 Ok(())
530 }
531
532 fn resume(&self) -> device::Result<()> {
533 Ok(())
534 }
535}
536
537impl MmioDev for Mutex<FwCfg> {}
538
539#[derive(Debug, PartialEq, Eq, Deserialize, Help)]
540pub enum FwCfgContentParam {
541 /// Path to a file with binary contents.
542 #[serde(alias = "file")]
543 File(Box<Path>),
544 /// A UTF-8 encoded string.
545 #[serde(alias = "string")]
546 String(String),
547}
548
549#[derive(Debug, PartialEq, Eq, Help)]
550pub struct FwCfgItemParam {
551 /// Selector key of an item.
552 pub name: String,
553 /// Item content.
554 #[serde_aco(flatten)]
555 pub content: FwCfgContentParam,
556}
557
558impl<'de> Deserialize<'de> for FwCfgItemParam {
559 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>2x
560 where2x
561 D: Deserializer<'de>,2x
562 {
563 #[derive(Deserialize)]
564 #[serde(field_identifier, rename_all = "lowercase")]
565 enum Field {
566 Name,
567 File,
568 String,
569 }
570
571 struct ParamVisitor;
572
573 impl<'de> Visitor<'de> for ParamVisitor {
574 type Value = FwCfgItemParam;
575
576 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
577 formatter.write_str("struct FwCfgItemParam")
578 }
579
580 fn visit_map<V>(self, mut map: V) -> std::result::Result<Self::Value, V::Error>2x
581 where2x
582 V: MapAccess<'de>,2x
583 {
584 let mut name = None;2x
585 let mut content = None;2x
586 while let Some(key) = map.next_key()? {6x
587 match key {4x
588 Field::Name => {
589 if name.is_some() {2x
590 return Err(de::Error::duplicate_field("file"));
591 }2x
592 name = Some(map.next_value()?);2x
593 }
594 Field::String => {
595 if content.is_some() {1x
596 return Err(de::Error::duplicate_field("string,file"));
597 }1x
598 content = Some(FwCfgContentParam::String(map.next_value()?));1x
599 }
600 Field::File => {
601 if content.is_some() {1x
602 return Err(de::Error::duplicate_field("string,file"));
603 }1x
604 content = Some(FwCfgContentParam::File(map.next_value()?));1x
605 }
606 }
607 }
608 let name = name.ok_or_else(|| de::Error::missing_field("name"))?;2x
609 let content = content.ok_or_else(|| de::Error::missing_field("file,string"))?;2x
610 Ok(FwCfgItemParam { name, content })2x
611 }2x
612 }
613
614 const FIELDS: &[&str] = &["name", "file", "string"];
615 deserializer.deserialize_struct("FwCfgItemParam", FIELDS, ParamVisitor)2x
616 }2x
617}
618
619impl FwCfgItemParam {
620 pub fn build(self) -> Result<FwCfgItem> {
621 match self.content {
622 FwCfgContentParam::File(file) => {
623 let f = File::open(file)?;
624 Ok(FwCfgItem {
625 name: self.name,
626 content: FwCfgContent::File(0, f),
627 })
628 }
629 FwCfgContentParam::String(string) => Ok(FwCfgItem {
630 name: self.name,
631 content: FwCfgContent::Bytes(string.into()),
632 }),
633 }
634 }
635}
636
637#[cfg(test)]
638#[path = "fw_cfg_test.rs"]
639mod tests;
640