Add runtime tools and Red Bear service wiring
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,371 @@
|
||||
use core::mem::size_of;
|
||||
use core::slice;
|
||||
|
||||
use redox_driver_sys::dma::DmaBuffer;
|
||||
|
||||
pub const COMMAND_ENTRY_SIZE: usize = 16;
|
||||
pub const EVENT_LOG_ENTRY_SIZE: usize = 16;
|
||||
|
||||
const DMA_ALIGNMENT: usize = 4096;
|
||||
|
||||
pub const CMD_COMPLETION_WAIT: u32 = 0x01;
|
||||
pub const CMD_INVALIDATE_DEVTAB_ENTRY: u32 = 0x02;
|
||||
pub const CMD_INVALIDATE_IOMMU_PAGES: u32 = 0x03;
|
||||
pub const CMD_INVALIDATE_INTERRUPT_TABLE: u32 = 0x04;
|
||||
pub const CMD_INVALIDATE_IOMMU_ALL: u32 = 0x05;
|
||||
|
||||
pub const EVENT_IO_PAGE_FAULT: u32 = 0x01;
|
||||
pub const EVENT_INVALIDATE_DEVICE_TABLE: u32 = 0x02;
|
||||
|
||||
const COMPLETION_WAIT_STORE_BIT: u32 = 1 << 4;
|
||||
const COMPLETION_WAIT_INTERRUPT_BIT: u32 = 1 << 5;
|
||||
const INVALIDATE_PAGES_PDE_BIT: u32 = 1 << 12;
|
||||
const INVALIDATE_PAGES_SIZE_BIT: u32 = 1 << 13;
|
||||
|
||||
/// Command buffer entry (128 bits = 16 bytes = 4 × u32).
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
#[repr(C)]
|
||||
pub struct CommandEntry {
|
||||
words: [u32; 4],
|
||||
}
|
||||
|
||||
impl CommandEntry {
|
||||
pub const fn new() -> Self {
|
||||
Self { words: [0; 4] }
|
||||
}
|
||||
|
||||
pub const fn from_words(words: [u32; 4]) -> Self {
|
||||
Self { words }
|
||||
}
|
||||
|
||||
pub fn words(&self) -> [u32; 4] {
|
||||
self.words
|
||||
}
|
||||
|
||||
pub fn opcode(&self) -> u32 {
|
||||
self.words[0] & 0xF
|
||||
}
|
||||
|
||||
/// COMPLETION_WAIT (opcode 0x01).
|
||||
pub fn completion_wait(store_addr: u64, store_data: u32) -> Self {
|
||||
debug_assert_eq!(
|
||||
store_addr & 0x7,
|
||||
0,
|
||||
"completion wait store address must be 8-byte aligned"
|
||||
);
|
||||
|
||||
Self {
|
||||
words: [
|
||||
CMD_COMPLETION_WAIT | COMPLETION_WAIT_STORE_BIT,
|
||||
store_addr as u32,
|
||||
(store_addr >> 32) as u32,
|
||||
store_data,
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// INVALIDATE_DEVTAB_ENTRY (opcode 0x02).
|
||||
pub fn invalidate_devtab_entry(device_id: u16) -> Self {
|
||||
Self {
|
||||
words: [CMD_INVALIDATE_DEVTAB_ENTRY, device_id as u32, 0, 0],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invalidate_pages(domain_id: u16, addr: u64) -> Self {
|
||||
Self::invalidate_pages_with_flags(domain_id, addr, false, false)
|
||||
}
|
||||
|
||||
pub fn invalidate_pages_with_flags(domain_id: u16, addr: u64, pde: bool, size: bool) -> Self {
|
||||
let mut word0 = CMD_INVALIDATE_IOMMU_PAGES;
|
||||
if pde {
|
||||
word0 |= INVALIDATE_PAGES_PDE_BIT;
|
||||
}
|
||||
if size {
|
||||
word0 |= INVALIDATE_PAGES_SIZE_BIT;
|
||||
}
|
||||
|
||||
Self {
|
||||
words: [word0, domain_id as u32, addr as u32, (addr >> 32) as u32],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invalidate_interrupt_table(device_id: u16) -> Self {
|
||||
Self {
|
||||
words: [CMD_INVALIDATE_INTERRUPT_TABLE, device_id as u32, 0, 0],
|
||||
}
|
||||
}
|
||||
|
||||
/// INVALIDATE_IOMMU_ALL (opcode 0x05).
|
||||
pub fn invalidate_all() -> Self {
|
||||
Self {
|
||||
words: [CMD_INVALIDATE_IOMMU_ALL, 0, 0, 0],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn completion_wait_store(&self) -> bool {
|
||||
self.words[0] & COMPLETION_WAIT_STORE_BIT != 0
|
||||
}
|
||||
|
||||
pub fn completion_wait_interrupt(&self) -> bool {
|
||||
self.words[0] & COMPLETION_WAIT_INTERRUPT_BIT != 0
|
||||
}
|
||||
|
||||
pub fn completion_wait_store_address(&self) -> u64 {
|
||||
(self.words[1] as u64) | ((self.words[2] as u64) << 32)
|
||||
}
|
||||
|
||||
pub fn completion_wait_store_data(&self) -> u32 {
|
||||
self.words[3]
|
||||
}
|
||||
|
||||
pub fn invalidate_device_id(&self) -> u16 {
|
||||
self.words[1] as u16
|
||||
}
|
||||
|
||||
pub fn invalidate_pages_pde(&self) -> bool {
|
||||
self.words[0] & INVALIDATE_PAGES_PDE_BIT != 0
|
||||
}
|
||||
|
||||
pub fn invalidate_pages_size(&self) -> bool {
|
||||
self.words[0] & INVALIDATE_PAGES_SIZE_BIT != 0
|
||||
}
|
||||
|
||||
pub fn invalidate_pages_address(&self) -> u64 {
|
||||
(self.words[2] as u64) | ((self.words[3] as u64) << 32)
|
||||
}
|
||||
}
|
||||
|
||||
const _: () = assert!(size_of::<CommandEntry>() == COMMAND_ENTRY_SIZE);
|
||||
|
||||
pub struct CommandBuffer {
|
||||
buffer: DmaBuffer,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl CommandBuffer {
|
||||
pub fn new(entry_count: usize) -> Result<Self, &'static str> {
|
||||
if entry_count == 0 {
|
||||
return Err("IOMMU command buffer entry count must be non-zero");
|
||||
}
|
||||
|
||||
let byte_len = entry_count
|
||||
.checked_mul(COMMAND_ENTRY_SIZE)
|
||||
.ok_or("IOMMU command buffer size overflow")?;
|
||||
|
||||
let buffer = DmaBuffer::allocate(byte_len, DMA_ALIGNMENT)
|
||||
.map_err(|_| "failed to allocate IOMMU command buffer")?;
|
||||
|
||||
if buffer.len() < byte_len {
|
||||
return Err("IOMMU command buffer allocation was smaller than requested");
|
||||
}
|
||||
|
||||
if !buffer.is_physically_contiguous() {
|
||||
return Err("IOMMU command buffer allocation is not physically contiguous");
|
||||
}
|
||||
|
||||
if buffer.physical_address() & (DMA_ALIGNMENT - 1) != 0 {
|
||||
return Err("IOMMU command buffer allocation is not 4KiB-aligned");
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
buffer,
|
||||
capacity: entry_count,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn physical_address(&self) -> usize {
|
||||
self.buffer.physical_address()
|
||||
}
|
||||
|
||||
/// Write a command at the given index.
|
||||
pub fn write_command(&mut self, index: usize, cmd: &CommandEntry) {
|
||||
assert!(index < self.capacity, "IOMMU command index out of bounds");
|
||||
self.commands_mut()[index] = *cmd;
|
||||
}
|
||||
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.capacity
|
||||
}
|
||||
|
||||
fn commands_mut(&mut self) -> &mut [CommandEntry] {
|
||||
unsafe {
|
||||
slice::from_raw_parts_mut(self.buffer.as_mut_ptr() as *mut CommandEntry, self.capacity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Event log entry (128 bits = 16 bytes).
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
#[repr(C)]
|
||||
pub struct EventLogEntry {
|
||||
words: [u32; 4],
|
||||
}
|
||||
|
||||
impl EventLogEntry {
|
||||
pub const fn new() -> Self {
|
||||
Self { words: [0; 4] }
|
||||
}
|
||||
|
||||
pub const fn from_words(words: [u32; 4]) -> Self {
|
||||
Self { words }
|
||||
}
|
||||
|
||||
pub fn words(&self) -> [u32; 4] {
|
||||
self.words
|
||||
}
|
||||
|
||||
pub fn event_type(&self) -> u32 {
|
||||
self.words[0] & 0xFFFF
|
||||
}
|
||||
|
||||
pub fn event_flags(&self) -> u16 {
|
||||
((self.words[0] >> 16) & 0xFFFF) as u16
|
||||
}
|
||||
|
||||
pub fn device_id(&self) -> u16 {
|
||||
self.words[1] as u16
|
||||
}
|
||||
|
||||
pub fn virtual_address(&self) -> u64 {
|
||||
((self.words[3] as u64) << 32) | (self.words[2] as u64)
|
||||
}
|
||||
}
|
||||
|
||||
const _: () = assert!(size_of::<EventLogEntry>() == EVENT_LOG_ENTRY_SIZE);
|
||||
|
||||
pub struct EventLog {
|
||||
buffer: DmaBuffer,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl EventLog {
|
||||
pub fn new(entry_count: usize) -> Result<Self, &'static str> {
|
||||
if entry_count == 0 {
|
||||
return Err("IOMMU event log entry count must be non-zero");
|
||||
}
|
||||
|
||||
let byte_len = entry_count
|
||||
.checked_mul(EVENT_LOG_ENTRY_SIZE)
|
||||
.ok_or("IOMMU event log size overflow")?;
|
||||
|
||||
let buffer = DmaBuffer::allocate(byte_len, DMA_ALIGNMENT)
|
||||
.map_err(|_| "failed to allocate IOMMU event log")?;
|
||||
|
||||
if buffer.len() < byte_len {
|
||||
return Err("IOMMU event log allocation was smaller than requested");
|
||||
}
|
||||
|
||||
if !buffer.is_physically_contiguous() {
|
||||
return Err("IOMMU event log allocation is not physically contiguous");
|
||||
}
|
||||
|
||||
if buffer.physical_address() & (DMA_ALIGNMENT - 1) != 0 {
|
||||
return Err("IOMMU event log allocation is not 4KiB-aligned");
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
buffer,
|
||||
capacity: entry_count,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn physical_address(&self) -> usize {
|
||||
self.buffer.physical_address()
|
||||
}
|
||||
|
||||
pub fn read_entry(&self, index: usize) -> EventLogEntry {
|
||||
assert!(index < self.capacity, "IOMMU event log index out of bounds");
|
||||
self.entries()[index]
|
||||
}
|
||||
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.capacity
|
||||
}
|
||||
|
||||
fn entries(&self) -> &[EventLogEntry] {
|
||||
unsafe {
|
||||
slice::from_raw_parts(self.buffer.as_ptr() as *const EventLogEntry, self.capacity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
CommandEntry, EventLogEntry, CMD_COMPLETION_WAIT, CMD_INVALIDATE_DEVTAB_ENTRY,
|
||||
CMD_INVALIDATE_IOMMU_ALL, CMD_INVALIDATE_IOMMU_PAGES, EVENT_IO_PAGE_FAULT,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_completion_wait_command() {
|
||||
let store_addr = 0x1234_5000_0000_1000;
|
||||
let store_data = 0xabcdefff;
|
||||
let cmd = CommandEntry::completion_wait(store_addr, store_data);
|
||||
let words = cmd.words();
|
||||
|
||||
assert_eq!(cmd.opcode(), CMD_COMPLETION_WAIT);
|
||||
assert!(cmd.completion_wait_store());
|
||||
assert!(!cmd.completion_wait_interrupt());
|
||||
assert_eq!(words[1], store_addr as u32);
|
||||
assert_eq!(words[2], (store_addr >> 32) as u32);
|
||||
assert_eq!(words[3], store_data);
|
||||
assert_eq!(cmd.completion_wait_store_address(), store_addr);
|
||||
assert_eq!(cmd.completion_wait_store_data(), store_data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalidate_devtab_command() {
|
||||
let device_id = 0x1234;
|
||||
let cmd = CommandEntry::invalidate_devtab_entry(device_id);
|
||||
let words = cmd.words();
|
||||
|
||||
assert_eq!(cmd.opcode(), CMD_INVALIDATE_DEVTAB_ENTRY);
|
||||
assert_eq!(cmd.invalidate_device_id(), device_id);
|
||||
assert_eq!(words[1], device_id as u32);
|
||||
assert_eq!(words[2], 0);
|
||||
assert_eq!(words[3], 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalidate_pages_command() {
|
||||
let device_id = 0x4321;
|
||||
let addr = 0xfeed_cafe_b000;
|
||||
let cmd = CommandEntry::invalidate_pages(device_id, addr);
|
||||
let words = cmd.words();
|
||||
|
||||
assert_eq!(cmd.opcode(), CMD_INVALIDATE_IOMMU_PAGES);
|
||||
assert_eq!(cmd.invalidate_device_id(), device_id);
|
||||
assert!(!cmd.invalidate_pages_pde());
|
||||
assert!(!cmd.invalidate_pages_size());
|
||||
assert_eq!(words[1], device_id as u32);
|
||||
assert_eq!(cmd.invalidate_pages_address(), addr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalidate_all_command() {
|
||||
let cmd = CommandEntry::invalidate_all();
|
||||
let words = cmd.words();
|
||||
|
||||
assert_eq!(cmd.opcode(), CMD_INVALIDATE_IOMMU_ALL);
|
||||
assert_eq!(words[1], 0);
|
||||
assert_eq!(words[2], 0);
|
||||
assert_eq!(words[3], 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_event_entry_parsing() {
|
||||
let device_id = 0x2468;
|
||||
let address = 0x0123_4567_89ab_cdef;
|
||||
let entry = EventLogEntry::from_words([
|
||||
EVENT_IO_PAGE_FAULT | ((0x5a as u32) << 16),
|
||||
device_id as u32,
|
||||
address as u32,
|
||||
(address >> 32) as u32,
|
||||
]);
|
||||
|
||||
assert_eq!(entry.event_type(), EVENT_IO_PAGE_FAULT);
|
||||
assert_eq!(entry.event_flags(), 0x5a);
|
||||
assert_eq!(entry.device_id(), device_id);
|
||||
assert_eq!(entry.virtual_address(), address);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user