diff --git a/ostd/src/arch/x86/allocator.rs b/ostd/src/arch/x86/allocator.rs index fb017b930..51e3b6b13 100644 --- a/ostd/src/arch/x86/allocator.rs +++ b/ostd/src/arch/x86/allocator.rs @@ -4,7 +4,10 @@ use alloc::vec::Vec; use align_ext::AlignExt; -use crate::{boot::memory_region::MemoryRegionType, io::IoMemAllocatorBuilder}; +use crate::{ + boot::memory_region::MemoryRegionType, + io::{IoMemAllocatorBuilder, IoPortAllocatorBuilder}, +}; /// Initializes the allocatable MMIO area based on the x86-64 memory distribution map. /// @@ -54,3 +57,12 @@ pub(super) fn construct_io_mem_allocator_builder() -> IoMemAllocatorBuilder { // SAFETY: The range is guaranteed not to access physical memory. unsafe { IoMemAllocatorBuilder::new(ranges) } } + +/// Initializes the allocatable PIO area based on the x86-64 port distribution map. +pub(super) fn construct_io_port_allocator_builder() -> IoPortAllocatorBuilder { + /// Port I/O definition reference: https://bochs.sourceforge.io/techspec/PORTS.LST + const MAX_IO_PORT: u16 = u16::MAX; + + // SAFETY: `MAX_IO_PORT` is guaranteed not to exceed the maximum value specified by x86-64. + unsafe { IoPortAllocatorBuilder::new(MAX_IO_PORT) } +} diff --git a/ostd/src/arch/x86/mod.rs b/ostd/src/arch/x86/mod.rs index 301b317be..a718c39c8 100644 --- a/ostd/src/arch/x86/mod.rs +++ b/ostd/src/arch/x86/mod.rs @@ -18,7 +18,7 @@ pub mod task; pub mod timer; pub mod trap; -use allocator::construct_io_mem_allocator_builder; +use allocator::{construct_io_mem_allocator_builder, construct_io_port_allocator_builder}; use cfg_if::cfg_if; use spin::Once; use x86::cpuid::{CpuId, FeatureInfo}; @@ -81,6 +81,7 @@ pub(crate) unsafe fn late_init_on_bsp() { kernel::acpi::init(); let io_mem_builder = construct_io_mem_allocator_builder(); + let io_port_builder = construct_io_port_allocator_builder(); match kernel::apic::init(&io_mem_builder) { Ok(_) => { @@ -109,9 +110,9 @@ pub(crate) unsafe fn late_init_on_bsp() { // Some driver like serial may use PIC kernel::pic::init(); - // SAFETY: All the system device memory I/Os have been removed from the builder. + // SAFETY: All the system device memory and port I/Os have been removed from the builder. unsafe { - crate::io::init(io_mem_builder); + crate::io::init(io_mem_builder, io_port_builder); } } diff --git a/ostd/src/io/io_port/allocator.rs b/ostd/src/io/io_port/allocator.rs new file mode 100644 index 000000000..ccec57591 --- /dev/null +++ b/ostd/src/io/io_port/allocator.rs @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! I/O port allocator. +use core::ops::Range; + +use id_alloc::IdAlloc; +use log::{debug, info}; +use spin::Once; + +use super::IoPort; +use crate::sync::{LocalIrqDisabled, SpinLock}; + +/// I/O port allocator that allocates port I/O access to device drivers. +pub struct IoPortAllocator { + /// Each ID indicates whether a Port I/O (1B) is allocated. + /// + /// Instead of using `RangeAllocator` like `IoMemAllocator` does, it is more reasonable to use `IdAlloc`, + /// as PIO space includes only a small region; for example, x86 module in OSTD allows just 65536 I/O ports. + allocator: SpinLock, +} + +impl IoPortAllocator { + /// Acquires the `IoPort`. Return None if any region in `port` cannot be allocated. + pub fn acquire(&self, port: u16) -> Option> { + let mut allocator = self.allocator.lock(); + let mut range = port..(port + size_of::() as u16); + if range.any(|i| allocator.is_allocated(i as usize)) { + return None; + } + + for i in range.clone() { + allocator.alloc_specific(i as usize); + } + + // SAFETY: The created IoPort is guaranteed not to access system device I/O + unsafe { Some(IoPort::new(port)) } + } + + /// Recycles an PIO range. + /// + /// # Safety + /// + /// The caller must have ownership of the PIO region through the `IoPortAllocator::acquire` interface. + pub(in crate::io) unsafe fn recycle(&self, range: Range) { + debug!("Recycling MMIO range: {:#x?}", range); + + self.allocator + .lock() + .free_consecutive(range.start as usize..range.end as usize); + } +} + +/// Builder for `IoPortAllocator`. +/// +/// The builder must contains the port I/O regions according to architecture specification. Also, OSTD +/// must exclude the port I/O regions of the system device before building the `IoPortAllocator`. +pub(crate) struct IoPortAllocatorBuilder { + allocator: IdAlloc, +} + +impl IoPortAllocatorBuilder { + /// Initializes port I/O region for devices. + /// + /// # Safety + /// + /// User must ensure `max_port` doesn't exceed the maximum value specified by architecture. + pub(crate) unsafe fn new(max_port: u16) -> Self { + info!( + "Creating new I/O port allocator builder, max_port: {:#x?}", + max_port + ); + + Self { + allocator: IdAlloc::with_capacity(max_port as usize), + } + } + + /// Removes access to a specific port I/O range. + /// + /// All drivers in OSTD must use this method to prevent peripheral drivers from accessing illegal port IO range. + pub(crate) fn remove(&mut self, range: Range) { + info!("Removing PIO range: {:#x?}", range); + + for i in range { + self.allocator.alloc_specific(i as usize); + } + } +} + +pub(super) static IO_PORT_ALLOCATOR: Once = Once::new(); + +/// Initializes the static `IO_PORT_ALLOCATOR` based on builder. +/// +/// # Safety +/// +/// User must ensure all the port I/O regions that belong to the system device have been removed by calling the +/// `remove` function. +pub(crate) unsafe fn init(io_port_builder: IoPortAllocatorBuilder) { + IO_PORT_ALLOCATOR.call_once(|| IoPortAllocator { + allocator: SpinLock::new(io_port_builder.allocator), + }); +} diff --git a/ostd/src/io/io_port/mod.rs b/ostd/src/io/io_port/mod.rs index 39bdc415f..371dcb983 100644 --- a/ostd/src/io/io_port/mod.rs +++ b/ostd/src/io/io_port/mod.rs @@ -2,9 +2,14 @@ //! I/O port and its allocator that allocates port I/O (PIO) to device drivers. -use core::marker::PhantomData; - use crate::arch::device::io_port::{IoPortReadAccess, IoPortWriteAccess, PortRead, PortWrite}; +mod allocator; + +use core::{marker::PhantomData, mem::size_of}; + +pub(super) use self::allocator::init; +pub(crate) use self::allocator::IoPortAllocatorBuilder; +use crate::{prelude::*, Error}; /// An I/O port, representing a specific address in the I/O address of x86. /// @@ -25,6 +30,15 @@ pub struct IoPort { } impl IoPort { + /// Acquires an `IoPort` instance for the given range. + pub fn acquire(port: u16) -> Result> { + allocator::IO_PORT_ALLOCATOR + .get() + .unwrap() + .acquire(port) + .ok_or(Error::AccessDenied) + } + /// Create an I/O port. /// /// # Safety @@ -55,3 +69,15 @@ impl IoPort { unsafe { PortWrite::write_to_port(self.port, value) } } } + +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)); + } + } +} diff --git a/ostd/src/io/mod.rs b/ostd/src/io/mod.rs index 12fd87d10..2bc488a74 100644 --- a/ostd/src/io/mod.rs +++ b/ostd/src/io/mod.rs @@ -10,16 +10,19 @@ mod io_mem; mod io_port; -pub(crate) use self::io_mem::IoMemAllocatorBuilder; pub use self::{io_mem::IoMem, io_port::IoPort}; +pub(crate) use self::{io_mem::IoMemAllocatorBuilder, io_port::IoPortAllocatorBuilder}; /// Initializes the static allocator based on builder. /// /// # Safety /// -/// User must ensure all the memory I/O regions that belong to the system device have been removed by calling the -/// `remove` function. -pub(crate) unsafe fn init(builder: IoMemAllocatorBuilder) { - self::io_mem::init(builder); - // TODO: IoPort initialization +/// User must ensure all the memory and port I/O regions that belong to the system device +/// have been removed by calling the corresponding `remove` function. +pub(crate) unsafe fn init( + io_mem_builder: IoMemAllocatorBuilder, + io_port_builder: IoPortAllocatorBuilder, +) { + self::io_mem::init(io_mem_builder); + self::io_port::init(io_port_builder); }