42 Commits

Author SHA1 Message Date
xamidev 0274e00b55 DOOM target + launcher code 2026-05-11 10:43:18 +02:00
xamidev deebd5d432 fix syscalls YET AGAIN 2026-05-10 21:49:17 +02:00
xamidev 5dc6b1124d fix syscalls 2026-05-10 21:44:54 +02:00
xamidev 3bbdc998cd PureDOOM + WAD 2026-05-10 21:35:35 +02:00
xamidev 18ab2c7628 syscalls needed for doom (tell/eof/draw_fb) + minor fixes, compiler shut up etc 2026-05-10 21:25:41 +02:00
xamidev 22f20d47ad Line discipline for carriage return + ps/kill kshell commands 2026-05-10 19:37:32 +02:00
xamidev 1142699c48 Alloc extra pages for raw binary 2026-05-10 19:36:01 +02:00
xamidev 01911bdd32 atoi 2026-05-10 19:34:35 +02:00
xamidev d65a736012 freestanding headers 2026-05-10 19:33:29 +02:00
xamidev 5e0bd98874 Merge pull request 'should be right?' (#20) from sys_fix into main
Reviewed-on: #20
2026-05-08 12:59:05 +02:00
xamidev 8c5911bef9 Update README.md 2026-05-08 12:58:08 +02:00
xamidev eb8a03facd Load raw C binary + docs 2026-05-08 12:38:16 +02:00
xamidev 9a1a0e428a tar_list 2026-05-06 14:04:28 +02:00
xamidev c061da4d81 sys_read/open/close 2026-05-06 13:29:35 +02:00
xamidev 63e9a761a3 should be right? 2026-05-06 11:26:33 +02:00
xamidev 935564c4b2 Merge pull request 'Tar filesystem' (#19) from tarfs into main
Reviewed-on: #19
2026-05-06 09:24:41 +02:00
xamidev c00a247ead Kshell: load executable command 2026-05-04 20:38:10 +02:00
xamidev ccb6ca89f1 Load TAR archive + run raw user program 2026-05-04 20:24:18 +02:00
xamidev e399ec6a46 alpha 0.1.121 2026-04-10 15:04:52 +02:00
xamidev dd9315f2f1 Update docs/MANUAL.md 2026-04-06 14:54:07 +02:00
xamidev f91831616c Merge pull request 'user-scheduler' (#18) from user-scheduler into main
Reviewed-on: #18
2026-04-05 19:37:15 +02:00
xamidev 0240220796 Scheduler fix, User RR 2026-04-03 19:18:08 +02:00
xamidev 437bd0e751 process_create_user 2026-04-03 18:45:12 +02:00
xamidev 1fe5eb2d38 Merge pull request 'syscall' (#17) from syscall into main
Reviewed-on: xamidev/pepperOS#17
2026-04-02 19:16:34 +02:00
xamidev dbffb7f5fa userland HELLO WORLD 2026-04-02 19:11:37 +02:00
xamidev aa30d9c6b5 user program (still many #PF) 2026-04-02 17:05:51 +02:00
xamidev 11a9dd4adb Load limine module + alloc user stack 2026-04-01 15:51:04 +02:00
xamidev d644126901 TSS setup 2026-04-01 13:04:33 +02:00
xamidev 8be2a744b4 Refactor GDT entry filling 2026-04-01 11:44:57 +02:00
xamidev e9b57f70b1 End the _t nonsense 2026-04-01 09:15:59 +02:00
xamidev e8a0a36889 Enable FPU 2026-03-31 21:04:44 +02:00
xamidev 1fc5225fd2 kheap info 2026-03-31 17:48:11 +02:00
xamidev 2f1eef9e15 UBSan conditional compilation 2026-03-29 09:38:24 +02:00
xamidev 65371077d9 more UBsan 2026-03-28 21:50:19 +01:00
xamidev 3cd38f654c Undefined Behavior Sanitization 2026-03-28 21:09:08 +01:00
xamidev 89259ec9b2 Stack Smashing Protection 2026-03-26 20:14:56 +01:00
xamidev 0fbaf6d26e Date functions (get current time) 2026-03-26 17:59:02 +01:00
xamidev 532953da4d CPU Name identification 2026-03-26 16:53:39 +01:00
xamidev ac788c55d3 Upgrade VMM for processes 2026-03-22 09:03:43 +01:00
xamidev 3ae56bbad5 Kernel debug shell 2026-03-21 11:34:46 +01:00
xamidev db36899152 Add kprintf for DEBUG(); differentiated from printf() 2026-03-21 10:36:54 +01:00
xamidev 7d03a0090b Merge pull request 'real-hw-fix' (#16) from real-hw-fix into main
Reviewed-on: xamidev/pepperOS#16
2026-03-20 16:58:08 +01:00
63 changed files with 51521 additions and 335 deletions
+19 -2
View File
@@ -1,13 +1,24 @@
USER_PROGRAMS := pedicel.raw apex.raw doom.raw
USER_FILES := wow.txt doom1.wad
BUILDDIR := build BUILDDIR := build
ELFFILE := pepperk ELFFILE := pepperk
SRC := src SRC := src
CC_PROBLEMATIC_FLAGS:=-Wno-unused-parameter -Wno-unused-variable
ifeq ($(UBSAN),true)
SOURCES := $(shell find src -name '*.c') SOURCES := $(shell find src -name '*.c')
CC_PROBLEMATIC_FLAGS:= -fsanitize=undefined
else
SOURCES := $(shell find src -name '*.c')
SOURCES := $(filter-out src/security/ubsan.c, $(SOURCES))
endif
OBJFILES := $(patsubst $(SRC)/%.c, $(BUILDDIR)/%.o, $(SOURCES)) OBJFILES := $(patsubst $(SRC)/%.c, $(BUILDDIR)/%.o, $(SOURCES))
CC := x86_64-elf-gcc 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_FLAGS=-Wall -Wextra -std=gnu99 -nostdlib -ffreestanding -fstack-protector -fno-omit-frame-pointer -fno-stack-check -fno-PIC -ffunction-sections -fdata-sections -mcmodel=kernel -mno-red-zone
CC_PROBLEMATIC_FLAGS=-Wno-unused-parameter -Wno-unused-variable
LD := x86_64-elf-ld LD := x86_64-elf-ld
@@ -32,12 +43,18 @@ limine/limine:
git clone https://github.com/limine-bootloader/limine.git --branch=v9.x-binary --depth=1 git clone https://github.com/limine-bootloader/limine.git --branch=v9.x-binary --depth=1
$(MAKE) -C limine $(MAKE) -C limine
.PHONY: user
user:
$(MAKE) -C user
tar cvf $(BUILDDIR)/initfs.tar -C $(BUILDDIR) $(USER_PROGRAMS) -C ../user $(USER_FILES)
build-iso: limine/limine $(ELFFILE) build-iso: limine/limine $(ELFFILE)
rm -rf iso_root rm -rf iso_root
mkdir -p iso_root/boot mkdir -p iso_root/boot
cp -v $(ELFFILE) iso_root/boot cp -v $(ELFFILE) iso_root/boot
mkdir -p iso_root/boot/limine mkdir -p iso_root/boot/limine
cp -v limine.conf iso_root/boot/limine cp -v limine.conf iso_root/boot/limine
cp $(BUILDDIR)/initfs.tar iso_root/boot/
mkdir -p iso_root/EFI/BOOT mkdir -p iso_root/EFI/BOOT
cp -v limine/limine-bios.sys limine/limine-bios-cd.bin limine/limine-uefi-cd.bin iso_root/boot/limine/ cp -v limine/limine-bios.sys limine/limine-bios-cd.bin limine/limine-uefi-cd.bin iso_root/boot/limine/
cp -v limine/BOOTX64.EFI iso_root/EFI/BOOT/ cp -v limine/BOOTX64.EFI iso_root/EFI/BOOT/
+17 -22
View File
@@ -1,5 +1,5 @@
# <img width="40" height="40" alt="red-pepper" src="https://i.ibb.co/mrHH6d1m/pixil-frame-0-4.png" /> pepperOS: "will never be done" # <img width="40" height="40" alt="red-pepper" src="https://i.ibb.co/mrHH6d1m/pixil-frame-0-4.png" /> pepperOS: "will never be done"
<a href="https://ibb.co/KxbfxmFB"><img src="https://i.ibb.co/GQn8QFcG/pepper.png" alt="pepper" border="0"></a>
## Description ## Description
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. 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.
@@ -23,8 +23,9 @@ CC := gcc
LD := ld LD := ld
``` ```
Then, to compile the kernel and make an ISO image file, run: `make build-iso` Then, to compile the kernel and make an ISO image file, run: `make`.
To run it with QEMU, do: `make run` To build the user programs and the initial filesystem, do `make user`.
To run it with QEMU, do: `make run`.
## Trying the kernel on real hardware ## Trying the kernel on real hardware
@@ -34,31 +35,23 @@ Compile the kernel and generate an ISO image like described above, then burn the
sudo dd if=pepper.iso of=/dev/sdX sudo dd if=pepper.iso of=/dev/sdX
``` ```
## TODO ## Compilation options
The basics that I'm targeting are: By default, PepperOS is compiled without the following features:
### Basic utility of what we call a "kernel" ```
UBSAN - undefined behavior sanitization
```
- Implement tasks, and task switching + context switching and spinlock acquire/release These features can be activated by setting them to "true" at the end of the make command, for example:
- Load an executable
- Filesystem (TAR for read-only initfs, then maybe read-write using FAT12/16/32 or easier fs) w/ VFS layer
- Getting to userspace (ring 3 switching, syscall interface)
- Porting musl libc or equivalent
### Scalability/maintenance/expansion features ```
make UBSAN=true
```
- Documentation ## Writing software for PepperOS
- SOME error handling in functions
- Unit tests
- Good error codes (like Linux kernel: ENOMEM, ENOENT, ...)
### Optional features If you want to write software for PepperOS, take a look at the [Software Developer's guide](docs/SOFTWARE.md).
In the future, maybe?
- SMP support (Limine provides functionality to make this easier)
- Parsing the ACPI tables and using them for something
- Replacing the PIT timer with APIC
## Thanks ## Thanks
@@ -74,3 +67,5 @@ PepperOS wouldn't be possible without the following freely-licensed software:
- Intel 64 and IA-32 Architectures Software Developer's Manual - Intel 64 and IA-32 Architectures Software Developer's Manual
- Documentation for the [GNU Compiler Collection](https://gcc.gnu.org/onlinedocs/gcc/) - Documentation for the [GNU Compiler Collection](https://gcc.gnu.org/onlinedocs/gcc/)
- dreamos82's [OSDev Notes](https://github.com/dreamportdev/Osdev-Notes/tree/master) - dreamos82's [OSDev Notes](https://github.com/dreamportdev/Osdev-Notes/tree/master)
- the [Sortix UBsan hook implementations](https://gitlab.com/sortix/sortix/-/blob/main/libc/ubsan/ubsan.c)
- the [CSC 395](https://curtsinger.cs.grinnell.edu/teaching/2022S/CSC395/kernel/) Kernel Development course from Grinnell College
+5 -1
View File
@@ -25,6 +25,9 @@ The recommended hardware to run PepperOS is the following:
## b. Features ## b. Features
- Round robin preemptive scheduling
- Coexistence of ring 0 and ring 3 processes
## II. Kernel architecture ## II. Kernel architecture
### a. Boot process ### a. Boot process
@@ -37,4 +40,5 @@ The recommended hardware to run PepperOS is the following:
## III. Syscall table ## III. Syscall table
Not yet implemented. The syscall interface in the Pepper kernel uses the System V ABI convention for argument order.
It vaguely mimics Unix-like systems. You will find it in [SYSCALLS.md](SYSCALLS.md).
+72
View File
@@ -0,0 +1,72 @@
# Writing software for PepperOS
## Why would you want to do that?
Honestly I have no idea. Maybe you have too much free time.
Keep in mind that the Pepper kernel is a personal project and it's full of bugs, inconsistencies, weird ways of doing things (and I don't care because it's my toy).
Now if you still want to write something for this OS, thank you. Follow along.
## Headers available in userspace
Of course, all of the freestanding headers are available:
- `<float.h>`: macros for floating-point types
- `<limits.h>`: macros for integer types
- `<iso646.h>`: macros for bitwise and logical operators
- `<stdarg.h>`: variadic function support
- `<stddef.h>`: definitions for `size_t`, `ptrdiff_t`, and others
- `<stdbool.h>`: definitions for boolean types
- `<stdint.h>`: definitions for `int_t` and `uint_t` types
Also available is the `<syscall.h>` header that gives access to low-level system call interface, notably the `syscallX` function family, X being the amount of arguments to use.
(TODO: put the other headers here once libc is more complete)
## 1. Write the source code
PepperOS is able to run programs written in x86 assembly, and C programs.
### x86 Assembly
Start your assembly file with the `bits 64` instruction, to emit 64-bit code.
You can add sections `.text`, `.data`, `.bss` as you need.
The three things to take in consideration here are:
- PepperOS does not use the `syscall` instruction, instead it uses the old-fashioned `int 0x80` to trigger a system call.
- The entry point should be labelled as `_start`.
- At the end of the file, there should be an exit system call followed by a loop, like so:
```nasm
.end:
mov rax, 0x3C
mov rdi, 0x0
int 0x80
.loop:
jmp .loop
```
For an example, look at the file [pedicel.S](../user/pedicel.S).
### C program
You will find relevant headers in the `libc` directory. They contain system call wrappers, utility functions, and more. See what's implemented there and what's not.
To invoke a system call you can use the functions defined in `libc/syscall.h`.
## 2. Add the Makefile rule and variable
Now that your code is complete, add a Makefile rule to `user/Makefile` with your program name. You can just copy-paste the rule that applies to you (either from an Assembly source or C source) and change the name of the files (.raw, .elf, etc...) in the rule.
For clarity, raw binaries have the `.raw` extension, and ELF ones have `.elf`.
You also now have to add the name of the executable to the `USER_PROGRAMS` variable at the top of the global Makefile.
Finally, do `make user` to compile your program.
## 3. Run your program
You can now boot up PepperOS, in a VM or on real hardware, and use the kernel's shell to `list` files in the filesystem (to see if your executable was properly added), and then, run it with the `load` command. Congratulations, you made a program for a random hobby OS!
## 4. (Optional) debugging
Use GDB with the `make debug` rule!
For your information, user programs are loaded at `0x400000`. Can be good to know to set breakpoints.
## 5. (Optional) contribute!
If you like what you've done and you think it could be nice to add it to PepperOS, send it to me by e-mail: `xamidev (at) riseup (dot) net`. It may or may not be added in a future release... who knows?
+8
View File
@@ -2,6 +2,14 @@
This document describes the coding style for the Pepper kernel. It is used as a guideline across all source files. This document describes the coding style for the Pepper kernel. It is used as a guideline across all source files.
## Setting up a language server (optional)
Before you do anything you might want to setup a language server with your editor. This will save you lots of time correcting errors and stuff. I use `clangd`, and generate my `compile_commands.json` like so:
```
bear -- make
```
## Indentation ## Indentation
Indentations should be 4 characters long. Indentations should be 4 characters long.
+15
View File
@@ -0,0 +1,15 @@
# Pepper kernel system call table
The following table contains all of the system calls supported by PepperOS, as well as their arguments. The explanation for what every system call does is available as a comment above each function in corresponding files in the `src/syscall` folder.
Name | Number (%rax) | arg0 (%rdi) | arg1 (%rsi) | arg2 (%rdx) | arg3 (%r10) |
|---|---|---|---|---|---|
| sys_read | 0 | unsigned int fd | char* buf | size_t count | |
| sys_write | 1 | unsigned int fd | const char* buf | size_t count | |
| sys_open | 2 | const char* filename | int flags | | |
| sys_close | 3 | unsigned int fd | | | |
| sys_lseek | 8 | unsigned int fd | int offset | int whence | |
| sys_tell | 9 | unsigned int fd | | | |
| sys_eof | 10 | unsigned int fd | | | |
| sys_draw | 11 | const uint8_t* src | int width | int height | int channels |
| sys_exit | 60 | int error_code | | | |
+15 -2
View File
@@ -1,6 +1,6 @@
/* /*
* @author xamidev <xamidev@riseup.net> * @author xamidev <xamidev@riseup.net>
* @brief Global Descriptor Table (for legacy reasons) * @brief Global Descriptor Table
* @license GPL-3.0-only * @license GPL-3.0-only
*/ */
@@ -13,19 +13,32 @@
// we'll only use this as a requirement for paging, not more. // we'll only use this as a requirement for paging, not more.
// This means base 0 and no limit (whole address space) // This means base 0 and no limit (whole address space)
#define NUM_GDT_ENTRIES 5 #define NUM_GDT_ENTRIES 7
#define NULL_SELECTOR 0x00 #define NULL_SELECTOR 0x00
#define KERNEL_CODE_SEGMENT 0x08 #define KERNEL_CODE_SEGMENT 0x08
#define KERNEL_DATA_SEGMENT 0x10 #define KERNEL_DATA_SEGMENT 0x10
#define USER_CODE_SEGMENT 0x18 #define USER_CODE_SEGMENT 0x18
#define USER_DATA_SEGMENT 0x20 #define USER_DATA_SEGMENT 0x20
#define TSS_SEGMENT 0x28
struct GDTR { struct GDTR {
uint16_t limit; uint16_t limit;
uint64_t address; uint64_t address;
} __attribute__((packed)); } __attribute__((packed));
struct tss {
uint32_t reserved0;
uint64_t rsp0;
uint64_t rsp1;
uint64_t rsp2;
uint64_t reserved1;
uint64_t ist[7];
uint64_t reserved2;
uint16_t reserved3;
uint16_t iopb;
} __attribute__((packed));
void gdt_init(void); void gdt_init(void);
#endif #endif
+6 -1
View File
@@ -10,6 +10,9 @@ void wrmsr(uint32_t msr, uint64_t value);
bool x86_has_msr(); bool x86_has_msr();
void x86_arch_init(); void x86_arch_init();
void x86_cpu_identification();
int cpuid_get_vendor_string(char* str);
/* Interrupt Descriptor Table */ /* Interrupt Descriptor Table */
void idt_init(void); void idt_init(void);
@@ -32,7 +35,7 @@ struct idtr {
// All general-purpose registers (except rsp) as stored on the stack, // All general-purpose registers (except rsp) as stored on the stack,
// plus the values we pushed (vector number, error code) and the iret frame // plus the values we pushed (vector number, error code) and the iret frame
// In reverse order because the stack grows downwards. // In reverse order because the stack grows downwards.
struct cpu_status_t { struct cpu_status {
uint64_t r15; uint64_t r15;
uint64_t r14; uint64_t r14;
uint64_t r13; uint64_t r13;
@@ -59,4 +62,6 @@ struct cpu_status_t {
uint64_t iret_ss; uint64_t iret_ss;
}; };
struct cpu_status* syscall_handler(struct cpu_status* regs);
#endif #endif
+14
View File
@@ -0,0 +1,14 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Boot routines
* @license GPL-3.0-only
*/
#ifndef BOOT_H
#define BOOT_H
#include <kernel.h>
void populate_boot_context(struct boot_context* boot_ctx);
#endif
+21 -4
View File
@@ -9,8 +9,8 @@
/* version */ /* version */
#define PEPPEROS_VERSION_MAJOR "0" #define PEPPEROS_VERSION_MAJOR "0"
#define PEPPEROS_VERSION_MINOR "0" #define PEPPEROS_VERSION_MINOR "1"
#define PEPPEROS_VERSION_PATCH "58" #define PEPPEROS_VERSION_PATCH "121"
#define PEPPEROS_SPLASH \ #define PEPPEROS_SPLASH \
"\x1b[38;5;196m \x1b[38;5;231m____ _____\r\n\x1b[0m"\ "\x1b[38;5;196m \x1b[38;5;231m____ _____\r\n\x1b[0m"\
"\x1b[38;5;196m ____ ___ ____ ____ ___ _____\x1b[38;5;231m/ __ \\/ ___/\r\n\x1b[0m"\ "\x1b[38;5;196m ____ ___ ____ ____ ___ _____\x1b[38;5;231m/ __ \\/ ___/\r\n\x1b[0m"\
@@ -20,11 +20,14 @@
"\x1b[38;5;196m/_/ /_/ /_/ \r\n\x1b[0m"\ "\x1b[38;5;196m/_/ /_/ /_/ \r\n\x1b[0m"\
" --- version \x1b[38;5;220m"PEPPEROS_VERSION_MAJOR"."PEPPEROS_VERSION_MINOR"."PEPPEROS_VERSION_PATCH"\x1b[0m built on \x1b[38;5;40m"__DATE__" "__TIME__"\x1b[0m\r\n" " --- version \x1b[38;5;220m"PEPPEROS_VERSION_MAJOR"."PEPPEROS_VERSION_MINOR"."PEPPEROS_VERSION_PATCH"\x1b[0m built on \x1b[38;5;40m"__DATE__" "__TIME__"\x1b[0m\r\n"
/* pedicel */
#define PEDICEL_PROMPT "pedicel$ "
#define PEDICEL_INPUT_SIZE 128
/* process */ /* process */
#define PROCESS_NAME_MAX 64 #define PROCESS_NAME_MAX 64
#define PROCESS_STACK_SIZE 0x10000 // 64kb #define PROCESS_STACK_SIZE 0x10000 // 64kb
#define PROCESS_BASE 0x400000 #define PROCESS_STACK_TOP 0x80000000
#define PROCESS_STACK_BASE 0x1000000
/* sched */ /* sched */
// 1 tick = 1 ms => quantum = 10ms // 1 tick = 1 ms => quantum = 10ms
@@ -37,6 +40,13 @@
#define KERNEL_STACK_SIZE 65536 #define KERNEL_STACK_SIZE 65536
#define KERNEL_IDT_ENTRIES 33 #define KERNEL_IDT_ENTRIES 33
/* user */
#define USER_STACK_TOP 0x80000000
#define USER_STACK_PAGES 16 // 16*4096 = 64kb
#define USER_CODE_START 0x400000 // like linux
#define USER_RAW_EXTRA_PAGES 8192 // Extra writable pages after raw image for .bss/heap
// TODO: throw this away and make an ELF loader instead bruh
/* paging */ /* paging */
#define PAGING_MAX_PHYS 0x200000000 #define PAGING_MAX_PHYS 0x200000000
@@ -52,4 +62,11 @@
/* time */ /* time */
#define TIMER_FREQUENCY 1000 #define TIMER_FREQUENCY 1000
/* ssp */
#define STACK_CHK_GUARD 0x7ABA5C007ABA5C00
/* fs */
#define FDT_MAX 8 // Maximum amount of file descriptors per process
#endif #endif
+24
View File
@@ -0,0 +1,24 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief PS/2 Keyboard driver
* @license GPL-3.0-only
*/
#ifndef INITFS_H
#define INITFS_H
#include <limine.h>
int initfs_init(struct limine_file* tar_file);
int tar_lookup(unsigned char* archive, char* filename, char** out);
int tar_exists(const char* filename);
int tar_read(char* filename, char* out, int count, int offset);
void tar_list();
enum Seek {
SEEK_SET,
SEEK_CUR,
SEEK_END
};
#endif
+1
View File
@@ -11,5 +11,6 @@ void kputs(const char* str);
void term_init(void); void term_init(void);
int printf(const char* fmt, ...); int printf(const char* fmt, ...);
void internal_putc(int c, void *_); void internal_putc(int c, void *_);
int kprintf(const char* fmt, ...);
#endif #endif
+18 -4
View File
@@ -7,9 +7,16 @@
#ifndef KERNEL_H #ifndef KERNEL_H
#define KERNEL_H #define KERNEL_H
#include "limine.h"
// Not in POSIX order.
enum ErrorCodes { enum ErrorCodes {
ENOMEM, ENOMEM, // No memory
EIO EIO, // Input/output error
ENOENT, // No entry
EBADFD, // Bad file descriptor
EMFILE, // Too many open files
EINVAL // Invalid argument
}; };
#define CLEAR_INTERRUPTS __asm__ volatile("cli") #define CLEAR_INTERRUPTS __asm__ volatile("cli")
@@ -21,7 +28,7 @@ enum ErrorCodes {
#include <stdbool.h> #include <stdbool.h>
extern volatile uint64_t ticks; extern volatile uint64_t ticks;
#define DEBUG(log, ...) printf("[%8u] debug: <%s>: " log "\r\n", ticks, __func__, ##__VA_ARGS__) #define DEBUG(log, ...) kprintf("[%8u] debug: <%s>: " log "\r\n", ticks, __func__, ##__VA_ARGS__)
/* #define DEBUG(log, ...) \ /* #define DEBUG(log, ...) \
printf("debug: [%s]: " log "\r\n", __FILE__, ##__VA_ARGS__); \ printf("debug: [%s]: " log "\r\n", __FILE__, ##__VA_ARGS__); \
@@ -34,15 +41,19 @@ extern volatile uint64_t ticks;
// printf("debug: [%s]: " log "\n", __FILE__, ##__VA_ARGS__); // printf("debug: [%s]: " log "\n", __FILE__, ##__VA_ARGS__);
void panic(struct cpu_status_t* ctx, const char* str); void panic(struct cpu_status* ctx, const char* str);
void hcf(void); void hcf(void);
void idle(void); void idle(void);
void pedicel_main(void* arg);
/* debug */ /* debug */
void debug_stack_trace(unsigned int max_frames); void debug_stack_trace(unsigned int max_frames);
const char* debug_find_symbol(uintptr_t rip, uintptr_t* offset); const char* debug_find_symbol(uintptr_t rip, uintptr_t* offset);
void boot_mem_display(void); void boot_mem_display(void);
int loader_load_raw();
#define assert(check) do { if(!(check)) hcf(); } while(0) #define assert(check) do { if(!(check)) hcf(); } while(0)
struct boot_context { struct boot_context {
@@ -50,6 +61,8 @@ struct boot_context {
struct limine_memmap_response* mmap; struct limine_memmap_response* mmap;
struct limine_hhdm_response* hhdm; struct limine_hhdm_response* hhdm;
struct limine_kernel_address_response* kaddr; struct limine_kernel_address_response* kaddr;
struct limine_boot_time_response* bootdate;
struct limine_module_response* module;
}; };
// Are these modules initialized yet? // Are these modules initialized yet?
@@ -58,6 +71,7 @@ struct init_status {
bool serial; bool serial;
bool keyboard; bool keyboard;
bool timer; bool timer;
bool all;
}; };
#endif #endif
+3 -2
View File
@@ -16,11 +16,11 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
struct heap_block_t { struct heap_block {
size_t size; size_t size;
bool free; // 1byte bool free; // 1byte
uint8_t reserved[7]; // (7+1 = 8 bytes) uint8_t reserved[7]; // (7+1 = 8 bytes)
struct heap_block_t* next; struct heap_block* next;
} __attribute__((aligned(16))); } __attribute__((aligned(16)));
void kheap_init(void); void kheap_init(void);
@@ -28,5 +28,6 @@ void* kmalloc(size_t size);
void kfree(void* ptr); void kfree(void* ptr);
void* kalloc_stack(void); void* kalloc_stack(void);
void kheap_map_page(void); void kheap_map_page(void);
void kheap_info();
#endif #endif
+3 -1
View File
@@ -16,9 +16,11 @@
void paging_init(struct boot_context boot_ctx); 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); void paging_map_page(uint64_t* root_table, uint64_t virt, uint64_t phys, uint64_t flags);
uint64_t* alloc_page_table();
// To swap root page tables // To swap root page tables
void load_cr3(uint64_t value); void load_cr3(uint64_t value);
void invlpg(void *addr);
extern uint64_t hhdm_off; extern uint64_t hhdm_off;
@@ -32,7 +34,7 @@ extern uint64_t hhdm_off;
#define PAGE_ALIGN_DOWN(x) ((x) & PTE_ADDR_MASK) #define PAGE_ALIGN_DOWN(x) ((x) & PTE_ADDR_MASK)
#define ALIGN(size) ALIGN_UP(size, 16) #define ALIGN(size) ALIGN_UP(size, 16)
#define BLOCK_MIN_SIZE (sizeof(struct heap_block_t) + 16) #define BLOCK_MIN_SIZE (sizeof(struct heap_block) + 16)
#define PML4_INDEX(x) (((x) >> 39) & 0x1FF) #define PML4_INDEX(x) (((x) >> 39) & 0x1FF)
#define PDPT_INDEX(x) (((x) >> 30) & 0x1FF) #define PDPT_INDEX(x) (((x) >> 30) & 0x1FF)
+1
View File
@@ -8,6 +8,7 @@
#define MEM_UTILS_H #define MEM_UTILS_H
#include <stddef.h> #include <stddef.h>
#include <limine.h>
void* memcpy(void* restrict dest, const void* restrict src, size_t n); void* memcpy(void* restrict dest, const void* restrict src, size_t n);
void* memset(void* s, int c, size_t n); void* memset(void* s, int c, size_t n);
+14 -17
View File
@@ -9,26 +9,23 @@
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include <stdbool.h>
/* struct vmm_context {
This will be our linked list of virtual memory objects. uint64_t* pml4;
Flags here aren't x86 flags, they are platform-agnostic
kernel-defined flags.
*/
struct vm_object {
uintptr_t base;
size_t length;
size_t flags;
struct vm_object* next;
}; };
// Flags bitfield
#define VM_FLAG_NONE 0
#define VM_FLAG_WRITE (1 << 0)
#define VM_FLAG_EXEC (1 << 1)
#define VM_FLAG_USER (1 << 2)
void vmm_init(void); void vmm_init(void);
void* vmm_alloc_region(uint64_t* pml4, size_t pages, uint64_t flags);
bool vmm_is_mapped(uint64_t* pml4, uint64_t virt);
void vmm_unmap(uint64_t* pml4, uint64_t virt);
void* vmm_map(uint64_t* pml4, uint64_t virt, uint64_t flags);
uint64_t* vmm_create_address_space();
uint64_t vmm_virt_to_phys(uint64_t* pml4, uint64_t virt);
uintptr_t vmm_alloc_user_stack(uint64_t* pml4);
uintptr_t vmm_alloc_user_code(uint64_t* pml4, void* code_addr, uint64_t code_size);
#define VMM_USER_SPACE_START 0x0000000000001000
#define VMM_USER_SPACE_END 0x00007FFFFFFFF000
#endif #endif
+25 -8
View File
@@ -10,6 +10,8 @@
#include <stddef.h> #include <stddef.h>
#include <config.h> #include <config.h>
#include <stdint.h> #include <stdint.h>
#include <limine.h>
#include <stdbool.h>
typedef enum { typedef enum {
READY, READY,
@@ -17,23 +19,38 @@ typedef enum {
DEAD DEAD
} status_t; } status_t;
struct process_t { struct fd {
int fd;
char filename[PROCESS_NAME_MAX]; // File opened
uint64_t cursor; // Cursor position in file
bool open;
};
struct process {
size_t pid; size_t pid;
char name[PROCESS_NAME_MAX]; char name[PROCESS_NAME_MAX];
status_t status; status_t status;
struct cpu_status_t* context; struct cpu_status* context;
void* root_page_table; // Process PML4 (should contain kernel PML4 in higher half [256-511] void* root_page_table; // Process PML4 (should contain kernel PML4 in higher half [256-511]
struct process_t* next; void* kernel_stack; // Used for interrupts (syscall: int 0x80), defines the TSS RSP0
struct fd fdt[FDT_MAX]; // File Descriptor Table
size_t next_free_fd;
struct process* next;
}; };
void process_init(void); void process_init(void);
struct process_t* process_create(char* name, void(*function)(void*), void* arg); struct process* process_create(char* name, void(*function)(void*), void* arg);
void process_add(struct process_t** processes_list, struct process_t* process); void process_add(struct process** processes_list, struct process* process);
void process_delete(struct process_t** processes_list, struct process_t* process); void process_delete(struct process** processes_list, struct process* process);
struct process_t* process_get_next(struct process_t* process); struct process* process_get_next(struct process* process);
void process_exit(void); void process_exit(void);
void process_display_list(struct process_t* processes_list); void process_display_list(struct process* processes_list);
void process_create_user_raw(char* file, int size, char* name);
#endif #endif
+1 -1
View File
@@ -7,7 +7,7 @@
#ifndef SCHEDULER_H #ifndef SCHEDULER_H
#define SCHEDULER_H #define SCHEDULER_H
struct cpu_status_t* scheduler_schedule(struct cpu_status_t* context); struct cpu_status* scheduler_schedule(struct cpu_status* context);
void scheduler_init(void); void scheduler_init(void);
#endif #endif
+4 -4
View File
@@ -1,4 +1,4 @@
/* /*
* @author xamidev <xamidev@riseup.net> * @author xamidev <xamidev@riseup.net>
* @brief Spinlock implementation * @brief Spinlock implementation
* @license GPL-3.0-only * @license GPL-3.0-only
@@ -10,13 +10,13 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
struct spinlock_t struct spinlock
{ {
bool locked; bool locked;
uint64_t rflags; uint64_t rflags;
}; };
void spinlock_acquire(struct spinlock_t* lock); void spinlock_acquire(struct spinlock* lock);
void spinlock_release(struct spinlock_t* lock); void spinlock_release(struct spinlock* lock);
#endif #endif
+65
View File
@@ -0,0 +1,65 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Undefined behavior sanitization hooks
* @license GPL-3.0-only
*/
#ifndef UBSAN_H
#define UBSAN_H
#include <stdint.h>
struct ubsan_source_location
{
const char* filename;
uint32_t line;
uint32_t column;
};
struct ubsan_type_descriptor
{
uint16_t type_kind;
uint16_t type_info;
char type_name[];
};
struct ubsan_type_mismatch_v1_data
{
struct ubsan_source_location location;
struct ubsan_type_descriptor* type;
unsigned char log_alignment;
unsigned char type_check_kind;
};
struct ubsan_pointer_overflow_data
{
struct ubsan_source_location location;
};
struct ubsan_shift_out_of_bounds_data
{
struct ubsan_source_location location;
struct ubsan_type_descriptor* lhs_type;
struct ubsan_type_descriptor* rhs_type;
};
struct ubsan_invalid_value_data
{
struct ubsan_source_location location;
struct ubsan_type_descriptor* type;
};
struct ubsan_out_of_bounds_data
{
struct ubsan_source_location location;
struct ubsan_type_descriptor* array_type;
struct ubsan_type_descriptor* index_type;
};
struct ubsan_overflow_data
{
struct ubsan_source_location location;
struct ubsan_type_descriptor* type;
};
#endif
+3
View File
@@ -12,5 +12,8 @@
char *strcpy(char *dest, const char *src); char *strcpy(char *dest, const char *src);
char *strcat(char *dest, const char *src); char *strcat(char *dest, const char *src);
void strncpy(char* dst, const char* src, size_t n); void strncpy(char* dst, const char* src, size_t n);
int strncmp(const char* s1, const char* s2, size_t n);
size_t strlen(const char* str);
int atoi(const char* str);
#endif #endif
+25
View File
@@ -0,0 +1,25 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Date helper functions
* @license GPL-3.0-only
*/
#ifndef DATE_H
#define DATE_H
#include <stdint.h>
struct date {
uint64_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
};
struct date date_timestamp_to_date(uint64_t timestamp);
struct date date_now();
#endif
+1
View File
@@ -6,3 +6,4 @@ interface_branding: Welcome to the PepperOS disk!
comment: Default configuration (warning: spicy) comment: Default configuration (warning: spicy)
path: boot():/boot/pepperk path: boot():/boot/pepperk
module_path: boot():/boot/initfs.tar
+27
View File
@@ -6,6 +6,8 @@
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include <kernel.h>
#include <string/string.h>
/* /*
* cpuid - Wrapper for CPUID instruction * cpuid - Wrapper for CPUID instruction
@@ -19,3 +21,28 @@ void cpuid(uint32_t leaf, uint32_t* eax, uint32_t* ebx, uint32_t* ecx, uint32_t*
{ {
__asm__ volatile("cpuid" : "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx) : "a"(leaf)); __asm__ volatile("cpuid" : "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx) : "a"(leaf));
} }
/*
* cpuid_get_vendor_string - Get the CPU vendor string
* @str: String at least 13 bytes long (for output)
*
* Return:
* %0 - on success
*/
int cpuid_get_vendor_string(char* str)
{
uint32_t eax, ebx, ecx, edx;
cpuid(0, &eax, &ebx, &ecx, &edx);
char output[13] = {0};
uint32_t regs[3] = {ebx, edx, ecx};
for (unsigned int j=0; j<3; j++) {
for (unsigned int i=0; i<4; i++) {
output[4*j+i] = (char)((regs[j] >> 8*i) & 0xff);
}
}
strncpy(str, output, 13);
return 0;
}
+138
View File
@@ -0,0 +1,138 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Global Descriptor Table and Task State Segment setup
* @license GPL-3.0-only
*/
#include <arch/gdt.h>
#include <stdint.h>
#include <io/serial/serial.h>
#include <kernel.h>
// Descriptors are 8-byte wide (64bits)
// So the selectors will be (in bytes): 0x0, 0x8, 0x10, 0x18, etc..
uint64_t gdt_entries[NUM_GDT_ENTRIES];
struct GDTR gdtr;
struct tss tss = {0};
/*
* 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
// and 0x10 is the kernel data selector
asm volatile (
"mov $0x10, %%ax \n" // Reload segments with kernel data selector
"mov %%ax, %%ds \n"
"mov %%ax, %%es \n"
"mov %%ax, %%fs \n"
"mov %%ax, %%gs \n"
"mov %%ax, %%ss \n"
"pushq $0x8 \n" // CS reload
"lea 1f(%%rip), %%rax \n"
"push %%rax \n"
"lretq \n"
"1: \n" // Execution continues here after CS reload
:
:
: "rax", "memory"
);
}
/*
* get_set_entry - Sets a GDT entry
* @num: Number of the entry (index in GDT)
* @flags: Flags (Granularity, Size, Long mode)
* @access: Access byte (contains Descriptor Privilege Level)
*
* This function fills a GDT entry with the specified @flags
* and @access byte. The base and limit fields are left to zero
* because we don't use segmentation for memory management.
*/
static void gdt_set_entry(int num, uint8_t flags, uint8_t access)
{
uint64_t gdt_entry = 0;
gdt_entry |= (access << 8);
gdt_entry |= (flags << 20);
// Rest (base, limit) is always zero
gdt_entries[num] = gdt_entry << 32;
}
/*
* gdt_set_tss - Setup the TSS entry in the GDT
* @num: Number of the entry (index in GDT)
*
* This function sets up a Task State Segment entry
* in the Global Descriptor Table.
*
* The entry is 128-bit long, so it actually takes
* two 64-bit GDT entries.
*/
static void gdt_set_tss(int num)
{
uint64_t tss_base = (uint64_t)&tss;
uint64_t tss_limit = sizeof(struct tss) - 1;
tss.iopb = sizeof(struct tss);
uint64_t tss_low = 0;
tss_low |= (tss_limit & 0xFFFFULL);
tss_low |= (tss_base & 0xFFFFFFULL) << 16;
tss_low |= 0x89ULL << 40;
tss_low |= ((tss_limit >> 16) & 0xFULL) << 48;
tss_low |= ((tss_base >> 24) & 0xFFULL) << 56;
uint64_t tss_high = (tss_base >> 32) & 0xFFFFFFFFULL;
gdt_entries[num] = tss_low;
gdt_entries[num + 1] = tss_high;
}
/*
* 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()
{
gdt_set_entry(0, 0, 0); // Null descriptor (0x0)
gdt_set_entry(1, 0xA, 0x9B); // Kernel code (0x8)
gdt_set_entry(2, 0xC, 0x93); // Kernel data (0x10)
gdt_set_entry(3, 0xA, 0xFB); // User code (0x18)
gdt_set_entry(4, 0xC, 0xF3); // User data (0x20)
gdt_set_tss(5); // TSS (0x28)
// The -1 subtraction is some wizardry explained in the OSDev wiki -> GDT
gdtr.limit = NUM_GDT_ENTRIES * sizeof(uint64_t) - 1;
gdtr.address = (uint64_t)gdt_entries;
// Load the GDT we created, flush the old one
gdt_load();
gdt_flush();
// Load task register with new TSS
asm volatile("ltr %%ax" : : "a"(TSS_SEGMENT) : "memory");
DEBUG("GDT initialized");
}
+9
View File
@@ -32,6 +32,8 @@ global vector_19_handler
global vector_20_handler global vector_20_handler
global vector_21_handler global vector_21_handler
global vector_128_handler
interrupt_stub: interrupt_stub:
; We'll push all general-purpose registers to the stack, ; We'll push all general-purpose registers to the stack,
; so they're intact and don't bother the code that was ; so they're intact and don't bother the code that was
@@ -313,3 +315,10 @@ vector_33_handler:
push qword 0 push qword 0
push qword 33 push qword 33
jmp interrupt_stub jmp interrupt_stub
; Syscall Interrupt (0x80)
align 16
vector_128_handler:
push qword 0
push qword 128
jmp interrupt_stub
+28 -10
View File
@@ -21,9 +21,13 @@ struct idtr idt_reg;
// Address to our first interrupt handler // Address to our first interrupt handler
extern char vector_0_handler[]; extern char vector_0_handler[];
extern char vector_128_handler[];
// Timer ticks // Timer ticks
extern volatile uint64_t ticks; extern volatile uint64_t ticks;
extern struct init_status init;
/* /*
* idt_set_entry - Sets an Interrupt Descriptor Table entry * idt_set_entry - Sets an Interrupt Descriptor Table entry
* @vector: Vector number in the IDT * @vector: Vector number in the IDT
@@ -72,6 +76,9 @@ void idt_init()
// Each vector handler is 16-byte aligned, so <vector_no>*16 = address of that handler // Each vector handler is 16-byte aligned, so <vector_no>*16 = address of that handler
idt_set_entry(i, vector_0_handler + (i*16), 0); idt_set_entry(i, vector_0_handler + (i*16), 0);
} }
idt_set_entry(0x80, vector_128_handler, 3);
idt_load(&idt); idt_load(&idt);
DEBUG("IDT initialized"); DEBUG("IDT initialized");
} }
@@ -101,7 +108,7 @@ static inline uint64_t read_cr2(void)
* Also displays an interpretation of the thrown error code. * Also displays an interpretation of the thrown error code.
* Then halts the system. We could implement demand paging later. * Then halts the system. We could implement demand paging later.
*/ */
static void page_fault_handler(struct cpu_status_t* ctx) static void page_fault_handler(struct cpu_status* ctx)
{ {
// It could be used to remap pages etc. to fix the fault, but right now what I'm more // It could be used to remap pages etc. to fix the fault, but right now what I'm more
// interested in is getting more info out of those numbers cause i'm lost each time i have // interested in is getting more info out of those numbers cause i'm lost each time i have
@@ -119,6 +126,19 @@ static void page_fault_handler(struct cpu_status_t* ctx)
CHECK_BIT(ctx->error_code, 7) ? " SGX_VIOLATION" : "", CHECK_BIT(ctx->error_code, 7) ? " SGX_VIOLATION" : "",
cr2); cr2);
if (init.all) {
printf("\x1b[38;5;231mPage Fault at rip=0x%p, err=%u (%s%s%s%s%s%s%s%s) when accessing addr=0x%p\x1b[0m", ctx->iret_rip, ctx->error_code,
CHECK_BIT(ctx->error_code, 0) ? "PAGE_PROTECTION_VIOLATION " : "PAGE_NOT_PRESENT ",
CHECK_BIT(ctx->error_code, 1) ? "ON_WRITE " : "ON_READ ",
CHECK_BIT(ctx->error_code, 2) ? "IN_USER_MODE" : "IN_KERNEL_MODE",
CHECK_BIT(ctx->error_code, 3) ? " WAS_RESERVED" : "",
CHECK_BIT(ctx->error_code, 4) ? " ON_INSTRUCTION_FETCH" : "",
CHECK_BIT(ctx->error_code, 5) ? " PK_VIOLATION" : "",
CHECK_BIT(ctx->error_code, 6) ? " ON_SHADOWSTACK_ACCESS" : "",
CHECK_BIT(ctx->error_code, 7) ? " SGX_VIOLATION" : "",
cr2);
}
panic(ctx, "page fault"); panic(ctx, "page fault");
} }
@@ -129,7 +149,7 @@ static void page_fault_handler(struct cpu_status_t* ctx)
* Shows detail about a General Protection Fault, * Shows detail about a General Protection Fault,
* and what may have caused it. Halts the system. * and what may have caused it. Halts the system.
*/ */
static void gp_fault_handler(struct cpu_status_t* ctx) static void gp_fault_handler(struct cpu_status* ctx)
{ {
DEBUG("\x1b[38;5;231mGeneral Protection Fault at rip=0x%p, err=%u (%s)\x1b[0m", DEBUG("\x1b[38;5;231mGeneral Protection Fault at rip=0x%p, err=%u (%s)\x1b[0m",
ctx->iret_rip, ctx->iret_rip,
@@ -154,12 +174,6 @@ static void gp_fault_handler(struct cpu_status_t* ctx)
panic(ctx, "gp fault"); panic(ctx, "gp fault");
} }
// DEBUG
void kbdproc_main(void* arg)
{
printf("Key pressed/released.\r\n");
}
/* /*
* interrupt_dispatch - Interrupt dispatcher * interrupt_dispatch - Interrupt dispatcher
* @context: CPU context * @context: CPU context
@@ -171,7 +185,7 @@ void kbdproc_main(void* arg)
* Return: * Return:
* <context> - CPU context after interrupt * <context> - CPU context after interrupt
*/ */
struct cpu_status_t* interrupt_dispatch(struct cpu_status_t* context) struct cpu_status* interrupt_dispatch(struct cpu_status* context)
{ {
if (context == NULL) { if (context == NULL) {
panic(NULL, "Interrupt dispatch recieved NULL context!"); panic(NULL, "Interrupt dispatch recieved NULL context!");
@@ -258,10 +272,14 @@ struct cpu_status_t* interrupt_dispatch(struct cpu_status_t* context)
case 33: // Keyboard Interrupt case 33: // Keyboard Interrupt
keyboard_handler(); keyboard_handler();
process_create("keyboard-initiated", kbdproc_main, NULL); // DEBUG //process_create("keyboard-initiated", kbdproc_main, NULL); // DEBUG
outb(0x20, 0x20); outb(0x20, 0x20);
break; break;
case 128: // Syscall Interrupt (0x80)
syscall_handler(context);
break;
default: default:
DEBUG("Unexpected Interrupt"); DEBUG("Unexpected Interrupt");
break; break;
+69 -1
View File
@@ -4,10 +4,11 @@
* @license GPL-3.0-only * @license GPL-3.0-only
*/ */
#include <mem/gdt.h> #include <arch/gdt.h>
#include <stdint.h> #include <stdint.h>
#include <arch/x86.h> #include <arch/x86.h>
#include <kernel.h> #include <kernel.h>
#include <mem/utils.h>
/* /*
* x86_overwrite_pat - Set PAT to WC * x86_overwrite_pat - Set PAT to WC
@@ -28,6 +29,26 @@ static void x86_overwrite_pat()
wrmsr(0x277, pat); wrmsr(0x277, pat);
} }
/*
* x86_enable_fpu - Enable Floating Point Unit
*
* This function enables the Floating Point Unit,
* which allows the CPU to do floating point
* operations.
*
* Here we do not check for FPU support but we
* should. However most processors support it.
*/
static void x86_enable_fpu()
{
size_t cr4;
__asm__ volatile("mov %%cr4, %0" : "=r"(cr4));
cr4 |= 0x200;
__asm__ volatile("mov %0, %%cr4" :: "r"(cr4));
uint16_t cw = 0x37F; // control word
asm volatile("fldcw %0" :: "m"(cw));
}
/* /*
* x86_arch_init - Initialize x86 CPU structures * x86_arch_init - Initialize x86 CPU structures
* *
@@ -41,6 +62,53 @@ static void x86_overwrite_pat()
void x86_arch_init() void x86_arch_init()
{ {
x86_overwrite_pat(); x86_overwrite_pat();
x86_enable_fpu();
x86_cpu_identification();
idt_init(); idt_init();
gdt_init(); gdt_init();
} }
/*
* cpu_supports_brandstring - Does the CPU support brand strings?
*
* Return:
* true - if it does
* false - if it doesn't
*/
bool cpu_supports_brandstring() {
uint32_t eax, ebx, ecx, edx;
cpuid(0x80000000, &eax, &ebx, &ecx, &edx);
if (eax < 0x80000004) {
return false;
} else {
return true;
}
}
/*
* x86_cpu_idenfitication - get info about the CPU
*
* This function displays the CPU vendor name or the
* extended "brand string" if it's supported, on
* debug output.
*/
void x86_cpu_identification()
{
if (cpu_supports_brandstring()) {
uint32_t regs[12];
// Some CPUs don't return null-terminated values so we do it as a failsafe default
char str[sizeof(regs)+1] = {0};
cpuid(0x80000002, &regs[0], &regs[1], &regs[2], &regs[3]);
cpuid(0x80000003, &regs[4], &regs[5], &regs[6], &regs[7]);
cpuid(0x80000004, &regs[8], &regs[9], &regs[10], &regs[11]);
memcpy(str, regs, sizeof(regs));
str[sizeof(regs)] = '\0';
DEBUG("CPU: %s", str);
} else {
char vendor_string[13] = {0};
cpuid_get_vendor_string(vendor_string);
DEBUG("CPU vendor is: %s", vendor_string);
}
}
+28 -2
View File
@@ -9,7 +9,12 @@
* @license GPL-3.0-only * @license GPL-3.0-only
*/ */
#include <kernel.h>
#include <limine.h> #include <limine.h>
#include <stddef.h>
__attribute__((used, section(".limine_requests_start")))
volatile LIMINE_REQUESTS_START_MARKER;
__attribute__((used, section(".limine_requests"))) __attribute__((used, section(".limine_requests")))
volatile struct limine_framebuffer_request framebuffer_request = { volatile struct limine_framebuffer_request framebuffer_request = {
@@ -35,8 +40,29 @@ volatile struct limine_kernel_address_request kerneladdr_request = {
.revision = 0 .revision = 0
}; };
__attribute__((used, section(".limine_requests_start"))) __attribute__((used, section(".limine_requests")))
volatile LIMINE_REQUESTS_START_MARKER; volatile struct limine_boot_time_request date_request = {
.id = LIMINE_BOOT_TIME_REQUEST,
.revision = 0
};
__attribute__((used, section(".limine_requests")))
volatile struct limine_module_request module_request = {
.id = LIMINE_MODULE_REQUEST,
.revision = 0
};
__attribute__((used, section(".limine_requests_end"))) __attribute__((used, section(".limine_requests_end")))
volatile LIMINE_REQUESTS_END_MARKER; volatile LIMINE_REQUESTS_END_MARKER;
void populate_boot_context(struct boot_context* ctx)
{
// Populate boot context
// This stays valid only if the BOOTLOADER_RECLAIMABLE regions are preserved
ctx->fb = framebuffer_request.response ? framebuffer_request.response->framebuffers[0] : NULL;
ctx->mmap = memmap_request.response ? memmap_request.response : NULL;
ctx->hhdm = hhdm_request.response ? hhdm_request.response : NULL;
ctx->kaddr = kerneladdr_request.response ? kerneladdr_request.response : NULL;
ctx->bootdate = date_request.response ? date_request.response : NULL;
ctx->module = module_request.response ? module_request.response : NULL;
}
+2 -2
View File
@@ -18,7 +18,7 @@ extern int panic_count;
*/ */
void read_rflags(uint64_t rflags) 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", 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",
CHECK_BIT(rflags, 0) ? "CF " : "", /*carry flag*/ CHECK_BIT(rflags, 0) ? "CF " : "", /*carry flag*/
CHECK_BIT(rflags, 2) ? "PF " : "", /*parity flag*/ CHECK_BIT(rflags, 2) ? "PF " : "", /*parity flag*/
CHECK_BIT(rflags, 4) ? "AF " : "", /*auxiliary carry flag*/ CHECK_BIT(rflags, 4) ? "AF " : "", /*auxiliary carry flag*/
@@ -47,7 +47,7 @@ void read_rflags(uint64_t rflags)
* Will display to terminal if it is initialized, otherwise serial only. * Will display to terminal if it is initialized, otherwise serial only.
* Can be called with or without a CPU context. * Can be called with or without a CPU context.
*/ */
void panic(struct cpu_status_t* ctx, const char* str) void panic(struct cpu_status* ctx, const char* str)
{ {
CLEAR_INTERRUPTS; CLEAR_INTERRUPTS;
panic_count += 1; panic_count += 1;
+147
View File
@@ -0,0 +1,147 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Initial TAR filesystem (read-only)
* @license GPL-3.0-only
*/
#include <sched/process.h>
#include <limine.h>
#include <fs/initfs.h>
#include <kernel.h>
#include <mem/utils.h>
#include <string/string.h>
void* archive_start_addr;
uint64_t archive_size;
/*
* tar_oct2bin - convert octal size string to an integer
* @str: octal size string
* @size: size of string
*
* Return:
* $n - file size as an integer
*/
int tar_oct2bin(unsigned char* str, int size)
{
int n = 0;
unsigned char* c = str;
while (size-- > 0) {
n *= 8;
n += *c - '0';
c++;
}
return n;
}
/*
* tar_lookup - lookup a file in the TAR file
* @archive: pointer to beginning of the archive
* @filename: file to lookup (absolute path)
* @out: where to store file data if found
*
* Return:
* $filesize - size of the file, if found
* $-ENOENT - file not found
*/
int tar_lookup(unsigned char* archive, char* filename, char** out)
{
unsigned char *ptr = archive;
while (!memcmp(ptr + 257, "ustar", 5)) {
int filesize = tar_oct2bin(ptr + 0x7c, 11);
if (!memcmp(ptr, filename, strlen(filename) + 1)) {
*out = (char*)(ptr + 512);
return filesize;
}
ptr += (((filesize + 511) / 512) + 1) * 512;
}
return -ENOENT;
}
/*
* tar_list - list all files present in archive
*/
void tar_list()
{
printf("++ Contents of initial filesystem ++\r\n\r\n");
unsigned char *ptr = archive_start_addr;
while (!memcmp(ptr + 257, "ustar", 5)) {
int filesize = tar_oct2bin(ptr + 0x7c, 11);
char* filename = (char*)ptr;
printf("file: %s\r\n", filename);
ptr += (((filesize + 511) / 512) + 1) * 512;
}
}
/*
* tar_read - read a file in the TAR archive
* @filename: file to read (absolute path)
* @out: where to store file data if found
* @count: amount of bytes to read
* @offset: read from byte offset (0 for none)
*
* Return:
* $filesize - size of the file, if found
* $-ENOENT - file not found
*/
int tar_read(char* filename, char* out, int count, int offset)
{
char* file_data;
int filesize = tar_lookup(archive_start_addr, filename, &file_data);
if (filesize <= 0) {
return filesize;
}
if (offset >= filesize) {
return -EINVAL;
}
int remaining = filesize - offset;
int to_read = remaining < count ? remaining : count;
memcpy(out, file_data + offset, to_read);
return to_read;
}
/*
* tar_exists - check if a file exists in the TAR archive
* @filename: file to check (absolute path)
*
* Return:
* $filesize - size of the file, if found
* $-ENOENT - file not found
*/
int tar_exists(const char* filename)
{
unsigned char* ptr = archive_start_addr;
while (!memcmp(ptr + 257, "ustar", 5)) {
int filesize = tar_oct2bin(ptr + 0x7c, 11);
if (!memcmp(ptr, filename, strlen(filename) + 1)) {
return filesize;
}
ptr += (((filesize + 511) / 512) + 1) * 512;
}
return -ENOENT;
}
/*
* initfs_init - initialize the TAR initial filesystem
* @tar_file: pointer to the Limine-loaded archive
*
* Return:
* $0 - on success
*/
int initfs_init(struct limine_file* tar_file)
{
archive_start_addr = tar_file->address;
archive_size = tar_file->size;
DEBUG("Loaded TAR initial filesystem (initfs.tar)");
return 0;
}
+13 -7
View File
@@ -289,15 +289,21 @@ int keyboard_getline(char* output, size_t size)
// Read until Enter is pressed // Read until Enter is pressed
while ((c = keyboard_getchar()) != 0x0A) { while ((c = keyboard_getchar()) != 0x0A) {
if (index == size-1) { if (c == '\b') {
output[index] = c; if (index > 0) {
output[index+1] = '\0'; index--;
return index; output[index] = '\0';
printf(" \b");
} }
output[index] = c; continue;
index++;
} }
output[index+1] = '\0';
if (index >= size-1) {
continue;
}
output[index++] = c;
}
output[index] = '\0';
return index; return index;
} }
+1 -1
View File
@@ -11,7 +11,7 @@
extern struct init_status init; extern struct init_status init;
extern int panic_count; extern int panic_count;
struct spinlock_t serial_lock = {0}; struct spinlock serial_lock = {0};
/* /*
* outb - Writes a byte to a CPU port * outb - Writes a byte to a CPU port
+82 -2
View File
@@ -29,8 +29,8 @@ because this shitty implementation will be replaced one day by Flanterm
extern struct flanterm_context* ft_ctx; extern struct flanterm_context* ft_ctx;
extern struct init_status init; extern struct init_status init;
struct spinlock_t term_lock = {0}; struct spinlock term_lock = {0};
struct spinlock_t printf_lock = {0}; struct spinlock printf_lock = {0};
extern int panic_count; extern int panic_count;
@@ -50,9 +50,57 @@ void internal_putc(int c, void *_)
if (init.terminal) { if (init.terminal) {
if (panic_count == 0) { if (panic_count == 0) {
spinlock_acquire(&term_lock); spinlock_acquire(&term_lock);
if (ch == '\n') {
char cr = '\r';
flanterm_write(ft_ctx, &cr, 1);
}
flanterm_write(ft_ctx, &ch, 1); flanterm_write(ft_ctx, &ch, 1);
spinlock_release(&term_lock); spinlock_release(&term_lock);
} else { } else {
if (ch == '\n') {
char cr = '\r';
flanterm_write(ft_ctx, &cr, 1);
}
flanterm_write(ft_ctx, &ch, 1);
}
}
if (init.serial) {
if (ch == '\n') {
skputc('\r');
}
skputc(ch);
}
}
/*
* debug_putc - Internal DEBUG putchar function
* @c: char to print
* @_: (unused, for nanoprintf)
*
* Prints a character to the terminal if it's ready and if
* the kernel is still initializing, and also always to the
* serial interface if it's ready.
*/
void debug_putc(int c, void *_)
{
(void)_;
char ch = (char)c;
if (init.terminal && (!init.all || panic_count > 0)) {
if (panic_count == 0) {
spinlock_acquire(&term_lock);
if (ch == '\n') {
char cr = '\r';
flanterm_write(ft_ctx, &cr, 1);
}
flanterm_write(ft_ctx, &ch, 1);
spinlock_release(&term_lock);
} else {
if (ch == '\n') {
char cr = '\r';
flanterm_write(ft_ctx, &cr, 1);
}
flanterm_write(ft_ctx, &ch, 1); flanterm_write(ft_ctx, &ch, 1);
} }
} }
@@ -96,6 +144,38 @@ int printf(const char* fmt, ...)
return -1; return -1;
} }
/*
* kprintf - Fromatted DEBUG printing
* @fmt: format string
* @...: variadic arguments
*
* Wrapper for nanoprintf; to be used only for
* kernel/debug messages.
*
* Return:
* <ret> - number of characters sent to the callback
* %-1 - error
*/
int kprintf(const char* fmt, ...)
{
if (panic_count == 0) {
spinlock_acquire(&printf_lock);
va_list args;
va_start(args, fmt);
int ret = npf_vpprintf(debug_putc, NULL, fmt, args);
va_end(args);
spinlock_release(&printf_lock);
return ret;
} else {
va_list args;
va_start(args, fmt);
int ret = npf_vpprintf(debug_putc, NULL, fmt, args);
va_end(args);
return ret;
}
return -1;
}
/* /*
* kputs - Kernel puts * kputs - Kernel puts
* @str: String to write * @str: String to write
+172
View File
@@ -0,0 +1,172 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief PepperOS kernel shell
* @license GPL-3.0-only
*/
#include "fs/initfs.h"
#include <io/term/term.h>
#include <config.h>
#include <io/kbd/ps2.h>
#include <string/string.h>
#include <stdint.h>
#include <kernel.h>
#include <time/date.h>
#include <mem/kheap.h>
#include <sched/process.h>
__attribute__((noinline))
void smash_it()
{
char buf[16]; (void)buf;
for (size_t i=0; i<256; i++) {
buf[i] = (char)i;
}
}
extern struct process* processes_list;
void ps()
{
printf("pid\tname\tstatus\n");
struct process* curr = processes_list;
while (curr != NULL) {
char* status;
switch (curr->status) {
case READY: status = "READY"; break;
case RUNNING: status = "RUNNING"; break;
case DEAD: status = "DEAD"; break;
default: status = "N/A"; break;
}
printf("%u\t%s\t%s\n", curr->pid, curr->name, status);
if (curr->next != NULL) {
curr = curr->next;
} else {
break;
}
}
}
void kill()
{
char input_buf[11] = {0};
printf("pid> ");
keyboard_getline(input_buf, 10);
int pid = atoi(input_buf);
struct process* curr = processes_list;
while (curr != NULL) {
if (curr->pid == (size_t)pid) {
curr->status = DEAD; // equivalent of SIGKILL (no clean termination like SIGTERM)
printf("killed %d\n", pid);
break;
}
if (curr->next != NULL) {
curr = curr->next;
} else {
printf("couldn't find process with PID %d\n", pid);
break;
}
}
}
/*
* pedicel_main - Kernel shell main function
* @arg: argument (optional)
*
* This is the entry point for the kernel shell process.
* It is used to start programs and to test different things
* on different real hardware easily.
*
* Named after the root part of the pepper.
*/
void pedicel_main(void* arg)
{
(void)arg;
printf("Welcome to the kernel shell!\r\nType 'help' for a list of commands.\r\n");
for (;;) {
char input_buf[PEDICEL_INPUT_SIZE] = {0};
printf(PEDICEL_PROMPT);
keyboard_getline(input_buf, PEDICEL_INPUT_SIZE);
if (strncmp(input_buf, "help", 4) == 0) {
printf("++ shell builtins ++\r\n\r\n"
"\tclear - clear the screen\n"
"\tpanic - trigger a test panic\n"
"\tsyscall - trigger int 0x80\n"
"\tpf - trigger a page fault\n"
"\tnow - get current date\n"
"\tsmash - smash the stack\n"
"\tmem - get used heap info\n"
"\tload - load an user executable\n"
"\tlist - list initfs.tar contents\n"
"\tps - list running processes\n"
"\tkill - kill a running process by PID\n");
continue;
}
if (strncmp(input_buf, "", 1) == 0) {
continue;
}
if (strncmp(input_buf, "clear", 5) == 0) {
printf("\x1b[2J\x1b[H");
continue;
}
if (strncmp(input_buf, "panic", 5) == 0) {
panic(NULL, "test panic");
}
if (strncmp(input_buf, "syscall", 7) == 0) {
__asm__ volatile("mov $0x00, %rdi");
__asm__ volatile("int $0x80");
continue;
}
if (strncmp(input_buf, "pf", 2) == 0) {
volatile uint64_t* fault = (uint64_t*)0xdeadbeef;
fault[0] = 1;
}
if (strncmp(input_buf, "now", 3) == 0) {
struct date now = date_now();
printf("Now is %02u:%02u:%02u on %u/%u/%u\r\n", now.hour, now.minute,
now.second, now.day, now.month, now.year);
continue;
}
if (strncmp(input_buf, "smash", 5) == 0) {
smash_it();
continue;
}
if (strncmp(input_buf, "mem", 3) == 0) {
kheap_info();
continue;
}
if (strncmp(input_buf, "load", 4) == 0) {
loader_load_raw();
continue;
}
if (strncmp(input_buf, "list", 4) == 0) {
tar_list();
continue;
}
if (strncmp(input_buf, "ps", 2) == 0) {
ps();
continue;
}
if (strncmp(input_buf, "kill", 4) == 0) {
kill();
continue;
}
printf("%s: command not found\r\n", input_buf);
}
}
+32
View File
@@ -0,0 +1,32 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Executable loader
* @license GPL-3.0-only
*/
#include <stddef.h>
#include <fs/initfs.h>
#include <kernel.h>
#include <sched/process.h>
#include <io/kbd/ps2.h>
#include <string/string.h>
extern void* archive_start_addr;
int loader_load_raw()
{
char input_buf[PEDICEL_INPUT_SIZE] = {0};
do {
printf("file> ");
keyboard_getline(input_buf, PEDICEL_INPUT_SIZE);
} while (strncmp(input_buf, "", 1) == 0);
char* data = NULL;
int sz = tar_lookup(archive_start_addr, input_buf,&data);
if (sz > 0) {
process_create_user_raw(data, sz, input_buf);
return 0; // TODO: should return something else on error
}
printf("Couldn't load file '%s'\r\n", input_buf);
return 1;
}
+21 -25
View File
@@ -4,13 +4,12 @@
* @license GPL-3.0-only * @license GPL-3.0-only
*/ */
#include "arch/x86.h"
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <limine.h> #include <limine.h>
#include <io/term/term.h> #include <io/term/term.h>
#include <io/serial/serial.h> #include <io/serial/serial.h>
#include <mem/gdt.h> #include <arch/gdt.h>
#include <mem/utils.h> #include <mem/utils.h>
#include <kernel.h> #include <kernel.h>
#include <time/timer.h> #include <time/timer.h>
@@ -25,6 +24,8 @@
#include <io/term/flanterm.h> #include <io/term/flanterm.h>
#include <io/term/flanterm_backends/fb.h> #include <io/term/flanterm_backends/fb.h>
#include <arch/x86.h> #include <arch/x86.h>
#include <boot/boot.h>
#include <fs/initfs.h>
// Limine version used // Limine version used
__attribute__((used, section(".limine_requests"))) __attribute__((used, section(".limine_requests")))
@@ -61,32 +62,23 @@ extern volatile struct limine_framebuffer_request framebuffer_request;
extern volatile struct limine_memmap_request memmap_request; extern volatile struct limine_memmap_request memmap_request;
extern volatile struct limine_hhdm_request hhdm_request; extern volatile struct limine_hhdm_request hhdm_request;
extern volatile struct limine_kernel_address_request kerneladdr_request; extern volatile struct limine_kernel_address_request kerneladdr_request;
extern volatile struct limine_boot_time_request date_request;
extern volatile struct limine_module_request module_request;
extern struct process_t* processes_list; struct limine_file* file;
extern struct process_t* current_process;
struct process_t* idle_proc;
// Never gets executed although pedicel is scheduled? extern struct process* processes_list;
void pedicel_main(void* arg) extern struct process* current_process;
{ struct process* idle_proc;
printf("\n\n\rWelcome to PepperOS! Pedicel speaking.\r\nNothing left to do, let's go idle!\r\n");
}
void idle_main(void* arg) void idle_main(void* arg)
{ {
(void)arg;
for (;;) { for (;;) {
asm("hlt"); asm("hlt");
} }
} }
void thing_main(void* arg)
{
printf("What's your name, pal? ");
char name[10];
keyboard_getline(name, 10);
printf("\r\n{%s} is such a nice name!\r\n", name);
}
extern uintptr_t kheap_start; extern uintptr_t kheap_start;
/* /*
@@ -102,11 +94,7 @@ void kmain()
CLEAR_INTERRUPTS; CLEAR_INTERRUPTS;
if (!LIMINE_BASE_REVISION_SUPPORTED) hcf(); if (!LIMINE_BASE_REVISION_SUPPORTED) hcf();
// Populate boot context populate_boot_context(&boot_ctx);
boot_ctx.fb = framebuffer_request.response ? framebuffer_request.response->framebuffers[0] : NULL;
boot_ctx.mmap = memmap_request.response ? memmap_request.response : NULL;
boot_ctx.hhdm = hhdm_request.response ? hhdm_request.response : NULL;
boot_ctx.kaddr = kerneladdr_request.response ? kerneladdr_request.response : NULL;
term_init(); term_init();
serial_init(); serial_init();
@@ -124,11 +112,19 @@ void kmain()
process_init(); process_init();
idle_proc = process_create("idle", (void*)idle_main, 0); idle_proc = process_create("idle", (void*)idle_main, 0);
process_create("pedicel", (void*)pedicel_main, 0);
process_create("thing", thing_main, NULL);
if (!boot_ctx.module) {
panic(NULL, "could not load initfs.tar :(");
}
if (boot_ctx.module->module_count == 1) {
initfs_init(boot_ctx.module->modules[0]);
}
process_create("kshell", (void*)pedicel_main, 0);
scheduler_init(); scheduler_init();
printf(PEPPEROS_SPLASH); printf(PEPPEROS_SPLASH);
init.all = true;
idle(); idle();
} }
-107
View File
@@ -1,107 +0,0 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Global Descriptor Table (for legacy reasons)
* @license GPL-3.0-only
*/
#include <mem/gdt.h>
#include <stdint.h>
#include <io/serial/serial.h>
#include <kernel.h>
// Descriptors are 8-byte wide (64bits)
// So the selectors will be (in bytes): 0x0, 0x8, 0x10, 0x18, etc..
uint64_t gdt_entries[NUM_GDT_ENTRIES];
struct GDTR gdtr;
/*
* gdt_load - Loads Global Descriptor Table
*/
static void gdt_load()
{
asm("lgdt %0" : : "m"(gdtr));
}
/*
* gdt_flush - Flushes the Global Descriptor Table
*
* This function loads new Segment Selectors to make
* the GDT changes take effect
*/
static void gdt_flush()
{
// Here, 0x8 is the kernel code selector
// and 0x10 is the kernel data selector
asm volatile (
"mov $0x10, %%ax \n" // Reload segments with kernel data selector
"mov %%ax, %%ds \n"
"mov %%ax, %%es \n"
"mov %%ax, %%fs \n"
"mov %%ax, %%gs \n"
"mov %%ax, %%ss \n"
"pushq $0x8 \n" // CS reload
"lea 1f(%%rip), %%rax \n"
"push %%rax \n"
"lretq \n"
"1: \n" // Execution continues here after CS reload
:
:
: "rax", "memory"
);
}
/*
* 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)
gdt_entries[0] = 0;
// Kernel code segment
uint64_t kernel_code = 0;
kernel_code |= 0b1101 << 8; // Selector type: accessed, read-enable, no conforming
kernel_code |= 1 << 12; // not a system descriptor
kernel_code |= 0 << 13; // DPL field = 0
kernel_code |= 1 << 15; // Present
kernel_code |= 1 << 21; // Long mode
// Left shift 32 bits so we place our stuff in the upper 32 bits of the descriptor.
// The lower 32 bits contain limit and part of base and therefore are ignored in Long Mode
// (because we'll use paging; segmentation is used only for legacy)
gdt_entries[1] = kernel_code << 32;
uint64_t kernel_data = 0;
kernel_data |= 0b0011 << 8;
kernel_data |= 1 << 12;
kernel_data |= 0 << 13;
kernel_data |= 1 << 15;
kernel_data |= 1 << 21;
gdt_entries[2] = kernel_data << 32;
// We re-use the kernel descriptors here, and just update their DPL fields
// (Descriptor privilege level) from ring 0 -> to ring 3 (userspace)
uint64_t user_code = kernel_code | (3 << 13);
gdt_entries[3] = user_code;
uint64_t user_data = kernel_data | (3 << 13);
gdt_entries[4] = user_data;
// The -1 subtraction is some wizardry explained in the OSDev wiki -> GDT
gdtr.limit = NUM_GDT_ENTRIES * sizeof(uint64_t) - 1;
gdtr.address = (uint64_t)gdt_entries;
// Load the GDT we created, flush the old one
gdt_load();
gdt_flush();
DEBUG("GDT initialized");
}
+39 -10
View File
@@ -17,7 +17,7 @@ extern uint64_t kernel_virt_base;
uintptr_t kheap_start; uintptr_t kheap_start;
static struct heap_block_t* head = NULL; static struct heap_block* head = NULL;
static uintptr_t end; static uintptr_t end;
// Kernel root table (level 4) // Kernel root table (level 4)
@@ -55,8 +55,8 @@ void kheap_init()
end = current_addr; end = current_addr;
// Give linked list head its properties // Give linked list head its properties
head = (struct heap_block_t*)kheap_start; head = (struct heap_block*)kheap_start;
head->size = (end-kheap_start) - sizeof(struct heap_block_t); head->size = (end-kheap_start) - sizeof(struct heap_block);
head->free = true; head->free = true;
head->next = NULL; head->next = NULL;
DEBUG("Kernel heap initialized, head=0x%p, size=%u bytes", head, head->size); DEBUG("Kernel heap initialized, head=0x%p, size=%u bytes", head, head->size);
@@ -80,16 +80,16 @@ void* kmalloc(size_t size)
if (!size) return NULL; if (!size) return NULL;
size = ALIGN(size); size = ALIGN(size);
struct heap_block_t* curr = head; struct heap_block* curr = head;
while (curr) { while (curr) {
// Is block free and big enough for us? // Is block free and big enough for us?
if (curr->free && curr->size >= size) { if (curr->free && curr->size >= size) {
// We split the block if it is big enough // We split the block if it is big enough
if (curr->size >= size + sizeof(struct heap_block_t) + 16) { if (curr->size >= size + sizeof(struct heap_block) + 16) {
struct heap_block_t* split = (struct heap_block_t*)((uintptr_t)curr + sizeof(struct heap_block_t) + size); struct heap_block* split = (struct heap_block*)((uintptr_t)curr + sizeof(struct heap_block) + size);
split->size = curr->size - size - sizeof(struct heap_block_t); split->size = curr->size - size - sizeof(struct heap_block);
split->free = true; split->free = true;
split->next = curr->next; split->next = curr->next;
@@ -99,7 +99,7 @@ void* kmalloc(size_t size)
// Found a good block, we return it // Found a good block, we return it
curr->free = false; curr->free = false;
return (void*)((uintptr_t)curr + sizeof(struct heap_block_t)); return (void*)((uintptr_t)curr + sizeof(struct heap_block));
} }
// Continue browsing the list if nothing good was found yet // Continue browsing the list if nothing good was found yet
curr = curr->next; curr = curr->next;
@@ -127,11 +127,11 @@ void kfree(void* ptr)
if (!ptr) return; if (!ptr) return;
// Set it free! // Set it free!
struct heap_block_t* block = (struct heap_block_t*)((uintptr_t)ptr - sizeof(struct heap_block_t)); struct heap_block* block = (struct heap_block*)((uintptr_t)ptr - sizeof(struct heap_block));
block->free = true; block->free = true;
// merge adjacent free blocks (coalescing) // merge adjacent free blocks (coalescing)
struct heap_block_t* curr = head; struct heap_block* curr = head;
while (curr && curr->next) { while (curr && curr->next) {
if (curr->free && curr->next->free) { if (curr->free && curr->next->free) {
curr->size += sizeof(*curr) + curr->next->size; curr->size += sizeof(*curr) + curr->next->size;
@@ -158,3 +158,32 @@ void* kalloc_stack()
uint8_t* ptr = kmalloc(PROCESS_STACK_SIZE); // As it's out of kmalloc, stack is already mapped into kernel space 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; return ptr ? ptr+PROCESS_STACK_SIZE : NULL;
} }
/*
* kheap_info - Display heap info
*
* This function writes the size of the heap (total),
* the number of allocated bytes, and the number of
* free bytes to the standard output.
*/
void kheap_info()
{
uint64_t free_bytes = 0;
struct heap_block* curr = (struct heap_block*)kheap_start;
while (curr) {
if (curr->free == true) {
free_bytes += curr->size;
}
curr = curr->next;
}
uint64_t total = end-kheap_start;
printf("total=% 8u bytes (%u kB)\r\n"
"alloc=% 8u bytes (%u kB)\r\n"
" free=% 8u bytes (%u kB)\r\n",
total, (total)/1000,
total-free_bytes, (total-free_bytes)/1000,
free_bytes, free_bytes/1000);
}
+21 -5
View File
@@ -43,7 +43,7 @@ void load_cr3(uint64_t value) {
* This function is used to flush at least the TLB entrie(s) * This function is used to flush at least the TLB entrie(s)
* for the page that contains the <addr> address. * for the page that contains the <addr> address.
*/ */
static inline void invlpg(void *addr) void invlpg(void *addr)
{ {
asm volatile("invlpg (%0)" :: "r"(addr) : "memory"); asm volatile("invlpg (%0)" :: "r"(addr) : "memory");
} }
@@ -59,7 +59,7 @@ static inline void invlpg(void *addr)
* Return: * Return:
* <virt> - Pointer to allocated page table * <virt> - Pointer to allocated page table
*/ */
static uint64_t* alloc_page_table() uint64_t* alloc_page_table()
{ {
uint64_t* virt = (uint64_t*)PHYS_TO_VIRT(pmm_alloc()); uint64_t* virt = (uint64_t*)PHYS_TO_VIRT(pmm_alloc());
@@ -99,30 +99,46 @@ void paging_map_page(uint64_t* root_table, uint64_t virt, uint64_t phys, uint64_
uint64_t *pdpt, *pd, *pt; uint64_t *pdpt, *pd, *pt;
// Any parent entry on a userspace mapping must also carry PTE_USER,
// otherwise CPL3 accesses fault even if the final PTE is user.
uint64_t parent_flags = PTE_PRESENT | PTE_WRITABLE;
if (flags & PTE_USER) {
parent_flags |= PTE_USER;
}
// PML4 // PML4
// If the entry at index is not present, allocate enough space for it // If the entry at index is not present, allocate enough space for it
// then populate the entry with correct addr + flags // 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(); pdpt = alloc_page_table();
root_table[pml4_i] = VIRT_TO_PHYS(pdpt) | PTE_PRESENT | PTE_WRITABLE; root_table[pml4_i] = VIRT_TO_PHYS(pdpt) | parent_flags;
} else { } else {
pdpt = (uint64_t *)PHYS_TO_VIRT(root_table[pml4_i] & PTE_ADDR_MASK); pdpt = (uint64_t *)PHYS_TO_VIRT(root_table[pml4_i] & PTE_ADDR_MASK);
if (flags & PTE_USER) {
root_table[pml4_i] |= PTE_USER;
}
} }
// PDPT: same here // PDPT: same here
if (!(pdpt[pdpt_i] & PTE_PRESENT)) { if (!(pdpt[pdpt_i] & PTE_PRESENT)) {
pd = alloc_page_table(); pd = alloc_page_table();
pdpt[pdpt_i] = VIRT_TO_PHYS(pd) | PTE_PRESENT | PTE_WRITABLE; pdpt[pdpt_i] = VIRT_TO_PHYS(pd) | parent_flags;
} else { } else {
pd = (uint64_t *)PHYS_TO_VIRT(pdpt[pdpt_i] & PTE_ADDR_MASK); pd = (uint64_t *)PHYS_TO_VIRT(pdpt[pdpt_i] & PTE_ADDR_MASK);
if (flags & PTE_USER) {
pdpt[pdpt_i] |= PTE_USER;
}
} }
// PD: and here // PD: and here
if (!(pd[pd_i] & PTE_PRESENT)) { if (!(pd[pd_i] & PTE_PRESENT)) {
pt = alloc_page_table(); pt = alloc_page_table();
pd[pd_i] = VIRT_TO_PHYS(pt) | PTE_PRESENT | PTE_WRITABLE; pd[pd_i] = VIRT_TO_PHYS(pt) | parent_flags;
} else { } else {
pt = (uint64_t *)PHYS_TO_VIRT(pd[pd_i] & PTE_ADDR_MASK); pt = (uint64_t *)PHYS_TO_VIRT(pd[pd_i] & PTE_ADDR_MASK);
if (flags & PTE_USER) {
pd[pd_i] |= PTE_USER;
}
} }
// PT: finally, populate the page table entry // PT: finally, populate the page table entry
+240 -47
View File
@@ -13,66 +13,259 @@ in a specified virtual space
compared to the PMM which allocs/frees 4kb frames ("physical pages"). compared to the PMM which allocs/frees 4kb frames ("physical pages").
*/ */
#include "config.h"
#include <mem/vmm.h> #include <mem/vmm.h>
#include <mem/paging.h> #include <mem/paging.h>
#include <stddef.h> #include <stddef.h>
#include <mem/pmm.h> #include <mem/pmm.h>
#include <mem/utils.h>
#include <kernel.h> #include <kernel.h>
void* vmm_pt_root = 0;
// Linked list head for virtual memory objects
struct vm_object* vm_objs = NULL;
/*
* Will have to be rewritten and expanded,
* to prepare for userspace.
* The platform-agnostic flags will be removed
* because as long as the kernel is x86 only,
* we don't need over complication.
* Plus I don't plan to port to other architectures
*/
uint64_t convert_x86_vm_flags(size_t flags)
{
uint64_t value = 0;
if (flags & VM_FLAG_WRITE)
{
value |= PTE_WRITABLE;
}
if (flags & VM_FLAG_USER)
{
value |= PTE_USER;
}
if ((flags & VM_FLAG_EXEC) == 0)
{
value |= PTE_NOEXEC;
}
return value;
}
extern uint64_t *kernel_pml4; extern uint64_t *kernel_pml4;
void vmm_setup_pt_root() /*
* vmm_switch_to - Switch to a different VMM context
* @ctx: VMM context to switch to
*
* This function makes the CPU switch to another
* virtual memory context, by using the PML4 address
* specified in the VMM context pointed to by @ctx.
*/
void vmm_switch_to(struct vmm_context* ctx)
{ {
// We alloc a physical page (frame) for the pointer, then map it if (!ctx || !ctx->pml4) {
// to virt (pointer) panic(NULL, "Attempted to switch to bad PML4!");
uintptr_t phys = pmm_alloc(); }
vmm_pt_root = (void*)kernel_pml4; uint64_t pml4 = VIRT_TO_PHYS(ctx->pml4);
paging_map_page(kernel_pml4, (uint64_t)vmm_pt_root, phys, convert_x86_vm_flags(VM_FLAG_WRITE | VM_FLAG_EXEC)); asm volatile ("mov %0, %%cr3" :: "r"(pml4) : "memory");
DEBUG("VMM setup: vmm_pt_root=0x%p (phys=0x%p)", vmm_pt_root, phys);
} }
/* void* vmm_alloc(size_t length, size_t flags) /*
* vmm_virt_to_phys - Translate from virtual to physical address
* @pml4: virtual address of the Page Map Level 4 (root page table)
* @virt: virtual address to translate
*
* This function goes through page table structures, beginning at
* the root page table which lives at @pml4, and translates @virt
* to a physical address, if it's found in the tables.
*
* Return:
* <phys> - physical address
* %-1 - address is not present in page tables pointed to by @pml4
*/
uint64_t vmm_virt_to_phys(uint64_t* pml4, uint64_t virt)
{ {
// We will try to allocate at least length bytes, which have to be rounded UP to uint64_t pml4_i = PML4_INDEX(virt);
// the next page so its coherent with the PMM uint64_t pdpt_i = PDPT_INDEX(virt);
size_t len = ALIGN_UP(length, PAGE_SIZE); uint64_t pd_i = PD_INDEX(virt);
uint64_t pt_i = PT_INDEX(virt);
// Need to implement this (as linked list) if (!(pml4[pml4_i] & PTE_PRESENT)) return -1;
// but for now kernel heap is sufficient uint64_t* pdpt = (uint64_t*)PHYS_TO_VIRT(pml4[pml4_i] & PTE_ADDR_MASK);
// The VMM will prob be more useful when we have userspace
} */ if (!(pdpt[pdpt_i] & PTE_PRESENT)) return -1;
uint64_t* pd = (uint64_t*)PHYS_TO_VIRT(pdpt[pdpt_i] & PTE_ADDR_MASK);
if (!(pd[pd_i] & PTE_PRESENT)) return -1;
uint64_t* pt = (uint64_t*)PHYS_TO_VIRT(pd[pd_i] & PTE_ADDR_MASK);
if (!(pt[pt_i] & PTE_PRESENT)) return -1;
uint64_t phys = (pt[pt_i] & PTE_ADDR_MASK) + (virt & 0xFFF);
return phys;
}
/*
* vmm_create_address_space - Create a new address space
*
* This function allocates a PML4, and then copies the kernel
* page tables into it.
*
* Return:
* <pml4> - address of the new PML4
* NULL - on error (couldn't allocate a page table)
*/
uint64_t* vmm_create_address_space()
{
uint64_t* pml4 = alloc_page_table();
if (!pml4) return NULL;
for (size_t i=256; i<512; i++) {
pml4[i] = kernel_pml4[i];
}
return pml4;
}
/*
* vmm_map - Map & allocate a page
* @pml4: Page Map Level 4 (root table)
* @virt: Virtual address to map
* @flags: Flags to apply on page
*
* This function allocates a page frame with the PMM,
* and maps this page to the provided @virt address,
* with the corresponding @flags.
*
* Return:
* <virt> - virtual address
*/
void* vmm_map(uint64_t* pml4, uint64_t virt, uint64_t flags)
{
uint64_t phys = pmm_alloc();
if (!phys) {
panic(NULL, "VMM/PMM out of memory!");
}
memset(PHYS_TO_VIRT(phys), 0, PAGE_SIZE);
paging_map_page(pml4, virt, phys, flags | PTE_PRESENT);
return (void*)virt;
}
/*
* vmm_unmap - Unmap & free a page
* @pml4: Page Map Level 4 (root table)
* @virt: Virtual address to unmap
*
* This function frees a page frame with the PMM,
* and unmaps the virtual page at @virt.
*/
void vmm_unmap(uint64_t* pml4, uint64_t virt)
{
uint64_t pml4_i = PML4_INDEX(virt);
uint64_t pdpt_i = PDPT_INDEX(virt);
uint64_t pd_i = PD_INDEX(virt);
uint64_t pt_i = PT_INDEX(virt);
if (!(pml4[pml4_i] & PTE_PRESENT)) return;
uint64_t* pdpt = (uint64_t*)PHYS_TO_VIRT(pml4[pml4_i] & PTE_ADDR_MASK);
if (!(pdpt[pdpt_i] & PTE_PRESENT)) return;
uint64_t* pd = (uint64_t*)PHYS_TO_VIRT(pdpt[pdpt_i] & PTE_ADDR_MASK);
if (!(pd[pd_i] & PTE_PRESENT)) return;
uint64_t* pt = (uint64_t*)PHYS_TO_VIRT(pd[pd_i] & PTE_ADDR_MASK);
if (!(pt[pt_i] & PTE_PRESENT)) return;
uint64_t phys = pt[pt_i] & PTE_ADDR_MASK;
pmm_free(phys);
pt[pt_i] = 0;
invlpg((void*)virt);
}
/*
* vmm_is_mapped - Check if an address is mapped
* @pml4: Page Map Level 4 (root table)
* @virt: Virtual address to check
*
* This function checks if the @virt address is
* mapped in the tables pointed to by @pml4.
*
* Return:
* true - @virt is mapped in tables of @pml4
* false - @virt is not mapped there
*/
bool vmm_is_mapped(uint64_t* pml4, uint64_t virt)
{
uint64_t pml4_i = PML4_INDEX(virt);
uint64_t pdpt_i = PDPT_INDEX(virt);
uint64_t pd_i = PD_INDEX(virt);
uint64_t pt_i = PT_INDEX(virt);
if (!(pml4[pml4_i] & PTE_PRESENT)) return false;
uint64_t* pdpt = (uint64_t*)PHYS_TO_VIRT(pml4[pml4_i] & PTE_ADDR_MASK);
if (!(pdpt[pdpt_i] & PTE_PRESENT)) return false;
uint64_t* pd = (uint64_t*)PHYS_TO_VIRT(pdpt[pdpt_i] & PTE_ADDR_MASK);
if (!(pd[pd_i] & PTE_PRESENT)) return false;
uint64_t* pt = (uint64_t*)PHYS_TO_VIRT(pd[pd_i] & PTE_ADDR_MASK);
return (pt[pt_i] & PTE_PRESENT);
}
/*
* vmm_alloc_range - Map and allocate a memory range
* @pml4: Page Map Level 4 (root table)
* @pages: Amount of pages to allocate/map
* @flags: Flags to put on mapped pages
*
* This function looks for enough space in page tables
* to map @pages pages, then maps them into the provided
* @pml4 with the provided @flags and allocates them.
*
* Return:
* <start_virt> - the starting virtual address for the mapped range
*/
void* vmm_alloc_region(uint64_t* pml4, size_t pages, uint64_t flags)
{
uint64_t found_pages = 0;
uint64_t start_virt = VMM_USER_SPACE_START;
for (uint64_t curr = VMM_USER_SPACE_START; curr < VMM_USER_SPACE_END; curr += PAGE_SIZE) {
if (!vmm_is_mapped(pml4, curr)) {
if (found_pages == 0) start_virt = curr;
found_pages++;
} else {
found_pages = 0;
}
if (found_pages == pages) {
for (size_t i = 0; i < pages; i++) {
uint64_t addr_to_map = start_virt + (i * PAGE_SIZE);
if (!vmm_map(pml4, addr_to_map, flags)) {
panic(NULL, "VMM out of memory!");
}
}
return (void*)start_virt;
}
}
panic(NULL, "VMM out of memory!");
return NULL;
}
/*
* vmm_map_user_stack - Map a user stack
* @pml4: the user process's PML4
*
* This function maps and allocates a userspace
* stack in the user @pml4 provided, according
* to constants USER_STACK_TOP and USER_STACK_PAGES.
*
* Return:
* <addr> - User stack top address
*/
uintptr_t vmm_alloc_user_stack(uint64_t* pml4)
{
uintptr_t stack_top = USER_STACK_TOP;
size_t stack_size = USER_STACK_PAGES*PAGE_SIZE;
for (size_t i=stack_top; i>stack_top-stack_size; i-=PAGE_SIZE) {
vmm_map(pml4, i, PTE_PRESENT | PTE_WRITABLE | PTE_USER);
}
return stack_top;
}
uintptr_t vmm_alloc_user_code(uint64_t* pml4, void* code_addr, uint64_t code_size)
{
uintptr_t code_start = USER_CODE_START;
// Round code_size up to next page boundary
uint64_t code_size_aligned = (code_size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
uint64_t mapped_size = code_size_aligned + ((uint64_t)USER_RAW_EXTRA_PAGES * PAGE_SIZE);
for (uint64_t i=code_start; i<code_start+mapped_size; i+=PAGE_SIZE) {
vmm_map(pml4, i, PTE_PRESENT | PTE_WRITABLE | PTE_USER);
}
return code_start;
}
void vmm_init() void vmm_init()
{ {
+113 -14
View File
@@ -4,20 +4,23 @@
* @license GPL-3.0-only * @license GPL-3.0-only
*/ */
#include "mem/paging.h"
#include "mem/vmm.h"
#include <stddef.h> #include <stddef.h>
#include <sched/process.h> #include <sched/process.h>
#include <mem/kheap.h> #include <mem/kheap.h>
#include <kernel.h> #include <kernel.h>
#include <string/string.h> #include <string/string.h>
#include <mem/gdt.h> #include <arch/gdt.h>
#include <config.h> #include <config.h>
#include <io/serial/serial.h> #include <io/serial/serial.h>
#include <io/term/flanterm.h> #include <io/term/flanterm.h>
#include <mem/utils.h>
extern struct flanterm_context* ft_ctx; extern struct flanterm_context* ft_ctx;
struct process_t* processes_list; struct process* processes_list;
struct process_t* current_process; struct process* current_process;
extern uint64_t *kernel_pml4; extern uint64_t *kernel_pml4;
@@ -39,10 +42,10 @@ void process_init()
* This function prints the linked list of processes * This function prints the linked list of processes
* to the DEBUG output. * to the DEBUG output.
*/ */
void process_display_list(struct process_t* processes_list) void process_display_list(struct process* processes_list)
{ {
int process_view_id = 0; int process_view_id = 0;
struct process_t* tmp = processes_list; struct process* tmp = processes_list;
while (tmp != NULL) { while (tmp != NULL) {
DEBUG("{%d: %p} -> ", process_view_id, tmp); DEBUG("{%d: %p} -> ", process_view_id, tmp);
tmp = tmp->next; tmp = tmp->next;
@@ -64,11 +67,11 @@ void process_display_list(struct process_t* processes_list)
* Return: * Return:
* <proc> - pointer to created process * <proc> - pointer to created process
*/ */
struct process_t* process_create(char* name, void(*function)(void*), void* arg) struct process* process_create(char* name, void(*function)(void*), void* arg)
{ {
CLEAR_INTERRUPTS; CLEAR_INTERRUPTS;
struct process_t* proc = (struct process_t*)kmalloc(sizeof(struct process_t)); struct process* proc = (struct process*)kmalloc(sizeof(struct process));
struct cpu_status_t* ctx = (struct cpu_status_t*)kmalloc(sizeof(struct cpu_status_t)); struct cpu_status* ctx = (struct cpu_status*)kmalloc(sizeof(struct cpu_status));
// No more memory? // No more memory?
if (!proc) return NULL; if (!proc) return NULL;
@@ -95,6 +98,8 @@ struct process_t* process_create(char* name, void(*function)(void*), void* arg)
// Kernel PML4 as it already maps code/stack (when switching to userland we'll have to change that) // 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->root_page_table = kernel_pml4;
proc->kernel_stack = kalloc_stack();
proc->next = 0; proc->next = 0;
process_add(&processes_list, proc); process_add(&processes_list, proc);
@@ -108,7 +113,7 @@ struct process_t* process_create(char* name, void(*function)(void*), void* arg)
* @processes_list: pointer to the head 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 * @process: process to add at the end of the linked list
*/ */
void process_add(struct process_t** processes_list, struct process_t* process) void process_add(struct process** processes_list, struct process* process)
{ {
if (!process) return; if (!process) return;
process->next = NULL; process->next = NULL;
@@ -119,7 +124,7 @@ void process_add(struct process_t** processes_list, struct process_t* process)
return; return;
} }
struct process_t* tmp = *processes_list; struct process* tmp = *processes_list;
while (tmp->next != NULL) { while (tmp->next != NULL) {
tmp = tmp->next; tmp = tmp->next;
} }
@@ -132,7 +137,7 @@ void process_add(struct process_t** processes_list, struct process_t* process)
* @processes_list: pointer to head of linked list * @processes_list: pointer to head of linked list
* @process: the process to delete from the list * @process: the process to delete from the list
*/ */
void process_delete(struct process_t** processes_list, struct process_t* process) void process_delete(struct process** processes_list, struct process* process)
{ {
if (!processes_list || !*processes_list || !process) return; if (!processes_list || !*processes_list || !process) return;
@@ -144,7 +149,7 @@ void process_delete(struct process_t** processes_list, struct process_t* process
return; return;
} }
struct process_t* tmp = *processes_list; struct process* tmp = *processes_list;
while (tmp->next && tmp->next != process) { while (tmp->next && tmp->next != process) {
tmp = tmp->next; tmp = tmp->next;
} }
@@ -167,7 +172,7 @@ void process_delete(struct process_t** processes_list, struct process_t* process
* Return: * Return:
* <process->next> - process right after the one specified * <process->next> - process right after the one specified
*/ */
struct process_t* process_get_next(struct process_t* process) struct process* process_get_next(struct process* process)
{ {
if (!process) return NULL; if (!process) return NULL;
return process->next; return process->next;
@@ -197,3 +202,97 @@ void process_exit()
asm("hlt"); asm("hlt");
} }
} }
/*
* process_jump_to_user - Jump to userland
* @stack_top: Address of the top of the user stack
* @user_code: Address of the first instruction of user code
*/
void process_jump_to_user(uintptr_t stack_top, uintptr_t user_code)
{
// 0x20 | 3 = 0x23 (user data segment | 3)
// 0x18 | 3 = 0x1B (user code segment | 3)
asm volatile(" \
push $0x23 \n\
push %0 \n\
push $0x202 \n\
push $0x1B \n\
push %1 \n\
iretq \n\
" :: "r"(stack_top), "r"(user_code));
}
extern struct tss tss;
/*
* process_create_user_raw - Create a new user process from raw binary
* @file: pointer to beginning of binary
* @size: size of the binary
* @name: name for the new process
*
* This function takes an executable loaded in memory
* and maps its code, a user stack, sets the TSS RSP0
* for interrupts, and finally jumps to the user code.
*/
void process_create_user_raw(char* file, int size, char* name)
{
CLEAR_INTERRUPTS;
struct process* proc = (struct process*)kmalloc(sizeof(struct process));
struct cpu_status* ctx = (struct cpu_status*)kmalloc(sizeof(struct cpu_status));
if (!proc || !ctx) panic(NULL, "out of memory while creating user process");
memset(proc, 0, sizeof(struct process));
memset(ctx, 0, sizeof(struct cpu_status));
strncpy(proc->name, name, PROCESS_NAME_MAX);
proc->pid = next_free_pid++;
proc->status = READY;
proc->next = 0;
proc->context = ctx;
proc->context->iret_ss = USER_DATA_SEGMENT | 3;
proc->context->iret_cs = USER_CODE_SEGMENT | 3;
proc->context->iret_flags = 0x202; // Interrupt Flag set
/* Set basic entries for the process's File Descriptor Table */
proc->fdt[0].fd = 0;
proc->fdt[0].open = true;
proc->fdt[0].cursor = 0;
strncpy(proc->fdt[0].filename, "stdin", PROCESS_NAME_MAX - 1);
proc->fdt[1].fd = 1;
proc->fdt[1].open = true;
proc->fdt[1].cursor = 0;
strncpy(proc->fdt[1].filename, "stdout", PROCESS_NAME_MAX - 1);
proc->fdt[2].fd = 2;
proc->fdt[2].open = true;
proc->fdt[2].cursor = 0;
strncpy(proc->fdt[2].filename, "stderr", PROCESS_NAME_MAX - 1);
proc->next_free_fd = 3; // file descriptors are also bump-allocated
void* exec_addr = (void*)file;
uint64_t exec_size = size;
uint64_t* user_pml4 = vmm_create_address_space();
if (!user_pml4) panic(NULL, "failed to create user address space");
proc->root_page_table = user_pml4;
uintptr_t stack_top = vmm_alloc_user_stack(user_pml4);
uint64_t code = vmm_alloc_user_code(user_pml4, exec_addr, exec_size);
proc->context->iret_rsp = stack_top;
proc->context->iret_rip = code;
proc->kernel_stack = kalloc_stack();
if (!proc->kernel_stack) panic(NULL, "failed to allocate kernel stack");
// Copy code into user pages; for that we need to temporarily switch to the user pml4
load_cr3(VIRT_TO_PHYS((uint64_t)user_pml4));
memcpy((uint64_t*)code, exec_addr, exec_size);
load_cr3(VIRT_TO_PHYS((uint64_t)kernel_pml4));
process_add(&processes_list, proc);
DEBUG("user process '%s' (pid=%u) enqueued for scheduling", name, proc->pid);
SET_INTERRUPTS;
}
+41 -23
View File
@@ -9,10 +9,13 @@
#include <mem/paging.h> #include <mem/paging.h>
#include <stdint.h> #include <stdint.h>
#include <io/serial/serial.h> #include <io/serial/serial.h>
#include <arch/gdt.h>
extern struct process_t* processes_list; extern struct process* processes_list;
extern struct process_t* current_process; extern struct process* current_process;
extern struct process_t* idle_proc; extern struct process* idle_proc;
extern struct tss tss;
/* /*
* scheduler_init - Choose the first process * scheduler_init - Choose the first process
@@ -20,6 +23,7 @@ extern struct process_t* idle_proc;
void scheduler_init() void scheduler_init()
{ {
current_process = processes_list; current_process = processes_list;
DEBUG("scheduler starting with: pid=%u, name='%s', context=%p", current_process->pid, current_process->name, current_process->context);
} }
/* /*
@@ -32,49 +36,63 @@ void scheduler_init()
* Return: * Return:
* <context> - CPU context for next process * <context> - CPU context for next process
*/ */
struct cpu_status_t* scheduler_schedule(struct cpu_status_t* context) struct cpu_status* scheduler_schedule(struct cpu_status* context)
{ {
if (context == NULL) { if (context == NULL) {
panic(NULL, "Scheduler called with NULL context"); panic(NULL, "Scheduler called with NULL context");
} }
if (current_process == NULL) { if (current_process == NULL) {
// If no more processes, then set IDLE as the current process, that's it. panic(NULL, "current_process is NULL");
current_process = idle_proc;
} }
if (current_process == idle_proc && current_process->next == NULL) if (current_process->context == NULL) {
{ panic(NULL, "current_process->context is NULL");
return idle_proc->context;
} }
current_process->context = context; current_process->context = context;
for (;;) { if (current_process->status == DEAD) {
struct process_t* prev_process = current_process; struct process* dead_process = current_process;
if (current_process->next != NULL) { struct process* next_process = (dead_process->next != NULL) ? dead_process->next : processes_list;
process_delete(&processes_list, dead_process);
if (processes_list == NULL || next_process == dead_process) {
current_process = idle_proc;
return idle_proc->context;
}
current_process = next_process;
} else if (current_process->next != NULL) {
current_process = current_process->next; current_process = current_process->next;
} else { } else {
current_process = processes_list; current_process = processes_list;
} }
if (current_process != NULL && current_process->status == DEAD) { for (;;) {
process_delete(&prev_process, current_process); if (current_process->status == DEAD) {
current_process = NULL; struct process* dead_process = current_process;
struct process* next_process = (current_process->next != NULL) ? current_process->next : processes_list;
process_delete(&processes_list, dead_process);
if (processes_list == NULL || next_process == dead_process) {
current_process = idle_proc;
return idle_proc->context; return idle_proc->context;
} else { }
current_process = next_process;
continue;
}
current_process->status = RUNNING; current_process->status = RUNNING;
/* if (prev_process != current_process) {
DEBUG("Changed from {pid=%u, name=%s} to {pid=%u, name=%s}", prev_process->pid, prev_process->name, current_process->pid, current_process->name);
} */
break; break;
} }
}
//DEBUG("current_process={pid=%u, name='%s', root_page_table[virt]=%p}", current_process->pid, current_process->name, current_process->root_page_table);
// Here, we chose next running process so we load its kernel stack & page tables
tss.rsp0 = (uint64_t)current_process->kernel_stack;
load_cr3(VIRT_TO_PHYS((uint64_t)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; return current_process->context;
} }
+2 -2
View File
@@ -16,7 +16,7 @@
* Saves the RFLAGS register, then acquires a lock. * Saves the RFLAGS register, then acquires a lock.
* Pause instruction is used to ease the CPU. * Pause instruction is used to ease the CPU.
*/ */
void spinlock_acquire(struct spinlock_t* lock) void spinlock_acquire(struct spinlock* lock)
{ {
uint64_t rflags; uint64_t rflags;
asm volatile("pushfq ; pop %0 ; cli" : "=rm"(rflags) : : "memory"); asm volatile("pushfq ; pop %0 ; cli" : "=rm"(rflags) : : "memory");
@@ -36,7 +36,7 @@ void spinlock_acquire(struct spinlock_t* lock)
* unlocks it (clears locked state). * unlocks it (clears locked state).
* RFLAGS is then restored. * RFLAGS is then restored.
*/ */
void spinlock_release(struct spinlock_t* lock) void spinlock_release(struct spinlock* lock)
{ {
uint64_t rflags = lock->rflags; uint64_t rflags = lock->rflags;
__atomic_clear(&lock->locked, __ATOMIC_RELEASE); __atomic_clear(&lock->locked, __ATOMIC_RELEASE);
+17
View File
@@ -0,0 +1,17 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Stack Smashing Protection feature
* @license GPL-3.0-only
*/
#include <config.h>
#include <stdint.h>
#include <kernel.h>
#include <stddef.h>
uint64_t __stack_chk_guard = STACK_CHK_GUARD;
void __stack_chk_fail(void)
{
panic(NULL, "SSP: Stask Smashing Detected!!! (very spicy)");
}
+293
View File
@@ -0,0 +1,293 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Undefined behavior sanitization hooks
* @license GPL-3.0-only
*/
#include <stdint.h>
#include <security/ubsan.h>
#include <kernel.h>
#include <stddef.h>
/*
* Hooks were inspired from the Sortix implementation:
* https://gitlab.com/sortix/sortix/-/blob/main/libc/ubsan/ubsan.c
*
* Not all hooks are implemented here.
*/
extern struct init_status init;
/*
* __ubsan_handle_type_mismatch_v1 - Hook for UBSan
* @data_raw: pointer to error data
* @pointer_raw: faulty pointer
*
* This function is executed when the UBSan library detects
* following undefined behavior: type mismatch, null pointer
* access, and unaligned access. It halts the system and
* gives the location of the code that triggered it.
*/
void __ubsan_handle_type_mismatch_v1(void* data_raw, void* pointer_raw)
{
struct ubsan_type_mismatch_v1_data* data = (struct ubsan_type_mismatch_v1_data*) data_raw;
uintptr_t pointer = (uintptr_t)pointer_raw;
uintptr_t alignment = (uintptr_t)1UL << data->log_alignment;
const char* violation = "type mismatch";
if ( !pointer ) {
violation = "null pointer access";
}
else if ( alignment && (pointer & (alignment - 1)) ) {
violation = "unaligned access";
}
struct ubsan_source_location* loc = &data->location;
DEBUG("\x1b[38;5;231mUBSan: %s (ptr=%p) at %s:%u:%u\x1b[0m", violation, pointer, loc->filename, loc->line, loc->column);
if (init.all) {
printf("\x1b[38;5;231mUBSan: %s (ptr=%p) at %s:%u:%u\x1b[0m\r\n", violation, pointer, loc->filename, loc->line, loc->column);
}
panic(NULL, "Undefined Behavior Sanitization error");
}
/*
* __ubsan_handle_pointer_overflow - Hook for UBSan
* @data_raw: pointer to error data
* @base_raw: base pointer
* @result_raw: pointer after faulty operation
*
* This function is executed when the UBSan library detects
* following undefined behavior: pointer overflow.
* It halts the system and gives the location of the code
* that triggered it.
*/
void __ubsan_handle_pointer_overflow(void* data_raw, void* base_raw, void* result_raw)
{
struct ubsan_pointer_overflow_data* data = (struct ubsan_pointer_overflow_data*) data_raw;
uintptr_t base = (uintptr_t)base_raw;
uintptr_t result = (uintptr_t)result_raw;
struct ubsan_source_location* loc = &data->location;
DEBUG("\x1b[38;5;231mUBSan: pointer overflow (base=%p, result=%p) at %s:%u:%u\x1b[0m", base, result, loc->filename, loc->line, loc->column);
if (init.all) {
printf("\x1b[38;5;231mUBSan: pointer overflow (base=%p, result=%p) at %s:%u:%u\x1b[0m", base, result, loc->filename, loc->line, loc->column);
}
panic(NULL, "Undefined Behavior Sanitization error: pointer overflow");
}
/*
* __ubsan_handle_shift_out_of_bounds - Hook for UBSan
* @data_raw: pointer to error data
* @lhs_raw: left hand side (value being shifted)
* @rhs_raw: right hand side (shift amount)
*
* This function is executed when the UBSan library detects
* following undefined behavior: shift out of bounds.
* It halts the system and gives the location of the code
* that triggered it.
*/
void __ubsan_handle_shift_out_of_bounds(void* data_raw, void* lhs_raw, void* rhs_raw)
{
struct ubsan_shift_out_of_bounds_data* data = (struct ubsan_shift_out_of_bounds_data*) data_raw;
uintptr_t lhs = (uintptr_t) lhs_raw;
uintptr_t rhs = (uintptr_t) rhs_raw;
struct ubsan_source_location* loc = &data->location;
DEBUG("\x1b[38;5;231mUBSan: shift out of bounds (lhs=%p, rhs=%p) at %s:%u:%u\x1b[0m", lhs, rhs, loc->filename, loc->line, loc->column);
if (init.all) {
printf("\x1b[38;5;231mUBSan: shift out of bounds (lhs=%p, rhs=%p) at %s:%u:%u\x1b[0m", lhs, rhs, loc->filename, loc->line, loc->column);
}
panic(NULL, "Undefined Behavior Sanitization error: shift out of bounds");
}
/*
* __ubsan_handle_load_invalid_value - Hook for UBSan
* @data_raw: pointer to error data
* @value_raw: value loaded
*
* This function is executed when the UBSan library detects
* following undefined behavior: invalid value load.
* It halts the system and gives the location of the code
* that triggered it.
*/
void __ubsan_handle_load_invalid_value(void* data_raw, void* value_raw)
{
struct ubsan_invalid_value_data* data = (struct ubsan_invalid_value_data*) data_raw;
uintptr_t value = (uintptr_t) value_raw;
struct ubsan_source_location* loc = &data->location;
DEBUG("\x1b[38;5;231mUBSan: invalid value load (value=%p) at %s:%u:%u\x1b[0m", value, loc->filename, loc->line, loc->column);
if (init.all) {
printf("\x1b[38;5;231mUBSan: invalid value load (value=%p) at %s:%u:%u\x1b[0m", value, loc->filename, loc->line, loc->column);
}
panic(NULL, "Undefined Behavior Sanitization error: invalid value load");
}
/*
* __ubsan_handle_out_of_bounds - Hook for UBSan
* @data_raw: pointer to error data
* @index_raw: out-of-bounds index in array
*
* This function is executed when the UBSan library detects
* following undefined behavior: access out of bounds.
* It halts the system and gives the location of the code
* that triggered it.
*/
void __ubsan_handle_out_of_bounds(void* data_raw, void* index_raw)
{
struct ubsan_out_of_bounds_data* data = (struct ubsan_out_of_bounds_data*) data_raw;
uintptr_t index = (uintptr_t) index_raw;
struct ubsan_source_location* loc = &data->location;
DEBUG("\x1b[38;5;231mUBSan: out of bounds (index=%p) at %s:%u:%u\x1b[0m", index, loc->filename, loc->line, loc->column);
if (init.all) {
printf("\x1b[38;5;231mUBSan: out of bounds (index=%p) at %s:%u:%u\x1b[0m", index, loc->filename, loc->line, loc->column);
}
panic(NULL, "Undefined Behavior Sanitization error: out of bounds");
}
/*
* __ubsan_handle_divrem_overflow - Hook for UBSan
* @data_raw: pointer to error data
* @lhs_raw: left hand side operator
* @rhs_raw: right hand side operator
*
* This function is executed when the UBSan library detects
* following undefined behavior: division remainder overflow.
* It halts the system and gives the location of the code
* that triggered it.
*/
void __ubsan_handle_divrem_overflow(void* data_raw, void* lhs_raw, void* rhs_raw)
{
struct ubsan_overflow_data* data = (struct ubsan_overflow_data*) data_raw;
uintptr_t lhs = (uintptr_t) lhs_raw;
uintptr_t rhs = (uintptr_t) rhs_raw;
struct ubsan_source_location* loc = &data->location;
DEBUG("\x1b[38;5;231mUBSan: division remainder overflow (lhs=%p, rhs=%p) at %s:%u:%u\x1b[0m", lhs, rhs, loc->filename, loc->line, loc->column);
if (init.all) {
printf("\x1b[38;5;231mUBSan: division remainder overflow (lhs=%p, rhs=%p) at %s:%u:%u\x1b[0m", lhs, rhs, loc->filename, loc->line, loc->column);
}
panic(NULL, "Undefined Behavior Sanitization error: division remainder overflow");
}
/*
* __ubsan_handle_sub_overflow - Hook for UBSan
* @data_raw: pointer to error data
* @lhs_raw: left hand side operator
* @rhs_raw: right hand side operator
*
* This function is executed when the UBSan library detects
* following undefined behavior: subtraction overflow.
* It halts the system and gives the location of the code
* that triggered it.
*/
void __ubsan_handle_sub_overflow(void* data_raw, void* lhs_raw, void* rhs_raw)
{
struct ubsan_overflow_data* data = (struct ubsan_overflow_data*) data_raw;
uintptr_t lhs = (uintptr_t) lhs_raw;
uintptr_t rhs = (uintptr_t) rhs_raw;
struct ubsan_source_location* loc = &data->location;
DEBUG("\x1b[38;5;231mUBSan: subtraction overflow (lhs=%p, rhs=%p) at %s:%u:%u\x1b[0m", lhs, rhs, loc->filename, loc->line, loc->column);
if (init.all) {
printf("\x1b[38;5;231mUBSan: subtraction overflow (lhs=%p, rhs=%p) at %s:%u:%u\x1b[0m", lhs, rhs, loc->filename, loc->line, loc->column);
}
panic(NULL, "Undefined Behavior Sanitization error: subtraction overflow");
}
/*
* __ubsan_handle_add_overflow - Hook for UBSan
* @data_raw: pointer to error data
* @lhs_raw: left hand side operator
* @rhs_raw: right hand side operator
*
* This function is executed when the UBSan library detects
* following undefined behavior: addition overflow.
* It halts the system and gives the location of the code
* that triggered it.
*/
void __ubsan_handle_add_overflow(void* data_raw, void* lhs_raw, void* rhs_raw)
{
struct ubsan_overflow_data* data = (struct ubsan_overflow_data*) data_raw;
uintptr_t lhs = (uintptr_t) lhs_raw;
uintptr_t rhs = (uintptr_t) rhs_raw;
struct ubsan_source_location* loc = &data->location;
DEBUG("\x1b[38;5;231mUBSan: addition overflow (lhs=%p, rhs=%p) at %s:%u:%u\x1b[0m", lhs, rhs, loc->filename, loc->line, loc->column);
if (init.all) {
printf("\x1b[38;5;231mUBSan: addition overflow (lhs=%p, rhs=%p) at %s:%u:%u\x1b[0m", lhs, rhs, loc->filename, loc->line, loc->column);
}
panic(NULL, "Undefined Behavior Sanitization error: addition overflow");
}
/*
* __ubsan_handle_mul_overflow - Hook for UBSan
* @data_raw: pointer to error data
* @lhs_raw: left hand side operator
* @rhs_raw: right hand side operator
*
* This function is executed when the UBSan library detects
* following undefined behavior: multiplication overflow.
* It halts the system and gives the location of the code
* that triggered it.
*/
void __ubsan_handle_mul_overflow(void* data_raw, void* lhs_raw, void* rhs_raw)
{
struct ubsan_overflow_data* data = (struct ubsan_overflow_data*) data_raw;
uintptr_t lhs = (uintptr_t) lhs_raw;
uintptr_t rhs = (uintptr_t) rhs_raw;
struct ubsan_source_location* loc = &data->location;
DEBUG("\x1b[38;5;231mUBSan: multiplication overflow (lhs=%p, rhs=%p) at %s:%u:%u\x1b[0m", lhs, rhs, loc->filename, loc->line, loc->column);
if (init.all) {
printf("\x1b[38;5;231mUBSan: multiplication overflow (lhs=%p, rhs=%p) at %s:%u:%u\x1b[0m", lhs, rhs, loc->filename, loc->line, loc->column);
}
panic(NULL, "Undefined Behavior Sanitization error: multiplication overflow");
}
/*
* __ubsan_handle_negate_overflow - Hook for UBSan
* @data_raw: pointer to error data
* @old_value_raw: value before overflow
*
* This function is executed when the UBSan library detects
* following undefined behavior: negation overflow.
* It halts the system and gives the location of the code
* that triggered it.
*/
void __ubsan_handle_negate_overflow(void* data_raw, void* old_value_raw)
{
struct ubsan_overflow_data* data = (struct ubsan_overflow_data*) data_raw;
uintptr_t old_value = (uintptr_t) old_value_raw;
struct ubsan_source_location* loc = &data->location;
DEBUG("\x1b[38;5;231mUBSan: negation overflow (old_value=%p) at %s:%u:%u\x1b[0m", old_value, loc->filename, loc->line, loc->column);
if (init.all) {
printf("\x1b[38;5;231mUBSan: negation overflow (old_value=%p) at %s:%u:%u\x1b[0m", old_value, loc->filename, loc->line, loc->column);
}
panic(NULL, "Undefined Behavior Sanitization error: negation overflow");
}
+62
View File
@@ -70,3 +70,65 @@ void strncpy(char* dst, const char* src, size_t n)
size_t i = 0; size_t i = 0;
while(i++ != n && (*dst++ = *src++)); while(i++ != n && (*dst++ = *src++));
} }
/*
* strncmp - compare two strings up to n characters
* @s1: first string
* @s2: second string
* @n: number of bytes to compare
*
* Taken from: https://github.com/DevSolar/pdclib/blob/master/functions/string/strncmp.c
*
* Return:
* $0 - @s1 and @s2 are equal
* $<0 - @s1 is less than @s2
* $>0 - @s1 is greater than @s2
*/
int strncmp(const char* s1, const char* s2, size_t n)
{
while ( n && *s1 && ( *s1 == *s2 ) ) {
++s1;
++s2;
--n;
}
if ( n == 0 ) {
return 0;
}
else {
return ( *(unsigned char *)s1 - *(unsigned char *)s2 );
}
}
/*
* strlen - get length of a string (BSD implementation)
* @str: NULL-terminated string
*
* Return:
* %len - length of string
*/
size_t strlen(const char* str)
{
const char* s;
for (s = str; *s; ++s);
return (s - str);
}
/*
* atoi - ascii to integer (PureDOOM implementation)
* @str: ASCII string to convert
*
* Return:
* %i - integer represented in @str
*/
int atoi(const char* str)
{
int i = 0;
int c;
while ((c = *str++) != 0) {
i *= 10;
i += c - '0';
}
return i;
}
+252
View File
@@ -0,0 +1,252 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief File-related system calls
* @license GPL-3.0-only
*/
#include <stddef.h>
#include <kernel.h>
#include <config.h>
#include <sched/process.h>
#include <string/string.h>
#include <fs/initfs.h>
#include <io/kbd/ps2.h>
extern struct process* current_process;
/*
* normalize_path - remove leading slashes from a path
* @path: path to normalize
*
* Return:
* %path - normalized path
* %NULL - if bad argument
*/
static const char* normalize_path(const char* path)
{
if (!path) return NULL;
while (*path == '/') {
path++;
}
return path;
}
/*
* sys_open - Open a file
* @filename: Absolute path to file
* @flags: (Not implemented)
*
* This system call opens a file in read-only mode,
* because of the TAR filesystem.
*
* Return:
* %fd - file descriptor refering to file
* On error, a negative number representing the error code is returned.
*/
int sys_open(const char* filename, int flags)
{
// TODO: support flags (right now everything is read only, O_RDONLY)
(void)flags; // gcc shut up
const char* path = normalize_path(filename);
if (tar_exists(path) < 0) {
return -ENOENT; // file doesn't exist..
}
// file exists here!
if (current_process->next_free_fd >= FDT_MAX) {
return -EMFILE;
}
int fd = current_process->next_free_fd++;
current_process->fdt[fd].fd = fd;
current_process->fdt[fd].open = true;
current_process->fdt[fd].cursor = 0;
strncpy(current_process->fdt[fd].filename, path, PROCESS_NAME_MAX - 1);
return fd;
}
/*
* sys_close - Close a file
* @fd: file descriptor
*
* This system call closes the file referred to by
* the file descriptor @fd.
*
* Return:
* %0 - file closed
* %-EBADFD - bad file descriptor
*/
int sys_close(unsigned int fd)
{
if (fd >= FDT_MAX) {
return -EBADFD;
}
if (!current_process->fdt[fd].open) {
return -EBADFD; // FD not opened in the first place
}
current_process->fdt[fd].open = false;
current_process->fdt[fd].filename[0] = '\0';
current_process->fdt[fd].cursor = 0;
return 0;
}
/*
* sys_lseek - reposition file cursor
* @fd: file descriptor referring to file
* @offset: byte amount to be used according to @whence
* @whence: resposition directive
*
* This system call repositions the cursor for @fd by @offset,
* from somewhere in the file, depending on the value of @whence:
* SEEK_SET (0) -> from the beginning
* SEEK_CUR (1) -> from the actual value of the cursor
* SEEK_END (2) -> from the end of the file
*
* Return:
* %new_cursor - the new cursor value
* On error, a negative value corresponding to an error code is returned.
*/
int sys_lseek(unsigned int fd, int offset, int whence)
{
if (fd >= FDT_MAX) {
return -EBADFD;
}
if (!current_process->fdt[fd].open) {
return -EBADFD;
}
int filesize = tar_exists(current_process->fdt[fd].filename);
if (filesize < 0) {
return -ENOENT;
}
int cursor = current_process->fdt[fd].cursor;
int base;
switch (whence) {
case SEEK_SET: base = 0; break;
case SEEK_CUR: base = cursor; break;
case SEEK_END: base = filesize; break;
default: return -EINVAL;
}
int new_cursor = base + offset;
if (new_cursor < 0) new_cursor = 0;
if (new_cursor > filesize) new_cursor = filesize;
current_process->fdt[fd].cursor = new_cursor;
return new_cursor;
}
/*
* sys_tell - Get cursor position for a file
* @fd: file descriptor to use
*
* Return:
* %cursor - cursor position of the file descriptor
* On error, a negative value with the corresponding error code is set.
*/
int sys_tell(unsigned int fd)
{
if (fd >= FDT_MAX) {
return -EBADFD;
}
if (!current_process->fdt[fd].open) {
return -EBADFD;
}
return current_process->fdt[fd].cursor;
}
/*
* sys_eof - Are we at the end of the file yet?
* @fd: file descriptor to use
*
* This function determines if the cursor for the
* file descriptor @fd is at or past the end of
* the file it refers to.
*
* Return:
* %>0 - we are at or past EOF
* %0 - not yet (still have bytes to read)
*/
int sys_eof(unsigned int fd)
{
if (fd >= FDT_MAX) {
return -EBADFD;
}
if (!current_process->fdt[fd].open) {
return -EBADFD;
}
int filesize = tar_exists(current_process->fdt[fd].filename);
if (filesize < 0) {
return -ENOENT;
}
return current_process->fdt[fd].cursor >= (uint64_t)filesize;
}
/*
* sys_read - read from an open file
* @fd: file descriptor to use
* @buf: out buffer for read data
* @count: amount of bytes to read
*
* Return:
* %sz - amount of bytes read (on success)
* On error, a negative value with an error code is returned.
*/
int sys_read(unsigned int fd, char* buf, size_t count)
{
size_t i;
switch (fd) {
case 0: //read from stdin (keyboard)
for (i=0; i<count; i++) {
buf[i] = keyboard_getchar();
}
return i;
case 1: // from stdout
case 2: // from stderr
return -EBADFD;
default: // from an open file?
if (current_process->fdt[fd].open == false) {
return -EBADFD; // File descriptor wasn't open
}
// Here fd refers to a valid opened file..
int sz = tar_read(current_process->fdt[fd].filename, buf, count,
current_process->fdt[fd].cursor);
if (sz == 0) {
return -ENOENT;
} else {
current_process->fdt[fd].cursor += sz;
return sz;
}
}
return -EBADFD;
}
/*
* sys_write - write to a file
* @fd: file descriptor to write to
* @buf: buffer of bytes to write
* @count: number of bytes to write
*
* Return:
* %count - number of bytes written
* On error, a negative value with corresponding error code is returned.
*/
int sys_write(unsigned int fd, const char* buf, size_t count)
{
switch (fd) {
case 1: //stdout
case 2: //stderr
for (size_t i=0; i<count; i++) {
internal_putc(buf[i], NULL);
}
return count;
default:
return -EBADFD;
}
}
+28
View File
@@ -0,0 +1,28 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Process-wide system calls
* @license GPL-3.0-only
*/
#include <kernel.h>
#include <sched/process.h>
extern struct process* current_process;
/*
* sys_exit - Terminate the current process
* @error_code: error code to return
*
* This system call exits the running process. Be aware that
* it does nothing except that. All resources are supposed to
* be cleaned up before calling this.
*
* Return:
* %error_code - the error code to return
*/
int sys_exit(int error_code)
{
current_process->status = DEAD;
DEBUG("(pid=%u, name=%s)", current_process->pid, current_process->name);
return error_code;
}
+105
View File
@@ -0,0 +1,105 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Screen/framebuffer system calls
* @license GPL-3.0-only
*/
// This is not part of POSIX or anything btw
#include <stdint.h>
#include <kernel.h>
extern struct boot_context boot_ctx;
/*
* pack_rbga_to_fb - Convert rgb values to rgba pixel for framebuffer
* @r: red value
* @g: green value
* @b: blue value
*
* This function uses the mask sizes and shifts of the Limine
* framebuffer to convert raw rgb values into usable pixels.
*
* Return:
* <pixel> - usable pixel
*/
static uint32_t pack_rgba_to_fb(uint8_t r, uint8_t g, uint8_t b)
{
uint32_t pixel = 0;
if (boot_ctx.fb->red_mask_size) {
pixel |= ((uint32_t)(r >> (8 - boot_ctx.fb->red_mask_size)) << boot_ctx.fb->red_mask_shift);
}
if (boot_ctx.fb->green_mask_size) {
pixel |= ((uint32_t)(g >> (8 - boot_ctx.fb->green_mask_size)) << boot_ctx.fb->green_mask_shift);
}
if (boot_ctx.fb->blue_mask_size) {
pixel |= ((uint32_t)(b >> (8 - boot_ctx.fb->blue_mask_size)) << boot_ctx.fb->blue_mask_shift);
}
return pixel;
}
/*
* sys_draw - Draw a framebuffer subset
* @src: Source frame
* @width: width of the frame
* @height: height of the frame
* @channels: how many channels (RGB, RGBA)
*
* This system call draws the frame @src, having some
* @width and @height, in the top right of the Limine
* framebuffer. (Used for DOOM)
*
* Return:
* %0 - on success
* On error, a negative value with an error code is returned.
*/
int sys_draw(const uint8_t* src, int width, int height, int channels)
{
if (!boot_ctx.fb || !src) {
return -EINVAL;
}
if (channels != 4 || width <= 0 || height <= 0) {
return -EINVAL;
}
if (boot_ctx.fb->bpp < 24) {
return -EIO;
}
uint32_t* dst = (uint32_t*)boot_ctx.fb->address;
uint64_t dst_w = boot_ctx.fb->width;
uint64_t dst_h = boot_ctx.fb->height;
uint64_t dst_pitch_px = boot_ctx.fb->pitch / 4;
uint64_t scale = 2;
uint64_t scaled_w = (uint64_t)width * scale;
uint64_t scaled_h = (uint64_t)height * scale;
if (scaled_w > dst_w || scaled_h > dst_h) {
return -EINVAL;
}
uint64_t dst_x = dst_w - scaled_w;
uint64_t dst_y = 0;
for (uint64_t y = 0; y < (uint64_t)height; ++y) {
const uint8_t* src_row = src + y * (uint64_t)width * 4ULL;
uint32_t* dst_row0 = dst + (dst_y + y * scale) * dst_pitch_px + dst_x;
uint32_t* dst_row1 = dst_row0 + dst_pitch_px;
for (uint64_t x = 0; x < (uint64_t)width; ++x) {
const uint8_t* p = src_row + x * 4ULL;
uint32_t pixel = pack_rgba_to_fb(p[0], p[1], p[2]);
uint64_t dx = x * scale;
// Write 2x2 block. This is because source DOOM frame (320x200)
// is quite small.
dst_row0[dx + 0] = pixel;
dst_row0[dx + 1] = pixel;
dst_row1[dx + 0] = pixel;
dst_row1[dx + 1] = pixel;
}
}
return 0;
}
+98
View File
@@ -0,0 +1,98 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief System call handling
* @license GPL-3.0-only
*/
#include "config.h"
#include "sched/scheduler.h"
#include <arch/x86.h>
#include <kernel.h>
#include <stddef.h>
#include <io/term/term.h>
#include <sched/process.h>
#include <io/kbd/ps2.h>
#include <fs/initfs.h>
#include <string/string.h>
#include <mem/utils.h>
extern struct process* current_process;
int sys_read(unsigned int fd, char* buf, size_t count);
int sys_write(unsigned int fd, const char* buf, size_t count);
int sys_open(const char* filename, int flags);
int sys_close(unsigned int fd);
int sys_lseek(unsigned int fd, int offset, int whence);
int sys_tell(unsigned int fd); // needed by doom, therefore TOP PRIORITY
int sys_eof(unsigned int fd); // same
int sys_draw(const uint8_t* src, int width, int height, int channels);
int sys_exit(int error_code);
/*
* syscall_handler - System call dispatcher
* @regs: CPU state
*
* This function is called from the interrupt dispatcher,
* when an interrupt 0x80 is emitted from userland.
*
* It switches control to the syscall number provided
* in %rax.
*
* We try to follow the System V convention here:
* - syscall number in %rax
* - args in %rdi, %rsi, %rdx, %r10, %r8, %r9
* - return value (if any) in %rax
*
* Return:
* <regs> - CPU state after system call
*/
struct cpu_status* syscall_handler(struct cpu_status* regs)
{
switch (regs->rax)
{
case 0:
DEBUG("sys_read(fd=%u, buf=%p, count=%u)", regs->rdi, regs->rsi, regs->rdx);
regs->rax = sys_read(regs->rdi, (char*)regs->rsi, regs->rdx);
break;
case 1:
DEBUG("sys_write(fd=%u, buf=%p, count=%u)", regs->rdi, regs->rsi, regs->rdx);
regs->rax = sys_write(regs->rdi, (char*)regs->rsi, regs->rdx);
break;
case 2:
DEBUG("sys_open(filename=%s, flags=%u)", regs->rdi, regs->rsi);
regs->rax = sys_open((const char*)regs->rdi, regs->rsi);
break;
case 3:
DEBUG("sys_close(fd=%u)", regs->rdi);
regs->rax = sys_close(regs->rdi);
break;
case 8:
DEBUG("sys_lseek(fd=%u, off=%d, whence=%u)", regs->rdi, regs->rsi, regs->rdx);
regs->rax = sys_lseek(regs->rdi, regs->rsi, regs->rdx);
break;
case 9:
DEBUG("sys_tell(fd=%u)", regs->rdi);
regs->rax = sys_tell(regs->rdi);
break;
case 10:
DEBUG("sys_eof(fd=%u)", regs->rdi);
regs->rax = sys_eof(regs->rdi);
break;
case 11: // No DEBUG() here because it makes significant overhead
regs->rax = sys_draw((const uint8_t*)regs->rdi, regs->rsi, regs->rdx, regs->r10);
break;
case 60:
DEBUG("sys_exit(error_code=%d)", regs->rdi);
regs->rax = sys_exit(regs->rdi);
break;
default:
DEBUG("Bad syscall! (rax=%p, rdi=%p, rsi=%p, rdx=%p)",
regs->rax, regs->rdi, regs->rsi, regs->rdx);
regs->rax = 0xbad515ca11;
break;
}
//DEBUG("returned rax=%p (%u)", regs->rax, regs->rax);
return regs;
}
+89
View File
@@ -0,0 +1,89 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Date helper functions
* @license GPL-3.0-only
*/
#include <stdint.h>
#include <time/date.h>
#include <mem/utils.h>
#include <kernel.h>
extern struct boot_context boot_ctx;
// Unix epoch used as reference: Jan 1st 1970, 00:00:00 UTC
struct date epoch = {
.year = 1970,
.month = 1,
.day = 1,
.hour = 0,
.minute = 0,
.second = 0
};
/*
* date_timestamp_to_date - Convert UNIX timestamp to a date structure
* @timestamp: UNIX timestamp
*
* Return:
* <date> - date structure
*/
struct date date_timestamp_to_date(uint64_t timestamp)
{
struct date result;
memcpy(&result, &epoch, sizeof(struct date));
uint64_t nr_days = timestamp / 86400;
while (nr_days > 0) {
unsigned int nr_month = 0;
int leap_year = 0;
if (result.year % 4 == 0 && (result.year % 100 != 0 || result.year % 400 == 0)) {
leap_year = 1;
} else {
leap_year = 0;
}
if (result.month == 2) {
if (leap_year != 0) {
nr_month = 29;
} else {
nr_month = 28;
}
} else {
nr_month = 31 - ((result.month -1) % 7 % 2);
}
if (nr_days >= nr_month) {
nr_days -= nr_month;
result.month++;
if (result.month > 12) {
result.month = 1;
result.year++;
}
} else {
result.day += nr_days;
nr_days = 0;
}
}
result.second = timestamp % 60;
timestamp /= 60;
result.minute = timestamp % 60;
timestamp /= 60;
result.hour = timestamp % 24;
return result;
}
/*
* date_now - Get the current date (time at boot + timer ticks)
*
* Return:
* <date> - date structure
*/
struct date date_now()
{
uint64_t timestamp_now = boot_ctx.bootdate->boot_time + (ticks/1000);
return date_timestamp_to_date(timestamp_now);
}
+28
View File
@@ -0,0 +1,28 @@
CC := x86_64-elf-gcc
CC_FLAGS := -ffreestanding -nostdlib -fno-pic -mno-red-zone -Ilibc
DOOM_CC_FLAGS := $(CC_FLAGS) -std=gnu11
LD := x86_64-elf-ld
BUILDDIR := ../build
LIBDIR := libc
all: pedicel apex doom
.PHONY: pedicel
pedicel:
nasm -f bin pedicel.S -o $(BUILDDIR)/pedicel.raw
.PHONY: apex
apex:
$(CC) $(CC_FLAGS) -c apex.c -o $(BUILDDIR)/apex.o
nasm -f elf64 $(LIBDIR)/crt0.S -o $(BUILDDIR)/crt0.o
$(LD) -T $(LIBDIR)/linker.ld $(BUILDDIR)/crt0.o $(BUILDDIR)/apex.o -o $(BUILDDIR)/apex.elf
objcopy -O binary $(BUILDDIR)/apex.elf $(BUILDDIR)/apex.raw
.PHONY: doom
doom:
$(CC) $(DOOM_CC_FLAGS) -c doom.c -o $(BUILDDIR)/doom.o
nasm -f elf64 $(LIBDIR)/crt0.S -o $(BUILDDIR)/crt0.o
$(LD) -T $(LIBDIR)/linker.ld $(BUILDDIR)/crt0.o $(BUILDDIR)/doom.o -o $(BUILDDIR)/doom.elf
objcopy -O binary $(BUILDDIR)/doom.elf $(BUILDDIR)/doom.raw
+48602
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
#include <syscall.h>
int main() {
const char* msg = "hi from C userland\r\n";
write(1, msg, 21);
return 42;
}
+147
View File
@@ -0,0 +1,147 @@
#define DOOM_IMPLEMENTATION
#include "PureDOOM.h"
#include <syscall.h>
#include <stdint.h>
//We use a separate heap because malloc is not yet available in userspace.
#define DOOM_HEAP_START ((unsigned char*)0x00500000)
#define DOOM_HEAP_SIZE (24 * 1024 * 1024) //24mb
static unsigned char* doom_heap_curr = DOOM_HEAP_START;
static unsigned char* doom_heap_end = DOOM_HEAP_START + DOOM_HEAP_SIZE;
// The following functions are wrappers for system calls made for
// compatibility with puredoom's function signatures
static int doom_cstr_len(const char* str)
{
int len = 0;
while (str && str[len]) len++;
return len;
}
static void doom_print_cb(const char* str)
{
int len = doom_cstr_len(str);
if (len > 0) {
write(1, str, len);
}
}
static void* doom_open_cb(const char* filename, const char* mode)
{
(void)mode; // open doesn't support flags/mode (yet)
int fd = open(filename, 0);
if (fd < 0) return 0;
return (void*)(intptr_t)(fd);
}
static void doom_close_cb(void* handle)
{
int fd = (int)(intptr_t)handle;
if (fd >= 0) {
close(fd);
}
}
static int doom_read_cb(void* handle, void* buf, int count)
{
int fd = (int)(intptr_t)handle;
if (fd < 0) return -1;
return read(fd, (char*)buf, count);
}
static int doom_write_cb(void* handle, const void* buf, int count)
{
int fd = (int)(intptr_t)handle;
if (fd < 0) return -1;
return (int)write(fd, (const char*)buf, count);
}
static int doom_seek_cb(void* handle, int offset, doom_seek_t origin)
{
int fd = (int)(intptr_t)handle;
if (fd < 0) return -1;
return seek(fd, offset, (int)origin);
}
static int doom_tell_cb(void* handle)
{
int fd = (int)(intptr_t)handle;
if (fd < 0) return -1;
return tell(fd);
}
static int doom_eof_cb(void* handle)
{
int fd = (int)(intptr_t)handle;
if (fd < 0) return 1;
return eof(fd);
}
// Bump allocator (bump ptr until we're out of heap memory)
static void* doom_malloc_cb(int size)
{
if (size <= 0) return 0;
uintptr_t curr = (uintptr_t)doom_heap_curr;
// 16-byte align allocated blocks
curr = (curr + 15ULL) & ~15ULL;
if (curr + (uintptr_t)size > (uintptr_t)doom_heap_end) {
return 0;
}
doom_heap_curr = (unsigned char*)(curr + (uintptr_t)size);
return (void*)curr;
}
// No free
static void doom_free_cb(void* ptr)
{
(void)ptr;
}
static void doom_exit_cb(int code)
{
exit(code);
}
// To get path for the WAD file
static char* doom_getenv_cb(const char* var)
{
static char home[] = "/";
static char waddir[] = "/";
if (!var) return 0;
if (doom_strcmp(var, "HOME") == 0) return home;
if (doom_strcmp(var, "DOOMWADDIR") == 0) return waddir;
return 0;
}
int main()
{
char* argv[2] = {"doom", 0};
// Override default implementations
doom_set_print(doom_print_cb);
doom_set_malloc(doom_malloc_cb, doom_free_cb);
doom_set_file_io(doom_open_cb,
doom_close_cb,
doom_read_cb,
doom_write_cb,
doom_seek_cb,
doom_tell_cb,
doom_eof_cb);
doom_set_exit(doom_exit_cb);
doom_set_getenv(doom_getenv_cb);
doom_init(1, argv, 0);
while (true)
{
doom_force_update();
const uint8_t* framebuffer = doom_get_framebuffer(4);
draw(framebuffer, 320, 200, 4);
}
}
BIN
View File
Binary file not shown.
+18
View File
@@ -0,0 +1,18 @@
bits 64
global _start
extern main
section .text
; Begin the program with main() function
_start:
call main
; Exit the program by exit() syscall
.exit:
mov rdi, rax ; put the value of "return X;" (rax) as arg1 (error_code)
mov rax, 60 ; sys_exit
int 0x80
.loop:
jmp .loop
+22
View File
@@ -0,0 +1,22 @@
ENTRY(_start)
SECTIONS
{
. = 0x400000;
.text : {
*(.text*)
}
.rodata : {
*(.rodata*)
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
}
}
+88
View File
@@ -0,0 +1,88 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief System call wrappers for userspace
* @license GPL-3.0-only
*/
#pragma once
// TODO: replace all ifndef/define/endif by pragma once..
#include <stddef.h>
#include <stdint.h>
// 3-args syscall
static inline long syscall3(long n, long a, long b, long c) {
long ret;
__asm__ volatile (
"int $0x80"
: "=a"(ret)
: "a"(n), "D"(a), "S"(b), "d"(c) // a = rax, D = rdi, S = rsi, d = rdx
: "memory"
);
return ret;
}
// 4-args syscall
static inline long syscall4(long n, long a, long b, long c, long d) {
long ret;
register long r10 __asm__("r10") = d;
__asm__ volatile (
"int $0x80"
: "=a"(ret)
: "a"(n), "D"(a), "S"(b), "d"(c), "r"(r10)
: "memory"
);
return ret;
}
// Single-arg syscall
static inline long syscall1(long n, long a) {
return syscall3(n, a, 0, 0);
}
static inline int write(int fd, const char* buf, long len) {
return (int)syscall3(1, fd, (long)buf, len);
}
static inline int read(int fd, char* buf, long len) {
return (int)syscall3(0, fd, (long)buf, len);
}
static inline int open(const char* path, int flags) {
return (int)syscall3(2, (long)path, flags, 0);
}
static inline int close(unsigned int fd) {
return (int)syscall3(3, fd, 0, 0);
}
static inline int seek(int fd, int offset, int whence) {
return (int)syscall3(8, fd, offset, whence);
}
static inline int tell(unsigned int fd) {
return (int)syscall1(9, fd);
}
static inline int eof(unsigned int fd) {
return (int)syscall1(10, fd);
}
static inline int draw(const unsigned char* buf, int width, int height, int channels) {
return (int)syscall4(11, (long)buf, width, height, channels);
}
static inline void exit(int code) {
__asm__ volatile (
"int $0x80"
:
: "a"(60), "D"(code)
: "memory"
);
for (;;);
}
+51
View File
@@ -0,0 +1,51 @@
bits 64
section .data
hello db 0x0A, 0x0D, "TEST PROGRAM...", 0x0A, 0x0D, 0
filename db "wow.txt", 0
section .text
_start:
mov rax, 0x1 ;sys_write
mov rdi, 0x1 ;stdout
lea rsi, [rel hello]
mov rdx, 19 ;count
int 0x80
; Open a file
mov rax, 0x2 ;sys_open
lea rdi, [rel filename] ;filename
mov rsi, 0x0 ;flags
int 0x80
mov rdi, rax ;fd
mov rax, 0x0 ;sys_read
lea rsi, [rel buf] ;buf
mov rdx, 33 ;count
int 0x80
mov rax, 0x1 ;sys_write
mov rdi, 0x1 ;stdout
lea rsi, [rel buf] ;buf
mov rdx, 33 ;count
int 0x80
mov rax, 0x3 ;sys_close
mov rdi, 0x3 ;fd
int 0x80
; when we are ready to have an os specific toolchain,
; this bit (exit & loop) should be appended at the end of every
; C program we compile.
.end:
mov rax, 0x3C
mov rdi, 0x0
int 0x80
.loop:
jmp .loop
section .bss
buf resb 10
+1
View File
@@ -0,0 +1 @@
hi from a file opened in usermode