QEMU triple faults when enabling paging - c

I'm implementing my own kernel and I'm stuck. I'm trying to load my kernel into the high half virtual addresses. I've tackled the identity addresses problem by identity mapping the low 1M of RAM. I've created an init section that is relocated to the kernel's physical address in order to take care of paging initialisation. My kernel's virtual offset is 0xc0000000. This is my linker script:
OUTPUT_FORMAT(elf32-i386)
ENTRY(start)
KERNEL_VIRTUAL_OFFSET = 0xC0000000;
SECTIONS
{
. = 1M;
kernel_start = .;
start_init = .;
.init ALIGN(4K) :
{ *(.multiboot);
*(.init);
*(.tables);
}
end_init = .;
. += KERNEL_VIRTUAL_OFFSET;
kernel_high_half_start = .;
.text ALIGN(4K) : AT(ADDR(.text) - KERNEL_VIRTUAL_OFFSET)
{*(.text) }
.data ALIGN(4K) : AT(ADDR(.data) - KERNEL_VIRTUAL_OFFSET)
{ *(.data) }
.rodata ALIGN(4K) : AT(ADDR(.rodata) - KERNEL_VIRTUAL_OFFSET)
{ *(.rodata) }
.bss ALIGN(4K) : AT(ADDR(.bss) - KERNEL_VIRTUAL_OFFSET)
{ *(.bss) }
kernel_high_half_end = .;
kernel_end = . - KERNEL_VIRTUAL_OFFSET;
}
Here's my entry point. I'm using GRUB as my bootloader. It successfully boots and jumps into my entry point because of the init section:
its 32
section .multiboot
;grub bootloader header
align 4
dd 0x1BADB002 ;magic
dd 0x00 ;flags
dd - (0x1BADB002 + 0x00) ;checksum. m+f+c should be zero
; Declarations
global start
extern kmain
extern paging_init
extern kernel_page_directory
section .init
enable_paging:
mov eax, kernel_page_directory
mov cr3, eax
mov eax, cr0
or eax, 0x80000000
mov cr0, eax ; ***** PAGING ENABLED HERE *****
ret
start:
cli ;block interrupts
mov esp, init_stack
call paging_init
call enable_paging
;mov eax, 0xb8000
;mov byte[eax], 'h'
;mov byte[eax+1], 0x7
; Now high half kernel is mapped to the page directory
mov esp, stack_space ;set stack pointer
push ebx ; grub boot info
call kmain
loop:
hlt ;halt the CPU
jmp loop
resb 4096; 4KB small stack for my init section.
init_stack:
section .bss
resb 8192 ;8KB for stack
stack_space:
Here is my code the fills the page table and the page directory of the kernel. As you can see, the whole code is linked into the init section, to avoid relocation problems:
page_table_t kernel_page_directory[PAGE_DIR_SIZE]
__attribute__((aligned(PAGE_SIZE))) __attribute__((section(".tables"))) = {0};
page_pointer_t kernel_page_tables[PAGE_TABLE_SIZE]
__attribute__((aligned(PAGE_SIZE))) __attribute__((section(".tables"))) = {0};
page_pointer_t identity_page_table[PAGE_TABLE_SIZE]
__attribute__((aligned(PAGE_SIZE))) __attribute__((section(".tables"))) = {0};
/* Identity map the low 1M
* In early boot stage.
*/
static void __attribute__((section(".init"))) map_identity()
{
//map bios
unsigned int current_page = 0;
for(int i = 0; i < BIOS_PAGE_TABLE_ENTRIES; i++, current_page += PAGE_SIZE)
{
identity_page_table[i] = (current_page) | 0x3;
}
//map init
current_page = INIT_START;
for(int i = INIT_START >> 12 & 0x3FF;
i < ((INIT_START >> 12 & 0x3FF) + (INIT_SIZE / PAGE_SIZE));
i++, current_page += PAGE_SIZE)
{
identity_page_table[i] = (current_page) | 0x3;
}
kernel_page_directory[0] = ((unsigned long)(identity_page_table)) | 0x3;
}
/* Map the kernel memory to its page directory,
* **in early boot stage.
* We don't need to map the init section, we don't need it anymore.
*/
__attribute__((section(".init"))) static void map_kernel_memory()
{
//Identity map the init section
//Start at 1MB i.e. its page aligned.
unsigned int start_index = 256;
unsigned long current_page = KERNEL_START;
for(int i = start_index;
i < start_index + (KERNEL_SIZE / PAGE_SIZE) + 1;
i++, current_page += PAGE_SIZE)
{
kernel_page_tables[i] = current_page | 0x3;
}
kernel_page_directory[KERNEL_DIRECTORY_ENTRY] = ((unsigned long)kernel_page_tables) | 0x3;
}
__attribute__((section(".init"))) void paging_init()
{
map_identity();
map_kernel_memory();
}
I tried to point to the exact assembly instruction, but that makes my kernel work incorrectly and I think it is because of mov cr0, eax when I enable paging. CR3 does contain the address of kernel_page_directory or with 0x3. As soon as I enable paging, QEMU stops responding and the system reboots constantly. The screen is being flushed and then printed repeatedly. Any ideas why this is happening? How I can fix it?

Is the address of your page directory Page-Aligned? The size of each page (frame) is 4 KB. I suggest to create a struct for page directory like this:
typedef struct page_directory{
page_table_t *tables[1024];
size_t tablesPhysical[1024]; // Physical address of page tables
size_t physicalAddr; // Physical address of `tablesPhysical'
} page_directory_t;
So, your address of the directory must be the multiple of 4 KB (0x1000). James Molloy's Tutorial may help you.

Related

GDT segment reload failed

