Implemented kprint

This commit is contained in:
Nocturn9x 2022-11-17 12:42:45 +01:00
parent edf0b4bf74
commit 4af94012ca
8 changed files with 263 additions and 71 deletions

View File

@ -30,15 +30,6 @@ jmp $ ; Keeps jumping at the current address (loops forever)
%include "src/boot/gdt.s"
%include "src/boot/switch32.s"
[bits 16] ; All x86 CPUs start in 16 bit (aka "real") mode, so we tell nasm to emit 16-bit code
load_kernel: ; Loads the kernel into memory
mov si, loading_kernel_msg
call bios_println
mov bx, kernel_offset
mov dh, 1
mov dl, [boot_drive]
call load_disk
ret
; Here we define our variables: They need to be defined after the
; halting because otherwise they will be executed as code
@ -48,6 +39,17 @@ loading_kernel_msg: db "Loading kernel into memory", 0
boot_drive: db 0
[bits 16] ; All x86 CPUs start in 16 bit (aka "real") mode, so we tell nasm to emit 16-bit code
load_kernel: ; Loads the kernel into memory
mov si, loading_kernel_msg
call bios_println
mov bx, kernel_offset
mov dh, 3
mov dl, [boot_drive]
call load_disk
ret
[bits 32]
BEGIN_32BIT: ; After the switch we will get here
mov esi, protected_mode_msg
@ -57,7 +59,7 @@ BEGIN_32BIT: ; After the switch we will get here
; 320 bytes so that we don't overwrite the log
; messages we have already written
mov ecx, 0x140
call vga_println
call vga_print
call kernel_offset
jmp $

View File

@ -141,16 +141,6 @@ vga_print:
ret
vga_println:
; Prints a null-terminated string located on the
; esi register and terminates it with a newline
pusha
call vga_print
;mov esi, NEWLINE
;call vga_print
popa
ret
vga_printh:
; Prints the value of edx in hexadecimal format

View File

@ -3,29 +3,35 @@
#include "ports.h"
byte vga_readb(u16 port) {
// Note: We use the volatile modifier everywhere because
// the compiler might try and optimize some of our code
// away, since its effects are not directly visible from
// the code itself, which would be a nightmare to debug!
byte readByte(u16 port) {
// Reads a byte from the specified I/O port
byte result;
__asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
volatile byte result;
__asm__ volatile ("in %%dx, %%al" : "=a" (result) : "d" (port));
return result;
}
void vga_writeb(u16 port, byte data) {
void writeByte(u16 port, byte data) {
// Writes a byte to the specified I/O port
__asm__("out %%al, %%dx" : : "a" (data), "d" (port));
__asm__ volatile ("out %%al, %%dx" : : "a" (data), "d" (port));
}
u16 vga_readw(u16 port) {
u16 readWord(u16 port) {
// Reads a word (16 bits) from the specified I/O port
u16 result;
__asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
volatile u16 result;
__asm__ volatile ("in %%dx, %%ax" : "=a" (result) : "d" (port));
return result;
}
void vga_writew(u16 port, u16 data) {
void writeWord(u16 port, u16 data) {
// Writes a word (16 bits) to the specified I/O port
__asm__("out %%ax, %%dx" : : "a" (data), "d" (port));
__asm__ volatile ("out %%ax, %%dx" : : "a" (data), "d" (port));
}

View File

@ -2,14 +2,13 @@
#define TSOS_DRV_PORTS_H
#define VMEM_ADDR 0xb8000
#include "kernel/ktypes.h"
byte vga_readb(u16 port);
void vga_writeb(u16 port, byte data);
u16 vga_readw(u16 port);
void vga_writew(u16 port, u16 data);
byte readByte(u16 port);
void writeByte(u16 port, byte data);
u16 readWord(u16 port);
void writeWord(u16 port, u16 data);

View File

@ -1,2 +1,216 @@
// Implementation of a simple text-only VGA driver
#include "screen.h"
#include "screen.h"
#include "kernel/ktypes.h"
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(byte* message, i32 col, i32 row) {
/*
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
*/
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);
// We compute the new row and column for the
// next iteration. Hopefully the compiler inlines
// these two calls
row = getRow(offset);
col = getColumn(offset);
}
}
void kprint(byte* message) {
/*
Prints a null-terminated string to the
VGA text buffer
*/
kprintAt(message, -1, -1);
}
// Private API below
i32 putchar(byte ch, i32 col, i32 row, byte attr) {
/*
Writes a single character to the VGA text buffer
at the given row and column and returns the new
location of the cursor. If the attr byte is zero,
its value is the result of the expansion of the
LIGHT_GREY_ON_BLACK macro. If both row and col
are equal to -1, the cursor location is retrieved
from the VGA controller using I/O ports. The following
escape sequences are handled:
- '\n' -> Goes to the next line
- '\t' -> Prints VGA_TABSIZE spaces
- '\r' -> Resets the cursor's column, but not the row
*/
if (!attr) {
// No color data provided? We supply our own
attr = LIGHT_GREY_ON_BLACK;
}
i32 offset = (col >= 0 && row >= 0)? getOffset(col, row): 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));
break;
case '\n':
offset = getOffset(0, getRow(offset) + 1);
break;
case '\t':
for (i32 i = 0; i < VGA_TABSIZE; i++) {
VMEM_BUF[offset] = ' ';
VMEM_BUF[offset + 1] = attr;
offset += 2;
}
break;
default:
VMEM_BUF[offset] = ch;
VMEM_BUF[offset + 1] = attr;
offset += 2;
break;
}
setCursorOffset(offset);
return offset;
}
i32 getCursorOffset(void) {
/*
Returns the current offset of
the VGA text cursor
*/
// We request the high byte of the
// cursor position by writing to the
// VGA control port. We can then read
// the result on the VGA data port.
// Memory-mapped I/O is great, isn't it?
writeByte(REG_SCREEN_CTRL, 14);
// And we read it
i32 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.
// Conveniently, that's exactly what a
// left shift is for :)
offset <<= 8;
// Now we request the low byte of the cursor
// position
writeByte(REG_SCREEN_CTRL, 15);
offset += readByte(REG_SCREEN_DATA);
return offset * 2; // Times 2 because VGA cells are 2 bytes long
}
void setCursorOffset(i32 offset) {
/*
Sets the offset of the VGA text
cursor to the desired value. Note
that, this being a low level wrapper
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!
*/
// We now divide by 2 because while we count
// 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
// from it, so the VGA controller updates the cursor position
// We write the high bits first
writeByte(REG_SCREEN_CTRL, 14);
writeByte(REG_SCREEN_DATA, (byte)(offset >> 8));
// 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
}
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 + 1] = LIGHT_GREY_ON_BLACK;
}
setCursorOffset(getOffset(0, 0));
}
i32 inline getOffset(i32 col, i32 row) {
/*
Converts a column, row pair into
an absolute offset into the VGA
text buffer
*/
// We multiply by 2 because each VGA
// character cell is 2 bytes long
return (row * MAX_COLS + col) * 2;
}
i32 inline getRow(i32 offset) {
/*
Converts an absolute offset into
the VGA text buffer into a row
*/
return offset / (2 * MAX_COLS);
}
i32 inline getColumn(i32 offset) {
/*
Converts an absolute offset into
the VGA text buffer into a column
*/
return (offset - (getRow(offset) * 2 * MAX_COLS)) / 2;
}

