Newer
Older
import argparse
import os
import re
import subprocess
r"__GENERATE_API \$ __GEN_ID_(?P<id>\w+) \$ (?P<decl>.*?) \$",
re.DOTALL | re.MULTILINE,
)
MATCH_ISR_EXPANSION = re.compile(
r"__GENERATE_API_ISR \$ __GEN_ID_(?P<id>\w+) \$ (?P<isr>.*?) \$",
re.DOTALL | re.MULTILINE,
)
MATCH_DECLARATION = re.compile(r"^(?P<typename>.*?)\s*\((?P<args>.*)\)$", re.DOTALL)
MATCH_TYPENAME = re.compile(
r"^(?P<type>(?:const )?(?:struct |enum |union )?\w+[*\s]+)(?P<name>\w+)$"
def sizeof(args):
"""Return a string that describes the size of a list of arguments."""
return " + ".join(a["sizeof"] for a in args) if args != [] else "0"
def bailout(message, *args, **kwargs):
fmt = "\x1B[31;1mError\x1B[0m: {}"
print(fmt.format(message.format(*args, **kwargs)), file=sys.stderr)
sys.exit(1)
def parse_declarations(source):
"""Parse all declarations in the given source."""
declarations = []
for exp in MATCH_API_EXPANSION.finditer(source):
id = exp.group("id")
decl = MATCH_DECLARATION.match(exp.group("decl"))
if decl is None:
bailout("Error in declaration '{}'", exp.group("decl"))
typename = MATCH_TYPENAME.match(decl.group("typename"))
args = []
args_str = decl.group("args")
if typename is None:
bailout("Error in declaration '{}'", exp.group("decl"))
for arg_str in map(str.strip, args_str.split(",")):
if arg_str in ["void", ""]:
continue
arg = MATCH_TYPENAME.match(arg_str.strip())
if arg is None:
bailout("Failed to parse argument '{}'", arg_str.strip())
args.append(
{
"type": arg.group("type").strip(),
"name": arg.group("name"),
"sizeof": "sizeof({})".format(arg.group("type").strip()),
"offset": sizeof(args),
}
)
declarations.append(
{
"id": id,
"return_type": typename.group("type").strip(),
"name": typename.group("name"),
"args": args,
"args_str": args_str,
}
)
def parse_interrupts(source):
"""Parse all isr declarations in the given source."""
interrupts = []
for exp in MATCH_ISR_EXPANSION.finditer(source):
id = exp.group("id")
isr = exp.group("isr")
interrupts.append({"id": id, "isr": isr})
def main():
parser = argparse.ArgumentParser(
description="Generate the API stubs from a header file."
)
parser.add_argument(
"-H", "--header", required=True, help="The header to base the definitions on."
)
parser.add_argument(
"-c", "--client", required=True, help="The output client-side c source file."
)
parser.add_argument(
"-s", "--server", required=True, help="The output server-side c source file."
)
args = parser.parse_args()
# Run the preprocessor on the header file to get the API definitions.
#
# For this, we first need a source to include the header which contains
# an alternative definition of the `API` macro that marks definitions in
# a way we can find later on.
api_src = """\
#define API(id, def) __GENERATE_API $ __GEN_ID_##id $ def $
#define API_ISR(id, isr) __GENERATE_API_ISR $ __GEN_ID_##id $ isr $
header=os.path.relpath(args.header)
)
# Evaluate the preprocessor
source = subprocess.check_output(
["gcc", "-E", "-P", "-"], input=api_src.encode()
declarations = parse_declarations(source)
fmt_header = {"header": os.path.basename(args.header)}
# Generate Client {{{
with open(args.client, "w") as f_client:
tmp = """\
"""
f_client.write(tmp.format(**fmt_header))
for decl in declarations:
decl["total_size"] = sizeof(decl["args"])
tmp = """\
{return_type} {name}({args_str})
const int epc__apistub_size = {total_size};
void*epc__apistub_buffer;
epc__apistub_buffer = _api_call_start({id}, epc__apistub_size);
/* TODO: Check if epc__apistub_buffer is not NULL */
"""
f_client.write(tmp.format(**decl))
for i, arg in enumerate(decl["args"]):
tmp = """\
*({type}*)(epc__apistub_buffer + {offset}) = {name};
"""
f_client.write(tmp.format(**arg))
if decl["return_type"] == "void":
# Don't return if return type is void
tmp = """\
_api_call_transact(epc__apistub_buffer);
}}
"""
f_client.write(tmp.format(**decl))
return *({return_type}*)_api_call_transact(epc__apistub_buffer);
"""
f_client.write(tmp.format(**decl))
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
tmp = """\
/* Weakly linked stubs for ISRs */
"""
f_client.write(tmp)
for isr in interrupts:
tmp = """\
void {isr}(api_int_id_t id)
__attribute__((weak, alias("__epic_isr_default_handler")));
"""
f_client.write(tmp.format(**isr))
tmp = """\
/* Default handler stub */
__attribute__((weak)) void epic_isr_default_handler(api_int_id_t id)
{
;
}
/*
* This function is needed because aliasing the weak default handler will
* lead to issues.
*/
void __epic_isr_default_handler(api_int_id_t id)
{
epic_isr_default_handler(id);
}
/*
* __dispatch_isr() will be called from the actual isr which was triggered
* by core 0. It will then call the appropriate isr.
*/
void __dispatch_isr(api_int_id_t id)
{
switch (id) {
"""
f_client.write(tmp)
for isr in interrupts:
tmp = """\
case {id}:
{isr}(id);
break;
"""
f_client.write(tmp.format(**isr))
tmp = """\
case (-1):
/* Ignore a spurious interrupt */
break;
default:
epic_isr_default_handler(id);
break;
}
}
"""
f_client.write(tmp)
# END: Generate Client }}}
# Generate Dispatcher {{{
with open(args.server, "w") as f_dispatcher:
tmp = """\
void __api_dispatch_call(uint32_t id, void*epc__apistub_buffer)
{{
switch (id) {{
"""
f_dispatcher.write(tmp.format(**fmt_header))
for decl in declarations:
if decl["return_type"] == "void":
tmp = """\
case {id}:
{name}("""
f_dispatcher.write(tmp.format(**decl))
*(({return_type}*)epc__apistub_buffer) = {name}("""
f_dispatcher.write(tmp.format(**decl))
for i, arg in enumerate(decl["args"]):
arg["comma"] = "" if i == 0 else ","
tmp = """{comma}
*({type}*)(epc__apistub_buffer + {offset})"""
f_dispatcher.write(tmp.format(**arg))
tmp = """
);
break;
"""
f_dispatcher.write(tmp.format(**decl))
tmp = """\
default:
/* TODO: Better error handling */
LOG_ERR("api-dispatcher", "API function 0x%lx is unknown!!", id);
"""
f_dispatcher.write(tmp.format(**fmt_header))
# END: Generate Dispatcher }}}