Matt Spinler | 7d381e1 | 2016-09-27 14:27:24 -0500 | [diff] [blame^] | 1 | #!/usr/bin/env perl |
| 2 | |
| 3 | #Generates a BMC device tree syntax file from the machine |
| 4 | #readable workbook. |
| 5 | |
| 6 | use strict; |
| 7 | use XML::Simple; |
| 8 | use mrw::Targets; |
| 9 | use Getopt::Long; |
| 10 | |
| 11 | use constant VERSION => "/dts-v1/;"; |
| 12 | use constant STANDALONE_PROPERTY => "standalone_property"; |
| 13 | use constant DTSI_INCLUDE => "DTSI_INCLUDE"; |
| 14 | |
| 15 | my $serverwizFile; |
| 16 | my $outputFile; |
| 17 | my $debug; |
| 18 | |
| 19 | GetOptions("x=s" => \$serverwizFile, |
| 20 | "o=s" => \$outputFile, |
| 21 | "d" => \$debug) |
| 22 | or printUsage(); |
| 23 | |
| 24 | if ((not defined $serverwizFile) || (not defined $outputFile)) { |
| 25 | printUsage(); |
| 26 | } |
| 27 | |
| 28 | my $g_targetObj = Targets->new; |
| 29 | $g_targetObj->loadXML($serverwizFile); |
| 30 | |
| 31 | my $g_bmc = getBMCTarget(); |
| 32 | if (length($g_bmc) == 0) { |
| 33 | die "Unable to find a BMC in this system\n"; |
| 34 | } |
| 35 | |
| 36 | my $g_bmcModel = $g_targetObj->getAttribute($g_bmc, "MODEL"); |
| 37 | my $g_bmcMfgr = $g_targetObj->getAttribute($g_bmc, "MANUFACTURER"); |
| 38 | my $g_systemName = $g_targetObj->getSystemName(); |
| 39 | |
| 40 | open (my $f, ">$outputFile") or die "Could not open $outputFile\n"; |
| 41 | |
| 42 | printVersion($f); |
| 43 | printIncludes($f, 0); |
| 44 | printRootNodeStart($f); |
| 45 | |
| 46 | printPropertyList($f, 1, "model", getSystemBMCModel()); |
| 47 | |
| 48 | printPropertyList($f, 1, "compatible", getBMCCompatibles()); |
| 49 | printNode($f, 1, "chosen", getChosen()); |
| 50 | printNode($f, 1, "memory", getMemory($g_bmc)); |
| 51 | |
| 52 | #TODO: LEDs, UART, I2C, aliases, pinctlr |
| 53 | printRootNodeEnd($f, 0); |
| 54 | |
| 55 | printNodes($f, 0, getMacNodes()); |
| 56 | |
| 57 | printNodes($f, 0, getVuartNodes()); |
| 58 | |
| 59 | close $f; |
| 60 | exit 0; |
| 61 | |
| 62 | |
| 63 | |
| 64 | #Return a hash that represents the 'chosen' node |
| 65 | sub getChosen() |
| 66 | { |
| 67 | my $bmcStdOut = $g_targetObj->getAttributeField($g_bmc, "BMC_DT_CHOSEN", |
| 68 | "stdout-path"); |
| 69 | my $args = $g_targetObj->getAttributeField($g_bmc, "BMC_DT_CHOSEN", |
| 70 | "bootargs"); |
| 71 | my %chosen; |
| 72 | $chosen{"stdout-path"} = $bmcStdOut; |
| 73 | $chosen{"bootargs"} = $args; |
| 74 | return %chosen; |
| 75 | } |
| 76 | |
| 77 | |
| 78 | #Returns a list of hashes that represent the MAC (ethernet) nodes on the BMC |
| 79 | sub getMacNodes() |
| 80 | { |
| 81 | my @nodes; |
| 82 | my $children = $g_targetObj->getTargetChildren($g_bmc); |
| 83 | |
| 84 | #The next version of this will look for ethernet connections in the |
| 85 | #MRW instead of just the units... |
| 86 | foreach my $c (@$children) { |
| 87 | |
| 88 | if ($g_targetObj->getTargetType($c) eq "unit-ethernet-master") { |
| 89 | |
| 90 | if ($g_targetObj->getAttribute($c, "UNIT_ENABLED") == 1) { |
| 91 | my %node; |
| 92 | my $num = $g_targetObj->getAttribute($c, "CHIP_UNIT"); |
| 93 | my $ncsi = $g_targetObj->getAttribute($c, "NCSI_MODE"); |
| 94 | my $hwChecksum = $g_targetObj->getAttribute($c, |
| 95 | "USE_HW_CHECKSUM"); |
| 96 | |
| 97 | my $name = "mac$num"; |
| 98 | $node{$name}{status} = "okay"; |
| 99 | if ($ncsi == 1) { |
| 100 | $node{$name}{"use-ncsi"} = STANDALONE_PROPERTY; |
| 101 | } |
| 102 | if ($hwChecksum == 0) { |
| 103 | $node{$name}{"no-hw-checksum"} = STANDALONE_PROPERTY; |
| 104 | } |
| 105 | |
| 106 | push @nodes, { %node }; |
| 107 | } |
| 108 | } |
| 109 | } |
| 110 | return @nodes; |
| 111 | } |
| 112 | |
| 113 | |
| 114 | #Returns a last of hashes that represent the virtual UART nodes |
| 115 | sub getVuartNodes() |
| 116 | { |
| 117 | my @nodes; |
| 118 | my %node; |
| 119 | |
| 120 | #For now, enable 1 node all the time. |
| 121 | #TODO if this needs to be fixed. |
| 122 | $node{vuart}{status} = "okay"; |
| 123 | |
| 124 | push @nodes, { %node }; |
| 125 | |
| 126 | return @nodes; |
| 127 | } |
| 128 | |
| 129 | |
| 130 | #Returns a hash{'reg'} = "<.....>" based on the |
| 131 | #BMC_DT_MEMORY attribute. This is used to display |
| 132 | #memory ranges. |
| 133 | sub getMemory() |
| 134 | { |
| 135 | my $target = shift; |
| 136 | my $memory = $g_targetObj->getAttribute($target, "BMC_DT_MEMORY"); |
| 137 | my @mem = split(',', $memory); |
| 138 | my %property; |
| 139 | my $val = "<"; |
| 140 | |
| 141 | #Encoded as 4 <base address>,<size> pairs of memory ranges |
| 142 | #Unused ranges are all 0s. |
| 143 | #For now, assumes 32 bit numbers, revisit later for 64 bit support |
| 144 | #Convert it into: <num1 num2 num3 num4 etc> |
| 145 | |
| 146 | for (my $i = 0;$i < scalar @mem;$i += 2) { |
| 147 | |
| 148 | #pair is valid if size is nonzero |
| 149 | if (hex($mem[$i+1]) != 0) { |
| 150 | $val .= "$mem[$i] $mem[$i+1] "; |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | $val =~ s/\s$//; |
| 155 | $val .= ">"; |
| 156 | $property{reg} = $val; |
| 157 | |
| 158 | return %property; |
| 159 | } |
| 160 | |
| 161 | |
| 162 | #Returns a list of compatible fields for the BMC itself. |
| 163 | sub getBMCCompatibles() |
| 164 | { |
| 165 | my @compats; |
| 166 | |
| 167 | #The first one is from the MRW, the next one is more generic |
| 168 | #and just <mfgr>-<model>. |
| 169 | |
| 170 | if (!$g_targetObj->isBadAttribute($g_bmc, "BMC_DT_COMPATIBLE", "NA")) { |
| 171 | my $attr = $g_targetObj->getAttribute($g_bmc, "BMC_DT_COMPATIBLE"); |
| 172 | push @compats, $attr; |
| 173 | } |
| 174 | |
| 175 | push @compats, lc($g_bmcMfgr).",".lc($g_bmcModel); |
| 176 | |
| 177 | return @compats; |
| 178 | } |
| 179 | |
| 180 | |
| 181 | #Returns a string for the system's BMC model property |
| 182 | sub getSystemBMCModel() |
| 183 | { |
| 184 | #<System> BMC |
| 185 | my $sys = lc $g_systemName; |
| 186 | $sys = uc(substr($sys, 0, 1)) . substr($sys, 1); |
| 187 | |
| 188 | return $sys . " BMC"; |
| 189 | } |
| 190 | |
| 191 | |
| 192 | #Prints a list of nodes at the same indent level |
| 193 | # $f = file handle |
| 194 | # $level = indent level (0,1,etc) |
| 195 | # @nodes = array of node hashes to print, where the |
| 196 | # key for the hash is the name of the node |
| 197 | sub printNodes() |
| 198 | { |
| 199 | my ($f, $level, @nodes) = @_; |
| 200 | |
| 201 | foreach my $n (@nodes) { |
| 202 | my %node = %$n; |
| 203 | |
| 204 | foreach my $name (sort keys %node) { |
| 205 | my %n = %{ $node{$name} }; |
| 206 | printNode($f, $level, $name, %n); |
| 207 | } |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | |
| 212 | #Print a single node and its children |
| 213 | # $f = file handle |
| 214 | # $level = indent level (0,1,etc) |
| 215 | # $name = the name of the node - shows up as: |
| 216 | # name { ... |
| 217 | # %vals = The contents of the node, with the following options: |
| 218 | # if the key is: |
| 219 | # - 'DTSI_INCLUDE', then value gets turned into a #include |
| 220 | # - 'COMMENT', then value gets turned into a // comment (coming soon) |
| 221 | # - 'STANDALONE_PROPERTY' then value gets turned into: value; |
| 222 | # |
| 223 | # If the value is: |
| 224 | # - a hash - then that hash gets turned into a child node |
| 225 | # where the key is the name of the child node |
| 226 | # - an array of hashes indicates an array of nodes (coming soon) |
| 227 | sub printNode() |
| 228 | { |
| 229 | my ($f, $level, $name, %vals) = @_; |
| 230 | my $include = ""; |
| 231 | |
| 232 | if ($level == 0) { |
| 233 | $name = "&".$name; |
| 234 | } |
| 235 | |
| 236 | print $f "\n".indent($level) . "$name {\n"; |
| 237 | |
| 238 | foreach my $v (sort keys %vals) { |
| 239 | |
| 240 | #A header file include, print it later |
| 241 | if ($v eq DTSI_INCLUDE) { |
| 242 | $include = $vals{$v}; |
| 243 | } |
| 244 | #A nested node |
| 245 | elsif (ref($vals{$v}) eq "HASH") { |
| 246 | printNode($f, $level+1, $v, %{$vals{$v}}); |
| 247 | } |
| 248 | elsif ($vals{$v} ne STANDALONE_PROPERTY) { |
| 249 | printProperty($f, $level+1, $v, $vals{$v}); |
| 250 | } |
| 251 | else { |
| 252 | printStandaloneProperty($f, $level+1, $v); |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | #Now print the includes, if any. |
| 257 | if ($include ne "") { |
| 258 | my @incs = split(',', $include); |
| 259 | foreach my $i (@incs) { |
| 260 | print $f "#include \"$i\";\n"; |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | print $f indent($level) . "};\n"; |
| 265 | } |
| 266 | |
| 267 | |
| 268 | #Prints a comma separated list of properties. |
| 269 | #e.g. a = "b, c, d"; |
| 270 | # $f = file handle |
| 271 | # $level = indent level (0,1,etc) |
| 272 | # $name = name of property |
| 273 | # @vals = list of property values |
| 274 | sub printPropertyList() |
| 275 | { |
| 276 | my ($f, $level, $name, @vals) = @_; |
| 277 | |
| 278 | print $f indent($level) . "$name = "; |
| 279 | |
| 280 | for (my $i = 0;$i < scalar @vals; $i++) { |
| 281 | print $f "\"$vals[$i]\""; |
| 282 | if ($i < (scalar(@vals) - 1)) { |
| 283 | print $f ", "; |
| 284 | } |
| 285 | } |
| 286 | print $f ";\n" |
| 287 | } |
| 288 | |
| 289 | |
| 290 | #Prints a single property. e.g. a = "b"; |
| 291 | # $f = file handle |
| 292 | # $level = indent level (0,1,etc) |
| 293 | # $name = name of property |
| 294 | # @vals = property values |
| 295 | sub printProperty() |
| 296 | { |
| 297 | my ($f, $level, $name, $val) = @_; |
| 298 | print $f indent($level) . "$name = \"" . convertAlias($val) . "\";\n"; |
| 299 | } |
| 300 | |
| 301 | |
| 302 | #Prints a standalone property e.g. some-property; |
| 303 | # $f = file handle |
| 304 | # $level = indent level (0,1,etc) |
| 305 | # $name = name of property |
| 306 | sub printStandaloneProperty() |
| 307 | { |
| 308 | my ($f, $level, $name) = @_; |
| 309 | print $f indent($level) . "$name;\n"; |
| 310 | } |
| 311 | |
| 312 | |
| 313 | #Replace '(alias)' with '&'. |
| 314 | #Needed because Serverwiz doesn't properly escape '&'s in the XML, |
| 315 | #so the '(alias)' string is used to represent the alias |
| 316 | #specifier instead of '&'. |
| 317 | sub convertAlias() { |
| 318 | my $val = shift; |
| 319 | $val =~ s/\(alias\)/&/g; |
| 320 | return $val |
| 321 | } |
| 322 | |
| 323 | |
| 324 | #Returns the target for the BMC chip. |
| 325 | #Not worrying about multiple BMC systems for now. |
| 326 | sub getBMCTarget() |
| 327 | { |
| 328 | foreach my $target (sort keys %{ $g_targetObj->getAllTargets() }) |
| 329 | { |
| 330 | if ($g_targetObj->getType($target) eq "BMC") { |
| 331 | return $target; |
| 332 | } |
| 333 | } |
| 334 | return ""; |
| 335 | } |
| 336 | |
| 337 | |
| 338 | #Prints the device tree version line. |
| 339 | # $f = file handle |
| 340 | sub printVersion() |
| 341 | { |
| 342 | my $f = shift; |
| 343 | print $f VERSION."\n" |
| 344 | } |
| 345 | |
| 346 | |
| 347 | #Prints the #include line for pulling in an include file. |
| 348 | # $f = file handle |
| 349 | # $level = indent level (0,1,etc) |
| 350 | sub printIncludes() |
| 351 | { |
| 352 | my ($f, $level) = @_; |
| 353 | my @includes = getIncludes($g_bmc); |
| 354 | |
| 355 | foreach my $i (@includes) { |
| 356 | #if a .dtsi, gets " ", otherwise < > |
| 357 | if ($i =~ /\.dtsi$/) { |
| 358 | $i = "\"" . $i . "\""; |
| 359 | } |
| 360 | else { |
| 361 | $i = "<" . $i . ">"; |
| 362 | } |
| 363 | print $f indent($level) . "#include $i;\n"; |
| 364 | } |
| 365 | } |
| 366 | |
| 367 | |
| 368 | #Returns an array of includes from the BMC_DT_INCLUDES attribute |
| 369 | #on the target passed in. |
| 370 | # $target = the target to get the includes from |
| 371 | sub getIncludes() |
| 372 | { |
| 373 | my $target = shift; |
| 374 | my @includes; |
| 375 | |
| 376 | |
| 377 | if (!$g_targetObj->isBadAttribute($target, "BMC_DT_INCLUDES")) { |
| 378 | my $attr = $g_targetObj->getAttribute($target, "BMC_DT_INCLUDES"); |
| 379 | my @incs = split(',', $attr); |
| 380 | |
| 381 | foreach my $i (@incs) { |
| 382 | if ($i ne "NA") { |
| 383 | push @includes, $i |
| 384 | } |
| 385 | } |
| 386 | } |
| 387 | |
| 388 | return @includes; |
| 389 | } |
| 390 | |
| 391 | |
| 392 | #Prints the root node starting bracket. |
| 393 | # $f = file handle |
| 394 | sub printRootNodeStart() { |
| 395 | my $f = shift; |
| 396 | print $f "\\ \{\n"; |
| 397 | } |
| 398 | |
| 399 | |
| 400 | #Prints the root node ending bracket. |
| 401 | # $f = file handle |
| 402 | # $level = indent level (0,1,etc) |
| 403 | sub printRootNodeEnd() { |
| 404 | my ($f, $level) = @_; |
| 405 | print $f indent($level)."\};\n"; |
| 406 | } |
| 407 | |
| 408 | |
| 409 | #Returns a string that can be used to indent based on the |
| 410 | #level passed in. Each level is an additional 4 spaces. |
| 411 | # $level = indent level (0,1,etc) |
| 412 | sub indent() { |
| 413 | my $level = shift; |
| 414 | return ' ' x ($level * 4); |
| 415 | } |
| 416 | |
| 417 | |
| 418 | sub printUsage |
| 419 | { |
| 420 | print "gen_devtree.pl -x [XML filename] -o [output filename]\n"; |
| 421 | exit(1); |
| 422 | } |