Finished Part I, moved part 2 relavent files to own folder.
This commit is contained in:
49
Part2/A4/03_strings/Makefile
Executable file
49
Part2/A4/03_strings/Makefile
Executable file
@@ -0,0 +1,49 @@
|
||||
C_SOURCES = $(wildcard kernel/*.c drivers/*.c)
|
||||
HEADERS = $(wildcard kernel/*.h drivers/*.h)
|
||||
# Nice syntax for file extension replacement
|
||||
OBJ = ${C_SOURCES:.c=.o}
|
||||
|
||||
# Change this if your cross-compiler is somewhere else
|
||||
CC = /usr/bin/gcc
|
||||
GDB = /usr/bin/gdb
|
||||
# -g: Use debugging symbols in gcc
|
||||
CFLAGS = -g -m32 -fno-pie -ffreestanding
|
||||
|
||||
# First rule is run by default
|
||||
os-image.bin: boot/bootsect.bin kernel.bin
|
||||
cat $^ > os-image.bin
|
||||
|
||||
# '--oformat binary' deletes all symbols as a collateral, so we don't need
|
||||
# to 'strip' them manually on this case
|
||||
kernel.bin: boot/kernel_entry.o ${OBJ}
|
||||
/usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^ --oformat binary
|
||||
|
||||
# Used for debugging purposes
|
||||
kernel.elf: boot/kernel_entry.o ${OBJ}
|
||||
/usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^
|
||||
|
||||
run: os-image.bin
|
||||
qemu-system-i386 -vga std -drive file=$<,index=0,if=floppy,format=raw
|
||||
|
||||
curses: os-image.bin
|
||||
qemu-system-i386 -curses -drive file=$<,index=0,if=floppy,format=raw
|
||||
|
||||
# Open the connection to qemu and load our kernel-object file with symbols
|
||||
debug: os-image.bin kernel.elf
|
||||
qemu-system-i386 -curses -s -drive file=os-image.bin,index=0,if=floppy,format=raw &
|
||||
${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
|
||||
|
||||
# Generic rules for wildcards
|
||||
# To make an object, always compile from its .c
|
||||
%.o: %.c ${HEADERS}
|
||||
${CC} ${CFLAGS} -c $< -o $@
|
||||
|
||||
%.o: %.asm
|
||||
nasm $< -f elf -o $@
|
||||
|
||||
%.bin: %.asm
|
||||
nasm $< -f bin -o $@
|
||||
|
||||
clean:
|
||||
rm -rf *.bin *.dis *.o os-image.bin *.elf
|
||||
rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o
|
26
Part2/A4/03_strings/boot/32bit_print.asm
Executable file
26
Part2/A4/03_strings/boot/32bit_print.asm
Executable file
@@ -0,0 +1,26 @@
|
||||
[bits 32] ; using 32-bit protected mode
|
||||
|
||||
; this is how constants are defined
|
||||
VIDEO_MEMORY equ 0xb8000
|
||||
WHITE_OB_BLACK equ 0x0f ; the color byte for each character
|
||||
|
||||
print_string_pm:
|
||||
pusha
|
||||
mov edx, VIDEO_MEMORY
|
||||
|
||||
print_string_pm_loop:
|
||||
mov al, [ebx] ; [ebx] is the address of our character
|
||||
mov ah, WHITE_OB_BLACK
|
||||
|
||||
cmp al, 0 ; check if end of string
|
||||
je print_string_pm_done
|
||||
|
||||
mov [edx], ax ; store character + attribute in video memory
|
||||
add ebx, 1 ; next char
|
||||
add edx, 2 ; next video memory position
|
||||
|
||||
jmp print_string_pm_loop
|
||||
|
||||
print_string_pm_done:
|
||||
popa
|
||||
ret
|
51
Part2/A4/03_strings/boot/bootsect.asm
Executable file
51
Part2/A4/03_strings/boot/bootsect.asm
Executable file
@@ -0,0 +1,51 @@
|
||||
; Identical to lesson 13's boot sector, but the %included files have new paths
|
||||
[org 0x7c00]
|
||||
KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
|
||||
|
||||
mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
|
||||
mov bp, 0x9000
|
||||
mov sp, bp
|
||||
|
||||
mov bx, MSG_REAL_MODE
|
||||
call print
|
||||
call print_nl
|
||||
|
||||
call load_kernel ; read the kernel from disk
|
||||
call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM'
|
||||
jmp $ ; Never executed
|
||||
|
||||
%include "boot/print.asm"
|
||||
%include "boot/print_hex.asm"
|
||||
%include "boot/disk.asm"
|
||||
%include "boot/gdt.asm"
|
||||
%include "boot/32bit_print.asm"
|
||||
%include "boot/switch_pm.asm"
|
||||
|
||||
[bits 16]
|
||||
load_kernel:
|
||||
mov bx, MSG_LOAD_KERNEL
|
||||
call print
|
||||
call print_nl
|
||||
|
||||
mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
|
||||
mov dh, 16 ; Our future kernel will be larger, make this big
|
||||
mov dl, [BOOT_DRIVE]
|
||||
call disk_load
|
||||
ret
|
||||
|
||||
[bits 32]
|
||||
BEGIN_PM:
|
||||
mov ebx, MSG_PROT_MODE
|
||||
call print_string_pm
|
||||
call KERNEL_OFFSET ; Give control to the kernel
|
||||
jmp $ ; Stay here when the kernel returns control to us (if ever)
|
||||
|
||||
|
||||
BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
|
||||
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
|
||||
MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
|
||||
MSG_LOAD_KERNEL db "Loading kernel into memory", 0
|
||||
|
||||
; padding
|
||||
times 510 - ($-$$) db 0
|
||||
dw 0xaa55
|
46
Part2/A4/03_strings/boot/disk.asm
Executable file
46
Part2/A4/03_strings/boot/disk.asm
Executable file
@@ -0,0 +1,46 @@
|
||||
; load 'dh' sectors from drive 'dl' into ES:BX
|
||||
disk_load:
|
||||
pusha
|
||||
; reading from disk requires setting specific values in all registers
|
||||
; so we will overwrite our input parameters from 'dx'. Let's save it
|
||||
; to the stack for later use.
|
||||
push dx
|
||||
|
||||
mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
|
||||
mov al, dh ; al <- number of sectors to read (0x01 .. 0x80)
|
||||
mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
|
||||
; 0x01 is our boot sector, 0x02 is the first 'available' sector
|
||||
mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
|
||||
; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
|
||||
; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
|
||||
mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
|
||||
|
||||
; [es:bx] <- pointer to buffer where the data will be stored
|
||||
; caller sets it up for us, and it is actually the standard location for int 13h
|
||||
int 0x13 ; BIOS interrupt
|
||||
jc disk_error ; if error (stored in the carry bit)
|
||||
|
||||
pop dx
|
||||
cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it.
|
||||
jne sectors_error
|
||||
popa
|
||||
ret
|
||||
|
||||
|
||||
disk_error:
|
||||
mov bx, DISK_ERROR
|
||||
call print
|
||||
call print_nl
|
||||
mov dh, ah ; ah = error code, dl = disk drive that dropped the error
|
||||
call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
|
||||
jmp disk_loop
|
||||
|
||||
sectors_error:
|
||||
mov bx, SECTORS_ERROR
|
||||
call print
|
||||
|
||||
disk_loop:
|
||||
jmp $
|
||||
|
||||
DISK_ERROR: db "Disk read error", 0
|
||||
SECTORS_ERROR: db "Incorrect number of sectors read", 0
|
35
Part2/A4/03_strings/boot/gdt.asm
Executable file
35
Part2/A4/03_strings/boot/gdt.asm
Executable file
@@ -0,0 +1,35 @@
|
||||
gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
|
||||
; the GDT starts with a null 8-byte
|
||||
dd 0x0 ; 4 byte
|
||||
dd 0x0 ; 4 byte
|
||||
|
||||
; GDT for code segment. base = 0x00000000, length = 0xfffff
|
||||
; for flags, refer to os-dev.pdf document, page 36
|
||||
gdt_code:
|
||||
dw 0xffff ; segment length, bits 0-15
|
||||
dw 0x0 ; segment base, bits 0-15
|
||||
db 0x0 ; segment base, bits 16-23
|
||||
db 10011010b ; flags (8 bits)
|
||||
db 11001111b ; flags (4 bits) + segment length, bits 16-19
|
||||
db 0x0 ; segment base, bits 24-31
|
||||
|
||||
; GDT for data segment. base and length identical to code segment
|
||||
; some flags changed, again, refer to os-dev.pdf
|
||||
gdt_data:
|
||||
dw 0xffff
|
||||
dw 0x0
|
||||
db 0x0
|
||||
db 10010010b
|
||||
db 11001111b
|
||||
db 0x0
|
||||
|
||||
gdt_end:
|
||||
|
||||
; GDT descriptor
|
||||
gdt_descriptor:
|
||||
dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
|
||||
dd gdt_start ; address (32 bit)
|
||||
|
||||
; define some constants for later use
|
||||
CODE_SEG equ gdt_code - gdt_start
|
||||
DATA_SEG equ gdt_data - gdt_start
|
4
Part2/A4/03_strings/boot/kernel_entry.asm
Executable file
4
Part2/A4/03_strings/boot/kernel_entry.asm
Executable file
@@ -0,0 +1,4 @@
|
||||
[bits 32]
|
||||
[extern _start] ; Define calling point. Must have same name as kernel.c '_start' function
|
||||
call _start ; Calls the C function. The linker will know where it is placed in memory
|
||||
jmp $
|
37
Part2/A4/03_strings/boot/print.asm
Executable file
37
Part2/A4/03_strings/boot/print.asm
Executable file
@@ -0,0 +1,37 @@
|
||||
print:
|
||||
pusha
|
||||
|
||||
; keep this in mind:
|
||||
; while (string[i] != 0) { print string[i]; i++ }
|
||||
|
||||
; the comparison for string end (null byte)
|
||||
start:
|
||||
mov al, [bx] ; 'bx' is the base address for the string
|
||||
cmp al, 0
|
||||
je done
|
||||
|
||||
; the part where we print with the BIOS help
|
||||
mov ah, 0x0e
|
||||
int 0x10 ; 'al' already contains the char
|
||||
|
||||
; increment pointer and do next loop
|
||||
add bx, 1
|
||||
jmp start
|
||||
|
||||
done:
|
||||
popa
|
||||
ret
|
||||
|
||||
|
||||
|
||||
print_nl:
|
||||
pusha
|
||||
|
||||
mov ah, 0x0e
|
||||
mov al, 0x0a ; newline char
|
||||
int 0x10
|
||||
mov al, 0x0d ; carriage return
|
||||
int 0x10
|
||||
|
||||
popa
|
||||
ret
|
46
Part2/A4/03_strings/boot/print_hex.asm
Executable file
46
Part2/A4/03_strings/boot/print_hex.asm
Executable file
@@ -0,0 +1,46 @@
|
||||
; receiving the data in 'dx'
|
||||
; For the examples we'll assume that we're called with dx=0x1234
|
||||
print_hex:
|
||||
pusha
|
||||
|
||||
mov cx, 0 ; our index variable
|
||||
|
||||
; Strategy: get the last char of 'dx', then convert to ASCII
|
||||
; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
|
||||
; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
|
||||
; Then, move the ASCII byte to the correct position on the resulting string
|
||||
hex_loop:
|
||||
cmp cx, 4 ; loop 4 times
|
||||
je end
|
||||
|
||||
; 1. convert last char of 'dx' to ascii
|
||||
mov ax, dx ; we will use 'ax' as our working register
|
||||
and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
|
||||
add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
|
||||
cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
|
||||
jle step2
|
||||
add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
|
||||
|
||||
step2:
|
||||
; 2. get the correct position of the string to place our ASCII char
|
||||
; bx <- base address + string length - index of char
|
||||
mov bx, HEX_OUT + 5 ; base + length
|
||||
sub bx, cx ; our index variable
|
||||
mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
|
||||
ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
|
||||
|
||||
; increment index and loop
|
||||
add cx, 1
|
||||
jmp hex_loop
|
||||
|
||||
end:
|
||||
; prepare the parameter and call the function
|
||||
; remember that print receives parameters in 'bx'
|
||||
mov bx, HEX_OUT
|
||||
call print
|
||||
|
||||
popa
|
||||
ret
|
||||
|
||||
HEX_OUT:
|
||||
db '0x0000',0 ; reserve memory for our new string
|
22
Part2/A4/03_strings/boot/switch_pm.asm
Executable file
22
Part2/A4/03_strings/boot/switch_pm.asm
Executable file
@@ -0,0 +1,22 @@
|
||||
[bits 16]
|
||||
switch_to_pm:
|
||||
cli ; 1. disable interrupts
|
||||
lgdt [gdt_descriptor] ; 2. load the GDT descriptor
|
||||
mov eax, cr0
|
||||
or eax, 0x1 ; 3. set 32-bit mode bit in cr0
|
||||
mov cr0, eax
|
||||
jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
|
||||
|
||||
[bits 32]
|
||||
init_pm: ; we are now using 32-bit instructions
|
||||
mov ax, DATA_SEG ; 5. update the segment registers
|
||||
mov ds, ax
|
||||
mov ss, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
|
||||
mov esp, ebp
|
||||
|
||||
call BEGIN_PM ; 7. Call a well-known label with useful code
|
35
Part2/A4/03_strings/drivers/ports.c
Executable file
35
Part2/A4/03_strings/drivers/ports.c
Executable 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
Part2/A4/03_strings/drivers/ports.h
Executable file
4
Part2/A4/03_strings/drivers/ports.h
Executable 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
Part2/A4/03_strings/drivers/screen.c
Executable file
122
Part2/A4/03_strings/drivers/screen.c
Executable 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 = (char *) 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
Part2/A4/03_strings/drivers/screen.h
Executable file
14
Part2/A4/03_strings/drivers/screen.h
Executable 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
Part2/A4/03_strings/kernel/kernel.c
Executable file
10
Part2/A4/03_strings/kernel/kernel.c
Executable file
@@ -0,0 +1,10 @@
|
||||
#include "../drivers/screen.h"
|
||||
|
||||
void _start() {
|
||||
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);
|
||||
}
|
Reference in New Issue
Block a user