crash/maple_tree.c

616 lines
17 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Maple Tree implementation
* Copyright (c) 2018-2022 Oracle Corporation
* Authors: Liam R. Howlett <Liam.Howlett@oracle.com>
* Matthew Wilcox <willy@infradead.org>
*
* 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;
}