#!/bin/sh

. /etc/init.d/modlibrc

usage()
{
cat << EOF
Usage: rc.usbroot [on|off|status|store [usb_device [mount_options]]|nostore]
USB root mount script - can only be called as an 'init' replacement.
Parameters:
  status  - check status of USB root; returns 'running' if USB root mount on '/'
            is active, 'stopped' otherwise.
  store   - persistently store USB device name in bootloader environment for
            later use with 'mount'. This setting must be configured at least
            once before USB root can be used. If called without parameter,
            this option prints the current values.
            Syntax of first parameter 'usb_device' is:
            mass-storage-device-name:[filesystem-type:]root-directory
            The second parameter 'mount_options' is optional and stores
            options which are passed to the mount call when mounting the
            USB mass storage device.
  nostore - remove USB storage name from bootloader environment. Even though it
            does no harm to have the USB variable around in the environment all
            the time, you may optionally remove it again using this option.
  on      - activate replacing 'init' by 'rc.usbroot' -> boot with USB root,
            adds something like 'init=/etc/init.d/rc.usbroot' to bootloader
            environment variable 'kernel_args',
            'store' has to been setup before
  off     - deactivate replacing 'init' by 'rc.usbroot' -> boot without
            USB root. Removes 'init' from bootloader environment variable
            'kernel_args'.
Examples:
  - rc.usbroot store
  - rc.usbroot store /dev/sda1:/usbroot
  - rc.usbroot store /dev/sda1:/usbroot ro,noatime,nodiratime
  - rc.usbroot store /dev/sdc1:ext2:/
  - rc.usbroot on
  - rc.usbroot off
  - rc.usbroot nostore
  - rc.usbroot status
EOF
}

# if we are called as hotplug helper we must not filter these env vars
case $1 in
	scsi|scsi_device|scsi_host|block|usb)
		;;
	*)
		DEVICE=
		DEVPATH=${3##/proc/bus/usb/}
		;;
esac
# all other vars can always be initialized
USBROOT_ENV_VAR=kernel_args1
USBROOT_ENV_STRING=USBROOT # capital letters required!
USBPATH=
FSTYPE=
MNTOPTIONS=
HWRevision=
HWRevision_BitFileCount=
SYSFS=/sys
MNTPOINT=/data
OLDROOT=oldroot
HOTPLUG=
HOTPLUGCACHE=.hotplug-cache

USBROOT_UNMOUNTOLDROOT='yes'
[ -f /mod/etc/conf/usbroot.cfg ] && . /mod/etc/conf/usbroot.cfg

serialize_env() {
	local ENV=""
	local VAL=""
	for i in $*; do
		eval "VAL=\$$i"
		ENV="export $i='$VAL';$ENV"
	done
	echo "$ENV"
}

hotplugging() {
	case $1 in
		enable)
			echo "$HOTPLUG" > /proc/sys/kernel/hotplug
			;;
		disable)
			HOTPLUG=$(cat /proc/sys/kernel/hotplug)
			echo "" > /proc/sys/kernel/hotplug
			;;
		defer)
			HOTPLUG=$(cat /proc/sys/kernel/hotplug)
			# prepare tmpfs which we later move to /dev;
			# we have to move to /dev as this is the only fs with
			# can be mounted conditionally (we will skip the mount
			# later when usb root is active)
			mount dev /oldroot -t tmpfs -o nosuid
			mknod /oldroot/console c 5 1
			# remap hotplug to us
			echo "/etc/init.d/rc.usbroot" > /proc/sys/kernel/hotplug
			;;
		flush)
			# flush is run with new root so we find the cache file under /dev
			HOTPLUG=$(cat /proc/sys/kernel/hotplug)
			if [ -f "/dev/$HOTPLUGCACHE" -a -n "$HOTPLUG" ]; then
				# separate environment in the following loop
				cat "/dev/$HOTPLUGCACHE" | while read AGENT ENVSTRING; do
					(
					eval "$ENVSTRING"
					exec "$HOTPLUG" "$AGENT"
					)
				done
				# clean up
				rm -f "/dev/$HOTPLUGCACHE"
			fi
			;;
		discard)
			echo "$HOTPLUG" > /proc/sys/kernel/hotplug
			rm -f "/oldroot/$HOTPLUGCACHE"
			umount /oldroot
			;;

		# hotplug actions
		scsi_device)
			ENV=$(serialize_env ACTION DEVPATH PHYSDEVBUS PHYSDEVDRIVER PHYSDEVPATH SEQNUM SUBSYSTEM)
			echo "$1 $ENV" >> "/oldroot/$HOTPLUGCACHE"
			;;
		scsi_host)
			ENV=$(serialize_env ACTION DEVPATH PHYSDEVPATH SEQNUM SUBSYSTEM)
			echo "$1 $ENV" >> "/oldroot/$HOTPLUGCACHE"
			;;
		scsi)
			ENV=$(serialize_env ACTION DEVPATH PHYSDEVBUS SEQNUM SUBSYSTEM)
			echo "$1 $ENV" >> "/oldroot/$HOTPLUGCACHE"
			;;
		block)
			ENV=$(serialize_env ACTION DEVPATH MAJOR MINOR PHYSDEVBUS PHYSDEVDRIVER PHYSDEVPATH SEQNUM SUBSYSTEM)
			echo "$1 $ENV" >> "/oldroot/$HOTPLUGCACHE"
			;;
		usb)
			ENV=$(serialize_env ACTION DEVICE DEVPATH INTERFACE MODALIAS PHYSDEVBUS PHYSDEVDRIVER PRODUCT SEQNUM SUBSYSTEM TYPE)
			echo "$1 $ENV" >> "/oldroot/$HOTPLUGCACHE"
			;;
		*)
			return 1
			;;
	esac
}

