Microkernel thing OS experiment (Zig ⚡)
1const common = @import("common");
2const arch = @import("../root.zig");
3const std = @import("std");
4const physToVirt = common.mm.physToHHDM;
5const Perms = common.mm.paging.Perms;
6
7pub const page_sizes = [_]usize{
8 0x1000, // 4K
9 0x200000, // 2M
10 0x40000000, // 1G
11 0x8000000000, // 512G
12 0x1000000000000, // 256T
13};
14
15pub const PageTable = extern struct {
16 entries: [512]Entry,
17
18 pub const Entry = packed struct {
19 present: bool,
20 writable: bool,
21 user_accessible: bool,
22 write_through: bool,
23 disable_cache: bool,
24 accessed: bool,
25 dirty: bool,
26 huge: bool,
27 global: bool,
28 idk: u3,
29 phys_addr: u40,
30 idk2: u11,
31 nx: bool,
32
33 const Self = @This();
34
35 pub fn getAddr(self: *const Self) u64 {
36 return self.phys_addr << 12;
37 }
38
39 pub fn setAddr(self: *Self, phys_addr: u64) void {
40 const addr = phys_addr >> 12;
41 self.phys_addr = @truncate(addr);
42 }
43 };
44};
45
46fn extract_index_from_vaddr(vaddr: u64, level: u6) u9 {
47 const shamt = 12 + level * 9;
48 return @truncate(vaddr >> shamt);
49}
50
51pub const TypedPTE = union(common.mm.paging.PTEType) {
52 Mapping: MappingHandle,
53 Table: TableHandle,
54 Empty,
55
56 const Self = @This();
57
58 pub fn decode(pte: *PageTable.Entry, level: u3) Self {
59 if (!pte.present) {
60 return .Empty;
61 }
62 if (!pte.huge and level != 0) {
63 return .{ .Table = decode_table(pte, level) };
64 }
65 return .{ .Mapping = decode_mapping(pte, level) };
66 }
67
68 pub fn decode_table(pte: *PageTable.Entry, level: u3) TableHandle {
69 return .{
70 .phys_addr = pte.getAddr(),
71 .level = level,
72 .underlying = pte,
73 .perms = .{
74 .writable = pte.writable,
75 .executable = !pte.nx,
76 .userspace_accessible = pte.user_accessible,
77 },
78 };
79 }
80
81 pub fn decode_mapping(pte: *PageTable.Entry, level: u3) MappingHandle {
82 return .{
83 .phys_addr = pte.getAddr(),
84 .level = level,
85 // TODO: memory types
86 .memory_type = null,
87 .underlying = pte,
88 .perms = .{
89 .writable = pte.writable,
90 .executable = !pte.nx,
91 .userspace_accessible = pte.user_accessible,
92 },
93 };
94 }
95};
96
97pub const MappingHandle = struct {
98 phys_addr: usize,
99 level: u3,
100 memory_type: ?MemoryType,
101 perms: Perms,
102 underlying: *PageTable.Entry,
103};
104
105pub const TableHandle = struct {
106 phys_addr: usize,
107 level: u3,
108 perms: Perms,
109 underlying: ?*PageTable.Entry,
110
111 const Self = @This();
112
113 // Get the child entries of this page table
114 pub fn get_children(self: *const Self) []PageTable.Entry {
115 const page_table = physToVirt(*PageTable, self.phys_addr);
116 return page_table.entries[0..];
117 }
118
119 // Get children from the position holding the table and on
120 pub fn skip_to(self: *const Self, vaddr: usize) []PageTable.Entry {
121 return self.get_children()[extract_index_from_vaddr(vaddr, self.level - 1)..];
122 }
123
124 // Decode child table given an entry
125 pub fn decode_child(self: *const Self, pte: *PageTable.Entry) TypedPTE {
126 return TypedPTE.decode(pte, self.level - 1);
127 }
128
129 pub fn addPerms(self: *const Self, perms: Perms) void {
130 if (perms.executable) {
131 self.underlying.?.nx = false;
132 }
133 if (perms.writable) {
134 self.underlying.?.writable = true;
135 }
136 if (perms.userspace_accessible) {
137 self.underlying.?.user_accessible = true;
138 }
139 }
140
141 pub fn child_domain(self: *const Self, vaddr: usize) UntypedSlice {
142 return domain(vaddr, self.level - 1);
143 }
144
145 pub fn make_child_table(self: *const Self, pte: *PageTable.Entry, perms: Perms) !TableHandle {
146 const pmem = try make_page_table();
147
148 const result: TableHandle = .{
149 .phys_addr = pmem,
150 .level = self.level - 1,
151 .perms = perms,
152 .underlying = pte,
153 };
154 pte.* = encode_table(result);
155
156 return result;
157 }
158
159 pub fn make_child_mapping(
160 self: *const Self,
161 pte: *PageTable.Entry,
162 paddr: ?usize,
163 perms: Perms,
164 memory_type: MemoryType,
165 ) !MappingHandle {
166 const page_size = page_sizes[self.level - 1];
167 const pmem = paddr orelse try common.init_data.bootmem.allocPhys(page_size);
168
169 const result: MappingHandle = .{
170 .level = self.level - 1,
171 .memory_type = memory_type,
172 .perms = perms,
173 .underlying = pte,
174 .phys_addr = pmem,
175 };
176
177 pte.* = encode_mapping(result);
178
179 return result;
180 }
181};
182
183pub fn root_table(vaddr: usize) TableHandle {
184 _ = vaddr;
185 const cr3_val = arch.registers.ControlRegisters.Cr3.read() & 0xFFFF_FFFF_FFFF_F000;
186 return .{
187 .phys_addr = cr3_val,
188 // TODO: detect and support 5 level paging!
189 .level = 4,
190 .perms = .{
191 .executable = true,
192 .writable = true,
193 },
194 .underlying = null,
195 };
196}
197
198fn encode_table(pte_handle: TableHandle) PageTable.Entry {
199 var pte = std.mem.zeroes(PageTable.Entry);
200
201 pte.setAddr(pte_handle.phys_addr);
202 pte.writable = pte_handle.perms.writable;
203 pte.user_accessible = pte_handle.perms.userspace_accessible;
204 pte.nx = !pte_handle.perms.executable;
205 pte.present = true;
206 pte.huge = false;
207
208 return pte;
209}
210
211fn encode_mapping(pte_handle: MappingHandle) PageTable.Entry {
212 var pte = std.mem.zeroes(PageTable.Entry);
213
214 pte.setAddr(pte_handle.phys_addr);
215 pte.present = true;
216
217 if (pte_handle.level != 0) {
218 pte.huge = true;
219 }
220
221 pte.writable = pte_handle.perms.writable;
222 pte.user_accessible = pte_handle.perms.userspace_accessible;
223 pte.nx = !pte_handle.perms.executable;
224
225 encode_memory_type(&pte, pte_handle);
226
227 return pte;
228}
229
230fn encode_memory_type(pte: *PageTable.Entry, pte_handle: MappingHandle) void {
231 const mt = pte_handle.memory_type orelse @panic("Unknown memory type");
232
233 // TODO: Page Attribute Table
234 switch (mt) {
235 .MemoryWritethrough => pte.write_through = true,
236 .DeviceUncacheable => pte.disable_cache = true,
237 .MemoryWriteBack => {},
238 else => @panic("Cannot set memory type"),
239 }
240}
241
242/// Returns physical address
243fn make_page_table() !usize {
244 const pt_phys = try common.init_data.bootmem.allocPhys(std.heap.pageSize());
245 const pt = physToVirt([*]u8, pt_phys);
246 @memset(pt[0..std.heap.pageSize()], 0x00);
247 return pt_phys;
248}
249
250pub fn invalidate(vaddr: u64) void {
251 asm volatile (
252 \\ invlpg (%[vaddr])
253 :
254 : [vaddr] "r" (vaddr),
255 : .{ .memory = true });
256}
257
258const UntypedSlice = struct {
259 len: usize,
260 ptr: usize,
261};
262
263pub fn domain(vaddr: usize, level: u3) UntypedSlice {
264 return .{
265 .len = page_sizes[level],
266 .ptr = vaddr & ~(page_sizes[level] - 1),
267 };
268}
269
270pub const MemoryType = enum {
271 DeviceUncacheable,
272 DeviceWriteCombining,
273 MemoryWritethrough,
274 MemoryWriteBack,
275};
276
277pub fn can_map_at(level: u3) bool {
278 return level < 2;
279}