| # SPDX-License-Identifier: GPL-2.0 |
| # |
| # Radix Tree Parser |
| # |
| # Copyright (c) 2016 Linaro Ltd |
| # Copyright (c) 2023 Broadcom |
| # |
| # Authors: |
| # Kieran Bingham <kieran.bingham@linaro.org> |
| # Florian Fainelli <f.fainelli@gmail.com> |
| |
| import gdb |
| |
| from linux import utils |
| from linux import constants |
| |
| radix_tree_root_type = utils.CachedType("struct xarray") |
| radix_tree_node_type = utils.CachedType("struct xa_node") |
| |
| def is_internal_node(node): |
| long_type = utils.get_long_type() |
| return ((node.cast(long_type) & constants.LX_RADIX_TREE_ENTRY_MASK) == constants.LX_RADIX_TREE_INTERNAL_NODE) |
| |
| def entry_to_node(node): |
| long_type = utils.get_long_type() |
| node_type = node.type |
| indirect_ptr = node.cast(long_type) & ~constants.LX_RADIX_TREE_INTERNAL_NODE |
| return indirect_ptr.cast(radix_tree_node_type.get_type().pointer()) |
| |
| def node_maxindex(node): |
| return (constants.LX_RADIX_TREE_MAP_SIZE << node['shift']) - 1 |
| |
| def resolve_root(root): |
| if root.type == radix_tree_root_type.get_type(): |
| return root |
| if root.type == radix_tree_root_type.get_type().pointer(): |
| return root.dereference() |
| raise gdb.GdbError("must be {} not {}" |
| .format(radix_tree_root_type.get_type(), root.type)) |
| |
| def lookup(root, index): |
| root = resolve_root(root) |
| node = root['xa_head'] |
| if node == 0: |
| return None |
| |
| if not (is_internal_node(node)): |
| if (index > 0): |
| return None |
| return node |
| |
| node = entry_to_node(node) |
| maxindex = node_maxindex(node) |
| |
| if (index > maxindex): |
| return None |
| |
| shift = node['shift'] + constants.LX_RADIX_TREE_MAP_SHIFT |
| |
| while True: |
| offset = (index >> node['shift']) & constants.LX_RADIX_TREE_MAP_MASK |
| slot = node['slots'][offset] |
| |
| if slot == 0: |
| return None |
| |
| node = slot.cast(node.type.pointer()).dereference() |
| if node == 0: |
| return None |
| |
| shift -= constants.LX_RADIX_TREE_MAP_SHIFT |
| if (shift <= 0): |
| break |
| |
| return node |
| |
| def descend(parent, index): |
| offset = (index >> int(parent["shift"])) & constants.LX_RADIX_TREE_MAP_MASK |
| return offset, parent["slots"][offset] |
| |
| def load_root(root): |
| node = root["xa_head"] |
| nodep = node |
| |
| if is_internal_node(node): |
| node = entry_to_node(node) |
| maxindex = node_maxindex(node) |
| return int(node["shift"]) + constants.LX_RADIX_TREE_MAP_SHIFT, \ |
| nodep, maxindex |
| |
| return 0, nodep, 0 |
| |
| class RadixTreeIter: |
| def __init__(self, start): |
| self.index = 0 |
| self.next_index = start |
| self.node = None |
| |
| def xa_mk_internal(v): |
| return (v << 2) | 2 |
| |
| LX_XA_RETRY_ENTRY = xa_mk_internal(256) |
| LX_RADIX_TREE_RETRY = LX_XA_RETRY_ENTRY |
| |
| def next_chunk(root, iter): |
| mask = (1 << (utils.get_ulong_type().sizeof * 8)) - 1 |
| |
| index = iter.next_index |
| if index == 0 and iter.index != 0: |
| return None |
| |
| restart = True |
| while restart: |
| restart = False |
| |
| _, child, maxindex = load_root(root) |
| if index > maxindex: |
| return None |
| if not child: |
| return None |
| |
| if not is_internal_node(child): |
| iter.index = index |
| iter.next_index = (maxindex + 1) & mask |
| iter.node = None |
| return root["xa_head"].address |
| |
| while True: |
| node = entry_to_node(child) |
| offset, child = descend(node, index) |
| |
| if not child: |
| while True: |
| offset += 1 |
| if offset >= constants.LX_RADIX_TREE_MAP_SIZE: |
| break |
| slot = node["slots"][offset] |
| if slot: |
| break |
| index &= ~node_maxindex(node) |
| index = (index + (offset << int(node["shift"]))) & mask |
| if index == 0: |
| return None |
| if offset == constants.LX_RADIX_TREE_MAP_SIZE: |
| restart = True |
| break |
| child = node["slots"][offset] |
| |
| if not child: |
| restart = True |
| break |
| if child == LX_XA_RETRY_ENTRY: |
| break |
| if not node["shift"] or not is_internal_node(child): |
| break |
| |
| iter.index = (index & ~node_maxindex(node)) | offset |
| iter.next_index = ((index | node_maxindex(node)) + 1) & mask |
| iter.node = node |
| |
| return node["slots"][offset].address |
| |
| def next_slot(slot, iter): |
| mask = (1 << (utils.get_ulong_type().sizeof * 8)) - 1 |
| for _ in range(iter.next_index - iter.index - 1): |
| slot += 1 |
| iter.index = (iter.index + 1) & mask |
| if slot.dereference(): |
| return slot |
| return None |
| |
| def for_each_slot(root, start=0): |
| iter = RadixTreeIter(start) |
| slot = None |
| while True: |
| if not slot: |
| slot = next_chunk(root, iter) |
| if not slot: |
| break |
| yield iter.index, slot |
| slot = next_slot(slot, iter) |
| |
| class LxRadixTreeLookup(gdb.Function): |
| """ Lookup and return a node from a RadixTree. |
| |
| $lx_radix_tree_lookup(root_node [, index]): Return the node at the given index. |
| If index is omitted, the root node is dereference and returned.""" |
| |
| def __init__(self): |
| super(LxRadixTreeLookup, self).__init__("lx_radix_tree_lookup") |
| |
| def invoke(self, root, index=0): |
| result = lookup(root, index) |
| if result is None: |
| raise gdb.GdbError("No entry in tree at index {}".format(index)) |
| |
| return result |
| |
| class LxRadixTree(gdb.Command): |
| """Show all values stored in a RadixTree.""" |
| |
| def __init__(self): |
| super(LxRadixTree, self).__init__("lx-radix-tree", gdb.COMMAND_DATA, |
| gdb.COMPLETE_NONE) |
| |
| def invoke(self, argument, from_tty): |
| args = gdb.string_to_argv(argument) |
| if len(args) != 1: |
| raise gdb.GdbError("Usage: lx-radix-tree ROOT") |
| root = gdb.parse_and_eval(args[0]) |
| for index, slot in for_each_slot(root): |
| gdb.write("[{}] = {}\n".format(index, slot.dereference())) |
| |
| LxRadixTree() |
| LxRadixTreeLookup() |