Official STM32CubeL4 drivers use and require support for 16-bit writes to UART registers for proper function.
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2540 Signed-off-by: Jacob Whitaker Abrams <[email protected]> --- hw/char/stm32l4x5_usart.c | 93 +++++++++++++++++++++++------- tests/qtest/stm32l4x5_usart-test.c | 50 +++++++++++++++- 2 files changed, 120 insertions(+), 23 deletions(-) diff --git a/hw/char/stm32l4x5_usart.c b/hw/char/stm32l4x5_usart.c index dd1b099195..88c4a3b5a7 100644 --- a/hw/char/stm32l4x5_usart.c +++ b/hw/char/stm32l4x5_usart.c @@ -154,8 +154,15 @@ REG32(RDR, 0x24) REG32(TDR, 0x28) FIELD(TDR, TDR, 0, 9) +#define ISR_RESET_VALUE (0x020000C0) + static void stm32l4x5_update_isr(Stm32l4x5UsartBaseState *s) { + if (!(s->cr1 & R_CR1_UE_MASK)) { + s->isr = ISR_RESET_VALUE; + return; + } + if (s->cr1 & R_CR1_TE_MASK) { s->isr |= R_ISR_TEACK_MASK; } else { @@ -404,9 +411,11 @@ static uint64_t stm32l4x5_usart_base_read(void *opaque, hwaddr addr, unsigned int size) { Stm32l4x5UsartBaseState *s = opaque; + hwaddr base = addr & ~0x3ULL; + unsigned int offset = addr & 0x3; uint64_t retvalue = 0; - switch (addr) { + switch (base) { case A_CR1: retvalue = s->cr1; break; @@ -451,6 +460,13 @@ static uint64_t stm32l4x5_usart_base_read(void *opaque, hwaddr addr, break; } + /* Adjust for partial access */ + if (size == 1) { + retvalue = (retvalue >> (offset * 8)) & 0xFF; + } else if (size == 2) { + retvalue = (retvalue >> (offset * 8)) & 0xFFFF; + } + trace_stm32l4x5_usart_read(addr, retvalue); return retvalue; @@ -460,55 +476,88 @@ static void stm32l4x5_usart_base_write(void *opaque, hwaddr addr, uint64_t val64, unsigned int size) { Stm32l4x5UsartBaseState *s = opaque; - const uint32_t value = val64; + hwaddr base = addr & ~0x3ULL; + unsigned int offset = addr & 0x3; + uint32_t value = (uint32_t)val64; + + /* Build mask for partial access */ + uint32_t mask; + if (size == 4) { + mask = 0xFFFFFFFF; + } else if (size == 2) { + mask = 0xFFFF << (offset * 8); + } else if (size == 1) { + mask = 0xFF << (offset * 8); + } else { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Unsupported access size %u\n", __func__, size); + return; + } + value = (value << (offset * 8)) & mask; trace_stm32l4x5_usart_write(addr, value); - switch (addr) { - case A_CR1: - s->cr1 = value; + switch (base) { + case A_CR1: { + uint32_t old = s->cr1; + s->cr1 = (old & ~mask) | value; stm32l4x5_update_params(s); stm32l4x5_update_isr(s); stm32l4x5_update_irq(s); return; - case A_CR2: - s->cr2 = value; + } + case A_CR2: { + uint32_t old = s->cr2; + s->cr2 = (old & ~mask) | value; stm32l4x5_update_params(s); return; - case A_CR3: - s->cr3 = value; + } + case A_CR3: { + uint32_t old = s->cr3; + s->cr3 = (old & ~mask) | value; return; - case A_BRR: - s->brr = value; + } + case A_BRR: { + uint32_t old = s->brr; + s->brr = (old & ~mask) | value; stm32l4x5_update_params(s); return; - case A_GTPR: - s->gtpr = value; + } + case A_GTPR: { + uint32_t old = s->gtpr; + s->gtpr = (old & ~mask) | value; return; - case A_RTOR: - s->rtor = value; + } + case A_RTOR: { + uint32_t old = s->rtor; + s->rtor = (old & ~mask) | value; return; - case A_RQR: + } + case A_RQR: { + /* RQR is write-only, assume full 32-bit access */ usart_update_rqr(s, value); return; + } case A_ISR: qemu_log_mask(LOG_GUEST_ERROR, "%s: ISR is read only !\n", __func__); return; - case A_ICR: - /* Clear the status flags */ + case A_ICR: { + /* Clear flags: value is masked to written bytes */ s->isr &= ~value; stm32l4x5_update_irq(s); return; + } case A_RDR: qemu_log_mask(LOG_GUEST_ERROR, "%s: RDR is read only !\n", __func__); return; - case A_TDR: - s->tdr = value; + case A_TDR: { + uint32_t old = s->tdr; + s->tdr = (old & ~mask) | value; s->isr &= ~R_ISR_TXE_MASK; usart_transmit(NULL, G_IO_OUT, s); return; + } default: qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr); @@ -521,12 +570,12 @@ static const MemoryRegionOps stm32l4x5_usart_base_ops = { .endianness = DEVICE_NATIVE_ENDIAN, .valid = { .max_access_size = 4, - .min_access_size = 4, + .min_access_size = 2, .unaligned = false }, .impl = { .max_access_size = 4, - .min_access_size = 4, + .min_access_size = 2, .unaligned = false }, }; diff --git a/tests/qtest/stm32l4x5_usart-test.c b/tests/qtest/stm32l4x5_usart-test.c index a72c5a685d..86b36915ac 100644 --- a/tests/qtest/stm32l4x5_usart-test.c +++ b/tests/qtest/stm32l4x5_usart-test.c @@ -332,6 +332,53 @@ static void test_ack(void) qtest_quit(qts); } +static void test_isr_reset(void) +{ + QTestState *qts = qtest_init("-M b-l475e-iot01a"); + init_uart(qts); + /* ISR should show TEACK/REACK after UART init */ + uint32_t isr = qtest_readl(qts, USART1_BASE_ADDR + A_ISR); + g_assert_true(isr & R_ISR_TEACK_MASK); + g_assert_true(isr & R_ISR_REACK_MASK); + + /* Clear UE bit in CR1 -> USART disabled */ + uint32_t cr1 = qtest_readl(qts, USART1_BASE_ADDR + A_CR1); + qtest_writel(qts, USART1_BASE_ADDR + A_CR1, cr1 & ~R_CR1_UE_MASK); + /* ISR must reset to reset value 0x020000C0 when UE is cleared */ + isr = qtest_readl(qts, USART1_BASE_ADDR + A_ISR); + g_assert_cmpuint(isr, ==, 0x020000C0); + + qtest_quit(qts); +} + +static void test_16bit_access(void) +{ + QTestState *qts = qtest_init("-M b-l475e-iot01a"); + + /* Test 16-bit write/read on RTOR */ + qtest_writel(qts, USART1_BASE_ADDR + A_RTOR, 0x00000000); + qtest_writew(qts, USART1_BASE_ADDR + A_RTOR, 0xABCD); + uint16_t rtor16 = qtest_readw(qts, USART1_BASE_ADDR + A_RTOR); + g_assert_cmpuint(rtor16, ==, 0xABCD); + uint32_t rtor32 = qtest_readl(qts, USART1_BASE_ADDR + A_RTOR); + g_assert_cmpuint(rtor32, ==, 0x0000ABCD); + + /* Test upper half 16-bit write */ + qtest_writew(qts, USART1_BASE_ADDR + A_RTOR + 2, 0x1234); + rtor32 = qtest_readl(qts, USART1_BASE_ADDR + A_RTOR); + g_assert_cmpuint(rtor32, ==, 0x1234ABCD); + + /* Test 16-bit write/read on CR1 */ + qtest_writel(qts, USART1_BASE_ADDR + A_CR1, 0x00000000); + qtest_writew(qts, USART1_BASE_ADDR + A_CR1, 0x00FF); + uint16_t cr116 = qtest_readw(qts, USART1_BASE_ADDR + A_CR1); + g_assert_cmpuint(cr116, ==, 0x00FF); + uint32_t cr132 = qtest_readl(qts, USART1_BASE_ADDR + A_CR1); + g_assert_cmpuint(cr132, ==, 0x000000FF); + + qtest_quit(qts); +} + static void check_clock(QTestState *qts, const char *path, uint32_t rcc_reg, uint32_t reg_offset) { @@ -369,7 +416,8 @@ int main(int argc, char **argv) qtest_add_func("stm32l4x5/usart/receive_str", test_receive_str); qtest_add_func("stm32l4x5/usart/send_str", test_send_str); qtest_add_func("stm32l4x5/usart/ack", test_ack); + qtest_add_func("stm32l4x5/usart/isr_reset", test_isr_reset); + qtest_add_func("stm32l4x5/usart/16bit_access", test_16bit_access); qtest_add_func("stm32l4x5/usart/clock_enable", test_clock_enable); return g_test_run(); } - -- 2.43.0
