On Sat, 10 May 2025 at 07:57, <jc...@duck.com> wrote:
>
> This patch implements UART support for the MAX78000 SOC
>
> Signed-off-by: Jackson Donaldson <jc...@duck.com>
> ---
>  hw/arm/Kconfig                  |   1 +
>  hw/arm/max78000_soc.c           |  27 +++-
>  hw/char/Kconfig                 |   3 +
>  hw/char/max78000_uart.c         | 263 ++++++++++++++++++++++++++++++++
>  hw/char/meson.build             |   1 +
>  include/hw/arm/max78000_soc.h   |   3 +
>  include/hw/char/max78000_uart.h |  77 ++++++++++
>  7 files changed, 370 insertions(+), 5 deletions(-)
>  create mode 100644 hw/char/max78000_uart.c
>  create mode 100644 include/hw/char/max78000_uart.h

Some of the comments I had on patch 2 apply also to this
and the other devices:
 * separate patches for "new device" and "wire up new
   device into the SoC"
 * missing migration state support
 * use DEVICE_LITTLE_ENDIAN
 * set .impl.min_access_size and .impl_max_access_size
   appropriately for the device
 * make sure you've implemented a reset method (this device
   has one but some of the later ones don't)
 * fix case indent and excess braces if necessary
 * make sure you're not using the _realize_and_unref functions


> diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
> index 3f23af3244..59450dc3cb 100644
> --- a/hw/arm/Kconfig
> +++ b/hw/arm/Kconfig
> @@ -368,6 +368,7 @@ config MAX78000_SOC
>      bool
>      select ARM_V7M
>      select MAX78000_ICC
> +    select MAX78000_UART
>
>  config RASPI
>      bool
> diff --git a/hw/arm/max78000_soc.c b/hw/arm/max78000_soc.c
> index 4d598bddd4..6334d8b49b 100644
> --- a/hw/arm/max78000_soc.c
> +++ b/hw/arm/max78000_soc.c
> @@ -16,7 +16,11 @@
>  #include "hw/qdev-clock.h"
>  #include "hw/misc/unimp.h"
>
> -static const uint32_t max78000_icc_addr[] = {0x4002a000, 0x4002a800};
> +static const uint32_t max78000_icc_addr[] =  {0x4002a000, 0x4002a800};
> +static const uint32_t max78000_uart_addr[] = {0x40042000, 0x40043000,
> +                                              0x40044000};
> +
> +static const int max78000_uart_irq[] = {30, 31, 50};

The GPIO inputs to the ARM7M object are only the external
interrupt lines, so GPIO 0 is the first external interrupt,
which is 16 in the datasheet's Table 3-3. So you need to
subtract 16 from all the numbers in the table to get
the right GPIO index. The UARTs here are at 14, 15, 34.

