Skip to content

Commit

Permalink
Generate all syscall glue code
Browse files Browse the repository at this point in the history
A lot of syscalls glue code is very repetitive and adheres to the same
pattern. It's easily generatable from a very simple and concise source
of truth. That source of truth is now in include/syscalls.hh, and the
scripts/gen-syscalls.py generates the following files from it:
* include/syscallnums.h
* include/syscalldecls.h
* src/syscalls.c
* user/inc/usyscalls.h
* user/src/usyscalls.S

All these files are still tracked in git, both for accountability, and
for easier readability of complete code.

A new syscall should now be added only to include/syscalls.hh, the rest
will get dealt with by the Makefile.
  • Loading branch information
rtfb committed Apr 21, 2024
1 parent 3692188 commit 8a509bf
Show file tree
Hide file tree
Showing 12 changed files with 390 additions and 161 deletions.
15 changes: 15 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,21 @@ run-virt: $(OUT)/os_virt
gdb:
$(GDB) $(shell cat .debug-session)

user/inc/usyscalls.h: include/syscalls.hh scripts/gen-syscalls.py
./scripts/gen-syscalls.py

user/inc/usyscalls.S: include/syscalls.hh scripts/gen-syscalls.py
./scripts/gen-syscalls.py

include/syscallnums.h: include/syscalls.hh scripts/gen-syscalls.py
./scripts/gen-syscalls.py

include/syscalldecls.h: include/syscalls.hh scripts/gen-syscalls.py
./scripts/gen-syscalls.py

src/syscalls.c: include/syscalls.hh scripts/gen-syscalls.py
./scripts/gen-syscalls.py

GCC_FLAGS=-static -mcmodel=medany -fvisibility=hidden -nostdlib -nostartfiles \
-ffreestanding \
-fno-plt -fno-pic \
Expand Down
30 changes: 30 additions & 0 deletions include/syscalldecls.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// This file is generated by scripts/gen-syscalls.py from include/syscalls.hh.
// To add a new system call, edit syscalls.hh. gen-syscalls.py will be rerun on
// the next build and this file will be modified. Commit in the changed file for
// easier readability.
#ifndef _SYSCALLDECLS_H_
#define _SYSCALLDECLS_H_

regsize_t sys_exit();
regsize_t sys_fork();
regsize_t sys_read();
regsize_t sys_write();
regsize_t sys_open();
regsize_t sys_close();
regsize_t sys_wait();
regsize_t sys_execv();
regsize_t sys_getpid();
regsize_t sys_dup();
regsize_t sys_pipe();
regsize_t sys_sysinfo();
regsize_t sys_sleep();
regsize_t sys_plist();
regsize_t sys_pinfo();
regsize_t sys_pgalloc();
regsize_t sys_pgfree();
regsize_t sys_gpio();
regsize_t sys_detach();
regsize_t sys_isopen();
regsize_t sys_pipeattch();
regsize_t sys_lsdir();
#endif
58 changes: 27 additions & 31 deletions include/syscallnums.h
Original file line number Diff line number Diff line change
@@ -1,32 +1,28 @@
// This file is to be included both in usyscalls.S and in C code, so let's keep
// it neat and minimal.
// This file is generated by scripts/gen-syscalls.py from include/syscalls.hh.
// To add a new system call, edit syscalls.hh. gen-syscalls.py will be rerun on
// the next build and this file will be modified. Commit in the changed file for
// easier readability.
#define SYS_NR_exit 1
#define SYS_NR_fork 2
#define SYS_NR_read 3
#define SYS_NR_write 4
#define SYS_NR_open 5
#define SYS_NR_close 6
#define SYS_NR_wait 7
#define SYS_NR_execv 11
#define SYS_NR_getpid 20
#define SYS_NR_dup 28
#define SYS_NR_pipe 29
#define SYS_NR_sysinfo 30
#define SYS_NR_sleep 31
#define SYS_NR_plist 32
#define SYS_NR_pinfo 33
#define SYS_NR_pgalloc 34
#define SYS_NR_pgfree 35
#define SYS_NR_gpio 36
#define SYS_NR_detach 37
#define SYS_NR_isopen 38
#define SYS_NR_pipeattch 39
#define SYS_NR_lsdir 40

#define SYS_NR_restart 0
#define SYS_NR_exit 1
#define SYS_NR_fork 2
#define SYS_NR_read 3
#define SYS_NR_write 4
#define SYS_NR_open 5
#define SYS_NR_close 6
#define SYS_NR_wait 7
#define SYS_NR_execv 11
#define SYS_NR_getpid 20

// The numbers below differ from Linux, may need to renumber one day if we ever
// want to achieve ABI compatibility. But for now, I don't want to make
// trap_vector too big while very sparsely populated.
#define SYS_NR_dup 28 // __NR_dup is 41 on Linux
#define SYS_NR_pipe 29 // __NR_pipe is 42 on Linux
#define SYS_NR_sysinfo 30 // __NR_sysinfo is 116 on Linux
#define SYS_NR_sleep 31 // __NR_nanosleep is 162 on Linux

