use ax_errno::{AxResult, ax_err};
use axdevice_base::{AccessWidth, BaseDeviceOps, EmuDeviceType, Port, PortRange};
pub(crate) struct HostPortPassthrough {
base: Port,
length: u16,
}
impl HostPortPassthrough {
pub(crate) fn new(base: u16, length: u16) -> AxResult<Self> {
if length == 0 {
return ax_err!(InvalidInput, "host port passthrough range is empty");
}
if base.checked_add(length - 1).is_none() {
return ax_err!(InvalidInput, "host port passthrough range overflows");
}
Ok(Self {
base: Port::new(base),
length,
})
}
fn end(&self) -> Port {
Port::new(self.base.number() + self.length - 1)
}
}
impl BaseDeviceOps<PortRange> for HostPortPassthrough {
fn emu_type(&self) -> EmuDeviceType {
EmuDeviceType::Dummy
}
fn address_range(&self) -> PortRange {
PortRange::new(self.base, self.end())
}
fn handle_read(&self, port: Port, width: AccessWidth) -> AxResult<usize> {
match width {
AccessWidth::Byte => Ok(unsafe { inb(port.number()) } as usize),
AccessWidth::Word => Ok(unsafe { inw(port.number()) } as usize),
AccessWidth::Dword => Ok(unsafe { inl(port.number()) } as usize),
AccessWidth::Qword => ax_err!(Unsupported, "x86 port I/O does not support qword read"),
}
}
fn handle_write(&self, port: Port, width: AccessWidth, value: usize) -> AxResult {
match width {
AccessWidth::Byte => unsafe { outb(port.number(), value as u8) },
AccessWidth::Word => unsafe { outw(port.number(), value as u16) },
AccessWidth::Dword => unsafe { outl(port.number(), value as u32) },
AccessWidth::Qword => {
return ax_err!(Unsupported, "x86 port I/O does not support qword write");
}
}
Ok(())
}
}
unsafe fn inb(port: u16) -> u8 {
let value: u8;
unsafe {
core::arch::asm!("in al, dx", in("dx") port, out("al") value, options(nomem, nostack));
}
value
}
unsafe fn inw(port: u16) -> u16 {
let value: u16;
unsafe {
core::arch::asm!("in ax, dx", in("dx") port, out("ax") value, options(nomem, nostack));
}
value
}
unsafe fn inl(port: u16) -> u32 {
let value: u32;
unsafe {
core::arch::asm!("in eax, dx", in("dx") port, out("eax") value, options(nomem, nostack));
}
value
}
unsafe fn outb(port: u16, value: u8) {
unsafe {
core::arch::asm!("out dx, al", in("dx") port, in("al") value, options(nomem, nostack));
}
}
unsafe fn outw(port: u16, value: u16) {
unsafe {
core::arch::asm!("out dx, ax", in("dx") port, in("ax") value, options(nomem, nostack));
}
}
unsafe fn outl(port: u16, value: u32) {
unsafe {
core::arch::asm!("out dx, eax", in("dx") port, in("eax") value, options(nomem, nostack));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn passthrough_port_range_is_inclusive() {
let dev = HostPortPassthrough::new(0x6000, 0x80).unwrap();
assert_eq!(
dev.address_range(),
PortRange::new(Port::new(0x6000), Port::new(0x607f))
);
}
#[test]
fn passthrough_port_range_rejects_empty_and_overflowing_ranges() {
assert!(HostPortPassthrough::new(0x6000, 0).is_err());
assert!(HostPortPassthrough::new(0xfff0, 0x20).is_err());
}
#[test]
fn passthrough_port_rejects_qword_without_touching_hardware() {
let dev = HostPortPassthrough::new(0x6000, 0x80).unwrap();
assert!(
dev.handle_read(Port::new(0x6000), AccessWidth::Qword)
.is_err()
);
assert!(
dev.handle_write(Port::new(0x6000), AccessWidth::Qword, 0)
.is_err()
);
}
}