katapult/scripts/buildcommands.py
Kevin O'Connor 24d4eb16c9 initial_pins: Port initial_pins capability from Klipper
Some boards require an initial gpio state in order to start USB.  Port
the initial_pins capability from Klipper to provide that support.

This also synchronizes scripts/buildcommands.py with the latest code
from Klipper.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2022-05-16 13:59:42 -04:00

343 lines
11 KiB
Python

#!/usr/bin/env python2
# Script to handle build time requests embedded in C code.
#
# Copyright (C) 2016-2021 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, optparse, logging
FILEHEADER = """
/* DO NOT EDIT! This is an autogenerated file. See scripts/buildcommands.py. */
#include <stdint.h>
#include "board/irq.h"
#include "board/pgm.h"
#include "compiler.h"
#include "initial_pins.h"
"""
def error(msg):
sys.stderr.write(msg + "\n")
sys.exit(-1)
Handlers = []
######################################################################
# C call list generation
######################################################################
# Create dynamic C functions that call a list of other C functions
class HandleCallList:
def __init__(self):
self.call_lists = {'ctr_run_initfuncs': []}
self.ctr_dispatch = { '_DECL_CALLLIST': self.decl_calllist }
def decl_calllist(self, req):
funcname, callname = req.split()[1:]
self.call_lists.setdefault(funcname, []).append(callname)
def update_data_dictionary(self, data):
pass
def generate_code(self, options):
code = []
for funcname, funcs in self.call_lists.items():
func_code = [' extern void %s(void);\n %s();' % (f, f)
for f in funcs]
if funcname == 'ctr_run_taskfuncs':
add_poll = ' irq_poll();\n'
func_code = [add_poll + fc for fc in func_code]
func_code.append(add_poll)
fmt = """
void
%s(void)
{
%s
}
"""
code.append(fmt % (funcname, "\n".join(func_code).strip()))
return "".join(code)
Handlers.append(HandleCallList())
######################################################################
# Enumeration and static string generation
######################################################################
STATIC_STRING_MIN = 2
# Generate a dynamic string to integer mapping
class HandleEnumerations:
def __init__(self):
self.static_strings = []
self.enumerations = {}
self.ctr_dispatch = {
'DECL_ENUMERATION': self.decl_enumeration,
'DECL_ENUMERATION_RANGE': self.decl_enumeration_range
}
def add_enumeration(self, enum, name, value):
enums = self.enumerations.setdefault(enum, {})
if name in enums and enums[name] != value:
error("Conflicting definition for enumeration '%s %s'" % (
enum, name))
enums[name] = value
def decl_enumeration(self, req):
enum, name, value = req.split()[1:]
self.add_enumeration(enum, name, int(value, 0))
def decl_enumeration_range(self, req):
enum, name, value, count = req.split()[1:]
self.add_enumeration(enum, name, (int(value, 0), int(count, 0)))
def get_available_pins(self):
avail_pins = {}
pin_enums = self.enumerations.get('pin')
if pin_enums is None:
error("No pin enumeration available")
for port_id, (offset, count) in pin_enums.items():
for i in range(count):
pin_name = "%s%d" % (port_id[:-1], i)
pin_num = i + offset
avail_pins[pin_name] = pin_num
return avail_pins
def generate_code(self, options):
return ""
HandlerEnumerations = HandleEnumerations()
Handlers.append(HandlerEnumerations)
######################################################################
# Constants
######################################################################
# Allow adding build time constants to the data dictionary
class HandleConstants:
def __init__(self):
self.constants = {}
self.ctr_dispatch = {
'DECL_CONSTANT': self.decl_constant,
'DECL_CONSTANT_STR': self.decl_constant_str,
}
self.reserved_pins = []
def set_value(self, name, value):
if name in self.constants and self.constants[name] != value:
error("Conflicting definition for constant '%s'" % name)
self.constants[name] = value
def decl_constant(self, req):
name, value = req.split()[1:]
self.set_value(name, int(value, 0))
def decl_constant_str(self, req):
name, value = req.split(None, 2)[1:]
value = value.strip()
if value.startswith('"') and value.endswith('"'):
value = value[1:-1]
self.set_value(name, value)
def get_reserved_pins(self):
reserved_pins = []
for name, val in self.constants.items():
if name.upper().startswith("RESERVE_PINS"):
rpins = [v.strip() for v in val.split(',') if v.strip()]
reserved_pins.extend(rpins)
return reserved_pins
def lookup_pin(self, pin):
avail_pins = HandlerEnumerations.get_available_pins()
gpio = avail_pins.get(pin)
if gpio is None:
error("Pin %s is not available for this build" % (pin,))
reserved_pins = self.get_reserved_pins()
if pin in reserved_pins:
error("Pin %s is reserved by an active MCU peripheral" % (pin,))
return gpio
def generate_code(self, options):
return ""
HandlerConstants = HandleConstants()
Handlers.append(HandlerConstants)
######################################################################
# Initial pins
######################################################################
class HandleInitialPins:
def __init__(self):
self.initial_pins = []
self.ctr_dispatch = { 'DECL_INITIAL_PINS': self.decl_initial_pins }
def decl_initial_pins(self, req):
pins = req.split(None, 1)[1].strip()
if pins.startswith('"') and pins.endswith('"'):
pins = pins[1:-1]
if pins:
self.initial_pins = [p.strip() for p in pins.split(',')]
HandlerConstants.decl_constant_str(
"_DECL_CONSTANT_STR INITIAL_PINS "
+ ','.join(self.initial_pins))
def map_pins(self):
if not self.initial_pins:
return []
out = []
for p in self.initial_pins:
flag = "IP_OUT_HIGH"
if p.startswith('!'):
flag = "0"
p = p[1:].strip()
gpio = HandlerConstants.lookup_pin(p)
out.append("\n {%d, %s}, // %s" % (gpio, flag, p))
return out
def generate_code(self, options):
out = self.map_pins()
fmt = """
const struct initial_pin_s initial_pins[] PROGMEM = {%s
};
const int initial_pins_size PROGMEM = ARRAY_SIZE(initial_pins);
"""
return fmt % (''.join(out),)
Handlers.append(HandleInitialPins())
######################################################################
# ARM IRQ vector table generation
######################################################################
# Create ARM IRQ vector table from interrupt handler declarations
class Handle_arm_irq:
def __init__(self):
self.irqs = {}
self.ctr_dispatch = { 'DECL_ARMCM_IRQ': self.decl_armcm_irq }
def decl_armcm_irq(self, req):
func, num = req.split()[1:]
num = int(num, 0)
if num in self.irqs and self.irqs[num] != func:
error("Conflicting IRQ definition %d (old %s new %s)"
% (num, self.irqs[num], func))
self.irqs[num] = func
def generate_code(self, options):
armcm_offset = 16
if 1 - armcm_offset not in self.irqs:
# The ResetHandler was not defined - don't build VectorTable
return ""
max_irq = max(self.irqs.keys())
table = [" DefaultHandler,\n"] * (max_irq + armcm_offset + 1)
defs = []
for num, func in self.irqs.items():
if num < 1 - armcm_offset:
error("Invalid IRQ %d (%s)" % (num, func))
defs.append("extern void %s(void);\n" % (func,))
table[num + armcm_offset] = " %s,\n" % (func,)
table[0] = " &_stack_end,\n"
fmt = """
extern void DefaultHandler(void);
extern uint32_t _stack_end;
%s
const void *VectorTable[] __visible __section(".vector_table") = {
%s};
"""
return fmt % (''.join(defs), ''.join(table))
Handlers.append(Handle_arm_irq())
######################################################################
# Status LED Functionality
######################################################################
class HandleStatusLED:
def __init__(self):
self.pin = None
self.ctr_dispatch = { 'DECL_LED_PIN': self.decl_led_pin }
def decl_led_pin(self, req):
pin = req.split(None, 1)[1].strip()
if pin.startswith('"') and pin.endswith('"'):
pin = pin[1:-1].strip()
self.pin = pin
def generate_code(self, options):
led_gpio = led_gpio_high = 0
pin = self.pin
if pin:
led_gpio_high = 1
if pin[0] == "!":
led_gpio_high = 0
pin = pin[1:].strip()
led_gpio = HandlerConstants.lookup_pin(pin)
fmt = """
uint32_t led_gpio = %d, led_gpio_high = %d; // "%s"
"""
return fmt % (led_gpio, led_gpio_high, self.pin)
Handlers.append(HandleStatusLED())
######################################################################
# Button entry functionality
######################################################################
class HandleButton:
def __init__(self):
self.pin = None
self.ctr_dispatch = { 'DECL_BUTTON': self.decl_button }
def decl_button(self, req):
pin = req.split(None, 1)[1].strip()
if pin.startswith('"') and pin.endswith('"'):
pin = pin[1:-1].strip()
self.pin = pin
def generate_code(self, options):
button_gpio = button_high = button_pullup = 0
pin = self.pin
if pin:
if pin[0] in "^~":
button_pullup = 1
if pin[0] == "~":
button_pullup = -1
pin = pin[1:].strip()
button_high = 1
if pin[0] == "!":
button_high = 0
pin = pin[1:].strip()
button_gpio = HandlerConstants.lookup_pin(pin)
fmt = """
int32_t button_gpio = %d, button_high = %d, button_pullup = %d; // "%s"
"""
return fmt % (button_gpio, button_high, button_pullup, self.pin)
Handlers.append(HandleButton())
######################################################################
# Main code
######################################################################
def main():
usage = "%prog [options] <cmd section file> <output.c>"
opts = optparse.OptionParser(usage)
opts.add_option("-v", action="store_true", dest="verbose",
help="enable debug messages")
options, args = opts.parse_args()
if len(args) != 2:
opts.error("Incorrect arguments")
incmdfile, outcfile = args
if options.verbose:
logging.basicConfig(level=logging.DEBUG)
# Parse request file
ctr_dispatch = { k: v for h in Handlers for k, v in h.ctr_dispatch.items() }
f = open(incmdfile, 'r')
data = f.read()
f.close()
for req in data.split('\n'):
req = req.lstrip()
if not req:
continue
cmd = req.split()[0]
if cmd not in ctr_dispatch:
error("Unknown build time command '%s'" % cmd)
ctr_dispatch[cmd](req)
# Write output
code = "".join([FILEHEADER] + [h.generate_code(options) for h in Handlers])
f = open(outcfile, 'w')
f.write(code)
f.close()
if __name__ == '__main__':
main()