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 */