Kernel falls into a boot loop at STI instruction

Viewed 96

I am writing an x86_64 kernel for an exam and it seems to reboot every time it runs an STI instruction, which, as defined in my code, is on every boot. I have set up GDT, IDT and ICWs, and masked all IRQs except IRQ1, which is for my keyboard input. The kernel runs perfectly without STI, except for keyboard input.

Here is my bootloader:

main.asm

global start

extern long_mode_start

bits 32
section .text

start:
    mov esp, stack_top

    call check_multiboot
    call check_cpuid
    call check_long_mode

    call setup_page_tables
    call enable_paging

    lgdt [gdt64.pointer]
    jmp gdt64.code_segment:long_mode_start

    hlt

check_multiboot:
    cmp eax, 0x36d76289
    jne .no_multiboot
    ret
.no_multiboot:
    mov al, "M"
    jmp error

check_cpuid:
    pushfd
    pop eax
    mov ecx, eax
    xor eax, 1 << 21
    push eax
    popfd
    pushfd
    pop eax
    push ecx
    popfd
    cmp eax, ecx
    je .no_cpuid
    ret
.no_cpuid:
    mov al, "C"
    jmp error

check_long_mode:
    mov eax, 0x80000000
    cpuid
    cmp eax, 0x80000001
    jb .no_long_mode

    mov eax, 0x80000001
    cpuid
    test edx, 1 << 29
    jz .no_long_mode
    
    ret
.no_long_mode:
    mov al, "L"
    jmp error

setup_page_tables:
    mov eax, page_table_l3
    or eax, 0b11 ; present, writable
    mov [page_table_l4], eax
    
    mov eax, page_table_l2
    or eax, 0b11 ; present, writable
    mov [page_table_l3], eax

    mov ecx, 0 ; counter
.loop:

    mov eax, 0x200000 ; 2MiB
    mul ecx
    or eax, 0b10000011 ; present, writable, huge page
    mov [page_table_l2 + ecx * 8], eax

    inc ecx ; increment counter
    cmp ecx, 512 ; checks if the whole table is mapped
    jne .loop ; if not, continue

    ret

enable_paging:
    ; pass page table location to cpu
    mov eax, page_table_l4
    mov cr3, eax

    ; enable PAE
    mov eax, cr4
    or eax, 1 << 5
    mov cr4, eax

    ; enable long mode
    mov ecx, 0xC0000080
    rdmsr
    or eax, 1 << 8
    wrmsr

    ; enable paging
    mov eax, cr0
    or eax, 1 << 31
    mov cr0, eax

    ret

error:
    ; print "ERR: X" where X is the error code
    mov dword [0xb8000], 0x4f524f45
    mov dword [0xb8004], 0x4f3a4f52
    mov dword [0xb8008], 0x4f204f20
    mov byte  [0xb800a], al
    hlt

section .bss
align 4096
page_table_l4:
    resb 4096
page_table_l3:
    resb 4096
page_table_l2:
    resb 4096
stack_bottom:
    resb 4096 * 4
stack_top:

section .rodata
gdt64:
    dq 0 ; zero entry
.code_segment: equ $ - gdt64
    dq (1 << 43) | (1 << 44) | (1 << 47) | (1 << 53) ; code segment
.pointer:
    dw $ - gdt64 - 1 ; length
    dq gdt64 ; address

main64.asm

global long_mode_start
global load_gdt
global load_idt
global keyboard_handler
global ioport_in
global ioport_out
global enable_interrupts

extern main
extern handle_keyboard_interrupt

section .text
bits 64

long_mode_start:
    ; load null into all data segment registers
    mov ax, 0
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    call main
    hlt

bits 32

load_idt:
    mov edx, [esp + 4]
    lidt [edx]
    ret

keyboard_handler:
    pushad
    cld
    call handle_keyboard_interrupt
    popad
    iretd

ioport_in:
    mov edx, [esp + 4]
    in al, dx
    ret

ioport_out:
    mov edx, [esp + 4]
    mov eax, [esp + 8]
    out dx, al
    ret

bits 16
enable_interrupts:
    sti
    ret
    

And here is my kernel:

main.c

#include "io/print.h"
#include "io/input.h"

void print_prompt(){
    print_str("> ");
}

