Keyboard IRQ within an x86 kernel - c

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:

Related

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!");
}

QEMU triple faults when enabling paging

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.

Keyboard IRQ fires only once

I'm developing a toy unix clone and I'm trying to wire up my interrupts properly. I've run into a problem where my Keyboard IRQ (IRQ 1) fires just once even after I properly acknowledge it and so on. I've enabled the PIT interrupt as well, to double check if my ACKs are going to the PIC okay, and that seems to work fine. (fires multiple times)
One catch with interrup.s is that I'm passing the struct register_t on the stack (by value) and the compiler was trashing that after it returns from the C interrupt handler. Surprisingly, the only value that was getting trashed was the value on the top of the stack (The data segment register in this case) and I've verified that the rest of the values in the stack look okay by printing the stack before and after the call to the interrupt handler occurs. I've added a temporary work-around to fix this issue, but I will clean this up later.
I've also verified that software interrupts work fine by triggering int $3 multiple times.
Any advice is appreciated! Here's the code:
interrupt.s
.macro ISR_NOERRCODE int_no # A macro for ISRs that don't push an error code
.global isr\int_no
isr\int_no:
cli
push $0
push $\int_no
jmp isr_irq_common_stub
.endm
.macro ISR_ERRORCODE int_no # A macro for ISRs that do push an error code
.global isr\int_no
isr\int_no:
cli
push $\int_no
jmp isr_irq_common_stub
.endm
.macro IRQ irq_no, isr_map # A macro for IRQs from the PIC
.global irq\irq_no
irq\irq_no:
xchgw %bx, %bx
cli
push $0 # Error code
push $\isr_map # Interrupt number
jmp isr_irq_common_stub
.endm
ISR_NOERRCODE 0
ISR_NOERRCODE 1
ISR_NOERRCODE 2
ISR_NOERRCODE 3
ISR_NOERRCODE 4
ISR_NOERRCODE 5
ISR_NOERRCODE 6
ISR_NOERRCODE 7
ISR_ERRORCODE 8 # ISR 8 pushes error code onto stack
ISR_NOERRCODE 9
ISR_ERRORCODE 10 # ISR 10 - 14 push error codes onto stack
ISR_ERRORCODE 11
ISR_ERRORCODE 12
ISR_ERRORCODE 13
ISR_ERRORCODE 14
ISR_NOERRCODE 15
ISR_NOERRCODE 16
ISR_ERRORCODE 17
ISR_NOERRCODE 18
ISR_NOERRCODE 19
ISR_NOERRCODE 20
ISR_NOERRCODE 21
ISR_NOERRCODE 22
ISR_NOERRCODE 23
ISR_NOERRCODE 24
ISR_NOERRCODE 25
ISR_NOERRCODE 26
ISR_NOERRCODE 27
ISR_NOERRCODE 28
ISR_NOERRCODE 29
ISR_ERRORCODE 30
ISR_NOERRCODE 31
IRQ 0, 32
IRQ 1, 33
IRQ 2, 34
IRQ 3, 35
IRQ 4, 36
IRQ 5, 37
IRQ 6, 38
IRQ 7, 39
IRQ 8, 40
IRQ 9, 41
IRQ 10, 42
IRQ 11, 43
IRQ 12, 44
IRQ 13, 45
IRQ 14, 46
IRQ 15, 47
# This is in isr.c
.extern isr_irq_handler
# This is our common isr stub. It saves the processor state, sets up for kernel
# mode segments, calls the C-level fault handler, and finally restores the stack
# frame
isr_irq_common_stub:
pusha # Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax
mov %ds, %ax # Lower 16-bits of eax = ds.
push %eax # save the data segment descriptor
mov $0x10, %ax # load the kernel data segment descriptor
mov %ax, %ds # Right now, we dont really have to do this
mov %ax, %es # but after we enter the user mode, the segment
mov %ax, %fs # registers will be different (0x18? and 0x20?)
mov %ax, %gs
call isr_irq_handler
# This does not work because the structure value we passed earlier
# is being messed up by the compiler. It does not preserve the previous eax
# we pushed on to the stack.
pop %eax
mov $0x10, %ax # reload the original data segment descriptor
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
popa # Pops edi,esi,ebp...
add $8, %esp # Cleans up the pushed error code and pushed ISR number
sti
iret # pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP
isr.h
#ifndef __isr_h
#define __isr_h
#include <stdint.h>
struct Registers {
uint32_t ds;
uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax;
uint32_t int_no, err_code;
uint32_t eip, cs, eflags, useresp, ss;
} __attribute__((packed));
typedef struct Registers register_t;
typedef void (*isr_t)(registers_t);
void register_interrupt_handler(uint8_t n, isr_t handler);
void isr_irq_handler(register_t regs);
#endif
isr.c
#include <kernel/isr.h>
#include <kernel/pic.h>
#include <stdio.h>
#include <log.h>
#include <kernel/tty.h>
isr_t interrupt_handlers[256];
void register_interrupt_handler(uint8_t n, isr_t handler)
{
interrupt_handlers[n] = handler;
}
void isr_irq_handler(register_t regs)
{
printf("Received ISR/IRQ: %d\n", regs.int_no);
if (interrupt_handlers[regs.int_no]) {
interrupt_handlers[regs.int_no]();
}
if (regs.int_no >= 32 && regs.int_no <= 47) {
pic_send_eoi(regs.int_no - 32);
}
return;
}
pic.c
#include <kernel/pic.h>
#include <asm.h>
#define PIC1 0x20 /* IO base address for master PIC */
#define PIC2 0xA0 /* IO base address for slave PIC */
#define PIC1_COMMAND PIC1
#define PIC1_DATA (PIC1+1)
#define PIC2_COMMAND PIC2
#define PIC2_DATA (PIC2+1)
#define PIC_EOI 0x20 /* End-of-interrupt command code */
#define ICW1_ICW4 0x01 /* ICW4 (not) needed */
#define ICW1_SINGLE 0x02 /* Single (cascade) mode */
#define ICW1_INTERVAL4 0x04 /* Call address interval 4 (8) */
#define ICW1_LEVEL 0x08 /* Level triggered (edge) mode */
#define ICW1_INIT 0x10 /* Initialization - required! */
#define ICW4_8086 0x01 /* 8086/88 (MCS-80/85) mode */
#define ICW4_AUTO 0x02 /* Auto (normal) EOI */
#define ICW4_BUF_SLAVE 0x08 /* Buffered mode/slave */
#define ICW4_BUF_MASTER 0x0C /* Buffered mode/master */
#define ICW4_SFNM 0x10 /* Special fully nested (not) */
#define PIC_READ_IRR 0x0a /* OCW3 irq ready next CMD read */
#define PIC_READ_ISR 0x0b /* OCW3 irq service next CMD read */
#define PIC1_OFFSET 0x20
#define PIC2_OFFSET 0x28
static void pic_mask(int pic_num, uint16_t mask);
static uint16_t pic_get_irq_reg(int ocw3);
static uint16_t pic_get_irr(void);
static uint16_t pic_get_isr(void);
void setup_remap_pics()
{
uint8_t a1, a2;
// Save existing masks
a1 = inb(PIC1_DATA);
a2 = inb(PIC2_DATA);
outb(PIC1_COMMAND, ICW1_INIT);
io_wait();
outb(PIC2_COMMAND, ICW1_INIT);
io_wait();
outb(PIC1_DATA, PIC1_OFFSET); // We're remapping the PICs interrupt codes from (0x07-0x7F) to (offset, offset + 8)
io_wait();
outb(PIC2_DATA, PIC2_OFFSET);
io_wait();
outb(PIC1_DATA, 4); // Tell the master PIC that there is a slave PIC at IRQ2 (00000100)
io_wait();
outb(PIC2_DATA, 2); // Tell the slave pic it's cascade identity (00000010)
io_wait();
outb(PIC1_DATA, ICW4_8086);
io_wait();
outb(PIC2_DATA, ICW4_8086);
io_wait();
// Restore saved masks
outb(PIC1_DATA, a1);
outb(PIC2_DATA, a2);
enable_interrupts();
// Mask everything except the keyboard, timer
pic_mask(1, 0xFD);
pic_mask(2, 0xFF);
}
static void pic_mask(int pic_num, uint16_t mask) {
uint16_t port = (pic_num == 1) ? PIC1_DATA : PIC2_DATA;
outb(port, mask);
}
// MARK :- Helpers
void pic_send_eoi(uint8_t irq)
{
if (irq >= 8) {
outb(PIC2_COMMAND, PIC_EOI);
}
printf("Sending EOI for IRQ: %d, EOI: %x, to CMD: %x\n", irq, PIC_EOI, PIC1_COMMAND);
// Always signal PIC1 that an interrupt has been handled
// because it's the PIC that forwards PIC2's irqs as well.
outb(PIC1_COMMAND, PIC_EOI);
}
static uint16_t pic_get_irq_reg(int ocw3)
{
/* OCW3 to PIC CMD to get the register values. PIC2 is chained, and
* represents IRQs 8-15. PIC1 is IRQs 0-7, with 2 being the chain */
outb(PIC1_COMMAND, ocw3);
outb(PIC2_COMMAND, ocw3);
return (inb(PIC2_COMMAND) << 8) | inb(PIC1_COMMAND);
}
/* Returns the combined value of the cascaded PICs irq request register */
static uint16_t pic_get_irr(void)
{
return pic_get_irq_reg(PIC_READ_IRR);
}
/* Returns the combined value of the cascaded PICs in-service register */
static uint16_t pic_get_isr(void)
{
return pic_get_irq_reg(PIC_READ_ISR);
}
I don't see any keyboard ISR code, and that could well be where your problem is. I'm guessing your are using the PC/AT style interface to interact with the KB controller (ports 60, 61, 64 if I remember correctly). There are one or more OUTs to one (or more) of these ports before they'll generate another interrupt - if memory serves.
In general any hardware device will require attention after it generates an interrupt. The PIC generating a time tick interrupt is an exception.
In the way of advice, I have a few suggestions:
Inspect the compiler code that you think is incorrectly trashing the AX register and/or the register_t structure (I'm not entirely sure from the OP which you are saying is being trashed). Identify exactly how you think the compiler is incorrect. I suspect the compiler is not incorrect, but rather that it is just tricky to call a C function from an ISR. In doing that in the past I had to go through a number of iterations to get it right.
Come up with a debugging scheme where you can view recent events.
a. One method is to use the fixed locations in the VGA screen memory (I'm assuming you are working in text mode at this point) where you can write single characters that indicate what your program is doing. A few characters at the end of the top line in particular, let's say 4. The 4 characters represent the 4 most recent events. When an event (an interrupt or a call from a user mode program to the kernel as examples) occurs you move the 3 most recent chars to the left (or right) and then put the newest char in the "most recent" slot. When you're program is working normally you'll see those locations on the screen as kind of a blur as they rapidly get updated due to various events. However if your program hangs the characters will stop changing and you'll see the 4 most recent events, even if you can't break in with a debugger.
b. Create a circular in memory trace log. Each entry in the log represents an event but can include details about the event like register values, or program state. If you break in with a debugger you can view the log as either bytes, words, dwords, and decode the events in your head. Or if you have a debugger that allows custom extensions, then write an extensions that supports displaying and querying the log for events of interest.
FWIW, I implemented a V86 monitor (old Intel terminology for virtualized real mode hypervisor) and so these suggestions come from experience. These suggestions apply to dev testing your kernel on both bare metal and to a somewhat lesser extent using a VM as your dev test platform.
With a VM and the right hypervisor, you will have access to a debugger specifically designed to debug a VM. Virtual Box, for example, has such a debugger. Windbg can be used on Hyper-V, although it was very slow when I tried it last. However someone wrote an extension that made kernel debugging of Hyper-V VMs much quicker.

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.)

IDT done in C is not working

I am not being able to get IDT working because my Interrupt routines are not getting called, especially the keyboard related one when i press a key on keyboard. I am passing the IDT table's special 48 bit pointer's address. I am doing it like
dt_ptr idt_ptr; // defining the pointer
loadidt(dt_ptr *a); // how i am passing the pointer's address to assembly routine
I also don't know whether at least GDT is working or not.
1) what shall i do to get my IDT working. I saw few tutorials too but didn't help
2) How can i verify whether GDT works properly?
Thanks in advance.
EDIT: I am doing this for my own OS. I am doubtful whether my assembly routine is able to properly receive the pointer's address. So i also tried to do lidt using inline assembly but didn't help. I don't know what is wrong. Any clues, idea?
void remap_irqs(void)
{
outb(0x20, 0x11);
outb(0xA0, 0x11);
outb(0x21, 0x20);
outb(0xA1, 0x28);
outb(0x21, 0x04);
outb(0xA1, 0x02);
outb(0x21, 0x01);
outb(0xA1, 0x01);
outb(0x21, 0x0);
outb(0xA1, 0x0);
outbyte('a');
}
void set_idt_gate(uint8 num, unsigned long base, word sel, uint8 flags)
{
IDT[num].offset_low = (base & 0xFFFF);
IDT[num].offset_high = (base >> 16) & 0xFFFF;
IDT[num].selector = sel;
IDT[num].zero = 0;
IDT[num].type_attrs = flags;
}
void init_idt()
{
outbyte('M');
idt_ptr.limit = (sizeof (idt_entry) * 256) - 1;
idt_ptr.base =(uint32) &IDT;
memset((uint8 *)&IDT, 0, sizeof(idt_entry) * 256);
remap_irqs();
set_idt_gate(0, (unsigned) irq_0, 0x08, 0x8E);
set_idt_gate(1, (unsigned) irq_1, 0x08, 0x8E);
//install_isrs();
//install_irqs();
//idt_load();
//print_message();
lidt(&IDT,idt_ptr.limit);
_LIDT(&idt_ptr);
loadidt(&idt_ptr);
}
void lidt( void * base, unsigned short size )
{
struct
{
unsigned short length;
unsigned long base;
} __attribute__((__packed__)) IDTR;
IDTR.length = size;
IDTR.base = (unsigned long)base;
asm( "lidt (%0)"
: : "p"(&IDTR) );
}
void _LIDT(dt_ptr *ptr)
{
asm("lidt (%0)" : :"p"(ptr));
outbyte('n');
}
void irq_0()
{
//before_interrupt();
ticks+=1;
if(ticks%18==0)
{
outbyte('+');
outbyte('1');
}
//after_interrupt();
}
void irq_1()
{
outbyte('a');
//before_interrupt();
irq1_keyb();
//after_interrupt();
}
typedef struct {
uint16 offset_low; // The lower 16 bits of the address to jump to when this
interrupt occures:// offset 0-15
uint16 selector; // Kernel segment selector in IDT
uint8 zero; // This must always be zero.
uint8 type_attrs; //gate types, atttribute types etc.
uint16 offset_high; // The upper 16 bits of the address to jump to. offset 16-31
} __attribute__((__packed__)) idt_entry;
typedef struct {
uint16 limit;
uint32 base; // The address of the first element in IDT array.
} __attribute__((__packed__)) dt_ptr;
global loadidt
loadidt:
push ebp
mov ebp,esp
mov eax,[ebp+8]
lidt [eax]
pop ebp
ret
You set IRQs vector offset to 0x20. That means IRQ0 is mapped to interrupt 0x20, IRQ1 to interrupt 0x21, etc. However, you set your irq handlers to be executed when interrupts 0x00 and 0x01 occur. That's how you should set IDR:
set_idt_gate(0x20, (unsigned) irq_0, 0x08, 0x8E);
set_idt_gate(0x21, (unsigned) irq_1, 0x08, 0x8E);
You can find more information regarding PIC here (section Initialization explains exactly what you are doing in remap_irqs function.

Resources