Support IOMMU

This commit is contained in:
Yuke Peng
2023-07-05 06:35:07 -07:00
committed by Tate, Hongliang Tian
parent 829575b3a6
commit a47b98b160
19 changed files with 1311 additions and 89 deletions

1
Cargo.lock generated
View File

@ -464,6 +464,7 @@ name = "jinux-boot"
version = "0.1.0"
dependencies = [
"anyhow",
"cfg-if",
"runner-utils",
]

View File

@ -7,7 +7,9 @@ edition = "2021"
[dependencies]
runner-utils = "0.0.2"
anyhow = "1.0.32"
cfg-if = "1.0"
[features]
default = ["limine"]
limine = []
iommu = []

View File

@ -10,24 +10,12 @@ use std::{
const COMMON_ARGS: &[&str] = &[
"--no-reboot",
"-machine",
"q35",
"q35,kernel-irqchip=split",
"-enable-kvm",
"-cpu",
"Icelake-Server,+x2apic",
"-m",
"2G",
"-device",
"isa-debug-exit,iobase=0xf4,iosize=0x04",
"-device",
"virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0",
"-device",
"virtio-keyboard-pci",
"-device",
"virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off",
"-netdev",
"user,id=net01,hostfwd=tcp::30022-:22,hostfwd=tcp::30080-:8080",
"-object",
"filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
"-monitor",
"vc",
"-serial",
@ -36,6 +24,44 @@ const COMMON_ARGS: &[&str] = &[
"none",
];
cfg_if::cfg_if!(
if #[cfg(feature="iommu")] {
macro_rules! virtio_device_args {
($($args:tt),*) => {
concat!($($args,)*"disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",)
};
}
const OPTION_ARGS: &[&str] = &[
"-device",
"intel-iommu,intremap=on,device-iotlb=on",
"-device",
"ioh3420,id=pcie.0,chassis=1",
];
} else {
macro_rules! virtio_device_args {
($($args:tt),*) => {
concat!($($args,)*"disable-legacy=on,disable-modern=off",)
};
}
const OPTION_ARGS: &[&str] = &[];
}
);
const DEVICE_ARGS: &[&str] = &[
"-device",
"isa-debug-exit,iobase=0xf4,iosize=0x04",
"-device",
virtio_device_args!("virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,"),
"-device",
virtio_device_args!("virtio-keyboard-pci,"),
"-device",
virtio_device_args!("virtio-net-pci,netdev=net01,"),
"-netdev",
"user,id=net01,hostfwd=tcp::30022-:22,hostfwd=tcp::30080-:8080",
"-object",
"filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
];
const RUN_ARGS: &[&str] = &[];
const TEST_ARGS: &[&str] = &[];
const TEST_TIMEOUT_SECS: u64 = 30;
@ -62,13 +88,12 @@ fn main() -> anyhow::Result<()> {
a.join(str.add(".iso"))
};
#[cfg(windows)]
let mut qemu_cmd = Command::new("qemu-system-x86_64.exe");
#[cfg(not(windows))]
let mut qemu_cmd = Command::new("qemu-system-x86_64");
let binary_kind = runner_utils::binary_kind(&kernel_binary_path);
let mut qemu_args = COMMON_ARGS.clone().to_vec();
qemu_args.extend(DEVICE_ARGS.clone().to_vec().iter());
qemu_args.extend(OPTION_ARGS.clone().to_vec().iter());
qemu_args.push("-drive");
let binding = create_fs_image(kernel_binary_path.as_path())?;
qemu_args.push(binding.as_str());
@ -110,9 +135,6 @@ fn call_limine_build_script(path: &PathBuf) -> anyhow::Result<()> {
fn create_fs_image(path: &Path) -> anyhow::Result<String> {
let mut fs_img_path = path.parent().unwrap().to_str().unwrap().to_string();
#[cfg(windows)]
fs_img_path.push_str("\\fs.img");
#[cfg(not(windows))]
fs_img_path.push_str("/fs.img");
let path = Path::new(fs_img_path.as_str());
if path.exists() {

View File

@ -0,0 +1,313 @@
use core::mem::size_of;
use alloc::collections::BTreeMap;
use log::warn;
use pod::Pod;
use crate::{
bus::pci::PciDeviceLocation,
config::PAGE_SIZE,
vm::{
page_table::{PageTableConfig, PageTableError},
Paddr, PageTable, Vaddr, VmAllocOptions, VmFrame, VmFrameVec, VmIo,
},
};
use super::second_stage::{PageTableEntry, PageTableFlags};
/// Bit 0 is `Present` bit, indicating whether this entry is present.
/// Bit 63:12 is the context-table pointer pointing to this bus's context-table.
#[derive(Pod, Clone, Copy)]
#[repr(C)]
pub struct RootEntry(u128);
impl RootEntry {
pub const fn present(&self) -> bool {
// Bit 0
(self.0 & 0b1) != 0
}
pub const fn addr(&self) -> u64 {
(self.0 & 0xFFFF_FFFF_FFFF_F000) as u64
}
}
pub struct RootTable {
/// Total 256 bus, each entry is 128 bits.
root_frame: VmFrame,
// TODO: Use radix tree instead.
context_tables: BTreeMap<Paddr, ContextTable>,
}
#[derive(Debug)]
pub enum ContextTableError {
InvalidDeviceId,
/// Error when modifying the page table
ModificationError(PageTableError),
}
impl RootTable {
pub fn new() -> Self {
Self {
root_frame: VmFrameVec::allocate(&VmAllocOptions::new(1).uninit(false))
.unwrap()
.pop()
.unwrap(),
context_tables: BTreeMap::new(),
}
}
pub fn map(
&mut self,
device: PciDeviceLocation,
vaddr: Vaddr,
paddr: Paddr,
) -> Result<(), ContextTableError> {
if device.device >= 32 || device.function >= 8 {
return Err(ContextTableError::InvalidDeviceId);
}
self.get_or_create_context_table(device)
.map(device, vaddr, paddr & !(PAGE_SIZE - 1))?;
Ok(())
}
pub fn unmap(
&mut self,
device: PciDeviceLocation,
vaddr: Vaddr,
) -> Result<(), ContextTableError> {
if device.device >= 32 || device.function >= 8 {
return Err(ContextTableError::InvalidDeviceId);
}
self.get_or_create_context_table(device)
.unmap(device, vaddr)?;
Ok(())
}
pub fn paddr(&self) -> Paddr {
self.root_frame.start_paddr()
}
fn get_or_create_context_table(&mut self, device_id: PciDeviceLocation) -> &mut ContextTable {
let bus_entry = self
.root_frame
.read_val::<RootEntry>(device_id.bus as usize * size_of::<RootEntry>())
.unwrap();
if !bus_entry.present() {
let table = ContextTable::new();
let address = table.paddr();
self.context_tables.insert(address, table);
let entry = RootEntry(address as u128 | 1);
self.root_frame
.write_val::<RootEntry>(device_id.bus as usize * size_of::<RootEntry>(), &entry)
.unwrap();
self.context_tables.get_mut(&address).unwrap()
} else {
self.context_tables
.get_mut(&(bus_entry.addr() as usize))
.unwrap()
}
}
/// Specify the device page table instead of creating a page table if not exists.
///
/// This will be useful if we want all the devices to use the same page table.
/// The original page table will be overwritten.
pub fn specify_device_page_table(
&mut self,
device_id: PciDeviceLocation,
page_table: PageTable<PageTableEntry>,
) {
let context_table = self.get_or_create_context_table(device_id);
let bus_entry = context_table
.entries_frame
.read_val::<ContextEntry>(
(device_id.device as usize * 8 + device_id.function as usize) as usize
* size_of::<ContextEntry>(),
)
.unwrap();
if bus_entry.present() {
warn!("IOMMU: Overwritting the existing device page table");
}
let address = page_table.root_paddr();
context_table.page_tables.insert(address, page_table);
let entry = ContextEntry(address as u128 | 3 | 0x1_0000_0000_0000_0000);
context_table
.entries_frame
.write_val::<ContextEntry>(
(device_id.device as usize * 8 + device_id.function as usize) as usize
* size_of::<ContextEntry>(),
&entry,
)
.unwrap();
context_table.page_tables.get_mut(&address).unwrap();
}
}
/// Context Entry in the Context Table, used in Intel iommu.
///
/// The format of context entry:
/// ```
/// 127--88: Reserved.
/// 87---72: Domain Identifier.
/// 71---71: Reserved.
/// 70---67: Ignored.
/// 66---64: Address Width.
/// 63---12: Second Stage Page Translation Pointer.
/// 11----4: Reserved.
/// 3-----2: Translation Type.
/// 1-----1: Fault Processing Disable.
/// 0-----0: Present
/// ```
///
#[derive(Pod, Clone, Copy)]
#[repr(C)]
pub struct ContextEntry(u128);
impl ContextEntry {
/// Identifier for the domain to which this context-entry maps. Hardware may use the domain
/// identifier to tag its internal caches
pub const fn domain_identifier(&self) -> u64 {
// Bit 87-72
((self.0 & 0xFF_FF00_0000_0000_0000_0000) >> 72) as u64
}
pub const fn address_width(&self) -> AddressWidth {
// Bit 66-64
let value = ((self.0 & 0x7_0000_0000_0000_0000) >> 64) as u64;
match value {
1 => AddressWidth::Level3PageTable,
2 => AddressWidth::Level4PageTable,
3 => AddressWidth::Level5PageTable,
_ => AddressWidth::Reserved,
}
}
/// Get the second stage page translation pointer.
///
/// This function will not right shift the value after the `and` operation.
pub const fn second_stage_pointer(&self) -> u64 {
// Bit 63~12
(self.0 & 0xFFFF_FFFF_FFFF_F000) as u64
}
/// This field is applicable only for requests-without-PASID, as hardware blocks all requests-with
/// PASID in legacy mode before they can use context table
pub const fn translation_type(&self) -> u64 {
// Bit 3~2
((self.0 & 0b1100) >> 2) as u64
}
/// Enables or disables recording/reporting of qualified non-recoverable faults.
pub const fn fault_process_disable(&self) -> bool {
// Bit 1
(self.0 & 0b10) != 0
}
pub const fn present(&self) -> bool {
// Bit 0
(self.0 & 0b1) != 0
}
}
#[derive(Debug)]
pub enum AddressWidth {
/// 000b, 100b~111b
Reserved,
/// 001b
Level3PageTable,
/// 010b
Level4PageTable,
/// 011b
Level5PageTable,
}
pub struct ContextTable {
/// Total 32 devices, each device has 8 functions.
entries_frame: VmFrame,
page_tables: BTreeMap<Paddr, PageTable<PageTableEntry>>,
}
impl ContextTable {
fn new() -> Self {
Self {
entries_frame: VmFrameVec::allocate(&VmAllocOptions::new(1).uninit(false))
.unwrap()
.pop()
.unwrap(),
page_tables: BTreeMap::new(),
}
}
fn paddr(&self) -> Paddr {
self.entries_frame.start_paddr()
}
fn get_or_create_page_table(
&mut self,
device: PciDeviceLocation,
) -> &mut PageTable<PageTableEntry> {
let bus_entry = self
.entries_frame
.read_val::<ContextEntry>(
(device.device as usize * 8 + device.function as usize) as usize
* size_of::<ContextEntry>(),
)
.unwrap();
if !bus_entry.present() {
let table: PageTable<PageTableEntry> = PageTable::new(PageTableConfig {
address_width: crate::vm::page_table::AddressWidth::Level3PageTable,
});
let address = table.root_paddr();
self.page_tables.insert(address, table);
let entry = ContextEntry(address as u128 | 3 | 0x1_0000_0000_0000_0000);
self.entries_frame
.write_val::<ContextEntry>(
(device.device as usize * 8 + device.function as usize) as usize
* size_of::<ContextEntry>(),
&entry,
)
.unwrap();
self.page_tables.get_mut(&address).unwrap()
} else {
self.page_tables
.get_mut(&(bus_entry.second_stage_pointer() as usize))
.unwrap()
}
}
fn map(
&mut self,
device: PciDeviceLocation,
vaddr: Vaddr,
paddr: Paddr,
) -> Result<(), ContextTableError> {
if device.device >= 32 || device.function >= 8 {
return Err(ContextTableError::InvalidDeviceId);
}
self.get_or_create_page_table(device)
.map(
vaddr,
paddr,
PageTableFlags::WRITABLE | PageTableFlags::READABLE | PageTableFlags::LAST_PAGE,
)
.map_err(|err| ContextTableError::ModificationError(err))
}
fn unmap(&mut self, device: PciDeviceLocation, vaddr: Vaddr) -> Result<(), ContextTableError> {
if device.device >= 32 || device.function >= 8 {
return Err(ContextTableError::InvalidDeviceId);
}
self.get_or_create_page_table(device)
.unmap(vaddr)
.map_err(|err| ContextTableError::ModificationError(err))
}
}

View File

@ -0,0 +1,173 @@
mod context_table;
mod second_stage;
use log::debug;
use spin::{Mutex, Once};
use crate::{
arch::{
iommu::{context_table::RootTable, second_stage::PageTableEntry},
x86::kernel::acpi::{
dmar::{Dmar, Remapping},
ACPI_TABLES,
},
},
bus::pci::PciDeviceLocation,
vm::{
paddr_to_vaddr,
page_table::{PageTableConfig, PageTableError},
Paddr, PageTable, Vaddr,
},
};
use volatile::{
access::{ReadOnly, ReadWrite, WriteOnly},
Volatile,
};
#[derive(Debug)]
pub enum IommuError {
NoIommu,
ModificationError(PageTableError),
}
// FIXME: Perform map operations by obtaining ownership of a VmFrame.
///
/// # Safety
///
/// Mapping an incorrect address may lead to a kernel data leak.
pub(crate) unsafe fn map(vaddr: Vaddr, paddr: Paddr) -> Result<(), IommuError> {
let Some(table) = PAGE_TABLE.get() else{
return Err(IommuError::NoIommu);
};
// The page table of all devices is the same. So we can use any device ID.
table
.lock()
.map(
PciDeviceLocation {
bus: 0,
device: 0,
function: 0,
},
vaddr,
paddr,
)
.map_err(|err| match err {
context_table::ContextTableError::InvalidDeviceId => unreachable!(),
context_table::ContextTableError::ModificationError(err) => {
IommuError::ModificationError(err)
}
})
}
pub(crate) fn unmap(vaddr: Vaddr) -> Result<(), IommuError> {
let Some(table) = PAGE_TABLE.get() else{
return Err(IommuError::NoIommu);
};
// The page table of all devices is the same. So we can use any device ID.
table
.lock()
.unmap(
PciDeviceLocation {
bus: 0,
device: 0,
function: 0,
},
vaddr,
)
.map_err(|err| match err {
context_table::ContextTableError::InvalidDeviceId => unreachable!(),
context_table::ContextTableError::ModificationError(err) => {
IommuError::ModificationError(err)
}
})
}
pub fn init() -> Result<(), IommuError> {
let mut remapping_reg = RemappingRegisters::new().ok_or(IommuError::NoIommu)?;
let mut root_table = RootTable::new();
// For all PCI Device, use the same page table.
let page_table: PageTable<PageTableEntry> = PageTable::new(PageTableConfig {
address_width: crate::vm::page_table::AddressWidth::Level3PageTable,
});
for table in PciDeviceLocation::all() {
root_table.specify_device_page_table(table, page_table.clone())
}
let paddr = root_table.paddr();
// write remapping register
remapping_reg.root_table_address.write(paddr as u64);
// start writing
remapping_reg.global_command.write(0x4000_0000);
// wait until complete
while remapping_reg.global_status.read() & 0x4000_0000 == 0 {}
// enable iommu
remapping_reg.global_command.write(0x8000_0000);
debug!("IOMMU registers:{:#x?}", remapping_reg);
PAGE_TABLE.call_once(|| Mutex::new(root_table));
Ok(())
}
#[derive(Debug)]
#[repr(C)]
struct RemappingRegisters {
version: Volatile<&'static u32, ReadOnly>,
capability: Volatile<&'static u64, ReadOnly>,
extended_capability: Volatile<&'static u64, ReadOnly>,
global_command: Volatile<&'static mut u32, WriteOnly>,
global_status: Volatile<&'static u32, ReadOnly>,
root_table_address: Volatile<&'static mut u64, ReadWrite>,
context_command: Volatile<&'static mut u64, ReadWrite>,
}
impl RemappingRegisters {
/// Create a instance from base address
fn new() -> Option<Self> {
let dmar = Dmar::new()?;
let acpi_table_lock = ACPI_TABLES.get().unwrap().lock();
debug!("DMAR:{:#x?}", dmar);
let base_address = {
let mut addr = 0;
for remapping in dmar.remapping_iter() {
match remapping {
Remapping::Drhd(drhd) => addr = drhd.register_base_addr(),
_ => {}
}
}
if addr == 0 {
panic!("There should be a DRHD structure in the DMAR table");
}
addr
};
let vaddr = paddr_to_vaddr(base_address as usize);
// Safety: All offsets and sizes are strictly adhered to in the manual, and the base address is obtained from Drhd.
unsafe {
let version = Volatile::new_read_only(&*(vaddr as *const u32));
let capability = Volatile::new_read_only(&*((vaddr + 0x08) as *const u64));
let extended_capability = Volatile::new_read_only(&*((vaddr + 0x10) as *const u64));
let global_command = Volatile::new_write_only(&mut *((vaddr + 0x18) as *mut u32));
let global_status = Volatile::new_read_only(&*((vaddr + 0x1C) as *const u32));
let root_table_address = Volatile::new(&mut *((vaddr + 0x20) as *mut u64));
let context_command = Volatile::new(&mut *((vaddr + 0x28) as *mut u64));
Some(Self {
version,
capability,
extended_capability,
global_command,
global_status,
root_table_address,
context_command,
})
}
}
}
static PAGE_TABLE: Once<Mutex<RootTable>> = Once::new();

View File

@ -0,0 +1,142 @@
use pod::Pod;
use crate::{
config::ENTRY_COUNT,
vm::page_table::{PageTableEntryTrait, PageTableFlagsTrait},
};
bitflags::bitflags! {
#[derive(Pod)]
#[repr(C)]
pub struct PageTableFlags : u64{
/// Whether accesses to this page must snoop processor caches.
const SNOOP = 1 << 11;
const DIRTY = 1 << 9;
const ACCESSED = 1 << 8;
/// Whether this page table entry is the last entry.
const LAST_PAGE = 1 << 7;
/// Ignore PAT, 1 if the scalable-mode PASID-table entry is not
/// used for effective memory-type determination.
const IGNORE_PAT = 1 << 6;
/// Extended Memory Type, ignored by hardware when the
/// Extended Memory Type Enable (EMTE) field is Clear.
///
/// When the EMTE field is Set, this field is used to compute effective
/// memory-type for second-stage-only and nested translations.
const EMT = 7 << 3;
const WRITABLE = 1 << 1;
const READABLE = 1 << 0;
}
}
#[derive(Debug, Clone, Copy, Pod)]
#[repr(C)]
pub struct PageTableEntry(u64);
impl PageTableFlagsTrait for PageTableFlags {
fn new() -> Self {
Self::empty()
}
fn set_present(self, present: bool) -> Self {
self
}
fn set_writable(mut self, writable: bool) -> Self {
self.set(Self::WRITABLE, writable);
self
}
fn set_readable(mut self, readable: bool) -> Self {
self.set(Self::READABLE, readable);
self
}
fn set_accessible_by_user(self, accessible: bool) -> Self {
self
}
fn set_executable(self, executable: bool) -> Self {
self
}
fn is_present(&self) -> bool {
true
}
fn writable(&self) -> bool {
self.contains(Self::WRITABLE)
}
fn readable(&self) -> bool {
self.contains(Self::READABLE)
}
fn executable(&self) -> bool {
true
}
fn has_accessed(&self) -> bool {
self.contains(Self::ACCESSED)
}
fn accessible_by_user(&self) -> bool {
true
}
fn union(&self, other: &Self) -> Self {
(*self).union(*other)
}
fn remove(&mut self, flags: &Self) {
self.remove(*flags)
}
fn insert(&mut self, flags: &Self) {
self.insert(*flags)
}
}
impl PageTableEntry {
const PHYS_MASK: usize = 0xFFFF_FFFF_F000;
}
impl PageTableEntryTrait for PageTableEntry {
// bit 47~12
type F = PageTableFlags;
fn new(paddr: crate::vm::Paddr, flags: PageTableFlags) -> Self {
Self(((paddr & Self::PHYS_MASK) as u64 | flags.bits) as u64)
}
fn paddr(&self) -> crate::vm::Paddr {
(self.0 & Self::PHYS_MASK as u64) as usize
}
fn flags(&self) -> PageTableFlags {
PageTableFlags::from_bits_truncate(self.0)
}
fn is_unused(&self) -> bool {
self.paddr() == 0
}
fn update(&mut self, paddr: crate::vm::Paddr, flags: Self::F) {
self.0 = (paddr & Self::PHYS_MASK) as u64 | flags.bits
}
fn clear(&mut self) {
self.0 = 0;
}
fn page_index(va: crate::vm::Vaddr, level: usize) -> usize {
debug_assert!(level >= 1 && level <= 5);
va >> (12 + 9 * (level - 1)) & (ENTRY_COUNT - 1)
}
}

View File

@ -1,50 +0,0 @@
use core::ptr::NonNull;
use crate::{config, vm::paddr_to_vaddr};
use acpi::{AcpiHandler, AcpiTables};
use limine::LimineRsdpRequest;
use log::info;
use spin::{Mutex, Once};
/// RSDP information, key is the signature, value is the virtual address of the signature
pub static ACPI_TABLES: Once<Mutex<AcpiTables<AcpiMemoryHandler>>> = Once::new();
#[derive(Debug, Clone)]
pub struct AcpiMemoryHandler {}
impl AcpiHandler for AcpiMemoryHandler {
unsafe fn map_physical_region<T>(
&self,
physical_address: usize,
size: usize,
) -> acpi::PhysicalMapping<Self, T> {
acpi::PhysicalMapping::new(
physical_address,
NonNull::new(paddr_to_vaddr(physical_address) as *mut T).unwrap(),
size,
size,
self.clone(),
)
}
fn unmap_physical_region<T>(region: &acpi::PhysicalMapping<Self, T>) {}
}
static RSDP_REQUEST: LimineRsdpRequest = LimineRsdpRequest::new(0);
pub fn init() {
let response = RSDP_REQUEST
.get_response()
.get()
.expect("Need RSDP address");
let rsdp = response.address.as_ptr().unwrap().addr() - config::PHYS_OFFSET;
let acpi_tables =
unsafe { AcpiTables::from_rsdp(AcpiMemoryHandler {}, rsdp as usize).unwrap() };
for (signature, sdt) in acpi_tables.sdts.iter() {
info!("ACPI found signature:{:?}", signature);
}
ACPI_TABLES.call_once(|| Mutex::new(acpi_tables));
info!("acpi init complete");
}

View File

@ -0,0 +1,129 @@
use core::{fmt::Debug, mem::size_of, slice::Iter};
use acpi::{sdt::Signature, AcpiTable};
use alloc::vec::Vec;
use crate::vm::paddr_to_vaddr;
use super::{
remapping::{Andd, Atsr, Drhd, Rhsa, Rmrr, Satc, Sidp},
SdtHeaderWrapper,
};
/// DMA Remapping structure. When IOMMU is enabled, the structure should be present in the ACPI table,
/// and the user can use the DRHD table in this structure to obtain the register base addresses used to configure functions such as IOMMU.
#[derive(Debug)]
pub struct Dmar {
header: DmarHeader,
/// Actual size is indicated by `length` in header
remapping_structures: Vec<Remapping>, // Followed by `n` entries with format `Remapping Structures`
}
/// A DMAR structure contains serval remapping structures. Among these structures,
/// one DRHD must exist, the others must not exist at all.
#[derive(Debug)]
pub enum Remapping {
Drhd(Drhd),
Rmrr(Rmrr),
Atsr(Atsr),
Rhsa(Rhsa),
Andd(Andd),
Satc(Satc),
Sidp(Sidp),
}
#[derive(Debug, Clone, Copy)]
#[repr(u16)]
pub enum RemappingType {
DRHD = 0,
RMRR = 1,
ATSR = 2,
RHSA = 3,
ANDD = 4,
SATC = 5,
SIDP = 6,
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
struct DmarHeader {
header: SdtHeaderWrapper,
host_address_width: u8,
flags: u8,
reserved: [u8; 10],
}
impl AcpiTable for DmarHeader {
fn header(&self) -> &acpi::sdt::SdtHeader {
&self.header.0
}
}
impl Dmar {
/// Create a instance from ACPI table.
pub fn new() -> Option<Self> {
let acpi_table_lock = super::ACPI_TABLES.get().unwrap().lock();
// Safety: The DmarHeader is the header for the DMAR structure, it fits all the field described in Intel manual.
let dmar_mapping = unsafe {
if let Some(temp) = acpi_table_lock
.get_sdt::<DmarHeader>(Signature::DMAR)
.unwrap()
{
temp
} else {
return None;
}
};
let physical_address = dmar_mapping.physical_start();
let len = dmar_mapping.mapped_length();
// Safety: The target address is the start of the remapping structures,
// and the length is valid since the value is read from the length field in SDTHeader minus the size of DMAR header.
let dmar_slice = unsafe {
core::slice::from_raw_parts_mut(
paddr_to_vaddr(physical_address + size_of::<DmarHeader>()) as *mut u8,
len - size_of::<DmarHeader>(),
)
};
let mut remapping_structures = Vec::new();
let mut index = 0;
let mut remain_length = len - size_of::<DmarHeader>();
// Safety: Indexes and offsets are strictly followed by the manual.
unsafe {
while remain_length > 0 {
// Common header: type: u16, length: u16
let length = *dmar_slice[index as usize + 2..index as usize + 4].as_ptr() as usize;
let typ = *dmar_slice[index as usize..index as usize + 2].as_ptr() as usize;
let bytes = &&dmar_slice[index as usize..index as usize + length];
let remapping = match typ {
0 => Remapping::Drhd(Drhd::from_bytes(bytes)),
1 => Remapping::Rmrr(Rmrr::from_bytes(bytes)),
2 => Remapping::Atsr(Atsr::from_bytes(bytes)),
3 => Remapping::Rhsa(Rhsa::from_bytes(bytes)),
4 => Remapping::Andd(Andd::from_bytes(bytes)),
5 => Remapping::Satc(Satc::from_bytes(bytes)),
6 => Remapping::Sidp(Sidp::from_bytes(bytes)),
_ => {
panic!("Unidentified remapping structure type");
}
};
// let temp = DeviceScope::from_bytes(
// &bytes[index as usize..index as usize + length],
// );
remapping_structures.push(remapping);
index += length;
remain_length -= length;
}
}
Some(Dmar {
header: *dmar_mapping,
remapping_structures: remapping_structures,
})
}
pub fn remapping_iter(&self) -> Iter<'_, Remapping> {
self.remapping_structures.iter()
}
}

View File

@ -0,0 +1,110 @@
pub mod dmar;
pub mod remapping;
use core::{
ops::{Deref, DerefMut},
ptr::NonNull,
};
use crate::{config, vm::paddr_to_vaddr};
use acpi::{sdt::SdtHeader, AcpiHandler, AcpiTable, AcpiTables};
use limine::LimineRsdpRequest;
use log::info;
use spin::{Mutex, Once};
/// RSDP information, key is the signature, value is the virtual address of the signature
pub static ACPI_TABLES: Once<Mutex<AcpiTables<AcpiMemoryHandler>>> = Once::new();
/// Sdt header wrapper, user can use this structure to easily derive Debug, get table information without creating a new struture.
///
/// For example, in DMAR (DMA Remapping) structure,
/// we can use the following code to get some information of DMAR, including address, length:
///
/// ```rust
/// acpi_table.get_sdt::<SdtHeaderWrapper>(Signature::DMAR).unwrap()
/// ```
///
#[derive(Clone, Copy)]
pub struct SdtHeaderWrapper(SdtHeader);
impl AcpiTable for SdtHeaderWrapper {
fn header(&self) -> &acpi::sdt::SdtHeader {
&self.0
}
}
impl Deref for SdtHeaderWrapper {
type Target = SdtHeader;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for SdtHeaderWrapper {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl core::fmt::Debug for SdtHeaderWrapper {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let length = self.0.length;
let oem_revision = self.0.oem_revision;
let creator_id = self.0.creator_id;
let creator_revision = self.0.creator_revision;
f.debug_struct("Dmar")
.field("signature", &self.0.signature)
.field("length", &length)
.field("revision", &self.0.revision)
.field("checksum", &self.0.checksum)
.field("oem_id", &self.0.oem_id())
.field("oem_table_id", &self.0.oem_table_id())
.field("oem_revision", &oem_revision)
.field("creator_id", &creator_id)
.field("creator_revision", &creator_revision)
.finish()
}
}
#[derive(Debug, Clone)]
pub struct AcpiMemoryHandler {}
impl AcpiHandler for AcpiMemoryHandler {
unsafe fn map_physical_region<T>(
&self,
physical_address: usize,
size: usize,
) -> acpi::PhysicalMapping<Self, T> {
acpi::PhysicalMapping::new(
physical_address,
NonNull::new(paddr_to_vaddr(physical_address) as *mut T).unwrap(),
size,
size,
self.clone(),
)
}
fn unmap_physical_region<T>(region: &acpi::PhysicalMapping<Self, T>) {}
}
static RSDP_REQUEST: LimineRsdpRequest = LimineRsdpRequest::new(0);
pub fn init() {
let response = RSDP_REQUEST
.get_response()
.get()
.expect("Need RSDP address");
let rsdp = response.address.as_ptr().unwrap().addr() - config::PHYS_OFFSET;
// Safety: The RSDP is the value provided by bootloader.
let acpi_tables =
unsafe { AcpiTables::from_rsdp(AcpiMemoryHandler {}, rsdp as usize).unwrap() };
for (signature, sdt) in acpi_tables.sdts.iter() {
info!("ACPI found signature:{:?}", signature);
}
ACPI_TABLES.call_once(|| Mutex::new(acpi_tables));
info!("acpi init complete");
}

View File

@ -0,0 +1,283 @@
//! Remapping structures of DMAR table.
//! This file defines these structures and provides a "Debug" implementation to see the value inside these structures.
//! Most of the introduction are copied from Intel vt-directed-io-specification.
use core::{fmt::Debug, mem::size_of};
use alloc::{string::String, vec::Vec};
/// DMA-remapping hardware unit definition (DRHD).
///
/// A DRHD structure uniquely represents a remapping hardware unit present in the platform.
/// There must be at least one instance of this structure for each
/// PCI segment in the platform.
#[derive(Debug, Clone)]
pub struct Drhd {
header: DrhdHeader,
device_scopes: Vec<DeviceScope>,
}
impl Drhd {
pub fn register_base_addr(&self) -> u64 {
self.header.register_base_addr
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct DrhdHeader {
typ: u16,
length: u16,
flags: u8,
size: u8,
segment_num: u16,
register_base_addr: u64,
}
/// Reserved Memory Region Reporting (RMRR).
///
/// BIOS allocated reserved memory ranges that may be DMA targets.
/// It may report each such reserved memory region through the RMRR structures, along
/// with the devices that requires access to the specified reserved memory region.
#[derive(Debug, Clone)]
pub struct Rmrr {
header: RmrrHeader,
device_scopes: Vec<DeviceScope>,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct RmrrHeader {
typ: u16,
length: u16,
reserved: u16,
segment_num: u16,
reserved_memory_region_base_addr: u64,
reserved_memory_region_limit_addr: u64,
}
/// Root Port ATS Capability Reporting (ATSR).
///
/// This structure is applicable only for platforms supporting Device-TLBs as reported through the
/// Extended Capability Register.
#[derive(Debug, Clone)]
pub struct Atsr {
header: AtsrHeader,
device_scopes: Vec<DeviceScope>,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct AtsrHeader {
typ: u16,
length: u16,
flags: u8,
reserved: u8,
segment_num: u16,
}
/// Remapping Hardware Status Affinity (RHSA).
///
/// It is applicable for platforms supporting non-uniform memory (NUMA), where Remapping hardware units spans across nodes.
/// This optional structure provides the association between each Remapping hardware unit (identified by its
/// espective Base Address) and the proximity domain to which that hardware unit belongs.
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct Rhsa {
typ: u16,
length: u16,
flags: u32,
register_base_addr: u64,
proximity_domain: u32,
}
/// ACPI Name-space Device Declaration (ANDD).
///
/// An ANDD structure uniquely represents an ACPI name-space
/// enumerated device capable of issuing DMA requests in the platform.
#[derive(Debug, Clone)]
pub struct Andd {
header: AnddHeader,
acpi_object_name: String,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct AnddHeader {
typ: u16,
length: u16,
reserved: [u8; 3],
acpi_device_num: u8,
}
/// SoC Integrated Address Translation Cache (SATC).
///
/// The SATC reporting structure identifies devices that have address translation cache (ATC),
/// as defined by the PCI Express Base Specification.
#[derive(Debug, Clone)]
pub struct Satc {
header: SatcHeader,
device_scopes: Vec<DeviceScope>,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct SatcHeader {
typ: u16,
length: u16,
flags: u8,
reserved: u8,
segment_num: u16,
}
/// SoC Integrated Device Property Reporting (SIDP).
///
/// The (SIDP) reporting structure identifies devices that have special
/// properties and that may put restrictions on how system software must configure remapping
/// structures that govern such devices in a platform where remapping hardware is enabled.
///
#[derive(Debug, Clone)]
pub struct Sidp {
header: SidpHeader,
device_scopes: Vec<DeviceScope>,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct SidpHeader {
typ: u16,
length: u16,
reserved: u16,
segment_num: u16,
}
/// The Device Scope Structure is made up of Device Scope Entries. Each Device Scope Entry may be
/// used to indicate a PCI endpoint device
#[derive(Debug, Clone)]
pub struct DeviceScope {
header: DeviceScopeHeader,
path: Vec<(u8, u8)>,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct DeviceScopeHeader {
typ: u8,
length: u8,
flags: u8,
reserved: u8,
enum_id: u8,
start_bus_number: u8,
}
macro_rules! impl_from_bytes {
($(($struct:tt,$header_struct:tt,$dst_name:ident)),*) => {
$(impl $struct {
/// Create instance from bytes
///
/// # Safety
///
/// User must ensure the bytes is valid.
///
pub unsafe fn from_bytes(bytes: &[u8]) -> Self {
let length = u16_from_slice(&bytes[2..4]) as usize;
debug_assert_eq!(length, bytes.len());
let mut index = core::mem::size_of::<$header_struct>();
let mut remain_length = length - core::mem::size_of::<$header_struct>();
let mut $dst_name = Vec::new();
while remain_length > 0 {
let length = *bytes[index + 1..index + 2].as_ptr() as usize;
let temp = DeviceScope::from_bytes(
&bytes[index..index + length],
);
$dst_name.push(temp);
index += length;
remain_length -= length;
}
let header = *(bytes.as_ptr() as *const $header_struct);
Self{
header,
$dst_name
}
}
})*
};
}
impl_from_bytes!(
(Drhd, DrhdHeader, device_scopes),
(Rmrr, RmrrHeader, device_scopes),
(Atsr, AtsrHeader, device_scopes),
(Satc, SatcHeader, device_scopes),
(Sidp, SidpHeader, device_scopes)
);
impl DeviceScope {
/// Create instance from bytes
///
/// # Safety
///
/// User must ensure the bytes is valid.
///
unsafe fn from_bytes(bytes: &[u8]) -> Self {
let length = bytes[1] as u32;
debug_assert_eq!(length, bytes.len() as u32);
let header = *(bytes.as_ptr() as *const DeviceScopeHeader);
let mut index = size_of::<DeviceScopeHeader>();
let mut remain_length = length - index as u32;
let mut path = Vec::new();
while remain_length > 0 {
let temp: (u8, u8) = *(bytes[index..index + 2].as_ptr() as *const (u8, u8));
path.push(temp);
index += 2;
remain_length -= 2;
}
Self { header, path }
}
}
impl Rhsa {
/// Create instance from bytes
///
/// # Safety
///
/// User must ensure the bytes is valid.
///
pub unsafe fn from_bytes(bytes: &[u8]) -> Self {
let length = u16_from_slice(&bytes[2..4]) as u32;
debug_assert_eq!(length, bytes.len() as u32);
let result = *(bytes.as_ptr() as *const Self);
result
}
}
impl Andd {
/// Create instance from bytes
///
/// # Safety
///
/// User must ensure the bytes is valid.
///
pub unsafe fn from_bytes(bytes: &[u8]) -> Self {
let length = u16_from_slice(&bytes[2..4]) as usize;
debug_assert_eq!(length, bytes.len());
let index = core::mem::size_of::<AnddHeader>();
let remain_length = length - core::mem::size_of::<AnddHeader>();
let string = String::from_utf8(bytes[index..index + length].to_vec()).unwrap();
let header = *(bytes.as_ptr() as *const AnddHeader);
Self {
header,
acpi_object_name: string,
}
}
}
fn u16_from_slice(input: &[u8]) -> u16 {
u16::from_ne_bytes(input[0..size_of::<u16>()].try_into().unwrap())
}

View File

@ -2,11 +2,11 @@ use alloc::{collections::BTreeMap, fmt, vec::Vec};
use limine::{LimineMemmapEntry, LimineMemmapRequest, LimineMemoryMapEntryType};
use log::debug;
use pod::Pod;
use spin::{Mutex, Once};
use spin::Mutex;
use x86_64::structures::paging::PhysFrame;
use crate::{
config::{ENTRY_COUNT, PAGE_SIZE},
config::ENTRY_COUNT,
vm::{
page_table::{table_of, PageTableEntryTrait, PageTableFlagsTrait},
MemoryRegions, MemoryRegionsType, Paddr,

View File

@ -1,6 +1,7 @@
mod boot;
pub(crate) mod cpu;
pub mod device;
pub mod iommu;
pub(crate) mod irq;
mod kernel;
pub(crate) mod mm;
@ -10,7 +11,7 @@ pub(crate) mod timer;
use alloc::fmt;
use core::fmt::Write;
use kernel::apic::ioapic;
use log::info;
use log::{info, warn};
pub(crate) fn before_all_init() {
enable_common_cpu_features();
@ -32,6 +33,10 @@ pub(crate) fn after_all_init() {
kernel::pic::enable();
}
}
match iommu::init() {
Ok(_) => {}
Err(err) => warn!("IOMMU initialization error:{:?}", err),
}
timer::init();
// Some driver like serial may use PIC
kernel::pic::init();

View File

@ -0,0 +1 @@
pub mod pci;

View File

@ -0,0 +1,46 @@
use core::iter;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct PciDeviceLocation {
pub bus: u8,
/// Max 31
pub device: u8,
/// Max 7
pub function: u8,
}
impl PciDeviceLocation {
pub const MIN_BUS: u8 = 0;
pub const MAX_BUS: u8 = 255;
pub const MIN_DEVICE: u8 = 0;
pub const MAX_DEVICE: u8 = 31;
pub const MIN_FUNCTION: u8 = 0;
pub const MAX_FUNCTION: u8 = 7;
/// By encoding bus, device, and function into u32, user can access a PCI device in x86 by passing in this value.
#[inline(always)]
pub fn encode_as_x86_address_value(self) -> u32 {
// 1 << 31: Configuration enable
(1 << 31)
| ((self.bus as u32) << 16)
| (((self.device as u32) & 0b11111) << 11)
| (((self.function as u32) & 0b111) << 8)
}
/// Returns an iterator that enumerates all possible PCI device locations.
pub fn all() -> impl Iterator<Item = PciDeviceLocation> {
iter::from_generator(|| {
for bus in Self::MIN_BUS..=Self::MAX_BUS {
for device in Self::MIN_DEVICE..=Self::MAX_DEVICE {
for function in Self::MIN_FUNCTION..=Self::MAX_FUNCTION {
let loc = PciDeviceLocation {
bus,
device,
function,
};
yield loc;
}
}
}
})
}
}

View File

@ -12,10 +12,13 @@
#![feature(strict_provenance)]
#![feature(const_trait_impl)]
#![feature(const_ops)]
#![feature(generators)]
#![feature(iter_from_generator)]
extern crate alloc;
pub mod arch;
pub mod bus;
pub mod config;
pub mod cpu;
mod error;

View File

@ -4,7 +4,7 @@ use core::{
ops::{BitAnd, BitOr, Not},
};
use crate::{config::PAGE_SIZE, prelude::*, Error};
use crate::{arch::iommu, config::PAGE_SIZE, prelude::*, Error};
use super::frame_allocator;
use super::{Paddr, VmIo};
@ -33,15 +33,35 @@ impl VmFrameVec {
/// For more information, see `VmAllocOptions`.
pub fn allocate(options: &VmAllocOptions) -> Result<Self> {
let page_size = options.page_size;
let frames = if options.is_contiguous {
frame_allocator::alloc_continuous(options.page_size).ok_or(Error::NoMemory)?
let mut flags = VmFrameFlags::empty();
if options.can_dma {
flags.insert(VmFrameFlags::CAN_DMA);
}
let mut frames = if options.is_contiguous {
frame_allocator::alloc_continuous(options.page_size, flags).ok_or(Error::NoMemory)?
} else {
let mut frame_list = Vec::new();
for _ in 0..page_size {
frame_list.push(frame_allocator::alloc().ok_or(Error::NoMemory)?);
frame_list.push(frame_allocator::alloc(flags).ok_or(Error::NoMemory)?);
}
frame_list
};
if options.can_dma {
for frame in frames.iter_mut() {
// Safety: The address is controlled by frame allocator.
unsafe {
if let Err(err) = iommu::map(frame.start_paddr(), frame.start_paddr()) {
match err {
// do nothing
iommu::IommuError::NoIommu => {}
iommu::IommuError::ModificationError(err) => {
panic!("iommu map error:{:?}", err)
}
}
}
}
}
}
let frame_vec = Self(frames);
if !options.uninit {
frame_vec.zero();
@ -204,6 +224,7 @@ pub struct VmAllocOptions {
page_size: usize,
is_contiguous: bool,
uninit: bool,
can_dma: bool,
}
impl VmAllocOptions {
@ -213,6 +234,7 @@ impl VmAllocOptions {
page_size: len,
is_contiguous: false,
uninit: false,
can_dma: false,
}
}
@ -243,13 +265,15 @@ impl VmAllocOptions {
/// In a TEE environment, DMAable pages are untrusted pages shared with
/// the VMM.
pub fn can_dma(&mut self, can_dma: bool) -> &mut Self {
todo!()
self.can_dma = can_dma;
self
}
}
bitflags::bitflags! {
pub(crate) struct VmFrameFlags : usize{
const NEED_DEALLOC = 1 << 63;
const CAN_DMA = 1 << 62;
}
}
@ -314,7 +338,7 @@ impl VmFrame {
/// In a TEE environment, DMAable pages are untrusted pages shared with
/// the VMM.
pub fn can_dma(&self) -> bool {
todo!()
(*self.frame_index & VmFrameFlags::CAN_DMA.bits()) != 0
}
fn need_dealloc(&self) -> bool {
@ -371,6 +395,17 @@ impl VmIo for VmFrame {
impl Drop for VmFrame {
fn drop(&mut self) {
if self.need_dealloc() && Arc::strong_count(&self.frame_index) == 1 {
if self.can_dma() {
if let Err(err) = iommu::unmap(self.start_paddr()) {
match err {
// do nothing
iommu::IommuError::NoIommu => {}
iommu::IommuError::ModificationError(err) => {
panic!("iommu map error:{:?}", err)
}
}
}
}
unsafe {
frame_allocator::dealloc(self.frame_index());
}

View File

@ -9,16 +9,16 @@ use super::{frame::VmFrameFlags, MemoryRegions, MemoryRegionsType, VmFrame};
static FRAME_ALLOCATOR: Once<Mutex<FrameAllocator>> = Once::new();
pub fn alloc() -> Option<VmFrame> {
pub(crate) fn alloc(flags: VmFrameFlags) -> Option<VmFrame> {
FRAME_ALLOCATOR
.get()
.unwrap()
.lock()
.alloc(1)
.map(|pa| unsafe { VmFrame::new(pa * PAGE_SIZE, VmFrameFlags::NEED_DEALLOC) })
.map(|pa| unsafe { VmFrame::new(pa * PAGE_SIZE, flags.union(VmFrameFlags::NEED_DEALLOC)) })
}
pub fn alloc_continuous(frame_count: usize) -> Option<Vec<VmFrame>> {
pub(crate) fn alloc_continuous(frame_count: usize, flags: VmFrameFlags) -> Option<Vec<VmFrame>> {
FRAME_ALLOCATOR
.get()
.unwrap()
@ -28,7 +28,10 @@ pub fn alloc_continuous(frame_count: usize) -> Option<Vec<VmFrame>> {
let mut vector = Vec::new();
unsafe {
for i in 0..frame_count {
let frame = VmFrame::new((start + i) * PAGE_SIZE, VmFrameFlags::NEED_DEALLOC);
let frame = VmFrame::new(
(start + i) * PAGE_SIZE,
flags.union(VmFrameFlags::NEED_DEALLOC),
);
vector.push(frame);
}
}
@ -36,8 +39,8 @@ pub fn alloc_continuous(frame_count: usize) -> Option<Vec<VmFrame>> {
})
}
pub(crate) fn alloc_zero() -> Option<VmFrame> {
let frame = alloc()?;
pub(crate) fn alloc_zero(flags: VmFrameFlags) -> Option<VmFrame> {
let frame = alloc(flags)?;
frame.zero();
Some(frame)
}

View File

@ -1,4 +1,7 @@
use super::page_table::{PageTable, PageTableConfig};
use super::{
frame::VmFrameFlags,
page_table::{PageTable, PageTableConfig},
};
use crate::{
arch::mm::{PageTableEntry, PageTableFlags},
config::{PAGE_SIZE, PHYS_OFFSET},
@ -33,7 +36,7 @@ impl MapArea {
pub fn clone(&self) -> Self {
let mut mapper = BTreeMap::new();
for (&va, old) in &self.mapper {
let new = frame_allocator::alloc().unwrap();
let new = frame_allocator::alloc(VmFrameFlags::empty()).unwrap();
unsafe {
new.as_slice().copy_from_slice(old.as_slice());
}
@ -94,7 +97,7 @@ impl MapArea {
match self.mapper.entry(va) {
Entry::Occupied(e) => e.get().start_paddr(),
Entry::Vacant(e) => e
.insert(frame_allocator::alloc_zero().unwrap())
.insert(frame_allocator::alloc_zero(VmFrameFlags::empty()).unwrap())
.start_paddr(),
}
}

View File

@ -1,4 +1,5 @@
use super::{
frame::VmFrameFlags,
frame_allocator, paddr_to_vaddr, VmAllocOptions, VmFrameVec, {Paddr, Vaddr},
};
use crate::{
@ -109,7 +110,7 @@ pub struct PageTable<T: PageTableEntryTrait> {
impl<T: PageTableEntryTrait> PageTable<T> {
pub fn new(config: PageTableConfig) -> Self {
let root_frame = frame_allocator::alloc_zero().unwrap();
let root_frame = frame_allocator::alloc_zero(VmFrameFlags::empty()).unwrap();
Self {
root_pa: root_frame.start_paddr(),
tables: vec![root_frame],