73 Commits

Author SHA1 Message Date
03f87723d1 Splash 2026-03-20 10:04:16 +01:00
3607a7179c printf spinlock + remove DEPRECATED stuff + begin separating x86 stuff 2026-03-20 09:01:57 +01:00
424b4c4632 Use MSR to map framebuffer as WC (write-combining) = huge speed diff on real HW 2026-03-19 19:34:31 +01:00
6a82d581fb Fix PMM for real HW + serial lock 2026-03-19 16:54:23 +01:00
b77c53ae99 Keyboard buffer + getline 2026-03-18 13:07:26 +01:00
f7735eb3a4 Move headers to include/ 2026-03-18 11:48:33 +01:00
a1e8aacd01 improve README.md 2026-03-17 10:33:10 +01:00
ccc8985d4c Merge pull request 'Improve Makefile' (#15) from furtest/pepperOS:makefile into main
Reviewed-on: #15
2026-03-15 21:17:44 +01:00
0482f594ef Flanterm back to bump alloc (allows earlier use for real hw debugging) 2026-03-15 21:11:47 +01:00
b02a4b5284 Fix build-iso prerequisites 2026-03-15 18:05:24 +01:00
32f3889565 Move PHONY tags and fix clean
Move the PHONY tags to make them clearer to read.
Fix the clean rule so it deletes the build directory.
2026-03-15 18:01:35 +01:00
803ac0879b Auto find source files check for changes
Previously the build process removed everything and did all the build
again on each make invocation.
This fixes this behaviour with two changes.
First dynamically find the list of files to build using find instead of
a manually written list.
Then use implicit rules to only build files that need to be built again
instead of recompiling everything.
2026-03-15 17:56:26 +01:00
9fc55f98d8 Use variables for build and pepperk and rename build target.
Instead of hardcoding the names set them using a variable.
Also rename the target build to the name of the file it builds which is
in the ELFFILE variable.
2026-03-15 16:58:04 +01:00
11bd628821 Extract CC and LD to variables.
This allows to change the name of the compiler or linker when calling
make.
2026-03-15 16:57:29 +01:00
80d8b49560 Merge pull request 'spinlock' (#14) from spinlock into main
Reviewed-on: #14
2026-03-15 09:55:45 +01:00
22fea378b4 comments 2026-03-15 09:53:29 +01:00
5eaf193d42 Fix panic/stack trace 2026-03-15 09:44:18 +01:00
af3a9e27fd Switch to nanoprintf + good spinlock (rflags) = no more FLANTERM ISSUES??? 2026-03-15 09:34:17 +01:00
6a3abb0f55 Read RFLAGS register at panic 2026-03-14 10:13:53 +01:00
6ceccb2374 Merge pull request 'style' (#13) from style into main
Reviewed-on: #13
2026-03-14 09:34:00 +01:00
e5c296238c Stack trace all black & void arg fix 2026-03-14 09:31:57 +01:00
5c0d02579b void parameter on functions of arity 0 2026-03-13 17:21:52 +01:00
8026c33639 Function comments (v1) 2026-03-13 12:51:29 +01:00
8e2a612d88 Fix braces + init_paging args 2026-03-11 19:58:00 +01:00
9d409317e2 DEBUG with Capital Letters 2026-03-11 15:24:45 +01:00
1dd4e728d4 Build folder + coding style guidelines 2026-03-11 14:59:20 +01:00
b9c77a316a Add panic/stack trace display on fb for real hardware debug 2026-03-10 09:48:14 +01:00
6fc28806e2 Merge pull request 'kbd_fix' (#12) from kbd_fix into main
Reviewed-on: #12
2026-03-09 09:30:42 +01:00
3f9b78b05e Scheduler returns to IDLE when.. idle. 2026-03-09 09:27:55 +01:00
42c7a55d3f Init struct + freeing a bit of kmain() 2026-03-08 13:21:19 +01:00
5e9c582833 Fixed kbd (buffer flush) 2026-03-08 09:54:45 +01:00
77d9df6f48 Merge pull request 'flanterm' (#11) from flanterm into main
Reviewed-on: #11
2026-03-08 09:18:34 +01:00
90dc26ee11 Flanterm support OK from kmain. No kbd. Writing from process = PF 2026-03-08 09:14:21 +01:00
c8a72244b1 remove old term support + PSFv1 font 2026-03-05 09:10:06 +01:00
b9f55d89f6 no more PF in kmain, but still PF in process OR corruption of fb 2026-03-05 08:08:50 +01:00
a7d9e70a61 Flanterm can write to fb but page fault before process creation. (BEFORE KHEAP UPDATE) 2026-03-04 12:21:20 +01:00
9df33b49d8 flanterm PAGE FAULT, tries to access NULL or NULL+small offset 2026-03-02 11:32:24 +01:00
1f055ab31c Flanterm integration? but page fault in flanterm_fb_double_buffer_flush 2026-02-22 18:27:57 +01:00
95c801b991 Merge pull request 'process_mem' (#10) from process_mem into main
Reviewed-on: #10
2026-02-21 19:57:30 +01:00
70f19ab299 symbols build files added to gitignore 2026-02-21 19:36:44 +01:00
9470dedb61 Stack trace with double linking to get symbol names 2026-02-21 19:28:17 +01:00
4cf4fb0dda Task switching fix? but doesnt exit process gracefully 2026-02-20 16:01:34 +01:00
ac7216d84a Setup kernel stack; but process is failing 2026-02-17 23:01:32 +01:00
458ba375f3 better panic 2026-02-07 02:18:15 +01:00
b920c87bab Merge pull request 'process' (#9) from process into main
Reviewed-on: #9
2026-02-06 21:46:07 +01:00
4fbd9b3987 minor fix 2026-02-06 21:44:51 +01:00
8aad1235c3 A bit of cleaning 2026-02-06 14:39:19 +01:00
38710653be Config header file + comment header 2026-02-06 13:59:46 +01:00
7f997f6611 alloc_stack ok (HHDM mapped from mmap) 2026-02-05 21:18:21 +01:00
7bb542d901 bump-allocated PID but kheap needs fix to kmalloc more than PAGE_SIZE 2026-02-02 11:05:27 +01:00
4a90de9521 10ms Round Robin scheduler (blank processes) 2026-02-01 11:25:43 +01:00
c46157fad0 Process linked list 2026-01-31 14:13:48 +01:00
6e633b44b7 Merge pull request 'term_fix' (#8) from term_fix into main
Reviewed-on: #8
2026-01-25 09:53:45 +01:00
b8a155fada Who cares 2026-01-25 09:51:28 +01:00
091f94f89e Broken term scrolling 2026-01-10 14:43:51 +01:00
b469952d91 scroll kinda works but keyboard is random 2026-01-10 11:32:27 +01:00
9cbecc1689 GP Fault handler 2026-01-10 11:04:08 +01:00
12ab12f1b2 serial Kernel panic 2026-01-10 09:45:20 +01:00
0f72987bc1 use boot_ctx 2026-01-04 11:18:20 +01:00
d9dfd4c749 version splash 2026-01-04 11:00:30 +01:00
be1be41a64 Merge pull request 'memory' (#7) from memory into main
Reviewed-on: #7
2026-01-04 09:27:59 +01:00
923758a4ea Remove useless code/comments 2026-01-04 09:24:25 +01:00
e18b73c8a0 Small kernel heap for VMM internals, kmalloc/kfree 2026-01-03 13:48:10 +01:00
c065df6ff3 Paging: mapped kernel, fb, early-mem, HHDM 2026-01-02 13:40:44 +01:00
bb5fb9db33 Cleaner include paths + some paging definitions 2026-01-02 11:24:24 +01:00
075058a958 PMM: init with freelist 2025-12-31 17:42:26 +01:00
05a862e97a PMM: init (find biggest usable region) 2025-12-31 12:02:41 +01:00
8f5e2eae3e First steps: getting memory map from Limine request and looking at it 2025-12-30 21:33:38 +01:00
cf4915d9f4 Update README.md 2025-12-30 18:13:53 +01:00
834891fd2a DEBUG fix 2025-12-28 12:32:29 +01:00
3853a1ace3 Efficient DEBUG logging system with __FILE__ and fctprintf 2025-12-28 12:15:32 +01:00
ead0ed6ae1 Folder restructuration 2025-12-28 11:39:39 +01:00
fabe0b1a10 Merge pull request 'kbd' (#6) from kbd into main
Reviewed-on: #6
2025-12-28 11:17:08 +01:00
67 changed files with 8642 additions and 1577 deletions

7
.gitignore vendored
View File

@@ -7,3 +7,10 @@ iso_root
*.gch
*/*.gch
*/*/*.gch
.gdb_history
symbols.map
symbols.S
*.log
build/
compile_commands.json
.cache/

View File

@@ -1,19 +1,41 @@
build:
rm -f *.o
x86_64-elf-gcc -g -c -I src src/kbd/ps2.c src/time/timer.c src/idt/idt.c src/mem/utils.c src/mem/gdt.c src/io/serial.c src/io/term.c src/io/printf.c src/kmain.c -Wall -Wextra -std=gnu99 -nostdlib -ffreestanding -fno-stack-protector -fno-stack-check -fno-PIC -ffunction-sections -fdata-sections -mcmodel=kernel
objcopy -O elf64-x86-64 -B i386 -I binary zap-light16.psf zap-light16.o
nasm -f elf64 src/idt/idt.S -o idt_stub.o
x86_64-elf-ld -o pepperk -T linker.ld *.o
BUILDDIR := build
ELFFILE := pepperk
SRC := src
SOURCES := $(shell find src -name '*.c')
OBJFILES := $(patsubst $(SRC)/%.c, $(BUILDDIR)/%.o, $(SOURCES))
CC := x86_64-elf-gcc
CC_FLAGS=-Wall -Wextra -std=gnu99 -nostdlib -ffreestanding -fno-stack-protector -fno-omit-frame-pointer -fno-stack-check -fno-PIC -ffunction-sections -fdata-sections -mcmodel=kernel
CC_PROBLEMATIC_FLAGS=-Wno-unused-parameter -Wno-unused-variable
LD := x86_64-elf-ld
$(ELFFILE): $(BUILDDIR) $(OBJFILES)
nasm -f elf64 src/arch/x86/idt.S -o $(BUILDDIR)/idt_stub.o
$(LD) -o $(ELFFILE) -T linker.ld $(OBJFILES) $(BUILDDIR)/idt_stub.o
# Get the symbols for debugging
nm -n $(ELFFILE) | awk '$$2 ~ /[TtDdBbRr]/ {print $$1, $$3}' > symbols.map
python3 symbols.py
nasm -f elf64 symbols.S -o $(BUILDDIR)/symbols.o
$(LD) -o $(ELFFILE) -T linker.ld $(OBJFILES) $(BUILDDIR)/idt_stub.o $(BUILDDIR)/symbols.o
$(BUILDDIR):
@mkdir -p $(BUILDDIR)
$(BUILDDIR)/%.o: $(SRC)/%.c
mkdir -p $(dir $@)
$(CC) -g -c -Iinclude $< $(CC_PROBLEMATIC_FLAGS) $(CC_FLAGS) -o $@
limine/limine:
rm -rf limine
git clone https://github.com/limine-bootloader/limine.git --branch=v9.x-binary --depth=1
$(MAKE) -C limine
build-iso: limine/limine build
build-iso: limine/limine $(ELFFILE)
rm -rf iso_root
mkdir -p iso_root/boot
cp -v pepperk iso_root/boot
cp -v $(ELFFILE) iso_root/boot
mkdir -p iso_root/boot/limine
cp -v limine.conf iso_root/boot/limine
mkdir -p iso_root/EFI/BOOT
@@ -27,12 +49,20 @@ build-iso: limine/limine build
iso_root -o pepper.iso
./limine/limine bios-install pepper.iso
.PHONY: debug
debug:
qemu-system-x86_64 -drive file=pepper.iso -s -S -d int -no-reboot &
gdb pepperk --command=debug.gdb
/usr/bin/qemu-system-x86_64 -drive file=pepper.iso -s -S -d int -D qemu.log -no-reboot -no-shutdown &
gdb $(ELFFILE) --command=debug.gdb
.PHONY: debug2
debug2:
/usr/bin/qemu-system-x86_64 -drive file=pepper.iso -s -S -d int -no-reboot -no-shutdown &
pwndbg $(ELFFILE) --command=debug.gdb
.PHONY: run
run: build-iso
qemu-system-x86_64 -cdrom pepper.iso -serial stdio
/usr/bin/qemu-system-x86_64 -cdrom pepper.iso -serial stdio
.PHONY: clean
clean:
rm -rf *.o pepperk iso_root pepper.iso limine
rm -rf $(BUILDDIR) symbols.map symbols.S $(ELFFILE) iso_root pepper.iso limine

View File

@@ -1,20 +1,76 @@
# <img width="40" height="40" alt="red-pepper" src="https://i.ibb.co/mrHH6d1m/pixil-frame-0-4.png" /> pepperOS: "will never be done"
## Trying the kernel
## Description
First install the dependencies: `sudo apt install xorriso make qemu-system`
PepperOS is a 64-bit freely-licensed monolithic kernel for x86 processors, with round-robin preemptive scheduling and 4-level paging. See the [manual](docs/MANUAL.md) for more.
Then, to compile the kernel and make an ISO image file: `make build-iso`
To run it with QEMU, `make run`
## Trying the kernel in QEMU
### Debian-based distributions
First, install the dependencies: `sudo apt install nasm python3 xorriso make qemu-system`
Then, you can get an x86_64 toolchain for compilation. The easiest way to do that on most systems is to install it from Homebrew:
```
brew install x86_64-elf-gcc
```
If you're already on a 64-bit machine (which you probably are), and don't want to install a cross-compiler, you can just override `CC` and `LD` variables in the Makefile, like so:
```
CC := gcc
LD := ld
```
Then, to compile the kernel and make an ISO image file, run: `make build-iso`
To run it with QEMU, do: `make run`
## Trying the kernel on real hardware
Compile the kernel and generate an ISO image like described above, then burn the image to a USB stick, `/dev/sdX` being the device name (you can get it using `lsblk`):
```
sudo dd if=pepper.iso of=/dev/sdX
```
## TODO
The basics that I'm targeting are:
### Basic utility of what we call a "kernel"
- Implement tasks, and task switching + context switching and spinlock acquire/release
- Load an executable
- Filesystem (TAR for read-only initfs, then maybe read-write using FAT12/16/32 or easier fs) w/ VFS layer
- Getting to userspace (ring 3 switching, syscall interface)
- Porting musl libc or equivalent
### Scalability/maintenance/expansion features
- Documentation
- SOME error handling in functions
- Unit tests
- Good error codes (like Linux kernel: ENOMEM, ENOENT, ...)
### Optional features
In the future, maybe?
- SMP support (Limine provides functionality to make this easier)
- Parsing the ACPI tables and using them for something
- Replacing the PIT timer with APIC
## Thanks
PepperOS wouldn't be possible without the following freely-licensed software:
- the [Limine](https://codeberg.org/Limine/Limine) portable bootloader
- Marco Paland's freestanding [printf implementation](https://github.com/mpaland)
- the [ZAP](https://www.zap.org.au/projects/console-fonts-zap/) PSF console fonts
- Charles Nicholson's [nanoprintf](https://github.com/charlesnicholson/nanoprintf)
- Mintuski's [Flanterm](https://codeberg.org/Mintsuki/Flanterm) terminal emulator
...and without these amazing resources:
- the [OSDev](https://osdev.org) wiki & forums
- Intel 64 and IA-32 Architectures Software Developer's Manual
- Documentation for the [GNU Compiler Collection](https://gcc.gnu.org/onlinedocs/gcc/)
- dreamos82's [OSDev Notes](https://github.com/dreamportdev/Osdev-Notes/tree/master)

View File

@@ -1,3 +1,7 @@
target remote localhost:1234
set disassembly-flavor intel
display/8i $rip
display/4i $rip
# Trying to debug that flanterm page fault
# b plot_char_unscaled_uncanvas if $rdi == 0 || $rsi == 0 || $rdx == 0 || $r10 == 0

40
docs/MANUAL.md Normal file
View File

@@ -0,0 +1,40 @@
# PepperOS Manual
# Table of Contents
- [Overview](#i-overview)
- [Supported Hardware](#a-supported-hardware)
- [Features](#b-features)
- [Kernel architecture](#ii-kernel-architecture)
- [Boot process](#a-boot-process)
- [Memory management](#b-memory-management)
- [Scheduling](#c-scheduling)
- [Input/output](#d-inputoutput)
- [Syscall table](#iii-syscall-table)
## I. Overview
## a. Supported Hardware
The recommended hardware to run PepperOS is the following:
- UEFI/BIOS
- Any x86 processor, 64-bits only
- PS/2 Keyboard
- Minimum 128MB of memory
## b. Features
## II. Kernel architecture
### a. Boot process
### b. Memory management
### c. Scheduling
### d. Input/Output
## III. Syscall table
Not yet implemented.

93
docs/STYLE.md Normal file
View File

@@ -0,0 +1,93 @@
# Pepper kernel coding style
This document describes the coding style for the Pepper kernel. It is used as a guideline across all source files.
## Indentation
Indentations should be 4 characters long.
## Line length
Lines should not be more than 100 characters long. Exceptions is made for printing strings.
## Variables
Variables should be declared at most once per line.
## Braces
Non-function statement blocks should have an opening brace last on the line, and a closing brace first. Exception is made for `else`, `else if` statements and the like:
```c
if (something) {
do_something();
} else if (something_else) {
do_something_else();
}
```
Having no braces for a single statement structure is fine.
Functions should have their opening brace on a separate line, and the same goes for the closing brace:
```c
void function()
{
do_something();
}
```
## Spaces
Use a space after `if, switch, case, for, do, while` keywords, but not for `sizeof, typeof, alignof, __attribute__` and the like.
For pointers, the asterisk should always be placed adjacent to the type name, like `char* str;`.
## Naming
Functions should be named with whole words, beginning with the corresponding name of the module in the kernel (the parent folder). Words should be spaced with underscores, like so:
```c
serial_init(void* ptr, char* str, int foo);
```
Constants should be named in all caps, separated by underscores:
```c
#define MAX_HEAP_SIZE 0x1000
```
Global variables need to have descriptive names. Local variables can be kept short (especially for loop counters).
## Typedefs
Structures should not be `typedef`'d. However using `typedef` for an enumeration is fine.
## Functions
Functions should be short, simple, and only do one thing.
Function prototypes should include parameter names and their data types.
## Commenting
Comments should describe what a function does and why, not how it does it. The preferred way of commenting functions is the following:
```c
/*
* function_name - Function brief description
* @arg1: Argument 1 description
* @arg2: Argument 2 description
*
* A longer description can be featured here, explaining more
* in detail what the function does and why it does it.
*/
```
## Kernel messages
When printing kernel messages with the `DEBUG` macro, they should start with a capital letter.
### Resources
Some of the elements here are inspired by the [Linux kernel coding style](https://www.kernel.org/doc/html/v4.10/process/coding-style.html).

View File

@@ -1,12 +1,20 @@
#ifndef IDT_H
#define IDT_H
#ifndef X86_H
#define X86_H
#include <stdbool.h>
#include <stdint.h>
void idt_init();
uint64_t rdmsr(uint32_t msr);
void cpuid(uint32_t leaf, uint32_t* eax, uint32_t* ebx, uint32_t* ecx, uint32_t* edx);
void wrmsr(uint32_t msr, uint64_t value);
bool x86_has_msr();
void x86_arch_init();
struct interrupt_descriptor
{
/* Interrupt Descriptor Table */
void idt_init(void);
struct interrupt_descriptor {
uint16_t address_low;
uint16_t selector;
uint8_t ist;
@@ -16,8 +24,7 @@ struct interrupt_descriptor
uint32_t reserved;
} __attribute__((packed));
struct idtr
{
struct idtr {
uint16_t limit;
uint64_t base;
} __attribute__((packed));
@@ -25,8 +32,7 @@ struct idtr
// All general-purpose registers (except rsp) as stored on the stack,
// plus the values we pushed (vector number, error code) and the iret frame
// In reverse order because the stack grows downwards.
struct cpu_status_t
{
struct cpu_status_t {
uint64_t r15;
uint64_t r14;
uint64_t r13;
@@ -36,7 +42,6 @@ struct cpu_status_t
uint64_t r9;
uint64_t r8;
uint64_t rbp;
//uint64_t rsp;
uint64_t rdi;
uint64_t rsi;
uint64_t rdx;

55
include/config.h Normal file
View File

@@ -0,0 +1,55 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief PepperOS configuration file
* @license GPL-3.0-only
*/
#ifndef CONFIG_H
#define CONFIG_H
/* version */
#define PEPPEROS_VERSION_MAJOR "0"
#define PEPPEROS_VERSION_MINOR "0"
#define PEPPEROS_VERSION_PATCH "58"
#define PEPPEROS_SPLASH \
"\x1b[38;5;196m \x1b[38;5;231m____ _____\r\n\x1b[0m"\
"\x1b[38;5;196m ____ ___ ____ ____ ___ _____\x1b[38;5;231m/ __ \\/ ___/\r\n\x1b[0m"\
"\x1b[38;5;196m / __ \\/ _ \\/ __ \\/ __ \\/ _ \\/ ___\x1b[38;5;231m/ / / /\\__ \\ \r\n\x1b[0m"\
"\x1b[38;5;196m / /_/ / __/ /_/ / /_/ / __/ / \x1b[38;5;231m/ /_/ /___/ / \r\n\x1b[0m"\
"\x1b[38;5;196m / .___/\\___/ .___/ .___/\\___/_/ \x1b[38;5;231m\\____//____/ \r\n\x1b[0m"\
"\x1b[38;5;196m/_/ /_/ /_/ \r\n\x1b[0m"\
" --- version \x1b[38;5;220m"PEPPEROS_VERSION_MAJOR"."PEPPEROS_VERSION_MINOR"."PEPPEROS_VERSION_PATCH"\x1b[0m built on \x1b[38;5;40m"__DATE__" "__TIME__"\x1b[0m\r\n"
/* process */
#define PROCESS_NAME_MAX 64
#define PROCESS_STACK_SIZE 0x10000 // 64kb
#define PROCESS_BASE 0x400000
#define PROCESS_STACK_BASE 0x1000000
/* sched */
// 1 tick = 1 ms => quantum = 10ms
#define SCHEDULER_QUANTUM 10
/* kernel */
#define KERNEL_BASE 0xFFFFFFFF80000000ULL
// 2 MB should be enough (as of now, the whole kernel ELF is around 75kb)
#define KERNEL_SIZE 0x200000
#define KERNEL_STACK_SIZE 65536
#define KERNEL_IDT_ENTRIES 33
/* paging */
#define PAGING_MAX_PHYS 0x200000000
/* heap */
#define KHEAP_SIZE (32*1024*1024)
/* term */
#define TERM_HISTORY_MAX_LINES 256
/* kbd */
#define KBD_BUFFER_MAX 256
/* time */
#define TIMER_FREQUENCY 1000
#endif

View File

@@ -1,21 +1,30 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief PS/2 Keyboard driver
* @license GPL-3.0-only
*/
#ifndef PS2_H
#define PS2_H
void keyboard_handler();
#include <stddef.h>
void keyboard_handler(void);
char keyboard_getchar();
int keyboard_putchar(char c);
int keyboard_getline(char* output, size_t size);
#define SHIFT_PRESSED_BIT 0b00000001
#define ALT_PRESSED_BIT 0b00000010
#define CTRL_PRESSED_BIT 0b00000100
enum SpecialKeys
{
enum SpecialKeys {
SHIFT = 255,
ALT = 254,
CTRL = 253
};
enum SpecialScancodes
{
enum SpecialScancodes {
LEFT_SHIFT_PRESSED = 0x2A,
LEFT_SHIFT_RELEASED = 0xAA,
RIGHT_SHIFT_PRESSED = 0x36,
@@ -26,8 +35,7 @@ enum SpecialScancodes
ALT_RELEASED = 0xB8
};
enum KeyboardLayout
{
enum KeyboardLayout {
US,
FR
};

View File

@@ -0,0 +1,20 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Debug serial driver
* @license GPL-3.0-only
*/
#ifndef SERIAL_H
#define SERIAL_H
// COM1
#define PORT 0x3F8
void outb(int port, unsigned char data);
unsigned char inb(int port);
int serial_init(void);
void skputs(const char* str);
void skputc(char c);
#endif

View File

@@ -0,0 +1,72 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/* Copyright (C) 2022-2026 Mintsuki and contributors.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef FLANTERM_H
#define FLANTERM_H 1
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#define FLANTERM_CB_DEC 10
#define FLANTERM_CB_BELL 20
#define FLANTERM_CB_PRIVATE_ID 30
#define FLANTERM_CB_STATUS_REPORT 40
#define FLANTERM_CB_POS_REPORT 50
#define FLANTERM_CB_KBD_LEDS 60
#define FLANTERM_CB_MODE 70
#define FLANTERM_CB_LINUX 80
#define FLANTERM_CB_OSC 90
#ifdef FLANTERM_IN_FLANTERM
#include "flanterm_private.h"
#else
struct flanterm_context;
#endif
void flanterm_write(struct flanterm_context *ctx, const char *buf, size_t count);
void flanterm_flush(struct flanterm_context *ctx);
void flanterm_full_refresh(struct flanterm_context *ctx);
void flanterm_deinit(struct flanterm_context *ctx, void (*_free)(void *ptr, size_t size));
void flanterm_get_dimensions(struct flanterm_context *ctx, size_t *cols, size_t *rows);
void flanterm_set_autoflush(struct flanterm_context *ctx, bool state);
void flanterm_set_callback(struct flanterm_context *ctx, void (*callback)(struct flanterm_context *, uint64_t, uint64_t, uint64_t, uint64_t));
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,79 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/* Copyright (C) 2022-2026 Mintsuki and contributors.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef FLANTERM_FB_H
#define FLANTERM_FB_H 1
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#include "../flanterm.h"
#ifdef FLANTERM_IN_FLANTERM
#include "fb_private.h"
#endif
#define FLANTERM_FB_ROTATE_0 0
#define FLANTERM_FB_ROTATE_90 1
#define FLANTERM_FB_ROTATE_180 2
#define FLANTERM_FB_ROTATE_270 3
struct flanterm_context *flanterm_fb_init(
/* If _malloc and _free are nulled, use the bump allocated instance (1 use only). */
void *(*_malloc)(size_t size),
void (*_free)(void *ptr, size_t size),
uint32_t *framebuffer, size_t width, size_t height, size_t pitch,
uint8_t red_mask_size, uint8_t red_mask_shift,
uint8_t green_mask_size, uint8_t green_mask_shift,
uint8_t blue_mask_size, uint8_t blue_mask_shift,
uint32_t *canvas, /* If nulled, no canvas. */
uint32_t *ansi_colours, uint32_t *ansi_bright_colours, /* If nulled, default. */
uint32_t *default_bg, uint32_t *default_fg, /* If nulled, default. */
uint32_t *default_bg_bright, uint32_t *default_fg_bright, /* If nulled, default. */
/* If font is null, use default font and font_width and font_height ignored. */
void *font, size_t font_width, size_t font_height, size_t font_spacing,
/* If scale_x and scale_y are 0, automatically scale font based on resolution. */
size_t font_scale_x, size_t font_scale_y,
size_t margin,
/* One of FLANTERM_FB_ROTATE_* values. */
int rotation
);
void flanterm_fb_set_flush_callback(struct flanterm_context *ctx, void (*flush_callback)(volatile void *address, size_t length));
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,127 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/* Copyright (C) 2022-2026 Mintsuki and contributors.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef FLANTERM_FB_PRIVATE_H
#define FLANTERM_FB_PRIVATE_H 1
#ifndef FLANTERM_IN_FLANTERM
#error "Do not use fb_private.h. Use interfaces defined in fb.h only."
#endif
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#define FLANTERM_FB_FONT_GLYPHS 256
struct flanterm_fb_char {
uint32_t c;
uint32_t fg;
uint32_t bg;
};
struct flanterm_fb_queue_item {
size_t x, y;
struct flanterm_fb_char c;
};
struct flanterm_fb_context {
struct flanterm_context term;
void (*plot_char)(struct flanterm_context *ctx, struct flanterm_fb_char *c, size_t x, size_t y);
void (*flush_callback)(volatile void *address, size_t length);
size_t font_width;
size_t font_height;
size_t glyph_width;
size_t glyph_height;
size_t font_scale_x;
size_t font_scale_y;
size_t offset_x, offset_y;
volatile uint32_t *framebuffer;
size_t pitch;
size_t width;
size_t height;
size_t phys_height;
size_t bpp;
uint8_t red_mask_size, red_mask_shift;
uint8_t green_mask_size, green_mask_shift;
uint8_t blue_mask_size, blue_mask_shift;
int rotation;
size_t font_bits_size;
uint8_t *font_bits;
size_t font_bool_size;
bool *font_bool;
uint32_t ansi_colours[8];
uint32_t ansi_bright_colours[8];
uint32_t default_fg, default_bg;
uint32_t default_fg_bright, default_bg_bright;
size_t canvas_size;
uint32_t *canvas;
size_t grid_size;
size_t queue_size;
size_t map_size;
struct flanterm_fb_char *grid;
struct flanterm_fb_queue_item *queue;
size_t queue_i;
struct flanterm_fb_queue_item **map;
uint32_t text_fg;
uint32_t text_bg;
size_t cursor_x;
size_t cursor_y;
uint32_t saved_state_text_fg;
uint32_t saved_state_text_bg;
size_t saved_state_cursor_x;
size_t saved_state_cursor_y;
size_t old_cursor_x;
size_t old_cursor_y;
};
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,133 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/* Copyright (C) 2022-2026 Mintsuki and contributors.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef FLANTERM_PRIVATE_H
#define FLANTERM_PRIVATE_H 1
#ifndef FLANTERM_IN_FLANTERM
#error "Do not use flanterm_private.h. Use interfaces defined in flanterm.h only."
#endif
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#define FLANTERM_MAX_ESC_VALUES 16
struct flanterm_context {
/* internal use */
size_t tab_size;
bool autoflush;
bool cursor_enabled;
bool scroll_enabled;
bool wrap_enabled;
bool origin_mode;
bool control_sequence;
bool escape;
bool osc;
bool osc_escape;
size_t osc_buf_i;
uint8_t osc_buf[256];
bool rrr;
bool discard_next;
bool bold;
bool bg_bold;
bool reverse_video;
bool dec_private;
bool insert_mode;
bool csi_unhandled;
uint64_t code_point;
size_t unicode_remaining;
uint8_t g_select;
uint8_t charsets[2];
size_t current_charset;
size_t escape_offset;
size_t esc_values_i;
size_t saved_cursor_x;
size_t saved_cursor_y;
size_t current_primary;
size_t current_bg;
size_t scroll_top_margin;
size_t scroll_bottom_margin;
uint32_t esc_values[FLANTERM_MAX_ESC_VALUES];
uint8_t last_printed_char;
bool last_was_graphic;
bool saved_state_bold;
bool saved_state_bg_bold;
bool saved_state_reverse_video;
bool saved_state_origin_mode;
bool saved_state_wrap_enabled;
size_t saved_state_current_charset;
uint8_t saved_state_charsets[2];
size_t saved_state_current_primary;
size_t saved_state_current_bg;
/* to be set by backend */
size_t rows, cols;
void (*raw_putchar)(struct flanterm_context *, uint8_t c);
void (*clear)(struct flanterm_context *, bool move);
void (*set_cursor_pos)(struct flanterm_context *, size_t x, size_t y);
void (*get_cursor_pos)(struct flanterm_context *, size_t *x, size_t *y);
void (*set_text_fg)(struct flanterm_context *, size_t fg);
void (*set_text_bg)(struct flanterm_context *, size_t bg);
void (*set_text_fg_bright)(struct flanterm_context *, size_t fg);
void (*set_text_bg_bright)(struct flanterm_context *, size_t bg);
void (*set_text_fg_rgb)(struct flanterm_context *, uint32_t fg);
void (*set_text_bg_rgb)(struct flanterm_context *, uint32_t bg);
void (*set_text_fg_default)(struct flanterm_context *);
void (*set_text_bg_default)(struct flanterm_context *);
void (*set_text_fg_default_bright)(struct flanterm_context *);
void (*set_text_bg_default_bright)(struct flanterm_context *);
void (*move_character)(struct flanterm_context *, size_t new_x, size_t new_y, size_t old_x, size_t old_y);
void (*scroll)(struct flanterm_context *);
void (*revscroll)(struct flanterm_context *);
void (*swap_palette)(struct flanterm_context *);
void (*save_state)(struct flanterm_context *);
void (*restore_state)(struct flanterm_context *);
void (*double_buffer_flush)(struct flanterm_context *);
void (*full_refresh)(struct flanterm_context *);
void (*deinit)(struct flanterm_context *, void (*)(void *, size_t));
/* to be set by client */
void (*callback)(struct flanterm_context *, uint64_t, uint64_t, uint64_t, uint64_t);
};
void flanterm_context_reinit(struct flanterm_context *ctx);
#ifdef __cplusplus
}
#endif
#endif

1597
include/io/term/nanoprintf.h Normal file

File diff suppressed because it is too large Load Diff

15
include/io/term/term.h Normal file
View File

@@ -0,0 +1,15 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Framebuffer-based terminal driver
* @license GPL-3.0-only
*/
#ifndef TERM_H
#define TERM_H
void kputs(const char* str);
void term_init(void);
int printf(const char* fmt, ...);
void internal_putc(int c, void *_);
#endif

63
include/kernel.h Normal file
View File

@@ -0,0 +1,63 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Kernel global macros
* @license GPL-3.0-only
*/
#ifndef KERNEL_H
#define KERNEL_H
enum ErrorCodes {
ENOMEM,
EIO
};
#define CLEAR_INTERRUPTS __asm__ volatile("cli")
#define SET_INTERRUPTS __asm__ volatile("sti")
#include <io/serial/serial.h>
#include <io/term/term.h>
#include <arch/x86.h>
#include <stdbool.h>
extern volatile uint64_t ticks;
#define DEBUG(log, ...) printf("[%8u] debug: <%s>: " log "\r\n", ticks, __func__, ##__VA_ARGS__)
/* #define DEBUG(log, ...) \
printf("debug: [%s]: " log "\r\n", __FILE__, ##__VA_ARGS__); \
fctprintf((void*)&skputc, 0, "debug: [%s]: " log "\r\n", __FILE__, ##__VA_ARGS__)
*/
#define DIE_DEBUG(str) printf(str)
#define CHECK_BIT(var,pos) ((var) & (1<<(pos)))
// printf("debug: [%s]: " log "\n", __FILE__, ##__VA_ARGS__);
void panic(struct cpu_status_t* ctx, const char* str);
void hcf(void);
void idle(void);
/* debug */
void debug_stack_trace(unsigned int max_frames);
const char* debug_find_symbol(uintptr_t rip, uintptr_t* offset);
void boot_mem_display(void);
#define assert(check) do { if(!(check)) hcf(); } while(0)
struct boot_context {
struct limine_framebuffer* fb;
struct limine_memmap_response* mmap;
struct limine_hhdm_response* hhdm;
struct limine_kernel_address_response* kaddr;
};
// Are these modules initialized yet?
struct init_status {
bool terminal;
bool serial;
bool keyboard;
bool timer;
};
#endif

View File

@@ -1,3 +1,9 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Global Descriptor Table (for legacy reasons)
* @license GPL-3.0-only
*/
#ifndef GDT_H
#define GDT_H
@@ -15,12 +21,11 @@
#define USER_CODE_SEGMENT 0x18
#define USER_DATA_SEGMENT 0x20
struct GDTR
{
struct GDTR {
uint16_t limit;
uint64_t address;
} __attribute__((packed));
void gdt_init();
void gdt_init(void);
#endif

32
include/mem/kheap.h Normal file
View File

@@ -0,0 +1,32 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Kernel heap
* @license GPL-3.0-only
*/
#ifndef KHEAP_H
#define KHEAP_H
// We need some kind of simple kernel heap to make our linked list
// for the VMM, as we need "malloc" and "free" for that data structure.
// When the kernel heap is ready, we can alloc our VM object linked list
// and then continue working on the VMM.
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
struct heap_block_t {
size_t size;
bool free; // 1byte
uint8_t reserved[7]; // (7+1 = 8 bytes)
struct heap_block_t* next;
} __attribute__((aligned(16)));
void kheap_init(void);
void* kmalloc(size_t size);
void kfree(void* ptr);
void* kalloc_stack(void);
void kheap_map_page(void);
#endif

55
include/mem/paging.h Normal file
View File

@@ -0,0 +1,55 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief x64 4-level paging implementation
* @license GPL-3.0-only
*/
#ifndef PAGING_H
#define PAGING_H
#define PAGE_SIZE 4096
#include <stdint.h>
#include <limine.h>
#include <mem/kheap.h>
#include <kernel.h>
void paging_init(struct boot_context boot_ctx);
void paging_map_page(uint64_t* root_table, uint64_t virt, uint64_t phys, uint64_t flags);
// To swap root page tables
void load_cr3(uint64_t value);
extern uint64_t hhdm_off;
#define PHYS_TO_VIRT(x) ((void*)((uintptr_t)(x) + hhdm_off))
#define VIRT_TO_PHYS(x) ((uintptr_t)(x) - hhdm_off)
#define PTE_ADDR_MASK 0x000FFFFFFFFFF000
// Stole it
#define ALIGN_UP(x, align) (((x) + ((align) - 1)) & ~((align) - 1))
#define ALIGN_DOWN(x, align) ((x) & ~((align) - 1))
#define PAGE_ALIGN_DOWN(x) ((x) & PTE_ADDR_MASK)
#define ALIGN(size) ALIGN_UP(size, 16)
#define BLOCK_MIN_SIZE (sizeof(struct heap_block_t) + 16)
#define PML4_INDEX(x) (((x) >> 39) & 0x1FF)
#define PDPT_INDEX(x) (((x) >> 30) & 0x1FF)
#define PD_INDEX(x) (((x) >> 21) & 0x1FF)
#define PT_INDEX(x) (((x) >> 12) & 0x1FF)
// Page entry special bits
// Bits set on a parent (directory, table) fall back to their children
enum PTE_FLAGS
{
PTE_PRESENT = (1ULL << 0),
PTE_WRITABLE = (1ULL << 1),
PTE_USER = (1ULL << 2),
PTE_PWT = (1ULL << 3),
PTE_PCD = (1ULL << 4),
PTE_HUGE = (1ULL << 7),
PTE_NOEXEC = (1ULL << 63)
};
#endif

17
include/mem/pmm.h Normal file
View File

@@ -0,0 +1,17 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Physical memory manager from freelist
* @license GPL-3.0-only
*/
#ifndef PAGING_PMM_H
#define PAGING_PMM_H
#include <limine.h>
#include <kernel.h>
void pmm_init(struct boot_context boot_ctx);
void pmm_free(uintptr_t addr);
uintptr_t pmm_alloc(void);
#endif

View File

@@ -1,3 +1,9 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Common memory utilities
* @license GPL-3.0-only
*/
#ifndef MEM_UTILS_H
#define MEM_UTILS_H
@@ -8,4 +14,8 @@ void* memset(void* s, int c, size_t n);
void* memmove(void *dest, const void* src, size_t n);
int memcmp(const void* s1, const void* s2, size_t n);
// DEBUG
void memmap_display(struct limine_memmap_response* response);
void hhdm_display(struct limine_hhdm_response* hhdm);
#endif

34
include/mem/vmm.h Normal file
View File

@@ -0,0 +1,34 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Virtual memory manager
* @license GPL-3.0-only
*/
#ifndef VMM_H
#define VMM_H
#include <stdint.h>
#include <stddef.h>
/*
This will be our linked list of virtual memory objects.
Flags here aren't x86 flags, they are platform-agnostic
kernel-defined flags.
*/
struct vm_object {
uintptr_t base;
size_t length;
size_t flags;
struct vm_object* next;
};
// Flags bitfield
#define VM_FLAG_NONE 0
#define VM_FLAG_WRITE (1 << 0)
#define VM_FLAG_EXEC (1 << 1)
#define VM_FLAG_USER (1 << 2)
void vmm_init(void);
#endif

39
include/sched/process.h Normal file
View File

@@ -0,0 +1,39 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Process definition
* @license GPL-3.0-only
*/
#ifndef PROCESS_H
#define PROCESS_H
#include <stddef.h>
#include <config.h>
#include <stdint.h>
typedef enum {
READY,
RUNNING,
DEAD
} status_t;
struct process_t {
size_t pid;
char name[PROCESS_NAME_MAX];
status_t status;
struct cpu_status_t* context;
void* root_page_table; // Process PML4 (should contain kernel PML4 in higher half [256-511]
struct process_t* next;
};
void process_init(void);
struct process_t* process_create(char* name, void(*function)(void*), void* arg);
void process_add(struct process_t** processes_list, struct process_t* process);
void process_delete(struct process_t** processes_list, struct process_t* process);
struct process_t* process_get_next(struct process_t* process);
void process_exit(void);
void process_display_list(struct process_t* processes_list);
#endif

13
include/sched/scheduler.h Normal file
View File

@@ -0,0 +1,13 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Round-robin scheduler
* @license GPL-3.0-only
*/
#ifndef SCHEDULER_H
#define SCHEDULER_H
struct cpu_status_t* scheduler_schedule(struct cpu_status_t* context);
void scheduler_init(void);
#endif

22
include/sched/spinlock.h Normal file
View File

@@ -0,0 +1,22 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Spinlock implementation
* @license GPL-3.0-only
*/
#ifndef SPINLOCK_H
#define SPINLOCK_H
#include <stdbool.h>
#include <stdint.h>
struct spinlock_t
{
bool locked;
uint64_t rflags;
};
void spinlock_acquire(struct spinlock_t* lock);
void spinlock_release(struct spinlock_t* lock);
#endif

16
include/string/string.h Normal file
View File

@@ -0,0 +1,16 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief String manipulation functions
* @license GPL-3.0-only
*/
#ifndef STRING_H
#define STRING_H
#include <stddef.h>
char *strcpy(char *dest, const char *src);
char *strcat(char *dest, const char *src);
void strncpy(char* dst, const char* src, size_t n);
#endif

13
include/time/timer.h Normal file
View File

@@ -0,0 +1,13 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief PIT functions
* @license GPL-3.0-only
*/
#ifndef TIMER_H
#define TIMER_H
void timer_init(void);
void timer_wait(unsigned int wait_ticks);
#endif

View File

@@ -1,6 +1,8 @@
timeout: 3
interface_branding: Welcome to the PepperOS disk!
/PepperOS
protocol: limine
comment: Default configuration (warning: spicy)
path: boot():/boot/pepperk

21
src/arch/x86/cpuid.c Normal file
View File

@@ -0,0 +1,21 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief x86 CPU identification
* @license GPL-3.0-only
*/
#include <stdint.h>
#include <stddef.h>
/*
* cpuid - Wrapper for CPUID instruction
* @leaf: Requested leaf (input EAX)
* @eax: EAX register value (output)
* @ebx: EBX register value (output)
* @ecx: ECX register value (output)
* @edx: EDX register value (output)
*/
void cpuid(uint32_t leaf, uint32_t* eax, uint32_t* ebx, uint32_t* ecx, uint32_t* edx)
{
__asm__ volatile("cpuid" : "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx) : "a"(leaf));
}

View File

@@ -1,4 +1,8 @@
; Assembly stub for the IDT
;
; @author xamidev <xamidev@riseup.net>
; @brief Stub for Interrupt Descriptor Table handlers
; @license GPL-3.0-only
;
bits 64
@@ -147,12 +151,13 @@ vector_7_handler:
align 16
vector_8_handler:
; No error code, we only push vector number
push qword 1
push qword 8
jmp interrupt_stub
; Coprocessor Segment Overrun
align 16
vector_9_handler:
push qword 0
push qword 9
jmp interrupt_stub

271
src/arch/x86/idt.c Normal file
View File

@@ -0,0 +1,271 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Interrupt Descriptor Table setup and dispatching
* @license GPL-3.0-only
*/
#include <arch/x86.h>
#include <stdint.h>
#include <stddef.h>
#include <io/serial/serial.h>
#include <io/kbd/ps2.h>
#include <kernel.h>
#include <stdbool.h>
#include <sched/scheduler.h>
#include <config.h>
#include <sched/process.h>
struct interrupt_descriptor idt[256];
struct idtr idt_reg;
// Address to our first interrupt handler
extern char vector_0_handler[];
// Timer ticks
extern volatile uint64_t ticks;
/*
* idt_set_entry - Sets an Interrupt Descriptor Table entry
* @vector: Vector number in the IDT
* @handler: Pointer to the executable Interrupt Service Routine
* @dpl: Desired privilege level
*/
void idt_set_entry(uint8_t vector, void* handler, uint8_t dpl)
{
uint64_t handler_addr = (uint64_t)handler;
struct interrupt_descriptor* entry = &idt[vector];
// Address is split in three parts so we right-shift progressively to get it all
entry->address_low = handler_addr & 0xFFFF;
entry->address_mid = (handler_addr >> 16) & 0xFFFF;
entry->address_high = handler_addr >> 32;
// Kernel code selector (as set in GDT)
entry->selector = 0x8;
// Interrupt gate, present, DPL (having: max DPL = 3)
entry->flags = 0b1110 | ((dpl & 0b11) << 5) | (1 << 7);
// We won't use IST for now
entry->ist = 0;
}
/*
* idt_load - Loads the Interrupt Descriptor Table
* @idt_addr: Address to the IDT
*/
void idt_load(void* idt_addr)
{
// "limit" = "size" = Size of the IDT - 1 byte = (16*256)-1 = 0xFFF
idt_reg.limit = 0xFFF;
idt_reg.base = (uint64_t)idt_addr;
asm volatile("lidt %0" :: "m"(idt_reg));
}
/*
* idt_init - Initializes the Interrupt Descriptor Table
*
* Sets all IDT entries and their corresponding service routines,
* then loads it.
*/
void idt_init()
{
for (size_t i=0; i<=KERNEL_IDT_ENTRIES; i++) {
// Each vector handler is 16-byte aligned, so <vector_no>*16 = address of that handler
idt_set_entry(i, vector_0_handler + (i*16), 0);
}
idt_load(&idt);
DEBUG("IDT initialized");
}
/*
* read_cr2 - Reads the CR2 register
*
* This function is useful because it gets the address
* that the CPU tried to access in the case of a #PF.
*
* Return:
* %val - CR2 register value
*/
static inline uint64_t read_cr2(void)
{
uint64_t val;
asm volatile ("mov %%cr2, %0" : "=r"(val));
return val;
}
/*
* page_fault_handler - Handler for #PF
* @ctx: CPU context
*
* Shows detail about a #PF, especially what instruction (RIP)
* caused it, and what address access (CR2) caused it.
* Also displays an interpretation of the thrown error code.
* Then halts the system. We could implement demand paging later.
*/
static void page_fault_handler(struct cpu_status_t* ctx)
{
// It could be used to remap pages etc. to fix the fault, but right now what I'm more
// interested in is getting more info out of those numbers cause i'm lost each time i have
// to read all this mess
uint64_t cr2 = read_cr2();
DEBUG("\x1b[38;5;231mPage Fault at rip=0x%p, err=%u (%s%s%s%s%s%s%s%s) when accessing addr=0x%p\x1b[0m", ctx->iret_rip, ctx->error_code,
CHECK_BIT(ctx->error_code, 0) ? "PAGE_PROTECTION_VIOLATION " : "PAGE_NOT_PRESENT ",
CHECK_BIT(ctx->error_code, 1) ? "ON_WRITE " : "ON_READ ",
CHECK_BIT(ctx->error_code, 2) ? "IN_USER_MODE" : "IN_KERNEL_MODE",
CHECK_BIT(ctx->error_code, 3) ? " WAS_RESERVED" : "",
CHECK_BIT(ctx->error_code, 4) ? " ON_INSTRUCTION_FETCH" : "",
CHECK_BIT(ctx->error_code, 5) ? " PK_VIOLATION" : "",
CHECK_BIT(ctx->error_code, 6) ? " ON_SHADOWSTACK_ACCESS" : "",
CHECK_BIT(ctx->error_code, 7) ? " SGX_VIOLATION" : "",
cr2);
panic(ctx, "page fault");
}
/*
* gp_fault_handler - Handler for #GP
* @ctx: CPU context
*
* Shows detail about a General Protection Fault,
* and what may have caused it. Halts the system.
*/
static void gp_fault_handler(struct cpu_status_t* ctx)
{
DEBUG("\x1b[38;5;231mGeneral Protection Fault at rip=0x%p, err=%u (%s)\x1b[0m",
ctx->iret_rip,
ctx->error_code,
(ctx->error_code == 0) ? "NOT_SEGMENT_RELATED" : "SEGMENT_RELATED");
// Segment-related
if (ctx->error_code != 0) {
bool is_external = CHECK_BIT(ctx->error_code, 0);
// is it IDT, GDT, LDT?
uint8_t table = ctx->error_code & 0x6; // 0b110 (isolate table)
uint16_t index = ctx->error_code & 0xFFF8; // 13*1 1111111111111 + 000 = 1111111111111000
char* table_names[4] = {"GDT", "IDT", "LDT", "IDT"};
DEBUG("\x1b[38;5;231m%s in %s index %u\x1b[0m",
is_external ? "EXTERNAL" : "INTERNAL",
table_names[table],
index);
}
panic(ctx, "gp fault");
}
// DEBUG
void kbdproc_main(void* arg)
{
printf("Key pressed/released.\r\n");
}
/*
* interrupt_dispatch - Interrupt dispatcher
* @context: CPU context
*
* This function is where all interrupt routines go, after they passed
* through their corresponding vector handler in the IDT assembly stub.
* It catches all exceptions.
*
* Return:
* <context> - CPU context after interrupt
*/
struct cpu_status_t* interrupt_dispatch(struct cpu_status_t* context)
{
if (context == NULL) {
panic(NULL, "Interrupt dispatch recieved NULL context!");
}
switch(context->vector_number) {
case 0:
panic(context, "Divide Error");
break;
case 1:
panic(context, "Debug Exception");
break;
case 2:
panic(context, "NMI Interrupt");
break;
case 3:
panic(context, "Breakpoint Interrupt");
break;
case 4:
panic(context, "Overflow Trap");
break;
case 5:
panic(context, "BOUND Range Exceeded");
break;
case 6:
panic(context, "Invalid Opcode");
break;
case 7:
panic(context, "Device Not Available");
break;
case 8:
panic(context, "Double Fault");
break;
case 9:
panic(context, "Coprocessor Segment Overrun");
break;
case 10:
panic(context, "Invalid TSS");
break;
case 11:
panic(context, "Segment Not Present");
break;
case 12:
panic(context, "Stack-Segment Fault");
break;
case 13:
gp_fault_handler(context);
break;
case 14:
page_fault_handler(context);
break;
case 15:
panic(context, "Intel Reserved Interrupt (Achievement unlocked: How Did We Get Here?)");
break;
case 16:
panic(context, "x87 Floating-Point Error");
break;
case 17:
panic(context, "Alignment Check Fault");
break;
case 18:
panic(context, "Machine Check");
break;
case 19:
panic(context, "SIMD Floating-Point Exception");
break;
case 20:
panic(context, "Virtualization Exception");
break;
case 21:
panic(context, "Control Protection Exception");
break;
case 32: // Timer Interrupt
ticks++;
// Send an EOI so that we can continue having interrupts
outb(0x20, 0x20);
if (ticks % SCHEDULER_QUANTUM == 0) {
return scheduler_schedule(context);
}
break;
case 33: // Keyboard Interrupt
keyboard_handler();
process_create("keyboard-initiated", kbdproc_main, NULL); // DEBUG
outb(0x20, 0x20);
break;
default:
DEBUG("Unexpected Interrupt");
break;
}
return context;
}

46
src/arch/x86/init.c Normal file
View File

@@ -0,0 +1,46 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief x86 architecture-dependant initialization
* @license GPL-3.0-only
*/
#include <mem/gdt.h>
#include <stdint.h>
#include <arch/x86.h>
#include <kernel.h>
/*
* x86_overwrite_pat - Set PAT to WC
*
* This function overwrites the 1st Page Attribute
* Table entry, to enable the Write-Combining property
* when we map memory regions later on.
* The framebuffer will be mapped with WC, which makes
* memory access significantly faster by using burst
* operations.
*/
static void x86_overwrite_pat()
{
uint64_t pat = rdmsr(0x277);
pat &= ~(0xFFULL << 8); // Clear PAT1
pat |= (0x01ULL << 8); // PAT1 = 0x01 (WC)
wrmsr(0x277, pat);
}
/*
* x86_arch_init - Initialize x86 CPU structures
*
* This function is responsible for overriding a PAT entry
* (to put the framebuffer area in WC mode) only.
*
* Later, all architecture-dependant init (GDT, IDT, TSS, ...)
* should be initialized here, and separate function pointers
* should be set up for each arch.
*/
void x86_arch_init()
{
x86_overwrite_pat();
idt_init();
gdt_init();
}

66
src/arch/x86/msr.c Normal file
View File

@@ -0,0 +1,66 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief x86 MSR C wrappers
* @description
* Wrapper functions to access Model Specific Registers
*
* @license GPL-3.0-only
*/
#include <stdint.h>
#include <stdbool.h>
#include <arch/x86.h>
/*
* rdmsr - Read from MSR
* @msr: model specific register number
*
* Read a 64-bit word from a Model Specific Register.
* Wrapper for the "rdmsr" instruction. It originally
* outputs to two 32-bit registers (EDX:EAX), so the
* function does the job of uniting them as a 64-bit
* value for us.
*
* Return:
* <value> - value read from MSR
*/
uint64_t rdmsr(uint32_t msr)
{
uint32_t low;
uint32_t high;
__asm__ volatile("rdmsr" : "=a"(low), "=d"(high) : "c"(msr));
return ((uint64_t)high << 32) | low;
}
/*
* wrmsr - Write to MSR
* @msr: model specific register number
*
* Write a 64-bit value to a Model Specific Register.
*/
void wrmsr(uint32_t msr, uint64_t value)
{
uint32_t low = (uint32_t)(value & 0xFFFFFFFF);
uint32_t high = (uint32_t)(value >> 32);
__asm__ volatile("wrmsr" : : "c"(msr), "a"(low), "d"(high) : "memory");
}
/*
* x86_has_msr - Test for MSR support
*
* Checks if CPU supports Model Specific Registers
* using CPUID.01h:EDX[bit 5].
*
* Return:
* true - MSR are supported
* false - MSR are not supported
*/
bool x86_has_msr()
{
uint32_t eax, ebx, ecx, edx;
cpuid(1, &eax, &ebx, &ecx, &edx);
return (edx & (1 << 5)) != 0;
}

42
src/boot/boot.c Normal file
View File

@@ -0,0 +1,42 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Limine requests for boot
* @description
* The kernel makes a few requests to the Limine bootloader
* in order to get precious information about the system.
* We get a framebuffer, a memory map, the address of the
* kernel in memory, and the Higher Half Direct Map offset.
* @license GPL-3.0-only
*/
#include <limine.h>
__attribute__((used, section(".limine_requests")))
volatile struct limine_framebuffer_request framebuffer_request = {
.id = LIMINE_FRAMEBUFFER_REQUEST,
.revision = 0
};
__attribute__((used, section(".limine_requests")))
volatile struct limine_memmap_request memmap_request = {
.id = LIMINE_MEMMAP_REQUEST,
.revision = 0
};
__attribute__((used, section(".limine_requests")))
volatile struct limine_hhdm_request hhdm_request = {
.id = LIMINE_HHDM_REQUEST,
.revision = 0
};
__attribute__((used, section(".limine_requests")))
volatile struct limine_kernel_address_request kerneladdr_request = {
.id = LIMINE_KERNEL_ADDRESS_REQUEST,
.revision = 0
};
__attribute__((used, section(".limine_requests_start")))
volatile LIMINE_REQUESTS_START_MARKER;
__attribute__((used, section(".limine_requests_end")))
volatile LIMINE_REQUESTS_END_MARKER;

79
src/debug/misc.c Normal file
View File

@@ -0,0 +1,79 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Miscellaneous debug features
* @license GPL-3.0-only
*/
#include <kernel.h>
#include <limine.h>
#include <string/string.h>
#include <stddef.h>
extern struct boot_context boot_ctx;
/*
* memmap_display - displays a memory map
* @response: Limine memory map response
*
* Displays the memory map we get from Limine
* to see different regions, their sizes, and
* how the memory is laid out at handoff.
*/
void memmap_display(struct limine_memmap_response* response)
{
DEBUG("Got memory map from Limine: revision %u, %u entries", response->revision, response->entry_count);
for (size_t i=0; i<response->entry_count; i++) {
struct limine_memmap_entry* entry = response->entries[i];
char type[32] = {0};
switch(entry->type) {
case LIMINE_MEMMAP_USABLE:
strcpy(type, "USABLE");
break;
case LIMINE_MEMMAP_RESERVED:
strcpy(type, "RESERVED");
break;
case LIMINE_MEMMAP_ACPI_RECLAIMABLE:
strcpy(type, "ACPI_RECLAIMABLE");
break;
case LIMINE_MEMMAP_ACPI_NVS:
strcpy(type, "ACPI_NVS");
break;
case LIMINE_MEMMAP_BAD_MEMORY:
strcpy(type, "BAD_MEMORY");
break;
case LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE:
strcpy(type, "BOOTLOADER_RECLAIMABLE");
break;
case LIMINE_MEMMAP_KERNEL_AND_MODULES:
strcpy(type, "KERNEL_AND_MODULES");
break;
case LIMINE_MEMMAP_FRAMEBUFFER:
strcpy(type, "FRAMEBUFFER");
break;
default:
strcpy(type, "UNKNOWN");
break;
}
DEBUG("Entry %02u: [0x%016x | %016u bytes] - %s", i, entry->base, entry->length, type);
}
}
/*
* hhdm_display - displays the HHDM offset
* @hhdm: Limine HHDM offset response
*/
void hhdm_display(struct limine_hhdm_response* hhdm)
{
DEBUG("Got HHDM revision=%u offset=0x%p", hhdm->revision, hhdm->offset);
}
/*
* boot_mem_display - displays all memory info
*/
void boot_mem_display()
{
memmap_display(boot_ctx.mmap);
hhdm_display(boot_ctx.hhdm);
DEBUG("Kernel is at phys_base=0x%p virt_base=0x%p", boot_ctx.kaddr->physical_base, boot_ctx.kaddr->virtual_base);
}

71
src/debug/panic.c Normal file
View File

@@ -0,0 +1,71 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Kernel panic
* @license GPL-3.0-only
*/
#include <stddef.h>
#include <arch/x86.h>
#include <io/serial/serial.h>
#include <kernel.h>
extern struct init_status init;
extern int panic_count;
/*
* reaf_rflags - provide easy reading of the RFLAGS register
* @rflags: RFLAGS register value
*/
void read_rflags(uint64_t rflags)
{
DEBUG("\x1b[38;5;226m%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\x1b[38;5;231m",
CHECK_BIT(rflags, 0) ? "CF " : "", /*carry flag*/
CHECK_BIT(rflags, 2) ? "PF " : "", /*parity flag*/
CHECK_BIT(rflags, 4) ? "AF " : "", /*auxiliary carry flag*/
CHECK_BIT(rflags, 6) ? "ZF " : "", /*zero flag*/
CHECK_BIT(rflags, 7) ? "SF " : "", /*sign flag*/
CHECK_BIT(rflags, 8) ? "TF " : "", /*trap flag*/
CHECK_BIT(rflags, 9) ? "IF " : "", /*interrupt enable flag*/
CHECK_BIT(rflags, 10) ? "DF " : "", /*direction flag*/
CHECK_BIT(rflags, 11) ? "OF " : "", /*overflow flag*/
(CHECK_BIT(rflags, 12) && CHECK_BIT(rflags, 13)) ? "IOPL3 " : "IOPL0 ", /*io privilege lvl*/
CHECK_BIT(rflags, 14) ? "NT " : "", /*nested task*/
CHECK_BIT(rflags, 16) ? "RF " : "", /*resume flag*/
CHECK_BIT(rflags, 17) ? "VM " : "", /*virtual 8086 mode*/
CHECK_BIT(rflags, 18) ? "AC " : "", /*alignment check/access control*/
CHECK_BIT(rflags, 19) ? "VIF " : "", /*virtual interrupt flag*/
CHECK_BIT(rflags, 20) ? "VIP " : "", /*virtual interrupt pending*/
CHECK_BIT(rflags, 21) ? "ID " : ""); /*id flag*/
}
/*
* panic - Kernel panic
* @ctx: CPU context (optional)
* @str: Error message
*
* Ends execution of the kernel in case of an unrecoverable error.
* Will display to terminal if it is initialized, otherwise serial only.
* Can be called with or without a CPU context.
*/
void panic(struct cpu_status_t* ctx, const char* str)
{
CLEAR_INTERRUPTS;
panic_count += 1;
if (ctx == NULL) {
printf("\r\n\x1b[38;5;231m\x1b[48;5;196mKernel panic!!!\x1b[48;5;232m Something went horribly wrong! (no cpu ctx)");
printf("\r\n%s\r\n\x1b[38;5;231m\x1b[0m", str);
debug_stack_trace(100);
hcf();
}
printf("\r\n\x1b[38;5;231m\x1b[48;5;196mKernel panic!!!\x1b[48;5;232mat rip=%p\r\nSomething went horribly wrong! (%s) vect=0x%.2x errcode=0x%x\n\rrax=%p rbx=%p rcx=%p rdx=%p\n\rrsi=%p rdi=%p r8=%p r9=%p\n\rr10=%p r11=%p r12=%p r13=%p\n\rr14=%p r15=%p\n\n\rflags=%p ",
ctx->iret_rip,
str,
ctx->vector_number, ctx->error_code, ctx->rax, ctx->rbx, ctx->rcx, ctx->rdx, ctx->rsi, ctx->rdi,
ctx->r8, ctx->r9, ctx->r10, ctx->r11, ctx->r12, ctx->r13, ctx->r14, ctx->r15, ctx->iret_flags);
read_rflags(ctx->iret_flags);
debug_stack_trace(100);
hcf();
}

96
src/debug/stacktrace.c Normal file
View File

@@ -0,0 +1,96 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Stack trace tools
* @license GPL-3.0-only
*/
#include <stdint.h>
#include <kernel.h>
#include <stddef.h>
extern struct init_status init;
/*
* debug_stack_trace - Prints the stack trace
* @max_frames: Maximum amount of stack frames to walk
*
* Walks back the stack and gets all return values (RIP)
* and prints them to the DEBUG interface.
*/
void debug_stack_trace(unsigned int max_frames)
{
printf("\r\n\x1b[48;5;232m\x1b[38;5;231m*** begin stack trace ***\r\n");
// Thanks GCC :)
uintptr_t* rbp = (uintptr_t*)__builtin_frame_address(0);
for (unsigned int frame=0; frame<max_frames && rbp != NULL; frame++) {
// Return address, 1 word above saved rbp
uintptr_t rip = rbp[1];
uintptr_t offset = 0;
const char* name = debug_find_symbol(rip, &offset);
printf("[%u] <0x%p> (%s+0x%x)\r\n", frame, (void*)rip, name, offset);
uintptr_t* next_rbp = (uintptr_t*)rbp[0];
// Invalid rbp or we're at the end
if (next_rbp <= rbp || next_rbp == NULL) {
break;
}
rbp = next_rbp;
}
printf("*** end stack trace ***\r\n[end Kernel panic]\r\nHalting system...\x1b[0m");
}
typedef struct {
uint64_t addr;
const char *name;
} __attribute__((packed)) kernel_symbol_t;
__attribute__((weak)) extern kernel_symbol_t symbol_table[];
__attribute__((weak)) extern uint64_t symbol_count;
/*
* debug_find_symbol - Finds the symbol name associated to an address
* @rip: Pointer to executable code
* @offset: Out pointer to reference the offset in the found function, if any
*
* Return:
* <symbol name> - symbol name
* "???" - no symbol table found
* "unknown" - symbol table found, but address isn't in the table
*/
const char* debug_find_symbol(uintptr_t rip, uintptr_t* offset)
{
if (!symbol_table || symbol_count == 0) {
if (offset) *offset = 0;
return "???";
}
int low = 0, high = (int)symbol_count - 1;
int best = -1;
while (low <= high) {
int mid = (low + high) / 2;
if (symbol_table[mid].addr <= rip) {
best = mid;
low = mid + 1;
} else {
high = mid - 1;
}
}
if (best != -1) {
if (offset) {
*offset = rip - symbol_table[best].addr;
}
return symbol_table[best].name;
}
if (offset) {
*offset = 0;
}
return "unknown";
}

View File

@@ -1,143 +0,0 @@
#include "idt.h"
#include <stdint.h>
#include <stddef.h>
#include "../io/serial.h"
#include "../kbd/ps2.h"
struct interrupt_descriptor idt[256];
struct idtr idt_reg;
// Address to our first interrupt handler
extern char vector_0_handler[];
// Timer ticks
extern uint64_t ticks;
void idt_set_entry(uint8_t vector, void* handler, uint8_t dpl)
{
uint64_t handler_addr = (uint64_t)handler;
struct interrupt_descriptor* entry = &idt[vector];
// Address is split in three parts so we right-shift progressively to get it all
entry->address_low = handler_addr & 0xFFFF;
entry->address_mid = (handler_addr >> 16) & 0xFFFF;
entry->address_high = handler_addr >> 32;
// Kernel code selector (as set in GDT)
entry->selector = 0x8;
// Interrupt gate, present, DPL (having: max DPL = 3)
entry->flags = 0b1110 | ((dpl & 0b11) << 5) | (1 << 7);
// We won't use IST for now
entry->ist = 0;
}
void idt_load(void* idt_addr)
{
// "limit" = "size" = Size of the IDT - 1 byte = (16*256)-1 = 0xFFF
idt_reg.limit = 0xFFF;
idt_reg.base = (uint64_t)idt_addr;
asm volatile("lidt %0" :: "m"(idt_reg));
}
void idt_init()
{
// We set 256 entries, but we have only the first few stubs.
// Undefined behavior?
for (size_t i=0; i<256; i++)
{
// Each vector handler is 16-byte aligned, so <vector_no>*16 = address of that handler
idt_set_entry(i, vector_0_handler + (i*16), 0);
}
idt_load(&idt);
serial_kputs("kernel: idt: Initialized IDT!\n");
}
struct cpu_status_t* interrupt_dispatch(struct cpu_status_t* context)
{
switch(context->vector_number)
{
case 0:
serial_kputs("kernel: idt: Divide Error!\n");
break;
case 1:
serial_kputs("kernel: idt: Debug Exception!\n");
break;
case 2:
serial_kputs("kernel: idt: NMI Interrupt!\n");
break;
case 3:
serial_kputs("kernel: idt: Breakpoint Interrupt!\n");
break;
case 4:
serial_kputs("kernel: idt: Overflow Trap!\n");
break;
case 5:
serial_kputs("kernel: idt: BOUND Range Exceeded!\n");
break;
case 6:
serial_kputs("kernel: idt: Invalid Opcode!\n");
break;
case 7:
serial_kputs("kernel: idt: Device Not Available!\n");
break;
case 8:
serial_kputs("kernel: idt: Double Fault!\n");
break;
case 9:
serial_kputs("kernel: idt: Coprocessor Segment Overrun!\n");
break;
case 10:
serial_kputs("kernel: idt: Invalid TSS!\n");
break;
case 11:
serial_kputs("kernel: idt: Segment Not Present!\n");
break;
case 12:
serial_kputs("kernel: idt: Stack-Segment Fault!\n");
break;
case 13:
serial_kputs("kernel: idt: General Protection Fault!\n");
break;
case 14:
serial_kputs("kernel: idt: Page Fault!\n");
break;
case 15:
serial_kputs("kernel: idt: Intel Reserved Interrupt! (Achievement unlocked: How Did We Get Here?)\n");
break;
case 16:
serial_kputs("kernel: idt: x87 Floating-Point Error!\n");
break;
case 17:
serial_kputs("kernel: idt: Alignment Check Fault!\n");
break;
case 18:
serial_kputs("kernel: idt: Machine Check!\n");
break;
case 19:
serial_kputs("kernel: idt: SIMD Floating-Point Exception!\n");
break;
case 20:
serial_kputs("kernel: idt: Virtualization Exception!\n");
break;
case 21:
serial_kputs("kernel: idt: Control Protection Exception!\n");
break;
case 32:
//serial_kputs("Tick!");
ticks++;
// Send an EOI so that we can continue having interrupts
outb(0x20, 0x20);
break;
case 33:
keyboard_handler();
break;
default:
serial_kputs("kernel: idt: Unexpected interrupt\n");
break;
}
return context;
}

View File

@@ -1,10 +1,16 @@
// PS/2 Keyboard support
/*
* @author xamidev <xamidev@riseup.net>
* @brief PS/2 Keyboard driver
* @license GPL-3.0-only
*/
#include "../io/serial.h"
#include "../io/printf.h"
#include "ps2.h"
#include "config.h"
#include <io/serial/serial.h>
#include <io/kbd/ps2.h>
#include <stdint.h>
#include "../io/term.h"
#include <io/term/term.h>
#include <kernel.h>
#include <stddef.h>
// The key status bitfield will be used to see if ALT, CONTROL, or SHIFT is pressed
uint8_t key_status = 0b00000000;
@@ -13,6 +19,13 @@ uint8_t key_status = 0b00000000;
unsigned char* keymap;
unsigned char* keymap_shifted;
// Keyboard buffer
char keyboard_buffer[KBD_BUFFER_MAX] = {0};
int write_index = 0;
int read_index = 0;
extern struct init_status init;
unsigned char kbdus[128] =
{
0, 27, '1', '2', '3', '4', '5', '6', '7', '8', /* 9 */
@@ -150,16 +163,22 @@ unsigned char kbdfr_shifted[128] =
0
};
/*
* keyboard_handler - Keyboard event handler
*
* Is called from the interrupt dispatcher.
* When a key is pressed or released, we get a scancode, and
* it is then translated to an ASCII character.
* Left Shift, Ctrl, and Alt keys are also taken into consideration.
*/
void keyboard_handler()
{
unsigned char scancode = inb(0x60);
// Key release (bit 7 set)
if (scancode & 0x80)
{
if (scancode & 0x80) {
unsigned char code = scancode & 0x7F;
switch (code)
{
switch (code) {
// Clear the corresponding bit if corresponding key is released
case LEFT_SHIFT_PRESSED:
case RIGHT_SHIFT_PRESSED:
@@ -172,16 +191,10 @@ void keyboard_handler()
key_status &= ~ALT_PRESSED_BIT;
break;
}
// Send EOI
outb(0x20, 0x20);
return;
}
else
{
} else {
// Key press
switch (scancode)
{
switch (scancode) {
// Set bits for corresponding special key press
case LEFT_SHIFT_PRESSED:
case RIGHT_SHIFT_PRESSED:
@@ -196,30 +209,110 @@ void keyboard_handler()
default:
{
// Avoiding buffer overflow from extended keys lol
if (scancode < 128) {
// Should we get a SHIFTED char or a regular one?
unsigned char c = (key_status & SHIFT_PRESSED_BIT) ? keymap_shifted[scancode] : keymap[scancode];
if (c)
if (c) {
if (c == '\n') {
internal_putc('\r', NULL);
}
internal_putc(c, NULL);
keyboard_putchar(c);
}
}
}
}
}
}
/*
* keyboard_getchar - Get a character from keyboard
*
* This function reads one character from the keyboard buffer.
* If the keyboard buffer is empty, it will block until a key
* is pressed.
*
* Return:
* <char> - character from keyboard buffer
*/
char keyboard_getchar()
{
putchar(c);
}
}
while (read_index == write_index); // Empty buffer
char c = keyboard_buffer[read_index];
read_index = (read_index+1) % KBD_BUFFER_MAX;
return c;
}
serial_kputs("key pressed!\n");
/*
* keyboard_putchar - Puts a character in the keyboard buffer
* @c: character to add
*
* This function is used in the keyboard handler to add new
* characters to the keyboard buffer.
*
* Return:
* %-1 - keyboard buffer is full
* %0 - operation completed successfully
*/
int keyboard_putchar(char c)
{
if ((write_index+1) % KBD_BUFFER_MAX == read_index) {
// Full buffer
return -1;
}
// End of Interrupt (to master PIC)
outb(0x20, 0x20);
keyboard_buffer[write_index] = c;
write_index = (write_index+1) % KBD_BUFFER_MAX;
return 0;
}
/*
* keyboard_getline - Gets a line of input from keyboard
* @output: Output string
* @size: Size of output string
*
* Read a line of characters from the keyboard, until the
* buffer fills or a newline character is read.
* The output string is NULL-terminated.
*
* Return:
* <num> - the number of characters read
*/
int keyboard_getline(char* output, size_t size)
{
char c;
size_t index = 0;
// Read until Enter is pressed
while ((c = keyboard_getchar()) != 0x0A) {
if (index == size-1) {
output[index] = c;
output[index+1] = '\0';
return index;
}
output[index] = c;
index++;
}
output[index+1] = '\0';
return index;
}
/*
* keyboard_init - Keyboard initialization
* @layout: Desired layout
*
* Prepares the PS/2 keyboard to recieve input.
*/
void keyboard_init(unsigned char layout)
{
// Here we might go and select PS/2, USB, or other... (once we implement multiple keyboard protocols)
// Keyboard layout selection
switch (layout)
{
switch (layout) {
case US:
keymap = kbdus;
keymap_shifted = kbdus_shifted;
@@ -230,7 +323,20 @@ void keyboard_init(unsigned char layout)
break;
default:
serial_kputs("Unsupported layout.");
break;
panic(NULL, "Unsupported keyboard layout");
return;
}
// Flush keyboard buffer
while (inb(0x64) & 1) {
inb(0x60);
}
// Unmask IRQ1
uint8_t mask = inb(0x21);
mask &= ~(1 << 1);
outb(0x21, mask);
DEBUG("PS/2 Keyboard initialized");
init.keyboard = true;
}

View File

@@ -1,914 +0,0 @@
///////////////////////////////////////////////////////////////////////////////
// \author (c) Marco Paland (info@paland.com)
// 2014-2019, PALANDesign Hannover, Germany
//
// \license The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on
// embedded systems with a very limited resources. These routines are thread
// safe and reentrant!
// Use this instead of the bloated standard/newlib printf cause these use
// malloc for printf (and may not be thread safe).
//
///////////////////////////////////////////////////////////////////////////////
#include <stdbool.h>
#include <stdint.h>
#include "printf.h"
// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the
// printf_config.h header file
// default: undefined
#ifdef PRINTF_INCLUDE_CONFIG_H
#include "printf_config.h"
#endif
// 'ntoa' conversion buffer size, this must be big enough to hold one converted
// numeric number including padded zeros (dynamically created on stack)
// default: 32 byte
#ifndef PRINTF_NTOA_BUFFER_SIZE
#define PRINTF_NTOA_BUFFER_SIZE 32U
#endif
// 'ftoa' conversion buffer size, this must be big enough to hold one converted
// float number including padded zeros (dynamically created on stack)
// default: 32 byte
#ifndef PRINTF_FTOA_BUFFER_SIZE
#define PRINTF_FTOA_BUFFER_SIZE 32U
#endif
// support for the floating point type (%f)
// default: activated
#ifndef PRINTF_DISABLE_SUPPORT_FLOAT
#define PRINTF_SUPPORT_FLOAT
#endif
// support for exponential floating point notation (%e/%g)
// default: activated
#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL
#define PRINTF_SUPPORT_EXPONENTIAL
#endif
// define the default floating point precision
// default: 6 digits
#ifndef PRINTF_DEFAULT_FLOAT_PRECISION
#define PRINTF_DEFAULT_FLOAT_PRECISION 6U
#endif
// define the largest float suitable to print with %f
// default: 1e9
#ifndef PRINTF_MAX_FLOAT
#define PRINTF_MAX_FLOAT 1e9
#endif
// support for the long long types (%llu or %p)
// default: activated
#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG
#define PRINTF_SUPPORT_LONG_LONG
#endif
// support for the ptrdiff_t type (%t)
// ptrdiff_t is normally defined in <stddef.h> as long or long long type
// default: activated
#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T
#define PRINTF_SUPPORT_PTRDIFF_T
#endif
///////////////////////////////////////////////////////////////////////////////
// internal flag definitions
#define FLAGS_ZEROPAD (1U << 0U)
#define FLAGS_LEFT (1U << 1U)
#define FLAGS_PLUS (1U << 2U)
#define FLAGS_SPACE (1U << 3U)
#define FLAGS_HASH (1U << 4U)
#define FLAGS_UPPERCASE (1U << 5U)
#define FLAGS_CHAR (1U << 6U)
#define FLAGS_SHORT (1U << 7U)
#define FLAGS_LONG (1U << 8U)
#define FLAGS_LONG_LONG (1U << 9U)
#define FLAGS_PRECISION (1U << 10U)
#define FLAGS_ADAPT_EXP (1U << 11U)
// import float.h for DBL_MAX
#if defined(PRINTF_SUPPORT_FLOAT)
#include <float.h>
#endif
// output function type
typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen);
// wrapper (used as buffer) for output function type
typedef struct {
void (*fct)(char character, void* arg);
void* arg;
} out_fct_wrap_type;
// internal buffer output
static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen)
{
if (idx < maxlen) {
((char*)buffer)[idx] = character;
}
}
// internal null output
static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen)
{
(void)character; (void)buffer; (void)idx; (void)maxlen;
}
// internal _putchar wrapper
static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen)
{
(void)buffer; (void)idx; (void)maxlen;
if (character) {
_putchar(character);
}
}
// internal output function wrapper
static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen)
{
(void)idx; (void)maxlen;
if (character) {
// buffer is the output fct pointer
((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg);
}
}
// internal secure strlen
// \return The length of the string (excluding the terminating 0) limited by 'maxsize'
static inline unsigned int _strnlen_s(const char* str, size_t maxsize)
{
const char* s;
for (s = str; *s && maxsize--; ++s);
return (unsigned int)(s - str);
}
// internal test if char is a digit (0-9)
// \return true if char is a digit
static inline bool _is_digit(char ch)
{
return (ch >= '0') && (ch <= '9');
}
// internal ASCII string to unsigned int conversion
static unsigned int _atoi(const char** str)
{
unsigned int i = 0U;
while (_is_digit(**str)) {
i = i * 10U + (unsigned int)(*((*str)++) - '0');
}
return i;
}
// output the specified string in reverse, taking care of any zero-padding
static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags)
{
const size_t start_idx = idx;
// pad spaces up to given width
if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
for (size_t i = len; i < width; i++) {
out(' ', buffer, idx++, maxlen);
}
}
// reverse string
while (len) {
out(buf[--len], buffer, idx++, maxlen);
}
// append pad spaces up to given width
if (flags & FLAGS_LEFT) {
while (idx - start_idx < width) {
out(' ', buffer, idx++, maxlen);
}
}
return idx;
}
// internal itoa format
static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags)
{
// pad leading zeros
if (!(flags & FLAGS_LEFT)) {
if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
width--;
}
while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = '0';
}
while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = '0';
}
}
// handle hash
if (flags & FLAGS_HASH) {
if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) {
len--;
if (len && (base == 16U)) {
len--;
}
}
if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = 'x';
}
else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = 'X';
}
else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = 'b';
}
if (len < PRINTF_NTOA_BUFFER_SIZE) {
buf[len++] = '0';
}
}
if (len < PRINTF_NTOA_BUFFER_SIZE) {
if (negative) {
buf[len++] = '-';
}
else if (flags & FLAGS_PLUS) {
buf[len++] = '+'; // ignore the space if the '+' exists
}
else if (flags & FLAGS_SPACE) {
buf[len++] = ' ';
}
}
return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
}
// internal itoa for 'long' type
static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags)
{
char buf[PRINTF_NTOA_BUFFER_SIZE];
size_t len = 0U;
// no hash for 0 values
if (!value) {
flags &= ~FLAGS_HASH;
}
// write if precision != 0 and value is != 0
if (!(flags & FLAGS_PRECISION) || value) {
do {
const char digit = (char)(value % base);
buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
value /= base;
} while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
}
return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
}
// internal itoa for 'long long' type
#if defined(PRINTF_SUPPORT_LONG_LONG)
static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags)
{
char buf[PRINTF_NTOA_BUFFER_SIZE];
size_t len = 0U;
// no hash for 0 values
if (!value) {
flags &= ~FLAGS_HASH;
}
// write if precision != 0 and value is != 0
if (!(flags & FLAGS_PRECISION) || value) {
do {
const char digit = (char)(value % base);
buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
value /= base;
} while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
}
return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
}
#endif // PRINTF_SUPPORT_LONG_LONG
#if defined(PRINTF_SUPPORT_FLOAT)
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT
static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags);
#endif
// internal ftoa for fixed decimal floating point
static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
{
char buf[PRINTF_FTOA_BUFFER_SIZE];
size_t len = 0U;
double diff = 0.0;
// powers of 10
static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
// test for special values
if (value != value)
return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags);
if (value < -DBL_MAX)
return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags);
if (value > DBL_MAX)
return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags);
// test for very large values
// standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad
if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) {
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
return _etoa(out, buffer, idx, maxlen, value, prec, width, flags);
#else
return 0U;
#endif
}
// test for negative
bool negative = false;
if (value < 0) {
negative = true;
value = 0 - value;
}
// set default precision, if not set explicitly
if (!(flags & FLAGS_PRECISION)) {
prec = PRINTF_DEFAULT_FLOAT_PRECISION;
}
// limit precision to 9, cause a prec >= 10 can lead to overflow errors
while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) {
buf[len++] = '0';
prec--;
}
int whole = (int)value;
double tmp = (value - whole) * pow10[prec];
unsigned long frac = (unsigned long)tmp;
diff = tmp - frac;
if (diff > 0.5) {
++frac;
// handle rollover, e.g. case 0.99 with prec 1 is 1.0
if (frac >= pow10[prec]) {
frac = 0;
++whole;
}
}
else if (diff < 0.5) {
}
else if ((frac == 0U) || (frac & 1U)) {
// if halfway, round up if odd OR if last digit is 0
++frac;
}
if (prec == 0U) {
diff = value - (double)whole;
if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) {
// exactly 0.5 and ODD, then round up
// 1.5 -> 2, but 2.5 -> 2
++whole;
}
}
else {
unsigned int count = prec;
// now do fractional part, as an unsigned number
while (len < PRINTF_FTOA_BUFFER_SIZE) {
--count;
buf[len++] = (char)(48U + (frac % 10U));
if (!(frac /= 10U)) {
break;
}
}
// add extra 0s
while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) {
buf[len++] = '0';
}
if (len < PRINTF_FTOA_BUFFER_SIZE) {
// add decimal
buf[len++] = '.';
}
}
// do whole part, number is reversed
while (len < PRINTF_FTOA_BUFFER_SIZE) {
buf[len++] = (char)(48 + (whole % 10));
if (!(whole /= 10)) {
break;
}
}
// pad leading zeros
if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {
if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
width--;
}
while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) {
buf[len++] = '0';
}
}
if (len < PRINTF_FTOA_BUFFER_SIZE) {
if (negative) {
buf[len++] = '-';
}
else if (flags & FLAGS_PLUS) {
buf[len++] = '+'; // ignore the space if the '+' exists
}
else if (flags & FLAGS_SPACE) {
buf[len++] = ' ';
}
}
return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
}
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse <m.jasperse@gmail.com>
static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
{
// check for NaN and special values
if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) {
return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags);
}
// determine the sign
const bool negative = value < 0;
if (negative) {
value = -value;
}
// default precision
if (!(flags & FLAGS_PRECISION)) {
prec = PRINTF_DEFAULT_FLOAT_PRECISION;
}
// determine the decimal exponent
// based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)
union {
uint64_t U;
double F;
} conv;
conv.F = value;
int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2
conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2)
// now approximate log10 from the log2 integer part and an expansion of ln around 1.5
int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168);
// now we want to compute 10^expval but we want to be sure it won't overflow
exp2 = (int)(expval * 3.321928094887362 + 0.5);
const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453;
const double z2 = z * z;
conv.U = (uint64_t)(exp2 + 1023) << 52U;
// compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
// correct for rounding errors
if (value < conv.F) {
expval--;
conv.F /= 10;
}
// the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters
unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U;
// in "%g" mode, "prec" is the number of *significant figures* not decimals
if (flags & FLAGS_ADAPT_EXP) {
// do we want to fall-back to "%f" mode?
if ((value >= 1e-4) && (value < 1e6)) {
if ((int)prec > expval) {
prec = (unsigned)((int)prec - expval - 1);
}
else {
prec = 0;
}
flags |= FLAGS_PRECISION; // make sure _ftoa respects precision
// no characters in exponent
minwidth = 0U;
expval = 0;
}
else {
// we use one sigfig for the whole part
if ((prec > 0) && (flags & FLAGS_PRECISION)) {
--prec;
}
}
}
// will everything fit?
unsigned int fwidth = width;
if (width > minwidth) {
// we didn't fall-back so subtract the characters required for the exponent
fwidth -= minwidth;
} else {
// not enough characters, so go back to default sizing
fwidth = 0U;
}
if ((flags & FLAGS_LEFT) && minwidth) {
// if we're padding on the right, DON'T pad the floating part
fwidth = 0U;
}
// rescale the float value
if (expval) {
value /= conv.F;
}
// output the floating part
const size_t start_idx = idx;
idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP);
// output the exponent part
if (minwidth) {
// output the exponential symbol
out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen);
// output the exponent value
idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS);
// might need to right-pad spaces
if (flags & FLAGS_LEFT) {
while (idx - start_idx < width) out(' ', buffer, idx++, maxlen);
}
}
return idx;
}
#endif // PRINTF_SUPPORT_EXPONENTIAL
#endif // PRINTF_SUPPORT_FLOAT
// internal vsnprintf
static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va)
{
unsigned int flags, width, precision, n;
size_t idx = 0U;
if (!buffer) {
// use null output function
out = _out_null;
}
while (*format)
{
// format specifier? %[flags][width][.precision][length]
if (*format != '%') {
// no
out(*format, buffer, idx++, maxlen);
format++;
continue;
}
else {
// yes, evaluate it
format++;
}
// evaluate flags
flags = 0U;
do {
switch (*format) {
case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break;
case '-': flags |= FLAGS_LEFT; format++; n = 1U; break;
case '+': flags |= FLAGS_PLUS; format++; n = 1U; break;
case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break;
case '#': flags |= FLAGS_HASH; format++; n = 1U; break;
default : n = 0U; break;
}
} while (n);
// evaluate width field
width = 0U;
if (_is_digit(*format)) {
width = _atoi(&format);
}
else if (*format == '*') {
const int w = va_arg(va, int);
if (w < 0) {
flags |= FLAGS_LEFT; // reverse padding
width = (unsigned int)-w;
}
else {
width = (unsigned int)w;
}
format++;
}
// evaluate precision field
precision = 0U;
if (*format == '.') {
flags |= FLAGS_PRECISION;
format++;
if (_is_digit(*format)) {
precision = _atoi(&format);
}
else if (*format == '*') {
const int prec = (int)va_arg(va, int);
precision = prec > 0 ? (unsigned int)prec : 0U;
format++;
}
}
// evaluate length field
switch (*format) {
case 'l' :
flags |= FLAGS_LONG;
format++;
if (*format == 'l') {
flags |= FLAGS_LONG_LONG;
format++;
}
break;
case 'h' :
flags |= FLAGS_SHORT;
format++;
if (*format == 'h') {
flags |= FLAGS_CHAR;
format++;
}
break;
#if defined(PRINTF_SUPPORT_PTRDIFF_T)
case 't' :
flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
format++;
break;
#endif
case 'j' :
flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
format++;
break;
case 'z' :
flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
format++;
break;
default :
break;
}
// evaluate specifier
switch (*format) {
case 'd' :
case 'i' :
case 'u' :
case 'x' :
case 'X' :
case 'o' :
case 'b' : {
// set the base
unsigned int base;
if (*format == 'x' || *format == 'X') {
base = 16U;
}
else if (*format == 'o') {
base = 8U;
}
else if (*format == 'b') {
base = 2U;
}
else {
base = 10U;
flags &= ~FLAGS_HASH; // no hash for dec format
}
// uppercase
if (*format == 'X') {
flags |= FLAGS_UPPERCASE;
}
// no plus or space flag for u, x, X, o, b
if ((*format != 'i') && (*format != 'd')) {
flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
}
// ignore '0' flag when precision is given
if (flags & FLAGS_PRECISION) {
flags &= ~FLAGS_ZEROPAD;
}
// convert the integer
if ((*format == 'i') || (*format == 'd')) {
// signed
if (flags & FLAGS_LONG_LONG) {
#if defined(PRINTF_SUPPORT_LONG_LONG)
const long long value = va_arg(va, long long);
idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
#endif
}
else if (flags & FLAGS_LONG) {
const long value = va_arg(va, long);
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
}
else {
const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int);
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
}
}
else {
// unsigned
if (flags & FLAGS_LONG_LONG) {
#if defined(PRINTF_SUPPORT_LONG_LONG)
idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags);
#endif
}
else if (flags & FLAGS_LONG) {
idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags);
}
else {
const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int);
idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags);
}
}
format++;
break;
}
#if defined(PRINTF_SUPPORT_FLOAT)
case 'f' :
case 'F' :
if (*format == 'F') flags |= FLAGS_UPPERCASE;
idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
format++;
break;
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
case 'e':
case 'E':
case 'g':
case 'G':
if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP;
if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE;
idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
format++;
break;
#endif // PRINTF_SUPPORT_EXPONENTIAL
#endif // PRINTF_SUPPORT_FLOAT
case 'c' : {
unsigned int l = 1U;
// pre padding
if (!(flags & FLAGS_LEFT)) {
while (l++ < width) {
out(' ', buffer, idx++, maxlen);
}
}
// char output
out((char)va_arg(va, int), buffer, idx++, maxlen);
// post padding
if (flags & FLAGS_LEFT) {
while (l++ < width) {
out(' ', buffer, idx++, maxlen);
}
}
format++;
break;
}
case 's' : {
const char* p = va_arg(va, char*);
unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1);
// pre padding
if (flags & FLAGS_PRECISION) {
l = (l < precision ? l : precision);
}
if (!(flags & FLAGS_LEFT)) {
while (l++ < width) {
out(' ', buffer, idx++, maxlen);
}
}
// string output
while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) {
out(*(p++), buffer, idx++, maxlen);
}
// post padding
if (flags & FLAGS_LEFT) {
while (l++ < width) {
out(' ', buffer, idx++, maxlen);
}
}
format++;
break;
}
case 'p' : {
width = sizeof(void*) * 2U;
flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE;
#if defined(PRINTF_SUPPORT_LONG_LONG)
const bool is_ll = sizeof(uintptr_t) == sizeof(long long);
if (is_ll) {
idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags);
}
else {
#endif
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags);
#if defined(PRINTF_SUPPORT_LONG_LONG)
}
#endif
format++;
break;
}
case '%' :
out('%', buffer, idx++, maxlen);
format++;
break;
default :
out(*format, buffer, idx++, maxlen);
format++;
break;
}
}
// termination
out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
// return written chars without terminating \0
return (int)idx;
}
///////////////////////////////////////////////////////////////////////////////
int printf_(const char* format, ...)
{
va_list va;
va_start(va, format);
char buffer[1];
const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
va_end(va);
return ret;
}
int sprintf_(char* buffer, const char* format, ...)
{
va_list va;
va_start(va, format);
const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va);
va_end(va);
return ret;
}
int snprintf_(char* buffer, size_t count, const char* format, ...)
{
va_list va;
va_start(va, format);
const int ret = _vsnprintf(_out_buffer, buffer, count, format, va);
va_end(va);
return ret;
}
int vprintf_(const char* format, va_list va)
{
char buffer[1];
return _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
}
int vsnprintf_(char* buffer, size_t count, const char* format, va_list va)
{
return _vsnprintf(_out_buffer, buffer, count, format, va);
}
int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...)
{
va_list va;
va_start(va, format);
const out_fct_wrap_type out_fct_wrap = { out, arg };
const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va);
va_end(va);
return ret;
}

View File

@@ -1,117 +0,0 @@
///////////////////////////////////////////////////////////////////////////////
// \author (c) Marco Paland (info@paland.com)
// 2014-2019, PALANDesign Hannover, Germany
//
// \license The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on
// embedded systems with a very limited resources.
// Use this instead of bloated standard/newlib printf.
// These routines are thread safe and reentrant.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef _PRINTF_H_
#define _PRINTF_H_
#include <stdarg.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Output a character to a custom device like UART, used by the printf() function
* This function is declared here only. You have to write your custom implementation somewhere
* \param character Character to output
*/
void _putchar(char character);
/**
* Tiny printf implementation
* You have to implement _putchar if you use printf()
* To avoid conflicts with the regular printf() API it is overridden by macro defines
* and internal underscore-appended functions like printf_() are used
* \param format A string that specifies the format of the output
* \return The number of characters that are written into the array, not counting the terminating null character
*/
#define printf printf_
int printf_(const char* format, ...);
/**
* Tiny sprintf implementation
* Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD!
* \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output!
* \param format A string that specifies the format of the output
* \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
*/
#define sprintf sprintf_
int sprintf_(char* buffer, const char* format, ...);
/**
* Tiny snprintf/vsnprintf implementation
* \param buffer A pointer to the buffer where to store the formatted string
* \param count The maximum number of characters to store in the buffer, including a terminating null character
* \param format A string that specifies the format of the output
* \param va A value identifying a variable arguments list
* \return The number of characters that COULD have been written into the buffer, not counting the terminating
* null character. A value equal or larger than count indicates truncation. Only when the returned value
* is non-negative and less than count, the string has been completely written.
*/
#define snprintf snprintf_
#define vsnprintf vsnprintf_
int snprintf_(char* buffer, size_t count, const char* format, ...);
int vsnprintf_(char* buffer, size_t count, const char* format, va_list va);
/**
* Tiny vprintf implementation
* \param format A string that specifies the format of the output
* \param va A value identifying a variable arguments list
* \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
*/
#define vprintf vprintf_
int vprintf_(const char* format, va_list va);
/**
* printf with output function
* You may use this as dynamic alternative to printf() with its fixed _putchar() output
* \param out An output function which takes one character and an argument pointer
* \param arg An argument pointer for user data passed to output function
* \param format A string that specifies the format of the output
* \return The number of characters that are sent to the output function, not counting the terminating null character
*/
int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...);
#ifdef __cplusplus
}
#endif
#endif // _PRINTF_H_

View File

@@ -1,62 +0,0 @@
#include "../kernel.h"
#include "serial.h"
void outb(int port, unsigned char data)
{
__asm__ __volatile__("outb %%al, %%dx" :: "a" (data),"d" (port));
}
unsigned char inb(int port)
{
unsigned char data = 0;
__asm__ __volatile__("inb %%dx, %%al" : "=a" (data) : "d" (port));
return data;
}
// COM1
#define PORT 0x3F8
int serial_init()
{
outb(PORT + 1, 0x00); // Disable all interrupts
outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor)
outb(PORT + 0, 0x03); // Set divisor to 3 (lo byte) 38400 baud
outb(PORT + 1, 0x00); // (hi byte)
outb(PORT + 3, 0x03); // 8 bits, no parity, one stop bit
outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold
outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set
outb(PORT + 4, 0x1E); // Set in loopback mode, test the serial chip
outb(PORT + 0, 0xAE); // Test serial chip (send byte 0xAE and check if serial returns same byte)
if (inb(PORT) != 0xAE)
{
return -EIO;
}
// Set normal operation mode
outb(PORT + 4, 0x0F);
serial_kputs("\n\nkernel: serial: Serial initialization OK!\n");
return 0;
}
static int is_transmit_empty()
{
return inb(PORT + 5) & 0x20;
}
void write_serial(char c)
{
while (!is_transmit_empty()); // wait for free spot
outb(PORT, c);
}
void serial_kputs(const char* str)
{
unsigned int i=0;
while (str[i])
{
write_serial(str[i]);
i++;
}
}

View File

@@ -1,10 +0,0 @@
#ifndef SERIAL_H
#define SERIAL_H
void outb(int port, unsigned char data);
unsigned char inb(int port);
int serial_init();
void serial_kputs(const char* str);
#endif

116
src/io/serial/serial.c Normal file
View File

@@ -0,0 +1,116 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Debug serial driver
* @license GPL-3.0-only
*/
#include <kernel.h>
#include <io/serial/serial.h>
#include <sched/spinlock.h>
extern struct init_status init;
extern int panic_count;
struct spinlock_t serial_lock = {0};
/*
* outb - Writes a byte to a CPU port
* @port: CPU port to write to
* @data: Byte to write
*
* Writes a single byte to the serial interface.
*/
void outb(int port, unsigned char data)
{
__asm__ __volatile__("outb %%al, %%dx" :: "a" (data),"d" (port));
}
/*
* inb - Gets a byte in through a CPU port
* @port: The CPU port to get a byte from
*
* Return:
* <data> - byte got from port
*/
unsigned char inb(int port)
{
unsigned char data = 0;
__asm__ __volatile__("inb %%dx, %%al" : "=a" (data) : "d" (port));
return data;
}
/*
* serial_init - Initializes serial interface
*
* Return:
* %-EIO - Input/output error
* %0 - Success
*/
int serial_init()
{
outb(PORT + 1, 0x00); // Disable all interrupts
outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor)
outb(PORT + 0, 0x03); // Set divisor to 3 (lo byte) 38400 baud
outb(PORT + 1, 0x00); // (hi byte)
outb(PORT + 3, 0x03); // 8 bits, no parity, one stop bit
outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold
outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set
outb(PORT + 4, 0x1E); // Set in loopback mode, test the serial chip
outb(PORT + 0, 0xAE); // Test serial chip (send byte 0xAE and check if serial returns same byte)
if (inb(PORT) != 0xAE) {
return -EIO;
}
// Set normal operation mode
outb(PORT + 4, 0x0F);
init.serial = true;
DEBUG("*** Welcome to PepperOS! (built @ %s %s) ***", __DATE__, __TIME__);
return 0;
}
/*
* is_transmit_empty - Check if the serial transmit register is empty
*
* Return: Non-zero if the transmit register is empty and a new
* byte can be written to the serial port, 0 otherwise.
*/
static int is_transmit_empty()
{
return inb(PORT + 5) & 0x20;
}
/*
* skputc - Serial kernel putchar
* @c: character to write
*
* Writes a single character to the serial interface.
*/
void skputc(char c)
{
if (panic_count == 0) {
spinlock_acquire(&serial_lock);
while (!is_transmit_empty()); // wait for free spot
outb(PORT, c);
spinlock_release(&serial_lock);
} else {
while (!is_transmit_empty());
outb(PORT, c);
}
}
/*
* skputs - Serial kernel puts
* @str: Message to write
*
* Writes a non-formatted string to serial output.
*/
void skputs(const char* str)
{
unsigned int i=0;
while (str[i]) {
skputc(str[i]);
i++;
}
}

View File

@@ -1,143 +0,0 @@
// Terminal output
#include <limine.h>
#include <stddef.h>
#include "kernel.h"
#include "term.h"
extern struct limine_framebuffer* framebuffer;
// Importing the PSF object file
extern unsigned char _binary_zap_light16_psf_start[];
extern unsigned char _binary_zap_light16_psf_end[];
PSF1_Header* font = (PSF1_Header*)_binary_zap_light16_psf_start;
uint8_t* glyphs = _binary_zap_light16_psf_start + sizeof(PSF1_Header);
#define FONT_WIDTH 8
#define FONT_HEIGHT font->characterSize
// Character cursor
typedef struct
{
unsigned int x;
unsigned int y;
} Cursor;
Cursor cursor = {0};
unsigned char* fb;
int term_init()
{
// Get framebuffer address from Limine struct
if (framebuffer)
{
fb = framebuffer->address;
return 0;
}
return -ENOMEM;
}
// These are marked "static" because we don't wanna expose them all around
// AKA they should just be seen here (kind of like private functions in cpp)
static void putpixel(int x, int y, int color)
{
// Depth isn't part of limine_framebuffer attributes so it will be 4
unsigned pos = x*4 + y*framebuffer->pitch;
fb[pos] = color & 255; // blue channel
fb[pos+1] = (color >> 8) & 255; // green
fb[pos+2] = (color >> 16) & 255; // blue
}
static void draw_char(char c, int px, int py, int fg, int bg)
{
uint8_t* glyph = glyphs + ((unsigned char)c * FONT_HEIGHT);
for (size_t y=0; y<FONT_HEIGHT; y++)
{
uint8_t row = glyph[y];
for (size_t x=0; x<8; x++)
{
int color = (row & (0x80 >> x)) ? fg : bg;
putpixel(px+x, py+y, color);
}
}
}
static void erase_char(int px, int py)
{
for (size_t y=0; y<FONT_HEIGHT; y++)
{
for (size_t x=0; x<8; x++)
{
// Black
putpixel(px+x, py+y, 0);
}
}
}
void putchar(char c)
{
if (c == '\n')
{
cursor.x = 0;
cursor.y++;
return;
}
// TODO: Improperly handled.
// When we're on an empty line it should get to the upper line's last character
// NOT just the last position possible; we would need to track the last line's character amount for that
if (c == '\b')
{
if (cursor.x == 0 && cursor.y == 0)
{
// Top-left corner
return;
}
if (cursor.x == 0)
{
cursor.y--;
cursor.x = (framebuffer->width / FONT_WIDTH) -1; // here
}
else {
cursor.x--;
}
int px = cursor.x * FONT_WIDTH;
int py = cursor.y * FONT_HEIGHT;
erase_char(px, py);
return;
}
if ((cursor.x+1)*FONT_WIDTH >= framebuffer->width)
{
cursor.x = 0;
cursor.y++;
}
int px = cursor.x * FONT_WIDTH;
int py = cursor.y * FONT_HEIGHT;
draw_char(c, px, py, WHITE, BLACK);
cursor.x++;
}
// Overhead that could be avoided, right? (for printf)
void _putchar(char character)
{
putchar(character);
}
// Debug-printing
void kputs(const char* str)
{
unsigned int i=0;
while (str[i] != 0)
{
putchar(str[i]);
i++;
}
}

View File

@@ -1,23 +0,0 @@
#ifndef TERM_H
#define TERM_H
int term_init();
void kputs(const char* str);
void putchar(char c);
enum TermColors
{
BLACK = 0x000000,
WHITE = 0xffffff
};
#define PSF1_FONT_MAGIC 0x0436
typedef struct
{
uint16_t magic;
uint8_t fontMode;
uint8_t characterSize; // height
} PSF1_Header;
#endif

2129
src/io/term/flanterm.c Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

142
src/io/term/term.c Normal file
View File

@@ -0,0 +1,142 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Framebuffer-based terminal driver
* @license GPL-3.0-only
*/
// Terminal output
/*
There are a couple of bugs here and there but for now I don't care too much
because this shitty implementation will be replaced one day by Flanterm
(once memory management is okay: paging & kernel malloc)
*/
#include <stddef.h>
#include <kernel.h>
#include <io/term/term.h>
#include <config.h>
#include <io/term/flanterm.h>
#include <io/term/flanterm_backends/fb.h>
#include <mem/kheap.h>
#include <limine.h>
#include <stdarg.h>
#include <sched/spinlock.h>
#include <io/serial/serial.h>
#define NANOPRINTF_IMPLEMENTATION
#include <io/term/nanoprintf.h>
extern struct flanterm_context* ft_ctx;
extern struct init_status init;
struct spinlock_t term_lock = {0};
struct spinlock_t printf_lock = {0};
extern int panic_count;
/*
* internal_putc - Internal putchar function
* @c: char to print
* @_: (unused, for nanoprintf)
*
* Prints a character to the terminal if it's ready,
* and also to the serial interface if it's ready.
*/
void internal_putc(int c, void *_)
{
(void)_;
char ch = (char)c;
if (init.terminal) {
if (panic_count == 0) {
spinlock_acquire(&term_lock);
flanterm_write(ft_ctx, &ch, 1);
spinlock_release(&term_lock);
} else {
flanterm_write(ft_ctx, &ch, 1);
}
}
if (init.serial) {
if (ch == '\n') {
skputc('\r');
}
skputc(ch);
}
}
/*
* printf - Fromatted printing
* @fmt: format string
* @...: variadic arguments
*
* Wrapper for nanoprintf
*
* Return:
* <ret> - number of characters sent to the callback
* %-1 - error
*/
int printf(const char* fmt, ...)
{
if (panic_count == 0) {
spinlock_acquire(&printf_lock);
va_list args;
va_start(args, fmt);
int ret = npf_vpprintf(internal_putc, NULL, fmt, args);
va_end(args);
spinlock_release(&printf_lock);
return ret;
} else {
va_list args;
va_start(args, fmt);
int ret = npf_vpprintf(internal_putc, NULL, fmt, args);
va_end(args);
return ret;
}
return -1;
}
/*
* kputs - Kernel puts
* @str: String to write
*
* Writes a non-formatted string to terminal
*/
void kputs(const char* str)
{
size_t i=0;
while (str[i] != 0) {
internal_putc(str[i], NULL);
i++;
}
}
extern struct flanterm_context* ft_ctx;
extern struct boot_context boot_ctx;
/*
* term_init - Video output/terminal initialization
*
* Uses Flanterm and the framebuffer given by Limine.
*/
void term_init()
{
uint32_t bgColor = 0x252525;
ft_ctx = flanterm_fb_init(
NULL,
NULL,
boot_ctx.fb->address, boot_ctx.fb->width, boot_ctx.fb->height, boot_ctx.fb->pitch,
boot_ctx.fb->red_mask_size, boot_ctx.fb->red_mask_shift,
boot_ctx.fb->green_mask_size, boot_ctx.fb->green_mask_shift,
boot_ctx.fb->blue_mask_size, boot_ctx.fb->blue_mask_shift,
NULL,
NULL, NULL,
&bgColor, NULL,
NULL, NULL,
NULL, 0, 0, 1,
0, 0,
0,
0
);
init.terminal = true;
}

View File

@@ -1,13 +0,0 @@
#ifndef KERNEL_H
#define KERNEL_H
enum ErrorCodes
{
ENOMEM,
EIO
};
#define CLEAR_INTERRUPTS __asm__ volatile("cli")
#define SET_INTERRUPTS __asm__ volatile("sti")
#endif

View File

@@ -1,68 +1,134 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief PepperOS kernel entry point
* @license GPL-3.0-only
*/
#include "arch/x86.h"
#include <stdbool.h>
#include <stddef.h>
#include <limine.h>
#include "io/term.h"
#include "io/printf.h"
#include "io/serial.h"
#include "mem/gdt.h"
#include "mem/utils.h"
#include "idt/idt.h"
#include "kernel.h"
#include "time/timer.h"
#include "kbd/ps2.h"
#include <io/term/term.h>
#include <io/serial/serial.h>
#include <mem/gdt.h>
#include <mem/utils.h>
#include <kernel.h>
#include <time/timer.h>
#include <io/kbd/ps2.h>
#include <mem/pmm.h>
#include <mem/paging.h>
#include <mem/vmm.h>
#include <mem/kheap.h>
#include <sched/process.h>
#include <sched/scheduler.h>
#include <config.h>
#include <io/term/flanterm.h>
#include <io/term/flanterm_backends/fb.h>
#include <arch/x86.h>
// Limine version used
__attribute__((used, section(".limine_requests")))
static volatile LIMINE_BASE_REVISION(3);
volatile LIMINE_BASE_REVISION(3);
// Framebuffer request
__attribute__((used, section(".limine_requests")))
static volatile struct limine_framebuffer_request framebuffer_request = {
.id = LIMINE_FRAMEBUFFER_REQUEST,
.revision = 0
};
int panic_count = 0;
__attribute__((used, section(".limine_requests_start")))
static volatile LIMINE_REQUESTS_START_MARKER;
__attribute__((used, section(".limine_requests_end")))
static volatile LIMINE_REQUESTS_END_MARKER;
struct limine_framebuffer* framebuffer;
// Panic
static void hcf()
/*
* hcf - Halt and catch fire
*
* This function is called only in the case of an unrecoverable
* error. It halts interrupts, and stops execution. The machine
* will stay in an infinite loop state.
*/
void hcf()
{
for (;;)
CLEAR_INTERRUPTS; for (;;)asm("hlt");
}
/*
* idle - Make the machine idle
*
* When there is nothing else to do, this function
* gets called. It can be interrupted, so it allows
* the scheduler, timer, and keyboard to work.
*/
void idle() {SET_INTERRUPTS; for(;;)asm("hlt");}
struct flanterm_context *ft_ctx;
struct boot_context boot_ctx;
struct init_status init = {0};
extern volatile struct limine_framebuffer_request framebuffer_request;
extern volatile struct limine_memmap_request memmap_request;
extern volatile struct limine_hhdm_request hhdm_request;
extern volatile struct limine_kernel_address_request kerneladdr_request;
extern struct process_t* processes_list;
extern struct process_t* current_process;
struct process_t* idle_proc;
// Never gets executed although pedicel is scheduled?
void pedicel_main(void* arg)
{
printf("\n\n\rWelcome to PepperOS! Pedicel speaking.\r\nNothing left to do, let's go idle!\r\n");
}
void idle_main(void* arg)
{
for (;;) {
asm("hlt");
}
}
// This is our entry point
void thing_main(void* arg)
{
printf("What's your name, pal? ");
char name[10];
keyboard_getline(name, 10);
printf("\r\n{%s} is such a nice name!\r\n", name);
}
extern uintptr_t kheap_start;
/*
* kmain - Kernel entry point
*
* This is where execution begins at handoff from Limine.
* The function fetches all needed information from the
* bootloader, initializes all kernel modules and structures,
* and then goes in an idle state.
*/
void kmain()
{
if (!LIMINE_BASE_REVISION_SUPPORTED) hcf();
if (framebuffer_request.response == NULL || framebuffer_request.response->framebuffer_count < 1) hcf();
// Get the first framebuffer from the response
framebuffer = framebuffer_request.response->framebuffers[0];
if (term_init()) hcf();
if (serial_init()) kputs("kernel: serial: error: Cannot init serial communication!");
CLEAR_INTERRUPTS;
gdt_init();
idt_init();
if (!LIMINE_BASE_REVISION_SUPPORTED) hcf();
// Populate boot context
boot_ctx.fb = framebuffer_request.response ? framebuffer_request.response->framebuffers[0] : NULL;
boot_ctx.mmap = memmap_request.response ? memmap_request.response : NULL;
boot_ctx.hhdm = hhdm_request.response ? hhdm_request.response : NULL;
boot_ctx.kaddr = kerneladdr_request.response ? kerneladdr_request.response : NULL;
term_init();
serial_init();
timer_init();
SET_INTERRUPTS;
x86_arch_init();
boot_mem_display();
pmm_init(boot_ctx);
paging_init(boot_ctx);
kheap_init();
keyboard_init(FR);
// Draw something
printf("%s, %s!", "Hello", "world");
process_init();
idle_proc = process_create("idle", (void*)idle_main, 0);
process_create("pedicel", (void*)pedicel_main, 0);
process_create("thing", thing_main, NULL);
//printf("%d", 4/0);
hcf();
scheduler_init();
printf(PEPPEROS_SPLASH);
idle();
}

View File

@@ -1,17 +1,33 @@
#include "gdt.h"
/*
* @author xamidev <xamidev@riseup.net>
* @brief Global Descriptor Table (for legacy reasons)
* @license GPL-3.0-only
*/
#include <mem/gdt.h>
#include <stdint.h>
#include "../io/serial.h"
#include <io/serial/serial.h>
#include <kernel.h>
// Descriptors are 8-byte wide (64bits)
// So the selectors will be (in bytes): 0x0, 0x8, 0x10, 0x18, etc..
uint64_t gdt_entries[NUM_GDT_ENTRIES];
struct GDTR gdtr;
/*
* gdt_load - Loads Global Descriptor Table
*/
static void gdt_load()
{
asm("lgdt %0" : : "m"(gdtr));
}
/*
* gdt_flush - Flushes the Global Descriptor Table
*
* This function loads new Segment Selectors to make
* the GDT changes take effect
*/
static void gdt_flush()
{
// Here, 0x8 is the kernel code selector
@@ -35,6 +51,15 @@ static void gdt_flush()
);
}
/*
* gdt_init - Global Descriptor Table initialization
*
* This function loads a new GDT in the CPU.
* It contains a null descriptor, kernel code and data
* segments, and user code and data segments.
* However, we do not use segmentation to manage memory on
* 64-bit x86, as it's deprecated. Instead, we use paging.
*/
void gdt_init()
{
// Null descriptor (required)
@@ -78,5 +103,5 @@ void gdt_init()
gdt_load();
gdt_flush();
serial_kputs("kernel: gdt: Initialized GDT!\n");
DEBUG("GDT initialized");
}

160
src/mem/kheap.c Normal file
View File

@@ -0,0 +1,160 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Kernel heap
* @license GPL-3.0-only
*/
#include <mem/kheap.h>
#include <mem/paging.h>
#include <mem/pmm.h>
#include <stddef.h>
#include <kernel.h>
#include <sched/process.h>
#include <config.h>
extern uint64_t kernel_phys_base;
extern uint64_t kernel_virt_base;
uintptr_t kheap_start;
static struct heap_block_t* head = NULL;
static uintptr_t end;
// Kernel root table (level 4)
extern uint64_t *kernel_pml4;
/*
* kheap_init - Kernel heap initialization
*
* This function physically allocates and maps enough pages
* of memory for KHEAP_SIZE, which is defined in config.h.
*
* It then creates one big heap block, which will be the
* base for a linked list.
*/
void kheap_init()
{
kheap_start = ALIGN_UP(kernel_virt_base + KERNEL_SIZE, PAGE_SIZE);
size_t heap_pages = ALIGN_UP(KHEAP_SIZE, PAGE_SIZE) / PAGE_SIZE;
DEBUG("Mapping %d kernel heap pages at 0x%p", heap_pages, kheap_start);
uintptr_t current_addr = kheap_start;
// Map/alloc enough pages for heap (KHEAP_SIZE)
for (size_t i=0; i<heap_pages; i++) {
uintptr_t phys = pmm_alloc();
if (phys == 0) {
panic(NULL, "Not enough memory available to initialize kernel heap.");
}
paging_map_page(kernel_pml4, current_addr, phys, PTE_PRESENT | PTE_WRITABLE);
current_addr += PAGE_SIZE;
}
end = current_addr;
// Give linked list head its properties
head = (struct heap_block_t*)kheap_start;
head->size = (end-kheap_start) - sizeof(struct heap_block_t);
head->free = true;
head->next = NULL;
DEBUG("Kernel heap initialized, head=0x%p, size=%u bytes", head, head->size);
}
/*
* kmalloc - Kernel memory allocation
* @size: number of bytes to allocate
*
* Looks for a big enough free block and marks it
* as taken. Each block of memory is preceded by
* the linked list header.
*
* Return:
* <ptr> - Pointer to at least <size> bytes of usable memory
* NULL - No more memory, or no valid size given
*/
void* kmalloc(size_t size)
{
// No size, no memory allocated!
if (!size) return NULL;
size = ALIGN(size);
struct heap_block_t* curr = head;
while (curr) {
// Is block free and big enough for us?
if (curr->free && curr->size >= size) {
// We split the block if it is big enough
if (curr->size >= size + sizeof(struct heap_block_t) + 16) {
struct heap_block_t* split = (struct heap_block_t*)((uintptr_t)curr + sizeof(struct heap_block_t) + size);
split->size = curr->size - size - sizeof(struct heap_block_t);
split->free = true;
split->next = curr->next;
curr->next = split;
curr->size = size;
}
// Found a good block, we return it
curr->free = false;
return (void*)((uintptr_t)curr + sizeof(struct heap_block_t));
}
// Continue browsing the list if nothing good was found yet
curr = curr->next;
}
// No growing. If we're here it means the initial pool
// wasn't sufficient. Too bad.
DEBUG("Kernel heap is OUT OF MEMORY!");
// if we were terrorists maybe we should panic
// or just wait for others to free stuff?
return NULL;
}
/*
* kfree - Kernel memory freeing
* @ptr: pointer to memory region to free
*
* Marks the memory block beginning at <ptr>
* as free. Also merges adjacent free blocks
* to lessen fragmentation.
*/
void kfree(void* ptr)
{
// Nothing to free
if (!ptr) return;
// Set it free!
struct heap_block_t* block = (struct heap_block_t*)((uintptr_t)ptr - sizeof(struct heap_block_t));
block->free = true;
// merge adjacent free blocks (coalescing)
struct heap_block_t* curr = head;
while (curr && curr->next) {
if (curr->free && curr->next->free) {
curr->size += sizeof(*curr) + curr->next->size;
curr->next = curr->next->next;
continue;
}
curr = curr->next;
}
}
/*
* kalloc_stack - Stack memory allocation
*
* Allocates a memory region of at least PROCESS_STACK_SIZE,
* to be used as a stack for a process. The pointer returned
* points to the end of the region, as the stack grows downwards.
*
* Return:
* <ptr> - Pointer to a region after at least PROCESS_STACK_SIZE bytes of usable memory
* NULL - No more memory
*/
void* kalloc_stack()
{
uint8_t* ptr = kmalloc(PROCESS_STACK_SIZE); // As it's out of kmalloc, stack is already mapped into kernel space
return ptr ? ptr+PROCESS_STACK_SIZE : NULL;
}

215
src/mem/paging.c Normal file
View File

@@ -0,0 +1,215 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief x64 4-level paging implementation
* @license GPL-3.0-only
*/
#include <mem/paging.h>
#include <mem/pmm.h>
#include <kernel.h>
#include <stddef.h>
#include <limine.h>
#include <config.h>
/*
Paging on x86 uses four different page table levels:
cr3 register contains the phys address for the PML4 (root directory)
Each directory/table is made of 512 entries, each one uint64_t
Each of these entries have special bits (PRESENT/WRITEABLE/USER/etc.)
that dictates their attributes. Also these bits fall back on children tables.
If we use 1GB huge pages: PML4 -> PDPT -> 1gb pages
2MB huge pages: PML4 -> PDPT -> PD -> 2mb pages
4KB (regular size): PML4 -> PDPT -> PD -> PT -> 4kb pages
*/
/*
* load_cr3 - Load a new value into the CR3 register
* @value: the value to load
*
* This function is used to load the physical address
* of the root page table (PML4), to switch the paging
* structures the CPU sees and uses.
*/
void load_cr3(uint64_t value) {
asm volatile ("mov %0, %%cr3" :: "r"(value) : "memory");
}
/*
* invlpg - Invalidates a Translation Lookaside Buffer entry
* @addr: page memory address
*
* This function is used to flush at least the TLB entrie(s)
* for the page that contains the <addr> address.
*/
static inline void invlpg(void *addr)
{
asm volatile("invlpg (%0)" :: "r"(addr) : "memory");
}
/*
* alloc_page_table - Page table allocation
*
* This function allocates enough memory for a 512-entry
* 64-bit page table, for any level (PML4/3/2).
*
* Memory allocated here is zeroed.
*
* Return:
* <virt> - Pointer to allocated page table
*/
static uint64_t* alloc_page_table()
{
uint64_t* virt = (uint64_t*)PHYS_TO_VIRT(pmm_alloc());
for (size_t i=0; i<512; i++) {
virt[i] = 0;
}
return virt;
}
// Kernel paging root table, that will be placed in cr3
__attribute__((aligned(4096)))
uint64_t *kernel_pml4;
/*
* paging_map_page - Mapping a memory page
* @root_table: Address of the PML4
* @virt: Virtual address
* @phys: Physical address
* @flags: Flags to set on page
*
* This function maps the physical address <phys> to the virtual
* address <virt>, using the paging structures beginning at
* <root_table>. <flags> can be set according to the PTE_FLAGS enum.
*
* If a page table/directory entry is not present yet, it creates it.
*/
void paging_map_page(uint64_t* root_table, uint64_t virt, uint64_t phys, uint64_t flags)
{
virt = PAGE_ALIGN_DOWN(virt);
phys = PAGE_ALIGN_DOWN(phys);
// Translate the virt address into page table indexes
uint64_t pml4_i = PML4_INDEX(virt);
uint64_t pdpt_i = PDPT_INDEX(virt);
uint64_t pd_i = PD_INDEX(virt);
uint64_t pt_i = PT_INDEX(virt);
uint64_t *pdpt, *pd, *pt;
// PML4
// If the entry at index is not present, allocate enough space for it
// then populate the entry with correct addr + flags
if (!(root_table[pml4_i] & PTE_PRESENT)) {
pdpt = alloc_page_table();
root_table[pml4_i] = VIRT_TO_PHYS(pdpt) | PTE_PRESENT | PTE_WRITABLE;
} else {
pdpt = (uint64_t *)PHYS_TO_VIRT(root_table[pml4_i] & PTE_ADDR_MASK);
}
// PDPT: same here
if (!(pdpt[pdpt_i] & PTE_PRESENT)) {
pd = alloc_page_table();
pdpt[pdpt_i] = VIRT_TO_PHYS(pd) | PTE_PRESENT | PTE_WRITABLE;
} else {
pd = (uint64_t *)PHYS_TO_VIRT(pdpt[pdpt_i] & PTE_ADDR_MASK);
}
// PD: and here
if (!(pd[pd_i] & PTE_PRESENT)) {
pt = alloc_page_table();
pd[pd_i] = VIRT_TO_PHYS(pt) | PTE_PRESENT | PTE_WRITABLE;
} else {
pt = (uint64_t *)PHYS_TO_VIRT(pd[pd_i] & PTE_ADDR_MASK);
}
// PT: finally, populate the page table entry
pt[pt_i] = phys | flags | PTE_PRESENT;
// Flush TLB (apply changes)
invlpg((void *)virt);
}
uint64_t kernel_phys_base;
uint64_t kernel_virt_base;
/*
* paging_init - Paging initialization
* @boot_ctx: Boot context structure
*
* This function initializes new paging structures, to replace
* the ones given by the bootloader.
*
* It maps the kernel, the HHDM space, and the framebuffer.
*/
void paging_init(struct boot_context boot_ctx)
{
// We should map the kernel, GDT, IDT, stack, framebuffer.
// Optionally we could map ACPI tables (we can find them in the Limine memmap)
kernel_phys_base = boot_ctx.kaddr->physical_base;
kernel_virt_base = boot_ctx.kaddr->virtual_base;
struct limine_framebuffer* fb = boot_ctx.fb;
DEBUG("Kernel lives at virt=0x%p phys=0x%p", kernel_virt_base, kernel_phys_base);
kernel_pml4 = alloc_page_table();
// for debug
uint64_t page_count = 0;
// Find max physical address from limine memmap
uint64_t max_phys = 0;
for (uint64_t i=0; i<boot_ctx.mmap->entry_count; i++) {
struct limine_memmap_entry* entry = boot_ctx.mmap->entries[i];
if (entry->length == 0) {
continue;
}
uint64_t top = entry->base + entry->length;
if (top > max_phys) {
max_phys = top;
}
}
// 8GB
if (max_phys > PAGING_MAX_PHYS) {
DEBUG("WARNING: max_phys capped to PAGING_MAX_PHYS (from max_phys=%p)", max_phys);
max_phys = PAGING_MAX_PHYS;
}
// HHDM map up to max_phys or PAGING_MAX_PHYS, whichever is smaller, using given offset
for (uint64_t i=0; i<max_phys; i += PAGE_SIZE) {
paging_map_page(kernel_pml4, i+hhdm_off, i, PTE_WRITABLE | PTE_PRESENT);
page_count++;
}
DEBUG("Mapped %u pages up to 0x%p (HHDM)", page_count, max_phys); page_count = 0;
// Map the kernel (according to virt/phys_base given by Limine)
// SOME DAY when we want a safer kernel we should map .text as Read/Exec
// .rodata as Read and .data as Read/Write
// For now who gives a shit, let's RWX all kernel
for (uint64_t i = 0; i < KERNEL_SIZE; i += PAGE_SIZE) {
paging_map_page(kernel_pml4, kernel_virt_base+i, kernel_phys_base+i, PTE_WRITABLE);
page_count++;
}
DEBUG("Mapped %u pages for kernel", page_count); page_count = 0;
// Get the framebuffer phys/virt address, and size
uint64_t fb_virt = (uint64_t)fb->address;
uint64_t fb_phys = VIRT_TO_PHYS(fb_virt);
uint64_t fb_size = fb->pitch * fb->height;
uint64_t fb_pages = (fb_size + PAGE_SIZE-1)/PAGE_SIZE;
// Map the framebuffer (PWT set, and no PCD means PAT1 [Write-Combining] for this region)
for (uint64_t i=0; i<fb_pages; i++) {
paging_map_page(kernel_pml4, fb_virt+i*PAGE_SIZE, fb_phys+i*PAGE_SIZE, PTE_WRITABLE | PTE_PWT);
page_count++;
}
DEBUG("Mapped %u pages for framebuffer", page_count);
// Finally, we load the physical address of our PML4 (root table) into cr3
load_cr3(VIRT_TO_PHYS(kernel_pml4));
DEBUG("Loaded kernel PML4 into CR3");
}

110
src/mem/pmm.c Normal file
View File

@@ -0,0 +1,110 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Physical memory manager from freelist
* @license GPL-3.0-only
*/
/*
pmm - Physical Memory Manager
will manage 4kb pages physically
it will probably need to get some info from Limine,
to see which pages are used by kernel/bootloader/mmio/fb etc.
*/
#include "config.h"
#include <mem/paging.h>
#include <limine.h>
#include <stddef.h>
#include <stdint.h>
#include <kernel.h>
#include <mem/utils.h>
#include <mem/pmm.h>
/*
First we'll have to discover the physical memory layout,
and for that we can use a Limine request.
*/
// Offset from Higher Half Direct Map
uint64_t hhdm_off;
static uintptr_t g_freelist = 0;
/*
* pmm_alloc - Allocate a physical page
*
* This function allocates a single physical page (frame)
*
* Return:
* <addr> - Address for the allocated page
*/
uintptr_t pmm_alloc()
{
if (!g_freelist) {
panic(NULL, "PMM is out of memory!");
}
uintptr_t addr = g_freelist;
g_freelist = *(uintptr_t*) PHYS_TO_VIRT(g_freelist);
return addr;
}
/*
* pmm_free - Frees a memory page
* @addr: Address to the page
*/
void pmm_free(uintptr_t addr)
{
*(uintptr_t*) PHYS_TO_VIRT(addr) = g_freelist;
g_freelist = addr;
}
/*
* pmm_init_freelist - PMM freelist initialization
*
* This function marks the biggest memory region as
* free, so we can use it in pmm_alloc.
*/
static void pmm_init_freelist(struct limine_memmap_response* memmap)
{
uint64_t total_pages = 0;
for (size_t i=0; i<memmap->entry_count; i++) {
struct limine_memmap_entry* entry = memmap->entries[i];
if (entry->type == LIMINE_MEMMAP_USABLE) {
uint64_t base = ALIGN_UP(entry->base, PAGE_SIZE);
uint64_t end = ALIGN_DOWN(entry->base + entry->length, PAGE_SIZE);
if (end > PAGING_MAX_PHYS) {
end = PAGING_MAX_PHYS;
}
// Region above PAGING_MAX_PHYS
if (base >= end) continue;
for (uint64_t addr = base; addr < end; addr += PAGE_SIZE) {
pmm_free(addr);
total_pages++;
}
}
}
DEBUG("%u frames in freelist, %u bytes available (%u MB)", total_pages, total_pages*PAGE_SIZE, total_pages*PAGE_SIZE/1000000);
}
/*
* pmm_init - Physical memory manager initialization
* @boot_ctx: Boot context structure
*
* This function prepares the PMM for use.
* The PMM works with a freelist.
*/
void pmm_init(struct boot_context boot_ctx)
{
hhdm_off = boot_ctx.hhdm->offset;
//pmm_find_biggest_usable_region(boot_ctx.mmap, boot_ctx.hhdm);
// Now we have biggest USABLE region,
// so to populate the free list we just iterate through it
pmm_init_freelist(boot_ctx.mmap);
}

View File

@@ -1,5 +1,14 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Common memory utilities
* @license GPL-3.0-only
*/
#include <stddef.h>
#include <stdint.h>
#include <limine.h>
#include <kernel.h>
#include <string/string.h>
// We won't be linked to standard library, but still need the basic mem* functions
// so everything goes allright with the compiler
@@ -7,61 +16,104 @@
// We use the "restrict" keyword on pointers so that the compiler knows it can
// do more optimization on them (and as it's a much used function, it's good to
// be able to do that)
/*
* memcpy - Copy memory from one place to another
* @dest: pointer to the destination region
* @src: pointer to the source region
* @n: amount of bytes to copy
*
* This function copies n bytes of memory from
* src to dest.
*
* Return:
* <dest> - Pointer to destination region
*/
void* memcpy(void* restrict dest, const void* restrict src, size_t n)
{
uint8_t* restrict pdest = (uint8_t* restrict)dest;
const uint8_t* restrict psrc = (const uint8_t* restrict)src;
for (size_t i=0; i<n; i++)
{
for (size_t i=0; i<n; i++) {
pdest[i] = psrc[i];
}
return dest;
}
/*
* memset - Sets a memory region to given byte
* @s: pointer to memory region
* @c: byte to be written
* @n: amount of bytes to write
*
* This function writes n times the byte c
* to the memory region pointed to by s.
*
* Return:
* <s> - Pointer to memory region
*/
void* memset(void* s, int c, size_t n)
{
uint8_t* p = (uint8_t*)s;
for (size_t i=0; i<n; i++)
{
for (size_t i=0; i<n; i++) {
p[i] = (uint8_t)c;
}
return s;
}
/*
* memmove - Move memory from one place to another
* @dest: pointer to the destination region
* @src: pointer to the source region
* @n: amount of bytes to move
*
* This function moves n bytes of memory from
* src to dest.
*
* Return:
* <dest> - Pointer to destination region
*/
void* memmove(void *dest, const void* src, size_t n)
{
uint8_t* pdest = (uint8_t*)dest;
const uint8_t* psrc = (uint8_t*)src;
if (src > dest)
{
for (size_t i=0; i<n; i++)
{
if (src > dest) {
for (size_t i=0; i<n; i++) {
pdest[i] = psrc[i];
}
} else if (src < dest)
{
for (size_t i=n; i>0; i--)
{
} else if (src < dest) {
for (size_t i=n; i>0; i--) {
pdest[i-1] = psrc[i-1];
}
}
return dest;
}
/*
* memcmp - Compare two memory regions
* @s1: pointer to the first region
* @s2: pointer to the second region
* @n: amount of bytes to compare
*
* This function compares n bytes of memory
* bewteen regions pointed to by s1 and s2.
*
* Return:
* %0 - if s1 and s2 are equal
* %-1 - if s1 is smaller than s2
* %1 - if s1 is greater than s2
*/
int memcmp(const void* s1, const void* s2, size_t n)
{
const uint8_t* p1 = (const uint8_t*)s1;
const uint8_t* p2 = (const uint8_t*)s2;
for (size_t i=0; i<n; i++)
{
if (p1[i] != p2[i])
{
for (size_t i=0; i<n; i++) {
if (p1[i] != p2[i]) {
return p1[i] < p2[i] ? -1 : 1;
}
}

81
src/mem/vmm.c Normal file
View File

@@ -0,0 +1,81 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Virtual memory manager
* @license GPL-3.0-only
*/
/*
The VMM (virtual memory manager) will have two roles:
- mapping pages
- unmapping pages
in a specified virtual space
compared to the PMM which allocs/frees 4kb frames ("physical pages").
*/
#include <mem/vmm.h>
#include <mem/paging.h>
#include <stddef.h>
#include <mem/pmm.h>
#include <kernel.h>
void* vmm_pt_root = 0;
// Linked list head for virtual memory objects
struct vm_object* vm_objs = NULL;
/*
* Will have to be rewritten and expanded,
* to prepare for userspace.
* The platform-agnostic flags will be removed
* because as long as the kernel is x86 only,
* we don't need over complication.
* Plus I don't plan to port to other architectures
*/
uint64_t convert_x86_vm_flags(size_t flags)
{
uint64_t value = 0;
if (flags & VM_FLAG_WRITE)
{
value |= PTE_WRITABLE;
}
if (flags & VM_FLAG_USER)
{
value |= PTE_USER;
}
if ((flags & VM_FLAG_EXEC) == 0)
{
value |= PTE_NOEXEC;
}
return value;
}
extern uint64_t *kernel_pml4;
void vmm_setup_pt_root()
{
// We alloc a physical page (frame) for the pointer, then map it
// to virt (pointer)
uintptr_t phys = pmm_alloc();
vmm_pt_root = (void*)kernel_pml4;
paging_map_page(kernel_pml4, (uint64_t)vmm_pt_root, phys, convert_x86_vm_flags(VM_FLAG_WRITE | VM_FLAG_EXEC));
DEBUG("VMM setup: vmm_pt_root=0x%p (phys=0x%p)", vmm_pt_root, phys);
}
/* void* vmm_alloc(size_t length, size_t flags)
{
// We will try to allocate at least length bytes, which have to be rounded UP to
// the next page so its coherent with the PMM
size_t len = ALIGN_UP(length, PAGE_SIZE);
// Need to implement this (as linked list)
// but for now kernel heap is sufficient
// The VMM will prob be more useful when we have userspace
} */
void vmm_init()
{
// NO U
//vmm_setup_pt_root();
}

199
src/sched/process.c Normal file
View File

@@ -0,0 +1,199 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Process linked list implementation
* @license GPL-3.0-only
*/
#include <stddef.h>
#include <sched/process.h>
#include <mem/kheap.h>
#include <kernel.h>
#include <string/string.h>
#include <mem/gdt.h>
#include <config.h>
#include <io/serial/serial.h>
#include <io/term/flanterm.h>
extern struct flanterm_context* ft_ctx;
struct process_t* processes_list;
struct process_t* current_process;
extern uint64_t *kernel_pml4;
size_t next_free_pid = 0;
/*
* process_init - Initializes process list
*/
void process_init()
{
processes_list = NULL;
current_process = NULL;
}
/*
* process_display_list - Debug function to display processes
* @processes_list: head of the process linked list
*
* This function prints the linked list of processes
* to the DEBUG output.
*/
void process_display_list(struct process_t* processes_list)
{
int process_view_id = 0;
struct process_t* tmp = processes_list;
while (tmp != NULL) {
DEBUG("{%d: %p} -> ", process_view_id, tmp);
tmp = tmp->next;
process_view_id++;
}
DEBUG("NULL");
}
/*
* process_create - Create a process
* @name: name of the process
* @function: beginning of process executable code
* @arg: (optional) argument provided to process
*
* This function creates a process, gives it all
* necessary context and a stack, and adds the
* process to the linked list.
*
* Return:
* <proc> - pointer to created process
*/
struct process_t* process_create(char* name, void(*function)(void*), void* arg)
{
CLEAR_INTERRUPTS;
struct process_t* proc = (struct process_t*)kmalloc(sizeof(struct process_t));
struct cpu_status_t* ctx = (struct cpu_status_t*)kmalloc(sizeof(struct cpu_status_t));
// No more memory?
if (!proc) return NULL;
if (!ctx) return NULL;
strncpy(proc->name, name, PROCESS_NAME_MAX);
proc->pid = next_free_pid++;
proc->status = READY;
uint64_t* stack_top = (uint64_t*)kalloc_stack();
// push return address to the stack so when "ret" hits we jmp to exit instead of idk what
// stack grows DOWNWARDS!!
*(--stack_top) = (uint64_t)process_exit;
proc->context = ctx;
proc->context->iret_ss = KERNEL_DATA_SEGMENT; // process will live in kernel mode
proc->context->iret_rsp = (uint64_t)stack_top;
proc->context->iret_flags = 0x202; //bit 2 and 9 set (Interrupt Flag)
proc->context->iret_cs = KERNEL_CODE_SEGMENT;
proc->context->iret_rip = (uint64_t)function; // beginning of executable code
proc->context->rdi = (uint64_t)arg; // 1st arg is in rdi (as per x64 calling convention)
proc->context->rbp = 0;
// Kernel PML4 as it already maps code/stack (when switching to userland we'll have to change that)
proc->root_page_table = kernel_pml4;
proc->next = 0;
process_add(&processes_list, proc);
SET_INTERRUPTS;
return proc;
}
/*
* process_add - Add a process to the end of the linked list
* @processes_list: pointer to the head of the linked list
* @process: process to add at the end of the linked list
*/
void process_add(struct process_t** processes_list, struct process_t* process)
{
if (!process) return;
process->next = NULL;
if (*processes_list == NULL) {
// List is empty
*processes_list = process;
return;
}
struct process_t* tmp = *processes_list;
while (tmp->next != NULL) {
tmp = tmp->next;
}
// We're at last process before NULL
tmp->next = process;
}
/*
* process_delete - Delete a process from the linked list
* @processes_list: pointer to head of linked list
* @process: the process to delete from the list
*/
void process_delete(struct process_t** processes_list, struct process_t* process)
{
if (!processes_list || !*processes_list || !process) return;
if (*processes_list == process) {
// process to delete is at head
*processes_list = process->next;
process->next = NULL;
kfree(process);
return;
}
struct process_t* tmp = *processes_list;
while (tmp->next && tmp->next != process) {
tmp = tmp->next;
}
if (tmp->next == NULL) {
// Didn't find the process
return;
}
// We're at process before the one we want to delete
tmp->next = process->next;
process->next = NULL;
kfree(process);
}
/*
* process_get_next - Get the next process (unused)
* @process: pointer to process
*
* Return:
* <process->next> - process right after the one specified
*/
struct process_t* process_get_next(struct process_t* process)
{
if (!process) return NULL;
return process->next;
}
/*
* process_exit - Exit from a process
*
* This function is pushed to all process stacks, as a last
* return address. Once the process is done executing, it
* ends up here.
*
* Process is marked as DEAD, and then execution loops.
* Next time the scheduler sees the process, it will
* automatically delete it from the linked list.
*/
void process_exit()
{
DEBUG("Exiting from process '%s'", current_process->name);
CLEAR_INTERRUPTS;
if (current_process) {
current_process->status = DEAD;
}
SET_INTERRUPTS;
for (;;) {
asm("hlt");
}
}

80
src/sched/scheduler.c Normal file
View File

@@ -0,0 +1,80 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Round-robin scheduler
* @license GPL-3.0-only
*/
#include <kernel.h>
#include <sched/process.h>
#include <mem/paging.h>
#include <stdint.h>
#include <io/serial/serial.h>
extern struct process_t* processes_list;
extern struct process_t* current_process;
extern struct process_t* idle_proc;
/*
* scheduler_init - Choose the first process
*/
void scheduler_init()
{
current_process = processes_list;
}
/*
* scheduler_schedule - Main scheduling routine
* @context: CPU context of previous process
*
* Chooses the next process that we should run.
* The routine is executed every SCHEDULER_QUANTUM ticks.
*
* Return:
* <context> - CPU context for next process
*/
struct cpu_status_t* scheduler_schedule(struct cpu_status_t* context)
{
if (context == NULL) {
panic(NULL, "Scheduler called with NULL context");
}
if (current_process == NULL) {
// If no more processes, then set IDLE as the current process, that's it.
current_process = idle_proc;
}
if (current_process == idle_proc && current_process->next == NULL)
{
return idle_proc->context;
}
current_process->context = context;
for (;;) {
struct process_t* prev_process = current_process;
if (current_process->next != NULL) {
current_process = current_process->next;
} else {
current_process = processes_list;
}
if (current_process != NULL && current_process->status == DEAD) {
process_delete(&prev_process, current_process);
current_process = NULL;
return idle_proc->context;
} else {
current_process->status = RUNNING;
/* if (prev_process != current_process) {
DEBUG("Changed from {pid=%u, name=%s} to {pid=%u, name=%s}", prev_process->pid, prev_process->name, current_process->pid, current_process->name);
} */
break;
}
}
//DEBUG("current_process={pid=%u, name='%s', root_page_table[virt]=%p}", current_process->pid, current_process->name, current_process->root_page_table);
load_cr3(VIRT_TO_PHYS((uint64_t)current_process->root_page_table));
//DEBUG("Loaded process PML4 into CR3");
return current_process->context;
}

44
src/sched/spinlock.c Normal file
View File

@@ -0,0 +1,44 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Spinlock implementation
* @license GPL-3.0-only
*/
#include <stdatomic.h>
#include <stdbool.h>
#include <kernel.h>
#include <sched/spinlock.h>
/*
* spinlock_acquire - Lock a lock
* @lock: pointer to desired spinlock
*
* Saves the RFLAGS register, then acquires a lock.
* Pause instruction is used to ease the CPU.
*/
void spinlock_acquire(struct spinlock_t* lock)
{
uint64_t rflags;
asm volatile("pushfq ; pop %0 ; cli" : "=rm"(rflags) : : "memory");
while (__atomic_test_and_set(&lock->locked, __ATOMIC_ACQUIRE)) {
__builtin_ia32_pause();
}
lock->rflags = rflags;
}
/*
* spinlock_release - Unlock a lock
* @lock: pointer to desired spinlock
*
* Gets saved RFLAGS register from the lock and
* unlocks it (clears locked state).
* RFLAGS is then restored.
*/
void spinlock_release(struct spinlock_t* lock)
{
uint64_t rflags = lock->rflags;
__atomic_clear(&lock->locked, __ATOMIC_RELEASE);
asm volatile("push %0 ; popfq" : : "rm"(rflags) : "memory");
}

72
src/string/string.c Normal file
View File

@@ -0,0 +1,72 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief String manipulation utilities
* @license GPL-3.0-only
*/
#include <stddef.h>
/*
* strcpy - copy a NULL-terminated string
* @dest: destination buffer where the string is copied
* @src: source string to copy from
*
* Copies the string pointed to by @src (including the terminating
* NULL byte) into the buffer pointed to by @dest.
*
* Return: pointer to the destination string (@dest)
*/
char* strcpy(char *dest, const char *src)
{
char *temp = dest;
while((*dest++ = *src++));
return temp;
}
/*
* strcat - append a NUL-terminated string
* @dest: destination buffer containing the initial string
* @src: source string to append
*
* Appends the string pointed to by @src to the end of the string
* pointed to by @dest. The terminating NUL byte in @dest is
* overwritten and a new terminating NUL byte is added.
*
* The destination buffer must be large enough to hold the result.
*
* Taken from: https://stackoverflow.com/questions/2488563/strcat-implementation
*
* Return: pointer to the destination string (@dest)
*/
char *strcat(char *dest, const char *src)
{
size_t i,j;
for (i = 0; dest[i] != '\0'; i++);
for (j = 0; src[j] != '\0'; j++)
dest[i+j] = src[j];
dest[i+j] = '\0';
return dest;
}
/*
* strncpy - copy a string with length limit
* @dst: destination buffer
* @src: source string
* @n: maximum number of bytes to copy
*
* Copies up to @n bytes from @src to @dst. Copying stops early if a
* NULL byte is encountered in @src. If @src is shorter than @n, the
* remaining bytes in @dst are left unchanged in this implementation.
*
* Note: This differs slightly from the standard strncpy behavior,
* which pads the remaining bytes with NULL.
*
* Taken from: https://stackoverflow.com/questions/14159625/implementation-of-strncpy
*/
void strncpy(char* dst, const char* src, size_t n)
{
size_t i = 0;
while(i++ != n && (*dst++ = *src++));
}

View File

@@ -1,5 +1,13 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Programmable Interval Timer init and enabling
* @license GPL-3.0-only
*/
#include <stdint.h>
#include "../io/serial.h"
#include <io/serial/serial.h>
#include <kernel.h>
#include <config.h>
/*
For now, the timer module will be using the PIC.
@@ -11,6 +19,15 @@ interested in multi-core functionnality like SMP)
volatile uint64_t ticks = 0;
extern struct init_status init;
/*
* pic_remap - Remap the Programmable Interrupt Controller
*
* By default, interrupts are mapped at the wrong place.
* This function remaps interrupt numbers so interrupts
* don't conflict with each other.
*/
void pic_remap()
{
uint8_t master_mask = inb(0x21);
@@ -38,22 +55,31 @@ void pic_remap()
outb(0xA1, slave_mask);
}
/*
* pic_enable - Enable the Programmable Interrupt Controller
*
* This function enables IRQ0 and IRQ1, which correspond to
* the timer and keyboard interrupts, respectively.
*/
void pic_enable()
{
// Enabling IRQ0 (unmasking it) but not the others
uint8_t mask = inb(0x21);
mask &= ~(1 << 0); // Set IRQ0 (timer, clear bit 0)
mask &= ~(1 << 1); // Set IRQ1 (PS/2 Keyboard, clear bit 1)
//mask &= ~(1 << 1); // Set IRQ1 (PS/2 Keyboard, clear bit 1)
outb(0x21, mask);
}
/*
Base frequency = 1.193182 MHz
1 tick per ms (divide by 1000) = roughly 1193 Hz
* pit_init - Initialization of the Programmable Interval Timer
*
* The PIT is the simplest timer we can get working on x86.
* It has a base frequency of 1.193182 MHz.
* A custom frequency can be set using TIMER_FREQUENCY macro.
*/
void pit_init()
{
uint32_t frequency = 1000; // 1 kHz
uint32_t frequency = TIMER_FREQUENCY;
uint32_t divisor = 1193182 / frequency;
// Set PIT to mode 3, channel 0
@@ -64,6 +90,25 @@ void pit_init()
outb(0x40, (divisor >> 8) & 0xFF);
}
/*
* timer_wait - Wait for X ticks
*
* By default, the timer frequency is 1000Hz, meaning
* ticks are equal to milliseconds.
*/
void timer_wait(uint64_t wait_ticks)
{
uint64_t then = ticks + wait_ticks;
while (ticks < then) {
asm("hlt");
};
}
/*
* timer_init - Initialization of the timer
*
* This function wakes the PIT.
*/
void timer_init()
{
// Remapping the PIC, because at startup it conflicts with
@@ -72,4 +117,6 @@ void timer_init()
pic_remap();
pic_enable();
pit_init();
DEBUG("PIT initialized");
init.timer = true;
}

View File

@@ -1,6 +0,0 @@
#ifndef TIMER_H
#define TIMER_H
void timer_init();
#endif

33
symbols.py Normal file
View File

@@ -0,0 +1,33 @@
# Make assembly file from ELF symbols map
# Then link it to kernel so it's aware of symbol names
# then we can use that for the stack trace.
print("Extracting symbols from map file to assembly...")
with open("symbols.map", "r") as f:
lines = f.readlines()
symbols = []
for line in lines:
parts = line.split()
# output is formed like "address name"
symbols.append((parts[0], parts[1]))
with open("symbols.S", "w") as f:
f.write("section .rodata\n")
f.write("global symbol_table\n")
f.write("global symbol_count\n")
f.write("symbol_table:\n")
for i, (addr, name) in enumerate(symbols):
f.write(f" dq 0x{addr}\n")
f.write(f" dq sym_name_{i}\n")
f.write("\nsymbol_count: dq " + str(len(symbols)) + "\n\n")
for i, (addr, name) in enumerate(symbols):
# escaping quotes
safe_name = name.replace('"', '\\"')
f.write(f'sym_name_{i}: db "{safe_name}", 0\n')
print("Done!")

Binary file not shown.