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