From 3530dd700c0db0eb30f680986cc71b4290766d4c Mon Sep 17 00:00:00 2001 From: Carlos Fenollosa Date: Wed, 22 Oct 2014 10:58:12 +0200 Subject: [PATCH] lesson 16, kprint --- 14-checkpoint/boot/bootsect.asm | 2 +- 16-video-driver/Makefile | 1 + 16-video-driver/README.md | 61 ++++++++++++++++ 16-video-driver/boot | 1 + 16-video-driver/drivers/ports.c | 35 +++++++++ 16-video-driver/drivers/ports.h | 4 + 16-video-driver/drivers/screen.c | 122 +++++++++++++++++++++++++++++++ 16-video-driver/drivers/screen.h | 14 ++++ 16-video-driver/kernel/kernel.c | 10 +++ 9 files changed, 249 insertions(+), 1 deletion(-) create mode 120000 16-video-driver/Makefile create mode 100644 16-video-driver/README.md create mode 120000 16-video-driver/boot create mode 100644 16-video-driver/drivers/ports.c create mode 100644 16-video-driver/drivers/ports.h create mode 100644 16-video-driver/drivers/screen.c create mode 100644 16-video-driver/drivers/screen.h create mode 100644 16-video-driver/kernel/kernel.c diff --git a/14-checkpoint/boot/bootsect.asm b/14-checkpoint/boot/bootsect.asm index 361d7ef..319cbac 100644 --- a/14-checkpoint/boot/bootsect.asm +++ b/14-checkpoint/boot/bootsect.asm @@ -28,7 +28,7 @@ load_kernel: call print_nl mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000 - mov dh, 2 + mov dh, 16 ; Our future kernel will be larger, make this big mov dl, [BOOT_DRIVE] call disk_load ret diff --git a/16-video-driver/Makefile b/16-video-driver/Makefile new file mode 120000 index 0000000..3fbcd60 --- /dev/null +++ b/16-video-driver/Makefile @@ -0,0 +1 @@ +../14-checkpoint/Makefile \ No newline at end of file diff --git a/16-video-driver/README.md b/16-video-driver/README.md new file mode 100644 index 0000000..5c4cd2b --- /dev/null +++ b/16-video-driver/README.md @@ -0,0 +1,61 @@ +*Concepts you may want to Google beforehand: VGA character cells, screen offset* + +**Goal: Write strings on the screen** + +Finally, we are going to be able to output text on the screen. This lesson contains +a bit more code than usual, so let's go step by step. + +Open `drivers/screen.h` and you'll see that we have defined some constants for the VGA +card driver and three public functions, one to clear the screen and another couple +to write strings, the famously named `kprint` for "kernel print" + +Now open `drivers/screen.c`. It starts with the declaration of private helper functions +that we will use to aid our `kprint` kernel API. + +There are the two I/O port access routines that we learned in the previous lesson, +`get` and `set_cursor_offset()`. + +Then there is the routine that directly manipulates the video memory, `print_char()` + +Finally, there are three small helper functions to transform rows and columns into offsets +and vice versa. + + +kprint_at +--------- + +`kprint_at` may be called with a `-1` value for `col` and `row`, which indicates that +we will print the string at the current cursor position. + +It first sets three variables for the col/row and the offset. Then it iterates through +the `char*` and calls `print_char()` with the current coordinates. + +Note that `print_char` itself returns the offset of the next cursor position, and we reuse +it for the next loop. + +`kprint` is basically a wrapper for `kprint_at` + + + +print_char +---------- + +Like `kprint_at`, `print_char` allows cols/rows to be `-1`. In that case it retrieves +the cursor position from the hardware, using the `ports.c` routines. + +`print_char` also handles newlines. In that case, we will position the cursor offset +to column 0 of the next row. + +Remember that the VGA cells take two bytes, one for the character itself and another one +for the attribute. + + +kernel.c +-------- + +Our new kernel is finally able to print strings. + +It tests correct character positioning, spanning through multiple lines, line breaks, +and finally it tries to write outside of the screen bounds. What happens then? + +In the next lesson we will learn how to scroll the screen. diff --git a/16-video-driver/boot b/16-video-driver/boot new file mode 120000 index 0000000..7ce5a3d --- /dev/null +++ b/16-video-driver/boot @@ -0,0 +1 @@ +../14-checkpoint/boot \ No newline at end of file diff --git a/16-video-driver/drivers/ports.c b/16-video-driver/drivers/ports.c new file mode 100644 index 0000000..60c9b9a --- /dev/null +++ b/16-video-driver/drivers/ports.c @@ -0,0 +1,35 @@ +/** + * Read a byte from the specified port + */ +unsigned char port_byte_in (unsigned short port) { + unsigned char result; + /* Inline assembler syntax + * !! Notice how the source and destination registers are switched from NASM !! + * + * '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x + * '"d" (port)': map the C variable '(port)' into e'd'x register + * + * Inputs and outputs are separated by colons + */ + __asm__("in %%dx, %%al" : "=a" (result) : "d" (port)); + return result; +} + +void port_byte_out (unsigned short port, unsigned char data) { + /* Notice how here both registers are mapped to C variables and + * nothing is returned, thus, no equals '=' in the asm syntax + * However we see a comma since there are two variables in the input area + * and none in the 'return' area + */ + __asm__("out %%al, %%dx" : : "a" (data), "d" (port)); +} + +unsigned short port_word_in (unsigned short port) { + unsigned short result; + __asm__("in %%dx, %%ax" : "=a" (result) : "d" (port)); + return result; +} + +void port_word_out (unsigned short port, unsigned short data) { + __asm__("out %%ax, %%dx" : : "a" (data), "d" (port)); +} diff --git a/16-video-driver/drivers/ports.h b/16-video-driver/drivers/ports.h new file mode 100644 index 0000000..db9253b --- /dev/null +++ b/16-video-driver/drivers/ports.h @@ -0,0 +1,4 @@ +unsigned char port_byte_in (unsigned short port); +void port_byte_out (unsigned short port, unsigned char data); +unsigned short port_word_in (unsigned short port); +void port_word_out (unsigned short port, unsigned short data); diff --git a/16-video-driver/drivers/screen.c b/16-video-driver/drivers/screen.c new file mode 100644 index 0000000..a449f20 --- /dev/null +++ b/16-video-driver/drivers/screen.c @@ -0,0 +1,122 @@ +#include "screen.h" +#include "ports.h" + +/* Declaration of private functions */ +int get_cursor_offset(); +void set_cursor_offset(int offset); +int print_char(char c, int col, int row, char attr); +int get_offset(int col, int row); +int get_offset_row(int offset); +int get_offset_col(int offset); + +/********************************************************** + * Public Kernel API functions * + **********************************************************/ + +/** + * Print a message on the specified location + * If col, row, are negative, we will use the current offset + */ +void kprint_at(char *message, int col, int row) { + /* Set cursor if col/row are negative */ + int offset; + if (col >= 0 && row >= 0) + offset = get_offset(col, row); + else { + offset = get_cursor_offset(); + row = get_offset_row(offset); + col = get_offset_col(offset); + } + + /* Loop through message and print it */ + int i = 0; + while (message[i] != 0) { + offset = print_char(message[i++], col, row, WHITE_ON_BLACK); + /* Compute row/col for next iteration */ + row = get_offset_row(offset); + col = get_offset_col(offset); + } +} + +void kprint(char *message) { + kprint_at(message, -1, -1); +} + + +/********************************************************** + * Private kernel functions * + **********************************************************/ + + +/** + * Innermost print function for our kernel, directly accesses the video memory + * + * If 'col' and 'row' are negative, we will print at current cursor location + * If 'attr' is zero it will use 'white on black' as default + * Returns the offset of the next character + * Sets the video cursor to the returned offset + */ +int print_char(char c, int col, int row, char attr) { + unsigned char *vidmem = (unsigned char*) VIDEO_ADDRESS; + if (!attr) attr = WHITE_ON_BLACK; + + /* Error control: print a red 'E' if the coords aren't right */ + if (col >= MAX_COLS || row >= MAX_ROWS) { + vidmem[2*(MAX_COLS)*(MAX_ROWS)-2] = 'E'; + vidmem[2*(MAX_COLS)*(MAX_ROWS)-1] = RED_ON_WHITE; + return get_offset(col, row); + } + + int offset; + if (col >= 0 && row >= 0) offset = get_offset(col, row); + else offset = get_cursor_offset(); + + if (c == '\n') { + row = get_offset_row(offset); + offset = get_offset(0, row+1); + } else { + vidmem[offset] = c; + vidmem[offset+1] = attr; + offset += 2; + } + set_cursor_offset(offset); + return offset; +} + +int get_cursor_offset() { + /* Use the VGA ports to get the current cursor position + * 1. Ask for high byte of the cursor offset (data 14) + * 2. Ask for low byte (data 15) + */ + port_byte_out(REG_SCREEN_CTRL, 14); + int offset = port_byte_in(REG_SCREEN_DATA) << 8; /* High byte: << 8 */ + port_byte_out(REG_SCREEN_CTRL, 15); + offset += port_byte_in(REG_SCREEN_DATA); + return offset * 2; /* Position * size of character cell */ +} + +void set_cursor_offset(int offset) { + /* Similar to get_cursor_offset, but instead of reading we write data */ + offset /= 2; + port_byte_out(REG_SCREEN_CTRL, 14); + port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset >> 8)); + port_byte_out(REG_SCREEN_CTRL, 15); + port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset & 0xff)); +} + +void clear_screen() { + int screen_size = MAX_COLS * MAX_ROWS; + int i; + char *screen = VIDEO_ADDRESS; + + for (i = 0; i < screen_size; i++) { + screen[i*2] = ' '; + screen[i*2+1] = WHITE_ON_BLACK; + } + set_cursor_offset(get_offset(0, 0)); +} + + +int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); } +int get_offset_row(int offset) { return offset / (2 * MAX_COLS); } +int get_offset_col(int offset) { return (offset - (get_offset_row(offset)*2*MAX_COLS))/2; } diff --git a/16-video-driver/drivers/screen.h b/16-video-driver/drivers/screen.h new file mode 100644 index 0000000..61bc2e6 --- /dev/null +++ b/16-video-driver/drivers/screen.h @@ -0,0 +1,14 @@ +#define VIDEO_ADDRESS 0xb8000 +#define MAX_ROWS 25 +#define MAX_COLS 80 +#define WHITE_ON_BLACK 0x0f +#define RED_ON_WHITE 0xf4 + +/* Screen i/o ports */ +#define REG_SCREEN_CTRL 0x3d4 +#define REG_SCREEN_DATA 0x3d5 + +/* Public kernel API */ +void clear_screen(); +void kprint_at(char *message, int col, int row); +void kprint(char *message); diff --git a/16-video-driver/kernel/kernel.c b/16-video-driver/kernel/kernel.c new file mode 100644 index 0000000..927acf6 --- /dev/null +++ b/16-video-driver/kernel/kernel.c @@ -0,0 +1,10 @@ +#include "../drivers/screen.h" + +void main() { + clear_screen(); + kprint_at("X", 1, 6); + kprint_at("This text spans multiple lines", 75, 10); + kprint_at("There is a line\nbreak", 0, 20); + kprint("There is a line\nbreak"); + kprint_at("What happens when we run out of space?", 45, 24); +}