Skip to content
Snippets Groups Projects
genapi.py 7.74 KiB
Newer Older
import argparse
import os
import re
import subprocess
MATCH_API_EXPANSION = re.compile(
    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)
    r"^(?P<type>(?:const )?(?:struct |enum |union )?\w+[*\s]+)(?P<name>\w+)$"
rahix's avatar
rahix committed
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"))

        # Parse arguments
        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})

    return interrupts


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 $
#include "{header}"
""".format(
        header=os.path.relpath(args.header)
    )
    # Evaluate the preprocessor
    source = subprocess.check_output(
        ["gcc", "-E", "-P", "-"], input=api_src.encode()
    declarations = parse_declarations(source)
    interrupts = parse_interrupts(source)

    fmt_header = {"header": os.path.basename(args.header)}
    # Generate Client {{{
    with open(args.client, "w") as f_client:
        tmp = """\
#include <stdio.h>

#define API_ISR(id, isr)
#include "{header}"
#include "api/caller.h"
"""
        f_client.write(tmp.format(**fmt_header))
        for decl in declarations:
            decl["total_size"] = sizeof(decl["args"])
            tmp = """\

/* Autogenerated stub for {id} */
{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))

        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 = """\
#include "modules/log.h"
#include "{header}"

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))
                tmp = """\
        case {id}:
                *(({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 }}}


if __name__ == "__main__":
    main()