diff options
author | Brad Bishop <bradleyb@fuzziesquirrel.com> | 2019-04-04 08:29:42 -0400 |
---|---|---|
committer | Brad Bishop <bradleyb@fuzziesquirrel.com> | 2019-04-04 08:30:31 -0400 |
commit | c0da84767c00657c0b3b1afff7d9c5c2f1ed5fdb (patch) | |
tree | 8182748d7d1acd0209def310e1f3bf89a12ed022 /pytools | |
parent | eaedbe94482eb5f8be91c4c104295ab3535f2c97 (diff) | |
download | talos-skeleton-c0da84767c00657c0b3b1afff7d9c5c2f1ed5fdb.tar.gz talos-skeleton-c0da84767c00657c0b3b1afff7d9c5c2f1ed5fdb.zip |
Revert "skeleton: Remove obmcutil"
Additional changes are required before this can go in.
This reverts commit a738350d00b3171a1cbf0a749381b1ac36b4b7f8.
Change-Id: Ie16d54d781743960356ad936c12f2a571a1ef8e2
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
Diffstat (limited to 'pytools')
-rw-r--r-- | pytools/obmcutil | 323 | ||||
-rw-r--r-- | pytools/obmcutil-completion.sh | 11 | ||||
-rw-r--r-- | pytools/setup.py | 2 |
3 files changed, 335 insertions, 1 deletions
diff --git a/pytools/obmcutil b/pytools/obmcutil new file mode 100644 index 0000000..0aa40a9 --- /dev/null +++ b/pytools/obmcutil @@ -0,0 +1,323 @@ +#!/usr/bin/python + +import sys +import dbus +import argparse + +from dbus.mainloop.glib import DBusGMainLoop +import gobject +import json +import os +import signal +import time +from glob import glob +from os.path import join +from subprocess import Popen + +import obmc_system_config + +descriptors = { + 'power': { + 'bus_name': 'org.openbmc.control.Power', + 'object_name': '/org/openbmc/control/power0', + 'interface_name': 'org.openbmc.control.Power' + }, + 'chassison': { + 'bus_name': 'xyz.openbmc_project.State.Chassis', + 'object_name': '/xyz/openbmc_project/state/chassis0', + 'interface_name': 'xyz.openbmc_project.State.Chassis', + 'property': 'RequestedPowerTransition', + 'value': 'xyz.openbmc_project.State.Chassis.Transition.On', + 'monitor': 'obmc-chassis-poweron@0.target', + }, + 'chassisoff': { + 'bus_name': 'xyz.openbmc_project.State.Chassis', + 'object_name': '/xyz/openbmc_project/state/chassis0', + 'interface_name': 'xyz.openbmc_project.State.Chassis', + 'property': 'RequestedPowerTransition', + 'value': 'xyz.openbmc_project.State.Chassis.Transition.Off', + 'monitor': 'obmc-chassis-hard-poweroff@0.target', + }, + 'poweron': { + 'bus_name': 'xyz.openbmc_project.State.Host', + 'object_name': '/xyz/openbmc_project/state/host0', + 'interface_name': 'xyz.openbmc_project.State.Host', + 'property': 'RequestedHostTransition', + 'value': 'xyz.openbmc_project.State.Host.Transition.On', + 'monitor': 'obmc-host-start@0.target', + }, + 'poweroff': { + 'bus_name': 'xyz.openbmc_project.State.Host', + 'object_name': '/xyz/openbmc_project/state/host0', + 'interface_name': 'xyz.openbmc_project.State.Host', + 'property': 'RequestedHostTransition', + 'value': 'xyz.openbmc_project.State.Host.Transition.Off', + 'monitor': 'obmc-host-stop@0.target', + }, + 'bmcstate': { + 'bus_name': 'xyz.openbmc_project.State.BMC', + 'object_name': '/xyz/openbmc_project/state/bmc0', + 'interface_name': 'xyz.openbmc_project.State.BMC', + 'property': 'CurrentBMCState', + }, + 'chassisstate': { + 'bus_name': 'xyz.openbmc_project.State.Chassis', + 'object_name': '/xyz/openbmc_project/state/chassis0', + 'interface_name': 'xyz.openbmc_project.State.Chassis', + 'property': 'CurrentPowerState', + }, + 'hoststate': { + 'bus_name': 'xyz.openbmc_project.State.Host', + 'object_name': '/xyz/openbmc_project/state/host0', + 'interface_name': 'xyz.openbmc_project.State.Host', + 'property': 'CurrentHostState', + }, + 'bootprogress': { + 'bus_name': 'xyz.openbmc_project.State.Host', + 'object_name': '/xyz/openbmc_project/state/host0', + 'interface_name': 'xyz.openbmc_project.State.Boot.Progress', + 'property': 'BootProgress', + }, + 'state' : ['bmcstate', 'chassisstate', 'hoststate'], + 'status' : ['bmcstate', 'chassisstate', 'hoststate'], +} + +GPIO_DEFS_FILE = '/etc/default/obmc/gpio/gpio_defs.json' + + +def find_gpio_base(path="/sys/class/gpio/"): + pattern = "gpiochip*" + for gc in glob(join(path, pattern)): + with open(join(gc, "label")) as f: + label = f.readline().strip() + if label == "1e780000.gpio": + with open(join(gc, "base")) as f: + return int(f.readline().strip()) + # trigger a file not found exception + open(join(path, "gpiochip")) + + +GPIO_BASE = find_gpio_base() + + +def convertGpio(name): + offset = int(''.join(list(filter(str.isdigit, name)))) + port = list(filter(str.isalpha, name.upper())) + a = ord(port[-1]) - ord('A') + if len(port) > 1: + a += 26 + base = a * 8 + GPIO_BASE + return base + offset + + +def run_set_property(dbus_bus, dbus_iface, descriptor, args): + mainloop = gobject.MainLoop() + + iface = descriptor['interface_name'] + prop = descriptor['property'] + + if 'monitor' not in descriptor: + dbus_iface.Set(iface, prop, descriptor['value']) + return True + + def property_listener(job, path, unit, state): + if descriptor['monitor'] != unit: + return + + property_listener.success = (state == 'done') + mainloop.quit() + + property_listener.success = True + + if args.wait and args.verbose: + pid = Popen(["/bin/journalctl", "-f", "--no-pager"]).pid + + if args.wait: + sig_match = dbus_bus.add_signal_receiver(property_listener, "JobRemoved") + + dbus_iface.Set(iface, prop, descriptor['value']) + + if args.wait: + mainloop.run() + sig_match.remove() + + if args.wait and args.verbose: + # wait some time for the journal output + time.sleep(args.wait_tune) + os.kill(pid, signal.SIGTERM) + + return property_listener.success + +def get_dbus_obj(dbus_bus, bus, obj, args): + if not args.wait: + return dbus_bus.get_object(bus, obj) + + mainloop = gobject.MainLoop() + + def property_listener(job, path, unit, state): + if 'obmc-standby.target' == unit: + mainloop.quit() + + sig_match = dbus_bus.add_signal_receiver(property_listener, "JobRemoved") + try: + return dbus_bus.get_object(bus, obj) + except dbus.exceptions.DBusException as e: + if args.verbose: + pid = Popen(["/bin/journalctl", "-f", "--no-pager"]).pid + + mainloop.run() + + if args.verbose: + os.kill(pid, signal.SIGTERM) + finally: + sig_match.remove() + + return dbus_bus.get_object(bus, obj) + +def run_one_command(dbus_bus, descriptor, args): + bus = descriptor['bus_name'] + obj = descriptor['object_name'] + iface = descriptor['interface_name'] + dbus_obj = get_dbus_obj(dbus_bus, bus, obj, args) + result = None + + if 'property' in descriptor: + dbus_iface = dbus.Interface(dbus_obj, "org.freedesktop.DBus.Properties") + if 'value' in descriptor: + result = run_set_property(dbus_bus, dbus_iface, descriptor, args) + else: + prop = descriptor['property'] + dbus_prop = dbus_iface.Get(iface, prop) + print '{:<20}: {}'.format(prop, str(dbus_prop)) + result = True + else: + dbus_iface = dbus.Interface(dbus_obj, "org.freedesktop.DBus.Properties") + props = dbus_iface.GetAll(iface) + for p in props: + print "{} = {}".format(p, str(props[p])) + result = True + + return result + +def run_all_commands(dbus_bus, recipe, args): + if isinstance(recipe, dict): + return run_one_command(dbus_bus, recipe, args) + + assert isinstance(recipe, list) + for command in recipe: + descriptor = descriptors[command] + if not run_one_command(dbus_bus, descriptor, args): + print "Failed to execute command: {}".format(descriptor) + return False + + return True + +def gpio_set_value(gpio_name, active_low, asserted): + gpio_id = convertGpio(gpio_name) + gpio_value_path = "/sys/class/gpio/gpio{}/value".format(gpio_id) + + with open(gpio_value_path, 'w') as gpio: + # Inversion behaviour needs to change with the resolution of + # https://github.com/openbmc/openbmc/issues/2489, where properly + # configuring the kernel will allow it to handle the inversion for us. + gpio.write(str(int(asserted ^ active_low))) + + return True + +def gpio_deassert(gpio_name, active_low, args): + # Deal with silly python2 exception handling as outlined in main + if args.verbose: + return gpio_set_value(gpio_name, active_low, False) + + try: + return gpio_set_value(gpio_name, active_low, False) + except IOError as e: + print >> sys.stderr, "Failed to access GPIO {}: {}".format(gpio_name, e.message) + return False + +def run_chassiskill(args): + # We shouldn't be able to invoke run_chassiskill() unless it's been + # explicitly added as a valid command to argparse in main() + assert can_chassiskill() + + data = {} + with open(GPIO_DEFS_FILE, 'r') as json_input: + data = json.load(json_input) + + gpios = data['gpio_configs']['power_config']['power_up_outs'] + defs = data['gpio_definitions'] + + for gpio in gpios: + + definition = filter(lambda g: g['name'] == gpio['name'], defs) + if len(definition) == 0: + print >> sys.stderr, "Missing definition for GPIO {}".format(gpio['name']) + continue + + pin = str(definition[0]['pin']) + active_low = not gpio['polarity'] + + if not gpio_deassert(pin, active_low, args): + return False + + return True + +def can_chassiskill(): + + try: + with open(GPIO_DEFS_FILE, 'r') as json_input: + data = json.load(json_input) + gpios = data['gpio_configs']['power_config']['power_up_outs'] + return len(gpios) > 0 + except: + pass + + return False + +def main(): + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + # Conditionally add the `chassiskill` command based on whether the + # required GPIO configuration is present in the system description. + if can_chassiskill(): + descriptors['chassiskill'] = None + + parser = argparse.ArgumentParser() + parser.add_argument('--verbose', '-v', action='store_true', + help="Verbose output") + parser.add_argument('--wait', '-w', action='store_true', + help='Block until the state transition succeeds or fails') + parser.add_argument('--wait-tune', '-t', nargs='?', default=8, type=float, + # help='Seconds to wait for journal output to complete after receiving DBus signal', + help=argparse.SUPPRESS) + parser.add_argument('recipe', choices=sorted(descriptors.keys())) + args = parser.parse_args() + + # This is a special case: directly pull the power, don't do any D-Bus + # related stuff + if args.recipe == "chassiskill": + return run_chassiskill(args) + + dbus_bus = dbus.SystemBus() + + # The only way to get a sensible backtrace with python 2 without stupid + # hoops is to let the uncaught exception handler do the work for you. + # Catching and binding an exception appears to overwrite the stack trace at + # the point of bind. + # + # So, if we're in verbose mode, don't try to catch the DBus exception. That + # way we can understand where it originated. + if args.verbose: + return run_all_commands(dbus_bus, descriptors[args.recipe], args) + + # Otherwise, we don't care about the traceback. Just catch it and print the + # error message. + try: + return run_all_commands(dbus_bus, descriptors[args.recipe], args) + except dbus.exceptions.DBusException as e: + print >> sys.stderr, "DBus error occurred: {}".format(e.get_dbus_message()) + finally: + dbus_bus.close() + +if __name__ == "__main__": + sys.exit(0 if main() else 1) diff --git a/pytools/obmcutil-completion.sh b/pytools/obmcutil-completion.sh new file mode 100644 index 0000000..9790025 --- /dev/null +++ b/pytools/obmcutil-completion.sh @@ -0,0 +1,11 @@ +_obmcutil() { + COMPREPLY=() + cur=${COMP_WORDS[COMP_CWORD]} + + opts="bmcstate bootprogress chassiskill chassisoff chassison chassisstate hoststate power poweroff poweron state -h --help -v --verbose -w --wait" + + # complete -* with long options. + COMPREPLY=($(compgen -W "$opts" -- $cur)) +} + +complete -F _obmcutil obmcutil diff --git a/pytools/setup.py b/pytools/setup.py index 673bd3b..2736366 100644 --- a/pytools/setup.py +++ b/pytools/setup.py @@ -2,5 +2,5 @@ from distutils.core import setup setup(name='pytools', version='1.0', - scripts=['gpioutil'], + scripts=['obmcutil', 'gpioutil'], ) |