#!/bin/bash
#
# calvados/spitfire/boot/scripts/pd-functions
# This script provides PD functions to other scripts.
#
# Copyright (c) 2017-2022 by Cisco Systems, Inc.
# All rights reserved.

PD_FUNCTION_SOURCED=1

# source PD-function library
if [ -f /etc/init.d/load_kernel_modules.sh ]; then
    source /etc/init.d/load_kernel_modules.sh
fi

# Include FPGA functions to implement watchdog functionality
if [ -f /usr/local/etc/fpga-functions ]; then
    source /usr/local/etc/fpga-functions
else
    echo "Error: missing /usr/local/etc/fpga-functions file."
fi

#
# Get the file size in bytes, cross os
#
function filesize
{
    local file=$1
    size=`stat -c %s $file 2>/dev/null` # linux
    if [ $? -eq 0 ]; then
        echo $size
        return 0
    fi

    eval $(stat -s $file) # macos
    if [ $? -eq 0 ]; then
        echo $st_size
        return 0
    fi

    echo 0
    return -1
}

#
# Check if pd_funcions should be skipped
#
function skip_pd_functions {
    local install=$(cat /proc/cmdline | grep install=)
    if [ -n "${install}" ]; then
        return 1
    fi
    local sim=$(cat /proc/cmdline | grep simulator=)
    if [ -n "${sim}" ]; then
        return 1
    fi
    return 0
}

#
# Which TTY should calvados con use
#
function platform_enable_calvados_con
{
    echo "Calvados console is not relevant"
}

#
# Which TTY should calvados aux use
#
function platform_enable_calvados_aux
{
    echo "Calvados aux is not relevant"
}

#
# Which TTY should XR con use
#
function platform_enable_xr_con
{
    echo "XR console is not relevant"
}

#
# Which TTY should XR aux use
#
function platform_enable_xr_aux
{
    echo "XR aux is not relevant"
}


#
# Should we allow the first serial port to have a tty login?
#
function platform_enable_host_login_on_first_serial
{
    skip_pd_functions
    if [ $? -ne 0 ]; then
        return;
    fi
    ACTIVE_SERIAL=''
}

# Do we want to enable host access on extra ttys? Good for development but
# is a security risk to leave host access visible.
#
function platform_starts_serial
{
    # Allow starts-serial to start serial login
    # If no active serial set, and on hostos, start serial on ttyS0 and ttyS1

    [ -z "$ACTIVE_SERIAL" ] && ACTIVE_SERIAL=/dev/ttyS[01]

    SPEED=`grep -o "console[^[:space:]]*" /proc/cmdline | cut -d ',' -f2`
}

#
# Check we have somewhere to write logs to. At early boot we might not have
# /var/log, so use /tmp until then
#
function platform_log_choose_log_file
{
    #
    # Where to log shell script output to. If /var/log is not available, use
    # /tmp which surely must be usable.
    #
    local PLATFORM_LOG=/var/log/platform.log

    #
    # If we are installing, append onto the host install logs as platform.log 
    # is not saved by xrnginstall in save_install_log (yet)
    #
    grep -q "root=/dev/ram" /proc/cmdline
    if [ $? -eq 0 ]; then
        PLATFORM_LOG=/var/log/host-install.log
        #
        # A tad obscure, but this is the FD that pxe_install uses for logging.
        # Only if we log to this will our output get into host-install.log
        #
        PLATFORM_LOG=/dev/fd/107
    fi

    PLATFORM_LOG_FILE=$PLATFORM_LOG
    if [ ! -f $PLATFORM_LOG_FILE ]; then
        touch $PLATFORM_LOG_FILE &>/dev/null
    fi

    if [ ! -w $PLATFORM_LOG_FILE ]; then
        local PLATFORM_TMP_LOG=/tmp/platform.early.log

        PLATFORM_LOG_FILE=$PLATFORM_TMP_LOG
    fi

    #
    # Sanity check it doesn't get too large
    #
    if [ -f $PLATFORM_LOG_FILE ]; then
        local FILESIZE=`filesize $PLATFORM_LOG_FILE`
        local MAX=1048576

        if [ $FILESIZE -ge $MAX ]; then
            #
            # Chop off the start of the file
            #
            sed -i '1,100d' $PLATFORM_LOG_FILE
        fi
    fi
}

#
# Utility function to log to our platform log file to help with
# debugging shell script flow.
#
function platform_log
{
    platform_log_choose_log_file

    local DATE=`date`
    echo "$DATE ($0): $*" >> $PLATFORM_LOG_FILE
}

#
# Utility function to log to our platform log file to help with
# debugging shell script flow.
#
function platform_log_error
{
    platform_log_console $*

    backtrace
}

#
# Some calvados scripts use log message but it is not defined. Get the output
# into our platform log if log_message is not defined.
#
function log_message
{
    platform_log $*
}

#
# Log to file and console
#
function platform_log_console 
{
    platform_log_choose_log_file

    local DATE=`date`
    echo "$DATE ($0): $*" | tee -a $PLATFORM_LOG_FILE

    return ${PIPESTATUS[0]}
}

#
# Keep this function aliased as a variable, so that if PLATFORM_LOG_EXEC is 
# not defined due to an error in including this file, the callers command 
# still executes
#
PLATFORM_LOG_EXEC=platform_log_exec
PLATFORM_LOG_EXEC_CONSOLE=platform_log_exec_console

#
# Log results to the log file only
#
function platform_log_exec
{
    platform_log_choose_log_file
    platform_log "exec: $*"
    platform_log "    : in cwd" `pwd`

    local PREFIX="`date -u`: -- "
    $* 2>&1 | sed "s/^/${PREFIX}/g" >>$PLATFORM_LOG_FILE 2>&1

    return ${PIPESTATUS[0]}
}

#
# Log results to the log file and the console
#
function platform_log_exec_console
{
    platform_log_choose_log_file
    platform_log "exec: $*"
    platform_log "    : in cwd" `pwd`

    local PREFIX="`date -u`: -- "
    $* 2>&1 | sed "s/^/${PREFIX}/g" | tee -a $PLATFORM_LOG_FILE

    return ${PIPESTATUS[0]}
}

