41 Commits

Author SHA1 Message Date
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
62 changed files with 7477 additions and 1925 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,23 +1,41 @@
SOURCES = src/boot/boot.c src/sched/scheduler.c src/sched/process.c src/mem/heap/kheap.c src/mem/paging/vmm.c src/mem/paging/paging.c src/mem/paging/pmm.c src/string/string.c src/io/kbd/ps2.c src/io/serial/serial.c src/io/term/printf.c src/io/term/term.c src/idt/idt.c src/mem/gdt/gdt.c src/mem/misc/utils.c src/time/timer.c src/kmain.c
PROBLEMATIC_FLAGS=-Wno-unused-parameter -Wno-unused-variable
BUILDDIR := build
ELFFILE := pepperk
SRC := src
SOURCES := $(shell find src -name '*.c')
OBJFILES := $(patsubst $(SRC)/%.c, $(BUILDDIR)/%.o, $(SOURCES))
build:
rm -f *.o
x86_64-elf-gcc -g -c -Isrc $(SOURCES) $(PROBLEMATIC_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
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
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/idt/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):
@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
@@ -31,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 -no-shutdown &
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,11 +1,38 @@
# <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
@@ -13,11 +40,10 @@ The basics that I'm targeting are:
### Basic utility of what we call a "kernel"
- Fix terminal driver (backspace issues, scrolling) OR add Flanterm or equivalent
- 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 (syscalls)
- Getting to userspace (ring 3 switching, syscall interface)
- Porting musl libc or equivalent
### Scalability/maintenance/expansion features
@@ -26,7 +52,6 @@ The basics that I'm targeting are:
- SOME error handling in functions
- Unit tests
- Good error codes (like Linux kernel: ENOMEM, ENOENT, ...)
- Make the panic function work within itself without dependencies + error message (and still get cpu context?)
### Optional features
@@ -40,9 +65,12 @@ In the future, maybe?
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

@@ -10,12 +10,14 @@
/* version */
#define PEPPEROS_VERSION_MAJOR "0"
#define PEPPEROS_VERSION_MINOR "0"
#define PEPPEROS_VERSION_PATCH "1"
#define PEPPEROS_SPLASH "pepperOS version "PEPPEROS_VERSION_MAJOR"."PEPPEROS_VERSION_MINOR"."PEPPEROS_VERSION_PATCH"\n"
#define PEPPEROS_VERSION_PATCH "58"
#define PEPPEROS_SPLASH "\x1b[38;5;196mPepperOS\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\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
@@ -25,11 +27,22 @@
#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 0x100000000
/* heap */
#define KHEAP_SIZE (16*1024*1024)
#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

@@ -9,10 +9,9 @@
#include <stdint.h>
void idt_init();
void idt_init(void);
struct interrupt_descriptor
{
struct interrupt_descriptor {
uint16_t address_low;
uint16_t selector;
uint8_t ist;
@@ -22,8 +21,7 @@ struct interrupt_descriptor
uint32_t reserved;
} __attribute__((packed));
struct idtr
{
struct idtr {
uint16_t limit;
uint64_t base;
} __attribute__((packed));
@@ -31,8 +29,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;
@@ -42,7 +39,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;

View File

@@ -7,21 +7,24 @@
#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,
@@ -32,8 +35,7 @@ enum SpecialScancodes
ALT_RELEASED = 0xB8
};
enum KeyboardLayout
{
enum KeyboardLayout {
US,
FR
};

View File

@@ -7,10 +7,13 @@
#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();
int serial_init(void);
void skputs(const char* str);
void skputc(char c);

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 _putchar(char character);
void term_init(void);
int printf(const char* fmt, ...);
#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 <idt/idt.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

@@ -21,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

View File

@@ -14,18 +14,19 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
struct heap_block_t
{
struct heap_block_t {
size_t size;
bool free;
bool free; // 1byte
uint8_t reserved[7]; // (7+1 = 8 bytes)
struct heap_block_t* next;
};
} __attribute__((aligned(16)));
void kheap_init();
void kheap_init(void);
void* kmalloc(size_t size);
void kfree(void* ptr);
void* kalloc_stack();
void kheap_map_page();
void* kalloc_stack(void);
void kheap_map_page(void);
#endif

View File

@@ -11,11 +11,15 @@
#include <stdint.h>
#include <limine.h>
#include "mem/heap/kheap.h"
#include <mem/kheap.h>
#include <kernel.h>
void paging_init();
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))
@@ -37,12 +41,15 @@ extern uint64_t hhdm_off;
// Page entry special bits
// Bits set on a parent (directory, table) fall back to their children
#define PTE_PRESENT (1ULL << 0)
#define PTE_WRITABLE (1ULL << 1)
#define PTE_USER (1ULL << 2)
#define PTE_PWT (1ULL << 3)
#define PTE_PCD (1ULL << 4)
#define PTE_HUGE (1ULL << 7)
#define PTE_NOEXEC (1ULL << 63)
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

View File

@@ -8,9 +8,10 @@
#define PAGING_PMM_H
#include <limine.h>
#include <kernel.h>
void pmm_init(struct limine_memmap_response* memmap, struct limine_hhdm_response* hhdm);
void pmm_init(struct boot_context boot_ctx);
void pmm_free(uintptr_t addr);
uintptr_t pmm_alloc();
uintptr_t pmm_alloc(void);
#endif

View File

