blob: 0a0d8d15e2c6671a21335ba009b7ae1cb457211f [file] [log] [blame]
Norman Jamesb4914ad2015-10-26 07:14:29 -05001#!/usr/bin/python
2
3import sys
Norman Jamesb4914ad2015-10-26 07:14:29 -05004import dbus
Andrew Jeffery0b6883d2017-10-11 22:08:55 +10305import argparse
Norman Jamesb4914ad2015-10-26 07:14:29 -05006
Andrew Jefferyde4ed022017-10-12 01:26:55 +10307from dbus.mainloop.glib import DBusGMainLoop
8import gobject
Matt Spinlerc4390de2018-08-08 11:16:40 -05009import json
Andrew Jefferyb43f7ed2017-10-12 01:33:59 +103010import os
11import signal
12import time
13from subprocess import Popen
Andrew Jefferyde4ed022017-10-12 01:26:55 +103014
Andrew Jeffery1be4fab2017-10-16 14:13:53 +103015import obmc_system_config
16import obmc.system
17
Andrew Jefferyc6b5deb2017-10-11 22:18:27 +103018descriptors = {
Andrew Geisslercae038e2016-12-19 15:29:10 -060019 'power': {
20 'bus_name': 'org.openbmc.control.Power',
21 'object_name': '/org/openbmc/control/power0',
22 'interface_name': 'org.openbmc.control.Power'
23 },
Andrew Geissler2cbbb672017-05-17 10:52:49 -050024 'chassison': {
25 'bus_name': 'xyz.openbmc_project.State.Chassis',
26 'object_name': '/xyz/openbmc_project/state/chassis0',
27 'interface_name': 'xyz.openbmc_project.State.Chassis',
28 'property': 'RequestedPowerTransition',
Andrew Jefferyde4ed022017-10-12 01:26:55 +103029 'value': 'xyz.openbmc_project.State.Chassis.Transition.On',
30 'monitor': 'obmc-chassis-poweron@0.target',
Andrew Geissler2cbbb672017-05-17 10:52:49 -050031 },
32 'chassisoff': {
33 'bus_name': 'xyz.openbmc_project.State.Chassis',
34 'object_name': '/xyz/openbmc_project/state/chassis0',
35 'interface_name': 'xyz.openbmc_project.State.Chassis',
36 'property': 'RequestedPowerTransition',
Andrew Jefferyde4ed022017-10-12 01:26:55 +103037 'value': 'xyz.openbmc_project.State.Chassis.Transition.Off',
38 'monitor': 'obmc-chassis-hard-poweroff@0.target',
Andrew Geissler2cbbb672017-05-17 10:52:49 -050039 },
Andrew Geisslercae038e2016-12-19 15:29:10 -060040 'poweron': {
Andrew Geissler25b9a852016-12-19 16:14:09 -060041 'bus_name': 'xyz.openbmc_project.State.Host',
42 'object_name': '/xyz/openbmc_project/state/host0',
43 'interface_name': 'xyz.openbmc_project.State.Host',
44 'property': 'RequestedHostTransition',
Andrew Jefferyde4ed022017-10-12 01:26:55 +103045 'value': 'xyz.openbmc_project.State.Host.Transition.On',
46 'monitor': 'obmc-host-start@0.target',
Andrew Geisslercae038e2016-12-19 15:29:10 -060047 },
48 'poweroff': {
Andrew Geissler25b9a852016-12-19 16:14:09 -060049 'bus_name': 'xyz.openbmc_project.State.Host',
50 'object_name': '/xyz/openbmc_project/state/host0',
51 'interface_name': 'xyz.openbmc_project.State.Host',
52 'property': 'RequestedHostTransition',
Andrew Jefferyde4ed022017-10-12 01:26:55 +103053 'value': 'xyz.openbmc_project.State.Host.Transition.Off',
54 'monitor': 'obmc-host-stop@0.target',
Andrew Geisslercae038e2016-12-19 15:29:10 -060055 },
Andrew Geissler8f8cbce2017-02-20 16:10:26 -060056 'bmcstate': {
57 'bus_name': 'xyz.openbmc_project.State.BMC',
58 'object_name': '/xyz/openbmc_project/state/bmc0',
59 'interface_name': 'xyz.openbmc_project.State.BMC',
60 'property': 'CurrentBMCState',
61 },
62 'chassisstate': {
63 'bus_name': 'xyz.openbmc_project.State.Chassis',
64 'object_name': '/xyz/openbmc_project/state/chassis0',
65 'interface_name': 'xyz.openbmc_project.State.Chassis',
66 'property': 'CurrentPowerState',
67 },
68 'hoststate': {
69 'bus_name': 'xyz.openbmc_project.State.Host',
70 'object_name': '/xyz/openbmc_project/state/host0',
71 'interface_name': 'xyz.openbmc_project.State.Host',
72 'property': 'CurrentHostState',
Andrew Geisslercae038e2016-12-19 15:29:10 -060073 },
74 'bootprogress': {
George Keishing3be09952017-08-18 09:48:03 -050075 'bus_name': 'xyz.openbmc_project.State.Host',
76 'object_name': '/xyz/openbmc_project/state/host0',
77 'interface_name': 'xyz.openbmc_project.State.Boot.Progress',
78 'property': 'BootProgress',
Andrew Geisslercae038e2016-12-19 15:29:10 -060079 },
Joel Stanleya30ab9d2018-01-31 13:34:16 +103080 'state' : ['bmcstate', 'chassisstate', 'hoststate'],
81 'status' : ['bmcstate', 'chassisstate', 'hoststate'],
Norman Jamesb4914ad2015-10-26 07:14:29 -050082}
83
Matt Spinlerc4390de2018-08-08 11:16:40 -050084GPIO_DEFS_FILE = '/etc/default/obmc/gpio/gpio_defs.json'
85
Andrew Jefferyde4ed022017-10-12 01:26:55 +103086def run_set_property(dbus_bus, dbus_iface, descriptor, args):
87 mainloop = gobject.MainLoop()
88
89 iface = descriptor['interface_name']
90 prop = descriptor['property']
91
92 if 'monitor' not in descriptor:
93 dbus_iface.Set(iface, prop, descriptor['value'])
94 return True
95
96 def property_listener(job, path, unit, state):
97 if descriptor['monitor'] != unit:
98 return
99
100 property_listener.success = (state == 'done')
101 mainloop.quit()
102
103 property_listener.success = True
104
Andrew Jefferyb43f7ed2017-10-12 01:33:59 +1030105 if args.wait and args.verbose:
106 pid = Popen(["/bin/journalctl", "-f", "--no-pager"]).pid
107
Andrew Jefferyde4ed022017-10-12 01:26:55 +1030108 if args.wait:
109 sig_match = dbus_bus.add_signal_receiver(property_listener, "JobRemoved")
110
111 dbus_iface.Set(iface, prop, descriptor['value'])
112
113 if args.wait:
114 mainloop.run()
115 sig_match.remove()
116
Andrew Jefferyb43f7ed2017-10-12 01:33:59 +1030117 if args.wait and args.verbose:
118 # wait some time for the journal output
119 time.sleep(args.wait_tune)
120 os.kill(pid, signal.SIGTERM)
121
Andrew Jefferyde4ed022017-10-12 01:26:55 +1030122 return property_listener.success
123
Andrew Jefferycf6a7842017-10-12 14:47:43 +1030124def get_dbus_obj(dbus_bus, bus, obj, args):
125 if not args.wait:
126 return dbus_bus.get_object(bus, obj)
127
128 mainloop = gobject.MainLoop()
129
130 def property_listener(job, path, unit, state):
131 if 'obmc-standby.target' == unit:
132 mainloop.quit()
133
134 sig_match = dbus_bus.add_signal_receiver(property_listener, "JobRemoved")
135 try:
136 return dbus_bus.get_object(bus, obj)
137 except dbus.exceptions.DBusException as e:
138 if args.verbose:
139 pid = Popen(["/bin/journalctl", "-f", "--no-pager"]).pid
140
141 mainloop.run()
142
143 if args.verbose:
144 os.kill(pid, signal.SIGTERM)
145 finally:
146 sig_match.remove()
147
148 return dbus_bus.get_object(bus, obj)
149
Andrew Jefferyde4ed022017-10-12 01:26:55 +1030150def run_one_command(dbus_bus, descriptor, args):
Andrew Jefferyc6b5deb2017-10-11 22:18:27 +1030151 bus = descriptor['bus_name']
152 obj = descriptor['object_name']
153 iface = descriptor['interface_name']
Andrew Jefferycf6a7842017-10-12 14:47:43 +1030154 dbus_obj = get_dbus_obj(dbus_bus, bus, obj, args)
Andrew Jefferyde4ed022017-10-12 01:26:55 +1030155 result = None
Andrew Jefferye4dcaef2017-10-11 18:08:01 +1030156
Andrew Jeffery08e149b2017-10-12 01:37:07 +1030157 if 'property' in descriptor:
Andrew Jefferye4dcaef2017-10-11 18:08:01 +1030158 dbus_iface = dbus.Interface(dbus_obj, "org.freedesktop.DBus.Properties")
Andrew Jeffery08e149b2017-10-12 01:37:07 +1030159 if 'value' in descriptor:
Andrew Jefferyde4ed022017-10-12 01:26:55 +1030160 result = run_set_property(dbus_bus, dbus_iface, descriptor, args)
Andrew Jefferye4dcaef2017-10-11 18:08:01 +1030161 else:
Andrew Jefferyde4ed022017-10-12 01:26:55 +1030162 prop = descriptor['property']
Andrew Jefferye4dcaef2017-10-11 18:08:01 +1030163 dbus_prop = dbus_iface.Get(iface, prop)
164 print '{:<20}: {}'.format(prop, str(dbus_prop))
Andrew Jefferyde4ed022017-10-12 01:26:55 +1030165 result = True
Andrew Jefferye4dcaef2017-10-11 18:08:01 +1030166 else:
167 dbus_iface = dbus.Interface(dbus_obj, "org.freedesktop.DBus.Properties")
168 props = dbus_iface.GetAll(iface)
169 for p in props:
Andrew Jefferybba82bd2017-10-11 22:20:30 +1030170 print "{} = {}".format(p, str(props[p]))
Andrew Jefferyde4ed022017-10-12 01:26:55 +1030171 result = True
Andrew Jefferye4dcaef2017-10-11 18:08:01 +1030172
Andrew Jefferyde4ed022017-10-12 01:26:55 +1030173 return result
174
175def run_all_commands(dbus_bus, recipe, args):
Andrew Jefferyc6b5deb2017-10-11 22:18:27 +1030176 if isinstance(recipe, dict):
Andrew Jefferyde4ed022017-10-12 01:26:55 +1030177 return run_one_command(dbus_bus, recipe, args)
Andrew Jeffery7b376e72017-10-11 18:12:05 +1030178
Andrew Jefferyc6b5deb2017-10-11 22:18:27 +1030179 assert isinstance(recipe, list)
180 for command in recipe:
Andrew Jefferyde4ed022017-10-12 01:26:55 +1030181 descriptor = descriptors[command]
182 if not run_one_command(dbus_bus, descriptor, args):
183 print "Failed to execute command: {}".format(descriptor)
184 return False
185
186 return True
Andrew Jeffery7b376e72017-10-11 18:12:05 +1030187
Andrew Jeffery1be4fab2017-10-16 14:13:53 +1030188def gpio_set_value(gpio_name, active_low, asserted):
189 gpio_id = obmc.system.convertGpio(gpio_name)
190 gpio_value_path = "/sys/class/gpio/gpio{}/value".format(gpio_id)
191
192 with open(gpio_value_path, 'w') as gpio:
193 # Inversion behaviour needs to change with the resolution of
194 # https://github.com/openbmc/openbmc/issues/2489, where properly
195 # configuring the kernel will allow it to handle the inversion for us.
196 gpio.write(str(int(asserted ^ active_low)))
197
198 return True
199
200def gpio_deassert(gpio_name, active_low, args):
201 # Deal with silly python2 exception handling as outlined in main
202 if args.verbose:
203 return gpio_set_value(gpio_name, active_low, False)
204
205 try:
206 return gpio_set_value(gpio_name, active_low, False)
207 except IOError as e:
208 print >> sys.stderr, "Failed to access GPIO {}: {}".format(gpio_name, e.message)
209 return False
210
211def run_chassiskill(args):
212 # We shouldn't be able to invoke run_chassiskill() unless it's been
213 # explicitly added as a valid command to argparse in main()
214 assert can_chassiskill()
215
Matt Spinlerc4390de2018-08-08 11:16:40 -0500216 data = {}
217 with open(GPIO_DEFS_FILE, 'r') as json_input:
218 data = json.load(json_input)
Andrew Jeffery1be4fab2017-10-16 14:13:53 +1030219
Matt Spinlerc4390de2018-08-08 11:16:40 -0500220 gpios = data['gpio_configs']['power_config']['power_up_outs']
221 defs = data['gpio_definitions']
Andrew Jeffery1be4fab2017-10-16 14:13:53 +1030222
223 for gpio in gpios:
Andrew Jeffery1be4fab2017-10-16 14:13:53 +1030224
Matt Spinlerc4390de2018-08-08 11:16:40 -0500225 definition = filter(lambda g: g['name'] == gpio['name'], defs)
226 if len(definition) == 0:
227 print >> sys.stderr, "Missing definition for GPIO {}".format(gpio['name'])
Andrew Jeffery1be4fab2017-10-16 14:13:53 +1030228 continue
229
Matt Spinlerc4390de2018-08-08 11:16:40 -0500230 pin = str(definition[0]['pin'])
231 active_low = not gpio['polarity']
Andrew Jeffery1be4fab2017-10-16 14:13:53 +1030232
Matt Spinlerc4390de2018-08-08 11:16:40 -0500233 if not gpio_deassert(pin, active_low, args):
Andrew Jeffery1be4fab2017-10-16 14:13:53 +1030234 return False
235
236 return True
237
238def can_chassiskill():
Andrew Jeffery1be4fab2017-10-16 14:13:53 +1030239
Matt Spinlerc4390de2018-08-08 11:16:40 -0500240 try:
241 with open(GPIO_DEFS_FILE, 'r') as json_input:
242 data = json.load(json_input)
243 gpios = data['gpio_configs']['power_config']['power_up_outs']
244 return len(gpios) > 0
245 except:
246 pass
Andrew Jeffery1be4fab2017-10-16 14:13:53 +1030247
248 return False
249
Andrew Jeffery44e6d9c2017-10-11 16:57:31 +1030250def main():
Andrew Jefferyde4ed022017-10-12 01:26:55 +1030251 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
252
Gunnar Millsaa4c3102017-10-25 20:50:47 -0500253 # Conditionally add the `chassiskill` command based on whether the
Andrew Jeffery1be4fab2017-10-16 14:13:53 +1030254 # required GPIO configuration is present in the system description.
255 if can_chassiskill():
256 descriptors['chassiskill'] = None
257
Andrew Jeffery0b6883d2017-10-11 22:08:55 +1030258 parser = argparse.ArgumentParser()
Andrew Jefferyb43f7ed2017-10-12 01:33:59 +1030259 parser.add_argument('--verbose', '-v', action='store_true',
260 help="Verbose output")
Andrew Jefferyde4ed022017-10-12 01:26:55 +1030261 parser.add_argument('--wait', '-w', action='store_true',
262 help='Block until the state transition succeeds or fails')
Andrew Jefferyb43f7ed2017-10-12 01:33:59 +1030263 parser.add_argument('--wait-tune', '-t', nargs='?', default=8, type=float,
264 # help='Seconds to wait for journal output to complete after receiving DBus signal',
265 help=argparse.SUPPRESS)
Andrew Jefferyc6b5deb2017-10-11 22:18:27 +1030266 parser.add_argument('recipe', choices=sorted(descriptors.keys()))
Andrew Jeffery0b6883d2017-10-11 22:08:55 +1030267 args = parser.parse_args()
Andrew Geissler8f8cbce2017-02-20 16:10:26 -0600268
Andrew Jeffery1be4fab2017-10-16 14:13:53 +1030269 # This is a special case: directly pull the power, don't do any D-Bus
270 # related stuff
271 if args.recipe == "chassiskill":
272 return run_chassiskill(args)
273
Andrew Jefferybdb74352017-10-11 17:29:48 +1030274 dbus_bus = dbus.SystemBus()
Andrew Jefferycf6a7842017-10-12 14:47:43 +1030275
276 # The only way to get a sensible backtrace with python 2 without stupid
277 # hoops is to let the uncaught exception handler do the work for you.
278 # Catching and binding an exception appears to overwrite the stack trace at
279 # the point of bind.
280 #
281 # So, if we're in verbose mode, don't try to catch the DBus exception. That
282 # way we can understand where it originated.
283 if args.verbose:
284 return run_all_commands(dbus_bus, descriptors[args.recipe], args)
285
286 # Otherwise, we don't care about the traceback. Just catch it and print the
287 # error message.
Andrew Jeffery0b6883d2017-10-11 22:08:55 +1030288 try:
Andrew Jefferyde4ed022017-10-12 01:26:55 +1030289 return run_all_commands(dbus_bus, descriptors[args.recipe], args)
Andrew Jefferycf6a7842017-10-12 14:47:43 +1030290 except dbus.exceptions.DBusException as e:
291 print >> sys.stderr, "DBus error occurred: {}".format(e.get_dbus_message())
Andrew Jeffery0b6883d2017-10-11 22:08:55 +1030292 finally:
293 dbus_bus.close()
Andrew Jeffery44e6d9c2017-10-11 16:57:31 +1030294
295if __name__ == "__main__":
Andrew Jefferyde4ed022017-10-12 01:26:55 +1030296 sys.exit(0 if main() else 1)