diff --git a/kernel/comps/pci/src/arch/x86/mod.rs b/kernel/comps/pci/src/arch/x86/mod.rs index 3885bfbf7..3e923b745 100644 --- a/kernel/comps/pci/src/arch/x86/mod.rs +++ b/kernel/comps/pci/src/arch/x86/mod.rs @@ -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 + // . + PCI_ADDRESS_PORT.call_once(|| IoPort::acquire_overlapping(0xCF8).unwrap()); PCI_DATA_PORT.call_once(|| IoPort::acquire(0xCFC).unwrap()); } diff --git a/ostd/src/io/io_port/allocator.rs b/ostd/src/io/io_port/allocator.rs index 9a7bcea49..f43600b47 100644 --- a/ostd/src/io/io_port/allocator.rs +++ b/ostd/src/io/io_port/allocator.rs @@ -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(&self, port: u16) -> Option> { - let range = port..port.checked_add(size_of::().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(&self, port: u16, is_overlapping: bool) -> Option> { + let range = if !is_overlapping { + port..port.checked_add(size_of::().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; + type ByteIoPort = crate::io::IoPort; #[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: + let pci_data = IoPort::acquire_overlapping(0xcf8); + // Reference: + 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()); + } } diff --git a/ostd/src/io/io_port/mod.rs b/ostd/src/io/io_port/mod.rs index 9abfade32..3028bb263 100644 --- a/ostd/src/io/io_port/mod.rs +++ b/ostd/src/io/io_port/mod.rs @@ -24,17 +24,31 @@ use crate::{prelude::*, Error}; /// pub struct IoPort { port: u16, + is_overlapping: bool, value_marker: PhantomData, access_marker: PhantomData, } impl IoPort { /// 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> { 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> { + allocator::IO_PORT_ALLOCATOR + .get() + .unwrap() + .acquire(port, true) .ok_or(Error::AccessDenied) } @@ -54,9 +68,25 @@ impl IoPort { /// /// 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 IoPort { impl Drop for IoPort { 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::() as u16)); - } + let range = if !self.is_overlapping { + self.port..(self.port + size_of::() 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) }; } }