# -*- coding: utf-8 -*-
"""
 Copyright 2015-2016 Freescale Semiconductor, Inc.
 Copyright 2017-2019, 2024-2025 NXP

 NXP Confidential and Proprietary. This software is owned or controlled by NXP and may only
 be used strictly in accordance with the applicable license terms. By
 expressly accepting such terms or by downloading, installing, activating
 and/or otherwise using the software, you are agreeing that you have read,
 and that you agree to comply with and are bound by, such license terms. If
 you do not agree to be bound by the applicable license terms, then you may
 not retain, install, activate or otherwise use the software.
"""
import re
import argparse
import binascii
import sys
import os
import inspect
import gdb  # @UnresolvedImport  # pylint: disable=F0401
import json

CURRENT_DIR = "".join([os.path.dirname(os.path.realpath(__file__)), "/"])
sys.path.insert(0, CURRENT_DIR)
sys.path.insert(0, CURRENT_DIR + "/../../utils")

import mem_reg_utils as utils

CMD_S32DBG_MMU_COMMAND = "mmu"

SCTLR_M_BITFIELD_MASK = 0x1
MMU_DESCRIPTOR_LEVEL_3_VALID_BITFIELD_MASK = 0x3
MMU_DESCRIPTOR_LEVEL_3_VALID_VALUE = 0x3
MMU_DESCRIPTOR_VALID_BITFIELD_MASK = 0x1
MMU_DESCRIPTOR_VALID_VALUE = 0x1
MMU_DESCRIPTOR_TYPE_BITFIELD_MASK = 0x3
MMU_DESCRIPTOR_TABLE_ENTRY_TYPE = 0x3
MMU_DESCRIPTOR_BLOCK_ENTRY_TYPE = 0x1
SIZE_1GB = 0x40000000
SIZE_2MB = 0x200000
SIZE_4KB = 0x1000
SIZE_32MB = 0x2000000
SIZE_16KB = 0x4000
SIZE_512MB = 0x20000000
SIZE_64KB = 0x10000
BIT_ACTIVE = 0x1
DESCRIPTOR_TO_AP_1_MAP_RSH = 6
DESCRIPTOR_TO_AP_2_MAP_RSH = 7
ATTRIBUTE_7_0 = 0xFF
ATTRIBUTE_7_4 = 0xF0
ATTRIBUTE_3_0 = 0xF
NON_SECURE_BITFIELD_MASK = 0x1 << 5
NON_GLOBAL_BITFIELD_MASK = 0x1 << 11
LOWER_ATTRIBUTES_RSH = 2
LOWER_ATTRIBUTES_BITFIELD_MASK = 0x7
SCR_EL3_NON_SECURE_BITFIELD_MASK = 0x1
HCR_EL2_VIRTUALIZATION_BITFIELD_MASK = 0x1
TCR_TRANSLATION_TABLE_WALK_DISABLED_FOR_TTBR_0_BITFIELD_MASK = 0x1 << 7
TCR_TRANSLATION_TABLE_WALK_DISABLED_FOR_TTBR_1_BITFIELD_MASK = 0x1 << 23
TCR_TG0_BITFIELD_MASK = 0xC000
TCR_TG0_RSH = 14
TCR_T0SZ_BITFIELD_MASK = 0x3F
TCR_TG1_BITFIELD_MASK = 0xC0000000
TCR_TG1_RSH = 30
TCR_T1SZ_BITFIELD_MASK = 0x3F0000
TCR_T1SZ_RSH = 16
TTBR_BADDR_BITFIELD_MASK = 0xFFFFFFFFFFFF


class S32DBGProgressBar(object):
    """
    Progress bar.
    """

    def __init__(self):
        """
        Constructor.
        """

        # Progress bar must disable the paging option, so it must save the
        # current state of that option.
        self.current_paging = False

        # Enable the progress bar only when in interactive mode.
        self.enabled = False

    def start_progress_bar(self):
        """
        Initialize the progress bar.
        """
        interactive_mode = gdb.execute("show interactive-mode", False, True)
        result = re.search(
            r"interactive mode is auto \(currently (.*)\)", interactive_mode
        )
        if result is None:
            result = re.search(r"interactive mode is (.*)\.", interactive_mode)

        if (result is not None) and (result.group(1) == "on"):
            self.enabled = True

        if self.enabled:
            pagination = gdb.execute("show pagination", False, True)
            result = re.search(r"pagination is (.*)\.", pagination)
            if (result is not None) and (result.group(1) == "on"):
                self.current_paging = True

            gdb.execute("set pagination off")

    def end_progress_bar(self):
        """
        Ends progress and restores pagination.
        """
        if self.enabled:
            if self.current_paging:
                gdb.execute("set pagination on")


class S32DBGMmuEntry(object):
    """
    MMU Entry.
    """

    def __init__(self):
        """
        Constructor.
        """

        # Virtual start address.
        self.virtual_address = 0

        # Size covered by the entry.
        self.size = 0

    def pprint(self, matching_address=None):
        """
        Prints translation data in a user friendly format. Must be implemented in derived classes
        """
        raise NotImplementedError(
            "Cannot call method {0} of abstract class {1} instance".format(
                inspect.currentframe().f_code.co_name, self.__class__.__name__
            )
        )

    def matches_address(self, address):
        """
        Predicate to determine if the provided address matches the current entry
        """
        return self.virtual_address <= address < self.virtual_address + self.size


