From 3dc0ad24df9709565c52eae5e421cef074767eb6 Mon Sep 17 00:00:00 2001
From: Davids Paskevics <davip00@mi.fu-berlin.de>
Date: Tue, 20 Jun 2023 13:26:30 +0200
Subject: [PATCH] Initial work on ESP32-C3 UART

---
 hw/char/esp32_c3_uart.c         | 267 ++++++++++++++++++++++++++++++++
 hw/char/meson.build             |   1 +
 hw/char/trace-events            |   4 +
 hw/riscv/esp32_c3.c             |  17 +-
 include/hw/char/esp32_c3_uart.h |  67 ++++++++
 include/hw/riscv/esp32_c3.h     |   3 +
 6 files changed, 358 insertions(+), 1 deletion(-)
 create mode 100644 hw/char/esp32_c3_uart.c
 create mode 100644 include/hw/char/esp32_c3_uart.h

diff --git a/hw/char/esp32_c3_uart.c b/hw/char/esp32_c3_uart.c
new file mode 100644
index 00000000000..4a8e0b163a4
--- /dev/null
+++ b/hw/char/esp32_c3_uart.c
@@ -0,0 +1,267 @@
+/*
+ * ESP32-C3 SoC UART emulation
+ *
+ * Copyright (c) 2023
+ * Davids Paskevics (davip00@zedat.fu-berlin.de)
+ * Nicolas Patzelt (nicolas.patzelt@fu-berlin.de)
+ * Jakob Knitter (jakok07@zedat.fu-berlin.de)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/char/esp32_c3_uart.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-properties-system.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+static uint64_t uart_read(void *opaque, hwaddr addr, unsigned int size)
+{
+    /* Note that this is for reading I/O memory, not FIFO */
+    /*
+    ESP32C3UARTState *s = ESP32_C3_UART(opaque);
+
+    uint64_t r;
+    TODO: Check if enabled
+    if (!s->rx_enabled) {
+        return 0;
+    }
+
+    switch (addr) {
+    default:
+        r = s->regs[addr / 4];
+        break;
+    }
+
+    trace_esp32_c3_uart_read(addr, r, size);
+
+    return r;
+    */
+    return 0;
+}
+
+static gboolean uart_transmit(void *do_not_use, GIOCondition cond, void *opaque)
+{
+    /*
+    ESP32C3UARTState *s = ESP32_C3_UART(opaque);
+    int r;
+    if (s->tx_fifo_pos == 0) {
+        return FALSE;
+    }
+    */
+    /* TODO: Bounds check / wraparound */
+    /*
+    uint8_t c = s->tx_fifo[s->tx_fifo_pos];
+    s->tx_fifo_pos++;
+
+    r = qemu_chr_fe_write(&s->chr, &c, 1);
+    return TRUE;
+    if (r <= 0) {
+        s->watch_tag = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP,
+                                             uart_transmit, s);
+        if (!s->watch_tag) {
+            goto buffer_drained;
+        }
+        return FALSE;
+    }
+
+buffer_drained:
+    s->reg[R_UART_TXDRDY] = 1;
+    s->pending_tx_byte = false;
+    return FALSE;
+    */
+    return FALSE;
+
+}
+
+static void uart_cancel_transmit(ESP32C3UARTState *s)
+{
+    /*
+    if (s->watch_tag) {
+        g_source_remove(s->watch_tag);
+        s->watch_tag = 0;
+    }
+    */
+}
+
+static void uart_write(void *opaque, hwaddr addr,
+                       uint64_t value, unsigned int size)
+{
+    ESP32C3UARTState *s = ESP32_C3_UART(opaque);
+
+    trace_esp32_c3_uart_write(addr, value, size);
+
+    /* TODO: Check if enabled */
+
+    switch (addr) {
+    case A_UART_FIFO:
+        if (s->tx_fifo_pos < ESP32_C3_UART_FIFO_LENGTH) {
+            s->tx_fifo[s->tx_fifo_pos] = value;
+            s->tx_fifo_pos++;
+            s->pending_tx = true;
+        } else {
+            /* TODO: Log */
+            return;
+        }
+        uart_transmit(NULL, G_IO_OUT, s);
+        break;
+    default:
+        /* TODO: Log */
+        s->regs[addr / 4] = value;
+        break;
+    }
+    /* TODO: Update IRQ */
+}
+
+static const MemoryRegionOps uart_ops = {
+    .read =  uart_read,
+    .write = uart_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void esp32_c3_uart_reset(DeviceState *dev)
+{
+    ESP32C3UARTState *s = ESP32_C3_UART(dev);
+
+    uart_cancel_transmit(s);
+
+    /* TODO: Fill regs w/ defaults per TRM */
+    memset(s->regs, 0, sizeof(s->regs));
+    memset(s->rx_fifo, 0, sizeof(s->rx_fifo));
+    memset(s->tx_fifo, 0, sizeof(s->tx_fifo));
+
+    s->rx_fifo_pos = 0;
+    s->tx_fifo_pos = 0;
+    s->pending_tx = false;
+}
+
+static void uart_receive(void *opaque, const uint8_t *buf, int size)
+{
+
+    ESP32C3UARTState *s = ESP32_C3_UART(opaque);
+    int i;
+
+    if (size == 0 || s->rx_fifo_pos >= ESP32_C3_UART_FIFO_LENGTH) {
+        // TODO: Log
+        return;
+    }
+
+    for (i = 0; i < size; i++) {
+        uint32_t pos = s->rx_fifo_pos % ESP32_C3_UART_FIFO_LENGTH;
+        s->rx_fifo[pos] = buf[i];
+        s->rx_fifo_pos++;
+    }
+
+    // TODO: Signal RX ready
+}
+
+static int uart_can_receive(void *opaque)
+{
+    ESP32C3UARTState *s = ESP32_C3_UART(opaque);
+
+    /* Does FIFO have space? */
+    // TODO: Check whether receiver is enabled
+    if (s->rx_fifo_pos < ESP32_C3_UART_FIFO_LENGTH) {
+        return ESP32_C3_UART_FIFO_LENGTH - s->rx_fifo_pos;
+    }
+    return 0;
+}
+
+static void uart_event(void *opaque, QEMUChrEvent event)
+{
+    // ESP32C3UARTState *s = ESP32_C3_UART(opaque);
+
+    if (event == CHR_EVENT_BREAK) {
+        /* TODO: Register as error */
+        /* TODO: Trigger error IRQ */
+    }
+}
+
+static void esp32_c3_uart_realize(DeviceState *dev, Error **errp)
+{
+    ESP32C3UARTState *s = ESP32_C3_UART(dev);
+
+    qemu_chr_fe_set_handlers(&s->chr, uart_can_receive, uart_receive,
+                             uart_event, NULL, s, NULL, true);
+}
+
+static void esp32_c3_uart_init(Object *obj)
+{
+    ESP32C3UARTState *s = ESP32_C3_UART(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+    memory_region_init_io(&s->iomem, obj, &uart_ops, s,
+                          TYPE_ESP32_C3_UART, ESP32_C3_UART_IOMEM_SIZE);
+    sysbus_init_mmio(sbd, &s->iomem);
+    /* sysbus_init_irq(sbd, &s->irq); */
+}
+
+static int esp32_c3_uart_post_load(void *opaque, int version_id)
+{
+    // ESP32C3UARTState *s = ESP32_C3_UART(opaque);
+
+    /*
+
+    if (s->pending_tx_byte) {
+        s->watch_tag = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP,
+                                             uart_transmit, s);
+    }
+
+    */
+
+    return 0;
+}
+
+static const VMStateDescription esp32_c3_uart_vmstate = {
+    .name = TYPE_ESP32_C3_UART,
+    .post_load = esp32_c3_uart_post_load,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, ESP32C3UARTState, ESP32_C3_UART_IOMEM_SIZE_WORDS),
+        VMSTATE_UINT8_ARRAY(rx_fifo, ESP32C3UARTState, ESP32_C3_UART_FIFO_LENGTH),
+        VMSTATE_UINT8_ARRAY(tx_fifo, ESP32C3UARTState, ESP32_C3_UART_FIFO_LENGTH),
+        VMSTATE_UINT32(rx_fifo_pos, ESP32C3UARTState),
+        VMSTATE_UINT32(tx_fifo_pos, ESP32C3UARTState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static Property esp32_c3_uart_properties[] = {
+    DEFINE_PROP_CHR("chardev", ESP32C3UARTState, chr),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void esp32_c3_uart_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->reset = esp32_c3_uart_reset;
+    dc->realize = esp32_c3_uart_realize;
+    device_class_set_props(dc, esp32_c3_uart_properties);
+    set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+    dc->vmsd = &esp32_c3_uart_vmstate;
+}
+
+static const TypeInfo esp32_c3_uart_info = {
+    .name = TYPE_ESP32_C3_UART,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(ESP32C3UARTState),
+    .instance_init = esp32_c3_uart_init,
+    .class_init = esp32_c3_uart_class_init
+};
+
+static void esp32_c3_uart_register_types(void)
+{
+    type_register_static(&esp32_c3_uart_info);
+}
+
+type_init(esp32_c3_uart_register_types)
diff --git a/hw/char/meson.build b/hw/char/meson.build
index 006d20f1e25..f5c51d51f00 100644
--- a/hw/char/meson.build
+++ b/hw/char/meson.build
@@ -24,6 +24,7 @@ system_ss.add(when: 'CONFIG_XILINX', if_true: files('xilinx_uartlite.c'))
 system_ss.add(when: 'CONFIG_AVR_USART', if_true: files('avr_usart.c'))
 system_ss.add(when: 'CONFIG_COLDFIRE', if_true: files('mcf_uart.c'))
 system_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic-uart.c'))
+system_ss.add(when: 'CONFIG_ESP32_C3', if_true: files('esp32_c3_uart.c'))
 system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_uart.c'))
 system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_uart.c'))
 system_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_aux.c'))
diff --git a/hw/char/trace-events b/hw/char/trace-events
index 2ecb36232e9..049b5f0e7fe 100644
--- a/hw/char/trace-events
+++ b/hw/char/trace-events
@@ -105,3 +105,7 @@ cadence_uart_baudrate(unsigned baudrate) "baudrate %u"
 # sh_serial.c
 sh_serial_read(char *id, unsigned size, uint64_t offs, uint64_t val) " %s size %d offs 0x%02" PRIx64 " -> 0x%02" PRIx64
 sh_serial_write(char *id, unsigned size, uint64_t offs, uint64_t val) "%s size %d offs 0x%02" PRIx64 " <- 0x%02" PRIx64
+
+# esp32_c3_uart.c
+esp32_c3_uart_read(uint64_t addr, uint64_t r, unsigned int size) "addr 0x%" PRIx64 " value 0x%" PRIx64 " size %u"
+esp32_c3_uart_write(uint64_t addr, uint64_t value, unsigned int size) "addr 0x%" PRIx64 " value 0x%" PRIx64 " size %u"
diff --git a/hw/riscv/esp32_c3.c b/hw/riscv/esp32_c3.c
index 6d7f425da3a..21d1bb3f2b3 100644
--- a/hw/riscv/esp32_c3.c
+++ b/hw/riscv/esp32_c3.c
@@ -16,12 +16,13 @@
  */
 
 #include "qemu/osdep.h"
+
+#include "hw/char/esp32_c3_uart.h"
 #include "qemu/log.h"
 #include "hw/boards.h"
 #include "hw/riscv/esp32_c3.h"
 #include "qapi/error.h"
 #include "qemu/error-report.h"
-#include "hw/intc/sifive_plic.h"
 #include "hw/intc/riscv_aclint.h"
 #include "sysemu/sysemu.h"
 #include "hw/qdev-properties.h"
@@ -154,6 +155,8 @@ type_init(esp32_c3_machine_type_info_register)
 
 static void esp32_c3_soc_state_realize(DeviceState *dev, Error **errp)
 {
+    MemoryRegion* uart_mem;
+
     MachineState *ms = MACHINE(qdev_get_machine());
     (void)ms;
     Esp32C3SoCState *sss = RISCV_ESP32_SOC(dev);
@@ -195,6 +198,14 @@ static void esp32_c3_soc_state_realize(DeviceState *dev, Error **errp)
                                         esp32_c3_clkcfg, &sss->clock, -1);
     create_unimplemented_device("riscv.esp32.c.sysregs", esp32_c3_sysregs, esp32_c3_sysregs_size);
 
+    /* Register implemented peripherals */
+    qdev_prop_set_chr(DEVICE(&(sss->uart)), "chardev", serial_hd(0));
+    if (!sysbus_realize(SYS_BUS_DEVICE(&sss->uart), errp)) {
+        return;
+    }
+    uart_mem = sysbus_mmio_get_region(SYS_BUS_DEVICE(&sss->uart), 0);
+    memory_region_add_subregion_overlap(&sss->container, ESP32_C3_UART0_BASE, uart_mem, 0);
+
     /* Load boot ROM dump into emulated ROM */
     riscv_load_firmware(ms->firmware, esp32_c3_memmap[ESP32_C3_IROM].base, NULL);
 }
@@ -227,6 +238,10 @@ static void esp32_c3_soc_instance_init(Object *obj)
                             TYPE_RISCV_CPU_ESP32_C3, &error_abort);
     object_property_set_int(OBJECT(&sss->cpus), "num-harts", 1,
                             &error_abort);
+
+    /* Init peripherals */
+    object_initialize_child(obj, "uart", &sss->uart, TYPE_ESP32_C3_UART);
+    //object_property_add_alias(obj, "serial0", &sss->uart, "chardev");
 }
 
 static const TypeInfo esp32_c3_type_info = {
diff --git a/include/hw/char/esp32_c3_uart.h b/include/hw/char/esp32_c3_uart.h
new file mode 100644
index 00000000000..b06769f2bba
--- /dev/null
+++ b/include/hw/char/esp32_c3_uart.h
@@ -0,0 +1,67 @@
+/*
+ * ESP32-C3 SoC UART emulation
+ *
+ * Copyright (c) 2023
+ * Davids Paskevics (davip00@zedat.fu-berlin.de)
+ * Nicolas Patzelt (nicolas.patzelt@fu-berlin.de)
+ * Jakob Knitter (jakok07@zedat.fu-berlin.de)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ */
+
+#ifndef ESP32_C3_UART_H
+#define ESP32_C3_UART_H
+
+#include "hw/sysbus.h"
+#include "chardev/char-fe.h"
+#include "hw/registerfields.h"
+#include "qom/object.h"
+
+
+#define TYPE_ESP32_C3_UART "esp32c3-uart"
+#define ESP32_C3_UART(obj) \
+    OBJECT_CHECK(ESP32C3UARTState, (obj), TYPE_ESP32_C3_UART)
+
+#define ESP32_C3_UART0_BASE 0x60000000
+#define ESP32_C3_UART1_BASE 0x60001000
+#define ESP32_C3_UART_IOMEM_SIZE 0x1000 /* 4K */
+#define ESP32_C3_UART_IOMEM_SIZE_WORDS (ESP32_C3_UART_IOMEM_SIZE / 4)
+
+#define ESP32_C3_UART_FIFO_LENGTH 128
+
+/* These are all relative to the base address */
+
+REG32(UART_FIFO, 0x0000) /* FIFO read / write operations */
+REG32(UART_CLKDIV, 0x0014)
+REG32(UART_RX_FILT, 0x0018)
+REG32(UART_CONF0, 0x0020)
+    FIELD(UART_CONF0, PARITY, 0, 1)         /* Parity config */
+    FIELD(UART_CONF0, PARITY_EN, 1, 1)      /* Parity enable */
+    FIELD(UART_CONF0, BIT_NUM, 2, 1)        /* Number of bits per transmission */
+    FIELD(UART_CONF0, STOP_BIT_NUM, 4, 1)   /* Number of stop bits per transmission */
+REG32(UART_CLK_CONF, 0x0078)
+REG32(UART_ID, 0x0080)
+
+typedef struct {
+    SysBusDevice parent_obj;
+    CharBackend chr; /* Tell qemu this is a character device */
+
+    MemoryRegion iomem; /* Config registers memory region */
+    uint32_t regs[ESP32_C3_UART_IOMEM_SIZE_WORDS]; /* Actual contents of config registers */
+
+    MemoryRegion fifo;  /* FIFO RAM memory region */
+    uint8_t rx_fifo[ESP32_C3_UART_FIFO_LENGTH]; /* RX FIFO RAM */
+    uint8_t tx_fifo[ESP32_C3_UART_FIFO_LENGTH]; /* TX FIFO RAM */
+
+    unsigned int rx_fifo_pos; /* How many bytes are waiting in RX FIFO */
+    unsigned int tx_fifo_pos; /* How many bytes are waiting in TX FIFO */
+    bool pending_tx;
+} ESP32C3UARTState;
+
+#endif
diff --git a/include/hw/riscv/esp32_c3.h b/include/hw/riscv/esp32_c3.h
index cb799764f56..80f2559e67d 100644
--- a/include/hw/riscv/esp32_c3.h
+++ b/include/hw/riscv/esp32_c3.h
@@ -25,6 +25,7 @@
 #include "hw/riscv/riscv_hart.h"
 #include "hw/boards.h"
 #include "qemu/typedefs.h"
+#include "hw/char/esp32_c3_uart.h"
 
 
 
@@ -48,6 +49,8 @@ typedef struct Esp32C3SoCState {
     /* Memory mapped I/O devices */
     MemoryRegion container;
     MemoryRegion clock;
+    /* Misc. devices */
+    ESP32C3UARTState uart;
 
 } Esp32C3SoCState;
 
-- 
GitLab