ergodox-firmware/firmware/lib/layout/eeprom-macro/atmega32u4.c

526 lines
18 KiB
C

/* ----------------------------------------------------------------------------
* Copyright (c) 2013 Ben Blazak <benblazak.dev@gmail.com>
* Released under The MIT License (see "doc/licenses/MIT.md")
* Project located at <https://github.com/benblazak/ergodox-firmware>
* ------------------------------------------------------------------------- */
/** description
* Implements the eeprom-macro functionality defined in "../eeprom-macro.h" for
* the ATMega32U4
*
*
* Implementation notes:
*
* - The default state (the "erased" state) of this EEPROM is all `1`s, which
* makes setting a byte to `0xFF` easier and faster in hardware than zeroing
* it. This is reflected in some of our choices for default values, and
* such.
*
* - GCC and AVR processors (and Intel processors, for that matter) are
* primarily little endian: in avr-gcc, multi-byte data types are allocated
* with the least significant byte occupying the lowest address. Protocols,
* data formats (including UTF-8), and such are primarily big endian. I like
* little endianness better -- it feels more mathematically consistent to me
* -- but after writing a bit of code, it seems that while writing is easier
* to do as little endian, reading is easier to do as big endian; and since
* we'll be reading much more often than writing, big endian seems like the
* logical choice. For that reason, this code organizes bytes in a big
* endian manner whenever it has a choice between the two.
*
* - For a long time, I was going to try to make this library robust in the
* event of power loss, but in the end I decided not to. This feature is
* meant to be used for *temporary* macros - so, with the risk of power loss
* during a critical time being fairly low, and the consequence of (detected)
* data corruption hopefully more of an annoyance than anything else, I
* decided the effort (and extra EEMEM usage) wasn't worth it.
*/
#include <stddef.h>
#include <stdint.h>
#include "../../../../firmware/keyboard.h"
#include "../../../../firmware/lib/eeprom.h"
#include "../eeprom-macro.h"
// ----------------------------------------------------------------------------
// checks ---------------------------------------------------------------------
/** macros/OPT__EEPROM__EEPROM_MACRO__END/description
* Implementation notes:
* - The ATMega32U4 only has 1024 bytes of EEPROM (beginning with byte 0)
*/
#if OPT__EEPROM__EEPROM_MACRO__END > 1023
#error "OPT__EEPROM__EEPROM_MACRO__END must not be greater than 1023"
#endif
// ----------------------------------------------------------------------------
// macros ---------------------------------------------------------------------
/** macros/VERSION/description
* The version number of the EEMEM layout
*
* Assignments:
* - 0x00: Reserved: EEPROM not yet initialized, or in inconsistent state
* - 0x01: First version
* - ... : (not yet assigned)
* - 0xFF: Reserved: EEPROM not yet initialized, or in inconsistent state
*/
#define VERSION 0x01
/** macros/(group) EEMEM layout/description
* To define the layout of our section of the EEPROM
*
* Members:
* - `EEMEM_START`: The address of the first byte of our block of EEMEM
* - `EEMEM_START_ADDRESS_START`
* - `EEMEM_START_ADDRESS_END`
* - `EEMEM_END_ADDRESS_START`
* - `EEMEM_END_ADDRESS_END`
* - `EEMEM_VERSION_START`
* - `EEMEM_VERSION_END`
* - `EEMEM_MACROS_START`
* - `EEMEM_MACROS_END`
* - `EEMEM_END`: The address of the last byte of our block of EEMEM
*
* EEMEM sections:
* - START_ADDRESS:
* - byte 0: MSB of `EEMEM_START`
* - byte 1: LSB of `EEMEM_START`
* - END_ADDRESS:
* - byte 0: MSB of `EEMEM_END`
* - byte 1: LSB of `EEMEM_END`
* - VERSION:
* - This byte will all be set to `VERSION` as the last step of
* initializing our portion of the EEPROM.
* - Upon initialization, if this value is not equal to the current
* `VERSION`, our portion of the EEPROM should be reinitialized.
* - MACROS:
* - byte 0..`(EEMEM_END - EEMEM_VERSION_END - 1)`: TODO
*
* Notes:
* - `START_ADDRESS` and `END_ADDRESS` are written as part of our effort to
* make sure that the assumptions in place when writing the data don't shift
* (undetected) by the time it gets read. Either of these values could
* change, legitimately, without `VERSION` being incremented, but it's
* important that any two builds of the firmware that deal with this section
* of the EEPROM have the same values for each.
*/
#define EEMEM_START ((void *)OPT__EEPROM__EEPROM_MACRO__START)
#define EEMEM_START_ADDRESS_START EEMEM_START
#define EEMEM_START_ADDRESS_END EEMEM_START_ADDRESS_START + 1
#define EEMEM_END_ADDRESS_START EEMEM_START_ADDRESS_END + 1
#define EEMEM_END_ADDRESS_END EEMEM_END_ADDRESS_START + 1
#define EEMEM_VERSION_START EEMEM_END_ADDRESS_END + 1
#define EEMEM_VERSION_END EEMEM_VERSION_START + 0
#define EEMEM_MACROS_START EEMEM_VERSION_END + 1
#define EEMEM_MACROS_END EEMEM_END
#define EEMEM_END ((void *)OPT__EEPROM__EEPROM_MACRO__END)
/** macros/(group) type/description
* Aliases for valid values of the "type" field in `MACROS`
*
* Members:
* - `TYPE_DELETED`
* - `TYPE_VALID_MACRO`
* - `TYPE_END`
*/
#define TYPE_DELETED 0x00
#define TYPE_VALID_MACRO 0x01
#define TYPE_END 0xFF
// ----------------------------------------------------------------------------
// types ----------------------------------------------------------------------
/** types/key_action_t/description
* TODO
*/
typedef struct {
bool pressed;
uint8_t layer;
uint8_t row;
uint8_t column;
} key_action_t;
// ----------------------------------------------------------------------------
// local functions ------------------------------------------------------------
// - if we go back to little endian, need to modify the comments at the top of
// the file, as well as the documented order of the bytes for the stored
// start and end addresses of the EEPROM
//
// - sizes (in bytes) (with optimizations on):
//
// function frame stack
// read big 154 3 10
// read little 200 3 12
// write big 208 0 9
// write little 172 0 5
/**
* TODO
*/
key_action_t read_key_action_big_endian(void * from) {
uint8_t byte = eeprom__read(from++);
key_action_t k = {
.pressed = byte >> 6 & 0b01,
.layer = byte >> 4 & 0b11,
.row = byte >> 2 & 0b11,
.column = byte >> 0 & 0b11,
};
while (byte >> 7) {
byte = eeprom__read(from++);
k.layer <<= 2;
k.row <<= 2;
k.column <<= 2;
k.layer |= byte >> 4 & 0b11;
k.row |= byte >> 2 & 0b11;
k.column |= byte >> 0 & 0b11;
}
return k; // success
}
key_action_t read_key_action_little_endian(void * from) {
uint8_t byte = eeprom__read(from++);
key_action_t k = {
.pressed = byte >> 6 & 0b01,
.layer = byte >> 4 & 0b11,
.row = byte >> 2 & 0b11,
.column = byte >> 0 & 0b11,
};
for (uint8_t i=2; i<8 && byte>>7; i+=2) {
byte = eeprom__read(from++);
k.layer |= ( byte >> 4 & 0b11 ) << i;
k.row |= ( byte >> 2 & 0b11 ) << i;
k.column |= ( byte >> 0 & 0b11 ) << i;
}
return k; // success
}
/**
* TODO
*/
uint8_t write_key_action_big_endian(void * to, key_action_t k) {
if (to > EEMEM_END-3)
return 1; // error: might not be enough space
int8_t i = 0;
uint8_t byte;
byte = k.layer | k.row | k.column;
while (byte >>= 2)
i += 2;
byte = (k.pressed ? 1 : 0) << 6;
for (; i>=0; i-=2) {
byte = byte | ( i>0 ? 1 : 0 ) << 7
| ( k.layer >> i & 0b11 ) << 4
| ( k.row >> i & 0b11 ) << 2
| ( k.column >> i & 0b11 ) << 0 ;
eeprom__write(to++, byte);
byte = 0;
}
return 0; // success
}
uint8_t write_key_action_little_endian(void * to, key_action_t k) {
if (to > EEMEM_END-3)
return 1; // error: might not be enough space
uint8_t byte = ( k.pressed ? 1 : 0 ) << 6
| ( k.layer & 0b11 ) << 4
| ( k.row & 0b11 ) << 2
| ( k.column & 0b11 ) << 0 ;
k.layer >>= 2;
k.row >>= 2;
k.column >>= 2;
while (k.layer|k.row|k.column) {
byte |= 1 << 7;
eeprom__write(to++, byte);
byte = ( k.layer & 0b11 ) << 4
| ( k.row & 0b11 ) << 2
| ( k.column & 0b11 ) << 0 ;
k.layer >>= 2;
k.row >>= 2;
k.column >>= 2;
}
eeprom__write(to, byte);
return 0; // success
}
// TODO: rewriting (yet again) - stopped here
#if 0
// ----------------------------------------------------------------------------
// variables in EEMEM ---------------------------------------------------------
/** variables/eeprom/description
* The layout of this library's data in the EEPROM
*
* Struct members:
* - `meta`: For keeping track of layout metadata
* - `version`: The version of this layout (`[8]` for fault tolerance and
* write balancing)
* - `table`: To help in quickly returning if the UID we're searching for does
* not exist
* - `rows`: The number of rows this table has
* - `columns`: The number of columns this table has
* - `data`:
* - For any `eeprom_macro__uid_t uid`
* - If `uid.layer > 7`, this table doesn't tell whether a macro
* exists for the UID or not
* - Otherwise, `! ( (eeprom.table.data[uid.row][uid.column]
* >> uid.layer) & 1 )` indicates whether a macro exists with the
* given UID (`true`) or not (`false`)
* - Note that the expression above will return `true` if
* `uid.layer > 7`
* - Note that the expression above implies that we are using `1`
* bits for `false`
* - `macros`: To hold a block of memory for storing macros
* - `length`: The number of bytes allocated to `macros.data`
* - `data`: A (non-padded) list of macros, where a macro (in EEMEM) is
* - 1 byte: `type`
* - as defined in `enum type`
* - 1 byte: `length`
* - the number of bytes in the entire macro (i.e. the number of
* bytes to skip over, if one had a pointer to the `type` byte of
* this macro, in order to reach the `type` byte of the next
* macro)
* - (variable length): `key_action`
* - as defined in `read_key_action()`
* - this is the key action that is being remapped
* - (list of 0 or more)
* - (variable length): `key_action`
* - as defined in `read_key_action()`
* - these are the key actions to be performed instead of the
* one being remapped
*
*
* Notes:
*
* - The struct must be `packed` and `aligned(1)`, or we risk allocating more
* than `OPT__EEPROM_MACRO__EEPROM_SIZE` bytes. This should be the default
* when compiling with `avr-gcc`, but it's important to emphasize that we
* depend on it.
*
* - We keep track of `table.rows`, `table.columns`, and `macros.length`, in
* addition to `header.version`, because they all effect the precise layout
* of the persistent data; if any of them is different, special handling is
* required at the least, and usually the stored data will be unusable.
*/
struct eeprom {
struct meta {
uint8_t version[8];
} meta;
struct table {
uint8_t rows;
uint8_t columns;
uint8_t data[OPT__KB__ROWS][OPT__KB__COLUMNS];
} table;
struct macros {
uint16_t length;
uint8_t data[MACROS_LENGTH];
} macros;
} __attribute__((packed, aligned(1))) eeprom EEMEM;
// ----------------------------------------------------------------------------
// variables in SRAM ----------------------------------------------------------
static void * current_macro;
uint8_t current_macro_length;
// ----------------------------------------------------------------------------
// TODO:
//
// - the calling function need not ignore layer shift keys, or any other keys.
//
// - use little endian order for the multi-byte encoding. explain why (since
// utf-8 is big endian)
//
// - 255 bytes (so, on average, about 100 keystrokes = 200 key actions) should
// be enough for a macro, i think. `length` can be 1 byte, and count the
// total number of bytes (including `type` and `length`, and anything else)
//
// - need to write a function to read, and another to write, multi-byte key
// actions
//
// - so for now, we have
// - macro = `type` `length` uid key_action*
// - uid = key_action
// - key_action =
// - 1 bit : are the fields in this byte extended by the next?
// - 1 bit : `pressed`
// - 2 bits : `layer`
// - 2 bits : `row`
// - 2 bits : `column`
//
// - need to write `kb__layout__exec_key_layer()` (or something)
//
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// local functions ------------------------------------------------------------
/** functions/find_uid/description
* Find the macro with the given `uid`
*
* Arguments:
* - `uid`: The UID of the macro we're trying to find
*
* Returns:
* - success: The EEMEM address of the beginning of the macro
* - failure: `NULL`
*
* Notes:
* - `NULL` is `#define`ed to `((void *)0)`, which for the EEPROM is a valid
* memory address; but because `struct eeprom` does not place
* `eeprom.macros.data` first in memory, `NULL` is guaranteed to be before
* the beginning of that array, and is therefore usable as a signal that the
* macro we're looking for does not exist.
*
* Implementation notes:
* - Using `memcmp()` to compare two structs is bad practice in general (what
* if there's uninitialized padding in the struct?); but I'm doing it here
* because we're already trusting the binary layout of the struct when we
* store and retrieve it from EEMEM, and it saves writing another function
* just for that.
* - It should not be strictly necessary for us to check whether we're
* iterating over the bounds of `eeprom.macros.data`, since the list of
* macros is supposed to be terminated in a well defined way. But we may as
* well, just to be safer :)
*/
static void * find_uid(eeprom_macro__uid_t uid) {
// if `eeprom.table.data` indicates that the macro does not exist
if ( (eeprom.table.data[uid.row][uid.column] >> uid.layer) & 1 )
return NULL;
// otherwise the macro may exist: we must search for it
for ( uint8_t * p = &eeprom.macros.data[0];
p < &eeprom.macros.data[MACROS_LENGTH-3]; ) {
header_t header;
eeprom__block_read(&header, p, sizeof(header));
switch (header.type) {
case HEADER_TYPE_VALID:
if ( ! memcmp(&uid, &header.uid, sizeof(uid)) )
return p;
// (do not break)
case HEADER_TYPE_DELETED:
p += sizeof(header_t) + header.length * sizeof(action_t);
break;
// `HEADER_TYPE_END` or invalid value
default:
// (no more macros to search)
return NULL;
}
}
// macro does not exist
return NULL;
}
/** functions/find_next_deleted/description
* Find the first deleted macro at or after the macro at the given position
*
* Arguments:
* - `start`: The address (in EEMEM) of the first byte of the header of the
* macro at which to begin searching
*
* Returns:
* - success: The address (in EEMEM) of the of the beginning of the first
* deleted macro found at or after `start`
* - failure: `NULL` (no deleted macros found)
*/
static void * find_next_deleted(void * start) {
for ( uint8_t * p = start;
p < &eeprom.macros.data[MACROS_LENGTH-3]; ) {
uint8_t type = eeprom__read(p);
uint8_t length = eeprom__read(p+1);
switch (type) {
case HEADER_TYPE_VALID:
p += sizeof(header_t) + length * sizeof(action_t);
break;
case HEADER_TYPE_DELETED:
return p;
// `HEADER_TYPE_END` or invalid value
default:
// (no more macros to search)
return NULL;
}
}
// no deleted macros found
return NULL;
}
/** functions/compress/description
* Compress `macros.data`
*
* Shift all macros towards index `0`, overwriting the areas previously
* occupied by deleted macros.
*/
static void compress(void) { return;
// TODO: this whole thing... just starting.
uint8_t * current_deleted = find_next_deleted(&eeprom.macros.data[0]);
uint8_t * next_deleted = find_next_deleted(current_deleted);
if (! next_deleted) next_deleted = macros_free_begin;
}
// ----------------------------------------------------------------------------
// public functions -----------------------------------------------------------
#endif // 0
uint8_t eeprom_macro__init(void) { return 0; }
#if 0
uint8_t eeprom_macro__record_init(void) { return 0;
}
uint8_t eeprom_macro__record_keystroke( bool pressed,
uint8_t row,
uint8_t column ) { return 0;
}
uint8_t eeprom_macro__record_finalize(eeprom_macro__uid_t index) { return 0;
}
uint8_t eeprom_macro__exists(eeprom_macro__uid_t index) {
return (bool) find_uid(index);
}
uint8_t eeprom_macro__play(eeprom_macro__uid_t index) { return 0;
}
void eeprom_macro__clear(eeprom_macro__uid_t index) { return;
}
void eeprom_macro__clear_all(void) { return;
}
#endif // 0