blob: ea02eee83caf78d49e3610fb1a1efa0efaac93dd [file] [log] [blame] [edit]
#!/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 binascii
import doctest
import optparse
import os
import re
import shutil
import struct
import sys
import cros_output
from tools import Tools
_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
_, ext = os.path.splitext(fname)
self._is_compiled = ext == '.dtb'
def ExtractFromImage(self, fname):
"""Extract an FDT from a Chrome OS firmware image (FDTMAP).
Args:
fname: Filename of image
Raises:
ValueError if no fdtmap is found in the image, or its crc32 is incorrect.
"""
data = self.tools.ReadFile(fname)
pos = data.find('__FDTM__')
if pos == -1:
raise ValueError("No fdtmap found in image '%s'" % fname)
size, crc32 = struct.unpack('<LL', data[pos + 8:pos + 16])
fdt_data = data[pos + 16:pos + 16 + size]
check_crc32 = binascii.crc32(fdt_data) & 0xffffffff
if check_crc32 != crc32:
raise ValueError('Invalid CRC for FDTMAP')
self.tools.WriteFile(self.fname, fdt_data)
def GetProp(self, node, prop, default=None, typespec=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.
typespec: Type character to use (None for default, 's' for 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)]
if typespec is not None:
args += ['-t%s' % typespec]
out = self.tools.Run('fdtget', args)
return out.strip()
def GetBool(self, node, prop):
"""Get a boolean property from a device tree.
If the property is present then it is considered True, else False.
>>> tools = Tools(cros_output.Output())
>>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb'))
>>> fdt.GetBool('/flash/ro-gbb', 'hash-target')
False
>>> fdt.GetBool('/flash/ro-onestop', 'hash-target')
True
Args:
node: Full path to node to look up.
prop: Property name to look up.
Returns:
True if property is present, False if not.
"""
value = self.GetProp(node, prop, '<none>')
return value != '<none>'
def PutBool(self, node, prop, bool_value):
"""Write a boolean property to a device tree.
If the property is present then it is considered True, else False. At
present we can't actually support removing the property since fdtput
does not have this option.
>>> tools = Tools(cros_output.Output())
>>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb'))
>>> fdt.GetBool('/putbool-test', 'dead-silence')
False
>>> fdt.PutBool('/putbool-test', 'dead-silence', False)
>>> fdt.GetBool('/putbool-test', 'dead-silence')
False
>>> fdt.PutBool('/putbool-test', 'dead-silence', True)
>>> fdt.GetBool('/putbool-test', 'dead-silence')
True
>>> fdt.PutBool('/putbool-test', 'dead-silence', False)
Traceback (most recent call last):
...
ValueError: ("Cannot set node '%s' property '%s' to False", \
('/putbool-test', 'dead-silence'))
>>> fdt.GetBool('/putbool-test', 'dead-silence')
True
Args:
node: Full path to node to update.
prop: Property name to write.
bool_value: Boolean to write.
Raises:
ValueError: if the property is True and we try to set it to False.
"""
if bool_value:
args = ['-p', self.fname, node, prop]
self.tools.Run('fdtput', args)
elif self.GetBool(node, prop):
raise ValueError("Cannot set node '%s' property '%s' to False",
(node, prop))
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': 'nvidia,seaboard nvidia,tegra250', '#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', '0xff', 1)
[255]
>>> 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, 0) 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.
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:
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.
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:
Value of property, as an integer.
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.
The 's' typespec is used to ask fdtget to convert the property to a
string even it is has embedded \0. This allows it to work with
compatible strings which contain a list.
>>> tools = Tools(cros_output.Output())
>>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb'))
>>> fdt.GetString('/display', 'compatible')
'nvidia,tegra250-display'
>>> fdt.GetString('/', 'compatible')
'nvidia,seaboard nvidia,tegra250'
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.
Returns:
Value of property, as a string.
Raises:
CmdError: if the property does not exist.
"""
return self.GetProp(node, prop, default, typespec='s')
def GetFlashNode(self, section, part):
"""Returns the node path to use for a particular flash section/path.
Args:
section: Section name to look at: ro, rw-a, etc.
part: Partition name to look at: gbb, vpd, etc.
Returns:
Full path to flash node
"""
return '/flash/%s-%s' % (section, part)
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(self.GetFlashNode(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
>>> fdt.GetFlashPartSize('rw', 'b-onestop')
32768
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.
"""
size = self.GetInt(self.GetFlashNode(section, part), 'size', -1)
if size == -1:
size = self.GetFlashPart(section, part)[1]
return size
def GetFlashPartUsed(self, section, part):
"""Returns the used part 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.GetFlashPartUsed('ro', 'onestop')
1234
>>> fdt.GetFlashPartUsed('rw-a', 'onestop')
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.
"""
size = self.GetInt(self.GetFlashNode(section, part), 'used', -1)
if size == -1:
return None
return size
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 -ts
Error at '/go/hotspurs': FDT_ERR_NOTFOUND
<BLANKLINE>
Args:
node: Node to return label property from.
Returns:
The value of the label property from the node.
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 InsertNodes(self, node_list):
"""Insert a list of new nodes into the FDT.
>>> tools = Tools(cros_output.Output())
>>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb'))
>>> arg = [{'path': '/new-node/walrus', 'int': 123}]
>>> arg.append({'path': '/new-node/walrus', 'bool': True, 'str': 'fred'})
>>> arg.append({'path': '/new-node/seal', 'str': 'frankly'})
>>> arg.append({'path': '/new-node/seal', 'list': [1, 2, 3]})
>>> fdt.InsertNodes(arg)
>>> fdt.GetString('/new-node/walrus', 'str')
'fred'
>>> fdt.GetInt('/new-node/walrus', 'int')
123
>>> fdt.GetBool('/new-node/walrus', 'bool')
True
>>> fdt.GetString('/new-node/seal', 'str')
'frankly'
>>> fdt.GetIntList('/new-node/seal', 'list')
[1, 2, 3]
>>> arg = [{'path': '/new-node/dodgy', 'dict': {1: 'fred'}}]
>>> fdt.InsertNodes(arg)
Traceback (most recent call last):
...
ValueError: Unrecognized type for prop 'dict', value '{1: 'fred'}'
Args:
node_list: A list of nodes, each a dictionary containing the properties
of the node. One of the properties must be 'path' which is the full
path to the node. Supported types for property values are string,
boolean, integer and integer list.
Raises:
ValueError: if an unrecognized type is provided.
"""
for node_props in node_list:
path = node_props['path']
for prop, value in node_props.iteritems():
if isinstance(value, int):
self.PutInteger(path, prop, value)
elif isinstance(value, str):
self.PutString(path, prop, value)
elif isinstance(value, bool):
self.PutBool(path, prop, value)
elif isinstance(value, list):
self.PutIntList(path, prop, value)
else:
raise ValueError("Unrecognized type for prop '%s', value '%s'" %
(prop, value))
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 = ['-p', '-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 = ['-p', '-t', 'i', self.fname, node, prop, str(value_int)]
self.tools.Run('fdtput', args)
def PutIntList(self, node, prop, int_list):
"""Write a list of integers into an fdt property.
>>> 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.PutIntList('/flash/shared-dev-cfg', 'victoria', [1, 2, 3])
>>> fdt.GetIntList('/flash/shared-dev-cfg', 'victoria', 3)
[1, 2, 3]
>>> fdt.PutIntList('/flash/shared-dev-cfg', 'victoria', [3])
>>> fdt.GetIntList('/flash/shared-dev-cfg', 'victoria', 1)
[3]
>>> fdt.PutIntList('/flash/shared-dev-cfg', 'victoria', [])
>>> fdt.GetIntList('/flash/shared-dev-cfg', 'victoria', 0)
[]
Args:
node: Full path to node to look up.
prop: Property name to look up.
int_list: List of integers to write.
"""
value_list = [str(s) for s in int_list]
args = ['-p', '-t', 'i', self.fname, node, prop]
args.extend(value_list)
self.tools.Run('fdtput', args)
def PutBytes(self, node, prop, bytes_list):
"""Write a sequence of bytes into an fdt property.
>>> tools = Tools(cros_output.Output())
>>> fdt = Fdt(tools, os.path.join(_base, '../tests/test.dtb'))
>>> out_copy = fdt.Copy(os.path.join(_base, '../tests/copy.dtb'))
>>> out_copy.PutBytes('/', 'foo', (0, 1, 2, 3))
>>> out_copy.GetProp('/', 'foo')
'66051'
>>> out_copy.PutBytes('/', 'foo2', (3, 2, 1, 0, 0, 1, 2, 3))
>>> out_copy.GetProp('/', 'foo2')
'50462976 66051'
>>> out_copy.PutBytes('/', 'foo3', (3, 2, 1))
>>> out_copy.GetProp('/', 'foo3')
'3 2 1'
Args:
node: Full path to the node to look up.
prop: Property name to look up.
bytes_list: List of bytes to write.
"""
value_list = [str(s) for s in bytes_list]
args = ['-p', '-t', 'bi', self.fname, node, prop]
args.extend(value_list)
self.tools.Run('fdtput', args)
def Compile(self, arch_dts):
"""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()
Args:
arch_dts: Architecture/SOC .dtsi include file.
"""
if not self._is_compiled:
root, _ = os.path.splitext(self.fname)
# Upstream U-Boot requires that we invoke the C preprocessor
data = self.tools.ReadFile(self.fname)
fname = self.fname
search_list = []
if 'ARCH_CPU_DTS' in data or '#ifdef' in data:
fname = os.path.join(self.tools.outdir, os.path.basename(root) +
'.dts')
args = ['-E', '-P', '-x', 'assembler-with-cpp', '-D__ASSEMBLY__']
args += ['-Ulinux']
args += ['-DARCH_CPU_DTS="%s"' % arch_dts]
args += ['-DCONFIG_CHROMEOS']
args += ['-o', fname, self.fname]
self.tools.Run('cc', args)
search_list.extend(['-i', os.path.dirname(self.fname)])
# 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')
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, _) = 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():
"""Run any built-in tests."""
assert doctest.testmod().failed == 0
if __name__ == '__main__':
# If first argument is --test, run testing code.
if sys.argv[1:2] == ['--test']:
_Test()
else:
main()