2020 release

This commit is contained in:
Yanyan Jiang 2020-08-11 22:03:04 +08:00 committed by Zihao Yu
commit 61348e8b07
86 changed files with 5127 additions and 0 deletions

24
am/src/mips32.h Normal file
View file

@ -0,0 +1,24 @@
#ifndef MIPS32_H__
#define MIPS32_H__
#include <stdint.h>
static inline uint8_t inb(uintptr_t addr) { return *(volatile uint8_t *)addr; }
static inline uint16_t inw(uintptr_t addr) { return *(volatile uint16_t *)addr; }
static inline uint32_t inl(uintptr_t addr) { return *(volatile uint32_t *)addr; }
static inline void outb(uintptr_t addr, uint8_t data) { *(volatile uint8_t *)addr = data; }
static inline void outw(uintptr_t addr, uint16_t data) { *(volatile uint16_t *)addr = data; }
static inline void outl(uintptr_t addr, uint32_t data) { *(volatile uint32_t *)addr = data; }
#define PTE_V 0x2
#define PTE_D 0x4
// Page directory and page table constants
#define PTXSHFT 12 // Offset of PTX in a linear address
#define PDXSHFT 22 // Offset of PDX in a linear address
#define PDX(va) (((uint32_t)(va) >> PDXSHFT) & 0x3ff)
#define PTX(va) (((uint32_t)(va) >> PTXSHFT) & 0x3ff)
#endif

196
am/src/native/cte.c Normal file
View file

@ -0,0 +1,196 @@
#include <sys/time.h>
#include "platform.h"
#define TIMER_HZ 100
#define YIELD_INSTR "0xff,0x14,0x25,0x08,0x00,0x10,0x00" // callq *0x100008
#define YIELD_INSTR_LEN ((sizeof(YIELD_INSTR)) / 5) // sizeof() counts the '\0' byte
#define SYSCALL_INSTR_LEN YIELD_INSTR_LEN
static_assert(SYSCALL_INSTR_LEN == 7);
static Context* (*user_handler)(Event, Context*) = NULL;
void __am_kcontext_start();
void __am_switch(Context *c);
int __am_in_userspace(void *addr);
void __am_pmem_protect();
void __am_pmem_unprotect();
void __am_panic_on_return() { panic("should not reach here\n"); }
static void irq_handle(Context *c) {
c->vm_head = thiscpu->vm_head;
c->ksp = thiscpu->ksp;
c = user_handler(thiscpu->ev, c);
assert(c != NULL);
__am_switch(c);
// magic call to restore context
asm volatile("call *0x100010" : : "a" (c));
__am_panic_on_return();
}
static void setup_stack(uintptr_t event, ucontext_t *uc) {
void *rip = (void *)uc->uc_mcontext.gregs[REG_RIP];
extern uint8_t _start, _etext;
int trap_from_user = __am_in_userspace(rip);
int signal_safe = IN_RANGE(rip, RANGE(&_start, &_etext)) || trap_from_user ||
// Hack here: "+13" points to the instruction after syscall. This is the
// instruction which will trigger the pending signal if interrupt is enabled.
(rip == (void *)&sigprocmask + 13);
if (((event == EVENT_IRQ_IODEV) || (event == EVENT_IRQ_TIMER)) && !signal_safe) {
// Shared libraries contain code which are not reenterable.
// If the signal comes when executing code in shared libraries,
// the signal handler can not call any function which is not signal-safe,
// else the behavior is undefined (may be dead lock).
// To handle this, we just refuse to handle the signal and return directly
// to pretend missing the interrupt.
// See man 7 signal-safety for more information.
return;
}
if (trap_from_user) __am_pmem_unprotect();
// skip the instructions causing SIGSEGV for syscall and yield
if (event == EVENT_SYSCALL) { rip += SYSCALL_INSTR_LEN; }
else if (event == EVENT_YIELD) { rip += YIELD_INSTR_LEN; }
uc->uc_mcontext.gregs[REG_RIP] = (uintptr_t)rip;
// switch to kernel stack if we were previously in user space
uintptr_t rsp = trap_from_user ? thiscpu->ksp : uc->uc_mcontext.gregs[REG_RSP];
rsp -= sizeof(Context);
// keep (rsp + 8) % 16 == 0 to support SSE
if ((rsp + 8) % 16 != 0) rsp -= 8;
Context *c = (void *)rsp;
// save the context on the stack
c->uc = *uc;
// disable interrupt
__am_get_intr_sigmask(&uc->uc_sigmask);
// call irq_handle after returning from the signal handler
uc->uc_mcontext.gregs[REG_RDI] = (uintptr_t)c;
uc->uc_mcontext.gregs[REG_RIP] = (uintptr_t)irq_handle;
uc->uc_mcontext.gregs[REG_RSP] = (uintptr_t)c;
}
static void iret(ucontext_t *uc) {
Context *c = (void *)uc->uc_mcontext.gregs[REG_RAX];
// restore the context
*uc = c->uc;
thiscpu->ksp = c->ksp;
if (__am_in_userspace((void *)uc->uc_mcontext.gregs[REG_RIP])) __am_pmem_protect();
}
static void sig_handler(int sig, siginfo_t *info, void *ucontext) {
thiscpu->ev = (Event) {0};
thiscpu->ev.event = EVENT_ERROR;
switch (sig) {
case SIGUSR1: thiscpu->ev.event = EVENT_IRQ_IODEV; break;
case SIGVTALRM: thiscpu->ev.event = EVENT_IRQ_TIMER; break;
case SIGSEGV:
if (info->si_code == SEGV_ACCERR) {
switch ((uintptr_t)info->si_addr) {
case 0x100000: thiscpu->ev.event = EVENT_SYSCALL; break;
case 0x100008: thiscpu->ev.event = EVENT_YIELD; break;
case 0x100010: iret(ucontext); return;
}
}
if (__am_in_userspace(info->si_addr)) {
assert(thiscpu->ev.event == EVENT_ERROR);
thiscpu->ev.event = EVENT_PAGEFAULT;
switch (info->si_code) {
case SEGV_MAPERR: thiscpu->ev.cause = MMAP_READ; break;
// we do not support mapped user pages with MMAP_NONE
case SEGV_ACCERR: thiscpu->ev.cause = MMAP_WRITE; break;
default: assert(0);
}
thiscpu->ev.ref = (uintptr_t)info->si_addr;
}
if (thiscpu->ev.event == EVENT_ERROR) {
uintptr_t rip = ((ucontext_t *)ucontext)->uc_mcontext.gregs[REG_RIP];
printf("Unhandle SIGSEGV at rip = %p, badaddr = %p\n", rip, info->si_addr);
}
break;
default: assert(0);
}
assert(thiscpu->ev.event != EVENT_ERROR);
setup_stack(thiscpu->ev.event, ucontext);
}
// signal handlers are inherited across fork()
static void install_signal_handler() {
struct sigaction s;
memset(&s, 0, sizeof(s));
s.sa_sigaction = sig_handler;
s.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK;
__am_get_intr_sigmask(&s.sa_mask);
int ret = sigaction(SIGVTALRM, &s, NULL);
assert(ret == 0);
ret = sigaction(SIGUSR1, &s, NULL);
assert(ret == 0);
ret = sigaction(SIGSEGV, &s, NULL);
assert(ret == 0);
}
// setitimer() are inherited across fork(), should be called again from children
void __am_init_timer_irq() {
iset(0);
struct itimerval it = {};
it.it_value.tv_sec = 0;
it.it_value.tv_usec = 1000000 / TIMER_HZ;
it.it_interval = it.it_value;
int ret = setitimer(ITIMER_VIRTUAL, &it, NULL);
assert(ret == 0);
}
bool cte_init(Context*(*handler)(Event, Context*)) {
user_handler = handler;
install_signal_handler();
__am_init_timer_irq();
return true;
}
Context* kcontext(Area kstack, void (*entry)(void *), void *arg) {
Context *c = (Context*)kstack.end - 1;
__am_get_example_uc(c);
c->uc.uc_mcontext.gregs[REG_RIP] = (uintptr_t)__am_kcontext_start;
c->uc.uc_mcontext.gregs[REG_RSP] = (uintptr_t)kstack.end;
int ret = sigemptyset(&(c->uc.uc_sigmask)); // enable interrupt
assert(ret == 0);
c->vm_head = NULL;
c->GPR1 = (uintptr_t)arg;
c->GPR2 = (uintptr_t)entry;
return c;
}
void yield() {
asm volatile (".byte " YIELD_INSTR);
}
bool ienabled() {
sigset_t set;
int ret = sigprocmask(0, NULL, &set);
assert(ret == 0);
return __am_is_sigmask_sti(&set);
}
void iset(bool enable) {
extern sigset_t __am_intr_sigmask;
// NOTE: sigprocmask does not supported in multithreading
int ret = sigprocmask(enable ? SIG_UNBLOCK : SIG_BLOCK, &__am_intr_sigmask, NULL);
assert(ret == 0);
}

71
am/src/native/ioe.c Normal file
View file

@ -0,0 +1,71 @@
#include <am.h>
#include <klib-macros.h>
bool __am_has_ioe = false;
static bool ioe_init_done = false;
void __am_timer_init();
void __am_gpu_init();
void __am_input_init();
void __am_input_config(AM_INPUT_CONFIG_T *);
void __am_timer_config(AM_TIMER_CONFIG_T *);
void __am_timer_rtc(AM_TIMER_RTC_T *);
void __am_timer_uptime(AM_TIMER_UPTIME_T *);
void __am_input_keybrd(AM_INPUT_KEYBRD_T *);
void __am_gpu_config(AM_GPU_CONFIG_T *);
void __am_gpu_status(AM_GPU_STATUS_T *);
void __am_gpu_fbdraw(AM_GPU_FBDRAW_T *);
void __am_audio_ctrl(AM_AUDIO_CTRL_T *);
void __am_audio_status(AM_AUDIO_STATUS_T *);
void __am_audio_play(AM_AUDIO_PLAY_T *);
static void __am_uart_config(AM_UART_CONFIG_T *cfg) { cfg->present = false; }
static void __am_audio_config(AM_AUDIO_CONFIG_T *cfg) { cfg->present = true; }
static void __am_disk_config(AM_DISK_CONFIG_T *cfg) { cfg->present = false; }
static void __am_net_config (AM_NET_CONFIG_T *cfg) { cfg->present = false; }
typedef void (*handler_t)(void *buf);
static void *lut[128] = {
[AM_TIMER_CONFIG] = __am_timer_config,
[AM_TIMER_RTC ] = __am_timer_rtc,
[AM_TIMER_UPTIME] = __am_timer_uptime,
[AM_INPUT_CONFIG] = __am_input_config,
[AM_INPUT_KEYBRD] = __am_input_keybrd,
[AM_GPU_CONFIG ] = __am_gpu_config,
[AM_GPU_FBDRAW ] = __am_gpu_fbdraw,
[AM_GPU_STATUS ] = __am_gpu_status,
[AM_UART_CONFIG ] = __am_uart_config,
[AM_AUDIO_CONFIG] = __am_audio_config,
[AM_AUDIO_CTRL ] = __am_audio_ctrl,
[AM_AUDIO_STATUS] = __am_audio_status,
[AM_AUDIO_PLAY ] = __am_audio_play,
[AM_DISK_CONFIG ] = __am_disk_config,
[AM_NET_CONFIG ] = __am_net_config,
};
bool ioe_init() {
panic_on(cpu_current() != 0, "call ioe_init() in other CPUs");
panic_on(ioe_init_done, "double-initialization");
__am_has_ioe = true;
return true;
}
static void fail(void *buf) { panic("access nonexist register"); }
void __am_ioe_init() {
for (int i = 0; i < LENGTH(lut); i++)
if (!lut[i]) lut[i] = fail;
__am_timer_init();
__am_gpu_init();
__am_input_init();
ioe_init_done = true;
}
static void do_io(int reg, void *buf) {
if (!ioe_init_done) {
__am_ioe_init();
}
((handler_t)lut[reg])(buf);
}
void ioe_read (int reg, void *buf) { do_io(reg, buf); }
void ioe_write(int reg, void *buf) { do_io(reg, buf); }

51
am/src/native/mpe.c Normal file
View file

@ -0,0 +1,51 @@
#include <stdatomic.h>
#include "platform.h"
int __am_mpe_init = 0;
extern bool __am_has_ioe;
void __am_ioe_init();
bool mpe_init(void (*entry)()) {
__am_mpe_init = 1;
int sync_pipe[2];
assert(0 == pipe(sync_pipe));
for (int i = 1; i < cpu_count(); i++) {
if (fork() == 0) {
char ch;
assert(read(sync_pipe[0], &ch, 1) == 1);
assert(ch == '+');
close(sync_pipe[0]); close(sync_pipe[1]);
thiscpu->cpuid = i;
__am_init_timer_irq();
entry();
}
}
if (__am_has_ioe) {
__am_ioe_init();
}
for (int i = 1; i < cpu_count(); i++) {
assert(write(sync_pipe[1], "+", 1) == 1);
}
close(sync_pipe[0]); close(sync_pipe[1]);
entry();
panic("MP entry should not return\n");
}
int cpu_count() {
extern int __am_ncpu;
return __am_ncpu;
}
int cpu_current() {
return thiscpu->cpuid;
}
int atomic_xchg(int *addr, int newval) {
return atomic_exchange((int *)addr, newval);
}

View file

@ -0,0 +1,74 @@
#include <klib.h>
#include <SDL2/SDL.h>
#define SBUF_SIZE_MAX 65536
static uint8_t sbuf [SBUF_SIZE_MAX] = {};
static int sbuf_size = 0;
static int head = 0, tail = 0;
static volatile int count = 0;
static void audio_play(void *userdata, uint8_t *stream, int len) {
int nread = len;
if (count < len) nread = count;
if (nread + tail < sbuf_size) {
memcpy(stream, sbuf + tail, nread);
tail += nread;
} else {
int first_cpy_len = sbuf_size - tail;
memcpy(stream, sbuf + tail, first_cpy_len);
memcpy(stream + first_cpy_len, sbuf, nread - first_cpy_len);
tail = nread - first_cpy_len;
}
count -= nread;
if (len > nread) memset(stream + nread, 0, len - nread);
}
static int audio_write(uint8_t *buf, int len) {
int free = sbuf_size - count;
int nwrite = len;
if (free < len) nwrite = free;
if (nwrite + head < sbuf_size) {
memcpy(sbuf + head, buf, nwrite);
head += nwrite;
} else {
int first_cpy_len = sbuf_size - head;
memcpy(sbuf + head, buf, first_cpy_len);
memcpy(sbuf, buf + first_cpy_len, nwrite - first_cpy_len);
head = nwrite - first_cpy_len;
}
count += nwrite;
return nwrite;
}
void __am_audio_ctrl(AM_AUDIO_CTRL_T *ctrl) {
SDL_AudioSpec s = {};
s.freq = ctrl->freq;
s.format = AUDIO_S16SYS;
s.channels = ctrl->channels;
s.samples = ctrl->samples;
s.callback = audio_play;
s.userdata = NULL;
sbuf_size = ctrl->bufsize;
assert(sbuf_size <= SBUF_SIZE_MAX);
head = tail = 0;
count = 0;
int ret = SDL_InitSubSystem(SDL_INIT_AUDIO);
if (ret == 0) {
SDL_OpenAudio(&s, NULL);
SDL_PauseAudio(0);
}
}
void __am_audio_status(AM_AUDIO_STATUS_T *stat) {
stat->count = count;
}
void __am_audio_play(AM_AUDIO_PLAY_T *ctl) {
int len = ctl->buf.end - ctl->buf.start;
assert(len <= sbuf_size);
while (sbuf_size - count < len);
audio_write(ctl->buf.start, len);
}

