1 #include "common/vsoc/lib/region_view.h" 2 3 #include <sys/mman.h> 4 5 #include "common/libs/glog/logging.h" 6 7 namespace { 8 const uint32_t UADDR_OFFSET_MASK = 0xFFFFFFFC; 9 const uint32_t UADDR_OFFSET_ROUND_TRIP_FLAG = 1; 10 } // namespace 11 12 using vsoc::layout::Sides; 13 14 vsoc::RegionWorker::RegionWorker(RegionView* region, 15 std::shared_ptr<RegionControl> control) 16 : control_(control), 17 region_(region), 18 stopping_(false) {} 19 20 void vsoc::RegionWorker::start() { 21 CHECK(!thread_.joinable()); 22 thread_ = std::thread(&vsoc::RegionWorker::Work, this); 23 } 24 25 void vsoc::RegionWorker::Work() { 26 while (!stopping_) { 27 region_->WaitForInterrupt(); 28 if (stopping_) { 29 return; 30 } 31 region_->ProcessSignalsFromPeer([this](uint32_t offset) { 32 control_->SignalSelf(offset); 33 }); 34 } 35 } 36 37 vsoc::RegionWorker::~RegionWorker() { 38 stopping_ = true; 39 40 if (thread_.joinable()) { 41 region_->InterruptSelf(); 42 thread_.join(); 43 } 44 } 45 46 vsoc::RegionView::~RegionView() { 47 // region_base_ is borrowed here. It's owned by control_, which is 48 // responsible for unmapping the memory 49 region_base_ = nullptr; 50 } 51 52 #if defined(CUTTLEFISH_HOST) 53 bool vsoc::RegionView::Open(const char* name, const char* domain) { 54 control_ = vsoc::RegionControl::Open(name, domain); 55 if (!control_) { 56 return false; 57 } 58 region_base_ = control_->Map(); 59 return region_base_ != nullptr; 60 } 61 #else 62 bool vsoc::RegionView::Open(const char* name) { 63 control_ = vsoc::RegionControl::Open(name); 64 if (!control_) { 65 return false; 66 } 67 region_base_ = control_->Map(); 68 return region_base_ != nullptr; 69 } 70 #endif 71 72 // Interrupt our peer, causing it to scan the outgoing_signal_table 73 bool vsoc::RegionView::MaybeInterruptPeer() { 74 if (region_offset_to_pointer<std::atomic<uint32_t>>( 75 outgoing_signal_table().interrupt_signalled_offset) 76 ->exchange(1)) { 77 return false; 78 } 79 return control_->InterruptPeer(); 80 } 81 82 // Wait for an interrupt from our peer 83 void vsoc::RegionView::WaitForInterrupt() { 84 while (1) { 85 if (region_offset_to_pointer<std::atomic<uint32_t>>( 86 incoming_signal_table().interrupt_signalled_offset) 87 ->exchange(0)) { 88 return; 89 } 90 control_->WaitForInterrupt(); 91 } 92 } 93 94 void vsoc::RegionView::ProcessSignalsFromPeer( 95 std::function<void(uint32_t)> signal_handler) { 96 const vsoc_signal_table_layout& table = incoming_signal_table(); 97 const size_t num_offsets = (1 << table.num_nodes_lg2); 98 std::atomic<uint32_t>* offsets = 99 region_offset_to_pointer<std::atomic<uint32_t>>( 100 table.futex_uaddr_table_offset); 101 for (size_t i = 0; i < num_offsets; ++i) { 102 uint32_t raw_offset = offsets[i].exchange(0); 103 if (raw_offset) { 104 bool round_trip = raw_offset & UADDR_OFFSET_ROUND_TRIP_FLAG; 105 uint32_t offset = raw_offset & UADDR_OFFSET_MASK; 106 signal_handler(offset); 107 if (round_trip) { 108 SendSignalToPeer( 109 region_offset_to_pointer<std::atomic<uint32_t>>(offset), false); 110 } 111 } 112 } 113 } 114 115 void vsoc::RegionView::SendSignal(Sides sides_to_signal, 116 std::atomic<uint32_t>* uaddr) { 117 if (sides_to_signal & Sides::Peer) { 118 // If we should also be signalling our side set the round trip flag on 119 // the futex signal. 120 bool round_trip = sides_to_signal & Sides::OurSide; 121 SendSignalToPeer(uaddr, round_trip); 122 // Return without signaling our waiters to give the other side a chance 123 // to run. 124 return; 125 } 126 if (sides_to_signal & Sides::OurSide) { 127 control_->SignalSelf(pointer_to_region_offset(uaddr)); 128 } 129 } 130 131 void vsoc::RegionView::SendSignalToPeer(std::atomic<uint32_t>* uaddr, 132 bool round_trip) { 133 const vsoc_signal_table_layout& table = outgoing_signal_table(); 134 std::atomic<uint32_t>* offsets = 135 region_offset_to_pointer<std::atomic<uint32_t>>( 136 table.futex_uaddr_table_offset); 137 // maximum index in the node that can hold an offset; 138 const size_t max_index = (1 << table.num_nodes_lg2) - 1; 139 uint32_t offset = pointer_to_region_offset(uaddr); 140 if (offset & ~UADDR_OFFSET_MASK) { 141 LOG(FATAL) << "uaddr offset is not naturally aligned " << uaddr; 142 } 143 // Guess at where this offset should go in the table. 144 // Do this before we set the round-trip flag. 145 size_t hash = (offset >> 2) & max_index; 146 if (round_trip) { 147 offset |= UADDR_OFFSET_ROUND_TRIP_FLAG; 148 } 149 while (1) { 150 uint32_t expected = 0; 151 if (offsets[hash].compare_exchange_strong(expected, offset)) { 152 // We stored the offset. Send the interrupt. 153 this->MaybeInterruptPeer(); 154 break; 155 } 156 // We didn't store, but the value was already in the table with our flag. 157 // Return without interrupting. 158 if (expected == offset) { 159 return; 160 } 161 // Hash collision. Try again in a different node 162 if ((expected & UADDR_OFFSET_MASK) != (offset & UADDR_OFFSET_MASK)) { 163 hash = (hash + 1) & max_index; 164 continue; 165 } 166 // Our offset was in the bucket, but the flags didn't match. 167 // We're done if the value in the node had the round trip flag set. 168 if (expected & UADDR_OFFSET_ROUND_TRIP_FLAG) { 169 return; 170 } 171 // We wanted the round trip flag, but the value in the bucket didn't set it. 172 // Do a second swap to try to set it. 173 if (offsets[hash].compare_exchange_strong(expected, offset)) { 174 // It worked. We're done. 175 return; 176 } 177 if (expected == offset) { 178 // expected was the offset without the flag. After the swap it has the 179 // the flag. This means that some other thread set the flag, so 180 // we're done. 181 return; 182 } 183 // Something about the offset changed. We need to go around again, because: 184 // our peer processed the old entry 185 // another thread may have stolen the node while we were distracted 186 } 187 } 188 189 std::unique_ptr<vsoc::RegionWorker> vsoc::RegionView::StartWorker() { 190 std::unique_ptr<vsoc::RegionWorker> worker( 191 new vsoc::RegionWorker(this /* region */, control_)); 192 193 worker->start(); 194 return worker; 195 } 196 197 int vsoc::RegionView::WaitForSignal(std::atomic<uint32_t>* uaddr, 198 uint32_t expected_value) { 199 return control_->WaitForSignal(pointer_to_region_offset(uaddr), 200 expected_value); 201 } 202