#!/bin/bash

# This script is used to setup a build environment and a configuration file
# from the command line options for mklinuximg build scripts. The result
# will be a RAM image of Linux that can boot on a SPARC/LEON with MMU.
#

usage() {
    echo "Usage:"
    echo "$0 <linux-image> <out-file> [optional parameters]..."
    echo "  <linux-image> linux image, for Linux 3.4 and older <linux-dir>/arch/sparc/boot/image."
    echo "                and for Linux 3.5 and newer <linux-dir>/vmlinux."
    echo "  <out-file>    output image that can be uploaded using GRMON or run in TSIM."
    echo ""
    echo " optional parameters:"
    echo "  -base <baseaddr>  optional baseaddress. The default is 0x40000000."
    echo "  -cmdline <string> kernel parameter string. Default is \"console=ttyS0,38400\"."
    echo "  -freq <frequency> optional frequency parameter in case if it cannot be retrieved from the Timer scaler."
    echo "  -amp <string>     optional string of format <idx0>=<val0>:<idx1>=<val1>:... that sets for core index"
    echo "                    n property ampopts to value n. Example 0=4:1=6 will set core index 0's ampopts to 4"
    echo "                    and core index 1's ampopts to 6."
    echo "  -ethmac <string>  set the ethernet mac address as 12 dgt hex. Default: 00007ccc0145"
    echo "  -ipi <irq_num>    IRQ number used by Linux SMP for IPIs. May not be shared. Allowed values: 1..14"
    echo "  -ioarea <ioarea>  GRLIB AMBA Plug&Play I/O AREA Base address. Defaults to 0xfff00000"
    echo "  -uartidx <index>  Select UART by index for PROM Console. Default 0 (first UART0)"
    echo "  -maxcpu <num>     Disable/Limit number of Linux CPUs 1..8 (default 8)"
    echo "  -mcpu <name>      Use -mcpu=<name> instead of the default -mcpu=leon3."
    echo "                    Specify '-' to not pass on any -mcpu, getting the default of the toolchain."
    echo "  -wakecpus <mask>  Wake the disabled/limited CPUs in bitmask, above the ones specified by -maxcpu."
    echo "  -wakediscpus      Wake all the disabled/limited CPUs above the ones specified by -maxcpu."
    echo "  --version         Print version of mklinuximg"
    echo "  -xml <file>       Specify an xml file that adds extra properties and nodes to the device tree."
    echo "  -flat             Create a flat device-tree."
    echo "  -grgpio-noprobe   Do not probe grgpio cores (otherwise mklinuximg probes grgpio cores for info)."
    echo "  -grgpio-nbits <comma-separated-list>"
    echo "                    Provide number of lines for grgpio cores (in scan order)."
    echo "  -grgpio-imask <comma-separated-list-of-bitmasks>"
    echo "                    Provide the imask generic (which gpio lines can generate interrupts)"
    echo "                    for grgpio cores (in scan order)."
    echo "  -grgpio-irqgen <comma-separated-list>"
    echo "                    Provide the irqgen generic for grgpio cores (in scan order)."
    echo "  -cflags <string>  Add compilation flags. Can be used multiple times."
    echo "  -sym <filename>   Export debug information for mklinuximg to file."
    echo "  -strip            Strip output image of symbols and debug information."
    echo "  -startupheap <sz> Set startup heap size in bytes. Memory later reclaimed by the kernel."
    echo "  -promheap <sz>    Set prom heap size in bytes. Never reclaimed by the kernel."
    echo "  -promstack <sz>   Set prom and startup stack size in bytes. Never reclaimed by the kernel."
    echo "  -watchdog         Enables and adds device tree information for the GPTIMER watchdog timer."
    echo "                    If a GRWATCHDOG core is found, MKLINXUXIMG will handle the challenge/response"
    echo "                    protocol during boot but not add any device tree information"
    echo "  -watchdog-user-timeout <timeout_sec>"
    echo "                    Set the user-timeout for the GPTIMER watchdog which overrides the"
    echo "                    default timeout (30 seconds)."
    echo "  -clockgate        Enables clock gating related device tree additions for supported components."
    echo "  -pinmux           Removes cores from the device tree that cannot be routed due to"
    echo "                    pin multiplexing settings detected on boot."
    echo "  -hrtimers         Provide device tree information for using high resolution timers in Linux."
    echo "  -dbg-uart <addr>  Set address to APBUART0, for use before AMBA Plug&Play scan (default: 0x80000100)."

#    echo "  -maxcpu <max_num> Maximum CPUs in the system [1..8]"

    exit 1;
}

version() {
    grep VERSION $prefix/VERSION
    exit 1;
}

failexit() {
    if [ "$isdump" == "0" ]; then
	rm -rf $tmpdir
    fi
    exit 1
}

prefix=`dirname $(readlink -f $0)`/

xml=;
nolin=0;ioarea="";uartidx="0";
isdump=0;isdbg=0;istest=0; ofile=""; ldir=""; baddr="0x40000000"; freq="0"; cmdline="console=ttyS0,38400"; amp=""; smp=""; ipi="0";
ethmac="00007ccc0145";
max_cpus="8"
wake_cpus=""
wake_dis_cpus="0"
macrostr=""
grgpio_probe=1
symfile=""
strip=""
mcpu="leon3"
watchdog=0
watchdog_user_timeout=0
clkgate=0
pinmux=0
hrtimers=0
dbg_uart=0
max_apb_buses=0
max_ahb_buses=0
while [ $# -ne 0 ]
do
    case "$1" in
	#-o) shift; ofile="$1";
	-dump) isdump=1;;
	-d) isdbg=1; macrostr="${macrostr} -O0";;
	-nolin) nolin=1;;
	-t) istest=1;;
	-freq) shift; freq="$1";;
	-flat) macrostr="${macrostr} -DFLATOFTREE ";;
	-cmdline) shift; cmdline="$1";;
	-base) shift; baddr="$1";;
	-ethmac) shift; ethmac="$1";;
	-amp) shift; amp="$1";;
	-xml) shift; xml="$1";;
	-smp) ;; # ignore deprecated SMP flag, is autodetected
	-ipi) shift; ipi="$1";;
	-ioarea) shift; ioarea="$1";;
	-uartidx) shift; uartidx="$1";;
	-maxcpu) shift; max_cpus="$1";;
	-wakediscpus) wake_dis_cpus=1;;
	-wakecpus) shift; wake_cpus="$1";;
	--version) version ;;
	-grgpio-noprobe) grgpio_probe=0 ;;
	-grgpio-nbits) shift; grgpio_nbits="$1" ;;
	-grgpio-imask) shift; grgpio_imask="$1" ;;
	-grgpio-irqgen) shift; grgpio_irqgen="$1" ;;
	-cflags) shift; macrostr="${macrostr} $1" ;;
	-sym) shift; symfile="$1" ;;
	-strip) strip=1 ;;
	-mcpu) shift; mcpu="$1" ;;
	-startupheap) shift; startupheap="$1" ;;
	-promheap) shift; promheap="$1" ;;
	-promstack) shift; promstack="$1" ;;
	-watchdog) watchdog=1;;
	-watchdog-user-timeout) shift; watchdog_user_timeout="$1" ;;
	-clockgate) clkgate=1 ;;
	-pinmux) pinmux=1 ;;
	-hrtimers) hrtimers=1 ;;
	-dbg-uart) shift; dbg_uart="$1" ;;
	-max-apb-buses) shift; max_apb_buses="$1" ;;
	-max-ahb-buses) shift; max_ahb_buses="$1" ;;

	*) a[${#a[*]}]="$1";;
    esac
    shift
done

if [ "${#a[*]}" != "2" ]; then
    usage
fi
limg=`readlink -f ${a[0]}`
o=${a[1]}


# Set up and check that we have a toolchain.
# Use CROSS_COMPILE from environment if available.
if [ -z "$CROSS_COMPILE" ]; then
    export CROSS_COMPILE=sparc-linux-
fi
OBJDUMP=${CROSS_COMPILE}objdump
READELF=${CROSS_COMPILE}readelf
if ! $OBJDUMP --version &> /dev/null; then
    echo >&2 "ERROR: Toolchain ${CROSS_COMPILE} not functioning or not in PATH"
    echo >&2 ""
    exit 1
fi

# Setup build environment
tmpdir=`mktemp -d`
#tmpdir="/tmp/mklinuximg"
mkdir -p $tmpdir/include
cfg="$tmpdir/include/config_auto.h"
ampoptfile="$tmpdir/include/config_ampopts.h"
xmlo="xml.c"

# Check that the specified linux image exists and is a readable ELF file,
# then check that it is a regular 32-bit SPARC ELF file (not even v8+ or v9).
if ! $READELF -s $limg > /dev/null ; then
    exit 1
else
    limg_machine=$($READELF -h $limg | sed -n 's/^ *Machine: *//p')
    if [ "$limg_machine" != "Sparc" ]; then
	echo "ERROR: $limg is an ELF file for \"$limg_machine\", expected \"Sparc\"" >&2
	exit 1
    fi
fi

# Autodetect if SMP image (if symbol trapbase_cpu0 exists)
smp_img="NO"
if $READELF -s $limg | grep trapbase_cpu0 > /dev/null ; then
	smp_img="YES"
	smp="1"
fi


# Autodetect Linux version from Linux header in head_32.S
hdr_base_adr=`$OBJDUMP -x $limg | grep -E ' root_flags$' | sed 's/[0-9a-fA-F][ ].*$//g'`
hdr_base_adr=`echo ${hdr_base_adr}0`
linux_ver=`$READELF -x 1 $limg | grep -E "^  0x${hdr_base_adr}" | cut -d' ' -f5`

# Autodetect Linux configuration options
linux_has_gptimer_drv=`$OBJDUMP -x $limg | grep -E ' timer-gptimer\.c$'`
linux_has_leon_cs_drv=`$OBJDUMP -x $limg | grep -E ' timer-leon\.c$'`

# Find Linux BSS in Virtual Addresses
bss_addr=0x`$OBJDUMP -h $limg | grep \\\\.bss\\[\\[:space:\\]\\] | awk '{ print $4 }' `;
bss_size=0x`$OBJDUMP -h $limg | grep \\\\.bss\\[\\[:space:\\]\\] | awk '{ print $3 }' `;
bss_end=$((($bss_addr+$bss_size)))

# Waking other CPUs
wakeable_cpus="(0xffff & ~((1 << $max_cpus) - 1))"
if [ -n "$wake_cpus" ]; then
    wake_cpu_mask_expr="($wakeable_cpus & $wake_cpus)"
elif [ $wake_dis_cpus -eq 1 ]; then
    wake_cpu_mask_expr="$wakeable_cpus";
else
    wake_cpu_mask_expr=0
fi
wake_cpu_mask=$(printf "0x%x" $(($wake_cpu_mask_expr)))

# Print settings
echo "======== DETECTED SETTINGS ========"
echo "Linux version :" $linux_ver
echo "Linux-img     :" $limg
echo "Output:       :" $o
echo "prefix        :" $prefix
echo "tmpdir        :" $tmpdir
echo "base          :" $baddr
echo "amp           :" $amp
echo "ethmac        :" $ethmac
echo "SMP detected  :" $smp_img
echo "ipi           :" $ipi "(zero = use Linux default)"
echo "Linux BSS VA  :" $bss_addr " of size " $bss_size " bytes"
echo "custom-xml    :" ${xml}
echo "Max CPU       :" $max_cpus
echo "Wake others   :" $wake_cpu_mask "(bitmask)"
echo "==================================="
echo
echo

if [ -z "$linux_ver" -o \
	-z "$bss_addr" -o "$bss_addr" = "0x" -o \
	-z "$bss_size" -o "$bss_size" = "0x" \
   ] ; then
    echo >&2 "ERROR: Could not extract needed information."
    echo >&2 "Did you use the right kind of image as input image?"
    echo >&2 ""
    exit 1
fi

# force a "symbol <FORCE_GRMON_LOAD_SYM>" after loading in grmon and grsim
macrostr="${macrostr} -DFORCE_GRMON_LOAD_SYM="\"\\\"${limg}\\\"\"" "

# Create configuration header file from arguments and Linux image, the
# header-file will override the defaults in include/config_auto.h

echo "/* Auto generated by mklinuximg utility */" > $cfg
echo "" >> $cfg
echo "#define CONFIG_RAM_START " $baddr >> $cfg
if [ "$freq" != "0" ]; then
  echo "#define CONFIG_BOOTLOADER_FREQ $freq">> $cfg
fi
if [ -n "$ioarea" ]; then
  echo "#define CONFIG_AMBA_IO_AREA 0x$ioarea">> $cfg
fi
echo "#define CONFIG_IPI_NUM " $ipi >> $cfg
if [ -n "$smp" ]; then
  echo "#define CONFIG_SMP" >> $cfg
fi
echo "#define CONFIG_MAX_CPUS $max_cpus" >> $cfg
echo "#define CONFIG_WAKE_CPU_MASK $wake_cpu_mask" >> $cfg
echo "#define CONFIG_ETHMAC 0x${ethmac}ULL" >> $cfg
if [ -n "$amp" ]; then
  echo "#define CONFIG_AMPOPTS" >> $cfg
fi
echo "#define CONFIG_LINUX_CMDLINE \"$cmdline\"" >> $cfg
echo "#define CONFIG_LINUX_VERSION_CODE 0x${linux_ver}" >> $cfg
echo "#define CONFIG_LINUX_BSS_START ${bss_addr}" >> $cfg
echo "#define CONFIG_LINUX_BSS_SIZE ${bss_size}" >> $cfg
echo "#define CONFIG_UART_INDEX ${uartidx}" >> $cfg
echo "#define CONFIG_GRGPIO_PROBE ${grgpio_probe}" >> $cfg
echo "#define CONFIG_GRGPIO_NBITS ${grgpio_nbits}" >> $cfg
echo "#define CONFIG_GRGPIO_IMASKGEN ${grgpio_imask}" >> $cfg
echo "#define CONFIG_GRGPIO_IRQGEN ${grgpio_irqgen}" >> $cfg
if [ -n "$startupheap" ]; then
    echo "#define CONFIG_STARTUP_HEAP_SIZE $startupheap" >> $cfg
fi
if [ -n "$promheap" ]; then
    echo "#define CONFIG_PROM_HEAP_SIZE $promheap" >> $cfg
fi
if [ -n "$promstack" ]; then
    echo "#define CONFIG_PROMSTART_STACK_SIZE $promstack" >> $cfg
fi

if [ "$isdbg" != 0 ]; then
  echo "#define CONFIG_DEBUG" >> $cfg
fi
# For debugging only
if [ "$dbg_uart" != 0 ]; then
  echo "#define CONFIG_DEBUG_APBUART $dbg_uart" >> $cfg
fi

if [ "$nolin" != 0 ]; then
  echo "#define CONFIG_NO_LINUX" >> $cfg
fi

if [ "$watchdog" != 0 ]; then
  echo "#define CONFIG_WATCHDOG" >> $cfg
  if [ "$watchdog_user_timeout" != 0 ]; then
    echo "#define CONFIG_WATCHDOG_USER_TIMEOUT $watchdog_user_timeout" >> $cfg
  fi
fi

if [ "$clkgate" != 0 ]; then
  echo "#define CONFIG_CLKGATE" >> $cfg
fi

if [ "$pinmux" != 0 ]; then
  echo "#define CONFIG_PINMUX" >> $cfg
fi

if [ "$hrtimers" != 0 ]; then
  if [ -n "$linux_has_gptimer_drv" ]; then
    echo "#define CONFIG_HRTIMERS" >> $cfg
    if [ -n "$linux_has_leon_cs_drv" ]; then
	    echo "#define CONFIG_PROBE_LEON_UPCOUNTER" >> $cfg
    fi
  else
    echo >&2 "ERROR: -hrtimers specified but no timer driver enabled in Linux."
    echo >&2 "Did you enable \"Frontgrade Gaisler GPTIMER Timer Driver\"?"
    echo >&2 ""
    exit 1
  fi
fi

if [ "$max_apb_buses" != "0" ]; then
  echo "#define CONFIG_AMBA_APB_MAX $max_apb_buses">> $cfg
fi

if [ "$max_ahb_buses" != "0" ]; then
  echo "#define CONFIG_AMBA_AHB_MAX $max_ahb_buses">> $cfg
fi

# Build optional AMP Options c-file
echo "" > ${ampoptfile}
if [ -n "$amp" ]; then
    echo $amp | awk '{split($0,a,":"); for (v in a) { print a[v]; }; }' | awk '{split($0,a,"="); print "\t{" a[1] "," a[2] "},"; }; ' >> ${ampoptfile}
fi

# custom xml 
if [ "${xml}" != "" ]; then
    echo "processing xml: ${prefix}scanxml ${xml} ${tmpdir}/${xmlo}"
    ${prefix}scanxml ${xml} ${tmpdir}/${xmlo} || failexit
    macrostr="${macrostr} -DHASXML="\"\\\"${tmpdir}/${xmlo}\\\"\"" "
fi

# Build mklinuximg
cd $tmpdir
make -C $prefix/src O=`pwd` LINUX_IMAGE=$limg CFLAGS_EXTRA="${macrostr}" \
     CROSS_COMPILE=$CROSS_COMPILE MCPU="$mcpu" all || failexit
cd -
mv $tmpdir/image $o || failexit

echo
if [ -n "$symfile" ]; then
    echo "Storing mklinuximg debug info in $symfile"
    ${CROSS_COMPILE}objcopy --only-keep-debug $o "$symfile"
fi
if [ -n "$strip" ]; then
    echo "Stripping $o"
    ${CROSS_COMPILE}strip $o
fi

if [ "$isdump" == "0" ]; then
    rm -rf $tmpdir
else
    echo TMPDIR: $tmpdir
fi
echo

ls -l $o
$OBJDUMP -h $o
