Implement `IoPortAllocator`

This commit is contained in:
Yuke Peng 2025-03-30 16:58:19 +08:00 committed by Tate, Hongliang Tian
parent f89b248f3b
commit d359cc44d6
5 changed files with 156 additions and 12 deletions

View File

@ -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) }
}

View File

@ -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);
}
}

View File

@ -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<IdAlloc, LocalIrqDisabled>,
}
impl IoPortAllocator {
/// Acquires the `IoPort`. Return None if any region in `port` cannot be allocated.
pub fn acquire<T, A>(&self, port: u16) -> Option<IoPort<T, A>> {
let mut allocator = self.allocator.lock();
let mut range = port..(port + size_of::<T>() 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<u16>) {
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<u16>) {
info!("Removing PIO range: {:#x?}", range);
for i in range {
self.allocator.alloc_specific(i as usize);
}
}
}
pub(super) static IO_PORT_ALLOCATOR: Once<IoPortAllocator> = 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),
});
}

View File

@ -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<T, A> {
}
impl<T, A> IoPort<T, A> {
/// Acquires an `IoPort` instance for the given range.
pub fn acquire(port: u16) -> Result<IoPort<T, A>> {
allocator::IO_PORT_ALLOCATOR
.get()
.unwrap()
.acquire(port)
.ok_or(Error::AccessDenied)
}
/// Create an I/O port.
///
/// # Safety
@ -55,3 +69,15 @@ impl<T: PortWrite, A: IoPortWriteAccess> IoPort<T, A> {
unsafe { PortWrite::write_to_port(self.port, value) }
}
}
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));
}
}
}

View File

@ -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);
}