ostd/io/io_port/mod.rs
1// SPDX-License-Identifier: MPL-2.0
2
3//! I/O port and its allocator that allocates port I/O (PIO) to device drivers.
4
5use crate::arch::device::io_port::{IoPortReadAccess, IoPortWriteAccess, PortRead, PortWrite};
6mod allocator;
7
8use core::marker::PhantomData;
9
10pub(super) use self::allocator::init;
11use crate::{Error, prelude::*};
12
13/// An I/O port, representing a specific address in the I/O address of x86.
14///
15/// The following code shows and example to read and write u32 value to an I/O port:
16///
17/// ```rust
18/// static PORT: IoPort<u32, ReadWriteAccess> = unsafe { IoPort::new(0x12) };
19///
20/// fn port_value_increase(){
21/// PORT.write(PORT.read() + 1)
22/// }
23/// ```
24///
25pub struct IoPort<T, A> {
26 port: u16,
27 is_overlapping: bool,
28 value_marker: PhantomData<T>,
29 access_marker: PhantomData<A>,
30}
31
32impl<T, A> IoPort<T, A> {
33 /// Acquires an `IoPort` instance for the given range.
34 ///
35 /// This method will mark all ports in the PIO range as occupied.
36 pub fn acquire(port: u16) -> Result<IoPort<T, A>> {
37 allocator::IO_PORT_ALLOCATOR
38 .get()
39 .unwrap()
40 .acquire(port, false)
41 .ok_or(Error::AccessDenied)
42 }
43
44 /// Acquires an `IoPort` instance that may overlap with other `IoPort`s.
45 ///
46 /// This method will only mark the first port in the PIO range as occupied.
47 pub fn acquire_overlapping(port: u16) -> Result<IoPort<T, A>> {
48 allocator::IO_PORT_ALLOCATOR
49 .get()
50 .unwrap()
51 .acquire(port, true)
52 .ok_or(Error::AccessDenied)
53 }
54
55 /// Returns the port number.
56 pub const fn port(&self) -> u16 {
57 self.port
58 }
59
60 /// Returns the size of the I/O port.
61 pub const fn size(&self) -> u16 {
62 size_of::<T>() as u16
63 }
64
65 /// Creates an I/O port.
66 ///
67 /// # Safety
68 ///
69 /// Reading from or writing to the I/O port may have side effects. Those side effects must not
70 /// cause soundness problems (e.g., they must not corrupt the kernel memory).
71 pub(crate) const unsafe fn new(port: u16) -> Self {
72 // SAFETY: The safety is upheld by the caller.
73 unsafe { Self::new_overlapping(port, false) }
74 }
75
76 /// Creates an I/O port.
77 ///
78 /// See [`IoPortAllocator::acquire`] for an explanation of the `is_overlapping` argument.
79 ///
80 /// [`IoPortAllocator::acquire`]: allocator::IoPortAllocator::acquire
81 ///
82 /// # Safety
83 ///
84 /// Reading from or writing to the I/O port may have side effects. Those side effects must not
85 /// cause soundness problems (e.g., they must not corrupt the kernel memory).
86 const unsafe fn new_overlapping(port: u16, is_overlapping: bool) -> Self {
87 Self {
88 port,
89 is_overlapping,
90 value_marker: PhantomData,
91 access_marker: PhantomData,
92 }
93 }
94}
95
96impl<T: PortRead, A: IoPortReadAccess> IoPort<T, A> {
97 /// Reads from the I/O port
98 pub fn read(&self) -> T {
99 unsafe { PortRead::read_from_port(self.port) }
100 }
101}
102
103impl<T: PortWrite, A: IoPortWriteAccess> IoPort<T, A> {
104 /// Writes to the I/O port
105 pub fn write(&self, value: T) {
106 unsafe { PortWrite::write_to_port(self.port, value) }
107 }
108}
109
110impl<T, A> Drop for IoPort<T, A> {
111 fn drop(&mut self) {
112 let range = if !self.is_overlapping {
113 self.port..(self.port + size_of::<T>() as u16)
114 } else {
115 self.port..(self.port + 1)
116 };
117
118 // SAFETY: We have ownership of the PIO region.
119 unsafe { allocator::IO_PORT_ALLOCATOR.get().unwrap().recycle(range) };
120 }
121}
122
123/// Reserves an I/O port range which may refer to the port I/O range used by the
124/// system device driver.
125///
126/// # Example
127/// ```
128/// reserve_io_port_range!(0x60..0x64);
129/// ```
130macro_rules! reserve_io_port_range {
131 ($range:expr) => {
132 crate::const_assert!(
133 $range.start < $range.end,
134 "I/O port range must be valid (start < end)"
135 );
136
137 const _: () = {
138 #[used]
139 // SAFETY: This is properly handled in the linker script.
140 #[unsafe(link_section = ".sensitive_io_ports")]
141 static _RANGE: crate::io::RawIoPortRange = crate::io::RawIoPortRange {
142 begin: $range.start,
143 end: $range.end,
144 };
145 };
146 };
147}
148
149/// Declares one or multiple sensitive I/O ports.
150///
151/// # Safety
152///
153/// User must ensures that:
154/// - The I/O port is valid and doesn't overlap with other sensitive I/O ports.
155/// - The I/O port is used by the target system device driver.
156///
157/// # Example
158/// ```no_run
159/// sensitive_io_port! {
160/// unsafe {
161/// /// Master PIC command port
162/// static MASTER_CMD: IoPort<u8, WriteOnlyAccess> = IoPort::new(0x20);
163/// /// Master PIC data port
164/// static MASTER_DATA: IoPort<u8, WriteOnlyAccess> = IoPort::new(0x21);
165/// }
166/// }
167/// ```
168macro_rules! sensitive_io_port {
169 (unsafe { $(
170 $(#[$meta:meta])*
171 $vis:vis static $name:ident: IoPort<$size:ty, $access:ty> = IoPort::new($port:expr);
172 )* }) => {
173 $(
174 $(#[$meta])*
175 $vis static $name: IoPort<$size, $access> = {
176 #[used]
177 // SAFETY: This is properly handled in the linker script.
178 #[unsafe(link_section = ".sensitive_io_ports")]
179 static _RESERVED_IO_PORT_RANGE: crate::io::RawIoPortRange = crate::io::RawIoPortRange {
180 begin: $name.port(),
181 end: $name.port() + $name.size(),
182 };
183
184 unsafe {
185 IoPort::new($port)
186 }
187 };
188 )*
189 };
190}
191
192pub(crate) use reserve_io_port_range;
193pub(crate) use sensitive_io_port;
194
195#[doc(hidden)]
196#[derive(Debug, Clone, Copy)]
197#[repr(C)]
198pub(crate) struct RawIoPortRange {
199 pub(crate) begin: u16,
200 pub(crate) end: u16,
201}