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