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
|
/* SPDX-License-Identifier: copyleft-next-0.3.1 */
/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
/**
* @file dmem.c
* Handle device memory, i.e. anything outside the physical RAM.
*
* \todo Handle NUMA.
*/
#include <kmi/assert.h>
#include <kmi/dmem.h>
/** Region before RAM. */
static struct mem_region_root pre_ram = { 0 };
/** Region after RAM. */
static struct mem_region_root post_ram = { 0 };
stat_t init_devmem(pm_t ram_base, pm_t ram_top)
{
init_region(&pre_ram, 0, ram_base, 0);
init_region(&post_ram, ram_top, -1, 0);
return OK;
}
/**
* Select between the area before RAM or after RAM, depending on \p addr.
*
* @param addr Physical address of device.
* @return Corresponding device memory region or NULL if \p addr is within RAM.
*/
static struct mem_region_root *__select_region(pm_t addr)
{
if (addr < get_ram_base())
return &pre_ram;
if (addr > (get_ram_base() + get_ram_size()))
return &post_ram;
return NULL;
}
vm_t alloc_devmem(struct tcb *t, pm_t start, size_t bytes, vmflags_t flags)
{
assert(t && is_proc(t));
struct mem_region_root *region = __select_region(start);
if (!region)
return NULL;
/* carve out physical region (vm_t is misleading, this region is
* physical memory) */
vm_t p = alloc_fixed_region(region, start, bytes, &bytes, flags);
if (!p)
return NULL;
/* carve out virtual region */
vm_t v = alloc_region(&t->uvmem.region, bytes, &bytes, flags | MR_DEV);
if (!v) {
free_region(region, p);
return NULL;
}
/* convert from literal physical address to direct mapping address,
* which map_* expects (kind of annoying to deal with, admittedly, is
* there maybe a better way to handle these things?) */
p = (pm_t)__va(p);
if (map_fixed_region(t->proc.vmem, v, p, bytes, flags)) {
unmap_region(t->proc.vmem, v, bytes);
free_region(&t->uvmem.region, v);
free_region(region, p);
return NULL;
}
return v;
}
stat_t free_known_devmem(struct tcb *t, struct mem_region *m)
{
assert(t && is_proc(t));
/* ensure region is a device memory region and is currently not shared */
assert(is_set(m->flags, MR_DEV));
assert(m->pid != 0 || m->refcount == 1);
pm_t addr = 0;
stat_vpage(t->proc.vmem, __addr(m->start), &addr, NULL, NULL);
/* free physical device memory */
pm_t p = (pm_t)__pa(addr);
struct mem_region_root *region = __select_region(p);
if (!region)
return ERR_INVAL;
free_region(region, p);
/* free virtual allocation associated with device mem */
vm_t base = __addr(m->start);
vm_t end = __addr(m->end);
/* free virtual memory associated with physical mapping */
size_t size = end - base;
unmap_fixed_region(t->proc.vmem, base, size);
free_known_region(&t->uvmem.region, m);
return OK;
}
stat_t free_devmem(struct tcb *t, vm_t start)
{
assert(t && is_proc(t));
struct mem_region *m = find_used_region(&t->uvmem.region, start);
if (!m)
return ERR_NF;
/* not a devmem region */
if (!is_set(m->flags, MR_DEV))
return ERR_INVAL;
/* mapping is shared, can't free right now */
if (m->pid == 0 && m->refcount > 1)
return ERR_INVAL;
stat_t ret = free_known_devmem(t, m);
if (ret)
return ret;
free_known_region(&t->uvmem.region, m);
return OK;
}
|