| // Copyright 2017 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 |
| |
| #ifndef ZIRCON_KERNEL_OBJECT_INCLUDE_OBJECT_PORT_DISPATCHER_H_ |
| #define ZIRCON_KERNEL_OBJECT_INCLUDE_OBJECT_PORT_DISPATCHER_H_ |
| |
| #include <sys/types.h> |
| #include <zircon/rights.h> |
| #include <zircon/syscalls/port.h> |
| #include <zircon/types.h> |
| |
| #include <fbl/canary.h> |
| #include <fbl/intrusive_double_list.h> |
| #include <kernel/mutex.h> |
| #include <kernel/semaphore.h> |
| #include <kernel/spinlock.h> |
| #include <ktl/unique_ptr.h> |
| #include <object/dispatcher.h> |
| #include <object/handle.h> |
| #include <object/signal_observer.h> |
| |
| // Important pointers diagram for PortObserver |
| // |
| // The diagrams below show the *relevant* pointers on different |
| // states of the system. The pure header view is really the |
| // union of all these pointer which can be confusing. |
| // |
| // PortDispatcher is responsible for destroying PortObservers (MaybeReap |
| // or on_zero_handles), however, their destruction may be initiated by |
| // either Dispatcher or PortDispatcher. |
| // |
| // rc = ref counted |
| // p = raw pointer |
| // o = owning pointer |
| // |
| // 1) Situation after object_wait_async(port, handle) is issued: |
| // |
| // |
| // list +--------+ |
| // +------p------+ +----p-----+ Port | |
| // | v v | | |
| // +-------+--+ +-----------+ +-+------+ |
| // | object | | Port | ^ |
| // | | <--rc--+ Observer | | |
| // +----------+ | +---rc----+ |
| // | | |
| // +-----------+ |
| // | Port | |
| // | Packet | |
| // +-----------+ |
| // |
| // State changes of the object are propagated from the object |
| // to the port via |p| --> observer --> |rc| calls. |
| // |
| // 2) Situation after the packet is queued on signal match or the wait |
| // is canceled. |
| // |
| // +--------+ |
| // | Port | |
| // | | |
| // +----------+ +-----------+ +-+---+--+ |
| // | object | | Port | ^ | |
| // | | | Observer | | | |
| // +----------+ | +---rc----+ | |
| // +---> | | | |
| // | +-----------+ | list |
| // | | Port | | |
| // +-rc--| Packet | <-----o-----+ |
| // +-----------+ |
| // |
| // Note that the object no longer has a |p| to the observer |
| // but the observer still owns the port via |rc|. |
| // |
| // The |o| pointer is used to destroy the port observer only |
| // when cancellation happens and the port still owns the packet. |
| // |
| |
| class PortDispatcher; |
| class PortObserver; |
| struct PortPacket; |
| |
| struct PortAllocator { |
| virtual ~PortAllocator() = default; |
| |
| virtual PortPacket* Alloc() = 0; |
| virtual void Free(PortPacket* port_packet) = 0; |
| }; |
| |
| struct PortPacket final : public fbl::DoublyLinkedListable<PortPacket*> { |
| zx_port_packet_t packet; |
| const void* const handle; |
| ktl::unique_ptr<const PortObserver> observer; |
| PortAllocator* const allocator; |
| |
| PortPacket(const void* handle, PortAllocator* allocator); |
| PortPacket(const PortPacket&) = delete; |
| void operator=(PortPacket) = delete; |
| |
| uint64_t key() const { return packet.key; } |
| bool is_ephemeral() const { return allocator != nullptr; } |
| void Free() { allocator->Free(this); } |
| }; |
| |
| struct PortInterruptPacket final : public fbl::DoublyLinkedListable<PortInterruptPacket*> { |
| zx_time_t timestamp; |
| uint64_t key; |
| }; |
| |
| // Observers are weakly contained in Dispatchers until their OnInitialize(), OnStateChange() or |
| // OnCancel() callbacks return StateObserver::kNeedRemoval. |
| class PortObserver final : public SignalObserver { |
| public: |
| using ListNodeState = fbl::DoublyLinkedListNodeState<PortObserver*>; |
| |
| // ListTraits allows PortObservers to be placed on a PortObserver::List. |
| struct ListTraits { |
| static ListNodeState& node_state(PortObserver& obj) { return obj.observer_list_node_state_; } |
| }; |
| |
| using List = fbl::DoublyLinkedListCustomTraits<PortObserver*, PortObserver::ListTraits>; |
| |
| PortObserver(uint32_t options, const Handle* handle, fbl::RefPtr<PortDispatcher> port, |
| Lock<Mutex>* port_lock, uint64_t key, zx_signals_t signals); |
| |
| ~PortObserver() final = default; |
| |
| // May only be called while holding PortDispatcher lock. |
| fbl::RefPtr<Dispatcher> UnlinkDispatcherLocked() { |
| DEBUG_ASSERT(port_lock_->lock().IsHeld()); |
| return ktl::move(dispatcher_); |
| } |
| |
| private: |
| PortObserver(const PortObserver&) = delete; |
| PortObserver& operator=(const PortObserver&) = delete; |
| |
| // |SignalObserver| implementation. |
| void OnMatch(zx_signals_t signals) final; |
| void OnCancel(zx_signals_t signals) final; |
| bool MatchesKey(const void* port, uint64_t key) final; |
| |
| const uint32_t options_; |
| PortPacket packet_; |
| |
| fbl::RefPtr<PortDispatcher> const port_; |
| Lock<Mutex>* const port_lock_; |
| |
| // Guarded by port_lock_; |
| ListNodeState observer_list_node_state_; |
| |
| // Guarded by port_lock_; |
| fbl::RefPtr<Dispatcher> dispatcher_; |
| }; |
| |
| // The PortDispatcher implements the port kernel object which is the cornerstone |
| // for waiting on object changes in Zircon. The PortDispatcher handles 3 usage |
| // cases: |
| // 1- Object state change notification: zx_object_wait_async() |
| // 2- Manual queuing: zx_port_queue() |
| // 3- Interrupt change notification: zx_interrupt_bind() |
| // |
| // This makes the implementation non-trivial. Cases 1 and 2 use the |packets_| |
| // linked list and case 3 uses |interrupt_packets_| linked list. |
| // |
| // The threads that wish to receive notifications block on Dequeue() (which |
| // maps to zx_port_wait()) and will receive packets from any of the four sources |
| // depending on what kind of object the port has been 'bound' to. |
| // |
| // When a packet from any of the sources arrives to the port, one waiting |
| // thread unblocks and gets the packet. In all cases |sema_| is used to signal |
| // and manage the waiting threads. |
| |
| class PortDispatcher final : public SoloDispatcher<PortDispatcher, ZX_DEFAULT_PORT_RIGHTS> { |
| public: |
| static void Init(); |
| static PortAllocator* DefaultPortAllocator(); |
| static zx_status_t Create(uint32_t options, KernelHandle<PortDispatcher>* handle, |
| zx_rights_t* rights); |
| |
| ~PortDispatcher() final; |
| zx_obj_type_t get_type() const final { return ZX_OBJ_TYPE_PORT; } |
| |
| bool can_bind_to_interrupt() const { return options_ & ZX_PORT_BIND_TO_INTERRUPT; } |
| void on_zero_handles() final; |
| |
| zx_status_t Queue(PortPacket* port_packet, zx_signals_t observed); |
| zx_status_t QueueUser(const zx_port_packet_t& packet); |
| bool QueueInterruptPacket(PortInterruptPacket* port_packet, zx_time_t timestamp); |
| zx_status_t Dequeue(const Deadline& deadline, zx_port_packet_t* packet); |
| bool RemoveInterruptPacket(PortInterruptPacket* port_packet); |
| |
| // This method determines the observer's fate. Upon return, one of the following will have |
| // occurred: |
| // |
| // 1. The observer is destroyed. |
| // |
| // 2. The observer is linked to an alreadyed queued packet and will be destroyed when the packet |
| // is destroyed (Queued or CancelQueued). |
| // |
| // 3. The observer is left for on_zero_handles to destroyed. |
| void MaybeReap(PortObserver* observer, PortPacket* port_packet); |
| |
| // The purpose of this type is to decouple memory allocation from object construction. |
| // |
| // Example usage: |
| // |
| // // Allocate a placeholder. |
| // fbl::AllocChecker ac; |
| // ktl::unique_ptr<PortDispatcher::PortObserverPlaceholder> placeholder{ |
| // new (&ac) PortDispatcher::PortObserverPlaceholder{}}; |
| // ASSERT(ac.check()); |
| // |
| // // At this point memory for a PortObserver has been allocated, however, the PortObserver has |
| // // not been constructed so if we were to call placeholder.reset() the destructor would not be |
| // // executed. |
| // |
| // // Construct the PortObserver and release the placeholder. |
| // ktl::unique_ptr<PortObserver> observer{new (&placeholder.release()->observer) |
| // PortObserver(...)}; |
| // |
| // // At this point |placeholder| is null and we have a fully constructed PortObserver retained |
| // // by |observer|. If were to call observer.reset() the destructor would execute. |
| // |
| union PortObserverPlaceholder { |
| PortObserverPlaceholder() {} |
| ~PortObserverPlaceholder() {} |
| |
| uint8_t trivially_destructible_default_variant; |
| PortObserver observer; |
| }; |
| static_assert(sizeof(PortObserverPlaceholder) == sizeof(PortObserver)); |
| static_assert(alignof(PortObserverPlaceholder) == alignof(PortObserver)); |
| |
| // Called under the handle table lock. |
| zx_status_t MakeObserver(ktl::unique_ptr<PortObserverPlaceholder> placeholder, uint32_t options, |
| Handle* handle, uint64_t key, zx_signals_t signals); |
| |
| // Returns true if at least one packet was removed from the queue. |
| // Called under the handle table lock when |handle| is not null. |
| // When |handle| is null, ephemeral PortPackets are removed from the queue but not freed. |
| bool CancelQueued(const void* handle, uint64_t key); |
| |
| // Removes |port_packet| from this port's queue. Returns false if the packet was |
| // not in this queue. It is undefined to call this with a packet queued in another port. |
| bool CancelQueued(PortPacket* port_packet); |
| |
| private: |
| explicit PortDispatcher(uint32_t options); |
| |
| const uint32_t options_; |
| Semaphore sema_; |
| bool zero_handles_ TA_GUARDED(get_lock()); |
| |
| // Next three members handle the object and manual notifications. |
| size_t num_ephemeral_packets_ TA_GUARDED(get_lock()); |
| fbl::DoublyLinkedList<PortPacket*> packets_ TA_GUARDED(get_lock()); |
| // Next two members handle the interrupt notifications. |
| DECLARE_SPINLOCK(PortDispatcher) spinlock_; |
| fbl::DoublyLinkedList<PortInterruptPacket*> interrupt_packets_ TA_GUARDED(spinlock_); |
| |
| // Keeps track of outstanding observers so they can be removed from dispatchers once handle |
| // count drops to zero. |
| PortObserver::List observers_ TA_GUARDED(get_lock()); |
| }; |
| |
| #endif // ZIRCON_KERNEL_OBJECT_INCLUDE_OBJECT_PORT_DISPATCHER_H_ |