On Mon, 2 Sept 2024 at 14:38, Jacob Abrams <satur9n...@gmail.com> wrote:
>
> These changes allow the official STM32L4xx HAL UART driver to function
> properly with the b-l475e-iot01a machine.
>
> Modifying USART_CR1 TE bit should alter USART_ISR TEACK bit, and
> likewise for RE and REACK bit.
>
> USART registers may be accessed via 16-bit instructions.
>
> Reseting USART_CR1 UE bit should restore ISR to default value.
>
> Fixes: 87b77e6e01ca ("hw/char/stm32l4x5_usart: Enable serial read and write")
> Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2540
> Signed-off-by: Jacob Abrams <satur9n...@gmail.com>

Thanks for this patch. I have one question below, and one
minor nit.

> ---
>  hw/char/stm32l4x5_usart.c          | 29 +++++++++++++++++++---
>  tests/qtest/stm32l4x5_usart-test.c | 39 +++++++++++++++++++++++++++++-
>  2 files changed, 64 insertions(+), 4 deletions(-)
>
> diff --git a/hw/char/stm32l4x5_usart.c b/hw/char/stm32l4x5_usart.c
> index fc5dcac0c4..859fc6236a 100644
> --- a/hw/char/stm32l4x5_usart.c
> +++ b/hw/char/stm32l4x5_usart.c
> @@ -154,6 +154,28 @@ 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 {
> +        s->isr &= ~R_ISR_TEACK_MASK;
> +    }
> +
> +    if (s->cr1 & R_CR1_RE_MASK) {
> +        s->isr |= R_ISR_REACK_MASK;
> +    } else {
> +        s->isr &= ~R_ISR_REACK_MASK;
> +    }
> +}

Should we be doing these things based on the value of
the CR1 bits (as this code does), or instead do them
when the bit changes from 0 to 1 (or 1 to 0)?
The wording in the datasheet seems unclear to me; my
impression is that hardware designers often like to
do these things on transitions rather than level based,
but of course you can design h/w both ways...

I guess it could be tested on real hardware:
 * write CR1.TE = 1
 * wait for ISR.TEACK = 1
 * write ISR.TEACK = 0
 * write CR1.TE = 1 (i.e. write again to the register
   without changing its value)
 * does ISR.TEACK go to 1 again, or does it stay 0?

> +
>  static void stm32l4x5_update_irq(Stm32l4x5UsartBaseState *s)
>  {
>      if (((s->isr & R_ISR_WUF_MASK) && (s->cr3 & R_CR3_WUFIE_MASK))        ||
> @@ -367,7 +389,7 @@ static void stm32l4x5_usart_base_reset_hold(Object *obj, 
> ResetType type)
>      s->brr = 0x00000000;
>      s->gtpr = 0x00000000;
>      s->rtor = 0x00000000;
> -    s->isr = 0x020000C0;
> +    s->isr = ISR_RESET_VALUE;
>      s->rdr = 0x00000000;
>      s->tdr = 0x00000000;
>
> @@ -456,6 +478,7 @@ static void stm32l4x5_usart_base_write(void *opaque, 
> hwaddr addr,
>      case A_CR1:
>          s->cr1 = value;
>          stm32l4x5_update_params(s);
> +        stm32l4x5_update_isr(s);
>          stm32l4x5_update_irq(s);
>          return;
>      case A_CR2:
> @@ -508,12 +531,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
>      },

The effect of these is that a 16-bit write not aligned
to a (4-aligned) register offset will generate a GUEST_ERROR
logged message, and a 16-bit write aligned to a 4-aligned
register offset will write the value zero-extended to 32 bits.
That seems reasonable to me.

>  };
> diff --git a/tests/qtest/stm32l4x5_usart-test.c 
> b/tests/qtest/stm32l4x5_usart-test.c
> index 8902518233..ef886074c0 100644
> --- a/tests/qtest/stm32l4x5_usart-test.c
> +++ b/tests/qtest/stm32l4x5_usart-test.c
> @@ -36,6 +36,8 @@ REG32(GTPR, 0x10)
>  REG32(RTOR, 0x14)
>  REG32(RQR, 0x18)
>  REG32(ISR, 0x1C)
> +    FIELD(ISR, REACK, 22, 1)
> +    FIELD(ISR, TEACK, 21, 1)
>      FIELD(ISR, TXE, 7, 1)
>      FIELD(ISR, RXNE, 5, 1)
>      FIELD(ISR, ORE, 3, 1)
> @@ -191,7 +193,7 @@ static void init_uart(QTestState *qts)
>
>      /* Enable the transmitter, the receiver and the USART. */
>      qtest_writel(qts, (USART1_BASE_ADDR + A_CR1),
> -        R_CR1_UE_MASK | R_CR1_RE_MASK | R_CR1_TE_MASK);
> +        cr1 | R_CR1_UE_MASK | R_CR1_RE_MASK | R_CR1_TE_MASK);
>  }
>
>  static void test_write_read(void)
> @@ -202,6 +204,11 @@ static void test_write_read(void)
>      qtest_writel(qts, USART1_BASE_ADDR + A_TDR, 0xFFFFFFFF);
>      const uint32_t tdr = qtest_readl(qts, USART1_BASE_ADDR + A_TDR);
>      g_assert_cmpuint(tdr, ==, 0x000001FF);
> +
> +    /* Official STM HAL uses uint16_t for TDR */
> +    qtest_writew(qts, USART1_BASE_ADDR + A_TDR, 0xFFFF);
> +    const uint16_t tdr16 = qtest_readw(qts, USART1_BASE_ADDR + A_TDR);
> +    g_assert_cmpuint(tdr16, ==, 0x000001FF);
>  }
>
>  static void test_receive_char(void)
> @@ -296,6 +303,35 @@ static void test_send_str(void)
>      qtest_quit(qts);
>  }
>
> +static void test_ack(void)
> +{
> +    uint32_t cr1;
> +    uint32_t isr;
> +    QTestState *qts = qtest_init("-M b-l475e-iot01a");
> +
> +    init_uart(qts);
> +
> +    cr1 = qtest_readl(qts, (USART1_BASE_ADDR + A_CR1));
> +
> +    /* Disable the transmitter and receiver. */
> +    qtest_writel(qts, (USART1_BASE_ADDR + A_CR1),
> +        cr1 & ~(R_CR1_RE_MASK | R_CR1_TE_MASK));
> +
> +    /* Test ISR ACK for transmitter and receiver disabled */
> +    isr = qtest_readl(qts, (USART1_BASE_ADDR + A_ISR));
> +    g_assert_false(isr & R_ISR_TEACK_MASK);
> +    g_assert_false(isr & R_ISR_REACK_MASK);
> +
> +    /* Enable the transmitter and receiver. */
> +    qtest_writel(qts, (USART1_BASE_ADDR + A_CR1),
> +        cr1 | (R_CR1_RE_MASK | R_CR1_TE_MASK));
> +
> +    /* Test ISR ACK for transmitter and receiver disabled */
> +    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);

This is missing a
       qtest_quit(qts);
at the end of the function. Without it, on non-Linux
hosts the QEMU process-under-tests will not be properly
killed. We were also missing one at the end of
test_write_read() in this file, which we just fixed
this week in commit d1e8bea9c9c186.

> +}
> +
>  int main(int argc, char **argv)
>  {
>      int ret;
> @@ -308,6 +344,7 @@ int main(int argc, char **argv)
>      qtest_add_func("stm32l4x5/usart/send_char", test_send_char);
>      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);
>      ret = g_test_run();
>
>      return ret;
> --
> 2.43.0

thanks
-- PMM

Reply via email to