mount_usbroot() {
	if [ ! -d "$MNTPOINT" ]; then
		echo "*** Mountpoint $MNTPOINT for USB device does not exist. Exiting. ***"
		return 1
	fi

	kver="$(uname -r)"
	case "$kver" in
		2.6.28* | 2.6.32* )
			# no need to cache hotplug events, udev does it
			mount dev /oldroot -t tmpfs -o nosuid

			# touch .hotplug-cache to prevent /dev mount in rc.S
			touch /oldroot/$HOTPLUGCACHE

			cp -a /dev/* /oldroot
			mount -t tmpfs tmpfs /dev

			# create sd* devices
			makedevs -d /etc/usbroot_device.table /

			if [ "$kver" = "2.6.28.10" ]; then
				load_modules_2_6_28_10
			else
				load_modules_2_6_32
			fi
			;;

		2.6.19.2 )
			# cache hotplug events
			hotplugging defer

			load_modules_2_6_19_2
			;;

		2.6.13* )
			# cache hotplug events
			hotplugging defer

			load_modules_2_6_13_1
			;;

		* )
			echo "Unknown kernel version ($kver). Exiting"
			return 1
			;;
	esac

	for i in $FSTYPE usb-storage sd_mod; do
		modprobe $i
	done

	echo -n "Waiting for usb to come up "
	sleep 15

	LOOP=0
	while ! dd if=$DEVICE of=/dev/null bs=1 count=1 2>/dev/null; do
		echo -n "."
		let LOOP=LOOP+1
		if [ $LOOP -gt 10 ]; then
			echo
			echo 'storage: SCSI device not responding!'
			# discard hotplug cache
			hotplugging discard
			# return with failure code
			return 1
		fi
		sleep 2
	done

	echo "found"

	# check fs if e2fsck is available
	if [ -x /usr/sbin/e2fsck ]; then
		echo "*** Checking filesystem on $DEVICE. ***"
		e2fsck -p $DEVICE
		FSCKCODE=$?
		echo "*** e2fsck returned with exit code: $FSCKCODE ***"
		if [ "$FSCKCODE" -gt 3 ]; then
			echo "*** USB root rejected: filesystem contains errors. ***"
			# discard hotplug cache
			hotplugging discard
			return 1
		elif [ "$FSCKCODE" -gt 1 ]; then
			# no need to reboot
			echo "*** Filesystem errors corrected. ***"
		fi
	fi
	# now mount the partition
	mount $DEVICE $MNTPOINT \
		${FSTYPE:+-t} $FSTYPE ${MNTOPTIONS:+-o} $MNTOPTIONS
	# reenable hotplug
	hotplugging enable
	# return with success
	return 0
}

# Load piglet module (hardware specific)
load_modules_2_6_13_1() {
	piglet_bitfile=/lib/modules/microvoip_isdn_top.bit${HWRevision_BitFileCount}
	piglet_load_params=" \
	piglet_width_running=1 \
	piglet_usb_power_bit=-1 \
	piglet_disable_test=1 \
	piglet_cs=5 \
	piglet_reset_bit=-2 \
	piglet_bitfile_write=-1 \
	piglet_bitfile_revbytes=1 \
	piglet_irq_gpio=18 \
	piglet_irq=9 \
	"

	case $HWRevision in
		94|127) # 7170
			piglet_load_params="$piglet_load_params piglet_bitfile_offset=0x0"
		;;
		95) # 7140
			piglet_load_params="$piglet_load_params piglet_bitfile_offset=0x4d"
		;;
		101) # W701V
			piglet_load_params="$piglet_load_params piglet_enable_button2=1 \
				piglet_enable_switch=1 piglet_bitfile_offset=0x51"
		;;
		102) # W900V
			piglet_load_params="$piglet_load_params piglet_bitfile_offset=0x51"
			[ "$HWRevision_BitFileCount" = "1" ] && \
				piglet_load_params="$piglet_load_params piglet_enable_switch=1"
		;;
		106) # 7150
			piglet_bitfile=/lib/modules/microvoip_top.bit${HWRevision_BitFileCount}
			piglet_load_params="$piglet_load_params piglet_bitfile_offset=0x0"
		;;
		108|112|117|118|119) # 7141,3130,3170,3131,2171
			piglet_load_params="$piglet_load_params piglet_bitfile_offset=0x4b"
		;;
	esac

	modprobe Piglet piglet_bitfile=$piglet_bitfile $piglet_load_params

	if [ $HWRevision = 102 ]; then
		mount -t proc proc /proc > /dev/null 2>&1
		mount -t tmpfs tmpfs /var > /dev/null 2>&1
		echo firmware_info $(/etc/version) > /proc/sys/urlader/environment
		modprobe tiatm firmware_load_file=/lib/modules/microvoip-dsl.bin annex=$ANNEX
		modprobe isdn_fbox_fon4
	fi

	if modprobe usbcore; then
		mount -t usbfs usbfs /proc/bus/usb
		modprobe usbahcicore AHCI_BaseAddress=0xbe008000 AHCI_RegisterOffset=0x4000 AHCI_IntLine=1
	fi

	if [ $HWRevision = 102 ]; then
		rmmod -f isdn_fbox_fon4 > /dev/null 2>&1
		rmmod -f ubik2 > /dev/null 2>&1
		rmmod -f tiatm > /dev/null 2>&1
	fi
}

load_modules_2_6_19_2() {
	piglet_bitfile=/lib/modules/microvoip_isdn_top.bit${HWRevision_BitFileCount}
	dect_firstlevelfile=/lib/modules/dectfw_firstlevel.hex
	dect_secondlevelfile=/lib/modules/dectfw_secondlevel.hex
	piglet_load_params=""

	case "$HWRevision" in
		122 | 139 ) # 7270 v1, v2
			piglet_load_params="piglet_enable_button=2"
			;;
		135 | 146 | 153 ) # W920V, IAD, 7570
			piglet_bitfile=/lib/modules/bitfile.bit${HWRevision_BitFileCount}
			piglet_load_params="piglet_enable_button=3"
			;;
	esac

	modprobe Piglet_noemif \
		piglet_bitfile=$piglet_bitfile \
		dect_firstlevelfile=$dect_firstlevelfile \
		dect_secondlevelfile=$dect_secondlevelfile \
		$piglet_load_params

	if modprobe usbcore; then
		mount -t usbfs usbfs /proc/bus/usb
		case "$HWRevision" in
			156 | 171 ) # 7390, 7340
				modprobe ehci_hcd
				modprobe ohci_hcd
				;;
			* )
				modprobe musb_hdrc
				;;
		esac
	fi
}

load_modules_2_6_28_10() {
	piglet_dectmode="0x0"
	dect_firstlevelfile=/lib/modules/dectfw_firstlevel.hex
	dect_secondlevelfile=/lib/modules/dectfw_secondlevel.hex
	piglet_load_params=""

	case "$HWRevision" in
		156 ) # 7390
			dect_firstlevelfile=/lib/modules/dectfw_firstlevel_488.hex
			dect_secondlevelfile=/lib/modules/dectfw_secondlevel_488.hex
			piglet_load_params="piglet_bitfile=/lib/modules/bitfile.bit${HWRevision_BitFileCount}"
			;;
		171 ) # 7340
			dect_firstlevelfile=/lib/modules/dectfw_firstlevel_488.hex
			dect_secondlevelfile=/lib/modules/dectfw_secondlevel_488.hex
			piglet_bitfile=/lib/modules/bitfile_isdn.bit${HWRevision_BitFileCount}
			piglet_potsbitfile=/lib/modules/bitfile_pots.bit${HWRevision_BitFileCount}
			#piglet_bitfilemode=`/bin/testvalue /var/flash/telefon_misc 4 2638`
			if [ -z $piglet_bitfilemode ]; then piglet_bitfilemode=0; fi
			piglet_load_params=" \
				piglet_bitfile=$piglet_bitfile \
				piglet_potsbitfile=$piglet_potsbitfile \
				piglet_bitfilemode=$piglet_bitfilemode \
			"
			;;
	esac

	modprobe Piglet_noemif \
		piglet_dectmode=$piglet_dectmode \
		dect_firstlevelfile=$dect_firstlevelfile \
		dect_secondlevelfile=$dect_secondlevelfile \
		$piglet_load_params

	if modprobe usbcore; then
		mount -t usbfs usbfs /proc/bus/usb
		case "$HWRevision" in
			156 | 171 ) # 7390, 7340
				modprobe ehci_hcd
				modprobe ohci_hcd
				;;
			172 | 175 | 179 ) # 7320, 3370, 7330
				modprobe ifxusb_host
				;;
			* )
				echo "HWRevision $HWRevision not supported here."
				;;
		esac
	fi
}

load_modules_2_6_32() {
	if [ -e /lib/modules/dectfw_firstlevel_488.hex ]; then
		# 7240, ...
		piglet_dectmode="0x0"
		dect_firstlevelfile=/lib/modules/dectfw_firstlevel_488.hex
		dect_secondlevelfile=/lib/modules/dectfw_secondlevel_488.hex
		piglet_load_params=" \
			piglet_bitfile=/lib/modules/bitfile_isdn.bit${HWRevision_BitFileCount} \
			piglet_use_pll3_clk=1 \
		"
	elif [ -e /lib/modules/dectfw_firstlevel_441.hex ]; then
		# 7320, 7330, 7360, 7360 SL, 7330 SL, ...
		piglet_dectmode="0x101"
		dect_firstlevelfile=/lib/modules/dectfw_firstlevel_441.hex
		dect_secondlevelfile=/lib/modules/dectfw_secondlevel_441.hex
		piglet_load_params=""
	else
		echo "HWRevision $HWRevision not supported here."
	fi

	if [ -n "$piglet_dectmode" ]; then
		modprobe Piglet_noemif \
			piglet_dectmode=$piglet_dectmode \
			dect_firstlevelfile=$dect_firstlevelfile \
			dect_secondlevelfile=$dect_secondlevelfile \
			$piglet_load_params
	fi

	modprobe usbcore 2>/dev/null
	mount -t usbfs usbfs /proc/bus/usb
	modprobe ifxusb_host 2>/dev/null
}

# Unmount stuff before pivot_root
unmount_virtualfs() {
	for i in /dev /proc/bus/usb /proc $SYSFS; do
		umount "$i"
	done
}

rmmodules() {
	# disable hotplug
	hotplugging disable

	for i in sd_mod scsi_mod usb-storage usbahcicore ehci_hcd ifxusb_host musb_hdrc ohci_hcd usbcore Piglet; do
		rmmod $i
	done

	# reenable hotplug
	hotplugging enable
}

cleanup_on_failure() {
	# unmount usbfs and filesystems given via parameter
	umount /proc/bus/usb "$@"
	# remove modules (requires /proc)
	rmmodules
	# unmount (remaining) virtual filesystems
	unmount_virtualfs
}

get_fstype() {
	# check if kernel modules are available
	# or if filesystem support is already built-in and available
	for i in ext2 ext3; do
		if [ -f "/lib/modules/$(uname -r)/kernel/fs/$i/$i.ko" ]; then
			eval $i=y
		elif [ -n "$(grep $i /proc/filesystems)" ]; then
			eval $i=y
		else
			eval $i=n
		fi
	done

	# check fs type on media if fstyp is available
	if [ -x /usr/bin/fstyp ]; then
		mediafs=$(/usr/bin/fstyp $DEVICE)
	fi

	# result matrix (only handle some special cases, thread all other cases as ext2)
	case "$ext2:$ext3:$mediafs" in
		n:y:*)
			FSTYPE="ext3" ;;		# only ext3 available
		y:y:*)
			FSTYPE=${mediafs:-ext2} ;;	# use fs type from media or ext2 as default
		*)
			FSTYPE="ext2" ;;		# defaults to ext2
	esac

	# user can force a fs type if kernel module is available
	if [ -n "$1" -a "$(eval echo \$$1)" == "y" ]; then
		FSTYPE=$1
	fi
}

get_env_var() {
	# Remove leading and trailing spaces from usbroot. Trailing invisible
	# garbage can be part of the value if the user defines the variable via
	# Putty directly at the Eva console prompt via copy & paste.
	usbroot=$(sed -rn "s/^$USBROOT_ENV_VAR[[:space:]]+([^[:space:]]+).*/\1/p" /proc/sys/urlader/environment)
	usbroot=${usbroot#$USBROOT_ENV_STRING=}
	DEVICE=${usbroot%%:*}
	USBPATH=${usbroot##*:}
	fstype=${usbroot#*:}; fstype=${fstype%:*}
	if [ "$fstype" == "$USBPATH" ]; then
		fstype=""
	fi
	MNTOPTIONS=$(sed -rn "s/^$USBROOT_ENV_VAR[[:space:]]+[^[:space:]]+[[:space:]]+([^[:space:]]+).*/\1/p" /proc/sys/urlader/environment)

	[ -n "$DEVICE" ] && get_fstype "$fstype"

	# Check variables for plausibility
	if [ "$1" == "check" ]; then
		# Device and USB path must be defined
		[ "$DEVICE" -a "$USBPATH" ] || return 1
		# Missing colon in $usbroot -> wrong variable expansion
		[ "$DEVICE" == "$USBPATH" ] && return 1
		# Device must be "/dev/..."
		[ "${DEVICE##/dev/*}" ] && return 1
		# Defined partition must be visible
		#cat /proc/partitions | grep -q "${DEVICE#/dev/}$" || return 1
	fi

	# Get HWRevision and HWRevision_BitFileCount from environment
	item_count=0
	for i in $(grep HWRevision /proc/sys/urlader/environment | tr '.' ' '); do
		case $item_count in
		1)
			HWRevision=$i
		;;
		3)
			HWRevision_BitFileCount=$i
			if [ $HWRevision_BitFileCount -eq 0 ]; then
				HWRevision_BitFileCount=""
			fi
		;;
		esac
		let item_count++
	done
}

