| #!/usr/bin/perl -w |
| # A simple Flattened Device Tree Blob (FDT/DTB) parser. |
| # |
| # Copyright (C) 2014 ARM Limited. All rights reserved. |
| # |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE.txt file. |
| |
| use warnings; |
| use strict; |
| use integer; |
| |
| package FDT; |
| |
| # Tokens. These will appear (big endian) in the FDT stream. |
| use constant { |
| FDT_BEGIN_NODE => 0x00000001, |
| FDT_END_NODE => 0x00000002, |
| FDT_PROP => 0x00000003, |
| FDT_NOP => 0x00000004, |
| FDT_END => 0x00000009, |
| }; |
| |
| use constant { |
| CELL_SIZE => 4, |
| TOK_SIZE => 4, |
| }; |
| |
| sub parse |
| { |
| my $class = shift; |
| my $fh = shift; |
| my $self = bless {}, $class; |
| |
| my $header = FDT::Header->parse($fh) or goto failed; |
| |
| seek($fh, $header->{off_dt_struct}, 0); |
| |
| my $tree = FDT::Node->parse($fh, $header) or goto failed; |
| |
| $self->{header} = $header; |
| $self->{tree} = $tree; |
| |
| return $self; |
| |
| failed: |
| warn("Unable to parse FDT"); |
| return undef; |
| } |
| |
| # All tokens (32-bit) are must be naturally aligned, and any arbitrarily sized |
| # data must be padded with zeroes to 32-bit alignment. We swallow the padding |
| # here so as to not have to duplicate the logic elsewhere. |
| sub read_padded_data |
| { |
| my $fh = shift; |
| my $len = shift; |
| my $off = tell($fh); |
| read($fh, my $data, $len) == $len or goto failed; |
| |
| if ($len % FDT::TOK_SIZE != 0) { |
| $len %= FDT::TOK_SIZE; |
| seek $fh, (FDT::TOK_SIZE - $len), 1; |
| } |
| |
| return $data; |
| |
| failed: |
| warn "Failed to read padded data"; |
| seek $fh, $off, 0; |
| return undef; |
| } |
| |
| sub skip_token |
| { |
| my $fh = shift; |
| my $expected = shift; |
| my $curp = tell($fh); |
| |
| read($fh, my $rawtok, FDT::TOK_SIZE) == FDT::TOK_SIZE or goto failed; |
| if (unpack("N", $rawtok) == $expected) { |
| return $expected; |
| } |
| |
| failed: |
| seek $fh, $curp, 0; |
| return undef; |
| } |
| |
| sub skip_nops |
| { |
| my $fh = shift; |
| while (defined(skip_token($fh, FDT::FDT_NOP))) { |
| # do nothing |
| } |
| } |
| |
| sub get_root |
| { |
| my $self = shift; |
| return $self->{tree}; |
| } |
| |
| package FDT::Header; |
| |
| use constant { |
| MAGIC => 0xD00DFEED, |
| LEN => 40, |
| }; |
| |
| sub parse |
| { |
| my $class = shift; |
| my $fh = shift; |
| my $self = bless {}, $class; |
| |
| read($fh, my $raw_header, FDT::Header::LEN) == FDT::Header::LEN or goto failed; |
| |
| ( |
| $self->{magic}, |
| $self->{total_size}, |
| $self->{off_dt_struct}, |
| $self->{off_dt_strings}, |
| $self->{off_mem_rsvmap}, |
| $self->{version}, |
| $self->{last_comp_version}, |
| $self->{boot_cpuid_phys}, |
| $self->{size_dt_strings}, |
| $self->{size_dt_struct} |
| ) = unpack("NNNNNNNNNN", $raw_header); |
| |
| if ($self->{magic} != FDT::Header::MAGIC) { |
| warn "DTB header magic not found"; |
| goto failed; |
| } |
| |
| $self->read_strings($fh); |
| |
| return $self; |
| |
| failed: |
| warn "Unable to parse header"; |
| return undef; |
| } |
| |
| sub read_strings |
| { |
| my $self = shift; |
| my $fh = shift; |
| |
| my $size = $self->{size_dt_strings}; |
| seek($fh, $self->{off_dt_strings}, 0); |
| read($fh, $self->{strings}, $size) == $size or warn "Unable to read strings"; |
| } |
| |
| sub get_string |
| { |
| my $self = shift; |
| my $off = shift; |
| return unpack("Z*", substr($self->{strings}, $off)); |
| } |
| |
| package FDT::Node; |
| |
| sub parse_name |
| { |
| my $fh = shift; |
| my $curp = tell($fh); |
| my $raw_name = ""; |
| my $name = ""; |
| |
| do { |
| read($fh, my $buf, FDT::TOK_SIZE) == FDT::TOK_SIZE or goto failed; |
| $raw_name .= $buf; |
| $name = unpack("A*", $raw_name); |
| } while (length($raw_name) == length($name)); |
| |
| return $name; |
| |
| failed: |
| warn "failed to read string"; |
| seek $fh, $curp, 0; |
| return undef; |
| } |
| |
| sub parse |
| { |
| my $class = shift; |
| my $fh = shift; |
| my $header = shift; |
| my $parent = shift; |
| my $curp = tell($fh); |
| |
| FDT::skip_nops($fh); |
| |
| FDT::skip_token($fh, FDT::FDT_BEGIN_NODE) or goto failed; |
| |
| my $name = parse_name($fh); |
| |
| my $self = bless {}, $class; |
| $self->{name} = $name; |
| $self->{parent} = $parent; |
| $self->{properties} = {}; |
| |
| while (my $prop = FDT::Property->parse($fh, $header)) { |
| $self->{properties}{$prop->{name}} = $prop; |
| } |
| |
| my @children = (); |
| my $child; |
| |
| for (;;) { |
| $child = FDT::Node->parse($fh, $header, $self); |
| last if (not $child); |
| push (@children, $child); |
| } |
| |
| $self->{children} = \@children; |
| |
| FDT::skip_token($fh, FDT::FDT_END_NODE) or goto failed; |
| |
| return $self; |
| |
| failed: |
| seek $fh, $curp, 0; |
| return; |
| } |
| |
| sub is_compatible |
| { |
| my $self = shift; |
| my $string = shift; |
| |
| my $compat_prop = $self->{properties}{"compatible"}; |
| return undef if (not defined($compat_prop)); |
| |
| my @compatible = $compat_prop->read_strings(); |
| |
| for my $compat (@compatible) { |
| return $self if ($string eq $compat); |
| } |
| } |
| |
| sub find_compatible |
| { |
| my $self = shift; |
| my $string = shift; |
| my @found = (); |
| |
| if ($self->is_compatible($string)) { |
| push @found, $self; |
| } |
| |
| for my $child (@{$self->{children}}) { |
| push @found, $child->find_compatible($string); |
| } |
| |
| return @found; |
| } |
| |
| sub find_by_device_type |
| { |
| my $self = shift; |
| my $type = shift; |
| my @found = (); |
| |
| my $selftype = $self->get_property("device_type"); |
| if (defined($selftype)) { |
| if($selftype->read_string_idx(0) eq $type) { |
| push @found, $self; |
| } |
| } |
| |
| for my $child (@{$self->{children}}) { |
| push @found, $child->find_by_device_type($type); |
| } |
| |
| return @found; |
| } |
| |
| sub get_property |
| { |
| my $self = shift; |
| my $name = shift; |
| return $self->{properties}{$name}; |
| } |
| |
| sub get_num_reg_cells |
| { |
| my $self = shift; |
| |
| my ($acp, $scp); |
| my ($ac, $sc); |
| |
| $acp = $self->get_property("#address-cells"); |
| $scp = $self->get_property("#size-cells"); |
| |
| return undef if (not defined($acp) && defined($scp)); |
| |
| $ac = $acp->read_u32_idx(0); |
| $sc = $scp->read_u32_idx(0); |
| |
| return ($ac, $sc); |
| } |
| |
| sub translate_address |
| { |
| my $self = shift; |
| my $addr = shift; |
| my $parent = $self->{parent}; |
| |
| # root node require no translation |
| return $addr if (not $parent); |
| |
| my $ranges = $self->get_property("ranges"); |
| if (not defined($ranges)) { |
| warn ("Missing ranges on " . $self->{name} . ", idmap assumed"); |
| return $addr; |
| } |
| |
| # An empty ranges property means idmap |
| return $addr if ($ranges->{len} == 0); |
| |
| my ($ac, $sc) = $self->get_num_reg_cells(); |
| my ($pac, $psc) = $parent->get_num_reg_cells(); |
| |
| if (not defined($ac) && defined($sc) && defined($pac) && defined($psc)) { |
| warn "Missing #address-cells or #size-cells"; |
| return undef; |
| } |
| |
| my $rc = $ac + $pac + $sc; |
| |
| if ($ranges->num_cells() % $rc != 0) { |
| warn("Malformed ranges property"); |
| return undef; |
| } |
| |
| for (my $off = 0; $off < $ranges->num_cells(); $off += $rc) { |
| my ($cba, $pba, $len) = $ranges->read_cell_list($off, [$ac, $pac, $sc]); |
| |
| next if ($cba > $addr or $addr >= $cba + $len); |
| |
| return $addr - $cba + $pba; |
| } |
| |
| warn "Did not find valid translation"; |
| return undef; |
| } |
| |
| sub get_translated_reg |
| { |
| my $self = shift; |
| my $idx = shift; |
| my $parent = $self->{parent}; |
| |
| my ($ac, $sc) = $parent->get_num_reg_cells(); |
| my $reg = $self->get_property("reg"); |
| |
| my $off = $idx * ($ac + $sc); |
| |
| return undef if ($off + $ac + $sc > $reg->num_cells()); |
| |
| my ($addr, $size) = $reg->read_cell_list($off, [$ac, $sc]); |
| |
| for (my $parent = $self->{parent}; $parent; $parent = $parent->{parent}) { |
| last if (not $addr); |
| $addr = $parent->translate_address($addr); |
| } |
| |
| return ($addr, $size); |
| } |
| |
| package FDT::Property; |
| |
| sub parse |
| { |
| my $class = shift; |
| my $fh = shift; |
| my $header = shift; |
| my $curp = tell($fh); |
| |
| my $self = bless {}, $class; |
| |
| FDT::skip_nops($fh); |
| |
| FDT::skip_token($fh, FDT::FDT_PROP) or goto failed; |
| |
| read ($fh, my $rawprop, 8) == 8 or goto failed; |
| |
| my ($len, $nameoff) = unpack("NN", $rawprop); |
| $self->{name} = $header->get_string($nameoff); |
| |
| if ($len != 0) { |
| $self->{data} = FDT::read_padded_data($fh, $len); |
| } |
| goto failed if ($len and not $self->{data}); |
| |
| $self->{len} = $len; |
| |
| return $self; |
| |
| failed: |
| seek $fh, $curp, 0; |
| return undef |
| } |
| |
| sub num_cells |
| { |
| my $self = shift; |
| return $self->{len} / FDT::CELL_SIZE; |
| } |
| |
| sub read_cells_off |
| { |
| my $self = shift; |
| my $off = shift; |
| my $cells = shift; |
| my $fmt; |
| |
| # We only support u32 and u64 values |
| if ($cells == 1) { |
| $fmt = "N"; |
| } elsif ($cells == 2) { |
| $fmt = "Q>"; |
| } else { |
| return undef; |
| } |
| |
| $off *= FDT::CELL_SIZE; |
| my $raw = substr($self->{data}, $off); |
| |
| return unpack($fmt, $raw) if (defined($raw)); |
| return undef; |
| } |
| |
| sub read_u32_idx |
| { |
| my $self = shift; |
| my $idx = shift; |
| return $self->read_cells_off($idx, 1); |
| } |
| |
| sub read_u64_idx |
| { |
| my $self = shift; |
| my $idx = shift; |
| return $self->read_cells_off($idx * 2, 2); |
| } |
| |
| sub read_cell_list |
| { |
| my $self = shift; |
| my $off = shift; |
| my $cells = shift; |
| |
| my @ret; |
| |
| for (@{$cells}) { |
| my $val = $self->read_cells_off($off, $_); |
| push @ret, ($val); |
| $off += $_; |
| } |
| |
| return @ret; |
| } |
| |
| sub read_strings |
| { |
| my $self = shift; |
| return split('\0', $self->{data}); |
| } |
| |
| sub read_string_idx |
| { |
| my $self = shift; |
| my $idx = shift; |
| my @strings = $self->read_strings(); |
| return $strings[$idx]; |
| } |
| |
| 1; |