| #pragma once |
| |
| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| // Object that represents a region on the Host |
| |
| #include <stdlib.h> |
| #include <sys/mman.h> |
| #include <atomic> |
| #include <cstdint> |
| |
| #include <functional> |
| #include <map> |
| #include <thread> |
| |
| #include "common/libs/fs/shared_fd.h" |
| #include "common/libs/glog/logging.h" |
| #include "common/vsoc/lib/lock_guard.h" |
| #include "common/vsoc/lib/region_control.h" |
| #include "common/vsoc/lib/region_signaling_interface.h" |
| #include "common/vsoc/shm/base.h" |
| #include "uapi/vsoc_shm.h" |
| |
| namespace vsoc { |
| |
| class RegionControl; |
| class RegionView; |
| |
| /** |
| * Represents a task that is tied to a RegionView. |
| * |
| * This is currently used for the task that forwards futexes across the |
| * shared memory window. |
| */ |
| class RegionWorker { |
| public: |
| RegionWorker(RegionView* region, std::shared_ptr<RegionControl> control); |
| ~RegionWorker(); |
| |
| void start(); |
| |
| void Work(); |
| |
| protected: |
| std::shared_ptr<RegionControl> control_; |
| RegionView* region_; |
| std::thread thread_; |
| volatile bool stopping_; |
| }; |
| |
| /** |
| * Base class to access a mapped region in VSoC shared memory. |
| * This class holds the methods that depends on the region's memory having an |
| * address. The RegionControl class holds the methods that can be invoked |
| * without mapping the region. |
| */ |
| class RegionView : public RegionSignalingInterface { |
| public: |
| virtual ~RegionView(); |
| |
| #if defined(CUTTLEFISH_HOST) |
| bool Open(const char* region_name, const char* domain); |
| #else |
| bool Open(const char* region_name); |
| #endif |
| |
| // Returns a pointer to the table that will be scanned for signals |
| const vsoc_signal_table_layout& incoming_signal_table(); |
| |
| // Returns a pointer to the table that will be used to post signals |
| const vsoc_signal_table_layout& outgoing_signal_table(); |
| |
| // Returns true iff an interrupt is queued in the signal table |
| bool HasIncomingInterrupt() { |
| return *region_offset_to_pointer<std::atomic<uint32_t>>( |
| incoming_signal_table().interrupt_signalled_offset); |
| } |
| |
| // Wake any threads waiting for an interrupt. This is generally used during |
| // shutdown. |
| void InterruptSelf() { control_->InterruptSelf(); } |
| |
| // Interrupt our peer if an interrupt is not already on the way. |
| // Returns true if the interrupt was sent, false if an interrupt was already |
| // pending. |
| bool MaybeInterruptPeer(); |
| |
| // Scan in the incoming signal table, issuing futex calls for any posted |
| // signals and then reposting them to the peer if they were round-trip |
| // signals. |
| // |
| // signal_handler: An action to perform on every offset signalled by our |
| // peer, usually a FUTEX_WAKE call, but can be customized for other |
| // purposes. |
| void ProcessSignalsFromPeer( |
| std::function<void(uint32_t)> signal_handler); |
| |
| // Post a signal to the guest, the host, or both. |
| // See futex(2) FUTEX_WAKE for details. |
| // |
| // sides_to_signal: controls where the signal is sent |
| // |
| // signal_addr: the memory location to signal. Must be within the region. |
| void SendSignal(layout::Sides sides_to_signal, |
| std::atomic<uint32_t>* signal_addr); |
| |
| // Post a signal to our peer for a specific memeory location. |
| // See futex(2) FUTEX_WAKE for details. |
| // |
| // signal_addr: the memory location to signal. Must be within the region. |
| // |
| // round_trip: true if there may be waiters on both sides of the shared |
| // memory. |
| void SendSignalToPeer(std::atomic<uint32_t>* signal_addr, bool round_trip); |
| |
| // Waits until an interrupt appears on this region, then clears the |
| // interrupted flag and returns. |
| void WaitForInterrupt(); |
| |
| // This implements the following: |
| // if (*signal_addr == last_observed_value) |
| // wait_for_signal_at(signal_addr); |
| // |
| // Note: the caller still needs to check the value at signal_addr because |
| // this function may return early for reasons that are implementation-defined. |
| // See futex(2) FUTEX_WAIT for details. |
| // |
| // signal_addr: the memory that will be signaled. Must be within the region. |
| // |
| // last_observed_value: the value that motivated the calling code to wait. |
| // |
| // The return value is -1 on error. On the guest positive values give the |
| // number of false wakes. |
| int WaitForSignal(std::atomic<uint32_t>* signal_addr, |
| uint32_t last_observed_value) override; |
| |
| // Starts the signal table scanner. This must be invoked by subclasses, which |
| // MUST store the returned unique_ptr as a class member. |
| __attribute__((warn_unused_result)) |
| std::unique_ptr<RegionWorker> StartWorker(); |
| |
| // Returns a pointer to the start of region data that is cast to the given |
| // type. Initializers that run in the launcher use this to get a typed view |
| // of the region. Most other cases should be handled via TypedRegionView. |
| template <typename LayoutType> |
| LayoutType* GetLayoutPointer() { |
| return this->region_offset_to_pointer<LayoutType>( |
| control_->region_desc().offset_of_region_data); |
| } |
| |
| // Helper functions for lock guards. This approach has two advantages: |
| // |
| // o Callers don't have to be refactored when lock types change |
| // o The region pointer can be provided automatically |
| template <typename T> |
| LockGuard<T> make_lock_guard(T* lock) { |
| return LockGuard<T>(lock); |
| } |
| |
| LockGuard<::vsoc::layout::GuestAndHostLock> make_lock_guard( |
| ::vsoc::layout::GuestAndHostLock* l) { |
| return LockGuard<::vsoc::layout::GuestAndHostLock>(l, this); |
| } |
| |
| protected: |
| template <typename T> |
| T* region_offset_to_pointer(uint32_t offset) { |
| return control_->region_offset_to_pointer<T>(offset); |
| } |
| |
| template <typename T> |
| const T& region_offset_to_reference(uint32_t offset) const { |
| if (offset > control_->region_size()) { |
| LOG(FATAL) << __FUNCTION__ << ": " << offset << " not in region @" |
| << region_base_; |
| } |
| return *reinterpret_cast<const T*>( |
| reinterpret_cast<uintptr_t>(region_base_) + offset); |
| } |
| |
| // Calculates an offset based on a pointer in the region. Crashes if the |
| // pointer isn't in the region. |
| // This is mostly for the RegionView's internal plumbing. Use TypedRegionView |
| // and RegionLayout to avoid this in most cases. |
| template <typename T> |
| uint32_t pointer_to_region_offset(T* ptr) const { |
| uint32_t rval = reinterpret_cast<uintptr_t>(ptr) - |
| reinterpret_cast<uintptr_t>(region_base_); |
| if (rval > control_->region_size()) { |
| LOG(FATAL) << __FUNCTION__ << ": " << ptr << " not in region @" |
| << region_base_; |
| } |
| return rval; |
| } |
| |
| std::shared_ptr<RegionControl> control_; |
| void* region_base_{}; |
| }; |
| |
| } // namespace vsoc |