1#!/usr/bin/env python3
2import re
3import fileinput
4
5# Pass an Xtensa core-isa.h file on stdin or the command line, emits a
6# C file on output containing optimized interrupt dispatch routines.
7
8# FIXME: looking at the assembly generated by the ESP-32 toolchain,
9# this isn't as optimal as I'd hoped.  the individual cases are tested
10# using a L32R + BNONE (i.e. a full mask test) instead of a BBSI, and
11# the handlers are being invoked with CALL8 instead of CALL4,
12# inexplicably wasting four words of stack.  Maybe this should be
13# emitting assembly instead.  Wouldn't be much more complicated and
14# would share all the same structure.
15
16# My manual count of instructions says that a linear search becomes
17# faster on average when there are three or fewer bits to test.  Would
18# be four, if the compiler would generate BBSI instructions.
19MAX_TESTS = 3
20
21ints_by_lvl = {}
22
23# print() wrapper that automatically handles indentation levels
24cindent = 0
25def cprint(s):
26    global cindent
27    if s.endswith(":"):
28        print(s)
29        return
30    if s.find("}") >= 0:
31        cindent -= 1
32    s = cindent*"\t" + s
33    print(s)
34    if s.find("{") >= 0:
35        cindent += 1
36
37def emit_int_handler(ints):
38    if len(ints) <= MAX_TESTS:
39        for i in ints:
40            # FIXME: a little work could allow us to extract the
41            # handler pointer and argument as literals, saving a few
42            # instructions and avoiding the need to link in
43            # _sw_isr_table entirely.
44            cprint("if (mask & BIT(%d)) {" % i)
45            cprint("mask = BIT(%d);" % i)
46            cprint("irq = %d;" % i)
47            cprint("goto handle_irq;")
48            cprint("}")
49    else:
50        half = int(len(ints)/2)
51
52        m = 0
53        for i in ints[0:half]:
54            m |= 1 << i
55        cprint("if (mask & " + ("0x%x" % (m)) + ") {")
56        emit_int_handler(ints[0:half])
57        cprint("} else {")
58        emit_int_handler(ints[half:])
59        cprint("}")
60
61########################################################################
62
63# Annoyingly need to join lines and remove #-marked annotations.  Some
64# versions of the preprocessor (ahem, esp32 SDK) like to include
65# newlines in the output where the original expressions are expanded
66# from 100% single line macros.  Slurp it into a single string and
67# parse via whitespace.
68blob = ""
69for l in fileinput.input():
70    l = l if l.find("#") < 0 else l[0:l.find("#")]
71    blob += l.rstrip() + " "
72
73for match in re.finditer(r'__xtensa_int_level_magic__\s+(\d+)\s+(\d+)', blob):
74    irq = int(match.group(1))
75    lvl = int(match.group(2))
76
77    if lvl not in ints_by_lvl:
78        ints_by_lvl[lvl] = []
79
80    ints_by_lvl[lvl].append(irq)
81
82cprint("/*")
83cprint(" * THIS FILE WAS AUTOMATICALLY GENERATED.  DO NOT EDIT.")
84cprint(" *")
85cprint(" * Functions here are designed to produce efficient code to")
86cprint(" * search an Xtensa bitmask of interrupts, inspecting only those bits")
87cprint(" * declared to be associated with a given interrupt level.  Each")
88cprint(" * dispatcher will handle exactly one flagged interrupt, in numerical")
89cprint(" * order (low bits first) and will return a mask of that bit that can")
90cprint(" * then be cleared by the calling code.  Unrecognized bits for the")
91cprint(" * level will invoke an error handler.")
92cprint(" */")
93cprint("")
94
95# Re-include the core-isa header and be sure our definitions match, for sanity
96cprint("#include <xtensa/config/core-isa.h>")
97cprint("#include <zephyr/sys/util.h>")
98cprint("#include <zephyr/sw_isr_table.h>")
99cprint("")
100for l in ints_by_lvl:
101    for i in ints_by_lvl[l]:
102        v = "XCHAL_INT" + str(i) + "_LEVEL"
103        cprint("#if !defined(" + v + ") || " + str(v) + " != " + str(l))
104        cprint("#error core-isa.h interrupt level does not match dispatcher!")
105        cprint("#endif")
106cprint("")
107
108# Populate empty levels just for sanity.  The second-to-last interrupt
109# level (usually "debug") typically doesn't have any associated
110# vectors, but we don't have any way to know that a-prioi.
111max = 0
112for lvl in ints_by_lvl:
113    if lvl > max:
114        max = lvl
115
116for lvl in range(0, max+1):
117    if not lvl in ints_by_lvl:
118        ints_by_lvl[lvl] = []
119
120# Emit the handlers
121for lvl in ints_by_lvl:
122    cprint("static inline int _xtensa_handle_one_int" + str(lvl) + "(unsigned int mask)")
123    cprint("{")
124
125    if not ints_by_lvl[lvl]:
126        cprint("return 0;")
127        cprint("}")
128        continue
129
130    cprint("int irq;")
131    print("")
132
133    emit_int_handler(sorted(ints_by_lvl[lvl]))
134
135    cprint("return 0;")
136    cprint("handle_irq:")
137    cprint("_sw_isr_table[irq].isr(_sw_isr_table[irq].arg);")
138    cprint("return mask;")
139    cprint("}")
140    cprint("")
141