547 lines
14 KiB
C
547 lines
14 KiB
C
/* BIOS-OS-APP note - a BIOS, an OS and an app to take a note */
|
|
typedef unsigned char u8;
|
|
typedef unsigned short u16;
|
|
typedef unsigned int u32;
|
|
|
|
#define GDT_CODE 0xB
|
|
#define GDT_DATA 0x2
|
|
#define GDT_TSS 0x9
|
|
#define GDT_CALL_GATE 0xC
|
|
#define FLAG_FLASH_ADDR 0xff800000
|
|
#define FLAG_ADDRESS 0x1400000
|
|
#define STACK_START 0x1000
|
|
#define USER_STACK 0x5000
|
|
#define GDT_BASE 0x1000000
|
|
#define TSS_BASE 0x1000100
|
|
#define KERNEL_CODE_BASE 0x0
|
|
#define KERNEL_CODE_LIMIT 0x200000
|
|
#define KERNEL_DATA_BASE 0x1000000
|
|
#define KERNEL_DATA_LIMIT 0x200000
|
|
#define USER_CODE_BASE 0x200000
|
|
#define USER_CODE_LIMIT 0x100000
|
|
#define USER_DATA_BASE 0x1100000
|
|
#define USER_DATA_LIMIT 0x100000
|
|
#define USER_RODATA (USER_DATA_BASE + 0x1000)
|
|
#define KERNEL_CS 0x08
|
|
#define KERNEL_DS 0x10
|
|
#define USER_CS 0x18
|
|
#define USER_DS 0x20
|
|
#define TSS_SEG 0x28
|
|
#define READ_GATE 0x30
|
|
#define WRITE_GATE 0x38
|
|
#define EXIT_GATE 0x40
|
|
#define UART_BASE 0x3F8
|
|
#define UART_THR (UART_BASE + 0)
|
|
#define UART_RBR (UART_BASE + 0)
|
|
#define UART_LCR (UART_BASE + 3)
|
|
#define UART_DLL (UART_BASE + 0)
|
|
#define UART_DLH (UART_BASE + 1)
|
|
#define UART_LSR (UART_BASE + 5)
|
|
#define UART_FCR (UART_BASE + 2)
|
|
#define BOOT_CS 0x08
|
|
#define BOOT_DS 0x10
|
|
#define KERNEL_LOAD_ADDR 0x10000
|
|
|
|
#define SEGMENT_INIT(base, limit, _type, _s, _dpl) \
|
|
{ \
|
|
.limit_low = (limit) & 0xFFFF, .base_low = (base) & 0xFFFF, \
|
|
.base_mid = ((base) >> 16) & 0xFF, .type = (_type), .s = (_s), \
|
|
.dpl = (_dpl), .p = 1, .limit_high = ((limit) >> 16) & 0x0F, .avl = 0, \
|
|
.rsvd = 0, .db = 1, .g = 1, .base_high = ((base) >> 24) & 0xFF \
|
|
}
|
|
|
|
#define IO_WRITE(byte, port) \
|
|
asm volatile("outb %b0, %1" : : "a"(byte), "d"(port))
|
|
|
|
#define IO_READ(port, byte) \
|
|
asm volatile("inb %1, %0" : "=a"(byte) : "d"(port))
|
|
|
|
#define IO_WRITE16(port, word) \
|
|
asm volatile("outw %w0, %w1" : : "a"(word), "d"(port))
|
|
|
|
#define APP_ADDR_TO_KERNEL_ADDR(X) ((u8*)X + 0x100000)
|
|
|
|
extern u8 _kernel_start[];
|
|
extern u8 _kernel_end[];
|
|
extern u8 _userspace_start[];
|
|
extern u8 _userspace_end[];
|
|
extern u8 _userspace_data_start[];
|
|
extern u8 _userspace_data_end[];
|
|
|
|
struct gdt_seg {
|
|
u16 limit_low;
|
|
u16 base_low;
|
|
u8 base_mid;
|
|
u8 type : 4;
|
|
u8 s : 1;
|
|
u8 dpl : 2;
|
|
u8 p : 1;
|
|
u8 limit_high : 4;
|
|
u8 avl : 1;
|
|
u8 rsvd : 1;
|
|
u8 db : 1;
|
|
u8 g : 1;
|
|
u8 base_high;
|
|
} __attribute__((packed));
|
|
|
|
struct gdt_ptr {
|
|
u16 limit;
|
|
u32 base;
|
|
} __attribute__((packed));
|
|
|
|
struct tss_desc {
|
|
u32 r1;
|
|
u32 esp0;
|
|
u32 ss0;
|
|
u32 r2[2];
|
|
u32 esp1;
|
|
u32 ss1;
|
|
u32 r3[2];
|
|
u32 esp2;
|
|
u32 ss2;
|
|
u32 r4[2];
|
|
u8 unused[76];
|
|
} __attribute__((packed));
|
|
|
|
struct callgate_desc {
|
|
u16 offset_low;
|
|
u16 selector;
|
|
u8 param_count;
|
|
u8 type : 4;
|
|
u8 zero : 1;
|
|
u8 dpl : 2;
|
|
u8 p : 1;
|
|
u16 offset_high;
|
|
} __attribute__((packed));
|
|
|
|
|
|
__attribute__((section(".bios.gdt"))) struct gdt_seg bios_gdt[] = {
|
|
SEGMENT_INIT(0x0, 0x0, 0x0, 0, 0),
|
|
SEGMENT_INIT(0x0, 0xFFFFFFFF, 0xB, 1, 0),
|
|
SEGMENT_INIT(0x0, 0xFFFFFFFF, 0x2, 1, 0),
|
|
};
|
|
|
|
__attribute__((section(".bios.gdt_ptr")))
|
|
struct gdt_ptr gdtr = {
|
|
.limit = sizeof(bios_gdt) - 1,
|
|
.base = 0xffffff00,
|
|
};
|
|
|
|
__attribute__((section(".bios.text")))
|
|
void bios_uart_init(void) {
|
|
/* setup the UART0 with baud 115200 8N1 */
|
|
IO_WRITE(0x80, UART_LCR);
|
|
IO_WRITE(0x01, UART_DLL);
|
|
IO_WRITE(0x00, UART_DLH);
|
|
IO_WRITE(0x03, UART_LCR);
|
|
IO_WRITE(0xC7, UART_FCR);
|
|
}
|
|
|
|
void kernel_run(void);
|
|
|
|
__attribute__((section(".bios.text")))
|
|
void bios_start(void) {
|
|
u8 *os, *os_origin;
|
|
u32 os_size;
|
|
u32 i;
|
|
|
|
bios_uart_init();
|
|
os = (u8*)KERNEL_LOAD_ADDR;
|
|
os_origin = _kernel_start;
|
|
os_size = _kernel_end - _kernel_start;
|
|
for (i = 0; i < os_size; i++) {
|
|
os[i] = os_origin[i];
|
|
}
|
|
kernel_run();
|
|
}
|
|
|
|
__attribute__((section(".bios.jmp_pm"), naked))
|
|
void bios_enable_protected(void) {
|
|
asm volatile(
|
|
".code16\n"
|
|
"cli\n"
|
|
"mov %[GDT], %%bx\n"
|
|
"lgdtl %%cs:(%%bx)\n"
|
|
"mov %%cr0, %%eax\n"
|
|
"or $0x01, %%eax\n"
|
|
"mov %%eax, %%cr0\n"
|
|
"ljmpl %[CODE_SEL],$1f\n"
|
|
"1:\n"
|
|
".code32\n"
|
|
"mov %[DATA_SEL], %%ax\n"
|
|
"movw %%ax, %%ds\n"
|
|
"movw %%ax, %%ss\n"
|
|
"mov %[STACK], %%esp\n"
|
|
"jmp bios_start\n"
|
|
:
|
|
: [GDT] "i"(&gdtr), [STACK] "i"(STACK_START), [DATA_SEL] "i"(BOOT_DS), [CODE_SEL] "i"(BOOT_CS)
|
|
: "eax");
|
|
}
|
|
|
|
/* where everything starts */
|
|
__attribute__((section(".bios.reset_vector"), naked))
|
|
void reset_vector(void) {
|
|
asm volatile(".code16\n"
|
|
"jmp bios_enable_protected\n"
|
|
".code32\n");
|
|
}
|
|
|
|
__attribute__((section(".kernel.text")))
|
|
void kernel_uart_tx(u8 byte) {
|
|
u8 status;
|
|
do {
|
|
IO_READ(UART_LSR, status);
|
|
} while ((status & 0x20) == 0);
|
|
IO_WRITE(byte, UART_THR);
|
|
}
|
|
|
|
__attribute__((section(".kernel.text")))
|
|
u8 kernel_uart_rx(void) {
|
|
u8 status;
|
|
do {
|
|
IO_READ(UART_LSR, status);
|
|
} while ((status & 0x01) == 0);
|
|
IO_READ(UART_RBR, status);
|
|
return status;
|
|
}
|
|
|
|
__attribute__((section(".kernel.text")))
|
|
void kernel_uart_write(const char *buf, u32 len) {
|
|
u8 *ubuf;
|
|
ubuf = APP_ADDR_TO_KERNEL_ADDR(buf);
|
|
while (len--) {
|
|
kernel_uart_tx(*ubuf++);
|
|
}
|
|
}
|
|
|
|
__attribute__((section(".kernel.text")))
|
|
int kernel_uart_read(char *buf, u32 buf_size) {
|
|
u8 *ubuf;
|
|
char c;
|
|
int i = 0;
|
|
ubuf = APP_ADDR_TO_KERNEL_ADDR(buf);
|
|
while (i < buf_size) {
|
|
c = kernel_uart_rx();
|
|
if (c == '\n' || c == '\r')
|
|
break;
|
|
ubuf[i] = c;
|
|
i++;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
__attribute__((section(".kernel.text")))
|
|
void kernel_write_gdt_segment(struct gdt_seg *s, u32 base, u32 limit, u8 type, u8 dpl) {
|
|
limit = limit >> 12;
|
|
s->limit_low = limit & 0xFFFF;
|
|
s->limit_high = (limit >> 16) & 0x0F;
|
|
s->base_low = base & 0xFFFF;
|
|
s->base_mid = (base >> 16) & 0xFF;
|
|
s->base_high = (base >> 24) & 0xFF;
|
|
s->type = type;
|
|
s->dpl = dpl;
|
|
s->p = s->db = s->g = 1;
|
|
s->s = (type == GDT_TSS) ? 0 : 1;
|
|
s->avl = s->rsvd = 0;
|
|
}
|
|
|
|
__attribute__((section(".kernel.text")))
|
|
void kernel_memset(void *dest, u8 byte, int len) {
|
|
u8 *p = (u8 *)dest;
|
|
while (len--) {
|
|
*p++ = byte;
|
|
}
|
|
}
|
|
|
|
__attribute__((section(".kernel.text"), naked))
|
|
void sys_read() {
|
|
asm volatile("pushl %%ebx\n"
|
|
"pushl %%eax\n"
|
|
"mov %[KDATA_SEL], %%ax\n"
|
|
"mov %%ax, %%ds\n"
|
|
"mov %%ax, %%es\n"
|
|
"call kernel_uart_read\n"
|
|
"mov %%eax, %%ebx\n"
|
|
"mov %[UDATA_SEL], %%ax\n"
|
|
"mov %%ax, %%ds\n"
|
|
"mov %%ax, %%es\n"
|
|
"mov %%ebx, %%eax\n"
|
|
"add $8, %%esp\n"
|
|
"retfl\n"
|
|
:
|
|
: [KDATA_SEL] "i" (KERNEL_DS), [UDATA_SEL] "i" (USER_DS)
|
|
: "eax", "ebx");
|
|
}
|
|
|
|
__attribute__((section(".kernel.text"), naked))
|
|
void sys_write() {
|
|
asm volatile("pushl %%ebx\n"
|
|
"pushl %%eax\n"
|
|
"mov %[KDATA_SEL], %%ax\n"
|
|
"mov %%ax, %%ds\n"
|
|
"mov %%ax, %%es\n"
|
|
"call kernel_uart_write\n"
|
|
"mov %%eax, %%ebx\n"
|
|
"mov %[UDATA_SEL], %%ax\n"
|
|
"mov %%ax, %%ds\n"
|
|
"mov %%ax, %%es\n"
|
|
"mov %%ebx, %%eax\n"
|
|
"add $8, %%esp\n"
|
|
"retfl\n"
|
|
:
|
|
: [KDATA_SEL] "i" (KERNEL_DS), [UDATA_SEL] "i" (USER_DS)
|
|
: "eax", "ebx");
|
|
}
|
|
|
|
__attribute__((section(".kernel.text"), naked))
|
|
void sys_exit() {
|
|
asm volatile("hlt\n");
|
|
}
|
|
|
|
__attribute__((section(".kernel.text")))
|
|
void kernel_load_application(void)
|
|
{
|
|
u8 *dst, *start;
|
|
u32 size;
|
|
u32 i;
|
|
|
|
dst = (u8*)USER_CODE_BASE;
|
|
start = _userspace_start;
|
|
size = _userspace_end - _userspace_start;
|
|
for (i = 0; i < size; i++) {
|
|
dst[i] = start[i];
|
|
}
|
|
|
|
dst = (u8*)USER_RODATA;
|
|
start = _userspace_data_start;
|
|
size = _userspace_data_end - _userspace_start;
|
|
for (i = 0; i < size; i++) {
|
|
dst[i] = start[i];
|
|
}
|
|
}
|
|
|
|
/* we need isolation! let's kernel and user application have different memory segments */
|
|
__attribute__((section(".kernel.text")))
|
|
struct gdt_seg *kernel_setup_segmentation(struct gdt_seg *gdt)
|
|
{
|
|
|
|
/* null segment */
|
|
kernel_memset(gdt, 0, sizeof(struct gdt_seg));
|
|
gdt++;
|
|
kernel_write_gdt_segment(gdt, KERNEL_CODE_BASE, KERNEL_CODE_LIMIT, GDT_CODE, 0);
|
|
gdt++;
|
|
kernel_write_gdt_segment(gdt,KERNEL_DATA_BASE, KERNEL_DATA_LIMIT, GDT_DATA, 0);
|
|
gdt++;
|
|
kernel_write_gdt_segment(gdt, USER_CODE_BASE, USER_CODE_LIMIT, GDT_CODE, 3);
|
|
gdt++;
|
|
kernel_write_gdt_segment(gdt, USER_DATA_BASE, USER_DATA_LIMIT, GDT_DATA, 3);
|
|
gdt++;
|
|
|
|
return gdt;
|
|
}
|
|
|
|
__attribute__((section(".kernel.text")))
|
|
struct gdt_seg *kernel_setup_tss(struct gdt_seg *gdt)
|
|
{
|
|
struct tss_desc *tss;
|
|
|
|
tss = (struct tss_desc *)TSS_BASE;
|
|
kernel_memset((void *)tss, 0, sizeof(struct tss_desc));
|
|
tss->ss0 = KERNEL_DS;
|
|
tss->esp0 = STACK_START;
|
|
tss->ss2 = USER_DS;
|
|
tss->esp2 = USER_STACK;
|
|
|
|
kernel_write_gdt_segment(gdt, TSS_BASE, sizeof(struct tss_desc), GDT_TSS, 0);
|
|
gdt++;
|
|
|
|
return gdt;
|
|
}
|
|
|
|
__attribute__((section(".kernel.text")))
|
|
void kernel_write_callgate_segment(struct gdt_seg *gdt, u32 syscall)
|
|
{
|
|
struct callgate_desc *call;
|
|
call = (struct callgate_desc *)gdt;
|
|
call->offset_low = syscall & 0xFFFF;
|
|
call->offset_high = syscall >> 16;
|
|
call->selector = KERNEL_CS;
|
|
call->param_count = 0x0;
|
|
call->type = GDT_CALL_GATE;
|
|
call->dpl = 3;
|
|
call->p = 1;
|
|
}
|
|
|
|
__attribute__((section(".kernel.text")))
|
|
struct gdt_seg *kernel_setup_syscalls(struct gdt_seg *gdt)
|
|
{
|
|
struct callgate_desc *call;
|
|
|
|
/* call gate: read 0x30 */
|
|
kernel_write_callgate_segment(gdt, (u32)sys_read);
|
|
gdt++;
|
|
|
|
/* call gate: write 0x38 */
|
|
kernel_write_callgate_segment(gdt, (u32)sys_write);
|
|
gdt++;
|
|
|
|
/* call gate: write 0x40 */
|
|
kernel_write_callgate_segment(gdt, (u32)sys_exit);
|
|
gdt++;
|
|
|
|
return gdt;
|
|
}
|
|
|
|
__attribute__((section(".kernel.text")))
|
|
void kernel_load_gdt_and_run_application(struct gdt_seg *gdt)
|
|
{
|
|
struct gdt_ptr gdtr;
|
|
gdtr.limit = (u32)gdt - GDT_BASE - 1;
|
|
gdtr.base = GDT_BASE;
|
|
asm volatile("lgdt %[GDT]\n"
|
|
"mov %[TSS], %%ax\n"
|
|
"ltr %%ax\n"
|
|
"mov %[DATA_SEL], %%ax\n"
|
|
"movw %%ax, %%ds\n"
|
|
"movw %%ax, %%es\n"
|
|
"pushl %[STACK_SEL]\n"
|
|
"pushl %[U_STACK]\n"
|
|
"pushl %[CODE_SEL]\n"
|
|
"pushl $user_app\n"
|
|
"retfl\n"
|
|
:
|
|
: [GDT] "m"(gdtr), [U_STACK] "i"(USER_STACK), [STACK_SEL] "i"(USER_DS | 0x3), [CODE_SEL] "i"(USER_CS | 0x3), [DATA_SEL] "i"(USER_DS), [TSS] "i"(TSS_SEG)
|
|
: "eax");
|
|
}
|
|
|
|
__attribute__((section(".kernel.text")))
|
|
void copy_flag(void) {
|
|
char *flag_dst = (char*)(FLAG_ADDRESS);
|
|
char *flag_src = (char*)FLAG_FLASH_ADDR;
|
|
while (*flag_src) {
|
|
*flag_dst++ = *flag_src++;
|
|
}
|
|
}
|
|
__attribute__((section(".kernel.text")))
|
|
void kernel_run(void) {
|
|
struct gdt_seg *gdt;
|
|
|
|
copy_flag();
|
|
kernel_load_application();
|
|
gdt = (struct gdt_seg *)GDT_BASE;
|
|
gdt = kernel_setup_segmentation(gdt);
|
|
gdt = kernel_setup_tss(gdt);
|
|
gdt = kernel_setup_syscalls(gdt);
|
|
kernel_load_gdt_and_run_application(gdt);
|
|
}
|
|
|
|
__attribute__((section(".userspace.text")))
|
|
int userspace_read(char *buf, int nbytes) {
|
|
int len;
|
|
asm volatile("mov %[_buf], %%eax\n"
|
|
"mov %[_nbyte], %%ebx\n"
|
|
"call %[GATE],$0x0\n"
|
|
"mov %%eax, %[_len]\n"
|
|
: [_len] "=m"(len)
|
|
: [_buf] "m"(buf), [_nbyte] "m"(nbytes), [GATE] "i"(READ_GATE)
|
|
: "eax", "ebx");
|
|
return len;
|
|
}
|
|
__attribute__((section(".userspace.text")))
|
|
void userspace_exit() {
|
|
asm volatile("call %[GATE],$0x0\n"
|
|
:
|
|
:[GATE] "i"(EXIT_GATE)
|
|
:);
|
|
}
|
|
|
|
__attribute__((section(".userspace.text")))
|
|
void userspace_write(const char *buf, int nbytes) {
|
|
/* my stack my call convention */
|
|
asm volatile("mov %[_buf], %%eax\n"
|
|
"mov %[_nbyte], %%ebx\n"
|
|
"call %[GATE],$0x0\n"
|
|
:
|
|
: [_buf] "m"(buf), [_nbyte] "m"(nbytes), [GATE] "i"(WRITE_GATE)
|
|
: "eax", "ebx");
|
|
}
|
|
|
|
__attribute__((section(".userspace.text")))
|
|
int strlen(const char *str) {
|
|
int len = 0;
|
|
while (*str++) {
|
|
len++;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
__attribute__((section(".userspace.text")))
|
|
void puts(const char *str) {
|
|
userspace_write(str, strlen(str));
|
|
}
|
|
|
|
__attribute__((section(".userspace.text")))
|
|
void memcpy(char *dst, const char *src, int len) {
|
|
while (len--) {
|
|
*dst++ = *src++;
|
|
}
|
|
}
|
|
|
|
__attribute__((section(".userspace.text")))
|
|
void print_menu(void)
|
|
{
|
|
puts("1. Write note\n");
|
|
puts("2. Read note\n");
|
|
puts("3. Write final remarks\n");
|
|
puts(">\n");
|
|
}
|
|
|
|
__attribute__((section(".userspace.text")))
|
|
void user_app(void) {
|
|
struct my_notebook {
|
|
char data[200];
|
|
char *ptr;
|
|
u8 used;
|
|
} notebook;
|
|
notebook.ptr = notebook.data;
|
|
notebook.used = 0;
|
|
char buf[128];
|
|
u8 bytes_read;
|
|
u8 new_size;
|
|
|
|
puts("HoD Notebook Taker!\n");
|
|
while (1) {
|
|
print_menu();
|
|
bytes_read = userspace_read(buf, 2);
|
|
if (bytes_read == 0) {
|
|
continue;
|
|
}
|
|
switch (buf[0]) {
|
|
case '1':
|
|
puts("what to note?\n");
|
|
bytes_read = userspace_read(buf, sizeof(buf));
|
|
new_size = notebook.used + bytes_read;
|
|
if (new_size >= sizeof(notebook.data)) {
|
|
puts("No space left\n");
|
|
break;
|
|
}
|
|
memcpy(notebook.ptr, buf, bytes_read);
|
|
notebook.ptr += bytes_read;
|
|
notebook.used += bytes_read;
|
|
break;
|
|
case '2':
|
|
puts("Read note\n");
|
|
userspace_write(notebook.data, notebook.used);
|
|
puts("\n");
|
|
break;
|
|
case '3':
|
|
puts("Write final remarks\n");
|
|
bytes_read = userspace_read(notebook.ptr, sizeof(notebook.data) - notebook.used);
|
|
notebook.used += bytes_read;
|
|
puts("Here is your note:\n");
|
|
userspace_write(notebook.data, notebook.used);
|
|
puts("\nexit\n");
|
|
userspace_exit();
|
|
}
|
|
}
|
|
}
|