|
|
@ -10,45 +10,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
namespace Service::Time {
|
|
|
|
namespace Service::Time {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Note: this type is not safe for concurrent writes.
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
|
|
|
struct LockFreeAtomicType {
|
|
|
|
|
|
|
|
u32 counter_;
|
|
|
|
|
|
|
|
std::array<T, 2> value_;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
|
|
|
static inline void StoreToLockFreeAtomicType(LockFreeAtomicType<T>* p, const T& value) {
|
|
|
|
|
|
|
|
// Get the current counter.
|
|
|
|
|
|
|
|
auto counter = p->counter_;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Increment the counter.
|
|
|
|
|
|
|
|
++counter;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Store the updated value.
|
|
|
|
|
|
|
|
p->value_[counter % 2] = value;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Fence memory.
|
|
|
|
|
|
|
|
std::atomic_thread_fence(std::memory_order_release);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Set the updated counter.
|
|
|
|
|
|
|
|
p->counter_ = counter;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
|
|
|
static inline T LoadFromLockFreeAtomicType(const LockFreeAtomicType<T>* p) {
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
|
|
// Get the counter.
|
|
|
|
|
|
|
|
auto counter = p->counter_;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Get the value.
|
|
|
|
|
|
|
|
auto value = p->value_[counter % 2];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Fence memory.
|
|
|
|
|
|
|
|
std::atomic_thread_fence(std::memory_order_acquire);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Check that the counter matches.
|
|
|
|
|
|
|
|
if (counter == p->counter_) {
|
|
|
|
|
|
|
|
return value;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class SharedMemory final {
|
|
|
|
class SharedMemory final {
|
|
|
|
public:
|
|
|
|
public:
|
|
|
|
explicit SharedMemory(Core::System& system_);
|
|
|
|
explicit SharedMemory(Core::System& system_);
|
|
|
|
~SharedMemory();
|
|
|
|
~SharedMemory();
|
|
|
|
|
|
|
|
|
|
|
|
// TODO(ogniK): We have to properly simulate memory barriers, how are we going to do this?
|
|
|
|
|
|
|
|
template <typename T, std::size_t Offset>
|
|
|
|
|
|
|
|
struct MemoryBarrier {
|
|
|
|
|
|
|
|
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
|
|
|
|
|
|
|
|
u32_le read_attempt{};
|
|
|
|
|
|
|
|
std::array<T, 2> data{};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// These are not actually memory barriers at the moment as we don't have multicore and all
|
|
|
|
|
|
|
|
// HLE is mutexed. This will need to properly be implemented when we start updating the time
|
|
|
|
|
|
|
|
// points on threads. As of right now, we'll be updated both values synchronously and just
|
|
|
|
|
|
|
|
// incrementing the read_attempt to indicate that we waited.
|
|
|
|
|
|
|
|
void StoreData(u8* shared_memory, T data_to_store) {
|
|
|
|
|
|
|
|
std::memcpy(this, shared_memory + Offset, sizeof(*this));
|
|
|
|
|
|
|
|
read_attempt++;
|
|
|
|
|
|
|
|
data[read_attempt & 1] = data_to_store;
|
|
|
|
|
|
|
|
std::memcpy(shared_memory + Offset, this, sizeof(*this));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// For reading we're just going to read the last stored value. If there was no value stored
|
|
|
|
|
|
|
|
// it will just end up reading an empty value as intended.
|
|
|
|
|
|
|
|
T ReadData(u8* shared_memory) {
|
|
|
|
|
|
|
|
std::memcpy(this, shared_memory + Offset, sizeof(*this));
|
|
|
|
|
|
|
|
return data[(read_attempt - 1) & 1];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Shared memory format
|
|
|
|
// Shared memory format
|
|
|
|
struct Format {
|
|
|
|
struct Format {
|
|
|
|
MemoryBarrier<Clock::SteadyClockContext, 0x0> standard_steady_clock_timepoint;
|
|
|
|
LockFreeAtomicType<Clock::StandardSteadyClockTimePointType> standard_steady_clock_timepoint;
|
|
|
|
MemoryBarrier<Clock::SystemClockContext, 0x38> standard_local_system_clock_context;
|
|
|
|
LockFreeAtomicType<Clock::SystemClockContext> standard_local_system_clock_context;
|
|
|
|
MemoryBarrier<Clock::SystemClockContext, 0x80> standard_network_system_clock_context;
|
|
|
|
LockFreeAtomicType<Clock::SystemClockContext> standard_network_system_clock_context;
|
|
|
|
MemoryBarrier<bool, 0xc8> standard_user_system_clock_automatic_correction;
|
|
|
|
LockFreeAtomicType<bool> is_standard_user_system_clock_automatic_correction_enabled;
|
|
|
|
u32_le format_version;
|
|
|
|
u32 format_version;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
static_assert(offsetof(Format, standard_steady_clock_timepoint) == 0x0);
|
|
|
|
|
|
|
|
static_assert(offsetof(Format, standard_local_system_clock_context) == 0x38);
|
|
|
|
|
|
|
|
static_assert(offsetof(Format, standard_network_system_clock_context) == 0x80);
|
|
|
|
|
|
|
|
static_assert(offsetof(Format, is_standard_user_system_clock_automatic_correction_enabled) ==
|
|
|
|
|
|
|
|
0xc8);
|
|
|
|
static_assert(sizeof(Format) == 0xd8, "Format is an invalid size");
|
|
|
|
static_assert(sizeof(Format) == 0xd8, "Format is an invalid size");
|
|
|
|
|
|
|
|
|
|
|
|
void SetupStandardSteadyClock(const Common::UUID& clock_source_id,
|
|
|
|
void SetupStandardSteadyClock(const Common::UUID& clock_source_id,
|
|
|
@ -56,10 +79,10 @@ public:
|
|
|
|
void UpdateLocalSystemClockContext(const Clock::SystemClockContext& context);
|
|
|
|
void UpdateLocalSystemClockContext(const Clock::SystemClockContext& context);
|
|
|
|
void UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context);
|
|
|
|
void UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context);
|
|
|
|
void SetAutomaticCorrectionEnabled(bool is_enabled);
|
|
|
|
void SetAutomaticCorrectionEnabled(bool is_enabled);
|
|
|
|
|
|
|
|
Format* GetFormat();
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
private:
|
|
|
|
Core::System& system;
|
|
|
|
Core::System& system;
|
|
|
|
Format shared_memory_format{};
|
|
|
|
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace Service::Time
|
|
|
|
} // namespace Service::Time
|
|
|
|