• Julius Werner's avatar
    plat/common/crash_console_helpers.S: Fix MULTI_CONSOLE_API support · 63c52d00
    Julius Werner authored
    Crash reporting via the default consoles registered by MULTI_CONSOLE_API
    has been broken since commit d35cc347
    
     (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: default avatarJulius Werner <jwerner@chromium.org>
    63c52d00
crash_console_helpers.S 5.94 KB
/*
 * Copyright (c) 2018, ARM Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

/*
 * If a platform wishes to use the functions in this file it has to be added to
 * the Makefile of the platform. It is not included in the common Makefile.
 */

#include <asm_macros.S>
#include <console.h>

	.globl	plat_crash_console_init
	.globl	plat_crash_console_putc
	.globl	plat_crash_console_flush

#if !MULTI_CONSOLE_API
#error "This crash console implementation only works with the MULTI_CONSOLE_API!"
#endif

	/*
	 * 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.
	 */
.section .data.crash_console_spinlock
	define_asm_spinlock crash_console_spinlock

	/*
	 * 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.
	 */
.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
#endif
endfunc plat_crash_console_init

	/* --------------------------------------------------------------------
	 * 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
	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

	/* --------------------------------------------------------------------
	 * 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
	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