a lot of cleanup, added pmp

master
Anton Lydike 3 years ago
parent e69cfa8a5a
commit bb13cbeca5

@ -1,23 +1,9 @@
# small makefile for compiling the kernel
# kernel lib dir
KLIBDIR=kinclude
# object file dir
ODIR=obj
# target dir
TARGET=out
GCC_PREF=riscv32-unknown-elf-
CC=$(GCC_PREF)gcc
OBJDUMP=$(GCC_PREF)objdump
CFLAGS=-I$(KLIBDIR) -MD -mcmodel=medany -Wall -Wextra -pedantic-errors -Wno-builtin-declaration-mismatch
KERNEL_CFLAGS=-nostdlib -T linker.ld
ARCH = rv32im
### Build configuration:
# Define the maximum number of running processes
# process and binary count
PROCESS_COUNT = 8
PACKAGED_BINARY_COUNT = 8
# Define the maximum number of binaries packaged with the kernel
PACKAGED_BINARY_COUNT = 4
@ -25,17 +11,34 @@ PACKAGED_BINARY_COUNT = 4
# Comment this out if you don't have any text IO device memory mapped
CFLAGS += -DTEXT_IO_ADDR=0xff0000 -DTEXT_IO_BUFLEN=64
# Uncomment these to build with only the rv32i standard
# If you want to build without any extension, you can uncomment the next line
#CFLAGS += -D__risc_no_ext=1
#ARCH = rv32i
# also change this to represent your target RISC-V architecture and extensions
ARCH = rv32im
# Configure if mtime is memory-mapped or inside a CSR:
# replace 0xFF11FF22FF33 with the correct address
#CFLAGS += -DTIMECMP_IN_MEMORY=1 -DTIMECMP_MEM_ADDR=0xFF11FF22
# Set this to the first out-of-bounds memory address
END_OF_USABLE_MEM=0xff0000
### End configuration
CFLAGS += -march=$(ARCH) -DPROCESS_COUNT=$(PROCESS_COUNT) -DNUM_BINARIES=$(PACKAGED_BINARY_COUNT)
# kernel lib dir
KLIBDIR=kinclude
# object file dir
ODIR=obj
# target dir
TARGET=out
GCC_PREF=riscv32-unknown-elf-
CC=$(GCC_PREF)gcc
OBJDUMP=$(GCC_PREF)objdump
CFLAGS+=-I$(KLIBDIR) -MD -mcmodel=medany -Wall -Wextra -pedantic-errors -Wno-builtin-declaration-mismatch -march=$(ARCH)
KERNEL_CFLAGS=-nostdlib -T linker.ld
CFLAGS+=-DPROCESS_COUNT=$(PROCESS_COUNT) -DPACKAGED_BINARY_COUNT=$(PACKAGED_BINARY_COUNT) -DEND_OF_USABLE_MEM=$(END_OF_USABLE_MEM)
# dependencies that need to be built:
_DEPS = ecall.c csr.c sched.c io.c malloc.c

