1// Copyright 2017 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <zircon/syscalls.h> 6#include <zircon/types.h> 7#include <fbl/auto_call.h> 8 9#include <dispatcher-pool/dispatcher-event-source.h> 10#include <dispatcher-pool/dispatcher-execution-domain.h> 11#include <dispatcher-pool/dispatcher-thread-pool.h> 12 13namespace dispatcher { 14 15EventSource::EventSource(zx_signals_t process_signal_mask) 16 : process_signal_mask_(process_signal_mask) { } 17 18EventSource::~EventSource() { 19 ZX_DEBUG_ASSERT(domain_ == nullptr); 20 ZX_DEBUG_ASSERT(!InExecutionDomain()); 21 ZX_DEBUG_ASSERT(!InPendingList()); 22 ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::Idle); 23} 24 25void EventSource::InternalDeactivateLocked() { 26 // If we are no longer active, we can just get out now. We should be able 27 // to assert that our handle has been closed and that we are in either the 28 // Idle or Dispatching state, or that handle is still valid and we are in 29 // WaitingOnPort state (meaning that there is a thread in flight from the 30 // thread pool which is about to realize that we have become deactivated) 31 if (!is_active()) { 32 ZX_DEBUG_ASSERT( 33 ( handle_.is_valid() && (dispatch_state() == DispatchState::WaitingOnPort)) || 34 (!handle_.is_valid() && ((dispatch_state() == DispatchState::Dispatching) || 35 (dispatch_state() == DispatchState::Idle)))); 36 return; 37 } 38 39 // Attempt to cancel any pending operations. Do not close the handle if it 40 // was too late to cancel and we are still waiting on the port. 41 CancelPendingLocked(); 42 if (dispatch_state() != DispatchState::WaitingOnPort) { 43 ZX_DEBUG_ASSERT((dispatch_state() == DispatchState::Idle) || 44 (dispatch_state() == DispatchState::Dispatching)); 45 handle_.reset(); 46 } 47 48 // If we still have a domain, remove ourselves from the domain's event 49 // source list, then release our reference to it. 50 if (domain_ != nullptr) { 51 domain_->RemoveEventSource(this); 52 domain_ = nullptr; 53 } 54 55 // Release our cached thread pool reference. 56 thread_pool_.reset(); 57} 58 59zx_status_t EventSource::ActivateLocked(zx::handle handle, fbl::RefPtr<ExecutionDomain> domain) { 60 if ((domain == nullptr) || !handle.is_valid()) 61 return ZX_ERR_INVALID_ARGS; 62 63 if (is_active() || handle_.is_valid()) 64 return ZX_ERR_BAD_STATE; 65 ZX_DEBUG_ASSERT(thread_pool_ == nullptr); 66 67 auto thread_pool = domain->GetThreadPool(); 68 if (thread_pool == nullptr) 69 return ZX_ERR_BAD_STATE; 70 71 // Add ourselves to our domain's list of event sources. 72 zx_status_t res = domain->AddEventSource(fbl::WrapRefPtr(this)); 73 if (res != ZX_OK) 74 return res; 75 76 handle_ = fbl::move(handle); 77 domain_ = fbl::move(domain); 78 thread_pool_ = fbl::move(thread_pool); 79 80 return ZX_OK; 81} 82 83zx_status_t EventSource::WaitOnPortLocked() { 84 // If we are attempting to wait, we should not already have a wait pending. 85 // In particular, we need to be in the idle state. 86 ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::Idle); 87 88 // Attempting to wait when our domain is null indicates that we are in the 89 // process of dying, and the wait should be denied. 90 if (!is_active()) 91 return ZX_ERR_BAD_STATE; 92 93 zx_status_t res = DoPortWaitLocked(); 94 95 // If the wait async succeeded, then we now have a pending wait operation, 96 // and the kernel is now holding an unmanaged reference to us. Flag the 97 // pending wait, and manually bump our ref count. 98 if (res == ZX_OK) { 99 dispatch_state_ = DispatchState::WaitingOnPort; 100 this->AddRef(); 101 } 102 103 return res; 104} 105 106zx_status_t EventSource::CancelPendingLocked() { 107 // If we are still active, remove ourselves from the domain's 108 // pending work list. 109 if (is_active()) { 110 // If we were on the pending work list, then our state must have been 111 // DispatchPending (and now should be Idle) 112 if (domain_->RemovePendingWork(this)) { 113 ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::DispatchPending); 114 dispatch_state_ = DispatchState::Idle; 115 } 116 117 // If there is a wait operation currently pending, attempt to cancel it. 118 // 119 // If we succeed, manually drop the unmanaged reference which the kernel 120 // was holding and transition to the Idle state. 121 // 122 // If we fail, it must be because the wait has completed and is being 123 // dispatched on another thread. Do not transition to Idle, or release 124 // the kernel reference. 125 if (dispatch_state() == DispatchState::WaitingOnPort) { 126 zx_status_t res = DoPortCancelLocked(); 127 128 if (res == ZX_OK) { 129 __UNUSED bool should_destruct; 130 131 dispatch_state_ = DispatchState::Idle; 132 should_destruct = this->Release(); 133 134 ZX_DEBUG_ASSERT(should_destruct == false); 135 } else { 136 ZX_DEBUG_ASSERT(res == ZX_ERR_NOT_FOUND); 137 } 138 } 139 } 140 141 return (dispatch_state() == DispatchState::Idle) ? ZX_OK : ZX_ERR_BAD_STATE; 142} 143 144zx_status_t EventSource::DoPortWaitLocked() { 145 ZX_DEBUG_ASSERT(thread_pool_ != nullptr); 146 return thread_pool_->WaitOnPort(handle_, 147 reinterpret_cast<uint64_t>(this), 148 process_signal_mask(), 149 ZX_WAIT_ASYNC_ONCE); 150} 151 152zx_status_t EventSource::DoPortCancelLocked() { 153 ZX_DEBUG_ASSERT(thread_pool_ != nullptr); 154 return thread_pool_->CancelWaitOnPort(handle_, reinterpret_cast<uint64_t>(this)); 155} 156 157bool EventSource::BeginDispatching() { 158 fbl::AutoLock obj_lock(&obj_lock_); 159 if (dispatch_state() != DispatchState::DispatchPending) 160 return false; 161 162 ZX_DEBUG_ASSERT(InPendingList()); 163 164 __UNUSED zx_status_t res; 165 res = CancelPendingLocked(); 166 ZX_DEBUG_ASSERT(res == ZX_OK); 167 ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::Idle); 168 169 dispatch_state_ = DispatchState::Dispatching; 170 171 return true; 172} 173 174fbl::RefPtr<ExecutionDomain> EventSource::ScheduleDispatch( 175 const zx_port_packet_t& pkt) { 176 // Something interesting happened. Enter the lock and... 177 // 178 // 1) Sanity check, then reset wait_pending_. There is no longer a wait pending. 179 // 2) Assert that something interesting happened. If none of the 180 // interesting things which happened are in the process_signal_mask_, then 181 // just return. The dispatcher thread will deactive us. 182 // 3) If our domain is still active, add this event source to the pending work 183 // queue. If we are the first event source to enter the queue, return a 184 // reference to our domain to the dispatcher thread so that it can start 185 // to process the pending work. 186 fbl::AutoLock obj_lock(&obj_lock_); 187 188 ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::WaitingOnPort); 189 190 ZX_DEBUG_ASSERT((pkt.type == ZX_PKT_TYPE_INTERRUPT) || 191 (pkt.signal.observed & process_signal_mask())); 192 193 if (domain_ == nullptr) { 194 dispatch_state_ = DispatchState::Idle; 195 return nullptr; 196 } 197 198 // Copy the pending port packet to the internal event source storage, 199 // then add ourselves to the domain's pending queue. If we were the 200 // first event source to join our domain's pending queue, then take a 201 // reference to our domain so that we can start to process pending work 202 // once we have left the obj_lock. If we are not the first to join the 203 // queue, just get out. The thread which is currently handling pending 204 // jobs for this domain will handle this one when the time comes. 205 pending_pkt_ = pkt; 206 return domain_->AddPendingWork(this) ? domain_ : nullptr; 207} 208 209} // namespace dispatcher 210