//! 内核内存分配器实现
//!
//! `NOTE!:`
//! Don't use bitflags!
//!
//! 不要使用 bitflags!
//!
//! bitflags 造成严重的性能下降

use core::ops::BitAnd;

use self::{
    error::Result,
    global::{free_pages_to_freelist, get_page_from_freelist},
};
use crate::{println, space::mm::pgtabledef::PAGE_SHIFT};

mod global;
mod page;
mod slab;

pub(crate) use global::kalloc_init;
use global::{global_nr_free_pages, global_nr_reserved_pages, global_nr_total_pages};
pub use page::Page;
pub use slab::vaddr_name;
pub(crate) use slab::{kfree, kmalloc};

use super::addr::Vaddr;

pub mod error;

/// 页申请 flags
#[derive(Clone, Copy)]
#[repr(usize)]
pub enum GfpFlags {
    /// 默认标记
    Normal = 0b0000000,
    /// 申请页清零
    Clean  = 0b0000001,
}

impl BitAnd for GfpFlags {
    type Output = usize;

    #[inline(always)]
    fn bitand(self, rhs: Self) -> Self::Output {
        self as usize & rhs as usize
    }
}

impl GfpFlags {
    #[inline(always)]
    pub(crate) fn clear(self) -> bool {
        self & GfpFlags::Clean != 0
    }
}

/// 申请内存页
///
/// # Errors
/// 内存不足将失败
///
/// # NOTES
/// 这里不返回申请到的 page 引用, 而是返回 page 对应的线性地址,
/// 因为 page 本身是静态存在的, 申请者没有必要去特意获取 page,
/// 如果真有必要, 可以通过 `virt_to_page{_mut}` 来获取对应的页引用
pub fn alloc_pages(flags: GfpFlags, order: usize) -> Result<Vaddr> {
    get_page_from_freelist(flags, order)
}

/// 释放内存页
pub fn free_pages(vaddr: Vaddr, order: usize) {
    free_pages_to_freelist(vaddr, order);
}

/// 申请一页内存页
///
/// # Errors
/// 内存不足将失败
#[inline]
pub fn alloc_page(flags: GfpFlags) -> Result<Vaddr> {
    alloc_pages(flags, 0)
}

/// 释放一页内存页
#[inline]
pub fn free_page(vaddr: Vaddr) {
    free_pages(vaddr, 0);
}

/// 总的内存页数量
pub fn nr_total_pages() -> usize {
    global_nr_total_pages()
}

/// 总的空闲页内存数量
pub fn nr_free_pages() -> usize {
    global_nr_free_pages()
}

/// 总的预留内存页数量
pub fn nr_reserved_pages() -> usize {
    global_nr_reserved_pages()
}

/// 打印内存信息
pub fn kalloc_dump() {
    println!(
        "Mem total: {}M, reserved: {}M, free {}M",
        (nr_total_pages() << (PAGE_SHIFT - 10)) / 1024,
        (nr_reserved_pages() << (PAGE_SHIFT - 10)) / 1024,
        (nr_free_pages() << (PAGE_SHIFT - 10)) / 1024
    );
}

#[cfg(not(test))]
mod alloc {
    use core::alloc::GlobalAlloc;

    use super::{
        super::addr::Vaddr,
        GfpFlags,
        slab::{kfree, kmalloc},
    };

    struct GAlloc;

    #[global_allocator]
    static GLOBAL_ALLOC: GAlloc = GAlloc;

    unsafe impl GlobalAlloc for GAlloc {
        unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 {
            let mut size = layout.size();
            let align = layout.align();
            size = (size + align - 1) & !(align - 1);
            let vaddr = kmalloc(size, GfpFlags::Normal).unwrap().to_value();
            vaddr as *mut u8
        }

        unsafe fn dealloc(&self, ptr: *mut u8, _: core::alloc::Layout) {
            let vaddr = Vaddr::from(ptr as usize);
            kfree(vaddr);
        }
    }
}
