1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
|
#include <kernel/arch/generic.h>
#include <kernel/mem/alloc.h>
#include <shared/mem.h>
#include <stdint.h>
/* <heat> nitpick: I highly recommend you dont use bitfields for paging
* structures
* <heat> you can't change them atomically and the way they're layed out
* in memory is implementation defined iirc
*/
struct pagetable_entry {
uint32_t present : 1;
uint32_t writeable : 1;
uint32_t user : 1;
uint32_t writethru : 1;
uint32_t uncached : 1;
uint32_t dirty : 1;
uint32_t always0 : 1; // memory type thing?
uint32_t global : 1;
uint32_t _unused : 3;
uint32_t address : 21;
};
struct pagedir_entry {
uint32_t present : 1;
uint32_t _writeable: 1; // don't use! not checked by multiple functions here
uint32_t _user : 1; // ^
uint32_t writethru : 1;
uint32_t uncached : 1;
uint32_t accessed : 1;
uint32_t always0 : 1;
uint32_t large : 1; // 4 MiB instead of 4 KiB
uint32_t _unused : 3;
uint32_t address : 21;
} __attribute__((packed));
struct pagedir {
struct pagedir_entry e[1024];
} __attribute__((packed));
struct pagedir *pagedir_new(void) {
struct pagedir *dir = page_alloc(1);
for (int i = 0; i < 1024; i++)
dir->e[i].present = 0;
return dir;
}
void pagedir_free(struct pagedir *dir) {
// assumes all user pages are unique and can be freed
struct pagetable_entry *pt;
void *page;
for (int i = 0; i < 1024; i++) {
if (!dir->e[i].present) continue;
pt = (void*)(dir->e[i].address << 11);
for (int j = 0; j < 1024; j++) {
if (!pt[j].present) continue;
if (!pt[j].user) continue;
page = (void*)(pt[j].address << 11);
page_free(page, 1);
}
page_free(pt, 1);
}
page_free(dir, 1);
}
static struct pagetable_entry*
get_entry(struct pagedir *dir, const void __user *virt) {
uint32_t pd_idx = ((uintptr_t)virt) >> 22;
uint32_t pt_idx = ((uintptr_t)virt) >> 12 & 0x03FF;
struct pagetable_entry *pagetable;
if (!dir->e[pd_idx].present) return NULL;
pagetable = (void*)(dir->e[pd_idx].address << 11);
return &pagetable[pt_idx];
}
void *pagedir_unmap(struct pagedir *dir, void __user *virt) {
void *phys = pagedir_virt2phys(dir, virt, false, false);
struct pagetable_entry *page = get_entry(dir, virt);
page->present = false;
return phys;
}
void pagedir_map(struct pagedir *dir, void __user *virt, void *phys,
bool user, bool writeable)
{
uintptr_t virt_cast = (uintptr_t) virt;
uint32_t pd_idx = virt_cast >> 22;
uint32_t pt_idx = virt_cast >> 12 & 0x03FF;
struct pagetable_entry *pagetable;
if (dir->e[pd_idx].present) {
pagetable = (void*) (dir->e[pd_idx].address << 11);
} else {
pagetable = page_alloc(1);
for (int i = 0; i < 1024; i++)
pagetable[i].present = 0;
dir->e[pd_idx] = (struct pagedir_entry) {
.present = 1,
._writeable= 1,
._user = 1,
.writethru = 1,
.uncached = 0,
.accessed = 0,
.always0 = 0,
.large = 0,
._unused = 0,
.address = (uintptr_t) pagetable >> 11
};
}
pagetable[pt_idx] = (struct pagetable_entry) {
.present = 1,
.writeable = writeable,
.user = user,
.writethru = 1,
.uncached = 0,
.dirty = 0,
.always0 = 0,
.global = 0,
._unused = 0,
.address = (uintptr_t) phys >> 11
};
}
void pagedir_switch(struct pagedir *dir) {
asm volatile("mov %0, %%cr3;" : : "r" (dir) : "memory");
}
// creates a new pagedir with exact copies of the user pages
struct pagedir *pagedir_copy(const struct pagedir *orig) {
struct pagedir *clone = page_alloc(1);
struct pagetable_entry *orig_pt, *clone_pt;
void *orig_page, *clone_page;
for (int i = 0; i < 1024; i++) {
clone->e[i] = orig->e[i];
if (!orig->e[i].present) continue;
orig_pt = (void*)(orig->e[i].address << 11);
clone_pt = page_alloc(1);
clone->e[i].address = (uintptr_t) clone_pt >> 11;
for (int j = 0; j < 1024; j++) {
clone_pt[j] = orig_pt[j];
if (!orig_pt[j].present) continue;
if (!orig_pt[j].user) continue;
// i could use .global?
orig_page = (void*)(orig_pt[j].address << 11);
clone_page = page_alloc(1);
clone_pt[j].address = (uintptr_t) clone_page >> 11;
memcpy(clone_page, orig_page, PAGE_SIZE);
}
}
return clone;
}
bool pagedir_iskern(struct pagedir *dir, const void __user *virt) {
struct pagetable_entry *page = get_entry(dir, virt);
return page && page->present && !page->user;
}
void *pagedir_virt2phys(struct pagedir *dir, const void __user *virt,
bool user, bool writeable)
{
struct pagetable_entry *page;
uintptr_t phys;
page = get_entry(dir, virt);
if (!page || !page->present) return NULL;
if (user && !page->user) return NULL;
if (writeable && !page->writeable) return NULL;
phys = page->address << 11;
phys |= ((uintptr_t)virt) & 0xFFF;
return (void*)phys;
}
void __user *pagedir_findfree(struct pagedir *dir, char __user *start, size_t len) {
struct pagetable_entry *page;
char __user *iter;
start = (userptr_t)(((uintptr_t __force)start + PAGE_MASK) & ~PAGE_MASK); // round up to next page
iter = start;
while (iter < (char __user *)0xFFF00000) { // TODO better boundary
page = get_entry(dir, iter);
if (page && page->present) {
start = iter + PAGE_SIZE;
} else {
if ((size_t)(iter + PAGE_SIZE - start) >= len)
return start;
}
iter += PAGE_SIZE;
}
return NULL;
}
|