239 lines
7.4 KiB
C
239 lines
7.4 KiB
C
|
/*
|
||
|
* Resettable interface.
|
||
|
*
|
||
|
* Copyright (c) 2019 GreenSocs SAS
|
||
|
*
|
||
|
* Authors:
|
||
|
* 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/module.h"
|
||
|
#include "hw/resettable.h"
|
||
|
#include "trace.h"
|
||
|
|
||
|
/**
|
||
|
* resettable_phase_enter/hold/exit:
|
||
|
* Function executing a phase recursively in a resettable object and its
|
||
|
* children.
|
||
|
*/
|
||
|
static void resettable_phase_enter(Object *obj, void *opaque, ResetType type);
|
||
|
static void resettable_phase_hold(Object *obj, void *opaque, ResetType type);
|
||
|
static void resettable_phase_exit(Object *obj, void *opaque, ResetType type);
|
||
|
|
||
|
/**
|
||
|
* enter_phase_in_progress:
|
||
|
* True if we are currently in reset enter phase.
|
||
|
*
|
||
|
* Note: This flag is only used to guarantee (using asserts) that the reset
|
||
|
* API is used correctly. We can use a global variable because we rely on the
|
||
|
* iothread mutex to ensure only one reset operation is in a progress at a
|
||
|
* given time.
|
||
|
*/
|
||
|
static bool enter_phase_in_progress;
|
||
|
|
||
|
void resettable_reset(Object *obj, ResetType type)
|
||
|
{
|
||
|
trace_resettable_reset(obj, type);
|
||
|
resettable_assert_reset(obj, type);
|
||
|
resettable_release_reset(obj, type);
|
||
|
}
|
||
|
|
||
|
void resettable_assert_reset(Object *obj, ResetType type)
|
||
|
{
|
||
|
/* TODO: change this assert when adding support for other reset types */
|
||
|
assert(type == RESET_TYPE_COLD);
|
||
|
trace_resettable_reset_assert_begin(obj, type);
|
||
|
assert(!enter_phase_in_progress);
|
||
|
|
||
|
enter_phase_in_progress = true;
|
||
|
resettable_phase_enter(obj, NULL, type);
|
||
|
enter_phase_in_progress = false;
|
||
|
|
||
|
resettable_phase_hold(obj, NULL, type);
|
||
|
|
||
|
trace_resettable_reset_assert_end(obj);
|
||
|
}
|
||
|
|
||
|
void resettable_release_reset(Object *obj, ResetType type)
|
||
|
{
|
||
|
/* TODO: change this assert when adding support for other reset types */
|
||
|
assert(type == RESET_TYPE_COLD);
|
||
|
trace_resettable_reset_release_begin(obj, type);
|
||
|
assert(!enter_phase_in_progress);
|
||
|
|
||
|
resettable_phase_exit(obj, NULL, type);
|
||
|
|
||
|
trace_resettable_reset_release_end(obj);
|
||
|
}
|
||
|
|
||
|
bool resettable_is_in_reset(Object *obj)
|
||
|
{
|
||
|
ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
|
||
|
ResettableState *s = rc->get_state(obj);
|
||
|
|
||
|
return s->count > 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* resettable_child_foreach:
|
||
|
* helper to avoid checking the existence of the method.
|
||
|
*/
|
||
|
static void resettable_child_foreach(ResettableClass *rc, Object *obj,
|
||
|
ResettableChildCallback cb,
|
||
|
void *opaque, ResetType type)
|
||
|
{
|
||
|
if (rc->child_foreach) {
|
||
|
rc->child_foreach(obj, cb, opaque, type);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* resettable_get_tr_func:
|
||
|
* helper to fetch transitional reset callback if any.
|
||
|
*/
|
||
|
static ResettableTrFunction resettable_get_tr_func(ResettableClass *rc,
|
||
|
Object *obj)
|
||
|
{
|
||
|
ResettableTrFunction tr_func = NULL;
|
||
|
if (rc->get_transitional_function) {
|
||
|
tr_func = rc->get_transitional_function(obj);
|
||
|
}
|
||
|
return tr_func;
|
||
|
}
|
||
|
|
||
|
static void resettable_phase_enter(Object *obj, void *opaque, ResetType type)
|
||
|
{
|
||
|
ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
|
||
|
ResettableState *s = rc->get_state(obj);
|
||
|
const char *obj_typename = object_get_typename(obj);
|
||
|
bool action_needed = false;
|
||
|
|
||
|
/* exit phase has to finish properly before entering back in reset */
|
||
|
assert(!s->exit_phase_in_progress);
|
||
|
|
||
|
trace_resettable_phase_enter_begin(obj, obj_typename, s->count, type);
|
||
|
|
||
|
/* Only take action if we really enter reset for the 1st time. */
|
||
|
/*
|
||
|
* TODO: if adding more ResetType support, some additional checks
|
||
|
* are probably needed here.
|
||
|
*/
|
||
|
if (s->count++ == 0) {
|
||
|
action_needed = true;
|
||
|
}
|
||
|
/*
|
||
|
* We limit the count to an arbitrary "big" value. The value is big
|
||
|
* enough not to be triggered normally.
|
||
|
* The assert will stop an infinite loop if there is a cycle in the
|
||
|
* reset tree. The loop goes through resettable_foreach_child below
|
||
|
* which at some point will call us again.
|
||
|
*/
|
||
|
assert(s->count <= 50);
|
||
|
|
||
|
/*
|
||
|
* handle the children even if action_needed is at false so that
|
||
|
* child counts are incremented too
|
||
|
*/
|
||
|
resettable_child_foreach(rc, obj, resettable_phase_enter, NULL, type);
|
||
|
|
||
|
/* execute enter phase for the object if needed */
|
||
|
if (action_needed) {
|
||
|
trace_resettable_phase_enter_exec(obj, obj_typename, type,
|
||
|
!!rc->phases.enter);
|
||
|
if (rc->phases.enter && !resettable_get_tr_func(rc, obj)) {
|
||
|
rc->phases.enter(obj, type);
|
||
|
}
|
||
|
s->hold_phase_pending = true;
|
||
|
}
|
||
|
trace_resettable_phase_enter_end(obj, obj_typename, s->count);
|
||
|
}
|
||
|
|
||
|
static void resettable_phase_hold(Object *obj, void *opaque, ResetType type)
|
||
|
{
|
||
|
ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
|
||
|
ResettableState *s = rc->get_state(obj);
|
||
|
const char *obj_typename = object_get_typename(obj);
|
||
|
|
||
|
/* exit phase has to finish properly before entering back in reset */
|
||
|
assert(!s->exit_phase_in_progress);
|
||
|
|
||
|
trace_resettable_phase_hold_begin(obj, obj_typename, s->count, type);
|
||
|
|
||
|
/* handle children first */
|
||
|
resettable_child_foreach(rc, obj, resettable_phase_hold, NULL, type);
|
||
|
|
||
|
/* exec hold phase */
|
||
|
if (s->hold_phase_pending) {
|
||
|
s->hold_phase_pending = false;
|
||
|
ResettableTrFunction tr_func = resettable_get_tr_func(rc, obj);
|
||
|
trace_resettable_phase_hold_exec(obj, obj_typename, !!rc->phases.hold);
|
||
|
if (tr_func) {
|
||
|
trace_resettable_transitional_function(obj, obj_typename);
|
||
|
tr_func(obj);
|
||
|
} else if (rc->phases.hold) {
|
||
|
rc->phases.hold(obj);
|
||
|
}
|
||
|
}
|
||
|
trace_resettable_phase_hold_end(obj, obj_typename, s->count);
|
||
|
}
|
||
|
|
||
|
static void resettable_phase_exit(Object *obj, void *opaque, ResetType type)
|
||
|
{
|
||
|
ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
|
||
|
ResettableState *s = rc->get_state(obj);
|
||
|
const char *obj_typename = object_get_typename(obj);
|
||
|
|
||
|
assert(!s->exit_phase_in_progress);
|
||
|
trace_resettable_phase_exit_begin(obj, obj_typename, s->count, type);
|
||
|
|
||
|
/* exit_phase_in_progress ensures this phase is 'atomic' */
|
||
|
s->exit_phase_in_progress = true;
|
||
|
resettable_child_foreach(rc, obj, resettable_phase_exit, NULL, type);
|
||
|
|
||
|
assert(s->count > 0);
|
||
|
if (s->count == 1) {
|
||
|
trace_resettable_phase_exit_exec(obj, obj_typename, !!rc->phases.exit);
|
||
|
if (rc->phases.exit && !resettable_get_tr_func(rc, obj)) {
|
||
|
rc->phases.exit(obj);
|
||
|
}
|
||
|
s->count = 0;
|
||
|
}
|
||
|
s->exit_phase_in_progress = false;
|
||
|
trace_resettable_phase_exit_end(obj, obj_typename, s->count);
|
||
|
}
|
||
|
|
||
|
void resettable_class_set_parent_phases(ResettableClass *rc,
|
||
|
ResettableEnterPhase enter,
|
||
|
ResettableHoldPhase hold,
|
||
|
ResettableExitPhase exit,
|
||
|
ResettablePhases *parent_phases)
|
||
|
{
|
||
|
*parent_phases = rc->phases;
|
||
|
if (enter) {
|
||
|
rc->phases.enter = enter;
|
||
|
}
|
||
|
if (hold) {
|
||
|
rc->phases.hold = hold;
|
||
|
}
|
||
|
if (exit) {
|
||
|
rc->phases.exit = exit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static const TypeInfo resettable_interface_info = {
|
||
|
.name = TYPE_RESETTABLE_INTERFACE,
|
||
|
.parent = TYPE_INTERFACE,
|
||
|
.class_size = sizeof(ResettableClass),
|
||
|
};
|
||
|
|
||
|
static void reset_register_types(void)
|
||
|
{
|
||
|
type_register_static(&resettable_interface_info);
|
||
|
}
|
||
|
|
||
|
type_init(reset_register_types)
|