mirror of
				https://github.com/cfenollosa/os-tutorial.git
				synced 2025-06-13 12:54:24 +00:00 
			
		
		
		
	lesson 16, kprint
This commit is contained in:
		
							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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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