Skip to content

Commit fcf37ae

Browse files
authored
more words about what Accessor is about (#1084)
1 parent 82f385c commit fcf37ae

File tree

3 files changed

+64
-14
lines changed

3 files changed

+64
-14
lines changed

lib/propolis/src/accessors.rs

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,51 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
//! Hierarchical access control for emulated resources
5+
//! Hierarchical access control for emulated resources.
66
//!
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.
913
//!
1014
//! For example: A PCI device performs DMA to guest memory. If bus-mastering is
1115
//! disabled on the device, or any parent bridge in its bus hierarchy, then its
1216
//! 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.
1350
1451
use std::collections::{BTreeMap, BTreeSet, VecDeque};
1552
use std::marker::PhantomData;
@@ -638,12 +675,25 @@ impl<T: AccessedResource> Accessor<T> {
638675
/// Will return [None] if any ancestor node disables access, or if the node
639676
/// is not attached to a hierarchy containing a valid resource.
640677
///
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>> {
647697
let guard = self.0.guard_borrow()?;
648698
if guard.res_leaf.is_some() {
649699
Some(LockedView { guard })

lib/propolis/src/hw/nvme/queue.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,9 @@ struct QueueState<QS> {
115115

116116
/// This queue's memory accessor node.
117117
///
118-
/// Be careful about lock ordering when using this accessor; access_borrow()
118+
/// Be careful about lock ordering when using this accessor; access_locked()
119119
/// holds this node's lock. If a user of this queue state requires both
120-
/// `access_borrow()` and `QueueInner`, the protocol is to lock queue
120+
/// `access_locked()` and `QueueInner`, the protocol is to lock queue
121121
/// state first and this accessor second.
122122
acc_mem: MemAccessor,
123123
}
@@ -659,7 +659,7 @@ impl SubQueue {
659659
// see docs on QueueState::acc_mem.
660660
let mut state = self.state.lock();
661661

662-
let Some(mem) = self.state.acc_mem.access_borrow() else { return None };
662+
let Some(mem) = self.state.acc_mem.access_locked() else { return None };
663663
let mem = mem.view();
664664

665665
// Attempt to reserve an entry on the Completion Queue
@@ -897,7 +897,7 @@ impl CompQueue {
897897
// XXX: handle a guest addr that becomes unmapped later
898898
let addr = self.base.offset::<CompletionQueueEntry>(idx as usize);
899899
// TODO: access disallowed?
900-
let Some(mem) = self.state.acc_mem.access_borrow() else {
900+
let Some(mem) = self.state.acc_mem.access_locked() else {
901901
// TODO: mark the queue/controller in error state?
902902
return;
903903
};

lib/propolis/src/hw/nvme/requests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ impl block::DeviceQueue for NvmeBlockQueue {
7373
/// the underlying block backend
7474
fn next_req(&self) -> Option<(Request, Self::Token, Option<Instant>)> {
7575
let sq = &self.sq;
76-
let mem = self.acc_mem.access_borrow()?;
76+
let mem = self.acc_mem.access_locked()?;
7777
let mem = mem.view();
7878
let params = self.sq.params();
7979

0 commit comments

Comments
 (0)