1#!/usr/bin/env python 2 3""" 4This script reads in fan definition and zone definition YAML 5files and generates a set of structures for use by the fan control code. 6""" 7 8import os 9import sys 10import yaml 11from argparse import ArgumentParser 12from mako.template import Template 13 14tmpl = '''/* This is a generated file. */ 15#include <sdbusplus/bus.hpp> 16#include "manager.hpp" 17#include "functor.hpp" 18#include "actions.hpp" 19#include "handlers.hpp" 20 21using namespace phosphor::fan::control; 22using namespace sdbusplus::bus::match::rules; 23 24const unsigned int Manager::_powerOnDelay{${mgr_data['power_on_delay']}}; 25 26const std::vector<ZoneGroup> Manager::_zoneLayouts 27{ 28%for zone_group in zones: 29 ZoneGroup{ 30 std::vector<Condition>{ 31 %for condition in zone_group['conditions']: 32 Condition{ 33 "${condition['type']}", 34 std::vector<ConditionProperty>{ 35 %for property in condition['properties']: 36 ConditionProperty{ 37 "${property['property']}", 38 "${property['interface']}", 39 "${property['path']}", 40 static_cast<${property['type']}>(${property['value']}), 41 }, 42 %endfor 43 }, 44 }, 45 %endfor 46 }, 47 std::vector<ZoneDefinition>{ 48 %for zone in zone_group['zones']: 49 ZoneDefinition{ 50 ${zone['num']}, 51 ${zone['full_speed']}, 52 ${zone['default_floor']}, 53 ${zone['increase_delay']}, 54 ${zone['decrease_interval']}, 55 std::vector<FanDefinition>{ 56 %for fan in zone['fans']: 57 FanDefinition{ 58 "${fan['name']}", 59 std::vector<std::string>{ 60 %for sensor in fan['sensors']: 61 "${sensor}", 62 %endfor 63 } 64 }, 65 %endfor 66 }, 67 std::vector<SetSpeedEvent>{ 68 %for event in zone['events']: 69 SetSpeedEvent{ 70 Group{ 71 %for member in event['group']: 72 { 73 "${member['name']}", 74 {"${member['interface']}", 75 "${member['property']}"} 76 }, 77 %endfor 78 }, 79 make_action(action::${event['action']['name']}( 80 %for i, p in enumerate(event['action']['parameters']): 81 %if (i+1) != len(event['action']['parameters']): 82 static_cast<${p['type']}>(${p['value']}), 83 %else: 84 static_cast<${p['type']}>(${p['value']}) 85 %endif 86 %endfor 87 )), 88 std::vector<PropertyChange>{ 89 %for s in event['signal']: 90 PropertyChange{ 91 interface("org.freedesktop.DBus.Properties") + 92 member("PropertiesChanged") + 93 type::signal() + 94 path("${s['path']}") + 95 arg0namespace("${s['interface']}"), 96 make_handler(propertySignal<${s['type']}>( 97 "${s['interface']}", 98 "${s['property']}", 99 handler::setProperty<${s['type']}>( 100 "${s['member']}", 101 "${s['interface']}", 102 "${s['property']}" 103 ) 104 )) 105 }, 106 %endfor 107 } 108 }, 109 %endfor 110 } 111 }, 112 %endfor 113 } 114 }, 115%endfor 116}; 117''' 118 119 120def convertToMap(listOfDict): 121 """ 122 Converts a list of dictionary entries to a std::map initialization list. 123 """ 124 listOfDict = listOfDict.replace('[', '{') 125 listOfDict = listOfDict.replace(']', '}') 126 listOfDict = listOfDict.replace(':', ',') 127 return listOfDict 128 129 130def getEventsInZone(zone_num, zone_conditions, events_data): 131 """ 132 Constructs the event entries defined for each zone using the events yaml 133 provided. 134 """ 135 events = [] 136 137 if 'events' in events_data: 138 for e in events_data['events']: 139 140 # Zone numbers are optional in the events yaml but skip if this 141 # zone's zone number is not in the event's zone numbers 142 if all('zones' in z and z['zones'] is not None and 143 zone_num not in z['zones'] for z in e['zone_conditions']): 144 continue 145 146 # Zone conditions are optional in the events yaml but skip if this 147 # event's condition is not in this zone's conditions 148 if all('name' in z and z['name'] is not None and 149 not any(c['name'] == z['name'] for c in zone_conditions) 150 for z in e['zone_conditions']): 151 continue 152 153 event = {} 154 # Add set speed event group 155 group = [] 156 groups = next(g for g in events_data['groups'] 157 if g['name'] == e['group']) 158 for member in groups['members']: 159 members = {} 160 members['type'] = groups['type'] 161 members['name'] = ("/xyz/openbmc_project/" + 162 groups['type'] + 163 member) 164 members['interface'] = e['interface'] 165 members['property'] = e['property']['name'] 166 group.append(members) 167 event['group'] = group 168 169 # Add set speed action and function parameters 170 action = {} 171 actions = next(a for a in events_data['actions'] 172 if a['name'] == e['action']['name']) 173 action['name'] = actions['name'] 174 params = [] 175 for p in actions['parameters']: 176 param = {} 177 if type(e['action'][p]) is not dict: 178 if p == 'property': 179 param['value'] = str(e['action'][p]).lower() 180 param['type'] = str(e['property']['type']).lower() 181 else: 182 # Default type to 'size_t' when not given 183 param['value'] = str(e['action'][p]).lower() 184 param['type'] = 'size_t' 185 params.append(param) 186 else: 187 param['type'] = str(e['action'][p]['type']).lower() 188 if p != 'map': 189 param['value'] = str(e['action'][p]['value']).lower() 190 else: 191 emap = convertToMap(str(e['action'][p]['value'])) 192 param['value'] = param['type'] + emap 193 params.append(param) 194 action['parameters'] = params 195 event['action'] = action 196 197 # Add property change signal handler 198 signal = [] 199 for path in group: 200 signals = {} 201 signals['path'] = path['name'] 202 signals['interface'] = e['interface'] 203 signals['property'] = e['property']['name'] 204 signals['type'] = e['property']['type'] 205 signals['member'] = path['name'] 206 signal.append(signals) 207 event['signal'] = signal 208 209 events.append(event) 210 211 return events 212 213 214def getFansInZone(zone_num, profiles, fan_data): 215 """ 216 Parses the fan definition YAML files to find the fans 217 that match both the zone passed in and one of the 218 cooling profiles. 219 """ 220 221 fans = [] 222 223 for f in fan_data['fans']: 224 225 if zone_num != f['cooling_zone']: 226 continue 227 228 # 'cooling_profile' is optional (use 'all' instead) 229 if f.get('cooling_profile') is None: 230 profile = "all" 231 else: 232 profile = f['cooling_profile'] 233 234 if profile not in profiles: 235 continue 236 237 fan = {} 238 fan['name'] = f['inventory'] 239 fan['sensors'] = f['sensors'] 240 fans.append(fan) 241 242 return fans 243 244 245def getConditionInZoneConditions(zone_condition, zone_conditions_data): 246 """ 247 Parses the zone conditions definition YAML files to find the condition 248 that match both the zone condition passed in. 249 """ 250 251 condition = {} 252 253 for c in zone_conditions_data['conditions']: 254 255 if zone_condition != c['name']: 256 continue 257 condition['type'] = c['type'] 258 properties = [] 259 for p in c['properties']: 260 property = {} 261 property['property'] = p['property'] 262 property['interface'] = p['interface'] 263 property['path'] = p['path'] 264 property['type'] = p['type'].lower() 265 property['value'] = str(p['value']).lower() 266 properties.append(property) 267 condition['properties'] = properties 268 269 return condition 270 271 272def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data): 273 """ 274 Combines the zone definition YAML and fan 275 definition YAML to create a data structure defining 276 the fan cooling zones. 277 """ 278 279 zone_groups = [] 280 281 for group in zone_data: 282 conditions = [] 283 # zone conditions are optional 284 if 'zone_conditions' in group and group['zone_conditions'] is not None: 285 for c in group['zone_conditions']: 286 287 if not zone_conditions_data: 288 sys.exit("No zone_conditions YAML file but " + 289 "zone_conditions used in zone YAML") 290 291 condition = getConditionInZoneConditions(c['name'], 292 zone_conditions_data) 293 294 if not condition: 295 sys.exit("Missing zone condition " + c['name']) 296 297 conditions.append(condition) 298 299 zone_group = {} 300 zone_group['conditions'] = conditions 301 302 zones = [] 303 for z in group['zones']: 304 zone = {} 305 306 # 'zone' is required 307 if ('zone' not in z) or (z['zone'] is None): 308 sys.exit("Missing fan zone number in " + zone_yaml) 309 310 zone['num'] = z['zone'] 311 312 zone['full_speed'] = z['full_speed'] 313 314 zone['default_floor'] = z['default_floor'] 315 316 # 'increase_delay' is optional (use 0 by default) 317 key = 'increase_delay' 318 zone[key] = z.setdefault(key, 0) 319 320 # 'decrease_interval' is optional (use 0 by default) 321 key = 'decrease_interval' 322 zone[key] = z.setdefault(key, 0) 323 324 # 'cooling_profiles' is optional (use 'all' instead) 325 if ('cooling_profiles' not in z) or \ 326 (z['cooling_profiles'] is None): 327 profiles = ["all"] 328 else: 329 profiles = z['cooling_profiles'] 330 331 fans = getFansInZone(z['zone'], profiles, fan_data) 332 events = getEventsInZone(z['zone'], group['zone_conditions'], 333 events_data) 334 335 if len(fans) == 0: 336 sys.exit("Didn't find any fans in zone " + str(zone['num'])) 337 338 zone['fans'] = fans 339 zone['events'] = events 340 zones.append(zone) 341 342 zone_group['zones'] = zones 343 zone_groups.append(zone_group) 344 345 return zone_groups 346 347 348if __name__ == '__main__': 349 parser = ArgumentParser( 350 description="Phosphor fan zone definition parser") 351 352 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml', 353 default="example/zones.yaml", 354 help='fan zone definitional yaml') 355 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml', 356 default="example/fans.yaml", 357 help='fan definitional yaml') 358 parser.add_argument('-e', '--events_yaml', dest='events_yaml', 359 help='events to set speeds yaml') 360 parser.add_argument('-c', '--zone_conditions_yaml', 361 dest='zone_conditions_yaml', 362 help='conditions to determine zone yaml') 363 parser.add_argument('-o', '--output_dir', dest='output_dir', 364 default=".", 365 help='output directory') 366 args = parser.parse_args() 367 368 if not args.zone_yaml or not args.fan_yaml: 369 parser.print_usage() 370 sys.exit(-1) 371 372 with open(args.zone_yaml, 'r') as zone_input: 373 zone_data = yaml.safe_load(zone_input) or {} 374 375 with open(args.fan_yaml, 'r') as fan_input: 376 fan_data = yaml.safe_load(fan_input) or {} 377 378 events_data = {} 379 if args.events_yaml: 380 with open(args.events_yaml, 'r') as events_input: 381 events_data = yaml.safe_load(events_input) or {} 382 383 zone_conditions_data = {} 384 if args.zone_conditions_yaml: 385 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input: 386 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {} 387 388 zone_config = buildZoneData(zone_data.get('zone_configuration', {}), 389 fan_data, events_data, zone_conditions_data) 390 391 manager_config = zone_data.get('manager_configuration', {}) 392 393 if manager_config.get('power_on_delay') is None: 394 manager_config['power_on_delay'] = 0 395 396 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp") 397 with open(output_file, 'w') as output: 398 output.write(Template(tmpl).render(zones=zone_config, 399 mgr_data=manager_config)) 400