mod kprint;
mod kshim;
use alloc::{
boxed::Box,
collections::btree_map::BTreeMap,
ffi::CString,
string::{String, ToString},
};
#[cfg(target_arch = "loongarch64")]
use ax_alloc::{UsageKind, global_allocator};
use ax_errno::{AxError, AxResult, LinuxError};
use ax_kspin::SpinNoPreempt;
#[cfg(not(target_arch = "loongarch64"))]
use ax_memory_addr::{MemoryAddr, VirtAddrRange};
use ax_memory_addr::{PAGE_SIZE_4K, VirtAddr};
use ax_runtime::hal::cpu::asm::{flush_icache_all, flush_tlb};
#[cfg(not(target_arch = "loongarch64"))]
use ax_runtime::hal::paging::MappingFlags;
use kmod_loader::{KernelModuleHelper, ModuleLoader, ModuleOwner, SectionMemOps};
pub struct KmodHelper;
#[cfg(not(target_arch = "loongarch64"))]
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 KmodMemSection {
vaddr: VirtAddr,
num_pages: usize,
backend: KmodMemBackend,
}
enum KmodMemBackend {
#[cfg(not(target_arch = "loongarch64"))]
KernelAspace,
#[cfg(target_arch = "loongarch64")]
DirectMap,
}
impl SectionMemOps for KmodMemSection {
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 {
match self.backend {
#[cfg(not(target_arch = "loongarch64"))]
KmodMemBackend::KernelAspace => {
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()
}
#[cfg(target_arch = "loongarch64")]
KmodMemBackend::DirectMap => {
if perms.contains(kmod_loader::SectionPerm::EXECUTE) {
flush_icache_all();
}
true
}
}
}
}
impl Drop for KmodMemSection {
fn drop(&mut self) {
match self.backend {
#[cfg(not(target_arch = "loongarch64"))]
KmodMemBackend::KernelAspace => {
let total = PAGE_SIZE_4K * self.num_pages;
ax_mm::kernel_aspace()
.lock()
.unmap(self.vaddr, total)
.unwrap_or_else(|_| {
error!(
"kmod: failed to unmap module section at {:#x} ({} pages)",
self.vaddr.as_usize(),
self.num_pages
);
});
crate::mm::flush_tlb_range(self.vaddr, total);
}
#[cfg(target_arch = "loongarch64")]
KmodMemBackend::DirectMap => {
global_allocator().dealloc_pages(
self.vaddr.as_usize(),
self.num_pages,
UsageKind::VirtMem,
);
flush_icache_all();
}
}
}
}
#[cfg(not(target_arch = "loongarch64"))]
unsafe extern "C" {
fn _ekernel();
}
#[cfg(not(target_arch = "loongarch64"))]
fn alloc_kmod_frames(num_pages: usize) -> AxResult<VirtAddr> {
let total = PAGE_SIZE_4K * num_pages;
let kernel_end = (_ekernel as *const () as usize).align_up_4k();
let kmod_alloc_start = VirtAddr::from_usize(kernel_end);
let vaddr = {
let kspace = ax_mm::kernel_aspace();
let mut guard = kspace.lock();
let vaddr = guard
.find_free_area(
kmod_alloc_start,
total,
VirtAddrRange::new(guard.base(), guard.end()),
)
.ok_or(AxError::NoMemory)?;
guard.map_alloc(vaddr, total, MappingFlags::READ | MappingFlags::WRITE, true)?;
vaddr
};
unsafe { core::ptr::write_bytes(vaddr.as_mut_ptr(), 0, total) };
Ok(vaddr)
}
#[cfg(target_arch = "loongarch64")]
fn alloc_kmod_dmw_frames(num_pages: usize) -> AxResult<VirtAddr> {
let vaddr = VirtAddr::from_usize(
global_allocator()
.alloc_pages(num_pages, PAGE_SIZE_4K, UsageKind::VirtMem)
.map_err(|_| AxError::NoMemory)?,
);
unsafe { core::ptr::write_bytes(vaddr.as_mut_ptr(), 0, PAGE_SIZE_4K * num_pages) };
Ok(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;
#[cfg(target_arch = "loongarch64")]
let (vaddr, backend) = (
alloc_kmod_dmw_frames(num_pages).expect("kmod vmalloc: out of memory"),
KmodMemBackend::DirectMap,
);
#[cfg(not(target_arch = "loongarch64"))]
let (vaddr, backend) = (
alloc_kmod_frames(num_pages).expect("kmod vmalloc: out of memory"),
KmodMemBackend::KernelAspace,
);
Box::new(KmodMemSection {
vaddr,
num_pages,
backend,
})
}
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");
}