towards 0.10.1
This commit is contained in:
parent
e392799b91
commit
08d447115d
17 changed files with 239 additions and 205 deletions
38
build.zig
38
build.zig
|
|
@ -1,4 +1,7 @@
|
|||
const Builder = @import("std").build.Builder;
|
||||
const Target = @import("std").Target;
|
||||
const CrossTarget = @import("std").zig.CrossTarget;
|
||||
const Feature = @import("std").Target.Cpu.Feature;
|
||||
const builtin = @import("builtin");
|
||||
const std = @import("std");
|
||||
|
||||
|
|
@ -6,6 +9,7 @@ pub fn build(b: *Builder) void {
|
|||
const kernel = b.addExecutable("kernel", "src/main.zig");
|
||||
kernel.addPackagePath("kernel", "src/index.zig");
|
||||
kernel.addPackagePath("x86", "src/arch/x86/index.zig");
|
||||
|
||||
kernel.setOutputDir("build");
|
||||
|
||||
kernel.addAssemblyFile("src/arch/x86/start.s");
|
||||
|
|
@ -14,18 +18,28 @@ pub fn build(b: *Builder) void {
|
|||
kernel.addAssemblyFile("src/arch/x86/paging.s");
|
||||
kernel.addAssemblyFile("src/arch/x86/switch_tasks.s");
|
||||
|
||||
// const features = Target.x86.Feature;
|
||||
|
||||
// var disabled_features = Feature.Set.empty;
|
||||
// var enabled_features = Feature.Set.empty;
|
||||
|
||||
// disabled_features.addFeature(@enumToInt(features.mmx));
|
||||
// disabled_features.addFeature(@enumToInt(features.sse));
|
||||
// disabled_features.addFeature(@enumToInt(features.sse2));
|
||||
// disabled_features.addFeature(@enumToInt(features.avx));
|
||||
// disabled_features.addFeature(@enumToInt(features.avx2));
|
||||
// enabled_features.addFeature(@enumToInt(features.soft_float));
|
||||
|
||||
const target = CrossTarget{
|
||||
.cpu_arch = Target.Cpu.Arch.i386,
|
||||
.os_tag = Target.Os.Tag.freestanding,
|
||||
.abi = Target.Abi.none,
|
||||
// .cpu_features_sub = disabled_features,
|
||||
// .cpu_features_add = enabled_features
|
||||
};
|
||||
|
||||
kernel.setTarget(target);
|
||||
kernel.setBuildMode(b.standardReleaseOptions());
|
||||
kernel.setTheTarget(std.Target{
|
||||
.Cross = std.Target.Cross{
|
||||
.arch = std.Target.Arch.i386,
|
||||
.os = std.Target.Os.freestanding,
|
||||
.abi = std.Target.Abi.none,
|
||||
.cpu_features = std.Target.CpuFeatures.initFromCpu(
|
||||
builtin.Arch.i386,
|
||||
&builtin.Target.x86.cpu._i686,
|
||||
),
|
||||
},
|
||||
});
|
||||
kernel.setLinkerScriptPath("src/arch/x86/linker.ld");
|
||||
kernel.setLinkerScriptPath(.{ .path = "src/arch/x86/linker.ld" });
|
||||
b.default_step.dependOn(&kernel.step);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// https://wiki.osdev.org/IDT
|
||||
// usingnamespace @import("kernel");
|
||||
// usingnamespace @import("x86");
|
||||
usingnamespace @import("index.zig");
|
||||
|
||||
const kernel = @import("kernel");
|
||||
const x86 = @import("x86");
|
||||
|
||||
// Types of gates.
|
||||
pub const INTERRUPT_GATE = 0x8E;
|
||||
|
|
@ -38,51 +38,52 @@ const IDTRegister = packed struct {
|
|||
// flags: Type and attributes.
|
||||
// offset: Address of the ISR.
|
||||
//
|
||||
pub fn setGate(n: u8, flags: u8, offset: extern fn () void) void {
|
||||
const intOffset = @ptrToInt(offset);
|
||||
pub fn setGate(n: u8, flags: u8, offset: anytype) void {
|
||||
const intOffset = @ptrToInt(&offset);
|
||||
// const intOffset = offset;
|
||||
|
||||
idt_table[n].offset_low = @truncate(u16, intOffset);
|
||||
idt_table[n].offset_high = @truncate(u16, intOffset >> 16);
|
||||
idt_table[n].flags = flags;
|
||||
idt_table[n].zero = 0;
|
||||
idt_table[n].selector = gdt.KERNEL_CODE;
|
||||
idt_table[n].selector = x86.gdt.KERNEL_CODE;
|
||||
}
|
||||
|
||||
// Initialize the Interrupt Descriptor Table.
|
||||
pub fn initialize() void {
|
||||
// configure PIC
|
||||
interrupt.remapPIC();
|
||||
interrupt.configPIT();
|
||||
x86.interrupt.remapPIC();
|
||||
x86.interrupt.configPIT();
|
||||
// install ISRs
|
||||
isr.install_exceptions();
|
||||
isr.install_irqs();
|
||||
isr.install_syscalls();
|
||||
interrupt.registerIRQ(0, kernel.time.increment);
|
||||
interrupt.registerIRQ(1, kernel.ps2.keyboard_handler);
|
||||
interrupt.register(1, debug_trap);
|
||||
interrupt.register(13, general_protection_fault);
|
||||
interrupt.register(14, page_fault);
|
||||
x86.isr.install_exceptions();
|
||||
x86.isr.install_irqs();
|
||||
x86.isr.install_syscalls();
|
||||
x86.interrupt.registerIRQ(0, kernel.time.increment);
|
||||
x86.interrupt.registerIRQ(1, kernel.ps2.keyboard_handler);
|
||||
x86.interrupt.register(1, debug_trap);
|
||||
x86.interrupt.register(13, general_protection_fault);
|
||||
x86.interrupt.register(14, page_fault);
|
||||
|
||||
// load IDT
|
||||
lidt(@ptrToInt(&idtr));
|
||||
x86.instr.lidt(@ptrToInt(&idtr));
|
||||
}
|
||||
|
||||
fn general_protection_fault() void {
|
||||
kernel.println("general protection fault", .{});
|
||||
hang();
|
||||
x86.instr.hang();
|
||||
}
|
||||
|
||||
fn debug_trap() void {
|
||||
kernel.println("debug fault/trap", .{});
|
||||
kernel.println("dr7: 0b{b}", .{dr7()});
|
||||
kernel.println("dr7: 0b{b}", .{x86.instr.dr7()});
|
||||
}
|
||||
|
||||
fn page_fault() void {
|
||||
const vaddr = cr2();
|
||||
const vaddr = x86.instr.cr2();
|
||||
kernel.println("cr2: 0x{x}", .{vaddr});
|
||||
kernel.println("phy: 0x{x}", .{paging.translate(vaddr)});
|
||||
kernel.println("pde: 0x{x} ({})", .{ paging.pde(vaddr), vaddr >> 22 });
|
||||
kernel.println("pte: 0x{x} ({})", .{ paging.pte(vaddr), vaddr >> 12 });
|
||||
kernel.println("phy: 0x{x}", .{kernel.paging.translate(vaddr)});
|
||||
kernel.println("pde: 0x{x} ({})", .{ kernel.paging.pde(vaddr), vaddr >> 22 });
|
||||
kernel.println("pte: 0x{x} ({})", .{ kernel.paging.pte(vaddr), vaddr >> 12 });
|
||||
// paging.format();
|
||||
hang();
|
||||
x86.instr.hang();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ pub const kernel = @import("../../index.zig");
|
|||
|
||||
// x86 namespace
|
||||
pub const PAGE_SIZE: usize = 4096;
|
||||
pub usingnamespace @import("lib/io.zig");
|
||||
pub usingnamespace @import("lib/instructions.zig");
|
||||
pub const io = @import("lib/io.zig");
|
||||
pub const instr = @import("lib/instructions.zig");
|
||||
pub usingnamespace @import("main.zig");
|
||||
pub const pmem = @import("pmem.zig");
|
||||
pub const paging = @import("paging.zig");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
usingnamespace @import("index.zig");
|
||||
const std = @import("std");
|
||||
const kernel = @import("kernel");
|
||||
const x86 = @import("x86");
|
||||
|
||||
// PIC ports.
|
||||
const PIC1_CMD = 0x20;
|
||||
|
|
@ -28,37 +30,37 @@ const IRQ_15 = IRQ_0 + 15;
|
|||
// Interrupt Vector offsets of syscalls.
|
||||
const SYSCALL = 128;
|
||||
|
||||
// Registered interrupt handlers. (see isr.s)
|
||||
// Registered interrupt handlers. (see x86.isr.s)
|
||||
var handlers = [_]fn () void{unhandled} ** 48;
|
||||
// Registered IRQ subscribers. (see isr.s)
|
||||
// Registered IRQ subscribers. (see x86.isr.s)
|
||||
// var irq_subscribers = []MailboxId{MailboxId.Kernel} ** 16;
|
||||
|
||||
fn unhandled() noreturn {
|
||||
const n = isr.context.interrupt_n;
|
||||
kernel.print("unhandled interrupt number {d}", .{n});
|
||||
if (n < IRQ_0) kernel.println(" (exception)", .{});
|
||||
if (n >= IRQ_0) kernel.println(" (IRQ number {d})", .{n - IRQ_0});
|
||||
hang();
|
||||
const n = x86.isr.context.interrupt_n;
|
||||
kernel.vga.print("unhandled interrupt number {d}", .{n});
|
||||
if (n < IRQ_0) kernel.vga.println(" (exception)", .{});
|
||||
if (n >= IRQ_0) kernel.vga.println(" (IRQ number {d})", .{n - IRQ_0});
|
||||
x86.instr.hang();
|
||||
}
|
||||
|
||||
inline fn picwait() void {
|
||||
outb(WAIT_PORT, 0);
|
||||
x86.io.outb(WAIT_PORT, 0);
|
||||
}
|
||||
|
||||
////
|
||||
// Call the correct handler based on the interrupt number.
|
||||
//
|
||||
export fn interruptDispatch() void {
|
||||
const n = @intCast(u8, isr.context.interrupt_n);
|
||||
const n = @intCast(u8, x86.isr.context.interrupt_n);
|
||||
|
||||
switch (n) {
|
||||
// Exceptions.
|
||||
EXCEPTION_0...EXCEPTION_31 => {
|
||||
kernel.println("", .{});
|
||||
kernel.println("num: {}", .{n});
|
||||
kernel.println("err: {}", .{@truncate(u8, isr.context.error_code)});
|
||||
kernel.println("ip: 0x{x}", .{@truncate(u16, isr.context.eip)});
|
||||
kernel.println("ip: 0x{x}", .{@truncate(u16, isr.context.eip >> 16)});
|
||||
kernel.vga.println("", .{});
|
||||
kernel.vga.println("num: {}", .{n});
|
||||
kernel.vga.println("err: {}", .{@truncate(u8, x86.isr.context.error_code)});
|
||||
kernel.vga.println("ip: 0x{x}", .{@truncate(u16, x86.isr.context.eip)});
|
||||
kernel.vga.println("ip: 0x{x}", .{@truncate(u16, x86.isr.context.eip >> 16)});
|
||||
return handlers[n]();
|
||||
},
|
||||
|
||||
|
|
@ -74,7 +76,7 @@ export fn interruptDispatch() void {
|
|||
|
||||
// Syscalls.
|
||||
// SYSCALL => {
|
||||
// const syscall_n = isr.context.registers.eax;
|
||||
// const syscall_n = x86.isr.context.registers.eax;
|
||||
// if (syscall_n < syscall.handlers.len) {
|
||||
// syscall.handlers[syscall_n]();
|
||||
// } else {
|
||||
|
|
@ -98,8 +100,8 @@ inline fn spuriousIRQ(irq: u8) bool {
|
|||
// TODO: handle spurious IRQ15.
|
||||
|
||||
// Read the value of the In-Service Register.
|
||||
outb(PIC1_CMD, ISR_READ);
|
||||
const in_service = inb(PIC1_CMD);
|
||||
x86.io.outb(PIC1_CMD, ISR_READ);
|
||||
const in_service = x86.io.inb(PIC1_CMD);
|
||||
|
||||
// Verify whether IRQ7 is set in the ISR.
|
||||
return (in_service & (1 << 7)) == 0;
|
||||
|
|
@ -109,11 +111,11 @@ inline fn startOfInterrupt(irq: u8) void {
|
|||
// mask the irq and then ACK
|
||||
if (irq >= 8) {
|
||||
maskIRQ(irq, true);
|
||||
outb(PIC1_CMD, ACK);
|
||||
outb(PIC2_CMD, ACK);
|
||||
x86.io.outb(PIC1_CMD, ACK);
|
||||
x86.io.outb(PIC2_CMD, ACK);
|
||||
} else {
|
||||
maskIRQ(irq, true);
|
||||
outb(PIC1_CMD, ACK);
|
||||
x86.io.outb(PIC1_CMD, ACK);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -121,10 +123,10 @@ inline fn endOfInterrupt(irq: u8) void {
|
|||
// unmask the irq and then ACK
|
||||
if (irq >= 8) {
|
||||
maskIRQ(irq, false);
|
||||
outb(PIC2_CMD, ACK);
|
||||
x86.io.outb(PIC2_CMD, ACK);
|
||||
} else {
|
||||
maskIRQ(irq, false);
|
||||
outb(PIC1_CMD, ACK);
|
||||
x86.io.outb(PIC1_CMD, ACK);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -139,33 +141,33 @@ pub fn registerIRQ(irq: u8, handler: fn () void) void {
|
|||
|
||||
pub fn remapPIC() void {
|
||||
// ICW1: start initialization sequence.
|
||||
outb(PIC1_CMD, ICW1_INIT | ICW1_ICW4);
|
||||
x86.io.outb(PIC1_CMD, ICW1_INIT | ICW1_ICW4);
|
||||
picwait();
|
||||
outb(PIC2_CMD, ICW1_INIT | ICW1_ICW4);
|
||||
x86.io.outb(PIC2_CMD, ICW1_INIT | ICW1_ICW4);
|
||||
picwait();
|
||||
|
||||
// ICW2: Interrupt Vector offsets of IRQs.
|
||||
outb(PIC1_DATA, IRQ_0); // IRQ 0..7 -> Interrupt 32..39
|
||||
x86.io.outb(PIC1_DATA, IRQ_0); // IRQ 0..7 -> Interrupt 32..39
|
||||
picwait();
|
||||
outb(PIC2_DATA, IRQ_0 + 8); // IRQ 8..15 -> Interrupt 40..47
|
||||
x86.io.outb(PIC2_DATA, IRQ_0 + 8); // IRQ 8..15 -> Interrupt 40..47
|
||||
picwait();
|
||||
|
||||
// ICW3: IRQ line 2 to connect master to slave PIC.
|
||||
outb(PIC1_DATA, 1 << 2);
|
||||
x86.io.outb(PIC1_DATA, 1 << 2);
|
||||
picwait();
|
||||
outb(PIC2_DATA, 2);
|
||||
x86.io.outb(PIC2_DATA, 2);
|
||||
picwait();
|
||||
|
||||
// ICW4: 80x86 mode.
|
||||
outb(PIC1_DATA, ICW4_8086);
|
||||
x86.io.outb(PIC1_DATA, ICW4_8086);
|
||||
picwait();
|
||||
outb(PIC2_DATA, ICW4_8086);
|
||||
x86.io.outb(PIC2_DATA, ICW4_8086);
|
||||
picwait();
|
||||
|
||||
// Mask all IRQs.
|
||||
outb(PIC1_DATA, 0xFF);
|
||||
x86.io.outb(PIC1_DATA, 0xFF);
|
||||
picwait();
|
||||
outb(PIC2_DATA, 0xFF);
|
||||
x86.io.outb(PIC2_DATA, 0xFF);
|
||||
picwait();
|
||||
}
|
||||
|
||||
|
|
@ -173,15 +175,16 @@ pub fn maskIRQ(irq: u8, comptime mask: bool) void {
|
|||
if (irq > 15) return;
|
||||
// Figure out if master or slave PIC owns the IRQ.
|
||||
const port = @as(u16, if (irq < 8) PIC1_DATA else PIC2_DATA);
|
||||
const old = inb(port); // Retrieve the current mask.
|
||||
const old = x86.io.inb(port); // Retrieve the current mask.
|
||||
|
||||
// Mask or unmask the interrupt.
|
||||
const shift = @truncate(u3, irq % 8);
|
||||
// const shift = @truncate(u3, if (irq < 8) irq else irq - 8);
|
||||
const bit = @as(u8, 1) << shift;
|
||||
if (mask) outb(port, old | bit);
|
||||
if (!mask) outb(port, old & ~bit);
|
||||
const new = inb(port); // Retrieve the current mask.
|
||||
if (mask) x86.io.outb(port, old | bit);
|
||||
if (!mask) x86.io.outb(port, old & ~bit);
|
||||
// TODO uncomment
|
||||
// const new = x86.io.inb(port); // Retrieve the current mask.
|
||||
}
|
||||
|
||||
// configures the chan0 with a rate generator, which will trigger irq0
|
||||
|
|
@ -189,10 +192,11 @@ pub const divisor = 2685;
|
|||
pub const tick = 2251; // f = 1.193182 MHz, TODO: turn into a function
|
||||
pub fn configPIT() void {
|
||||
const chanNum = 0;
|
||||
const chan = PIT_CHAN0;
|
||||
// const chan = PIT_CHAN0;
|
||||
const LOHI = 0b11; // bit4 | bit5
|
||||
const PITMODE_RATE_GEN = 0x2;
|
||||
outb(PIT_CMD, chanNum << 6 | LOHI << 4 | PITMODE_RATE_GEN << 1);
|
||||
outb(PIT_CHAN0, divisor & 0xff);
|
||||
outb(PIT_CHAN0, divisor >> 8);
|
||||
|
||||
x86.io.outb(PIT_CMD, chanNum << 6 | LOHI << 4 | PITMODE_RATE_GEN << 1);
|
||||
x86.io.outb(PIT_CHAN0, divisor & 0xff);
|
||||
x86.io.outb(PIT_CHAN0, divisor >> 8);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
usingnamespace @import("index.zig");
|
||||
const idt = @import("idt.zig");
|
||||
|
||||
// Interrupt Service Routines defined externally in assembly.
|
||||
extern fn isr0() void;
|
||||
|
|
@ -65,9 +65,9 @@ pub const Context = packed struct {
|
|||
esp: u32,
|
||||
ss: u32,
|
||||
|
||||
pub inline fn setReturnValue(self: *volatile Context, value: var) void {
|
||||
self.registers.eax = if (@TypeOf(value) == bool) @boolToInt(value) else @intCast(u32, value);
|
||||
}
|
||||
// pub inline fn setReturnValue(self: *volatile Context, value: var) void {
|
||||
// self.registers.eax = if (@TypeOf(value) == bool) @boolToInt(value) else @intCast(u32, value);
|
||||
// }
|
||||
};
|
||||
|
||||
// Structure holding general purpose registers as saved by PUSHA.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
pub inline fn ltr(desc: u16) void {
|
||||
asm volatile ("ltr %[desc]"
|
||||
:
|
||||
: [desc] "r" (desc)
|
||||
: [desc] "r" (desc),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -30,18 +30,18 @@ pub inline fn int3() void {
|
|||
pub inline fn lidt(idtr: usize) void {
|
||||
asm volatile ("lidt (%[idtr])"
|
||||
:
|
||||
: [idtr] "r" (idtr)
|
||||
: [idtr] "r" (idtr),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn cr2() usize {
|
||||
return asm volatile ("movl %%cr2, %[result]"
|
||||
: [result] "=r" (-> usize)
|
||||
: [result] "=r" (-> usize),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn dr7() usize {
|
||||
return asm volatile ("movl %%dr7, %[result]"
|
||||
: [result] "=r" (-> usize)
|
||||
: [result] "=r" (-> usize),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,32 +2,32 @@ usingnamespace @import("../index.zig");
|
|||
|
||||
pub inline fn inb(port: u16) u8 {
|
||||
return asm volatile ("inb %[port], %[result]"
|
||||
: [result] "={al}" (-> u8)
|
||||
: [port] "N{dx}" (port)
|
||||
: [result] "={al}" (-> u8),
|
||||
: [port] "N{dx}" (port),
|
||||
);
|
||||
}
|
||||
|
||||
pub inline fn inw(port: u16) u16 {
|
||||
return asm volatile ("inw %[port], %[result]"
|
||||
: [result] "={ax}" (-> u16)
|
||||
: [port] "N{dx}" (port)
|
||||
: [result] "={ax}" (-> u16),
|
||||
: [port] "N{dx}" (port),
|
||||
);
|
||||
}
|
||||
|
||||
pub inline fn inl(port: u16) u32 {
|
||||
return asm volatile ("inl %[port], %[result]"
|
||||
: [result] "={eax}" (-> u32)
|
||||
: [port] "N{dx}" (port)
|
||||
: [result] "={eax}" (-> u32),
|
||||
: [port] "N{dx}" (port),
|
||||
);
|
||||
}
|
||||
|
||||
pub inline fn insl(port: u16, addr: var, cnt: usize) void {
|
||||
pub inline fn insl(port: u16, addr: anytype, cnt: usize) void {
|
||||
asm volatile ("cld; repne; insl;"
|
||||
: [addr] "={edi}" (addr),
|
||||
[cnt] "={ecx}" (cnt)
|
||||
[cnt] "={ecx}" (cnt),
|
||||
: [port] "{dx}" (port),
|
||||
[addr] "0" (addr),
|
||||
[cnt] "1" (cnt)
|
||||
[cnt] "1" (cnt),
|
||||
: "memory", "cc"
|
||||
);
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ pub inline fn outb(port: u16, value: u8) void {
|
|||
asm volatile ("outb %[value], %[port]"
|
||||
:
|
||||
: [value] "{al}" (value),
|
||||
[port] "N{dx}" (port)
|
||||
[port] "N{dx}" (port),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ pub inline fn outw(port: u16, value: u16) void {
|
|||
asm volatile ("outw %[value], %[port]"
|
||||
:
|
||||
: [value] "{ax}" (value),
|
||||
[port] "N{dx}" (port)
|
||||
[port] "N{dx}" (port),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -52,6 +52,6 @@ pub inline fn outl(port: u16, value: u32) void {
|
|||
asm volatile ("outl %[value], %[port]"
|
||||
:
|
||||
: [value] "{eax}" (value),
|
||||
[port] "N{dx}" (port)
|
||||
[port] "N{dx}" (port),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
usingnamespace @import("index.zig");
|
||||
const std = @import("std");
|
||||
const kernel = @import("kernel");
|
||||
const x86 = @import("x86");
|
||||
|
||||
/// x86 specific intialization
|
||||
pub fn x86_main(info: *const kernel.multiboot.MultibootInfo) void {
|
||||
gdt.initialize();
|
||||
idt.initialize();
|
||||
pmem.initialize(info);
|
||||
paging.initialize();
|
||||
sti();
|
||||
x86.gdt.initialize();
|
||||
x86.idt.initialize();
|
||||
x86.pmem.initialize(info);
|
||||
x86.paging.initialize();
|
||||
x86.instr.sti();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
usingnamespace @import("index.zig");
|
||||
const std = @import("std");
|
||||
const kernel = @import("kernel");
|
||||
const x86 = @import("x86");
|
||||
|
||||
extern fn setupPaging(phys_pd: usize) void;
|
||||
|
||||
|
|
@ -17,7 +19,7 @@ pub var pageDirectory: [1024]PageEntry align(4096) linksection(".bss") = [_]Page
|
|||
|
||||
// TODO: inline these
|
||||
fn pageBase(virt: usize) usize {
|
||||
return virt & (~PAGE_SIZE +% 1);
|
||||
return virt & (~x86.PAGE_SIZE +% 1);
|
||||
}
|
||||
pub fn pde(virt: usize) *PageEntry {
|
||||
return &PD[virt >> 22]; //relies on recursive mapping
|
||||
|
|
@ -34,18 +36,18 @@ pub fn translate(virt: usize) ?usize {
|
|||
|
||||
pub fn unmap(virt: usize) void {
|
||||
if (translate(virt)) |phys| {
|
||||
pmem.free(phys);
|
||||
x86.pmem.free(phys);
|
||||
} else {
|
||||
kernel.println("can't unmap 0x{x} because it is not mapped.", .{virt});
|
||||
kernel.vga.println("can't unmap 0x{x} because it is not mapped.", .{virt});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mmap(virt: usize, phys: ?usize) !void {
|
||||
//TODO: support hugepages
|
||||
// allocate a page directory if there is none
|
||||
if (pde(virt).* == 0) pde(virt).* = (try pmem.allocate()) | WRITE | PRESENT;
|
||||
if (pde(virt).* == 0) pde(virt).* = (try x86.pmem.allocate()) | WRITE | PRESENT;
|
||||
// allocate a frame if phys isn't specified
|
||||
pte(virt).* = (if (phys) |p| p else try pmem.allocate()) | PRESENT;
|
||||
pte(virt).* = (if (phys) |p| p else try x86.pmem.allocate()) | PRESENT;
|
||||
}
|
||||
|
||||
pub fn initialize() void {
|
||||
|
|
@ -57,7 +59,7 @@ pub fn initialize() void {
|
|||
p2[1023] = @ptrToInt(&p2[0]) | PRESENT | WRITE;
|
||||
|
||||
// TODO: verify is this a hack?
|
||||
assert(pmem.stack_end < kernel.layout.IDENTITY);
|
||||
std.debug.assert(x86.pmem.stack_end < kernel.layout.IDENTITY);
|
||||
|
||||
setupPaging(@ptrToInt(&pageDirectory[0])); //asm routine
|
||||
}
|
||||
|
|
@ -67,12 +69,12 @@ pub fn format() void {
|
|||
i = 0;
|
||||
while (i < 1024) : (i += 1) {
|
||||
if (PD[i] == 0) continue;
|
||||
kernel.println("p2[{}] -> 0x{x}", .{ i, PD[i] });
|
||||
kernel.vga.println("p2[{}] -> 0x{x}", .{ i, PD[i] });
|
||||
if (PD[i] & HUGE != 0) continue;
|
||||
var j: usize = 0;
|
||||
while (j < 1024) : (j += 1) {
|
||||
var entry: PageEntry = PT[i * 1024 + j];
|
||||
if (entry != 0) kernel.println("p2[{}]p1[{}] -> 0x{x}", .{ i, j, entry });
|
||||
if (entry != 0) kernel.vga.println("p2[{}]p1[{}] -> 0x{x}", .{ i, j, entry });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
usingnamespace @import("index.zig");
|
||||
const std = @import("std");
|
||||
const kernel = @import("kernel");
|
||||
const x86 = @import("x86");
|
||||
|
||||
var stack: [*]usize = undefined; // Stack of free physical page.
|
||||
var stack_index: usize = 0; // Index into the stack.
|
||||
|
|
@ -11,13 +13,13 @@ pub inline fn pageAlign(address: u32) u32 {
|
|||
// 4095 -> 4096
|
||||
// 4096 -> 4096
|
||||
// 4097 -> 8192
|
||||
return (address + PAGE_SIZE - 1) & (~PAGE_SIZE +% 1);
|
||||
return (address + x86.PAGE_SIZE - 1) & (~x86.PAGE_SIZE +% 1);
|
||||
}
|
||||
|
||||
// Return the amount of variable elements (in bytes).
|
||||
//
|
||||
pub fn available() usize {
|
||||
return stack_index * PAGE_SIZE;
|
||||
return stack_index * x86.PAGE_SIZE;
|
||||
}
|
||||
|
||||
pub inline fn available_MiB() usize {
|
||||
|
|
@ -52,8 +54,8 @@ pub fn free(address: usize) void {
|
|||
//
|
||||
pub fn initialize(info: *const kernel.multiboot.MultibootInfo) void {
|
||||
// Ensure the bootloader has given us the memory map.
|
||||
assert((info.flags & kernel.multiboot.MULTIBOOT_INFO_MEMORY) != 0);
|
||||
assert((info.flags & kernel.multiboot.MULTIBOOT_INFO_MEM_MAP) != 0);
|
||||
std.debug.assert((info.flags & kernel.multiboot.MULTIBOOT_INFO_MEMORY) != 0);
|
||||
std.debug.assert((info.flags & kernel.multiboot.MULTIBOOT_INFO_MEM_MAP) != 0);
|
||||
|
||||
// TODO: WHAT WHY WHAAAAT, must check back here later
|
||||
// Place stack at 0x200000 so that in the future I trigger a
|
||||
|
|
@ -64,7 +66,7 @@ pub fn initialize(info: *const kernel.multiboot.MultibootInfo) void {
|
|||
// stack = @intToPtr([*]usize, pageAlign(info.mods_addr));
|
||||
|
||||
// Calculate the approximate size of the stack based on the amount of total upper memory.
|
||||
stack_size = ((info.mem_upper * 1024) / PAGE_SIZE) * @sizeOf(usize);
|
||||
stack_size = ((info.mem_upper * 1024) / x86.PAGE_SIZE) * @sizeOf(usize);
|
||||
stack_end = pageAlign(@ptrToInt(stack) + stack_size);
|
||||
|
||||
var map: usize = info.mmap_addr;
|
||||
|
|
@ -78,17 +80,16 @@ pub fn initialize(info: *const kernel.multiboot.MultibootInfo) void {
|
|||
start = if (start >= stack_end) start else stack_end;
|
||||
|
||||
// Flag all the pages in this memory area as free.
|
||||
if (entry.type == kernel.multiboot.MULTIBOOT_MEMORY_AVAILABLE) while (start < end) : (start += PAGE_SIZE)
|
||||
if (entry.type == kernel.multiboot.MULTIBOOT_MEMORY_AVAILABLE) while (start < end) : (start += x86.PAGE_SIZE)
|
||||
free(start);
|
||||
|
||||
// Go to the next entry in the memory map.
|
||||
map += entry.size + @sizeOf(@TypeOf(entry.size));
|
||||
}
|
||||
|
||||
const a = available();
|
||||
kernel.println("available memory: {d} MiB ", .{available() / 1024 / 1024});
|
||||
kernel.vga.println("available memory: {d} MiB ", .{available() / 1024 / 1024});
|
||||
}
|
||||
|
||||
pub fn format() void {
|
||||
kernel.println("physframes left: {d} ({d} MiB)", .{ stack_index, available_MiB() });
|
||||
kernel.vga.println("physframes left: {d} ({d} MiB)", .{ stack_index, available_MiB() });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
/// DeltaQueue is a singly-linked list where each
|
||||
/// node has a counter. Each counter is relative
|
||||
|
|
|
|||
|
|
@ -1,24 +1,25 @@
|
|||
pub usingnamespace @import("common.zig");
|
||||
pub usingnamespace @import("delta_queue.zig");
|
||||
pub usingnamespace @import("ring_buffer.zig");
|
||||
pub usingnamespace @import("vga.zig");
|
||||
// pub usingnamespace @import("common.zig");
|
||||
// pub usingnamespace @import("delta_queue.zig");
|
||||
// pub usingnamespace @import("ring_buffer.zig");
|
||||
|
||||
///drivers
|
||||
pub const driver = @import("driver/index.zig");
|
||||
pub const vga = @import("vga.zig");
|
||||
|
||||
///arch
|
||||
//drivers
|
||||
// pub const driver = @import("driver/index.zig");
|
||||
|
||||
//arch
|
||||
pub const x86 = @import("arch/x86/index.zig");
|
||||
|
||||
///core
|
||||
//core
|
||||
pub const constants = @import("constants.zig");
|
||||
pub const layout = @import("layout.zig");
|
||||
pub const multiboot = @import("multiboot.zig");
|
||||
pub const vmem = @import("vmem.zig");
|
||||
pub const task = @import("task.zig");
|
||||
// pub const task = @import("task.zig");
|
||||
pub const time = @import("time.zig");
|
||||
|
||||
///extra
|
||||
pub const console = @import("console.zig");
|
||||
pub const bio = @import("bio.zig");
|
||||
pub const pci = @import("pci/pci.zig");
|
||||
pub const ps2 = @import("ps2.zig");
|
||||
//extra
|
||||
// pub const console = @import("console.zig");
|
||||
// pub const bio = @import("bio.zig");
|
||||
// pub const pci = @import("pci/pci.zig");
|
||||
// pub const ps2 = @import("ps2.zig");
|
||||
|
|
|
|||
54
src/main.zig
54
src/main.zig
|
|
@ -1,14 +1,16 @@
|
|||
usingnamespace @import("kernel");
|
||||
const std = @import("std");
|
||||
const builtin = std.builtin;
|
||||
const kernel = @import("index.zig");
|
||||
|
||||
// Place the header at the very beginning of the binary.
|
||||
export const multiboot_header align(4) linksection(".multiboot") = multiboot: {
|
||||
const MAGIC = @as(u32, 0x1BADB002); // multiboot magic
|
||||
const ALIGN = @as(u32, 1 << 0); // Align loaded modules at 4k
|
||||
const MEMINFO = @as(u32, 1 << 1); // Receive a memory map from the bootloader.
|
||||
const ADDR = @as(u32, 1 << 16); // Load specific addr
|
||||
// const ADDR = @as(u32, 1 << 16); // Load specific addr
|
||||
const FLAGS = ALIGN | MEMINFO; // Combine the flags.
|
||||
|
||||
break :multiboot multiboot.MultibootHeader{
|
||||
break :multiboot kernel.multiboot.MultibootHeader{
|
||||
.magic = MAGIC,
|
||||
.flags = FLAGS,
|
||||
.checksum = ~(MAGIC +% FLAGS) +% 1,
|
||||
|
|
@ -16,32 +18,32 @@ export const multiboot_header align(4) linksection(".multiboot") = multiboot: {
|
|||
};
|
||||
|
||||
// arch independant initialization
|
||||
export fn kmain(magic: u32, info: *const multiboot.MultibootInfo) noreturn {
|
||||
assert(magic == multiboot.MULTIBOOT_BOOTLOADER_MAGIC);
|
||||
clear();
|
||||
println("--- x86 initialization ---", .{});
|
||||
x86.x86_main(info);
|
||||
println("--- core initialization ---", .{});
|
||||
vmem.init();
|
||||
pci.scan();
|
||||
println("--- finished booting --- ", .{});
|
||||
export fn kmain(magic: u32, info: *const kernel.multiboot.MultibootInfo) noreturn {
|
||||
std.debug.assert(magic == kernel.multiboot.MULTIBOOT_BOOTLOADER_MAGIC);
|
||||
kernel.vga.clear();
|
||||
kernel.vga.println("--- x86 initialization ---", .{});
|
||||
kernel.x86.x86_main(info);
|
||||
kernel.vga.println("--- core initialization ---", .{});
|
||||
kernel.vmem.init();
|
||||
kernel.pci.scan();
|
||||
kernel.vga.println("--- finished booting --- ", .{});
|
||||
|
||||
task.cleaner_task = task.new(@ptrToInt(task.cleaner_loop)) catch unreachable;
|
||||
_ = task.new(@ptrToInt(topbar)) catch unreachable;
|
||||
_ = task.new(@ptrToInt(console.loop)) catch unreachable;
|
||||
// kernel.task.cleaner_task = kernel.task.new(@ptrToInt(kernel.task.cleaner_loop)) catch unreachable;
|
||||
// _ = kernel.task.new(@ptrToInt(kernel.vga.topbar)) catch unreachable;
|
||||
// _ = kernel.task.new(@ptrToInt(kernel.console.loop)) catch unreachable;
|
||||
|
||||
var buf = vmem.allocator.create([512]u8) catch unreachable;
|
||||
println("buf at 0x{x}", .{@ptrToInt(buf)});
|
||||
driver.ide.first_ide_drive.read(2, buf);
|
||||
// var buf = kernel.vmem.allocator.create([512]u8) catch unreachable;
|
||||
// kernel.vga.println("buf at 0x{x}", .{@ptrToInt(buf)});
|
||||
// kernel.driver.ide.first_ide_drive.read(2, buf);
|
||||
|
||||
const sig = buf[56..58];
|
||||
println("sig: {x}", .{sig});
|
||||
// const sig = buf[56..58];
|
||||
// kernel.vga.println("sig: {x}", .{sig});
|
||||
|
||||
task.terminate();
|
||||
// kernel.task.terminate();
|
||||
}
|
||||
|
||||
pub fn panic(a: []const u8, b: ?*builtin.StackTrace) noreturn {
|
||||
println("{}", .{a});
|
||||
println("{}", .{b});
|
||||
while (true) asm volatile ("hlt");
|
||||
}
|
||||
// pub fn panic(a: []const u8, b: ?*builtin.StackTrace) noreturn {
|
||||
// kernel.vga.println("{}", .{a});
|
||||
// kernel.vga.println("{}", .{b});
|
||||
// while (true) asm volatile ("hlt");
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -29,16 +29,16 @@ pub const MultibootInfo = packed struct {
|
|||
mods_count: u32,
|
||||
mods_addr: u32,
|
||||
|
||||
syms: extern union {
|
||||
syms: packed union {
|
||||
// present if flags[4]
|
||||
nlist: extern struct {
|
||||
nlist: packed struct {
|
||||
tabsize: u32,
|
||||
strsize: u32,
|
||||
addr: u32,
|
||||
_reserved: u32,
|
||||
},
|
||||
// present if flags[5]
|
||||
shdr: extern struct {
|
||||
shdr: packed struct {
|
||||
num: u32,
|
||||
size: u32,
|
||||
addr: u32,
|
||||
|
|
@ -124,7 +124,7 @@ pub const MultibootModule = packed struct {
|
|||
};
|
||||
|
||||
// Multiboot structure to be read by the bootloader.
|
||||
pub const MultibootHeader = packed struct {
|
||||
pub const MultibootHeader = extern struct {
|
||||
magic: u32, // Must be equal to header magic number.
|
||||
flags: u32, // Feature flags.
|
||||
checksum: u32, // Above fields plus this one must equal 0 mod 2^32.
|
||||
|
|
|
|||
11
src/time.zig
11
src/time.zig
|
|
@ -1,4 +1,5 @@
|
|||
usingnamespace @import("index.zig");
|
||||
const kernel = @import("index.zig");
|
||||
const x86 = @import("x86");
|
||||
|
||||
pub var offset_us: u64 = 0;
|
||||
pub var task_slice_remaining: u64 = 0;
|
||||
|
|
@ -8,14 +9,14 @@ pub fn increment() void {
|
|||
|
||||
offset_us += tick; //global time counter
|
||||
|
||||
var should_preempt = task.wakeup_tick(tick);
|
||||
var should_preempt = kernel.task.wakeup_tick(tick);
|
||||
|
||||
if (task_slice_remaining != 0) {
|
||||
// There is a time slice length
|
||||
if (task_slice_remaining <= tick) should_preempt = true;
|
||||
if (task_slice_remaining > tick) task_slice_remaining -= tick;
|
||||
}
|
||||
if (should_preempt) task.preempt();
|
||||
if (should_preempt) kernel.task.preempt();
|
||||
}
|
||||
|
||||
pub fn uptime() void {
|
||||
|
|
@ -23,9 +24,9 @@ pub fn uptime() void {
|
|||
const offset_s: u64 = offset_ms / 1000;
|
||||
offset_ms = @mod(offset_ms / 100, 10);
|
||||
|
||||
print("{}.{:.3}", .{ offset_s, offset_ms });
|
||||
kernel.vga.print("{}.{:.3}", .{ offset_s, offset_ms });
|
||||
}
|
||||
|
||||
pub fn utilisation() void {
|
||||
print("{}%", .{100 * (offset_us - task.CPU_idle_time) / offset_us});
|
||||
kernel.vga.print("{}%", .{100 * (offset_us - kernel.task.CPU_idle_time) / offset_us});
|
||||
}
|
||||
|
|
|
|||
52
src/vga.zig
52
src/vga.zig
|
|
@ -1,7 +1,6 @@
|
|||
usingnamespace @import("index.zig");
|
||||
// const time = @import("time.zig");
|
||||
// const x86 = @import("arch/x86/index.zig");
|
||||
// const std = @import("std");
|
||||
const std = @import("std");
|
||||
const kernel = @import("index.zig");
|
||||
|
||||
// Screen size.
|
||||
pub const VGA_WIDTH = 80;
|
||||
pub const VGA_HEIGHT = 25;
|
||||
|
|
@ -42,23 +41,26 @@ pub const VGAEntry = packed struct {
|
|||
|
||||
// Enable hardware cursor.
|
||||
pub fn enableCursor() void {
|
||||
outb(0x3D4, 0x0A);
|
||||
outb(0x3D5, 0x00);
|
||||
kernel.x86.io.outb(0x3D4, 0x0A);
|
||||
kernel.x86.io.outb(0x3D5, 0x00);
|
||||
}
|
||||
|
||||
// Disable hardware cursor.
|
||||
pub fn disableCursor() void {
|
||||
outb(0x3D4, 0x0A);
|
||||
outb(0x3D5, 1 << 5);
|
||||
kernel.x86.io.outb(0x3D4, 0x0A);
|
||||
kernel.x86.io.outb(0x3D5, 1 << 5);
|
||||
}
|
||||
|
||||
const Errors = error{};
|
||||
pub fn print(comptime format: []const u8, args: var) void {
|
||||
var a = std.fmt.format({}, Errors, printCallback, format, args);
|
||||
pub fn print(comptime format: []const u8, args: anytype) void {
|
||||
try std.fmt.format(.{ .writeAll = printCallback }, format, args);
|
||||
}
|
||||
pub fn println(comptime format: []const u8, args: var) void {
|
||||
var a = print(format ++ "\n", args);
|
||||
|
||||
pub fn println(comptime format: []const u8, args: anytype) void {
|
||||
print(format ++ "\n", args);
|
||||
}
|
||||
|
||||
// const time = @import("time.zig");
|
||||
pub fn clear() void {
|
||||
vga.clear();
|
||||
}
|
||||
|
|
@ -73,11 +75,11 @@ pub fn topbar() void {
|
|||
vga.cursor = 0;
|
||||
vga.cursor_enabled = false;
|
||||
|
||||
time.uptime();
|
||||
kernel.time.uptime();
|
||||
print(" | ", .{});
|
||||
time.utilisation();
|
||||
kernel.time.utilisation();
|
||||
print(" | ", .{});
|
||||
task.format_short();
|
||||
kernel.task.format_short();
|
||||
println("", .{});
|
||||
|
||||
vga.cursor_enabled = true;
|
||||
|
|
@ -85,11 +87,11 @@ pub fn topbar() void {
|
|||
vga.background = bg;
|
||||
vga.foreground = fg;
|
||||
|
||||
task.usleep(50 * 1000) catch unreachable; // 60ms
|
||||
kernel.task.usleep(50 * 1000) catch unreachable; // 60ms
|
||||
}
|
||||
}
|
||||
|
||||
fn printCallback(context: void, string: []const u8) Errors!void {
|
||||
fn printCallback(string: []const u8) Errors!void {
|
||||
vga.writeString(string);
|
||||
}
|
||||
|
||||
|
|
@ -182,10 +184,10 @@ const VGA = struct {
|
|||
// Use the software cursor as the source of truth.
|
||||
//
|
||||
pub fn updateCursor(self: *const VGA) void {
|
||||
x86.outb(0x3D4, 0x0F);
|
||||
x86.outb(0x3D5, @truncate(u8, self.cursor));
|
||||
x86.outb(0x3D4, 0x0E);
|
||||
x86.outb(0x3D5, @truncate(u8, self.cursor >> 8));
|
||||
kernel.x86.io.outb(0x3D4, 0x0F);
|
||||
kernel.x86.io.outb(0x3D5, @truncate(u8, self.cursor));
|
||||
kernel.x86.io.outb(0x3D4, 0x0E);
|
||||
kernel.x86.io.outb(0x3D5, @truncate(u8, self.cursor >> 8));
|
||||
}
|
||||
|
||||
////
|
||||
|
|
@ -195,11 +197,11 @@ const VGA = struct {
|
|||
pub fn fetchCursor(self: *VGA) void {
|
||||
var cursor: usize = 0;
|
||||
|
||||
x86.outb(0x3D4, 0x0E);
|
||||
cursor |= usize(x86.inb(0x3D5)) << 8;
|
||||
kernel.x86.io.outb(0x3D4, 0x0E);
|
||||
cursor |= usize(kernel.x86.io.inb(0x3D5)) << 8;
|
||||
|
||||
x86.outb(0x3D4, 0x0F);
|
||||
cursor |= x86.inb(0x3D5);
|
||||
kernel.x86.outb(0x3D4, 0x0F);
|
||||
cursor |= kernel.x86.io.inb(0x3D5);
|
||||
|
||||
self.cursor = cursor;
|
||||
}
|
||||
|
|
|
|||
17
src/vmem.zig
17
src/vmem.zig
|
|
@ -1,4 +1,7 @@
|
|||
pub usingnamespace @import("index.zig");
|
||||
const std = @import("std");
|
||||
const kernel = @import("kernel");
|
||||
const x86 = @import("x86");
|
||||
|
||||
pub var allocator: std.mem.Allocator = undefined;
|
||||
|
||||
// TODO: make a better memory allocator
|
||||
|
|
@ -7,7 +10,7 @@ pub var allocator: std.mem.Allocator = undefined;
|
|||
// - no defragmentation
|
||||
// - no allocation bigger than a page
|
||||
|
||||
const stack_size: usize = (layout.HEAP_END - layout.HEAP) / x86.PAGE_SIZE;
|
||||
const stack_size: usize = (kernel.layout.HEAP_END - kernel.layout.HEAP) / kernel.x86.PAGE_SIZE;
|
||||
var stack_index: usize = 0; // Index into the stack.
|
||||
var stack: [stack_size]usize = undefined; // Stack of free virtual addresses
|
||||
|
||||
|
|
@ -32,7 +35,7 @@ fn realloc(
|
|||
) ![]u8 {
|
||||
if (old_mem.len == 0) {
|
||||
// new allocation
|
||||
assert(new_byte_count < x86.PAGE_SIZE); // this allocator only support 1:1 mapping
|
||||
std.debug.assert(new_byte_count < x86.PAGE_SIZE); // this allocator only support 1:1 mapping
|
||||
if (available() == 0) return error.OutOfMemory;
|
||||
stack_index -= 1;
|
||||
var vaddr: usize = stack[stack_index];
|
||||
|
|
@ -44,7 +47,7 @@ fn realloc(
|
|||
dealloc(@ptrToInt(&old_mem[0]));
|
||||
return &[_]u8{};
|
||||
}
|
||||
println("vmem: unsupported allocator operation", .{});
|
||||
kernel.vga.println("vmem: unsupported allocator operation", .{});
|
||||
x86.hang();
|
||||
// return undefined;
|
||||
}
|
||||
|
|
@ -62,7 +65,7 @@ fn shrink(
|
|||
return &[_]u8{};
|
||||
}
|
||||
|
||||
println("vmem doesn't support shrinking, {}, {}, {}, {}", .{
|
||||
kernel.vga.println("vmem doesn't support shrinking, {}, {}, {}, {}", .{
|
||||
old_mem,
|
||||
old_alignment,
|
||||
new_byte_count,
|
||||
|
|
@ -77,8 +80,8 @@ pub fn init() void {
|
|||
.reallocFn = realloc,
|
||||
.shrinkFn = shrink,
|
||||
};
|
||||
var addr: usize = layout.HEAP;
|
||||
while (addr < layout.HEAP_END) : (addr += x86.PAGE_SIZE) {
|
||||
var addr: usize = kernel.layout.HEAP;
|
||||
while (addr < kernel.layout.HEAP_END) : (addr += x86.PAGE_SIZE) {
|
||||
stack[stack_index] = addr;
|
||||
stack_index += 1;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue