#!/bin/bash

set -e
set -o pipefail

unset dts
unset SED

while [ "$1" != "" ]
do
    case "$1"
    in
    --dts)      dts="$2";       shift 2;;
    *) echo "$0: Unknown argument $1"; exit 1;;
    esac
done

if [ "${dts}" == "" ]
then
    echo "$0: please provide '--dts' option" >&2
    exit 1
fi

# When installed on MacOS with Homebrew, GNU sed is named gsed.
# If gsed exists, use it.
if [ "`which gsed`" == "" ]; then
    SED=sed
else
    SED=gsed
fi

# Add a PMP node if it doesn't exist

if [ `grep -c 'riscv,pmp' ${dts}` -eq 0 ]; then

    echo "$0: PMP node not found in ${dts}."

    # Check for targets without PMP support

    TARGET=`echo ${dts} | cut -d '/' -f 1`
    if [ $TARGET != "freedom-e310-arty" -a \
         $TARGET != "sifive-hifive1" -a \
         $TARGET != "coreip-e20-rtl" -a \
         $TARGET != "coreip-e20-arty" ]; then

        ${SED} -i '/riscv,isa/a riscv,pmpregions = <1>;' ${dts}

        echo -e "$0: \tAdded pmp@0"
    fi
fi

# Add numintbits for the clic node if it doesn't exist

if [ `grep -c 'sifive,clic0' ${dts}` -ne 0 ]; then
    if [ `grep -c 'sifive,numintbits' ${dts}` -eq 0 ]; then
        echo "$0: Clic missing numintbits in  ${dts}."

        ${SED} -i 's/interrupt-controller@2000000 {/interrupt-controller@2000000 {\n\t\t\tsifive,numintbits = <2>;/g' ${dts}

        echo -e "$0: \tAdded numintbits to clic."
    fi
fi

# Add compat string for the global-interrupt node if it doesn't exist

if [ `grep -c 'global-external-interrupts {' ${dts}` -ne 0 ]; then
    if [ `grep -c 'sifive,global-external-interrupts0' ${dts}` -eq 0 ]; then
        echo "$0: Global external interrupts missing compat string in ${dts}."

        ${SED} -i 's/global-external-interrupts {/global-external-interrupts {\n\t\t\tcompatible = "sifive,global-external-interrupts0";/g' ${dts}

        echo -e "$0: \tAdded compat string to global-external-interrupts."
    fi
fi

# Add compat string for the local-interrupt node if it doesn't exist

if [ `grep -c 'local-external-interrupts-0 {' ${dts}` -ne 0 ]; then
    if [ `grep -c 'sifive,local-external-interrupts0' ${dts}` -eq 0 ]; then
        echo "$0: Local external interrupts missing compat string in ${dts}."

        ${SED} -i 's/local-external-interrupts-0 {/local-external-interrupts {\n\t\t\tcompatible = "sifive,local-external-interrupts0";/g' ${dts}

        echo -e "$0: \tAdded compat string to local-external-interrupts-0."
    fi
fi

# Add a stdout-path to the chosen node if one doesn't exist and a serial port does

if [ `grep -c 'stdout-path' ${dts}` -eq 0 ]; then
    if [ `grep -c 'sifive,uart0' ${dts}` -ne 0 ]; then
        echo "$0: stdout-path property not given, but a UART device exists."

        serial_node=`grep -oP "serial@\d+" ${dts} | sort | uniq | head -n 1`
        serial_path="/soc/${serial_node}:115200"

        if [ `grep -c 'chosen' ${dts}` -eq 0 ]; then
            ${SED} -i "/cpus/i chosen {\n};" ${dts}
        fi

        ${SED} -i "/chosen/a stdout-path=\"${serial_path}\";" ${dts}

        echo -e "$0: \tAdded stdout-path ${serial_path}"
    fi
fi


# FPGA targets need a 0x400000 byte offset into their SPI flash

# Is the target an FPGA?
if [ `echo ${dts} | grep -c 'arty'` -ne 0 -o `echo ${dts} | grep -c 'fpga'` -ne 0 ] ; then
    # Is there a metal,entry property in the chosen node?
    if [ `grep -c 'metal,entry' ${dts}` -eq 0 ] ; then
        echo "$0: FPGA target needs metal,entry property to set boot offset."

        # Find the SPI device with the lowest base address
        first_spi_node=`grep -oE "spi@[[:xdigit:]]+?" ${dts} | sort | head -n 1`

        # Get the DTS node label of that SPI device
        spi_entry_label=`grep -oE "[^[:space:]]+?:\s+?${first_spi_node}" ${dts} | cut -d ':' -f 1`

        if [ `grep -c 'chosen' ${dts}` -eq 0 ]; then
            ${SED} -i "/cpus/i chosen {\n};" ${dts}
        fi

        ${SED} -i "/chosen/a metal,entry = <&${spi_entry_label} 0x400000>;" ${dts}

        echo -e "$0: \tAdded metal,entry for SPI node ${first_spi_node} with label ${spi_entry_label}"
    fi