View File

@ -1,23 +1,28 @@
#ifndef TSOS_DRV_VGA_SCREEN
#define TSOS_DRV_VGA_SCREEN
#include "kernel/ktypes.h"
#include "kernel/drivers/ports/ports.h"
#define VMEM_ADDRESS 0xb8000
#define VMEM_BUF ((byte*)VMEM_ADDRESS)
#define MAX_ROWS 25
#define MAX_COLS 80
#define SCREEN_SIZE MAX_ROWS * MAX_COLS
#define LIGHT_GREY_ON_BLACK 0x07
#define RED_ON_WHITE 0xf4
#define VGA_TABSIZE 4
// VGA I/O ports
#define REG_SCREEN_CTRL 0x3d4
#define REG_SCREEN_DATA 0x3d5
#include "kernel/ktypes.h"
#include "kernel/drivers/ports/ports.h"
// Exposed API
void clear_screen(void);
void kprint_at(char *message, int col, int row);
void kprint(char *message);
// Public API
void clearScreen(void);
void kprintAt(byte* message, i32 col, i32 row);
void kprint(byte* message);
#endif

View File

@ -8,6 +8,7 @@ typedef unsigned char byte;
typedef unsigned short int u16;
typedef short int i16;
typedef unsigned int uint;
typedef unsigned int u32;
typedef int i32;
#endif

View File

@ -4,32 +4,7 @@
void kmain(void) {
byte* vga = (byte*)VMEM_ADDRESS;
vga_writeb(0x3d4, 14); // We request the high byte of cursor position on port 0x3D4
int cursor = vga_readb(0x3d5); // And now we read it
// Since this is the high byte, we shift the
// cursor by 8 bits to make room for the low
// byte
cursor <<= 8;
// Now we request the low byte
vga_writeb(0x3d4, 15);
// And we add it to the cursor
cursor += vga_readb(0x3d5);
// VGA 'cells' are 2 bytes long, but
// the cursor counts cells as a unit,
// so we multiply it by 2
cursor *= 2;
// We add 160 because when we print a
// message via VGA in the bootloader,
// and that leaves the cursor in the
// wrong position
cursor += 160;
// Now we write some message onto the screen
byte *s = "TSOS Kernel started";
for (int i = 0; s[i] != '\0'; i++) {
vga[cursor] = s[i]; // We set the character code we want
cursor++;
vga[cursor] = 0x07; // Light grey on black
cursor++;
}
// Newline so we skip the log
// messages from the bootloader
kprint("\nTSOS Kernel is starting up");
}