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:
PixelyIon 2021-02-21 23:38:13 +05:30 committed by ◱ Mark
parent 20bdda6a63
commit 1884d98163
4 changed files with 187 additions and 2 deletions

View File

@ -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{};
}
}

View File

@ -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

View File

@ -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 {};
}
}

View File

@ -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);
};
}
}