diff --git a/epicardium/main.c b/epicardium/main.c index 56639ea56ead01fc06138e0f2f712518193c475f..417f8929e7648a03343ba74692d3809664606030 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -1,6 +1,7 @@ #include "modules/modules.h" #include "modules/log.h" #include "modules/filesystem.h" +#include "modules/config.h" #include "card10-version.h" #include "FreeRTOS.h" diff --git a/epicardium/modules/config.c b/epicardium/modules/config.c new file mode 100644 index 0000000000000000000000000000000000000000..13ce3abaf12947237bf9963ad78af6e437e2c525 --- /dev/null +++ b/epicardium/modules/config.c @@ -0,0 +1,315 @@ +#include "modules/log.h" +#include "modules/config.h" +#include "modules/filesystem.h" + +#include <assert.h> +#include <stdbool.h> +#include <ctype.h> +#include <string.h> +#include <stdlib.h> + +#define CONFIG_MAX_LINE_LENGTH 80 + +enum OptionType { + OptionType_Boolean, + OptionType_Int, + OptionType_Float, + OptionType_String, +}; + +struct config_option { + const char *name; + enum OptionType type; + union { + bool boolean; + long integer; + double floating_point; + char *string; + } value; +}; + +static struct config_option s_options[_EpicOptionCount] = { + [OptionExecuteElf] = { .name = "execute_elf", + .type = OptionType_Boolean, + .value = { .boolean = false } }, +}; + +static struct config_option *findOption(const char *key) +{ + for (int i = 0; i < _EpicOptionCount; ++i) { + if (!strcmp(key, s_options[i].name)) { + return &s_options[i]; + } + } + return NULL; +} + +static bool set_bool(struct config_option *opt, const char *value) +{ + bool val; + if (!strcmp(value, "1")) { + val = true; + } else if (!strcmp(value, "true")) { + val = true; + } else if (!strcmp(value, "0")) { + val = false; + } else if (!strcmp(value, "false")) { + val = false; + } else { + return false; + } + opt->value.boolean = val; + LOG_DEBUG( + "card10.cfg", + "setting '%s' to %s", + opt->name, + val ? "true" : "false" + ); + return true; +} + +static bool set_int(struct config_option *opt, const char *value) +{ + char *endptr; + size_t len = strlen(value); + int base = 10; + if (len > 2 && value[0] == '0' && value[1] == 'x') { + base = 16; +#ifdef CONFIG_ENABLE_OCTAL_NUMBERS + } else if (len > 1 && value[0] == '0') { + base = 8; +#endif + } + int v = strtol(value, &endptr, base); + if (endptr != (value + len)) { + return false; + } + opt->value.integer = v; + LOG_DEBUG("card10.cfg", "setting '%s' to %d (0x%08x)", opt->name, v, v); + return true; +} + +static bool set_float(struct config_option *opt, const char *value) +{ + char *endptr; + size_t len = strlen(value); + double v = strtod(value, &endptr); + if (endptr != (value + len)) { + return false; + } + opt->value.floating_point = v; + LOG_DEBUG("card10.cfg", "setting '%s' to %f", opt->name, v); + return true; +} + +const char *elide(const char *str) +{ + static char ret[21]; + size_t len = strlen(str); + if (len <= 20) { + return str; + } + strncpy(ret, str, 17); + ret[17] = '.'; + ret[18] = '.'; + ret[19] = '.'; + ret[20] = '\0'; + return ret; +} + +static bool set_string(struct config_option *opt, const char *value) +{ + //this leaks, but the lifetime of these ends when epicardium exits, so... + size_t len = strlen(value); + char *leaks = (char *)malloc(len); + strncpy(leaks, value, len); + opt->value.string = leaks; + LOG_DEBUG("card10.cfg", "setting '%s' to %s", opt->name, elide(leaks)); + return true; +} + +static void configure(const char *key, const char *value, int lineNumber) +{ + struct config_option *opt = findOption(key); + if (!opt) { + //invalid key + LOG_WARN( + "card10.cfg", + "line %d: ignoring unknown option '%s'", + lineNumber, + key + ); + return; + } + bool ok = false; + switch (opt->type) { + case OptionType_Boolean: + ok = set_bool(opt, value); + break; + case OptionType_Int: + ok = set_int(opt, value); + break; + case OptionType_Float: + ok = set_float(opt, value); + break; + case OptionType_String: + ok = set_string(opt, value); + break; + default: + assert(0 && "unreachable"); + } + if (!ok) { + LOG_WARN( + "card10.cfg", + "line %d: ignoring invalid value '%s' for option '%s'", + lineNumber, + value, + key + ); + } +} + +static void doline(char *line, char *eol, int lineNumber) +{ + //skip leading whitespace + while (*line && isspace(*line)) + ++line; + + char *key = line; + if (*key == '#') { + //skip comments + return; + } + + char *eq = strchr(line, '='); + if (!eq) { + if (*key) { + LOG_WARN( + "card10.cfg", + "line %d (%s): syntax error", + lineNumber, + elide(line) + ); + } + return; + } + + char *e_key = eq - 1; + //skip trailing whitespace in key + while (e_key > key && isspace(*e_key)) + --e_key; + e_key[1] = '\0'; + if (*key == '\0') { + LOG_WARN("card10.cfg", "line %d: empty key", lineNumber); + return; + } + + char *value = eq + 1; + //skip leading whitespace + while (*value && isspace(*value)) + ++value; + + char *e_val = eol - 1; + //skip trailing whitespace + while (e_val > value && isspace(*e_val)) + --e_val; + if (*value == '\0') { + LOG_WARN( + "card10.cfg", + "line %d: empty value for option '%s'", + lineNumber, + key + ); + return; + } + + configure(key, value, lineNumber); +} + +bool config_get_boolean(enum EpicConfigOption option) +{ + struct config_option *opt = &s_options[option]; + assert(opt->type == OptionType_Boolean); + return opt->value.boolean; +} + +long config_get_integer(enum EpicConfigOption option) +{ + struct config_option *opt = &s_options[option]; + assert(opt->type == OptionType_Int); + return opt->value.integer; +} + +double config_get_float(enum EpicConfigOption option) +{ + struct config_option *opt = &s_options[option]; + assert(opt->type == OptionType_Float); + return opt->value.floating_point; +} + +const char *config_get_string(enum EpicConfigOption option) +{ + struct config_option *opt = &s_options[option]; + assert(opt->type == OptionType_String); + return opt->value.string; +} + +void load_config(void) +{ + LOG_DEBUG("card10.cfg", "loading..."); + int fd = epic_file_open("card10.cfg", "r"); + if (fd < 0) { + LOG_DEBUG( + "card10.cfg", + "loading failed: %s (%d)", + strerror(-fd), + fd + ); + return; + } + char buf[CONFIG_MAX_LINE_LENGTH]; + int nread = epic_file_read(fd, buf, sizeof(buf)); + if (nread < sizeof(buf)) { + //add fake EOL + buf[nread] = '\n'; + } + int lineNumber = 0; + while (nread) { + char *line = buf; + char *eol = NULL; + while (line) { + eol = strchr(line, '\n'); + if (!eol) { + break; + } + ++lineNumber; + *eol = '\0'; + doline(line, eol, lineNumber); + line = eol + 1; + if (line - buf >= sizeof(buf)) { + //eol was right at the end of buf, + //break this loop and prevent memmove: + line = NULL; + } + } + if (line) { + int head = line - buf; + if (head == 0) { + //line did not fit into buf + LOG_WARN( + "card10.cfg", + "line:%d: too long", + lineNumber + ); + break; + } + int tail = (int)sizeof(buf) - head; + memmove(buf, line, tail); + nread = epic_file_read(fd, buf + tail, head); + if (nread < head) { + //add fake-eol in case of partial read + buf[tail + nread] = '\n'; + } + } + } +} diff --git a/epicardium/modules/config.h b/epicardium/modules/config.h new file mode 100644 index 0000000000000000000000000000000000000000..cfde8b163bfc761ebd9aac9111e7be2b6de7a5ea --- /dev/null +++ b/epicardium/modules/config.h @@ -0,0 +1,20 @@ +#ifndef EPICARDIUM_MODULES_CONFIG_H_INCLUDED +#define EPICARDIUM_MODULES_CONFIG_H_INCLUDED + +#include <stdbool.h> + +enum EpicConfigOption { + OptionExecuteElf, + _EpicOptionCount +}; + +//initialize configuration values and load card10.cfg +void load_config(void); + +bool config_get_boolean(enum EpicConfigOption option); +long config_get_integer(enum EpicConfigOption option); +double config_get_float(enum EpicConfigOption option); +const char* config_get_string(enum EpicConfigOption option); + + +#endif//EPICARDIUM_MODULES_CONFIG_H_INCLUDED diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build index 693ca9a8f94a432ff64d770f91d9e9005c88d42a..628249a305098911b6dfe3c861b8759d6d5a5a62 100644 --- a/epicardium/modules/meson.build +++ b/epicardium/modules/meson.build @@ -21,5 +21,6 @@ module_sources = files( 'trng.c', 'vibra.c', 'watchdog.c', - 'usb.c' + 'usb.c', + 'config.c', )