diff options
Diffstat (limited to 'avr/env.c')
-rw-r--r-- | avr/env.c | 881 |
1 files changed, 881 insertions, 0 deletions
diff --git a/avr/env.c b/avr/env.c new file mode 100644 index 0000000..f167859 --- /dev/null +++ b/avr/env.c @@ -0,0 +1,881 @@ +/* + * (C) Copyright 2014 Leo C. <erbl259-lmu@yahoo.de> + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include "env.h" +#include <avr/eeprom.h> + +#include "config.h" +#include "debug.h" +#include "crc.h" +#include "getopt-min.h" + +#define DEBUG_ENV 0 /* set to 1 to debug */ + +#define debug_env(fmt, args...) \ + debug_cond(DEBUG_ENV, fmt, ##args) + + +/* + * Default Environment + */ + +#define DELIM "\0" + +const FLASH char default_env[] = { + ENV_BAUDRATE "=" stringify(CONFIG_BAUDRATE) DELIM + ENV_BOOTDELAY "=" stringify(CONFIG_BOOTDELAY) DELIM + ENV_BOOTCMD "=" "pin ${pins};loadcpm3;go ${startaddress}" DELIM + ENV_CPM3_SYSFILE "=" CONFIG_CPM3_SYSFILE DELIM + ENV_PINALIAS "=" "0:PG5,1:PG4,2:PB4,3:PB5,4:PB6,5:PB7," + "6:PG3,7:PG2,8:PG1,9:PG0,10:PE7" DELIM + "pins" "=" "2,8 low 9 high 3 2" DELIM + DELIM +}; + + +#define ENV_SIZE (CONFIG_ENV_SIZE - sizeof(uint16_t) - sizeof(uint8_t)) + +#define ENVLIST_DELETE (1<<0) + + +/* EEPROM storage */ +typedef struct environment_s { + uint16_t crc; /* CRC16 over data bytes */ + uint8_t flags; /* active/obsolete flags */ +#define ACTIVE_FLAG 1 +#define OBSOLETE_FLAG 0 + char data[ENV_SIZE]; /* Environment data */ +} env_t; + + +static uint8_t env_valid; +static char env_list[ENV_SIZE]; + +static +char env_get_char(uint_fast16_t index) +{ + unsigned int off = CONFIG_ENV_OFFSET; + char ret; + + switch (env_valid) { + case 2: + off += CONFIG_ENV_SIZE; + /* FALL TROUGH */ + case 1: + ret = (char) eeprom_read_byte((const uint8_t *)off + index + + offsetof(env_t, data)); + break; + + default: + ret = default_env[index]; + } + + return ret; +} + +static void envlist_clear(void) +{ + memset(env_list, 0, sizeof env_list); +} + +static +int env_item_delete(char *ep) +{ + char *next = ep + strlen(ep) + 1; + char *end = next; + while (*end++ != 0) + while (*end++ != 0); + + memmove(ep, next, end - next); + + return 0; +} + +static int envcmp(const char *s1, const char *s2) +{ + const unsigned char *u1 = (const unsigned char *)s1; + const unsigned char *u2 = (const unsigned char *)s2; + unsigned char c1, c2; + + do { + c1 = *u1++; c2 = *u2++; + if (c1 == '=') c1 = '\0'; + if (c2 == '=') c2 = '\0'; + } while (c1 != 0 && c1 - c2 == 0); + + return c1 - c2; +} + +static char *envlist_search(const MEMX char *name) +{ + char key[CONFIG_SYS_ENV_NAMELEN + 1]; + const MEMX char *np = name; + int keylen = 0; + char c; + + while (keylen <= CONFIG_SYS_ENV_NAMELEN && (c = *np++) != '\0') + key[keylen++] = c; + key[keylen] = '\0'; + + for (char *lp = env_list; *lp != 0; ++lp) { + + if (envcmp(lp, key) == 0) + return lp; + + while (*lp != 0) + ++lp; + } + + return NULL; +} + +static char *env_item_insert_name(char *pos, const char *name, int len) +{ + char *dstp = pos + len + 1; + char *end = pos; + while (*end++ != 0) + while (*end++ != 0); + + if (end + len >= env_list + ENV_SIZE) + return NULL; + + memmove(dstp, pos, end - pos); + strcpy(pos, name); + memset(pos + strlen(name), '=', dstp - pos - strlen(name)); + *(dstp - 1) = '\0'; + + return pos; +} + + +static char *envlist_alloc(const char *name, int len) +{ + char *lp; + int rc; + + + for (lp = env_list; *lp != 0; ++lp) { + + rc = envcmp(lp, name); + if (rc >= 0) + break; + + /* rc < 0 : check next place */ + while (*lp != 0) + ++lp; + } + + /* rc == 0 : entry found, replace */ + if (rc == 0) + env_item_delete(lp); + + /* rc > 0 : entry not in list, insert */ + /* *lp == 0 : entry not in list, insert */ + + lp = env_item_insert_name(lp, name, len); + + return lp; +} + +#if 0 +static char *env_item_insert(char *pos, const char *envstr) +{ + char *dstp = pos + strlen(envstr) + 1; + char *end = pos; + while (*end++ != 0) + while (*end++ != 0); + + if (end > env_list + ENV_SIZE) + return NULL; + + memmove(dstp, pos, end - pos); + strcpy(pos, envstr); + + return pos; +} + +static char *envlist_enter(const char *ep) +{ + char *lp; + int rc; + + for (lp = env_list; *lp != 0; ++lp) { + + rc = envcmp(lp, ep); + if (rc >= 0) + break; + + /* rc < 0 : check next place */ + while (*lp != 0) + ++lp; + } + + /* rc == 0 : entry found, replace */ + if (rc == 0) + env_item_delete(lp); + + /* rc > 0 : entry not in list, insert */ + /* *lp == 0 : entry not in list, insert */ + + lp = env_item_insert(lp, ep); + + return lp; +} +#endif + +static +int envlist_delete(const MEMX char *name) +{ + char *ep = envlist_search(name); + + if (ep != NULL) + return env_item_delete(ep); + + return 1; +} + +static +int envlist_import(uint8_t flags) +{ + if ((flags & ENVLIST_DELETE) != 0) + envlist_clear(); + + for (uint_fast16_t index = 0; env_get_char(index) != '\0'; ) { + char name[CONFIG_SYS_ENV_NAMELEN+1]; + uint_fast8_t nlen = 0; + size_t len; + char ch; + for (len = 0; (ch = env_get_char(index + len)) != '\0'; ++len) { + if ((index + len) >= ENV_SIZE) + return -1; + if (nlen == 0) { + if (ch == '=') { + nlen = len; + name[nlen] = '\0'; + } else { + if (len > CONFIG_SYS_ENV_NAMELEN) + return -1; + name[len] = ch; + } + } + } + + char *ep = envlist_alloc(name, len); + if (ep == NULL) { + printf_P(PSTR("## Error inserting \"%s\" variable.\n"), name); + return 1; + } + + index += nlen; + char *p = ep + nlen; + while ((*p++ = env_get_char(index++)) != '\0') + ; + } + return 0; +} + + +static +uint16_t env_crc(uint16_t data_offset) +{ + uint16_t crc; + uint16_t i; + char c, c0; + + crc = 0xffff; + c = 0xff; + for (i = 0; !(c == 0 && c0 == 0) && i < ENV_SIZE; i++) + { + c0 = c; + c = eeprom_read_byte((const uint8_t *) data_offset + i); + crc = crc16(crc, c); + } + return crc ; +} + + +/** + * return valid env + */ +static +int env_check_valid(void) +{ + const uint16_t offset[2] = {CONFIG_ENV_OFFSET, + CONFIG_ENV_OFFSET + CONFIG_ENV_SIZE}; + uint_fast8_t flags[2], crc_ok[2]; + int rc; + + /* read FLAGS */ + flags[0] = eeprom_read_byte ((uint8_t *) offset[0] + + offsetof(env_t, flags)); + flags[1] = eeprom_read_byte ((uint8_t *) offset[1] + + offsetof(env_t, flags)); + + /* check CRC */ + crc_ok[0] = ( + eeprom_read_word((uint16_t *) offset[0] + + offsetof(env_t, crc)) + == env_crc(offset[0] + offsetof(env_t, data)) + ); + crc_ok[1] = ( + eeprom_read_word((uint16_t *) offset[1] + + offsetof(env_t, crc)) + == env_crc(offset[1] + offsetof(env_t, data)) + ); + + if (!crc_ok[0] && !crc_ok[1]) { + rc = 0; + + } else if (crc_ok[0] && !crc_ok[1]) { + rc = 1; + } else if (!crc_ok[0] && crc_ok[1]) { + rc = 2; + } else { + /* both ok - check serial */ +#if 1 + if (flags[1] == ACTIVE_FLAG && flags[0] != ACTIVE_FLAG) + rc = 2; + else if (flags[1] == OBSOLETE_FLAG && flags[0] == 0xFF) + rc = 2; + else + rc = 1; +#else + if (flags[0] == ACTIVE_FLAG && flags[1] == OBSOLETE_FLAG) + rc = 1; + else if (flags[0] == OBSOLETE_FLAG && flags[1] == ACTIVE_FLAG) + rc = 2; + else if (flags[0] == 0xFF && flags[1] == 0) + rc = 2; + else if (flags[1] == 0xFF && flags[0] == 0) + rc = 1; + else /* flags are equal - almost impossible */ + rc = 1; +#endif + } + + return rc; +} + + +int env_init(void) +{ + env_valid = env_check_valid(); + if (env_valid == 0) { + printf_P(PSTR("*** Warning - bad CRC, " + "using default environment\n\n")); + } + + envlist_import(ENVLIST_DELETE); + return 0; +} + + +char *getenv_str(const MEMX char *name) +{ + char *ep = envlist_search(name); + if (ep != NULL) + return strchr(ep, '=') + 1; + else + return NULL; +} + +static +int env_item_save(char *ep, uint16_t offset, int space_left) +{ + int len; + + len = strlen(ep); + if (len == 0) + return 0; + len += 1; + if (len > space_left) + return 0; + + eeprom_update_block(ep, (uint8_t *) offset, len); + + return len; +} + + +static +int saveenv(void) +{ + unsigned int off = CONFIG_ENV_OFFSET + CONFIG_ENV_SIZE; + unsigned int off_red = CONFIG_ENV_OFFSET; + unsigned int pos; + int len, left; + uint16_t crc; + int rc = 0; + + if (env_valid == 2) { + off = CONFIG_ENV_OFFSET; + off_red = CONFIG_ENV_OFFSET + CONFIG_ENV_SIZE; + } + + eeprom_update_byte((uint8_t *) off + offsetof(env_t, flags), 0xff); + + pos = off + offsetof(env_t, data); + left = ENV_SIZE - 1; + + char *ep = env_list; + while (*ep) { + len = env_item_save(ep, pos, left); + if (len == 0) { + return 1; + } + pos += len; + left -= len; + while (*ep++); + } + + /* terminate list */ + eeprom_update_byte((uint8_t *) pos, 0); + crc = env_crc(off + offsetof(env_t, data)); + eeprom_update_word((uint16_t *) off + offsetof(env_t, crc), crc); + eeprom_update_byte((uint8_t *) off + offsetof(env_t, flags), + ACTIVE_FLAG); + + if (rc == 0) { + eeprom_update_byte((uint8_t *) off_red + offsetof(env_t, flags), + OBSOLETE_FLAG); + env_valid = (env_valid == 2) ? 1 : 2; + } + + return rc; +} + + +static +int env_item_print(char *ep, bool mode) +{ + int len = strlen(ep) + 1; + + if (mode) { + my_puts_P(PSTR("setenv ")); + char ch; + while ((ch = *ep++) != '=') + putchar(ch); + printf_P(PSTR(" '%s'\n"), ep); + } else { + puts(ep); + } + + return len; +} + + +/* + * Command interface: print one or all environment variables + * + * Returns -1 in case of error, or length of printed string + */ +static +int env_print(const char *name, bool mode) +{ + int len = -1; + char *ep; + + if (name != NULL) { /* print a single name */ + + ep = envlist_search(name); + if (ep != NULL) + len = env_item_print(ep, mode); + + } else { /* print whole list */ + len = 0; + ep = env_list; + while (*ep) { + len += env_item_print(ep, mode); + while (*ep++); + } + } + return len; +} + + +/** + * Set or delete environment variable + * + * Set a new environment variable, + * or replace or delete an existing one. + * + * @param flag (not used) + * @param argc + * @param argv[1] Environment variable to set or delete + * if no more args + * @param argv[2] ... Value to set it to + * + * @return 0 if ok, 1 on error + */ +static +command_ret_t _do_env_set(uint_fast8_t flag UNUSED, int argc, char * const argv[]) +{ + char *name = argv[1]; + + if (strchr(name, '=')) { + printf_P(PSTR("## Error: illegal character '='" + "in variable name \"%s\"\n"), name); + return CMD_RET_FAILURE; + } + size_t len = strlen(name); + if (len > CONFIG_SYS_ENV_NAMELEN) { + printf_P(PSTR("## Error: Variable name \"%s\" too long. " + "(max %d characters)\n"), name, CONFIG_SYS_ENV_NAMELEN); + return CMD_RET_FAILURE; + } + + /* Delete only ? */ + if (argc < 3) { + int rc = envlist_delete(name); + return rc ? CMD_RET_FAILURE : CMD_RET_SUCCESS; + } + + /* Insert / replace new value */ + + for (int_fast8_t i = 2; i < argc; ++i) + len += strlen(argv[i]) + 1; + + char *pos = envlist_alloc(name, len); + if (pos == NULL) { + printf_P(PSTR("## Error inserting \"%s\" variable.\n"), name); + return CMD_RET_FAILURE; + } + + char *vp = pos + strlen(name) + 1; + for (int_fast8_t i = 2; i < argc; ++i) { + char *ap = argv[i]; + while ((*vp++ = *ap++) != '\0'); + //if (i < argc-1) + *(vp-1) = ' '; + } + *(vp-1) = '\0'; + + return CMD_RET_SUCCESS; +} + +/** + * Set an environment variable + * + * @param varname Environment variable to set + * @param varvalue Value to set it to + * @return 0 if ok, 1 on error + */ + +int setenv(const MEMX char *varname, const char *varvalue) +{ + int rc; + +#ifdef __MEMX + char *tmpname; + if (__builtin_avr_flash_segment(varname) != -1) { + tmpname = malloc(strlen_P(varname)+1); + if (tmpname == NULL) { + printf_P(PSTR("setenv: Out of Memory!\n")); + return 1; + } + strcpy_P(tmpname, varname); + } else + tmpname = (char *) varname; + + const char * const argv[3] = { NULL, tmpname, varvalue }; +#else + const char * const argv[3] = { NULL, varname, varvalue }; +#endif + + int argc = 3; + + if (varvalue == NULL || varvalue[0] == '\0') + --argc; + + rc = (int) _do_env_set(0, argc, (char * const *)argv); + +#ifdef __MEMX + if (__builtin_avr_flash_segment(varname) != -1) + free(tmpname); +#endif + return rc; +} + +/** + * Set an environment variable to an integer value + * + * @param name Environment variable to set + * @param value Value to set it to + * @param radix Base + * @return 0 if ok, 1 on error + */ +static +int setenv_intval(const MEMX char *name, unsigned long value, int radix) +{ + char buf[11]; + + ultoa(value, buf, radix); + + return setenv(name, buf); +} + +/** + * Set an environment variable to a decimal integer value + * + * @param name Environment variable to set + * @param value Value to set it to + * @return 0 if ok, 1 on error + */ +int setenv_ulong(const MEMX char *name, unsigned long value) +{ + return setenv_intval(name, value, 10); +} + + +/** + * Set an environment variable to a value in hex + * + * @param name Environment variable to set + * @param value Value to set it to + * @return 0 if ok, 1 on error + */ +int setenv_hex(const MEMX char *name, unsigned long value) +{ + return setenv_intval(name, value, 16); +} + + +/** + * Decode the integer value of an environment variable and return it. + * + * @param name Name of environemnt variable + * @param base Number base to use (normally 10, or 16 for hex) + * @param default_val Default value to return if the variable is not + * found + * @return the decoded value, or default_val if not found + */ +unsigned long getenv_ulong(const MEMX char *name, int base, unsigned long default_val) +{ + unsigned long value; + char *vp, *endp; + + vp = getenv_str(name); + + if (vp != NULL ) { + value = strtoul(vp, &endp, base); + if (endp != vp) + return value; + } + + return default_val; +} + + +/* + * Read an environment variable as a boolean + */ +bool getenv_yesno(const MEMX char *name) +{ + char *s = getenv_str(name); + + if (s == NULL) + return false; + + return strchr_P(PSTR("1yYtT"), *s) != NULL; + +/* + return *s == '1' || *s == 'y' || *s == 'Y' || *s == 't' || *s == 'T' ? + 1 : 0; +*/ +} + +command_ret_t do_env_print(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, + int argc, char * const argv[]) +{ + bool mode = 0; + command_ret_t rc = CMD_RET_SUCCESS; + + int opt; + while ((opt = getopt(argc, argv, PSTR("s"))) != -1) { + switch (opt) { + case 's': + mode = 1; + break; + default: /* '?' */ + return CMD_RET_USAGE; + } + } + + if (optind == argc) { + /* print all env vars */ + int size = env_print(NULL, mode); + if (size < 0) + return CMD_RET_FAILURE; + if (mode == 0) + printf_P(PSTR("\nEnvironment size: %d/%d bytes\n"), + size, ENV_SIZE - 1); + return CMD_RET_SUCCESS; + } + + /* print selected env vars */ + while (optind < argc) { + int len = env_print(argv[optind], mode); + if (len < 0) { + rc = CMD_RET_FAILURE; + } + optind++; + } + + return rc; +} + + +command_ret_t do_env_set(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag, int argc, char * const argv[]) +{ + if (argc < 2) + return CMD_RET_USAGE; + + return _do_env_set(flag, argc, argv); +} + + +command_ret_t do_env_default(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, + int argc UNUSED, char * const argv[] UNUSED) +{ + uint8_t tmp = env_valid; + env_valid = 0; + + /* Reset the whole environment */ + printf_P(PSTR("## Resetting to default environment\n")); + envlist_import(ENVLIST_DELETE); + + env_valid = tmp; + + return CMD_RET_SUCCESS; +} + + +command_ret_t do_env_save(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc UNUSED, char * const argv[] UNUSED) +{ + printf_P(PSTR("Saving Environment ...\n")); + return saveenv() ? CMD_RET_FAILURE : CMD_RET_SUCCESS; +} + + +#if defined(CONFIG_AUTO_COMPLETE) +int env_complete(char *var, int maxv, char *cmdv[], int bufsz, char *buf) +{ + ENTRY *match; + int found, idx; + + idx = 0; + found = 0; + cmdv[0] = NULL; + + while ((idx = hmatch_r(var, idx, &match, &env_htab))) { + int vallen = strlen(match->key) + 1; + + if (found >= maxv - 2 || bufsz < vallen) + break; + + cmdv[found++] = buf; + memcpy(buf, match->key, vallen); + buf += vallen; + bufsz -= vallen; + } + + qsort(cmdv, found, sizeof(cmdv[0]), strcmp_compar); + + if (idx) + cmdv[found++] = "..."; + + cmdv[found] = NULL; + return found; +} +#endif + +cmd_tbl_t cmd_tbl_env[] = { +CMD_TBL_ITEM_COMPLETE( + printenv, CONFIG_SYS_MAXARGS, 1|CTBL_SUBCMDAUTO, do_env_print, + "print environment variables", + "[-s] [name ...]\n" + " Print value of environment variable(s) 'name'\n" + " If no names are given, print values of all environment variables\n" + " -s Print in setenv form", + var_complete +), +CMD_TBL_ITEM_COMPLETE( + setenv, CONFIG_SYS_MAXARGS, CTBL_SUBCMDAUTO, do_env_set, + "set environment variables", + "name value ...\n" + " - set environment variable 'name' to 'value ...'\n" + "setenv name\n" + " - delete environment variable 'name'", + var_complete +), +CMD_TBL_ITEM( + saveenv, 1, CTBL_SUBCMDAUTO, do_env_save, + "save environment variables to persistent storage", + "" +), +CMD_TBL_ITEM( + defaultenv, 1, CTBL_SUBCMDAUTO, do_env_default, + "set all environment variables to their default values", + "" +), + +CMD_TBL_ITEM_COMPLETE( + print, CONFIG_SYS_MAXARGS, 1, do_env_print, + "print environment variables", + "[-s] [name ...]\n" + " Print value of environment variable(s) 'name'\n" + " If no names are given, print values of all environment variables\n" + " -s Print in setenv form", + var_complete +), +CMD_TBL_ITEM_COMPLETE( + set, CONFIG_SYS_MAXARGS, 0, do_env_set, + "set environment variables", + "name value ...\n" + " - set environment variable 'name' to 'value ...'\n" + "setenv name\n" + " - delete environment variable 'name'", + var_complete +), +CMD_TBL_ITEM( + save, 1, 0, do_env_save, + "save environment variables to persistent storage", + "" +), +CMD_TBL_ITEM( + default, 1, 0, do_env_default, + "set all environment variables to their default values", + "" +), + +CMD_TBL_ITEM( + help, CONFIG_SYS_MAXARGS, CTBL_RPT, do_help, + "Print sub command description/usage", + "\n" + " - print brief description of all sub commands\n" + "env help command ...\n" + " - print detailed usage of sub cmd 'command'" +), + +/* This does not use the CMD_TBL_ITEM macro as ? can't be used in symbol names */ + {FSTR("?"), CONFIG_SYS_MAXARGS, 1, do_help, + NULL, +#ifdef CONFIG_SYS_LONGHELP + FSTR(""), +#endif /* CONFIG_SYS_LONGHELP */ + NULL, +#ifdef CONFIG_AUTO_COMPLETE + NULL, +#endif +}, +/* Mark end of table */ +CMD_TBL_END(cmd_tbl_env) +}; + +command_ret_t do_env(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc UNUSED, char * const argv[] UNUSED) +{ + return CMD_RET_USAGE; +} |