blob: 9f357fb431ed13931e728574b282d38581e2091c [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
Matt Spinlerbbaf7382020-06-02 11:16:28 -0500152# FSII2CCallout object for FSI-I2C callouts
153# Ideally this would be derived from FSICallout, but I can't
154# get it to work.
155package FSII2CCallout;
156
157our @ISA = qw(Callout);
158sub new
159{
160 my ($class) = @_;
161 # source, dest, calloutList
162 my $self = $class->SUPER::new("FSI-I2C", $_[1], $_[2], $_[3]);
163 $self->{FSILink} = $_[4];
164 $self->{i2cBus} = $_[5];
165 $self->{i2cAddr} = $_[6];
166 return bless $self, $class;
167}
168
169sub fsiLink
170{
171 my $self = shift;
172 return $self->{FSILink};
173}
174
175sub i2cBus
176{
177 my $self = shift;
178 return $self->{i2cBus};
179}
180
181sub i2cAddress
182{
183 my $self = shift;
184 return $self->{i2cAddr};
185}
186
187#FSISPICallout object for FSI-SPI callouts
188package FSISPICallout;
189
190our @ISA = qw(Callout);
191sub new
192{
193 my ($class) = @_;
194 # source, dest, calloutList
195 my $self = $class->SUPER::new("FSI-SPI", $_[1], $_[2], $_[3]);
196 $self->{FSILink} = $_[4];
197 $self->{SPIBus} = $_[5];
198 return bless $self, $class;
199}
200
201sub fsiLink
202{
203 my $self = shift;
204 return $self->{FSILink};
205}
206
207sub spiBus
208{
209 my $self = shift;
210 return $self->{SPIBus};
211}
212
Matt Spinler00ef7662020-06-02 10:58:24 -0500213package main;
214
Matt Spinler1cb9ecc2020-06-02 10:48:31 -0500215use mrw::Targets;
216use mrw::Util;
217use Getopt::Long;
218use File::Basename;
219use JSON;
220
221my $mrwFile = "";
222my $outFile = "";
223my $printSegments = 0;
224
225# Not supporting priorites A, B, or C until necessary
226my %priorities = (H => 3, M => 2, L => 1);
227
228# Segment bus types
229my %busTypes = ( I2C => 1, FSIM => 1, FSICM => 1, SPI => 1 );
230
231GetOptions(
232 "m=s" => \$mrwFile,
233 "o=s" => \$outFile,
234 "segments" => \$printSegments
235)
236 or printUsage();
237
238if (($mrwFile eq "") or ($outFile eq ""))
239{
240 printUsage();
241}
242
243# Load system MRW
244my $targets = Targets->new;
245$targets->loadXML($mrwFile);
246
247# Find all single segment buses that we care about
248my %allSegments = getPathSegments();
249
Matt Spinler00ef7662020-06-02 10:58:24 -0500250my @callouts;
251
252# Build the single and multi segment callouts
253buildCallouts(\%allSegments, \@callouts);
254
255
Matt Spinler1cb9ecc2020-06-02 10:48:31 -0500256# Write the segments to a JSON file
257if ($printSegments)
258{
259 my $outDir = dirname($outFile);
260 my $segmentsFile = "$outDir/segments.json";
261
262 open(my $fh, '>', $segmentsFile) or
263 die "Could not open file '$segmentsFile' $!";
264
265 my $json = JSON->new;
266 $json->indent(1);
267 $json->canonical(1);
268 my $text = $json->encode(\%allSegments);
269 print $fh $text;
270 close $fh;
271}
272
273# Returns a hash of all the FSI, I2C, and SPI segments in the MRW
274sub getPathSegments
275{
276 my %segments;
277 foreach my $target (sort keys %{$targets->getAllTargets()})
278 {
279 my $numConnections = $targets->getNumConnections($target);
280
281 if ($numConnections == 0)
282 {
283 next;
284 }
285
286 for (my $connIndex=0;$connIndex<$numConnections;$connIndex++)
287 {
288 my $connBusObj = $targets->getConnectionBus($target, $connIndex);
289 my $busType = $connBusObj->{bus_type};
290
291 # We only care about certain bus types
292 if (not exists $busTypes{$busType})
293 {
294 next;
295 }
296
297 my $dest = $targets->getConnectionDestination($target, $connIndex);
298
299 my %segment;
300 $segment{BusType} = $busType;
301 $segment{SourceUnit} = $target;
302 $segment{SourceChip} = getParentByClass($target, "CHIP");
303 if ($segment{SourceChip} eq "")
304 {
305 die "Warning: Could not get parent chip for source $target\n";
306 }
307
308 $segment{DestUnit} = $dest;
309 $segment{DestChip} = getParentByClass($dest, "CHIP");
310
311 # If the unit's direct parent is a connector that's OK too.
312 if ($segment{DestChip} eq "")
313 {
314 my $parent = $targets->getTargetParent($dest);
315 if ($targets->getAttribute($parent, "CLASS") eq "CONNECTOR")
316 {
317 $segment{DestChip} = $parent;
318 }
319 }
320
321 if ($segment{DestChip} eq "")
322 {
323 die "Warning: Could not get parent chip for dest $dest\n";
324 }
325
326 my $fruPath = $targets->getBusAttribute(
327 $target, $connIndex, "FRU_PATH");
328
329 if (defined $fruPath)
330 {
331 $segment{FRUPath} = $fruPath;
332 my @callouts = getFRUPathCallouts($fruPath);
333 $segment{Callouts} = \@callouts;
334 }
335 else
336 {
337 $segment{FRUPath} = "";
338 my @empty;
339 $segment{Callouts} = \@empty;
340 }
341
342 if ($busType eq "I2C")
343 {
344 $segment{I2CBus} = $targets->getAttribute($target, "I2C_PORT");
345 $segment{I2CAddress} =
346 hex($targets->getAttribute($dest, "I2C_ADDRESS"));
347
348 $segment{I2CBus} = $segment{I2CBus};
349
350 # Convert to the 7 bit address that linux uses
351 $segment{I2CAddress} =
352 Util::adjustI2CAddress($segment{I2CAddress});
353 }
354 elsif ($busType eq "FSIM")
355 {
356 $segment{FSILink} =
357 hex($targets->getAttribute($target, "FSI_LINK"));
358 }
359 elsif ($busType eq "SPI")
360 {
361 $segment{SPIBus} = $targets->getAttribute($target, "SPI_PORT");
362
363 # Seems to be in HEX sometimes
364 if ($segment{SPIBus} =~ /^0x/i)
365 {
366 $segment{SPIBus} = hex($segment{SPIBus});
367 }
368 }
369
370 push @{$segments{$busType}}, { %segment };
371 }
372 }
373
374 return %segments;
375}
376
377#Breaks the FRU_PATH atttribute up into its component callouts.
378#It looks like: "H:<some target>,L:<some other target>(<MRU>)"
379#Where H/L are the priorities and can be H/M/L.
380#The MRU that is in parentheses is optional and is a chip name on that
381#FRU target.
382sub getFRUPathCallouts
383{
384 my @callouts;
385 my $fruPath = shift;
386
387 my @entries = split(',', $fruPath);
388
389 for my $entry (@entries)
390 {
391 my %callout;
392 my ($priority, $path) = split(':', $entry);
393
394 # pull the MRU out of the parentheses at the end and then
395 # remove the parentheses.
396 if ($path =~ /\(.+\)$/)
397 {
398 ($callout{MRU}) = $path =~ /\((.+)\)/;
399
400 $path =~ s/\(.+\)$//;
401 }
402
403 # check if the target we read out is valid by
404 # checking for a required attribute
405 if ($targets->isBadAttribute($path, "CLASS"))
406 {
407 die "FRU Path $path not a valid target\n";
408 }
409
410 $callout{Priority} = $priority;
411 if (not exists $priorities{$priority})
412 {
413 die "Invalid priority: '$priority' on callout $path\n";
414 }
415
416 $callout{Name} = $path;
417
418 push @callouts, \%callout;
419 }
420
421 return @callouts;
422}
423
424# Returns an ancestor target based on its class
425sub getParentByClass
426{
427 my ($target, $class) = @_;
428 my $parent = $targets->getTargetParent($target);
429
430 while (defined $parent)
431 {
432 if (!$targets->isBadAttribute($parent, "CLASS"))
433 {
434 if ($class eq $targets->getAttribute($parent, "CLASS"))
435 {
436 return $parent;
437 }
438 }
439 $parent = $targets->getTargetParent($parent);
440 }
441
442 return "";
443}
444
Matt Spinler00ef7662020-06-02 10:58:24 -0500445# Build the callout objects
446sub buildCallouts
447{
448 my ($segments, $callouts) = @_;
449
450 # Callouts for 1 segment connections directly off of the BMC.
451 buildBMCSingleSegmentCallouts($segments, $callouts);
Matt Spinlerbbaf7382020-06-02 11:16:28 -0500452
453 # Callouts more than 1 segment away
454 buildMultiSegmentCallouts($segments, $callouts);
Matt Spinler00ef7662020-06-02 10:58:24 -0500455}
456
457# Build the callout objects for devices 1 segment away.
458sub buildBMCSingleSegmentCallouts
459{
460 my ($segments, $callouts) = @_;
461
462 for my $busType (keys %$segments)
463 {
464 for my $segment (@{$$segments{$busType}})
465 {
466 my $chipType = $targets->getType($segment->{SourceChip});
467 if ($chipType eq "BMC")
468 {
469 my $callout = buildSingleSegmentCallout($segment);
470
471 if (defined $callout)
472 {
473 push @{$callouts}, $callout;
474 }
475 }
476 }
477 }
478}
479
480# Build the callout object based on the callout type using the
481# callout list from the single segment.
482sub buildSingleSegmentCallout
483{
484 my ($segment, $callouts) = @_;
485
486 if ($segment->{BusType} eq "I2C")
487 {
488 return createI2CCallout($segment, $callouts);
489 }
490 elsif ($segment->{BusType} eq "FSIM")
491 {
492 return createFSICallout($segment, $callouts);
493 }
494 elsif ($segment->{BusType} eq "SPI")
495 {
496 return createSPICallout($segment, $callouts);
497 }
498
499 return undef;
500}
501
Matt Spinlerbbaf7382020-06-02 11:16:28 -0500502# Build the callout objects for devices more than 1 segment away from
503# the BMC. All but the last segment will always be FSI. The FRU
504# callouts accumulate as segments are added.
505sub buildMultiSegmentCallouts
506{
507 my ($segments, $callouts) = @_;
508 my $hops = 0;
509 my $found = 1;
510
511 # Connect FSI link callouts to other FSI segments to make new callouts, and
512 # connect all FSI link callouts up with the I2C/SPI segments to make even
513 # more new callouts. Note: Deal with I2C muxes, if they are ever modeled,
514 # when there are some.
515
516 # Each time through the loop, go out another FSI hop.
517 # Stop when no more new hops are found.
518 while ($found)
519 {
520 my @newCallouts;
521 $found = 0;
522
523 for my $callout (@$callouts)
524 {
525 if ($callout->type() ne "FSI")
526 {
527 next;
528 }
529
530 # link numbers are separated by '-'s in the link field,
531 # so 0-5 = 1 hop
532 my @numHops = $callout->fsiLink() =~ /(-)/g;
533
534 # only deal with callout objects that contain $hops hops.
535 if ($hops != scalar @numHops)
536 {
537 next;
538 }
539
540 for my $segmentType (keys %$segments)
541 {
542 for my $segment (@{$$segments{$segmentType}})
543 {
544 # If the destination on this callout is the same
545 # as the source of the segment, then make a new
546 # callout that spans both.
547 if ($callout->destChip() eq $segment->{SourceChip})
548 {
549 # First build the new single segment callout
550 my $segmentCallout =
551 buildSingleSegmentCallout($segment);
552
553 # Now merge both callouts into one.
554 if (defined $segmentCallout)
555 {
556 my $newCallout =
557 mergeCallouts($callout, $segmentCallout);
558
559 push @newCallouts, $newCallout;
560 $found = 1;
561 }
562 }
563 }
564 }
565 }
566
567 if ($found)
568 {
569 push @{$callouts}, @newCallouts;
570 }
571
572 $hops = $hops + 1;
573 }
574}
575
576# Merge 2 callout objects into 1 that contains all of their FRU callouts.
577sub mergeCallouts
578{
579 my ($firstCallout, $secondCallout) = @_;
580
581 # This callout list will be merged/sorted later.
582 # Endpoint callouts are added first, so they will be higher
583 # in the callout list (priority permitting).
584 my @calloutList;
585 push @calloutList, @{$secondCallout->calloutList()};
586 push @calloutList, @{$firstCallout->calloutList()};
587
588 # FSI
589 if (($firstCallout->type() eq "FSI") && ($secondCallout->type() eq "FSI"))
590 {
591 # combine the FSI links with a -
592 my $FSILink = $firstCallout->fsiLink() . "-" .
593 $secondCallout->fsiLink();
594
595 my $fsiCallout = new FSICallout($firstCallout->sourceChip(),
596 $secondCallout->destChip(), \@calloutList, $FSILink);
597
598 return $fsiCallout;
599 }
600 # FSI-I2C
601 elsif (($firstCallout->type() eq "FSI") &&
602 ($secondCallout->type() eq "I2C"))
603 {
604 my $i2cCallout = new FSII2CCallout($firstCallout->sourceChip(),
605 $secondCallout->destChip(), \@calloutList,
606 $firstCallout->fsiLink(), $secondCallout->i2cBus(),
607 $secondCallout->i2cAddress());
608
609 return $i2cCallout;
610 }
611 # FSI-SPI
612 elsif (($firstCallout->type() eq "FSI") &&
613 ($secondCallout->type() eq "SPI"))
614 {
615 my $spiCallout = new FSISPICallout($firstCallout->sourceChip(),
616 $secondCallout->destChip(), \@calloutList,
617 $firstCallout->fsiLink(), $secondCallout->spiBus());
618
619 return $spiCallout;
620 }
621
622 die "Unrecognized callouts to merge: " . $firstCallout->type() .
623 " " . $secondCallout->type() . "\n";
624}
625
Matt Spinler00ef7662020-06-02 10:58:24 -0500626# Create an I2CCallout object
627sub createI2CCallout
628{
629 my $segment = shift;
630 my $bus = $segment->{I2CBus};
631
632 # Convert MRW BMC I2C numbering to the linux one for the BMC
633 if ($targets->getAttribute($segment->{SourceChip}, "TYPE") eq "BMC")
634 {
635 $bus = Util::adjustI2CPort($segment->{I2CBus});
636
637 if ($bus < 0)
638 {
639 die "After adjusting BMC I2C bus $segment->{I2CBus}, " .
640 "got a negative number\n";
641 }
642 }
643
644 my $i2cCallout = new I2CCallout($segment->{SourceChip},
645 $segment->{DestChip}, $segment->{Callouts}, $bus,
646 $segment->{I2CAddress});
647
648 return $i2cCallout;
649}
650
651# Create an FSICallout object
652sub createFSICallout
653{
654 my $segment = shift;
655
656 my $fsiCallout = new FSICallout($segment->{SourceChip},
657 $segment->{DestChip}, $segment->{Callouts},
658 $segment->{FSILink}, $segment);
659
660 return $fsiCallout;
661}
662
663# Create a SPICallout object
664sub createSPICallout
665{
666 my $segment = shift;
667
668 my $spiCallout = new SPICallout($segment->{SourceChip},
669 $segment->{DestChip}, $segment->{Callouts},
670 $segment->{SPIBus});
671
672 return $spiCallout;
673}
674
675
Matt Spinler1cb9ecc2020-06-02 10:48:31 -0500676sub printUsage
677{
678 print "$0 -m <MRW file> -o <Output filename> [--segments] [-n]\n" .
679 " -m <MRW file> = The MRW XML\n" .
680 " -o <Output filename> = The output JSON\n" .
681 " [--segments] = Optionally create a segments.json file\n" .
682 exit(1);
683}