blob: 0273febfdb53bace50af3287f6481de4e7c650ea [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <object/dispatcher.h>
#include <arch/ops.h>
#include <lib/ktrace.h>
#include <fbl/atomic.h>
#include <fbl/auto_lock.h>
#include <fbl/mutex.h>
using fbl::AutoLock;
namespace {
// The first 1K koids are reserved.
fbl::atomic<zx_koid_t> global_koid(1024ULL);
zx_koid_t GenerateKernelObjectId() {
return global_koid.fetch_add(1ULL, fbl::memory_order_relaxed);
}
} // namespace
Dispatcher::Dispatcher(zx_signals_t signals)
: koid_(GenerateKernelObjectId()),
handle_count_(0u),
signals_(signals) {
}
Dispatcher::~Dispatcher() {
#if WITH_LIB_KTRACE
ktrace(TAG_OBJECT_DELETE, (uint32_t)koid_, 0, 0, 0);
#endif
}
zx_status_t Dispatcher::add_observer(StateObserver* observer) {
if (!has_state_tracker())
return ZX_ERR_NOT_SUPPORTED;
AddObserver(observer, nullptr);
return ZX_OK;
}
zx_status_t Dispatcher::user_signal(uint32_t clear_mask, uint32_t set_mask, bool peer) {
if (peer)
return ZX_ERR_NOT_SUPPORTED;
if (!has_state_tracker())
return ZX_ERR_NOT_SUPPORTED;
// Generic objects can set all USER_SIGNALs. Particular object
// types (events and eventpairs) may be able to set more.
if ((set_mask & ~ZX_USER_SIGNAL_ALL) || (clear_mask & ~ZX_USER_SIGNAL_ALL))
return ZX_ERR_INVALID_ARGS;
UpdateState(clear_mask, set_mask);
return ZX_OK;
}
namespace {
template <typename Func>
StateObserver::Flags CancelWithFunc(Dispatcher::ObserverList* observers,
fbl::Mutex* observer_lock, Func f) {
StateObserver::Flags flags = 0;
Dispatcher::ObserverList obs_to_remove;
{
AutoLock lock(observer_lock);
for (auto it = observers->begin(); it != observers->end();) {
StateObserver::Flags it_flags = f(it.CopyPointer());
flags |= it_flags;
if (it_flags & StateObserver::kNeedRemoval) {
auto to_remove = it;
++it;
obs_to_remove.push_back(observers->erase(to_remove));
} else {
++it;
}
}
}
while (!obs_to_remove.is_empty()) {
obs_to_remove.pop_front()->OnRemoved();
}
// We've processed the removal flag, so strip it
return flags & (~StateObserver::kNeedRemoval);
}
} // namespace
// Since this conditionally takes the dispatcher's |lock_|, based on
// the type of Mutex (either fbl::Mutex or fbl::NullLock), the thread
// safety analysis is unable to prove that the accesses to |signals_|
// and to |observers_| are always protected.
template <typename Mutex>
void Dispatcher::AddObserverHelper(StateObserver* observer,
const StateObserver::CountInfo* cinfo,
Mutex* mutex) TA_NO_THREAD_SAFETY_ANALYSIS {
ZX_DEBUG_ASSERT(has_state_tracker());
DEBUG_ASSERT(observer != nullptr);
StateObserver::Flags flags;
{
AutoLock lock(mutex);
flags = observer->OnInitialize(signals_, cinfo);
if (!(flags & StateObserver::kNeedRemoval))
observers_.push_front(observer);
}
if (flags & StateObserver::kNeedRemoval)
observer->OnRemoved();
if (flags & StateObserver::kWokeThreads)
thread_reschedule();
}
void Dispatcher::AddObserver(StateObserver* observer, const StateObserver::CountInfo* cinfo) {
AddObserverHelper(observer, cinfo, &lock_);
}
void Dispatcher::AddObserverLocked(StateObserver* observer, const StateObserver::CountInfo* cinfo) {
fbl::NullLock mutex;
AddObserverHelper(observer, cinfo, &mutex);
}
void Dispatcher::RemoveObserver(StateObserver* observer) {
ZX_DEBUG_ASSERT(has_state_tracker());
AutoLock lock(&lock_);
DEBUG_ASSERT(observer != nullptr);
observers_.erase(*observer);
}
bool Dispatcher::Cancel(Handle* handle) {
ZX_DEBUG_ASSERT(has_state_tracker());
StateObserver::Flags flags = CancelWithFunc(&observers_, &lock_, [handle](StateObserver* obs) {
return obs->OnCancel(handle);
});
// We could request a reschedule if kWokeThreads is asserted,
// but cancellation is not likely to benefit from aggressive
// rescheduling.
return flags & StateObserver::kHandled;
}
bool Dispatcher::CancelByKey(Handle* handle, const void* port, uint64_t key) {
ZX_DEBUG_ASSERT(has_state_tracker());
StateObserver::Flags flags = CancelWithFunc(&observers_, &lock_, [handle, port, key](StateObserver* obs) {
return obs->OnCancelByKey(handle, port, key);
});
// We could request a reschedule if kWokeThreads is asserted,
// but cancellation is not likely to benefit from aggressive
// rescheduling.
return flags & StateObserver::kHandled;
}
// Since this conditionally takes the dispatcher's |lock_|, based on
// the type of Mutex (either fbl::Mutex or fbl::NullLock), the thread
// safety analysis is unable to prove that the accesses to |signals_|
// are always protected.
template <typename Mutex>
void Dispatcher::UpdateStateHelper(zx_signals_t clear_mask,
zx_signals_t set_mask,
Mutex* mutex) TA_NO_THREAD_SAFETY_ANALYSIS {
StateObserver::Flags flags;
Dispatcher::ObserverList obs_to_remove;
{
AutoLock lock(mutex);
auto previous_signals = signals_;
signals_ &= ~clear_mask;
signals_ |= set_mask;
if (previous_signals == signals_)
return;
flags = UpdateInternalLocked(&obs_to_remove, signals_);
}
while (!obs_to_remove.is_empty()) {
obs_to_remove.pop_front()->OnRemoved();
}
if (flags & StateObserver::kWokeThreads)
thread_reschedule();
}
void Dispatcher::UpdateState(zx_signals_t clear_mask,
zx_signals_t set_mask) {
ZX_DEBUG_ASSERT(has_state_tracker());
UpdateStateHelper(clear_mask, set_mask, &lock_);
}
void Dispatcher::UpdateStateLocked(zx_signals_t clear_mask,
zx_signals_t set_mask) {
ZX_DEBUG_ASSERT(has_state_tracker());
fbl::NullLock mutex;
UpdateStateHelper(clear_mask, set_mask, &mutex);
}
zx_status_t Dispatcher::SetCookie(CookieJar* cookiejar, zx_koid_t scope, uint64_t cookie) {
ZX_DEBUG_ASSERT(has_state_tracker());
if (cookiejar == nullptr)
return ZX_ERR_NOT_SUPPORTED;
AutoLock lock(&lock_);
if (cookiejar->scope_ == ZX_KOID_INVALID) {
cookiejar->scope_ = scope;
cookiejar->cookie_ = cookie;
return ZX_OK;
}
if (cookiejar->scope_ == scope) {
cookiejar->cookie_ = cookie;
return ZX_OK;
}
return ZX_ERR_ACCESS_DENIED;
}
zx_status_t Dispatcher::GetCookie(CookieJar* cookiejar, zx_koid_t scope, uint64_t* cookie) {
ZX_DEBUG_ASSERT(has_state_tracker());
if (cookiejar == nullptr)
return ZX_ERR_NOT_SUPPORTED;
AutoLock lock(&lock_);
if (cookiejar->scope_ == scope) {
*cookie = cookiejar->cookie_;
return ZX_OK;
}
return ZX_ERR_ACCESS_DENIED;
}
zx_status_t Dispatcher::InvalidateCookie(CookieJar* cookiejar) {
ZX_DEBUG_ASSERT(has_state_tracker());
if (cookiejar == nullptr)
return ZX_ERR_NOT_SUPPORTED;
AutoLock lock(&lock_);
cookiejar->scope_ = ZX_KOID_KERNEL;
return ZX_OK;
}
StateObserver::Flags Dispatcher::UpdateInternalLocked(ObserverList* obs_to_remove, zx_signals_t signals) {
ZX_DEBUG_ASSERT(has_state_tracker());
StateObserver::Flags flags = 0;
for (auto it = observers_.begin(); it != observers_.end();) {
StateObserver::Flags it_flags = it->OnStateChange(signals);
flags |= it_flags;
if (it_flags & StateObserver::kNeedRemoval) {
auto to_remove = it;
++it;
obs_to_remove->push_back(observers_.erase(to_remove));
} else {
++it;
}
}
// Filter out NeedRemoval flag because we processed that here
return flags & (~StateObserver::kNeedRemoval);
}