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
32# Some kernel headers cannot include automated tracing without causing unintended recursion or
33# other serious issues.
34# These headers typically already have very specific tracing hooks for all relevant things
35# written by hand so are excluded.
36notracing = ["kernel.h", "zephyr/kernel.h", "errno_private.h",
37             "zephyr/errno_private.h"]
38
39types64 = ["int64_t", "uint64_t"]
40
41# The kernel linkage is complicated.  These functions from
42# userspace_handlers.c are present in the kernel .a library after
43# userspace.c, which contains the weak fallbacks defined here.  So the
44# linker finds the weak one first and stops searching, and thus won't
45# see the real implementation which should override.  Yet changing the
46# order runs afoul of a comment in CMakeLists.txt that the order is
47# critical.  These are core syscalls that won't ever be unconfigured,
48# just disable the fallback mechanism as a simple workaround.
49noweak = ["z_mrsh_k_object_release",
50          "z_mrsh_k_object_access_grant",
51          "z_mrsh_k_object_alloc"]
52
53table_template = """/* auto-generated by gen_syscalls.py, don't edit */
54
55/* Weak handler functions that get replaced by the real ones unless a system
56 * call is not implemented due to kernel configuration.
57 */
58%s
59
60const _k_syscall_handler_t _k_syscall_table[K_SYSCALL_LIMIT] = {
61\t%s
62};
63"""
64
65list_template = """/* auto-generated by gen_syscalls.py, don't edit */
66
67#ifndef ZEPHYR_SYSCALL_LIST_H
68#define ZEPHYR_SYSCALL_LIST_H
69
70%s
71
72#ifndef _ASMLANGUAGE
73
74#include <stdarg.h>
75#include <stdint.h>
76
77#endif /* _ASMLANGUAGE */
78
79#endif /* ZEPHYR_SYSCALL_LIST_H */
80"""
81
82syscall_template = """/* auto-generated by gen_syscalls.py, don't edit */
83
84{include_guard}
85
86{tracing_include}
87
88#ifndef _ASMLANGUAGE
89
90#include <stdarg.h>
91
92#include <syscall_list.h>
93#include <zephyr/syscall.h>
94
95#include <zephyr/linker/sections.h>
96
97
98#ifdef __cplusplus
99extern "C" {{
100#endif
101
102{invocations}
103
104#ifdef __cplusplus
105}}
106#endif
107
108#endif
109#endif /* include guard */
110"""
111
112handler_template = """
113extern uintptr_t z_hdlr_%s(uintptr_t arg1, uintptr_t arg2, uintptr_t arg3,
114                uintptr_t arg4, uintptr_t arg5, uintptr_t arg6, void *ssf);
115"""
116
117weak_template = """
118__weak ALIAS_OF(handler_no_syscall)
119uintptr_t %s(uintptr_t arg1, uintptr_t arg2, uintptr_t arg3,
120         uintptr_t arg4, uintptr_t arg5, uintptr_t arg6, void *ssf);
121"""
122
123# defines a macro wrapper which supersedes the syscall when used
124# and provides tracing enter/exit hooks while allowing per compilation unit
125# enable/disable of syscall tracing. Used for returning functions
126# Note that the last argument to the exit macro is the return value.
127syscall_tracer_with_return_template = """
128#if defined(CONFIG_TRACING_SYSCALL)
129#ifndef DISABLE_SYSCALL_TRACING
130{trace_diagnostic}
131#define {func_name}({argnames}) ({{ \
132	{func_type} syscall__retval; \
133	sys_port_trace_syscall_enter({syscall_id}, {func_name}{trace_argnames}); \
134	syscall__retval = {func_name}({argnames}); \
135	sys_port_trace_syscall_exit({syscall_id}, {func_name}{trace_argnames}, syscall__retval); \
136	syscall__retval; \
137}})
138#endif
139#endif
140"""
141
142# defines a macro wrapper which supersedes the syscall when used
143# and provides tracing enter/exit hooks while allowing per compilation unit
144# enable/disable of syscall tracing. Used for non-returning (void) functions
145syscall_tracer_void_template = """
146#if defined(CONFIG_TRACING_SYSCALL)
147#ifndef DISABLE_SYSCALL_TRACING
148{trace_diagnostic}
149#define {func_name}({argnames}) do {{ \
150	sys_port_trace_syscall_enter({syscall_id}, {func_name}{trace_argnames}); \
151	{func_name}({argnames}); \
152	sys_port_trace_syscall_exit({syscall_id}, {func_name}{trace_argnames}); \
153}} while(false)
154#endif
155#endif
156"""
157
158typename_regex = re.compile(r'(.*?)([A-Za-z0-9_]+)$')
159
160
161class SyscallParseException(Exception):
162    pass
163
164
165def typename_split(item):
166    if "[" in item:
167        raise SyscallParseException(
168            "Please pass arrays to syscalls as pointers, unable to process '%s'" %
169            item)
170
171    if "(" in item:
172        raise SyscallParseException(
173            "Please use typedefs for function pointers")
174
175    mo = typename_regex.match(item)
176    if not mo:
177        raise SyscallParseException("Malformed system call invocation")
178
179    m = mo.groups()
180    return (m[0].strip(), m[1])
181
182def need_split(argtype):
183    return (not args.long_registers) and (argtype in types64)
184
185# Note: "lo" and "hi" are named in little endian conventions,
186# but it doesn't matter as long as they are consistently
187# generated.
188def union_decl(type, split):
189    middle = "struct { uintptr_t lo, hi; } split" if split else "uintptr_t x"
190    return "union { %s; %s val; }" % (middle, type)
191
192def wrapper_defs(func_name, func_type, args, fn):
193    ret64 = need_split(func_type)
194    mrsh_args = [] # List of rvalue expressions for the marshalled invocation
195
196    decl_arglist = ", ".join([" ".join(argrec) for argrec in args]) or "void"
197    syscall_id = "K_SYSCALL_" + func_name.upper()
198
199    wrap = "extern %s z_impl_%s(%s);\n" % (func_type, func_name, decl_arglist)
200    wrap += "\n"
201    wrap += "__pinned_func\n"
202    wrap += "static inline %s %s(%s)\n" % (func_type, func_name, decl_arglist)
203    wrap += "{\n"
204    wrap += "#ifdef CONFIG_USERSPACE\n"
205    wrap += ("\t" + "uint64_t ret64;\n") if ret64 else ""
206    wrap += "\t" + "if (z_syscall_trap()) {\n"
207
208    valist_args = []
209    for argnum, (argtype, argname) in enumerate(args):
210        split = need_split(argtype)
211        wrap += "\t\t%s parm%d" % (union_decl(argtype, split), argnum)
212        if argtype != "va_list":
213            wrap += " = { .val = %s };\n" % argname
214        else:
215            # va_list objects are ... peculiar.
216            wrap += ";\n" + "\t\t" + "va_copy(parm%d.val, %s);\n" % (argnum, argname)
217            valist_args.append("parm%d.val" % argnum)
218        if split:
219            mrsh_args.append("parm%d.split.lo" % argnum)
220            mrsh_args.append("parm%d.split.hi" % argnum)
221        else:
222            mrsh_args.append("parm%d.x" % argnum)
223
224    if ret64:
225        mrsh_args.append("(uintptr_t)&ret64")
226
227    if len(mrsh_args) > 6:
228        wrap += "\t\t" + "uintptr_t more[] = {\n"
229        wrap += "\t\t\t" + (",\n\t\t\t".join(mrsh_args[5:])) + "\n"
230        wrap += "\t\t" + "};\n"
231        mrsh_args[5:] = ["(uintptr_t) &more"]
232
233    invoke = ("arch_syscall_invoke%d(%s)"
234              % (len(mrsh_args),
235                 ", ".join(mrsh_args + [syscall_id])))
236
237    if ret64:
238        invoke = "\t\t" + "(void) %s;\n" % invoke
239        retcode = "\t\t" + "return (%s) ret64;\n" % func_type
240    elif func_type == "void":
241        invoke = "\t\t" + "(void) %s;\n" % invoke
242        retcode = "\t\t" + "return;\n"
243    elif valist_args:
244        invoke = "\t\t" + "%s invoke__retval = %s;\n" % (func_type, invoke)
245        retcode = "\t\t" + "return invoke__retval;\n"
246    else:
247        invoke = "\t\t" + "return (%s) %s;\n" % (func_type, invoke)
248        retcode = ""
249
250    wrap += invoke
251    for argname in valist_args:
252        wrap += "\t\t" + "va_end(%s);\n" % argname
253    wrap += retcode
254    wrap += "\t" + "}\n"
255    wrap += "#endif\n"
256
257    # Otherwise fall through to direct invocation of the impl func.
258    # Note the compiler barrier: that is required to prevent code from
259    # the impl call from being hoisted above the check for user
260    # context.
261    impl_arglist = ", ".join([argrec[1] for argrec in args])
262    impl_call = "z_impl_%s(%s)" % (func_name, impl_arglist)
263    wrap += "\t" + "compiler_barrier();\n"
264    wrap += "\t" + "%s%s;\n" % ("return " if func_type != "void" else "",
265                               impl_call)
266
267    wrap += "}\n"
268
269    if fn not in notracing:
270        argnames = ", ".join([f"{argname}" for _, argname in args])
271        trace_argnames = ""
272        if len(args) > 0:
273            trace_argnames = ", " + argnames
274        trace_diagnostic = ""
275        if os.getenv('TRACE_DIAGNOSTICS'):
276            trace_diagnostic = f"#warning Tracing {func_name}"
277        if func_type != "void":
278            wrap += syscall_tracer_with_return_template.format(func_type=func_type, func_name=func_name,
279                                                               argnames=argnames, trace_argnames=trace_argnames,
280                                                               syscall_id=syscall_id, trace_diagnostic=trace_diagnostic)
281        else:
282            wrap += syscall_tracer_void_template.format(func_type=func_type, func_name=func_name,
283                                                        argnames=argnames, trace_argnames=trace_argnames,
284                                                        syscall_id=syscall_id, trace_diagnostic=trace_diagnostic)
285
286    return wrap
287
288# Returns an expression for the specified (zero-indexed!) marshalled
289# parameter to a syscall, with handling for a final "more" parameter.
290def mrsh_rval(mrsh_num, total):
291    if mrsh_num < 5 or total <= 6:
292        return "arg%d" % mrsh_num
293    else:
294        return "(((uintptr_t *)more)[%d])" % (mrsh_num - 5)
295
296def marshall_defs(func_name, func_type, args):
297    mrsh_name = "z_mrsh_" + func_name
298
299    nmrsh = 0        # number of marshalled uintptr_t parameter
300    vrfy_parms = []  # list of (argtype, bool_is_split)
301    for (argtype, _) in args:
302        split = need_split(argtype)
303        vrfy_parms.append((argtype, split))
304        nmrsh += 2 if split else 1
305
306    # Final argument for a 64 bit return value?
307    if need_split(func_type):
308        nmrsh += 1
309
310    decl_arglist = ", ".join([" ".join(argrec) for argrec in args])
311    mrsh = "extern %s z_vrfy_%s(%s);\n" % (func_type, func_name, decl_arglist)
312
313    mrsh += "uintptr_t %s(uintptr_t arg0, uintptr_t arg1, uintptr_t arg2,\n" % mrsh_name
314    if nmrsh <= 6:
315        mrsh += "\t\t" + "uintptr_t arg3, uintptr_t arg4, uintptr_t arg5, void *ssf)\n"
316    else:
317        mrsh += "\t\t" + "uintptr_t arg3, uintptr_t arg4, void *more, void *ssf)\n"
318    mrsh += "{\n"
319    mrsh += "\t" + "_current->syscall_frame = ssf;\n"
320
321    for unused_arg in range(nmrsh, 6):
322        mrsh += "\t(void) arg%d;\t/* unused */\n" % unused_arg
323
324    if nmrsh > 6:
325        mrsh += ("\tK_OOPS(K_SYSCALL_MEMORY_READ(more, "
326                 + str(nmrsh - 5) + " * sizeof(uintptr_t)));\n")
327
328    argnum = 0
329    for i, (argtype, split) in enumerate(vrfy_parms):
330        mrsh += "\t%s parm%d;\n" % (union_decl(argtype, split), i)
331        if split:
332            mrsh += "\t" + "parm%d.split.lo = %s;\n" % (i, mrsh_rval(argnum, nmrsh))
333            argnum += 1
334            mrsh += "\t" + "parm%d.split.hi = %s;\n" % (i, mrsh_rval(argnum, nmrsh))
335        else:
336            mrsh += "\t" + "parm%d.x = %s;\n" % (i, mrsh_rval(argnum, nmrsh))
337        argnum += 1
338
339    # Finally, invoke the verify function
340    out_args = ", ".join(["parm%d.val" % i for i in range(len(args))])
341    vrfy_call = "z_vrfy_%s(%s)" % (func_name, out_args)
342
343    if func_type == "void":
344        mrsh += "\t" + "%s;\n" % vrfy_call
345        mrsh += "\t" + "_current->syscall_frame = NULL;\n"
346        mrsh += "\t" + "return 0;\n"
347    else:
348        mrsh += "\t" + "%s ret = %s;\n" % (func_type, vrfy_call)
349
350        if need_split(func_type):
351            ptr = "((uint64_t *)%s)" % mrsh_rval(nmrsh - 1, nmrsh)
352            mrsh += "\t" + "K_OOPS(K_SYSCALL_MEMORY_WRITE(%s, 8));\n" % ptr
353            mrsh += "\t" + "*%s = ret;\n" % ptr
354            mrsh += "\t" + "_current->syscall_frame = NULL;\n"
355            mrsh += "\t" + "return 0;\n"
356        else:
357            mrsh += "\t" + "_current->syscall_frame = NULL;\n"
358            mrsh += "\t" + "return (uintptr_t) ret;\n"
359
360    mrsh += "}\n"
361
362    return mrsh, mrsh_name
363
364def analyze_fn(match_group, fn):
365    func, args = match_group
366
367    try:
368        if args == "void":
369            args = []
370        else:
371            args = [typename_split(a.strip()) for a in args.split(",")]
372
373        func_type, func_name = typename_split(func)
374    except SyscallParseException:
375        sys.stderr.write("In declaration of %s\n" % func)
376        raise
377
378    sys_id = "K_SYSCALL_" + func_name.upper()
379
380    marshaller = None
381    marshaller, handler = marshall_defs(func_name, func_type, args)
382    invocation = wrapper_defs(func_name, func_type, args, fn)
383
384    # Entry in _k_syscall_table
385    table_entry = "[%s] = %s" % (sys_id, handler)
386
387    return (handler, invocation, marshaller, sys_id, table_entry)
388
389def parse_args():
390    global args
391    parser = argparse.ArgumentParser(
392        description=__doc__,
393        formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False)
394
395    parser.add_argument("-i", "--json-file", required=True,
396                        help="Read syscall information from json file")
397    parser.add_argument("-d", "--syscall-dispatch", required=True,
398                        help="output C system call dispatch table file")
399    parser.add_argument("-l", "--syscall-list", required=True,
400                        help="output C system call list header")
401    parser.add_argument("-o", "--base-output", required=True,
402                        help="Base output directory for syscall macro headers")
403    parser.add_argument("-s", "--split-type", action="append",
404                        help="A long type that must be split/marshalled on 32-bit systems")
405    parser.add_argument("-x", "--long-registers", action="store_true",
406                        help="Indicates we are on system with 64-bit registers")
407    parser.add_argument("--gen-mrsh-files", action="store_true",
408                        help="Generate marshalling files (*_mrsh.c)")
409    args = parser.parse_args()
410
411
412def main():
413    parse_args()
414
415    if args.split_type is not None:
416        for t in args.split_type:
417            types64.append(t)
418
419    with open(args.json_file, 'r') as fd:
420        syscalls = json.load(fd)
421
422    invocations = {}
423    mrsh_defs = {}
424    mrsh_includes = {}
425    ids_emit = []
426    ids_not_emit = []
427    table_entries = []
428    handlers = []
429    emit_list = []
430
431    for match_group, fn, to_emit in syscalls:
432        handler, inv, mrsh, sys_id, entry = analyze_fn(match_group, fn)
433
434        if fn not in invocations:
435            invocations[fn] = []
436
437        invocations[fn].append(inv)
438        handlers.append(handler)
439
440        if to_emit:
441            ids_emit.append(sys_id)
442            table_entries.append(entry)
443            emit_list.append(handler)
444        else:
445            ids_not_emit.append(sys_id)
446
447        if mrsh and to_emit:
448            syscall = typename_split(match_group[0])[1]
449            mrsh_defs[syscall] = mrsh
450            mrsh_includes[syscall] = "#include <syscalls/%s>" % fn
451
452    with open(args.syscall_dispatch, "w") as fp:
453        table_entries.append("[K_SYSCALL_BAD] = handler_bad_syscall")
454
455        weak_defines = "".join([weak_template % name
456                                for name in handlers
457                                if not name in noweak and name in emit_list])
458
459        # The "noweak" ones just get a regular declaration
460        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);"
461                                   % s for s in noweak])
462
463        fp.write(table_template % (weak_defines,
464                                   ",\n\t".join(table_entries)))
465
466    # Listing header emitted to stdout
467    ids_emit.sort()
468    ids_emit.extend(["K_SYSCALL_BAD", "K_SYSCALL_LIMIT"])
469
470    ids_as_defines = ""
471    for i, item in enumerate(ids_emit):
472        ids_as_defines += "#define {} {}\n".format(item, i)
473
474    if ids_not_emit:
475        # There are syscalls that are not used in the image but
476        # their IDs are used in the generated stubs. So need to
477        # make them usable but outside the syscall ID range.
478        ids_as_defines += "\n\n/* Following syscalls are not used in image */\n"
479        ids_not_emit.sort()
480        num_emitted_ids = len(ids_emit)
481        for i, item in enumerate(ids_not_emit):
482            ids_as_defines += "#define {} {}\n".format(item, i + num_emitted_ids)
483
484    with open(args.syscall_list, "w") as fp:
485        fp.write(list_template % ids_as_defines)
486
487    os.makedirs(args.base_output, exist_ok=True)
488    for fn, invo_list in invocations.items():
489        out_fn = os.path.join(args.base_output, fn)
490
491        ig = re.sub("[^a-zA-Z0-9]", "_", "Z_INCLUDE_SYSCALLS_" + fn).upper()
492        include_guard = "#ifndef %s\n#define %s\n" % (ig, ig)
493        tracing_include = ""
494        if fn not in notracing:
495            tracing_include = "#include <zephyr/tracing/tracing_syscall.h>"
496        header = syscall_template.format(include_guard=include_guard, tracing_include=tracing_include, invocations="\n\n".join(invo_list))
497
498        with open(out_fn, "w") as fp:
499            fp.write(header)
500
501    # Likewise emit _mrsh.c files for syscall inclusion
502    if args.gen_mrsh_files:
503        for fn in mrsh_defs:
504            mrsh_fn = os.path.join(args.base_output, fn + "_mrsh.c")
505
506            with open(mrsh_fn, "w") as fp:
507                fp.write("/* auto-generated by gen_syscalls.py, don't edit */\n\n")
508                fp.write(mrsh_includes[fn] + "\n")
509                fp.write("\n")
510                fp.write(mrsh_defs[fn] + "\n")
511
512if __name__ == "__main__":
513    main()
514