Add methods to allow overlapping I/O ports

This commit is contained in:
Ruihan Li 2025-12-07 14:36:41 +08:00 committed by Tate, Hongliang Tian
parent 05886011f8
commit 6ac45fe406
3 changed files with 82 additions and 14 deletions

View File

@ -50,7 +50,11 @@ pub(crate) fn has_pci_bus() -> bool {
}
pub(crate) fn init() {
PCI_ADDRESS_PORT.call_once(|| IoPort::acquire(0xCF8).unwrap());
// We use `acquire_overlapping` to acquire the port at 0xCF8 because 0xCF9 may be used as a
// reset control register in the PIIX4. Although the two ports overlap in their I/O range, they
// serve completely different purposes. See
// <https://www.intel.com/Assets/PDF/datasheet/290562.pdf>.
PCI_ADDRESS_PORT.call_once(|| IoPort::acquire_overlapping(0xCF8).unwrap());
PCI_DATA_PORT.call_once(|| IoPort::acquire(0xCFC).unwrap());
}

View File

@ -23,9 +23,17 @@ pub(super) struct IoPortAllocator {
}
impl IoPortAllocator {
/// Acquires the `IoPort`. Return None if any region in `port` cannot be allocated.
pub(super) fn acquire<T, A>(&self, port: u16) -> Option<IoPort<T, A>> {
let range = port..port.checked_add(size_of::<T>().try_into().ok()?)?;
/// Acquires an `IoPort`. Returns `None` if the PIO range is unavailable.
///
/// `is_overlapping` indicates whether another `IoPort` can have a PIO range that overlaps with
/// this one. If it is true, only the first port in the PIO range will be marked as occupied;
/// otherwise, all ports in the PIO range will be marked as occupied.
pub(super) fn acquire<T, A>(&self, port: u16, is_overlapping: bool) -> Option<IoPort<T, A>> {
let range = if !is_overlapping {
port..port.checked_add(size_of::<T>().try_into().ok()?)?
} else {
port..port.checked_add(1)?
};
debug!("Try to acquire PIO range: {:#x?}", range);
let mut allocator = self.allocator.lock();
@ -38,7 +46,7 @@ impl IoPortAllocator {
}
// SAFETY: The created `IoPort` is guaranteed not to access system device I/O.
unsafe { Some(IoPort::new(port)) }
unsafe { Some(IoPort::new_overlapping(port, is_overlapping)) }
}
/// Recycles an PIO range.
@ -105,6 +113,7 @@ mod test {
use crate::{arch::device::io_port::ReadWriteAccess, prelude::*};
type IoPort = crate::io::IoPort<u32, ReadWriteAccess>;
type ByteIoPort = crate::io::IoPort<u8, ReadWriteAccess>;
#[ktest]
fn illegal_region() {
@ -131,4 +140,28 @@ mod test {
let io_port_b = IoPort::acquire(0x62);
assert!(io_port_b.is_ok());
}
#[ktest]
fn overlapping_region() {
// Reference: <https://wiki.osdev.org/PCI#Configuration_Space_Access_Mechanism_#1>
let pci_data = IoPort::acquire_overlapping(0xcf8);
// Reference: <https://www.intel.com/Assets/PDF/datasheet/290562.pdf>
let rst_ctrl = ByteIoPort::acquire(0xcf9);
assert!(pci_data.is_ok());
assert!(rst_ctrl.is_ok());
let pci_data2 = IoPort::acquire_overlapping(0xcf8);
let rst_ctrl2 = ByteIoPort::acquire(0xcf9);
assert!(pci_data2.is_err());
assert!(rst_ctrl2.is_err());
drop(pci_data);
drop(rst_ctrl);
let rst_ctrl3 = ByteIoPort::acquire(0xcf9);
assert!(rst_ctrl3.is_ok());
let pci_data3 = IoPort::acquire_overlapping(0xcf8);
assert!(pci_data3.is_ok());
}
}

View File

@ -24,17 +24,31 @@ use crate::{prelude::*, Error};
///
pub struct IoPort<T, A> {
port: u16,
is_overlapping: bool,
value_marker: PhantomData<T>,
access_marker: PhantomData<A>,
}
impl<T, A> IoPort<T, A> {
/// Acquires an `IoPort` instance for the given range.
///
/// This method will mark all ports in the PIO range as occupied.
pub fn acquire(port: u16) -> Result<IoPort<T, A>> {
allocator::IO_PORT_ALLOCATOR
.get()
.unwrap()
.acquire(port)
.acquire(port, false)
.ok_or(Error::AccessDenied)
}
/// Acquires an `IoPort` instance that may overlap with other `IoPort`s.
///
/// This method will only mark the first port in the PIO range as occupied.
pub fn acquire_overlapping(port: u16) -> Result<IoPort<T, A>> {
allocator::IO_PORT_ALLOCATOR
.get()
.unwrap()
.acquire(port, true)
.ok_or(Error::AccessDenied)
}
@ -54,9 +68,25 @@ impl<T, A> IoPort<T, A> {
///
/// Reading from or writing to the I/O port may have side effects. Those side effects must not
/// cause soundness problems (e.g., they must not corrupt the kernel memory).
pub const unsafe fn new(port: u16) -> Self {
pub(crate) const unsafe fn new(port: u16) -> Self {
// SAFETY: The safety is upheld by the caller.
unsafe { Self::new_overlapping(port, false) }
}
/// Creates an I/O port.
///
/// See [`IoPortAllocator::acquire`] for an explanation of the `is_overlapping` argument.
///
/// [`IoPortAllocator::acquire`]: allocator::IoPortAllocator::acquire
///
/// # Safety
///
/// Reading from or writing to the I/O port may have side effects. Those side effects must not
/// cause soundness problems (e.g., they must not corrupt the kernel memory).
const unsafe fn new_overlapping(port: u16, is_overlapping: bool) -> Self {
Self {
port,
is_overlapping,
value_marker: PhantomData,
access_marker: PhantomData,
}
@ -79,13 +109,14 @@ impl<T: PortWrite, A: IoPortWriteAccess> IoPort<T, A> {
impl<T, A> Drop for IoPort<T, A> {
fn drop(&mut self) {
// SAFETY: The caller have ownership of the PIO region.
unsafe {
allocator::IO_PORT_ALLOCATOR
.get()
.unwrap()
.recycle(self.port..(self.port + size_of::<T>() as u16));
}
let range = if !self.is_overlapping {
self.port..(self.port + size_of::<T>() as u16)
} else {
self.port..(self.port + 1)
};
// SAFETY: We have ownership of the PIO region.
unsafe { allocator::IO_PORT_ALLOCATOR.get().unwrap().recycle(range) };
}
}