blob: 82799577f3e959c655ccca861ee394b3280b5262 [file] [log] [blame]
/*
* Device's clock input and output
*
* 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 "qemu/error-report.h"
#include "hw/qdev-clock.h"
#include "hw/qdev-core.h"
#include "qapi/error.h"
/*
* qdev_init_clocklist:
* Add a new clock in a device
*/
static NamedClockList *qdev_init_clocklist(DeviceState *dev, const char *name,
bool output, Clock *clk)
{
NamedClockList *ncl;
/*
* Clock must be added before realize() so that we can compute the
* clock's canonical path during device_realize().
*/
assert(!dev->realized);
/*
* The ncl structure is freed by qdev_finalize_clocklist() which will
* be called during @dev's device_finalize().
*/
ncl = g_new0(NamedClockList, 1);
ncl->name = g_strdup(name);
ncl->output = output;
ncl->alias = (clk != NULL);
/*
* Trying to create a clock whose name clashes with some other
* clock or property is a bug in the caller and we will abort().
*/
if (clk == NULL) {
clk = CLOCK(object_new(TYPE_CLOCK));
object_property_add_child(OBJECT(dev), name, OBJECT(clk));
if (output) {
/*
* Remove object_new()'s initial reference.
* Note that for inputs, the reference created by object_new()
* will be deleted in qdev_finalize_clocklist().
*/
object_unref(OBJECT(clk));
}
} else {
object_property_add_link(OBJECT(dev), name,
object_get_typename(OBJECT(clk)),
(Object **) &ncl->clock,
NULL, OBJ_PROP_LINK_STRONG);
/*
* Since the link property has the OBJ_PROP_LINK_STRONG flag, the clk
* object reference count gets decremented on property deletion.
* However object_property_add_link does not increment it since it
* doesn't know the linked object. Increment it here to ensure the
* aliased clock stays alive during this device life-time.
*/
object_ref(OBJECT(clk));
}
ncl->clock = clk;
QLIST_INSERT_HEAD(&dev->clocks, ncl, node);
return ncl;
}
void qdev_finalize_clocklist(DeviceState *dev)
{
/* called by @dev's device_finalize() */
NamedClockList *ncl, *ncl_next;
QLIST_FOREACH_SAFE(ncl, &dev->clocks, node, ncl_next) {
QLIST_REMOVE(ncl, node);
if (!ncl->output && !ncl->alias) {
/*
* We kept a reference on the input clock to ensure it lives up to
* this point so we can safely remove the callback.
* It avoids having a callback to a deleted object if ncl->clock
* is still referenced somewhere else (eg: by a clock output).
*/
clock_clear_callback(ncl->clock);
object_unref(OBJECT(ncl->clock));
}
g_free(ncl->name);
g_free(ncl);
}
}
Clock *qdev_init_clock_out(DeviceState *dev, const char *name)
{
NamedClockList *ncl;
assert(name);
ncl = qdev_init_clocklist(dev, name, true, NULL);
return ncl->clock;
}
Clock *qdev_init_clock_in(DeviceState *dev, const char *name,
ClockCallback *callback, void *opaque,
unsigned int events)
{
NamedClockList *ncl;
assert(name);
ncl = qdev_init_clocklist(dev, name, false, NULL);
if (callback) {
clock_set_callback(ncl->clock, callback, opaque, events);
}
return ncl->clock;
}
void qdev_init_clocks(DeviceState *dev, const ClockPortInitArray clocks)
{
const struct ClockPortInitElem *elem;
for (elem = &clocks[0]; elem->name != NULL; elem++) {
Clock **clkp;
/* offset cannot be inside the DeviceState part */
assert(elem->offset > sizeof(DeviceState));
clkp = ((void *)dev) + elem->offset;
if (elem->is_output) {
*clkp = qdev_init_clock_out(dev, elem->name);
} else {
*clkp = qdev_init_clock_in(dev, elem->name, elem->callback, dev,
elem->callback_events);
}
}
}
static NamedClockList *qdev_get_clocklist(DeviceState *dev, const char *name)
{
NamedClockList *ncl;
QLIST_FOREACH(ncl, &dev->clocks, node) {
if (strcmp(name, ncl->name) == 0) {
return ncl;
}
}
return NULL;
}
Clock *qdev_get_clock_in(DeviceState *dev, const char *name)
{
NamedClockList *ncl;
assert(name);
ncl = qdev_get_clocklist(dev, name);
if (!ncl) {
error_report("Can not find clock-in '%s' for device type '%s'",
name, object_get_typename(OBJECT(dev)));
abort();
}
assert(!ncl->output);
return ncl->clock;
}
Clock *qdev_get_clock_out(DeviceState *dev, const char *name)
{
NamedClockList *ncl;
assert(name);
ncl = qdev_get_clocklist(dev, name);
if (!ncl) {
error_report("Can not find clock-out '%s' for device type '%s'",
name, object_get_typename(OBJECT(dev)));
abort();
}
assert(ncl->output);
return ncl->clock;
}
Clock *qdev_alias_clock(DeviceState *dev, const char *name,
DeviceState *alias_dev, const char *alias_name)
{
NamedClockList *ncl;
assert(name && alias_name);
ncl = qdev_get_clocklist(dev, name);
qdev_init_clocklist(alias_dev, alias_name, ncl->output, ncl->clock);
return ncl->clock;
}
void qdev_connect_clock_in(DeviceState *dev, const char *name, Clock *source)
{
assert(!dev->realized);
clock_set_source(qdev_get_clock_in(dev, name), source);
}