Files
x86-bare-metal-examples/common.h

336 lines
6.1 KiB
C
Raw Normal View History

2015-09-20 15:51:38 +02:00
/*
Using macros for everything to make linking simpler.
The big ones do bloat the executable.
2015-10-18 14:56:12 +02:00
## Calling convention
2015-09-20 15:51:38 +02:00
*/
.altmacro
2015-10-18 14:56:12 +02:00
.macro BEGIN
.code16
cli
/* Set %cs to 0. TODO Is that really needed? */ ;\
ljmp $0, $1f
1:
xor %ax, %ax
/* We must zero %ds for any data access. */ \
2015-10-18 14:56:12 +02:00
mov %ax, %ds
/* TODO is it really need to clear all those segment registers, e.g. for BIOS calls? */ \
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
2015-09-20 20:37:12 +02:00
/* TODO What to move into BP and SP? http://stackoverflow.com/questions/10598802/which-value-should-be-used-for-sp-for-booting-process */ \
2015-10-18 14:56:12 +02:00
mov 0x0000, %bp
/* Automatically disables interrupts until the end of the next instruction. */ \
2015-10-18 14:56:12 +02:00
mov %ax, %ss
2015-09-20 20:37:12 +02:00
/* We should set SP because BIOS calls may depend on that. TODO confirm. */ \
mov %bp, %sp
2015-10-18 14:56:12 +02:00
.endm
2015-09-20 10:59:36 +02:00
/*
Load stage2 from disk to memory, and jump to it.
TODO not working.
To be used when the program does not fit in the 512 bytes.
Sample usage:
STAGE2
Stage 2 code here.
*/
2015-10-18 14:56:12 +02:00
.macro STAGE2
mov $2, %ah
/*
TODO get working on linker script.
Above my paygrade for now, so I just load a bunch of sectors instead.
*/
/* mov __stage2_size, %al;\ */
mov $9, %al
mov $0x80, %dl
mov $0, %ch
mov $0, %dh
mov $2, %cl
mov $1f, %bx
int $0x13
jmp 1f
.section .stage2
1:
2015-10-18 14:56:12 +02:00
.endm
/*
Enter protected mode.
Use the simplest GDT possible.
*/
.macro PROTECTED_MODE
/* Must come before they are used. */
.equ CODE_SEG, 8
.equ DATA_SEG, gdt_data - gdt_start
2015-10-18 14:56:12 +02:00
cli
/* Tell the processor where our Global Descriptor Table is in memory. */
lgdt gdt_descriptor
/*
Set PE (Protection Enable) bit in CR0 (Control Register 0),
effectively entering protected mode.
*/
2015-10-18 14:56:12 +02:00
mov %cr0, %eax
orl $0x1, %eax
mov %eax, %cr0
ljmp $CODE_SEG, $protected_mode
2015-10-18 14:56:12 +02:00
.code32
gdt_start:
gdt_null:
.long 0x0
.long 0x0
gdt_code:
.word 0xffff
.word 0x0
.byte 0x0
.byte 0b10011010
.byte 0b11001111
.byte 0x0
gdt_data:
.word 0xffff
.word 0x0
.byte 0x0
.byte 0b10010010
.byte 0b11001111
.byte 0x0
gdt_end:
gdt_descriptor:
.word gdt_end - gdt_start
.long gdt_start
vga_current_line:
.long 0
2015-10-18 14:56:12 +02:00
protected_mode:
/*
Setup the other segments.
Those movs are mandatory because they update the descriptor cache:
http://wiki.osdev.org/Descriptor_Cache
*/
2015-10-18 14:56:12 +02:00
mov $DATA_SEG, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
/* TODO detect memory properly. */
mov $0X7000, %ebp
2015-10-18 14:56:12 +02:00
mov %ebp, %esp
.endm
/* BIOS */
2015-09-20 10:59:36 +02:00
#define CURSOR_POSITION(x, y) \
mov $0x02, %ah;\
mov $0x00, %bh;\
mov $0x ## x ## y, %dx;\
int $0x10
/* Clear the screen, move to position 0, 0. */
2015-10-18 14:56:12 +02:00
.macro CLEAR
mov $0x0600, %ax
mov $0x7, %bh
mov $0x0, %cx
mov $0x184f, %dx
int $0x10
2015-09-20 10:59:36 +02:00
CURSOR_POSITION(0, 0)
2015-10-18 14:56:12 +02:00
.endm
2015-09-20 10:59:36 +02:00
/*
2015-09-20 15:51:38 +02:00
Print a single immediate byte or 8 bit register.
2015-09-20 10:59:36 +02:00
`c` is it's value in hex.
2015-09-20 15:51:38 +02:00
Usage: character 'A' (ASCII 61):
PUTS(61)
Clobbers: ax
2015-09-20 10:59:36 +02:00
*/
#define PUTC(c) \
2015-09-20 15:51:38 +02:00
mov $0x0E, %ah;\
mov c, %al;\
int $0x10
2015-09-24 10:17:28 +02:00
/*
Convert a byte to hex ASCII value.
c: r/m8 byte to be converted
Output: two ASCII characters, is stored in `ah:al`
2015-09-24 10:17:28 +02:00
http://stackoverflow.com/questions/3853730/printing-hexadecimal-digits-with-assembly
*/
#define HEX(c) GAS_HEX c
.macro GAS_HEX c
mov \c, %al
mov \c, %ah
2015-09-24 10:17:28 +02:00
shr $4, %al
GAS_HEX_NIBBLE al
and $0x0F, %ah
GAS_HEX_NIBBLE ah
2015-09-24 10:17:28 +02:00
.endm
/*
Convert the low nibble of a r8 reg to ASCII of 8-bit in-place.
reg: r8 to be converted
Clobbered registers: none
2015-09-24 10:17:28 +02:00
Output: stored in reg itself. Letters are uppercase.
*/
.macro GAS_HEX_NIBBLE reg
LOCAL letter, end
cmp $10, %\reg
jae letter
/* 0x30 == '0' */
add $0x30, %\reg
jmp end
letter:
/* 0x37 == 'A' - 10 */
add $0x37, %\reg
end:
.endm
/*
Print a byte as two hexadecimal digits.
2015-10-06 12:27:24 +02:00
reg: 1 byte register.
Clobbers: ax, dl
*/
2015-10-20 21:04:49 +02:00
.macro PRINT_HEX reg
HEX(<\reg>)
mov %ah, %dl
PUTC(%al)
PUTC(%dl)
2015-10-20 21:04:49 +02:00
.endm
2015-09-29 23:55:54 +02:00
#define PRINT_NEWLINE \
PUTC($0x0A);\
PUTC($0x0D)
2015-09-20 15:51:38 +02:00
/*
Print a null terminated string.
Use as:
PRINT($s)
hlt
s:
.asciz "string"
We use this `cpp` macro to allow writing `PRINT(S)` with parenthesis.
*/
#define PRINT(s) GAS_PRINT s
.macro GAS_PRINT s
2015-09-24 10:17:28 +02:00
LOCAL loop, end
2015-09-20 15:51:38 +02:00
mov s, %si
mov $0x0e, %ah
loop:
lodsb
or %al, %al
2015-09-24 10:17:28 +02:00
jz end
2015-09-20 10:59:36 +02:00
int $0x10
2015-09-20 15:51:38 +02:00
jmp loop
2015-09-24 10:17:28 +02:00
end:
2015-09-20 15:51:38 +02:00
.endm
2015-10-06 12:27:24 +02:00
/* VGA */
2015-10-18 14:56:12 +02:00
/*
Print a NULL terminated string to position 0 in VGA.
s: 32-bit register or memory containing the address of the string to print.
Clobbers: none.
Uses and updates vga_current_line to decide the current line.
Loops around the to the top.
2015-10-18 14:56:12 +02:00
*/
.macro VGA_PRINT_STRING s
2015-10-18 14:56:12 +02:00
LOCAL loop, end
push %eax
push %ebx
push %ecx
push %edx
mov \s, %ecx
mov vga_current_line, %eax
mov $0, %edx
/* Number of horizontal lines. */
mov $25, %ebx
div %ebx
mov %edx, %eax
/* 160 == 80 * 2 == line width * bytes per character on screen */
mov $160, %edx
mul %edx
/* 0xb8000 == magic video memory address which shows on the screen. */
lea 0xb8000(%eax), %edx
2015-10-18 14:56:12 +02:00
/* White on black. */
mov $0x0f, %ah
loop:
mov (%ecx), %al
cmp $0, %al
je end
mov %ax, (%edx)
add $1, %ecx
add $2, %edx
jmp loop
end:
incl vga_current_line
pop %edx
pop %ecx
pop %ebx
pop %eax
.endm
2015-10-20 21:00:24 +02:00
/*
Print a 32-bit register in hex.
Sample usage:
mov $12345678, %eax
VGA_PRINT_REG <%eax>
Expected output on screen:
12345678
*/
.macro VGA_PRINT_REG reg=<%eax>
LOCAL loop
push %eax
push %ebx
push %ecx
push %edx
/* Null terminator. */
mov \reg, %ecx
/* Write ASCII representation to memory. */
push $0
mov $2, %ebx
loop:
GAS_HEX <%cl>
mov %ax, %dx
shl $16, %edx
GAS_HEX <%ch>
mov %ax, %dx
push %edx
shr $16, %ecx
dec %ebx
cmp $0, %ebx
jne loop
/* Print it. */
mov %esp, %edx
VGA_PRINT_STRING <%edx>
/* Restore the stack. We have pushed 3 * 4 bytes. */
add $12, %esp
pop %edx
pop %ecx
pop %ebx
pop %eax
2015-10-18 14:56:12 +02:00
.endm