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

first draft

parent 8349ea64
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
LICENSE 0 → 100644
This diff is collapsed.
Makefile 0 → 100644
IMAGE=mykernel.iso
BINFILE=iso/boot/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 -g
LDFLAGS=-melf_i386 -z max-page-size=0x1000 -g
ASFLAGS=-felf
# Include directories
CFLAGS+=-I$(INC_DIR)
all: $(IMAGE)
@echo "Done !"
$(IMAGE): $(BINFILE)
grub-mkrescue -d /usr/lib/grub/i386-pc/ -o $(IMAGE) iso
run:
qemu-system-x86_64 -boot d -m 2048 -cdrom mykernel.iso
run_curses:
qemu-system-x86_64 -boot d -m 2048 -cdrom mykernel.iso -curses
debug:
qemu-system-i386 -s -S -boot d -m 2048 -cdrom mykernel.iso -d cpu_reset -monitor stdio
debug_curses:
qemu-system-i386 -s -S -boot d -m 2048 -cdrom mykernel.iso -curses -d cpu_reset -monitor stdio
attach:
gdb -ex "set arch i386" -ex "file $(BINFILE)" -ex "target remote localhost:1234"
$(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) $(IMAGE)
.PHONY: run run_curses
# my kernel 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
## Getting started Here is the initial file structure of the project :
To make it easy for you to get started with GitLab, here's a list of recommended next steps. ```
.
├── 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.
```
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! ## get started
## Add your files 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
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files 0boot.s is the real entry point of your kernel. It just stops hardware interrupt, sets a call-stack and
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: 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
cd existing_repo with the IDT configuration to handle various interrupts.
git remote add origin https://gitlab.univ-lille.fr/fil-ase2023-groupe2/my-kernel.git
git branch -M main main.c is the C code that will run : your kernel ! You can change it in order to print "Hello My World".
git push -uf origin main
``` $ 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).
## Integrate with your tools It will use a specific link script "./link.ld" to build the binary image (using ld).
- [ ] [Set up project integrations](https://gitlab.univ-lille.fr/fil-ase2023-groupe2/my-kernel/-/settings/integrations) The binary image (namely mykernel.bin) will be copied in ./grub/ in order to build the .iso file
(using grub-mkrescue).
## Collaborate with your team 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.
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) In order to run your .iso file on qemu just try :
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy $ make run
Use the built-in continuous integration in GitLab. qemu will emulate an x86 hardware environment running your iso file. That's it !
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
*** You could also clean your project using :
# Editing this README $ make clean
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template. ## Build the kernel on OSX
## Suggestions for a good README First, you need to set up yours. Using [MacPorts](https://www.macports.org/install.php) you will be able to install needed tools.
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name $ port install i386-elf-binutils nasm
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges After that, test if and are installed, for example with the following command:
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals $ which i386-elf-gcc nasm
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. /opt/local/bin/i386-elf-gcc
/opt/local/bin/nasm
## Installation Now, you need to install GRUB2. Source: [os-dev](http://wiki.osdev.org/GRUB_2#Installing_GRUB_2_on_OS_X)
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage 1. Clone the developer version of the sources:
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support ```
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. $ 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.
## Roadmap Finaly, you will need a version of xorriso above 1.2.9, using [brew](https://brew.sh/):
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing $ brew install xorriso
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. PS: If you miss some dependencies, fix it and let me know.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. 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:
## Authors and acknowledgment $ sudo find / -type f -name modinfo.sh
Show your appreciation to those who have contributed to the project.
## License In my case: /usr/local/lib/grub/i386-pc/
For open source projects, say how it is licensed.
## Project status /!\ Don't forget to update the Makefile with the right tools /!\
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
/**
* 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 }
#define GDT_TSS(base, limit, ring_level) {(limit & 0xFFFF),(base & 0xFFFF),(base >> 16) & 0xFF,1,0,0,1,0,ring_level,1,((limit >> 16) & 0x0F),0,0,0,0,(base >> 24) & 0xFF }
/**
* here after the gdt_entries used by the default_setup.
*/
#define DEFAULT_GDT_ENTRIES 6
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_irq_handler(int irq, void (*handler)(int_regs_t *r));
void idt_setup_int_handler(int intno, void (*handler)(int_regs_t *r));
extern void zero_idt(struct idt_entry_struct *entries);
void end_of_interrupt();
#define irq_enable() asm("sti")
#define irq_disable() asm("cli")
#endif
#ifndef _IOPORT_H_
#define _IOPORT_H_
/* read a byte from the ioport 'port' */
static inline unsigned char _inb(unsigned short port) {
unsigned char 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
#ifndef __MINILIB_H__
#define __MINILIB_H__
#ifndef NULL
#define NULL ((void*)0)
#endif
extern void __assert__(int x, char *f, unsigned line);
#define assert(x) __assert__(x, __FILE__, __LINE__)
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(unsigned aNumber); /* print an Hex number on screen */
void putud(unsigned aNumber); /* print a unsigned decimal on screen */
void memset(void *, int, int);
#endif
#ifndef __MMU_H__
#define __MMU_H__
#define CR0_PG 0x80000000
#define CR0_WP 0x00010000
#define PAGE_SIZE 4096
#define PAGE_TABLE_KERNEL 0
#define PAGE_TABLE_USER 1
#define PAGE_TABLE_FREE 2
struct pde_s {
int p:1;
int rw:1;
int us:1;
int pwt: 1;
int pcd:1;
int a:1;
int unused2:1;
int ps:1;
int unused1:4;
int address:20;
} __attribute__((packed));
struct pte_s {
int p:1;
int rw:1;
int us:1;
int pwt: 1;
int pcd:1;
int a:1;
int d:1;
int pat:1;
int g:1;
int unused1:3;
int address:20;
} __attribute__((packed));
#define PT_SIZE 1024
extern void setup_mmu();
extern unsigned int get_fault_address();
extern void flush_tlb();
extern void setup_page_table(int, struct pte_s[]);
#endif
#ifndef __SYSCALLS_H__
#define __SYSCALLS_H__
#define SYSCALL_PUTC 1
#endif
#ifndef __TSS_H__
#define __TSS_H__
#include "gdt.h"
struct tss_s { /* Copypaste from OSDev wiki */
unsigned int prev_tss;
unsigned int esp0;
unsigned int ss0;
unsigned int esp1;
unsigned int ss1;
unsigned int esp2;
unsigned int ss2;
unsigned int cr3;
unsigned int eip;
unsigned int eflags;
unsigned int eax;
unsigned int ecx;
unsigned int edx;
unsigned int ebx;
unsigned int esp;
unsigned int ebp;
unsigned int esi;
unsigned int edi;
unsigned int es;
unsigned int cs;
unsigned int ss;
unsigned int ds;
unsigned int fs;
unsigned int gs;
unsigned int ldt;
unsigned short int trap;
unsigned short int iomap_base;
} __attribute__ ((packed));
extern void setup_tss(struct gdt_entry_s*);
#endif
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
link.ld 0 → 100644
OUTPUT_FORMAT("elf32-i386")
ENTRY(start)
phys = 0x00100000;
SECTIONS
{
.user 0x400000 :
{
src/user.o (.text* .data .rodata .bss)
}
.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);
}
end = .; _end = .; __end = .;
}
[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:
global user
extern user_entry
extern user_stack
user:
mov ax, 0x28
ltr ax
mov ax, 0x23
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov eax, user_stack + 65536 - 4
push 0x23
push eax
pushf
push 0x1B
push user_entry
iret
#include "minilib.h"
void app_main()
{
puts("TODO\n");
}
src/gdt.c 0 → 100644
#include "gdt.h"
#include "tss.h"
#include "minilib.h"
#define GDT_TSS_INDEX 5
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 */
GDT_CODE_SEGMENT(0,0xFFFFF,1,1,0,3), /* 0x18 is code seg: base 0, limit 4Gb, ring lvl 3 */
GDT_DATA_SEGMENT(0,0xFFFFF,1,1,0,3), /* 0x20 is data seg: base 0, limit 4Gb, ring lvl 3 */
GDT_TSS(0, sizeof(struct tss_s), 0), /* 0x28 is TSS, 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;
setup_tss(gdt_base + GDT_TSS_INDEX);
/* 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( "pushl %0 \n"
"push $cs_set \n"
"retf \n" /* far jump to cs_set with %0 as cs */
"cs_set: \n"
::"r" ((uint32_t)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/idt.c 0 → 100644
#include "idt.h"
#include "ioport.h"
#include <stdint.h>
extern void puts(char *);
#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 *int_handlers[256];
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_irq_handler(int irq, void (*handler)(int_regs_t *r))
{
irq_handlers[irq] = handler;
}
void idt_setup_int_handler(int intno, void (*handler)(int_regs_t *r))
{
int_handlers[intno] = 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_irq_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);
}
int_regs_t* last_regs = 0;
void end_of_interrupt()
{
if (last_regs->int_no >= 40) {
_outb(0xA0, 0x20);
}
_outb(0x20, 0x20);
}
void irq_handler(int_regs_t* r)
{
void (*handler)(int_regs_t* r);
last_regs = r;
handler = irq_handlers[r->int_no - 32];
if(handler)
handler(r);
end_of_interrupt();
}
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);
void (*handler)(int_regs_t* r);
handler = int_handlers[r->int_no];
if(handler) {
handler(r);
return;
}
if(r->int_no < 32)
{
puts("Caught fault, halting. ");
puts(int_mesg[r->int_no]);
for(;;);
}
}
%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
global reload_idt
reload_idt:
mov eax, [esp + 4]
lidt [eax]
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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment