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