#!/usr/bin/env python3 # # Copyright (c) 2017 Intel Corporation # # SPDX-License-Identifier: Apache-2.0 import sys import re import argparse import os import json 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(os.path.join(os.sep, '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 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( "-j", "--json-file", required=True, help="Write system call prototype information as json to file") args = parser.parse_args() def main(): parse_args() syscalls = analyze_headers(args.include) syscalls_in_json = json.dumps( syscalls, indent=4, sort_keys=True ) # Check if the file already exists, and if there are no changes, # don't touch it since that will force an incremental rebuild path = args.json_file new = syscalls_in_json if os.path.exists(path): with open(path, 'r') as fp: old = fp.read() if new != old: with open(path, 'w') as fp: fp.write(new) else: with open(path, 'w') as fp: fp.write(new) if __name__ == "__main__": main()