Keyboard Work - setting up PLIC, UART, Interrupts

This commit is contained in:
Liam Kerr 2026-02-10 01:21:49 +00:00
parent 4c0a4958a2
commit 50757427be
12 changed files with 295 additions and 68 deletions

View file

@ -1,22 +1,20 @@
.section .text
.section .text.boot
.global _start
_start:
la sp, stack_top # Set up the stack pointer
la t0, trap_entry # Set up the trap handler
csrw mtvec, t0 # Set the trap vector to our trap handler
li t0, 0x00006000 # Load the mask for FS bits. enable float
csrs mstatus, t0 # Set the FS bits to 11 (Dirty/Initial). enable float
call kmain # Jump to our C code
# 1. Set up the stack pointer using the symbol from our linker script
la sp, stack_top
# 2. Set up the trap handler (pointing to the one in traps.S)
la t0, trap_entry
csrw mtvec, t0
# 3. Enable FPU (Floating Point)
li t0, 0x00006000
csrs mstatus, t0
# 4. Jump to C
call kmain
loop:
wfi # Wait for Interrupt (saves CPU)
j loop # Infinite loop if C returns
.align 4
trap_entry:
csrr a0, mcause # Argument 1: mcause
csrr a1, mepc # Argument 2: mepc
j handle_trap
.section .bss
.align 16
stack_low:
.skip 4096 # 4KB of stack space
stack_top:
wfi
j loop

83
src/boot/traps.S Normal file
View file

@ -0,0 +1,83 @@
# traps.S
.section .text
.align 4 # mtvec requires 4-byte alignment
.global trap_entry
trap_entry:
# 1. Create space on the stack for 32 registers (32 * 8 = 256 bytes)
addi sp, sp, -256
# 2. Save all General Purpose Registers (GPRs)
# We don't save x0 (zero) because it's always zero
sd ra, 0(sp)
sd gp, 8(sp)
sd tp, 16(sp)
sd t0, 24(sp)
sd t1, 32(sp)
sd t2, 40(sp)
sd s0, 48(sp)
sd s1, 56(sp)
sd a0, 64(sp)
sd a1, 72(sp)
sd a2, 80(sp)
sd a3, 88(sp)
sd a4, 96(sp)
sd a5, 104(sp)
sd a6, 112(sp)
sd a7, 120(sp)
sd s2, 128(sp)
sd s3, 136(sp)
sd s4, 144(sp)
sd s5, 152(sp)
sd s6, 160(sp)
sd s7, 168(sp)
sd s8, 176(sp)
sd s9, 184(sp)
sd s10, 192(sp)
sd s11, 200(sp)
sd t3, 208(sp)
sd t4, 216(sp)
sd t5, 224(sp)
sd t6, 232(sp)
# 3. Call your C handler
# The CPU already put the cause in 'mcause', so C can read it
call handle_trap
# 4. Restore all GPRs
ld ra, 0(sp)
ld gp, 8(sp)
ld tp, 16(sp)
ld t0, 24(sp)
ld t1, 32(sp)
ld t2, 40(sp)
ld s0, 48(sp)
ld s1, 56(sp)
ld a0, 64(sp)
ld a1, 72(sp)
ld a2, 80(sp)
ld a3, 88(sp)
ld a4, 96(sp)
ld a5, 104(sp)
ld a6, 112(sp)
ld a7, 120(sp)
ld s2, 128(sp)
ld s3, 136(sp)
ld s4, 144(sp)
ld s5, 152(sp)
ld s6, 160(sp)
ld s7, 168(sp)
ld s8, 176(sp)
ld s9, 184(sp)
ld s10, 192(sp)
ld s11, 200(sp)
ld t3, 208(sp)
ld t4, 216(sp)
ld t5, 224(sp)
ld t6, 232(sp)
# 5. Shrink the stack back
addi sp, sp, 256
# 6. Return from Machine-mode trap
mret

View file

