Skip to content
Snippets Groups Projects
Commit 822ded76 authored by Gilles Grimaud's avatar Gilles Grimaud
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
# Contributors
- Quentin "MrXedac" Bergougnoux
- Gilles Grimaud
Some sources of inspirations:
the OSDev Wiki & Forums:
- http://wiki.osdev.org/Main_Page
- http://forum.osdev.org
James Molloy's Kernel Dev "Tutorial" as well as from Bran's one:
- http://www.jamesmolloy.co.uk/tutorial_html/
- http://www.osdever.net/bkerndev/Docs/intro.htm
This diff is collapsed.
Makefile 0 → 100644
IMAGE=mykernel.iso
BINFILE=mykernel.bin
SRC_DIR=src
INC_DIR=include
# Toolchain
CC=gcc #x86_64-pc-linux-gnu-gcc
AS=nasm
LD=ld #x86_64-elf-ld
# Sources
CSOURCES=$(wildcard $(SRC_DIR)/*.c)
HSOURCES=$(wildcard $(INC_DIR)/*.h)
ASOURCES=$(sort $(wildcard $(SRC_DIR)/*.s))
# Objects
COBJ=$(CSOURCES:.c=.o)
AOBJ=$(ASOURCES:.s=.o)
# Toolchain flags
CFLAGS=-m32 -Wall -Werror -nostdlib -fno-builtin -fno-stack-protector -std=gnu99 -ffreestanding -c -g -Wno-unused-variable -fPIC
LDFLAGS=-melf_i386 -z max-page-size=0x1000
ASFLAGS=-felf
# Include directories
CFLAGS+=-I$(INC_DIR)
.PHONY: $(IMAGE) run
all: $(IMAGE)
@echo "Done !"
$(IMAGE): $(BINFILE)
cp $(BINFILE) boot/mykernel.bin
grub-mkrescue --target=i386 -d /usr/lib/grub/i386-pc/ -o $(IMAGE) ./
run:
qemu-system-x86_64 -boot d -m 2048 -cdrom mykernel.iso -serial stdio
$(BINFILE): $(AOBJ) $(COBJ)
$(LD) $(LDFLAGS) -Tlink.ld $^ -o $@
%.o: %.c $(HSOURCES)
$(CC) $(CFLAGS) $< -o $@
%.o: %.s
$(AS) $(ASFLAGS) $< -o $@
clean:
rm -f $(COBJ) $(AOBJ) $(BINFILE) boot/$(BINFILE) $(IMAGE)
README.md 0 → 100644
first kernel
------------
The first kernel project is a skeleton for low level direct programming of x86 hardware.
It builds a simple bootable .iso file using grub and running your main.c file.
## files description
Here is the initial file structure of the project :
```
.
├── boot The directory structure used by grub-mkrescue to build a bootable .iso
│ └── grub
│ ├── grub.cfg The grub configuration file
│ └── unicode.pf2 The unicode font used by grub
├── CONTRIBUTORS.md The contributors list of this skeleton
├── include The directory used to store .h file
├── ioport.h C wrapper to in and out intel assembly instructions
├── gdt.h Global Descriptor Table interface providing a default setup
├── idt.h Interrupt Descriptor Table configuration and interface
├── LICENSE The GNU General Public Licence used by this project
├── link.ld The link script used by gcc to build the project
├── Makefile The makefile building mykernel.iso from the source
├── README.md This file (using markdown)
└── src The directory used to store the assembly and C sources
├── 0boot.s The actual entrypoint called by multiboot
├── idt0.s Interrupt Descriptor Table assembly routines
├── idt.c Interrupt Descriptor Table configuration and interface
├── gdt.c Global Descriptor Table configuration and interface
└── main.c The main.c file where the main() is implemented.
```
## get started
The source of your kernel is splitted in five files :
1. 0boot.s
2. main.c
4. gdt.c
5. idt0.s
6. idt.c
0boot.s is the real entry point of your kernel. It just stops hardware interrupt, sets a call-stack and
jumps to the C main with the multiboot structure address as argument of main.
IDT and GDT-related files should be working out of the box, and configure the CPU on boot. You can play
with the IDT configuration to handle various interrupts.
main.c is the C code that will run : your kernel ! You can change it in order to print "Hello My World".
$ vi src/main.c
In order to build you first kernel, just run the Makefile :
$ make
"make" will assembly the assembly code (using nasm) and compile the C code (using gcc).
It will use a specific link script "./link.ld" to build the binary image (using ld).
The binary image (namely mykernel.bin) will be copied in ./grub/ in order to build the .iso file
(using grub-mkrescue).
If nothing goes wrong you will get a "mykernel.iso" file in "./" of the project.
You can burn it to run your software. In this case your code will be run on a bare metal environment.
This is the goal of the project. Nevertheless, developping software on bare metal environment is boring.
You don't have your usual "operating system facility" to run and test your software.
Most of the time, a bug will conclude by freezing or rebooting the machine. So we invite you to debug your
kernel on an emulator, namely qemu.
In order to run your .iso file on qemu just try :
$ make run
qemu will emulate an x86 hardware environment running your iso file. That's it !
You could also clean your project using :
$ make clean
## Build the kernel on OSX
First, you need to set up yours. Using [MacPorts](https://www.macports.org/install.php) you will be able to install needed tools.
$ port install i386-elf-binutils nasm
After that, test if and are installed, for example with the following command:
$ which i386-elf-gcc nasm
/opt/local/bin/i386-elf-gcc
/opt/local/bin/nasm
Now, you need to install GRUB2. Source: [os-dev](http://wiki.osdev.org/GRUB_2#Installing_GRUB_2_on_OS_X)
1. Clone the developer version of the sources:
```
$ git clone git://git.savannah.gnu.org/grub.git
```
2. A tool named [objconv](https://github.com/vertis/objconv) is required, get it from:
```
$ git clone https://github.com/vertis/objconv.git
```
Download sources, compile (see website for details) and make available in your PATH.
3. Run "autogen.sh" in the GRUB sources folder
4. Create a seperate build directory, switch to it, and run GRUB's configure script (insert your target-specific tools!):
```
$ ../grub/configure --disable-werror TARGET_CC=i386-elf-gcc TARGET_OBJCOPY=i386-elf-objcopy \ TARGET_STRIP=i386-elf-strip TARGET_NM=i386-elf-nm TARGET_RANLIB=i386-elf-ranlib --target=i386-elf
```
5. Run "make" and "make install"
Now you have a working GRUB 2 that has the required files to build an image that boots on i386 platforms.
Finaly, you will need a version of xorriso above 1.2.9, using [brew](https://brew.sh/):
$ brew install xorriso
PS: If you miss some dependencies, fix it and let me know.
Now, you will need to tell, where are your installed package. In order to do that, add the prefix i386-elf to gcc and ld. Then change targeted directory by the one you will found with the next command:
$ sudo find / -type f -name modinfo.sh
In my case: /usr/local/lib/grub/i386-pc/
/!\ Don't forget to update the Makefile with the right tools /!\
set timeout=10
set default=0
function load_video {
insmod vbe
insmod vga
insmod png
}
if loadfont /boot/grub/unicode.pf2
then
load_video
insmod gfxterm
set gfxmode=640x480x32
set gfxpayload=keep
terminal_output gfxterm
fi
menuentry "my first kernel" {
multiboot2 /boot/mykernel.bin -v
boot
}
File added
File added
/**
* Global Descriptor Table structure and configuration
*/
#ifndef __GDT__
#define __GDT__
#include <stdint.h>
/**
* gdt_init_default init the global descriptor table with to provide a complete access to the
* memory. In this way segments are almost "invisibles". others gdt.h declarations are useless
* until you wanna to manage intel segmentation.
*/
void gdt_init_default();
/**
* struct gdt_entry_s is the data structure for a single gdt entry as specify by intel.
*/
struct gdt_entry_s {
unsigned int limit_low : 16;/* Lower 16bits of address limit */
unsigned int base_low : 16;/* Lower 16bits of base address */
unsigned int base_middle : 8; /* Middle 8bits of base address */
unsigned int access_ac : 1; /* Access bit will be set when cpu actually use the entry */
unsigned int access_RW : 1; /* Readable segment for code/Writable segment for data (cfEx)*/
unsigned int access_DC : 1; /* Direction bit for data or Conforming bit for code */
unsigned int access_Ex : 1; /* Executable bit. 1 means code segment, 0 means data segment */
unsigned int access_one : 1; /* must be set to 1 */
unsigned int access_Privl: 2; /* Ring level of the segment : from 0(kernel) to 3(user)*/
unsigned int access_Pr : 1; /* Present bit : set to one for a valid gdt entry */
unsigned int limit_high : 4; /* Higher 4bits of the address limit */
unsigned int flags_zero : 1; /* must be set to 0 */
unsigned int flags_L : 1; /* must be set for 64bits large gdt entry. */
unsigned int flags_Sz : 1; /* must be set for 32bits protected mod entry */
unsigned int flags_Gr : 1; /* when set, the limit is multiply by 4096 */
unsigned int base_high : 8; /* Higher bytes of base address */
} ;
/**
* GDT_NULL_SEGMENT() define a struct gdt_entry initializer declaring a NULL segment gdt_entry_s.
* i.e. All flags init to zero.
*/
#define GDT_NULL_SEGMENT() {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
/**
* GDT_CODE_SEGMENT() define a struct gdt_entry initializer declaring a code segment gdt_entry_s.
* parameters description are described in the dynamic counterpart 'void gdt_set_codesegment_entry(...)'.
*/
#define GDT_CODE_SEGMENT(base,limit,limit_gr,accessible, conforming, ring_level) {(limit & 0xFFFF),(base & 0xFFFF),(base>>16)&0xFF,0,accessible,conforming,1,1,ring_level,1,((limit >> 16) & 0x0F),0,0,1,limit_gr,(base >> 24) & 0xFF }
/**
* GDT_DATA_SEGMENT() define a struct gdt_entry initializer declaring a data segment gdt_entry_s.
* parameters description are described in the dynamic counterpart 'void gdt_set_datasegment_entry(...)'.
*/
#define GDT_DATA_SEGMENT(base, limit, limit_gr, accessible, direction, ring_level) {(limit & 0xFFFF),(base & 0xFFFF),(base >> 16) & 0xFF,0,accessible,direction,0,1,ring_level,1,((limit >> 16) & 0x0F),0,0,1,limit_gr,(base >> 24) & 0xFF }
/**
* here after the gdt_entries used by the default_setup.
*/
#define DEFAULT_GDT_ENTRIES 3
extern struct gdt_entry_s default_gdt[DEFAULT_GDT_ENTRIES];
/**
* gdt_activate setup the cpu gdt register and associated segments register
* gdt_base : the address of the global descriptor table (part of intel register gdt)
* gdt_limit : the size, in bytes, of the global descriptor table - 1 (part of intel register gdt)
* cs : offset of the code segment to use in this table (intel register cs)
* ds : offset of the 1st data segment to use in this table (intel register ds)
* es : offset of the 2nd data segment to use in this table (intel register es)
* fs : offset of the 3rd data segment to use in this table (intel register fs)
* gs : offset of the 4th data segment to use in this table (intel register gs)
* ss : offset of the stack segment to use in this table (intel register ss)
*/
void gdt_activate(struct gdt_entry_s *gdt_base, uint16_t gdt_limit, uint16_t cs, uint16_t ds, uint16_t es, uint16_t fs, uint16_t gs, uint16_t ss);
/**
* gdt_set_codesegment_entry is used to setup an entry of the gdt.
* - entry : the entry address ;
* - base : the base address for the segment ;
* - limit : the size of the segment ;
* - limit_granularity : the metric of 'limit'
* 0 means limit is give in bytes,
* 1 means limit is give in page of 4096 bytes ;
* - accessible : for code segment, if accessible, reading is allowed,
* for data segment, if accessible, writing is allowed (reading allways allowed)
* - conforming :
* 0 means the segment can only be call/jump form a segment of the same ring level
* 1 means the segment can be call/jump from a segment of higher ring level
* - ring_level : the ring_level give to the segment ;
*/
void gdt_set_codesegment_entry(struct gdt_entry_s *entry, uint32_t base, uint32_t limit, int limit_granularity, int accessible, int conforming, int ring_level );
/**
* gdt_set_datasegment_entry is used to setup an entry of the gdt.
* - entry : the entry address ;
* - base : the base address for the segment ;
* - limit : the size of the segment ;
* - limit_granularity : the metric of 'limit'
* 0 means limit is give in bytes,
* 1 means limit is give in page of 4096 bytes ;
* - accessible : if accessible(==1), writing is allowed (reading allways allowed)
* - direction :
* 0 means segment is growing (limit is after base address)
* 1 means segment is decreasing (limit is before base address, like a stack)
* - ring_level : the ring_level give to the segment ;
*/
void gdt_set_datasegment_entry(struct gdt_entry_s *entry, uint32_t base, uint32_t limit, int limit_granularity, int accessible, int direction, int ring_level );
/**
* gdt_set_null_entry is used to setup an entry of the gdt.
* - entry : the entry address ;
*/
void gdt_set_null_entry(struct gdt_entry_s *entry);
#endif
/**
* Interrupt Descriptor Table structure and configuration
*/
#ifndef __IDT__
#define __IDT__
#include <stdint.h>
struct idt_entry_struct
{
uint16_t base_lo;
uint16_t sel;
uint8_t always0;
uint8_t flags;
uint16_t base_hi;
} __attribute__((packed));
struct idt_ptr_struct
{
uint16_t limit; //!< Address limit
uint32_t base; //!< IDT pointer base
} __attribute__((packed));
typedef const struct int_regs
{
uint32_t ds;
uint32_t edi;
uint32_t esi;
uint32_t ebp;
uint32_t esp;
uint32_t ebx;
uint32_t edx;
uint32_t ecx;
uint32_t eax;
uint32_t int_no;
uint32_t err_code;
uint32_t eip;
uint32_t cs;
uint32_t eflags;
uint32_t useresp;
uint32_t ss;
} int_regs_t ;
void setup_idt();
void idt_setup_descriptor(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags);
void idt_setup_handler(int irq, void (*handler)(int_regs_t *r));
extern void zero_idt(struct idt_entry_struct *entries);
#endif
#ifndef _IOPORT_H_
#define _IOPORT_H_
/* read a byte from the ioport 'port' */
static inline unsigned char _inb(unsigned short port) {
int ret;
asm("inb %1,%0": "=a" (ret) : "Nd" (port) :);
/***
* N.B. in inb %0,%1 :
* %0 must be the register al => "=a"
* %1 must be a 8bits imediate value (N) or the register dx (d) =>"Nd"
* %1 constraint also limit the var. port to a 16bits value => "[unsigned] short".
*/
return ret;
}
/* write a byte value in the ioport 'port' */
static inline void _outb(unsigned short port, unsigned char value) {
asm("outb %0,%1":: "a" (value), "Nd" (port) :);
/***
* N.B. in outb %0,%1 :
* %0 must be the register al => "a"
* %1 must be a 8bits imediate value (N) or the register dx =>"Nd"
* %1 constraint also limit the var. port to a 16bits value => "[unsigned] short".
*/
}
#endif
link.ld 0 → 100644
OUTPUT_FORMAT("elf32-i386")
ENTRY(start)
phys = 0x00100000;
SECTIONS
{
.text phys : AT(phys)
{
code = .; _code = .; __code = .;
*(.text)
. = ALIGN(4096);
}
.data :
{
data = .; _data = .; __data = .;
*(.data)
*(.rodata)
. = ALIGN(4096);
}
.bss :
{
bss = .; _bss = .; __bss = .;
*(.bss)
. = ALIGN(4096);
}
.stack :
{
stack = .; _stack = .; __stack = .;
*(.stack)
. = ALIGN(4096);
}
.kernel 0x300000 :
{
kernel = .; _kernel = .; __kernel = .;
*(.kernel)
. = ALIGN(4096);
}
end = .; _end = .; __end = .;
}
File added
[BITS 32]
[GLOBAL start]
[GLOBAL mboot]
[EXTERN main]
ALIGN 4
SECTION .text
; Multiboot2 header
mboot:
MBOOT_HEADER_MAGIC equ 0xe85250d6
MBOOT_ARCH_I386 equ 0x0
MBOOT_SIZE equ multiboot_header_end - mboot
MBOOT_CHECKSUM equ -(MBOOT_HEADER_MAGIC + MBOOT_ARCH_I386 + multiboot_header_end - mboot)
EXTERN code, bss, end
dd MBOOT_HEADER_MAGIC
dd MBOOT_ARCH_I386
dd MBOOT_SIZE
dd MBOOT_CHECKSUM
dd mboot
dd code
dd bss
dd end
dd start
multiboot_header_end:
; x86 bootstrap code
start:
cli ; Clear interrupts
mov esp, _earlystack ; Setup early-boot stack
push ebx ; Push multiboot2 info to main()
call main ; Call main()
jmp $ ; We shouldn't return from main. If so, loop forever.
SECTION .stack
times 8192 db 0 ; Allocate a 8kb early-boot stack. This should be more than enough.
_earlystack:
src/gdt.c 0 → 100644
#include "gdt.h"
struct gdt_entry_s default_gdt[DEFAULT_GDT_ENTRIES] = {
GDT_NULL_SEGMENT(), /* 0x00 is not present */
GDT_CODE_SEGMENT(0,0xFFFFF,1,1,0,0), /* 0x08 is code seg: base 0, limit 4Gb, ring lvl 0 */
GDT_DATA_SEGMENT(0,0xFFFFF,1,1,0,0) /* 0x10 is data seg: base 0, limit 4Gb, ring lvl 0 */
};
/**
* struct gdt_register is the 48 bits data structure of the intel Gobal Descriptor Table register.
*/
struct gdt_register
{
uint16_t limit : 16; /* 16 bits for the size of the Global Descriptor Table (-1) */
uint32_t base : 32; /* 32 bits for the address of the Global Descriptor Table */
} __attribute__((packed)); /* this gcc directive avoid fields padding, base will not be aligned */
/**
* gdt_activate setup the cpu gdt register and associated segments register
* gdt_base : the address of the global descriptor table (part of intel register gdt)
* gdt_limit : the size, in bytes, of the global descriptor table - 1 (part of intel register gdt)
* cs : offset of the code segment to use in this table (intel register cs)
* ds : offset of the 1st data segment to use in this table (intel register ds)
* es : offset of the 2nd data segment to use in this table (intel register es)
* fs : offset of the 3rd data segment to use in this table (intel register fs)
* gs : offset of the 4th data segment to use in this table (intel register gs)
* ss : offset of the stack segment to use in this table (intel register ss)
*/
void gdt_activate(struct gdt_entry_s *gdt_base, uint16_t gdt_limit, uint16_t cs, uint16_t ds, uint16_t es, uint16_t fs, uint16_t gs, uint16_t ss) {
struct gdt_register gr;
gr.base = (uint32_t)gdt_base;
gr.limit = gdt_limit;
/* set the gdt for the 48bits ptr */
asm( "lgdt %0" : :"m" (gr) :);
/* set the data segment registers (ds,es,fs,gs & ss) */
asm( "movw %0,%%ds" : :"r" (ds) :);
asm( "movw %0,%%es" : :"r" (es) :);
asm( "movw %0,%%fs" : :"r" (fs) :);
asm( "movw %0,%%gs" : :"r" (gs) :);
asm( "movw %0,%%ss" : :"r" (ss) :);
/* set the code segment register (cs) using a fake retf */
asm( "push %0 \n"
"push $cs_set \n"
"retf \n" /* far jump to cs_set with %0 as cs */
"cs_set: \n"
::"r" (cs):);
}
/**
* gdt_set_codesegment_entry is used to set up an entry of the gdt used as a codesegment.
* - entry : the entry address ;
* - base : the base address for the segment ;
* - limit : the size of the segment ;
* - limit_granularity : the metric of 'limit'
* 0 means limit is give in bytes,
* 1 means limit is give in page of 4096 bytes ;
* - accessible : for code segment, if accessible, reading is allowed,
* for data segment, if accessible, writing is allowed (reading allways allowed)
* - conforming :
* 0 means the segment can only be call/jump form a segment of the same ring level
* 1 means the segment can be call/jump from a segment of higher ring level
* - ring_level : the ring_level give to the segment ;
*/
void gdt_set_codesegment_entry(struct gdt_entry_s *entry, uint32_t base, uint32_t limit, int limit_granularity, int accessible, int conforming, int ring_level ) {
entry->base_low = (base & 0xFFFF);
entry->base_middle = (base >> 16) & 0xFF;
entry->base_high = (base >> 24) & 0xFF;
entry->limit_low = (limit & 0xFFFF);
entry->limit_high = ((limit >> 16) & 0x0F);
entry->flags_zero = 0;
entry->flags_L = 0; /* here we assume 32bits protected mod and not 64 */
entry->flags_Sz = 1; /* here we assume 32bits protected mod and not 16 */
entry->flags_Gr = limit_granularity;
entry->access_ac = 0;
entry->access_RW = accessible;
entry->access_DC = conforming;
entry->access_Ex = 1; /* this is a code segment entry */
entry->access_one = 1;
entry->access_Privl= ring_level;
entry->access_Pr = 1;
}
/**
* gdt_set_datasegment_entry is used to set up an entry of the gdt used as datasegment.
* - entry : the entry address ;
* - base : the base address for the segment ;
* - limit : the size of the segment ;
* - limit_granularity : the metric of 'limit'
* 0 means limit is give in bytes,
* 1 means limit is give in page of 4096 bytes ;
* - accessible : if accessible(==1), writing is allowed (reading allways allowed)
* - direction :
* 0 means segment is growing (limit is after base address)
* 1 means segment is decreasing (limit is before base address, like a stack)
* - ring_level : the ring_level give to the segment ;
*/
void gdt_set_datasegment_entry(struct gdt_entry_s *entry, uint32_t base, uint32_t limit, int limit_granularity, int accessible, int direction, int ring_level ) {
entry->base_low = (base & 0xFFFF);
entry->base_middle = (base >> 16) & 0xFF;
entry->base_high = (base >> 24) & 0xFF;
entry->limit_low = (limit & 0xFFFF);
entry->limit_high = ((limit >> 16) & 0x0F);
entry->flags_zero = 0;
entry->flags_L = 0; /* this is not a 64bits large entry */
entry->flags_Sz = 1; /* this is not a 16bits entry, but a 32 bits protected mod entry */
entry->flags_Gr = limit_granularity;
entry->access_ac = 0;
entry->access_RW = accessible;
entry->access_DC = direction;
entry->access_Ex = 0; /* this is a data segment entry */
entry->access_one = 1;
entry->access_Privl= ring_level;
entry->access_Pr = 1;
}
/**
* gdt_set_null_entry is used to set up an entry of the gdt unused (nul).
* - entry : the entry address ;
*/
void gdt_set_null_entry(struct gdt_entry_s *entry) {
entry->base_low = 0;
entry->base_middle = 0;
entry->base_high = 0;
entry->limit_low = 0;
entry->limit_high = 0;
entry->flags_zero = 0;
entry->flags_L = 0;
entry->flags_Sz = 0;
entry->flags_Gr = 0;
entry->access_ac = 0;
entry->access_RW = 0;
entry->access_DC = 0;
entry->access_Ex = 0;
entry->access_one = 0;
entry->access_Privl= 0;
entry->access_Pr = 0;
}
/**
* init a default global descriptor table.
*/
void gdt_init_default()
{
int gdt_size = (DEFAULT_GDT_ENTRIES*sizeof(struct gdt_entry_s))-1;
gdt_activate(default_gdt,gdt_size,0x08,0x10,0x10,0x10,0x10,0x10);
}
src/gdt.o 0 → 100644
File added
src/idt.c 0 → 100644
#include "idt.h"
#include "ioport.h"
#include <stdint.h>
#define MAX_IDT_ENTRIES 256
/* Use this to define a hardware interrupt entry into the IDT */
#define IRQ_IDT(X) { extern void irq ## X(); idt_setup_descriptor (32+X, (uint32_t) irq ## X, 0x08, 0x8E); }
/* Use this to define a kernel-only software interrupt into the IDT */
#define KERN_IDT(X) { extern void isr ## X(); idt_setup_descriptor(X, (uint32_t) isr ## X, 0x08, 0x8E); }
/* Use this to define a software interrupt triggerable by anyone into the IDT */
#define USER_IDT(X) { extern void isr ## X(); idt_setup_descriptor (X, (uint32_t) isr ## X, 0x08, 0xEE); }
struct idt_entry_struct idt_entries[MAX_IDT_ENTRIES];
/**
* struct idt_register is the 48 bits data structure of the intel Gobal Descriptor Table register.
*/
struct idt_register {
uint16_t limit :16; /* 16 bits for the size of the Interrupt Descriptor Table (-1) */
uint32_t base : 32; /* 32 bits for the address of the Interrupt Descriptor Table */
} __attribute__((packed)); /* this gcc directive avoid fields padding, base will not be aligned */
void _lidt(struct idt_register *idt_register) {
asm("lidt (%0)" :: "r"(idt_register) :);
}
void *irq_handlers[16] =
{
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
void idt_setup_descriptor(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags)
{
idt_entries[num].base_lo = base & 0xFFFF;
idt_entries[num].base_hi = (base >> 16) & 0xFFFF;
idt_entries[num].sel = sel;
idt_entries[num].always0 = 0;
idt_entries[num].flags = flags;
}
void idt_setup_handler(int irq, void (*handler)(int_regs_t *r))
{
irq_handlers[irq] = handler;
}
void null_handler(int_regs_t *r)
{
return;
}
void remap_irq()
{
_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);
}
void setup_irq()
{
IRQ_IDT(0);
IRQ_IDT(1);
IRQ_IDT(2);
IRQ_IDT(3);
IRQ_IDT(4);
IRQ_IDT(5);
IRQ_IDT(6);
IRQ_IDT(7);
IRQ_IDT(8);
IRQ_IDT(9);
IRQ_IDT(10);
IRQ_IDT(11);
IRQ_IDT(12);
IRQ_IDT(13);
IRQ_IDT(14);
IRQ_IDT(15);
uint8_t irq_num;
for(irq_num = 0; irq_num < 16; irq_num++)
idt_setup_handler(irq_num, null_handler);
remap_irq();
}
void setup_softint()
{
KERN_IDT(0);
KERN_IDT(1);
KERN_IDT(2);
KERN_IDT(3);
KERN_IDT(4);
KERN_IDT(5);
KERN_IDT(6);
KERN_IDT(7);
KERN_IDT(8);
KERN_IDT(9);
KERN_IDT(10);
KERN_IDT(11);
KERN_IDT(12);
KERN_IDT(13);
KERN_IDT(14);
KERN_IDT(15);
KERN_IDT(16);
KERN_IDT(17);
KERN_IDT(18);
KERN_IDT(19);
KERN_IDT(20);
KERN_IDT(21);
KERN_IDT(22);
KERN_IDT(23);
KERN_IDT(24);
KERN_IDT(25);
KERN_IDT(26);
KERN_IDT(27);
KERN_IDT(28);
KERN_IDT(29);
KERN_IDT(30);
KERN_IDT(31);
/* In idt0.s we defined a catchable interrupt 87, and want it to be triggerable from userland */
USER_IDT(87);
}
void setup_idt()
{
struct idt_register idt_reg;
idt_reg.limit = sizeof(struct idt_entry_struct) * MAX_IDT_ENTRIES - 1;
idt_reg.base = (uint32_t)&idt_entries;
setup_irq();
setup_softint();
_lidt(&idt_reg);
}
void irq_handler(int_regs_t* r)
{
void (*handler)(int_regs_t* r);
handler = irq_handlers[r->int_no - 32];
if(handler)
handler(r);
if(r->int_no >= 40){
_outb(0xA0, 0x20);
}
_outb(0x20, 0x20);
}
char *int_mesg[] = {
"Division By Zero",
"Debug",
"Non Maskable Interrupt",
"Breakpoint",
"Into Detected Overflow",
"Out of Bounds",
"Invalid Opcode",
"No Coprocessor",
"Double Fault",
"Coprocessor Segment Overrun",
"Bad TSS",
"Segment Not Present",
"Stack Fault",
"General Protection Fault",
"Page Fault",
"Unknown Interrupt",
"Coprocessor Fault",
"Alignment Check",
"Machine Check",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved"
};
void isr_handler(int_regs_t* r)
{
extern void puts(char* str);
extern void puthex(int d);
if(r->int_no < 32)
{
puts("Caught fault, halting. ");
puts(int_mesg[r->int_no]);
for(;;);
}
else
{
puts("I caught fault 0x");
puthex(r->int_no);
puts(", resuming execution\n");
// Process your software interrupts here...
}
}
src/idt.o 0 → 100644
File added
File added
%macro ISR_NOERRCODE 1
global isr%1
isr%1:
cli
push byte 0
push byte %1
jmp process_isr
%endmacro
%macro ISR_ERRCODE 1
global isr%1
isr%1:
cli
push byte %1
jmp process_isr
%endmacro
%macro IRQ 2
global irq%1
irq%1:
cli
push byte 0
push byte %2
jmp process_irq
%endmacro
%macro DEFINE_HANDLER 1
extern %1_handler
process_%1:
pusha
push esp
mov si, ds
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov eax, esp
push eax
call %1_handler
pop eax
add esp, 4
mov ds, si
mov es, si
mov fs, si
mov gs, si
popa
add esp, 8
iret
%endmacro
DEFINE_HANDLER isr
DEFINE_HANDLER irq
global zero_idt
zero_idt:
mov edi, [esp + 4] ; Set begin to IDT pointer
mov eax, 0x00000000 ; Zero this
mov ecx, 0x200 ; 256 entries, 2 words per entry : 512
rep stosd ; So we would write 512 null words - this should zero our IDT
ret
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_ERRCODE 8
ISR_NOERRCODE 9
ISR_ERRCODE 10
ISR_ERRCODE 11
ISR_ERRCODE 12
ISR_ERRCODE 13
ISR_ERRCODE 14
ISR_NOERRCODE 15
ISR_NOERRCODE 16
ISR_NOERRCODE 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_NOERRCODE 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
; We only handle the processor's and PIC's interrupts right now.
; If you want to handle other software interrupts, feel free to add something like
; ISR_NOERRCODE XX
; XX being the interrupt number.
; Here is an example for the 87 interrupt :
ISR_NOERRCODE 87
#include "ioport.h"
#include "gdt.h"
#include "idt.h"
void clear_screen(); /* clear screen */
void putc(char aChar); /* print a single char on screen */
void puts(char *aString); /* print a string on the screen */
void puthex(int aNumber); /* print an Hex number on screen */
void timer_int(int_regs_t *r)
{
puts(".");
}
void kbd_int(int_regs_t *r)
{
int c=_inb(0x60);
puts("!");
puthex(c);
puts("!");
}
/* multiboot entry-point with datastructure as arg. */
void main(unsigned int * mboot_info)
{
/* clear the screen */
clear_screen();
puts("Early boot.\n");
puts("\t-> Setting up the GDT... ");
gdt_init_default();
puts("done\n");
puts("\t-> Setting up the IDT... ");
setup_idt();
puts("OK\n");
puts("\n\n");
idt_setup_handler(0, timer_int);
idt_setup_handler(1,kbd_int);
__asm volatile("sti");
// init keyboard
while(_inb(0x64)&0x1) {
_inb(0x60);
}
while(_inb(0x64) & 0x2) {
_outb(0x60,0xF4);
}
/* print something */
puts("Hello World!\n\n");
puts("Multiboot Info at ");
puthex((unsigned int)mboot_info);
puts("\n");
for(;;) ;
}
/* base address for the video output assume to be set as character oriented by the multiboot */
unsigned char *video_memory = (unsigned char *) 0xB8000;
/* clear screen */
void clear_screen() {
int i;
for(i=0;i<80*25;i++) { /* for each one of the 80 char by 25 lines */
video_memory[i*2+1]=0x0F; /* color is set to black background and white char */
video_memory[i*2]=(unsigned char)' '; /* character shown is the space char */
}
}
/* print a string on the screen */
void puts(char *aString) {
char *current_char=aString;
while(*current_char!=0) {
putc(*current_char++);
}
}
/* print an number in hexa */
char *hex_digit="0123456789ABCDEF";
void puthex(int aNumber) {
int i;
int started=0;
for(i=28;i>=0;i-=4) {
int k=(aNumber>>i)&0xF;
if(k!=0 || started) {
putc(hex_digit[k]);
started=1;
}
}
}
/* print a char on the screen */
int cursor_x=0; /* here is the cursor position on X [0..79] */
int cursor_y=0; /* here is the cursor position on Y [0..24] */
void setCursor() {
int cursor_offset = cursor_x+cursor_y*80;
_outb(0x3d4,14);
_outb(0x3d5,((cursor_offset>>8)&0xFF));
_outb(0x3d4,15);
_outb(0x3d5,(cursor_offset&0xFF));
}
void putc(char c) {
if(cursor_x>79) {
cursor_x=0;
cursor_y++;
}
if(cursor_y>24) {
cursor_y=0;
clear_screen();
}
switch(c) { /* deal with a special char */
case '\r': cursor_x=0; break; /* carriage return */
case '\n': cursor_x=0; cursor_y++; break; /* new ligne */
case 0x8 : if(cursor_x>0) cursor_x--; break;/* backspace */
case 0x9 : cursor_x=(cursor_x+8)&~7; break; /* tabulation */
/* or print a simple character */
default :
video_memory[(cursor_x+80*cursor_y)*2]=c;
cursor_x++;
break;
}
setCursor();
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment