header_generator.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. """This script generates the XAP protocol generated header to be compiled into QMK.
  2. """
  3. import re
  4. from fnvhash import fnv1a_32
  5. from qmk.casing import to_snake
  6. from qmk.commands import dump_lines
  7. from qmk.git import git_get_version
  8. from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
  9. from qmk.xap.common import merge_xap_defs, route_conditions
  10. def _get_c_type(xap_type):
  11. if xap_type == 'bool':
  12. return 'bool'
  13. elif xap_type == 'u8':
  14. return 'uint8_t'
  15. elif xap_type == 'u16':
  16. return 'uint16_t'
  17. elif xap_type == 'u32':
  18. return 'uint32_t'
  19. elif xap_type == 'u64':
  20. return 'uint64_t'
  21. elif xap_type == 'struct':
  22. return 'struct'
  23. elif xap_type == 'string':
  24. return 'const char *'
  25. return 'unknown'
  26. def _append_route_defines(lines, container, container_id=None, route_stack=None):
  27. """Handles building the list of the XAP routes, combining parent and child names together, as well as the route number.
  28. """
  29. if route_stack is None:
  30. route_stack = [container]
  31. else:
  32. route_stack.append(container)
  33. route_name = '_'.join([r['define'] for r in route_stack])
  34. if container_id:
  35. lines.append(f'#define {route_name} {container_id}')
  36. if 'routes' in container:
  37. for route_id in container['routes']:
  38. route = container['routes'][route_id]
  39. _append_route_defines(lines, route, route_id, route_stack)
  40. route_stack.pop()
  41. def _append_route_masks(lines, container, container_id=None, route_stack=None):
  42. """Handles creating the equivalent XAP route masks, for capabilities checks. Forces value of `0` if disabled in the firmware.
  43. """
  44. if route_stack is None:
  45. route_stack = [container]
  46. else:
  47. route_stack.append(container)
  48. route_name = '_'.join([r['define'] for r in route_stack])
  49. condition = route_conditions(route_stack)
  50. if container_id:
  51. if condition:
  52. lines.append('')
  53. lines.append(f'#if {condition}')
  54. lines.append(f'#define {route_name}_MASK (1ul << ({route_name}))')
  55. if condition:
  56. lines.append(f'#else // {condition}')
  57. lines.append(f'#define {route_name}_MASK 0')
  58. lines.append(f'#endif // {condition}')
  59. lines.append('')
  60. if 'routes' in container:
  61. for route_id in container['routes']:
  62. route = container['routes'][route_id]
  63. _append_route_masks(lines, route, route_id, route_stack)
  64. route_stack.pop()
  65. def _append_route_capabilities(lines, container, container_id=None, route_stack=None):
  66. """Handles creating the equivalent XAP route masks, for capabilities checks. Forces value of `0` if disabled in the firmware.
  67. """
  68. if route_stack is None:
  69. route_stack = [container]
  70. else:
  71. route_stack.append(container)
  72. route_name = '_'.join([r['define'] for r in route_stack])
  73. if 'routes' in container:
  74. lines.append('')
  75. lines.append(f'#define {route_name}_CAPABILITIES (0 \\')
  76. if 'routes' in container:
  77. for route_id in container['routes']:
  78. route = container['routes'][route_id]
  79. route_stack.append(route)
  80. child_name = '_'.join([r['define'] for r in route_stack])
  81. lines.append(f' | ({child_name}_MASK) \\')
  82. route_stack.pop()
  83. lines.append(' )')
  84. if 'routes' in container:
  85. for route_id in container['routes']:
  86. route = container['routes'][route_id]
  87. _append_route_capabilities(lines, route, route_id, route_stack)
  88. route_stack.pop()
  89. def _append_route_types(lines, container, container_id=None, route_stack=None):
  90. """Handles creating typedefs used by routes
  91. """
  92. if route_stack is None:
  93. route_stack = [container]
  94. else:
  95. route_stack.append(container)
  96. route_name = to_snake('_'.join([r['define'] for r in route_stack]))
  97. # Inbound
  98. if 'request_struct_members' in container:
  99. request_struct_members = container['request_struct_members']
  100. lines.append('typedef struct {')
  101. for member in request_struct_members:
  102. member_type = _get_c_type(member['type'])
  103. member_name = to_snake(member['name'])
  104. lines.append(f' {member_type} {member_name};')
  105. lines.append(f'}} __attribute__((__packed__)) {route_name}_arg_t;')
  106. req_len = container['request_struct_length']
  107. lines.append(f'_Static_assert(sizeof({route_name}_arg_t) == {req_len}, "{route_name}_arg_t needs to be {req_len} bytes in size");')
  108. elif 'request_type' in container:
  109. request_type = container['request_type']
  110. found = re.search(r'(u\d+)\[(\d+)\]', request_type)
  111. if found:
  112. request_type, size = found.groups()
  113. lines.append(f'typedef struct __attribute__((__packed__)) {{ {_get_c_type(request_type)} x[{size}]; }} {route_name}_arg_t;')
  114. else:
  115. lines.append(f'typedef {_get_c_type(request_type)} {route_name}_arg_t;')
  116. # Outbound
  117. qualifier = 'const' if 'return_constant' in container else ''
  118. if 'return_struct_members' in container:
  119. return_struct_members = container['return_struct_members']
  120. lines.append('typedef struct {')
  121. for member in return_struct_members:
  122. member_type = _get_c_type(member['type'])
  123. member_name = f'{qualifier} {to_snake(member["name"])}'
  124. lines.append(f' {member_type} {member_name};')
  125. lines.append(f'}} __attribute__((__packed__)) {route_name}_t;')
  126. req_len = container['return_struct_length']
  127. lines.append(f'_Static_assert(sizeof({route_name}_t) == {req_len}, "{route_name}_t needs to be {req_len} bytes in size");')
  128. elif 'return_type' in container:
  129. return_type = container['return_type']
  130. found = re.search(r'(u\d+)\[(\d+)\]', return_type)
  131. if found:
  132. return_type, size = found.groups()
  133. lines.append(f'typedef struct __attribute__((__packed__)) {{ {_get_c_type(return_type)} x[{size}]; }} {route_name}_t;')
  134. else:
  135. lines.append(f'typedef {_get_c_type(return_type)} {route_name}_t;')
  136. # Recurse
  137. if 'routes' in container:
  138. for route_id in container['routes']:
  139. route = container['routes'][route_id]
  140. _append_route_types(lines, route, route_id, route_stack)
  141. route_stack.pop()
  142. def _append_internal_types(lines, container):
  143. """Handles creating the various constants, types, defines, etc.
  144. """
  145. response_flags = container.get('response_flags', {})
  146. prefix = response_flags['define_prefix']
  147. for key, value in response_flags['bits'].items():
  148. define = value.get('define')
  149. lines.append(f'#define {prefix}_{define} (1ul << ({key}))')
  150. # Add special
  151. lines.append(f'#define {prefix}_FAILED 0x00')
  152. lines.append('')
  153. broadcast_messages = container.get('broadcast_messages', {})
  154. broadcast_prefix = broadcast_messages['define_prefix']
  155. for key, value in broadcast_messages['messages'].items():
  156. define = value.get('define')
  157. name = to_snake(f'{broadcast_prefix}_{define}')
  158. lines.append(f'#define {broadcast_prefix}_{define} {key}')
  159. if 'return_type' in value:
  160. ret_type = _get_c_type(value['return_type'])
  161. lines.append(f'void {name}({ret_type} value);')
  162. else:
  163. lines.append(f'void {name}(const void *data, size_t length);')
  164. # Add special
  165. lines.append(f'#define {broadcast_prefix}_TOKEN 0xFFFF')
  166. lines.append('')
  167. additional_types = {}
  168. types = container.get('type_definitions', {})
  169. for key, value in types.items():
  170. data_type = _get_c_type(value['type'])
  171. additional_types[key] = f'xap_{key}_t'
  172. for key, value in types.items():
  173. data_type = _get_c_type(value['type'])
  174. if data_type == 'struct':
  175. members = value['struct_members']
  176. lines.append(f'typedef {data_type} {{')
  177. for member in members:
  178. member_name = member["name"]
  179. member_type = _get_c_type(member["type"])
  180. if member_type == 'unknown':
  181. member_type = additional_types[member["type"]]
  182. lines.append(f' {member_type} {member_name};')
  183. lines.append(f'}} __attribute__((__packed__)) xap_{key}_t;')
  184. req_len = value['struct_length']
  185. lines.append(f'_Static_assert(sizeof(xap_{key}_t) == {req_len}, "xap_{key}_t needs to be {req_len} bytes in size");')
  186. else:
  187. lines.append(f'typedef {data_type} xap_{key}_t;')
  188. def generate_header(output_file, keyboard, keymap):
  189. """Generates the XAP protocol header file, generated during normal build.
  190. """
  191. xap_defs = merge_xap_defs(keyboard, keymap)
  192. # Preamble
  193. lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '']
  194. # Versions
  195. prog = re.compile(r'^(\d+)\.(\d+)\.(\d+)')
  196. b = prog.match(xap_defs['version'])
  197. lines.append(f'#define XAP_BCD_VERSION 0x{int(b.group(1)):02X}{int(b.group(2)):02X}{int(b.group(3)):04X}ul')
  198. b = prog.findall(git_get_version() or "") or [('0', '0', '0')]
  199. lines.append(f'#define QMK_BCD_VERSION 0x{int(b[0][0]):02X}{int(b[0][1]):02X}{int(b[0][2]):04X}ul')
  200. keyboard_id = fnv1a_32(bytes(keyboard, 'utf-8'))
  201. lines.append(f'#define XAP_KEYBOARD_IDENTIFIER 0x{keyboard_id:08X}ul')
  202. lines.append('')
  203. # Types
  204. _append_internal_types(lines, xap_defs)
  205. lines.append('')
  206. _append_route_types(lines, xap_defs)
  207. lines.append('')
  208. # Append the route and command defines
  209. _append_route_defines(lines, xap_defs)
  210. lines.append('')
  211. _append_route_masks(lines, xap_defs)
  212. lines.append('')
  213. _append_route_capabilities(lines, xap_defs)
  214. lines.append('')
  215. dump_lines(output_file, lines)