9292422458
Same bug class as the ahcid fix (ad40fffd): the kernel masks the IRQ
line when it delivers an interrupt and only re-arms it on the userspace
write-back (irq scheme write -> acknowledge() -> pic/ioapic_unmask).
When a driver acks only if the interrupt was its own, one foreign
interrupt on a shared INTx line leaves the line masked forever and all
later interrupts for the device are lost.
- e1000d: ack unconditionally; ICR is read-to-clear, so the ownership
check doubles as the device-level status clear
- ihdad: ack before the not-ours continue
- vboxd: ack unconditionally; device-side ack and event handling stay
conditional on host_events
- xhcid: ack unconditionally in the IRQ reactor; for INTx the ownership
check already clears IMAN.IP (RW1C) and device interrupts are masked
before re-arming, so no storm. MSI/MSI-X vectors are never shared, so
behavior there is unchanged (every delivery is ours by construction)
Verified: cargo check -Z build-std --target x86_64-unknown-redox for
e1000d, ihdad, vboxd, xhcid — clean, no new warnings.
424 lines
12 KiB
Rust
424 lines
12 KiB
Rust
//#![deny(warnings)]
|
|
|
|
use event::{user_data, EventQueue};
|
|
use std::fs::File;
|
|
use std::io::{Read, Write};
|
|
use std::os::unix::io::AsRawFd;
|
|
use std::{iter, mem};
|
|
|
|
use common::io::{Io, Mmio};
|
|
use pcid_interface::PciFunctionHandle;
|
|
|
|
use common::dma::Dma;
|
|
|
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
mod bga;
|
|
|
|
const VBOX_REQUEST_HEADER_VERSION: u32 = 0x10001;
|
|
const VBOX_VMMDEV_VERSION: u32 = 0x00010003;
|
|
|
|
const VBOX_EVENT_DISPLAY: u32 = 1 << 2;
|
|
const VBOX_EVENT_MOUSE: u32 = 1 << 9;
|
|
|
|
/// VBox VMMDevMemory
|
|
#[repr(C, packed)]
|
|
struct VboxVmmDev {
|
|
size: Mmio<u32>,
|
|
version: Mmio<u32>,
|
|
host_events: Mmio<u32>,
|
|
guest_events: Mmio<u32>,
|
|
}
|
|
|
|
/// VBox Guest packet header
|
|
#[repr(C, packed)]
|
|
struct VboxHeader {
|
|
/// Size of the entire packet (including this header)
|
|
size: Mmio<u32>,
|
|
/// Version; always VBOX_REQUEST_HEADER_VERSION
|
|
version: Mmio<u32>,
|
|
/// Request type
|
|
request: Mmio<u32>,
|
|
/// Return code
|
|
result: Mmio<u32>,
|
|
_reserved1: Mmio<u32>,
|
|
_reserved2: Mmio<u32>,
|
|
}
|
|
|
|
/// VBox Get Mouse
|
|
#[repr(C, packed)]
|
|
struct VboxGetMouse {
|
|
header: VboxHeader,
|
|
features: Mmio<u32>,
|
|
x: Mmio<u32>,
|
|
y: Mmio<u32>,
|
|
}
|
|
|
|
impl VboxGetMouse {
|
|
fn request() -> u32 {
|
|
1
|
|
}
|
|
|
|
fn new() -> syscall::Result<Dma<Self>> {
|
|
let mut packet = unsafe { Dma::<Self>::zeroed()?.assume_init() };
|
|
|
|
packet.header.size.write(mem::size_of::<Self>() as u32);
|
|
packet.header.version.write(VBOX_REQUEST_HEADER_VERSION);
|
|
packet.header.request.write(Self::request());
|
|
|
|
Ok(packet)
|
|
}
|
|
}
|
|
|
|
/// VBox Set Mouse
|
|
#[repr(C, packed)]
|
|
struct VboxSetMouse {
|
|
header: VboxHeader,
|
|
features: Mmio<u32>,
|
|
x: Mmio<u32>,
|
|
y: Mmio<u32>,
|
|
}
|
|
|
|
impl VboxSetMouse {
|
|
fn request() -> u32 {
|
|
2
|
|
}
|
|
|
|
fn new() -> syscall::Result<Dma<Self>> {
|
|
let mut packet = unsafe { Dma::<Self>::zeroed()?.assume_init() };
|
|
|
|
packet.header.size.write(mem::size_of::<Self>() as u32);
|
|
packet.header.version.write(VBOX_REQUEST_HEADER_VERSION);
|
|
packet.header.request.write(Self::request());
|
|
|
|
Ok(packet)
|
|
}
|
|
}
|
|
|
|
/// VBox Acknowledge Events packet
|
|
#[repr(C, packed)]
|
|
struct VboxAckEvents {
|
|
header: VboxHeader,
|
|
events: Mmio<u32>,
|
|
}
|
|
|
|
impl VboxAckEvents {
|
|
fn request() -> u32 {
|
|
41
|
|
}
|
|
|
|
fn new() -> syscall::Result<Dma<Self>> {
|
|
let mut packet = unsafe { Dma::<Self>::zeroed()?.assume_init() };
|
|
|
|
packet.header.size.write(mem::size_of::<Self>() as u32);
|
|
packet.header.version.write(VBOX_REQUEST_HEADER_VERSION);
|
|
packet.header.request.write(Self::request());
|
|
|
|
Ok(packet)
|
|
}
|
|
}
|
|
|
|
/// VBox Guest Capabilities packet
|
|
#[repr(C, packed)]
|
|
struct VboxGuestCaps {
|
|
header: VboxHeader,
|
|
caps: Mmio<u32>,
|
|
}
|
|
|
|
impl VboxGuestCaps {
|
|
fn request() -> u32 {
|
|
55
|
|
}
|
|
|
|
fn new() -> syscall::Result<Dma<Self>> {
|
|
let mut packet = unsafe { Dma::<Self>::zeroed()?.assume_init() };
|
|
|
|
packet.header.size.write(mem::size_of::<Self>() as u32);
|
|
packet.header.version.write(VBOX_REQUEST_HEADER_VERSION);
|
|
packet.header.request.write(Self::request());
|
|
|
|
Ok(packet)
|
|
}
|
|
}
|
|
|
|
/* VBox GetDisplayChange packet */
|
|
struct VboxDisplayChange {
|
|
header: VboxHeader,
|
|
xres: Mmio<u32>,
|
|
yres: Mmio<u32>,
|
|
bpp: Mmio<u32>,
|
|
eventack: Mmio<u32>,
|
|
}
|
|
|
|
impl VboxDisplayChange {
|
|
fn request() -> u32 {
|
|
51
|
|
}
|
|
|
|
fn new() -> syscall::Result<Dma<Self>> {
|
|
let mut packet = unsafe { Dma::<Self>::zeroed()?.assume_init() };
|
|
|
|
packet.header.size.write(mem::size_of::<Self>() as u32);
|
|
packet.header.version.write(VBOX_REQUEST_HEADER_VERSION);
|
|
packet.header.request.write(Self::request());
|
|
|
|
Ok(packet)
|
|
}
|
|
}
|
|
|
|
/// VBox Guest Info packet (legacy)
|
|
#[repr(C, packed)]
|
|
struct VboxGuestInfo {
|
|
header: VboxHeader,
|
|
version: Mmio<u32>,
|
|
ostype: Mmio<u32>,
|
|
}
|
|
|
|
impl VboxGuestInfo {
|
|
fn request() -> u32 {
|
|
50
|
|
}
|
|
|
|
fn new() -> syscall::Result<Dma<Self>> {
|
|
let mut packet = unsafe { Dma::<Self>::zeroed()?.assume_init() };
|
|
|
|
packet.header.size.write(mem::size_of::<Self>() as u32);
|
|
packet.header.version.write(VBOX_REQUEST_HEADER_VERSION);
|
|
packet.header.request.write(Self::request());
|
|
|
|
Ok(packet)
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
pcid_interface::pci_daemon(daemon);
|
|
}
|
|
|
|
fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
|
let pci_config = pcid_handle.config();
|
|
|
|
let mut name = pci_config.func.name();
|
|
name.push_str("_vbox");
|
|
|
|
let bar0 = match pci_config.func.bars[0].try_port() {
|
|
Ok(port) => port,
|
|
Err(err) => {
|
|
eprintln!("vboxd: invalid BAR0: {err}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
|
|
let irq = pci_config
|
|
.func
|
|
.legacy_interrupt_line
|
|
.unwrap_or_else(|| {
|
|
eprintln!("vboxd: no legacy interrupts supported");
|
|
std::process::exit(1);
|
|
});
|
|
|
|
println!(" + VirtualBox {}", pci_config.func.display());
|
|
|
|
if let Err(err) = common::acquire_port_io_rights() {
|
|
eprintln!("vboxd: failed to get I/O permission: {err}");
|
|
std::process::exit(1);
|
|
}
|
|
|
|
let mut width = 0;
|
|
let mut height = 0;
|
|
let mut display_opt = File::open("inputd:producer").ok();
|
|
if let Some(ref display) = display_opt {
|
|
let mut buf: [u8; 4096] = [0; 4096];
|
|
if let Ok(count) = libredox::call::fpath(display.as_raw_fd() as usize, &mut buf) {
|
|
let path = unsafe { String::from_utf8_unchecked(Vec::from(&buf[..count])) };
|
|
let res = path.split(":").nth(1).unwrap_or("");
|
|
width = res
|
|
.split("/")
|
|
.nth(1)
|
|
.unwrap_or("")
|
|
.parse::<u32>()
|
|
.unwrap_or(0);
|
|
height = res
|
|
.split("/")
|
|
.nth(2)
|
|
.unwrap_or("")
|
|
.parse::<u32>()
|
|
.unwrap_or(0);
|
|
}
|
|
}
|
|
|
|
let mut irq_file = match irq.try_irq_handle("vboxd") {
|
|
Ok(file) => file,
|
|
Err(err) => {
|
|
eprintln!("vboxd: failed to open IRQ handle: {err}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
|
|
let address = match unsafe { pcid_handle.try_map_bar(1) } {
|
|
Ok(bar) => bar.ptr.as_ptr(),
|
|
Err(err) => {
|
|
eprintln!("vboxd: failed to map BAR1: {err}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
{
|
|
let mut port = common::io::Pio::<u32>::new(bar0 as u16);
|
|
|
|
let vmmdev = unsafe { &mut *(address as *mut VboxVmmDev) };
|
|
|
|
let mut guest_info = match VboxGuestInfo::new() {
|
|
Ok(value) => value,
|
|
Err(err) => {
|
|
eprintln!("vboxd: failed to map GuestInfo: {err}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
guest_info.version.write(VBOX_VMMDEV_VERSION);
|
|
guest_info.ostype.write(0x100);
|
|
port.write(guest_info.physical() as u32);
|
|
|
|
let mut guest_caps = match VboxGuestCaps::new() {
|
|
Ok(value) => value,
|
|
Err(err) => {
|
|
eprintln!("vboxd: failed to map GuestCaps: {err}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
guest_caps.caps.write(1 << 2);
|
|
port.write(guest_caps.physical() as u32);
|
|
|
|
let mut set_mouse = match VboxSetMouse::new() {
|
|
Ok(value) => value,
|
|
Err(err) => {
|
|
eprintln!("vboxd: failed to map SetMouse: {err}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
set_mouse.features.write(1 << 4 | 1);
|
|
port.write(set_mouse.physical() as u32);
|
|
|
|
vmmdev
|
|
.guest_events
|
|
.write(VBOX_EVENT_DISPLAY | VBOX_EVENT_MOUSE);
|
|
|
|
user_data! {
|
|
enum Source {
|
|
Irq,
|
|
}
|
|
}
|
|
|
|
let event_queue = match EventQueue::<Source>::new() {
|
|
Ok(queue) => queue,
|
|
Err(err) => {
|
|
eprintln!("vboxd: could not create event queue: {err}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
event_queue
|
|
.subscribe(
|
|
irq_file.as_raw_fd() as usize,
|
|
Source::Irq,
|
|
event::EventFlags::READ,
|
|
)
|
|
.unwrap_or_else(|err| {
|
|
eprintln!("vboxd: failed to subscribe IRQ fd: {err}");
|
|
std::process::exit(1);
|
|
});
|
|
|
|
daemon.ready();
|
|
|
|
if let Err(err) = libredox::call::setrens(0, 0) {
|
|
eprintln!("vboxd: failed to enter null namespace: {err}");
|
|
std::process::exit(1);
|
|
}
|
|
|
|
let mut bga = crate::bga::Bga::new();
|
|
let get_mouse = match VboxGetMouse::new() {
|
|
Ok(value) => value,
|
|
Err(err) => {
|
|
eprintln!("vboxd: failed to map GetMouse: {err}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
let display_change = match VboxDisplayChange::new() {
|
|
Ok(value) => value,
|
|
Err(err) => {
|
|
eprintln!("vboxd: failed to map DisplayChange: {err}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
let ack_events = match VboxAckEvents::new() {
|
|
Ok(value) => value,
|
|
Err(err) => {
|
|
eprintln!("vboxd: failed to map AckEvents: {err}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
|
|
for Source::Irq in iter::once(Source::Irq).chain(event_queue.map(|e| match e {
|
|
Ok(event) => event.user_data,
|
|
Err(err) => {
|
|
eprintln!("vboxd: failed to get next event: {err}");
|
|
std::process::exit(1);
|
|
}
|
|
})) {
|
|
let mut irq = [0; 8];
|
|
match irq_file.read(&mut irq) {
|
|
Ok(read) if read >= irq.len() => {
|
|
let host_events = vmmdev.host_events.read();
|
|
if host_events != 0 {
|
|
port.write(ack_events.physical() as u32);
|
|
}
|
|
// The kernel masks the IRQ line on delivery and only re-arms
|
|
// it on write-back; ack unconditionally or a foreign interrupt
|
|
// on a shared line leaves the line masked forever.
|
|
if let Err(err) = irq_file.write(&irq) {
|
|
eprintln!("vboxd: failed to acknowledge IRQ: {err}");
|
|
std::process::exit(1);
|
|
}
|
|
|
|
if host_events != 0 {
|
|
if host_events & VBOX_EVENT_DISPLAY == VBOX_EVENT_DISPLAY {
|
|
port.write(display_change.physical() as u32);
|
|
if let Some(ref mut display) = display_opt {
|
|
let new_width = display_change.xres.read();
|
|
let new_height = display_change.yres.read();
|
|
if width != new_width || height != new_height {
|
|
width = new_width;
|
|
height = new_height;
|
|
println!("Display {}, {}", width, height);
|
|
bga.set_size(width as u16, height as u16);
|
|
let _ = display
|
|
.write(&orbclient::ResizeEvent { width, height }.to_event());
|
|
}
|
|
}
|
|
}
|
|
|
|
if host_events & VBOX_EVENT_MOUSE == VBOX_EVENT_MOUSE {
|
|
port.write(get_mouse.physical() as u32);
|
|
if let Some(ref mut display) = display_opt {
|
|
let x = get_mouse.x.read() * width / 0x10000;
|
|
let y = get_mouse.y.read() * height / 0x10000;
|
|
let _ = display.write(
|
|
&orbclient::MouseEvent {
|
|
x: x as i32,
|
|
y: y as i32,
|
|
}
|
|
.to_event(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(_) => {}
|
|
Err(err) => {
|
|
eprintln!("vboxd: failed to read IRQ file: {err}");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::process::exit(1);
|
|
}
|