1package Inventory; 2 3use strict; 4use warnings; 5 6#Target types to always include in the inventory if present 7my %TYPES = (SYS => 1, NODE => 1, PROC => 1, 8 BMC => 1, GPU => 1, CORE => 1, OCC => 1, TPM => 1); 9 10#RU_TYPES of cards to include 11#FRU = field replaceable unit, CRU = customer replaceable unit 12my %RU_TYPES = (FRU => 1, CRU => 1); 13 14#Chips that are modeled as modules (card-chip together) 15my %MODULE_TYPES = (PROC => 1, GPU => 1); 16 17#Returns an array of hashes that represents the inventory 18#for a system. The hash elements are: 19#TARGET: The MRW target of the item 20#OBMC_NAME: The OpenBMC name for the item. This is usually 21# a simplified version of the target. 22sub getInventory 23{ 24 my $targetObj = shift; 25 my @inventory; 26 27 if (ref($targetObj) ne "Targets") { 28 die "Invalid Targets object passed to getInventory\n"; 29 } 30 31 findItems($targetObj, \@inventory); 32 33 pruneModuleCards($targetObj, \@inventory); 34 35 makeOBMCNames($targetObj, \@inventory); 36 37 return @inventory; 38} 39 40 41#Finds the inventory targets in the MRW. 42#It selects them if the target's type is in %TYPES 43#or the target's RU_TYPE is in %RU_TYPES. 44#This will pick up FRUs and other chips like the BMC and processor. 45sub findItems 46{ 47 my ($targetObj, $inventory) = @_; 48 49 for my $target (sort keys %{$targetObj->getAllTargets()}) { 50 my $type = ""; 51 my $ruType = ""; 52 53 if (!$targetObj->isBadAttribute($target, "TYPE")) { 54 $type = $targetObj->getAttribute($target, "TYPE"); 55 } 56 57 if (!$targetObj->isBadAttribute($target, "RU_TYPE")) { 58 $ruType = $targetObj->getAttribute($target, "RU_TYPE"); 59 } 60 61 if ((exists $TYPES{$type}) || (exists $RU_TYPES{$ruType})) { 62 my %item; 63 $item{TARGET} = $target; 64 $item{OBMC_NAME} = $target; #Will fixup later 65 push @$inventory, { %item }; 66 } 67 } 68} 69 70 71#Removes entries from the inventory for the card target of a module. 72#Needed because processors and GPUs are modeled as a package which 73#is a card-chip instance that plugs into a connector on the 74#backplane/processor card. Since we already include the chip target 75#in the inventory (that's how we can identify what it is), we don't 76#need the entry for the card target. 77# 78#For example, we'll already have .../module-0/proc-0 so we don't 79#need a separate .../module-0 entry. 80sub pruneModuleCards 81{ 82 my ($targetObj, $inventory) = @_; 83 my @toRemove; 84 85 #Find the parent (a card) of items of type %type 86 for my $item (@$inventory) { 87 88 if (exists $MODULE_TYPES{$targetObj->getType($item->{TARGET})}) { 89 my $card = $targetObj->getTargetParent($item->{TARGET}); 90 push @toRemove, $card; 91 } 92 } 93 94 #Remove these parent cards 95 for my $c (@toRemove) { 96 for my $i (0 .. (scalar @$inventory) - 1) { 97 if ($c eq $inventory->[$i]{TARGET}) { 98 splice(@$inventory, $i, 1); 99 last; 100 } 101 } 102 } 103} 104 105 106#Makes the OpenBMC name for the targets in the inventory. 107#Removes unnecessary segments of the path name, renames 108#some segments to match standard conventions, and numbers 109#segments based on their position attribute. 110sub makeOBMCNames 111{ 112 my ($targetObj, $inventory) = @_; 113 114 #Remove connector segments from the OBMC_NAME 115 removeConnectors($targetObj, $inventory); 116 117 #Don't need the card instance of a PROC/GPU module 118 removeModuleFromPath($targetObj, $inventory); 119 120 #Don't need card segments for non-FRUs 121 removeNonFRUCardSegments($targetObj, $inventory); 122 123 #Don't need to show the middle units between proc & core 124 removeIntermediateUnits($targetObj, $inventory); 125 126 #Certain parts have standard naming 127 renameSegmentWithType("PROC", "cpu", $targetObj, $inventory); 128 renameSegmentWithType("SYS", "system", $targetObj, $inventory); 129 renameSegmentWithType("NODE", "chassis", $targetObj, $inventory); 130 131 #Make sure the motherboard is called 'motherboard' in the OBMC_NAME. 132 #The only way to identify it is with its TARGET_TYPE, 133 #which is different than the regular type. 134 renameSegmentWithTargetType("card-motherboard", "motherboard", 135 $targetObj, $inventory); 136 137 #Don't need instance numbers unless there are more than 1 present 138 removeInstNumIfOneInstPresent($inventory); 139 140 #We want card1, not card-1 141 removeHyphensFromInstanceNum($inventory); 142 143 pointChassisAtMotherboard($targetObj, $inventory); 144} 145 146 147#Removes non-FRU cards in the middle of a hierarchy from OBMC_NAME. 148#For example, .../motherboard/fanriser-0/fan-0 -> 149# .../motherboard/fan-0 when fanriser-0 isn't a FRU. 150sub removeNonFRUCardSegments 151{ 152 my ($targetObj, $inventory) = @_; 153 154 for my $item (@$inventory) { 155 156 #Split the target into segments, then start 157 #adding segments in to make new targets so we can 158 #make API calls on the segment instances. 159 my @segments = split('/', $item->{TARGET}); 160 my $target = ""; 161 for my $s (@segments) { 162 next if (length($s) == 0); 163 164 $target .= "/$s"; 165 166 my $class = $targetObj->getAttribute($target, "CLASS"); 167 next if ($class ne "CARD"); 168 169 my $ruType = $targetObj->getAttribute($target, "RU_TYPE"); 170 171 #If this segment is a card but not a FRU, 172 #remove it from the path. 173 if (not exists $RU_TYPES{$ruType}) { 174 my $segment = $targetObj->getInstanceName($target); 175 $item->{OBMC_NAME} =~ s/\b$segment-\d+\b\///; 176 } 177 } 178 } 179} 180 181 182#Removes connectors from the OBMC_NAME element. Also 183#takes the POSITION value of the connector and adds it 184#to the card segment that plugs into the connector. 185#For example: 186# /motherboard/card-conn-5/card-0 -> 187# /motherobard/card-5 188sub removeConnectors 189{ 190 my ($targetObj, $inventory) = @_; 191 192 #Find the connectors embedded in the segments 193 for my $item (@$inventory) { 194 195 #Split the target into segments, then start 196 #adding segments in to make new targets 197 my @segments = split('/', $item->{TARGET}); 198 my $target = ""; 199 for my $s (@segments) { 200 next if (length($s) == 0); 201 202 $target .= "/$s"; 203 my $class = $targetObj->getAttribute($target, "CLASS"); 204 next unless ($class eq "CONNECTOR"); 205 206 my ($segment) = $target =~ /\b(\w+-\d+)$/; 207 my $pos = $targetObj->getAttribute($target, "POSITION"); 208 209 #change /connector-11/card-2/ to /card-11/ 210 $item->{OBMC_NAME} =~ s/\b$segment\/(\w+)-\d+/$1-$pos/; 211 212 } 213 } 214} 215 216 217#Units, typically cores, can be subunits of other subunits of 218#their parent chip. We can remove all of these intermediate 219#units. For example, cpu0/someunit1/someunit2/core3 -> 220#cpu0/core3. 221sub removeIntermediateUnits 222{ 223 my ($targetObj, $inventory) = @_; 224 225 for my $item (@$inventory) { 226 227 my $class = $targetObj->getAttribute($item->{TARGET}, "CLASS"); 228 next unless ($class eq "UNIT"); 229 230 my $parent = $targetObj->getTargetParent($item->{TARGET}); 231 232 #Remove all of these intermediate units until we find 233 #something that isn't a unit (most likely a chip). 234 while ($targetObj->getAttribute($parent, "CLASS") eq "UNIT") { 235 236 my $name = $targetObj->getInstanceName($parent); 237 $item->{OBMC_NAME} =~ s/$name(-)*(\d+)*\///; 238 239 $parent = $targetObj->getTargetParent($parent); 240 } 241 } 242} 243 244 245#Renames segments of the paths to the name passed in 246#based on the type of the segment. 247#For example: 248# renameSegmentWithType("PROC", "cpu", ...); 249# With a target of: 250# motherboard-0/myp9proc-5/core-3 251# Where target motherboard-0/myp9proc-5 has a type 252# of PROC, gives: 253# motherboard-0/cpu-5/core-3 254sub renameSegmentWithType 255{ 256 my ($type, $newSegment, $targetObj, $inventory) = @_; 257 258 #Find the targets for all the segments, and 259 #if their type matches what we're looking for, then 260 #save it so we can rename them later. 261 for my $item (@$inventory) { 262 263 my @segments = split('/', $item->{TARGET}); 264 my $target = ""; 265 266 for my $s (@segments) { 267 next if (length($s) == 0); 268 269 $target .= "/$s"; 270 my $curType = $targetObj->getType($target); 271 next unless ($curType eq $type); 272 273 my $oldSegment = $targetObj->getInstanceName($target); 274 $item->{OBMC_NAME} =~ s/$oldSegment/$newSegment/; 275 } 276 } 277} 278 279 280#Removes the card portion of a module from OBMC_NAME. 281#For example, .../motherboard-0/module-1/proc-0 -> 282#.../motherboard-0/proc-1. 283#This needs to be revisited if multi-processor modules 284#ever come into plan. 285sub removeModuleFromPath 286{ 287 my ($targetObj, $inventory) = @_; 288 my %chipNames; 289 290 #Find the names of the chips on the modules 291 for my $item (@$inventory) { 292 if (exists $MODULE_TYPES{$targetObj->getType($item->{TARGET})}) { 293 $chipNames{$targetObj->getInstanceName($item->{TARGET})} = 1; 294 } 295 } 296 297 #Now convert module-A/name-B to name-A 298 #Note that the -B isn't always present 299 for my $item (@$inventory) { 300 301 for my $name (keys %chipNames) { 302 $item->{OBMC_NAME} =~ s/\w+-(\d+)\/$name(-\d+)*/$name-$1/; 303 } 304 } 305} 306 307 308#The same as renameSegmentWithType, but finds the segment 309#to rename by calling Targets::getTargetType() on it 310#instead of Targets::getType(). 311sub renameSegmentWithTargetType 312{ 313 my ($type, $newSegment, $targetObj, $inventory) = @_; 314 315 for my $item (@$inventory) { 316 317 my @segments = split('/', $item->{TARGET}); 318 my $target = ""; 319 320 for my $s (@segments) { 321 next if (length($s) == 0); 322 323 $target .= "/$s"; 324 my $curType = $targetObj->getTargetType($target); 325 next unless ($curType eq $type); 326 327 my $oldSegment = $targetObj->getInstanceName($target); 328 $item->{OBMC_NAME} =~ s/$oldSegment/$newSegment/; 329 } 330 } 331} 332 333 334#FRU management code needs the node/chassis item to point 335#to the motherboard and not /system/chassis. Can revisit this 336#for multi-chassis systems if they ever show up. 337sub pointChassisAtMotherboard 338{ 339 my ($targetObj, $inventory) = @_; 340 my $newName = undef; 341 342 for my $item (@$inventory) { 343 my $type = $targetObj->getTargetType($item->{TARGET}); 344 if ($type eq "card-motherboard") { 345 $newName = $item->{OBMC_NAME}; 346 last; 347 } 348 } 349 350 for my $item (@$inventory) { 351 if ($targetObj->getType($item->{TARGET}) eq "NODE") { 352 if (defined $newName) { 353 $item->{OBMC_NAME} = $newName; 354 } 355 last; 356 } 357 } 358} 359 360 361#Removes the instance number from the OBMC_NAME segments 362#where only 1 of those segments exists because numbering isn't 363#necessary to distinguish them. 364sub removeInstNumIfOneInstPresent 365{ 366 my ($inventory) = @_; 367 my %instanceHash; 368 369 for my $item (@$inventory) { 370 #Look at all the segments, keeping track if we've 371 #seen a particular segment with the same instance before. 372 my @segments = split('/', $item->{OBMC_NAME}); 373 for my $segment (@segments) { 374 my ($s, $inst) = $segment =~ /(\w+)-(\d+)/; 375 if (defined $s) { 376 if (not exists $instanceHash{$s}) { 377 $instanceHash{$s}{inst} = $inst; 378 } 379 else { 380 if ($instanceHash{$s}{inst} ne $inst) { 381 $instanceHash{$s}{keep} = 1; 382 } 383 } 384 } 385 } 386 } 387 388 #Remove the instanc numbers we don't need to keep. 389 for my $segment (keys %instanceHash) { 390 if (not exists $instanceHash{$segment}{keep}) { 391 for my $item (@$inventory) { 392 $item->{OBMC_NAME} =~ s/$segment-\d+/$segment/; 393 } 394 } 395 } 396} 397 398 399#Removes the '-' from between the segment name and instance. 400sub removeHyphensFromInstanceNum 401{ 402 my ($inventory) = @_; 403 404 for my $item (@$inventory) { 405 $item->{OBMC_NAME} =~ s/-(\d+)\b/$1/g; 406 } 407} 408 4091; 410 411=head1 NAME 412 413Inventory 414 415=head1 DESCRIPTION 416 417Retrieves the OpenBMC inventory from the MRW. 418 419The inventory contains: 420 421=over 4 422 423=item * The system target 424 425=item * The chassis target(s) (Called a 'node' in the MRW.) 426 427=item * All targets of class CARD or CHIP that are FRUs. 428 429=item * All targets of type PROC 430 431=item * All targets of type CORE 432 433=item * All targets of type BMC 434 435=item * All targets of type GPU 436 437=back 438 439=head2 Notes: 440 441The processor and GPU chips are usually modeled in the MRW as a 442card->chip package that would plug into a connector on the motherboard 443or other parent card. So, even if both card and chip are marked as a FRU, 444there will only be 1 entry in the inventory for both, and the MRW 445target associated with it will be for the chip and not the card. 446 447In addition, this intermediate card will be removed from the path name: 448 /system/chassis/motheboard/cpu and not 449 /system/chassis/motherboard/cpucard/cpu 450 451=head2 Inventory naming conventions 452 453The inventory names returned in the OBMC_NAME hash element will follow 454the conventions listed below. An example of an inventory name is: 455/system/chassis/motherboard/cpu5 456 457=over 4 458 459=item * If there is only 1 instance of any segment in the system, then 460 it won't have an instance number, otherwise there will be one. 461 462=item * The root of the name is '/system'. 463 464=item * After system is 'chassis', of which there can be 1 or more. 465 466=item * The name is based on the MRW card plugging order, and not what 467 the system looks like from the outside. For example, a power 468 supply that plugs into a motherboard (maybe via a non-fru riser 469 or cable, see the item below), would be: 470 /system/chassis/motherboard/psu2 and not 471 /system/chassis/psu2. 472 473=item * If a card is not a FRU so isn't in the inventory itself, then it 474 won't show up in the name of any child cards that are FRUs. 475 For example, if fan-riser isn't a FRU, it would be 476 /system/chassis/motherboard/fan3 and not 477 /system/chassis/motherboard/fan-riser/fan3. 478 479=item * The MRW models connectors between cards, but these never show up 480 in the inventory name. 481 482=item * If there is a motherboard, it is always called 'motherboard'. 483 484=item * Processors, GPUs, and BMCs are always called: 'cpu', 'gpu' and 485 'bmc' respectively. 486 487=back 488 489=head1 METHODS 490 491=over 4 492 493=item getInventory (C<TargetsObj>) 494 495Returns an array of hashes representing inventory items. 496 497The Hash contains: 498 499* TARGET: The MRW target of the item, for example: 500 501 /sys-0/node-0/motherboard-0/proc_socket-0/module-0/p9_proc_m 502 503* OBMC_NAME: The OpenBMC name of the item, for example: 504 505 /system/chassis/motherboard/cpu2 506 507=back 508 509=cut 510