Compare commits

..

9 commits

Author SHA1 Message Date
Imbus
ddddf903dc Notes in ispinlock 2025-08-16 16:12:01 +02:00
Imbus
97e2508516 ispinlock make, phony all 2025-08-16 15:50:38 +02:00
Imbus
b0fe61094d proc calloc 2025-08-16 15:48:08 +02:00
Imbus
39ef34d41e Restructure, use ispinlock 2025-08-16 15:01:01 +02:00
Imbus
608968668b Types 2025-08-16 15:00:01 +02:00
Imbus
74a39e1822 kprint 2025-08-16 14:59:27 +02:00
Imbus
dc0487648a ispinlock 2025-08-16 14:59:24 +02:00
Imbus
6c3030c896 Libkern string 2025-08-16 14:57:54 +02:00
Imbus
f45d2a29fc Clang-format update 2025-08-16 14:47:10 +02:00
11 changed files with 410 additions and 29 deletions

View file

@ -2,8 +2,9 @@ BasedOnStyle: LLVM
IndentWidth: 4 # Use 4 spaces for indentation IndentWidth: 4 # Use 4 spaces for indentation
TabWidth: 4 # Tab width is also 4 spaces TabWidth: 4 # Tab width is also 4 spaces
UseTab: Never # Always use spaces instead of tabs UseTab: Never # Always use spaces instead of tabs
ColumnLimit: 80 # Wrap lines after 80 characters ColumnLimit: 120 # Wrap lines after 80 characters
AllowShortLoopsOnASingleLine: true AllowShortLoopsOnASingleLine: true
AllowShortFunctionsOnASingleLine: false
AlwaysBreakTemplateDeclarations: true AlwaysBreakTemplateDeclarations: true
BreakConstructorInitializers: BeforeComma BreakConstructorInitializers: BeforeComma
AlignConsecutiveDeclarations: AlignConsecutiveDeclarations:
@ -14,3 +15,4 @@ AlignConsecutiveDeclarations:
AlignFunctionPointers: false AlignFunctionPointers: false
PadOperators: false PadOperators: false
AlignConsecutiveMacros: true AlignConsecutiveMacros: true
AllowShortCaseLabelsOnASingleLine: true

View file

