blob: 1662e6f816575d8b7a37960303b365f86a2f68be [file] [log] [blame]
Matt Spinler1cb9ecc2020-06-02 10:48:31 -05001#! /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
Matt Spinler00ef7662020-06-02 10:58:24 -050049# Callout object
50# Base class for other callouts.
51# There is an object per device, so it can contain multiple
52# FRU callouts in the calloutList attribute.
53package Callout;
54sub new
55{
56 my $class = shift;
57 my $self = {
58 type => shift,
59 sourceChip => shift,
60 destChip => shift,
61 calloutList => shift,
62 };
63
64 return bless $self, $class;
65}
66
67sub sourceChip
68{
69 my $self = shift;
70 return $self->{sourceChip};
71}
72
73sub destChip
74{
75 my $self = shift;
76 return $self->{destChip};
77}
78
79sub type
80{
81 my $self = shift;
82 return $self->{type};
83}
84
85sub calloutList
86{
87 my $self = shift;
88 return $self->{calloutList};
89}
90
91# I2CCallout object for I2C callouts
92package I2CCallout;
93our @ISA = qw(Callout);
94sub new
95{
96 my ($class) = @_;
97 # source, dest, calloutList
98 my $self = $class->SUPER::new("I2C", $_[1], $_[2], $_[3]);
99 $self->{i2cBus} = $_[4];
100 $self->{i2cAddr} = $_[5];
101 return bless $self, $class;
102}
103
104sub i2cBus
105{
106 my $self = shift;
107 return $self->{i2cBus};
108}
109
110sub i2cAddress
111{
112 my $self = shift;
113 return $self->{i2cAddr};
114}
115
116# FSICallout object for FSI callouts
117package FSICallout;
118our @ISA = qw(Callout);
119sub new
120{
121 my ($class) = @_;
122 my $self = $class->SUPER::new("FSI", $_[1], $_[2], $_[3]);
123 $self->{FSILink} = $_[4];
124 bless $self, $class;
125 return $self;
126}
127
128sub fsiLink
129{
130 my $self = shift;
131 return $self->{FSILink};
132}
133
134# SPICallout object for SPI callouts
135package SPICallout;
136our @ISA = qw(Callout);
137sub new
138{
139 my ($class) = @_;
140 my $self = $class->SUPER::new("SPI", $_[1], $_[2], $_[3]);
141 $self->{SPIBus} = $_[4];
142 bless $self, $class;
143 return $self;
144}
145
146sub spiBus
147{
148 my $self = shift;
149 return $self->{SPIBus};
150}
151
152package main;
153
Matt Spinler1cb9ecc2020-06-02 10:48:31 -0500154use mrw::Targets;
155use mrw::Util;
156use Getopt::Long;
157use File::Basename;
158use JSON;
159
160my $mrwFile = "";
161my $outFile = "";
162my $printSegments = 0;
163
164# Not supporting priorites A, B, or C until necessary
165my %priorities = (H => 3, M => 2, L => 1);
166
167# Segment bus types
168my %busTypes = ( I2C => 1, FSIM => 1, FSICM => 1, SPI => 1 );
169
170GetOptions(
171 "m=s" => \$mrwFile,
172 "o=s" => \$outFile,
173 "segments" => \$printSegments
174)
175 or printUsage();
176
177if (($mrwFile eq "") or ($outFile eq ""))
178{
179 printUsage();
180}
181
182# Load system MRW
183my $targets = Targets->new;
184$targets->loadXML($mrwFile);
185
186# Find all single segment buses that we care about
187my %allSegments = getPathSegments();
188
Matt Spinler00ef7662020-06-02 10:58:24 -0500189my @callouts;
190
191# Build the single and multi segment callouts
192buildCallouts(\%allSegments, \@callouts);
193
194
Matt Spinler1cb9ecc2020-06-02 10:48:31 -0500195# Write the segments to a JSON file
196if ($printSegments)
197{
198 my $outDir = dirname($outFile);
199 my $segmentsFile = "$outDir/segments.json";
200
201 open(my $fh, '>', $segmentsFile) or
202 die "Could not open file '$segmentsFile' $!";
203
204 my $json = JSON->new;
205 $json->indent(1);
206 $json->canonical(1);
207 my $text = $json->encode(\%allSegments);
208 print $fh $text;
209 close $fh;
210}
211
212# Returns a hash of all the FSI, I2C, and SPI segments in the MRW
213sub getPathSegments
214{
215 my %segments;
216 foreach my $target (sort keys %{$targets->getAllTargets()})
217 {
218 my $numConnections = $targets->getNumConnections($target);
219
220 if ($numConnections == 0)
221 {
222 next;
223 }
224
225 for (my $connIndex=0;$connIndex<$numConnections;$connIndex++)
226 {
227 my $connBusObj = $targets->getConnectionBus($target, $connIndex);
228 my $busType = $connBusObj->{bus_type};
229
230 # We only care about certain bus types
231 if (not exists $busTypes{$busType})
232 {
233 next;
234 }
235
236 my $dest = $targets->getConnectionDestination($target, $connIndex);
237
238 my %segment;
239 $segment{BusType} = $busType;
240 $segment{SourceUnit} = $target;
241 $segment{SourceChip} = getParentByClass($target, "CHIP");
242 if ($segment{SourceChip} eq "")
243 {
244 die "Warning: Could not get parent chip for source $target\n";
245 }
246
247 $segment{DestUnit} = $dest;
248 $segment{DestChip} = getParentByClass($dest, "CHIP");
249
250 # If the unit's direct parent is a connector that's OK too.
251 if ($segment{DestChip} eq "")
252 {
253 my $parent = $targets->getTargetParent($dest);
254 if ($targets->getAttribute($parent, "CLASS") eq "CONNECTOR")
255 {
256 $segment{DestChip} = $parent;
257 }
258 }
259
260 if ($segment{DestChip} eq "")
261 {
262 die "Warning: Could not get parent chip for dest $dest\n";
263 }
264
265 my $fruPath = $targets->getBusAttribute(
266 $target, $connIndex, "FRU_PATH");
267
268 if (defined $fruPath)
269 {
270 $segment{FRUPath} = $fruPath;
271 my @callouts = getFRUPathCallouts($fruPath);
272 $segment{Callouts} = \@callouts;
273 }
274 else
275 {
276 $segment{FRUPath} = "";
277 my @empty;
278 $segment{Callouts} = \@empty;
279 }
280
281 if ($busType eq "I2C")
282 {
283 $segment{I2CBus} = $targets->getAttribute($target, "I2C_PORT");
284 $segment{I2CAddress} =
285 hex($targets->getAttribute($dest, "I2C_ADDRESS"));
286
287 $segment{I2CBus} = $segment{I2CBus};
288
289 # Convert to the 7 bit address that linux uses
290 $segment{I2CAddress} =
291 Util::adjustI2CAddress($segment{I2CAddress});
292 }
293 elsif ($busType eq "FSIM")
294 {
295 $segment{FSILink} =
296 hex($targets->getAttribute($target, "FSI_LINK"));
297 }
298 elsif ($busType eq "SPI")
299 {
300 $segment{SPIBus} = $targets->getAttribute($target, "SPI_PORT");
301
302 # Seems to be in HEX sometimes
303 if ($segment{SPIBus} =~ /^0x/i)
304 {
305 $segment{SPIBus} = hex($segment{SPIBus});
306 }
307 }
308
309 push @{$segments{$busType}}, { %segment };
310 }
311 }
312
313 return %segments;
314}
315
316#Breaks the FRU_PATH atttribute up into its component callouts.
317#It looks like: "H:<some target>,L:<some other target>(<MRU>)"
318#Where H/L are the priorities and can be H/M/L.
319#The MRU that is in parentheses is optional and is a chip name on that
320#FRU target.
321sub getFRUPathCallouts
322{
323 my @callouts;
324 my $fruPath = shift;
325
326 my @entries = split(',', $fruPath);
327
328 for my $entry (@entries)
329 {
330 my %callout;
331 my ($priority, $path) = split(':', $entry);
332
333 # pull the MRU out of the parentheses at the end and then
334 # remove the parentheses.
335 if ($path =~ /\(.+\)$/)
336 {
337 ($callout{MRU}) = $path =~ /\((.+)\)/;
338
339 $path =~ s/\(.+\)$//;
340 }
341
342 # check if the target we read out is valid by
343 # checking for a required attribute
344 if ($targets->isBadAttribute($path, "CLASS"))
345 {
346 die "FRU Path $path not a valid target\n";
347 }
348
349 $callout{Priority} = $priority;
350 if (not exists $priorities{$priority})
351 {
352 die "Invalid priority: '$priority' on callout $path\n";
353 }
354
355 $callout{Name} = $path;
356
357 push @callouts, \%callout;
358 }
359
360 return @callouts;
361}
362
363# Returns an ancestor target based on its class
364sub getParentByClass
365{
366 my ($target, $class) = @_;
367 my $parent = $targets->getTargetParent($target);
368
369 while (defined $parent)
370 {
371 if (!$targets->isBadAttribute($parent, "CLASS"))
372 {
373 if ($class eq $targets->getAttribute($parent, "CLASS"))
374 {
375 return $parent;
376 }
377 }
378 $parent = $targets->getTargetParent($parent);
379 }
380
381 return "";
382}
383
Matt Spinler00ef7662020-06-02 10:58:24 -0500384# Build the callout objects
385sub buildCallouts
386{
387 my ($segments, $callouts) = @_;
388
389 # Callouts for 1 segment connections directly off of the BMC.
390 buildBMCSingleSegmentCallouts($segments, $callouts);
391}
392
393# Build the callout objects for devices 1 segment away.
394sub buildBMCSingleSegmentCallouts
395{
396 my ($segments, $callouts) = @_;
397
398 for my $busType (keys %$segments)
399 {
400 for my $segment (@{$$segments{$busType}})
401 {
402 my $chipType = $targets->getType($segment->{SourceChip});
403 if ($chipType eq "BMC")
404 {
405 my $callout = buildSingleSegmentCallout($segment);
406
407 if (defined $callout)
408 {
409 push @{$callouts}, $callout;
410 }
411 }
412 }
413 }
414}
415
416# Build the callout object based on the callout type using the
417# callout list from the single segment.
418sub buildSingleSegmentCallout
419{
420 my ($segment, $callouts) = @_;
421
422 if ($segment->{BusType} eq "I2C")
423 {
424 return createI2CCallout($segment, $callouts);
425 }
426 elsif ($segment->{BusType} eq "FSIM")
427 {
428 return createFSICallout($segment, $callouts);
429 }
430 elsif ($segment->{BusType} eq "SPI")
431 {
432 return createSPICallout($segment, $callouts);
433 }
434
435 return undef;
436}
437
438# Create an I2CCallout object
439sub createI2CCallout
440{
441 my $segment = shift;
442 my $bus = $segment->{I2CBus};
443
444 # Convert MRW BMC I2C numbering to the linux one for the BMC
445 if ($targets->getAttribute($segment->{SourceChip}, "TYPE") eq "BMC")
446 {
447 $bus = Util::adjustI2CPort($segment->{I2CBus});
448
449 if ($bus < 0)
450 {
451 die "After adjusting BMC I2C bus $segment->{I2CBus}, " .
452 "got a negative number\n";
453 }
454 }
455
456 my $i2cCallout = new I2CCallout($segment->{SourceChip},
457 $segment->{DestChip}, $segment->{Callouts}, $bus,
458 $segment->{I2CAddress});
459
460 return $i2cCallout;
461}
462
463# Create an FSICallout object
464sub createFSICallout
465{
466 my $segment = shift;
467
468 my $fsiCallout = new FSICallout($segment->{SourceChip},
469 $segment->{DestChip}, $segment->{Callouts},
470 $segment->{FSILink}, $segment);
471
472 return $fsiCallout;
473}
474
475# Create a SPICallout object
476sub createSPICallout
477{
478 my $segment = shift;
479
480 my $spiCallout = new SPICallout($segment->{SourceChip},
481 $segment->{DestChip}, $segment->{Callouts},
482 $segment->{SPIBus});
483
484 return $spiCallout;
485}
486
487
Matt Spinler1cb9ecc2020-06-02 10:48:31 -0500488sub printUsage
489{
490 print "$0 -m <MRW file> -o <Output filename> [--segments] [-n]\n" .
491 " -m <MRW file> = The MRW XML\n" .
492 " -o <Output filename> = The output JSON\n" .
493 " [--segments] = Optionally create a segments.json file\n" .
494 exit(1);
495}