>
>  static void max78000_soc_initfn(Object *obj)
>  {
> @@ -29,6 +33,10 @@ static void max78000_soc_initfn(Object *obj)
>          object_initialize_child(obj, "icc[*]", &s->icc[i], 
> TYPE_MAX78000_ICC);
>      }
>
> +    for (i = 0; i < MAX78000_NUM_UART; i++) {
> +        object_initialize_child(obj, "uart[*]", &s->uart[i], 
> TYPE_MAX78000_UART);

I didn't notice this for the icc patch, but rather than using the
same string for each UART, you can give them unique names with
           g_autofree char *name = g_strdup_printf("uart%d", i);
           object_initialize_child(obj, name, ...)

See eg hw/arm/stm32l4x5_soc.c for examples.

> +    }
> +
>      s->sysclk = qdev_init_clock_in(DEVICE(s), "sysclk", NULL, NULL, 0);
>      s->refclk = qdev_init_clock_in(DEVICE(s), "refclk", NULL, NULL, 0);
>  }
> @@ -38,6 +46,7 @@ static void max78000_soc_realize(DeviceState *dev_soc, 
> Error **errp)
>      MAX78000State *s = MAX78000_SOC(dev_soc);
>      MemoryRegion *system_memory = get_system_memory();
>      DeviceState *dev, *armv7m;
> +    SysBusDevice *busdev;
>      Error *err = NULL;
>      int i;
>
> @@ -101,6 +110,18 @@ static void max78000_soc_realize(DeviceState *dev_soc, 
> Error **errp)
>          sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, max78000_icc_addr[i]);
>      }
>
> +    for (i = 0; i < MAX78000_NUM_UART; i++) {
> +        dev = DEVICE(&(s->uart[i]));
> +        qdev_prop_set_chr(dev, "chardev", serial_hd(i));
> +        if (!sysbus_realize(SYS_BUS_DEVICE(&s->uart[i]), errp)) {
> +            return;
> +        }
> +        busdev = SYS_BUS_DEVICE(dev);
> +        sysbus_mmio_map(busdev, 0, max78000_uart_addr[i]);
> +        sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(armv7m,
> +                                                       
> max78000_uart_irq[i]));
> +    }
> +
>
>      create_unimplemented_device("globalControl",    0x40000000, 0x400);
>      create_unimplemented_device("systemInterface",  0x40000400, 0x400);
> @@ -140,10 +161,6 @@ static void max78000_soc_realize(DeviceState *dev_soc, 
> Error **errp)
>      create_unimplemented_device("oneWireMaster",    0x4003d000, 0x1000);
>      create_unimplemented_device("semaphore",        0x4003e000, 0x1000);
>
> -    create_unimplemented_device("uart0",            0x40042000, 0x1000);
> -    create_unimplemented_device("uart1",            0x40043000, 0x1000);
> -    create_unimplemented_device("uart2",            0x40044000, 0x1000);
> -
>      create_unimplemented_device("spi1",             0x40046000, 0x2000);
>      create_unimplemented_device("trng",             0x4004d000, 0x1000);
>      create_unimplemented_device("i2s",              0x40060000, 0x1000);
> diff --git a/hw/char/Kconfig b/hw/char/Kconfig
> index 9d517f3e28..020c0a84bb 100644
> --- a/hw/char/Kconfig
> +++ b/hw/char/Kconfig
> @@ -48,6 +48,9 @@ config VIRTIO_SERIAL
>      default y
>      depends on VIRTIO
>
> +config MAX78000_UART
> +    bool
> +
>  config STM32F2XX_USART
>      bool
>
> diff --git a/hw/char/max78000_uart.c b/hw/char/max78000_uart.c
> new file mode 100644
> index 0000000000..edd39c5a8b
> --- /dev/null
> +++ b/hw/char/max78000_uart.c
> @@ -0,0 +1,263 @@
> +/*
> + * MAX78000 UART
> + *
> + * Copyright (c) 2025 Jackson Donaldson <jc...@duck.com>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/char/max78000_uart.h"
> +#include "hw/irq.h"
> +#include "hw/qdev-properties.h"
> +#include "hw/qdev-properties-system.h"
> +#include "qemu/log.h"
> +#include "qemu/module.h"
> +#include "trace.h"
> +
> +static int max78000_uart_can_receive(void *opaque)
> +{
> +    Max78000UartState *s = opaque;
> +    if (!(s->ctrl & UART_BCLKEN)) {
> +        return 0;
> +    }
> +    return fifo8_num_free(&s->rx_fifo);
> +}
> +
> +static void max78000_update_irq(Max78000UartState *s)
> +{
> +    int interrupt_level = 0;
> +    uint32_t rx_threshold = s->ctrl & 0xf;
> +
> +    /*
> +     * Because tx is synchronous and we should have no frame errors, the only
> +     * possible interrupt is receive fifo threshold
> +     */
> +    if ((s->int_en & UART_RX_THD) && fifo8_num_used(&s->rx_fifo) >= 
> rx_threshold) {
> +        interrupt_level = 1;
> +        s->int_fl = s->int_fl & UART_RX_THD;
> +    } else{
> +        s->int_fl = s->int_fl & ~UART_RX_THD;
> +    }

This looks a little odd. The usual pattern for interrupt
status bits is:
 * when we notice the condition (in this case, when we receive
   a char and it puts us above the rx threshold), set the
   bit in the int_fl register
 * option A: devices where the flag bit tracks the underlying
   condition:
     - when we notice that the condition is no longer true (in this
       case, when the guest reads a char from the rx fifo and it
       puts us below the rx threshold again), clear the int_fl bit,
 * option B: devices where the flag bit latches and the guest must
   explicitly clear it:
     - no action when the condition is no longer true

   The MAX78000 UART is an "option B" device -- see section 12.5.
 * in the update_irq() function, set the interrupt if
   the int_fl bit and the int_en bit are set, otherwise clear it.
   If the bit definitions in the two registers line up, this is
   as simple as
    bool interrupt_level = s->int_fl & s->int_en;
   If they don't line up then it needs a bit more manual work.

The code you have at the moment doesn't implement the "int_fl
bits latch and must be explicitly cleared by software" behaviour.

> +    qemu_set_irq(s->irq, interrupt_level);
> +}
> +
> +static void max78000_uart_receive(void *opaque, const uint8_t *buf, int size)
> +{
> +    Max78000UartState *s = opaque;
> +
> +    if (size <= fifo8_num_free(&s->rx_fifo)) {
> +        fifo8_push_all(&s->rx_fifo, buf, size);
> +    } else{
> +        fifo8_push_all(&s->rx_fifo, buf, fifo8_num_free(&s->rx_fifo));
> +        printf("rx_fifo overrun!\n");

This should be a "can't happen" condition -- your receive
method will never be passed more bytes of data than you
said you could handle in can_receive.

Don't printf() in device code. Your options are:
 * if it's something that the device spec says is
   forbidden but a badly behaved guest can trigger,
   use qemu_log(LOG_GUEST_ERROR, ...)
 * if it's something that the device should implement but
   we don't, use qemu_log(LOG_UNIMP, ...)
 * if it's something that can't happen unless there's a bug
   in QEMU, use some kind of assert()
 * if it's nice-to-have information for debugging or for
   a user to see what the device is doing, use a tracepoint
   (trace_* functions, see docs/devel/tracing.rst)


>From your register write function:

> +    case UART_INT_FL:
> +        s->int_fl = value;
> +        return;

This register's bits are W1C, i.e. write-1-to-clear.

thanks
-- PMM

Reply via email to