I'm writing a little kernel in c for x86 platform, but I'm having trouble to load the gdt and reload the segment selectors.
I am using bochs to test my kernel.
The issue is, when I load the GDT but don't reload the segment selectors, I can stop my program, type info gdt and get a nice result:
When I dont load my GDT:
<bochs:2> info gdt
Global Descriptor Table (base=0x00000000000010b0, limit=32):
GDT[0x0000]=??? descriptor hi=0x00000000, lo=0x00000000
GDT[0x0008]=??? descriptor hi=0x00000000, lo=0x00000000
GDT[0x0010]=Code segment, base=0x00000000, limit=0xffffffff, Execute/Read, Non-Conforming, Accessed, 32-bit
GDT[0x0018]=Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
You can list individual entries with 'info gdt [NUM]' or groups with 'info gdt [NUM] [NUM]'
<bochs:3>
When I load my GDT:
<bochs:2> info gdt
Global Descriptor Table (base=0x00000000001022a0, limit=48):
GDT[0x0000]=??? descriptor hi=0x00000000, lo=0x00000000
GDT[0x0008]=Code segment, base=0x00000000, limit=0xffffffff, Execute/Read, Non-Conforming, 32-bit
GDT[0x0010]=Data segment, base=0x00000000, limit=0xffffffff, Read/Write
GDT[0x0018]=Code segment, base=0x00000000, limit=0x00000fff, Execute-Only, Non-Conforming, 32-bit
GDT[0x0020]=Data segment, base=0x00000000, limit=0x00000fff, Read-Only
GDT[0x0028]=??? descriptor hi=0x00000000, lo=0x00000000
You can list individual entries with 'info gdt [NUM]' or groups with 'info gdt [NUM] [NUM]'
<bochs:3>
So it seems that my GDT is loaded properly.
Now comes the tricky part.
When I want to reload the segment selectors, I'm having this error:
04641352650e[CPU0 ] fetch_raw_descriptor: GDT: index (ff57) 1fea > limit (30)
04641352650e[CPU0 ] interrupt(): vector must be within IDT table limits, IDT.limit = 0x0
04641352650e[CPU0 ] interrupt(): vector must be within IDT table limits, IDT.limit = 0x0
04641352650i[CPU0 ] CPU is in protected mode (active)
04641352650i[CPU0 ] CS.mode = 32 bit
04641352650i[CPU0 ] SS.mode = 32 bit
04641352650i[CPU0 ] EFER = 0x00000000
04641352650i[CPU0 ] | EAX=0000ff53 EBX=00010000 ECX=001022e0 EDX=00000000
04641352650i[CPU0 ] | ESP=00102294 EBP=001022b0 ESI=00000000 EDI=00000000
04641352650i[CPU0 ] | IOPL=0 id vip vif ac vm RF nt of df if tf sf zf af PF cf
04641352650i[CPU0 ] | SEG sltr(index|ti|rpl) base limit G D
04641352650i[CPU0 ] | CS:0010( 0002| 0| 0) 00000000 ffffffff 1 1
04641352650i[CPU0 ] | DS:0018( 0003| 0| 0) 00000000 ffffffff 1 1
04641352650i[CPU0 ] | SS:0018( 0003| 0| 0) 00000000 ffffffff 1 1
04641352650i[CPU0 ] | ES:0018( 0003| 0| 0) 00000000 ffffffff 1 1
04641352650i[CPU0 ] | FS:0018( 0003| 0| 0) 00000000 ffffffff 1 1
04641352650i[CPU0 ] | GS:0018( 0003| 0| 0) 00000000 ffffffff 1 1
04641352650i[CPU0 ] | EIP=001001d8 (001001d8)
04641352650i[CPU0 ] | CR0=0x60000011 CR2=0x00000000
04641352650i[CPU0 ] | CR3=0x00000000 CR4=0x00000000
(0).[4641352650] [0x0000001001d8] 0010:00000000001001d8 (unk. ctxt): mov ds, ax ; 8ed8
04641352650e[CPU0 ] exception(): 3rd (13) exception with no resolution, shutdown status is 00h, resetting
And with that, when I type info gdt again, it gives me a very huge array,
which doesn't even fit in my terminal scrollback capacity.
Here are the last lines:
GDT[0xffd8]=??? descriptor hi=0x72670074, lo=0x64696c61
GDT[0xffe0]=16-Bit TSS (available) at 0x6c65725f, length 0xc6275
GDT[0xffe8]=Data segment, base=0x5f726700, limit=0x0002636f, Read-Only, Expand-down, Accessed
GDT[0xfff0]=Data segment, base=0x00657266, limit=0x00086572, Read/Write, Accessed
GDT[0xfff8]=Data segment, base=0x675f6275, limit=0x00057267, Read/Write
You can list individual entries with 'info gdt [NUM]' or groups with 'info gdt [NUM] [NUM]'
It says me that I want to access data outside of my GDT.
Here is the code I have written so far:
enum SEG_TYPE {
// Data
SEG_TYPE_DRO = 0b0000,
SEG_TYPE_DRW = 0b0010,
SEG_TYPE_DROE = 0b0100,
SEG_TYPE_DRWE = 0b0110,
// Code
SEG_TYPE_CEO = 0b1000,
SEG_TYPE_CER = 0b1010,
SEG_TYPE_CEOC = 0b1100,
SEG_TYPE_CERC = 0b1110,
};
enum SEG_AC {
SEG_AC_KERNEL = 0b11,
SEG_AC_USER = 0b00,
};
void gdt_entry_init(struct gdt_entry* entry, u32 base, u32 limit, enum SEG_TYPE type, enum SEG_AC access_rights) {
// Base address
entry->base_0_15 = base;
entry->base_16_23 = base >> 16;
entry->base_24_31 = base >> 24;
// Limit
entry->limit_0_15 = limit;
entry->limit_16_19 = limit >> 16;
// Segment type
entry->type = type;
// Access rights
entry->dpl = access_rights;
// AVL
entry->avl = 0;
// Default operation set to 32 bits
entry->db = 1;
// Code segment
entry->l = 0;
// Present (always present)
entry->p = 1;
// Descriptor type (code or data)
entry->s = 1;
// Granularity (enabled with 4KBytes increment)
entry->g = 1;
}
struct gdt_entry {
u32 limit_0_15 : 16;
u32 base_0_15 : 16;
u32 base_16_23 : 8;
u32 type : 4;
u32 s : 1;
u32 dpl : 2;
u32 p : 1;
u32 limit_16_19 : 4;
u32 avl : 1;
u32 l : 1;
u32 db : 1;
u32 g : 1;
u32 base_24_31 : 8;
} __attribute__((packed));
struct gdt_r {
u16 limit;
u32 base;
} __attribute__((packed));
struct gdt_entry gdt[6];
void gdt_init() {
// Null segment
struct gdt_entry null_entry = { 0 };
gdt[0] = null_entry;
// Kernel code segment
gdt_entry_init(gdt + 1, 0x0, 0xFFFFFFFF, SEG_TYPE_CER, SEG_AC_KERNEL);
// Kernel data segment
gdt_entry_init(gdt + 2, 0x0, 0xFFFFFFFF, SEG_TYPE_DRW, SEG_AC_KERNEL);
// User code segment
gdt_entry_init(gdt + 3, 0x0, 0x0, SEG_TYPE_CEO, SEG_AC_USER);
// User data segment
gdt_entry_init(gdt + 4, 0x0, 0x0, SEG_TYPE_DRO, SEG_AC_USER);
// TSS
gdt[5] = null_entry;
struct gdt_r gdtr;
gdtr.base = (u32)gdt;
gdtr.limit = sizeof(gdt);
asm volatile("lgdt %0\n"
: /* no output */
: "m" (gdtr)
: "memory");
// 0x10 is the address of the the kernel data segment
asm volatile("movw 0x10, %%ax\n":);
asm volatile("movw %%ax, %%ds\n":);
asm volatile("movw %%ax, %%fs\n":);
asm volatile("movw %%ax, %%gs\n":);
asm volatile("movw %%ax, %%ss\n":);
// 0x8 is the address of the kernel code segment
asm volatile("pushl 0x8\n"
"pushl $1f\n"
"lret\n"
"1:\n"
: /* no output */);
}
If you guys have any idea whats going on with this.
It turned out a really smart person find the issues:
I missed the $ in front of direct value when writing inline asm:
// 0x10 is the address of the the kernel data segment
asm volatile("movw $0x10, %%ax\n":);
asm volatile("movw %%ax, %%ds\n":);
asm volatile("movw %%ax, %%fs\n":);
asm volatile("movw %%ax, %%gs\n":);
asm volatile("movw %%ax, %%ss\n":);
// 0x8 is the address of the kernel code segment
asm volatile("pushl $0x8\n"
"pushl $1f\n"
"lret\n"
"1:\n"
: /* no output */);
I exchanged permissions for KERNEL and USER, correct should be
enum SEG_AC {
SEG_AC_KERNEL = 0b00,
SEG_AC_USER = 0b11,
};

