From a3c2aa238dd3470cf7ead817f22299a804caa2f0 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Mon, 12 Jul 2021 01:26:08 +1000 Subject: [PATCH] Initial codegen. --- build_keyboard.mk | 16 +- common_features.mk | 2 +- data/xap/xap_0.0.1.hjson | 11 +- data/xap/xap_0.1.0.hjson | 34 +++- lib/python/qmk/cli/__init__.py | 3 +- .../qmk/cli/xap/{docs.py => generate_docs.py} | 35 +--- lib/python/qmk/cli/xap/generate_qmk.py | 156 ++++++++++++++++++ quantum/xap/xap.c | 83 +++------- quantum/xap/xap.h | 10 +- quantum/xap/xap_handlers.c | 30 ++++ 10 files changed, 272 insertions(+), 108 deletions(-) rename lib/python/qmk/cli/xap/{docs.py => generate_docs.py} (73%) create mode 100755 lib/python/qmk/cli/xap/generate_qmk.py create mode 100755 quantum/xap/xap_handlers.c diff --git a/build_keyboard.mk b/build_keyboard.mk index 3a57852eb1e6..9317c52328a3 100644 --- a/build_keyboard.mk +++ b/build_keyboard.mk @@ -354,14 +354,26 @@ VPATH += $(COMMON_VPATH) include common_features.mk # XAP embedded info.json -ifeq ($(strip $(EMBED_INFO_JSON)), yes) +ifeq ($(strip $(XAP_ENABLE)), yes) + $(KEYMAP_OUTPUT)/src/info_json_xz.h: $(INFO_JSON_FILES) mkdir -p $(KEYMAP_OUTPUT)/src cat $(INFO_JSON_FILES) | xz -cz9e - > $(KEYMAP_OUTPUT)/src/info.json.xz cd $(KEYMAP_OUTPUT)/src \ && xxd -i info.json.xz info_json_xz.h \ && cd - -generated-files: $(KEYMAP_OUTPUT)/src/info_json_xz.h + +XAP_FILES = $(shell ls -1 data/xap/* | sort | xargs echo) +$(info $(XAP_FILES)) + +$(KEYMAP_OUTPUT)/src/xap_generated.inl: $(XAP_FILES) + $(QMK_BIN) xap-generate-qmk-inc -o "$(KEYMAP_OUTPUT)/src/xap_generated.inl" + +$(KEYMAP_OUTPUT)/src/xap_generated.h: $(XAP_FILES) + $(QMK_BIN) xap-generate-qmk-h -o "$(KEYMAP_OUTPUT)/src/xap_generated.h" + +generated-files: $(KEYMAP_OUTPUT)/src/info_json_xz.h $(KEYMAP_OUTPUT)/src/xap_generated.inl $(KEYMAP_OUTPUT)/src/xap_generated.h + VPATH += $(KEYMAP_OUTPUT)/src endif diff --git a/common_features.mk b/common_features.mk index 1479fb24280b..50755280994e 100644 --- a/common_features.mk +++ b/common_features.mk @@ -735,5 +735,5 @@ ifeq ($(strip $(XAP_ENABLE)), yes) DYNAMIC_KEYMAP_ENABLE := yes EMBED_INFO_JSON := yes VPATH += $(QUANTUM_DIR)/xap - SRC += $(QUANTUM_DIR)/xap/xap.c + SRC += $(QUANTUM_DIR)/xap/xap.c $(QUANTUM_DIR)/xap/xap_handlers.c endif diff --git a/data/xap/xap_0.0.1.hjson b/data/xap/xap_0.0.1.hjson index c65bac39546f..3325cdc18e16 100755 --- a/data/xap/xap_0.0.1.hjson +++ b/data/xap/xap_0.0.1.hjson @@ -1,4 +1,9 @@ { + version: 0.0.1 + + // Needed for table generation + define: SUBSYSTEM + // Documentation section is used purely for `qmk xap-generate-docs`. documentation: { order: [ @@ -140,16 +145,18 @@ } } - subsystems: { + routes: { 0x00: { + type: router name: XAP - define: XAP_SUBSYSTEM + define: XAP description: ''' This subsystem is always present, and provides the ability to query information about the XAP protocol of the connected device. ''' routes: { 0x00: { + type: command name: Version Query define: VERSION_QUERY description: diff --git a/data/xap/xap_0.1.0.hjson b/data/xap/xap_0.1.0.hjson index 41d275555d5e..976d205ef3d0 100755 --- a/data/xap/xap_0.1.0.hjson +++ b/data/xap/xap_0.1.0.hjson @@ -1,4 +1,6 @@ { + version: 0.1.0 + documentation: { order: [ broadcast_messages @@ -98,10 +100,11 @@ } } - subsystems: { + routes: { 0x00: { routes: { 0x01: { + type: command name: Enabled subsystem query define: SUBSYSTEM_QUERY description: @@ -111,6 +114,33 @@ always_present: true } } + }, + + 0x01: { + type: router + name: QMK + define: QMK + description: + ''' + This subsystem is always present, and provides the ability to address QMK-specific functionality. + ''' + routes: { + 0x00: { + type: command + name: Version Query + define: VERSION_QUERY + description: + ''' + QMK protocol version query. + + * Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ` + * e.g. 1.1.15 will match `0x01010015`. + * Response: + * `u32` value. + ''' + always_present: true + } + } } } -} \ No newline at end of file +} diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index ca2c3e842c51..03e3b18055ef 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -66,7 +66,8 @@ 'qmk.cli.new.keymap', 'qmk.cli.pyformat', 'qmk.cli.pytest', - 'qmk.cli.xap.docs', + 'qmk.cli.xap.generate_docs', + 'qmk.cli.xap.generate_qmk', ] diff --git a/lib/python/qmk/cli/xap/docs.py b/lib/python/qmk/cli/xap/generate_docs.py similarity index 73% rename from lib/python/qmk/cli/xap/docs.py rename to lib/python/qmk/cli/xap/generate_docs.py index 70c13ce71610..3e07a84e9e76 100755 --- a/lib/python/qmk/cli/xap/docs.py +++ b/lib/python/qmk/cli/xap/generate_docs.py @@ -1,43 +1,10 @@ """This script generates the XAP protocol documentation. """ -from typing import OrderedDict import hjson -from qmk.constants import QMK_FIRMWARE from milc import cli +from qmk.constants import QMK_FIRMWARE from qmk.xap import get_xap_definition_files, update_xap_definitions, latest_xap_defs -def _merge_ordered_dicts(dicts): - """Merges nested OrderedDict objects resulting from reading a hjson file. - - Later input dicts overrides earlier dicts for plain values. - Arrays will be appended. If the first entry of an array is "!reset!", the contents of the array will be cleared and replaced with RHS. - Dictionaries will be recursively merged. If any entry is "!reset!", the contents of the dictionary will be cleared and replaced with RHS. - """ - - result = OrderedDict() - - def add_entry(target, k, v): - if k in target and isinstance(v, OrderedDict): - if "!reset!" in v: - target[k] = v - else: - target[k] = _merge_ordered_dicts([target[k], v]) - if "!reset!" in target[k]: - del target[k]["!reset!"] - elif k in target and isinstance(v, list): - if v[0] == '!reset!': - target[k] = v[1:] - else: - target[k] = target[k] + v - else: - target[k] = v - - for d in dicts: - for (k,v) in d.items(): - add_entry(result, k, v) - - return result - def _update_type_docs(overall): defs = overall['type_docs'] diff --git a/lib/python/qmk/cli/xap/generate_qmk.py b/lib/python/qmk/cli/xap/generate_qmk.py new file mode 100755 index 000000000000..0d6fb1623e5e --- /dev/null +++ b/lib/python/qmk/cli/xap/generate_qmk.py @@ -0,0 +1,156 @@ +"""This script generates the XAP protocol generated source to be compiled into QMK. +""" +import hjson +import datetime + +from milc import cli +from qmk.path import normpath +from qmk.xap import latest_xap_defs + + +this_year = datetime.date.today().year +gpl_header = f'''\ +/* Copyright {this_year} QMK + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +''' + +generated = '''\ +/******************************************************************************* + +88888888888 888 d8b .d888 d8b 888 d8b + 888 888 Y8P d88P" Y8P 888 Y8P + 888 888 888 888 + 888 88888b. 888 .d8888b 888888 888 888 .d88b. 888 .d8888b + 888 888 "88b 888 88K 888 888 888 d8P Y8b 888 88K + 888 888 888 888 "Y8888b. 888 888 888 88888888 888 "Y8888b. + 888 888 888 888 X88 888 888 888 Y8b. 888 X88 + 888 888 888 888 88888P' 888 888 888 "Y8888 888 88888P' + + 888 888 + 888 888 + 888 888 + .d88b. .d88b. 88888b. .d88b. 888d888 8888b. 888888 .d88b. .d88888 + d88P"88b d8P Y8b 888 "88b d8P Y8b 888P" "88b 888 d8P Y8b d88" 888 + 888 888 88888888 888 888 88888888 888 .d888888 888 88888888 888 888 + Y88b 888 Y8b. 888 888 Y8b. 888 888 888 Y88b. Y8b. Y88b 888 + "Y88888 "Y8888 888 888 "Y8888 888 "Y888888 "Y888 "Y8888 "Y88888 + 888 + Y8b d88P + "Y88P" + +*******************************************************************************/ +''' + + +def _append_defines(lines, route_container, name_stack=[]): + name_stack.append(route_container['define'].upper()) + for route_id in route_container['routes']: + route = route_container['routes'][route_id] + name_stack.append(route['define'].upper()) + name_stack_str = ('_'.join(name_stack)).upper() + if route['type'] == 'router': + lines.append(f'#define {name_stack_str} {route_id}') + lines.append('') + _append_defines(lines, route, name_stack[:-1]) + lines.append('') + elif route['type'] == 'command': + lines.append(f'#define {name_stack_str} {route_id}') + name_stack.pop() + name_stack.pop() + + +def _append_routing_table(lines, route, name_stack=[]): + name_stack.append(route['define'].upper()) + name_stack_str = ('_'.join(name_stack)).lower() + lines.append(f'static const xap_route_t {name_stack_str}_routes[] = {{') + for child_id in route['routes']: + child = route['routes'][child_id] + name_stack.append(child['define'].upper()) + name_stack_str = ('_'.join(name_stack)).upper() + is_secure = 1 if ('secure' in child and child['secure'] == 'true') else 0 + if child['type'] == 'router': + lines.append(f' [{name_stack_str}] = {{ .flags = {{ .type = XAP_ROUTE, .is_secure = {is_secure} }}, .child_routes = {name_stack_str.lower()}_routes, .child_routes_len = sizeof({name_stack_str.lower()}_routes)/sizeof({name_stack_str.lower()}_routes[0]) }},') + elif child['type'] == 'command': + lines.append(f' [{name_stack_str}] = {{ .flags = {{ .type = XAP_EXECUTE, .is_secure = {is_secure} }}, .handler = &{name_stack_str.lower()}_handler }},') + name_stack.pop() + lines.append('};') + lines.append('') + name_stack.pop() + + +def _append_routing_tables(lines, route_container, name_stack=[]): + name_stack.append(route_container['define'].upper()) + for route_id in route_container['routes']: + route = route_container['routes'][route_id] + name_stack.append(route['define'].upper()) + name_stack_str = ('_'.join(name_stack)).upper() + if route['type'] == 'router': + _append_routing_tables(lines, route, name_stack[:-1]) + lines.append('') + elif route['type'] == 'command': + lines.append(f'extern bool {name_stack_str.lower()}_handler(xap_token_t token, const uint8_t *data, size_t data_len);') + lines.append('') + name_stack.pop() + name_stack.pop() + _append_routing_table(lines, route_container, name_stack) + + +@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') +@cli.subcommand('Generates the XAP protocol include.') +def xap_generate_qmk_inc(cli): + """Generates the XAP protocol inline codegen file, generated during normal build. + """ + xap_defs = latest_xap_defs() + + xap_generated_inl_lines = [gpl_header, generated] + + # Append the routing tables + _append_routing_tables(xap_generated_inl_lines, xap_defs) + + xap_generated_inl = '\n'.join(xap_generated_inl_lines) + + if cli.args.output: + if cli.args.output.name == '-': + print(xap_generated_inl) + else: + cli.args.output.parent.mkdir(parents=True, exist_ok=True) + if cli.args.output.exists(): + cli.args.output.replace(cli.args.output.parent / (cli.args.output.name + '.bak')) + cli.args.output.write_text(xap_generated_inl) + + +@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') +@cli.subcommand('Generates the XAP protocol include.') +def xap_generate_qmk_h(cli): + """Generates the XAP protocol header file, generated during normal build. + """ + xap_defs = latest_xap_defs() + + xap_generated_inl_lines = [gpl_header, generated] + + # Append the route and command defines + _append_defines(xap_generated_inl_lines, xap_defs) + + xap_generated_inl = '\n'.join(xap_generated_inl_lines) + + if cli.args.output: + if cli.args.output.name == '-': + print(xap_generated_inl) + else: + cli.args.output.parent.mkdir(parents=True, exist_ok=True) + if cli.args.output.exists(): + cli.args.output.replace(cli.args.output.parent / (cli.args.output.name + '.bak')) + cli.args.output.write_text(xap_generated_inl) diff --git a/quantum/xap/xap.c b/quantum/xap/xap.c index 6937b0f5b3f7..6a3c58064924 100644 --- a/quantum/xap/xap.c +++ b/quantum/xap/xap.c @@ -1,4 +1,4 @@ -/* Copyright 2021 QMK +/* Copyright 2021 Nick Brassel (@tzarc) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,108 +19,63 @@ #include typedef enum xap_route_type_t { - XAP_UNKNOWN = 0, + XAP_UNUSED = 0, // "Unused" needs to be zero -- undefined routes (through preprocessor) will be skipped XAP_ROUTE, XAP_EXECUTE, + TOTAL_XAP_ROUTE_TYPES } xap_route_type_t; -typedef struct xap_route_flags_t { - xap_route_type_t type : 2; +#define XAP_ROUTE_TYPE_BIT_COUNT 2 + +typedef struct __attribute__((packed)) xap_route_flags_t { + xap_route_type_t type : XAP_ROUTE_TYPE_BIT_COUNT; uint8_t is_secure : 1; } xap_route_flags_t; +_Static_assert(TOTAL_XAP_ROUTE_TYPES <= (1 << (XAP_ROUTE_TYPE_BIT_COUNT)), "Number of XAP route types is too large for XAP_ROUTE_TYPE_BITS."); _Static_assert(sizeof(xap_route_flags_t) == 1, "xap_route_flags_t is not length of 1"); -extern void xap_send_base(uint8_t *data, uint8_t length); - -#define XAP_SUBSYSTEM_XAP 0x00 -#define XAP_SUBSYSTEM_XAP_ROUTE_VERSION 0x00 - -#define XAP_SUBSYSTEM_QMK 0x01 -#define XAP_SUBSYSTEM_QMK_CAPABILITIES_QUERY 0x00 -#define XAP_SUBSYSTEM_QMK_ROUTE_VERSION 0x01 - -#define XAP_SUBSYSTEM_KEYBOARD 0x02 - -#define XAP_SUBSYSTEM_USER 0x03 - typedef struct xap_route_t xap_route_t; -struct xap_route_t { +struct __attribute__((packed)) xap_route_t { const xap_route_flags_t flags; union { struct { const xap_route_t *child_routes; const uint8_t child_routes_len; }; - void (*handler)(xap_token_t token, const uint8_t *data, size_t data_len); + bool (*handler)(xap_token_t token, const uint8_t *data, size_t data_len); }; }; -#ifdef CONSOLE_ENABLE -# define DUMP_XAP_DATA(name, token, data, len) \ - do { \ - dprintf("%s(%04X, ..., %d)%s", (#name), (int)(token), (int)(len), (len > 0) ? ":" : ""); \ - for (int i = 0; i < (len); ++i) { \ - dprintf(" %02X", (int)((data)[i])); \ - } \ - dprint("\n"); \ - } while (0) -#else -# define DUMP_XAP_DATA(name, token, data, len) \ - do { \ - } while (0) -#endif - -void xap_respond_failure(xap_token_t token) { xap_send(token, 0, NULL, 0); } - -void xap_route_version(xap_token_t token, const uint8_t *data, size_t data_len) {} -void qmk_route_version(xap_token_t token, const uint8_t *data, size_t data_len) { - DUMP_XAP_DATA(qmk_route_version, token, data, data_len); - uint32_t version = 0x12345678; - xap_send(token, XAP_RESPONSE_FLAG_SUCCESS, &version, sizeof(version)); -} -void qmk_caps_query(xap_token_t token, const uint8_t *data, size_t data_len) {} - -static const xap_route_t xap_routes[] = { - [XAP_SUBSYSTEM_XAP_ROUTE_VERSION] = {.flags = {.type = XAP_EXECUTE, .is_secure = 0}, .handler = &xap_route_version}, -}; - -static const xap_route_t qmk_routes[] = { - [XAP_SUBSYSTEM_QMK_CAPABILITIES_QUERY] = {.flags = {.type = XAP_EXECUTE, .is_secure = 0}, .handler = &qmk_caps_query}, - [XAP_SUBSYSTEM_QMK_ROUTE_VERSION] = {.flags = {.type = XAP_EXECUTE, .is_secure = 0}, .handler = &qmk_route_version}, -}; - -static const xap_route_t root_routes[] = { - [XAP_SUBSYSTEM_XAP] = {.flags = {.type = XAP_ROUTE, .is_secure = 0}, .child_routes = xap_routes, .child_routes_len = sizeof(xap_routes) / sizeof(xap_routes[0])}, - [XAP_SUBSYSTEM_QMK] = {.flags = {.type = XAP_ROUTE, .is_secure = 0}, .child_routes = qmk_routes, .child_routes_len = sizeof(qmk_routes) / sizeof(qmk_routes[0])}, -}; +#include void xap_execute_route(xap_token_t token, const xap_route_t *routes, size_t max_routes, const uint8_t *data, size_t data_len) { if (data_len == 0) return; - - DUMP_XAP_DATA(xap_execute_route, token, data, data_len); xap_identifier_t id = data[0]; if (id < max_routes) { const xap_route_t *route = &routes[id]; switch (route->flags.type) { case XAP_ROUTE: - if (route->child_routes != NULL && route->child_routes_len > 0) { + if (route->child_routes != NULL && route->child_routes_len > 0 && data_len > 0) { xap_execute_route(token, route->child_routes, route->child_routes_len, &data[1], data_len - 1); + return; } break; case XAP_EXECUTE: if (route->handler != NULL) { - (route->handler)(token, &data[1], data_len - 1); + bool ok = (route->handler)(token, data_len == 1 ? NULL : &data[1], data_len - 1); + if (ok) return; } break; default: - xap_respond_failure(token); break; } } + + // Nothing got handled, so we respond with failure. + xap_respond_failure(token, XAP_RESPONSE_FLAG_FAILED); } void xap_receive(xap_token_t token, const uint8_t *data, size_t length) { - DUMP_XAP_DATA(xap_receive, token, data, length); - xap_execute_route(token, root_routes, sizeof(root_routes) / sizeof(root_routes[0]), data, length); + xap_execute_route(token, subsystem_routes, sizeof(subsystem_routes) / sizeof(subsystem_routes[0]), data, length); } diff --git a/quantum/xap/xap.h b/quantum/xap/xap.h index dd23e1d0e27f..e3882bd6b08f 100644 --- a/quantum/xap/xap.h +++ b/quantum/xap/xap.h @@ -1,4 +1,4 @@ -/* Copyright 2021 QMK +/* Copyright 2021 Nick Brassel (@tzarc) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,8 +20,14 @@ #include typedef uint8_t xap_identifier_t; +typedef uint8_t xap_response_flags_t; typedef uint16_t xap_token_t; +#define XAP_RESPONSE_FLAG_FAILED 0 #define XAP_RESPONSE_FLAG_SUCCESS (1 << 0) -void xap_send(xap_token_t token, uint8_t response_flags, const void *data, size_t length); +void xap_respond_failure(xap_token_t token, xap_response_flags_t response_flags); + +void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void *data, size_t length); + +#include diff --git a/quantum/xap/xap_handlers.c b/quantum/xap/xap_handlers.c new file mode 100755 index 000000000000..824845decc75 --- /dev/null +++ b/quantum/xap/xap_handlers.c @@ -0,0 +1,30 @@ +/* Copyright 2021 Nick Brassel (@tzarc) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +void xap_respond_failure(xap_token_t token, xap_response_flags_t response_flags) { xap_send(token, response_flags, NULL, 0); } + +bool subsystem_xap_version_query_handler(xap_token_t token, const uint8_t *data, size_t data_len) { return false; } + +bool subsystem_xap_subsystem_query_handler(xap_token_t token, const uint8_t *data, size_t data_len) { return false; } + +bool subsystem_qmk_version_query_handler(xap_token_t token, const uint8_t *data, size_t data_len) { + uint32_t version = 0x12345678; + xap_send(token, XAP_RESPONSE_FLAG_SUCCESS, &version, sizeof(version)); + return true; +}