Compare commits
54 commits
feat/multi
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 59d3db9738 | |||
| 77fd0ce4cf | |||
| 08d447115d | |||
| e392799b91 | |||
| 50ac72e24b | |||
| 22f05324fa | |||
| 8380a657ac | |||
| 635134b1e3 | |||
| bc46a138ff | |||
| 2db4f9899a | |||
| 7483995316 | |||
| b1c76c405b | |||
| c3dcb449cb | |||
| e1dbcf80da | |||
| 8f3aea077a | |||
| b7fe260871 | |||
| 276e73a198 | |||
| 5718710584 | |||
| 0269925bf4 | |||
| b9ff019c39 | |||
| 7bb65b0395 | |||
|
|
bf78080782 | ||
| 9887fba6a9 | |||
| 88279f3f0d | |||
| 0307afe365 | |||
| a01e9a5f2a | |||
| 4148314d84 | |||
| c64c6cbcfe | |||
| ad329b5f81 | |||
| 2a37ac201e | |||
| 78f7197cb9 | |||
| a8c68611ce | |||
| e30f016977 | |||
| 9ec23055bc | |||
| a527695202 | |||
| f2d2ab867e | |||
| b3d8b7abc7 | |||
| 27e6f2684b | |||
| ec7ee599a1 | |||
| 6af31b5b89 | |||
| 8d7e7591e9 | |||
| ed5b97a87b | |||
| 5880d5296e | |||
| 902aa136c6 | |||
| bd12f0495f | |||
| bdc3b2939b | |||
| 888b51282d | |||
| 9fd40142fa | |||
| 28175d9336 | |||
| d051e1b0c8 | |||
| 4c9f4b54d8 | |||
| 07089c2ea1 | |||
| 97aa541b34 | |||
| e832dede16 |
45 changed files with 2281 additions and 667 deletions
5
.gdbinit
Normal file
5
.gdbinit
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
file build/kernel
|
||||
target remote localhost:4242
|
||||
set arch i386
|
||||
set step-mode on
|
||||
directory src
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
|||
zig-cache
|
||||
disk.img
|
||||
|
|
|
|||
46
README.md
46
README.md
|
|
@ -1,29 +1,49 @@
|
|||
## hobby kernel in zig
|
||||
# hobby kernel in zig
|
||||
|
||||
slowly porting from rust.
|
||||
# WARNING: this project was written for zig 0.5, it doesn't compile for future versions
|
||||
|
||||

