use crate::cap::cnode; use crate::cap::object::{ObjectTag, PortRange}; use crate::cap::ops; use crate::cap::pool::POOL; use crate::cap::table::Rights; use crate::error::KernelError; use crate::pci::config::{self, EcamAddress}; use crate::pci::{BarInfo, DEVICE_TABLE}; use crate::proc::PROCESSES; use crate::proc::address_space::map_fb_page_inner; use crate::proc::context::CpuContext; use crate::syscall::{SyscallResult, try_syscall, validate_user_vaddr}; use lancer_core::header::KernelObjectHeader; use lancer_core::object_layout::{IrqHandlerObject, KernelObject, PciDeviceObject}; use x86_64::PhysAddr; use x86_64::structures::paging::{PhysFrame, Size4KiB}; pub fn sys_pci_device_count(ctx: &mut CpuContext) { let table = DEVICE_TABLE.lock(); ctx.rax = SyscallResult::success(table.len() as u64).raw(); } pub fn sys_pci_device_info(ctx: &mut CpuContext) { let cap_addr = ctx.rdi; let buf_ptr = ctx.rsi; let buf_len = try_syscall!(ctx, super::u32_from_reg(ctx.rdx)); let pid = crate::arch::syscall::current_pid(); let ptable = PROCESSES.lock(); let cap = { let pool = POOL.lock_after(&ptable); try_syscall!( ctx, cnode::resolve_caller_validate( pid, cap_addr, ObjectTag::PciDevice, Rights::READ, &ptable, &pool ) ) }; drop(ptable); let pool = POOL.lock(); let idx = match pool.read_as::(cap.phys(), cap.generation()) { Ok(d) => d.device_table_idx, Err(e) => { ctx.rax = SyscallResult::error(e).raw(); return; } }; drop(pool); let dev_table = DEVICE_TABLE.lock(); let dev = match dev_table.get(idx as usize) { Some(d) => *d, None => { ctx.rax = SyscallResult::error(KernelError::InvalidObject).raw(); return; } }; drop(dev_table); let wire = dev.to_wire(); let info_bytes = zerocopy::IntoBytes::as_bytes(&wire); let copy_len = (buf_len as usize).min(info_bytes.len()); let proof = match crate::sync::InterruptsDisabledToken::new_checked() { Some(tok) => tok, None => { ctx.rax = SyscallResult::error(KernelError::BadState).raw(); return; } }; ctx.rax = match super::copy_to_user(buf_ptr, &info_bytes[..copy_len], copy_len, &proof) { Ok(()) => SyscallResult::success(copy_len as u64).raw(), Err(e) => SyscallResult::error(e).raw(), }; } pub fn sys_pci_config_read32(ctx: &mut CpuContext) { let cap_addr = ctx.rdi; let offset = try_syscall!(ctx, super::u16_from_reg(ctx.rsi)); if offset > 0xFFC || offset & 0x3 != 0 { ctx.rax = SyscallResult::error(KernelError::InvalidParameter).raw(); return; } let pid = crate::arch::syscall::current_pid(); let ptable = PROCESSES.lock(); let cap = { let pool = POOL.lock_after(&ptable); try_syscall!( ctx, cnode::resolve_caller_validate( pid, cap_addr, ObjectTag::PciDevice, Rights::READ, &ptable, &pool ) ) }; drop(ptable); let pool = POOL.lock(); let idx = match pool.read_as::(cap.phys(), cap.generation()) { Ok(d) => d.device_table_idx, Err(e) => { ctx.rax = SyscallResult::error(e).raw(); return; } }; drop(pool); let dev_table = DEVICE_TABLE.lock(); let dev = match dev_table.get(idx as usize) { Some(d) => *d, None => { ctx.rax = SyscallResult::error(KernelError::InvalidObject).raw(); return; } }; drop(dev_table); let mcfg_entries = crate::acpi::mcfg::cached_mcfg_entries(); let ecam_base = match mcfg_entries .iter() .find(|e| dev.bus >= e.start_bus && dev.bus <= e.end_bus) { Some(e) => e.base_address, None => { ctx.rax = SyscallResult::error(KernelError::InvalidObject).raw(); return; } }; let addr = match EcamAddress::new(ecam_base, dev.bus, dev.device, dev.function) { Some(a) => a, None => { ctx.rax = SyscallResult::error(KernelError::InvalidParameter).raw(); return; } }; let value = config::read_config_u32(addr, offset); ctx.rax = SyscallResult::success(value as u64).raw(); } pub(crate) fn is_config_write_safe(offset: u16, dev: &crate::pci::PciDeviceInfo) -> bool { if matches!(offset, 0x04 | 0x10..=0x27 | 0x30) { return false; } let write_end = offset + 4; dev.blocked_config_ranges .iter() .flatten() .all(|r| offset >= r.end || write_end <= r.start) } pub fn sys_pci_config_write32(ctx: &mut CpuContext) { let cap_addr = ctx.rdi; let offset = try_syscall!(ctx, super::u16_from_reg(ctx.rsi)); let value = try_syscall!(ctx, super::u32_from_reg(ctx.rdx)); if offset > 0xFFC || offset & 0x3 != 0 { ctx.rax = SyscallResult::error(KernelError::InvalidParameter).raw(); return; } let pid = crate::arch::syscall::current_pid(); let ptable = PROCESSES.lock(); let cap = { let pool = POOL.lock_after(&ptable); try_syscall!( ctx, cnode::resolve_caller_validate( pid, cap_addr, ObjectTag::PciDevice, Rights::WRITE, &ptable, &pool ) ) }; drop(ptable); let pool = POOL.lock(); let idx = match pool.read_as::(cap.phys(), cap.generation()) { Ok(d) => d.device_table_idx, Err(e) => { ctx.rax = SyscallResult::error(e).raw(); return; } }; drop(pool); let dev_table = DEVICE_TABLE.lock(); let dev = match dev_table.get(idx as usize) { Some(d) => *d, None => { ctx.rax = SyscallResult::error(KernelError::InvalidObject).raw(); return; } }; drop(dev_table); if !is_config_write_safe(offset, &dev) { ctx.rax = SyscallResult::error(KernelError::PermissionDenied).raw(); return; } let mcfg_entries = crate::acpi::mcfg::cached_mcfg_entries(); let ecam_base = match mcfg_entries .iter() .find(|e| dev.bus >= e.start_bus && dev.bus <= e.end_bus) { Some(e) => e.base_address, None => { ctx.rax = SyscallResult::error(KernelError::InvalidObject).raw(); return; } }; let addr = match EcamAddress::new(ecam_base, dev.bus, dev.device, dev.function) { Some(a) => a, None => { ctx.rax = SyscallResult::error(KernelError::InvalidParameter).raw(); return; } }; config::write_config_u32(addr, offset, value); ctx.rax = SyscallResult::ok().raw(); } pub fn sys_pci_bar_map(ctx: &mut CpuContext) { let cap_addr = ctx.rdi; let bar_idx = try_syscall!(ctx, super::u8_from_reg(ctx.rsi)); let dest_vaddr = ctx.rdx; let pid = crate::arch::syscall::current_pid(); if bar_idx >= 6 { ctx.rax = SyscallResult::error(KernelError::InvalidParameter).raw(); return; } let _ = try_syscall!(ctx, validate_user_vaddr(dest_vaddr)); match dest_vaddr & 0xFFF { 0 => {} _ => { ctx.rax = SyscallResult::error(KernelError::InvalidAddress).raw(); return; } } let (cap, pml4_phys) = { let ptable = PROCESSES.lock(); let pool = POOL.lock_after(&ptable); match cnode::resolve_caller_validate( pid, cap_addr, ObjectTag::PciDevice, Rights::WRITE, &ptable, &pool, ) { Ok(c) => { let pml4 = ptable .exec(pid) .map(|e| e.pml4_phys) .ok_or(KernelError::InvalidObject); match pml4 { Ok(pml4_phys) => (c, pml4_phys), Err(e) => { ctx.rax = SyscallResult::error(e).raw(); return; } } } Err(e) => { ctx.rax = SyscallResult::error(e).raw(); return; } } }; let device_table_idx = { let pool = POOL.lock(); match pool.read_as::(cap.phys(), cap.generation()) { Ok(d) => d.device_table_idx, Err(e) => { ctx.rax = SyscallResult::error(e).raw(); return; } } }; { let bar_mappings = crate::pci::BAR_MAPPINGS.lock(); let already_mapped = bar_mappings .iter() .any(|e| e.matches(pid, device_table_idx, bar_idx)); if already_mapped { ctx.rax = SyscallResult::error(KernelError::BadState).raw(); return; } } let dev = { let dev_table = DEVICE_TABLE.lock(); match dev_table.get(device_table_idx as usize) { Some(d) => *d, None => { ctx.rax = SyscallResult::error(KernelError::InvalidObject).raw(); return; } } }; let (phys_base, size) = match dev.bars[bar_idx as usize] { BarInfo::Memory { phys_base, size, .. } => (phys_base, size), _ => { ctx.rax = SyscallResult::error(KernelError::InvalidParameter).raw(); return; } }; const MAX_BAR_PAGES: usize = 16384; let page_count = size.div_ceil(4096) as usize; match page_count > MAX_BAR_PAGES { true => { ctx.rax = SyscallResult::error(KernelError::InvalidParameter).raw(); return; } false => {} } match dest_vaddr.checked_add((page_count as u64) * 4096) { Some(end) if end <= super::USER_ADDR_LIMIT => {} _ => { ctx.rax = SyscallResult::error(KernelError::InvalidAddress).raw(); return; } } let mut allocator = crate::mem::phys::BitmapFrameAllocator; let result = (0..page_count).try_fold(0usize, |count, i| { let phys = PhysAddr::new(phys_base + (i as u64) * 4096); let virt = x86_64::VirtAddr::new(dest_vaddr + (i as u64) * 4096); let frame = PhysFrame::::containing_address(phys); match map_fb_page_inner(pml4_phys, virt, frame, &mut allocator) { Ok(()) => Ok(count + 1), Err(_) => Err((KernelError::ResourceExhausted, count)), } }); ctx.rax = match result { Ok(n) => { let entry = crate::pci::BarMappingEntry { pid, device_idx: device_table_idx, bar_idx, base_vaddr: dest_vaddr, }; let _ = crate::pci::BAR_MAPPINGS.lock().push(entry); SyscallResult::success(n as u64).raw() } Err((e, mapped)) => { (0..mapped).for_each(|i| { let virt = x86_64::VirtAddr::new(dest_vaddr + (i as u64) * 4096); let _ = crate::proc::address_space::unmap_user_page(pml4_phys, virt); }); SyscallResult::error(e).raw() } }; } pub fn sys_pci_bar_unmap(ctx: &mut CpuContext) { let cap_addr = ctx.rdi; let bar_idx = try_syscall!(ctx, super::u8_from_reg(ctx.rsi)); let pid = crate::arch::syscall::current_pid(); if bar_idx >= 6 { ctx.rax = SyscallResult::error(KernelError::InvalidParameter).raw(); return; } let ptable = PROCESSES.lock(); let (cap, pml4_phys) = { let pool = POOL.lock_after(&ptable); match cnode::resolve_caller_validate( pid, cap_addr, ObjectTag::PciDevice, Rights::WRITE, &ptable, &pool, ) { Ok(c) => { let pml4 = ptable .exec(pid) .map(|e| e.pml4_phys) .ok_or(KernelError::InvalidObject); match pml4 { Ok(pml4_phys) => (c, pml4_phys), Err(e) => { ctx.rax = SyscallResult::error(e).raw(); return; } } } Err(e) => { ctx.rax = SyscallResult::error(e).raw(); return; } } }; let device_table_idx = { let pool = POOL.lock_after(&ptable); match pool.read_as::(cap.phys(), cap.generation()) { Ok(d) => d.device_table_idx, Err(e) => { ctx.rax = SyscallResult::error(e).raw(); return; } } }; drop(ptable); let mut bar_mappings = crate::pci::BAR_MAPPINGS.lock(); let mapping_pos = bar_mappings .iter() .enumerate() .find(|(_, e)| e.matches(pid, device_table_idx, bar_idx)) .map(|(i, _)| i); let recorded_vaddr = match mapping_pos { Some(pos) => { let vaddr = bar_mappings.get(pos).map(|e| e.base_vaddr).unwrap_or(0); bar_mappings.swap_remove(pos); vaddr } None => { ctx.rax = SyscallResult::error(KernelError::NotFound).raw(); return; } }; drop(bar_mappings); let dev_table = DEVICE_TABLE.lock(); let dev = match dev_table.get(device_table_idx as usize) { Some(d) => *d, None => { ctx.rax = SyscallResult::error(KernelError::InvalidObject).raw(); return; } }; drop(dev_table); let size = match dev.bars[bar_idx as usize] { BarInfo::Memory { size, .. } => size, _ => { ctx.rax = SyscallResult::error(KernelError::InvalidParameter).raw(); return; } }; let page_count = size.div_ceil(4096) as usize; (0..page_count).for_each(|i| { let virt = x86_64::VirtAddr::new(recorded_vaddr + (i as u64) * 4096); let _ = crate::proc::address_space::unmap_user_page(pml4_phys, virt); }); ctx.rax = SyscallResult::ok().raw(); } pub fn sys_pci_msix_configure(ctx: &mut CpuContext) { let pid = crate::arch::syscall::current_pid(); if pid != crate::types::Pid::new(0) { ctx.rax = SyscallResult::error(KernelError::PermissionDenied).raw(); return; } let pci_cap_addr = ctx.rdi; let entry_idx = try_syscall!(ctx, super::u16_from_reg(ctx.rsi)); let vector = match crate::arch::x86_64::idt::IrqVector::try_new(try_syscall!( ctx, super::u8_from_reg(ctx.rdx) )) { Some(v) => v, None => { ctx.rax = SyscallResult::error(KernelError::InvalidParameter).raw(); return; } }; let irq_dest_addr = ctx.r10; let device_table_idx = { let ptable = PROCESSES.lock(); let pool = POOL.lock_after(&ptable); let pci_cap = try_syscall!( ctx, cnode::resolve_caller_validate( pid, pci_cap_addr, ObjectTag::PciDevice, Rights::WRITE, &ptable, &pool ) ); match pool.read_as::(pci_cap.phys(), pci_cap.generation()) { Ok(d) => d.device_table_idx, Err(e) => { ctx.rax = SyscallResult::error(e).raw(); return; } } }; { let dev_table = DEVICE_TABLE.lock(); match dev_table .get(device_table_idx as usize) .and_then(|d| d.msix_cap) { Some(_) => {} None => { ctx.rax = SyscallResult::error(KernelError::InvalidParameter).raw(); return; } } } let hhdm = crate::mem::addr::hhdm_offset(); let boot_pml4 = crate::proc::address_space::boot_pml4_phys(); let mut page_table = unsafe { crate::arch::paging::init_from_phys(boot_pml4, hhdm) }; let mut frame_alloc = crate::mem::phys::BitmapFrameAllocator; let map_result = crate::pci::msix::ensure_table_mapped( device_table_idx, &mut page_table, &mut frame_alloc, hhdm, ); if map_result.is_ok() { crate::proc::address_space::sync_kernel_entries(); } let configure_result = map_result .and_then(|()| crate::pci::msix::configure_entry(device_table_idx, entry_idx, vector, 0)) .and_then(|()| crate::pci::msix::enable_msix(device_table_idx)) .and_then(|()| crate::pci::msix::unmask_entry(device_table_idx, entry_idx)); match configure_result { Ok(()) => {} Err(e) => { ctx.rax = SyscallResult::error(e).raw(); return; } } let _port_range = match PortRange::new(0, 1) { Some(pr) => pr, None => { ctx.rax = SyscallResult::error(KernelError::InvalidParameter).raw(); return; } }; let obj_phys = match crate::cap::kernel_objects::alloc_slot() { Some(p) => p, None => { ctx.rax = SyscallResult::error(KernelError::PoolExhausted).raw(); return; } }; let header = KernelObjectHeader::new(ObjectTag::IrqHandler, 0, 64); let mut obj = IrqHandlerObject::init_default(header); obj.vector = vector.raw(); obj.source_kind = 1; obj.source_data = (device_table_idx as u32) | ((entry_idx as u32) << 8); obj.port_base = 0; obj.port_count = 1; crate::cap::kernel_objects::write_at(obj_phys, obj); let ptable = PROCESSES.lock(); let (cnode_id, cnode_gen, depth, gv, gb) = try_syscall!(ctx, cnode::cnode_coords(pid, &ptable)); let mut pool = POOL.lock_after(&ptable); ctx.rax = match ops::insert_phys_cap_via_cnode( &mut pool, cnode_id, cnode_gen, irq_dest_addr, depth, gv, gb, ObjectTag::IrqHandler, obj_phys, Rights::ALL, ) { Ok(phys) => SyscallResult::success(phys.raw()).raw(), Err(e) => { crate::cap::kernel_objects::free_slot(obj_phys); SyscallResult::error(e).raw() } }; } pub fn sys_pci_create_cap(ctx: &mut CpuContext) { let pid = crate::arch::syscall::current_pid(); if pid != crate::types::Pid::new(0) { ctx.rax = SyscallResult::error(KernelError::PermissionDenied).raw(); return; } let device_table_idx = try_syscall!(ctx, super::u8_from_reg(ctx.rdi)); let dest_addr = ctx.rsi; { let dev_table = DEVICE_TABLE.lock(); match dev_table.get(device_table_idx as usize) { Some(_) => {} None => { ctx.rax = SyscallResult::error(KernelError::InvalidObject).raw(); return; } } } let obj_phys = match crate::cap::kernel_objects::alloc_slot() { Some(p) => p, None => { ctx.rax = SyscallResult::error(KernelError::PoolExhausted).raw(); return; } }; let header = KernelObjectHeader::new(ObjectTag::PciDevice, 0, 64); let mut obj = PciDeviceObject::init_default(header); obj.device_table_idx = device_table_idx; crate::cap::kernel_objects::write_at(obj_phys, obj); let ptable = PROCESSES.lock(); let (cnode_id, cnode_gen, depth, gv, gb) = try_syscall!(ctx, cnode::cnode_coords(pid, &ptable)); let mut pool = POOL.lock_after(&ptable); ctx.rax = match ops::insert_phys_cap_via_cnode( &mut pool, cnode_id, cnode_gen, dest_addr, depth, gv, gb, ObjectTag::PciDevice, obj_phys, Rights::ALL, ) { Ok(_phys) => SyscallResult::ok().raw(), Err(e) => { crate::cap::kernel_objects::free_slot(obj_phys); SyscallResult::error(e).raw() } }; } pub fn sys_pci_enable_bus_master(ctx: &mut CpuContext) { let pid = crate::arch::syscall::current_pid(); if pid != crate::types::Pid::new(0) { ctx.rax = SyscallResult::error(KernelError::PermissionDenied).raw(); return; } let pci_cap_addr = ctx.rdi; let device_table_idx = { let ptable = PROCESSES.lock(); let pool = POOL.lock_after(&ptable); let cap = try_syscall!( ctx, cnode::resolve_caller_validate( pid, pci_cap_addr, ObjectTag::PciDevice, Rights::WRITE, &ptable, &pool ) ); match pool.read_as::(cap.phys(), cap.generation()) { Ok(d) => d.device_table_idx, Err(e) => { ctx.rax = SyscallResult::error(e).raw(); return; } } }; ctx.rax = match crate::pci::enable_bus_master(device_table_idx) { Ok(()) => SyscallResult::ok().raw(), Err(e) => SyscallResult::error(e).raw(), }; }