// These are non-standard syscalls
#define SYS_NR_plist 32
#define SYS_NR_pinfo 33
#define SYS_NR_pgalloc 34
#define SYS_NR_pgfree 35
#define SYS_NR_gpio 36
#define SYS_NR_detach 37
#define SYS_NR_isopen 38
#define SYS_NR_pipeattch 39
#define SYS_NR_lsdir 40
#define SYSCALL_VECTOR_LEN 40
29 changes: 2 additions & 27 deletions include/syscalls.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define _SYSCALLS_H_

#include "sys.h"
#include "syscalldecls.h"
#include "syscallnums.h"

extern void *syscall_vector[];
Expand Down Expand Up @@ -56,33 +57,7 @@ typedef struct wait_cond_s {
uint64_t want_nscheds;
} wait_cond_t;

// TODO: unify the return values of syscalls

regsize_t sys_restart();
regsize_t sys_exit();
uint32_t sys_fork();
int32_t sys_read();
int32_t sys_write();
int32_t sys_open();
int32_t sys_close();
int32_t sys_wait();
uint32_t sys_execv();
uint32_t sys_getpid();
uint32_t sys_dup();
uint32_t sys_pipe();
uint32_t sys_sysinfo();
uint32_t sys_sleep();
uint32_t sys_plist();
uint32_t sys_pinfo();
regsize_t sys_pgalloc();
regsize_t sys_pgfree();
uint32_t sys_gpio();
uint32_t sys_detach();
int32_t sys_isopen();
uint32_t sys_pipeattch();
uint32_t sys_lsdir();

// These are implemented in assembler as of now:
// Implemented in src/baremetal-poweroff.S
extern void poweroff();

#endif // ifndef _SYSCALLS_H_
41 changes: 41 additions & 0 deletions include/syscalls.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// This file is an input to scripts/gen-syscalls.py, not meant to be directly
// #included in a .c file.

1: exit(int status);
2: fork();
3: read(uint32_t fd, char* buf, uint32_t size);

// write writes the given data to file descriptor fd. The three special file
// descriptors are stdin=0, stdout=1 and stderr=2. Other descriptors should be obtained
// via open(). The size parameter is optional: it specifies how many bytes to
// write from data, but it can be -1 if data contains a zero-terminated string,
// then data will be written until the first zero byte is encountered.
4: write(uint32_t fd, void* data, uint32_t size);
5: open(char const *filepath, uint32_t flags);
6: close(uint32_t fd);
7: wait(wait_cond_t *cond);
11: execv(char const* filename, char const* argv[]);
20: getpid();

// The numbers below differ from Linux, may need to renumber one day if we ever
// want to achieve ABI compatibility. But for now, I don't want to make
// syscall_vector too big while very sparsely populated.
// __NR_dup is 41 on Linux
28: dup(uint32_t fd);
// __NR_pipe is 42 on Linux
29: pipe(uint32_t fd[2]);
// __NR_sysinfo is 116 on Linux
30: sysinfo(sysinfo_t* info);
// __NR_nanosleep is 162 on Linux
31: sleep(uint64_t milliseconds);

// These are non-standard syscalls
32: plist(uint32_t *pids, uint32_t size);
33: pinfo(uint32_t pid, pinfo_t *pinfo);
34: pgalloc();
35: pgfree(void *page);
36: gpio(uint32_t pin_num, uint32_t enable, uint32_t value);
37: detach();
38: isopen(int32_t fd);
39: pipeattch(uint32_t pid, int32_t src_fd);
40: lsdir(char const *dir, dirent_t *dirents, int size);
177 changes: 177 additions & 0 deletions scripts/gen-syscalls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#!/usr/bin/env python3

from dataclasses import dataclass
from io import StringIO
from typing import Callable


@dataclass
class Param:
input: str
type: str
name: str
array_qual: str


@dataclass
class SyscallDecl:
full_input: str
ret_type: str
decl: str
func_name: str
syscall_num: str
params: list[Param]


@dataclass
class GenFile:
filename: str
generator: Callable[[list[SyscallDecl]], str]


def parse_params(input: str) -> list[Param]:
if input == '':
return []
plist = input.split(',')
result = []
for p in plist:
array_qual = ''
type, _, name = p.strip().rpartition(' ')
if name.startswith('*'):
type += '*'
name = name[1:]
if name.startswith('*'):
type += '*'
name = name[1:]
if name.endswith(']'):
open_sq_br = name.find('[')
array_qual = name[open_sq_br:]
name = name[:open_sq_br]
result.append(Param(p, type, name, array_qual))
return result


