16bf212c8SKalesh Singh /* SPDX-License-Identifier: GPL-2.0-only */
26bf212c8SKalesh Singh /*
36bf212c8SKalesh Singh  * Common arm64 stack unwinder code.
46bf212c8SKalesh Singh  *
5a4c750e2SMarc Zyngier  * See: arch/arm64/kernel/stacktrace.c for the reference implementation.
6051ece67SKalesh Singh  *
76bf212c8SKalesh Singh  * Copyright (C) 2012 ARM Ltd.
86bf212c8SKalesh Singh  */
96bf212c8SKalesh Singh #ifndef __ASM_STACKTRACE_COMMON_H
106bf212c8SKalesh Singh #define __ASM_STACKTRACE_COMMON_H
116bf212c8SKalesh Singh 
126bf212c8SKalesh Singh #include <linux/types.h>
136bf212c8SKalesh Singh 
146bf212c8SKalesh Singh struct stack_info {
156bf212c8SKalesh Singh 	unsigned long low;
166bf212c8SKalesh Singh 	unsigned long high;
176bf212c8SKalesh Singh };
186bf212c8SKalesh Singh 
1916283c54SMark Rutland /**
2016283c54SMark Rutland  * struct unwind_state - state used for robust unwinding.
216bf212c8SKalesh Singh  *
226bf212c8SKalesh Singh  * @fp:          The fp value in the frame record (or the real fp)
236bf212c8SKalesh Singh  * @pc:          The lr value in the frame record (or the real lr)
246bf212c8SKalesh Singh  *
258df13730SMark Rutland  * @stack:       The stack currently being unwound.
268df13730SMark Rutland  * @stacks:      An array of stacks which can be unwound.
278df13730SMark Rutland  * @nr_stacks:   The number of stacks in @stacks.
286bf212c8SKalesh Singh  */
296bf212c8SKalesh Singh struct unwind_state {
306bf212c8SKalesh Singh 	unsigned long fp;
316bf212c8SKalesh Singh 	unsigned long pc;
328df13730SMark Rutland 
338df13730SMark Rutland 	struct stack_info stack;
348df13730SMark Rutland 	struct stack_info *stacks;
358df13730SMark Rutland 	int nr_stacks;
366bf212c8SKalesh Singh };
376bf212c8SKalesh Singh 
stackinfo_get_unknown(void)38d1f684e4SMark Rutland static inline struct stack_info stackinfo_get_unknown(void)
39d1f684e4SMark Rutland {
40d1f684e4SMark Rutland 	return (struct stack_info) {
41d1f684e4SMark Rutland 		.low = 0,
42d1f684e4SMark Rutland 		.high = 0,
43d1f684e4SMark Rutland 	};
44d1f684e4SMark Rutland }
45d1f684e4SMark Rutland 
stackinfo_on_stack(const struct stack_info * info,unsigned long sp,unsigned long size)4636f9a879SMark Rutland static inline bool stackinfo_on_stack(const struct stack_info *info,
4736f9a879SMark Rutland 				      unsigned long sp, unsigned long size)
4836f9a879SMark Rutland {
4936f9a879SMark Rutland 	if (!info->low)
5036f9a879SMark Rutland 		return false;
5136f9a879SMark Rutland 
5236f9a879SMark Rutland 	if (sp < info->low || sp + size < sp || sp + size > info->high)
5336f9a879SMark Rutland 		return false;
5436f9a879SMark Rutland 
5536f9a879SMark Rutland 	return true;
5636f9a879SMark Rutland }
5736f9a879SMark Rutland 
unwind_init_common(struct unwind_state * state)581beef60eSMark Rutland static inline void unwind_init_common(struct unwind_state *state)
596bf212c8SKalesh Singh {
608df13730SMark Rutland 	state->stack = stackinfo_get_unknown();
616bf212c8SKalesh Singh }
626bf212c8SKalesh Singh 
63*f05a4a42SMark Rutland /**
64*f05a4a42SMark Rutland  * unwind_find_stack() - Find the accessible stack which entirely contains an
65*f05a4a42SMark Rutland  * object.
66*f05a4a42SMark Rutland  *
67*f05a4a42SMark Rutland  * @state: the current unwind state.
68*f05a4a42SMark Rutland  * @sp:    the base address of the object.
69*f05a4a42SMark Rutland  * @size:  the size of the object.
70*f05a4a42SMark Rutland  *
71*f05a4a42SMark Rutland  * Return: a pointer to the relevant stack_info if found; NULL otherwise.
72*f05a4a42SMark Rutland  */
unwind_find_stack(struct unwind_state * state,unsigned long sp,unsigned long size)73*f05a4a42SMark Rutland static struct stack_info *unwind_find_stack(struct unwind_state *state,
748df13730SMark Rutland 					    unsigned long sp,
758df13730SMark Rutland 					    unsigned long size)
768df13730SMark Rutland {
77*f05a4a42SMark Rutland 	struct stack_info *info = &state->stack;
788df13730SMark Rutland 
798df13730SMark Rutland 	if (stackinfo_on_stack(info, sp, size))
808df13730SMark Rutland 		return info;
81*f05a4a42SMark Rutland 
82*f05a4a42SMark Rutland 	for (int i = 0; i < state->nr_stacks; i++) {
83*f05a4a42SMark Rutland 		info = &state->stacks[i];
84*f05a4a42SMark Rutland 		if (stackinfo_on_stack(info, sp, size))
85*f05a4a42SMark Rutland 			return info;
868df13730SMark Rutland 	}
878df13730SMark Rutland 
888df13730SMark Rutland 	return NULL;
898df13730SMark Rutland }
908df13730SMark Rutland 
9116283c54SMark Rutland /**
92*f05a4a42SMark Rutland  * unwind_consume_stack() - Update stack boundaries so that future unwind steps
93*f05a4a42SMark Rutland  * cannot consume this object again.
944e00532fSMarc Zyngier  *
958df13730SMark Rutland  * @state: the current unwind state.
96*f05a4a42SMark Rutland  * @info:  the stack_info of the stack containing the object.
978df13730SMark Rutland  * @sp:    the base address of the object.
988df13730SMark Rutland  * @size:  the size of the object.
9916283c54SMark Rutland  *
1008df13730SMark Rutland  * Return: 0 upon success, an error code otherwise.
1014e00532fSMarc Zyngier  */
unwind_consume_stack(struct unwind_state * state,struct stack_info * info,unsigned long sp,unsigned long size)102*f05a4a42SMark Rutland static inline void unwind_consume_stack(struct unwind_state *state,
103*f05a4a42SMark Rutland 					struct stack_info *info,
1048df13730SMark Rutland 					unsigned long sp,
1058df13730SMark Rutland 					unsigned long size)
1068df13730SMark Rutland {
107*f05a4a42SMark Rutland 	struct stack_info tmp;
1088df13730SMark Rutland 
1098df13730SMark Rutland 	/*
1108df13730SMark Rutland 	 * Stack transitions are strictly one-way, and once we've
1118df13730SMark Rutland 	 * transitioned from one stack to another, it's never valid to
1128df13730SMark Rutland 	 * unwind back to the old stack.
1138df13730SMark Rutland 	 *
114*f05a4a42SMark Rutland 	 * Destroy the old stack info so that it cannot be found upon a
115*f05a4a42SMark Rutland 	 * subsequent transition. If the stack has not changed, we'll
116*f05a4a42SMark Rutland 	 * immediately restore the current stack info.
1178df13730SMark Rutland 	 *
1188df13730SMark Rutland 	 * Note that stacks can nest in several valid orders, e.g.
1198df13730SMark Rutland 	 *
1208df13730SMark Rutland 	 *   TASK -> IRQ -> OVERFLOW -> SDEI_NORMAL
1218df13730SMark Rutland 	 *   TASK -> SDEI_NORMAL -> SDEI_CRITICAL -> OVERFLOW
1228df13730SMark Rutland 	 *   HYP -> OVERFLOW
1238df13730SMark Rutland 	 *
1248df13730SMark Rutland 	 * ... so we do not check the specific order of stack
1258df13730SMark Rutland 	 * transitions.
1268df13730SMark Rutland 	 */
127*f05a4a42SMark Rutland 	tmp = *info;
128*f05a4a42SMark Rutland 	*info = stackinfo_get_unknown();
129*f05a4a42SMark Rutland 	state->stack = tmp;
1308df13730SMark Rutland 
1318df13730SMark Rutland 	/*
1328df13730SMark Rutland 	 * Future unwind steps can only consume stack above this frame record.
1338df13730SMark Rutland 	 * Update the current stack to start immediately above it.
1348df13730SMark Rutland 	 */
1358df13730SMark Rutland 	state->stack.low = sp + size;
1368df13730SMark Rutland }
1374e00532fSMarc Zyngier 
138b532ab5fSMark Rutland /**
139b532ab5fSMark Rutland  * unwind_next_frame_record() - Unwind to the next frame record.
140b532ab5fSMark Rutland  *
141b532ab5fSMark Rutland  * @state:        the current unwind state.
142b532ab5fSMark Rutland  *
143b532ab5fSMark Rutland  * Return: 0 upon success, an error code otherwise.
144b532ab5fSMark Rutland  */
145b532ab5fSMark Rutland static inline int
unwind_next_frame_record(struct unwind_state * state)1464b5e694eSMark Rutland unwind_next_frame_record(struct unwind_state *state)
147be63c647SKalesh Singh {
148*f05a4a42SMark Rutland 	struct stack_info *info;
149886c2b0bSMark Rutland 	struct frame_record *record;
1504b5e694eSMark Rutland 	unsigned long fp = state->fp;
151be63c647SKalesh Singh 
152be63c647SKalesh Singh 	if (fp & 0x7)
153be63c647SKalesh Singh 		return -EINVAL;
154be63c647SKalesh Singh 
155*f05a4a42SMark Rutland 	info = unwind_find_stack(state, fp, sizeof(*record));
156*f05a4a42SMark Rutland 	if (!info)
157*f05a4a42SMark Rutland 		return -EINVAL;
158*f05a4a42SMark Rutland 
159*f05a4a42SMark Rutland 	unwind_consume_stack(state, info, fp, sizeof(*record));
160be63c647SKalesh Singh 
161be63c647SKalesh Singh 	/*
1628df13730SMark Rutland 	 * Record this frame record's values.
163be63c647SKalesh Singh 	 */
164886c2b0bSMark Rutland 	record = (struct frame_record *)fp;
165886c2b0bSMark Rutland 	state->fp = READ_ONCE(record->fp);
166886c2b0bSMark Rutland 	state->pc = READ_ONCE(record->lr);
167be63c647SKalesh Singh 
168be63c647SKalesh Singh 	return 0;
169be63c647SKalesh Singh }
170f51e7146SKalesh Singh 
1716bf212c8SKalesh Singh #endif	/* __ASM_STACKTRACE_COMMON_H */
172