first commit
boot ok vga ok console soon interrupt mapping ok isr triggers ok but doesn't iret still lots to port from rust
This commit is contained in:
commit
14ff4db74a
20 changed files with 1265 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
zig-cache
|
||||
25
README.md
Normal file
25
README.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
## hobby kernel in zig
|
||||
|
||||
slowly porting from rust.
|
||||
|
||||
### features
|
||||
|
||||
- vga frame buffer
|
||||
- interrupts
|
||||
- todo: terminal console
|
||||
- todo: memory mapping
|
||||
- todo: cfs scheduler
|
||||
|
||||
### dependencies
|
||||
|
||||
`ziglang` compiler
|
||||
|
||||
### compile
|
||||
|
||||
`zig build` compile and links a multiboot kernel, without a bootloader.
|
||||
|
||||
### test
|
||||
|
||||
`./run.sh qemu start`
|
||||
`./run.sh qemu monitor`
|
||||
`./run.sh gdb`
|
||||
18
build.zig
Normal file
18
build.zig
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
const Builder = @import("std").build.Builder;
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pub fn build(b: *Builder) void {
|
||||
const kernel = b.addExecutable("bzImage", "src/arch/x86/main.zig");
|
||||
kernel.addPackagePath("kernel", "src/index.zig");
|
||||
kernel.addPackagePath("arch", "src/arch/x86/lib/index.zig");
|
||||
kernel.setOutputDir("build");
|
||||
|
||||
kernel.addAssemblyFile("src/arch/x86/gdt.s");
|
||||
kernel.addAssemblyFile("src/arch/x86/isr.s");
|
||||
|
||||
kernel.setBuildMode(b.standardReleaseOptions());
|
||||
kernel.setTarget(builtin.Arch.i386, builtin.Os.freestanding, builtin.Abi.none);
|
||||
kernel.setLinkerScriptPath("src/arch/x86/linker.ld");
|
||||
|
||||
b.default_step.dependOn(&kernel.step);
|
||||
}
|
||||
38
run.sh
Executable file
38
run.sh
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
QEMU_SOCKET=/tmp/qemu.sock
|
||||
QEMU_MONITOR="socat - unix-connect:${QEMU_SOCKET}"
|
||||
QEMU_GDB_PORT=4242
|
||||
KERNEL=build/bzImage
|
||||
|
||||
qemu() {
|
||||
start() {
|
||||
sudo qemu-system-i386\
|
||||
-kernel ${KERNEL}\
|
||||
-gdb tcp::${QEMU_GDB_PORT}\
|
||||
-monitor unix:${QEMU_SOCKET},server,nowait\
|
||||
-display curses\
|
||||
-enable-kvm\
|
||||
$*
|
||||
}
|
||||
|
||||
monitor() {
|
||||
sudo ${QEMU_MONITOR}
|
||||
}
|
||||
|
||||
reload() {
|
||||
echo "stop" | ${QEMU_MONITOR} &>/dev/null
|
||||
echo "change ide1-cd0 ${KERNEL}" | ${QEMU_MONITOR} &>/dev/null
|
||||
echo "system_reset" | ${QEMU_MONITOR} &>/dev/null
|
||||
}
|
||||
|
||||
"$@"
|
||||
}
|
||||
|
||||
gdb() {
|
||||
gdb\
|
||||
-q\
|
||||
-symbols "${KERNEL}" \
|
||||
-ex "target remote :${QEMU_GDB_PORT}"\
|
||||
-ex "set arch i386"
|
||||
}
|
||||
|
||||
"$@"
|
||||
23
src/arch/x86/gdt.s
Normal file
23
src/arch/x86/gdt.s
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
.type loadGDT, @function
|
||||
|
||||
//
|
||||
// Load the GDT into the system registers.
|
||||
//
|
||||
// Arguments:
|
||||
// gdtr: Pointer to the GDTR.
|
||||
//
|
||||
loadGDT:
|
||||
mov +4(%esp), %eax // Fetch the gdtr parameter.
|
||||
lgdt (%eax) // Load the new GDT.
|
||||
|
||||
// Reload data segments (GDT entry 2: kernel data).
|
||||
mov $0x10, %ax
|
||||
mov %ax, %ds
|
||||
mov %ax, %es
|
||||
mov %ax, %fs
|
||||
mov %ax, %gs
|
||||
mov %ax, %ss
|
||||
|
||||
// Reload code segment (GDT entry 1: kernel code).
|
||||
ljmp $0x08, $1f
|
||||
1: ret
|
||||
133
src/arch/x86/gdt.zig
Normal file
133
src/arch/x86/gdt.zig
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
// const tty = @import("tty.zig");
|
||||
const x86 = @import("lib/index.zig");
|
||||
|
||||
// GDT segment selectors.
|
||||
pub const KERNEL_CODE = 0x08;
|
||||
pub const KERNEL_DATA = 0x10;
|
||||
pub const USER_CODE = 0x18;
|
||||
pub const USER_DATA = 0x20;
|
||||
pub const TSS_DESC = 0x28;
|
||||
|
||||
// Privilege level of segment selector.
|
||||
pub const KERNEL_RPL = 0b00;
|
||||
pub const USER_RPL = 0b11;
|
||||
|
||||
// Access byte values.
|
||||
const KERNEL = 0x90;
|
||||
const USER = 0xF0;
|
||||
const CODE = 0x0A;
|
||||
const DATA = 0x02;
|
||||
const TSS_ACCESS = 0x89;
|
||||
|
||||
// Segment flags.
|
||||
const PROTECTED = (1 << 2);
|
||||
const BLOCKS_4K = (1 << 3);
|
||||
|
||||
// Structure representing an entry in the GDT.
|
||||
const GDTEntry = packed struct {
|
||||
limit_low: u16,
|
||||
base_low: u16,
|
||||
base_mid: u8,
|
||||
access: u8,
|
||||
limit_high: u4,
|
||||
flags: u4,
|
||||
base_high: u8,
|
||||
};
|
||||
|
||||
// GDT descriptor register.
|
||||
const GDTRegister = packed struct {
|
||||
limit: u16,
|
||||
base: *const GDTEntry,
|
||||
};
|
||||
|
||||
// Task State Segment.
|
||||
const TSS = packed struct {
|
||||
unused1: u32,
|
||||
esp0: u32, // Stack to use when coming to ring 0 from ring > 0.
|
||||
ss0: u32, // Segment to use when coming to ring 0 from ring > 0.
|
||||
unused2: [22]u32,
|
||||
unused3: u16,
|
||||
iomap_base: u16, // Base of the IO bitmap.
|
||||
};
|
||||
|
||||
////
|
||||
// Generate a GDT entry structure.
|
||||
//
|
||||
// Arguments:
|
||||
// base: Beginning of the segment.
|
||||
// limit: Size of the segment.
|
||||
// access: Access byte.
|
||||
// flags: Segment flags.
|
||||
//
|
||||
fn makeEntry(base: usize, limit: usize, access: u8, flags: u4) GDTEntry {
|
||||
return GDTEntry{
|
||||
.limit_low = @truncate(u16, limit),
|
||||
.base_low = @truncate(u16, base),
|
||||
.base_mid = @truncate(u8, base >> 16),
|
||||
.access = @truncate(u8, access),
|
||||
.limit_high = @truncate(u4, limit >> 16),
|
||||
.flags = @truncate(u4, flags),
|
||||
.base_high = @truncate(u8, base >> 24),
|
||||
};
|
||||
}
|
||||
|
||||
// Fill in the GDT.
|
||||
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),
|
||||
makeEntry(0, 0xFFFFF, USER | CODE, PROTECTED | BLOCKS_4K),
|
||||
makeEntry(0, 0xFFFFF, USER | DATA, PROTECTED | BLOCKS_4K),
|
||||
makeEntry(0, 0, 0, 0), // TSS (fill in at runtime).
|
||||
};
|
||||
|
||||
// GDT descriptor register pointing at the GDT.
|
||||
var gdtr = GDTRegister{
|
||||
.limit = u16(@sizeOf(@typeOf(gdt))),
|
||||
.base = &gdt[0],
|
||||
};
|
||||
|
||||
// Instance of the Task State Segment.
|
||||
var tss = TSS{
|
||||
.unused1 = 0,
|
||||
.esp0 = undefined,
|
||||
.ss0 = KERNEL_DATA,
|
||||
.unused2 = []u32{0} ** 22,
|
||||
.unused3 = 0,
|
||||
.iomap_base = @sizeOf(TSS),
|
||||
};
|
||||
|
||||
////
|
||||
// Set the kernel stack to use when interrupting user mode.
|
||||
//
|
||||
// Arguments:
|
||||
// esp0: Stack for Ring 0.
|
||||
//
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
// tty.stepOK();
|
||||
}
|
||||
58
src/arch/x86/idt.zig
Normal file
58
src/arch/x86/idt.zig
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// https://wiki.osdev.org/IDT
|
||||
const x86 = @import("lib/index.zig");
|
||||
const interrupt = @import("interrupt.zig");
|
||||
const gdt = @import("gdt.zig");
|
||||
|
||||
// Types of gates.
|
||||
pub const INTERRUPT_GATE = 0x8E;
|
||||
pub const SYSCALL_GATE = 0xEE;
|
||||
|
||||
// Structure representing an entry in the IDT.
|
||||
const IDTEntry = packed struct {
|
||||
offset_low: u16,
|
||||
selector: u16,
|
||||
zero: u8,
|
||||
flags: u8,
|
||||
offset_high: u16,
|
||||
};
|
||||
|
||||
// IDT descriptor register.
|
||||
const IDTRegister = packed struct {
|
||||
limit: u16,
|
||||
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:
|
||||
// n: Index of the gate.
|
||||
// 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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
////
|
||||
// Initialize the Interrupt Descriptor Table.
|
||||
//
|
||||
pub fn initialize() void {
|
||||
interrupt.initialize();
|
||||
x86.lidt(@ptrToInt(&idtr));
|
||||
}
|
||||
188
src/arch/x86/interrupt.zig
Normal file
188
src/arch/x86/interrupt.zig
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
const x86 = @import("lib/index.zig");
|
||||
const isr = @import("isr.zig");
|
||||
|
||||
// PIC ports.
|
||||
const PIC1_CMD = 0x20;
|
||||
const PIC1_DATA = 0x21;
|
||||
const PIC2_CMD = 0xA0;
|
||||
const PIC2_DATA = 0xA1;
|
||||
// PIC commands:
|
||||
const ISR_READ = 0x0B; // Read the In-Service Register.
|
||||
const ACK = 0x20; // Acknowledge interrupt.
|
||||
// Initialization Control Words commands.
|
||||
const ICW1_INIT = 0x10;
|
||||
const ICW1_ICW4 = 0x01;
|
||||
const ICW4_8086 = 0x01;
|
||||
// write 0 to wait
|
||||
const WAIT_PORT = 0x80;
|
||||
// Interrupt Vector offsets of exceptions.
|
||||
const EXCEPTION_0 = 0;
|
||||
const EXCEPTION_31 = EXCEPTION_0 + 31;
|
||||
// Interrupt Vector offsets of IRQs.
|
||||
const IRQ_0 = EXCEPTION_31 + 1;
|
||||
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)
|
||||
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();
|
||||
}
|
||||
|
||||
inline fn picwait() void {
|
||||
x86.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);
|
||||
|
||||
switch (n) {
|
||||
// Exceptions.
|
||||
EXCEPTION_0...EXCEPTION_31 => {
|
||||
handlers[n]();
|
||||
},
|
||||
|
||||
// IRQs.
|
||||
IRQ_0...IRQ_15 => {
|
||||
const irq = n - IRQ_0;
|
||||
// if (spuriousIRQ(irq)) return;
|
||||
|
||||
startOfInterrupt(irq);
|
||||
handlers[n]();
|
||||
endOfInterrupt(irq);
|
||||
},
|
||||
|
||||
// Syscalls.
|
||||
// SYSCALL => {
|
||||
// const syscall_n = isr.context.registers.eax;
|
||||
// if (syscall_n < syscall.handlers.len) {
|
||||
// syscall.handlers[syscall_n]();
|
||||
// } else {
|
||||
// syscall.invalid();
|
||||
// }
|
||||
// },
|
||||
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
// If no user thread is ready to run, halt here and wait for interrupts.
|
||||
// if (scheduler.current() == null) {
|
||||
// x86.sti();
|
||||
// x86.hlt();
|
||||
// }
|
||||
}
|
||||
|
||||
inline fn spuriousIRQ(irq: u8) bool {
|
||||
// Only IRQ 7 and IRQ 15 can be spurious.
|
||||
if (irq != 7) return false;
|
||||
// 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);
|
||||
|
||||
// Verify whether IRQ7 is set in the ISR.
|
||||
return (in_service & (1 << 7)) == 0;
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
maskIRQ(irq, true);
|
||||
x86.outb(PIC1_CMD, ACK);
|
||||
}
|
||||
}
|
||||
|
||||
inline fn endOfInterrupt(irq: u8) void {
|
||||
// unmask the irq and then ACK
|
||||
if (irq >= 8) {
|
||||
maskIRQ(irq, false);
|
||||
x86.outb(PIC2_CMD, ACK);
|
||||
} else {
|
||||
maskIRQ(irq, false);
|
||||
x86.outb(PIC1_CMD, ACK);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(n: u8, handler: fn () void) void {
|
||||
handlers[n] = handler;
|
||||
}
|
||||
|
||||
pub fn registerIRQ(irq: u8, handler: fn () void) void {
|
||||
register(IRQ_0 + irq, handler);
|
||||
maskIRQ(irq, false); // Unmask the IRQ.
|
||||
}
|
||||
|
||||
fn remapPIC() void {
|
||||
// ICW1: start initialization sequence.
|
||||
x86.outb(PIC1_CMD, ICW1_INIT | ICW1_ICW4);
|
||||
picwait();
|
||||
x86.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
|
||||
picwait();
|
||||
x86.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);
|
||||
picwait();
|
||||
x86.outb(PIC2_DATA, 2);
|
||||
picwait();
|
||||
|
||||
// ICW4: 80x86 mode.
|
||||
x86.outb(PIC1_DATA, ICW4_8086);
|
||||
picwait();
|
||||
x86.outb(PIC2_DATA, ICW4_8086);
|
||||
picwait();
|
||||
|
||||
// Mask all IRQs.
|
||||
x86.outb(PIC1_DATA, 0xFF);
|
||||
picwait();
|
||||
x86.outb(PIC2_DATA, 0xFF);
|
||||
picwait();
|
||||
}
|
||||
|
||||
pub fn maskIRQ(irq: u8, 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.
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
////
|
||||
// Initialize interrupts.
|
||||
//
|
||||
pub fn initialize() void {
|
||||
remapPIC();
|
||||
isr.install();
|
||||
}
|
||||
102
src/arch/x86/isr.s
Normal file
102
src/arch/x86/isr.s
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// Kernel stack for interrupt handling.
|
||||
KERNEL_STACK = 0x80000
|
||||
// GDT selectors.
|
||||
KERNEL_DS = 0x10
|
||||
USER_DS = 0x23
|
||||
|
||||
// Template for the Interrupt Service Routines.
|
||||
.macro isrGenerate n
|
||||
.align 4
|
||||
.type isr\n, @function
|
||||
|
||||
isr\n:
|
||||
// Push a dummy error code for interrupts that don't have one.
|
||||
.if (\n != 8 && !(\n >= 10 && \n <= 14) && \n != 17)
|
||||
push $0
|
||||
.endif
|
||||
push $\n // Push the interrupt number.
|
||||
jmp isrCommon // Jump to the common handler.
|
||||
.endmacro
|
||||
|
||||
// Common code for all Interrupt Service Routines.
|
||||
isrCommon:
|
||||
pusha // Save the registers state.
|
||||
|
||||
// Setup kernel data segment.
|
||||
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
|
||||
|
||||
call interruptDispatch // Handle the interrupt event.
|
||||
|
||||
// Restore the pointer to the context (of a different thread, potentially).
|
||||
mov context, %esp
|
||||
|
||||
// Setup user data segment.
|
||||
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.
|
||||
iret
|
||||
.type isrCommon, @function
|
||||
|
||||
// Exceptions.
|
||||
isrGenerate 0
|
||||
isrGenerate 1
|
||||
isrGenerate 2
|
||||
isrGenerate 3
|
||||
isrGenerate 4
|
||||
isrGenerate 5
|
||||
isrGenerate 6
|
||||
isrGenerate 7
|
||||
isrGenerate 8
|
||||
isrGenerate 9
|
||||
isrGenerate 10
|
||||
isrGenerate 11
|
||||
isrGenerate 12
|
||||
isrGenerate 13
|
||||
isrGenerate 14
|
||||
isrGenerate 15
|
||||
isrGenerate 16
|
||||
isrGenerate 17
|
||||
isrGenerate 18
|
||||
isrGenerate 19
|
||||
isrGenerate 20
|
||||
isrGenerate 21
|
||||
isrGenerate 22
|
||||
isrGenerate 23
|
||||
isrGenerate 24
|
||||
isrGenerate 25
|
||||
isrGenerate 26
|
||||
isrGenerate 27
|
||||
isrGenerate 28
|
||||
isrGenerate 29
|
||||
isrGenerate 30
|
||||
isrGenerate 31
|
||||
|
||||
// IRQs.
|
||||
isrGenerate 32
|
||||
isrGenerate 33
|
||||
isrGenerate 34
|
||||
isrGenerate 35
|
||||
isrGenerate 36
|
||||
isrGenerate 37
|
||||
isrGenerate 38
|
||||
isrGenerate 39
|
||||
isrGenerate 40
|
||||
isrGenerate 41
|
||||
isrGenerate 42
|
||||
isrGenerate 43
|
||||
isrGenerate 44
|
||||
isrGenerate 45
|
||||
isrGenerate 46
|
||||
isrGenerate 47
|
||||
|
||||
// Syscalls.
|
||||
isrGenerate 128
|
||||
158
src/arch/x86/isr.zig
Normal file
158
src/arch/x86/isr.zig
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
const idt = @import("idt.zig");
|
||||
|
||||
// Interrupt Service Routines defined externally in assembly.
|
||||
extern fn isr0() void;
|
||||
extern fn isr1() void;
|
||||
extern fn isr2() void;
|
||||
extern fn isr3() void;
|
||||
extern fn isr4() void;
|
||||
extern fn isr5() void;
|
||||
extern fn isr6() void;
|
||||
extern fn isr7() void;
|
||||
extern fn isr8() void;
|
||||
extern fn isr9() void;
|
||||
extern fn isr10() void;
|
||||
extern fn isr11() void;
|
||||
extern fn isr12() void;
|
||||
extern fn isr13() void;
|
||||
extern fn isr14() void;
|
||||
extern fn isr15() void;
|
||||
extern fn isr16() void;
|
||||
extern fn isr17() void;
|
||||
extern fn isr18() void;
|
||||
extern fn isr19() void;
|
||||
extern fn isr20() void;
|
||||
extern fn isr21() void;
|
||||
extern fn isr22() void;
|
||||
extern fn isr23() void;
|
||||
extern fn isr24() void;
|
||||
extern fn isr25() void;
|
||||
extern fn isr26() void;
|
||||
extern fn isr27() void;
|
||||
extern fn isr28() void;
|
||||
extern fn isr29() void;
|
||||
extern fn isr30() void;
|
||||
extern fn isr31() void;
|
||||
extern fn isr32() void;
|
||||
extern fn isr33() void;
|
||||
extern fn isr34() void;
|
||||
extern fn isr35() void;
|
||||
extern fn isr36() void;
|
||||
extern fn isr37() void;
|
||||
extern fn isr38() void;
|
||||
extern fn isr39() void;
|
||||
extern fn isr40() void;
|
||||
extern fn isr41() void;
|
||||
extern fn isr42() void;
|
||||
extern fn isr43() void;
|
||||
extern fn isr44() void;
|
||||
extern fn isr45() void;
|
||||
extern fn isr46() void;
|
||||
extern fn isr47() void;
|
||||
extern fn isr128() void;
|
||||
|
||||
// Context saved by Interrupt Service Routines.
|
||||
pub const Context = packed struct {
|
||||
registers: Registers, // General purpose registers.
|
||||
|
||||
interrupt_n: u32, // Number of the interrupt.
|
||||
error_code: u32, // Associated error code (or 0).
|
||||
|
||||
// CPU status:
|
||||
eip: u32,
|
||||
cs: u32,
|
||||
eflags: u32,
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
// Structure holding general purpose registers as saved by PUSHA.
|
||||
pub const Registers = packed struct {
|
||||
edi: u32,
|
||||
esi: u32,
|
||||
ebp: u32,
|
||||
esp: u32,
|
||||
ebx: u32,
|
||||
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.
|
||||
pub export var context: *volatile Context = undefined;
|
||||
////
|
||||
// Install the Interrupt Service Routines in the IDT.
|
||||
//
|
||||
pub fn install() 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);
|
||||
idt.setGate(4, idt.INTERRUPT_GATE, isr4);
|
||||
idt.setGate(5, idt.INTERRUPT_GATE, isr5);
|
||||
idt.setGate(6, idt.INTERRUPT_GATE, isr6);
|
||||
idt.setGate(7, idt.INTERRUPT_GATE, isr7);
|
||||
idt.setGate(8, idt.INTERRUPT_GATE, isr8);
|
||||
idt.setGate(9, idt.INTERRUPT_GATE, isr9);
|
||||
idt.setGate(10, idt.INTERRUPT_GATE, isr10);
|
||||
idt.setGate(11, idt.INTERRUPT_GATE, isr11);
|
||||
idt.setGate(12, idt.INTERRUPT_GATE, isr12);
|
||||
idt.setGate(13, idt.INTERRUPT_GATE, isr13);
|
||||
idt.setGate(14, idt.INTERRUPT_GATE, isr14);
|
||||
idt.setGate(15, idt.INTERRUPT_GATE, isr15);
|
||||
idt.setGate(16, idt.INTERRUPT_GATE, isr16);
|
||||
idt.setGate(17, idt.INTERRUPT_GATE, isr17);
|
||||
idt.setGate(18, idt.INTERRUPT_GATE, isr18);
|
||||
idt.setGate(19, idt.INTERRUPT_GATE, isr19);
|
||||
idt.setGate(20, idt.INTERRUPT_GATE, isr20);
|
||||
idt.setGate(21, idt.INTERRUPT_GATE, isr21);
|
||||
idt.setGate(22, idt.INTERRUPT_GATE, isr22);
|
||||
idt.setGate(23, idt.INTERRUPT_GATE, isr23);
|
||||
idt.setGate(24, idt.INTERRUPT_GATE, isr24);
|
||||
idt.setGate(25, idt.INTERRUPT_GATE, isr25);
|
||||
idt.setGate(26, idt.INTERRUPT_GATE, isr26);
|
||||
idt.setGate(27, idt.INTERRUPT_GATE, isr27);
|
||||
idt.setGate(28, idt.INTERRUPT_GATE, isr28);
|
||||
idt.setGate(29, idt.INTERRUPT_GATE, isr29);
|
||||
idt.setGate(30, idt.INTERRUPT_GATE, isr30);
|
||||
idt.setGate(31, idt.INTERRUPT_GATE, isr31);
|
||||
|
||||
// IRQs.
|
||||
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);
|
||||
}
|
||||
2
src/arch/x86/lib/index.zig
Normal file
2
src/arch/x86/lib/index.zig
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
use @import("io.zig");
|
||||
use @import("instructions.zig");
|
||||
39
src/arch/x86/lib/instructions.zig
Normal file
39
src/arch/x86/lib/instructions.zig
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
////
|
||||
// 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)
|
||||
);
|
||||
}
|
||||
|
||||
////
|
||||
// Completely stop the computer.
|
||||
//
|
||||
pub inline fn hang() noreturn {
|
||||
asm volatile ("cli");
|
||||
while (true) {
|
||||
asm volatile ("hlt");
|
||||
}
|
||||
}
|
||||
|
||||
pub inline fn sti() void {
|
||||
asm volatile ("sti");
|
||||
}
|
||||
|
||||
////
|
||||
// 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)
|
||||
);
|
||||
}
|
||||
30
src/arch/x86/lib/io.zig
Normal file
30
src/arch/x86/lib/io.zig
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
////
|
||||
// Read a byte from a port.
|
||||
//
|
||||
// Arguments:
|
||||
// port: Port from where to read.
|
||||
//
|
||||
// Returns:
|
||||
// The read byte.
|
||||
//
|
||||
pub inline fn inb(port: u16) u8 {
|
||||
return asm volatile ("inb %[port], %[result]"
|
||||
: [result] "={al}" (-> u8)
|
||||
: [port] "N{dx}" (port)
|
||||
);
|
||||
}
|
||||
|
||||
////
|
||||
// Write a byte on a port.
|
||||
//
|
||||
// Arguments:
|
||||
// port: Port where to write the value.
|
||||
// value: Value to be written.
|
||||
//
|
||||
pub inline fn outb(port: u16, value: u8) void {
|
||||
asm volatile ("outb %[value], %[port]"
|
||||
:
|
||||
: [value] "{al}" (value),
|
||||
[port] "N{dx}" (port)
|
||||
);
|
||||
}
|
||||
80
src/arch/x86/linker.ld
Normal file
80
src/arch/x86/linker.ld
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
ENTRY(_start)
|
||||
/* OUTPUT_FORMAT(elf32-i386) */
|
||||
|
||||
SECTIONS {
|
||||
/* VGA, cannot use section for this */
|
||||
VGA_PTR = 0xb8000;
|
||||
. = 0xb8000;
|
||||
. += 80 * 25 * 2;
|
||||
|
||||
. = 1M;
|
||||
/* ensure that the multiboot header is at the beginning */
|
||||
.multiboot :
|
||||
{
|
||||
/* KEEP otherwise it gets garbage collected by linker */
|
||||
KEEP(*(.multiboot))
|
||||
. = ALIGN(4K);
|
||||
}
|
||||
|
||||
.text :
|
||||
{
|
||||
*(.text .text.*)
|
||||
. = ALIGN(4K);
|
||||
}
|
||||
|
||||
.rodata :
|
||||
{
|
||||
*(.rodata .rodata.*)
|
||||
. = ALIGN(4K);
|
||||
}
|
||||
|
||||
.data :
|
||||
{
|
||||
*(.data.rel.ro.local*) *(.data.rel.ro .data.rel.ro.*) *(.data.*)
|
||||
. = 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); */
|
||||
/* } */
|
||||
}
|
||||
13
src/arch/x86/main.zig
Normal file
13
src/arch/x86/main.zig
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
use @import("kernel").main;
|
||||
const idt = @import("idt.zig");
|
||||
const gdt = @import("gdt.zig");
|
||||
const x86 = @import("lib/index.zig");
|
||||
|
||||
/// x86 specific intialization
|
||||
/// first entry point (see linker.ld)
|
||||
export nakedcc fn _start() noreturn {
|
||||
gdt.initialize();
|
||||
idt.initialize();
|
||||
x86.sti();
|
||||
kmain();
|
||||
}
|
||||
15
src/console.zig
Normal file
15
src/console.zig
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
const interrupt = @import("arch/x86/interrupt.zig");
|
||||
use @import("vga.zig");
|
||||
|
||||
var vga = VGA.init(VRAM_ADDR);
|
||||
|
||||
pub fn keyboard_handler() void {
|
||||
vga.writeString("hello");
|
||||
}
|
||||
|
||||
pub fn initialize() void {
|
||||
vga.clear();
|
||||
vga.writeString("zzzzzzzzzzzzzzzz");
|
||||
interrupt.registerIRQ(1, keyboard_handler);
|
||||
vga.writeString("aaaa");
|
||||
}
|
||||
2
src/index.zig
Normal file
2
src/index.zig
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub const vga = @import("vga.zig");
|
||||
pub const main = @import("main.zig");
|
||||
9
src/main.zig
Normal file
9
src/main.zig
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use @import("multiboot.zig");
|
||||
const console = @import("console.zig");
|
||||
const arch = @import("arch/x86/lib/index.zig");
|
||||
|
||||
// platform independant initialization
|
||||
pub fn kmain() noreturn {
|
||||
console.initialize();
|
||||
while (true) {}
|
||||
}
|
||||
128
src/multiboot.zig
Normal file
128
src/multiboot.zig
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// zig fmt: off
|
||||
const tty = @import("tty.zig");
|
||||
const cstr = @import("std").cstr;
|
||||
const Process = @import("process.zig").Process;
|
||||
|
||||
// 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.
|
||||
mem_lower: u32,
|
||||
mem_upper: u32,
|
||||
|
||||
// "root" partition.
|
||||
boot_device: u32,
|
||||
|
||||
// Kernel command line.
|
||||
cmdline: u32,
|
||||
|
||||
// Boot-Module list.
|
||||
mods_count: u32,
|
||||
mods_addr: u32,
|
||||
|
||||
// TODO: use the real types here.
|
||||
u: u128,
|
||||
|
||||
// Memory Mapping buffer.
|
||||
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 {
|
||||
const mods = @intToPtr([*]MultibootModule, self.mods_addr);
|
||||
return mods[self.mods_count - 1].mod_end;
|
||||
}
|
||||
|
||||
////
|
||||
// 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.
|
||||
const MultibootHeader = packed 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.
|
||||
};
|
||||
// NOTE: this structure is incomplete.
|
||||
|
||||
// Place the header at the very beginning of the binary.
|
||||
export const multiboot_header align(4) linksection(".multiboot") = multiboot: {
|
||||
const MAGIC = u32(0x1BADB002); // Magic number for validation.
|
||||
const ALIGN = u32(1 << 0); // Align loaded modules.
|
||||
const MEMINFO = u32(1 << 1); // Receive a memory map from the bootloader.
|
||||
const FLAGS = ALIGN | MEMINFO; // Combine the flags.
|
||||
|
||||
break :multiboot MultibootHeader {
|
||||
.magic = MAGIC,
|
||||
.flags = FLAGS,
|
||||
.checksum = ~(MAGIC +% FLAGS) +% 1,
|
||||
};
|
||||
};
|
||||
203
src/vga.zig
Normal file
203
src/vga.zig
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
const builtin = @import("builtin");
|
||||
const mem = @import("std").mem;
|
||||
const arch = @import("arch/x86/lib/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;
|
||||
|
||||
// Color codes.
|
||||
pub const Color = enum(u4) {
|
||||
Black = 0,
|
||||
Blue = 1,
|
||||
Green = 2,
|
||||
Cyan = 3,
|
||||
Red = 4,
|
||||
Magenta = 5,
|
||||
Brown = 6,
|
||||
LightGrey = 7,
|
||||
DarkGrey = 8,
|
||||
LightBlue = 9,
|
||||
LightGreen = 10,
|
||||
LightCyan = 11,
|
||||
LightRed = 12,
|
||||
LightMagenta = 13,
|
||||
LightBrown = 14,
|
||||
White = 15,
|
||||
};
|
||||
|
||||
// Character with attributes.
|
||||
pub const VGAEntry = packed struct {
|
||||
char: u8,
|
||||
foreground: Color,
|
||||
background: Color,
|
||||
};
|
||||
|
||||
////
|
||||
// Enable hardware cursor.
|
||||
//
|
||||
pub fn enableCursor() void {
|
||||
outb(0x3D4, 0x0A);
|
||||
outb(0x3D5, 0x00);
|
||||
}
|
||||
|
||||
////
|
||||
// Disable hardware cursor.
|
||||
//
|
||||
pub fn disableCursor() void {
|
||||
outb(0x3D4, 0x0A);
|
||||
outb(0x3D5, 1 << 5);
|
||||
}
|
||||
|
||||
// VGA status.
|
||||
pub const VGA = struct {
|
||||
vram: []VGAEntry,
|
||||
cursor: usize,
|
||||
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(' '));
|
||||
|
||||
self.cursor = 0;
|
||||
self.updateCursor();
|
||||
}
|
||||
|
||||
////
|
||||
// Print a character to the screen.
|
||||
//
|
||||
// Arguments:
|
||||
// char: Character to be printed.
|
||||
//
|
||||
fn writeChar(self: *VGA, char: u8) void {
|
||||
if (self.cursor == VGA_WIDTH * VGA_HEIGHT - 1) {
|
||||
self.scrollDown();
|
||||
}
|
||||
|
||||
switch (char) {
|
||||
// Newline.
|
||||
'\n' => {
|
||||
self.writeChar(' ');
|
||||
while (self.cursor % VGA_WIDTH != 0)
|
||||
self.writeChar(' ');
|
||||
},
|
||||
// Tab.
|
||||
'\t' => {
|
||||
self.writeChar(' ');
|
||||
while (self.cursor % 4 != 0)
|
||||
self.writeChar(' ');
|
||||
},
|
||||
// Backspace.
|
||||
// FIXME: hardcoded 8 here is horrible.
|
||||
8 => {
|
||||
self.cursor -= 1;
|
||||
self.writeChar(' ');
|
||||
self.cursor -= 1;
|
||||
},
|
||||
// Any other character.
|
||||
else => {
|
||||
self.vram[self.cursor] = self.entry(char);
|
||||
self.cursor += 1;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
////
|
||||
// Print a string to the screen.
|
||||
//
|
||||
// Arguments:
|
||||
// string: String to be printed.
|
||||
//
|
||||
pub fn writeString(self: *VGA, string: []const u8) void {
|
||||
for (string) |char| {
|
||||
self.writeChar(char);
|
||||
}
|
||||
|
||||
self.updateCursor();
|
||||
}
|
||||
|
||||
////
|
||||
// Scroll the screen one line down.
|
||||
//
|
||||
fn scrollDown(self: *VGA) void {
|
||||
const first = 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]);
|
||||
// Clean the last line.
|
||||
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;
|
||||
}
|
||||
|
||||
////
|
||||
// Update the position of the hardware cursor.
|
||||
// Use the software cursor as the source of truth.
|
||||
//
|
||||
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));
|
||||
}
|
||||
|
||||
////
|
||||
// Update the position of the software cursor.
|
||||
// Use the hardware cursor as the source of truth.
|
||||
//
|
||||
pub fn fetchCursor(self: *VGA) void {
|
||||
var cursor: usize = 0;
|
||||
|
||||
arch.outb(0x3D4, 0x0E);
|
||||
cursor |= usize(arch.inb(0x3D5)) << 8;
|
||||
|
||||
arch.outb(0x3D4, 0x0F);
|
||||
cursor |= arch.inb(0x3D5);
|
||||
|
||||
self.cursor = cursor;
|
||||
}
|
||||
|
||||
////
|
||||
// Build a VGAEntry with current foreground and background.
|
||||
//
|
||||
// Arguments:
|
||||
// char: The character of the entry.
|
||||
//
|
||||
// Returns:
|
||||
// The requested VGAEntry.
|
||||
//
|
||||
fn entry(self: *VGA, char: u8) VGAEntry {
|
||||
return VGAEntry{
|
||||
.char = char,
|
||||
.foreground = self.foreground,
|
||||
.background = self.background,
|
||||
};
|
||||
}
|
||||
};
|
||||
Loading…
Reference in a new issue