View file

@ -0,0 +1,54 @@
#include <am.h>
#include <SDL2/SDL.h>
#define W 400
#define H 300
#define FPS 60
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
static SDL_Texture *texture = NULL;
static uint32_t fb[W * H] = {};
static inline int min(int x, int y) { return (x < y) ? x : y; }
static Uint32 texture_sync(Uint32 interval, void *param) {
SDL_UpdateTexture(texture, NULL, fb, W * sizeof(Uint32));
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
return interval;
}
void __am_gpu_init() {
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
SDL_CreateWindowAndRenderer(W * 2, H * 2, 0, &window, &renderer);
SDL_SetWindowTitle(window, "Native Application");
texture = SDL_CreateTexture(renderer,
SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, W, H);
memset(fb, 0, W * H * sizeof(uint32_t));
SDL_AddTimer(1000 / FPS, texture_sync, NULL);
}
void __am_gpu_config(AM_GPU_CONFIG_T *cfg) {
*cfg = (AM_GPU_CONFIG_T) {
.present = true, .has_accel = false,
.width = W, .height = H,
.vmemsz = 0
};
}
void __am_gpu_status(AM_GPU_STATUS_T *stat) {
stat->ready = true;
}
void __am_gpu_fbdraw(AM_GPU_FBDRAW_T *ctl) {
int x = ctl->x, y = ctl->y, w = ctl->w, h = ctl->h;
uint32_t *pixels = ctl->pixels;
int cp_bytes = sizeof(uint32_t) * min(w, W - x);
for (int j = 0; j < h && y + j < H; j ++) {
memcpy(&fb[(y + j) * W + x], pixels, cp_bytes);
pixels += w;
}
}

View file

@ -0,0 +1,63 @@
#include <am.h>
#include <SDL2/SDL.h>
#include "platform.h"
#define KEYDOWN_MASK 0x8000
#define KEY_QUEUE_LEN 1024
static int key_queue[KEY_QUEUE_LEN] = {};
static int key_f = 0, key_r = 0;
static SDL_mutex *key_queue_lock = NULL;
#define XX(k) [SDL_SCANCODE_##k] = AM_KEY_##k,
static int keymap[256] = {
AM_KEYS(XX)
};
static int event_thread(void *args) {
SDL_Event event;
while (1) {
SDL_WaitEvent(&event);
switch (event.type) {
case SDL_QUIT: halt(0);
case SDL_KEYDOWN:
case SDL_KEYUP: {
SDL_Keysym k = event.key.keysym;
int keydown = event.key.type == SDL_KEYDOWN;
int scancode = k.scancode;
if (keymap[scancode] != 0) {
int am_code = keymap[scancode] | (keydown ? KEYDOWN_MASK : 0);
SDL_LockMutex(key_queue_lock);
key_queue[key_r] = am_code;
key_r = (key_r + 1) % KEY_QUEUE_LEN;
SDL_UnlockMutex(key_queue_lock);
kill(getpid(), SIGUSR1);
}
break;
}
}
}
}
void __am_input_init() {
key_queue_lock = SDL_CreateMutex();
SDL_CreateThread(event_thread, "event thread", NULL);
}
void __am_input_config(AM_INPUT_CONFIG_T *cfg) {
cfg->present = true;
}
void __am_input_keybrd(AM_INPUT_KEYBRD_T *kbd) {
int k = AM_KEY_NONE;
SDL_LockMutex(key_queue_lock);
if (key_f != key_r) {
k = key_queue[key_f];
key_f = (key_f + 1) % KEY_QUEUE_LEN;
}
SDL_UnlockMutex(key_queue_lock);
kbd->keydown = (k & KEYDOWN_MASK ? true : false);
kbd->keycode = k & ~KEYDOWN_MASK;
}

View file

@ -0,0 +1,33 @@
#include <am.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
static struct timeval boot_time = {};
void __am_timer_config(AM_TIMER_CONFIG_T *cfg) {
cfg->present = cfg->has_rtc = true;
}
void __am_timer_rtc(AM_TIMER_RTC_T *rtc) {
time_t t = time(NULL);
struct tm *tm = localtime(&t);
rtc->second = tm->tm_sec;
rtc->minute = tm->tm_min;
rtc->hour = tm->tm_hour;
rtc->day = tm->tm_mday;
rtc->month = tm->tm_mon + 1;
rtc->year = tm->tm_year + 1900;
}
void __am_timer_uptime(AM_TIMER_UPTIME_T *uptime) {
struct timeval now;
gettimeofday(&now, NULL);
long seconds = now.tv_sec - boot_time.tv_sec;
long useconds = now.tv_usec - boot_time.tv_usec;
uptime->us = seconds * 1000000 + (useconds + 500);
}
void __am_timer_init() {
gettimeofday(&boot_time, NULL);
}

212
am/src/native/platform.c Normal file
View file

