#!/bin/sh

# Copyright (C) 2006  Joey Hess  <joeyh@debian.org>
# Copyright (C) 2006  Martin Michlmayr <tbm@cyrius.com>

# This code is covered by the GNU General Public License.

set -e

error() {
	echo "$@" >&2
	exit 1
}

check_mtd() {
	if [ ! -e /proc/mtd ]; then
		error "/proc/mtd doesn't exist"
	fi
}

mtdblock() {
	grep "$1" /proc/mtd | cut -d: -f 1 | sed 's/mtd/\/dev\/mtdblock/'
}

# See http://www.nslu2-linux.org/wiki/Info/BootFlash -- the NSLU2 uses a
# 16 byte MTD header, the first four bytes (big endian) give the length of
# the remainder of the image, and the remaining bytes are zero.  Generate
# this header.
sercomm_header() {
	perl -e 'print pack("N4", shift)' "$1"
}

nslu2_swap() {
	if [ "$little_endian" ]; then
		devio "<<"$1 "xp $,4"
	else
		cat $1
	fi
}


if [ -n "$1" ]; then
	kvers="$1"
	kfile=/boot/vmlinuz-$kvers
	ifile=/boot/initrd.img-$kvers
else
	if [ -e /vmlinuz ]; then
		kfile=/vmlinuz
		ifile=/initrd.img
	elif [ -e /boot/vmlinuz ]; then
		kfile=/boot/vmlinuz
		ifile=/boot/initrd.img
	else
		error "Cannot find a default kernel in /vmlinuz or /boot/vmlinuz"
	fi
fi

if [ ! -e $kfile ] || [ ! -e $ifile ]; then
	error "Can't find $kfile and $ifile"
fi

machine=$(grep "^Hardware" /proc/cpuinfo | sed 's/Hardware\s*:\s*//')
case "$machine" in
	"Linksys NSLU2")
		case "$(dpkg --print-architecture)" in
			arm|armel)
				little_endian=1
			;;
			armeb)
				little_endian=0
			;;
		esac
		mtdfis=$(mtdblock "FIS directory")
		if [ -z "$mtdfis" ]; then
			error "Cannot find mtd FIS directory"
		fi
		mtdkernel=$(mtdblock Kernel)
		if [ -z "$mtdkernel" ]; then
			error "Cannot find mtd partition 'Kernel'"
		fi
		mtdramdisk=$(mtdblock Ramdisk)
		if [ -z "$mtdramdisk" ]; then
			error "Cannot find mtd partition 'Ramdisk'"
		fi
		# The following devio magic parses the FIS directory to
		# obtain the size, offset and name of each partition.  This
		# used used to obtain the offset of the Kernel partition.
		offset=$(echo "$(devio "<<$mtdfis" '
			<= $ 0x20000 -
			L= 0x1000
			$( 1
				# 0xff byte in name[0] ends the partition table
				$? @ 255 =
				# output size base name
				<= f15+
				.= b 0xfffffff &
				<= f4+
				.= b
				pf "%lu %lu "
				<= f28-
				cp 16
				pn
				<= f240+
				L= L256-
			$) L255>')" |
			while read a b c; do
				if [ "$c" = "Kernel" ]; then
					echo $b
				fi
			done)
		# The Kernel partition, starting at $offset, is divided into
		# two areas at $boundary.  We therefore need to split the
		# kernel into two and write them to flash with two Sercomm
		# headers.
		boundary=1441792 # 0x00160000
		ksize=$(wc -c $kfile | awk '{print $1}')
		ksize1=$(expr $boundary - $offset - 16)
		tmp=$(tempfile)
		printf "Flashing kernel: " >&2
		(
			sercomm_header $(expr $ksize + 16)
			dd if=$kfile of=$tmp bs=$ksize1 count=1 2>/dev/null
			nslu2_swap $tmp
			sercomm_header 131072
			dd if=$kfile of=$tmp ibs=$ksize1 skip=1 2>/dev/null
			nslu2_swap $tmp
			rm -f $tmp
		) > "$mtdkernel" || error "failed."
		echo "done." >&2
		printf "Flashing initramfs: " >&2
		size=$(grep "Ramdisk" /proc/mtd | cut -d " " -f 2)
		size=$(printf "%d" 0x$size)
		isize=$(wc -c $ifile | awk '{print $1}')
		cat $ifile > $tmp
		pad=$(expr $size - $isize - 16)
		if [ "$pad" -gt 0 ]; then
			dd if=/dev/zero bs=$pad count=1 2>/dev/null >> $tmp
		fi
		(
			sercomm_header $isize
			nslu2_swap $tmp
			rm -f $tmp
		) > $mtdramdisk || error "failed."
		echo "done." >&2
	;;
	"Thecus N2100")
		check_mtd
		mtdramdisk=$(mtdblock ramdisk)
		if [ -z "$mtdramdisk" ]; then
			error "Cannot find mtd partition 'ramdisk'"
		fi
		mtdkernel=$(mtdblock kernel)
		if [ -z "$mtdkernel" ]; then
			error "Cannot find mtd partition 'kernel'"
		fi
		printf "Flashing kernel... " >&2
		(
			devio 'wl 0xe3a01c04,4' 'wl 0xe381104d,4'
			cat $kfile
		) > $mtdkernel || error "failed."
		echo "done." >&2
		printf "Flashing initramfs... " >&2
		size=$(grep "ramdisk" /proc/mtd | cut -d " " -f 2)
		size=$(printf "%d" 0x$size)
		isize=$(wc -c $ifile | awk '{print $1}')
		pad=$(expr $size - $isize)
		(
			cat $ifile
			dd if=/dev/zero bs=$pad count=1 2>/dev/null
		) > $mtdramdisk || error "failed."
		echo "done." >&2
	;;
	*)
		error "Unsupported platform."
	;;
esac

