drivers/console: Reimplement MUTLI_CONSOLE_API framework in C

Now that we have switched to using the stack in MULTI_CONSOLE_API
framework functions and have factored all code involved in crash
reporting out into a separate file, there's really no reason to keep the
main framework code in assembly anymore. This patch rewrites it in C
which allows us to have a single implementation across aarch32/64 and
should be much easier to maintain going forward.

Change-Id: I6c85a01e89a79e8b233f3f8bee812f0dbd026221
Signed-off-by: Julius Werner <jwerner@chromium.org>
diff --git a/Makefile b/Makefile
index 63f283f..eed7c7a 100644
--- a/Makefile
+++ b/Makefile
@@ -214,7 +214,7 @@
 BL_COMMON_SOURCES	+=	common/bl_common.c			\
 				common/tf_log.c				\
 				common/${ARCH}/debug.S			\
-				drivers/console/${ARCH}/multi_console.S	\
+				drivers/console/multi_console.c		\
 				lib/${ARCH}/cache_helpers.S		\
 				lib/${ARCH}/misc_helpers.S		\
 				plat/common/plat_bl_common.c		\
diff --git a/drivers/console/aarch32/multi_console.S b/drivers/console/aarch32/multi_console.S
deleted file mode 100644
index 713dc3c..0000000
--- a/drivers/console/aarch32/multi_console.S
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * Copyright (c) 2015-2018, ARM Limited and Contributors. All rights reserved.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
-
-#if MULTI_CONSOLE_API
-
-#include <asm_macros.S>
-#include <assert_macros.S>
-#include <console.h>
-
-	.globl	console_register
-	.globl	console_unregister
-	.globl	console_is_registered
-	.globl	console_set_scope
-	.globl	console_switch_state
-	.globl	console_putc
-	.globl	console_getc
-	.globl	console_flush
-
-	/*
-	 *  The console list pointer is in the data section and not in
-	 *  .bss even though it is zero-init. In particular, this allows
-	 *  the console functions to start using this variable before
-	 *  the runtime memory is initialized for images which do not
-	 *  need to copy the .data section from ROM to RAM.
-	 */
-.section .data.console_list ; .align 2
-	console_list: .word 0x0
-.section .data.console_state ; .align 0
-	console_state: .byte CONSOLE_FLAG_BOOT
-
-	/* -----------------------------------------------
-	 * int console_register(console_t *console)
-	 * Function to insert a new console structure into
-	 * the console list. Should usually be called by
-	 * console_<driver>_register implementations. The
-	 * data structure passed will be taken over by the
-	 * console framework and *MUST* be allocated in
-	 * persistent memory (e.g. the data section).
-	 * In : r0 - address of console_t structure
-	 * Out: r0 - Always 1 (for easier tail calling)
-	 * Clobber list: r0, r1
-	 * -----------------------------------------------
-	 */
-func console_register
-	push	{r6,  lr}
-#if ENABLE_ASSERTIONS
-	/* Assert that r0 isn't a NULL pointer */
-	cmp	r0, #0
-	ASM_ASSERT(ne)
-	/* Assert that the struct isn't in the stack */
-	ldr	r1, =__STACKS_START__
-	cmp	r0, r1
-	blo	not_on_stack
-	ldr	r1, =__STACKS_END__
-	cmp	r0, r1
-	ASM_ASSERT(hs)
-not_on_stack:
-	/* Assert that this struct isn't in the list */
-	mov	r1, r0 /* Preserve r0 and lr */
-	bl	console_is_registered
-	cmp	r0, #0
-	ASM_ASSERT(eq)
-	mov	r0, r1
-#endif /* ENABLE_ASSERTIONS */
-	ldr	r6, =console_list
-	ldr	r1, [r6]	/* R1 = first struct in list */
-	str	r0, [r6]	/* list head = new console */
-	str	r1, [r0, #CONSOLE_T_NEXT]	/* new console next ptr = R1 */
-	mov	r0, #1
-	pop	{r6, pc}
-endfunc console_register
-
-	/* -----------------------------------------------
-	 * int console_unregister(console_t *console)
-	 * Function to find a specific console in the list
-	 * of currently active consoles and remove it.
-	 * In: r0 - address of console_t struct to remove
-	 * Out: r0 - removed address, or NULL if not found
-	 * Clobber list: r0, r1
-	 * -----------------------------------------------
-	 */
-func console_unregister
-#if ENABLE_ASSERTIONS
-	/* Assert that r0 isn't a NULL pointer */
-	cmp	r0, #0
-	ASM_ASSERT(ne)
-#endif /* ENABLE_ASSERTIONS */
-	push	{r6}
-	ldr	r6, =console_list		/* R6 = ptr to first struct */
-	ldr	r1, [r6]			/* R1 = first struct */
-
-unregister_loop:
-	cmp	r1, #0
-	beq	unregister_not_found
-	cmp	r0, r1
-	beq	unregister_found
-	ldr	r6, [r6]			/* R6 = next ptr of struct */
-	ldr	r1, [r6]			/* R1 = next struct */
-	b	unregister_loop
-
-unregister_found:
-	ldr	r1, [r1]			/* R1 = next struct */
-	str	r1, [r6]			/* prev->next = cur->next */
-	pop	{r6}
-	bx	lr
-
-unregister_not_found:
-	mov	r0, #0				/* return NULL if not found */
-	pop	{r6}
-	bx	lr
-endfunc console_unregister
-
-	/* -----------------------------------------------
-	 * int console_is_registered(console_t *console)
-	 * Function to detect if a specific console is
-	 * registered or not.
-	 * In: r0 - address of console_t struct to remove
-	 * Out: r0 - 1 if it is registered, 0 if not.
-	 * Clobber list: r0
-	 * -----------------------------------------------
-	 */
-func console_is_registered
-#if ENABLE_ASSERTIONS
-	/* Assert that r0 isn't a NULL pointer */
-	cmp	r0, #0
-	ASM_ASSERT(ne)
-#endif /* ENABLE_ASSERTIONS */
-	push	{r6}
-	ldr	r6, =console_list
-	ldr	r6, [r6]	/* R6 = first console struct */
-check_registered_loop:
-	cmp	r6, #0			/* Check if end of list */
-	beq	console_not_registered
-	cmp	r0, r6		/* Check if the pointers are different */
-	beq	console_registered
-	ldr	r6, [r6, #CONSOLE_T_NEXT]	/* Get pointer to next struct */
-	b	check_registered_loop
-console_not_registered:
-	mov	r0, #0
-	pop	{r6}
-	bx	lr
-console_registered:
-	mov	r0, #1
-	pop	{r6}
-	bx	lr
-endfunc console_is_registered
-
-	/* -----------------------------------------------
-	 * void console_switch_state(unsigned int new_state)
-	 * Function to switch the current console state.
-	 * The console state determines which of the
-	 * registered consoles are actually used at a time.
-	 * In : r0 - global console state to move to
-	 * Clobber list: r0, r1
-	 * -----------------------------------------------
-	 */
-func console_switch_state
-	ldr	r1, =console_state
-	strb	r0, [r1]
-	bx	lr
-endfunc console_switch_state
-
-	/* -----------------------------------------------
-	 * void console_set_scope(console_t *console,
-	 *                       unsigned int scope)
-	 * Function to update the states that a given console
-	 * may be active in.
-	 * In : r0 - pointer to console_t struct
-	 *    : r1 - new active state mask
-	 * Clobber list: r0, r1, r2
-	 * -----------------------------------------------
-	 */
-func console_set_scope
-#if ENABLE_ASSERTIONS
-	ands	r2, r1, #~CONSOLE_FLAG_SCOPE_MASK
-	ASM_ASSERT(eq)
-#endif /* ENABLE_ASSERTIONS */
-	ldr	r2, [r0, #CONSOLE_T_FLAGS]
-	and	r2, r2, #~CONSOLE_FLAG_SCOPE_MASK
-	orr	r2, r2, r1
-	str	r2, [r0, #CONSOLE_T_FLAGS]
-	bx	lr
-endfunc console_set_scope
-
-	/* ---------------------------------------------
-	 * int console_putc(int c)
-	 * Function to output a character. Calls all
-	 * active console's putc() handlers in succession.
-	 * In : r0 - character to be printed
-	 * Out: r0 - printed character on success, or < 0
-	             if at least one console had an error
-	 * Clobber list : r0, r1, r2
-	 * ---------------------------------------------
-	 */
-func console_putc
-	push	{r4-r6, lr}
-	mov	r5, #ERROR_NO_VALID_CONSOLE	/* R5 = current return value */
-	mov	r4, r0				/* R4 = character to print */
-	ldr	r6, =console_list
-	ldr	r6, [r6]	/* R6 = first console struct */
-
-putc_loop:
-	cmp	r6, #0
-	beq	putc_done
-	ldr	r1, =console_state
-	ldrb	r1, [r1]
-	ldr	r2, [r6, #CONSOLE_T_FLAGS]
-	tst	r1, r2
-	beq	putc_continue
-	ldr	r2, [r6, #CONSOLE_T_PUTC]
-	cmp	r2, #0
-	beq	putc_continue
-	mov	r0, r4
-	mov	r1, r6
-	blx	r2
-	cmp	r5, #ERROR_NO_VALID_CONSOLE	/* update R5 if it's NOVALID */
-	cmpne	r0, #0				/* else update it if R0 < 0 */
-	movlt	r5, r0
-putc_continue:
-	ldr	r6, [r6]			/* R6 = next struct */
-	b	putc_loop
-
-putc_done:
-	mov	r0, r5
-	pop	{r4-r6, pc}
-endfunc console_putc
-
-	/* ---------------------------------------------
-	 * int console_getc(void)
-	 * Function to get a character from any console.
-	 * Keeps looping through all consoles' getc()
-	 * handlers until one of them returns a
-	 * character, then stops iterating and returns
-	 * that character to the caller. Will stop looping
-	 * if all active consoles report real errors
-	 * (other than just not having a char available).
-	 * Out : r0 - read character, or < 0 on error
-	 * Clobber list : r0, r1
-	 * ---------------------------------------------
-	 */
-func console_getc
-	push	{r5-r6, lr}
-getc_try_again:
-	mov	r5, #ERROR_NO_VALID_CONSOLE	/* R5 = current return value */
-	ldr	r6, =console_list
-	ldr	r6, [r6]			/* R6 = first console struct */
-	cmp	r6, #0
-	bne	getc_loop
-	mov	r0, r5				/* If no consoles registered */
-	pop	{r5-r6, pc}			/* return immediately. */
-
-getc_loop:
-	ldr	r0, =console_state
-	ldrb	r0, [r0]
-	ldr	r1, [r6, #CONSOLE_T_FLAGS]
-	tst	r0, r1
-	beq	getc_continue
-	ldr	r1, [r6, #CONSOLE_T_GETC]
-	cmp	r1, #0
-	beq	getc_continue
-	mov	r0, r6
-	blx	r1
-	cmp	r0, #0				/* if R0 >= 0: return */
-	bge	getc_found
-	cmp	r5, #ERROR_NO_PENDING_CHAR	/* may update R5 (NOCHAR has */
-	movne	r5, r0				/* precedence vs real errors) */
-getc_continue:
-	ldr	r6, [r6]			/* R6 = next struct */
-	cmp	r6, #0
-	bne	getc_loop
-	cmp	r5, #ERROR_NO_PENDING_CHAR	/* Keep scanning if at least */
-	beq	getc_try_again			/* one console returns NOCHAR */
-	mov	r0, r5
-
-getc_found:
-	pop	{r5-r6, pc}
-endfunc console_getc
-
-	/* ---------------------------------------------
-	 * int console_flush(void)
-	 * Function to force a write of all buffered
-	 * data that hasn't been output. Calls all
-	 * console's flush() handlers in succession.
-	 * Out: r0 - 0 on success, < 0 if at least one error
-	 * Clobber list : r0, r1, r2
-	 * ---------------------------------------------
-	 */
-func console_flush
-	push	{r5-r6, lr}
-	mov	r5, #ERROR_NO_VALID_CONSOLE	/* R5 = current return value */
-	ldr	r6, =console_list
-	ldr	r6, [r6]			/* R6 = first console struct */
-
-flush_loop:
-	cmp	r6, #0
-	beq	flush_done
-	ldr	r1, =console_state
-	ldrb	r1, [r1]
-	ldr	r2, [r6, #CONSOLE_T_FLAGS]
-	tst	r1, r2
-	beq	flush_continue
-	ldr	r1, [r6, #CONSOLE_T_FLUSH]
-	cmp	r1, #0
-	beq	flush_continue
-	mov	r0, r6
-	blx	r1
-	cmp	r5, #ERROR_NO_VALID_CONSOLE	/* update R5 if it's NOVALID */
-	cmpne	r0, #0				/* else update it if R0 < 0 */
-	movlt	r5, r0
-flush_continue:
-	ldr	r6, [r6]			/* R6 = next struct */
-	b	flush_loop
-
-flush_done:
-	mov	r0, r5
-	pop	{r5-r6, pc}
-endfunc console_flush
-
-#endif	/* MULTI_CONSOLE_API */
diff --git a/drivers/console/aarch64/multi_console.S b/drivers/console/aarch64/multi_console.S
deleted file mode 100644
index 40d500d..0000000
--- a/drivers/console/aarch64/multi_console.S
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (c) 2015-2018, ARM Limited and Contributors. All rights reserved.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
-
-#if MULTI_CONSOLE_API
-
-#include <asm_macros.S>
-#include <assert_macros.S>
-#include <console.h>
-
-	.globl	console_register
-	.globl	console_unregister
-	.globl	console_is_registered
-	.globl	console_set_scope
-	.globl	console_switch_state
-	.globl	console_putc
-	.globl	console_getc
-	.globl	console_flush
-	.globl	console_list
-
-	/*
-	 *  The console list pointer is in the data section and not in
-	 *  .bss even though it is zero-init. In particular, this allows
-	 *  the console functions to start using this variable before
-	 *  the runtime memory is initialized for images which do not
-	 *  need to copy the .data section from ROM to RAM.
-	 */
-.section .data.console_list ; .align 3
-	console_list: .quad 0x0
-.section .data.console_state ; .align 0
-	console_state: .byte CONSOLE_FLAG_BOOT
-
-	/* -----------------------------------------------
-	 * int console_register(console_t *console)
-	 * Function to insert a new console structure into
-	 * the console list. Should usually be called by
-	 * console_<driver>_register implementations. The
-	 * data structure passed will be taken over by the
-	 * console framework and *MUST* be allocated in
-	 * persistent memory (e.g. the data section).
-	 * In : x0 - address of console_t structure
-	 * Out: x0 - Always 1 (for easier tail calling)
-	 * Clobber list: x0, x1
-	 * -----------------------------------------------
-	 */
-func console_register
-	stp	x21, x30, [sp, #-16]!
-#if ENABLE_ASSERTIONS
-	/* Assert that x0 isn't a NULL pointer */
-	cmp	x0, #0
-	ASM_ASSERT(ne)
-	/* Assert that the struct isn't in the stack */
-	adrp	x1, __STACKS_START__
-	add	x1, x1, :lo12:__STACKS_START__
-	cmp	x0, x1
-	b.lo	not_on_stack
-	adrp	x1, __STACKS_END__
-	add	x1, x1, :lo12:__STACKS_END__
-	cmp	x0, x1
-	ASM_ASSERT(hs)
-not_on_stack:
-	/* Assert that this struct isn't in the list */
-	mov	x1, x0 /* Preserve x0 and x30 */
-	bl	console_is_registered
-	cmp	x0, #0
-	ASM_ASSERT(eq)
-	mov	x0, x1
-#endif /* ENABLE_ASSERTIONS */
-	adrp	x21, console_list
-	ldr	x1, [x21, :lo12:console_list]	/* X1 = first struct in list */
-	str	x0, [x21, :lo12:console_list]	/* list head = new console */
-	str	x1, [x0, #CONSOLE_T_NEXT]	/* new console next ptr = X1 */
-	mov	x0, #1
-	ldp	x21, x30, [sp], #16
-	ret
-endfunc console_register
-
-	/* -----------------------------------------------
-	 * int console_unregister(console_t *console)
-	 * Function to find a specific console in the list
-	 * of currently active consoles and remove it.
-	 * In: x0 - address of console_t struct to remove
-	 * Out: x0 - removed address, or NULL if not found
-	 * Clobber list: x0, x1
-	 * -----------------------------------------------
-	 */
-func console_unregister
-#if ENABLE_ASSERTIONS
-	/* Assert that x0 isn't a NULL pointer */
-	cmp	x0, #0
-	ASM_ASSERT(ne)
-#endif /* ENABLE_ASSERTIONS */
-	stp	x21, xzr, [sp, #-16]!
-	adrp	x21, console_list
-	add	x21, x21, :lo12:console_list	/* X21 = ptr to first struct */
-	ldr	x1, [x21]			/* X1 = first struct */
-
-unregister_loop:
-	cbz	x1, unregister_not_found
-	cmp	x0, x1
-	b.eq	unregister_found
-	ldr	x21, [x21]			/* X21 = next ptr of struct */
-	ldr	x1, [x21]			/* X1 = next struct */
-	b	unregister_loop
-
-unregister_found:
-	ldr	x1, [x1]			/* X1 = next struct */
-	str	x1, [x21]			/* prev->next = cur->next */
-	ldp	x21, xzr, [sp], #16
-	ret
-
-unregister_not_found:
-	mov	x0, #0				/* return NULL if not found */
-	ldp	x21, xzr, [sp], #16
-	ret
-endfunc console_unregister
-
-	/* -----------------------------------------------
-	 * int console_is_registered(console_t *console)
-	 * Function to detect if a specific console is
-	 * registered or not.
-	 * In: x0 - address of console_t struct to remove
-	 * Out: x0 - 1 if it is registered, 0 if not.
-	 * Clobber list: x0
-	 * -----------------------------------------------
-	 */
-func console_is_registered
-#if ENABLE_ASSERTIONS
-	/* Assert that x0 isn't a NULL pointer */
-	cmp	x0, #0
-	ASM_ASSERT(ne)
-#endif /* ENABLE_ASSERTIONS */
-	stp	x21, xzr, [sp, #-16]!
-	adrp	x21, console_list
-	ldr	x21, [x21, :lo12:console_list]	/* X21 = first console struct */
-check_registered_loop:
-	cbz	x21, console_not_registered /* Check if end of list */
-	cmp	x0, x21		/* Check if the pointers are different */
-	b.eq	console_registered
-	ldr	x21, [x21, #CONSOLE_T_NEXT]	/* Get pointer to next struct */
-	b	check_registered_loop
-console_not_registered:
-	mov	x0, #0
-	ldp	x21, xzr, [sp], #16
-	ret
-console_registered:
-	mov	x0, #1
-	ldp	x21, xzr, [sp], #16
-	ret
-endfunc console_is_registered
-
-	/* -----------------------------------------------
-	 * void console_switch_state(unsigned int new_state)
-	 * Function to switch the current console state.
-	 * The console state determines which of the
-	 * registered consoles are actually used at a time.
-	 * In : w0 - global console state to move to
-	 * Clobber list: x0, x1
-	 * -----------------------------------------------
-	 */
-func console_switch_state
-	adrp	x1, console_state
-	strb	w0, [x1, :lo12:console_state]
-	ret
-endfunc console_switch_state
-
-	/* -----------------------------------------------
-	 * void console_set_scope(console_t *console,
-	 *                       unsigned int scope)
-	 * Function to update the states that a given console
-	 * may be active in.
-	 * In : x0 - pointer to console_t struct
-	 *    : w1 - new active state mask
-	 * Clobber list: x0, x1, x2
-	 * -----------------------------------------------
-	 */
-func console_set_scope
-#if ENABLE_ASSERTIONS
-	tst	w1, #~CONSOLE_FLAG_SCOPE_MASK
-	ASM_ASSERT(eq)
-#endif /* ENABLE_ASSERTIONS */
-	ldr	w2, [x0, #CONSOLE_T_FLAGS]
-	and	w2, w2, #~CONSOLE_FLAG_SCOPE_MASK
-	orr	w2, w2, w1
-	str	w2, [x0, #CONSOLE_T_FLAGS]
-	ret
-endfunc console_set_scope
-
-	/* ---------------------------------------------
-	 * int console_putc(int c)
-	 * Function to output a character. Calls all
-	 * active console's putc() handlers in succession.
-	 * In : x0 - character to be printed
-	 * Out: x0 - printed character on success, or < 0
-	             if at least one console had an error
-	 * Clobber list : x0, x1, x2
-	 * ---------------------------------------------
-	 */
-func console_putc
-	stp	x21, x30, [sp, #-16]!
-	stp	x19, x20, [sp, #-16]!
-	mov	w20, #ERROR_NO_VALID_CONSOLE	/* W20 = current return value */
-	mov	w19, w0				/* W19 = character to print */
-	adrp	x21, console_list
-	ldr	x21, [x21, :lo12:console_list]	/* X21 = first console struct */
-
-putc_loop:
-	cbz	x21, putc_done
-	adrp	x1, console_state
-	ldrb	w1, [x1, :lo12:console_state]
-	ldr	w2, [x21, #CONSOLE_T_FLAGS]
-	tst	w1, w2
-	b.eq	putc_continue
-	ldr	x2, [x21, #CONSOLE_T_PUTC]
-	cbz	x2, putc_continue
-	mov	w0, w19
-	mov	x1, x21
-	blr	x2
-	cmp	w20, #ERROR_NO_VALID_CONSOLE	/* update W20 if it's NOVALID */
-	ccmp	w0, #0, #0x8, ne		/* else update it if W0 < 0 */
-	csel	w20, w0, w20, lt
-putc_continue:
-	ldr	x21, [x21]			/* X21 = next struct */
-	b	putc_loop
-
-putc_done:
-	mov	w0, w20
-	ldp	x19, x20, [sp], #16
-	ldp	x21, x30, [sp], #16
-	ret
-endfunc console_putc
-
-	/* ---------------------------------------------
-	 * int console_getc(void)
-	 * Function to get a character from any console.
-	 * Keeps looping through all consoles' getc()
-	 * handlers until one of them returns a
-	 * character, then stops iterating and returns
-	 * that character to the caller. Will stop looping
-	 * if all active consoles report real errors
-	 * (other than just not having a char available).
-	 * Out : x0 - read character, or < 0 on error
-	 * Clobber list : x0, x1
-	 * ---------------------------------------------
-	 */
-func console_getc
-	stp	x30, xzr, [sp, #-16]!
-	stp	x20, x21, [sp, #-16]!
-getc_try_again:
-	mov	w20, #ERROR_NO_VALID_CONSOLE	/* W20 = current return value */
-	adrp	x21, console_list
-	ldr	x21, [x21, :lo12:console_list]	/* X21 = first console struct */
-	cbnz	x21, getc_loop
-	mov	w0, w20				/* If no consoles registered */
-	ldp	x20, x21, [sp], #16
-	ldp	x30, xzr, [sp], #16
-	ret					/* return immediately. */
-
-getc_loop:
-	adrp	x0, console_state
-	ldrb	w0, [x0, :lo12:console_state]
-	ldr	w1, [x21, #CONSOLE_T_FLAGS]
-	tst	w0, w1
-	b.eq	getc_continue
-	ldr	x1, [x21, #CONSOLE_T_GETC]
-	cbz	x1, getc_continue
-	mov	x0, x21
-	blr	x1
-	cmp	w0, #0				/* if X0 >= 0: return */
-	b.ge	getc_found
-	cmp	w20, #ERROR_NO_PENDING_CHAR	/* may update W20 (NOCHAR has */
-	csel	w20, w20, w0, eq		/* precedence vs real errors) */
-getc_continue:
-	ldr	x21, [x21]			/* X21 = next struct */
-	cbnz	x21, getc_loop
-	cmp	w20, #ERROR_NO_PENDING_CHAR	/* Keep scanning if at least */
-	b.eq	getc_try_again			/* one console returns NOCHAR */
-	mov	w0, w20
-
-getc_found:
-	ldp	x20, x21, [sp], #16
-	ldp	x30, xzr, [sp], #16
-	ret
-endfunc console_getc
-
-	/* ---------------------------------------------
-	 * int console_flush(void)
-	 * Function to force a write of all buffered
-	 * data that hasn't been output. Calls all
-	 * console's flush() handlers in succession.
-	 * Out: x0 - 0 on success, < 0 if at least one error
-	 * Clobber list : x0, x1, x2
-	 * ---------------------------------------------
-	 */
-func console_flush
-	stp	x30, xzr, [sp, #-16]!
-	stp	x20, x21, [sp, #-16]!
-	mov	w20, #ERROR_NO_VALID_CONSOLE	/* W20 = current return value */
-	adrp	x21, console_list
-	ldr	x21, [x21, :lo12:console_list]	/* X21 = first console struct */
-
-flush_loop:
-	cbz	x21, flush_done
-	adrp	x1, console_state
-	ldrb	w1, [x1, :lo12:console_state]
-	ldr	w2, [x21, #CONSOLE_T_FLAGS]
-	tst	w1, w2
-	b.eq	flush_continue
-	ldr	x1, [x21, #CONSOLE_T_FLUSH]
-	cbz	x1, flush_continue
-	mov	x0, x21
-	blr	x1
-	cmp	w20, #ERROR_NO_VALID_CONSOLE	/* update W20 if it's NOVALID */
-	ccmp	w0, #0, #0x8, ne		/* else update it if W0 < 0 */
-	csel	w20, w0, w20, lt
-flush_continue:
-	ldr	x21, [x21]			/* X21 = next struct */
-	b	flush_loop
-
-flush_done:
-	mov	w0, w20
-	ldp	x20, x21, [sp], #16
-	ldp	x30, xzr, [sp], #16
-	ret
-endfunc console_flush
-
-#endif	/* MULTI_CONSOLE_API */
diff --git a/drivers/console/multi_console.c b/drivers/console/multi_console.c
new file mode 100644
index 0000000..c678de0
--- /dev/null
+++ b/drivers/console/multi_console.c
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2018, ARM Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#if MULTI_CONSOLE_API
+
+#include <assert.h>
+#include <drivers/console.h>
+
+console_t *console_list;
+uint8_t console_state = CONSOLE_FLAG_BOOT;
+
+int console_register(console_t *console)
+{
+	IMPORT_SYM(console_t *, __STACKS_START__, stacks_start)
+	IMPORT_SYM(console_t *, __STACKS_END__, stacks_end)
+
+	/* Assert that the struct is not on the stack (common mistake). */
+	assert((console < stacks_start) || (console >= stacks_end));
+	/* Assert that we won't make a circle in the list. */
+	assert(!console_is_registered(console));
+
+	console->next = console_list;
+	console_list = console;
+
+	/* Return 1 for convenient tail-calling from console_xxx_register(). */
+	return 1;
+}
+
+console_t *console_unregister(console_t *to_be_deleted)
+{
+	console_t **ptr;
+
+	assert(to_be_deleted != NULL);
+
+	for (ptr = &console_list; *ptr != NULL; ptr = &(*ptr)->next)
+		if (*ptr == to_be_deleted) {
+			*ptr = (*ptr)->next;
+			return to_be_deleted;
+		}
+
+	return NULL;
+}
+
+int console_is_registered(console_t *to_find)
+{
+	console_t *console;
+
+	assert(to_find != NULL);
+
+	for (console = console_list; console != NULL; console = console->next)
+		if (console == to_find)
+			return 1;
+
+	return 0;
+}
+
+void console_switch_state(unsigned int new_state)
+{
+	console_state = new_state;
+}
+
+void console_set_scope(console_t *console, unsigned int scope)
+{
+	assert(console != NULL);
+
+	console->flags = (console->flags & ~CONSOLE_FLAG_SCOPE_MASK) | scope;
+}
+
+int console_putc(int c)
+{
+	int err = ERROR_NO_VALID_CONSOLE;
+	console_t *console;
+
+	for (console = console_list; console != NULL; console = console->next)
+		if (console->flags & console_state) {
+			int ret = console->putc(c, console);
+			if ((err == ERROR_NO_VALID_CONSOLE) || (ret < err))
+				err = ret;
+		}
+
+	return err;
+}
+
+int console_getc(void)
+{
+	int err = ERROR_NO_VALID_CONSOLE;
+	console_t *console;
+
+	do {	/* Keep polling while at least one console works correctly. */
+		for (console = console_list; console != NULL;
+		     console = console->next)
+			if (console->flags & console_state) {
+				int ret = console->getc(console);
+				if (ret >= 0)
+					return ret;
+				if (err != ERROR_NO_PENDING_CHAR)
+					err = ret;
+			}
+	} while (err == ERROR_NO_PENDING_CHAR);
+
+	return err;
+}
+
+int console_flush(void)
+{
+	int err = ERROR_NO_VALID_CONSOLE;
+	console_t *console;
+
+	for (console = console_list; console != NULL; console = console->next)
+		if (console->flags & console_state) {
+			int ret = console->flush(console);
+			if ((err == ERROR_NO_VALID_CONSOLE) || (ret < err))
+				err = ret;
+		}
+
+	return err;
+}
+
+#endif	/* MULTI_CONSOLE_API */