FreeNOS
ARM64FirstTable.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2025 Ivan Tan
3 * Copyright (C) 2015 Niek Linnenbank
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include <FreeNOS/System.h>
20#include <SplitAllocator.h>
21#include <MemoryBlock.h>
22#include "ARM64Constant.h"
23#include "ARM64FirstTable.h"
24
26{
27 for (Address virt = 0; virt < GigaByte(4UL); virt += L1_BLOCK_SIZE) {
28 unsigned int l1_idx = L1_IDX(virt);
29 u64 tbl = (u64) &(firstTable->m_tables_l2[l1_idx][0]);
30 firstTable->m_tables_l1[l1_idx] = tbl | PT_PAGE;
31 }
32}
33
34u64 ARM64FirstTable::get_l2_entry(Address virt, SplitAllocator *alloc, u64 **tbl_l2, unsigned int *l2_idx) const
35{
36 unsigned int l1_idx = L1_IDX(virt);
37
38 assert(l1_idx < 4); // virtual address must be lower than 4GB
39 u64 entry_l1 = m_tables_l1[l1_idx];
40
41 // already mapped as table while initialization
42 //if (!IS_PT_PAGE_TBL(entry_l1))
43 // return ZERO;
44
45 *tbl_l2 = (u64 *)(entry_l1 & PAGEMASK);
46 *l2_idx = L2_IDX(virt);
47
48 return (*tbl_l2)[*l2_idx];
49}
50
63{
64
65 u64 *tbl_l2;
66 unsigned int l2_idx;
67
68 u64 entry_l2 = get_l2_entry(virt, alloc, &tbl_l2, &l2_idx);
69
70 // Check if the page table is present.
71 if (!IS_PT_PAGE_TBL(entry_l2))
72 return ZERO;
73 else
74 return (ARM64SecondTable *) alloc->toVirtual(entry_l2 & PAGEMASK);
75}
76
78 Address phys,
79 Memory::Access access,
80 SplitAllocator *alloc)
81{
82 ARM64SecondTable *table = getSecondTable(virt, alloc);
83 Arch::Cache cache;
84 Allocator::Range allocPhys, allocVirt;
85
86 // Check if the page table is present.
87 if (!table)
88 {
89 u64 *tbl_l2;
90 unsigned int l2_idx;
91
92 u64 entry_l2 = get_l2_entry(virt, alloc, &tbl_l2, &l2_idx);
93
94 // Reject if already mapped as a (super)section
95 if (IS_PT_BLOCK(entry_l2))
97
98 // Allocate a new page table
99 allocPhys.address = 0;
100 allocPhys.size = sizeof(ARM64SecondTable);
101 allocPhys.alignment = PAGESIZE;
102
103 if (alloc->allocate(allocPhys, allocVirt) != Allocator::Success)
105
106 MemoryBlock::set((void *)allocVirt.address, 0, PAGESIZE);
107
108 // Assign to the page directory. Do not assign permission flags (only for direct sections).
109 tbl_l2[l2_idx] = allocPhys.address | PT_PAGE;
110 //cache.cleanData(&m_tables[DIRENTRY(virt)]);
111 table = getSecondTable(virt, alloc);
112 }
113 return table->map(virt, phys, access);
114}
115
117 SplitAllocator *alloc)
118{
119 if (range.size & L2_BLOCK_RANGE)
121
122 if ((range.phys & ~L2_BLOCK_MASK) || (range.virt & ~L2_BLOCK_MASK))
124
125 for (Address i = 0; i < range.size; i += L2_BLOCK_SIZE)
126 {
127 u64 *tbl_l2;
128 unsigned int l2_idx;
129
130 u64 entry_l2 = get_l2_entry(range.virt + i, alloc, &tbl_l2, &l2_idx);
131
132 if (IS_PT_BLOCK(entry_l2))
134
135 u64 val = (range.phys + i) | PT_BLOCK | flags(range.access);
136 tbl_l2[l2_idx] = val;
137 }
139}
140
142{
143 ARM64SecondTable *table = getSecondTable(virt, alloc);
144
145 if (!table)
146 {
147 u64 *tbl_l2;
148 unsigned int l2_idx;
149
150 u64 entry_l2 = get_l2_entry(virt, alloc, &tbl_l2, &l2_idx);
151 if (IS_PT_BLOCK(entry_l2))
152 {
153 tbl_l2[l2_idx] = PT_NONE;
155 }
156 else
158 }
159 else
160 return table->unmap(virt);
161}
162
164 Address *phys,
165 SplitAllocator *alloc) const
166{
167 ARM64SecondTable *table = getSecondTable(virt, alloc);
168 if (!table)
169 {
170 u64 *tbl_l2;
171 unsigned int l2_idx;
172
173 u64 entry_l2 = get_l2_entry(virt, alloc, &tbl_l2, &l2_idx);
174 if (IS_PT_BLOCK(entry_l2))
175 {
176 const Address offsetInSection = virt % L2_BLOCK_SIZE;
177
178 *phys = (entry_l2 & L2_BLOCK_RANGE) +
179 ((offsetInSection / PAGESIZE) * PAGESIZE);
181 }
183 }
184 else
185 return table->translate(virt, phys);
186}
187
189 Memory::Access *access,
190 SplitAllocator *alloc) const
191{
192 ARM64SecondTable *table = getSecondTable(virt, alloc);
193 if (!table)
195 else
196 return table->access(virt, access);
197}
198
200{
201 u64 f = PT_KERNEL | PT_AF;
202
203 // Permissions
204 if (!(access & Memory::Executable)) f |= PT_NX;
205 if ((access & Memory::User)) f |= PT_USER;
206 if (!(access & Memory::Writable)) f |= PT_RO;
207
208 // Cache
210 f |= PT_OSH | PT_DEV;
211 else
212 f |= PT_ISH | PT_NC; /* FIXME: Is PT_OSH appropriate? */
213 return f;
214}
215
217 const Address phys)
218{
219 // Some pages that are part of the boot core's memory region
220 // are mapped on secondary cores. They can't be released there.
221 const Address allocBase = alloc->base();
222 const Size allocSize = alloc->size();
223 if (phys < allocBase || phys > allocBase + allocSize)
224 {
225 return;
226 }
227
228 // Note that some pages may have double mappings.
229 // Avoid attempting to release the same page twice or more.
230 if (alloc->isAllocated(phys))
231 {
232 alloc->release(phys);
233 }
234}
235
237 SplitAllocator *alloc)
238{
239 Address phys;
240
241 // Walk the full range of memory specified
242 for (Address addr = range.virt; addr < range.virt + range.size; addr += PAGESIZE)
243 {
244 ARM64SecondTable *table = getSecondTable(addr, alloc);
245 if (table == ZERO)
246 {
248 }
249
250 if (table->translate(addr, &phys) != MemoryContext::Success)
251 {
253 }
254
255 releasePhysical(alloc, phys);
256 table->unmap(addr);
257 }
258
260}
261
263 SplitAllocator *alloc,
264 const bool tablesOnly)
265{
266 Address phys;
267
268 // Input must be aligned to section address
269 if (range.virt & ~L2_BLOCK_MASK)
270 {
272 }
273
274 // Walk the page directory
275 for (Address addr = range.virt; addr < range.virt + range.size; addr += L2_BLOCK_SIZE)
276 {
277 ARM64SecondTable *table = getSecondTable(addr, alloc);
278 if (!table)
279 {
280 continue;
281 }
282 //else, what about large 2MB block?
283
284 // Release mapped pages, if requested
285 if (!tablesOnly)
286 {
287 for (Address i = 0; i < L2_BLOCK_SIZE; i += PAGESIZE)
288 {
289 if (table->translate(i, &phys) == MemoryContext::Success)
290 {
291 releasePhysical(alloc, phys);
292 }
293 }
294 }
295 // Release page table
296 u64 *tbl_l2;
297 unsigned int l2_idx;
298
299 u64 entry_l2 = get_l2_entry(addr, alloc, &tbl_l2, &l2_idx);
300
301 alloc->release(entry_l2 & PAGEMASK);
302 tbl_l2[l2_idx] = 0;
303 }
304
306}
u32 flags
Definition IntelACPI.h:3
ARM64 first level page table.
MemoryContext::Result translate(Address virt, Address *phys, SplitAllocator *alloc) const
Translate virtual address to physical address.
MemoryContext::Result unmap(Address virt, SplitAllocator *alloc)
Remove virtual address mapping.
u64 m_tables_l2[4][512]
MemoryContext::Result map(Address virt, Address phys, Memory::Access access, SplitAllocator *alloc)
Map a virtual address to a physical address.
u64 get_l2_entry(Address virt, SplitAllocator *alloc, u64 **tbl_l2, unsigned int *l2_idx) const
Get entry of Level 2 table.
u64 m_tables_l1[512]
Array of page table entries.
MemoryContext::Result access(Address virt, Memory::Access *access, SplitAllocator *alloc) const
Get Access flags for a virtual address.
static void initialize(ARM64FirstTable *firstTable)
void releasePhysical(SplitAllocator *alloc, const Address phys)
Release a single physical page.
MemoryContext::Result releaseRange(const Memory::Range range, SplitAllocator *alloc)
Release range of memory.
u32 flags(Memory::Access access) const
Convert Memory::Access to first level page table flags.
ARM64SecondTable * getSecondTable(Address virt, SplitAllocator *alloc) const
Retrieve second level page table.
MemoryContext::Result releaseSection(const Memory::Range range, SplitAllocator *alloc, const bool tablesOnly)
Release memory sections.
MemoryContext::Result mapLarge(Memory::Range range, SplitAllocator *alloc)
Map a contigous range of virtual memory to physical memory.
ARM64 second level page table implementation.
MemoryContext::Result translate(Address virt, Address *phys) const
Translate virtual address to physical address.
MemoryContext::Result unmap(Address virt)
Remove virtual address mapping.
MemoryContext::Result access(Address virt, Memory::Access *access) const
Get Access flags for a virtual address.
MemoryContext::Result map(Address virt, Address phys, Memory::Access)
Map a virtual address to a physical address.
ARMv6 cache management implementation.
Definition ARMCacheV6.h:43
Address base() const
Get memory base address for allocations.
Definition Allocator.cpp:69
virtual Size size() const
Get memory size.
Definition Allocator.cpp:64
static void * set(void *dest, int ch, unsigned count)
Fill memory with a constant byte.
Result
Result codes.
Allocator which separates kernel mapped memory at virtual and physical addresses.
virtual Result allocate(Range &args)
Allocate physical memory.
bool isAllocated(const Address page) const
Check if a physical page is allocated.
Address toVirtual(const Address phys) const
Convert Address to virtual pointer.
virtual Result release(const Address addr)
Release memory page.
#define PT_AF
#define PT_DEV
#define PT_NX
#define L1_IDX(va)
#define PT_ISH
#define IS_PT_BLOCK(entry)
#define PT_KERNEL
#define PT_OSH
#define PAGESIZE
ARM uses 4K pages.
Definition ARMConstant.h:97
#define L2_IDX(va)
#define PT_USER
#define PT_NC
#define PAGEMASK
Mask to find the page.
#define L2_BLOCK_MASK
Mask for large 2MB block mappings.
#define PT_BLOCK
#define PT_PAGE
#define L1_BLOCK_SIZE
#define PT_RO
#define L2_BLOCK_RANGE
#define L2_BLOCK_SIZE
#define IS_PT_PAGE_TBL(entry)
#define PT_NONE
#define assert(exp)
Insert program diagnostics.
Definition assert.h:60
unsigned int u32
Unsigned 32-bit number.
Definition Types.h:53
unsigned long Address
A memory address.
Definition Types.h:131
#define GigaByte(v)
Convert gigabytes to bytes.
Definition Macros.h:60
unsigned int Size
Any sane size indicator cannot go negative.
Definition Types.h:128
#define ZERO
Zero value.
Definition Macros.h:43
unsigned long long u64
Unsigned 64-bit number.
Definition Types.h:50
Access
Memory access flags.
Definition Memory.h:39
@ Executable
Definition Memory.h:43
@ User
Definition Memory.h:44
@ Device
Definition Memory.h:48
@ Writable
Definition Memory.h:42
Describes a range of memory.
Definition Allocator.h:66
Size alignment
Alignment in bytes or ZERO for default alignment.
Definition Allocator.h:69
Address address
Starting address of the memory range.
Definition Allocator.h:67
Size size
Amount of memory in bytes.
Definition Allocator.h:68
Memory range.
Definition Memory.h:56
Size size
Size in number of bytes.
Definition Memory.h:59
Address phys
Physical address.
Definition Memory.h:58
Address virt
Virtual address.
Definition Memory.h:57
Access access
Page access flags.
Definition Memory.h:60