qemu-riscv
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH 2/3] Andes RISC-V PLMT


From: Dylan Jhong
Subject: [PATCH 2/3] Andes RISC-V PLMT
Date: Wed, 10 Mar 2021 11:33:57 +0800

Andes PLMT (Platform-Level Machine Timer) device provides an
timer functionality and issues timer interrupts.

The Andes PLMT is implemented based on Andes's PLMT specification.

Signed-off-by: Dylan Jhong <dylan@andestech.com>
Signed-off-by: Ruinland ChuanTzu Tsai <ruinland@andestech.com>
---
 hw/timer/Kconfig              |   3 +
 hw/timer/andes_plmt.c         | 225 ++++++++++++++++++++++++++++++++++
 hw/timer/meson.build          |   1 +
 include/hw/timer/andes_plmt.h |  50 ++++++++
 4 files changed, 279 insertions(+)
 create mode 100644 hw/timer/andes_plmt.c
 create mode 100644 include/hw/timer/andes_plmt.h

diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig
index 18936ef55b..d96814e5a4 100644
--- a/hw/timer/Kconfig
+++ b/hw/timer/Kconfig
@@ -1,3 +1,6 @@
+config ANDES_PLMT
+    bool
+
 config ARM_TIMER
     bool
     select PTIMER