class Armv8MmuEntry(S32DBGMmuEntry):
    """
    ARMv8 MMU Entry.
    """

    def __init__(self):
        """
        Constructor.
        """

        super(Armv8MmuEntry, self).__init__()

        self.descriptor = 0

        # Physical start address.
        self.physical_address = 0

        # Non-secure bit (False - secure; True - non-secure)
        self.non_secure = False

        # Global bit (True - not global; False - global)
        self.non_global = False

        # Decoded memory attributes
        self.mem_attr = ""

        # Data access permissions
        self.access_permissions = ""

        # Shareable
        self.shareable = False

        # Outer shareable
        self.outer_shareable = False

    # Pretty print function.
    def pprint(self, matching_address=None):
        """
        Prints translation data in a user friendly format.
        """
        res = ""
        if matching_address is not None:
            physical_addr = self.physical_address + (
                matching_address - self.virtual_address
            )
            res += "P: %s" % hex(physical_addr).rstrip("L") + "\n"
            res += "MMU descriptor: %s" % hex(self.descriptor)

        res += (
            "V: "
            + hex(self.virtual_address).rstrip("L")
            + ", "
            + "P: "
            + hex(self.physical_address).rstrip("L")
            + ", "
            + "Size: "
            + hex(self.size).rstrip("L")
            + ", "
            + ("Non-secure" if self.non_secure else "Secure")
            + ", "
            + ("Non-global" if self.non_global else "Global")
            + ", "
            + "Permissions: "
            + self.access_permissions
            + ", "
            + self.mem_attr
        )
        return res

    def miprint(self, matching_address=None):
        """
        Create translation data in a JSON format.
        """
        result = {}
        if matching_address is not None:
            physical_addr = self.physical_address + (
                matching_address - self.virtual_address
            )
            result["physical_addr"] = hex(physical_addr).rstrip("L")
            result["mmu_descriptor"] = hex(self.descriptor)
        result["virtual_start"] = hex(self.virtual_address).rstrip("L")
        result["physical_start"] = hex(self.physical_address).rstrip("L")
        result["size"] = hex(self.size).rstrip("L")
        result["secure"] = "Non-secure" if self.non_secure else "Secure"
        result["global"] = "Non-global" if self.non_global else "Global"
        result["access_permissions"] = self.access_permissions
        result["mem_attr"] = self.mem_attr
        return result


class S32DBGMmu(object):
    """
    MMU implementation.
    """

    # SoC types.
    SOC_TYPE_HARDWARE = "hardware"
    SOC_TYPE_EMULATOR = "emulator"
    SOC_TYPE_SIMULATOR = "simulator"

    def __init__(self):
        """
        Constructor.
        """
        # Address to be translated.
        self.translate_address = None

        # Endianess
        self.little_endian = True

        # Progress bar
        self.progress = S32DBGProgressBar()

        # Cached translations
        self.mmu_translations = list()

    @staticmethod
    def get_soc_type():
        """Check if we are connected to simulator or to hardware."""

        # Get current context.
        ctx_str = checkError(utils.gdb_exec("mon ctx get current")).strip()
        ctx_parts = ctx_str.split(":")

        if len(ctx_parts) < 3:
            raise gdb.GdbError("ERROR: invalid context %s" % ctx_str)

        soc_name_type = ctx_parts[2].split("-")

        soc_type = S32DBGMmu.SOC_TYPE_HARDWARE

        if len(soc_name_type) == 2:
            # Parse the SoC type.
            if soc_name_type[1] == "sim":
                soc_type = S32DBGMmu.SOC_TYPE_SIMULATOR
            elif soc_name_type[1] == "emu":
                soc_type = S32DBGMmu.SOC_TYPE_EMULATOR

        return soc_type

    @staticmethod
    def get_architecture():
        """Get the architecture of the current context"""

        # Get current context.
        ctx_str = checkError(utils.gdb_exec("mon ctx get current")).strip()
        ctx_parts = ctx_str.split(":")

        if len(ctx_parts) < 3:
            raise gdb.GdbError("ERROR: invalid context %s" % ctx_str)
        elif len(ctx_parts) == 3:
            cores = checkError(
                utils.gdb_exec("mon ctx find . | has prop type = core")
            ).strip()
            ctx_str = "{0}:{1}".format(ctx_str, cores.split()[0].strip())

        return checkError(
            utils.gdb_exec("mon ctx id {0} get prop architecture".format(ctx_str))
        ).strip()

    def set_translate_address(self, translate_address):
        """Set address to be translated"""
        self.translate_address = translate_address

    def initialize_endianess(self):
        """Initialize endianess"""
        string = checkError(utils.gdb_exec("show endian")).strip()
        if "big endian" in string:
            self.little_endian = False

    def read_reg(self, register_name):
        """Read register by name."""
        raw_output = checkError(utils.gdb_exec("reg_read %s" % (register_name))).strip()

        output_parts = []
        # Trim '[S32Debugger]' from the start of the string if any.
        if raw_output.startswith("[S32Debugger]"):
            output_parts = raw_output.split()

        # Trim '\n' from the end of the string if any.
        value_str = output_parts[1].strip()

        # Trim '0x' from the start of the string if any.
        if value_str.startswith("0x"):
            ret_value = int(value_str, 16)
        else:
            ret_value = int(value_str)
        return ret_value

    def read_mem(self, address, access_size, count, space="physical_noncoherent"):
        """Read memory using the given parameters"""
        if not isinstance(address, str):
            address = hex(address)
        if address[-1] == "L":
            address = address[:-1]

        result = checkError(
            utils.gdb_exec(
                "mem_read %s %d %s %d" % (address, access_size, space, count)
            )
        ).strip()
        if result.startswith("[S32Debugger]"):
            result = result[len("[S32Debugger]") :]
        return bytearray.fromhex(str(result))

    def pprint(self):
        """
        Print results.
        """
        res = ""
        if self.translate_address is None:
            for entry in self.mmu_translations:
                res += entry.pprint() + "\n"
        else:
            matching_translations = [
                e
                for e in self.mmu_translations
                if e.matches_address(self.translate_address)
            ]
            if not matching_translations:
                raise gdb.error(self.msg_not_matching())

            for e in matching_translations:
                res += e.pprint(self.translate_address) + "\n"
        return res

    def miprint(self):
        """
        Format JSON results.
        """
        result = []
        if self.translate_address is None:
            for entry in self.mmu_translations:
                result.append(entry.miprint())

        else:
            matching_translations = [
                e
                for e in self.mmu_translations
                if e.matches_address(self.translate_address)
            ]
            if not matching_translations:
                raise gdb.error(self.msg_not_matching())
            for e in matching_translations:
                result.append(e.miprint(self.translate_address))
        return result

    def msg_not_matching(self):
        """
        Return the message to be used when np matching entry is found
        """
        return "No valid translation found for the specified virtual address."

    def read_mmu(self):
        """
        The effective MMU read logic. It must be overridden in derived classes.
        """
        raise NotImplementedError(
            "Cannot call method {0} of abstract class {1} instance".format(
                inspect.currentframe().f_code.co_name, self.__class__.__name__
            )
        )


