WIP: WIP, WIP
This commit is contained in:
@@ -1,72 +1,86 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn build(b: *std.Build) void {
|
fn insertFakeLinuxImageHeader(step: *std.Build.Step, opts: std.Build.Step.MakeOptions) anyerror!void {
|
||||||
const optimize = .Debug;
|
const RISCV_MAGIC1 = "RISCV\x00\x00\x00";
|
||||||
const target = b.standardTargetOptions(.{
|
const RISCV_MAGIC2 = "RSC\x05";
|
||||||
.default_target = .{
|
|
||||||
.cpu_arch = .riscv64,
|
const elf = std.elf;
|
||||||
.os_tag = .freestanding,
|
|
||||||
.abi = .none,
|
var ehdr: elf.Ehdr = undefined;
|
||||||
|
|
||||||
|
var file = try std.fs.cwd().openFile("zig-out/bin/kernel", .{ .mode = .read_write });
|
||||||
|
const stat = try file.stat();
|
||||||
|
_ = try file.readAll(std.mem.asBytes(&ehdr));
|
||||||
|
|
||||||
|
for (0..ehdr.e_phnum) |i| {
|
||||||
|
var phdr: elf.Phdr = undefined;
|
||||||
|
var data: [64]u8 = undefined;
|
||||||
|
|
||||||
|
_ = try file.preadAll(std.mem.asBytes(&phdr), ehdr.e_phoff + i * ehdr.e_phentsize);
|
||||||
|
|
||||||
|
if (phdr.p_type != elf.PT_LOAD) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
const kernel_module = b.addModule("kernel", .{
|
_ = try file.preadAll(&data, phdr.p_offset);
|
||||||
.optimize = optimize,
|
|
||||||
.target = target,
|
|
||||||
.pic = true,
|
|
||||||
.red_zone = false,
|
|
||||||
.code_model = .medium,
|
|
||||||
.root_source_file = b.path("src/kernel.zig")
|
|
||||||
});
|
|
||||||
|
|
||||||
const kernel = b.addExecutable(.{
|
if (std.mem.eql(u8, RISCV_MAGIC1, data[48..56]) and std.mem.eql(u8, RISCV_MAGIC2, data[56..60])) {
|
||||||
.name = "kernel",
|
const size: u64 = stat.size;
|
||||||
.root_module = kernel_module,
|
try file.pwriteAll(std.mem.asBytes(&size), phdr.p_offset + 16);
|
||||||
.pic = true
|
break;
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = step;
|
||||||
|
_ = opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) anyerror!void {
|
||||||
|
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
|
||||||
|
const target = b.standardTargetOptions(.{ .default_target = .{
|
||||||
|
.cpu_arch = .riscv64,
|
||||||
|
.os_tag = .freestanding,
|
||||||
|
.abi = .none,
|
||||||
|
} });
|
||||||
|
|
||||||
|
const kernel_module = b.addModule("kernel", .{ .optimize = optimize, .target = target, .pic = true, .red_zone = false, .code_model = .medium, .root_source_file = b.path("src/kernel.zig") });
|
||||||
|
|
||||||
|
const kernel = b.addExecutable(.{ .name = "kernel", .root_module = kernel_module, .pic = true });
|
||||||
kernel.pie = true;
|
kernel.pie = true;
|
||||||
|
|
||||||
kernel.entry = .{.symbol_name = "__rv64_entry"};
|
kernel.entry = .{ .symbol_name = "__rv64_entry" };
|
||||||
|
|
||||||
kernel.setLinkerScript(b.path("etc/riscv64-unknown-none.ld"));
|
kernel.setLinkerScript(b.path("etc/riscv64-unknown-none.ld"));
|
||||||
kernel.addCSourceFiles(.{
|
kernel.addCSourceFiles(.{
|
||||||
.files = &.{
|
.files = &.{"src/arch/riscv64/entry.S"},
|
||||||
"src/arch/riscv64/entry.S"
|
|
||||||
},
|
|
||||||
.flags = &.{},
|
.flags = &.{},
|
||||||
});
|
});
|
||||||
b.installArtifact(kernel);
|
b.installArtifact(kernel);
|
||||||
|
|
||||||
const elf2bin = b.addSystemCommand(&.{
|
const fakeLinuxHeader: *std.Build.Step = try b.allocator.create(std.Build.Step);
|
||||||
"llvm-objcopy",
|
fakeLinuxHeader.* = std.Build.Step.init(.{
|
||||||
"-O", "binary",
|
.id = std.Build.Step.Id.custom,
|
||||||
"zig-out/bin/kernel",
|
.name = "insert fake linux header",
|
||||||
"zig-out/bin/kernel.bin"
|
.owner = kernel.step.owner,
|
||||||
|
.makeFn = insertFakeLinuxImageHeader,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const elf2bin = b.addSystemCommand(&.{ "llvm-objcopy", "-O", "binary", "zig-out/bin/kernel", "zig-out/bin/kernel.bin" });
|
||||||
|
|
||||||
// TODO QEMU binary override
|
// TODO QEMU binary override
|
||||||
const qemu_info = switch (target.result.cpu.arch) {
|
const qemu_info = switch (target.result.cpu.arch) {
|
||||||
.riscv64 => .{ "qemu-system-riscv64", "rv64" },
|
.riscv64 => .{ "qemu-system-riscv64", "rv64" },
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
|
|
||||||
const qemu_cmd = b.addSystemCommand(&.{
|
const qemu_cmd = b.addSystemCommand(&.{ qemu_info[0], "-M", "virt", "-kernel", "zig-out/bin/kernel.bin", "-m", "256M", "-cpu", qemu_info[1], "-serial", "mon:stdio", "-display", "none" });
|
||||||
qemu_info[0],
|
|
||||||
"-M", "virt",
|
|
||||||
"-kernel", "zig-out/bin/kernel.bin",
|
|
||||||
"-m", "256M",
|
|
||||||
"-cpu", qemu_info[1],
|
|
||||||
"-serial", "mon:stdio",
|
|
||||||
"-display", "none"
|
|
||||||
});
|
|
||||||
|
|
||||||
if (target.result.cpu.arch == .riscv64) {
|
if (target.result.cpu.arch == .riscv64) {
|
||||||
qemu_cmd.addArgs(&.{
|
qemu_cmd.addArgs(&.{ "-bios", "etc/boot/rv64_fw_jump.bin" });
|
||||||
"-bios", "etc/boot/rv64_fw_jump.bin"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
elf2bin.step.dependOn(b.getInstallStep());
|
fakeLinuxHeader.dependOn(b.getInstallStep());
|
||||||
|
elf2bin.step.dependOn(fakeLinuxHeader);
|
||||||
qemu_cmd.step.dependOn(&elf2bin.step);
|
qemu_cmd.step.dependOn(&elf2bin.step);
|
||||||
if (b.args) |args| qemu_cmd.addArgs(args);
|
if (b.args) |args| qemu_cmd.addArgs(args);
|
||||||
const run_step = b.step("run", "Start the OS in qemu");
|
const run_step = b.step("run", "Start the OS in qemu");
|
||||||
|
|||||||
+37
-5
@@ -1,4 +1,7 @@
|
|||||||
const boot = @import("riscv64/boot.zig");
|
const boot = @import("riscv64/boot.zig");
|
||||||
|
const regs = @import("riscv64/regs.zig");
|
||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
export const _ = boot.rv64BspLowerEntry;
|
export const _ = boot.rv64BspLowerEntry;
|
||||||
|
|
||||||
pub fn arch() type {
|
pub fn arch() type {
|
||||||
@@ -6,18 +9,47 @@ pub fn arch() type {
|
|||||||
pub inline fn halt() noreturn {
|
pub inline fn halt() noreturn {
|
||||||
while (true) {
|
while (true) {
|
||||||
_ = setInterruptMask(true);
|
_ = setInterruptMask(true);
|
||||||
pause();
|
waitForInterrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn setInterruptMask(mask: bool) bool {
|
pub inline fn setInterruptMask(mask: bool) bool {
|
||||||
// TODO
|
const old = interruptMask();
|
||||||
_ = mask;
|
if (mask) {
|
||||||
return true;
|
regs.SSTATUS.modify(.{}, .{ .SIE = true });
|
||||||
|
} else {
|
||||||
|
regs.SSTATUS.modify(.{ .SIE = true }, .{});
|
||||||
|
}
|
||||||
|
return old;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn pause() void {
|
pub fn interruptMask() bool {
|
||||||
|
return regs.SSTATUS.read().SIE;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn waitForInterrupt() void {
|
||||||
asm volatile ("wfi");
|
asm volatile ("wfi");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub inline fn spinHint() void {
|
||||||
|
// Don't want to explicitly enable Zihintpause ext, so just paste this as raw opcode
|
||||||
|
asm volatile (".word 0x0100000f");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn barrier(comptime ordering: std.builtin.AtomicOrder) void {
|
||||||
|
switch (ordering) {
|
||||||
|
.acquire => {
|
||||||
|
asm volatile ("fence rw, w");
|
||||||
|
},
|
||||||
|
.release => {
|
||||||
|
asm volatile ("fence w, rw");
|
||||||
|
},
|
||||||
|
.acq_rel, .seq_cst => {
|
||||||
|
asm volatile ("fence rw, rw");
|
||||||
|
},
|
||||||
|
.unordered, .monotonic => {},
|
||||||
|
}
|
||||||
|
asm volatile ("":::"memory");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-51
@@ -1,15 +1,20 @@
|
|||||||
const sbi = @import("sbi.zig");
|
const sbi = @import("sbi.zig");
|
||||||
const debug = @import("../../debug.zig");
|
const debug = @import("../../debug.zig");
|
||||||
const arch = @import("../../kernel.zig").arch;
|
const kernel = @import("../../kernel.zig");
|
||||||
const vmm = @import("vmm.zig");
|
const vmm = @import("vmm.zig");
|
||||||
const regs = @import("regs.zig");
|
const regs = @import("regs.zig");
|
||||||
|
const dtb = @import("../../util/dtb.zig");
|
||||||
|
|
||||||
const log = debug.log;
|
const log = debug.log;
|
||||||
|
const arch = kernel.arch;
|
||||||
|
|
||||||
extern const __rela_start: u8;
|
extern const __rela_start: u8;
|
||||||
extern const __rela_end: u8;
|
extern const __rela_end: u8;
|
||||||
extern const __rv64_bsp_stack_top: u8;
|
extern const __rv64_bsp_stack_top: u8;
|
||||||
|
|
||||||
|
var gDtbAddress: usize = 0;
|
||||||
|
var gBspHartId: usize = 0;
|
||||||
|
|
||||||
pub export fn rv64RelocateKernel(imageBase: usize, relaStart: usize, relaEnd: usize) void {
|
pub export fn rv64RelocateKernel(imageBase: usize, relaStart: usize, relaEnd: usize) void {
|
||||||
const elf = @import("std").elf;
|
const elf = @import("std").elf;
|
||||||
|
|
||||||
@@ -26,78 +31,54 @@ pub export fn rv64RelocateKernel(imageBase: usize, relaStart: usize, relaEnd: us
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bspUpperEntry(a0: usize, a1: usize) callconv(.C) noreturn {
|
fn bspUpperEntry(realAddress: usize, unused: usize) callconv(.C) noreturn {
|
||||||
asm volatile ("":::"memory");
|
_ = unused;
|
||||||
|
|
||||||
|
arch.barrier(.acq_rel);
|
||||||
|
|
||||||
_ = a1;
|
|
||||||
// Relocate the kernel yet again, this time to another base
|
// Relocate the kernel yet again, this time to another base
|
||||||
const relaStart = @intFromPtr(&__rela_start);
|
const relaStart = @intFromPtr(&__rela_start);
|
||||||
const relaEnd = @intFromPtr(&__rela_end);
|
const relaEnd = @intFromPtr(&__rela_end);
|
||||||
const relOffset = vmm.KERNEL_VIRTUAL_BASE + vmm.L1.offset(a0);
|
const relOffset = vmm.KERNEL_VIRTUAL_BASE + vmm.L1.offset(realAddress);
|
||||||
|
|
||||||
asm volatile ("":::"memory");
|
|
||||||
|
|
||||||
|
arch.barrier(.acq_rel);
|
||||||
rv64RelocateKernel(relOffset, relaStart, relaEnd);
|
rv64RelocateKernel(relOffset, relaStart, relaEnd);
|
||||||
|
vmm.unmapEarly();
|
||||||
asm volatile ("":::"memory");
|
|
||||||
|
|
||||||
// Can unmap lower half now
|
|
||||||
for (0..4) |i| {
|
|
||||||
vmm.fixed.entry(i).* = .INVALID;
|
|
||||||
}
|
|
||||||
|
|
||||||
asm volatile ("":::"memory");
|
|
||||||
|
|
||||||
debug.log.setWriteFn(&sbi.debugPrintByte);
|
debug.log.setWriteFn(&sbi.debugPrintByte);
|
||||||
|
kernel.mem.PhysicalAddress.gVirtualizeBase = 0;
|
||||||
|
kernel.mem.PhysicalAddress.gVirtualizeSize = vmm.virtualizeRange();
|
||||||
|
|
||||||
log.info("Still alive", .{});
|
// Setup physical memory management
|
||||||
|
const fdt = dtb.Fdt.fromPhysicalAddress(.{ .raw = gDtbAddress }) catch |err| {
|
||||||
|
log.panic("Cannot initialize raw DTB: {}", .{ err });
|
||||||
|
};
|
||||||
|
fdt.dump();
|
||||||
|
|
||||||
arch.halt();
|
kernel.kernel_main();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn longJump(pc: usize, sp: usize, a0: usize, a1: usize) noreturn {
|
inline fn longJump(pc: usize, sp: usize, a0: usize, a1: usize) noreturn {
|
||||||
asm volatile (
|
asm volatile (
|
||||||
\\ mv sp, %[sp]
|
\\ mv sp, %[sp]
|
||||||
\\ jr %[pc]
|
\\ jr %[pc]
|
||||||
:
|
:
|
||||||
: [a0]"{a0}"(a0),
|
: [a0] "{a0}" (a0),
|
||||||
[a1]"{a1}"(a1),
|
[a1] "{a1}" (a1),
|
||||||
[pc]"r"(pc),
|
[pc] "r" (pc),
|
||||||
[sp]"r"(sp)
|
[sp] "r" (sp),
|
||||||
:"memory"
|
: "memory"
|
||||||
);
|
);
|
||||||
unreachable;
|
unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setupMmu(realAddress: usize) void {
|
pub export fn rv64BspLowerEntry(realAddress: usize, bspHartId: usize, dtbAddress: usize) callconv(.C) noreturn {
|
||||||
var table = &vmm.fixed;
|
|
||||||
const realL1 = vmm.L1.index(realAddress);
|
|
||||||
|
|
||||||
// Lower half
|
|
||||||
for (0..4) |i| {
|
|
||||||
table.entry(i).* = vmm.TableEntry(vmm.L1).page(
|
|
||||||
.{ .raw = i * vmm.L1.SIZE },
|
|
||||||
.{ .r = true, .w = true, .x = true }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map 1GiB at KERNEL_VIRTUAL_BASE -> physical 1GiB where the kernel is loaded
|
|
||||||
table.entry(vmm.KERNEL_VIRTUAL_L1I).* = vmm.TableEntry(vmm.L1).page(
|
|
||||||
.{ .raw = vmm.L1.address(realL1) },
|
|
||||||
.{ .r = true, .w = true, .x = true }
|
|
||||||
);
|
|
||||||
|
|
||||||
const address = @as(usize, @intFromPtr(table));
|
|
||||||
regs.SATP.write(.{
|
|
||||||
.PPN = @intCast(address >> 12),
|
|
||||||
.MODE = .sv39
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub export fn rv64BspLowerEntry(realAddress: usize) callconv(.C) noreturn {
|
|
||||||
debug.log.setWriteFn(&sbi.debugPrintByte);
|
debug.log.setWriteFn(&sbi.debugPrintByte);
|
||||||
|
|
||||||
setupMmu(realAddress);
|
gDtbAddress = dtbAddress;
|
||||||
|
gBspHartId = bspHartId;
|
||||||
|
|
||||||
|
vmm.mapEarly(realAddress);
|
||||||
|
|
||||||
// &bspUpperEntry will yield a pointer like: X + P, where
|
// &bspUpperEntry will yield a pointer like: X + P, where
|
||||||
// * X is symbol's raw address,
|
// * X is symbol's raw address,
|
||||||
|
|||||||
@@ -8,12 +8,35 @@
|
|||||||
.extern RELOC_SYMBOL
|
.extern RELOC_SYMBOL
|
||||||
|
|
||||||
.pushsection .text.entry
|
.pushsection .text.entry
|
||||||
.option push
|
|
||||||
.option norvc
|
|
||||||
|
|
||||||
.type __rv64_entry, @function
|
.type __rv64_entry, @function
|
||||||
|
// .option push
|
||||||
|
// .option rvc
|
||||||
__rv64_entry:
|
__rv64_entry:
|
||||||
auipc s0, 0 // a0 = real PC (also a real load address/offset)
|
// .ascii "MZ" // Magic 0
|
||||||
|
// j __rv64_real_entry // Jump to real entry (if entered by non-Linux bootloader)
|
||||||
|
// .long 0
|
||||||
|
// .quad 0x200000 // Offset from RAM start
|
||||||
|
// #.quad 0 // Image size (filled by build.zig)
|
||||||
|
// .quad 0x1122334455667788 // Image size (filled by build.zig)
|
||||||
|
// .quad 0 // Kernel flags
|
||||||
|
// .long 0x2 // Header version
|
||||||
|
// .long 0
|
||||||
|
// .quad 0
|
||||||
|
// .ascii "RISCV\x00\x00\x00" // Magic 1
|
||||||
|
// .ascii "RSC\x05" // Magic 2
|
||||||
|
// .long 0
|
||||||
|
// .option pop
|
||||||
|
|
||||||
|
.option push
|
||||||
|
.option norvc
|
||||||
|
__rv64_real_entry:
|
||||||
|
|
||||||
|
// a0 - bootstrap HART ID
|
||||||
|
// a1 - device tree blob physical address
|
||||||
|
auipc s0, 0 // s0 = real PC (also a real load address/offset)
|
||||||
|
mv s1, a0
|
||||||
|
mv s2, a1
|
||||||
|
|
||||||
csrw sie, zero
|
csrw sie, zero
|
||||||
csrw sip, zero
|
csrw sip, zero
|
||||||
@@ -53,6 +76,8 @@ __rv64_entry:
|
|||||||
addi t0, t0, %pcrel_lo(.P06)
|
addi t0, t0, %pcrel_lo(.P06)
|
||||||
|
|
||||||
mv a0, s0
|
mv a0, s0
|
||||||
|
mv a1, s1
|
||||||
|
mv a2, s2
|
||||||
jr t0
|
jr t0
|
||||||
.size __rv64_entry, . - __rv64_entry
|
.size __rv64_entry, . - __rv64_entry
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ fn makeRegister(comptime name: []const u8, comptime bits: type) type {
|
|||||||
set(@bitCast(value));
|
set(@bitCast(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn modify(s: bits, c: bits) void {
|
||||||
|
const v = get();
|
||||||
|
set((v & ~@as(repr, @bitCast(c))) | @as(repr, @bitCast(s)));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read() bits {
|
pub fn read() bits {
|
||||||
return @bitCast(get());
|
return @bitCast(get());
|
||||||
}
|
}
|
||||||
@@ -33,3 +38,20 @@ pub const SATP = makeRegister("satp", packed struct(u64) {
|
|||||||
_,
|
_,
|
||||||
} = .bare
|
} = .bare
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pub const SSTATUS = makeRegister("sstatus", packed struct(u64) {
|
||||||
|
// 0
|
||||||
|
_0: u1 = 0,
|
||||||
|
// 1
|
||||||
|
SIE: bool = false,
|
||||||
|
// 2..8
|
||||||
|
_1: u6 = 0,
|
||||||
|
// 8
|
||||||
|
SPP: bool = false,
|
||||||
|
// 9..18
|
||||||
|
_2: u9 = 0,
|
||||||
|
// 18
|
||||||
|
SUM: bool = false,
|
||||||
|
// 19..64
|
||||||
|
_3: u45 = 0,
|
||||||
|
});
|
||||||
|
|||||||
+80
-33
@@ -1,30 +1,18 @@
|
|||||||
const PhysicalAddress = @import("../../mem.zig").PhysicalAddress;
|
const sync = @import("../../sync.zig");
|
||||||
|
const regs = @import("regs.zig");
|
||||||
|
const mem = @import("../../mem.zig");
|
||||||
|
const arch = @import("../../kernel.zig").arch;
|
||||||
|
|
||||||
|
const PhysicalAddress = mem.PhysicalAddress;
|
||||||
|
|
||||||
pub const KERNEL_VIRTUAL_BASE: usize = 0xFFFFFFF000000000;
|
pub const KERNEL_VIRTUAL_BASE: usize = 0xFFFFFFF000000000;
|
||||||
pub const KERNEL_VIRTUAL_L1I: usize = (KERNEL_VIRTUAL_BASE >> L1.SHIFT) & 511;
|
pub const KERNEL_VIRTUAL_L1I: usize = (KERNEL_VIRTUAL_BASE >> L1.SHIFT) & 511;
|
||||||
|
// 16 GiB
|
||||||
|
const EARLY_MAPPING_SIZE: usize = 16;
|
||||||
|
|
||||||
fn translationLevel(comptime shift: usize) type {
|
pub const L1 = mem.translationLevel(30);
|
||||||
return struct {
|
pub const L2 = mem.translationLevel(21);
|
||||||
pub const SHIFT: usize = shift;
|
pub const L3 = mem.translationLevel(12);
|
||||||
pub const SIZE: usize = 1 << shift;
|
|
||||||
|
|
||||||
pub inline fn index(addr: usize) usize {
|
|
||||||
return (addr >> shift) & 511;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub inline fn offset(addr: usize) usize {
|
|
||||||
return addr & ((1 << shift) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub inline fn address(idx: usize) usize {
|
|
||||||
return idx << shift;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const L1 = translationLevel(30);
|
|
||||||
pub const L2 = translationLevel(21);
|
|
||||||
pub const L3 = translationLevel(12);
|
|
||||||
|
|
||||||
pub const RawEntry = packed struct(u64) {
|
pub const RawEntry = packed struct(u64) {
|
||||||
// 0: Valid
|
// 0: Valid
|
||||||
@@ -96,12 +84,10 @@ pub fn TableEntry(comptime Level: type) type {
|
|||||||
|
|
||||||
pub fn table(addr: PhysicalAddress, flags: RawEntry) @This() {
|
pub fn table(addr: PhysicalAddress, flags: RawEntry) @This() {
|
||||||
flags.clear(.{ .r = true, .w = true, .x = true });
|
flags.clear(.{ .r = true, .w = true, .x = true });
|
||||||
return .{
|
return .{ .raw = flags.makeUnion(.{
|
||||||
.raw = flags.makeUnion(.{
|
.address = @as(u39, @intCast(addr.raw >> 12)),
|
||||||
.address = @as(u39, @intCast(addr.raw >> 12)),
|
.v = true,
|
||||||
.v = true,
|
}) };
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -113,9 +99,7 @@ pub fn Table(comptime Level: type) type {
|
|||||||
entries: [512]Entry align(4096),
|
entries: [512]Entry align(4096),
|
||||||
|
|
||||||
pub fn empty() @This() {
|
pub fn empty() @This() {
|
||||||
return .{
|
return .{ .entries = [_]Entry{.INVALID} ** 512 };
|
||||||
.entries = [_]Entry{.INVALID} ** 512
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn entry(self: *@This(), index: usize) *Entry {
|
pub inline fn entry(self: *@This(), index: usize) *Entry {
|
||||||
@@ -124,4 +108,67 @@ pub fn Table(comptime Level: type) type {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub var fixed = Table(L1).empty();
|
var gFixed = Table(L1).empty();
|
||||||
|
var gFixedLock: sync.IrqSafeSpinlock = .{};
|
||||||
|
|
||||||
|
pub fn virtualizeRange() usize {
|
||||||
|
return EARLY_MAPPING_SIZE * L1.SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmapEarly() void {
|
||||||
|
// Make lower half mappings non-executable
|
||||||
|
gFixedLock.lock();
|
||||||
|
defer gFixedLock.release();
|
||||||
|
for (0..EARLY_MAPPING_SIZE) |i| {
|
||||||
|
gFixed.entry(i).* = .page(
|
||||||
|
.{ .raw = L1.address(i) },
|
||||||
|
.{ .r = true, .w = true },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mapEarly(realAddress: usize) void {
|
||||||
|
const realL1 = L1.index(realAddress);
|
||||||
|
|
||||||
|
// Identity map first 16GiB of memory
|
||||||
|
for (0..EARLY_MAPPING_SIZE) |i| {
|
||||||
|
gFixed.entry(i).* = .page(.{ .raw = L1.address(i) }, .{ .r = true, .w = true, .x = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map 1GiB at KERNEL_VIRTUAL_BASE -> physical 1GiB where the kernel is loaded
|
||||||
|
gFixed.entry(KERNEL_VIRTUAL_L1I).* = .page(.{ .raw = L1.address(realL1) }, .{ .r = true, .w = true, .x = true });
|
||||||
|
|
||||||
|
const address = @as(usize, @intFromPtr(&gFixed));
|
||||||
|
regs.SATP.write(.{ .PPN = @intCast(address >> 12), .MODE = .sv39 });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn flush_vma(page: usize) void {
|
||||||
|
asm volatile ("sfence.vma %[page], zero"
|
||||||
|
:
|
||||||
|
: [page] "r" (page),
|
||||||
|
: "memory"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn flush_vma_asid(page: usize, asid: usize) void {
|
||||||
|
asm volatile ("sfence.vma %[page], %[asid]"
|
||||||
|
:
|
||||||
|
: [page] "r" (page),
|
||||||
|
[asid] "r" (asid),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flush_vma_range(start: usize, end: usize, asid: ?usize) void {
|
||||||
|
var i = L3.align_down(start);
|
||||||
|
const rend = L3.align_up(end);
|
||||||
|
|
||||||
|
if (asid) |a| {
|
||||||
|
while (i < rend) : (i += L3.SIZE) {
|
||||||
|
flush_vma_asid(i, a);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while (i < rend) : (i += L3.SIZE) {
|
||||||
|
flush_vma(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+45
-6
@@ -27,16 +27,55 @@ pub const log = struct {
|
|||||||
writeFn = f;
|
writeFn = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn info(comptime format: []const u8, args: anytype) void {
|
pub inline fn info(comptime format: []const u8, args: anytype) void {
|
||||||
write(.info, format ++ "\r\n", args);
|
write(.info, format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn debug(comptime format: []const u8, args: anytype) void {
|
||||||
|
write(.debug, format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn warn(comptime format: []const u8, args: anytype) void {
|
||||||
|
write(.warn, format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn err(comptime format: []const u8, args: anytype) void {
|
||||||
|
write(.err, format, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn writeRaw(data: []const u8) void {
|
pub fn writeRaw(data: []const u8) void {
|
||||||
writeWrapperFn(void, data);
|
_ = writeWrapperFn(0, data) catch return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(level: Level, comptime format: []const u8, args: anytype) void {
|
pub fn write(comptime level: Level, comptime format: []const u8, args: anytype) void {
|
||||||
_ = level;
|
const prefix = comptime logPrefix(level);
|
||||||
writer.print(format, args) catch return;
|
const suffix = comptime logSuffix(level);
|
||||||
|
writer.print(prefix ++ format ++ suffix ++ "\r\n", args) catch return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn todo(comptime msg: []const u8, args: anytype) noreturn {
|
||||||
|
err("Not yet implemented: " ++ msg, args);
|
||||||
|
@panic("Not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn panic(comptime msg: []const u8, args: anytype) noreturn {
|
||||||
|
err("PANIC: " ++ msg, args);
|
||||||
|
@panic("Explicit kernel panic");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn logPrefix(comptime level: Level) []const u8 {
|
||||||
|
switch (level) {
|
||||||
|
.debug => return "",
|
||||||
|
.info => return "\x1B[1;36m",
|
||||||
|
.warn => return "\x1B[1;33m",
|
||||||
|
.err => return "\x1B[1;31m",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn logSuffix(comptime level: Level) []const u8 {
|
||||||
|
if (level == .debug) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
return "\x1B[0m";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+19
-1
@@ -1,7 +1,25 @@
|
|||||||
// export const _ = @import("arch/riscv64/boot.zig").rv64BspLowerEntry;
|
// export const _ = @import("arch/riscv64/boot.zig").rv64BspLowerEntry;
|
||||||
pub const arch = @import("arch.zig").arch();
|
pub const arch = @import("arch.zig").arch();
|
||||||
pub const mem = @import("mem.zig");
|
pub const mem = @import("mem.zig");
|
||||||
|
pub const debug = @import("debug.zig");
|
||||||
|
|
||||||
|
pub const log = debug.log;
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub export fn kernel_main() callconv(.C) noreturn {
|
||||||
|
log.info("Hello", .{});
|
||||||
|
arch.halt();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, return_address: ?usize) noreturn {
|
||||||
|
_ = error_return_trace;
|
||||||
|
|
||||||
|
const ra = return_address orelse @returnAddress();
|
||||||
|
|
||||||
|
log.err("!!! Kernel panic !!!", .{});
|
||||||
|
log.err(" Reason: {s}", .{ msg });
|
||||||
|
log.err(" At: 0x{x}", .{ ra });
|
||||||
|
|
||||||
export fn kernel_main() callconv(.C) void {
|
|
||||||
arch.halt();
|
arch.halt();
|
||||||
}
|
}
|
||||||
|
|||||||
+21
@@ -1,9 +1,30 @@
|
|||||||
|
pub const translationLevel = @import("mem/vmm.zig").translationLevel;
|
||||||
|
|
||||||
pub const PhysicalAddress = packed struct(u64) {
|
pub const PhysicalAddress = packed struct(u64) {
|
||||||
raw: u64,
|
raw: u64,
|
||||||
|
|
||||||
pub const NULL: @This() = .{ .raw = 0 };
|
pub const NULL: @This() = .{ .raw = 0 };
|
||||||
|
|
||||||
|
pub var gVirtualizeBase: usize = 0;
|
||||||
|
pub var gVirtualizeSize: usize = 0;
|
||||||
|
|
||||||
pub fn add(self: @This(), offset: usize) @This() {
|
pub fn add(self: @This(), offset: usize) @This() {
|
||||||
return .{ .raw = self.raw + @as(u64, @intCast(offset)) };
|
return .{ .raw = self.raw + @as(u64, @intCast(offset)) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn virtualize(self: @This()) usize {
|
||||||
|
if (self.raw > gVirtualizeSize) {
|
||||||
|
@panic("Physical address out of virtualize bounds");
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.raw + gVirtualizeBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_virtualized(virt: usize) @This() {
|
||||||
|
if ((virt < gVirtualizeBase) || (virt - gVirtualizeBase > gVirtualizeSize)) {
|
||||||
|
@panic("Invalid virtualized physical address");
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .raw = virt - gVirtualizeBase };
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
pub fn translationLevel(comptime shift: usize) type {
|
||||||
|
return struct {
|
||||||
|
pub const SHIFT: usize = shift;
|
||||||
|
pub const SIZE: usize = 1 << shift;
|
||||||
|
|
||||||
|
pub inline fn index(addr: usize) usize {
|
||||||
|
return (addr >> shift) & 511;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn offset(addr: usize) usize {
|
||||||
|
return addr & (SIZE - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn address(idx: usize) usize {
|
||||||
|
return idx << shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn align_down(addr: usize) usize {
|
||||||
|
return addr & ~(SIZE - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn align_up(addr: usize) usize {
|
||||||
|
return (addr + SIZE - 1) & ~(SIZE - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
+19
-5
@@ -1,6 +1,20 @@
|
|||||||
pub fn IrqSafeSpinlock(comptime T: type) type {
|
const std = @import("std");
|
||||||
return struct {
|
const arch = @import("kernel.zig").arch;
|
||||||
inner: T,
|
|
||||||
|
|
||||||
};
|
pub const IrqSafeSpinlock = struct {
|
||||||
}
|
state: std.atomic.Value(bool) = .{ .raw = false },
|
||||||
|
|
||||||
|
pub fn tryLock(self: *@This()) bool {
|
||||||
|
return self.state.cmpxchgStrong(false, true, .acquire, .monotonic) orelse false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lock(self: *@This()) void {
|
||||||
|
while (!self.tryLock()) {
|
||||||
|
arch.spinHint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(self: *@This()) void {
|
||||||
|
self.state.store(false, .release);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,244 @@
|
|||||||
|
const mem = @import("../mem.zig");
|
||||||
|
const log = @import("../debug.zig").log;
|
||||||
|
const value = @import("value.zig");
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const fdt_header = extern struct {
|
||||||
|
magic: u32,
|
||||||
|
totalsize: u32,
|
||||||
|
off_dt_struct: u32,
|
||||||
|
off_dt_strings: u32,
|
||||||
|
off_mem_rsvmap: u32,
|
||||||
|
version: u32,
|
||||||
|
last_comp_version: u32,
|
||||||
|
boot_cpuid_phys: u32,
|
||||||
|
size_dt_strings: u32,
|
||||||
|
size_dt_struct: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fdt_op = enum(u32) { FDT_BEGIN_NODE = 1, FDT_END_NODE = 2, FDT_PROP = 3, FDT_NOP = 4, FDT_END = 9, _ };
|
||||||
|
|
||||||
|
const fdt_prop = extern struct {
|
||||||
|
len: u32,
|
||||||
|
nameoff: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
const FdtPropTag = struct {
|
||||||
|
len: u32,
|
||||||
|
nameoff: u32,
|
||||||
|
data: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
const FdtTag = union(enum) {
|
||||||
|
begin_node: []const u8,
|
||||||
|
end_node,
|
||||||
|
prop: FdtPropTag,
|
||||||
|
nop,
|
||||||
|
end,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const FdtNode = struct {
|
||||||
|
fdt: *const Fdt,
|
||||||
|
off: usize,
|
||||||
|
name: []const u8,
|
||||||
|
depth: usize,
|
||||||
|
|
||||||
|
pub fn propIterator(self: *const @This()) FdtNodePropIterator {
|
||||||
|
return .{ .node = self, .tagIter = self.fdt.tagIteratorAt(self.off) };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const FdtNodeProp = struct {
|
||||||
|
node: *const FdtNode,
|
||||||
|
name: []const u8,
|
||||||
|
value: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const FdtNodePropIterator = struct {
|
||||||
|
node: *const FdtNode,
|
||||||
|
tagIter: FdtTagIterator,
|
||||||
|
depth: usize = 0,
|
||||||
|
|
||||||
|
fn next(self: *FdtNodePropIterator) ?FdtNodeProp {
|
||||||
|
while (self.tagIter.next()) |tag| {
|
||||||
|
switch (tag) {
|
||||||
|
.begin_node => |_| {
|
||||||
|
self.depth += 1;
|
||||||
|
},
|
||||||
|
.nop => {},
|
||||||
|
.prop => |prop| {
|
||||||
|
if (self.depth == 0) {
|
||||||
|
const name = self.node.fdt.stringAt(prop.nameoff);
|
||||||
|
return .{
|
||||||
|
.node = self.node,
|
||||||
|
.value = prop.data,
|
||||||
|
.name = name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.end_node => {
|
||||||
|
if (self.depth == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.depth -= 1;
|
||||||
|
},
|
||||||
|
.end => {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const FdtNodeIterator = struct {
|
||||||
|
tagIter: FdtTagIterator,
|
||||||
|
depth: usize = 0,
|
||||||
|
|
||||||
|
fn next(self: *FdtNodeIterator) ?FdtNode {
|
||||||
|
while (self.tagIter.next()) |tag| {
|
||||||
|
switch (tag) {
|
||||||
|
.begin_node => |name| {
|
||||||
|
self.depth += 1;
|
||||||
|
return .{
|
||||||
|
.fdt = self.tagIter.fdt,
|
||||||
|
.off = self.tagIter.off,
|
||||||
|
.name = name,
|
||||||
|
.depth = self.depth - 1,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.end_node => {
|
||||||
|
self.depth -= 1;
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const FdtTagIterator = struct {
|
||||||
|
fdt: *const Fdt,
|
||||||
|
raw: []const u8,
|
||||||
|
off: usize,
|
||||||
|
|
||||||
|
fn next(self: *FdtTagIterator) ?FdtTag {
|
||||||
|
if (self.off >= self.raw.len) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tag: fdt_op = @enumFromInt(value.u32FromBigEndian(@as(*const u32, @ptrCast(@alignCast(&self.raw[self.off]))).*));
|
||||||
|
|
||||||
|
self.off += @sizeOf(u32);
|
||||||
|
|
||||||
|
switch (tag) {
|
||||||
|
.FDT_BEGIN_NODE => {
|
||||||
|
const nameCStr: [*c]const u8 = @ptrCast(self.raw[self.off..]);
|
||||||
|
const nameLength = std.mem.len(nameCStr);
|
||||||
|
const name = self.raw[self.off .. self.off + nameLength];
|
||||||
|
self.off += (nameLength + 4) & ~@as(usize, 3);
|
||||||
|
return .{ .begin_node = name };
|
||||||
|
},
|
||||||
|
.FDT_PROP => {
|
||||||
|
const info: *const fdt_prop = @ptrCast(@alignCast(&self.raw[self.off]));
|
||||||
|
const nameoff = value.u32FromBigEndian(info.nameoff);
|
||||||
|
const len = value.u32FromBigEndian(info.len);
|
||||||
|
self.off += @sizeOf(fdt_prop);
|
||||||
|
const data = self.raw[self.off .. self.off + len];
|
||||||
|
self.off += (len + 3) & ~@as(usize, 3);
|
||||||
|
|
||||||
|
return .{ .prop = .{ .nameoff = nameoff, .len = len, .data = data } };
|
||||||
|
},
|
||||||
|
.FDT_NOP => {
|
||||||
|
return .nop;
|
||||||
|
},
|
||||||
|
.FDT_END => {
|
||||||
|
self.off = self.raw.len;
|
||||||
|
return .end;
|
||||||
|
},
|
||||||
|
.FDT_END_NODE => {
|
||||||
|
return .end_node;
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const FdtError = error{invalid_magic};
|
||||||
|
|
||||||
|
pub const FDT_MAGIC: u32 = 0xD00DFEED;
|
||||||
|
|
||||||
|
pub const Fdt = struct {
|
||||||
|
bytes: []const u8,
|
||||||
|
|
||||||
|
pub fn fromPhysicalAddress(phys: mem.PhysicalAddress) FdtError!@This() {
|
||||||
|
const virt = phys.virtualize();
|
||||||
|
const hdr = @as(*const fdt_header, @ptrFromInt(virt));
|
||||||
|
if (value.u32FromBigEndian(hdr.magic) != FDT_MAGIC) {
|
||||||
|
return error.invalid_magic;
|
||||||
|
}
|
||||||
|
const totalsize = value.u32FromBigEndian(hdr.totalsize);
|
||||||
|
const x = @as([*]const u8, @ptrFromInt(virt));
|
||||||
|
return .{ .bytes = x[0..totalsize] };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn header(self: *const @This()) *const fdt_header {
|
||||||
|
return @ptrCast(@alignCast(&self.bytes[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(self: *const @This()) []const u8 {
|
||||||
|
const off = value.u32FromBigEndian(self.header().off_dt_struct);
|
||||||
|
return self.bytes[off..];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tagIterator(self: *const @This()) FdtTagIterator {
|
||||||
|
return self.tagIteratorAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tagIteratorAt(self: *const @This(), off: usize) FdtTagIterator {
|
||||||
|
return .{ .raw = self.data(), .fdt = self, .off = off };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nodeIterator(self: *const @This()) FdtNodeIterator {
|
||||||
|
return .{ .tagIter = self.tagIterator() };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stringData(self: *const @This()) [*c]const u8 {
|
||||||
|
const offStrings = value.u32FromBigEndian(self.header().off_dt_strings);
|
||||||
|
const sizeStrings = value.u32FromBigEndian(self.header().off_dt_strings);
|
||||||
|
const off = @min(offStrings, self.bytes.len);
|
||||||
|
const len = @min(sizeStrings, self.bytes.len - off);
|
||||||
|
return @ptrCast(self.bytes[off..off + len]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stringAt(self: *const @This(), off: usize) []const u8 {
|
||||||
|
const raw = self.stringData()[off..];
|
||||||
|
const len = std.mem.len(raw);
|
||||||
|
return @ptrCast(raw[0..len]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump(self: *const @This()) void {
|
||||||
|
var nodeIter = self.nodeIterator();
|
||||||
|
while (nodeIter.next()) |node| {
|
||||||
|
for (0..node.depth) |_| {
|
||||||
|
log.writeRaw(" ");
|
||||||
|
}
|
||||||
|
if (node.name.len == 0) {
|
||||||
|
log.info("Root node", .{});
|
||||||
|
} else {
|
||||||
|
log.info("Node {s}", .{ node.name });
|
||||||
|
}
|
||||||
|
var nodePropIter = node.propIterator();
|
||||||
|
while (nodePropIter.next()) |prop| {
|
||||||
|
for (0..node.depth + 1) |_| {
|
||||||
|
log.writeRaw(" ");
|
||||||
|
}
|
||||||
|
log.info("Prop {s}", .{ prop.name });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
pub fn u32FromBigEndian(input: u32) u32 {
|
||||||
|
if (comptime builtin.cpu.arch.endian() == .little) {
|
||||||
|
return @byteSwap(input);
|
||||||
|
} else {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user