#!/usr/bin/python
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from analyze_mcu_flash_config import *

import argparse
import binutils
import sh


def contains(a, b):
    """ True if b is inside a """
    return b[0] >= a[0] and b[1] <= a[1]


def claim(c, unclaimed_regions, symbol):
    """ Removes region (c_start, c_end) from the set of unclaimed_regions
        Return True if the region was sucessfully removed, False if it was
        already claimed.

    """
    if c[0] == c[1]:
        raise Exception("Invalid region: 0 size! %s" % c)

    for u in unclaimed_regions:
        if contains(u, c):
            unclaimed_regions.remove(u)

            # Defensive programming:
            if c[0] < u[0]:
                raise Exception("WTF! %s %s" % (u, c))
            if c[1] > u[1]:
                raise Exception("WTF! %s %s" % (u, c))

            if u[0] != c[0]:
                # Lower edge of the claimed region does not overlap with
                # the unclaimed region. Add a piece of unclaimed padding:
                unclaimed_regions.add((u[0], c[0]))
            if u[1] != c[1]:
                # Upper edge of the claimed region does not overlap with
                # the unclaimed region. Add a piece of unclaimed padding:
                unclaimed_regions.add((c[1], u[1]))
            return True

    print "Warning: doubly claimed %s, 0x%08x - 0x%08x?" % (symbol, c[0], c[1])
    return False


if (__name__ == '__main__'):
    parser = argparse.ArgumentParser()
    parser.add_argument('--verbose', action='store_true')
    parser.add_argument('--dump', action='store_true',
                        help='objdump unclaimed regions')
    parser.add_argument('--fast', action='store_true')
    parser.add_argument(
        '--config', default='tintin', choices=CONFIG_CLASSES.keys())
    parser.add_argument('elf_file', nargs='?')
    args = parser.parse_args()

    config_class = CONFIG_CLASSES[args.config]
    config = config_class()

    elf_file = args.elf_file
    if not elf_file:
        elf_file = config.default_elf_abs_path()

    # The set of (addr_start, addr_end) tuples that we use to keep track of
    # unclaimed space in the flash:
    unclaimed_regions = set([config.memory_region_to_analyze()])

    # Using arm-none-eabi-nm, 'claim' all .text symbols by removing the regions
    # from the unclaimed_regions set
    symbols = binutils.nm_generator(elf_file, args.fast)
    bytes_claimed = 0
    for addr, section, symbol, src_path, line, size in symbols:
        if section != 't':
            continue
        c = (addr, addr + size)
        if not contains(config.memory_region_to_analyze(), c):
            raise Exception("Not in memory region: %s 0x%08x - 0x%08x" %
                            (symbol, c[0], c[1]))
        claim(c, unclaimed_regions, symbol)
        bytes_claimed += size

    # Using the resulting map of unused space,
    # calculate the total unclaimed space:
    bytes_unclaimed = 0
    for u in unclaimed_regions:
        bytes_unclaimed += u[1] - u[0]

    # Print out the results
    text_size = binutils.size(elf_file)[0]
    region = config.memory_region_to_analyze()
    print "------------------------------------------------------------"
    print ".text:                            %u" % text_size
    print "unclaimed memory:                 %u" % bytes_unclaimed
    print "claimed memory:                   %u" % bytes_claimed
    print "unknown .text regions             %u" % (text_size - bytes_claimed)
    print ""
    print "These should add up:"
    print "bytes_unclaimed + bytes_claimed = %u" % (bytes_unclaimed +
                                                    bytes_claimed)
    print "REGION_END - REGION_START =       %u" % (region[1] - region[0])
    print ""

    num = 30
    print "------------------------------------------------------------"
    print "Top %u unclaimed memory regions:" % num

    def comparator(a, b):
        return cmp(a[1] - a[0], b[1] - b[0])
    unclaimed_sorted_by_size = sorted(unclaimed_regions,
                                      cmp=comparator, reverse=True)
    for x in xrange(0, num):
        region = unclaimed_sorted_by_size[x]
        size = region[1] - region[0]
        if args.dump:
            print "-----------------------------------------------------------"
            print "%u bytes @ 0x%08x" % (size, region[0])
            print ""
            print sh.arm_none_eabi_objdump('-S',
                                           '--start-address=0x%x' % region[0],
                                           '--stop-address=0x%x' % region[1],
                                           elf_file)
        else:
            print "%u bytes @ 0x%08x" % (size, region[0])

    print "------------------------------------------------------------"
    print "Unclaimed regions are regions that did map to symbols in the .elf."