xref: /rk3399_ARM-atf/plat/marvell/armada/a8k/a80x0_nbx/board/ramoopsies.c (revision 90b7958b2a6a0010be69b1d8f5ec1aa1cd73d026)
1 /*
2  * Copyright (C) 2017-2026 Freebox SAS
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  *
6  * ramoopsies - Save/restore ramoops buffer during DDR ECC initialization
7  *
8  * When booting with ECC-enabled DDR and uninitialized memory (cold boot),
9  * the memory controller will raise SError exceptions due to parity errors.
10  * This module saves the ramoops buffer (kernel crash logs) before DDR
11  * scrubbing and restores it afterward, also handling any pending SError.
12  *
13  * This uses the linker --wrap feature to intercept mv_ddr_mem_scrubbing()
14  * without modifying the upstream mv-ddr-marvell repository.
15  *
16  * Original author: Nicolas Schichan <nschichan@freebox.fr>
17  */
18 
19 #include <string.h>
20 
21 #include <arch_helpers.h>
22 
23 /*
24  * ISR_EL1.A bit indicates pending SError
25  */
26 #define ISR_A_BIT	(1U << ISR_A_SHIFT)
27 
28 /*
29  * ARMv8 exception vectors must be aligned on a 2048 byte boundary.
30  * Each vector entry has space for 32 instructions (128 bytes) for
31  * sync aborts, IRQs, FIQs and SErrors.
32  *
33  * We only need to handle SError - use ERET (0xd69f03e0) to return.
34  * All other vectors are filled with zeros (invalid instruction trap).
35  *
36  * Vector table layout (4 exception levels x 4 exception types x 32 insns):
37  *   - Offset 0x000: Current EL with SP_EL0 (Sync, IRQ, FIQ, SError)
38  *   - Offset 0x200: Current EL with SP_ELx (Sync, IRQ, FIQ, SError)
39  *   - Offset 0x400: Lower EL AArch64 (Sync, IRQ, FIQ, SError)
40  *   - Offset 0x600: Lower EL AArch32 (Sync, IRQ, FIQ, SError)
41  *
42  * SError vectors are at offsets 0x180, 0x380, 0x580, 0x780 within the table.
43  * We use designated initializers to place ERET at these SError entry points.
44  */
45 #define ARMV8_ERET	0xd69f03e0U
46 
47 /* SError vector offsets in uint32_t units (byte offset / 4) */
48 #define SERROR_EL0_IDX		(0x180 / 4)	/* Current EL SP_EL0 SError */
49 #define SERROR_ELX_IDX		(0x380 / 4)	/* Current EL SP_ELx SError */
50 #define SERROR_AARCH64_IDX	(0x580 / 4)	/* Lower EL AArch64 SError */
51 #define SERROR_AARCH32_IDX	(0x780 / 4)	/* Lower EL AArch32 SError */
52 
53 /*
54  * Custom exception vector table - only handles SError with ERET
55  * Total size: 2048 bytes (512 x uint32_t)
56  */
57 static uint32_t vectors[512] __aligned(2048) = {
58 	[SERROR_EL0_IDX]     = ARMV8_ERET,
59 	[SERROR_ELX_IDX]     = ARMV8_ERET,
60 	[SERROR_AARCH64_IDX] = ARMV8_ERET,
61 	[SERROR_AARCH32_IDX] = ARMV8_ERET,
62 };
63 
64 /*
65  * clear_serror - Clear a raised SError exception
66  *
67  * There is no architecturally defined way of clearing an SError exception,
68  * so we need to actually take the exception. This is done by:
69  *
70  * 1. Installing a custom vector table at VBAR_EL3
71  * 2. Routing SError to EL3 via SCR_EL3.EA
72  * 3. Unmasking SError exceptions
73  * 4. The custom vector handler just executes ERET
74  * 5. Restoring original VBAR and SCR
75  */
clear_serror(void)76 static void clear_serror(void)
77 {
78 	u_register_t isr = read_isr_el1();
79 	u_register_t old_vbar = read_vbar_el3();
80 	u_register_t old_scr = read_scr_el3();
81 
82 	if ((isr & ISR_A_BIT) == 0U)
83 		/* No pending SError, nothing to do */
84 		return;
85 
86 	/* Install custom exception vector */
87 	write_vbar_el3((u_register_t)vectors);
88 
89 	/* Route SError exceptions to EL3 */
90 	write_scr_el3(read_scr_el3() | SCR_EA_BIT);
91 
92 	/* Ensure changes are visible */
93 	isb();
94 
95 	/* Unmask and wait for SError to clear */
96 	enable_serror();
97 	do {
98 		isr = read_isr_el1();
99 	} while ((isr & ISR_A_BIT) != 0U);
100 	disable_serror();
101 
102 	/* Restore original SCR and VBAR */
103 	write_vbar_el3(old_vbar);
104 	write_scr_el3(old_scr);
105 	isb();
106 }
107 
108 /*
109  * Ramoops buffer location and size
110  * This is where Linux stores kernel crash/oops logs
111  */
112 #define RAMOOPSIES_ADDR	((void *)0x3fff8000ULL)
113 #define RAMOOPSIES_SIZE	(32U << 10)	/* 32KB */
114 
115 /*
116  * Backup area in SRAM during BLE execution
117  */
118 static uint8_t ramoopsies_backup[RAMOOPSIES_SIZE];
119 
120 /*
121  * Reference to the real mv_ddr_mem_scrubbing() provided by --wrap
122  */
123 extern void __real_mv_ddr_mem_scrubbing(void);
124 
125 /*
126  * __wrap_mv_ddr_mem_scrubbing - Wrapper for DDR memory scrubbing
127  *
128  * This function is called instead of mv_ddr_mem_scrubbing() due to
129  * the --wrap linker option. It:
130  * 1. Saves the ramoops buffer and clears any pending SError
131  * 2. Calls the real scrubbing function
132  * 3. Restores the ramoops buffer
133  */
__wrap_mv_ddr_mem_scrubbing(void)134 void __wrap_mv_ddr_mem_scrubbing(void)
135 {
136 	/* Save ramoops buffer before scrubbing clears it */
137 	memcpy(ramoopsies_backup, RAMOOPSIES_ADDR, RAMOOPSIES_SIZE);
138 
139 	/*
140 	 * When booting with uninitialized DDR (cold boot with ECC enabled),
141 	 * the memory controller will raise an SError due to ECC parity
142 	 * errors. Clear it so we can continue.
143 	 */
144 	clear_serror();
145 
146 	/* Call the real scrubbing function */
147 	__real_mv_ddr_mem_scrubbing();
148 
149 	/* Restore the ramoops buffer */
150 	memcpy(RAMOOPSIES_ADDR, ramoopsies_backup, RAMOOPSIES_SIZE);
151 }
152