423 lines
13 KiB
Python
Executable File
423 lines
13 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (c) 2017 Intel Corporation
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
"""
|
|
Script to generate system call invocation macros
|
|
|
|
This script parses the system call metadata JSON file emitted by
|
|
parse_syscalls.py to create several files:
|
|
|
|
- A file containing weak aliases of any potentially unimplemented system calls,
|
|
as well as the system call dispatch table, which maps system call type IDs
|
|
to their handler functions.
|
|
|
|
- A header file defing the system call type IDs, as well as function
|
|
prototypes for all system call handler functions.
|
|
|
|
- A directory containing header files. Each header corresponds to a header
|
|
that was identified as containing system call declarations. These
|
|
generated headers contain the inline invocation functions for each system
|
|
call in that header.
|
|
"""
|
|
|
|
import sys
|
|
import re
|
|
import argparse
|
|
import os
|
|
import json
|
|
|
|
types64 = ["s64_t", "u64_t"]
|
|
|
|
# The kernel linkage is complicated. These functions from
|
|
# userspace_handlers.c are present in the kernel .a library after
|
|
# userspace.c, which contains the weak fallbacks defined here. So the
|
|
# linker finds the weak one first and stops searching, and thus won't
|
|
# see the real implementation which should override. Yet changing the
|
|
# order runs afoul of a comment in CMakeLists.txt that the order is
|
|
# critical. These are core syscalls that won't ever be unconfigured,
|
|
# just disable the fallback mechanism as a simple workaround.
|
|
noweak = ["z_mrsh_k_object_release",
|
|
"z_mrsh_k_object_access_grant",
|
|
"z_mrsh_k_object_alloc"]
|
|
|
|
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
|
|
|
|
%s
|
|
|
|
#ifndef _ASMLANGUAGE
|
|
|
|
#include <zephyr/types.h>
|
|
|
|
#endif /* _ASMLANGUAGE */
|
|
|
|
#endif /* ZEPHYR_SYSCALL_LIST_H */
|
|
"""
|
|
|
|
syscall_template = """
|
|
/* auto-generated by gen_syscalls.py, don't edit */
|
|
%s
|
|
|
|
#ifndef _ASMLANGUAGE
|
|
|
|
#include <syscall_list.h>
|
|
#include <syscall_macros.h>
|
|
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
%s
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#pragma GCC diagnostic pop
|
|
|
|
#endif
|
|
#endif /* include guard */
|
|
"""
|
|
|
|
handler_template = """
|
|
extern u32_t z_hdlr_%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);
|
|
"""
|
|
|
|
|
|
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 need_split(argtype):
|
|
return argtype in types64
|
|
|
|
# Note: "lo" and "hi" are named in little endian conventions,
|
|
# but it doesn't matter as long as they are consistently
|
|
# generated.
|
|
def union_decl(type):
|
|
return "union { struct { u32_t lo, hi; } split; %s val; }" % type
|
|
|
|
def wrapper_defs(func_name, func_type, args):
|
|
ret64 = func_type in types64
|
|
mrsh_args = [] # List of rvalue expressions for the marshalled invocation
|
|
split_args = []
|
|
nsplit = 0
|
|
for argtype, argname in args:
|
|
if need_split(argtype):
|
|
split_args.append((argtype, argname))
|
|
mrsh_args.append("parm%d.split.lo" % nsplit)
|
|
mrsh_args.append("parm%d.split.hi" % nsplit)
|
|
nsplit += 1
|
|
else:
|
|
mrsh_args.append("*(u32_t *)&" + argname)
|
|
|
|
if ret64:
|
|
mrsh_args.append("(u32_t)&ret64")
|
|
|
|
decl_arglist = ", ".join([" ".join(argrec) for argrec in args])
|
|
|
|
wrap = "extern %s z_impl_%s(%s);\n" % (func_type, func_name, decl_arglist)
|
|
wrap += "static inline %s %s(%s)\n" % (func_type, func_name, decl_arglist)
|
|
wrap += "{\n"
|
|
wrap += "#ifdef CONFIG_USERSPACE\n"
|
|
wrap += ("\t" + "u64_t ret64;\n") if ret64 else ""
|
|
wrap += "\t" + "if (z_syscall_trap()) {\n"
|
|
|
|
for parmnum, rec in enumerate(split_args):
|
|
(argtype, argname) = rec
|
|
wrap += "\t\t%s parm%d;\n" % (union_decl(argtype), parmnum)
|
|
wrap += "\t\t" + "parm%d.val = %s;\n" % (parmnum, argname)
|
|
|
|
if len(mrsh_args) > 6:
|
|
wrap += "\t\t" + "u32_t more[] = {\n"
|
|
wrap += "\t\t\t" + (",\n\t\t\t".join(mrsh_args[5:])) + "\n"
|
|
wrap += "\t\t" + "};\n"
|
|
mrsh_args[5:] = ["(u32_t) &more"]
|
|
|
|
syscall_id = "K_SYSCALL_" + func_name.upper()
|
|
invoke = ("z_arch_syscall_invoke%d(%s)"
|
|
% (len(mrsh_args),
|
|
", ".join(mrsh_args + [syscall_id])))
|
|
|
|
if ret64:
|
|
wrap += "\t\t" + "(void)%s;\n" % invoke
|
|
wrap += "\t\t" + "return (%s)ret64;\n" % func_type
|
|
elif func_type == "void":
|
|
wrap += "\t\t" + "%s;\n" % invoke
|
|
wrap += "\t\t" + "return;\n"
|
|
else:
|
|
wrap += "\t\t" + "return (%s) %s;\n" % (func_type, invoke)
|
|
|
|
wrap += "\t" + "}\n"
|
|
wrap += "#endif\n"
|
|
|
|
# Otherwise fall through to direct invocation of the impl func.
|
|
# Note the compiler barrier: that is required to prevent code from
|
|
# the impl call from being hoisted above the check for user
|
|
# context.
|
|
impl_arglist = ", ".join([argrec[1] for argrec in args])
|
|
impl_call = "z_impl_%s(%s)" % (func_name, impl_arglist)
|
|
wrap += "\t" + "compiler_barrier();\n"
|
|
wrap += "\t" + "%s%s;\n" % ("return " if func_type != "void" else "",
|
|
impl_call)
|
|
|
|
wrap += "}\n"
|
|
|
|
return wrap
|
|
|
|
# Returns an expression for the specified (zero-indexed!) marshalled
|
|
# parameter to a syscall, with handling for a final "more" parameter.
|
|
def mrsh_rval(mrsh_num, total):
|
|
if mrsh_num < 5 or total <= 6:
|
|
return "arg%d" % mrsh_num
|
|
else:
|
|
return "(((u32_t *)more)[%d])" % (mrsh_num - 5)
|
|
|
|
def marshall_defs(func_name, func_type, args):
|
|
mrsh_name = "z_mrsh_" + func_name
|
|
|
|
nmrsh = 0 # number of marshalled u32_t parameter
|
|
vrfy_parms = [] # list of (arg_num, mrsh_or_parm_num, bool_is_split)
|
|
split_parms = [] # list of a (arg_num, mrsh_num) for each split
|
|
for i, (argtype, _) in enumerate(args):
|
|
if need_split(argtype):
|
|
vrfy_parms.append((i, len(split_parms), True))
|
|
split_parms.append((i, nmrsh))
|
|
nmrsh += 2
|
|
else:
|
|
vrfy_parms.append((i, nmrsh, False))
|
|
nmrsh += 1
|
|
|
|
# Final argument for a 64 bit return value?
|
|
if func_type in types64:
|
|
nmrsh += 1
|
|
|
|
decl_arglist = ", ".join([" ".join(argrec) for argrec in args])
|
|
mrsh = "extern %s z_vrfy_%s(%s);\n" % (func_type, func_name, decl_arglist)
|
|
|
|
mrsh += "u32_t %s(u32_t arg0, u32_t arg1, u32_t arg2,\n" % mrsh_name
|
|
if nmrsh <= 6:
|
|
mrsh += "\t\t" + "u32_t arg3, u32_t arg4, u32_t arg5, void *ssf)\n"
|
|
else:
|
|
mrsh += "\t\t" + "u32_t arg3, u32_t arg4, void *more, void *ssf)\n"
|
|
mrsh += "{\n"
|
|
mrsh += "\t" + "_current_cpu->syscall_frame = ssf;\n"
|
|
|
|
for unused_arg in range(nmrsh, 6):
|
|
mrsh += "\t(void) arg%d;\t/* unused */\n" % unused_arg
|
|
|
|
if nmrsh > 6:
|
|
mrsh += ("\tZ_OOPS(Z_SYSCALL_MEMORY_READ(more, "
|
|
+ str(nmrsh - 6) + " * sizeof(u32_t)));\n")
|
|
|
|
for i, split_rec in enumerate(split_parms):
|
|
arg_num, mrsh_num = split_rec
|
|
arg_type = args[arg_num][0]
|
|
mrsh += "\t%s parm%d;\n" % (union_decl(arg_type), i)
|
|
mrsh += "\t" + "parm%d.split.lo = %s;\n" % (i, mrsh_rval(mrsh_num,
|
|
nmrsh))
|
|
mrsh += "\t" + "parm%d.split.hi = %s;\n" % (i, mrsh_rval(mrsh_num + 1,
|
|
nmrsh))
|
|
# Finally, invoke the verify function
|
|
out_args = []
|
|
for i, argn, is_split in vrfy_parms:
|
|
if is_split:
|
|
out_args.append("parm%d.val" % argn)
|
|
else:
|
|
out_args.append("*(%s*)&%s" % (args[i][0], mrsh_rval(argn, nmrsh)))
|
|
|
|
vrfy_call = "z_vrfy_%s(%s)\n" % (func_name, ", ".join(out_args))
|
|
|
|
if func_type == "void":
|
|
mrsh += "\t" + "%s;\n" % vrfy_call
|
|
mrsh += "\t" + "return 0;\n"
|
|
else:
|
|
mrsh += "\t" + "%s ret = %s;\n" % (func_type, vrfy_call)
|
|
if func_type in types64:
|
|
ptr = "((u64_t *)%s)" % mrsh_rval(nmrsh - 1, nmrsh)
|
|
mrsh += "\t" + "Z_OOPS(Z_SYSCALL_MEMORY_WRITE(%s, 8));\n" % ptr
|
|
mrsh += "\t" + "*%s = ret;\n" % ptr
|
|
mrsh += "\t" + "return 0;\n"
|
|
else:
|
|
mrsh += "\t" + "return (u32_t) ret;\n"
|
|
|
|
mrsh += "}\n"
|
|
|
|
return mrsh, mrsh_name
|
|
|
|
def analyze_fn(match_group):
|
|
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()
|
|
|
|
marshaller = None
|
|
marshaller, handler = marshall_defs(func_name, func_type, args)
|
|
invocation = wrapper_defs(func_name, func_type, args)
|
|
|
|
# Entry in _k_syscall_table
|
|
table_entry = "[%s] = %s" % (sys_id, handler)
|
|
|
|
return (handler, invocation, marshaller, sys_id, table_entry)
|
|
|
|
def parse_args():
|
|
global args
|
|
parser = argparse.ArgumentParser(
|
|
description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
|
|
parser.add_argument("-i", "--json-file", required=True,
|
|
help="Read syscall information from json file")
|
|
parser.add_argument("-d", "--syscall-dispatch", required=True,
|
|
help="output C system call dispatch table file")
|
|
parser.add_argument("-l", "--syscall-list", required=True,
|
|
help="output C system call list header")
|
|
parser.add_argument("-o", "--base-output", required=True,
|
|
help="Base output directory for syscall macro headers")
|
|
parser.add_argument("-s", "--split-type", action="append",
|
|
help="A long type that must be split/marshalled")
|
|
args = parser.parse_args()
|
|
|
|
|
|
def main():
|
|
parse_args()
|
|
|
|
if args.split_type is not None:
|
|
for t in args.split_type:
|
|
types64.append(t)
|
|
|
|
with open(args.json_file, 'r') as fd:
|
|
syscalls = json.load(fd)
|
|
|
|
invocations = {}
|
|
mrsh_defs = {}
|
|
mrsh_includes = {}
|
|
ids = []
|
|
table_entries = []
|
|
handlers = []
|
|
|
|
for match_group, fn in syscalls:
|
|
handler, inv, mrsh, sys_id, entry = analyze_fn(match_group)
|
|
|
|
if fn not in invocations:
|
|
invocations[fn] = []
|
|
|
|
invocations[fn].append(inv)
|
|
ids.append(sys_id)
|
|
table_entries.append(entry)
|
|
handlers.append(handler)
|
|
|
|
if mrsh:
|
|
syscall = typename_split(match_group[0])[1]
|
|
mrsh_defs[syscall] = mrsh
|
|
mrsh_includes[syscall] = "#include <syscalls/%s>" % fn
|
|
|
|
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
|
|
if not name in noweak])
|
|
|
|
# The "noweak" ones just get a regular declaration
|
|
weak_defines += "\n".join(["extern u32_t %s(u32_t arg1, u32_t arg2, u32_t arg3, u32_t arg4, u32_t arg5, u32_t arg6, void *ssf);"
|
|
% s for s in noweak])
|
|
|
|
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"])
|
|
|
|
ids_as_defines = ""
|
|
for i, item in enumerate(ids):
|
|
ids_as_defines += "#define {} {}\n".format(item, i)
|
|
|
|
with open(args.syscall_list, "w") as fp:
|
|
fp.write(list_template % ids_as_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)
|
|
|
|
ig = re.sub("[^a-zA-Z0-9]", "_", "Z_INCLUDE_SYSCALLS_" + fn).upper()
|
|
include_guard = "#ifndef %s\n#define %s\n" % (ig, ig)
|
|
header = syscall_template % (include_guard, "\n\n".join(invo_list))
|
|
|
|
with open(out_fn, "w") as fp:
|
|
fp.write(header)
|
|
|
|
# Likewise emit _mrsh.c files for syscall inclusion
|
|
for fn in mrsh_defs:
|
|
mrsh_fn = os.path.join(args.base_output, fn + "_mrsh.c")
|
|
|
|
with open(mrsh_fn, "w") as fp:
|
|
fp.write("/* auto-generated by gen_syscalls.py, don't edit */\n")
|
|
fp.write("#pragma GCC diagnostic push\n")
|
|
fp.write("#pragma GCC diagnostic ignored \"-Wstrict-aliasing\"\n")
|
|
fp.write(mrsh_includes[fn] + "\n")
|
|
fp.write("\n")
|
|
fp.write(mrsh_defs[fn] + "\n")
|
|
fp.write("#pragma GCC diagnostic pop\n")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|