#
# Print a shell backtrace
#
function backtrace () {
    local deptn=${#FUNCNAME[@]}

    for ((i=1; i<$deptn; i++)); do
        local func="${FUNCNAME[$i]}"
        local line="${BASH_LINENO[$((i-1))]}"
        local src="${BASH_SOURCE[$((i-1))]}"
        printf '%*s at: %s(), %s, line %s\n' $i '' $func $src $line # indent
    done
}

function platform_log_backtrace_ () {
    local depth=${#FUNCNAME[@]}

    for ((i=1; i<$depth; i++)); do
        local func="${FUNCNAME[$i]}"
        local line="${BASH_LINENO[$((i-1))]}"
        local src="${BASH_SOURCE[$((i-1))]}"
        printf '%*s at: %s(), %s, line %s\n' $i '' $func $src $line >> $PLATFORM_LOG_FILE
    done
}

function platform_log_backtrace () {
    platform_log_choose_log_file

    platform_log_backtrace_
}


# Console Mux details
#
function platform_get_mux_tty_settings
{
# No console mux
  echo "Should not come to mux tty"
}

function platform_post_xr_launch
{
# No XR LXC
  echo "Should not come to XR launch"
}

# App hosting Cgroup settings
function pd_tpa_cgroup_settings
{
        APP_CGROUP_MEM_NUMA_NODES=0
        APP_CGROUP_CPUSET_CPUS=0-7
        APP_CGROUP_CPU_SHARES=256
        APP_CGROUP_MEM_LIMIT_IN_BYTES=1G
}

function platform_copy_fru_json ()
{
    cp /usr/local/etc/fru-db-ncs5700.json /usr/local/etc/fru-db.json
}

function platform_info_setup ()
{
    platform_copy_fru_json
}


#
# Function to return whether the system is a multi-XR node system
# Returns 0 if not a multi-XR node system.
# Returns 1 if it is a multi-XR node system.
# Usage:
#     pd_is_multi_xr_node
#     local multi_node=$?
function pd_is_multi_xr_node()
{
    return 0
}

#
# Function to set BOARDTYPE variable
function get_board_type ()
{
     # If BOARDTYPE variable is already set, don't try to get it again.
     if [ -n "${BOARDTYPE}" ]; then
         return 0
     fi
 
     # Setup platform info if is not yet done from this routine as this is
     # usually the first function that any PI script will call.
     fpga_platform_setup
 
     BOARDTYPE=${MY_CARD_TYPE}
 
     # Make this variable available to subshells, so we don't have to get
     # this data again.
     export BOARDTYPE
}

#
# Function to set MY_CARD_PID variable
function get_my_card_pid ()
{
    # If MY_CARD_PID variable is already set, don't try to get it again.
    if [ -n "${MY_CARD_PID}" ]; then
        return 0
    fi

    # Setup platform info if is not yet done from this routine as this is
    # usually the first function that any PI script will call.
    fpga_platform_setup

    if [ -n "${CARD_PID}" ]; then
        MY_CARD_PID=${CARD_PID}
    else
        MY_CARD_PID="UNKNOWN"
    fi

    # Make this variable available to subshells, so we don't have to get
    # this data again.
    export MY_CARD_PID
}

function platform_copy_required_kernel_crash_modules() {
    local dest=$1

    cp /lib/modules/4*/kernel/drivers/md/dm-mod.ko $dest
    # On sim, disk shows as virtio device. Copy these modules only for sim.
    if [ -n "`cat /proc/cpuinfo |grep 'model name.*:.*VXR'`" ]; then
       cp /lib/modules/4*/kernel/drivers/virtio/virtio.ko $dest
       cp /lib/modules/4*/kernel/drivers/virtio/virtio_ring.ko $dest
       cp /lib/modules/4*/kernel/drivers/virtio/virtio_pci.ko $dest
       cp /lib/modules/4*/kernel/drivers/block/virtio_blk.ko $dest
    fi
    cp /lib/modules/4*/kernel/drivers/md/dm-bio-prison.ko $dest
    cp /lib/modules/4*/kernel/drivers/md/persistent-data/dm-persistent-data.ko $dest
    cp /lib/modules/4*/kernel/drivers/md/dm-bufio.ko $dest
    cp /lib/modules/4*/kernel/drivers/md/dm-thin-pool.ko $dest
}

function platform_load_required_kernel_crash_modules() {
    modprobe dm-mod >/dev/null
    # Only modprobe virtio modules for sim.
    if [ -n "`cat /proc/cpuinfo |grep 'model name.*:.*VXR'`" ]; then
       modprobe virtio >/dev/null
       modprobe virtio_ring >/dev/null
       modprobe virtio_pci >/dev/null
       modprobe virtio_blk >/dev/null
    fi
    modprobe dm-bio-prison >/dev/null
    modprobe dm-bufio >/dev/null
    modprobe dm-persistent-data >/dev/null
    modprobe dm-thin-pool >/dev/null
}

function platform_binaries_set
{
   local set=('udevd' 'dmsetup')
   for prog in $(rpm -ql $(rpm -qa | grep thin-prov))
    do
        [ -d $prog ] && continue
        set=(${set[@]} $(basename $prog))
    done
    echo ${set[@]}
}

function platform_copy_bins_libs
{
    local crash_root=$1
    for prog in $(rpm -ql $(rpm -qa | grep thin-prov))
    do
        [ -d $prog ] && continue
        cp -d $prog $crash_root/$prog
    done
    cp /sbin/udevd $crash_root/sbin/
    mkdir -p $crash_root/run $crash_root/lib/udev/rules.d
    cp /lib/udev/rules.d/*dm*.rules $crash_root/lib/udev/rules.d/
    cp /usr/sbin/dmsetup $crash_root/usr/sbin/
}


# Function to start any binaries
function pd_start_bin()
{
    udevd --daemon
}

function pd_cfg_watchdog_timeout()
{

        source /usr/local/etc/fpga_platform_data_config 2>/dev/null

        if [ $(pd_config_get_aikido_fpga_region) -eq 0 ]; then
            FPGA_ID=$FPGA_ID_KALARI
        else
            FPGA_ID=$NCS5700_F_FPGA_ID_EXCELSIOR_X86_CPU_FPGA
        fi
        PLATFORM_ID=$PLATFORM_ID_FIXED

        pd_config_data_based_on_platform_and_fpga_id 

        if [ $? -ne 0 ]; then
                msg_log "ERROR: Could not find IP BLOCK"
                retrun 1
        fi

       fpga_get_wdog_state _wd_state

       if [ $_wd_state -ne ${WDOG_STATE_DISABLED} ]; then
              if [ $_wd_state -eq ${WDOG_STATE_STAGE1} ]; then
                      local _stg1_timeout=$1
                      echo "Extending watchdog stage1 timeout to $_stg1_timeout seconds ..."
                      fpga_reconfig_wdog_stage1 $_stg1_timeout
              elif [ $_wd_state -eq ${WDOG_STATE_STAGE2} ] || [ $_wd_state -eq ${WDOG_STATE_STAGE1_EXPIRED} ]; then
                      local _stg2_timeout=$1
                      echo "Extending watchdog stage2 timeout to $_stg2_timeout seconds ..."
                      fpga_reconfig_wdog_stage2 $_stg2_timeout
              fi
       fi
       
       return 0

}

#Set the size of the Encrypted Partition (150MB)
function pd_get_part_size_encrypted
{
    echo 150
}

function pd_get_part_name_encrypted
{
    echo "install-data-encrypted"
}

function pd_get_part_mntpt_encrypted
{
    echo "/var/xr/enc"
}

function pd_get_part_dm_name_encrypted
{
    echo "encrypted"
}

function pd_punch_watchdog()
{
    fpga_get_wdog_state _wd_state
    if [ $_wd_state -ne ${WDOG_STATE_DISABLED} ]; then
        echo "Punching watchdog"
        fpga_punch_wdog
    fi
}

# Configure the watchdog and run a script to punch it in the background
# The argument to the function is only for debug process, pass a string
# that indicates who is configuring the watchdog
# E.g: pd_start_punching_watchdog INSTALL
function pd_start_punching_watchdog {
    local wdog_state=0
    fpga_get_wdog_state wdog_state
    if [ $wdog_state -ne ${WDOG_STATE_DISABLED} ]; then
        # Disable, configure and enable the watchdog again to
        # bring it back to stage 1
        fpga_disable_wdog
        # Setting the stage 1 timer to 3 minutes = 3 times the punching interval
        # which is a minute. This way, even if the punch does not happen twice,
        # there is room to recover
        fpga_config_wdog 180 60
        /usr/local/etc/punch-wd.sh&
    fi
    fpga_dump_watchdog_debug_if_enabled $1
    # if FIT handling is enabled for watchdog,
    # return error from this function
    # pd_check_fit_config REIMAGE_FIT_OPT_WD_START_FAILURE 
}

# Kill the watchdog running script
function pd_stop_punching_watchdog {
    if [ ! -z `pgrep punch-wd` ] ; then
        killall punch-wd.sh
    fi
    # Set the timer values back to what BIOS is setting it to    
    local _wdog_state
    fpga_get_wdog_state _wdog_state
    if [ $_wdog_state -ne ${WDOG_STATE_DISABLED} ]; then
        # Disable, configure and enable the watchdog again to
        # bring it back to stage 1
        fpga_disable_wdog
        fpga_config_wdog 540 60
    fi
    # if FIT handling is enabled for watchdog,
    # return error from this function
    # pd_check_fit_config REIMAGE_FIT_OPT_WD_STOP_FAILURE
}

# This function is called from xr_kdump script to trigger PD specific
# operation to reset the board after KDUMP operation.
# Since the cisco_fpga_shutdown_handler.ko does not work under KDUMP minimal
# kernel environment, because reboot_notifier doesn't get triggered. So, we
# will have to manually disable the SSD before power cycle the x86 power zone
# without the help of the cisco_fpga_shutdown_handler.ko
# Note that as long as we shutdown SSD before reloading the card, we are
# achieving the same result as with the logic in cisco_fpga_shutdown_handler.ko
# Input optional argument is the reset reason string.
function pd_board_reset()
{
    local _reset_reason=$1
    # First, put the SSD in offline state
    if [ -f /sys/block/sda/device/state ]; then
        echo "Putting SSD in offline state ..."
        echo offline > /sys/block/sda/device/state
    fi
    # Now delete the disk from the kernel handling before taking out the power
    if [ -f /sys/block/sda/device/delete ]; then
        echo 1 > /sys/block/sda/device/delete
    fi
    fpga_platform_setup
    if [ -n "${MAGIC_COOKIE_OFFSET}" ]; then
        echo "Waiting for linux reset, PCI_ID: ${AIKIDO_PCI_ID} "
        if [ ${AIKIDO_PCI_ID} == "0281" ]; then
            board_reset_val=0x94cb2f10
            echo "Setting Magic cookie to $board_reset_val "
            fpga_write ${FPGA_INST_AIKIDO} ${MAGIC_COOKIE_OFFSET} $board_reset_val
        fi
    else
        echo "Reset x86 CPU ..."
        # Call funtion to do the CPU power-cycling
        fpga_x86_cpu_pwr_cyle
    
        # If somehow fpga_x86_cpu_pwr_cycle() function did not trigger the CPU
        # power zone power-cycle via FPGA due to PCI issue, then we should not
        # exit from this routine, or the generic KDUMP script will do a warm
        # reset of just CPU and that can cause problem.
        # So we shoudl not exit from this routine when this is called from
        # KDUMP script.
        #
        # NOTE: to detect that this is being called via KDUMP script, we are
        #       checking if the DUMPDISK variable is being set or not.
        if [ -n "$DUMPDISK" ]; then
            echo "Waiting for CPU power-cycling ..."
            # 600 seconds will be long enough for watchdog to timeout and
            # have the FPGA doing the proper CPU power-zone cycling as a
            # way to deal with failure in fpga_x86_cpu_pwr_cyle()
            sleep 600
        fi
    fi    
}
