summaryrefslogtreecommitdiffstats
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/arm/arm_interface.h9
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp9
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.h2
-rw-r--r--src/core/arm/dyncom/arm_dyncom.cpp8
-rw-r--r--src/core/arm/dyncom/arm_dyncom.h2
-rw-r--r--src/core/core_timing.cpp36
-rw-r--r--src/core/core_timing.h6
-rw-r--r--src/core/hle/kernel/thread.cpp13
-rw-r--r--src/core/hle/kernel/thread.h15
-rw-r--r--src/core/hle/kernel/wait_object.cpp11
-rw-r--r--src/core/hle/service/apt/apt.cpp39
-rw-r--r--src/core/hle/service/apt/apt.h10
-rw-r--r--src/core/hle/service/apt/apt_s.cpp4
-rw-r--r--src/core/hle/service/nim/nim.cpp18
-rw-r--r--src/core/hle/service/nim/nim.h11
-rw-r--r--src/core/hle/service/nim/nim_u.cpp2
-rw-r--r--src/core/hle/service/nwm/nwm_uds.cpp121
-rw-r--r--src/core/hle/service/nwm/uds_data.cpp80
-rw-r--r--src/core/hle/service/nwm/uds_data.h68
-rw-r--r--src/core/hle/svc.cpp71
20 files changed, 447 insertions, 88 deletions
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index 2aa017a54..ba528403c 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -125,12 +125,6 @@ public:
virtual void SetCP15Register(CP15Register reg, u32 value) = 0;
/**
- * Advance the CPU core by the specified number of ticks (e.g. to simulate CPU execution time)
- * @param ticks Number of ticks to advance the CPU core
- */
- virtual void AddTicks(u64 ticks) = 0;
-
- /**
* Saves the current CPU context
* @param ctx Thread context to save
*/
@@ -150,9 +144,6 @@ public:
return num_instructions;
}
- s64 down_count = 0; ///< A decreasing counter of remaining cycles before the next event,
- /// decreased by the cpu run loop
-
protected:
/**
* Executes the given number of instructions
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index 42ae93ae8..2cb56d12f 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -124,13 +124,6 @@ void ARM_Dynarmic::SetCP15Register(CP15Register reg, u32 value) {
interpreter_state->CP15[reg] = value;
}
-void ARM_Dynarmic::AddTicks(u64 ticks) {
- down_count -= ticks;
- if (down_count < 0) {
- CoreTiming::Advance();
- }
-}
-
MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64));
void ARM_Dynarmic::ExecuteInstructions(int num_instructions) {
@@ -139,7 +132,7 @@ void ARM_Dynarmic::ExecuteInstructions(int num_instructions) {
std::size_t ticks_executed = jit->Run(static_cast<unsigned>(num_instructions));
- AddTicks(ticks_executed);
+ CoreTiming::AddTicks(ticks_executed);
}
void ARM_Dynarmic::SaveContext(ARM_Interface::ThreadContext& ctx) {
diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h
index 96148a1a5..0b00158a5 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.h
+++ b/src/core/arm/dynarmic/arm_dynarmic.h
@@ -32,8 +32,6 @@ public:
u32 GetCP15Register(CP15Register reg) override;
void SetCP15Register(CP15Register reg, u32 value) override;
- void AddTicks(u64 ticks) override;
-
void SaveContext(ThreadContext& ctx) override;
void LoadContext(const ThreadContext& ctx) override;
diff --git a/src/core/arm/dyncom/arm_dyncom.cpp b/src/core/arm/dyncom/arm_dyncom.cpp
index da955c9b9..4d72aef77 100644
--- a/src/core/arm/dyncom/arm_dyncom.cpp
+++ b/src/core/arm/dyncom/arm_dyncom.cpp
@@ -77,12 +77,6 @@ void ARM_DynCom::SetCP15Register(CP15Register reg, u32 value) {
state->CP15[reg] = value;
}
-void ARM_DynCom::AddTicks(u64 ticks) {
- down_count -= ticks;
- if (down_count < 0)
- CoreTiming::Advance();
-}
-
void ARM_DynCom::ExecuteInstructions(int num_instructions) {
state->NumInstrsToExecute = num_instructions;
@@ -90,7 +84,7 @@ void ARM_DynCom::ExecuteInstructions(int num_instructions) {
// executing one instruction at a time. Otherwise, if a block is being executed, more
// instructions may actually be executed than specified.
unsigned ticks_executed = InterpreterMainLoop(state.get());
- AddTicks(ticks_executed);
+ CoreTiming::AddTicks(ticks_executed);
}
void ARM_DynCom::SaveContext(ThreadContext& ctx) {
diff --git a/src/core/arm/dyncom/arm_dyncom.h b/src/core/arm/dyncom/arm_dyncom.h
index 0ae535671..fc1ffed6a 100644
--- a/src/core/arm/dyncom/arm_dyncom.h
+++ b/src/core/arm/dyncom/arm_dyncom.h
@@ -31,8 +31,6 @@ public:
u32 GetCP15Register(CP15Register reg) override;
void SetCP15Register(CP15Register reg, u32 value) override;
- void AddTicks(u64 ticks) override;
-
void SaveContext(ThreadContext& ctx) override;
void LoadContext(const ThreadContext& ctx) override;
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index 276ecfdf6..5e2a5d00f 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -57,6 +57,9 @@ static s64 idled_cycles;
static s64 last_global_time_ticks;
static s64 last_global_time_us;
+static s64 down_count = 0; ///< A decreasing counter of remaining cycles before the next event,
+ /// decreased by the cpu run loop
+
static std::recursive_mutex external_event_section;
// Warning: not included in save state.
@@ -146,7 +149,7 @@ void UnregisterAllEvents() {
}
void Init() {
- Core::CPU().down_count = INITIAL_SLICE_LENGTH;
+ down_count = INITIAL_SLICE_LENGTH;
g_slice_length = INITIAL_SLICE_LENGTH;
global_timer = 0;
idled_cycles = 0;
@@ -185,8 +188,15 @@ void Shutdown() {
}
}
+void AddTicks(u64 ticks) {
+ down_count -= ticks;
+ if (down_count < 0) {
+ Advance();
+ }
+}
+
u64 GetTicks() {
- return (u64)global_timer + g_slice_length - Core::CPU().down_count;
+ return (u64)global_timer + g_slice_length - down_count;
}
u64 GetIdleTicks() {
@@ -460,18 +470,18 @@ void MoveEvents() {
}
void ForceCheck() {
- s64 cycles_executed = g_slice_length - Core::CPU().down_count;
+ s64 cycles_executed = g_slice_length - down_count;
global_timer += cycles_executed;
// This will cause us to check for new events immediately.
- Core::CPU().down_count = 0;
+ down_count = 0;
// But let's not eat a bunch more time in Advance() because of this.
g_slice_length = 0;
}
void Advance() {
- s64 cycles_executed = g_slice_length - Core::CPU().down_count;
+ s64 cycles_executed = g_slice_length - down_count;
global_timer += cycles_executed;
- Core::CPU().down_count = g_slice_length;
+ down_count = g_slice_length;
if (has_ts_events)
MoveEvents();
@@ -480,7 +490,7 @@ void Advance() {
if (!first) {
if (g_slice_length < 10000) {
g_slice_length += 10000;
- Core::CPU().down_count += g_slice_length;
+ down_count += g_slice_length;
}
} else {
// Note that events can eat cycles as well.
@@ -490,7 +500,7 @@ void Advance() {
const int diff = target - g_slice_length;
g_slice_length += diff;
- Core::CPU().down_count += diff;
+ down_count += diff;
}
if (advance_callback)
advance_callback(static_cast<int>(cycles_executed));
@@ -506,12 +516,12 @@ void LogPendingEvents() {
}
void Idle(int max_idle) {
- s64 cycles_down = Core::CPU().down_count;
+ s64 cycles_down = down_count;
if (max_idle != 0 && cycles_down > max_idle)
cycles_down = max_idle;
if (first && cycles_down > 0) {
- s64 cycles_executed = g_slice_length - Core::CPU().down_count;
+ s64 cycles_executed = g_slice_length - down_count;
s64 cycles_next_event = first->time - global_timer;
if (cycles_next_event < cycles_executed + cycles_down) {
@@ -526,9 +536,9 @@ void Idle(int max_idle) {
cycles_down / (float)(g_clock_rate_arm11 * 0.001f));
idled_cycles += cycles_down;
- Core::CPU().down_count -= cycles_down;
- if (Core::CPU().down_count == 0)
- Core::CPU().down_count = -1;
+ down_count -= cycles_down;
+ if (down_count == 0)
+ down_count = -1;
}
std::string GetScheduledEventsSummary() {
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index d2f85cd4d..897350801 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -67,6 +67,12 @@ void Shutdown();
typedef void (*MHzChangeCallback)();
typedef std::function<void(u64 userdata, int cycles_late)> TimedCallback;
+/**
+* Advance the CPU core by the specified number of ticks (e.g. to simulate CPU execution time)
+* @param ticks Number of ticks to advance the CPU core
+*/
+void AddTicks(u64 ticks);
+
u64 GetTicks();
u64 GetIdleTicks();
u64 GetGlobalTimeUs();
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 6ebc8c151..0f7970ebe 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -247,12 +247,15 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
if (thread->status == THREADSTATUS_WAIT_SYNCH_ANY ||
thread->status == THREADSTATUS_WAIT_SYNCH_ALL || thread->status == THREADSTATUS_WAIT_ARB) {
- thread->wait_set_output = false;
+
+ // Invoke the wakeup callback before clearing the wait objects
+ if (thread->wakeup_callback)
+ thread->wakeup_callback(ThreadWakeupReason::Timeout, thread, nullptr);
+
// Remove the thread from each of its waiting objects' waitlists
for (auto& object : thread->wait_objects)
object->RemoveWaitingThread(thread.get());
thread->wait_objects.clear();
- thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
}
thread->ResumeFromWait();
@@ -278,6 +281,9 @@ void Thread::ResumeFromWait() {
break;
case THREADSTATUS_READY:
+ // The thread's wakeup callback must have already been cleared when the thread was first
+ // awoken.
+ ASSERT(wakeup_callback == nullptr);
// If the thread is waiting on multiple wait objects, it might be awoken more than once
// before actually resuming. We can ignore subsequent wakeups if the thread status has
// already been set to THREADSTATUS_READY.
@@ -293,6 +299,8 @@ void Thread::ResumeFromWait() {
return;
}
+ wakeup_callback = nullptr;
+
ready_queue.push_back(current_priority, this);
status = THREADSTATUS_READY;
Core::System::GetInstance().PrepareReschedule();
@@ -395,7 +403,6 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
thread->nominal_priority = thread->current_priority = priority;
thread->last_running_ticks = CoreTiming::GetTicks();
thread->processor_id = processor_id;
- thread->wait_set_output = false;
thread->wait_objects.clear();
thread->wait_address = 0;
thread->name = std::move(name);
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index 520ac22c2..314fba81f 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -41,6 +41,11 @@ enum ThreadStatus {
THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated
};
+enum class ThreadWakeupReason {
+ Signal, // The thread was woken up by WakeupAllWaitingThreads due to an object signal.
+ Timeout // The thread was woken up due to a wait timeout.
+};
+
namespace Kernel {
class Mutex;
@@ -205,14 +210,18 @@ public:
VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address
- /// True if the WaitSynchronizationN output parameter should be set on thread wakeup.
- bool wait_set_output;
-
std::string name;
/// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
Handle callback_handle;
+ using WakeupCallback = void(ThreadWakeupReason reason, SharedPtr<Thread> thread,
+ SharedPtr<WaitObject> object);
+ // Callback that will be invoked when the thread is resumed from a waiting state. If the thread
+ // was waiting via WaitSynchronizationN then the object will be the last object that became
+ // available. In case of a timeout, the object will be nullptr.
+ std::function<WakeupCallback> wakeup_callback;
+
private:
Thread();
~Thread() override;
diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp
index 56fdd977f..469554908 100644
--- a/src/core/hle/kernel/wait_object.cpp
+++ b/src/core/hle/kernel/wait_object.cpp
@@ -71,23 +71,20 @@ void WaitObject::WakeupAllWaitingThreads() {
while (auto thread = GetHighestPriorityReadyThread()) {
if (!thread->IsSleepingOnWaitAll()) {
Acquire(thread.get());
- // Set the output index of the WaitSynchronizationN call to the index of this object.
- if (thread->wait_set_output) {
- thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(this));
- thread->wait_set_output = false;
- }
} else {
for (auto& object : thread->wait_objects) {
object->Acquire(thread.get());
}
- // Note: This case doesn't update the output index of WaitSynchronizationN.
}
+ // Invoke the wakeup callback before clearing the wait objects
+ if (thread->wakeup_callback)
+ thread->wakeup_callback(ThreadWakeupReason::Signal, thread, this);
+
for (auto& object : thread->wait_objects)
object->RemoveWaitingThread(thread.get());
thread->wait_objects.clear();
- thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
thread->ResumeFromWait();
}
}
diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp
index 4c6156345..2f7362748 100644
--- a/src/core/hle/service/apt/apt.cpp
+++ b/src/core/hle/service/apt/apt.cpp
@@ -65,6 +65,7 @@ union AppletAttributes {
u32 raw;
BitField<0, 3, u32> applet_pos;
+ BitField<29, 1, u32> is_home_menu;
AppletAttributes() : raw(0) {}
AppletAttributes(u32 attributes) : raw(attributes) {}
@@ -158,6 +159,11 @@ static AppletSlotData* GetAppletSlotData(AppletAttributes attributes) {
if (slot == AppletSlot::Error)
return nullptr;
+ // The Home Menu is a system applet, however, it has its own applet slot so that it can run
+ // concurrently with other system applets.
+ if (slot == AppletSlot::SystemApplet && attributes.is_home_menu)
+ return &applet_slots[static_cast<size_t>(AppletSlot::HomeMenu)];
+
return &applet_slots[static_cast<size_t>(slot)];
}
@@ -197,6 +203,19 @@ void Initialize(Service::Interface* self) {
rb.Push(RESULT_SUCCESS);
rb.PushCopyHandles(Kernel::g_handle_table.Create(slot_data->notification_event).Unwrap(),
Kernel::g_handle_table.Create(slot_data->parameter_event).Unwrap());
+
+ if (slot_data->applet_id == AppletId::Application ||
+ slot_data->applet_id == AppletId::HomeMenu) {
+ // Initialize the APT parameter to wake up the application.
+ next_parameter.emplace();
+ next_parameter->signal = static_cast<u32>(SignalType::Wakeup);
+ next_parameter->sender_id = static_cast<u32>(AppletId::None);
+ next_parameter->destination_id = app_id;
+ // Not signaling the parameter event will cause the application (or Home Menu) to hang
+ // during startup. In the real console, it is usually the Kernel and Home Menu who cause NS
+ // to signal the HomeMenu and Application parameter events, respectively.
+ slot_data->parameter_event->Signal();
+ }
}
static u32 DecompressLZ11(const u8* in, u8* out) {
@@ -757,6 +776,20 @@ void PrepareToStartLibraryApplet(Service::Interface* self) {
LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id);
}
+void PrepareToStartNewestHomeMenu(Service::Interface* self) {
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1A, 0, 0); // 0x1A0000
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+
+ // TODO(Subv): This command can only be called by a System Applet (return 0xC8A0CC04 otherwise).
+
+ // This command must return an error when called, otherwise the Home Menu will try to reboot the
+ // system.
+ rb.Push(ResultCode(ErrorDescription::AlreadyExists, ErrorModule::Applet,
+ ErrorSummary::InvalidState, ErrorLevel::Status));
+
+ LOG_DEBUG(Service_APT, "called");
+}
+
void PreloadLibraryApplet(Service::Interface* self) {
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x16, 1, 0); // 0x160040
AppletId applet_id = static_cast<AppletId>(rp.Pop<u32>());
@@ -1041,12 +1074,6 @@ void Init() {
slot_data.parameter_event =
Kernel::Event::Create(Kernel::ResetType::OneShot, "APT:Parameter");
}
-
- // Initialize the parameter to wake up the application.
- next_parameter.emplace();
- next_parameter->signal = static_cast<u32>(SignalType::Wakeup);
- next_parameter->destination_id = static_cast<u32>(AppletId::Application);
- applet_slots[static_cast<size_t>(AppletSlot::Application)].parameter_event->Signal();
}
void Shutdown() {
diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h
index 96b28b438..7b79e1f3e 100644
--- a/src/core/hle/service/apt/apt.h
+++ b/src/core/hle/service/apt/apt.h
@@ -420,6 +420,16 @@ void GetAppCpuTimeLimit(Service::Interface* self);
void PrepareToStartLibraryApplet(Service::Interface* self);
/**
+ * APT::PrepareToStartNewestHomeMenu service function
+ * Inputs:
+ * 0 : Command header [0x001A0000]
+ * Outputs:
+ * 0 : Return header
+ * 1 : Result of function
+ */
+void PrepareToStartNewestHomeMenu(Service::Interface* self);
+
+/**
* APT::PreloadLibraryApplet service function
* Inputs:
* 0 : Command header [0x00160040]
diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp
index ec5668d05..fe1d21fff 100644
--- a/src/core/hle/service/apt/apt_s.cpp
+++ b/src/core/hle/service/apt/apt_s.cpp
@@ -17,7 +17,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00060040, GetAppletInfo, "GetAppletInfo"},
{0x00070000, nullptr, "GetLastSignaledAppletId"},
{0x00080000, nullptr, "CountRegisteredApplet"},
- {0x00090040, nullptr, "IsRegistered"},
+ {0x00090040, IsRegistered, "IsRegistered"},
{0x000A0040, nullptr, "GetAttribute"},
{0x000B0040, InquireNotification, "InquireNotification"},
{0x000C0104, nullptr, "SendParameter"},
@@ -34,7 +34,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00170040, nullptr, "FinishPreloadingLibraryApplet"},
{0x00180040, PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"},
{0x00190040, nullptr, "PrepareToStartSystemApplet"},
- {0x001A0000, nullptr, "PrepareToStartNewestHomeMenu"},
+ {0x001A0000, PrepareToStartNewestHomeMenu, "PrepareToStartNewestHomeMenu"},
{0x001B00C4, nullptr, "StartApplication"},
{0x001C0000, nullptr, "WakeupApplication"},
{0x001D0000, nullptr, "CancelApplication"},
diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp
index d5624fe54..b10d5852b 100644
--- a/src/core/hle/service/nim/nim.cpp
+++ b/src/core/hle/service/nim/nim.cpp
@@ -5,6 +5,8 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/hle/ipc.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/event.h"
#include "core/hle/service/nim/nim.h"
#include "core/hle/service/nim/nim_aoc.h"
#include "core/hle/service/nim/nim_s.h"
@@ -14,6 +16,16 @@
namespace Service {
namespace NIM {
+static Kernel::SharedPtr<Kernel::Event> nim_system_update_event;
+
+void CheckForSysUpdateEvent(Service::Interface* self) {
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x5, 0, 0); // 0x50000
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(nim_system_update_event).Unwrap());
+ LOG_TRACE(Service_NIM, "called");
+}
+
void CheckSysUpdateAvailable(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
@@ -29,9 +41,13 @@ void Init() {
AddService(new NIM_AOC_Interface);
AddService(new NIM_S_Interface);
AddService(new NIM_U_Interface);
+
+ nim_system_update_event = Kernel::Event::Create(ResetType::OneShot, "NIM System Update Event");
}
-void Shutdown() {}
+void Shutdown() {
+ nim_system_update_event = nullptr;
+}
} // namespace NIM
diff --git a/src/core/hle/service/nim/nim.h b/src/core/hle/service/nim/nim.h
index c3106f18b..dbf605e5a 100644
--- a/src/core/hle/service/nim/nim.h
+++ b/src/core/hle/service/nim/nim.h
@@ -11,6 +11,17 @@ class Interface;
namespace NIM {
/**
+ * NIM::CheckForSysUpdateEvent service function
+ * Inputs:
+ * 1 : None
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : Copy handle descriptor
+ * 3 : System Update event handle
+ */
+void CheckForSysUpdateEvent(Service::Interface* self);
+
+/**
* NIM::CheckSysUpdateAvailable service function
* Inputs:
* 1 : None
diff --git a/src/core/hle/service/nim/nim_u.cpp b/src/core/hle/service/nim/nim_u.cpp
index 7664bad60..569660278 100644
--- a/src/core/hle/service/nim/nim_u.cpp
+++ b/src/core/hle/service/nim/nim_u.cpp
@@ -12,7 +12,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00010000, nullptr, "StartSysUpdate"},
{0x00020000, nullptr, "GetUpdateDownloadProgress"},
{0x00040000, nullptr, "FinishTitlesInstall"},
- {0x00050000, nullptr, "CheckForSysUpdateEvent"},
+ {0x00050000, CheckForSysUpdateEvent, "CheckForSysUpdateEvent"},
{0x00090000, CheckSysUpdateAvailable, "CheckSysUpdateAvailable"},
{0x000A0000, nullptr, "GetState"},
{0x000B0000, nullptr, "GetSystemTitleHash"},
diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp
index 8ef0cda09..0aa63cc1e 100644
--- a/src/core/hle/service/nwm/nwm_uds.cpp
+++ b/src/core/hle/service/nwm/nwm_uds.cpp
@@ -15,6 +15,7 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/shared_memory.h"
+#include "core/hle/lock.h"
#include "core/hle/result.h"
#include "core/hle/service/nwm/nwm_uds.h"
#include "core/hle/service/nwm/uds_beacon.h"
@@ -100,6 +101,20 @@ void SendPacket(Network::WifiPacket& packet) {
// TODO(Subv): Implement.
}
+/*
+ * Returns an available index in the nodes array for the
+ * currently-hosted UDS network.
+ */
+static u16 GetNextAvailableNodeId() {
+ for (u16 index = 0; index < connection_status.max_nodes; ++index) {
+ if ((connection_status.node_bitmask & (1 << index)) == 0)
+ return index;
+ }
+
+ // Any connection attempts to an already full network should have been refused.
+ ASSERT_MSG(false, "No available connection slots in the network");
+}
+
// Inserts the received beacon frame in the beacon queue and removes any older beacons if the size
// limit is exceeded.
void HandleBeaconFrame(const Network::WifiPacket& packet) {
@@ -143,18 +158,88 @@ void HandleAssociationResponseFrame(const Network::WifiPacket& packet) {
SendPacket(eapol_start);
}
-/*
- * Returns an available index in the nodes array for the
- * currently-hosted UDS network.
- */
-static u16 GetNextAvailableNodeId() {
- for (u16 index = 0; index < connection_status.max_nodes; ++index) {
- if ((connection_status.node_bitmask & (1 << index)) == 0)
- return index;
- }
+static void HandleEAPoLPacket(const Network::WifiPacket& packet) {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
- // Any connection attempts to an already full network should have been refused.
- ASSERT_MSG(false, "No available connection slots in the network");
+ if (GetEAPoLFrameType(packet.data) == EAPoLStartMagic) {
+ if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) {
+ LOG_DEBUG(Service_NWM, "Connection sequence aborted, because connection status is %u",
+ connection_status.status);
+ return;
+ }
+
+ auto node = DeserializeNodeInfoFromFrame(packet.data);
+
+ if (connection_status.max_nodes == connection_status.total_nodes) {
+ // Reject connection attempt
+ LOG_ERROR(Service_NWM, "Reached maximum nodes, but reject packet wasn't sent.");
+ // TODO(B3N30): Figure out what packet is sent here
+ return;
+ }
+
+ // Get an unused network node id
+ u16 node_id = GetNextAvailableNodeId();
+ node.network_node_id = node_id + 1;
+
+ connection_status.node_bitmask |= 1 << node_id;
+ connection_status.changed_nodes |= 1 << node_id;
+ connection_status.nodes[node_id] = node.network_node_id;
+ connection_status.total_nodes++;
+
+ u8 current_nodes = network_info.total_nodes;
+ node_info[current_nodes] = node;
+
+ network_info.total_nodes++;
+
+ // Send the EAPoL-Logoff packet.
+ using Network::WifiPacket;
+ WifiPacket eapol_logoff;
+ eapol_logoff.channel = network_channel;
+ eapol_logoff.data =
+ GenerateEAPoLLogoffFrame(packet.transmitter_address, node.network_node_id, node_info,
+ network_info.max_nodes, network_info.total_nodes);
+ // TODO(Subv): Encrypt the packet.
+ eapol_logoff.destination_address = packet.transmitter_address;
+ eapol_logoff.type = WifiPacket::PacketType::Data;
+
+ SendPacket(eapol_logoff);
+ // TODO(B3N30): Broadcast updated node list
+ // The 3ds does this presumably to support spectators.
+ std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ connection_status_event->Signal();
+ } else {
+ if (connection_status.status != static_cast<u32>(NetworkStatus::NotConnected)) {
+ LOG_DEBUG(Service_NWM, "Connection sequence aborted, because connection status is %u",
+ connection_status.status);
+ return;
+ }
+ auto logoff = ParseEAPoLLogoffFrame(packet.data);
+
+ network_info.total_nodes = logoff.connected_nodes;
+ network_info.max_nodes = logoff.max_nodes;
+
+ connection_status.network_node_id = logoff.assigned_node_id;
+ connection_status.total_nodes = logoff.connected_nodes;
+ connection_status.max_nodes = logoff.max_nodes;
+
+ node_info.clear();
+ node_info.reserve(network_info.max_nodes);
+ for (size_t index = 0; index < logoff.connected_nodes; ++index) {
+ connection_status.node_bitmask |= 1 << index;
+ connection_status.changed_nodes |= 1 << index;
+ connection_status.nodes[index] = logoff.nodes[index].network_node_id;
+
+ node_info.emplace_back(DeserializeNodeInfo(logoff.nodes[index]));
+ }
+
+ // We're now connected, signal the application
+ connection_status.status = static_cast<u32>(NetworkStatus::ConnectedAsClient);
+ // Some games require ConnectToNetwork to block, for now it doesn't
+ // If blocking is implemented this lock needs to be changed,
+ // otherwise it might cause deadlocks
+ std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ connection_status_event->Signal();
+ }
}
/*
@@ -238,6 +323,17 @@ void HandleAuthenticationFrame(const Network::WifiPacket& packet) {
}
}
+static void HandleDataFrame(const Network::WifiPacket& packet) {
+ switch (GetFrameEtherType(packet.data)) {
+ case EtherType::EAPoL:
+ HandleEAPoLPacket(packet);
+ break;
+ case EtherType::SecureData:
+ // TODO(B3N30): Handle SecureData packets
+ break;
+ }
+}
+
/// Callback to parse and handle a received wifi packet.
void OnWifiPacketReceived(const Network::WifiPacket& packet) {
switch (packet.type) {
@@ -250,6 +346,9 @@ void OnWifiPacketReceived(const Network::WifiPacket& packet) {
case Network::WifiPacket::PacketType::AssociationResponse:
HandleAssociationResponseFrame(packet);
break;
+ case Network::WifiPacket::PacketType::Data:
+ HandleDataFrame(packet);
+ break;
}
}
diff --git a/src/core/hle/service/nwm/uds_data.cpp b/src/core/hle/service/nwm/uds_data.cpp
index 3ef2a84b6..4b389710f 100644
--- a/src/core/hle/service/nwm/uds_data.cpp
+++ b/src/core/hle/service/nwm/uds_data.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <cstring>
#include <cryptopp/aes.h>
#include <cryptopp/ccm.h>
@@ -277,10 +278,10 @@ std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16
std::vector<u8> GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info) {
EAPoLStartPacket eapol_start{};
eapol_start.association_id = association_id;
- eapol_start.friend_code_seed = node_info.friend_code_seed;
+ eapol_start.node.friend_code_seed = node_info.friend_code_seed;
- for (int i = 0; i < node_info.username.size(); ++i)
- eapol_start.username[i] = node_info.username[i];
+ std::copy(node_info.username.begin(), node_info.username.end(),
+ eapol_start.node.username.begin());
// Note: The network_node_id and unknown bytes seem to be uninitialized in the NWM module.
// TODO(B3N30): The last 8 bytes seem to have a fixed value of 07 88 15 00 04 e9 13 00 in
@@ -295,5 +296,78 @@ std::vector<u8> GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node
return buffer;
}
+EtherType GetFrameEtherType(const std::vector<u8>& frame) {
+ LLCHeader header;
+ std::memcpy(&header, frame.data(), sizeof(header));
+
+ u16 ethertype = header.protocol;
+ return static_cast<EtherType>(ethertype);
+}
+
+u16 GetEAPoLFrameType(const std::vector<u8>& frame) {
+ // Ignore the LLC header
+ u16_be eapol_type;
+ std::memcpy(&eapol_type, frame.data() + sizeof(LLCHeader), sizeof(eapol_type));
+ return eapol_type;
+}
+
+NodeInfo DeserializeNodeInfoFromFrame(const std::vector<u8>& frame) {
+ EAPoLStartPacket eapol_start;
+
+ // Skip the LLC header
+ std::memcpy(&eapol_start, frame.data() + sizeof(LLCHeader), sizeof(eapol_start));
+
+ NodeInfo node{};
+ node.friend_code_seed = eapol_start.node.friend_code_seed;
+
+ std::copy(eapol_start.node.username.begin(), eapol_start.node.username.end(),
+ node.username.begin());
+
+ return node;
+}
+
+NodeInfo DeserializeNodeInfo(const EAPoLNodeInfo& node) {
+ NodeInfo node_info{};
+ node_info.friend_code_seed = node.friend_code_seed;
+ node_info.network_node_id = node.network_node_id;
+
+ std::copy(node.username.begin(), node.username.end(), node_info.username.begin());
+
+ return node_info;
+}
+
+std::vector<u8> GenerateEAPoLLogoffFrame(const MacAddress& mac_address, u16 network_node_id,
+ const NodeList& nodes, u8 max_nodes, u8 total_nodes) {
+ EAPoLLogoffPacket eapol_logoff{};
+ eapol_logoff.assigned_node_id = network_node_id;
+ eapol_logoff.connected_nodes = total_nodes;
+ eapol_logoff.max_nodes = max_nodes;
+
+ for (size_t index = 0; index < total_nodes; ++index) {
+ const auto& node_info = nodes[index];
+ auto& node = eapol_logoff.nodes[index];
+
+ node.friend_code_seed = node_info.friend_code_seed;
+ node.network_node_id = node_info.network_node_id;
+
+ std::copy(node_info.username.begin(), node_info.username.end(), node.username.begin());
+ }
+
+ std::vector<u8> eapol_buffer(sizeof(EAPoLLogoffPacket));
+ std::memcpy(eapol_buffer.data(), &eapol_logoff, sizeof(eapol_logoff));
+
+ std::vector<u8> buffer = GenerateLLCHeader(EtherType::EAPoL);
+ buffer.insert(buffer.end(), eapol_buffer.begin(), eapol_buffer.end());
+ return buffer;
+}
+
+EAPoLLogoffPacket ParseEAPoLLogoffFrame(const std::vector<u8>& frame) {
+ EAPoLLogoffPacket eapol_logoff;
+
+ // Skip the LLC header
+ std::memcpy(&eapol_logoff, frame.data() + sizeof(LLCHeader), sizeof(eapol_logoff));
+ return eapol_logoff;
+}
+
} // namespace NWM
} // namespace Service
diff --git a/src/core/hle/service/nwm/uds_data.h b/src/core/hle/service/nwm/uds_data.h
index 76e8f546b..76bccb1bf 100644
--- a/src/core/hle/service/nwm/uds_data.h
+++ b/src/core/hle/service/nwm/uds_data.h
@@ -8,6 +8,7 @@
#include <vector>
#include "common/common_types.h"
#include "common/swap.h"
+#include "core/hle/service/nwm/uds_beacon.h"
#include "core/hle/service/service.h"
namespace Service {
@@ -67,6 +68,16 @@ struct DataFrameCryptoCTR {
static_assert(sizeof(DataFrameCryptoCTR) == 16, "DataFrameCryptoCTR has the wrong size");
+struct EAPoLNodeInfo {
+ u64_be friend_code_seed;
+ std::array<u16_be, 10> username;
+ INSERT_PADDING_BYTES(4);
+ u16_be network_node_id;
+ INSERT_PADDING_BYTES(6);
+};
+
+static_assert(sizeof(EAPoLNodeInfo) == 0x28, "EAPoLNodeInfo has the wrong size");
+
constexpr u16 EAPoLStartMagic = 0x201;
/*
@@ -78,15 +89,27 @@ struct EAPoLStartPacket {
// This value is hardcoded to 1 in the NWM module.
u16_be unknown = 1;
INSERT_PADDING_BYTES(2);
+ EAPoLNodeInfo node;
+};
- u64_be friend_code_seed;
- std::array<u16_be, 10> username;
- INSERT_PADDING_BYTES(4);
- u16_be network_node_id;
+static_assert(sizeof(EAPoLStartPacket) == 0x30, "EAPoLStartPacket has the wrong size");
+
+constexpr u16 EAPoLLogoffMagic = 0x202;
+
+struct EAPoLLogoffPacket {
+ u16_be magic = EAPoLLogoffMagic;
+ INSERT_PADDING_BYTES(2);
+ u16_be assigned_node_id;
+ MacAddress client_mac_address;
INSERT_PADDING_BYTES(6);
+ u8 connected_nodes;
+ u8 max_nodes;
+ INSERT_PADDING_BYTES(4);
+
+ std::array<EAPoLNodeInfo, UDSMaxNodes> nodes;
};
-static_assert(sizeof(EAPoLStartPacket) == 0x30, "EAPoLStartPacket has the wrong size");
+static_assert(sizeof(EAPoLLogoffPacket) == 0x298, "EAPoLLogoffPacket has the wrong size");
/**
* Generates an unencrypted 802.11 data payload.
@@ -102,5 +125,40 @@ std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16
*/
std::vector<u8> GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info);
+/*
+ * Returns the EtherType of the specified 802.11 frame.
+ */
+EtherType GetFrameEtherType(const std::vector<u8>& frame);
+
+/*
+ * Returns the EAPoL type (Start / Logoff) of the specified 802.11 frame.
+ * Note: The frame *must* be an EAPoL frame.
+ */
+u16 GetEAPoLFrameType(const std::vector<u8>& frame);
+
+/*
+ * Returns a deserialized NodeInfo structure from the information inside an EAPoL-Start packet
+ * encapsulated in an 802.11 data frame.
+ */
+NodeInfo DeserializeNodeInfoFromFrame(const std::vector<u8>& frame);
+
+/*
+ * Returns a NodeInfo constructed from the data in the specified EAPoLNodeInfo.
+ */
+NodeInfo DeserializeNodeInfo(const EAPoLNodeInfo& node);
+
+/*
+ * Generates an unencrypted 802.11 data frame body with the EAPoL-Logoff format for UDS
+ * communication.
+ * @returns The generated frame body.
+ */
+std::vector<u8> GenerateEAPoLLogoffFrame(const MacAddress& mac_address, u16 network_node_id,
+ const NodeList& nodes, u8 max_nodes, u8 total_nodes);
+
+/*
+ * Returns a EAPoLLogoffPacket representing the specified 802.11-encapsulated data frame.
+ */
+EAPoLLogoffPacket ParseEAPoLLogoffFrame(const std::vector<u8>& frame);
+
} // namespace NWM
} // namespace Service
diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp
index 41c82c922..6be5db13f 100644
--- a/src/core/hle/svc.cpp
+++ b/src/core/hle/svc.cpp
@@ -271,6 +271,24 @@ static ResultCode WaitSynchronization1(Kernel::Handle handle, s64 nano_seconds)
// Create an event to wake the thread up after the specified nanosecond delay has passed
thread->WakeAfterDelay(nano_seconds);
+ thread->wakeup_callback = [](ThreadWakeupReason reason,
+ Kernel::SharedPtr<Kernel::Thread> thread,
+ Kernel::SharedPtr<Kernel::WaitObject> object) {
+
+ ASSERT(thread->status == THREADSTATUS_WAIT_SYNCH_ANY);
+
+ if (reason == ThreadWakeupReason::Timeout) {
+ thread->SetWaitSynchronizationResult(Kernel::RESULT_TIMEOUT);
+ return;
+ }
+
+ ASSERT(reason == ThreadWakeupReason::Signal);
+ thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
+
+ // WaitSynchronization1 doesn't have an output index like WaitSynchronizationN, so we
+ // don't have to do anything else here.
+ };
+
Core::System::GetInstance().PrepareReschedule();
// Note: The output of this SVC will be set to RESULT_SUCCESS if the thread
@@ -344,6 +362,23 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha
// Create an event to wake the thread up after the specified nanosecond delay has passed
thread->WakeAfterDelay(nano_seconds);
+ thread->wakeup_callback = [](ThreadWakeupReason reason,
+ Kernel::SharedPtr<Kernel::Thread> thread,
+ Kernel::SharedPtr<Kernel::WaitObject> object) {
+
+ ASSERT(thread->status == THREADSTATUS_WAIT_SYNCH_ALL);
+
+ if (reason == ThreadWakeupReason::Timeout) {
+ thread->SetWaitSynchronizationResult(Kernel::RESULT_TIMEOUT);
+ return;
+ }
+
+ ASSERT(reason == ThreadWakeupReason::Signal);
+
+ thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
+ // The wait_all case does not update the output index.
+ };
+
Core::System::GetInstance().PrepareReschedule();
// This value gets set to -1 by default in this case, it is not modified after this.
@@ -389,12 +424,28 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha
// Create an event to wake the thread up after the specified nanosecond delay has passed
thread->WakeAfterDelay(nano_seconds);
+ thread->wakeup_callback = [](ThreadWakeupReason reason,
+ Kernel::SharedPtr<Kernel::Thread> thread,
+ Kernel::SharedPtr<Kernel::WaitObject> object) {
+
+ ASSERT(thread->status == THREADSTATUS_WAIT_SYNCH_ANY);
+
+ if (reason == ThreadWakeupReason::Timeout) {
+ thread->SetWaitSynchronizationResult(Kernel::RESULT_TIMEOUT);
+ return;
+ }
+
+ ASSERT(reason == ThreadWakeupReason::Signal);
+
+ thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
+ thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(object.get()));
+ };
+
Core::System::GetInstance().PrepareReschedule();
// Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a
// signal in one of its wait objects.
// Otherwise we retain the default value of timeout, and -1 in the out parameter
- thread->wait_set_output = true;
*out = -1;
return Kernel::RESULT_TIMEOUT;
}
@@ -483,8 +534,6 @@ static ResultCode ReplyAndReceive(s32* index, Kernel::Handle* handles, s32 handl
// No objects were ready to be acquired, prepare to suspend the thread.
- // TODO(Subv): Perform IPC translation upon wakeup.
-
// Put the thread to sleep
thread->status = THREADSTATUS_WAIT_SYNCH_ANY;
@@ -496,12 +545,24 @@ static ResultCode ReplyAndReceive(s32* index, Kernel::Handle* handles, s32 handl
thread->wait_objects = std::move(objects);
+ thread->wakeup_callback = [](ThreadWakeupReason reason,
+ Kernel::SharedPtr<Kernel::Thread> thread,
+ Kernel::SharedPtr<Kernel::WaitObject> object) {
+
+ ASSERT(thread->status == THREADSTATUS_WAIT_SYNCH_ANY);
+ ASSERT(reason == ThreadWakeupReason::Signal);
+
+ thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
+ thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(object.get()));
+
+ // TODO(Subv): Perform IPC translation upon wakeup.
+ };
+
Core::System::GetInstance().PrepareReschedule();
// Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a
// signal in one of its wait objects, or to 0xC8A01836 if there was a translation error.
// By default the index is set to -1.
- thread->wait_set_output = true;
*index = -1;
return RESULT_SUCCESS;
}
@@ -978,7 +1039,7 @@ static void SleepThread(s64 nanoseconds) {
static s64 GetSystemTick() {
s64 result = CoreTiming::GetTicks();
// Advance time to defeat dumb games (like Cubic Ninja) that busy-wait for the frame to end.
- Core::CPU().AddTicks(150); // Measured time between two calls on a 9.2 o3DS with Ninjhax 1.1b
+ CoreTiming::AddTicks(150); // Measured time between two calls on a 9.2 o3DS with Ninjhax 1.1b
return result;
}