diff --git a/builddefs/build_keyboard.mk b/builddefs/build_keyboard.mk index acfad61e7d1..e53892cf500 100644 --- a/builddefs/build_keyboard.mk +++ b/builddefs/build_keyboard.mk @@ -187,9 +187,9 @@ include $(COMMUNITY_RULES_MK) ifneq ($(COMMUNITY_MODULES),) -$(INTERMEDIATE_OUTPUT)/src/community_config.h: $(KEYMAP_JSON) $(DD_CONFIG_FILES) +$(INTERMEDIATE_OUTPUT)/src/community_post_config.h: $(KEYMAP_JSON) $(DD_CONFIG_FILES) @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) - $(eval CMD=$(QMK_BIN) generate-community-config-h -kb $(KEYBOARD) --quiet --output $(INTERMEDIATE_OUTPUT)/src/community_config.h $(KEYMAP_JSON)) + $(eval CMD=$(QMK_BIN) generate-community-post-config-h -kb $(KEYBOARD) --quiet --output $(INTERMEDIATE_OUTPUT)/src/community_post_config.h $(KEYMAP_JSON)) @$(BUILD_CMD) $(INTERMEDIATE_OUTPUT)/src/community_modules.h: $(KEYMAP_JSON) $(DD_CONFIG_FILES) @@ -227,10 +227,10 @@ $(INTERMEDIATE_OUTPUT)/src/split_transaction_id_community_modules.inc: $(KEYMAP_ $(eval CMD=$(QMK_BIN) generate-split-transaction-id-community-modules-inc -kb $(KEYBOARD) --quiet --output $(INTERMEDIATE_OUTPUT)/src/split_transaction_id_community_modules.inc $(KEYMAP_JSON)) @$(BUILD_CMD) -COMMUNITY_CONFIG_H = $(INTERMEDIATE_OUTPUT)/src/community_config.h +COMMUNITY_POST_CONFIG_H = $(INTERMEDIATE_OUTPUT)/src/community_post_config.h SRC += $(INTERMEDIATE_OUTPUT)/src/community_modules.c -generated-files: $(INTERMEDIATE_OUTPUT)/src/community_config.h $(INTERMEDIATE_OUTPUT)/src/community_modules.h $(INTERMEDIATE_OUTPUT)/src/community_modules.c $(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.c $(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.h $(INTERMEDIATE_OUTPUT)/src/led_matrix_community_modules.inc $(INTERMEDIATE_OUTPUT)/src/rgb_matrix_community_modules.inc $(INTERMEDIATE_OUTPUT)/src/split_transaction_id_community_modules.inc +generated-files: $(INTERMEDIATE_OUTPUT)/src/community_post_config.h $(INTERMEDIATE_OUTPUT)/src/community_modules.h $(INTERMEDIATE_OUTPUT)/src/community_modules.c $(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.c $(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.h $(INTERMEDIATE_OUTPUT)/src/led_matrix_community_modules.inc $(INTERMEDIATE_OUTPUT)/src/rgb_matrix_community_modules.inc $(INTERMEDIATE_OUTPUT)/src/split_transaction_id_community_modules.inc endif @@ -331,10 +331,6 @@ define config_h_community_module_appender endef $(foreach module,$(COMMUNITY_MODULE_PATHS),$(eval $(call config_h_community_module_appender,$(module)))) -ifneq ($(COMMUNITY_CONFIG_H),) - CONFIG_H += $(COMMUNITY_CONFIG_H) -endif - ifneq ("$(wildcard $(KEYBOARD_PATH_5)/config.h)","") CONFIG_H += $(KEYBOARD_PATH_5)/config.h endif @@ -376,6 +372,10 @@ ifneq ("$(wildcard $(KEYBOARD_PATH_5)/post_config.h)","") POST_CONFIG_H += $(KEYBOARD_PATH_5)/post_config.h endif +ifneq ($(COMMUNITY_POST_CONFIG_H),) + POST_CONFIG_H += $(COMMUNITY_POST_CONFIG_H) +endif + CONFIG_H += $(INTERMEDIATE_OUTPUT)/src/info_config.h KEYBOARD_SRC += $(INTERMEDIATE_OUTPUT)/src/default_keyboard.c diff --git a/data/constants/module_hooks/1.1.3.hjson b/data/constants/module_hooks/1.1.3.hjson new file mode 100644 index 00000000000..61764a0af66 --- /dev/null +++ b/data/constants/module_hooks/1.1.3.hjson @@ -0,0 +1,3 @@ +{ + // This version exists to signify addition of eeconfig support. +} diff --git a/docs/features/community_modules.md b/docs/features/community_modules.md index 2b0086134bd..d38ee79460f 100644 --- a/docs/features/community_modules.md +++ b/docs/features/community_modules.md @@ -135,28 +135,45 @@ This file defines LED matrix effects in the same form as used with `led_matrix_k This file defines RGB matrix effects in the same form as used with `rgb_matrix_kb.inc` and `rgb_matrix_user.inc` (see [Custom RGB Matrix Effects](rgb_matrix#custom-rgb-matrix-effects)). Effect mode names are prepended with `RGB_MATRIX_COMMUNITY_MODULE_`. -### Custom split keyboard data sync +### Additional Customization -Defines follow the convention, `SPLIT_TRANSACTION_IDS_MODULE_` (see [Custom data sync](split_keyboard#custom-data-sync)). +#### Split Keyboard Data Sync + +Defines follow the convention, `SPLIT_TRANSACTION_IDS_MODULE_` (see [Custom data sync](split_keyboard#custom-data-sync)). + +#### Persistent Configuration + +Defines follow the convention, `EECONFIG_MODULE__DATA_SIZE` and `EECONFIG_MODULE__DATA_VERSION` (see [Custom Persistent Configuration](../feature_eeprom#datablock)). + +When configured, the following APIs are available: + +| API Format | Example (`hello_world` module) | API Version | +|--------------------------------------------|-----------------------------------------------|-------------| +| `eeconfig_is__datablock_valid` | `eeconfig_is_hello_world_datablock_valid` | `1.1.3` | +| `eeconfig_read__datablock` | `eeconfig_read_hello_world_datablock` | `1.1.3` | +| `eeconfig_update__datablock` | `eeconfig_update_hello_world_datablock` | `1.1.3` | +| `eeconfig_init__datablock` | `eeconfig_init_hello_world_datablock` | `1.1.3` | +| `eeconfig_read__datablock_field` | `eeconfig_read_hello_world_datablock_field` | `1.1.3` | +| `eeconfig_update__datablock_field` | `eeconfig_update_hello_world_datablock_field` | `1.1.3` | ### Compatible APIs Community Modules may provide specializations for the following APIs: -| Base API | API Format | Example (`hello_world` module) | API Version | -|----------------------------------|-------------------------------------------|---------------------------------------------|-------------| -| `keyboard_pre_init` | `keyboard_pre_init_` | `keyboard_pre_init_hello_world` | `0.1.0` | -| `keyboard_post_init` | `keyboard_post_init_` | `keyboard_post_init_hello_world` | `0.1.0` | -| `pre_process_record` | `pre_process_record_` | `pre_process_record_hello_world` | `0.1.0` | -| `process_record` | `process_record_` | `process_record_hello_world` | `0.1.0` | -| `post_process_record` | `post_process_record_` | `post_process_record_hello_world` | `0.1.0` | -| `housekeeping_task` | `housekeeping_task_` | `housekeeping_task_hello_world` | `1.0.0` | -| `suspend_power_down` | `suspend_power_down_` | `suspend_power_down_hello_world` | `1.0.0` | -| `suspend_wakeup_init` | `suspend_wakeup_init_` | `suspend_wakeup_init_hello_world` | `1.0.0` | -| `shutdown` | `shutdown_` | `shutdown_hello_world` | `1.0.0` | -| `process_detected_host_os` | `process_detected_host_os_` | `process_detected_host_os_hello_world` | `1.0.0` | -| `default_layer_state_set` | `default_layer_state_set_` | `default_layer_state_set_hello_world` | `1.1.0` | -| `layer_state_set` | `layer_state_set_` | `layer_state_set_hello_world` | `1.1.0` | +| Base API | API Format | Example (`hello_world` module) | API Version | +|----------------------------------|-------------------------------------------|----------------------------------------------|-------------| +| `keyboard_pre_init` | `keyboard_pre_init_` | `keyboard_pre_init_hello_world` | `0.1.0` | +| `keyboard_post_init` | `keyboard_post_init_` | `keyboard_post_init_hello_world` | `0.1.0` | +| `pre_process_record` | `pre_process_record_` | `pre_process_record_hello_world` | `0.1.0` | +| `process_record` | `process_record_` | `process_record_hello_world` | `0.1.0` | +| `post_process_record` | `post_process_record_` | `post_process_record_hello_world` | `0.1.0` | +| `housekeeping_task` | `housekeeping_task_` | `housekeeping_task_hello_world` | `1.0.0` | +| `suspend_power_down` | `suspend_power_down_` | `suspend_power_down_hello_world` | `1.0.0` | +| `suspend_wakeup_init` | `suspend_wakeup_init_` | `suspend_wakeup_init_hello_world` | `1.0.0` | +| `shutdown` | `shutdown_` | `shutdown_hello_world` | `1.0.0` | +| `process_detected_host_os` | `process_detected_host_os_` | `process_detected_host_os_hello_world` | `1.0.0` | +| `default_layer_state_set` | `default_layer_state_set_` | `default_layer_state_set_hello_world` | `1.1.0` | +| `layer_state_set` | `layer_state_set_` | `layer_state_set_hello_world` | `1.1.0` | | `led_matrix_indicators` | `led_matrix_indicators_` | `led_matrix_indicators_hello_world` | `1.1.0` | | `led_matrix_indicators_advanced` | `led_matrix_indicators_advanced_` | `led_matrix_indicators_advanced_hello_world` | `1.1.0` | | `rgb_matrix_indicators` | `rgb_matrix_indicators_` | `rgb_matrix_indicators_hello_world` | `1.1.0` | @@ -164,7 +181,6 @@ Community Modules may provide specializations for the following APIs: | `pointing_device_init` | `pointing_device_init_` | `pointing_device_init_hello_world` | `1.1.0` | | `pointing_device_task` | `pointing_device_task_` | `pointing_device_task_hello_world` | `1.1.0` | - ::: info An unspecified API is disregarded if a Community Module does not provide a specialization for it. ::: diff --git a/keyboards/handwired/onekey/keymaps/nvm_test/keymap.c b/keyboards/handwired/onekey/keymaps/nvm_test/keymap.c new file mode 100644 index 00000000000..777bf6b37de --- /dev/null +++ b/keyboards/handwired/onekey/keymaps/nvm_test/keymap.c @@ -0,0 +1,13 @@ +// Copyright 2026 QMK +// SPDX-License-Identifier: GPL-2.0-or-later +#include QMK_KEYBOARD_H + +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {LAYOUT_ortho_1x1(KC_A)}; + +void keyboard_post_init_user(void) { + // Customise these values to desired behaviour + debug_enable = true; + debug_matrix = true; + // debug_keyboard=true; + // debug_mouse=true; +} diff --git a/keyboards/handwired/onekey/keymaps/nvm_test/keymap.json b/keyboards/handwired/onekey/keymaps/nvm_test/keymap.json new file mode 100644 index 00000000000..5f328b3364e --- /dev/null +++ b/keyboards/handwired/onekey/keymaps/nvm_test/keymap.json @@ -0,0 +1,10 @@ +{ + "config": { + "features": { + "console": true + } + }, + "modules": [ + "qmk/nvm_test" + ] +} diff --git a/lib/python/qmk/cli/generate/community_modules.py b/lib/python/qmk/cli/generate/community_modules.py index 22bb7c0a130..ee7aae28bb0 100644 --- a/lib/python/qmk/cli/generate/community_modules.py +++ b/lib/python/qmk/cli/generate/community_modules.py @@ -169,6 +169,139 @@ def _generate_modules_rules(keyboard, filename): return lines +def _module_slugs(modules): + return [Path(m).name.lower() for m in modules] + + +def _render_eeconfig_declarations(modules): + lines = [] + lines.append('') + lines.append('// nvm eeconfig') + for module_slug in _module_slugs(modules): + lines.extend([ + f'#if (EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE) > 0', + f'bool eeconfig_is_{module_slug}_datablock_valid(void);', + f'uint32_t eeconfig_read_{module_slug}_datablock(void *data, uint32_t offset, uint32_t length) __attribute__((nonnull));', + f'uint32_t eeconfig_update_{module_slug}_datablock(const void *data, uint32_t offset, uint32_t length) __attribute__((nonnull));', + f'void eeconfig_init_{module_slug}_datablock(void);', + f'# define eeconfig_read_{module_slug}_datablock_field(__object, __field) eeconfig_read_{module_slug}_datablock(&(__object.__field), offsetof(typeof(__object), __field), sizeof(__object.__field))', + f'# define eeconfig_update_{module_slug}_datablock_field(__object, __field) eeconfig_update_{module_slug}_datablock(&(__object.__field), offsetof(typeof(__object), __field), sizeof(__object.__field))', + '', + f'bool nvm_eeconfig_is_{module_slug}_datablock_valid(void);', + f'uint32_t nvm_eeconfig_read_{module_slug}_datablock(void *data, uint32_t offset, uint32_t length);', + f'uint32_t nvm_eeconfig_update_{module_slug}_datablock(const void *data, uint32_t offset, uint32_t length);', + f'void nvm_eeconfig_init_{module_slug}_datablock(void);', + f'#endif // (EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE) > 0', + '', + ]) + + lines.append('typedef struct PACKED {') + for module_slug in _module_slugs(modules): + lines.extend([ + f'#if (EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE) > 0', + f' uint32_t {module_slug}_version;', + f' uint8_t {module_slug}[EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE];', + f'#endif // (EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE) > 0', + ]) + lines.append('} eeprom_modules_t;') + lines.append('') + + for module_slug in _module_slugs(modules): + lines.append(f'#define EECONFIG_MODULE_{module_slug.upper()}_VERSION (uint32_t *)(EECONFIG_MODULES_DATABLOCK + (offsetof(eeprom_modules_t, {module_slug}_version)))') + lines.append(f'#define EECONFIG_MODULE_{module_slug.upper()}_DATABLOCK (uint8_t *)(EECONFIG_MODULES_DATABLOCK + (offsetof(eeprom_modules_t, {module_slug})))') + lines.append('') + + lines.append('bool eeconfig_is_modules_datablock_valid(void);') + lines.append('void eeconfig_init_modules_datablock(void);') + lines.append('') + + return lines + + +def _render_eeconfig_implementation(modules): + lines = [] + + lines.append('') + lines.append('// nvm eeconfig') + lines.append('#if defined(NVM_DRIVER_EEPROM)'), + lines.append('# include "nvm_eeprom_eeconfig_internal.h"') + lines.append('# include "eeprom.h"') + lines.append('#endif // defined(NVM_DRIVER_EEPROM)'), + lines.append('') + + for module_slug in _module_slugs(modules): + lines.extend([ + f'#if (EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE) > 0', + f'bool eeconfig_is_{module_slug}_datablock_valid(void) {{ return nvm_eeconfig_is_{module_slug}_datablock_valid(); }}', + f'uint32_t eeconfig_read_{module_slug}_datablock(void *data, uint32_t offset, uint32_t length) {{ return nvm_eeconfig_read_{module_slug}_datablock(data, offset, length); }}', + f'uint32_t eeconfig_update_{module_slug}_datablock(const void *data, uint32_t offset, uint32_t length) {{ return nvm_eeconfig_update_{module_slug}_datablock(data, offset, length); }}', + f'void eeconfig_init_{module_slug}_datablock(void) {{ nvm_eeconfig_init_{module_slug}_datablock(); }}', + '', + '# if defined(NVM_DRIVER_EEPROM)', + f'bool nvm_eeconfig_is_{module_slug}_datablock_valid(void) {{', + f' return eeprom_read_dword(EECONFIG_MODULE_{module_slug.upper()}_VERSION) == (EECONFIG_MODULE_{module_slug.upper()}_DATA_VERSION);', + '}', + f'uint32_t nvm_eeconfig_read_{module_slug}_datablock(void *data, uint32_t offset, uint32_t length) {{', + f' if (eeconfig_is_{module_slug}_datablock_valid()) {{', + f' void *ee_start = (void *)(uintptr_t)(EECONFIG_MODULE_{module_slug.upper()}_DATABLOCK + offset);', + f' void *ee_end = (void *)(uintptr_t)(EECONFIG_MODULE_{module_slug.upper()}_DATABLOCK + MIN((EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE), offset + length));', + ' eeprom_read_block(data, ee_start, ee_end - ee_start);', + ' return ee_end - ee_start;', + ' } else {', + ' memset(data, 0, length);', + ' return length;', + ' }', + '}', + f'uint32_t nvm_eeconfig_update_{module_slug}_datablock(const void *data, uint32_t offset, uint32_t length) {{', + f' eeprom_update_dword(EECONFIG_MODULE_{module_slug.upper()}_VERSION, (EECONFIG_MODULE_{module_slug.upper()}_DATA_VERSION));', + f' void *ee_start = (void *)(uintptr_t)(EECONFIG_MODULE_{module_slug.upper()}_DATABLOCK + offset);', + f' void *ee_end = (void *)(uintptr_t)(EECONFIG_MODULE_{module_slug.upper()}_DATABLOCK + MIN((EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE), offset + length));', + ' eeprom_update_block(data, ee_start, ee_end - ee_start);', + ' return ee_end - ee_start;', + '}', + f'void nvm_eeconfig_init_{module_slug}_datablock(void) {{', + f' eeprom_update_dword(EECONFIG_MODULE_{module_slug.upper()}_VERSION, (EECONFIG_MODULE_{module_slug.upper()}_DATA_VERSION));', + f' void *start = (void *)(uintptr_t)(EECONFIG_MODULE_{module_slug.upper()}_DATABLOCK);', + f' void *end = (void *)(uintptr_t)(EECONFIG_MODULE_{module_slug.upper()}_DATABLOCK + (EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE));', + ' long remaining = end - start;', + ' uint8_t dummy[16] = {0};', + f' for (int i = 0; i < EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE; i += sizeof(dummy)) {{', + ' int this_loop = remaining < sizeof(dummy) ? remaining : sizeof(dummy);', + ' eeprom_update_block(dummy, start, this_loop);', + ' start += this_loop;', + ' remaining -= this_loop;', + ' }', + '}', + '# endif // defined(NVM_DRIVER_EEPROM)', + f'#endif // (EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE) > 0', + '', + ]) + + lines.append('bool eeconfig_is_modules_datablock_valid(void) {') + lines.append(' return true') + for module_slug in _module_slugs(modules): + lines.extend([ + f'#if (EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE) > 0', + f' && eeconfig_is_{module_slug}_datablock_valid()', + f'#endif // (EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE) > 0', + ]) + lines.append(' ;') + lines.append('}') + lines.append('') + + lines.append('void eeconfig_init_modules_datablock(void) {'), + for module_slug in _module_slugs(modules): + lines.extend([ + f'#if (EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE) > 0', + f' eeconfig_init_{module_slug}_datablock();', + f'#endif // (EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE) > 0', + ]) + lines.append('}') + lines.append('') + + return lines + + @cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") @cli.argument('-e', '--escape', arg_only=True, action='store_true', help="Escape spaces in quiet mode") @@ -196,11 +329,11 @@ def generate_community_modules_rules_mk(cli): @cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") -@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate community_config.h for.') +@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate community_post_config.h for.') @cli.argument('filename', nargs='?', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.json'), help='Configurator JSON file') -@cli.subcommand('Creates a community_config.h from a keymap.json file.') -def generate_community_config_h(cli): - """Creates a community_config.h from a keymap.json file +@cli.subcommand('Creates a community_post_config.h from a keymap.json file.') +def generate_community_post_config_h(cli): + """Creates a community_post_config.h from a keymap.json file """ if cli.args.output and cli.args.output.name == '-': cli.args.output = None @@ -215,14 +348,29 @@ def generate_community_config_h(cli): modules = get_modules(cli.args.keyboard, cli.args.filename) if len(modules) > 0: lines.append('// Split transactions') - for module in modules: + for module_slug in _module_slugs(modules): lines.extend([ - f'#ifdef SPLIT_TRANSACTION_IDS_MODULE_{Path(module).name.upper()}', + f'#ifdef SPLIT_TRANSACTION_IDS_MODULE_{module_slug.upper()}', '# define SPLIT_TRANSACTION_RPC', '#endif', ]) lines.append('') + lines.append('// nvm eeconfig') + for module_slug in _module_slugs(modules): + lines.extend([ + f'#ifndef EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE', + f'# define EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE 0', + '#endif', + f'#ifndef EECONFIG_MODULE_{module_slug.upper()}_DATA_VERSION', + f'# define EECONFIG_MODULE_{module_slug.upper()}_DATA_VERSION (EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE)', + '#endif', + '', + ]) + module_size = " + ".join([f'(4 + (EECONFIG_MODULE_{module_slug.upper()}_DATA_SIZE))' for module_slug in _module_slugs(modules)]) + lines.append(f'#define EECONFIG_MODULE_DATA_SIZE ({module_size})') + lines.append('') + dump_lines(cli.args.output, lines, cli.args.quiet, remove_repeated_newlines=True) @@ -245,6 +393,7 @@ def generate_community_modules_h(cli): '#pragma once', '#include ', '#include ', + '#include ', '#include ', '', '#include "compiler_support.h"', @@ -272,6 +421,8 @@ def generate_community_modules_h(cli): lines.extend(_render_api_declarations(api, Path(module).name)) lines.append('') + lines.extend(_render_eeconfig_declarations(modules)) + lines.append('// Core wrapper') for api in api_list: lines.extend(_render_api_declarations(api, 'modules', user_kb=False)) @@ -309,6 +460,8 @@ def generate_community_modules_c(cli): for api in api_list: lines.extend(_render_core_implementation(api, modules)) + lines.extend(_render_eeconfig_implementation(modules)) + dump_lines(cli.args.output, lines, cli.args.quiet, remove_repeated_newlines=True) diff --git a/modules/qmk/nvm_test/config.h b/modules/qmk/nvm_test/config.h new file mode 100644 index 00000000000..1d98b69e7be --- /dev/null +++ b/modules/qmk/nvm_test/config.h @@ -0,0 +1,5 @@ +// Copyright 2026 QMK +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#define EECONFIG_MODULE_NVM_TEST_DATA_SIZE 4 diff --git a/modules/qmk/nvm_test/nvm_test.c b/modules/qmk/nvm_test/nvm_test.c new file mode 100644 index 00000000000..e35790fc8ff --- /dev/null +++ b/modules/qmk/nvm_test/nvm_test.c @@ -0,0 +1,53 @@ +// Copyright 2026 QMK +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include "community_modules.h" +#include "debug.h" +#include "timer.h" +#include "eeconfig.h" + +ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(1, 1, 3); + +typedef struct my_config_t { + uint32_t data; +} my_config_t; + +static my_config_t config; + +// Helpers required to bind to debounce helper +void eeconfig_read_my_config(my_config_t *value) { + eeconfig_read_nvm_test_datablock(value, 0, sizeof(my_config_t)); +} +void eeconfig_update_my_config(my_config_t *value) { + eeconfig_update_nvm_test_datablock(value, 0, sizeof(my_config_t)); +} + +EECONFIG_DEBOUNCE_HELPER(my_config, config); + +void keyboard_post_init_nvm_test(void) { + if (!eeconfig_is_nvm_test_datablock_valid()) { + eeconfig_init_nvm_test_datablock(); + } + + eeconfig_init_my_config(); +} + +bool process_record_nvm_test(uint16_t keycode, keyrecord_t *record) { + if (!record->event.pressed) { + config.data += 1; + eeconfig_flag_my_config(true); + } + + return true; +} + +void housekeeping_task_nvm_test(void) { + eeconfig_flush_my_config(false); + + static uint32_t last_sync = 0; + if (timer_elapsed32(last_sync) > 1000) { + last_sync = timer_read32(); + + dprintf("Config: %ld\n", config.data); + } +} diff --git a/modules/qmk/nvm_test/qmk_module.json b/modules/qmk/nvm_test/qmk_module.json new file mode 100644 index 00000000000..26146f6d075 --- /dev/null +++ b/modules/qmk/nvm_test/qmk_module.json @@ -0,0 +1,5 @@ +{ + "module_name": "Example nvm persistence", + "maintainer": "QMK Maintainers", + "license": "GPL-2.0-or-later" +} diff --git a/quantum/eeconfig.c b/quantum/eeconfig.c index f14e6ddf97c..6855de654c3 100644 --- a/quantum/eeconfig.c +++ b/quantum/eeconfig.c @@ -39,6 +39,10 @@ # include "connection.h" #endif // CONNECTION_ENABLE +#ifdef COMMUNITY_MODULES_ENABLE +# include "community_modules.h" +#endif + #ifdef VIA_ENABLE bool via_eeprom_is_valid(void); void via_eeprom_set_valid(bool valid); @@ -148,6 +152,10 @@ void eeconfig_init_quantum(void) { eeconfig_init_user_datablock(); #endif // (EECONFIG_USER_DATA_SIZE) > 0 +#ifdef COMMUNITY_MODULES_ENABLE + eeconfig_init_modules_datablock(); +#endif // COMMUNITY_MODULES_ENABLE + #if defined(VIA_ENABLE) // Invalidate VIA eeprom config, and then reset. // Just in case if power is lost mid init, this makes sure that it gets diff --git a/quantum/nvm/eeprom/nvm_eeprom_eeconfig_internal.h b/quantum/nvm/eeprom/nvm_eeprom_eeconfig_internal.h index 78b8190eaf9..1bac5f6366b 100644 --- a/quantum/nvm/eeprom/nvm_eeprom_eeconfig_internal.h +++ b/quantum/nvm/eeprom/nvm_eeprom_eeconfig_internal.h @@ -56,8 +56,14 @@ typedef struct PACKED { #define EECONFIG_KB_DATABLOCK ((uint8_t *)(EECONFIG_BASE_SIZE)) #define EECONFIG_USER_DATABLOCK ((uint8_t *)((EECONFIG_BASE_SIZE) + (EECONFIG_KB_DATA_SIZE))) +#define EECONFIG_MODULES_DATABLOCK ((uint8_t *)((EECONFIG_BASE_SIZE) + (EECONFIG_KB_DATA_SIZE) + (EECONFIG_USER_DATA_SIZE))) + +// Actual value is injected via the generated community_post_config.h +#ifndef EECONFIG_MODULE_DATA_SIZE +# define EECONFIG_MODULE_DATA_SIZE 0 +#endif // Size of EEPROM being used, other code can refer to this for available EEPROM -#define EECONFIG_SIZE ((EECONFIG_BASE_SIZE) + (EECONFIG_KB_DATA_SIZE) + (EECONFIG_USER_DATA_SIZE)) +#define EECONFIG_SIZE ((EECONFIG_BASE_SIZE) + (EECONFIG_KB_DATA_SIZE) + (EECONFIG_USER_DATA_SIZE) + (EECONFIG_MODULE_DATA_SIZE)) STATIC_ASSERT((intptr_t)EECONFIG_HANDEDNESS == 14, "EEPROM handedness offset is incorrect");