# Copyright (C) 2006 Joey Hess # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Martin Michlmayr # Copyright (C) 2011 Loïc Minier # Copyright (C) 2011 Julian Andres Klode # 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. BOOTSCRIPTS_DIR="${FK_CHECKOUT:-$FK_DIR}/bootscript" MACHINE_DB="$(cat "${FK_CHECKOUT:-$FK_DIR}/db/"*.db)" PROC_CPUINFO="/proc/cpuinfo" PROC_MTD="/proc/mtd" PROC_DT="/proc/device-tree/model" error() { echo "$@" >&2 exit 1 } mtdblock() { local mtdname="$1" sed -rn "s,^mtd([^:]*).*\"$mtdname\"\$,/dev/mtdblock\\1,p" "$PROC_MTD" } check_block_dev() { local dev="$1" if [ ! -b "$dev" ]; then error "$dev is not a block device" fi } mtdsize() { local mtdname="$1" size=$(grep "\"$mtdname\"$" "$PROC_MTD" | cut -d " " -f 2) printf "%d" "0x$size" } check_kflavors() { local kfile_suffix="$1" shift if [ -z "$kfile_suffix" ]; then return 0 fi for kflavor; do if [ "$kfile_suffix" = "$kflavor" ]; then return 0 fi done return 1 } check_mtd_size() { local mtd_name="$1" local required_size="$2" local actual_size="$3" if [ $required_size -gt $actual_size ]; then error "Not enough space in MTD $mtd_name (need $required_size but is actually $actual_size)." fi } check_supported() { local machine="$1" local field local value echo "$MACHINE_DB" | { while read field value; do if [ "$field" = "Machine:" ] && [ "$value" = "$machine" ]; then return 0 fi done return 1 } } get_root_uuid() { echo $(blkid -o value -s UUID $(mount | grep "on /${1%/} " | tail -n1 | cut -d' ' -f1)) } get_cpuinfo_hardware() { if [ -f "$PROC_DT" ]; then cat "$PROC_DT" else grep "^Hardware" "$PROC_CPUINFO" | sed 's/Hardware\s*:\s*//' fi } get_kfile_suffix() { local kfile="$1" local full_version=${kfile#*-} local flavour_abi=${full_version#*-} local flavour=${flavour_abi#*-} echo "$flavour" } # this is case-sensitive and doesn't support fields spanning multiple lines get_machine_field() { local machine="$1" local field_name="$2" local state="machine" local field local value echo "$MACHINE_DB" | { while read field value; do if [ "$state" = "machine" ] && [ "$field" = "Machine:" ] && [ "$value" = "$machine" ]; then state="fields" fi if [ "$state" = "fields" ]; then case "$field" in "${field_name}:") echo "$value" return 0 ;; "") state="machine" ;; esac fi done return 1 } } machine_uses_flash() { local machine="$1" if ! check_supported "$machine"; then # assume devices not explicitly listed are using flash return 0 fi if [ -n "$(get_machine_field "$machine" "Mtd-Kernel")" ] || [ -n "$(get_machine_field "$machine" "Mtd-Initrd")" ]; then return 0 fi return 1 } # output ARM instructions to set machine number; argument is the decimal # machine number as found in linux/arch/arm/tools/mach-types set_machine_id() { local machine_id="$1" local high local low if [ -z "$machine_id" ]; then return fi high="$(printf "%02x" $(($machine_id / 256)))" low="$(printf "%02x" $(($machine_id % 256)))" devio "wl 0xe3a01c$high,4" "wl 0xe38110$low,4" } gen_kernel() { local input="$1" local output="$2" local machine_id="$3" { set_machine_id "$machine_id" cat "$input" } >"$output" } gen_ubootenv() { ENVSTUBDIRS="/etc/flash-kernel/ubootenv.d /usr/share/flash-kernel/ubootenv.d" ENVSTUBS="$(find $ENVSTUBDIRS -type f -regex '.*/[0-9a-zA-Z_-]+' -printf '%f\n' | LC_ALL=C sort -u)" for file in $ENVSTUBS; do for dir in $ENVSTUBDIRS; do if [ -f $dir/$file ]; then cat $dir/$file break fi done done } append_dtb() { local kernel="$1" local dtb="$2" local output="$3" { cat "$kernel" cat "$dtb" } >"$output" } flash_kernel() { local input_file="$1" local output_mtd="$2" local machine_id="$3" printf "Flashing kernel... " >&2 gen_kernel "$input_file" "$output_mtd" "$machine_id" || error "failed." echo "done." >&2 } flash_initrd() { local input_file="$1" local output_mtd="$2" local pad="$3" printf "Flashing initramfs... " >&2 { cat "$input_file" if [ "$pad" -gt 0 ]; then dd if=/dev/zero bs="$pad" count=1 2>/dev/null fi } >"$output_mtd" || error "failed." echo "done." >&2 } mkimage_kernel() { local kaddr="$1" local kdesc="$2" local kdata="$3" local uimage="$4" printf "Generating kernel u-boot image... " >&2 mkimage -A arm -O linux -T kernel -C none -a "$kaddr" -e "$kaddr" \ -n "$kdesc" -d "$kdata" "$uimage" >&2 1>/dev/null echo "done." >&2 } mkimage_initrd() { local iaddr="$1" local idesc="$2" local idata="$3" local uinitrd="$4" printf "Generating initramfs u-boot image... " >&2 mkimage -A arm -O linux -T ramdisk -C gzip -a "$iaddr" -e "$iaddr" \ -n "$idesc" -d "$idata" "$uinitrd" >&2 1>/dev/null echo "done." >&2 } mkimage_script() { local saddr="$1" local sdesc="$2" local sdata="$3" local script="$4" local tdata="$tmpdir/$(basename $sdata)" local ubootenv="$(mktemp --tmpdir=$tmpdir)" gen_ubootenv > $ubootenv printf "Generating boot script u-boot image... " >&2 sed -e "/@@UBOOT_ENV_EXTRA@@/{ s/@@UBOOT_ENV_EXTRA@@//g r $ubootenv }" < $sdata > $tdata mkimage -A arm -O linux -T script -C none -a "$saddr" -e "$saddr" \ -n "$sdesc" -d "$tdata" "$script" >&2 1>/dev/null echo "done." >&2 } mkimage_multi() { local maddr="$1" local mdesc="$2" local kdata="$3" local idata="$4" local umulti="$5" printf "Generating u-boot image..." >&2 mkimage -A arm -O linux -T multi -C none -a "$maddr" -e "$maddr" \ -n "$mdesc" -d "$kdata:$idata" "$umulti" >&2 1>/dev/null echo "done." >&2 } backup_and_install() { local source="$1" local dest="$2" if [ -e "$dest" ]; then echo "Taking backup of $(basename "$dest")." >&2 mv "$dest" "$dest.bak" fi echo "Installing new $(basename "$dest")." >&2 mv "$source" "$dest" } # 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 } # XXX needs testsuite coverage abootimg_get_image_size() { local abootimg="$1" echo "$abootimg" | sed -rn 's/^\* image size = ([0-9]+) bytes.*/\1/p' } # XXX needs testsuite coverage android_flash() { local device="$1" if [ -z "$android_skip_initrd" ]; then printf "Flashing kernel and initramfs to $device... " >&2 abootimg -u "$device" -k "$kfile" -r "$ifile" >/dev/null || error "failed." echo "done." >&2 else printf "Flashing the kernel (skipping initrd) to $device... " >&2 abootimg -u "$device" -k "$kfile" >/dev/null || error "failed." echo "done." >&2 fi } include_only_flavors() { # include_only_flavors(flav1, flav2, flav3) # filter lines of input in uname -r format (X.Y.Z-N-flavor) # and filter-out anything that is not in the listed input # if exactly zero flavors are given, then assume everything is a match local cur_uname cur_flav allowed_flav while read cur_uname; do if [ $# -eq 0 ]; then echo "$cur_uname" else # could use cur_flav=$(get_kfile_suffix "$cur_uname") # but this is much faster. cur_flav=${cur_uname#*-*-} for allowed_flav in "$@"; do if [ "${cur_flav}" = "${allowed_flav}" ]; then echo "$cur_uname" break fi done fi done } main() { if [ "x$1" = "x--machine" ]; then machine="$2" shift 2 else machine="$(get_cpuinfo_hardware)" fi if [ "x$1" = "x--supported" ]; then if check_supported "$machine"; then exit 0 fi exit 1 fi # kernel + initrd installation/upgrade mode, with optional version kvers="$1" # if get_machine_field returns non-zero, then all flavors are allowed kflavors="$(get_machine_field "$machine" "Kernel-Flavors")" || : latest_version=$(linux-version list | include_only_flavors $kflavors | linux-version sort | tail -1) if [ -n "$kvers" ] && [ "$kvers" != "$latest_version" ]; then echo "Ignoring old or unknown version $kvers (latest is $latest_version)" >&2 exit 0 fi kvers="$latest_version" # accumulate multiple calls in a trigger to only run flash-kernel once; the # trigger will just call flash-kernel again with FLASH_KERNEL_NOTRIGGER set to # force a real run if [ -z "$FLASH_KERNEL_NOTRIGGER" ] && [ -n "$DPKG_MAINTSCRIPT_PACKAGE" ] && dpkg-trigger --check-supported 2>/dev/null; then # flash-kernel trigger isn't disabled, and we're called from # some package maintainer script (e.g. some postinst calls # flash-kernel), and dpkg-trigger is installed and confirms # that the running dpkg support triggers: we can use the # flash-kernel trigger (asynchronously) if dpkg-trigger --no-await flash-kernel; then echo "flash-kernel: deferring update (trigger activated)" >&2 exit 0 fi # dpkg-trigger failed for some reason, proceed to a normal run fi kfile="/boot/vmlinuz-$kvers" ifile="/boot/initrd.img-$kvers" desc="kernel $kvers" idesc="ramdisk $kvers" if [ ! -e $kfile ] || [ ! -e $ifile ]; then error "Can't find $kfile or $ifile" fi kfilesize=$(stat -c '%s' "$kfile") ifilesize=$(stat -c '%s' "$ifile") if [ -L "$kfile" ]; then kfile=$(readlink -e "$kfile") fi kfile_suffix=$(get_kfile_suffix "$kfile") if ! check_supported "$machine"; then error "Unsupported platform." fi if [ -n "$kflavors" ]; then if ! check_kflavors "$kfile_suffix" $kflavors; then echo "Kernel suffix $kfile_suffix does not match any of the expected flavors ($kflavors), therefore not writing it to flash." >&2 exit 0 fi fi echo "flash-kernel: installing version $kvers" >&2 machine_id="$(get_machine_field "$machine" "Machine-Id")" || : method="$(get_machine_field "$machine" "Method")" || method="generic" mtd_kernel="$(get_machine_field "$machine" "Mtd-Kernel")" || : mtd_initrd="$(get_machine_field "$machine" "Mtd-Initrd")" || : dtb_name="$(get_machine_field "$machine" "DTB-Id")" || : dtb_kver="$(get_machine_field "$machine" "DTB-Kernel-Version")" || : ukaddr="$(get_machine_field "$machine" "U-Boot-Kernel-Address")" || : uiaddr="$(get_machine_field "$machine" "U-Boot-Initrd-Address")" || : umaddr="$(get_machine_field "$machine" "U-Boot-Multi-Address")" || : usaddr="$(get_machine_field "$machine" "U-Boot-Script-Address")" || : usname="$(get_machine_field "$machine" "U-Boot-Script-Name")" || : boot_device="$(get_machine_field "$machine" "Boot-Device")" || : boot_kernel_path="$(get_machine_field "$machine" "Boot-Kernel-Path")" || : boot_initrd_path="$(get_machine_field "$machine" "Boot-Initrd-Path")" || : boot_script_path="$(get_machine_field "$machine" "Boot-Script-Path")" || : boot_dtb_path="$(get_machine_field "$machine" "Boot-Dtb-Path")" || : boot_multi_path="$(get_machine_field "$machine" "Boot-Multi-Path")" || : android_boot_device="$(get_machine_field "$machine" "Android-Boot-Device")" || : android_skip_initrd="$(get_machine_field "$machine" "Android-Skip-Initrd")" || : if [ -n "$mtd_kernel" ] || [ -n "$mtd_initrd" ]; then if [ ! -e "$PROC_MTD" ]; then error "$PROC_MTD doesn't exist" fi fi if [ -n "$mtd_kernel" ]; then kmtd=$(mtdblock "$mtd_kernel") if [ -z "$kmtd" ]; then error "Cannot find mtd partition '$mtd_kernel'" fi check_block_dev "$kmtd" kmtdsize=$(mtdsize "$mtd_kernel") kreqsize=$kfilesize # setting the machine id prepends 8 bytes in front of the kernel if [ -n "$machine_id" ]; then kreqsize=$(($kreqsize + 8)) fi # encapsulating in an U-Boot image grows the size by 64 bytes if [ -n "$ukaddr" ]; then kreqsize=$(($kreqsize + 64)) fi check_mtd_size "$mtd_kernel" $kreqsize $kmtdsize fi if [ -n "$mtd_initrd" ]; then imtd=$(mtdblock "$mtd_initrd") if [ -z "$imtd" ]; then error "Cannot find mtd partition '$mtd_initrd'" fi check_block_dev "$imtd" imtdsize=$(mtdsize "$mtd_initrd") ireqsize=$ifilesize # encapsulating in an U-Boot image grows the size by 64 bytes if [ -n "$uiaddr" ]; then ireqsize=$(($ireqsize + 64)) fi check_mtd_size "$mtd_initrd" $ireqsize $imtdsize fi tmpdir="" boot_mnt_dir="" cleanups() { rm -rf "$tmpdir" if [ -d "$boot_mnt_dir" ]; then umount -l "$boot_mnt_dir" rmdir "$boot_mnt_dir" fi } trap cleanups EXIT HUP INT QUIT ILL KILL SEGV PIPE TERM self="$(basename "$0")" tmpdir="$(mktemp -dt "$self.XXXXXXXX")" case "$method" in "android") if abootimg -i "$android_boot_device" > /dev/null 2>&1; then part="$android_boot_device" else largest_size="-1" for p in "$android_boot_device"*[0-9]; do abootimg="$(LC_ALL=C abootimg -i "$p" 2>/dev/null || true)" image_size="$(abootimg_get_image_size "$abootimg")" if [ -n "$image_size" ] && [ "$image_size" -gt "$largest_size" ]; then part="$p" fi done fi if [ -z "$part" ]; then error "Couldn't find Android boot partition on $android_boot_device" fi android_flash "$part" ;; "generic") kernel="$kfile" initrd="$ifile" if [ -n "$machine_id" ]; then gen_kernel "$kernel" "$tmpdir/kernel" "$machine_id" kernel="$tmpdir/kernel" fi if [ -n "$dtb_kver" ]; then if dpkg --compare-versions "$kvers" ge "$dtb_kver" >/dev/null; then dtb="$(find /lib/firmware/$kvers/device-tree/ -name $dtb_name)" if [ ! -f "$dtb" ] ; then error "Couldn't find $dtb" fi append_dtb "$kernel" "$dtb" "$tmpdir/kernel.dtb" mv "$tmpdir/kernel.dtb" "$tmpdir/kernel" kernel="$tmpdir/kernel" fi fi if [ -n "$ukaddr" ]; then mkimage_kernel "$ukaddr" "$desc" "$kernel" \ "$tmpdir/uImage" kernel="$tmpdir/uImage" rm -f "$tmpdir/kernel" fi if [ -n "$umaddr" ]; then mkimage_multi "$umaddr" "$desc" "$kernel" "$initrd" \ "$tmpdir/uImage" rm -f "$tmpdir/kernel" fi if [ -n "$boot_device" ]; then check_block_dev "$boot_device" # don't use $tmpdir/boot as to not nuke it accidentally # if umount fails boot_mnt_dir="$(mktemp -dt "$self.XXXXXXXX")" mount "$boot_device" "$boot_mnt_dir" fi if [ -n "$boot_kernel_path" ]; then boot_kernel_path="$boot_mnt_dir/$boot_kernel_path" # don't mv the original kernel if [ "$kernel" != "$kfile" ]; then backup_and_install "$kernel" \ "$boot_kernel_path" else # TODO add support for kernel symlink : fi elif [ -n "$kmtd" ]; then flash_kernel "$tmpdir/uImage" "$kmtd" "" rm -f "$tmpdir/uImage" fi if [ -n "$dtb_name" ] && [ -n "$boot_dtb_path" ]; then dtb="$(find /lib/firmware/$kvers/device-tree/ -name $dtb_name)" if [ -f "$dtb" ]; then cp "$dtb" "$tmpdir/$dtb_name" dtb_path="$boot_mnt_dir/$boot_dtb_path" backup_and_install "$tmpdir/$dtb_name" "$dtb_path" fi fi if [ -n "$boot_multi_path" ]; then backup_and_install "$tmpdir/uImage" "$boot_multi_path" fi if [ -n "$uiaddr" ]; then mkimage_initrd "$uiaddr" "$idesc" "$initrd" \ "$tmpdir/uInitrd" initrd="$tmpdir/uInitrd" fi if [ -n "$boot_initrd_path" ]; then boot_initrd_path="$boot_mnt_dir/$boot_initrd_path" # don't mv the original initrd if [ "$initrd" != "$ifile" ]; then backup_and_install "$initrd" \ "$boot_initrd_path" else # TODO add support for initrd symlink : fi elif [ -n "$imtd" ]; then ipad=0 # padding isn't needed for U-Boot images if [ -z "$uiaddr" ]; then ipad=$(($imtdsize - $ireqsize)) fi flash_initrd "$initrd" "$imtd" $ipad rm -f "$tmpdir/uInitrd" fi if [ -n "$boot_script_path" ]; then case $usname in bootscr*) boot_script_path="$boot_mnt_dir/$boot_script_path" boot_script="$BOOTSCRIPTS_DIR/$usname" mkimage_script "$usaddr" "boot script" "$boot_script" \ "$tmpdir/boot.scr" boot_script="$tmpdir/boot.scr" backup_and_install "$boot_script" "$boot_script_path" ;; uEnvtxt*) VOLID=${VOLID:-"$(get_root_uuid)"} boot_script_in="$BOOTSCRIPTS_DIR/$usname" boot_script="$boot_mnt_dir/$usname" cp $boot_script_in $boot_script boot_script_path="$boot_mnt_dir/uEnv.txt" boot_script_env="$boot_mnt_dir/preEnv.txt" env_script="$tmpdir/preEnv.txt" [ -e /etc/default/flash-kernel ] && . /etc/default/flash-kernel echo "bootargs=$UBOOT_DEFAULTS root=UUID=$VOLID" > $env_script backup_and_install "$env_script" "$boot_script_env" backup_and_install "$boot_script" "$boot_script_path" ;; esac fi ;; "symlink") rm -f /boot/initrd /boot/zImage ln -s "$(basename "$ifile")" /boot/initrd gen_kernel "$kfile" "/boot/zImage" "$machine_id" ;; "multi") gen_kernel "$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_multi "$umaddr" "$desc" "$tmpdir/kernel" "$ifile" \ "$tmpdir/uImage" rm -f "$tmpdir/kernel" backup_and_install "$tmpdir/uImage" "$boot_multi_path" ;; "redboot") flash_kernel "$kfile" "$kmtd" "$machine_id" pad=$(($imtdsize - $ifilesize)) flash_initrd "$ifile" "$imtd" $pad ;; "slug") case "$(dpkg --print-architecture)" in arm|armel) little_endian=1 ;; armeb) little_endian=0 ;; esac mtd_fis="FIS directory" fismtd=$(mtdblock "$mtd_fis") if [ -z "$fismtd" ]; then error "Cannot find mtd partition '$mtd_fis'" fi check_mtd_size "$mtd_kernel" $(($kfilesize + 16 + 16)) \ $kmtdsize check_mtd_size "$mtd_initrd" $(($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=$(($boundary - $offset - 16)) printf "Flashing kernel: " >&2 { sercomm_header $(($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 ;; esac } # vim:noexpandtab shiftwidth=8 syntax=sh