1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright 2024 Google LLC 3# Written by Simon Glass <sjg@chromium.org> 4 5"""Entry-type module for producing multiple alternate sections""" 6 7import glob 8import os 9 10from binman.entry import EntryArg 11from binman.etype.section import Entry_section 12from dtoc import fdt_util 13from u_boot_pylib import tools 14 15class Entry_alternates_fdt(Entry_section): 16 """Entry that generates alternative sections for each devicetree provided 17 18 When creating an image designed to boot on multiple models, each model 19 requires its own devicetree. This entry deals with selecting the correct 20 devicetree from a directory containing them. Each one is read in turn, then 21 used to produce section contents which are written to a file. This results 22 in a number of images, one for each model. 23 24 For example this produces images for each .dtb file in the 'dtb' directory:: 25 26 alternates-fdt { 27 fdt-list-dir = "dtb"; 28 filename-pattern = "NAME.bin"; 29 fdt-phase = "tpl"; 30 31 section { 32 u-boot-tpl { 33 }; 34 }; 35 }; 36 37 Each output file is named based on its input file, so an input file of 38 `model1.dtb` results in an output file of `model1.bin` (i.e. the `NAME` in 39 the `filename-pattern` property is replaced with the .dtb basename). 40 41 Note that this entry type still produces contents for the 'main' image, in 42 that case using the normal dtb provided to Binman, e.g. `u-boot-tpl.dtb`. 43 But that image is unlikely to be useful, since it relates to whatever dtb 44 happened to be the default when U-Boot builds 45 (i.e. `CONFIG_DEFAULT_DEVICE_TREE`). However, Binman ensures that the size 46 of each of the alternates is the same as the 'default' one, so they can in 47 principle be 'slotted in' to the appropriate place in the main image. 48 49 The optional `fdt-phase` property indicates the phase to build. In this 50 case, it etype runs fdtgrep to obtain the devicetree subset for that phase, 51 respecting the `bootph-xxx` tags in the devicetree. 52 """ 53 def __init__(self, section, etype, node): 54 super().__init__(section, etype, node) 55 self.fdt_list_dir = None 56 self.filename_pattern = None 57 self.required_props = ['fdt-list-dir'] 58 self._cur_fdt = None 59 self._fdt_phase = None 60 self.fdtgrep = None 61 self._fdt_dir = None 62 self._fdts = None 63 self._fname_pattern = None 64 self._remove_props = None 65 self.alternates = None 66 67 def ReadNode(self): 68 """Read properties from the node""" 69 super().ReadNode() 70 self._fdt_dir = fdt_util.GetString(self._node, 'fdt-list-dir') 71 fname = tools.get_input_filename(self._fdt_dir) 72 fdts = glob.glob('*.dtb', root_dir=fname) 73 self._fdts = [os.path.splitext(f)[0] for f in fdts] 74 75 self._fdt_phase = fdt_util.GetString(self._node, 'fdt-phase') 76 77 # This is used by Image.WriteAlternates() 78 self.alternates = self._fdts 79 80 self._fname_pattern = fdt_util.GetString(self._node, 'filename-pattern') 81 82 self._remove_props = [] 83 props, = self.GetEntryArgsOrProps( 84 [EntryArg('of-spl-remove-props', str)], required=False) 85 if props: 86 self._remove_props = props.split() 87 88 def FdtContents(self, fdt_etype): 89 # If there is no current FDT, just use the normal one 90 if not self._cur_fdt: 91 return self.section.FdtContents(fdt_etype) 92 93 # Find the file to use 94 fname = os.path.join(self._fdt_dir, f'{self._cur_fdt}.dtb') 95 infile = tools.get_input_filename(fname) 96 97 # Run fdtgrep if needed, to remove unwanted nodes and properties 98 if self._fdt_phase: 99 uniq = self.GetUniqueName() 100 outfile = tools.get_output_filename( 101 f'{uniq}.{self._cur_fdt}-{self._fdt_phase}.dtb') 102 self.fdtgrep.create_for_phase(infile, self._fdt_phase, outfile, 103 self._remove_props) 104 return outfile, tools.read_file(outfile) 105 return fname, tools.read_file(infile) 106 107 def ProcessWithFdt(self, alt): 108 """Produce the contents of this entry, using a particular FDT blob 109 110 Args: 111 alt (str): Name of the alternate 112 113 Returns: 114 tuple: 115 str: Filename to use for the alternate's .bin file 116 bytes: Contents of this entry's section, using the selected FDT 117 """ 118 pattern = self._fname_pattern or 'NAME.bin' 119 fname = pattern.replace('NAME', alt) 120 121 data = b'' 122 try: 123 self._cur_fdt = alt 124 self.ProcessContents() 125 data = self.GetPaddedData() 126 finally: 127 self._cur_fdt = None 128 return fname, data 129 130 def AddBintools(self, btools): 131 super().AddBintools(btools) 132 self.fdtgrep = self.AddBintool(btools, 'fdtgrep') 133