zephyr/scripts/gen_syscalls.py

255 lines
6.5 KiB
Python
Executable File

#!/usr/bin/env python3
#
# Copyright (c) 2017 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0
import sys
import re
import argparse
import os
api_regex = re.compile(r'''
__syscall\s+ # __syscall attribute, must be first
([^(]+) # type and name of system call (split later)
[(] # Function opening parenthesis
([^)]*) # Arg list (split later)
[)] # Closing parenthesis
''', re.MULTILINE | re.VERBOSE)
typename_regex = re.compile(r'(.*?)([A-Za-z0-9_]+)$')
class SyscallParseException(Exception):
pass
def typename_split(item):
if "[" in item:
raise SyscallParseException("Please pass arrays to syscalls as pointers, unable to process '%s'"
% item)
if "(" in item:
raise SyscallParseException("Please use typedefs for function pointers")
mo = typename_regex.match(item)
if not mo:
raise SyscallParseException("Malformed system call invocation")
m = mo.groups()
return (m[0].strip(), m[1])
def analyze_fn(match_group, fn):
func, args = match_group
try:
if args == "void":
args = []
else:
args = [typename_split(a.strip()) for a in args.split(",")]
func_type, func_name = typename_split(func)
except SyscallParseException:
sys.stderr.write("In declaration of %s\n" % func)
raise
sys_id = "K_SYSCALL_" + func_name.upper()
if func_type == "void":
suffix = "_VOID"
is_void = True
else:
is_void = False
if func_type in ["s64_t", "u64_t"]:
suffix = "_RET64"
else:
suffix = ""
is_void = (func_type == "void")
# Get the proper system call macro invocation, which depends on the
# number of arguments, the return type, and whether the implementation
# is an inline function
macro = "K_SYSCALL_DECLARE%d%s" % (len(args), suffix)
# Flatten the argument lists and generate a comma separated list
# of t0, p0, t1, p1, ... tN, pN as expected by the macros
flat_args = [i for sublist in args for i in sublist]
if not is_void:
flat_args = [func_type] + flat_args
flat_args = [sys_id, func_name] + flat_args
argslist = ", ".join(flat_args)
invocation = "%s(%s);" % (macro, argslist)
handler = "_handler_" + func_name
# Entry in _k_syscall_table
table_entry = "[%s] = %s" % (sys_id, handler)
return (fn, handler, invocation, sys_id, table_entry)
def analyze_headers(base_path):
ret = []
for root, dirs, files in os.walk(base_path):
for fn in files:
# toolchain/common.h has the definition of __syscall which we
# don't want to trip over
path = os.path.join(root, fn)
if not fn.endswith(".h") or path.endswith("toolchain/common.h"):
continue
with open(path, "r", encoding="utf-8") as fp:
try:
result = [analyze_fn(mo.groups(), fn)
for mo in api_regex.finditer(fp.read())]
except Exception:
sys.stderr.write("While parsing %s\n" % fn)
raise
ret.extend(result)
return ret
table_template = """/* auto-generated by gen_syscalls.py, don't edit */
/* Weak handler functions that get replaced by the real ones unless a system
* call is not implemented due to kernel configuration.
*/
%s
const _k_syscall_handler_t _k_syscall_table[K_SYSCALL_LIMIT] = {
\t%s
};
"""
list_template = """
/* auto-generated by gen_syscalls.py, don't edit */
#ifndef _ZEPHYR_SYSCALL_LIST_H_
#define _ZEPHYR_SYSCALL_LIST_H_
#ifndef _ASMLANGUAGE
#ifdef __cplusplus
extern "C" {
#endif
enum {
\t%s
};
%s
#ifdef __cplusplus
}
#endif
#endif /* _ASMLANGUAGE */
#endif /* _ZEPHYR_SYSCALL_LIST_H_ */
"""
syscall_template = """
/* auto-generated by gen_syscalls.py, don't edit */
#ifndef _ASMLANGUAGE
#include <syscall_list.h>
#include <syscall_macros.h>
#ifdef __cplusplus
extern "C" {
#endif
%s
#ifdef __cplusplus
}
#endif
#endif
"""
handler_template = """
extern u32_t %s(u32_t arg1, u32_t arg2, u32_t arg3,
u32_t arg4, u32_t arg5, u32_t arg6, void *ssf);
"""
weak_template = """
__weak ALIAS_OF(_handler_no_syscall)
u32_t %s(u32_t arg1, u32_t arg2, u32_t arg3,
u32_t arg4, u32_t arg5, u32_t arg6, void *ssf);
"""
def parse_args():
global args
parser = argparse.ArgumentParser(description = __doc__,
formatter_class = argparse.RawDescriptionHelpFormatter)
parser.add_argument("-i", "--include", required=True,
help="Base include directory")
parser.add_argument("-d", "--syscall-dispatch", required=True,
help="output C system call dispatch table file")
parser.add_argument("-o", "--base-output", required=True,
help="Base output directory for syscall macro headers")
args = parser.parse_args()
def main():
parse_args()
syscalls = analyze_headers(args.include)
invocations = {}
ids = []
table_entries = []
handlers = []
for fn, handler, inv, sys_id, entry in syscalls:
if fn not in invocations:
invocations[fn] = []
invocations[fn].append(inv)
ids.append(sys_id)
table_entries.append(entry)
handlers.append(handler)
with open(args.syscall_dispatch, "w") as fp:
table_entries.append("[K_SYSCALL_BAD] = _handler_bad_syscall")
weak_defines = "".join([weak_template % name for name in handlers])
fp.write(table_template % (weak_defines, ",\n\t".join(table_entries)))
# Listing header emitted to stdout
ids.sort()
ids.extend(["K_SYSCALL_BAD", "K_SYSCALL_LIMIT"])
handler_defines = "".join([handler_template % name for name in handlers])
sys.stdout.write(list_template % (",\n\t".join(ids), handler_defines))
os.makedirs(args.base_output, exist_ok=True)
for fn, invo_list in invocations.items():
out_fn = os.path.join(args.base_output, fn)
header = syscall_template % "\n\n".join(invo_list)
# Check if the file already exists, and if there are no changes,
# don't touch it since that will force an incremental rebuild
if os.path.exists(out_fn):
with open(out_fn, "r") as fp:
old_data = fp.read()
if old_data == header:
continue
with open(out_fn, "w") as fp:
fp.write(header)
if __name__ == "__main__":
main()