Keyboard interrupt in x86 protected mode causes processor error

I'm working on a simple kernel and I've been trying to implement a keyboard interrupt handler to get rid of port polling. I've been using QEMU in -kernel mode (to reduce compile time, because generating the iso using grub-mkrescue takes quite some time) and it worked just fine, but when I wanted to switch to -cdrom mode it suddenly started crashing. I had no idea why.
Eventually I've realized that when it boots from an iso it also runs a GRUB bootloader before booting the kernel itself. I've figured out GRUB probably switches the processor into protected mode and that causes the problem.
the problem:
Normally I'd simply initialize the interrupt handler and whenever I'd press a key it would be handled. However when I run my kernel using an iso and pressed a key the virtual machine simply crashed. This happened in both qemu and VMWare so I assume there must be something wrong with my interrupts.
Bear in mind that the code work just fine for as long as I don't use GRUB.
interrupts_init()(see below) is one of the first things called in the main() kernel function.
Essentially the question is: Is there a way to make this work in protected mode?.
A complete copy of my kernel can be found in my GitHub repository. Some relevant files:
lowlevel.asm:
section .text
global keyboard_handler_int
global load_idt
extern keyboard_handler
keyboard_handler_int:
pushad
cld
call keyboard_handler
popad
iretd
load_idt:
mov edx, [esp + 4]
lidt [edx]
sti
ret
interrupts.c:
#include <assembly.h> // defines inb() and outb()
#define IDT_SIZE 256
#define PIC_1_CTRL 0x20
#define PIC_2_CTRL 0xA0
#define PIC_1_DATA 0x21
#define PIC_2_DATA 0xA1
extern void keyboard_handler_int(void);
extern void load_idt(void*);
struct idt_entry
{
unsigned short int offset_lowerbits;
unsigned short int selector;
unsigned char zero;
unsigned char flags;
unsigned short int offset_higherbits;
} __attribute__((packed));
struct idt_pointer
{
unsigned short limit;
unsigned int base;
} __attribute__((packed));
struct idt_entry idt_table[IDT_SIZE];
struct idt_pointer idt_ptr;
void load_idt_entry(int isr_number, unsigned long base, short int selector, unsigned char flags)
{
idt_table[isr_number].offset_lowerbits = base & 0xFFFF;
idt_table[isr_number].offset_higherbits = (base >> 16) & 0xFFFF;
idt_table[isr_number].selector = selector;
idt_table[isr_number].flags = flags;
idt_table[isr_number].zero = 0;
}
static void initialize_idt_pointer()
{
idt_ptr.limit = (sizeof(struct idt_entry) * IDT_SIZE) - 1;
idt_ptr.base = (unsigned int)&idt_table;
}
static void initialize_pic()
{
/* ICW1 - begin initialization */
outb(PIC_1_CTRL, 0x11);
outb(PIC_2_CTRL, 0x11);
/* ICW2 - remap offset address of idt_table */
/*
* In x86 protected mode, we have to remap the PICs beyond 0x20 because
* Intel have designated the first 32 interrupts as "reserved" for cpu exceptions
*/
outb(PIC_1_DATA, 0x20);
outb(PIC_2_DATA, 0x28);
/* ICW3 - setup cascading */
outb(PIC_1_DATA, 0x00);
outb(PIC_2_DATA, 0x00);
/* ICW4 - environment info */
outb(PIC_1_DATA, 0x01);
outb(PIC_2_DATA, 0x01);
/* Initialization finished */
/* mask interrupts */
outb(0x21 , 0xFF);
outb(0xA1 , 0xFF);
}
void idt_init(void)
{
initialize_pic();
initialize_idt_pointer();
load_idt(&idt_ptr);
}
void interrupts_init(void)
{
idt_init();
load_idt_entry(0x21, (unsigned long) keyboard_handler_int, 0x08, 0x8E);
/* 0xFD is 11111101 - enables only IRQ1 (keyboard)*/
outb(0x21 , 0xFD);
}
kernel.c
#if defined(__linux__)
#error "You are not using a cross-compiler, you will most certainly run into trouble!"
#endif
#if !defined(__i386__)
#error "This kernel needs to be compiled with a ix86-elf compiler!"
#endif
#include <kernel.h>
// These _init() functions are not in their respective headers because
// they're supposed to be never called from anywhere else than from here
void term_init(void);
void mem_init(void);
void dev_init(void);
void interrupts_init(void);
void shell_init(void);
void kernel_main(void)
{
// Initialize basic components
term_init();
mem_init();
dev_init();
interrupts_init();
// Start the Shell module
shell_init();
// This should be unreachable code
kernel_panic("End of kernel reached!");
}
boot.asm:
bits 32
section .text
;grub bootloader header
align 4
dd 0x1BADB002 ;magic
dd 0x00 ;flags
dd - (0x1BADB002 + 0x00) ;checksum. m+f+c should be zero
global start
extern kernel_main
start:
mov esp, stack_space ;set stack pointer
call kernel_main
; We shouldn't get to here, but just in case do an infinite loop
endloop:
hlt ;halt the CPU
jmp endloop
section .bss
resb 8192 ;8KB for stack
stack_space:
I had a hunch last night as to why loading through GRUB and loading through the Multiboot -kernel feature of QEMU might not work as expected. That is captured in the comments. I have managed to confirm the findings based on more of the source code being released by the OP.
In the Mulitboot Specification there is a note about the GDTR and the GDT with regards to modifying selectors that is relevant:
GDTR
Even though the segment registers are set up as described above, the ‘GDTR’ may be invalid, so the OS image must not load any segment registers (even just reloading the same values!) until it sets up its own ‘GDT’.
An interrupt routine could alter the CS selector causing issues.
There is another concern and most likely the root cause of problems. The Multiboot specification also states this about the selectors it creates in its GDT:
‘CS’
Must be a 32-bit read/execute code segment with an offset of ‘0’ and a
limit of ‘0xFFFFFFFF’. The exact value is undefined.
‘DS’
‘ES’
‘FS’
‘GS’
‘SS’
Must be a 32-bit read/write data segment with an offset of ‘0’ and a limit
of ‘0xFFFFFFFF’. The exact values are all undefined.
Although it says what types of descriptors will be set up it doesn't actually specify that a descriptor has to have a particular index. One Multiboot loader may have a Code segment descriptor at index 0x08 and another bootloader may use 0x10. This is of particular relevance when you look at one line of your code:
load_idt_entry(0x21, (unsigned long) keyboard_handler_int, 0x08, 0x8E);
This creates an IDT descriptor for interrupt 0x21. The third parameter 0x08 is the Code selector the CPU needs to use to access the interrupt handler. I discovered this works on QEMU where the code selector is 0x08, but in GRUB it appears to be 0x10. In GRUB the 0x10 selector points at a non-executable Data segment and this will not work.
To get around all these problems the best thing to do is set up your own GDT shortly after starting up your kernel and before setting up an IDT and enabling interrupts. There is a tutorial on the GDT in the OSDev Wiki if you want more information.
To set up a GDT I'll simply create an assembler routine in lowlevel.asm to do it by adding a load_gdt function and data structures:
global load_gdt
; GDT with a NULL Descriptor, a 32-Bit code Descriptor
; and a 32-bit Data Descriptor
gdt_start:
gdt_null:
dd 0x0
dd 0x0
gdt_code:
dw 0xffff
dw 0x0
db 0x0
db 10011010b
db 11001111b
db 0x0
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0
gdt_end:
; GDT descriptor record
gdt_descriptor:
dw gdt_end - gdt_start - 1
dd gdt_start
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
; Load GDT and set selectors for a flat memory model
load_gdt:
lgdt [gdt_descriptor]
jmp CODE_SEG:.setcs ; Set CS selector with far JMP
.setcs:
mov eax, DATA_SEG ; Set the Data selectors to defaults
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov ss, eax
ret
This creates and loads a GDT that has a NULL Descriptor at index 0x00, a 32-bit code descriptor at 0x08, and a 32-bit data descriptor at 0x10. Since we are using 0x08 as the code selector this matches what you specify as a code selector in your IDT entry initialization for interrupt 0x21:
load_idt_entry(0x21, (unsigned long) keyboard_handler_int, 0x08, 0x8E);
The only other thing is that you'll need to amend your kernel.c to call load_gdt. One can do that with something like:
extern void load_gdt(void);
void kernel_main(void)
{
// Initialize basic components
load_gdt();
term_init();
mem_init();
dev_init();
interrupts_init();
// Start the Shell module
shell_init();
// This should be unreachable code
kernel_panic("End of kernel reached!");
}

