1/* @title: Memory Descriptor */
2#pragma once
3#include <mem/vma_range.h>
4#include <structures/rbit.h>
5#include <sync/rwlock.h>
6#include <types/refcount.h>
7
8enum mm_map_flags {
9 MM_MAP_FIXED = 1 << 0,
10 MM_MAP_ANON = 1 << 1,
11 /* TODO: MM_MAP_SHARED / file-backed... */
12};
13
14enum mm_fault_flags {
15 MM_FAULT_WRITE = 1 << 0, /* error_code & 0x02 */
16 MM_FAULT_USER = 1 << 1, /* error_code & 0x04 */
17 MM_FAULT_INSN = 1 << 2, /* error_code & 0x10 */
18};
19
20enum mm_fault_result {
21 MM_FAULT_OK,
22 MM_FAULT_RETRY,
23 MM_FAULT_SIGSEGV,
24 MM_FAULT_OOM
25};
26
27/* TODO: hold 6 per-cpu, switch between them with
28 * generation counters. we can look towards linux
29 * once we go and implement this thing for ASID */
30struct mm_context {
31 size_t ctx_id;
32 _Atomic size_t tlb_gen;
33 struct cpu_mask cpus;
34};
35
36struct mm {
37 struct vas *vas;
38 struct rbit
39 vma_range_tree; /* VMAs keyed by [start, end-1], augmented `max` */
40 struct rwlock lock; /* top of the lock order */
41 paddr_t pml4; /* physical root of the page tables */
42 vaddr_t mmap_cursor; /* next-fit hint: where the last gap search ended */
43 refcount_t users; /* threads sharing this address space */
44 refcount_t refcount; /* keep the struct alive past last user */
45};
46
47struct mm *mm_alloc(void);
48void mm_free(struct mm *mm);
49struct mm *mm_fork(struct mm *src); /* clone VMAs + anon_vma_fork each */
50
51void mm_exit(struct mm *mm); /* exit_mmap: unmap + tear down every VMA,
52 * but leave the struct/pml4 standing */
53
54void mm_vma_range_insert(struct mm *mm,
55 struct vma_range *vma_range); /* assert no overlap */
56void mm_vma_range_remove(struct mm *mm, struct vma_range *vma_range);
57struct vma_range *
58mm_vma_range_find(struct mm *mm,
59 vaddr_t addr); /* first vma_range, end > addr */
60struct vma_range *mm_vma_range_find_intersection(struct mm *mm, vaddr_t s,
61 vaddr_t e);
62
63/* lowest gap >= len in [low, high) */
64vaddr_t mm_vma_range_find_gap(struct mm *mm, size_t len, size_t align,
65 vaddr_t low, vaddr_t high);
66
67/* find a gap (or honor a hint), build a vma_range, insert */
68vaddr_t mm_map(struct mm *mm, vaddr_t hint, size_t len,
69 enum vma_range_protection prot, enum mm_map_flags flags);
70
71/* tear down [start, start+len): split VMAs at the boundaries, unmap their
72 * PTEs, drop rmap, free/trim the VMAs */
73enum errno mm_unmap(struct mm *mm, vaddr_t start, size_t len);
74
75/* change protection over a range: split at boundaries, vma_range_set_prot each,
76 * re-protect the live PTEs */
77enum errno mm_protect(struct mm *mm, vaddr_t start, size_t len,
78 enum vma_range_protection prot);
79
80enum errno
81mm_pgtable_init(struct mm *mm); /* vmm_make_user_pml4() -> mm->pml4 */
82
83void mm_pgtable_free(struct mm *mm); /* free user PT pages; never the
84 * shared kernel higher-half */
85
86void mm_activate(struct mm *mm); /* load CR3 = mm->pml4 on switch */
87
88enum errno mm_map_page(struct mm *mm, vaddr_t va, paddr_t pa, uint64_t pflags);
89void mm_unmap_page(struct mm *mm, vaddr_t va);
90paddr_t mm_query(struct mm *mm, vaddr_t va, uint64_t *pflags_out);
91
92enum mm_fault_result mm_fault(struct mm *mm, vaddr_t addr,
93 enum mm_fault_flags flags);
94
95enum mm_fault_result mm_do_anon_fault(struct vma_range *vma_range, vaddr_t addr,
96 enum mm_fault_flags);
97enum mm_fault_result mm_do_cow_fault(struct vma_range *vma_range, vaddr_t addr,
98 enum mm_fault_flags);
99
100static inline bool mm_get(struct mm *mm) {
101 return refcount_inc_not_zero(rc: &mm->refcount);
102}
103
104static inline void mm_put(struct mm *mm) {
105 if (refcount_dec_and_test(rc: &mm->refcount))
106 mm_free(mm);
107}
108
109/* a new thread joins the address space */
110static inline bool mm_users_inc(struct mm *mm) {
111 return refcount_inc_not_zero(rc: &mm->users);
112}
113
114/* last user -> mm_exit(mm); then mm_put() */
115static inline void mm_users_put(struct mm *mm) {
116 if (refcount_dec_and_test(rc: &mm->users)) {
117 mm_exit(mm);
118 mm_put(mm); /* release the mm_refcount that mm_users held */
119 }
120}
121