Timer interrupts don't work under qemu (work under Vmware)

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
bolevole96
Posts: 3
Joined: Tue Jun 12, 2018 8:57 am

Timer interrupts don't work under qemu (work under Vmware)

Post by bolevole96 »

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:
Image
QEMU:
Image
VirtualBox:
Image

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
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:

Code: Select all

idt times 2048 db 0
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.

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"
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.
MichaelPetch
Member
Member
Posts: 799
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: Timer interrupts don't work under qemu (work under Vmwar

Post by MichaelPetch »

in IRQ.ASM you have this:

Code: Select all

    mov ax, %2
    out %1, ax
out %1, ax will use the OUTW instruction to write a WORD to the port since you use the AX register. The ports in question require OUTB. This causes the remapping of IRQs to not work and thus your interrupts don't fire as expected. Change to:

Code: Select all

mov al, %2
out %1, al
bolevole96
Posts: 3
Joined: Tue Jun 12, 2018 8:57 am

Re: Timer interrupts don't work under qemu (work under Vmwar

Post by bolevole96 »

MichaelPetch wrote:in IRQ.ASM you have this:

Code: Select all

    mov ax, %2
    out %1, ax
out %1, ax will use the OUTW instruction to write a WORD to the port since you use the AX register. The ports in question require OUTB. This causes the remapping of IRQs to not work and thus your interrupts don't fire as expected. Change to:

Code: Select all

mov al, %2
out %1, al
I had checked this code dozen of times, never find these two lines suspicious, I thought I missed something on larger scale.
I am kinda embarrassed now, but then again thank you for having time to check this.
Post Reply