@@ -16,8 +16,7 @@ Flags here aren't x86 flags, they are platform-agnostic
kernel-defined flags.
*/
struct vm_object
{
struct vm_object {
uintptr_t base;
size_t length;
size_t flags;
@@ -30,6 +29,6 @@ struct vm_object
#define VM_FLAG_EXEC (1 << 1)
#define VM_FLAG_USER (1 << 2)
void vmm_init();
void vmm_init(void);
#endif

View File

@@ -8,30 +8,31 @@
#define PROCESS_H
#include <stddef.h>
#include "config.h"
#include <config.h>
#include <stdint.h>
typedef enum
{
typedef enum {
READY,
RUNNING,
DEAD
} status_t;
struct process_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 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);

View File

@@ -7,7 +7,7 @@
#ifndef SCHEDULER_H
#define SCHEDULER_H
void scheduler_schedule();
void scheduler_init();
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

View File

@@ -7,6 +7,8 @@
#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);

View File

@@ -7,7 +7,7 @@
#ifndef TIMER_H
#define TIMER_H
void timer_init();
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

View File

@@ -1,33 +1,34 @@
/*
* @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>
// Framebuffer request
__attribute__((used, section(".limine_requests")))
volatile struct limine_framebuffer_request framebuffer_request = {
.id = LIMINE_FRAMEBUFFER_REQUEST,
.revision = 0
};
// Memory map request
__attribute__((used, section(".limine_requests")))
volatile struct limine_memmap_request memmap_request = {
.id = LIMINE_MEMMAP_REQUEST,
.revision = 0
};
// Higher Half Direct Map
__attribute__((used, section(".limine_requests")))
volatile struct limine_hhdm_request hhdm_request = {
.id = LIMINE_HHDM_REQUEST,
.revision = 0
};
// Executable Address/Kernel Address (find base phys/virt address of kernel)
__attribute__((used, section(".limine_requests")))
volatile struct limine_kernel_address_request kerneladdr_request = {
.id = LIMINE_KERNEL_ADDRESS_REQUEST,

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

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

@@ -0,0 +1,92 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Kernel panic
* @license GPL-3.0-only
*/
#include <stddef.h>
#include <idt/idt.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*/
if (init.terminal) {
printf("\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\r\n",
CHECK_BIT(rflags, 0) ? "CF " : "",
CHECK_BIT(rflags, 2) ? "PF " : "",
CHECK_BIT(rflags, 4) ? "AF " : "",
CHECK_BIT(rflags, 6) ? "ZF " : "",
CHECK_BIT(rflags, 7) ? "SF " : "",
CHECK_BIT(rflags, 8) ? "TF " : "",
CHECK_BIT(rflags, 9) ? "IF " : "",
CHECK_BIT(rflags, 10) ? "DF " : "",
CHECK_BIT(rflags, 11) ? "OF " : "",
(CHECK_BIT(rflags, 12) && CHECK_BIT(rflags, 13)) ? "IOPL3 " : "IOPL0 ",
CHECK_BIT(rflags, 14) ? "NT " : "",
CHECK_BIT(rflags, 16) ? "RF " : "",
CHECK_BIT(rflags, 17) ? "VM " : "",
CHECK_BIT(rflags, 18) ? "AC " : "",
CHECK_BIT(rflags, 19) ? "VIF " : "",
CHECK_BIT(rflags, 20) ? "VIP " : "",
CHECK_BIT(rflags, 21) ? "ID " : "");
}
}
/*
* 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

@@ -151,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

View File

@@ -4,15 +4,16 @@
* @license GPL-3.0-only
*/
#include "idt.h"
#include <idt/idt.h>
#include <stdint.h>
#include <stddef.h>
#include "io/serial/serial.h"
#include "io/kbd/ps2.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/scheduler.h>
#include <config.h>
#include <sched/process.h>
struct interrupt_descriptor idt[256];
struct idtr idt_reg;
@@ -21,8 +22,14 @@ struct idtr idt_reg;
extern char vector_0_handler[];
// Timer ticks
extern uint64_t 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;
@@ -41,6 +48,10 @@ void idt_set_entry(uint8_t vector, void* handler, uint8_t dpl)
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
@@ -49,12 +60,15 @@ void idt_load(void* 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()
{
// We set 256 entries, but we have only the first few stubs.
// Undefined behavior?
for (size_t i=0; i<256; i++)
{
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);
}
@@ -62,6 +76,15 @@ void idt_init()
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;
@@ -69,6 +92,15 @@ static inline uint64_t read_cr2(void)
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
@@ -87,13 +119,16 @@ static void page_fault_handler(struct cpu_status_t* ctx)
CHECK_BIT(ctx->error_code, 7) ? " SGX_VIOLATION" : "",
cr2);
/* if (CHECK_BIT(ctx->error_code, 0))
{
panic(ctx);
} */
panic(ctx);
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",
@@ -102,8 +137,7 @@ static void gp_fault_handler(struct cpu_status_t* ctx)
(ctx->error_code == 0) ? "NOT_SEGMENT_RELATED" : "SEGMENT_RELATED");
// Segment-related
if (ctx->error_code != 0)
{
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)
@@ -117,102 +151,119 @@ static void gp_fault_handler(struct cpu_status_t* ctx)
index);
}
panic(ctx);
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)
{
switch(context->vector_number)
{
if (context == NULL) {
panic(NULL, "Interrupt dispatch recieved NULL context!");
}
switch(context->vector_number) {
case 0:
DEBUG("Divide Error!");
panic(context, "Divide Error");
break;
case 1:
DEBUG("Debug Exception!");
panic(context, "Debug Exception");
break;
case 2:
DEBUG("NMI Interrupt!");
panic(context, "NMI Interrupt");
break;
case 3:
DEBUG("Breakpoint Interrupt!");
panic(context, "Breakpoint Interrupt");
break;
case 4:
DEBUG("Overflow Trap!");
panic(context, "Overflow Trap");
break;
case 5:
DEBUG("BOUND Range Exceeded!");
panic(context, "BOUND Range Exceeded");
break;
case 6:
DEBUG("Invalid Opcode!");
panic(context, "Invalid Opcode");
break;
case 7:
DEBUG("Device Not Available!");
panic(context, "Device Not Available");
break;
case 8:
DEBUG("Double Fault!");
panic(context, "Double Fault");
break;
case 9:
DEBUG("Coprocessor Segment Overrun!");
panic(context, "Coprocessor Segment Overrun");
break;
case 10:
DEBUG("Invalid TSS!");
panic(context, "Invalid TSS");
break;
case 11:
DEBUG("Segment Not Present!");
panic(context, "Segment Not Present");
break;
case 12:
DEBUG("Stack-Segment Fault!");
panic(context, "Stack-Segment Fault");
break;
case 13:
gp_fault_handler(context);
break;
case 14:
// Better debugging for page faults...
page_fault_handler(context);
break;
case 15:
DEBUG("Intel Reserved Interrupt! (Achievement unlocked: How Did We Get Here?)");
panic(context, "Intel Reserved Interrupt (Achievement unlocked: How Did We Get Here?)");
break;
case 16:
DEBUG("x87 Floating-Point Error!");
panic(context, "x87 Floating-Point Error");
break;
case 17:
DEBUG("Alignment Check Fault!");
panic(context, "Alignment Check Fault");
break;
case 18:
DEBUG("Machine Check!");
panic(context, "Machine Check");
break;
case 19:
DEBUG("SIMD Floating-Point Exception!");
panic(context, "SIMD Floating-Point Exception");
break;
case 20:
DEBUG("Virtualization Exception!");
panic(context, "Virtualization Exception");
break;
case 21:
DEBUG("Control Protection Exception!");
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)
{
CLEAR_INTERRUPTS;
scheduler_schedule();
SET_INTERRUPTS;
if (ticks % SCHEDULER_QUANTUM == 0) {
return scheduler_schedule(context);
}
// Send an EOI so that we can continue having interrupts
break;
case 33: // Keyboard Interrupt
keyboard_handler();
//process_create("keyboard-initiated", kbdproc_main, NULL); // DEBUG
outb(0x20, 0x20);
break;
case 33:
DEBUG("Keyboard Interrupt");
keyboard_handler();
break;
default:
DEBUG("Unexpected interrupt");
DEBUG("Unexpected Interrupt");
break;
}

View File

@@ -4,11 +4,13 @@
* @license GPL-3.0-only
*/
#include "io/serial/serial.h"
#include "ps2.h"
#include "config.h"
#include <io/serial/serial.h>
#include <io/kbd/ps2.h>
#include <stdint.h>
#include "io/term/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;
@@ -17,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 */
@@ -154,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:
@@ -176,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:
@@ -200,29 +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') {
_putchar('\r');
}
// Should probably have a keyboard buffer here... instead of this
putchar(c);
_putchar(c);
keyboard_putchar(c);
}
}
}
}
}
}
// End of Interrupt (to master PIC)
outb(0x20, 0x20);
/*
* 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()
{
while (read_index == write_index); // Empty buffer
char c = keyboard_buffer[read_index];
read_index = (read_index+1) % KBD_BUFFER_MAX;
return c;
}
/*
* 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;
}
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;
@@ -233,14 +323,20 @@ void keyboard_init(unsigned char layout)
break;
default:
skputs("Unsupported layout.");
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

@@ -5,13 +5,29 @@
*/
#include <kernel.h>
#include "serial.h"
#include <io/serial/serial.h>
extern struct init_status init;
/*
* 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;
@@ -19,9 +35,13 @@ unsigned char inb(int port)
return data;
}
// COM1
#define PORT 0x3F8
/*
* serial_init - Initializes serial interface
*
* Return:
* %-EIO - Input/output error
* %0 - Success
*/
int serial_init()
{
outb(PORT + 1, 0x00); // Disable all interrupts
@@ -34,36 +54,52 @@ int serial_init()
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)
{
if (inb(PORT) != 0xAE) {
return -EIO;
}
// Set normal operation mode
outb(PORT + 4, 0x0F);
DEBUG("serial initialized");
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;
}
// Serial kernel putchar
/*
* skputc - Serial kernel putchar
* @c: character to write
*
* Writes a single character to the serial interface.
*/
void skputc(char c)
{
// TODO: Spinlock here (serial access)
while (!is_transmit_empty()); // wait for free spot
outb(PORT, c);
}
// Serial kernel putstring
/*
* 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])
{
while (str[i]) {
skputc(str[i]);
i++;
}

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

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

@@ -11,205 +11,145 @@ because this shitty implementation will be replaced one day by Flanterm
(once memory management is okay: paging & kernel malloc)
*/
#include <limine.h>
#include <stddef.h>
#include <kernel.h>
#include "term.h"
#include "mem/misc/utils.h"
#include "config.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>
extern struct boot_context boot_ctx;
#define NANOPRINTF_IMPLEMENTATION
#include <io/term/nanoprintf.h>
// Importing the PSF object file
extern unsigned char _binary_zap_light16_psf_start[];
extern unsigned char _binary_zap_light16_psf_end[];
extern struct flanterm_context* ft_ctx;
extern struct init_status init;
PSF1_Header* font = (PSF1_Header*)_binary_zap_light16_psf_start;
uint8_t* glyphs = _binary_zap_light16_psf_start + sizeof(PSF1_Header);
struct spinlock_t term_lock = {0};
#define FONT_WIDTH 8
#define FONT_HEIGHT font->characterSize
extern int panic_count;
// Character cursor
typedef struct
{
size_t x;
size_t y;
} Cursor;
static Cursor cursor = {0, 0};
static uint8_t* fb;
static struct limine_framebuffer* framebuffer;
uint8_t lines_length[TERM_HISTORY_MAX_LINES];
static inline size_t term_max_cols(void)
{
return framebuffer->width / FONT_WIDTH;
}
static inline size_t term_max_lines(void)
{
return framebuffer->height / FONT_HEIGHT;
}
int term_init()
{
// Get framebuffer address from Limine struct
if (!boot_ctx.fb)
{
return -ENOMEM;
}
framebuffer = boot_ctx.fb;
fb = (uint8_t*)framebuffer->address;
memset(lines_length, 0, sizeof(lines_length));
DEBUG("terminal initialized, fb=0x%p (width=%u height=%u pitch=%u bpp=%u)", fb, framebuffer->width, framebuffer->height, framebuffer->pitch, framebuffer->bpp);
return 0;
}
// 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 inline void putpixel(size_t x, size_t y, uint32_t color)
{
// Guard so we don't write past fb boundaries
if (x >= framebuffer->width || y >= framebuffer->height) return;
// Depth isn't part of limine_framebuffer attributes so it will be 4
size_t 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, size_t px, size_t py, uint32_t fg, uint32_t bg)
{
// So we cannot write past fb
if (px+FONT_WIDTH > framebuffer->width || py+FONT_HEIGHT > framebuffer->height) return;
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<FONT_WIDTH; x++)
{
uint32_t color = (row & (0x80 >> x)) ? fg : bg;
putpixel(px+x, py+y, color);
}
}
}
static void erase_char(size_t px, size_t py)
{
if (px+FONT_WIDTH > framebuffer->width || py+FONT_HEIGHT > framebuffer->height) return;
for (size_t y=0; y<FONT_HEIGHT; y++)
{
for (size_t x=0; x<FONT_WIDTH; x++)
{
// Black
putpixel(px+x, py+y, 0);
}
}
}
void term_scroll()
{
// Erase first text line
memset(fb, 255, FONT_HEIGHT*framebuffer->pitch);
// Move whole framebuffer up by one text line
memmove(fb, fb+(FONT_HEIGHT*framebuffer->pitch), (framebuffer->height-FONT_HEIGHT)*framebuffer->pitch);
// Clear last text line
size_t clear_start = (framebuffer->height - FONT_HEIGHT) * framebuffer->pitch;
memset(fb + clear_start, 255, FONT_HEIGHT * framebuffer->pitch);
// Shift line lengths by 1 (for backspace handling)
size_t max_lines = term_max_lines();
for (size_t i = 1; i < max_lines; i++)
{
lines_length[i - 1] = lines_length[i];
}
lines_length[max_lines - 1] = 0;
}
void putchar(char c)
{
const size_t max_cols = term_max_cols();
const size_t max_lines = term_max_lines();
if (c == '\n') {
lines_length[cursor.y] = cursor.x;
cursor.x = 0;
if (cursor.y + 1 >= max_lines)
{
term_scroll();
}
else
{
cursor.y++;
}
return;
}
if (c == '\b')
{
if (cursor.x > 0)
{
cursor.x--;
}
else if (cursor.y > 0)
{
cursor.y--;
cursor.x = lines_length[cursor.y];
}
else
{
return;
}
erase_char(cursor.x * FONT_WIDTH, cursor.y * FONT_HEIGHT);
return;
}
if (cursor.x >= max_cols)
{
cursor.x = 0;
if (cursor.y + 1 >= max_lines)
{
term_scroll();
}
else
{
cursor.y++;
}
}
draw_char(c, cursor.x * FONT_WIDTH, cursor.y * FONT_HEIGHT, WHITE, BLACK);
cursor.x++;
}
// Overhead that could be avoided, right? (for printf)
/*
* _putchar - Writes a character to terminal (DEPRECATED)
* @character: character to write
*/
void _putchar(char character)
{
putchar(character);
// TODO: Spinlock here (terminal access)
flanterm_write(ft_ctx, &character, 1);
}
// Debug-printing
/*
* 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
*/
int printf(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
int ret = npf_vpprintf(internal_putc, NULL, fmt, args);
va_end(args);
return ret;
}
/*
* 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)
{
putchar(str[i]);
while (str[i] != 0) {
_putchar(str[i]);
i++;
}
_putchar('\r');
}
extern struct flanterm_context* ft_ctx;
extern struct boot_context boot_ctx;
/*
* flanterm_free_wrapper - free() wrapper for Flanterm
* @ptr: pointer to free
* @size: amount of bytes to free
*
* This function exists solely because the Flanterm initialization
* function only accepts a free() function with a size parameter,
* and the default one doesn't have it.
*/
void flanterm_free_wrapper(void* ptr, size_t size)
{
(void)size;
kfree(ptr);
}
/*
* 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,32 +0,0 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Framebuffer-based terminal driver
* @license GPL-3.0-only
*/
#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;
// debug
void term_scroll();
#endif

