Optimize Scheduler/IPC/HID + Fix Various Bugs

* Optimize Scheduler With Per-Thread Scheduler Conditions
* Optimize IPC by yielding
* Optimize HID Vibration
* Fix Priority Inheritance
* Fix `KThread` Start/Kill/Signal Races 
* Fix `YieldPending` Races in `StartThread` & `SvcHandler` 
* Fix POSIX Time -> NN CalendarTime Conversion 
* Fix HID `TouchScreen`/`NPad` Activation
This commit is contained in:
◱ PixelyIon 2021-01-12 01:11:21 +05:30 committed by ◱ Mark
parent ef52e22cef
commit 98b1fd9056
14 changed files with 181 additions and 126 deletions

View File

@ -11,7 +11,7 @@ namespace skyline::input {
{*this, hid->npad[4], NpadId::Player5}, {*this, hid->npad[5], NpadId::Player6}, {*this, hid->npad[4], NpadId::Player5}, {*this, hid->npad[5], NpadId::Player6},
{*this, hid->npad[6], NpadId::Player7}, {*this, hid->npad[7], NpadId::Player8}, {*this, hid->npad[6], NpadId::Player7}, {*this, hid->npad[7], NpadId::Player8},
{*this, hid->npad[8], NpadId::Handheld}, {*this, hid->npad[9], NpadId::Unknown}, {*this, hid->npad[8], NpadId::Handheld}, {*this, hid->npad[9], NpadId::Unknown},
} {} } { Activate(); /* NPads are activated by default, certain homebrew is reliant on this behavior */ }
void NpadManager::Update() { void NpadManager::Update() {
std::lock_guard guard(mutex); std::lock_guard guard(mutex);
@ -83,25 +83,27 @@ namespace skyline::input {
void NpadManager::Activate() { void NpadManager::Activate() {
std::lock_guard guard(mutex); std::lock_guard guard(mutex);
if (!activated) {
supportedIds = {NpadId::Handheld, NpadId::Player1, NpadId::Player2, NpadId::Player3, NpadId::Player4, NpadId::Player5, NpadId::Player6, NpadId::Player7, NpadId::Player8};
styles = {.proController = true, .joyconHandheld = true, .joyconDual = true, .joyconLeft = true, .joyconRight = true};
activated = true;
supportedIds = {NpadId::Handheld, NpadId::Player1, NpadId::Player2, NpadId::Player3, NpadId::Player4, NpadId::Player5, NpadId::Player6, NpadId::Player7, NpadId::Player8}; Update();
styles = {.proController = true, .joyconHandheld = true, .joyconDual = true, .joyconLeft = true, .joyconRight = true}; }
activated = true;
Update();
} }
void NpadManager::Deactivate() { void NpadManager::Deactivate() {
std::lock_guard guard(mutex); std::lock_guard guard(mutex);
if (activated) {
supportedIds = {};
styles = {};
activated = false;
supportedIds = {}; for (auto &npad : npads)
styles = {}; npad.Disconnect();
activated = false;
for (auto &npad : npads) for (auto &controller : controllers)
npad.Disconnect(); controller.device = nullptr;
}
for (auto &controller : controllers)
controller.device = nullptr;
} }
} }

View File

@ -21,7 +21,7 @@ namespace skyline::input {
class NpadManager { class NpadManager {
private: private:
const DeviceState &state; const DeviceState &state;
bool activated{false}; //!< If this NpadManager is activated or not bool activated{};
friend NpadDevice; friend NpadDevice;

View File

@ -425,10 +425,15 @@ namespace skyline::input {
} }
void NpadDevice::Vibrate(bool isRight, const NpadVibrationValue &value) { void NpadDevice::Vibrate(bool isRight, const NpadVibrationValue &value) {
if (isRight) if (isRight) {
if (vibrationRight && (*vibrationRight) == value)
return;
vibrationRight = value; vibrationRight = value;
else } else {
if (vibrationLeft == value)
return;
vibrationLeft = value; vibrationLeft = value;
}
if (vibrationRight) if (vibrationRight)
Vibrate(vibrationLeft, *vibrationRight); Vibrate(vibrationLeft, *vibrationRight);
@ -437,6 +442,12 @@ namespace skyline::input {
} }
void NpadDevice::Vibrate(const NpadVibrationValue &left, const NpadVibrationValue &right) { void NpadDevice::Vibrate(const NpadVibrationValue &left, const NpadVibrationValue &right) {
if (vibrationLeft == left && vibrationRight && (*vibrationRight) == right)
return;
vibrationLeft = left;
vibrationRight = right;
if (partnerIndex == constant::NullIndex) { if (partnerIndex == constant::NullIndex) {
std::array<VibrationInfo, 4> vibrations{ std::array<VibrationInfo, 4> vibrations{
VibrationInfo{left.frequencyLow, left.amplitudeLow * (constant::AmplitudeMax / 4)}, VibrationInfo{left.frequencyLow, left.amplitudeLow * (constant::AmplitudeMax / 4)},

View File

@ -105,6 +105,13 @@ namespace skyline::input {
float frequencyLow; float frequencyLow;
float amplitudeHigh; float amplitudeHigh;
float frequencyHigh; float frequencyHigh;
bool operator==(const NpadVibrationValue &other) const {
return (amplitudeLow == other.amplitudeLow) &&
(frequencyLow == other.frequencyLow) &&
(amplitudeHigh == other.amplitudeHigh) &&
(frequencyHigh == other.frequencyHigh);
}
}; };
static_assert(sizeof(NpadVibrationValue) == 0x10); static_assert(sizeof(NpadVibrationValue) == 0x10);

View File

@ -5,12 +5,14 @@
namespace skyline::input { namespace skyline::input {
TouchManager::TouchManager(const DeviceState &state, input::HidSharedMemory *hid) : state(state), section(hid->touchScreen) { TouchManager::TouchManager(const DeviceState &state, input::HidSharedMemory *hid) : state(state), section(hid->touchScreen) {
Activate(); Activate(); // The touch screen is expected to be activated by default, commercial games are reliant on this behavior
} }
void TouchManager::Activate() { void TouchManager::Activate() {
activated = true; if (!activated) {
SetState({}); activated = true;
SetState({});
}
} }
void TouchManager::SetState(const span<TouchScreenPoint> &points) { void TouchManager::SetState(const span<TouchScreenPoint> &points) {
@ -37,7 +39,7 @@ namespace skyline::input {
section.header.timestamp = util::GetTimeTicks(); section.header.timestamp = util::GetTimeTicks();
section.header.entryCount = std::min(static_cast<u8>(section.header.entryCount + 1), constant::HidEntryCount); section.header.entryCount = std::min(static_cast<u8>(section.header.entryCount + 1), constant::HidEntryCount);
section.header.maxEntry = constant::HidEntryCount - 1; section.header.maxEntry = section.header.entryCount;
section.header.currentEntry = entryIndex; section.header.currentEntry = entryIndex;
} }
} }

View File

@ -15,8 +15,8 @@ namespace skyline::kernel {
if (*tls) { if (*tls) {
const auto &state{*reinterpret_cast<nce::ThreadContext *>(*tls)->state}; const auto &state{*reinterpret_cast<nce::ThreadContext *>(*tls)->state};
state.scheduler->Rotate(false); state.scheduler->Rotate(false);
state.scheduler->WaitSchedule();
YieldPending = false; YieldPending = false;
state.scheduler->WaitSchedule();
} else { } else {
YieldPending = true; YieldPending = true;
} }
@ -59,7 +59,10 @@ namespace skyline::kernel {
} }
if (optimalCore != currentCore) { if (optimalCore != currentCore) {
RemoveThread(); if (!alwaysInsert && thread == state.thread)
RemoveThread();
else if (!alwaysInsert && thread != state.thread)
throw exception("Migrating an external thread (T{}) without 'alwaysInsert' isn't supported", thread->id);
thread->coreId = optimalCore->id; thread->coreId = optimalCore->id;
InsertThread(thread); InsertThread(thread);
state.logger->Debug("Load Balancing T{}: C{} -> C{}", thread->id, currentCore->id, optimalCore->id); state.logger->Debug("Load Balancing T{}: C{} -> C{}", thread->id, currentCore->id, optimalCore->id);
@ -81,27 +84,6 @@ namespace skyline::kernel {
void Scheduler::InsertThread(const std::shared_ptr<type::KThread> &thread) { void Scheduler::InsertThread(const std::shared_ptr<type::KThread> &thread) {
auto &core{cores.at(thread->coreId)}; auto &core{cores.at(thread->coreId)};
thread_local bool signalHandlerSetup{};
if (!signalHandlerSetup) {
signal::SetSignalHandler({YieldSignal}, SignalHandler);
signalHandlerSetup = true;
}
if (!thread->preemptionTimer) {
struct sigevent event{
.sigev_signo = YieldSignal,
.sigev_notify = SIGEV_THREAD_ID,
._sigev_un = {
._tid = gettid(),
},
};
timer_t timer;
if (timer_create(CLOCK_THREAD_CPUTIME_ID, &event, &timer))
throw exception("timer_create has failed with '{}'", strerror(errno));
thread->preemptionTimer.emplace(timer);
}
std::unique_lock lock(core.mutex); std::unique_lock lock(core.mutex);
auto nextThread{std::upper_bound(core.queue.begin(), core.queue.end(), thread->priority.load(), type::KThread::IsHigherPriority)}; auto nextThread{std::upper_bound(core.queue.begin(), core.queue.end(), thread->priority.load(), type::KThread::IsHigherPriority)};
if (nextThread == core.queue.begin()) { if (nextThread == core.queue.begin()) {
@ -116,10 +98,8 @@ namespace skyline::kernel {
} else { } else {
core.queue.push_front(thread); core.queue.push_front(thread);
} }
if (thread != state.thread) { if (thread != state.thread)
lock.unlock(); // We should unlock this prior to waking all threads to not cause contention on the core lock thread->wakeCondition.notify_one(); // We only want to trigger the conditional variable if the current thread isn't inserting itself
core.frontCondition.notify_all(); // We only want to trigger the conditional variable if the current thread isn't inserting itself
}
} else { } else {
core.queue.insert(nextThread, thread); core.queue.insert(nextThread, thread);
} }
@ -132,7 +112,7 @@ namespace skyline::kernel {
std::shared_lock lock(core->mutex); std::shared_lock lock(core->mutex);
if (loadBalance && thread->affinityMask.count() > 1) { if (loadBalance && thread->affinityMask.count() > 1) {
std::chrono::milliseconds loadBalanceThreshold{PreemptiveTimeslice * 2}; //!< The amount of time that needs to pass unscheduled for a thread to attempt load balancing std::chrono::milliseconds loadBalanceThreshold{PreemptiveTimeslice * 2}; //!< The amount of time that needs to pass unscheduled for a thread to attempt load balancing
while (!core->frontCondition.wait_for(lock, loadBalanceThreshold, [&]() { return !core->queue.empty() && core->queue.front() == thread; })) { while (!thread->wakeCondition.wait_for(lock, loadBalanceThreshold, [&]() { return !core->queue.empty() && core->queue.front() == thread; })) {
lock.unlock(); lock.unlock();
LoadBalance(state.thread); LoadBalance(state.thread);
if (thread->coreId == core->id) { if (thread->coreId == core->id) {
@ -145,12 +125,12 @@ namespace skyline::kernel {
loadBalanceThreshold *= 2; // We double the duration required for future load balancing for this invocation to minimize pointless load balancing loadBalanceThreshold *= 2; // We double the duration required for future load balancing for this invocation to minimize pointless load balancing
} }
} else { } else {
core->frontCondition.wait(lock, [&]() { return !core->queue.empty() && core->queue.front() == thread; }); thread->wakeCondition.wait(lock, [&]() { return !core->queue.empty() && core->queue.front() == thread; });
} }
if (thread->priority == core->preemptionPriority) { if (thread->priority == core->preemptionPriority) {
struct itimerspec spec{.it_value = {.tv_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(PreemptiveTimeslice).count()}}; struct itimerspec spec{.it_value = {.tv_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(PreemptiveTimeslice).count()}};
timer_settime(*thread->preemptionTimer, 0, &spec, nullptr); timer_settime(thread->preemptionTimer, 0, &spec, nullptr);
thread->isPreempted = true; thread->isPreempted = true;
} }
@ -162,10 +142,10 @@ namespace skyline::kernel {
auto *core{&cores.at(thread->coreId)}; auto *core{&cores.at(thread->coreId)};
std::shared_lock lock(core->mutex); std::shared_lock lock(core->mutex);
if (core->frontCondition.wait_for(lock, timeout, [&]() { return !core->queue.empty() && core->queue.front() == thread; })) { if (thread->wakeCondition.wait_for(lock, timeout, [&]() { return !core->queue.empty() && core->queue.front() == thread; })) {
if (thread->priority == core->preemptionPriority) { if (thread->priority == core->preemptionPriority) {
struct itimerspec spec{.it_value = {.tv_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(PreemptiveTimeslice).count()}}; struct itimerspec spec{.it_value = {.tv_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(PreemptiveTimeslice).count()}};
timer_settime(*thread->preemptionTimer, 0, &spec, nullptr); timer_settime(thread->preemptionTimer, 0, &spec, nullptr);
thread->isPreempted = true; thread->isPreempted = true;
} }
@ -188,18 +168,18 @@ namespace skyline::kernel {
// Splice the linked element from the beginning of the queue to where it's priority is present // Splice the linked element from the beginning of the queue to where it's priority is present
core.queue.splice(std::upper_bound(core.queue.begin(), core.queue.end(), thread->priority.load(), type::KThread::IsHigherPriority), core.queue, core.queue.begin()); core.queue.splice(std::upper_bound(core.queue.begin(), core.queue.end(), thread->priority.load(), type::KThread::IsHigherPriority), core.queue, core.queue.begin());
if (core.queue.front() != thread) { auto front{core.queue.front()};
// If we aren't at the front of the queue, only then should we notify other threads that the front of the queue has changed if (front != thread)
lock.unlock(); front->wakeCondition.notify_one(); // If we aren't at the front of the queue, only then should we wake the thread at the front up
core.frontCondition.notify_all();
}
if (cooperative && thread->isPreempted) { if (cooperative && thread->isPreempted) {
// If a preemptive thread did a cooperative yield then we need to disarm the preemptive timer // If a preemptive thread did a cooperative yield then we need to disarm the preemptive timer
struct itimerspec spec{}; struct itimerspec spec{};
timer_settime(*thread->preemptionTimer, 0, &spec, nullptr); timer_settime(thread->preemptionTimer, 0, &spec, nullptr);
} }
thread->isPreempted = false; thread->isPreempted = false;
} else {
throw exception("T{} called Rotate while not being at the front of C{}'s queue", thread->id, thread->coreId);
} }
} }
@ -213,11 +193,14 @@ namespace skyline::kernel {
// If the thread isn't in the queue then the new priority will be handled automatically on insertion // If the thread isn't in the queue then the new priority will be handled automatically on insertion
return; return;
if (currentIt == core->queue.begin()) { if (currentIt == core->queue.begin()) {
// Alternatively, if it's currently running then we'd just want to reorder after it rotates // Alternatively, if it's currently running then we'd just want to cause it to yield if it's priority is lower than the the thread behind it
if (!thread->isPreempted && thread->priority == core->preemptionPriority) { auto nextIt{std::next(currentIt)};
if (nextIt != core->queue.end() && (*nextIt)->priority < thread->priority) {
thread->SendSignal(YieldSignal);
} else if (!thread->isPreempted && thread->priority == core->preemptionPriority) {
// If the thread needs to be preempted due to the new priority then arm it's preemption timer // If the thread needs to be preempted due to the new priority then arm it's preemption timer
struct itimerspec spec{.it_value = {.tv_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(PreemptiveTimeslice).count()}}; struct itimerspec spec{.it_value = {.tv_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(PreemptiveTimeslice).count()}};
timer_settime(*thread->preemptionTimer, 0, &spec, nullptr); timer_settime(thread->preemptionTimer, 0, &spec, nullptr);
thread->isPreempted = true; thread->isPreempted = true;
} }
return; return;
@ -232,7 +215,7 @@ namespace skyline::kernel {
if (thread->isPreempted && thread->priority != core->preemptionPriority) { if (thread->isPreempted && thread->priority != core->preemptionPriority) {
struct itimerspec spec{}; struct itimerspec spec{};
timer_settime(*thread->preemptionTimer, 0, &spec, nullptr); timer_settime(thread->preemptionTimer, 0, &spec, nullptr);
thread->isPreempted = false; thread->isPreempted = false;
} }
@ -247,13 +230,21 @@ namespace skyline::kernel {
void Scheduler::ParkThread() { void Scheduler::ParkThread() {
auto &thread{state.thread}; auto &thread{state.thread};
std::lock_guard migrationLock(thread->coreMigrationMutex);
RemoveThread(); RemoveThread();
{
auto originalCoreId{thread->coreId};
thread->coreId = constant::ParkedCoreId;
for (auto &core : cores)
if (originalCoreId != core.id && thread->affinityMask.test(core.id) && (core.queue.empty() || core.queue.front()->priority > thread->priority))
thread->coreId = core.id;
if (thread->coreId == constant::ParkedCoreId) {
std::unique_lock lock(parkedMutex); std::unique_lock lock(parkedMutex);
parkedQueue.insert(std::upper_bound(parkedQueue.begin(), parkedQueue.end(), thread->priority.load(), type::KThread::IsHigherPriority), thread); parkedQueue.insert(std::upper_bound(parkedQueue.begin(), parkedQueue.end(), thread->priority.load(), type::KThread::IsHigherPriority), thread);
thread->coreId = constant::ParkedCoreId; thread->wakeCondition.wait(lock, [&]() { return parkedQueue.front() == thread && thread->coreId != constant::ParkedCoreId; });
parkedFrontCondition.wait(lock, [&]() { return parkedCore.queue.front() == thread && thread->coreId != constant::ParkedCoreId; });
} }
InsertThread(thread); InsertThread(thread);
} }
@ -272,7 +263,7 @@ namespace skyline::kernel {
if (parkedThread->priority < thread->priority || (parkedThread->priority == thread->priority && (!nextThread || parkedThread->timesliceStart < nextThread->timesliceStart))) { if (parkedThread->priority < thread->priority || (parkedThread->priority == thread->priority && (!nextThread || parkedThread->timesliceStart < nextThread->timesliceStart))) {
parkedThread->coreId = thread->coreId; parkedThread->coreId = thread->coreId;
parkedLock.unlock(); parkedLock.unlock();
parkedFrontCondition.notify_all(); thread->wakeCondition.notify_one();
} }
} }
} }
@ -290,16 +281,15 @@ namespace skyline::kernel {
if (thread->timesliceStart) if (thread->timesliceStart)
thread->averageTimeslice = (thread->averageTimeslice / 4) + (3 * (util::GetTimeTicks() - thread->timesliceStart / 4)); thread->averageTimeslice = (thread->averageTimeslice / 4) + (3 * (util::GetTimeTicks() - thread->timesliceStart / 4));
// We need to notify that the front was changed, if we were at the front previously if (it != core.queue.end())
lock.unlock(); (*it)->wakeCondition.notify_one(); // We need to wake the thread at the front of the queue, if we were at the front previously
core.frontCondition.notify_all();
} }
} }
} }
if (thread->isPreempted) { if (thread->isPreempted) {
struct itimerspec spec{}; struct itimerspec spec{};
timer_settime(*thread->preemptionTimer, 0, &spec, nullptr); timer_settime(thread->preemptionTimer, 0, &spec, nullptr);
thread->isPreempted = false; thread->isPreempted = false;
} }

View File

@ -30,7 +30,7 @@ namespace skyline {
return (std::numeric_limits<u64>::max() >> ((std::numeric_limits<u64>::digits - 1 + min) - max)) << min; return (std::numeric_limits<u64>::max() >> ((std::numeric_limits<u64>::digits - 1 + min) - max)) << min;
} }
constexpr bool Valid(i8 value) const { constexpr bool Valid(u8 value) const {
return (value >= min) && (value <= max); return (value >= min) && (value <= max);
} }
}; };
@ -47,7 +47,6 @@ namespace skyline {
u8 id; u8 id;
u8 preemptionPriority; //!< The priority at which this core becomes preemptive as opposed to cooperative u8 preemptionPriority; //!< The priority at which this core becomes preemptive as opposed to cooperative
std::shared_mutex mutex; //!< Synchronizes all operations on the queue std::shared_mutex mutex; //!< Synchronizes all operations on the queue
std::condition_variable_any frontCondition; //!< A conditional variable which is signalled when the front of the queue has changed
std::list<std::shared_ptr<type::KThread>> queue; //!< A queue of threads which are running or to be run on this core std::list<std::shared_ptr<type::KThread>> queue; //!< A queue of threads which are running or to be run on this core
CoreContext(u8 id, u8 preemptionPriority); CoreContext(u8 id, u8 preemptionPriority);
@ -59,8 +58,6 @@ namespace skyline {
std::condition_variable parkedFrontCondition; //!< A conditional variable which is signalled when the front of the parked queue has changed std::condition_variable parkedFrontCondition; //!< A conditional variable which is signalled when the front of the parked queue has changed
std::list<std::shared_ptr<type::KThread>> parkedQueue; //!< A queue of threads which are parked and waiting on core migration std::list<std::shared_ptr<type::KThread>> parkedQueue; //!< A queue of threads which are parked and waiting on core migration
CoreContext parkedCore{constant::CoreCount, 64}; //!< A psuedo-core which all parked threads are moved onto
public: public:
static constexpr std::chrono::milliseconds PreemptiveTimeslice{10}; //!< The duration of time a preemptive thread can run before yielding static constexpr std::chrono::milliseconds PreemptiveTimeslice{10}; //!< The duration of time a preemptive thread can run before yielding
inline static int YieldSignal{SIGRTMIN}; //!< The signal used to cause a yield in running threads inline static int YieldSignal{SIGRTMIN}; //!< The signal used to cause a yield in running threads
@ -78,6 +75,7 @@ namespace skyline {
* @param alwaysInsert If to insert the thread even if it hasn't migrated cores, this is used during thread creation * @param alwaysInsert If to insert the thread even if it hasn't migrated cores, this is used during thread creation
* @return A reference to the CoreContext of the core which the calling thread is running on after load balancing * @return A reference to the CoreContext of the core which the calling thread is running on after load balancing
* @note This inserts the thread into the migrated process's queue after load balancing, there is no need to call it redundantly * @note This inserts the thread into the migrated process's queue after load balancing, there is no need to call it redundantly
* @note alwaysInsert makes the assumption that the thread isn't inserted in any core's queue currently
*/ */
CoreContext& LoadBalance(const std::shared_ptr<type::KThread> &thread, bool alwaysInsert = false); CoreContext& LoadBalance(const std::shared_ptr<type::KThread> &thread, bool alwaysInsert = false);

View File

@ -347,7 +347,14 @@ namespace skyline::kernel::svc {
auto thread{state.process->GetHandle<type::KThread>(handle)}; auto thread{state.process->GetHandle<type::KThread>(handle)};
state.logger->Debug("svcSetThreadPriority: Setting thread #{}'s priority to {}", thread->id, priority); state.logger->Debug("svcSetThreadPriority: Setting thread #{}'s priority to {}", thread->id, priority);
if (thread->priority != priority) { if (thread->priority != priority) {
thread->priority = priority; thread->basePriority = priority;
u8 newPriority{};
do {
// Try to CAS the priority of the thread with it's new base priority
// If the new priority is equivalent to the current priority then we don't need to CAS
newPriority = thread->priority.load();
newPriority = std::min(newPriority, priority);
} while (newPriority != priority && thread->priority.compare_exchange_strong(newPriority, priority));
state.scheduler->UpdatePriority(thread); state.scheduler->UpdatePriority(thread);
} }
state.ctx->gpr.w0 = Result{}; state.ctx->gpr.w0 = Result{};
@ -773,6 +780,7 @@ namespace skyline::kernel::svc {
} }
void SendSyncRequest(const DeviceState &state) { void SendSyncRequest(const DeviceState &state) {
SchedulerScopedLock schedulerLock(state);
state.os->serviceManager.SyncRequestHandler(static_cast<KHandle>(state.ctx->gpr.x0)); state.os->serviceManager.SyncRequestHandler(static_cast<KHandle>(state.ctx->gpr.x0));
state.ctx->gpr.w0 = Result{}; state.ctx->gpr.w0 = Result{};
} }

View File

@ -137,7 +137,7 @@ namespace skyline::kernel::type {
u8 priority, ownerPriority; u8 priority, ownerPriority;
do { do {
// Try to CAS the priority of the owner with the current thread // Try to CAS the priority of the owner with the current thread
// If they're equivalent then we don't need to CAS as the priority won't be inherited // If the new priority is equivalent to the current priority then we don't need to CAS
ownerPriority = owner->priority.load(); ownerPriority = owner->priority.load();
priority = std::min(ownerPriority, state.thread->priority.load()); priority = std::min(ownerPriority, state.thread->priority.load());
} while (ownerPriority != priority && owner->priority.compare_exchange_strong(ownerPriority, priority)); } while (ownerPriority != priority && owner->priority.compare_exchange_strong(ownerPriority, priority));
@ -198,13 +198,21 @@ namespace skyline::kernel::type {
if (!waiters.empty()) { if (!waiters.empty()) {
// If there are threads still waiting on us then try to inherit their priority // If there are threads still waiting on us then try to inherit their priority
auto highestPriority{waiters.front()}; auto highestPriorityThread{waiters.front()};
u8 priority, ownerPriority; u8 newPriority, basePriority;
do { do {
ownerPriority = state.thread->priority.load(); basePriority = state.thread->basePriority.load();
priority = std::min(ownerPriority, highestPriority->priority.load()); newPriority = std::min(basePriority, highestPriorityThread->priority.load());
} while (ownerPriority != priority && nextOwner->priority.compare_exchange_strong(ownerPriority, priority)); } while (basePriority != newPriority && state.thread->priority.compare_exchange_strong(basePriority, newPriority));
state.scheduler->UpdatePriority(state.thread); state.scheduler->UpdatePriority(state.thread);
} else {
u8 priority, basePriority;
do {
basePriority = state.thread->basePriority.load();
priority = state.thread->priority.load();
} while (priority != basePriority && !state.thread->priority.compare_exchange_strong(priority, basePriority));
if (priority != basePriority)
state.scheduler->UpdatePriority(state.thread);
} }
if (nextWaiter) { if (nextWaiter) {

View File

@ -10,22 +10,16 @@
#include "KThread.h" #include "KThread.h"
namespace skyline::kernel::type { namespace skyline::kernel::type {
KThread::KThread(const DeviceState &state, KHandle handle, KProcess *parent, size_t id, void *entry, u64 argument, void *stackTop, u8 priority, i8 idealCore) : handle(handle), parent(parent), id(id), entry(entry), entryArgument(argument), stackTop(stackTop), priority(priority), idealCore(idealCore), coreId(idealCore), KSyncObject(state, KType::KThread) { KThread::KThread(const DeviceState &state, KHandle handle, KProcess *parent, size_t id, void *entry, u64 argument, void *stackTop, u8 priority, i8 idealCore) : handle(handle), parent(parent), id(id), entry(entry), entryArgument(argument), stackTop(stackTop), priority(priority), basePriority(priority), idealCore(idealCore), coreId(idealCore), KSyncObject(state, KType::KThread) {
affinityMask.set(coreId); affinityMask.set(coreId);
} }
KThread::~KThread() { KThread::~KThread() {
std::unique_lock lock(mutex); Kill(true);
if (running && pthread != pthread_self()) {
pthread_kill(pthread, SIGINT);
if (!thread.joinable())
pthread_join(pthread, nullptr);
}
if (thread.joinable()) if (thread.joinable())
thread.join(); thread.join();
if (preemptionTimer) if (preemptionTimer)
timer_delete(*preemptionTimer); timer_delete(preemptionTimer);
} }
void KThread::StartThread() { void KThread::StartThread() {
@ -44,7 +38,13 @@ namespace skyline::kernel::type {
if (setjmp(originalCtx)) { // Returns 1 if it's returning from guest, 0 otherwise if (setjmp(originalCtx)) { // Returns 1 if it's returning from guest, 0 otherwise
state.scheduler->RemoveThread(); state.scheduler->RemoveThread();
running = false; {
std::unique_lock lock(statusMutex);
running = false;
ready = false;
statusCondition.notify_all();
}
Signal(); Signal();
if (threadName[0] != 'H' || threadName[1] != 'O' || threadName[2] != 'S' || threadName[3] != '-') { if (threadName[0] != 'H' || threadName[1] != 'O' || threadName[2] != 'S' || threadName[3] != '-') {
@ -55,10 +55,32 @@ namespace skyline::kernel::type {
return; return;
} }
struct sigevent event{
.sigev_signo = Scheduler::YieldSignal,
.sigev_notify = SIGEV_THREAD_ID,
.sigev_notify_thread_id = gettid(),
};
if (timer_create(CLOCK_THREAD_CPUTIME_ID, &event, &preemptionTimer))
throw exception("timer_create has failed with '{}'", strerror(errno));
signal::SetSignalHandler({SIGINT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV}, nce::NCE::SignalHandler); signal::SetSignalHandler({SIGINT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV}, nce::NCE::SignalHandler);
signal::SetSignalHandler({Scheduler::YieldSignal}, Scheduler::SignalHandler);
{
std::lock_guard lock(statusMutex);
ready = true;
statusCondition.notify_all();
}
try { try {
state.scheduler->WaitSchedule(); if (!Scheduler::YieldPending)
state.scheduler->WaitSchedule();
while (Scheduler::YieldPending) {
// If there is a yield pending on us after thread creation
state.scheduler->Rotate();
Scheduler::YieldPending = false;
state.scheduler->WaitSchedule();
}
asm volatile( asm volatile(
"MRS X0, TPIDR_EL0\n\t" "MRS X0, TPIDR_EL0\n\t"
@ -161,10 +183,13 @@ namespace skyline::kernel::type {
} }
void KThread::Start(bool self) { void KThread::Start(bool self) {
std::unique_lock lock(mutex); std::unique_lock lock(statusMutex);
if (!running) { if (!running) {
running = true;
state.scheduler->LoadBalance(shared_from_this(), true); // This will automatically insert the thread into the core queue after load balancing state.scheduler->LoadBalance(shared_from_this(), true); // This will automatically insert the thread into the core queue after load balancing
running = true;
killed = false;
statusCondition.notify_all();
if (self) { if (self) {
pthread = pthread_self(); pthread = pthread_self();
lock.unlock(); lock.unlock();
@ -177,22 +202,23 @@ namespace skyline::kernel::type {
} }
void KThread::Kill(bool join) { void KThread::Kill(bool join) {
std::lock_guard lock(mutex); std::unique_lock lock(statusMutex);
if (running) { if (!killed && running) {
pthread_kill(pthread, SIGINT); statusCondition.wait(lock, [this]() { return ready || killed; });
if (join) { if (!killed) {
if (thread.joinable()) pthread_kill(pthread, SIGINT);
thread.join(); killed = true;
else statusCondition.notify_all();
pthread_join(pthread, nullptr);
} }
running = false;
} }
if (join)
statusCondition.wait(lock, [this]() { return !running; });
} }
void KThread::SendSignal(int signal) { void KThread::SendSignal(int signal) {
std::lock_guard lock(mutex); std::unique_lock lock(statusMutex);
if (running) statusCondition.wait(lock, [this]() { return ready || killed; });
if (!killed && running)
pthread_kill(pthread, signal); pthread_kill(pthread, signal);
} }
} }

View File

@ -24,9 +24,11 @@ namespace skyline {
void StartThread(); void StartThread();
public: public:
std::mutex mutex; //!< Synchronizes all thread state changes std::mutex statusMutex; //!< Synchronizes all thread state changes, running/ready
std::condition_variable statusCondition; //!< A conditional variable signalled on the status of the thread changing
bool running{false}; //!< If the host thread that corresponds to this thread is running, this doesn't reflect guest scheduling changes bool running{false}; //!< If the host thread that corresponds to this thread is running, this doesn't reflect guest scheduling changes
bool killed{false}; //!< If this thread was previously running and has been killed bool killed{false}; //!< If this thread was previously running and has been killed
bool ready{false}; //!< If this thread is ready to recieve signals or not
KHandle handle; KHandle handle;
size_t id; //!< Index of thread in parent process's KThread vector size_t id; //!< Index of thread in parent process's KThread vector
@ -38,6 +40,7 @@ namespace skyline {
u64 entryArgument; u64 entryArgument;
void *stackTop; void *stackTop;
std::condition_variable_any wakeCondition; //!< A conditional variable which is signalled to wake the current thread while it's sleeping
std::atomic<u8> basePriority; //!< The priority of the thread for the scheduler without any priority-inheritance std::atomic<u8> basePriority; //!< The priority of the thread for the scheduler without any priority-inheritance
std::atomic<u8> priority; //!< The priority of the thread for the scheduler std::atomic<u8> priority; //!< The priority of the thread for the scheduler
i8 idealCore; //!< The ideal CPU core for this thread to run on i8 idealCore; //!< The ideal CPU core for this thread to run on
@ -46,7 +49,7 @@ namespace skyline {
std::mutex coreMigrationMutex; //!< Synchronizes operations which depend on which core the thread is running on std::mutex coreMigrationMutex; //!< Synchronizes operations which depend on which core the thread is running on
u64 timesliceStart{}; //!< Start of the scheduler timeslice u64 timesliceStart{}; //!< Start of the scheduler timeslice
u64 averageTimeslice{}; //!< A weighted average of the timeslice duration for this thread u64 averageTimeslice{}; //!< A weighted average of the timeslice duration for this thread
std::optional<timer_t> preemptionTimer{}; //!< A kernel timer used for preemption interrupts timer_t preemptionTimer{}; //!< A kernel timer used for preemption interrupts
bool isPreempted{}; //!< If the preemption timer has been armed and will fire bool isPreempted{}; //!< If the preemption timer has been armed and will fire
std::mutex waiterMutex; //!< Synchronizes operations on mutation of the waiter members std::mutex waiterMutex; //!< Synchronizes operations on mutation of the waiter members
u32* waitKey; //!< The key of the mutex which this thread is waiting on u32* waitKey; //!< The key of the mutex which this thread is waiting on

View File

@ -25,10 +25,10 @@ namespace skyline::nce {
throw exception("Unimplemented SVC 0x{:X}", svc); throw exception("Unimplemented SVC 0x{:X}", svc);
} }
if (kernel::Scheduler::YieldPending) { while (kernel::Scheduler::YieldPending) {
state.scheduler->Rotate(false); state.scheduler->Rotate(false);
state.scheduler->WaitSchedule();
kernel::Scheduler::YieldPending = false; kernel::Scheduler::YieldPending = false;
state.scheduler->WaitSchedule();
} }
} catch (const signal::SignalException &e) { } catch (const signal::SignalException &e) {
if (e.signal != SIGINT) { if (e.signal != SIGINT) {

View File

@ -7,12 +7,12 @@ namespace skyline::service::timesrv {
ITimeZoneService::ITimeZoneService(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager) {} ITimeZoneService::ITimeZoneService(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager) {}
Result ITimeZoneService::ToCalendarTimeWithMyRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { Result ITimeZoneService::ToCalendarTimeWithMyRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto curTime{std::time(nullptr)}; auto& time{request.Pop<u64>()};
auto calender{*std::gmtime(&curTime)}; auto calender{*std::gmtime(reinterpret_cast<const time_t *>(&time))};
CalendarTime calendarTime{ CalendarTime calendarTime{
.year = static_cast<u16>(calender.tm_year), .year = static_cast<u16>(calender.tm_year),
.month = static_cast<u8>(calender.tm_mon), .month = static_cast<u8>(calender.tm_mon + 1),
.day = static_cast<u8>(calender.tm_hour), .day = static_cast<u8>(calender.tm_hour),
.minute = static_cast<u8>(calender.tm_min), .minute = static_cast<u8>(calender.tm_min),
.second = static_cast<u8>(calender.tm_sec), .second = static_cast<u8>(calender.tm_sec),
@ -22,7 +22,7 @@ namespace skyline::service::timesrv {
CalendarAdditionalInfo calendarInfo{ CalendarAdditionalInfo calendarInfo{
.dayWeek = static_cast<u32>(calender.tm_wday), .dayWeek = static_cast<u32>(calender.tm_wday),
.dayMonth = static_cast<u32>(calender.tm_mday), .dayMonth = static_cast<u32>(calender.tm_mday),
.name = *reinterpret_cast<const u64 *>(calender.tm_zone), .tzName = *reinterpret_cast<const u64 *>(calender.tm_zone),
.dst = static_cast<i32>(calender.tm_isdst), .dst = static_cast<i32>(calender.tm_isdst),
.utcRel = static_cast<u32>(calender.tm_gmtoff), .utcRel = static_cast<u32>(calender.tm_gmtoff),
}; };

View File

@ -13,28 +13,28 @@ namespace skyline::service::timesrv {
class ITimeZoneService : public BaseService { class ITimeZoneService : public BaseService {
private: private:
/** /**
* @brief A particular time point in calendar format * @brief A particular time point in Nintendo's calendar format
*/ */
struct CalendarTime { struct CalendarTime {
u16 year; //!< The Year component of the date u16 year; //!< Amount of years that have passed since 1900
u8 month; //!< The Month component of the date u8 month; //!< Month of the year (1-12) [POSIX time uses 0-11]
u8 day; //!< The Day component of the date u8 day; //!< Day of the month (1-31)
u8 hour; //!< The Hour component of the date u8 hour; //!< Hour of the day (0-23)
u8 minute; //!< The Minute component of the date u8 minute; //!< Minute of the hour (0-59)
u8 second; //!< The Second component of the date u8 second; //!< Second of the minute (0-60)
u8 _pad_; u8 _pad_;
}; };
static_assert(sizeof(CalendarTime) == 0x8); static_assert(sizeof(CalendarTime) == 0x8);
/** /**
* @brief Information that is packaged along with CalendarTime * @brief Additional metadata about the time alongside CalendarTime
*/ */
struct CalendarAdditionalInfo { struct CalendarAdditionalInfo {
u32 dayWeek; //!< The amount of days since Sunday u32 dayWeek; //!< Amount of days since Sunday
u32 dayMonth; //!< The amount of days since the start of the month u32 dayMonth; //!< Amount of days since the start of the month
u64 name; //!< The name of the time zone u64 tzName; //!< The name of the time zone
i32 dst; //!< If DST is in effect or not i32 dst; //!< If DST is in effect or not
u32 utcRel; //!< The offset of the time from GMT in seconds u32 utcRel; //!< Offset of the time from GMT in seconds
}; };
static_assert(sizeof(CalendarAdditionalInfo) == 0x18); static_assert(sizeof(CalendarAdditionalInfo) == 0x18);