Home | History | Annotate | Download | only in lib
      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