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