mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-27 01:24:16 +01:00
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:
parent
ef52e22cef
commit
98b1fd9056
@ -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,17 +83,18 @@ 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};
|
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};
|
styles = {.proController = true, .joyconHandheld = true, .joyconDual = true, .joyconLeft = true, .joyconRight = true};
|
||||||
activated = true;
|
activated = true;
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void NpadManager::Deactivate() {
|
void NpadManager::Deactivate() {
|
||||||
std::lock_guard guard(mutex);
|
std::lock_guard guard(mutex);
|
||||||
|
if (activated) {
|
||||||
supportedIds = {};
|
supportedIds = {};
|
||||||
styles = {};
|
styles = {};
|
||||||
activated = false;
|
activated = false;
|
||||||
@ -105,3 +106,4 @@ namespace skyline::input {
|
|||||||
controller.device = nullptr;
|
controller.device = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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)},
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -5,13 +5,15 @@
|
|||||||
|
|
||||||
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() {
|
||||||
|
if (!activated) {
|
||||||
activated = true;
|
activated = true;
|
||||||
SetState({});
|
SetState({});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TouchManager::SetState(const span<TouchScreenPoint> &points) {
|
void TouchManager::SetState(const span<TouchScreenPoint> &points) {
|
||||||
if (!activated)
|
if (!activated)
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
if (!alwaysInsert && thread == state.thread)
|
||||||
RemoveThread();
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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{};
|
||||||
}
|
}
|
||||||
|
@ -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,12 +198,20 @@ 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);
|
||||||
|
} 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);
|
state.scheduler->UpdatePriority(state.thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock lock(statusMutex);
|
||||||
running = false;
|
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 {
|
||||||
|
if (!Scheduler::YieldPending)
|
||||||
state.scheduler->WaitSchedule();
|
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) {
|
||||||
|
statusCondition.wait(lock, [this]() { return ready || killed; });
|
||||||
|
if (!killed) {
|
||||||
pthread_kill(pthread, SIGINT);
|
pthread_kill(pthread, SIGINT);
|
||||||
if (join) {
|
killed = true;
|
||||||
if (thread.joinable())
|
statusCondition.notify_all();
|
||||||
thread.join();
|
|
||||||
else
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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),
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user