Skip to content

Commit

Permalink
ramips: add support for ZyXEL NWA50AX / NWA55AXE
Browse files Browse the repository at this point in the history
Hardware
--------
CPU:    Mediatek MT7621
RAM:    256M DDR3
FLASH:  128M NAND
ETH:    1x Gigabit Ethernet
WiFi:   Mediatek MT7915 (2.4/5GHz 802.11ax 2x2 DBDC)
BTN:    1x Reset (NWA50AX only)
LED:    1x Multi-Color (NWA50AX only)

UART Console
------------
NWA50AX:
Available below the rubber cover next to the ethernet port.
NWA55AXE:
Available on the board when disassembling the device.

Settings: 115200 8N1

Layout:

<12V> <LAN> GND-RX-TX-VCC

Logic-Level is 3V3. Don't connect VCC to your UART adapter!

Installation Web-UI
-------------------
Upload the Factory image using the devices Web-Interface.

As the device uses a dual-image partition layout, OpenWrt can only
installed on Slot A. This requires the current active image prior
flashing the device to be on Slot B.

If the currently installed image is started from Slot A, the device will
flash OpenWrt to Slot B. OpenWrt will panic upon first boot in this case
and the device will return to the ZyXEL firmware upon next boot.

If this happens, first install a ZyXEL firmware upgrade of any version
and install OpenWrt after that.

Installation TFTP
-----------------
This installation routine is especially useful in case
 * unknown device password (NWA55AXE lacks reset button)
 * bricked device

Attach to the UART console header of the device. Interrupt the boot
procedure by pressing Enter.

The bootloader has a reduced command-set available from CLI, but more
commands can be executed by abusing the atns command.

Boot a OpenWrt initramfs image available on a TFTP server at
192.168.1.66. Rename the image to owrt.bin

 $ atnf owrt.bin
 $ atna 192.168.1.88
 $ atns "192.168.1.66; tftpboot; bootm"

Upon booting, set the booted image to the correct slot:

 $ zyxel-bootconfig /dev/mtd10 get-status
 $ zyxel-bootconfig /dev/mtd10 set-image-status 0 valid
 $ zyxel-bootconfig /dev/mtd10 set-active-image 0

Copy the OpenWrt ramboot-factory image to the device using scp.
Write the factory image to NAND and reboot the device.

 $ mtd write ramboot-factory.bin firmware
 $ reboot

Signed-off-by: David Bauer <mail@david-bauer.net>
  • Loading branch information
blocktrron committed Jul 20, 2022
1 parent 3f2b2fe commit a0b7fef
Show file tree
Hide file tree
Showing 12 changed files with 678 additions and 2 deletions.
38 changes: 38 additions & 0 deletions package/utils/zyxel-bootconfig/Makefile
@@ -0,0 +1,38 @@
#
# Copyright (C) 2022 David Bauer <mail@david-bauer.net>
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#

include $(TOPDIR)/rules.mk

PKG_NAME:=zyxel-bootconfig
PKG_RELEASE:=1

include $(INCLUDE_DIR)/package.mk

define Package/zyxel-bootconfig
SECTION:=utils
CATEGORY:=Base system
TITLE:=Utility for handling ZyXEL Bootconfig settings
MAINTAINER:=David Bauer <mail@david-bauer.net>
endef

define Package/zyxel-bootconfig/description
This package contains an utility that allows handling ZyXEL Bootconfig settings.
endef

define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) \
CC="$(TARGET_CC)" \
CFLAGS="$(TARGET_CFLAGS) -Wall"
endef

define Package/zyxel-bootconfig/install
$(INSTALL_DIR) $(1)/usr/bin $(1)/lib/preinit
$(INSTALL_BIN) $(PKG_BUILD_DIR)/zyxel-bootconfig $(1)/usr/bin/
$(CP) ./files/95_apply_bootconfig $(1)/lib/preinit/95_apply_bootconfig
endef

$(eval $(call BuildPackage,zyxel-bootconfig))
16 changes: 16 additions & 0 deletions package/utils/zyxel-bootconfig/files/95_apply_bootconfig
@@ -0,0 +1,16 @@
apply_bootconfig() {
. /lib/functions.sh

local part

case $(board_name) in
zyxel,nwa50ax|\
zyxel,nwa55axe)
mtd_idx=$(find_mtd_index "bootconfig")
zyxel-bootconfig "/dev/mtd$mtd_idx" set-image-status 0 valid
zyxel-bootconfig "/dev/mtd$mtd_idx" set-active-image 0
;;
esac
}

[ "$INITRAMFS" = "1" ] || boot_hook_add preinit_main apply_bootconfig
7 changes: 7 additions & 0 deletions package/utils/zyxel-bootconfig/src/Makefile
@@ -0,0 +1,7 @@
all: zyxel-bootconfig

zyxel-bootconfig:
$(CC) $(CFLAGS) -Wall zyxel-bootconfig.c -o zyxel-bootconfig

