qemu/hw/core/resettable.c

301 lines
9.4 KiB
C
Raw Normal View History

hw/core: create Resettable QOM interface This commit defines an interface allowing multi-phase reset. This aims to solve a problem of the actual single-phase reset (built in DeviceClass and BusClass): reset behavior is dependent on the order in which reset handlers are called. In particular doing external side-effect (like setting an qemu_irq) is problematic because receiving object may not be reset yet. The Resettable interface divides the reset in 3 well defined phases. To reset an object tree, all 1st phases are executed then all 2nd then all 3rd. See the comments in include/hw/resettable.h for a more complete description. The interface defines 3 phases to let the future possibility of holding an object into reset for some time. The qdev/qbus reset in DeviceClass and BusClass will be modified in following commits to use this interface. A mechanism is provided to allow executing a transitional reset handler in place of the 2nd phase which is executed in children-then-parent order inside a tree. This will allow to transition devices and buses smoothly while keeping the exact current qdev/qbus reset behavior for now. Documentation will be added in a following commit. Signed-off-by: Damien Hedde <damien.hedde@greensocs.com> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com> Message-id: 20200123132823.1117486-4-damien.hedde@greensocs.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
2020-01-30 19:02:03 +03:00
/*
* 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.
*
* exit_phase_in_progress:
* count the number of exit phase we are in.
*
* Note: These flags are only used to guarantee (using asserts) that the reset
* API is used correctly. We can use global variables because we rely on the
hw/core: create Resettable QOM interface This commit defines an interface allowing multi-phase reset. This aims to solve a problem of the actual single-phase reset (built in DeviceClass and BusClass): reset behavior is dependent on the order in which reset handlers are called. In particular doing external side-effect (like setting an qemu_irq) is problematic because receiving object may not be reset yet. The Resettable interface divides the reset in 3 well defined phases. To reset an object tree, all 1st phases are executed then all 2nd then all 3rd. See the comments in include/hw/resettable.h for a more complete description. The interface defines 3 phases to let the future possibility of holding an object into reset for some time. The qdev/qbus reset in DeviceClass and BusClass will be modified in following commits to use this interface. A mechanism is provided to allow executing a transitional reset handler in place of the 2nd phase which is executed in children-then-parent order inside a tree. This will allow to transition devices and buses smoothly while keeping the exact current qdev/qbus reset behavior for now. Documentation will be added in a following commit. Signed-off-by: Damien Hedde <damien.hedde@greensocs.com> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com> Message-id: 20200123132823.1117486-4-damien.hedde@greensocs.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
2020-01-30 19:02:03 +03:00
* iothread mutex to ensure only one reset operation is in a progress at a
* given time.
*/
static bool enter_phase_in_progress;
static unsigned exit_phase_in_progress;
hw/core: create Resettable QOM interface This commit defines an interface allowing multi-phase reset. This aims to solve a problem of the actual single-phase reset (built in DeviceClass and BusClass): reset behavior is dependent on the order in which reset handlers are called. In particular doing external side-effect (like setting an qemu_irq) is problematic because receiving object may not be reset yet. The Resettable interface divides the reset in 3 well defined phases. To reset an object tree, all 1st phases are executed then all 2nd then all 3rd. See the comments in include/hw/resettable.h for a more complete description. The interface defines 3 phases to let the future possibility of holding an object into reset for some time. The qdev/qbus reset in DeviceClass and BusClass will be modified in following commits to use this interface. A mechanism is provided to allow executing a transitional reset handler in place of the 2nd phase which is executed in children-then-parent order inside a tree. This will allow to transition devices and buses smoothly while keeping the exact current qdev/qbus reset behavior for now. Documentation will be added in a following commit. Signed-off-by: Damien Hedde <damien.hedde@greensocs.com> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com> Message-id: 20200123132823.1117486-4-damien.hedde@greensocs.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
2020-01-30 19:02:03 +03:00
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);
exit_phase_in_progress += 1;
hw/core: create Resettable QOM interface This commit defines an interface allowing multi-phase reset. This aims to solve a problem of the actual single-phase reset (built in DeviceClass and BusClass): reset behavior is dependent on the order in which reset handlers are called. In particular doing external side-effect (like setting an qemu_irq) is problematic because receiving object may not be reset yet. The Resettable interface divides the reset in 3 well defined phases. To reset an object tree, all 1st phases are executed then all 2nd then all 3rd. See the comments in include/hw/resettable.h for a more complete description. The interface defines 3 phases to let the future possibility of holding an object into reset for some time. The qdev/qbus reset in DeviceClass and BusClass will be modified in following commits to use this interface. A mechanism is provided to allow executing a transitional reset handler in place of the 2nd phase which is executed in children-then-parent order inside a tree. This will allow to transition devices and buses smoothly while keeping the exact current qdev/qbus reset behavior for now. Documentation will be added in a following commit. Signed-off-by: Damien Hedde <damien.hedde@greensocs.com> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com> Message-id: 20200123132823.1117486-4-damien.hedde@greensocs.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
2020-01-30 19:02:03 +03:00
resettable_phase_exit(obj, NULL, type);
exit_phase_in_progress -= 1;
hw/core: create Resettable QOM interface This commit defines an interface allowing multi-phase reset. This aims to solve a problem of the actual single-phase reset (built in DeviceClass and BusClass): reset behavior is dependent on the order in which reset handlers are called. In particular doing external side-effect (like setting an qemu_irq) is problematic because receiving object may not be reset yet. The Resettable interface divides the reset in 3 well defined phases. To reset an object tree, all 1st phases are executed then all 2nd then all 3rd. See the comments in include/hw/resettable.h for a more complete description. The interface defines 3 phases to let the future possibility of holding an object into reset for some time. The qdev/qbus reset in DeviceClass and BusClass will be modified in following commits to use this interface. A mechanism is provided to allow executing a transitional reset handler in place of the 2nd phase which is executed in children-then-parent order inside a tree. This will allow to transition devices and buses smoothly while keeping the exact current qdev/qbus reset behavior for now. Documentation will be added in a following commit. Signed-off-by: Damien Hedde <damien.hedde@greensocs.com> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com> Message-id: 20200123132823.1117486-4-damien.hedde@greensocs.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
2020-01-30 19:02:03 +03:00
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);
hw/core/resettable: fix reset level counting The code for handling the reset level count in the Resettable code has two issues: The reset count is only decremented for the 1->0 case. This means that if there's ever a nested reset that takes the count to 2 then it will never again be decremented. Eventually the count will exceed the '50' limit in resettable_phase_enter() and QEMU will trip over the assertion failure. The repro case in issue 1266 is an example of this that happens now the SCSI subsystem uses three-phase reset. Secondly, the count is decremented only after the exit phase handler is called. Moving the reset count decrement from "just after" to "just before" calling the exit phase handler allows resettable_is_in_reset() to return false during the handler execution. This simplifies reset handling in resettable devices. Typically, a function that updates the device state will just need to read the current reset state and not anymore treat the "in a reset-exit transition" as a special case. Note that the semantics change to the *_is_in_reset() functions will have no effect on the current codebase, because only two devices (hw/char/cadence_uart.c and hw/misc/zynq_sclr.c) currently call those functions, and in neither case do they do it from the device's exit phase methed. Fixes: 4a5fc890 ("scsi: Use device_cold_reset() and bus_cold_reset()") Resolves: https://gitlab.com/qemu-project/qemu/-/issues/1266 Signed-off-by: Damien Hedde <damien.hedde@greensocs.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reported-by: Michael Peter <michael.peter@hensoldt-cyber.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20221020142749.3357951-1-peter.maydell@linaro.org Buglink: https://bugs.launchpad.net/qemu/+bug/1905297 Reported-by: Michael Peter <michael.peter@hensoldt-cyber.com> [PMM: adjust the docs paragraph changed to get the name of the 'enter' phase right and to clarify exactly when the count is adjusted; rewrite the commit message] Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
2022-10-20 17:27:49 +03:00
if (--s->count == 0) {
hw/core: create Resettable QOM interface This commit defines an interface allowing multi-phase reset. This aims to solve a problem of the actual single-phase reset (built in DeviceClass and BusClass): reset behavior is dependent on the order in which reset handlers are called. In particular doing external side-effect (like setting an qemu_irq) is problematic because receiving object may not be reset yet. The Resettable interface divides the reset in 3 well defined phases. To reset an object tree, all 1st phases are executed then all 2nd then all 3rd. See the comments in include/hw/resettable.h for a more complete description. The interface defines 3 phases to let the future possibility of holding an object into reset for some time. The qdev/qbus reset in DeviceClass and BusClass will be modified in following commits to use this interface. A mechanism is provided to allow executing a transitional reset handler in place of the 2nd phase which is executed in children-then-parent order inside a tree. This will allow to transition devices and buses smoothly while keeping the exact current qdev/qbus reset behavior for now. Documentation will be added in a following commit. Signed-off-by: Damien Hedde <damien.hedde@greensocs.com> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com> Message-id: 20200123132823.1117486-4-damien.hedde@greensocs.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
2020-01-30 19:02:03 +03:00
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->exit_phase_in_progress = false;
trace_resettable_phase_exit_end(obj, obj_typename, s->count);
}
/*
* resettable_get_count:
* Get the count of the Resettable object @obj. Return 0 if @obj is NULL.
*/
static unsigned resettable_get_count(Object *obj)
{
if (obj) {
ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
return rc->get_state(obj)->count;
}
return 0;
}
void resettable_change_parent(Object *obj, Object *newp, Object *oldp)
{
ResettableClass *rc = RESETTABLE_GET_CLASS(obj);
ResettableState *s = rc->get_state(obj);
unsigned newp_count = resettable_get_count(newp);
unsigned oldp_count = resettable_get_count(oldp);
/*
* Ensure we do not change parent when in enter or exit phase.
* During these phases, the reset subtree being updated is partly in reset
* and partly not in reset (it depends on the actual position in
* resettable_child_foreach()s). We are not able to tell in which part is a
* leaving or arriving device. Thus we cannot set the reset count of the
* moving device to the proper value.
*/
assert(!enter_phase_in_progress && !exit_phase_in_progress);
trace_resettable_change_parent(obj, oldp, oldp_count, newp, newp_count);
/*
* At most one of the two 'for' loops will be executed below
* in order to cope with the difference between the two counts.
*/
/* if newp is more reset than oldp */
for (unsigned i = oldp_count; i < newp_count; i++) {
resettable_assert_reset(obj, RESET_TYPE_COLD);
}
/*
* if obj is leaving a bus under reset, we need to ensure
* hold phase is not pending.
*/
if (oldp_count && s->hold_phase_pending) {
resettable_phase_hold(obj, NULL, RESET_TYPE_COLD);
}
/* if oldp is more reset than newp */
for (unsigned i = newp_count; i < oldp_count; i++) {
resettable_release_reset(obj, RESET_TYPE_COLD);
}
}
void resettable_cold_reset_fn(void *opaque)
{
resettable_reset((Object *) opaque, RESET_TYPE_COLD);
}
hw/core: create Resettable QOM interface This commit defines an interface allowing multi-phase reset. This aims to solve a problem of the actual single-phase reset (built in DeviceClass and BusClass): reset behavior is dependent on the order in which reset handlers are called. In particular doing external side-effect (like setting an qemu_irq) is problematic because receiving object may not be reset yet. The Resettable interface divides the reset in 3 well defined phases. To reset an object tree, all 1st phases are executed then all 2nd then all 3rd. See the comments in include/hw/resettable.h for a more complete description. The interface defines 3 phases to let the future possibility of holding an object into reset for some time. The qdev/qbus reset in DeviceClass and BusClass will be modified in following commits to use this interface. A mechanism is provided to allow executing a transitional reset handler in place of the 2nd phase which is executed in children-then-parent order inside a tree. This will allow to transition devices and buses smoothly while keeping the exact current qdev/qbus reset behavior for now. Documentation will be added in a following commit. Signed-off-by: Damien Hedde <damien.hedde@greensocs.com> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com> Message-id: 20200123132823.1117486-4-damien.hedde@greensocs.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
2020-01-30 19:02:03 +03:00
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)