#!/bin/sh

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

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,
# USA.

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/'
}

check_dev_mtdblock() {
	if [ ! -b "$1" ]; then
		error "$1 is not a block device"
	fi
}

mtdsize() {
	size=$(grep "\"$1\"" /proc/mtd | cut -d " " -f 2)
	printf "%d" 0x$size
}

check_subarch() {
	if [ -n "$subarch" ] && [ "$subarch" != "$1" ]; then
		echo "Kernel $kfile does not match your subarchitecture" >&2
		echo "$1, therefore not writing it to flash." >&2
		exit 0
	fi
}

check_size() {
	if [ $2 -gt $3 ]; then
		error "The $1 doesn't fit in flash."
	fi
}

# 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 [ "x$1" = "x--machine" ]; then
	machine=$2
	shift 2
else
	machine=$(grep "^Hardware" /proc/cpuinfo | sed 's/Hardware\s*:\s*//')
fi

if [ "x$1" = "x--supported" ]; then
	case "$machine" in
		"Buffalo Linkstation LiveV3 (LS-CHL)")	exit 0 ;;
		"Buffalo Linkstation Mini")		exit 0 ;;
		"Buffalo Linkstation Pro/Live")		exit 0 ;;
		"Buffalo/Revogear Kurobox Pro")		exit 0 ;;
		"D-Link DNS-323")			exit 0 ;;
		"GLAN Tank")				exit 0 ;;
		"GTA02")				exit 0 ;;
		"HP t5325 Thin Client")			exit 0 ;;
		"HP Media Vault mv2120")		exit 0 ;;
		"Linksys NSLU2")			exit 0 ;;
		"Marvell GuruPlug Reference Board")	exit 0 ;;
		"Marvell OpenRD Base Board")		exit 0 ;;
		"Marvell OpenRD Client Board")		exit 0 ;;
		"Marvell OpenRD Ultimate Board")	exit 0 ;;
		"Marvell SheevaPlug Reference Board")	exit 0 ;;
		"Marvell eSATA SheevaPlug Reference Board")	exit 0 ;;
		"QNAP TS-109/TS-209" | "QNAP TS-409")	exit 0 ;;
		"QNAP TS-119/TS-219")			exit 0 ;;
		"QNAP TS-41x")				exit 0 ;;
		"Seagate FreeAgent DockStar")		exit 0 ;;
		"Thecus N2100")				exit 0 ;;
		"Lanner EM7210")			exit 0 ;;
		*)					exit 1 ;;
	esac
fi

if [ -n "$1" ]; then
	kvers="$1"
	kfile="/boot/vmlinuz-$kvers"
	ifile="/boot/initrd.img-$kvers"
	desc="Debian kernel $1"
	idesc="Debian ramdisk $1"
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
	desc="Debian kernel"
	idesc="Debian ramdisk"
fi

if [ ! -e $kfile ] || [ ! -e $ifile ]; then
	error "Can't find $kfile and $ifile"
fi
kfilesize=$(wc -c "$kfile" | awk '{print $1}')
ifilesize=$(wc -c "$ifile" | awk '{print $1}')

# Extract the subarchitecture from the kernel name
if [ -L "$kfile" ]; then
	kfile=$(readlink -e "$kfile")
fi
subarch=$(echo "$kfile" | sed -e 's/.*-//')

