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