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