From a51ec563632cb9eb0915e2acd771a27352c9093d Mon Sep 17 00:00:00 2001
From: David Bauer <blocktrron@users.noreply.github.com>
Date: Wed, 14 Nov 2018 23:02:25 +0100
Subject: [PATCH] ipq40xx: add support for ZyXEL NBG6617 (#1560)

---
 docs/index.rst                                |    1 +
 ...xx-add-support-for-the-ZyXEL-NBG6617.patch | 1023 +++++++++++++++++
 .../openwrt/0010-build-add-mkrasimage.patch   |  782 +++++++++++++
 ...0011-ipq40xx-fix-NBG6617-LED-mapping.patch |   27 +
 targets/ipq40xx                               |    3 +
 5 files changed, 1836 insertions(+)
 create mode 100644 patches/openwrt/0009-ipq40xx-add-support-for-the-ZyXEL-NBG6617.patch
 create mode 100644 patches/openwrt/0010-build-add-mkrasimage.patch
 create mode 100644 patches/openwrt/0011-ipq40xx-fix-NBG6617-LED-mapping.patch

diff --git a/docs/index.rst b/docs/index.rst
index 2342481f5..3f1f075c2 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -315,6 +315,7 @@ ipq40xx
 
 * ZyXEL
 
+  - NBG6617 [#80211s]_
   - WRE6606 [#80211s]_
 
 ipq806x
diff --git a/patches/openwrt/0009-ipq40xx-add-support-for-the-ZyXEL-NBG6617.patch b/patches/openwrt/0009-ipq40xx-add-support-for-the-ZyXEL-NBG6617.patch
new file mode 100644
index 000000000..bf4451694
--- /dev/null
+++ b/patches/openwrt/0009-ipq40xx-add-support-for-the-ZyXEL-NBG6617.patch
@@ -0,0 +1,1023 @@
+From: Christian Lamparter <chunkeey@googlemail.com>
+Date: Thu, 21 Jun 2018 14:24:59 +0200
+Subject: ipq40xx: add support for the ZyXEL NBG6617
+
+This patch adds support for ZyXEL NBG6617
+
+Hardware highlights:
+
+SOC:    IPQ4018 / QCA Dakota
+CPU:    Quad-Core ARMv7 Processor rev 5 (v7l) Cortex-A7
+DRAM:   256 MiB DDR3L-1600/1866 Nanya NT5CC128M16IP-DI @ 537 MHz
+NOR:    32 MiB Macronix MX25L25635F
+ETH:    Qualcomm Atheros QCA8075 Gigabit Switch (4 x LAN, 1 x WAN)
+USB:    1 x 3.0 (via Synopsys DesignWare DWC3 controller in the SoC)
+WLAN1:  Qualcomm Atheros QCA4018 2.4GHz 802.11bgn 2:2x2
+WLAN2:  Qualcomm Atheros QCA4018 5GHz 802.11a/n/ac 2:2x2
+INPUT:  RESET Button, WIFI/Rfkill Togglebutton, WPS Button
+LEDS:   Power, WAN, LAN 1-4, WLAN 2.4GHz, WLAN 5GHz, USB, WPS
+
+Serial:
+	WARNING: The serial port needs a TTL/RS-232 3.3v level converter!
+	The Serial setting is 115200-8-N-1. The 1x4 .1" header comes
+	pre-soldered. Pinout:
+	  1. 3v3 (Label printed on the PCB), 2. RX, 3. GND, 4. TX
+
+first install / debricking / restore stock:
+ 0. Have a PC running a tftp-server @ 192.168.1.99/24
+ 1. connect the PC to any LAN-Ports
+ 2. put the openwrt...-factory.bin (or V1.00(ABCT.X).bin for stock) file
+    into the tftp-server root directory and rename it to just "ras.bin".
+ 3. power-cycle the router and hold down the the WPS button (for 30sek)
+ 4. Wait (for a long time - the serial console provides some progress
+    reports. The u-boot says it best: "Please be patient".
+ 5. Once the power LED starts to flashes slowly and the USB + WPS LEDs
+    flashes fast at the same time. You have to reboot the device and
+    it should then come right up.
+
+Installation via Web-UI:
+ 0. Connect a PC to the powered-on router. It will assign your PC a
+    IP-address via DHCP
+ 1. Access the Web-UI at 192.168.1.1 (Default Passwort: 1234)
+ 2. Go to the "Expert Mode"
+ 3. Under "Maintenance", select "Firmware-Upgrade"
+ 4. Upload the OpenWRT factory image
+ 5. Wait for the Device to finish.
+    It will reboot into OpenWRT without any additional actions needed.
+
+To open the ZyXEL NBG6617:
+ 0. remove the four rubber feet glued on the backside
+ 1. remove the four philips screws and pry open the top cover
+    (by applying force between the plastic top housing from the
+    backside/lan-port side)
+
+Access the real u-boot shell:
+ZyXEL uses a proprietary loader/shell on top of u-boot: "ZyXEL zloader v2.02"
+When the device is starting up, the user can enter the the loader shell
+by simply pressing a key within the 3 seconds once the following string
+appears on the serial console:
+
+|   Hit any key to stop autoboot:  3
+
+The user is then dropped to a locked shell.
+
+|NBG6617> HELP
+|ATEN    x[,y]     set BootExtension Debug Flag (y=password)
+|ATSE    x         show the seed of password generator
+|ATSH              dump manufacturer related data in ROM
+|ATRT    [x,y,z,u] RAM read/write test (x=level, y=start addr, z=end addr, u=iterations)
+|ATGO              boot up whole system
+|ATUR    x         upgrade RAS image (filename)
+|NBG6617>
+
+In order to escape/unlock a password challenge has to be passed.
+Note: the value is dynamic! you have to calculate your own!
+
+First use ATSE $MODELNAME (MODELNAME is the hostname in u-boot env)
+to get the challange value/seed.
+
+|NBG6617> ATSE NBG6617
+|012345678901
+
+This seed/value can be converted to the password with the help of this
+bash script (Thanks to http://www.adslayuda.com/Zyxel650-9.html authors):
+
+- tool.sh -
+ror32() {
+  echo $(( ($1 >> $2) | (($1 << (32 - $2) & (2**32-1)) ) ))
+}
+v="0x$1"
+a="0x${v:2:6}"
+b=$(( $a + 0x10F0A563))
+c=$(( 0x${v:12:14} & 7 ))
+p=$(( $(ror32 $b $c) ^ $a ))
+printf "ATEN 1,%X\n" $p
+- end of tool.sh -
+
+|# bash ./tool.sh 012345678901
+|
+|ATEN 1,879C711
+
+copy and paste the result into the shell to unlock zloader.
+
+|NBG6617> ATEN 1,0046B0017430
+
+If the entered code was correct the shell will change to
+use the ATGU command to enter the real u-boot shell.
+
+|NBG6617> ATGU
+|NBG6617#
+
+Co-authored-by: David Bauer <mail@david-bauer.net>
+Signed-off-by: Christian Lamparter <chunkeey@googlemail.com>
+Signed-off-by: David Bauer <mail@david-bauer.net>
+
+diff --git a/include/image-commands.mk b/include/image-commands.mk
+index 4d3f025b123f44cfd48d5b6cf081ae7633577277..28b39c310e499caea4fe8a6d9ad6dceb5d95363b 100644
+--- a/include/image-commands.mk
++++ b/include/image-commands.mk
+@@ -49,6 +49,19 @@ define Build/eva-image
+ 	mv $@.new $@
+ endef
+ 
++define Build/make-ras
++	let \
++		newsize="$(subst k,* 1024,$(RAS_ROOTFS_SIZE))"; \
++		$(TOPDIR)/scripts/make-ras.sh \
++			--board $(RAS_BOARD) \
++			--version $(RAS_VERSION) \
++			--kernel $(call param_get_default,kernel,$(1),$(IMAGE_KERNEL)) \
++			--rootfs $@ \
++			--rootfssize $$newsize \
++			$@.new
++	@mv $@.new $@
++endef
++
+ define Build/netgear-chk
+ 	$(STAGING_DIR_HOST)/bin/mkchkimg \
+ 		-o $@.new \
+diff --git a/package/boot/uboot-envtools/files/ipq40xx b/package/boot/uboot-envtools/files/ipq40xx
+index d475e566299416176af3b8646a7510cd3c9787e7..8cd4799b295fd38c9e846af5769c9fa9f5490f8b 100644
+--- a/package/boot/uboot-envtools/files/ipq40xx
++++ b/package/boot/uboot-envtools/files/ipq40xx
+@@ -35,6 +35,9 @@ openmesh,a42 |\
+ openmesh,a62)
+ 	ubootenv_add_uci_config "/dev/mtd5" "0x0" "0x10000" "0x10000"
+ 	;;
++zyxel,nbg6617)
++	ubootenv_add_uci_config "/dev/mtd6" "0x0" "0x10000" "0x10000"
++	;;
+ esac
+ 
+ config_load ubootenv
+diff --git a/package/firmware/ipq-wifi/Makefile b/package/firmware/ipq-wifi/Makefile
+index 1cfcc33de3af58e9773d22cdcbe0c95f1633e2d9..b1428f173ddb507f0c92313dcca7aae57284a0b8 100644
+--- a/package/firmware/ipq-wifi/Makefile
++++ b/package/firmware/ipq-wifi/Makefile
+@@ -14,7 +14,7 @@ define Build/Compile
+ endef
+ 
+ #ALLWIFIBOARDS:=<devicename>
+-ALLWIFIBOARDS:=zyxel_wre6606
++ALLWIFIBOARDS:=zyxel_nbg6617 zyxel_wre6606
+ ALLWIFIPACKAGES:=$(foreach BOARD,$(ALLWIFIBOARDS),ipq-wifi-$(BOARD))
+ 
+ define Package/ipq-wifi-default
+@@ -50,5 +50,6 @@ endef
+ 
+ $(eval $(call generate-ipq-wifi-package,zyxel_wre6606,board-zyxel_wre6606.bin,ZyXEL WRE6606))
+ #$(eval $(call generate-ipq-wifi-package,<devicename>,<filename>,<displayname>))
++$(eval $(call generate-ipq-wifi-package,zyxel_nbg6617,board-zyxel_nbg6617.bin,ZyXEL NBG6617))
+ 
+ $(foreach PACKAGE,$(ALLWIFIPACKAGES),$(eval $(call BuildPackage,$(PACKAGE))))
+diff --git a/package/firmware/ipq-wifi/board-zyxel_nbg6617.bin b/package/firmware/ipq-wifi/board-zyxel_nbg6617.bin
+new file mode 100644
+index 0000000000000000000000000000000000000000..ab4d43e8cbc949c06f74e332807b8d92239a7de0
+GIT binary patch
+literal 24276
+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
+
+literal 0
+HcmV?d00001
+
+diff --git a/scripts/make-ras.sh b/scripts/make-ras.sh
+new file mode 100755
+index 0000000000000000000000000000000000000000..ccddaa0016b0c926d4737abb5757e7212b0a1157
+--- /dev/null
++++ b/scripts/make-ras.sh
+@@ -0,0 +1,196 @@
++#!/usr/bin/env bash
++#
++# --- ZyXEL header format ---
++# Original Version by Benjamin Berg <benjamin@sipsolutions.net>
++#
++# The firmware image prefixed with a header (which is written into the MTD device).
++# The header is one erase block (~64KiB) in size, but the checksum only convers the
++# first 2KiB. Padding is 0xff. All integers are in big-endian.
++#
++# The checksum is always a 16-Bit System V checksum (sum -s) stored in a 32-Bit integer.
++#
++#   4 bytes:  checksum of the rootfs image
++#   4 bytes:  length of the contained rootfs image file (big endian)
++#  32 bytes:  Firmware Version string (NUL terminated, 0xff padded)
++#   4 bytes:  checksum over the header partition (big endian - see below)
++#  32 bytes:  Model (e.g. "NBG6617", NUL termiated, 0xff padded)
++#   4 bytes:  checksum of the kernel partition
++#   4 bytes:  length of the contained kernel image file (big endian)
++#      rest: 0xff padding
++#
++# The checksums are calculated by adding up all bytes and if a 16bit
++# overflow occurs, one is added and the sum is masked to 16 bit:
++#   csum = csum + databyte; if (csum > 0xffff) { csum += 1; csum &= 0xffff };
++# Should the file have an odd number of bytes then the byte len-0x800 is
++# used additionally.
++#
++# The checksum for the header is calculated over the first 2048 bytes with
++# the rootfs image checksum as the placeholder during calculation.
++#
++# The header is padded with 0xff to the erase block size of the device.
++#
++board=""
++version=""
++kernel=""
++rootfs=""
++outfile=""
++err=""
++
++while [ "$1" ]; do
++	case "$1" in
++	"--board")
++		board="$2"
++		shift
++		shift
++		continue
++		;;
++	"--version")
++		version="$2"
++		shift
++		shift
++		continue
++		;;
++	"--kernel")
++		kernel="$2"
++		shift
++		shift
++		continue
++		;;
++	"--rootfs")
++		rootfs="$2"
++		shift
++		shift
++		continue
++		;;
++	"--rootfssize")
++		rootfssize="$2"
++		shift
++		shift
++		continue
++		;;
++	*)
++		if [ ! "$outfile" ]; then
++			outfile=$1
++			shift
++			continue
++		fi
++		;;
++	esac
++done
++
++if [ ! -n "$board" -o ! -n "$version" -o ! -r "$kernel" -o ! -r "$rootfs" -o ! "$rootfssize" -o ! "$outfile" ]; then
++	echo "syntax: $0 [--board ras-boardname] [--version ras-version] [--kernel kernelimage] [--rootfs rootfs] out"
++	exit 1
++fi
++
++rootfs_len=$(wc -c < "$rootfs")
++
++if [ "$rootfs_len" -lt "$rootfssize" ]; then
++	dd if=$rootfs of=$rootfs.new bs=$rootfssize conv=sync
++	mv $rootfs.new $rootfs
++fi
++
++if [ ${#version} -ge 28 ]; then
++	echo "version: '$version' is too long"
++	exit 1
++fi
++
++tmpdir="$( mktemp -d 2> /dev/null )"
++if [ -z "$tmpdir" ]; then
++	# try OSX signature
++	tmpdir="$( mktemp -t 'ubitmp' -d )"
++fi
++
++if [ -z "$tmpdir" ]; then
++	exit 1
++fi
++
++to_be() {
++	local val="$1"
++	local size="$2"
++
++	case "$size" in
++	4)
++		echo $(( "$val" >> 24 | ("$val" & 0xff0000) >> 8 | ("$val" & 0xff00) << 8 | ("$val" & 0xff) << 24 ))
++		;;
++	2)
++		echo $(( "$val" >> 8 | ("$val" & 0xff) << 8))
++		;;
++	esac
++}
++
++checksum_file() {
++	local file=$1
++
++	# ZyXEL seems to use System V sum mode... Now this is classy, who would have thought?!
++	echo $(sum -s ${file} | cut -f1 -d" ")
++}
++
++append_bin() {
++	local val=$1
++	local size=$2
++	local file=$3
++
++	while [ "$size" -ne 0 ]; do
++		printf \\$(printf %o $(("$val" & 0xff)))  >> "$file"
++		val=$(($val >> 8))
++		let size-=1
++	done
++	return
++}
++
++tf=${tmpdir}/out
++pad=$(printf '%0.1s' $(printf "\xff"){1..64})
++
++rootfs_header_file="$tmpdir/rootfs_header"
++rootfs_chksum=$(to_be $(checksum_file ${rootfs}) 4)
++rootfs_len=$(to_be $(wc -c < "$rootfs") 4)
++
++versionpadlen=$(( 32 - ( ${#version} + 1) ))
++
++# 4 bytes:  checksum of the rootfs image
++append_bin "$rootfs_chksum" 4 "$rootfs_header_file"
++# 4 bytes:  length of the contained rootfs image file (big endian)
++append_bin "$rootfs_len" 4 "$rootfs_header_file"
++# 32 bytes:  Firmware Version string (NUL terminated, 0xff padded)
++printf "%s\x00%.*s" "$version" "$versionpadlen" "$pad" >> "$rootfs_header_file"
++
++kernel_header_file="$tmpdir/kernel_header"
++kernel_chksum=$(to_be $(checksum_file ${kernel}) 4)
++kernel_len=$(to_be $(wc -c < "$kernel") 4)
++
++# 4 bytes:  checksum of the kernel image
++append_bin "$kernel_chksum" 4 "$kernel_header_file"
++# 4 bytes:  length of the contained kernel image file (big endian)
++append_bin "$kernel_len" 4 "$kernel_header_file"
++
++board_header_file="$tmpdir/board_header"
++board_file="$tmpdir/board"
++boardpadlen=$(( 64 - ( ${#board} + 1) ))
++# 32 bytes:  Model (e.g. "NBG6617", NUL termiated, 0xff padded)
++printf "%s\x00%.*s" "$board" "$boardpadlen" "$pad" > "$board_file"
++cat "$kernel_header_file" >> "$board_file"
++printf "%.12s" "$pad" >> "$board_file"
++#      rest: 0xff padding
++for i in {1..511}; do
++	printf "%s%s" "$pad" "$pad" >> "$board_file"
++done
++
++tmp_board_file="$tmpdir/tmp_board_file"
++cat "$rootfs_header_file" > "$tmp_board_file"
++
++# The checksum for the header is calculated over the first 2048 bytes with
++# the rootfs image checksum as the placeholder during calculation.
++append_bin "$rootfs_chksum" 4 "$tmp_board_file"
++cat "$board_file" >> "$tmp_board_file"
++
++truncate -s 2048 $tmp_board_file
++board_chksum=$(to_be $(checksum_file ${tmp_board_file}) 4)
++
++# 4 bytes:  checksum over the header partition (big endian)
++append_bin "$board_chksum" 4 "$board_header_file"
++cat "$board_file" >> "$board_header_file"
++
++cat "$rootfs_header_file" "$board_header_file" "$rootfs" "$kernel" > "$outfile"
++
++rm -rf "$tmpdir"
+diff --git a/target/linux/ipq40xx/base-files/etc/board.d/01_leds b/target/linux/ipq40xx/base-files/etc/board.d/01_leds
+index fcba2aea543cdead03ef372bc6b751481846fbf7..475a8623f705d683a7ee3521ee71f2b57fa2e44d 100755
+--- a/target/linux/ipq40xx/base-files/etc/board.d/01_leds
++++ b/target/linux/ipq40xx/base-files/etc/board.d/01_leds
+@@ -34,6 +34,11 @@ netgear,ex6150v2)
+ meraki,mr33)
+ 	ucidef_set_interface_lan "eth0"
+ 	;;
++zyxel,nbg6617)
++	ucidef_set_led_wlan "wlan2g" "WLAN2G" "$board:green:wlan2G" "phy0tpt"
++	ucidef_set_led_wlan "wlan5g" "WLAN5G" "$board:green:wlan5G" "phy1tpt"
++	ucidef_set_led_usbport "usb" "USB" "${board}:green:usb" "usb1-port1" "usb2-port1" "usb3-port1" "usb4-port1"
++	;;
+ zyxel,wre6606)
+ 	ucidef_set_led_wlan "wlan2g" "WLAN2G" "${boardname}:green:wlan2g" "phy0tpt"
+ 	ucidef_set_led_wlan "wlan5g" "WLAN5G" "${boardname}:green:wlan5g" "phy1tpt"
+diff --git a/target/linux/ipq40xx/base-files/etc/board.d/02_network b/target/linux/ipq40xx/base-files/etc/board.d/02_network
+index 03e0c0e16c33f43fafcbba9c5af4baf6dd73e425..5c90ef136fd7cbbe023001030d8c9003347bd249 100755
+--- a/target/linux/ipq40xx/base-files/etc/board.d/02_network
++++ b/target/linux/ipq40xx/base-files/etc/board.d/02_network
+@@ -48,6 +48,11 @@ netgear,ex6150v2 |\
+ zyxel,wre6606)
+ 	ucidef_set_interface_lan "eth0"
+ 	;;
++zyxel,nbg6617)
++	ucidef_set_interfaces_lan_wan "eth0" "eth1"
++	ucidef_add_switch "switch0" \
++		"0u@eth0" "1:lan:4" "2:lan:3" "3:lan:2" "4:lan:1"
++	;;
+ *)
+ 	echo "Unsupported hardware. Network interfaces not intialized"
+ 	;;
+diff --git a/target/linux/ipq40xx/base-files/etc/hotplug.d/firmware/11-ath10k-caldata b/target/linux/ipq40xx/base-files/etc/hotplug.d/firmware/11-ath10k-caldata
+index d89ddf6e57ee9f759f6dcc7c7f8152c11305499b..1d4fd9cd8f9a3983ac3323ce72153c1dcc17a69a 100644
+--- a/target/linux/ipq40xx/base-files/etc/hotplug.d/firmware/11-ath10k-caldata
++++ b/target/linux/ipq40xx/base-files/etc/hotplug.d/firmware/11-ath10k-caldata
+@@ -146,6 +146,7 @@ case "$FIRMWARE" in
+ 	openmesh,a62)
+ 		ath10kcal_extract "0:ART" 4096 12064
+ 		;;
++	zyxel,nbg6617 |\
+ 	zyxel,wre6606)
+ 		ath10kcal_extract "ART" 4096 12064
+ 		ath10kcal_patch_mac_crc $(macaddr_add $(cat /sys/class/net/eth0/address) -2)
+@@ -181,6 +182,7 @@ case "$FIRMWARE" in
+ 	openmesh,a62)
+ 		ath10kcal_extract "0:ART" 20480 12064
+ 		;;
++	zyxel,nbg6617 |\
+ 	zyxel,wre6606)
+ 		ath10kcal_extract "ART" 20480 12064
+ 		ath10kcal_patch_mac_crc $(macaddr_add $(cat /sys/class/net/eth0/address) -1)
+diff --git a/target/linux/ipq40xx/base-files/lib/preinit/05_set_iface_mac_ipq40xx.sh b/target/linux/ipq40xx/base-files/lib/preinit/05_set_iface_mac_ipq40xx.sh
+index 1acd7366c81f7c70ef9018d97a833e4d355e8119..44e81ca366dd57d3d5d7fbb9065254925e94061a 100644
+--- a/target/linux/ipq40xx/base-files/lib/preinit/05_set_iface_mac_ipq40xx.sh
++++ b/target/linux/ipq40xx/base-files/lib/preinit/05_set_iface_mac_ipq40xx.sh
+@@ -8,6 +8,10 @@ preinit_set_mac_address() {
+ 		mac_lan=$(get_mac_binary "/sys/bus/i2c/devices/0-0050/eeprom" 102)
+ 		[ -n "$mac_lan" ] && ip link set dev eth0 address "$mac_lan"
+ 		;;
++	zyxel,nbg6617)
++		base_mac=$(cat /sys/class/net/eth0/address)
++		ip link set dev eth0 address $(macaddr_add "$base_mac" +2)
++		ip link set dev eth1 address $(macaddr_add "$base_mac" +3)
+ 	esac
+ }
+ 
+diff --git a/target/linux/ipq40xx/base-files/lib/preinit/06_set_preinit_iface_ipq40xx.sh b/target/linux/ipq40xx/base-files/lib/preinit/06_set_preinit_iface_ipq40xx.sh
+new file mode 100644
+index 0000000000000000000000000000000000000000..578ddbe4c4f6e91d925ea3a0de9e060daac82e72
+--- /dev/null
++++ b/target/linux/ipq40xx/base-files/lib/preinit/06_set_preinit_iface_ipq40xx.sh
+@@ -0,0 +1,19 @@
++#!/bin/sh
++
++set_preinit_iface() {
++	. /lib/functions.sh
++
++	case $(board_name) in
++	asus,rt-ac58u| \
++	avm,fritzbox-4040| \
++	glinet,gl-b1300| \
++	meraki,mr33| \
++	zyxel,nbg6617)
++		ifname=eth0
++		;;
++	*)
++		;;
++	esac
++}
++
++boot_hook_add preinit_main set_preinit_iface
+diff --git a/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh b/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh
+index 4096e127ea9eaa8106475a3143b0b2ea796b5c7c..0e29302465fb5fca6f2a2194c751728f492053d5 100644
+--- a/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh
++++ b/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh
+@@ -29,6 +29,21 @@ EOF
+ 	return 0;
+ }
+ 
++zyxel_do_upgrade() {
++	local tar_file="$1"
++
++	local board_dir=$(tar tf $tar_file | grep -m 1 '^sysupgrade-.*/$')
++	board_dir=${board_dir%/}
++
++	tar Oxf $tar_file ${board_dir}/kernel | mtd write - kernel
++
++	if [ "$SAVE_CONFIG" -eq 1 ]; then
++		tar Oxf $tar_file ${board_dir}/root | mtd -j "$CONF_TAR" write - rootfs
++	else
++		tar Oxf $tar_file ${board_dir}/root | mtd write - rootfs
++	fi
++}
++
+ platform_do_upgrade() {
+ 	case "$(board_name)" in
+ 	8dev,jalapeno)
+@@ -48,6 +63,9 @@ platform_do_upgrade() {
+ 		CI_KERNPART="part.safe"
+ 		nand_do_upgrade "$1"
+ 		;;
++	zyxel,nbg6617)
++		zyxel_do_upgrade "$1"
++		;;
+ 	*)
+ 		default_do_upgrade "$ARGV"
+ 		;;
+diff --git a/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4018-nbg6617.dts b/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4018-nbg6617.dts
+new file mode 100644
+index 0000000000000000000000000000000000000000..d7f8c5955caee15d373a342b75c8c1947f5b8022
+--- /dev/null
++++ b/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4018-nbg6617.dts
+@@ -0,0 +1,321 @@
++/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
++ *
++ * Permission to use, copy, modify, and/or distribute this software for any
++ * purpose with or without fee is hereby granted, provided that the above
++ * copyright notice and this permission notice appear in all copies.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
++ *
++ */
++
++#include "qcom-ipq4019.dtsi"
++#include "qcom-ipq4019-bus.dtsi"
++#include <dt-bindings/gpio/gpio.h>
++#include <dt-bindings/input/input.h>
++#include <dt-bindings/input/linux-event-codes.h>
++#include <dt-bindings/soc/qcom,tcsr.h>
++
++/ {
++	model = "ZyXEL NBG6617";
++	compatible = "zyxel,nbg6617", "qcom,ipq4019";
++
++	chosen {
++		/*
++		 * the vendor u-boot adds root and mtdparts cmdline parameters
++		 * which we don't want... but we have to overwrite them or else
++		 * the kernel will take them at face value.
++		 */
++		bootargs-append = " mtdparts= root=31:13";
++	};
++
++	aliases {
++		led-boot = &power;
++		led-failsafe = &power;
++		led-running = &power;
++		led-upgrade = &power;
++	};
++
++	soc {
++		mdio@90000 {
++			status = "okay";
++		};
++
++		ess-psgmii@98000 {
++			status = "okay";
++		};
++
++		tcsr@1949000 {
++			compatible = "qcom,tcsr";
++			reg = <0x1949000 0x100>;
++			qcom,wifi_glb_cfg = <TCSR_WIFI_GLB_CFG>;
++		};
++
++		tcsr@194b000 {
++			compatible = "qcom,tcsr";
++			reg = <0x194b000 0x100>;
++			qcom,usb-hsphy-mode-select = <TCSR_USB_HSPHY_HOST_MODE>;
++		};
++
++		ess_tcsr@1953000 {
++			compatible = "qcom,tcsr";
++			reg = <0x1953000 0x1000>;
++			qcom,ess-interface-select = <TCSR_ESS_PSGMII>;
++		};
++
++		tcsr@1957000 {
++			compatible = "qcom,tcsr";
++			reg = <0x1957000 0x100>;
++			qcom,wifi_noc_memtype_m0_m2 = <TCSR_WIFI_NOC_MEMTYPE_M0_M2>;
++		};
++
++		usb2@60f8800 {
++			status = "okay";
++		};
++
++		serial@78af000 {
++			pinctrl-0 = <&serial_pins>;
++			pinctrl-names = "default";
++			status = "okay";
++		};
++
++		usb3@8af8800 {
++			status = "okay";
++		};
++
++		crypto@8e3a000 {
++			status = "okay";
++		};
++
++		watchdog@b017000 {
++			status = "okay";
++		};
++
++		ess-switch@c000000 {
++			status = "okay";
++		};
++
++		edma@c080000 {
++			status = "okay";
++		};
++	};
++
++	gpio-keys {
++		compatible = "gpio-keys";
++
++		wlan {
++			label = "wlan";
++			gpios = <&tlmm 2 GPIO_ACTIVE_HIGH>;
++			linux,code = <KEY_RFKILL>;
++			linux,input-type = <EV_SW>;
++		};
++
++		wps {
++			label = "wps";
++			gpios = <&tlmm 63 GPIO_ACTIVE_LOW>;
++			linux,code = <KEY_WPS_BUTTON>;
++		};
++
++		reset {
++			label = "reset";
++			gpios = <&tlmm 4 GPIO_ACTIVE_LOW>;
++			linux,code = <KEY_RESTART>;
++		};
++	};
++
++	gpio-leds {
++		compatible = "gpio-leds";
++		pinctrl-0 = <&led_pins>;
++		pinctrl-names = "default";
++
++		power: power {
++			label = "nbg6617:green:power";
++			gpios = <&tlmm 3 GPIO_ACTIVE_HIGH>;
++		};
++
++		usb {
++			label = "nbg6617:green:usb";
++			gpios = <&tlmm 0 GPIO_ACTIVE_HIGH>;
++		};
++
++		wlan2G {
++			label = "nbg6617:green:wlan2G";
++			gpios = <&tlmm 58 GPIO_ACTIVE_HIGH>;
++		};
++
++		wlan5G {
++			label = "nbg6617:green:wlan5G";
++			gpios = <&tlmm 5 GPIO_ACTIVE_HIGH>;
++		};
++
++		wps {
++			label = "nbg6617:green:wps";
++			gpios = <&tlmm 1 GPIO_ACTIVE_HIGH>;
++		};
++	};
++};
++
++&tlmm {
++	serial_pins: serial_pinmux {
++		mux {
++			pins = "gpio60", "gpio61";
++			function = "blsp_uart0";
++			bias-disable;
++		};
++	};
++	spi_0_pins: spi_0_pinmux {
++		mux {
++			function = "blsp_spi0";
++			pins = "gpio55", "gpio56", "gpio57";
++			drive-strength = <12>;
++			bias-disable;
++		};
++
++		mux_cs {
++			function = "gpio";
++			pins = "gpio54";
++			drive-strength = <2>;
++			bias-disable;
++			output-low;
++		};
++	};
++	led_pins: led_pinmux  {
++		mux {
++			pins = "gpio0", "gpio1", "gpio3", "gpio5", "gpio58";
++			drive-strength = <0x8>;
++			bias-disable;
++			output-low;
++		};
++	};
++};
++
++&spi_0 { /* BLSP1 QUP1 */
++	pinctrl-0 = <&spi_0_pins>;
++	pinctrl-names = "default";
++	status = "okay";
++	cs-gpios = <&tlmm 54 GPIO_ACTIVE_HIGH>;
++
++	mx25l25635f@0 {
++		compatible = "mx25l25635f", "jedec,spi-nor";
++		#address-cells = <1>;
++		#size-cells = <0>;
++		reg = <0>;
++		spi-max-frequency = <24000000>;
++		status = "okay";
++		m25p,fast-read;
++
++		partitions {
++			compatible = "fixed-partitions";
++			#address-cells = <1>;
++			#size-cells = <1>;
++
++			partition0@0 {
++				label = "SBL1";
++				reg = <0x00000000 0x00040000>;
++				read-only;
++			};
++			partition1@40000 {
++				label = "MIBIB";
++				reg = <0x00040000 0x00020000>;
++				read-only;
++			};
++			partition2@60000 {
++				label = "QSEE";
++				reg = <0x00060000 0x00060000>;
++				read-only;
++			};
++			partition3@c0000 {
++				label = "CDT";
++				reg = <0x000c0000 0x00010000>;
++				read-only;
++			};
++			partition4@d0000 {
++				label = "DDRPARAMS";
++				reg = <0x000d0000 0x00010000>;
++				read-only;
++			};
++			partition5@e0000 {
++				label = "APPSBL"; /* u-boot */
++				reg = <0x000e0000 0x00080000>;
++				/* U-Boot Standalone App "zloader" is located at 0x64000 */
++				read-only;
++			};
++			partition6@160000 {
++				label = "APPSBLENV"; /* u-boot env */
++				reg = <0x00160000 0x00010000>;
++			};
++			partition7@170000 {
++				/* make a backup of this partition! */
++				label = "ART";
++				reg = <0x00170000 0x00010000>;
++				read-only;
++			};
++			partition8@180000 {
++				label = "kernel";
++				reg = <0x00180000 0x00400000>;
++			};
++			partition9@580000 {
++				label = "dualflag";
++				reg = <0x00580000 0x00010000>;
++				read-only;
++			};
++			partition10@590000 {
++				label = "header";
++				reg = <0x00590000 0x00010000>;
++			};
++			partition11@5a0000 {
++				label = "romd";
++				reg = <0x005a0000 0x00100000>;
++				read-only;
++			};
++			partition12@6a0000 {
++				label = "not_root_data";
++				/*
++				 * for some strange reason, someone at ZyXEL
++				 * had the "great" idea to put the rootfs_data
++				 * in front of rootfs... Don't do that!
++				 * As a result this one, full MebiByte remains
++				 * unused.
++				 */
++				reg = <0x006a0000 0x00100000>;
++			};
++			partition13@7a0000 {
++				label = "rootfs";
++				reg = <0x007a0000 0x01860000>;
++			};
++		};
++	};
++};
++
++&cryptobam {
++	status = "okay";
++};
++
++&blsp_dma {
++	status = "okay";
++};
++
++&wifi0 {
++	status = "okay";
++};
++
++&wifi1 {
++	status = "okay";
++};
++
++&usb3_ss_phy {
++	status = "okay";
++};
++
++&usb3_hs_phy {
++	status = "okay";
++};
++
++&usb2_hs_phy {
++	status = "okay";
++};
+diff --git a/target/linux/ipq40xx/image/Makefile b/target/linux/ipq40xx/image/Makefile
+index 38600cf979242142aa08c30163e63d911f0ddb63..d1ee1004fddce6cf6007259229d8eee5b5b2ea3b 100644
+--- a/target/linux/ipq40xx/image/Makefile
++++ b/target/linux/ipq40xx/image/Makefile
+@@ -1,6 +1,8 @@
+ include $(TOPDIR)/rules.mk
+ include $(INCLUDE_DIR)/image.mk
+ 
++DEVICE_VARS += RAS_BOARD RAS_ROOTFS_SIZE RAS_VERSION
++
+ define Device/Default
+ 	PROFILES := Default
+ 	KERNEL_DEPENDS = $$(wildcard $(DTS_DIR)/$$(DEVICE_DTS).dts)
+@@ -205,6 +207,26 @@ define Device/qcom_ap-dk04.1-c1
+ endef
+ TARGET_DEVICES += qcom_ap-dk04.1-c1
+ 
++define Device/zyxel_nbg6617
++	$(call Device/FitImageLzma)
++	DEVICE_DTS := qcom-ipq4018-nbg6617
++	DEVICE_TITLE := ZyXEL NBG6617
++	ROOTFS_SIZE := 24960k
++	RAS_BOARD := NBG6617
++	RAS_ROOTFS_SIZE := 19840k
++	RAS_VERSION := "$(VERSION_DIST) $(REVISION)"
++	IMAGE/sysupgrade.bin := append-kernel | append-rootfs | pad-rootfs | append-metadata
++	IMAGES := sysupgrade.bin factory.bin
++#	The ZyXEL firmware allows flashing thru the web-gui only when the rootfs is
++#	at least as large as the one of the initial firmware image (not the current
++#	one on the device). This only applies to the Web-UI, the bootlaoder ignores
++#	this minimum-size. However, the larger image can be flashed both ways.
++	IMAGE/factory.bin := append-rootfs | pad-rootfs | check-size $$$$(ROOTFS_SIZE) | make-ras
++	IMAGE/sysupgrade.bin/squashfs := append-rootfs | pad-rootfs | check-size $$$$(ROOTFS_SIZE) | sysupgrade-tar rootfs=$$$$@ | append-metadata
++	DEVICE_PACKAGES := ipq-wifi-zyxel_nbg6617 uboot-envtools
++endef
++TARGET_DEVICES += zyxel_nbg6617
++
+ define Device/zyxel_wre6606
+ 	$(call Device/FitImage)
+ 	DEVICE_TITLE := ZyXEL WRE6606
+diff --git a/target/linux/ipq40xx/patches-4.14/069-arm-boot-add-dts-files.patch b/target/linux/ipq40xx/patches-4.14/069-arm-boot-add-dts-files.patch
+index 3aa368be8381d9c48aefccbe6d4cc768b00dc41e..8066f986a4e030c9c8fe0ec4438e92d30daa77f0 100644
+--- a/target/linux/ipq40xx/patches-4.14/069-arm-boot-add-dts-files.patch
++++ b/target/linux/ipq40xx/patches-4.14/069-arm-boot-add-dts-files.patch
+@@ -10,7 +10,7 @@ Signed-off-by: John Crispin <john@phrozen.org>
+ 
+ --- a/arch/arm/boot/dts/Makefile
+ +++ b/arch/arm/boot/dts/Makefile
+-@@ -697,7 +697,19 @@ dtb-$(CONFIG_ARCH_QCOM) += \
++@@ -697,7 +697,20 @@ dtb-$(CONFIG_ARCH_QCOM) += \
+  	qcom-apq8074-dragonboard.dtb \
+  	qcom-apq8084-ifc6540.dtb \
+  	qcom-apq8084-mtp.dtb \
+@@ -19,6 +19,7 @@ Signed-off-by: John Crispin <john@phrozen.org>
+ +	qcom-ipq4018-ex6150v2.dtb \
+ +	qcom-ipq4018-fritz4040.dtb \
+ +	qcom-ipq4018-jalapeno.dtb \
+++	qcom-ipq4018-nbg6617.dtb \
+ +	qcom-ipq4018-rt-ac58u.dtb \
+ +	qcom-ipq4018-wre6606.dtb \
+  	qcom-ipq4019-ap.dk01.1-c1.dtb \
+diff --git a/target/linux/ipq40xx/patches-4.14/303-spi-nor-enable-4B-opcodes-for-mx25l25635f.patch b/target/linux/ipq40xx/patches-4.14/303-spi-nor-enable-4B-opcodes-for-mx25l25635f.patch
+new file mode 100644
+index 0000000000000000000000000000000000000000..7c513a6ad8ec817bf388c67e1181f08982888fa5
+--- /dev/null
++++ b/target/linux/ipq40xx/patches-4.14/303-spi-nor-enable-4B-opcodes-for-mx25l25635f.patch
+@@ -0,0 +1,64 @@
++Index: linux-4.14.50/drivers/mtd/spi-nor/spi-nor.c
++===================================================================
++--- linux-4.14.50.orig/drivers/mtd/spi-nor/spi-nor.c
+++++ linux-4.14.50/drivers/mtd/spi-nor/spi-nor.c
++@@ -1025,6 +1025,7 @@ static const struct flash_info spi_nor_i
++ 	{ "mx25l12805d", INFO(0xc22018, 0, 64 * 1024, 256, 0) },
++ 	{ "mx25l12855e", INFO(0xc22618, 0, 64 * 1024, 256, 0) },
++ 	{ "mx25l25635e", INFO(0xc22019, 0, 64 * 1024, 512, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
+++	{ "mx25l25635f", INFO(0xc22019, 0, 64 * 1024, 512, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
++ 	{ "mx25u25635f", INFO(0xc22539, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_4B_OPCODES) },
++ 	{ "mx25l25655e", INFO(0xc22619, 0, 64 * 1024, 512, 0) },
++ 	{ "mx66l51235l", INFO(0xc2201a, 0, 64 * 1024, 1024, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
++@@ -1189,11 +1190,12 @@ static const struct flash_info spi_nor_i
++ 	{ },
++ };
++ 
++-static const struct flash_info *spi_nor_read_id(struct spi_nor *nor)
+++static const struct flash_info *spi_nor_read_id(struct spi_nor *nor,
+++						const char *name)
++ {
++ 	int			tmp;
++ 	u8			id[SPI_NOR_MAX_ID_LEN];
++-	const struct flash_info	*info;
+++	const struct flash_info	*info, *first_match = NULL;
++ 
++ 	tmp = nor->read_reg(nor, SPINOR_OP_RDID, id, SPI_NOR_MAX_ID_LEN);
++ 	if (tmp < 0) {
++@@ -1204,10 +1206,16 @@ static const struct flash_info *spi_nor_
++ 	for (tmp = 0; tmp < ARRAY_SIZE(spi_nor_ids) - 1; tmp++) {
++ 		info = &spi_nor_ids[tmp];
++ 		if (info->id_len) {
++-			if (!memcmp(info->id, id, info->id_len))
++-				return &spi_nor_ids[tmp];
+++			if (!memcmp(info->id, id, info->id_len)) {
+++				if (!name || !strcmp(name, info->name))
+++					return info;
+++				if (!first_match)
+++					first_match = info;
+++			}
++ 		}
++ 	}
+++	if (first_match)
+++		return first_match;
++ 	dev_err(nor->dev, "unrecognized JEDEC id bytes: %02x, %02x, %02x\n",
++ 		id[0], id[1], id[2]);
++ 	return ERR_PTR(-ENODEV);
++@@ -2667,7 +2675,7 @@ int spi_nor_scan(struct spi_nor *nor, co
++ 		info = spi_nor_match_id(name);
++ 	/* Try to auto-detect if chip name wasn't specified or not found */
++ 	if (!info)
++-		info = spi_nor_read_id(nor);
+++		info = spi_nor_read_id(nor, NULL);
++ 	if (IS_ERR_OR_NULL(info))
++ 		return -ENOENT;
++ 
++@@ -2678,7 +2686,7 @@ int spi_nor_scan(struct spi_nor *nor, co
++ 	if (name && info->id_len) {
++ 		const struct flash_info *jinfo;
++ 
++-		jinfo = spi_nor_read_id(nor);
+++		jinfo = spi_nor_read_id(nor, name);
++ 		if (IS_ERR(jinfo)) {
++ 			return PTR_ERR(jinfo);
++ 		} else if (jinfo != info) {
diff --git a/patches/openwrt/0010-build-add-mkrasimage.patch b/patches/openwrt/0010-build-add-mkrasimage.patch
new file mode 100644
index 000000000..941c2fe10
--- /dev/null
+++ b/patches/openwrt/0010-build-add-mkrasimage.patch
@@ -0,0 +1,782 @@
+From: David Bauer <mail@david-bauer.net>
+Date: Wed, 22 Aug 2018 17:30:44 +0200
+Subject: build: add mkrasimage
+
+The current make-ras.sh image generation script for the ZyXEL NBG6617
+has portability issues with bash. Because of this, factory images are
+currently not built correctly by the OpenWRT buildbots.
+
+This commit replaces the make-ras.sh by C-written mkrasimage.
+
+The new mkrasimage is also compatible with other ZyXEL devices using
+the ras image-format.
+This is not tested with the NBG6616 but it correctly builds the
+header for ZyXEL factory image.
+
+Signed-off-by: David Bauer <mail@david-bauer.net>
+
+diff --git a/include/image-commands.mk b/include/image-commands.mk
+index 28b39c310e499caea4fe8a6d9ad6dceb5d95363b..552d8db1cbacf533c12d0d8e2e5cffbe5591adb4 100644
+--- a/include/image-commands.mk
++++ b/include/image-commands.mk
+@@ -49,17 +49,17 @@ define Build/eva-image
+ 	mv $@.new $@
+ endef
+ 
+-define Build/make-ras
++define Build/zyxel-ras-image
+ 	let \
+ 		newsize="$(subst k,* 1024,$(RAS_ROOTFS_SIZE))"; \
+-		$(TOPDIR)/scripts/make-ras.sh \
+-			--board $(RAS_BOARD) \
+-			--version $(RAS_VERSION) \
+-			--kernel $(call param_get_default,kernel,$(1),$(IMAGE_KERNEL)) \
+-			--rootfs $@ \
+-			--rootfssize $$newsize \
+-			$@.new
+-	@mv $@.new $@
++		$(STAGING_DIR_HOST)/bin/mkrasimage \
++			-b $(RAS_BOARD) \
++			-v $(RAS_VERSION) \
++			-r $@ \
++			-s $$newsize \
++			-o $@.new \
++			$(if $(findstring separate-kernel,$(word 1,$(1))),-k $(IMAGE_KERNEL)) \
++		&& mv $@.new $@
+ endef
+ 
+ define Build/netgear-chk
+diff --git a/scripts/make-ras.sh b/scripts/make-ras.sh
+deleted file mode 100755
+index ccddaa0016b0c926d4737abb5757e7212b0a1157..0000000000000000000000000000000000000000
+--- a/scripts/make-ras.sh
++++ /dev/null
+@@ -1,196 +0,0 @@
+-#!/usr/bin/env bash
+-#
+-# --- ZyXEL header format ---
+-# Original Version by Benjamin Berg <benjamin@sipsolutions.net>
+-#
+-# The firmware image prefixed with a header (which is written into the MTD device).
+-# The header is one erase block (~64KiB) in size, but the checksum only convers the
+-# first 2KiB. Padding is 0xff. All integers are in big-endian.
+-#
+-# The checksum is always a 16-Bit System V checksum (sum -s) stored in a 32-Bit integer.
+-#
+-#   4 bytes:  checksum of the rootfs image
+-#   4 bytes:  length of the contained rootfs image file (big endian)
+-#  32 bytes:  Firmware Version string (NUL terminated, 0xff padded)
+-#   4 bytes:  checksum over the header partition (big endian - see below)
+-#  32 bytes:  Model (e.g. "NBG6617", NUL termiated, 0xff padded)
+-#   4 bytes:  checksum of the kernel partition
+-#   4 bytes:  length of the contained kernel image file (big endian)
+-#      rest: 0xff padding
+-#
+-# The checksums are calculated by adding up all bytes and if a 16bit
+-# overflow occurs, one is added and the sum is masked to 16 bit:
+-#   csum = csum + databyte; if (csum > 0xffff) { csum += 1; csum &= 0xffff };
+-# Should the file have an odd number of bytes then the byte len-0x800 is
+-# used additionally.
+-#
+-# The checksum for the header is calculated over the first 2048 bytes with
+-# the rootfs image checksum as the placeholder during calculation.
+-#
+-# The header is padded with 0xff to the erase block size of the device.
+-#
+-board=""
+-version=""
+-kernel=""
+-rootfs=""
+-outfile=""
+-err=""
+-
+-while [ "$1" ]; do
+-	case "$1" in
+-	"--board")
+-		board="$2"
+-		shift
+-		shift
+-		continue
+-		;;
+-	"--version")
+-		version="$2"
+-		shift
+-		shift
+-		continue
+-		;;
+-	"--kernel")
+-		kernel="$2"
+-		shift
+-		shift
+-		continue
+-		;;
+-	"--rootfs")
+-		rootfs="$2"
+-		shift
+-		shift
+-		continue
+-		;;
+-	"--rootfssize")
+-		rootfssize="$2"
+-		shift
+-		shift
+-		continue
+-		;;
+-	*)
+-		if [ ! "$outfile" ]; then
+-			outfile=$1
+-			shift
+-			continue
+-		fi
+-		;;
+-	esac
+-done
+-
+-if [ ! -n "$board" -o ! -n "$version" -o ! -r "$kernel" -o ! -r "$rootfs" -o ! "$rootfssize" -o ! "$outfile" ]; then
+-	echo "syntax: $0 [--board ras-boardname] [--version ras-version] [--kernel kernelimage] [--rootfs rootfs] out"
+-	exit 1
+-fi
+-
+-rootfs_len=$(wc -c < "$rootfs")
+-
+-if [ "$rootfs_len" -lt "$rootfssize" ]; then
+-	dd if=$rootfs of=$rootfs.new bs=$rootfssize conv=sync
+-	mv $rootfs.new $rootfs
+-fi
+-
+-if [ ${#version} -ge 28 ]; then
+-	echo "version: '$version' is too long"
+-	exit 1
+-fi
+-
+-tmpdir="$( mktemp -d 2> /dev/null )"
+-if [ -z "$tmpdir" ]; then
+-	# try OSX signature
+-	tmpdir="$( mktemp -t 'ubitmp' -d )"
+-fi
+-
+-if [ -z "$tmpdir" ]; then
+-	exit 1
+-fi
+-
+-to_be() {
+-	local val="$1"
+-	local size="$2"
+-
+-	case "$size" in
+-	4)
+-		echo $(( "$val" >> 24 | ("$val" & 0xff0000) >> 8 | ("$val" & 0xff00) << 8 | ("$val" & 0xff) << 24 ))
+-		;;
+-	2)
+-		echo $(( "$val" >> 8 | ("$val" & 0xff) << 8))
+-		;;
+-	esac
+-}
+-
+-checksum_file() {
+-	local file=$1
+-
+-	# ZyXEL seems to use System V sum mode... Now this is classy, who would have thought?!
+-	echo $(sum -s ${file} | cut -f1 -d" ")
+-}
+-
+-append_bin() {
+-	local val=$1
+-	local size=$2
+-	local file=$3
+-
+-	while [ "$size" -ne 0 ]; do
+-		printf \\$(printf %o $(("$val" & 0xff)))  >> "$file"
+-		val=$(($val >> 8))
+-		let size-=1
+-	done
+-	return
+-}
+-
+-tf=${tmpdir}/out
+-pad=$(printf '%0.1s' $(printf "\xff"){1..64})
+-
+-rootfs_header_file="$tmpdir/rootfs_header"
+-rootfs_chksum=$(to_be $(checksum_file ${rootfs}) 4)
+-rootfs_len=$(to_be $(wc -c < "$rootfs") 4)
+-
+-versionpadlen=$(( 32 - ( ${#version} + 1) ))
+-
+-# 4 bytes:  checksum of the rootfs image
+-append_bin "$rootfs_chksum" 4 "$rootfs_header_file"
+-# 4 bytes:  length of the contained rootfs image file (big endian)
+-append_bin "$rootfs_len" 4 "$rootfs_header_file"
+-# 32 bytes:  Firmware Version string (NUL terminated, 0xff padded)
+-printf "%s\x00%.*s" "$version" "$versionpadlen" "$pad" >> "$rootfs_header_file"
+-
+-kernel_header_file="$tmpdir/kernel_header"
+-kernel_chksum=$(to_be $(checksum_file ${kernel}) 4)
+-kernel_len=$(to_be $(wc -c < "$kernel") 4)
+-
+-# 4 bytes:  checksum of the kernel image
+-append_bin "$kernel_chksum" 4 "$kernel_header_file"
+-# 4 bytes:  length of the contained kernel image file (big endian)
+-append_bin "$kernel_len" 4 "$kernel_header_file"
+-
+-board_header_file="$tmpdir/board_header"
+-board_file="$tmpdir/board"
+-boardpadlen=$(( 64 - ( ${#board} + 1) ))
+-# 32 bytes:  Model (e.g. "NBG6617", NUL termiated, 0xff padded)
+-printf "%s\x00%.*s" "$board" "$boardpadlen" "$pad" > "$board_file"
+-cat "$kernel_header_file" >> "$board_file"
+-printf "%.12s" "$pad" >> "$board_file"
+-#      rest: 0xff padding
+-for i in {1..511}; do
+-	printf "%s%s" "$pad" "$pad" >> "$board_file"
+-done
+-
+-tmp_board_file="$tmpdir/tmp_board_file"
+-cat "$rootfs_header_file" > "$tmp_board_file"
+-
+-# The checksum for the header is calculated over the first 2048 bytes with
+-# the rootfs image checksum as the placeholder during calculation.
+-append_bin "$rootfs_chksum" 4 "$tmp_board_file"
+-cat "$board_file" >> "$tmp_board_file"
+-
+-truncate -s 2048 $tmp_board_file
+-board_chksum=$(to_be $(checksum_file ${tmp_board_file}) 4)
+-
+-# 4 bytes:  checksum over the header partition (big endian)
+-append_bin "$board_chksum" 4 "$board_header_file"
+-cat "$board_file" >> "$board_header_file"
+-
+-cat "$rootfs_header_file" "$board_header_file" "$rootfs" "$kernel" > "$outfile"
+-
+-rm -rf "$tmpdir"
+diff --git a/target/linux/ar71xx/image/generic.mk b/target/linux/ar71xx/image/generic.mk
+index 640557532c8a02f37bc6f84ade8cb34e7172162d..4568b656219419e9ca1156c6716bd2124074cb32 100644
+--- a/target/linux/ar71xx/image/generic.mk
++++ b/target/linux/ar71xx/image/generic.mk
+@@ -1086,8 +1086,12 @@ define Device/NBG6616
+   IMAGE_SIZE := 15323k
+   MTDPARTS := spi0.0:192k(u-boot)ro,64k(env)ro,64k(RFdata)ro,384k(zyxel_rfsd),384k(romd),64k(header),2048k(kernel),13184k(rootfs),15232k@0x120000(firmware)
+   CMDLINE += mem=128M
+-  IMAGES := sysupgrade.bin
++  RAS_BOARD := NBG6616
++  RAS_ROOTFS_SIZE := 14464k
++  RAS_VERSION := "$(VERSION_DIST) $(REVISION)"
++  IMAGES := factory.bin sysupgrade.bin
+   KERNEL := kernel-bin | patch-cmdline | lzma | uImage lzma | jffs2 boot/vmlinux.lzma.uImage
++  IMAGE/factory.bin := append-kernel | pad-to $$$$(KERNEL_SIZE) | append-rootfs | pad-rootfs | pad-to 64k | check-size $$$$(IMAGE_SIZE) | zyxel-ras-image
+   IMAGE/sysupgrade.bin := append-kernel | pad-to $$$$(KERNEL_SIZE) | append-rootfs | pad-rootfs | check-size $$$$(IMAGE_SIZE)
+   # We cannot currently build a factory image. It is the sysupgrade image
+   # prefixed with a header (which is actually written into the MTD device).
+diff --git a/target/linux/ipq40xx/image/Makefile b/target/linux/ipq40xx/image/Makefile
+index d1ee1004fddce6cf6007259229d8eee5b5b2ea3b..5cd11cae237b4658906652967d8120bd0dc080a8 100644
+--- a/target/linux/ipq40xx/image/Makefile
++++ b/target/linux/ipq40xx/image/Makefile
+@@ -221,7 +221,7 @@ define Device/zyxel_nbg6617
+ #	at least as large as the one of the initial firmware image (not the current
+ #	one on the device). This only applies to the Web-UI, the bootlaoder ignores
+ #	this minimum-size. However, the larger image can be flashed both ways.
+-	IMAGE/factory.bin := append-rootfs | pad-rootfs | check-size $$$$(ROOTFS_SIZE) | make-ras
++	IMAGE/factory.bin := append-rootfs | pad-rootfs | pad-to 64k | check-size $$$$(ROOTFS_SIZE) | zyxel-ras-image separate-kernel
+ 	IMAGE/sysupgrade.bin/squashfs := append-rootfs | pad-rootfs | check-size $$$$(ROOTFS_SIZE) | sysupgrade-tar rootfs=$$$$@ | append-metadata
+ 	DEVICE_PACKAGES := ipq-wifi-zyxel_nbg6617 uboot-envtools
+ endef
+diff --git a/target/linux/ipq806x/image/Makefile b/target/linux/ipq806x/image/Makefile
+index 2902af3231a87592e9c040e7e7e1e4e1958a01a5..a7f740ff62e05c7f6f24caa6da538302abee8d8f 100644
+--- a/target/linux/ipq806x/image/Makefile
++++ b/target/linux/ipq806x/image/Makefile
+@@ -67,7 +67,8 @@ define Device/ZyXELImage
+ 	KERNEL_SUFFIX := -uImage
+ 	KERNEL = kernel-bin | append-dtb | uImage none | pad-to $${KERNEL_SIZE}
+ 	KERNEL_NAME := zImage
+-	IMAGES := sysupgrade.bin mmcblk0p5-rootfs.bin mmcblk0p4-kernel.bin
++	IMAGES := factory.bin sysupgrade.bin mmcblk0p5-rootfs.bin mmcblk0p4-kernel.bin
++	IMAGE/factory.bin := append-rootfs | pad-rootfs | pad-to $$$$(BLOCKSIZE) | zyxel-ras-image separate-kernel
+ 	IMAGE/sysupgrade.bin/squashfs := append-rootfs | pad-to $$$${BLOCKSIZE} | sysupgrade-tar rootfs=$$$$@ | append-metadata
+ 	IMAGE/mmcblk0p5-rootfs.bin := append-rootfs | pad-rootfs | pad-to $$$${BLOCKSIZE}
+ 	IMAGE/mmcblk0p4-kernel.bin := append-kernel
+@@ -245,6 +246,9 @@ define Device/zyxel_nbg6817
+ 	KERNEL_SIZE := 4096k
+ 	BLOCKSIZE := 64k
+ 	BOARD_NAME := nbg6817
++	RAS_BOARD := NBG6817
++	RAS_ROOTFS_SIZE := 20934k
++	RAS_VERSION := "V1.99(OWRT.9999)C0"
+ 	SUPPORTED_DEVICES += nbg6817
+ 	DEVICE_TITLE := ZyXEL NBG6817
+ 	DEVICE_PACKAGES := ath10k-firmware-qca9984 e2fsprogs kmod-fs-ext4 losetup
+diff --git a/tools/firmware-utils/Makefile b/tools/firmware-utils/Makefile
+index 4b4af999088455e3d9231b227fecfa71c8284b2f..a6379e35eb01f1cbbe2b1ece3fc9eb20bcd68d90 100644
+--- a/tools/firmware-utils/Makefile
++++ b/tools/firmware-utils/Makefile
+@@ -70,6 +70,7 @@ define Host/Compile
+ 	$(call cc,fix-u-media-header cyg_crc32,-Wall)
+ 	$(call cc,hcsmakeimage bcmalgo)
+ 	$(call cc,mkporayfw, -Wall)
++	$(call cc,mkrasimage, --std=gnu99)
+ 	$(call cc,mkhilinkfw, -lcrypto)
+ 	$(call cc,mkdcs932, -Wall)
+ 	$(call cc,mkheader_gemtek,-lz)
+diff --git a/tools/firmware-utils/src/mkrasimage.c b/tools/firmware-utils/src/mkrasimage.c
+new file mode 100644
+index 0000000000000000000000000000000000000000..8eee29cc086a04104b51e263aeac0e73340056ca
+--- /dev/null
++++ b/tools/firmware-utils/src/mkrasimage.c
+@@ -0,0 +1,459 @@
++/*
++ * --- ZyXEL header format ---
++ * Original Version by Benjamin Berg <benjamin@sipsolutions.net>
++ * C implementation based on generation-script by Christian Lamparter <chunkeey@gmail.com>
++ *
++ * The firmware image prefixed with a header (which is written into the MTD device).
++ * The header is one erase block (~64KiB) in size, but the checksum only convers the
++ * first 2KiB. Padding is 0xff. All integers are in big-endian.
++ *
++ * The checksum is always a 16-Bit System V checksum (sum -s) stored in a 32-Bit integer.
++ *
++ *   4 bytes:  checksum of the rootfs image
++ *   4 bytes:  length of the contained rootfs image file (big endian)
++ *  32 bytes:  Firmware Version string (NUL terminated, 0xff padded)
++ *   4 bytes:  checksum over the header partition (big endian - see below)
++ *  64 bytes:  Model (e.g. "NBG6617", NUL termiated, 0xff padded)
++ *   4 bytes:  checksum of the kernel partition
++ *   4 bytes:  length of the contained kernel image file (big endian)
++ *      rest:  0xff padding (To erase block size)
++ *
++ * The kernel partition checksum and length is not used for every device.
++ * If it's notused, pad those 8 bytes with 0xFF.
++ *
++ * The checksums are calculated by adding up all bytes and if a 16bit
++ * overflow occurs, one is added and the sum is masked to 16 bit:
++ *   csum = csum + databyte; if (csum > 0xffff) { csum += 1; csum &= 0xffff };
++ * Should the file have an odd number of bytes then the byte len-0x800 is
++ * used additionally.
++ *
++ * The checksum for the header is calculated over the first 2048 bytes with
++ * the rootfs image checksum as the placeholder during calculation.
++ *
++ * This program is free software; you can redistribute it and/or modify it
++ * under the terms of the GNU General Public License version 2 as published
++ * by the Free Software Foundation.
++ *
++ */
++#include <fcntl.h>
++#include <getopt.h>
++#include <libgen.h>
++#include <stdio.h>
++#include <string.h>
++#include <stdlib.h>
++#include <unistd.h>
++
++#include <sys/mman.h>
++#include <sys/stat.h>
++
++#include <arpa/inet.h>
++
++#define VERSION_STRING_LEN 31
++#define ROOTFS_HEADER_LEN 40
++
++#define KERNEL_HEADER_LEN 8
++
++#define BOARD_NAME_LEN 64
++#define BOARD_HEADER_LEN 68
++
++#define HEADER_PARTITION_CALC_LENGTH 2048
++#define HEADER_PARTITION_LENGTH 0x10000
++
++struct file_info {
++    char *name;    /* name of the file */
++    char *data;    /* file content */
++    size_t size;   /* length of the file */
++};
++
++static char *progname;
++
++static char *board_name = 0;
++static char *version_name = 0;
++static unsigned int rootfs_size = 0;
++
++static struct file_info kernel = { NULL, NULL, 0 };
++static struct file_info rootfs = { NULL, NULL, 0 };
++static struct file_info rootfs_out = { NULL, NULL, 0 };
++static struct file_info out = { NULL, NULL, 0 };
++
++#define ERR(fmt, ...) do { \
++    fprintf(stderr, "[%s] *** error: " fmt "\n", \
++            progname, ## __VA_ARGS__ ); \
++} while (0)
++
++void map_file(struct file_info *finfo)
++{
++    struct stat file_stat = {0};
++    int fd;
++
++    fd = open(finfo->name, O_RDONLY, (mode_t)0600);
++    if (fd == -1) {
++        ERR("Error while opening file %s.", finfo->name);
++        exit(EXIT_FAILURE);
++    }
++
++    if (fstat(fd, &file_stat) == -1) {
++        ERR("Error getting file size for %s.", finfo->name);
++        exit(EXIT_FAILURE);
++    }
++
++    finfo->size = file_stat.st_size;
++    finfo->data = mmap(0, finfo->size, PROT_READ, MAP_SHARED, fd, 0);
++
++    if (finfo->data == MAP_FAILED) {
++        ERR("Error mapping file %s.", finfo->name);
++        exit(EXIT_FAILURE);
++    }
++
++    close(fd);
++}
++
++void unmap_file(struct file_info *finfo)
++{
++    if(munmap(finfo->data, finfo->size) == -1) {
++        ERR("Error unmapping file %s.", finfo->name);
++        exit(EXIT_FAILURE);
++    }
++}
++
++void write_file(struct file_info *finfo)
++{
++    FILE *fout = fopen(finfo->name, "w");
++
++    fwrite(finfo->data, finfo->size, 1, fout);
++
++    if (ferror(fout)) {
++        ERR("Wanted to write, but something went wrong.");
++        exit(EXIT_FAILURE);
++    }
++
++    fclose(fout);
++}
++
++void usage(int status)
++{
++    FILE *stream = (status != EXIT_SUCCESS) ? stderr : stdout;
++
++    fprintf(stream, "Usage: %s [OPTIONS...]\n", progname);
++    fprintf(stream,
++            "\n"
++            "Options:\n"
++            "  -k <kernel>     path for kernel image\n"
++            "  -r <rootfs>     path for rootfs image\n"
++            "  -s <rfssize>    size of output rootfs\n"
++            "  -v <version>    version string\n"
++            "  -b <boardname>  name of board to generate image for\n"
++            "  -o <out_name>   name of output image\n"
++            "  -h              show this screen\n"
++    );
++
++    exit(status);
++}
++
++static int sysv_chksm(const unsigned char *data, int size)
++{
++    int r;
++    int checksum;
++    unsigned int s = 0; /* The sum of all the input bytes, modulo (UINT_MAX + 1).  */
++
++
++    for (int i = 0; i < size; i++) {
++        s += data[i];
++    }
++
++    r = (s & 0xffff) + ((s & 0xffffffff) >> 16);
++    checksum = (r & 0xffff) + (r >> 16);
++
++    return checksum;
++}
++
++static int zyxel_chksm(const unsigned char *data, int size)
++{
++     return htonl(sysv_chksm(data, size));
++}
++
++char *generate_rootfs_header(struct file_info filesystem, char *version)
++{
++    size_t version_string_length;
++    unsigned int chksm, size;
++    char *rootfs_header;
++    size_t ptr = 0;
++
++    rootfs_header = malloc(ROOTFS_HEADER_LEN);
++    if (!rootfs_header) {
++        ERR("Couldn't allocate memory for rootfs header!");
++        exit(EXIT_FAILURE);
++    }
++
++    /* Prepare padding for firmware-version string here */
++    memset(rootfs_header, 0xff, ROOTFS_HEADER_LEN);
++
++    chksm = zyxel_chksm((const unsigned char *)filesystem.data, filesystem.size);
++    size = htonl(filesystem.size);
++
++    /* 4 bytes:  checksum of the rootfs image */
++    memcpy(rootfs_header + ptr, &chksm, 4);
++    ptr += 4;
++
++    /* 4 bytes:  length of the contained rootfs image file (big endian) */
++    memcpy(rootfs_header + ptr, &size, 4);
++    ptr += 4;
++
++    /* 32 bytes:  Firmware Version string (NUL terminated, 0xff padded) */
++    version_string_length = strlen(version) <= VERSION_STRING_LEN ? strlen(version) : VERSION_STRING_LEN;
++    memcpy(rootfs_header + ptr, version, version_string_length);
++    ptr += version_string_length;
++    /* Add null-terminator */
++    rootfs_header[ptr] = 0x0;
++
++    return rootfs_header;
++}
++
++char *generate_kernel_header(struct file_info kernel)
++{
++    unsigned int chksm, size;
++    char *kernel_header;
++    size_t ptr = 0;
++
++    kernel_header = malloc(KERNEL_HEADER_LEN);
++    if (!kernel_header) {
++        ERR("Couldn't allocate memory for kernel header!");
++        exit(EXIT_FAILURE);
++    }
++
++    chksm = zyxel_chksm((const unsigned char *)kernel.data, kernel.size);
++    size = htonl(kernel.size);
++
++    /* 4 bytes:  checksum of the kernel image */
++    memcpy(kernel_header + ptr, &chksm, 4);
++    ptr += 4;
++
++    /* 4 bytes:  length of the contained kernel image file (big endian) */
++    memcpy(kernel_header + ptr, &size, 4);
++
++    return kernel_header;
++}
++
++unsigned int generate_board_header_checksum(char *kernel_hdr, char *rootfs_hdr, char *boardname)
++{
++    char *board_hdr_tmp;
++    unsigned int sum;
++    size_t ptr = 0;
++
++    /*
++     * The checksum of the board header is calculated over the first 2048 bytes of
++     * the header partition with the rootfs checksum used as a placeholder for then
++     * board checksum we calculate in this step. The checksum gained from this step
++     * is then used for the final board header partition.
++     */
++
++    board_hdr_tmp = malloc(HEADER_PARTITION_CALC_LENGTH);
++    if (!board_hdr_tmp) {
++        ERR("Couldn't allocate memory for temporary board header!");
++        exit(EXIT_FAILURE);
++    }
++    memset(board_hdr_tmp, 0xff, HEADER_PARTITION_CALC_LENGTH);
++
++    /* 40 bytes:  RootFS header */
++    memcpy(board_hdr_tmp, rootfs_hdr, ROOTFS_HEADER_LEN);
++    ptr += ROOTFS_HEADER_LEN;
++
++    /* 4 bytes:  RootFS checksum (BE) as placeholder for board-header checksum */
++    memcpy(board_hdr_tmp + ptr, rootfs_hdr, 4);
++    ptr += 4;
++
++    /* 32 bytes:  Model (e.g. "NBG6617", NUL termiated, 0xff padded) */
++    memcpy(board_hdr_tmp + ptr, boardname, strlen(boardname));
++    ptr += strlen(boardname);
++    /* Add null-terminator */
++    board_hdr_tmp[ptr] = 0x0;
++    ptr = ROOTFS_HEADER_LEN + 4 + BOARD_NAME_LEN;
++
++    /* 8 bytes:  Kernel header */
++    if (kernel_hdr)
++        memcpy(board_hdr_tmp + ptr, kernel_hdr, 8);
++
++    /* Calculate the checksum over the first 2048 bytes */
++    sum = zyxel_chksm((const unsigned char *)board_hdr_tmp, HEADER_PARTITION_CALC_LENGTH);
++    free(board_hdr_tmp);
++    return sum;
++}
++
++char *generate_board_header(char *kernel_hdr, char *rootfs_hdr, char *boardname)
++{
++    unsigned int board_checksum;
++    char *board_hdr;
++
++    board_hdr = malloc(BOARD_HEADER_LEN);
++    if (!board_hdr) {
++        ERR("Couldn't allocate memory for board header!");
++        exit(EXIT_FAILURE);
++    }
++    memset(board_hdr, 0xff, BOARD_HEADER_LEN);
++
++    /* 4 bytes:  checksum over the header partition (big endian) */
++    board_checksum = generate_board_header_checksum(kernel_hdr, rootfs_hdr, boardname);
++    memcpy(board_hdr, &board_checksum, 4);
++
++    /* 32 bytes:  Model (e.g. "NBG6617", NUL termiated, 0xff padded) */
++    memcpy(board_hdr + 4, boardname, strlen(boardname));
++    board_hdr[4 + strlen(boardname)] = 0x0;
++
++    return board_hdr;
++}
++
++int build_image()
++{
++    char *rootfs_header = NULL;
++    char *kernel_header = NULL;
++    char *board_header = NULL;
++
++    size_t ptr;
++
++    /* Load files */
++    if (kernel.name)
++        map_file(&kernel);
++    map_file(&rootfs);
++
++    /*
++     * Allocate memory and copy input rootfs for temporary output rootfs.
++     * This is important as we have to generate the rootfs checksum over the
++     * entire rootfs partition. As we might have to pad the partition to allow
++     * for flashing via ZyXEL's Web-GUI, we prepare the rootfs partition for the
++     * output image here (and also use it for calculating the rootfs checksum).
++     *
++     * The roofs padding has to be done with 0x00.
++     */
++    rootfs_out.data = calloc(rootfs_out.size, sizeof(char));
++    memcpy(rootfs_out.data, rootfs.data, rootfs.size);
++
++    /* Prepare headers */
++    rootfs_header = generate_rootfs_header(rootfs_out, version_name);
++    if (kernel.name)
++        kernel_header = generate_kernel_header(kernel);
++    board_header = generate_board_header(kernel_header, rootfs_header, board_name);
++
++    /* Prepare output file */
++    out.size = HEADER_PARTITION_LENGTH + rootfs_out.size;
++    if (kernel.name)
++        out.size += kernel.size;
++    out.data = malloc(out.size);
++    memset(out.data, 0xFF, out.size);
++
++    /* Build output image */
++    memcpy(out.data, rootfs_header, ROOTFS_HEADER_LEN);
++    memcpy(out.data + ROOTFS_HEADER_LEN, board_header, BOARD_HEADER_LEN);
++    if (kernel.name)
++        memcpy(out.data + ROOTFS_HEADER_LEN + BOARD_HEADER_LEN, kernel_header, KERNEL_HEADER_LEN);
++    ptr = HEADER_PARTITION_LENGTH;
++    memcpy(out.data + ptr, rootfs_out.data, rootfs_out.size);
++    ptr += rootfs_out.size;
++    if (kernel.name)
++        memcpy(out.data + ptr, kernel.data, kernel.size);
++
++    /* Write back output image */
++    write_file(&out);
++
++    /* Free allocated memory */
++    if (kernel.name)
++        unmap_file(&kernel);
++    unmap_file(&rootfs);
++    free(out.data);
++    free(rootfs_out.data);
++
++    free(rootfs_header);
++    if (kernel.name)
++        free(kernel_header);
++    free(board_header);
++
++    return 0;
++}
++
++int check_options()
++{
++    if (!rootfs.name) {
++        ERR("No rootfs filename supplied");
++        return -2;
++    }
++
++    if (!out.name) {
++        ERR("No output filename supplied");
++        return -3;
++    }
++
++    if (!board_name) {
++        ERR("No board-name supplied");
++        return -4;
++    }
++
++    if (!version_name) {
++        ERR("No version supplied");
++        return -5;
++    }
++
++    if (rootfs_size <= 0) {
++        ERR("Invalid rootfs size supplied");
++        return -6;
++    }
++
++    if (strlen(board_name) > 31) {
++        ERR("Board name is to long");
++        return -7;
++    }
++    return 0;
++}
++
++int main(int argc, char *argv[])
++{
++    int ret;
++    progname = basename(argv[0]);
++    while (1) {
++        int c;
++
++        c = getopt(argc, argv, "b:k:o:r:s:v:h");
++        if (c == -1)
++            break;
++
++        switch (c) {
++            case 'b':
++                board_name = optarg;
++                break;
++            case 'h':
++                usage(EXIT_SUCCESS);
++                break;
++            case 'k':
++                kernel.name = optarg;
++                break;
++            case 'o':
++                out.name = optarg;
++                break;
++            case 'r':
++                rootfs.name = optarg;
++                break;
++            case 's':
++                sscanf(optarg, "%u", &rootfs_size);
++                break;
++            case 'v':
++                version_name = optarg;
++                break;
++            default:
++                usage(EXIT_FAILURE);
++                break;
++        }
++    }
++
++    ret = check_options();
++    if (ret)
++        usage(EXIT_FAILURE);
++
++    /* As ZyXEL Web-GUI only accept images with a rootfs equal or larger than the first firmware shipped
++     * for the device, we need to pad rootfs partition to this size. To perform further calculations, we
++     * decide the size of this part here. In case the rootfs we want to integrate in our image is larger,
++     * take it's size, otherwise the supplied size.
++     *
++     * Be careful! We rely on assertion of correct size to be performed beforehand. It is unknown if images
++     * with a to large rootfs are accepted or not.
++     */
++    rootfs_out.size = rootfs_size < rootfs.size ? rootfs.size : rootfs_size;
++    return build_image();
++}
diff --git a/patches/openwrt/0011-ipq40xx-fix-NBG6617-LED-mapping.patch b/patches/openwrt/0011-ipq40xx-fix-NBG6617-LED-mapping.patch
new file mode 100644
index 000000000..b3fff8d21
--- /dev/null
+++ b/patches/openwrt/0011-ipq40xx-fix-NBG6617-LED-mapping.patch
@@ -0,0 +1,27 @@
+From: David Bauer <mail@david-bauer.net>
+Date: Sun, 4 Nov 2018 01:00:18 +0100
+Subject: ipq40xx: fix NBG6617 LED mapping
+
+The NBG6617's LEDs are wrongly identified in the 01_leds boardinit
+script (board instead of boardname), thus referencing non-existant LEDs
+in UCI.
+
+Signed-off-by: David Bauer <mail@david-bauer.net>
+
+diff --git a/target/linux/ipq40xx/base-files/etc/board.d/01_leds b/target/linux/ipq40xx/base-files/etc/board.d/01_leds
+index 475a8623f705d683a7ee3521ee71f2b57fa2e44d..ab2f77ee10a8e4371d8c52d6d6b6a35bae8aceb6 100755
+--- a/target/linux/ipq40xx/base-files/etc/board.d/01_leds
++++ b/target/linux/ipq40xx/base-files/etc/board.d/01_leds
+@@ -35,9 +35,9 @@ meraki,mr33)
+ 	ucidef_set_interface_lan "eth0"
+ 	;;
+ zyxel,nbg6617)
+-	ucidef_set_led_wlan "wlan2g" "WLAN2G" "$board:green:wlan2G" "phy0tpt"
+-	ucidef_set_led_wlan "wlan5g" "WLAN5G" "$board:green:wlan5G" "phy1tpt"
+-	ucidef_set_led_usbport "usb" "USB" "${board}:green:usb" "usb1-port1" "usb2-port1" "usb3-port1" "usb4-port1"
++	ucidef_set_led_wlan "wlan2g" "WLAN2G" "${boardname}:green:wlan2G" "phy0tpt"
++	ucidef_set_led_wlan "wlan5g" "WLAN5G" "${boardname}:green:wlan5G" "phy1tpt"
++	ucidef_set_led_usbport "usb" "USB" "${boardname}:green:usb" "usb1-port1" "usb2-port1" "usb3-port1" "usb4-port1"
+ 	;;
+ zyxel,wre6606)
+ 	ucidef_set_led_wlan "wlan2g" "WLAN2G" "${boardname}:green:wlan2g" "phy0tpt"
diff --git a/targets/ipq40xx b/targets/ipq40xx
index 1ab2530d8..23e29a089 100644
--- a/targets/ipq40xx
+++ b/targets/ipq40xx
@@ -43,6 +43,9 @@ packages $ATH10K_PACKAGES_IPQ40XX $ATH10K_PACKAGES_QCA9888
 
 # ZyXEL
 
+device zyxel-nbg6617 zyxel_nbg6617
+packages $ATH10K_PACKAGES_IPQ40XX
+
 device zyxel-wre6606 zyxel_wre6606
 factory
 packages $ATH10K_PACKAGES_IPQ40XX
-- 
GitLab