|
||||
|
||||
### features
|
||||
|
||||
- vga frame buffer
|
||||
- 80x25 frame buffer
|
||||
- ps2 keyboard driver
|
||||
- interrupts
|
||||
- terminal console
|
||||
- lspci
|
||||
- x86
|
||||
- MMU
|
||||
- interrupts
|
||||
- pit timer
|
||||
- scheduler
|
||||
- time slice preemption with round robin
|
||||
- sleep()
|
||||
- block()/unblock()
|
||||
- Storage
|
||||
- IDE ATA driver (in progress)
|
||||
|
||||
### dependencies
|
||||
|
||||
`zig` compiler
|
||||
- [ziglang](https://github.com/ziglang/zig) 0.5.0
|
||||
|
||||
### compile
|
||||
### compiling
|
||||
|
||||
`zig build` compiles and links the multiboot kernel, without a bootloader.
|
||||
`zig build` compiles and links the multiboot kernel (without a bootloader)
|
||||
|
||||
### test
|
||||
### running
|
||||
|
||||
`./qemu.sh start`
|
||||
`./qemu.sh monitor`
|
||||
`./qemu.sh gdb`
|
||||
- `./qemu.sh start`
|
||||
- `./qemu.sh monitor`
|
||||
- `./qemu.sh quit`
|
||||
- `gdb` (see provided `.gdbinit`)
|
||||
|
||||
### todo
|
||||
# Notes
|
||||
|
||||
- recycling allocator that wraps the bump allocator
|
||||
## interrupt call chain
|
||||
|
||||
`interrupt` -> `idt[n]` -> `isrN` -> `isrDispatch` -> `handlers[n]` (default `unhandled()`)
|
||||
|
||||
## References
|
||||
|
||||
- zig microkernel: https://github.com/AndreaOrru/zen
|
||||
- scheduling tutorial: https://wiki.osdev.org/Brendan%27s_Multi-tasking_Tutorial
|
||||
- booting/paging/interrupts: https://os.phil-opp.com/
|
||||
|
|
|
|||
37
build.zig
37
build.zig
|
|
@ -1,18 +1,45 @@
|
|||
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");
|
||||
|
||||
pub fn build(b: *Builder) void {
|
||||
const kernel = b.addExecutable("kernel", "src/arch/x86/main.zig");
|
||||
const kernel = b.addExecutable("kernel", "src/main.zig");
|
||||
kernel.addPackagePath("kernel", "src/index.zig");
|
||||
kernel.addPackagePath("arch", "src/arch/x86/lib/index.zig");
|
||||
kernel.addPackagePath("x86", "src/arch/x86/index.zig");
|
||||
|
||||
kernel.setOutputDir("build");
|
||||
|
||||
kernel.addAssemblyFile("src/arch/x86/_start.s");
|
||||
kernel.addAssemblyFile("src/arch/x86/start.s");
|
||||
kernel.addAssemblyFile("src/arch/x86/gdt.s");
|
||||
kernel.addAssemblyFile("src/arch/x86/isr.s");
|
||||
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.setTarget(builtin.Arch.i386, builtin.Os.freestanding, builtin.Abi.none);
|
||||
kernel.setLinkerScriptPath("src/arch/x86/linker.ld");
|
||||
kernel.setLinkerScriptPath(.{ .path = "src/arch/x86/linker.ld" });
|
||||
b.default_step.dependOn(&kernel.step);
|
||||
}
|
||||
|
|
|
|||
20
grub.sh
20
grub.sh
|
|
@ -1,20 +0,0 @@
|
|||
#!/bin/bash
|
||||
exit_missing() {
|
||||
printf "$_ must be installed\n" ; exit 1;
|
||||
}
|
||||
|
||||
which xorriso || exit_missing
|
||||
which grub-mkrescue || exit_missing
|
||||
mkdir -p build/iso/boot/grub
|
||||
cp build/kernel build/iso/boot
|
||||
>build/iso/boot/grub/grub.cfg <<EOF
|
||||
set timeout=0
|
||||
set default=0
|
||||
|
||||
menuentry "OS" {
|
||||
multiboot2 /boot/kernel
|
||||
boot
|
||||
}
|
||||
EOF
|
||||
|
||||
grub-mkrescue -o build/kernel.iso build/iso
|
||||
9
mkiso.sh
Executable file
9
mkiso.sh
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
exit_missing() { printf "$_ must be installed\n" ; exit 1; }
|
||||
which xorriso || exit_missing
|
||||
which grub-mkrescue || exit_missing
|
||||
|
||||
mkdir -p build/iso/boot
|
||||
cp build/bzImage build/iso/boot
|
||||
grub-mkrescue -o build/kernel.iso build/iso
|
||||
62
qemu.sh
62
qemu.sh
|
|
@ -1,41 +1,35 @@
|
|||
#!/bin/bash
|
||||
# usage: ./qemu.sh start
|
||||
# ./qemu.sh quit
|
||||
|
||||
QEMU_SOCKET=/tmp/qemu.sock
|
||||
QEMU_MONITOR="socat - UNIX-CONNECT:${QEMU_SOCKET}"
|
||||
QEMU_MONITOR="sudo socat - UNIX-CONNECT:${QEMU_SOCKET}"
|
||||
QEMU_GDB_PORT=4242
|
||||
KERNEL=build/kernel
|
||||
|
||||
start() {
|
||||
sudo qemu-system-i386 \
|
||||
-gdb tcp::${QEMU_GDB_PORT} \
|
||||
-monitor unix:${QEMU_SOCKET},server,nowait \
|
||||
-enable-kvm \
|
||||
-m 50M \
|
||||
-serial mon:stdio \
|
||||
-curses \
|
||||
-kernel ${KERNEL}
|
||||
# -cdrom ${KERNEL}.iso
|
||||
# -append "Hello" \
|
||||
# -S
|
||||
# -device virtio-net,netdev=network0 -netdev tap,id=network0,ifname=tap0,script=no,downscript=no \
|
||||
# build/kernel.iso
|
||||
"$@"
|
||||
qemu_start() {
|
||||
touch disk.img
|
||||
# sudo pkill -9 qemu-system-i386
|
||||
sudo qemu-system-i386 \
|
||||
-gdb tcp::${QEMU_GDB_PORT} \
|
||||
-monitor unix:${QEMU_SOCKET},server,nowait \
|
||||
-enable-kvm \
|
||||
-curses \
|
||||
-m 1341M \
|
||||
-hda disk.img \
|
||||
-kernel ${KERNEL}
|
||||
# -drive file=disk.img,if=virtio\
|
||||
# -no-reboot \
|
||||
# -device virtio-net,netdev=network0 -netdev tap,id=network0,ifname=tap0,script=no,downscript=no \
|
||||
# -S \
|
||||
# build/kernel.iso
|
||||
|
||||
# this allows to monitor with ^a-c, but doesn't
|
||||
# play nice with irqs apparently...
|
||||
# -serial mon:stdio \
|
||||
}
|
||||
|
||||
monitor() {
|
||||
[ "$1" == "" ] && sudo ${QEMU_MONITOR} || echo "$1" | sudo ${QEMU_MONITOR} >/dev/null
|
||||
}
|
||||
qemu_quit() { echo quit | sudo ${QEMU_MONITOR} >/dev/null; }
|
||||
qemu_monitor() { sudo ${QEMU_MONITOR}; }
|
||||
|
||||
reload() {
|
||||
monitor stop
|
||||
# monitor "change ide1-cd0 ${KERNEL}"
|
||||
monitor system_reset
|
||||
monitor cont
|
||||
}
|
||||
|
||||
gdb() {
|
||||
gdb -q \
|
||||
-symbols "${KERNEL}" \
|
||||
-ex "target remote :${QEMU_GDB_PORT}" \
|
||||
-ex "set arch i386"
|
||||
}
|
||||
|
||||
"$@"
|
||||
qemu_"$@"
|
||||
|
|
|
|||
BIN
screenshot.png
Normal file
BIN
screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
|
|
@ -1,5 +1,5 @@
|
|||
// const tty = @import("tty.zig");
|
||||
const x86 = @import("lib/index.zig");
|
||||
const x86 = @import("x86");
|
||||
|
||||
// GDT segment selectors.
|
||||
pub const KERNEL_CODE = 0x08;
|
||||
|
|
@ -72,7 +72,7 @@ fn makeEntry(base: usize, limit: usize, access: u8, flags: u4) GDTEntry {
|
|||
}
|
||||
|
||||
// Fill in the GDT.
|
||||
var gdt align(4) = []GDTEntry{
|
||||
var gdt align(4) = [_]GDTEntry{
|
||||
makeEntry(0, 0, 0, 0),
|
||||
makeEntry(0, 0xFFFFF, KERNEL | CODE, PROTECTED | BLOCKS_4K),
|
||||
makeEntry(0, 0xFFFFF, KERNEL | DATA, PROTECTED | BLOCKS_4K),
|
||||
|
|
@ -83,7 +83,7 @@ var gdt align(4) = []GDTEntry{
|
|||
|
||||
// GDT descriptor register pointing at the GDT.
|
||||
var gdtr = GDTRegister{
|
||||
.limit = u16(@sizeOf(@typeOf(gdt))),
|
||||
.limit = @as(u16, @sizeOf(@TypeOf(gdt))),
|
||||
.base = &gdt[0],
|
||||
};
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ var tss = TSS{
|
|||
.unused1 = 0,
|
||||
.esp0 = undefined,
|
||||
.ss0 = KERNEL_DATA,
|
||||
.unused2 = []u32{0} ** 22,
|
||||
.unused2 = [_]u32{0} ** 22,
|
||||
.unused3 = 0,
|
||||
.iomap_base = @sizeOf(TSS),
|
||||
};
|
||||
|
|
@ -107,27 +107,16 @@ pub fn setKernelStack(esp0: usize) void {
|
|||
tss.esp0 = esp0;
|
||||
}
|
||||
|
||||
////
|
||||
// Load the GDT into the system registers (defined in assembly).
|
||||
//
|
||||
// Arguments:
|
||||
// gdtr: Pointer to the GDTR.
|
||||
//
|
||||
extern fn loadGDT(gdtr: *const GDTRegister) void;
|
||||
|
||||
////
|
||||
// Initialize the Global Descriptor Table.
|
||||
//
|
||||
pub fn initialize() void {
|
||||
// tty.step("Setting up the Global Descriptor Table");
|
||||
|
||||
// Initialize GDT.
|
||||
loadGDT(&gdtr);
|
||||
loadGDT(&gdtr); //asm routine
|
||||
|
||||
// Initialize TSS.
|
||||
const tss_entry = makeEntry(@ptrToInt(&tss), @sizeOf(TSS) - 1, TSS_ACCESS, PROTECTED);
|
||||
gdt[TSS_DESC / @sizeOf(GDTEntry)] = tss_entry;
|
||||
x86.ltr(TSS_DESC);
|
||||
// const tss_entry = makeEntry(@ptrToInt(&tss), @sizeOf(TSS) - 1, TSS_ACCESS, PROTECTED);
|
||||
// gdt[TSS_DESC / @sizeOf(GDTEntry)] = tss_entry;
|
||||
// x86.ltr(TSS_DESC);
|
||||
|
||||
// tty.stepOK();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,21 @@
|
|||
// https://wiki.osdev.org/IDT
|
||||
const x86 = @import("lib/index.zig");
|
||||
const interrupt = @import("interrupt.zig");
|
||||
const gdt = @import("gdt.zig");
|
||||
|
||||
const kernel = @import("kernel");
|
||||
const x86 = @import("x86");
|
||||
|
||||
// Types of gates.
|
||||
pub const INTERRUPT_GATE = 0x8E;
|
||||
pub const SYSCALL_GATE = 0xEE;
|
||||
|
||||
// Interrupt Descriptor Table.
|
||||
var idt_table: [256]IDTEntry = undefined;
|
||||
|
||||
// IDT descriptor register pointing at the IDT.
|
||||
const idtr = IDTRegister{
|
||||
.limit = @as(u16, @sizeOf(@TypeOf(idt_table))),
|
||||
.base = &idt_table,
|
||||
};
|
||||
|
||||
// Structure representing an entry in the IDT.
|
||||
const IDTEntry = packed struct {
|
||||
offset_low: u16,
|
||||
|
|
@ -22,16 +31,6 @@ const IDTRegister = packed struct {
|
|||
base: *[256]IDTEntry,
|
||||
};
|
||||
|
||||
// Interrupt Descriptor Table.
|
||||
var idt: [256]IDTEntry = undefined;
|
||||
|
||||
// IDT descriptor register pointing at the IDT.
|
||||
const idtr = IDTRegister{
|
||||
.limit = u16(@sizeOf(@typeOf(idt))),
|
||||
.base = &idt,
|
||||
};
|
||||
|
||||
////
|
||||
// Setup an IDT entry.
|
||||
//
|
||||
// Arguments:
|
||||
|
|
@ -39,20 +38,52 @@ const idtr = IDTRegister{
|
|||
// 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[n].offset_low = @truncate(u16, intOffset);
|
||||
idt[n].offset_high = @truncate(u16, intOffset >> 16);
|
||||
idt[n].flags = flags;
|
||||
idt[n].zero = 0;
|
||||
idt[n].selector = gdt.KERNEL_CODE;
|
||||
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 = x86.gdt.KERNEL_CODE;
|
||||
}
|
||||
|
||||
////
|
||||
// Initialize the Interrupt Descriptor Table.
|
||||
//
|
||||
pub fn initialize() void {
|
||||
interrupt.initialize();
|
||||
x86.lidt(@ptrToInt(&idtr));
|
||||
// configure PIC
|
||||
x86.interrupt.remapPIC();
|
||||
x86.interrupt.configPIT();
|
||||
// install ISRs
|
||||
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
|
||||
x86.instr.lidt(@ptrToInt(&idtr));
|
||||
}
|
||||
|
||||
fn general_protection_fault() void {
|
||||
kernel.vga.println("general protection fault", .{});
|
||||
x86.instr.hang();
|
||||
}
|
||||
|
||||
fn debug_trap() void {
|
||||
kernel.vga.println("debug fault/trap", .{});
|
||||
kernel.vga.println("dr7: 0b{b}", .{x86.instr.dr7()});
|
||||
}
|
||||
|
||||
fn page_fault() void {
|
||||
const vaddr = x86.instr.cr2();
|
||||
kernel.vga.println("cr2: 0x{x}", .{vaddr});
|
||||
kernel.vga.println("phy: 0x{x}", .{x86.paging.translate(vaddr)});
|
||||
kernel.vga.println("pde: 0x{x} ({})", .{ x86.paging.pde(vaddr), vaddr >> 22 });
|
||||
kernel.vga.println("pte: 0x{x} ({})", .{ x86.paging.pte(vaddr), vaddr >> 12 });
|
||||
// paging.format();
|
||||
x86.instr.hang();
|
||||
}
|
||||
|
|
|
|||
16
src/arch/x86/index.zig
Normal file
16
src/arch/x86/index.zig
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
pub usingnamespace @import("../../common.zig");
|
||||
|
||||
// from core kernel
|
||||
pub const kernel = @import("../../index.zig");
|
||||
|
||||
// x86 namespace
|
||||
pub const PAGE_SIZE: usize = 4096;
|
||||
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");
|
||||
pub const idt = @import("idt.zig");
|
||||
pub const isr = @import("isr.zig");
|
||||
pub const gdt = @import("gdt.zig");
|
||||
pub const interrupt = @import("interrupt.zig");
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
const x86 = @import("lib/index.zig");
|
||||
const isr = @import("isr.zig");
|
||||
const std = @import("std");
|
||||
const kernel = @import("kernel");
|
||||
const x86 = @import("x86");
|
||||
|
||||
// PIC ports.
|
||||
const PIC1_CMD = 0x20;
|
||||
|
|
@ -15,6 +16,11 @@ const ICW1_ICW4 = 0x01;
|
|||
const ICW4_8086 = 0x01;
|
||||
// write 0 to wait
|
||||
const WAIT_PORT = 0x80;
|
||||
// PIT Channels
|
||||
const PIT_CHAN0 = 0x40;
|
||||
const PIT_CHAN1 = 0x41;
|
||||
const PIT_CHAN2 = 0x42;
|
||||
const PIT_CMD = 0x43;
|
||||
// Interrupt Vector offsets of exceptions.
|
||||
const EXCEPTION_0 = 0;
|
||||
const EXCEPTION_31 = EXCEPTION_0 + 31;
|
||||
|
|
@ -24,35 +30,38 @@ const IRQ_15 = IRQ_0 + 15;
|
|||
// Interrupt Vector offsets of syscalls.
|
||||
const SYSCALL = 128;
|
||||
|
||||
// Registered interrupt handlers. (see isr.s)
|
||||
var handlers = [_]fn () void{unhandled} ** 48;
|
||||
// Registered IRQ subscribers. (see isr.s)
|
||||
// Registered interrupt handlers. (see x86.isr.s)
|
||||
const handlers = [_]fn () void{unhandled} ** 48;
|
||||
// Registered IRQ subscribers. (see x86.isr.s)
|
||||
// var irq_subscribers = []MailboxId{MailboxId.Kernel} ** 16;
|
||||
|
||||
fn unhandled() noreturn {
|
||||
const n = isr.context.interrupt_n;
|
||||
// if (n >= IRQ_0) {
|
||||
// tty.panic("unhandled IRQ number {d}", n - IRQ_0);
|
||||
// } else {
|
||||
// tty.panic("unhandled exception number {d}", n);
|
||||
// }
|
||||
x86.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 {
|
||||
x86.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 => {
|
||||
handlers[n]();
|
||||
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]();
|
||||
},
|
||||
|
||||
// IRQs.
|
||||
|
|
@ -67,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 {
|
||||
|
|
@ -80,8 +89,8 @@ export fn interruptDispatch() void {
|
|||
|
||||
// If no user thread is ready to run, halt here and wait for interrupts.
|
||||
// if (scheduler.current() == null) {
|
||||
// x86.sti();
|
||||
// x86.hlt();
|
||||
// sti();
|
||||
// hlt();
|
||||
// }
|
||||
}
|
||||
|
||||
|
|
@ -91,8 +100,8 @@ inline fn spuriousIRQ(irq: u8) bool {
|
|||
// TODO: handle spurious IRQ15.
|
||||
|
||||
// Read the value of the In-Service Register.
|
||||
x86.outb(PIC1_CMD, ISR_READ);
|
||||
const in_service = x86.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;
|
||||
|
|
@ -102,11 +111,11 @@ inline fn startOfInterrupt(irq: u8) void {
|
|||
// mask the irq and then ACK
|
||||
if (irq >= 8) {
|
||||
maskIRQ(irq, true);
|
||||
x86.outb(PIC1_CMD, ACK);
|
||||
x86.outb(PIC2_CMD, ACK);
|
||||
x86.io.outb(PIC1_CMD, ACK);
|
||||
x86.io.outb(PIC2_CMD, ACK);
|
||||
} else {
|
||||
maskIRQ(irq, true);
|
||||
x86.outb(PIC1_CMD, ACK);
|
||||
x86.io.outb(PIC1_CMD, ACK);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -114,75 +123,80 @@ inline fn endOfInterrupt(irq: u8) void {
|
|||
// unmask the irq and then ACK
|
||||
if (irq >= 8) {
|
||||
maskIRQ(irq, false);
|
||||
x86.outb(PIC2_CMD, ACK);
|
||||
x86.io.outb(PIC2_CMD, ACK);
|
||||
} else {
|
||||
maskIRQ(irq, false);
|
||||
x86.outb(PIC1_CMD, ACK);
|
||||
x86.io.outb(PIC1_CMD, ACK);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(n: u8, handler: fn () void) void {
|
||||
pub fn register(n: u8, comptime handler: fn () void) void {
|
||||
handlers[n] = handler;
|
||||
}
|
||||
|
||||
pub fn registerIRQ(irq: u8, handler: fn () void) void {
|
||||
pub fn registerIRQ(irq: u8, comptime handler: fn () void) void {
|
||||
register(IRQ_0 + irq, handler);
|
||||
maskIRQ(irq, false); // Unmask the IRQ.
|
||||
}
|
||||
|
||||
fn remapPIC() void {
|
||||
pub fn remapPIC() void {
|
||||
// ICW1: start initialization sequence.
|
||||
x86.outb(PIC1_CMD, ICW1_INIT | ICW1_ICW4);
|
||||
x86.io.outb(PIC1_CMD, ICW1_INIT | ICW1_ICW4);
|
||||
picwait();
|
||||
x86.outb(PIC2_CMD, ICW1_INIT | ICW1_ICW4);
|
||||
x86.io.outb(PIC2_CMD, ICW1_INIT | ICW1_ICW4);
|
||||
picwait();
|
||||
|
||||
// ICW2: Interrupt Vector offsets of IRQs.
|
||||
x86.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();
|
||||
x86.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.
|
||||
x86.outb(PIC1_DATA, 1 << 2);
|
||||
x86.io.outb(PIC1_DATA, 1 << 2);
|
||||
picwait();
|
||||
x86.outb(PIC2_DATA, 2);
|
||||
x86.io.outb(PIC2_DATA, 2);
|
||||
picwait();
|
||||
|
||||
// ICW4: 80x86 mode.
|
||||
x86.outb(PIC1_DATA, ICW4_8086);
|
||||
x86.io.outb(PIC1_DATA, ICW4_8086);
|
||||
picwait();
|
||||
x86.outb(PIC2_DATA, ICW4_8086);
|
||||
x86.io.outb(PIC2_DATA, ICW4_8086);
|
||||
picwait();
|
||||
|
||||
// Mask all IRQs.
|
||||
x86.outb(PIC1_DATA, 0xFF);
|
||||
x86.io.outb(PIC1_DATA, 0xFF);
|
||||
picwait();
|
||||
x86.outb(PIC2_DATA, 0xFF);
|
||||
x86.io.outb(PIC2_DATA, 0xFF);
|
||||
picwait();
|
||||
}
|
||||
|
||||
pub fn maskIRQ(irq: u8, mask: bool) void {
|
||||
if (irq > 15) {
|
||||
return;
|
||||
}
|
||||
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 = if (irq < 8) u16(PIC1_DATA) else u16(PIC2_DATA);
|
||||
const old = x86.inb(port); // Retrieve the current mask.
|
||||
const port = @as(u16, if (irq < 8) PIC1_DATA else PIC2_DATA);
|
||||
const old = x86.io.inb(port); // Retrieve the current mask.
|
||||
|
||||
// Mask or unmask the interrupt.
|
||||
const shift = @intCast(u3, irq % 8); // TODO: waiting for Andy to fix this.
|
||||
if (mask) {
|
||||
x86.outb(port, old | (u8(1) << shift));
|
||||
} else {
|
||||
x86.outb(port, old & ~(u8(1) << shift));
|
||||
}
|
||||
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) 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.
|
||||
}
|
||||
|
||||
////
|
||||
// Initialize interrupts.
|
||||
//
|
||||
pub fn initialize() void {
|
||||
remapPIC();
|
||||
isr.install();
|
||||
// configures the chan0 with a rate generator, which will trigger irq0
|
||||
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 LOHI = 0b11; // bit4 | bit5
|
||||
const PITMODE_RATE_GEN = 0x2;
|
||||
|
||||
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,5 +1,5 @@
|
|||
// Kernel stack for interrupt handling.
|
||||
KERNEL_STACK = 0x80000
|
||||
// KERNEL_STACK = 0x10000
|
||||
// GDT selectors.
|
||||
KERNEL_DS = 0x10
|
||||
|
||||
|
|
@ -27,24 +27,23 @@ isrCommon:
|
|||
pusha // Save the registers state.
|
||||
|
||||
// Setup kernel data segment.
|
||||
mov $KERNEL_DS, %ax
|
||||
mov %ax, %ds
|
||||
mov %ax, %es
|
||||
// mov $KERNEL_DS, %ax
|
||||
// mov %ax, %ds
|
||||
// mov %ax, %es
|
||||
|
||||
// Save the pointer to the current context and switch to the kernel stack.
|
||||
mov %esp, context
|
||||
mov $KERNEL_STACK, %esp
|
||||
// mov $KERNEL_STACK, %esp
|
||||
|
||||
call interruptDispatch // Handle the interrupt event.
|
||||
|
||||
// Restore the pointer to the context (of a different thread, potentially).
|
||||
mov context, %esp
|
||||
// mov context, %esp
|
||||
|
||||
// Setup user data segment.
|
||||
mov $USER_DS, %ax
|
||||
mov %ax, %ds
|
||||
mov %ax, %es
|
||||
|
||||
// mov $USER_DS, %ax
|
||||
// mov %ax, %ds
|
||||
// mov %ax, %es
|
||||
|
||||
popa // Restore the registers state.
|
||||
add $8, %esp // Remove interrupt number and error code from stack.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -80,19 +80,6 @@ pub const Registers = packed struct {
|
|||
edx: u32,
|
||||
ecx: u32,
|
||||
eax: u32,
|
||||
|
||||
pub fn init() Registers {
|
||||
return Registers{
|
||||
.edi = 0,
|
||||
.esi = 0,
|
||||
.ebp = 0,
|
||||
.esp = 0,
|
||||
.ebx = 0,
|
||||
.edx = 0,
|
||||
.ecx = 0,
|
||||
.eax = 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Pointer to the current saved context.
|
||||
|
|
@ -100,9 +87,8 @@ pub export var context: *volatile Context = undefined;
|
|||
////
|
||||
// Install the Interrupt Service Routines in the IDT.
|
||||
//
|
||||
pub fn install() void {
|
||||
pub fn install_exceptions() void {
|
||||
// Exceptions.
|
||||
idt.setGate(0, idt.INTERRUPT_GATE, isr0);
|
||||
idt.setGate(1, idt.INTERRUPT_GATE, isr1);
|
||||
idt.setGate(2, idt.INTERRUPT_GATE, isr2);
|
||||
idt.setGate(3, idt.INTERRUPT_GATE, isr3);
|
||||
|
|
@ -134,25 +120,29 @@ pub fn install() void {
|
|||
idt.setGate(29, idt.INTERRUPT_GATE, isr29);
|
||||
idt.setGate(30, idt.INTERRUPT_GATE, isr30);
|
||||
idt.setGate(31, idt.INTERRUPT_GATE, isr31);
|
||||
}
|
||||
|
||||
// // IRQs.
|
||||
// IRQs.
|
||||
pub fn install_irqs() void {
|
||||
idt.setGate(32, idt.INTERRUPT_GATE, isr32);
|
||||
idt.setGate(33, idt.INTERRUPT_GATE, isr33);
|
||||
// idt.setGate(34, idt.INTERRUPT_GATE, isr34);
|
||||
// idt.setGate(35, idt.INTERRUPT_GATE, isr35);
|
||||
// idt.setGate(36, idt.INTERRUPT_GATE, isr36);
|
||||
// idt.setGate(37, idt.INTERRUPT_GATE, isr37);
|
||||
// idt.setGate(38, idt.INTERRUPT_GATE, isr38);
|
||||
// idt.setGate(39, idt.INTERRUPT_GATE, isr39);
|
||||
// idt.setGate(40, idt.INTERRUPT_GATE, isr40);
|
||||
// idt.setGate(41, idt.INTERRUPT_GATE, isr41);
|
||||
// idt.setGate(42, idt.INTERRUPT_GATE, isr42);
|
||||
// idt.setGate(43, idt.INTERRUPT_GATE, isr43);
|
||||
// idt.setGate(44, idt.INTERRUPT_GATE, isr44);
|
||||
// idt.setGate(45, idt.INTERRUPT_GATE, isr45);
|
||||
// idt.setGate(46, idt.INTERRUPT_GATE, isr46);
|
||||
// idt.setGate(47, idt.INTERRUPT_GATE, isr47);
|
||||
|
||||
// // Syscalls.
|
||||
// idt.setGate(128, idt.SYSCALL_GATE, isr128);
|
||||
idt.setGate(34, idt.INTERRUPT_GATE, isr34);
|
||||
idt.setGate(35, idt.INTERRUPT_GATE, isr35);
|
||||
idt.setGate(36, idt.INTERRUPT_GATE, isr36);
|
||||
idt.setGate(37, idt.INTERRUPT_GATE, isr37);
|
||||
idt.setGate(38, idt.INTERRUPT_GATE, isr38);
|
||||
idt.setGate(39, idt.INTERRUPT_GATE, isr39);
|
||||
idt.setGate(40, idt.INTERRUPT_GATE, isr40);
|
||||
idt.setGate(41, idt.INTERRUPT_GATE, isr41);
|
||||
idt.setGate(42, idt.INTERRUPT_GATE, isr42);
|
||||
idt.setGate(43, idt.INTERRUPT_GATE, isr43);
|
||||
idt.setGate(44, idt.INTERRUPT_GATE, isr44);
|
||||
idt.setGate(45, idt.INTERRUPT_GATE, isr45);
|
||||
idt.setGate(46, idt.INTERRUPT_GATE, isr46);
|
||||
idt.setGate(47, idt.INTERRUPT_GATE, isr47);
|
||||
}
|
||||
|
||||
// Syscalls.
|
||||
pub fn install_syscalls() void {
|
||||
idt.setGate(128, idt.SYSCALL_GATE, isr128);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
use @import("io.zig");
|
||||
use @import("instructions.zig");
|
||||
|
|
@ -1,39 +1,47 @@
|
|||
////
|
||||
// Load a new Task Register.
|
||||
//
|
||||
// Arguments:
|
||||
// desc: Segment selector of the TSS.
|
||||
//
|
||||
pub inline fn ltr(desc: u16) void {
|
||||
asm volatile ("ltr %[desc]"
|
||||
:
|
||||
: [desc] "r" (desc)
|
||||
: [desc] "r" (desc),
|
||||
);
|
||||
}
|
||||
|
||||
////
|
||||
// Completely stop the computer.
|
||||
//
|
||||
pub inline fn hang() noreturn {
|
||||
asm volatile ("cli");
|
||||
while (true) {
|
||||
asm volatile ("hlt");
|
||||
}
|
||||
cli();
|
||||
while (true) asm volatile ("hlt");
|
||||
}
|
||||
|
||||
pub inline fn sti() void {
|
||||
//TODO: inline this
|
||||
pub fn cli() void {
|
||||
asm volatile ("cli");
|
||||
}
|
||||
|
||||
//TODO: inline this
|
||||
pub fn sti() void {
|
||||
asm volatile ("sti");
|
||||
}
|
||||
pub inline fn hlt() void {
|
||||
asm volatile ("hlt");
|
||||
}
|
||||
|
||||
pub inline fn int3() void {
|
||||
asm volatile ("int3");
|
||||
}
|
||||
|
||||
////
|
||||
// Load a new Interrupt Descriptor Table.
|
||||
//
|
||||
// Arguments:
|
||||
// idtr: Address of the IDTR register.
|
||||
//
|
||||
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),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn dr7() usize {
|
||||
return asm volatile ("movl %%dr7, %[result]"
|
||||
: [result] "=r" (-> usize),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,34 @@
|
|||
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: anytype, cnt: usize) void {
|
||||
asm volatile ("cld; repne; insl;"
|
||||
: [addr] "={edi}" (addr),
|
||||
[cnt] "={ecx}" (cnt),
|
||||
: [port] "{dx}" (port),
|
||||
[addr] "0" (addr),
|
||||
[cnt] "1" (cnt),
|
||||
: "memory", "cc"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -21,20 +36,22 @@ 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),
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ SECTIONS {
|
|||
. = 0xb8000;
|
||||
. += 80 * 25 * 2;
|
||||
|
||||
. = 0x100000;
|
||||
/* lower half kernel <1MiB */
|
||||
. = 0x10000;
|
||||
|
||||
/* ensure that the multiboot header is at the beginning */
|
||||
.multiboot :
|
||||
|
|
@ -36,47 +37,15 @@ SECTIONS {
|
|||
. = ALIGN(4K);
|
||||
}
|
||||
|
||||
/* NOT A GOOD IDEA TO GROUP debug_* SYMBOLS ! */
|
||||
/* .debug : */
|
||||
/* { */
|
||||
/* /1* KEEP(*(.debug_*)) *1/ */
|
||||
/* *(.debug_*) */
|
||||
/* . = ALIGN(4K); */
|
||||
/* } */
|
||||
|
||||
.gdt :
|
||||
{
|
||||
*(.gdt)
|
||||
. = ALIGN(4K);
|
||||
}
|
||||
|
||||
.got :
|
||||
{
|
||||
*(.got)
|
||||
. = ALIGN(4K);
|
||||
}
|
||||
|
||||
.got.plt :
|
||||
{
|
||||
*(.got.plt)
|
||||
. = ALIGN(4K);
|
||||
}
|
||||
|
||||
.bss :
|
||||
{
|
||||
*(.bss .bss.*)
|
||||
. = ALIGN(4K);
|
||||
}
|
||||
|
||||
/* .stab : */
|
||||
/* { */
|
||||
/* KEEP(*(.stab)) */
|
||||
/* . = ALIGN(4K); */
|
||||
/* } */
|
||||
|
||||
/* .stabstr : */
|
||||
/* { */
|
||||
/* KEEP(*(.stabstr)) */
|
||||
/* . = ALIGN(4K); */
|
||||
/* } */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,12 @@
|
|||
usingnamespace @import("kernel").main;
|
||||
usingnamespace @import("kernel").multiboot;
|
||||
const idt = @import("idt.zig");
|
||||
const mem = @import("mem.zig");
|
||||
const gdt = @import("gdt.zig");
|
||||
const x86 = @import("lib/index.zig");
|
||||
const console = @import("../console.zig");
|
||||
const printf = @import("../../vga.zig").printf;
|
||||
const std = @import("std");
|
||||
const kernel = @import("kernel");
|
||||
const x86 = @import("x86");
|
||||
|
||||
/// x86 specific intialization
|
||||
/// first entry point (see linker.ld)
|
||||
pub fn x86_main(info: *const multibootInfo) void {
|
||||
var allocator = mem.initialize(info);
|
||||
|
||||
// gdt.initialize();
|
||||
idt.initialize();
|
||||
x86.sti();
|
||||
pub fn x86_main(info: *const kernel.multiboot.MultibootInfo) void {
|
||||
x86.gdt.initialize();
|
||||
x86.idt.initialize();
|
||||
x86.pmem.initialize(info);
|
||||
x86.paging.initialize();
|
||||
x86.instr.sti();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,142 +0,0 @@
|
|||
usingnamespace @import("kernel").multiboot;
|
||||
const assert = @import("std").debug.assert;
|
||||
const vga = @import("../../vga.zig");
|
||||
const std = @import("std");
|
||||
const println = @import("../../vga.zig").println;
|
||||
|
||||
pub fn initialize(info: *const multibootInfo) void {
|
||||
// assert((info.flags & MULTIBOOT_INFO_MEMORY) != 0);
|
||||
// assert((info.flags & MULTIBOOT_INFO_MEM_MAP) != 0);
|
||||
format_multibootinfo(info);
|
||||
// return bumpAllocator.new(info);
|
||||
}
|
||||
|
||||
pub fn format_multibootentry(entry: *multibootMMapEntry) void {
|
||||
if (entry.type == MULTIBOOT_MEMORY_AVAILABLE) {
|
||||
vga.printf("AVAILABLE: ");
|
||||
} else {
|
||||
vga.printf("NOT AVAILABLE: ");
|
||||
}
|
||||
vga.printf("{x} ", entry.addr);
|
||||
if (entry.len / (1024 * 1024) > 0) {
|
||||
vga.printf("({} MB)\n", entry.len / (1024 * 1024));
|
||||
} else {
|
||||
vga.printf("({} kB)\n", entry.len / (1024));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_multibootinfo(info: *const multibootInfo) void {
|
||||
println("-- multiboot2 info --");
|
||||
// println("start: {}, end {}, size {}", info.start_address(), info.end_address(), info.total_size());
|
||||
}
|
||||
|
||||
// returns each available physical frame one by one in order
|
||||
// pub const bumpAllocator = struct {
|
||||
// next_free_frame: physFrame,
|
||||
// current_area: ?*multibootMMapEntry,
|
||||
// info: *const multibootInfo,
|
||||
|
||||
// pub fn new(info: *const multibootInfo) bumpAllocator {
|
||||
// const first_area = @intToPtr(*MultibootMMapEntry, info.mmap_addr);
|
||||
// var allocator = bumpAllocator{
|
||||
// .current_area = first_area,
|
||||
// .next_free_frame = physFrame.from_addr(first_area.addr),
|
||||
// .info = info,
|
||||
// };
|
||||
// // allocator.choose_next_area();
|
||||
// return allocator;
|
||||
// }
|
||||
|
||||
// pub fn allocate(self: var, count: u64) ?physFrame {
|
||||
// if (count == 0) {
|
||||
// return null;
|
||||
// }
|
||||
// if (self.current_area == null) {
|
||||
// return null;
|
||||
// }
|
||||
// if (self.current_area.?.type != MULTIBOOT_MEMORY_AVAILABLE) {
|
||||
// self.choose_next_area();
|
||||
// return self.allocate(count);
|
||||
// }
|
||||
|
||||
// const start_frame = self.next_free_frame;
|
||||
// const end_frame = self.next_free_frame.add(count - 1);
|
||||
// const current_area_last_frame = physFrame.from_addr(self.current_area.?.addr + self.current_area.?.len);
|
||||
// if (end_frame.number > current_area_last_frame.number) {
|
||||
// self.choose_next_area();
|
||||
// return self.allocate(count);
|
||||
// }
|
||||
// self.next_free_frame.number += count;
|
||||
// return start_frame;
|
||||
// }
|
||||
|
||||
// pub fn choose_next_area(self: var) void {
|
||||
// printf("choosing next area\n");
|
||||
// const current = self.current_area.?;
|
||||
// var next_area = @ptrToInt(current) + current.size + @sizeOf(@typeOf(current));
|
||||
// if (next_area >= self.info.mmap_addr + self.info.mmap_length) {
|
||||
// self.current_area = null;
|
||||
// } else {
|
||||
// format_multibootentry(self.current_area.?);
|
||||
// self.current_area = @intToPtr(*MultibootMMapEntry, next_area);
|
||||
// format_multibootentry(self.current_area.?);
|
||||
// self.next_free_frame = physFrame.from_addr(self.current_area.?.addr);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
// pub const PAGE_SIZE = 4096;
|
||||
|
||||
// pub const physFrame = struct {
|
||||
// number: u64,
|
||||
|
||||
// pub fn from_addr(addr: u64) physFrame {
|
||||
// return physFrame{ .number = @divTrunc(addr, PAGE_SIZE) };
|
||||
// }
|
||||
|
||||
// pub fn add(self: physFrame, count: u64) physFrame {
|
||||
// return physFrame{ .number = self.number + count };
|
||||
// }
|
||||
|
||||
// pub fn start_addr(self: physFrame) u64 {
|
||||
// return (self.number * PAGE_SIZE);
|
||||
// }
|
||||
// };
|
||||
|
||||
// const PageDirectory = packed struct {
|
||||
// entries: [1024]PageDirectoryEntry,
|
||||
// };
|
||||
|
||||
// const PageDirectoryEntry = packed struct {
|
||||
// address: u21,
|
||||
// available: u2,
|
||||
// ignored: u1,
|
||||
// size: u1,
|
||||
// zero: u1,
|
||||
// accessed: u1,
|
||||
// cache_disabled: u1,
|
||||
// write_through: u1,
|
||||
// user: u1,
|
||||
// writeable: u1,
|
||||
// present: u1,
|
||||
// };
|
||||
|
||||
// const PageTable = packed struct {
|
||||
// entries: [1024]PageTableEntry,
|
||||
// };
|
||||
|
||||
// const PageTableEntry = packed struct {
|
||||
// address: u21,
|
||||
// available: u2,
|
||||
// global: u1,
|
||||
// zero: u1,
|
||||
// dirty: u1,
|
||||
// accessed: u1,
|
||||
// cache_disabled: u1,
|
||||
// write_through: u1,
|
||||
// user: u1,
|
||||
// writeable: u1,
|
||||
// present: u1,
|
||||
// };
|
||||
|
||||
// // assert(@sizeOf(PageTableEntry) == 32);
|
||||
26
src/arch/x86/paging.s
Normal file
26
src/arch/x86/paging.s
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
.type setupPaging, @function
|
||||
.global setupPaging
|
||||
|
||||
////
|
||||
// Enable the paging system.
|
||||
//
|
||||
// Arguments:
|
||||
// phys_pd: Physical pointer to the page directory.
|
||||
//
|
||||
setupPaging:
|
||||
mov +4(%esp), %eax // Fetch the phys_pd parameter.
|
||||
mov %eax, %cr3 // Point CR3 to the page directory.
|
||||
|
||||
// Enable Page Size Extension
|
||||
mov %cr4, %eax
|
||||
or $0x10, %eax
|
||||
mov %eax, %cr4
|
||||
|
||||
// Enable Paging.
|
||||
mov %cr0, %eax
|
||||
mov $1, %eax
|
||||
or $(1 << 0), %eax
|
||||
or $(1 << 31), %eax
|
||||
mov %eax, %cr0
|
||||
|
||||
ret
|
||||
80
src/arch/x86/paging.zig
Normal file
80
src/arch/x86/paging.zig
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
const std = @import("std");
|
||||
const kernel = @import("kernel");
|
||||
const x86 = @import("x86");
|
||||
|
||||
extern fn setupPaging(phys_pd: usize) void;
|
||||
|
||||
const PageEntry = usize;
|
||||
pub const PT = @intToPtr([*]PageEntry, 0xFFC00000);
|
||||
pub const PD = @intToPtr([*]PageEntry, 0xFFFFF000);
|
||||
const PRESENT = 0x1;
|
||||
const WRITE = 0x2;
|
||||
const USER = 0x4;
|
||||
const WRITE_THRU = 0x8;
|
||||
const NOCACHE = 0x10;
|
||||
const ACCESSED = 0x20;
|
||||
const HUGE = 0x80;
|
||||
|
||||
pub var pageDirectory: [1024]PageEntry align(4096) linksection(".bss") = [_]PageEntry{0} ** 1024;
|
||||
|
||||
// TODO: inline these
|
||||
fn pageBase(virt: usize) usize {
|
||||
return virt & (~x86.PAGE_SIZE +% 1);
|
||||
}
|
||||
pub fn pde(virt: usize) *PageEntry {
|
||||
return &PD[virt >> 22]; //relies on recursive mapping
|
||||
}
|
||||
pub fn pte(virt: usize) *PageEntry {
|
||||
return &PT[virt >> 12]; //relies on recursive mapping
|
||||
}
|
||||
|
||||
// virtual to physical
|
||||
pub fn translate(virt: usize) ?usize {
|
||||
if (pde(virt).* == 0) return null;
|
||||
return pageBase(pte(virt).*);
|
||||
}
|
||||
|
||||
pub fn unmap(virt: usize) void {
|
||||
if (translate(virt)) |phys| {
|
||||
x86.pmem.free(phys);
|
||||
} else {
|
||||
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 x86.pmem.allocate()) | WRITE | PRESENT;
|
||||
// allocate a frame if phys isn't specified
|
||||
pte(virt).* = (if (phys) |p| p else try x86.pmem.allocate()) | PRESENT;
|
||||
}
|
||||
|
||||
pub fn initialize() void {
|
||||
var p2 = pageDirectory[0..];
|
||||
|
||||
// identity map 0 -> 4MB
|
||||
p2[0] = 0x000000 | PRESENT | WRITE | HUGE;
|
||||
// recursive mapping
|
||||
p2[1023] = @ptrToInt(&p2[0]) | PRESENT | WRITE;
|
||||
|
||||
// TODO: verify is this a hack?
|
||||
std.debug.assert(x86.pmem.stack_end < kernel.layout.IDENTITY);
|
||||
|
||||
setupPaging(@ptrToInt(&pageDirectory[0])); //asm routine
|
||||
}
|
||||
|
||||
pub fn format() void {
|
||||
var i: usize = 1;
|
||||
i = 0;
|
||||
while (i < 1024) : (i += 1) {
|
||||
if (PD[i] == 0) continue;
|
||||
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.vga.println("p2[{}]p1[{}] -> 0x{x}", .{ i, j, entry });
|
||||
}
|
||||
}
|
||||
}
|
||||
95
src/arch/x86/pmem.zig
Normal file
95
src/arch/x86/pmem.zig
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
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.
|
||||
|
||||
// Boundaries of the frame stack.
|
||||
pub var stack_size: usize = undefined;
|
||||
pub var stack_end: usize = undefined;
|
||||
|
||||
pub inline fn pageAlign(address: u32) u32 {
|
||||
// 4095 -> 4096
|
||||
// 4096 -> 4096
|
||||
// 4097 -> 8192
|
||||
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 * x86.PAGE_SIZE;
|
||||
}
|
||||
|
||||
pub inline fn available_MiB() usize {
|
||||
return available() / (1024 * 1024);
|
||||
}
|
||||
|
||||
// Request a free physical page and return its address.
|
||||
//
|
||||
pub fn allocate() !usize {
|
||||
if (available() == 0) {
|
||||
kernel.println("out of memory", .{});
|
||||
return error.OutOfMemory;
|
||||
}
|
||||
stack_index -= 1;
|
||||
return stack[stack_index];
|
||||
}
|
||||
|
||||
// Free a previously allocated physical page.
|
||||
//
|
||||
// Arguments:
|
||||
// address: Address of the page to be freed.
|
||||
//
|
||||
pub fn free(address: usize) void {
|
||||
stack[stack_index] = address;
|
||||
stack_index += 1;
|
||||
}
|
||||
|
||||
// Scan the memory map to index all available memory.
|
||||
//
|
||||
// Arguments:
|
||||
// info: Information structure from bootloader.
|
||||
//
|
||||
pub fn initialize(info: *const kernel.multiboot.MultibootInfo) void {
|
||||
// Ensure the bootloader has given us the memory map.
|
||||
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
|
||||
// random bug and I won't ever know where it came from, great.
|
||||
stack = @intToPtr([*]usize, 0x200000); // 2 MiB
|
||||
|
||||
// Place the stack of free pages after the last Multiboot module.
|
||||
// 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) / x86.PAGE_SIZE) * @sizeOf(usize);
|
||||
stack_end = pageAlign(@ptrToInt(stack) + stack_size);
|
||||
|
||||
var map: usize = info.mmap_addr;
|
||||
while (map < info.mmap_addr + info.mmap_length) {
|
||||
var entry = @intToPtr(*kernel.multiboot.MultibootMMapEntry, map);
|
||||
|
||||
// Calculate the start and end of this memory area.
|
||||
var start = @truncate(usize, entry.addr);
|
||||
var end = @truncate(usize, start + entry.len);
|
||||
// Anything that comes before the end of the stack of free pages is reserved.
|
||||
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 += x86.PAGE_SIZE)
|
||||
free(start);
|
||||
|
||||
// Go to the next entry in the memory map.
|
||||
map += entry.size + @sizeOf(@TypeOf(entry.size));
|
||||
}
|
||||
|
||||
kernel.vga.println("available memory: {d} MiB ", .{available() / 1024 / 1024});
|
||||
}
|
||||
|
||||
pub fn format() void {
|
||||
kernel.vga.println("physframes left: {d} ({d} MiB)", .{ stack_index, available_MiB() });
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ __start:
|
|||
|
||||
push %ebx // Pass multiboot info structure.
|
||||
push %eax // Pass multiboot magic code.
|
||||
|
||||
call kmain // Call the kernel.
|
||||
|
||||
// Halt the CPU.
|
||||
33
src/arch/x86/switch_tasks.s
Normal file
33
src/arch/x86/switch_tasks.s
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
//https://wiki.osdev.org/Multitasking_Systems
|
||||
|
||||
//C declaration:
|
||||
// void switch_tasks(thread_control_block *next_thread)//
|
||||
//
|
||||
//WARNING: Caller is expected to disable IRQs before calling, and enable IRQs again after function returns
|
||||
|
||||
.type switch_tasks, @function
|
||||
.global switch_tasks
|
||||
switch_tasks:
|
||||
push %ebp
|
||||
mov %esp, %ebp
|
||||
mov +12(%esp), %eax
|
||||
mov %esp, (%eax) //save old esp
|
||||
mov +8(%esp), %eax
|
||||
mov %eax, %esp // move the forged stack to esp
|
||||
pop %ebp // the top of the forged stack contains ebp
|
||||
ret //the top of the forged stack contains eip to go to
|
||||
|
||||
// .type jmp_to_entrypoint, @function
|
||||
// .global jmp_to_entrypoint
|
||||
// jmp_to_entrypoint:
|
||||
// mov %esp, %ebp
|
||||
// mov +4(%esp), %eax
|
||||
// jmp *%eax
|
||||
|
||||
// .type birthasm, @function
|
||||
// .global birthasm
|
||||
// birthasm:
|
||||
// call unlock_scheduler
|
||||
// mov %esp, %ebp
|
||||
// mov +4(%esp), %eax
|
||||
// jmp *%eax
|
||||
10
src/bio.zig
Normal file
10
src/bio.zig
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Block Device
|
||||
// Glue code between Driver and FS
|
||||
|
||||
pub fn BlockDev(comptime sector_size: usize) type {
|
||||
return struct {
|
||||
const sector_size;
|
||||
read: fn (u64, *[sector_size]u8) void, //TODO: inferred !void or var (issue 447)
|
||||
write: ?fn (u64, *[sector_size]u8) void,
|
||||
};
|
||||
}
|
||||
3
src/common.zig
Normal file
3
src/common.zig
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
pub const std = @import("std");
|
||||
pub const assert = std.debug.assert;
|
||||
pub const builtin = @import("builtin");
|
||||
|
|
@ -1,30 +1,60 @@
|
|||
const interrupt = @import("arch/x86/interrupt.zig");
|
||||
const x86 = @import("arch/x86/lib/index.zig");
|
||||
const ps2 = @import("ps2.zig");
|
||||
const pci = @import("pci.zig");
|
||||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
usingnamespace @import("vga.zig");
|
||||
usingnamespace @import("index.zig");
|
||||
|
||||
var input_ring: Ring(u8) = undefined;
|
||||
|
||||
var command: [10]u8 = undefined;
|
||||
var command_len: usize = 0;
|
||||
|
||||
fn execute(com: []u8) void {
|
||||
if (mem.eql(u8, com, "lspci")) pci.lspci();
|
||||
fn sleep2() void {
|
||||
task.usleep(2 * 1000 * 1000) catch unreachable;
|
||||
}
|
||||
|
||||
fn t_sleep2() void {
|
||||
_ = task.new(@ptrToInt(sleep2)) catch unreachable;
|
||||
}
|
||||
|
||||
const Command = struct {
|
||||
name: []const u8,
|
||||
f: fn () void,
|
||||
};
|
||||
|
||||
const commands = [_]Command{
|
||||
Command{ .name = "clear", .f = clear },
|
||||
Command{ .name = "paging", .f = x86.paging.format },
|
||||
Command{ .name = "memory", .f = x86.pmem.format },
|
||||
Command{ .name = "tasks", .f = task.format },
|
||||
Command{ .name = "lspci", .f = pci.lspci },
|
||||
Command{ .name = "sleep2", .f = sleep2 },
|
||||
Command{ .name = "t-sleep2", .f = t_sleep2 },
|
||||
Command{ .name = "uptime", .f = time.uptime },
|
||||
};
|
||||
|
||||
fn execute(input: []u8) void {
|
||||
for (commands) |c| if (std.mem.eql(u8, input, c.name)) return c.f();
|
||||
println("{}: command not found, list of available commands:", .{input});
|
||||
for (commands) |c| println("{}", .{c.name});
|
||||
}
|
||||
|
||||
pub fn keypress(char: u8) void {
|
||||
// this is a custom "readline" capped at 10 characters
|
||||
switch (char) {
|
||||
'\n' => {
|
||||
vga.writeChar('\n');
|
||||
execute(command[0..command_len]);
|
||||
vga.writeString("> ");
|
||||
print("\n", .{});
|
||||
if (command_len > 0) execute(command[0..command_len]);
|
||||
print("> ", .{});
|
||||
command_len = 0;
|
||||
},
|
||||
'\x00' => return,
|
||||
'\x08' => {
|
||||
// backspace
|
||||
if (command_len == 0) return;
|
||||
vga.writeChar(char);
|
||||
command_len -= 1;
|
||||
command[command_len] = '\x00';
|
||||
},
|
||||
else => {
|
||||
if (command_len == 10)
|
||||
return;
|
||||
// general case
|
||||
if (command_len == 10) return;
|
||||
vga.writeChar(char);
|
||||
command[command_len] = char;
|
||||
command_len += 1;
|
||||
|
|
@ -32,8 +62,17 @@ pub fn keypress(char: u8) void {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn initialize() void {
|
||||
vga.clear();
|
||||
// vga.writeString("> ");
|
||||
interrupt.registerIRQ(1, ps2.keyboard_handler);
|
||||
pub fn keyboard_callback(char: u8) void {
|
||||
input_ring.write(char);
|
||||
}
|
||||
|
||||
pub fn loop() void {
|
||||
input_ring.init(vmem.allocator) catch unreachable;
|
||||
input_ring.task = task.current_task;
|
||||
ps2.keyboard_callback = keyboard_callback;
|
||||
print("> ", .{});
|
||||
while (true) {
|
||||
while (input_ring.read()) |c| keypress(c);
|
||||
task.block(.IOWait);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
src/constants.zig
Normal file
1
src/constants.zig
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub const SMP: bool = false;
|
||||
153
src/delta_queue.zig
Normal file
153
src/delta_queue.zig
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
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
|
||||
/// to the previous.
|
||||
///
|
||||
/// Inspired by https://wiki.osdev.org/Blocking_Process
|
||||
/// Based on std.SinglyLinkedList
|
||||
pub fn DeltaQueue(comptime T: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
/// Node inside the linked list wrapping the actual data.
|
||||
pub const Node = struct {
|
||||
next: ?*Node,
|
||||
data: T,
|
||||
counter: u64,
|
||||
|
||||
pub fn init(data: T, counter: u64) Node {
|
||||
return Node{
|
||||
.next = null,
|
||||
.data = data,
|
||||
.counter = counter,
|
||||
};
|
||||
}
|
||||
|
||||
/// Insert a new node after the current one.
|
||||
///
|
||||
/// Arguments:
|
||||
/// new_node: Pointer to the new node to insert.
|
||||
fn insertAfter(node: *Node, new_node: *Node) void {
|
||||
if (node.next) |after| {
|
||||
std.debug.assert(new_node.counter <= after.counter); //sanity check
|
||||
after.counter -= new_node.counter;
|
||||
}
|
||||
new_node.next = node.next;
|
||||
node.next = new_node;
|
||||
}
|
||||
};
|
||||
|
||||
len: usize = 0,
|
||||
first: ?*Node,
|
||||
|
||||
/// Initialize a delta queue.
|
||||
///
|
||||
/// Returns:
|
||||
/// An empty linked list.
|
||||
pub fn init() Self {
|
||||
return Self{
|
||||
.first = null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Insert a node in the list
|
||||
///
|
||||
/// Arguments:
|
||||
/// node: Pointer to a node in the list.
|
||||
/// new_node: Pointer to the new node to insert.
|
||||
pub fn insert(list: *Self, node: *Node) void {
|
||||
list.len += 1;
|
||||
|
||||
var target: ?*Node = null;
|
||||
var next: ?*Node = list.first;
|
||||
while (true) {
|
||||
if (next == null or node.counter <= next.?.counter) {
|
||||
if (target) |tg| return tg.insertAfter(node);
|
||||
return list.prepend(node);
|
||||
}
|
||||
if (target) |tg| node.counter -= tg.counter;
|
||||
target = next;
|
||||
next = target.?.next;
|
||||
}
|
||||
}
|
||||
|
||||
/// worst case is O(n)
|
||||
/// Could be better with a different data structure
|
||||
/// Example: case of large list with all counters at 0,
|
||||
/// we need to traverse the whole list.
|
||||
pub fn decrement(list: *Self, count: u64) void {
|
||||
var it = list.first;
|
||||
var i = count;
|
||||
while (it) |node| : (it = node.next) {
|
||||
if (node.counter >= i) {
|
||||
node.counter -= i;
|
||||
return;
|
||||
}
|
||||
i -= node.counter;
|
||||
node.counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a new node at the head.
|
||||
///
|
||||
/// Arguments:
|
||||
/// new_node: Pointer to the new node to insert.
|
||||
fn prepend(list: *Self, new_node: *Node) void {
|
||||
if (list.first) |after| {
|
||||
std.debug.assert(new_node.counter <= after.counter); //sanity check
|
||||
after.counter -= new_node.counter;
|
||||
}
|
||||
new_node.next = list.first;
|
||||
list.first = new_node;
|
||||
}
|
||||
|
||||
/// Remove and return the first node in the list
|
||||
/// if its counter is 0.
|
||||
///
|
||||
/// Returns:
|
||||
/// A pointer to the first node in the list.
|
||||
pub fn popZero(list: *Self) ?*Node {
|
||||
const first = list.first orelse return null;
|
||||
if (first.counter != 0) return null;
|
||||
list.first = first.next;
|
||||
list.len -= 1;
|
||||
return first;
|
||||
}
|
||||
|
||||
/// Allocate a new node.
|
||||
///
|
||||
/// Arguments:
|
||||
/// allocator: Dynamic memory allocator.
|
||||
///
|
||||
/// Returns:
|
||||
/// A pointer to the new node.
|
||||
pub fn allocateNode(list: *Self, allocator: *Allocator) !*Node {
|
||||
return allocator.create(Node);
|
||||
}
|
||||
|
||||
/// Deallocate a node.
|
||||
///
|
||||
/// Arguments:
|
||||
/// node: Pointer to the node to deallocate.
|
||||
/// allocator: Dynamic memory allocator.
|
||||
pub fn destroyNode(list: *Self, node: *Node, allocator: *Allocator) void {
|
||||
allocator.destroy(node);
|
||||
}
|
||||
|
||||
/// Allocate and initialize a node and its data.
|
||||
///
|
||||
/// Arguments:
|
||||
/// data: The data to put inside the node.
|
||||
/// allocator: Dynamic memory allocator.
|
||||
///
|
||||
/// Returns:
|
||||
/// A pointer to the new node.
|
||||
pub fn createNode(list: *Self, data: T, counter: u64, allocator: *Allocator) !*Node {
|
||||
var node = try list.allocateNode(allocator);
|
||||
node.* = Node.init(data, counter);
|
||||
return node;
|
||||
}
|
||||
};
|
||||
}
|
||||
425
src/driver/ide.zig
Normal file
425
src/driver/ide.zig
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
usingnamespace @import("index.zig");
|
||||
|
||||
const IDE_ATA = 0x00;
|
||||
const IDE_ATAPI = 0x01;
|
||||
|
||||
const ATA_MASTER = 0x00;
|
||||
const ATA_SLAVE = 0x01;
|
||||
|
||||
// Channels:
|
||||
const ATA_PRIMARY = 0x00;
|
||||
const ATA_SECONDARY = 0x01;
|
||||
|
||||
// Directions:
|
||||
const ATA_READ = 0x00;
|
||||
const ATA_WRITE = 0x01;
|
||||
|
||||
// Commands
|
||||
const ATA_CMD_READ_PIO = 0x20;
|
||||
const ATA_CMD_READ_PIO_EXT = 0x24;
|
||||
const ATA_CMD_READ_DMA = 0xC8;
|
||||
const ATA_CMD_READ_DMA_EXT = 0x25;
|
||||
const ATA_CMD_WRITE_PIO = 0x30;
|
||||
const ATA_CMD_WRITE_PIO_EXT = 0x34;
|
||||
const ATA_CMD_WRITE_DMA = 0xCA;
|
||||
const ATA_CMD_WRITE_DMA_EXT = 0x35;
|
||||
const ATA_CMD_CACHE_FLUSH = 0xE7;
|
||||
const ATA_CMD_CACHE_FLUSH_EXT = 0xEA;
|
||||
const ATA_CMD_PACKET = 0xA0;
|
||||
const ATA_CMD_IDENTIFY_PACKET = 0xA1;
|
||||
const ATA_CMD_IDENTIFY = 0xEC;
|
||||
|
||||
// Status:
|
||||
const ATA_SR_BUSY = 0x80;
|
||||
const ATA_SR_DRDY = 0x40; // Drive ready
|
||||
const ATA_SR_DF = 0x20; // Drive write fault
|
||||
const ATA_SR_DSC = 0x10; // Drive seek complete
|
||||
const ATA_SR_DRQ = 0x08; // Data request ready
|
||||
const ATA_SR_CORR = 0x04; // Corrected data
|
||||
const ATA_SR_IDX = 0x02;
|
||||
const ATA_SR_ERR = 0x01;
|
||||
|
||||
// Registers:
|
||||
const ATA_REG_DATA = 0x00;
|
||||
const ATA_REG_ERROR = 0x01;
|
||||
const ATA_REG_FEATURES = 0x01;
|
||||
const ATA_REG_SECCOUNT0 = 0x02;
|
||||
const ATA_REG_LBA0 = 0x03;
|
||||
const ATA_REG_LBA1 = 0x04;
|
||||
const ATA_REG_LBA2 = 0x05;
|
||||
const ATA_REG_HDDEVSEL = 0x06;
|
||||
const ATA_REG_COMMAND = 0x07;
|
||||
const ATA_REG_STATUS = 0x07;
|
||||
const ATA_REG_SECCOUNT1 = 0x08;
|
||||
const ATA_REG_LBA3 = 0x09;
|
||||
const ATA_REG_LBA4 = 0x0A;
|
||||
const ATA_REG_LBA5 = 0x0B;
|
||||
const ATA_REG_CONTROL = 0x0C;
|
||||
const ATA_REG_ALTSTATUS = 0x0C;
|
||||
const ATA_REG_DEVADDRESS = 0x0D;
|
||||
|
||||
// Identification space
|
||||
const ATA_IDENT_DEVICETYPE = 0;
|
||||
const ATA_IDENT_CYLINDERS = 2;
|
||||
const ATA_IDENT_HEADS = 6;
|
||||
const ATA_IDENT_SECTORS = 12;
|
||||
const ATA_IDENT_SERIAL = 20;
|
||||
const ATA_IDENT_MODEL = 54;
|
||||
const ATA_IDENT_CAPABILITIES = 98;
|
||||
const ATA_IDENT_FIELDVALID = 106;
|
||||
const ATA_IDENT_MAX_LBA = 120;
|
||||
const ATA_IDENT_COMMANDSETS = 164;
|
||||
const ATA_IDENT_MAX_LBA_EXT = 200;
|
||||
|
||||
const atapi_packet: [12]u8 = [1]u8{0xA8} ++ [1]u8{0} ** 11;
|
||||
var ide_buf: [2048]u8 = [1]u8{0} ** 2048;
|
||||
|
||||
const IDEDevice = struct {
|
||||
reserved: u8, // 0 (Empty) or 1 (This Drive really exists).
|
||||
channel: IDEChannelRegister,
|
||||
drive: u8, // 0 (Master Drive) or 1 (Slave Drive).
|
||||
idetype: u16, // 0: ATA, 1:ATAPI.
|
||||
signature: u16, // Drive Signature
|
||||
capabilities: u16, // Features.
|
||||
commandsets: usize, // Command Sets Supported.
|
||||
size: usize, // Size in Sectors.
|
||||
model: [41]u8, // Model in string.
|
||||
ide_irq_invoked: bool = false,
|
||||
|
||||
pub fn init(channel: IDEChannelRegister, drive: u8) !?*IDEDevice {
|
||||
var idetype: u8 = IDE_ATA;
|
||||
var err: u8 = 0;
|
||||
var status: u8 = 0;
|
||||
|
||||
// TODO: make this nicer
|
||||
var self = try kernel.vmem.allocator.create(IDEDevice);
|
||||
errdefer kernel.vmem.allocator.destroy(self);
|
||||
self.reserved = 1;
|
||||
self.channel = channel;
|
||||
self.drive = drive;
|
||||
|
||||
// (0) Turn off irqs
|
||||
self.write(ATA_REG_CONTROL, 2);
|
||||
|
||||
// (I) Select Drive:
|
||||
self.write(ATA_REG_HDDEVSEL, 0xA0 | (drive << 4)); // Select Drive.
|
||||
try kernel.task.usleep(1000); // Wait 1ms for drive select to work.
|
||||
|
||||
// (II) Send ATA Identify Command:
|
||||
self.write(ATA_REG_COMMAND, ATA_CMD_IDENTIFY);
|
||||
try kernel.task.usleep(1000);
|
||||
|
||||
if (self.read(ATA_REG_STATUS) == 0) return null; // If Status = 0, No Device.
|
||||
|
||||
while (true) {
|
||||
status = self.read(ATA_REG_STATUS);
|
||||
if (status & ATA_SR_ERR != 0) {
|
||||
err = 1;
|
||||
break;
|
||||
} // If Err, Device is not ATA.
|
||||
if ((status & ATA_SR_BUSY == 0) and (status & ATA_SR_DRQ != 0)) break; // Everything is right.
|
||||
}
|
||||
|
||||
// (IV) Probe for ATAPI Devices:)
|
||||
if (err != 0) {
|
||||
// Device is not ATA
|
||||
const cl = self.read(ATA_REG_LBA1);
|
||||
const ch = self.read(ATA_REG_LBA2);
|
||||
|
||||
if (cl == 0x14 and ch == 0xEB) idetype = IDE_ATAPI;
|
||||
if (cl == 0x69 and ch == 0x96) idetype = IDE_ATAPI;
|
||||
if (idetype != IDE_ATAPI) {
|
||||
return null; // Unknown Type (may not be a device).
|
||||
}
|
||||
|
||||
self.write(ATA_REG_COMMAND, ATA_CMD_IDENTIFY_PACKET);
|
||||
try kernel.task.usleep(1000);
|
||||
}
|
||||
self.idetype = idetype;
|
||||
|
||||
// (V) Read Identification Space of the Device:
|
||||
self.read_buffer(ATA_REG_DATA, &ide_buf, 128);
|
||||
self.signature = @ptrCast(*const u8, &ide_buf[ATA_IDENT_DEVICETYPE]).*;
|
||||
self.capabilities = @ptrCast(*const u8, &ide_buf[ATA_IDENT_CAPABILITIES]).*;
|
||||
self.commandsets = @ptrCast(*const usize, &ide_buf[ATA_IDENT_COMMANDSETS]).*;
|
||||
|
||||
// (VII) Get Size:
|
||||
if (self.commandsets & (1 << 26) != 0) {
|
||||
// Device uses 48-Bit Addressing:
|
||||
self.size = @ptrCast(*const usize, &ide_buf[ATA_IDENT_MAX_LBA_EXT]).*;
|
||||
} else {
|
||||
// Device uses CHS or 28-bit Addressing:
|
||||
self.size = @ptrCast(*const usize, &ide_buf[ATA_IDENT_MAX_LBA]).*;
|
||||
}
|
||||
// (VIII) String indicates model of device (like Western Digital HDD and SONY DVD-RW...):
|
||||
var k: u16 = 0;
|
||||
while (k < 40) : (k = k + 2) {
|
||||
self.model[k] = ide_buf[ATA_IDENT_MODEL + k + 1];
|
||||
self.model[k + 1] = ide_buf[ATA_IDENT_MODEL + k];
|
||||
}
|
||||
self.model[40] = 0; // Terminate String.
|
||||
self.format();
|
||||
return self;
|
||||
}
|
||||
|
||||
inline fn poll(self: IDEDevice) void {
|
||||
for ([_]u8{ 0, 1, 2, 3 }) |_| _ = self.read(ATA_REG_ALTSTATUS); // wait 100ns per call
|
||||
while (self.read(ATA_REG_STATUS) & ATA_SR_BUSY != 0) {} // Wait for BSY to be zero.
|
||||
}
|
||||
inline fn poll_check(self: IDEDevice) !void {
|
||||
// (I) Delay 400 nanosecond for BSY to be set:
|
||||
self.poll();
|
||||
const state = self.read(ATA_REG_STATUS); // Read Status Register.
|
||||
if (state & ATA_SR_ERR != 0) return error.ATAStatusReg; // Error.
|
||||
if (state & ATA_SR_DF != 0) return error.ATADeviceFault; // Device Fault.
|
||||
if ((state & ATA_SR_DRQ) == 0) return error.ATANoDRQ; // DRQ should be set
|
||||
}
|
||||
|
||||
pub inline fn read(self: IDEDevice, comptime reg: u8) u8 {
|
||||
if (reg > 0x07 and reg < 0x0C) self.write(ATA_REG_CONTROL, 0x80 | self.channel.nIEN);
|
||||
defer if (reg > 0x07 and reg < 0x0C) self.write(ATA_REG_CONTROL, self.channel.nIEN);
|
||||
return switch (reg) {
|
||||
0x0...0x7 => x86.inb(self.channel.base + reg - 0x0),
|
||||
0x8...0xb => x86.inb(self.channel.base + reg - 0x6),
|
||||
0xc...0xd => x86.inb(self.channel.ctrl + reg - 0xa),
|
||||
0xe...0x16 => x86.inb(self.channel.bmide + reg - 0xe),
|
||||
else => @compileError("bad IDE register."),
|
||||
};
|
||||
}
|
||||
pub inline fn read_buffer(self: IDEDevice, comptime reg: u8, buf: var, cnt: usize) void {
|
||||
if (reg > 0x07 and reg < 0x0C) self.write(ATA_REG_CONTROL, 0x80 | self.channel.nIEN);
|
||||
defer if (reg > 0x07 and reg < 0x0C) self.write(ATA_REG_CONTROL, self.channel.nIEN);
|
||||
switch (reg) {
|
||||
0x0...0x7 => x86.insl(self.channel.base + reg - 0x0, buf, cnt),
|
||||
0x8...0xb => x86.insl(self.channel.base + reg - 0x6, buf, cnt),
|
||||
0xc...0xd => x86.insl(self.channel.ctrl + reg - 0xa, buf, cnt),
|
||||
0xe...0x16 => x86.insl(self.channel.bmide + reg - 0xe, buf, cnt),
|
||||
else => @compileError("bad IDE register."),
|
||||
}
|
||||
}
|
||||
pub inline fn write(self: IDEDevice, comptime reg: u8, data: u8) void {
|
||||
if (reg > 0x07 and reg < 0x0C) self.write(ATA_REG_CONTROL, 0x80 | self.channel.nIEN);
|
||||
defer if (reg > 0x07 and reg < 0x0C) self.write(ATA_REG_CONTROL, self.channel.nIEN);
|
||||
switch (reg) {
|
||||
0x0...0x7 => x86.outb(self.channel.base + reg - 0x0, data),
|
||||
0x8...0xb => x86.outb(self.channel.base + reg - 0x6, data),
|
||||
0xc...0xd => x86.outb(self.channel.ctrl + reg - 0xa, data),
|
||||
0xe...0x16 => x86.outb(self.channel.bmide + reg - 0xe, data),
|
||||
else => @compileError("bad IDE register."),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_sectors(self: *IDEDevice, numsects: u8, lba: u64, selector: u8, buf: usize) !void {
|
||||
// 1: Check if the drive presents:
|
||||
if (self.reserved == 0) {
|
||||
return error.DriveNotFound; // Drive Not Found!
|
||||
} else if (self.idetype == IDE_ATA and (lba + numsects) > self.size) {
|
||||
// 2: Check if inputs are valid:
|
||||
return error.InvalidSeek; // Seeking to invalid position.
|
||||
} else {
|
||||
// 3: Read in PIO Mode through Polling & IRQs:
|
||||
if (self.idetype == IDE_ATA) {
|
||||
try self.ata_access(ATA_READ, lba, numsects, selector, buf);
|
||||
} else if (self.idetype == IDE_ATAPI) {
|
||||
return error.ATAPINotImplemented;
|
||||
// var i: u8 = 0;
|
||||
// while (i < numsects) : (i = i + 1) {
|
||||
// // err = ide_atapi_read(drive, lba + i, 1, selector, buf + (i * 2048));
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format(self: IDEDevice) void {
|
||||
kernel.println("[ide] {} drive ({}MB) - {}", .{
|
||||
if (self.idetype == 0) "ATA " else "ATAPI",
|
||||
self.size * 512 / 1024 / 1024,
|
||||
self.model,
|
||||
});
|
||||
}
|
||||
|
||||
fn ata_access(self: *IDEDevice, direction: u8, lba: u64, numsects: u8, selector: u16, buf: usize) !void {
|
||||
var dma = false; // 0: No DMA, 1: DMA
|
||||
var cmd: u8 = 0;
|
||||
var lba_io = [1]u8{0} ** 8;
|
||||
const bus: u16 = self.channel.base; // Bus Base, like 0x1F0 which is also data port.
|
||||
const words: usize = 256; // Almost every ATA drive has a sector-size of 512-byte.
|
||||
|
||||
self.ide_irq_invoked = false;
|
||||
self.write(ATA_REG_CONTROL, 2); // disable IRQa
|
||||
|
||||
var lba_mode: u8 = undefined;
|
||||
var head: u8 = undefined;
|
||||
|
||||
// (I) Select one from LBA28, LBA48 or CHS;
|
||||
if (lba >= 0x10000000) {
|
||||
// Sure Drive should support LBA in this case, or you are giving a wrong LBA.
|
||||
// LBA48:
|
||||
lba_io = @bitCast([8]u8, lba);
|
||||
head = 0; // Lower 4-bits of HDDEVSEL are not used here.
|
||||
lba_mode = 2;
|
||||
} else if (self.capabilities & 0x200 != 0) { // Drive supports LBA?
|
||||
// LBA28:
|
||||
lba_io = @bitCast([8]u8, lba);
|
||||
assert(lba_io[3] == 0);
|
||||
head = @intCast(u8, (lba & 0xF000000) >> 24);
|
||||
lba_mode = 1;
|
||||
} else {
|
||||
// CHS:
|
||||
const sect = @intCast(u8, (lba % 63) + 1);
|
||||
const cyl = (lba + 1 - sect) / (16 * 63);
|
||||
lba_io[0] = sect;
|
||||
lba_io[1] = @intCast(u8, (cyl >> 0) & 0xFF);
|
||||
lba_io[2] = @intCast(u8, (cyl >> 8) & 0xFF);
|
||||
lba_io[3] = 0;
|
||||
lba_io[4] = 0;
|
||||
lba_io[5] = 0;
|
||||
head = @intCast(u8, (lba + 1 - sect) % (16 * 63) / (63)); // Head number is written to HDDEVSEL lower 4-bits.
|
||||
lba_mode = 0;
|
||||
}
|
||||
|
||||
// (III) Wait if the drive is busy;
|
||||
while (self.read(ATA_REG_STATUS) & ATA_SR_BUSY != 0) {} // Wait if busy.)
|
||||
|
||||
// (IV) Select Drive from the controller;
|
||||
if (lba_mode == 0) self.write(ATA_REG_HDDEVSEL, 0xA0 | (self.drive << 4) | head); // Drive & CHS.
|
||||
if (lba_mode != 0) self.write(ATA_REG_HDDEVSEL, 0xE0 | (self.drive << 4) | head); // Drive & LBA
|
||||
|
||||
// (V) Write Parameters;
|
||||
if (lba_mode == 2) {
|
||||
self.write(ATA_REG_SECCOUNT1, 0);
|
||||
self.write(ATA_REG_LBA3, lba_io[3]);
|
||||
self.write(ATA_REG_LBA4, lba_io[4]);
|
||||
self.write(ATA_REG_LBA5, lba_io[5]);
|
||||
}
|
||||
self.write(ATA_REG_SECCOUNT0, numsects);
|
||||
self.write(ATA_REG_LBA0, lba_io[0]);
|
||||
self.write(ATA_REG_LBA1, lba_io[1]);
|
||||
self.write(ATA_REG_LBA2, lba_io[2]);
|
||||
|
||||
// (VI) Select the command and send it;
|
||||
if (lba_mode == 0 and direction == 0 and !dma) cmd = ATA_CMD_READ_PIO;
|
||||
if (lba_mode == 1 and direction == 0 and !dma) cmd = ATA_CMD_READ_PIO;
|
||||
if (lba_mode == 2 and direction == 0 and !dma) cmd = ATA_CMD_READ_PIO_EXT;
|
||||
if (lba_mode == 0 and direction == 0 and dma) cmd = ATA_CMD_READ_DMA;
|
||||
if (lba_mode == 1 and direction == 0 and dma) cmd = ATA_CMD_READ_DMA;
|
||||
if (lba_mode == 2 and direction == 0 and dma) cmd = ATA_CMD_READ_DMA_EXT;
|
||||
if (lba_mode == 0 and direction == 1 and !dma) cmd = ATA_CMD_WRITE_PIO;
|
||||
if (lba_mode == 1 and direction == 1 and !dma) cmd = ATA_CMD_WRITE_PIO;
|
||||
if (lba_mode == 2 and direction == 1 and !dma) cmd = ATA_CMD_WRITE_PIO_EXT;
|
||||
if (lba_mode == 0 and direction == 1 and dma) cmd = ATA_CMD_WRITE_DMA;
|
||||
if (lba_mode == 1 and direction == 1 and dma) cmd = ATA_CMD_WRITE_DMA;
|
||||
if (lba_mode == 2 and direction == 1 and dma) cmd = ATA_CMD_WRITE_DMA_EXT;
|
||||
self.write(ATA_REG_COMMAND, cmd); // Send the Command.
|
||||
|
||||
if (dma) {
|
||||
//TODO: dma
|
||||
// if (direction == 0);
|
||||
// // DMA Read.
|
||||
// else;
|
||||
// // DMA Write.
|
||||
}
|
||||
if (!dma and direction == 0) {
|
||||
// PIO Read.
|
||||
var i: u8 = 0;
|
||||
while (i < numsects) : (i = i + 1) {
|
||||
var iedi = buf + i * (words * 2);
|
||||
try self.poll_check(); // Polling, set error and exit if there is.
|
||||
|
||||
// TODO? use selectors for non flat layouts
|
||||
// asm volatile ("pushw %%es");
|
||||
// asm volatile ("mov %[a], %%es"
|
||||
// :
|
||||
// : [a] "{eax}" (selector)
|
||||
// );
|
||||
asm volatile ("rep insw"
|
||||
: [iedi] "={edi}" (iedi),
|
||||
[words] "={ecx}" (words)
|
||||
: [bus] "{dx}" (bus),
|
||||
[iedi] "0" (iedi),
|
||||
[words] "1" (words)
|
||||
: "memory", "cc"
|
||||
);
|
||||
// asm volatile ("popw %%es");
|
||||
// x86.hang();
|
||||
}
|
||||
}
|
||||
if (!dma and direction == 1) {
|
||||
// PIO Write.
|
||||
var i: u8 = 0;
|
||||
while (i < numsects) : (i = i + 1) {
|
||||
var iedi = buf + i * (words * 2);
|
||||
self.poll(); // Polling.
|
||||
asm volatile ("pushw %%ds");
|
||||
asm volatile ("mov %%ax, %%ds"
|
||||
: [selector] "={eax}" (selector)
|
||||
);
|
||||
asm volatile ("rep outsw"
|
||||
:
|
||||
: [words] "{ecx}" (words),
|
||||
[bus] "{dx}" (bus),
|
||||
[iedi] "{esi}" (iedi)
|
||||
); // Send Data
|
||||
asm volatile ("popw %%ds");
|
||||
}
|
||||
if (lba_mode == 2) self.write(ATA_REG_COMMAND, ATA_CMD_CACHE_FLUSH_EXT);
|
||||
if (lba_mode != 2) self.write(ATA_REG_COMMAND, ATA_CMD_CACHE_FLUSH);
|
||||
try self.poll_check(); // Polling.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var ide_device_0: ?*IDEDevice = null;
|
||||
var ide_device_1: ?*IDEDevice = null;
|
||||
var ide_device_2: ?*IDEDevice = null;
|
||||
var ide_device_3: ?*IDEDevice = null;
|
||||
|
||||
const IDEChannelRegister = struct {
|
||||
base: u16, // I/O Base.
|
||||
ctrl: u16, // Control Base
|
||||
bmide: u16, // Bus Master IDE
|
||||
nIEN: u8, // nIEN (No Interrupt);
|
||||
};
|
||||
|
||||
pub const first_ide_drive = kernel.bio.BlockDev(512){
|
||||
.read = ide_block_read,
|
||||
.write = null,
|
||||
};
|
||||
|
||||
pub fn ide_block_read(lba: u64, buf: *[512]u8) void {
|
||||
// read 1 sector on drive 0
|
||||
return ide_device_0.?.read_sectors(1, lba, 0x10, @ptrToInt(buf)) catch unreachable;
|
||||
}
|
||||
|
||||
pub fn init(dev: kernel.pci.PciDevice) void {
|
||||
kernel.println("-- ide init --", .{});
|
||||
kernel.print("[ide] ", .{});
|
||||
dev.format();
|
||||
assert(dev.header_type() == 0x0); // mass storage device
|
||||
|
||||
dev.config_write(@intCast(u8, 0xfe), 0x3c);
|
||||
if (dev.intr_line() == 0xfe) {
|
||||
kernel.println("[ide] detected ATA device", .{});
|
||||
} else {
|
||||
kernel.println("[ide] Potential SATA device. Not implemented. Hanging", .{});
|
||||
x86.hang();
|
||||
}
|
||||
|
||||
const BAR0 = @intCast(u16, dev.bar(0));
|
||||
const BAR1 = @intCast(u16, dev.bar(1));
|
||||
const BAR2 = @intCast(u16, dev.bar(2));
|
||||
const BAR3 = @intCast(u16, dev.bar(3));
|
||||
const BAR4 = @intCast(u16, dev.bar(4));
|
||||
var channels: [2]IDEChannelRegister = undefined;
|
||||
channels[ATA_PRIMARY].base = if (BAR0 == 0) 0x1f0 else BAR0;
|
||||
channels[ATA_PRIMARY].ctrl = if (BAR1 == 0) 0x3F6 else BAR1;
|
||||
channels[ATA_SECONDARY].base = if (BAR2 == 0) 0x170 else BAR2;
|
||||
channels[ATA_SECONDARY].ctrl = if (BAR3 == 0) 0x376 else BAR3;
|
||||
channels[ATA_PRIMARY].bmide = (BAR4 & 0xFFFC); // Bus Master IDE
|
||||
channels[ATA_SECONDARY].bmide = (BAR4 & 0xFFFC) + 8; // Bus Master IDE
|
||||
|
||||
ide_device_0 = IDEDevice.init(channels[ATA_PRIMARY], 0) catch unreachable;
|
||||
ide_device_1 = IDEDevice.init(channels[ATA_PRIMARY], 1) catch unreachable;
|
||||
ide_device_2 = IDEDevice.init(channels[ATA_SECONDARY], 0) catch unreachable;
|
||||
ide_device_3 = IDEDevice.init(channels[ATA_SECONDARY], 1) catch unreachable;
|
||||
}
|
||||
8
src/driver/index.zig
Normal file
8
src/driver/index.zig
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// pub usingnamespace @import("../common.zig");
|
||||
|
||||
// pub const kernel = @import("../index.zig");
|
||||
|
||||
pub const x86 = @import("../arch/x86/index.zig");
|
||||
|
||||
pub const ide = @import("ide.zig");
|
||||
pub const virtio = @import("virtio.zig");
|
||||
48
src/driver/virtio.zig
Normal file
48
src/driver/virtio.zig
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
usingnamespace @import("index.zig");
|
||||
|
||||
pub fn init(dev: kernel.pci.PciDevice) void {
|
||||
kernel.println("-- virtio-block init --", .{});
|
||||
dev.format();
|
||||
assert(dev.header_type() == 0x0); // mass storage device
|
||||
assert(dev.subsystem() == 0x2); // virtio-block
|
||||
|
||||
const intr_line = dev.config_read(u8, 0x3c);
|
||||
const intr_pin = dev.config_read(u8, 0x3d);
|
||||
const min_grant = dev.config_read(u8, 0x3e);
|
||||
const max_lat = dev.config_read(u8, 0x3f);
|
||||
kernel.println("{x} {} {} {}", .{ intr_line, intr_pin, min_grant, max_lat });
|
||||
|
||||
// all virtio
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | dev features |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | guest features |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | queue address |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | queue size | queue notify |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | device status | isr status |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
// println("dev feats =0x{x}", dev.config_read(u32, 0x10));
|
||||
// println("guest feats =0x{x}", dev.config_read(u32, 0x14));
|
||||
// println("queue addr =0x{x}", dev.config_read(u32, 0x18));
|
||||
// println("queue size =0x{x}", dev.config_read(u16, 0x1c));
|
||||
// println("queue select =0x{x}", dev.config_read(u16, 0x1e));
|
||||
// println("queue notify =0x{x}", dev.config_read(u16, 0x20));
|
||||
// println("device status=0x{x}", dev.config_read(u8, 0x22));
|
||||
// println("isr status =0x{x}", dev.config_read(u8, 0x23));
|
||||
|
||||
// all virtio-block
|
||||
// println("Total Sector Count={}", dev.config_read(u32, 0x24));
|
||||
// println("Total Sector Count={}", dev.config_read(u32, 0x28));
|
||||
// println("Maximum Seg Size ={}", dev.config_read(u16, 0x2c));
|
||||
// println("Maximum Seg Count ={}", dev.config_read(u32, 0x30));
|
||||
// println("Cylinder Count ={}", dev.config_read(u16, 0x34));
|
||||
// println("Head Count ={}", dev.config_read(u8, 0x36));
|
||||
// println("Sector Count ={}", dev.config_read(u8, 0x37));
|
||||
// println("Block Length ={}", dev.config_read(u8, 0x38));
|
||||
}
|
||||
|
|
@ -1,3 +1,25 @@
|
|||
pub const dq = @import("delta_queue.zig");
|
||||
// pub usingnamespace @import("common.zig");
|
||||
// pub usingnamespace @import("ring_buffer.zig");
|
||||
|
||||
pub const vga = @import("vga.zig");
|
||||
pub const main = @import("main.zig");
|
||||
pub const multiboot = @import("multiboot2.zig");
|
||||
|
||||
//drivers
|
||||
pub const driver = @import("driver/index.zig");
|
||||
|
||||
//arch
|
||||
pub const x86 = @import("arch/x86/index.zig");
|
||||
|
||||
//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 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");
|
||||
|
|
|
|||
17
src/layout.zig
Normal file
17
src/layout.zig
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
//https://wiki.osdev.org/Memory_Map_(x86)
|
||||
// virtual memory layout of the kernel
|
||||
|
||||
const kiB = 1024;
|
||||
const MiB = 1024 * kiB;
|
||||
const GiB = 1024 * MiB;
|
||||
|
||||
// zig fmt: off
|
||||
pub const KSTACK = 0x80000; // todo: move to .bss
|
||||
pub const KERNEL = 1 * MiB;
|
||||
pub const IDENTITY = 4 * MiB; // 0->4MiB
|
||||
|
||||
pub const HEAP = 8 * MiB;
|
||||
pub const HEAP_END = 0x01000000;
|
||||
pub const USER_STACKS = 0x01000000;
|
||||
pub const USER_STACKS_END = 0x02000000;
|
||||
// zig fmt: on
|
||||
67
src/main.zig
67
src/main.zig
|
|
@ -1,38 +1,49 @@
|
|||
usingnamespace @import("multiboot2.zig");
|
||||
usingnamespace @import("vga.zig");
|
||||
const std = @import("std");
|
||||
const builtin = std.builtin;
|
||||
const kernel = @import("index.zig");
|
||||
|
||||
const arch = @import("arch/x86/lib/index.zig");
|
||||
const x86 = @import("arch/x86/main.zig");
|
||||
const multiboot = @import("multiboot2.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 FLAGS = ALIGN | MEMINFO; // Combine the flags.
|
||||
|
||||
const assert = @import("std").debug.assert;
|
||||
const pci = @import("pci.zig");
|
||||
const console = @import("console.zig");
|
||||
break :multiboot kernel.multiboot.MultibootHeader{
|
||||
.magic = MAGIC,
|
||||
.flags = FLAGS,
|
||||
.checksum = ~(MAGIC +% FLAGS) +% 1,
|
||||
};
|
||||
};
|
||||
|
||||
// arch independant initialization
|
||||
export fn kmain(magic: u32, info_addr: u32) noreturn {
|
||||
println("--- hello x86_main ---");
|
||||
assert(magic == MULTIBOOT2_BOOTLOADER_MAGIC);
|
||||
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 --- ", .{});
|
||||
|
||||
const info = multiboot.load(info_addr);
|
||||
console.initialize();
|
||||
// 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;
|
||||
|
||||
println("--- hello x86_main ---");
|
||||
// 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);
|
||||
|
||||
x86.x86_main(&info);
|
||||
// const sig = buf[56..58];
|
||||
// kernel.vga.println("sig: {x}", .{sig});
|
||||
|
||||
// pagefault_test(0xfeffc000);
|
||||
|
||||
println("--- end ---");
|
||||
while (true) {}
|
||||
// kernel.task.terminate();
|
||||
}
|
||||
|
||||
fn pagefault_test(addr: u32) void {
|
||||
const ptr = @intToPtr(*volatile u8, addr);
|
||||
var a: u8 = ptr.*;
|
||||
printf("a = {}\n", a);
|
||||
|
||||
ptr.* += 1;
|
||||
|
||||
printf("a = {}\n", ptr.*);
|
||||
}
|
||||
// pub fn panic(a: []const u8, b: ?*builtin.StackTrace) noreturn {
|
||||
// kernel.vga.println("{}", .{a});
|
||||
// kernel.vga.println("{}", .{b});
|
||||
// while (true) asm volatile ("hlt");
|
||||
// }
|
||||
|
|
|
|||
138
src/multiboot.zig
Normal file
138
src/multiboot.zig
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
// MULTIBOOT1
|
||||
// https://www.gnu.org/software/grub/manual/multiboot/multiboot.html
|
||||
|
||||
// This should be in EAX.
|
||||
pub const MULTIBOOT_BOOTLOADER_MAGIC = 0x2BADB002;
|
||||
|
||||
// Is there basic lower/upper memory information?
|
||||
pub const MULTIBOOT_INFO_MEMORY = 0x00000001;
|
||||
// Is there a full memory map?
|
||||
pub const MULTIBOOT_INFO_MEM_MAP = 0x00000040;
|
||||
|
||||
// System information structure passed by the bootloader.
|
||||
pub const MultibootInfo = packed struct {
|
||||
// Multiboot info version number.
|
||||
flags: u32,
|
||||
|
||||
// Available memory from BIOS.
|
||||
// present if flags[0]
|
||||
mem_lower: u32,
|
||||
mem_upper: u32,
|
||||
|
||||
// present if flags[1]
|
||||
boot_device: u32,
|
||||
|
||||
// present if flags[2]
|
||||
cmdline: u32,
|
||||
|
||||
// Boot-Module list.
|
||||
mods_count: u32,
|
||||
mods_addr: u32,
|
||||
|
||||
syms: packed union {
|
||||
// present if flags[4]
|
||||
nlist: packed struct {
|
||||
tabsize: u32,
|
||||
strsize: u32,
|
||||
addr: u32,
|
||||
_reserved: u32,
|
||||
},
|
||||
// present if flags[5]
|
||||
shdr: packed struct {
|
||||
num: u32,
|
||||
size: u32,
|
||||
addr: u32,
|
||||
shndx: u32,
|
||||
},
|
||||
},
|
||||
|
||||
// Memory Mapping buffer.
|
||||
// present if flags[6]
|
||||
mmap_length: u32,
|
||||
mmap_addr: u32,
|
||||
|
||||
// Drive Info buffer.
|
||||
drives_length: u32,
|
||||
drives_addr: u32,
|
||||
|
||||
// ROM configuration table.
|
||||
config_table: u32,
|
||||
|
||||
// Boot Loader Name.
|
||||
boot_loader_name: u32,
|
||||
|
||||
// APM table.
|
||||
apm_table: u32,
|
||||
|
||||
// Video.
|
||||
vbe_control_info: u32,
|
||||
vbe_mode_info: u32,
|
||||
vbe_mode: u16,
|
||||
vbe_interface_seg: u16,
|
||||
vbe_interface_off: u16,
|
||||
vbe_interface_len: u16,
|
||||
|
||||
////
|
||||
// Return the ending address of the last module.
|
||||
//
|
||||
pub fn lastModuleEnd(self: *const MultibootInfo) usize {
|
||||
if (self.mods_count > 0) {
|
||||
const mods = @intToPtr([*]MultibootModule, self.mods_addr);
|
||||
return mods[self.mods_count - 1].mod_end;
|
||||
} else {
|
||||
return self.mods_addr;
|
||||
}
|
||||
}
|
||||
|
||||
////
|
||||
// Load all the modules passed by the bootloader.
|
||||
//
|
||||
// pub fn loadModules(self: *const MultibootInfo) void {
|
||||
// const mods = @intToPtr([*]MultibootModule, self.mods_addr)[0..self.mods_count];
|
||||
|
||||
// for (mods) |mod| {
|
||||
// const cmdline = cstr.toSlice(@intToPtr([*]u8, mod.cmdline));
|
||||
// tty.step("Loading \"{}\"", cmdline);
|
||||
|
||||
// _ = Process.create(mod.mod_start, null);
|
||||
// // TODO: deallocate the original memory.
|
||||
|
||||
// tty.stepOK();
|
||||
// }
|
||||
// }
|
||||
};
|
||||
|
||||
// Types of memory map entries.
|
||||
pub const MULTIBOOT_MEMORY_AVAILABLE = 1;
|
||||
pub const MULTIBOOT_MEMORY_RESERVED = 2;
|
||||
|
||||
// Entries in the memory map.
|
||||
pub const MultibootMMapEntry = packed struct {
|
||||
size: u32,
|
||||
addr: u64,
|
||||
len: u64,
|
||||
type: u32,
|
||||
};
|
||||
|
||||
pub const MultibootModule = packed struct {
|
||||
// The memory used goes from bytes 'mod_start' to 'mod_end-1' inclusive.
|
||||
mod_start: u32,
|
||||
mod_end: u32,
|
||||
|
||||
cmdline: u32, // Module command line.
|
||||
pad: u32, // Padding to take it to 16 bytes (must be zero).
|
||||
};
|
||||
|
||||
// Multiboot structure to be read by the bootloader.
|
||||
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.
|
||||
// following fields are used if flag bit 16 is specified
|
||||
header_addr: u32 = 0,
|
||||
load_addr: u32 = 0,
|
||||
load_end_addr: u32 = 0,
|
||||
bss_end_addr: u32 = 0,
|
||||
entry_addr: u32 = 0,
|
||||
};
|
||||
// NOTE: this structure is incomplete.
|
||||
97
src/pci.zig
97
src/pci.zig
|
|
@ -1,97 +0,0 @@
|
|||
const arch = @import("arch/x86/lib/index.zig");
|
||||
|
||||
const PCI_CONFIG_ADDRESS = 0xCF8;
|
||||
const PCI_CONFIG_DATA = 0xCFC;
|
||||
const vga = @import("vga.zig");
|
||||
|
||||
pub const PciAddress = packed struct {
|
||||
offset: u8,
|
||||
function: u3,
|
||||
slot: u5,
|
||||
bus: u8,
|
||||
reserved: u7,
|
||||
enable: u1,
|
||||
};
|
||||
|
||||
pub const PciDevice = struct {
|
||||
bus: u8,
|
||||
slot: u5,
|
||||
function: u3,
|
||||
device: u16,
|
||||
vendor: u16,
|
||||
class: u8,
|
||||
subclass: u8,
|
||||
header_type: u8,
|
||||
|
||||
pub fn init(bus: u8, slot: u5, function: u3) ?PciDevice {
|
||||
var pcidevice = PciDevice{
|
||||
.bus = bus,
|
||||
.slot = slot,
|
||||
.function = function,
|
||||
.device = undefined,
|
||||
.class = undefined,
|
||||
.subclass = undefined,
|
||||
.header_type = undefined,
|
||||
.vendor = undefined,
|
||||
};
|
||||
pcidevice.vendor = pci_config_read_word(pcidevice, 0);
|
||||
if (pcidevice.vendor == 0xffff)
|
||||
return null;
|
||||
pcidevice.device = pci_config_read_word(pcidevice, 2);
|
||||
pcidevice.subclass = pci_config_read_byte(pcidevice, 10);
|
||||
pcidevice.class = pci_config_read_byte(pcidevice, 11);
|
||||
pcidevice.header_type = pci_config_read_byte(pcidevice, 14);
|
||||
return (pcidevice);
|
||||
}
|
||||
|
||||
pub fn address(self: PciDevice, offset: u8) u32 {
|
||||
var addr = PciAddress{
|
||||
.enable = 1,
|
||||
.reserved = 0,
|
||||
.bus = self.bus,
|
||||
.slot = self.slot,
|
||||
.function = self.function,
|
||||
.offset = offset,
|
||||
};
|
||||
return (@bitCast(u32, addr));
|
||||
}
|
||||
|
||||
pub fn format(self: PciDevice) void {
|
||||
vga.printf("{}:{}.{} {x},{x}: {x} {x}\n", self.bus, self.slot, self.function, self.class, self.subclass, self.vendor, self.device);
|
||||
}
|
||||
|
||||
pub fn access(self: PciDevice, offset: u8) void {
|
||||
arch.outl(PCI_CONFIG_ADDRESS, self.address(offset));
|
||||
}
|
||||
|
||||
pub fn pci_config_read_byte(self: PciDevice, offset: u8) u8 {
|
||||
self.access(offset);
|
||||
return (arch.inb(PCI_CONFIG_DATA));
|
||||
}
|
||||
|
||||
pub fn pci_config_read_word(self: PciDevice, offset: u8) u16 {
|
||||
self.access(offset);
|
||||
return (arch.inw(PCI_CONFIG_DATA));
|
||||
}
|
||||
|
||||
pub fn pci_config_read_long(self: PciDevice, offset: u8) u32 {
|
||||
self.access(offset);
|
||||
return (arch.inl(PCI_CONFIG_DATA));
|
||||
}
|
||||
};
|
||||
|
||||
pub fn lspci() void {
|
||||
var slot: u5 = 0;
|
||||
while (true) {
|
||||
if (PciDevice.init(0, slot, 0)) |device| {
|
||||
device.format();
|
||||
var function: u3 = 0;
|
||||
while (true) {
|
||||
if (PciDevice.init(0, slot, function)) |vf|
|
||||
vf.format();
|
||||
if (function == 7) break else function += 1;
|
||||
}
|
||||
}
|
||||
if (slot == 31) break else slot += 1;
|
||||
}
|
||||
}
|
||||
189
src/pci/pci.zig
Normal file
189
src/pci/pci.zig
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
pub const std = @import("std");
|
||||
|
||||
pub const kernel = @import("index.zig");
|
||||
pub const x86 = @import("x86.zig");
|
||||
pub const driver = @import("driver/index.zig");
|
||||
|
||||
const PCI_CONFIG_ADDRESS = 0xCF8;
|
||||
const PCI_CONFIG_DATA = 0xCFC;
|
||||
|
||||
// https://wiki.osdev.org/Pci
|
||||
pub const PciAddress = packed struct {
|
||||
offset: u8,
|
||||
function: u3,
|
||||
slot: u5,
|
||||
bus: u8,
|
||||
reserved: u7,
|
||||
enable: u1,
|
||||
};
|
||||
|
||||
pub const PciDevice = struct {
|
||||
bus: u8,
|
||||
slot: u5,
|
||||
function: u3,
|
||||
vendor: u16 = undefined,
|
||||
|
||||
pub fn init(bus: u8, slot: u5, function: u3) ?PciDevice {
|
||||
var dev = PciDevice{ .bus = bus, .slot = slot, .function = function };
|
||||
dev.vendor = dev.vendor_id();
|
||||
if (dev.vendor == 0xffff) return null;
|
||||
return dev;
|
||||
}
|
||||
|
||||
pub fn address(self: PciDevice, offset: u8) u32 {
|
||||
var addr = PciAddress{
|
||||
.enable = 1,
|
||||
.reserved = 0,
|
||||
.bus = self.bus,
|
||||
.slot = self.slot,
|
||||
.function = self.function,
|
||||
.offset = offset,
|
||||
};
|
||||
return @bitCast(u32, addr);
|
||||
}
|
||||
|
||||
pub fn format(self: PciDevice) void {
|
||||
kernel.vga.print("{}:{}.{}", .{ self.bus, self.slot, self.function });
|
||||
kernel.vga.print(" {x},{x:2}", .{ self.class(), self.subclass() });
|
||||
kernel.vga.print(" 0x{x} 0x{x}", .{ self.vendor, self.device() });
|
||||
kernel.vga.println(" {}", .{if (self.driver()) |d| d.name else " (none)"});
|
||||
}
|
||||
|
||||
pub fn driver(self: PciDevice) ?Driver {
|
||||
var i: usize = 0;
|
||||
while (i < Drivers.len) : (i += 1) {
|
||||
var drv = Drivers[i];
|
||||
if (self.class() != drv.class or self.subclass() != drv.subclass)
|
||||
continue;
|
||||
if (drv.vendor) |v|
|
||||
if (self.vendor != v)
|
||||
continue;
|
||||
if (drv.subsystem) |ss|
|
||||
if (self.subsystem() != ss)
|
||||
continue;
|
||||
return drv;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | vendor ID | device ID |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | command | status |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | revision ID | prog IF | subclass | class |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |cache line size| latency timer | header type | bist |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
pub fn vendor_id(self: PciDevice) u16 {
|
||||
return self.config_read(u16, 0x0);
|
||||
}
|
||||
pub fn device(self: PciDevice) u16 {
|
||||
return self.config_read(u16, 0x2);
|
||||
}
|
||||
pub fn subclass(self: PciDevice) u8 {
|
||||
return self.config_read(u8, 0xa);
|
||||
}
|
||||
pub fn class(self: PciDevice) u8 {
|
||||
return self.config_read(u8, 0xb);
|
||||
}
|
||||
pub fn header_type(self: PciDevice) u8 {
|
||||
return self.config_read(u8, 0xe);
|
||||
}
|
||||
pub fn intr_line(self: PciDevice) u8 {
|
||||
return self.config_read(u8, 0x3c);
|
||||
}
|
||||
pub fn bar(self: PciDevice, comptime n: usize) u32 {
|
||||
std.debug.assert(n <= 5);
|
||||
return self.config_read(u32, 0x10 + 4 * n);
|
||||
}
|
||||
// only for header_type == 0
|
||||
pub fn subsystem(self: PciDevice) u16 {
|
||||
return self.config_read(u8, 0x2e);
|
||||
}
|
||||
|
||||
pub inline fn config_write(self: PciDevice, value: anytype, comptime offset: u8) void {
|
||||
// ask for access before writing config
|
||||
x86.outl(PCI_CONFIG_ADDRESS, self.address(offset));
|
||||
switch (@TypeOf(value)) {
|
||||
// read the correct size
|
||||
u8 => return x86.outb(PCI_CONFIG_DATA, value),
|
||||
u16 => return x86.outw(PCI_CONFIG_DATA, value),
|
||||
u32 => return x86.outl(PCI_CONFIG_DATA, value),
|
||||
else => @compileError("pci config space only supports writing u8, u16, u32."),
|
||||
}
|
||||
}
|
||||
|
||||
pub inline fn config_read(self: PciDevice, comptime size: type, comptime offset: u8) size {
|
||||
// ask for access before reading config
|
||||
x86.outl(PCI_CONFIG_ADDRESS, self.address(offset));
|
||||
switch (size) {
|
||||
// read the correct size
|
||||
u8 => return x86.inb(PCI_CONFIG_DATA),
|
||||
u16 => return x86.inw(PCI_CONFIG_DATA),
|
||||
u32 => return x86.inl(PCI_CONFIG_DATA),
|
||||
else => @compileError("pci config space only supports reading u8, u16, u32."),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Driver = struct {
|
||||
name: []const u8,
|
||||
class: u8,
|
||||
subclass: u8,
|
||||
vendor: ?u16 = null,
|
||||
subsystem: ?u16 = null,
|
||||
init: fn (PciDevice) void,
|
||||
};
|
||||
|
||||
const Drivers = [_]Driver{
|
||||
Driver{
|
||||
.name = "virtio-blk",
|
||||
.class = 0x1,
|
||||
.subclass = 0x0,
|
||||
.vendor = 0x1af4,
|
||||
.subsystem = 0x2,
|
||||
.init = driver.virtio.init,
|
||||
},
|
||||
Driver{
|
||||
.name = "ide-ata",
|
||||
.class = 0x1,
|
||||
.subclass = 0x1,
|
||||
.init = driver.ide.init,
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: factor 2 functions with a closure or a generator when released
|
||||
pub fn scan() void {
|
||||
var slot: u5 = 0;
|
||||
// 0..31
|
||||
while (slot < 31) : (slot += 1) {
|
||||
if (PciDevice.init(0, slot, 0)) |dev| {
|
||||
var function: u3 = 0;
|
||||
// 0..7
|
||||
while (function < 7) : (function += 1) {
|
||||
if (PciDevice.init(0, slot, function)) |vf| {
|
||||
if (vf.driver()) |d| d.init(vf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lspci() void {
|
||||
var slot: u5 = 0;
|
||||
kernel.vga.println("b:s.f c, s vendor device driver", .{});
|
||||
while (slot < 31) : (slot += 1) {
|
||||
if (PciDevice.init(0, slot, 0)) |dev| {
|
||||
var function: u3 = 0;
|
||||
// 0..7
|
||||
while (function < 7) : (function += 1) {
|
||||
if (PciDevice.init(0, slot, function)) |vf| {
|
||||
vf.format();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/ps2.zig
35
src/ps2.zig
|
|
@ -1,10 +1,9 @@
|
|||
const x86 = @import("arch/x86/lib/index.zig");
|
||||
const console = @import("console.zig");
|
||||
usingnamespace @import("index.zig");
|
||||
const x86 = @import("x86");
|
||||
|
||||
const PS2_DATA = 0x60;
|
||||
const PS2_STATUS = 0x64;
|
||||
const KEYMAP_MAX = 59;
|
||||
const KEYMAP_US = [_][2]u8{
|
||||
const KEYMAP_US = [_][]const u8{
|
||||
"\x00\x00",
|
||||
"\x00\x00", //escape
|
||||
"1!",
|
||||
|
|
@ -19,7 +18,7 @@ const KEYMAP_US = [_][2]u8{
|
|||
"0)",
|
||||
"-_",
|
||||
"=+",
|
||||
"\x00\x00", //backspace
|
||||
"\x08\x08", //backspace
|
||||
"\x00\x00", //tab
|
||||
"qQ",
|
||||
"wW",
|
||||
|
|
@ -68,25 +67,23 @@ const KEYMAP_US = [_][2]u8{
|
|||
|
||||
fn ps2_scancode() u8 {
|
||||
var scancode: u8 = 0;
|
||||
while (true) {
|
||||
if (x86.inb(PS2_DATA) != scancode) {
|
||||
scancode = x86.inb(PS2_DATA);
|
||||
if (scancode > 0)
|
||||
return scancode;
|
||||
}
|
||||
}
|
||||
while (true) if (x86.io.inb(PS2_DATA) != scancode) {
|
||||
scancode = x86.io.inb(PS2_DATA);
|
||||
if (scancode > 0) return scancode;
|
||||
};
|
||||
}
|
||||
|
||||
fn key_isrelease(scancode: u8) bool {
|
||||
return (scancode & 1 << 7 != 0);
|
||||
return scancode & (1 << 7) != 0;
|
||||
}
|
||||
|
||||
pub var keyboard_callback: fn (u8) void = undefined;
|
||||
|
||||
pub fn keyboard_handler() void {
|
||||
const scancode = ps2_scancode();
|
||||
const isrelease = key_isrelease(scancode);
|
||||
if (isrelease) {
|
||||
return;
|
||||
}
|
||||
const shift = false;
|
||||
console.keypress(KEYMAP_US[scancode][if (shift) 1 else 0]);
|
||||
if (scancode > KEYMAP_US.len) return;
|
||||
if (key_isrelease(scancode)) return; // don't process releases
|
||||
const shift = false; // don't know about modifiers yet
|
||||
const character = KEYMAP_US[scancode][if (shift) 1 else 0];
|
||||
if (keyboard_callback != undefined) keyboard_callback(character);
|
||||
}
|
||||
|
|
|
|||
32
src/ring_buffer.zig
Normal file
32
src/ring_buffer.zig
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
usingnamespace @import("index.zig");
|
||||
|
||||
pub fn Ring(comptime T: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
const Size = u10; // 0-1023
|
||||
const size = @import("std").math.maxInt(Size);
|
||||
allocator: std.mem.Allocator = undefined,
|
||||
buffer: *[size]T,
|
||||
task: ?*task.TaskNode = null,
|
||||
read_index: Size = 0,
|
||||
write_index: Size = 0,
|
||||
|
||||
pub fn init(ring: *Self, alloc: std.mem.Allocator) !void {
|
||||
ring.allocator = alloc;
|
||||
ring.buffer = try ring.allocator.create(@TypeOf(ring.buffer.*));
|
||||
}
|
||||
|
||||
pub fn write(ring: *Self, elem: T) void {
|
||||
ring.buffer[ring.write_index] = elem;
|
||||
ring.write_index +%= 1;
|
||||
if (ring.task) |t| task.unblock(t);
|
||||
}
|
||||
|
||||
pub fn read(ring: *Self) ?T {
|
||||
if (ring.write_index == ring.read_index) return null;
|
||||
const id = ring.read_index;
|
||||
ring.read_index +%= 1; // add with overflow to loop the ring
|
||||
return ring.buffer[id];
|
||||
}
|
||||
};
|
||||
}
|
||||
366
src/task.zig
Normal file
366
src/task.zig
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
const kernel = @import("kernel");
|
||||
const std = @import("std");
|
||||
const x86 = @import("x86");
|
||||
|
||||
var boot_task = Task{ .tid = 0, .esp = 0x47, .state = .Running, .born = true };
|
||||
var tid_counter: u16 = 1;
|
||||
|
||||
pub const TaskNode = std.TailQueue(*Task).Node;
|
||||
pub const SleepNode = kernel.dq.DeltaQueue(*TaskNode).Node;
|
||||
|
||||
pub var current_task: *TaskNode = &TaskNode.init(&boot_task);
|
||||
pub var cleaner_task: *TaskNode = undefined;
|
||||
pub var ready_tasks = std.TailQueue(*Task).init();
|
||||
pub var blocked_tasks = std.TailQueue(*Task).init();
|
||||
pub var terminated_tasks = std.TailQueue(*Task).init();
|
||||
pub var sleeping_tasks = kernel.dq.DeltaQueue(*TaskNode).init();
|
||||
|
||||
const STACK_SIZE = x86.PAGE_SIZE; // Size of thread stacks.
|
||||
|
||||
var timer_last_count: u64 = 0;
|
||||
pub fn update_time_used() void {
|
||||
const current_count = kernel.time.offset_us;
|
||||
const elapsed = current_count - timer_last_count;
|
||||
// if (current_task.data.tid == 1) kernel.vga.println("{} adding {} time", current_task.data.tid, elapsed);
|
||||
timer_last_count = current_count;
|
||||
current_task.data.time_used += elapsed;
|
||||
}
|
||||
|
||||
pub const TaskState = enum {
|
||||
Running, // <=> current_task
|
||||
ReadyToRun, // <=> inside of ready_tasks
|
||||
IOWait, // waiting to be woken up by IO
|
||||
Paused, // unpaused arbitrarily by another process
|
||||
Sleeping, // woken up by timer
|
||||
Terminated, // <=> inside of terminated_tasks, waiting to be destroyed
|
||||
};
|
||||
|
||||
pub const Task = struct {
|
||||
stack: *[STACK_SIZE]u8 = undefined,
|
||||
esp: usize,
|
||||
tid: u16,
|
||||
time_used: u64 = 0,
|
||||
born: bool = false,
|
||||
state: TaskState,
|
||||
//context: isr.Context,
|
||||
//cr3: usize,
|
||||
|
||||
pub fn create(entrypoint: usize) !*Task {
|
||||
// Allocate and initialize the thread structure.
|
||||
var t = try kernel.vmem.allocator.create(Task);
|
||||
errdefer kernel.vmem.allocator.destroy(t);
|
||||
|
||||
t.time_used = 0;
|
||||
t.state = .ReadyToRun;
|
||||
t.tid = tid_counter;
|
||||
tid_counter +%= 1;
|
||||
std.debug.assert(tid_counter != 0); //overflow
|
||||
|
||||
// allocate a new stack
|
||||
t.stack = try kernel.vmem.allocator.create([STACK_SIZE]u8);
|
||||
t.esp = @ptrToInt(t.stack) + STACK_SIZE;
|
||||
// if the tasks rets from its main function, it will go to terminate
|
||||
// NOTE: if terminate is called this way it has an incorrect ebp!
|
||||
t.esp -= 4;
|
||||
@intToPtr(*usize, t.esp).* = @ptrToInt(terminate);
|
||||
// this will be what ret goes to
|
||||
t.esp -= 4;
|
||||
@intToPtr(*usize, t.esp).* = entrypoint;
|
||||
// this will be popped into ebp
|
||||
t.esp -= 4;
|
||||
@intToPtr(*usize, t.esp).* = t.esp + 8;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
pub fn destroy(self: *Task) void {
|
||||
kernel.vmem.allocator.destroy(self.stack);
|
||||
kernel.vmem.allocator.destroy(self);
|
||||
}
|
||||
};
|
||||
|
||||
///ASM
|
||||
extern fn switch_tasks(new_esp: usize, old_esp_addr: usize) void;
|
||||
|
||||
pub fn new(entrypoint: usize) !*TaskNode {
|
||||
kernel.task.lock_scheduler();
|
||||
defer kernel.task.unlock_scheduler();
|
||||
|
||||
const node = try kernel.vmem.allocator.create(TaskNode);
|
||||
errdefer kernel.vmem.allocator.destroy(node);
|
||||
|
||||
node.data = try Task.create(entrypoint);
|
||||
ready_tasks.prepend(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
pub fn wakeup_tick(tick: usize) bool {
|
||||
kernel.task.lock_scheduler();
|
||||
defer kernel.task.unlock_scheduler();
|
||||
|
||||
kernel.task.sleeping_tasks.decrement(tick);
|
||||
var popped = false;
|
||||
while (kernel.task.sleeping_tasks.popZero()) |sleepnode| {
|
||||
const tasknode = sleepnode.data;
|
||||
tasknode.data.state = .ReadyToRun;
|
||||
kernel.vmem.allocator.destroy(sleepnode);
|
||||
ready_tasks.prepend(tasknode);
|
||||
popped = true;
|
||||
}
|
||||
return popped;
|
||||
}
|
||||
|
||||
// TODO: make a sleep without malloc
|
||||
pub fn usleep(usec: u64) !void {
|
||||
std.debug.assert(current_task.data.state == .Running);
|
||||
update_time_used();
|
||||
|
||||
const node = try kernel.vmem.allocator.create(SleepNode);
|
||||
|
||||
lock_scheduler();
|
||||
defer unlock_scheduler();
|
||||
|
||||
current_task.data.state = .Sleeping;
|
||||
node.data = current_task;
|
||||
node.counter = usec;
|
||||
sleeping_tasks.insert(node);
|
||||
schedule();
|
||||
}
|
||||
|
||||
pub fn block(state: TaskState) void {
|
||||
std.debug.assert(current_task.data.state == .Running);
|
||||
std.debug.assert(state != .Running);
|
||||
std.debug.assert(state != .ReadyToRun);
|
||||
update_time_used();
|
||||
|
||||
// println("blocking {} as {}", current_task.data.tid, state);
|
||||
|
||||
lock_scheduler();
|
||||
defer unlock_scheduler();
|
||||
|
||||
current_task.data.state = state;
|
||||
blocked_tasks.append(current_task);
|
||||
schedule();
|
||||
}
|
||||
|
||||
pub fn unblock(node: *TaskNode) void {
|
||||
if (node.data.state != .Paused and node.data.state != .IOWait) return;
|
||||
lock_scheduler();
|
||||
defer unlock_scheduler();
|
||||
|
||||
node.data.state = .ReadyToRun;
|
||||
blocked_tasks.remove(node);
|
||||
|
||||
// TODO: find a way to fastpath here, hard because of unblock inside of interrupts
|
||||
// if (current_task.data.state != .Running and ready_tasks.first == null) {
|
||||
// // Only one task was running before, fastpath
|
||||
// switch_to(node);
|
||||
// } else {
|
||||
// // There's at least one task on the "ready to run" queue already, so don't pre-empt
|
||||
// ready_tasks.append(node);
|
||||
// }
|
||||
ready_tasks.append(node);
|
||||
}
|
||||
|
||||
pub fn terminate() noreturn {
|
||||
std.debug.assert(current_task.data.state == .Running);
|
||||
|
||||
lock_scheduler();
|
||||
current_task.data.state = .Terminated;
|
||||
terminated_tasks.append(current_task);
|
||||
unblock(cleaner_task);
|
||||
unlock_scheduler();
|
||||
|
||||
preempt();
|
||||
|
||||
unreachable;
|
||||
}
|
||||
|
||||
pub fn cleaner_loop() noreturn {
|
||||
while (true) {
|
||||
if (terminated_tasks.popFirst()) |n| {
|
||||
notify("DESTROYING {}", .{n.data.tid});
|
||||
n.data.destroy();
|
||||
kernel.vmem.allocator.destroy(n);
|
||||
} else {
|
||||
notify("NOTHING TO CLEAN", .{});
|
||||
block(.Paused);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub var IRQ_disable_counter: usize = 0;
|
||||
pub var postpone_task_switches_counter: isize = 0; // this counter can go negative when we are scheduling after a postpone
|
||||
pub var postpone_task_switches_flag: bool = false;
|
||||
fn lock_scheduler() void {
|
||||
if (kernel.constants.SMP == false) {
|
||||
x86.instr.cli();
|
||||
IRQ_disable_counter += 1;
|
||||
postpone_task_switches_counter += 1;
|
||||
}
|
||||
}
|
||||
fn unlock_scheduler() void {
|
||||
if (kernel.constants.SMP == false) {
|
||||
std.debug.assert(IRQ_disable_counter > 0);
|
||||
postpone_task_switches_counter -= 1;
|
||||
if (postpone_task_switches_flag == true and postpone_task_switches_counter == 0) {
|
||||
postpone_task_switches_flag = false;
|
||||
notify("AFTER POSTPONE", .{});
|
||||
schedule();
|
||||
}
|
||||
IRQ_disable_counter -= 1;
|
||||
// must be the last instruction because we do interrupts inside interrupts
|
||||
if (IRQ_disable_counter == 0) x86.instr.sti();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn preempt() void {
|
||||
if (current_task.data.state != .Running and current_task.data.state != .Terminated) return;
|
||||
|
||||
update_time_used();
|
||||
if (ready_tasks.first == null) {
|
||||
notify("NO PREEMPT SINGLE TASK", .{});
|
||||
kernel.time.task_slice_remaining = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
lock_scheduler();
|
||||
schedule();
|
||||
unlock_scheduler();
|
||||
}
|
||||
|
||||
// expects:
|
||||
// - chosen is .ReadyToRun
|
||||
// - chosen is not in any scheduler lists
|
||||
// - current_task has been moved to a queue
|
||||
// - scheduler is locked
|
||||
// - the tasks being switched to will unlock_scheduler()
|
||||
pub fn switch_to(chosen: *TaskNode) void {
|
||||
std.debug.assert(chosen.data.state == .ReadyToRun);
|
||||
std.debug.assert(current_task.data.state != .Running);
|
||||
|
||||
// save old stack
|
||||
const old_task_esp_addr = ¤t_task.data.esp;
|
||||
|
||||
// switch states
|
||||
chosen.data.state = .Running;
|
||||
current_task = chosen;
|
||||
if (ready_tasks.first == null) kernel.time.task_slice_remaining = 0;
|
||||
if (ready_tasks.first != null) kernel.time.task_slice_remaining = kernel.time.TASK_SLICE;
|
||||
|
||||
// we don't have any startup code for tasks, so i do it here
|
||||
if (current_task.data.born == false) {
|
||||
current_task.data.born = true;
|
||||
unlock_scheduler();
|
||||
}
|
||||
|
||||
// don't inline the asm function, it needs to ret
|
||||
@call(
|
||||
.{ .modifier = .never_inline },
|
||||
switch_tasks,
|
||||
.{ chosen.data.esp, @ptrToInt(old_task_esp_addr) },
|
||||
);
|
||||
}
|
||||
|
||||
pub var CPU_idle_time: u64 = 0;
|
||||
pub var CPU_idle_start_time: u64 = 0;
|
||||
// expects:
|
||||
// lock_scheduler should be called before
|
||||
// unlock_scheduler should be called after
|
||||
// current_task is blocked or running (preemption)
|
||||
pub fn schedule() void {
|
||||
std.debug.assert(IRQ_disable_counter > 0);
|
||||
std.debug.assert(current_task.data.state != .ReadyToRun);
|
||||
|
||||
// postponed
|
||||
if (postpone_task_switches_counter != 0 and current_task.data.state == .Running) {
|
||||
postpone_task_switches_flag = true;
|
||||
notify("POSTPONING SCHEDULE", .{});
|
||||
return;
|
||||
}
|
||||
// next task
|
||||
if (ready_tasks.popFirst()) |t| {
|
||||
t.prev = null;
|
||||
t.next = null;
|
||||
|
||||
// notify("SWITCHING TO 0x{x}", t.data.esp);
|
||||
if (current_task.data.state == .Running) {
|
||||
current_task.data.state = .ReadyToRun;
|
||||
ready_tasks.append(current_task);
|
||||
}
|
||||
return switch_to(t);
|
||||
}
|
||||
// single task
|
||||
if (current_task.data.state == .Running) {
|
||||
notify("SINGLE TASK", .{});
|
||||
kernel.time.task_slice_remaining = 0;
|
||||
return;
|
||||
}
|
||||
// no tasks
|
||||
idle_mode();
|
||||
}
|
||||
|
||||
fn idle_mode() void {
|
||||
std.debug.assert(ready_tasks.first == null);
|
||||
std.debug.assert(current_task.data.state != .Running);
|
||||
std.debug.assert(current_task.data.state != .ReadyToRun);
|
||||
|
||||
notify("IDLE", .{});
|
||||
|
||||
// borrow the current task
|
||||
const borrow = current_task;
|
||||
|
||||
CPU_idle_start_time = kernel.time.offset_us; //for power management
|
||||
|
||||
while (true) { // idle loop
|
||||
if (ready_tasks.popFirst()) |t| { // found a new task
|
||||
CPU_idle_time += kernel.time.offset_us - CPU_idle_start_time; // count time as idle
|
||||
timer_last_count = kernel.time.offset_us; // don't count time as used
|
||||
// println("went into idle mode for {}usecs", time.offset_us - CPU_idle_start_time);
|
||||
|
||||
if (t == borrow) {
|
||||
t.data.state = .Running;
|
||||
return; //no need to ctx_switch we are already running this
|
||||
}
|
||||
return switch_to(t);
|
||||
} else { // no tasks ready, let the timer fire
|
||||
x86.instr.sti(); // enable interrupts to allow the timer to fire
|
||||
x86.instr.hlt(); // halt and wait for the timer to fire
|
||||
x86.instr.cli(); // disable interrupts again to see if there is something to do
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify(comptime message: []const u8, args: anytype) void {
|
||||
const bg = kernel.vga.background;
|
||||
const fg = kernel.vga.foreground;
|
||||
const cursor = kernel.vga.cursor;
|
||||
kernel.vga.background = fg;
|
||||
kernel.vga.foreground = bg;
|
||||
kernel.vga.cursor = 80 - message.len;
|
||||
kernel.vga.cursor_enabled = false;
|
||||
|
||||
kernel.vga.print(message, args);
|
||||
|
||||
kernel.vga.cursor_enabled = true;
|
||||
kernel.vga.cursor = cursor;
|
||||
kernel.vga.background = bg;
|
||||
kernel.vga.foreground = fg;
|
||||
}
|
||||
|
||||
pub fn format_short() void {
|
||||
kernel.vga.print("{}R {}B {}S", .{ ready_tasks.len, blocked_tasks.len, sleeping_tasks.len });
|
||||
}
|
||||
|
||||
pub fn format() void {
|
||||
update_time_used();
|
||||
|
||||
kernel.vga.println("{}", .{current_task.data});
|
||||
|
||||
var it = ready_tasks.first;
|
||||
while (it) |node| : (it = node.next) kernel.vga.println("{}", .{node.data});
|
||||
it = blocked_tasks.first;
|
||||
while (it) |node| : (it = node.next) kernel.vga.println("{}", .{node.data});
|
||||
var sit = sleeping_tasks.first;
|
||||
while (sit) |node| : (sit = node.next) kernel.vga.println("{} {}", .{ node.data.data, node.counter });
|
||||
}
|
||||
32
src/time.zig
Normal file
32
src/time.zig
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
const kernel = @import("index.zig");
|
||||
const x86 = @import("x86");
|
||||
|
||||
pub var offset_us: u64 = 0;
|
||||
pub var task_slice_remaining: u64 = 0;
|
||||
pub var TASK_SLICE: u64 = 50 * 1000;
|
||||
pub fn increment() void {
|
||||
const tick = x86.interrupt.tick; //us
|
||||
|
||||
offset_us += tick; //global time counter
|
||||
|
||||
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) kernel.task.preempt();
|
||||
}
|
||||
|
||||
pub fn uptime() void {
|
||||
var offset_ms: u64 = offset_us / 1000;
|
||||
const offset_s: u64 = offset_ms / 1000;
|
||||
offset_ms = @mod(offset_ms / 100, 10);
|
||||
|
||||
kernel.vga.print("{}.{:.3}", .{ offset_s, offset_ms });
|
||||
}
|
||||
|
||||
pub fn utilisation() void {
|
||||
kernel.vga.print("{}%", .{100 * (offset_us - kernel.task.CPU_idle_time) / offset_us});
|
||||
}
|
||||
132
src/vga.zig
132
src/vga.zig
|
|
@ -1,16 +1,16 @@
|
|||
const builtin = @import("builtin");
|
||||
const mem = @import("std").mem;
|
||||
const arch = @import("arch/x86/lib/index.zig");
|
||||
const std = @import("std");
|
||||
const kernel = @import("index.zig");
|
||||
|
||||
// VRAM buffer address in physical memory.
|
||||
pub const VRAM_ADDR = 0xB8000;
|
||||
pub const VRAM_SIZE = 0x8000;
|
||||
// Screen size.
|
||||
pub const VGA_WIDTH = 80;
|
||||
pub const VGA_HEIGHT = 25;
|
||||
pub const VGA_SIZE = VGA_WIDTH * VGA_HEIGHT;
|
||||
pub var vga = VGA.init(VRAM_ADDR);
|
||||
pub var vga = VGA{
|
||||
.vram = @intToPtr([*]VGAEntry, 0xb8000)[0..0x4000],
|
||||
.cursor = 80 * 2,
|
||||
.foreground = Color.Black,
|
||||
.background = Color.White,
|
||||
};
|
||||
|
||||
// Color codes.
|
||||
pub const Color = enum(u4) {
|
||||
|
|
@ -39,66 +39,76 @@ pub const VGAEntry = packed struct {
|
|||
background: Color,
|
||||
};
|
||||
|
||||
////
|
||||
// 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 printf(comptime format: []const u8, args: ...) void {
|
||||
var a = std.fmt.format({}, Errors, printCallback, format, args);
|
||||
}
|
||||
pub fn println(comptime format: []const u8, args: ...) void {
|
||||
var a = std.fmt.format({}, Errors, printCallback, format ++ "\n", args);
|
||||
pub fn print(comptime format: []const u8, args: anytype) void {
|
||||
try std.fmt.format(.{ .writeAll = printCallback }, format, args);
|
||||
}
|
||||
|
||||
fn printCallback(context: void, string: []const u8) Errors!void {
|
||||
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();
|
||||
}
|
||||
pub fn topbar() void {
|
||||
const bg = vga.background;
|
||||
const fg = vga.foreground;
|
||||
// println("topbar1");
|
||||
while (true) {
|
||||
const cursor = vga.cursor;
|
||||
vga.background = Color.Black;
|
||||
vga.foreground = Color.White;
|
||||
vga.cursor = 0;
|
||||
vga.cursor_enabled = false;
|
||||
|
||||
kernel.time.uptime();
|
||||
print(" | ", .{});
|
||||
kernel.time.utilisation();
|
||||
print(" | ", .{});
|
||||
kernel.task.format_short();
|
||||
println("", .{});
|
||||
|
||||
vga.cursor_enabled = true;
|
||||
vga.cursor = cursor;
|
||||
vga.background = bg;
|
||||
vga.foreground = fg;
|
||||
|
||||
kernel.task.usleep(50 * 1000) catch unreachable; // 60ms
|
||||
}
|
||||
}
|
||||
|
||||
fn printCallback(string: []const u8) anyerror!void {
|
||||
vga.writeString(string);
|
||||
}
|
||||
|
||||
// VGA status.
|
||||
pub const VGA = struct {
|
||||
const VGA = struct {
|
||||
vram: []VGAEntry,
|
||||
cursor: usize,
|
||||
cursor_enabled: bool = true,
|
||||
foreground: Color,
|
||||
background: Color,
|
||||
|
||||
////
|
||||
// Initialize the VGA status.
|
||||
//
|
||||
// Arguments:
|
||||
// vram: The address of the VRAM buffer.
|
||||
//
|
||||
// Returns:
|
||||
// A structure holding the VGA status.
|
||||
//
|
||||
pub fn init(vram: usize) VGA {
|
||||
return VGA{
|
||||
.vram = @intToPtr([*]VGAEntry, vram)[0..0x4000],
|
||||
.cursor = 0,
|
||||
.foreground = Color.Black,
|
||||
.background = Color.Brown,
|
||||
};
|
||||
}
|
||||
|
||||
////
|
||||
// Clear the screen.
|
||||
//
|
||||
pub fn clear(self: *VGA) void {
|
||||
mem.set(VGAEntry, self.vram[0..VGA_SIZE], self.entry(' '));
|
||||
std.mem.set(VGAEntry, self.vram[0..VGA_SIZE], self.entry(' '));
|
||||
|
||||
self.cursor = 0;
|
||||
self.cursor = 80; // skip 1 line for topbar
|
||||
self.updateCursor();
|
||||
}
|
||||
|
||||
|
|
@ -127,12 +137,9 @@ pub const VGA = struct {
|
|||
self.writeChar(' ');
|
||||
},
|
||||
// Backspace.
|
||||
// FIXME: hardcoded 8 here is horrible.
|
||||
8 => {
|
||||
'\x08' => {
|
||||
self.cursor -= 1;
|
||||
// self.writeChar(' ');
|
||||
self.vram[self.cursor] = self.entry(' ');
|
||||
// self.cursor -= 1;
|
||||
},
|
||||
// Any other character.
|
||||
else => {
|
||||
|
|
@ -140,7 +147,7 @@ pub const VGA = struct {
|
|||
self.cursor += 1;
|
||||
},
|
||||
}
|
||||
self.updateCursor();
|
||||
if (self.cursor_enabled) self.updateCursor();
|
||||
}
|
||||
|
||||
////
|
||||
|
|
@ -150,11 +157,8 @@ pub const VGA = struct {
|
|||
// string: String to be printed.
|
||||
//
|
||||
pub fn writeString(self: *VGA, string: []const u8) void {
|
||||
for (string) |char| {
|
||||
self.writeChar(char);
|
||||
}
|
||||
|
||||
self.updateCursor();
|
||||
for (string) |char| self.writeChar(char);
|
||||
if (self.cursor_enabled) self.updateCursor();
|
||||
}
|
||||
|
||||
////
|
||||
|
|
@ -162,12 +166,14 @@ pub const VGA = struct {
|
|||
//
|
||||
fn scrollDown(self: *VGA) void {
|
||||
const first = VGA_WIDTH; // Index of first line.
|
||||
const second = 2 * VGA_WIDTH; // Index of first line.
|
||||
const last = VGA_SIZE - VGA_WIDTH; // Index of last line.
|
||||
|
||||
// Copy all the screen (apart from the first line) up one line.
|
||||
mem.copy(VGAEntry, self.vram[0..last], self.vram[first..VGA_SIZE]);
|
||||
// std.mem.copy(VGAEntry, self.vram[0..last], self.vram[first .. VGA_SIZE]); // whole screen
|
||||
std.mem.copy(VGAEntry, self.vram[first..last], self.vram[second..VGA_SIZE]); // skip topbar
|
||||
// Clean the last line.
|
||||
mem.set(VGAEntry, self.vram[last..VGA_SIZE], self.entry(' '));
|
||||
std.mem.set(VGAEntry, self.vram[last..VGA_SIZE], self.entry(' '));
|
||||
|
||||
// Bring the cursor back to the beginning of the last line.
|
||||
self.cursor -= VGA_WIDTH;
|
||||
|
|
@ -178,10 +184,10 @@ pub const VGA = struct {
|
|||
// Use the software cursor as the source of truth.
|
||||
//
|
||||
pub fn updateCursor(self: *const VGA) void {
|
||||
arch.outb(0x3D4, 0x0F);
|
||||
arch.outb(0x3D5, @truncate(u8, self.cursor));
|
||||
arch.outb(0x3D4, 0x0E);
|
||||
arch.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));
|
||||
}
|
||||
|
||||
////
|
||||
|
|
@ -191,11 +197,11 @@ pub const VGA = struct {
|
|||
pub fn fetchCursor(self: *VGA) void {
|
||||
var cursor: usize = 0;
|
||||
|
||||
arch.outb(0x3D4, 0x0E);
|
||||
cursor |= usize(arch.inb(0x3D5)) << 8;
|
||||
kernel.x86.io.outb(0x3D4, 0x0E);
|
||||
cursor |= usize(kernel.x86.io.inb(0x3D5)) << 8;
|
||||
|
||||
arch.outb(0x3D4, 0x0F);
|
||||
cursor |= arch.inb(0x3D5);
|
||||
kernel.x86.outb(0x3D4, 0x0F);
|
||||
cursor |= kernel.x86.io.inb(0x3D5);
|
||||
|
||||
self.cursor = cursor;
|
||||
}
|
||||
|
|
|
|||
60
src/vmem.zig
Normal file
60
src/vmem.zig
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
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
|
||||
// stupid simple virtual memory allocator
|
||||
// - does 1:1 virtual/physical mapping
|
||||
// - no defragmentation
|
||||
// - no allocation bigger than a page
|
||||
|
||||
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
|
||||
|
||||
pub fn available() usize {
|
||||
return stack_index * x86.PAGE_SIZE;
|
||||
}
|
||||
|
||||
fn free(addr: usize) void {
|
||||
x86.paging.unmap(addr);
|
||||
stack[stack_index] = addr;
|
||||
stack_index += 1;
|
||||
}
|
||||
|
||||
// const Error = error{OutOfMemory};
|
||||
|
||||
fn alloc(
|
||||
ctx: *anyopaque,
|
||||
len: usize,
|
||||
ptr_align: u8,
|
||||
ret_addr: usize,
|
||||
) ?[*]u8 {
|
||||
// new allocation
|
||||
std.debug.assert(len < 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];
|
||||
try x86.paging.mmap(vaddr, null);
|
||||
return @intToPtr([*]u8, vaddr)[0..len];
|
||||
}
|
||||
|
||||
pub fn init() void {
|
||||
allocator = std.mem.Allocator{
|
||||
.ptr = undefined,
|
||||
.vtable = std.mem.Allocator.VTable{
|
||||
.alloc = alloc,
|
||||
.resize = undefined,
|
||||
.free = free,
|
||||
}
|
||||
// .reallocFn = realloc,
|
||||
// .shrinkFn = shrink,
|
||||
};
|
||||
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