/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include "sm_wait_list.hpp"
namespace ams::sm::impl {
namespace {
static constexpr size_t DeferredSessionCountMax = 0x40;
struct WaitListEntry {
sm::ServiceName service;
os::WaitableHolderType *session;
};
constinit WaitListEntry g_entries[DeferredSessionCountMax];
constinit WaitListEntry *g_processing_entry = nullptr;
constinit ServiceName g_triggered_service = InvalidServiceName;
WaitListEntry *Find(ServiceName service) {
for (auto &entry : g_entries) {
if (entry.service == service) {
return std::addressof(entry);
}
}
return nullptr;
}
}
Result StartRegisterRetry(ServiceName service) {
/* Check that we're not already processing a retry. */
AMS_ABORT_UNLESS(g_processing_entry == nullptr);
/* Find a free entry. */
auto *entry = Find(InvalidServiceName);
R_UNLESS(entry != nullptr, sm::ResultOutOfProcesses());
/* Initialize the entry. */
entry->service = service;
entry->session = nullptr;
/* Set the processing entry. */
g_processing_entry = entry;
return sf::ResultRequestDeferredByUser();
}
void ProcessRegisterRetry(os::WaitableHolderType *session_holder) {
/* Verify that we have a processing entry. */
AMS_ABORT_UNLESS(g_processing_entry != nullptr);
/* Process the session. */
g_processing_entry->session = session_holder;
g_processing_entry = nullptr;
}
void TriggerResume(ServiceName service) {
/* Check that we haven't already triggered a resume. */
AMS_ABORT_UNLESS(g_triggered_service == InvalidServiceName);
/* Set the triggered resume. */
g_triggered_service = service;
}
void TestAndResume(ResumeFunction resume_function) {
/* If we don't have a triggered service, there's nothing to do. */
if (g_triggered_service == InvalidServiceName) {
return;
}
/* Get and clear the triggered service. */
const auto resumed_service = g_triggered_service;
g_triggered_service = InvalidServiceName;
/* Process all entries. */
for (size_t i = 0; i < util::size(g_entries); /* ... */) {
auto &entry = g_entries[i];
if (entry.service == resumed_service) {
/* Get the entry's session. */
auto * const session = entry.session;
/* Clear the entry. */
entry.service = InvalidServiceName;
entry.session = nullptr;
/* Resume the request. */
R_TRY_CATCH(resume_function(session)) {
R_CATCH(sf::ResultRequestDeferred) {
ProcessRegisterRetry(session);
}
} R_END_TRY_CATCH_WITH_ABORT_UNLESS;
/* Handle nested resumes. */
if (g_triggered_service != InvalidServiceName) {
AMS_ABORT_UNLESS(g_triggered_service == resumed_service);
g_triggered_service = InvalidServiceName;
i = 0;
continue;
}
}
/* Advance. */
++i;
}
}
}