mirror of
https://github.com/cfenollosa/os-tutorial.git
synced 2024-10-27 20:34:19 +00:00
lesson 16, kprint
This commit is contained in:
parent
ee368f41d4
commit
3530dd700c
@ -28,7 +28,7 @@ load_kernel:
|
|||||||
call print_nl
|
call print_nl
|
||||||
|
|
||||||
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
|
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]
|
mov dl, [BOOT_DRIVE]
|
||||||
call disk_load
|
call disk_load
|
||||||
ret
|
ret
|
||||||
|
1
16-video-driver/Makefile
Symbolic link
1
16-video-driver/Makefile
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../14-checkpoint/Makefile
|
61
16-video-driver/README.md
Normal file
61
16-video-driver/README.md
Normal file
@ -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.
|
1
16-video-driver/boot
Symbolic link
1
16-video-driver/boot
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../14-checkpoint/boot
|
35
16-video-driver/drivers/ports.c
Normal file
35
16-video-driver/drivers/ports.c
Normal file
@ -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));
|
||||||
|
}
|
4
16-video-driver/drivers/ports.h
Normal file
4
16-video-driver/drivers/ports.h
Normal file
@ -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);
|
122
16-video-driver/drivers/screen.c
Normal file
122
16-video-driver/drivers/screen.c
Normal file
@ -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; }
|
14
16-video-driver/drivers/screen.h
Normal file
14
16-video-driver/drivers/screen.h
Normal file
@ -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);
|
10
16-video-driver/kernel/kernel.c
Normal file
10
16-video-driver/kernel/kernel.c
Normal file
@ -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…
Reference in New Issue
Block a user