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 49# 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 152# 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 213package main; 214 215use 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 250my @callouts; 251 252# Build the single and multi segment callouts 253buildCallouts(\%allSegments, \@callouts); 254 255 256# 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 445# 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); 452 453 # Callouts more than 1 segment away 454 buildMultiSegmentCallouts($segments, $callouts); 455} 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 502# 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 626# 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 676sub 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} 684