/* SPDX-License-Identifier: copyleft-next-0.3.1 */
/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */

#ifndef KMI_ARCH_PAGES_H
#define KMI_ARCH_PAGES_H

/**
 * @file vmem.h
 * Arch-specific virtual memory handling, generally implemented in
 * arch/whatever/kernel/vmem.c
 */

#if defined(__riscv)
# if __riscv_xlen == 64
#include "../../arch/riscv64/include/vmem.h"
# else
#include "../../arch/riscv32/include/vmem.h"
# endif
#endif

#include <kmi/types.h>
#include <kmi/attrs.h>

/**
 * Map one virtual page to physical page.
 *
 * The caller is responsible for checking that both virtual and physical page of
 * the correct order are available.
 *
 * @param branch Virtual memory to work in.
 * @param paddr Physical address of page.
 * @param vaddr Virtual address to map page to.
 * @param flags Page flags.
 * @param order Order of page.
 * @return \ref OK when succesful, possibly \ref ERR_OOMEM if we don't have
 * enough pages to create the mapping.
 */
stat_t map_vpage(struct vmem *branch, pm_t paddr, vm_t vaddr, vmflags_t flags,
                 enum mm_order order);

/**
 * Unmap one page.
 *
 * @param branch Virtual memory to work in.
 * @param vaddr Virtual address of page to unmap.
 * @return \ref OK when succesful, \ref ERR_NF if no page at \c vaddr could be
 * found.
 */
stat_t unmap_vpage(struct vmem *branch, vm_t vaddr);

/**
 * Set flags in page at virtual address \p vaddr.
 *
 * @param branch Branch in which to work.
 * @param vaddr Virtual address of page.
 * @param flags Flags to set.
 * @return \ref ERR_NF if no page could be found at \p vaddr, otherwise \ref OK.
 */
stat_t set_vpage_flags(struct vmem *branch, vm_t vaddr, vmflags_t flags);

/**
 * Clear flags in page at virtual address \p vaddr.
 *
 * @param branch Branch in which to work.
 * @param vaddr Virtual address of page.
 * @param flags Flags to clear.
 * @return \ref ERR_NF if no page could be found at \p vaddr,
 *	otherwise \ref OK.
 */
stat_t clear_vpage_flags(struct vmem *branch, vm_t vaddr, vmflags_t flags);

/**
 * Modify page mapping physical address and flags.
 *
 * Note that changing the order of a page is not supported, to do that unmap and
 * remap a page.
 *
 * @param branch Virtual memory to work in.
 * @param vaddr Virtual address of map to modify.
 * @param paddr Physical address to map to.
 * @param flags Flags to set.
 * @return \ref ERR_NF if no virtual page is found at \c vaddr, \ref OK otherwise.
 */
stat_t mod_vpage(struct vmem *branch, vm_t vaddr, pm_t paddr, vmflags_t flags);

/**
 * Get information about physical page.
 *
 * @param branch Virtual memory to work in.
 * @param vaddr Virtual address of map to get parameters of.
 * @param paddr Where to write physical address of the page.
 * @param order Where to write the order of the page.
 * @param flags Where to write the physical mapping flags of the page.
 * @return \ref ERR_NF when no page is found at \c vaddr, \ref OK otherwise.
 */
stat_t stat_vpage(struct vmem *branch, vm_t vaddr, pm_t *paddr,
                  enum mm_order *order, vmflags_t *flags);

/**
 * Flush tlb entry where associated with address \p addr.
 *
 * @param addr Virtual address region to flush.
 */
void flush_tlb(uintptr_t addr);

/** Flush full tlb of current address space. */
void flush_tlb_full();

/** Flush tlbs of all processors. */
void flush_tlb_all();

/** \todo Add flushing of only some region in memory? */

/**
 * Populate virtual memory with kernel virtual address info.
 *
 * @param b Virtual memory to work in.
 * @return \ref OK.
 */
stat_t populate_kvmem(struct vmem *b);

/**
 * Initialize arch virtual memory and return boot virtual memory.
 *
 * @param fdt Global FDT pointer.
 * @return Pointer to new boot virtual memory when succesful, \c NULL otherwise.
 */
struct vmem *init_vmem(void *fdt);

#if defined(DEBUG)
/**
 * Map and setup serial port.
 *
 * @param b Virtual memory to work in.
 * @param paddr Physical address of serial port.
 * @return Virtual address of serial port.
 */
vm_t setup_kernel_io(struct vmem *b, vm_t paddr);
#endif

/**
 * Create a direct mapping, with 1:1 physical addresses in one half of the
 * address space and the kernel address space in the other.
 * Called at boot, not allowed to allocate memory.
 *
 * @param load_addr Where in physical memory kernel was loaded to. Could in
 * theory be fetched from \ref get_load_addr(), but the fewer globals are
 * touched during initialization the better.
 *
 * @return The vmem node used to build the address space. Probably statically
 * allocated.
 */
struct vmem *init_mapping(uintptr_t load_addr);

/**
 * Create new virtual memory space.
 *
 * @return Pointer to virtual memory root when succesful, \c NULL otherwise.
 */
struct vmem *create_vmem();

/**
 * Jump into virtual memory context.
 *
 * @param b Virtual memory to jump into.
 */
void use_vmem(struct vmem *b);

/**
 * Completely destroy virtual memory space.
 * Should only be called for ->proc.vmem, as it is the only one that is sure to
 * own all nodes.
 *
 * @param b Virtual memory to destroy.
 */
void destroy_vmem(struct vmem *b);

/**
 * Destroy 'regular' virtual memory space.
 * Should be called for ->rpc.vmem, as rpc can have some references that it
 * doesn't own into ->proc.vmem, and this function is careful not to free those.
 *
 * @param b Virtual memory to destroy.
 */
void destroy_rpcmem(struct vmem *b);

/**
 * Raw clone user virtual memory.
 *
 * @param r Source virtual memory of clone.
 * @param b Destination virtual memory of clone.
 */
void clone_uvmem(struct vmem *r, struct vmem *b);

/**
 * Jump into kernelspace from a physical address space.
 * Really only called by main() during boot so the parameters are kind of weird.
 *
 * @param fdt Flattened devicetree.
 * @param load_addr Address where kernel was loaded to.
 * @param direct_mapping Direct mapping vmem.
 * @param ram_base Physical base address of ram.
 */
__noreturn void to_kernelspace(void *fdt,
                               uintptr_t load_addr,
                               struct vmem *direct_mapping,
                               pm_t ram_base);
#endif /* KMI_ARCH_PAGES_H */
