mod kprint;
use alloc::{
boxed::Box,
collections::btree_map::BTreeMap,
ffi::CString,
string::{String, ToString},
};
use ax_alloc::{UsageKind, global_allocator};
use ax_errno::{AxError, AxResult, LinuxError};
use ax_kspin::SpinNoPreempt;
use ax_memory_addr::{PAGE_SIZE_4K, PhysAddr, VirtAddr};
use ax_runtime::hal::{
cpu::asm::{flush_icache_all, flush_tlb},
mem::{phys_to_virt, virt_to_phys},
paging::{MappingFlags, PageSize},
};
use kmod_loader::{KernelModuleHelper, ModuleLoader, ModuleOwner, SectionMemOps};
pub struct KmodHelper;
fn section_perms_to_mapping_flags(perms: kmod_loader::SectionPerm) -> MappingFlags {
let mut flags = MappingFlags::empty();
if perms.contains(kmod_loader::SectionPerm::READ) {
flags |= MappingFlags::READ;
}
if perms.contains(kmod_loader::SectionPerm::WRITE) {
flags |= MappingFlags::WRITE;
}
if perms.contains(kmod_loader::SectionPerm::EXECUTE) {
flags |= MappingFlags::EXECUTE;
}
flags
}
struct KmodMem {
paddr: PhysAddr,
vaddr: VirtAddr,
num_pages: usize,
}
impl SectionMemOps for KmodMem {
fn as_mut_ptr(&mut self) -> *mut u8 {
self.vaddr.as_mut_ptr()
}
fn as_ptr(&self) -> *const u8 {
self.vaddr.as_ptr()
}
fn change_perms(&mut self, perms: kmod_loader::SectionPerm) -> bool {
let mapping_flags = section_perms_to_mapping_flags(perms);
let kspace = ax_mm::kernel_aspace();
let mut guard = kspace.lock();
guard
.protect(self.vaddr, PAGE_SIZE_4K * self.num_pages, mapping_flags)
.is_ok()
}
}
impl Drop for KmodMem {
fn drop(&mut self) {
let total = PAGE_SIZE_4K * self.num_pages;
let restored = {
let kspace = ax_mm::kernel_aspace();
let mut guard = kspace.lock();
guard
.protect(self.vaddr, total, MappingFlags::READ | MappingFlags::WRITE)
.is_ok()
};
if !restored {
error!(
"kmod: failed to restore RW mapping for module section at {:#x} ({} pages); \
leaking frames",
self.vaddr.as_usize(),
self.num_pages
);
return;
}
crate::mm::flush_tlb_range(self.vaddr, total);
let vaddr = phys_to_virt(self.paddr);
global_allocator().dealloc_pages(vaddr.as_usize(), self.num_pages, UsageKind::Global);
}
}
fn alloc_kmod_frames(num_pages: usize) -> AxResult<(PhysAddr, VirtAddr)> {
let page_size = PageSize::Size4K as usize;
let total = page_size * num_pages;
let vaddr_usize = global_allocator()
.alloc_pages(num_pages, page_size, UsageKind::Global)
.map_err(|_| AxError::NoMemory)?;
let vaddr = VirtAddr::from(vaddr_usize);
unsafe { core::ptr::write_bytes(vaddr.as_mut_ptr(), 0, total) };
Ok((virt_to_phys(vaddr), vaddr))
}
fn linux_code_to_ax_error(code: i32) -> AxError {
LinuxError::try_from(code)
.map(AxError::from)
.unwrap_or_else(|_| AxError::from(LinuxError::EINVAL))
}
impl KernelModuleHelper for KmodHelper {
fn vmalloc(size: usize) -> Box<dyn SectionMemOps> {
assert!(
size.is_multiple_of(PAGE_SIZE_4K),
"kmod vmalloc size must be page-aligned"
);
let num_pages = size / PAGE_SIZE_4K;
let (paddr, vaddr) = alloc_kmod_frames(num_pages).expect("kmod vmalloc: out of memory");
Box::new(KmodMem {
paddr,
vaddr,
num_pages,
})
}
fn resolve_symbol(name: &str) -> Option<usize> {
if name.is_empty() {
return None;
}
match crate::pseudofs::proc::KALLSYMS
.get()
.and_then(|t| t.lookup_name(name))
{
Some(addr) => Some(addr as usize),
None => {
error!("kmod: failed to resolve symbol `{}`", name);
None
}
}
}
fn flsuh_cache(_addr: usize, _size: usize) {
flush_tlb(None);
flush_icache_all();
}
}
type Module = ModuleOwner<KmodHelper>;
static MODULES: SpinNoPreempt<BTreeMap<String, Module>> = SpinNoPreempt::new(BTreeMap::new());
pub fn init_module(elf: &[u8], params: Option<&str>) -> AxResult<()> {
let loader =
ModuleLoader::<KmodHelper>::new(elf).map_err(|err| linux_code_to_ax_error(err.code()))?;
let params = match params {
Some(p) => CString::new(p).map_err(|_| AxError::InvalidInput)?,
None => CString::new("").unwrap(),
};
let mut owner = loader
.load_module(params)
.map_err(|err| linux_code_to_ax_error(err.code()))?;
let name = owner.name().to_string();
if MODULES.lock().contains_key(&name) {
return Err(AxError::AlreadyExists);
}
let ret = owner
.call_init()
.map_err(|err| linux_code_to_ax_error(err.code()))?;
if ret != 0 {
warn!("module `{name}` init returned {ret}");
return Err(AxError::InvalidInput);
}
info!("module `{name}` loaded");
let mut modules = MODULES.lock();
if modules.contains_key(&name) {
drop(modules);
owner.call_exit();
return Err(AxError::AlreadyExists);
}
modules.insert(name, owner);
Ok(())
}
pub fn delete_module(name: &str) -> AxResult<()> {
let mut modules = MODULES.lock();
let mut owner = modules.remove(name).ok_or(AxError::NotFound)?;
owner.call_exit();
warn!("module `{name}` exited");
Ok(())
}
struct StdOut;
impl lwprintf_rs::CustomOutPut for StdOut {
fn putch(ch: i32) -> i32 {
ax_print!("{}", ch as u8 as char);
ch
}
}
pub fn init_kmod() {
lwprintf_rs::lwprintf_init::<StdOut>();
ax_println!("kmod subsystem initialized");
}