case "$machine" in
	"Buffalo Linkstation LiveV3 (LS-CHL)" | "Buffalo Linkstation Mini" | "Buffalo Linkstation Pro/Live" | "Buffalo/Revogear Kurobox Pro")
		check_subarch "orion5x"
		tmpdir="$(mktemp -d)"
		printf "Generating kernel u-boot image... " >&2
		case "$machine" in
			"Buffalo Linkstation LiveV3 (LS-CHL)")
				# Set machine id 2913 (0x0b61)
				devio > "$tmpdir/kernel" 'wl 0xe3a01c0b,4' 'wl 0xe3811061,4'
			;;
			"Buffalo Linkstation Mini")
				# Set machine id 1858 (0x0742)
				devio > "$tmpdir/kernel" 'wl 0xe3a01c07,4' 'wl 0xe3811042,4'
			;;
			"Buffalo Linkstation Pro/Live")
				# Set machine id 1585 (0x0631)
				devio > "$tmpdir/kernel" 'wl 0xe3a01c06,4' 'wl 0xe3811031,4'
			;;
			"Buffalo/Revogear Kurobox Pro")
				# Set machine id 1509 (0x05e5)
				devio > "$tmpdir/kernel" 'wl 0xe3a01c05,4' 'wl 0xe38110e5,4'
			;;
		esac
		cat "$kfile" >> "$tmpdir/kernel"
		mkimage -A arm -O linux -T kernel -C none -a 0x00008000 \
			-e 0x00008000 -n "$desc" -d "$tmpdir/kernel" "$tmpdir/uImage.buffalo" >&2 1>/dev/null
		rm -f "$tmpdir/kernel"
		echo "done." >&2
		if [ -e /boot/uImage.buffalo ]; then
			echo "Creating backup of /boot/uImage.buffalo." >&2
			mv /boot/uImage.buffalo /boot/uImage.buffalo.bak
		fi
		echo "Creating new /boot/uImage.buffalo." >&2
		mv "$tmpdir/uImage.buffalo" /boot/
		printf "Generating initrd u-boot image... " >&2
		mkimage -A arm -O linux -T ramdisk -C gzip -a 0x0 \
			-e 0x0 -n "$idesc" -d "$ifile" "$tmpdir/initrd.buffalo" >&2 1>/dev/null
		echo "done." >&2
		if [ -e /boot/initrd.buffalo ]; then
			echo "Creating backup of /boot/initrd.buffalo." >&2
			mv /boot/initrd.buffalo /boot/initrd.buffalo.bak
		fi
		echo "Creating new /boot/initrd.buffalo." >&2
		mv "$tmpdir/initrd.buffalo" /boot/
		rmdir "$tmpdir"
	;;
	"D-Link DNS-323")
		check_subarch "orion5x"
		check_mtd
		imtd=$(mtdblock "File System")
		if [ -z "$imtd" ]; then
			error "Cannot find mtd partition 'File System'"
		fi
		check_dev_mtdblock "$imtd"
		imtdsize=$(mtdsize "File System")
		check_size "File System" $((ifilesize + 64)) $imtdsize
		kmtd=$(mtdblock "Linux Kernel")
		if [ -z "$kmtd" ]; then
			error "Cannot find mtd partition 'Linux Kernel'"
		fi
		check_dev_mtdblock "$kmtd"
		kmtdsize=$(mtdsize "Linux Kernel")
		check_size "Linux Kernel" $(($kfilesize + 8 + 64)) $kmtdsize
		printf "Generating kernel u-boot image... " >&2
		tmpdir="$(mktemp -d)"
		# Set machine id 1542 (0x0606)
		devio > "$tmpdir/kernel" 'wl 0xe3a01c06,4' 'wl 0xe3811006,4'
		cat "$kfile" >> "$tmpdir/kernel"
		mkimage -A arm -O linux -T kernel -C none -a 0x00008000 \
			-e 0x00008000 -n "$desc" -d "$tmpdir/kernel" "$tmpdir/uImage" >&2 1>/dev/null
		rm -f "$tmpdir/kernel"
		echo "done." >&2
		printf "Flashing kernel... " >&2
		cat "$tmpdir/uImage" > "$kmtd" || error "failed."
		echo "done." >&2
		rm -f "$tmpdir/uImage"
		printf "Generating initramfs u-boot image... " >&2
		mkimage -A arm -O linux -T ramdisk -C gzip -a 0x00800000 \
			-e 0x00800000 -n "$idesc" -d "$ifile" "$tmpdir/uInitrd" >&2 1>/dev/null
		echo "done." >&2
		printf "Flashing initramfs... " >&2
		cat "$tmpdir/uInitrd" > "$imtd" || error "failed."
		echo "done." >&2
		rm -f "$tmpdir/uInitrd"
		rmdir "$tmpdir"
	;;
	"GLAN Tank")
		rm -f /boot/initrd /boot/zImage
		ln -s "$(basename "$ifile")" /boot/initrd
		(
			# Set machine id 1100 (0x044c)
			devio 'wl 0xe3a01c04,4' 'wl 0xe381104c,4'
			cat "$kfile"
		) > /boot/zImage
	;;
	"GTA02")
		check_subarch "s3c24xx"
		tmpdir="$(mktemp -d)"
		printf "Generating u-boot image..." >&2
		cp "$kfile" "$tmpdir/kernel"
		# Hack to work around a bug in some U-Boot versions:
		if [ $(($(stat -c '%s' "$tmpdir/kernel") % 4)) -eq 0 ]; then
			echo >> "$tmpdir/kernel"
		fi
		mkimage -A arm -O linux -T multi -C none -n "$desc" -a 0x30008000 \
			-e 0x30008000 -d "$tmpdir/kernel":"$ifile" "$tmpdir/uImage" >&2 1>/dev/null
		rm -f "$tmpdir/kernel"
		echo "done." >&2
		if [ -e /boot/uImage.bin ]; then
			echo "Creating backup of /boot/uImage.bin." >&2
			mv /boot/uImage.bin /boot/uImage.bin.bak
		fi
		mv "$tmpdir/uImage" /boot/uImage.bin
		rmdir $tmpdir
	;;
	"HP t5325 Thin Client" | "HP Media Vault mv2120")
		case "$machine" in
			"HP t5325 Thin Client")
				check_subarch "kirkwood"
			;;
			"HP Media Vault mv2120")
				check_subarch "orion5x"
			;;
		esac
		tmpdir="$(mktemp -d)"
		printf "Generating kernel u-boot image... " >&2
		case "$machine" in
			"HP t5325 Thin Client")
				# Set machine id 2846 (0x0b1e)
				devio > "$tmpdir/kernel" 'wl 0xe3a01c0b,4' 'wl 0xe381101e,4'
			;;
			"HP Media Vault mv2120")
				# Set machine id 1693 (0x069d)
				devio > "$tmpdir/kernel" 'wl 0xe3a01c06,4' 'wl 0xe381109d,4'
			;;
		esac
		cat "$kfile" >> "$tmpdir/kernel"
		mkimage -A arm -O linux -T multi -C none -n "$desc" -a 0x01600000 \
			-e 0x01600000 -d "$tmpdir/kernel:$ifile" "$tmpdir/uImage" >&2 1>/dev/null
		rm -f "$tmpdir/kernel"
		echo "done." >&2
		if [ -e /boot/uImage ]; then
			echo "Creating backup of /boot/uImage." >&2
			mv /boot/uImage /boot/uImage.bak
		fi
		echo "Creating new /boot/uImage." >&2
		mv "$tmpdir/uImage" /boot/
		rmdir "$tmpdir"
	;;
	"Linksys NSLU2")
		check_subarch "ixp4xx"
		case "$(dpkg --print-architecture)" in
			arm|armel)
				little_endian=1
			;;
			armeb)
				little_endian=0
			;;
		esac
		check_mtd
		fismtd=$(mtdblock "FIS directory")
		if [ -z "$fismtd" ]; then
			error "Cannot find mtd FIS directory"
		fi
		kmtd=$(mtdblock Kernel)
		if [ -z "$kmtd" ]; then
			error "Cannot find mtd partition 'Kernel'"
		fi
		check_dev_mtdblock "$kmtd"
		kmtdsize=$(mtdsize "Kernel")
		check_size "kernel" $(($kfilesize + 16 + 16)) $kmtdsize
		imtd=$(mtdblock Ramdisk)
		if [ -z "$imtd" ]; then
			error "Cannot find mtd partition 'Ramdisk'"
		fi
		check_dev_mtdblock "$imtd"
		imtdsize=$(mtdsize "Ramdisk")
		check_size "ramdisk" $(($ifilesize + 16)) $imtdsize
		# 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 "<<$fismtd" '
			<= $ 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
		ksize1=$(expr $boundary - $offset - 16)
		tmpdir="$(mktemp -d)"
		printf "Flashing kernel: " >&2
		(
			sercomm_header $(expr $kfilesize + 16)
			dd if="$kfile" of="$tmpdir/kpart1" bs=$ksize1 count=1 2>/dev/null
			nslu2_swap "$tmpdir/kpart1"
			rm -f "$tmpdir/kpart1"
			sercomm_header 131072
			dd if="$kfile" of="$tmpdir/kpart2" ibs=$ksize1 skip=1 2>/dev/null
			nslu2_swap "$tmpdir/kpart2"
			rm -f "$tmpdir/kpart2"
		) > "$kmtd" || error "failed."
		echo "done." >&2
		printf "Flashing initramfs: " >&2
		dd if="$ifile" of="$tmpdir/initrd" ibs=$(($imtdsize - 16)) conv=sync 2>/dev/null
		(
			sercomm_header $ifilesize
			nslu2_swap "$tmpdir/initrd"
			rm -f "$tmpdir/initrd"
		) > "$imtd" || error "failed."
		echo "done." >&2
		rmdir "$tmpdir"
	;;
	"Marvell GuruPlug Reference Board" | "Marvell OpenRD Base Board" | "Marvell OpenRD Client Board" | "Marvell OpenRD Ultimate Board" | "Marvell SheevaPlug Reference Board" | "Marvell eSATA SheevaPlug Reference Board" | "Seagate FreeAgent DockStar")
		check_subarch "kirkwood"
		tmpdir="$(mktemp -d)"
		printf "Generating kernel u-boot image... " >&2
		mkimage -A arm -O linux -T kernel -C none -a 0x00008000 \
			-e 0x00008000 -n "$desc" -d "$kfile" "$tmpdir/uImage" >&2 1>/dev/null
		echo "done." >&2
		if [ -e /boot/uImage ]; then
			echo "Creating backup of /boot/uImage." >&2
			mv /boot/uImage /boot/uImage.bak
		fi
		echo "Creating new /boot/uImage." >&2
		mv "$tmpdir/uImage" /boot/
		printf "Generating initrd u-boot image... " >&2
		mkimage -A arm -O linux -T ramdisk -C gzip -a 0x0 \
			-e 0x0 -n "$idesc" -d "$ifile" "$tmpdir/uInitrd" >&2 1>/dev/null
		echo "done." >&2
		if [ -e /boot/uInitrd ]; then
			echo "Creating backup of /boot/uInitrd." >&2
			mv /boot/uInitrd /boot/uInitrd.bak
		fi
		echo "Creating new /boot/uInitrd." >&2
		mv "$tmpdir/uInitrd" /boot/
		rmdir "$tmpdir"
	;;
	"QNAP TS-109/TS-209" | "QNAP TS-119/TS-219" | "QNAP TS-409" | "QNAP TS-41x")
		case "$machine" in
			"QNAP TS-109/TS-209" | "QNAP TS-409")
				check_subarch "orion5x"
			;;
			"QNAP TS-119/TS-219" | "QNAP TS-41x")
				check_subarch "kirkwood"
			;;
		esac
		check_mtd
		imtd=$(mtdblock "RootFS1")
		if [ -z "$imtd" ]; then
			error "Cannot find mtd partition 'RootFS1'"
		fi
		check_dev_mtdblock "$imtd"
		imtdsize=$(mtdsize "RootFS1")
		check_size "RootFS1" $ifilesize $imtdsize
		kmtd=$(mtdblock Kernel)
		if [ -z "$kmtd" ]; then
			error "Cannot find mtd partition 'Kernel'"
		fi
		check_dev_mtdblock "$kmtd"
		kmtdsize=$(mtdsize "Kernel")
		check_size "Kernel" $(($kfilesize + 8 + 64)) $kmtdsize
		printf "Generating kernel u-boot image... " >&2
		tmpdir="$(mktemp -d)"
		case "$machine" in
			"QNAP TS-109/TS-209")
				# Set machine id 1565 (0x061d)
				devio > "$tmpdir/kernel" 'wl 0xe3a01c06,4' 'wl 0xe381101d,4'
			;;
			"QNAP TS-119/TS-219")
				# Set machine id 2139 (0x085b)
				devio > "$tmpdir/kernel" 'wl 0xe3a01c08,4' 'wl 0xe381105b,4'
			;;
			"QNAP TS-409")
				# Set machine id 1601 (0x0641)
				devio > "$tmpdir/kernel" 'wl 0xe3a01c06,4' 'wl 0xe3811041,4'
			;;
			"QNAP TS-41x")
				# Set machine id 2502 (0x09c6)
				devio > "$tmpdir/kernel" 'wl 0xe3a01c09,4' 'wl 0xe38110c6,4'
			;;
		esac
		cat "$kfile" >> "$tmpdir/kernel"
		mkimage -A arm -O linux -T kernel -C none -a 0x00008000 \
			-e 0x00008000 -n "$desc" -d "$tmpdir/kernel" "$tmpdir/uImage" >&2 1>/dev/null
		rm -f "$tmpdir/kernel"
		echo "done." >&2
		printf "Flashing kernel... " >&2
		cat "$tmpdir/uImage" > "$kmtd" || error "failed."
		echo "done." >&2
		rm -f "$tmpdir/uImage"
		rmdir "$tmpdir"
		printf "Flashing initramfs... " >&2
		pad=$(expr $imtdsize - $ifilesize)
		(
			cat "$ifile"
			dd if=/dev/zero bs=$pad count=1 2>/dev/null
		) > "$imtd" || error "failed."
		echo "done." >&2
	;;
	"Thecus N2100")
		check_subarch "iop32x"
		check_mtd
		imtd=$(mtdblock ramdisk)
		if [ -z "$imtd" ]; then
			error "Cannot find mtd partition 'ramdisk'"
		fi
		check_dev_mtdblock "$imtd"
		imtdsize=$(mtdsize "ramdisk")
		check_size "ramdisk" $ifilesize $imtdsize
		kmtd=$(mtdblock kernel)
		if [ -z "$kmtd" ]; then
			error "Cannot find mtd partition 'kernel'"
		fi
		check_dev_mtdblock "$kmtd"
		kmtdsize=$(mtdsize "kernel")
		check_size "kernel" $(($kfilesize + 8)) $kmtdsize
		printf "Flashing kernel... " >&2
		(
			# Set machine id 1101 (0x044d)
			devio 'wl 0xe3a01c04,4' 'wl 0xe381104d,4'
			cat "$kfile"
		) > "$kmtd" || error "failed."
		echo "done." >&2
		printf "Flashing initramfs... " >&2
		pad=$(expr $imtdsize - $ifilesize)
		(
			cat "$ifile"
			dd if=/dev/zero bs=$pad count=1 2>/dev/null
		) > "$imtd" || error "failed."
		echo "done." >&2
	;;
	"Lanner EM7210")
		# Really: Intel SS4000-e and compatibles
		check_subarch "iop32x"
		check_mtd
		imtd=$(mtdblock "ramdisk.gz")
		if [ -z "$imtd" ]; then
			error "Cannot find mtd partition 'ramdisk.gz'"
		fi
		check_dev_mtdblock "$imtd"
		imtdsize=$(mtdsize "ramdisk.gz")
		check_size "ramdisk" $ifilesize $imtdsize
		kmtd=$(mtdblock zImage)
		if [ -z "$kmtd" ]; then
			error "Cannot find mtd partition 'zImage'"
		fi
		kmtdsize=$(mtdsize "zImage")
		check_size "zImage" $(($kfilesize + 8)) $kmtdsize
		printf "Flashing kernel..." >&2
		(
			# Set machine ID 1212 (0x04bc)
			devio 'wl 0xe3a01c04,4' 'wl 0xe38110bc,4'
			cat "$kfile"
		) > "$kmtd" || error "failed."
		echo "done." >&2
		printf "Flashing initramfs... " >&2
		pad=$(expr $imtdsize - $ifilesize)
		(
			cat "$ifile"
			dd if=/dev/zero bs=$pad count=1 2>/dev/null
		) > "$imtd" || error "failed."
		echo "done." >&2
	;;
	*)
		error "Unsupported platform."
	;;
esac

