lesson 16, kprint

pull/6/head
Carlos Fenollosa 10 years ago
parent ee368f41d4
commit 3530dd700c

@ -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

@ -0,0 +1 @@
../14-checkpoint/Makefile

@ -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.

@ -0,0 +1 @@
../14-checkpoint/boot

@ -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));
}

@ -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);

@ -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; }

@ -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);

@ -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);
}
Loading…
Cancel
Save