class Armv8Mmu(S32DBGMmu):
    """
    MMU implementation.
    """

    # Granule sizes.
    GRANULE_SIZE_4K = 4
    GRANULE_SIZE_16K = 16
    GRANULE_SIZE_64K = 64

    # Used registers
    REGISTER_SCTLR_EL1 = "0x0001C080"
    REGISTER_SCTLR_EL2 = "0x0001E080"
    REGISTER_SCTLR_EL3 = "0x0001F080"

    REGISTER_MAIR_EL1 = "0x0001C510"
    REGISTER_MAIR_EL2 = "0x0001E510"
    REGISTER_MAIR_EL3 = "0x0001F510"

    REGISTER_TCR_EL1 = "0x0001C102"
    REGISTER_TCR_EL2 = "0x0001E102"
    REGISTER_TCR_EL3 = "0x0001F102"

    REGISTER_TTBR0_EL1 = "0x0001C100"
    REGISTER_TTBR0_EL2 = "0x0001E100"
    REGISTER_TTBR0_EL3 = "0x0001F100"

    REGISTER_TTBR1_EL1 = "0x0001C101"

    REGISTER_CURRENTEL = "0x0001C212"

    """
    {
        "el": {
            "register_name": register_address,
            ...
        },
        ...
    }
    """
    REGISTERS = {
        "1": {
            "SCTLR": REGISTER_SCTLR_EL1,
            "MAIR": REGISTER_MAIR_EL1,
            "TCR": REGISTER_TCR_EL1,
            "TTBR0": REGISTER_TTBR0_EL1,
            "TTBR1": REGISTER_TTBR1_EL1,
        },
        "2": {
            "SCTLR": REGISTER_SCTLR_EL2,
            "MAIR": REGISTER_MAIR_EL2,
            "TCR": REGISTER_TCR_EL2,
            "TTBR0": REGISTER_TTBR0_EL2,
        },
        "3": {
            "SCTLR": REGISTER_SCTLR_EL3,
            "MAIR": REGISTER_MAIR_EL3,
            "TCR": REGISTER_TCR_EL3,
            "TTBR0": REGISTER_TTBR0_EL3,
        },
    }

    def __init__(self, el):
        """
        Constructor.
        """

        super(Armv8Mmu, self).__init__()

        # Current exception level
        self.crt_exc_level = el

        # Current granule size
        self.crt_granule_size = self.GRANULE_SIZE_4K

        # Current size of the virtual address
        self.crt_virt_addr_size = 0

        # Current value of the MAIR_Elx register
        self.crt_mair = 0

        # SCR_EL3 (security state for EL1/0)
        self.scr_el3 = 0

    def pprint(self):
        """
        Print results.
        """
        return (
            "The MMU has been read for EL"
            + str(self.crt_exc_level)
            + "\n"
            + S32DBGMmu.pprint(self)
        )

    def miprint(self, pretty_print=True):
        """
        Print results in JSON format.
        """
        if pretty_print:
            return json.dumps(
                {"currentEl": self.crt_exc_level, "mmu": S32DBGMmu.miprint(self)},
                indent=4,
            )
        else:
            return json.dumps(
                {"currentEl": self.crt_exc_level, "mmu": S32DBGMmu.miprint(self)}
            )

    def get_register_name(self, reg_group, reg_name):
        """Get register name for a certain register."""
        if reg_name == "CurrentEL":
            return self.REGISTER_CURRENTEL + " 8"

        el = 1 if self.crt_exc_level == 0 else self.crt_exc_level
        if not (self.REGISTERS[str(el)][reg_name.upper()] is None):
            return self.REGISTERS[str(el)][reg_name.upper()] + " 8"

        return "%s.%s_el%d" % (
            reg_group,
            reg_name,
            1 if (self.crt_exc_level == 0) else self.crt_exc_level,
        )

    def extract_descriptor(self, buff, position):
        """Extract a 64bit descriptor from the buffer read from the target."""
        temp = buff[position * 8 : (position + 1) * 8]
        if self.little_endian:
            temp.reverse()
        return int(binascii.hexlify(temp), 16)

    def create_mask(self, low, high):
        """
        Create a mask with all the bits from 'low' bit to 'high' bit set to 1.
        Bits: | | ... | | |high| ... |low| | | ... | |
        Mask: |0| ... |0| | 1  | ... | 1 | |0| ... |0|
        """
        return ((1 << (high - low + 1)) - 1) << low

    def get_current_el(self):
        """Determine current ELx."""
        self.crt_exc_level = (
            self.read_reg(self.get_register_name("", "CurrentEL")) & 0xF
        ) >> 2
        return self.crt_exc_level

    def read_mem(self, address, access_size, count, space="physical_noncoherent"):
        """Read memory using the given parameters"""
        return super(Armv8Mmu, self).read_mem(address, access_size, count, space)

    def get_mmu_enabled(self):
        """
        Determine if the MMU is enabled.
        Global enable for the EL MMU. The possible values are:

        0x0	Disables EL MMU. This is the reset value.
        0x1	Enables EL MMU.
        """
        sctlr = self.read_reg(self.get_register_name("system", "sctlr"))
        if sctlr & SCTLR_M_BITFIELD_MASK:
            return True

        return False

    def is_valid_descriptor(self, descriptor, level):
        """Check if the descriptor is a valid one."""
        if level > 3:
            return False

        if level == 3:
            return (
                descriptor & MMU_DESCRIPTOR_LEVEL_3_VALID_BITFIELD_MASK
            ) == MMU_DESCRIPTOR_LEVEL_3_VALID_VALUE

        return (
            descriptor & MMU_DESCRIPTOR_VALID_BITFIELD_MASK
        ) == MMU_DESCRIPTOR_VALID_VALUE

    def is_block_descriptor(self, descriptor, level):
        """Check if the descriptor points to a block."""
        if level == 3:
            return (
                descriptor & MMU_DESCRIPTOR_TYPE_BITFIELD_MASK
            ) == MMU_DESCRIPTOR_TABLE_ENTRY_TYPE

        return (
            descriptor & MMU_DESCRIPTOR_TYPE_BITFIELD_MASK
        ) == MMU_DESCRIPTOR_BLOCK_ENTRY_TYPE

    def input_addr_boundaries(self, level):
        """
        Get the boundaries (bits) from input address.

        Function returns (0, 0) for invalid combinations
        """

        if self.crt_granule_size == self.GRANULE_SIZE_4K:
            # Granule size: 4KByte
            if level == 0:
                if self.crt_virt_addr_size > 39:
                    # Level 0: Indexed by IA[n:39]
                    return (self.crt_virt_addr_size - 1, 39)
                else:
                    return (0, 0)
            elif level == 1:
                # Level 1: Indexed by IA[38:30]
                return (38, 30)
            elif level == 2:
                # Level 2: Indexed by IA[29:21]
                return (29, 21)
            else:
                # Level 3: # Indexed by IA[20:12]
                return (20, 12)

        if self.crt_granule_size == self.GRANULE_SIZE_16K:
            # Granule size: 16KByte
            if level == 0:
                # Level 0: Indexed by IA[47]
                return (47, 47)
            elif level == 1:
                # Level 1: Indexed by IA[46:36]
                return (46, 36)
            elif level == 2:
                # Level 2: Indexed by IA[35:25]
                return (35, 25)
            else:
                # Level 3: Indexed by IA[24:14]
                return (24, 14)

        if self.crt_granule_size == self.GRANULE_SIZE_64K:
            # Granule size: 64KByte
            if level == 1:
                if self.crt_virt_addr_size > 42:
                    # Level 1: Indexed by IA[n:42]
                    return (self.crt_virt_addr_size - 1, 42)
                else:
                    return (0, 0)
            elif level == 2:
                # Level 2: Indexed by IA[41:29]
                return (41, 29)
            else:
                # Level 3: Indexed by IA[28:16]
                return (28, 16)

        return (0, 0)

    def output_addr_boundaries(self, descriptor, level):
        """
        Get the boundaries (bits) from output address.

        Function returns (0, 0) for invalid combinations
        """
        if self.crt_granule_size == self.GRANULE_SIZE_4K:
            # Granule size: 4KByte
            if (
                descriptor & MMU_DESCRIPTOR_TYPE_BITFIELD_MASK
            ) == MMU_DESCRIPTOR_BLOCK_ENTRY_TYPE:
                if level == 1:
                    # 47:30
                    return (47, 30)
                elif level == 2:
                    # 47:21
                    return (47, 21)
            elif (
                descriptor & MMU_DESCRIPTOR_TYPE_BITFIELD_MASK
            ) == MMU_DESCRIPTOR_TABLE_ENTRY_TYPE:
                # 47:12
                return (47, 12)

        if self.crt_granule_size == self.GRANULE_SIZE_16K:
            # Granule size: 16KByte
            if (
                descriptor & MMU_DESCRIPTOR_TYPE_BITFIELD_MASK
            ) == MMU_DESCRIPTOR_BLOCK_ENTRY_TYPE:
                if level == 2:
                    # 47:25
                    return (47, 25)
            elif (
                descriptor & MMU_DESCRIPTOR_TYPE_BITFIELD_MASK
            ) == MMU_DESCRIPTOR_TABLE_ENTRY_TYPE:
                # 47:14
                return (47, 14)

        if self.crt_granule_size == self.GRANULE_SIZE_64K:
            # Granule size: 64KByte
            if (
                descriptor & MMU_DESCRIPTOR_TYPE_BITFIELD_MASK
            ) == MMU_DESCRIPTOR_BLOCK_ENTRY_TYPE:
                # 47:29
                return (47, 29)
            elif (
                descriptor & MMU_DESCRIPTOR_TYPE_BITFIELD_MASK
            ) == MMU_DESCRIPTOR_TABLE_ENTRY_TYPE:
                # 47:16
                return (47, 16)

        return (0, 0)

    def get_block_size(self, level):
        """
        Get the size of the block.
        """
        if self.crt_granule_size == self.GRANULE_SIZE_4K:
            if level == 1:
                # 1GB
                return SIZE_1GB
            elif level == 2:
                # 2MB
                return SIZE_2MB
            elif level == 3:
                # 4KB
                return SIZE_4KB
        elif self.crt_granule_size == self.GRANULE_SIZE_16K:
            if level == 2:
                # 32MB
                return SIZE_32MB
            elif level == 3:
                # 16KB
                return SIZE_16KB
        elif self.crt_granule_size == self.GRANULE_SIZE_64K:
            if level == 2:
                # 512MB
                return SIZE_512MB
            elif level == 3:
                # 64KB
                return SIZE_64KB

        return 0

    def get_access_permissions(self, descriptor):
        """
        Obtain memory access permissions for current descriptor.
        """
        # Only stage 1 block/page descriptors are handled below
        # AP[2:1] map to lower attributes part of descriptor, bits [7:6]
        ap_1 = (descriptor >> DESCRIPTOR_TO_AP_1_MAP_RSH) & BIT_ACTIVE
        ap_2 = (descriptor >> DESCRIPTOR_TO_AP_2_MAP_RSH) & BIT_ACTIVE
        if ap_2 == 0:
            return "None" if (ap_1 == 0 and self.crt_exc_level == 0) else "Read/write"
        else:
            # AP[2] == 1
            return "None" if (ap_1 == 0 and self.crt_exc_level == 0) else "Read-only"

    def get_mem_attributes(self, attr_index):
        """
        Populate attributes for current descriptor.
        """
        attr = self.crt_mair >> (8 * attr_index)
        attr = attr & ATTRIBUTE_7_0

        attr_7_4 = (attr & ATTRIBUTE_7_4) >> 4
        attr_3_0 = attr & ATTRIBUTE_3_0

        result = ""

        if attr_7_4 == 0:
            # Device
            if attr_3_0 == 0x0:
                result = "Device-nGnRnE"
            elif attr_3_0 == 0x4:
                result = "Device-nGnRE"
            elif attr_3_0 == 0x8:
                result = "Device-nGRE"
            elif attr_3_0 == 0xC:
                result = "Device-GRE"
            else:
                result = "Device"
        else:
            # Memory
            if (attr_7_4 > 0x0) and (attr_7_4 < 0x4):
                result = "Memory, Outer Write-through transient"
            elif attr_7_4 == 0x4:
                result = "Memory, Outer Non-Cacheable"
            elif (attr_7_4 > 0x4) and (attr_7_4 < 0x8):
                result = "Memory, Outer Write-back transient"
            elif (attr_7_4 >= 0x8) and (attr_7_4 < 0xC):
                result = "Memory, Outer Write-through non-transient"
            else:
                result = "Memory, Outer Write-back non-transient"

            if (attr_3_0 > 0x0) and (attr_3_0 < 0x4):
                result = result + ", Inner Write-through transient"
            elif attr_3_0 == 0x4:
                result = result + ", Inner Non-Cacheable"
            elif (attr_3_0 > 0x4) and (attr_3_0 < 0x8):
                result = result + ", Inner Write-back transient"
            elif (attr_3_0 >= 0x8) and (attr_3_0 < 0xC):
                result = result + ", Inner Write-through non-transient"
            elif attr_3_0 >= 0xC:
                result = result + ", Inner Write-back non-transient"

        return result

    def process_descriptor(
        self, descriptor, level, input_base_address, index, non_secure
    ):
        """
        Process current descriptor.
        """
        if not self.is_valid_descriptor(descriptor, level):
            return

        (crt_output_high, crt_output_low) = self.output_addr_boundaries(
            descriptor, level
        )

        crt_output_mask = self.create_mask(crt_output_low, crt_output_high)
        crt_output_addr = descriptor & crt_output_mask

        (_, crt_input_low) = self.input_addr_boundaries(level)
        crt_input_addr = input_base_address + (index << crt_input_low)

        crt_non_secure = False
        if not non_secure:
            crt_non_secure = (descriptor & NON_SECURE_BITFIELD_MASK) != 0
        if self.is_block_descriptor(descriptor, level):
            entry = Armv8MmuEntry()
            entry.descriptor = descriptor
            entry.virtual_address = crt_input_addr
            entry.physical_address = crt_output_addr
            entry.size = self.get_block_size(level)
            if (self.crt_exc_level == 0) or (self.crt_exc_level == 1):
                entry.non_global = (descriptor & NON_GLOBAL_BITFIELD_MASK) != 0
            entry.access_permissions = self.get_access_permissions(descriptor)
            entry.mem_attr = self.get_mem_attributes(
                (descriptor >> LOWER_ATTRIBUTES_RSH) & LOWER_ATTRIBUTES_BITFIELD_MASK
            )
            entry.non_secure = crt_non_secure
            self.mmu_translations.append(entry)
            return

        if level >= 3:
            return

        (next_input_high, next_input_low) = self.input_addr_boundaries(level + 1)

        if self.translate_address is None:
            next_no_entries = 1 << (next_input_high - next_input_low + 1)

            buff = self.read_mem(hex(crt_output_addr), 1, next_no_entries * 8)
            for i in range(0, next_no_entries):
                self.process_descriptor(
                    self.extract_descriptor(buff, i),
                    level + 1,
                    crt_input_addr,
                    i,
                    crt_non_secure,
                )
        else:
            # Determine the masks to be applied to base address and input address.
            next_input_addr_mask = self.create_mask(next_input_low, next_input_high)

            # Current index for the input address
            next_index = (
                self.translate_address & next_input_addr_mask
            ) >> next_input_low

            # Read the next descriptor
            buff = self.read_mem(hex(crt_output_addr + (next_index << 3)), 1, 8)

            self.process_descriptor(
                self.extract_descriptor(buff, 0),
                level + 1,
                crt_input_addr,
                next_index,
                crt_non_secure,
            )

    def read_translation_table(self, group_name, ttbr_index):
        """
        Function that reads the MMU entries starting with a translation table.
        EL1 & EL0 may use both TTBR0 & TTBR1
        """
        read_stage2 = False
        if (self.crt_exc_level == 0) or (self.crt_exc_level == 1):
            if self.scr_el3 & SCR_EL3_NON_SECURE_BITFIELD_MASK:
                hcr_el2 = self.read_reg("0x0001E088 8")  # HCR_EL2
                if hcr_el2 & HCR_EL2_VIRTUALIZATION_BITFIELD_MASK:
                    read_stage2 = True
                    return

        if read_stage2:
            raise gdb.error(
                "Stage 2 translation table walks are currently not supported!"
            )

        # Read TTBR
        ttbr_name = ""
        if ttbr_index == 0:
            ttbr_name = "ttbr0"
        else:
            ttbr_name = "ttbr1"

        ttbr = self.read_reg(self.get_register_name(group_name, ttbr_name))

        # Read TCR
        tcr = self.read_reg(self.get_register_name(group_name, "tcr"))

        # For EL0 & EL1 is possible to disable translation walks on a certain TTBR
        disable = False
        if (self.crt_exc_level == 0) or (self.crt_exc_level == 1):
            if ttbr_index == 0:
                epd = tcr & TCR_TRANSLATION_TABLE_WALK_DISABLED_FOR_TTBR_0_BITFIELD_MASK
            else:
                epd = tcr & TCR_TRANSLATION_TABLE_WALK_DISABLED_FOR_TTBR_1_BITFIELD_MASK

            if epd != 0:
                disable = True

        if disable:
            return
        crt_tg = 0
        if ttbr_index == 0:
            crt_tg = (tcr & TCR_TG0_BITFIELD_MASK) >> TCR_TG0_RSH
            if crt_tg == 0:
                self.crt_granule_size = self.GRANULE_SIZE_4K
            elif crt_tg == 2:
                self.crt_granule_size = self.GRANULE_SIZE_16K
            elif crt_tg == 1:
                self.crt_granule_size = self.GRANULE_SIZE_64K

            tsz = tcr & TCR_T0SZ_BITFIELD_MASK
        else:
            crt_tg = (tcr & TCR_TG1_BITFIELD_MASK) >> TCR_TG1_RSH
            if crt_tg == 2:
                self.crt_granule_size = self.GRANULE_SIZE_4K
            elif crt_tg == 1:
                self.crt_granule_size = self.GRANULE_SIZE_16K
            elif crt_tg == 3:
                self.crt_granule_size = self.GRANULE_SIZE_64K

            tsz = (tcr & TCR_T1SZ_BITFIELD_MASK) >> TCR_T1SZ_RSH

        if tsz > 39:
            tsz = 39

        # Virtual address bits
        self.crt_virt_addr_size = 64 - tsz

        virt_space_size = 1 << self.crt_virt_addr_size

        if ttbr_index == 0:
            # 0x0000_0000_0000_0000
            virtual_start = 0
            virtual_end = virt_space_size - 1
        else:
            # (2^64 - 2^(64-T1SZ))
            virtual_start = (1 << 64) - virt_space_size
            virtual_end = (1 << 64) - 1
        # Check if the address to be translated (if any) is in the interval for current table.
        if (self.translate_address is not None) and (
            (self.translate_address < virtual_start)
            or (self.translate_address > virtual_end)
        ):
            return

        # determine where to start
        start_level = 0
        lower_boundary = 0
        upper_boundary = 0
        while start_level <= 3:
            (upper_boundary, lower_boundary) = self.input_addr_boundaries(start_level)

            if upper_boundary > 0:
                break

            start_level = start_level + 1

        if start_level > 3:
            raise gdb.error("MMU is enabled but no valid descriptors were found.")

        no_entries = 1 << (upper_boundary - lower_boundary + 1)

        base_addr = ttbr & TTBR_BADDR_BITFIELD_MASK
        if self.translate_address is None:
            buff_level = self.read_mem(hex(base_addr), 1, no_entries * 8)
            for i in range(0, no_entries):
                self.process_descriptor(
                    self.extract_descriptor(buff_level, i),
                    start_level,
                    virtual_start,
                    i,
                    False,
                )
        else:
            # Determine the masks to be applied to base address and input address.
            input_addr_mask = self.create_mask(lower_boundary, upper_boundary)
            input_base_addr_mask = self.create_mask(
                upper_boundary - lower_boundary + 3, self.crt_virt_addr_size
            )

            # Current index for the input address
            crt_index = (
                (self.translate_address - virtual_start) & input_base_addr_mask
            ) >> lower_boundary

            # Address of the descriptor to be used for current level.
            next_descr_address = (base_addr & input_base_addr_mask) | (
                ((self.translate_address - virtual_start) & input_addr_mask)
                >> (lower_boundary - 3)
            )

            # Read descriptor.
            buff_level = self.read_mem(hex(next_descr_address), 1, 8)

            self.process_descriptor(
                self.extract_descriptor(buff_level, 0),
                start_level,
                virtual_start,
                crt_index,
                False,
            )

    def read_mmu(self):
        """
        Read MMU.
        """
        # start progress bar
        self.progress.start_progress_bar()

        group_name = "virtual_memory"

        # Initializes endianess
        self.initialize_endianess()

        if self.crt_exc_level < 0:
            # Get current EL
            self.crt_exc_level = self.get_current_el()

        # Will need SCR_EL3 to determine security state of EL0/EL1
        self.scr_el3 = self.read_reg("0x0001F088 8")

        if not self.get_mmu_enabled():
            self.progress.end_progress_bar()
            raise gdb.error("MMU is not enabled for the selected exception level.")

        # MAIR_ELx will help us determine mem attributes later
        self.crt_mair = self.read_reg(self.get_register_name(group_name, "mair"))

        self.read_translation_table(group_name, 0)
        if (self.crt_exc_level == 0) or (self.crt_exc_level == 1):
            self.read_translation_table(group_name, 1)

        self.progress.end_progress_bar()