@ -1,14 +1,42 @@
#include <stdint.h>
#include <stddef.h>
#include <stdarg.h>
#include <syscon/syscon.h>
#include <drivers/uart.h>
#include <lib/string.h>
void uart_init()
{
kprint("UART Drive Init...");
volatile uint8_t *UART_IER = (uint8_t *)(UART_ADDRESS + 1);
*UART_IER = 0x01; // Enable "Received Data Available" interrupt
kputs("OK");
}
void uart_put(size_t base_addr, uint8_t data)
{
*(volatile uint8_t *)base_addr = data;
}
char uart_getc()
{
if (*UART_LSR & 0x01)
{
return (char)(*UART_RBR);
}
return '\0';
}
void uart_handle_interrupt()
{
char c = uart_getc();
if (c != '\0')
{
// Later, this will go into a "Circular Buffer"
kputchar(c);
}
}
int kputchar(int ch)
{
uart_put(UART_ADDRESS, ch);
@ -107,6 +135,11 @@ void kprintf(const char *format, ...)
p++;
switch (*p)
{
case 'c':
{
char c = (char)va_arg(args, int);
kputchar(c);
}
case 's':
{
char *s = va_arg(args, char *);

View file

@ -1,8 +1,19 @@
#ifndef UART_H
#define UART_H
#define UART_RBR (volatile uint8_t *)(UART_ADDRESS + 0)
#define UART_LSR (volatile uint8_t *)(UART_ADDRESS + 5)
#define UART_ADDRESS 0x10000000
#define UART_INTERRUPT_ENABLE_REGISTER 0x1001100C
#define UART_INTEN_OFFSET 0x0C
#define UART_TX_OFFSET 0x04
#define UART_INTERRUPT_ENABLE_REGISTER (UART_BASE_ADDR + UART_INTEN_OFFSET)
void uart_init();
void uart_put(size_t base_addr, uint8_t data);
char uart_getc();
void uart_handle_interrupt();
int kputchar(int ch);
void kprint_hex(uint64_t val);
void kprint_int(int num);

View file

@ -2,7 +2,24 @@
#include <stdint.h>
#include <drivers/uart.h>
#include <syscon/syscon.h>
#include "memory.h"
#include <kernel/plic.h>
#include <kernel/interrupts.h>
#include <kernel/memory.h>
void interrupt_init()
{
kprint("Initialising Interrupts...");
uint64_t mstatus_val;
asm volatile("csrr %0, mstatus" : "=r"(mstatus_val));
mstatus_val |= (1 << MSTATUS_BIT_MIE);
asm volatile("csrw mstatus, %0" ::"r"(mstatus_val));
uint64_t mie_val;
asm volatile("csrr %0, mie" : "=r"(mie_val));
mie_val |= (1 << MIE_BIT_MEIE); //|(1 << MIE_BIT_MTIE);
asm volatile("csrw mie, %0" ::"r"(mie_val));
kputs("OK");
}
void kpanic(const char *reason)
{
@ -31,48 +48,74 @@ void kpanic_force()
void handle_trap()
{
kprint("\n!!! HARDWARE EXCEPTION DETECTED !!!\n");
// Read the 'mcause' register to see WHY we trapped
unsigned long cause;
__asm__ volatile("csrr %0, mcause" : "=r"(cause));
// fault address (if applicable)
uintptr_t mtval;
asm volatile("csrr %0, mtval" : "=r"(mtval));
// Check if the top bit is 1 (Interrupt) or 0 (Exception)
// For 64-bit RISC-V, the bit is 63
int is_interrupt = (cause >> 63) & 1;
switch (cause)
if (is_interrupt)
{
case 0:
kprint("Reason: Instruction Address Misaligned\n");
break;
case 1:
kprint("Reason: Instruction Access Fault\n");
break;
case 2:
kprint("Reason: Illegal Instruction\n");
break;
case 3:
kprint("Reason: Breakpoint (ebreak)\n");
break;
case 4:
kprint("Reason: Load Address Misaligned\n");
break;
case 5:
kprint("Reason: Load Access Fault\n");
break;
case 6:
kprint("Reason: Store/AMO Address Misaligned\n");
break;
case 7:
kprint("Reason: Store/AMO Access Fault\n");
break;
default:
kprintf("Reason: Unknown Exception Code %d\n", cause);
unsigned long code = cause & 0xfff;
handle_interrupt(code);
return;
}
kprintf("Faulting Address (if applicable): %x\n", mtval);
else
{
// fault address (if applicable)
uintptr_t mtval;
asm volatile("csrr %0, mtval" : "=r"(mtval));
heap_stats();
poweroff();
switch (cause)
{
case 0:
kpanic("Reason: Instruction Address Misaligned\n");
break;
case 1:
kpanic("Reason: Instruction Access Fault\n");
break;
case 2:
kpanic("Reason: Illegal Instruction\n");
break;
case 3:
kpanic("Reason: Breakpoint (ebreak)\n");
break;
case 4:
kpanic("Reason: Load Address Misaligned\n");
break;
case 5:
kpanic("Reason: Load Access Fault\n");
break;
case 6:
kpanic("Reason: Store/AMO Address Misaligned\n");
break;
case 7:
kpanic("Reason: Store/AMO Access Fault\n");
break;
default:
break;
// kpanic("Reason: Unknown Exception Code %d\n", cause);
}
kprintf("Faulting Address (if applicable): %x\n", mtval);
}
}
void handle_interrupt(unsigned long code)
{
switch (code)
{
case 7:
break; // timer Interrupt. Ignoring for now.
case 11:
volatile uint32_t *claim_reg = (uint32_t *)PLIC_CLAIM_COMPLETE;
uint32_t irq = *claim_reg;
if (irq == 10)
{
uart_handle_interrupt();
}
*claim_reg = irq;
break;
}
}

View file

@ -1,8 +1,10 @@
#ifndef PANIC_H
#define PANIC_H
void kpanic(const char *reason);
void interrupt_init();
void kpanic(const char *);
void kpanic_force();
void handle_interrupt(unsigned long code);
#define KASSERT(cond, msg) \
if (!(cond)) \

View file

@ -2,6 +2,7 @@
#include <stddef.h>
#include <drivers/uart.h>
#include <syscon/syscon.h>
#include <kernel/plic.h>
#include <kernel/interrupts.h>
#include <kernel/memory.h>
@ -11,9 +12,8 @@ void kmain()
kprintf("Hello, from %s!", "SquidgeOS");
kputs("----------------------");
knewline();
plic_init();
uart_init();
page_init();
test_memory_integrity();
test_memory_alignment();
test_memory_stress();
poweroff();
interrupt_init();
}

View file

@ -11,7 +11,7 @@ extern uint8_t _heap_start[]; // named in the linker script
void page_init()
{
kprint("Initialising page allocator.\n");
kprint("Initialising page allocator...");
uintptr_t start = ((uintptr_t)_heap_start + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
uintptr_t end = 0x88000000; // Default QEMU RAM limit
@ -19,6 +19,7 @@ void page_init()
{
page_free((void *)addr);
}
kputs("OK");
}
void page_free(void *addr)

19
src/kernel/plic.c Normal file
View file

@ -0,0 +1,19 @@
#include <stdint.h>
#include "kernel/plic.h"
void plic_init()
{
int hart = 0;
// 1. Set the priority of the UART interrupt
// We set it to 1. If it's 0 (the default), the interrupt is effectively disabled.
*PLIC_PRIORITY(UART_IRQ) = 1;
// 2. Enable the UART interrupt for Hart 0
// This is a bitmask, so we shift 1 by the IRQ number.
*PLIC_ENABLE(hart) = (1 << UART_IRQ);
// 3. Set the priority threshold for Hart 0
// We set this to 0 so that ANY interrupt with priority > 0 gets through.
*PLIC_THRESHOLD(hart) = 0;
}

23
src/kernel/plic.h Normal file
View file

@ -0,0 +1,23 @@
#ifndef PLIC_H
#define PLIC_H
#define PLIC_BASE 0x0c000000
#define PLIC_CLAIM_COMPLETE 0x0c200004
// Priorities: 4 bytes per IRQ (IRQ 0 is reserved/null)
#define PLIC_PRIORITY(irq) ((volatile uint32_t *)(PLIC_BASE + (irq) * 4))
// Enables: Each Hart has a 0x80 byte stride for its enable bits
// For Hart 0 Machine Mode: 0x0c002000
#define PLIC_ENABLE(hart) ((volatile uint32_t *)(PLIC_BASE + 0x2000 + (hart) * 0x80))
// Threshold and Claim/Complete: Each Hart has a 0x1000 byte stride
// For Hart 0 Machine Mode: 0x0c200000 and 0x0c200004
#define PLIC_THRESHOLD(hart) ((volatile uint32_t *)(PLIC_BASE + 0x200000 + (hart) * 0x1000))
#define PLIC_CLAIM(hart) ((volatile uint32_t *)(PLIC_BASE + 0x200004 + (hart) * 0x1000))
#define UART_IRQ 10
void plic_init();
#endif

View file

@ -11,19 +11,20 @@ void *memset(void *dest, int val, size_t size)
return dest;
}
void *memcpy(void* dest, const void* src, size_t size)
void *memcpy(void *dest, const void *src, size_t size)
{
unsigned char *d = dest;
const unsigned char *s = src;
while(--size)
while (--size)
{
*d++ = *s++;
}
return dest;
}
int strcmp(const char * str1, const char * str2)
{ while(*str1 == *str2)
int strcmp(const char *str1, const char *str2)
{
while (*str1 == *str2)
{
if (*str1 == '\0')
{
@ -35,10 +36,11 @@ int strcmp(const char * str1, const char * str2)
return *str1 - *str2;
}
size_t strlen(const char* str)
size_t strlen(const char *str)
{
size_t c = 0;
while(*str++ != '\0'){
while (*str++ != '\0')
{
c++;
}
return c;

View file

@ -8,6 +8,18 @@
#define SYSCON_POWEROFF 0x5555
#define SYSCON_REBOOT 0x7777
#define write_register(register, value) \
asm volatile("csrrw zero, %0, %1" ::"i"(register), "r"(value))
#define read_register(register, destination) \
asm volatile("csrrs %0, %1, zero" : "=r"(destination) : "i"(register))
#define MSTATUS 0x300
#define MIE 0x304
#define MSTATUS_BIT_MIE 3
#define MIE_BIT_MTIE 7
#define MIE_BIT_MEIE 11
void poweroff(void);
void reboot(void);