# SPDX-License-Identifier: GPL-2.0+
# Copyright (c) 2016 Google, Inc
# Written by Simon Glass <sjg@chromium.org>
#
# Entry-type module for a U-Boot binary with an embedded microcode pointer
#

import struct

from binman import elf
from binman.entry import Entry
from binman.etype.blob import Entry_blob
from dtoc import fdt_util
from u_boot_pylib import tools
from u_boot_pylib import command

class Entry_u_boot_with_ucode_ptr(Entry_blob):
    """U-Boot with embedded microcode pointer

    Properties / Entry arguments:
        - filename: Filename of u-boot-nodtb.bin (default 'u-boot-nodtb.bin')
        - optional-ucode: boolean property to make microcode optional. If the
            u-boot.bin image does not include microcode, no error will
            be generated.

    See Entry_u_boot_ucode for full details of the three entries involved in
    this process. This entry updates U-Boot with the offset and size of the
    microcode, to allow early x86 boot code to find it without doing anything
    complicated. Otherwise it is the same as the u-boot entry.
    """
    def __init__(self, section, etype, node, auto_write_symbols=False):
        super().__init__(section, etype, node, auto_write_symbols)
        self.elf_fname = 'u-boot'
        self.target_offset = None

    def GetDefaultFilename(self):
        return 'u-boot-nodtb.bin'

    def ProcessFdt(self, fdt):
        # Figure out where to put the microcode pointer
        fname = tools.get_input_filename(self.elf_fname)
        sym = elf.GetSymbolAddress(fname, '_dt_ucode_base_size')
        if sym:
           self.target_offset = sym
        elif not fdt_util.GetBool(self._node, 'optional-ucode'):
            self.Raise('Cannot locate _dt_ucode_base_size symbol in u-boot')
        return True

    def ProcessContents(self):
        # If the image does not need microcode, there is nothing to do
        if not self.target_offset:
            return True

        # Get the offset of the microcode
        ucode_entry = self.section.FindEntryType('u-boot-ucode')
        if not ucode_entry:
            self.Raise('Cannot find microcode region u-boot-ucode')

        # Check the target pos is in the section. If it is not, then U-Boot is
        # being linked incorrectly, or is being placed at the wrong offset
        # in the section.
        #
        # The section must be set up so that U-Boot is placed at the
        # flash address to which it is linked. For example, if
        # CONFIG_TEXT_BASE is 0xfff00000, and the ROM is 8MB, then
        # the U-Boot region must start at offset 7MB in the section. In this
        # case the ROM starts at 0xff800000, so the offset of the first
        # entry in the section corresponds to that.
        if (self.target_offset < self.image_pos or
                self.target_offset >= self.image_pos + self.size):
            self.Raise('Microcode pointer _dt_ucode_base_size at %08x is outside the section ranging from %08x to %08x' %
                (self.target_offset, self.image_pos,
                 self.image_pos + self.size))

        # Get the microcode, either from u-boot-ucode or u-boot-dtb-with-ucode.
        # If we have left the microcode in the device tree, then it will be
        # in the latter. If we extracted the microcode from the device tree
        # and collated it in one place, it will be in the former.
        if ucode_entry.size:
            offset, size = ucode_entry.offset, ucode_entry.size
        else:
            dtb_entry = self.section.FindEntryType('u-boot-dtb-with-ucode')
            if not dtb_entry:
                dtb_entry = self.section.FindEntryType(
                        'u-boot-tpl-dtb-with-ucode')
            if not dtb_entry:
                self.Raise('Cannot find microcode region u-boot-dtb-with-ucode')
            offset = dtb_entry.offset + dtb_entry.ucode_offset
            size = dtb_entry.ucode_size

        # Write the microcode offset and size into the entry
        offset_and_size = struct.pack('<2L', offset, size)
        self.target_offset -= self.image_pos
        return self.ProcessContentsUpdate(self.data[:self.target_offset] +
                                          offset_and_size +
                                          self.data[self.target_offset + 8:])
