162306a36Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0-only */
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Common arm64 stack unwinder code.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * See: arch/arm64/kernel/stacktrace.c for the reference implementation.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Copyright (C) 2012 ARM Ltd.
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci#ifndef __ASM_STACKTRACE_COMMON_H
1062306a36Sopenharmony_ci#define __ASM_STACKTRACE_COMMON_H
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/kprobes.h>
1362306a36Sopenharmony_ci#include <linux/types.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_cistruct stack_info {
1662306a36Sopenharmony_ci	unsigned long low;
1762306a36Sopenharmony_ci	unsigned long high;
1862306a36Sopenharmony_ci};
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/**
2162306a36Sopenharmony_ci * struct unwind_state - state used for robust unwinding.
2262306a36Sopenharmony_ci *
2362306a36Sopenharmony_ci * @fp:          The fp value in the frame record (or the real fp)
2462306a36Sopenharmony_ci * @pc:          The lr value in the frame record (or the real lr)
2562306a36Sopenharmony_ci *
2662306a36Sopenharmony_ci * @kr_cur:      When KRETPROBES is selected, holds the kretprobe instance
2762306a36Sopenharmony_ci *               associated with the most recently encountered replacement lr
2862306a36Sopenharmony_ci *               value.
2962306a36Sopenharmony_ci *
3062306a36Sopenharmony_ci * @task:        The task being unwound.
3162306a36Sopenharmony_ci *
3262306a36Sopenharmony_ci * @stack:       The stack currently being unwound.
3362306a36Sopenharmony_ci * @stacks:      An array of stacks which can be unwound.
3462306a36Sopenharmony_ci * @nr_stacks:   The number of stacks in @stacks.
3562306a36Sopenharmony_ci */
3662306a36Sopenharmony_cistruct unwind_state {
3762306a36Sopenharmony_ci	unsigned long fp;
3862306a36Sopenharmony_ci	unsigned long pc;
3962306a36Sopenharmony_ci#ifdef CONFIG_KRETPROBES
4062306a36Sopenharmony_ci	struct llist_node *kr_cur;
4162306a36Sopenharmony_ci#endif
4262306a36Sopenharmony_ci	struct task_struct *task;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	struct stack_info stack;
4562306a36Sopenharmony_ci	struct stack_info *stacks;
4662306a36Sopenharmony_ci	int nr_stacks;
4762306a36Sopenharmony_ci};
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistatic inline struct stack_info stackinfo_get_unknown(void)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	return (struct stack_info) {
5262306a36Sopenharmony_ci		.low = 0,
5362306a36Sopenharmony_ci		.high = 0,
5462306a36Sopenharmony_ci	};
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic inline bool stackinfo_on_stack(const struct stack_info *info,
5862306a36Sopenharmony_ci				      unsigned long sp, unsigned long size)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	if (!info->low)
6162306a36Sopenharmony_ci		return false;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	if (sp < info->low || sp + size < sp || sp + size > info->high)
6462306a36Sopenharmony_ci		return false;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	return true;
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic inline void unwind_init_common(struct unwind_state *state,
7062306a36Sopenharmony_ci				      struct task_struct *task)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	state->task = task;
7362306a36Sopenharmony_ci#ifdef CONFIG_KRETPROBES
7462306a36Sopenharmony_ci	state->kr_cur = NULL;
7562306a36Sopenharmony_ci#endif
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	state->stack = stackinfo_get_unknown();
7862306a36Sopenharmony_ci}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistatic struct stack_info *unwind_find_next_stack(const struct unwind_state *state,
8162306a36Sopenharmony_ci						 unsigned long sp,
8262306a36Sopenharmony_ci						 unsigned long size)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	for (int i = 0; i < state->nr_stacks; i++) {
8562306a36Sopenharmony_ci		struct stack_info *info = &state->stacks[i];
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci		if (stackinfo_on_stack(info, sp, size))
8862306a36Sopenharmony_ci			return info;
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	return NULL;
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci/**
9562306a36Sopenharmony_ci * unwind_consume_stack() - Check if an object is on an accessible stack,
9662306a36Sopenharmony_ci * updating stack boundaries so that future unwind steps cannot consume this
9762306a36Sopenharmony_ci * object again.
9862306a36Sopenharmony_ci *
9962306a36Sopenharmony_ci * @state: the current unwind state.
10062306a36Sopenharmony_ci * @sp:    the base address of the object.
10162306a36Sopenharmony_ci * @size:  the size of the object.
10262306a36Sopenharmony_ci *
10362306a36Sopenharmony_ci * Return: 0 upon success, an error code otherwise.
10462306a36Sopenharmony_ci */
10562306a36Sopenharmony_cistatic inline int unwind_consume_stack(struct unwind_state *state,
10662306a36Sopenharmony_ci				       unsigned long sp,
10762306a36Sopenharmony_ci				       unsigned long size)
10862306a36Sopenharmony_ci{
10962306a36Sopenharmony_ci	struct stack_info *next;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	if (stackinfo_on_stack(&state->stack, sp, size))
11262306a36Sopenharmony_ci		goto found;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	next = unwind_find_next_stack(state, sp, size);
11562306a36Sopenharmony_ci	if (!next)
11662306a36Sopenharmony_ci		return -EINVAL;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	/*
11962306a36Sopenharmony_ci	 * Stack transitions are strictly one-way, and once we've
12062306a36Sopenharmony_ci	 * transitioned from one stack to another, it's never valid to
12162306a36Sopenharmony_ci	 * unwind back to the old stack.
12262306a36Sopenharmony_ci	 *
12362306a36Sopenharmony_ci	 * Remove the current stack from the list of stacks so that it cannot
12462306a36Sopenharmony_ci	 * be found on a subsequent transition.
12562306a36Sopenharmony_ci	 *
12662306a36Sopenharmony_ci	 * Note that stacks can nest in several valid orders, e.g.
12762306a36Sopenharmony_ci	 *
12862306a36Sopenharmony_ci	 *   TASK -> IRQ -> OVERFLOW -> SDEI_NORMAL
12962306a36Sopenharmony_ci	 *   TASK -> SDEI_NORMAL -> SDEI_CRITICAL -> OVERFLOW
13062306a36Sopenharmony_ci	 *   HYP -> OVERFLOW
13162306a36Sopenharmony_ci	 *
13262306a36Sopenharmony_ci	 * ... so we do not check the specific order of stack
13362306a36Sopenharmony_ci	 * transitions.
13462306a36Sopenharmony_ci	 */
13562306a36Sopenharmony_ci	state->stack = *next;
13662306a36Sopenharmony_ci	*next = stackinfo_get_unknown();
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cifound:
13962306a36Sopenharmony_ci	/*
14062306a36Sopenharmony_ci	 * Future unwind steps can only consume stack above this frame record.
14162306a36Sopenharmony_ci	 * Update the current stack to start immediately above it.
14262306a36Sopenharmony_ci	 */
14362306a36Sopenharmony_ci	state->stack.low = sp + size;
14462306a36Sopenharmony_ci	return 0;
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci/**
14862306a36Sopenharmony_ci * unwind_next_frame_record() - Unwind to the next frame record.
14962306a36Sopenharmony_ci *
15062306a36Sopenharmony_ci * @state:        the current unwind state.
15162306a36Sopenharmony_ci *
15262306a36Sopenharmony_ci * Return: 0 upon success, an error code otherwise.
15362306a36Sopenharmony_ci */
15462306a36Sopenharmony_cistatic inline int
15562306a36Sopenharmony_ciunwind_next_frame_record(struct unwind_state *state)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	unsigned long fp = state->fp;
15862306a36Sopenharmony_ci	int err;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	if (fp & 0x7)
16162306a36Sopenharmony_ci		return -EINVAL;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	err = unwind_consume_stack(state, fp, 16);
16462306a36Sopenharmony_ci	if (err)
16562306a36Sopenharmony_ci		return err;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	/*
16862306a36Sopenharmony_ci	 * Record this frame record's values.
16962306a36Sopenharmony_ci	 */
17062306a36Sopenharmony_ci	state->fp = READ_ONCE(*(unsigned long *)(fp));
17162306a36Sopenharmony_ci	state->pc = READ_ONCE(*(unsigned long *)(fp + 8));
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	return 0;
17462306a36Sopenharmony_ci}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci#endif	/* __ASM_STACKTRACE_COMMON_H */
177