~ecs/k

354b6b991e68fe3ab6bdb0ff42f3261064ba6b21 — Eyal Sawady 8 months ago master
Initial commit

Hello, world!
9 files changed, 284 insertions(+), 0 deletions(-)

A .editorconfig
A .gitignore
A BOOT.md
A Makefile
A PORTABILITY.md
A README.md
A boot.S
A boot.c
A link.ld
A  => .editorconfig +10 -0
@@ 1,10 @@
root = true

[*.{c,h}]
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
indent_style = tab
indent_size = 8
max_line_length = 80

A  => .gitignore +2 -0
@@ 1,2 @@
k
*.o

A  => BOOT.md +27 -0
@@ 1,27 @@
# Boot Process for `k`

## Stage-1 bootloader

Initialize the CPU in an architecture-dependent manner, load a temporary
page table, and read the stage-2 bootloader from the disk.

The stage-1 bootloader is written in assembly, and is not portable.

### amd64 initialization

Put the CPU in 64-bit mode

## Stage-2 bootloader

Parse the root filesystem and read the full kernel into memory.

The stage-2 bootloader is mostly written in portable C, with a few parts
written in assembly.

## Kernel

Initialize hardware, initialize userspace, load entry points, load a
better page table, and start init.

The kernel is mostly written in portable C, with a few parts written in
assembly.

A  => Makefile +18 -0
@@ 1,18 @@
AS:=nasm
CC:=cproc
LD:=ld

qemu: all
	qemu-system-x86_64 -drive file=k,format=raw

all: boot.o cboot.o
	$(LD) -T link.ld -o k boot.o cboot.o

boot.o: boot.S
	$(AS) -f elf64 -o boot.o boot.S

cboot.o: boot.c
	$(CC) -c boot.c -o cboot.o

clean:
	rm -f *.o k

A  => PORTABILITY.md +17 -0
@@ 1,17 @@
# Portability

`k` aims to be highly portable, with most of the kernel written in
arch-independent C. However, there are times when this is impossible.

When possible, there should be an abstraction layer, implemented in
assembly, upon which portable C can be written. This abstraction layer
should be as thin as possible while still being easy to implement on all
hardware.

The exception to this rule is the stage-1 bootloader, which is fully
implemented in assembly. The tasks that the stage-1 bootloader performs
are entirely architecture-dependent, and it is usually severely limited
in size.

All architecture-specific code MUST live in `arch/$(uname -m)`. Porting
`k` to a new architecture should take at most a few weeks.

A  => README.md +31 -0
@@ 1,31 @@
# `k`

Simple kernel written in C99 (with some assembly)

## Building

Run `make` to build `k` for `$(uname -m)`, and run `make ARCH` to build
`k` for ARCH.

See [PORTABILITY.md](PORTABILITY.md) for notes on portability.

Currently supported architectures: NONE

In-progress ports:

  - amd64

## Notes

`k` has its own first- and second-stage bootloaders, see
[BOOT.md](BOOT.md) for details.

`k` does not currently support dual-booting.

`k` aims to implement a de-crufted subset of POSIX.1-2017.

`k` follows sircmpwn's [C style
guide](https://git.sr.ht/~sircmpwn/cstyle/tree/master/README.md).

`k` currently uses NASM as its assembler, but will switch to a better
assembler if one is made.

A  => boot.S +147 -0
@@ 1,147 @@
; Set up page table, run kmain

%define PTE_P 0x1
%define PTE_W 0x2
%define PTE_U 0x4
%define PTE_PS 0x80
%define CONSOLE_ADDR 0xB8000
%define CR4_PSE 0x10
%define CR4_PAE 0x20
%define MSR_IA32_EFER 0xC0000080
%define IA32_EFER_LME 0x100
%define IA32_EFER_NXE 0x800
%define IA32_EFER_SCE 0x1
%define CR0_PE 0x1
%define CR0_WP 0x10000
%define CR0_PG 0x80000000
%define BOOT_CS 0x8

%define BOOT_PAGETABLE 0x1000

bits 16
section .text

global start
extern kmain

start:
	cli
	cld

	mov sp, start

	mov esi, 0

	; BIOS magic
	mov ax, 0xEC00
	mov dx, 2
	int 0x15

	; Page table magic
	mov edi, 0x1000
	mov cr3, edi
	xor eax, eax
	mov ecx, 4096
	rep stosd
	mov edi, cr3

	mov dword [edi], 0x2000 + PTE_P + PTE_W
	add edi, 0x1000
	mov dword [edi], 0x3000 + PTE_P + PTE_W
	add edi, 0x1000
	mov dword [edi], 0x4000 + PTE_P + PTE_W
	add edi, 0x1000

	mov ebx, PTE_P + PTE_W
	mov ecx, 512

.setentry:
	mov dword [edi], ebx
	add ebx, 0x1000
	add edi, 8
	loop .setentry

	; Enable PAE
	mov eax, cr4
	or eax, 1 << 5
	mov cr4, eax

	; Protected mode
	mov ecx, 0xC0000080
	rdmsr
	or eax, 1 << 8
	wrmsr

	mov eax, cr0
	or eax, 1 << 31 | 1 << 0
	mov cr0, eax

	lgdt [gdt.pointer]
	jmp gdt.code:.64bit

	bits 64
.64bit

	mov rsi, 0

	mov byte [rsi + CONSOLE_ADDR + 10], 'k'

; Write to the screen once we're done initializing, for testing purposes
	mov rax, 0
.loop:
	mov byte [rax + CONSOLE_ADDR], 'k'
	mov byte [rax + CONSOLE_ADDR + 1], 0x7
	add rax, 2
	cmp rax, (96 * 64 * 2)
	jl .loop

	call kmain

	hlt

align 16
gdt:
.null:	equ $ - gdt
	; Low limit
	dw 0xFFFF
	; Low base
	dw 0
	; Middle base
	db 0
	; Access
	db 0
	; Granularity
	db 1
	; High base
	db 0
.code:	equ $ - gdt
	; Low limit
	dw 0
	; Low base
	dw 0
	; Middle base
	db 0
	; Access (exec/read)
	db 10011010b
	; Granularity, 64-bit flag, limit19:16
	db 10101111b
	; High base
	db 0
.data:	equ $ - gdt
	; Low limit
	dw 0
	; Low base
	dw 0
	; Middle base
	db 0
	; Access (read/write)
	db 10010010b
	; Granularity
	db 00000000b
	; High base
	db 0
.pointer:
	; Limit
	dw $ - gdt - 1
	; Base
	dq gdt

A  => boot.c +17 -0
@@ 1,17 @@
void
kmain(void)
{
	const char *str = "Hello, world!";
	char *console = (char*)0xb8000;

	for (int i = 0; i < 80 * 25 * 2; i += 2) {
		console[i] = ' ';
		console[i + 1] = 0x7;
	}

	for (int i = 0; str[i]; i++) {
		console[2 * i] = str[i];
	}

	return;
}

A  => link.ld +15 -0
@@ 1,15 @@
OUTPUT_FORMAT(binary)
ENTRY(start)
SECTIONS
{
	. = 0x7c00;
	.text : { *(.text) }
	.data : { *(.data) }
	.bss  : { *(.bss)  }

	. = 0x7dfe;
	.header : {
		BYTE(0x55)
		BYTE(0xAA)
	}
}