| #!/usr/bin/python |
| |
| # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """This library provides basic access to an fdt blob.""" |
| |
| import optparse |
| import os |
| import re |
| import shutil |
| import sys |
| |
| import cros_output |
| import tools |
| from tools import Tools |
| from tools import CmdError |
| |
| _base = os.path.dirname(sys.argv[0]) |
| |
| class Fdt: |
| """Provides simple access to a flat device tree blob |
| |
| Properties: |
| fname: Filename of fdt |
| """ |
| def __init__(self, tools, fname): |
| self.fname = fname |
| self.tools = tools |
| root, ext = os.path.splitext(fname) |
| self._is_compiled = ext == '.dtb' |
| |
| def GetProp(self, node, prop, default=None): |
| """Get a property from a device tree. |
| |
| >>> tools = Tools(cros_output.Output()) |
| >>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb')) |
| >>> fdt.GetProp('/lcd', 'width') |
| '1366' |
| |
| >>> fdt.GetProp('/', 'fluffy') |
| Traceback (most recent call last): |
| ... |
| CmdError: Command failed: fdtget ../tests/test.dtb / fluffy |
| Error at 'fluffy': FDT_ERR_NOTFOUND |
| <BLANKLINE> |
| |
| This looks up the given node and property, and returns the value as a |
| string, |
| |
| If the node or property does not exist, this will return the default value. |
| |
| Args: |
| node: Full path to node to look up. |
| prop: Property name to look up. |
| default: Default value to return if nothing is present in the fdt, or |
| None to raise in this case. This will be converted to a string. |
| |
| Returns: |
| string containing the property value. |
| |
| Raises: |
| CmdError: if the property does not exist and no default is provided. |
| """ |
| args = [self.fname, node, prop] |
| if default is not None: |
| args += ['-d', str(default)] |
| out = self.tools.Run('fdtget', args) |
| return out.strip() |
| |
| def GetProps(self, node, convert_dashes=False): |
| """Get all properties from a node. |
| |
| >>> tools = Tools(cros_output.Output()) |
| >>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb')) |
| >>> fdt.GetProps('/') |
| {'compatible': '1853253988 1767976051 1700881007 1634886656 1853253988 \ |
| 1767976052 1701278305 842346496', '#size-cells': '1', 'model': \ |
| 'NVIDIA Seaboard', '#address-cells': '1', 'interrupt-parent': '1'} |
| |
| Args: |
| node: node name to look in. |
| convert_dashes: True to convert - to _ in node names. |
| |
| Returns: |
| A dictionary containing all the properties, indexed by node name. |
| The entries are simply strings - no decoding of lists or numbers is |
| done. |
| |
| Raises: |
| CmdError: if the node does not exist. |
| """ |
| out = self.tools.Run('fdtget', [self.fname, node, '-p']) |
| props = out.strip().splitlines() |
| props_dict = {} |
| for prop in props: |
| name = prop |
| if convert_dashes: |
| prop = re.sub('-', '_', prop) |
| props_dict[prop] = self.GetProp(node, name) |
| return props_dict |
| |
| def DecodeIntList(self, node, prop, int_list_str, num_values=None): |
| """Decode a string into a list of integers. |
| |
| >>> tools = Tools(cros_output.Output()) |
| >>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb')) |
| >>> fdt.DecodeIntList('/', 'galveston', '1 2 3 4') |
| [1, 2, 3, 4] |
| |
| >>> fdt.DecodeIntList('/', 'galveston', '1 2 3 4', 4) |
| [1, 2, 3, 4] |
| |
| >>> fdt.DecodeIntList('/', 'galveston', '1 2 3 4', 3) |
| Traceback (most recent call last): |
| ... |
| ValueError: GetIntList of node '/' prop 'galveston' returns \ |
| '<type 'list'>', which has 4 elements, but 3 expected |
| |
| This decodes a string containing a list of integers like '1 2 3' into |
| a list like [1 2 3]. |
| |
| Args: |
| node: Full path to node to report in any error raised. |
| prop: Property name to report in any error raised. |
| int_list_str: String to decode. |
| num_values: If not None, then the array is checked to make sure it |
| has this many values, and an error is raised if not. |
| |
| Returns: |
| List of integers. |
| |
| Raises: |
| ValueError if the list is the wrong size. |
| """ |
| int_list = int_list_str.split() |
| if num_values and num_values != len(int_list): |
| raise ValueError, ("GetIntList of node '%s' prop '%s' returns '%s'" |
| ", which has %d elements, but %d expected" % |
| (node, prop, list, len(int_list), num_values)) |
| return [int(item) for item in int_list] |
| |
| def GetIntList(self, node, prop, num_values=None, default=None): |
| """Read a property and decode it into a list of integers. |
| |
| >>> tools = Tools(cros_output.Output()) |
| >>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb')) |
| >>> fdt.GetIntList('/flash@0/shared-dev-cfg@180000', 'reg') |
| [1572864, 262144] |
| |
| >>> fdt.GetIntList('/flash/shared-dev-cfg', 'reg') |
| [1572864, 262144] |
| |
| >>> fdt.GetIntList('/flash/shared-dev-cfg', 'reg', 3) |
| Traceback (most recent call last): |
| ... |
| ValueError: GetIntList of node '/flash/shared-dev-cfg' prop 'reg' returns \ |
| '<type 'list'>', which has 2 elements, but 3 expected |
| |
| >>> fdt.GetIntList('/swaffham', 'bulbeck', 2) |
| Traceback (most recent call last): |
| ... |
| CmdError: Command failed: fdtget ../tests/test.dtb /swaffham bulbeck |
| Error at '/swaffham': FDT_ERR_NOTFOUND |
| <BLANKLINE> |
| >>> fdt.GetIntList('/lcd', 'bulbeck', 2, '5 6') |
| [5, 6] |
| |
| This decodes a property containing a list of integers like '1 2 3' into |
| a list like [1 2 3]. |
| |
| Args: |
| node: Full path to node to look up. |
| prop: Property name to look up. |
| num_values: If not None, then the array is checked to make sure it |
| has this many values, and an error is raised if not. |
| |
| Returns: |
| List of integers. |
| |
| Raises: |
| ValueError if the list is the wrong size. |
| CmdError: if the property does not exist. |
| """ |
| return self.DecodeIntList(node, prop, self.GetProp(node, prop, default), |
| num_values) |
| |
| def GetInt(self, node, prop, default=None): |
| """Gets an integer from a device tree property. |
| |
| >>> tools = Tools(cros_output.Output()) |
| >>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb')) |
| >>> fdt.GetInt('/lcd', 'width') |
| 1366 |
| >>> fdt.GetInt('/lcd', 'rangiora') |
| Traceback (most recent call last): |
| ... |
| CmdError: Command failed: fdtget ../tests/test.dtb /lcd rangiora |
| Error at 'rangiora': FDT_ERR_NOTFOUND |
| <BLANKLINE> |
| |
| >>> fdt.GetInt('/lcd', 'rangiora', 1366) |
| 1366 |
| |
| Args: |
| node: Full path to node to look up. |
| prop: Property name to look up. |
| |
| Raises: |
| ValueError if the property cannot be converted to an integer. |
| CmdError: if the property does not exist. |
| """ |
| value = self.GetIntList(node, prop, 1, default)[0] |
| return int(value) |
| |
| def GetString(self, node, prop, default=None): |
| """Gets a string from a device tree property. |
| |
| >>> tools = Tools(cros_output.Output()) |
| >>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb')) |
| >>> fdt.GetString('/display', 'compatible') |
| 'nvidia,tegra250-display' |
| |
| Args: |
| node: Full path to node to look up. |
| prop: Property name to look up. |
| |
| Raises: |
| CmdError: if the property does not exist. |
| """ |
| return self.GetProp(node, prop, default) |
| |
| def GetFlashPart(self, section, part): |
| """Returns the setup of the given section/part number in the flash map. |
| |
| >>> tools = Tools(cros_output.Output()) |
| >>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb')) |
| >>> fdt.GetFlashPart('ro', 'onestop') |
| [65536, 524288] |
| |
| Args: |
| section: Section name to look at: ro, rw-a, etc. |
| part: Partition name to look at: gbb, vpd, etc. |
| |
| Returns: |
| Tuple (position, size) of flash area in bytes. |
| """ |
| return self.GetIntList('/flash/%s-%s' % (section, part), 'reg', 2) |
| |
| def GetFlashPartSize(self, section, part): |
| """Returns the size of the given section/part number in the flash map. |
| |
| >>> tools = Tools(cros_output.Output()) |
| >>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb')) |
| >>> fdt.GetFlashPartSize('ro', 'onestop') |
| 524288 |
| |
| Args: |
| section: Section name to look at: ro, rw-a, etc. |
| part: Partition name to look at: gbb, vpd, etc. |
| |
| Returns: |
| Size of flash area in bytes. |
| """ |
| return self.GetFlashPart(section, part)[1] |
| |
| def GetChildren(self, node): |
| """Returns a list of children of a given node. |
| |
| >>> tools = Tools(cros_output.Output()) |
| >>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb')) |
| >>> fdt.GetChildren('/amba') |
| ['interrupt-controller@50041000'] |
| |
| >>> fdt.GetChildren('/flash@0') |
| ['onestop-layout@0', 'firmware-image@0', 'verification-block@7df00', \ |
| 'firmware-id@7ff00', 'readonly@0', 'bct@0', 'ro-onestop@10000', \ |
| 'ro-gbb@90000', 'ro-data@b0000', 'ro-vpd@c0000', 'fmap@d0000', \ |
| 'readwrite@100000', 'rw-vpd@100000', 'shared-dev-cfg@180000', \ |
| 'shared-data@1c0000', 'shared-env@1ff000', 'readwrite-a@200000', \ |
| 'rw-a-onestop@200000', 'readwrite-b@300000', 'rw-b-onestop@300000'] |
| |
| Args: |
| node: Node to return children from. |
| |
| Returns: |
| List of children in the node. |
| |
| Raises: |
| CmdError: if the node does not exist. |
| """ |
| out = self.tools.Run('fdtget', [self.fname, '-l', node]) |
| return out.strip().splitlines() |
| |
| def GetLabel(self, node): |
| """Returns the label property of a given node. |
| |
| >>> tools = Tools(cros_output.Output()) |
| >>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb')) |
| >>> fdt.GetLabel('/flash/ro-onestop') |
| 'ro-onestop' |
| |
| >>> fdt.GetLabel('/go/hotspurs') |
| Traceback (most recent call last): |
| ... |
| CmdError: Command failed: fdtget ../tests/test.dtb /go/hotspurs label |
| Error at '/go/hotspurs': FDT_ERR_NOTFOUND |
| <BLANKLINE> |
| |
| Args: |
| node: Node to return label property from. |
| |
| Raises: |
| CmdError: if the node or property does not exist. |
| """ |
| return self.GetString(node, 'label') |
| |
| def Copy(self, new_name): |
| """Make a copy of the FDT into another file, and return its object. |
| |
| >>> tools = Tools(cros_output.Output()) |
| >>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb')) |
| >>> our_copy = fdt.Copy(os.path.join(_base, '../tests/copy.dtb')) |
| >>> our_copy.PutString('/display', 'compatible', 'north') |
| >>> fdt.GetString('/display', 'compatible') |
| 'nvidia,tegra250-display' |
| >>> our_copy.GetString('/display', 'compatible') |
| 'north' |
| |
| This copies the FDT into a supplied file, then creates an FDT object to |
| access the copy. |
| |
| Args: |
| new_name: Filename to write copy to. |
| |
| Returns: |
| An Fdt object for the copy. |
| """ |
| shutil.copyfile(self.tools.Filename(self.fname), |
| self.tools.Filename(new_name)) |
| return Fdt(self.tools, new_name) |
| |
| def PutString(self, node, prop, value_str): |
| """Writes a string to a property in the fdt. |
| |
| >>> tools = Tools(cros_output.Output()) |
| >>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb')) |
| >>> our_copy = fdt.Copy(os.path.join(_base, '../tests/copy.dtb')) |
| >>> our_copy.PutString('/display', 'compatible', 'north') |
| >>> fdt.GetString('/display', 'compatible') |
| 'nvidia,tegra250-display' |
| >>> our_copy.PutString('/display', 'compatible', 'south') |
| >>> our_copy.GetString('/display', 'compatible') |
| 'south' |
| |
| Args: |
| node: Full path to node to look up. |
| prop: Property name to look up. |
| value_str: String to write. |
| """ |
| args = ['-t', 's', self.fname, node, prop, value_str] |
| self.tools.Run('fdtput', args) |
| |
| def PutInteger(self, node, prop, value_int): |
| """Writes a string to a property in the fdt. |
| |
| >>> tools = Tools(cros_output.Output()) |
| >>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb')) |
| >>> our_copy = fdt.Copy(os.path.join(_base, '../tests/copy.dtb')) |
| >>> our_copy.PutString('/display', 'compatible', 'north') |
| >>> fdt.GetString('/display', 'compatible') |
| 'nvidia,tegra250-display' |
| >>> our_copy.PutString('/display', 'compatible', 'south') |
| >>> our_copy.GetString('/display', 'compatible') |
| 'south' |
| |
| Args: |
| node: Full path to node to look up. |
| prop: Property name to look up. |
| value_int: Integer to write. |
| """ |
| args = ['-t', 'i', self.fname, node, prop, str(value_int)] |
| self.tools.Run('fdtput', args) |
| |
| def Compile(self): |
| """Compile an fdt .dts source file into a .dtb binary blob |
| |
| >>> tools = Tools(cros_output.Output()) |
| >>> tools.PrepareOutputDir(None) |
| >>> src_path = '../tests/dts' |
| >>> src = os.path.join(src_path, 'source.dts') |
| >>> fdt = Fdt(tools, src) |
| |
| >>> fdt.Compile() |
| >>> os.path.exists(os.path.join(tools.outdir, 'source.dtb')) |
| True |
| >>> if os.path.exists('../tests/source.dtb'): |
| ... os.remove('../tests/source.dtb') |
| |
| # Now check that search paths work |
| >>> fdt = Fdt(tools, '../tests/source.dts') |
| >>> fdt.Compile() #doctest:+IGNORE_EXCEPTION_DETAIL |
| Traceback (most recent call last): |
| ... |
| CmdError: Command failed: dtc -I dts -o /tmp/tmpcYO7Fm/source.dtb -O \ |
| dtb -p 4096 ../tests/source.dts |
| DTC: dts->dtb on file "../tests/source.dts" |
| FATAL ERROR: Couldn't open "tegra250.dtsi": No such file or directory |
| <BLANKLINE> |
| >>> tools.search_paths = ['../tests/dts'] |
| >>> #fdt.Compile() |
| """ |
| if not self._is_compiled: |
| root, ext = os.path.splitext(self.fname) |
| |
| # crosbug.com/31621 |
| # This is a temporary hack to support upstream U-Boot |
| # Since it does not have the benefit of the dtc -i option, it uses |
| # the C preprocessor to find its include files. Here we must perform |
| # that task manually for the compiler. Since it is just as easy to |
| # do with the string replace feature, use that. |
| data = self.tools.ReadFile(self.fname) |
| fname = self.fname |
| if 'ARCH_CPU_DTS' in data: |
| fname = os.path.join(self.tools.outdir, os.path.basename(root) + |
| '.dts') |
| data = data.replace('ARCH_CPU_DTS', '"tegra20.dtsi"') |
| self.tools.WriteFile(fname, data) |
| |
| # If we don't have a directory, put it in the tools tempdir |
| out_fname = os.path.join(self.tools.outdir, os.path.basename(root) + |
| '.dtb') |
| search_list = [] |
| for path in self.tools.search_paths: |
| search_list.extend(['-i', path]) |
| args = ['-I', 'dts', '-o', out_fname, '-O', 'dtb', '-p', '4096'] |
| args.extend(search_list) |
| args.append(fname) |
| self.tools.Run('dtc', args) |
| self.fname = out_fname |
| self._is_compiled = True |
| |
| def main(): |
| """Main function for cros_bundle_firmware. |
| |
| This just lists out the children of the root node, along with all their |
| properties. |
| """ |
| parser = optparse.OptionParser() |
| parser.add_option('-d', '--dt', dest='fdt', type='string', action='store', |
| help='Path to fdt file to use (binary ,dtb)', default='u-boot.dtb') |
| |
| (options, args) = parser.parse_args(sys.argv) |
| tools = Tools(cros_output.Output()) |
| fdt = Fdt(tools, options.fdt) |
| children = fdt.GetChildren('/') |
| for child in children: |
| print '%s: %s\n' % (child, fdt.GetProps('/' + child)) |
| |
| |
| def _Test(argv): |
| """Run any built-in tests.""" |
| import doctest |
| doctest.testmod() |
| |
| if __name__ == '__main__': |
| # If first argument is --test, run testing code. |
| if sys.argv[1:2] == ["--test"]: |
| _Test([sys.argv[0]] + sys.argv[2:]) |
| else: |
| main() |