clean:
rm -f zyxel-bootconfig
331 changes: 331 additions & 0 deletions package/utils/zyxel-bootconfig/src/zyxel-bootconfig.c
@@ -0,0 +1,331 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (C) 2022 David Bauer <mail@david-bauer.net> */

/*
* First byte: Image status
*
* Possible status-codes:
* 0x0: none
* 0x1: new
* 0x2: valid
* 0x3: invalid
*
* Example: Image 0 valid; Image 1 invalid
* 11001000
* || ||
* img1||
* img0
*
* Second byte: Active Image
* Possible values:
* 0x0: Image0 active
* 0x1: Image1 active
*/

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stddef.h>

#include <fcntl.h>
#include <string.h>
#include <unistd.h>

#include <sys/ioctl.h>
#include <mtd/mtd-user.h>

#define BOOTCONFIG_SIZE 0x20
#define BOOTCONFIG_IMAGE_STATUS 0x0
#define BOOTCONFIG_ACTIVE_IMAGE 0x1

#define IMAGE_0_SHIFT 2
#define IMAGE_0_MASK 0x0c
#define IMAGE_1_SHIFT 6
#define IMAGE_1_MASK 0xc0

#define IMAGE_STATUS(img0, img1) (((img0 << IMAGE_0_SHIFT) & IMAGE_0_MASK) | ((img1 << IMAGE_1_SHIFT) & IMAGE_1_MASK))

#define ACTIVE_IMAGE_MASK 0x1
#define ACTIVE_IMAGE(img) (img & ACTIVE_IMAGE_MASK)

enum zyxel_bootconfig_image_status {
IMAGE_STATUS_NONE = 0x0,
IMAGE_STATUS_NEW = 0x1,
IMAGE_STATUS_VALID = 0x2,
IMAGE_STATUS_INVALID = 0x3,
__IMAGE_STATUS_EINVAL,
};

struct zyxel_bootconfig {
enum zyxel_bootconfig_image_status image0_status;
enum zyxel_bootconfig_image_status image1_status;
unsigned int active_image;
};

struct zyxel_bootconfig_mtd {
struct mtd_info_user mtd_info;
int fd;
};

struct zyxel_image_status {
enum zyxel_bootconfig_image_status code;
const char *name;
};

struct zyxel_image_status image_status_codes[] = {
{ .code = IMAGE_STATUS_NONE, .name = "none" },
{ .code = IMAGE_STATUS_NEW, .name = "new" },
{ .code = IMAGE_STATUS_VALID, .name = "valid" },
{ .code = IMAGE_STATUS_INVALID, .name = "invalid" },
{},
};


static enum zyxel_bootconfig_image_status zyxel_bootconfig_image_status_parse(const char *status) {
struct zyxel_image_status* s;

for (s = image_status_codes; s->name; s++) {
if (!strcmp(status, s->name)) {
return s->code;
}
}

return __IMAGE_STATUS_EINVAL;
}

const char *zyxel_bootconfig_image_status_name(const enum zyxel_bootconfig_image_status bootconfig) {
struct zyxel_image_status* s;

for (s = image_status_codes; s->name; s++) {
if (bootconfig == s->code) {
return s->name;
}
}

return "N/A";
}

static void zyxel_bootconfig_mtd_close(struct zyxel_bootconfig_mtd *mtd) {
close(mtd->fd);
}


static int zyxel_bootconfig_mtd_open(struct zyxel_bootconfig_mtd *mtd, const char *mtd_name) {
int ret = 0;

mtd->fd = open(mtd_name, O_RDWR | O_SYNC);
if (mtd->fd < 0) {
fprintf(stderr, "Could not open mtd device: %s\n", mtd_name);
ret = -1;
goto out;
}

if (ioctl(mtd->fd, MEMGETINFO, &mtd->mtd_info)) {
fprintf(stderr, "Could not get MTD device info from %s\n", mtd_name);
ret = -1;
zyxel_bootconfig_mtd_close(mtd);
goto out;
}

out:
return ret;
}


static int zyxel_bootconfig_read(struct zyxel_bootconfig *config, struct zyxel_bootconfig_mtd *mtd) {
char *args = NULL;
int ret = 0;

/* Allocate memory for reading boot-config partition */
args = calloc(1, mtd->mtd_info.erasesize);
if (!args) {
fprintf(stderr, "Could not allocate memory!\n");
ret = -1;
goto out;
}

/* Read bootconfig partition */
pread(mtd->fd, args, mtd->mtd_info.erasesize, 0);

/* Parse config */
memset(config, 0, sizeof(*config));

config->image0_status = (args[BOOTCONFIG_IMAGE_STATUS] & IMAGE_0_MASK) >> IMAGE_0_SHIFT;
config->image1_status = (args[BOOTCONFIG_IMAGE_STATUS] & IMAGE_1_MASK) >> IMAGE_1_SHIFT;
config->active_image = (args[BOOTCONFIG_ACTIVE_IMAGE] & ACTIVE_IMAGE_MASK);

out:
if (args)
free(args);
return ret;
}


