diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp
index 60537f355..c42003e9d 100644
--- a/src/core/hle/kernel/timer.cpp
+++ b/src/core/hle/kernel/timer.cpp
@@ -52,9 +52,14 @@ void Timer::Set(s64 initial, s64 interval) {
     initial_delay = initial;
     interval_delay = interval;
 
-    u64 initial_microseconds = initial / 1000;
-    CoreTiming::ScheduleEvent(usToCycles(initial_microseconds), timer_callback_event_type,
-                              callback_handle);
+    if (initial == 0) {
+        // Immediately invoke the callback
+        Signal(0);
+    } else {
+        u64 initial_microseconds = initial / 1000;
+        CoreTiming::ScheduleEvent(usToCycles(initial_microseconds), timer_callback_event_type,
+                                  callback_handle);
+    }
 }
 
 void Timer::Cancel() {
@@ -72,6 +77,20 @@ void Timer::WakeupAllWaitingThreads() {
         signaled = false;
 }
 
+void Timer::Signal(int cycles_late) {
+    LOG_TRACE(Kernel, "Timer %08" PRIx64 " fired", timer_handle);
+
+    // Resume all waiting threads
+    WakeupAllWaitingThreads();
+
+    if (interval_delay != 0) {
+        // Reschedule the timer with the interval delay
+        u64 interval_microseconds = interval_delay / 1000;
+        CoreTiming::ScheduleEvent(usToCycles(interval_microseconds) - cycles_late,
+                                  timer_callback_event_type, callback_handle);
+    }
+}
+
 /// The timer callback event, called when a timer is fired
 static void TimerCallback(u64 timer_handle, int cycles_late) {
     SharedPtr<Timer> timer =
@@ -82,19 +101,7 @@ static void TimerCallback(u64 timer_handle, int cycles_late) {
         return;
     }
 
-    LOG_TRACE(Kernel, "Timer %08" PRIx64 " fired", timer_handle);
-
-    timer->signaled = true;
-
-    // Resume all waiting threads
-    timer->WakeupAllWaitingThreads();
-
-    if (timer->interval_delay != 0) {
-        // Reschedule the timer with the interval delay
-        u64 interval_microseconds = timer->interval_delay / 1000;
-        CoreTiming::ScheduleEvent(usToCycles(interval_microseconds) - cycles_late,
-                                  timer_callback_event_type, timer_handle);
-    }
+    timer->Signal(cycles_late);
 }
 
 void TimersInit() {
diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h
index c174f5664..b0f818933 100644
--- a/src/core/hle/kernel/timer.h
+++ b/src/core/hle/kernel/timer.h
@@ -54,6 +54,14 @@ public:
     void Cancel();
     void Clear();
 
+    /**
+     * Signals the timer, waking up any waiting threads and rescheduling it
+     * for the next interval.
+     * This method should not be called from outside the timer callback handler,
+     * lest multiple callback events get scheduled.
+     */
+    void Signal(int cycles_late);
+
 private:
     Timer();
     ~Timer() override;
diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp
index 96db39ad9..1baa80671 100644
--- a/src/core/hle/svc.cpp
+++ b/src/core/hle/svc.cpp
@@ -837,6 +837,11 @@ static ResultCode SetTimer(Kernel::Handle handle, s64 initial, s64 interval) {
 
     LOG_TRACE(Kernel_SVC, "called timer=0x%08X", handle);
 
+    if (initial < 0 || interval < 0) {
+        return ResultCode(ErrorDescription::OutOfRange, ErrorModule::Kernel,
+                          ErrorSummary::InvalidArgument, ErrorLevel::Permanent);
+    }
+
     SharedPtr<Timer> timer = Kernel::g_handle_table.Get<Timer>(handle);
     if (timer == nullptr)
         return ERR_INVALID_HANDLE;