#include "common/vsoc/lib/region_view.h" #include #include "common/libs/glog/logging.h" namespace { const uint32_t UADDR_OFFSET_MASK = 0xFFFFFFFC; const uint32_t UADDR_OFFSET_ROUND_TRIP_FLAG = 1; } // namespace using vsoc::layout::Sides; vsoc::RegionWorker::RegionWorker(RegionView* region, std::shared_ptr control) : control_(control), region_(region), stopping_(false) {} void vsoc::RegionWorker::start() { CHECK(!thread_.joinable()); thread_ = std::thread(&vsoc::RegionWorker::Work, this); } void vsoc::RegionWorker::Work() { while (!stopping_) { region_->WaitForInterrupt(); if (stopping_) { return; } region_->ProcessSignalsFromPeer([this](uint32_t offset) { control_->SignalSelf(offset); }); } } vsoc::RegionWorker::~RegionWorker() { stopping_ = true; if (thread_.joinable()) { region_->InterruptSelf(); thread_.join(); } } vsoc::RegionView::~RegionView() { // region_base_ is borrowed here. It's owned by control_, which is // responsible for unmapping the memory region_base_ = nullptr; } #if defined(CUTTLEFISH_HOST) bool vsoc::RegionView::Open(const char* name, const char* domain) { control_ = vsoc::RegionControl::Open(name, domain); if (!control_) { return false; } region_base_ = control_->Map(); return region_base_ != nullptr; } #else bool vsoc::RegionView::Open(const char* name) { control_ = vsoc::RegionControl::Open(name); if (!control_) { return false; } region_base_ = control_->Map(); return region_base_ != nullptr; } #endif // Interrupt our peer, causing it to scan the outgoing_signal_table bool vsoc::RegionView::MaybeInterruptPeer() { if (region_offset_to_pointer>( outgoing_signal_table().interrupt_signalled_offset) ->exchange(1)) { return false; } return control_->InterruptPeer(); } // Wait for an interrupt from our peer void vsoc::RegionView::WaitForInterrupt() { while (1) { if (region_offset_to_pointer>( incoming_signal_table().interrupt_signalled_offset) ->exchange(0)) { return; } control_->WaitForInterrupt(); } } void vsoc::RegionView::ProcessSignalsFromPeer( std::function signal_handler) { const vsoc_signal_table_layout& table = incoming_signal_table(); const size_t num_offsets = (1 << table.num_nodes_lg2); std::atomic* offsets = region_offset_to_pointer>( table.futex_uaddr_table_offset); for (size_t i = 0; i < num_offsets; ++i) { uint32_t raw_offset = offsets[i].exchange(0); if (raw_offset) { bool round_trip = raw_offset & UADDR_OFFSET_ROUND_TRIP_FLAG; uint32_t offset = raw_offset & UADDR_OFFSET_MASK; signal_handler(offset); if (round_trip) { SendSignalToPeer( region_offset_to_pointer>(offset), false); } } } } void vsoc::RegionView::SendSignal(Sides sides_to_signal, std::atomic* uaddr) { if (sides_to_signal & Sides::Peer) { // If we should also be signalling our side set the round trip flag on // the futex signal. bool round_trip = sides_to_signal & Sides::OurSide; SendSignalToPeer(uaddr, round_trip); // Return without signaling our waiters to give the other side a chance // to run. return; } if (sides_to_signal & Sides::OurSide) { control_->SignalSelf(pointer_to_region_offset(uaddr)); } } void vsoc::RegionView::SendSignalToPeer(std::atomic* uaddr, bool round_trip) { const vsoc_signal_table_layout& table = outgoing_signal_table(); std::atomic* offsets = region_offset_to_pointer>( table.futex_uaddr_table_offset); // maximum index in the node that can hold an offset; const size_t max_index = (1 << table.num_nodes_lg2) - 1; uint32_t offset = pointer_to_region_offset(uaddr); if (offset & ~UADDR_OFFSET_MASK) { LOG(FATAL) << "uaddr offset is not naturally aligned " << uaddr; } // Guess at where this offset should go in the table. // Do this before we set the round-trip flag. size_t hash = (offset >> 2) & max_index; if (round_trip) { offset |= UADDR_OFFSET_ROUND_TRIP_FLAG; } while (1) { uint32_t expected = 0; if (offsets[hash].compare_exchange_strong(expected, offset)) { // We stored the offset. Send the interrupt. this->MaybeInterruptPeer(); break; } // We didn't store, but the value was already in the table with our flag. // Return without interrupting. if (expected == offset) { return; } // Hash collision. Try again in a different node if ((expected & UADDR_OFFSET_MASK) != (offset & UADDR_OFFSET_MASK)) { hash = (hash + 1) & max_index; continue; } // Our offset was in the bucket, but the flags didn't match. // We're done if the value in the node had the round trip flag set. if (expected & UADDR_OFFSET_ROUND_TRIP_FLAG) { return; } // We wanted the round trip flag, but the value in the bucket didn't set it. // Do a second swap to try to set it. if (offsets[hash].compare_exchange_strong(expected, offset)) { // It worked. We're done. return; } if (expected == offset) { // expected was the offset without the flag. After the swap it has the // the flag. This means that some other thread set the flag, so // we're done. return; } // Something about the offset changed. We need to go around again, because: // our peer processed the old entry // another thread may have stolen the node while we were distracted } } std::unique_ptr vsoc::RegionView::StartWorker() { std::unique_ptr worker( new vsoc::RegionWorker(this /* region */, control_)); worker->start(); return worker; } int vsoc::RegionView::WaitForSignal(std::atomic* uaddr, uint32_t expected_value) { return control_->WaitForSignal(pointer_to_region_offset(uaddr), expected_value); }