Compare commits
7 Commits
3909a1f581
...
313aeb5872
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
313aeb5872 | ||
|
|
bb09de6a6d | ||
|
|
f63cd9eb3f | ||
|
|
fb61ab7c15 | ||
|
|
f2e7d6c5d7 | ||
|
|
cf3059747a | ||
|
|
f1923fdbcf |
@@ -20,7 +20,7 @@ file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/release)
|
|||||||
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/isodir/boot/grub)
|
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/isodir/boot/grub)
|
||||||
|
|
||||||
# Create grub.cfg for ISO
|
# Create grub.cfg for ISO
|
||||||
file(WRITE ${CMAKE_BINARY_DIR}/isodir/boot/grub/grub.cfg "set timeout=0\nset default=0\nsearch --set=root --file /boot/kernel.bin\nmenuentry \"ClaudeOS\" { multiboot /boot/kernel.bin }")
|
file(WRITE ${CMAKE_BINARY_DIR}/isodir/boot/grub/grub.cfg "set timeout=0\nset default=0\nsearch --set=root --file /boot/kernel.bin\nmenuentry \"ClaudeOS\" { multiboot2 /boot/kernel.bin }")
|
||||||
|
|
||||||
|
|
||||||
# ISO Generation
|
# ISO Generation
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -43,12 +43,12 @@ Once a task is completed, it should be checked off.
|
|||||||
- [x] Update the build system to create both ISO and Floppy images. Verify these work using a test script. The standard CMake build target should automatically generate both images. (Only ISO supported for now)
|
- [x] Update the build system to create both ISO and Floppy images. Verify these work using a test script. The standard CMake build target should automatically generate both images. (Only ISO supported for now)
|
||||||
- [x] Update the kernel to correctly setup the GDT
|
- [x] Update the kernel to correctly setup the GDT
|
||||||
- [x] Create an interrupt handler.
|
- [x] Create an interrupt handler.
|
||||||
- [ ] Implement a PIC handler
|
- [x] Implement a PIC handler
|
||||||
- [ ] Create a physical memory allocator and mapper. The kernel should live in the upper last gigabyte of virtual memory. It should support different zones (e.g.: `SUB_16M`, `DEFAULT`, ...) These zones describe the region of memory that memory should be allocated in. If it is not possible to allocate in that region (because it is full, or has 0 capacity to begin with), it should fallback to another zone.
|
- [x] Create a physical memory allocator and mapper. The kernel should live in the upper last gigabyte of virtual memory. It should support different zones (e.g.: `SUB_16M`, `DEFAULT`, ...) These zones describe the region of memory that memory should be allocated in. If it is not possible to allocate in that region (because it is full, or has 0 capacity to begin with), it should fallback to another zone.
|
||||||
- [ ] Create a paging subsystem. It should allow drivers to allocate and deallocate pages at will.
|
- [x] Create a paging subsystem. It should allow drivers to allocate and deallocate pages at will.
|
||||||
- [ ] Create a memory allocator. This should provide the kernel with `malloc` and `free`. Internally, it should use the paging subsystem to ensure that the address it returns have actual RAM paged to them.
|
- [x] Create a memory allocator. This should provide the kernel with `malloc` and `free`. Internally, it should use the paging subsystem to ensure that the address it returns have actual RAM paged to them.
|
||||||
- [ ] Create an initial driver architecture, allowing different drivers included in the kernel to test whether they should load or not.
|
- [x] Create an initial driver architecture, allowing different drivers included in the kernel to test whether they should load or not.
|
||||||
- [ ] Create a VGA driver. On startup, some memory statistics should be displayed, as well as boot progress.
|
- [x] Create a VGA driver. On startup, some memory statistics should be displayed, as well as boot progress.
|
||||||
- [ ] Create subsystem for loading new processes in Ring 3.
|
- [ ] Create subsystem for loading new processes in Ring 3.
|
||||||
- [ ] Update the build script to generate a ramdisk containing any applications to run. This initial ramdisk is in CPIO format.
|
- [ ] Update the build script to generate a ramdisk containing any applications to run. This initial ramdisk is in CPIO format.
|
||||||
- [ ] Write a VFS subsystem.
|
- [ ] Write a VFS subsystem.
|
||||||
|
|||||||
62
docs/interrupts.md
Normal file
62
docs/interrupts.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Interrupt Subsystem
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The interrupt subsystem handles both CPU exceptions (faults, traps, aborts) and hardware interrupts (IRQs) from external devices. It consists of three cooperating components:
|
||||||
|
|
||||||
|
1. **IDT (Interrupt Descriptor Table)** — Maps interrupt vectors 0–255 to handler entry points.
|
||||||
|
2. **ISR (Interrupt Service Routines)** — Assembly stubs and a C dispatcher that routes interrupts.
|
||||||
|
3. **PIC (Programmable Interrupt Controller)** — Manages the 8259A PIC chips that multiplex hardware IRQs onto CPU interrupt lines.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Interrupt Vector Layout
|
||||||
|
|
||||||
|
| Vector Range | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| 0–31 | CPU exceptions (Division by Zero, Page Fault, GPF, etc.) |
|
||||||
|
| 32–47 | Hardware IRQs (remapped from default 0–15) |
|
||||||
|
| 48–255 | Available for software interrupts / future use |
|
||||||
|
|
||||||
|
### Flow of an Interrupt
|
||||||
|
|
||||||
|
1. CPU or device raises an interrupt.
|
||||||
|
2. CPU looks up the vector in the IDT and jumps to the assembly stub.
|
||||||
|
3. The stub pushes a dummy error code (if the CPU didn't push one), the interrupt number, and all general-purpose registers onto the stack.
|
||||||
|
4. The stub loads the kernel data segment (0x10) and calls `isr_handler()` in C.
|
||||||
|
5. For hardware interrupts (vectors 32–47), `isr_handler` sends an End-of-Interrupt (EOI) to the PIC.
|
||||||
|
6. For CPU exceptions (vectors 0–31), the handler prints the exception name and halts.
|
||||||
|
7. On return, the stub restores all registers and executes `iret`.
|
||||||
|
|
||||||
|
### PIC Remapping
|
||||||
|
|
||||||
|
The 8259A PIC ships with IRQ 0–7 mapped to vectors 8–15, which collide with CPU exceptions. During initialization, we remap:
|
||||||
|
|
||||||
|
- **Master PIC** (IRQ 0–7) → vectors 32–39
|
||||||
|
- **Slave PIC** (IRQ 8–15) → vectors 40–47
|
||||||
|
|
||||||
|
The PIC is initialized in cascade mode with ICW4 (8086 mode). Original IRQ masks are saved and restored after remapping.
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
- `src/idt.c` / `src/idt.h` — IDT setup and gate registration.
|
||||||
|
- `src/interrupts.S` — Assembly stubs for ISRs 0–31 and IRQs 0–15.
|
||||||
|
- `src/isr.c` / `src/isr.h` — C interrupt dispatcher and `registers_t` structure.
|
||||||
|
- `src/pic.c` / `src/pic.h` — PIC initialization, EOI, mask/unmask.
|
||||||
|
- `src/port_io.h` — Inline `inb`, `outb`, `io_wait` helpers.
|
||||||
|
|
||||||
|
## Register Save Frame
|
||||||
|
|
||||||
|
When an interrupt fires, the following is pushed onto the stack (from high to low address):
|
||||||
|
|
||||||
|
```
|
||||||
|
ss, useresp (only on privilege change)
|
||||||
|
eflags
|
||||||
|
cs, eip (pushed by CPU)
|
||||||
|
err_code (pushed by CPU or stub as 0)
|
||||||
|
int_no (pushed by stub)
|
||||||
|
eax..edi (pushed by pusha)
|
||||||
|
ds (pushed by stub)
|
||||||
|
```
|
||||||
|
|
||||||
|
This matches the `registers_t` struct in `isr.h`.
|
||||||
61
docs/kmalloc.md
Normal file
61
docs/kmalloc.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Kernel Memory Allocator (kmalloc)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The kernel memory allocator provides `kmalloc()` and `kfree()` for dynamic memory allocation within the kernel. It uses the paging subsystem to obtain physical memory on demand.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Free-List Allocator
|
||||||
|
|
||||||
|
The allocator maintains a singly-linked free list of available memory blocks, ordered by address. Each block carries an inline header:
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct block_header {
|
||||||
|
uint32_t size; // Usable bytes (excludes header)
|
||||||
|
uint32_t magic; // 0xCAFEBABE for integrity checking
|
||||||
|
struct block_header *next; // Next free block (free list only)
|
||||||
|
uint8_t is_free; // 1 = free, 0 = allocated
|
||||||
|
} block_header_t;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Allocation Strategy
|
||||||
|
|
||||||
|
1. **First-fit search:** Walk the free list for the first block with `size >= requested`.
|
||||||
|
2. **Block splitting:** If the found block is significantly larger than needed, it is split into the allocated portion and a new free block.
|
||||||
|
3. **Page expansion:** If no suitable block exists, a new 4 KiB page is obtained from `paging_alloc_page()`.
|
||||||
|
|
||||||
|
All allocations are 8-byte aligned. Minimum block size is 16 bytes to limit fragmentation.
|
||||||
|
|
||||||
|
### Deallocation
|
||||||
|
|
||||||
|
1. The block header is located by subtracting `sizeof(block_header_t)` from the user pointer.
|
||||||
|
2. The block's magic number is verified to detect corruption.
|
||||||
|
3. The block is inserted back into the free list in address order.
|
||||||
|
4. **Coalescing:** Adjacent free blocks are merged to reduce fragmentation.
|
||||||
|
|
||||||
|
### Integrity Checks
|
||||||
|
|
||||||
|
- A magic value (`0xCAFEBABE`) in each block header detects heap corruption.
|
||||||
|
- Double-free is detected by checking the `is_free` flag.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```c
|
||||||
|
void init_kmalloc(void);
|
||||||
|
void *kmalloc(size_t size);
|
||||||
|
void kfree(void *ptr);
|
||||||
|
void *kcalloc(size_t count, size_t size);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
- `src/kmalloc.c` / `src/kmalloc.h` — Allocator implementation.
|
||||||
|
- `src/string.c` — Freestanding `memset`, `memcpy`, `strlen`, etc.
|
||||||
|
- `src/paging.c` — Provides physical page backing.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- Maximum single allocation is ~4080 bytes (one page minus header).
|
||||||
|
- No multi-page allocations for large objects.
|
||||||
|
- Free virtual addresses are not reused after `paging_free_page()`.
|
||||||
75
docs/paging.md
Normal file
75
docs/paging.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Paging Subsystem
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The paging subsystem manages virtual memory using the x86 two-level paging scheme (no PAE). It provides identity mapping for all physical memory and a kernel heap region for dynamic virtual page allocation.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Page Table Structure
|
||||||
|
|
||||||
|
x86 32-bit paging uses two levels:
|
||||||
|
|
||||||
|
| Level | Entries | Each Entry Maps | Total Coverage |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Page Directory | 1024 | 4 MiB (one page table) | 4 GiB |
|
||||||
|
| Page Table | 1024 | 4 KiB (one page) | 4 MiB |
|
||||||
|
|
||||||
|
Each entry is a 32-bit value containing a 20-bit physical page frame number and 12 bits of flags.
|
||||||
|
|
||||||
|
### Identity Mapping
|
||||||
|
|
||||||
|
During initialization, all detected physical memory is identity-mapped (virtual address = physical address). This is done in two phases:
|
||||||
|
|
||||||
|
1. **Static mapping (first 16 MiB):** Four page tables are statically allocated in BSS. This avoids a chicken-and-egg problem since the PMM bitmap itself resides in this region.
|
||||||
|
|
||||||
|
2. **Dynamic mapping (above 16 MiB):** Additional page tables are allocated from the PMM *before* paging is enabled (so physical addresses are still directly accessible). These cover all remaining detected physical memory.
|
||||||
|
|
||||||
|
### Kernel Heap
|
||||||
|
|
||||||
|
The kernel heap region occupies virtual addresses `0xD0000000` through `0xF0000000` (768 MiB).
|
||||||
|
|
||||||
|
When `paging_alloc_page()` is called:
|
||||||
|
1. A physical page is allocated from the PMM.
|
||||||
|
2. A page table entry is created mapping the next free virtual address to the physical page.
|
||||||
|
3. The virtual address is returned.
|
||||||
|
|
||||||
|
When `paging_free_page()` is called:
|
||||||
|
1. The physical address is looked up via the page table entry.
|
||||||
|
2. The virtual mapping is removed.
|
||||||
|
3. The physical page is returned to the PMM.
|
||||||
|
|
||||||
|
### TLB Management
|
||||||
|
|
||||||
|
- Single-page invalidations use `invlpg`.
|
||||||
|
- Full TLB flushes use CR3 reload.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```c
|
||||||
|
void init_paging(void);
|
||||||
|
void paging_map_page(uint32_t vaddr, uint32_t paddr, uint32_t flags);
|
||||||
|
void paging_unmap_page(uint32_t vaddr);
|
||||||
|
void *paging_alloc_page(void);
|
||||||
|
void paging_free_page(void *vaddr);
|
||||||
|
uint32_t paging_get_physical(uint32_t vaddr);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flags
|
||||||
|
|
||||||
|
| Flag | Value | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `PAGE_PRESENT` | 0x001 | Page is present in memory |
|
||||||
|
| `PAGE_WRITE` | 0x002 | Page is writable |
|
||||||
|
| `PAGE_USER` | 0x004 | Page is user-accessible (ring 3) |
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
- `src/paging.c` / `src/paging.h` — Implementation and API.
|
||||||
|
- `src/pmm.c` / `src/pmm.h` — Physical page allocation backing.
|
||||||
|
|
||||||
|
## Design Decisions
|
||||||
|
|
||||||
|
- **No higher-half kernel yet:** The kernel runs at its physical load address (1 MiB) with identity mapping. Higher-half mapping (0xC0000000) can be added later without changing the paging API.
|
||||||
|
- **Static + dynamic page tables:** The first 16 MiB uses BSS-allocated tables to bootstrap, while memory above 16 MiB uses PMM-allocated tables. This keeps BSS usage bounded at ~16 KiB regardless of total RAM.
|
||||||
|
- **Sequential heap allocation:** The heap grows upward linearly. No free-list reuse of freed virtual addresses is implemented yet.
|
||||||
63
docs/pmm.md
Normal file
63
docs/pmm.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Physical Memory Manager (PMM)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The PMM manages physical page frames using a bitmap allocator. It tracks which 4 KiB pages of physical RAM are free or in use, and supports allocating pages from different memory zones.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Bitmap Allocator
|
||||||
|
|
||||||
|
Each bit in a global bitmap corresponds to one 4 KiB physical page frame:
|
||||||
|
- **Bit = 1** → page is in use (allocated or reserved)
|
||||||
|
- **Bit = 0** → page is free
|
||||||
|
|
||||||
|
The bitmap covers the entire 32-bit physical address space (up to 4 GiB), requiring 128 KiB of storage in BSS.
|
||||||
|
|
||||||
|
### Memory Zones
|
||||||
|
|
||||||
|
The allocator supports zone-based allocation to satisfy constraints from different subsystems:
|
||||||
|
|
||||||
|
| Zone | Range | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| `PMM_ZONE_DMA` | 0 – 16 MiB | ISA DMA-compatible memory |
|
||||||
|
| `PMM_ZONE_NORMAL` | 16 MiB – 4 GiB | General-purpose allocation |
|
||||||
|
|
||||||
|
When `PMM_ZONE_NORMAL` is requested but no pages are available above 16 MiB (e.g., on systems with less than 16 MiB of RAM), the allocator falls back to `PMM_ZONE_DMA`.
|
||||||
|
|
||||||
|
Page 0 (address 0x00000000) is always marked as used to prevent returning NULL as a valid physical address.
|
||||||
|
|
||||||
|
### Initialization
|
||||||
|
|
||||||
|
1. The entire bitmap is initialized to "all used" (0xFFFFFFFF).
|
||||||
|
2. The Multiboot2 memory map is parsed to discover available RAM regions.
|
||||||
|
3. Available regions are marked as free in the bitmap.
|
||||||
|
4. The kernel's own memory (between `_kernel_start` and `_kernel_end` linker symbols) is re-marked as used.
|
||||||
|
5. The Multiboot2 info structure itself is marked as used.
|
||||||
|
|
||||||
|
### Linker Symbols
|
||||||
|
|
||||||
|
The linker script exports `_kernel_start` and `_kernel_end` symbols so the PMM knows which physical pages the kernel occupies and must not allocate.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
```c
|
||||||
|
void init_pmm(uint32_t multiboot_addr);
|
||||||
|
phys_addr_t pmm_alloc_page(pmm_zone_t zone);
|
||||||
|
void pmm_free_page(phys_addr_t addr);
|
||||||
|
```
|
||||||
|
|
||||||
|
- `init_pmm` — Parse Multiboot2 info and build the free-page bitmap.
|
||||||
|
- `pmm_alloc_page` — Allocate a single 4 KiB page from the specified zone. Returns 0 on OOM.
|
||||||
|
- `pmm_free_page` — Return a page to the free pool.
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
- `src/pmm.c` / `src/pmm.h` — Allocator implementation and zone definitions.
|
||||||
|
- `src/linker.ld` — Exports `_kernel_start` / `_kernel_end`.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- First-fit allocation (linear scan) — O(n) per allocation.
|
||||||
|
- No per-zone free counts or statistics yet.
|
||||||
|
- Does not handle memory hot-plug or ACPI reclaim regions.
|
||||||
@@ -5,6 +5,14 @@ add_executable(kernel
|
|||||||
gdt_flush.S
|
gdt_flush.S
|
||||||
gdt.c
|
gdt.c
|
||||||
idt.c
|
idt.c
|
||||||
|
isr.c
|
||||||
|
pic.c
|
||||||
|
pmm.c
|
||||||
|
paging.c
|
||||||
|
kmalloc.c
|
||||||
|
string.c
|
||||||
|
driver.c
|
||||||
|
vga.c
|
||||||
interrupts.S
|
interrupts.S
|
||||||
kernel.c
|
kernel.c
|
||||||
)
|
)
|
||||||
|
|||||||
72
src/driver.c
Normal file
72
src/driver.c
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* @file driver.c
|
||||||
|
* @brief Driver framework implementation.
|
||||||
|
*
|
||||||
|
* Iterates over all driver descriptors placed in the .drivers linker section
|
||||||
|
* by the REGISTER_DRIVER macro. Each driver is probed and, if the probe
|
||||||
|
* succeeds, initialized.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "driver.h"
|
||||||
|
#include "port_io.h"
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/* Debug print helpers defined in kernel.c */
|
||||||
|
extern void offset_print(const char *str);
|
||||||
|
extern void print_hex(uint32_t val);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linker-provided symbols marking the start and end of the .drivers section.
|
||||||
|
* Each entry is a pointer to a driver_t.
|
||||||
|
*/
|
||||||
|
extern const driver_t *__drivers_start[];
|
||||||
|
extern const driver_t *__drivers_end[];
|
||||||
|
|
||||||
|
void init_drivers(void) {
|
||||||
|
const driver_t **drv;
|
||||||
|
int loaded = 0;
|
||||||
|
int skipped = 0;
|
||||||
|
int failed = 0;
|
||||||
|
|
||||||
|
offset_print(" DRIVERS: scanning registered drivers...\n");
|
||||||
|
|
||||||
|
for (drv = __drivers_start; drv < __drivers_end; drv++) {
|
||||||
|
const driver_t *d = *drv;
|
||||||
|
if (!d || !d->name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_print(" DRIVERS: probing ");
|
||||||
|
offset_print(d->name);
|
||||||
|
offset_print("... ");
|
||||||
|
|
||||||
|
/* Run probe function if provided */
|
||||||
|
if (d->probe) {
|
||||||
|
driver_probe_result_t result = d->probe();
|
||||||
|
if (result == DRIVER_PROBE_NOT_FOUND) {
|
||||||
|
offset_print("not found\n");
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
} else if (result == DRIVER_PROBE_ERROR) {
|
||||||
|
offset_print("probe error\n");
|
||||||
|
failed++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Run init function */
|
||||||
|
if (d->init) {
|
||||||
|
int ret = d->init();
|
||||||
|
if (ret != 0) {
|
||||||
|
offset_print("init failed\n");
|
||||||
|
failed++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_print("loaded\n");
|
||||||
|
loaded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_print(" DRIVERS: done\n");
|
||||||
|
}
|
||||||
56
src/driver.h
Normal file
56
src/driver.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* @file driver.h
|
||||||
|
* @brief Kernel driver architecture.
|
||||||
|
*
|
||||||
|
* Provides a simple framework for registering and initializing kernel drivers.
|
||||||
|
* Each driver provides a probe function that returns whether it should load,
|
||||||
|
* and an init function that performs the actual initialization.
|
||||||
|
*
|
||||||
|
* Drivers are registered at compile time using the REGISTER_DRIVER macro,
|
||||||
|
* which places driver descriptors in a special linker section. The kernel
|
||||||
|
* iterates over all registered drivers during boot.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DRIVER_H
|
||||||
|
#define DRIVER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/** Driver probe result codes. */
|
||||||
|
typedef enum {
|
||||||
|
DRIVER_PROBE_OK = 0, /**< Driver should be loaded. */
|
||||||
|
DRIVER_PROBE_NOT_FOUND = 1, /**< Hardware not found, skip this driver. */
|
||||||
|
DRIVER_PROBE_ERROR = 2 /**< Error during probing. */
|
||||||
|
} driver_probe_result_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Driver descriptor structure.
|
||||||
|
*
|
||||||
|
* Each driver provides a name, a probe function (to test if hardware is
|
||||||
|
* present), and an init function (to set up the driver).
|
||||||
|
*/
|
||||||
|
typedef struct driver {
|
||||||
|
const char *name; /**< Human-readable driver name. */
|
||||||
|
driver_probe_result_t (*probe)(void); /**< Probe function. Returns DRIVER_PROBE_OK if driver should load. */
|
||||||
|
int (*init)(void); /**< Init function. Returns 0 on success, non-zero on failure. */
|
||||||
|
} driver_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a driver.
|
||||||
|
*
|
||||||
|
* Places the driver descriptor in the .drivers linker section so it
|
||||||
|
* is automatically discovered during boot.
|
||||||
|
*/
|
||||||
|
#define REGISTER_DRIVER(drv) \
|
||||||
|
static const driver_t * __attribute__((used, section(".drivers"))) \
|
||||||
|
_driver_##drv = &(drv)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize all registered drivers.
|
||||||
|
*
|
||||||
|
* Iterates over the .drivers section, probes each driver, and initializes
|
||||||
|
* those that respond positively to probing.
|
||||||
|
*/
|
||||||
|
void init_drivers(void);
|
||||||
|
|
||||||
|
#endif /* DRIVER_H */
|
||||||
158
src/idt.c
158
src/idt.c
@@ -1,46 +1,136 @@
|
|||||||
#include "idt.h"
|
#include "idt.h"
|
||||||
#include <stdint.h>
|
#include <string.h> // For memset
|
||||||
#include <stddef.h> /* for memset */
|
|
||||||
|
|
||||||
idt_entry_t idt_entries[256];
|
// The IDT itself
|
||||||
idt_ptr_t idt_ptr;
|
idt_entry_t idt[256];
|
||||||
|
idt_ptr_t idt_ptr;
|
||||||
|
|
||||||
extern void idt_load();
|
// Helper to set a gate in the IDT
|
||||||
|
static void set_idt_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags) {
|
||||||
static void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags)
|
idt[num].base_lo = base & 0xFFFF;
|
||||||
{
|
idt[num].base_hi = (base >> 16) & 0xFFFF;
|
||||||
idt_entries[num].base_lo = base & 0xFFFF;
|
idt[num].sel = sel;
|
||||||
idt_entries[num].base_hi = (base >> 16) & 0xFFFF;
|
idt[num].always0 = 0;
|
||||||
|
// flags: 0x8E = 10001110 (Present, Ring0, 32-bit Interrupt Gate)
|
||||||
idt_entries[num].sel = sel;
|
idt[num].flags = flags;
|
||||||
idt_entries[num].always0 = 0;
|
|
||||||
// We must uncomment the OR below when we get to using user-mode.
|
|
||||||
// It sets the interrupt gate's privilege level to 3.
|
|
||||||
idt_entries[num].flags = flags /* | 0x60 */;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Note: memset implementation because we don't link with libc */
|
// Exception Handlers (ISRs)
|
||||||
static void *memset(void *s, int c, size_t n)
|
extern void isr0();
|
||||||
{
|
extern void isr1();
|
||||||
unsigned char *p = s;
|
extern void isr2();
|
||||||
while(n--)
|
extern void isr3();
|
||||||
*p++ = (unsigned char)c;
|
extern void isr4();
|
||||||
return s;
|
extern void isr5();
|
||||||
}
|
extern void isr6();
|
||||||
|
extern void isr7();
|
||||||
|
extern void isr8();
|
||||||
|
extern void isr9();
|
||||||
|
extern void isr10();
|
||||||
|
extern void isr11();
|
||||||
|
extern void isr12();
|
||||||
|
extern void isr13();
|
||||||
|
extern void isr14();
|
||||||
|
extern void isr15();
|
||||||
|
extern void isr16();
|
||||||
|
extern void isr17();
|
||||||
|
extern void isr18();
|
||||||
|
extern void isr19();
|
||||||
|
extern void isr20();
|
||||||
|
extern void isr21();
|
||||||
|
extern void isr22();
|
||||||
|
extern void isr23();
|
||||||
|
extern void isr24();
|
||||||
|
extern void isr25();
|
||||||
|
extern void isr26();
|
||||||
|
extern void isr27();
|
||||||
|
extern void isr28();
|
||||||
|
extern void isr29();
|
||||||
|
extern void isr30();
|
||||||
|
extern void isr31();
|
||||||
|
|
||||||
void init_idt()
|
// Hardware Interrupt Handlers (IRQs)
|
||||||
{
|
extern void irq0();
|
||||||
|
extern void irq1();
|
||||||
|
extern void irq2();
|
||||||
|
extern void irq3();
|
||||||
|
extern void irq4();
|
||||||
|
extern void irq5();
|
||||||
|
extern void irq6();
|
||||||
|
extern void irq7();
|
||||||
|
extern void irq8();
|
||||||
|
extern void irq9();
|
||||||
|
extern void irq10();
|
||||||
|
extern void irq11();
|
||||||
|
extern void irq12();
|
||||||
|
extern void irq13();
|
||||||
|
extern void irq14();
|
||||||
|
extern void irq15();
|
||||||
|
|
||||||
|
void init_idt() {
|
||||||
|
// 1. Set up the IDT pointer
|
||||||
idt_ptr.limit = sizeof(idt_entry_t) * 256 - 1;
|
idt_ptr.limit = sizeof(idt_entry_t) * 256 - 1;
|
||||||
idt_ptr.base = (uint32_t)&idt_entries;
|
idt_ptr.base = (uint32_t)&idt;
|
||||||
|
|
||||||
memset(&idt_entries, 0, sizeof(idt_entry_t) * 256);
|
// 2. Clear the IDT
|
||||||
|
memset(&idt, 0, sizeof(idt_entry_t) * 256);
|
||||||
|
|
||||||
/* Determine valid flags:
|
// 3. Set the ISRs (Exceptions 0-31)
|
||||||
* 0x8E = 1000 1110
|
// Code Selector is 0x08 usually (defined in GDT)
|
||||||
* P=1, DPL=00, S=0 (System), Type=1110 (32-bit Interrupt Gate) */
|
// Flags: 0x8E = Present(1), DPL(00), Storage(0), GateType(1110 = 32-bit Int)
|
||||||
|
set_idt_gate( 0, (uint32_t)isr0, 0x08, 0x8E);
|
||||||
|
set_idt_gate( 1, (uint32_t)isr1, 0x08, 0x8E);
|
||||||
|
set_idt_gate( 2, (uint32_t)isr2, 0x08, 0x8E);
|
||||||
|
set_idt_gate( 3, (uint32_t)isr3, 0x08, 0x8E);
|
||||||
|
set_idt_gate( 4, (uint32_t)isr4, 0x08, 0x8E);
|
||||||
|
set_idt_gate( 5, (uint32_t)isr5, 0x08, 0x8E);
|
||||||
|
set_idt_gate( 6, (uint32_t)isr6, 0x08, 0x8E);
|
||||||
|
set_idt_gate( 7, (uint32_t)isr7, 0x08, 0x8E);
|
||||||
|
set_idt_gate( 8, (uint32_t)isr8, 0x08, 0x8E);
|
||||||
|
set_idt_gate( 9, (uint32_t)isr9, 0x08, 0x8E);
|
||||||
|
set_idt_gate(10, (uint32_t)isr10, 0x08, 0x8E);
|
||||||
|
set_idt_gate(11, (uint32_t)isr11, 0x08, 0x8E);
|
||||||
|
set_idt_gate(12, (uint32_t)isr12, 0x08, 0x8E);
|
||||||
|
set_idt_gate(13, (uint32_t)isr13, 0x08, 0x8E);
|
||||||
|
set_idt_gate(14, (uint32_t)isr14, 0x08, 0x8E);
|
||||||
|
set_idt_gate(15, (uint32_t)isr15, 0x08, 0x8E);
|
||||||
|
set_idt_gate(16, (uint32_t)isr16, 0x08, 0x8E);
|
||||||
|
set_idt_gate(17, (uint32_t)isr17, 0x08, 0x8E);
|
||||||
|
set_idt_gate(18, (uint32_t)isr18, 0x08, 0x8E);
|
||||||
|
set_idt_gate(19, (uint32_t)isr19, 0x08, 0x8E);
|
||||||
|
set_idt_gate(20, (uint32_t)isr20, 0x08, 0x8E);
|
||||||
|
set_idt_gate(21, (uint32_t)isr21, 0x08, 0x8E);
|
||||||
|
set_idt_gate(22, (uint32_t)isr22, 0x08, 0x8E);
|
||||||
|
set_idt_gate(23, (uint32_t)isr23, 0x08, 0x8E);
|
||||||
|
set_idt_gate(24, (uint32_t)isr24, 0x08, 0x8E);
|
||||||
|
set_idt_gate(25, (uint32_t)isr25, 0x08, 0x8E);
|
||||||
|
set_idt_gate(26, (uint32_t)isr26, 0x08, 0x8E);
|
||||||
|
set_idt_gate(27, (uint32_t)isr27, 0x08, 0x8E);
|
||||||
|
set_idt_gate(28, (uint32_t)isr28, 0x08, 0x8E);
|
||||||
|
set_idt_gate(29, (uint32_t)isr29, 0x08, 0x8E);
|
||||||
|
set_idt_gate(30, (uint32_t)isr30, 0x08, 0x8E);
|
||||||
|
set_idt_gate(31, (uint32_t)isr31, 0x08, 0x8E);
|
||||||
|
|
||||||
/* For now, just load the IDT without any entries to verify loading works without crashing.
|
// 4. Set the IRQs (Remapped to 32-47)
|
||||||
Later we will set up ISRs. */
|
set_idt_gate(32, (uint32_t)irq0, 0x08, 0x8E);
|
||||||
|
set_idt_gate(33, (uint32_t)irq1, 0x08, 0x8E);
|
||||||
|
set_idt_gate(34, (uint32_t)irq2, 0x08, 0x8E);
|
||||||
|
set_idt_gate(35, (uint32_t)irq3, 0x08, 0x8E);
|
||||||
|
set_idt_gate(36, (uint32_t)irq4, 0x08, 0x8E);
|
||||||
|
set_idt_gate(37, (uint32_t)irq5, 0x08, 0x8E);
|
||||||
|
set_idt_gate(38, (uint32_t)irq6, 0x08, 0x8E);
|
||||||
|
set_idt_gate(39, (uint32_t)irq7, 0x08, 0x8E);
|
||||||
|
set_idt_gate(40, (uint32_t)irq8, 0x08, 0x8E);
|
||||||
|
set_idt_gate(41, (uint32_t)irq9, 0x08, 0x8E);
|
||||||
|
set_idt_gate(42, (uint32_t)irq10, 0x08, 0x8E);
|
||||||
|
set_idt_gate(43, (uint32_t)irq11, 0x08, 0x8E);
|
||||||
|
set_idt_gate(44, (uint32_t)irq12, 0x08, 0x8E);
|
||||||
|
set_idt_gate(45, (uint32_t)irq13, 0x08, 0x8E);
|
||||||
|
set_idt_gate(46, (uint32_t)irq14, 0x08, 0x8E);
|
||||||
|
set_idt_gate(47, (uint32_t)irq15, 0x08, 0x8E);
|
||||||
|
|
||||||
idt_load((uint32_t)&idt_ptr);
|
// 5. Load the IDT using assembly instruction 'lidt'
|
||||||
|
// We can use inline assembly or a helper function.
|
||||||
|
// Assuming lid is available via inline asm or similar to gdt_flush
|
||||||
|
__asm__ volatile("lidt (%0)" : : "r" (&idt_ptr));
|
||||||
}
|
}
|
||||||
|
|||||||
117
src/interrupts.S
117
src/interrupts.S
@@ -2,6 +2,119 @@
|
|||||||
.global idt_load
|
.global idt_load
|
||||||
.type idt_load, @function
|
.type idt_load, @function
|
||||||
idt_load:
|
idt_load:
|
||||||
mov 4(%esp), %eax /* Turn the argument into the EAX register */
|
mov 4(%esp), %eax
|
||||||
lidt (%eax) /* Load the IDT pointer */
|
lidt (%eax)
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
/* Common ISR stub */
|
||||||
|
isr_common_stub:
|
||||||
|
pusha
|
||||||
|
|
||||||
|
/* Save segment registers */
|
||||||
|
mov %ds, %ax
|
||||||
|
push %eax
|
||||||
|
|
||||||
|
/* Load kernel data segment */
|
||||||
|
mov $0x10, %ax
|
||||||
|
mov %ax, %ds
|
||||||
|
mov %ax, %es
|
||||||
|
mov %ax, %fs
|
||||||
|
mov %ax, %gs
|
||||||
|
|
||||||
|
/* Push pointer to stack structure as argument for C handler */
|
||||||
|
push %esp
|
||||||
|
call isr_handler
|
||||||
|
add $4, %esp /* Clean up pushed pointer */
|
||||||
|
|
||||||
|
/* Restore segment registers */
|
||||||
|
pop %eax
|
||||||
|
mov %eax, %ds
|
||||||
|
mov %eax, %es
|
||||||
|
mov %eax, %fs
|
||||||
|
mov %eax, %gs
|
||||||
|
|
||||||
|
popa
|
||||||
|
add $8, %esp /* Cleans up error code and ISR number */
|
||||||
|
iret
|
||||||
|
|
||||||
|
/* Macro for exceptions with NO Error Code */
|
||||||
|
.macro ISR_NOERRCODE num
|
||||||
|
.global isr\num
|
||||||
|
.type isr\num, @function
|
||||||
|
isr\num:
|
||||||
|
cli
|
||||||
|
push $0
|
||||||
|
push $\num
|
||||||
|
jmp isr_common_stub
|
||||||
|
.endm
|
||||||
|
|
||||||
|
/* Macro for exceptions WITH Error Code */
|
||||||
|
.macro ISR_ERRCODE num
|
||||||
|
.global isr\num
|
||||||
|
.type isr\num, @function
|
||||||
|
isr\num:
|
||||||
|
cli
|
||||||
|
push $\num
|
||||||
|
jmp isr_common_stub
|
||||||
|
.endm
|
||||||
|
|
||||||
|
ISR_NOERRCODE 0
|
||||||
|
ISR_NOERRCODE 1
|
||||||
|
ISR_NOERRCODE 2
|
||||||
|
ISR_NOERRCODE 3
|
||||||
|
ISR_NOERRCODE 4
|
||||||
|
ISR_NOERRCODE 5
|
||||||
|
ISR_NOERRCODE 6
|
||||||
|
ISR_NOERRCODE 7
|
||||||
|
ISR_ERRCODE 8
|
||||||
|
ISR_NOERRCODE 9
|
||||||
|
ISR_ERRCODE 10
|
||||||
|
ISR_ERRCODE 11
|
||||||
|
ISR_ERRCODE 12
|
||||||
|
ISR_ERRCODE 13
|
||||||
|
ISR_ERRCODE 14
|
||||||
|
ISR_NOERRCODE 15
|
||||||
|
ISR_NOERRCODE 16
|
||||||
|
ISR_ERRCODE 17
|
||||||
|
ISR_NOERRCODE 18
|
||||||
|
ISR_NOERRCODE 19
|
||||||
|
ISR_NOERRCODE 20
|
||||||
|
ISR_NOERRCODE 21
|
||||||
|
ISR_NOERRCODE 22
|
||||||
|
ISR_NOERRCODE 23
|
||||||
|
ISR_NOERRCODE 24
|
||||||
|
ISR_NOERRCODE 25
|
||||||
|
ISR_NOERRCODE 26
|
||||||
|
ISR_NOERRCODE 27
|
||||||
|
ISR_NOERRCODE 28
|
||||||
|
ISR_NOERRCODE 29
|
||||||
|
ISR_ERRCODE 30
|
||||||
|
ISR_NOERRCODE 31
|
||||||
|
/* Macro for IRQs (Hardware Interrupts) */
|
||||||
|
.macro ISR_IRQ num, idt_index
|
||||||
|
.global irq\num
|
||||||
|
.type irq\num, @function
|
||||||
|
irq\num:
|
||||||
|
cli
|
||||||
|
push $0
|
||||||
|
push $\idt_index
|
||||||
|
jmp isr_common_stub
|
||||||
|
.endm
|
||||||
|
|
||||||
|
/* Hardware Interrupts */
|
||||||
|
ISR_IRQ 0, 32
|
||||||
|
ISR_IRQ 1, 33
|
||||||
|
ISR_IRQ 2, 34
|
||||||
|
ISR_IRQ 3, 35
|
||||||
|
ISR_IRQ 4, 36
|
||||||
|
ISR_IRQ 5, 37
|
||||||
|
ISR_IRQ 6, 38
|
||||||
|
ISR_IRQ 7, 39
|
||||||
|
ISR_IRQ 8, 40
|
||||||
|
ISR_IRQ 9, 41
|
||||||
|
ISR_IRQ 10, 42
|
||||||
|
ISR_IRQ 11, 43
|
||||||
|
ISR_IRQ 12, 44
|
||||||
|
ISR_IRQ 13, 45
|
||||||
|
ISR_IRQ 14, 46
|
||||||
|
ISR_IRQ 15, 47
|
||||||
|
|||||||
75
src/isr.c
Normal file
75
src/isr.c
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
#include "isr.h"
|
||||||
|
#include "pic.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* Forward declaration for kernel panic or similar */
|
||||||
|
void offset_print(const char *str);
|
||||||
|
void print_hex(uint32_t val);
|
||||||
|
|
||||||
|
/* Exception messages */
|
||||||
|
char *exception_messages[] = {
|
||||||
|
"Division By Zero",
|
||||||
|
"Debug",
|
||||||
|
"Non Maskable Interrupt",
|
||||||
|
"Breakpoint",
|
||||||
|
"Into Detected Overflow",
|
||||||
|
"Out of Bounds",
|
||||||
|
"Invalid Opcode",
|
||||||
|
"No Coprocessor",
|
||||||
|
"Double Fault",
|
||||||
|
"Coprocessor Segment Overrun",
|
||||||
|
"Bad TSS",
|
||||||
|
"Segment Not Present",
|
||||||
|
"Stack Fault",
|
||||||
|
"General Protection Fault",
|
||||||
|
"Page Fault",
|
||||||
|
"Unknown Interrupt",
|
||||||
|
"Coprocessor Fault",
|
||||||
|
"Alignment Check",
|
||||||
|
"Machine Check",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved",
|
||||||
|
"Reserved"
|
||||||
|
};
|
||||||
|
|
||||||
|
void isr_handler(registers_t *regs)
|
||||||
|
{
|
||||||
|
// If it's a hardware interrupt (IRQ), we must acknowledge it
|
||||||
|
if (regs->int_no >= 32 && regs->int_no < 48) {
|
||||||
|
// Send EOI to PIC (IRQ number 0-15)
|
||||||
|
pic_send_eoi(regs->int_no - 32);
|
||||||
|
|
||||||
|
// Here we would call the registered handler for this IRQ
|
||||||
|
// For now, just print something for the timer tick so we know it works,
|
||||||
|
// but limit it to avoid flooding the log.
|
||||||
|
if (regs->int_no == 32) {
|
||||||
|
// Timer tick - do nothing verbose
|
||||||
|
// offset_print(".");
|
||||||
|
} else if (regs->int_no == 33) {
|
||||||
|
// Keyboard
|
||||||
|
offset_print("Keyboard IRQ!\n");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_print("received interrupt: ");
|
||||||
|
print_hex(regs->int_no);
|
||||||
|
offset_print("\n");
|
||||||
|
|
||||||
|
if (regs->int_no < 32)
|
||||||
|
{
|
||||||
|
offset_print(exception_messages[regs->int_no]);
|
||||||
|
offset_print(" Exception. System Halted!\n");
|
||||||
|
for (;;) ;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/isr.h
Normal file
18
src/isr.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef ISR_H
|
||||||
|
#define ISR_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef struct registers
|
||||||
|
{
|
||||||
|
uint32_t ds; // Data segment selector
|
||||||
|
uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; // Pushed by pusha
|
||||||
|
uint32_t int_no, err_code; // Interrupt number and error code (if applicable)
|
||||||
|
uint32_t eip, cs, eflags, useresp, ss; // Pushed by the processor automatically
|
||||||
|
} registers_t;
|
||||||
|
|
||||||
|
typedef void (*isr_t)(registers_t*);
|
||||||
|
|
||||||
|
void isr_handler(registers_t* regs);
|
||||||
|
|
||||||
|
#endif
|
||||||
65
src/kernel.c
65
src/kernel.c
@@ -3,11 +3,13 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include "gdt.h"
|
#include "gdt.h"
|
||||||
#include "idt.h"
|
#include "idt.h"
|
||||||
|
#include "pic.h"
|
||||||
static inline void outb(uint16_t port, uint8_t val)
|
#include "port_io.h"
|
||||||
{
|
#include "pmm.h"
|
||||||
asm volatile ( "outb %b0, %w1" : : "a"(val), "Nd"(port) );
|
#include "paging.h"
|
||||||
}
|
#include "kmalloc.h"
|
||||||
|
#include "driver.h"
|
||||||
|
#include "vga.h"
|
||||||
|
|
||||||
void offset_print(const char *str)
|
void offset_print(const char *str)
|
||||||
{
|
{
|
||||||
@@ -17,8 +19,6 @@ void offset_print(const char *str)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002
|
|
||||||
|
|
||||||
void print_hex(uint32_t val)
|
void print_hex(uint32_t val)
|
||||||
{
|
{
|
||||||
const char *hex = "0123456789ABCDEF";
|
const char *hex = "0123456789ABCDEF";
|
||||||
@@ -31,9 +31,7 @@ void print_hex(uint32_t val)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void kernel_main(uint32_t magic, uint32_t addr) {
|
void kernel_main(uint32_t magic, uint32_t addr) {
|
||||||
(void)addr; // Unused for now
|
if (magic != MULTIBOOT2_BOOTLOADER_MAGIC) {
|
||||||
|
|
||||||
if (magic != MULTIBOOT2_BOOTLOADER_MAGIC && magic != MULTIBOOT_BOOTLOADER_MAGIC) {
|
|
||||||
offset_print("Invalid magic number: ");
|
offset_print("Invalid magic number: ");
|
||||||
print_hex(magic);
|
print_hex(magic);
|
||||||
return;
|
return;
|
||||||
@@ -46,6 +44,53 @@ void kernel_main(uint32_t magic, uint32_t addr) {
|
|||||||
|
|
||||||
init_idt();
|
init_idt();
|
||||||
offset_print("IDT initialized\n");
|
offset_print("IDT initialized\n");
|
||||||
|
|
||||||
|
init_pic();
|
||||||
|
offset_print("PIC initialized\n");
|
||||||
|
|
||||||
|
init_pmm(addr);
|
||||||
|
offset_print("PMM initialized\n");
|
||||||
|
|
||||||
|
init_paging();
|
||||||
|
offset_print("Paging initialized\n");
|
||||||
|
|
||||||
|
/* Test paging: allocate a page and write to it */
|
||||||
|
void *test_page = paging_alloc_page();
|
||||||
|
if (test_page) {
|
||||||
|
offset_print("Allocated virtual page at: ");
|
||||||
|
print_hex((uint32_t)test_page);
|
||||||
|
*((volatile uint32_t *)test_page) = 0xDEADBEEF;
|
||||||
|
offset_print("Virtual page write/read OK\n");
|
||||||
|
paging_free_page(test_page);
|
||||||
|
} else {
|
||||||
|
offset_print("FAILED to allocate virtual page\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
init_kmalloc();
|
||||||
|
offset_print("Memory allocator initialized\n");
|
||||||
|
|
||||||
|
init_drivers();
|
||||||
|
offset_print("Drivers initialized\n");
|
||||||
|
|
||||||
|
/* Show memory statistics and boot progress on VGA */
|
||||||
|
vga_show_mem_stats();
|
||||||
|
vga_puts("Boot complete.\n\n");
|
||||||
|
|
||||||
|
/* Test kmalloc/kfree */
|
||||||
|
uint32_t *test_alloc = (uint32_t *)kmalloc(64);
|
||||||
|
if (test_alloc) {
|
||||||
|
*test_alloc = 42;
|
||||||
|
offset_print("kmalloc(64) = ");
|
||||||
|
print_hex((uint32_t)test_alloc);
|
||||||
|
kfree(test_alloc);
|
||||||
|
offset_print("kfree OK\n");
|
||||||
|
} else {
|
||||||
|
offset_print("FAILED to kmalloc\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enable interrupts */
|
||||||
|
asm volatile("sti");
|
||||||
|
offset_print("Interrupts enabled\n");
|
||||||
|
|
||||||
offset_print("Hello, world\n");
|
offset_print("Hello, world\n");
|
||||||
}
|
}
|
||||||
|
|||||||
253
src/kmalloc.c
Normal file
253
src/kmalloc.c
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
/**
|
||||||
|
* @file kmalloc.c
|
||||||
|
* @brief Kernel memory allocator implementation.
|
||||||
|
*
|
||||||
|
* A simple first-fit free-list allocator. Memory is obtained from the paging
|
||||||
|
* subsystem in 4 KiB page increments. The allocator maintains a linked list
|
||||||
|
* of free blocks. On allocation, the first block large enough is split if
|
||||||
|
* needed. On free, adjacent blocks are coalesced to reduce fragmentation.
|
||||||
|
*
|
||||||
|
* The allocator metadata (block headers) are stored inline at the beginning
|
||||||
|
* of each block, so the minimum allocation overhead is sizeof(block_header_t).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "kmalloc.h"
|
||||||
|
#include "paging.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Debug print helpers defined in kernel.c */
|
||||||
|
extern void offset_print(const char *str);
|
||||||
|
extern void print_hex(uint32_t val);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block header stored at the start of every allocated or free block.
|
||||||
|
* The usable memory starts immediately after this header.
|
||||||
|
*/
|
||||||
|
typedef struct block_header {
|
||||||
|
uint32_t size; /**< Size of the usable area (excludes header). */
|
||||||
|
uint32_t magic; /**< Magic number for integrity checking. */
|
||||||
|
struct block_header *next; /**< Next block in the free list (free blocks only). */
|
||||||
|
uint8_t is_free; /**< 1 if block is free, 0 if allocated. */
|
||||||
|
} block_header_t;
|
||||||
|
|
||||||
|
/** Magic value to detect heap corruption. */
|
||||||
|
#define BLOCK_MAGIC 0xCAFEBABE
|
||||||
|
|
||||||
|
/** Minimum block size to avoid excessive fragmentation. */
|
||||||
|
#define MIN_BLOCK_SIZE 16
|
||||||
|
|
||||||
|
/** Alignment for all allocations (8-byte aligned). */
|
||||||
|
#define ALIGNMENT 8
|
||||||
|
|
||||||
|
/** Round up to alignment boundary. */
|
||||||
|
#define ALIGN_UP(x, a) (((x) + (a) - 1) & ~((a) - 1))
|
||||||
|
|
||||||
|
/** Head of the free list. */
|
||||||
|
static block_header_t *free_list = NULL;
|
||||||
|
|
||||||
|
/** Number of pages currently allocated for the heap. */
|
||||||
|
static uint32_t heap_pages = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a new page from the paging subsystem and add it to the free list.
|
||||||
|
*
|
||||||
|
* @return Pointer to the new block header, or NULL on failure.
|
||||||
|
*/
|
||||||
|
static block_header_t *request_page(void) {
|
||||||
|
void *page = paging_alloc_page();
|
||||||
|
if (!page) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
heap_pages++;
|
||||||
|
|
||||||
|
block_header_t *block = (block_header_t *)page;
|
||||||
|
block->size = 4096 - sizeof(block_header_t);
|
||||||
|
block->magic = BLOCK_MAGIC;
|
||||||
|
block->next = NULL;
|
||||||
|
block->is_free = 1;
|
||||||
|
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a block into the free list, maintaining address order.
|
||||||
|
* Then attempt to coalesce with adjacent blocks.
|
||||||
|
*
|
||||||
|
* @param block The block to insert.
|
||||||
|
*/
|
||||||
|
static void insert_free_block(block_header_t *block) {
|
||||||
|
block->is_free = 1;
|
||||||
|
|
||||||
|
/* Find insertion point (maintain address order for coalescing) */
|
||||||
|
block_header_t *prev = NULL;
|
||||||
|
block_header_t *curr = free_list;
|
||||||
|
|
||||||
|
while (curr && curr < block) {
|
||||||
|
prev = curr;
|
||||||
|
curr = curr->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Insert between prev and curr */
|
||||||
|
block->next = curr;
|
||||||
|
if (prev) {
|
||||||
|
prev->next = block;
|
||||||
|
} else {
|
||||||
|
free_list = block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Coalesce with next block if adjacent */
|
||||||
|
if (block->next) {
|
||||||
|
uint8_t *block_end = (uint8_t *)block + sizeof(block_header_t) + block->size;
|
||||||
|
if (block_end == (uint8_t *)block->next) {
|
||||||
|
block->size += sizeof(block_header_t) + block->next->size;
|
||||||
|
block->next = block->next->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Coalesce with previous block if adjacent */
|
||||||
|
if (prev) {
|
||||||
|
uint8_t *prev_end = (uint8_t *)prev + sizeof(block_header_t) + prev->size;
|
||||||
|
if (prev_end == (uint8_t *)block) {
|
||||||
|
prev->size += sizeof(block_header_t) + block->size;
|
||||||
|
prev->next = block->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split a block if it is large enough to hold the requested size
|
||||||
|
* plus a new block header and minimum block.
|
||||||
|
*
|
||||||
|
* @param block The block to potentially split.
|
||||||
|
* @param size The requested allocation size (aligned).
|
||||||
|
*/
|
||||||
|
static void split_block(block_header_t *block, uint32_t size) {
|
||||||
|
uint32_t remaining = block->size - size - sizeof(block_header_t);
|
||||||
|
|
||||||
|
if (remaining >= MIN_BLOCK_SIZE) {
|
||||||
|
block_header_t *new_block = (block_header_t *)((uint8_t *)block + sizeof(block_header_t) + size);
|
||||||
|
new_block->size = remaining;
|
||||||
|
new_block->magic = BLOCK_MAGIC;
|
||||||
|
new_block->is_free = 1;
|
||||||
|
new_block->next = block->next;
|
||||||
|
|
||||||
|
block->size = size;
|
||||||
|
block->next = new_block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_kmalloc(void) {
|
||||||
|
/* Allocate the initial heap page */
|
||||||
|
block_header_t *initial = request_page();
|
||||||
|
if (!initial) {
|
||||||
|
offset_print(" KMALLOC: FATAL - could not allocate initial heap page\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
free_list = initial;
|
||||||
|
offset_print(" KMALLOC: initialized with 1 page\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void *kmalloc(size_t size) {
|
||||||
|
if (size == 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Align the requested size */
|
||||||
|
size = ALIGN_UP(size, ALIGNMENT);
|
||||||
|
|
||||||
|
/* First-fit search through free list */
|
||||||
|
block_header_t *prev = NULL;
|
||||||
|
block_header_t *curr = free_list;
|
||||||
|
|
||||||
|
while (curr) {
|
||||||
|
if (curr->magic != BLOCK_MAGIC) {
|
||||||
|
offset_print(" KMALLOC: HEAP CORRUPTION detected!\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curr->is_free && curr->size >= size) {
|
||||||
|
/* Found a suitable block */
|
||||||
|
split_block(curr, size);
|
||||||
|
|
||||||
|
/* Remove from free list */
|
||||||
|
if (prev) {
|
||||||
|
prev->next = curr->next;
|
||||||
|
} else {
|
||||||
|
free_list = curr->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
curr->is_free = 0;
|
||||||
|
curr->next = NULL;
|
||||||
|
|
||||||
|
/* Return pointer past the header */
|
||||||
|
return (void *)((uint8_t *)curr + sizeof(block_header_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = curr;
|
||||||
|
curr = curr->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No suitable block found; request a new page */
|
||||||
|
block_header_t *new_block = request_page();
|
||||||
|
if (!new_block) {
|
||||||
|
offset_print(" KMALLOC: out of memory\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the new page is large enough, use it directly */
|
||||||
|
if (new_block->size >= size) {
|
||||||
|
split_block(new_block, size);
|
||||||
|
|
||||||
|
/* If there's a remainder, add it to the free list */
|
||||||
|
if (new_block->next && new_block->next->is_free) {
|
||||||
|
insert_free_block(new_block->next);
|
||||||
|
}
|
||||||
|
|
||||||
|
new_block->is_free = 0;
|
||||||
|
new_block->next = NULL;
|
||||||
|
return (void *)((uint8_t *)new_block + sizeof(block_header_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page too small (shouldn't happen for reasonable sizes) */
|
||||||
|
offset_print(" KMALLOC: requested size too large for single page\n");
|
||||||
|
insert_free_block(new_block);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kfree(void *ptr) {
|
||||||
|
if (!ptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the block header */
|
||||||
|
block_header_t *block = (block_header_t *)((uint8_t *)ptr - sizeof(block_header_t));
|
||||||
|
|
||||||
|
if (block->magic != BLOCK_MAGIC) {
|
||||||
|
offset_print(" KMALLOC: kfree() invalid pointer or corruption!\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block->is_free) {
|
||||||
|
offset_print(" KMALLOC: kfree() double free detected!\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
insert_free_block(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *kcalloc(size_t count, size_t size) {
|
||||||
|
size_t total = count * size;
|
||||||
|
|
||||||
|
/* Overflow check */
|
||||||
|
if (count != 0 && total / count != size) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *ptr = kmalloc(total);
|
||||||
|
if (ptr) {
|
||||||
|
memset(ptr, 0, total);
|
||||||
|
}
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
46
src/kmalloc.h
Normal file
46
src/kmalloc.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* @file kmalloc.h
|
||||||
|
* @brief Kernel memory allocator.
|
||||||
|
*
|
||||||
|
* Provides malloc/free for the kernel. Internally uses the paging subsystem
|
||||||
|
* to ensure returned addresses are backed by physical RAM. Uses a simple
|
||||||
|
* first-fit free-list allocator with block splitting and coalescing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KMALLOC_H
|
||||||
|
#define KMALLOC_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the kernel memory allocator.
|
||||||
|
*
|
||||||
|
* Must be called after init_paging(). Allocates the initial heap pages.
|
||||||
|
*/
|
||||||
|
void init_kmalloc(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate a block of kernel memory.
|
||||||
|
*
|
||||||
|
* @param size Number of bytes to allocate.
|
||||||
|
* @return Pointer to the allocated memory, or NULL on failure.
|
||||||
|
*/
|
||||||
|
void *kmalloc(size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free a previously allocated block of kernel memory.
|
||||||
|
*
|
||||||
|
* @param ptr Pointer returned by kmalloc. NULL is safely ignored.
|
||||||
|
*/
|
||||||
|
void kfree(void *ptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate a block of zero-initialized kernel memory.
|
||||||
|
*
|
||||||
|
* @param count Number of elements.
|
||||||
|
* @param size Size of each element in bytes.
|
||||||
|
* @return Pointer to the allocated and zeroed memory, or NULL on failure.
|
||||||
|
*/
|
||||||
|
void *kcalloc(size_t count, size_t size);
|
||||||
|
|
||||||
|
#endif /* KMALLOC_H */
|
||||||
@@ -4,6 +4,8 @@ SECTIONS
|
|||||||
{
|
{
|
||||||
. = 1M;
|
. = 1M;
|
||||||
|
|
||||||
|
_kernel_start = .;
|
||||||
|
|
||||||
.text BLOCK(4K) : ALIGN(4K)
|
.text BLOCK(4K) : ALIGN(4K)
|
||||||
{
|
{
|
||||||
KEEP(*(.multiboot))
|
KEEP(*(.multiboot))
|
||||||
@@ -13,6 +15,14 @@ SECTIONS
|
|||||||
.rodata BLOCK(4K) : ALIGN(4K)
|
.rodata BLOCK(4K) : ALIGN(4K)
|
||||||
{
|
{
|
||||||
*(.rodata)
|
*(.rodata)
|
||||||
|
*(.rodata.*)
|
||||||
|
}
|
||||||
|
|
||||||
|
.drivers BLOCK(4K) : ALIGN(4K)
|
||||||
|
{
|
||||||
|
__drivers_start = .;
|
||||||
|
KEEP(*(.drivers))
|
||||||
|
__drivers_end = .;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data BLOCK(4K) : ALIGN(4K)
|
.data BLOCK(4K) : ALIGN(4K)
|
||||||
@@ -25,4 +35,6 @@ SECTIONS
|
|||||||
*(COMMON)
|
*(COMMON)
|
||||||
*(.bss)
|
*(.bss)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_kernel_end = .;
|
||||||
}
|
}
|
||||||
|
|||||||
278
src/paging.c
Normal file
278
src/paging.c
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
/**
|
||||||
|
* @file paging.c
|
||||||
|
* @brief Virtual memory paging subsystem implementation.
|
||||||
|
*
|
||||||
|
* Implements two-level x86 paging (page directory + page tables) with 4 KiB
|
||||||
|
* pages. At initialization, all detected physical memory is identity-mapped
|
||||||
|
* so that physical addresses equal virtual addresses. Drivers and the kernel
|
||||||
|
* can then allocate additional virtual pages as needed.
|
||||||
|
*
|
||||||
|
* The kernel heap region starts at KERNEL_HEAP_START (0xD0000000) and grows
|
||||||
|
* upward as pages are requested through paging_alloc_page().
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "paging.h"
|
||||||
|
#include "pmm.h"
|
||||||
|
#include "port_io.h"
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Debug print helpers defined in kernel.c */
|
||||||
|
extern void offset_print(const char *str);
|
||||||
|
extern void print_hex(uint32_t val);
|
||||||
|
|
||||||
|
/** Kernel heap starts at 0xD0000000 (above the 0xC0000000 higher-half region). */
|
||||||
|
#define KERNEL_HEAP_START 0xD0000000
|
||||||
|
/** Kernel heap ends at 0xF0000000 (768 MiB of virtual space for kernel heap). */
|
||||||
|
#define KERNEL_HEAP_END 0xF0000000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The page directory. Must be page-aligned (4 KiB).
|
||||||
|
* Each entry either points to a page table or is zero (not present).
|
||||||
|
*/
|
||||||
|
static uint32_t page_directory[PAGE_ENTRIES] __attribute__((aligned(4096)));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storage for page tables. We pre-allocate enough for identity mapping.
|
||||||
|
* For a system with up to 4 GiB, we'd need 1024 page tables, but we
|
||||||
|
* only use these for the first 16 MiB during early boot. Additional page
|
||||||
|
* tables are allocated from the PMM as needed.
|
||||||
|
*
|
||||||
|
* The first 16 MiB must be statically allocated because the PMM bitmap
|
||||||
|
* itself lives in BSS within this region.
|
||||||
|
*/
|
||||||
|
#define STATIC_PT_COUNT 4
|
||||||
|
static uint32_t static_page_tables[STATIC_PT_COUNT][PAGE_ENTRIES] __attribute__((aligned(4096)));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamically allocated page tables for memory above 16 MiB.
|
||||||
|
* Before paging is enabled, we allocate these from the PMM and store
|
||||||
|
* their physical addresses here so we can access them after paging.
|
||||||
|
*/
|
||||||
|
#define MAX_DYNAMIC_PT 256
|
||||||
|
static uint32_t *dynamic_page_tables[MAX_DYNAMIC_PT];
|
||||||
|
static uint32_t dynamic_pt_count = 0;
|
||||||
|
|
||||||
|
/** Next virtual address to hand out from the kernel heap. */
|
||||||
|
static uint32_t heap_next = KERNEL_HEAP_START;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush a single TLB entry for the given virtual address.
|
||||||
|
*
|
||||||
|
* @param vaddr The virtual address whose TLB entry to invalidate.
|
||||||
|
*/
|
||||||
|
static inline void tlb_flush_single(uint32_t vaddr) {
|
||||||
|
__asm__ volatile("invlpg (%0)" : : "r"(vaddr) : "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload CR3 to flush the entire TLB.
|
||||||
|
*/
|
||||||
|
static inline void tlb_flush_all(void) {
|
||||||
|
uint32_t cr3;
|
||||||
|
__asm__ volatile("mov %%cr3, %0" : "=r"(cr3));
|
||||||
|
__asm__ volatile("mov %0, %%cr3" : : "r"(cr3) : "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a page table for a given page directory index.
|
||||||
|
*
|
||||||
|
* If the page directory entry is not present, allocate a new page table
|
||||||
|
* from the PMM and install it.
|
||||||
|
*
|
||||||
|
* @param pd_idx Page directory index (0–1023).
|
||||||
|
* @param create If non-zero, create the page table if it doesn't exist.
|
||||||
|
* @return Pointer to the page table, or NULL if not present and !create.
|
||||||
|
*/
|
||||||
|
static uint32_t *get_page_table(uint32_t pd_idx, int create) {
|
||||||
|
if (page_directory[pd_idx] & PAGE_PRESENT) {
|
||||||
|
return (uint32_t *)(page_directory[pd_idx] & 0xFFFFF000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!create) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate a new page table from the PMM */
|
||||||
|
phys_addr_t pt_phys = pmm_alloc_page(PMM_ZONE_NORMAL);
|
||||||
|
if (pt_phys == 0) {
|
||||||
|
offset_print(" PAGING: FATAL - could not allocate page table\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zero the new page table */
|
||||||
|
memset((void *)pt_phys, 0, 4096);
|
||||||
|
|
||||||
|
/* Install it in the page directory */
|
||||||
|
page_directory[pd_idx] = pt_phys | PAGE_PRESENT | PAGE_WRITE;
|
||||||
|
|
||||||
|
return (uint32_t *)pt_phys;
|
||||||
|
}
|
||||||
|
|
||||||
|
void paging_map_page(uint32_t vaddr, uint32_t paddr, uint32_t flags) {
|
||||||
|
uint32_t pd_idx = PD_INDEX(vaddr);
|
||||||
|
uint32_t pt_idx = PT_INDEX(vaddr);
|
||||||
|
|
||||||
|
uint32_t *pt = get_page_table(pd_idx, 1);
|
||||||
|
if (!pt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pt[pt_idx] = (paddr & 0xFFFFF000) | (flags & 0xFFF);
|
||||||
|
tlb_flush_single(vaddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void paging_unmap_page(uint32_t vaddr) {
|
||||||
|
uint32_t pd_idx = PD_INDEX(vaddr);
|
||||||
|
uint32_t pt_idx = PT_INDEX(vaddr);
|
||||||
|
|
||||||
|
uint32_t *pt = get_page_table(pd_idx, 0);
|
||||||
|
if (!pt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pt[pt_idx] = 0;
|
||||||
|
tlb_flush_single(vaddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t paging_get_physical(uint32_t vaddr) {
|
||||||
|
uint32_t pd_idx = PD_INDEX(vaddr);
|
||||||
|
uint32_t pt_idx = PT_INDEX(vaddr);
|
||||||
|
|
||||||
|
uint32_t *pt = get_page_table(pd_idx, 0);
|
||||||
|
if (!pt) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(pt[pt_idx] & PAGE_PRESENT)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (pt[pt_idx] & 0xFFFFF000) | (vaddr & 0xFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *paging_alloc_page(void) {
|
||||||
|
if (heap_next >= KERNEL_HEAP_END) {
|
||||||
|
offset_print(" PAGING: kernel heap exhausted\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate a physical page */
|
||||||
|
phys_addr_t paddr = pmm_alloc_page(PMM_ZONE_NORMAL);
|
||||||
|
if (paddr == 0) {
|
||||||
|
offset_print(" PAGING: out of physical memory\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Map it into the kernel heap */
|
||||||
|
uint32_t vaddr = heap_next;
|
||||||
|
paging_map_page(vaddr, paddr, PAGE_PRESENT | PAGE_WRITE);
|
||||||
|
heap_next += 4096;
|
||||||
|
|
||||||
|
return (void *)vaddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void paging_free_page(void *vaddr) {
|
||||||
|
uint32_t va = (uint32_t)vaddr;
|
||||||
|
|
||||||
|
/* Look up the physical address before unmapping */
|
||||||
|
uint32_t paddr = paging_get_physical(va);
|
||||||
|
if (paddr == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unmap the virtual page */
|
||||||
|
paging_unmap_page(va);
|
||||||
|
|
||||||
|
/* Return the physical page to the PMM */
|
||||||
|
pmm_free_page(paddr & 0xFFFFF000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_paging(void) {
|
||||||
|
/* 1. Zero the page directory */
|
||||||
|
memset(page_directory, 0, sizeof(page_directory));
|
||||||
|
|
||||||
|
/* 2. Identity map the first 16 MiB using static page tables.
|
||||||
|
* This covers the kernel (loaded at 1 MiB), the PMM bitmap (in BSS),
|
||||||
|
* the stack, and typical BIOS/device regions.
|
||||||
|
* Each page table maps 4 MiB (1024 entries × 4 KiB).
|
||||||
|
*/
|
||||||
|
for (uint32_t i = 0; i < STATIC_PT_COUNT; i++) {
|
||||||
|
memset(static_page_tables[i], 0, sizeof(static_page_tables[i]));
|
||||||
|
|
||||||
|
for (uint32_t j = 0; j < PAGE_ENTRIES; j++) {
|
||||||
|
uint32_t paddr = (i * PAGE_ENTRIES + j) * 4096;
|
||||||
|
static_page_tables[i][j] = paddr | PAGE_PRESENT | PAGE_WRITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
page_directory[i] = (uint32_t)static_page_tables[i] | PAGE_PRESENT | PAGE_WRITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_print(" PAGING: identity mapped first 16 MiB\n");
|
||||||
|
|
||||||
|
/* 3. Identity map memory above 16 MiB using dynamically allocated page
|
||||||
|
* tables. We do this BEFORE enabling paging, so physical addresses
|
||||||
|
* are still directly accessible.
|
||||||
|
*
|
||||||
|
* mem_upper is in KiB and starts at 1 MiB, so total memory is
|
||||||
|
* approximately (mem_upper + 1024) KiB.
|
||||||
|
*/
|
||||||
|
uint32_t mem_kb = pmm_get_memory_size() + 1024; /* total memory in KiB */
|
||||||
|
uint32_t total_bytes = mem_kb * 1024;
|
||||||
|
uint32_t pd_entries_needed = (total_bytes + (4 * 1024 * 1024 - 1)) / (4 * 1024 * 1024);
|
||||||
|
|
||||||
|
if (pd_entries_needed > PAGE_ENTRIES) {
|
||||||
|
pd_entries_needed = PAGE_ENTRIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic_pt_count = 0;
|
||||||
|
for (uint32_t i = STATIC_PT_COUNT; i < pd_entries_needed; i++) {
|
||||||
|
if (dynamic_pt_count >= MAX_DYNAMIC_PT) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate a page for this page table from the DMA zone,
|
||||||
|
* since we need it to be accessible before paging is enabled
|
||||||
|
* (i.e., within the first 16 MiB identity map won't help for
|
||||||
|
* the page table itself, but we haven't enabled paging yet so
|
||||||
|
* ALL physical memory is accessible). */
|
||||||
|
phys_addr_t pt_phys = pmm_alloc_page(PMM_ZONE_DMA);
|
||||||
|
if (pt_phys == 0) {
|
||||||
|
pt_phys = pmm_alloc_page(PMM_ZONE_NORMAL);
|
||||||
|
}
|
||||||
|
if (pt_phys == 0) {
|
||||||
|
offset_print(" PAGING: WARNING - could not alloc page table\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t *pt = (uint32_t *)pt_phys;
|
||||||
|
dynamic_page_tables[dynamic_pt_count++] = pt;
|
||||||
|
|
||||||
|
/* Fill the page table with identity mappings */
|
||||||
|
for (uint32_t j = 0; j < PAGE_ENTRIES; j++) {
|
||||||
|
uint32_t paddr = (i * PAGE_ENTRIES + j) * 4096;
|
||||||
|
pt[j] = paddr | PAGE_PRESENT | PAGE_WRITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
page_directory[i] = pt_phys | PAGE_PRESENT | PAGE_WRITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dynamic_pt_count > 0) {
|
||||||
|
offset_print(" PAGING: identity mapped ");
|
||||||
|
print_hex(pd_entries_needed * 4);
|
||||||
|
offset_print(" PAGING: MiB total using ");
|
||||||
|
print_hex(dynamic_pt_count);
|
||||||
|
offset_print(" PAGING: additional page tables\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. Load the page directory into CR3 */
|
||||||
|
__asm__ volatile("mov %0, %%cr3" : : "r"(page_directory) : "memory");
|
||||||
|
|
||||||
|
/* 5. Enable paging by setting bit 31 (PG) of CR0 */
|
||||||
|
uint32_t cr0;
|
||||||
|
__asm__ volatile("mov %%cr0, %0" : "=r"(cr0));
|
||||||
|
cr0 |= 0x80000000;
|
||||||
|
__asm__ volatile("mov %0, %%cr0" : : "r"(cr0) : "memory");
|
||||||
|
|
||||||
|
offset_print(" PAGING: enabled\n");
|
||||||
|
}
|
||||||
86
src/paging.h
Normal file
86
src/paging.h
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* @file paging.h
|
||||||
|
* @brief Virtual memory paging subsystem.
|
||||||
|
*
|
||||||
|
* Provides page directory and page table management for the x86 two-level
|
||||||
|
* paging scheme (no PAE). Allows mapping and unmapping individual 4 KiB pages,
|
||||||
|
* as well as allocating virtual pages backed by physical memory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef PAGING_H
|
||||||
|
#define PAGING_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/** Page table entry flags. */
|
||||||
|
#define PAGE_PRESENT 0x001 /**< Page is present in memory. */
|
||||||
|
#define PAGE_WRITE 0x002 /**< Page is writable. */
|
||||||
|
#define PAGE_USER 0x004 /**< Page is accessible from ring 3. */
|
||||||
|
#define PAGE_WRITETHROUGH 0x008 /**< Write-through caching. */
|
||||||
|
#define PAGE_NOCACHE 0x010 /**< Disable caching for this page. */
|
||||||
|
#define PAGE_ACCESSED 0x020 /**< CPU has read from this page. */
|
||||||
|
#define PAGE_DIRTY 0x040 /**< CPU has written to this page. */
|
||||||
|
#define PAGE_SIZE_4MB 0x080 /**< 4 MiB page (page directory only). */
|
||||||
|
|
||||||
|
/** Number of entries in a page directory or page table. */
|
||||||
|
#define PAGE_ENTRIES 1024
|
||||||
|
|
||||||
|
/** Extract the page directory index from a virtual address. */
|
||||||
|
#define PD_INDEX(vaddr) (((uint32_t)(vaddr) >> 22) & 0x3FF)
|
||||||
|
|
||||||
|
/** Extract the page table index from a virtual address. */
|
||||||
|
#define PT_INDEX(vaddr) (((uint32_t)(vaddr) >> 12) & 0x3FF)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the paging subsystem.
|
||||||
|
*
|
||||||
|
* Sets up a page directory, identity-maps all detected physical memory,
|
||||||
|
* and enables paging by writing to CR3 and CR0.
|
||||||
|
*/
|
||||||
|
void init_paging(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map a virtual address to a physical address with the given flags.
|
||||||
|
*
|
||||||
|
* If no page table exists for the virtual address range, one is allocated
|
||||||
|
* from the PMM automatically.
|
||||||
|
*
|
||||||
|
* @param vaddr Virtual address (must be page-aligned).
|
||||||
|
* @param paddr Physical address (must be page-aligned).
|
||||||
|
* @param flags Page flags (PAGE_PRESENT | PAGE_WRITE | ...).
|
||||||
|
*/
|
||||||
|
void paging_map_page(uint32_t vaddr, uint32_t paddr, uint32_t flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unmap a virtual address, freeing the mapping but not the physical page.
|
||||||
|
*
|
||||||
|
* @param vaddr Virtual address to unmap (must be page-aligned).
|
||||||
|
*/
|
||||||
|
void paging_unmap_page(uint32_t vaddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate a new virtual page backed by physical memory.
|
||||||
|
*
|
||||||
|
* Finds a free virtual address, allocates a physical page from the PMM,
|
||||||
|
* and creates a mapping.
|
||||||
|
*
|
||||||
|
* @return Virtual address of the allocated page, or NULL on failure.
|
||||||
|
*/
|
||||||
|
void *paging_alloc_page(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free a virtual page, unmapping it and returning the physical page to the PMM.
|
||||||
|
*
|
||||||
|
* @param vaddr Virtual address of the page to free.
|
||||||
|
*/
|
||||||
|
void paging_free_page(void *vaddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up the physical address mapped to a virtual address.
|
||||||
|
*
|
||||||
|
* @param vaddr Virtual address to translate.
|
||||||
|
* @return Physical address, or 0 if the page is not mapped.
|
||||||
|
*/
|
||||||
|
uint32_t paging_get_physical(uint32_t vaddr);
|
||||||
|
|
||||||
|
#endif /* PAGING_H */
|
||||||
95
src/pic.c
Normal file
95
src/pic.c
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
#include "pic.h"
|
||||||
|
#include "port_io.h"
|
||||||
|
|
||||||
|
#define PIC1_COMMAND 0x20
|
||||||
|
#define PIC1_DATA 0x21
|
||||||
|
#define PIC2_COMMAND 0xA0
|
||||||
|
#define PIC2_DATA 0xA1
|
||||||
|
|
||||||
|
#define ICW1_ICW4 0x01 /* ICW4 (not) needed */
|
||||||
|
#define ICW1_SINGLE 0x02 /* Single (cascade) mode */
|
||||||
|
#define ICW1_INTERVAL4 0x04 /* Call address interval 4 (8) */
|
||||||
|
#define ICW1_LEVEL 0x08 /* Level triggered (edge) mode */
|
||||||
|
#define ICW1_INIT 0x10 /* Initialization - required! */
|
||||||
|
|
||||||
|
#define ICW4_8086 0x01 /* 8086/88 (MCS-80/85) mode */
|
||||||
|
#define ICW4_AUTO 0x02 /* Auto (normal) EOI */
|
||||||
|
#define ICW4_BUF_SLAVE 0x08 /* Buffered mode/slave */
|
||||||
|
#define ICW4_BUF_MASTER 0x0C /* Buffered mode/master */
|
||||||
|
#define ICW4_SFNM 0x10 /* Special fully nested (not) */
|
||||||
|
|
||||||
|
#define PIC_EOI 0x20
|
||||||
|
|
||||||
|
void pic_send_eoi(uint8_t irq)
|
||||||
|
{
|
||||||
|
if(irq >= 8)
|
||||||
|
outb(PIC2_COMMAND, PIC_EOI);
|
||||||
|
|
||||||
|
outb(PIC1_COMMAND, PIC_EOI);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
reinitialize the PIC controllers, giving them specified vector offsets
|
||||||
|
rather than 8h and 70h, as configured by default
|
||||||
|
*/
|
||||||
|
#define PIC1_OFFSET 0x20
|
||||||
|
#define PIC2_OFFSET 0x28
|
||||||
|
|
||||||
|
void init_pic(void)
|
||||||
|
{
|
||||||
|
unsigned char a1, a2;
|
||||||
|
|
||||||
|
a1 = inb(PIC1_DATA); // save masks
|
||||||
|
a2 = inb(PIC2_DATA);
|
||||||
|
|
||||||
|
outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4); // starts the initialization sequence (in cascade mode)
|
||||||
|
io_wait();
|
||||||
|
outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4);
|
||||||
|
io_wait();
|
||||||
|
|
||||||
|
outb(PIC1_DATA, PIC1_OFFSET); // ICW2: Master PIC vector offset
|
||||||
|
io_wait();
|
||||||
|
outb(PIC2_DATA, PIC2_OFFSET); // ICW2: Slave PIC vector offset
|
||||||
|
io_wait();
|
||||||
|
|
||||||
|
outb(PIC1_DATA, 4); // ICW3: tell Master PIC that there is a slave PIC at IRQ2 (0000 0100)
|
||||||
|
io_wait();
|
||||||
|
outb(PIC2_DATA, 2); // ICW3: tell Slave PIC its cascade identity (0000 0010)
|
||||||
|
io_wait();
|
||||||
|
|
||||||
|
outb(PIC1_DATA, ICW4_8086);
|
||||||
|
io_wait();
|
||||||
|
outb(PIC2_DATA, ICW4_8086);
|
||||||
|
io_wait();
|
||||||
|
|
||||||
|
outb(PIC1_DATA, a1); // restore saved masks.
|
||||||
|
outb(PIC2_DATA, a2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pic_clear_mask(uint8_t irq) {
|
||||||
|
uint16_t port;
|
||||||
|
uint8_t value;
|
||||||
|
|
||||||
|
if(irq < 8) {
|
||||||
|
port = PIC1_DATA;
|
||||||
|
} else {
|
||||||
|
port = PIC2_DATA;
|
||||||
|
irq -= 8;
|
||||||
|
}
|
||||||
|
value = inb(port) & ~(1 << irq);
|
||||||
|
outb(port, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pic_set_mask(uint8_t irq) {
|
||||||
|
uint16_t port;
|
||||||
|
uint8_t value;
|
||||||
|
|
||||||
|
if(irq < 8) {
|
||||||
|
port = PIC1_DATA;
|
||||||
|
} else {
|
||||||
|
port = PIC2_DATA;
|
||||||
|
irq -= 8;
|
||||||
|
}
|
||||||
|
value = inb(port) | (1 << irq);
|
||||||
|
outb(port, value);
|
||||||
|
}
|
||||||
11
src/pic.h
Normal file
11
src/pic.h
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#ifndef PIC_H
|
||||||
|
#define PIC_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void init_pic(void);
|
||||||
|
void pic_send_eoi(uint8_t irq);
|
||||||
|
void pic_clear_mask(uint8_t irq);
|
||||||
|
void pic_set_mask(uint8_t irq);
|
||||||
|
|
||||||
|
#endif
|
||||||
165
src/pmm.c
Normal file
165
src/pmm.c
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#include "pmm.h"
|
||||||
|
#include "port_io.h"
|
||||||
|
#include <multiboot2.h>
|
||||||
|
|
||||||
|
extern uint32_t _kernel_start;
|
||||||
|
extern uint32_t _kernel_end;
|
||||||
|
|
||||||
|
/* Printing helpers (defined in kernel.c) */
|
||||||
|
extern void offset_print(const char *str);
|
||||||
|
extern void print_hex(uint32_t val);
|
||||||
|
|
||||||
|
#define MAX_PHYSICAL_MEMORY 0xFFFFFFFF // 4GB
|
||||||
|
#define TOTAL_FRAMES (MAX_PHYSICAL_MEMORY / PAGE_SIZE + 1)
|
||||||
|
#define FRAMES_PER_INDEX 32
|
||||||
|
#define BITMAP_SIZE ((TOTAL_FRAMES + FRAMES_PER_INDEX - 1) / FRAMES_PER_INDEX)
|
||||||
|
|
||||||
|
static uint32_t memory_bitmap[BITMAP_SIZE];
|
||||||
|
static uint32_t memory_size = 0;
|
||||||
|
// static uint32_t used_frames = 0; // Unused for now
|
||||||
|
|
||||||
|
static void set_frame(phys_addr_t frame_addr) {
|
||||||
|
uint32_t frame = frame_addr / PAGE_SIZE;
|
||||||
|
uint32_t idx = frame / FRAMES_PER_INDEX;
|
||||||
|
uint32_t off = frame % FRAMES_PER_INDEX;
|
||||||
|
memory_bitmap[idx] |= (1 << off);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clear_frame(phys_addr_t frame_addr) {
|
||||||
|
uint32_t frame = frame_addr / PAGE_SIZE;
|
||||||
|
uint32_t idx = frame / FRAMES_PER_INDEX;
|
||||||
|
uint32_t off = frame % FRAMES_PER_INDEX;
|
||||||
|
memory_bitmap[idx] &= ~(1 << off);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t first_free_frame(size_t start_frame, size_t end_frame) {
|
||||||
|
for (size_t i = start_frame; i < end_frame; i++) {
|
||||||
|
uint32_t idx = i / FRAMES_PER_INDEX;
|
||||||
|
uint32_t off = i % FRAMES_PER_INDEX;
|
||||||
|
if (!(memory_bitmap[idx] & (1 << off))) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (uint32_t)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_pmm(uint32_t multiboot_addr) {
|
||||||
|
struct multiboot_tag *tag;
|
||||||
|
uint32_t addr = multiboot_addr;
|
||||||
|
|
||||||
|
/* Initialize all memory as used (1) initially */
|
||||||
|
for (uint32_t i = 0; i < BITMAP_SIZE; i++) {
|
||||||
|
memory_bitmap[i] = 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addr & 7) {
|
||||||
|
offset_print(" PMM: multiboot alignment error\n");
|
||||||
|
return; // Alignment issue
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t size = *(uint32_t *)addr;
|
||||||
|
|
||||||
|
uint32_t regions_found = 0;
|
||||||
|
for (tag = (struct multiboot_tag *)(addr + 8);
|
||||||
|
tag->type != MULTIBOOT_TAG_TYPE_END;
|
||||||
|
tag = (struct multiboot_tag *)((multiboot_uint8_t *)tag + ((tag->size + 7) & ~7))) {
|
||||||
|
|
||||||
|
if (tag->type == MULTIBOOT_TAG_TYPE_BASIC_MEMINFO) {
|
||||||
|
struct multiboot_tag_basic_meminfo *meminfo = (struct multiboot_tag_basic_meminfo *)tag;
|
||||||
|
memory_size = meminfo->mem_upper;
|
||||||
|
offset_print(" PMM: mem_upper = ");
|
||||||
|
print_hex(memory_size);
|
||||||
|
} else if (tag->type == MULTIBOOT_TAG_TYPE_MMAP) {
|
||||||
|
multiboot_memory_map_t *mmap;
|
||||||
|
struct multiboot_tag_mmap *tag_mmap = (struct multiboot_tag_mmap *)tag;
|
||||||
|
|
||||||
|
for (mmap = tag_mmap->entries;
|
||||||
|
(multiboot_uint8_t *)mmap < (multiboot_uint8_t *)tag + tag->size;
|
||||||
|
mmap = (multiboot_memory_map_t *)((unsigned long)mmap + tag_mmap->entry_size)) {
|
||||||
|
|
||||||
|
if (mmap->type == MULTIBOOT_MEMORY_AVAILABLE) {
|
||||||
|
uint64_t start = mmap->addr;
|
||||||
|
uint64_t len = mmap->len;
|
||||||
|
uint64_t end = start + len;
|
||||||
|
|
||||||
|
regions_found++;
|
||||||
|
|
||||||
|
if (start % PAGE_SIZE != 0) {
|
||||||
|
start = (start + PAGE_SIZE) & ~(PAGE_SIZE - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint64_t addr_iter = start; addr_iter < end; addr_iter += PAGE_SIZE) {
|
||||||
|
if (addr_iter + PAGE_SIZE <= end && addr_iter < MAX_PHYSICAL_MEMORY) {
|
||||||
|
clear_frame((phys_addr_t)addr_iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t kstart = (uint32_t)&_kernel_start;
|
||||||
|
uint32_t kend = (uint32_t)&_kernel_end;
|
||||||
|
|
||||||
|
kstart &= ~(PAGE_SIZE - 1);
|
||||||
|
kend = (kend + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
|
||||||
|
|
||||||
|
for (uint32_t i = kstart; i < kend; i += PAGE_SIZE) {
|
||||||
|
set_frame(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark multiboot structure
|
||||||
|
uint32_t mb_start = multiboot_addr;
|
||||||
|
uint32_t mb_end = multiboot_addr + size;
|
||||||
|
mb_start &= ~(PAGE_SIZE - 1);
|
||||||
|
mb_end = (mb_end + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
|
||||||
|
|
||||||
|
for (uint32_t i = mb_start; i < mb_end; i += PAGE_SIZE) {
|
||||||
|
set_frame(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark first page as used to avoid returning 0 (NULL)
|
||||||
|
set_frame(0);
|
||||||
|
|
||||||
|
offset_print(" PMM: found ");
|
||||||
|
print_hex(regions_found);
|
||||||
|
offset_print(" PMM: available memory regions\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
phys_addr_t pmm_alloc_page(pmm_zone_t zone) {
|
||||||
|
uint32_t start_frame = 0;
|
||||||
|
uint32_t end_frame = BITMAP_SIZE * FRAMES_PER_INDEX;
|
||||||
|
uint32_t frame = (uint32_t)-1;
|
||||||
|
|
||||||
|
// 16MB boundary is at 16*1024*1024 / 4096 = 4096 frames
|
||||||
|
uint32_t dma_limit = 4096;
|
||||||
|
|
||||||
|
if (zone == PMM_ZONE_DMA) {
|
||||||
|
end_frame = dma_limit;
|
||||||
|
frame = first_free_frame(start_frame, end_frame);
|
||||||
|
} else {
|
||||||
|
start_frame = dma_limit;
|
||||||
|
frame = first_free_frame(start_frame, end_frame);
|
||||||
|
|
||||||
|
// Fallback to DMA if NORMAL failed
|
||||||
|
if (frame == (uint32_t)-1) {
|
||||||
|
frame = first_free_frame(0, dma_limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame != (uint32_t)-1) {
|
||||||
|
phys_addr_t addr = frame * PAGE_SIZE;
|
||||||
|
set_frame(addr);
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0; // OOM
|
||||||
|
}
|
||||||
|
|
||||||
|
void pmm_free_page(phys_addr_t addr) {
|
||||||
|
clear_frame(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t pmm_get_memory_size(void) {
|
||||||
|
return memory_size;
|
||||||
|
}
|
||||||
42
src/pmm.h
Normal file
42
src/pmm.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#ifndef PMM_H
|
||||||
|
#define PMM_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#define PAGE_SIZE 4096
|
||||||
|
|
||||||
|
typedef uintptr_t phys_addr_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PMM_ZONE_DMA, // < 16 MB
|
||||||
|
PMM_ZONE_NORMAL, // Any other memory
|
||||||
|
PMM_ZONE_HIGHMEM // > 4 GB (if 64-bit or PAE, but we are 32-bit for now) - actually sticking to DMA and NORMAL is enough for 32-bit typically
|
||||||
|
} pmm_zone_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize the physical memory manager.
|
||||||
|
* @param multiboot_addr Address of the multiboot info structure
|
||||||
|
*/
|
||||||
|
void init_pmm(uint32_t multiboot_addr);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocate a physical page.
|
||||||
|
* @param zone The preferred zone to allocate from.
|
||||||
|
* @return Physical address of the allocated page, or 0 if out of memory.
|
||||||
|
*/
|
||||||
|
phys_addr_t pmm_alloc_page(pmm_zone_t zone);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free a physical page.
|
||||||
|
* @param addr Physical address of the page to free.
|
||||||
|
*/
|
||||||
|
void pmm_free_page(phys_addr_t addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the total detected upper memory in KiB.
|
||||||
|
* @return Upper memory size in KiB as reported by Multiboot.
|
||||||
|
*/
|
||||||
|
uint32_t pmm_get_memory_size(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
25
src/port_io.h
Normal file
25
src/port_io.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#ifndef PORT_IO_H
|
||||||
|
#define PORT_IO_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
static inline void outb(uint16_t port, uint8_t val)
|
||||||
|
{
|
||||||
|
asm volatile ( "outb %b0, %w1" : : "a"(val), "Nd"(port) );
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint8_t inb(uint16_t port)
|
||||||
|
{
|
||||||
|
uint8_t ret;
|
||||||
|
asm volatile ( "inb %w1, %b0" : "=a"(ret) : "Nd"(port) );
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void io_wait(void)
|
||||||
|
{
|
||||||
|
/* Port 0x80 is used for 'checkpoints' during POST. */
|
||||||
|
/* The Linux kernel seems to think it is free for use :-/ */
|
||||||
|
asm volatile ( "outb %%al, $0x80" : : "a"(0) );
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
172
src/string.c
Normal file
172
src/string.c
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
/**
|
||||||
|
* @file string.c
|
||||||
|
* @brief Minimal C string/memory functions for the freestanding kernel.
|
||||||
|
*
|
||||||
|
* These implementations replace the standard library versions since the
|
||||||
|
* kernel is compiled with -ffreestanding and does not link against libc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill a region of memory with a byte value.
|
||||||
|
*
|
||||||
|
* @param s Destination pointer.
|
||||||
|
* @param c Byte value to fill (only low 8 bits used).
|
||||||
|
* @param n Number of bytes to fill.
|
||||||
|
* @return The destination pointer.
|
||||||
|
*/
|
||||||
|
void *memset(void *s, int c, size_t n) {
|
||||||
|
unsigned char *p = (unsigned char *)s;
|
||||||
|
while (n--) {
|
||||||
|
*p++ = (unsigned char)c;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy a region of memory (non-overlapping).
|
||||||
|
*
|
||||||
|
* @param dest Destination pointer.
|
||||||
|
* @param src Source pointer.
|
||||||
|
* @param n Number of bytes to copy.
|
||||||
|
* @return The destination pointer.
|
||||||
|
*/
|
||||||
|
void *memcpy(void *dest, const void *src, size_t n) {
|
||||||
|
unsigned char *d = (unsigned char *)dest;
|
||||||
|
const unsigned char *s = (const unsigned char *)src;
|
||||||
|
while (n--) {
|
||||||
|
*d++ = *s++;
|
||||||
|
}
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy a region of memory, handling overlaps correctly.
|
||||||
|
*
|
||||||
|
* @param dest Destination pointer.
|
||||||
|
* @param src Source pointer.
|
||||||
|
* @param n Number of bytes to copy.
|
||||||
|
* @return The destination pointer.
|
||||||
|
*/
|
||||||
|
void *memmove(void *dest, const void *src, size_t n) {
|
||||||
|
unsigned char *d = (unsigned char *)dest;
|
||||||
|
const unsigned char *s = (const unsigned char *)src;
|
||||||
|
|
||||||
|
if (d < s) {
|
||||||
|
while (n--) {
|
||||||
|
*d++ = *s++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d += n;
|
||||||
|
s += n;
|
||||||
|
while (n--) {
|
||||||
|
*--d = *--s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two regions of memory.
|
||||||
|
*
|
||||||
|
* @param s1 First memory region.
|
||||||
|
* @param s2 Second memory region.
|
||||||
|
* @param n Number of bytes to compare.
|
||||||
|
* @return 0 if equal, negative if s1 < s2, positive if s1 > s2.
|
||||||
|
*/
|
||||||
|
int memcmp(const void *s1, const void *s2, size_t n) {
|
||||||
|
const unsigned char *a = (const unsigned char *)s1;
|
||||||
|
const unsigned char *b = (const unsigned char *)s2;
|
||||||
|
while (n--) {
|
||||||
|
if (*a != *b) {
|
||||||
|
return *a - *b;
|
||||||
|
}
|
||||||
|
a++;
|
||||||
|
b++;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the length of a null-terminated string.
|
||||||
|
*
|
||||||
|
* @param s The string.
|
||||||
|
* @return Number of characters before the null terminator.
|
||||||
|
*/
|
||||||
|
size_t strlen(const char *s) {
|
||||||
|
size_t len = 0;
|
||||||
|
while (*s++) {
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two null-terminated strings.
|
||||||
|
*
|
||||||
|
* @param s1 First string.
|
||||||
|
* @param s2 Second string.
|
||||||
|
* @return 0 if equal, negative if s1 < s2, positive if s1 > s2.
|
||||||
|
*/
|
||||||
|
int strcmp(const char *s1, const char *s2) {
|
||||||
|
while (*s1 && *s1 == *s2) {
|
||||||
|
s1++;
|
||||||
|
s2++;
|
||||||
|
}
|
||||||
|
return *(unsigned char *)s1 - *(unsigned char *)s2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare at most n characters of two strings.
|
||||||
|
*
|
||||||
|
* @param s1 First string.
|
||||||
|
* @param s2 Second string.
|
||||||
|
* @param n Maximum number of characters to compare.
|
||||||
|
* @return 0 if equal, negative if s1 < s2, positive if s1 > 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;
|
||||||
|
}
|
||||||
|
return *(unsigned char *)s1 - *(unsigned char *)s2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy a null-terminated string.
|
||||||
|
*
|
||||||
|
* @param dest Destination buffer (must be large enough).
|
||||||
|
* @param src Source string.
|
||||||
|
* @return The destination pointer.
|
||||||
|
*/
|
||||||
|
char *strcpy(char *dest, const char *src) {
|
||||||
|
char *d = dest;
|
||||||
|
while ((*d++ = *src++))
|
||||||
|
;
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy at most n characters of a string, null-padding if shorter.
|
||||||
|
*
|
||||||
|
* @param dest Destination buffer.
|
||||||
|
* @param src Source string.
|
||||||
|
* @param n Maximum characters to copy.
|
||||||
|
* @return The destination pointer.
|
||||||
|
*/
|
||||||
|
char *strncpy(char *dest, const char *src, size_t n) {
|
||||||
|
char *d = dest;
|
||||||
|
while (n && (*d++ = *src++)) {
|
||||||
|
n--;
|
||||||
|
}
|
||||||
|
while (n--) {
|
||||||
|
*d++ = '\0';
|
||||||
|
}
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
230
src/vga.c
Normal file
230
src/vga.c
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
/**
|
||||||
|
* @file vga.c
|
||||||
|
* @brief VGA text-mode driver implementation.
|
||||||
|
*
|
||||||
|
* Drives the standard VGA text-mode framebuffer at 0xB8000. The buffer
|
||||||
|
* is an array of 80×25 16-bit values, where the low byte is the ASCII
|
||||||
|
* character and the high byte encodes foreground and background color.
|
||||||
|
*
|
||||||
|
* This driver registers itself via the REGISTER_DRIVER macro and is
|
||||||
|
* automatically discovered during boot.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "vga.h"
|
||||||
|
#include "driver.h"
|
||||||
|
#include "port_io.h"
|
||||||
|
#include "pmm.h"
|
||||||
|
|
||||||
|
/** Base address of the VGA text-mode framebuffer. */
|
||||||
|
#define VGA_BUFFER 0xB8000
|
||||||
|
|
||||||
|
/** Pointer to the VGA framebuffer, treated as an array of uint16_t. */
|
||||||
|
static uint16_t *vga_buffer = (uint16_t *)VGA_BUFFER;
|
||||||
|
|
||||||
|
/** Current cursor row (0-based). */
|
||||||
|
static uint8_t cursor_row = 0;
|
||||||
|
|
||||||
|
/** Current cursor column (0-based). */
|
||||||
|
static uint8_t cursor_col = 0;
|
||||||
|
|
||||||
|
/** Current text attribute byte (foreground | background << 4). */
|
||||||
|
static uint8_t text_attr = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a VGA entry (character + attribute) for the framebuffer.
|
||||||
|
*
|
||||||
|
* @param c ASCII character.
|
||||||
|
* @param attr Color attribute byte.
|
||||||
|
* @return 16-bit VGA entry.
|
||||||
|
*/
|
||||||
|
static inline uint16_t vga_entry(char c, uint8_t attr) {
|
||||||
|
return (uint16_t)c | ((uint16_t)attr << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a color attribute byte from foreground and background colors.
|
||||||
|
*
|
||||||
|
* @param fg Foreground color (0–15).
|
||||||
|
* @param bg Background color (0–15).
|
||||||
|
* @return Attribute byte.
|
||||||
|
*/
|
||||||
|
static inline uint8_t vga_color(vga_color_t fg, vga_color_t bg) {
|
||||||
|
return (uint8_t)fg | ((uint8_t)bg << 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the hardware cursor position via VGA I/O ports.
|
||||||
|
*/
|
||||||
|
static void update_cursor(void) {
|
||||||
|
uint16_t pos = cursor_row * VGA_WIDTH + cursor_col;
|
||||||
|
|
||||||
|
outb(0x3D4, 0x0F);
|
||||||
|
outb(0x3D5, (uint8_t)(pos & 0xFF));
|
||||||
|
outb(0x3D4, 0x0E);
|
||||||
|
outb(0x3D5, (uint8_t)((pos >> 8) & 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll the screen up by one line.
|
||||||
|
*
|
||||||
|
* The top line is discarded, all other lines move up, and the bottom
|
||||||
|
* line is filled with spaces.
|
||||||
|
*/
|
||||||
|
static void scroll(void) {
|
||||||
|
/* Move all lines up by one */
|
||||||
|
for (int i = 0; i < (VGA_HEIGHT - 1) * VGA_WIDTH; i++) {
|
||||||
|
vga_buffer[i] = vga_buffer[i + VGA_WIDTH];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clear the last line */
|
||||||
|
uint16_t blank = vga_entry(' ', text_attr);
|
||||||
|
for (int i = (VGA_HEIGHT - 1) * VGA_WIDTH; i < VGA_HEIGHT * VGA_WIDTH; i++) {
|
||||||
|
vga_buffer[i] = blank;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor_row = VGA_HEIGHT - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vga_clear(void) {
|
||||||
|
uint16_t blank = vga_entry(' ', text_attr);
|
||||||
|
for (int i = 0; i < VGA_WIDTH * VGA_HEIGHT; i++) {
|
||||||
|
vga_buffer[i] = blank;
|
||||||
|
}
|
||||||
|
cursor_row = 0;
|
||||||
|
cursor_col = 0;
|
||||||
|
update_cursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void vga_set_color(vga_color_t fg, vga_color_t bg) {
|
||||||
|
text_attr = vga_color(fg, bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vga_putchar(char c) {
|
||||||
|
if (c == '\n') {
|
||||||
|
cursor_col = 0;
|
||||||
|
cursor_row++;
|
||||||
|
} else if (c == '\r') {
|
||||||
|
cursor_col = 0;
|
||||||
|
} else if (c == '\t') {
|
||||||
|
cursor_col = (cursor_col + 8) & ~7;
|
||||||
|
if (cursor_col >= VGA_WIDTH) {
|
||||||
|
cursor_col = 0;
|
||||||
|
cursor_row++;
|
||||||
|
}
|
||||||
|
} else if (c == '\b') {
|
||||||
|
if (cursor_col > 0) {
|
||||||
|
cursor_col--;
|
||||||
|
vga_buffer[cursor_row * VGA_WIDTH + cursor_col] = vga_entry(' ', text_attr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vga_buffer[cursor_row * VGA_WIDTH + cursor_col] = vga_entry(c, text_attr);
|
||||||
|
cursor_col++;
|
||||||
|
if (cursor_col >= VGA_WIDTH) {
|
||||||
|
cursor_col = 0;
|
||||||
|
cursor_row++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor_row >= VGA_HEIGHT) {
|
||||||
|
scroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
update_cursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void vga_puts(const char *str) {
|
||||||
|
while (*str) {
|
||||||
|
vga_putchar(*str);
|
||||||
|
str++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void vga_put_hex(uint32_t val) {
|
||||||
|
const char *hex = "0123456789ABCDEF";
|
||||||
|
vga_putchar('0');
|
||||||
|
vga_putchar('x');
|
||||||
|
for (int i = 28; i >= 0; i -= 4) {
|
||||||
|
vga_putchar(hex[(val >> i) & 0xF]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void vga_put_dec(uint32_t val) {
|
||||||
|
if (val == 0) {
|
||||||
|
vga_putchar('0');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[12];
|
||||||
|
int pos = 0;
|
||||||
|
while (val > 0) {
|
||||||
|
buf[pos++] = '0' + (val % 10);
|
||||||
|
val /= 10;
|
||||||
|
}
|
||||||
|
/* Print in reverse */
|
||||||
|
while (pos > 0) {
|
||||||
|
vga_putchar(buf[--pos]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void vga_show_mem_stats(void) {
|
||||||
|
uint32_t mem_kb = pmm_get_memory_size() + 1024; /* total including lower */
|
||||||
|
|
||||||
|
vga_set_color(VGA_LIGHT_CYAN, VGA_BLACK);
|
||||||
|
vga_puts("=== ClaudeOS Memory Statistics ===\n");
|
||||||
|
|
||||||
|
vga_set_color(VGA_WHITE, VGA_BLACK);
|
||||||
|
vga_puts(" Total RAM: ");
|
||||||
|
vga_put_dec(mem_kb);
|
||||||
|
vga_puts(" KiB (");
|
||||||
|
vga_put_dec(mem_kb / 1024);
|
||||||
|
vga_puts(" MiB)\n");
|
||||||
|
|
||||||
|
vga_puts(" Kernel start: ");
|
||||||
|
extern uint32_t _kernel_start;
|
||||||
|
vga_put_hex((uint32_t)&_kernel_start);
|
||||||
|
vga_puts("\n");
|
||||||
|
|
||||||
|
vga_puts(" Kernel end: ");
|
||||||
|
extern uint32_t _kernel_end;
|
||||||
|
vga_put_hex((uint32_t)&_kernel_end);
|
||||||
|
vga_puts("\n");
|
||||||
|
|
||||||
|
uint32_t kernel_size = (uint32_t)&_kernel_end - (uint32_t)&_kernel_start;
|
||||||
|
vga_puts(" Kernel size: ");
|
||||||
|
vga_put_dec(kernel_size / 1024);
|
||||||
|
vga_puts(" KiB\n");
|
||||||
|
|
||||||
|
vga_set_color(VGA_LIGHT_CYAN, VGA_BLACK);
|
||||||
|
vga_puts("==================================\n");
|
||||||
|
vga_set_color(VGA_LIGHT_GREY, VGA_BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Driver registration --- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VGA probe: always succeeds since VGA text mode is always available
|
||||||
|
* on the target platform (i386).
|
||||||
|
*/
|
||||||
|
static driver_probe_result_t vga_probe(void) {
|
||||||
|
return DRIVER_PROBE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int vga_init(void) {
|
||||||
|
text_attr = vga_color(VGA_LIGHT_GREY, VGA_BLACK);
|
||||||
|
vga_clear();
|
||||||
|
|
||||||
|
vga_set_color(VGA_LIGHT_GREEN, VGA_BLACK);
|
||||||
|
vga_puts("ClaudeOS v0.1 booting...\n\n");
|
||||||
|
vga_set_color(VGA_LIGHT_GREY, VGA_BLACK);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** VGA driver descriptor. */
|
||||||
|
static const driver_t vga_driver = {
|
||||||
|
.name = "vga",
|
||||||
|
.probe = vga_probe,
|
||||||
|
.init = vga_init,
|
||||||
|
};
|
||||||
|
|
||||||
|
REGISTER_DRIVER(vga_driver);
|
||||||
97
src/vga.h
Normal file
97
src/vga.h
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* @file vga.h
|
||||||
|
* @brief VGA text-mode driver interface.
|
||||||
|
*
|
||||||
|
* Provides functions to write text to the VGA text-mode framebuffer
|
||||||
|
* (typically at 0xB8000). Supports an 80x25 character display with
|
||||||
|
* 16 foreground and 16 background colors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef VGA_H
|
||||||
|
#define VGA_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/** VGA color constants. */
|
||||||
|
typedef enum {
|
||||||
|
VGA_BLACK = 0,
|
||||||
|
VGA_BLUE = 1,
|
||||||
|
VGA_GREEN = 2,
|
||||||
|
VGA_CYAN = 3,
|
||||||
|
VGA_RED = 4,
|
||||||
|
VGA_MAGENTA = 5,
|
||||||
|
VGA_BROWN = 6,
|
||||||
|
VGA_LIGHT_GREY = 7,
|
||||||
|
VGA_DARK_GREY = 8,
|
||||||
|
VGA_LIGHT_BLUE = 9,
|
||||||
|
VGA_LIGHT_GREEN = 10,
|
||||||
|
VGA_LIGHT_CYAN = 11,
|
||||||
|
VGA_LIGHT_RED = 12,
|
||||||
|
VGA_LIGHT_MAGENTA = 13,
|
||||||
|
VGA_YELLOW = 14,
|
||||||
|
VGA_WHITE = 15,
|
||||||
|
} vga_color_t;
|
||||||
|
|
||||||
|
/** VGA screen dimensions. */
|
||||||
|
#define VGA_WIDTH 80
|
||||||
|
#define VGA_HEIGHT 25
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the VGA driver.
|
||||||
|
*
|
||||||
|
* Clears the screen and sets the default colors.
|
||||||
|
*
|
||||||
|
* @return 0 on success.
|
||||||
|
*/
|
||||||
|
int vga_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the VGA screen.
|
||||||
|
*/
|
||||||
|
void vga_clear(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the foreground and background color for subsequent writes.
|
||||||
|
*
|
||||||
|
* @param fg Foreground color.
|
||||||
|
* @param bg Background color.
|
||||||
|
*/
|
||||||
|
void vga_set_color(vga_color_t fg, vga_color_t bg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a single character at the current cursor position.
|
||||||
|
*
|
||||||
|
* @param c Character to write.
|
||||||
|
*/
|
||||||
|
void vga_putchar(char c);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a null-terminated string at the current cursor position.
|
||||||
|
*
|
||||||
|
* @param str String to write.
|
||||||
|
*/
|
||||||
|
void vga_puts(const char *str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a 32-bit value in hexadecimal to the VGA display.
|
||||||
|
*
|
||||||
|
* @param val Value to display.
|
||||||
|
*/
|
||||||
|
void vga_put_hex(uint32_t val);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a 32-bit value in decimal to the VGA display.
|
||||||
|
*
|
||||||
|
* @param val Value to display.
|
||||||
|
*/
|
||||||
|
void vga_put_dec(uint32_t val);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display boot memory statistics on VGA.
|
||||||
|
*
|
||||||
|
* Shows detected memory, kernel size, and free pages.
|
||||||
|
*/
|
||||||
|
void vga_show_mem_stats(void);
|
||||||
|
|
||||||
|
#endif /* VGA_H */
|
||||||
Reference in New Issue
Block a user