• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
RegionWorker(RegionView * region,std::shared_ptr<RegionControl> control)14 vsoc::RegionWorker::RegionWorker(RegionView* region,
15                                  std::shared_ptr<RegionControl> control)
16     : control_(control),
17       region_(region),
18       stopping_(false) {}
19 
start()20 void vsoc::RegionWorker::start() {
21   CHECK(!thread_.joinable());
22   thread_ = std::thread(&vsoc::RegionWorker::Work, this);
23 }
24 
Work()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 
~RegionWorker()37 vsoc::RegionWorker::~RegionWorker() {
38   stopping_ = true;
39 
40   if (thread_.joinable()) {
41     region_->InterruptSelf();
42     thread_.join();
43   }
44 }
45 
~RegionView()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)
Open(const char * name,const char * domain)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
Open(const char * name)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
MaybeInterruptPeer()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
WaitForInterrupt()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 
ProcessSignalsFromPeer(std::function<void (uint32_t)> signal_handler)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 
SendSignal(Sides sides_to_signal,std::atomic<uint32_t> * uaddr)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 
SendSignalToPeer(std::atomic<uint32_t> * uaddr,bool round_trip)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 
StartWorker()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 
WaitForSignal(std::atomic<uint32_t> * uaddr,uint32_t expected_value)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