blob: c76108a1a0c4a2acfb4cb7fad67765f2301e52b0 [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Stand-alone header for basic debug output on the PL011 UART. To use it,
* ensure that CONFIG_KVM_ARM_HYP_DEBUG_UART is enabled and that
* CONFIG_KVM_ARM_HYP_DEBUG_UART_ADDR is the physical address of the PL011
* UART that you want to use. Then just include this header and try not to
* vomit at the state of the macros and functions it provides.
*
* The C functions only work when the MMU is enabled, but the assembly macros
* should work pretty much everywhere.
*
* It's slow and racy, but you'll be fine. Patches unwelcome.
*/
#ifndef __ARM64_KVM_HYP_DEBUG_PL011_H__
#define __ARM64_KVM_HYP_DEBUG_PL011_H__
#ifdef CONFIG_KVM_ARM_HYP_DEBUG_UART
#define HYP_PL011_BASE_PHYS CONFIG_KVM_ARM_HYP_DEBUG_UART_ADDR
#define HYP_PL011_UARTFR 0x18
#define HYP_PL011_UARTFR_BUSY 3
#define HYP_PL011_UARTFR_FULL 5
#ifdef __ASSEMBLY__
.macro hyp_pl011_base, tmp
mrs \tmp, sctlr_el2
tbz \tmp, #0, 9990f
isb
alternative_cb kvm_hyp_debug_uart_set_basep
movz \tmp, #0
movk \tmp, #0, lsl #16
movk \tmp, #0, lsl #32
movk \tmp, #0, lsl #48
alternative_cb_end
kern_hyp_va \tmp
ldr \tmp, [\tmp]
b 9991f
9990: mov \tmp, HYP_PL011_BASE_PHYS
9991:
.endm
/*
* 'c' is a W register containing the character to transmit. Preserved.
* 'tmpnr' is the number of another scratch register. Clobbered.
*/
.macro hyp_putc, c, tmpnr
9992: hyp_pl011_base x\tmpnr
ldr w\tmpnr, [x\tmpnr, HYP_PL011_UARTFR]
tbnz w\tmpnr, HYP_PL011_UARTFR_FULL, 9992b
hyp_pl011_base x\tmpnr
str \c, [x\tmpnr]
9992: hyp_pl011_base x\tmpnr
ldr w\tmpnr, [x\tmpnr, HYP_PL011_UARTFR]
tbnz w\tmpnr, HYP_PL011_UARTFR_BUSY, 9992b
.endm
/*
* 's' is an X register containing the address of the string to print.
* 'tmpnr1' and 'tmpnr2' are numbers of other scratch registers.
* All three registers clobbered.
*
* The string must be mapped, so it's best to use a combination of '.ascii'
* and PC-relative addressing (i.e. an ADR instruction)
*/
.macro hyp_puts, s, tmpnr1, tmpnr2
9993: ldrb w\tmpnr1, [\s]
cbz w\tmpnr1, 9993f
hyp_putc w\tmpnr1, \tmpnr2
add \s, \s, #1
b 9993b
9993: mov w\tmpnr1, '\n'
hyp_putc w\tmpnr1, \tmpnr2
.endm
.macro __hyp_putx4, xnr, tmpnr
bic x\xnr, x\xnr, #0xfffffff0
sub w\tmpnr, w\xnr, #10
tbnz w\tmpnr, #31, 9994f
add x\xnr, x\xnr, #0x27
9994: add x\xnr, x\xnr, #0x30
hyp_putc w\xnr, \tmpnr
.endm
/*
* 'x' is an X register containing a value to printed in hex. Preserved.
* 'tmpnr1' and 'tmpnr2' are numbers of other scratch registers. Clobbered.
*/
.macro hyp_putx64, x, tmpnr1, tmpnr2
mov w\tmpnr1, '0'
hyp_putc w\tmpnr1, \tmpnr2
mov w\tmpnr1, 'x'
hyp_putc w\tmpnr1, \tmpnr2
movz x\tmpnr1, #15, lsl #32
9995: bfxil x\tmpnr1, \x, #60, #4
ror \x, \x, #60
__hyp_putx4 \tmpnr1, \tmpnr2
ror x\tmpnr1, x\tmpnr1, #32
cbz w\tmpnr1, 9995f
sub x\tmpnr1, x\tmpnr1, #1
ror x\tmpnr1, x\tmpnr1, #32
b 9995b
9995: mov w\tmpnr1, '\n'
hyp_putc w\tmpnr1, \tmpnr2
.endm
#else
static inline void *__hyp_pl011_base(void)
{
unsigned long ioaddr;
asm volatile(ALTERNATIVE_CB(
"movz %0, #0\n"
"movk %0, #0, lsl #16\n"
"movk %0, #0, lsl #32\n"
"movk %0, #0, lsl #48",
kvm_hyp_debug_uart_set_basep)
: "=r" (ioaddr));
return *((void **)kern_hyp_va(ioaddr));
}
static inline unsigned int __hyp_readw(void *ioaddr)
{
unsigned int val;
asm volatile("ldr %w0, [%1]" : "=r" (val) : "r" (ioaddr));
return val;
}
static inline void __hyp_writew(unsigned int val, void *ioaddr)
{
asm volatile("str %w0, [%1]" : : "r" (val), "r" (ioaddr));
}
static inline void hyp_putc(char c)
{
unsigned int val;
void *base = __hyp_pl011_base();
do {
val = __hyp_readw(base + HYP_PL011_UARTFR);
} while (val & (1U << HYP_PL011_UARTFR_FULL));
__hyp_writew(c, base);
do {
val = __hyp_readw(base + HYP_PL011_UARTFR);
} while (val & (1U << HYP_PL011_UARTFR_BUSY));
}
/*
* Caller needs to ensure string is mapped. If it lives in .rodata, you should
* be good as long as we're using PC-relative addressing (probably true).
*/
static inline void hyp_puts(char *s)
{
while (*s)
hyp_putc(*s++);
hyp_putc('\n');
}
static inline void __hyp_putx4(unsigned int x)
{
x &= 0xf;
if (x <= 9)
x += '0';
else
x += ('a' - 0xa);
hyp_putc(x);
}
static inline void __hyp_putx4n(unsigned long x, int n)
{
int i = n >> 2;
hyp_putc('0');
hyp_putc('x');
while (i--)
__hyp_putx4(x >> (4 * i));
hyp_putc('\n');
}
static inline void hyp_putx32(unsigned int x)
{
__hyp_putx4n(x, 32);
}
static inline void hyp_putx64(unsigned long x)
{
__hyp_putx4n(x, 64);
}
#endif
#else
#warning "Please don't include debug-pl011.h if you're not debugging"
#ifdef __ASSEMBLY__
.macro hyp_putc, c, tmpnr
.endm
.macro hyp_puts, s, tmpnr1, tmpnr2
.endm
.macro hyp_putx64, x, tmpnr1, tmpnr2
.endm
#else
static inline void hyp_putc(char c) { }
static inline void hyp_puts(char *s) { }
static inline void hyp_putx32(unsigned int x) { }
static inline void hyp_putx64(unsigned long x) { }
#endif
#endif /* CONFIG_KVM_ARM_HYP_DEBUG_UART */
#endif /* __ARM64_KVM_HYP_DEBUG_PL011_H__ */