start()
{
	# Mount proc and sysfs
	[ -e /proc/mounts ] || mount proc
	mount sysfs

	if ! get_env_var check; then
		echo "*** Missing or invalid 'usbroot' configuration in bootloader environment, exiting. ***"
		umount proc
		return 1
	fi

	if grep -q "^$DEVICE / " /proc/mounts; then
		echo "*** USB root already mounted, done. ***"
		return 0
	fi

	echo "*** Mounting USB root ... ***"
	if ! mount_usbroot; then
		cleanup_on_failure
		return 1
	fi

	# Ensure there is a directory which picks up the old root fs
	if [ ! -d "$MNTPOINT$USBPATH/$OLDROOT" ]; then
		# try to create, will fail on ro mounted filesystems
		mkdir -p "$MNTPOINT$USBPATH/$OLDROOT"
		# now check again and fail if still not present
		if [ ! -d "$MNTPOINT$USBPATH/$OLDROOT" ]; then
			echo "*** USB root rejected: $OLDROOT missing, not a directory or/and creation failed! ***"
			cleanup_on_failure "$MNTPOINT"
			return 1
		fi
	fi

	echo "*** Pivoting to USB root ... ***"
	[ -d "$MNTPOINT$USBPATH" ] && mount -o bind "$MNTPOINT$USBPATH" "$MNTPOINT"
	cd "$MNTPOINT"
	# To flush the hotplug cache finally we have to save the cache file in a location from
	# where it is read later. The /dev filesystem is the only one which can be mounted
	# conditionally (in /etc/init.d/rc.S), all other fs are mounted via a "mount -a" call.
	# /dev would be mounted as tmpfs so we prepared it above and move it now to its final
	# place.
	# Note: some avm daemons seem to grab the first mounted tmpfs to store some mmap files,
	#       this is why we can't use an additional tmpfs and have to 're-use' our tmpfs for /dev
	mount --move /oldroot "$MNTPOINT/dev"

	# some cleanup before pivot_root
	unmount_virtualfs

	if pivot_root . "$OLDROOT"; then
		# explicitly chdir to new root as advised by 'man pivot_root'
		cd /
		echo "*** USB root pivoting succeeded, done. ***"
	else
		echo "*** USB root pivoting failed, exiting. ***"
		# discard hotplug cache
		hotplugging discard
		cleanup_on_failure "$MNTPOINT" "$DEVICE"
		return 1
	fi
}

