/src/crosvm/devices/src/proxy.rs
Line | Count | Source |
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 | 0 | fn new() -> anyhow::Result<SnapshotFile> { |
73 | | Ok(SnapshotFile { |
74 | 0 | file: tempfile().context("failed to create snasphot wrapper tempfile")?, |
75 | | }) |
76 | 0 | } |
77 | | |
78 | 0 | fn from_data(data: AnySnapshot) -> anyhow::Result<SnapshotFile> { |
79 | 0 | let mut snapshot = SnapshotFile::new()?; |
80 | 0 | snapshot.write(data)?; |
81 | 0 | Ok(snapshot) |
82 | 0 | } |
83 | | |
84 | 0 | fn read(&mut self) -> anyhow::Result<AnySnapshot> { |
85 | 0 | let data: AnySnapshot = ciborium::from_reader(&mut BufReader::new(&self.file)) |
86 | 0 | .context("failed to read snapshot data from snapshot temp file")?; |
87 | | |
88 | 0 | self.file |
89 | 0 | .rewind() |
90 | 0 | .context("failed to rewind snapshot temp file after read")?; |
91 | | |
92 | 0 | Ok(data) |
93 | 0 | } |
94 | | |
95 | 0 | fn write(&mut self, data: AnySnapshot) -> anyhow::Result<()> { |
96 | | { |
97 | 0 | let mut writer = BufWriter::new(&self.file); |
98 | | |
99 | 0 | ciborium::into_writer(&data, &mut writer) |
100 | 0 | .context("failed to write data to snasphot temp file")?; |
101 | | |
102 | 0 | writer |
103 | 0 | .flush() |
104 | 0 | .context("failed to flush data to snapshot temp file")?; |
105 | | } |
106 | | |
107 | 0 | self.file |
108 | 0 | .rewind() |
109 | 0 | .context("failed to rewind snapshot temp file after write")?; |
110 | | |
111 | 0 | Ok(()) |
112 | 0 | } |
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 | 0 | fn child_proc<D: BusDevice>(tube: Tube, mut device: D) { |
181 | | // Wait for activation signal to function as BusDevice. |
182 | 0 | match tube.recv() { |
183 | | Ok(Command::Activate) => { |
184 | 0 | if let Err(e) = tube.send(&CommandResult::Ok) { |
185 | 0 | error!( |
186 | 0 | "sending {} activation result failed: {}", |
187 | 0 | device.debug_label(), |
188 | | e, |
189 | | ); |
190 | 0 | return; |
191 | 0 | } |
192 | | } |
193 | | // Commands other than activate is unexpected, close device. |
194 | 0 | Ok(cmd) => { |
195 | 0 | panic!("Receiving Command {:?} before device is activated", &cmd); |
196 | | } |
197 | | // Most likely tube error is caused by other end is dropped, release resource. |
198 | 0 | Err(e) => { |
199 | 0 | error!( |
200 | 0 | "{} device failed before activation: {}. Dropping device", |
201 | 0 | device.debug_label(), |
202 | | e, |
203 | | ); |
204 | 0 | drop(device); |
205 | 0 | return; |
206 | | } |
207 | | }; |
208 | | loop { |
209 | 0 | let cmd = match tube.recv() { |
210 | 0 | Ok(cmd) => cmd, |
211 | 0 | Err(e) => { |
212 | 0 | error!( |
213 | 0 | "recv from {} child device process failed: {}", |
214 | 0 | device.debug_label(), |
215 | | e, |
216 | | ); |
217 | 0 | break; |
218 | | } |
219 | | }; |
220 | | |
221 | 0 | let res = match cmd { |
222 | | Command::Activate => { |
223 | 0 | panic!("Device shall only be activated once, duplicated ProxyDevice likely"); |
224 | | } |
225 | 0 | Command::Read { len, info } => { |
226 | 0 | let mut buffer = [0u8; 8]; |
227 | 0 | device.read(info, &mut buffer[0..len as usize]); |
228 | 0 | tube.send(&CommandResult::ReadResult(buffer)) |
229 | | } |
230 | 0 | Command::Write { len, info, data } => { |
231 | 0 | let len = len as usize; |
232 | 0 | device.write(info, &data[0..len]); |
233 | | // Command::Write does not have a result. |
234 | 0 | Ok(()) |
235 | | } |
236 | 0 | Command::ReadConfig(idx) => { |
237 | 0 | let val = device.config_register_read(idx as usize); |
238 | 0 | tube.send(&CommandResult::ReadConfigResult(val)) |
239 | | } |
240 | | Command::WriteConfig { |
241 | 0 | reg_idx, |
242 | 0 | offset, |
243 | 0 | len, |
244 | 0 | data, |
245 | | } => { |
246 | 0 | let len = len as usize; |
247 | 0 | let res = |
248 | 0 | device.config_register_write(reg_idx as usize, offset as u64, &data[0..len]); |
249 | 0 | tube.send(&CommandResult::WriteConfigResult { |
250 | 0 | mmio_remove: res.mmio_remove, |
251 | 0 | mmio_add: res.mmio_add, |
252 | 0 | io_remove: res.io_remove, |
253 | 0 | io_add: res.io_add, |
254 | 0 | removed_pci_devices: res.removed_pci_devices, |
255 | 0 | }) |
256 | | } |
257 | 0 | Command::InitPciConfigMapping { shmem, base, len } => { |
258 | 0 | let success = device.init_pci_config_mapping(&shmem, base, len); |
259 | 0 | tube.send(&CommandResult::InitPciConfigMappingResult(success)) |
260 | | } |
261 | 0 | Command::ReadVirtualConfig(idx) => { |
262 | 0 | let val = device.virtual_config_register_read(idx as usize); |
263 | 0 | tube.send(&CommandResult::ReadVirtualConfigResult(val)) |
264 | | } |
265 | 0 | Command::WriteVirtualConfig { reg_idx, value } => { |
266 | 0 | device.virtual_config_register_write(reg_idx as usize, value); |
267 | 0 | tube.send(&CommandResult::Ok) |
268 | | } |
269 | | Command::DestroyDevice => { |
270 | 0 | device.destroy_device(); |
271 | 0 | 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 | 0 | drop(device); |
277 | | |
278 | 0 | let _ = tube.send(&CommandResult::Ok); |
279 | 0 | return; |
280 | | } |
281 | | Command::GetRanges => { |
282 | 0 | let ranges = device.get_ranges(); |
283 | 0 | tube.send(&CommandResult::GetRangesResult(ranges)) |
284 | | } |
285 | 0 | Command::Snapshot { mut snapshot } => { |
286 | 0 | let res = device.snapshot().and_then(|data| { |
287 | 0 | snapshot.write(data)?; |
288 | 0 | Ok(snapshot) |
289 | 0 | }); |
290 | 0 | tube.send(&CommandResult::SnapshotResult( |
291 | 0 | res.map_err(|e| e.to_string()), |
292 | | )) |
293 | | } |
294 | 0 | Command::Restore { mut snapshot } => { |
295 | 0 | let res = snapshot.read().and_then(|data| device.restore(data)); |
296 | 0 | tube.send(&CommandResult::RestoreResult( |
297 | 0 | res.map_err(|e| e.to_string()), |
298 | | )) |
299 | | } |
300 | | Command::Sleep => { |
301 | 0 | let res = device.sleep(); |
302 | 0 | tube.send(&CommandResult::SleepResult(res.map_err(|e| e.to_string()))) |
303 | | } |
304 | | Command::Wake => { |
305 | 0 | let res = device.wake(); |
306 | 0 | tube.send(&CommandResult::WakeResult(res.map_err(|e| e.to_string()))) |
307 | | } |
308 | | }; |
309 | 0 | if let Err(e) = res { |
310 | 0 | error!( |
311 | 0 | "send to {} child device process failed: {}", |
312 | 0 | device.debug_label(), |
313 | | e, |
314 | | ); |
315 | 0 | } |
316 | | } |
317 | 0 | } |
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 | 0 | pub fn new<D: BusDevice, #[cfg(feature = "swap")] P: swap::PrepareFork>( |
344 | 0 | mut device: D, |
345 | 0 | jail: Minijail, |
346 | 0 | mut keep_rds: Vec<RawDescriptor>, |
347 | 0 | #[cfg(feature = "swap")] swap_prepare_fork: &mut Option<P>, |
348 | 0 | ) -> Result<ChildProcIntf> { |
349 | 0 | let debug_label = device.debug_label(); |
350 | 0 | let (child_tube, parent_tube) = Tube::pair()?; |
351 | | |
352 | 0 | 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 | 0 | if cfg!(target_arch = "x86_64") && debug_label == "pcivirtio-gpu" { |
366 | 0 | if let Ok(cmd) = fs::read_to_string("/proc/self/cmdline") { |
367 | 0 | if cmd.contains("arcvm") { |
368 | 0 | if let Ok(share) = fs::read_to_string("/sys/fs/cgroup/cpu/arcvm/cpu.shares") { |
369 | 0 | info!("arcvm cpu share when booting gpu is {:}", share.trim()); |
370 | 0 | } |
371 | 0 | } |
372 | 0 | } |
373 | 0 | } |
374 | | |
375 | 0 | 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 | 0 | device.on_sandboxed(); |
387 | 0 | 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 | 0 | unsafe { libc::exit(0) }; |
399 | 0 | })?; |
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 | 0 | let pid = child_process.into_pid(); |
405 | | |
406 | 0 | Ok(ChildProcIntf { |
407 | 0 | tube: parent_tube, |
408 | 0 | pid, |
409 | 0 | debug_label, |
410 | 0 | }) |
411 | 0 | } |
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 | 0 | fn try_from(child_proc_intf: ChildProcIntf) -> Result<Self> { |
424 | | // Notify child process to be activated as a BusDevice. |
425 | 0 | child_proc_intf.tube.send(&Command::Activate)?; |
426 | | // Device returns Ok if it is activated only once. |
427 | 0 | match child_proc_intf.tube.recv()? { |
428 | 0 | CommandResult::Ok => Ok(Self { child_proc_intf }), |
429 | 0 | _ => Err(Error::ActivatingProxyDevice), |
430 | | } |
431 | 0 | } |
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 | 0 | pub fn new<D: BusDevice, #[cfg(feature = "swap")] P: swap::PrepareFork>( |
447 | 0 | device: D, |
448 | 0 | jail: Minijail, |
449 | 0 | keep_rds: Vec<RawDescriptor>, |
450 | 0 | #[cfg(feature = "swap")] swap_prepare_fork: &mut Option<P>, |
451 | 0 | ) -> Result<ProxyDevice> { |
452 | 0 | ChildProcIntf::new( |
453 | 0 | device, |
454 | 0 | jail, |
455 | 0 | keep_rds, |
456 | | #[cfg(feature = "swap")] |
457 | | swap_prepare_fork, |
458 | 0 | )? |
459 | 0 | .try_into() |
460 | 0 | } |
461 | | |
462 | 0 | pub fn pid(&self) -> pid_t { |
463 | 0 | self.child_proc_intf.pid |
464 | 0 | } |
465 | | |
466 | | /// Send a command that does not expect a response from the child device process. |
467 | 0 | fn send_no_result(&self, cmd: &Command) { |
468 | 0 | let res = self.child_proc_intf.tube.send(cmd); |
469 | 0 | if let Err(e) = res { |
470 | 0 | error!( |
471 | 0 | "failed write to child device process {}: {}", |
472 | | self.child_proc_intf.debug_label, e, |
473 | | ); |
474 | 0 | } |
475 | 0 | } |
476 | | |
477 | | /// Send a command and read its response from the child device process. |
478 | 0 | fn sync_send(&self, cmd: &Command) -> Option<CommandResult> { |
479 | 0 | self.send_no_result(cmd); |
480 | 0 | match self.child_proc_intf.tube.recv() { |
481 | 0 | Err(e) => { |
482 | 0 | error!( |
483 | 0 | "failed to read result of {:?} from child device process {}: {}", |
484 | | cmd, self.child_proc_intf.debug_label, e, |
485 | | ); |
486 | 0 | None |
487 | | } |
488 | 0 | Ok(r) => Some(r), |
489 | | } |
490 | 0 | } |
491 | | } |
492 | | |
493 | | impl BusDevice for ProxyDevice { |
494 | 0 | fn device_id(&self) -> DeviceId { |
495 | 0 | PlatformDeviceId::ProxyDevice.into() |
496 | 0 | } |
497 | | |
498 | 0 | fn debug_label(&self) -> String { |
499 | 0 | self.child_proc_intf.debug_label.clone() |
500 | 0 | } |
501 | | |
502 | 0 | fn config_register_write( |
503 | 0 | &mut self, |
504 | 0 | reg_idx: usize, |
505 | 0 | offset: u64, |
506 | 0 | data: &[u8], |
507 | 0 | ) -> ConfigWriteResult { |
508 | 0 | let len = data.len() as u32; |
509 | 0 | let mut buffer = [0u8; 4]; |
510 | 0 | buffer[0..data.len()].clone_from_slice(data); |
511 | 0 | let reg_idx = reg_idx as u32; |
512 | 0 | let offset = offset as u32; |
513 | | if let Some(CommandResult::WriteConfigResult { |
514 | 0 | mmio_remove, |
515 | 0 | mmio_add, |
516 | 0 | io_remove, |
517 | 0 | io_add, |
518 | 0 | removed_pci_devices, |
519 | 0 | }) = self.sync_send(&Command::WriteConfig { |
520 | 0 | reg_idx, |
521 | 0 | offset, |
522 | 0 | len, |
523 | 0 | data: buffer, |
524 | 0 | }) { |
525 | 0 | ConfigWriteResult { |
526 | 0 | mmio_remove, |
527 | 0 | mmio_add, |
528 | 0 | io_remove, |
529 | 0 | io_add, |
530 | 0 | removed_pci_devices, |
531 | 0 | } |
532 | | } else { |
533 | 0 | Default::default() |
534 | | } |
535 | 0 | } |
536 | | |
537 | 0 | fn config_register_read(&self, reg_idx: usize) -> u32 { |
538 | 0 | let res = self.sync_send(&Command::ReadConfig(reg_idx as u32)); |
539 | 0 | if let Some(CommandResult::ReadConfigResult(val)) = res { |
540 | 0 | val |
541 | | } else { |
542 | 0 | 0 |
543 | | } |
544 | 0 | } |
545 | | |
546 | 0 | fn init_pci_config_mapping(&mut self, shmem: &SharedMemory, base: usize, len: usize) -> bool { |
547 | 0 | let Ok(shmem) = shmem.try_clone() else { |
548 | 0 | error!("Failed to clone pci config mapping shmem"); |
549 | 0 | return false; |
550 | | }; |
551 | 0 | let res = self.sync_send(&Command::InitPciConfigMapping { shmem, base, len }); |
552 | 0 | matches!(res, Some(CommandResult::InitPciConfigMappingResult(true))) |
553 | 0 | } |
554 | | |
555 | 0 | fn virtual_config_register_write(&mut self, reg_idx: usize, value: u32) { |
556 | 0 | let reg_idx = reg_idx as u32; |
557 | 0 | self.sync_send(&Command::WriteVirtualConfig { reg_idx, value }); |
558 | 0 | } |
559 | | |
560 | 0 | fn virtual_config_register_read(&self, reg_idx: usize) -> u32 { |
561 | 0 | let res = self.sync_send(&Command::ReadVirtualConfig(reg_idx as u32)); |
562 | 0 | if let Some(CommandResult::ReadVirtualConfigResult(val)) = res { |
563 | 0 | val |
564 | | } else { |
565 | 0 | 0 |
566 | | } |
567 | 0 | } |
568 | | |
569 | 0 | fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) { |
570 | 0 | let len = data.len() as u32; |
571 | 0 | if let Some(CommandResult::ReadResult(buffer)) = |
572 | 0 | self.sync_send(&Command::Read { len, info }) |
573 | 0 | { |
574 | 0 | let len = data.len(); |
575 | 0 | data.clone_from_slice(&buffer[0..len]); |
576 | 0 | } |
577 | 0 | } |
578 | | |
579 | 0 | fn write(&mut self, info: BusAccessInfo, data: &[u8]) { |
580 | 0 | let mut buffer = [0u8; 8]; |
581 | 0 | let len = data.len() as u32; |
582 | 0 | buffer[0..data.len()].clone_from_slice(data); |
583 | 0 | self.send_no_result(&Command::Write { |
584 | 0 | len, |
585 | 0 | info, |
586 | 0 | data: buffer, |
587 | 0 | }); |
588 | 0 | } |
589 | | |
590 | 0 | fn get_ranges(&self) -> Vec<(BusRange, BusType)> { |
591 | 0 | if let Some(CommandResult::GetRangesResult(ranges)) = self.sync_send(&Command::GetRanges) { |
592 | 0 | ranges |
593 | | } else { |
594 | 0 | Default::default() |
595 | | } |
596 | 0 | } |
597 | | |
598 | 0 | fn destroy_device(&mut self) { |
599 | 0 | self.send_no_result(&Command::DestroyDevice); |
600 | 0 | } |
601 | | } |
602 | | |
603 | | impl Suspendable for ProxyDevice { |
604 | 0 | fn snapshot(&mut self) -> anyhow::Result<AnySnapshot> { |
605 | 0 | let res = self.sync_send(&Command::Snapshot { |
606 | 0 | snapshot: SnapshotFile::new()?, |
607 | | }); |
608 | 0 | match res { |
609 | 0 | Some(CommandResult::SnapshotResult(Ok(mut snapshot))) => snapshot.read(), |
610 | 0 | Some(CommandResult::SnapshotResult(Err(e))) => Err(anyhow!( |
611 | 0 | "failed to snapshot {}: {:#}", |
612 | 0 | self.debug_label(), |
613 | 0 | e |
614 | 0 | )), |
615 | 0 | _ => Err(anyhow!("unexpected snapshot result {:?}", res)), |
616 | | } |
617 | 0 | } |
618 | | |
619 | 0 | fn restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> { |
620 | 0 | let res = self.sync_send(&Command::Restore { |
621 | 0 | snapshot: SnapshotFile::from_data(data)?, |
622 | | }); |
623 | 0 | match res { |
624 | 0 | Some(CommandResult::RestoreResult(Ok(()))) => Ok(()), |
625 | 0 | Some(CommandResult::RestoreResult(Err(e))) => { |
626 | 0 | Err(anyhow!("failed to restore {}: {:#}", self.debug_label(), e)) |
627 | | } |
628 | 0 | _ => Err(anyhow!("unexpected restore result {:?}", res)), |
629 | | } |
630 | 0 | } |
631 | | |
632 | 0 | fn sleep(&mut self) -> anyhow::Result<()> { |
633 | 0 | let res = self.sync_send(&Command::Sleep); |
634 | 0 | match res { |
635 | 0 | Some(CommandResult::SleepResult(Ok(()))) => Ok(()), |
636 | 0 | Some(CommandResult::SleepResult(Err(e))) => { |
637 | 0 | Err(anyhow!("failed to sleep {}: {:#}", self.debug_label(), e)) |
638 | | } |
639 | 0 | _ => Err(anyhow!("unexpected sleep result {:?}", res)), |
640 | | } |
641 | 0 | } |
642 | | |
643 | 0 | fn wake(&mut self) -> anyhow::Result<()> { |
644 | 0 | let res = self.sync_send(&Command::Wake); |
645 | 0 | match res { |
646 | 0 | Some(CommandResult::WakeResult(Ok(()))) => Ok(()), |
647 | 0 | Some(CommandResult::WakeResult(Err(e))) => { |
648 | 0 | Err(anyhow!("failed to wake {}: {:#}", self.debug_label(), e)) |
649 | | } |
650 | 0 | _ => Err(anyhow!("unexpected wake result {:?}", res)), |
651 | | } |
652 | 0 | } |
653 | | } |
654 | | |
655 | | impl Drop for ProxyDevice { |
656 | 0 | fn drop(&mut self) { |
657 | 0 | self.sync_send(&Command::Shutdown); |
658 | 0 | } |
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 | | } |