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


Reply via email to