blob: 9c9d22992aa47dbf041d2814ffd838b0152df6ab [file] [log] [blame]
// Copyright 2024 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 "src/devices/hrtimer/drivers/aml-hrtimer/aml-hrtimer-server.h"
#include <lib/driver/logging/cpp/structured_logger.h>
#include <lib/fit/defer.h>
#include <variant>
#include "src/devices/hrtimer/drivers/aml-hrtimer/aml-hrtimer-regs.h"
namespace hrtimer {
// To use switch like logic for std::visitor on std::variat.
template <class... Ts>
struct overloaded : Ts... {
using Ts::operator()...;
};
AmlHrtimerServer::AmlHrtimerServer(
async_dispatcher_t* dispatcher, fdf::MmioBuffer mmio,
std::optional<fidl::SyncClient<fuchsia_power_system::ActivityGovernor>> sag,
zx::interrupt irq_a, zx::interrupt irq_b, zx::interrupt irq_c, zx::interrupt irq_d,
zx::interrupt irq_f, zx::interrupt irq_g, zx::interrupt irq_h, zx::interrupt irq_i,
inspect::ComponentInspector& inspect)
: sag_(std::move(sag)), inspect_node_(inspect.root().CreateChild("hrtimer-trace")) {
mmio_.emplace(std::move(mmio));
dispatcher_ = dispatcher;
lease_requests_ = inspect_node_.CreateUint("lease_requests", 0);
lease_replies_ = inspect_node_.CreateUint("lease_replies", 0);
update_requests_ = inspect_node_.CreateUint("update_requests", 0);
update_replies_ = inspect_node_.CreateUint("update_replies", 0);
irq_entries_ = inspect_node_.CreateUint("irq_entries", 0);
irq_exits_ = inspect_node_.CreateUint("irq_exits", 0);
inspect_node_.RecordLazyValues("lazy_values", [&]() {
inspect::Inspector inspector;
auto all = inspector.GetRoot().CreateChild("events");
all.RecordUint("event_index", event_index_);
for (size_t i = 0; i < kMaxInspectEvents; ++i) {
if (events_[i].type == EventType::None) {
continue;
}
const char* type = nullptr;
switch (events_[i].type) {
// clang-format off
case EventType::None: type = ""; break;
case EventType::Start: type = "Start"; break;
case EventType::StartAndWait: type = "StartAndWait"; break;
case EventType::StartAndWait2: type = "StartAndWait2"; break;
case EventType::StartHardware: type = "StartHardware"; break;
case EventType::RetriggerIrq: type = "RetriggerIrq"; break;
case EventType::TriggerIrq: type = "TriggerIrq"; break;
case EventType::TriggerIrqWait: type = "TriggerIrqWait"; break;
case EventType::TriggerIrqWait2: type = "TriggerIrqWait2"; break;
case EventType::Stop: type = "Stop"; break;
case EventType::StopWait: type = "StopWait"; break;
case EventType::StopWait2: type = "StopWait2"; break;
// clang-format on
}
all.RecordChild(std::to_string(i).c_str(), [&](inspect::Node& event) {
event.RecordInt("@time", events_[i].timestamp);
event.RecordUint("id", events_[i].id);
event.RecordString("type", type);
event.RecordUint("data", events_[i].data);
});
}
inspector.emplace(std::move(all));
return fpromise::make_ok_promise(std::move(inspector));
});
timers_[0].irq_handler.set_object(irq_a.get());
timers_[1].irq_handler.set_object(irq_b.get());
timers_[2].irq_handler.set_object(irq_c.get());
timers_[3].irq_handler.set_object(irq_d.get());
// No IRQ on timer id 4.
timers_[5].irq_handler.set_object(irq_f.get());
timers_[6].irq_handler.set_object(irq_g.get());
timers_[7].irq_handler.set_object(irq_h.get());
timers_[8].irq_handler.set_object(irq_i.get());
timers_[0].irq = std::move(irq_a);
timers_[1].irq = std::move(irq_b);
timers_[2].irq = std::move(irq_c);
timers_[3].irq = std::move(irq_d);
// No IRQ on timer id 4.
timers_[5].irq = std::move(irq_f);
timers_[6].irq = std::move(irq_g);
timers_[7].irq = std::move(irq_h);
timers_[8].irq = std::move(irq_i);
timers_[0].irq_handler.Begin(dispatcher_);
timers_[1].irq_handler.Begin(dispatcher_);
timers_[2].irq_handler.Begin(dispatcher_);
timers_[3].irq_handler.Begin(dispatcher_);
// No IRQ on timer id 4.
timers_[5].irq_handler.Begin(dispatcher_);
timers_[6].irq_handler.Begin(dispatcher_);
timers_[7].irq_handler.Begin(dispatcher_);
timers_[8].irq_handler.Begin(dispatcher_);
}
// This method runs on the same dispatcher as all FIDL methods like Start() and Stop().
void AmlHrtimerServer::Timer::HandleIrq(async_dispatcher_t* dispatcher, async::IrqBase* irq_base,
zx_status_t status,
const zx_packet_interrupt_t* interrupt) {
parent.IrqEntries().Add(1);
auto on_exit = fit::defer([this]() {
if (irq.is_valid()) {
zx_status_t status = irq.ack();
if (status != ZX_OK) {
FDF_LOG(ERROR, "IRQ timer id: %zu IRQ error: %s", properties.id,
zx_status_get_string(status));
}
} else {
FDF_LOG(ERROR, "IRQ timer id: %zu invalid IRQ", properties.id);
}
parent.IrqExits().Add(1);
});
if (status != ZX_OK) {
FDF_LOG(ERROR, "IRQ timer id: %zu triggered with error: %s", properties.id,
zx_status_get_string(status));
return;
}
if (!parent.IsTimerStarted(properties.id)) {
FDF_LOG(INFO, "IRQ timer id: %zu IRQ with stopped timer", properties.id);
return;
}
// Timer extends max ticks and its ticks requires re-trigger.
if (properties.extend_max_ticks && start_ticks_left > std::numeric_limits<uint16_t>::max()) {
// Log re-triggering since it may wakeup the system.
start_ticks_left -= std::numeric_limits<uint16_t>::max();
FDF_LOG(DEBUG, "Timer id: %zu IRQ re-trigger, new start ticks left: %lu", properties.id,
start_ticks_left);
parent.RecordEvent(zx::clock::get_monotonic().get(), properties.id, EventType::RetriggerIrq,
start_ticks_left);
size_t timer_index = TimerIndexFromId(properties.id);
auto start_result = parent.StartHardware(timer_index);
if (start_result.is_error()) {
FDF_LOG(ERROR, "Could not restart the hardware for timer id: %zu", properties.id);
std::visit(
overloaded{
[&](StartAndWaitCompleter::Async& completer) {
FDF_LOG(ERROR, "Could not restart the hardware for timer id: %zu", properties.id);
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kBadState));
},
[&](StartAndWait2Completer::Async& completer) {
FDF_LOG(ERROR, "Could not restart the hardware for timer id: %zu", properties.id);
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kBadState));
},
[](std::monostate& empty) {}},
power_enabled_wait_completer);
power_enabled_wait_completer = std::monostate{};
}
return;
}
ZX_ASSERT(last_ticks == start_ticks_left);
// If we have a wait, before we ack the IRQ we take a lease to prevent the system
// from suspending while we notify any clients. The lease is passed to the completer or
// dropped as we exit its scope which guarantees the waiting client was notified before the
// system suspends. We don't exit on error conditions since we need to potentially signal an
// event and ack the IRQ regardless.
std::visit(
overloaded{[&](StartAndWaitCompleter::Async& completer) {
FDF_LOG(DEBUG, "Timer id: %zu IRQ w/wait triggered, last ticks: %lu",
properties.id, last_ticks);
parent.RecordEvent(zx::clock::get_monotonic().get(), properties.id,
EventType::TriggerIrqWait, last_ticks);
parent.lease_requests_.Add(1);
auto wake_lease = (*parent.sag_)->TakeWakeLease(std::string("aml-hrtimer"));
parent.lease_replies_.Add(1);
if (wake_lease.is_error()) {
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kBadState));
} else {
completer.Reply(zx::ok(fuchsia_hardware_hrtimer::DeviceStartAndWaitResponse{
{.keep_alive = std::move(wake_lease->token())}}));
}
},
[&](StartAndWait2Completer::Async& completer) {
FDF_LOG(DEBUG, "Timer id: %zu IRQ w/wait2 triggered, last ticks: %lu",
properties.id, last_ticks);
parent.RecordEvent(zx::clock::get_monotonic().get(), properties.id,
EventType::TriggerIrqWait2, last_ticks);
parent.lease_requests_.Add(1);
auto wake_lease = (*parent.sag_)->TakeWakeLease(std::string("aml-hrtimer"));
parent.lease_replies_.Add(1);
if (wake_lease.is_error()) {
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kBadState));
} else {
completer.Reply(zx::ok(fuchsia_hardware_hrtimer::DeviceStartAndWait2Response{
{.expiration_keep_alive = std::move(wake_lease->token())}}));
}
},
[&](std::monostate& empty) {
FDF_LOG(DEBUG, "Timer id: %zu IRQ triggered, last ticks: %lu", properties.id,
last_ticks);
parent.RecordEvent(zx::clock::get_monotonic().get(), properties.id,
EventType::TriggerIrq, last_ticks);
}},
power_enabled_wait_completer);
power_enabled_wait_completer = std::monostate();
if (event) {
event->signal(0, ZX_EVENT_SIGNALED);
}
}
AmlHrtimerServer::Timer::Timer(AmlHrtimerServer& server, TimersProperties& props)
: parent(server), properties(props) {}
void AmlHrtimerServer::ShutDown() {
for (auto& i : timers_properties_) {
size_t timer_index = TimerIndexFromId(i.id);
if (timers_[timer_index].irq.is_valid()) {
// TODO(https://fxbug.dev/374733154): Make sure no unacked IRQs remain.
zx_status_t status = timers_[timer_index].irq_handler.Cancel();
if (status != ZX_OK) {
FDF_LOG(WARNING, "Canceling IRQ for timer id: %lu failed: %s", i.id,
zx_status_get_string(status));
}
}
std::visit(
overloaded{[](StartAndWaitCompleter::Async& completer) {
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kCanceled));
},
[](StartAndWait2Completer::Async& completer) {
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kCanceled));
},
[](std::monostate& empty) {}},
timers_[timer_index].power_enabled_wait_completer);
timers_[timer_index].power_enabled_wait_completer = std::monostate();
}
}
void AmlHrtimerServer::GetTicksLeft(GetTicksLeftRequest& request,
GetTicksLeftCompleter::Sync& completer) {
size_t timer_index = TimerIndexFromId(request.id());
if (timer_index >= kNumberOfTimers) {
FDF_LOG(ERROR, "Invalid timer id: %lu", request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kInvalidArgs));
return;
}
uint64_t ticks = 0;
switch (request.id()) {
case 0:
ticks = IsaTimerA::Get().ReadFrom(&*mmio_).current_count_value();
break;
case 1:
ticks = IsaTimerB::Get().ReadFrom(&*mmio_).current_count_value();
break;
case 2:
ticks = IsaTimerC::Get().ReadFrom(&*mmio_).current_count_value();
break;
case 3:
ticks = IsaTimerD::Get().ReadFrom(&*mmio_).current_count_value();
break;
case 4:
// We either have not started the timer, or we stopped it.
if (timers_[timer_index].start_ticks_left == 0) {
ticks = 0;
} else {
// Must read lower 32 bits first.
ticks = IsaTimerE::Get().ReadFrom(&*mmio_).current_count_value();
ticks += static_cast<uint64_t>(IsaTimerEHi::Get().ReadFrom(&*mmio_).current_count_value())
<< 32;
if (timers_[timer_index].start_ticks_left > ticks) {
ticks = timers_[timer_index].start_ticks_left - ticks;
} else {
ticks = 0;
}
}
break;
case 5:
ticks = IsaTimerF::Get().ReadFrom(&*mmio_).current_count_value();
break;
case 6:
ticks = IsaTimerG::Get().ReadFrom(&*mmio_).current_count_value();
break;
case 7:
ticks = IsaTimerH::Get().ReadFrom(&*mmio_).current_count_value();
break;
case 8:
ticks = IsaTimerI::Get().ReadFrom(&*mmio_).current_count_value();
break;
default:
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kInvalidArgs));
return;
}
if (timers_properties_[timer_index].extend_max_ticks) {
if (timers_[timer_index].start_ticks_left > std::numeric_limits<uint16_t>::max()) {
ticks += timers_[timer_index].start_ticks_left - std::numeric_limits<uint16_t>::max();
}
}
completer.Reply(zx::ok(ticks));
}
bool AmlHrtimerServer::IsTimerStarted(size_t id) {
switch (id) {
case 0:
return IsaTimerMux::Get().ReadFrom(&*mmio_).TIMERA_EN();
case 1:
return IsaTimerMux::Get().ReadFrom(&*mmio_).TIMERB_EN();
case 2:
return IsaTimerMux::Get().ReadFrom(&*mmio_).TIMERC_EN();
case 3:
return IsaTimerMux::Get().ReadFrom(&*mmio_).TIMERD_EN();
case 5:
return IsaTimerMux1::Get().ReadFrom(&*mmio_).TIMERF_EN();
case 6:
return IsaTimerMux1::Get().ReadFrom(&*mmio_).TIMERG_EN();
case 7:
return IsaTimerMux1::Get().ReadFrom(&*mmio_).TIMERH_EN();
case 8:
return IsaTimerMux1::Get().ReadFrom(&*mmio_).TIMERI_EN();
default:
FDF_LOG(ERROR, "Invalid timer id: %lu", id);
return false;
}
}
void AmlHrtimerServer::ReadTimer(ReadTimerRequest& request, ReadTimerCompleter::Sync& completer) {
completer.Reply(fit::error(fuchsia_hardware_hrtimer::DriverError::kNotSupported));
}
void AmlHrtimerServer::ReadClock(ReadClockRequest& request, ReadClockCompleter::Sync& completer) {
completer.Reply(fit::error(fuchsia_hardware_hrtimer::DriverError::kNotSupported));
}
void AmlHrtimerServer::Stop(StopRequest& request, StopCompleter::Sync& completer) {
size_t timer_index = TimerIndexFromId(request.id());
if (timer_index >= kNumberOfTimers) {
FDF_LOG(ERROR, "Invalid timer id: %lu", request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kInvalidArgs));
return;
}
switch (request.id()) {
case 0:
IsaTimerMux::Get().ReadFrom(&*mmio_).set_TIMERA_EN(false).WriteTo(&*mmio_);
break;
case 1:
IsaTimerMux::Get().ReadFrom(&*mmio_).set_TIMERB_EN(false).WriteTo(&*mmio_);
break;
case 2:
IsaTimerMux::Get().ReadFrom(&*mmio_).set_TIMERC_EN(false).WriteTo(&*mmio_);
break;
case 3:
IsaTimerMux::Get().ReadFrom(&*mmio_).set_TIMERD_EN(false).WriteTo(&*mmio_);
break;
case 4:
// Since there is no way to stop the ticking in the hardware we emulate a stop by clearing
// the ticks requested in start.
timers_[timer_index].start_ticks_left = 0;
break;
case 5:
IsaTimerMux1::Get().ReadFrom(&*mmio_).set_TIMERF_EN(false).WriteTo(&*mmio_);
break;
case 6:
IsaTimerMux1::Get().ReadFrom(&*mmio_).set_TIMERG_EN(false).WriteTo(&*mmio_);
break;
case 7:
IsaTimerMux1::Get().ReadFrom(&*mmio_).set_TIMERH_EN(false).WriteTo(&*mmio_);
break;
case 8:
IsaTimerMux1::Get().ReadFrom(&*mmio_).set_TIMERI_EN(false).WriteTo(&*mmio_);
break;
default:
FDF_LOG(ERROR, "Invalid internal stop timer id: %lu", request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kInternalError));
return;
}
std::visit(
overloaded{
[&](StartAndWaitCompleter::Async& completer) {
FDF_LOG(DEBUG, "Received Stop canceling wait for timer id: %zu", request.id());
RecordEvent(zx::clock::get_monotonic().get(), request.id(), EventType::StopWait, 0);
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kCanceled));
},
[&](StartAndWait2Completer::Async& completer) {
FDF_LOG(DEBUG, "Received Stop canceling wait2 for timer id: %zu", request.id());
RecordEvent(zx::clock::get_monotonic().get(), request.id(), EventType::StopWait2, 0);
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kCanceled));
},
[&](std::monostate& empty) {
FDF_LOG(DEBUG, "Received Stop canceling timer id: %zu", request.id());
RecordEvent(zx::clock::get_monotonic().get(), request.id(), EventType::Stop, 0);
}},
timers_[timer_index].power_enabled_wait_completer);
timers_[timer_index].power_enabled_wait_completer = std::monostate();
completer.Reply(zx::ok());
}
void AmlHrtimerServer::RecordEvent(int64_t now, uint64_t id, EventType type, uint64_t data) {
events_[event_index_].timestamp = now;
events_[event_index_].id = id;
events_[event_index_].type = type;
events_[event_index_].data = data;
if (++event_index_ >= kMaxInspectEvents) {
event_index_ = 0;
}
}
void AmlHrtimerServer::Start(StartRequest& request, StartCompleter::Sync& completer) {
FDF_LOG(DEBUG, "Timer id: %zu start, requested ticks: %lu", request.id(), request.ticks());
RecordEvent(zx::clock::get_monotonic().get(), request.id(), EventType::Start, request.ticks());
size_t timer_index = TimerIndexFromId(request.id());
if (timer_index >= kNumberOfTimers) {
FDF_LOG(ERROR, "Invalid timer id: %lu", request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kInvalidArgs));
return;
}
timers_[timer_index].start_ticks_left = request.ticks();
if (!request.resolution().duration()) {
FDF_LOG(ERROR, "Invalid resolution, no duration for timer id: %zu", request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kInvalidArgs));
return;
}
timers_[timer_index].resolution_nsecs = request.resolution().duration().value();
auto start_result = StartHardware(timer_index);
if (start_result.is_error()) {
completer.Reply(zx::error(start_result.error_value()));
return;
}
completer.Reply(zx::ok());
}
void AmlHrtimerServer::StartAndWait(StartAndWaitRequest& request,
StartAndWaitCompleter::Sync& completer) {
FDF_LOG(DEBUG, "Timer id: %zu start and wait, requested ticks: %lu", request.id(),
request.ticks());
RecordEvent(zx::clock::get_monotonic().get(), request.id(), EventType::StartAndWait,
request.ticks());
size_t timer_index = TimerIndexFromId(request.id());
if (timer_index >= kNumberOfTimers) {
FDF_LOG(ERROR, "Invalid timer id: %lu", request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kInvalidArgs));
return;
}
// Fail power enabled StartAndWait if power management is not functional.
if (!sag_) {
FDF_LOG(ERROR, "Power management not functional. StartAndWait failed for timer id: %lu",
request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kBadState));
return;
}
if (!timers_properties_[timer_index].supports_notifications) {
FDF_LOG(ERROR, "Notifications not supported for timer id: %lu", request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kNotSupported));
return;
}
if (!timers_[timer_index].irq.is_valid()) {
FDF_LOG(ERROR, "Invalid IRQ for timer id: %lu", request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kInternalError));
return;
}
if (!std::holds_alternative<std::monostate>(timers_[timer_index].power_enabled_wait_completer)) {
FDF_LOG(ERROR, "Invalid state for wait, already waiting for timer id: %lu", request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kBadState));
return;
}
timers_[timer_index].start_ticks_left = request.ticks();
if (!request.resolution().duration()) {
FDF_LOG(ERROR, "Invalid resolution, no duration for timer id: %zu", request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kInvalidArgs));
return;
}
timers_[timer_index].resolution_nsecs = request.resolution().duration().value();
auto start_result = StartHardware(timer_index);
if (start_result.is_error()) {
completer.Reply(zx::error(start_result.error_value()));
return;
}
if (request.setup_event().is_valid()) {
request.setup_event().signal(0, ZX_EVENT_SIGNALED);
} else {
FDF_LOG(WARNING, "Invalid setup_event for timer id: %zu", request.id());
}
timers_[timer_index].power_enabled_wait_completer = completer.ToAsync();
}
void AmlHrtimerServer::StartAndWait2(StartAndWait2Request& request,
StartAndWait2Completer::Sync& completer) {
FDF_LOG(DEBUG, "Timer id: %zu start and wait2, requested ticks: %lu", request.id(),
request.ticks());
RecordEvent(zx::clock::get_monotonic().get(), request.id(), EventType::StartAndWait2,
request.ticks());
size_t timer_index = TimerIndexFromId(request.id());
if (timer_index >= kNumberOfTimers) {
FDF_LOG(ERROR, "Invalid timer id: %lu", request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kInvalidArgs));
return;
}
// Fail power enabled StartAndWait2 if power management is not functional.
if (!sag_) {
FDF_LOG(ERROR, "Power management not functional. StartAndWait2 failed for timer id: %lu",
request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kBadState));
return;
}
if (!timers_properties_[timer_index].supports_notifications) {
FDF_LOG(ERROR, "Notifications not supported for timer id: %lu", request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kNotSupported));
return;
}
if (!timers_[timer_index].irq.is_valid()) {
FDF_LOG(ERROR, "Invalid IRQ for timer id: %lu", request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kInternalError));
return;
}
if (!std::holds_alternative<std::monostate>(timers_[timer_index].power_enabled_wait_completer)) {
FDF_LOG(ERROR, "Invalid state for wait, already waiting for timer id: %lu", request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kBadState));
return;
}
timers_[timer_index].start_ticks_left = request.ticks();
if (!request.resolution().duration()) {
FDF_LOG(ERROR, "Invalid resolution, no duration for timer id: %zu", request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kInvalidArgs));
return;
}
timers_[timer_index].resolution_nsecs = request.resolution().duration().value();
auto start_result = StartHardware(timer_index);
if (start_result.is_error()) {
completer.Reply(zx::error(start_result.error_value()));
return;
}
timers_[timer_index].power_enabled_wait_completer = completer.ToAsync();
{
// request.setup_keep_alive() is dropped here since the timer has been setup.
auto to_drop = std::move(request.setup_keep_alive());
}
}
fit::result<const fuchsia_hardware_hrtimer::DriverError> AmlHrtimerServer::StartHardware(
size_t timer_index) {
const uint64_t start_ticks = timers_[timer_index].start_ticks_left;
uint32_t current_ticks = 0;
uint64_t id = timers_properties_[timer_index].id;
switch (timers_properties_[timer_index].max_ticks_support) {
case MaxTicks::k16Bit:
if (timers_properties_[timer_index].extend_max_ticks &&
start_ticks > std::numeric_limits<uint16_t>::max()) {
current_ticks = std::numeric_limits<uint16_t>::max();
} else {
if (start_ticks > std::numeric_limits<uint16_t>::max()) {
FDF_LOG(ERROR, "Invalid ticks range: %lu for timer id: %zu", start_ticks, id);
return fit::error(fuchsia_hardware_hrtimer::DriverError::kInvalidArgs);
}
current_ticks = static_cast<uint32_t>(start_ticks);
}
break;
case MaxTicks::k64Bit:
break;
}
uint32_t input_clock_selection = 0;
const uint64_t resolution_nsecs = timers_[timer_index].resolution_nsecs;
if (timers_properties_[timer_index].supports_1usec &&
timers_properties_[timer_index].supports_10usecs &&
timers_properties_[timer_index].supports_100usecs &&
timers_properties_[timer_index].supports_1msec) {
switch (resolution_nsecs) {
// clang-format off
case zx::usec(1).to_nsecs(): input_clock_selection = 0; break;
case zx::usec(10).to_nsecs(): input_clock_selection = 1; break;
case zx::usec(100).to_nsecs(): input_clock_selection = 2; break;
case zx::msec(1).to_nsecs(): input_clock_selection = 3; break;
// clang-format on
default:
FDF_LOG(ERROR, "Invalid resolution: %lu nsecs for timer id: %zu", resolution_nsecs, id);
return fit::error(fuchsia_hardware_hrtimer::DriverError::kInvalidArgs);
}
FDF_LOG(TRACE, "Timer id: %zu resolution: %lu nsecs", id, resolution_nsecs);
} else if (timers_properties_[timer_index].supports_1usec &&
timers_properties_[timer_index].supports_10usecs &&
timers_properties_[timer_index].supports_100usecs) {
switch (resolution_nsecs) {
// clang-format off
case zx::usec(1).to_nsecs(): input_clock_selection = 1; break;
case zx::usec(10).to_nsecs(): input_clock_selection = 2; break;
case zx::usec(100).to_nsecs(): input_clock_selection = 3; break;
// clang-format on
default:
FDF_LOG(ERROR, "Invalid resolution: %lu nsecs for timer id: %zu", resolution_nsecs, id);
return fit::error(fuchsia_hardware_hrtimer::DriverError::kInvalidArgs);
}
FDF_LOG(TRACE, "Timer id: %zu resolution: %lu nsecs", id, resolution_nsecs);
} else {
FDF_LOG(ERROR, "Invalid resolution state, unsupported combination for timer id: %zu", id);
return fit::error(fuchsia_hardware_hrtimer::DriverError::kInternalError);
}
switch (timer_index) {
case 0:
IsaTimerA::Get().ReadFrom(&*mmio_).set_starting_count_value(current_ticks).WriteTo(&*mmio_);
IsaTimerMux::Get()
.ReadFrom(&*mmio_)
.set_TIMERA_EN(true)
.set_TIMERA_MODE(false)
.set_TIMERA_input_clock_selection(input_clock_selection)
.WriteTo(&*mmio_);
break;
case 1:
IsaTimerB::Get().ReadFrom(&*mmio_).set_starting_count_value(current_ticks).WriteTo(&*mmio_);
IsaTimerMux::Get()
.ReadFrom(&*mmio_)
.set_TIMERB_EN(true)
.set_TIMERB_MODE(false)
.set_TIMERB_input_clock_selection(input_clock_selection)
.WriteTo(&*mmio_);
break;
case 2:
IsaTimerC::Get().ReadFrom(&*mmio_).set_starting_count_value(current_ticks).WriteTo(&*mmio_);
IsaTimerMux::Get()
.ReadFrom(&*mmio_)
.set_TIMERC_EN(true)
.set_TIMERC_MODE(false)
.set_TIMERC_input_clock_selection(input_clock_selection)
.WriteTo(&*mmio_);
break;
case 3:
IsaTimerD::Get().ReadFrom(&*mmio_).set_starting_count_value(current_ticks).WriteTo(&*mmio_);
IsaTimerMux::Get()
.ReadFrom(&*mmio_)
.set_TIMERD_EN(true)
.set_TIMERD_MODE(false)
.set_TIMERD_input_clock_selection(input_clock_selection)
.WriteTo(&*mmio_);
break;
case 4:
IsaTimerMux::Get()
.ReadFrom(&*mmio_)
.set_TIMERE_input_clock_selection(input_clock_selection)
.WriteTo(&*mmio_);
IsaTimerE::Get().ReadFrom(&*mmio_).set_current_count_value(0).WriteTo(&*mmio_);
break;
case 5:
IsaTimerF::Get().ReadFrom(&*mmio_).set_starting_count_value(current_ticks).WriteTo(&*mmio_);
IsaTimerMux1::Get()
.ReadFrom(&*mmio_)
.set_TIMERF_EN(true)
.set_TIMERF_MODE(false)
.set_TIMERF_input_clock_selection(input_clock_selection)
.WriteTo(&*mmio_);
break;
case 6:
IsaTimerG::Get().ReadFrom(&*mmio_).set_starting_count_value(current_ticks).WriteTo(&*mmio_);
IsaTimerMux1::Get()
.ReadFrom(&*mmio_)
.set_TIMERG_EN(true)
.set_TIMERG_MODE(false)
.set_TIMERG_input_clock_selection(input_clock_selection)
.WriteTo(&*mmio_);
break;
case 7:
IsaTimerH::Get().ReadFrom(&*mmio_).set_starting_count_value(current_ticks).WriteTo(&*mmio_);
IsaTimerMux1::Get()
.ReadFrom(&*mmio_)
.set_TIMERH_EN(true)
.set_TIMERH_MODE(false)
.set_TIMERH_input_clock_selection(input_clock_selection)
.WriteTo(&*mmio_);
break;
case 8:
IsaTimerI::Get().ReadFrom(&*mmio_).set_starting_count_value(current_ticks).WriteTo(&*mmio_);
IsaTimerMux1::Get()
.ReadFrom(&*mmio_)
.set_TIMERI_EN(true)
.set_TIMERI_MODE(false)
.set_TIMERI_input_clock_selection(input_clock_selection)
.WriteTo(&*mmio_);
break;
default:
FDF_LOG(ERROR, "Invalid internal state for timer id: %zu", id);
return fit::error(fuchsia_hardware_hrtimer::DriverError::kInternalError);
}
timers_[timer_index].last_ticks = current_ticks;
FDF_LOG(DEBUG, "Timer id: %zu started, start ticks left: %lu last ticks: %lu", id,
timers_[timer_index].start_ticks_left, timers_[timer_index].last_ticks);
RecordEvent(zx::clock::get_monotonic().get(), id, EventType::StartHardware,
timers_[timer_index].last_ticks);
return fit::success();
}
void AmlHrtimerServer::SetEvent(SetEventRequest& request, SetEventCompleter::Sync& completer) {
size_t timer_index = TimerIndexFromId(request.id());
if (timer_index >= kNumberOfTimers) {
FDF_LOG(ERROR, "Invalid timer id: %lu", request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kInvalidArgs));
return;
}
if (!timers_properties_[timer_index].supports_notifications) {
FDF_LOG(ERROR, "Notifications not supported for timer id: %lu", request.id());
completer.Reply(zx::error(fuchsia_hardware_hrtimer::DriverError::kNotSupported));
return;
}
timers_[timer_index].event.emplace(std::move(request.event()));
completer.Reply(zx::ok());
}
void AmlHrtimerServer::GetProperties(GetPropertiesCompleter::Sync& completer) {
std::vector<fuchsia_hardware_hrtimer::TimerProperties> timers_properties;
for (auto& i : timers_properties_) {
fuchsia_hardware_hrtimer::TimerProperties timer_properties;
timer_properties.id(i.id);
std::vector<fuchsia_hardware_hrtimer::Resolution> resolutions;
if (i.supports_1usec) {
resolutions.emplace_back(
fuchsia_hardware_hrtimer::Resolution::WithDuration(zx::usec(1).to_nsecs()));
}
if (i.supports_10usecs) {
resolutions.emplace_back(
fuchsia_hardware_hrtimer::Resolution::WithDuration(zx::usec(10).to_nsecs()));
}
if (i.supports_100usecs) {
resolutions.emplace_back(
fuchsia_hardware_hrtimer::Resolution::WithDuration(zx::usec(100).to_nsecs()));
}
if (i.supports_1msec) {
resolutions.emplace_back(
fuchsia_hardware_hrtimer::Resolution::WithDuration(zx::msec(1).to_nsecs()));
}
timer_properties.supported_resolutions(std::move(resolutions));
switch (i.max_ticks_support) {
case MaxTicks::k16Bit:
if (i.extend_max_ticks) {
timer_properties.max_ticks(std::numeric_limits<uint64_t>::max());
} else {
timer_properties.max_ticks(std::numeric_limits<uint16_t>::max());
}
break;
case MaxTicks::k64Bit:
timer_properties.max_ticks(std::numeric_limits<uint64_t>::max());
break;
}
timer_properties.supports_event(i.supports_notifications);
// Only support wait if we can return a lease in StartAndWait.
timer_properties.supports_wait(sag_ && i.supports_notifications);
timer_properties.supports_read(false);
timers_properties.emplace_back(std::move(timer_properties));
}
fuchsia_hardware_hrtimer::Properties properties = {};
properties.timers_properties(std::move(timers_properties));
completer.Reply(std::move(properties));
}
void AmlHrtimerServer::handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_hardware_hrtimer::Device> metadata,
fidl::UnknownMethodCompleter::Sync& completer) {}
} // namespace hrtimer