class S32DBGMmuCommand(gdb.Command):
    """usage: mmu [<arch-specific-options>]

    Display MMU state in a user readable format.

        architecture-specific arguments:
          -el {0,1,2,3}         specify the exception level for which to read the
                                translations;if not specified, current exception level
                                will be used.
          -t <virtual address>, --translate <virtual address>
                                specify the virtual address to be translated.
          -j                  , --json
                                print the result in json format
          -p                  , --pretty-print
                                It must be used together with -j to specify that
                                printing in JSON format must be done in a user friendly format"""

    def __init__(self):
        """
        Constructor.
        """
        super(S32DBGMmuCommand, self).__init__(CMD_S32DBG_MMU_COMMAND, gdb.COMMAND_USER)
        self.parser = None

    def invoke(self, argument, from_tty):
        # pylint: disable=W0613
        """
        Function that will be executed when command is invoked.
        """
        self.dont_repeat()
        gdb.write(self.execute_commands(argument) + "\n")
        gdb.flush()

    def execute_commands(self, argument):
        self.parser = argparse.ArgumentParser(
            prog="mmu",
            formatter_class=argparse.RawDescriptionHelpFormatter,
            add_help=False,
        )
        res = None

        mmu_inst = ArchSpecificMmuCommand.get_instance()
        mmu_inst.contribute_args(self.parser)

        args = None
        args, unknown_args = self.parser.parse_known_args(argument.split())

        if unknown_args:
            raise gdb.GdbError("Error: invalid parameters")

        mmu = None
        error = None
        try:
            translate_address = None

            # Save the address to be translated, if any.
            if args.translate and args.translate.strip():
                # Trim '0x' from the start of the string if any.
                addr_string = args.translate
                if addr_string.startswith("0x"):
                    addr_string = addr_string[2:]

                if len(addr_string) > 16:
                    raise gdb.error(
                        "Input address is not a valid 64 bit hexadecimal value."
                    )

                addr_string = addr_string.zfill(16)

                try:
                    translate_address = int(
                        binascii.hexlify(bytearray.fromhex(addr_string)), 16
                    )
                except:
                    raise gdb.error(
                        "Address to be translated is not a valid hexadecimal value."
                    )

            mmu = mmu_inst.get_mmu_handler(args)
            mmu.set_translate_address(translate_address)
            mmu.read_mmu()
        except KeyboardInterrupt:
            pass
        except gdb.error as gdd_excp:
            error = str(gdd_excp)

        # Even if an exception was raised, try to print the computed translations if any
        try:
            if mmu is not None:
                if args.json == False:
                    res = mmu.pprint()
                else:
                    res = mmu.miprint(args.pretty_print)
        except KeyboardInterrupt:
            pass
        except gdb.error as gdd_excp:
            # Save the error only if no other error was raised until this point
            if error is None:
                error = str(gdd_excp)

        if error is not None:
            if args.json:
                gdb.write(str({"error": error}) + "\n")
            else:
                gdb.write(error + "\n")
        if res is None:
            res = "No data to display."

        return res


