Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • add_menu_vibration
  • blinkisync-as-preload
  • ch3/api-speed-eval2
  • ch3/dual-core
  • ch3/genapi-refactor
  • ch3/leds-api
  • ch3/splashscreen
  • dualcore
  • dx/flatten-config-module
  • dx/meh-bdf-to-stm
  • freertos-btle
  • genofire/ble-follow-py
  • koalo/bhi160-works-but-dirty
  • koalo/factory-reset
  • koalo/wip/i2c-for-python
  • master
  • msgctl/faultscreen
  • msgctl/textbuffer_api
  • plaetzchen/ios-workaround
  • rahix/bhi
  • rahix/bluetooth-app-favorite
  • rahix/bma
  • rahix/user-space-ctx
  • renze/hatchery_apps
  • renze/safe_mode
  • schleicher-test
  • schneider/212-reset-hardware-when-entering-repl
  • schneider/ancs
  • schneider/ble-buffers
  • schneider/ble-central
  • schneider/ble-ecg-stream-visu
  • schneider/ble-fixes-2020-3
  • schneider/ble-mini-demo
  • schneider/ble-stability
  • schneider/ble-stability-new-phy
  • schneider/bonding
  • schneider/bonding-fail-if-full
  • schneider/bootloader-update-9a0d158
  • schneider/deepsleep
  • schneider/deepsleep2
  • schneider/deepsleep4
  • schneider/default-main
  • schneider/freertos-list-debug
  • schneider/fundamental-test
  • schneider/iaq-python
  • schneider/ir
  • schneider/max30001
  • schneider/max30001-epicaridum
  • schneider/max30001-pycardium
  • schneider/maxim-sdk-update
  • schneider/mp-exception-print
  • schneider/mp-for-old-bl
  • schneider/png
  • schneider/schleicher-test
  • schneider/sdk-0.2.1-11
  • schneider/sdk-0.2.1-7
  • schneider/sleep-display
  • schneider/spo2-playground
  • schneider/stream-locks
  • schneider/v1.17-changelog
  • bootloader-v1
  • release-1
  • v0.0
  • v1.0
  • v1.1
  • v1.10
  • v1.11
  • v1.12
  • v1.13
  • v1.14
  • v1.15
  • v1.16
  • v1.17
  • v1.18
  • v1.2
  • v1.3
  • v1.4
  • v1.5
  • v1.6
  • v1.7
  • v1.8
  • v1.9
82 results

Target

Select target project
  • card10/firmware
  • annejan/firmware
  • astro/firmware
  • fpletz/firmware
  • gerd/firmware
  • fleur/firmware
  • swym/firmware
  • l/firmware
  • uberardy/firmware
  • wink/firmware
  • madonius/firmware
  • mot/firmware
  • filid/firmware
  • q3k/firmware
  • hauke/firmware
  • Woazboat/firmware
  • pink/firmware
  • mossmann/firmware
  • omniskop/firmware
  • zenox/firmware
  • trilader/firmware
  • Danukeru/firmware
  • shoragan/firmware
  • zlatko/firmware
  • sistason/firmware
  • datenwolf/firmware
  • bene/firmware
  • amedee/firmware
  • martinling/firmware
  • griffon/firmware
  • chris007/firmware
  • adisbladis/firmware
  • dbrgn/firmware
  • jelly/firmware
  • rnestler/firmware
  • mh/firmware
  • ln/firmware
  • penguineer/firmware
  • monkeydom/firmware
  • jens/firmware
  • jnaulty/firmware
  • jeffmakes/firmware
  • marekventur/firmware
  • pete/firmware
  • h2obrain/firmware
  • DooMMasteR/firmware
  • jackie/firmware
  • prof_r/firmware
  • Draradech/firmware
  • Kartoffel/firmware
  • hinerk/firmware
  • abbradar/firmware
  • JustTB/firmware
  • LuKaRo/firmware
  • iggy/firmware
  • ente/firmware
  • flgr/firmware
  • Lorphos/firmware
  • matejo/firmware
  • ceddral7/firmware
  • danb/firmware
  • joshi/firmware
  • melle/firmware
  • fitch/firmware
  • deurknop/firmware
  • sargon/firmware
  • markus/firmware
  • kloenk/firmware
  • lucaswerkmeister/firmware
  • derf/firmware
  • meh/firmware
  • dx/card10-firmware
  • torben/firmware
  • yuvadm/firmware
  • AndyBS/firmware
  • klausdieter1/firmware
  • katzenparadoxon/firmware
  • xiretza/firmware
  • ole/firmware
  • techy/firmware
  • thor77/firmware
  • TilCreator/firmware
  • fuchsi/firmware
  • dos/firmware
  • yrlf/firmware
  • PetePriority/firmware
  • SuperVirus/firmware
  • sur5r/firmware
  • tazz/firmware
  • Alienmaster/firmware
  • flo_h/firmware
  • baldo/firmware
  • mmu_man/firmware
  • Foaly/firmware
  • sodoku/firmware
  • Guinness/firmware
  • ssp/firmware
  • led02/firmware
  • Stormwind/firmware
  • arist/firmware
  • coon/firmware
  • mdik/firmware
  • pippin/firmware
  • royrobotiks/firmware
  • zigot83/firmware
  • mo_k/firmware
106 results
Select Git revision
  • blinkisync-as-preload
  • ch3/api-speed-eval2
  • ch3/dual-core
  • ch3/genapi-refactor
  • ch3/leds-api
  • ch3/splashscreen
  • drawcall_clipping
  • dualcore
  • faulty_unsigned_comparisons
  • freertos-btle
  • fuchsi-ecg-app
  • genofire/ble-follow-py
  • genofire/haule-ble-fs-deactive
  • genofire/leds_rgb_get_state
  • genofire/rockets-state
  • gpio_in_adc_fix
  • ios-workarounds
  • koalo/bhi160-works-but-dirty
  • koalo/factory-reset
  • koalo/wip/i2c-for-python
  • master
  • msgctl/faultscreen
  • msgctl/gfx_rle
  • msgctl/textbuffer_api
  • plaetzchen/ios-workaround
  • rahix/bhi
  • rahix/bma
  • renze/hatchery_apps
  • renze/safe_mode
  • schleicher-test
  • schneider/ble-buffers
  • schneider/bonding
  • schneider/bootloader-update-9a0d158
  • schneider/bsec
  • schneider/fundamental-test
  • schneider/max30001
  • schneider/max30001-epicaridum
  • schneider/max30001-pycardium
  • schneider/maxim-sdk-update
  • schneider/mp-for-old-bl
  • schneider/schleicher-test
  • schneider/stream-locks
  • bootloader-v1
  • release-1
  • v0.0
  • v1.0
  • v1.1
  • v1.2
  • v1.3
  • v1.4
  • v1.5
  • v1.6
  • v1.7
  • v1.8
  • v1.9
55 results
Show changes
Showing
with 2309 additions and 13 deletions
#include "os/core.h"
#include "os/config.h"
#include "epicardium.h"
#include <assert.h>
#include <stdbool.h>
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <stddef.h>
#include <stdio.h>
#define MAX_LINE_LENGTH 80
#define KEYS_PER_BLOCK 16
#define KEY_LENGTH 16
#define NOT_INT_MAGIC ((int)0x80000000)
// one key-value pair representing a line in the config
typedef struct {
char key[KEY_LENGTH];
// the value in the config file, if it's an integer.
// for strings it's set to NOT_INT_MAGIC
int value;
// the byte offset in the config file to read the value string
size_t value_offset;
} config_slot;
// a block of 16 config slots
// if more are needed, this becomes a linked list
typedef struct {
config_slot slots[KEYS_PER_BLOCK];
void *next;
} config_block;
static config_block *config_data = NULL;
// returns the config slot for a key name
static config_slot *find_config_slot(const char *key)
{
config_block *current = config_data;
while (current) {
for (int i = 0; i < KEYS_PER_BLOCK; i++) {
config_slot *k = &current->slots[i];
if (strcmp(k->key, key) == 0) {
// found what we're looking for
return k;
} else if (*k->key == '\0') {
// found the first empty key
return NULL;
}
}
current = current->next;
}
return NULL;
}
// returns the next available config slot, or allocates a new block if needed
static config_slot *allocate_config_slot()
{
config_block *current;
if (config_data == NULL) {
config_data = malloc(sizeof(config_block));
assert(config_data != NULL);
memset(config_data, 0, sizeof(config_block));
}
current = config_data;
while (true) {
for (int i = 0; i < KEYS_PER_BLOCK; i++) {
config_slot *k = &current->slots[i];
if (*k->key == '\0') {
return k;
}
}
// this block is full and there's no next allocated block
if (current->next == NULL) {
current->next = malloc(sizeof(config_block));
assert(current->next != NULL);
memset(current->next, 0, sizeof(config_block));
}
current = current->next;
}
}
// parses an int out of 'value' or returns NOT_INT_MAGIC
static int try_parse_int(const char *value)
{
char *endptr;
size_t len = strlen(value);
int v = strtol(value, &endptr, 0);
if (endptr != (value + len)) {
return NOT_INT_MAGIC;
}
return v;
}
// loads a key/value pair into a new config slot
static void add_config_pair(
const char *key, const char *value, int line_number, size_t value_offset
) {
if (strlen(key) > KEY_LENGTH - 1) {
LOG_WARN(
"card10.cfg",
"line:%d: too long - aborting",
line_number
);
return;
}
config_slot *slot = allocate_config_slot();
strncpy(slot->key, key, KEY_LENGTH);
slot->value = try_parse_int(value);
slot->value_offset = value_offset;
}
static void trim(char *str)
{
char *start = str;
while (*start && !isgraph(*start))
start++;
if (strlen(start) > 0) {
char *end = start + strlen(start) - 1;
while (*end && !isgraph(*end))
end--;
end[1] = 0;
}
memmove(str, start, strlen(start) + 1);
}
// parses one line of the config file
static void parse_line(char *line, int line_number, size_t line_offset)
{
char *line_start = line;
char *value_start = strchr(line, '=') + 1;
trim(line);
//printf(line);
if (*line == '#') {
//skip comments
return;
}
char *eq = strchr(line, '=');
if (!eq) {
if (*line) {
LOG_WARN(
"card10.cfg",
"line %d: syntax error",
line_number
);
}
return;
}
*eq = 0;
char *key = line;
trim(key);
if (*key == '\0') {
LOG_WARN("card10.cfg", "line %d: empty key", line_number);
return;
}
char *value = eq + 1;
trim(value);
if (*value == '\0') {
LOG_WARN(
"card10.cfg",
"line %d: empty value for option '%s'",
line_number,
key
);
return;
}
size_t value_offset = value_start - line_start + line_offset;
add_config_pair(key, value, line_number, value_offset);
}
typedef struct {
int line_number;
int file_offset;
int line_start;
char line[MAX_LINE_LENGTH + 1];
int line_length;
} parser_state;
int parse_character(char c, parser_state *s)
{
if (c != '\r' && c != '\n') {
if (s->line_length == MAX_LINE_LENGTH) {
LOG_WARN(
"card10.cfg",
"line:%d: too long - aborting",
s->line_number
);
return -1;
}
s->line[s->line_length++] = c;
} else {
s->line[s->line_length] = 0;
//printf("New line: %s (%d %d)\n", s->line, s->line_number, s->line_start);
parse_line(s->line, s->line_number, s->line_start);
s->line_length = 0;
s->line_start = s->file_offset + 1;
if (c == '\n') {
s->line_number++;
}
}
s->file_offset++;
return 0;
}
// parses the entire config file
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;
}
/* Clear any existing configuration */
/* Don't free the blocks as we are most likely
* going to re-use all of them. */
config_block *current;
if (config_data != NULL) {
current = config_data;
while (true) {
memset(current->slots, 0, sizeof(current->slots));
if (current->next == NULL) {
break;
}
current = current->next;
}
}
char buf[128];
int nread;
parser_state s;
memset(&s, 0, sizeof(s));
s.line_number = 1;
do {
nread = epic_file_read(fd, buf, sizeof(buf));
int i;
for (i = 0; i < nread; i++) {
parse_character(buf[i], &s);
}
} while (nread == sizeof(buf));
parse_character('\n', &s);
epic_file_close(fd);
}
// opens the config file, seeks to seek_offset and reads buf_len bytes
// used for reading strings without storing them in memory
// since we don't need to optimize for that use case as much
static size_t read_config_offset(size_t seek_offset, char *buf, size_t buf_len)
{
int fd = epic_file_open("card10.cfg", "r");
if (fd < 0) {
LOG_DEBUG(
"card10.cfg",
"opening config failed: %s (%d)",
strerror(-fd),
fd
);
return 0;
}
int rc = epic_file_seek(fd, seek_offset, SEEK_SET);
if (rc < 0) {
LOG_ERR("card10.cfg", "seek2 failed (%d), aborting", rc);
return 0;
}
// one byte less to accommodate the 0 termination
int nread = epic_file_read(fd, buf, buf_len - 1);
buf[nread] = '\0';
epic_file_close(fd);
return nread;
}
// returns error if not found or invalid
int epic_config_get_integer(const char *key, int *value)
{
config_slot *slot = find_config_slot(key);
if (slot && slot->value != NOT_INT_MAGIC) {
*value = slot->value;
return 0;
}
return -ENOENT;
}
// returns default_value if not found or invalid
int config_get_integer_with_default(const char *key, int default_value)
{
int value;
int ret = epic_config_get_integer(key, &value);
if (ret) {
return default_value;
} else {
return value;
}
}
// returns error if not found
int epic_config_get_string(const char *key, char *buf, size_t buf_len)
{
config_slot *slot = find_config_slot(key);
if (!(slot && slot->value_offset)) {
return -ENOENT;
}
size_t nread = read_config_offset(slot->value_offset, buf, buf_len);
if (nread == 0) {
return -ENOENT;
}
char *end = buf;
while (*end && !iscntrl(*end))
end++;
*end = 0;
trim(buf);
return 0;
}
// returns dflt if not found, otherwise same pointer as buf
char *config_get_string_with_default(
const char *key, char *buf, size_t buf_len, char *dflt
) {
int ret = epic_config_get_string(key, buf, buf_len);
if (ret) {
return dflt;
} else {
return buf;
}
}
// returns error if not found or invalid
int epic_config_get_boolean(const char *key, bool *value)
{
int int_value;
int ret = epic_config_get_integer(key, &int_value);
if (ret == 0) {
*value = !!int_value;
return 0;
}
char buf[MAX_LINE_LENGTH];
ret = epic_config_get_string(key, buf, MAX_LINE_LENGTH);
if (ret < 0) {
return ret;
}
if (!strcasecmp(buf, "true")) {
*value = true;
return 0;
} else if (!strcasecmp(buf, "false")) {
*value = false;
return 0;
}
return -ERANGE;
}
// returns default_value if not found or invalid
bool config_get_boolean_with_default(const char *key, bool default_value)
{
bool value;
int ret = epic_config_get_boolean(key, &value);
if (ret) {
return default_value;
} else {
return value;
}
}
int epic_config_set_string(const char *key, const char *value_in)
{
char value[MAX_LINE_LENGTH + 1];
if (strlen(key) > MAX_LINE_LENGTH) {
return -EINVAL;
}
/* TODO: Change interface of trim to take the buffer and size directly */
if (strlen(value_in) > MAX_LINE_LENGTH) {
return -EINVAL;
}
strcpy(value, value_in);
trim(value);
if (snprintf(NULL, 0, "\n%s = %s\n", key, value) > MAX_LINE_LENGTH) {
return -EINVAL;
}
/* Check if key is sane. No control characters, spaces, equal signs or pounds allowed */
for (size_t i = 0; i < strlen(key); i++) {
char c = key[i];
if (!isgraph(c) || c == '=' || c == '#') {
return -EINVAL;
}
}
/* Check if value is sane. No control characters allowed */
for (size_t i = 0; i < strlen(value); i++) {
char c = value[i];
if (!isprint(c)) {
return -EINVAL;
}
}
config_slot *slot = find_config_slot(key);
bool present = slot && slot->value_offset;
int ret = 0;
if (!present) {
/* Easy case: We simply add the new option at the
* end of the file. */
char buf[MAX_LINE_LENGTH];
/* Leading new line because I'm lazy */
ret = snprintf(buf, sizeof(buf), "\n%s = %s\n", key, value);
if (ret < 0 || ret >= (int)sizeof(buf)) {
return -EINVAL;
}
int fd = epic_file_open("card10.cfg", "a");
if (fd < 0) {
LOG_DEBUG(
"card10.cfg",
"open for appending failed: %s (%d)",
strerror(-fd),
fd
);
return fd;
}
int write_ret = epic_file_write(fd, buf, strlen(buf));
if (write_ret < 0) {
LOG_DEBUG(
"card10.cfg",
"writing failed: %s (%d)",
strerror(-write_ret),
write_ret
);
}
if (write_ret < (int)strlen(buf)) {
LOG_DEBUG(
"card10.cfg",
"writing failed to write all bytes (%d of %d)",
write_ret,
strlen(buf)
);
}
ret = epic_file_close(fd);
if (ret < 0) {
LOG_DEBUG(
"card10.cfg",
"close failed: %s (%d)",
strerror(-ret),
ret
);
}
if (write_ret < 0) {
ret = write_ret;
}
if (ret < 0) {
goto out;
}
if (write_ret < (int)strlen(buf)) {
LOG_DEBUG(
"card10.cfg",
"writing failed to write all bytes (%d of %d)",
write_ret,
strlen(buf)
);
ret = -EIO;
goto out;
}
} else {
/* Complex case: The value is already somewhere in the file.
* We do not want to lose existing formatting or comments.
* Solution: Copy parts of the file, insert new value, copy
* rest, rename.
*/
char buf[MAX_LINE_LENGTH + 1];
int fd1 = -1;
int fd2 = -1;
ret = epic_config_get_string(key, buf, sizeof(buf));
if (ret == 0 && strcmp(buf, value) == 0) {
/* Nothing to do: the values are the same. */
return 0;
}
size_t nread = read_config_offset(
slot->value_offset, buf, sizeof(buf)
);
if (nread == 0) {
LOG_DEBUG("card10.cfg", "could not read old value");
ret = -EIO;
goto complex_out;
}
char *end = buf;
while (*end && (!iscntrl(*end) || isblank(*end)))
end++;
*end = 0;
int old_len = strlen(buf);
fd1 = epic_file_open("card10.cfg", "r");
if (fd1 < 0) {
LOG_DEBUG(
"card10.cfg",
"open for read failed: %s (%d)",
strerror(-fd1),
fd1
);
ret = fd1;
goto complex_out;
}
fd2 = epic_file_open("card10.nfg", "w");
if (fd2 < 0) {
LOG_DEBUG(
"card10.nfg",
"open for writing failed: %s (%d)",
strerror(-fd2),
fd2
);
ret = fd2;
goto complex_out;
}
/* Copy over slot->value_offset bytes */
int i = slot->value_offset;
while (i > 0) {
int n = i > (int)sizeof(buf) ? (int)sizeof(buf) : i;
ret = epic_file_read(fd1, buf, n);
if (ret < 0) {
LOG_DEBUG(
"card10.cfg",
"read failed: rc: %d",
ret
);
goto complex_out;
}
int ret2 = epic_file_write(fd2, buf, ret);
if (ret2 < 0) {
ret = ret2;
LOG_DEBUG(
"card10.nfg",
"write failed: rc: %d",
ret
);
goto complex_out;
}
i -= ret;
}
/* Insert new value into the new file */
ret = epic_file_write(fd2, value, strlen(value));
if (ret < 0) {
LOG_DEBUG("card10.nfg", "write failed: rc: %d", ret);
goto complex_out;
}
/* Skip the old value inside the old file */
epic_file_seek(fd1, old_len, SEEK_CUR);
/* Copy the rest of the old file to the new file */
while (true) {
int ret = epic_file_read(fd1, buf, sizeof(buf));
if (ret == 0) {
break;
}
if (ret < 0) {
LOG_DEBUG(
"card10.cfg",
"read failed: rc: %d",
ret
);
goto complex_out;
}
int ret2 = epic_file_write(fd2, buf, ret);
if (ret2 < 0) {
ret = ret2;
LOG_DEBUG(
"card10.nfg",
"write failed: rc: %d",
ret
);
goto complex_out;
}
if (ret < (int)sizeof(buf)) {
break;
}
}
complex_out:
if (fd1 >= 0) {
epic_file_close(fd1);
}
if (fd2 >= 0) {
int ret2 = epic_file_close(fd2);
if (ret >= 0) {
ret = ret2;
}
}
if (ret >= 0) {
epic_file_unlink("card10.cfg");
epic_file_rename("card10.nfg", "card10.cfg");
}
}
out:
/* Reload config so the new key or the changed value is available */
load_config();
return ret < 0 ? ret : 0;
}
#ifndef EPICARDIUM_MODULES_CONFIG_H_INCLUDED
#define EPICARDIUM_MODULES_CONFIG_H_INCLUDED
#include <stdbool.h>
#include <stddef.h>
//initialize configuration values and load card10.cfg
void load_config(void);
// returns default_value if not found or invalid
bool config_get_boolean_with_default(const char *key, bool default_value);
int config_get_integer_with_default(const char *key, int default_value);
// returns dflt if not found, otherwise same pointer as buf
char *config_get_string_with_default(const char *key, char *buf, size_t buf_len, char *dflt);
#endif//EPICARDIUM_MODULES_CONFIG_H_INCLUDED
#pragma once
/**
* Panic
* =====
* Panicking should be used in situations where no automatic recovery is
* possible of where a fatal bug was detected. Calling :c:func:`panic()` will
* show a message on the screen and serial console and then reboot the device.
*
* Keep in mind that screen space is limited and thus the message should be as
* concise as possible.
*/
/**
* Trigger a firmware panic.
*
* This function will not return but instead reboot the device. No
* synchronization of e.g. the filesystem is done so this could potentially lead
* to data loss.
*/
void panic(const char *format, ...)
__attribute__((noreturn, format(printf, 1, 2)));
/**
* Logging
* =======
* All logging macros take a "subsystem" and a log message. This subsystem
* argument should be used to uniquely identify where a message came from.
*
* **Example**:
*
* .. code-block:: cpp
*
* LOG_ERR("pmic",
* "Failed reading battery voltage: %s (%d)",
* strerror(-res),
* res);
*/
/* Whether to enable logging at all */
#ifndef LOG_ENABLE
#define LOG_ENABLE 1
#endif
/* Whether to enable even more verbose logging */
#ifndef LOG_ENABLE_DEBUG
#define LOG_ENABLE_DEBUG 0
#endif
/* Whether to enable colorful log */
#ifndef LOG_ENABLE_COLOR
#define LOG_ENABLE_COLOR 1
#endif
#define SEV_DEBUG "D"
#define SEV_INFO "I"
#define SEV_WARN "W"
#define SEV_ERR "E"
#define SEV_CRIT "C"
#if LOG_ENABLE && !defined(__SPHINX_DOC)
int log_msg(const char *subsys, const char *format, ...)
__attribute__((format(printf, 2, 3)));
#if LOG_ENABLE_DEBUG
#define LOG_DEBUG(subsys, format, ...) \
log_msg(subsys, SEV_DEBUG format, ##__VA_ARGS__)
#else /* LOG_ENABLE_DEBUG */
#define LOG_DEBUG(subsys, format, ...)
#endif /* LOG_ENABLE_DEBUG */
#define LOG_INFO(subsys, format, ...) \
log_msg(subsys, SEV_INFO format, ##__VA_ARGS__)
#define LOG_WARN(subsys, format, ...) \
log_msg(subsys, SEV_WARN format, ##__VA_ARGS__)
#define LOG_ERR(subsys, format, ...) \
log_msg(subsys, SEV_ERR format, ##__VA_ARGS__)
#define LOG_CRIT(subsys, format, ...) \
log_msg(subsys, SEV_CRIT format, ##__VA_ARGS__)
#else /* LOG_ENABLE */
/** */
#define LOG_INFO(subsys, format, ...) 0
/** */
#define LOG_WARN(subsys, format, ...) 0
/** */
#define LOG_ERR(subsys, format, ...) 0
/** */
#define LOG_CRIT(subsys, format, ...) 0
/** Only prints when debug logging is enabled in the meson configuration. */
#define LOG_DEBUG(subsys, format, ...) 0
inline __attribute__((format(printf, 2, 3))) int
log_msg(const char *subsys, const char *format, ...)
{
return 0;
}
#endif /* LOG_ENABLE */
#include "modules/log.h" #include "os/core.h"
#include "FreeRTOS.h" #include "FreeRTOS.h"
#include "task.h" #include "task.h"
......
os_sources = files(
'config.c',
'log.c',
'mutex.c',
'panic.c',
'work_queue.c',
)
#include "os/mutex.h"
#include <assert.h>
void _mutex_create(struct mutex *m, const char *name)
{
/* Assert that the mutex has not been initialized already */
assert(m->name == NULL);
/*
* The name is just the parameter stringified which is almost always a
* pointer. If it is, skip over the '&' because it adds no value as
* part of the name.
*/
if (name[0] == '&') {
m->name = &name[1];
} else {
m->name = name;
}
m->_rtos_mutex = xSemaphoreCreateMutexStatic(&m->_rtos_mutex_data);
}
void mutex_lock(struct mutex *m)
{
if (xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED) {
return;
}
int ret = xSemaphoreTake(m->_rtos_mutex, portMAX_DELAY);
/* Ensure locking was actually successful */
assert(ret == pdTRUE);
}
bool mutex_trylock(struct mutex *m)
{
if (xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED) {
return true;
}
int ret = xSemaphoreTake(m->_rtos_mutex, 0);
return ret == pdTRUE;
}
void mutex_assert_locked(struct mutex *m)
{
assert(mutex_get_owner(m) == xTaskGetCurrentTaskHandle());
}
void mutex_unlock(struct mutex *m)
{
if (xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED) {
return;
}
/* Ensure that only the owner can unlock a mutex */
mutex_assert_locked(m);
int ret = xSemaphoreGive(m->_rtos_mutex);
/*
* Ensure that unlocking was successful; that is, the mutex must have
* been acquired previously (no multiple unlocks).
*/
assert(ret == pdTRUE);
}
TaskHandle_t mutex_get_owner(struct mutex *m)
{
return xSemaphoreGetMutexHolder(m->_rtos_mutex);
}
#ifndef _MUTEX_H
#define _MUTEX_H
#ifndef __SPHINX_DOC
/* Some headers are not recognized by hawkmoth for some odd reason */
#include <stddef.h>
#include <stdbool.h>
#include "FreeRTOS.h"
#include "semphr.h"
#else
typedef unsigned int size_t;
typedef _Bool bool;
typedef void *TaskHandle_t;
typedef void *SemaphoreHandle_t;
typedef int StaticSemaphore_t;
#endif /* __SPHINX_DOC */
/**
* Mutex data type.
*/
struct mutex {
/* Name of this mutex, kept for debugging purposes. */
const char *name;
/* FreeRTOS mutex data structures. */
SemaphoreHandle_t _rtos_mutex;
StaticSemaphore_t _rtos_mutex_data;
};
/**
* Create a new mutex.
*
* Call this function as early as possible, in an obvious location so it is easy
* to find. Mutexes should be defined statically so they stay alive for the
* entire run-time of the firmware.
*/
#define mutex_create(mutex) _mutex_create(mutex, #mutex)
void _mutex_create(struct mutex *m, const char *name);
/**
* Lock a mutex.
*
* If the mutex is held by another task, :c:func:`mutex_lock` will block the
* current task until the mutex is unlocked.
*
* .. warning::
*
* This function is **not** safe to use in a timer!
*/
void mutex_lock(struct mutex *m);
/**
* Try locking a mutex.
*
* If the mutex is currently locked by another task, :c:func:`mutex_trylock`
* will return ``false`` immediately. If the attmept to lock was successful, it
* will return ``true``.
*
* This funciton is safe for use in timers.
*/
bool mutex_trylock(struct mutex *m);
/**
* Unlock a mutex.
*
* You **must** call this function from the same task which originally locked
* the mutex.
*/
void mutex_unlock(struct mutex *m);
/**
* Assert that the current task is holding a mutex lock.
*
* If a function requires a certain mutex to be held for safe operation,
* but does not take care of the locking itself, :c:func:`mutex_assert_locked`
* can be used to assert correct caller behavior.
*/
void mutex_assert_locked(struct mutex *m);
/**
* Get the current owner of the mutex.
*
* Returns the task-handle of the task currently holding the mutex. If the
* mutex is unlocked, ``NULL`` is returned.
*/
TaskHandle_t mutex_get_owner(struct mutex *m);
#endif /* _MUTEX_H */
/*
* Panic
* =====
*
* Under some conditions the firmware should crash and reboot automatically.
* This module provides the necessary facilities to do so.
*
* Note that a panic should indicate **only** logic-errors in the firmware or
* unrecoverable hardware conditions.
*/
#include "os/core.h"
#include "modules/modules.h"
#include "drivers/drivers.h"
#include "drivers/display/epic_ctx.h"
#include "drivers/display/lcd.h"
#include "card10.h"
#include "card10-version.h"
#include "LCD_Driver.h"
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
static void faultsplash(const char *msg);
void __attribute__((noreturn)) panic(const char *format, ...)
{
/* Turn off interrupts. We won't get back from here anyway. */
__asm volatile("cpsid i" ::: "memory");
/*
* Turn off asynchronous printing because that won't ever work from
* here ...
*/
serial_return_to_synchronous();
printf("\x1b[31;1m --- SYSTEM PANIC ---\n"
"\x1b[0;31m --- ---\n"
" --- ---\n"
"\x1b[0m A fatal error occured:\n \x1b[1m");
va_list ap;
va_start(ap, format);
vprintf(format, ap);
va_end(ap);
printf("\n"
"\x1b[0m\n"
" Firmware Version:\n"
"\x1b[35m %s\n",
CARD10_VERSION);
printf("\x1b[0m\n"
" Stack Trace:\n"
"\x1b[36m %p\n",
__builtin_return_address(0));
printf("\x1b[33m\n"
" Please report this error to the card10 firmware team!\n"
"\x1b[0m -> https://git.card10.badge.events.ccc.de/card10/firmware/issues/new <-\n"
"\x1b[31m --- ====== ===== ---\x1b[0m\n");
char faultsplash_buffer[14 * 4];
va_start(ap, format);
vsnprintf(faultsplash_buffer, sizeof(faultsplash_buffer), format, ap);
va_end(ap);
faultsplash(faultsplash_buffer);
for (int i = 0; i < 96000000; i++) {
__asm volatile("nop");
}
card10_reset();
}
void __attribute__((noreturn)) __assert_func(
const char *file, int line, const char *func, const char *failedexpr
) {
panic("Assertion failure:\n"
" \"%s\"\n"
" failed in \"%s:%d\",\n"
" function: %s()",
failedexpr,
file,
line,
func);
}
static const unsigned char faultsplash_rle[] = {
0x7f, 0x50, 0x83, 0x0f, 0x82, 0x7f, 0x0d, 0x83, 0x0f, 0x82, 0x7f, 0x1d,
0x82, 0x7f, 0x1f, 0x82, 0x7f, 0x1f, 0x82, 0x7f, 0x12, 0x82, 0x09, 0x82,
0x7f, 0x14, 0x82, 0x09, 0x82, 0x7f, 0x09, 0x82, 0x11, 0x83, 0x7f, 0x0b,
0x82, 0x11, 0x83, 0x7f, 0x0f, 0x82, 0x07, 0x82, 0x7f, 0x16, 0x82, 0x07,
0x82, 0x7f, 0x16, 0x82, 0x07, 0x82, 0x7f, 0x1a, 0x83, 0x7f, 0x1e, 0x83,
0x7f, 0x0a, 0x8b, 0x17, 0x82, 0x02, 0x82, 0x02, 0x83, 0x73, 0x8c, 0x16,
0x82, 0x02, 0x82, 0x02, 0x83, 0x72, 0x81, 0x0c, 0x84, 0x07, 0x85, 0x7f,
0x03, 0x82, 0x0c, 0x84, 0x07, 0x86, 0x7f, 0x02, 0x82, 0x0c, 0x84, 0x07,
0x86, 0x7f, 0x82, 0x12, 0x87, 0x7f, 0x06, 0x82, 0x12, 0x87, 0x7f, 0x06,
0x82, 0x24, 0x82, 0x78, 0x82, 0x24, 0x82, 0x78, 0x82, 0x24, 0x82, 0x78,
0x82, 0x16, 0x83, 0x04, 0x82, 0x07, 0x82, 0x76, 0x82, 0x16, 0x83, 0x04,
0x82, 0x07, 0x82, 0x70, 0x8f, 0x0d, 0x82, 0x12, 0x82, 0x6e, 0x8f, 0x0d,
0x82, 0x12, 0x82, 0x6e, 0x8f, 0x0b, 0x82, 0x09, 0x82, 0x0b, 0x82, 0x6c,
0x8f, 0x0b, 0x82, 0x09, 0x82, 0x0b, 0x82, 0x6c, 0x8f, 0x0b, 0x82, 0x09,
0x82, 0x0b, 0x82, 0x6c, 0x8f, 0x7f, 0x12, 0x8f, 0x7f, 0x0d, 0x98, 0x12,
0x82, 0x74, 0x98, 0x12, 0x82, 0x70, 0xa1, 0x7f, 0xa1, 0x7f, 0xa1, 0x7f,
0xa1, 0x7f, 0xa1, 0x7d, 0xa5, 0x7b, 0xa5, 0x7b, 0xa5, 0x7b, 0xa5, 0x7a,
0xa6, 0x78, 0x89, 0x02, 0x9f, 0x76, 0x89, 0x02, 0x9f, 0x76, 0xaa, 0x76,
0xaa, 0x76, 0x87, 0x02, 0xa1, 0x76, 0x87, 0x02, 0xa1, 0x76, 0x87, 0x02,
0xa1, 0x76, 0x87, 0x02, 0xa1, 0x76, 0x87, 0x02, 0xa1, 0x76, 0x87, 0x02,
0xa1, 0x76, 0x87, 0x02, 0xa1, 0x76, 0xaa, 0x76, 0xaa, 0x76, 0xaa, 0x76,
0x89, 0x02, 0x9f, 0x76, 0x89, 0x02, 0x9f, 0x79, 0xa5, 0x7b, 0xa5, 0x7b,
0xa5, 0x7b, 0x8b, 0x02, 0x98, 0x7b, 0x8b, 0x02, 0x98, 0x7d, 0xa1, 0x7f,
0xa1, 0x7f, 0xa1, 0x7f, 0xa1, 0x7f, 0xa1, 0x7f, 0x04, 0x98, 0x7f, 0x09,
0x98, 0x7f, 0x0e, 0x8f, 0x7f, 0x12, 0x8f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x06
};
static void faultsplash(const char *msg)
{
LCD_SetBacklight(100);
size_t idx = 0;
for (size_t i = 0; i < sizeof(faultsplash_rle); i++) {
uint8_t color = (faultsplash_rle[i] & 0x80) ? 0xff : 0x00;
uint8_t length = faultsplash_rle[i] & 0x7f;
memset(&epicardium_ctx_fb[idx], color, length * 2);
idx += length * 2;
}
lcd_write_fb(epicardium_ctx_fb);
/* make sure we get a clean graphics ctx */
disp_ctx_reinit();
ctx_text_baseline(epicardium_ctx, CTX_TEXT_BASELINE_TOP);
ctx_font_size(epicardium_ctx, 20.0f);
ctx_move_to(epicardium_ctx, 80.0f, 8.0f);
ctx_rgba8(epicardium_ctx, 0xff, 0x00, 0x00, 0xff);
ctx_text(epicardium_ctx, "Panic");
ctx_font_size(epicardium_ctx, 12.0f);
ctx_rgba8(epicardium_ctx, 197, 197, 197, 0xff);
size_t len = strlen(msg);
char buf[15] = { 0 };
float offset = 34.0f;
for (size_t i = 0; i < len; i += 14) {
strncpy(buf, &msg[i], 14);
ctx_move_to(epicardium_ctx, 52.0f, offset);
ctx_text(epicardium_ctx, buf);
offset += 12.0f;
}
lcd_write_fb(epicardium_ctx_fb);
}
#include "epicardium.h"
#include "os/core.h"
#include "os/work_queue.h"
#include "FreeRTOS.h"
#include "queue.h"
struct work {
void (*func)(void *data);
void *data;
};
static QueueHandle_t work_queue;
static uint8_t buffer[sizeof(struct work) * WORK_QUEUE_SIZE];
static StaticQueue_t work_queue_data;
void workqueue_init(void)
{
work_queue = xQueueCreateStatic(
WORK_QUEUE_SIZE, sizeof(struct work), buffer, &work_queue_data
);
}
int workqueue_schedule(void (*func)(void *data), void *data)
{
struct work work = { func, data };
if (xQueueSend(work_queue, &work, 0) != pdTRUE) {
/* Likely full */
LOG_WARN("workqueue", "could not schedule %p(%p)", func, data);
return -EAGAIN;
}
return 0;
}
void vWorkQueueTask(void *pvParameters)
{
struct work work;
workqueue_init();
while (1) {
if (xQueueReceive(work_queue, &work, portMAX_DELAY) == pdTRUE) {
if (work.func) {
work.func(work.data);
}
}
}
}
#ifndef _WORK_QUEUE_H
#define _WORK_QUEUE_H
#define WORK_QUEUE_SIZE 20
void workqueue_init(void);
int workqueue_schedule(void (*func)(void *data), void *data);
void vWorkQueueTask(void *pvParameters);
#endif
...@@ -6,33 +6,146 @@ ...@@ -6,33 +6,146 @@
#include "task.h" #include "task.h"
#include "api/dispatcher.h" #include "api/dispatcher.h"
#include "modules/log.h" #include "user_core/user_core.h"
#include "os/core.h"
#include "drivers/drivers.h"
#include "modules/modules.h"
#define BB_CLK_RATE_HZ 1600000
#include "usb.h"
#include "mxc_sys.h"
#include "wut.h"
#include "wsf_types.h"
#include "wsf_os.h"
#include "sch_api_ble.h"
#include "bb_drv.h"
#include "card10.h" #include "card10.h"
extern TaskHandle_t dispatcher_task_id; #define US_TO_BBTICKS(x) (((x) * (BB_CLK_RATE_HZ / 100000) + 9) / 10)
#define MIN(a, b) (a < b) ? a : b
#define MIN_SLEEP_TIME_MS 1
static int32_t ble_sleep_ticks(void)
{
uint32_t nextDbbEventDue;
bool_t dueValid = SchBleGetNextDueTime(&nextDbbEventDue);
int sleep_ticks = nextDbbEventDue - BbDrvGetCurrentTime();
if (dueValid) {
if (sleep_ticks > 0) {
uint32_t bb_idle = BB_TICKS_TO_US(sleep_ticks) / 1000;
return bb_idle;
} else {
return 0;
}
} else {
return -1;
}
}
/* /*
* This hook is called before FreeRTOS enters tickless idle. * This hook is called before FreeRTOS enters tickless idle.
*/ */
void pre_idle_sleep(TickType_t xExpectedIdleTime) void pre_idle_sleep(TickType_t xExpectedIdleTime)
{ {
if (xExpectedIdleTime > 0) { if (xExpectedIdleTime > 0 &&
(CoreDebug->DHCSR & CoreDebug_DHCSR_C_DEBUGEN_Msk) == 0) {
/* /*
* WFE because the other core should be able to notify * WFE because the other core should be able to notify
* epicardium if it wants to issue an API call. * epicardium if it wants to issue an API call.
*/ */
/* /* If the other core is waiting for a call to return and we are able
* TODO: Ensure this is actually correct and does not have any * to sleep, reduce the system clock to save energy. */
* race conditions. if (xExpectedIdleTime >= pdMS_TO_TICKS(MIN_SLEEP_TIME_MS) &&
*/ api_dispatcher_call_pending() && wsfOsReadyToSleep()) {
if ((CoreDebug->DHCSR & CoreDebug_DHCSR_C_DEBUGEN_Msk) == 0) { uint32_t ms = xExpectedIdleTime;
if (ble_is_enabled()) {
int32_t ble_idle = ble_sleep_ticks();
if (ble_idle >= 0) {
ms =
MIN(xExpectedIdleTime,
(uint32_t)ble_idle);
}
}
// Us the WUT only if we can sleep a significant amount of time
if (ms >= MIN_SLEEP_TIME_MS) {
/* Initialize Wakeup timer */
WUT_Init(WUT_PRES_1);
wut_cfg_t wut_cfg;
wut_cfg.mode = WUT_MODE_COMPARE;
wut_cfg.cmp_cnt = 0xFFFFFFFF;
WUT_Config(&wut_cfg);
WUT_Enable();
/* Enable WUT as a wakup source */
NVIC_EnableIRQ(WUT_IRQn);
uint32_t targetTick;
targetTick = WUT_GetCount();
targetTick +=
((uint64_t)(ms)*SYS_WUT_GetFreq() /
1000);
WUT_SetCompare(targetTick);
/* Stop SysTick */
uint32_t val = SysTick->VAL;
SysTick->CTRL &= ~(SysTick_CTRL_ENABLE_Msk);
if (usb_get_status() & MAXUSB_STATUS_VBUS_ON) {
/* Need to stay on 96 MHz. USB serial becomes
* unstable otherwise. Don't know why. */
//SYS_Clock_Select(SYS_CLOCK_HIRC, NULL);
} else {
MXC_GCR->clkcn |=
MXC_S_GCR_CLKCN_PSC_DIV4;
SYS_Clock_Select(SYS_CLOCK_HIRC, NULL);
SYS_ClockSourceDisable(
SYS_CLOCK_HIRC96
);
}
disp_update_backlight_clock();
__asm volatile("dsb" ::: "memory"); __asm volatile("dsb" ::: "memory");
__asm volatile("wfe"); __asm volatile("wfe");
__asm volatile("isb"); __asm volatile("isb");
MXC_GCR->clkcn &= ~(MXC_S_GCR_CLKCN_PSC_DIV4);
SYS_Clock_Select(SYS_CLOCK_HIRC96, NULL);
#if 0
/* Allow to serve high priority interrupts before
* doing the lengthy xTaskIncrementTick loop. */
portDISABLE_INTERRUPTS();
__set_PRIMASK(0);
__set_PRIMASK(1);
portENABLE_INTERRUPTS();
#endif
disp_update_backlight_clock();
SYS_ClockSourceDisable(SYS_CLOCK_HIRC);
SysTick->LOAD = val;
SysTick->VAL = 0;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
ms = (WUT_GetCount() * 1000) /
SYS_WUT_GetFreq();
for (uint32_t t = 0; t < ms; t++) {
xTaskIncrementTick();
}
return;
} }
} }
/* Fall back to light sleep with clocks on if we can't
* sleep long enough for the WUT */
__asm volatile("dsb" ::: "memory");
__asm volatile("wfe");
__asm volatile("isb");
}
} }
/* /*
...@@ -118,5 +231,5 @@ void vApplicationGetTimerTaskMemory( ...@@ -118,5 +231,5 @@ void vApplicationGetTimerTaskMemory(
void vApplicationStackOverflowHook(TaskHandle_t xTask, signed char *pcTaskName) void vApplicationStackOverflowHook(TaskHandle_t xTask, signed char *pcTaskName)
{ {
LOG_CRIT("rtos", "Task \"%s\" overflowed stack!", pcTaskName); panic("Task \"%s\" overflowed stack!", pcTaskName);
} }
/**
*
* epicardium-specific implemnetation of cdcacm device
* services CDCACM API
*/
#include "usb/epc_usb.h"
#include "usb/cdcacm.h"
#include "usb/descriptors.h"
#include <stdio.h>
#include <stddef.h>
#include <errno.h>
#include "usb.h"
#include "usb_event.h"
#include "cdc_acm.h"
#include "os/core.h"
#include "modules/modules.h"
#include "drivers/drivers.h"
#include "FreeRTOS.h"
#include "task.h"
static volatile int lockup_disable = 0;
static inline struct config_descriptor_cdcacm *
descriptors(struct esb_config *self)
{
return self->descriptors->cdcacm;
}
/******************************************************************************/
static volatile int usb_read_complete; // SWYM: only ever written to
static int cb_acm_read_ready(void)
{
usb_read_complete = 1;
return 0;
}
static int cb_acm_connected(void)
{
lockup_disable = 0;
return 0;
}
int esb_cdc_init(struct esb_config *self)
{
LOG_DEBUG("cdcacm", "init");
struct config_descriptor_cdcacm *dsc = descriptors(self);
usb_read_complete = 0;
int ret = acm_init(&dsc->comm_interface);
acm_register_callback(
ACM_CB_READ_READY,
cb_acm_read_ready); //SWYM: actually not needed
acm_register_callback(ACM_CB_CONNECTED, cb_acm_connected);
return ret;
}
int esb_cdc_configure(struct esb_config *self)
{
struct config_descriptor_cdcacm *dsc = descriptors(self);
//acm_configure does not keep the amc_cfg_t pointer around
//so stack-local lifetime is fine
acm_cfg_t acm_cfg = {
.in_ep = dsc->endpoint_in.bEndpointAddress & 0x0f,
.in_maxpacket = MXC_USBHS_MAX_PACKET,
.out_ep = dsc->endpoint_out.bEndpointAddress,
.out_maxpacket = MXC_USBHS_MAX_PACKET,
.notify_ep = dsc->endpoint_notify.bEndpointAddress & 0x0f,
.notify_maxpacket = MXC_USBHS_MAX_PACKET,
};
LOG_DEBUG(
"cdcacm",
"configure, endpoints %d,%d,%d",
acm_cfg.in_ep,
acm_cfg.out_ep,
acm_cfg.notify_ep
);
return acm_configure(&acm_cfg);
}
int esb_cdc_deconfigure(struct esb_config *self)
{
LOG_DEBUG("cdcacm", "deconfigure");
return acm_deconfigure();
}
int cdcacm_num_read_avail(void)
{
return acm_canread();
}
uint8_t cdcacm_read(void)
{
while (acm_canread() <= 0) {
}
uint8_t buf;
acm_read(&buf, 1);
return buf;
}
void cdcacm_write(uint8_t *data, int len)
{
if (acm_present() && !lockup_disable) {
int ret = acm_write(data, len);
if (ret < 0) {
LOG_ERR("cdcacm", "fifo lockup detected");
lockup_disable = 1;
} else if (ret != len) {
LOG_WARN(
"cdcacm", "write length mismatch, got %d", ret
);
}
}
}
/******************************************************************************/
extern TaskHandle_t serial_task_id;
void USB_IRQHandler(void)
{
usb_event_handler();
if (serial_task_id != NULL) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xTaskNotifyFromISR(
serial_task_id,
SERIAL_READ_NOTIFY,
eSetBits,
&xHigherPriorityTaskWoken
);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
#ifndef CDCACM_H
#define CDCACM_H
#include "usb/epc_usb.h"
// callbacks for esb_config
int esb_cdc_init(struct esb_config* self);
int esb_cdc_configure(struct esb_config* self);
int esb_cdc_deconfigure(struct esb_config* self);
int cdcacm_num_read_avail(void);
uint8_t cdcacm_read(void);
void cdcacm_write(uint8_t *data, int len);
#endif
#ifndef _DESCRIPTORS_H_
#define _DESCRIPTORS_H_
#include <stdint.h>
#include "usb.h"
#define ESB_ENDPOINT_MSC_IN 2
#define ESB_ENDPOINT_MSC_OUT 1
#define ESB_ENDPOINT_CDCACM_NOTIFY 3
#define ESB_ENDPOINT_CDCACM_IN 2
#define ESB_ENDPOINT_CDCACM_OUT 1
/* device types */
#define DT_DEVICE 0x01
#define DT_CONFIG 0x02
#define DT_INTERFACE 0x04
#define DT_ENDPOINT 0x05
#define DT_FUNCTIONAL 0x24
/* interface classes */
#define CLS_UNSPECIFIED 0x00
#define CLS_COMM 0x02
#define CLS_MASS_STOR 0x08
#define CLS_DATA 0x0a
/* sub-classes */
#define SCLS_NONE 0x00
#define SCLS_ACM 0x02
#define SCLS_SCSI_CMDS 0x06
/* interface protocols */
#define PROT_AT_CMDS 0x01
#define PROT_BULK_TRANS 0x50
/* endpoint attributes */
#define ATTR_BULK 0x02
#define ATTR_INTERRUPT 0x03
#define VNDR_MAXIM 0x0B6A
#if defined(__GNUC__)
#define PACKED_STRUCT struct __attribute__((packed))
#else
#define PACKED_STRUCT __packed struct
#endif
PACKED_STRUCT
config_descriptor_cdcacm {
usb_configuration_descriptor_t config;
usb_interface_descriptor_t comm_interface;
uint8_t header_functional[5];
uint8_t call_management[5];
uint8_t acm_functional[4];
uint8_t union_functional[5];
usb_endpoint_descriptor_t endpoint_notify;
usb_interface_descriptor_t data_interface;
usb_endpoint_descriptor_t endpoint_out;
usb_endpoint_descriptor_t endpoint_in;
};
PACKED_STRUCT
config_descriptor_msc {
usb_configuration_descriptor_t config;
usb_interface_descriptor_t msc_interface;
usb_endpoint_descriptor_t endpoint_out;
usb_endpoint_descriptor_t endpoint_in;
};
struct esb_device_descriptors {
usb_device_descriptor_t* device;
union {
usb_configuration_descriptor_t* config;
struct config_descriptor_cdcacm* cdcacm;
struct config_descriptor_msc* msc;
};
};
#endif /* _DESCRIPTORS_H_ */
/**
*
*
* Core of the USB implementation:
*
* - device independent
* - handles setup of MAXUSB stack
* - handles events and state of the MAXUSB stack
*
* Also contains definitions of device-independent String descriptors
*
*
*
*
*
* swym's USB ramblings:
*
* setting up USB basically consists of two parts:
*
* - set up descriptors
* - initialize the corresponding MAXUSB stacks - acm, msc...
*
* at the moment, the descriptors are statically configured in
* descriptors.h several descriptors point to enumeration descriptors
* via iFooBar indices. Those enumeration descriptors are registered
* via enum_register_descriptor(type, desc, idx), where the idx passed
* corresponds to the iFooBar index mentioned above.
*
* There are several callbacks, some of which do not perform anything meaningful
* at the moment. For example usb_write_callback sets a global flag that is never
* read from. These will be removed later on.
*
* The initialization routines of acm & msc refer to the descriptors from which,
* among other things, they get their enpoint IDs. These are hard-coded right now
* but it would make sense to allocate these IDs at runtime. There is, AFAICT, no
* reason for these endpoint IDs to be 3, 1, 2 in the CDCACM case for example.
*
* Allocating those at runtime would also make the setup less rigid: we can have all
* combinations of {cdcacm, storage} interfaces without the awkward descriptor hackery
* in epc_usb_init.
*
* To generalize even further, the descriptors could be malloced. The total length of
* the descriptor structure can easily be derived from the number of interfaces to be
* initialized. The memory allocated will then be split up (with alignment of the single
* descriptor structs taken care of) in a simple bump-allocator strategy on demand.
*
*
*/
#include "usb/epc_usb.h"
#include <assert.h>
#include <stdio.h>
#include <stddef.h>
#include <FreeRTOS.h>
#include <task.h>
#include <timers.h>
#include "mxc_config.h"
#include "mxc_sys.h"
#include "mxc_delay.h"
// MAXUSB includes
#include "usb.h"
#include "usb_event.h"
#include "enumerate.h"
#include "msc.h"
#include "cdc_acm.h"
#include "usb/descriptors.h"
#include "os/core.h"
#include "fs/filesystem.h"
//#define USE_REMOTE_WAKE_ENABLE
/***** Function Prototypes *****/
static int cb_usb_setconfig(usb_setup_pkt *sud, void *cbdata);
#ifdef USE_REMOTE_WAKE_ENABLE
static int cb_usb_setfeature(usb_setup_pkt *sud, void *cbdata);
static int cb_usb_clrfeature(usb_setup_pkt *sud, void *cbdata);
#endif
static int cb_usb_event(maxusb_event_t evt, void *data);
static int cb_usb_shutdown();
static int cb_usb_init();
static void usb_app_sleep(void);
static void usb_app_wakeup(void);
/* needed for usb_opts. mxc_delay() takes unsigned long, so can't use it directly */
static void delay_us(unsigned int usec);
static void device_deinit();
static bool device_deconfigure();
static int device_configure();
volatile int suspended;
#ifdef USE_REMOTE_WAKE_ENABLE
int remote_wake_en; //SWYM: unused, only written to!
#endif
static StaticTimer_t x;
static TimerHandle_t timerWakeup = NULL;
static void cb_timerReset(TimerHandle_t t);
static struct esb_config s_device = { 0 };
__attribute__((aligned(4))) uint8_t lang_id_desc[] = {
0x04, /* bLength */
0x03, /* bDescriptorType */
0x09,
0x04 /* bString = wLANGID (see usb_20.pdf 9.6.7 String) */
};
__attribute__((aligned(4))) uint8_t mfg_id_desc[] = {
0x22, /* bLength */
0x03, /* bDescriptorType */
'M', 0, 'a', 0, 'x', 0, 'i', 0, 'm', 0, ' ', 0, 'I', 0, 'n', 0,
't', 0, 'e', 0, 'g', 0, 'r', 0, 'a', 0, 't', 0, 'e', 0, 'd', 0,
};
__attribute__((aligned(4))) uint8_t prod_id_desc[] = {
0x24, /* bLength */
0x03, /* bDescriptorType */
'C', 0, 'A', 0, 'R', 0, 'D', 0, '1', 0, '0', 0, ' ', 0, 'U', 0, 'S', 0,
'B', 0, ' ', 0, 'G', 0, 'A', 0, 'D', 0, 'G', 0, 'E', 0, 'T', 0,
};
/* Not currently used (see device descriptor), but could be enabled if desired */
__attribute__((aligned(4)))
uint8_t serial_id_desc[] = { 0x14, /* bLength */
0x03, /* bDescriptorType */
'c', 0, 'a', 0, 'a', 0, 'a', 0, 'a',
0, 'd', 0, '1', 0, '0', 0, '0', 0 };
int cb_usb_init()
{
const sys_cfg_usbhs_t sys_usbhs_cfg = NULL;
LOG_DEBUG("usb", "init");
return SYS_USBHS_Init(&sys_usbhs_cfg);
}
volatile bool shutDownCompleted;
void do_usb_shutdown()
{
LOG_DEBUG("usb", "shutting down...");
shutDownCompleted = false;
usb_shutdown();
for (int i = 0; i < 10000 && !shutDownCompleted; ++i) {
}
}
int cb_usb_shutdown()
{
SYS_USBHS_Shutdown();
SYS_Reset_Periph(SYS_RESET_USB);
device_deinit();
LOG_DEBUG("usb", "shutdown complete");
shutDownCompleted = true;
return 0;
}
/* User-supplied function to delay usec micro-seconds */
static void delay_us(unsigned int usec)
{
/* mxc_delay() takes unsigned long, so can't use it directly */
mxc_delay(usec);
}
static void device_deinit()
{
esb_cfg_handler deinit = s_device.deinit;
s_device.deinit = NULL;
s_device.configure = NULL;
s_device.deconfigure = NULL;
if (deinit) {
LOG_DEBUG("usb", "deinit");
deinit(&s_device);
}
}
//de-configure last device, if any
static volatile bool s_configured = false;
static bool device_deconfigure()
{
if (s_configured) {
s_configured = false;
if (s_device.deconfigure) {
LOG_DEBUG("usb", "deconfigure");
enum_clearconfig();
s_device.deconfigure(&s_device);
return true;
}
}
return false;
}
static int device_configure()
{
if (!s_configured && s_device.configure) {
s_configured = true;
LOG_DEBUG("usb", "configure");
return s_device.configure(&s_device);
}
return 0;
}
static volatile bool s_connected = false;
static void disconnect()
{
if (s_connected) {
LOG_DEBUG("usb", "disconnect");
s_connected = false;
usb_disconnect();
// SYS_Reset_Periph(SYS_RESET_USB);
mxc_delay(10000);
}
}
static void connect()
{
s_connected = true;
usb_connect();
}
static volatile bool s_initialized = false;
void esb_deinit(void)
{
if (s_initialized) {
s_initialized = false;
disconnect();
device_deconfigure();
do_usb_shutdown();
device_deinit();
}
}
int esb_init(struct esb_config *cfg)
{
esb_deinit();
if (cfg == NULL) {
return 0;
}
if (timerWakeup == NULL) {
timerWakeup = xTimerCreateStatic(
"timerWakeup", /* name */
pdMS_TO_TICKS(50), /* period/time */
pdFALSE, /* auto reload */
NULL, /* timer ID */
cb_timerReset,
&x);
}
maxusb_cfg_options_t usb_opts;
/* Initialize state */
suspended = 0;
#ifdef USE_REMOTE_WAKE_ENABLE
remote_wake_en = 0;
#endif
/* Start out in full speed */
usb_opts.enable_hs = 0;
usb_opts.delay_us = delay_us;
usb_opts.init_callback = cb_usb_init;
usb_opts.shutdown_callback = cb_usb_shutdown;
/* Initialize the usb module */
if (usb_init(&usb_opts) != 0) {
LOG_ERR("usb", "usb_init() failed");
return -EIO;
}
/* Initialize the enumeration module */
if (enum_init() != 0) {
do_usb_shutdown();
LOG_ERR("usb", "enum_init() failed");
return -EIO;
}
LOG_INFO("usb", "initializing device %s", cfg->name);
if (cfg->init(cfg)) {
enum_clearconfig();
do_usb_shutdown();
LOG_ERR("usb", "device init failed");
return -EIO;
}
s_initialized = true;
s_device = *cfg;
enum_register_descriptor(
ENUM_DESC_DEVICE, (uint8_t *)cfg->descriptors->device, 0
);
enum_register_descriptor(
ENUM_DESC_CONFIG, (uint8_t *)cfg->descriptors->config, 0
);
enum_register_descriptor(ENUM_DESC_STRING, lang_id_desc, 0);
enum_register_descriptor(ENUM_DESC_STRING, mfg_id_desc, 1);
enum_register_descriptor(ENUM_DESC_STRING, prod_id_desc, 2);
enum_register_descriptor(ENUM_DESC_STRING, serial_id_desc, 3);
//enum_register_descriptor(ENUM_DESC_STRING, cdcacm_func_desc, 4);
//enum_register_descriptor(ENUM_DESC_STRING, msc_func_desc, 5);
/* Handle configuration */
enum_register_callback(ENUM_SETCONFIG, cb_usb_setconfig, NULL);
#ifdef USE_REMOTE_WAKE_ENABLE
/* Handle feature set/clear */
enum_register_callback(ENUM_SETFEATURE, cb_usb_setfeature, NULL);
enum_register_callback(ENUM_CLRFEATURE, cb_usb_clrfeature, NULL);
#endif
/* Register callbacks */
usb_event_enable(MAXUSB_EVENT_NOVBUS, cb_usb_event, NULL);
usb_event_enable(MAXUSB_EVENT_VBUS, cb_usb_event, NULL);
/* Start with USB in low power mode */
usb_app_sleep();
/* TODO: Fix priority */
NVIC_SetPriority(USB_IRQn, 6);
NVIC_EnableIRQ(USB_IRQn);
return 0;
}
static int cb_usb_setconfig(usb_setup_pkt *sud, void *cbdata)
{
LOG_DEBUG("usb", "setconfig %d", sud->wValue);
if (sud->wValue == s_device.descriptors->config->bConfigurationValue) {
return device_configure();
} else if (sud->wValue == 0) {
device_deconfigure();
return 0;
}
return -1;
}
#ifdef USE_REMOTE_WAKE_ENABLE
static int cb_usb_setfeature(usb_setup_pkt *sud, void *cbdata)
{
if (sud->wValue == FEAT_REMOTE_WAKE) {
remote_wake_en = 1;
return 0;
}
return -1;
}
static int cb_usb_clrfeature(usb_setup_pkt *sud, void *cbdata)
{
if (sud->wValue == FEAT_REMOTE_WAKE) {
remote_wake_en = 0;
return 0;
}
return -1;
}
#endif
/******************************************************************************/
static void usb_app_sleep(void)
{
/* TODO: Place low-power code here */
suspended = 1;
}
/******************************************************************************/
static void usb_app_wakeup(void)
{
/* TODO: Place low-power code here */
suspended = 0;
}
static void cb_timerReset(TimerHandle_t t)
{
(void)t;
LOG_DEBUG("usb", "cb_timerReset %08x", usb_get_status());
LOG_DEBUG("usb", "SYS_USBHS_Shutdown");
SYS_USBHS_Shutdown();
LOG_DEBUG("usb", "SYS_Reset_Periph");
SYS_Reset_Periph(SYS_RESET_USB);
//copy-paste from esb_init(), need to refactor
maxusb_cfg_options_t usb_opts;
usb_opts.enable_hs = 0;
usb_opts.delay_us = delay_us;
usb_opts.init_callback = cb_usb_init;
usb_opts.shutdown_callback = cb_usb_shutdown;
/* Initialize the usb module */
if (usb_init(&usb_opts) != 0) {
LOG_ERR("usb", "usb_init() failed");
return;
}
usb_event_enable(MAXUSB_EVENT_NOVBUS, cb_usb_event, NULL);
usb_event_enable(MAXUSB_EVENT_VBUS, cb_usb_event, NULL);
}
static void scheduleReset()
{
LOG_DEBUG("usb", "scheduleReset");
xTimerChangePeriodFromISR(timerWakeup, pdMS_TO_TICKS(500), NULL);
}
#if LOG_ENABLE_DEBUG
static const char *maxusb_event_string(maxusb_event_t evt)
{
static const char *names[MAXUSB_NUM_EVENTS] = {
[MAXUSB_EVENT_DPACT] = "DPACT",
[MAXUSB_EVENT_RWUDN] = "RWUDN",
[MAXUSB_EVENT_BACT] = "BACT",
[MAXUSB_EVENT_BRST] = "BRST",
[MAXUSB_EVENT_SUSP] = "SUSP",
[MAXUSB_EVENT_NOVBUS] = "NOVBUS",
[MAXUSB_EVENT_VBUS] = "VBUS",
[MAXUSB_EVENT_BRSTDN] = "BRSTDN",
[MAXUSB_EVENT_SUDAV] = "SUDAV",
};
return evt < MAXUSB_NUM_EVENTS ? names[evt] : "<INVALID>";
}
#endif
/******************************************************************************/
static int cb_usb_event(maxusb_event_t evt, void *data)
{
static int s_VBUS_SUSP_sequence = 0;
LOG_DEBUG("usb", "event %s (%d)\n", maxusb_event_string(evt), evt);
switch (evt) {
case MAXUSB_EVENT_NOVBUS:
usb_event_disable(MAXUSB_EVENT_BRST);
usb_event_disable(MAXUSB_EVENT_SUSP);
usb_event_disable(MAXUSB_EVENT_DPACT);
usb_disconnect();
device_deconfigure();
usb_app_sleep();
break;
case MAXUSB_EVENT_VBUS:
s_VBUS_SUSP_sequence = 1;
usb_event_clear(MAXUSB_EVENT_BRST);
usb_event_enable(MAXUSB_EVENT_BRST, cb_usb_event, NULL);
usb_event_clear(MAXUSB_EVENT_SUSP);
usb_event_enable(MAXUSB_EVENT_SUSP, cb_usb_event, NULL);
connect();
usb_app_sleep();
break;
case MAXUSB_EVENT_BRST:
usb_app_wakeup();
device_deconfigure();
suspended = 0;
break;
case MAXUSB_EVENT_SUSP:
if (!s_VBUS_SUSP_sequence) {
scheduleReset();
}
s_VBUS_SUSP_sequence = 0;
//usb_app_sleep();
break;
case MAXUSB_EVENT_DPACT:
usb_app_wakeup();
break;
default:
break;
}
return 0;
}
#ifndef EPICARDIUM_USB_EPC_USB_H_INCLUDED
#define EPICARDIUM_USB_EPC_USB_H_INCLUDED
/**
*
* EPC - it's not just a Universal Serial Bus,
* it's an Epic Serial Bus!
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
struct esb_config;
typedef int (*esb_cfg_handler)(struct esb_config* self);
struct esb_config {
const char* name;
esb_cfg_handler init;
esb_cfg_handler configure;
esb_cfg_handler deconfigure;
esb_cfg_handler deinit;
struct esb_device_descriptors* descriptors;
void* deviceData;
};
int esb_init(struct esb_config* cfg);
void esb_deinit(void);
#endif//EPICARDIUM_USB_EPC_USB_H_INCLUDED
/**
* backend-independent implementation of the mass storage device
* services MSC API and esb_cfg API
*/
#include "usb/mass_storage.h"
#include "usb/epc_usb.h"
#include "usb/descriptors.h"
#include "os/core.h"
#include "msc.h"
#include "usb.h"
#include "max32665.h"
#define VENDOR_STRING "CCC"
#define PRODUCT_STRING "card10"
#define VERSION_STRING "1.0"
static inline struct config_descriptor_msc *descriptors(struct esb_config *self)
{
return self->descriptors->msc;
}
int esb_msc_configure(struct esb_config *self)
{
LOG_DEBUG("msc", "configure");
struct config_descriptor_msc *dsc = descriptors(self);
const msc_cfg_t msc_cfg = {
dsc->endpoint_out.bEndpointAddress,
MXC_USBHS_MAX_PACKET, /* OUT max packet size */
dsc->endpoint_in.bEndpointAddress & 0x0f,
MXC_USBHS_MAX_PACKET, /* IN max packet size */
};
return msc_configure(&msc_cfg);
}
int esb_msc_deconfigure(struct esb_config *self)
{
LOG_DEBUG("msc", "deconfigure");
(void)self;
return msc_deconfigure();
}
int esb_msc_init(struct esb_config *self)
{
LOG_DEBUG("msc", "init");
static const msc_idstrings_t ids = { VENDOR_STRING,
PRODUCT_STRING,
VERSION_STRING };
msc_mem_t *mem = self->deviceData;
struct config_descriptor_msc *dsc = descriptors(self);
return msc_init(&dsc->msc_interface, &ids, mem);
}
#ifndef EPICARDIUM_USB_MSC_H_INCLUDED
#define EPICARDIUM_USB_MSC_H_INCLUDED
#include "usb/epc_usb.h"
int esb_msc_configure(struct esb_config* self);
int esb_msc_deconfigure(struct esb_config* self);
int esb_msc_init(struct esb_config* self);
#endif//EPICARDIUM_USB_MSC_H_INCLUDED
#include "os/core.h"
#include "os/mutex.h"
#include "api/dispatcher.h"
#include "FreeRTOS.h"
#include "task.h"
#define TIMEOUT pdMS_TO_TICKS(2000)
TaskHandle_t dispatcher_task_id;
struct mutex api_mutex = { 0 };
void dispatcher_mutex_init(void)
{
mutex_create(&api_mutex);
}
/*
* API dispatcher task. This task will sleep until an API call is issued and
* then wake up to dispatch it.
*/
void vApiDispatcher(void *pvParameters)
{
LOG_DEBUG("dispatcher", "Ready.");
while (1) {
if (api_dispatcher_poll()) {
mutex_lock(&api_mutex);
api_dispatcher_exec();
mutex_unlock(&api_mutex);
}
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
}
#include "os/mutex.h"
#include "os/core.h"
#include "epicardium.h"
#include "api/interrupt-sender.h"
#include "user_core/user_core.h"
#include <assert.h>
struct interrupt_priv {
/* Whether this interrupt can be triggered */
bool int_enabled[EPIC_INT_NUM];
/* Whether this interrupt is waiting to be delivered */
bool int_pending[EPIC_INT_NUM];
/* Whether any interrupts are currently waiting to be triggered */
bool has_pending;
};
static struct interrupt_priv interrupt_data;
static struct mutex interrupt_mutex;
static TaskHandle_t interrupts_task;
void interrupt_trigger(api_int_id_t id)
{
assert(id < EPIC_INT_NUM);
mutex_lock(&interrupt_mutex);
if (interrupt_data.int_enabled[id]) {
interrupt_data.int_pending[id] = true;
interrupt_data.has_pending = true;
mutex_unlock(&interrupt_mutex);
xTaskNotifyGive(interrupts_task);
} else {
mutex_unlock(&interrupt_mutex);
}
}
void interrupt_trigger_sync(api_int_id_t id)
{
assert(id < EPIC_INT_NUM);
mutex_lock(&interrupt_mutex);
if (!interrupt_data.int_enabled[id])
goto out;
while (!api_interrupt_is_ready())
;
api_interrupt_trigger(id);
/* Break the dispatcher task out of a potential call
* to epic_sleep() */
xTaskNotifyGive(dispatcher_task_id);
out:
mutex_unlock(&interrupt_mutex);
}
/*
* This function solely exists because of that one use of interrupts that breaks
* the rules: The RTC ALARM interrupt is triggered from a hardware ISR where
* interrupt_trigger_sync() won't work because it needs to lock a mutex.
*
* DO NOT USE THIS FUNCTION IN ANY NEW CODE.
*/
void __attribute__((deprecated)) interrupt_trigger_unsafe(api_int_id_t id)
{
assert(id < EPIC_INT_NUM);
if (!interrupt_data.int_enabled[id])
return;
while (!api_interrupt_is_ready())
;
api_interrupt_trigger(id);
}
static void interrupt_set_enabled(api_int_id_t id, bool enabled)
{
assert(id < EPIC_INT_NUM);
mutex_lock(&interrupt_mutex);
interrupt_data.int_enabled[id] = enabled;
mutex_unlock(&interrupt_mutex);
}
static bool interrupt_get_enabled(api_int_id_t id)
{
assert(id < EPIC_INT_NUM);
bool enabled;
mutex_lock(&interrupt_mutex);
enabled = interrupt_data.int_enabled[id];
mutex_unlock(&interrupt_mutex);
return enabled;
}
void interrupt_init(void)
{
if (interrupt_mutex.name == NULL)
mutex_create(&interrupt_mutex);
api_interrupt_init();
/* Reset all irqs to disabled */
for (size_t i = 0; i < EPIC_INT_NUM; i++) {
interrupt_set_enabled(i, false);
}
/* Reset interrupt is always enabled */
interrupt_set_enabled(EPIC_INT_RESET, true);
}
/* Epic-calls {{{ */
int epic_interrupt_enable(api_int_id_t int_id)
{
if (int_id >= EPIC_INT_NUM) {
return -EINVAL;
}
interrupt_set_enabled(int_id, true);
return 0;
}
int epic_interrupt_disable(api_int_id_t int_id)
{
if (int_id >= EPIC_INT_NUM || int_id == EPIC_INT_RESET) {
return -EINVAL;
}
interrupt_set_enabled(int_id, false);
return 0;
}
int epic_interrupt_is_enabled(api_int_id_t int_id, bool *enabled)
{
if (int_id >= EPIC_INT_NUM) {
return -EINVAL;
}
*enabled = interrupt_get_enabled(int_id);
return 0;
}
/* }}} */
void vInterruptsTask(void *pvParameters)
{
interrupts_task = xTaskGetCurrentTaskHandle();
while (true) {
mutex_lock(&interrupt_mutex);
if (!interrupt_data.has_pending) {
/* Wait for a wakeup event from interrupt_trigger() */
mutex_unlock(&interrupt_mutex);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
mutex_lock(&interrupt_mutex);
}
while (!api_interrupt_is_ready()) {
mutex_unlock(&interrupt_mutex);
vTaskDelay(pdMS_TO_TICKS(5));
mutex_lock(&interrupt_mutex);
}
api_int_id_t current_irq = EPIC_INT_NUM;
for (size_t i = 0; i < EPIC_INT_NUM; i++) {
if (interrupt_data.int_pending[i]) {
current_irq = i;
interrupt_data.int_pending[i] = false;
break;
}
}
if (current_irq == EPIC_INT_NUM) {
interrupt_data.has_pending = false;
} else if (interrupt_data.int_enabled[current_irq]) {
api_interrupt_trigger(current_irq);
/* Break the dispatcher task out of a potential call
* to epic_sleep() */
xTaskNotifyGive(dispatcher_task_id);
}
mutex_unlock(&interrupt_mutex);
}
}