blob: 06d6b20f366d0db8a4b229d09cc4773bff26027b [file]
// Copyright 2021 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 "dispatcher_internals.h"
#include "dispatcher_coordinator.h"
#include "src/devices/lib/log/log.h"
#include "thread_pool.h"
namespace driver_runtime {
AsyncIrq::AsyncIrq(async_irq_t* original_irq, Dispatcher& dispatcher)
: async_irq_t{{ASYNC_STATE_INIT}, &AsyncIrq::Handler, original_irq->object},
original_irq_(original_irq) {
// Store a pointer to our IRQ wrapper, so |UnbindIrq| can back map from the user's IRQ object.
original_irq_->state.reserved[0] = reinterpret_cast<uintptr_t>(this);
}
AsyncIrq::~AsyncIrq() {
// This shouldn't destruct until after the irq has been unbound, either by the user or
// |ShutdownAsync|.
ZX_ASSERT(dispatcher_ == nullptr);
}
// static
zx_status_t AsyncIrq::Bind(std::unique_ptr<AsyncIrq> irq, Dispatcher& dispatcher) {
// The AsyncIrq will hold the dispatcher reference until the irq is unbound.
irq->SetDispatcherRef(fbl::RefPtr(&dispatcher));
auto* irq_ref = irq.get();
dispatcher.AddIrqLocked(std::move(irq));
zx_status_t status = async_bind_irq(dispatcher.process_shared_dispatcher_, irq_ref);
if (status != ZX_OK) {
ZX_ASSERT(dispatcher.RemoveIrqLocked(irq_ref) != nullptr);
irq->SetDispatcherRef(nullptr);
return status;
}
return ZX_OK;
}
bool AsyncIrq::Unbind() {
auto dispatcher = GetDispatcherRef();
if (!dispatcher) {
return false;
}
auto status = async_unbind_irq(dispatcher->process_shared_dispatcher_, this);
if (status != ZX_OK) {
return false;
}
SetDispatcherRef(nullptr);
original_irq_->state.reserved[0] = 0;
return true;
}
std::unique_ptr<driver_runtime::CallbackRequest> AsyncIrq::CreateCallbackRequest(
Dispatcher& dispatcher, bool locked) __TA_NO_THREAD_SAFETY_ANALYSIS {
auto async_dispatcher = dispatcher.GetAsyncDispatcher();
// TODO(https://fxbug.dev/42052990): We should consider something more efficient than creating a
// callback request each time the irq is triggered. This is complex due to an AsyncIrq not having
// a 1:1 mapping to interrupt callbacks, and we cannot easily return ownership of a
// |CallbackRequest| after dispatching it. See https://fxbug.dev/42052990 for a longer
// explanation.
auto callback_request =
std::make_unique<driver_runtime::CallbackRequest>(CallbackRequest::RequestType::kIrq);
callback_request->set_handle(original_irq_->object);
bool is_wake = false;
if (locked) {
is_wake = dispatcher.IsWakeVectorLocked(original_irq_->object, 0);
} else {
fbl::AutoLock lock(&dispatcher.callback_lock_);
is_wake = dispatcher.IsWakeVectorLocked(original_irq_->object, 0);
}
if (is_wake) {
callback_request->set_request_type(CallbackRequest::RequestType::kWakeIrq);
}
driver_runtime::Callback callback =
[this, async_dispatcher](std::unique_ptr<driver_runtime::CallbackRequest> callback_request,
zx_status_t status) {
// We should not clear the reserved state, as this AsyncIrq object is still bound for
// future interrupts.
original_irq_->handler(async_dispatcher, original_irq_, status, &interrupt_packet_);
};
callback_request->SetCallback(&dispatcher, std::move(callback), this);
return callback_request;
}
// static
void AsyncIrq::Handler(async_dispatcher_t* dispatcher, async_irq_t* irq, zx_status_t status,
const zx_packet_interrupt_t* packet) {
static_cast<AsyncIrq*>(irq)->OnSignal(dispatcher, status, packet);
}
void AsyncIrq::OnSignal(async_dispatcher_t* global_dispatcher, zx_status_t status,
const zx_packet_interrupt_t* packet) {
fbl::RefPtr<Dispatcher> dispatcher = GetDispatcherRef();
// This may be cleared if the irq has already been unbound, but this irq packet was already pulled
// from the port. If so, we should not deliver the irq to the user.
if (!dispatcher) {
return;
}
interrupt_packet_ = *packet;
// We do not hold the irq lock before calling |QueueIrq|, as it would cause
// incorrect lock ordering.
dispatcher->QueueIrq(this, status);
dispatcher->thread_pool()->OnThreadWakeup();
}
void EventWaiter::HandleEvent(std::unique_ptr<EventWaiter> event_waiter,
async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
if (status == ZX_ERR_CANCELED) {
LOGF(TRACE, "Dispatcher: event waiter shutting down");
event_waiter->dispatcher_ref_->SetEventWaiter(nullptr);
event_waiter->dispatcher_ref_ = nullptr;
return;
} else if (status != ZX_OK) {
LOGF(ERROR, "Dispatcher: event waiter error: %d", status);
event_waiter->dispatcher_ref_->SetEventWaiter(nullptr);
event_waiter->dispatcher_ref_ = nullptr;
return;
}
if (signal->observed & ZX_USER_SIGNAL_0) {
// The callback is in charge of calling |BeginWaitWithRef| on the event waiter.
fbl::RefPtr<Dispatcher> dispatcher_ref = std::move(event_waiter->dispatcher_ref_);
event_waiter->InvokeCallback(std::move(event_waiter), dispatcher_ref);
} else {
LOGF(ERROR, "Dispatcher: event waiter got unexpected signals: %x", signal->observed);
}
}
// static
zx_status_t EventWaiter::BeginWaitWithRef(std::unique_ptr<EventWaiter> event,
fbl::RefPtr<Dispatcher> dispatcher,
async_dispatcher_t* async_dispatcher) {
ZX_ASSERT(dispatcher != nullptr);
event->dispatcher_ref_ = dispatcher;
return BeginWait(std::move(event), async_dispatcher);
}
AsyncWait::AsyncWait(async_wait_t* original_wait, Dispatcher& dispatcher)
: CallbackRequest(CallbackRequest::RequestType::kWait),
async_wait_t{{ASYNC_STATE_INIT},
&AsyncWait::Handler,
original_wait->object,
original_wait->trigger,
original_wait->options},
original_wait_(original_wait) {
// Use one of the async_wait_t's reserved fields to stash a pointer to the AsyncWait object.
original_wait_->state.reserved[0] = reinterpret_cast<uintptr_t>(this);
auto async_dispatcher = dispatcher.GetAsyncDispatcher();
driver_runtime::Callback callback =
[this, async_dispatcher](std::unique_ptr<driver_runtime::CallbackRequest> callback_request,
zx_status_t status) {
// Clear the pointer to the AsyncWait object.
original_wait_->state.reserved[0] = 0;
zx_packet_signal_t* signal_packet = signal_packet_ ? &signal_packet_.value() : nullptr;
original_wait_->handler(async_dispatcher, original_wait_, status, signal_packet);
};
// Note that this callback is called *after* |OnSignal|, which is the immediate callback that is
// invoked when the async wait is signaled.
SetCallback(&dispatcher, std::move(callback), original_wait_);
}
AsyncWait::~AsyncWait() {
// This shouldn't destruct until the wait was canceled or it has been completed.
ZX_ASSERT(dispatcher_ref_ == nullptr);
}
// static
zx_status_t AsyncWait::BeginWait(std::unique_ptr<AsyncWait> wait, Dispatcher& dispatcher) {
// Purposefully create a cycle which is broken in Cancel or OnSignal.
// This needs to be done ahead of starting the async wait in case another thread on the dispatcher
// signals the dispatcher.
auto dispatcher_ref = fbl::RefPtr(&dispatcher);
wait->dispatcher_ref_ = fbl::ExportToRawPtr(&dispatcher_ref);
auto* wait_ref = wait.get();
dispatcher.AddWaitLocked(std::move(wait));
zx_status_t status = async_begin_wait(
const_cast<async_dispatcher_t*>(dispatcher.process_shared_dispatcher()), wait_ref);
if (status != ZX_OK) {
fbl::ImportFromRawPtr(wait_ref->dispatcher_ref_.exchange(nullptr));
dispatcher.RemoveWaitLocked(wait_ref);
return status;
}
return ZX_OK;
}
bool AsyncWait::Cancel() {
// We do a load here rather than an exchange as OnSignal may still be triggered and we need to
// avoid preventing it from accessing the |dispatcher_ref_|.
auto* dispatcher_ref = dispatcher_ref_.load();
if (dispatcher_ref == nullptr) {
// OnSignal was triggered in another thread.
return false;
}
auto dispatcher = fbl::RefPtr(dispatcher_ref);
auto status = async_cancel_wait(
const_cast<async_dispatcher_t*>(dispatcher->process_shared_dispatcher()), this);
if (status != ZX_OK) {
// OnSignal was triggered in another thread, or is about to be.
ZX_DEBUG_ASSERT(status == ZX_ERR_NOT_FOUND);
return false;
}
// It is now safe to recover the dispatcher reference.
dispatcher_ref = dispatcher_ref_.exchange(nullptr);
ZX_DEBUG_ASSERT(dispatcher_ref != nullptr);
fbl::ImportFromRawPtr(dispatcher_ref);
return true;
}
// static
void AsyncWait::Handler(async_dispatcher_t* dispatcher, async_wait_t* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
static_cast<AsyncWait*>(wait)->OnSignal(dispatcher, status, signal);
}
void AsyncWait::OnSignal(async_dispatcher_t* async_dispatcher, zx_status_t status,
const zx_packet_signal_t* signal) {
auto* dispatcher_ref = dispatcher_ref_.exchange(nullptr);
ZX_DEBUG_ASSERT(dispatcher_ref != nullptr);
auto dispatcher = fbl::ImportFromRawPtr(dispatcher_ref);
if (signal) {
signal_packet_ = *signal;
} else {
signal_packet_ = std::nullopt;
}
dispatcher->QueueWait(this, status);
dispatcher->thread_pool()->OnThreadWakeup();
}
AllowedSchedulerRoles* AllowedSchedulerRoles::Get() {
static AllowedSchedulerRoles instance;
return &instance;
}
void AllowedSchedulerRoles::AddForDriver(const void* driver, std::string_view role) {
fbl::AutoLock al(&lock_);
allowed_roles_.try_emplace(driver);
allowed_roles_[driver].emplace(role);
}
bool AllowedSchedulerRoles::IsAllowed(std::string_view role) {
if (unlikely(!DispatcherCoordinator::enforce_allowed_scheduler_roles())) {
return true;
}
fbl::AutoLock guard(&lock_);
auto iter = allowed_roles_.find(thread_context::GetCurrentDriver());
return iter != allowed_roles_.end() && iter->second.contains(std::string(role));
}
} // namespace driver_runtime