calcite/src/kernel/entry.c

318 lines
9.9 KiB
C

/* Calcite, src/kernel/entry.c
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "framebuffer.h"
#include "interrupts.h"
#include "scheduler.h"
#include "syscalls.h"
#include "process.h"
#include "utility.h"
#include "drives.h"
#include "paging.h"
#include "serial.h"
#include "debug.h"
#include "input.h"
#include "timer.h"
#include "heap.h"
#include "pci.h"
#include "ps2.h"
#include "fs.h"
#include <kernel-public/syscall-numbers.h>
#include <limine.h>
LIMINE_BASE_REVISION(3)
static volatile struct limine_framebuffer_request fb_request = {
.id = LIMINE_FRAMEBUFFER_REQUEST,
.revision = 0,
.response = 0
};
static volatile struct limine_hhdm_request hhdm_request = {
.id = LIMINE_HHDM_REQUEST,
.revision = 0,
.response = 0
};
static volatile struct limine_kernel_address_request ka_request = {
.id = LIMINE_KERNEL_ADDRESS_REQUEST,
.revision = 0,
.response = 0
};
static volatile struct limine_memmap_request memmap_request = {
.id = LIMINE_MEMMAP_REQUEST,
.revision = 0,
.response = 0
};
static volatile struct limine_executable_cmdline_request cmdline_request = {
.id = LIMINE_EXECUTABLE_CMDLINE_REQUEST,
.revision = 0,
.response = 0
};
[[noreturn]] static void die() {
while (1)
__asm__ ("hlt");
}
//these are defined in the linker script. they are page-aligned.
extern uint8_t __kernel_rx_start;
extern uint8_t __kernel_rx_end;
extern uint8_t __kernel_ro_start;
extern uint8_t __kernel_ro_end;
extern uint8_t __kernel_rw_start;
extern uint8_t __kernel_rw_end;
static void map_kernel_region(
uint64_t physical_start, void *virtual_start,
uint64_t length, int writable, int executable) {
for (uint64_t i = 0; i < length; i += 4096)
map_in_kernel_page_table(
physical_start + i, (uint8_t *)virtual_start + i, writable, executable);
}
#define MAX_CMDLINE_LENGTH 1000
static char cmdline_copy[MAX_CMDLINE_LENGTH + 1];
[[noreturn]] static void with_kernel_page_tables();
[[noreturn]] void kernel_entry() {
//die if the bootloader hasn't given us something that we need.
if (fb_request.response == 0 || hhdm_request.response == 0 ||
ka_request.response == 0 || memmap_request.response == 0 ||
fb_request.response->framebuffer_count == 0)
die();
struct limine_framebuffer *fb = fb_request.response->framebuffers[0];
if (fb->memory_model != LIMINE_FRAMEBUFFER_RGB || fb->bpp != 32 ||
fb->red_mask_shift != 16 || fb->red_mask_size != 8 ||
fb->green_mask_shift != 8 || fb->green_mask_size != 8 ||
fb->blue_mask_shift != 0 || fb->blue_mask_size != 8)
die();
//set up page tables. we will mark the regions with bootloader structures as
//usable, so we need to be careful not to allocate any physical pages until
//after we are done using bootloader structures. we map the kernel into our
//page tables, and we remap the framebuffer to somewhere in kernel memory.
//we do not map the bootloader structures, so we also need to not switch
//to the new tables until we are done using them.
uint64_t kernel_physical_base = ka_request.response->physical_base;
uint8_t *kernel_virtual_base = (uint8_t *)ka_request.response->virtual_base;
init_paging(kernel_physical_base, kernel_virtual_base);
struct limine_memmap_response *mm_response = memmap_request.response;
for (uint64_t i = 0; i < mm_response->entry_count; ++i) {
struct limine_memmap_entry *entry = mm_response->entries[i];
if (entry->type == LIMINE_MEMMAP_USABLE ||
entry->type == LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE)
//limine guarantees these two types are page-aligned already.
mark_physical_memory_free(entry->base, entry->length);
}
map_kernel_region(
&__kernel_rx_start - kernel_virtual_base + kernel_physical_base,
&__kernel_rx_start, &__kernel_rx_end - &__kernel_rx_start, 0, 1);
map_kernel_region(
&__kernel_ro_start - kernel_virtual_base + kernel_physical_base,
&__kernel_ro_start, &__kernel_ro_end - &__kernel_ro_start, 0, 0);
map_kernel_region(
&__kernel_rw_start - kernel_virtual_base + kernel_physical_base,
&__kernel_rw_start, &__kernel_rw_end - &__kernel_rw_start, 1, 0);
//we round up to a multiple of a page.
uint64_t fb_length = ((fb->height * fb->pitch - 1) / 4096 + 1) * 4096;
fb_physical_base = (uint64_t)fb->address - hhdm_request.response->offset;
fb_base = find_free_kernel_region(fb_length);
map_kernel_region(
fb_physical_base, fb_base, fb_length, 1, 0);
//store rest of framebuffer information
fb_width = fb->width;
fb_height = fb->height;
fb_pitch = fb->pitch;
//make a copy of the kernel cmdline
const char *original_cmdline = cmdline_request.response->cmdline;
int cmdline_length = 0;
while (original_cmdline[cmdline_length] != 0)
++cmdline_length;
if (cmdline_length > MAX_CMDLINE_LENGTH)
die();
memcpy(cmdline_copy, original_cmdline, cmdline_length + 1);
//switch to kernel page tables!
switch_to_kernel_page_tables(&with_kernel_page_tables);
}
struct cmdline_pair {
const char *key;
const char *value;
};
static const struct cmdline_pair *cmdline_pairs;
static int cmdline_pair_count;
//returns the corresponding value if the key exists, otherwise returns 0.
static const char *cmdline_look_up(const char *key) {
for (int i = 0; i < cmdline_pair_count; ++i)
if (strequ(cmdline_pairs[i].key, key))
return cmdline_pairs[i].value;
return 0;
}
[[noreturn]] static void with_kernel_page_tables() {
init_serial();
//store cmdline as key-value pairs
if (cmdline_copy[0] == 0)
cmdline_pair_count = 0;
else {
int comma_count = 0;
for (int i = 0; cmdline_copy[i] != 0; ++i)
if (cmdline_copy[i] == ',')
++comma_count;
cmdline_pair_count = comma_count + 1;
struct cmdline_pair *pairs = heap_alloc(cmdline_pair_count * sizeof(struct cmdline_pair));
int key_start = 0;
for (int i = 0; i < cmdline_pair_count; ++i) {
int key_end = key_start;
while (1) {
if (cmdline_copy[key_end] == '=')
break;
if (cmdline_copy[key_end] == ',' || cmdline_copy[key_end] == 0)
die();
++key_end;
}
int value_start = key_end + 1;
int value_end = value_start;
while (1) {
if (cmdline_copy[value_end] == ',' || cmdline_copy[value_end] == 0)
break;
if (cmdline_copy[value_end] == '=')
die();
++value_end;
}
char *key = heap_alloc(key_end - key_start + 1);
memcpy(key, &cmdline_copy[key_start], key_end - key_start);
key[key_end - key_start] = 0;
pairs[i].key = key;
char *value = heap_alloc(value_end - value_start + 1);
memcpy(value, &cmdline_copy[value_start], value_end - value_start);
value[value_end - value_start] = 0;
pairs[i].value = value;
key_start = value_end + 1;
}
cmdline_pairs = pairs;
}
debug_log("command line:")
for (int i = 0; i < cmdline_pair_count; ++i)
debug_log(" %s = %s", cmdline_pairs[i].key, cmdline_pairs[i].value)
//set up interrupts
init_timer();
init_ps2();
set_irq_handler(0x00, &on_pit_irq);
set_irq_handler(0x01, &on_keyboard_irq);
set_irq_handler(0x0c, &on_mouse_irq);
enable_interrupts();
//set up syscalls
init_syscalls();
register_syscall(SYSCALL_END_THREAD, (void *)&syscall_end_thread);
register_syscall(SYSCALL_MAP_FRAMEBUFFER, (void *)&syscall_map_framebuffer);
register_syscall(SYSCALL_OPEN_FILE, (void *)&syscall_open_file);
register_syscall(SYSCALL_CLOSE_FILE, (void *)&syscall_close_file);
register_syscall(SYSCALL_GET_FILE_SIZE, (void *)&syscall_get_file_size);
register_syscall(SYSCALL_READ_FILE, (void *)&syscall_read_file);
register_syscall(SYSCALL_WAIT_FOR_MOUSE_PACKET, (void *)&syscall_wait_for_mouse_packet);
register_syscall(SYSCALL_MAP_PAGES, (void *)&syscall_map_pages);
register_syscall(SYSCALL_SLEEP_MS, (void *)&syscall_sleep_ms);
register_syscall(SYSCALL_START_ELF, (void *)&syscall_start_elf);
register_syscall(SYSCALL_IPC_CREATE_DGRAM_RECEIVER, (void *)&syscall_ipc_create_dgram_receiver);
register_syscall(SYSCALL_IPC_CREATE_DGRAM_SENDER, (void *)&syscall_ipc_create_dgram_sender);
register_syscall(SYSCALL_IPC_RECEIVE_DGRAM, (void *)&syscall_ipc_receive_dgram);
register_syscall(SYSCALL_IPC_SEND_DGRAM, (void *)&syscall_ipc_send_dgram);
register_syscall(SYSCALL_CREATE_THREAD, (void *)&syscall_create_thread);
register_syscall(SYSCALL_GET_ENVVAR, (void *)&syscall_get_envvar);
//probe pci devices
probe_pci();
//load root file system
const char *root_drive_name = cmdline_look_up("root-drive");
if (!root_drive_name)
panic("root drive mising from cmdline")
const struct drive_info *root_drive = look_up_drive(root_drive_name);
if (!root_drive)
panic("could not find root drive")
const char *root_fs_type = cmdline_look_up("root-fs");
if (!root_fs_type)
panic("root fs missing from cmdline")
struct fs_info *root_fs = heap_alloc(sizeof(struct fs_info));
if (create_fs_info(root_drive, root_fs, root_fs_type) != FAR_SUCCESS)
panic("could not create root file system info")
set_fs("root", root_fs);
//load init and start it
init_scheduler();
if (start_elf("root://calcite/apps/init/init.elf") == 0)
panic("could not start init.elf")
debug_log("about to switch to init")
debug_log(" free physical memory %B", count_free_pram())
debug_log(" free kernel virtual memory %B", count_free_kernel_vram())
resume_next_continuation();
}