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