@ -0,0 +1,212 @@
#define _GNU_SOURCE
#include <sys/mman.h>
#include <sys/auxv.h>
#include <elf.h>
#include <stdlib.h>
#include <stdio.h>
#include "platform.h"
#define MAX_CPU 16
#define TRAP_PAGE_START (void *)0x100000
#define PMEM_START (void *)0x3000000 // for nanos-lite with vme disabled
#define PMEM_SIZE (128 * 1024 * 1024)
static int pmem_fd = 0;
static void *pmem = NULL;
static ucontext_t uc_example = {};
static int sys_pgsz;
sigset_t __am_intr_sigmask = {};
__am_cpu_t *__am_cpu_struct = NULL;
int __am_ncpu = 0;
int __am_pgsize;
static void save_context_handler(int sig, siginfo_t *info, void *ucontext) {
memcpy(&uc_example, ucontext, sizeof(uc_example));
}
static void save_example_context() {
// getcontext() does not save segment registers. In the signal
// handler, restoring a context previously saved by getcontext()
// will trigger segmentation fault because of the invalid segment
// registers. So we save the example context during signal handling
// to get a context with everything valid.
struct sigaction s;
memset(&s, 0, sizeof(s));
s.sa_sigaction = save_context_handler;
s.sa_flags = SA_SIGINFO;
int ret = sigaction(SIGUSR1, &s, NULL);
assert(ret == 0);
raise(SIGUSR1);
s.sa_flags = 0;
s.sa_handler = SIG_DFL;
ret = sigaction(SIGUSR1, &s, NULL);
assert(ret == 0);
}
static void setup_sigaltstack() {
stack_t ss;
ss.ss_sp = thiscpu->sigstack;
ss.ss_size = sizeof(thiscpu->sigstack);
ss.ss_flags = 0;
int ret = sigaltstack(&ss, NULL);
assert(ret == 0);
}
int main(const char *args);
static void init_platform() __attribute__((constructor));
static void init_platform() {
// create memory object and set up mapping to simulate the physical memory
pmem_fd = memfd_create("pmem", 0);
assert(pmem_fd != -1);
assert(0 == ftruncate(pmem_fd, PMEM_SIZE));
pmem = mmap(PMEM_START, PMEM_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_SHARED | MAP_FIXED, pmem_fd, 0);
assert(pmem != (void *)-1);
// allocate private per-cpu structure
thiscpu = mmap(NULL, sizeof(*thiscpu), PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(thiscpu != (void *)-1);
thiscpu->cpuid = 0;
thiscpu->vm_head = NULL;
// create trap page to receive syscall and yield by SIGSEGV
sys_pgsz = sysconf(_SC_PAGESIZE);
void *ret = mmap(TRAP_PAGE_START, sys_pgsz, PROT_NONE,
MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
assert(ret != (void *)-1);
// remap writable sections as MAP_SHARED
Elf64_Phdr *phdr = (void *)getauxval(AT_PHDR);
int phnum = (int)getauxval(AT_PHNUM);
int i;
int ret2;
for (i = 0; i < phnum; i ++) {
if (phdr[i].p_type == PT_LOAD && (phdr[i].p_flags & PF_W)) {
// allocate temporary memory
extern char end;
void *vaddr = (void *)&end - phdr[i].p_memsz;
uintptr_t pad = (uintptr_t)vaddr & 0xfff;
void *vaddr_align = vaddr - pad;
uintptr_t size = phdr[i].p_memsz + pad;
void *temp_mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(temp_mem != (void *)-1);
// save data and bss sections
memcpy(temp_mem, vaddr_align, size);
// save the addresses of library functions which will be used after munamp()
// since calling the library functions requires accessing GOT, which will be unmapped
void *(*volatile mmap_libc)(void *, size_t, int, int, int, off_t) = &mmap;
void *(*volatile memcpy_libc)(void *, const void *, size_t) = &memcpy;
// unmap the data and bss sections
ret2 = munmap(vaddr_align, size);
assert(ret2 == 0);
// map the sections again with MAP_SHARED, which will be shared across fork()
ret = mmap_libc(vaddr_align, size, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
assert(ret == vaddr_align);
// restore the data in the sections
memcpy_libc(vaddr_align, temp_mem, size);
// unmap the temporary memory
ret2 = munmap(temp_mem, size);
assert(ret2 == 0);
}
}
// set up the AM heap
heap = RANGE(pmem, pmem + PMEM_SIZE);
// initialize sigmask for interrupts
ret2 = sigemptyset(&__am_intr_sigmask);
assert(ret2 == 0);
ret2 = sigaddset(&__am_intr_sigmask, SIGVTALRM);
assert(ret2 == 0);
ret2 = sigaddset(&__am_intr_sigmask, SIGUSR1);
assert(ret2 == 0);
// setup alternative signal stack
setup_sigaltstack();
// save the context template
save_example_context();
__am_get_intr_sigmask(&uc_example.uc_sigmask);
// disable interrupts by default
iset(0);
// set ncpu
const char *smp = getenv("smp");
__am_ncpu = smp ? atoi(smp) : 1;
assert(0 < __am_ncpu && __am_ncpu <= MAX_CPU);
// set pgsize
const char *pgsize = getenv("pgsize");
__am_pgsize = pgsize ? atoi(pgsize) : sys_pgsz;
assert(__am_pgsize > 0 && __am_pgsize % sys_pgsz == 0);
// set stdout unbuffered
setbuf(stdout, NULL);
const char *args = getenv("mainargs");
halt(main(args ? args : "")); // call main here!
}
void __am_exit_platform(int code) {
// let Linux clean up other resource
extern int __am_mpe_init;
if (__am_mpe_init && cpu_count() > 1) kill(0, SIGKILL);
exit(code);
}
void __am_pmem_map(void *va, void *pa, int prot) {
// translate AM prot to mmap prot
int mmap_prot = PROT_NONE;
// we do not support executable bit, so mark
// all readable pages executable as well
if (prot & MMAP_READ) mmap_prot |= PROT_READ | PROT_EXEC;
if (prot & MMAP_WRITE) mmap_prot |= PROT_WRITE;
void *ret = mmap(va, __am_pgsize, mmap_prot,
MAP_SHARED | MAP_FIXED, pmem_fd, (uintptr_t)(pa - pmem));
assert(ret != (void *)-1);
}
void __am_pmem_unmap(void *va) {
int ret = munmap(va, __am_pgsize);
assert(ret == 0);
}
void __am_get_example_uc(Context *r) {
memcpy(&r->uc, &uc_example, sizeof(uc_example));
}
void __am_get_intr_sigmask(sigset_t *s) {
memcpy(s, &__am_intr_sigmask, sizeof(__am_intr_sigmask));
}
int __am_is_sigmask_sti(sigset_t *s) {
return !sigismember(s, SIGVTALRM);
}
void __am_pmem_protect() {
int ret = mprotect(PMEM_START, PMEM_SIZE, PROT_NONE);
assert(ret == 0);
}
void __am_pmem_unprotect() {
int ret = mprotect(PMEM_START, PMEM_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC);
assert(ret == 0);
}
// This dummy function will be called in trm.c.
// The purpose of this dummy function is to let linker add this file to the object
// file set. Without it, the constructor of @_init_platform will not be linked.
void __am_platform_dummy() {
}

28
am/src/native/platform.h Normal file
View file

@ -0,0 +1,28 @@
#ifndef __PLATFORM_H__
#define __PLATFORM_H__
#include <am.h>
#include <unistd.h>
#include <signal.h>
#include <klib.h>
#include <klib-macros.h>
void __am_get_example_uc(Context *r);
void __am_get_intr_sigmask(sigset_t *s);
int __am_is_sigmask_sti(sigset_t *s);
void __am_init_timer_irq();
void __am_pmem_map(void *va, void *pa, int prot);
void __am_pmem_unmap(void *va);
// per-cpu structure
typedef struct {
void *vm_head;
uintptr_t ksp;
int cpuid;
Event ev; // similar to cause register in mips/riscv
uint8_t sigstack[SIGSTKSZ];
} __am_cpu_t;
extern __am_cpu_t *__am_cpu_struct;
#define thiscpu __am_cpu_struct
#endif

10
am/src/native/trap.S Normal file
View file

@ -0,0 +1,10 @@
.global __am_kcontext_start
__am_kcontext_start:
// rdi = arg, rsi = entry
// (rsp + 8) should be multiple of 16 when
// control is transfered to the function entry point.
// See amd64 ABI manual for more details
andq $0xfffffffffffffff0, %rsp
call *%rsi
call __am_panic_on_return

22
am/src/native/trm.c Normal file
View file

@ -0,0 +1,22 @@
#include <am.h>
#include <stdio.h>
void __am_platform_dummy();
void __am_exit_platform(int code);
void trm_init() {
__am_platform_dummy();
}
void putch(char ch) {
putchar(ch);
}
void halt(int code) {
printf("Exit (%d)\n", code);
__am_exit_platform(code);
printf("Should not reach here!\n");
while (1);
}
Area heap = {};

116
am/src/native/vme.c Normal file
View file

@ -0,0 +1,116 @@
#include "platform.h"
#define USER_SPACE RANGE(0x40000000, 0xc0000000)
typedef struct PageMap {
void *va;
void *pa;
struct PageMap *next;
int prot;
int is_mapped;
} PageMap;
#define list_foreach(p, head) \
for (p = ((PageMap *)(head))->next; p != NULL; p = p->next)
extern int __am_pgsize;
static int vme_enable = 0;
static void* (*pgalloc)(int) = NULL;
static void (*pgfree)(void *) = NULL;
bool vme_init(void* (*pgalloc_f)(int), void (*pgfree_f)(void*)) {
pgalloc = pgalloc_f;
pgfree = pgfree_f;
vme_enable = 1;
return true;
}
void protect(AddrSpace *as) {
assert(as != NULL);
as->ptr = pgalloc(__am_pgsize); // used as head of the list
as->pgsize = __am_pgsize;
as->area = USER_SPACE;
}
void unprotect(AddrSpace *as) {
}
void __am_switch(Context *c) {
if (!vme_enable) return;
PageMap *head = c->vm_head;
if (head == thiscpu->vm_head) return;
PageMap *pp;
if (thiscpu->vm_head != NULL) {
// munmap all mappings
list_foreach(pp, thiscpu->vm_head) {
if (pp->is_mapped) {
__am_pmem_unmap(pp->va);
pp->is_mapped = false;
}
}
}
if (head != NULL) {
// mmap all mappings
list_foreach(pp, head) {
assert(IN_RANGE(pp->va, USER_SPACE));
__am_pmem_map(pp->va, pp->pa, pp->prot);
pp->is_mapped = true;
}
}
thiscpu->vm_head = head;
}
void map(AddrSpace *as, void *va, void *pa, int prot) {
assert(IN_RANGE(va, USER_SPACE));
assert((uintptr_t)va % __am_pgsize == 0);
assert((uintptr_t)pa % __am_pgsize == 0);
assert(as != NULL);
PageMap *pp = NULL;
PageMap *vm_head = as->ptr;
assert(vm_head != NULL);
list_foreach(pp, vm_head) {
if (pp->va == va) break;
}
if (pp == NULL) {
pp = pgalloc(__am_pgsize); // this will waste memory, any better idea?
}
pp->va = va;
pp->pa = pa;
pp->prot = prot;
pp->is_mapped = false;
// add after to vm_head to keep vm_head unchanged,
// since vm_head is a key to describe an address space
pp->next = vm_head->next;
vm_head->next = pp;
if (vm_head == thiscpu->vm_head) {
// enforce the map immediately
__am_pmem_map(pp->va, pp->pa, pp->prot);
pp->is_mapped = true;
}
}
Context* ucontext(AddrSpace *as, Area kstack, void *entry) {
Context *c = (Context*)kstack.end - 1;
__am_get_example_uc(c);
c->uc.uc_mcontext.gregs[REG_RIP] = (uintptr_t)entry;
c->uc.uc_mcontext.gregs[REG_RSP] = (uintptr_t)USER_SPACE.end;
int ret = sigemptyset(&(c->uc.uc_sigmask)); // enable interrupt
assert(ret == 0);
c->vm_head = as->ptr;
c->ksp = (uintptr_t)kstack.end;
return c;
}
int __am_in_userspace(void *addr) {
return vme_enable && thiscpu->vm_head != NULL && IN_RANGE(addr, USER_SPACE);
}

View file

@ -0,0 +1,47 @@
#ifndef NEMU_H__
#define NEMU_H__
#include <klib-macros.h>
#include ISA_H // "x86.h", "mips32.h", ...
#if defined(__ISA_X86__)
# define nemu_trap(code) asm volatile (".byte 0xd6" : :"a"(code))
#elif defined(__ISA_MIPS32__)
# define nemu_trap(code) asm volatile ("move $v0, %0; .word 0xf0000000" : :"r"(code))
#elif defined(__ISA_RISCV32__)
# define nemu_trap(code) asm volatile("mv a0, %0; .word 0x0000006b" : :"r"(code))
#elif
# error unsupported ISA __ISA__
#endif
#ifdef __ARCH_X86_NEMU
# define SERIAL_PORT 0x3f8
# define KBD_ADDR 0x60
# define RTC_ADDR 0x48
# define SCREEN_ADDR 0x100
# define AUDIO_ADDR 0x200
#else
# define SERIAL_PORT 0xa10003f8
# define KBD_ADDR 0xa1000060
# define RTC_ADDR 0xa1000048
# define SCREEN_ADDR 0xa1000100
# define AUDIO_ADDR 0xa1000200
#endif
#define FB_ADDR 0xa0000000
#define AUDIO_SBUF_ADDR 0xa0800000
extern char _pmem_start;
#define PMEM_SIZE (128 * 1024 * 1024)
#define PMEM_END ((uintptr_t)&_pmem_start + PMEM_SIZE)
#define NEMU_PADDR_SPACE \
RANGE(&_pmem_start, PMEM_END), \
RANGE(FB_ADDR, FB_ADDR + 0x200000), \
RANGE(0xa1000000, 0xa1000000 + 0x1000) /* serial, rtc, screen, keyboard */
typedef uintptr_t PTE;
#define PGSIZE 4096
#endif

23
am/src/nemu/ioe/audio.c Normal file
View file

@ -0,0 +1,23 @@
#include <am.h>
#include <nemu.h>
#define AUDIO_FREQ_ADDR (AUDIO_ADDR + 0x00)
#define AUDIO_CHANNELS_ADDR (AUDIO_ADDR + 0x04)
#define AUDIO_SAMPLES_ADDR (AUDIO_ADDR + 0x08)
#define AUDIO_SBUF_SIZE_ADDR (AUDIO_ADDR + 0x0c)
#define AUDIO_INIT_ADDR (AUDIO_ADDR + 0x10)
#define AUDIO_COUNT_ADDR (AUDIO_ADDR + 0x14)
void __am_audio_config(AM_AUDIO_CONFIG_T *cfg) {
cfg->present = false;
}
void __am_audio_ctrl(AM_AUDIO_CTRL_T *ctrl) {
}
void __am_audio_status(AM_AUDIO_STATUS_T *stat) {
stat->count = 0;
}
void __am_audio_play(AM_AUDIO_PLAY_T *ctl) {
}

25
am/src/nemu/ioe/gpu.c Normal file
View file

@ -0,0 +1,25 @@
#include <am.h>
#include <nemu.h>
#define SYNC_ADDR (SCREEN_ADDR + 4)
void __am_gpu_init() {
}
void __am_gpu_config(AM_GPU_CONFIG_T *cfg) {
*cfg = (AM_GPU_CONFIG_T) {
.present = true, .has_accel = false,
.width = 0, .height = 0,
.vmemsz = 0
};
}
void __am_gpu_fbdraw(AM_GPU_FBDRAW_T *ctl) {
if (ctl->sync) {
outl(SYNC_ADDR, 0);
}
}
void __am_gpu_status(AM_GPU_STATUS_T *status) {
status->ready = true;
}

9
am/src/nemu/ioe/input.c Normal file
View file

@ -0,0 +1,9 @@
#include <am.h>
#include <nemu.h>
#define KEYDOWN_MASK 0x8000
void __am_input_keybrd(AM_INPUT_KEYBRD_T *kbd) {
kbd->keydown = 0;
kbd->keycode = AM_KEY_NONE;
}

53
am/src/nemu/ioe/ioe.c Normal file
View file

@ -0,0 +1,53 @@
#include <am.h>
#include <klib-macros.h>
void __am_timer_init();
void __am_gpu_init();
void __am_input_keybrd(AM_INPUT_KEYBRD_T *);
void __am_timer_rtc(AM_TIMER_RTC_T *);
void __am_timer_uptime(AM_TIMER_UPTIME_T *);
void __am_gpu_config(AM_GPU_CONFIG_T *);
void __am_gpu_status(AM_GPU_STATUS_T *);
void __am_gpu_fbdraw(AM_GPU_FBDRAW_T *);
void __am_audio_config(AM_AUDIO_CONFIG_T *);
void __am_audio_ctrl(AM_AUDIO_CTRL_T *);
void __am_audio_status(AM_AUDIO_STATUS_T *);
void __am_audio_play(AM_AUDIO_PLAY_T *);
static void __am_timer_config(AM_TIMER_CONFIG_T *cfg) { cfg->present = true; cfg->has_rtc = true; }
static void __am_input_config(AM_INPUT_CONFIG_T *cfg) { cfg->present = true; }
static void __am_uart_config(AM_UART_CONFIG_T *cfg) { cfg->present = false; }
static void __am_disk_config(AM_DISK_CONFIG_T *cfg) { cfg->present = false; }
static void __am_net_config (AM_NET_CONFIG_T *cfg) { cfg->present = false; }
typedef void (*handler_t)(void *buf);
static void *lut[128] = {
[AM_TIMER_CONFIG] = __am_timer_config,
[AM_TIMER_RTC ] = __am_timer_rtc,
[AM_TIMER_UPTIME] = __am_timer_uptime,
[AM_INPUT_CONFIG] = __am_input_config,
[AM_INPUT_KEYBRD] = __am_input_keybrd,
[AM_GPU_CONFIG ] = __am_gpu_config,
[AM_GPU_FBDRAW ] = __am_gpu_fbdraw,
[AM_GPU_STATUS ] = __am_gpu_status,
[AM_UART_CONFIG ] = __am_uart_config,
[AM_AUDIO_CONFIG] = __am_audio_config,
[AM_AUDIO_CTRL ] = __am_audio_ctrl,
[AM_AUDIO_STATUS] = __am_audio_status,
[AM_AUDIO_PLAY ] = __am_audio_play,
[AM_DISK_CONFIG ] = __am_disk_config,
[AM_NET_CONFIG ] = __am_net_config,
};
static void fail(void *buf) { panic("access nonexist register"); }
bool ioe_init() {
for (int i = 0; i < LENGTH(lut); i++)
if (!lut[i]) lut[i] = fail;
__am_gpu_init();
__am_timer_init();
return true;
}
void ioe_read (int reg, void *buf) { ((handler_t)lut[reg])(buf); }
void ioe_write(int reg, void *buf) { ((handler_t)lut[reg])(buf); }

18
am/src/nemu/ioe/timer.c Normal file
View file

@ -0,0 +1,18 @@
#include <am.h>
#include <nemu.h>
void __am_timer_init() {
}
void __am_timer_uptime(AM_TIMER_UPTIME_T *uptime) {
uptime->us = 0;
}
void __am_timer_rtc(AM_TIMER_RTC_T *rtc) {
rtc->second = 0;
rtc->minute = 0;
rtc->hour = 0;
rtc->day = 0;
rtc->month = 0;
rtc->year = 1900;
}

View file

@ -0,0 +1,4 @@
_pmem_start = 0x80000000;
/* at $(AM_HOME)/am/src/nemu/scripts/section.ld */
INCLUDE "section.ld"

View file

@ -0,0 +1,8 @@
.section entry, "ax"
.globl _start
.type _start, @function
_start:
move $fp, $zero
la $sp, _stack_pointer
jal _trm_init

View file

@ -0,0 +1,54 @@
#include <am.h>
#include <mips32.h>
#include <klib.h>
static Context* (*user_handler)(Event, Context*) = NULL;
Context* __am_irq_handle(Context *c) {
if (user_handler) {
Event ev = {0};
uint32_t ex_code = 0;
switch (ex_code) {
default: ev.event = EVENT_ERROR; break;
}
c = user_handler(ev, c);
assert(c != NULL);
}
return c;
}
extern void __am_asm_trap(void);
#define EX_ENTRY 0x80000180
bool cte_init(Context*(*handler)(Event, Context*)) {
// initialize exception entry
const uint32_t j_opcode = 0x08000000;
uint32_t instr = j_opcode | (((uint32_t)__am_asm_trap >> 2) & 0x3ffffff);
*(uint32_t *)EX_ENTRY = instr;
*(uint32_t *)(EX_ENTRY + 4) = 0; // delay slot
*(uint32_t *)0x80000000 = instr; // TLB refill exception
*(uint32_t *)(0x80000000 + 4) = 0; // delay slot
// register event handler
user_handler = handler;
return true;
}
Context *kcontext(Area kstack, void (*entry)(void *), void *arg) {
return NULL;
}
void yield() {
asm volatile("syscall 1");
}
bool ienabled() {
return false;
}
void iset(bool enable) {
}

View file

@ -0,0 +1,71 @@
#define MAP(c, f) c(f)
#define REGS(f) \
f( 1) f( 2) f( 3) f( 4) f( 5) f( 6) f( 7) f( 8) f( 9) \
f(10) f(11) f(12) f(13) f(14) f(15) f(16) f(17) f(18) f(19) \
f(20) f(21) f(22) f(23) f(24) f(25) f(28) \
f(30) f(31)
#define PUSH(n) sw $n, (n * 4)($sp);
#define POP(n) lw $n, (n * 4)($sp);
#define CONTEXT_SIZE ((31 + 6) * 4)
#define OFFSET_SP (29 * 4)
#define OFFSET_LO (32 * 4)
#define OFFSET_HI (33 * 4)
#define OFFSET_CAUSE (34 * 4)
#define OFFSET_STATUS (35 * 4)
#define OFFSET_EPC (36 * 4)
#define CP0_STATUS 12
#define CP0_CAUSE 13
#define CP0_EPC 14
.set noat
.globl __am_asm_trap
__am_asm_trap:
move $k0, $sp
addiu $sp, $sp, -CONTEXT_SIZE
MAP(REGS, PUSH)
sw $k0, OFFSET_SP($sp)
mflo $t0
mfhi $t1
mfc0 $t2, $CP0_CAUSE
mfc0 $t3, $CP0_STATUS
mfc0 $t4, $CP0_EPC
sw $t0, OFFSET_LO($sp)
sw $t1, OFFSET_HI($sp)
sw $t2, OFFSET_CAUSE($sp)
sw $t3, OFFSET_STATUS($sp)
sw $t4, OFFSET_EPC($sp)
# allow nested exception
li $a0, ~0x3
and $t3, $t3, $a0 # clear status.exl and status.ie
mtc0 $t3, $CP0_STATUS
move $a0, $sp
jal __am_irq_handle
lw $t0, OFFSET_LO($sp)
lw $t1, OFFSET_HI($sp)
lw $t3, OFFSET_STATUS($sp)
lw $t4, OFFSET_EPC($sp)
# set status.exl
ori $t3, $t3, 0x2
mtlo $t0
mthi $t1
mtc0 $t3, $CP0_STATUS
mtc0 $t4, $CP0_EPC
MAP(REGS, POP)
addiu $sp, $sp, CONTEXT_SIZE
eret

View file

@ -0,0 +1,44 @@
#include <am.h>
#include <mips32.h>
#include <nemu.h>
#define USER_SPACE RANGE(0x40000000, 0x80000000)
static void* (*pgalloc_usr)(int) = NULL;
static void (*pgfree_usr)(void*) = NULL;
static int vme_enable = 0;
bool vme_init(void* (*pgalloc_f)(int), void (*pgfree_f)(void*)) {
pgalloc_usr = pgalloc_f;
pgfree_usr = pgfree_f;
vme_enable = 1;
return true;
}
void protect(AddrSpace *as) {
as->ptr = (PTE*)(pgalloc_usr(PGSIZE));
as->pgsize = PGSIZE;
as->area = USER_SPACE;
}
void unprotect(AddrSpace *as) {
}
static PTE *cur_pdir = NULL;
void __am_get_cur_as(Context *c) {
c->pdir = cur_pdir;
}
void __am_switch(Context *c) {
if (vme_enable && c->pdir != NULL) {
cur_pdir = c->pdir;
}
}
void map(AddrSpace *as, void *va, void *pa, int prot) {
}
Context *ucontext(AddrSpace *as, Area kstack, void *entry) {
return NULL;
}

View file

@ -0,0 +1,4 @@
_pmem_start = 0x80000000;
/* at $(AM_HOME)/am/src/nemu/scripts/section.ld */
INCLUDE "section.ld"

View file

@ -0,0 +1,8 @@
.section entry, "ax"
.globl _start
.type _start, @function
_start:
mv s0, zero
la sp, _stack_pointer
jal _trm_init

View file

@ -0,0 +1,46 @@
#include <am.h>
#include <riscv32.h>
#include <klib.h>
static Context* (*user_handler)(Event, Context*) = NULL;
Context* __am_irq_handle(Context *c) {
if (user_handler) {
Event ev = {0};
switch (c->cause) {
default: ev.event = EVENT_ERROR; break;
}
c = user_handler(ev, c);
assert(c != NULL);
}
return c;
}
extern void __am_asm_trap(void);
bool cte_init(Context*(*handler)(Event, Context*)) {
// initialize exception entry
asm volatile("csrw stvec, %0" : : "r"(__am_asm_trap));
// register event handler
user_handler = handler;
return true;
}
Context *kcontext(Area kstack, void (*entry)(void *), void *arg) {
return NULL;
}
void yield() {
asm volatile("li a7, -1; ecall");
}
bool ienabled() {
return false;
}
void iset(bool enable) {
}

View file

@ -0,0 +1,51 @@
#define concat_temp(x, y) x ## y
#define concat(x, y) concat_temp(x, y)
#define MAP(c, f) c(f)
#define REGS(f) \
f( 1) f( 3) f( 4) f( 5) f( 6) f( 7) f( 8) f( 9) \
f(10) f(11) f(12) f(13) f(14) f(15) f(16) f(17) f(18) f(19) \
f(20) f(21) f(22) f(23) f(24) f(25) f(26) f(27) f(28) f(29) \
f(30) f(31)
#define PUSH(n) sw concat(x, n), (n * 4)(sp);
#define POP(n) lw concat(x, n), (n * 4)(sp);
#define CONTEXT_SIZE ((32 + 3) * 4)
#define OFFSET_SP ( 2 * 4)
#define OFFSET_CAUSE (32 * 4)
#define OFFSET_STATUS (33 * 4)
#define OFFSET_EPC (34 * 4)
.globl __am_asm_trap
__am_asm_trap:
addi sp, sp, -CONTEXT_SIZE
MAP(REGS, PUSH)
mv t0, sp
addi t0, t0, CONTEXT_SIZE
sw t0, OFFSET_SP(sp)
csrr t0, scause
csrr t1, sstatus
csrr t2, sepc
sw t0, OFFSET_CAUSE(sp)
sw t1, OFFSET_STATUS(sp)
sw t2, OFFSET_EPC(sp)
mv a0, sp
jal __am_irq_handle
lw t1, OFFSET_STATUS(sp)
lw t2, OFFSET_EPC(sp)
csrw sstatus, t1
csrw sepc, t2
MAP(REGS, POP)
addi sp, sp, CONTEXT_SIZE
sret

View file

@ -0,0 +1,73 @@
#include <am.h>
#include <nemu.h>
#include <klib.h>
static AddrSpace kas = {};
static void* (*pgalloc_usr)(int) = NULL;
static void (*pgfree_usr)(void*) = NULL;
static int vme_enable = 0;
static Area segments[] = { // Kernel memory mappings
NEMU_PADDR_SPACE
};
#define USER_SPACE RANGE(0x40000000, 0x80000000)
static inline void set_satp(void *pdir) {
asm volatile("csrw satp, %0" : : "r"(0x80000000 | ((uintptr_t)pdir >> 12)));
}
static inline uintptr_t get_satp() {
uintptr_t satp;
asm volatile("csrr %0, satp" : "=r"(satp));
return satp << 12;
}
bool vme_init(void* (*pgalloc_f)(int), void (*pgfree_f)(void*)) {
pgalloc_usr = pgalloc_f;
pgfree_usr = pgfree_f;
kas.ptr = pgalloc_f(PGSIZE);
int i;
for (i = 0; i < LENGTH(segments); i ++) {
void *va = segments[i].start;
for (; va < segments[i].end; va += PGSIZE) {
map(&kas, va, va, 0);
}
}
set_satp(kas.ptr);
vme_enable = 1;
return true;
}
void protect(AddrSpace *as) {
PTE *updir = (PTE*)(pgalloc_usr(PGSIZE));
as->ptr = updir;
as->area = USER_SPACE;
as->pgsize = PGSIZE;
// map kernel space
memcpy(updir, kas.ptr, PGSIZE);
}
void unprotect(AddrSpace *as) {
}
void __am_get_cur_as(Context *c) {
c->pdir = (vme_enable ? (void *)get_satp() : NULL);
}
void __am_switch(Context *c) {
if (vme_enable && c->pdir != NULL) {
set_satp(c->pdir);
}
}
void map(AddrSpace *as, void *va, void *pa, int prot) {
}
Context *ucontext(AddrSpace *as, Area kstack, void *entry) {
return NULL;
}

View file

@ -0,0 +1,4 @@
_pmem_start = 0x0;
/* at $(AM_HOME)/am/src/nemu/scripts/section.ld */
INCLUDE "section.ld"

View file

@ -0,0 +1,8 @@
.section entry, "ax"
.globl _start
.type _start, @function
_start:
mov $0, %ebp
mov $_stack_pointer, %esp
call _trm_init # never return

67
am/src/nemu/isa/x86/cte.c Normal file
View file

@ -0,0 +1,67 @@
#include <am.h>
#include <x86.h>
#include <klib.h>
#define NR_IRQ 256 // IDT size
#define SEG_KCODE 1
#define SEG_KDATA 2
static Context* (*user_handler)(Event, Context*) = NULL;
void __am_irq0();
void __am_vecsys();
void __am_vectrap();
void __am_vecnull();
Context* __am_irq_handle(Context *c) {
if (user_handler) {
Event ev = {0};
switch (c->irq) {
default: ev.event = EVENT_ERROR; break;
}
c = user_handler(ev, c);
assert(c != NULL);
}
return c;
}
bool cte_init(Context*(*handler)(Event, Context*)) {
static GateDesc32 idt[NR_IRQ];
// initialize IDT
for (unsigned int i = 0; i < NR_IRQ; i ++) {
idt[i] = GATE32(STS_TG, KSEL(SEG_KCODE), __am_vecnull, DPL_KERN);
}
// ----------------------- interrupts ----------------------------
idt[32] = GATE32(STS_IG, KSEL(SEG_KCODE), __am_irq0, DPL_KERN);
// ---------------------- system call ----------------------------
idt[0x80] = GATE32(STS_TG, KSEL(SEG_KCODE), __am_vecsys, DPL_USER);
idt[0x81] = GATE32(STS_TG, KSEL(SEG_KCODE), __am_vectrap, DPL_KERN);
set_idt(idt, sizeof(idt));
// register event handler
user_handler = handler;
return true;
}
Context* kcontext(Area kstack, void (*entry)(void *), void *arg) {
return NULL;
}
void yield() {
asm volatile("int $0x81");
}
bool ienabled() {
return false;
}
void iset(bool enable) {
}

View file

@ -0,0 +1,22 @@
#----|------------entry------------|---usp---|---irq id---|-----handler-----|
.globl __am_vecsys; __am_vecsys: pushl $0; pushl $0x80; jmp __am_asm_trap
.globl __am_vectrap; __am_vectrap: pushl $0; pushl $0x81; jmp __am_asm_trap
.globl __am_irq0; __am_irq0: pushl $0; pushl $32; jmp __am_asm_trap
.globl __am_vecnull; __am_vecnull: pushl $0; pushl $-1; jmp __am_asm_trap
__am_asm_trap:
pushal
pushl $0
pushl %esp
call __am_irq_handle
addl $4, %esp
addl $4, %esp
popal
addl $4, %esp
iret

64
am/src/nemu/isa/x86/vme.c Normal file
View file

@ -0,0 +1,64 @@
#include <am.h>
#include <nemu.h>
#include <klib.h>
static AddrSpace kas = {};
static void* (*pgalloc_usr)(int) = NULL;
static void (*pgfree_usr)(void*) = NULL;
static int vme_enable = 0;
static Area segments[] = { // Kernel memory mappings
NEMU_PADDR_SPACE
};
#define USER_SPACE RANGE(0x40000000, 0xc0000000)
bool vme_init(void* (*pgalloc_f)(int), void (*pgfree_f)(void*)) {
pgalloc_usr = pgalloc_f;
pgfree_usr = pgfree_f;
kas.ptr = pgalloc_f(PGSIZE);
int i;
for (i = 0; i < LENGTH(segments); i ++) {
void *va = segments[i].start;
for (; va < segments[i].end; va += PGSIZE) {
map(&kas, va, va, 0);
}
}
set_cr3(kas.ptr);
set_cr0(get_cr0() | CR0_PG);
vme_enable = 1;
return true;
}
void protect(AddrSpace *as) {
PTE *updir = (PTE*)(pgalloc_usr(PGSIZE));
as->ptr = updir;
as->area = USER_SPACE;
as->pgsize = PGSIZE;
// map kernel space
memcpy(updir, kas.ptr, PGSIZE);
}
void unprotect(AddrSpace *as) {
}
void __am_get_cur_as(Context *c) {
c->cr3 = (vme_enable ? (void *)get_cr3() : NULL);
}
void __am_switch(Context *c) {
if (vme_enable && c->cr3 != NULL) {
set_cr3(c->cr3);
}
}
void map(AddrSpace *as, void *va, void *pa, int prot) {
}
Context* ucontext(AddrSpace *as, Area kstack, void *entry) {
return NULL;
}

17
am/src/nemu/mpe.c Normal file
View file

@ -0,0 +1,17 @@
#include <am.h>
bool mpe_init(void (*entry)()) {
return false;
}
int cpu_count() {
return 1;
}
int cpu_current() {
return 0;
}
int atomic_xchg(int *addr, intptr_t newval) {
return 0;
}

View file

@ -0,0 +1,31 @@
ENTRY(_start)
SECTIONS {
. = _pmem_start + 0x100000;
.text : {
*(entry)
*(.text*)
}
etext = .;
_etext = .;
.rodata : {
*(.rodata*)
}
.data : {
*(.data)
}
edata = .;
_data = .;
.bss : {
_bss_start = .;
*(.bss*)
*(.sbss*)
*(.scommon)
}
_stack_top = ALIGN(0x1000);
. = _stack_top + 0x8000;
_stack_pointer = .;
end = .;
_end = .;
_heap_start = ALIGN(0x1000);
}

27
am/src/nemu/trm.c Normal file
View file

@ -0,0 +1,27 @@
#include <am.h>
#include <nemu.h>
extern char _heap_start;
int main(const char *args);
Area heap = RANGE(&_heap_start, PMEM_END);
#ifndef MAINARGS
#define MAINARGS ""
#endif
static const char mainargs[] = MAINARGS;
void putch(char ch) {
outb(SERIAL_PORT, ch);
}
void halt(int code) {
nemu_trap(code);
// should not reach here
while (1);
}
void _trm_init() {
int ret = main(mainargs);
halt(ret);
}

20
am/src/riscv32.h Normal file
View file

@ -0,0 +1,20 @@
#ifndef RISCV32_H__
#define RISCV32_H__
#include <stdint.h>
static inline uint8_t inb(uintptr_t addr) { return *(volatile uint8_t *)addr; }
static inline uint16_t inw(uintptr_t addr) { return *(volatile uint16_t *)addr; }
static inline uint32_t inl(uintptr_t addr) { return *(volatile uint32_t *)addr; }
static inline void outb(uintptr_t addr, uint8_t data) { *(volatile uint8_t *)addr = data; }
static inline void outw(uintptr_t addr, uint16_t data) { *(volatile uint16_t *)addr = data; }
static inline void outl(uintptr_t addr, uint32_t data) { *(volatile uint32_t *)addr = data; }
#define PTE_V 0x01
#define PTE_R 0x02
#define PTE_W 0x04
#define PTE_X 0x08
#define PTE_U 0x10
#endif

353
am/src/x86.h Normal file
View file

@ -0,0 +1,353 @@
// CPU rings
#define DPL_KERN 0x0 // Kernel (ring 0)
#define DPL_USER 0x3 // User (ring 3)
// Application Segment type bits
#define STA_X 0x8 // Executable segment
#define STA_W 0x2 // Writeable (non-executable segments)
#define STA_R 0x2 // Readable (executable segments)
// System Segment type bits
#define STS_T32A 0x9 // Available 32-bit TSS
#define STS_IG 0xe // 32/64-bit Interrupt Gate
#define STS_TG 0xf // 32/64-bit Trap Gate
// EFLAGS register
#define FL_IF 0x00000200 // Interrupt Enable
// Control Register flags
#define CR0_PE 0x00000001 // Protection Enable
#define CR0_PG 0x80000000 // Paging
#define CR4_PAE 0x00000020 // Physical Address Extension
// Page table/directory entry flags
#define PTE_P 0x001 // Present
#define PTE_W 0x002 // Writeable
#define PTE_U 0x004 // User
#define PTE_PS 0x080 // Large Page (1 GiB or 2 MiB)
// GDT selectors
#define KSEL(seg) (((seg) << 3) | DPL_KERN)
#define USEL(seg) (((seg) << 3) | DPL_USER)
// Interrupts and exceptions
#define T_IRQ0 32
#define IRQ_TIMER 0
#define IRQ_KBD 1
#define IRQ_COM1 4
#define IRQ_ERROR 19
#define IRQ_SPURIOUS 31
#define EX_DE 0
#define EX_UD 6
#define EX_NM 7
#define EX_DF 8
#define EX_TS 10
#define EX_NP 11
#define EX_SS 12
#define EX_GP 13
#define EX_PF 14
#define EX_MF 15
#define EX_SYSCALL 0x80
#define EX_YIELD 0x81
// List of interrupts and exceptions (#irq, DPL, hardware errorcode)
#define IRQS(_) \
_( 0, KERN, NOERR) \
_( 1, KERN, NOERR) \
_( 2, KERN, NOERR) \
_( 3, KERN, NOERR) \
_( 4, KERN, NOERR) \
_( 5, KERN, NOERR) \
_( 6, KERN, NOERR) \
_( 7, KERN, NOERR) \
_( 8, KERN, ERR) \
_( 9, KERN, NOERR) \
_( 10, KERN, ERR) \
_( 11, KERN, ERR) \
_( 12, KERN, ERR) \
_( 13, KERN, ERR) \
_( 14, KERN, ERR) \
_( 15, KERN, NOERR) \
_( 16, KERN, NOERR) \
_( 19, KERN, NOERR) \
_( 31, KERN, NOERR) \
_( 32, KERN, NOERR) \
_( 33, KERN, NOERR) \
_( 34, KERN, NOERR) \
_( 35, KERN, NOERR) \
_( 36, KERN, NOERR) \
_( 37, KERN, NOERR) \
_( 38, KERN, NOERR) \
_( 39, KERN, NOERR) \
_( 40, KERN, NOERR) \
_( 41, KERN, NOERR) \
_( 42, KERN, NOERR) \
_( 43, KERN, NOERR) \
_( 44, KERN, NOERR) \
_( 45, KERN, NOERR) \
_( 46, KERN, NOERR) \
_( 47, KERN, NOERR) \
_(128, USER, NOERR) \
_(129, USER, NOERR)
// AM-specific configurations
#define MAX_CPU 8
#define BOOTREC_ADDR 0x07000
#define MAINARG_ADDR 0x10000
// Below are only visible to c/c++ files
#ifndef __ASSEMBLER__
#include <stdint.h>
// Segment Descriptor
typedef struct {
uint32_t lim_15_0 : 16; // Low bits of segment limit
uint32_t base_15_0 : 16; // Low bits of segment base address
uint32_t base_23_16 : 8; // Middle bits of segment base address
uint32_t type : 4; // Segment type (see STS_ constants)
uint32_t s : 1; // 0 = system, 1 = application
uint32_t dpl : 2; // Descriptor Privilege Level
uint32_t p : 1; // Present
uint32_t lim_19_16 : 4; // High bits of segment limit
uint32_t avl : 1; // Unused (available for software use)
uint32_t l : 1; // 64-bit segment
uint32_t db : 1; // 32-bit segment
uint32_t g : 1; // Granularity: limit scaled by 4K when set
uint32_t base_31_24 : 8; // High bits of segment base address
} SegDesc;
// Gate descriptors for interrupts and traps
typedef struct {
uint32_t off_15_0 : 16; // Low 16 bits of offset in segment
uint32_t cs : 16; // Code segment selector
uint32_t args : 5; // # args, 0 for interrupt/trap gates
uint32_t rsv1 : 3; // Reserved(should be zero I guess)
uint32_t type : 4; // Type(STS_{TG,IG32,TG32})
uint32_t s : 1; // Must be 0 (system)
uint32_t dpl : 2; // Descriptor(meaning new) privilege level
uint32_t p : 1; // Present
uint32_t off_31_16 : 16; // High bits of offset in segment
} GateDesc32;
typedef struct {
uint32_t off_15_0 : 16;
uint32_t cs : 16;
uint32_t isv : 3;
uint32_t zero1 : 5;
uint32_t type : 4;
uint32_t zero2 : 1;
uint32_t dpl : 2;
uint32_t p : 1;
uint32_t off_31_16 : 16;
uint32_t off_63_32 : 32;
uint32_t rsv : 32;
} GateDesc64;
// Task State Segment (TSS)
typedef struct {
uint32_t link; // Unused
uint32_t esp0; // Stack pointers and segment selectors
uint32_t ss0; // after an increase in privilege level
uint32_t padding[23];
} __attribute__((packed)) TSS32;
typedef struct {
uint32_t rsv;
uint64_t rsp0, rsp1, rsp2;
uint32_t padding[19];
} __attribute__((packed)) TSS64;
// Multiprocesor configuration
typedef struct { // configuration table header
uint8_t signature[4]; // "PCMP"
uint16_t length; // total table length
uint8_t version; // [14]
uint8_t checksum; // all bytes must add up to 0
uint8_t product[20]; // product id
uint32_t oemtable; // OEM table pointer
uint16_t oemlength; // OEM table length
uint16_t entry; // entry count
uint32_t lapicaddr; // address of local APIC
uint16_t xlength; // extended table length
uint8_t xchecksum; // extended table checksum
uint8_t reserved;
} MPConf;
typedef struct {
int magic;
uint32_t conf; // MP config table addr
uint8_t length; // 1
uint8_t specrev; // [14]
uint8_t checksum; // all bytes add to 0
uint8_t type; // config type
uint8_t imcrp;
uint8_t reserved[3];
} MPDesc;
typedef struct {
uint32_t jmp_code;
int32_t is_ap;
} BootRecord;
#define SEG16(type, base, lim, dpl) (SegDesc) \
{ (lim) & 0xffff, (uintptr_t)(base) & 0xffff, \
((uintptr_t)(base) >> 16) & 0xff, type, 0, dpl, 1, \
(uintptr_t)(lim) >> 16, 0, 0, 1, 0, (uintptr_t)(base) >> 24 }
#define SEG32(type, base, lim, dpl) (SegDesc) \
{ ((lim) >> 12) & 0xffff, (uintptr_t)(base) & 0xffff, \
((uintptr_t)(base) >> 16) & 0xff, type, 1, dpl, 1, \
(uintptr_t)(lim) >> 28, 0, 0, 1, 1, (uintptr_t)(base) >> 24 }
#define SEG64(type, dpl) (SegDesc) \
{ 0, 0, 0, type, 1, dpl, 1, 0, 0, 1, 0, 0 }
#define SEGTSS64(type, base, lim, dpl) (SegDesc) \
{ (lim) & 0xffff, (uint32_t)(base) & 0xffff, \
((uint32_t)(base) >> 16) & 0xff, type, 0, dpl, 1, \
(uint32_t)(lim) >> 16, 0, 0, 0, 0, (uint32_t)(base) >> 24 }
#define GATE32(type, cs, entry, dpl) (GateDesc32) \
{ (uint32_t)(entry) & 0xffff, (cs), 0, 0, (type), 0, (dpl), \
1, (uint32_t)(entry) >> 16 }
#define GATE64(type, cs, entry, dpl) (GateDesc64) \
{ (uint64_t)(entry) & 0xffff, (cs), 0, 0, (type), 0, (dpl), \
1, ((uint64_t)(entry) >> 16) & 0xffff, (uint64_t)(entry) >> 32, 0 }
// Instruction wrappers
static inline uint8_t inb(int port) {
uint8_t data;
asm volatile ("inb %1, %0" : "=a"(data) : "d"((uint16_t)port));
return data;
}
static inline uint16_t inw(int port) {
uint16_t data;
asm volatile ("inw %1, %0" : "=a"(data) : "d"((uint16_t)port));
return data;
}
static inline uint32_t inl(int port) {
uint32_t data;
asm volatile ("inl %1, %0" : "=a"(data) : "d"((uint16_t)port));
return data;
}
static inline void outb(int port, uint8_t data) {
asm volatile ("outb %%al, %%dx" : : "a"(data), "d"((uint16_t)port));
}
static inline void outw(int port, uint16_t data) {
asm volatile ("outw %%ax, %%dx" : : "a"(data), "d"((uint16_t)port));
}
static inline void outl(int port, uint32_t data) {
asm volatile ("outl %%eax, %%dx" : : "a"(data), "d"((uint16_t)port));
}
static inline void cli() {
asm volatile ("cli");
}
static inline void sti() {
asm volatile ("sti");
}
static inline void hlt() {
asm volatile ("hlt");
}
static inline void pause() {
asm volatile ("pause");
}
static inline uint32_t get_efl() {
volatile uintptr_t efl;
asm volatile ("pushf; pop %0": "=r"(efl));
return efl;
}
static inline uintptr_t get_cr0(void) {
volatile uintptr_t val;
asm volatile ("mov %%cr0, %0" : "=r"(val));
return val;
}
static inline void set_cr0(uintptr_t cr0) {
asm volatile ("mov %0, %%cr0" : : "r"(cr0));
}
static inline void set_idt(void *idt, int size) {
static volatile struct {
int16_t size;
void *idt;
} __attribute__((packed)) data;
data.size = size;
data.idt = idt;
asm volatile ("lidt (%0)" : : "r"(&data));
}
static inline void set_gdt(void *gdt, int size) {
static volatile struct {
int16_t size;
void *gdt;
} __attribute__((packed)) data;
data.size = size;
data.gdt = gdt;
asm volatile ("lgdt (%0)" : : "r"(&data));
}
static inline void set_tr(int selector) {
asm volatile ("ltr %0" : : "r"((uint16_t)selector));
}
static inline uintptr_t get_cr2() {
volatile uintptr_t val;
asm volatile ("mov %%cr2, %0" : "=r"(val));
return val;
}
static inline uintptr_t get_cr3() {
volatile uintptr_t val;
asm volatile ("mov %%cr3, %0" : "=r"(val));
return val;
}
static inline void set_cr3(void *pdir) {
asm volatile ("mov %0, %%cr3" : : "r"(pdir));
}
static inline int xchg(int *addr, int newval) {
int result;
asm volatile ("lock xchg %0, %1":
"+m"(*addr), "=a"(result) : "1"(newval) : "cc", "memory");
return result;
}
static inline uint64_t rdtsc() {
uint32_t lo, hi;
asm volatile ("rdtsc": "=a"(lo), "=d"(hi));
return ((uint64_t)hi << 32) | lo;
}
#define interrupt(id) \
asm volatile ("int $" #id);
static inline void stack_switch_call(void *sp, void *entry, uintptr_t arg) {
asm volatile (
#if __x86_64__
"movq %0, %%rsp; movq %2, %%rdi; jmp *%1" : : "b"((uintptr_t)sp), "d"(entry), "a"(arg)
#else
"movl %0, %%esp; movl %2, 4(%0); jmp *%1" : : "b"((uintptr_t)sp - 8), "d"(entry), "a"(arg)
#endif
);
}
static inline volatile BootRecord *boot_record() {
return (BootRecord *)BOOTREC_ADDR;
}
#endif // __ASSEMBLER__

View file

@ -0,0 +1,8 @@
SRCS := start.S main.c
bootblock.o: $(SRCS) Makefile
@echo + CC $(SRCS)
@gcc -m32 -Os -nostdlib -Ttext 0x7c00 -I$(AM_HOME)/am/src -o bootblock.o $(SRCS)
@python3 genboot.py bootblock.o
clean:
rm -rf *.o

View file

@ -0,0 +1,13 @@
import sys, pathlib, subprocess
f = pathlib.Path(sys.argv[1])
try:
data = subprocess.run(
['objcopy', '-S', '-O', 'binary', '-j', '.text', f, '/dev/stdout'],
capture_output=True).stdout
assert len(data) <= 510
data += b'\0' * (510 - len(data)) + b'\x55\xaa'
f.write_bytes(data)
except:
f.unlink()
raise

View file

@ -0,0 +1,90 @@
#include <stdint.h>
#include <elf.h>
#include <x86.h>
#define SECTSIZE 512
#define ARGSIZE 1024
static inline void wait_disk(void) {
while ((inb(0x1f7) & 0xc0) != 0x40);
}
static inline void read_disk(void *buf, int sect) {
wait_disk();
outb(0x1f2, 1);
outb(0x1f3, sect);
outb(0x1f4, sect >> 8);
outb(0x1f5, sect >> 16);
outb(0x1f6, (sect >> 24) | 0xE0);
outb(0x1f7, 0x20);
wait_disk();
for (int i = 0; i < SECTSIZE / 4; i ++) {
((uint32_t *)buf)[i] = inl(0x1f0);
}
}
static inline void copy_from_disk(void *buf, int nbytes, int disk_offset) {
uint32_t cur = (uint32_t)buf & ~(SECTSIZE - 1);
uint32_t ed = (uint32_t)buf + nbytes;
uint32_t sect = (disk_offset / SECTSIZE) + (ARGSIZE / SECTSIZE) + 1;
for(; cur < ed; cur += SECTSIZE, sect ++)
read_disk((void *)cur, sect);
}
static void load_program(uint32_t filesz, uint32_t memsz, uint32_t paddr, uint32_t offset) {
copy_from_disk((void *)paddr, filesz, offset);
char *bss = (void *)(paddr + filesz);
for (uint32_t i = filesz; i != memsz; i++) {
*bss++ = 0;
}
}
static void load_elf64(Elf64_Ehdr *elf) {
Elf64_Phdr *ph = (Elf64_Phdr *)((char *)elf + elf->e_phoff);
for (int i = 0; i < elf->e_phnum; i++, ph++) {
load_program(
(uint32_t)ph->p_filesz,
(uint32_t)ph->p_memsz,
(uint32_t)ph->p_paddr,
(uint32_t)ph->p_offset
);
}
}
static void load_elf32(Elf32_Ehdr *elf) {
Elf32_Phdr *ph = (Elf32_Phdr *)((char *)elf + elf->e_phoff);
for (int i = 0; i < elf->e_phnum; i++, ph++) {
load_program(
(uint32_t)ph->p_filesz,
(uint32_t)ph->p_memsz,
(uint32_t)ph->p_paddr,
(uint32_t)ph->p_offset
);
}
}
void load_kernel(void) {
Elf32_Ehdr *elf32 = (void *)0x8000;
Elf64_Ehdr *elf64 = (void *)0x8000;
int is_ap = boot_record()->is_ap;
if (!is_ap) {
// load argument (string) to memory
copy_from_disk((void *)MAINARG_ADDR, 1024, -1024);
// load elf header to memory
copy_from_disk(elf32, 4096, 0);
if (elf32->e_machine == EM_X86_64) {
load_elf64(elf64);
} else {
load_elf32(elf32);
}
} else {
// everything should be loaded
}
if (elf32->e_machine == EM_X86_64) {
((void(*)())(uint32_t)elf64->e_entry)();
} else {
((void(*)())(uint32_t)elf32->e_entry)();
}
}

View file

@ -0,0 +1,60 @@
#define CR0_PE 0x00000001
#define GDT_ENTRY(n) \
((n) << 3)
#define SEG_NULLASM \
.word 0, 0; \
.byte 0, 0, 0, 0
#define SEG_ASM(type, base, lim) \
.word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \
.byte (((base) >> 16) & 0xff), (0x90 | (type)), \
(0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff)
.code16
.globl _start
_start:
cli
xorw %ax, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
# Set a 640 x 480 x 32 video mode
mov $0x4f01, %ax
mov $0x0112, %cx
mov $0x4000, %di
int $0x10
mov $0x4f02, %ax
mov $0x4112, %bx
int $0x10
lgdt gdtdesc
movl %cr0, %eax
orl $CR0_PE, %eax
movl %eax, %cr0
ljmp $GDT_ENTRY(1), $start32
.code32
start32:
movw $GDT_ENTRY(2), %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
movl $0xa000, %esp
call load_kernel
# GDT
.p2align 2
gdt:
SEG_NULLASM
SEG_ASM(0xA, 0x0, 0xffffffff)
SEG_ASM(0x2, 0x0, 0xffffffff)
gdtdesc:
.word (gdtdesc - gdt - 1)
.long gdt

165
am/src/x86/qemu/cte.c Normal file
View file

@ -0,0 +1,165 @@
#include "x86-qemu.h"
static Context* (*user_handler)(Event, Context*) = NULL;
#if __x86_64__
static GateDesc64 idt[NR_IRQ];
#define GATE GATE64
#else
static GateDesc32 idt[NR_IRQ];
#define GATE GATE32
#endif
#define IRQHANDLE_DECL(id, dpl, err) \
void __am_irq##id();
IRQS(IRQHANDLE_DECL)
void __am_irqall();
void __amkcontext_start();
void __am_irq_handle(struct trap_frame *tf) {
Context *saved_ctx = &tf->saved_context;
Event ev = {
.event = EVENT_NULL,
.cause = 0, .ref = 0,
.msg = "(no message)",
};
#if __x86_64
saved_ctx->rip = tf->rip;
saved_ctx->cs = tf->cs;
saved_ctx->rflags = tf->rflags;
saved_ctx->rsp = tf->rsp;
saved_ctx->rsp0 = CPU->tss.rsp0;
saved_ctx->ss = tf->ss;
#else
saved_ctx->eip = tf->eip;
saved_ctx->cs = tf->cs;
saved_ctx->eflags = tf->eflags;
saved_ctx->esp0 = CPU->tss.esp0;
saved_ctx->ss3 = USEL(SEG_UDATA);
// no ss/esp saved for DPL_KERNEL
saved_ctx->esp = (tf->cs & DPL_USER ? tf->esp : (uint32_t)(tf + 1) - 8);
#endif
saved_ctx->cr3 = (void *)get_cr3();
#define IRQ T_IRQ0 +
#define MSG(m) ev.msg = m;
if (IRQ 0 <= tf->irq && tf->irq < IRQ 32) {
__am_lapic_eoi();
}
switch (tf->irq) {
case IRQ 0: MSG("timer interrupt (lapic)")
ev.event = EVENT_IRQ_TIMER; break;
case IRQ 1: MSG("I/O device IRQ1 (keyboard)")
ev.event = EVENT_IRQ_IODEV; break;
case IRQ 4: MSG("I/O device IRQ4 (COM1)")
ev.event = EVENT_IRQ_IODEV; break;
case EX_SYSCALL: MSG("int $0x80 system call")
ev.event = EVENT_SYSCALL; break;
case EX_YIELD: MSG("int $0x81 yield")
ev.event = EVENT_YIELD; break;
case EX_DE: MSG("DE #0 divide by zero")
ev.event = EVENT_ERROR; break;
case EX_UD: MSG("UD #6 invalid opcode")
ev.event = EVENT_ERROR; break;
case EX_NM: MSG("NM #7 coprocessor error")
ev.event = EVENT_ERROR; break;
case EX_DF: MSG("DF #8 double fault")
ev.event = EVENT_ERROR; break;
case EX_TS: MSG("TS #10 invalid TSS")
ev.event = EVENT_ERROR; break;
case EX_NP: MSG("NP #11 segment/gate not present")
ev.event = EVENT_ERROR; break;
case EX_SS: MSG("SS #12 stack fault")
ev.event = EVENT_ERROR; break;
case EX_GP: MSG("GP #13, general protection fault")
ev.event = EVENT_ERROR; break;
case EX_PF: MSG("PF #14, page fault, @cause: PROT_XXX")
ev.event = EVENT_PAGEFAULT;
if (tf->errcode & 0x1) ev.cause |= MMAP_NONE;
if (tf->errcode & 0x2) ev.cause |= MMAP_WRITE;
else ev.cause |= MMAP_READ;
ev.ref = get_cr2();
break;
default: MSG("unrecognized interrupt/exception")
ev.event = EVENT_ERROR;
ev.cause = tf->errcode;
break;
}
Context *ret_ctx = user_handler(ev, saved_ctx);
panic_on(!ret_ctx, "returning to NULL context");
if (ret_ctx->cr3) {
set_cr3(ret_ctx->cr3);
#if __x86_64__
CPU->tss.rsp0 = ret_ctx->rsp0;
#else
CPU->tss.ss0 = KSEL(SEG_KDATA);
CPU->tss.esp0 = ret_ctx->esp0;
#endif
}
__am_iret(ret_ctx);
}
bool cte_init(Context *(*handler)(Event, Context *)) {
panic_on(cpu_current() != 0, "init CTE in non-bootstrap CPU");
panic_on(!handler, "no interrupt handler");
for (int i = 0; i < NR_IRQ; i ++) {
idt[i] = GATE(STS_TG, KSEL(SEG_KCODE), __am_irqall, DPL_KERN);
}
#define IDT_ENTRY(id, dpl, err) \
idt[id] = GATE(STS_TG, KSEL(SEG_KCODE), __am_irq##id, DPL_##dpl);
IRQS(IDT_ENTRY)
user_handler = handler;
return true;
}
void yield() {
interrupt(0x81);
}
bool ienabled() {
return (get_efl() & FL_IF) != 0;
}
void iset(bool enable) {
if (enable) sti();
else cli();
}
void __am_panic_on_return() { panic("kernel context returns"); }
Context* kcontext(Area kstack, void (*entry)(void *), void *arg) {
Context *ctx = kstack.end - sizeof(Context);
*ctx = (Context) { 0 };
#if __x86_64__
ctx->cs = KSEL(SEG_KCODE);
ctx->rip = (uintptr_t)__amkcontext_start;
ctx->rflags = FL_IF;
ctx->rsp = (uintptr_t)kstack.end;
#else
ctx->ds = KSEL(SEG_KDATA);
ctx->cs = KSEL(SEG_KCODE);
ctx->eip = (uintptr_t)__amkcontext_start;
ctx->eflags = FL_IF;
ctx->esp = (uintptr_t)kstack.end;
#endif
ctx->GPR1 = (uintptr_t)arg;
ctx->GPR2 = (uintptr_t)entry;
return ctx;
}
void __am_percpu_initirq() {
__am_ioapic_enable(IRQ_KBD, 0);
__am_ioapic_enable(IRQ_COM1, 0);
set_idt(idt, sizeof(idt));
}

471
am/src/x86/qemu/ioe.c Normal file
View file

@ -0,0 +1,471 @@
#include "x86-qemu.h"
#include <klib.h> // TODO: delete
// UART
// ====================================================
#define COM1 0x3f8
static int uart_init() {
outb(COM1 + 2, 0);
outb(COM1 + 3, 0x80);
outb(COM1 + 0, 115200 / 9600);
outb(COM1 + 1, 0);
outb(COM1 + 3, 0x03);
outb(COM1 + 4, 0);
outb(COM1 + 1, 0x01);
inb (COM1 + 2);
inb (COM1 + 0);
return 0;
}
static void uart_config(AM_UART_CONFIG_T *cfg) {
cfg->present = true;
}
static void uart_tx(AM_UART_TX_T *send) {
outb(COM1, send->data);
}
static void uart_rx(AM_UART_RX_T *recv) {
recv->data = (inb(COM1 + 5) & 0x1) ? inb(COM1) : -1;
}
// Timer
// ====================================================
static AM_TIMER_RTC_T boot_date;
static uint32_t freq_mhz = 2000;
static uint64_t uptsc;
static void timer_rtc(AM_TIMER_RTC_T *rtc);
static inline int read_rtc(int reg) {
outb(0x70, reg);
int ret = inb(0x71);
return (ret & 0xf) + (ret >> 4) * 10;
}
static void read_rtc_async(AM_TIMER_RTC_T *rtc) {
*rtc = (AM_TIMER_RTC_T) {
.second = read_rtc(0),
.minute = read_rtc(2),
.hour = read_rtc(4),
.day = read_rtc(7),
.month = read_rtc(8),
.year = read_rtc(9) + 2000,
};
}
static void wait_sec(AM_TIMER_RTC_T *t1) {
AM_TIMER_RTC_T t0;
while (1) {
read_rtc_async(&t0);
for (int volatile i = 0; i < 100000; i++) ;
read_rtc_async(t1);
if (t0.second != t1->second) {
return;
}
}
}
static uint32_t estimate_freq() {
AM_TIMER_RTC_T rtc1, rtc2;
uint64_t tsc1, tsc2, t1, t2;
wait_sec(&rtc1); tsc1 = rdtsc(); t1 = rtc1.minute * 60 + rtc1.second;
wait_sec(&rtc2); tsc2 = rdtsc(); t2 = rtc2.minute * 60 + rtc2.second;
if (t1 >= t2) return estimate_freq(); // passed an hour; try again
return ((tsc2 - tsc1) >> 20) / (t2 - t1);
}
static void timer_init() {
freq_mhz = estimate_freq();
timer_rtc(&boot_date);
uptsc = rdtsc();
}
static void timer_config(AM_TIMER_CONFIG_T *cfg) {
cfg->present = cfg->has_rtc = true;
}
static void timer_rtc(AM_TIMER_RTC_T *rtc) {
int tmp;
do {
read_rtc_async(rtc);
tmp = read_rtc(0);
} while (tmp != rtc->second);
}
static void timer_uptime(AM_TIMER_UPTIME_T *upt) {
upt->us = (rdtsc() - uptsc) / freq_mhz;
}
// Input
// ====================================================
static int keylut[128] = {
[0x01] = AM_KEY_ESCAPE, [0x02] = AM_KEY_1, [0x03] = AM_KEY_2,
[0x04] = AM_KEY_3, [0x05] = AM_KEY_4, [0x06] = AM_KEY_5, [0x07] = AM_KEY_6,
[0x08] = AM_KEY_7, [0x09] = AM_KEY_8, [0x0a] = AM_KEY_9, [0x0b] = AM_KEY_0,
[0x0c] = AM_KEY_MINUS, [0x0d] = AM_KEY_EQUALS,
[0x0e] = AM_KEY_BACKSPACE, [0x0f] = AM_KEY_TAB,
[0x10] = AM_KEY_Q, [0x11] = AM_KEY_W, [0x12] = AM_KEY_E, [0x13] = AM_KEY_R,
[0x14] = AM_KEY_T, [0x15] = AM_KEY_Y, [0x16] = AM_KEY_U, [0x17] = AM_KEY_I,
[0x18] = AM_KEY_O, [0x19] = AM_KEY_P, [0x1a] = AM_KEY_LEFTBRACKET,
[0x1b] = AM_KEY_RIGHTBRACKET, [0x1c] = AM_KEY_RETURN,
[0x1d] = AM_KEY_LCTRL, [0x1e] = AM_KEY_A, [0x1f] = AM_KEY_S,
[0x20] = AM_KEY_D, [0x21] = AM_KEY_F, [0x22] = AM_KEY_G, [0x23] = AM_KEY_H,
[0x24] = AM_KEY_J, [0x25] = AM_KEY_K, [0x26] = AM_KEY_L,
[0x27] = AM_KEY_SEMICOLON, [0x28] = AM_KEY_APOSTROPHE,
[0x29] = AM_KEY_GRAVE, [0x2a] = AM_KEY_LSHIFT,
[0x2b] = AM_KEY_BACKSLASH, [0x2c] = AM_KEY_Z, [0x2d] = AM_KEY_X,
[0x2e] = AM_KEY_C, [0x2f] = AM_KEY_V, [0x30] = AM_KEY_B, [0x31] = AM_KEY_N,
[0x32] = AM_KEY_M, [0x33] = AM_KEY_COMMA, [0x34] = AM_KEY_PERIOD,
[0x35] = AM_KEY_SLASH, [0x36] = AM_KEY_RSHIFT, [0x38] = AM_KEY_LALT,
[0x38] = AM_KEY_RALT, [0x39] = AM_KEY_SPACE, [0x3a] = AM_KEY_CAPSLOCK,
[0x3b] = AM_KEY_F1, [0x3c] = AM_KEY_F2, [0x3d] = AM_KEY_F3,
[0x3e] = AM_KEY_F4, [0x3f] = AM_KEY_F5, [0x40] = AM_KEY_F6,
[0x41] = AM_KEY_F7, [0x42] = AM_KEY_F8, [0x43] = AM_KEY_F9,
[0x44] = AM_KEY_F10, [0x48] = AM_KEY_INSERT,
[0x4b] = AM_KEY_HOME, [0x4d] = AM_KEY_END, [0x50] = AM_KEY_DELETE,
[0x57] = AM_KEY_F11, [0x58] = AM_KEY_F12, [0x5b] = AM_KEY_APPLICATION,
};
static void input_config(AM_INPUT_CONFIG_T *cfg) {
cfg->present = true;
}
static void input_keybrd(AM_INPUT_KEYBRD_T *ev) {
if (inb(0x64) & 0x1) {
int code = inb(0x60) & 0xff;
ev->keydown = code < 128;
ev->keycode = keylut[code & 0x7f];
} else {
ev->keydown = false;
ev->keycode = AM_KEY_NONE;
}
}
// GPU (Frame Buffer and 2D Accelerated Graphics)
// ====================================================
#define VMEM_SIZE (512 << 10)
struct vbe_info {
uint8_t ignore[18];
uint16_t width;
uint16_t height;
uint8_t ignore1[18];
uint32_t framebuffer;
} __attribute__ ((packed));
static inline uint8_t R(uint32_t p) { return p >> 16; }
static inline uint8_t G(uint32_t p) { return p >> 8; }
static inline uint8_t B(uint32_t p) { return p; }
struct pixel {
uint8_t b, g, r;
} __attribute__ ((packed));
static struct pixel *fb;
static uint8_t vmem[VMEM_SIZE], vbuf[VMEM_SIZE], *vbuf_head;
static struct gpu_canvas display;
static inline void *to_host(gpuptr_t ptr) { return ptr == AM_GPU_NULL ? NULL : vmem + ptr; }
static void gpu_init() {
struct vbe_info *info = (struct vbe_info *)0x00004000;
display.w = info->width;
display.h = info->height;
fb = (void *)((intptr_t)(info->framebuffer));
}
static void gpu_config(AM_GPU_CONFIG_T *cfg) {
*cfg = (AM_GPU_CONFIG_T) {
.present = true,
.width = display.w, .height = display.h,
.vmemsz = sizeof(vmem),
};
}
static void gpu_fbdraw(AM_GPU_FBDRAW_T *draw) {
int x = draw->x, y = draw->y, w = draw->w, h = draw->h;
int W = display.w, H = display.h;
uint32_t *pixels = draw->pixels;
int len = (x + w >= W) ? W - x : w;
for (int j = 0; j < h; j ++, pixels += w) {
if (y + j < H) {
struct pixel *px = &fb[x + (j + y) * W];
for (int i = 0; i < len; i ++, px ++) {
uint32_t p = pixels[i];
*px = (struct pixel) { .r = R(p), .g = G(p), .b = B(p) };
}
}
}
}
static void gpu_status(AM_GPU_STATUS_T *stat) {
stat->ready = true;
}
static void gpu_memcpy(AM_GPU_MEMCPY_T *params) {
char *src = params->src, *dst = to_host(params->dest);
for (int i = 0; i < params->size; i++)
dst[i] = src[i];
}
static void *vbuf_alloc(int size) {
void *ret = vbuf_head;
vbuf_head += size;
panic_on(vbuf_head > vbuf + sizeof(vbuf), "no memory");
for (int i = 0; i < size; i++)
((char *)ret)[i] = 0;
return ret;
}
static struct pixel *render(struct gpu_canvas *cv, struct gpu_canvas *parent, struct pixel *px) {
struct pixel *px_local;
int W = parent->w, w, h;
switch (cv->type) {
case AM_GPU_TEXTURE: {
w = cv->texture.w; h = cv->texture.h;
px_local = to_host(cv->texture.pixels);
break;
}
case AM_GPU_SUBTREE: {
w = cv->w; h = cv->h;
px_local = vbuf_alloc(w * h * sizeof(struct pixel));
for (struct gpu_canvas *ch = to_host(cv->child); ch; ch = to_host(ch->sibling)) {
render(ch, cv, px_local);
}
break;
}
default:
panic("invalid node");
}
// draw local canvas (w * h) -> px (x1, y1) - (x1 + w1, y1 + h1)
for (int i = 0; i < cv->w1; i++)
for (int j = 0; j < cv->h1; j++) {
int x = cv->x1 + i, y = cv->y1 + j;
px[W * y + x] = px_local[w * (j * h / cv->h1) + (i * w / cv->w1)];
}
return 0;
}
static void gpu_render(AM_GPU_RENDER_T *ren) {
vbuf_head = vbuf;
render(to_host(ren->root), &display, fb);
}
// Disk (ATA0)
// ====================================================
#define BLKSZ 512
#define DISKSZ (64 << 20)
static void disk_config(AM_DISK_CONFIG_T *cfg) {
cfg->present = true;
cfg->blksz = BLKSZ;
cfg->blkcnt = DISKSZ / BLKSZ;
}
static void disk_status(AM_DISK_STATUS_T *status) {
status->ready = true;
}
static inline void wait_disk(void) {
while ((inb(0x1f7) & 0xc0) != 0x40);
}
static void disk_blkio(AM_DISK_BLKIO_T *bio) {
uint32_t blkno = bio->blkno, remain = bio->blkcnt;
uint32_t *ptr = bio->buf;
for (remain = bio->blkcnt; remain; remain--, blkno++) {
wait_disk();
outb(0x1f2, 1);
outb(0x1f3, blkno);
outb(0x1f4, blkno >> 8);
outb(0x1f5, blkno >> 16);
outb(0x1f6, (blkno >> 24) | 0xe0);
outb(0x1f7, bio->write? 0x30 : 0x20);
wait_disk();
if (bio->write) {
for (int i = 0; i < BLKSZ / 4; i ++)
outl(0x1f0, *ptr++);
} else {
for (int i = 0; i < BLKSZ / 4; i ++)
*ptr++ = inl(0x1f0);
}
}
}
// ====================================================
static void audio_config(AM_AUDIO_CONFIG_T *cfg) { cfg->present = false; }
static void net_config(AM_NET_CONFIG_T *cfg) { cfg->present = false; }
static void fail(void *buf) { panic("access nonexist register"); }
typedef void (*handler_t)(void *buf);
static void *lut[128] = {
[AM_UART_CONFIG ] = uart_config,
[AM_UART_TX ] = uart_tx,
[AM_UART_RX ] = uart_rx,
[AM_TIMER_CONFIG] = timer_config,
[AM_TIMER_RTC ] = timer_rtc,
[AM_TIMER_UPTIME] = timer_uptime,
[AM_INPUT_CONFIG] = input_config,
[AM_INPUT_KEYBRD] = input_keybrd,
[AM_GPU_CONFIG ] = gpu_config,
[AM_GPU_FBDRAW ] = gpu_fbdraw,
[AM_GPU_STATUS ] = gpu_status,
[AM_GPU_MEMCPY ] = gpu_memcpy,
[AM_GPU_RENDER ] = gpu_render,
[AM_AUDIO_CONFIG] = audio_config,
[AM_DISK_CONFIG ] = disk_config,
[AM_DISK_STATUS ] = disk_status,
[AM_DISK_BLKIO ] = disk_blkio,
[AM_NET_CONFIG ] = net_config,
};
bool ioe_init() {
panic_on(cpu_current() != 0, "init IOE in non-bootstrap CPU");
for (int i = 0; i < LENGTH(lut); i++)
if (!lut[i]) lut[i] = fail;
uart_init();
timer_init();
gpu_init();
return true;
}
void ioe_read (int reg, void *buf) { ((handler_t)lut[reg])(buf); }
void ioe_write(int reg, void *buf) { ((handler_t)lut[reg])(buf); }
// LAPIC/IOAPIC (from xv6)
#define ID (0x0020/4) // ID
#define VER (0x0030/4) // Version
#define TPR (0x0080/4) // Task Priority
#define EOI (0x00B0/4) // EOI
#define SVR (0x00F0/4) // Spurious Interrupt Vector
#define ENABLE 0x00000100 // Unit Enable
#define ESR (0x0280/4) // Error Status
#define ICRLO (0x0300/4) // Interrupt Command
#define INIT 0x00000500 // INIT/RESET
#define STARTUP 0x00000600 // Startup IPI
#define DELIVS 0x00001000 // Delivery status
#define ASSERT 0x00004000 // Assert interrupt (vs deassert)
#define DEASSERT 0x00000000
#define LEVEL 0x00008000 // Level triggered
#define BCAST 0x00080000 // Send to all APICs, including self.
#define BUSY 0x00001000
#define FIXED 0x00000000
#define ICRHI (0x0310/4) // Interrupt Command [63:32]
#define TIMER (0x0320/4) // Local Vector Table 0 (TIMER)
#define X1 0x0000000B // divide counts by 1
#define PERIODIC 0x00020000 // Periodic
#define PCINT (0x0340/4) // Performance Counter LVT
#define LINT0 (0x0350/4) // Local Vector Table 1 (LINT0)
#define LINT1 (0x0360/4) // Local Vector Table 2 (LINT1)
#define ERROR (0x0370/4) // Local Vector Table 3 (ERROR)
#define MASKED 0x00010000 // Interrupt masked
#define TICR (0x0380/4) // Timer Initial Count
#define TCCR (0x0390/4) // Timer Current Count
#define TDCR (0x03E0/4) // Timer Divide Configuration
#define IOAPIC_ADDR 0xFEC00000 // Default physical address of IO APIC
#define REG_ID 0x00 // Register index: ID
#define REG_VER 0x01 // Register index: version
#define REG_TABLE 0x10 // Redirection table base
#define INT_DISABLED 0x00010000 // Interrupt disabled
#define INT_LEVEL 0x00008000 // Level-triggered (vs edge-)
#define INT_ACTIVELOW 0x00002000 // Active low (vs high)
#define INT_LOGICAL 0x00000800 // Destination is CPU id (vs APIC ID)
volatile unsigned int *__am_lapic = NULL; // Initialized in mp.c
struct IOAPIC {
uint32_t reg, pad[3], data;
} __attribute__((packed));
typedef struct IOAPIC IOAPIC;
static volatile IOAPIC *ioapic;
static void lapicw(int index, int value) {
__am_lapic[index] = value;
__am_lapic[ID];
}
void __am_percpu_initlapic(void) {
lapicw(SVR, ENABLE | (T_IRQ0 + IRQ_SPURIOUS));
lapicw(TDCR, X1);
lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER));
lapicw(TICR, 10000000);
lapicw(LINT0, MASKED);
lapicw(LINT1, MASKED);
if (((__am_lapic[VER]>>16) & 0xFF) >= 4)
lapicw(PCINT, MASKED);
lapicw(ERROR, T_IRQ0 + IRQ_ERROR);
lapicw(ESR, 0);
lapicw(ESR, 0);
lapicw(EOI, 0);
lapicw(ICRHI, 0);
lapicw(ICRLO, BCAST | INIT | LEVEL);
while(__am_lapic[ICRLO] & DELIVS) ;
lapicw(TPR, 0);
}
void __am_lapic_eoi(void) {
if (__am_lapic)
lapicw(EOI, 0);
}
void __am_lapic_bootap(uint32_t apicid, void *addr) {
int i;
uint16_t *wrv;
outb(0x70, 0xF);
outb(0x71, 0x0A);
wrv = (unsigned short*)((0x40<<4 | 0x67));
wrv[0] = 0;
wrv[1] = (uintptr_t)addr >> 4;
lapicw(ICRHI, apicid<<24);
lapicw(ICRLO, INIT | LEVEL | ASSERT);
lapicw(ICRLO, INIT | LEVEL);
for (i = 0; i < 2; i++){
lapicw(ICRHI, apicid<<24);
lapicw(ICRLO, STARTUP | ((uintptr_t)addr>>12));
}
}
static unsigned int ioapicread(int reg) {
ioapic->reg = reg;
return ioapic->data;
}
static void ioapicwrite(int reg, unsigned int data) {
ioapic->reg = reg;
ioapic->data = data;
}
void __am_ioapic_init(void) {
int i, maxintr;
ioapic = (volatile IOAPIC*)IOAPIC_ADDR;
maxintr = (ioapicread(REG_VER) >> 16) & 0xFF;
for (i = 0; i <= maxintr; i++){
ioapicwrite(REG_TABLE+2*i, INT_DISABLED | (T_IRQ0 + i));
ioapicwrite(REG_TABLE+2*i+1, 0);
}
}
void __am_ioapic_enable(int irq, int cpunum) {
ioapicwrite(REG_TABLE+2*irq, T_IRQ0 + irq);
ioapicwrite(REG_TABLE+2*irq+1, cpunum << 24);
}

55
am/src/x86/qemu/mpe.c Normal file
View file

@ -0,0 +1,55 @@
#include "x86-qemu.h"
struct cpu_local __am_cpuinfo[MAX_CPU] = {};
static void (* volatile user_entry)();
static int ap_ready = 0;
static void call_user_entry() {
user_entry();
panic("MPE entry should not return");
}
bool mpe_init(void (*entry)()) {
user_entry = entry;
boot_record()->jmp_code = 0x000bfde9; // (16-bit) jmp (0x7c00)
for (int cpu = 1; cpu < __am_ncpu; cpu++) {
boot_record()->is_ap = 1;
__am_lapic_bootap(cpu, (void *)boot_record());
while (xchg(&ap_ready, 0) != 1) {
pause();
}
}
call_user_entry();
return true;
}
static void othercpu_entry() {
__am_percpu_init();
xchg(&ap_ready, 1);
call_user_entry();
}
void __am_othercpu_entry() {
stack_switch_call(stack_top(&CPU->stack), othercpu_entry, 0);
}
int cpu_count() {
return __am_ncpu;
}
int cpu_current(void) {
return __am_lapic[8] >> 24;
}
int atomic_xchg(int *addr, int newval) {
return xchg(addr, newval);
}
void __am_stop_the_world() {
boot_record()->jmp_code = 0x0000feeb; // (16-bit) jmp .
for (int cpu_ = 0; cpu_ < __am_ncpu; cpu_++) {
if (cpu_ != cpu_current()) {
__am_lapic_bootap(cpu_, (void *)boot_record());
}
}
}

View file

@ -0,0 +1,7 @@
#include "x86-qemu.h"
.globl _start
_start:
pushl $MAINARG_ADDR
pushl $0
jmp _start_c

69
am/src/x86/qemu/start64.S Normal file
View file

@ -0,0 +1,69 @@
#include <x86.h>
#include "x86-qemu.h"
.code32
.globl _start
_start:
movl $(PDPT_ADDR | PTE_P | PTE_W), %eax
cmpl (PML4_ADDR), %eax
je .long_mode_init
movl $(PDPT_ADDR | PTE_P | PTE_W), %eax
movl %eax, (PML4_ADDR)
movl $0, %ecx
movl $512, %esi // 512 pages
// |
.loop: // x
movl %ecx, %eax // |
shll $30, %eax // |
orl $(PTE_P | PTE_W | PTE_PS), %eax // 1 GiB page
movl %eax, PDPT_ADDR(, %ecx, 8)
movl %ecx, %eax
shrl $2, %eax
movl %eax, PDPT_ADDR + 4(, %ecx, 8)
inc %ecx
cmp %esi, %ecx
jne .loop
.long_mode_init:
movl $PML4_ADDR, %eax
movl %eax, %cr3 // %cr3 = PML4 base
movl $CR4_PAE, %eax
movl %eax, %cr4 // %cr4.PAE = 1
movl $0xc0000080, %ecx
rdmsr
orl $0x100, %eax
wrmsr // %EFER.LME = 1
movl %cr0, %eax
orl $CR0_PG, %eax
movl %eax, %cr0 // %cr0.PG = 1
lgdt gdt_ptr // bootstrap GDT
ljmp $8, $_start64 // should not return
.code64
_start64:
movw $0, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
movw %ax, %fs
movw %ax, %gs
movq $MAINARG_ADDR, %rdi
pushq $0
jmp _start_c
.align 16
gdt_ptr:
.word gdt64_end - gdt64_begin - 1
.quad gdt64_begin
gdt64_begin:
.long 0x00000000 // 0: null desc
.long 0x00000000
.long 0x00000000 // 1: code
.long 0x00209800
gdt64_end:

99
am/src/x86/qemu/trap32.S Normal file
View file

@ -0,0 +1,99 @@
#include "x86-qemu.h"
.globl __amkcontext_start
__amkcontext_start:
// eax = arg, ebx = entry
pushl %eax
pushl $__am_panic_on_return
jmpl *%ebx
trap:
cli
subl $20, %esp
pushl %ebp
pushl %edi
pushl %esi
pushl $0
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
movw %ds, %ax
pushl %eax
pushl $0
movw $KSEL(SEG_KDATA), %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
pushl %esp
call __am_irq_handle
.globl __am_iret
__am_iret:
addl $4, %esp
popl %eax
movl %eax, %esp
addl $4, %esp
popl %eax
movw %ax, %ds
movw %ax, %es
cmpw $KSEL(SEG_KCODE), 36(%esp)
je .kernel_iret
.user_iret:
popl %eax
popl %ebx
popl %ecx
popl %edx
addl $4, %esp
popl %esi
popl %edi
popl %ebp
iret
.kernel_iret:
popl %eax
popl %ebx
popl %ecx
popl %edx
addl $4, %esp
/* stack frame:
28 ss
24 esp (not popped by iret when returning to ring0)
20 eflags ---> move to new-esp
16 cs
12 eip
8 ebp
4 edi
0 esi <--- %esp
*/
movl %esp, %ebp
movl 24(%ebp), %edi // %edi is new-esp
movl 20(%ebp), %esi; movl %esi, -4(%edi)
movl 16(%ebp), %esi; movl %esi, -8(%edi)
movl 12(%ebp), %esi; movl %esi, -12(%edi)
movl 8(%ebp), %esi; movl %esi, -16(%edi)
movl 4(%ebp), %esi; movl %esi, -20(%edi)
movl 0(%ebp), %esi; movl %esi, -24(%edi)
leal -24(%edi), %esp
popl %esi
popl %edi
popl %ebp
iret
#define NOERR push $0
#define ERR
#define IRQ_DEF(id, dpl, err) \
.globl __am_irq##id; __am_irq##id: cli; err; push $id; jmp trap;
IRQS(IRQ_DEF)
.globl __am_irqall; __am_irqall: cli; push $0; push $-1; jmp trap;

61
am/src/x86/qemu/trap64.S Normal file
View file

@ -0,0 +1,61 @@
#include "x86-qemu.h"
.globl __amkcontext_start
__amkcontext_start:
// rdi = arg, rsi = entry
pushq $__am_panic_on_return
jmpq *%rsi
trap:
cli
subq $48, %rsp
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %r11
pushq %r10
pushq %r9
pushq %r8
pushq %rdi
pushq %rsi
pushq %rbp
pushq %rdx
pushq %rcx
pushq %rbx
pushq %rax
pushq $0 // cr3, saved in __am_irq_handle
movq %rsp, %rdi
call __am_irq_handle
.globl __am_iret
__am_iret:
movq %rdi, %rsp
movq 160(%rsp), %rax
movw %ax, %ds
movw %ax, %es
addq $8, %rsp
popq %rax
popq %rbx
popq %rcx
popq %rdx
popq %rbp
popq %rsi
popq %rdi
popq %r8
popq %r9
popq %r10
popq %r11
popq %r12
popq %r13
popq %r14
popq %r15
iretq
#define NOERR push $0
#define ERR
#define IRQ_DEF(id, dpl, err) \
.globl __am_irq##id; __am_irq##id: cli; err; push $id; jmp trap;
IRQS(IRQ_DEF)
.globl __am_irqall; __am_irqall: cli; push $0; push $-1; jmp trap;

114
am/src/x86/qemu/trm.c Normal file
View file

@ -0,0 +1,114 @@
#include "x86-qemu.h"
Area heap = {};
volatile uint32_t *__am_lapic;
int __am_ncpu = 0;
struct cpu_local __am_cpuinfo[MAX_CPU];
int main(const char *args);
static void call_main(const char *args) {
halt(main(args));
}
void _start_c(char *args) {
if (boot_record()->is_ap) {
__am_othercpu_entry();
} else {
__am_bootcpu_init();
stack_switch_call(stack_top(&CPU->stack), call_main, (uintptr_t)args);
}
}
void __am_bootcpu_init() {
heap = __am_heap_init();
__am_lapic_init();
__am_ioapic_init();
__am_percpu_init();
}
void __am_percpu_init() {
__am_percpu_initgdt();
__am_percpu_initlapic();
__am_percpu_initirq();
}
void putch(char ch) {
#define COM1 0x3f8
outb(COM1, ch);
}
void halt(int code) {
const char *hex = "0123456789abcdef";
const char *fmt = "CPU #$ Halt (40).\n";
cli();
__am_stop_the_world();
for (const char *p = fmt; *p; p++) {
char ch = *p;
switch (ch) {
case '$':
putch(hex[cpu_current()]);
break;
case '0': case '4':
putch(hex[(code >> (ch - '0')) & 0xf]);
break;
default:
putch(ch);
}
}
outw(0x604, 0x2000); // offer of qemu :)
while (1) hlt();
}
Area __am_heap_init() {
extern char end;
outb(0x70, 0x34);
uint32_t lo = inb(0x71);
outb(0x70, 0x35);
uint32_t hi = inb(0x71) + 1;
return RANGE(ROUNDUP(&end, 1 << 20), (uintptr_t)((lo | hi << 8) << 16));
}
void __am_lapic_init() {
for (char *st = (char *)0xf0000; st != (char *)0xffffff; st ++) {
if (*(volatile uint32_t *)st == 0x5f504d5f) {
uint32_t mpconf_ptr = ((volatile MPDesc *)st)->conf;
MPConf *conf = (void *)((uintptr_t)(mpconf_ptr));
__am_lapic = (void *)((uintptr_t)(conf->lapicaddr));
for (volatile char *ptr = (char *)(conf + 1);
ptr < (char *)conf + conf->length; ptr += 8) {
if (*ptr == '\0') {
ptr += 12;
panic_on(++__am_ncpu > MAX_CPU, "cannot support > MAX_CPU processors");
}
}
return;
}
}
bug();
}
void __am_percpu_initgdt() {
#if __x86_64__
SegDesc *gdt = CPU->gdt;
TSS64 *tss = &CPU->tss;
gdt[SEG_KCODE] = SEG64(STA_X | STA_R, DPL_KERN);
gdt[SEG_KDATA] = SEG64(STA_W, DPL_KERN);
gdt[SEG_UCODE] = SEG64(STA_X | STA_R, DPL_USER);
gdt[SEG_UDATA] = SEG64(STA_W, DPL_USER);
gdt[SEG_TSS] = SEG16(STS_T32A, tss, sizeof(*tss)-1, DPL_KERN);
bug_on((uintptr_t)tss >> 32);
set_gdt(gdt, sizeof(gdt[0]) * (NR_SEG + 1));
set_tr(KSEL(SEG_TSS));
#else
SegDesc *gdt = CPU->gdt;
TSS32 *tss = &CPU->tss;
gdt[SEG_KCODE] = SEG32(STA_X | STA_R, 0, 0xffffffff, DPL_KERN);
gdt[SEG_KDATA] = SEG32(STA_W, 0, 0xffffffff, DPL_KERN);
gdt[SEG_UCODE] = SEG32(STA_X | STA_R, 0, 0xffffffff, DPL_USER);
gdt[SEG_UDATA] = SEG32(STA_W, 0, 0xffffffff, DPL_USER);
gdt[SEG_TSS] = SEG16(STS_T32A, tss, sizeof(*tss)-1, DPL_KERN);
set_gdt(gdt, sizeof(gdt[0]) * NR_SEG);
set_tr(KSEL(SEG_TSS));
#endif
}

181
am/src/x86/qemu/vme.c Normal file
View file

@ -0,0 +1,181 @@
#include "x86-qemu.h"
const struct mmu_config mmu = {
.pgsize = 4096,
#if __x86_64__
.ptlevels = 4,
.pgtables = {
{ "CR3", 0x000000000000, 0, 0 },
{ "PML4", 0xff8000000000, 39, 9 },
{ "PDPT", 0x007fc0000000, 30, 9 },
{ "PD", 0x00003fe00000, 21, 9 },
{ "PT", 0x0000001ff000, 12, 9 },
},
#else
.ptlevels = 2,
.pgtables = {
{ "CR3", 0x00000000, 0, 0 },
{ "PD", 0xffc00000, 22, 10 },
{ "PT", 0x003ff000, 12, 10 },
},
#endif
};
static const struct vm_area vm_areas[] = {
#ifdef __x86_64__
{ RANGE(0x100000000000, 0x108000000000), 0 }, // 512 GiB user space
{ RANGE(0x000000000000, 0x008000000000), 1 }, // 512 GiB kernel
#else
{ RANGE( 0x40000000, 0x80000000), 0 }, // 1 GiB user space
{ RANGE( 0x00000000, 0x40000000), 1 }, // 1 GiB kernel
{ RANGE( 0xfd000000, 0x00000000), 1 }, // memory-mapped I/O
#endif
};
#define uvm_area (vm_areas[0].area)
static uintptr_t *kpt;
static void *(*pgalloc)(int size);
static void (*pgfree)(void *);
static void *pgallocz() {
uintptr_t *base = pgalloc(mmu.pgsize);
panic_on(!base, "cannot allocate page");
for (int i = 0; i < mmu.pgsize / sizeof(uintptr_t); i++) {
base[i] = 0;
}
return base;
}
static int indexof(uintptr_t addr, const struct ptinfo *info) {
return ((uintptr_t)addr & info->mask) >> info->shift;
}
static uintptr_t baseof(uintptr_t addr) {
return addr & ~(mmu.pgsize - 1);
}
static uintptr_t *ptwalk(AddrSpace *as, uintptr_t addr, int flags) {
uintptr_t cur = (uintptr_t)&as->ptr;
for (int i = 0; i <= mmu.ptlevels; i++) {
const struct ptinfo *ptinfo = &mmu.pgtables[i];
uintptr_t *pt = (uintptr_t *)cur, next_page;
int index = indexof(addr, ptinfo);
if (i == mmu.ptlevels) return &pt[index];
if (!(pt[index] & PTE_P)) {
next_page = (uintptr_t)pgallocz();
pt[index] = next_page | PTE_P | flags;
} else {
next_page = baseof(pt[index]);
}
cur = next_page;
}
bug();
}
static void teardown(int level, uintptr_t *pt) {
if (level > mmu.ptlevels) return;
for (int index = 0; index < (1 << mmu.pgtables[level].bits); index++) {
if ((pt[index] & PTE_P) && (pt[index] & PTE_U)) {
teardown(level + 1, (void *)baseof(pt[index]));
}
}
if (level >= 1) {
pgfree(pt);
}
}
bool vme_init(void *(*_pgalloc)(int size), void (*_pgfree)(void *)) {
panic_on(cpu_current() != 0, "init VME in non-bootstrap CPU");
pgalloc = _pgalloc;
pgfree = _pgfree;
#if __x86_64__
kpt = (void *)PML4_ADDR;
#else
AddrSpace as;
as.ptr = NULL;
for (int i = 0; i < LENGTH(vm_areas); i++) {
const struct vm_area *vma = &vm_areas[i];
if (vma->kernel) {
for (uintptr_t cur = (uintptr_t)vma->area.start;
cur != (uintptr_t)vma->area.end;
cur += mmu.pgsize) {
*ptwalk(&as, cur, PTE_W) = cur | PTE_P | PTE_W;
}
}
}
kpt = (void *)baseof((uintptr_t)as.ptr);
#endif
set_cr3(kpt);
set_cr0(get_cr0() | CR0_PG);
return true;
}
void protect(AddrSpace *as) {
uintptr_t *upt = pgallocz();
for (int i = 0; i < LENGTH(vm_areas); i++) {
const struct vm_area *vma = &vm_areas[i];
if (vma->kernel) {
const struct ptinfo *info = &mmu.pgtables[1]; // level-1 page table
for (uintptr_t cur = (uintptr_t)vma->area.start;
cur != (uintptr_t)vma->area.end;
cur += (1L << info->shift)) {
int index = indexof(cur, info);
upt[index] = kpt[index];
}
}
}
as->pgsize = mmu.pgsize;
as->area = uvm_area;
as->ptr = (void *)((uintptr_t)upt | PTE_P | PTE_U);
}
void unprotect(AddrSpace *as) {
teardown(0, (void *)&as->ptr);
}
void map(AddrSpace *as, void *va, void *pa, int prot) {
panic_on(!IN_RANGE(va, uvm_area), "mapping an invalid address");
panic_on((uintptr_t)va != ROUNDDOWN(va, mmu.pgsize) ||
(uintptr_t)pa != ROUNDDOWN(pa, mmu.pgsize), "non-page-boundary address");
uintptr_t *ptentry = ptwalk(as, (uintptr_t)va, PTE_W | PTE_U);
if (prot == MMAP_NONE) {
panic_on(!(*ptentry & PTE_P), "unmapping a non-mapped page");
*ptentry = 0;
} else {
panic_on(*ptentry & PTE_P, "remapping a mapped page");
uintptr_t pte = (uintptr_t)pa | PTE_P | PTE_U | ((prot & MMAP_WRITE) ? PTE_W : 0);
*ptentry = pte;
}
ptwalk(as, (uintptr_t)va, PTE_W | PTE_U);
}
Context *ucontext(AddrSpace *as, Area kstack, void *entry) {
Context *ctx = kstack.end - sizeof(Context);
*ctx = (Context) { 0 };
#if __x86_64__
ctx->cs = USEL(SEG_UCODE);
ctx->ss = USEL(SEG_UDATA);
ctx->rip = (uintptr_t)entry;
ctx->rflags = FL_IF;
ctx->rsp = (uintptr_t)uvm_area.end;
ctx->rsp0 = (uintptr_t)kstack.end;
#else
ctx->cs = USEL(SEG_UCODE);
ctx->ds = USEL(SEG_UDATA);
ctx->ss3 = USEL(SEG_UDATA);
ctx->eip = (uintptr_t)entry;
ctx->eflags = FL_IF;
ctx->esp = (uintptr_t)uvm_area.end;
ctx->esp0 = (uintptr_t)kstack.end;
#endif
ctx->cr3 = as->ptr;
return ctx;
}

100
am/src/x86/qemu/x86-qemu.h Normal file
View file

@ -0,0 +1,100 @@
#include <x86.h>
#define PML4_ADDR 0x1000
#define PDPT_ADDR 0x2000
#define NR_SEG 6 // GDT size
#define SEG_KCODE 1 // Kernel code
#define SEG_KDATA 2 // Kernel data/stack
#define SEG_UCODE 3 // User code
#define SEG_UDATA 4 // User data/stack
#define SEG_TSS 5 // Global unique task state segement
#define NR_IRQ 256 // IDT size
#ifndef __ASSEMBLER__
#include <am.h>
#include <klib-macros.h>
struct kernel_stack {
uint8_t stack[8192];
};
static inline void *stack_top(struct kernel_stack *stk) {
return stk->stack + sizeof(stk->stack);
}
struct mmu_config {
int ptlevels, pgsize;
struct ptinfo {
const char *name;
uintptr_t mask;
int shift, bits;
} pgtables[];
};
struct vm_area {
Area area;
int kernel;
};
void __am_iret(Context *ctx);
struct cpu_local {
AddrSpace *uvm;
#if __x86_64__
SegDesc gdt[NR_SEG + 1];
TSS64 tss;
#else
SegDesc gdt[NR_SEG];
TSS32 tss;
#endif
struct kernel_stack stack;
};
#if __x86_64__
struct trap_frame {
Context saved_context;
uint64_t irq, errcode;
uint64_t rip, cs, rflags, rsp, ss;
};
#else
struct trap_frame {
Context saved_context;
uint32_t irq, errcode;
uint32_t eip, cs, eflags, esp, ss;
};
#endif
extern volatile uint32_t *__am_lapic;
extern int __am_ncpu;
extern struct cpu_local __am_cpuinfo[MAX_CPU];
#define CPU (&__am_cpuinfo[cpu_current()])
#define bug_on(cond) \
do { \
if (cond) panic("internal error (likely a bug in AM)"); \
} while (0)
#define bug() bug_on(1)
// apic utils
void __am_lapic_eoi();
void __am_ioapic_init();
void __am_lapic_bootap(uint32_t cpu, void *address);
void __am_ioapic_enable(int irq, int cpu);
// x86-specific operations
void __am_bootcpu_init();
void __am_percpu_init();
Area __am_heap_init();
void __am_lapic_init();
void __am_othercpu_entry();
void __am_percpu_initirq();
void __am_percpu_initgdt();
void __am_percpu_initlapic();
void __am_stop_the_world();
#endif