aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hw/core/Makefile.objs1
-rw-r--r--hw/core/clock.c130
-rw-r--r--hw/core/trace-events7
-rw-r--r--include/hw/clock.h216
4 files changed, 354 insertions, 0 deletions
diff --git a/hw/core/Makefile.objs b/hw/core/Makefile.objs
index 6215e7c208..1d9b0aa205 100644
--- a/hw/core/Makefile.objs
+++ b/hw/core/Makefile.objs
@@ -7,6 +7,7 @@ common-obj-y += hotplug.o
common-obj-y += vmstate-if.o
# irq.o needed for qdev GPIO handling:
common-obj-y += irq.o
+common-obj-y += clock.o
common-obj-$(CONFIG_SOFTMMU) += reset.o
common-obj-$(CONFIG_SOFTMMU) += qdev-fw.o
diff --git a/hw/core/clock.c b/hw/core/clock.c
new file mode 100644
index 0000000000..3c0daf7d4c
--- /dev/null
+++ b/hw/core/clock.c
@@ -0,0 +1,130 @@
+/*
+ * Hardware Clocks
+ *
+ * Copyright GreenSocs 2016-2020
+ *
+ * Authors:
+ * Frederic Konrad
+ * Damien Hedde
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/clock.h"
+#include "trace.h"
+
+#define CLOCK_PATH(_clk) (_clk->canonical_path)
+
+void clock_setup_canonical_path(Clock *clk)
+{
+ g_free(clk->canonical_path);
+ clk->canonical_path = object_get_canonical_path(OBJECT(clk));
+}
+
+void clock_set_callback(Clock *clk, ClockCallback *cb, void *opaque)
+{
+ clk->callback = cb;
+ clk->callback_opaque = opaque;
+}
+
+void clock_clear_callback(Clock *clk)
+{
+ clock_set_callback(clk, NULL, NULL);
+}
+
+void clock_set(Clock *clk, uint64_t period)
+{
+ trace_clock_set(CLOCK_PATH(clk), CLOCK_PERIOD_TO_NS(clk->period),
+ CLOCK_PERIOD_TO_NS(period));
+ clk->period = period;
+}
+
+static void clock_propagate_period(Clock *clk, bool call_callbacks)
+{
+ Clock *child;
+
+ QLIST_FOREACH(child, &clk->children, sibling) {
+ if (child->period != clk->period) {
+ child->period = clk->period;
+ trace_clock_update(CLOCK_PATH(child), CLOCK_PATH(clk),
+ CLOCK_PERIOD_TO_NS(clk->period),
+ call_callbacks);
+ if (call_callbacks && child->callback) {
+ child->callback(child->callback_opaque);
+ }
+ clock_propagate_period(child, call_callbacks);
+ }
+ }
+}
+
+void clock_propagate(Clock *clk)
+{
+ assert(clk->source == NULL);
+ trace_clock_propagate(CLOCK_PATH(clk));
+ clock_propagate_period(clk, true);
+}
+
+void clock_set_source(Clock *clk, Clock *src)
+{
+ /* changing clock source is not supported */
+ assert(!clk->source);
+
+ trace_clock_set_source(CLOCK_PATH(clk), CLOCK_PATH(src));
+
+ clk->period = src->period;
+ QLIST_INSERT_HEAD(&src->children, clk, sibling);
+ clk->source = src;
+ clock_propagate_period(clk, false);
+}
+
+static void clock_disconnect(Clock *clk)
+{
+ if (clk->source == NULL) {
+ return;
+ }
+
+ trace_clock_disconnect(CLOCK_PATH(clk));
+
+ clk->source = NULL;
+ QLIST_REMOVE(clk, sibling);
+}
+
+static void clock_initfn(Object *obj)
+{
+ Clock *clk = CLOCK(obj);
+
+ QLIST_INIT(&clk->children);
+}
+
+static void clock_finalizefn(Object *obj)
+{
+ Clock *clk = CLOCK(obj);
+ Clock *child, *next;
+
+ /* clear our list of children */
+ QLIST_FOREACH_SAFE(child, &clk->children, sibling, next) {
+ clock_disconnect(child);
+ }
+
+ /* remove us from source's children list */
+ clock_disconnect(clk);
+
+ g_free(clk->canonical_path);
+}
+
+static const TypeInfo clock_info = {
+ .name = TYPE_CLOCK,
+ .parent = TYPE_OBJECT,
+ .instance_size = sizeof(Clock),
+ .instance_init = clock_initfn,
+ .instance_finalize = clock_finalizefn,
+};
+
+static void clock_register_types(void)
+{
+ type_register_static(&clock_info);
+}
+
+type_init(clock_register_types)
diff --git a/hw/core/trace-events b/hw/core/trace-events
index aecd8e160e..1ac60ede6b 100644
--- a/hw/core/trace-events
+++ b/hw/core/trace-events
@@ -27,3 +27,10 @@ resettable_phase_exit_begin(void *obj, const char *objtype, unsigned count, int
resettable_phase_exit_exec(void *obj, const char *objtype, int has_method) "obj=%p(%s) method=%d"
resettable_phase_exit_end(void *obj, const char *objtype, unsigned count) "obj=%p(%s) count=%d"
resettable_transitional_function(void *obj, const char *objtype) "obj=%p(%s)"
+
+# clock.c
+clock_set_source(const char *clk, const char *src) "'%s', src='%s'"
+clock_disconnect(const char *clk) "'%s'"
+clock_set(const char *clk, uint64_t old, uint64_t new) "'%s', ns=%"PRIu64"->%"PRIu64
+clock_propagate(const char *clk) "'%s'"
+clock_update(const char *clk, const char *src, uint64_t val, int cb) "'%s', src='%s', ns=%"PRIu64", cb=%d"
diff --git a/include/hw/clock.h b/include/hw/clock.h
new file mode 100644
index 0000000000..e0e19db9bd
--- /dev/null
+++ b/include/hw/clock.h
@@ -0,0 +1,216 @@
+/*
+ * Hardware Clocks
+ *
+ * Copyright GreenSocs 2016-2020
+ *
+ * Authors:
+ * Frederic Konrad
+ * Damien Hedde
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef QEMU_HW_CLOCK_H
+#define QEMU_HW_CLOCK_H
+
+#include "qom/object.h"
+#include "qemu/queue.h"
+
+#define TYPE_CLOCK "clock"
+#define CLOCK(obj) OBJECT_CHECK(Clock, (obj), TYPE_CLOCK)
+
+typedef void ClockCallback(void *opaque);
+
+/*
+ * clock store a value representing the clock's period in 2^-32ns unit.
+ * It can represent:
+ * + periods from 2^-32ns up to 4seconds
+ * + frequency from ~0.25Hz 2e10Ghz
+ * Resolution of frequency representation decreases with frequency:
+ * + at 100MHz, resolution is ~2mHz
+ * + at 1Ghz, resolution is ~0.2Hz
+ * + at 10Ghz, resolution is ~20Hz
+ */
+#define CLOCK_PERIOD_1SEC (1000000000llu << 32)
+
+/*
+ * macro helpers to convert to hertz / nanosecond
+ */
+#define CLOCK_PERIOD_FROM_NS(ns) ((ns) * (CLOCK_PERIOD_1SEC / 1000000000llu))
+#define CLOCK_PERIOD_TO_NS(per) ((per) / (CLOCK_PERIOD_1SEC / 1000000000llu))
+#define CLOCK_PERIOD_FROM_HZ(hz) (((hz) != 0) ? CLOCK_PERIOD_1SEC / (hz) : 0u)
+#define CLOCK_PERIOD_TO_HZ(per) (((per) != 0) ? CLOCK_PERIOD_1SEC / (per) : 0u)
+
+/**
+ * Clock:
+ * @parent_obj: parent class
+ * @period: unsigned integer representing the period of the clock
+ * @canonical_path: clock path string cache (used for trace purpose)
+ * @callback: called when clock changes
+ * @callback_opaque: argument for @callback
+ * @source: source (or parent in clock tree) of the clock
+ * @children: list of clocks connected to this one (it is their source)
+ * @sibling: structure used to form a clock list
+ */
+
+typedef struct Clock Clock;
+
+struct Clock {
+ /*< private >*/
+ Object parent_obj;
+
+ /* all fields are private and should not be modified directly */
+
+ /* fields */
+ uint64_t period;
+ char *canonical_path;
+ ClockCallback *callback;
+ void *callback_opaque;
+
+ /* Clocks are organized in a clock tree */
+ Clock *source;
+ QLIST_HEAD(, Clock) children;
+ QLIST_ENTRY(Clock) sibling;
+};
+
+/**
+ * clock_setup_canonical_path:
+ * @clk: clock
+ *
+ * compute the canonical path of the clock (used by log messages)
+ */
+void clock_setup_canonical_path(Clock *clk);
+
+/**
+ * clock_set_callback:
+ * @clk: the clock to register the callback into
+ * @cb: the callback function
+ * @opaque: the argument to the callback
+ *
+ * Register a callback called on every clock update.
+ */
+void clock_set_callback(Clock *clk, ClockCallback *cb, void *opaque);
+
+/**
+ * clock_clear_callback:
+ * @clk: the clock to delete the callback from
+ *
+ * Unregister the callback registered with clock_set_callback.
+ */
+void clock_clear_callback(Clock *clk);
+
+/**
+ * clock_set_source:
+ * @clk: the clock.
+ * @src: the source clock
+ *
+ * Setup @src as the clock source of @clk. The current @src period
+ * value is also copied to @clk and its subtree but no callback is
+ * called.
+ * Further @src update will be propagated to @clk and its subtree.
+ */
+void clock_set_source(Clock *clk, Clock *src);
+
+/**
+ * clock_set:
+ * @clk: the clock to initialize.
+ * @value: the clock's value, 0 means unclocked
+ *
+ * Set the local cached period value of @clk to @value.
+ */
+void clock_set(Clock *clk, uint64_t value);
+
+static inline void clock_set_hz(Clock *clk, unsigned hz)
+{
+ clock_set(clk, CLOCK_PERIOD_FROM_HZ(hz));
+}
+
+static inline void clock_set_ns(Clock *clk, unsigned ns)
+{
+ clock_set(clk, CLOCK_PERIOD_FROM_NS(ns));
+}
+
+/**
+ * clock_propagate:
+ * @clk: the clock
+ *
+ * Propagate the clock period that has been previously configured using
+ * @clock_set(). This will update recursively all connected clocks.
+ * It is an error to call this function on a clock which has a source.
+ * Note: this function must not be called during device inititialization
+ * or migration.
+ */
+void clock_propagate(Clock *clk);
+
+/**
+ * clock_update:
+ * @clk: the clock to update.
+ * @value: the new clock's value, 0 means unclocked
+ *
+ * Update the @clk to the new @value. All connected clocks will be informed
+ * of this update. This is equivalent to call @clock_set() then
+ * @clock_propagate().
+ */
+static inline void clock_update(Clock *clk, uint64_t value)
+{
+ clock_set(clk, value);
+ clock_propagate(clk);
+}
+
+static inline void clock_update_hz(Clock *clk, unsigned hz)
+{
+ clock_update(clk, CLOCK_PERIOD_FROM_HZ(hz));
+}
+
+static inline void clock_update_ns(Clock *clk, unsigned ns)
+{
+ clock_update(clk, CLOCK_PERIOD_FROM_NS(ns));
+}
+
+/**
+ * clock_get:
+ * @clk: the clk to fetch the clock
+ *
+ * @return: the current period.
+ */
+static inline uint64_t clock_get(const Clock *clk)
+{
+ return clk->period;
+}
+
+static inline unsigned clock_get_hz(Clock *clk)
+{
+ return CLOCK_PERIOD_TO_HZ(clock_get(clk));
+}
+
+static inline unsigned clock_get_ns(Clock *clk)
+{
+ return CLOCK_PERIOD_TO_NS(clock_get(clk));
+}
+
+/**
+ * clock_is_enabled:
+ * @clk: a clock
+ *
+ * @return: true if the clock is running.
+ */
+static inline bool clock_is_enabled(const Clock *clk)
+{
+ return clock_get(clk) != 0;
+}
+
+static inline void clock_init(Clock *clk, uint64_t value)
+{
+ clock_set(clk, value);
+}
+static inline void clock_init_hz(Clock *clk, uint64_t value)
+{
+ clock_set_hz(clk, value);
+}
+static inline void clock_init_ns(Clock *clk, uint64_t value)
+{
+ clock_set_ns(clk, value);
+}
+
+#endif /* QEMU_HW_CLOCK_H */