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