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