diff --git a/hw/timer/andes_plmt.c b/hw/timer/andes_plmt.c
new file mode 100644
index 0000000000..a6adb05199
--- /dev/null
+++ b/hw/timer/andes_plmt.c
@@ -0,0 +1,225 @@
+/*
+ * Andes PLMT (Platform Level Machine Timer)
+ *
+ * Copyright (c) 2021 Andes Tech. Corp.
+ *
+ * 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
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/timer.h"
+#include "qemu/module.h"
+#include "hw/sysbus.h"
+#include "target/riscv/cpu.h"
+#include "hw/qdev-properties.h"
+#include "hw/timer/andes_plmt.h"
+
+static uint64_t andes_cpu_riscv_read_rtc(uint32_t timebase_freq)
+{
+    return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
+        timebase_freq, NANOSECONDS_PER_SECOND);
+}
+
+/*
+ * Called when timecmp is written to update the QEMU timer or immediately
+ * trigger timer interrupt if mtimecmp <= current timer value.
+ */
+static void
+andes_plmt_write_timecmp(RISCVCPU *cpu, uint64_t value)
+{
+    uint64_t next;
+    uint64_t diff;
+
+    uint64_t rtc_r = andes_cpu_riscv_read_rtc(ANDES_PLMT_TIMEBASE_FREQ);
+
+    cpu->env.timecmp = (uint64_t)value;
+    if (cpu->env.timecmp <= rtc_r) {
+        /*
+         * if we're setting an MTIMECMP value in the "past",
+         * immediately raise the timer interrupt
+         */
+        riscv_cpu_update_mip(cpu, MIP_MTIP, BOOL_TO_MASK(1));
+        return;
+    }
+
+    /* otherwise, set up the future timer interrupt */
+    riscv_cpu_update_mip(cpu, MIP_MTIP, BOOL_TO_MASK(0));
+    diff = cpu->env.timecmp - rtc_r;
+    /* back to ns (note args switched in muldiv64) */
+    next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+        muldiv64(diff, NANOSECONDS_PER_SECOND, ANDES_PLMT_TIMEBASE_FREQ);
+    timer_mod(cpu->env.timer, next);
+}
+
+/*
+ * Callback used when the timer set using timer_mod expires.
+ * Should raise the timer interrupt line
+ */
+static void
+andes_plmt_timer_cb(void *opaque)
+{
+    RISCVCPU *cpu = opaque;
+    riscv_cpu_update_mip(cpu, MIP_MTIP, BOOL_TO_MASK(1));
+}
+
+static uint64_t
+andes_plmt_read(void *opaque, hwaddr addr, unsigned size)
+{
+    AndesPLMTState *plmt = opaque;
+    uint64_t rz = 0;
+
+    if ((addr >= (plmt->timecmp_base)) &&
+        (addr < (plmt->timecmp_base + (plmt->num_harts << 3)))) {
+        /* %8=0:timecmp_lo, %8=4:timecmp_hi */
+        size_t hartid = (addr - plmt->timecmp_base) >> 3;
+        CPUState *cpu = qemu_get_cpu(hartid);
+        CPURISCVState *env = cpu ? cpu->env_ptr : NULL;
+        if (!env) {
+            error_report("plmt: invalid timecmp hartid: %zu", hartid);
+        } else if ((addr & 0x7) == 0) {
+            rz = env->timecmp & (unsigned long)0xFFFFFFFF;
+        } else if ((addr & 0x7) == 4) {
+            rz = (env->timecmp >> 32) & (unsigned long)0xFFFFFFFF;
+        } else {
+            error_report("plmt: invalid read: %08x", (uint32_t)addr);
+        }
+    } else if (addr == (plmt->time_base)) {
+        /* time_lo */
+        rz = andes_cpu_riscv_read_rtc(ANDES_PLMT_TIMEBASE_FREQ)
+                    & (unsigned long)0xFFFFFFFF;
+    } else if (addr == (plmt->time_base + 4)) {
+        /* time_hi */
+        rz = (andes_cpu_riscv_read_rtc(ANDES_PLMT_TIMEBASE_FREQ) >> 32)
+                    & (unsigned long)0xFFFFFFFF;
+    } else {
+        error_report("plmt: invalid read: %08x", (uint32_t)addr);
+    }
+
+    return rz;
+}
+
+static void
+andes_plmt_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
+{
+    AndesPLMTState *plmt = opaque;
+
+    if ((addr >= (plmt->timecmp_base)) &&
+        (addr < (plmt->timecmp_base + (plmt->num_harts << 3)))) {
+        /* %8=0:timecmp_lo, %8=4:timecmp_hi */
+        size_t hartid = (addr - plmt->timecmp_base) >> 3;
+        CPUState *cpu = qemu_get_cpu(hartid);
+        CPURISCVState *env = cpu ? cpu->env_ptr : NULL;
+        if (!env) {
+            error_report("plmt: invalid timecmp hartid: %zu", hartid);
+        } else if ((addr & 0x7) == 0) {
+            uint64_t timecmp_hi = (unsigned long)env->timecmp >> 32;
+            andes_plmt_write_timecmp(RISCV_CPU(cpu),
+                (timecmp_hi << 32) | (value & (unsigned long)0xFFFFFFFF));
+        } else if ((addr & 0x7) == 4) {
+            uint64_t timecmp_lo = env->timecmp;
+            andes_plmt_write_timecmp(RISCV_CPU(cpu),
+                (value << 32) | (timecmp_lo & (unsigned long)0xFFFFFFFF));
+        } else {
+            error_report("plmt: invalid write: %08x", (uint32_t)addr);
+        }
+    } else if (addr == (plmt->time_base)) {
+        /* time_lo */
+        error_report("plmt: time_lo write not implemented");
+    } else if (addr == (plmt->time_base + 4)) {
+        /* time_hi */
+        error_report("plmt: time_hi write not implemented");
+    } else {
+        error_report("plmt: invalid write: %08x", (uint32_t)addr);
+    }
+}
+
+static const MemoryRegionOps andes_plmt_ops = {
+    .read = andes_plmt_read,
+    .write = andes_plmt_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8
+    }
+};
+
+static Property andes_plmt_properties[] = {
+    DEFINE_PROP_UINT32("num-harts", AndesPLMTState, num_harts, 0),
+    DEFINE_PROP_UINT32("time-base", AndesPLMTState, time_base, 0),
+    DEFINE_PROP_UINT32("timecmp-base", AndesPLMTState, timecmp_base, 0),
+    DEFINE_PROP_UINT32("aperture-size", AndesPLMTState, aperture_size, 0),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void andes_plmt_realize(DeviceState *dev, Error **errp)
+{
+    AndesPLMTState *s = ANDES_PLMT(dev);
+    memory_region_init_io(&s->mmio, OBJECT(dev), &andes_plmt_ops, s,
+        TYPE_ANDES_PLMT, s->aperture_size);
+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio);
+}
+
+static void andes_plmt_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    dc->realize = andes_plmt_realize;
+    device_class_set_props(dc, andes_plmt_properties);
+}
+
+static const TypeInfo andes_plmt_info = {
+    .name = TYPE_ANDES_PLMT,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(AndesPLMTState),
+    .class_init = andes_plmt_class_init,
+};
+
+static void andes_plmt_register_types(void)
+{
+    type_register_static(&andes_plmt_info);
+}
+
+type_init(andes_plmt_register_types)
+
+/*
+ * Create PLMT device.
+ */
+DeviceState*
+andes_plmt_create(hwaddr addr, hwaddr size, uint32_t num_harts,
+    uint32_t time_base, uint32_t timecmp_base)
+{
+    int i;
+    for (i = 0; i < num_harts; i++) {
+        CPUState *cpu = qemu_get_cpu(i);
+        CPURISCVState *env = cpu ? cpu->env_ptr : NULL;
+        if (!env) {
+            continue;
+        }
+        riscv_cpu_set_rdtime_fn(env, andes_cpu_riscv_read_rtc,
+            ANDES_PLMT_TIMEBASE_FREQ);
+
+        env->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+                                  &andes_plmt_timer_cb, cpu);
+        env->timecmp = 0;
+    }
+
+    DeviceState *dev = qdev_new(TYPE_ANDES_PLMT);
+    qdev_prop_set_uint32(dev, "num-harts", num_harts);
+    qdev_prop_set_uint32(dev, "time-base", time_base);
+    qdev_prop_set_uint32(dev, "timecmp-base", timecmp_base);
+    qdev_prop_set_uint32(dev, "aperture-size", size);
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
+    return dev;
+}
diff --git a/hw/timer/meson.build b/hw/timer/meson.build
index 26c2701fd7..02c1f205cc 100644
--- a/hw/timer/meson.build
+++ b/hw/timer/meson.build
@@ -35,4 +35,5 @@ softmmu_ss.add(when: 'CONFIG_SLAVIO', if_true: 
files('slavio_timer.c'))
 softmmu_ss.add(when: 'CONFIG_STM32F2XX_TIMER', if_true: 
files('stm32f2xx_timer.c'))
 softmmu_ss.add(when: 'CONFIG_XILINX', if_true: files('xilinx_timer.c'))
 
