1#!/bin/sh
2# Copyright (C) 2014 OpenWrt.org
3#
4
5. /lib/functions.sh
6
7# 'kernel' partition on NAND contains the kernel
8CI_KERNPART="kernel"
9
10# 'ubi' partition on NAND contains UBI
11CI_UBIPART="ubi"
12
13ubi_mknod() {
14	local dir="$1"
15	local dev="/dev/$(basename $dir)"
16
17	[ -e "$dev" ] && return 0
18
19	local devid="$(cat $dir/dev)"
20	local major="${devid%%:*}"
21	local minor="${devid##*:}"
22	mknod "$dev" c $major $minor
23}
24
25nand_find_volume() {
26	local ubidevdir ubivoldir
27	ubidevdir="/sys/devices/virtual/ubi/$1"
28	[ ! -d "$ubidevdir" ] && return 1
29	for ubivoldir in $ubidevdir/${1}_*; do
30		[ ! -d "$ubivoldir" ] && continue
31		if [ "$( cat $ubivoldir/name )" = "$2" ]; then
32			basename $ubivoldir
33			ubi_mknod "$ubivoldir"
34			return 0
35		fi
36	done
37}
38
39nand_find_ubi() {
40	local ubidevdir ubidev mtdnum
41	mtdnum="$( find_mtd_index $1 )"
42	[ ! "$mtdnum" ] && return 1
43	for ubidevdir in /sys/devices/virtual/ubi/ubi*; do
44		[ ! -d "$ubidevdir" ] && continue
45		cmtdnum="$( cat $ubidevdir/mtd_num )"
46		[ ! "$mtdnum" ] && continue
47		if [ "$mtdnum" = "$cmtdnum" ]; then
48			ubidev=$( basename $ubidevdir )
49			ubi_mknod "$ubidevdir"
50			echo $ubidev
51			return 0
52		fi
53	done
54}
55
56nand_get_magic_long() {
57	dd if="$1" skip=$2 bs=4 count=1 2>/dev/null | hexdump -v -n 4 -e '1/1 "%02x"'
58}
59
60get_magic_long_tar() {
61	( tar xf $1 $2 -O | dd bs=4 count=1 | hexdump -v -n 4 -e '1/1 "%02x"') 2> /dev/null
62}
63
64identify_magic() {
65	local magic=$1
66	case "$magic" in
67		"55424923")
68			echo "ubi"
69			;;
70		"31181006")
71			echo "ubifs"
72			;;
73		"68737173")
74			echo "squashfs"
75			;;
76		"d00dfeed")
77			echo "fit"
78			;;
79		"4349"*)
80			echo "combined"
81			;;
82		*)
83			echo "unknown $magic"
84			;;
85	esac
86}
87
88
89identify() {
90	identify_magic $(nand_get_magic_long "$1" "${2:-0}")
91}
92
93identify_tar() {
94	identify_magic $(get_magic_long_tar "$1" "$2")
95}
96
97nand_restore_config() {
98	sync
99	local ubidev=$( nand_find_ubi $CI_UBIPART )
100	local ubivol="$( nand_find_volume $ubidev rootfs_data )"
101	[ ! "$ubivol" ] &&
102		ubivol="$( nand_find_volume $ubidev rootfs )"
103	mkdir /tmp/new_root
104	if ! mount -t ubifs /dev/$ubivol /tmp/new_root; then
105		echo "mounting ubifs $ubivol failed"
106		rmdir /tmp/new_root
107		return 1
108	fi
109	mv "$1" "/tmp/new_root/sysupgrade.tgz"
110	umount /tmp/new_root
111	sync
112	rmdir /tmp/new_root
113}
114
115nand_upgrade_prepare_ubi() {
116	local rootfs_length="$1"
117	local rootfs_type="$2"
118	local has_kernel="${3:-0}"
119	local has_env="${4:-0}"
120
121	local mtdnum="$( find_mtd_index "$CI_UBIPART" )"
122	if [ ! "$mtdnum" ]; then
123		echo "cannot find ubi mtd partition $CI_UBIPART"
124		return 1
125	fi
126
127	local ubidev="$( nand_find_ubi "$CI_UBIPART" )"
128	if [ ! "$ubidev" ]; then
129		ubiattach -m "$mtdnum"
130		sync
131		ubidev="$( nand_find_ubi "$CI_UBIPART" )"
132	fi
133
134	if [ ! "$ubidev" ]; then
135		ubiformat /dev/mtd$mtdnum -y
136		ubiattach -m "$mtdnum"
137		sync
138		ubidev="$( nand_find_ubi "$CI_UBIPART" )"
139		[ "$has_env" -gt 0 ] && {
140			ubimkvol /dev/$ubidev -n 0 -N ubootenv -s 1MiB
141			ubimkvol /dev/$ubidev -n 1 -N ubootenv2 -s 1MiB
142		}
143	fi
144
145	local kern_ubivol="$( nand_find_volume $ubidev kernel )"
146	local root_ubivol="$( nand_find_volume $ubidev rootfs )"
147	local data_ubivol="$( nand_find_volume $ubidev rootfs_data )"
148
149	# remove ubiblock device of rootfs
150	local root_ubiblk="ubiblock${root_ubivol:3}"
151	if [ "$root_ubivol" -a -e "/dev/$root_ubiblk" ]; then
152		echo "removing $root_ubiblk"
153		if ! ubiblock -r /dev/$root_ubivol; then
154			echo "cannot remove $root_ubiblk"
155			return 1;
156		fi
157	fi
158
159	# kill volumes
160	[ "$kern_ubivol" ] && ubirmvol /dev/$ubidev -N kernel || true
161	[ "$root_ubivol" ] && ubirmvol /dev/$ubidev -N rootfs || true
162	[ "$data_ubivol" ] && ubirmvol /dev/$ubidev -N rootfs_data || true
163
164	# update kernel
165	if [ "$has_kernel" = "1" ]; then
166		if ! ubimkvol /dev/$ubidev -N kernel -s $kernel_length; then
167			echo "cannot create kernel volume"
168			return 1;
169		fi
170	fi
171
172	# update rootfs
173	local root_size_param
174	if [ "$rootfs_type" = "ubifs" ]; then
175		root_size_param="-m"
176	else
177		root_size_param="-s $rootfs_length"
178	fi
179	if ! ubimkvol /dev/$ubidev -N rootfs $root_size_param; then
180		echo "cannot create rootfs volume"
181		return 1;
182	fi
183
184	# create rootfs_data for non-ubifs rootfs
185	if [ "$rootfs_type" != "ubifs" ]; then
186		if ! ubimkvol /dev/$ubidev -N rootfs_data -m; then
187			echo "cannot initialize rootfs_data volume"
188			return 1
189		fi
190	fi
191	sync
192	return 0
193}
194
195nand_do_upgrade_success() {
196	local conf_tar="/tmp/sysupgrade.tgz"
197	
198	sync
199	[ -f "$conf_tar" ] && nand_restore_config "$conf_tar"
200	echo "sysupgrade successful"
201	reboot -f
202}
203
204# Flash the UBI image to MTD partition
205nand_upgrade_ubinized() {
206	local ubi_file="$1"
207	local mtdnum="$(find_mtd_index "$CI_UBIPART")"
208
209	[ ! "$mtdnum" ] && {
210		CI_UBIPART="rootfs"
211		mtdnum="$(find_mtd_index "$CI_UBIPART")"
212	}
213
214	if [ ! "$mtdnum" ]; then
215		echo "cannot find mtd device $CI_UBIPART"
216		reboot -f
217	fi
218
219	local mtddev="/dev/mtd${mtdnum}"
220	ubidetach -p "${mtddev}" || true
221	sync
222	ubiformat "${mtddev}" -y -f "${ubi_file}"
223	ubiattach -p "${mtddev}"
224	nand_do_upgrade_success
225}
226
227# Write the UBIFS image to UBI volume
228nand_upgrade_ubifs() {
229	local rootfs_length=`(cat $1 | wc -c) 2> /dev/null`
230
231	nand_upgrade_prepare_ubi "$rootfs_length" "ubifs" "0" "0"
232	
233	local ubidev="$( nand_find_ubi "$CI_UBIPART" )"
234	local root_ubivol="$(nand_find_volume $ubidev rootfs)"
235	ubiupdatevol /dev/$root_ubivol -s $rootfs_length $1
236
237	nand_do_upgrade_success
238}
239
240nand_upgrade_tar() {
241	local tar_file="$1"
242	local board_name="$(cat /tmp/sysinfo/board_name)"
243	local kernel_mtd="$(find_mtd_index $CI_KERNPART)"
244
245	local kernel_length=`(tar xf $tar_file sysupgrade-$board_name/kernel -O | wc -c) 2> /dev/null`
246	local rootfs_length=`(tar xf $tar_file sysupgrade-$board_name/root -O | wc -c) 2> /dev/null`
247
248	local rootfs_type="$(identify_tar "$tar_file" sysupgrade-$board_name/root)"
249
250	local has_kernel=1
251	local has_env=0
252
253	[ "$kernel_length" != 0 -a -n "$kernel_mtd" ] && {
254		tar xf $tar_file sysupgrade-$board_name/kernel -O | mtd write - $CI_KERNPART
255	}
256	[ "$kernel_length" = 0 -o ! -z "$kernel_mtd" ] && has_kernel=0
257
258	nand_upgrade_prepare_ubi "$rootfs_length" "$rootfs_type" "$has_kernel" "$has_env"
259
260	local ubidev="$( nand_find_ubi "$CI_UBIPART" )"
261	[ "$has_kernel" = "1" ] && {
262		local kern_ubivol="$(nand_find_volume $ubidev kernel)"
263	 	tar xf $tar_file sysupgrade-$board_name/kernel -O | \
264			ubiupdatevol /dev/$kern_ubivol -s $kernel_length -
265	}
266
267	local root_ubivol="$(nand_find_volume $ubidev rootfs)"
268	tar xf $tar_file sysupgrade-$board_name/root -O | \
269		ubiupdatevol /dev/$root_ubivol -s $rootfs_length -
270
271	nand_do_upgrade_success
272}
273
274# Recognize type of passed file and start the upgrade process
275nand_do_upgrade_stage2() {
276	local file_type=$(identify $1)
277
278	if type 'platform_nand_pre_upgrade' >/dev/null 2>/dev/null; then
279		platform_nand_pre_upgrade "$1"
280	fi
281
282	[ ! "$(find_mtd_index "$CI_UBIPART")" ] && CI_UBIPART="rootfs"
283
284	case "$file_type" in
285		"ubi")		nand_upgrade_ubinized $1;;
286		"ubifs")	nand_upgrade_ubifs $1;;
287		*)		nand_upgrade_tar $1;;
288	esac
289}
290
291nand_upgrade_stage2() {
292	[ $1 = "nand" ] && {
293		[ -f "$2" ] && {
294			touch /tmp/sysupgrade
295
296			killall -9 telnetd
297			killall -9 dropbear
298			killall -9 ash
299
300			kill_remaining TERM
301			sleep 3
302			kill_remaining KILL
303
304			sleep 1
305
306			if [ -n "$(rootfs_type)" ]; then
307				v "Switching to ramdisk..."
308				run_ramfs ". /lib/functions.sh; include /lib/upgrade; nand_do_upgrade_stage2 $2"
309			else
310				nand_do_upgrade_stage2 $2
311			fi
312			return 0
313		}
314		echo "Nand upgrade failed"
315		exit 1
316	}
317}
318
319nand_upgrade_stage1() {
320	[ -f /tmp/sysupgrade-nand-path ] && {
321		path="$(cat /tmp/sysupgrade-nand-path)"
322		[ "$SAVE_CONFIG" != 1 -a -f "$CONF_TAR" ] &&
323			rm $CONF_TAR
324
325		ubus call system nandupgrade "{\"path\": \"$path\" }"
326		exit 0
327	}
328}
329
330# Check if passed file is a valid one for NAND sysupgrade. Currently it accepts
331# 3 types of files:
332# 1) UBI - should contain an ubinized image, header is checked for the proper
333#    MAGIC
334# 2) UBIFS - should contain UBIFS partition that will replace "rootfs" volume,
335#    header is checked for the proper MAGIC
336# 3) TAR - archive has to include "sysupgrade-BOARD" directory with a non-empty
337#    "CONTROL" file (at this point its content isn't verified)
338#
339# You usually want to call this function in platform_check_image.
340#
341# $(1): board name, used in case of passing TAR file
342# $(2): file to be checked
343nand_do_platform_check() {
344	local board_name="$1"
345	local tar_file="$2"
346	local control_length=`(tar xf $tar_file sysupgrade-$board_name/CONTROL -O | wc -c) 2> /dev/null`
347	local file_type="$(identify $2)"
348
349	[ "$control_length" = 0 -a "$file_type" != "ubi" -a "$file_type" != "ubifs" ] && {
350		echo "Invalid sysupgrade file."
351		return 1
352	}
353
354	return 0
355}
356
357# Start NAND upgrade process
358#
359# $(1): file to be used for upgrade
360nand_do_upgrade() {
361	echo -n $1 > /tmp/sysupgrade-nand-path
362	cp /sbin/upgraded /tmp/
363	nand_upgrade_stage1
364}
365