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 152package main; 153 154use 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 189my @callouts; 190 191# Build the single and multi segment callouts 192buildCallouts(\%allSegments, \@callouts); 193 194 195# 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 384# 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 488sub 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} 496