Implemented kprint
This commit is contained in:
parent
edf0b4bf74
commit
4af94012ca
|
@ -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 $
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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");
|
||||
}
|
Loading…
Reference in New Issue