gen_path_callouts: Write callouts to JSON file

Save all of the Callout objects to a JSON file.  This is where the FRU
callout has its duplicates removed and is sorted based on priority.

This also adds a new Util module function getLocationCode to find the
location code of a target.

The JSON file is organized into sections for each callout type,
with keys based on the type.  I2c uses a bus and address, FSI uses a
link, and SPI uses a bus number. If FSI is combined with I2C or SPI,
then the link plus the I2C/SPI keys is used.  Multi-hop FSI links are
indicated by a dash in between the links, eg "0-1".  A contrived example
of an entry in the FSI section, on link 5, is:

"FSI":
{
   "5":
   {
      "Callouts":[
        {
           "Priority":"H"
           "LocationCode": "P1-C50",
           "MRU":"/sys-0/node-0/motherboard/proc_socket-0/module-0/power9-0",
           "Name":"/sys-0/node-0/motherboard/cpu0"
        },
        {
           "Priority":"M",
           "LocationCode": "P1-C42",
           "MRU":"/sys-0/node-0/motherboard/ebmc-card/BMC-0",
           "Name":"/sys-0/node-0/motherboard/ebmc-card"
        },
        {
           "Priority":"L",
           "LocationCode": "P1",
           "Name":"/sys-0/node-0/motherboard"
        }
     ],
     "Dest":"/sys-0/node-0/motherboard-0/proc_socket-0/module-0/power9-0",
     "Source":"/sys-0/node-0/motherboard-0/ebmc-card-connector-0/card-0/bmc-0"
   }
}

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Iee3493834d5045c553a52c80bbb62f7aa71140e0
diff --git a/Util.pm b/Util.pm
index ea6b914..d555637 100644
--- a/Util.pm
+++ b/Util.pm
@@ -149,6 +149,61 @@
     return $port - 1;
 }
 
+# Get the location code for the target passed in, like P1-C5.
+#  $targets = the targets object
+#  $target = target to get the location code of
+sub getLocationCode
+{
+    my ($targets, $passedInTarget) = @_;
+    my $locCode = undef;
+    my $target = $passedInTarget;
+    my $done = 0;
+
+    # Walk up the parent chain prepending segments until an absolute
+    # location code is found.
+    while (!$done)
+    {
+        if (!$targets->isBadAttribute($target, "LOCATION_CODE"))
+        {
+            my $loc = $targets->getAttribute($target, "LOCATION_CODE");
+            my $type = $targets->getAttribute($target, "LOCATION_CODE_TYPE");
+
+            if (length($loc) > 0 )
+            {
+                if (defined $locCode)
+                {
+                    $locCode = $loc . '-' . $locCode;
+                }
+                else
+                {
+                    $locCode = $loc;
+                }
+
+                if ($type eq "ABSOLUTE")
+                {
+                    $done = 1;
+                }
+            }
+            else
+            {
+                die "Missing location code on $target\n" if $type eq "ABSOLUTE";
+            }
+        }
+
+        if (!$done)
+        {
+            $target = $targets->getTargetParent($target);
+            if (not defined $target)
+            {
+                die "Did not find complete location " .
+                    "code for $passedInTarget\n";
+            }
+        }
+    }
+
+    return $locCode;
+}
+
 1;
 
 =head1 NAME
@@ -200,6 +255,10 @@
 
 Returns C<I2CPort> converted from MRW numbering scheme to Linux numbering scheme.
 
+=item getLocationCode(C<TargetsObj, C<Target>)
+
+Gets the location code for the input Target.
+
 =back
 
 =cut
diff --git a/gen_path_callouts.pl b/gen_path_callouts.pl
index 9f357fb..c49524f 100755
--- a/gen_path_callouts.pl
+++ b/gen_path_callouts.pl
@@ -252,6 +252,7 @@
 # Build the single and multi segment callouts
 buildCallouts(\%allSegments, \@callouts);
 
+printJSON(\@callouts);
 
 # Write the segments to a JSON file
 if ($printSegments)
@@ -672,6 +673,162 @@
     return $spiCallout;
 }
 