View File

@@ -1,41 +0,0 @@
/*
* @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/printf.h"
#include "idt/idt.h"
#define DEBUG(log, ...) fctprintf((void*)&skputc, 0, "debug: [%s]: " log "\r\n", __FILE__, ##__VA_ARGS__)
#define CHECK_BIT(var,pos) ((var) & (1<<(pos)))
// printf("debug: [%s]: " log "\n", __FILE__, ##__VA_ARGS__);
void panic(struct cpu_status_t* ctx);
void hcf();
#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;
};
#endif

View File

@@ -7,47 +7,54 @@
#include <stdbool.h>
#include <stddef.h>
#include <limine.h>
#include "io/term/term.h"
#include "io/term/printf.h"
#include "io/serial/serial.h"
#include "mem/gdt/gdt.h"
#include "mem/misc/utils.h"
#include "idt/idt.h"
#include "kernel.h"
#include "time/timer.h"
#include "io/kbd/ps2.h"
#include "mem/paging/pmm.h"
#include "mem/paging/paging.h"
#include "mem/paging/vmm.h"
#include "mem/heap/kheap.h"
#include "sched/process.h"
#include "sched/scheduler.h"
#include "config.h"
#include <io/term/term.h>
#include <io/serial/serial.h>
#include <mem/gdt.h>
#include <mem/utils.h>
#include <idt/idt.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>
// Limine version used
__attribute__((used, section(".limine_requests")))
volatile LIMINE_BASE_REVISION(3);
// Halt and catch fire (makes machine stall)
int panic_count = 0;
/*
* 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()
{
CLEAR_INTERRUPTS; for (;;)asm("hlt");
}
// Doing nothing (can be interrupted)
void idle() {for(;;)asm("hlt");}
void panic(struct cpu_status_t* ctx)
{
DEBUG("\x1b[38;5;231m\x1b[48;5;196mKernel panic!!!\x1b[0m at rip=%p\nSomething went horribly wrong! 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\n\rstack at rbp=%p\n\rHalting...",
ctx->iret_rip,
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,
ctx->rbp);
hcf();
}
/*
* 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;
@@ -56,25 +63,42 @@ 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\nWelcome to PepperOS! Pedicel speaking.\r\nNothing left to do, let's go idle!\r\n");
}
void two_main(void* arg)
void idle_main(void* arg)
{
for (;;) {
asm("hlt");
}
}
void three_main(void* arg)
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);
}
// This is our entry point
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()
{
CLEAR_INTERRUPTS;
if (!LIMINE_BASE_REVISION_SUPPORTED) hcf();
// Populate boot context
@@ -83,38 +107,30 @@ void kmain()
boot_ctx.hhdm = hhdm_request.response ? hhdm_request.response : NULL;
boot_ctx.kaddr = kerneladdr_request.response ? kerneladdr_request.response : NULL;
term_init();
serial_init();
memmap_display(boot_ctx.mmap);
hhdm_display(boot_ctx.hhdm);
DEBUG("kernel: phys_base=0x%p virt_base=0x%p", boot_ctx.kaddr->physical_base, boot_ctx.kaddr->virtual_base);
CLEAR_INTERRUPTS;
gdt_init();
idt_init();
timer_init();
pmm_init(boot_ctx.mmap, boot_ctx.hhdm);
boot_mem_display();
pmm_init(boot_ctx);
// Remap kernel , HHDM and framebuffer
paging_init(boot_ctx.kaddr, boot_ctx.fb);
paging_init(boot_ctx);
kheap_init();
vmm_init();
keyboard_init(FR);
gdt_init();
idt_init();
process_init();
idle_proc = process_create("idle", (void*)idle_main, 0);
struct process_t* pedicel = process_create("pedicel", (void*)pedicel_main, 0);
struct process_t* two = process_create("two", (void*)two_main, 0);
struct process_t* three = process_create("three", (void*)three_main, 0);
process_create("thing", thing_main, NULL);
process_display_list(processes_list);
scheduler_init();
SET_INTERRUPTS;
keyboard_init(FR);
term_init();
kputs(PEPPEROS_SPLASH);
idle();
}

View File

@@ -4,9 +4,9 @@
* @license GPL-3.0-only
*/
#include "gdt.h"
#include <mem/gdt.h>
#include <stdint.h>
#include "io/serial/serial.h"
#include <io/serial/serial.h>
#include <kernel.h>
// Descriptors are 8-byte wide (64bits)
@@ -14,11 +14,20 @@
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
@@ -42,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)

