1#!/usr/bin/env python3
2#
3# Copyright (c) 2017 Intel Corporation
4#
5# SPDX-License-Identifier: Apache-2.0
6
7"""Generate Interrupt Descriptor Table for x86 CPUs.
8
9This script generates the interrupt descriptor table (IDT) for x86.
10Please consult the IA Architecture SW Developer Manual, volume 3,
11for more details on this data structure.
12
13This script accepts as input the zephyr_prebuilt.elf binary,
14which is a link of the Zephyr kernel without various build-time
15generated data structures (such as the IDT) inserted into it.
16This kernel image has been properly padded such that inserting
17these data structures will not disturb the memory addresses of
18other symbols. From the kernel binary we read a special section
19"intList" which contains the desired interrupt routing configuration
20for the kernel, populated by instances of the IRQ_CONNECT() macro.
21
22This script outputs three binary tables:
23
241. The interrupt descriptor table itself.
252. A bitfield indicating which vectors in the IDT are free for
26   installation of dynamic interrupts at runtime.
273. An array which maps configured IRQ lines to their associated
28   vector entries in the IDT, used to program the APIC at runtime.
29"""
30
31import argparse
32import sys
33import struct
34import os
35import elftools
36from packaging import version
37from elftools.elf.elffile import ELFFile
38from elftools.elf.sections import SymbolTableSection
39
40if version.parse(elftools.__version__) < version.parse('0.24'):
41    sys.exit("pyelftools is out of date, need version 0.24 or later")
42
43# This will never change, first selector in the GDT after the null selector
44KERNEL_CODE_SEG = 0x08
45
46# These exception vectors push an error code onto the stack.
47ERR_CODE_VECTORS = [8, 10, 11, 12, 13, 14, 17]
48
49
50def debug(text):
51    if not args.verbose:
52        return
53    sys.stdout.write(os.path.basename(sys.argv[0]) + ": " + text + "\n")
54
55
56def error(text):
57    sys.exit(os.path.basename(sys.argv[0]) + ": " + text)
58
59
60# See Section 6.11 of the Intel Architecture Software Developer's Manual
61gate_desc_format = "<HHBBH"
62
63
64def create_irq_gate(handler, dpl):
65    present = 1
66    gate_type = 0xE  # 32-bit interrupt gate
67    type_attr = gate_type | (dpl << 5) | (present << 7)
68
69    offset_hi = handler >> 16
70    offset_lo = handler & 0xFFFF
71
72    data = struct.pack(gate_desc_format, offset_lo, KERNEL_CODE_SEG, 0,
73                       type_attr, offset_hi)
74    return data
75
76
77def create_task_gate(tss, dpl):
78    present = 1
79    gate_type = 0x5  # 32-bit task gate
80    type_attr = gate_type | (dpl << 5) | (present << 7)
81
82    data = struct.pack(gate_desc_format, 0, tss, 0, type_attr, 0)
83    return data
84
85
86def create_idt_binary(idt_config, filename):
87    with open(filename, "wb") as fp:
88        for handler, tss, dpl in idt_config:
89            if handler and tss:
90                error("entry specifies both handler function and tss")
91
92            if not handler and not tss:
93                error("entry does not specify either handler or tss")
94
95            if handler:
96                data = create_irq_gate(handler, dpl)
97            else:
98                data = create_task_gate(tss, dpl)
99
100            fp.write(data)
101
102
103map_fmt = "<B"
104
105
106def create_irq_vec_map_binary(irq_vec_map, filename):
107    with open(filename, "wb") as fp:
108        for i in irq_vec_map:
109            fp.write(struct.pack(map_fmt, i))
110
111
112def priority_range(prio):
113    # Priority levels are represented as groups of 16 vectors within the IDT
114    base = 32 + (prio * 16)
115    return range(base, base + 16)
116
117
118def update_irq_vec_map(irq_vec_map, irq, vector, max_irq):
119    # No IRQ associated; exception or software interrupt
120    if irq == -1:
121        return
122
123    if irq >= max_irq:
124        error("irq %d specified, but CONFIG_MAX_IRQ_LINES is %d" %
125              (irq, max_irq))
126
127    # This table will never have values less than 32 since those are for
128    # exceptions; 0 means unconfigured
129    if irq_vec_map[irq] != 0:
130        error("multiple vector assignments for interrupt line %d" % irq)
131
132    debug("assign IRQ %d to vector %d" % (irq, vector))
133    irq_vec_map[irq] = vector
134
135
136def setup_idt(spur_code, spur_nocode, intlist, max_vec, max_irq):
137    irq_vec_map = [0 for i in range(max_irq)]
138    vectors = [None for i in range(max_vec)]
139
140    # Pass 1: sanity check and set up hard-coded interrupt vectors
141    for handler, irq, prio, vec, dpl, tss in intlist:
142        if vec == -1:
143            if prio == -1:
144                error("entry does not specify vector or priority level")
145            continue
146
147        if vec >= max_vec:
148            error("Vector %d specified, but size of IDT is only %d vectors" %
149                  (vec, max_vec))
150
151        if vectors[vec] is not None:
152            error("Multiple assignments for vector %d" % vec)
153
154        vectors[vec] = (handler, tss, dpl)
155        update_irq_vec_map(irq_vec_map, irq, vec, max_irq)
156
157    # Pass 2: set up priority-based interrupt vectors
158    for handler, irq, prio, vec, dpl, tss in intlist:
159        if vec != -1:
160            continue
161
162        for vi in priority_range(prio):
163            if vi >= max_vec:
164                break
165            if vectors[vi] is None:
166                vec = vi
167                break
168
169        if vec == -1:
170            error("can't find a free vector in priority level %d" % prio)
171
172        vectors[vec] = (handler, tss, dpl)
173        update_irq_vec_map(irq_vec_map, irq, vec, max_irq)
174
175    # Pass 3: fill in unused vectors with spurious handler at dpl=0
176    for i in range(max_vec):
177        if vectors[i] is not None:
178            continue
179
180        if i in ERR_CODE_VECTORS:
181            handler = spur_code
182        else:
183            handler = spur_nocode
184
185        vectors[i] = (handler, 0, 0)
186
187    return vectors, irq_vec_map
188
189
190def get_symbols(obj):
191    for section in obj.iter_sections():
192        if isinstance(section, SymbolTableSection):
193            return {sym.name: sym.entry.st_value
194                    for sym in section.iter_symbols()}
195
196    raise LookupError("Could not find symbol table")
197
198# struct genidt_header_s {
199#	uint32_t spurious_addr;
200#	uint32_t spurious_no_error_addr;
201#	int32_t num_entries;
202# };
203
204
205intlist_header_fmt = "<II"
206
207# struct genidt_entry_s {
208#	uint32_t isr;
209#	int32_t irq;
210#	int32_t priority;
211#	int32_t vector_id;
212#	int32_t dpl;
213#	int32_t tss;
214# };
215
216intlist_entry_fmt = "<Iiiiii"
217
218
219def get_intlist(elf):
220    intdata = elf.get_section_by_name("intList").data()
221
222    header_sz = struct.calcsize(intlist_header_fmt)
223    header = struct.unpack_from(intlist_header_fmt, intdata, 0)
224    intdata = intdata[header_sz:]
225
226    spurious_code = header[0]
227    spurious_nocode = header[1]
228
229    debug("spurious handler (code)    : %s" % hex(header[0]))
230    debug("spurious handler (no code) : %s" % hex(header[1]))
231
232    intlist = [i for i in
233               struct.iter_unpack(intlist_entry_fmt, intdata)]
234
235    debug("Configured interrupt routing")
236    debug("handler    irq pri vec dpl")
237    debug("--------------------------")
238
239    for irq in intlist:
240        debug("{0:<10} {1:<3} {2:<3} {3:<3} {4:<2}".format(
241            hex(irq[0]),
242            "-" if irq[1] == -1 else irq[1],
243            "-" if irq[2] == -1 else irq[2],
244            "-" if irq[3] == -1 else irq[3],
245            irq[4]))
246
247    return (spurious_code, spurious_nocode, intlist)
248
249
250def parse_args():
251    global args
252    parser = argparse.ArgumentParser(
253        description=__doc__,
254        formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False)
255
256    parser.add_argument("-m", "--vector-map", required=True,
257                        help="Output file mapping IRQ lines to IDT vectors")
258    parser.add_argument("-o", "--output-idt", required=True,
259                        help="Output file containing IDT binary")
260    parser.add_argument("-a", "--output-vectors-alloc", required=False,
261                        help="Output file indicating allocated vectors")
262    parser.add_argument("-k", "--kernel", required=True,
263                        help="Zephyr kernel image")
264    parser.add_argument("-v", "--verbose", action="store_true",
265                        help="Print extra debugging information")
266    args = parser.parse_args()
267    if "VERBOSE" in os.environ:
268        args.verbose = 1
269
270
271def create_irq_vectors_allocated(vectors, spur_code, spur_nocode, filename):
272    # Construct a bitfield over all the IDT vectors, where if bit n is 1,
273    # that vector is free. those vectors have either of the two spurious
274    # interrupt handlers installed, they are free for runtime installation
275    # of interrupts
276    num_chars = (len(vectors) + 7) // 8
277    vbits = num_chars*[0]
278    for i, (handler, _, _) in enumerate(vectors):
279        if handler not in (spur_code, spur_nocode):
280            continue
281
282        vbit_index = i // 8
283        vbit_val = 1 << (i % 8)
284        vbits[vbit_index] = vbits[vbit_index] | vbit_val
285
286    with open(filename, "wb") as fp:
287        for char in vbits:
288            fp.write(struct.pack("<B", char))
289
290
291def main():
292    parse_args()
293
294    with open(args.kernel, "rb") as fp:
295        kernel = ELFFile(fp)
296
297        syms = get_symbols(kernel)
298        spur_code, spur_nocode, intlist = get_intlist(kernel)
299
300    max_irq = syms["CONFIG_MAX_IRQ_LINES"]
301    max_vec = syms["CONFIG_IDT_NUM_VECTORS"]
302
303    vectors, irq_vec_map = setup_idt(spur_code, spur_nocode, intlist, max_vec,
304                                     max_irq)
305
306    create_idt_binary(vectors, args.output_idt)
307    create_irq_vec_map_binary(irq_vec_map, args.vector_map)
308    if args.output_vectors_alloc:
309        create_irq_vectors_allocated(vectors, spur_code, spur_nocode,
310                                     args.output_vectors_alloc)
311
312
313if __name__ == "__main__":
314    main()
315