class ArchSpecificMmuCommand(object):
    """
    Base class for architecture-specific MmuCommand implementations
    """

    @staticmethod
    def get_instance():
        """
        Get architecture-specific MMU command instance
        """

        arch = S32DBGMmu.get_architecture()
        if "aarch64" in arch:
            return Armv8MmuCommand()
        else:
            raise gdb.GdbError("ERROR: Unsupported architecture %s" % arch)

    def contribute_args(self, parser):
        """
        Contribute architecture-specific arguments. It must be overridden in derived classes.
        """
        raise NotImplementedError(
            "Cannot call method {0} of abstract class {1} instance".format(
                inspect.currentframe().f_code.co_name, self.__class__.__name__
            )
        )

    def get_mmu_handler(self, args):
        """
        Get architecture-specific MMU handler instance. It must be overridden in derived classes.
        """
        raise NotImplementedError(
            "Cannot call method {0} of abstract class {1} instance".format(
                inspect.currentframe().f_code.co_name, self.__class__.__name__
            )
        )


class Armv8MmuCommand(ArchSpecificMmuCommand):
    """
    Armv8 specialized class
    """

    def contribute_args(self, parser):
        """
        architecture-specific arguments:
          -el {0,1,2,3}         specify the exception level for which to read the
                                translations;if not specified, current exception level
                                will be used.
          -t <virtual address>, --translate <virtual address>
                                specify the virtual address to be translated.
          -j                  , --json
                                print the result in json format
          -p                  , --pretty-print
                                It must be used together with -j to specify that
                                printing in JSON format must be done in a user friendly format
        """

        parser.add_argument(
            "-el",
            type=int,
            choices=[0, 1, 2, 3],
            default=-1,
            help="specify the exception level for which to read the translations; "
            "if not specified, current exception level will be used.",
        )

        parser.add_argument(
            "-t",
            "--translate",
            metavar="<virtual address>",
            help="specify the virtual address to be translated.",
        )
        parser.add_argument(
            "-j", "--json", action="store_true", help="print the result in json format"
        )
        parser.add_argument(
            "-p",
            "--pretty-print",
            action="store_true",
            help="It must be used together with -j to specify that printing in JSON format must be done in a user friendly format",
        )

    def get_mmu_handler(self, args):
        """
        Get Armv8 MMU handler instance
        """
        return Armv8Mmu(args.el)


def create_mmu_command():
    """Create MMU command"""
    S32DBGMmuCommand()


def checkError(result):
    if result.iserror():
        exc = utils.gdb_exec("monitor errorinfo").unwrap()
        additional_str = "Additional error details:"
        additional = exc.find(additional_str)
        raise gdb.GdbError(
            "[S32Debugger]Error: {}\n{}".format(
                result.error.error_type.value,
                exc[additional + len(additional_str) :]
                .replace("[", "")
                .replace("]", ""),
            )
        )
    else:
        res = result.unwrap()
        if res[-1] == "\n":
            res = res[:-1]
        return res
