From b603f1df0a912b9a397c18911946535ae421de5d Mon Sep 17 00:00:00 2001 From: Christoph Muellner Date: Mon, 7 Oct 2019 15:23:58 +0200 Subject: Add support for cp2102 simple control cicuits. This patch factors out the low-level routines to control a CP2102 to cp210x_controller.py. command_processor.py provides a framework for defining commands and to specify options. It represents the board independet boilerplate code. cp2102_haikou.py contains the commands to control a Haikou-like circuit. cp2102_simple.py contains the commands to control a simple control circuit. The board files cp2102-mini-evk.py, haikou.py, and telaviv.py are the front-ends to control the specified boards. Signed-off-by: Christoph Muellner --- usb-control/README.md | 39 ++++++---- usb-control/command_processor.py | 105 +++++++++++++++++++++++++++ usb-control/cp2102-mini-evk.py | 10 +-- usb-control/cp2102_haikou.py | 144 +++++++++++++++++++++++++++++++++++++ usb-control/cp2102_simple.py | 113 +++++++++++++++++++++++++++++ usb-control/cp210x_controller.py | 149 +++------------------------------------ usb-control/haikou.py | 10 +-- usb-control/telaviv.py | 10 +-- 8 files changed, 415 insertions(+), 165 deletions(-) create mode 100644 usb-control/command_processor.py create mode 100644 usb-control/cp2102_haikou.py create mode 100644 usb-control/cp2102_simple.py diff --git a/usb-control/README.md b/usb-control/README.md index 74201e2..48e0172 100644 --- a/usb-control/README.md +++ b/usb-control/README.md @@ -1,7 +1,7 @@ # Haikou USB Control -This tool allows to control the **BIOS Disable** and **Power** Button via USB. - +This tool allows to control the **BIOS disable** signal and +**power** state via USB. ## Dependencies @@ -15,19 +15,30 @@ apt install python-usb # Usage -To manipulate **BIOS Disable** via USB the slider has to be in the **Normal -Boot** position. +To manipulate **BIOS disable** via USB the slider has to be in the **normal +boot** position. + +To list connected Haikou boards: + + haikou.py list + +To power cycle the board: + + haikou.py cycle + +To select a specific board with given serial number (can be fetched via 'list'): + + haikou.py --serial SERIALNUMBER + +To enable the BIOS disable signal: + + haikou.py biosdisable + +To switch back to normal boot: -The script takes following commands: + haikou.py normalboot - list list serial numbers of connected baseboards +For more features use the built-in help: - power controls the power and BIOS Disable state - --serial SERIALNUMBER, -s SERIALNUMBER - defines the serialnumber to use - --bios-disable, -b activate BIOS Disable - --normal-boot, -n activate Normal Boot + haikou.py --help -Each Haikou base board has a unique USB ID so they can be differentiated if more -then one is connected to the host. Provide the serialnumber on the commandline -to select a specific board, otherwise the first one found will be used. diff --git a/usb-control/command_processor.py b/usb-control/command_processor.py new file mode 100644 index 0000000..ee03ae2 --- /dev/null +++ b/usb-control/command_processor.py @@ -0,0 +1,105 @@ +""" +Generic class for arg parsing and command processing. +""" + +__copyright__ = 'Copyright (C) 2019 Theobroma Systems Design und Consulting GmbH' +__license__ = 'MIT' + +import sys +import cp210x_controller + +def do_delay(dev): + time.sleep(0.3) + +def do_list(devs): + print "Found %d Baseboard%s:" % (len(devs), "s"[len(devs) == 1:]) + for idx, dev in enumerate(devs): + print "%d: %s %s" % (idx, dev.serial_number, dev.product) + +class CommandProcessor: + def __init__(self, product_string, options_dict, command_dict): + self.product_string = product_string + self.options_dict = options_dict + self.command_dict = { + 'delay': (do_delay, + 'delays execution for 300 ms'), + 'list': (do_list, + 'lists serial numbers of attached Haikou Baseboards'), + } + + self.command_dict.update(command_dict) + + def print_helpstring(self): + print 'Tool to controls power and BIOS disable state' + print 'Synopsis: ' + sys.argv[0] + ' OPTIONS COMMAND(S)' + print 'Options:' + print "\t-h, --help...displays this help text" + print "\t-i, --ignore-product-string...ignore the product string of the device" + print "\t-s SERIAL, --serial SERIAL...specify the serial number of the device" + if self.options_dict: + for o in self.options_dict: + (cb, helpstr) = self.options_dict[o] + print "\t" + o + "..." + helpstr + print 'Commands:' + for c in self.command_dict: + (cb, helpstr) = self.command_dict[c] + print "\t" + c + "..." + helpstr + print "" + + def validate_commandline(self, argv): + self.ignore_product_string = False + self.serialnumber = None + self.command_list = [] + + consume = None + for arg in argv: + if arg == '-h' or arg == '--help': + self.print_helpstring() + sys.exit(0) + elif arg == '-i' or arg == '--ignore-product-string': + self.ignore_product_string = True; + elif arg == '-s' or arg == '--serial': + consume = '-s' + elif consume == '-s': + self.serialnumber = arg + consume = None + else: + if self.options_dict and arg in self.options_dict: + (cb, helpstr) = self.options_dict[arg] + cb() + elif arg in self.command_dict: + self.command_list.append(arg) + else: + print "Unkown command " + arg + sys.exit(1) + + def run(self): + # Collect list of potential devices + if self.ignore_product_string == True: + self.product_string = None + + # Get list of matching USB devices + devs = cp210x_controller.find_board_list(self.product_string, self.serialnumber) + + if len(devs) == 0: + print "No devices found!" + sys.exit(2) + + # List devices if required + if 'list' in self.command_list: + do_list(devs) + sys.exit(0) + + # Other commands than 'list' require exactly one device + if len(devs) != 1: + print "Please specify target device (found " + str(len(devs)) + " matching devices)" + sys.exit(3) + + # Get the one and only device + dev = devs[0] + + # Execute commands + for cmd in self.command_list: + (cb, helpstr) = self.command_dict[cmd] + cb(dev) + diff --git a/usb-control/cp2102-mini-evk.py b/usb-control/cp2102-mini-evk.py index e8c0a69..f8b727d 100755 --- a/usb-control/cp2102-mini-evk.py +++ b/usb-control/cp2102-mini-evk.py @@ -1,15 +1,17 @@ #!/usr/bin/env python -"""Controls power button and BIOS Disable of the Haikou Baseboard +""" +Controls power button and BIOS-disable signal on the +CP2102 Mini-EVK board connected to an arbitray board. """ __copyright__ = 'Copyright (C) 2019 Theobroma Systems Design und Consulting GmbH' __license__ = 'MIT' -import cp210x_controller +import cp2102_simple -PRODUCT_STRING = "CP2102 Mini-EVK" +PRODUCT_STRING = "CP2102 Mini-EVK" if __name__ == '__main__': - cp210x_controller.cp210x_controller(PRODUCT_STRING) + cp2102_simple.main(PRODUCT_STRING) diff --git a/usb-control/cp2102_haikou.py b/usb-control/cp2102_haikou.py new file mode 100644 index 0000000..4b44b3e --- /dev/null +++ b/usb-control/cp2102_haikou.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python + +""" +Abstraction layer for Haikou-like control circuits. +The Haikou circuit offers to "press" the power button +via GPIO.0 in order to toggle the power state, controls +the BIOS disable (maskrom) signal via GPIO.1, and +an read the current power state via CTS line +(modem status). +""" + +__copyright__ = 'Copyright (C) 2019 Theobroma Systems Design und Consulting GmbH' +__license__ = 'MIT' + +import sys +import time +import command_processor +import cp210x_controller + +# Power supply GPIO +GPIO_POWER = 0 +VALUE_POWER_RELEASE = 0 +VALUE_POWER_PRESS = 1 + +# BIOS disable GPIO +GPIO_BIOS = 1 +VALUE_BIOS_NORMAL = 0 +VALUE_BIOS_DISABLE = 1 + +# Power state +POWERSTATE_ON = 0 +POWERSTATE_OFF = 1 + +def toggle_power(dev): + # We emulate a power button press for 300 ms + cp210x_controller.set_gpio(dev, GPIO_POWER, VALUE_POWER_PRESS) + time.sleep(0.3) + cp210x_controller.set_gpio(dev, GPIO_POWER, VALUE_POWER_RELEASE) + time.sleep(0.3) + +def get_powerstate(dev): + # Reading the GPIO latch (get_gpio()) is useless, because it + # does not reflect the real status. However, we have CTS connected + # to 3V3, which is enabled when the PMIC is active + # Returns POWERSTATE_ON or POWERSTATE_OFF + return cp210x_controller.get_modemstatus(dev) + +def set_bootmode(dev, state): + # Set the bootmode state according the given argument. + # state should be VALUE_BIOS_NORMAL or VALUE_BIOS_DISABLE + cp210x_controller.set_gpio(dev, GPIO_BIOS, state) + +def get_bootmode(dev): + # We can only check if we pull the line low. + # This could be overruled with the on-board slider. + # Returns VALUE_BIOS_NORMAL or VALUE_BIOS_DISABLE + return cp210x_controller.get_gpio(dev, GPIO_BIOS) + + + +def do_status(dev): + powerstate = get_powerstate(dev) + bootmode = get_bootmode(dev) + print "Board is {} ({})".format( + ("ON" if powerstate == POWERSTATE_ON else "OFF"), + ("BIOS disabled" if bootmode == VALUE_BIOS_DISABLE else "Normal boot (if not overruled by on-board switch)")) + +def do_normalboot(dev): + set_bootmode(dev, VALUE_BIOS_NORMAL) + +def do_biosdisable(dev): + set_bootmode(dev, VALUE_BIOS_DISABLE) + +def do_powerbutton(dev): + toggle_power(dev) + +def do_on(dev): + # Turn on if needed + if get_powerstate(dev) == POWERSTATE_OFF: + toggle_power(dev) + +def do_off(dev): + # Turn off if needed + if get_powerstate(dev) == POWERSTATE_ON: + toggle_power(dev) + +def do_cycle(dev): + # Turn off if needed + if get_powerstate(dev) == POWERSTATE_ON: + toggle_power(dev) + # Turn on board + toggle_power(dev) + +def do_cycle_to_normal(dev): + # Turn off if needed + if get_powerstate(dev) == POWERSTATE_ON: + toggle_power(dev) + # Disable maskrom mode + set_bootmode(dev, VALUE_BIOS_NORMAL) + # Turn on board + toggle_power(dev) + +def do_cycle_to_maskrom(dev): + # Turn off if needed + if get_powerstate(dev) == POWERSTATE_ON: + toggle_power(dev) + # Enable maskrom mode + set_bootmode(dev, VALUE_BIOS_DISABLE) + # Turn on board + toggle_power(dev) + + + +def main(product_string): + command_dict = { + 'status': (do_status, + 'show status of attached Haikou Baseboards'), + 'normalboot': (do_normalboot, + 'set bootmode to normal boot'), + 'biosdisable': (do_normalboot, + 'set bootmode to BIOS disabled'), + 'powerbutton': (do_powerbutton, + 'emulate power button press'), + 'on': (do_on, + 'turn on attached Haikou Baseboards'), + 'off': (do_off, + 'turn off attached Haikou Baseboards'), + 'cycle': (do_cycle, + 'power cycle attached Haikou Baseboards'), + 'cycle-to-normal': (do_cycle_to_normal, + 'power cycle with normal boot'), + 'cycle-to-maskrom': (do_cycle_to_maskrom, + 'power cycle with BIOS disabled') + } + + # Create argparser object + cp = command_processor.CommandProcessor(product_string, None, command_dict) + + # Valiate command line arguments and commands + cp.validate_commandline(sys.argv[1:]) + + # Start executing the commands + cp.run() + diff --git a/usb-control/cp2102_simple.py b/usb-control/cp2102_simple.py new file mode 100644 index 0000000..c4b2924 --- /dev/null +++ b/usb-control/cp2102_simple.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python + +""" +Abstraction layer for simple control circuits. +The simple control circuit can control the reset +signal via GPIO.0 and the maskrom signal via GPIO.1. +""" + +__copyright__ = 'Copyright (C) 2019 Theobroma Systems Design und Consulting GmbH' +__license__ = 'MIT' + +import sys +import time +import command_processor +import cp210x_controller + +# Power supply GPIO +GPIO_RESET = 0 +VALUE_RESET_DEASSERT = 0 +VALUE_RESET_ASSERT = 1 + +# Maskrom (BIOS disable) GPIO +GPIO_MASKROM = 1 +VALUE_MASKROM_DEASSERT = 0 +VALUE_MASKROM_ASSERT = 1 + +def invert_reset(): + global VALUE_RESET_DEASSERT + global VALUE_RESET_ASSERT + VALUE_RESET_DEASSERT = 1 + VALUE_RESET_ASSERT = 0 + +def invert_maskrom(): + global VALUE_MASKROM_DEASSERT + global VALUE_MASKROM_ASSERT + VALUE_MASKROM_DEASSERT = 1 + VALUE_MASKROM_ASSERT = 0 + +def do_status(dev): + resetstate = cp210x_controller.get_gpio(dev, GPIO_RESET) + bootmode = cp210x_controller.get_gpio(dev, GPIO_MASKROM) + print "Board is {} ({})".format( + ("running" if reset == RESETSTATE_DEASSERTED else "in reset"), + ("normal boot" if bootmode == VALUE_MASKROM_DEASSERTED else "boot to maskrom")) + +def do_reset_assert(dev): + cp210x_controller.set_gpio(dev, GPIO_RESET, VALUE_RESET_ASSERT) + +def do_reset_deassert(dev): + cp210x_controller.set_gpio(dev, GPIO_RESET, VALUE_RESET_DEASSERT) + +def do_reset(dev): + cp210x_controller.set_gpio(dev, GPIO_RESET, VALUE_RESET_ASSERT) + time.sleep(0.3) + cp210x_controller.set_gpio(dev, GPIO_RESET, VALUE_RESET_DEASSERT) + +def do_normalboot(dev): + cp210x_controller.set_gpio(dev, GPIO_MASKROM, VALUE_MASKROM_DEASSERT) + +def do_maskrom(dev): + cp210x_controller.set_gpio(dev, GPIO_MASKROM, VALUE_MASKROM_ASSERT) + +def do_reset_to_normal(dev): + cp210x_controller.set_gpio(dev, GPIO_MASKROM, VALUE_MASKROM_DEASSERT) + cp210x_controller.set_gpio(dev, GPIO_RESET, VALUE_RESET_ASSERT) + time.sleep(0.3) + cp210x_controller.set_gpio(dev, GPIO_RESET, VALUE_RESET_DEASSERT) + +def do_reset_to_maskrom(dev): + cp210x_controller.set_gpio(dev, GPIO_MASKROM, VALUE_MASKROM_ASSERT) + cp210x_controller.set_gpio(dev, GPIO_RESET, VALUE_RESET_ASSERT) + time.sleep(0.3) + cp210x_controller.set_gpio(dev, GPIO_RESET, VALUE_RESET_DEASSERT) + time.sleep(0.3) + cp210x_controller.set_gpio(dev, GPIO_MASKROM, VALUE_MASKROM_DEASSERT) + +def main(product_string): + options_dict = { + '--invert-reset': (invert_reset, + 'invert voltage level of reset line'), + '--invert-maskrom': (invert_maskrom, + 'invert voltage level of maskrom line') + } + + command_dict = { + 'status': (do_status, + 'show status of attached board'), + 'reset-assert': (do_reset_assert, + 'assert the reset line'), + 'reset-deassert': (do_reset_deassert, + 'deassert the reset line'), + 'reset': (do_reset, + 'assert the reset line for 300 ms'), + 'normalboot': (do_normalboot, + 'set bootmode to normal boot'), + 'maskrom': (do_maskrom, + 'set bootmode to maskrom'), + 'reset-to-normal': (do_reset_to_normal, + 'reset with normal boot target'), + 'reset-to-maskrom': (do_reset_to_maskrom, + 'reset to maskrom') + } + + # Create argparser object + cp = command_processor.CommandProcessor(product_string, options_dict, + command_dict) + + # Valiate command line arguments and commands + cp.validate_commandline(sys.argv[1:]) + + # Start executing the commands + cp.run() + diff --git a/usb-control/cp210x_controller.py b/usb-control/cp210x_controller.py index 2fe9008..b4d9d15 100644 --- a/usb-control/cp210x_controller.py +++ b/usb-control/cp210x_controller.py @@ -1,13 +1,13 @@ #!/usr/bin/env python +"""Low-level library to control the CP210x. +""" + __copyright__ = 'Copyright (C) 2019 Theobroma Systems Design und Consulting GmbH' __license__ = 'MIT' -import argparse import usb.core import usb.util -import sys -import time VENDOR_ID = 0x10c4 PRODUCT_ID = 0xea60 @@ -21,20 +21,6 @@ CP210X_WRITE_LATCH = 0x37E1 CP210X_READ_LATCH = 0x00C2 CP210X_GET_MDMSTS = 0x8 -# Power supply GPIO -GPIO_POWER = 0 -VALUE_POWER_RELEASE = 0 -VALUE_POWER_PRESS = 1 - -# BIOS disable GPIO -GPIO_BIOS = 1 -VALUE_BIOS_NORMAL = 0 -VALUE_BIOS_DISABLE = 1 - -# Power state -POWERSTATE_ON = 0 -POWERSTATE_OFF = 1 - def set_gpio(dev, gpio, value): # the latch register has 16 bit. the upper 8 bits are the gpio value, # the lower 8 bit are the write mask. @@ -52,15 +38,7 @@ def get_gpio(dev, gpio): data_or_wLength = 1) return not not (data[0] & (1 << gpio)) -def toggle_power(dev): - # We emulate a power button press for 300 ms - set_gpio(dev, GPIO_POWER, VALUE_POWER_PRESS) - time.sleep(1) - set_gpio(dev, GPIO_POWER, VALUE_POWER_RELEASE) - -def get_powerstate(dev): - # Reading the latch would be useless, but we have CTS connected to 3V3 - # Get modem status +def get_modemstatus(dev): data = dev.ctrl_transfer(bmRequestType = REQTYPE_DEVICE_TO_HOST, bRequest = CP210X_GET_MDMSTS, wValue = 0, @@ -69,62 +47,12 @@ def get_powerstate(dev): # Check CTS line return ((data[0] >> 4) & 1) -def get_bootmode(dev): - # We can only check if we pull the line low - return get_gpio(dev, GPIO_BIOS) - -def create_parser(): - parser = argparse.ArgumentParser(description= - 'Controls power and BIOS disable state of the Haikou baseboard') - - # Generic arguments (for identifying the USB device) - parser.add_argument('--ignore-product-string', '-i', - action='store_true', - dest='ignore_product_string', - help='ignore product string') - - parser.add_argument('--serial', '-s', - action='store', - dest='serialnumber', - type=str, - help='defines the serialnumber to use') - - # Arguments to set the bootmode - parser_bootmode_group = parser.add_mutually_exclusive_group() - parser_bootmode_group.add_argument('--normal-boot', '-n', - action='store_true', - dest='normal_boot', - help='activate Normal Boot') - parser_bootmode_group.add_argument('--bios-disable', '-b', - action='store_true', - dest='bios_disable', - help='activate BIOS Disable') - - # Commands - subparsers = parser.add_subparsers(help='command to perform', - dest='subcommand') - subparsers.add_parser('list', - help='lists serial numbers of attached Haikou Baseboards') - subparsers.add_parser('on', - help='turn on attached Haikou Baseboards') - subparsers.add_parser('off', - help='turn off attached Haikou Baseboards') - subparsers.add_parser('cycle', - help='power cycle attached Haikou Baseboards') - subparsers.add_parser('toggle', - help='emulate power button press on attached Haikou Baseboards') - subparsers.add_parser('toggle2', - help='emulate two power button presses on attached Haikou Baseboards') - subparsers.add_parser('status', - help='show status of attached Haikou Baseboards') - subparsers.add_parser('force-on', - help='tdb') - subparsers.add_parser('force-off', - help='tdb') - - return parser - def find_board_list(product_string, serialnumber): + """ + This function returns a list of found CP210x controllers. + If product_string is not None it will be used for filtering. + If serialnumber is not None it will be used for filtering. + """ try: kwargs = dict() kwargs['idVendor'] = VENDOR_ID @@ -138,9 +66,8 @@ def find_board_list(product_string, serialnumber): # Find the devices matching the specified requirements dev_it = usb.core.find(**kwargs) - # Convert iterator to list + # Convert iterator to list and return devs = list(dev_it) - return devs except ValueError, e: @@ -149,59 +76,3 @@ def find_board_list(product_string, serialnumber): "This may be a permission issue. See: \n" + "https://github.com/pyusb/pyusb/issues/139") -def cp210x_controller(product_string): - # Create the argument parser - parser = create_parser() - - # Apply the parser - args = parser.parse_args() - - # Collect list of potential devices - if args.ignore_product_string == True: - product_string = None - devs = find_board_list(product_string, args.serialnumber) - - if args.subcommand == 'list': - print "Found %d Baseboard%s:" % (len(devs), "s"[len(devs) == 1:]) - for idx, dev in enumerate(devs): - print "%d: %s %s" % (idx, dev.serial_number, dev.product) - sys.exit() - - - for dev in devs: - if args.bios_disable: - set_gpio(dev, GPIO_BIOS, VALUE_BIOS_DISABLE) - elif args.normal_boot: - set_gpio(dev, GPIO_BIOS, VALUE_BIOS_NORMAL) - - if args.subcommand == 'on': - if get_powerstate(dev) == POWERSTATE_OFF: - toggle_power(dev) - elif args.subcommand == 'off': - if get_powerstate(dev) == POWERSTATE_ON: - toggle_power(dev) - elif args.subcommand == 'cycle': - if get_powerstate(dev) == POWERSTATE_ON: - toggle_power(dev) - time.sleep(1) - toggle_power(dev) - elif args.subcommand == 'toggle': - toggle_power(dev) - elif args.subcommand == 'toggle2': - toggle_power(dev) - time.sleep(1) - toggle_power(dev) - elif args.subcommand == 'status': - powerstate = get_powerstate(dev) - bootmode = get_bootmode(dev) - print "Board is {} ({})".format( - ("ON" if powerstate == POWERSTATE_ON else "OFF"), - ("BIOS disabled" if bootmode == VALUE_BIOS_DISABLE else "Normal boot (if not overruled by on-board switch)")) - elif args.subcommand == "force-on": - set_gpio(dev, GPIO_POWER, VALUE_POWER_PRESS) - elif args.subcommand == "force-off": - set_gpio(dev, GPIO_POWER, VALUE_POWER_RELEASE) - -if __name__ == '__main__': - cp210x_controller(None) - diff --git a/usb-control/haikou.py b/usb-control/haikou.py index ef9f401..a91ca18 100755 --- a/usb-control/haikou.py +++ b/usb-control/haikou.py @@ -1,15 +1,17 @@ #!/usr/bin/env python -"""Controls power button and BIOS Disable of the Haikou Baseboard +""" +Controls power button and BIOS-disable signal on the +Haikou baseboard. """ __copyright__ = 'Copyright (C) 2019 Theobroma Systems Design und Consulting GmbH' __license__ = 'MIT' -import cp210x_controller +import cp2102_haikou -PRODUCT_STRING = "Haikou Baseboard" +PRODUCT_STRING = "Haikou Baseboard" if __name__ == '__main__': - cp210x_controller.cp210x_controller(PRODUCT_STRING) + cp2102_haikou.main(PRODUCT_STRING) diff --git a/usb-control/telaviv.py b/usb-control/telaviv.py index b7576bd..16e7ffc 100755 --- a/usb-control/telaviv.py +++ b/usb-control/telaviv.py @@ -1,15 +1,17 @@ #!/usr/bin/env python -"""Controls power button and BIOS Disable of the Haikou Baseboard +""" +Controls power button and BIOS-disable signal on the +TelAviv baseboard. """ __copyright__ = 'Copyright (C) 2019 Theobroma Systems Design und Consulting GmbH' __license__ = 'MIT' -import cp210x_controller +import cp2102_haikou -PRODUCT_STRING = "Tel-Aviv" +PRODUCT_STRING = "Tel-Aviv" if __name__ == '__main__': - cp210x_controller.cp210x_controller(PRODUCT_STRING) + cp2102_haikou.main(PRODUCT_STRING) -- cgit v1.2.3