Skip to content

Commit 3530dd7

Browse files
committed
lesson 16, kprint
1 parent ee368f4 commit 3530dd7

File tree

9 files changed

+249
-1
lines changed

9 files changed

+249
-1
lines changed

14-checkpoint/boot/bootsect.asm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ load_kernel:
2828
call print_nl
2929

3030
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
31-
mov dh, 2
31+
mov dh, 16 ; Our future kernel will be larger, make this big
3232
mov dl, [BOOT_DRIVE]
3333
call disk_load
3434
ret

16-video-driver/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../14-checkpoint/Makefile

16-video-driver/README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
*Concepts you may want to Google beforehand: VGA character cells, screen offset*
2+
3+
**Goal: Write strings on the screen**
4+
5+
Finally, we are going to be able to output text on the screen. This lesson contains
6+
a bit more code than usual, so let's go step by step.
7+
8+
Open `drivers/screen.h` and you'll see that we have defined some constants for the VGA
9+
card driver and three public functions, one to clear the screen and another couple
10+
to write strings, the famously named `kprint` for "kernel print"
11+
12+
Now open `drivers/screen.c`. It starts with the declaration of private helper functions
13+
that we will use to aid our `kprint` kernel API.
14+
15+
There are the two I/O port access routines that we learned in the previous lesson,
16+
`get` and `set_cursor_offset()`.
17+
18+
Then there is the routine that directly manipulates the video memory, `print_char()`
19+
20+
Finally, there are three small helper functions to transform rows and columns into offsets
21+
and vice versa.
22+
23+
24+
kprint_at
25+
---------
26+
27+
`kprint_at` may be called with a `-1` value for `col` and `row`, which indicates that
28+
we will print the string at the current cursor position.
29+
30+
It first sets three variables for the col/row and the offset. Then it iterates through
31+
the `char*` and calls `print_char()` with the current coordinates.
32+
33+
Note that `print_char` itself returns the offset of the next cursor position, and we reuse
34+
it for the next loop.
35+
36+
`kprint` is basically a wrapper for `kprint_at`
37+
38+
39+
40+
print_char
41+
----------
42+
43+
Like `kprint_at`, `print_char` allows cols/rows to be `-1`. In that case it retrieves
44+
the cursor position from the hardware, using the `ports.c` routines.
45+
46+
`print_char` also handles newlines. In that case, we will position the cursor offset
47+
to column 0 of the next row.
48+
49+
Remember that the VGA cells take two bytes, one for the character itself and another one
50+
for the attribute.
51+
52+
53+
kernel.c
54+
--------
55+
56+
Our new kernel is finally able to print strings.
57+
58+
It tests correct character positioning, spanning through multiple lines, line breaks,
59+
and finally it tries to write outside of the screen bounds. What happens then?
60+
61+
In the next lesson we will learn how to scroll the screen.

16-video-driver/boot

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../14-checkpoint/boot

16-video-driver/drivers/ports.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Read a byte from the specified port
3+
*/
4+
unsigned char port_byte_in (unsigned short port) {
5+
unsigned char result;
6+
/* Inline assembler syntax
7+
* !! Notice how the source and destination registers are switched from NASM !!
8+
*
9+
* '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
10+
* '"d" (port)': map the C variable '(port)' into e'd'x register
11+
*
12+
* Inputs and outputs are separated by colons
13+
*/
14+
__asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
15+
return result;
16+
}
17+
18+
void port_byte_out (unsigned short port, unsigned char data) {
19+
/* Notice how here both registers are mapped to C variables and
20+
* nothing is returned, thus, no equals '=' in the asm syntax
21+
* However we see a comma since there are two variables in the input area
22+
* and none in the 'return' area
23+
*/
24+
__asm__("out %%al, %%dx" : : "a" (data), "d" (port));
25+
}
26+
27+
unsigned short port_word_in (unsigned short port) {
28+
unsigned short result;
29+
__asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
30+
return result;
31+
}
32+
33+
void port_word_out (unsigned short port, unsigned short data) {
34+
__asm__("out %%ax, %%dx" : : "a" (data), "d" (port));
35+
}

16-video-driver/drivers/ports.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
unsigned char port_byte_in (unsigned short port);
2+
void port_byte_out (unsigned short port, unsigned char data);
3+
unsigned short port_word_in (unsigned short port);
4+
void port_word_out (unsigned short port, unsigned short data);

