mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-04 23:35:12 +01:00
Implement Address Arbiter
The entirety of the address arbiter is implemented in this commit, all three arbitration types: `WaitIfLessThan`, `DecrementAndWaitIfLessThan` and `WaitIfEqual`, and all three signal types: `Signal`, `SignalAndIncrementIfEqual` and `SignalAndModifyBasedOnWaitingThreadCountIfEqual` have been implemented. This allows any application which uses levent (Light Events) to function which includes titles such as ARMS.
This commit is contained in:
parent
20bdda6a63
commit
1884d98163
@ -1032,4 +1032,119 @@ namespace skyline::kernel::svc {
|
||||
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
}
|
||||
|
||||
void WaitForAddress(const DeviceState &state) {
|
||||
auto address{reinterpret_cast<u32 *>(state.ctx->gpr.x0)};
|
||||
if (!util::WordAligned(address)) [[unlikely]] {
|
||||
state.logger->Warn("svcWaitForAddress: 'address' not word aligned: 0x{:X}", address);
|
||||
state.ctx->gpr.w0 = result::InvalidAddress;
|
||||
return;
|
||||
}
|
||||
|
||||
enum class ArbitrationType : u32 {
|
||||
WaitIfLessThan = 0,
|
||||
DecrementAndWaitIfLessThan = 1,
|
||||
WaitIfEqual = 2,
|
||||
} arbitrationType{static_cast<ArbitrationType>(static_cast<u32>(state.ctx->gpr.w1))};
|
||||
u32 value{state.ctx->gpr.w2};
|
||||
i64 timeout{static_cast<i64>(state.ctx->gpr.x3)};
|
||||
|
||||
Result result;
|
||||
switch (arbitrationType) {
|
||||
case ArbitrationType::WaitIfLessThan:
|
||||
state.logger->Debug("svcWaitForAddress: Waiting on 0x{:X} if less than {} for {}ns", address, value, timeout);
|
||||
result = state.process->WaitForAddress(address, value, timeout, [](u32 *address, u32 value) {
|
||||
return *address < value;
|
||||
});
|
||||
break;
|
||||
|
||||
case ArbitrationType::DecrementAndWaitIfLessThan:
|
||||
state.logger->Debug("svcWaitForAddress: Waiting on and decrementing 0x{:X} if less than {} for {}ns", address, value, timeout);
|
||||
result = state.process->WaitForAddress(address, value, timeout, [](u32 *address, u32 value) {
|
||||
u32 userValue{__atomic_load_n(address, __ATOMIC_SEQ_CST)};
|
||||
do {
|
||||
if (value <= userValue) [[unlikely]] // We want to explicitly decrement **after** the check
|
||||
return false;
|
||||
} while (!__atomic_compare_exchange_n(address, &userValue, userValue - 1, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST));
|
||||
return true;
|
||||
});
|
||||
break;
|
||||
|
||||
case ArbitrationType::WaitIfEqual:
|
||||
state.logger->Debug("svcWaitForAddress: Waiting on 0x{:X} if equal to {} for {}ns", address, value, timeout);
|
||||
result = state.process->WaitForAddress(address, value, timeout, [](u32 *address, u32 value) {
|
||||
return *address == value;
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
[[unlikely]]
|
||||
state.logger->Error("svcWaitForAddress: 'arbitrationType' invalid: {}", arbitrationType);
|
||||
state.ctx->gpr.w0 = result::InvalidEnumValue;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == Result{})
|
||||
[[likely]]
|
||||
state.logger->Debug("svcWaitForAddress: Waited on 0x{:X} successfully", address);
|
||||
else if (result == result::TimedOut)
|
||||
state.logger->Debug("svcWaitForAddress: Wait on 0x{:X} has timed out after {}ns", address, timeout);
|
||||
else if (result == result::InvalidState)
|
||||
state.logger->Debug("svcWaitForAddress: The value at 0x{:X} did not satisfy the arbitration condition", address);
|
||||
|
||||
state.ctx->gpr.w0 = result;
|
||||
}
|
||||
|
||||
void SignalToAddress(const DeviceState &state) {
|
||||
auto address{reinterpret_cast<u32 *>(state.ctx->gpr.x0)};
|
||||
if (!util::WordAligned(address)) [[unlikely]] {
|
||||
state.logger->Warn("svcWaitForAddress: 'address' not word aligned: 0x{:X}", address);
|
||||
state.ctx->gpr.w0 = result::InvalidAddress;
|
||||
return;
|
||||
}
|
||||
|
||||
enum class SignalType : u32 {
|
||||
Signal = 0,
|
||||
SignalAndIncrementIfEqual = 1,
|
||||
SignalAndModifyBasedOnWaitingThreadCountIfEqual = 2,
|
||||
} signalType{static_cast<SignalType>(static_cast<u32>(state.ctx->gpr.w1))};
|
||||
u32 value{state.ctx->gpr.w2};
|
||||
i32 count{static_cast<i32>(state.ctx->gpr.w3)};
|
||||
|
||||
Result result;
|
||||
switch (signalType) {
|
||||
case SignalType::Signal:
|
||||
state.logger->Debug("svcSignalToAddress: Signalling 0x{:X} for {} waiters", address, count);
|
||||
result = state.process->SignalToAddress(address, value, count);
|
||||
break;
|
||||
|
||||
case SignalType::SignalAndIncrementIfEqual:
|
||||
state.logger->Debug("svcSignalToAddress: Signalling 0x{:X} and incrementing if equal to {} for {} waiters", address, value, count);
|
||||
result = state.process->SignalToAddress(address, value, count, [](u32 *address, u32 value, u32) {
|
||||
return __atomic_compare_exchange_n(address, &value, value + 1, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
|
||||
});
|
||||
break;
|
||||
|
||||
case SignalType::SignalAndModifyBasedOnWaitingThreadCountIfEqual:
|
||||
state.logger->Debug("svcSignalToAddress: Signalling 0x{:X} and setting to waiting thread count if equal to {} for {} waiters", address, value, count);
|
||||
result = state.process->SignalToAddress(address, value, count, [](u32 *address, u32 value, u32 waiterCount) {
|
||||
return __atomic_compare_exchange_n(address, &value, waiterCount, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
[[unlikely]]
|
||||
state.logger->Error("svcSignalToAddress: 'signalType' invalid: {}", signalType);
|
||||
state.ctx->gpr.w0 = result::InvalidEnumValue;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == Result{})
|
||||
[[likely]]
|
||||
state.logger->Debug("svcSignalToAddress: Signalled 0x{:X} for {} successfully", address, count);
|
||||
else if (result == result::InvalidState)
|
||||
state.logger->Debug("svcSignalToAddress: The value at 0x{:X} did not satisfy the mutation condition", address);
|
||||
|
||||
state.ctx->gpr.w0 = Result{};
|
||||
}
|
||||
}
|
||||
|
@ -216,6 +216,18 @@ namespace skyline::kernel::svc {
|
||||
*/
|
||||
void UnmapPhysicalMemory(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Waits on an address based on the value of the address
|
||||
* @url https://switchbrew.org/wiki/SVC#WaitForAddress
|
||||
*/
|
||||
void WaitForAddress(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Signals a thread which is waiting on an address
|
||||
* @url https://switchbrew.org/wiki/SVC#SignalToAddress
|
||||
*/
|
||||
void SignalToAddress(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief The SVC Table maps all SVCs to their corresponding functions
|
||||
*/
|
||||
@ -272,8 +284,8 @@ namespace skyline::kernel::svc {
|
||||
nullptr, // 0x31
|
||||
nullptr, // 0x32
|
||||
nullptr, // 0x33
|
||||
nullptr, // 0x34
|
||||
nullptr, // 0x35
|
||||
WaitForAddress, // 0x34
|
||||
SignalToAddress, // 0x35
|
||||
nullptr, // 0x36
|
||||
nullptr, // 0x37
|
||||
nullptr, // 0x38
|
||||
|
@ -285,4 +285,52 @@ namespace skyline::kernel::type {
|
||||
if (it == queue.second)
|
||||
__atomic_store_n(key, false, __ATOMIC_SEQ_CST); // We need to update the boolean flag denoting that there are no more threads waiting on this conditional variable
|
||||
}
|
||||
|
||||
Result KProcess::WaitForAddress(u32 *address, u32 value, i64 timeout, bool (*arbitrationFunction)(u32 *, u32)) {
|
||||
{
|
||||
std::lock_guard lock(syncWaiterMutex);
|
||||
if (!arbitrationFunction(address, value)) [[unlikely]]
|
||||
return result::InvalidState;
|
||||
|
||||
auto queue{syncWaiters.equal_range(address)};
|
||||
syncWaiters.insert(std::upper_bound(queue.first, queue.second, state.thread->priority.load(), [](const i8 priority, const SyncWaiters::value_type &it) { return it.second->priority > priority; }), {address, state.thread});
|
||||
|
||||
state.scheduler->RemoveThread();
|
||||
}
|
||||
|
||||
if (timeout > 0 && !state.scheduler->TimedWaitSchedule(std::chrono::nanoseconds(timeout))) {
|
||||
std::unique_lock lock(syncWaiterMutex);
|
||||
auto queue{syncWaiters.equal_range(address)};
|
||||
auto iterator{std::find(queue.first, queue.second, SyncWaiters::value_type{address, state.thread})};
|
||||
if (iterator != queue.second)
|
||||
if (syncWaiters.erase(iterator) == queue.second)
|
||||
__atomic_store_n(address, false, __ATOMIC_SEQ_CST);
|
||||
|
||||
lock.unlock();
|
||||
state.scheduler->InsertThread(state.thread);
|
||||
state.scheduler->WaitSchedule();
|
||||
|
||||
return result::TimedOut;
|
||||
} else {
|
||||
state.scheduler->WaitSchedule(false);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result KProcess::SignalToAddress(u32 *address, u32 value, i32 amount, bool(*mutateFunction)(u32 *address, u32 value, u32 waiterCount)) {
|
||||
std::lock_guard lock(syncWaiterMutex);
|
||||
auto queue{syncWaiters.equal_range(address)};
|
||||
|
||||
if (mutateFunction)
|
||||
if (!mutateFunction(address, value, (amount <= 0) ? 0 : std::min(static_cast<u32>(std::distance(queue.first, queue.second) - amount), 0U))) [[unlikely]]
|
||||
return result::InvalidState;
|
||||
|
||||
i32 waiterCount{amount};
|
||||
if (queue.first != queue.second)
|
||||
for (auto it{queue.first}; it != queue.second && (amount <= 0 || waiterCount); it = syncWaiters.erase(it), waiterCount--)
|
||||
state.scheduler->InsertThread(it->second);
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
@ -222,6 +222,16 @@ namespace skyline {
|
||||
* @brief Signals the conditional variable at the specified address
|
||||
*/
|
||||
void ConditionalVariableSignal(u32 *key, u64 amount);
|
||||
|
||||
/**
|
||||
* @brief Waits on the supplied address with the specified arbitration function
|
||||
*/
|
||||
Result WaitForAddress(u32 *address, u32 value, i64 timeout, bool(*arbitrationFunction)(u32* address, u32 value));
|
||||
|
||||
/**
|
||||
* @brief Signals a variable amount of waiters at the supplied address
|
||||
*/
|
||||
Result SignalToAddress(u32 *address, u32 value, i32 amount, bool(*mutateFunction)(u32* address, u32 value, u32 waiterCount) = nullptr);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user