+specific_ss.add(when: 'CONFIG_ANDES_PLMT', if_true: files('andes_plmt.c'))
 specific_ss.add(when: 'CONFIG_AVR_TIMER16', if_true: files('avr_timer16.c'))
diff --git a/include/hw/timer/andes_plmt.h b/include/hw/timer/andes_plmt.h
new file mode 100644
index 0000000000..8864013d6f
--- /dev/null
+++ b/include/hw/timer/andes_plmt.h
@@ -0,0 +1,50 @@
+/*
+ * Andes PLMT (Platform Level Machine Timer) interface
+ *
+ * Copyright (c) 2021 Andes Tech. Corp.
+ *
+ * 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
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef HW_ANDES_PLMT_H
+#define HW_ANDES_PLMT_H
+
+#define TYPE_ANDES_PLMT "riscv.andes.plmt"
+
+#define ANDES_PLMT(obj) \
+    OBJECT_CHECK(AndesPLMTState, (obj), TYPE_ANDES_PLMT)
+
+typedef struct AndesPLMTState {
+    /*< private >*/
+    SysBusDevice parent_obj;
+
+    /*< public >*/
+    MemoryRegion mmio;
+    uint32_t num_harts;
+    uint32_t time_base;
+    uint32_t timecmp_base;
+    uint32_t aperture_size;
+} AndesPLMTState;
+
+DeviceState *
+andes_plmt_create(hwaddr addr, hwaddr size, uint32_t num_harts,
+    uint32_t time_base, uint32_t timecmp_base);
+
+enum {
+    ANDES_PLMT_TIME_BASE = 0,
+    ANDES_PLMT_TIMECMP_BASE = 8,
+    ANDES_PLMT_MMIO_SIZE = 0x100000,
+    ANDES_PLMT_TIMEBASE_FREQ = 60 * 1000 * 1000
+};
+
+#endif
-- 
2.17.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]