plat/common/crash_console_helpers.S: Fix MULTI_CONSOLE_API support

Crash reporting via the default consoles registered by MULTI_CONSOLE_API
has been broken since commit d35cc34 (Console: Use callee-saved
registers), which was introduced to allow console drivers written in C.
It's not really possible with the current crash reporting framework to
support console drivers in C, however we should make sure that the
existing assembly drivers that do support crash reporting continue to
work through the MULTI_CONSOLE_API.

This patch fixes the problem by creating custom console_putc() and
console_flush() implementations for the crash reporting case that do not
use the stack. Platforms that want to use this feature will have to link
plat/common/aarch64/crash_console_helpers.S explicitly.

Also update the documentation to better reflect the new reality (of this
being an option rather than the expected default for most platforms).

Change-Id: Id0c761e5e2fddaf25c277bc7b8ab603946ca73cb
Signed-off-by: Julius Werner <jwerner@chromium.org>
diff --git a/plat/common/aarch64/crash_console_helpers.S b/plat/common/aarch64/crash_console_helpers.S
index 55e7abb..8f8ca11 100644
--- a/plat/common/aarch64/crash_console_helpers.S
+++ b/plat/common/aarch64/crash_console_helpers.S
@@ -20,53 +20,163 @@
 #error "This crash console implementation only works with the MULTI_CONSOLE_API!"
 #endif
 
-	/* -----------------------------------------------------
-	 * int plat_crash_console_init(void)
-	 * Use normal console by default. Switch it to crash
-	 * mode so serial consoles become active again.
-	 * NOTE: This default implementation will only work for
-	 * crashes that occur after a normal console (marked
-	 * valid for the crash state) has been registered with
-	 * the console framework. To debug crashes that occur
-	 * earlier, the platform has to override these functions
-	 * with an implementation that initializes a console
-	 * driver with hardcoded parameters. See
-	 * docs/porting-guide.rst for more information.
-	 * -----------------------------------------------------
+	/*
+	 * Spinlock to syncronize access to crash_console_triggered. We cannot
+	 * acquire spinlocks when the cache is disabled, so in some cases (like
+	 * late during CPU suspend) some risk remains.
 	 */
-func plat_crash_console_init
-#if defined(IMAGE_BL1)
+.section .data.crash_console_spinlock
+	define_asm_spinlock crash_console_spinlock
+
 	/*
-	 * BL1 code can possibly crash so early that the data segment is not yet
-	 * accessible. Don't risk undefined behavior by trying to run the normal
-	 * console framework. Platforms that want to debug BL1 will need to
-	 * override this with custom functions that can run from registers only.
+	 * Flag to make sure that only one CPU can write a crash dump even if
+	 * multiple crash at the same time. Interleaving crash dumps on the same
+	 * console would just make the output unreadable, so it's better to only
+	 * get a single but uncorrupted dump. This also means that we don't have
+	 * to duplicate the reg_stash below for each CPU.
 	 */
-	mov	x0, #0
-	ret
-#else	/* IMAGE_BL1 */
-	mov	x3, x30
-	mov	x0, #CONSOLE_FLAG_CRASH
-	bl	console_switch_state
+.section .data.crash_console_triggered
+	crash_console_triggered: .byte 0
+
+	/*
+	 * Space to stash away some register values while we're calling into
+	 * console drivers and don't have a real stack available. We need x14,
+	 * x15 and x30 for bookkeeping within the plat_crash_console functions
+	 * themselves, and some console drivers use x16 and x17 as additional
+	 * scratch space that is not preserved by the main crash reporting
+	 * framework. (Note that x16 and x17 should really never be expected to
+	 * retain their values across any function call, even between carefully
+	 * designed assembly functions, since the linker is always free to
+	 * insert a function call veneer that uses these registers as scratch
+	 * space at any time. The current crash reporting framework doesn't
+	 * really respect that, but since TF is usually linked as a single
+	 * contiguous binary of less than 128MB, it seems to work in practice.)
+	 */
+.section .data.crash_console_reg_stash
+	.align 3
+	crash_console_reg_stash: .quad 0, 0, 0, 0, 0
+
+	/* --------------------------------------------------------------------
+	 * int plat_crash_console_init(void)
+	 * Takes the crash console spinlock (if possible) and checks the trigger
+	 * flag to make sure we're the first CPU to dump. If not, return an
+	 * error (so crash dumping will fail but the CPU will still call
+	 * plat_panic_handler() which may do important platform-specific tasks
+	 * that may be needed on all crashing CPUs). In either case, the lock
+	 * will be released so other CPUs can make forward progress on this.
+	 * Clobbers: x0 - x4, x30
+	 * --------------------------------------------------------------------
+	 */
+func plat_crash_console_init
+#if defined(IMAGE_BL31)
+	mov	x4, x30		/* x3 and x4 are not clobbered by spin_lock() */
+	mov	x3, #0		/* return value */
+
+	mrs	x1, sctlr_el3
+	tst	x1, #SCTLR_C_BIT
+	beq	skip_spinlock	/* can't synchronize when cache disabled */
+
+	adrp	x0, crash_console_spinlock
+	add	x0, x0, :lo12:crash_console_spinlock
+	bl	spin_lock
+
+skip_spinlock:
+	adrp	x1, crash_console_triggered
+	add	x1, x1, :lo12:crash_console_triggered
+	ldarb	w2, [x1]
+	cmp	w2, #0
+	bne	init_error
+
+	mov	x3, #1		/* set return value to success */
+	stlrb	w3, [x1]
+
+init_error:
+	bl	spin_unlock	/* harmless if we didn't acquire the lock */
+	mov	x0, x3
+	ret	x4
+#else	/* Only one CPU in BL1/BL2, no need to synchronize anything */
 	mov	x0, #1
