1#! /usr/bin/perl
2
3# This script is used for generating callout lists from the MRW for devices
4# that can be accessed from the BMC.  The callouts have a location code, the
5# target name (for debug), a priority, and in some cases a MRU.  The script
6# supports I2C, FSI, SPI, FSI-I2C, and FSI-SPI devices.  The output is a JSON
7# file organized into sections for each callout type, with keys based on the
8# type.  I2c uses a bus and address, FSI uses a link, and SPI uses a bus
9# number. If FSI is combined with I2C or SPI, then the link plus the I2C/SPI
10# keys is used.  Multi-hop FSI links are indicated by a dash in between the
11# links, eg "0-1".
12#
13# An example section is:
14# "FSI":
15# {
16#   "5":
17#   {
18#      "Callouts":[
19#        {
20#           "Priority":"H"
21#           "LocationCode": "P1-C50",
22#           "MRU":"/sys-0/node-0/motherboard/proc_socket-0/module-0/power9-0",
23#           "Name":"/sys-0/node-0/motherboard/cpu0"
24#        },
25#        {
26#           "Priority":"H",
27#           "LocationCode": "P1-C42",
28#           "MRU":"/sys-0/node-0/motherboard/ebmc-card/BMC-0",
29#           "Name":"/sys-0/node-0/motherboard/ebmc-card"
30#        },
31#        {
32#           "Priority":"L",
33#           "LocationCode": "P1",
34#           "Name":"/sys-0/node-0/motherboard"
35#        }
36#     ],
37#     "Dest":"/sys-0/node-0/motherboard-0/proc_socket-0/module-0/power9-0",
38#     "Source":"/sys-0/node-0/motherboard-0/ebmc-card-connector-0/card-0/bmc-0"
39#   }
40# }
41# The Name, Dest and Source entries are MRW targets and are just for debug.
42#
43# The optional --segments argument will output a JSON file of all the bus
44# segments in the system, which is what the callouts are made from.
45
46use strict;
47use warnings;
48
49use mrw::Targets;
50use mrw::Util;
51use Getopt::Long;
52use File::Basename;
53use JSON;
54
55my $mrwFile = "";
56my $outFile = "";
57my $printSegments = 0;
58
59# Not supporting priorites A, B, or C until necessary
60my %priorities = (H => 3, M => 2, L => 1);
61
62# Segment bus types
63my %busTypes = ( I2C => 1, FSIM => 1, FSICM => 1, SPI => 1 );
64
65GetOptions(
66    "m=s" => \$mrwFile,
67    "o=s" => \$outFile,
68    "segments" => \$printSegments
69)
70    or printUsage();
71
72if (($mrwFile eq "") or ($outFile eq ""))
73{
74    printUsage();
75}
76
77# Load system MRW
78my $targets = Targets->new;
79$targets->loadXML($mrwFile);
80
81# Find all single segment buses that we care about
82my %allSegments = getPathSegments();
83
84# Write the segments to a JSON file
85if ($printSegments)
86{
87    my $outDir = dirname($outFile);
88    my $segmentsFile = "$outDir/segments.json";
89
90    open(my $fh, '>', $segmentsFile) or
91        die "Could not open file '$segmentsFile' $!";
92
93    my $json = JSON->new;
94    $json->indent(1);
95    $json->canonical(1);
96    my $text = $json->encode(\%allSegments);
97    print $fh $text;
98    close $fh;
99}
100
101# Returns a hash of all the FSI, I2C, and SPI segments in the MRW
102sub getPathSegments
103{
104    my %segments;
105    foreach my $target (sort keys %{$targets->getAllTargets()})
106    {
107        my $numConnections = $targets->getNumConnections($target);
108
109        if ($numConnections == 0)
110        {
111            next;
112        }
113
114        for (my $connIndex=0;$connIndex<$numConnections;$connIndex++)
115        {
116            my $connBusObj = $targets->getConnectionBus($target, $connIndex);
117            my $busType = $connBusObj->{bus_type};
118
119            # We only care about certain bus types
120            if (not exists $busTypes{$busType})
121            {
122                next;
123            }
124
125            my $dest = $targets->getConnectionDestination($target, $connIndex);
126
127            my %segment;
128            $segment{BusType} = $busType;
129            $segment{SourceUnit} = $target;
130            $segment{SourceChip} = getParentByClass($target, "CHIP");
131            if ($segment{SourceChip} eq "")
132            {
133                die "Warning: Could not get parent chip for source $target\n";
134            }
135
136            $segment{DestUnit} = $dest;
137            $segment{DestChip} = getParentByClass($dest, "CHIP");
138
139            # If the unit's direct parent is a connector that's OK too.
140            if ($segment{DestChip} eq "")
141            {
142                my $parent = $targets->getTargetParent($dest);
143                if ($targets->getAttribute($parent, "CLASS") eq "CONNECTOR")
144                {
145                    $segment{DestChip} = $parent;
146                }
147            }
148
149            if ($segment{DestChip} eq "")
150            {
151                die "Warning: Could not get parent chip for dest $dest\n";
152            }
153
154            my $fruPath = $targets->getBusAttribute(
155                $target, $connIndex, "FRU_PATH");
156
157            if (defined $fruPath)
158            {
159                $segment{FRUPath} = $fruPath;
160                my @callouts = getFRUPathCallouts($fruPath);
161                $segment{Callouts} = \@callouts;
162            }
163            else
164            {
165                $segment{FRUPath} = "";
166                my @empty;
167                $segment{Callouts} = \@empty;
168            }
169
170            if ($busType eq "I2C")
171            {
172                $segment{I2CBus} = $targets->getAttribute($target, "I2C_PORT");
173                $segment{I2CAddress} =
174                    hex($targets->getAttribute($dest, "I2C_ADDRESS"));
175
176                $segment{I2CBus} = $segment{I2CBus};
177
178                # Convert to the 7 bit address that linux uses
179                $segment{I2CAddress} =
180                    Util::adjustI2CAddress($segment{I2CAddress});
181            }
182            elsif ($busType eq "FSIM")
183            {
184                $segment{FSILink} =
185                    hex($targets->getAttribute($target, "FSI_LINK"));
186            }
187            elsif ($busType eq "SPI")
188            {
189                $segment{SPIBus} = $targets->getAttribute($target, "SPI_PORT");
190
191                # Seems to be in HEX sometimes
192                if ($segment{SPIBus} =~ /^0x/i)
193                {
194                    $segment{SPIBus} = hex($segment{SPIBus});
195                }
196            }
197
198            push @{$segments{$busType}}, { %segment };
199        }
200    }
201
202    return %segments;
203}
204
205#Breaks the FRU_PATH atttribute up into its component callouts.
206#It looks like:  "H:<some target>,L:<some other target>(<MRU>)"
207#Where H/L are the priorities and can be H/M/L.
208#The MRU that is in parentheses is optional and is a chip name on that
209#FRU target.
210sub getFRUPathCallouts
211{
212    my @callouts;
213    my $fruPath = shift;
214
215    my @entries = split(',', $fruPath);
216
217    for my $entry (@entries)
218    {
219        my %callout;
220        my ($priority, $path) = split(':', $entry);
221
222        # pull the MRU out of the parentheses at the end and then
223        # remove the parentheses.
224        if ($path =~ /\(.+\)$/)
225        {
226            ($callout{MRU}) = $path =~ /\((.+)\)/;
227
228            $path =~ s/\(.+\)$//;
229        }
230
231        # check if the target we read out is valid by
232        # checking for a required attribute
233        if ($targets->isBadAttribute($path, "CLASS"))
234        {
235            die "FRU Path $path not a valid target\n";
236        }
237
238        $callout{Priority} = $priority;
239        if (not exists $priorities{$priority})
240        {
241            die "Invalid priority: '$priority' on callout $path\n";
242        }
243
244        $callout{Name} = $path;
245
246        push @callouts, \%callout;
247    }
248
249    return @callouts;
250}
251
252# Returns an ancestor target based on its class
253sub getParentByClass
254{
255    my ($target, $class) = @_;
256    my $parent = $targets->getTargetParent($target);
257
258    while (defined $parent)
259    {
260        if (!$targets->isBadAttribute($parent, "CLASS"))
261        {
262            if ($class eq $targets->getAttribute($parent, "CLASS"))
263            {
264                return $parent;
265            }
266        }
267        $parent = $targets->getTargetParent($parent);
268    }
269
270    return "";
271}
272
273sub printUsage
274{
275    print "$0 -m <MRW file> -o <Output filename> [--segments] [-n]\n" .
276    "        -m <MRW file> = The MRW XML\n" .
277    "        -o <Output filename> = The output JSON\n" .
278    "        [--segments] = Optionally create a segments.json file\n" .
279    exit(1);
280}
281