View File

@@ -1,147 +0,0 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Kernel heap
* @license GPL-3.0-only
*/
#include "kheap.h"
#include "mem/paging/paging.h"
#include "mem/paging/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;
static void kheap_grow(size_t size)
{
size_t pages = ALIGN_UP(size + sizeof(struct heap_block_t), PAGE_SIZE) / PAGE_SIZE;
if (pages == 0) pages = 1;
for (size_t i = 0; i < pages; i++)
{
kheap_map_page();
}
}
void kheap_map_page()
{
uintptr_t phys = pmm_alloc();
paging_map_page(kernel_pml4, end, phys, PTE_PRESENT | PTE_WRITABLE | PTE_NOEXEC);
end += PAGE_SIZE;
//DEBUG("Mapped first kheap page");
}
void kheap_init()
{
kheap_start = ALIGN_UP(kernel_virt_base + KERNEL_SIZE, PAGE_SIZE);
end = kheap_start;
// At least 1 page must be mapped for it to work
kheap_map_page();
// Give linked list head its properties
head = (struct heap_block_t*)kheap_start;
head->size = PAGE_SIZE - sizeof(struct heap_block_t);
head->free = true;
head->next = NULL;
DEBUG("kheap initialized, head=0x%p, size=%u", head, head->size);
}
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 + BLOCK_MIN_SIZE)
{
//struct heap_block_t* new_block = (struct heap_block_t*)((uintptr_t)curr + sizeof(struct heap_block_t) + size);
struct heap_block_t* split = (struct heap_block_t*)((uintptr_t)curr + sizeof(*curr) + size);
split->size = curr->size - size - sizeof(*curr);
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;
}
// If we're here it means we didn't have enough memory
// for the block allocation. So we will allocate more..
uintptr_t old_end = end;
kheap_grow(size + sizeof(struct heap_block_t));
struct heap_block_t* block = (struct heap_block_t*)old_end;
block->size = ALIGN_UP(end - old_end - sizeof(struct heap_block_t), 16);
block->free = true;
block->next = NULL;
// Put the block at the end of the list
curr = head;
while (curr->next)
{
curr = curr->next;
}
curr->next = block;
return kmalloc(size);
}
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;
}
}
// Should alloc enough for a stack (at least 64kb) to be used for a process.
// Should return a pointer to top of the stack (as stack grows DOWNWARDS)
void* kalloc_stack()
{
uint8_t* ptr = kmalloc(PROCESS_STACK_SIZE);
return ptr ? ptr+PROCESS_STACK_SIZE : NULL;
}

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;
}

