NJU-ProjectN/abstract-machine ics2023 initialized
NJU-ProjectN/abstract-machine 3348db971fd860be5cb28e21c18f9d0e65d0c96a Merge pull request #8 from Jasonyanyusong/master
This commit is contained in:
parent
2824efad33
commit
8e4feb4010
129 changed files with 9017 additions and 0 deletions
199
abstract-machine/am/src/native/cte.c
Normal file
199
abstract-machine/am/src/native/cte.c
Normal file
|
@ -0,0 +1,199 @@
|
|||
#include <sys/time.h>
|
||||
#include <string.h>
|
||||
#include "platform.h"
|
||||
|
||||
#define TIMER_HZ 100
|
||||
#define 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;
|
||||
|
||||
if (thiscpu->ev.event == EVENT_ERROR) {
|
||||
uintptr_t rip = c->uc.uc_mcontext.gregs[REG_RIP];
|
||||
printf("Unhandle signal '%s' at rip = %p, badaddr = %p, cause = 0x%x\n",
|
||||
thiscpu->ev.msg, rip, thiscpu->ev.ref, thiscpu->ev.cause);
|
||||
assert(0);
|
||||
}
|
||||
c = user_handler(thiscpu->ev, c);
|
||||
assert(c != NULL);
|
||||
|
||||
__am_switch(c);
|
||||
|
||||
// magic call to restore context
|
||||
void (*p)(Context *c) = (void *)(uintptr_t)0x100008;
|
||||
p(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
|
||||
if (event == EVENT_SYSCALL) { rip += SYSCALL_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_RDI];
|
||||
// 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 SIGUSR2: thiscpu->ev.event = EVENT_YIELD; 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: 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;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (thiscpu->ev.event == EVENT_ERROR) {
|
||||
thiscpu->ev.ref = (uintptr_t)info->si_addr;
|
||||
thiscpu->ev.cause = (uintptr_t)info->si_code;
|
||||
thiscpu->ev.msg = strsignal(sig);
|
||||
}
|
||||
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(SIGUSR2, &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() {
|
||||
raise(SIGUSR2);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
79
abstract-machine/am/src/native/ioe.c
Normal file
79
abstract-machine/am/src/native/ioe.c
Normal file
|
@ -0,0 +1,79 @@
|
|||
#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_audio_init();
|
||||
void __am_disk_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_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 *);
|
||||
void __am_disk_config(AM_DISK_CONFIG_T *cfg);
|
||||
void __am_disk_status(AM_DISK_STATUS_T *stat);
|
||||
void __am_disk_blkio(AM_DISK_BLKIO_T *io);
|
||||
static void __am_uart_config(AM_UART_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_DISK_STATUS ] = __am_disk_status,
|
||||
[AM_DISK_BLKIO ] = __am_disk_blkio,
|
||||
[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();
|
||||
__am_audio_init();
|
||||
__am_disk_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); }
|
72
abstract-machine/am/src/native/ioe/audio.c
Normal file
72
abstract-machine/am/src/native/ioe/audio.c
Normal file
|
@ -0,0 +1,72 @@
|
|||
#define _GNU_SOURCE
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <klib.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
static int rfd = -1, wfd = -1;
|
||||
static volatile int count = 0;
|
||||
|
||||
void __am_audio_init() {
|
||||
int fds[2];
|
||||
int ret = pipe2(fds, O_NONBLOCK);
|
||||
assert(ret == 0);
|
||||
rfd = fds[0];
|
||||
wfd = fds[1];
|
||||
}
|
||||
|
||||
static void audio_play(void *userdata, uint8_t *stream, int len) {
|
||||
int nread = len;
|
||||
if (count < len) nread = count;
|
||||
int b = 0;
|
||||
while (b < nread) {
|
||||
int n = read(rfd, stream, nread);
|
||||
if (n > 0) b += n;
|
||||
}
|
||||
|
||||
count -= nread;
|
||||
if (len > nread) {
|
||||
memset(stream + nread, 0, len - nread);
|
||||
}
|
||||
}
|
||||
|
||||
static void audio_write(uint8_t *buf, int len) {
|
||||
int nwrite = 0;
|
||||
while (nwrite < len) {
|
||||
int n = write(wfd, buf, len);
|
||||
if (n == -1) n = 0;
|
||||
count += n;
|
||||
nwrite += n;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
audio_write(ctl->buf.start, len);
|
||||
}
|
||||
|
||||
void __am_audio_config(AM_AUDIO_CONFIG_T *cfg) {
|
||||
cfg->present = true;
|
||||
cfg->bufsize = fcntl(rfd, F_GETPIPE_SZ);
|
||||
}
|
41
abstract-machine/am/src/native/ioe/disk.c
Normal file
41
abstract-machine/am/src/native/ioe/disk.c
Normal file
|
@ -0,0 +1,41 @@
|
|||
#include <am.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define BLKSZ 512
|
||||
|
||||
static int disk_size = 0;
|
||||
static FILE *fp = NULL;
|
||||
|
||||
void __am_disk_init() {
|
||||
const char *diskimg = getenv("diskimg");
|
||||
if (diskimg) {
|
||||
fp = fopen(diskimg, "r+");
|
||||
if (fp) {
|
||||
fseek(fp, 0, SEEK_END);
|
||||
disk_size = (ftell(fp) + 511) / 512;
|
||||
rewind(fp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void __am_disk_config(AM_DISK_CONFIG_T *cfg) {
|
||||
cfg->present = (fp != NULL);
|
||||
cfg->blksz = BLKSZ;
|
||||
cfg->blkcnt = disk_size;
|
||||
}
|
||||
|
||||
void __am_disk_status(AM_DISK_STATUS_T *stat) {
|
||||
stat->ready = 1;
|
||||
}
|
||||
|
||||
void __am_disk_blkio(AM_DISK_BLKIO_T *io) {
|
||||
if (fp) {
|
||||
fseek(fp, io->blkno * BLKSZ, SEEK_SET);
|
||||
int ret;
|
||||
if (io->write) ret = fwrite(io->buf, io->blkcnt * BLKSZ, 1, fp);
|
||||
else ret = fread(io->buf, io->blkcnt * BLKSZ, 1, fp);
|
||||
assert(ret == 1);
|
||||
}
|
||||
}
|
66
abstract-machine/am/src/native/ioe/gpu.c
Normal file
66
abstract-machine/am/src/native/ioe/gpu.c
Normal file
|
@ -0,0 +1,66 @@
|
|||
#include <am.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <fenv.h>
|
||||
|
||||
//#define MODE_800x600
|
||||
#ifdef MODE_800x600
|
||||
# define W 800
|
||||
# define H 600
|
||||
#else
|
||||
# define W 400
|
||||
# define H 300
|
||||
#endif
|
||||
|
||||
#define FPS 60
|
||||
|
||||
#define RMASK 0x00ff0000
|
||||
#define GMASK 0x0000ff00
|
||||
#define BMASK 0x000000ff
|
||||
#define AMASK 0x00000000
|
||||
|
||||
static SDL_Window *window = NULL;
|
||||
static SDL_Surface *surface = NULL;
|
||||
|
||||
static Uint32 texture_sync(Uint32 interval, void *param) {
|
||||
SDL_BlitScaled(surface, NULL, SDL_GetWindowSurface(window), NULL);
|
||||
SDL_UpdateWindowSurface(window);
|
||||
return interval;
|
||||
}
|
||||
|
||||
void __am_gpu_init() {
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
|
||||
window = SDL_CreateWindow("Native Application",
|
||||
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
||||
#ifdef MODE_800x600
|
||||
W, H,
|
||||
#else
|
||||
W * 2, H * 2,
|
||||
#endif
|
||||
SDL_WINDOW_OPENGL);
|
||||
surface = SDL_CreateRGBSurface(SDL_SWSURFACE, W, H, 32,
|
||||
RMASK, GMASK, BMASK, AMASK);
|
||||
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;
|
||||
if (w == 0 || h == 0) return;
|
||||
feclearexcept(-1);
|
||||
SDL_Surface *s = SDL_CreateRGBSurfaceFrom(ctl->pixels, w, h, 32, w * sizeof(uint32_t),
|
||||
RMASK, GMASK, BMASK, AMASK);
|
||||
SDL_Rect rect = { .x = x, .y = y };
|
||||
SDL_BlitSurface(s, NULL, surface, &rect);
|
||||
SDL_FreeSurface(s);
|
||||
}
|
63
abstract-machine/am/src/native/ioe/input.c
Normal file
63
abstract-machine/am/src/native/ioe/input.c
Normal file
|
@ -0,0 +1,63 @@
|
|||
#include <am.h>
|
||||
#include <SDL2/SDL.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);
|
||||
void __am_send_kbd_intr();
|
||||
__am_send_kbd_intr();
|
||||
}
|
||||
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;
|
||||
}
|
32
abstract-machine/am/src/native/ioe/timer.c
Normal file
32
abstract-machine/am/src/native/ioe/timer.c
Normal file
|
@ -0,0 +1,32 @@
|
|||
#include <am.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.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);
|
||||
}
|
51
abstract-machine/am/src/native/mpe.c
Normal file
51
abstract-machine/am/src/native/mpe.c
Normal 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);
|
||||
}
|
230
abstract-machine/am/src/native/platform.c
Normal file
230
abstract-machine/am/src/native/platform.c
Normal file
|
@ -0,0 +1,230 @@
|
|||
#define _GNU_SOURCE
|
||||
#include <sys/mman.h>
|
||||
#include <sys/auxv.h>
|
||||
#include <dlfcn.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 *)0x1000000 // for nanos-lite with vme disabled
|
||||
#define PMEM_SIZE (128 * 1024 * 1024) // 128MB
|
||||
static int pmem_fd = 0;
|
||||
static void *pmem = NULL;
|
||||
static ucontext_t uc_example = {};
|
||||
static void *(*memcpy_libc)(void *, const void *, size_t) = NULL;
|
||||
sigset_t __am_intr_sigmask = {};
|
||||
__am_cpu_t *__am_cpu_struct = NULL;
|
||||
int __am_ncpu = 0;
|
||||
int __am_pgsize = 0;
|
||||
|
||||
static void save_context_handler(int sig, siginfo_t *info, void *ucontext) {
|
||||
memcpy_libc(&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;
|
||||
void *(*memset_libc)(void *, int, size_t) = dlsym(RTLD_NEXT, "memset");
|
||||
memset_libc(&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() {
|
||||
assert(sizeof(thiscpu->sigstack) >= SIGSTKSZ);
|
||||
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);
|
||||
// use dynamic linking to avoid linking to the same function in RT-Thread
|
||||
int (*ftruncate_libc)(int, off_t) = dlsym(RTLD_NEXT, "ftruncate");
|
||||
assert(ftruncate_libc != NULL);
|
||||
int ret2 = ftruncate_libc(pmem_fd, PMEM_SIZE);
|
||||
assert(ret2 == 0);
|
||||
|
||||
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
|
||||
int 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);
|
||||
|
||||
// save the address of memcpy() in glibc, since it may be linked with klib
|
||||
memcpy_libc = dlsym(RTLD_NEXT, "memcpy");
|
||||
assert(memcpy_libc != NULL);
|
||||
|
||||
// remap writable sections as MAP_SHARED
|
||||
Elf64_Phdr *phdr = (void *)getauxval(AT_PHDR);
|
||||
int phnum = (int)getauxval(AT_PHNUM);
|
||||
int i;
|
||||
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_libc(temp_mem, vaddr_align, size);
|
||||
|
||||
// save the address of mmap() which will be used after munamp(),
|
||||
// since calling the library functions requires accessing GOT, which will be unmapped
|
||||
void *(*mmap_libc)(void *, size_t, int, int, int, off_t) = dlsym(RTLD_NEXT, "mmap");
|
||||
assert(mmap_libc != NULL);
|
||||
// load the address of memcpy() on stack, which can still be accessed
|
||||
// after the data section is unmapped
|
||||
void *(*volatile memcpy_libc_temp)(void *, const void *, size_t) = memcpy_libc;
|
||||
|
||||
// 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_temp(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();
|
||||
uc_example.uc_mcontext.fpregs = NULL; // clear the FPU 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_libc(&r->uc, &uc_example, sizeof(uc_example));
|
||||
}
|
||||
|
||||
void __am_get_intr_sigmask(sigset_t *s) {
|
||||
memcpy_libc(s, &__am_intr_sigmask, sizeof(__am_intr_sigmask));
|
||||
}
|
||||
|
||||
int __am_is_sigmask_sti(sigset_t *s) {
|
||||
return !sigismember(s, SIGVTALRM);
|
||||
}
|
||||
|
||||
void __am_send_kbd_intr() {
|
||||
kill(getpid(), SIGUSR1);
|
||||
}
|
||||
|
||||
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
abstract-machine/am/src/native/platform.h
Normal file
28
abstract-machine/am/src/native/platform.h
Normal 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[32768];
|
||||
} __am_cpu_t;
|
||||
extern __am_cpu_t *__am_cpu_struct;
|
||||
#define thiscpu __am_cpu_struct
|
||||
|
||||
#endif
|
10
abstract-machine/am/src/native/trap.S
Normal file
10
abstract-machine/am/src/native/trap.S
Normal 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
|
30
abstract-machine/am/src/native/trm.c
Normal file
30
abstract-machine/am/src/native/trm.c
Normal file
|
@ -0,0 +1,30 @@
|
|||
#include <am.h>
|
||||
#include <stdio.h>
|
||||
#include <klib-macros.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) {
|
||||
const char *fmt = "Exit code = 40h\n";
|
||||
for (const char *p = fmt; *p; p++) {
|
||||
char ch = *p;
|
||||
if (ch == '0' || ch == '4') {
|
||||
ch = "0123456789abcdef"[(code >> (ch - '0')) & 0xf];
|
||||
}
|
||||
putch(ch);
|
||||
}
|
||||
__am_exit_platform(code);
|
||||
putstr("Should not reach here!\n");
|
||||
while (1);
|
||||
}
|
||||
|
||||
Area heap = {};
|
141
abstract-machine/am/src/native/vme.c
Normal file
141
abstract-machine/am/src/native/vme.c
Normal file
|
@ -0,0 +1,141 @@
|
|||
#define _GNU_SOURCE
|
||||
#include <search.h>
|
||||
#include "platform.h"
|
||||
|
||||
#define USER_SPACE RANGE(0x40000000, 0xc0000000)
|
||||
|
||||
typedef struct PageMap {
|
||||
void *va;
|
||||
void *pa;
|
||||
struct PageMap *next;
|
||||
int prot;
|
||||
int is_mapped;
|
||||
char key[32]; // used for hsearch_r()
|
||||
} PageMap;
|
||||
|
||||
typedef struct VMHead {
|
||||
PageMap *head;
|
||||
struct hsearch_data hash;
|
||||
int nr_page;
|
||||
} VMHead;
|
||||
|
||||
#define list_foreach(p, head) \
|
||||
for (p = (PageMap *)(head); 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);
|
||||
VMHead *h = pgalloc(__am_pgsize); // used as head of the list
|
||||
assert(h != NULL);
|
||||
memset(h, 0, sizeof(*h));
|
||||
int max_pg = (USER_SPACE.end - USER_SPACE.start) / __am_pgsize;
|
||||
int ret = hcreate_r(max_pg, &h->hash);
|
||||
assert(ret != 0);
|
||||
|
||||
as->ptr = h;
|
||||
as->pgsize = __am_pgsize;
|
||||
as->area = USER_SPACE;
|
||||
}
|
||||
|
||||
void unprotect(AddrSpace *as) {
|
||||
}
|
||||
|
||||
void __am_switch(Context *c) {
|
||||
if (!vme_enable) return;
|
||||
|
||||
VMHead *head = c->vm_head;
|
||||
VMHead *now_head = thiscpu->vm_head;
|
||||
if (head == now_head) goto end;
|
||||
|
||||
PageMap *pp;
|
||||
if (now_head != NULL) {
|
||||
// munmap all mappings
|
||||
list_foreach(pp, now_head->head) {
|
||||
if (pp->is_mapped) {
|
||||
__am_pmem_unmap(pp->va);
|
||||
pp->is_mapped = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (head != NULL) {
|
||||
// mmap all mappings
|
||||
list_foreach(pp, head->head) {
|
||||
assert(IN_RANGE(pp->va, USER_SPACE));
|
||||
__am_pmem_map(pp->va, pp->pa, pp->prot);
|
||||
pp->is_mapped = true;
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
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;
|
||||
VMHead *vm_head = as->ptr;
|
||||
assert(vm_head != NULL);
|
||||
char buf[32];
|
||||
snprintf(buf, 32, "%x", va);
|
||||
ENTRY item = { .key = buf };
|
||||
ENTRY *item_find;
|
||||
hsearch_r(item, FIND, &item_find, &vm_head->hash);
|
||||
if (item_find == NULL) {
|
||||
pp = pgalloc(__am_pgsize); // this will waste memory, any better idea?
|
||||
snprintf(pp->key, 32, "%x", va);
|
||||
item.key = pp->key;
|
||||
item.data = pp;
|
||||
int ret = hsearch_r(item, ENTER, &item_find, &vm_head->hash);
|
||||
assert(ret != 0);
|
||||
vm_head->nr_page ++;
|
||||
} else {
|
||||
pp = item_find->data;
|
||||
}
|
||||
pp->va = va;
|
||||
pp->pa = pa;
|
||||
pp->prot = prot;
|
||||
pp->is_mapped = false;
|
||||
pp->next = vm_head->head;
|
||||
vm_head->head = 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);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue