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