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 .global _start
_start: _start:
la sp, stack_top # Set up the stack pointer # 1. Set up the stack pointer using the symbol from our linker script
la t0, trap_entry # Set up the trap handler la sp, stack_top
csrw mtvec, t0 # Set the trap vector to our trap handler
li t0, 0x00006000 # Load the mask for FS bits. enable float # 2. Set up the trap handler (pointing to the one in traps.S)
csrs mstatus, t0 # Set the FS bits to 11 (Dirty/Initial). enable float la t0, trap_entry
call kmain # Jump to our C code csrw mtvec, t0
# 3. Enable FPU (Floating Point)
li t0, 0x00006000
csrs mstatus, t0
# 4. Jump to C
call kmain
loop: loop:
wfi # Wait for Interrupt (saves CPU) wfi
j loop # Infinite loop if C returns j loop
.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:

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 <stdint.h>
#include <stddef.h> #include <stddef.h>
#include <stdarg.h> #include <stdarg.h>
#include <syscon/syscon.h>
#include <drivers/uart.h> #include <drivers/uart.h>
#include <lib/string.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) void uart_put(size_t base_addr, uint8_t data)
{ {
*(volatile uint8_t *)base_addr = 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) int kputchar(int ch)
{ {
uart_put(UART_ADDRESS, ch); uart_put(UART_ADDRESS, ch);
@ -107,6 +135,11 @@ void kprintf(const char *format, ...)
p++; p++;
switch (*p) switch (*p)
{ {
case 'c':
{
char c = (char)va_arg(args, int);
kputchar(c);
}
case 's': case 's':
{ {
char *s = va_arg(args, char *); char *s = va_arg(args, char *);

View file

@ -1,8 +1,19 @@
#ifndef UART_H #ifndef UART_H
#define 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_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); void uart_put(size_t base_addr, uint8_t data);
char uart_getc();
void uart_handle_interrupt();
int kputchar(int ch); int kputchar(int ch);
void kprint_hex(uint64_t val); void kprint_hex(uint64_t val);
void kprint_int(int num); void kprint_int(int num);

View file

@ -2,7 +2,24 @@
#include <stdint.h> #include <stdint.h>
#include <drivers/uart.h> #include <drivers/uart.h>
#include <syscon/syscon.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) void kpanic(const char *reason)
{ {
@ -31,12 +48,22 @@ void kpanic_force()
void handle_trap() void handle_trap()
{ {
kprint("\n!!! HARDWARE EXCEPTION DETECTED !!!\n");
// Read the 'mcause' register to see WHY we trapped // Read the 'mcause' register to see WHY we trapped
unsigned long cause; unsigned long cause;
__asm__ volatile("csrr %0, mcause" : "=r"(cause)); __asm__ volatile("csrr %0, mcause" : "=r"(cause));
// 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;
if (is_interrupt)
{
unsigned long code = cause & 0xfff;
handle_interrupt(code);
return;
}
else
{
// fault address (if applicable) // fault address (if applicable)
uintptr_t mtval; uintptr_t mtval;
asm volatile("csrr %0, mtval" : "=r"(mtval)); asm volatile("csrr %0, mtval" : "=r"(mtval));
@ -44,35 +71,51 @@ void handle_trap()
switch (cause) switch (cause)
{ {
case 0: case 0:
kprint("Reason: Instruction Address Misaligned\n"); kpanic("Reason: Instruction Address Misaligned\n");
break; break;
case 1: case 1:
kprint("Reason: Instruction Access Fault\n"); kpanic("Reason: Instruction Access Fault\n");
break; break;
case 2: case 2:
kprint("Reason: Illegal Instruction\n"); kpanic("Reason: Illegal Instruction\n");
break; break;
case 3: case 3:
kprint("Reason: Breakpoint (ebreak)\n"); kpanic("Reason: Breakpoint (ebreak)\n");
break; break;
case 4: case 4:
kprint("Reason: Load Address Misaligned\n"); kpanic("Reason: Load Address Misaligned\n");
break; break;
case 5: case 5:
kprint("Reason: Load Access Fault\n"); kpanic("Reason: Load Access Fault\n");
break; break;
case 6: case 6:
kprint("Reason: Store/AMO Address Misaligned\n"); kpanic("Reason: Store/AMO Address Misaligned\n");
break; break;
case 7: case 7:
kprint("Reason: Store/AMO Access Fault\n"); kpanic("Reason: Store/AMO Access Fault\n");
break; break;
default: default:
kprintf("Reason: Unknown Exception Code %d\n", cause); break;
// kpanic("Reason: Unknown Exception Code %d\n", cause);
} }
kprintf("Faulting Address (if applicable): %x\n", mtval); kprintf("Faulting Address (if applicable): %x\n", mtval);
}
heap_stats(); }
poweroff(); 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 #ifndef PANIC_H
#define PANIC_H #define PANIC_H
void kpanic(const char *reason); void interrupt_init();
void kpanic(const char *);
void kpanic_force(); void kpanic_force();
void handle_interrupt(unsigned long code);
#define KASSERT(cond, msg) \ #define KASSERT(cond, msg) \
if (!(cond)) \ if (!(cond)) \

View file

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

View file

@ -11,7 +11,7 @@ extern uint8_t _heap_start[]; // named in the linker script
void page_init() 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 start = ((uintptr_t)_heap_start + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
uintptr_t end = 0x88000000; // Default QEMU RAM limit uintptr_t end = 0x88000000; // Default QEMU RAM limit
@ -19,6 +19,7 @@ void page_init()
{ {
page_free((void *)addr); page_free((void *)addr);
} }
kputs("OK");
} }
void page_free(void *addr) 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; 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; unsigned char *d = dest;
const unsigned char *s = src; const unsigned char *s = src;
while(--size) while (--size)
{ {
*d++ = *s++; *d++ = *s++;
} }
return dest; return dest;
} }
int strcmp(const char * str1, const char * str2) int strcmp(const char *str1, const char *str2)
{ while(*str1 == *str2) {
while (*str1 == *str2)
{ {
if (*str1 == '\0') if (*str1 == '\0')
{ {
@ -35,10 +36,11 @@ int strcmp(const char * str1, const char * str2)
return *str1 - *str2; return *str1 - *str2;
} }
size_t strlen(const char* str) size_t strlen(const char *str)
{ {
size_t c = 0; size_t c = 0;
while(*str++ != '\0'){ while (*str++ != '\0')
{
c++; c++;
} }
return c; return c;

View file

@ -8,6 +8,18 @@
#define SYSCON_POWEROFF 0x5555 #define SYSCON_POWEROFF 0x5555
#define SYSCON_REBOOT 0x7777 #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 poweroff(void);
void reboot(void); void reboot(void);