Skip to content

Commit

Permalink
[platform][arm-qemu] fix issue with uart driver on KVM
Browse files Browse the repository at this point in the history
The accessing method the compiler is emitting for the *REG32 macros on
arm32 and arm64 is occasionally generating load/stores with writeback.
Though this has worked before, it seems to be rejected with whatever
combination of qemu + linux + hardware on this Raspberry Pi 5.

Convert the register accessors to inline asm that uses basic load/store
instructions, which is really the only correct thing to do now and in
the long run. Add a TODO to move this to reg.h and start to revamp how
registers are accessed across LK, but for now keep it just here to fix
things.
  • Loading branch information
travisg committed May 10, 2024
1 parent d54735c commit 3c71b66
Showing 1 changed file with 61 additions and 19 deletions.
80 changes: 61 additions & 19 deletions platform/qemu-virt-arm/uart.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,55 @@
#define UART_ICR (0x44)
#define UART_DMACR (0x48)

#define UARTREG(base, reg) (*REG32((base) + (reg)))

#define RXBUF_SIZE 16
#define NUM_UART 1

static cbuf_t uart_rx_buf[NUM_UART];

// ARM & ARM64 specific register accessors that use specific instructions to avoid
// trapping into a virtual machine.
// TODO: make a generic version of this since trapping nonstandard instructions is
// a general problem while running under a VM.
#if __aarch64__
static inline uint32_t __arm64_read_reg32(uint32_t *addr) {
uint32_t val;
__asm__ volatile("ldr %w0, %1" : "=r"(val) : "m"(*addr) : "memory");
return val;
}

static inline void __arm64_write_reg32(uint32_t *addr, uint32_t val) {
__asm__ volatile("str %w0, %1" :: "r"(val), "m"(*addr) : "memory");
}
#elif __arm__
static inline uint32_t __arm64_read_reg32(uint32_t *addr) {
uint32_t val;
__asm__ volatile("ldr %0, %1" : "=r"(val) : "m"(*addr) : "memory");
return val;
}

static inline void __arm64_write_reg32(uint32_t *addr, uint32_t val) {
__asm__ volatile("str %0, %1" :: "r"(val), "m"(*addr) : "memory");
}
#else
#error need for this arch
#endif

static inline void write_uart_reg(uintptr_t base, size_t offset, uint32_t val) {
__arm64_write_reg32((uint32_t *)(base + offset), val);
}

static inline uint32_t read_uart_reg(uintptr_t base, size_t offset) {
return __arm64_read_reg32((uint32_t *)(base + offset));
}

static inline void set_uart_reg_bits(uintptr_t base, size_t offset, uint32_t bits) {
write_uart_reg(base, offset, read_uart_reg(base, offset) | bits);
}

static inline void clear_uart_reg_bits(uintptr_t base, size_t offset, uint32_t bits) {
write_uart_reg(base, offset, read_uart_reg(base, offset) & ~bits);
}

static inline uintptr_t uart_to_ptr(unsigned int n) {
switch (n) {
default:
Expand All @@ -53,27 +95,27 @@ static enum handler_return uart_irq(void *arg) {
uintptr_t base = uart_to_ptr(port);

/* read interrupt status and mask */
uint32_t isr = UARTREG(base, UART_TMIS);
uint32_t isr = read_uart_reg(base, UART_TMIS);

if (isr & (1<<4)) { // rxmis
cbuf_t *rxbuf = &uart_rx_buf[port];

/* while fifo is not empty, read chars out of it */
while ((UARTREG(base, UART_TFR) & (1<<4)) == 0) {
while ((read_uart_reg(base, UART_TFR) & (1<<4)) == 0) {
#if CONSOLE_HAS_INPUT_BUFFER
if (port == DEBUG_UART) {
char c = UARTREG(base, UART_DR);
char c = read_uart_reg(base, UART_DR);
cbuf_write_char(&console_input_cbuf, c, false);
} else
#endif
{
/* if we're out of rx buffer, mask the irq instead of handling it */
if (cbuf_space_avail(rxbuf) == 0) {
UARTREG(base, UART_IMSC) &= ~(1<<4); // !rxim
clear_uart_reg_bits(base, UART_IMSC, (1<<4)); // !rxim
break;
}

char c = UARTREG(base, UART_DR);
char c = read_uart_reg(base, UART_DR);
cbuf_write_char(rxbuf, c, false);
}

Expand All @@ -95,16 +137,16 @@ void uart_init(void) {
register_int_handler(UART0_INT + i, &uart_irq, (void *)i);

// clear all irqs
UARTREG(base, UART_ICR) = 0x3ff;
write_uart_reg(base, UART_ICR, 0x3ff);

// set fifo trigger level
UARTREG(base, UART_IFLS) = 0; // 1/8 rxfifo, 1/8 txfifo
write_uart_reg(base, UART_IFLS, 0); // 1/8 rxfifo, 1/8 txfifo

// enable rx interrupt
UARTREG(base, UART_IMSC) = (1<<4); // rxim
write_uart_reg(base, UART_IMSC, 1<<4); // rxim

// enable receive
UARTREG(base, UART_CR) |= (1<<9); // rxen
set_uart_reg_bits(base, UART_CR, (1<<9)); // rxen

// enable interrupt
unmask_interrupt(UART0_INT + i);
Expand All @@ -113,17 +155,17 @@ void uart_init(void) {

void uart_init_early(void) {
for (size_t i = 0; i < NUM_UART; i++) {
UARTREG(uart_to_ptr(i), UART_CR) = (1<<8)|(1<<0); // tx_enable, uarten
write_uart_reg(uart_to_ptr(i), UART_CR, (1<<8)|(1<<0)); // tx_enable, uarten
}
}

int uart_putc(int port, char c) {
uintptr_t base = uart_to_ptr(port);

/* spin while fifo is full */
while (UARTREG(base, UART_TFR) & (1<<5))
while (read_uart_reg(base, UART_TFR) & (1<<5))
;
UARTREG(base, UART_DR) = c;
write_uart_reg(base, UART_DR, c);

return 1;
}
Expand All @@ -133,7 +175,7 @@ int uart_getc(int port, bool wait) {

char c;
if (cbuf_read_char(rxbuf, &c, wait) == 1) {
UARTREG(uart_to_ptr(port), UART_IMSC) = (1<<4); // rxim
write_uart_reg(uart_to_ptr(port), UART_IMSC, (1<<4)); // rxim
return c;
}

Expand All @@ -145,18 +187,18 @@ int uart_pputc(int port, char c) {
uintptr_t base = uart_to_ptr(port);

/* spin while fifo is full */
while (UARTREG(base, UART_TFR) & (1<<5))
while (read_uart_reg(base, UART_TFR) & (1<<5))
;
UARTREG(base, UART_DR) = c;
write_uart_reg(base, UART_DR, c);

return 1;
}

int uart_pgetc(int port) {
uintptr_t base = uart_to_ptr(port);

if ((UARTREG(base, UART_TFR) & (1<<4)) == 0) {
return UARTREG(base, UART_DR);
if ((read_uart_reg(base, UART_TFR) & (1<<4)) == 0) {
return read_uart_reg(base, UART_DR);
} else {
return -1;
}
Expand Down

0 comments on commit 3c71b66

Please sign in to comment.