merge paging into main

This commit is contained in:
uosfz 2025-07-02 19:50:27 +02:00
commit 526ebed2fb
Signed by: uosfz
SSH key fingerprint: SHA256:FlktuluyhTQg3jHZNLKwxOOC5hbfrUXM0tz3IA3lGJo
24 changed files with 1656 additions and 582 deletions

View file

@ -11,10 +11,12 @@ KERNEL_SOURCES_x86_64 := \
src/x86_64/apic.c \
src/x86_64/loadcs.S \
src/x86_64/uart.c \
src/x86_64/mem.c \
src/x86_64/string.S \
src/x86_64/cpu.c \
src/x86_64/paging.c \
src/x86_64/asm.c \
src/x86_64/address.c \
src/x86_64/ps2_driver.c \
# end of x86_64 specific kernel sources list
# Architecture-agnostic kernel sources.

View file

@ -1,3 +1,3 @@
#!/bin/sh
qemu-system-x86_64 -drive if=ide,format=raw,file=build/disk.img -drive if=virtio,media=disk,format=raw,file=drive.img "$@" -S -gdb tcp::3117
qemu-system-x86_64 -drive if=ide,format=raw,file=build/disk.img -drive if=virtio,media=disk,format=raw,file=drive.img -d int "$@" -S -gdb tcp::3117

View file

@ -1,23 +1,24 @@
#ifndef KARLOS_ADDRESS_H
#define KARLOS_ADDRESS_H
#include <stdbool.h>
#include <stdint.h>
// do not use these fields directly! use *_to_* functions.
struct va {
uint64_t value; // 48-bit
uint64_t value;
};
struct vpn {
uint64_t pagenum; // 36-bit
uint64_t pagenum;
};
struct pa {
uint64_t value; // 48 - 12 = 36-bit
uint64_t value;
};
struct ppn {
uint64_t pagenum; // 36 - 12 = 24-bit
uint64_t pagenum;
};
struct va va_from_value(uint64_t value);
@ -27,15 +28,23 @@ struct va va_from_vpn_with_offset(struct vpn vpn, uint64_t offset);
uint64_t va_to_value(struct va va);
uint64_t va_to_canonical(struct va va);
uint64_t va_offset(struct va va);
uint64_t va_offset_huge_2mb(struct va va);
uint64_t va_offset_huge_1gb(struct va va);
struct vpn vpn_from_pagenum(uint64_t pagenum);
struct vpn vpn_from_aligned_va(struct va va);
struct vpn vpn_from_unaligned_va(struct va va);
uint64_t vpn_to_pagenum(struct vpn vpn);
unsigned int vpn_level_idx(struct vpn vpn, unsigned int level);
bool vpn_is_huge_2mb(struct vpn vpn);
bool vpn_is_huge_1gb(struct vpn vpn);
struct pa pa_from_value(uint64_t value);
struct pa pa_from_ppn(struct ppn ppn);
struct pa pa_from_ppn_with_offset(struct ppn ppn, uint64_t offset);
struct pa pa_from_ppn_huge_2mb_with_offset(struct ppn ppn, uint64_t offset);
struct pa pa_from_ppn_huge_1gb_with_offset(struct ppn ppn, uint64_t offset);
struct pa pa_from_pt_with_idx(struct ppn ppn, unsigned int idx);
uint64_t pa_to_value(struct pa pa);
void *pa_to_pointer(struct pa pa);
uint64_t pa_offset(struct pa pa);
@ -44,5 +53,9 @@ struct ppn ppn_from_pagenum(uint64_t pagenum);
struct ppn ppn_from_aligned_pa(struct pa pa);
struct ppn ppn_from_unaligned_pa(struct pa pa);
uint64_t ppn_to_pagenum(struct ppn ppn);
bool ppn_is_huge_2mb(struct ppn ppn);
bool ppn_is_huge_1gb(struct ppn ppn);
void set_identity_mapping(struct vpn first_page);
#endif

10
include/cpu.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef KARLOS_CPU_H
#define KARLOS_CPU_H
void cpu_init(void);
unsigned int cpu_get_core_id(void);
void cpu_set_timer(void);
void interrupt_handler_register(unsigned int vector, void (*handler)(void));
#endif

38
include/paging.h Normal file
View file

@ -0,0 +1,38 @@
#ifndef KARLOS_PAGING_H
#define KARLOS_PAGING_H
// restricted, architecture-agnostic interface.
// For full interface see include/<arch>/paging.h
// TODO remove mentions of cr3 and replace with top_pt or root or something
// I guess we only have to do this here
#include "address.h"
// --- init ---
void init_paging();
// --- traversal and mapping ---
bool pt_translate(struct va va, struct ppn cr3, struct pa *pa_out);
bool pt_translate_current(struct va va, struct pa *pa_out);
bool pt_map_single(struct vpn virt, struct ppn phys,
bool writable, bool supervisor, bool global,
struct ppn cr3);
bool pt_map_single_current(struct vpn virt, struct ppn phys,
bool writable, bool supervisor, bool global);
bool pt_map_range(struct vpn virt, struct ppn phys, uint64_t num_pages,
bool writable, bool supervisor, bool global,
struct ppn cr3);
bool pt_map_range_current(struct vpn virt, struct ppn phys, uint64_t num_pages,
bool writable, bool supervisor, bool global);
void pt_free(struct ppn cr3);
// --- page table creation ---
__attribute__((noreturn))
void pt_create_minimal(void);
#endif

View file

