Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/proxy.rs
5392 views
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
use std::fs::File;
9
use std::io::BufReader;
10
use std::io::BufWriter;
11
use std::io::Seek;
12
use std::io::Write;
13
14
use anyhow::anyhow;
15
use anyhow::Context;
16
use base::error;
17
use base::info;
18
use base::with_as_descriptor;
19
use base::AsRawDescriptor;
20
#[cfg(feature = "swap")]
21
use base::AsRawDescriptors;
22
use base::RawDescriptor;
23
use base::SharedMemory;
24
use base::Tube;
25
use base::TubeError;
26
use jail::fork::fork_process;
27
use libc::pid_t;
28
use minijail::Minijail;
29
use remain::sorted;
30
use serde::Deserialize;
31
use serde::Serialize;
32
use snapshot::AnySnapshot;
33
use tempfile::tempfile;
34
use thiserror::Error;
35
use vm_control::DeviceId;
36
use vm_control::PlatformDeviceId;
37
38
use crate::bus::ConfigWriteResult;
39
use crate::pci::PciAddress;
40
use crate::BusAccessInfo;
41
use crate::BusDevice;
42
use crate::BusRange;
43
use crate::BusType;
44
use crate::Suspendable;
45
46
/// Errors for proxy devices.
47
#[sorted]
48
#[derive(Error, Debug)]
49
pub 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
60
pub 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)]
66
struct SnapshotFile {
67
#[serde(with = "with_as_descriptor")]
68
file: File,
69
}
70
71
impl 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)]
116
enum 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)]
160
enum 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
180
fn 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)]
325
pub struct ChildProcIntf {
326
tube: Tube,
327
pid: pid_t,
328
debug_label: String,
329
}
330
331
impl 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.
417
pub struct ProxyDevice {
418
child_proc_intf: ChildProcIntf,
419
}
420
421
impl 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
434
impl 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
493
impl BusDevice for ProxyDevice {
494
fn device_id(&self) -> DeviceId {
495
PlatformDeviceId::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
603
impl 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
655
impl 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)]
664
mod tests {
665
use vm_control::PciId;
666
667
use super::*;
668
669
/// A simple test echo device that outputs the same u8 that was written to it.
670
struct EchoDevice {
671
data: u8,
672
config: u8,
673
}
674
impl EchoDevice {
675
fn new() -> EchoDevice {
676
EchoDevice { data: 0, config: 0 }
677
}
678
}
679
impl BusDevice for EchoDevice {
680
fn device_id(&self) -> DeviceId {
681
PciId::new(0, 0).into()
682
}
683
684
fn debug_label(&self) -> String {
685
"EchoDevice".to_owned()
686
}
687
688
fn write(&mut self, _info: BusAccessInfo, data: &[u8]) {
689
assert!(data.len() == 1);
690
self.data = data[0];
691
}
692
693
fn read(&mut self, _info: BusAccessInfo, data: &mut [u8]) {
694
assert!(data.len() == 1);
695
data[0] = self.data;
696
}
697
698
fn config_register_write(
699
&mut self,
700
_reg_idx: usize,
701
_offset: u64,
702
data: &[u8],
703
) -> ConfigWriteResult {
704
let result = ConfigWriteResult {
705
..Default::default()
706
};
707
assert!(data.len() == 1);
708
self.config = data[0];
709
result
710
}
711
712
fn config_register_read(&self, _reg_idx: usize) -> u32 {
713
self.config as u32
714
}
715
}
716
717
impl Suspendable for EchoDevice {}
718
719
fn new_proxied_echo_device() -> ProxyDevice {
720
let device = EchoDevice::new();
721
let keep_fds: Vec<RawDescriptor> = Vec::new();
722
let minijail = Minijail::new().unwrap();
723
ProxyDevice::new(
724
device,
725
minijail,
726
keep_fds,
727
#[cfg(feature = "swap")]
728
&mut None::<swap::SwapController>,
729
)
730
.unwrap()
731
}
732
733
// TODO(b/173833661): Find a way to ensure these tests are run single-threaded.
734
#[test]
735
#[ignore]
736
fn test_debug_label() {
737
let proxy_device = new_proxied_echo_device();
738
assert_eq!(proxy_device.debug_label(), "EchoDevice");
739
}
740
741
#[test]
742
#[ignore]
743
fn test_proxied_read_write() {
744
let mut proxy_device = new_proxied_echo_device();
745
let address = BusAccessInfo {
746
offset: 0,
747
address: 0,
748
id: 0,
749
};
750
proxy_device.write(address, &[42]);
751
let mut read_buffer = [0];
752
proxy_device.read(address, &mut read_buffer);
753
assert_eq!(read_buffer, [42]);
754
}
755
756
#[test]
757
#[ignore]
758
fn test_proxied_config() {
759
let mut proxy_device = new_proxied_echo_device();
760
proxy_device.config_register_write(0, 0, &[42]);
761
assert_eq!(proxy_device.config_register_read(0), 42);
762
}
763
}
764
765