syscalls: greatly simplify system call declaration

To define a system call, it's now sufficient to simply tag the inline
prototype with "__syscall" or "__syscall_inline" and include a special
generated header at the end of the header file.

The system call dispatch table and enumeration of system call IDs is now
automatically generated.

Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
diff --git a/scripts/gen_syscalls.py b/scripts/gen_syscalls.py
new file mode 100755
index 0000000..8a35dbf
--- /dev/null
+++ b/scripts/gen_syscalls.py
@@ -0,0 +1,238 @@
+#!/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|syscall_inline)\s+   # __syscall or __syscall_inline
+static\s+inline\s+              # All prototypes are static inline functions
+([^(]+)                         # 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")
+
+    m = typename_regex.match(item).groups()
+    return (m[0].strip(), m[1])
+
+
+def analyze_fn(match_group, fn):
+    variant, 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()
+    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%s" % (len(args),
+            "_VOID" if is_void else "",
+            "_INLINE" if variant == "syscall_inline" else "")
+
+    # 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:
+            if not fn.endswith(".h"):
+                continue
+
+            with open(os.path.join(root, fn)) 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 unles 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()
+