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 std::vector<FanDefinition>{ 53 %for fan in zone['fans']: 54 FanDefinition{ 55 "${fan['name']}", 56 std::vector<std::string>{ 57 %for sensor in fan['sensors']: 58 "${sensor}", 59 %endfor 60 } 61 }, 62 %endfor 63 }, 64 std::vector<SetSpeedEvent>{ 65 %for event in zone['events']: 66 SetSpeedEvent{ 67 Group{ 68 %for member in event['group']: 69 { 70 "${member['name']}", 71 {"${member['interface']}", 72 "${member['property']}"} 73 }, 74 %endfor 75 }, 76 make_action(action::${event['action']['name']}( 77 %for i, p in enumerate(event['action']['parameters']): 78 %if (i+1) != len(event['action']['parameters']): 79 static_cast<${p['type']}>(${p['value']}), 80 %else: 81 static_cast<${p['type']}>(${p['value']}) 82 %endif 83 %endfor 84 )), 85 std::vector<PropertyChange>{ 86 %for s in event['signal']: 87 PropertyChange{ 88 interface("org.freedesktop.DBus.Properties") + 89 member("PropertiesChanged") + 90 type::signal() + 91 path("${s['path']}") + 92 arg0namespace("${s['interface']}"), 93 make_handler(propertySignal<${s['type']}>( 94 "${s['interface']}", 95 "${s['property']}", 96 handler::setProperty<${s['type']}>( 97 "${s['member']}", 98 "${s['property']}" 99 ) 100 )) 101 }, 102 %endfor 103 } 104 }, 105 %endfor 106 } 107 }, 108 %endfor 109 } 110 }, 111%endfor 112}; 113''' 114 115 116def getEventsInZone(zone_num, events_data): 117 """ 118 Constructs the event entries defined for each zone using the events yaml 119 provided. 120 """ 121 events = [] 122 123 if 'events' in events_data: 124 for e in events_data['events']: 125 for z in e['zone_conditions']: 126 if zone_num not in z['zones']: 127 continue 128 129 event = {} 130 # Add set speed event group 131 group = [] 132 groups = next(g for g in events_data['groups'] 133 if g['name'] == e['group']) 134 for member in groups['members']: 135 members = {} 136 members['type'] = groups['type'] 137 members['name'] = ("/xyz/openbmc_project/" + 138 groups['type'] + 139 member) 140 members['interface'] = e['interface'] 141 members['property'] = e['property']['name'] 142 group.append(members) 143 event['group'] = group 144 145 # Add set speed action and function parameters 146 action = {} 147 actions = next(a for a in events_data['actions'] 148 if a['name'] == e['action']['name']) 149 action['name'] = actions['name'] 150 params = [] 151 for p in actions['parameters']: 152 param = {} 153 if type(e['action'][p]) is not dict: 154 if p == 'property': 155 param['value'] = str(e['action'][p]).lower() 156 param['type'] = str(e['property']['type']).lower() 157 else: 158 # Default type to 'size_t' when not given 159 param['value'] = str(e['action'][p]).lower() 160 param['type'] = 'size_t' 161 params.append(param) 162 else: 163 param['value'] = str(e['action'][p]['value']).lower() 164 param['type'] = str(e['action'][p]['type']).lower() 165 params.append(param) 166 action['parameters'] = params 167 event['action'] = action 168 169 # Add property change signal handler 170 signal = [] 171 for path in group: 172 signals = {} 173 signals['path'] = path['name'] 174 signals['interface'] = e['interface'] 175 signals['property'] = e['property']['name'] 176 signals['type'] = e['property']['type'] 177 signals['member'] = path['name'] 178 signal.append(signals) 179 event['signal'] = signal 180 181 events.append(event) 182 183 return events 184 185 186def getFansInZone(zone_num, profiles, fan_data): 187 """ 188 Parses the fan definition YAML files to find the fans 189 that match both the zone passed in and one of the 190 cooling profiles. 191 """ 192 193 fans = [] 194 195 for f in fan_data['fans']: 196 197 if zone_num != f['cooling_zone']: 198 continue 199 200 # 'cooling_profile' is optional (use 'all' instead) 201 if f.get('cooling_profile') is None: 202 profile = "all" 203 else: 204 profile = f['cooling_profile'] 205 206 if profile not in profiles: 207 continue 208 209 fan = {} 210 fan['name'] = f['inventory'] 211 fan['sensors'] = f['sensors'] 212 fans.append(fan) 213 214 return fans 215 216 217def getConditionInZoneConditions(zone_condition, zone_conditions_data): 218 """ 219 Parses the zone conditions definition YAML files to find the condition 220 that match both the zone condition passed in. 221 """ 222 223 condition = {} 224 225 for c in zone_conditions_data['conditions']: 226 227 if zone_condition != c['name']: 228 continue 229 condition['type'] = c['type'] 230 properties = [] 231 for p in c['properties']: 232 property = {} 233 property['property'] = p['property'] 234 property['interface'] = p['interface'] 235 property['path'] = p['path'] 236 property['type'] = p['type'].lower() 237 property['value'] = str(p['value']).lower() 238 properties.append(property) 239 condition['properties'] = properties 240 241 return condition 242 243 244def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data): 245 """ 246 Combines the zone definition YAML and fan 247 definition YAML to create a data structure defining 248 the fan cooling zones. 249 """ 250 251 zone_groups = [] 252 253 for group in zone_data: 254 conditions = [] 255 # zone conditions are optional 256 if 'zone_conditions' in group and group['zone_conditions'] is not None: 257 for c in group['zone_conditions']: 258 259 if not zone_conditions_data: 260 sys.exit("No zone_conditions YAML file but" + 261 "zone_conditions used in zone YAML") 262 263 condition = getConditionInZoneConditions(c['name'], 264 zone_conditions_data) 265 266 if not condition: 267 sys.exit("Missing zone condition " + c['name']) 268 269 conditions.append(condition) 270 271 zone_group = {} 272 zone_group['conditions'] = conditions 273 274 zones = [] 275 for z in group['zones']: 276 zone = {} 277 278 # 'zone' is required 279 if ('zone' not in z) or (z['zone'] is None): 280 sys.exit("Missing fan zone number in " + zone_yaml) 281 282 zone['num'] = z['zone'] 283 284 zone['full_speed'] = z['full_speed'] 285 286 # 'cooling_profiles' is optional (use 'all' instead) 287 if ('cooling_profiles' not in z) or \ 288 (z['cooling_profiles'] is None): 289 profiles = ["all"] 290 else: 291 profiles = z['cooling_profiles'] 292 293 fans = getFansInZone(z['zone'], profiles, fan_data) 294 events = getEventsInZone(z['zone'], events_data) 295 296 if len(fans) == 0: 297 sys.exit("Didn't find any fans in zone " + str(zone['num'])) 298 299 zone['fans'] = fans 300 zone['events'] = events 301 zones.append(zone) 302 303 zone_group['zones'] = zones 304 zone_groups.append(zone_group) 305 306 return zone_groups 307 308 309if __name__ == '__main__': 310 parser = ArgumentParser( 311 description="Phosphor fan zone definition parser") 312 313 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml', 314 default="example/zones.yaml", 315 help='fan zone definitional yaml') 316 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml', 317 default="example/fans.yaml", 318 help='fan definitional yaml') 319 parser.add_argument('-e', '--events_yaml', dest='events_yaml', 320 help='events to set speeds yaml') 321 parser.add_argument('-c', '--zone_conditions_yaml', 322 dest='zone_conditions_yaml', 323 help='conditions to determine zone yaml') 324 parser.add_argument('-o', '--output_dir', dest='output_dir', 325 default=".", 326 help='output directory') 327 args = parser.parse_args() 328 329 if not args.zone_yaml or not args.fan_yaml: 330 parser.print_usage() 331 sys.exit(-1) 332 333 with open(args.zone_yaml, 'r') as zone_input: 334 zone_data = yaml.safe_load(zone_input) or {} 335 336 with open(args.fan_yaml, 'r') as fan_input: 337 fan_data = yaml.safe_load(fan_input) or {} 338 339 events_data = {} 340 if args.events_yaml: 341 with open(args.events_yaml, 'r') as events_input: 342 events_data = yaml.safe_load(events_input) or {} 343 344 zone_conditions_data = {} 345 if args.zone_conditions_yaml: 346 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input: 347 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {} 348 349 zone_config = buildZoneData(zone_data.get('zone_configuration', {}), 350 fan_data, events_data, zone_conditions_data) 351 352 manager_config = zone_data.get('manager_configuration', {}) 353 354 if manager_config.get('power_on_delay') is None: 355 manager_config['power_on_delay'] = 0 356 357 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp") 358 with open(output_file, 'w') as output: 359 output.write(Template(tmpl).render(zones=zone_config, 360 mgr_data=manager_config)) 361