vm_device/device_manager.rs
1// Copyright © 2019 Intel Corporation. All Rights Reserved.
2// SPDX-License-Identifier: (Apache-2.0 OR BSD-3-Clause)
3
4//! System level device management.
5//!
6//! [`IoManager`] is responsible for managing
7//! all devices of virtual machine, registering IO resources callback,
8//! deregistering devices and helping VM IO exit handling.
9//! It defines two buses, one for PIO and one for MMIO, and provides default
10//! implementations of [`PioManager`] and [`MmioManager`].
11//!
12//! The VMM must first allocate unique resources (such as bus ranges), and then
13//! call into the vm-device interface to register the devices with their
14//! corresponding resources.
15//!
16//! # Examples
17//!
18//! Registering a new device can be done using the register methods of [`PioManager`]
19//! and [`MmioManager`] with an appropriate bus range
20//! ([`PioRange`](../bus/type.PioRange.html) or [`MmioRange`](../bus/type.MmioRange.html)).
21//! ```
22//! # use std::sync::Arc;
23//! # use vm_device::bus::{PioAddress, PioAddressOffset, PioRange};
24//! # use vm_device::bus::{MmioAddress, MmioAddressOffset, MmioRange};
25//! # use vm_device::device_manager::{IoManager, PioManager, MmioManager};
26//! # use vm_device::{DevicePio, DeviceMmio};
27//! struct NoopDevice {}
28//!
29//! impl DevicePio for NoopDevice {
30//! fn pio_read(&self, base: PioAddress, offset: PioAddressOffset, data: &mut [u8]) {}
31//! fn pio_write(&self, base: PioAddress, offset: PioAddressOffset, data: &[u8]) {}
32//! }
33//!
34//! impl DeviceMmio for NoopDevice {
35//! fn mmio_read(&self, base: MmioAddress, offset: MmioAddressOffset, data: &mut [u8]) {}
36//! fn mmio_write(&self, base: MmioAddress, offset: MmioAddressOffset, data: &[u8]) {}
37//! }
38//!
39//! // IoManager implements both PioManager and MmioManager.
40//! let mut manager = IoManager::new();
41//!
42//! // Register the device on the PIO bus.
43//! let pio_range = PioRange::new(PioAddress(0), 10).unwrap();
44//! manager
45//! .register_pio(pio_range, Arc::new(NoopDevice {}))
46//! .unwrap();
47//!
48//! // Register the device on the MMIO bus.
49//! let mmio_range = MmioRange::new(MmioAddress(0), 10).unwrap();
50//! manager
51//! .register_mmio(mmio_range, Arc::new(NoopDevice {}))
52//! .unwrap();
53//!
54//! // Dispatch I/O on the PIO bus.
55//! manager.pio_write(PioAddress(0), &vec![b'o', b'k']).unwrap();
56//!
57//! // Dispatch I/O on the MMIO bus.
58//! manager
59//! .mmio_write(MmioAddress(0), &vec![b'o', b'k'])
60//! .unwrap();
61//! ```
62//!
63//! An alternative way would be to use [`resources`](../resources/index.html) and the
64//! resources registration methods of [`IoManager`]:
65//! * [`register_pio_resources`](struct.IoManager.html#method.register_pio_resources)
66//! * [`register_mmio_resources`](struct.IoManager.html#method.register_mmio_resources)
67//! * or generic [`register_resources`](struct.IoManager.html#method.register_resources)
68//! ```
69//! # use std::sync::Arc;
70//! # use vm_device::bus::{PioAddress, PioAddressOffset, PioRange};
71//! # use vm_device::bus::{MmioAddress, MmioAddressOffset, MmioRange};
72//! # use vm_device::device_manager::{IoManager, PioManager, MmioManager};
73//! # use vm_device::{DevicePio, DeviceMmio};
74//! # use vm_device::resources::Resource;
75//! # struct NoopDevice {}
76//! #
77//! # impl DevicePio for NoopDevice {
78//! # fn pio_read(&self, base: PioAddress, offset: PioAddressOffset, data: &mut [u8]) {}
79//! # fn pio_write(&self, base: PioAddress, offset: PioAddressOffset, data: &[u8]) {}
80//! # }
81//! #
82//! # impl DeviceMmio for NoopDevice {
83//! # fn mmio_read(&self, base: MmioAddress, offset: MmioAddressOffset, data: &mut [u8]) {}
84//! # fn mmio_write(&self, base: MmioAddress, offset: MmioAddressOffset, data: &[u8]) {}
85//! # }
86//! // Use the same NoopDevice defined above.
87//!
88//! let mut manager = IoManager::new();
89//!
90//! // Define a PIO address range resource.
91//! let pio = Resource::PioAddressRange {
92//! base: 0,
93//! size: 10,
94//! };
95//!
96//! // Define a MMIO address range resource.
97//! let mmio = Resource::MmioAddressRange {
98//! base: 0,
99//! size: 10,
100//! };
101//!
102//! // Register the PIO resource.
103//! manager
104//! .register_pio_resources(Arc::new(NoopDevice {}), &vec![pio])
105//! .unwrap();
106//!
107//! // Register the MMIO resource.
108//! manager
109//! .register_mmio_resources(Arc::new(NoopDevice {}), &vec![mmio])
110//! .unwrap();
111//!
112//! // Dispatching I/O is the same.
113//! manager.pio_write(PioAddress(0), &vec![b'o', b'k']).unwrap();
114//! manager.mmio_write(MmioAddress(0), &vec![b'o', b'k']).unwrap();
115//! ```
116
117use std::fmt::{Display, Formatter};
118use std::result::Result;
119use std::sync::Arc;
120
121use crate::bus::{self, BusManager, MmioAddress, MmioBus, MmioRange, PioAddress, PioBus, PioRange};
122use crate::resources::Resource;
123use crate::{DeviceMmio, DevicePio};
124
125/// Error type for [IoManager] usage.
126#[derive(Debug)]
127pub enum Error {
128 /// Error during bus operation.
129 Bus(bus::Error),
130}
131
132impl Display for Error {
133 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
134 match self {
135 Error::Bus(_) => write!(f, "device_manager: bus error"),
136 }
137 }
138}
139
140impl std::error::Error for Error {
141 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
142 match self {
143 Error::Bus(e) => Some(e),
144 }
145 }
146}
147
148/// Represents an object that provides PIO manager operations.
149pub trait PioManager {
150 /// Type of the objects that can be registered with this `PioManager`.
151 type D: DevicePio;
152
153 /// Return a reference to the device registered at `addr`, together with the associated
154 /// range, if available.
155 fn pio_device(&self, addr: PioAddress) -> Option<(&PioRange, &Self::D)>;
156
157 /// Dispatch a read operation to the device registered at `addr`.
158 fn pio_read(&self, addr: PioAddress, data: &mut [u8]) -> Result<(), bus::Error>;
159
160 /// Dispatch a write operation to the device registered at `addr`.
161 fn pio_write(&self, addr: PioAddress, data: &[u8]) -> Result<(), bus::Error>;
162
163 /// Register the provided device with the specified range.
164 fn register_pio(&mut self, range: PioRange, device: Self::D) -> Result<(), bus::Error>;
165
166 /// Deregister the device currently registered at `addr` together with the
167 /// associated range.
168 fn deregister_pio(&mut self, addr: PioAddress) -> Option<(PioRange, Self::D)>;
169}
170
171// This automatically provides a `PioManager` implementation for types that already implement
172// `BusManager<PioAddress>` if their inner associated type implements `DevicePio` as well.
173impl<T> PioManager for T
174where
175 T: BusManager<PioAddress>,
176 T::D: DevicePio,
177{
178 type D = <Self as BusManager<PioAddress>>::D;
179
180 fn pio_device(&self, addr: PioAddress) -> Option<(&PioRange, &Self::D)> {
181 self.bus().device(addr)
182 }
183
184 fn pio_read(&self, addr: PioAddress, data: &mut [u8]) -> Result<(), bus::Error> {
185 self.bus()
186 .check_access(addr, data.len())
187 .map(|(range, device)| device.pio_read(range.base(), addr - range.base(), data))
188 }
189
190 fn pio_write(&self, addr: PioAddress, data: &[u8]) -> Result<(), bus::Error> {
191 self.bus()
192 .check_access(addr, data.len())
193 .map(|(range, device)| device.pio_write(range.base(), addr - range.base(), data))
194 }
195
196 fn register_pio(&mut self, range: PioRange, device: Self::D) -> Result<(), bus::Error> {
197 self.bus_mut().register(range, device)
198 }
199
200 fn deregister_pio(&mut self, addr: PioAddress) -> Option<(PioRange, Self::D)> {
201 self.bus_mut().deregister(addr)
202 }
203}
204
205/// Represents an object that provides MMIO manager operations.
206pub trait MmioManager {
207 /// Type of the objects that can be registered with this `MmioManager`.
208 type D: DeviceMmio;
209
210 /// Return a reference to the device registered at `addr`, together with the associated
211 /// range, if available.
212 fn mmio_device(&self, addr: MmioAddress) -> Option<(&MmioRange, &Self::D)>;
213
214 /// Dispatch a read operation to the device registered at `addr`.
215 fn mmio_read(&self, addr: MmioAddress, data: &mut [u8]) -> Result<(), bus::Error>;
216
217 /// Dispatch a write operation to the device registered at `addr`.
218 fn mmio_write(&self, addr: MmioAddress, data: &[u8]) -> Result<(), bus::Error>;
219
220 /// Register the provided device with the specified range.
221 fn register_mmio(&mut self, range: MmioRange, device: Self::D) -> Result<(), bus::Error>;
222
223 /// Deregister the device currently registered at `addr` together with the
224 /// associated range.
225 fn deregister_mmio(&mut self, addr: MmioAddress) -> Option<(MmioRange, Self::D)>;
226}
227
228// This automatically provides a `MmioManager` implementation for types that already implement
229// `BusManager<MmioAddress>` if their inner associated type implements `DeviceMmio` as well.
230impl<T> MmioManager for T
231where
232 T: BusManager<MmioAddress>,
233 T::D: DeviceMmio,
234{
235 type D = <Self as BusManager<MmioAddress>>::D;
236
237 fn mmio_device(&self, addr: MmioAddress) -> Option<(&MmioRange, &Self::D)> {
238 self.bus().device(addr)
239 }
240
241 fn mmio_read(&self, addr: MmioAddress, data: &mut [u8]) -> Result<(), bus::Error> {
242 self.bus()
243 .check_access(addr, data.len())
244 .map(|(range, device)| device.mmio_read(range.base(), addr - range.base(), data))
245 }
246
247 fn mmio_write(&self, addr: MmioAddress, data: &[u8]) -> Result<(), bus::Error> {
248 self.bus()
249 .check_access(addr, data.len())
250 .map(|(range, device)| device.mmio_write(range.base(), addr - range.base(), data))
251 }
252
253 fn register_mmio(&mut self, range: MmioRange, device: Self::D) -> Result<(), bus::Error> {
254 self.bus_mut().register(range, device)
255 }
256
257 fn deregister_mmio(&mut self, addr: MmioAddress) -> Option<(MmioRange, Self::D)> {
258 self.bus_mut().deregister(addr)
259 }
260}
261
262/// System IO manager serving for all devices management and VM exit handling.
263#[derive(Default)]
264pub struct IoManager {
265 // Range mapping for VM exit pio operations.
266 pio_bus: PioBus<Arc<dyn DevicePio + Send + Sync>>,
267 // Range mapping for VM exit mmio operations.
268 mmio_bus: MmioBus<Arc<dyn DeviceMmio + Send + Sync>>,
269}
270
271// Enables the automatic implementation of `PioManager` for `IoManager`.
272impl BusManager<PioAddress> for IoManager {
273 type D = Arc<dyn DevicePio + Send + Sync>;
274
275 fn bus(&self) -> &PioBus<Arc<dyn DevicePio + Send + Sync>> {
276 &self.pio_bus
277 }
278
279 fn bus_mut(&mut self) -> &mut PioBus<Arc<dyn DevicePio + Send + Sync>> {
280 &mut self.pio_bus
281 }
282}
283
284// Enables the automatic implementation of `MmioManager` for `IoManager`.
285impl BusManager<MmioAddress> for IoManager {
286 type D = Arc<dyn DeviceMmio + Send + Sync>;
287
288 fn bus(&self) -> &MmioBus<Arc<dyn DeviceMmio + Send + Sync>> {
289 &self.mmio_bus
290 }
291
292 fn bus_mut(&mut self) -> &mut MmioBus<Arc<dyn DeviceMmio + Send + Sync>> {
293 &mut self.mmio_bus
294 }
295}
296
297impl IoManager {
298 /// Create an default IoManager with empty IO member.
299 pub fn new() -> Self {
300 IoManager::default()
301 }
302
303 /// Register a new MMIO device with its allocated resources.
304 /// VMM is responsible for providing the allocated resources to virtual device.
305 ///
306 /// # Arguments
307 ///
308 /// * `device`: device instance object to be registered
309 /// * `resources`: resources that this device owns, might include
310 /// port I/O and memory-mapped I/O ranges, irq number, etc.
311 pub fn register_mmio_resources(
312 &mut self,
313 device: Arc<dyn DeviceMmio + Send + Sync>,
314 resources: &[Resource],
315 ) -> Result<(), Error> {
316 // Register and mark device resources
317 // The resources addresses being registered are sucessfully allocated before.
318 for res in resources.iter() {
319 match *res {
320 Resource::MmioAddressRange { base, size } => {
321 self.register_mmio(
322 MmioRange::new(MmioAddress(base), size).unwrap(),
323 device.clone(),
324 )
325 .map_err(Error::Bus)?;
326 }
327 _ => continue,
328 }
329 }
330 Ok(())
331 }
332
333 /// Register a new PIO device with its allocated resources.
334 /// VMM is responsible for providing the allocated resources to virtual device.
335 ///
336 /// # Arguments
337 ///
338 /// * `device`: device instance object to be registered
339 /// * `resources`: resources that this device owns, might include
340 /// port I/O and memory-mapped I/O ranges, irq number, etc.
341 pub fn register_pio_resources(
342 &mut self,
343 device: Arc<dyn DevicePio + Send + Sync>,
344 resources: &[Resource],
345 ) -> Result<(), Error> {
346 // Register and mark device resources
347 // The resources addresses being registered are sucessfully allocated before.
348 for res in resources.iter() {
349 match *res {
350 Resource::PioAddressRange { base, size } => {
351 self.register_pio(
352 PioRange::new(PioAddress(base), size).unwrap(),
353 device.clone(),
354 )
355 .map_err(Error::Bus)?;
356 }
357 _ => continue,
358 }
359 }
360 Ok(())
361 }
362
363 /// Register a new MMIO + PIO device with its allocated resources.
364 /// VMM is responsible for providing the allocated resources to virtual device.
365 ///
366 /// # Arguments
367 ///
368 /// * `device`: device instance object to be registered
369 /// * `resources`: resources that this device owns, might include
370 /// port I/O and memory-mapped I/O ranges, irq number, etc.
371 pub fn register_resources<T: DeviceMmio + DevicePio + 'static + Send + Sync>(
372 &mut self,
373 device: Arc<T>,
374 resources: &[Resource],
375 ) -> Result<(), Error> {
376 self.register_mmio_resources(device.clone(), resources)?;
377 self.register_pio_resources(device, resources)
378 }
379
380 /// Deregister a device from `IoManager`, e.g. users specified removing.
381 /// VMM pre-fetches the resources e.g. dev.get_assigned_resources()
382 /// VMM is responsible for freeing the resources. Returns the number
383 /// of deregistered devices.
384 ///
385 /// # Arguments
386 ///
387 /// * `resources`: resources that this device owns, might include
388 /// port I/O and memory-mapped I/O ranges, irq number, etc.
389 pub fn deregister_resources(&mut self, resources: &[Resource]) -> usize {
390 let mut count = 0;
391 for res in resources.iter() {
392 match *res {
393 Resource::PioAddressRange { base, .. } => {
394 if self.deregister_pio(PioAddress(base)).is_some() {
395 count += 1;
396 }
397 }
398 Resource::MmioAddressRange { base, .. } => {
399 if self.deregister_mmio(MmioAddress(base)).is_some() {
400 count += 1;
401 }
402 }
403 _ => continue,
404 }
405 }
406 count
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413
414 use std::error::Error;
415 use std::sync::Mutex;
416
417 use bus::{MmioAddressOffset, PioAddressOffset};
418
419 const PIO_ADDRESS_SIZE: u16 = 4;
420 const PIO_ADDRESS_BASE: u16 = 0x40;
421 const MMIO_ADDRESS_SIZE: u64 = 0x8765_4321;
422 const MMIO_ADDRESS_BASE: u64 = 0x1234_5678;
423 const LEGACY_IRQ: u32 = 4;
424 const CONFIG_DATA: u32 = 0x1234;
425
426 struct DummyDevice {
427 config: Mutex<u32>,
428 }
429
430 impl DummyDevice {
431 fn new(config: u32) -> Self {
432 DummyDevice {
433 config: Mutex::new(config),
434 }
435 }
436 }
437
438 impl DevicePio for DummyDevice {
439 fn pio_read(&self, _base: PioAddress, _offset: PioAddressOffset, data: &mut [u8]) {
440 if data.len() > 4 {
441 return;
442 }
443 for (idx, iter) in data.iter_mut().enumerate() {
444 let config = self.config.lock().expect("failed to acquire lock");
445 *iter = (*config >> (idx * 8) & 0xff) as u8;
446 }
447 }
448
449 fn pio_write(&self, _base: PioAddress, _offset: PioAddressOffset, data: &[u8]) {
450 let mut config = self.config.lock().expect("failed to acquire lock");
451 *config = u32::from(data[0]) & 0xff;
452 }
453 }
454
455 impl DeviceMmio for DummyDevice {
456 fn mmio_read(&self, _base: MmioAddress, _offset: MmioAddressOffset, data: &mut [u8]) {
457 if data.len() > 4 {
458 return;
459 }
460 for (idx, iter) in data.iter_mut().enumerate() {
461 let config = self.config.lock().expect("failed to acquire lock");
462 *iter = (*config >> (idx * 8) & 0xff) as u8;
463 }
464 }
465
466 fn mmio_write(&self, _base: MmioAddress, _offset: MmioAddressOffset, data: &[u8]) {
467 let mut config = self.config.lock().expect("failed to acquire lock");
468 *config = u32::from(data[0]) & 0xff;
469 }
470 }
471
472 #[test]
473 fn test_register_deregister_device_io() {
474 let mut io_mgr = IoManager::new();
475 let dummy = DummyDevice::new(0);
476 let dum = Arc::new(dummy);
477
478 let mut resource: Vec<Resource> = Vec::new();
479 let mmio = Resource::MmioAddressRange {
480 base: MMIO_ADDRESS_BASE,
481 size: MMIO_ADDRESS_SIZE,
482 };
483 let irq = Resource::LegacyIrq(LEGACY_IRQ);
484 let pio = Resource::PioAddressRange {
485 base: PIO_ADDRESS_BASE,
486 size: PIO_ADDRESS_SIZE,
487 };
488
489 resource.push(mmio);
490 resource.push(irq);
491 resource.push(pio);
492
493 assert!(io_mgr
494 .register_mmio_resources(dum.clone(), &resource)
495 .is_ok());
496 assert!(io_mgr.register_pio_resources(dum, &resource).is_ok());
497 assert_eq!(io_mgr.deregister_resources(&resource), 2);
498 }
499
500 #[test]
501 fn test_mmio_read_write() {
502 let mut io_mgr: IoManager = Default::default();
503 let dum = Arc::new(DummyDevice::new(CONFIG_DATA));
504 let mut resource: Vec<Resource> = Vec::new();
505
506 let mmio = Resource::MmioAddressRange {
507 base: MMIO_ADDRESS_BASE,
508 size: MMIO_ADDRESS_SIZE,
509 };
510 resource.push(mmio);
511 assert!(io_mgr
512 .register_mmio_resources(dum.clone(), &resource)
513 .is_ok());
514
515 let mut data = [0; 4];
516 assert!(io_mgr
517 .mmio_read(MmioAddress(MMIO_ADDRESS_BASE), &mut data)
518 .is_ok());
519 assert_eq!(data, [0x34, 0x12, 0, 0]);
520
521 assert!(io_mgr
522 .mmio_read(
523 MmioAddress(MMIO_ADDRESS_BASE + MMIO_ADDRESS_SIZE),
524 &mut data
525 )
526 .is_err());
527
528 data = [0; 4];
529 assert!(io_mgr
530 .mmio_write(MmioAddress(MMIO_ADDRESS_BASE), &data)
531 .is_ok());
532 assert_eq!(*dum.config.lock().unwrap(), 0);
533
534 assert!(io_mgr
535 .mmio_write(MmioAddress(MMIO_ADDRESS_BASE + MMIO_ADDRESS_SIZE), &data)
536 .is_err());
537 }
538
539 #[test]
540 fn test_pio_read_write() {
541 let mut io_mgr: IoManager = Default::default();
542 let dum = Arc::new(DummyDevice::new(CONFIG_DATA));
543 let mut resource: Vec<Resource> = Vec::new();
544
545 let pio = Resource::PioAddressRange {
546 base: PIO_ADDRESS_BASE,
547 size: PIO_ADDRESS_SIZE,
548 };
549 resource.push(pio);
550 assert!(io_mgr
551 .register_pio_resources(dum.clone(), &resource)
552 .is_ok());
553
554 let mut data = [0; 4];
555 assert!(io_mgr
556 .pio_read(PioAddress(PIO_ADDRESS_BASE), &mut data)
557 .is_ok());
558 assert_eq!(data, [0x34, 0x12, 0, 0]);
559
560 assert!(io_mgr
561 .pio_read(PioAddress(PIO_ADDRESS_BASE + PIO_ADDRESS_SIZE), &mut data)
562 .is_err());
563
564 data = [0; 4];
565 assert!(io_mgr
566 .pio_write(PioAddress(PIO_ADDRESS_BASE), &data)
567 .is_ok());
568 assert_eq!(*dum.config.lock().unwrap(), 0);
569
570 assert!(io_mgr
571 .pio_write(PioAddress(PIO_ADDRESS_BASE + PIO_ADDRESS_SIZE), &data)
572 .is_err());
573 }
574
575 #[test]
576 fn test_error_code() {
577 let err = super::Error::Bus(bus::Error::DeviceOverlap);
578
579 assert!(err.source().is_some());
580 assert_eq!(format!("{}", err), "device_manager: bus error");
581 }
582}