16-video-driver/drivers/screen.c

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#include "screen.h"
2+
#include "ports.h"
3+
4+
/* Declaration of private functions */
5+
int get_cursor_offset();
6+
void set_cursor_offset(int offset);
7+
int print_char(char c, int col, int row, char attr);
8+
int get_offset(int col, int row);
9+
int get_offset_row(int offset);
10+
int get_offset_col(int offset);
11+
12+
/**********************************************************
13+
* Public Kernel API functions *
14+
**********************************************************/
15+
16+
/**
17+
* Print a message on the specified location
18+
* If col, row, are negative, we will use the current offset
19+
*/
20+
void kprint_at(char *message, int col, int row) {
21+
/* Set cursor if col/row are negative */
22+
int offset;
23+
if (col >= 0 && row >= 0)
24+
offset = get_offset(col, row);
25+
else {
26+
offset = get_cursor_offset();
27+
row = get_offset_row(offset);
28+
col = get_offset_col(offset);
29+
}
30+
31+
/* Loop through message and print it */
32+
int i = 0;
33+
while (message[i] != 0) {
34+
offset = print_char(message[i++], col, row, WHITE_ON_BLACK);
35+
/* Compute row/col for next iteration */
36+
row = get_offset_row(offset);
37+
col = get_offset_col(offset);
38+
}
39+
}
40+
41+
void kprint(char *message) {
42+
kprint_at(message, -1, -1);
43+
}
44+
45+
46+
/**********************************************************
47+
* Private kernel functions *
48+
**********************************************************/
49+
50+
51+
/**
52+
* Innermost print function for our kernel, directly accesses the video memory
53+
*
54+
* If 'col' and 'row' are negative, we will print at current cursor location
55+
* If 'attr' is zero it will use 'white on black' as default
56+
* Returns the offset of the next character
57+
* Sets the video cursor to the returned offset
58+
*/
59+
int print_char(char c, int col, int row, char attr) {
60+
unsigned char *vidmem = (unsigned char*) VIDEO_ADDRESS;
61+
if (!attr) attr = WHITE_ON_BLACK;
62+
63+
/* Error control: print a red 'E' if the coords aren't right */
64+
if (col >= MAX_COLS || row >= MAX_ROWS) {
65+
vidmem[2*(MAX_COLS)*(MAX_ROWS)-2] = 'E';
66+
vidmem[2*(MAX_COLS)*(MAX_ROWS)-1] = RED_ON_WHITE;
67+
return get_offset(col, row);
68+
}
69+
70+
int offset;
71+
if (col >= 0 && row >= 0) offset = get_offset(col, row);
72+
else offset = get_cursor_offset();
73+
74+
if (c == '\n') {
75+
row = get_offset_row(offset);
76+
offset = get_offset(0, row+1);
77+
} else {
78+
vidmem[offset] = c;
79+
vidmem[offset+1] = attr;
80+
offset += 2;
81+
}
82+
set_cursor_offset(offset);
83+
return offset;
84+
}
85+
86+
int get_cursor_offset() {
87+
/* Use the VGA ports to get the current cursor position
88+
* 1. Ask for high byte of the cursor offset (data 14)
89+
* 2. Ask for low byte (data 15)
90+
*/
91+
port_byte_out(REG_SCREEN_CTRL, 14);
92+
int offset = port_byte_in(REG_SCREEN_DATA) << 8; /* High byte: << 8 */
93+
port_byte_out(REG_SCREEN_CTRL, 15);
94+
offset += port_byte_in(REG_SCREEN_DATA);
95+
return offset * 2; /* Position * size of character cell */
96+
}
97+
98+
void set_cursor_offset(int offset) {
99+
/* Similar to get_cursor_offset, but instead of reading we write data */
100+
offset /= 2;
101+
port_byte_out(REG_SCREEN_CTRL, 14);
102+
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset >> 8));
103+
port_byte_out(REG_SCREEN_CTRL, 15);
104+
port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset & 0xff));
105+
}
106+
107+
void clear_screen() {
108+
int screen_size = MAX_COLS * MAX_ROWS;
109+
int i;
110+
char *screen = VIDEO_ADDRESS;
111+
112+
for (i = 0; i < screen_size; i++) {
113+
screen[i*2] = ' ';
114+
screen[i*2+1] = WHITE_ON_BLACK;
115+
}
116+
set_cursor_offset(get_offset(0, 0));
117+
}
118+
119+
120+
int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); }
121+
int get_offset_row(int offset) { return offset / (2 * MAX_COLS); }
122+
int get_offset_col(int offset) { return (offset - (get_offset_row(offset)*2*MAX_COLS))/2; }

16-video-driver/drivers/screen.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#define VIDEO_ADDRESS 0xb8000
2+
#define MAX_ROWS 25
3+
#define MAX_COLS 80
4+
#define WHITE_ON_BLACK 0x0f
5+
#define RED_ON_WHITE 0xf4
6+
7+
/* Screen i/o ports */
8+
#define REG_SCREEN_CTRL 0x3d4
9+
#define REG_SCREEN_DATA 0x3d5
10+
11+
/* Public kernel API */
12+
void clear_screen();
13+
void kprint_at(char *message, int col, int row);
14+
void kprint(char *message);

16-video-driver/kernel/kernel.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#include "../drivers/screen.h"
2+
3+
void main() {
4+
clear_screen();
5+
kprint_at("X", 1, 6);
6+
kprint_at("This text spans multiple lines", 75, 10);
7+
kprint_at("There is a line\nbreak", 0, 20);
8+
kprint("There is a line\nbreak");
9+
kprint_at("What happens when we run out of space?", 45, 24);
10+
}

0 commit comments

Comments
 (0)