1/*
2 * Copyright (c) 2022, Carlo Caione <ccaione@baylibre.com>
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7/**
8 * @file
9 * @brief ARM Cortex-M suspend-to-RAM code (S2RAM)
10 */
11
12#include <zephyr/toolchain.h>
13#include <offsets_short.h>
14#include <zephyr/arch/cpu.h>
15#include <zephyr/arch/common/pm_s2ram.h>
16
17/**
18 * Macro expanding to an integer literal equal to the offset of
19 * field `sr_name` in `struct __cpu_context`. This macro has to
20 * be implemented in C, because GEN_OFFSET_SYM provides offsets
21 * as C preprocessor definitions - there are not visible to the
22 * assembler.
23 *
24 * See also: `arch/arm/core/offsets/offsets_aarch32.c`
25 */
26#define CPU_CTX_SR_OFFSET(sr_name) \
27	___cpu_context_t_ ## sr_name ## _OFFSET
28
29/**
30 * Macros used to save / load a special register in __cpu_context.
31 * These also have to be implemented in C due to CPU_CTX_SR_OFFSET.
32 */
33#define SAVE_SPECIAL_REG(sr_name, cpu_ctx_reg, tmp_reg)	\
34	mrs	tmp_reg, sr_name;			\
35	str	tmp_reg, [cpu_ctx_reg, # CPU_CTX_SR_OFFSET(sr_name)];
36
37#define RESTORE_SPECIAL_REG(sr_name, cpu_ctx_reg, tmp_reg)		\
38	ldr	tmp_reg, [cpu_ctx_reg, # CPU_CTX_SR_OFFSET(sr_name)];	\
39	msr	sr_name, tmp_reg;
40
41/*
42 * The following macros could be written as assembler macros, but C is used
43 * for portability (assembler macro syntax may differ between toolchains).
44 */
45
46/*
47 * Pushes registers r4~r12 and lr on the stack.
48 * r0 is unmodified but other GPRs may be overwritten.
49 */
50#if !defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
51/* `push` on ARMv6-M / ARMv8-M Baseline:
52 * only r0~r7 and lr may be pushed
53 */
54#define PUSH_GPRS							\
55	push	{r4-r7};						\
56	mov	r1, r8;							\
57	mov	r2, r9;							\
58	mov	r3, r10;						\
59	mov	r4, r11;						\
60	mov	r5, r12;						\
61	push	{r1-r5, lr}
62#else
63/* `push` on ARMv7-M and ARMv8-M Mainline: no limitation */
64#define PUSH_GPRS							\
65	push	{r4-r12, lr}
66#endif /* !CONFIG_ARMV7_M_ARMV8_M_MAINLINE */
67
68/*
69 * Pops registers r4~r12 and lr from the stack
70 * r0 is unmodified but other GPRs may be overwritten.
71 */
72#if !defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
73/* `pop` on ARMv6-M / ARMv8-M Baseline:
74 * can only pop to r0~r7 and pc (not lr!)
75 */
76#define POP_GPRS							\
77	pop	{r1-r6};						\
78	mov 	lr, r6;							\
79	mov 	r12, r5;						\
80	mov 	r11, r4;						\
81	mov 	r10, r3;						\
82	mov 	r9, r2;							\
83	mov 	r8, r1;							\
84	pop	{r4-r7}
85#else
86/* `pop` on ARMv7-M and ARMv8-M Mainline: no limitation */
87#define POP_GPRS							\
88	pop	{r4-r12, lr}
89#endif /* !CONFIG_ARMV7_M_ARMV8_M_MAINLINE */
90
91
92#if defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
93/* Registers present only on ARMv7-M and ARMv8-M Mainline */
94#define SAVE_FM_BP_REGS(cpu_ctx, tmp_reg)				\
95	SAVE_SPECIAL_REG(faultmask,	cpu_ctx, tmp_reg)		\
96	SAVE_SPECIAL_REG(basepri,	cpu_ctx, tmp_reg)
97
98#define RESTORE_FM_BP_REGS(cpu_ctx, tmp_reg)				\
99	RESTORE_SPECIAL_REG(faultmask,	cpu_ctx, tmp_reg)		\
100	RESTORE_SPECIAL_REG(basepri,	cpu_ctx, tmp_reg)
101#else
102/* Registers not present: do nothing */
103#define SAVE_FM_BP_REGS(cpu_ctx, tmp_reg)
104#define RESTORE_FM_BP_REGS(cpu_ctx, tmp_reg)
105#endif /* CONFIG_ARMV7_M_ARMV8_M_MAINLINE */
106
107#if defined(CONFIG_CPU_CORTEX_M_HAS_SPLIM)
108/* Registers present only on certain ARMv8-M implementations */
109#define SAVE_SPLIM_REGS(cpu_ctx, tmp_reg)				\
110	SAVE_SPECIAL_REG(msplim,	cpu_ctx, tmp_reg)		\
111	SAVE_SPECIAL_REG(psplim,	cpu_ctx, tmp_reg)
112
113#define RESTORE_SPLIM_REGS(cpu_ctx, tmp_reg)				\
114	RESTORE_SPECIAL_REG(msplim,	cpu_ctx, tmp_reg)		\
115	RESTORE_SPECIAL_REG(psplim,	cpu_ctx, tmp_reg)
116#else
117/* Registers not present: do nothing */
118#define SAVE_SPLIM_REGS(cpu_ctx, tmp_reg)
119#define RESTORE_SPLIM_REGS(cpu_ctx, tmp_reg)
120#endif /* CONFIG_CPU_CORTEX_M_HAS_SPLIM */
121
122/*
123 * Saves the CPU's special registers in the `struct __cpu_context`
124 * pointed to by the `cpu_ctx` register.
125 * The `tmp_reg` register is overwritten as part of this process.
126 */
127#define SAVE_SPECIAL_REGISTERS(cpu_ctx, tmp_reg)			\
128	SAVE_SPECIAL_REG(msp,		cpu_ctx, tmp_reg)		\
129	SAVE_SPECIAL_REG(psp,		cpu_ctx, tmp_reg)		\
130	SAVE_SPECIAL_REG(primask,	cpu_ctx, tmp_reg)		\
131	SAVE_SPLIM_REGS(		cpu_ctx, tmp_reg)		\
132	SAVE_FM_BP_REGS(		cpu_ctx, tmp_reg)		\
133	SAVE_SPECIAL_REG(control,	cpu_ctx, tmp_reg)
134
135/*
136 * Restores the CPU's special registers from the `struct __cpu_context`
137 * pointed to by the `cpu_ctx` register.
138 * The `tmp_reg` register is overwritten as part of this process.
139 *
140 * N.B.: ISB at the end is required because "Software must use an ISB
141 * barrier instruction to ensure a write to the CONTROL register takes
142 * effect before the next instruction is executed."
143 *
144 * If this macro is modified, make sure CONTROL is always the last
145 * restored register, and that an ISB follows the MSR instruction.
146 */
147#define RESTORE_SPECIAL_REGISTERS(cpu_ctx, tmp_reg)			\
148	RESTORE_SPECIAL_REG(msp,	cpu_ctx, tmp_reg)		\
149	RESTORE_SPECIAL_REG(psp,	cpu_ctx, tmp_reg)		\
150	RESTORE_SPECIAL_REG(primask,	cpu_ctx, tmp_reg)		\
151	RESTORE_SPLIM_REGS(		cpu_ctx, tmp_reg)		\
152	RESTORE_FM_BP_REGS(		cpu_ctx, tmp_reg)		\
153	RESTORE_SPECIAL_REG(control,	cpu_ctx, tmp_reg)		\
154	isb
155
156_ASM_FILE_PROLOGUE
157
158GTEXT(pm_s2ram_mark_set)
159GTEXT(pm_s2ram_mark_check_and_clear)
160GDATA(_cpu_context)
161
162SECTION_FUNC(TEXT, arch_pm_s2ram_suspend)
163	/*
164	 * Save the CPU context
165	 *
166	 * r0: address of the system_off function
167	 */
168	PUSH_GPRS
169
170	/* Move system_off to protected register. */
171	mov 	r4, r0
172
173	/* Store CPU context */
174	ldr	r1, =_cpu_context
175
176	SAVE_SPECIAL_REGISTERS(/* ctx: */ r1, /* tmp: */ r2)
177
178	/*
179	 * Mark entering suspend to RAM.
180	 */
181	mov 	r1, lr
182	bl 	pm_s2ram_mark_set
183	mov	lr, r1
184
185	/*
186	 * Call the system_off function passed as parameter. This should never
187	 * return.
188	 */
189	blx	r4
190
191	/*
192	 * The system_off function returns here only when the powering off was
193	 * not successful (in r0 the return value).
194	 */
195
196	/* Move return value of system_off to callee-saved register. */
197	mov 	r4, r0
198
199	/*
200	 * Reset the marking of suspend to RAM, return is ignored.
201	 */
202	mov 	r1, lr
203	bl	pm_s2ram_mark_check_and_clear
204	mov 	lr, r1
205
206	/* Move the stored return value of system_off back to r0,
207	 * setting it as return value for this function.
208	 */
209	mov	r0, r4
210
211	POP_GPRS
212	bx	lr
213
214
215GTEXT(arch_pm_s2ram_resume)
216SECTION_FUNC(TEXT, arch_pm_s2ram_resume)
217	/*
218	 * Check if reset occurred after suspending to RAM.
219	 */
220	mov 	r1, lr
221	bl 	pm_s2ram_mark_check_and_clear
222	mov 	lr, r1
223	cmp     r0, #0x1
224	beq     resume
225	bx      lr
226
227resume:
228	/*
229	 * Restore the CPU context
230	 */
231	ldr	r0, =_cpu_context
232
233	RESTORE_SPECIAL_REGISTERS(/* ctx: */ r0, /* tmp: */ r1)
234
235	POP_GPRS
236
237	/*
238	 * Set the return value and return
239	 */
240	movs	r0, #0
241	bx	lr
242