Attempts at fixing broken VGA driver + initial work on interrupts
This commit is contained in:
parent
946f7e72c3
commit
bdfb10e3a6
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"files.associations": {
|
||||
"ktypes.h": "c",
|
||||
"ports.h": "c"
|
||||
}
|
||||
}
|
14
Makefile
14
Makefile
|
@ -12,7 +12,8 @@ SRCDIR := src
|
|||
BUILDDIR := build
|
||||
OBJDIR := obj
|
||||
DISTDIR := dist
|
||||
CFLAGS := -g -I $(IDIR) -Wall -pedantic -Wno-builtin-declaration-mismatch -ffreestanding
|
||||
GDB := gdb
|
||||
CFLAGS := -o0 -ggdb -I $(IDIR) -Wall -pedantic -Wno-builtin-declaration-mismatch -ffreestanding
|
||||
KERNEL_SOURCES := $(shell find src/kernel -name '*.c' -type f)
|
||||
KERNEL_OBJS := $(KERNEL_SOURCES:.c=.o)
|
||||
KERNEL_OBJS := $(subst src/,$(OBJDIR)/,$(KERNEL_OBJS))
|
||||
|
@ -72,6 +73,10 @@ drivers: $(DRIVERS_OBJS)
|
|||
$(BUILDDIR)/kernel.bin: $(OBJDIR)/entrypoint.o $(KERNEL_OBJS) $(DRIVERS_OBJS)
|
||||
$(LD) -o $@ -Ttext 0x1000 $^ --oformat binary
|
||||
|
||||
# Compile and link the kernel with debug symbols
|
||||
$(BUILDDIR)/kernel.elf: $(OBJDIR)/entrypoint.o $(KERNEL_OBJS) $(DRIVERS_OBJS)
|
||||
$(LD) -o $@ -Ttext 0x1000 $^
|
||||
|
||||
|
||||
kernel: $(BUILDDIR)/kernel.bin
|
||||
|
||||
|
@ -89,7 +94,8 @@ image: $(BUILDDIR)/mbr.bin $(BUILDDIR)/kernel.bin
|
|||
|
||||
|
||||
run: image
|
||||
qemu-system-x86_64 -drive format=raw,file=$(DISTDIR)/os.img,index=0,media=disk
|
||||
qemu-system-i386 -drive format=raw,file=$(DISTDIR)/os.img,index=0,media=disk
|
||||
|
||||
debug: image
|
||||
qemu-system-x86_64 -drive format=raw,file=$(DISTDIR)/os.img,index=0,media=disk -monitor stdio
|
||||
|
||||
debug: $(BUILDDIR)/kernel.elf image
|
||||
qemu-system-i386 -drive format=raw,file=$(DISTDIR)/os.img,index=0,media=disk -gdb tcp:localhost:8080 -S
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
Copyright 2022 Mattia Giambirtone & Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TSOS_CPU_IDT_H
|
||||
#define TSOS_CPU_IDT_H
|
||||
|
||||
#include "kernel/types.h"
|
||||
|
||||
#define KERNEL_CODE_SEGMENT_SELECTOR 0x8
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
/*
|
||||
An entry in the Interrupt
|
||||
Descriptor Table of the CPU
|
||||
*/
|
||||
|
||||
u16 handlerOffsetLow; /* Low 16 bits of handler's address */
|
||||
u16 segmentSelector; /* Kernel segment selector */
|
||||
u8 _unused; /* This is always zero! (padding?) */
|
||||
u8 flags; /* Flags of this handler */
|
||||
u16 handlerOffsetHigh; /* High 16 bits of handler's address */
|
||||
/*
|
||||
The flags byte is structured as follows:
|
||||
- Bit 7 : "Interrupt is present". Serves to make the whole structure valid
|
||||
- Bits 6-5: Privilege level ("ring") of the caller. Ring zero is the kernel,
|
||||
ring one is usually reserved for driver code, ring two is unused and ring 3
|
||||
is userland code
|
||||
- Bits 4-0: The gate's type and attributes. They are structured as follows:
|
||||
- Bit 4 : Set to zero for interrupts and one for traps (traps are usually used for exceptions)
|
||||
- Bit 3-0: The type of the gate (whether it's 16- or 32-bit, etc.)
|
||||
The only valid values for bits 4-0 are five (taken from the OSDev wiki):
|
||||
- 0b0101 or 0x5: Task Gate. Note that in this case, the offset value is unused and should be set to zero.
|
||||
- 0b0110 or 0x6: 16-bit Interrupt Gate
|
||||
- 0b0111 or 0x7: 16-bit Trap Gate
|
||||
- 0b1110 or 0xE: 32-bit Interrupt Gate
|
||||
- 0b1111 or 0xF: 32-bit Trap Gate
|
||||
*/
|
||||
} __attribute__((packed)) GateDescriptor;
|
||||
// The packed attribute tells gcc not to align
|
||||
// or reorder the struct's fields in memory.
|
||||
// Doing so in normal code is fine and speeds up
|
||||
// memory accessing, but the CPU expects the structure
|
||||
// of a GDT entry to be exactly in this order, with no
|
||||
// padding (besides, each entry is exactly 8 bytes long
|
||||
// already, so the performance hit is probably neglibible)
|
||||
|
||||
|
||||
typedef struct {
|
||||
/*
|
||||
The Interrupt Descriptor Table
|
||||
Register. Stores the location
|
||||
of the IDT in memory
|
||||
*/
|
||||
|
||||
u16 size; /* Size of the IDT. Always one less than its true size*/
|
||||
u32 loc;
|
||||
} __attribute__((packed)) IDTRegister;
|
||||
|
||||
|
||||
/*
|
||||
Note: While the IDT can contain up to 2 ** 16 entries, only the first
|
||||
256 are considered and the rest is ignored; However, if there's less than
|
||||
256 entries in the table, when the CPU then tries to fetch an interrupt gate
|
||||
and doesn't find one, a GP (General Protection) fault is triggered. This may
|
||||
cause the CPU to triple fault if the GP handler itself hasn't been set, as it
|
||||
is one of the interrupt handlers the CPU expects to be present
|
||||
*/
|
||||
|
||||
|
||||
#define IDT_SIZE 256
|
||||
|
||||
/* These are the IDT and IDTR (We'll access these from asm) */
|
||||
GateDescriptor idt[IDT_SIZE];
|
||||
IDTRegister idtReg;
|
||||
|
||||
/* Prototypes */
|
||||
void setIDTGate(i32 n, u32 handler);
|
||||
void setIDT(void);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
Copyright 2022 Mattia Giambirtone & Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TSOS_CPU_ISR_H
|
||||
#define TSOS_CPU_ISR_H
|
||||
|
||||
#include "kernel/types.h"
|
||||
|
||||
/*
|
||||
The first 32 ISRs are reserved for CPU exceptions, so we
|
||||
manually forward-declare them. They will be implemented in
|
||||
assembly (hence why "extern" is explicit here, despite it
|
||||
being the default for C functions)
|
||||
*/
|
||||
|
||||
extern void isr0(void);
|
||||
extern void isr1(void);
|
||||
extern void isr2(void);
|
||||
extern void isr3(void);
|
||||
extern void isr4(void);
|
||||
extern void isr5(void);
|
||||
extern void isr6(void);
|
||||
extern void isr7(void);
|
||||
extern void isr8(void);
|
||||
extern void isr9(void);
|
||||
extern void isr10(void);
|
||||
extern void isr11(void);
|
||||
extern void isr12(void);
|
||||
extern void isr13(void);
|
||||
extern void isr14(void);
|
||||
extern void isr15(void);
|
||||
extern void isr16(void);
|
||||
extern void isr17(void);
|
||||
extern void isr18(void);
|
||||
extern void isr19(void);
|
||||
extern void isr20(void);
|
||||
extern void isr21(void);
|
||||
extern void isr22(void);
|
||||
extern void isr23(void);
|
||||
extern void isr24(void);
|
||||
extern void isr25(void);
|
||||
extern void isr26(void);
|
||||
extern void isr27(void);
|
||||
extern void isr28(void);
|
||||
extern void isr29(void);
|
||||
extern void isr30(void);
|
||||
extern void isr31(void);
|
||||
|
||||
|
||||
typedef struct {
|
||||
/*
|
||||
A wrapper around the metadata
|
||||
that we pass to ISRs (from asm)
|
||||
when they're called
|
||||
*/
|
||||
|
||||
u32 ds; /* The DS register */
|
||||
/* General Purpose registers saved by pusha */
|
||||
u32 edi;
|
||||
u32 esi;
|
||||
u32 ebp;
|
||||
u32 esp;
|
||||
u32 ebx;
|
||||
u32 edx;
|
||||
u32 ecx;
|
||||
u32 eax;
|
||||
u32 kind; /* The interrupt number */
|
||||
u32 err; /* The interrupt's error code (optional, may be zero) */
|
||||
/*
|
||||
These are pushed by the processor automatically when an interrupt
|
||||
is triggered
|
||||
*/
|
||||
u32 eip;
|
||||
u32 cs;
|
||||
u32 eflags;
|
||||
u32 useresp;
|
||||
u32 ss;
|
||||
} ISRMetadata;
|
||||
|
||||
|
||||
void installDefaultHandlers(void);
|
||||
void interruptHandler(ISRMetadata data);
|
||||
|
||||
// Maps each builtin ISR number to an error message
|
||||
char* errorMessages[] = {
|
||||
"Division By Zero",
|
||||
"Debug",
|
||||
"Non Maskable Interrupt",
|
||||
"Breakpoint",
|
||||
"Into Detected Overflow",
|
||||
"Out of Bounds",
|
||||
"Invalid Opcode",
|
||||
"No Coprocessor",
|
||||
|
||||
"Double Fault",
|
||||
"Coprocessor Segment Overrun",
|
||||
"Bad TSS",
|
||||
"Segment Not Present",
|
||||
"Stack Fault",
|
||||
"General Protection Fault",
|
||||
"Page Fault",
|
||||
"Unknown Interrupt",
|
||||
|
||||
"Coprocessor Fault",
|
||||
"Alignment Check",
|
||||
"Machine Check",
|
||||
"Reserved",
|
||||
"Reserved",
|
||||
"Reserved",
|
||||
"Reserved",
|
||||
"Reserved",
|
||||
|
||||
"Reserved",
|
||||
"Reserved",
|
||||
"Reserved",
|
||||
"Reserved",
|
||||
"Reserved",
|
||||
"Reserved",
|
||||
"Reserved",
|
||||
"Reserved"
|
||||
};
|
||||
|
||||
#define interruptMessage(x) errorMessages[x]
|
||||
|
||||
|
||||
#endif
|
|
@ -15,8 +15,8 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
|
||||
#ifndef TSOS_DRV_VGA_SCREEN
|
||||
#define TSOS_DRV_VGA_SCREEN
|
||||
#ifndef TSOS_DRV_VGA_SCREEN_H
|
||||
#define TSOS_DRV_VGA_SCREEN_H
|
||||
|
||||
#include "kernel/types.h"
|
||||
#include "kernel/drivers/ports/ports.h"
|
||||
|
@ -39,9 +39,8 @@ limitations under the License.
|
|||
|
||||
// Public API
|
||||
void clearScreen(void);
|
||||
void kprintAt(char* message, i32 col, i32 row);
|
||||
void kprint(char* message);
|
||||
void kprintln(char* message);
|
||||
|
||||
void kprint(const char* message);
|
||||
void kprintln(const char* message);
|
||||
void kprintf(const char *fmt, ...);
|
||||
|
||||
#endif
|
|
@ -16,8 +16,8 @@ limitations under the License.
|
|||
|
||||
// Shorthand definitions of various types
|
||||
// used in the kernel
|
||||
#ifndef TSOS_KTYPES
|
||||
#define TSOS_KTYPES
|
||||
#ifndef TSOS_KTYPES_H
|
||||
#define TSOS_KTYPES_H
|
||||
|
||||
#define true 1
|
||||
#define false 0
|
||||
|
|
|
@ -19,8 +19,41 @@ limitations under the License.
|
|||
|
||||
#include "kernel/types.h"
|
||||
|
||||
// Some handy macros
|
||||
|
||||
/*
|
||||
These bitwise tricks *seem* like black
|
||||
magic, but they're quite simple: shifting
|
||||
the offset by 8 bits moves the high bits 8
|
||||
positions down, so we lose the low bits and
|
||||
the high bits now fit into a single byte. To
|
||||
get the high bits, we do a bitwise and with 0xff
|
||||
(11111111 in decimal) leaving us with only the low
|
||||
bits: the reason this works is because 0xff (which
|
||||
is smaller than our offset), is zero-extended from
|
||||
the beginning with zeroes, so when we perform the
|
||||
operation the high bits are cancelled out. Neat huh?
|
||||
*/
|
||||
|
||||
#define LOW8(x) (u8)(x & 0xff)
|
||||
#define HIGH8(x) (u8)(x >> 8)
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Here we do the same thing, except it's to get the
|
||||
low/high 16 bits of a 32-bit value instead
|
||||
*/
|
||||
|
||||
#define LOW16(x) (u16)(x & 0xffff)
|
||||
#define HIGH16(x) (u16)(LOW16(x >> 16))
|
||||
|
||||
|
||||
|
||||
|
||||
void copystr(const char* source, char* dest, i32 n);
|
||||
void memset(u8* dest, u8 val, u32 len);
|
||||
void memset(byte* dest, byte val, u32 len);
|
||||
void itoa(const i32 i, char* a);
|
||||
void utoa(const u32 i, char* a);
|
||||
|
||||
#endif
|
|
@ -120,7 +120,7 @@ load_kernel:
|
|||
; Loads the kernel into memory
|
||||
mov bx, kernel_offset
|
||||
mov cl, reserved_sectors + 1
|
||||
mov dh, 4
|
||||
mov dh, 5
|
||||
mov dl, [boot_drive]
|
||||
call load_disk
|
||||
mov si, kernel_loaded_msg
|
||||
|
|
|
@ -183,15 +183,16 @@ enableA20:
|
|||
;; the fact that we had to use the nonstandard alternate enabling
|
||||
;; method
|
||||
.success:
|
||||
;sti ; Note: When this is uncommented, shit breaks. I have a few theories as to why,
|
||||
; but none of them make sense, so ¯\_(ツ)_/¯
|
||||
;sti ; Note: When this is uncommented, shit breaks. Probably because the CPU
|
||||
; throws a fault or a trap without the IDT being loaded, so it triple
|
||||
; faults and resets. Or maybe not, who knows
|
||||
|
||||
popa
|
||||
xor eax, eax
|
||||
ret
|
||||
|
||||
.fail:
|
||||
sti
|
||||
; sti
|
||||
popa
|
||||
mov eax, -1
|
||||
ret
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
Copyright 2022 Mattia Giambirtone & Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Low-level handling of interrupts
|
||||
|
||||
#include "kernel/types.h"
|
||||
#include "kernel/util.h"
|
||||
#include "cpu/isr.h"
|
||||
#include "cpu/idt.h"
|
||||
|
||||
|
||||
void installDefaultHandlers(void) {
|
||||
/*
|
||||
Installs the first 32 ISRs that are
|
||||
reserved for the CPU
|
||||
*/
|
||||
|
||||
// If only C functions were first-class
|
||||
// objects :(
|
||||
setIDTGate(0, (u32)isr0);
|
||||
setIDTGate(1, (u32)isr1);
|
||||
setIDTGate(2, (u32)isr2);
|
||||
setIDTGate(3, (u32)isr3);
|
||||
setIDTGate(4, (u32)isr4);
|
||||
setIDTGate(5, (u32)isr5);
|
||||
setIDTGate(6, (u32)isr6);
|
||||
setIDTGate(7, (u32)isr7);
|
||||
setIDTGate(8, (u32)isr8);
|
||||
setIDTGate(9, (u32)isr9);
|
||||
setIDTGate(10, (u32)isr10);
|
||||
setIDTGate(11, (u32)isr11);
|
||||
setIDTGate(12, (u32)isr12);
|
||||
setIDTGate(13, (u32)isr13);
|
||||
setIDTGate(14, (u32)isr14);
|
||||
setIDTGate(15, (u32)isr15);
|
||||
setIDTGate(16, (u32)isr16);
|
||||
setIDTGate(17, (u32)isr17);
|
||||
setIDTGate(18, (u32)isr18);
|
||||
setIDTGate(19, (u32)isr19);
|
||||
setIDTGate(20, (u32)isr20);
|
||||
setIDTGate(21, (u32)isr21);
|
||||
setIDTGate(22, (u32)isr22);
|
||||
setIDTGate(23, (u32)isr23);
|
||||
setIDTGate(24, (u32)isr24);
|
||||
setIDTGate(25, (u32)isr25);
|
||||
setIDTGate(26, (u32)isr26);
|
||||
setIDTGate(27, (u32)isr27);
|
||||
setIDTGate(28, (u32)isr28);
|
||||
setIDTGate(29, (u32)isr29);
|
||||
setIDTGate(30, (u32)isr30);
|
||||
setIDTGate(31, (u32)isr31);
|
||||
setIDT(); // Here we're actually calling assembly code!
|
||||
}
|
||||
|
||||
|
||||
void interruptHandler(ISRMetadata data) {
|
||||
/*
|
||||
Handles CPU interrupts
|
||||
*/
|
||||
char v[3];
|
||||
itoa(data.kind, v);
|
||||
kprint("TSKL: Received interrupt ");
|
||||
kprint(v);
|
||||
kprint("(");
|
||||
kprint(interruptMessage(data.err));
|
||||
kprintln(")");
|
||||
}
|
|
@ -28,26 +28,26 @@ limitations under the License.
|
|||
byte readByte(u16 port) {
|
||||
// Reads a byte from the specified I/O port
|
||||
volatile byte result;
|
||||
__asm__ volatile ("in %%dx, %%al" : "=a" (result) : "d" (port));
|
||||
asm volatile ("in %%dx, %%al" : "=a" (result) : "d" (port));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void writeByte(u16 port, byte data) {
|
||||
// Writes a byte to the specified I/O port
|
||||
__asm__ volatile ("out %%al, %%dx" : : "a" (data), "d" (port));
|
||||
asm volatile ("out %%al, %%dx" : : "a" (data), "d" (port));
|
||||
}
|
||||
|
||||
|
||||
u16 readWord(u16 port) {
|
||||
// Reads a word (16 bits) from the specified I/O port
|
||||
volatile u16 result;
|
||||
__asm__ volatile ("in %%dx, %%ax" : "=a" (result) : "d" (port));
|
||||
asm volatile ("in %%dx, %%ax" : "=a" (result) : "d" (port));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void writeWord(u16 port, u16 data) {
|
||||
// Writes a word (16 bits) to the specified I/O port
|
||||
__asm__ volatile ("out %%ax, %%dx" : : "a" (data), "d" (port));
|
||||
asm volatile ("out %%ax, %%dx" : : "a" (data), "d" (port));
|
||||
}
|
|
@ -19,67 +19,81 @@ limitations under the License.
|
|||
#include "kernel/types.h"
|
||||
#include "kernel/util.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
|
||||
u16 getCursorOffset(void);
|
||||
void setCursorOffset(u16 offset);
|
||||
u16 putchar(char ch, u8 row, u8 col, char attr);
|
||||
u16 getOffset(u8 row, u8 col);
|
||||
u8 getRow(u16 offset);
|
||||
u8 getColumn(u16 offset);
|
||||
void printc(char c);
|
||||
|
||||
i32 getCursorOffset(void);
|
||||
void setCursorOffset(i32 offset);
|
||||
i32 putchar(byte ch, i32 col, i32 row, byte attr);
|
||||
i32 getOffset(i32 col, i32 row);
|
||||
i32 getRow(i32 offset);
|
||||
i32 getColumn(i32 offset);
|
||||
|
||||
|
||||
// Public API below
|
||||
|
||||
void kprintAt(char* message, i32 col, i32 row) {
|
||||
void kprint(const char* message) {
|
||||
/*
|
||||
Prints a null-terminated string to the VGA
|
||||
text buffer at the specified row and column.
|
||||
The cursor's position is not updated. If both
|
||||
col and row are negative, the current cursor
|
||||
location is used
|
||||
text buffer
|
||||
*/
|
||||
i32 offset;
|
||||
if (col >= 0 && row >= 0) {
|
||||
offset = getOffset(col, row);
|
||||
}
|
||||
else {
|
||||
offset = getCursorOffset();
|
||||
row = getRow(offset);
|
||||
col = getColumn(offset);
|
||||
}
|
||||
for (i32 i = 0; message[i]; i++) {
|
||||
offset = putchar(message[i], col, row, 0);
|
||||
i32 offset = getCursorOffset();
|
||||
i32 row = getRow(offset), col = getColumn(offset);
|
||||
while (*message) {
|
||||
offset = putchar(*(message++), row, col, 0);
|
||||
// We compute the new row and column for the
|
||||
// next iteration. Hopefully the compiler inlines
|
||||
// these two calls
|
||||
row = getRow(offset);
|
||||
col = getColumn(offset);
|
||||
}
|
||||
|
||||
setCursorOffset(getOffset(row, col));
|
||||
}
|
||||
|
||||
|
||||
void inline kprint(char* message) {
|
||||
|
||||
void kprintf(const char *fmt, ...) {
|
||||
/*
|
||||
Prints a null-terminated string to the
|
||||
VGA text buffer
|
||||
A simple reimplementation of printf in
|
||||
kernel space. The following basic format
|
||||
specifiers can be used:
|
||||
- %s -> string
|
||||
- %d -> signed number
|
||||
- %u -> unsigned number
|
||||
- %c -> character
|
||||
*/
|
||||
kprintAt(message, -1, -1);
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
char* sbuf; /* Used to print strings */
|
||||
char ibuf[12]; /* Used as input to itoa. 12 is the number of
|
||||
digits of 2 ** 32 + 2 (for the sign and null byte)*/
|
||||
u32 offset = getCursorOffset();
|
||||
u32 row = getRow(offset), col = getColumn(offset);
|
||||
memset(ibuf, '0', 12);
|
||||
while (*fmt) {
|
||||
offset = putchar(*(fmt++), row, col, 0);
|
||||
row = getRow(offset);
|
||||
col = getColumn(offset);
|
||||
}
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
|
||||
void inline kprintln(char* message) {
|
||||
void inline kprintln(const char* message) {
|
||||
/*
|
||||
Identical to kprint, but calls
|
||||
kprint("\n") afterwards
|
||||
*/
|
||||
kprint(message);
|
||||
kprint("\n");
|
||||
kprint(message);
|
||||
kprint("\n");
|
||||
}
|
||||
|
||||
|
||||
// Private API below
|
||||
|
||||
i32 putchar(byte ch, i32 col, i32 row, byte attr) {
|
||||
u16 putchar(char ch, u8 row, u8 col, char attr) {
|
||||
/*
|
||||
Writes a single character to the VGA text buffer
|
||||
at the given row and column and returns the new
|
||||
|
@ -108,16 +122,16 @@ i32 putchar(byte ch, i32 col, i32 row, byte attr) {
|
|||
if (row >= MAX_ROWS) {
|
||||
return -2;
|
||||
}
|
||||
i32 offset = (col >= 0 && row >= 0)? getOffset(col, row): getCursorOffset();
|
||||
u16 offset = (col >= 0 && row >= 0)? getOffset(row, col): getCursorOffset();
|
||||
switch (ch) {
|
||||
// Note the difference between a carriage return (which
|
||||
// only brings the write head back at 0 without changing
|
||||
// the row) and the newline (which increases the row as well)
|
||||
case '\r':
|
||||
offset = getOffset(0, getRow(offset));
|
||||
offset = getOffset(getRow(offset), 0);
|
||||
break;
|
||||
case '\n':
|
||||
offset = getOffset(0, getRow(offset) + 1);
|
||||
offset = getOffset(getRow(offset) + 1, 0);
|
||||
break;
|
||||
case '\t':
|
||||
for (i32 i = 0; i < VGA_TABSIZE; i++) {
|
||||
|
@ -135,29 +149,28 @@ i32 putchar(byte ch, i32 col, i32 row, byte attr) {
|
|||
// We check if we reached the end of the screen, in which
|
||||
// case we scroll
|
||||
if (offset >= SCREEN_SIZE * 2) {
|
||||
for (i32 i = 1; i < MAX_ROWS; i ++) {
|
||||
for (u8 i = 1; i < MAX_ROWS; i++) {
|
||||
// This loop will take the bytes of row i and copy
|
||||
// them to row i - 1, effectively erasing the first
|
||||
// one and causing the text on the screen to scroll!
|
||||
copystr((char *)(getOffset(0, i) + VMEM_ADDRESS),
|
||||
(char*)(getOffset(0, i - 1) + VMEM_ADDRESS),
|
||||
copystr((char*)(getOffset(i, 0) + VMEM_ADDRESS),
|
||||
(char*)(getOffset(i - 1, 0) + VMEM_ADDRESS),
|
||||
MAX_COLS * 2);
|
||||
}
|
||||
// We empty the last line
|
||||
char* last = (char*)(getOffset(0, MAX_ROWS - 1) + VMEM_ADDRESS);
|
||||
for (i32 i = 0; i < MAX_COLS * 2; i++) {
|
||||
char* last = (char*)(getOffset(MAX_ROWS - 1, 0) + VMEM_ADDRESS);
|
||||
for (u8 i = 0; i < MAX_COLS * 2; i++) {
|
||||
last[i] = 0;
|
||||
}
|
||||
|
||||
offset -= 2 * MAX_COLS;
|
||||
}
|
||||
|
||||
setCursorOffset(offset);
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
i32 getCursorOffset(void) {
|
||||
u16 getCursorOffset(void) {
|
||||
/*
|
||||
Returns the current offset of
|
||||
the VGA text cursor
|
||||
|
@ -170,7 +183,7 @@ i32 getCursorOffset(void) {
|
|||
// Memory-mapped I/O is great, isn't it?
|
||||
writeByte(REG_SCREEN_CTRL, 14);
|
||||
// And we read it
|
||||
i32 offset = readByte(REG_SCREEN_DATA);
|
||||
u16 offset = readByte(REG_SCREEN_DATA);
|
||||
// Since we requested the high byte
|
||||
// (i.e. the 8 most significant bits)
|
||||
// we now want to move them by 8 places.
|
||||
|
@ -185,7 +198,7 @@ i32 getCursorOffset(void) {
|
|||
}
|
||||
|
||||
|
||||
void setCursorOffset(i32 offset) {
|
||||
void setCursorOffset(u16 offset) {
|
||||
/*
|
||||
Sets the offset of the VGA text
|
||||
cursor to the desired value. Note
|
||||
|
@ -193,11 +206,11 @@ void setCursorOffset(i32 offset) {
|
|||
around I/O ports, no bounds checking
|
||||
is performed: the caller should make
|
||||
sure that the offset fits within the
|
||||
80x25 grid of the VGA screen!
|
||||
80x25 grid of the VGA screen
|
||||
*/
|
||||
|
||||
// We now divide by 2 because while we count
|
||||
// in bytes, the VGA controller counts in cells!
|
||||
// in bytes, the VGA controller counts in cells
|
||||
offset /= 2;
|
||||
// This function is almost identical to getCursorOffset,
|
||||
// except we're now writing on the bus instead of reading
|
||||
|
@ -205,23 +218,11 @@ void setCursorOffset(i32 offset) {
|
|||
|
||||
// We write the high bits first
|
||||
writeByte(REG_SCREEN_CTRL, 14);
|
||||
writeByte(REG_SCREEN_DATA, (byte)(offset >> 8));
|
||||
writeByte(REG_SCREEN_DATA, HIGH8(offset));
|
||||
// Then the low bits
|
||||
writeByte(REG_SCREEN_CTRL, 15);
|
||||
writeByte(REG_SCREEN_DATA, (byte)(offset & 0xff));
|
||||
// These bitwise tricks *seem* like black
|
||||
// magic, but they're quite simple: shifting
|
||||
// the offset by 8 bits moves the high bits 8
|
||||
// positions down (so we lose the low bits and
|
||||
// the high bits now fit into a single byte, which
|
||||
// is what writeByte wants). Then we get rid of the
|
||||
// high bits (0xff is 11110000) with a bitwise and,
|
||||
// leaving us with only the low bits set which we
|
||||
// then feed to the I/O port again; The reason this
|
||||
// works is because 0xff (which is smaller than our
|
||||
// offset), is zero-extended from the beginning with
|
||||
// zeroes, so when we perform the operation the high
|
||||
// bits are cancelled out
|
||||
writeByte(REG_SCREEN_DATA, LOW8(offset));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -230,17 +231,16 @@ void clearScreen(void) {
|
|||
Clears the screen and resets
|
||||
the cursor position to 0, 0
|
||||
*/
|
||||
|
||||
for (i32 i = 0; i < SCREEN_SIZE; i++) {
|
||||
VMEM_BUF[i * 2] = ' '; // The screen is actually never "empty", just filled with spaces!
|
||||
VMEM_BUF[i * 2] = ' '; // The screen is actually never "empty", just filled with spaces
|
||||
VMEM_BUF[i * 2 + 1] = LIGHT_GREY_ON_BLACK;
|
||||
}
|
||||
|
||||
setCursorOffset(getOffset(0, 0));
|
||||
|
||||
}
|
||||
|
||||
|
||||
i32 inline getOffset(i32 col, i32 row) {
|
||||
u16 inline getOffset(u8 row, u8 col) {
|
||||
/*
|
||||
Converts a column, row pair into
|
||||
an absolute offset into the VGA
|
||||
|
@ -253,17 +253,21 @@ i32 inline getOffset(i32 col, i32 row) {
|
|||
}
|
||||
|
||||
|
||||
i32 inline getRow(i32 offset) {
|
||||
u8 inline getRow(u16 offset) {
|
||||
|
||||
/*
|
||||
Converts an absolute offset into
|
||||
the VGA text buffer into a row
|
||||
*/
|
||||
|
||||
// Conversely, we divide by two
|
||||
// when converting back from the
|
||||
// raw offset to columns/rows
|
||||
return offset / (2 * MAX_COLS);
|
||||
}
|
||||
|
||||
|
||||
i32 inline getColumn(i32 offset) {
|
||||
u8 inline getColumn(u16 offset) {
|
||||
/*
|
||||
Converts an absolute offset into
|
||||
the VGA text buffer into a column
|
||||
|
|
|
@ -26,9 +26,9 @@ i32 kmain(void) {
|
|||
*/
|
||||
|
||||
|
||||
// TODO: Set VGA cursor position from assembly. Skipping
|
||||
// log messages like this is just awful
|
||||
kprintln("\n\n\nTSKL - INFO: Kernel booted successfully");
|
||||
clearScreen();
|
||||
kprint("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
|
||||
kprint("X");
|
||||
// TODO...
|
||||
return 0x202172; // :D
|
||||
}
|
|
@ -35,9 +35,10 @@ void copystr(const char* restrict source, char* restrict dest, i32 n) {
|
|||
}
|
||||
|
||||
|
||||
void memset(u8* dest, u8 val, u32 len) {
|
||||
void memset(byte* dest, byte val, u32 len) {
|
||||
/*
|
||||
Implementation for memset
|
||||
Fills the first len bytes of the memory area pointed to by dest
|
||||
with the constant byte val
|
||||
*/
|
||||
u8* temp = (u8*)dest;
|
||||
while (len) {
|
||||
|
@ -47,7 +48,22 @@ void memset(u8* dest, u8 val, u32 len) {
|
|||
}
|
||||
|
||||
|
||||
i32 countDigits(i32 n) {
|
||||
u8 countDigits(i32 n) {
|
||||
/*
|
||||
Returns the number of digits
|
||||
of n
|
||||
*/
|
||||
i8 result = 0;
|
||||
while (n) {
|
||||
n /= 10;
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
i32 uCountDigits(u32 n) {
|
||||
/*
|
||||
Returns the number of digits
|
||||
of n
|
||||
|
@ -73,13 +89,33 @@ void itoa(i32 n, char* a) {
|
|||
buffer is not large enough, the
|
||||
behavior is undefined)
|
||||
*/
|
||||
int i, sign;
|
||||
if ((sign = n) < 0) n = -n;
|
||||
i = countDigits(n);
|
||||
|
||||
bool sign = n >= 0;
|
||||
if (!sign) n = -n;
|
||||
i32 i = countDigits(n);
|
||||
do {
|
||||
a[--i] = n % 10 + '0';
|
||||
} while ((n /= 10) > 0);
|
||||
} while (n /= 10);
|
||||
|
||||
if (sign < 0) a[--i] = '-';
|
||||
if (!sign) a[--i] = '-';
|
||||
a[--i] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void utoa(u32 n, char* a) {
|
||||
/*
|
||||
Converts the unsigned integer n to
|
||||
a string. The result is written
|
||||
to a, which is assumed to be large
|
||||
enough to accomodate the number
|
||||
itself and a null byte at the end
|
||||
(if the buffer is not large enough,
|
||||
the behavior is undefined)
|
||||
*/
|
||||
|
||||
i32 i = uCountDigits(n);
|
||||
do {
|
||||
a[--i] = n % 10 + '0';
|
||||
} while (n /= 10);
|
||||
a[--i] = '\0';
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue