1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "berberis/runtime_primitives/memory_region_reservation.h"
18 
19 #include <array>
20 #include <atomic>
21 
22 #include "berberis/guest_state/guest_addr.h"
23 #include "berberis/guest_state/guest_state_arch.h"
24 
25 namespace berberis {
26 
27 namespace {
28 
29 template <bool flag = false>
static_bad_size()30 void static_bad_size() {
31   static_assert(flag, "Expected Reservation to be of size 8 or 16");
32 }
33 
34 template <typename ReservationType>
MemoryRegionReservationLoadTemplate(GuestAddr addr,std::memory_order mem_order)35 inline ReservationType MemoryRegionReservationLoadTemplate(GuestAddr addr,
36                                                            std::memory_order mem_order) {
37   if constexpr (sizeof(ReservationType) == 16) {
38     // Intel doesn't have atomic 128-bit load other that CMPXCHG16B, which is also
39     // a store, and doesn't work for read-only memory. We only support guests that
40     // are similar to x86 in that a 128-bit load is two atomic 64-bit loads.
41     ReservationType low =
42         std::atomic_load_explicit(ToHostAddr<std::atomic<uint64_t>>(addr), mem_order);
43     ReservationType high =
44         std::atomic_load_explicit(ToHostAddr<std::atomic<uint64_t>>(addr + 8), mem_order);
45     return (high << 64) | low;
46   } else if constexpr (sizeof(ReservationType) == 8) {
47     ReservationType reservation;
48 #if defined(__i386__)
49     // Starting from i486 all accesses for all instructions are atomic when they are used for
50     // naturally-aligned variables of uint8_t, uint16_t and uint32_t types.  But situation is not so
51     // straightforward when we are dealing with uint64_t.
52     //
53     // This is what Intel manual says about atomicity of 64-bit memory operations:
54     //   The Pentium processor (and newer processors since) guarantees that the following additional
55     //   memory operations will always be carried out atomically:
56     //     * Reading or writing a quadword aligned on a 64-bit boundary
57     //
58     // AMD manual says the same thing:
59     //   Single load or store operations (from instructions that do just a single load or store) are
60     //   naturally atomic on any AMD64 processor as long as they do not cross an aligned 8-byte
61     //   boundary. Accesses up to eight bytes in size which do cross such a boundary may be
62     //   performed atomically using certain instructions with a lock prefix, such as XCHG, CMPXCHG
63     //   or CMPXCHG8B, as long as all such accesses are done using the same technique.
64     //
65     // Fortunately, the RISC-V ISA manual agrees as well - only accesses to naturally aligned memory
66     // are required to be performed atomically.
67     //
68     // Thus using regular x86 movq is good enough for emulation of RISC-V behavior.
69     //
70     // But std::atomic<uint64_t> would always use heavy "lock chmpxchg8b" operation on IA32 platform
71     // because uint64_t is not guaranteed to be naturally-aligned on IA32!
72     //
73     // Not only is this slow, but this fails when we are accessing read-only memory!
74     //
75     // Use raw "movq" assembler instruction to circumvent that limitation of IA32 ABI.
76     __asm__ __volatile__("movq (%1),%0" : "=x"(reservation) : "r"(addr));
77 #else
78     reservation = std::atomic_load_explicit(ToHostAddr<std::atomic<uint64_t>>(addr), mem_order);
79 #endif
80     return reservation;
81   } else {
82     static_bad_size();
83   }
84 }
85 
MemoryRegionReservationLoad(GuestAddr addr,std::memory_order mem_order)86 inline Reservation MemoryRegionReservationLoad(GuestAddr addr, std::memory_order mem_order) {
87   return MemoryRegionReservationLoadTemplate<Reservation>(addr, mem_order);
88 }
89 
GetEntry(GuestAddr addr)90 MemoryRegionReservation::Entry& GetEntry(GuestAddr addr) {
91   static constexpr size_t kHashSize = 4096;
92   static std::array<MemoryRegionReservation::Entry, kHashSize> g_owners;
93 
94   return g_owners[(addr / sizeof(Reservation)) % kHashSize];
95 }
96 
97 // Special owner to disallow stealing. Only used when exclusive store is in progress.
98 int g_fake_cpu;
99 constexpr void* kLockedOwner = &g_fake_cpu;
100 
101 }  // namespace
102 
SetOwner(GuestAddr aligned_addr,void * cpu)103 void MemoryRegionReservation::SetOwner(GuestAddr aligned_addr, void* cpu) {
104   auto& entry = GetEntry(aligned_addr);
105 
106   // Try stealing. Fails if another thread is doing an exclusive store or wins a race.
107   // If stealing fails, then the subsequent exclusive store fails as well.
108   auto prev = entry.load();
109   if (prev != kLockedOwner) {
110     entry.compare_exchange_strong(prev, cpu);
111   }
112 }
113 
TryLock(GuestAddr aligned_addr,void * cpu)114 MemoryRegionReservation::Entry* MemoryRegionReservation::TryLock(GuestAddr aligned_addr,
115                                                                  void* cpu) {
116   auto& entry = GetEntry(aligned_addr);
117 
118   // Try locking. Fails if Load failed to steal the address or the address was stolen afterwards.
119   if (!entry.compare_exchange_strong(cpu, kLockedOwner)) {
120     return nullptr;
121   }
122 
123   return &entry;
124 }
125 
Unlock(MemoryRegionReservation::Entry * entry)126 void MemoryRegionReservation::Unlock(MemoryRegionReservation::Entry* entry) {
127   // No need to compare and swap as the locked address cannot be stolen.
128   entry->store(nullptr);
129 }
130 
ReservationLoad(void * cpu,GuestAddr aligned_addr,std::memory_order mem_order)131 Reservation MemoryRegionReservation::ReservationLoad(void* cpu,
132                                                      GuestAddr aligned_addr,
133                                                      std::memory_order mem_order) {
134   SetOwner(aligned_addr, cpu);
135 
136   // ATTENTION!
137   // For region size <= 8, region load is atomic, so this always returns a consistent value.
138   // For region size > 8, region load is NOT atomic! The returned value might be inconsistent.
139   //
140   // If, to load a 16-byte value atomically, the guest architecture suggests to perform a 16-byte
141   // exclusive load and then an exclusive store of the loaded value. The loaded value can be used
142   // only if the exclusive store succeeds.
143   //
144   // If developers are aware of the above and do not use the result of 16-byte exclusive load
145   // without a subsequent check by an exclusive store, an inconsistent return value here is safe.
146   // Too bad if this is not the case...
147   return MemoryRegionReservationLoad(aligned_addr, mem_order);
148 }
149 
ReservationExchange(void * cpu,GuestAddr aligned_addr,Reservation expected,Reservation value,std::memory_order mem_order)150 bool MemoryRegionReservation::ReservationExchange(void* cpu,
151                                                   GuestAddr aligned_addr,
152                                                   Reservation expected,
153                                                   Reservation value,
154                                                   std::memory_order mem_order) {
155   auto* entry = TryLock(aligned_addr, cpu);
156 
157   if (!entry) {
158     return false;
159   }
160 
161   bool written = std::atomic_compare_exchange_strong_explicit(
162       ToHostAddr<std::atomic<Reservation>>(aligned_addr),
163       &expected,
164       value,
165       mem_order,
166       std::memory_order_relaxed);
167 
168   Unlock(entry);
169 
170   return written;
171 }
172 
173 }  // namespace berberis
174