315 lines
9.4 KiB
Python
Executable File
315 lines
9.4 KiB
Python
Executable File
#!/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()
|