• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 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 //! Runs hardware devices in child processes.
6 
7 use std::fs;
8 
9 use anyhow::anyhow;
10 use base::error;
11 use base::info;
12 use base::linux::process::fork_process;
13 use base::AsRawDescriptor;
14 #[cfg(feature = "swap")]
15 use base::AsRawDescriptors;
16 use base::RawDescriptor;
17 use base::SharedMemory;
18 use base::Tube;
19 use base::TubeError;
20 use libc::pid_t;
21 use minijail::Minijail;
22 use remain::sorted;
23 use serde::Deserialize;
24 use serde::Serialize;
25 use thiserror::Error;
26 
27 use crate::bus::ConfigWriteResult;
28 use crate::pci::CrosvmDeviceId;
29 use crate::pci::PciAddress;
30 use crate::BusAccessInfo;
31 use crate::BusDevice;
32 use crate::BusRange;
33 use crate::BusType;
34 use crate::DeviceId;
35 use crate::Suspendable;
36 
37 /// Errors for proxy devices.
38 #[sorted]
39 #[derive(Error, Debug)]
40 pub enum Error {
41     #[error("Failed to activate ProxyDevice")]
42     ActivatingProxyDevice,
43     #[error("Failed to fork jail process: {0}")]
44     ForkingJail(#[from] minijail::Error),
45     #[error("Failed to configure swap: {0}")]
46     Swap(anyhow::Error),
47     #[error("Failed to configure tube: {0}")]
48     Tube(#[from] TubeError),
49 }
50 
51 pub type Result<T> = std::result::Result<T, Error>;
52 
53 #[derive(Debug, Serialize, Deserialize)]
54 enum Command {
55     Activate,
56     Read {
57         len: u32,
58         info: BusAccessInfo,
59     },
60     Write {
61         len: u32,
62         info: BusAccessInfo,
63         data: [u8; 8],
64     },
65     ReadConfig(u32),
66     WriteConfig {
67         reg_idx: u32,
68         offset: u32,
69         len: u32,
70         data: [u8; 4],
71     },
72     InitPciConfigMapping {
73         shmem: SharedMemory,
74         base: usize,
75         len: usize,
76     },
77     ReadVirtualConfig(u32),
78     WriteVirtualConfig {
79         reg_idx: u32,
80         value: u32,
81     },
82     DestroyDevice,
83     Shutdown,
84     GetRanges,
85     Snapshot,
86     Restore {
87         data: serde_json::Value,
88     },
89     Sleep,
90     Wake,
91 }
92 #[derive(Debug, Serialize, Deserialize)]
93 enum CommandResult {
94     Ok,
95     ReadResult([u8; 8]),
96     ReadConfigResult(u32),
97     WriteConfigResult {
98         mmio_remove: Vec<BusRange>,
99         mmio_add: Vec<BusRange>,
100         io_remove: Vec<BusRange>,
101         io_add: Vec<BusRange>,
102         removed_pci_devices: Vec<PciAddress>,
103     },
104     InitPciConfigMappingResult(bool),
105     ReadVirtualConfigResult(u32),
106     GetRangesResult(Vec<(BusRange, BusType)>),
107     SnapshotResult(std::result::Result<serde_json::Value, String>),
108     RestoreResult(std::result::Result<(), String>),
109     SleepResult(std::result::Result<(), String>),
110     WakeResult(std::result::Result<(), String>),
111 }
112 
child_proc<D: BusDevice>(tube: Tube, mut device: D)113 fn child_proc<D: BusDevice>(tube: Tube, mut device: D) {
114     // Wait for activation signal to function as BusDevice.
115     match tube.recv() {
116         Ok(Command::Activate) => {
117             if let Err(e) = tube.send(&CommandResult::Ok) {
118                 error!("sending activation result failed: {:?}", &e);
119                 return;
120             }
121         }
122         // Commands other than activate is unexpected, close device.
123         Ok(cmd) => {
124             panic!("Receiving Command {:?} before device is activated", &cmd);
125         }
126         // Most likely tube error is caused by other end is dropped, release resource.
127         Err(e) => {
128             error!("device failed before activation: {:?}. Dropping device", e);
129             drop(device);
130             return;
131         }
132     };
133     loop {
134         let cmd = match tube.recv() {
135             Ok(cmd) => cmd,
136             Err(err) => {
137                 error!("child device process failed recv: {}", err);
138                 break;
139             }
140         };
141 
142         let res = match cmd {
143             Command::Activate => {
144                 panic!("Device shall only be activated once, duplicated ProxyDevice likely");
145             }
146             Command::Read { len, info } => {
147                 let mut buffer = [0u8; 8];
148                 device.read(info, &mut buffer[0..len as usize]);
149                 tube.send(&CommandResult::ReadResult(buffer))
150             }
151             Command::Write { len, info, data } => {
152                 let len = len as usize;
153                 device.write(info, &data[0..len]);
154                 // Command::Write does not have a result.
155                 Ok(())
156             }
157             Command::ReadConfig(idx) => {
158                 let val = device.config_register_read(idx as usize);
159                 tube.send(&CommandResult::ReadConfigResult(val))
160             }
161             Command::WriteConfig {
162                 reg_idx,
163                 offset,
164                 len,
165                 data,
166             } => {
167                 let len = len as usize;
168                 let res =
169                     device.config_register_write(reg_idx as usize, offset as u64, &data[0..len]);
170                 tube.send(&CommandResult::WriteConfigResult {
171                     mmio_remove: res.mmio_remove,
172                     mmio_add: res.mmio_add,
173                     io_remove: res.io_remove,
174                     io_add: res.io_add,
175                     removed_pci_devices: res.removed_pci_devices,
176                 })
177             }
178             Command::InitPciConfigMapping { shmem, base, len } => {
179                 let success = device.init_pci_config_mapping(&shmem, base, len);
180                 tube.send(&CommandResult::InitPciConfigMappingResult(success))
181             }
182             Command::ReadVirtualConfig(idx) => {
183                 let val = device.virtual_config_register_read(idx as usize);
184                 tube.send(&CommandResult::ReadVirtualConfigResult(val))
185             }
186             Command::WriteVirtualConfig { reg_idx, value } => {
187                 device.virtual_config_register_write(reg_idx as usize, value);
188                 tube.send(&CommandResult::Ok)
189             }
190             Command::DestroyDevice => {
191                 device.destroy_device();
192                 Ok(())
193             }
194             Command::Shutdown => {
195                 // Explicitly drop the device so that its Drop implementation has a chance to run
196                 // before sending the `Command::Shutdown` response.
197                 drop(device);
198 
199                 let _ = tube.send(&CommandResult::Ok);
200                 return;
201             }
202             Command::GetRanges => {
203                 let ranges = device.get_ranges();
204                 tube.send(&CommandResult::GetRangesResult(ranges))
205             }
206             Command::Snapshot => {
207                 let res = device.snapshot();
208                 tube.send(&CommandResult::SnapshotResult(
209                     res.map_err(|e| e.to_string()),
210                 ))
211             }
212             Command::Restore { data } => {
213                 let res = device.restore(data);
214                 tube.send(&CommandResult::RestoreResult(
215                     res.map_err(|e| e.to_string()),
216                 ))
217             }
218             Command::Sleep => {
219                 let res = device.sleep();
220                 tube.send(&CommandResult::SleepResult(res.map_err(|e| e.to_string())))
221             }
222             Command::Wake => {
223                 let res = device.wake();
224                 tube.send(&CommandResult::WakeResult(res.map_err(|e| e.to_string())))
225             }
226         };
227         if let Err(e) = res {
228             error!("child device process failed send: {}", e);
229         }
230     }
231 }
232 
233 /// ChildProcIntf is the interface to the device child process.
234 ///
235 /// ChildProcIntf implements Serialize, and can be sent across process before it functions as a
236 /// ProxyDevice. However, a child process shall only correspond to one ProxyDevice. The uniqueness
237 /// is checked when ChildProcIntf is casted into ProxyDevice.
238 #[derive(Serialize, Deserialize)]
239 pub struct ChildProcIntf {
240     tube: Tube,
241     pid: pid_t,
242     debug_label: String,
243 }
244 
245 impl ChildProcIntf {
246     /// Creates ChildProcIntf that shall be turned into exactly one ProxyDevice.
247     ///
248     /// The ChildProcIntf struct holds the interface to the device process. It shall be turned into
249     /// a ProxyDevice exactly once (at an arbitrary process). Since ChildProcIntf may be duplicated
250     /// by serde, the uniqueness of the interface is checked when ChildProcIntf is converted into
251     /// ProxyDevice.
252     ///
253     /// # Arguments
254     /// * `device` - The device to isolate to another process.
255     /// * `jail` - The jail to use for isolating the given device.
256     /// * `keep_rds` - File descriptors that will be kept open in the child.
new<D: BusDevice, #[cfg(feature = "swap")] P: swap::PrepareFork>( mut device: D, jail: Minijail, mut keep_rds: Vec<RawDescriptor>, #[cfg(feature = "swap")] swap_prepare_fork: &mut Option<P>, ) -> Result<ChildProcIntf>257     pub fn new<D: BusDevice, #[cfg(feature = "swap")] P: swap::PrepareFork>(
258         mut device: D,
259         jail: Minijail,
260         mut keep_rds: Vec<RawDescriptor>,
261         #[cfg(feature = "swap")] swap_prepare_fork: &mut Option<P>,
262     ) -> Result<ChildProcIntf> {
263         let debug_label = device.debug_label();
264         let (child_tube, parent_tube) = Tube::pair()?;
265 
266         keep_rds.push(child_tube.as_raw_descriptor());
267 
268         #[cfg(feature = "swap")]
269         let swap_device_uffd_sender = if let Some(prepare_fork) = swap_prepare_fork {
270             let sender = prepare_fork.prepare_fork().map_err(Error::Swap)?;
271             keep_rds.extend(sender.as_raw_descriptors());
272             Some(sender)
273         } else {
274             None
275         };
276 
277         // This will be removed after b/183540186 gets fixed.
278         // Only enabled it for x86_64 since the original bug mostly happens on x86 boards.
279         if cfg!(target_arch = "x86_64") && debug_label == "pcivirtio-gpu" {
280             if let Ok(cmd) = fs::read_to_string("/proc/self/cmdline") {
281                 if cmd.contains("arcvm") {
282                     if let Ok(share) = fs::read_to_string("/sys/fs/cgroup/cpu/arcvm/cpu.shares") {
283                         info!("arcvm cpu share when booting gpu is {:}", share.trim());
284                     }
285                 }
286             }
287         }
288 
289         let child_process = fork_process(jail, keep_rds, Some(debug_label.clone()), || {
290             #[cfg(feature = "swap")]
291             if let Some(swap_device_uffd_sender) = swap_device_uffd_sender {
292                 if let Err(e) = swap_device_uffd_sender.on_process_forked() {
293                     error!("failed to SwapController::on_process_forked: {:?}", e);
294                     // SAFETY:
295                     // exit() is trivially safe.
296                     unsafe { libc::exit(1) };
297                 }
298             }
299 
300             device.on_sandboxed();
301             child_proc(child_tube, device);
302 
303             // We're explicitly not using std::process::exit here to avoid the cleanup of
304             // stdout/stderr globals. This can cause cascading panics and SIGILL if a worker
305             // thread attempts to log to stderr after at_exit handlers have been run.
306             // TODO(crbug.com/992494): Remove this once device shutdown ordering is clearly
307             // defined.
308             //
309             // SAFETY:
310             // exit() is trivially safe.
311             // ! Never returns
312             unsafe { libc::exit(0) };
313         })?;
314 
315         // Suppress the no waiting warning from `base::sys::linux::process::Child` because crosvm
316         // does not wait for the processes from ProxyDevice explicitly. Instead it reaps all the
317         // child processes on its exit by `crosvm::sys::linux::main::wait_all_children()`.
318         let pid = child_process.into_pid();
319 
320         Ok(ChildProcIntf {
321             tube: parent_tube,
322             pid,
323             debug_label,
324         })
325     }
326 }
327 
328 /// Wraps an inner `BusDevice` that is run inside a child process via fork.
329 ///
330 /// The forked device process will automatically be terminated when this is dropped.
331 pub struct ProxyDevice {
332     child_proc_intf: ChildProcIntf,
333 }
334 
335 impl TryFrom<ChildProcIntf> for ProxyDevice {
336     type Error = Error;
try_from(child_proc_intf: ChildProcIntf) -> Result<Self>337     fn try_from(child_proc_intf: ChildProcIntf) -> Result<Self> {
338         // Notify child process to be activated as a BusDevice.
339         child_proc_intf.tube.send(&Command::Activate)?;
340         // Device returns Ok if it is activated only once.
341         match child_proc_intf.tube.recv()? {
342             CommandResult::Ok => Ok(Self { child_proc_intf }),
343             _ => Err(Error::ActivatingProxyDevice),
344         }
345     }
346 }
347 
348 impl ProxyDevice {
349     /// Takes the given device and isolates it into another process via fork before returning.
350     ///
351     /// Because forks are very unfriendly to destructors and all memory mappings and file
352     /// descriptors are inherited, this should be used as early as possible in the main process.
353     /// ProxyDevice::new shall not be used for hotplugging. Call ChildProcIntf::new on jail warden
354     /// process, send using serde, then cast into ProxyDevice instead.
355     ///
356     /// # Arguments
357     /// * `device` - The device to isolate to another process.
358     /// * `jail` - The jail to use for isolating the given device.
359     /// * `keep_rds` - File descriptors that will be kept open in the child.
new<D: BusDevice, #[cfg(feature = "swap")] P: swap::PrepareFork>( device: D, jail: Minijail, keep_rds: Vec<RawDescriptor>, #[cfg(feature = "swap")] swap_prepare_fork: &mut Option<P>, ) -> Result<ProxyDevice>360     pub fn new<D: BusDevice, #[cfg(feature = "swap")] P: swap::PrepareFork>(
361         device: D,
362         jail: Minijail,
363         keep_rds: Vec<RawDescriptor>,
364         #[cfg(feature = "swap")] swap_prepare_fork: &mut Option<P>,
365     ) -> Result<ProxyDevice> {
366         ChildProcIntf::new(
367             device,
368             jail,
369             keep_rds,
370             #[cfg(feature = "swap")]
371             swap_prepare_fork,
372         )?
373         .try_into()
374     }
375 
pid(&self) -> pid_t376     pub fn pid(&self) -> pid_t {
377         self.child_proc_intf.pid
378     }
379 
380     /// Send a command that does not expect a response from the child device process.
send_no_result(&self, cmd: &Command)381     fn send_no_result(&self, cmd: &Command) {
382         let res = self.child_proc_intf.tube.send(cmd);
383         if let Err(e) = res {
384             error!(
385                 "failed write to child device process {}: {}",
386                 self.child_proc_intf.debug_label, e,
387             );
388         }
389     }
390 
391     /// Send a command and read its response from the child device process.
sync_send(&self, cmd: &Command) -> Option<CommandResult>392     fn sync_send(&self, cmd: &Command) -> Option<CommandResult> {
393         self.send_no_result(cmd);
394         match self.child_proc_intf.tube.recv() {
395             Err(e) => {
396                 error!(
397                     "failed to read result of {:?} from child device process {}: {}",
398                     cmd, self.child_proc_intf.debug_label, e,
399                 );
400                 None
401             }
402             Ok(r) => Some(r),
403         }
404     }
405 }
406 
407 impl BusDevice for ProxyDevice {
device_id(&self) -> DeviceId408     fn device_id(&self) -> DeviceId {
409         CrosvmDeviceId::ProxyDevice.into()
410     }
411 
debug_label(&self) -> String412     fn debug_label(&self) -> String {
413         self.child_proc_intf.debug_label.clone()
414     }
415 
config_register_write( &mut self, reg_idx: usize, offset: u64, data: &[u8], ) -> ConfigWriteResult416     fn config_register_write(
417         &mut self,
418         reg_idx: usize,
419         offset: u64,
420         data: &[u8],
421     ) -> ConfigWriteResult {
422         let len = data.len() as u32;
423         let mut buffer = [0u8; 4];
424         buffer[0..data.len()].clone_from_slice(data);
425         let reg_idx = reg_idx as u32;
426         let offset = offset as u32;
427         if let Some(CommandResult::WriteConfigResult {
428             mmio_remove,
429             mmio_add,
430             io_remove,
431             io_add,
432             removed_pci_devices,
433         }) = self.sync_send(&Command::WriteConfig {
434             reg_idx,
435             offset,
436             len,
437             data: buffer,
438         }) {
439             ConfigWriteResult {
440                 mmio_remove,
441                 mmio_add,
442                 io_remove,
443                 io_add,
444                 removed_pci_devices,
445             }
446         } else {
447             Default::default()
448         }
449     }
450 
config_register_read(&self, reg_idx: usize) -> u32451     fn config_register_read(&self, reg_idx: usize) -> u32 {
452         let res = self.sync_send(&Command::ReadConfig(reg_idx as u32));
453         if let Some(CommandResult::ReadConfigResult(val)) = res {
454             val
455         } else {
456             0
457         }
458     }
459 
init_pci_config_mapping(&mut self, shmem: &SharedMemory, base: usize, len: usize) -> bool460     fn init_pci_config_mapping(&mut self, shmem: &SharedMemory, base: usize, len: usize) -> bool {
461         let Ok(shmem) = shmem.try_clone() else {
462             error!("Failed to clone pci config mapping shmem");
463             return false;
464         };
465         let res = self.sync_send(&Command::InitPciConfigMapping { shmem, base, len });
466         matches!(res, Some(CommandResult::InitPciConfigMappingResult(true)))
467     }
468 
virtual_config_register_write(&mut self, reg_idx: usize, value: u32)469     fn virtual_config_register_write(&mut self, reg_idx: usize, value: u32) {
470         let reg_idx = reg_idx as u32;
471         self.sync_send(&Command::WriteVirtualConfig { reg_idx, value });
472     }
473 
virtual_config_register_read(&self, reg_idx: usize) -> u32474     fn virtual_config_register_read(&self, reg_idx: usize) -> u32 {
475         let res = self.sync_send(&Command::ReadVirtualConfig(reg_idx as u32));
476         if let Some(CommandResult::ReadVirtualConfigResult(val)) = res {
477             val
478         } else {
479             0
480         }
481     }
482 
read(&mut self, info: BusAccessInfo, data: &mut [u8])483     fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
484         let len = data.len() as u32;
485         if let Some(CommandResult::ReadResult(buffer)) =
486             self.sync_send(&Command::Read { len, info })
487         {
488             let len = data.len();
489             data.clone_from_slice(&buffer[0..len]);
490         }
491     }
492 
write(&mut self, info: BusAccessInfo, data: &[u8])493     fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
494         let mut buffer = [0u8; 8];
495         let len = data.len() as u32;
496         buffer[0..data.len()].clone_from_slice(data);
497         self.send_no_result(&Command::Write {
498             len,
499             info,
500             data: buffer,
501         });
502     }
503 
get_ranges(&self) -> Vec<(BusRange, BusType)>504     fn get_ranges(&self) -> Vec<(BusRange, BusType)> {
505         if let Some(CommandResult::GetRangesResult(ranges)) = self.sync_send(&Command::GetRanges) {
506             ranges
507         } else {
508             Default::default()
509         }
510     }
511 
destroy_device(&mut self)512     fn destroy_device(&mut self) {
513         self.send_no_result(&Command::DestroyDevice);
514     }
515 }
516 
517 impl Suspendable for ProxyDevice {
snapshot(&mut self) -> anyhow::Result<serde_json::Value>518     fn snapshot(&mut self) -> anyhow::Result<serde_json::Value> {
519         let res = self.sync_send(&Command::Snapshot);
520         match res {
521             Some(CommandResult::SnapshotResult(Ok(snap))) => Ok(snap),
522             Some(CommandResult::SnapshotResult(Err(e))) => Err(anyhow!(
523                 "failed to snapshot {}: {:#}",
524                 self.debug_label(),
525                 e
526             )),
527             _ => Err(anyhow!("unexpected snapshot result {:?}", res)),
528         }
529     }
530 
restore(&mut self, data: serde_json::Value) -> anyhow::Result<()>531     fn restore(&mut self, data: serde_json::Value) -> anyhow::Result<()> {
532         let res = self.sync_send(&Command::Restore { data });
533         match res {
534             Some(CommandResult::RestoreResult(Ok(()))) => Ok(()),
535             Some(CommandResult::RestoreResult(Err(e))) => {
536                 Err(anyhow!("failed to restore {}: {:#}", self.debug_label(), e))
537             }
538             _ => Err(anyhow!("unexpected restore result {:?}", res)),
539         }
540     }
541 
sleep(&mut self) -> anyhow::Result<()>542     fn sleep(&mut self) -> anyhow::Result<()> {
543         let res = self.sync_send(&Command::Sleep);
544         match res {
545             Some(CommandResult::SleepResult(Ok(()))) => Ok(()),
546             Some(CommandResult::SleepResult(Err(e))) => {
547                 Err(anyhow!("failed to sleep {}: {:#}", self.debug_label(), e))
548             }
549             _ => Err(anyhow!("unexpected sleep result {:?}", res)),
550         }
551     }
552 
wake(&mut self) -> anyhow::Result<()>553     fn wake(&mut self) -> anyhow::Result<()> {
554         let res = self.sync_send(&Command::Wake);
555         match res {
556             Some(CommandResult::WakeResult(Ok(()))) => Ok(()),
557             Some(CommandResult::WakeResult(Err(e))) => {
558                 Err(anyhow!("failed to wake {}: {:#}", self.debug_label(), e))
559             }
560             _ => Err(anyhow!("unexpected wake result {:?}", res)),
561         }
562     }
563 }
564 
565 impl Drop for ProxyDevice {
drop(&mut self)566     fn drop(&mut self) {
567         self.sync_send(&Command::Shutdown);
568     }
569 }
570 
571 /// Note: These tests must be run with --test-threads=1 to allow minijail to fork
572 /// the process.
573 #[cfg(test)]
574 mod tests {
575     use super::*;
576     use crate::pci::PciId;
577 
578     /// A simple test echo device that outputs the same u8 that was written to it.
579     struct EchoDevice {
580         data: u8,
581         config: u8,
582     }
583     impl EchoDevice {
new() -> EchoDevice584         fn new() -> EchoDevice {
585             EchoDevice { data: 0, config: 0 }
586         }
587     }
588     impl BusDevice for EchoDevice {
device_id(&self) -> DeviceId589         fn device_id(&self) -> DeviceId {
590             PciId::new(0, 0).into()
591         }
592 
debug_label(&self) -> String593         fn debug_label(&self) -> String {
594             "EchoDevice".to_owned()
595         }
596 
write(&mut self, _info: BusAccessInfo, data: &[u8])597         fn write(&mut self, _info: BusAccessInfo, data: &[u8]) {
598             assert!(data.len() == 1);
599             self.data = data[0];
600         }
601 
read(&mut self, _info: BusAccessInfo, data: &mut [u8])602         fn read(&mut self, _info: BusAccessInfo, data: &mut [u8]) {
603             assert!(data.len() == 1);
604             data[0] = self.data;
605         }
606 
config_register_write( &mut self, _reg_idx: usize, _offset: u64, data: &[u8], ) -> ConfigWriteResult607         fn config_register_write(
608             &mut self,
609             _reg_idx: usize,
610             _offset: u64,
611             data: &[u8],
612         ) -> ConfigWriteResult {
613             let result = ConfigWriteResult {
614                 ..Default::default()
615             };
616             assert!(data.len() == 1);
617             self.config = data[0];
618             result
619         }
620 
config_register_read(&self, _reg_idx: usize) -> u32621         fn config_register_read(&self, _reg_idx: usize) -> u32 {
622             self.config as u32
623         }
624     }
625 
626     impl Suspendable for EchoDevice {}
627 
new_proxied_echo_device() -> ProxyDevice628     fn new_proxied_echo_device() -> ProxyDevice {
629         let device = EchoDevice::new();
630         let keep_fds: Vec<RawDescriptor> = Vec::new();
631         let minijail = Minijail::new().unwrap();
632         ProxyDevice::new(
633             device,
634             minijail,
635             keep_fds,
636             #[cfg(feature = "swap")]
637             &mut None::<swap::SwapController>,
638         )
639         .unwrap()
640     }
641 
642     // TODO(b/173833661): Find a way to ensure these tests are run single-threaded.
643     #[test]
644     #[ignore]
test_debug_label()645     fn test_debug_label() {
646         let proxy_device = new_proxied_echo_device();
647         assert_eq!(proxy_device.debug_label(), "EchoDevice");
648     }
649 
650     #[test]
651     #[ignore]
test_proxied_read_write()652     fn test_proxied_read_write() {
653         let mut proxy_device = new_proxied_echo_device();
654         let address = BusAccessInfo {
655             offset: 0,
656             address: 0,
657             id: 0,
658         };
659         proxy_device.write(address, &[42]);
660         let mut read_buffer = [0];
661         proxy_device.read(address, &mut read_buffer);
662         assert_eq!(read_buffer, [42]);
663     }
664 
665     #[test]
666     #[ignore]
test_proxied_config()667     fn test_proxied_config() {
668         let mut proxy_device = new_proxied_echo_device();
669         proxy_device.config_register_write(0, 0, &[42]);
670         assert_eq!(proxy_device.config_register_read(0), 42);
671     }
672 }
673