Timer interrupts don't work under qemu (work under Vmware)
Posted: Sat Aug 11, 2018 10:49 am
I started writing kernel on top of code provided in Bare Bones tutorial.
First thing I wanted to implement is timer interrupt handling.
So I implemented GDT and IDT initialization, implemented functionality for setting up interrupts handlers in C code.
The problem is timer interrupts fire and work as expected in VMware Workstation, they are not firing when testing on QEMU or VirtualBox.
I spent week trying to figure out why this is happening, no success so far.
Honestly I do not know what I am doing wrong, I dont think the QEMU or VirtualBox are buggy.
I will post images and explanation how I implemented GDT and IDT initialization( Could this be a problem).
Here are images:
VMware:
QEMU:
VirtualBox:
Link to repo: https://github.com/bojan96/kernel
As I wrote earlier I started making kernel with code provided in Bare Bones tutorial.
In src directory there is assembly code and C code.
boot.asm is modified boot.s from Bare Bones tutorial.
When _start function is called( i.e. when GRUB boots the kernel), I disable all interrupts, set up stack and call procedure gdt_init which performs GDT initialization which is defined in same file.
In .data section I define 3 descriptors null, code and data.
gdt_init copies address of null descriptor(table start address), and GDT_SIZE(23, this is last byte in table) to memory location gdt_reg,
from there I load GDTR.
After that I load all data segment registers with DATA_SELECTOR (0x10), and perform far jump to label inside this procedure in order to initialize CS register.
After that remap_irq procedure is called, this procedure is defined in irq.asm.
This procedure remaps PIC, 0-15 PIC interrupts are mapped to 32-48 entries in IDT.
The code:
masks all hardware interrupts except timer interrupt.
Next initialization procedure is init_idt.
I reserve memory in data segment for IDT(idt label).
The line:
initializes 2048 bytes(max 256 entries, each entry has size of 8 bytes) with 0.
I defined three macros for populating IDT.
- IDT_ENTRY_SETUP - This macro intializes one entry with isr_handler_<number> (isr_handler_<number> are global labels defined in isr.asm. Those handlers call C function with argument that corresponds to IDT index. We use that code to print message that corresponds to fault/trap which caused the interrupt.(e.g. 0 corresponds to Divide by zero exception).
- IDT_ENTRY_SETUP_ERR - So far I did not care about these interrupts, this macro initializes entry handler with interrupt_error(this handler just pops error code and returns).
- IDT_IRQ_ENTRY_SETUP - this one initializes entry handler with irq_handler_<number>(<number> corresponds to IRQ index, e.g. irq_handler_0 is handler for timer interrupt). Those handlers are defined in irq.asm via macro. Those handlers just invoke common asm procedure which saves all general purpose registers, and invoke common C function(irq_common_high_level) defined in irq_high_level.c.
After returning, we check which IRQ invoked handler, and based on that we send EOI either to master or both master and slave.
Now it is time to initialize IDT register.
I initialize memory location(idt_reg) with IDT_SIZE(index of last byte in table) and address of table(idt label).
From that memory location I load IDTR.
After that kernel_main function(defined in kernel.c) is called which initializes terminal and registers timer handler( which prints "Timer Handler").
Interrupts are enabled with function "enableInterrupts"(sti instruction).
Also I invoke handlers manually to test interrupt return.
Also on QEMU for unknown reasons the extra "Division by zero" is printed. Neither do I perform division nor I invoke it manually.
( I noticed this happening every time I enable interrupts with sti instruction).
Question:
Did I miss something during initialization, is it possible that VB and QEMU have buggy PIC implemenations.
First thing I wanted to implement is timer interrupt handling.
So I implemented GDT and IDT initialization, implemented functionality for setting up interrupts handlers in C code.
The problem is timer interrupts fire and work as expected in VMware Workstation, they are not firing when testing on QEMU or VirtualBox.
I spent week trying to figure out why this is happening, no success so far.
Honestly I do not know what I am doing wrong, I dont think the QEMU or VirtualBox are buggy.
I will post images and explanation how I implemented GDT and IDT initialization( Could this be a problem).
Here are images:
VMware:
QEMU:
VirtualBox:
Link to repo: https://github.com/bojan96/kernel
As I wrote earlier I started making kernel with code provided in Bare Bones tutorial.
In src directory there is assembly code and C code.
boot.asm is modified boot.s from Bare Bones tutorial.
When _start function is called( i.e. when GRUB boots the kernel), I disable all interrupts, set up stack and call procedure gdt_init which performs GDT initialization which is defined in same file.
In .data section I define 3 descriptors null, code and data.
gdt_init copies address of null descriptor(table start address), and GDT_SIZE(23, this is last byte in table) to memory location gdt_reg,
from there I load GDTR.
After that I load all data segment registers with DATA_SELECTOR (0x10), and perform far jump to label inside this procedure in order to initialize CS register.
After that remap_irq procedure is called, this procedure is defined in irq.asm.
This procedure remaps PIC, 0-15 PIC interrupts are mapped to 32-48 entries in IDT.
The code:
Code: Select all
OUT 0x21, 0xfe
OUT 0xa1, 0xff
Next initialization procedure is init_idt.
I reserve memory in data segment for IDT(idt label).
The line:
Code: Select all
idt times 2048 db 0
I defined three macros for populating IDT.
- IDT_ENTRY_SETUP - This macro intializes one entry with isr_handler_<number> (isr_handler_<number> are global labels defined in isr.asm. Those handlers call C function with argument that corresponds to IDT index. We use that code to print message that corresponds to fault/trap which caused the interrupt.(e.g. 0 corresponds to Divide by zero exception).
- IDT_ENTRY_SETUP_ERR - So far I did not care about these interrupts, this macro initializes entry handler with interrupt_error(this handler just pops error code and returns).
- IDT_IRQ_ENTRY_SETUP - this one initializes entry handler with irq_handler_<number>(<number> corresponds to IRQ index, e.g. irq_handler_0 is handler for timer interrupt). Those handlers are defined in irq.asm via macro. Those handlers just invoke common asm procedure which saves all general purpose registers, and invoke common C function(irq_common_high_level) defined in irq_high_level.c.
After returning, we check which IRQ invoked handler, and based on that we send EOI either to master or both master and slave.
Now it is time to initialize IDT register.
I initialize memory location(idt_reg) with IDT_SIZE(index of last byte in table) and address of table(idt label).
From that memory location I load IDTR.
After that kernel_main function(defined in kernel.c) is called which initializes terminal and registers timer handler( which prints "Timer Handler").
Interrupts are enabled with function "enableInterrupts"(sti instruction).
Also I invoke handlers manually to test interrupt return.
Code: Select all
__asm__ volatile("int 0\n\t":::); // "Division by zero"
__asm__ volatile("int 1\n\t":::); // "Debug"
__asm__ volatile("int 2\n\t":::); // "Non Maskable Interrupt"
__asm__ volatile("int 3\n\t":::); // "Breakpoint"
( I noticed this happening every time I enable interrupts with sti instruction).
Question:
Did I miss something during initialization, is it possible that VB and QEMU have buggy PIC implemenations.