move to boot/ folder
[openwrt/staging/wigyori.git] / package / boot / rbcfg / src / main.c
diff --git a/package/boot/rbcfg/src/main.c b/package/boot/rbcfg/src/main.c
new file mode 100644 (file)
index 0000000..5614a6c
--- /dev/null
@@ -0,0 +1,791 @@
+/*
+ *  RouterBOOT configuration utility
+ *
+ *  Copyright (C) 2010 Gabor Juhos <juhosg@openwrt.org>
+ *
+ *  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 <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <linux/limits.h>
+
+#include "rbcfg.h"
+#include "cyg_crc.h"
+
+#define RBCFG_TMP_FILE "/tmp/.rbcfg"
+#define RBCFG_MTD_NAME "soft_config"
+
+#define RB_ERR_NOTFOUND                1
+#define RB_ERR_INVALID         2
+#define RB_ERR_NOMEM           3
+#define RB_ERR_IO              4
+
+#define ARRAY_SIZE(_a) (sizeof((_a)) / sizeof((_a)[0]))
+
+struct rbcfg_ctx {
+       char            *mtd_device;
+       char            *tmp_file;
+       char            *buf;
+       unsigned        buflen;
+};
+
+struct rbcfg_value {
+       const char              *name;
+       const char              *desc;
+       union {
+               uint32_t        u32;
+               const char      *raw;
+       } val;
+};
+
+#define RBCFG_ENV_TYPE_U32     0
+
+struct rbcfg_env {
+       const char                      *name;
+       int                             type;
+       uint16_t                        id;
+       const struct rbcfg_value        *values;
+       int                             num_values;
+};
+
+#define CMD_FLAG_USES_CFG      0x01
+
+struct rbcfg_command {
+       const char      *command;
+       const char      *usage;
+       int             flags;
+       int             (*exec)(int argc, const char *argv[]);
+};
+
+static void usage(void);
+
+/* Globals */
+
+static struct rbcfg_ctx *rbcfg_ctx;
+static char *rbcfg_name;
+
+#define CFG_U32(_name, _desc, _val) {  \
+       .name           = (_name),      \
+       .desc           = (_desc),      \
+       .val.u32        = (_val),       \
+}
+
+static const struct rbcfg_value rbcfg_boot_delay[] = {
+       CFG_U32("1", "1 second", RB_BOOT_DELAY_1SEC),
+       CFG_U32("2", "2 seconds", RB_BOOT_DELAY_2SEC),
+       CFG_U32("3", "3 seconds", RB_BOOT_DELAY_3SEC),
+       CFG_U32("4", "4 seconds", RB_BOOT_DELAY_4SEC),
+       CFG_U32("5", "5 seconds", RB_BOOT_DELAY_5SEC),
+       CFG_U32("6", "6 seconds", RB_BOOT_DELAY_6SEC),
+       CFG_U32("7", "7 seconds", RB_BOOT_DELAY_7SEC),
+       CFG_U32("8", "8 seconds", RB_BOOT_DELAY_8SEC),
+       CFG_U32("9", "9 seconds", RB_BOOT_DELAY_9SEC),
+};
+
+static const struct rbcfg_value rbcfg_boot_device[] = {
+       CFG_U32("eth", "boot over Ethernet",
+               RB_BOOT_DEVICE_ETHER),
+       CFG_U32("nandeth", "boot from NAND, if fail then Ethernet",
+               RB_BOOT_DEVICE_NANDETH),
+       CFG_U32("ethnand", "boot Ethernet once, then NAND",
+               RB_BOOT_DEVICE_ETHONCE),
+       CFG_U32("nand", "boot from NAND only",
+               RB_BOOT_DEVICE_NANDONLY),
+};
+
+static const struct rbcfg_value rbcfg_boot_key[] = {
+       CFG_U32("any", "any key", RB_BOOT_KEY_ANY),
+       CFG_U32("del", "<Delete> key only", RB_BOOT_KEY_DEL),
+};
+
+static const struct rbcfg_value rbcfg_boot_protocol[] = {
+       CFG_U32("bootp", "BOOTP protocol", RB_BOOT_PROTOCOL_BOOTP),
+       CFG_U32("dhcp", "DHCP protocol", RB_BOOT_PROTOCOL_DHCP),
+};
+
+static const struct rbcfg_value rbcfg_uart_speed[] = {
+       CFG_U32("115200", "", RB_UART_SPEED_115200),
+       CFG_U32("57600", "", RB_UART_SPEED_57600),
+       CFG_U32("38400", "", RB_UART_SPEED_38400),
+       CFG_U32("19200", "", RB_UART_SPEED_19200),
+       CFG_U32("9600", "", RB_UART_SPEED_9600),
+       CFG_U32("4800", "", RB_UART_SPEED_4800),
+       CFG_U32("2400", "", RB_UART_SPEED_2400),
+       CFG_U32("1200", "", RB_UART_SPEED_1200),
+       CFG_U32("off", "disable console output", RB_UART_SPEED_OFF),
+};
+
+static const struct rbcfg_value rbcfg_cpu_mode[] = {
+       CFG_U32("powersave", "power save", RB_CPU_MODE_POWERSAVE),
+       CFG_U32("regular", "regular (better for -0c environment)",
+               RB_CPU_MODE_REGULAR),
+};
+
+static const struct rbcfg_value rbcfg_booter[] = {
+       CFG_U32("regular", "load regular booter", RB_BOOTER_REGULAR),
+       CFG_U32("backup", "force backup-booter loading", RB_BOOTER_BACKUP),
+};
+
+static const struct rbcfg_env rbcfg_envs[] = {
+       {
+               .name           = "boot_delay",
+               .id             = RB_ID_BOOT_DELAY,
+               .type           = RBCFG_ENV_TYPE_U32,
+               .values         = rbcfg_boot_delay,
+               .num_values     = ARRAY_SIZE(rbcfg_boot_delay),
+       }, {
+               .name           = "boot_device",
+               .id             = RB_ID_BOOT_DEVICE,
+               .type           = RBCFG_ENV_TYPE_U32,
+               .values         = rbcfg_boot_device,
+               .num_values     = ARRAY_SIZE(rbcfg_boot_device),
+       }, {
+               .name           = "boot_key",
+               .id             = RB_ID_BOOT_KEY,
+               .type           = RBCFG_ENV_TYPE_U32,
+               .values         = rbcfg_boot_key,
+               .num_values     = ARRAY_SIZE(rbcfg_boot_key),
+       }, {
+               .name           = "boot_protocol",
+               .id             = RB_ID_BOOT_PROTOCOL,
+               .type           = RBCFG_ENV_TYPE_U32,
+               .values         = rbcfg_boot_protocol,
+               .num_values     = ARRAY_SIZE(rbcfg_boot_protocol),
+       }, {
+               .name           = "booter",
+               .id             = RB_ID_BOOTER,
+               .type           = RBCFG_ENV_TYPE_U32,
+               .values         = rbcfg_booter,
+               .num_values     = ARRAY_SIZE(rbcfg_booter),
+       }, {
+               .name           = "cpu_mode",
+               .id             = RB_ID_CPU_MODE,
+               .type           = RBCFG_ENV_TYPE_U32,
+               .values         = rbcfg_cpu_mode,
+               .num_values     = ARRAY_SIZE(rbcfg_cpu_mode),
+       }, {
+               .name           = "uart_speed",
+               .id             = RB_ID_UART_SPEED,
+               .type           = RBCFG_ENV_TYPE_U32,
+               .values         = rbcfg_uart_speed,
+               .num_values     = ARRAY_SIZE(rbcfg_uart_speed),
+       }
+};
+
+static inline uint16_t
+get_u16(const void *buf)
+{
+       const uint8_t *p = buf;
+
+       return ((uint16_t) p[1] + ((uint16_t) p[0] << 8));
+}
+
+static inline uint32_t
+get_u32(const void *buf)
+{
+       const uint8_t *p = buf;
+
+       return ((uint32_t) p[3] + ((uint32_t) p[2] << 8) +
+              ((uint32_t) p[1] << 16) + ((uint32_t) p[0] << 24));
+}
+
+static inline void
+put_u32(void *buf, uint32_t val)
+{
+       uint8_t *p = buf;
+
+       p[3] = val & 0xff;
+       p[2] = (val >> 8) & 0xff;
+       p[1] = (val >> 16) & 0xff;
+       p[0] = (val >> 24) & 0xff;
+}
+
+static int
+rbcfg_find_tag(struct rbcfg_ctx *ctx, uint16_t tag_id, uint16_t *tag_len,
+              void **tag_data)
+{
+       uint16_t id;
+       uint16_t len;
+       char *buf = ctx->buf;
+       unsigned int buflen = ctx->buflen;
+       int ret = RB_ERR_NOTFOUND;
+
+       /* skip magic and CRC value */
+       buf += 8;
+       buflen -= 8;
+
+       while (buflen > 2) {
+               len = get_u16(buf);
+               buf += 2;
+               buflen -= 2;
+
+               if (buflen < 2)
+                       break;
+
+               id = get_u16(buf);
+               buf += 2;
+               buflen -= 2;
+
+               if (id == RB_ID_TERMINATOR)
+                       break;
+
+               if (buflen < len)
+                       break;
+
+               if (id == tag_id) {
+                       *tag_len = len;
+                       *tag_data = buf;
+                       ret = 0;
+                       break;
+               }
+
+               buf += len;
+               buflen -= len;
+       }
+
+       if (ret)
+               fprintf(stderr, "no tag found with id=%u\n", tag_id);
+
+       return ret;
+}
+
+static int
+rbcfg_get_u32(struct rbcfg_ctx *ctx, uint16_t id, uint32_t *val)
+{
+       void *tag_data;
+       uint16_t tag_len;
+       int err;
+
+       err = rbcfg_find_tag(ctx, id, &tag_len, &tag_data);
+       if (err)
+               return err;
+
+       *val = get_u32(tag_data);
+       return 0;
+}
+
+static int
+rbcfg_set_u32(struct rbcfg_ctx *ctx, uint16_t id, uint32_t val)
+{
+       void *tag_data;
+       uint16_t tag_len;
+       int err;
+
+       err = rbcfg_find_tag(ctx, id, &tag_len, &tag_data);
+       if (err)
+               return err;
+
+       put_u32(tag_data, val);
+       return 0;
+}
+
+char *rbcfg_find_mtd(const char *name, int *erase_size)
+{
+       FILE *f;
+       int mtd_num;
+       char dev[PATH_MAX];
+       char *ret = NULL;
+       struct stat s;
+       int err;
+
+       f = fopen("/proc/mtd", "r");
+       if (!f)
+               return NULL;
+
+       while (1) {
+               char *p;
+               p = fgets(dev, sizeof(dev), f);
+               if (!p)
+                       break;
+
+               if (!strstr(dev, name))
+                       continue;
+
+               err = sscanf(dev, "mtd%d: %08x", &mtd_num, erase_size);
+               if (err != 2)
+                       break;
+
+               sprintf(dev, "/dev/mtdblock%d", mtd_num);
+               err = stat(dev, &s);
+               if (err < 0)
+                       break;
+
+               if ((s.st_mode & S_IFBLK) == 0)
+                       break;
+
+               ret = malloc(strlen(dev) + 1);
+               if (ret == NULL)
+                       break;
+
+               strncpy(ret, dev, strlen(dev) + 1);
+               break;
+       }
+
+       fclose(f);
+       return ret;
+}
+
+static int
+rbcfg_check_tmp(struct rbcfg_ctx *ctx)
+{
+       struct stat s;
+       int err;
+
+       err = stat(ctx->tmp_file, &s);
+       if (err < 0)
+               return 0;
+
+       if ((s.st_mode & S_IFREG) == 0)
+               return 0;
+
+       if (s.st_size != ctx->buflen)
+               return 0;
+
+       return 1;
+}
+
+static int
+rbcfg_load(struct rbcfg_ctx *ctx)
+{
+       uint32_t magic;
+       uint32_t crc_orig, crc;
+       char *name;
+       int tmp;
+       int fd;
+       int err;
+
+       tmp = rbcfg_check_tmp(ctx);
+       name = (tmp) ? ctx->tmp_file : ctx->mtd_device;
+
+       fd = open(name, O_RDONLY);
+       if (fd < 0) {
+               fprintf(stderr, "unable to open %s\n", name);
+               err = RB_ERR_IO;
+               goto err;
+       }
+
+       err = read(fd, ctx->buf, ctx->buflen);
+       if (err != ctx->buflen) {
+               fprintf(stderr, "unable to read from %s\n", name);
+               err = RB_ERR_IO;
+               goto err_close;
+       }
+
+       magic = get_u32(ctx->buf);
+       if (magic != RB_MAGIC_SOFT) {
+               fprintf(stderr, "invalid configuration\n");
+               err = RB_ERR_INVALID;
+               goto err_close;
+       }
+
+       crc_orig = get_u32(ctx->buf + 4);
+       put_u32(ctx->buf + 4, 0);
+       crc = cyg_ether_crc32((unsigned char *) ctx->buf, ctx->buflen);
+       if (crc != crc_orig) {
+               fprintf(stderr, "configuration has CRC error\n");
+               err = RB_ERR_INVALID;
+               goto err_close;
+       }
+
+       err = 0;
+
+ err_close:
+       close(fd);
+ err:
+       return err;
+}
+
+static int
+rbcfg_open()
+{
+       char *mtd_device;
+       struct rbcfg_ctx *ctx;
+       int buflen;
+       int err;
+
+       mtd_device = rbcfg_find_mtd(RBCFG_MTD_NAME, &buflen);
+       if (!mtd_device) {
+               fprintf(stderr, "unable to find configuration\n");
+               return RB_ERR_NOTFOUND;
+       }
+
+       ctx = malloc(sizeof(struct rbcfg_ctx) + buflen);
+       if (ctx == NULL) {
+               err = RB_ERR_NOMEM;
+               goto err_free_mtd;
+       }
+
+       ctx->mtd_device = mtd_device;
+       ctx->tmp_file = RBCFG_TMP_FILE;
+       ctx->buflen = buflen;
+       ctx->buf = (char *) &ctx[1];
+
+       err = rbcfg_load(ctx);
+       if (err)
+               goto err_free_ctx;
+
+       rbcfg_ctx = ctx;
+       return 0;
+
+ err_free_ctx:
+       free(ctx);
+ err_free_mtd:
+       free(mtd_device);
+       return err;
+}
+
+static int
+rbcfg_update(int tmp)
+{
+       struct rbcfg_ctx *ctx = rbcfg_ctx;
+       char *name;
+       uint32_t crc;
+       int fd;
+       int err;
+
+       put_u32(ctx->buf, RB_MAGIC_SOFT);
+       put_u32(ctx->buf + 4, 0);
+       crc = cyg_ether_crc32((unsigned char *) ctx->buf, ctx->buflen);
+       put_u32(ctx->buf + 4, crc);
+
+       name = (tmp) ? ctx->tmp_file : ctx->mtd_device;
+       fd = open(name, O_WRONLY | O_CREAT);
+       if (fd < 0) {
+               fprintf(stderr, "unable to open %s for writing\n", name);
+               err = RB_ERR_IO;
+               goto out;
+       }
+
+       err = write(fd, ctx->buf, ctx->buflen);
+       if (err != ctx->buflen) {
+               err = RB_ERR_IO;
+               goto out_close;
+       }
+
+       fsync(fd);
+       err = 0;
+
+ out_close:
+       close(fd);
+ out:
+       return err;
+}
+
+static void
+rbcfg_close(void)
+{
+       struct rbcfg_ctx *ctx;
+
+       ctx = rbcfg_ctx;
+       free(ctx->mtd_device);
+       free(ctx);
+}
+
+static const struct rbcfg_value *
+rbcfg_env_find(const struct rbcfg_env *env, const char *name)
+{
+       unsigned i;
+
+       for (i = 0; i < env->num_values; i++) {
+               const struct rbcfg_value *v = &env->values[i];
+
+               if (strcmp(v->name, name) == 0)
+                       return v;
+       }
+
+       return NULL;
+}
+
+static const struct rbcfg_value *
+rbcfg_env_find_u32(const struct rbcfg_env *env, uint32_t val)
+{
+       unsigned i;
+
+       for (i = 0; i < env->num_values; i++) {
+               const struct rbcfg_value *v = &env->values[i];
+
+               if (v->val.u32 == val)
+                       return v;
+       }
+
+       return NULL;
+}
+
+static const char *
+rbcfg_env_get_u32(const struct rbcfg_env *env)
+{
+       const struct rbcfg_value *v;
+       uint32_t val;
+       int err;
+
+       err = rbcfg_get_u32(rbcfg_ctx, env->id, &val);
+       if (err)
+               return NULL;
+
+       v = rbcfg_env_find_u32(env, val);
+       if (v == NULL) {
+               fprintf(stderr, "unknown value %08x found for %s\n",
+                       val, env->name);
+               return NULL;
+       }
+
+       return v->name;
+}
+
+static int
+rbcfg_env_set_u32(const struct rbcfg_env *env, const char *data)
+{
+       const struct rbcfg_value *v;
+       int err;
+
+       v = rbcfg_env_find(env, data);
+       if (v == NULL) {
+               fprintf(stderr, "invalid value '%s'\n", data);
+               return RB_ERR_INVALID;
+       }
+
+       err = rbcfg_set_u32(rbcfg_ctx, env->id, v->val.u32);
+       return err;
+}
+
+static const char *
+rbcfg_env_get(const struct rbcfg_env *env)
+{
+       const char *ret = NULL;
+
+       switch (env->type) {
+       case RBCFG_ENV_TYPE_U32:
+               ret = rbcfg_env_get_u32(env);
+               break;
+       }
+
+       return ret;
+}
+
+static int
+rbcfg_env_set(const struct rbcfg_env *env, const char *data)
+{
+       int ret = 0;
+
+       switch (env->type) {
+       case RBCFG_ENV_TYPE_U32:
+               ret = rbcfg_env_set_u32(env, data);
+               break;
+       }
+
+       return ret;
+}
+
+static int
+rbcfg_cmd_apply(int argc, const char *argv[])
+{
+       return rbcfg_update(0);
+}
+
+static int
+rbcfg_cmd_help(int argc, const char *argv[])
+{
+       usage();
+       return 0;
+}
+
+static int
+rbcfg_cmd_get(int argc, const char *argv[])
+{
+       int err = RB_ERR_NOTFOUND;
+       int i;
+
+       if (argc != 1) {
+               usage();
+               return RB_ERR_INVALID;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(rbcfg_envs); i++) {
+               const struct rbcfg_env *env = &rbcfg_envs[i];
+               const char *value;
+
+               if (strcmp(env->name, argv[0]))
+                       continue;
+
+               value = rbcfg_env_get(env);
+               if (value) {
+                       fprintf(stdout, "%s\n", value);
+                       err = 0;
+               }
+               break;
+       }
+
+       return err;
+}
+
+static int
+rbcfg_cmd_set(int argc, const char *argv[])
+{
+       int err = RB_ERR_INVALID;
+       int i;
+
+       if (argc != 2) {
+               /* not enough parameters */
+               usage();
+               return RB_ERR_INVALID;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(rbcfg_envs); i++) {
+               const struct rbcfg_env *env = &rbcfg_envs[i];
+
+               if (strcmp(env->name, argv[0]))
+                       continue;
+
+               err = rbcfg_env_set(env, argv[1]);
+               if (err == 0)
+                       err = rbcfg_update(1);
+               break;
+       }
+
+       return err;
+}
+
+static int
+rbcfg_cmd_show(int argc, const char *argv[])
+{
+       int i;
+
+       if (argc != 0) {
+               usage();
+               return RB_ERR_INVALID;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(rbcfg_envs); i++) {
+               const struct rbcfg_env *env = &rbcfg_envs[i];
+               const char *value;
+
+               value = rbcfg_env_get(env);
+               if (value)
+                       fprintf(stdout, "%s=%s\n", env->name, value);
+       }
+
+       return 0;
+}
+
+static const struct rbcfg_command rbcfg_commands[] = {
+       {
+               .command        = "apply",
+               .usage          = "apply\n"
+                                 "\t- write configuration to the mtd device",
+               .flags          = CMD_FLAG_USES_CFG,
+               .exec           = rbcfg_cmd_apply,
+       }, {
+               .command        = "help",
+               .usage          = "help\n"
+                                 "\t- show this screen",
+               .exec           = rbcfg_cmd_help,
+       }, {
+               .command        = "get",
+               .usage          = "get <name>\n"
+                                 "\t- get value of the configuration option <name>",
+               .flags          = CMD_FLAG_USES_CFG,
+               .exec           = rbcfg_cmd_get,
+       }, {
+               .command        = "set",
+               .usage          = "set <name> <value>\n"
+                                 "\t- set value of the configuration option <name> to <value>",
+               .flags          = CMD_FLAG_USES_CFG,
+               .exec           = rbcfg_cmd_set,
+       }, {
+               .command        = "show",
+               .usage          = "show\n"
+                                 "\t- show value of all configuration options",
+               .flags          = CMD_FLAG_USES_CFG,
+               .exec           = rbcfg_cmd_show,
+       }
+};
+
+static void
+usage(void)
+{
+       char buf[255];
+       int len;
+       int i;
+
+       fprintf(stderr, "Usage: %s <command>\n", rbcfg_name);
+
+       fprintf(stderr, "\nCommands:\n");
+       for (i = 0; i < ARRAY_SIZE(rbcfg_commands); i++) {
+               const struct rbcfg_command *cmd;
+               cmd = &rbcfg_commands[i];
+
+               len = snprintf(buf, sizeof(buf), cmd->usage);
+               buf[len] = '\0';
+               fprintf(stderr, "%s\n", buf);
+       }
+
+       fprintf(stderr, "\nConfiguration options:\n");
+       for (i = 0; i < ARRAY_SIZE(rbcfg_envs); i++) {
+               const struct rbcfg_env *env;
+               int j;
+
+               env = &rbcfg_envs[i];
+               fprintf(stderr, "\n%s:\n", env->name);
+               for (j = 0; j < env->num_values; j++) {
+                       const struct rbcfg_value *v = &env->values[j];
+                       fprintf(stderr, "\t%-12s %s\n", v->name, v->desc);
+               }
+       }
+       fprintf(stderr, "\n");
+}
+
+int main(int argc, const char *argv[])
+{
+       const struct rbcfg_command *cmd = NULL;
+       int ret;
+       int i;
+
+       rbcfg_name = (char *) argv[0];
+
+       if (argc < 2) {
+               usage();
+               return EXIT_FAILURE;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(rbcfg_commands); i++) {
+               if (strcmp(rbcfg_commands[i].command, argv[1]) == 0) {
+                       cmd = &rbcfg_commands[i];
+                       break;
+               }
+       }
+
+       if (cmd == NULL) {
+               fprintf(stderr, "unknown command '%s'\n", argv[1]);
+               usage();
+               return EXIT_FAILURE;
+       }
+
+       argc -= 2;
+       argv += 2;
+
+       if (cmd->flags & CMD_FLAG_USES_CFG) {
+               ret = rbcfg_open();
+               if (ret)
+                       return EXIT_FAILURE;
+       }
+
+       ret = cmd->exec(argc, argv);
+
+       if (cmd->flags & CMD_FLAG_USES_CFG)
+               rbcfg_close();
+
+       if (ret)
+               return EXIT_FAILURE;
+
+       return EXIT_SUCCESS;
+}