blob: dc1088c1c42b1491a2868a12378fe36eb105cc95 [file] [log] [blame]
Cody Schuffelen134ff032019-11-22 00:25:32 -08001#include "common/vsoc/lib/region_view.h"
2
3#include <sys/mman.h>
4
5#include "common/libs/glog/logging.h"
6
7namespace {
8const uint32_t UADDR_OFFSET_MASK = 0xFFFFFFFC;
9const uint32_t UADDR_OFFSET_ROUND_TRIP_FLAG = 1;
10} // namespace
11
12using vsoc::layout::Sides;
13
14vsoc::RegionWorker::RegionWorker(RegionView* region,
15 std::shared_ptr<RegionControl> control)
16 : control_(control),
17 region_(region),
18 stopping_(false) {}
19
20void vsoc::RegionWorker::start() {
21 CHECK(!thread_.joinable());
22 thread_ = std::thread(&vsoc::RegionWorker::Work, this);
23}
24
25void 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
37vsoc::RegionWorker::~RegionWorker() {
38 stopping_ = true;
39
40 if (thread_.joinable()) {
41 region_->InterruptSelf();
42 thread_.join();
43 }
44}
45
46vsoc::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)
53bool 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
62bool 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
73bool 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
83void 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
94void 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
115void 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
131void 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
189std::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
197int 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}