1#!/usr/bin/env python3 2# 3# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230) 4# 5# SPDX-License-Identifier: BSD-2-Clause 6# 7 8""" 9Script to generate a c header file containing function prototypes and 10doxygen comments from a given interface defined in an xml file. 11""" 12 13import os 14import sys 15import argparse 16import operator 17import logging 18import itertools 19from libsel4_tools import syscall_stub_gen 20from lxml import etree 21 22# Word size is required by the syscall_stub_gen library, but won't affect the output 23WORD_SIZE = 32 24FN_DECL_PREFIX = "static inline" 25DEFAULT_RETURN_TYPE = "int" 26 27 28def init_all_types(): 29 """ 30 Return an array of all c types involved in the sel4 interface 31 """ 32 33 data_types = syscall_stub_gen.init_data_types(WORD_SIZE) 34 arch_types = list(itertools.chain(*syscall_stub_gen.init_arch_types(WORD_SIZE).values())) 35 36 return data_types + arch_types 37 38 39def generate_prototype(interface_name, method_name, method_id, inputs, outputs, comment): 40 """ 41 Returns a string containing a commented function prototype based on its arguments 42 """ 43 44 prefix = FN_DECL_PREFIX 45 if syscall_stub_gen.generate_result_struct(interface_name, method_name, outputs): 46 return_type = "%s_%s_t" % (interface_name, method_name) 47 else: 48 return_type = DEFAULT_RETURN_TYPE 49 50 param_list = syscall_stub_gen.generate_param_list(inputs, outputs) 51 name = "%s_%s" % (interface_name, method_name) 52 53 return "%s\n%s %s %s(%s);" % (comment, prefix, return_type, name, param_list) 54 55 56def gen_invocations(input_files, output_file): 57 """ 58 Given a collection of input xml files describing sel4 interfaces, 59 generates a c header file containing doxygen-commented function 60 prototypes. 61 """ 62 63 types = init_all_types() 64 65 for input_file in input_files: 66 methods, _, api = syscall_stub_gen.parse_xml_file(input_file, types) 67 prototypes = [] 68 69 # figure out the prefix to use for an interface group id. This makes groups per arch, 70 # sel4_arch unique even through the interface name is the same. 71 prefix = None 72 if "arch_include" in input_file: 73 # extract the 2nd last path member 74 (path, tail) = os.path.split(os.path.dirname(input_file)) 75 assert tail == "interfaces" 76 (path, prefix) = os.path.split(path) 77 78 # group the methods in each interface 79 for interface_name, methods in itertools.groupby(methods, lambda x: x[0]): 80 group_id = interface_name if prefix is None else prefix + '_' + interface_name 81 group_name = interface_name 82 output_file.write("/**\n * @defgroup %s %s\n * @{\n */\n\n" % (group_id, group_name)) 83 output_file.write("/** @} */\n") 84 for (interface_name, method_name, method_id, inputs, outputs, _, comment) in methods: 85 prototype = "/**\n * @addtogroup %s %s\n * @{\n */\n\n" % (group_id, group_name) 86 prototype += generate_prototype(interface_name, 87 method_name, method_id, inputs, outputs, comment) 88 prototype += "/** @} */\n" 89 prototypes.append(prototype) 90 91 prototypes.sort() 92 93 output_file.write("/**\n * @defgroup %s %s\n * @{\n */\n\n" % (api.name, api.name)) 94 95 for prototype in prototypes: 96 output_file.write(prototype) 97 output_file.write("\n\n") 98 99 output_file.write("/** @} */\n") 100 101 102def process_args(): 103 usage_str = "%(prog)s [OPTIONS] [FILES]" 104 105 parser = argparse.ArgumentParser(description='Generates doxygen-annotated header ' 106 'containing object invocation prototypes', 107 usage=usage_str) 108 109 parser.add_argument("-o", "--output", dest="output", default="/dev/stdout", 110 type=str, 111 help="Output file to write stub to. (default: %(default)s).") 112 parser.add_argument("files", metavar="FILES", nargs="+", type=str, 113 help="Input XML files.") 114 115 parser.add_argument("-d", "--dtd", nargs="?", type=str, 116 help="DTD xml schema to validate input files against") 117 118 return parser 119 120 121def main(): 122 parser = process_args() 123 args = parser.parse_args() 124 125 if args.dtd is not None: 126 dtd = etree.DTD(args.dtd) 127 for f in args.files: 128 xml = etree.parse(f) 129 if not dtd.validate(xml): 130 logging.error("Failed to validate %s against %s" % (f, args.dtd)) 131 logging.error(dtd.error_log) 132 return -1 133 134 if not os.path.exists(os.path.dirname(args.output)): 135 os.makedirs(os.path.dirname(args.output)) 136 137 with open(args.output, "w") as output: 138 gen_invocations(args.files, output) 139 140 141if __name__ == "__main__": 142 sys.exit(main()) 143