#!/usr/bin/env python3
#
# Copyright (c) 2017 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0

import argparse
import sys
import struct
import os
import elftools
from distutils.version import LooseVersion
from elftools.elf.elffile import ELFFile
from elftools.elf.sections import SymbolTableSection

if LooseVersion(elftools.__version__) < LooseVersion('0.24'):
    sys.stderr.write("pyelftools is out of date, need version 0.24 or later\n")
    sys.exit(1)

# 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.stderr.write(os.path.basename(sys.argv[0]) + ": " + text + "\n")
    sys.exit(1)

# 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] != 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] == 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] != 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 = "<IIi"

# 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)

    parser.add_argument("-m", "--vector-map", required=True,
            help="Output file mapping IRQ lines to IDT vectors")