Skip to content

Commit

Permalink
Initial codegen.
Browse files Browse the repository at this point in the history
  • Loading branch information
tzarc committed Jul 30, 2021
1 parent 770a833 commit a3c2aa2
Show file tree
Hide file tree
Showing 10 changed files with 272 additions and 108 deletions.
16 changes: 14 additions & 2 deletions build_keyboard.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion common_features.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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
11 changes: 9 additions & 2 deletions data/xap/xap_0.0.1.hjson
Original file line number Diff line number Diff line change
@@ -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: [
Expand Down Expand Up @@ -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:
Expand Down
34 changes: 32 additions & 2 deletions data/xap/xap_0.1.0.hjson
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
version: 0.1.0

documentation: {
order: [
broadcast_messages
Expand Down Expand Up @@ -98,10 +100,11 @@
}
}

subsystems: {
routes: {
0x00: {
routes: {
0x01: {
type: command
name: Enabled subsystem query
define: SUBSYSTEM_QUERY
description:
Expand All @@ -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
}
}
}
}
}
}
3 changes: 2 additions & 1 deletion lib/python/qmk/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
]


Expand Down
Original file line number Diff line number Diff line change
@@ -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']
Expand Down
156 changes: 156 additions & 0 deletions lib/python/qmk/cli/xap/generate_qmk.py
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
'''

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)
Loading

0 comments on commit a3c2aa2

Please sign in to comment.