@ -28,7 +28,7 @@ CFLAGS += -fno-omit-frame-pointer # More reliable backtraces in GDB
all: kernel.elf all: kernel.elf
kernel.elf: entry.o start.o lib/string.o lib/proc.o lib/spinlock.o lib/proc.o lib/uart.o lib/panic.o kern/kalloc.o lib/memory.o kernel.elf: entry.o start.o lib/string.o lib/proc.o lib/proc.o lib/uart.o lib/panic.o kern/kalloc.o lib/memory.o kern/ispinlock.o lib/spinlock.o
@echo LD $@ @echo LD $@
@$(LD) $(LDFLAGS) -o $@ $^ @$(LD) $(LDFLAGS) -o $@ $^
@ -48,3 +48,5 @@ clean:
rm -f *.o *.elf *.d lib/*.o lib/*.d rm -f *.o *.elf *.d lib/*.o lib/*.d
-include *.d -include *.d
.PHONY: all

45
kern/ispinlock.c Normal file
View file

@ -0,0 +1,45 @@
#include "ispinlock.h"
void spinlock_init(spinlock_t *l) {
l->v = 0;
}
bool spin_trylock(spinlock_t *l) {
uint32_t old;
// old = xchg_acquire(&l->v, 1) using AMO
__asm__ volatile("amoswap.w.aq %0, %2, (%1)\n" : "=&r"(old) : "r"(&l->v), "r"(1u) : "memory");
return old == 0;
}
void spin_unlock(spinlock_t *l) {
// Release: store 0 with .rl ordering.
uint32_t dummy;
__asm__ volatile("amoswap.w.rl %0, %2, (%1)\n" : "=&r"(dummy) : "r"(&l->v), "r"(0u) : "memory");
}
// Optional: tiny pause/backoff (works even if Zihintpause isn't present).
// See: https://github.com/riscv/riscv-isa-manual/blob/main/src/zihintpause.adoc
void cpu_relax(void) {
#if defined(__riscv_zihintpause)
__asm__ volatile("pause");
#else
__asm__ volatile("nop");
#endif
}
// Test-and-test-and-set acquire with polite spinning + exponential backoff.
void spin_lock(spinlock_t *l) {
unsigned backoff = 1;
for (;;) {
if (spin_trylock(l))
return;
// Contended: spin on plain loads (no AMO) until it looks free.
while (__atomic_load_n(&l->v, __ATOMIC_RELAXED) != 0) {
for (unsigned i = 0; i < backoff; ++i) cpu_relax();
if (backoff < 1u << 12)
backoff <<= 1;
}
// Try again; loop.
}
}

12
kern/ispinlock.h Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include <types.h>
typedef struct {
volatile uint32_t v; // 0 = unlocked, 1 = locked
} spinlock_t;
void spinlock_init(spinlock_t *l);
bool spin_trylock(spinlock_t *l);
void spin_unlock(spinlock_t *l);
void cpu_relax(void);
void spin_lock(spinlock_t *l);

View file

@ -1,8 +1,8 @@
#include <ispinlock.h>
#include <kalloc.h> #include <kalloc.h>
#include <memory.h> #include <memory.h>
#include <panic.h> #include <panic.h>
#include <riscv.h> #include <riscv.h>
#include <spinlock.h>
#include <string.h> #include <string.h>
#include <types.h> #include <types.h>
@ -23,12 +23,12 @@ struct Run {
/** Kernel memory allocator. */ /** Kernel memory allocator. */
struct { struct {
struct Spinlock lock; spinlock_t lock;
struct Run *freelist; struct Run *freelist;
} kmem; } kmem;
void kalloc_init() { void kalloc_init() {
initlock(&kmem.lock, "kmem"); spinlock_init(&kmem.lock);
freerange(kernel_end, (void *)PHYSTOP); freerange(kernel_end, (void *)PHYSTOP);
} }
@ -43,8 +43,7 @@ void kfree(void *pa) {
// Assert that page is a ligned to a page boundary and that its correctly // Assert that page is a ligned to a page boundary and that its correctly
// sized // sized
if (((u64)pa % PGSIZE) != 0 || (char *)pa < kernel_end || if (((u64)pa % PGSIZE) != 0 || (char *)pa < kernel_end || (u64)pa >= PHYSTOP)
(u64)pa >= PHYSTOP)
panic("kfree"); panic("kfree");
// Fill with junk to catch dangling refs. // Fill with junk to catch dangling refs.
@ -52,23 +51,23 @@ void kfree(void *pa) {
r = (struct Run *)pa; r = (struct Run *)pa;
acquire(&kmem.lock); spin_lock(&kmem.lock);
r->next = kmem.freelist; r->next = kmem.freelist;
kmem.freelist = r; kmem.freelist = r;
release(&kmem.lock); spin_unlock(&kmem.lock);
} }
void *kalloc(void) { void *kalloc(void) {
struct Run *r; struct Run *r;
acquire(&kmem.lock); spin_lock(&kmem.lock);
r = kmem.freelist; r = kmem.freelist;
if (r) if (r)
kmem.freelist = r->next; kmem.freelist = r->next;
release(&kmem.lock); spin_unlock(&kmem.lock);
if (r) if (r)
memset((char *)r, 5, PGSIZE); // fill with junk memset((char *)r, 5, PGSIZE); // fill with junk

103
kern/kprint.c Normal file
View file

@ -0,0 +1,103 @@
#include <stdarg.h>
#include <stdbool.h>
static void append_char(char **buf, size_t *remaining, char c) {
if (*remaining > 1) { // Leave space for null terminator
**buf = c;
(*buf)++;
(*remaining)--;
}
}
static void append_str(char **buf, size_t *remaining, const char *str) {
while (*str) {
append_char(buf, remaining, *str++);
}
}
static void append_int(char **buf, size_t *remaining, int value, int base) {
char tmp[32];
const char *digits = "0123456789abcdef";
bool neg = false;
int i = 0;
if (base == 10 && value < 0) {
neg = true;
value = -value;
}
do {
tmp[i++] = digits[value % base];
value /= base;
} while (value && i < (int)sizeof(tmp));
if (neg) {
tmp[i++] = '-';
}
while (i--) {
append_char(buf, remaining, tmp[i]);
}
}
int kvsnprintf(char *buf, size_t size, const char *fmt, va_list args) {
char *p = buf;
size_t remaining = size;
while (*fmt) {
if (*fmt != '%') {
append_char(&p, &remaining, *fmt++);
continue;
}
fmt++; // skip '%'
switch (*fmt) {
case 's':
append_str(&p, &remaining, va_arg(args, const char *));
break;
case 'd':
append_int(&p, &remaining, va_arg(args, int), 10);
break;
case 'x':
append_int(&p, &remaining, va_arg(args, unsigned int), 16);
break;
case 'c':
append_char(&p, &remaining, (char)va_arg(args, int));
break;
case '%':
append_char(&p, &remaining, '%');
break;
default:
append_char(&p, &remaining, '?');
break;
}
fmt++;
}
if (remaining > 0) {
*p = '\0';
} else {
buf[size - 1] = '\0';
}
return (int)(p - buf);
}
int ksnprintf(char *buf, size_t size, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int ret = kvsnprintf(buf, size, fmt, args);
va_end(args);
return ret;
}
void kprintf(const char *fmt, ...) {
char buffer[256];
va_list args;
va_start(args, fmt);
kvsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
// Now send buffer to your console/serial output
console_write(buffer);
}

127
kern/libkern/string.c Normal file
View file

@ -0,0 +1,127 @@
#include <string.h>
void *memcpy(void *s1, const void *s2, size_t n) {
const char *f = s2;
char *t = s1;
while (n-- > 0) *t++ = *f++;
return s1;
}
void *memmove(void *s1, const void *s2, size_t n) {
const char *f = s2;
char *t = s1;
if (f < t) {
f += n;
t += n;
while (n-- > 0) *--t = *--f;
} else
while (n-- > 0) *t++ = *f++;
return s1;
}
void *memset(void *dest, int c, size_t n) {
unsigned char *s = dest;
size_t k;
/* Fill head and tail with minimal branching. Each
* conditional ensures that all the subsequently used
* offsets are well-defined and in the dest region. */
if (!n)
return dest;
s[0] = c;
s[n - 1] = c;
if (n <= 2)
return dest;
s[1] = c;
s[2] = c;
s[n - 2] = c;
s[n - 3] = c;
if (n <= 6)
return dest;
s[3] = c;
s[n - 4] = c;
if (n <= 8)
return dest;
/* Advance pointer to align it at a 4-byte boundary,
* and truncate n to a multiple of 4. The previous code
* already took care of any head/tail that get cut off
* by the alignment. */
k = -(uintptr_t)s & 3;
s += k;
n -= k;
n &= -4;
#ifdef __GNUC__
typedef uint32_t __attribute__((__may_alias__)) u32;
typedef uint64_t __attribute__((__may_alias__)) u64;
u32 c32 = ((u32)-1) / 255 * (unsigned char)c;
/* In preparation to copy 32 bytes at a time, aligned on
* an 8-byte bounary, fill head/tail up to 28 bytes each.
* As in the initial byte-based head/tail fill, each
* conditional below ensures that the subsequent offsets
* are valid (e.g. !(n<=24) implies n>=28). */
*(u32 *)(s + 0) = c32;
*(u32 *)(s + n - 4) = c32;
if (n <= 8)
return dest;
*(u32 *)(s + 4) = c32;
*(u32 *)(s + 8) = c32;
*(u32 *)(s + n - 12) = c32;
*(u32 *)(s + n - 8) = c32;
if (n <= 24)
return dest;
*(u32 *)(s + 12) = c32;
*(u32 *)(s + 16) = c32;
*(u32 *)(s + 20) = c32;
*(u32 *)(s + 24) = c32;
*(u32 *)(s + n - 28) = c32;
*(u32 *)(s + n - 24) = c32;
*(u32 *)(s + n - 20) = c32;
*(u32 *)(s + n - 16) = c32;
/* Align to a multiple of 8 so we can fill 64 bits at a time,
* and avoid writing the same bytes twice as much as is
* practical without introducing additional branching. */
k = 24 + ((uintptr_t)s & 4);
s += k;
n -= k;
/* If this loop is reached, 28 tail bytes have already been
* filled, so any remainder when n drops below 32 can be
* safely ignored. */
u64 c64 = c32 | ((u64)c32 << 32);
for (; n >= 32; n -= 32, s += 32) {
*(u64 *)(s + 0) = c64;
*(u64 *)(s + 8) = c64;
*(u64 *)(s + 16) = c64;
*(u64 *)(s + 24) = c64;
}
#else
/* Pure C fallback with no aliasing violations. */
for (; n; n--, s++) *s = c;
#endif
return dest;
}
int memcmp(const void *s1, const void *s2, size_t n) {
if (n != 0) {
const unsigned char *p1 = s1, *p2 = s2;
do {
if (*p1++ != *p2++)
return (*--p1 - *--p2);
} while (--n != 0);
}
return (0);
}

7
kern/libkern/string.h Normal file
View file

@ -0,0 +1,7 @@
#pragma once
#include <types.h>
void *memcpy(void *s1, const void *s2, size_t n);
void *memmove(void *s1, const void *s2, size_t n);
void *memset(void *dest, int c, size_t n);
int memcmp(const void *s1, const void *s2, size_t n);

77
kern/proc.h Normal file
View file

@ -0,0 +1,77 @@
#include <stdint.h>
typedef enum {
UNUSED,
USED,
SLEEPING,
RUNNABLE,
RUNNING,
ZOMBIE,
} ProcessState;
/** Saved registers for kernel context switches. */
struct Context {
uint64_t ra;
uint64_t sp;
// callee-saved
uint64_t s0;
uint64_t s1;
uint64_t s2;
uint64_t s3;
uint64_t s4;
uint64_t s5;
uint64_t s6;
uint64_t s7;
uint64_t s8;
uint64_t s9;
uint64_t s10;
uint64_t s11;
};
/** Per-CPU state. */
struct Cpu {
struct Process *proc; // The process running on this cpu, or null.
struct Context context; // swtch() here to enter scheduler().
int noff; // Depth of push_off() nesting.
int intena; // Were interrupts enabled before push_off()?
};
typedef struct {
/* 0 */ uint64_t kernel_satp; // kernel page table
/* 8 */ uint64_t kernel_sp; // top of process's kernel stack
/* 16 */ uint64_t kernel_trap; // usertrap()
/* 24 */ uint64_t epc; // saved user program counter
/* 32 */ uint64_t kernel_hartid; // saved kernel tp
/* 40 */ uint64_t ra;
/* 48 */ uint64_t sp;
/* 56 */ uint64_t gp;
/* 64 */ uint64_t tp;
/* 72 */ uint64_t t0;
/* 80 */ uint64_t t1;
/* 88 */ uint64_t t2;
/* 96 */ uint64_t s0;
/* 104 */ uint64_t s1;
/* 112 */ uint64_t a0;
/* 120 */ uint64_t a1;
/* 128 */ uint64_t a2;
/* 136 */ uint64_t a3;
/* 144 */ uint64_t a4;
/* 152 */ uint64_t a5;
/* 160 */ uint64_t a6;
/* 168 */ uint64_t a7;
/* 176 */ uint64_t s2;
/* 184 */ uint64_t s3;
/* 192 */ uint64_t s4;
/* 200 */ uint64_t s5;
/* 208 */ uint64_t s6;
/* 216 */ uint64_t s7;
/* 224 */ uint64_t s8;
/* 232 */ uint64_t s9;
/* 240 */ uint64_t s10;
/* 248 */ uint64_t s11;
/* 256 */ uint64_t t3;
/* 264 */ uint64_t t4;
/* 272 */ uint64_t t5;
/* 280 */ uint64_t t6;
} TrapFrame_t;

30
start.c
View file

@ -1,9 +1,9 @@
#include <config.h> #include <config.h>
#include <ispinlock.h>
#include <kalloc.h> #include <kalloc.h>
#include <memory.h> #include <memory.h>
#include <proc.h> #include <proc.h>
#include <riscv.h> #include <riscv.h>
#include <spinlock.h>
#include <types.h> #include <types.h>
#include <uart.h> #include <uart.h>
@ -16,8 +16,8 @@
char stack0[4096 * NCPU] __attribute__((aligned(16))); char stack0[4096 * NCPU] __attribute__((aligned(16)));
/* Keep this here and sync on it until we have synchronized printf */ /* Keep this here and sync on it until we have synchronized printf */
struct Spinlock sl = {0}; spinlock_t sl = {0};
volatile int greeted = 0; volatile int hold = 1;
/* This is where entry.S drops us of. All cores land here */ /* This is where entry.S drops us of. All cores land here */
void start() { void start() {
@ -29,24 +29,22 @@ void start() {
// cpu (struct Cpu). // cpu (struct Cpu).
write_tp(id); write_tp(id);
acquire(&sl);
if (!greeted) {
uart_puts("Hello Neptune!\n");
greeted = 1;
}
uart_puts("Hart number: ");
uart_putc(id + '0');
uart_putc('\n');
release(&sl);
if (id == 0) { if (id == 0) {
/* Here we will do a bunch of initialization steps */ /* Here we will do a bunch of initialization steps */
kalloc_init(); kalloc_init();
uart_puts("Hello Neptune!\n");
spinlock_init(&sl);
hold = 0;
} }
while (hold);
spin_lock(&sl);
uart_puts("Hart number: ");
uart_putc(id + '0');
uart_putc('\n');
spin_unlock(&sl);
// We should not arrive here, but if we do, hang in a while on wfi. // We should not arrive here, but if we do, hang in a while on wfi.
while (1) __asm__ volatile("wfi"); // (Wait For Interrupt) while (1) __asm__ volatile("wfi"); // (Wait For Interrupt)
} }

11
types.h
View file

@ -4,4 +4,13 @@ typedef unsigned char u8;
typedef unsigned short u16; typedef unsigned short u16;
typedef unsigned int u32; typedef unsigned int u32;
typedef unsigned long u64; typedef unsigned long u64;
typedef u64 size_t;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long uint64_t;
typedef uint64_t size_t;
typedef uint64_t uintptr_t;
typedef u8 bool;