fi


# Add a test memory node if one doesn't exist

if [ `grep -c 'sifive,testram0' ${dts}` -eq 0 ]; then

    # bullet and U cores have a memory defined already
    if [ `grep -c 'sifive,bullet0' ${dts}` -eq 0 -a `grep -c 'SiFive,FU' ${dts}` -eq 0 ]; then

        echo "$0: Test memory node not found in ${dts}."

        # The heuristic for determining which memory address contains the
        # program code loaded by the RTL testbench is determined by taking
        # the design ports and sorting them in order of (periph, sys, mem),
        # and then lexicographically by protocol.

        port_types="periph sys mem"
        protocols="ahb axi4 tl"

        for port_type in ${port_types}; do
            for protocol in ${protocols}; do

                # Check if the port exists
                if [ `grep -c "${protocol}-${port_type}-port" ${dts}` -ne 0 ]; then
                    
                    # Build the node name
                    port_node_name=`egrep -o "${protocol}-${port_type}-port@[a-fA-F0-9]+" ${dts} | head -n 1`
                    echo -e "$0: \tUsing node \t${port_node_name}"

                    # Get the address and size cells
                    address_cells=`cat ${dts} | tr -d '\n\t' | grep -oP "${port_node_name}.*?address-cells = <\K(\d+)"`
                    echo -e "$0: \tAddress cells \t${address_cells}"
                    size_cells=`cat ${dts} | tr -d '\n\t' | grep -oP "${port_node_name}.*?size-cells = <\K(\d+)"`
                    echo -e "$0: \tSize cells \t${size_cells}"

                    # Get the base address and size
                    if [ ${address_cells} -eq 1 -a ${size_cells} -eq 1 ]; then
                        address_and_size=(`cat ${dts} | tr -d '\n\t' | grep -oP "${port_node_name}.*?ranges = <0x[[:xdigit:]]+ \K(0x[[:xdigit:]]+ 0x[[:xdigit:]]+)"`)
                        base_address=${address_and_size[0]}
                        size=${address_and_size[1]}
                    elif [ ${address_cells} -eq 1 -a ${size_cells} -eq 2 ]; then
                        address_and_size=(`cat ${dts} | tr -d '\n\t' | grep -oP "${port_node_name}.*?ranges = <0x[[:xdigit:]]+ \K(0x[[:xdigit:]]+ 0x[[:xdigit:]]+ 0x[[:xdigit:]]+)"`)
                        base_address=${address_and_size[0]}
                        size="${address_and_size[1]} ${address_and_size[2]}"
                    elif [ ${address_cells} -eq 2 -a ${size_cells} -eq 1 ]; then
                        address_and_size=(`cat ${dts} | tr -d '\n\t' | grep -oP "${port_node_name}.*?ranges = <0x[[:xdigit:]]+ 0x[[:xdigit:]]+ \K(0x[[:xdigit:]]+ 0x[[:xdigit:]]+ 0x[[:xdigit:]]+)"`)
                        base_address="${address_and_size[0]} ${address_and_size[1]}"
                        size=${address_and_size[2]}
                    elif [ ${address_cells} -eq 2 -a ${size_cells} -eq 2 ]; then
                        address_and_size=(`cat ${dts} | tr -d '\n\t' | grep -oP "${port_node_name}.*?ranges = <0x[[:xdigit:]]+ 0x[[:xdigit:]]+ \K(0x[[:xdigit:]]+ 0x[[:xdigit:]]+ 0x[[:xdigit:]]+ 0x[[:xdigit:]]+)"`)
                        base_address="${address_and_size[0]} ${address_and_size[1]}"
                        size="${address_and_size[2]} ${address_and_size[3]}"
                    fi
                    echo -e "$0: \tBase addr \t${base_address}"
                    echo -e "$0: \tSize \t\t${size}"

                    # Build the name of the testram node
                    if [ "${address_and_size[0]}" == "0x0" ]; then
                        node_name_addr=`echo ${address_and_size[1]} | cut -c 3-`
                    else
                        node_name_addr=`echo ${address_and_size[0]} | cut -c 3-`
                    fi

                    # Determine word size from ISA bitness
                    if [ `grep -c 'riscv,isa = "rv32' ${dts}` -ne 0 ]; then
                        word_size=4
                    else
                        word_size=8
                    fi
                    echo -e "$0: \tWord size \t${word_size}"

                    # Create the test memory
                    ${SED} -i "s/ranges;/ranges;\n\t\ttest_memory: testram@${node_name_addr} {\n\t\t\tcompatible = \"sifive,testram0\";\n\t\t\treg = <${base_address} ${size}>;\n\t\t\treg-names = \"mem\";\n\t\t\tword-size-bytes = <${word_size}>;\n\t\t};/" ${dts}
                    echo -e "$0: \tAdded testram@${node_name_addr}"

                    # Break out of both loops
                    break 2
                fi
            done
        done
    fi
fi