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