// SPDX-License-Identifier: GPL-2.0+ /* * Maple Tree implementation * Copyright (c) 2018-2022 Oracle Corporation * Authors: Liam R. Howlett * Matthew Wilcox * * The following are copied and modified from lib/maple_tree.c */ #include "maple_tree.h" #include "defs.h" unsigned char *mt_slots = NULL; unsigned char *mt_pivots = NULL; ulong mt_max[4] = {0}; #define MAPLE_BUFSIZE 512 static inline ulong mte_to_node(ulong maple_enode_entry) { return maple_enode_entry & ~MAPLE_NODE_MASK; } static inline enum maple_type mte_node_type(ulong maple_enode_entry) { return (maple_enode_entry >> MAPLE_NODE_TYPE_SHIFT) & MAPLE_NODE_TYPE_MASK; } static inline ulong mt_slot(void **slots, unsigned char offset) { return (ulong)slots[offset]; } static inline bool ma_is_leaf(const enum maple_type type) { return type < maple_range_64; } /*************** For cmd_tree ********************/ struct do_maple_tree_info { ulong maxcount; ulong count; void *data; }; struct maple_tree_ops { void (*entry)(ulong node, ulong slot, const char *path, ulong index, void *private); void *private; bool is_td; }; static const char spaces[] = " "; static void do_mt_range64(ulong, ulong, ulong, uint, char *, ulong *, struct maple_tree_ops *); static void do_mt_arange64(ulong, ulong, ulong, uint, char *, ulong *, struct maple_tree_ops *); static void do_mt_entry(ulong, ulong, ulong, uint, uint, char *, ulong *, struct maple_tree_ops *); static void do_mt_node(ulong, ulong, ulong, uint, char *, ulong *, struct maple_tree_ops *); struct req_entry *fill_member_offsets(char *); void dump_struct_members_fast(struct req_entry *, int, ulong); void dump_struct_members_for_tree(struct tree_data *, int, ulong); static void mt_dump_range(ulong min, ulong max, uint depth) { if (min == max) fprintf(fp, "%.*s%lu: ", depth * 2, spaces, min); else fprintf(fp, "%.*s%lu-%lu: ", depth * 2, spaces, min, max); } static inline bool mt_is_reserved(ulong entry) { return (entry < MAPLE_RESERVED_RANGE) && xa_is_internal(entry); } static inline bool mte_is_leaf(ulong maple_enode_entry) { return ma_is_leaf(mte_node_type(maple_enode_entry)); } static uint mt_height(char *mt_buf) { return (UINT(mt_buf + OFFSET(maple_tree_ma_flags)) & MT_FLAGS_HEIGHT_MASK) >> MT_FLAGS_HEIGHT_OFFSET; } static void dump_mt_range64(char *mr64_buf) { int i; fprintf(fp, " contents: "); for (i = 0; i < mt_slots[maple_range_64] - 1; i++) fprintf(fp, "%p %lu ", VOID_PTR(mr64_buf + OFFSET(maple_range_64_slot) + sizeof(void *) * i), ULONG(mr64_buf + OFFSET(maple_range_64_pivot) + sizeof(ulong) * i)); fprintf(fp, "%p\n", VOID_PTR(mr64_buf + OFFSET(maple_range_64_slot) + sizeof(void *) * i)); } static void dump_mt_arange64(char *ma64_buf) { int i; fprintf(fp, " contents: "); for (i = 0; i < mt_slots[maple_arange_64]; i++) fprintf(fp, "%lu ", ULONG(ma64_buf + OFFSET(maple_arange_64_gap) + sizeof(ulong) * i)); fprintf(fp, "| %02X %02X| ", UCHAR(ma64_buf + OFFSET(maple_arange_64_meta) + OFFSET(maple_metadata_end)), UCHAR(ma64_buf + OFFSET(maple_arange_64_meta) + OFFSET(maple_metadata_gap))); for (i = 0; i < mt_slots[maple_arange_64] - 1; i++) fprintf(fp, "%p %lu ", VOID_PTR(ma64_buf + OFFSET(maple_arange_64_slot) + sizeof(void *) * i), ULONG(ma64_buf + OFFSET(maple_arange_64_pivot) + sizeof(ulong) * i)); fprintf(fp, "%p\n", VOID_PTR(ma64_buf + OFFSET(maple_arange_64_slot) + sizeof(void *) * i)); } static void dump_mt_entry(ulong entry, ulong min, ulong max, uint depth) { mt_dump_range(min, max, depth); if (xa_is_value(entry)) fprintf(fp, "value %ld (0x%lx) [0x%lx]\n", xa_to_value(entry), xa_to_value(entry), entry); else if (xa_is_zero(entry)) fprintf(fp, "zero (%ld)\n", xa_to_internal(entry)); else if (mt_is_reserved(entry)) fprintf(fp, "UNKNOWN ENTRY (0x%lx)\n", entry); else fprintf(fp, "0x%lx\n", entry); } static void dump_mt_node(ulong maple_node, char *node_data, uint type, ulong min, ulong max, uint depth) { mt_dump_range(min, max, depth); fprintf(fp, "node 0x%lx depth %d type %d parent %p", maple_node, depth, type, maple_node ? VOID_PTR(node_data + OFFSET(maple_node_parent)) : NULL); } static void do_mt_range64(ulong entry, ulong min, ulong max, uint depth, char *path, ulong *global_index, struct maple_tree_ops *ops) { ulong maple_node_m_node = mte_to_node(entry); char node_buf[MAPLE_BUFSIZE]; bool leaf = mte_is_leaf(entry); ulong first = min, last; int i; int len = strlen(path); struct tree_data *td = ops->is_td ? (struct tree_data *)ops->private : NULL; char *mr64_buf; if (SIZE(maple_node) > MAPLE_BUFSIZE) error(FATAL, "MAPLE_BUFSIZE should be larger than maple_node struct"); readmem(maple_node_m_node, KVADDR, node_buf, SIZE(maple_node), "mt_dump_range64 read maple_node", FAULT_ON_ERROR); mr64_buf = node_buf + OFFSET(maple_node_mr64); if (td && td->flags & TREE_STRUCT_VERBOSE) { dump_mt_range64(mr64_buf); } for (i = 0; i < mt_slots[maple_range_64]; i++) { last = max; if (i < (mt_slots[maple_range_64] - 1)) last = ULONG(mr64_buf + OFFSET(maple_range_64_pivot) + sizeof(ulong) * i); else if (!VOID_PTR(mr64_buf + OFFSET(maple_range_64_slot) + sizeof(void *) * i) && max != mt_max[mte_node_type(entry)]) break; if (last == 0 && i > 0) break; if (leaf) do_mt_entry(mt_slot((void **)(mr64_buf + OFFSET(maple_range_64_slot)), i), first, last, depth + 1, i, path, global_index, ops); else if (VOID_PTR(mr64_buf + OFFSET(maple_range_64_slot) + sizeof(void *) * i)) { sprintf(path + len, "/%d", i); do_mt_node(mt_slot((void **)(mr64_buf + OFFSET(maple_range_64_slot)), i), first, last, depth + 1, path, global_index, ops); } if (last == max) break; if (last > max) { fprintf(fp, "node %p last (%lu) > max (%lu) at pivot %d!\n", mr64_buf, last, max, i); break; } first = last + 1; } } static void do_mt_arange64(ulong entry, ulong min, ulong max, uint depth, char *path, ulong *global_index, struct maple_tree_ops *ops) { ulong maple_node_m_node = mte_to_node(entry); char node_buf[MAPLE_BUFSIZE]; bool leaf = mte_is_leaf(entry); ulong first = min, last; int i; int len = strlen(path); struct tree_data *td = ops->is_td ? (struct tree_data *)ops->private : NULL; char *ma64_buf; if (SIZE(maple_node) > MAPLE_BUFSIZE) error(FATAL, "MAPLE_BUFSIZE should be larger than maple_node struct"); readmem(maple_node_m_node, KVADDR, node_buf, SIZE(maple_node), "mt_dump_arange64 read maple_node", FAULT_ON_ERROR); ma64_buf = node_buf + OFFSET(maple_node_ma64); if (td && td->flags & TREE_STRUCT_VERBOSE) { dump_mt_arange64(ma64_buf); } for (i = 0; i < mt_slots[maple_arange_64]; i++) { last = max; if (i < (mt_slots[maple_arange_64] - 1)) last = ULONG(ma64_buf + OFFSET(maple_arange_64_pivot) + sizeof(ulong) * i); else if (!VOID_PTR(ma64_buf + OFFSET(maple_arange_64_slot) + sizeof(void *) * i)) break; if (last == 0 && i > 0) break; if (leaf) do_mt_entry(mt_slot((void **)(ma64_buf + OFFSET(maple_arange_64_slot)), i), first, last, depth + 1, i, path, global_index, ops); else if (VOID_PTR(ma64_buf + OFFSET(maple_arange_64_slot) + sizeof(void *) * i)) { sprintf(path + len, "/%d", i); do_mt_node(mt_slot((void **)(ma64_buf + OFFSET(maple_arange_64_slot)), i), first, last, depth + 1, path, global_index, ops); } if (last == max) break; if (last > max) { fprintf(fp, "node %p last (%lu) > max (%lu) at pivot %d!\n", ma64_buf, last, max, i); break; } first = last + 1; } } static void do_mt_entry(ulong entry, ulong min, ulong max, uint depth, uint index, char *path, ulong *global_index, struct maple_tree_ops *ops) { int print_radix = 0, i; static struct req_entry **e = NULL; struct tree_data *td = ops->is_td ? (struct tree_data *)ops->private : NULL; if (ops->entry && entry) ops->entry(entry, entry, path, max, ops->private); if (!td) return; if (!td->count && td->structname_args) { /* * Retrieve all members' info only once (count == 0) * After last iteration all memory will be freed up */ e = (struct req_entry **)GETBUF(sizeof(*e) * td->structname_args); for (i = 0; i < td->structname_args; i++) e[i] = fill_member_offsets(td->structname[i]); } td->count++; if (td->flags & TREE_STRUCT_VERBOSE) { dump_mt_entry(entry, min, max, depth); } else if (td->flags & VERBOSE && entry) fprintf(fp, "%lx\n", entry); if (td->flags & TREE_POSITION_DISPLAY && entry) fprintf(fp, " index: %ld position: %s/%u\n", ++(*global_index), path, index); if (td->structname && entry) { if (td->flags & TREE_STRUCT_RADIX_10) print_radix = 10; else if (td->flags & TREE_STRUCT_RADIX_16) print_radix = 16; else print_radix = 0; for (i = 0; i < td->structname_args; i++) { switch (count_chars(td->structname[i], '.')) { case 0: dump_struct(td->structname[i], entry, print_radix); break; default: if (td->flags & TREE_PARSE_MEMBER) dump_struct_members_for_tree(td, i, entry); else if (td->flags & TREE_READ_MEMBER) dump_struct_members_fast(e[i], print_radix, entry); } } } if (e) FREEBUF(e); } static void do_mt_node(ulong entry, ulong min, ulong max, uint depth, char *path, ulong *global_index, struct maple_tree_ops *ops) { ulong maple_node = mte_to_node(entry); uint type = mte_node_type(entry); uint i; char node_buf[MAPLE_BUFSIZE]; struct tree_data *td = ops->is_td ? (struct tree_data *)ops->private : NULL; if (SIZE(maple_node) > MAPLE_BUFSIZE) error(FATAL, "MAPLE_BUFSIZE should be larger than maple_node struct"); readmem(maple_node, KVADDR, node_buf, SIZE(maple_node), "mt_dump_node read maple_node", FAULT_ON_ERROR); if (td && td->flags & TREE_STRUCT_VERBOSE) { dump_mt_node(maple_node, node_buf, type, min, max, depth); } switch (type) { case maple_dense: for (i = 0; i < mt_slots[maple_dense]; i++) { if (min + i > max) fprintf(fp, "OUT OF RANGE: "); do_mt_entry(mt_slot((void **)(node_buf + OFFSET(maple_node_slot)), i), min + i, min + i, depth, i, path, global_index, ops); } break; case maple_leaf_64: case maple_range_64: do_mt_range64(entry, min, max, depth, path, global_index, ops); break; case maple_arange_64: do_mt_arange64(entry, min, max, depth, path, global_index, ops); break; default: fprintf(fp, " UNKNOWN TYPE\n"); } } static int do_maple_tree_traverse(ulong ptr, int is_root, struct maple_tree_ops *ops) { char path[BUFSIZE] = {0}; char tree_buf[MAPLE_BUFSIZE]; ulong entry; struct tree_data *td = ops->is_td ? (struct tree_data *)ops->private : NULL; ulong global_index = 0; if (SIZE(maple_tree) > MAPLE_BUFSIZE) error(FATAL, "MAPLE_BUFSIZE should be larger than maple_tree struct"); if (!is_root) { strcpy(path, "direct"); do_mt_node(ptr, 0, mt_max[mte_node_type(ptr)], 0, path, &global_index, ops); } else { readmem(ptr, KVADDR, tree_buf, SIZE(maple_tree), "mt_dump read maple_tree", FAULT_ON_ERROR); entry = ULONG(tree_buf + OFFSET(maple_tree_ma_root)); if (td && td->flags & TREE_STRUCT_VERBOSE) { fprintf(fp, "maple_tree(%lx) flags %X, height %u root 0x%lx\n\n", ptr, UINT(tree_buf + OFFSET(maple_tree_ma_flags)), mt_height(tree_buf), entry); } if (!xa_is_node(entry)) do_mt_entry(entry, 0, 0, 0, 0, path, &global_index, ops); else if (entry) { strcpy(path, "root"); do_mt_node(entry, 0, mt_max[mte_node_type(entry)], 0, path, &global_index, ops); } } return 0; } int do_mptree(struct tree_data *td) { struct maple_tree_ops ops = { .entry = NULL, .private = td, .is_td = true, }; int is_root = !(td->flags & TREE_NODE_POINTER); do_maple_tree_traverse(td->start, is_root, &ops); return 0; } /************* For do_maple_tree *****************/ static void do_maple_tree_count(ulong node, ulong slot, const char *path, ulong index, void *private) { struct do_maple_tree_info *info = private; info->count++; } static void do_maple_tree_search(ulong node, ulong slot, const char *path, ulong index, void *private) { struct do_maple_tree_info *info = private; struct list_pair *lp = info->data; if (lp->index == index) { lp->value = (void *)slot; info->count = 1; } } static void do_maple_tree_dump(ulong node, ulong slot, const char *path, ulong index, void *private) { struct do_maple_tree_info *info = private; fprintf(fp, "[%lu] %lx\n", index, slot); info->count++; } static void do_maple_tree_gather(ulong node, ulong slot, const char *path, ulong index, void *private) { struct do_maple_tree_info *info = private; struct list_pair *lp = info->data; if (info->maxcount) { lp[info->count].index = index; lp[info->count].value = (void *)slot; info->count++; info->maxcount--; } } static void do_maple_tree_dump_cb(ulong node, ulong slot, const char *path, ulong index, void *private) { struct do_maple_tree_info *info = private; struct list_pair *lp = info->data; int (*cb)(ulong) = lp->value; /* Caller defined operation */ if (!cb(slot)) { error(FATAL, "do_maple_tree: callback " "operation failed: entry: %ld item: %lx\n", info->count, slot); } info->count++; } /* * do_maple_tree argument usage: * * root: Address of a maple_tree_root structure * * flag: MAPLE_TREE_COUNT - Return the number of entries in the tree. * MAPLE_TREE_SEARCH - Search for an entry at lp->index; if found, * store the entry in lp->value and return a count of 1; otherwise * return a count of 0. * MAPLE_TREE_DUMP - Dump all existing index/value pairs. * MAPLE_TREE_GATHER - Store all existing index/value pairs in the * passed-in array of list_pair structs starting at lp, * returning the count of entries stored; the caller can/should * limit the number of returned entries by putting the array size * (max count) in the lp->index field of the first structure * in the passed-in array. * MAPLE_TREE_DUMP_CB - Similar with MAPLE_TREE_DUMP, but for each * maple tree entry, a user defined callback at lp->value will * be invoked. * * lp: Unused by MAPLE_TREE_COUNT and MAPLE_TREE_DUMP. * A pointer to a list_pair structure for MAPLE_TREE_SEARCH. * A pointer to an array of list_pair structures for * MAPLE_TREE_GATHER; the dimension (max count) of the array may * be stored in the index field of the first structure to avoid * any chance of an overrun. * For MAPLE_TREE_DUMP_CB, the lp->value must be initialized as a * callback function. The callback prototype must be: int (*)(ulong); */ ulong do_maple_tree(ulong root, int flag, struct list_pair *lp) { struct do_maple_tree_info info = { .count = 0, .data = lp, }; struct maple_tree_ops ops = { .private = &info, .is_td = false, }; switch (flag) { case MAPLE_TREE_COUNT: ops.entry = do_maple_tree_count; break; case MAPLE_TREE_SEARCH: ops.entry = do_maple_tree_search; break; case MAPLE_TREE_DUMP: ops.entry = do_maple_tree_dump; break; case MAPLE_TREE_GATHER: if (!(info.maxcount = lp->index)) info.maxcount = (ulong)(-1); /* caller beware */ ops.entry = do_maple_tree_gather; break; case MAPLE_TREE_DUMP_CB: if (lp->value == NULL) { error(FATAL, "do_maple_tree: need set callback function"); } ops.entry = do_maple_tree_dump_cb; break; default: error(FATAL, "do_maple_tree: invalid flag: %lx\n", flag); } do_maple_tree_traverse(root, true, &ops); return info.count; } /***********************************************/ void maple_init(void) { int array_len; STRUCT_SIZE_INIT(maple_tree, "maple_tree"); STRUCT_SIZE_INIT(maple_node, "maple_node"); MEMBER_OFFSET_INIT(maple_tree_ma_root, "maple_tree", "ma_root"); MEMBER_OFFSET_INIT(maple_tree_ma_flags, "maple_tree", "ma_flags"); MEMBER_OFFSET_INIT(maple_node_parent, "maple_node", "parent"); MEMBER_OFFSET_INIT(maple_node_ma64, "maple_node", "ma64"); MEMBER_OFFSET_INIT(maple_node_mr64, "maple_node", "mr64"); MEMBER_OFFSET_INIT(maple_node_slot, "maple_node", "slot"); MEMBER_OFFSET_INIT(maple_arange_64_pivot, "maple_arange_64", "pivot"); MEMBER_OFFSET_INIT(maple_arange_64_slot, "maple_arange_64", "slot"); MEMBER_OFFSET_INIT(maple_arange_64_gap, "maple_arange_64", "gap"); MEMBER_OFFSET_INIT(maple_arange_64_meta, "maple_arange_64", "meta"); MEMBER_OFFSET_INIT(maple_range_64_pivot, "maple_range_64", "pivot"); MEMBER_OFFSET_INIT(maple_range_64_slot, "maple_range_64", "slot"); MEMBER_OFFSET_INIT(maple_metadata_end, "maple_metadata", "end"); MEMBER_OFFSET_INIT(maple_metadata_gap, "maple_metadata", "gap"); array_len = get_array_length("mt_slots", NULL, sizeof(char)); mt_slots = calloc(array_len, sizeof(char)); readmem(symbol_value("mt_slots"), KVADDR, mt_slots, array_len * sizeof(char), "maple_init read mt_slots", RETURN_ON_ERROR); array_len = get_array_length("mt_pivots", NULL, sizeof(char)); mt_pivots = calloc(array_len, sizeof(char)); readmem(symbol_value("mt_pivots"), KVADDR, mt_pivots, array_len * sizeof(char), "maple_init read mt_pivots", RETURN_ON_ERROR); mt_max[maple_dense] = mt_slots[maple_dense]; mt_max[maple_leaf_64] = ULONG_MAX; mt_max[maple_range_64] = ULONG_MAX; mt_max[maple_arange_64] = ULONG_MAX; }