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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
|
/* SPDX-License-Identifier: copyleft-next-0.3.1 */
/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
/**
* @file vmem.c
* Virtual memory handling, mainly userspace virtual memory.
*/
#include <kmi/mem_regions.h>
#include <kmi/assert.h>
#include <kmi/string.h>
#include <kmi/debug.h>
#include <kmi/bits.h>
#include <kmi/vmem.h>
#include <arch/vmem.h>
stat_t init_uvmem(struct tcb *t, vm_t base, vm_t top)
{
return init_region(&t->sp_r, base, top);
}
/**
* Clone process memory region.
*
* @param d Destination tcb.
* @param s Source tcb.
* @param m Memory region to clone.
* @return \ref ERR_MISC if clone failed, otherwise \ref OK.
*
* @todo check shared memory regions.
*/
static stat_t __clone_mapped_region(struct tcb *d, struct tcb *s,
struct mem_region *m)
{
vm_t start = m->start * order_size(BASE_PAGE);
vm_t end = m->end * order_size(BASE_PAGE);
size_t size = end - start, actual_size = 0;
vm_t va = alloc_fixed_region(&d->sp_r, start, size,
&actual_size, m->flags);
catastrophic_assert(va == start);
if (!copy_allocd_region(d->proc.vmem, va, size, m->flags, s->proc.vmem))
return ERR_MISC;
return OK;
}
/**
* Unmap and free private memory region.
*
* @param t Current thread.
* @param m Memory region to free.
* @return \see unmap_freed_region().
*/
static stat_t __free_mapped_private_region(struct tcb *t, struct mem_region *m)
{
stat_t status = OK;
pm_t start = __addr(m->start);
pm_t end = __addr(m->end);
if (!unmap_freed_region(t->proc.vmem, start, end - start, m->flags,
&status))
return ERR_MISC;
return status;
}
/**
* Check whether process associated with shared memory is still using it.
*
* @param pid Process to check.
* @param start Start of memory region
* @return \ref true if it is still in use, \ref false otherwise.
*/
static bool __proc_has_region(id_t pid, vm_t start)
{
/** @todo this has a slight potential to have a race condition, where
* both threads want to free the same shared region at the same time. */
struct tcb *p = get_tcb(pid);
if (!p)
return false;
struct mem_region *m = find_used_region(&p->sp_r, start);
if (!m)
return false;
return true;
}
/**
* Unmap shared region and free associated physical pages if they're not being
* used by the other process.
*
* @param t Current thread.
* @param m Memory region to free.
* @return \see unmap_vpage().
*/
static stat_t __free_mapped_shared_region(struct tcb *t, struct mem_region *m)
{
vm_t start = __addr(m->start);
bool in_use = __proc_has_region(m->pid, m->alt_va);
size_t osize = order_size(BASE_PAGE);
size_t pages = m->start - m->end;
stat_t status = OK;
for (size_t i = 0; i < pages; ++i) {
vm_t va = start + i * osize;
pm_t pa = 0;
stat_vpage(t->proc.vmem, va, &pa, 0, 0);
status = unmap_vpage(t->proc.vmem, va);
if (!in_use)
free_page(pa, BASE_PAGE);
}
return status;
}
/**
* Convenience function for freeing mapped regions.
*
* @param t Thread to work in.
* @param m Memory region to free.
* @return \ref OK
*/
static stat_t __free_mapped_region(struct tcb *t, struct mem_region *m)
{
if (m->pid != 0)
return __free_mapped_shared_region(t, m);
return __free_mapped_private_region(t, m);
}
stat_t clear_uvmem(struct tcb *t)
{
struct mem_region *m = find_first_region(&t->sp_r);
while (m) {
if (!is_region_kept(m))
__free_mapped_region(t, m);
m = m->next;
}
return OK;
}
stat_t purge_uvmem(struct tcb *t)
{
struct mem_region *m = find_first_region(&t->sp_r);
while (m) {
__free_mapped_region(t, m);
m = m->next;
}
return OK;
}
stat_t destroy_uvmem(struct tcb *t)
{
/* force clear all regions */
purge_uvmem(t);
/* destroy region tree itself */
return destroy_region(&t->sp_r);
}
stat_t clone_mem_regions(struct tcb *d, struct tcb *s)
{
/** @todo implement some way to only iterate used regions, this loops
* through all regions which is likely a slight bit slower. */
struct mem_region *m = find_first_region(&s->sp_r);
while (m) {
if (is_region_used(m))
__clone_mapped_region(d, s, m);
m = m->next;
}
return OK;
}
vm_t alloc_uvmem(struct tcb *t, size_t size, vmflags_t flags)
{
/* t exists and is the process tcb of the current process */
hard_assert(t && is_proc(t), ERR_INVAL);
stat_t status = OK;
const vm_t v = alloc_region(&t->sp_r, size, &size, flags);
const vm_t w = map_allocd_region(t->proc.vmem, v, size, flags, &status);
return w;
}
vm_t alloc_uvpage(struct tcb *t, size_t size, vmflags_t flags, size_t *asize,
pm_t *paddr)
{
hard_assert(t && is_proc(t), ERR_INVAL);
enum mm_order order = nearest_order(size);
size_t actual_size = order_size(order);
stat_t status = OK;
const vm_t v = alloc_region(&t->sp_r, size, &size, flags);
const vm_t w = __addr(__page(v));
pm_t addr = alloc_page(order);
/** @todo should free region */
if (!addr)
return NULL;
status = map_vpage(t->proc.vmem, addr, w, flags, order);
if (status)
return NULL;
if (asize)
*asize = actual_size;
if (paddr)
*paddr = addr;
return w;
}
vm_t alloc_fixed_uvmem(struct tcb *t, vm_t start, size_t size, vmflags_t flags)
{
hard_assert(t && is_proc(t), ERR_INVAL);
stat_t status = OK;
const vm_t v = alloc_fixed_region(&t->sp_r, start, size, &size, flags);
const vm_t w = map_allocd_region(t->proc.vmem, v, size, flags, &status);
return w;
}
/**
* Helper for \ref map_fixed_mem().
* Maps some contiguous bit of physical memory to an allocated virtual memory region.
*
* @param b Virtual memory to work in.
* @param v Start of virtual memory region.
* @param p Start of physical memory region.
* @param size Size of virtual memory region.
* @param flags Flags to use for mappings.
* @param status Is written to with the status of this function.
* @return The start of the virtual address mapping.
*/
static vm_t map_fixed_region(struct vmem *b, vm_t v, pm_t p, size_t size,
vmflags_t flags, stat_t *status)
{
vm_t w = v;
stat_t stat = OK;
size_t pages = size / BASE_PAGE_SIZE;
for (size_t i = 0; i < pages; ++i) {
stat = map_vpage(b, p, v, flags, BASE_PAGE);
v += BASE_PAGE_SIZE;
p += BASE_PAGE_SIZE;
}
if (status)
*status = stat;
return w;
}
vm_t map_fixed_mem(struct tcb *t, pm_t start, size_t size, vmflags_t flags)
{
stat_t status = OK;
const vm_t v = alloc_region(&t->sp_r, size, &size, flags);
const vm_t w = map_fixed_region(t->proc.vmem, v, start, size, flags,
&status);
return w + (start % BASE_PAGE_SIZE);
}
/* free_shared_uvmem shouldn't be needed, likely to work with free_uvmem */
stat_t alloc_shared_uvmem(struct tcb *s, struct tcb *c,
size_t size, vmflags_t sflags, vmflags_t cflags,
vm_t *sstart, vm_t *cstart)
{
hard_assert(sstart, ERR_INVAL);
hard_assert(cstart, ERR_INVAL);
hard_assert(s && is_proc(s), ERR_INVAL);
hard_assert(c && is_proc(c), ERR_INVAL);
size_t ssize, csize;
vm_t sv = alloc_shared_region(&s->sp_r, size, &ssize, sflags, c->rid);
vm_t cv = alloc_shared_region(&c->sp_r, size, &csize, cflags, s->rid);
/* not exactly optimal but good enough for now, I can start worrying
* about hyperoptimizations whenever. */
set_alt_region_addr(&s->sp_r, sv, cv);
set_alt_region_addr(&c->sp_r, cv, sv);
if (csize != ssize) {
/** @todo cleanup, better errors? */
return ERR_INVAL;
}
stat_t cstatus = OK, sstatus = OK;
size_t osize = order_size(BASE_PAGE);
size_t pages = ssize / osize;
for (size_t i = 0; i < pages; ++i) {
pm_t p = alloc_page(BASE_PAGE);
sstatus = map_vpage(s->proc.vmem, p, sv + i * osize, sflags,
BASE_PAGE);
cstatus = map_vpage(c->proc.vmem, p, cv + i * osize, cflags,
BASE_PAGE);
}
*sstart = sv;
*cstart = cv;
if (sstatus)
return sstatus;
if (cstatus)
return cstatus;
return OK;
}
stat_t free_uvmem(struct tcb *r, vm_t va)
{
/** \todo assume tcb is root tcb? */
struct mem_region *m = find_used_region(&r->sp_r, va);
if (!m)
return ERR_NF;
stat_t status = __free_mapped_region(r, m);
if (status)
return ERR_MISC;
return free_known_region(&r->sp_r, m);
}
stat_t alloc_uvmem_wrapper(struct vmem *b, pm_t *offset, vm_t vaddr,
vmflags_t flags, enum mm_order order, void *data)
{
*offset = alloc_page(order);
if (!*offset)
return INFO_TRGN; /* try again */
stat_t *status = (stat_t *)data, ret;
ret = map_vpage(b, *offset, vaddr, flags, order);
if (status)
*status = ret;
return ret;
}
stat_t alloc_shared_wrapper(struct vmem *b, pm_t *offset, vm_t vaddr,
vmflags_t flags, enum mm_order order, void *data)
{
if (order != MM_O0)
return INFO_TRGN;
*offset = alloc_page(MM_O0);
stat_t *status = (stat_t *)data, ret;
ret = map_vpage(b, *offset, vaddr, flags, order);
if (status)
*status = ret;
return ret;
}
stat_t copy_allocd_wrapper(struct vmem *b, pm_t *offset, vm_t vaddr,
vmflags_t flags, enum mm_order order, void *data)
{
struct vmem *s = (struct vmem *)data;
pm_t paddr = 0;
enum mm_order v_order = 0;
stat_vpage(s, vaddr, &paddr, &v_order, 0);
/** @todo what if we could combine multiple pages into one in the new
* process? */
if (order > v_order)
return INFO_TRGN;
pm_t new_page = alloc_page(order);
if (!new_page)
return INFO_TRGN;
/* set write flags temporarily */
vmflags_t wrflags = flags | VM_W;
map_vpage(b, new_page, vaddr, wrflags, order);
memcpy((void *)new_page, (void *)(paddr + *offset), order_size(order));
/* set actual flags */
map_vpage(b, new_page, vaddr, flags, order);
if (v_order > order)
*offset += order_size(order);
else
*offset = 0;
return OK;
}
stat_t free_uvmem_wrapper(struct vmem *b, pm_t *offset, vm_t vaddr,
vmflags_t flags, enum mm_order order, void *data)
{
UNUSED(flags);
UNUSED(offset);
pm_t paddr = 0;
enum mm_order v_order = 0;
stat_vpage(b, vaddr, &paddr, &v_order, 0);
if (order != v_order)
return INFO_TRGN;
/** @todo we might need to cause an ipi to flush the tlb for other
* cores */
stat_t *status = (stat_t *)data, ret;
ret = unmap_vpage(b, vaddr);
if (status)
*status = ret;
free_page(order, paddr);
return ret;
}
|