initgraph, whole lot of refactor
This commit is contained in:
+4
-2
@@ -1,3 +1,5 @@
|
||||
#define pr_fmt(fmt) "clock: " fmt
|
||||
|
||||
#include <zinnia/clock.h>
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/print.h>
|
||||
@@ -47,14 +49,14 @@ bool clock_switch(struct clock* clock) {
|
||||
|
||||
spin_unlock(&lock);
|
||||
|
||||
log("clock: Switching to clock source \"%s\"\n", clock->name);
|
||||
log("Switching to clock source \"%s\"\n", clock->name);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void clock_spin_ns(uint64_t ns) {
|
||||
if (!clock_available()) {
|
||||
log("clock: Unable to sleep for %lu ns, no clock available!\n", ns);
|
||||
log_err("Unable to sleep for %lu ns, no clock available!\n", ns);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+31
-32
@@ -1,45 +1,44 @@
|
||||
#include <zinnia/cmdline.h>
|
||||
#include <zinnia/console.h>
|
||||
#include <zinnia/init.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <zinnia/spin.h>
|
||||
|
||||
extern struct console* const __ld_console_start[];
|
||||
extern struct console* const __ld_console_end[];
|
||||
|
||||
struct console* active = nullptr;
|
||||
static struct console* registered = nullptr;
|
||||
static struct spinlock console_lock;
|
||||
|
||||
void console_write(const char* buf, size_t len) {
|
||||
if (__likely(active))
|
||||
active->write(active, buf, len);
|
||||
spin_lock(&console_lock);
|
||||
for (struct console* con = registered; con; con = con->next)
|
||||
con->write(con, buf, len);
|
||||
spin_unlock(&console_lock);
|
||||
}
|
||||
|
||||
static void console_setup(const char* value) {
|
||||
if (!value)
|
||||
return;
|
||||
bool console_register(struct console* con) {
|
||||
if (!con->init(con))
|
||||
return false;
|
||||
|
||||
// Find a suitable boot console.
|
||||
struct console* const* cur = __ld_console_start;
|
||||
while (cur < __ld_console_end) {
|
||||
if (!strcmp(value, (*cur)->name)) {
|
||||
active = *cur;
|
||||
spin_lock(&console_lock);
|
||||
con->next = registered;
|
||||
registered = con;
|
||||
spin_unlock(&console_lock);
|
||||
|
||||
log("console: Registered console \"%s\"\n", con->name);
|
||||
return true;
|
||||
}
|
||||
|
||||
void console_unregister(struct console* con) {
|
||||
spin_lock(&console_lock);
|
||||
for (struct console** link = ®istered; *link; link = &(*link)->next) {
|
||||
if (*link == con) {
|
||||
*link = con->next;
|
||||
break;
|
||||
}
|
||||
cur++;
|
||||
}
|
||||
spin_unlock(&console_lock);
|
||||
}
|
||||
|
||||
CMDLINE_OPTION("console", "Set a default console", console_setup);
|
||||
|
||||
void console_init() {
|
||||
// If we have no console set and at least one set, use that as a default.
|
||||
if (!active && &__ld_console_start[0] != &__ld_console_end[0])
|
||||
active = __ld_console_start[0];
|
||||
|
||||
if (active) {
|
||||
if (active->init(active) == false) {
|
||||
active = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
log("console: Logging on console \"%s\"\n", active->name);
|
||||
}
|
||||
[[__init]]
|
||||
static void console_stage_init(struct boot_info*) {
|
||||
kprintf("\ec");
|
||||
}
|
||||
INIT_TASK(console_stage, "core.console", console_stage_init);
|
||||
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/assert.h>
|
||||
#include <zinnia/console.h>
|
||||
#include <zinnia/init.h>
|
||||
#include <zinnia/pmap.h>
|
||||
#include <zinnia/sched.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <zinnia/vma.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
static const uint8_t fbcon_font[] = {
|
||||
#embed "fbcon_font.bin"
|
||||
};
|
||||
|
||||
constexpr size_t FONT_WIDTH = 8;
|
||||
constexpr size_t FONT_HEIGHT = 12;
|
||||
constexpr uint32_t FG_COLOR = 0xaaaaaa;
|
||||
constexpr uint32_t BG_COLOR = 0x000000;
|
||||
|
||||
enum esc_state {
|
||||
ESC_NONE,
|
||||
ESC_START,
|
||||
ESC_CSI,
|
||||
};
|
||||
|
||||
static struct {
|
||||
struct boot_framebuffer fb;
|
||||
uint32_t fg, bg;
|
||||
size_t cols, rows;
|
||||
size_t offset_x, offset_y;
|
||||
size_t cx, cy;
|
||||
enum esc_state esc;
|
||||
} fbcon;
|
||||
|
||||
static inline uint8_t* fb_ptr() {
|
||||
return HHDM_PTR(fbcon.fb.base);
|
||||
}
|
||||
|
||||
static uint32_t pack_color(uint32_t rgb) {
|
||||
const struct boot_framebuffer* fb = &fbcon.fb;
|
||||
uint32_t r = (rgb >> 16) & 0xff;
|
||||
uint32_t g = (rgb >> 8) & 0xff;
|
||||
uint32_t b = rgb & 0xff;
|
||||
return ((r >> (8 - fb->red.size)) << fb->red.shift) | ((g >> (8 - fb->green.size)) << fb->green.shift) |
|
||||
((b >> (8 - fb->blue.size)) << fb->blue.shift);
|
||||
}
|
||||
|
||||
static void fill_rect(size_t x, size_t y, size_t w, size_t h, uint32_t color) {
|
||||
for (size_t j = 0; j < h; j++) {
|
||||
uint32_t* row = (uint32_t*)(fb_ptr() + (y + j) * fbcon.fb.pitch) + x;
|
||||
for (size_t i = 0; i < w; i++)
|
||||
row[i] = color;
|
||||
}
|
||||
}
|
||||
|
||||
static void clear_screen() {
|
||||
fill_rect(0, 0, fbcon.fb.width, fbcon.fb.height, fbcon.bg);
|
||||
fbcon.cx = fbcon.cy = 0;
|
||||
}
|
||||
|
||||
static void draw_glyph(size_t col, size_t row, uint8_t ch) {
|
||||
const uint8_t* glyph = &fbcon_font[ch * FONT_HEIGHT];
|
||||
size_t x = fbcon.offset_x + col * FONT_WIDTH;
|
||||
size_t y = fbcon.offset_y + row * FONT_HEIGHT;
|
||||
|
||||
for (size_t j = 0; j < FONT_HEIGHT; j++) {
|
||||
uint32_t* line = (uint32_t*)(fb_ptr() + (y + j) * fbcon.fb.pitch) + x;
|
||||
for (size_t i = 0; i < FONT_WIDTH; i++)
|
||||
line[i] = (glyph[j] & (0x80 >> i)) ? fbcon.fg : fbcon.bg;
|
||||
}
|
||||
}
|
||||
|
||||
static void scroll_up() {
|
||||
const size_t pitch = fbcon.fb.pitch;
|
||||
const size_t text_y = fbcon.offset_y;
|
||||
const size_t text_h = fbcon.rows * FONT_HEIGHT;
|
||||
|
||||
memmove(fb_ptr() + text_y * pitch, fb_ptr() + (text_y + FONT_HEIGHT) * pitch, (text_h - FONT_HEIGHT) * pitch);
|
||||
fill_rect(0, text_y + text_h - FONT_HEIGHT, fbcon.fb.width, FONT_HEIGHT, fbcon.bg);
|
||||
}
|
||||
|
||||
static void newline() {
|
||||
fbcon.cx = 0;
|
||||
if (++fbcon.cy >= fbcon.rows) {
|
||||
fbcon.cy = fbcon.rows - 1;
|
||||
scroll_up();
|
||||
}
|
||||
}
|
||||
|
||||
static void put_char(uint8_t ch) {
|
||||
switch (fbcon.esc) {
|
||||
case ESC_START:
|
||||
fbcon.esc = ch == '[' ? ESC_CSI : ESC_NONE;
|
||||
if (ch == 'c')
|
||||
clear_screen();
|
||||
return;
|
||||
case ESC_CSI:
|
||||
if (ch >= 0x40 && ch <= 0x7e)
|
||||
fbcon.esc = ESC_NONE;
|
||||
return;
|
||||
case ESC_NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
case 0x1b:
|
||||
fbcon.esc = ESC_START;
|
||||
break;
|
||||
case '\n':
|
||||
newline();
|
||||
break;
|
||||
case '\r':
|
||||
fbcon.cx = 0;
|
||||
break;
|
||||
case '\b':
|
||||
if (fbcon.cx > 0)
|
||||
fbcon.cx--;
|
||||
break;
|
||||
case '\t':
|
||||
fbcon.cx = (fbcon.cx + 8) & ~(size_t)7;
|
||||
if (fbcon.cx >= fbcon.cols)
|
||||
newline();
|
||||
break;
|
||||
default:
|
||||
if (fbcon.cx >= fbcon.cols)
|
||||
newline();
|
||||
draw_glyph(fbcon.cx++, fbcon.cy, ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void fbcon_write(struct console* con, const char* buf, size_t count) {
|
||||
for (size_t i = 0; i < count; i++)
|
||||
put_char((uint8_t)buf[i]);
|
||||
}
|
||||
|
||||
static bool fbcon_setup(struct console* con) {
|
||||
const struct boot_framebuffer* fb = &fbcon.fb;
|
||||
if (fb->bytes_per_pixel != 4 || fb->red.size > 8 || fb->green.size > 8 || fb->blue.size > 8)
|
||||
return false;
|
||||
|
||||
fbcon.fg = pack_color(FG_COLOR);
|
||||
fbcon.bg = pack_color(BG_COLOR);
|
||||
fbcon.cols = fb->width / FONT_WIDTH;
|
||||
fbcon.rows = fb->height / FONT_HEIGHT;
|
||||
fbcon.offset_x = (fb->width % FONT_WIDTH) / 2;
|
||||
fbcon.offset_y = (fb->height % FONT_HEIGHT) / 2;
|
||||
if (!fbcon.cols || !fbcon.rows)
|
||||
return false;
|
||||
|
||||
clear_screen();
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct console fbcon_con = {
|
||||
.name = "fbcon",
|
||||
.init = fbcon_setup,
|
||||
.write = fbcon_write,
|
||||
};
|
||||
|
||||
[[__init]]
|
||||
void fbcon_init(struct boot_info* info) {
|
||||
const struct boot_framebuffer* fb = &info->fb;
|
||||
if (!fb->base)
|
||||
return;
|
||||
|
||||
fbcon.fb = *fb;
|
||||
|
||||
const size_t pgsz = arch_mem_page_size();
|
||||
const phys_t start = ALIGN_DOWN(fb->base, pgsz);
|
||||
const phys_t end = ALIGN_UP(fb->base + fb->pitch * fb->height, pgsz);
|
||||
|
||||
for (phys_t p = start; p < end; p += pgsz) {
|
||||
errno_t status =
|
||||
pmap_map(&kernel_vma.pmap, (uintptr_t)HHDM_PTR(p), p, PTE_READ | PTE_WRITE, CACHE_WRITE_COMBINE);
|
||||
if (status)
|
||||
return;
|
||||
}
|
||||
|
||||
console_register(&fbcon_con);
|
||||
}
|
||||
INIT_TASK(fbcon_stage, "core.fbcon", fbcon_init);
|
||||
INIT_DEPENDS(fbcon_stage, sched_stage);
|
||||
Binary file not shown.
@@ -0,0 +1,168 @@
|
||||
#define pr_fmt(fmt) "init: " fmt
|
||||
|
||||
#include <zinnia/assert.h>
|
||||
#include <zinnia/cmdline.h>
|
||||
#include <zinnia/initgraph.h>
|
||||
#include <zinnia/print.h>
|
||||
|
||||
extern struct init_node* const __ld_initgraph_nodes_start[];
|
||||
extern struct init_node* const __ld_initgraph_nodes_end[];
|
||||
extern const struct init_edges* const __ld_initgraph_edges_start[];
|
||||
extern const struct init_edges* const __ld_initgraph_edges_end[];
|
||||
|
||||
static bool dump_graph = false;
|
||||
static void initgraph_option(bool value) {
|
||||
dump_graph = value;
|
||||
}
|
||||
CMDLINE_OPTION("initgraph", "Print the init graph in DOT format after running", initgraph_option);
|
||||
|
||||
static bool edges_linked = false;
|
||||
|
||||
struct init_queue {
|
||||
struct init_node* head;
|
||||
struct init_node* tail;
|
||||
};
|
||||
|
||||
static void queue_push(struct init_queue* queue, struct init_node* node) {
|
||||
node->queue_next = nullptr;
|
||||
if (queue->tail)
|
||||
queue->tail->queue_next = node;
|
||||
else
|
||||
queue->head = node;
|
||||
queue->tail = node;
|
||||
}
|
||||
|
||||
static struct init_node* queue_pop(struct init_queue* queue) {
|
||||
struct init_node* node = queue->head;
|
||||
if (node) {
|
||||
queue->head = node->queue_next;
|
||||
if (!queue->head)
|
||||
queue->tail = nullptr;
|
||||
node->queue_next = nullptr;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
static void link_edges() {
|
||||
for (auto cur = __ld_initgraph_edges_start; cur < __ld_initgraph_edges_end; cur++) {
|
||||
const struct init_edges* group = *cur;
|
||||
for (size_t i = 0; i < group->count; i++) {
|
||||
struct init_edge* edge = &group->edges[i];
|
||||
edge->source = group->depends ? group->nodes[i] : group->node;
|
||||
edge->target = group->depends ? group->node : group->nodes[i];
|
||||
edge->propagates_disable = group->depends;
|
||||
|
||||
edge->next_out = edge->source->out_edges;
|
||||
edge->source->out_edges = edge;
|
||||
edge->next_in = edge->target->in_edges;
|
||||
edge->target->in_edges = edge;
|
||||
edge->target->unsatisfied++;
|
||||
}
|
||||
}
|
||||
edges_linked = true;
|
||||
}
|
||||
|
||||
static void mark_wanted(struct init_node* goal) {
|
||||
if (!goal) {
|
||||
for (auto cur = __ld_initgraph_nodes_start; cur < __ld_initgraph_nodes_end; cur++)
|
||||
(*cur)->wanted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
struct init_queue queue = {};
|
||||
|
||||
if (!goal->wanted) {
|
||||
goal->wanted = true;
|
||||
queue_push(&queue, goal);
|
||||
}
|
||||
|
||||
struct init_node* node;
|
||||
while ((node = queue_pop(&queue))) {
|
||||
for (struct init_edge* edge = node->in_edges; edge; edge = edge->next_in) {
|
||||
if (!edge->source->wanted) {
|
||||
edge->source->wanted = true;
|
||||
queue_push(&queue, edge->source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_ready(struct init_node* node) {
|
||||
return node->wanted && !node->done && node->unsatisfied == 0;
|
||||
}
|
||||
|
||||
static void reach_node(struct init_node* node, struct boot_info* info) {
|
||||
if (node->disabled) {
|
||||
log_status("Skipping stage \"%s\"\n", node->name);
|
||||
} else {
|
||||
log_status("Running stage \"%s\"\n", node->name);
|
||||
switch (node->action) {
|
||||
case INIT_EMPTY:
|
||||
break;
|
||||
case INIT_CALLBACK:
|
||||
node->callback(info);
|
||||
break;
|
||||
case INIT_GATE:
|
||||
if (!node->gate(info))
|
||||
node->disabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
node->done = true;
|
||||
}
|
||||
|
||||
static void dump() {
|
||||
kprintf("digraph initgraph {\n");
|
||||
for (auto cur = __ld_initgraph_nodes_start; cur < __ld_initgraph_nodes_end; cur++) {
|
||||
struct init_node* node = *cur;
|
||||
kprintf("\tn%p [label=\"%s\"];\n", node, node->name);
|
||||
for (struct init_edge* edge = node->in_edges; edge; edge = edge->next_in)
|
||||
kprintf("\tn%p -> n%p;\n", edge->source, edge->target);
|
||||
}
|
||||
kprintf("}\n");
|
||||
}
|
||||
|
||||
void initgraph_run(struct init_node* goal, struct boot_info* info) {
|
||||
if (!edges_linked)
|
||||
link_edges();
|
||||
|
||||
mark_wanted(goal);
|
||||
|
||||
struct init_queue pending = {};
|
||||
|
||||
for (auto cur = __ld_initgraph_nodes_start; cur < __ld_initgraph_nodes_end; cur++) {
|
||||
if (is_ready(*cur))
|
||||
queue_push(&pending, *cur);
|
||||
}
|
||||
|
||||
struct init_node* node;
|
||||
while ((node = queue_pop(&pending))) {
|
||||
reach_node(node, info);
|
||||
|
||||
for (struct init_edge* edge = node->out_edges; edge; edge = edge->next_out) {
|
||||
struct init_node* successor = edge->target;
|
||||
assert(successor->unsatisfied != 0, "Stage \"%s\" has no unsatisfied dependencies left", successor->name);
|
||||
successor->unsatisfied--;
|
||||
|
||||
if (node->disabled && edge->propagates_disable)
|
||||
successor->disabled = true;
|
||||
|
||||
if (is_ready(successor))
|
||||
queue_push(&pending, successor);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto cur = __ld_initgraph_nodes_start; cur < __ld_initgraph_nodes_end; cur++) {
|
||||
assert(
|
||||
!(*cur)->wanted || (*cur)->done,
|
||||
"The dependencies for stage \"%s\" could not be resolved",
|
||||
(*cur)->name
|
||||
);
|
||||
}
|
||||
|
||||
if (!goal) {
|
||||
log_status("All stages are complete!\n");
|
||||
if (dump_graph)
|
||||
dump();
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/assert.h>
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/elf.h>
|
||||
#include <zinnia/hashmap.h>
|
||||
#include <zinnia/init.h>
|
||||
#include <zinnia/ksyms.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <bits/elf.h>
|
||||
#include <string.h>
|
||||
|
||||
[[__visibility("hidden")]]
|
||||
extern const elf_sym_t __ld_dynsym_start[];
|
||||
[[__visibility("hidden")]]
|
||||
extern const elf_sym_t __ld_dynsym_end[];
|
||||
[[__visibility("hidden")]]
|
||||
extern const char __ld_dynstr_start[];
|
||||
[[__visibility("hidden")]]
|
||||
extern const char __ld_dynstr_end[];
|
||||
[[__visibility("hidden")]]
|
||||
extern const elf_dyn_t _DYNAMIC[];
|
||||
|
||||
static uintptr_t slide;
|
||||
static bool slide_known;
|
||||
static const char* kernel_name;
|
||||
static hashmap(const char*, uintptr_t) symbols;
|
||||
|
||||
static uint64_t sym_hash(const void* key, size_t) {
|
||||
const char* name = *(const char* const*)key;
|
||||
return hashmap_hash_default(name, strlen(name));
|
||||
}
|
||||
|
||||
static bool sym_eq(const void* a, const void* b, size_t) {
|
||||
return !strcmp(*(const char* const*)a, *(const char* const*)b);
|
||||
}
|
||||
|
||||
static size_t num_syms() {
|
||||
return __ld_dynsym_end - __ld_dynsym_start;
|
||||
}
|
||||
|
||||
static const char* dynstr(uint64_t offset) {
|
||||
if (offset >= (size_t)(__ld_dynstr_end - __ld_dynstr_start))
|
||||
return nullptr;
|
||||
return &__ld_dynstr_start[offset];
|
||||
}
|
||||
|
||||
static uintptr_t kernel_slide() {
|
||||
if (slide_known)
|
||||
return slide;
|
||||
|
||||
for (size_t i = 0; i < num_syms(); i++) {
|
||||
const char* name = dynstr(__ld_dynsym_start[i].st_name);
|
||||
if (name != nullptr && !strcmp(name, "ksym_lookup")) {
|
||||
slide = (uintptr_t)ksym_lookup - __ld_dynsym_start[i].st_value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
slide_known = true;
|
||||
return slide;
|
||||
}
|
||||
|
||||
void ksym_init() {
|
||||
const uintptr_t base = kernel_slide();
|
||||
|
||||
for (const elf_dyn_t* dyn = _DYNAMIC; dyn->d_tag != DT_NULL; dyn++) {
|
||||
if (dyn->d_tag == DT_SONAME)
|
||||
kernel_name = dynstr(dyn->d_un.d_val);
|
||||
}
|
||||
|
||||
hashmap_init(&symbols);
|
||||
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < num_syms(); i++) {
|
||||
const elf_sym_t* sym = &__ld_dynsym_start[i];
|
||||
if (sym->st_shndx == SHN_UNDEF)
|
||||
continue;
|
||||
|
||||
const char* name = dynstr(sym->st_name);
|
||||
if (name == nullptr || *name == 0)
|
||||
continue;
|
||||
|
||||
const errno_t status = hashmap_insert(&symbols, name, sym->st_value + base, sym_hash, sym_eq);
|
||||
assert(status == 0, "Failed to register kernel symbol \"%s\" (%i)", name, status);
|
||||
count++;
|
||||
}
|
||||
|
||||
log("ksyms: Registered %zu kernel symbols\n", count);
|
||||
}
|
||||
|
||||
bool ksym_find(const char* name, uintptr_t* addr) {
|
||||
uintptr_t* value = hashmap_get(&symbols, name, sym_hash, sym_eq);
|
||||
if (value == nullptr)
|
||||
return false;
|
||||
*addr = *value;
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* ksym_kernel_name() {
|
||||
return kernel_name;
|
||||
}
|
||||
|
||||
bool ksym_lookup(uintptr_t addr, const char** name, size_t* offset) {
|
||||
addr -= kernel_slide();
|
||||
for (size_t i = 0; i < num_syms(); i++) {
|
||||
const elf_sym_t* sym = &__ld_dynsym_start[i];
|
||||
if (ELF_ST_TYPE(sym->st_info) != STT_FUNC || sym->st_shndx == SHN_UNDEF)
|
||||
continue;
|
||||
if (addr >= sym->st_value && addr - sym->st_value < sym->st_size) {
|
||||
const char* sym_name = dynstr(sym->st_name);
|
||||
if (sym_name == nullptr)
|
||||
return false;
|
||||
*name = sym_name;
|
||||
*offset = addr - sym->st_value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ksym_format(uintptr_t addr, char* buf, size_t size) {
|
||||
const char* name;
|
||||
size_t offset;
|
||||
if (ksym_lookup(addr, &name, &offset))
|
||||
ksnprintf(buf, size, "%s+0x%zx", name, offset);
|
||||
else
|
||||
ksnprintf(buf, size, "<\?\?\?>");
|
||||
}
|
||||
|
||||
[[__init]]
|
||||
static void ksyms_stage_init(struct boot_info*) {
|
||||
ksym_init();
|
||||
}
|
||||
INIT_TASK(ksyms_stage, "core.ksyms", ksyms_stage_init);
|
||||
INIT_DEPENDS(ksyms_stage, mem_stage);
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
#include <stdint.h>
|
||||
#include <zinnia/stdlib.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
static void reverse(char* s) {
|
||||
int i, j;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
+17
-8
@@ -1,9 +1,11 @@
|
||||
#define pr_fmt(fmt) "mem: " fmt
|
||||
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/assert.h>
|
||||
#include <zinnia/console.h>
|
||||
#include <zinnia/init.h>
|
||||
#include <zinnia/panic.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <zinnia/vma.h>
|
||||
|
||||
@@ -23,7 +25,7 @@ void mem_init(struct phys_mem* map, size_t map_len, uintptr_t kernel_virt, phys_
|
||||
|
||||
const size_t pgsz = arch_mem_page_size();
|
||||
|
||||
log("mem: Memory map:\n");
|
||||
log("Memory map:\n");
|
||||
for (size_t i = 0; i < map_len; i++) {
|
||||
if (map[i].length == 0)
|
||||
continue;
|
||||
@@ -44,7 +46,7 @@ void mem_init(struct phys_mem* map, size_t map_len, uintptr_t kernel_virt, phys_
|
||||
break;
|
||||
}
|
||||
|
||||
log("mem: [%p - %p] %s\n", (void*)map[i].address, (void*)(map[i].address + map[i].length - 1), label);
|
||||
log("[%p - %p] %s\n", (void*)map[i].address, (void*)(map[i].address + map[i].length - 1), label);
|
||||
}
|
||||
|
||||
// Set up the bootstrap allocator.
|
||||
@@ -55,7 +57,7 @@ void mem_init(struct phys_mem* map, size_t map_len, uintptr_t kernel_virt, phys_
|
||||
}
|
||||
assert(largest && largest->length >= pgsz, "No usable memory for bootstrap allocator");
|
||||
mem_phys_bootstrap(largest);
|
||||
log("mem: Using region [%p - %p] for bootstrap allocator\n",
|
||||
log("Using region [%p - %p] for bootstrap allocator\n",
|
||||
(void*)largest->address,
|
||||
(void*)(largest->address + largest->length - 1));
|
||||
|
||||
@@ -68,7 +70,7 @@ void mem_init(struct phys_mem* map, size_t map_len, uintptr_t kernel_virt, phys_
|
||||
tailq_init(&kernel_vma.mappings);
|
||||
|
||||
// text
|
||||
log("mem: Mapping text segment at %p\n", __ld_text_start);
|
||||
log("Mapping text segment at %p\n", __ld_text_start);
|
||||
for (uint8_t* p = __ld_text_start; p < __ld_text_end; p += pgsz) {
|
||||
errno_t status = pmap_map(
|
||||
&kernel_vma.pmap,
|
||||
@@ -81,7 +83,7 @@ void mem_init(struct phys_mem* map, size_t map_len, uintptr_t kernel_virt, phys_
|
||||
}
|
||||
|
||||
// rodata
|
||||
log("mem: Mapping rodata segment at %p\n", __ld_rodata_start);
|
||||
log("Mapping rodata segment at %p\n", __ld_rodata_start);
|
||||
for (uint8_t* p = __ld_rodata_start; p < __ld_rodata_end; p += pgsz) {
|
||||
errno_t status = pmap_map(
|
||||
&kernel_vma.pmap,
|
||||
@@ -94,7 +96,7 @@ void mem_init(struct phys_mem* map, size_t map_len, uintptr_t kernel_virt, phys_
|
||||
}
|
||||
|
||||
// data
|
||||
log("mem: Mapping data segment at %p\n", __ld_data_start);
|
||||
log("Mapping data segment at %p\n", __ld_data_start);
|
||||
for (uint8_t* p = __ld_data_start; p < __ld_data_end; p += pgsz) {
|
||||
errno_t status = pmap_map(
|
||||
&kernel_vma.pmap,
|
||||
@@ -165,5 +167,12 @@ void mem_init(struct phys_mem* map, size_t map_len, uintptr_t kernel_virt, phys_
|
||||
|
||||
slab_init();
|
||||
|
||||
log("mem: Memory initialization complete\n");
|
||||
log("Memory initialization complete\n");
|
||||
}
|
||||
|
||||
[[__init]]
|
||||
static void mem_stage_init(struct boot_info* info) {
|
||||
mem_init(info->mem_map, info->num_mem_maps, info->virt_base, info->phys_base, info->hhdm_base);
|
||||
}
|
||||
INIT_TASK(mem_stage, "core.mem", mem_stage_init);
|
||||
INIT_DEPENDS(mem_stage, console_stage);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include <zinnia/errno.h>
|
||||
#include <zinnia/pmap.h>
|
||||
#include <zinnia/spin.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <zinnia/vma.h>
|
||||
#include <config.h>
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <zinnia/panic.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/spin.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <zinnia/virt.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/errno.h>
|
||||
#include <zinnia/spin.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
size_t num_pages; // Amount of pages connected to this slab.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/errno.h>
|
||||
#include <zinnia/list.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <zinnia/vmo.h>
|
||||
|
||||
|
||||
+3
-1
@@ -1,13 +1,15 @@
|
||||
kernel_sources += files(
|
||||
'clock.c',
|
||||
'console.c',
|
||||
'fbcon.c',
|
||||
'initgraph.c',
|
||||
'irq.c',
|
||||
'ksyms.c',
|
||||
'module.c',
|
||||
'panic.c',
|
||||
'percpu.c',
|
||||
'print.c',
|
||||
'spin.c',
|
||||
'wait.c',
|
||||
)
|
||||
|
||||
subdir('lib')
|
||||
|
||||
+603
-2
@@ -1,12 +1,613 @@
|
||||
#define pr_fmt(fmt) "module: " fmt
|
||||
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/assert.h>
|
||||
#include <zinnia/cmdline.h>
|
||||
#include <zinnia/elf.h>
|
||||
#include <zinnia/ksyms.h>
|
||||
#include <zinnia/module.h>
|
||||
#include <zinnia/pmap.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/sched.h>
|
||||
#include <zinnia/spin.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <zinnia/virt.h>
|
||||
#include <zinnia/vma.h>
|
||||
#include <bits/elf.h>
|
||||
#include <stdckdint.h>
|
||||
#include <string.h>
|
||||
|
||||
extern errno_t (*__ld_mod_entry_start[])(struct module*);
|
||||
extern errno_t (*__ld_mod_entry_end[])(struct module*);
|
||||
|
||||
void module_init() {
|
||||
static struct spinlock module_lock;
|
||||
static tailq_head(struct module) module_list;
|
||||
|
||||
static bool in_range(size_t total, uint64_t offset, uint64_t size) {
|
||||
return offset <= total && size <= total - offset;
|
||||
}
|
||||
|
||||
static void* image_ptr(struct module* mod, uint64_t vaddr, uint64_t size) {
|
||||
uintptr_t addr, end;
|
||||
if (ckd_add(&addr, mod->bias, vaddr) || ckd_add(&end, addr, size))
|
||||
return nullptr;
|
||||
|
||||
for (size_t i = 0; i < mod->num_segments; i++) {
|
||||
const struct module_segment* seg = &mod->segments[i];
|
||||
if (addr >= seg->virt && end <= seg->virt + seg->size)
|
||||
return (void*)addr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static const char* image_cstr(struct module* mod, uint64_t vaddr, uint64_t size) {
|
||||
const char* str = image_ptr(mod, vaddr, size);
|
||||
if (str == nullptr || size == 0 || strnlen(str, size) >= size)
|
||||
return nullptr;
|
||||
return str;
|
||||
}
|
||||
|
||||
static const char* table_str(const char* table, size_t size, uint64_t offset) {
|
||||
if (offset >= size)
|
||||
return nullptr;
|
||||
const char* str = table + offset;
|
||||
if (strnlen(str, size - offset) >= size - offset)
|
||||
return nullptr;
|
||||
return str;
|
||||
}
|
||||
|
||||
static const char* sym_name(struct module* mod, uint64_t offset) {
|
||||
return table_str(mod->strtab, mod->strtab_size, offset);
|
||||
}
|
||||
|
||||
static struct module* find_locked(const char* name) {
|
||||
for (struct module* m = tailq_first(&module_list); m != nullptr; m = tailq_next(m, link)) {
|
||||
if (!strcmp(m->name, name))
|
||||
return m;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void module_init(struct boot_info*) {
|
||||
spin_init(&module_lock);
|
||||
tailq_init(&module_list);
|
||||
|
||||
for (auto start = __ld_mod_entry_start; start < __ld_mod_entry_end; start += 1) {
|
||||
errno_t mod_status = (*start)(nullptr);
|
||||
assert(mod_status == EOK, "Built-in entry point returned %i", mod_status);
|
||||
assert(mod_status == 0, "Built-in entry point returned %i", mod_status);
|
||||
}
|
||||
}
|
||||
INIT_TASK(module_stage, "core.module", module_init);
|
||||
INIT_DEPENDS(module_stage, sched_stage, ksyms_stage);
|
||||
|
||||
static errno_t resolve_symbol(struct module* mod, uint32_t index, uintptr_t* out) {
|
||||
if (index >= mod->num_syms)
|
||||
return ENOEXEC;
|
||||
|
||||
const elf_sym_t* sym = &((const elf_sym_t*)mod->symtab)[index];
|
||||
if (sym->st_shndx != SHN_UNDEF) {
|
||||
*out = mod->bias + sym->st_value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* name = sym_name(mod, sym->st_name);
|
||||
if (name == nullptr)
|
||||
return ENOEXEC;
|
||||
|
||||
if (ksym_find(name, out))
|
||||
return 0;
|
||||
|
||||
if (ELF_ST_BIND(sym->st_info) == STB_WEAK) {
|
||||
*out = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
log_err("%s: Unresolved symbol \"%s\"\n", mod->name, name);
|
||||
return ENOENT;
|
||||
}
|
||||
|
||||
static errno_t store_word(struct module* mod, uint64_t vaddr, uintptr_t value) {
|
||||
uintptr_t* where = image_ptr(mod, vaddr, sizeof(uintptr_t));
|
||||
if (where == nullptr)
|
||||
return ENOEXEC;
|
||||
*where = value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static errno_t relocate_relative(struct module* mod, uint64_t vaddr) {
|
||||
uintptr_t* where = image_ptr(mod, vaddr, sizeof(uintptr_t));
|
||||
if (where == nullptr)
|
||||
return ENOEXEC;
|
||||
*where += mod->bias;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static errno_t apply_rela(struct module* mod, uint64_t addr, uint64_t size, uint64_t entsize) {
|
||||
if (entsize == 0)
|
||||
entsize = sizeof(elf_rela_t);
|
||||
if (entsize < sizeof(elf_rela_t))
|
||||
return ENOEXEC;
|
||||
|
||||
const uint8_t* table = image_ptr(mod, addr, size);
|
||||
if (table == nullptr)
|
||||
return ENOEXEC;
|
||||
|
||||
for (uint64_t off = 0; off + sizeof(elf_rela_t) <= size; off += entsize) {
|
||||
elf_rela_t rela;
|
||||
memcpy(&rela, table + off, sizeof(rela));
|
||||
|
||||
errno_t status = 0;
|
||||
uintptr_t value;
|
||||
switch (ELF_R_TYPE(rela.r_info)) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_64:
|
||||
case R_GLOB_DAT:
|
||||
case R_JUMP_SLOT:
|
||||
status = resolve_symbol(mod, ELF_R_SYM(rela.r_info), &value);
|
||||
if (status == 0)
|
||||
status = store_word(mod, rela.r_offset, value + rela.r_addend);
|
||||
break;
|
||||
case R_RELATIVE:
|
||||
status = store_word(mod, rela.r_offset, mod->bias + rela.r_addend);
|
||||
break;
|
||||
default:
|
||||
log_err("%s: Unsupported relocation type %u\n", mod->name, ELF_R_TYPE(rela.r_info));
|
||||
status = ENOEXEC;
|
||||
break;
|
||||
}
|
||||
|
||||
if (status)
|
||||
return status;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static errno_t apply_relr(struct module* mod, uint64_t addr, uint64_t size, uint64_t entsize) {
|
||||
if (entsize != 0 && entsize != sizeof(elf_relr_t))
|
||||
return ENOEXEC;
|
||||
|
||||
const elf_relr_t* table = image_ptr(mod, addr, size);
|
||||
if (table == nullptr)
|
||||
return ENOEXEC;
|
||||
|
||||
uint64_t where = 0;
|
||||
for (size_t i = 0; i < size / sizeof(elf_relr_t); i++) {
|
||||
elf_relr_t entry = table[i];
|
||||
errno_t status;
|
||||
|
||||
if ((entry & 1) == 0) {
|
||||
status = relocate_relative(mod, entry);
|
||||
if (status)
|
||||
return status;
|
||||
where = entry + sizeof(elf_relr_t);
|
||||
} else {
|
||||
for (size_t bit = 0; (entry >>= 1) != 0; bit++) {
|
||||
if ((entry & 1) == 0)
|
||||
continue;
|
||||
status = relocate_relative(mod, where + bit * sizeof(elf_relr_t));
|
||||
if (status)
|
||||
return status;
|
||||
}
|
||||
where += (sizeof(elf_relr_t) * 8 - 1) * sizeof(elf_relr_t);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void module_free(struct module* mod) {
|
||||
const size_t page_size = arch_mem_page_size();
|
||||
|
||||
mem_free(mod->cmdline);
|
||||
|
||||
for (size_t i = 0; i < mod->num_segments; i++) {
|
||||
const struct module_segment* seg = &mod->segments[i];
|
||||
for (size_t off = 0; off < seg->size; off += page_size)
|
||||
pmap_unmap(&kernel_vma.pmap, seg->virt + off);
|
||||
mem_phys_free(seg->phys, seg->size / page_size);
|
||||
}
|
||||
mem_free(mod->segments);
|
||||
|
||||
if (mod->base != 0)
|
||||
vm_free(mod->base, mod->size);
|
||||
|
||||
mem_free(mod);
|
||||
}
|
||||
|
||||
static enum pte_flags prot_from_phdr(const elf_phdr_t* phdr) {
|
||||
enum pte_flags prot = 0;
|
||||
if (phdr->p_flags & PF_R)
|
||||
prot |= PTE_READ;
|
||||
if (phdr->p_flags & PF_W)
|
||||
prot |= PTE_WRITE;
|
||||
if (phdr->p_flags & PF_X)
|
||||
prot |= PTE_EXEC;
|
||||
return prot;
|
||||
}
|
||||
|
||||
static errno_t read_headers(const void* data, size_t length, elf_ehdr_t* ehdr, elf_phdr_t** out) {
|
||||
const uint8_t* file = data;
|
||||
|
||||
if (data == nullptr || length < sizeof(elf_ehdr_t))
|
||||
return ENOEXEC;
|
||||
|
||||
memcpy(ehdr, file, sizeof(*ehdr));
|
||||
|
||||
if (memcmp(ehdr->e_ident, ELF_MAG, sizeof(ELF_MAG)) != 0 || ehdr->e_ident[EI_VERSION] != EV_CURRENT ||
|
||||
ehdr->e_ident[EI_CLASS] != ELF_ARCH_CLASS || ehdr->e_ident[EI_DATA] != ELF_ARCH_DATA ||
|
||||
ehdr->e_machine != ELF_ARCH_MACHINE || ehdr->e_type != ET_DYN)
|
||||
return ENOEXEC;
|
||||
|
||||
if (ehdr->e_phentsize < sizeof(elf_phdr_t) || ehdr->e_phnum == 0 ||
|
||||
!in_range(length, ehdr->e_phoff, (uint64_t)ehdr->e_phnum * ehdr->e_phentsize))
|
||||
return ENOEXEC;
|
||||
|
||||
elf_phdr_t* phdrs = mem_alloc(ehdr->e_phnum * sizeof(elf_phdr_t), 0);
|
||||
if (phdrs == nullptr)
|
||||
return ENOMEM;
|
||||
for (size_t i = 0; i < ehdr->e_phnum; i++)
|
||||
memcpy(&phdrs[i], file + ehdr->e_phoff + i * ehdr->e_phentsize, sizeof(elf_phdr_t));
|
||||
|
||||
*out = phdrs;
|
||||
return 0;
|
||||
}
|
||||
|
||||
errno_t module_load(const void* data, size_t length, const char* cmdline, struct module** out) {
|
||||
const uint8_t* file = data;
|
||||
const size_t page_size = arch_mem_page_size();
|
||||
|
||||
elf_ehdr_t ehdr;
|
||||
elf_phdr_t* phdrs;
|
||||
errno_t header_status = read_headers(data, length, &ehdr, &phdrs);
|
||||
if (header_status)
|
||||
return header_status;
|
||||
|
||||
struct module* mod = mem_alloc(sizeof(struct module), 0);
|
||||
if (mod == nullptr) {
|
||||
mem_free(phdrs);
|
||||
return ENOMEM;
|
||||
}
|
||||
mod->version = "";
|
||||
mod->author = "";
|
||||
mod->desc = "";
|
||||
mod->license = "";
|
||||
|
||||
errno_t status = ENOEXEC;
|
||||
bool listed = false;
|
||||
|
||||
uintptr_t load_min = UINTPTR_MAX;
|
||||
uintptr_t load_end = 0;
|
||||
size_t num_loads = 0;
|
||||
for (size_t i = 0; i < ehdr.e_phnum; i++) {
|
||||
const elf_phdr_t* phdr = &phdrs[i];
|
||||
if (phdr->p_type != PT_LOAD || phdr->p_memsz == 0)
|
||||
continue;
|
||||
if (phdr->p_filesz > phdr->p_memsz || !in_range(length, phdr->p_offset, phdr->p_filesz))
|
||||
goto fail;
|
||||
|
||||
uintptr_t end;
|
||||
if (ckd_add(&end, phdr->p_vaddr, phdr->p_memsz) || end > UINTPTR_MAX - page_size)
|
||||
goto fail;
|
||||
|
||||
const uintptr_t start = ALIGN_DOWN(phdr->p_vaddr, page_size);
|
||||
if (start < load_end)
|
||||
goto fail;
|
||||
if (load_min == UINTPTR_MAX)
|
||||
load_min = start;
|
||||
load_end = ALIGN_UP(end, page_size);
|
||||
num_loads++;
|
||||
}
|
||||
|
||||
if (num_loads == 0)
|
||||
goto fail;
|
||||
|
||||
status = ENOMEM;
|
||||
mod->segments = mem_alloc(num_loads * sizeof(struct module_segment), 0);
|
||||
if (mod->segments == nullptr)
|
||||
goto fail;
|
||||
|
||||
mod->base = vm_alloc(load_end - load_min);
|
||||
if (mod->base == 0)
|
||||
goto fail;
|
||||
mod->size = load_end - load_min;
|
||||
mod->bias = mod->base - load_min;
|
||||
|
||||
for (size_t i = 0; i < ehdr.e_phnum; i++) {
|
||||
const elf_phdr_t* phdr = &phdrs[i];
|
||||
if (phdr->p_type != PT_LOAD || phdr->p_memsz == 0)
|
||||
continue;
|
||||
|
||||
const uintptr_t start = ALIGN_DOWN(phdr->p_vaddr, page_size);
|
||||
const size_t size = ALIGN_UP(phdr->p_vaddr + phdr->p_memsz, page_size) - start;
|
||||
|
||||
phys_t phys;
|
||||
status = mem_phys_alloc(size / page_size, 0, &phys);
|
||||
if (status)
|
||||
goto fail;
|
||||
|
||||
struct module_segment* seg = &mod->segments[mod->num_segments++];
|
||||
*seg = (struct module_segment){.phys = phys, .virt = mod->bias + start, .size = size};
|
||||
|
||||
for (size_t off = 0; off < size; off += page_size) {
|
||||
status = pmap_map(&kernel_vma.pmap, seg->virt + off, phys + off, PTE_READ | PTE_WRITE, CACHE_WRITE_BACK);
|
||||
if (status)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
memcpy((void*)(mod->bias + phdr->p_vaddr), file + phdr->p_offset, phdr->p_filesz);
|
||||
}
|
||||
|
||||
status = ENOEXEC;
|
||||
|
||||
const elf_dyn_t* dyn = nullptr;
|
||||
size_t num_dyn = 0;
|
||||
for (size_t i = 0; i < ehdr.e_phnum; i++) {
|
||||
if (phdrs[i].p_type != PT_DYNAMIC)
|
||||
continue;
|
||||
dyn = image_ptr(mod, phdrs[i].p_vaddr, phdrs[i].p_memsz);
|
||||
num_dyn = phdrs[i].p_memsz / sizeof(elf_dyn_t);
|
||||
break;
|
||||
}
|
||||
if (dyn == nullptr)
|
||||
goto fail;
|
||||
|
||||
uint64_t dt_strtab = 0, dt_strsz = 0, dt_symtab = 0, dt_hash = 0, dt_soname = 0;
|
||||
uint64_t dt_rela = 0, dt_relasz = 0, dt_relaent = 0;
|
||||
uint64_t dt_jmprel = 0, dt_pltrelsz = 0, dt_pltrel = DT_RELA;
|
||||
uint64_t dt_relr = 0, dt_relrsz = 0, dt_relrent = 0;
|
||||
bool has_strtab = false, has_symtab = false, has_hash = false, has_soname = false;
|
||||
bool has_rela = false, has_jmprel = false, has_relr = false, has_needed = false;
|
||||
|
||||
for (size_t i = 0; i < num_dyn && dyn[i].d_tag != DT_NULL; i++) {
|
||||
const uint64_t val = dyn[i].d_un.d_val;
|
||||
switch (dyn[i].d_tag) {
|
||||
case DT_STRTAB:
|
||||
dt_strtab = val;
|
||||
has_strtab = true;
|
||||
break;
|
||||
case DT_STRSZ:
|
||||
dt_strsz = val;
|
||||
break;
|
||||
case DT_SYMTAB:
|
||||
dt_symtab = val;
|
||||
has_symtab = true;
|
||||
break;
|
||||
case DT_HASH:
|
||||
dt_hash = val;
|
||||
has_hash = true;
|
||||
break;
|
||||
case DT_SONAME:
|
||||
dt_soname = val;
|
||||
has_soname = true;
|
||||
break;
|
||||
case DT_NEEDED:
|
||||
has_needed = true;
|
||||
break;
|
||||
case DT_RELA:
|
||||
dt_rela = val;
|
||||
has_rela = true;
|
||||
break;
|
||||
case DT_RELASZ:
|
||||
dt_relasz = val;
|
||||
break;
|
||||
case DT_RELAENT:
|
||||
dt_relaent = val;
|
||||
break;
|
||||
case DT_JMPREL:
|
||||
dt_jmprel = val;
|
||||
has_jmprel = true;
|
||||
break;
|
||||
case DT_PLTRELSZ:
|
||||
dt_pltrelsz = val;
|
||||
break;
|
||||
case DT_PLTREL:
|
||||
dt_pltrel = val;
|
||||
break;
|
||||
case DT_RELR:
|
||||
dt_relr = val;
|
||||
has_relr = true;
|
||||
break;
|
||||
case DT_RELRSZ:
|
||||
dt_relrsz = val;
|
||||
break;
|
||||
case DT_RELRENT:
|
||||
dt_relrent = val;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_strtab || !has_symtab || !has_hash || !has_soname || dt_strsz == 0 || dt_pltrel != DT_RELA)
|
||||
goto fail;
|
||||
|
||||
const elf_hash_t* hash = image_ptr(mod, dt_hash, sizeof(elf_hash_t));
|
||||
if (hash == nullptr)
|
||||
goto fail;
|
||||
|
||||
mod->num_syms = hash->n_chain;
|
||||
mod->symtab = image_ptr(mod, dt_symtab, (uint64_t)mod->num_syms * sizeof(elf_sym_t));
|
||||
mod->strtab = image_ptr(mod, dt_strtab, dt_strsz);
|
||||
mod->strtab_size = dt_strsz;
|
||||
if (mod->symtab == nullptr || mod->strtab == nullptr) {
|
||||
mod->num_syms = 0;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mod->name = sym_name(mod, dt_soname);
|
||||
if (mod->name == nullptr || *mod->name == 0)
|
||||
goto fail;
|
||||
|
||||
for (size_t i = 0; has_needed && i < num_dyn && dyn[i].d_tag != DT_NULL; i++) {
|
||||
if (dyn[i].d_tag != DT_NEEDED)
|
||||
continue;
|
||||
|
||||
// TODO: Modules can technically claim they have dependencies on other modules, but we don't support that yet.
|
||||
const char* needed = sym_name(mod, dyn[i].d_un.d_val);
|
||||
const char* kernel_name = ksym_kernel_name();
|
||||
if (needed == nullptr || kernel_name == nullptr || strcmp(needed, kernel_name) != 0) {
|
||||
log_err("%s: Modules can only depend on the kernel, not \"%s\"\n", mod->name, needed ? needed : "?");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_relr) {
|
||||
status = apply_relr(mod, dt_relr, dt_relrsz, dt_relrent);
|
||||
if (status)
|
||||
goto fail;
|
||||
}
|
||||
if (has_rela) {
|
||||
status = apply_rela(mod, dt_rela, dt_relasz, dt_relaent);
|
||||
if (status)
|
||||
goto fail;
|
||||
}
|
||||
if (has_jmprel) {
|
||||
status = apply_rela(mod, dt_jmprel, dt_pltrelsz, dt_relaent);
|
||||
if (status)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
spin_lock(&module_lock);
|
||||
if (find_locked(mod->name) != nullptr) {
|
||||
spin_unlock(&module_lock);
|
||||
status = EEXIST;
|
||||
goto fail;
|
||||
}
|
||||
tailq_insert_tail(&module_list, mod, link);
|
||||
listed = true;
|
||||
spin_unlock(&module_lock);
|
||||
|
||||
const struct cmdline_option* const* opts_start = nullptr;
|
||||
const struct cmdline_option* const* opts_end = nullptr;
|
||||
|
||||
status = ENOEXEC;
|
||||
for (size_t i = 0; i < ehdr.e_phnum; i++) {
|
||||
const elf_phdr_t* phdr = &phdrs[i];
|
||||
const char* str;
|
||||
switch (phdr->p_type) {
|
||||
case PT_MOD_VERSION:
|
||||
if ((str = image_cstr(mod, phdr->p_vaddr, phdr->p_memsz)) != nullptr)
|
||||
mod->version = str;
|
||||
break;
|
||||
case PT_MOD_AUTHOR:
|
||||
if ((str = image_cstr(mod, phdr->p_vaddr, phdr->p_memsz)) != nullptr)
|
||||
mod->author = str;
|
||||
break;
|
||||
case PT_MOD_DESC:
|
||||
if ((str = image_cstr(mod, phdr->p_vaddr, phdr->p_memsz)) != nullptr)
|
||||
mod->desc = str;
|
||||
break;
|
||||
case PT_MOD_LICENSE:
|
||||
if ((str = image_cstr(mod, phdr->p_vaddr, phdr->p_memsz)) != nullptr)
|
||||
mod->license = str;
|
||||
break;
|
||||
case PT_MOD_CMDLINE:
|
||||
if (phdr->p_memsz < sizeof(void*))
|
||||
break;
|
||||
opts_start = image_ptr(mod, phdr->p_vaddr, phdr->p_memsz);
|
||||
if (opts_start == nullptr)
|
||||
goto fail;
|
||||
opts_end = opts_start + (phdr->p_memsz / sizeof(void*));
|
||||
break;
|
||||
case PT_MOD_EXIT: {
|
||||
if (phdr->p_memsz < sizeof(void*))
|
||||
break;
|
||||
const void* slot = image_ptr(mod, phdr->p_vaddr, sizeof(void*));
|
||||
if (slot == nullptr)
|
||||
goto fail;
|
||||
memcpy(&mod->exit, slot, sizeof(void*));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (image_ptr(mod, ehdr.e_entry, 1) == nullptr)
|
||||
goto fail;
|
||||
|
||||
size_t seg_idx = 0;
|
||||
for (size_t i = 0; i < ehdr.e_phnum; i++) {
|
||||
const elf_phdr_t* phdr = &phdrs[i];
|
||||
if (phdr->p_type != PT_LOAD || phdr->p_memsz == 0)
|
||||
continue;
|
||||
|
||||
const struct module_segment* seg = &mod->segments[seg_idx++];
|
||||
const enum pte_flags prot = prot_from_phdr(phdr);
|
||||
for (size_t off = 0; off < seg->size; off += page_size) {
|
||||
pmap_unmap(&kernel_vma.pmap, seg->virt + off);
|
||||
status = pmap_map(&kernel_vma.pmap, seg->virt + off, seg->phys + off, prot, CACHE_WRITE_BACK);
|
||||
if (status)
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmdline == nullptr)
|
||||
cmdline = "";
|
||||
const size_t cmdline_len = strlen(cmdline);
|
||||
mod->cmdline = mem_alloc(cmdline_len + 1, 0);
|
||||
if (mod->cmdline == nullptr) {
|
||||
status = ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
memcpy(mod->cmdline, cmdline, cmdline_len);
|
||||
if (opts_start != nullptr)
|
||||
cmdline_parse_buf(mod->cmdline, cmdline_len, opts_start, opts_end);
|
||||
|
||||
log("Loaded module \"%s\" (%s, %s) at %p\n", mod->name, mod->desc, mod->version, (void*)mod->base);
|
||||
|
||||
errno_t (*entry)(struct module*) = (errno_t (*)(struct module*))(mod->bias + ehdr.e_entry);
|
||||
status = entry(mod);
|
||||
if (status) {
|
||||
log_err("%s: Entry point returned %i\n", mod->name, status);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mem_free(phdrs);
|
||||
|
||||
if (out != nullptr)
|
||||
*out = mod;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (listed) {
|
||||
spin_lock(&module_lock);
|
||||
tailq_remove(&module_list, mod, link);
|
||||
spin_unlock(&module_lock);
|
||||
}
|
||||
module_free(mod);
|
||||
mem_free(phdrs);
|
||||
return status;
|
||||
}
|
||||
|
||||
errno_t module_unload(struct module* mod) {
|
||||
if (mod == nullptr)
|
||||
return EINVAL;
|
||||
|
||||
if (mod->exit == nullptr)
|
||||
return ENOTSUP;
|
||||
|
||||
spin_lock(&module_lock);
|
||||
tailq_remove(&module_list, mod, link);
|
||||
spin_unlock(&module_lock);
|
||||
|
||||
errno_t status = mod->exit(mod);
|
||||
if (status) {
|
||||
log_err("%s: Exit function returned %i\n", mod->name, status);
|
||||
spin_lock(&module_lock);
|
||||
tailq_insert_tail(&module_list, mod, link);
|
||||
spin_unlock(&module_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
log("Unloaded module \"%s\"\n", mod->name);
|
||||
module_free(mod);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct module* module_find(const char* name) {
|
||||
spin_lock(&module_lock);
|
||||
struct module* mod = find_locked(name);
|
||||
spin_unlock(&module_lock);
|
||||
return mod;
|
||||
}
|
||||
|
||||
+36
-2
@@ -1,11 +1,42 @@
|
||||
#include <zinnia/alloc.h>
|
||||
#include <zinnia/irq.h>
|
||||
#include <zinnia/ksyms.h>
|
||||
#include <zinnia/panic.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define panic_log(msg, ...) kprintf("[ !!!! ] \e[31m" msg "\e[0m", ##__VA_ARGS__)
|
||||
|
||||
static void print_backtrace() {
|
||||
constexpr size_t max_depth = 32;
|
||||
void** fp = __builtin_frame_address(0);
|
||||
|
||||
panic_log("Backtrace:\n");
|
||||
for (size_t i = 0; i < max_depth; i++) {
|
||||
if (!fp || (uintptr_t)fp < mem_hhdm_base || (uintptr_t)fp % alignof(void*))
|
||||
break;
|
||||
|
||||
void* ret = fp[1];
|
||||
if (!ret)
|
||||
break;
|
||||
|
||||
const char* name;
|
||||
size_t offset;
|
||||
// The return address may already point past the end of a noreturn call.
|
||||
if (ksym_lookup((uintptr_t)ret - 1, &name, &offset))
|
||||
panic_log(" #%zu [%p] %s+0x%zx\n", i, ret, name, offset + 1);
|
||||
else
|
||||
panic_log(" #%zu [%p] <\?\?\?>\n", i, ret);
|
||||
|
||||
void** next = fp[0];
|
||||
if (next <= fp)
|
||||
break;
|
||||
fp = next;
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
void panic(const char* msg, ...) {
|
||||
// TODO: Stop all other CPUs with IPI.
|
||||
@@ -15,12 +46,14 @@ void panic(const char* msg, ...) {
|
||||
struct task* current = cpu->sched.current;
|
||||
kprintf(
|
||||
"[ !!!! ] \e[31m"
|
||||
"Kernel panic on CPU %zu (T%zu, \"%s\"): ",
|
||||
"Kernel panic on CPU %zu (T%zu, \"%s\"):\e[0m\n",
|
||||
cpu->id,
|
||||
current ? current->id : 0,
|
||||
current ? current->name : nullptr
|
||||
);
|
||||
|
||||
kprintf("[ !!!! ] \e[31m");
|
||||
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
kvprintf(msg, args);
|
||||
@@ -28,9 +61,10 @@ void panic(const char* msg, ...) {
|
||||
|
||||
kprintf("\e[0m");
|
||||
|
||||
print_backtrace();
|
||||
panic_log("End of panic message\n");
|
||||
|
||||
while (1) {
|
||||
while (true) {
|
||||
irq_wait();
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -2,7 +2,6 @@
|
||||
#include <zinnia/cmdline.h>
|
||||
#include <zinnia/compiler.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <zinnia/utils.h>
|
||||
#include <arch/percpu.h>
|
||||
#include <stdatomic.h>
|
||||
@@ -55,6 +54,8 @@ void percpu_bsp_init() {
|
||||
arch_percpu_bsp_init();
|
||||
}
|
||||
|
||||
void percpu_init() {
|
||||
void percpu_init(struct boot_info*) {
|
||||
arch_percpu_init();
|
||||
}
|
||||
INIT_TASK(percpu_stage, "core.percpu", percpu_init);
|
||||
INIT_DEPENDS(percpu_stage, mem_stage);
|
||||
|
||||
+149
-6
@@ -1,22 +1,22 @@
|
||||
#include <zinnia/clock.h>
|
||||
#include <zinnia/console.h>
|
||||
#include <zinnia/panic.h>
|
||||
#include <zinnia/percpu.h>
|
||||
#include <zinnia/print.h>
|
||||
#include <zinnia/sched.h>
|
||||
#include <zinnia/spin.h>
|
||||
#include <zinnia/stdlib.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef void (*log_fn_t)(void* ctx, const char* buf, size_t len);
|
||||
#include <string.h>
|
||||
|
||||
static void print_pad(log_fn_t callback, void* ctx, char c, size_t count) {
|
||||
for (size_t i = 0; i < count; i++)
|
||||
callback(ctx, &c, 1);
|
||||
}
|
||||
|
||||
static void do_print(log_fn_t callback, void* ctx, const char* restrict fmt, va_list args) {
|
||||
void print_fmt(log_fn_t callback, void* ctx, const char* restrict fmt, va_list args) {
|
||||
// Amount of bytes written.
|
||||
int32_t written = 0;
|
||||
|
||||
@@ -320,16 +320,159 @@ void kprintf(const char* message, ...) {
|
||||
|
||||
va_list args;
|
||||
va_start(args, message);
|
||||
do_print(printf_console, nullptr, message, args);
|
||||
print_fmt(printf_console, nullptr, message, args);
|
||||
va_end(args);
|
||||
|
||||
spin_unlock(&print_lock);
|
||||
}
|
||||
|
||||
struct snprintf_ctx {
|
||||
char* buf;
|
||||
size_t size;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
static void snprintf_write(void* ctx, const char* str, size_t len) {
|
||||
struct snprintf_ctx* c = ctx;
|
||||
// Always keep room for the terminator.
|
||||
if (c->len < c->size) {
|
||||
size_t room = c->size - c->len - 1;
|
||||
memcpy(c->buf + c->len, str, len < room ? len : room);
|
||||
}
|
||||
c->len += len;
|
||||
}
|
||||
|
||||
size_t kvsnprintf(char* buf, size_t size, const char* format, va_list args) {
|
||||
struct snprintf_ctx ctx = {.buf = buf, .size = size};
|
||||
print_fmt(snprintf_write, &ctx, format, args);
|
||||
if (size > 0)
|
||||
buf[ctx.len < size ? ctx.len : size - 1] = '\0';
|
||||
return ctx.len;
|
||||
}
|
||||
|
||||
size_t ksnprintf(char* buf, size_t size, const char* format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
size_t len = kvsnprintf(buf, size, format, args);
|
||||
va_end(args);
|
||||
return len;
|
||||
}
|
||||
|
||||
void kvprintf(const char* message, va_list args) {
|
||||
spin_lock(&print_lock);
|
||||
|
||||
do_print(printf_console, nullptr, message, args);
|
||||
print_fmt(printf_console, nullptr, message, args);
|
||||
|
||||
spin_unlock(&print_lock);
|
||||
}
|
||||
|
||||
struct log_record {
|
||||
uint64_t time_ns;
|
||||
uint32_t tid;
|
||||
uint16_t len;
|
||||
uint8_t level;
|
||||
};
|
||||
|
||||
#define LOG_BUFFER_SIZE (64 * 1024)
|
||||
#define LOG_MESSAGE_MAX 255
|
||||
|
||||
static uint8_t log_buffer[LOG_BUFFER_SIZE];
|
||||
|
||||
// Protected by print_lock.
|
||||
static size_t log_head = 0;
|
||||
static size_t log_tail = 0;
|
||||
|
||||
static void log_ring_copy_in(const void* src, size_t len) {
|
||||
for (size_t i = 0; i < len; i++)
|
||||
log_buffer[(log_head + i) % LOG_BUFFER_SIZE] = ((const uint8_t*)src)[i];
|
||||
log_head += len;
|
||||
}
|
||||
|
||||
static void log_ring_peek(size_t offset, void* dst, size_t len) {
|
||||
for (size_t i = 0; i < len; i++)
|
||||
((uint8_t*)dst)[i] = log_buffer[(offset + i) % LOG_BUFFER_SIZE];
|
||||
}
|
||||
|
||||
static void log_store(const struct log_record* rec, const char* msg) {
|
||||
const size_t total = sizeof(*rec) + rec->len;
|
||||
|
||||
// Drop the oldest records until the new one fits.
|
||||
while (LOG_BUFFER_SIZE - (log_head - log_tail) < total) {
|
||||
struct log_record old;
|
||||
log_ring_peek(log_tail, &old, sizeof(old));
|
||||
log_tail += sizeof(old) + old.len;
|
||||
}
|
||||
|
||||
log_ring_copy_in(rec, sizeof(*rec));
|
||||
log_ring_copy_in(msg, rec->len);
|
||||
}
|
||||
|
||||
struct log_line {
|
||||
char buf[LOG_MESSAGE_MAX];
|
||||
size_t len;
|
||||
};
|
||||
|
||||
static void log_line_append(void* ctx, const char* str, size_t len) {
|
||||
struct log_line* line = ctx;
|
||||
if (len > sizeof(line->buf) - line->len)
|
||||
len = sizeof(line->buf) - line->len;
|
||||
memcpy(line->buf + line->len, str, len);
|
||||
line->len += len;
|
||||
}
|
||||
|
||||
// ANSI colors for each level.
|
||||
static const char* const log_level_colors[] = {
|
||||
[LOG_DEBUG] = "\e[90m",
|
||||
[LOG_STATUS] = "\e[32m",
|
||||
[LOG_INFO] = "",
|
||||
[LOG_WARN] = "\e[33m",
|
||||
[LOG_ERR] = "\e[31m",
|
||||
[LOG_PANIC] = "\e[1;31m",
|
||||
};
|
||||
|
||||
static void log_console_printf(const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
print_fmt(printf_console, nullptr, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void log_vwrite(enum log_level level, const char* message, va_list args) {
|
||||
log_vwrite_prefixed(level, "", message, args);
|
||||
}
|
||||
|
||||
void log_vwrite_prefixed(enum log_level level, const char* prefix, const char* message, va_list args) {
|
||||
struct log_line line = {};
|
||||
log_line_append(&line, prefix, strlen(prefix));
|
||||
print_fmt(log_line_append, &line, message, args);
|
||||
if (line.len > 0 && line.buf[line.len - 1] == '\n')
|
||||
line.len--;
|
||||
|
||||
auto cur = percpu_get()->sched.current;
|
||||
const struct log_record rec = {
|
||||
.time_ns = clock_get_elapsed_ns(),
|
||||
.tid = cur ? cur->id : 0,
|
||||
.len = line.len,
|
||||
.level = level,
|
||||
};
|
||||
|
||||
spin_lock(&print_lock);
|
||||
log_store(&rec, line.buf);
|
||||
log_console_printf(
|
||||
"[%5lu.%06lu] [%5lu] %s",
|
||||
rec.time_ns / 1'000'000'000,
|
||||
rec.time_ns / 1'000 % 1'000'000,
|
||||
(uint64_t)rec.tid,
|
||||
log_level_colors[level]
|
||||
);
|
||||
console_write(line.buf, line.len);
|
||||
console_write("\e[0m\n", 5);
|
||||
spin_unlock(&print_lock);
|
||||
}
|
||||
|
||||
void log_write(enum log_level level, const char* message, ...) {
|
||||
va_list args;
|
||||
va_start(args, message);
|
||||
log_vwrite(level, message, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
+29
-2
@@ -9,7 +9,6 @@
|
||||
#include <zinnia/sched.h>
|
||||
#include <zinnia/spin.h>
|
||||
#include <zinnia/stack.h>
|
||||
#include <zinnia/string.h>
|
||||
#include <zinnia/tailq.h>
|
||||
#include <zinnia/vma.h>
|
||||
#include <arch/sched.h>
|
||||
@@ -54,6 +53,7 @@ void sched_init(struct scheduler* sched) {
|
||||
|
||||
void sched_add_task(struct scheduler* sched, struct task* task) {
|
||||
assert(task != nullptr, "Tried to add NULL to the run queue\n");
|
||||
task->state = TASK_STATE_READY;
|
||||
tailq_insert_tail(&sched->run_queue, task, next);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ static struct task* next_task(struct scheduler* sched) {
|
||||
return sched->idle_task;
|
||||
|
||||
tailq_remove(&sched->run_queue, t, next);
|
||||
t->state = TASK_STATE_RUNNING;
|
||||
|
||||
return t;
|
||||
}
|
||||
@@ -110,6 +111,25 @@ void sched_yield(struct scheduler* sched) {
|
||||
do_reschedule(sched);
|
||||
}
|
||||
|
||||
void sched_block(struct scheduler* sched) {
|
||||
irq_lock();
|
||||
if (atomic_exchange_explicit(&sched->current->wake_pending, false, memory_order_acq_rel)) {
|
||||
irq_unlock();
|
||||
return;
|
||||
}
|
||||
sched->current->state = TASK_STATE_BLOCKED;
|
||||
do_reschedule(sched);
|
||||
}
|
||||
|
||||
void sched_wake(struct task* task) {
|
||||
irq_lock();
|
||||
if (task->state == TASK_STATE_BLOCKED)
|
||||
sched_add_task(&percpu_get()->sched, task);
|
||||
else
|
||||
atomic_store_explicit(&task->wake_pending, true, memory_order_release);
|
||||
irq_unlock();
|
||||
}
|
||||
|
||||
errno_t task_create(const char* name, struct vma* space, task_fn_t entry, uintptr_t arg0, struct task** out) {
|
||||
if (!out)
|
||||
return EINVAL;
|
||||
@@ -118,7 +138,7 @@ errno_t task_create(const char* name, struct vma* space, task_fn_t entry, uintpt
|
||||
if (new_task == nullptr)
|
||||
return ENOMEM;
|
||||
|
||||
static _Atomic(size_t) last_tid = 0;
|
||||
static atomic_size_t last_tid = 0;
|
||||
new_task->id = atomic_fetch_add_explicit(&last_tid, 1, memory_order_acquire);
|
||||
new_task->space = space;
|
||||
strncpy(new_task->name, name, sizeof(new_task->name));
|
||||
@@ -165,3 +185,10 @@ void task_entry(task_fn_t entry, uintptr_t arg0, struct task* prev) {
|
||||
sched_yield(sched);
|
||||
panic("unreachable");
|
||||
}
|
||||
|
||||
[[__init]]
|
||||
static void sched_stage_init(struct boot_info*) {
|
||||
sched_init(&percpu_get()->sched);
|
||||
}
|
||||
INIT_TASK(sched_stage, "core.sched", sched_stage_init);
|
||||
INIT_DEPENDS(sched_stage, percpu_stage);
|
||||
|
||||
Reference in New Issue
Block a user