1#!/usr/bin/env perl
2use strict;
3use warnings;
4
5use mrw::Targets; # Set of APIs allowing access to parsed ServerWiz2 XML output
6use mrw::Inventory; # To get list of Inventory targets
7use Getopt::Long; # For parsing command line arguments
8use Data::Dumper qw(Dumper); # Dumping blob
9use List::Util qw(first);
10
11# Globals
12my $force           = 0;
13my $serverwizFile  = "";
14my $debug           = 0;
15my $outputFile     = "";
16my $verbose         = 0;
17
18# Command line argument parsing
19GetOptions(
20"f"   => \$force,             # numeric
21"i=s" => \$serverwizFile,    # string
22"o=s" => \$outputFile,       # string
23"d"   => \$debug,
24"v"   => \$verbose,
25)
26or printUsage();
27
28if (($serverwizFile eq "") or ($outputFile eq ""))
29{
30    printUsage();
31}
32
33# Hashmap of all the LED groups with the properties
34my %hashGroup;
35
36# hash of targets to Names that have the FRU Inventory instances
37my %invHash;
38
39# Hash of Enclosure Fault LED names and their properties
40# These are generally front-fault-led and rear-fault-led
41my %encFaults;
42
43# These groups are a must in all the systems.
44# Its fine if they don't map to any physical LED
45my @defaultGroup = ("BmcBooted", "PowerOn");
46
47# This group contains all the LEDs with the action Blink
48my $lampTest = "LampTest";
49
50# API used to access parsed XML data
51my $targetObj = Targets->new;
52if($verbose == 1)
53{
54    $targetObj->{debug} = 1;
55}
56
57if($force == 1)
58{
59    $targetObj->{force} = 1;
60}
61
62$targetObj->loadXML($serverwizFile);
63print "Loaded MRW XML: $serverwizFile \n";
64
65# Iterate over Inventory and get all the Inventory targets.
66my @inventory = Inventory::getInventory($targetObj);
67for my $item (@inventory)
68{
69    # Target to Obmc_Name hash.
70    $invHash{$item->{TARGET}} = $item->{OBMC_NAME};
71}
72
73# For debugging purpose.
74printDebug("\nList of Inventory targets\n");
75foreach my $key (sort keys %invHash)
76{
77    printDebug("$invHash{$key}\n");
78}
79
80# Process all the targets in the XML. If the target is associated with a FRU,
81# then remember it so that when we do the FRU inventory lookup, we know if
82# that Inventory has a LED associated with it or not.
83foreach my $target (sort keys %{$targetObj->getAllTargets()})
84{
85    # Some the target instances may *not* have this MRW_TYPE attribute.
86    if($targetObj->isBadAttribute($target, "MRW_TYPE"))
87    {
88        next;
89    }
90
91    # Return true if not populated -or- not present
92    if("LED" eq $targetObj->getMrwType($target))
93    {
94        # Just for clarity.
95        my $ledTarget = $target;
96
97        # OBMC_NAME field of the FRU
98        # fruPath ex /system/chassis/motherboard/dimm1
99        # device "dimm1"
100        my $fruPath = '';
101        my $device = '';
102
103        # Find if this LED is associated with a FRU.
104        # Example, FAN will have LED on that assembly.
105        my $conns = $targetObj->findConnections($ledTarget, "LOGICAL_ASSOCIATION");
106        if ($conns ne "")
107        {
108            # This LED is associated with a FRU
109            for my $conn (@{$conns->{CONN}})
110            {
111                my $destTarget = $conn->{DEST_PARENT};
112                # If we have found this, then that means, we do not need to
113                # hand cook a group name. delete this value from the inventory
114                # array
115                if(exists($invHash{$destTarget}))
116                {
117                    # This will remove a particular {key, value} pair
118                    $fruPath = $invHash{$destTarget};
119                    printDebug("$destTarget : $fruPath is having associated LED\n");
120                    delete ($invHash{$destTarget});
121                }
122            }
123            # fetch FruName from the device path
124            $device = getFruName($fruPath);
125            printDebug("$target; $device has device\n");
126        }
127
128        if($targetObj->isBadAttribute($ledTarget, "CONTROL_GROUPS"))
129        {
130            next;
131        }
132
133        # By default, Blink takes higher priority
134        my $priority = "'Blink'";
135
136        # Get the priority. Since rest everything is populated,
137        # default to Blink  than err'ing out. Not checking for
138        # validity of this since it must be present.
139        if($targetObj->getAttribute($ledTarget, "LED_PRIORITY") eq "ON")
140        {
141            $priority = "'On'";
142        }
143
144        #The MRW instance name must match the LED name in the device tree
145        my $name = $targetObj->getInstanceName($ledTarget);
146
147        # Get if this LED is a ENC-FAULT type.
148        if(!$targetObj->isBadAttribute($target, "LED_TYPE"))
149        {
150            if("ENC-FAULT" eq $targetObj->getAttribute($ledTarget, "LED_TYPE"))
151            {
152                $encFaults{$name} = $priority;
153            }
154        }
155
156        # Defines the LEDs and the Groups that they belong to
157        my $controlGroup = $targetObj->getAttribute($ledTarget, "CONTROL_GROUPS");
158
159        #remove spaces, because serverwiz isn't good at removing them itself
160        $controlGroup =~ s/\s//g;
161        my @groups= split(',', $controlGroup);  #just a long 16x3 = 48 element list
162
163        for (my $i = 0; $i < scalar @groups; $i += 3)
164        {
165            if (($groups[$i] ne "NA") && ($groups[$i] ne ""))
166            {
167                my $groupName = $groups[$i];
168                printDebug("$groupName\n");
169
170                my $blinkFreq = $groups[$i+1];
171                my $action = "'On'";
172                my $period = 0;
173
174                # Period in milli seconds
175                my $dutyCycle = $groups[$i+2];
176                if($blinkFreq > 0)
177                {
178                    $action = "'Blink'";
179                    $period = (1 / $blinkFreq) * 1000;
180                }
181
182                # Insert into hash map;
183                $hashGroup{$groupName}{$name}{"Action"} = $action;
184                $hashGroup{$groupName}{$name}{"Period"} = $period;
185                $hashGroup{$groupName}{$name}{"DutyOn"} = $dutyCycle;
186                $hashGroup{$groupName}{$name}{"Priority"} = $priority;
187
188                # Need to update the LampTest group.
189                $hashGroup{$lampTest}{$name}{"Action"} = "'Blink'";
190                $hashGroup{$lampTest}{$name}{"Period"} = 1000;
191                $hashGroup{$lampTest}{$name}{"DutyOn"} = 50;
192
193                # Priority of a particular LED needs to stay SAME across
194                # all groups
195                $hashGroup{$lampTest}{$name}{"Priority"} = $priority;
196            }
197        } # Walk CONTROL_GROUP
198    } # Has LED target
199} # All the targets
200
201
202# These are the FRUs that do not have associated LEDs. All of these need to be
203# mapped to some group, which will be named after this target name and the
204# elements of the group are EnclosureFaults Front and Back
205printDebug("\n======================================================================\n");
206printDebug("\nFRUs that do not have associated LEDs\n");
207foreach my $key (sort keys %invHash)
208{
209    my $device = getFruName($invHash{$key});
210
211    # For each of these device, the Group record would be this :
212    my $groupName = $device . "Fault";
213    printDebug("$device :: $groupName\n");
214
215    # Setup roll-up LEDs to the ones that are of type ENC-FAULT
216    foreach my $led (sort keys %encFaults)
217    {
218        $hashGroup{$groupName}{$led}{"Action"} = "'On'";
219        $hashGroup{$groupName}{$led}{"Period"} = 0;
220        $hashGroup{$groupName}{$led}{"DutyOn"} = 50;
221
222        # Priority of a particular LED needs to stay SAME across
223        # all groups
224        $hashGroup{$groupName}{$led}{"Priority"} = $encFaults{$led};
225    }
226}
227printDebug("\n======================================================================\n");
228
229my $index = rindex($outputFile, ".");
230my $suffix = substr($outputFile, $index + 1);
231if (lc($suffix) eq "json")
232{
233    # Generate the JSON file
234    generateJSONFile();
235}
236else
237{
238    # Generate the yaml file
239    generateYamlFile();
240}
241
242#------------------------------------END OF MAIN-----------------------
243
244# Gven a '/' separated string, returns the leaf.
245# Ex: /a/b/c/d returns device=d
246sub getFruName
247{
248    my $path = shift;
249    my $device = '';
250    my $lastSlash=rindex($path, '/');
251    $device=substr($path, $lastSlash+1);
252}
253
254sub generateYamlFile
255{
256    my $fileName = $outputFile;
257    my $groupCopy = '';
258    my $ledCopy = '';
259    open(my $fh, '>', $fileName) or die "Could not open file '$fileName' $!";
260
261    foreach my $group (sort keys %hashGroup)
262    {
263        if($group ne $groupCopy)
264        {
265            # If one of these is a default group, then delete it from the array
266            # that is being maintained to create one by hand if all default ones
267            # are not defined
268            my $index = first {$defaultGroup[$_] eq $group} 0..$#defaultGroup;
269            if (defined $index)
270            {
271                splice @defaultGroup, $index, 1;
272            }
273
274            $groupCopy = '';
275            $ledCopy = '';
276        }
277
278        foreach my $led (sort keys %{ $hashGroup{$group} })
279        {
280            foreach my $property (sort keys %{ $hashGroup{$group}{$led}})
281            {
282                if($group ne $groupCopy)
283                {
284                    $groupCopy = $group;
285                    print $fh "$group:\n";
286                }
287                print $fh "    ";
288                if($led ne $ledCopy)
289                {
290                    $ledCopy = $led;
291                    print $fh "$led:\n";
292                    print $fh "    ";
293                }
294                print $fh "    ";
295                print $fh "$property:";
296                print $fh " $hashGroup{$group}{$led}{$property}\n";
297            }
298        }
299    }
300    # If we need to hand create some of the groups, do so now.
301    foreach my $name (@defaultGroup)
302    {
303        print $fh "$name:\n";
304    }
305    close $fh;
306}
307
308sub generateJSONFile
309{
310    package LEDGroups;
311    use JSON;
312    my $JSON = JSON->new->utf8->pretty(1);
313    $JSON->convert_blessed(1);
314
315    sub led
316    {
317        my $class = shift;
318        my $self = {
319            group => shift,
320            members => shift,
321        };
322        bless $self, $class;
323        return $self;
324    }
325
326    sub member
327    {
328        my $class = shift;
329        my $self = {
330            name => shift,
331            Action => shift,
332            DutyOn => shift,
333            Period => shift,
334            Priority => shift,
335        };
336        bless $self, $class;
337        return $self;
338    }
339
340    sub TO_JSON {
341        return { %{ shift() } };
342    }
343
344    my $fileName = $outputFile;
345    open(my $fh, '>', $fileName) or die "Could not open file '$fileName' $!";
346
347    my @leds = ();
348    foreach my $group (sort keys %hashGroup)
349    {
350        my @members = ();
351        foreach my $led (sort keys %{ $hashGroup{$group} })
352        {
353            my $action;
354            my $dutyOn;
355            my $period;
356            my $priority;
357
358            if (exists $hashGroup{$group}{$led}{Action})
359            {
360                $action = $hashGroup{$group}{$led}{Action};
361                $action = substr($action, 1, length($action) - 2);
362            }
363
364            if (exists $hashGroup{$group}{$led}{DutyOn})
365            {
366                $dutyOn = $hashGroup{$group}{$led}{DutyOn};
367            }
368
369            if (exists $hashGroup{$group}{$led}{Period})
370            {
371                $period = $hashGroup{$group}{$led}{Period};
372            }
373
374            if (exists $hashGroup{$group}{$led}{Priority})
375            {
376                $priority = $hashGroup{$group}{$led}{Priority};
377                $priority = substr($priority, 1, length($priority) - 2);
378            }
379
380            my $m = member LEDGroups($led, $action, $dutyOn, $period, $priority);
381            push @members, $m;
382        }
383        my $l = led LEDGroups($group, \@members);
384        push @leds, $l;
385    }
386    my %ledJson = ('leds' => \@leds);
387    my $json = $JSON->canonical(1)->encode(\%ledJson);
388    print $fh $json;
389    close $fh;
390}
391
392# Helper function to put debug statements.
393sub printDebug
394{
395    my $str = shift;
396    print "DEBUG: ", $str, "\n" if $debug;
397}
398
399# Usage
400sub printUsage
401{
402    print "
403    $0 -i [XML filename] -o [Output filename] [OPTIONS]
404Options:
405    -f = force output file creation even when errors
406    -d = debug mode
407    -v = verbose mode - for verbose o/p from Targets.pm
408
409PS: mrw::Targets can be found in https://github.com/open-power/serverwiz/
410    mrw::Inventory can be found in https://github.com/openbmc/phosphor-mrw-tools/
411    \n";
412    exit(1);
413}
414#------------------------------------END OF SUB-----------------------
415