devices/
proxy.rs

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