#!/bin/bash
#
# Bring up/down sxdkernel
#
# chkconfig: 2345 05 95
# description: Activates/Deactivates SwitchX Driver to \
#              start at boot time.
#
### BEGIN INIT INFO
# Provides:       sxdkernel
# Required-Start:
# Required-Stop:
# Default-Start:  3 5
# Default-Stop:   0 1 2 6
# Short-Description: SwitchX driver service
# Description:    Activates/Deactivates SwitchX Driver
### END INIT INFO

# Therefore determine the base, follow a runlevel link name ...
base=${0##*/}
link=${base#*[SK][0-9][0-9]}
usr_params=$*
# ... and compare them
if [ "$link" == "$base" ] ; then
	RUNMODE=manual
else
	RUNMODE=auto
fi

ACTION=$1
CHIP_TYPE=$2
shift

if [ -z "$RESET_TRIGGER" ]; then
    RESET_TRIGGER=1
fi

if [ -z "$FAST_BOOT" ]; then
    export FAST_BOOT=0
fi

if [ -z "$SX_I2C_MODE" ]; then
    export SX_I2C_MODE=0
fi

if [ -z "$SX_MST_MODE" ]; then
    export SX_MST_MODE=0
fi

if [ -z "$SX_SKIP_RESET" ]; then
    export SX_SKIP_RESET=0
fi

if [ -z "$WITHOUT_SX_NETDEV" ]; then
    export WITHOUT_SX_NETDEV=0
fi

# this is relevant only on Palladium
if [ -z "$CMD_IFC_INCREASED_TIMEOUT" ]; then
    export CMD_IFC_INCREASED_TIMEOUT=900 # default timeout: 15min
fi

# Predefined PCI devices environment variable format:
# export PREDEFINED_DEV_PCI_BUS=<dev_id>@<bus_id>,<dev_id>@<bus_id> ...
# EXAMPLE:
#     export PREDEFINED_DEV_PCI_BUS=1@06:00.0,2@03:00.0
if [ -z "$PREDEFINED_DEV_PCI_BUS" ]; then
	export PREDEFINED_DEV_PCI_BUS_PARAM=""
else
	export PREDEFINED_DEV_PCI_BUS_PARAM="predefined_dev_pci_bus=${PREDEFINED_DEV_PCI_BUS}"
fi

if [ -z "$FORCE_NO_SBR_CAP" ]; then
    export FORCE_NO_SBR_CAP=0
fi

#If PCI_WAIT_AFTER_LINK_UP_TIME not defined by user use sx_core internal defaults.
if [ -z "$PCI_WAIT_AFTER_LINK_UP_TIME" ]; then
    export PCI_WAIT_AFTER_LINK_UP_TIME=0
fi

if [ -z "$ADDITIONAL_D3COLD_DELAY" ]; then
    export ADDITIONAL_D3COLD_DELAY=0
fi

if [ "x$RESUME_SDK" == "x1" ]; then
    FAST_BOOT=1
fi

RESTART=0

UNLOAD_MODULES=""
STATUS_MODULES="sx_core sx_netdev 8021q"

SXDKERNEL_PRESTART="sxdkernel-pre-start"
SXDKERNEL_POSTSTOP="sxdkernel-post-stop"

#########################################################################
# Get a sane screen width
[ -z "${COLUMNS:-}" ] && COLUMNS=80

if test -n "${PREDEFINED_DEV}"; then
	asicd_str="asicd_${PREDEFINED_DEV}: "
	unset -f echo	# Avoid custom echo cmds from wrapping scripts, sxdkernel has its own set of echo functions.
fi

echo_success() {
	echo -n $@

	[ "$BOOTUP" = "color" ] && $MOVE_TO_COL
	echo -n "[  "
	[ "$BOOTUP" = "color" ] && $SETCOLOR_SUCCESS
	echo -n $"OK"
	[ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
	echo -n "  ]"
	echo -e "\r"
	return 0
}

echo_failure() {
	echo -n $@
	[ "$BOOTUP" = "color" ] && $MOVE_TO_COL
	echo -n "["
	[ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
	echo -n $"FAILED"
	[ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
	echo -n "]"
	echo -e "\r"
	return 1
}

echo_warning() {
	echo -n $@
	[ "$BOOTUP" = "color" ] && $MOVE_TO_COL
	echo -n "["
	[ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
	echo -n $"WARNING"
	[ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
	echo -n "]"
	echo -e "\r"
	return 1
}

# If module $1 is loaded return - 0 else - 1
is_module()
{
	local RC

	/sbin/lsmod | grep -w "$1" > /dev/null
	RC=$?

	return $RC
}

# Return module's refcnt
is_ref()
{
	local refcnt
	refcnt=`cat /sys/module/"$1"/refcnt 2> /dev/null`
	return $refcnt
}

start()
{
	if [ -x "$SXDKERNEL_PRESTART" ]; then
		"$SXDKERNEL_PRESTART"
	else
		# default sdkernel pre-start procedure
		lsmod | grep sx_core > /dev/null
		is_core_running=$?
		if [ $is_core_running -eq 0 ]; then
			echo_warning $"${asicd_str}sx_core service been already started"
			return 0
		fi
	fi

    if [ $WITHOUT_SX_NETDEV -eq 0 ]; then
        /sbin/modprobe 8021q
        RC=$?
        if [ $RC -ne 0 ]; then
            echo_failure $"${asicd_str}Loading 8021q: "
            return $RC
        fi
    fi

	if [ -z "$CHIP_TYPE" ]; then
		/sbin/modprobe sx_core g_chip_type=1 i2c_mode=$SX_I2C_MODE mst_mode=$SX_MST_MODE fast_boot=$FAST_BOOT reset_trigger=$RESET_TRIGGER skip_reset=$SX_SKIP_RESET cmd_ifc_increased_timeout=$CMD_IFC_INCREASED_TIMEOUT force_no_sbr_cap=$FORCE_NO_SBR_CAP __pci_wait_after_link_up_time=$PCI_WAIT_AFTER_LINK_UP_TIME additional_d3cold_delay=$ADDITIONAL_D3COLD_DELAY $PREDEFINED_DEV_PCI_BUS_PARAM
	else
		/sbin/modprobe sx_core g_chip_type=$CHIP_TYPE i2c_mode=$SX_I2C_MODE mst_mode=$SX_MST_MODE fast_boot=$FAST_BOOT reset_trigger=$RESET_TRIGGER skip_reset=$SX_SKIP_RESET cmd_ifc_increased_timeout=$CMD_IFC_INCREASED_TIMEOUT force_no_sbr_cap=$FORCE_NO_SBR_CAP __pci_wait_after_link_up_time=$PCI_WAIT_AFTER_LINK_UP_TIME additional_d3cold_delay=$ADDITIONAL_D3COLD_DELAY $PREDEFINED_DEV_PCI_BUS_PARAM
	fi

	RC=$?
	if [ $RC -ne 0 ]; then
		echo_failure $"${asicd_str}Loading SX core: "
		return $RC
	fi

	# Driver EMAD_TIMEOUT Only. Defined in usec.
	if test -n "${EMAD_TIMEOUT}"; then
		dbg_proc_file="/proc/dbg_dump/dev_${PREDEFINED_DEV:-1}/dbg_cmd"
		# Best-Effort only, do not fail script in case we didn't succeed.
		echo "emad_timeout_set ${EMAD_TIMEOUT}" 2> /dev/null > ${dbg_proc_file} && echo_success "${asicd_str}Set Driver EMAD Timeout to ${EMAD_TIMEOUT} [usec]: " || echo_warning "${asicd_str}Failed to set EMAD Timeout to ${EMAD_TIMEOUT} [usec] in ${dbg_proc_file}"
	fi

	rm -rf /dev/sxdevs
	mkdir /dev/sxdevs
	RC=$?
	if [ $RC -ne 0 ]; then
		echo_failure $"${asicd_str}Preparing /dev/sxdevs folder, has service been already started?"
		return $RC
	fi

	major=$(awk -F ' ' '$2 == "sxcdev" { print $1}' /proc/devices)
	if [ -z "$PREDEFINED_DEV_PCI_BUS" ]; then
		# Single Asic - single char dev - sxcdev
		mknod -m 666 /dev/sxdevs/sxcdev c $major 1
	else
		# Multi Asic - multiple char dev.
		# for example - if PREDEFINED_DEV_PCI_BUS=1@06:00.0,2@03:00.0
		# char devices will be sxcdev1 and sxcdev2
		IFS='@'
		read -a strarr <<< "${PREDEFINED_DEV_PCI_BUS}"
		mknod -m 666 /dev/sxdevs/sxcdev${strarr[0]} c $major ${strarr[0]}
		for (( n=1; n < ${#strarr[*]} - 1; n++ ))
		do
			IFS=','
			read -a dev <<< "${strarr[n]}"
			mknod -m 666 /dev/sxdevs/sxcdev${dev[1]} c $major ${dev[1]}
		done
	fi

	RC=$?
	if [ $RC -ne 0 ]; then
		echo_failure $"${asicd_str}Preparing /dev/sxdevs/sxcdev major='$major' device"
		return $RC
	fi

    if [ $WITHOUT_SX_NETDEV -eq 0 ]; then
        /sbin/modprobe sx_netdev
        RC=$?
        if [ $RC -ne 0 ]; then
            echo_failure $"${asicd_str}Loading sx_netdev: "
            return $RC
        fi

        /sbin/modprobe sx_bfd
        RC=$?
        if [ $RC -ne 0 ]; then
            echo_failure $"${asicd_str}Loading sx_bfd: "
            return $RC
        fi

        major=$(awk -F ' ' '$2 == "bfdcdev" { print $1}' /proc/devices)
        if [ -z "$PREDEFINED_DEV_PCI_BUS" ]; then
            # Single Asic - single char dev - sxcdev
            mknod -m 666 /dev/sxdevs/bfdcdev c $major 1
        else
            # Multi Asic - multiple char dev.
            # for example - if PREDEFINED_DEV_PCI_BUS=1@06:00.0,2@03:00.0
            # char devices will be bfdcdev1 and bfdcdev2
            IFS='@'
            read -a strarr <<< "${PREDEFINED_DEV_PCI_BUS}"
            mknod -m 666 /dev/sxdevs/bfdcdev${strarr[0]} c $major ${strarr[0]}
            for (( n=1; n < ${#strarr[*]} - 1; n++ ))
            do
                IFS=','
                read -a dev <<< "${strarr[n]}"
                mknod -m 666 /dev/sxdevs/bfdcdev${dev[1]} c $major ${dev[1]}
            done
        fi

        RC=$?
        if [ $RC -ne 0 ]; then
            echo_failure $"${asicd_str}Preparing /dev/sxdevs/bfdcdev major='$major' device"
            return $RC
        fi
    fi

    echo_success $"${asicd_str}Loading SX driver: "

    # check if we have sx_ib driver installed (on IB switch)
    # if so, we'll load it.
    /sbin/modinfo sx_ib > /dev/null 2>&1
    RC=$?

    if [ $RC -eq 0 ]; then
        /sbin/modprobe sx_ib
        RC=$?
    else
        RC=0
    fi

    return $RC
}

rm_mod()
{
	local mod=$1
	shift

	unload_log=`/sbin/rmmod $mod 2>&1`
	if [ $? -ne 0 ]; then
		echo_failure $"${asicd_str}Unloading $mod"
		if [ -n "${unload_log}" ]; then
			echo ${asicd_str}$unload_log
		fi
		[ -n "$2" ] && echo ${asicd_str}$2
		exit 1
	fi
}

unload()
{
	# Unload module $1
	local mod=$1
	local unload_log

	if is_module $mod; then
		case $mod in
			*)
			/sbin/modprobe -r $mod
			if [ $? -ne 0 ] || is_module $mod; then
				# Try rmmod if modprobe failed.
				rm_mod $mod
			fi
			;;
		esac
	fi
}

stop()
{
    local NEED_UNLOAD=0

    if [ $WITHOUT_SX_NETDEV -eq 0 ]; then
        if ! is_module sx_netdev ; then
            if [ $RESTART -eq 0 ]; then
                echo
                echo_warning $"${asicd_str}SX NETDEV driver is not loaded or loaded with errors"
                echo
            fi
        else
            NEED_UNLOAD=1
        fi
    fi


    if ! is_module sx_core ; then
        if [ $RESTART -eq 0 ]; then
            echo
            echo_warning $"${asicd_str}SX driver is not loaded or loaded with errors"
            echo
        fi
    else
        NEED_UNLOAD=1
    fi

    if [ $WITHOUT_SX_NETDEV -eq 0 ]; then
        if ! is_module 8021q ; then
            if [ $RESTART -eq 0 ]; then
                echo
                echo_warning $"${asicd_str}8021q is not loaded or loaded with errors"
                echo
            fi
        else
            NEED_UNLOAD=1
        fi
    fi

    if [ $NEED_UNLOAD -eq 0 ]; then
        return 0
    fi

	# Unload modules
	if [ "$UNLOAD_MODULES" != "" ]; then
		for mod in  $UNLOAD_MODULES
		do
			unload $mod
		done
	fi

	local done=0

    # check if we have sx_ib up (on IB switch)
    # if so, we'll unload it
    if is_module sx_ib; then
        is_ref sx_ib
        unload sx_ib
        done=1
    fi

    # Unload sx_bfd
    if is_module sx_bfd; then
        is_ref sx_bfd
        unload sx_bfd
        done=1
    fi

	if [ -x "$SXDKERNEL_POSTSTOP" ]; then
		"$SXDKERNEL_POSTSTOP"
	else
		# default sxdkernel post-stop procedure
		# Unload mlx_sx_netdev
		if is_module sx_netdev; then
			is_ref sx_netdev
			unload sx_netdev
		fi

		# Unload mlx_sx_core
		if is_module sx_core; then
			is_ref sx_core
			unload sx_core
			rm -rf /dev/sxdevs
			done=1
		fi

		# Unload memtrack
		if is_module memtrack; then
			unload memtrack
		fi

		# don't unload 8021q because some system components might use it
	fi

	if [ $done -eq 1 ]; then
		echo_success $"${asicd_str}Unloading SX driver: "
	fi
}

status()
{
	local RC=0

	if is_module sx_core; then
		echo
		echo "${asicd_str}  SX driver loaded"
		echo
	else
		echo
		echo $"${asicd_str}SX driver is not loaded"
		echo
	fi

	local cnt=0

	for mod in  $STATUS_MODULES
	do
		if is_module $mod; then
			[ $cnt -eq 0 ] && echo "${asicd_str}The following SX modules are loaded:" && echo
			let cnt++
			echo "${asicd_str}  $mod"
		fi
	done

	echo

	return $RC
}


RC=0
start_time=$(date +%s | tr -d '[:space:]')

default_trap_handler()
{
	let run_time=$(date +%s | tr -d '[:space:]')-${start_time}

	# Ask to wait for 5 seconds if trying to stop sx
	if [ $run_time -gt 5 ] && [ "$ACTION" == "stop" ]; then
		printf "\nProbably some application are still using SX modules...\n"
	else
		printf "\nPlease wait ...\n"
	fi
	return 0
}

SCRIPTNAME=$(basename $0)
LOCK_DIR="/var/lock/${SCRIPTNAME}"
PIDFILE="${LOCK_DIR}/PID"

lock()
{
  mkdir $LOCK_DIR 2> /dev/null
  if [ $? == 0 ]; then
     echo $$ > $PIDFILE
     DO_UNLOCK=1
     return 0
  else
    return 1
  fi
}

remove_lock()
{
  rm -rf $LOCK_DIR
}

exit_on_signal()
{
  default_trap_handler
  echo "${asicd_str}Kill, cleanup..."
  if [ "$DO_UNLOCK" != "0" ]; then
     remove_lock
  fi
  exit 1
}

exit_on_end()
{
  if [ "$DO_UNLOCK" != "0" ]; then
    remove_lock
  fi
  exit $1
}

lock_failed()
{
  # check for PID
  PID=$(cat $PIDFILE)

  # Maybe there was an error reading the PID file
  if [ $? != 0 ]; then
     echo "${asicd_str}$PIDFILE read failed, $SCRIPTNAME is running (PID ${PID})" >&2
     DO_UNLOCK=1
     exit 1
  fi

  # Check if the PID is really existing
  if ! kill -0 $PID &>/dev/null; then
     echo "${asicd_str}Removing stale lock of nonexistant PID ${PID}" >&2
     remove_lock
     echo "${asicd_str}Restarting myself (${SCRIPTNAME})" >&2
     # On Unix-like operating systems, exec is a builtin command of the Bash shell.
     # It allows you to execute a command that completely replaces the current process.
     # The current shell process is destroyed, and entirely replaced by the command you specify.
     exec "$0" $usr_params
  else
     echo "${asicd_str}Locking failed, another instance of $SCRIPTNAME is still running (PID ${PID})" >&2
     DO_UNLOCK=0
     exit 1
  fi
}
trap exit_on_signal INT KILL TERM
trap 'exit_on_end $?' QUIT EXIT

# Lock mechanism to insure sxdkernel run as singleton
lock || lock_failed

case $ACTION in
	start)
		start
	;;
	stop)
		stop
	;;
	restart)
		RESTART=1
		stop
		start
	;;
	status)
		status
	;;
	*)
		echo
		echo "${asicd_str}Usage: `basename $0` {start|stop|restart|status}"
		echo
		exit 1
	;;
esac

RC=$?
exit $RC