Keyboard IRQ within an x86 kernel

I'm trying to program a very simple kernel for learning purposes. After reading a bunch of articles about the PIC and IRQs in the x86 architecture,
I've figured out that IRQ1 is the keyboard handler. I'm using the following code to print the keys being pressed:
#include "port_io.h"
#define IDT_SIZE 256
#define PIC_1_CTRL 0x20
#define PIC_2_CTRL 0xA0
#define PIC_1_DATA 0x21
#define PIC_2_DATA 0xA1
void keyboard_handler();
void load_idt(void*);
struct idt_entry
{
unsigned short int offset_lowerbits;
unsigned short int selector;
unsigned char zero;
unsigned char flags;
unsigned short int offset_higherbits;
};
struct idt_pointer
{
unsigned short limit;
unsigned int base;
};
struct idt_entry idt_table[IDT_SIZE];
struct idt_pointer idt_ptr;
void load_idt_entry(char isr_number, unsigned long base, short int selector, char flags)
{
idt_table[isr_number].offset_lowerbits = base & 0xFFFF;
idt_table[isr_number].offset_higherbits = (base >> 16) & 0xFFFF;
idt_table[isr_number].selector = selector;
idt_table[isr_number].flags = flags;
idt_table[isr_number].zero = 0;
}
static void initialize_idt_pointer()
{
idt_ptr.limit = (sizeof(struct idt_entry) * IDT_SIZE) - 1;
idt_ptr.base = (unsigned int)&idt_table;
}
static void initialize_pic()
{
/* ICW1 - begin initialization */
write_port(PIC_1_CTRL, 0x11);
write_port(PIC_2_CTRL, 0x11);
/* ICW2 - remap offset address of idt_table */
/*
* In x86 protected mode, we have to remap the PICs beyond 0x20 because
* Intel have designated the first 32 interrupts as "reserved" for cpu exceptions
*/
write_port(PIC_1_DATA, 0x20);
write_port(PIC_2_DATA, 0x28);
/* ICW3 - setup cascading */
write_port(PIC_1_DATA, 0x00);
write_port(PIC_2_DATA, 0x00);
/* ICW4 - environment info */
write_port(PIC_1_DATA, 0x01);
write_port(PIC_2_DATA, 0x01);
/* Initialization finished */
/* mask interrupts */
write_port(0x21 , 0xff);
write_port(0xA1 , 0xff);
}
void idt_init()
{
initialize_pic();
initialize_idt_pointer();
load_idt(&idt_ptr);
}
load_idt just uses the lidt x86 instruction. Afterwards I'm loading the keyboard handler:
void kmain(void)
{
//Using grub bootloader..
idt_init();
kb_init();
load_idt_entry(0x21, (unsigned long) keyboard_handler, 0x08, 0x8e);
}
This is the implementation:
#include "kprintf.h"
#include "port_io.h"
#include "keyboard_map.h"
void kb_init(void)
{
/* 0xFD is 11111101 - enables only IRQ1 (keyboard)*/
write_port(0x21 , 0xFD);
}
void keyboard_handler(void)
{
unsigned char status;
char keycode;
char *vidptr = (char*)0xb8000; //video mem begins here.
/* Acknownlegment */
int current_loc = 0;
status = read_port(0x64);
/* Lowest bit of status will be set if buffer is not empty */
if (status & 0x01) {
keycode = read_port(0x60);
if(keycode < 0)
return;
vidptr[current_loc++] = keyboard_map[keycode];
vidptr[current_loc++] = 0x07;
}
write_port(0x20, 0x20);
}
This is the extra code I'm using:
section .text
global load_idt
global keyboard_handler
extern kprintf
extern keyboard_handler_main
load_idt:
sti
mov edx, [esp + 4]
lidt [edx]
ret
global read_port
global write_port
; arg: int, port number.
read_port:
mov edx, [esp + 4]
in al, dx
ret
; arg: int, (dx)port number
; int, (al)value to write
write_port:
mov edx, [esp + 4]
mov al, [esp + 4 + 4]
out dx, al
ret
This is my entry point:
bits 32
section .text
;grub bootloader header
align 4
dd 0x1BADB002 ;magic
dd 0x00 ;flags
dd - (0x1BADB002 + 0x00) ;checksum. m+f+c should be zero
global start
extern kmain
start:
; cli ;block interrupts
mov esp, stack_space ;set stack pointer
call kmain
hlt ;halt the CPU
section .bss
resb 8192 ;8KB for stack
stack_space:
I'm using QEMU to run the kernel:
qemu-system-i386 -kernel kernel
The problem is that I'm not getting any character on the screen. Instead, I still get the same output:
SeaBIOS (version Ubuntu-1.8.2-1-ubuntu1)
Booting from ROM...
How do I solve this problem? Any suggestions?
You have a number of issues with your code. The main ones are discussed individually below.
The HLT instruction will halt the current CPU waiting for the next interrupt. You do have interrupts enabled by this point. After the first interrupt (keystroke) the code after HLT will be executed. It will start executing whatever random data is in memory. You could modify your kmain to do an infinite loop with a HLT instruction. Something like this should work:
while(1) __asm__("hlt\n\t");
In this code:
load_idt:
sti
mov edx, [esp + 4]
lidt [edx]
ret
It is generally a better idea to use STI after you update the interrupt table, not before it. This would be better:
load_idt:
mov edx, [esp + 4]
lidt [edx]
sti
ret
Your interrupt handler needs to perform an iretd to properly return from an interrupt. Your function keyboard_handler will do a ret to return. To resolve this you could create an assembly wrapper that calls the C keyboard_handler function and then does an IRETD.
In a NASM assembly file you could define a global function called keyboard_handler_int like this:
extern keyboard_handler
global keyboard_handler_int
keyboard_handler_int:
call keyboard_handler
iretd
The code to setup the IDT entry would look like this:
load_idt_entry(0x21, (unsigned long) keyboard_handler_int, 0x08, 0x8e);
Your kb_init function eventually enables (via a mask) the keyboard interrupt. Unfortunately, you set up the keyboard handler after you enable that interrupt. It is possible for a keystroke to be pressed after the interrupt is enabled and before the entry is placed in the IDT. A quick fix is to set your keyboard handler up before the call to kb_init with something like:
void kmain(void)
{
//Using grub bootloader..
idt_init();
load_idt_entry(0x21, (unsigned long) keyboard_handler_int, 0x08, 0x8e);
kb_init();
while(1) __asm__("hlt\n\t");
}
The most serious problem that is likely causing your kernel to triple fault (and effectively rebooting the virtual machine) is the way you defined the idt_pointer structure. You used:
struct idt_pointer
{
unsigned short limit;
unsigned int base;
};
The problem is that default alignment rules will place 2 bytes of padding after limit and before base so that the unsigned int will be aligned at a 4 byte offset within the structure. To alter this behavior and pack the data without padding, you can use __attribute__((packed)) on the structure. The definition would look like this:
struct idt_pointer
{
unsigned short limit;
unsigned int base;
} __attribute__((packed));
Doing it this way means that there are no extra bytes placed between limit and base for alignment purposes. Failure to deal with the alignment issue effectively yields a base address that is incorrectly placed in the structure. The IDT pointer needs a 16-bit value representing the size of the IDT followed immediately by a 32-bit value representing the base address of your IDT.
More information on structure alignment and padding can be found in one of Eric Raymond's blogs. Because of the way that members of struct idt_entry are placed there are no extra padding bytes. If you are creating structs that you never want padded I recommend using __attribute__((packed));. This is generally the case when you are mapping a C data structure with a system defined structure. With that in mind I'd also pack struct idt_entry for clarity.
Other considerations
In the interrupt handler, although I suggested an IRETD, there is another issue. As your kernel grows and you add more interrupts you'll discover another problem. Your kernel may act erratically and registers may change values unexpectedly. The issue is that C functions acting as interrupt handlers will destroy the contents of some registers, but we don't save and restore them. Secondly, the direction flag (per the 32-bit ABI) is required to be cleared (CLD) before a function is called. You can't assume the direction flag is cleared upon entry to the interrupt routine. The ABI says:
EFLAGS The flags register contains the system flags, such as the direction
flag and the carry flag. The direction flag must be set to the
‘‘forward’’ (that is, zero) direction before entry and upon exit
from a function. Other user flags have no specified role in the
standard calling sequence and are not preserved
You could push all the volatile registers individually but for brevity you can use the PUSHAD and POPAD instructions. An interrupt handler would be better if it looked like:
keyboard_handler_int:
pushad ; Push all general purpose registers
cld ; Clear direction flag (forward movement)
call keyboard_handler
popad ; Restore all general purpose registers
iretd ; IRET will restore required parts of EFLAGS
; including the direction flag
If you were to save and restore all the volatile registers manually you'd have to save and restore EAX, ECX, and EDX as they don't need to be preserved across C function calls. It generally isn't a good idea to to use x87 FPU instructions in an interrupt handler (mostly for performance), but if you did you'd have to save and restore the x87 FPU state as well.
Sample Code
You didn't provide a complete example, so I filled in some of the gaps (including a simple keyboard map) and slight change to your keyboard handler. The revised keyboard handler only displays key down events and skips over characters that had no mapping. In all cases the code drops through to the end of the handler so that the PIC is sent an EOI (End Of Interrupt). The current cursor location is a static integer that will retain its value across interrupt calls. This allows the position to advance between each character press.
My kprintd.h file is empty, and I put ALL the assembler prototypes into your port_io.h. The prototypes should be divided properly into multiple headers. I only did it this way to reduce the number of files. My file lowlevel.asm defines all the low level assembly routines. The final code is as follows:
kernel.asm:
bits 32
section .text
;grub bootloader header
align 4
dd 0x1BADB002 ;magic
dd 0x00 ;flags
dd - (0x1BADB002 + 0x00) ;checksum. m+f+c should be zero
global start
extern kmain
start:
lgdt [gdtr] ; Load our own GDT, the GDTR of Grub may be invalid
jmp CODE32_SEL:.setcs ; Set CS to our 32-bit flat code selector
.setcs:
mov ax, DATA32_SEL ; Setup the segment registers with our flat data selector
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, stack_space ; set stack pointer
call kmain
; If we get here just enter an infinite loop
endloop:
hlt ; halt the CPU
jmp endloop
; Macro to build a GDT descriptor entry
%define MAKE_GDT_DESC(base, limit, access, flags) \
(((base & 0x00FFFFFF) << 16) | \
((base & 0xFF000000) << 32) | \
(limit & 0x0000FFFF) | \
((limit & 0x000F0000) << 32) | \
((access & 0xFF) << 40) | \
((flags & 0x0F) << 52))
section .data
align 4
gdt_start:
dq MAKE_GDT_DESC(0, 0, 0, 0); null descriptor
gdt32_code:
dq MAKE_GDT_DESC(0, 0x00ffffff, 10011010b, 1100b)
; 32-bit code, 4kb gran, limit 0xffffffff bytes, base=0
gdt32_data:
dq MAKE_GDT_DESC(0, 0x00ffffff, 10010010b, 1100b)
; 32-bit data, 4kb gran, limit 0xffffffff bytes, base=0
end_of_gdt:
gdtr:
dw end_of_gdt - gdt_start - 1
; limit (Size of GDT - 1)
dd gdt_start ; base of GDT
CODE32_SEL equ gdt32_code - gdt_start
DATA32_SEL equ gdt32_data - gdt_start
section .bss
resb 8192 ; 8KB for stack
stack_space:
lowlevel.asm:
section .text
extern keyboard_handler
global read_port
global write_port
global load_idt
global keyboard_handler_int
keyboard_handler_int:
pushad
cld
call keyboard_handler
popad
iretd
load_idt:
mov edx, [esp + 4]
lidt [edx]
sti
ret
; arg: int, port number.
read_port:
mov edx, [esp + 4]
in al, dx
ret
; arg: int, (dx)port number
; int, (al)value to write
write_port:
mov edx, [esp + 4]
mov al, [esp + 4 + 4]
out dx, al
ret
port_io.h:
extern unsigned char read_port (int port);
extern void write_port (int port, unsigned char val);
extern void kb_init(void);
kprintf.h:
/* Empty file */
keyboard_map.h:
unsigned char keyboard_map[128] =
{
0, 27, '1', '2', '3', '4', '5', '6', '7', '8', /* 9 */
'9', '0', '-', '=', '\b', /* Backspace */
'\t', /* Tab */
'q', 'w', 'e', 'r', /* 19 */
't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', /* Enter key */
0, /* 29 - Control */
'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', /* 39 */
'\'', '`', 0, /* Left shift */
'\\', 'z', 'x', 'c', 'v', 'b', 'n', /* 49 */
'm', ',', '.', '/', 0, /* Right shift */
'*',
0, /* Alt */
' ', /* Space bar */
0, /* Caps lock */
0, /* 59 - F1 key ... > */
0, 0, 0, 0, 0, 0, 0, 0,
0, /* < ... F10 */
0, /* 69 - Num lock*/
0, /* Scroll Lock */
0, /* Home key */
0, /* Up Arrow */
0, /* Page Up */
'-',
0, /* Left Arrow */
0,
0, /* Right Arrow */
'+',
0, /* 79 - End key*/
0, /* Down Arrow */
0, /* Page Down */
0, /* Insert Key */
0, /* Delete Key */
0, 0, 0,
0, /* F11 Key */
0, /* F12 Key */
0, /* All other keys are undefined */
};
keyb.c:
#include "kprintf.h"
#include "port_io.h"
#include "keyboard_map.h"
void kb_init(void)
{
/* This is a very basic keyboard initialization. The assumption is we have a
* PS/2 keyboard and it is already in a proper state. This may not be the case
* on real hardware. We simply enable the keyboard interupt */
/* Get current master PIC interrupt mask */
unsigned char curmask_master = read_port (0x21);
/* 0xFD is 11111101 - enables only IRQ1 (keyboard) on master pic
by clearing bit 1. bit is clear for enabled and bit is set for disabled */
write_port(0x21, curmask_master & 0xFD);
}
/* Maintain a global location for the current video memory to write to */
static int current_loc = 0;
/* Video memory starts at 0xb8000. Make it a constant pointer to
characters as this can improve compiler optimization since it
is a hint that the value of the pointer won't change */
static volatile char *const vidptr = (char*)0xb8000;
void keyboard_handler(void)
{
signed char keycode;
keycode = read_port(0x60);
/* Only print characters on keydown event that have
* a non-zero mapping */
if(keycode >= 0 && keyboard_map[keycode]) {
vidptr[current_loc++] = keyboard_map[keycode];
/* Attribute 0x07 is white on black characters */
vidptr[current_loc++] = 0x07;
}
/* Send End of Interrupt (EOI) to master PIC */
write_port(0x20, 0x20);
}
main.c:
#include "port_io.h"
#define IDT_SIZE 256
#define PIC_1_CTRL 0x20
#define PIC_2_CTRL 0xA0
#define PIC_1_DATA 0x21
#define PIC_2_DATA 0xA1
void keyboard_handler_int();
void load_idt(void*);
struct idt_entry
{
unsigned short int offset_lowerbits;
unsigned short int selector;
unsigned char zero;
unsigned char flags;
unsigned short int offset_higherbits;
} __attribute__((packed));
struct idt_pointer
{
unsigned short limit;
unsigned int base;
} __attribute__((packed));
struct idt_entry idt_table[IDT_SIZE];
struct idt_pointer idt_ptr;
void load_idt_entry(int isr_number, unsigned long base, short int selector, unsigned char flags)
{
idt_table[isr_number].offset_lowerbits = base & 0xFFFF;
idt_table[isr_number].offset_higherbits = (base >> 16) & 0xFFFF;
idt_table[isr_number].selector = selector;
idt_table[isr_number].flags = flags;
idt_table[isr_number].zero = 0;
}
static void initialize_idt_pointer()
{
idt_ptr.limit = (sizeof(struct idt_entry) * IDT_SIZE) - 1;
idt_ptr.base = (unsigned int)&idt_table;
}
static void initialize_pic()
{
/* ICW1 - begin initialization */
write_port(PIC_1_CTRL, 0x11);
write_port(PIC_2_CTRL, 0x11);
/* ICW2 - remap offset address of idt_table */
/*
* In x86 protected mode, we have to remap the PICs beyond 0x20 because
* Intel have designated the first 32 interrupts as "reserved" for cpu exceptions
*/
write_port(PIC_1_DATA, 0x20);
write_port(PIC_2_DATA, 0x28);
/* ICW3 - setup cascading */
write_port(PIC_1_DATA, 0x04);
write_port(PIC_2_DATA, 0x02);
/* ICW4 - environment info */
write_port(PIC_1_DATA, 0x01);
write_port(PIC_2_DATA, 0x01);
/* Initialization finished */
/* mask interrupts */
write_port(0x21 , 0xff);
write_port(0xA1 , 0xff);
}
void idt_init()
{
initialize_pic();
initialize_idt_pointer();
load_idt(&idt_ptr);
}
void kmain(void)
{
//Using grub bootloader..
idt_init();
load_idt_entry(0x21, (unsigned long) keyboard_handler_int, 0x08, 0x8e);
kb_init();
while(1) __asm__("hlt\n\t");
}
In order to link this kernel I use a file link.ld with this definition:
/*
* link.ld
*/
OUTPUT_FORMAT(elf32-i386)
ENTRY(start)
SECTIONS
{
. = 0x100000;
.text : { *(.text) }
.rodata : { *(.rodata) }
.data : { *(.data) }
.bss : { *(.bss) }
}
I compile and link this code using a GCC i686 cross compiler with these commands:
nasm -f elf32 -g -F dwarf kernel.asm -o kernel.o
nasm -f elf32 -g -F dwarf lowlevel.asm -o lowlevel.o
i686-elf-gcc -g -m32 -c main.c -o main.o -ffreestanding -O3 -Wall -Wextra -pedantic
i686-elf-gcc -g -m32 -c keyb.c -o keyb.o -ffreestanding -O3 -Wall -Wextra -pedantic
i686-elf-gcc -g -m32 -Wl,--build-id=none -T link.ld -o kernel.elf -ffreestanding -nostdlib lowlevel.o main.o keyb.o kernel.o -lgcc
The result is a kernel called kernel.elf with debug information. I prefer an optimization level of -O3 rather than a default of -O0. Debug information makes it easier to debug with QEMU and GDB. The kernel can be debugged with these commands:
qemu-system-i386 -kernel kernel.elf -S -s &
gdb kernel.elf \
-ex 'target remote localhost:1234' \
-ex 'layout src' \
-ex 'layout regs' \
-ex 'break kmain' \
-ex 'continue'
If you wish to debug at the assembly code level replace layout src with layout asm. When run with the input the quick brown fox jumps over the lazy dog 01234567890 QEMU displayed this:

Jumping to a second firmware on stm32f4

I'm building a bootloader for an application running on a stm32.
The purpose of this is to be able to update the main application.
Since our software is pretty modular, my idea was to just configure a minimal version of it. All the initializations are the same, it jumps to a main function that contains all bootloader functionalities (checking if a new firmware is available on external flash, writing it to internal flash if that's the case) and in the end jumping to the actual application - which does the initialization all over again, but this time with additional peripherals, etc., eventually calling the real main.
The memory layout on the internal flash is like this
|0x08000000 boot loader
|----------------------
|0x08006000 application
bootloader main looks like this
extern void CallApplication(void);
int main(void) {
printf("starting bootloader\n");
printf("will jump to " TOSTRING(APP_START_ADDRESS) "\n");
CallApplication();
return 0;
}
where CallApplication is written in assembler
#define VTABLE_START_ADDRESS APP_START_ADDRESS
#define NVIC_VTABLE 0xE000ED08 // Vector Table Offset
.globl CallApplication
.thumb_func
CallApplication:
// Set the application's vector table start address.
movw r0, #(VTABLE_START_ADDRESS & 0xffff)
movt r0, #(VTABLE_START_ADDRESS >> 16)
movw r1, #(NVIC_VTABLE & 0xffff)
movt r1, #(NVIC_VTABLE >> 16)
str r0, [r1]
// Load the stack pointer from the application's vector table.
ldr sp, [r0]
// Load the initial PC from the application's vector table and branch to
// the application's entry point.
ldr r0, [r0, #4]
bx r0
This almost works - the 'real' application is called, does its initialization but eventually crashes for a yet unknown reason.
What's interesting though is that the fault ISR of the bootloader (0x080022ae) is being called, not that of the real application (> 0x08006000) so something about setting the new vector table obviously failed.
2016-02-11 00:21:16,958 - INFO # init UART
2016-02-11 00:21:16,963 - INFO # Application: boot_loader
2016-02-11 00:21:16,973 - INFO # -- init done, starting main --
2016-02-11 00:21:16,974 - INFO # starting bootloader
2016-02-11 00:21:16,976 - INFO # will jump to 0x8006000
2016-02-11 00:21:16,978 - INFO # init UART
2016-02-11 00:21:16,985 - INFO # Application: hello_world
2016-02-11 00:21:17,797 - INFO # -- init done, starting main --
(hard fault led starts flashing)
What am I missing here?
The linker script for the main application defines
MEMORY
{
FLASH (rx) : ORIGIN = 0x08006000, LENGTH = 488K
SRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
}
whereas the bootloader does
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 24K
SRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
}
the rest is shared
SECTIONS
{
.text :
{
_text = .;
/*
* The vector table must be placed to the top of the
* memory map. To achieve this, it was assigned to a
* special section called ".isr_vector"
*/
KEEP(*(.isr_vector))
/* followed by .text and .rodata: */
*(.text*)
*(.rodata*)
_etext = .;
} > FLASH
/* Just to make sure that the contents does not exceed the flash size */
. = ORIGIN(FLASH) + LENGTH(FLASH);
/*
* .data and .bss are placed into SRAM:
*/
.data : AT(ADDR(.text) + SIZEOF(.text))
{
_data = .;
*(.data*)
_edata = .;
} > SRAM
.bss :
{
/* _bss and _ebss will be required during initialization */
_bss = .;
*(.bss*)
_ebss = .;
} > SRAM
.aux : {
. = ALIGN(4);
*(.auxdata) /* .auxdata section */
. = ALIGN(4);
} > SRAM
/* Just to make sure that the contents does not exceed the SRAM size */
. = ORIGIN(SRAM) + LENGTH(SRAM);
}
Edit: I rewrote the section where VTOR is set in C to make it clearer for me what's going on, but I still end up in the bootloader's DefaultISR
printf("starting bootloader\n");
printf("will jump to " TOSTRING(APP_START_ADDRESS) "\n");
printf("before: %x\n", SCB->VTOR);
SCB->VTOR += APP_START_ADDRESS;
printf("after: %x\n", SCB->VTOR);
asm volatile("mov r0, #0x6000");
asm volatile("ldr sp, [r0]");
asm volatile("ldr r0, [r0, #4]");
asm volatile("bx r0");
outputs
2016-02-11 23:49:31,833 - INFO # starting bootloader
2016-02-11 23:49:31,835 - INFO # will jump to 0x6000
2016-02-11 23:49:31,836 - INFO # before: 8000000
2016-02-11 23:49:31,837 - INFO # after: 8006000
2016-02-11 23:49:31,839 - INFO # init UART
2016-02-11 23:49:31,841 - INFO # …
In my case is a STM32L Cortex-M3, but I think it works in the same way.
In the bootloader, after disabling all sources of interrupt (not masking them), I do the following:
#define APP_LOCATION 0x08006000
typedef void (*pFunction)(void);
pFunction jump;
volatile uint32_t jumpAddress;
register uint32_t regMainStackPointer __ASM("msp");
void Jump( void ) {
jumpAddress = *( volatile uint32_t* )( APP_LOCATION + 4 );
jump = ( pFunction )jumpAddress;
mainStackPointer = *( volatile uint32_t* )APP_LOCATION;
jump();
}
And in the application itself, the first thing to do before enabling any interrupt is:
SCB->VTOR = 0x0x08006000;
The linkers here are equal.
I noticed something strange in your code:
SCB->VTOR += APP_START_ADDRESS;
If APP_START_ADDRESS contains the address (0x08006000) instead of the offset (0x6000), the resulting value in VTOR will be 0x08000000 + 0x08006000, perhaps the problem is here?
It might help if you show some code from the application.
Hope it helps.
the CPU init function from the HAL was doing
/* Configure the Vector Table location add offset address ------------------*/
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
and by that overwriting my setting to SCB->VTOR.
It works when this is removed, no further magic required.

C Kernel - Runs fine in Qemu but not in VM

I am developing a kernel from scratch in C. I am having a problem with the keyboard. In Qemu, when I press a key on my keyboard, the keypress is handled normally. When I run it in VirtualBox or on an actual computer, it works fine until I press a key. In virtualbox, when it crashes, it gives me the error code VINF_EM_TRIPLE_FAULT. Here is my (important parts) of the code:
Assembly:
bits 32
section .text
;multiboot spec
align 4
dd 0x1BADB002 ;magic
dd 0x00 ;flags
dd - (0x1BADB002 + 0x00)
global start
global keyboard_handler
global read_port
global write_port
global load_idt
extern main ;this is defined in the c file
extern keyboard_handler_main
read_port:
mov edx, [esp + 4]
;al is the lower 8 bits of eax
in al, dx ;dx is the lower 16 bits of edx
ret
write_port:
mov edx, [esp + 4]
mov al, [esp + 4 + 4]
out dx, al
ret
load_idt:
mov edx, [esp + 4]
lidt [edx]
sti ;turn on interrupts
ret
keyboard_handler:
call keyboard_handler_main
iretd
start:
cli ;block interrupts
mov esp, stack_space
call main
hlt ;halt the CPU
section .bss
resb 8192; 8KB for stack
stack_space:
C (The important parts, I omitted parts that don't involve the keyboard):
struct IDT_entry{
unsigned short int offset_lowerbits;
unsigned short int selector;
unsigned char zero;
unsigned char type_attr;
unsigned short int offset_higherbits;
};
struct IDT_entry IDT[IDT_SIZE];
void idt_init(void)
{
unsigned long keyboard_address;
unsigned long idt_address;
unsigned long idt_ptr[2];
/* populate IDT entry of keyboard's interrupt */
keyboard_address = (unsigned long)keyboard_handler;
IDT[0x21].offset_lowerbits = keyboard_address & 0xffff;
IDT[0x21].selector = KERNEL_CODE_SEGMENT_OFFSET;
IDT[0x21].zero = 0;
IDT[0x21].type_attr = INTERRUPT_GATE;
IDT[0x21].offset_higherbits = (keyboard_address & 0xffff0000) >> 16;
/* Ports
* PIC1 PIC2
*Command 0x20 0xA0
*Data 0x21 0xA1
*/
/* ICW1 - begin initialization */
write_port(0x20 , 0x11);
write_port(0xA0 , 0x11);
/* ICW2 - remap offset address of IDT */
/*
* In x86 protected mode, we have to remap the PICs beyond 0x20 because
* Intel have designated the first 32 interrupts as "reserved" for cpu exceptions
*/
write_port(0x21 , 0x20);
write_port(0xA1 , 0x28);
/* ICW3 - setup cascading */
write_port(0x21 , 0x00);
write_port(0xA1 , 0x00);
/* ICW4 - environment info */
write_port(0x21 , 0x01);
write_port(0xA1 , 0x01);
/* Initialization finished */
/* mask interrupts */
write_port(0x21 , 0xff);
write_port(0xA1 , 0xff);
/* fill the IDT descriptor */
idt_address = (unsigned long)IDT ;
idt_ptr[0] = (sizeof (struct IDT_entry) * IDT_SIZE) + ((idt_address & 0xffff) << 16);
idt_ptr[1] = idt_address >> 16 ;
load_idt(idt_ptr);
}
void kb_init(void)
{
/* 0xFD is 11111101 - enables only IRQ1 (keyboard)*/
write_port(0x21 , 0xFD);
}
void keyboard_handler_main(void) {
unsigned char status;
char keycode;
/* write EOI */
write_port(0x20, 0x20);
status = read_port(KEYBOARD_STATUS_PORT);
/* Lowest bit of status will be set if buffer is not empty */
if (status & 0x01) {
keycode = read_port(KEYBOARD_DATA_PORT);
if(keycode < 0){
return;
}
char c = keyboard_map[(unsigned char) keycode]; //keyboard_map converts key codes to ASCII
(prints the char)
}
}
}
void main(void)
{
idt_init();
kb_init();
while(1);
print("Welcome to Code OS");
}
And then of course they are compiled with a linker.
So why is this working in qemu but not an actual computer? (On an actual computer it works fine until I press any key.)

Resources