blob: 8c3f6c953769e987a96fcd33b7618c0d6e2692f8 [file] [log] [blame]
#!/usr/bin/python
import sys
import dbus
import argparse
from dbus.mainloop.glib import DBusGMainLoop
import gobject
import os
import signal
import time
from subprocess import Popen
import obmc_system_config
import obmc.system
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'],
}
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 = obmc.system.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()
# Multi-dimensional fetch is now exception-safe
gpios = obmc_system_config.GPIO_CONFIGS['power_config']['power_up_outs']
gc = obmc_system_config.GPIO_CONFIG
for gpio in gpios:
function = gpio[0]
if function not in gc or 'gpio_pin' not in gc[function]:
print >> sys.stderr, "Missing or invalid definition for '{}' in system GPIO_CONFIG".format(function)
continue
name = gc[function]['gpio_pin']
# The second element of the tuples stashed in 'power_up_outs'
# represents the boolean condition of the statement 'active high'. To
# mirror the code at [1] we instead need the condition of the statement
# 'active low', thus we negate gpio[1].
#
# [1] https://github.com/openbmc/skeleton/blob/93b84e42834893313616f96c70743369f26a7190/op-pwrctl/power_control_obj.c#L283
active_low = not gpio[1]
if not gpio_deassert(name, active_low, args):
return False
return True
def can_chassiskill():
gcs = obmc_system_config.GPIO_CONFIGS
if 'power_config' in gcs:
if 'power_up_outs' in gcs['power_config']:
# Just to be sure
return len(gcs['power_config']) > 0
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)