mirror of
https://github.com/opnsense/src.git
synced 2026-02-13 15:57:05 -05:00
This adds support for the NVRAM handling and the basic SPROM hardware used on siba(4) and bcma(4) devices, including: * SPROM directly attached to the PCI core, accessible via PCI configuration space. * SPROM attached to later ChipCommon cores. * SPROM variables vended from the parent SoC bus (e.g. via a directly-attached flash device). Additional improvements to the NVRAM/SPROM interface will be required, but this changeset stands alone as working checkpoint. Submitted by: Landon Fuller <landonf@landonf.org> Reviewed by: Michael Zhilin <mizkha@gmail.com> (Broadcom MIPS support) Differential Revision: https://reviews.freebsd.org/D6196
1162 lines
26 KiB
Awk
Executable file
1162 lines
26 KiB
Awk
Executable file
#!/usr/bin/awk -f
|
|
|
|
#-
|
|
# Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions
|
|
# are met:
|
|
# 1. Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer,
|
|
# without modification.
|
|
# 2. Redistributions in binary form must reproduce at minimum a disclaimer
|
|
# similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
|
|
# redistribution must be conditioned upon including a substantially
|
|
# similar Disclaimer requirement for further binary redistribution.
|
|
#
|
|
# NO WARRANTY
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
# LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
|
|
# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
# THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
|
|
# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
|
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
# THE POSSIBILITY OF SUCH DAMAGES.
|
|
#
|
|
# $FreeBSD$
|
|
|
|
BEGIN {
|
|
RS="\n"
|
|
|
|
depth = 0
|
|
symbols[depth,"_file"] = FILENAME
|
|
num_output_vars = 0
|
|
OUTPUT_FILE = null
|
|
|
|
# Seed rand()
|
|
srand()
|
|
|
|
# Output type
|
|
OUT_T = null
|
|
OUT_T_HEADER = "HEADER"
|
|
OUT_T_DATA = "DATA"
|
|
|
|
# Enable debug output
|
|
DEBUG = 0
|
|
|
|
# Maximum revision
|
|
REV_MAX = 255
|
|
|
|
# Parse arguments
|
|
if (ARGC < 2)
|
|
usage()
|
|
|
|
for (i = 1; i < ARGC; i++) {
|
|
if (ARGV[i] == "--debug") {
|
|
DEBUG = 1
|
|
} else if (ARGV[i] == "-d" && OUT_T == null) {
|
|
OUT_T = OUT_T_DATA
|
|
} else if (ARGV[i] == "-h" && OUT_T == null) {
|
|
OUT_T = OUT_T_HEADER
|
|
} else if (ARGV[i] == "-o") {
|
|
i++
|
|
if (i >= ARGC)
|
|
usage()
|
|
|
|
OUTPUT_FILE = ARGV[i]
|
|
} else if (ARGV[i] == "--") {
|
|
i++
|
|
break
|
|
} else if (ARGV[i] !~ /^-/) {
|
|
FILENAME = ARGV[i]
|
|
} else {
|
|
print "unknown option " ARGV[i]
|
|
usage()
|
|
}
|
|
}
|
|
|
|
ARGC=2
|
|
|
|
if (OUT_T == null) {
|
|
print("error: one of -d or -h required")
|
|
usage()
|
|
}
|
|
|
|
if (FILENAME == null) {
|
|
print("error: no input file specified")
|
|
usage()
|
|
}
|
|
|
|
if (OUTPUT_FILE == "-") {
|
|
OUTPUT_FILE = "/dev/stdout"
|
|
} else if (OUTPUT_FILE == null) {
|
|
_bi = split(FILENAME, _paths, "/")
|
|
OUTPUT_FILE = _paths[_bi]
|
|
|
|
if (OUTPUT_FILE !~ /^bhnd_/)
|
|
OUTPUT_FILE = "bhnd_" OUTPUT_FILE
|
|
|
|
if (OUT_T == OUT_T_HEADER)
|
|
OUTPUT_FILE = OUTPUT_FILE ".h"
|
|
else
|
|
OUTPUT_FILE = OUTPUT_FILE "_data.h"
|
|
}
|
|
|
|
# Format Constants
|
|
FMT["hex"] = "BHND_NVRAM_VFMT_HEX"
|
|
FMT["decimal"] = "BHND_NVRAM_VFMT_DEC"
|
|
FMT["ccode"] = "BHND_NVRAM_VFMT_CCODE"
|
|
FMT["macaddr"] = "BHND_NVRAM_VFMT_MACADDR"
|
|
FMT["led_dc"] = "BHND_NVRAM_VFMT_LEDDC"
|
|
|
|
# Data Type Constants
|
|
DTYPE["u8"] = "BHND_NVRAM_DT_UINT8"
|
|
DTYPE["u16"] = "BHND_NVRAM_DT_UINT16"
|
|
DTYPE["u32"] = "BHND_NVRAM_DT_UINT32"
|
|
DTYPE["i8"] = "BHND_NVRAM_DT_INT8"
|
|
DTYPE["i16"] = "BHND_NVRAM_DT_INT16"
|
|
DTYPE["i32"] = "BHND_NVRAM_DT_INT32"
|
|
DTYPE["char"] = "BHND_NVRAM_DT_CHAR"
|
|
|
|
# Default masking for standard types
|
|
TMASK["u8"] = "0x000000FF"
|
|
TMASK["u16"] = "0x0000FFFF"
|
|
TMASK["u32"] = "0xFFFFFFFF"
|
|
TMASK["i8"] = TMASK["u8"]
|
|
TMASK["i16"] = TMASK["u16"]
|
|
TMASK["i32"] = TMASK["u32"]
|
|
TMASK["char"] = TMASK["u8"]
|
|
|
|
# Byte sizes for standard types
|
|
TSIZE["u8"] = "1"
|
|
TSIZE["u16"] = "2"
|
|
TSIZE["u32"] = "4"
|
|
TSIZE["i8"] = TSIZE["u8"]
|
|
TSIZE["i16"] = TSIZE["u8"]
|
|
TSIZE["i32"] = TSIZE["u8"]
|
|
TSIZE["char"] = "1"
|
|
|
|
# Common Regexs
|
|
INT_REGEX = "^(0|[1-9][0-9]*),?$"
|
|
HEX_REGEX = "^0x[A-Fa-f0-9]+,?$"
|
|
|
|
ARRAY_REGEX = "\\[(0|[1-9][0-9]*)\\]"
|
|
TYPES_REGEX = "^(((u|i)(8|16|32))|char)("ARRAY_REGEX")?,?$"
|
|
|
|
IDENT_REGEX = "^[A-Za-z_][A-Za-z0-9_]*,?$"
|
|
SROM_OFF_REGEX = "("TYPES_REGEX"|"HEX_REGEX")"
|
|
|
|
# Parser states types
|
|
ST_STRUCT_BLOCK = "struct" # struct block
|
|
ST_VAR_BLOCK = "var" # variable block
|
|
ST_SROM_DEFN = "srom" # srom offset defn
|
|
ST_NONE = "NONE" # default state
|
|
|
|
# Property types
|
|
PROP_T_SFMT = "sfmt"
|
|
PROP_T_ALL1 = "all1"
|
|
|
|
# Internal variables used for parser state
|
|
# tracking
|
|
STATE_TYPE = "_state_type"
|
|
STATE_IDENT = "_state_block_name"
|
|
STATE_LINENO = "_state_first_line"
|
|
STATE_ISBLOCK = "_state_is_block"
|
|
|
|
# Common array keys
|
|
DEF_LINE = "def_line"
|
|
NUM_REVS = "num_revs"
|
|
REV = "rev"
|
|
|
|
# Revision array keys
|
|
REV_START = "rev_start"
|
|
REV_END = "rev_end"
|
|
REV_DESC = "rev_decl"
|
|
REV_NUM_OFFS = "num_offs"
|
|
|
|
# Offset array keys
|
|
OFF = "off"
|
|
OFF_NUM_SEGS = "off_num_segs"
|
|
OFF_SEG = "off_seg"
|
|
|
|
# Segment array keys
|
|
SEG_ADDR = "seg_addr"
|
|
SEG_COUNT = "seg_count"
|
|
SEG_TYPE = "seg_type"
|
|
SEG_MASK = "seg_mask"
|
|
SEG_SHIFT = "seg_shift"
|
|
|
|
# Variable array keys
|
|
VAR_NAME = "v_name"
|
|
VAR_TYPE = "v_type"
|
|
VAR_BASE_TYPE = "v_base_type"
|
|
VAR_FMT = "v_fmt"
|
|
VAR_STRUCT = "v_parent_struct"
|
|
VAR_PRIVATE = "v_private"
|
|
VAR_ARRAY = "v_array"
|
|
VAR_IGNALL1 = "v_ignall1"
|
|
}
|
|
|
|
# return the flag definition for variable `v`
|
|
function gen_var_flags (v)
|
|
{
|
|
_num_flags = 0;
|
|
if (vars[v,VAR_ARRAY])
|
|
_flags[_num_flags++] = "BHND_NVRAM_VF_ARRAY"
|
|
|
|
if (vars[v,VAR_PRIVATE])
|
|
_flags[_num_flags++] = "BHND_NVRAM_VF_MFGINT"
|
|
|
|
if (vars[v,VAR_IGNALL1])
|
|
_flags[_num_flags++] = "BHND_NVRAM_VF_IGNALL1"
|
|
|
|
if (_num_flags == 0)
|
|
_flags[_num_flags++] = "0"
|
|
|
|
return (join(_flags, "|", _num_flags))
|
|
}
|
|
|
|
# emit the bhnd_sprom_offsets for a given variable revision key
|
|
function emit_var_sprom_offsets (v, revk)
|
|
{
|
|
emit(sprintf("{{%u, %u}, (struct bhnd_sprom_offset[]) {\n",
|
|
vars[revk,REV_START],
|
|
vars[revk,REV_END]))
|
|
output_depth++
|
|
|
|
num_offs = vars[revk,REV_NUM_OFFS]
|
|
num_offs_written = 0
|
|
elem_count = 0
|
|
for (offset = 0; offset < num_offs; offset++) {
|
|
offk = subkey(revk, OFF, offset"")
|
|
num_segs = vars[offk,OFF_NUM_SEGS]
|
|
|
|
for (seg = 0; seg < num_segs; seg++) {
|
|
segk = subkey(offk, OFF_SEG, seg"")
|
|
|
|
for (seg_n = 0; seg_n < vars[segk,SEG_COUNT]; seg_n++) {
|
|
seg_addr = vars[segk,SEG_ADDR]
|
|
seg_addr += TSIZE[vars[segk,SEG_TYPE]] * seg_n
|
|
|
|
emit(sprintf("{%s, %s, %s, %s, %s},\n",
|
|
seg_addr,
|
|
(seg > 0) ? "true" : "false",
|
|
DTYPE[vars[segk,SEG_TYPE]],
|
|
vars[segk,SEG_SHIFT],
|
|
vars[segk,SEG_MASK]))
|
|
|
|
num_offs_written++
|
|
}
|
|
}
|
|
}
|
|
|
|
output_depth--
|
|
emit("}, " num_offs_written "},\n")
|
|
}
|
|
|
|
# emit the bhnd_nvram_var definition for variable name `v`
|
|
function emit_var_defn (v)
|
|
{
|
|
emit(sprintf("{\"%s\", %s, %s, %s, (struct bhnd_sprom_var[]) {\n",
|
|
v suffix,
|
|
DTYPE[vars[v,VAR_BASE_TYPE]],
|
|
FMT[vars[v,VAR_FMT]],
|
|
gen_var_flags(v)))
|
|
output_depth++
|
|
|
|
for (rev = 0; rev < vars[v,NUM_REVS]; rev++) {
|
|
revk = subkey(v, REV, rev"")
|
|
emit_var_sprom_offsets(v, revk)
|
|
}
|
|
|
|
output_depth--
|
|
emit("}, " vars[v,NUM_REVS] "},\n")
|
|
}
|
|
|
|
# emit a header name #define for variable `v`
|
|
function emit_var_namedef (v)
|
|
{
|
|
emit("#define\tBHND_NVAR_" toupper(v) "\t\"" v "\"\n")
|
|
}
|
|
|
|
# generate a set of var offset definitions for struct variable `st_vid`
|
|
function gen_struct_var_offsets (vid, revk, st_vid, st_revk, base_addr)
|
|
{
|
|
# Copy all offsets to the new variable
|
|
for (offset = 0; offset < vars[v,REV_NUM_OFFS]; offset++) {
|
|
st_offk = subkey(st_revk, OFF, offset"")
|
|
offk = subkey(revk, OFF, offset"")
|
|
|
|
# Copy all segments to the new variable, applying base
|
|
# address adjustment
|
|
num_segs = vars[st_offk,OFF_NUM_SEGS]
|
|
vars[offk,OFF_NUM_SEGS] = num_segs
|
|
|
|
for (seg = 0; seg < num_segs; seg++) {
|
|
st_segk = subkey(st_offk, OFF_SEG, seg"")
|
|
segk = subkey(offk, OFF_SEG, seg"")
|
|
|
|
vars[segk,SEG_ADDR] = vars[st_segk,SEG_ADDR] + \
|
|
base_addr""
|
|
vars[segk,SEG_COUNT] = vars[st_segk,SEG_COUNT]
|
|
vars[segk,SEG_TYPE] = vars[st_segk,SEG_TYPE]
|
|
vars[segk,SEG_MASK] = vars[st_segk,SEG_MASK]
|
|
vars[segk,SEG_SHIFT] = vars[st_segk,SEG_SHIFT]
|
|
}
|
|
}
|
|
}
|
|
|
|
# generate a complete set of variable definitions for struct variable `st_vid`.
|
|
function gen_struct_vars (st_vid)
|
|
{
|
|
st = vars[st_vid,VAR_STRUCT]
|
|
st_max_off = 0
|
|
|
|
# determine the total number of variables to generate
|
|
for (st_rev = 0; st_rev < structs[st,NUM_REVS]; st_rev++) {
|
|
srevk = subkey(st, REV, st_rev"")
|
|
for (off = 0; off < structs[srevk,REV_NUM_OFFS]; off++) {
|
|
if (off > st_max_off)
|
|
st_max_off = off
|
|
}
|
|
}
|
|
|
|
# generate variable records for each defined struct offset
|
|
for (off = 0; off < st_max_off; off++) {
|
|
# Construct basic variable definition
|
|
v = st_vid off""
|
|
vars[v,VAR_TYPE] = vars[st_vid,VAR_TYPE]
|
|
vars[v,VAR_BASE_TYPE] = vars[st_vid,VAR_BASE_TYPE]
|
|
vars[v,VAR_FMT] = vars[st_vid,VAR_FMT]
|
|
vars[v,VAR_PRIVATE] = vars[st_vid,VAR_PRIVATE]
|
|
vars[v,VAR_ARRAY] = vars[st_vid,VAR_ARRAY]
|
|
vars[v,VAR_IGNALL1] = vars[st_vid,VAR_IGNALL1]
|
|
vars[v,NUM_REVS] = 0
|
|
|
|
# Add to output variable list
|
|
output_vars[num_output_vars++] = v
|
|
|
|
# Construct revision / offset entries
|
|
for (srev = 0; srev < structs[st,NUM_REVS]; srev++) {
|
|
# Struct revision key
|
|
st_revk = subkey(st, REV, srev"")
|
|
|
|
# Skip offsets not defined for this revision
|
|
if (off > structs[st_revk,REV_NUM_OFFS])
|
|
continue
|
|
|
|
# Strut offset key and associated base address */
|
|
offk = subkey(st_revk, OFF, off"")
|
|
base_addr = structs[offk,SEG_ADDR]
|
|
|
|
for (vrev = 0; vrev < vars[st_vid,NUM_REVS]; vrev++) {
|
|
st_var_revk = subkey(st_vid, REV, vrev"")
|
|
v_start = vars[st_var_revk,REV_START]
|
|
v_end = vars[st_var_revk,REV_END]
|
|
s_start = structs[st_revk,REV_START]
|
|
s_end = structs[st_revk,REV_END]
|
|
|
|
# We don't support computing the union
|
|
# of partially overlapping ranges
|
|
if ((v_start < s_start && v_end >= s_start) ||
|
|
(v_start <= s_end && v_end > s_end))
|
|
{
|
|
errorx("partially overlapping " \
|
|
"revision ranges are not supported")
|
|
}
|
|
|
|
# skip variables revs that are not within
|
|
# the struct offset's compatibility range
|
|
if (v_start < s_start || v_start > s_end ||
|
|
v_end < s_start || v_end > s_end)
|
|
continue
|
|
|
|
# Generate the new revision record
|
|
rev = vars[v,NUM_REVS] ""
|
|
revk = subkey(v, REV, rev)
|
|
vars[v,NUM_REVS]++
|
|
|
|
vars[revk,DEF_LINE] = vars[st_revk,DEF_LINE]
|
|
vars[revk,REV_START] = v_start
|
|
vars[revk,REV_END] = v_end
|
|
vars[revk,REV_NUM_OFFS] = \
|
|
vars[st_var_revk,REV_NUM_OFFS]
|
|
|
|
gen_struct_var_offsets(v, revk, st_vid, st_revk,
|
|
base_addr)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
END {
|
|
# Skip completion handling if exiting from an error
|
|
if (_EARLY_EXIT)
|
|
exit 1
|
|
|
|
# Check for complete block closure
|
|
if (depth > 0) {
|
|
block_start = g(STATE_LINENO)
|
|
errorx("missing '}' for block opened on line " block_start "")
|
|
}
|
|
|
|
# Generate concrete variable definitions for all struct variables
|
|
for (v in var_names) {
|
|
if (vars[v,VAR_STRUCT] != null) {
|
|
gen_struct_vars(v)
|
|
} else {
|
|
output_vars[num_output_vars++] = v
|
|
}
|
|
}
|
|
|
|
# Apply lexicographical sorting. To support more effecient table
|
|
# searching, we guarantee a stable sort order (using C collation).
|
|
sort(output_vars)
|
|
|
|
# Truncate output file and write common header
|
|
printf("") > OUTPUT_FILE
|
|
emit("/*\n")
|
|
emit(" * THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.\n")
|
|
emit(" *\n")
|
|
emit(" * generated from nvram map: " FILENAME "\n")
|
|
emit(" */\n")
|
|
emit("\n")
|
|
|
|
# Emit all variable definitions
|
|
if (OUT_T == OUT_T_DATA) {
|
|
emit("#include <dev/bhnd/nvram/nvramvar.h>\n")
|
|
emit("static const struct bhnd_nvram_var bhnd_nvram_vars[] = "\
|
|
"{\n")
|
|
output_depth++
|
|
for (i = 0; i < num_output_vars; i++)
|
|
emit_var_defn(output_vars[i])
|
|
output_depth--
|
|
emit("};\n")
|
|
} else if (OUT_T == OUT_T_HEADER) {
|
|
for (i = 0; i < num_output_vars; i++)
|
|
emit_var_namedef(output_vars[i])
|
|
}
|
|
|
|
printf("%u variable records written to %s\n", num_output_vars,
|
|
OUTPUT_FILE) >> "/dev/stderr"
|
|
}
|
|
|
|
|
|
#
|
|
# Print usage
|
|
#
|
|
function usage ()
|
|
{
|
|
print "usage: bhnd_nvram_map.awk <input map> [-hd] [-o output file]"
|
|
_EARLY_EXIT = 1
|
|
exit 1
|
|
}
|
|
|
|
#
|
|
# Join all array elements with the given separator
|
|
#
|
|
function join (array, sep, count)
|
|
{
|
|
if (count == 0)
|
|
return ("")
|
|
|
|
_result = array[0]
|
|
for (_ji = 1; _ji < count; _ji++)
|
|
_result = _result sep array[_ji]
|
|
|
|
return (_result)
|
|
}
|
|
|
|
#
|
|
# Sort a contiguous integer-indexed array, using standard awk comparison
|
|
# operators over its values.
|
|
#
|
|
function sort (array) {
|
|
# determine array size
|
|
_sort_alen = 0
|
|
|
|
for (_ssort_key in array)
|
|
_sort_alen++
|
|
|
|
if (_sort_alen <= 1)
|
|
return
|
|
|
|
# perform sort
|
|
_qsort(array, 0, _sort_alen-1)
|
|
}
|
|
|
|
function _qsort (array, first, last)
|
|
{
|
|
if (first >= last)
|
|
return
|
|
|
|
# select pivot element
|
|
_qpivot = int(first + int((last-first+1) * rand()))
|
|
_qleft = first
|
|
_qright = last
|
|
|
|
_qpivot_val = array[_qpivot]
|
|
|
|
# partition
|
|
while (_qleft <= _qright) {
|
|
while (array[_qleft] < _qpivot_val)
|
|
_qleft++
|
|
|
|
while (array[_qright] > _qpivot_val)
|
|
_qright--
|
|
|
|
# swap
|
|
if (_qleft <= _qright) {
|
|
_qleft_val = array[_qleft]
|
|
_qright_val = array[_qright]
|
|
|
|
array[_qleft] = _qright_val
|
|
array[_qright] = _qleft_val
|
|
|
|
_qleft++
|
|
_qright--
|
|
}
|
|
}
|
|
|
|
# sort the partitions
|
|
_qsort(array, first, _qright)
|
|
_qsort(array, _qleft, last)
|
|
}
|
|
|
|
#
|
|
# Print msg to output file, without indentation
|
|
#
|
|
function emit_ni (msg)
|
|
{
|
|
printf("%s", msg) >> OUTPUT_FILE
|
|
}
|
|
|
|
#
|
|
# Print msg to output file, indented for the current `output_depth`
|
|
#
|
|
function emit (msg)
|
|
{
|
|
for (_ind = 0; _ind < output_depth; _ind++)
|
|
emit_ni("\t")
|
|
|
|
emit_ni(msg)
|
|
}
|
|
|
|
#
|
|
# Print a warning to stderr
|
|
#
|
|
function warn (msg)
|
|
{
|
|
print "warning:", msg, "at", FILENAME, "line", NR > "/dev/stderr"
|
|
}
|
|
|
|
#
|
|
# Print a compiler error to stderr
|
|
#
|
|
function error (msg)
|
|
{
|
|
errorx(msg " at " FILENAME " line " NR ":\n\t" $0)
|
|
}
|
|
|
|
#
|
|
# Print an error message without including the source line information
|
|
#
|
|
function errorx (msg)
|
|
{
|
|
print "error:", msg > "/dev/stderr"
|
|
_EARLY_EXIT=1
|
|
exit 1
|
|
}
|
|
|
|
#
|
|
# Print a debug output message
|
|
#
|
|
function debug (msg)
|
|
{
|
|
if (!DEBUG)
|
|
return
|
|
for (_di = 0; _di < depth; _di++)
|
|
printf("\t") > "/dev/stderr"
|
|
print msg > "/dev/stderr"
|
|
}
|
|
|
|
#
|
|
# Return an array key composed of the given (parent, selector, child)
|
|
# tuple.
|
|
# The child argument is optional and may be omitted.
|
|
#
|
|
function subkey (parent, selector, child)
|
|
{
|
|
if (child != null)
|
|
return (parent SUBSEP selector SUBSEP child)
|
|
else
|
|
return (parent SUBSEP selector)
|
|
}
|
|
|
|
#
|
|
# Advance to the next non-comment input record
|
|
#
|
|
function next_line ()
|
|
{
|
|
do {
|
|
_result = getline
|
|
} while (_result > 0 && $0 ~ /^[ \t]*#.*/) # skip comment lines
|
|
return (_result)
|
|
}
|
|
|
|
#
|
|
# Advance to the next input record and verify that it matches @p regex
|
|
#
|
|
function getline_matching (regex)
|
|
{
|
|
_result = next_line()
|
|
if (_result <= 0)
|
|
return (_result)
|
|
|
|
if ($0 ~ regex)
|
|
return (1)
|
|
|
|
return (-1)
|
|
}
|
|
|
|
#
|
|
# Shift the current fields left by `n`.
|
|
#
|
|
# If all fields are consumed and the optional do_getline argument is true,
|
|
# read the next line.
|
|
#
|
|
function shiftf (n, do_getline)
|
|
{
|
|
if (n > NF) error("shift past end of line")
|
|
for (_si = 1; _si <= NF-n; _si++) {
|
|
$(_si) = $(_si+n)
|
|
}
|
|
NF = NF - n
|
|
|
|
if (NF == 0 && do_getline)
|
|
next_line()
|
|
}
|
|
|
|
#
|
|
# Parse a revision descriptor from the current line.
|
|
#
|
|
function parse_revdesc (result)
|
|
{
|
|
_rstart = 0
|
|
_rend = 0
|
|
|
|
if ($2 ~ "[0-9]*-[0-9*]") {
|
|
split($2, _revrange, "[ \t]*-[ \t]*")
|
|
_rstart = _revrange[1]
|
|
_rend = _revrange[2]
|
|
} else if ($2 ~ "(>|>=|<|<=)" && $3 ~ "[1-9][0-9]*") {
|
|
if ($2 == ">") {
|
|
_rstart = int($3)+1
|
|
_rend = REV_MAX
|
|
} else if ($2 == ">=") {
|
|
_rstart = int($3)
|
|
_rend = REV_MAX
|
|
} else if ($2 == "<" && int($3) > 0) {
|
|
_rstart = 0
|
|
_rend = int($3)-1
|
|
} else if ($2 == "<=") {
|
|
_rstart = 0
|
|
_rend = int($3)-1
|
|
} else {
|
|
error("invalid revision descriptor")
|
|
}
|
|
} else if ($2 ~ "[1-9][0-9]*") {
|
|
_rstart = int($2)
|
|
_rend = int($2)
|
|
} else {
|
|
error("invalid revision descriptor")
|
|
}
|
|
|
|
result[REV_START] = _rstart
|
|
result[REV_END] = _rend
|
|
}
|
|
|
|
#
|
|
# Push a new parser state.
|
|
#
|
|
# The name may be null, in which case the STATE_IDENT variable will not be
|
|
# defined in this scope
|
|
#
|
|
function push_state (type, name, block) {
|
|
depth++
|
|
push(STATE_LINENO, NR)
|
|
if (name != null)
|
|
push(STATE_IDENT, name)
|
|
push(STATE_TYPE, type)
|
|
push(STATE_ISBLOCK, block)
|
|
}
|
|
|
|
#
|
|
# Pop the top of the parser state stack.
|
|
#
|
|
function pop_state () {
|
|
# drop all symbols defined at this depth
|
|
for (s in symbols) {
|
|
if (s ~ "^"depth"[^0-9]")
|
|
delete symbols[s]
|
|
}
|
|
depth--
|
|
}
|
|
|
|
#
|
|
# Find opening brace and push a new parser state for a brace-delimited block.
|
|
#
|
|
# The name may be null, in which case the STATE_IDENT variable will not be
|
|
# defined in this scope
|
|
#
|
|
function open_block (type, name)
|
|
{
|
|
if ($0 ~ "{" || getline_matching("^[ \t]*{") > 0) {
|
|
push_state(type, name, 1)
|
|
sub("^[^{]+{", "", $0)
|
|
return
|
|
}
|
|
|
|
error("found '"$1 "' instead of expected '{' for '" name "'")
|
|
}
|
|
|
|
#
|
|
# Find closing brace and pop parser states until the first
|
|
# brace-delimited block is discarded.
|
|
#
|
|
function close_block ()
|
|
{
|
|
if ($0 !~ "}")
|
|
error("internal error - no closing brace")
|
|
|
|
# pop states until we exit the first enclosing block
|
|
do {
|
|
_closed_block = g(STATE_ISBLOCK)
|
|
pop_state()
|
|
} while (!_closed_block)
|
|
|
|
# strip everything prior to the block closure
|
|
sub("^[^}]*}", "", $0)
|
|
}
|
|
|
|
# Internal symbol table lookup function. Returns the symbol depth if
|
|
# name is found at or above scope; if scope is null, it defauls to 0
|
|
function _find_sym (name, scope)
|
|
{
|
|
if (scope == null)
|
|
scope = 0;
|
|
|
|
for (i = scope; i < depth; i++) {
|
|
if ((depth-i,name) in symbols)
|
|
return (depth-i)
|
|
}
|
|
|
|
return (-1)
|
|
}
|
|
|
|
#
|
|
# Look up a variable in the symbol table with `name` and return its value.
|
|
#
|
|
# If `scope` is not null, the variable search will start at the provided
|
|
# scope level -- 0 is the current scope, 1 is the parent's scope, etc.
|
|
#
|
|
function g (name, scope)
|
|
{
|
|
_g_depth = _find_sym(name, scope)
|
|
if (_g_depth < 0)
|
|
error("'" name "' is undefined")
|
|
|
|
return (symbols[_g_depth,name])
|
|
}
|
|
|
|
function is_defined (name, scope)
|
|
{
|
|
return (_find_sym(name, scope) >= 0)
|
|
}
|
|
|
|
# Define a new variable in the symbol table's current scope,
|
|
# with the given value
|
|
function push (name, value)
|
|
{
|
|
symbols[depth,name] = value
|
|
}
|
|
|
|
# Set an existing variable's value in the symbol table; if not yet defined,
|
|
# will trigger an error
|
|
function set (name, value, scope)
|
|
{
|
|
for (i = 0; i < depth; i++) {
|
|
if ((depth-i,name) in symbols) {
|
|
symbols[depth-i,name] = value
|
|
return
|
|
}
|
|
}
|
|
# No existing value, cannot define
|
|
error("'" name "' is undefined")
|
|
}
|
|
|
|
# Evaluates to true if immediately within a block scope of the given type
|
|
function in_state (type)
|
|
{
|
|
if (!is_defined(STATE_TYPE))
|
|
return (type == ST_NONE)
|
|
|
|
return (type == g(STATE_TYPE))
|
|
}
|
|
|
|
# Evaluates to true if within an immediate or non-immediate block scope of the
|
|
# given type
|
|
function in_nested_state (type)
|
|
{
|
|
for (i = 0; i < depth; i++) {
|
|
if ((depth-i,STATE_TYPE) in symbols) {
|
|
if (symbols[depth-i,STATE_TYPE] == type)
|
|
return (1)
|
|
}
|
|
}
|
|
return (0)
|
|
}
|
|
|
|
# Evaluates to true if definitions of the given type are permitted within
|
|
# the current scope
|
|
function allow_def (type)
|
|
{
|
|
if (type == ST_VAR_BLOCK) {
|
|
return (in_state(ST_NONE) || in_state(ST_STRUCT_BLOCK))
|
|
} else if (type == ST_STRUCT_BLOCK) {
|
|
return (in_state(ST_NONE))
|
|
} else if (type == ST_SROM_DEFN) {
|
|
return (in_state(ST_VAR_BLOCK) || in_state(ST_STRUCT_BLOCK))
|
|
}
|
|
|
|
error("unknown type '" type "'")
|
|
}
|
|
|
|
# struct definition
|
|
$1 == ST_STRUCT_BLOCK && allow_def($1) {
|
|
name = $2
|
|
|
|
# Remove array[] specifier
|
|
if (sub(/\[\]$/, "", name) == 0)
|
|
error("expected '" name "[]', not '" name "'")
|
|
|
|
if (name !~ IDENT_REGEX || name ~ TYPES_REGEX)
|
|
error("invalid identifier '" name "'")
|
|
|
|
# Add top-level struct entry
|
|
if ((name,DEF_LINE) in structs)
|
|
error("struct identifier '" name "' previously defined on " \
|
|
"line " structs[name,DEF_LINE])
|
|
structs[name,DEF_LINE] = NR
|
|
structs[name,NUM_REVS] = 0
|
|
|
|
# Open the block
|
|
debug("struct " name " {")
|
|
open_block(ST_STRUCT_BLOCK, name)
|
|
}
|
|
|
|
# struct srom descriptor
|
|
$1 == ST_SROM_DEFN && allow_def(ST_SROM_DEFN) && in_state(ST_STRUCT_BLOCK) {
|
|
sid = g(STATE_IDENT)
|
|
|
|
# parse revision descriptor
|
|
rev_desc[REV_START] = 0
|
|
parse_revdesc(rev_desc)
|
|
|
|
# assign revision id
|
|
rev = structs[sid,NUM_REVS] ""
|
|
revk = subkey(sid, REV, rev)
|
|
structs[sid,NUM_REVS]++
|
|
|
|
# init basic revision state
|
|
structs[revk,REV_START] = rev_desc[REV_START]
|
|
structs[revk,REV_END] = rev_desc[REV_END]
|
|
|
|
if (match($0, "\\[[^]]*\\]") <= 0)
|
|
error("expected base address array")
|
|
|
|
addrs_str = substr($0, RSTART+1, RLENGTH-2)
|
|
num_offs = split(addrs_str, addrs, ",[ \t]*")
|
|
structs[revk, REV_NUM_OFFS] = num_offs
|
|
for (i = 1; i <= num_offs; i++) {
|
|
offk = subkey(revk, OFF, (i-1) "")
|
|
|
|
if (addrs[i] !~ HEX_REGEX)
|
|
error("invalid base address '" addrs[i] "'")
|
|
|
|
structs[offk,SEG_ADDR] = addrs[i]
|
|
}
|
|
|
|
debug("struct_srom " structs[revk,REV_START] "... [" addrs_str "]")
|
|
next
|
|
}
|
|
|
|
# close any previous srom revision descriptor
|
|
$1 == ST_SROM_DEFN && in_state(ST_SROM_DEFN) {
|
|
pop_state()
|
|
}
|
|
|
|
# open a new srom revision descriptor
|
|
$1 == ST_SROM_DEFN && allow_def(ST_SROM_DEFN) {
|
|
# parse revision descriptor
|
|
parse_revdesc(rev_desc)
|
|
|
|
# assign revision id
|
|
vid = g(STATE_IDENT)
|
|
rev = vars[vid,NUM_REVS] ""
|
|
revk = subkey(vid, REV, rev)
|
|
vars[vid,NUM_REVS]++
|
|
|
|
# vend scoped rev/revk variables for use in the
|
|
# revision offset block
|
|
push("rev_id", rev)
|
|
push("rev_key", revk)
|
|
|
|
# init basic revision state
|
|
vars[revk,DEF_LINE] = NR
|
|
vars[revk,REV_START] = rev_desc[REV_START]
|
|
vars[revk,REV_END] = rev_desc[REV_END]
|
|
vars[revk,REV_NUM_OFFS] = 0
|
|
|
|
debug("srom " rev_desc[REV_START] "-" rev_desc[REV_END] " {")
|
|
push_state(ST_SROM_DEFN, null, 0)
|
|
|
|
# seek to the first offset definition
|
|
do {
|
|
shiftf(1)
|
|
} while ($1 !~ SROM_OFF_REGEX && NF > 0)
|
|
}
|
|
|
|
#
|
|
# Extract and return the array length from the given type string.
|
|
# Returns -1 if the type is not an array.
|
|
#
|
|
function type_array_len (type)
|
|
{
|
|
# extract byte count[] and width
|
|
if (match(type, ARRAY_REGEX"$") > 0) {
|
|
return (substr(type, RSTART+1, RLENGTH-2))
|
|
} else {
|
|
return (-1)
|
|
}
|
|
}
|
|
|
|
#
|
|
# Parse an offset declaration from the current line.
|
|
#
|
|
function parse_offset_segment (revk, offk)
|
|
{
|
|
vid = g(STATE_IDENT)
|
|
|
|
# use explicit type if specified, otherwise use the variable's
|
|
# common type
|
|
if ($1 !~ HEX_REGEX) {
|
|
type = $1
|
|
if (type !~ TYPES_REGEX)
|
|
error("unknown field type '" type "'")
|
|
|
|
shiftf(1)
|
|
} else {
|
|
type = vars[vid,VAR_TYPE]
|
|
}
|
|
|
|
# read offset value
|
|
offset = $1
|
|
if (offset !~ HEX_REGEX)
|
|
error("invalid offset value '" offset "'")
|
|
|
|
# extract byte count[], base type, and width
|
|
if (match(type, ARRAY_REGEX"$") > 0) {
|
|
count = int(substr(type, RSTART+1, RLENGTH-2))
|
|
type = substr(type, 1, RSTART-1)
|
|
} else {
|
|
count = 1
|
|
}
|
|
width = TSIZE[type]
|
|
|
|
# seek to attributes or end of the offset expr
|
|
sub("^[^,(|){}]+", "", $0)
|
|
|
|
# parse attributes
|
|
mask=TMASK[type]
|
|
shift=0
|
|
|
|
if ($1 ~ "^\\(") {
|
|
# extract attribute list
|
|
if (match($0, "\\([^|\(\)]*\\)") <= 0)
|
|
error("expected attribute list")
|
|
attr_str = substr($0, RSTART+1, RLENGTH-2)
|
|
|
|
# drop from input line
|
|
$0 = substr($0, RSTART+RLENGTH, length($0) - RSTART+RLENGTH)
|
|
|
|
# parse attributes
|
|
num_attr = split(attr_str, attrs, ",[ \t]*")
|
|
for (i = 1; i <= num_attr; i++) {
|
|
attr = attrs[i]
|
|
if (sub("^&[ \t]*", "", attr) > 0) {
|
|
mask = attr
|
|
} else if (sub("^<<[ \t]*", "", attr) > 0) {
|
|
shift = "-"attr
|
|
} else if (sub("^>>[ \t]*", "", attr) > 0) {
|
|
shift = attr
|
|
} else {
|
|
error("unknown attribute '" attr "'")
|
|
}
|
|
}
|
|
}
|
|
|
|
# assign segment id
|
|
seg = vars[offk,OFF_NUM_SEGS] ""
|
|
segk = subkey(offk, OFF_SEG, seg)
|
|
vars[offk,OFF_NUM_SEGS]++
|
|
|
|
vars[segk,SEG_ADDR] = offset + (width * _oi)
|
|
vars[segk,SEG_COUNT] = count
|
|
vars[segk,SEG_TYPE] = type
|
|
vars[segk,SEG_MASK] = mask
|
|
vars[segk,SEG_SHIFT] = shift
|
|
|
|
debug("{"vars[segk,SEG_ADDR]", "type", "mask", "shift"}" \
|
|
_comma)
|
|
}
|
|
|
|
# revision offset definition
|
|
$1 ~ SROM_OFF_REGEX && in_state(ST_SROM_DEFN) {
|
|
vid = g(STATE_IDENT)
|
|
|
|
# fetch rev id/key defined by our parent block
|
|
rev = g("rev_id")
|
|
revk = g("rev_key")
|
|
|
|
# parse all offsets
|
|
do {
|
|
# assign offset id
|
|
off = vars[revk,REV_NUM_OFFS] ""
|
|
offk = subkey(revk, OFF, off)
|
|
vars[revk,REV_NUM_OFFS]++
|
|
|
|
# initialize segment count
|
|
vars[offk,DEF_LINE] = NR
|
|
vars[offk,OFF_NUM_SEGS] = 0
|
|
|
|
debug("[")
|
|
# parse all segments
|
|
do {
|
|
parse_offset_segment(revk, offk)
|
|
_more_seg = ($1 == "|")
|
|
if (_more_seg)
|
|
shiftf(1, 1)
|
|
} while (_more_seg)
|
|
debug("],")
|
|
_more_vals = ($1 == ",")
|
|
if (_more_vals)
|
|
shiftf(1, 1)
|
|
} while (_more_vals)
|
|
}
|
|
|
|
# variable definition
|
|
(($1 == "private" && $2 ~ TYPES_REGEX) || $1 ~ TYPES_REGEX) &&
|
|
allow_def(ST_VAR_BLOCK) \
|
|
{
|
|
# check for 'private' flag
|
|
if ($1 == "private") {
|
|
private = 1
|
|
shiftf(1)
|
|
} else {
|
|
private = 0
|
|
}
|
|
|
|
type = $1
|
|
name = $2
|
|
array = 0
|
|
debug(type " " name " {")
|
|
|
|
# Check for and remove any array[] specifier
|
|
base_type = type
|
|
if (sub(ARRAY_REGEX"$", "", base_type) > 0)
|
|
array = 1
|
|
|
|
# verify type
|
|
if (!base_type in DTYPE)
|
|
error("unknown type '" $1 "'")
|
|
|
|
# Add top-level variable entry
|
|
if (name in var_names)
|
|
error("variable identifier '" name "' previously defined on " \
|
|
"line " vars[name,DEF_LINE])
|
|
|
|
var_names[name] = 0
|
|
vars[name,VAR_NAME] = name
|
|
vars[name,DEF_LINE] = NR
|
|
vars[name,VAR_TYPE] = type
|
|
vars[name,VAR_BASE_TYPE] = base_type
|
|
vars[name,NUM_REVS] = 0
|
|
vars[name,VAR_PRIVATE] = private
|
|
vars[name,VAR_ARRAY] = array
|
|
vars[name,VAR_FMT] = "hex" # default if not specified
|
|
|
|
open_block(ST_VAR_BLOCK, name)
|
|
|
|
debug("type=" DTYPE[base_type])
|
|
|
|
if (in_nested_state(ST_STRUCT_BLOCK)) {
|
|
# Fetch the enclosing struct's name
|
|
sid = g(STATE_IDENT, 1)
|
|
|
|
# Mark as a struct-based variable
|
|
vars[name,VAR_STRUCT] = sid
|
|
}
|
|
}
|
|
|
|
# variable parameters
|
|
$1 ~ IDENT_REGEX && $2 ~ IDENT_REGEX && in_state(ST_VAR_BLOCK) {
|
|
vid = g(STATE_IDENT)
|
|
if ($1 == PROP_T_SFMT) {
|
|
if (!$2 in FMT)
|
|
error("invalid fmt '" $2 "'")
|
|
|
|
vars[vid,VAR_FMT] = $2
|
|
debug($1 "=" FMT[$2])
|
|
} else if ($1 == PROP_T_ALL1 && $2 == "ignore") {
|
|
vars[vid,VAR_IGNALL1] = 1
|
|
} else {
|
|
error("unknown parameter " $1)
|
|
}
|
|
next
|
|
}
|
|
|
|
# Skip comments and blank lines
|
|
/^[ \t]*#/ || /^$/ {
|
|
next
|
|
}
|
|
|
|
# Close blocks
|
|
/}/ && !in_state(ST_NONE) {
|
|
while (!in_state(ST_NONE) && $0 ~ "}") {
|
|
close_block();
|
|
debug("}")
|
|
}
|
|
next
|
|
}
|
|
|
|
# Report unbalanced '}'
|
|
/}/ && in_state(ST_NONE) {
|
|
error("extra '}'")
|
|
}
|
|
|
|
# Invalid variable type
|
|
$1 && allow_def(ST_VAR_BLOCK) {
|
|
error("unknown type '" $1 "'")
|
|
}
|
|
|
|
# Generic parse failure
|
|
{
|
|
error("unrecognized statement")
|
|
}
|