blob: a115e5525c161647b20bfb6b49cca478e1a3a322 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdio.h>
#include <zircon/assert.h>
#include <fbl/auto_lock.h>
#include "baseclock.h"
#include "clocktree.h"
#include "types.h"
namespace clk {
namespace {
constexpr bool IsError(const zx_status_t st) {
// A clock may or may not choose to implement any of the core clock operations.
// If an operation is not implemented the method must return ZX_ERR_NOT_SUPPORTED
// which is not considered an error.
return st != ZX_OK && st != ZX_ERR_NOT_SUPPORTED;
}
} // namespace
zx_status_t Tree::Enable(const uint32_t id) {
fbl::AutoLock lock(&topology_mutex_);
return EnableLocked(id);
}
zx_status_t Tree::EnableLocked(const uint32_t id) {
if (id == kClkNoParent) {
return ZX_OK;
} // At the root.
if (!InRange(id)) {
return ZX_ERR_OUT_OF_RANGE;
}
BaseClock* self = clocks_[id];
const uint32_t parent_id = self->ParentId();
const uint32_t self_enable_count = self->EnableCount();
if (self_enable_count == 0) {
zx_status_t parent_enable_st = EnableLocked(parent_id);
if (IsError(parent_enable_st)) {
return parent_enable_st;
}
zx_status_t st = self->Enable();
if (IsError(st)) {
DisableLocked(parent_id);
return st;
}
}
self->SetEnableCount(self_enable_count + 1);
return ZX_OK;
}
zx_status_t Tree::Disable(const uint32_t id) {
fbl::AutoLock lock(&topology_mutex_);
return DisableLocked(id);
}
zx_status_t Tree::DisableLocked(const uint32_t id) {
if (id == kClkNoParent) {
return ZX_OK;
} // At the root.
if (!InRange(id)) {
return ZX_ERR_OUT_OF_RANGE;
}
BaseClock* self = clocks_[id];
const uint32_t parent_id = self->ParentId();
if (self->EnableCount() == 0) {
return ZX_ERR_BAD_STATE;
}
// Decremet the refs.
self->SetEnableCount(self->EnableCount() - 1);
if (self->EnableCount() > 0) {
return ZX_OK;
}
// At this point we're about to disable the clock so we should definitely
// have 0 refs.
ZX_ASSERT(self->EnableCount() == 0);
// Disable this clock and then disable its parent. Don't try to unwind if
// disable fails.
zx_status_t self_st = self->Disable();
zx_status_t parent_st = DisableLocked(parent_id);
// If this clock fails to disable and a clock somewhere in the parent chain
// fails to disable, return the error caused by the clock closest to the
// caller (i.e. this clock).
if (IsError(self_st)) {
return self_st;
}
if (IsError(parent_st)) {
return parent_st;
}
return ZX_OK;
}
zx_status_t Tree::IsEnabled(const uint32_t id, bool* out) {
fbl::AutoLock lock(&topology_mutex_);
return IsEnabledLocked(id, out);
}
zx_status_t Tree::IsEnabledLocked(const uint32_t id, bool* out) {
if (!InRange(id)) {
return ZX_ERR_OUT_OF_RANGE;
}
BaseClock* self = clocks_[id];
return self->IsEnabled(out);
}
zx_status_t Tree::SetRate(const uint32_t id, const Hertz rate) {
fbl::AutoLock lock(&topology_mutex_);
return SetRateLocked(id, rate);
}
zx_status_t Tree::SetRateLocked(const uint32_t id, const Hertz rate) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Tree::QuerySupportedRate(const uint32_t id, const Hertz max) {
fbl::AutoLock lock(&topology_mutex_);
return QuerySupportedRateLocked(id, max);
}
zx_status_t Tree::QuerySupportedRateLocked(const uint32_t id, const Hertz max) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Tree::GetRate(const uint32_t id, Hertz* out) {
fbl::AutoLock lock(&topology_mutex_);
return GetRateLocked(id, out);
}
zx_status_t Tree::GetRateLocked(const uint32_t id, Hertz* out) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t Tree::SetInput(const uint32_t id, const uint32_t input_index) {
fbl::AutoLock lock(&topology_mutex_);
return SetInputLocked(id, input_index);
}
zx_status_t Tree::SetInputLocked(const uint32_t id, const uint32_t input_index) {
if (!InRange(id)) {
return ZX_ERR_OUT_OF_RANGE;
}
BaseClock* self = clocks_[id];
uint32_t old_parent_id = self->ParentId();
uint32_t new_parent_id;
zx_status_t st = self->GetInputId(input_index, &new_parent_id);
if (st != ZX_OK) {
return st;
}
if (!InRange(new_parent_id)) {
return ZX_ERR_INVALID_ARGS;
}
if (old_parent_id == new_parent_id) {
// Input is already set correctly, no work to do.
return ZX_OK;
}
const bool should_migrate_refs = self->EnableCount() > 0;
// (1) if `self` is enabled then it should add a ref to its new parent.
if (should_migrate_refs) {
st = EnableLocked(new_parent_id);
if (st != ZX_OK) {
return st;
}
}
// (2) Perform the reparent operation.
st = self->SetInput(input_index);
if (st != ZX_OK) {
if (should_migrate_refs) {
DisableLocked(new_parent_id);
}
return st;
}
// (3) If `self` is enabled then it should remember to drop a ref to its old
// parent after the reparent operation completes.
if (should_migrate_refs) {
DisableLocked(old_parent_id);
}
return ZX_OK;
}
zx_status_t Tree::GetNumInputs(const uint32_t id, uint32_t* out) {
fbl::AutoLock lock(&topology_mutex_);
return GetNumInputsLocked(id, out);
}
zx_status_t Tree::GetNumInputsLocked(const uint32_t id, uint32_t* out) {
if (!InRange(id)) {
return ZX_ERR_OUT_OF_RANGE;
}
BaseClock* self = clocks_[id];
return self->GetNumInputs(out);
}
zx_status_t Tree::GetInput(const uint32_t id, uint32_t* out) {
fbl::AutoLock lock(&topology_mutex_);
return GetInputLocked(id, out);
}
zx_status_t Tree::GetInputLocked(const uint32_t id, uint32_t* out) {
if (!InRange(id)) {
return ZX_ERR_OUT_OF_RANGE;
}
BaseClock* self = clocks_[id];
return self->GetInput(out);
}
// Helper functions
bool Tree::InRange(const uint32_t index) const { return index < count_; }
} // namespace clk