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 255printJSON(\@callouts); 256 257# Write the segments to a JSON file 258if ($printSegments) 259{ 260 my $outDir = dirname($outFile); 261 my $segmentsFile = "$outDir/segments.json"; 262 263 open(my $fh, '>', $segmentsFile) or 264 die "Could not open file '$segmentsFile' $!"; 265 266 my $json = JSON->new; 267 $json->indent(1); 268 $json->canonical(1); 269 my $text = $json->encode(\%allSegments); 270 print $fh $text; 271 close $fh; 272} 273 274# Returns a hash of all the FSI, I2C, and SPI segments in the MRW 275sub getPathSegments 276{ 277 my %segments; 278 foreach my $target (sort keys %{$targets->getAllTargets()}) 279 { 280 my $numConnections = $targets->getNumConnections($target); 281 282 if ($numConnections == 0) 283 { 284 next; 285 } 286 287 for (my $connIndex=0;$connIndex<$numConnections;$connIndex++) 288 { 289 my $connBusObj = $targets->getConnectionBus($target, $connIndex); 290 my $busType = $connBusObj->{bus_type}; 291 292 # We only care about certain bus types 293 if (not exists $busTypes{$busType}) 294 { 295 next; 296 } 297 298 my $dest = $targets->getConnectionDestination($target, $connIndex); 299 300 my %segment; 301 $segment{BusType} = $busType; 302 $segment{SourceUnit} = $target; 303 $segment{SourceChip} = getParentByClass($target, "CHIP"); 304 if ($segment{SourceChip} eq "") 305 { 306 die "Warning: Could not get parent chip for source $target\n"; 307 } 308 309 $segment{DestUnit} = $dest; 310 $segment{DestChip} = getParentByClass($dest, "CHIP"); 311 312 # If the unit's direct parent is a connector that's OK too. 313 if ($segment{DestChip} eq "") 314 { 315 my $parent = $targets->getTargetParent($dest); 316 if ($targets->getAttribute($parent, "CLASS") eq "CONNECTOR") 317 { 318 $segment{DestChip} = $parent; 319 } 320 } 321 322 if ($segment{DestChip} eq "") 323 { 324 die "Warning: Could not get parent chip for dest $dest\n"; 325 } 326 327 my $fruPath = $targets->getBusAttribute( 328 $target, $connIndex, "FRU_PATH"); 329 330 if (defined $fruPath) 331 { 332 $segment{FRUPath} = $fruPath; 333 my @callouts = getFRUPathCallouts($fruPath); 334 $segment{Callouts} = \@callouts; 335 } 336 else 337 { 338 $segment{FRUPath} = ""; 339 my @empty; 340 $segment{Callouts} = \@empty; 341 } 342 343 if ($busType eq "I2C") 344 { 345 $segment{I2CBus} = $targets->getAttribute($target, "I2C_PORT"); 346 $segment{I2CAddress} = 347 hex($targets->getAttribute($dest, "I2C_ADDRESS")); 348 349 $segment{I2CBus} = $segment{I2CBus}; 350 351 # Convert to the 7 bit address that linux uses 352 $segment{I2CAddress} = 353 Util::adjustI2CAddress($segment{I2CAddress}); 354 } 355 elsif ($busType eq "FSIM") 356 { 357 $segment{FSILink} = 358 hex($targets->getAttribute($target, "FSI_LINK")); 359 } 360 elsif ($busType eq "SPI") 361 { 362 $segment{SPIBus} = $targets->getAttribute($target, "SPI_PORT"); 363 364 # Seems to be in HEX sometimes 365 if ($segment{SPIBus} =~ /^0x/i) 366 { 367 $segment{SPIBus} = hex($segment{SPIBus}); 368 } 369 } 370 371 push @{$segments{$busType}}, { %segment }; 372 } 373 } 374 375 return %segments; 376} 377 378#Breaks the FRU_PATH atttribute up into its component callouts. 379#It looks like: "H:<some target>,L:<some other target>(<MRU>)" 380#Where H/L are the priorities and can be H/M/L. 381#The MRU that is in parentheses is optional and is a chip name on that 382#FRU target. 383sub getFRUPathCallouts 384{ 385 my @callouts; 386 my $fruPath = shift; 387 388 my @entries = split(',', $fruPath); 389 390 for my $entry (@entries) 391 { 392 my %callout; 393 my ($priority, $path) = split(':', $entry); 394 395 # pull the MRU out of the parentheses at the end and then 396 # remove the parentheses. 397 if ($path =~ /\(.+\)$/) 398 { 399 ($callout{MRU}) = $path =~ /\((.+)\)/; 400 401 $path =~ s/\(.+\)$//; 402 } 403 404 # check if the target we read out is valid by 405 # checking for a required attribute 406 if ($targets->isBadAttribute($path, "CLASS")) 407 { 408 die "FRU Path $path not a valid target\n"; 409 } 410 411 $callout{Priority} = $priority; 412 if (not exists $priorities{$priority}) 413 { 414 die "Invalid priority: '$priority' on callout $path\n"; 415 } 416 417 $callout{Name} = $path; 418 419 push @callouts, \%callout; 420 } 421 422 return @callouts; 423} 424 425# Returns an ancestor target based on its class 426sub getParentByClass 427{ 428 my ($target, $class) = @_; 429 my $parent = $targets->getTargetParent($target); 430 431 while (defined $parent) 432 { 433 if (!$targets->isBadAttribute($parent, "CLASS")) 434 { 435 if ($class eq $targets->getAttribute($parent, "CLASS")) 436 { 437 return $parent; 438 } 439 } 440 $parent = $targets->getTargetParent($parent); 441 } 442 443 return ""; 444} 445 446# Build the callout objects 447sub buildCallouts 448{ 449 my ($segments, $callouts) = @_; 450 451 # Callouts for 1 segment connections directly off of the BMC. 452 buildBMCSingleSegmentCallouts($segments, $callouts); 453 454 # Callouts more than 1 segment away 455 buildMultiSegmentCallouts($segments, $callouts); 456} 457 458# Build the callout objects for devices 1 segment away. 459sub buildBMCSingleSegmentCallouts 460{ 461 my ($segments, $callouts) = @_; 462 463 for my $busType (keys %$segments) 464 { 465 for my $segment (@{$$segments{$busType}}) 466 { 467 my $chipType = $targets->getType($segment->{SourceChip}); 468 if ($chipType eq "BMC") 469 { 470 my $callout = buildSingleSegmentCallout($segment); 471 472 if (defined $callout) 473 { 474 push @{$callouts}, $callout; 475 } 476 } 477 } 478 } 479} 480 481# Build the callout object based on the callout type using the 482# callout list from the single segment. 483sub buildSingleSegmentCallout 484{ 485 my ($segment, $callouts) = @_; 486 487 if ($segment->{BusType} eq "I2C") 488 { 489 return createI2CCallout($segment, $callouts); 490 } 491 elsif ($segment->{BusType} eq "FSIM") 492 { 493 return createFSICallout($segment, $callouts); 494 } 495 elsif ($segment->{BusType} eq "SPI") 496 { 497 return createSPICallout($segment, $callouts); 498 } 499 500 return undef; 501} 502 503# Build the callout objects for devices more than 1 segment away from 504# the BMC. All but the last segment will always be FSI. The FRU 505# callouts accumulate as segments are added. 506sub buildMultiSegmentCallouts 507{ 508 my ($segments, $callouts) = @_; 509 my $hops = 0; 510 my $found = 1; 511 512 # Connect FSI link callouts to other FSI segments to make new callouts, and 513 # connect all FSI link callouts up with the I2C/SPI segments to make even 514 # more new callouts. Note: Deal with I2C muxes, if they are ever modeled, 515 # when there are some. 516 517 # Each time through the loop, go out another FSI hop. 518 # Stop when no more new hops are found. 519 while ($found) 520 { 521 my @newCallouts; 522 $found = 0; 523 524 for my $callout (@$callouts) 525 { 526 if ($callout->type() ne "FSI") 527 { 528 next; 529 } 530 531 # link numbers are separated by '-'s in the link field, 532 # so 0-5 = 1 hop 533 my @numHops = $callout->fsiLink() =~ /(-)/g; 534 535 # only deal with callout objects that contain $hops hops. 536 if ($hops != scalar @numHops) 537 { 538 next; 539 } 540 541 for my $segmentType (keys %$segments) 542 { 543 for my $segment (@{$$segments{$segmentType}}) 544 { 545 # If the destination on this callout is the same 546 # as the source of the segment, then make a new 547 # callout that spans both. 548 if ($callout->destChip() eq $segment->{SourceChip}) 549 { 550 # First build the new single segment callout 551 my $segmentCallout = 552 buildSingleSegmentCallout($segment); 553 554 # Now merge both callouts into one. 555 if (defined $segmentCallout) 556 { 557 my $newCallout = 558 mergeCallouts($callout, $segmentCallout); 559 560 push @newCallouts, $newCallout; 561 $found = 1; 562 } 563 } 564 } 565 } 566 } 567 568 if ($found) 569 { 570 push @{$callouts}, @newCallouts; 571 } 572 573 $hops = $hops + 1; 574 } 575} 576 577# Merge 2 callout objects into 1 that contains all of their FRU callouts. 578sub mergeCallouts 579{ 580 my ($firstCallout, $secondCallout) = @_; 581 582 # This callout list will be merged/sorted later. 583 # Endpoint callouts are added first, so they will be higher 584 # in the callout list (priority permitting). 585 my @calloutList; 586 push @calloutList, @{$secondCallout->calloutList()}; 587 push @calloutList, @{$firstCallout->calloutList()}; 588 589 # FSI 590 if (($firstCallout->type() eq "FSI") && ($secondCallout->type() eq "FSI")) 591 { 592 # combine the FSI links with a - 593 my $FSILink = $firstCallout->fsiLink() . "-" . 594 $secondCallout->fsiLink(); 595 596 my $fsiCallout = new FSICallout($firstCallout->sourceChip(), 597 $secondCallout->destChip(), \@calloutList, $FSILink); 598 599 return $fsiCallout; 600 } 601 # FSI-I2C 602 elsif (($firstCallout->type() eq "FSI") && 603 ($secondCallout->type() eq "I2C")) 604 { 605 my $i2cCallout = new FSII2CCallout($firstCallout->sourceChip(), 606 $secondCallout->destChip(), \@calloutList, 607 $firstCallout->fsiLink(), $secondCallout->i2cBus(), 608 $secondCallout->i2cAddress()); 609 610 return $i2cCallout; 611 } 612 # FSI-SPI 613 elsif (($firstCallout->type() eq "FSI") && 614 ($secondCallout->type() eq "SPI")) 615 { 616 my $spiCallout = new FSISPICallout($firstCallout->sourceChip(), 617 $secondCallout->destChip(), \@calloutList, 618 $firstCallout->fsiLink(), $secondCallout->spiBus()); 619 620 return $spiCallout; 621 } 622 623 die "Unrecognized callouts to merge: " . $firstCallout->type() . 624 " " . $secondCallout->type() . "\n"; 625} 626 627# Create an I2CCallout object 628sub createI2CCallout 629{ 630 my $segment = shift; 631 my $bus = $segment->{I2CBus}; 632 633 # Convert MRW BMC I2C numbering to the linux one for the BMC 634 if ($targets->getAttribute($segment->{SourceChip}, "TYPE") eq "BMC") 635 { 636 $bus = Util::adjustI2CPort($segment->{I2CBus}); 637 638 if ($bus < 0) 639 { 640 die "After adjusting BMC I2C bus $segment->{I2CBus}, " . 641 "got a negative number\n"; 642 } 643 } 644 645 my $i2cCallout = new I2CCallout($segment->{SourceChip}, 646 $segment->{DestChip}, $segment->{Callouts}, $bus, 647 $segment->{I2CAddress}); 648 649 return $i2cCallout; 650} 651 652# Create an FSICallout object 653sub createFSICallout 654{ 655 my $segment = shift; 656 657 my $fsiCallout = new FSICallout($segment->{SourceChip}, 658 $segment->{DestChip}, $segment->{Callouts}, 659 $segment->{FSILink}, $segment); 660 661 return $fsiCallout; 662} 663 664# Create a SPICallout object 665sub createSPICallout 666{ 667 my $segment = shift; 668 669 my $spiCallout = new SPICallout($segment->{SourceChip}, 670 $segment->{DestChip}, $segment->{Callouts}, 671 $segment->{SPIBus}); 672 673 return $spiCallout; 674} 675 676# Convert all of the *Callout objects to JSON and write them to a file. 677# It will convert the callout target names to location codes and also sort 678# the callout list before doing so. 679sub printJSON 680{ 681 my $callouts = shift; 682 my %output; 683 684 for my $callout (@$callouts) 685 { 686 my %c; 687 $c{Source} = $callout->sourceChip(); 688 $c{Dest} = $callout->destChip(); 689 690 for my $fruCallout (@{$callout->calloutList()}) 691 { 692 my %entry; 693 694 if (length($fruCallout->{Name}) == 0) 695 { 696 die "Could not get target name for a callout on path:\n" . 697 " (" . $callout->sourceChip() . ") ->\n" . 698 " (" . $callout->destChip() . ")\n"; 699 } 700 701 $entry{Name} = $fruCallout->{Name}; 702 703 $entry{LocationCode} = 704 Util::getLocationCode($targets, $fruCallout->{Name}); 705 706 # MRUs - for now just use the path + MRU name 707 if (exists $fruCallout->{MRU}) 708 { 709 $entry{MRU} = $entry{Name} . "/" . $fruCallout->{MRU}; 710 } 711 712 $entry{Priority} = validatePriority($fruCallout->{Priority}); 713 714 push @{$c{Callouts}}, \%entry; 715 } 716 717 718 # Remove duplicate callouts and sort 719 @{$c{Callouts}} = sortCallouts(@{$c{Callouts}}); 720 721 # Setup the entry based on the callout type 722 if ($callout->type() eq "I2C") 723 { 724 # The address key is in decimal, but save the hex value 725 # for easier debug. 726 $c{HexAddress} = $callout->i2cAddress(); 727 my $decimal = hex($callout->i2cAddress()); 728 729 $output{"I2C"}{$callout->i2cBus()}{$decimal} = \%c; 730 } 731 elsif ($callout->type() eq "SPI") 732 { 733 $output{"SPI"}{$callout->spiBus()} = \%c; 734 } 735 elsif ($callout->type() eq "FSI") 736 { 737 $output{"FSI"}{$callout->fsiLink()} = \%c; 738 } 739 elsif ($callout->type() eq "FSI-I2C") 740 { 741 $c{HexAddress} = $callout->i2cAddress(); 742 my $decimal = hex($callout->i2cAddress()); 743 744 $output{"FSI-I2C"}{$callout->fsiLink()} 745 {$callout->i2cBus()}{$decimal} = \%c; 746 } 747 elsif ($callout->type() eq "FSI-SPI") 748 { 749 $output{"FSI-SPI"}{$callout->fsiLink()}{$callout->spiBus()} = \%c; 750 } 751 } 752 753 open(my $fh, '>', $outFile) or die "Could not open file '$outFile' $!"; 754 my $json = JSON->new->utf8; 755 $json->indent(1); 756 $json->canonical(1); 757 my $text = $json->encode(\%output); 758 print $fh $text; 759 close $fh; 760} 761 762# This will remove duplicate callouts from the input callout list, keeping 763# the highest priority value and MRU, and then also sort by priority. 764# 765# There could be duplicates when multiple single segment callouts are 766# combined into 1. 767sub sortCallouts 768{ 769 my @callouts = @_; 770 771 # This will undef the duplicates, and then remove them at the end, 772 for (my $i = 0; $i < (scalar @callouts) - 1; $i++) 773 { 774 next if not defined $callouts[$i]; 775 776 for (my $j = $i + 1; $j < (scalar @callouts); $j++) 777 { 778 next if not defined $callouts[$j]; 779 780 if ($callouts[$i]->{LocationCode} eq $callouts[$j]->{LocationCode}) 781 { 782 # Keep the highest priority value 783 $callouts[$i]->{Priority} = getHighestPriority( 784 $callouts[$i]->{Priority}, $callouts[$j]->{Priority}); 785 786 # Keep the MRU if present 787 if (defined $callouts[$j]->{MRU}) 788 { 789 $callouts[$i]->{MRU} = $callouts[$j]->{MRU}; 790 } 791 792 $callouts[$j] = undef; 793 } 794 } 795 } 796 797 # removed the undefined ones 798 @callouts = grep {defined ($_)} @callouts; 799 800 # sort from highest to lowest priorities 801 @callouts = sort { 802 $priorities{$b->{Priority}} <=> $priorities{$a->{Priority}} 803 } @callouts; 804 805 return @callouts; 806} 807 808# Returns the highest priority value of the two passed in 809sub getHighestPriority 810{ 811 my ($p1, $p2) = @_; 812 813 if ($priorities{$p1} > $priorities{$p2}) 814 { 815 return $p1; 816 } 817 return $p2; 818} 819 820# Dies if the input priority isn't valid 821sub validatePriority 822{ 823 my $priority = shift; 824 825 if (not exists $priorities{$priority}) 826 { 827 die "Invalid callout priority found: $priority\n"; 828 } 829 830 return $priority; 831} 832 833sub printUsage 834{ 835 print "$0 -m <MRW file> -o <Output filename> [--segments] [-n]\n" . 836 " -m <MRW file> = The MRW XML\n" . 837 " -o <Output filename> = The output JSON\n" . 838 " [--segments] = Optionally create a segments.json file\n"; 839 exit(1); 840} 841