diff options
Diffstat (limited to 'package/boot/rbcfg/src/main.c')
-rw-r--r-- | package/boot/rbcfg/src/main.c | 791 |
1 files changed, 791 insertions, 0 deletions
diff --git a/package/boot/rbcfg/src/main.c b/package/boot/rbcfg/src/main.c new file mode 100644 index 0000000000..5614a6c223 --- /dev/null +++ b/package/boot/rbcfg/src/main.c @@ -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; +} |