1#!/usr/bin/env python3
2#
3# Copyright (c) 2017 Intel Corporation
4#
5# SPDX-License-Identifier: Apache-2.0
6
7"""
8Script to generate system call invocation macros
9
10This script parses the system call metadata JSON file emitted by
11parse_syscalls.py to create several files:
12
13- A file containing weak aliases of any potentially unimplemented system calls,
14  as well as the system call dispatch table, which maps system call type IDs
15  to their handler functions.
16
17- A header file defining the system call type IDs, as well as function
18  prototypes for all system call handler functions.
19
20- A directory containing header files. Each header corresponds to a header
21  that was identified as containing system call declarations. These
22  generated headers contain the inline invocation functions for each system
23  call in that header.
24"""
25
26import sys
27import re
28import argparse
29import os
30import json
31
32types64 = ["int64_t", "uint64_t"]
33
34# The kernel linkage is complicated.  These functions from
35# userspace_handlers.c are present in the kernel .a library after
36# userspace.c, which contains the weak fallbacks defined here.  So the
37# linker finds the weak one first and stops searching, and thus won't
38# see the real implementation which should override.  Yet changing the
39# order runs afoul of a comment in CMakeLists.txt that the order is
40# critical.  These are core syscalls that won't ever be unconfigured,
41# just disable the fallback mechanism as a simple workaround.
42noweak = ["z_mrsh_k_object_release",
43          "z_mrsh_k_object_access_grant",
44          "z_mrsh_k_object_alloc"]
45
46table_template = """/* auto-generated by gen_syscalls.py, don't edit */
47
48/* Weak handler functions that get replaced by the real ones unless a system
49 * call is not implemented due to kernel configuration.
50 */
51%s
52
53const _k_syscall_handler_t _k_syscall_table[K_SYSCALL_LIMIT] = {
54\t%s
55};
56"""
57
58list_template = """/* auto-generated by gen_syscalls.py, don't edit */
59
60#ifndef ZEPHYR_SYSCALL_LIST_H
61#define ZEPHYR_SYSCALL_LIST_H
62
63%s
64
65#ifndef _ASMLANGUAGE
66
67#include <stdint.h>
68
69#endif /* _ASMLANGUAGE */
70
71#endif /* ZEPHYR_SYSCALL_LIST_H */
72"""
73
74syscall_template = """
75/* auto-generated by gen_syscalls.py, don't edit */
76%s
77
78#ifndef _ASMLANGUAGE
79
80#include <syscall_list.h>
81#include <syscall.h>
82
83#include <linker/sections.h>
84
85#ifdef __cplusplus
86extern "C" {
87#endif
88
89%s
90
91#ifdef __cplusplus
92}
93#endif
94
95#endif
96#endif /* include guard */
97"""
98
99handler_template = """
100extern uintptr_t z_hdlr_%s(uintptr_t arg1, uintptr_t arg2, uintptr_t arg3,
101                uintptr_t arg4, uintptr_t arg5, uintptr_t arg6, void *ssf);
102"""
103
104weak_template = """
105__weak ALIAS_OF(handler_no_syscall)
106uintptr_t %s(uintptr_t arg1, uintptr_t arg2, uintptr_t arg3,
107         uintptr_t arg4, uintptr_t arg5, uintptr_t arg6, void *ssf);
108"""
109
110
111typename_regex = re.compile(r'(.*?)([A-Za-z0-9_]+)$')
112
113
114class SyscallParseException(Exception):
115    pass
116
117
118def typename_split(item):
119    if "[" in item:
120        raise SyscallParseException(
121            "Please pass arrays to syscalls as pointers, unable to process '%s'" %
122            item)
123
124    if "(" in item:
125        raise SyscallParseException(
126            "Please use typedefs for function pointers")
127
128    mo = typename_regex.match(item)
129    if not mo:
130        raise SyscallParseException("Malformed system call invocation")
131
132    m = mo.groups()
133    return (m[0].strip(), m[1])
134
135def need_split(argtype):
136    return (not args.long_registers) and (argtype in types64)
137
138# Note: "lo" and "hi" are named in little endian conventions,
139# but it doesn't matter as long as they are consistently
140# generated.
141def union_decl(type, split):
142    middle = "struct { uintptr_t lo, hi; } split" if split else "uintptr_t x"
143    return "union { %s; %s val; }" % (middle, type)
144
145def wrapper_defs(func_name, func_type, args):
146    ret64 = need_split(func_type)
147    mrsh_args = [] # List of rvalue expressions for the marshalled invocation
148
149    decl_arglist = ", ".join([" ".join(argrec) for argrec in args]) or "void"
150
151    wrap = "extern %s z_impl_%s(%s);\n" % (func_type, func_name, decl_arglist)
152    wrap += "\n"
153    wrap += "__pinned_func\n"
154    wrap += "static inline %s %s(%s)\n" % (func_type, func_name, decl_arglist)
155    wrap += "{\n"
156    wrap += "#ifdef CONFIG_USERSPACE\n"
157    wrap += ("\t" + "uint64_t ret64;\n") if ret64 else ""
158    wrap += "\t" + "if (z_syscall_trap()) {\n"
159
160    valist_args = []
161    for argnum, (argtype, argname) in enumerate(args):
162        split = need_split(argtype)
163        wrap += "\t\t%s parm%d" % (union_decl(argtype, split), argnum)
164        if argtype != "va_list":
165            wrap += " = { .val = %s };\n" % argname
166        else:
167            # va_list objects are ... peculiar.
168            wrap += ";\n" + "\t\t" + "va_copy(parm%d.val, %s);\n" % (argnum, argname)
169            valist_args.append("parm%d.val" % argnum)
170        if split:
171            mrsh_args.append("parm%d.split.lo" % argnum)
172            mrsh_args.append("parm%d.split.hi" % argnum)
173        else:
174            mrsh_args.append("parm%d.x" % argnum)
175
176    if ret64:
177        mrsh_args.append("(uintptr_t)&ret64")
178
179    if len(mrsh_args) > 6:
180        wrap += "\t\t" + "uintptr_t more[] = {\n"
181        wrap += "\t\t\t" + (",\n\t\t\t".join(mrsh_args[5:])) + "\n"
182        wrap += "\t\t" + "};\n"
183        mrsh_args[5:] = ["(uintptr_t) &more"]
184
185    syscall_id = "K_SYSCALL_" + func_name.upper()
186    invoke = ("arch_syscall_invoke%d(%s)"
187              % (len(mrsh_args),
188                 ", ".join(mrsh_args + [syscall_id])))
189
190    if ret64:
191        invoke = "\t\t" + "(void) %s;\n" % invoke
192        retcode = "\t\t" + "return (%s) ret64;\n" % func_type
193    elif func_type == "void":
194        invoke = "\t\t" + "(void) %s;\n" % invoke
195        retcode = "\t\t" + "return;\n"
196    elif valist_args:
197        invoke = "\t\t" + "%s retval = %s;\n" % (func_type, invoke)
198        retcode = "\t\t" + "return retval;\n"
199    else:
200        invoke = "\t\t" + "return (%s) %s;\n" % (func_type, invoke)
201        retcode = ""
202
203    wrap += invoke
204    for argname in valist_args:
205        wrap += "\t\t" + "va_end(%s);\n" % argname
206    wrap += retcode
207    wrap += "\t" + "}\n"
208    wrap += "#endif\n"
209
210    # Otherwise fall through to direct invocation of the impl func.
211    # Note the compiler barrier: that is required to prevent code from
212    # the impl call from being hoisted above the check for user
213    # context.
214    impl_arglist = ", ".join([argrec[1] for argrec in args])
215    impl_call = "z_impl_%s(%s)" % (func_name, impl_arglist)
216    wrap += "\t" + "compiler_barrier();\n"
217    wrap += "\t" + "%s%s;\n" % ("return " if func_type != "void" else "",
218                               impl_call)
219
220    wrap += "}\n"
221
222    return wrap
223
224# Returns an expression for the specified (zero-indexed!) marshalled
225# parameter to a syscall, with handling for a final "more" parameter.
226def mrsh_rval(mrsh_num, total):
227    if mrsh_num < 5 or total <= 6:
228        return "arg%d" % mrsh_num
229    else:
230        return "(((uintptr_t *)more)[%d])" % (mrsh_num - 5)
231
232def marshall_defs(func_name, func_type, args):
233    mrsh_name = "z_mrsh_" + func_name
234
235    nmrsh = 0        # number of marshalled uintptr_t parameter
236    vrfy_parms = []  # list of (argtype, bool_is_split)
237    for (argtype, _) in args:
238        split = need_split(argtype)
239        vrfy_parms.append((argtype, split))
240        nmrsh += 2 if split else 1
241
242    # Final argument for a 64 bit return value?
243    if need_split(func_type):
244        nmrsh += 1
245
246    decl_arglist = ", ".join([" ".join(argrec) for argrec in args])
247    mrsh = "extern %s z_vrfy_%s(%s);\n" % (func_type, func_name, decl_arglist)
248
249    mrsh += "uintptr_t %s(uintptr_t arg0, uintptr_t arg1, uintptr_t arg2,\n" % mrsh_name
250    if nmrsh <= 6:
251        mrsh += "\t\t" + "uintptr_t arg3, uintptr_t arg4, uintptr_t arg5, void *ssf)\n"
252    else:
253        mrsh += "\t\t" + "uintptr_t arg3, uintptr_t arg4, void *more, void *ssf)\n"
254    mrsh += "{\n"
255    mrsh += "\t" + "_current->syscall_frame = ssf;\n"
256
257    for unused_arg in range(nmrsh, 6):
258        mrsh += "\t(void) arg%d;\t/* unused */\n" % unused_arg
259
260    if nmrsh > 6:
261        mrsh += ("\tZ_OOPS(Z_SYSCALL_MEMORY_READ(more, "
262                 + str(nmrsh - 5) + " * sizeof(uintptr_t)));\n")
263
264    argnum = 0
265    for i, (argtype, split) in enumerate(vrfy_parms):
266        mrsh += "\t%s parm%d;\n" % (union_decl(argtype, split), i)
267        if split:
268            mrsh += "\t" + "parm%d.split.lo = %s;\n" % (i, mrsh_rval(argnum, nmrsh))
269            argnum += 1
270            mrsh += "\t" + "parm%d.split.hi = %s;\n" % (i, mrsh_rval(argnum, nmrsh))
271        else:
272            mrsh += "\t" + "parm%d.x = %s;\n" % (i, mrsh_rval(argnum, nmrsh))
273        argnum += 1
274
275    # Finally, invoke the verify function
276    out_args = ", ".join(["parm%d.val" % i for i in range(len(args))])
277    vrfy_call = "z_vrfy_%s(%s)" % (func_name, out_args)
278
279    if func_type == "void":
280        mrsh += "\t" + "%s;\n" % vrfy_call
281        mrsh += "\t" + "_current->syscall_frame = NULL;\n"
282        mrsh += "\t" + "return 0;\n"
283    else:
284        mrsh += "\t" + "%s ret = %s;\n" % (func_type, vrfy_call)
285
286        if need_split(func_type):
287            ptr = "((uint64_t *)%s)" % mrsh_rval(nmrsh - 1, nmrsh)
288            mrsh += "\t" + "Z_OOPS(Z_SYSCALL_MEMORY_WRITE(%s, 8));\n" % ptr
289            mrsh += "\t" + "*%s = ret;\n" % ptr
290            mrsh += "\t" + "_current->syscall_frame = NULL;\n"
291            mrsh += "\t" + "return 0;\n"
292        else:
293            mrsh += "\t" + "_current->syscall_frame = NULL;\n"
294            mrsh += "\t" + "return (uintptr_t) ret;\n"
295
296    mrsh += "}\n"
297
298    return mrsh, mrsh_name
299
300def analyze_fn(match_group):
301    func, args = match_group
302
303    try:
304        if args == "void":
305            args = []
306        else:
307            args = [typename_split(a.strip()) for a in args.split(",")]
308
309        func_type, func_name = typename_split(func)
310    except SyscallParseException:
311        sys.stderr.write("In declaration of %s\n" % func)
312        raise
313
314    sys_id = "K_SYSCALL_" + func_name.upper()
315
316    marshaller = None
317    marshaller, handler = marshall_defs(func_name, func_type, args)
318    invocation = wrapper_defs(func_name, func_type, args)
319
320    # Entry in _k_syscall_table
321    table_entry = "[%s] = %s" % (sys_id, handler)
322
323    return (handler, invocation, marshaller, sys_id, table_entry)
324
325def parse_args():
326    global args
327    parser = argparse.ArgumentParser(
328        description=__doc__,
329        formatter_class=argparse.RawDescriptionHelpFormatter)
330
331    parser.add_argument("-i", "--json-file", required=True,
332                        help="Read syscall information from json file")
333    parser.add_argument("-d", "--syscall-dispatch", required=True,
334                        help="output C system call dispatch table file")
335    parser.add_argument("-l", "--syscall-list", required=True,
336                        help="output C system call list header")
337    parser.add_argument("-o", "--base-output", required=True,
338                        help="Base output directory for syscall macro headers")
339    parser.add_argument("-s", "--split-type", action="append",
340                        help="A long type that must be split/marshalled on 32-bit systems")
341    parser.add_argument("-x", "--long-registers", action="store_true",
342                        help="Indicates we are on system with 64-bit registers")
343    args = parser.parse_args()
344
345
346def main():
347    parse_args()
348
349    if args.split_type is not None:
350        for t in args.split_type:
351            types64.append(t)
352
353    with open(args.json_file, 'r') as fd:
354        syscalls = json.load(fd)
355
356    invocations = {}
357    mrsh_defs = {}
358    mrsh_includes = {}
359    ids = []
360    table_entries = []
361    handlers = []
362
363    for match_group, fn in syscalls:
364        handler, inv, mrsh, sys_id, entry = analyze_fn(match_group)
365
366        if fn not in invocations:
367            invocations[fn] = []
368
369        invocations[fn].append(inv)
370        ids.append(sys_id)
371        table_entries.append(entry)
372        handlers.append(handler)
373
374        if mrsh:
375            syscall = typename_split(match_group[0])[1]
376            mrsh_defs[syscall] = mrsh
377            mrsh_includes[syscall] = "#include <syscalls/%s>" % fn
378
379    with open(args.syscall_dispatch, "w") as fp:
380        table_entries.append("[K_SYSCALL_BAD] = handler_bad_syscall")
381
382        weak_defines = "".join([weak_template % name
383                                for name in handlers
384                                if not name in noweak])
385
386        # The "noweak" ones just get a regular declaration
387        weak_defines += "\n".join(["extern uintptr_t %s(uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4, uintptr_t arg5, uintptr_t arg6, void *ssf);"
388                                   % s for s in noweak])
389
390        fp.write(table_template % (weak_defines,
391                                   ",\n\t".join(table_entries)))
392
393    # Listing header emitted to stdout
394    ids.sort()
395    ids.extend(["K_SYSCALL_BAD", "K_SYSCALL_LIMIT"])
396
397    ids_as_defines = ""
398    for i, item in enumerate(ids):
399        ids_as_defines += "#define {} {}\n".format(item, i)
400
401    with open(args.syscall_list, "w") as fp:
402        fp.write(list_template % ids_as_defines)
403
404    os.makedirs(args.base_output, exist_ok=True)
405    for fn, invo_list in invocations.items():
406        out_fn = os.path.join(args.base_output, fn)
407
408        ig = re.sub("[^a-zA-Z0-9]", "_", "Z_INCLUDE_SYSCALLS_" + fn).upper()
409        include_guard = "#ifndef %s\n#define %s\n" % (ig, ig)
410        header = syscall_template % (include_guard, "\n\n".join(invo_list))
411
412        with open(out_fn, "w") as fp:
413            fp.write(header)
414
415    # Likewise emit _mrsh.c files for syscall inclusion
416    for fn in mrsh_defs:
417        mrsh_fn = os.path.join(args.base_output, fn + "_mrsh.c")
418
419        with open(mrsh_fn, "w") as fp:
420            fp.write("/* auto-generated by gen_syscalls.py, don't edit */\n\n")
421            fp.write(mrsh_includes[fn] + "\n")
422            fp.write("\n")
423            fp.write(mrsh_defs[fn] + "\n")
424
425if __name__ == "__main__":
426    main()
427