91 lines
3.4 KiB
Rust
91 lines
3.4 KiB
Rust
//! 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<CapSlot>,
|
|
pub irq: Option<CapSlot>,
|
|
/// 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<usize> {
|
|
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)
|
|
}
|