@ -5,8 +5,7 @@
#include <stddef.h>
#include <stdbool.h>
#include <x86_64/address.h>
#include <address.h>
enum frame_size {
RAM_PAGE_NORMAL = 0,

6
include/serial.h Normal file
View file

@ -0,0 +1,6 @@
#ifndef KARLOS_SERIAL_H
#define KARLOS_SERIAL_H
void serial_write_char(char c);
#endif

View file

@ -1,9 +1,11 @@
#ifndef KARLOS_APIC_H
#define KARLOS_APIC_H
#ifndef KARLOS_X86_64_APIC_H
#define KARLOS_X86_64_APIC_H
void lapic_init(void);
unsigned lapic_get_id(void);
void lapic_eoi(void);
void lapic_set_timer(void);
void pic_disable(void);
#endif

View file

@ -1,5 +1,5 @@
#ifndef KARLOS_ASM_H
#define KARLOS_ASM_H
#ifndef KARLOS_X86_64_ASM_H
#define KARLOS_X86_64_ASM_H
#include <stdint.h>
@ -20,8 +20,11 @@ void out32(int port, uint32_t value);
uint64_t get_cr0(void);
uint64_t get_cr3(void);
void set_cr3(uint64_t value);
uint64_t get_cr4(void);
#define X86_ASM_INT(value) __asm__("int $" #value :: )
static inline uint64_t
readmsr(uint32_t msr)
{

View file

@ -1,64 +0,0 @@
#ifndef KARLOS_MEM_H
#define KARLOS_MEM_H
#include <stdbool.h>
#include <stdint.h>
#include "x86_64/address.h"
void init_gdt();
void init_idt();
struct pt_entry {
bool present;
bool writable;
bool supervisor;
bool writethrough;
bool cache_disable;
bool accessed;
bool dirty;
bool page_attr_table_low; // in high this is the page_size field to signal hugepages.
bool global;
bool page_attr_table_high;
struct ppn ppn;
// TODO MPK, NX
};
uint64_t pt_entry_pack(const struct pt_entry *ent_in);
void pt_entry_unpack(uint64_t ent_in, struct pt_entry *ent_out);
void pt_entry_print(const struct pt_entry *ent);
bool pt_translate(struct va va, struct ppn cr3, struct pa *pa_out);
bool pt_translate_current(struct va va, struct pa *pa_out);
bool pt_map_single(struct vpn virt, struct ppn phys,
bool writable, bool supervisor, bool global,
struct ppn cr3);
bool pt_map_single_current(struct vpn virt, struct ppn phys,
bool writable, bool supervisor, bool global);
bool pt_map_range(struct vpn virt, struct ppn phys, uint64_t num_pages,
bool writable, bool supervisor, bool global,
struct ppn cr3);
bool pt_map_range_current(struct vpn virt, struct ppn phys, uint64_t num_pages,
bool writable, bool supervisor, bool global);
struct mem_range {
struct vpn vpn_start;
struct pt_entry entry_start;
uint64_t npages;
};
void mem_range_print(const struct mem_range *mr);
struct mem_range_buf {
struct mem_range *ptr;
uint64_t next_entry;
uint64_t num_entries;
};
#define FOR_MEM_RANGE_IN(var, buf) for(struct mem_range *var = (buf)->ptr; \
var < (buf)->ptr + (buf)->next_entry; \
var++)
void pt_get_ranges(struct mem_range_buf *buf_out);
#endif

132
include/x86_64/paging.h Normal file
View file

@ -0,0 +1,132 @@
#ifndef KARLOS_X86_64_PAGING_H
#define KARLOS_X86_64_PAGING_H
// Naming conventions used in paging code:
//
// A _pt_ (page table) can refer to a single level or to an entire hierarchy.
// We mostly use it to refer to a single level.
//
// Pts have 4 _levels_.
// The highest level is level 4. There is exactly one level 4 pt in a hierarchy.
// The lowest level is level 1.
//
// Pts contain _entries_. Each entry is also associated with a level.
// It's the level this entry is located in, NOT the level it points to.
// This means entry levels also range from 4 to 1.
//
// A _leaf entry_ is an entry that points to data pages.
// This includes all entries on level 1, as well as hugepage entries on level 2 and 3.
#include <stdbool.h>
#include <stdint.h>
#include "address.h"
// --- init ---
void init_paging();
// --- entries ---
struct pt_entry {
// fields not in actual entry
unsigned int level; // possible values are 1..=4
// fields that are level-independent
bool present;
bool writable;
bool supervisor;
bool writethrough;
bool cache_disable;
bool accessed;
bool dirty;
bool global;
struct ppn ppn;
// fields that are level-dependent
bool hugepage;
bool pat_bit;
// TODO MPK, NX
};
#define PT_ENTRY_IS_LEAF(ent) ((ent).present && ((ent).level == 1 || (ent).hugepage))
#define PT_ENTRY_IS_NONLEAF(ent) ((ent).present && (ent).level > 1 && !(ent).hugepage)
uint64_t pt_entry_pack(const struct pt_entry *ent_in);
void pt_entry_unpack(uint64_t ent_in, unsigned int level, struct pt_entry *ent_out);
void pt_entry_init_nonleaf(struct pt_entry *ent, unsigned int level, struct ppn ppn);
void pt_entry_init_leaf(struct pt_entry *ent, unsigned int level,
bool writable, bool supervisor, bool global,
struct ppn ppn);
void pt_entry_print(const struct pt_entry *ent);
// --- traversal and mapping ---
struct access_level {
struct ppn ppn;
unsigned int idx;
};
struct traversal {
unsigned int depth;
struct access_level levels[4];
};
bool pt_translate(struct va va, struct ppn cr3, struct pa *pa_out);
bool pt_translate_current(struct va va, struct pa *pa_out);
bool pt_map_single(struct vpn virt, struct ppn phys,
bool writable, bool supervisor, bool global,
struct ppn cr3);
bool pt_map_single_current(struct vpn virt, struct ppn phys,
bool writable, bool supervisor, bool global);
bool pt_map_range(struct vpn virt, struct ppn phys, uint64_t num_pages,
bool writable, bool supervisor, bool global,
struct ppn cr3);
bool pt_map_range_current(struct vpn virt, struct ppn phys, uint64_t num_pages,
bool writable, bool supervisor, bool global);
void pt_free(struct ppn root);
// --- page table creation ---
__attribute__((noreturn))
void pt_create_minimal(void);
// --- iterators ---
struct pt_leaf {
struct vpn vpn_start;
struct pt_entry ent;
};
struct pt_leaf_iter {
bool done;
struct traversal trav;
};
void pt_leaf_iter_init(struct pt_leaf_iter *it, struct ppn cr3);
void pt_leaf_iter_init_current(struct pt_leaf_iter *it);
void pt_leaf_iter_init_custom_start(struct pt_leaf_iter *it, struct ppn cr3, struct vpn virt);
bool pt_leaf_iter_next(struct pt_leaf_iter *it, struct pt_leaf *leaf_out);
struct mem_range {
struct vpn vpn_start;
struct pt_entry entry_start;
uint64_t npages;
};
void mem_range_print(const struct mem_range *mr);
struct mem_range_iter {
struct pt_leaf_iter leaf_it;
bool have_held_leaf;
struct pt_leaf held_leaf;
};
void mem_range_iter_init(struct mem_range_iter *it, struct ppn cr3);
void mem_range_iter_init_current(struct mem_range_iter *it);
void mem_range_iter_init_custom_start(struct mem_range_iter *it, struct ppn cr3, struct vpn virt);
bool mem_range_iter_next(struct mem_range_iter *it, struct mem_range *range_out);
#endif

View file

@ -0,0 +1,9 @@
#include <stdint.h>
#include <stddef.h>
#include <std.h>
uint8_t ps2_read_data();
void ps2_write_data(uint8_t data);
uint8_t ps2_cmd_response(uint8_t cmd);
void ps2_cmd_with_data(uint8_t cmd, uint8_t data);
void ps2_init();

View file

@ -1,5 +1,5 @@
#ifndef KARLOS_UART_H
#define KARLOS_UART_H
#ifndef KARLOS_X86_64_UART_H
#define KARLOS_X86_64_UART_H
void uart_write_char(char c);

View file

@ -1,3 +1,3 @@
#!/bin/sh
qemu-system-x86_64 -drive if=ide,format=raw,file=build/disk.img -drive if=virtio,media=disk,format=raw,file=drive.img "$@"
qemu-system-x86_64 -drive if=ide,format=raw,file=build/disk.img -drive if=virtio,media=disk,format=raw,file=drive.img -d int "$@"

View file

@ -30,58 +30,24 @@
#include <stdint.h>
#include <cpuid.h>
#include "x86_64/apic.h"
#include "x86_64/asm.h"
#include "x86_64/address.h"
#include "x86_64/mem.h"
#include "bootboot.h"
#include "ram.h"
#include "pci.h"
#include "std.h"
#include "tar.h"
#include "framebuffer.h"
#include "console.h"
#include "cpu.h"
#include "paging.h"
#include "x86_64/ps2_driver.h"
/* imported virtual addresses, see linker script */
extern BOOTBOOT bootboot; // see bootboot.h
extern unsigned char environment[4096]; // configuration, UTF-8 text key=value pairs
extern uint8_t fb; // linear framebuffer mapped
int temp_global_var = 5; // TODO remove
void print_virtio_blk_bars() {
uint16_t bdf;
if (pci_search(0x1AF4, 0x1001, &bdf) && pci_search(0x1AF4, 0x1042, &bdf)) {
PANIC("couldn't find virtio_blk device!");
void basic_interrupt_handler(void) {
printf("Hello from interrupt!\n");
}
printf("found virtio_blk device at bdf %X16.\n", bdf);
// read BARs
for (int i = 0; i < 6; i++) {
struct pci_bar_desc desc = pci_bar_desc_read(bdf, i);
pci_bar_desc_print(&desc);
}
}
void check_initrd() {
putln();
uint64_t printed_in_line = 0;
for (uint64_t off = 0; off < bootboot.initrd_size; off++) {
uint8_t byte = ((uint8_t *)bootboot.initrd_ptr)[off];
printf("%X8 ", byte);
if (++printed_in_line % 32 == 0) {
printed_in_line = 0;
putln();
}
}
putln();
}
void console_init(void);
struct mem_range range_ptr[10];
/******************************************
* Entry point, called by BOOTBOOT Loader *
@ -100,10 +66,7 @@ void _start() {
console_init();
// memory stuff
init_gdt();
init_idt();
lapic_init();
cpu_init();
ram_init();
// HACK by luck the frame at phys. addr 0 might be returned by the allocator.
@ -118,97 +81,30 @@ void _start() {
fb_init();
console_clear();
printf("LAPIC ID: %d\n", lapic_get_id());
printf("LAPIC ID: %d\n", cpu_get_core_id());
#if 0
struct tar_header hd;
int res = tar_get_file("hello.txt", &hd);
ASSERT(res == 1);
printf("%s %X32\n", hd.name, hd.size);
for (uint64_t i = 0; i < hd.size; i++) {
putc(((char *)hd.data)[i]);
}
putln();
#endif
interrupt_handler_register(0xf1, basic_interrupt_handler);
__asm__("int $0xf1" :: );
__asm__("int $0xf7" :: );
#if 0
for (int l = 0; l < 100; l++) {
for (int i = 0; i < 300; i++) {
printf("line %d ", l);
}
printf("\n");
}
#endif
printf("Core ID: %d\n", cpu_get_core_id());
__asm__("sti");
#if 1
lapic_set_timer();
cpu_set_timer();
#endif
#if 1
struct mem_range_buf buf_out = { .ptr = range_ptr, .next_entry = 0, .num_entries = 10 };
pt_get_ranges(&buf_out);
FOR_MEM_RANGE_IN(curr_range, &buf_out) {
mem_range_print(curr_range);
ps2_init();
while (1) {
// do nothing. PS2 controller should send interrupts.
// uint8_t data = ps2_read_data();
// putu8x(data);
// putln();
}
#endif
#if 0
// get mmapents
for (MMapEnt *mmap_ent = &bootboot.mmap;
mmap_ent < (MMapEnt*)((char*)&bootboot + bootboot.size);
mmap_ent++)
{
printf("MMapEnt {\n");
printf(" ptr=");
putu64x(MMapEnt_Ptr(mmap_ent));
printf("\n");
printf(" size=");
putu64x(MMapEnt_Size(mmap_ent));
printf("\n");
char *type = NULL;
switch (MMapEnt_Type(mmap_ent)) {
case MMAP_USED:
type = "used";
break;
case MMAP_FREE:
type = "free";
break;
case MMAP_ACPI:
type = "acpi";
break;
case MMAP_MMIO:
type = "mmio";
break;
default:
UNREACHABLE();
}
printf(" type=%s\n", type);
printf("}\n");
}
#endif
#if 1
int temp_local_var = 5;
temp_global_var = 6;
temp_local_var = 6;
struct pa pa;
ASSERT(pt_translate_current(va_from_canonical((uint64_t)(intptr_t)&temp_global_var), &pa));
uint64_t data_phys = pa_to_value(pa);
ASSERT(pt_translate_current(va_from_canonical((uint64_t)(intptr_t)&temp_local_var), &pa));
uint64_t stack_phys = pa_to_value(pa);
ASSERT(pt_translate_current(va_from_canonical((uint64_t)(intptr_t)&_start), &pa));
uint64_t code_phys = pa_to_value(pa);
printf("data: %p -> %p\n", &temp_global_var, data_phys);
printf("stack: %p -> %p\n", &temp_local_var, stack_phys);
printf("code: %p -> %p\n", &check_initrd, code_phys);
#endif
#if 1
uint64_t cr0 = get_cr0();
printf("wp=%d\n", (cr0 >> 16) & 1);
#endif
pt_create_minimal();
// hang for now
PANIC("end of kernel");

View file

@ -2,7 +2,7 @@
#include <stddef.h>
#include "std.h"
#include "x86_64/uart.h"
#include "serial.h"
#define BUFFER_SIZE 1024
@ -71,9 +71,9 @@ static unsigned int current_buffer_position = 0;
static void linebuf_flush(void) {
for (unsigned int i = 0; i < current_buffer_position; i++) {
uart_write_char(linebuf[i]);
extern void visual_putc(char c);
visual_putc(linebuf[i]);
serial_write_char(linebuf[i]);
// extern void visual_putc(char c);
// visual_putc(linebuf[i]);
}
current_buffer_position = 0;
}

View file

@ -1,10 +1,7 @@
#include "x86_64/address.h"
#include "address.h"
#include "std.h"
#define MEM_SIZE (16ull * 1024 * 1024 * 1024)
#define IDMAP_END 0x400000000ull
#define IDMAP_START (IDMAP_END - MEM_SIZE)
#define PHYS_TO_IDMAPPED(addr) ((addr) + IDMAP_START)
uint64_t identity_mapping_start = 0;
struct va va_from_value(uint64_t value) {
ASSERT(value < (1ull << 48));
@ -45,6 +42,14 @@ uint64_t va_offset(struct va va) {
return va.value & 0xfff;
}
uint64_t va_offset_huge_2mb(struct va va) {
return va.value & 0x1fffff;
}
uint64_t va_offset_huge_1gb(struct va va) {
return va.value & 0x3fffffff;
}
struct vpn vpn_from_pagenum(uint64_t pagenum) {
ASSERT(pagenum < (1ull << 36));
return (struct vpn){ .pagenum = pagenum };
@ -63,6 +68,20 @@ uint64_t vpn_to_pagenum(struct vpn vpn) {
return vpn.pagenum;
}
unsigned int vpn_level_idx(struct vpn vpn, unsigned int level) {
ASSERT(level >= 1 && level <= 4);
return (vpn_to_pagenum(vpn) >> ((level - 1)*9)) & 0x1ffull;
}
bool vpn_is_huge_2mb(struct vpn vpn) {
return (vpn_to_pagenum(vpn) & ((1ull << 9) - 1)) == 0;
}
bool vpn_is_huge_1gb(struct vpn vpn) {
return (vpn_to_pagenum(vpn) & ((1ull << 18) - 1)) == 0;
}
struct pa pa_from_value(uint64_t value) {
ASSERT(value < (1ull << 36));
return (struct pa){ .value = value };
@ -77,12 +96,29 @@ struct pa pa_from_ppn_with_offset(struct ppn ppn, uint64_t offset) {
return pa_from_value((ppn.pagenum << 12) + offset);
}
struct pa pa_from_ppn_huge_2mb_with_offset(struct ppn ppn, uint64_t offset) {
ASSERT(ppn_is_huge_2mb(ppn));
ASSERT(offset < (1ull << 21));
return pa_from_value((ppn.pagenum << 12) + offset);
}
struct pa pa_from_ppn_huge_1gb_with_offset(struct ppn ppn, uint64_t offset) {
ASSERT(ppn_is_huge_1gb(ppn));
ASSERT(offset < (1ull << 30));
return pa_from_value((ppn.pagenum << 12) + offset);
}
struct pa pa_from_pt_with_idx(struct ppn ppn, unsigned int idx) {
return pa_from_ppn_with_offset(ppn, (uint64_t)idx << 3);
}
uint64_t pa_to_value(struct pa pa) {
return pa.value;
}
void *pa_to_pointer(struct pa pa) {
return (void*)PHYS_TO_IDMAPPED(pa.value);
struct va va = va_from_value(pa_to_value(pa) + identity_mapping_start);
return (void *)va_to_canonical(va);
}
uint64_t pa_offset(struct pa pa) {
@ -106,3 +142,15 @@ struct ppn ppn_from_unaligned_pa(struct pa pa) {
uint64_t ppn_to_pagenum(struct ppn ppn) {
return ppn.pagenum;
}
bool ppn_is_huge_2mb(struct ppn ppn) {
return (ppn_to_pagenum(ppn) & ((1ull << 9) - 1)) == 0;
}
bool ppn_is_huge_1gb(struct ppn ppn) {
return (ppn_to_pagenum(ppn) & ((1ull << 18) - 1)) == 0;
}
void set_identity_mapping(struct vpn first_page) {
identity_mapping_start = va_to_value(va_from_vpn(first_page));
}

View file

@ -1,12 +1,9 @@
#include "x86_64/asm.h"
#include "x86_64/apic.h"
#include "address.h"
#include "std.h"
#define MEM_SIZE (16ull * 1024 * 1024 * 1024)
#define IDMAP_END 0x400000000ull
#define IDMAP_START (IDMAP_END - MEM_SIZE)
#define PHYS_TO_IDMAPPED(addr) ((addr) + IDMAP_START)
#define LAPIC_BASE 0xFEE00000
#define LAPIC_BASE_PHYS 0xFEE00000
#define SPURIOUS_VECTOR_APIC_ENABLE 0x100
@ -47,31 +44,44 @@ struct lapic {
struct lapic_register timer_divisor;
struct lapic_register reserved4[1];
};
STATIC_ASSERT(sizeof (struct lapic) == 0x400);
static struct lapic *lapic = (struct lapic *)PHYS_TO_IDMAPPED(LAPIC_BASE);
#define LAPIC_ADDR ((volatile struct lapic *)pa_to_pointer(pa_from_value(LAPIC_BASE_PHYS)))
void lapic_init(void)
{
ASSERT((lapic->lapic_version.value & 0xff) <= 0x15);
ASSERT((LAPIC_ADDR->lapic_version.value & 0xff) <= 0x15);
lapic->spurious_vector.value = SPURIOUS_VECTOR_APIC_ENABLE | 0xff;
LAPIC_ADDR->spurious_vector.value = SPURIOUS_VECTOR_APIC_ENABLE | 0xff;
}
unsigned lapic_get_id(void)
{
return lapic->lapic_id.value >> 24;
return LAPIC_ADDR->lapic_id.value >> 24;
}
unsigned int cpu_get_core_id(void) {
return lapic_get_id();
}
void lapic_eoi(void)
{
lapic->eoi.value = 0;
LAPIC_ADDR->eoi.value = 0;
}
void lapic_set_timer(void)
{
lapic->timer_divisor.value = 0b1010;
lapic->lvt_timer.value = 0xFE | (1 << 17);
lapic->timer_initial.value = 0x1000000;
LAPIC_ADDR->timer_divisor.value = 0b1010;
LAPIC_ADDR->lvt_timer.value = 0xFE | (1 << 17);
LAPIC_ADDR->timer_initial.value = 0x1000000;
}
void pic_disable(void) {
// https://wiki.osdev.org/8259_PIC#Programming_the_PIC_chips
#define PIC1 0x20
#define PIC2 0xA0
#define PIC1_DATA (PIC1 + 1)
#define PIC2_DATA (PIC2 + 1)
out8(PIC1_DATA, 0xff);
out8(PIC2_DATA, 0xff);
}

View file

@ -42,6 +42,10 @@ uint64_t get_cr3(void) {
return cr3;
}
void set_cr3(uint64_t value) {
__asm__("mov %0, %%cr3" :: "r"(value));
}
uint64_t get_cr4(void) {
uint64_t cr4;
__asm__("mov %%cr4, %0" : "=r"(cr4)::);

466
src/x86_64/cpu.c Normal file
View file

@ -0,0 +1,466 @@
#include "x86_64/apic.h"
#include "cpu.h"
#include "std.h"
// --- segmentation ---
extern void loadcs(uint16_t ss, uint16_t cs);
static uint64_t gdt[3];
static void write_segment_descriptor(uint64_t *entry, uint8_t dpl, uint8_t executable) {
uint8_t access_byte = (1 << 7) // present bit
| (dpl << 5) | (1 << 4) // S
| (executable << 3) | (0 << 2) // DC
| (1 << 1) // RW
| (0 << 0); // A
uint8_t flags = (1 << 3) // G
| (0 << 2) // DB
| (1 << 1) // L
| (0 << 0); // reserved
*entry = 0xffff // limit
| ((uint64_t)0x0000 << 16) // base
| ((uint64_t)0x00 << 32) // base
| ((uint64_t)access_byte << 40) // access byte
| ((uint64_t)0xf << 48) // limit
| ((uint64_t)flags << 52) // flags
| ((uint64_t)0x00 << 56); // base
}
#define CODE_SEGMENT 1
#define DATA_SEGMENT 2
void init_gdt() {
gdt[0] = 0;
write_segment_descriptor(&gdt[CODE_SEGMENT], 0, 1);
write_segment_descriptor(&gdt[DATA_SEGMENT], 0, 0);
uint8_t gdtr[10];
*(uint16_t *)gdtr = sizeof(gdt) - 1;
*(uint64_t **)(gdtr + 2) = (uint64_t *)gdt;
__asm__("lgdt (%0)" ::"r"(gdtr));
__asm__("mov %0, %%es" ::"r"(DATA_SEGMENT << 3));
__asm__("mov %0, %%ds" ::"r"(DATA_SEGMENT << 3));
__asm__("mov %0, %%fs" ::"r"(DATA_SEGMENT << 3));
__asm__("mov %0, %%gs" ::"r"(DATA_SEGMENT << 3));
loadcs(DATA_SEGMENT << 3, CODE_SEGMENT << 3);
}
// --- interrupts ---
struct int_desc_entry {
uint16_t offset1;
uint16_t selector;
uint8_t ist;
uint8_t attr;
uint16_t offset2;
uint32_t offset3;
uint32_t pad;
} __attribute__((aligned(16))); // TODO not sure if this is necessary
STATIC_ASSERT(sizeof(struct int_desc_entry) == 16);
#define PRIV_KERNEL 0
#define PRIV_USER 3
static void write_segment_selector(uint16_t *ss, uint16_t index) {
*ss = (uint16_t)PRIV_KERNEL
| 0 // use GDT
| (index << 3);
}
#define GATE_TYPE_INTERRUPT 0xe
static void write_int_desc_entry(struct int_desc_entry *e, uint64_t offset) {
e->offset1 = (uint16_t)(offset & 0xffff);
write_segment_selector(&e->selector, CODE_SEGMENT);
e->ist = 0;
e->attr = (uint8_t)GATE_TYPE_INTERRUPT
| (uint8_t)(PRIV_KERNEL << 5)
| (uint8_t)(1 << 7); // present
e->offset2 = (uint16_t) ((offset >> 16) & 0xffff);
e->offset3 = (uint32_t) ((offset >> 32) & 0xffffffff);
e->pad = 0;
}
#define NUM_INTERRUPTS 256
static struct int_desc_entry idt[NUM_INTERRUPTS];
void load_idt() {
uint8_t idtr[10];
*(uint16_t *)idtr = sizeof(idt) - 1;
*(uint64_t **)(idtr + 2) = (uint64_t *)idt;
__asm__("lidt (%0)" ::"r"(idtr));
}
#define NUM_INTERRUPT_HANDLERS 32
struct {
uint8_t vector;
void (*handler)(void);
} interrupt_handlers[NUM_INTERRUPT_HANDLERS];
unsigned int num_interrupt_handlers = 0;
void interrupt_handler_register(unsigned int vector, void (*handler)(void)) {
ASSERT(vector <= 0xff);
ASSERT(num_interrupt_handlers < NUM_INTERRUPT_HANDLERS);
unsigned int i = num_interrupt_handlers++;
interrupt_handlers[i].vector = vector;
interrupt_handlers[i].handler = handler;
}
#define DEBUG_INTERRUPT
void common_isr_2(uint64_t num) {
#ifdef DEBUG_INTERRUPT
printf("DEBUG: Interrupt %lu\n", num);
#endif
for (unsigned int i = 0; i < num_interrupt_handlers; i++) {
if (interrupt_handlers[i].vector == num) {
interrupt_handlers[i].handler();
}
}
lapic_eoi();
}
// TODO This is done manually because we need to know how the stack looks.
__attribute__((naked))
void common_isr(void) {
__asm__("push %%rbp" :: );
__asm__("mov %%rsp,%%rbp" :: );
__asm__("push %%r11" :: );
__asm__("push %%r10" :: );
__asm__("push %%r9" :: );
__asm__("push %%r8" :: );
__asm__("push %%rdi" :: );
__asm__("push %%rsi" :: );
__asm__("push %%rcx" :: );
__asm__("push %%rdx" :: );
__asm__("push %%rax" :: );
// https://wiki.osdev.org/Interrupt_Service_Routines
// "C code following the sysV ABI requires DF to be clear on function entry"
__asm__("cld");
// get interrupt number and call "real" handler
__asm__("mov 0x8(%%rbp),%%rdi" :: );
__asm__("call common_isr_2" :: );
__asm__("pop %%rax" :: );
__asm__("pop %%rdx" :: );
__asm__("pop %%rcx" :: );
__asm__("pop %%rsi" :: );
__asm__("pop %%rdi" :: );
__asm__("pop %%r8" :: );
__asm__("pop %%r9" :: );
__asm__("pop %%r10" :: );
__asm__("pop %%r11" :: );
__asm__("pop %%rbp" :: );
// remove interrupt number and error code pushed in the isr stub (16 bytes)
__asm__("add $0x10,%%rsp" :: );
__asm__("iretq" :: );
}
// this has to be set manually.
#define ISR_STUB_SIZE 16
// absolutely horrifying stringify thing
// https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html
#define MY_XSTR(s) MY_STR(s)
#define MY_STR(s) #s
// TODO removed cli for now
#define ISR_STUB_NO_PUSH_ERROR(n) __asm__( \
".align " MY_XSTR(ISR_STUB_SIZE) "\n\t" \
"push $" #n "\n\t" \
"jmp common_isr\n\t" \
)
#define ISR_STUB(n) __asm__( \
".align " MY_XSTR(ISR_STUB_SIZE) "\n\t" \
"push $0 \n\t" \
"push $" #n "\n\t" \
"jmp common_isr\n\t" \
)
// error code or not: see https://wiki.osdev.org/Exceptions
__attribute__((aligned(ISR_STUB_SIZE)))
__attribute__((naked))
void inttable(void) {
ISR_STUB(0);
ISR_STUB(1);
ISR_STUB(2);
ISR_STUB(3);
ISR_STUB(4);
ISR_STUB(5);
ISR_STUB(6);
ISR_STUB(7);
ISR_STUB_NO_PUSH_ERROR(8);
ISR_STUB(9);
ISR_STUB_NO_PUSH_ERROR(10);
ISR_STUB_NO_PUSH_ERROR(11);
ISR_STUB_NO_PUSH_ERROR(12);
ISR_STUB_NO_PUSH_ERROR(13);
ISR_STUB_NO_PUSH_ERROR(14);
ISR_STUB(15);
ISR_STUB(16);
ISR_STUB_NO_PUSH_ERROR(17);
ISR_STUB(18);
ISR_STUB(19);
ISR_STUB(20);
ISR_STUB_NO_PUSH_ERROR(21);
ISR_STUB(22);
ISR_STUB(23);
ISR_STUB(24);
ISR_STUB(25);
ISR_STUB(26);
ISR_STUB(27);
ISR_STUB(28);
ISR_STUB_NO_PUSH_ERROR(29);
ISR_STUB_NO_PUSH_ERROR(30);
ISR_STUB(31);
ISR_STUB(32);
ISR_STUB(33);
ISR_STUB(34);
ISR_STUB(35);
ISR_STUB(36);
ISR_STUB(37);
ISR_STUB(38);
ISR_STUB(39);
ISR_STUB(40);
ISR_STUB(41);
ISR_STUB(42);
ISR_STUB(43);
ISR_STUB(44);
ISR_STUB(45);
ISR_STUB(46);
ISR_STUB(47);
ISR_STUB(48);
ISR_STUB(49);
ISR_STUB(50);
ISR_STUB(51);
ISR_STUB(52);
ISR_STUB(53);
ISR_STUB(54);
ISR_STUB(55);
ISR_STUB(56);
ISR_STUB(57);
ISR_STUB(58);
ISR_STUB(59);
ISR_STUB(60);
ISR_STUB(61);
ISR_STUB(62);
ISR_STUB(63);
ISR_STUB(64);
ISR_STUB(65);
ISR_STUB(66);
ISR_STUB(67);
ISR_STUB(68);
ISR_STUB(69);
ISR_STUB(70);
ISR_STUB(71);
ISR_STUB(72);
ISR_STUB(73);
ISR_STUB(74);
ISR_STUB(75);
ISR_STUB(76);
ISR_STUB(77);
ISR_STUB(78);
ISR_STUB(79);
ISR_STUB(80);
ISR_STUB(81);
ISR_STUB(82);
ISR_STUB(83);
ISR_STUB(84);
ISR_STUB(85);
ISR_STUB(86);
ISR_STUB(87);
ISR_STUB(88);
ISR_STUB(89);
ISR_STUB(90);
ISR_STUB(91);
ISR_STUB(92);
ISR_STUB(93);
ISR_STUB(94);
ISR_STUB(95);
ISR_STUB(96);
ISR_STUB(97);
ISR_STUB(98);
ISR_STUB(99);
ISR_STUB(100);
ISR_STUB(101);
ISR_STUB(102);
ISR_STUB(103);
ISR_STUB(104);
ISR_STUB(105);
ISR_STUB(106);
ISR_STUB(107);
ISR_STUB(108);
ISR_STUB(109);
ISR_STUB(110);
ISR_STUB(111);
ISR_STUB(112);
ISR_STUB(113);
ISR_STUB(114);
ISR_STUB(115);
ISR_STUB(116);
ISR_STUB(117);
ISR_STUB(118);
ISR_STUB(119);
ISR_STUB(120);
ISR_STUB(121);
ISR_STUB(122);
ISR_STUB(123);
ISR_STUB(124);
ISR_STUB(125);
ISR_STUB(126);
ISR_STUB(127);
ISR_STUB(128);
ISR_STUB(129);
ISR_STUB(130);
ISR_STUB(131);
ISR_STUB(132);
ISR_STUB(133);
ISR_STUB(134);
ISR_STUB(135);
ISR_STUB(136);
ISR_STUB(137);
ISR_STUB(138);
ISR_STUB(139);
ISR_STUB(140);
ISR_STUB(141);
ISR_STUB(142);
ISR_STUB(143);
ISR_STUB(144);
ISR_STUB(145);
ISR_STUB(146);
ISR_STUB(147);
ISR_STUB(148);
ISR_STUB(149);
ISR_STUB(150);
ISR_STUB(151);
ISR_STUB(152);
ISR_STUB(153);
ISR_STUB(154);
ISR_STUB(155);
ISR_STUB(156);
ISR_STUB(157);
ISR_STUB(158);
ISR_STUB(159);
ISR_STUB(160);
ISR_STUB(161);
ISR_STUB(162);
ISR_STUB(163);
ISR_STUB(164);
ISR_STUB(165);
ISR_STUB(166);
ISR_STUB(167);
ISR_STUB(168);
ISR_STUB(169);
ISR_STUB(170);
ISR_STUB(171);
ISR_STUB(172);
ISR_STUB(173);
ISR_STUB(174);
ISR_STUB(175);
ISR_STUB(176);
ISR_STUB(177);
ISR_STUB(178);
ISR_STUB(179);
ISR_STUB(180);
ISR_STUB(181);
ISR_STUB(182);
ISR_STUB(183);
ISR_STUB(184);
ISR_STUB(185);
ISR_STUB(186);
ISR_STUB(187);
ISR_STUB(188);
ISR_STUB(189);
ISR_STUB(190);
ISR_STUB(191);
ISR_STUB(192);
ISR_STUB(193);
ISR_STUB(194);
ISR_STUB(195);
ISR_STUB(196);
ISR_STUB(197);
ISR_STUB(198);
ISR_STUB(199);
ISR_STUB(200);
ISR_STUB(201);
ISR_STUB(202);
ISR_STUB(203);
ISR_STUB(204);
ISR_STUB(205);
ISR_STUB(206);
ISR_STUB(207);
ISR_STUB(208);
ISR_STUB(209);
ISR_STUB(210);
ISR_STUB(211);
ISR_STUB(212);
ISR_STUB(213);
ISR_STUB(214);
ISR_STUB(215);
ISR_STUB(216);
ISR_STUB(217);
ISR_STUB(218);
ISR_STUB(219);
ISR_STUB(220);
ISR_STUB(221);
ISR_STUB(222);
ISR_STUB(223);
ISR_STUB(224);
ISR_STUB(225);
ISR_STUB(226);
ISR_STUB(227);
ISR_STUB(228);
ISR_STUB(229);
ISR_STUB(230);
ISR_STUB(231);
ISR_STUB(232);
ISR_STUB(233);
ISR_STUB(234);
ISR_STUB(235);
ISR_STUB(236);
ISR_STUB(237);
ISR_STUB(238);
ISR_STUB(239);
ISR_STUB(240);
ISR_STUB(241);
ISR_STUB(242);
ISR_STUB(243);
ISR_STUB(244);
ISR_STUB(245);
ISR_STUB(246);
ISR_STUB(247);
ISR_STUB(248);
ISR_STUB(249);
ISR_STUB(250);
ISR_STUB(251);
ISR_STUB(252);
ISR_STUB(253);
ISR_STUB(254);
ISR_STUB(255);
}
void cpu_init(void) {
init_gdt();
for (int i = 0; i < 256; i++) {
write_int_desc_entry(&idt[i], (uint64_t)(intptr_t)((char*)&inttable + ISR_STUB_SIZE * i));
}
load_idt();
pic_disable();
lapic_init();
}
void cpu_set_timer(void) {
lapic_set_timer();
}

View file

@ -1,353 +0,0 @@
#include <stdint.h>
#include "x86_64/asm.h"
#include "x86_64/mem.h"
#include "std.h" // remove later, this is only for interrupt handler
#include "x86_64/apic.h"
extern void loadcs(uint16_t ss, uint16_t cs);
static uint64_t gdt[3];
static void write_segment_descriptor(uint64_t *entry, uint8_t dpl, uint8_t executable) {
uint8_t access_byte = (1 << 7) // present bit
| (dpl << 5) | (1 << 4) // S
| (executable << 3) | (0 << 2) // DC
| (1 << 1) // RW
| (0 << 0); // A
uint8_t flags = (1 << 3) // G
| (0 << 2) // DB
| (1 << 1) // L
| (0 << 0); // reserved
*entry = 0xffff // limit
| ((uint64_t)0x0000 << 16) // base
| ((uint64_t)0x00 << 32) // base
| ((uint64_t)access_byte << 40) // access byte
| ((uint64_t)0xf << 48) // limit
| ((uint64_t)flags << 52) // flags
| ((uint64_t)0x00 << 56); // base
}
#define CODE_SEGMENT 1
#define DATA_SEGMENT 2
void init_gdt() {
gdt[0] = 0;
write_segment_descriptor(&gdt[CODE_SEGMENT], 0, 1);
write_segment_descriptor(&gdt[DATA_SEGMENT], 0, 0);
uint8_t gdtr[10];
*(uint16_t *)gdtr = sizeof(gdt) - 1;
*(uint64_t **)(gdtr + 2) = (uint64_t *)gdt;
__asm__("lgdt (%0)" ::"r"(gdtr));
__asm__("mov %0, %%es" ::"r"(DATA_SEGMENT << 3));
__asm__("mov %0, %%ds" ::"r"(DATA_SEGMENT << 3));
__asm__("mov %0, %%fs" ::"r"(DATA_SEGMENT << 3));
__asm__("mov %0, %%gs" ::"r"(DATA_SEGMENT << 3));
loadcs(DATA_SEGMENT << 3, CODE_SEGMENT << 3);
}
struct int_desc_entry {
uint16_t offset1;
uint16_t selector;
uint8_t ist;
uint8_t attr;
uint16_t offset2;
uint32_t offset3;
uint32_t pad;
};
STATIC_ASSERT(sizeof(struct int_desc_entry) == 16);
#define PRIV_KERNEL 0
#define PRIV_USER 3
static void write_segment_selector(uint16_t *ss, uint16_t index) {
*ss = (uint16_t)PRIV_KERNEL
| 0 // use GDT
| (index << 3);
}
#define GATE_TYPE_INTERRUPT 0xe
static void write_int_desc_entry(struct int_desc_entry *e, uint64_t offset) {
e->offset1 = (uint16_t)(offset & 0xffff);
write_segment_selector(&e->selector, CODE_SEGMENT);
e->ist = 0;
e->attr = (uint8_t)GATE_TYPE_INTERRUPT
| (uint8_t)(PRIV_KERNEL << 5)
| (uint8_t)(1 << 7); // present
e->offset2 = (uint16_t) ((offset >> 16) & 0xffff);
e->offset3 = (uint32_t) ((offset >> 32) & 0xffffffff);
e->pad = 0;
}
static uint64_t interrupt_counter;
__attribute__ ((interrupt))
void basic_interrupt_handler(void *ptr) {
printf("Hello Interrupt %lu!\n", interrupt_counter);
interrupt_counter++;
lapic_eoi();
}
#define NUM_INTERRUPTS 256
static struct int_desc_entry idt[NUM_INTERRUPTS];
void init_idt() {
write_int_desc_entry(&idt[0xFE], (uint64_t)(intptr_t)basic_interrupt_handler);
uint8_t idtr[10];
*(uint16_t *)idtr = sizeof(idt) - 1;
*(uint64_t **)(idtr + 2) = (uint64_t *)idt;
__asm__("lidt (%0)" ::"r"(idtr));
}
// --- paging ---
uint64_t pt_entry_pack(const struct pt_entry *ent_in) {
return (uint64_t)ent_in->present
| ((uint64_t)ent_in->writable) << 1
| ((uint64_t)ent_in->supervisor) << 2
| ((uint64_t)ent_in->writethrough) << 3
| ((uint64_t)ent_in->cache_disable) << 4
| ((uint64_t)ent_in->accessed) << 5
| ((uint64_t)ent_in->dirty) << 6
| ((uint64_t)ent_in->page_attr_table_low) << 7
| ((uint64_t)ent_in->global) << 8
| ((uint64_t)ent_in->page_attr_table_high) << 12
| pa_to_value(pa_from_ppn(ent_in->ppn));
}
void pt_entry_unpack(uint64_t ent_in, struct pt_entry *ent_out) {
ent_out->present = (ent_in & (0x1ull << 0)) != 0;
ent_out->writable = (ent_in & (0x1ull << 1)) != 0;
ent_out->supervisor = (ent_in & (0x1ull << 2)) != 0;
ent_out->writethrough = (ent_in & (0x1ull << 3)) != 0;
ent_out->cache_disable = (ent_in & (0x1ull << 4)) != 0;
ent_out->accessed = (ent_in & (0x1ull << 5)) != 0;
ent_out->dirty = (ent_in & (0x1ull << 6)) != 0;
ent_out->page_attr_table_low = (ent_in & (0x1ull << 7)) != 0;
ent_out->global = (ent_in & (0x1ull << 8)) != 0;
ent_out->page_attr_table_high = (ent_in & (0x1ull << 12)) != 0;
// this may panic if the address is 57 bit, which is intended here.
ent_out->ppn = ppn_from_aligned_pa(pa_from_value(ent_in & 0x000ffffffffff000ull));
}
void pt_entry_print(const struct pt_entry *ent) {
printf("pt_entry {\n");
printf(" present: %d\n", (int)ent->present);
printf(" writable: %d\n", (int)ent->writable);
printf(" supervisor: %d\n", (int)ent->supervisor);
printf(" writethrough: %d\n", (int)ent->writethrough);
printf(" cache_disable: %d\n", (int)ent->cache_disable);
printf(" accessed: %d\n", (int)ent->accessed);
printf(" dirty: %d\n", (int)ent->dirty);
printf(" page_attr_table_low: %d\n", (int)ent->page_attr_table_low);
printf(" global: %d\n", (int)ent->global);
printf(" page_attr_table_high: %d\n", (int)ent->page_attr_table_high);
printf(" page base: %p\n", pa_from_ppn(ent->ppn));
printf("}\n");
}
static struct ppn get_cr3_ppn(void) {
uint64_t cr3_val = get_cr3();
return ppn_from_aligned_pa(pa_from_value(cr3_val & 0x000ffffffffff000ull));
}
#define NUM_LEVELS 4
static uint64_t *pt_get_leaf_ptr(struct vpn vpn, struct ppn cr3, bool alloc) {
uint64_t va_value = va_to_value(va_from_vpn(vpn));
int level = NUM_LEVELS;
struct ppn ppn = cr3;
while (1) {
uint64_t idx = (va_value >> (12 + (level - 1)*9)) & 0x1ffull;
uint64_t *entry_ptr = (uint64_t*)pa_to_pointer(pa_from_ppn_with_offset(ppn, idx << 3));
struct pt_entry ent;
pt_entry_unpack(*entry_ptr, &ent);
if (level > 1 && ent.page_attr_table_low) {
PANIC("no hugepage support yet!");
}
if (level == 1) {
return entry_ptr;
}
if (!ent.present) {
if (alloc) {
TODO();
} else {
return NULL;
}
}
ppn = ent.ppn;
level -= 1;
}
}
bool pt_translate(struct va va, struct ppn cr3, struct pa *pa_out) {
uint64_t *leaf_ptr = pt_get_leaf_ptr(vpn_from_unaligned_va(va), cr3, false);
if (leaf_ptr == NULL) {
return false;
}
struct pt_entry ent;
pt_entry_unpack(*leaf_ptr, &ent);
*pa_out = pa_from_ppn_with_offset(ent.ppn, va_offset(va));
return true;
}
bool pt_translate_current(struct va va, struct pa *pa_out) {
return pt_translate(va, get_cr3_ppn(), pa_out);
}
bool pt_map_single(struct vpn virt, struct ppn phys,
bool writable, bool supervisor, bool global,
struct ppn cr3)
{
uint64_t *leaf_ptr = pt_get_leaf_ptr(virt, cr3, true);
if (leaf_ptr == NULL) {
return false;
}
struct pt_entry ent;
ent.present = true;
ent.writable = writable;
ent.supervisor = supervisor;
ent.writethrough = false;
ent.cache_disable = false;
ent.accessed = false;
ent.dirty = false;
ent.page_attr_table_low = false;
ent.global = global;
ent.page_attr_table_high = false;
ent.ppn = phys;
*leaf_ptr = pt_entry_pack(&ent);
return true;
}
bool pt_map_single_current(struct vpn virt, struct ppn phys,
bool writable, bool supervisor, bool global)
{
return pt_map_single(virt, phys, writable, supervisor, global, get_cr3_ppn());
}
bool pt_map_range(struct vpn virt, struct ppn phys, uint64_t num_pages,
bool writable, bool supervisor, bool global,
struct ppn cr3)
{
for (uint64_t i = 0; i < num_pages; i++) {
// TODO error handling: what to do if it fails in the middle?
pt_map_single(virt, phys, writable, supervisor, global, cr3);
virt = vpn_from_pagenum(vpn_to_pagenum(virt) + 1);
phys = ppn_from_pagenum(ppn_to_pagenum(phys) + 1);
}
return true;
}
bool pt_map_range_current(struct vpn virt, struct ppn phys, uint64_t num_pages,
bool writable, bool supervisor, bool global)
{
return pt_map_range(virt, phys, num_pages, writable, supervisor, global, get_cr3_ppn());
}
// --- range finder ---
void mem_range_print(const struct mem_range *mr) {
uint64_t virt_canonical = va_to_canonical(va_from_vpn(mr->vpn_start));
uint64_t phys_start = pa_to_value(pa_from_ppn(mr->entry_start.ppn));
printf("virt: %p .. %p (exclusive)\n",
virt_canonical,
virt_canonical + (mr->npages << 12));
printf("phys: %p .. %p (exclusive)\n",
phys_start,
phys_start + (mr->npages << 12));
printf("attr: %c, %c, %s, %s, %s | npages: %lu, size: %lu\n\n",
mr->entry_start.writable ? 'w' : 'r',
mr->entry_start.supervisor ? 's' : 'u',
mr->entry_start.writethrough ? "wt" : "wb",
mr->entry_start.cache_disable ? "cached:n" : "cached:y",
mr->entry_start.global ? "global:y" : "global:n",
mr->npages,
mr->npages << 12);
}
static bool pt_contiguous(struct mem_range *mr, struct vpn vpn, struct pt_entry *ent) {
return vpn_to_pagenum(vpn) == vpn_to_pagenum(mr->vpn_start) + mr->npages
&& ppn_to_pagenum(ent->ppn) == ppn_to_pagenum(mr->entry_start.ppn) + mr->npages
&& ent->writable == mr->entry_start.writable
&& ent->supervisor == mr->entry_start.supervisor
&& ent->writethrough == mr->entry_start.writethrough
&& ent->cache_disable == mr->entry_start.cache_disable
&& ent->global == mr->entry_start.global;
// TODO should PAT also be same?
}
#define LOWEST_LEVEL(level) ((level) == 1)
#define LEAF(level, ent) (LOWEST_LEVEL(level) || (ent).page_attr_table_low)
static void pt_get_ranges_rec(struct ppn ppn, int level, uint64_t virt_prev,
struct mem_range_buf *buf_out, struct mem_range *curr_range)
{
struct pt_entry ent;
for (uint64_t i = 0; i < 512; i++) {
uint64_t entry = *(uint64_t*)pa_to_pointer(pa_from_ppn_with_offset(ppn, i << 3));
pt_entry_unpack(entry, &ent);
if (!ent.present) {
continue;
}
uint64_t virt_part = i << (12 + 9*(level - 1));
uint64_t virt_new = virt_prev | virt_part;
if (LEAF(level, ent)) {
struct vpn curr_vpn = vpn_from_aligned_va(va_from_value(virt_new));
// for huge pages this is > 1
uint64_t num_pages_covered = 1ull << (9*(level - 1));
if (curr_range->npages == 0) {
curr_range->vpn_start = curr_vpn;
curr_range->entry_start = ent;
curr_range->npages = num_pages_covered;
} else {
if (pt_contiguous(curr_range, curr_vpn, &ent)) {
curr_range->npages += num_pages_covered;
} else {
// copy last range and start new one
ASSERT(buf_out->next_entry < buf_out->num_entries);
buf_out->ptr[buf_out->next_entry++] = *curr_range;
curr_range->vpn_start = curr_vpn;
curr_range->entry_start = ent;
curr_range->npages = num_pages_covered;
}
}
} else {
pt_get_ranges_rec(ent.ppn, level - 1, virt_new, buf_out, curr_range);
}
}
}
#define CR4_LA57 12
void pt_get_ranges(struct mem_range_buf *buf_out) {
uint64_t cr4 = get_cr4();
// find out if we have 5 levels or 4
if (cr4 & (1ull << CR4_LA57)) {
PANIC("we don't support 5-level page tables");
}
struct mem_range curr_range = { .npages = 0 };
pt_get_ranges_rec(get_cr3_ppn(), NUM_LEVELS, 0, buf_out, &curr_range);
// last range
ASSERT(buf_out->next_entry < buf_out->num_entries);
buf_out->ptr[buf_out->next_entry++] = curr_range;
}

700
src/x86_64/paging.c Normal file
View file

@ -0,0 +1,700 @@
#include "std.h"
#include "ram.h"
#include "address.h"
#include "x86_64/asm.h"
#include "x86_64/paging.h"
// --- init ---
void init_paging() {
// assert paging enabled (PG)
ASSERT((get_cr0() >> 31) & 1ull);
// assert PAE enabled (this should never be disabled in 64-bit mode)
ASSERT((get_cr4() >> 5) & 1ull);
// PSE is ignored in long mode
// ASSERT((get_cr4() >> 4) & 1ull);
// TODO check that everything is setup correctly
// - CR0.WP
// - Long-Mode Active (EFER.LMA)
// - PAT idx 000 points to default strategy
// - See if NX bits are used and decide if we want to
// - See if MPK is used and decide if we want to
// - SMEP/SMAP?
// - ...
}
// --- pat ---
enum page_attr {
PA_UC = 0x00,
PA_WC = 0x01,
PA_WT = 0x04,
PA_WP = 0x05,
PA_WB = 0x06,
PA_UCMINUS = 0x07
};
#define MSR_PAT 0x277
static enum page_attr get_pa(unsigned int idx) {
ASSERT(idx < 8);
uint64_t value = readmsr(MSR_PAT);
value = (value >> (idx << 3)) & 0x7;
ASSERT(value == PA_UC
|| value == PA_WC
|| value == PA_WT
|| value == PA_WP
|| value == PA_WB
|| value == PA_UCMINUS);
return value;
}
static uint8_t pt_entry_to_pat_idx(const struct pt_entry *ent) {
return ((uint8_t)ent->pat_bit) << 2
| ((uint8_t)ent->cache_disable) << 1
| ((uint8_t)ent->writethrough);
}
static const char *page_attr_to_str(enum page_attr attr) {
switch (attr) {
case PA_UC: return "UC (uncacheable)";
case PA_WC: return "WC (write-combining)";
case PA_WT: return "WT (write-through)";
case PA_WP: return "WP (write-protect)";
case PA_WB: return "WB (write-back)";
case PA_UCMINUS: return "UC- (uncacheable minus)";
default: UNREACHABLE();
}
}
// --- entries ---
uint64_t pt_entry_pack(const struct pt_entry *ent_in) {
uint64_t retval = (uint64_t)ent_in->present
| ((uint64_t)ent_in->writable) << 1
| ((uint64_t)ent_in->supervisor) << 2
| ((uint64_t)ent_in->writethrough) << 3
| ((uint64_t)ent_in->cache_disable) << 4
| ((uint64_t)ent_in->accessed) << 5
| ((uint64_t)ent_in->dirty) << 6
| ((uint64_t)ent_in->global) << 8
| pa_to_value(pa_from_ppn(ent_in->ppn));
switch (ent_in->level) {
case 4:
ASSERT(ent_in->pat_bit == 0); // cannot use PAT bit on highest level (AMD 2 p.228)
ASSERT(ent_in->hugepage == false);
return retval;
case 3: // fallthrough
case 2:
return retval
| ((uint64_t)ent_in->hugepage) << 7
| ((uint64_t)ent_in->pat_bit) << 12;
case 1:
ASSERT(ent_in->hugepage == false);
return retval
| ((uint64_t)ent_in->pat_bit) << 7;
default:
UNREACHABLE();
}
}
void pt_entry_unpack(uint64_t ent_in, unsigned int level, struct pt_entry *ent_out) {
ent_out->level = level;
ent_out->present = (ent_in & (0x1ull << 0)) != 0;
ent_out->writable = (ent_in & (0x1ull << 1)) != 0;
ent_out->supervisor = (ent_in & (0x1ull << 2)) != 0;
ent_out->writethrough = (ent_in & (0x1ull << 3)) != 0;
ent_out->cache_disable = (ent_in & (0x1ull << 4)) != 0;
ent_out->accessed = (ent_in & (0x1ull << 5)) != 0;
ent_out->dirty = (ent_in & (0x1ull << 6)) != 0;
ent_out->global = (ent_in & (0x1ull << 8)) != 0;
ent_out->ppn = ppn_from_aligned_pa(pa_from_value(ent_in & 0x000ffffffffff000ull));
switch (level) {
case 4:
ent_out->hugepage = 0;
ent_out->pat_bit = false;
break;
case 3: // fallthrough
case 2:
ent_out->hugepage = (ent_in & (0x1ull << 7)) != 0;
ent_out->pat_bit = (ent_in & (0x1ull << 12)) != 0;
break;
case 1:
ent_out->hugepage = 0;
ent_out->pat_bit = (ent_in & (0x1ull << 7)) != 0;
break;
default:
UNREACHABLE();
}
}
void pt_entry_init_nonleaf(struct pt_entry *ent, unsigned int level, struct ppn ppn) {
ASSERT(level == 4 || level == 3 || level == 2);
ent->level = level;
ent->present = true;
// maximum privileges in upper level
ent->writable = true;
ent->supervisor = false;
ent->writethrough = false;
ent->cache_disable = false;
ent->accessed = false;
ent->dirty = false;
ent->global = false; // ignored
ent->ppn = ppn;
ent->hugepage = false;
ent->pat_bit = false;
}
void pt_entry_init_leaf(struct pt_entry *ent, unsigned int level,
bool writable, bool supervisor, bool global,
struct ppn ppn)
{
ASSERT(level == 3 || level == 2 || level == 1);
ent->level = level;
ent->present = true;
ent->writable = writable;
ent->supervisor = supervisor;
ent->writethrough = false;
ent->cache_disable = false;
ent->accessed = false;
ent->dirty = false;
ent->global = global;
ent->ppn = ppn;
ent->hugepage = (level == 3 || level == 2);
ent->pat_bit = false;
}
void pt_entry_print(const struct pt_entry *ent) {
printf("pt_entry {\n");
printf(" level: %d\n", (int)ent->level);
printf(" present: %d\n", (int)ent->present);
printf(" writable: %d\n", (int)ent->writable);
printf(" supervisor: %d\n", (int)ent->supervisor);
printf(" accessed: %d\n", (int)ent->accessed);
printf(" dirty: %d\n", (int)ent->dirty);
printf(" global: %d\n", (int)ent->global);
printf(" page base: %p\n", pa_from_ppn(ent->ppn));
switch (ent->level) {
case 4:
printf(" (no hugepage)\n");
break;
case 3: // fallthrough
case 2:
printf(" hugepage: %d\n", (int)ent->hugepage);
break;
case 1:
printf(" (no hugepage)\n");
break;
default:
UNREACHABLE();
}
// TODO right now we assume PAT is present
printf(" caching: %s\n", page_attr_to_str(get_pa(pt_entry_to_pat_idx(ent))));
printf("}\n");
}
// --- convenience ---
static struct ppn get_cr3_ppn(void) {
uint64_t cr3_val = get_cr3();
return ppn_from_aligned_pa(pa_from_value(cr3_val & 0x000ffffffffff000ull));
}
// --- traversal and mapping ---
static struct pa access_level_pa(const struct access_level *ac) {
return pa_from_pt_with_idx(ac->ppn, ac->idx);
}
// TODO for inspection, we need to accumulate permissions over all levels
// (upper supervisor bit will mean lower PTs are also supervisor, even without their bit set)
static uint64_t *pt_get_leaf_ptr(struct vpn vpn, struct ppn cr3, bool alloc, unsigned int *level_out) {
unsigned int level = 4;
struct ppn ppn = cr3;
while (1) {
uint64_t *entry_ptr = (uint64_t*)pa_to_pointer(pa_from_pt_with_idx(ppn, vpn_level_idx(vpn, level)));
struct pt_entry ent;
pt_entry_unpack(*entry_ptr, level, &ent);
if (level == 1 || ent.hugepage) {
*level_out = level;
return entry_ptr;
}
if (!ent.present) {
if (alloc) {
struct ppn new_ppn;
ASSERT(ram_alloc_frame_zeroed(&new_ppn, RAM_PAGE_NORMAL));
pt_entry_init_nonleaf(&ent, level, new_ppn);
*entry_ptr = pt_entry_pack(&ent);
} else {
return NULL;
}
}
ppn = ent.ppn;
level -= 1;
}
}
bool pt_translate(struct va va, struct ppn cr3, struct pa *pa_out) {
unsigned int leaf_level = 42;
uint64_t *leaf_ptr = pt_get_leaf_ptr(vpn_from_unaligned_va(va), cr3, false, &leaf_level);
if (leaf_ptr == NULL) {
return false;
}
ASSERT(leaf_level != 42);
struct pt_entry ent;
pt_entry_unpack(*leaf_ptr, leaf_level, &ent);
switch (leaf_level) {
case 1:
*pa_out = pa_from_ppn_with_offset(ent.ppn, va_offset(va));
break;
case 2:
*pa_out = pa_from_ppn_huge_2mb_with_offset(ent.ppn, va_offset_huge_2mb(va));
break;
case 3:
*pa_out = pa_from_ppn_huge_1gb_with_offset(ent.ppn, va_offset_huge_1gb(va));
break;
default:
UNREACHABLE();
}
return true;
}
bool pt_translate_current(struct va va, struct pa *pa_out) {
return pt_translate(va, get_cr3_ppn(), pa_out);
}
bool pt_map_single(struct vpn virt, struct ppn phys,
bool writable, bool supervisor, bool global,
struct ppn cr3)
{
unsigned int leaf_level = 42;
uint64_t *leaf_ptr = pt_get_leaf_ptr(virt, cr3, true, &leaf_level);
if (leaf_ptr == NULL) {
return false;
}
ASSERT(leaf_level != 42);
// check for and avoid double mapping
struct pt_entry old_ent;
pt_entry_unpack(*leaf_ptr, leaf_level, &old_ent);
if (leaf_level != 1 || old_ent.present) {
return false;
}
struct pt_entry ent;
pt_entry_init_leaf(&ent, 1, writable, supervisor, global, phys);
*leaf_ptr = pt_entry_pack(&ent);
return true;
}
bool pt_map_single_current(struct vpn virt, struct ppn phys,
bool writable, bool supervisor, bool global)
{
return pt_map_single(virt, phys, writable, supervisor, global, get_cr3_ppn());
}
#define TRAVERSAL_LEVEL(trav) (4 - (trav)->depth)
#define TRAVERSAL_NPAGES_COVERED(trav) (1ull << (9 * (TRAVERSAL_LEVEL(trav) - 1)))
static void traversal_init(struct traversal *trav, struct vpn virt, struct ppn cr3) {
trav->depth = 0;
trav->levels[0].ppn = cr3;
trav->levels[0].idx = vpn_level_idx(virt, 4);
}
static uint64_t *traversal_entry_ptr(const struct traversal *trav) {
return pa_to_pointer(access_level_pa(&trav->levels[trav->depth]));
}
static struct vpn traversal_vpn(const struct traversal *trav) {
uint64_t value = 0;
for (unsigned int i = 0; i <= trav->depth; i++) {
unsigned int level = 4 - i;
int shift = 9 * (level - 1);
value |= (uint64_t)(trav->levels[i].idx) << shift;
}
return vpn_from_pagenum(value);
}
static bool traversal_have_leaf(const struct traversal *trav) {
uint64_t *entry_ptr = traversal_entry_ptr(trav);
struct pt_entry ent;
pt_entry_unpack(*entry_ptr, TRAVERSAL_LEVEL(trav), &ent);
return PT_ENTRY_IS_LEAF(ent);
}
static void traversal_push(struct traversal *trav, struct pt_entry *ent, struct vpn virt) {
ASSERT(PT_ENTRY_IS_NONLEAF(*ent));
unsigned int old_depth = trav->depth;
unsigned int new_depth = old_depth + 1;
trav->levels[new_depth].ppn = ent->ppn;
trav->levels[new_depth].idx = vpn_level_idx(virt, 4 - new_depth);
trav->depth = new_depth;
}
// return if we descended or not
static bool traversal_descend_single_noalloc(struct traversal *trav, struct vpn virt) {
ASSERT(trav->depth < 3);
uint64_t *entry_ptr = traversal_entry_ptr(trav);
struct pt_entry ent;
pt_entry_unpack(*entry_ptr, TRAVERSAL_LEVEL(trav), &ent);
if (ent.present) {
traversal_push(trav, &ent, virt);
return true;
} else {
return false;
}
}
static void traversal_descend_single_with_alloc(struct traversal *trav, struct vpn virt) {
ASSERT(trav->depth < 3);
uint64_t *entry_ptr = traversal_entry_ptr(trav);
struct pt_entry ent;
pt_entry_unpack(*entry_ptr, TRAVERSAL_LEVEL(trav), &ent);
if (!ent.present) {
struct ppn ppn;
ASSERT(ram_alloc_frame_zeroed(&ppn, RAM_PAGE_NORMAL));
pt_entry_init_nonleaf(&ent, TRAVERSAL_LEVEL(trav), ppn);
*entry_ptr = pt_entry_pack(&ent);
}
traversal_push(trav, &ent, virt);
}
static void traversal_map(struct traversal *trav, struct ppn phys,
bool writable, bool supervisor, bool global)
{
switch (trav->depth) {
case 0: UNREACHABLE(); // there are no 512 GB pages
case 1: ASSERT(ppn_is_huge_1gb(phys)); break;
case 2: ASSERT(ppn_is_huge_2mb(phys)); break;
case 3: break;
default: UNREACHABLE();
}
uint64_t *entry_ptr = traversal_entry_ptr(trav);
struct pt_entry ent;
pt_entry_unpack(*entry_ptr, TRAVERSAL_LEVEL(trav), &ent);
ASSERT(!ent.present);
pt_entry_init_leaf(&ent, TRAVERSAL_LEVEL(trav), writable, supervisor, global, phys);
*entry_ptr = pt_entry_pack(&ent);
}
// returns whether we're done or not.
static bool traversal_advance_idx(struct traversal *trav) {
while (true) {
trav->levels[trav->depth].idx += 1;
if (trav->levels[trav->depth].idx == 512) {
if (trav->depth > 0) {
trav->depth -= 1;
} else {
return true;
}
} else {
return false;
}
}
}
static bool can_map_huge_1gb(struct ppn phys, uint64_t num_pages) {
return ppn_is_huge_1gb(phys) && num_pages >= (1ull << 18);
}
static bool can_map_huge_2mb(struct ppn phys, uint64_t num_pages) {
return ppn_is_huge_2mb(phys) && num_pages >= (1ull << 9);
}
// if a single mapping fails, PT is left in half-mapped state
bool pt_map_range(struct vpn virt, struct ppn phys, uint64_t num_pages,
bool writable, bool supervisor, bool global,
struct ppn cr3)
{
struct traversal trav;
traversal_init(&trav, virt, cr3);
// we always need at least 2 levels
traversal_descend_single_with_alloc(&trav, virt);
if (!vpn_is_huge_1gb(virt) || !can_map_huge_1gb(phys, num_pages)) {
traversal_descend_single_with_alloc(&trav, virt);
}
if (!vpn_is_huge_2mb(virt) || !can_map_huge_2mb(phys, num_pages)) {
traversal_descend_single_with_alloc(&trav, virt);
}
while (true) {
traversal_map(&trav, phys, writable, supervisor, global);
// advance
uint64_t np = TRAVERSAL_NPAGES_COVERED(&trav);
num_pages -= np;
if (num_pages == 0) {
break;
}
virt = vpn_from_pagenum(vpn_to_pagenum(virt) + np);
phys = ppn_from_pagenum(ppn_to_pagenum(phys) + np);
ASSERT(!traversal_advance_idx(&trav));
if (trav.depth < 1) {
traversal_descend_single_with_alloc(&trav, virt);
}
if (trav.depth < 2 && !can_map_huge_1gb(phys, num_pages)) {
traversal_descend_single_with_alloc(&trav, virt);
}
if (trav.depth < 3 && !can_map_huge_2mb(phys, num_pages)) {
traversal_descend_single_with_alloc(&trav, virt);
}
}
return true;
}
bool pt_map_range_current(struct vpn virt, struct ppn phys, uint64_t num_pages,
bool writable, bool supervisor, bool global)
{
return pt_map_range(virt, phys, num_pages, writable, supervisor, global, get_cr3_ppn());
}
void pt_free(struct ppn cr3) {
// this assumes single ownership
TODO();
}
// --- page table creation ---
#define PT_CREATE_DEBUG
__attribute__((noreturn))
void pt_create_minimal(void) {
// get a top level page table
struct ppn new_cr3;
ASSERT(ram_alloc_frame_zeroed(&new_cr3, RAM_PAGE_NORMAL));
// copy mappings we had previously for everything important
// this start value comes from the bootboot docs
struct mem_range range;
struct mem_range_iter it;
mem_range_iter_init_custom_start(&it, get_cr3_ppn(),
vpn_from_aligned_va(va_from_canonical(0xfffffffff8000000ull)));
while (mem_range_iter_next(&it, &range)) {
pt_map_range(range.vpn_start, range.entry_start.ppn, range.npages,
true, true, false,
new_cr3);
}
// 16 GiB identity map with hugepages starting at low end of upper half of AS
unsigned int npages_in_gb = 1ull << 18;
unsigned int npages_idmap = 16 * npages_in_gb;
struct vpn vpn_idmap_start = vpn_from_aligned_va(va_from_value(1ull << 47));
pt_map_range(vpn_idmap_start,
ppn_from_pagenum(0),
npages_idmap,
true, true, false,
new_cr3);
// new stack with 4 GiB size and guard pages around
// TODO one stack for each core
struct vpn stack_virt = vpn_from_pagenum(vpn_to_pagenum(vpn_idmap_start) + npages_idmap + 1);
struct ppn stack_phys;
ASSERT(ram_alloc_frame_zeroed(&stack_phys, RAM_PAGE_NORMAL));
pt_map_single(stack_virt, stack_phys, true, true, false, new_cr3);
#ifdef PT_CREATE_DEBUG
uint64_t value = pa_to_value(pa_from_ppn(new_cr3));
printf("new cr3: %p\n", value);
// compare old and new to make sure we don't do any bullshit
printf("old ranges:\n");
mem_range_iter_init_current(&it);
while (true) {
if (!mem_range_iter_next(&it, &range)) {
break;
}
mem_range_print(&range);
}
printf("new ranges:\n");
mem_range_iter_init(&it, new_cr3);
while (true) {
if (!mem_range_iter_next(&it, &range)) {
break;
}
mem_range_print(&range);
}
#endif
// switch
set_cr3(value);
set_identity_mapping(vpn_idmap_start);
// stack switch
// it doesn't really make sense to copy stack contents because rbp and pointers
// to local variables will point to the wrong location.
// this means we won't leave this function for now.
uint64_t new_sp = va_to_canonical(va_from_vpn(stack_virt)) + 4096;
__asm__("mov %0,%%rsp" ::"r"(new_sp));
#ifdef PT_CREATE_DEBUG
printf("hello from new page table!\n");
#endif
PANIC("end of pt_create_minimal");
}
// --- iterators ---
void mem_range_print(const struct mem_range *mr) {
uint64_t virt_canonical = va_to_canonical(va_from_vpn(mr->vpn_start));
uint64_t phys_start = pa_to_value(pa_from_ppn(mr->entry_start.ppn));
printf("virt: %p .. %p (exclusive)\n",
virt_canonical,
virt_canonical + (mr->npages << 12));
printf("phys: %p .. %p (exclusive)\n",
phys_start,
phys_start + (mr->npages << 12));
printf("attr: %c, %c, %s, %s | npages: %lu, size: %lu\n\n",
mr->entry_start.writable ? 'w' : 'r',
mr->entry_start.supervisor ? 's' : 'u',
// TODO right now we assume PAT is present
page_attr_to_str(get_pa(pt_entry_to_pat_idx(&mr->entry_start))),
mr->entry_start.global ? "global:y" : "global:n",
mr->npages,
mr->npages << 12);
}
static bool pt_contiguous(struct mem_range *mr, struct vpn vpn, struct pt_entry *ent) {
return vpn_to_pagenum(vpn) == vpn_to_pagenum(mr->vpn_start) + mr->npages
&& ppn_to_pagenum(ent->ppn) == ppn_to_pagenum(mr->entry_start.ppn) + mr->npages
&& ent->writable == mr->entry_start.writable
&& ent->supervisor == mr->entry_start.supervisor
&& ent->writethrough == mr->entry_start.writethrough
&& ent->cache_disable == mr->entry_start.cache_disable
&& ent->global == mr->entry_start.global
&& ent->pat_bit == mr->entry_start.pat_bit;
}
void pt_leaf_iter_init(struct pt_leaf_iter *it, struct ppn cr3) {
it->done = false;
traversal_init(&it->trav, vpn_from_pagenum(0), cr3);
}
void pt_leaf_iter_init_current(struct pt_leaf_iter *it) {
pt_leaf_iter_init(it, get_cr3_ppn());
}
void pt_leaf_iter_init_custom_start(struct pt_leaf_iter *it, struct ppn cr3, struct vpn virt) {
it->done = false;
traversal_init(&it->trav, virt, cr3);
// "incorporate" virt into this traversal so we don't have to store it
while(!traversal_have_leaf(&it->trav) && traversal_descend_single_noalloc(&it->trav, virt)) {
// continue
}
}
bool pt_leaf_iter_next(struct pt_leaf_iter *it, struct pt_leaf *leaf_out) {
ASSERT(it != NULL && leaf_out != NULL);
if (it->done) {
return false;
}
while (true) {
uint64_t *entry_ptr = traversal_entry_ptr(&it->trav);
struct pt_entry ent;
pt_entry_unpack(*entry_ptr, TRAVERSAL_LEVEL(&it->trav), &ent);
if (!ent.present) {
it->done = traversal_advance_idx(&it->trav);
if (it->done) {
return false;
}
} else {
if (PT_ENTRY_IS_LEAF(ent)) {
*leaf_out = (struct pt_leaf){ .vpn_start = traversal_vpn(&it->trav), .ent = ent };
it->done = traversal_advance_idx(&it->trav);
return true;
} else {
traversal_push(&it->trav, &ent, vpn_from_pagenum(0));
}
}
}
}
static uint64_t leaf_level_to_npages(unsigned int level) {
switch (level) {
case 1: return 1;
case 2: return 1 << 9;
case 3: return 1 << 18;
default: UNREACHABLE();
}
}
void mem_range_iter_init(struct mem_range_iter *it, struct ppn cr3) {
pt_leaf_iter_init(&it->leaf_it, cr3);
it->have_held_leaf = false;
}
void mem_range_iter_init_current(struct mem_range_iter *it) {
mem_range_iter_init(it, get_cr3_ppn());
}
void mem_range_iter_init_custom_start(struct mem_range_iter *it, struct ppn cr3, struct vpn virt) {
pt_leaf_iter_init_custom_start(&it->leaf_it, cr3, virt);
it->have_held_leaf = false;
}
static bool mem_range_iter_leaf_get(struct mem_range_iter *it, struct pt_leaf *leaf_out) {
if (it->have_held_leaf) {
it->have_held_leaf = false;
*leaf_out = it->held_leaf;
return true;
} else {
return pt_leaf_iter_next(&it->leaf_it, leaf_out);
}
}
static void mem_range_iter_leaf_unget(struct mem_range_iter *it, struct pt_leaf leaf) {
ASSERT(!it->have_held_leaf);
it->have_held_leaf = true;
it->held_leaf = leaf;
}
bool mem_range_iter_next(struct mem_range_iter *it, struct mem_range *range_out) {
struct pt_leaf leaf;
bool success = mem_range_iter_leaf_get(it, &leaf);
if (!success) {
return false;
}
struct mem_range range = (struct mem_range){
.vpn_start = leaf.vpn_start,
.entry_start = leaf.ent,
.npages = leaf_level_to_npages(leaf.ent.level),
};
while (true) {
bool success = mem_range_iter_leaf_get(it, &leaf);
if (!success) {
*range_out = range;
return true;
}
if (pt_contiguous(&range, leaf.vpn_start, &leaf.ent)) {
range.npages += leaf_level_to_npages(leaf.ent.level);
} else {
mem_range_iter_leaf_unget(it, leaf);
*range_out = range;
return true;
}
}
}

149
src/x86_64/ps2_driver.c Normal file
View file

@ -0,0 +1,149 @@
#include <stdint.h>
#include "std.h"
#include "x86_64/asm.h"
#include "x86_64/ps2_driver.h"
#define PORT_STATUS 0x64
#define PORT_CMD 0x64
#define PORT_DATA 0x60
#define CMD_CONFIGBYTE_READ 0x20
#define CMD_CONFIGBYTE_WRITE 0x20
#define CMD_PORT2_DISABLE 0xA7
#define CMD_PORT2_ENABLE 0xA8
#define CMD_PORT2_TEST 0xA9
#define CMD_TEST 0xAA
#define CMD_PORT1_TEST 0xAB
#define CMD_PORT1_DISABLE 0xAD
#define CMD_PORT1_ENABLE 0xAE
#define STATUS_BIT_OUTPUT_FULL 0
#define STATUS_BIT_INPUT_FULL 1
#define STATUS_BIT_SYSTEM 2
#define STATUS_BIT_COMMAND_DATA 3
#define STATUS_BIT_ERR_TIMEOUT 6
#define STATUS_BIT_ERR_PARITY 7
#define CONFIG_BIT_PORT1_INT_ENABLED 0
#define CONFIG_BIT_PORT2_INT_ENABLED 1
#define CONFIG_BIT_SYSTEM 2
#define CONFIG_BIT_PORT1_CLOCK_DISABLED 4
#define CONFIG_BIT_PORT2_CLOCK_DISABLED 5
#define CONFIG_BIT_PORT1_TRANSLATION 6
uint8_t
ps2_read_status() {
return in8(PORT_STATUS);
}
bool
ps2_get_status_bit(uint8_t bit) {
return (ps2_read_status() >> bit) & 1;
}
bool
ps2_can_read() {
return ps2_get_status_bit(STATUS_BIT_OUTPUT_FULL);
}
bool
ps2_can_write() {
return !ps2_get_status_bit(STATUS_BIT_INPUT_FULL);
}
void ps2_write_cmd(uint8_t cmd) {
while (!ps2_can_write()) {
// wait
}
out8(PORT_CMD, cmd);
}
uint8_t ps2_read_data() {
while(!ps2_can_read()) {
// wait
}
return in8(PORT_DATA);
}
void ps2_empty_output_buffer() {
while (ps2_can_read()) {
in8(PORT_DATA);
}
}
void ps2_write_data(uint8_t data) {
while (!ps2_can_write()) {
// wait
}
out8(PORT_DATA, data);
}
uint8_t ps2_cmd_response(uint8_t cmd) {
ps2_write_cmd(cmd);
return ps2_read_data();
}
void ps2_cmd_with_data(uint8_t cmd, uint8_t data) {
ps2_write_cmd(cmd);
ps2_write_data(data);
}
void ps2_init() {
uint8_t config_byte;
/* Disable devices */
ps2_write_cmd(CMD_PORT1_DISABLE);
ps2_write_cmd(CMD_PORT2_DISABLE);
/* Flush Outputbuffer */
ps2_empty_output_buffer();
/* Set Configbyte */
config_byte = ps2_cmd_response(CMD_CONFIGBYTE_READ);
config_byte &= ~CONFIG_BIT_PORT1_INT_ENABLED;
config_byte &= ~CONFIG_BIT_PORT1_CLOCK_DISABLED;
config_byte &= ~CONFIG_BIT_PORT1_TRANSLATION;
ps2_cmd_with_data(CMD_CONFIGBYTE_WRITE, config_byte);
/* Selftest */
uint8_t test = ps2_cmd_response(CMD_TEST);
ASSERT(test == 0x55);
/* Query for 2nd port */
ps2_write_cmd(CMD_PORT2_ENABLE);
config_byte = ps2_cmd_response(CMD_CONFIGBYTE_READ);
bool port_2_enabled = ((config_byte >> 5) & 1) == 0;
if (port_2_enabled) {
ps2_write_cmd(CMD_PORT2_DISABLE);
config_byte = ps2_cmd_response(CMD_CONFIGBYTE_READ);
config_byte &= ~CONFIG_BIT_PORT2_INT_ENABLED;
config_byte &= ~CONFIG_BIT_PORT2_CLOCK_DISABLED;
ps2_cmd_with_data(CMD_CONFIGBYTE_WRITE, config_byte);
}
/* Test Ports */
uint8_t port_test = ps2_cmd_response(CMD_PORT1_TEST);
ASSERT(port_test == 0x00);
if (port_2_enabled) {
port_test = ps2_cmd_response(CMD_PORT2_TEST);
ASSERT(port_test == 0x00);
}
/* Enable devices */
ps2_write_cmd(CMD_PORT1_ENABLE);
if (port_2_enabled) {
ps2_write_cmd(CMD_PORT2_ENABLE);
}
/* Enable interrupts */
config_byte = ps2_cmd_response(CMD_CONFIGBYTE_READ);
config_byte |= CONFIG_BIT_PORT1_INT_ENABLED;
if (port_2_enabled) {
config_byte |= CONFIG_BIT_PORT2_INT_ENABLED;
}
ps2_cmd_with_data(CMD_CONFIGBYTE_WRITE, config_byte);
/* Reset devices */
// TODO
}

View file

@ -12,3 +12,7 @@ void uart_write_char(char c) {
out8(COM1_BASE_PORT, c);
}
void serial_write_char(char c) {
uart_write_char(c);
}