forked from Imagelibrary/seL4
This way common error codes can be in their own file. To use, add the xmlns:xi="http://www.w3.org/2001/XInclude attribute to the top level node and use: <xi:include href="file-to-include.xml"/> The content of the file will be included verbatim. Include files must be complete XML documents, but without <?xml version="1.0" ?> at the top. Practically this means all nodes within the file need to be contained in one root element. Caveat: There is no proper dependency for Xincludes files in the Makefile, so you need to do make clean after changing an included XML file! Signed-off-by: Indan Zupancic <indan@nul.nu>
281 lines
9.1 KiB
Python
Executable File
281 lines
9.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
|
|
#
|
|
# SPDX-License-Identifier: BSD-2-Clause
|
|
#
|
|
|
|
"""
|
|
Script to generate a c header file containing function prototypes and
|
|
doxygen comments from a given interface defined in an xml file.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import argparse
|
|
import logging
|
|
import itertools
|
|
from pathlib import Path
|
|
from libsel4_tools import syscall_stub_gen
|
|
from lxml import etree
|
|
|
|
# Word size is required by the syscall_stub_gen library, but won't affect the output
|
|
WORD_SIZE = 32
|
|
FN_DECL_PREFIX = "static inline"
|
|
DEFAULT_RETURN_TYPE = "int"
|
|
|
|
|
|
def init_all_types(args):
|
|
"""
|
|
Return an array of all c types involved in the seL4 interface
|
|
"""
|
|
|
|
data_types = syscall_stub_gen.init_data_types(WORD_SIZE)
|
|
arch_types = list(itertools.chain(*syscall_stub_gen.init_arch_types(WORD_SIZE, args).values()))
|
|
|
|
return data_types + arch_types
|
|
|
|
|
|
def generate_prototype(interface_name, method_name, method_id, inputs, outputs, comment):
|
|
"""
|
|
Returns a string containing a commented function prototype based on its arguments
|
|
"""
|
|
|
|
prefix = FN_DECL_PREFIX
|
|
if syscall_stub_gen.generate_result_struct(interface_name, method_name, outputs):
|
|
return_type = "%s_%s_t" % (interface_name, method_name)
|
|
else:
|
|
return_type = DEFAULT_RETURN_TYPE
|
|
|
|
param_list = syscall_stub_gen.generate_param_list(inputs, outputs)
|
|
name = "%s_%s" % (interface_name, method_name)
|
|
|
|
return "%s\n%s %s %s(%s);" % (comment, prefix, return_type, name, param_list)
|
|
|
|
|
|
def eval_condition(condition, values):
|
|
"""
|
|
Evaluates a method condition string to True or False. Empty values and
|
|
expressions evaluate to False. Raises an exception if the parse failed.
|
|
Values dict must map to strings "True", "False", or to None.
|
|
|
|
Accepted grammar:
|
|
condition ::= term ("&&" term)*
|
|
term ::= "defined" "(" identifier ")" | "!" term | "(" condition ")"
|
|
"""
|
|
|
|
pos = 0
|
|
|
|
def accept(string):
|
|
nonlocal pos
|
|
# if we overflow len(condition), the equality will fail
|
|
if condition[pos:pos+len(string)] == string:
|
|
pos += len(string)
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def skip_whitespace():
|
|
nonlocal pos
|
|
while pos < len(condition) and condition[pos].isspace():
|
|
pos += 1
|
|
|
|
def parse_defined():
|
|
nonlocal pos
|
|
skip_whitespace()
|
|
if not accept("defined("):
|
|
return None
|
|
skip_whitespace()
|
|
if not condition[pos].isalpha():
|
|
return None
|
|
start = pos
|
|
while pos < len(condition) and (condition[pos].isalnum() or condition[pos] == "_"):
|
|
pos += 1
|
|
end = pos
|
|
if not accept(")"):
|
|
return None
|
|
return values.get(condition[start:end], "False")
|
|
|
|
def parse_not():
|
|
nonlocal pos
|
|
skip_whitespace()
|
|
if not accept("!"):
|
|
return None
|
|
term = parse_term()
|
|
if term == "True":
|
|
return "False"
|
|
elif term == "False":
|
|
return "True"
|
|
else:
|
|
return None
|
|
|
|
def parse_paren():
|
|
nonlocal pos
|
|
skip_whitespace()
|
|
if not accept("("):
|
|
return None
|
|
result = parse_condition()
|
|
if not accept(")"):
|
|
return None
|
|
return result
|
|
|
|
def parse_term():
|
|
return parse_defined() or parse_not() or parse_paren()
|
|
|
|
def parse_condition():
|
|
nonlocal pos
|
|
skip_whitespace()
|
|
term = parse_term()
|
|
if not term:
|
|
return None
|
|
skip_whitespace()
|
|
while accept("&&"):
|
|
next_term = parse_term()
|
|
if not next_term:
|
|
return None
|
|
skip_whitespace()
|
|
if next_term == "False":
|
|
term = "False"
|
|
return term
|
|
|
|
if condition == '':
|
|
return False
|
|
cond = parse_condition()
|
|
skip_whitespace()
|
|
if not cond or pos != len(condition):
|
|
raise Exception(f"Failed to parse condition '{condition}'")
|
|
if cond == "True":
|
|
return True
|
|
if cond == "False":
|
|
return False
|
|
raise Exception(f"Unexpected value {cond} for condition '{condition}'")
|
|
|
|
|
|
def is_mcs(method_condition):
|
|
"""
|
|
Returns whether the condition evaluates to true when CONFIG_KERNEL_MCS is set.
|
|
"""
|
|
return eval_condition(method_condition, {"CONFIG_KERNEL_MCS": "True"})
|
|
|
|
|
|
def gen_invocations(input_files, output_dir, args):
|
|
"""
|
|
Given a collection of input xml files describing seL4 interfaces,
|
|
generates a c header file containing doxygen-commented function
|
|
prototypes.
|
|
"""
|
|
|
|
types = init_all_types(args)
|
|
|
|
group_file_name = output_dir / "group_defs.h"
|
|
if os.path.exists(group_file_name):
|
|
os.remove(group_file_name)
|
|
|
|
for input_file in input_files:
|
|
methods, _, api = syscall_stub_gen.parse_xml_file(input_file, types)
|
|
|
|
# dict mapping group id to list of prototypes
|
|
prototypes = {}
|
|
|
|
# dict mapping group id to group name
|
|
groups = {}
|
|
|
|
# figure out the prefix to use for an interface group id. This makes groups per arch,
|
|
# sel4_arch unique even through the interface name is the same.
|
|
prefix = None
|
|
if "arch_include" in input_file:
|
|
# extract the 2nd last path member
|
|
(path, tail) = os.path.split(os.path.dirname(input_file))
|
|
assert tail == "interfaces"
|
|
(path, prefix) = os.path.split(path)
|
|
|
|
# group the methods in each interface
|
|
for interface_name, methods in itertools.groupby(methods, lambda x: x[0]):
|
|
group_id = interface_name if prefix is None else prefix + '_' + interface_name
|
|
group_id_mcs = group_id + "_mcs"
|
|
group_name = interface_name
|
|
|
|
if group_id in groups:
|
|
if group_name != groups[group_id]:
|
|
raise Exception(f"Group {group_id} has inconsistent names: {group_name} "
|
|
f"!= {groups[group_id]}")
|
|
else:
|
|
groups[group_id] = group_name
|
|
groups[group_id_mcs] = group_name + " (MCS)"
|
|
|
|
for (interface_name, method_name, method_id, inputs, outputs, condition,
|
|
comment) in methods:
|
|
g_id = group_id_mcs if is_mcs(condition) else group_id
|
|
prototype = "/**\n * @addtogroup %s\n * @{\n */\n\n" % g_id
|
|
prototype += generate_prototype(interface_name, method_name, method_id, inputs,
|
|
outputs, comment)
|
|
prototype += "\n/** @} */\n"
|
|
if g_id not in prototypes:
|
|
prototypes[g_id] = [prototype]
|
|
else:
|
|
prototypes[g_id].append(prototype)
|
|
|
|
with open(group_file_name, "a") as groups_file:
|
|
groups_file.write("/**\n * @defgroup %s %s\n * @{\n */\n\n" % (api.name, api.name))
|
|
for group_id, group_name in sorted(groups.items()):
|
|
if group_id in prototypes:
|
|
groups_file.write("/**\n * @defgroup %s %s\n */\n\n" % (group_id, group_name))
|
|
groups_file.write("/** @} */\n\n")
|
|
|
|
for group_id, group_prototypes in prototypes.items():
|
|
group_prototypes.sort()
|
|
output_name = output_dir / f"{group_id}.h"
|
|
with open(output_name, "w") as output_file:
|
|
for prototype in group_prototypes:
|
|
output_file.write(prototype)
|
|
output_file.write("\n\n")
|
|
|
|
|
|
def process_args():
|
|
usage_str = "%(prog)s [OPTIONS] [FILES]"
|
|
|
|
parser = argparse.ArgumentParser(description='Generates doxygen-annotated header '
|
|
'containing object invocation prototypes',
|
|
usage=usage_str)
|
|
|
|
parser.add_argument("--x86-vtx-64-bit-guests", dest="x86_vtx_64bit", action="store_true", default=False,
|
|
help="Whether the vtx VCPU objects need to be large enough for 64-bit guests.")
|
|
parser.add_argument("-o", "--output", dest="output", default="stage",
|
|
type=str,
|
|
help="Output directory to write stubs to. (default: %(default)s).")
|
|
parser.add_argument("files", metavar="FILES", nargs="+", type=str,
|
|
help="Input XML files.")
|
|
|
|
parser.add_argument("-d", "--dtd", nargs="?", type=str,
|
|
help="DTD xml schema to validate input files against")
|
|
|
|
return parser
|
|
|
|
|
|
def main():
|
|
parser = process_args()
|
|
args = parser.parse_args()
|
|
|
|
if args.dtd is not None:
|
|
dtd = etree.XMLSchema(etree.parse(args.dtd))
|
|
for f in args.files:
|
|
xml = etree.parse(f)
|
|
xml.xinclude()
|
|
if not dtd.validate(xml):
|
|
logging.error("Failed to validate %s against %s" % (f, args.dtd))
|
|
logging.error(dtd.error_log)
|
|
return -1
|
|
|
|
if not os.path.exists(args.output):
|
|
os.makedirs(args.output)
|
|
|
|
if not os.path.isdir(args.output):
|
|
logging.error(f"{args.output} is not a directory")
|
|
return -1
|
|
|
|
gen_invocations(args.files, Path(args.output), args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|