|
2 | 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. |
4 | 4 |
|
5 | | -//! Hierarchical access control for emulated resources |
| 5 | +//! Hierarchical access control for emulated resources. |
6 | 6 | //! |
7 | | -//! Device emulation logic requires access to resources which may be |
8 | | -//! subsequently moderated by intervening parts of the emulation. |
| 7 | +//! The structures in this module are designed to support some of our current |
| 8 | +//! needs, but also aspirationally for anticipated needs. |
| 9 | +//! |
| 10 | +//! First and foremost, device emulation logic requires access to resources |
| 11 | +//! which may be subsequently moderated by intervening parts of the emulation. |
| 12 | +//! Acquisition of the underlying resource is fallible for this reason. |
9 | 13 | //! |
10 | 14 | //! For example: A PCI device performs DMA to guest memory. If bus-mastering is |
11 | 15 | //! disabled on the device, or any parent bridge in its bus hierarchy, then its |
12 | 16 | //! contained emulation should fail any DMA accesses. |
| 17 | +//! |
| 18 | +//! This also motivates the tree-like structure of the accessor. Keeping the |
| 19 | +//! PCI bus mastering example, an individual endpoint can be allowed to perform |
| 20 | +//! bus mastering if Bus Master Enable is set. Additionally, a PCI-PCI bridge |
| 21 | +//! has a Bus Master Enable bit with a similar semantic for all devices behind |
| 22 | +//! that bridge. |
| 23 | +//! |
| 24 | +//! There is not yet any support for bus mastering bits, but it's expected this |
| 25 | +//! should be straightforward on top of `Node` or `NodeEntry`. |
| 26 | +//! |
| 27 | +//! Secondly, and more relevant to how Accessor is used in Propolis today, an |
| 28 | +//! accessor tree provides a mechanism to provide or remove a reference to the |
| 29 | +//! protected resource from an entire device or machine. While the accessor tree |
| 30 | +//! is at heart a fancy `Arc<Mutex<Arc<T>>`, an `Arc<T>` is never exposed in the |
| 31 | +//! accessor's API; only a wrapper that derefs as `T`. |
| 32 | +//! |
| 33 | +//! Accessor structures being the sole access mechanism to a guarded resource |
| 34 | +//! ensures that the resource can be added or removed *almost*[1] arbitrarily. |
| 35 | +//! [`MsiAccessor`] is an example of double-duty here; on one hand, a PCI bridge |
| 36 | +//! can have MSI enabled or disabled, as well as the functions behind that |
| 37 | +//! bridge. On the other hand, the MSI accessor is mostly just an `Arc<VmmHdl>`, |
| 38 | +//! and it would be unfortunate to have stray `Arc<VmmHdl>` littered across |
| 39 | +//! device emulation[2]. |
| 40 | +//! |
| 41 | +//! 1: A user of Propolis should only change the guarded resource for devices that |
| 42 | +//! are in the initial (pre-run) state, paused, or halted. Removing a guarded |
| 43 | +//! resource during arbitrary device operation could, at worst, look to a device |
| 44 | +//! like it was the bus master while also losing its ownership of the bus! |
| 45 | +//! There is no expectation of correct operation in such a bogus state. |
| 46 | +//! |
| 47 | +//! 2: `Arc<VmmHdl>` has since found its way into device emulation in different |
| 48 | +//! ways, though the ownership model is simple enough there is little risk of |
| 49 | +//! cyclic references keeping a `VmmHdl` alive overly-long. |
13 | 50 |
|
14 | 51 | use std::collections::{BTreeMap, BTreeSet, VecDeque}; |
15 | 52 | use std::marker::PhantomData; |
@@ -638,12 +675,25 @@ impl<T: AccessedResource> Accessor<T> { |
638 | 675 | /// Will return [None] if any ancestor node disables access, or if the node |
639 | 676 | /// is not attached to a hierarchy containing a valid resource. |
640 | 677 | /// |
641 | | - /// Unlike [`Accesor::access()`], this returns a guard on the node's Mutex. |
642 | | - /// This will block other calls to `access()` or `access_borrow()`, as well |
643 | | - /// as attempts to replace or modify the tree's guarded resource. Prefer |
644 | | - /// frequently refreshing borrows with calls to `access_borrow()` to holding |
645 | | - /// a `LockedView` for substantial durations. |
646 | | - pub fn access_borrow(&self) -> Option<LockedView<'_, T>> { |
| 678 | + /// Unlike [`Accesor::access()`], this returns a wrapped MutexGuard for this |
| 679 | + /// accessor node; callers must carefully consider lock ordering when |
| 680 | + /// holding this guard across other operations. As with any other mutex, |
| 681 | + /// perfer holding this guard for as small a window as permitted. |
| 682 | + /// |
| 683 | + /// This function exists solely to support very hot code accessing the same |
| 684 | + /// resource across many processors. When the underlying resource is an |
| 685 | + /// `Arc`, `access()` implies an `Arc::clone`, which would contentously |
| 686 | + /// modify the reference count to disastrous effect. `access_locked()` only |
| 687 | + /// involves this (hopefully-uncontended!) node, at the cost of a more |
| 688 | + /// error-prone API. If `lock incq/lock decq` aren't in your profile, this |
| 689 | + /// probably isn't helpful! |
| 690 | + /// |
| 691 | + /// Some examples of the added consideration with this function: holding |
| 692 | + /// this guard will block other calls to this node's `access()` or |
| 693 | + /// `access_locked()`, and *may* block attempts to `access()` or |
| 694 | + /// `access_locked()` a child of this node. Holding this guard will block |
| 695 | + /// removal of the underlying resource, potentially blocking VM teardown. |
| 696 | + pub fn access_locked(&self) -> Option<LockedView<'_, T>> { |
647 | 697 | let guard = self.0.guard_borrow()?; |
648 | 698 | if guard.res_leaf.is_some() { |
649 | 699 | Some(LockedView { guard }) |
|
0 commit comments