View File

@@ -1,128 +0,0 @@
/*
* @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
// 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)
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++)
{
pdest[i] = psrc[i];
}
return dest;
}
void* memset(void* s, int c, size_t n)
{
uint8_t* p = (uint8_t*)s;
for (size_t i=0; i<n; i++)
{
p[i] = (uint8_t)c;
}
return s;
}
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++)
{
pdest[i] = psrc[i];
}
} else if (src < dest)
{
for (size_t i=n; i>0; i--)
{
pdest[i-1] = psrc[i-1];
}
}
return dest;
}
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])
{
return p1[i] < p2[i] ? -1 : 1;
}
}
return 0;
}
// Display the memmap so we see 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);
}
}
// Display the HHDM
void hhdm_display(struct limine_hhdm_response* hhdm)
{
DEBUG("Got HHDM revision=%u offset=0x%p", hhdm->revision, hhdm->offset);
}

View File

@@ -4,12 +4,12 @@
* @license GPL-3.0-only
*/
#include "paging.h"
#include "pmm.h"
#include <mem/paging.h>
#include <mem/pmm.h>
#include <kernel.h>
#include <stddef.h>
#include <limine.h>
#include "config.h"
#include <config.h>
/*
Paging on x86 uses four different page table levels:
@@ -24,23 +24,46 @@ If we use 1GB huge pages: PML4 -> PDPT -> 1gb pages
4KB (regular size): PML4 -> PDPT -> PD -> PT -> 4kb pages
*/
static inline void load_cr3(uint64_t value) {
/*
* 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");
}
// To flush TLB
/*
* 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");
}
// Allocates a 512-entry 64bit page table/directory/whatever (zeroed)
/*
* 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++)
{
for (size_t i=0; i<512; i++) {
virt[i] = 0;
}
return virt;
@@ -50,10 +73,19 @@ static uint64_t* alloc_page_table()
__attribute__((aligned(4096)))
uint64_t *kernel_pml4;
// Map a page, taking virt and phys address. This will go through the paging structures
// beginning at the given root table, translate the virtual address in indexes in
// page table/directories, and then mapping the correct page table entry with the
// given physical address + flags
/*
* 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);
@@ -70,32 +102,26 @@ void paging_map_page(uint64_t* root_table, uint64_t virt, uint64_t phys, uint64_
// 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))
{
if (!(root_table[pml4_i] & PTE_PRESENT)) {
pdpt = alloc_page_table();
root_table[pml4_i] = VIRT_TO_PHYS(pdpt) | PTE_PRESENT | PTE_WRITABLE;
}
else {
} else {
pdpt = (uint64_t *)PHYS_TO_VIRT(root_table[pml4_i] & PTE_ADDR_MASK);
}
// PDPT: same here
if (!(pdpt[pdpt_i] & PTE_PRESENT))
{
if (!(pdpt[pdpt_i] & PTE_PRESENT)) {
pd = alloc_page_table();
pdpt[pdpt_i] = VIRT_TO_PHYS(pd) | PTE_PRESENT | PTE_WRITABLE;
}
else {
} else {
pd = (uint64_t *)PHYS_TO_VIRT(pdpt[pdpt_i] & PTE_ADDR_MASK);
}
// PD: and here
if (!(pd[pd_i] & PTE_PRESENT))
{
if (!(pd[pd_i] & PTE_PRESENT)) {
pt = alloc_page_table();
pd[pd_i] = VIRT_TO_PHYS(pt) | PTE_PRESENT | PTE_WRITABLE;
}
else {
} else {
pt = (uint64_t *)PHYS_TO_VIRT(pd[pd_i] & PTE_ADDR_MASK);
}
@@ -109,9 +135,16 @@ void paging_map_page(uint64_t* root_table, uint64_t virt, uint64_t phys, uint64_
uint64_t kernel_phys_base;
uint64_t kernel_virt_base;
extern struct boot_context boot_ctx;
void paging_init()
/*
* 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)
@@ -129,32 +162,25 @@ void paging_init()
// Find max physical address from limine memmap
uint64_t max_phys = 0;
for (uint64_t i=0; i<boot_ctx.mmap->entry_count; i++)
{
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)
{
if (entry->length == 0) {
continue;
}
uint64_t top = entry->base + entry->length;
if (top > max_phys)
{
if (top > max_phys) {
max_phys = top;
}
//DEBUG("max_phys=0x%p", max_phys);
}
// 4GB
if (max_phys > 0x100000000)
{
DEBUG("WARNING: max_phys capped to 4GB (0x100000000) (from max_phys=%p)", max_phys);
max_phys = 0x100000000;
if (max_phys > PAGING_MAX_PHYS) {
DEBUG("WARNING: max_phys capped to 4GB (%x) (from max_phys=%p)", PAGING_MAX_PHYS, max_phys);
max_phys = PAGING_MAX_PHYS;
}
// HHDM map up to max_phys or 4GB, whichever is smaller, using given offset
for (uint64_t i=0; i<max_phys; i += PAGE_SIZE)
{
//paging_kmap_page(i+hhdm_off, i, PTE_WRITABLE);
// 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++;
}
@@ -164,9 +190,7 @@ void paging_init()
// 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_kmap_page(kernel_virt_base+i, kernel_phys_base+i, PTE_WRITABLE);
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++;
}
@@ -179,9 +203,7 @@ void paging_init()
uint64_t fb_pages = (fb_size + PAGE_SIZE-1)/PAGE_SIZE;
// Map the framebuffer (with cache-disable & write-through)
for (uint64_t i=0; i<fb_pages; i++)
{
//paging_kmap_page(fb_virt+i*PAGE_SIZE, fb_phys+i*PAGE_SIZE, PTE_WRITABLE | PTE_PCD | PTE_PWT);
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_PCD | PTE_PWT);
page_count++;
}
@@ -189,5 +211,5 @@ void paging_init()
// Finally, we load the physical address of our PML4 (root table) into cr3
load_cr3(VIRT_TO_PHYS(kernel_pml4));
DEBUG("cr3 loaded, we're still alive");
DEBUG("Loaded kernel PML4 into CR3");
}

View File

@@ -11,26 +11,29 @@ it will probably need to get some info from Limine,
to see which pages are used by kernel/bootloader/mmio/fb etc.
*/
#include "paging.h"
#include <mem/paging.h>
#include <limine.h>
#include <stddef.h>
#include <stdint.h>
#include <kernel.h>
#include "mem/misc/utils.h"
#include "pmm.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.
*/
/*
We will look for the biggest usable physical memory region
and use this for the bitmap. The reserved memory will be ignored.
*/
struct limine_memmap_entry* biggest_entry;
/*
* pmm_find_biggest_usable_region - Finding the biggest free memory region
* @memmap: Limine memory map
* @hhdm: Limine HHDM offset
*
* This function uses the memory map provided by the bootloader
* to find the single biggest free memory region we can use.
*/
static void pmm_find_biggest_usable_region(struct limine_memmap_response* memmap, struct limine_hhdm_response* hhdm)
{
// Max length of a usable memory region
@@ -38,12 +41,10 @@ static void pmm_find_biggest_usable_region(struct limine_memmap_response* memmap
uint64_t offset = hhdm->offset;
DEBUG("Usable Memory:");
for (size_t i=0; i<memmap->entry_count; i++)
{
for (size_t i=0; i<memmap->entry_count; i++) {
struct limine_memmap_entry* entry = memmap->entries[i];
if (entry->type == LIMINE_MEMMAP_USABLE)
{
if (entry->type == LIMINE_MEMMAP_USABLE) {
DEBUG("0x%p-0x%p mapped at 0x%p-0x%p", entry->base, entry->base+entry->length,
entry->base+offset, entry->base+entry->length+offset);
if (entry->length > length_max)
@@ -64,20 +65,40 @@ 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) return 0;
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()
{
// We simply call pmm_free() on each page that is marked USABLE
@@ -86,20 +107,24 @@ static void pmm_init_freelist()
uint64_t end = ALIGN_DOWN(biggest_entry->base + biggest_entry->length, PAGE_SIZE);
uint64_t page_count=0;
for (uint64_t addr = base; addr < end; addr += PAGE_SIZE)
{
for (uint64_t addr = base; addr < end; addr += PAGE_SIZE) {
pmm_free(addr);
//DEBUG("page %u lives at phys 0x%p (virt 0x%p)", page_count, addr, PHYS_TO_VIRT(addr));
page_count++;
}
DEBUG("%u frames in freelist, available for use (%u bytes)", page_count, page_count*PAGE_SIZE);
}
void pmm_init(struct limine_memmap_response* memmap, struct limine_hhdm_response* hhdm)
/*
* 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 = hhdm->offset;
pmm_find_biggest_usable_region(memmap, hhdm);
//pmm_allocate_bitmap(hhdm); too complicated for my small brain
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

122
src/mem/utils.c Normal file
View File

@@ -0,0 +1,122 @@
/*
* @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
// 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++) {
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++) {
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++) {
pdest[i] = psrc[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]) {
return p1[i] < p2[i] ? -1 : 1;
}
}
return 0;
}

View File

@@ -13,10 +13,10 @@ in a specified virtual space
compared to the PMM which allocs/frees 4kb frames ("physical pages").
*/
#include "vmm.h"
#include "paging.h"
#include <mem/vmm.h>
#include <mem/paging.h>
#include <stddef.h>
#include "pmm.h"
#include <mem/pmm.h>
#include <kernel.h>
void* vmm_pt_root = 0;
@@ -24,6 +24,14 @@ 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)
{
@@ -68,5 +76,6 @@ void vmm_setup_pt_root()
void vmm_init()
{
vmm_setup_pt_root();
// NO U
//vmm_setup_pt_root();
}

View File

@@ -5,31 +5,45 @@
*/
#include <stddef.h>
#include "process.h"
#include "mem/heap/kheap.h"
#include "kernel.h"
#include "string/string.h"
#include "mem/gdt/gdt.h"
#include "config.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;
}
// Only for debug
/*
* 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)
{
while (tmp != NULL) {
DEBUG("{%d: %p} -> ", process_view_id, tmp);
tmp = tmp->next;
process_view_id++;
@@ -37,11 +51,23 @@ void process_display_list(struct process_t* processes_list)
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?
@@ -52,15 +78,23 @@ struct process_t* process_create(char* name, void(*function)(void*), void* arg)
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)kalloc_stack();
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);
@@ -69,34 +103,40 @@ struct process_t* process_create(char* name, void(*function)(void*), void* arg)
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)
{
if (*processes_list == NULL) {
// List is empty
*processes_list = process;
return;
}
struct process_t* tmp = *processes_list;
while (tmp->next != NULL)
{
while (tmp->next != NULL) {
tmp = tmp->next;
}
// We're at last process before NULL
tmp->next = process;
// process->next = NULL;
}
/*
* 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)
{
if (*processes_list == process) {
// process to delete is at head
*processes_list = process->next;
process->next = NULL;
@@ -105,13 +145,11 @@ void process_delete(struct process_t** processes_list, struct process_t* process
}
struct process_t* tmp = *processes_list;
while (tmp->next && tmp->next != process)
{
while (tmp->next && tmp->next != process) {
tmp = tmp->next;
}
if (tmp->next == NULL)
{
if (tmp->next == NULL) {
// Didn't find the process
return;
}
@@ -122,8 +160,40 @@ void process_delete(struct process_t** processes_list, struct process_t* process
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");
}
}

View File

@@ -4,43 +4,75 @@
* @license GPL-3.0-only
*/
#include "kernel.h"
#include "process.h"
#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()
{
// Choose first process?
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;
current_process->status = READY;
//current_process->status = READY;
for (;;) {
struct process_t* prev_process = current_process;
if (current_process->next != NULL)
{
if (current_process->next != NULL) {
current_process = current_process->next;
} else
{
} else {
current_process = processes_list;
}
if (current_process != NULL && current_process->status == DEAD)
{
if (current_process != NULL && current_process->status == DEAD) {
process_delete(&prev_process, current_process);
} else
{
current_process = NULL;
return idle_proc->context;
} else {
current_process->status = RUNNING;
break;
}
}
DEBUG("current_process={pid=%u name='%s'}", current_process->pid, current_process->name);
//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");
}

View File

@@ -6,6 +6,16 @@
#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;
@@ -13,18 +23,48 @@ char* strcpy(char *dest, const char *src)
return temp;
}
// https://stackoverflow.com/questions/2488563/strcat-implementation
char *strcat(char *dest, const char *src){
/*
* 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 (i = 0; dest[i] != '\0'; i++);
for (j = 0; src[j] != '\0'; j++)
dest[i+j] = src[j];
dest[i+j] = '\0';
return dest;
}
// https://stackoverflow.com/questions/14159625/implementation-of-strncpy
/*
* 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;

View File

@@ -5,8 +5,9 @@
*/
#include <stdint.h>
#include "io/serial/serial.h"
#include <io/serial/serial.h>
#include <kernel.h>
#include <config.h>
/*
For now, the timer module will be using the PIC.
@@ -18,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);
@@ -45,6 +55,12 @@ 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
@@ -55,12 +71,15 @@ void pic_enable()
}
/*
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
@@ -71,17 +90,25 @@ void pit_init()
outb(0x40, (divisor >> 8) & 0xFF);
}
// Wait n ticks
// Given that there's a tick every 1ms, wait n milliseconds
/*
* 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)
{
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
@@ -91,4 +118,5 @@ void timer_init()
pic_enable();
pit_init();
DEBUG("PIT initialized");
init.timer = true;
}

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

View File

@@ -1 +0,0 @@
<EFBFBD>ュ゙<EFBFBD>

Binary file not shown.