-	ret	x3
+	ret
 #endif
 endfunc plat_crash_console_init
 
-	/* -----------------------------------------------------
-	 * void plat_crash_console_putc(int character)
-	 * Output through the normal console by default.
-	 * -----------------------------------------------------
+	/* --------------------------------------------------------------------
+	 * int plat_crash_console_putc(char c)
+	 * Prints the character on all consoles registered with the console
+	 * framework that have CONSOLE_FLAG_CRASH set. Note that this is only
+	 * helpful for crashes that occur after the platform intialization code
+	 * has registered a console. Platforms using this implementation need to
+	 * ensure that all console drivers they use that have the CRASH flag set
+	 * support this (i.e. are written in assembly and comply to the register
+	 * clobber requirements of plat_crash_console_putc().
+	 * --------------------------------------------------------------------
 	 */
 func plat_crash_console_putc
-	b	console_putc
+	adrp	x1, crash_console_reg_stash
+	add	x1, x1, :lo12:crash_console_reg_stash
+	stp	x14, x15, [x1]
+	stp	x16, x17, [x1, #16]
+	str	x30, [x1, #32]
+
+	mov	w14, w0				/* W14 = character to print */
+	adrp	x15, console_list
+	ldr	x15, [x15, :lo12:console_list]	/* X15 = first console struct */
+
+putc_loop:
+	cbz	x15, putc_done
+	ldr	w1, [x15, #CONSOLE_T_FLAGS]
+	tst	w1, #CONSOLE_FLAG_CRASH
+	b.eq	putc_continue
+	ldr	x2, [x15, #CONSOLE_T_PUTC]
+	cbz	x2, putc_continue
+	mov	x1, x15
+	blr	x2
+	mov	w0, w14
+putc_continue:
+	ldr	x15, [x15]			/* X15 = next struct */
+	b	putc_loop
+
+putc_done:
+	adrp	x1, crash_console_reg_stash
+	add	x1, x1, :lo12:crash_console_reg_stash
+	ldp	x14, x15, [x1]
+	ldp	x16, x17, [x1, #16]
+	ldr	x30, [x1, #32]
+	ret
 endfunc plat_crash_console_putc
 
-	/* -----------------------------------------------------
-	 * void plat_crash_console_flush(void)
-	 * Flush normal console by default.
-	 * -----------------------------------------------------
+	/* --------------------------------------------------------------------
+	 * int plat_crash_console_flush(char c)
+	 * Flushes all consoles registered with the console framework that have
+	 * CONSOLE_FLAG_CRASH set. Same requirements as putc().
+	 * --------------------------------------------------------------------
 	 */
 func plat_crash_console_flush
-	b	console_flush
+	adrp	x1, crash_console_reg_stash
+	add	x1, x1, :lo12:crash_console_reg_stash
+	stp	x30, x15, [x1]
+	stp	x16, x17, [x1, #16]
+
+	adrp	x15, console_list
+	ldr	x15, [x15, :lo12:console_list]	/* X15 = first console struct */
+
+flush_loop:
+	cbz	x15, flush_done
+	ldr	w1, [x15, #CONSOLE_T_FLAGS]
+	tst	w1, #CONSOLE_FLAG_CRASH
+	b.eq	flush_continue
+	ldr	x2, [x15, #CONSOLE_T_FLUSH]
+	cbz	x2, flush_continue
+	mov	x0, x15
+	blr	x2
+flush_continue:
+	ldr	x15, [x15]			/* X15 = next struct */
+	b	flush_loop
+
+flush_done:
+	adrp	x1, crash_console_reg_stash
+	add	x1, x1, :lo12:crash_console_reg_stash
+	ldp	x30, x15, [x1]
+	ldp	x16, x17, [x1, #16]
+	ret
 endfunc plat_crash_console_flush