Microkernel thing OS experiment (Zig ⚡)
1const arch = @import("arch");
2const std = @import("std");
3const TableHandle = arch.mm.paging.TableHandle;
4const MemoryType = arch.mm.paging.MemoryType;
5
6pub const Perms = struct {
7 writable: bool,
8 executable: bool,
9 userspace_accessible: bool = false,
10
11 const Self = @This();
12
13 /// Verify that the current permissions are a superset of the provided ones
14 pub fn allows(self: Self, other: Self) bool {
15 if (!self.writable and other.writable) {
16 return false;
17 }
18 if (!self.executable and other.executable) {
19 return false;
20 }
21 if (!self.userspace_accessible and other.userspace_accessible) {
22 return false;
23 }
24 return true;
25 }
26
27 /// OR two permissions
28 pub fn addPerms(self: Self, other: Self) Self {
29 return .{
30 .writable = self.writable or other.writable,
31 .executable = self.executable or other.executable,
32 .userspace = self.userspace_accessible or other.userspace_accessible,
33 };
34 }
35};
36
37pub const PTEType = enum { Mapping, Table, Empty };
38
39pub fn mapPhys(args: struct {
40 vaddr: usize,
41 paddr: usize,
42 size: usize,
43 perms: Perms,
44 memory_type: MemoryType,
45}) !void {
46 const root = arch.mm.paging.root_table(args.vaddr);
47 var vaddr = args.vaddr;
48 var paddr = args.paddr;
49 var size = args.size;
50 try mapPageImpl(&vaddr, &paddr, &size, root, args.perms, args.memory_type);
51}
52
53pub fn map(args: struct {
54 vaddr: usize,
55 size: usize,
56 perms: Perms,
57 memory_type: MemoryType,
58}) !void {
59 const root = arch.mm.paging.root_table(args.vaddr);
60 var vaddr = args.vaddr;
61 var size = args.size;
62 try mapPageImpl(&vaddr, null, &size, root, args.perms, args.memory_type);
63}
64
65fn mapPageImpl(
66 vaddr: *usize,
67 paddr: ?*usize,
68 size: *usize,
69 table: TableHandle,
70 perms: Perms,
71 memory_type: MemoryType,
72) !void {
73 // 1. Get slice of every child from the target forwards
74 const children = table.skip_to(vaddr.*);
75
76 // 2. For each PTE, decode to the type (Mapping, Table, Empty)
77 // If there's already a mapping, we're fucked
78 // If it's a table, keep going forward till we reach Mapping or Empty,
79 // while of course ensuring permissions
80 // If it's empty, check if we reached our target level. If we didn't,
81 // then make a new child table and keep going. If it's not empty, then
82 // make the child mapping and reduce the amount of size we're targetting
83 for (children) |*child| {
84 switch (table.decode_child(child)) {
85 .Mapping => return error.AlreadyPresent,
86 .Table => |*tbl| {
87 try mapPageImpl(vaddr, paddr, size, tbl.*, perms, memory_type);
88 if (!tbl.perms.allows(perms)) {
89 tbl.addPerms(perms);
90 arch.mm.paging.invalidate(vaddr.*);
91 }
92 },
93 .Empty => {
94 const domain = table.child_domain(vaddr.*);
95 if (domain.ptr == vaddr.* and domain.len <= size.* and arch.mm.paging.can_map_at(table.level - 1) and is_aligned(vaddr.*, paddr, table.level - 1)) {
96 // Make child mapping etc
97 _ = try table.make_child_mapping(child, if (paddr) |p| p.* else null, perms, memory_type);
98 const step = domain.len;
99 if (step >= size.*) {
100 size.* = 0;
101 return;
102 } else {
103 size.* -= step;
104 vaddr.* += step;
105 if (paddr) |p| {
106 p.* += step;
107 }
108 }
109 } else {
110 const tbl = try table.make_child_table(child, perms);
111 try mapPageImpl(vaddr, paddr, size, tbl, perms, memory_type);
112 }
113 },
114 }
115 if (size.* == 0) return;
116 }
117}
118
119fn is_aligned(vaddr: usize, paddr: ?*usize, level: u3) bool {
120 if (!std.mem.isAligned(vaddr, arch.mm.paging.page_sizes[level])) {
121 return false;
122 }
123
124 if (paddr) |p| {
125 return std.mem.isAligned(p.*, arch.mm.paging.page_sizes[level]);
126 }
127
128 return true;
129}