You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
314 lines
9.4 KiB
314 lines
9.4 KiB
#!/usr/bin/env python3 |
|
# |
|
# Copyright (c) 2017 Intel Corporation |
|
# |
|
# SPDX-License-Identifier: Apache-2.0 |
|
|
|
"""Generate Interrupt Descriptor Table for x86 CPUs. |
|
|
|
This script generates the interrupt descriptor table (IDT) for x86. |
|
Please consult the IA Architecture SW Developer Manual, volume 3, |
|
for more details on this data structure. |
|
|
|
This script accepts as input the zephyr_prebuilt.elf binary, |
|
which is a link of the Zephyr kernel without various build-time |
|
generated data structures (such as the IDT) inserted into it. |
|
This kernel image has been properly padded such that inserting |
|
these data structures will not disturb the memory addresses of |
|
other symbols. From the kernel binary we read a special section |
|
"intList" which contains the desired interrupt routing configuration |
|
for the kernel, populated by instances of the IRQ_CONNECT() macro. |
|
|
|
This script outputs three binary tables: |
|
|
|
1. The interrupt descriptor table itself. |
|
2. A bitfield indicating which vectors in the IDT are free for |
|
installation of dynamic interrupts at runtime. |
|
3. An array which maps configured IRQ lines to their associated |
|
vector entries in the IDT, used to program the APIC at runtime. |
|
""" |
|
|
|
import argparse |
|
import sys |
|
import struct |
|
import os |
|
import elftools |
|
from packaging import version |
|
from elftools.elf.elffile import ELFFile |
|
from elftools.elf.sections import SymbolTableSection |
|
|
|
if version.parse(elftools.__version__) < version.parse('0.24'): |
|
sys.exit("pyelftools is out of date, need version 0.24 or later") |
|
|
|
# This will never change, first selector in the GDT after the null selector |
|
KERNEL_CODE_SEG = 0x08 |
|
|
|
# These exception vectors push an error code onto the stack. |
|
ERR_CODE_VECTORS = [8, 10, 11, 12, 13, 14, 17] |
|
|
|
|
|
def debug(text): |
|
if not args.verbose: |
|
return |
|
sys.stdout.write(os.path.basename(sys.argv[0]) + ": " + text + "\n") |
|
|
|
|
|
def error(text): |
|
sys.exit(os.path.basename(sys.argv[0]) + ": " + text) |
|
|
|
|
|
# See Section 6.11 of the Intel Architecture Software Developer's Manual |
|
gate_desc_format = "<HHBBH" |
|
|
|
|
|
def create_irq_gate(handler, dpl): |
|
present = 1 |
|
gate_type = 0xE # 32-bit interrupt gate |
|
type_attr = gate_type | (dpl << 5) | (present << 7) |
|
|
|
offset_hi = handler >> 16 |
|
offset_lo = handler & 0xFFFF |
|
|
|
data = struct.pack(gate_desc_format, offset_lo, KERNEL_CODE_SEG, 0, |
|
type_attr, offset_hi) |
|
return data |
|
|
|
|
|
def create_task_gate(tss, dpl): |
|
present = 1 |
|
gate_type = 0x5 # 32-bit task gate |
|
type_attr = gate_type | (dpl << 5) | (present << 7) |
|
|
|
data = struct.pack(gate_desc_format, 0, tss, 0, type_attr, 0) |
|
return data |
|
|
|
|
|
def create_idt_binary(idt_config, filename): |
|
with open(filename, "wb") as fp: |
|
for handler, tss, dpl in idt_config: |
|
if handler and tss: |
|
error("entry specifies both handler function and tss") |
|
|
|
if not handler and not tss: |
|
error("entry does not specify either handler or tss") |
|
|
|
if handler: |
|
data = create_irq_gate(handler, dpl) |
|
else: |
|
data = create_task_gate(tss, dpl) |
|
|
|
fp.write(data) |
|
|
|
|
|
map_fmt = "<B" |
|
|
|
|
|
def create_irq_vec_map_binary(irq_vec_map, filename): |
|
with open(filename, "wb") as fp: |
|
for i in irq_vec_map: |
|
fp.write(struct.pack(map_fmt, i)) |
|
|
|
|
|
def priority_range(prio): |
|
# Priority levels are represented as groups of 16 vectors within the IDT |
|
base = 32 + (prio * 16) |
|
return range(base, base + 16) |
|
|
|
|
|
def update_irq_vec_map(irq_vec_map, irq, vector, max_irq): |
|
# No IRQ associated; exception or software interrupt |
|
if irq == -1: |
|
return |
|
|
|
if irq >= max_irq: |
|
error("irq %d specified, but CONFIG_MAX_IRQ_LINES is %d" % |
|
(irq, max_irq)) |
|
|
|
# This table will never have values less than 32 since those are for |
|
# exceptions; 0 means unconfigured |
|
if irq_vec_map[irq] != 0: |
|
error("multiple vector assignments for interrupt line %d" % irq) |
|
|
|
debug("assign IRQ %d to vector %d" % (irq, vector)) |
|
irq_vec_map[irq] = vector |
|
|
|
|
|
def setup_idt(spur_code, spur_nocode, intlist, max_vec, max_irq): |
|
irq_vec_map = [0 for i in range(max_irq)] |
|
vectors = [None for i in range(max_vec)] |
|
|
|
# Pass 1: sanity check and set up hard-coded interrupt vectors |
|
for handler, irq, prio, vec, dpl, tss in intlist: |
|
if vec == -1: |
|
if prio == -1: |
|
error("entry does not specify vector or priority level") |
|
continue |
|
|
|
if vec >= max_vec: |
|
error("Vector %d specified, but size of IDT is only %d vectors" % |
|
(vec, max_vec)) |
|
|
|
if vectors[vec] is not None: |
|
error("Multiple assignments for vector %d" % vec) |
|
|
|
vectors[vec] = (handler, tss, dpl) |
|
update_irq_vec_map(irq_vec_map, irq, vec, max_irq) |
|
|
|
# Pass 2: set up priority-based interrupt vectors |
|
for handler, irq, prio, vec, dpl, tss in intlist: |
|
if vec != -1: |
|
continue |
|
|
|
for vi in priority_range(prio): |
|
if vi >= max_vec: |
|
break |
|
if vectors[vi] is None: |
|
vec = vi |
|
break |
|
|
|
if vec == -1: |
|
error("can't find a free vector in priority level %d" % prio) |
|
|
|
vectors[vec] = (handler, tss, dpl) |
|
update_irq_vec_map(irq_vec_map, irq, vec, max_irq) |
|
|
|
# Pass 3: fill in unused vectors with spurious handler at dpl=0 |
|
for i in range(max_vec): |
|
if vectors[i] is not None: |
|
continue |
|
|
|
if i in ERR_CODE_VECTORS: |
|
handler = spur_code |
|
else: |
|
handler = spur_nocode |
|
|
|
vectors[i] = (handler, 0, 0) |
|
|
|
return vectors, irq_vec_map |
|
|
|
|
|
def get_symbols(obj): |
|
for section in obj.iter_sections(): |
|
if isinstance(section, SymbolTableSection): |
|
return {sym.name: sym.entry.st_value |
|
for sym in section.iter_symbols()} |
|
|
|
raise LookupError("Could not find symbol table") |
|
|
|
# struct genidt_header_s { |
|
# uint32_t spurious_addr; |
|
# uint32_t spurious_no_error_addr; |
|
# int32_t num_entries; |
|
# }; |
|
|
|
|
|
intlist_header_fmt = "<II" |
|
|
|
# struct genidt_entry_s { |
|
# uint32_t isr; |
|
# int32_t irq; |
|
# int32_t priority; |
|
# int32_t vector_id; |
|
# int32_t dpl; |
|
# int32_t tss; |
|
# }; |
|
|
|
intlist_entry_fmt = "<Iiiiii" |
|
|
|
|
|
def get_intlist(elf): |
|
intdata = elf.get_section_by_name("intList").data() |
|
|
|
header_sz = struct.calcsize(intlist_header_fmt) |
|
header = struct.unpack_from(intlist_header_fmt, intdata, 0) |
|
intdata = intdata[header_sz:] |
|
|
|
spurious_code = header[0] |
|
spurious_nocode = header[1] |
|
|
|
debug("spurious handler (code) : %s" % hex(header[0])) |
|
debug("spurious handler (no code) : %s" % hex(header[1])) |
|
|
|
intlist = [i for i in |
|
struct.iter_unpack(intlist_entry_fmt, intdata)] |
|
|
|
debug("Configured interrupt routing") |
|
debug("handler irq pri vec dpl") |
|
debug("--------------------------") |
|
|
|
for irq in intlist: |
|
debug("{0:<10} {1:<3} {2:<3} {3:<3} {4:<2}".format( |
|
hex(irq[0]), |
|
"-" if irq[1] == -1 else irq[1], |
|
"-" if irq[2] == -1 else irq[2], |
|
"-" if irq[3] == -1 else irq[3], |
|
irq[4])) |
|
|
|
return (spurious_code, spurious_nocode, intlist) |
|
|
|
|
|
def parse_args(): |
|
global args |
|
parser = argparse.ArgumentParser( |
|
description=__doc__, |
|
formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False) |
|
|
|
parser.add_argument("-m", "--vector-map", required=True, |
|
help="Output file mapping IRQ lines to IDT vectors") |
|
parser.add_argument("-o", "--output-idt", required=True, |
|
help="Output file containing IDT binary") |
|
parser.add_argument("-a", "--output-vectors-alloc", required=False, |
|
help="Output file indicating allocated vectors") |
|
parser.add_argument("-k", "--kernel", required=True, |
|
help="Zephyr kernel image") |
|
parser.add_argument("-v", "--verbose", action="store_true", |
|
help="Print extra debugging information") |
|
args = parser.parse_args() |
|
if "VERBOSE" in os.environ: |
|
args.verbose = 1 |
|
|
|
|
|
def create_irq_vectors_allocated(vectors, spur_code, spur_nocode, filename): |
|
# Construct a bitfield over all the IDT vectors, where if bit n is 1, |
|
# that vector is free. those vectors have either of the two spurious |
|
# interrupt handlers installed, they are free for runtime installation |
|
# of interrupts |
|
num_chars = (len(vectors) + 7) // 8 |
|
vbits = num_chars*[0] |
|
for i, (handler, _, _) in enumerate(vectors): |
|
if handler not in (spur_code, spur_nocode): |
|
continue |
|
|
|
vbit_index = i // 8 |
|
vbit_val = 1 << (i % 8) |
|
vbits[vbit_index] = vbits[vbit_index] | vbit_val |
|
|
|
with open(filename, "wb") as fp: |
|
for char in vbits: |
|
fp.write(struct.pack("<B", char)) |
|
|
|
|
|
def main(): |
|
parse_args() |
|
|
|
with open(args.kernel, "rb") as fp: |
|
kernel = ELFFile(fp) |
|
|
|
syms = get_symbols(kernel) |
|
spur_code, spur_nocode, intlist = get_intlist(kernel) |
|
|
|
max_irq = syms["CONFIG_MAX_IRQ_LINES"] |
|
max_vec = syms["CONFIG_IDT_NUM_VECTORS"] |
|
|
|
vectors, irq_vec_map = setup_idt(spur_code, spur_nocode, intlist, max_vec, |
|
max_irq) |
|
|
|
create_idt_binary(vectors, args.output_idt) |
|
create_irq_vec_map_binary(irq_vec_map, args.vector_map) |
|
if args.output_vectors_alloc: |
|
create_irq_vectors_allocated(vectors, spur_code, spur_nocode, |
|
args.output_vectors_alloc) |
|
|
|
|
|
if __name__ == "__main__": |
|
main()
|
|
|