static int zyxel_bootconfig_write(struct zyxel_bootconfig *config, struct zyxel_bootconfig_mtd *mtd)
{
struct erase_info_user erase_info;
char img_status, img_active;
char *args = NULL;
int ret = 0;

/* Allocate memory for reading boot-config partition */
args = calloc(1, mtd->mtd_info.erasesize);
if (!args) {
fprintf(stderr, "Could not allocate memory!\n");
ret = -1;
goto out;
}

/* Read bootconfig partition */
pread(mtd->fd, args, mtd->mtd_info.erasesize, 0);

img_status = IMAGE_STATUS(config->image0_status, config->image1_status);
img_active = ACTIVE_IMAGE(config->active_image);

/* Check if bootconfig has to be written */
if (args[BOOTCONFIG_IMAGE_STATUS] == img_status && args[BOOTCONFIG_ACTIVE_IMAGE] == img_active) {
ret = 0;
goto out;
}

/* Erase first block (containing the magic) */
erase_info.start = 0;
erase_info.length = mtd->mtd_info.erasesize;
ret = ioctl(mtd->fd, MEMERASE, &erase_info);
if (ret < 0) {
fprintf(stderr, "Failed to erase block: %i\n", ret);
goto out;
}


/* Write bootconfig */
args[BOOTCONFIG_IMAGE_STATUS] = img_status;
args[BOOTCONFIG_ACTIVE_IMAGE] = img_active;

if (pwrite(mtd->fd, args, mtd->mtd_info.erasesize, 0) != mtd->mtd_info.erasesize) {
fprintf(stderr, "Error writing bootconfig!\n");
ret = -1;
goto out;
}

out:
if (args)
free(args);
return ret;
}


static void zyxel_bootconfig_print_usage(char *programm)
{
struct zyxel_image_status* s = image_status_codes;

printf("Usage: %s <mtd-device> <command> [args]\n", programm);
printf("Available commands:\n");
printf(" get-status\n");
printf(" set-image-status [0/1] [");

while (s->name) {
printf("%s", s->name);
s++;

if (s->name)
printf(",");
}

printf("]\n");
printf(" set-active-image [0/1]\n");
}

int main(int argc, char *argv[])
{
enum zyxel_bootconfig_image_status image_status;
struct zyxel_bootconfig_mtd mtd;
struct zyxel_bootconfig config;
const char *mtd_name, *command;
bool writeback = false;
int image_idx;

if (argc < 3) {
zyxel_bootconfig_print_usage(argv[0]);
return 1;
}

mtd_name = argv[1];
command = argv[2];

if (zyxel_bootconfig_mtd_open(&mtd, mtd_name)) {
fprintf(stderr, "Error opening %s!\n", mtd_name);
return 1;
}

if (zyxel_bootconfig_read(&config, &mtd)) {
fprintf(stderr, "Error reading bootconfig!\n");
zyxel_bootconfig_mtd_close(&mtd);
return 1;
}

if (!strcmp(command, "set-image-status")) {
if (argc < 5) {
zyxel_bootconfig_print_usage(argv[0]);
zyxel_bootconfig_mtd_close(&mtd);
return 1;
}

image_idx = atoi(argv[3]);
if (image_idx > 1 || image_idx < 0) {
fprintf(stderr, "Invalid image-slot set!\n");
zyxel_bootconfig_mtd_close(&mtd);
return 1;
}

image_status = zyxel_bootconfig_image_status_parse(argv[4]);
if (image_status == __IMAGE_STATUS_EINVAL) {
fprintf(stderr, "Invalid image-status!\n");
zyxel_bootconfig_mtd_close(&mtd);
return 1;
}

if (image_idx == 0) {
config.image0_status = image_status;
} else {
config.image1_status = image_status;
}

writeback = true;
} else if (!strcmp(command, "set-active-image")) {
if (argc < 4) {
zyxel_bootconfig_print_usage(argv[0]);
zyxel_bootconfig_mtd_close(&mtd);
return 1;
}

image_idx = atoi(argv[3]);
if (image_idx > 1 || image_idx < 0) {
fprintf(stderr, "Invalid image-slot set!\n");
zyxel_bootconfig_mtd_close(&mtd);
return 1;
}

config.active_image = image_idx;

writeback = true;
} else if (!strcmp(command, "get-status")) {
printf("Active Image: %d\n", config.active_image);
printf("Image 0 Status: %s\n", zyxel_bootconfig_image_status_name(config.image0_status));
printf("Image 1 Status: %s\n", zyxel_bootconfig_image_status_name(config.image1_status));

writeback = false;
}

if (writeback) {
if (zyxel_bootconfig_write(&config, &mtd)) {
fprintf(stderr, "Error writing bootconfig!\n");
zyxel_bootconfig_mtd_close(&mtd);
return 1;
}

zyxel_bootconfig_mtd_close(&mtd);
}

return 0;
}

0 comments on commit a0b7fef

Please sign in to comment.