if [ "$PPID" == "0" ]; then
	echo "*** $0 called as an init process, start setting up USB root ... ***"
	start
	echo "*** Switching to init ... ***"
	exec init >/dev/console 2>&1 <&1
fi

case $1 in
	""|load)
		# flush hotplug cache
		hotplugging flush

		# register with web frontend
		modreg cgi 'usbroot' 'USB Root'

		# disable status control buttons
		modreg daemon --disable usbroot

		# cleanup if usb root is running
		get_env_var
		if grep -q "^$DEVICE / " /proc/mounts; then
			cd /
			# unmount usb filesystem in old root
			[ -d "$OLDROOT$MNTPOINT" ] && umount "$OLDROOT$MNTPOINT"

			if [ "$USBROOT_UNMOUNTOLDROOT" == 'yes' ]; then
				# unmount old squashfs root
				[ -d "$OLDROOT" ] && umount "$OLDROOT"
			fi
		fi
		;;
	start)
		echo "Starting USB root is only possible during boot up. Nothing done."
		exit 1
		;;
	stop)
		echo "Stopping USB root is not possible. Deactivate and reboot."
		exit 1
		;;
	restart)
		# automatically called when storing parameters via web interface, do nothing
		;;
	unload)
		modunreg daemon usbroot
		modunreg cgi 'usbroot'
		;;
	status)
		get_env_var
		grep -q "^$DEVICE / " /proc/mounts \
			&& echo 'running' \
			|| echo 'stopped'
		;;
	on)
		. kernel_args
		newval=$(ka_getArgs | sed -r 's/(.*[^ ])?( *init=[^ ]*)(.*)/\1\3/ ; s/^ +(.*)/\1/')
		echo "kernel_args init=/etc/init.d/rc.usbroot $newval" > /proc/sys/urlader/environment
		echo "USB root switched on (reboot to apply)"
		;;
	off)
		. kernel_args
		newval=$(ka_getArgs | sed -r 's/(.*[^ ])?( *init=[^ ]*)(.*)/\1\3/ ; s/^ +(.*)/\1/')
		echo "kernel_args $newval" > /proc/sys/urlader/environment
		echo "USB root switched off (reboot to apply)"
		;;
	store)
		if [ -n "$2" ]; then
			echo "$USBROOT_ENV_VAR $USBROOT_ENV_STRING=$2 $3" > /proc/sys/urlader/environment
			echo "USB device name added to bootloader environment"
		else
			usbroot=$(sed -nr "s/^$USBROOT_ENV_VAR[[:space:]]+(.*)/\1/p" /proc/sys/urlader/environment)
			usbroot=${usbroot#*=}
			[ -n "$usbroot" ] \
				&& echo "$usbroot" \
				|| echo "USB root variable is currently empty"
		fi
		;;
	nostore)
		echo "$USBROOT_ENV_VAR" > /proc/sys/urlader/environment
		echo "USB variable removed from bootloader environment"
		;;
	scsi|scsi_device|scsi_host|block|usb)
		hotplugging $1
		;;
	*)
		usage >&2
		exit 1
		;;
esac