@ -4,20 +4,26 @@
#include "sched.h"
#include "io.h"
#include "malloc.h"
#include "csr.h"
void read_binary_table();
extern struct process_control_block processes[PROCESS_COUNT];
void setup_mem_protection();
// this array is populated when the memory image is built, therefore it should
// resign in a section which is not overwritten with zeros on startup
loaded_binary binary_table[NUM_BINARIES] __attribute__ ((section(".data")));
loaded_binary binary_table[PACKAGED_BINARY_COUNT] __attribute__ ((section(".data")));
// access the memset function defined in boot.S
extern void memset(unsigned int, void*, void*);
// access linker symbols:
extern byte* _end, _ftext;
extern void init()
{
dbgln("Kernel started!", 15);
// setup phsycial memory protection
setup_mem_protection();
// initialize scheduler
scheudler_init();
// initialize tabel for associating ecall codes with their handlers
@ -33,13 +39,13 @@ void read_binary_table()
{
char msg[28] = "found bin with id 0 at pos 0";
malloc_info info = {
.allocate_memory_end = (void*) 0xFF0000,
struct malloc_info info = {
.allocate_memory_end = (void*) END_OF_USABLE_MEM,
.allocate_memory_start = (void*) 0
};
// calculate the end of loaded binaries
for (int i = 0; i < NUM_BINARIES; i++) {
for (int i = 0; i < PACKAGED_BINARY_COUNT; i++) {
if (binary_table[i].binid == 0)
break;
@ -56,15 +62,34 @@ void read_binary_table()
// initialize malloc
malloc_init(&info);
for (int i = 0; i < NUM_BINARIES; i++) {
for (int i = 0; i < PACKAGED_BINARY_COUNT; i++) {
if (binary_table[i].binid == 0)
break;
// create a new process for each binary found
// it should have around 4kb stack
optional_pcbptr res = create_new_process(binary_table + i, 1 << 12);
optional_pcbptr res = create_new_process(binary_table + i);
if (has_error(res)) {
dbgln("Error creating initial process!", 31);
}
}
}
void setup_mem_protection()
{
// this pmp config uses Top-of-Range mode - read more in the privileged spec p.49
// we disallow all access to 0x0-0x100 from user and machine mode
// and all access to 0x100-kernel_end from user mode
// to do this, we must first calculate the kernel bin length
uint32 kernel_bin_len = ((uint32) &_end) - ((uint32) &_ftext);
CSR_WRITE(CSR_PMPADDR, 0x100);
CSR_WRITE(CSR_PMPADDR + 1, 0x100 + kernel_bin_len);
// this contains two pmp configs:
// fields: L A RWX
// pmpcfg0: 0b1_00_01_000 <- disallow RWX from U and M mode
// pmpcfg1: 0b0_00_01_000 <- disallow RWX from U mode
// the resulint number is 0b0000100010001000, hex 0x888
CSR_WRITE(CSR_PMPCFG, 0x888);
}

@ -1,3 +1,4 @@
#include "ktypes.h"
#include "csr.h"
#ifdef TIMECMP_IN_MEMORY
@ -6,12 +7,12 @@
#error "You set TIMECMP_IN_MEMORY but did not provide a memory addres in TIMECMP_MEM_ADDR!"
#endif
void write_mtimecmp(unsigned long long int mtimecmp)
void write_mtimecmp(uint64 mtimecmp)
{
unsigned int lo = mtimecmp & 0xffffffff;
unsigned int hi = mtimecmp >> 32;
uint32 lo = mtimecmp & 0xffffffff;
uint32 hi = mtimecmp >> 32;
__asm__ volatile(
__asm__ volatile (
"li t0, %0\n"
"sw %1, 0(t0)\n"
"sw %2, 4(t0)" ::
@ -21,12 +22,12 @@ void write_mtimecmp(unsigned long long int mtimecmp)
#else
void write_mtimecmp(unsigned long long int mtimecmp)
void write_mtimecmp(uint64 mtimecmp)
{
unsigned int lower = mtimecmp & 0xffffffff;
unsigned int higher = mtimecmp >> 32;
uint32 lower = mtimecmp & 0xffffffff;
uint32 higher = mtimecmp >> 32;
__asm__ volatile(
__asm__ volatile (
"csrw %0, %2\n"
"csrw %1, %3" ::
"I"(CSR_MTIMECMP),"I"(CSR_MTIMECMPH),

@ -16,6 +16,9 @@
#define CSR_MTIMECMP 0x780 // mtimecmp register for timer interrupts
#define CSR_MTIMECMPH 0x781 // mtimecmph register for timer interrupts
#define CSR_PMPCFG 0x3A0 // start of physical memory protection config
#define CSR_PMPADDR 0x3B0 // start of pmp addresses
#ifndef CSR_HALT
#define CSR_HALT 0x789 // writing nonzero value here will halt the cpu
#endif
@ -35,19 +38,19 @@
__asm__ ("csrw %0, %1" :: "I"(CSR_HALT), "I"(code)); \
}
void write_mtimecmp(unsigned long long int mtimecmp);
void write_mtimecmp(uint64 mtimecmp);
inline __attribute__((always_inline)) unsigned long long int read_time()
inline __attribute__((always_inline)) uint64 read_time()
{
unsigned int lower, higher;
__asm__ volatile(
__asm__ volatile (
"csrr %0, %2\n"
"csrr %1, %3\n"
: "=r"(lower), "=r"(higher)
: "i"(CSR_TIME), "i"(CSR_TIMEH)
);
return (unsigned long long) higher << 32 | lower;
return (uint64) higher << 32 | lower;
}
#endif

@ -16,12 +16,15 @@ ecall_handler ecall_table[ECALL_TABLE_LEN] = { 0 };
optional_int ecall_handle_spawn_thread(int* args_ptr, struct process_control_block* pcb)
{
// args_ptr is a pointer to the pcb->regs field, starting at a0.
// args_ptr[0] is a0, args_ptr[1] is a1, etc.
void* entry = (void*) args_ptr[0]; // a0
void* args = (void*) args_ptr[1]; // a1
//int flags = args_ptr[2]; // a2
optional_pcbptr pcb_or_err = create_new_thread(pcb, entry, args, 1 << 14);
// create a new thread
optional_pcbptr pcb_or_err = create_new_thread(pcb, entry, args);
// if an error occured, pass it along to the process
if (has_error(pcb_or_err))
return (optional_int) { .error = pcb_or_err.error };
@ -30,12 +33,15 @@ optional_int ecall_handle_spawn_thread(int* args_ptr, struct process_control_blo
optional_int ecall_handle_sleep(int* args, struct process_control_block* pcb)
{
// read len from a0
int len = args[0];
// only allow sleeping for positive intervals
if (len < 0) {
return (optional_int) { .error = EINVAL };
}
// if a positive interval is given, calculate the wakeup time
if (len > 0) {
pcb->asleep_until = read_time() + len;
pcb->status = PROC_WAIT_SLEEP;
@ -46,28 +52,35 @@ optional_int ecall_handle_sleep(int* args, struct process_control_block* pcb)
optional_int ecall_handle_join(int* args, struct process_control_block* pcb)
{
int pid = args[0]; // a0
int pid = args[0]; // read pid from processes a0 register
// find the referenced process
struct process_control_block* target = process_from_pid(pid);
if (target == NULL)
return (optional_int) { .error = ESRCH };
// if the process is dead, join can return immediately
if (target->status == PROC_DEAD)
return (optional_int) { .value = target->exit_code };
// mark the current process as waiting for the target process
pcb->status = PROC_WAIT_PROC;
pcb->waiting_for_process = target;
// check if a valid timeout was passed in register a1
int timeout = args[1];
if (timeout <= 0)
return (optional_int) { .value = 0 };
// set the asleep_until field
unsigned int len = (unsigned int) timeout;
pcb->asleep_until = read_time() + len;
// here we can return whatever value we want, as it is overwritten when
// the process is awoken again
return (optional_int) { .value = 0 };
}
@ -100,6 +113,7 @@ optional_int ecall_handle_exit(int* args, struct process_control_block* pcb)
pcb->status = PROC_DEAD;
pcb->exit_code = *args;
// print a message if debugging is enabled
if (DEBUGGING) {
char msg[34] = "process exited with code ";
@ -119,10 +133,12 @@ optional_int ecall_handle_exit(int* args, struct process_control_block* pcb)
void trap_handle_ecall()
{
// save current clock so we don't waste too much process time
mark_ecall_entry();
// get the current process
struct process_control_block* pcb = get_current_process();
int *regs = pcb->regs;
int code = regs[REG_A0 + 7]; // code is stored inside a7
int code = regs[REG_A0 + 7]; // syscall code is stored inside a7
// check if the code is too large/small or if the handler is zero
if (code < 0 || code > ECALL_TABLE_LEN || ecall_table[code] == NULL) {
@ -154,7 +170,7 @@ void trap_handle(int interrupt_bit, int code, int mtval)
scheduler_run_next();
break;
default:
// impossible
// any other interrupt is not supported currently
HALT(12);
break;
}
@ -187,6 +203,8 @@ void trap_handle(int interrupt_bit, int code, int mtval)
__builtin_unreachable();
}
// this writes the function pointers to the ecall table
// it's called from the kernels init() function
void init_ecall_table()
{
ecall_table[ECALL_SPAWN] = ecall_handle_spawn_thread;
@ -196,6 +214,8 @@ void init_ecall_table()
ecall_table[ECALL_EXIT] = ecall_handle_exit;
}
// this exception handler is crude and just kills off any process who
// causes an exception.
void handle_exception(int ecode, int mtval)
{
// kill off offending process

@ -1,23 +1,33 @@
#include "io.h"
// this file should only be used for debugging purposes. Most functions here
// could easily corrupt kernel memory if used with untrusted input.
// ALWAYS check your buffer lengths!
#ifdef TEXT_IO_ADDR
// this function writes a string to the TEXT_IO buffer
// it adds a newline at the end and splits the passed string into smaller chunks
// if it is larger than TEXT_IO_BUFLEN.
void dbgln(char* text, int len)
{
// if the passed text is longer than TEXT_IO_BUFLEN, print it in chunks
while (len > TEXT_IO_BUFLEN) {
dbgln(text, TEXT_IO_BUFLEN);
text += TEXT_IO_BUFLEN;
len -= TEXT_IO_BUFLEN;
}
// this is the address of the textIO
char* ioaddr = (char*) TEXT_IO_ADDR + 4;
// write message bytewise to buffer (this could be implemented faster)
for (int i = 0; i < len; i++) {
if (*text == 0)
break;
*ioaddr++ = *text++;
}
// add a newline
if (len < TEXT_IO_BUFLEN)
*ioaddr = '\n';
@ -28,13 +38,16 @@ void dbgln(char* text, int len)
/* alphabet for itoa */
char alpha[16] = "0123456789abcdef";
// convert int to str
char* itoa(int value, char* str, int base)
{
// fail on unknown base
if (base > 16 || base < 2) {
*str++ = '?';
return str;
}
// handle negative numbers
if (value < 0) {
*str++ = '-';
value *= -1;
@ -51,6 +64,7 @@ char* itoa(int value, char* str, int base)
digits++;
} while (value > 0);
// write reversed number to the buffer
value = num;
do {
num = value % base;

@ -1,8 +1,14 @@
#ifndef H_ktypes
#define H_ktypes
// define the nullpointer
#define NULL ((void*) 0)
// types with explicit bit-widths
typedef unsigned int uint32;
typedef unsigned long long uint64;
typedef unsigned char byte;
/*
* Error codes
*/

@ -5,7 +5,7 @@
#include "io.h"
// information about the systems memory layout is stored here
static malloc_info global_malloc_info = { 0 };
static struct malloc_info global_malloc_info = { 0 };
// this pointer points to the end of unused memory
static void* allocate_memory_end;
// this stack holds currently unused program stacks
@ -22,8 +22,11 @@ void stash_stack(void* stack_top)
}
}
void malloc_init(malloc_info* given_info)
// this function is called by the kernels init() function after it parsed the
// list of loaded binaries and calculated the available memory for general allocation
void malloc_init(struct malloc_info* given_info)
{
// save the passed info
global_malloc_info = *given_info;
allocate_memory_end = given_info->allocate_memory_end;
allocate_memory_end = (void*) (((int) allocate_memory_end) - PROCESS_COUNT);
@ -53,6 +56,8 @@ optional_voidptr malloc_stack()
return (optional_voidptr) { .value = stack_top };
}
// a stack is not returned to the general memory pool (since EMBARK has no such
// pool) but rather just added to the pool of free process stacks.
void free_stack(void* stack_top)
{
stash_stack(stack_top);

@ -3,14 +3,14 @@
#include "ktypes.h"
typedef struct malloc_info {
struct malloc_info {
void* allocate_memory_end;
void* allocate_memory_start;
} malloc_info;
};
optional_voidptr malloc_stack();
void free_stack(void* stack_top);
void malloc_init(malloc_info* info);
void malloc_init(struct malloc_info* info);
#endif

@ -15,8 +15,8 @@ struct process_control_block processes[PROCESS_COUNT];
// pointer to the currently scheduled process
struct process_control_block* current_process = NULL;
// timer variables to add kernel time back to the processes time slice
unsigned long long int scheduling_interrupted_start;
unsigned long long int next_interrupt_scheduled_for;
uint64 scheduling_interrupted_start;
uint64 next_interrupt_scheduled_for;
// this counter generates process ids
int next_process_id = 1;
@ -60,7 +60,7 @@ void scheduler_try_return_to(struct process_control_block* pcb)
// select a new process to run next
struct process_control_block* scheduler_select_free()
{
unsigned long long int mtime;
uint64 mtime;
int timeout_available = 0; // note if a timeout is available
while (1) {
@ -122,6 +122,7 @@ struct process_control_block* scheduler_select_free()
}
}
// performs the context switch from kernel to userspace mode
void scheduler_switch_to(struct process_control_block* pcb)
{
CSR_WRITE(CSR_MEPC, pcb->pc);
@ -167,6 +168,7 @@ void scheduler_switch_to(struct process_control_block* pcb)
__builtin_unreachable();
}
// get a PCB from a pid
struct process_control_block* process_from_pid(int pid)
{
for (int i = 0; i < PROCESS_COUNT; i++) {
@ -186,6 +188,7 @@ struct process_control_block* get_current_process()
return current_process;
}
// this method sets up the mtimecmp register to trigger the next timer interrupt
void set_next_interrupt()
{
next_interrupt_scheduled_for = read_time() + TIME_SLICE_LEN;
@ -197,14 +200,20 @@ void mark_ecall_entry()
scheduling_interrupted_start = read_time();
}
// this function selects an unused entry in the processes list
// it tries to select slots which have been unused the longest
optional_pcbptr find_available_pcb_slot()
{
// this method loops over the process list using an index which persists
// over multiple function calls, wrapping when it reaches the end. This
// makes free space selection relatively fair.
static int index = 0;
int start_index = index;
struct process_control_block* pcb = processes + index;
while (pcb->status != PROC_DEAD) {
index = (index + 1) % PROCESS_COUNT;
// if we iterated over the whole list and found nothing, we have no space left!
if (index == start_index)
return (optional_pcbptr) { .error = ENOBUFS };
pcb = processes + index;
@ -214,9 +223,9 @@ optional_pcbptr find_available_pcb_slot()
return (optional_pcbptr) { .value = pcb };
}
optional_pcbptr create_new_process(loaded_binary* bin, int stack_size)
optional_pcbptr create_new_process(loaded_binary* bin)
{
// try to get a position in the processes list
// try to get an unused entry in the processes list
optional_pcbptr slot_or_err = find_available_pcb_slot();
// if that failed, we cannot creat a new process
@ -226,7 +235,7 @@ optional_pcbptr create_new_process(loaded_binary* bin, int stack_size)
}
// allocate stack for the new process
optional_voidptr stack_top_or_err = malloc_stack(stack_size); // allocate 4Kib stack
optional_voidptr stack_top_or_err = malloc_stack(); // allocate stack
// if that failed, we also can't create a new process
if (has_error(stack_top_or_err)) {
@ -259,9 +268,9 @@ optional_pcbptr create_new_process(loaded_binary* bin, int stack_size)
return (optional_pcbptr) { .value = pcb };
}
optional_pcbptr create_new_thread(struct process_control_block* parent, void* entrypoint, void* args, int stack_size)
optional_pcbptr create_new_thread(struct process_control_block* parent, void* entrypoint, void* args)
{
// try to get a position in the processes list
// try to get an unused entry in the processes list
optional_pcbptr slot_or_err = find_available_pcb_slot();
// if that failed, we cannot creat a new process
@ -271,7 +280,7 @@ optional_pcbptr create_new_thread(struct process_control_block* parent, void* en
}
// allocate stack for the new process
optional_voidptr stack_top_or_err = malloc_stack(stack_size); // allocate 4Kib stack
optional_voidptr stack_top_or_err = malloc_stack(); // allocate stack
// if that failed, we also can't create a new process
if (has_error(stack_top_or_err)) {

@ -20,8 +20,8 @@ struct process_control_block* get_current_process();
void mark_ecall_entry();
// process creation / destruction
optional_pcbptr create_new_process(loaded_binary*, int);
optional_pcbptr create_new_thread(struct process_control_block*, void*, void*, int);
optional_pcbptr create_new_process(loaded_binary*);
optional_pcbptr create_new_thread(struct process_control_block*, void*, void*);
void destroy_process(struct process_control_block* pcb);
void kill_child_processes(struct process_control_block* pcb);
#endif

@ -14,17 +14,8 @@ SECTOR_SIZE = 512
# start address
MEM_START = 0x100
# this is the name of the global variable holding the list of loaded binaries
KERNEL_BINARY_TABLE = 'binary_table'
# loaded_binary struct size (4 integers)
KERNEL_BINARY_TABLE_ENTRY_SIZE = 4 * 4
# overwrite this function to generate the entries for the loaded binary list
def create_loaded_bin_struct(binid: int, entrypoint: int, start: int, end: int):
"""
Creates the binary data to populate the KERNEL_BINARY_TABLE structs
"""
return b''.join(num.to_bytes(4, 'little') for num in (binid, entrypoint, start, end))
# address where the userspace binaries should be located (-1 to start directly after the kernel)
USR_BIN_START = -1
## end of config
@ -40,7 +31,23 @@ EMPTY_SECTIONS = set((
'.bss', '.sbss', '.stack'
))
# this is the name of the global variable holding the list of loaded binaries
KERNEL_BINARY_TABLE = 'binary_table'
# loaded_binary struct size (4 integers)
KERNEL_BINARY_TABLE_ENTRY_SIZE = 4 * 4
# overwrite this function to generate the entries for the loaded binary list
def create_loaded_bin_struct(binid: int, entrypoint: int, start: int, end: int):
"""
Creates the binary data to populate the KERNEL_BINARY_TABLE structs
"""
return b''.join(num.to_bytes(4, 'little') for num in (binid, entrypoint, start, end))
def overlaps(p1, l1, p2, l2) -> bool:
"""
check if the intervals (p1, p1+l1) and (p2, p2+l2) overlap
"""
return (p1 <= p2 and p1 + l1 > p2) or (p2 <= p1 and p2 + l2 > p1)
class Section:
@ -202,6 +209,9 @@ def package(kernel: str, binaries: List[str], out: str):
img.putBin(kernel)
if USR_BIN_START > 0:
img.seek(USR_BIN_START)
binid = 0
for bin_name in binaries:
img.align(8) # align to eight bytes

@ -11,5 +11,5 @@ spawn:
thread:
$(CC) $(CFLAGS) -o threads threads.c
all: simple spawn threads
all: simple spawn thread

@ -110,22 +110,6 @@ char* itoa(int value, char* str, int base)
return str;
}
void wrap_main()
{
dbgln("start", 5);
register int code asm ("s1") = main();
dbgln("end", 3);
__asm__ __volatile__ (
"mv a0, s1\n"
"li a7, 5\n"
"ecall\n"
"ebreak\n"
);
}
void _start()
{
__asm__ __volatile__ (
@ -134,6 +118,10 @@ void _start()
" la gp, _gp\n"
".option pop\n"
);
__asm__ __volatile__ (
"mv a0, %0\n"
"li a7, 5\n"
"ecall\n" :: "r"(main())
);
wrap_main();
}

@ -9,12 +9,13 @@ int main()
struct optional_int t1 = spawn(thread, &arg1);
if (has_error(t1)) {
__asm__("ebreak");
__asm__ ("ebreak");
return 1;
}
struct optional_int t2 = spawn(thread, &arg2);
if (has_error(t2)) {
__asm__("ebreak");
__asm__ ("ebreak");
return 2;
}
@ -36,6 +37,7 @@ int thread(void* args)
{
// read value
int arg = *((int*) args);
//char buff[64] = "sleeping for ";
//char* end = itoa(arg, &buff[13], 10);
@ -118,12 +120,10 @@ void _start()
" la gp, _gp\n"
".option pop\n"
);
register int code asm ("s1") = main();
main();
__asm__ __volatile__ (
"mv a0, s1\n"
"li a7, 5\n"
"ecall\n"
"ebreak\n"
);
__builtin_unreachable();
}

@ -26,7 +26,11 @@ char* itoa(int value, char* str, int base);
int thread(void* args);
__attribute__((naked)) struct optional_int spawn(int (*target)(void*), void* args) {
// ignore unused parameter errors only for these functions
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
__attribute__((naked)) struct optional_int spawn(int (*target)(void*), void* args)
{
__asm__ (
"li a7, 1\n"
"ecall\n"
@ -35,7 +39,8 @@ __attribute__((naked)) struct optional_int spawn(int (*target)(void*), void* arg
__builtin_unreachable();
}
__attribute__((naked)) struct optional_int join(int pid, int timeout) {
__attribute__((naked)) struct optional_int join(int pid, int timeout)
{
__asm__ (
"li a7, 3\n"
"ecall\n"
@ -45,7 +50,8 @@ __attribute__((naked)) struct optional_int join(int pid, int timeout) {
}
__attribute__((naked)) struct optional_int sleep(int timeout) {
__attribute__((naked)) struct optional_int sleep(int timeout)
{
__asm__ (
"li a7, 2\n"
"ecall\n"
@ -53,3 +59,4 @@ __attribute__((naked)) struct optional_int sleep(int timeout) {
);
__builtin_unreachable();
}
#pragma GCC diagnostic pop

Loading…
Cancel
Save