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