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