+# Convert all of the *Callout objects to JSON and write them to a file.
+# It will convert the callout target names to location codes and also sort
+# the callout list before doing so.
+sub printJSON
+{
+    my $callouts = shift;
+    my %output;
+
+    for my $callout (@$callouts)
+    {
+        my %c;
+        $c{Source} = $callout->sourceChip();
+        $c{Dest} = $callout->destChip();
+
+        for my $fruCallout (@{$callout->calloutList()})
+        {
+            my %entry;
+
+            if (length($fruCallout->{Name}) == 0)
+            {
+                die "Could not get target name for a callout on path:\n" .
+                 "    (" . $callout->sourceChip() . ") ->\n" .
+                 "    (" . $callout->destChip() . ")\n";
+            }
+
+            $entry{Name} = $fruCallout->{Name};
+
+            $entry{LocationCode} =
+                Util::getLocationCode($targets, $fruCallout->{Name});
+
+            # MRUs - for now just use the path + MRU name
+            if (exists $fruCallout->{MRU})
+            {
+                $entry{MRU} = $entry{Name} . "/" . $fruCallout->{MRU};
+            }
+
+            $entry{Priority} = validatePriority($fruCallout->{Priority});
+
+            push @{$c{Callouts}}, \%entry;
+        }
+
+
+        # Remove duplicate callouts and sort
+        @{$c{Callouts}} = sortCallouts(@{$c{Callouts}});
+
+        # Setup the entry based on the callout type
+        if ($callout->type() eq "I2C")
+        {
+            # The address key is in decimal, but save the hex value
+            # for easier debug.
+            $c{HexAddress} = $callout->i2cAddress();
+            my $decimal = hex($callout->i2cAddress());
+
+            $output{"I2C"}{$callout->i2cBus()}{$decimal} = \%c;
+        }
+        elsif ($callout->type() eq "SPI")
+        {
+            $output{"SPI"}{$callout->spiBus()} = \%c;
+        }
+        elsif ($callout->type() eq "FSI")
+        {
+            $output{"FSI"}{$callout->fsiLink()} = \%c;
+        }
+        elsif ($callout->type() eq "FSI-I2C")
+        {
+            $c{HexAddress} = $callout->i2cAddress();
+            my $decimal = hex($callout->i2cAddress());
+
+            $output{"FSI-I2C"}{$callout->fsiLink()}
+                {$callout->i2cBus()}{$decimal} = \%c;
+        }
+        elsif ($callout->type() eq "FSI-SPI")
+        {
+            $output{"FSI-SPI"}{$callout->fsiLink()}{$callout->spiBus()} = \%c;
+        }
+    }
+
+    open(my $fh, '>', $outFile) or die "Could not open file '$outFile' $!";
+    my $json = JSON->new->utf8;
+    $json->indent(1);
+    $json->canonical(1);
+    my $text = $json->encode(\%output);
+    print $fh $text;
+    close $fh;
+}
+
+# This will remove duplicate callouts from the input callout list, keeping
+# the highest priority value and MRU, and then also sort by priority.
+#
+# There could be duplicates when multiple single segment callouts are
+# combined into 1.
+sub sortCallouts
+{
+    my @callouts = @_;
+
+    # This will undef the duplicates, and then remove them at the end,
+    for (my $i = 0; $i < (scalar @callouts) - 1; $i++)
+    {
+        next if not defined $callouts[$i];
+
+        for (my $j = $i + 1; $j < (scalar @callouts); $j++)
+        {
+            next if not defined $callouts[$j];
+
+            if ($callouts[$i]->{LocationCode} eq $callouts[$j]->{LocationCode})
+            {
+                # Keep the highest priority value
+                $callouts[$i]->{Priority} = getHighestPriority(
+                    $callouts[$i]->{Priority}, $callouts[$j]->{Priority});
+
+                # Keep the MRU if present
+                if (defined $callouts[$j]->{MRU})
+                {
+                    $callouts[$i]->{MRU} = $callouts[$j]->{MRU};
+                }
+
+                $callouts[$j] = undef;
+            }
+        }
+    }
+
+    # removed the undefined ones
+    @callouts = grep {defined ($_)} @callouts;
+
+    # sort from highest to lowest priorities
+    @callouts = sort {
+        $priorities{$b->{Priority}} <=> $priorities{$a->{Priority}}
+    } @callouts;
+
+    return @callouts;
+}
+
+# Returns the highest priority value of the two passed in
+sub getHighestPriority
+{
+    my ($p1, $p2) = @_;
+
+    if ($priorities{$p1} > $priorities{$p2})
+    {
+        return $p1;
+    }
+    return $p2;
+}
+
+# Dies if the input priority isn't valid
+sub validatePriority
+{
+    my $priority = shift;
+
+    if (not exists $priorities{$priority})
+    {
+        die "Invalid callout priority found: $priority\n";
+    }
+
+    return $priority;
+}
 
 sub printUsage
 {