def parse(input: str) -> SyscallDecl:
syscall_num, _, decl = input.partition(': ')
decl = decl.strip()
# ret_type, decl = input.split(maxsplit=1)
ret_type = 'regsize_t'
func_name, _, temp = decl.partition('(')
params_str, _, _ = temp.partition(')')
params = parse_params(params_str)
return SyscallDecl(input, ret_type, decl, func_name, syscall_num, params)


def gen_usyscalls_h(data: list[SyscallDecl]) -> str:
f = StringIO()
f.write('#include "syscalls.h"\n')
for d in data:
f.write(f'extern {d.ret_type} {d.decl}\n')
return f.getvalue()


def gen_usyscalls_s(data: list[SyscallDecl]) -> str:
f = StringIO()
f.write('''#include "syscallnums.h"
// for fun let\'s pretend syscall is kinda like Linux: syscall nr in a7, other
// arguments in a0..a6')
.macro macro_syscall nr
li a7, \\nr
ecall
.endm
''')
f.write('\n.balign 4\n')
f.write('.section .user_text\n')
for d in data:
f.write(f'\n.globl {d.func_name}\n')
f.write(f'{d.func_name}:\n')
f.write(f' macro_syscall SYS_NR_{d.func_name}\n')
f.write(f' ret\n')
return f.getvalue()


def gen_syscallnums(data: list[SyscallDecl]) -> str:
f = StringIO()
for d in data:
f.write(f'#define SYS_NR_{d.func_name}'.ljust(32) + f'{d.syscall_num}\n')
greatest_syscall_num = max(map(lambda d: int(d.syscall_num), data))
f.write(f'\n#define SYSCALL_VECTOR_LEN {greatest_syscall_num}\n')
return f.getvalue()


def gen_syscalls(data: list[SyscallDecl]) -> None:
f = StringIO()
f.write('''
#include "proc.h"
#include "syscalls.h"
// for fun let's pretend syscall table is kinda like 32bit Linux on x86,
// /usr/include/asm/unistd_32.h: __NR_restart_syscall 0, __NR_exit 1, _NR_fork 2, __NR_read 3, __NR_write 4
//
// Note that we place syscall_vector in a .text segment in order to have it in
// ROM, since it's read-only after all.
''')
f.write('void *syscall_vector[] _text = {\n')
for d in data:
f.write(f' [SYS_NR_{d.func_name}]'.ljust(32) + f'sys_{d.func_name},\n')
f.write('};\n')
for d in data:
f.write(f'\n{d.ret_type} sys_{d.func_name}() {{\n')
for i, p in enumerate(d.params):
type = p.type
if p.array_qual != '':
type += '*'
f.write(f' {type} {p.name} = ({type})trap_frame.regs[REG_A{i}];\n')
calllist = ', '.join([p.name for p in d.params])
f.write(f' return proc_{d.func_name}({calllist});\n')
f.write('}\n')
return f.getvalue()


def gen_syscalldecls(data: list[SyscallDecl]) -> str:
f = StringIO()
f.write('''#ifndef _SYSCALLDECLS_H_
#define _SYSCALLDECLS_H_
''')
for d in data:
f.write(f'regsize_t sys_{d.func_name}();\n')
f.write('#endif\n')
return f.getvalue()


def load_data(filename: str) -> list[SyscallDecl]:
ilines = open(filename).readlines()
data = []
for line in ilines:
if line.strip() == '':
continue
if line.startswith('//'):
continue
data.append(parse(line))
return data


def main():
data = load_data('include/syscalls.hh')
gens = [
GenFile('user/inc/usyscalls.h', gen_usyscalls_h),
GenFile('user/src/usyscalls.S', gen_usyscalls_s),
GenFile('include/syscallnums.h', gen_syscallnums),
GenFile('src/syscalls.c', gen_syscalls),
GenFile('include/syscalldecls.h', gen_syscalldecls),
]
for g in gens:
fo = open(g.filename, 'w')
fo.write('''// This file is generated by scripts/gen-syscalls.py from include/syscalls.hh.
// To add a new system call, edit syscalls.hh. gen-syscalls.py will be rerun on
// the next build and this file will be modified. Commit in the changed file for
// easier readability.
''')
fo.write(g.generator(data))
fo.flush()


if __name__ == '__main__':
main()
2 changes: 1 addition & 1 deletion src/syscall.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ void syscall(regsize_t kernel_sp) {
// TODO: kill proc
return;
}
if (nr >= 0 && nr <= syscall_vector_len && syscall_vector[nr] != 0) {
if (nr >= 0 && nr <= SYSCALL_VECTOR_LEN && syscall_vector[nr] != 0) {
int32_t (*funcPtr)(void) = syscall_vector[nr];
trap_frame.regs[REG_A0] = (*funcPtr)();
} else {
Expand Down
Loading

0 comments on commit 8a509bf

Please sign in to comment.