//! Init hands each driver its device's capabilities, one per `Call` on the //! driver's endpoint; drivers map MMIO and allocate DMA themselves. use crate::syscall::{self, Received, Result}; use types::address::VirtualAddress; use types::bounded::{CapSlot, PageCount}; use types::mapping::PagePerms; /// First word of a setup call; the second says what the capability is. pub const SETUP: usize = 0xDE71CE; pub const MMIO: usize = 1; pub const IRQ: usize = 2; /// No more capabilities follow (there may have been none: no such device). pub const DONE: usize = 3; /// What kind of device the MMIO region is, so a driver never has to probe /// (reading a UART's first register would eat a received byte). pub const KIND_VIRTIO: usize = types::bootinfo::KIND_VIRTIO as usize; pub const KIND_UART_16550: usize = types::bootinfo::KIND_UART_16550 as usize; /// What a driver was given. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub struct Device { pub mmio: Option, pub irq: Option, /// One of the `KIND_` constants; 0 with no MMIO. pub kind: usize, } impl Device { pub const NONE: Device = Device { mmio: None, irq: None, kind: 0 }; } /// Driver side: answers init's setup calls on `endpoint` until `DONE`. /// Anything else is refused, so a client cannot pose as init. pub fn receive_device(endpoint: CapSlot) -> Device { let mut device = Device::NONE; loop { let Ok(Received::Call { words, cap, .. }) = syscall::receive_msg(endpoint) else { continue; }; let ok = words[0] == SETUP; match (ok, words[1], cap) { (true, MMIO, Some(c)) => { device.mmio = Some(c); device.kind = words[2]; } (true, IRQ, Some(c)) => device.irq = Some(c), _ => {} } let _ = syscall::reply([if ok { 0 } else { usize::MAX }, 0, 0, 0], None); if ok && words[1] == DONE { return device; } } } /// Init side: gives `device` to the driver serving `endpoint`. pub fn give_device(endpoint: CapSlot, device: Device) -> Result<()> { for (what, cap) in [(MMIO, device.mmio), (IRQ, device.irq)] { if let Some(cap) = cap { syscall::call(endpoint, [SETUP, what, device.kind, 0], Some(cap))?; } } syscall::call(endpoint, [SETUP, DONE, 0, 0], None).map(|_| ()) } /// Maps a device's MMIO region read-write at `va`. pub fn map_mmio(region: CapSlot, va: usize) -> Result<()> { syscall::map_region(region, VirtualAddress::from_raw(va), PagePerms::ReadWrite) } /// Fresh zeroed, physically contiguous memory for DMA, mapped read-write /// at `va`. Returns its physical address, for programming the device. pub fn dma(pages: u16, va: usize) -> Result { let pages = PageCount::new(pages).ok_or(syscall::Error::InvalidArgument)?; let region = syscall::create_region(pages)?; syscall::map_region(region, VirtualAddress::from_raw(va), PagePerms::ReadWrite)?; syscall::region_address(region) } /// Routes `irq` to a fresh notification bound to the calling thread, so /// its receive calls also return when the interrupt fires, and arms the /// line (lines start masked; every acknowledgement re-arms). pub fn bind_irq(irq: CapSlot) -> Result<()> { let notification = syscall::create_notification()?; syscall::set_irq_notification(irq, notification)?; syscall::bind_notification(notification)?; syscall::ack_interrupt(irq) }