binman: Add a ProcessFdt() method

Some entry types modify the device tree, e.g. to remove microcode or add a
property. So far this just modifies their local copy and does not affect
a 'shared' device tree.

Rather than doing this modification in the ObtainContents() method, and a
new ProcessFdt() method which is specifically designed to modify this
shared device tree.

Move the existing device-tree code over to use this method, reducing
ObtainContents() to the goal of just obtaining the contents without any
processing, even for device tree.

Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass 2018-07-06 10:27:40 -06:00
parent 0a4357c4c2
commit ecab89737a
9 changed files with 117 additions and 37 deletions

View file

@ -462,7 +462,14 @@ Order of image creation
Image creation proceeds in the following order, for each entry in the image. Image creation proceeds in the following order, for each entry in the image.
1. GetEntryContents() - the contents of each entry are obtained, normally by 1. ProcessFdt() - process the device tree information as required by the
particular entry. This may involve adding or deleting properties. If the
processing is complete, this method should return True. If the processing
cannot complete because it needs the ProcessFdt() method of another entry to
run first, this method should return False, in which case it will be called
again later.
2. GetEntryContents() - the contents of each entry are obtained, normally by
reading from a file. This calls the Entry.ObtainContents() to read the reading from a file. This calls the Entry.ObtainContents() to read the
contents. The default version of Entry.ObtainContents() calls contents. The default version of Entry.ObtainContents() calls
Entry.GetDefaultFilename() and then reads that file. So a common mechanism Entry.GetDefaultFilename() and then reads that file. So a common mechanism
@ -471,35 +478,35 @@ functions must return True when they have read the contents. Binman will
retry calling the functions a few times if False is returned, allowing retry calling the functions a few times if False is returned, allowing
dependencies between the contents of different entries. dependencies between the contents of different entries.
2. GetEntryPositions() - calls Entry.GetPositions() for each entry. This can 3. GetEntryPositions() - calls Entry.GetPositions() for each entry. This can
return a dict containing entries that need updating. The key should be the return a dict containing entries that need updating. The key should be the
entry name and the value is a tuple (pos, size). This allows an entry to entry name and the value is a tuple (pos, size). This allows an entry to
provide the position and size for other entries. The default implementation provide the position and size for other entries. The default implementation
of GetEntryPositions() returns {}. of GetEntryPositions() returns {}.
3. PackEntries() - calls Entry.Pack() which figures out the position and 4. PackEntries() - calls Entry.Pack() which figures out the position and
size of an entry. The 'current' image position is passed in, and the function size of an entry. The 'current' image position is passed in, and the function
returns the position immediately after the entry being packed. The default returns the position immediately after the entry being packed. The default
implementation of Pack() is usually sufficient. implementation of Pack() is usually sufficient.
4. CheckSize() - checks that the contents of all the entries fits within 5. CheckSize() - checks that the contents of all the entries fits within
the image size. If the image does not have a defined size, the size is set the image size. If the image does not have a defined size, the size is set
large enough to hold all the entries. large enough to hold all the entries.
5. CheckEntries() - checks that the entries do not overlap, nor extend 6. CheckEntries() - checks that the entries do not overlap, nor extend
outside the image. outside the image.
6. ProcessEntryContents() - this calls Entry.ProcessContents() on each entry. 7. ProcessEntryContents() - this calls Entry.ProcessContents() on each entry.
The default implementatoin does nothing. This can be overriden to adjust the The default implementatoin does nothing. This can be overriden to adjust the
contents of an entry in some way. For example, it would be possible to create contents of an entry in some way. For example, it would be possible to create
an entry containing a hash of the contents of some other entries. At this an entry containing a hash of the contents of some other entries. At this
stage the position and size of entries should not be adjusted. stage the position and size of entries should not be adjusted.
7. WriteSymbols() - write the value of symbols into the U-Boot SPL binary. 8. WriteSymbols() - write the value of symbols into the U-Boot SPL binary.
See 'Access to binman entry positions at run time' below for a description of See 'Access to binman entry positions at run time' below for a description of
what happens in this stage. what happens in this stage.
8. BuildImage() - builds the image and writes it to a file. This is the final 9. BuildImage() - builds the image and writes it to a file. This is the final
step. step.

View file

@ -90,6 +90,21 @@ class Section(object):
entry.SetPrefix(self._name_prefix) entry.SetPrefix(self._name_prefix)
self._entries[node.name] = entry self._entries[node.name] = entry
def ProcessFdt(self, fdt):
todo = self._entries.values()
for passnum in range(3):
next_todo = []
for entry in todo:
if not entry.ProcessFdt(fdt):
next_todo.append(entry)
todo = next_todo
if not todo:
break
if todo:
self._Raise('Internal error: Could not complete processing of Fdt: '
'remaining %s' % todo)
return True
def CheckSize(self): def CheckSize(self):
"""Check that the section contents does not exceed its size, etc.""" """Check that the section contents does not exceed its size, etc."""
contents_size = 0 contents_size = 0

View file

@ -21,6 +21,11 @@ import tout
# Make this global so that it can be referenced from tests # Make this global so that it can be referenced from tests
images = OrderedDict() images = OrderedDict()
# Records the device-tree files known to binman, keyed by filename (e.g.
# 'u-boot-spl.dtb')
fdt_files = {}
def _ReadImageDesc(binman_node): def _ReadImageDesc(binman_node):
"""Read the image descriptions from the /binman node """Read the image descriptions from the /binman node
@ -53,6 +58,24 @@ def _FindBinmanNode(dtb):
return node return node
return None return None
def GetFdt(fname):
"""Get the Fdt object for a particular device-tree filename
Binman keeps track of at least one device-tree file called u-boot.dtb but
can also have others (e.g. for SPL). This function looks up the given
filename and returns the associated Fdt object.
Args:
fname: Filename to look up (e.g. 'u-boot.dtb').
Returns:
Fdt object associated with the filename
"""
return fdt_files[fname]
def GetFdtPath(fname):
return fdt_files[fname]._fname
def Binman(options, args): def Binman(options, args):
"""The main control code for binman """The main control code for binman
@ -93,12 +116,39 @@ def Binman(options, args):
try: try:
tools.SetInputDirs(options.indir) tools.SetInputDirs(options.indir)
tools.PrepareOutputDir(options.outdir, options.preserve) tools.PrepareOutputDir(options.outdir, options.preserve)
dtb = fdt.FdtScan(dtb_fname)
# Get the device tree ready by compiling it and copying the compiled
# output into a file in our output directly. Then scan it for use
# in binman.
dtb_fname = fdt_util.EnsureCompiled(dtb_fname)
fname = tools.GetOutputFilename('u-boot-out.dtb')
with open(dtb_fname) as infd:
with open(fname, 'wb') as outfd:
outfd.write(infd.read())
dtb = fdt.FdtScan(fname)
# Note the file so that GetFdt() can find it
fdt_files['u-boot.dtb'] = dtb
node = _FindBinmanNode(dtb) node = _FindBinmanNode(dtb)
if not node: if not node:
raise ValueError("Device tree '%s' does not have a 'binman' " raise ValueError("Device tree '%s' does not have a 'binman' "
"node" % dtb_fname) "node" % dtb_fname)
images = _ReadImageDesc(node) images = _ReadImageDesc(node)
# Prepare the device tree by making sure that any missing
# properties are added (e.g. 'pos' and 'size'). The values of these
# may not be correct yet, but we add placeholders so that the
# size of the device tree is correct. Later, in
# SetCalculatedProperties() we will insert the correct values
# without changing the device-tree size, thus ensuring that our
# entry positions remain the same.
for image in images.values():
image.ProcessFdt(dtb)
dtb.Pack()
dtb.Flush()
for image in images.values(): for image in images.values():
# Perform all steps for this image, including checking and # Perform all steps for this image, including checking and
# writing it. This means that errors found with a later # writing it. This means that errors found with a later

View file

@ -130,6 +130,9 @@ class Entry(object):
self.align_end = fdt_util.GetInt(self._node, 'align-end') self.align_end = fdt_util.GetInt(self._node, 'align-end')
self.pos_unset = fdt_util.GetBool(self._node, 'pos-unset') self.pos_unset = fdt_util.GetBool(self._node, 'pos-unset')
def ProcessFdt(self, fdt):
return True
def SetPrefix(self, prefix): def SetPrefix(self, prefix):
"""Set the name prefix for a node """Set the name prefix for a node

View file

@ -17,6 +17,9 @@ class Entry_section(Entry):
Entry.__init__(self, image, etype, node) Entry.__init__(self, image, etype, node)
self._section = bsection.Section(node.name, node) self._section = bsection.Section(node.name, node)
def ProcessFdt(self, fdt):
return self._section.ProcessFdt(fdt)
def ObtainContents(self): def ObtainContents(self):
return self._section.GetEntryContents() return self._section.GetEntryContents()

View file

@ -5,6 +5,7 @@
# Entry-type module for U-Boot device tree with the microcode removed # Entry-type module for U-Boot device tree with the microcode removed
# #
import control
import fdt import fdt
from entry import Entry from entry import Entry
from blob import Entry_blob from blob import Entry_blob
@ -22,13 +23,13 @@ class Entry_u_boot_dtb_with_ucode(Entry_blob):
self.collate = False self.collate = False
self.ucode_offset = None self.ucode_offset = None
self.ucode_size = None self.ucode_size = None
self.ucode = None
self.ready = False
def GetDefaultFilename(self): def GetDefaultFilename(self):
return 'u-boot.dtb' return 'u-boot.dtb'
def ObtainContents(self): def ProcessFdt(self, fdt):
Entry_blob.ObtainContents(self)
# If the section does not need microcode, there is nothing to do # If the section does not need microcode, there is nothing to do
ucode_dest_entry = self.section.FindEntryType( ucode_dest_entry = self.section.FindEntryType(
'u-boot-spl-with-ucode-ptr') 'u-boot-spl-with-ucode-ptr')
@ -38,36 +39,35 @@ class Entry_u_boot_dtb_with_ucode(Entry_blob):
if not ucode_dest_entry or not ucode_dest_entry.target_pos: if not ucode_dest_entry or not ucode_dest_entry.target_pos:
return True return True
# Create a new file to hold the copied device tree
dtb_name = 'u-boot-dtb-with-ucode.dtb'
fname = tools.GetOutputFilename(dtb_name)
with open(fname, 'wb') as fd:
fd.write(self.data)
# Remove the microcode # Remove the microcode
dtb = fdt.FdtScan(fname) fname = self.GetDefaultFilename()
ucode = dtb.GetNode('/microcode') fdt = control.GetFdt(fname)
if not ucode: self.ucode = fdt.GetNode('/microcode')
if not self.ucode:
raise self.Raise("No /microcode node found in '%s'" % fname) raise self.Raise("No /microcode node found in '%s'" % fname)
# There's no need to collate it (move all microcode into one place) # There's no need to collate it (move all microcode into one place)
# if we only have one chunk of microcode. # if we only have one chunk of microcode.
self.collate = len(ucode.subnodes) > 1 self.collate = len(self.ucode.subnodes) > 1
for node in ucode.subnodes: for node in self.ucode.subnodes:
data_prop = node.props.get('data') data_prop = node.props.get('data')
if data_prop: if data_prop:
self.ucode_data += ''.join(data_prop.bytes) self.ucode_data += ''.join(data_prop.bytes)
if self.collate: if self.collate:
prop = node.DeleteProp('data') node.DeleteProp('data')
else: return True
def ObtainContents(self):
# Call the base class just in case it does something important.
Entry_blob.ObtainContents(self)
self._pathname = control.GetFdtPath(self._filename)
self.ReadContents()
if self.ucode:
for node in self.ucode.subnodes:
data_prop = node.props.get('data')
if data_prop and not self.collate:
# Find the offset in the device tree of the ucode data # Find the offset in the device tree of the ucode data
self.ucode_offset = data_prop.GetOffset() + 12 self.ucode_offset = data_prop.GetOffset() + 12
self.ucode_size = len(data_prop.bytes) self.ucode_size = len(data_prop.bytes)
if self.collate: self.ready = True
dtb.Pack()
dtb.Flush()
# Make this file the contents of this entry
self._pathname = fname
self.ReadContents()
return True return True

View file

@ -71,7 +71,7 @@ class Entry_u_boot_ucode(Entry_blob):
fdt_entry = self.section.FindEntryType('u-boot-dtb-with-ucode') fdt_entry = self.section.FindEntryType('u-boot-dtb-with-ucode')
if not fdt_entry: if not fdt_entry:
return True return True
if not fdt_entry.ucode_data: if not fdt_entry.ready:
return False return False
if not fdt_entry.collate: if not fdt_entry.collate:

View file

@ -28,7 +28,7 @@ class Entry_u_boot_with_ucode_ptr(Entry_blob):
def GetDefaultFilename(self): def GetDefaultFilename(self):
return 'u-boot-nodtb.bin' return 'u-boot-nodtb.bin'
def ObtainContents(self): def ProcessFdt(self, fdt):
# Figure out where to put the microcode pointer # Figure out where to put the microcode pointer
fname = tools.GetInputFilename(self.elf_fname) fname = tools.GetInputFilename(self.elf_fname)
sym = elf.GetSymbolAddress(fname, '_dt_ucode_base_size') sym = elf.GetSymbolAddress(fname, '_dt_ucode_base_size')
@ -36,8 +36,7 @@ class Entry_u_boot_with_ucode_ptr(Entry_blob):
self.target_pos = sym self.target_pos = sym
elif not fdt_util.GetBool(self._node, 'optional-ucode'): elif not fdt_util.GetBool(self._node, 'optional-ucode'):
self.Raise('Cannot locate _dt_ucode_base_size symbol in u-boot') self.Raise('Cannot locate _dt_ucode_base_size symbol in u-boot')
return True
return Entry_blob.ObtainContents(self)
def ProcessContents(self): def ProcessContents(self):
# If the image does not need microcode, there is nothing to do # If the image does not need microcode, there is nothing to do
@ -73,7 +72,7 @@ class Entry_u_boot_with_ucode_ptr(Entry_blob):
pos, size = ucode_entry.pos, ucode_entry.size pos, size = ucode_entry.pos, ucode_entry.size
else: else:
dtb_entry = self.section.FindEntryType('u-boot-dtb-with-ucode') dtb_entry = self.section.FindEntryType('u-boot-dtb-with-ucode')
if not dtb_entry: if not dtb_entry or not dtb_entry.ready:
self.Raise('Cannot find microcode region u-boot-dtb-with-ucode') self.Raise('Cannot find microcode region u-boot-dtb-with-ucode')
pos = dtb_entry.pos + dtb_entry.ucode_offset pos = dtb_entry.pos + dtb_entry.ucode_offset
size = dtb_entry.ucode_size size = dtb_entry.ucode_size

View file

@ -54,6 +54,9 @@ class Image:
self._filename = filename self._filename = filename
self._section = bsection.Section('main-section', self._node) self._section = bsection.Section('main-section', self._node)
def ProcessFdt(self, fdt):
return self._section.ProcessFdt(fdt)
def GetEntryContents(self): def GetEntryContents(self):
"""Call ObtainContents() for the section """Call ObtainContents() for the section
""" """