void kernel_main() {
    print_clear();
    print_set_color(PRINT_COLOR_YELLOW, PRINT_COLOR_BLACK);
    print_str("Welcome to vgOS v0.1!!");
    print_newline();
    print_newline();
    print_prompt();
}

int main(){
    kernel_main();
    init_idt();
    enable_interrupts();
    init_kb();
    print_str("here");
    print_newline();

    while(1);
    return 0;
}

input.h

#pragma once

#include <stdint.h>

#define IDT_SIZE 256
#define KERNEL_CODE_SEGMENT_OFFSET 0x8
#define IDT_INTERRUPT_GATE_64BIT 0x0e
#define PIC1_COMMAND_PORT 0x20
#define PIC1_DATA_PORT 0x21
#define PIC2_COMMAND_PORT 0xA0
#define PIC2_DATA_PORT 0xA1
#define KEYBOARD_DATA_PORT 0x60
#define KEYBOARD_STATUS_PORT 0x64

extern void load_gdt();
extern void load_idt(unsigned int idt_address);
extern void keyboard_handler();
extern char ioport_in(unsigned short port);
extern void ioport_out(unsigned short port, unsigned char data);
extern void enable_interrupts();

struct IDTPointer{
    uint16_t limit;
    unsigned long long base;
} __attribute__((packed));

struct IDTEntry{
    uint16_t offset_1;                  // Offset bits 0-15
    uint16_t selector;                  // Code segment selector
    uint8_t ist;                        // Interrupt Stack Table offset
    uint32_t zero;                      
    uint8_t type_attr;                  // Gate, type, dpl and p fields
    uint16_t offset_2;                  // Offset bits 16-31
    uint32_t offset_3;                  // Offset bits 32-63
} __attribute__((packed));

void init_idt();
void init_kb();

input.c

#include "input.h"
#include "print.h"

// Declare IDT
struct IDTEntry IDT[IDT_SIZE];

void init_idt(){
    // Set IDT keyboard entry
    uint64_t offset = (uint64_t)keyboard_handler;
    IDT[0x21].offset_1 = offset & 0x000000000000FFFF;
    IDT[0x21].selector = KERNEL_CODE_SEGMENT_OFFSET;
    IDT[0x21].ist = 0xE;                                        // Set gate type to 'Interrupt'
    IDT[0x21].zero = 0;                                         // 0 for testing purposes
    IDT[0x21].type_attr = IDT_INTERRUPT_GATE_64BIT;
    IDT[0x21].offset_2 = (offset & 0x00000000FFFF0000) >> 16; 
    IDT[0x21].offset_3 = (offset & 0xFFFFFFFF00000000) >> 32;

    // Setup ICWs
    // ICW1
    ioport_out(PIC1_COMMAND_PORT, 0x11);
    ioport_out(PIC2_COMMAND_PORT, 0x11);
    
    // ICW2
    ioport_out(PIC1_DATA_PORT, 0x20);
    ioport_out(PIC2_DATA_PORT, 0x28);

    // ICW3
    ioport_out(PIC1_DATA_PORT, 0x4);
    ioport_out(PIC2_DATA_PORT, 0x2);

    // ICW4
    ioport_out(PIC1_DATA_PORT, 0x01);
    ioport_out(PIC2_DATA_PORT, 0x01);

    // Mask all interrupts
    ioport_out(PIC1_DATA_PORT, 0xff);
    ioport_out(PIC2_DATA_PORT, 0xff);

    // Load IDT data structure
    struct IDTPointer idt_ptr;
    idt_ptr.limit = (sizeof(struct IDTEntry) * IDT_SIZE) - 1;
    idt_ptr.base = (unsigned long long)(&IDT);
    load_idt(&idt_ptr);
}

void init_kb(){
    // 0xFD = 1111 1101 - Unmask IRQ1
    ioport_out(PIC1_DATA_PORT, 0xFD);
}

void handle_keyboard_interrupt(){
    ioport_out(PIC1_COMMAND_PORT, 0x20);
    unsigned char status = ioport_in(KEYBOARD_STATUS_PORT);
    if(status & 0x1){
        char keycode = ioport_in(KEYBOARD_DATA_PORT);
        if(keycode < 0) return;
        print_char(keycode);
    }
}
0 Answers
Related