1#!/usr/bin/env python3
2# Copyright 2023 The ChromiumOS Authors
3# SPDX-License-Identifier: Apache-2.0
4import sys
5import re
6
7# Xtensa Vector Table linker generator
8#
9# Takes a pre-processed (gcc -dM) core-isa.h file as its first
10# argument, and emits a GNU linker section declartion which will
11# correctly load the exception vectors and literals as long as their
12# code is declared using standard conventions (see below).
13#
14# The section name will be ".z_xtensa_vectors", and a symbol
15# "z_xtensa_vecbase" is emitted containing a valid value for the
16# VECBASE SR at runtime.
17#
18# Obviously, this requires that XCHAL_HAVE_VECBASE=1.  A similar trick
19# could be played to load vectors at fixed addresses on hardware that
20# lacks VECBASE, but the core-isa.h interface is inexplicably
21# different.
22#
23# Because the "standard conventions" (which descend from somewhere in
24# Cadence) are not documented anywhere and just end up cut and pasted
25# between devices, here's an attempt at a specification:
26#
27# + The six register window exception vectors are defined with offsets
28#   internal to their assembly code.  They are linked in a single
29#   section named ".WindowVectors.text".
30#
31# + The "kernel", "user" and "double exception" vectors are emitted in
32#   sections named ".KernelExceptionVector.text",
33#   "UserExceptionVector.text" and "DoubleExceptionVector.text"
34#   respectively.
35#
36# + XEA2 interrupt vectors are in sections named
37#   ".Level<n>InterruptVector.text", except (!) for ones which are
38#   given special names.  The "debug" and "NMI" interrupts (if they
39#   exist) are technically implemented as standard interrupt vectors
40#   (of a platform-dependent level), but the code for them is emitted
41#   in ".DebugExceptionVector.text" and ".NMIExceptionVector.text",
42#   and not a section corresponding to their interrupt level.
43#
44# + Any unused bytes at the end of a vector are made available as
45#   storage for immediate values used by the following vector (Xtensa
46#   can only back-reference immediates for MOVI/L32R instructions) as
47#   a "<name>Vector.literal" section.  Note that there is no guarantee
48#   of how much space is available, it depends on the previous
49#   vector's code size.  Zephyr code has historically not used this
50#   space, as support in existing linker scripts is inconsistent.  But
51#   it's exposed here.
52
53coreisa = sys.argv[1]
54
55debug_level = 0
56
57# Translation for the core-isa.h vs. linker section naming conventions
58sect_names = { "DOUBLEEXC" : "DoubleException",
59               "KERNEL" : "KernelException",
60               "NMI" : "NMIException",
61               "USER" : "UserException" }
62
63offsets = {}
64
65with open(coreisa) as infile:
66    for line in infile.readlines():
67        m = re.match(r"^#define\s+XCHAL_([^ ]+)_VECOFS\s*(.*)", line.rstrip())
68        if m:
69            (sym, val) = (m.group(1), m.group(2))
70            if sym == "WINDOW_OF4":
71                # This must be the start of the section
72                assert eval(val) == 0
73            elif sym.startswith("WINDOW"):
74                # Ignore the other window exceptions, they're internally sorted
75                pass
76            elif sym == "RESET":
77                # Ignore, not actually part of the vector table
78                pass
79            elif sym == "DEBUG":
80                # This one is a recursive macro that doesn't expand,
81                # so handle manually
82                m = re.match(r"XCHAL_INTLEVEL(\d+)_VECOFS", val)
83                if not m:
84                    print(f"no intlevel match for debug val {val}")
85                assert m
86                debug_level = eval(m.group(1))
87            else:
88                if val == "XCHAL_NMI_VECOFS":
89                    # This gets recursively defined in the other
90                    # direction, so ignore the INTLEVEL
91                    pass
92                else:
93                    addr = eval(val)
94                    m = re.match(r"^INTLEVEL(\d+)", sym)
95                    if m:
96                        offsets[f"Level{m.group(1)}Interrupt"] = addr
97                    else:
98                        offsets[sect_names[sym]] = addr
99
100if debug_level > 0:
101    old = f"Level{debug_level}Interrupt"
102    offsets[f"DebugException"] = offsets[old]
103    del offsets[old]
104
105sects = list(offsets)
106sects.sort(key=lambda s: offsets[s])
107
108print("/* Automatically Generated Code - Do Not Edit */")
109print("/* See arch/xtensa/core/gen_vector.py */")
110print("")
111
112# The 1k alignment is experimental, the docs on the Relocatable Vector
113# Option doesn't specify an alignment at all, but writes to the
114# bottom bits don't take...
115print( "  .z_xtensa_vectors : ALIGN(1024) {")
116print( "    z_xtensa_vecbase = .;")
117print(f"    KEEP(*(.WindowVectors.text));")
118for s in sects:
119    print(f"    KEEP(*(.{s}Vector.literal));")
120    print( "    . = 0x%3.3x;" % (offsets[s]))
121    print(f"    KEEP(*(.{s}Vector.text));")
122print("  }")
123