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