vmotherboard/chipset/io_ranges/
mod.rsmod address_filter;
use self::address_filter::RangeKey;
use chipset_device::ChipsetDevice;
use closeable_mutex::CloseableMutex;
use inspect::Inspect;
use inspect_counters::SharedCounter;
use parking_lot::RwLock;
use range_map_vec::RangeMap;
use std::ops::RangeInclusive;
use std::sync::Arc;
use std::sync::OnceLock;
use std::sync::Weak;
struct IoRangesInner<T> {
map: RangeMap<T, RangeEntry>,
trace_on: address_filter::AddressFilter<T>,
break_on: address_filter::AddressFilter<T>,
static_registration_conflicts: Option<Vec<IoRangeConflict<T>>>,
fallback_device: Option<Arc<CloseableMutex<dyn ChipsetDevice>>>,
}
#[derive(Debug, Clone)]
pub struct IoRangeConflict<T> {
existing_dev_region: (Arc<str>, Arc<str>, RangeInclusive<T>),
conflict_dev_region: (Arc<str>, Arc<str>, RangeInclusive<T>),
}
impl<T> std::error::Error for IoRangeConflict<T> where T: std::fmt::LowerHex + core::fmt::Debug {}
impl<T> std::fmt::Display for IoRangeConflict<T>
where
T: std::fmt::LowerHex + core::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}/{}:{:#x?} conflicts with existing {}/{}:{:#x?}",
self.conflict_dev_region.0,
self.conflict_dev_region.1,
self.conflict_dev_region.2,
self.existing_dev_region.0,
self.existing_dev_region.1,
self.existing_dev_region.2,
)
}
}
#[derive(Inspect)]
struct RangeEntry {
region_name: Arc<str>,
dev_name: Arc<str>,
#[inspect(rename = "device_is_init", with = "|x| x.upgrade().is_some()")]
dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
read_count: SharedCounter,
write_count: SharedCounter,
}
#[derive(Clone)]
pub struct IoRanges<T> {
inner: Arc<RwLock<IoRangesInner<T>>>,
}
impl<T: RangeKey> IoRanges<T> {
pub fn new(
trace_on_unknown: bool,
fallback_device: Option<Arc<CloseableMutex<dyn ChipsetDevice>>>,
) -> Self {
Self {
inner: Arc::new(RwLock::new(IoRangesInner {
map: RangeMap::new(),
trace_on: address_filter::AddressFilter::new(trace_on_unknown),
break_on: address_filter::AddressFilter::new(false),
static_registration_conflicts: Some(Vec::new()),
fallback_device,
})),
}
}
pub fn register(
&self,
start: T,
end: T,
region_name: Arc<str>,
dev: Weak<CloseableMutex<dyn ChipsetDevice>>,
dev_name: Arc<str>,
) -> Result<(), IoRangeConflict<T>> {
let mut inner = self.inner.write();
match inner.map.entry(start..=end) {
range_map_vec::Entry::Vacant(entry) => {
entry.insert(RangeEntry {
region_name,
dev,
dev_name,
read_count: Default::default(),
write_count: Default::default(),
});
Ok(())
}
range_map_vec::Entry::Overlapping(entry) => {
let existing_dev_region = {
let (start, end, entry) = entry.get();
(
entry.dev_name.clone(),
entry.region_name.clone(),
*start..=*end,
)
};
let conflict = IoRangeConflict {
existing_dev_region,
conflict_dev_region: (dev_name, region_name, start..=end),
};
if let Some(v) = inner.static_registration_conflicts.as_mut() {
v.push(conflict.clone())
}
Err(conflict)
}
}
}
pub fn revoke(&self, start: T) {
let mut inner = self.inner.write();
inner.map.remove(&start);
}
pub fn lookup(&self, addr: T, is_read: bool) -> LookupResult {
static UNKNOWN_DEVICE: OnceLock<Arc<CloseableMutex<dyn ChipsetDevice>>> = OnceLock::new();
static UNKNOWN_DEVICE_NAME: OnceLock<Arc<str>> = OnceLock::new();
static UNKNOWN_RANGE: OnceLock<Arc<str>> = OnceLock::new();
let inner = self.inner.read();
let entry = inner.map.get(&addr);
if let Some(entry) = entry {
if is_read {
entry.read_count.increment()
} else {
entry.write_count.increment()
}
}
let (dev, dev_name) =
entry
.and_then(|e| e.dev.upgrade().map(|d| (d, e.dev_name.clone())))
.unwrap_or_else(|| {
(
inner.fallback_device.clone().unwrap_or_else(|| {
UNKNOWN_DEVICE
.get_or_init(|| {
Arc::new(CloseableMutex::new(missing_dev::MissingDev::from_manifest(
missing_dev::MissingDevManifest::new(),
&mut chipset_device::mmio::ExternallyManagedMmioIntercepts,
&mut chipset_device::pio::ExternallyManagedPortIoIntercepts,
)))
})
.clone()
}),
UNKNOWN_DEVICE_NAME
.get_or_init(|| "<unknown>".into())
.clone(),
)
});
let trace = inner.trace_on.filtered(&addr, entry.is_some());
let trace = trace.then(|| {
entry.map_or_else(
|| UNKNOWN_RANGE.get_or_init(|| "<unknown>".into()).clone(),
|e| e.region_name.clone(),
)
});
let debug_break = inner.break_on.filtered(&addr, entry.is_some());
LookupResult {
dev,
dev_name,
trace,
debug_break,
}
}
pub fn take_static_registration_conflicts(&mut self) -> Vec<IoRangeConflict<T>> {
self.inner
.write()
.static_registration_conflicts
.take()
.expect("must only be called once")
}
pub fn is_occupied(&self, addr: T) -> bool {
self.inner.read().map.contains(&addr)
}
}
pub struct LookupResult {
pub dev: Arc<CloseableMutex<dyn ChipsetDevice>>,
pub dev_name: Arc<str>,
pub trace: Option<Arc<str>>,
pub debug_break: bool,
}
impl<T: RangeKey> Inspect for IoRanges<T> {
fn inspect(&self, req: inspect::Request<'_>) {
let mut resp = req.respond();
let mut inner = self.inner.write();
resp.field_mut("trace_on", &mut inner.trace_on)
.field_mut("break_on", &mut inner.break_on);
for (range, entry) in inner.map.iter() {
resp.field(&format!("{:#x}-{:#x}", range.start(), range.end()), entry);
}
}
}