mirror of
https://github.com/skyline-emu/skyline.git
synced 2025-01-09 14:09:23 +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{};
|
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);
|
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
|
* @brief The SVC Table maps all SVCs to their corresponding functions
|
||||||
*/
|
*/
|
||||||
@ -272,8 +284,8 @@ namespace skyline::kernel::svc {
|
|||||||
nullptr, // 0x31
|
nullptr, // 0x31
|
||||||
nullptr, // 0x32
|
nullptr, // 0x32
|
||||||
nullptr, // 0x33
|
nullptr, // 0x33
|
||||||
nullptr, // 0x34
|
WaitForAddress, // 0x34
|
||||||
nullptr, // 0x35
|
SignalToAddress, // 0x35
|
||||||
nullptr, // 0x36
|
nullptr, // 0x36
|
||||||
nullptr, // 0x37
|
nullptr, // 0x37
|
||||||
nullptr, // 0x38
|
nullptr, // 0x38
|
||||||
|
@ -285,4 +285,52 @@ namespace skyline::kernel::type {
|
|||||||
if (it == queue.second)
|
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
|
__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
|
* @brief Signals the conditional variable at the specified address
|
||||||
*/
|
*/
|
||||||
void ConditionalVariableSignal(u32 *key, u64 amount);
|
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…
x
Reference in New Issue
Block a user