diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index 1484784881..8d2616c79d 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -202,6 +202,16 @@ public:
         return is_64bit_process;
     }
 
+    /// Gets the total running time of the process instance in ticks.
+    u64 GetCPUTimeTicks() const {
+        return total_process_running_time_ticks;
+    }
+
+    /// Updates the total running time, adding the given ticks to it.
+    void UpdateCPUTimeTicks(u64 ticks) {
+        total_process_running_time_ticks += ticks;
+    }
+
     /**
      * Loads process-specifics configuration info with metadata provided
      * by an executable.
@@ -305,6 +315,9 @@ private:
     /// specified by metadata provided to the process during loading.
     bool is_64bit_process = true;
 
+    /// Total running time for the process in ticks.
+    u64 total_process_running_time_ticks = 0;
+
     /// Per-process handle table for storing created object handles in.
     HandleTable handle_table;
 
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index 1342c597ed..5a5f4cef1a 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -9,6 +9,7 @@
 #include "common/logging/log.h"
 #include "core/arm/arm_interface.h"
 #include "core/core.h"
+#include "core/core_timing.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/process.h"
 #include "core/hle/kernel/scheduler.h"
@@ -34,6 +35,10 @@ Thread* Scheduler::GetCurrentThread() const {
     return current_thread.get();
 }
 
+u64 Scheduler::GetLastContextSwitchTicks() const {
+    return last_context_switch_time;
+}
+
 Thread* Scheduler::PopNextReadyThread() {
     Thread* next = nullptr;
     Thread* thread = GetCurrentThread();
@@ -54,7 +59,10 @@ Thread* Scheduler::PopNextReadyThread() {
 }
 
 void Scheduler::SwitchContext(Thread* new_thread) {
-    Thread* previous_thread = GetCurrentThread();
+    Thread* const previous_thread = GetCurrentThread();
+    Process* const previous_process = Core::CurrentProcess();
+
+    UpdateLastContextSwitchTime(previous_thread, previous_process);
 
     // Save context for previous thread
     if (previous_thread) {
@@ -78,8 +86,6 @@ void Scheduler::SwitchContext(Thread* new_thread) {
         // Cancel any outstanding wakeup events for this thread
         new_thread->CancelWakeupTimer();
 
-        auto* const previous_process = Core::CurrentProcess();
-
         current_thread = new_thread;
 
         ready_queue.remove(new_thread->GetPriority(), new_thread);
@@ -102,6 +108,22 @@ void Scheduler::SwitchContext(Thread* new_thread) {
     }
 }
 
+void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
+    const u64 prev_switch_ticks = last_context_switch_time;
+    const u64 most_recent_switch_ticks = CoreTiming::GetTicks();
+    const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
+
+    if (thread != nullptr) {
+        thread->UpdateCPUTimeTicks(update_ticks);
+    }
+
+    if (process != nullptr) {
+        process->UpdateCPUTimeTicks(update_ticks);
+    }
+
+    last_context_switch_time = most_recent_switch_ticks;
+}
+
 void Scheduler::Reschedule() {
     std::lock_guard<std::mutex> lock(scheduler_mutex);
 
diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h
index 2c94641ecd..c63032b7d5 100644
--- a/src/core/hle/kernel/scheduler.h
+++ b/src/core/hle/kernel/scheduler.h
@@ -17,6 +17,8 @@ class ARM_Interface;
 
 namespace Kernel {
 
+class Process;
+
 class Scheduler final {
 public:
     explicit Scheduler(Core::ARM_Interface& cpu_core);
@@ -31,6 +33,9 @@ public:
     /// Gets the current running thread
     Thread* GetCurrentThread() const;
 
+    /// Gets the timestamp for the last context switch in ticks.
+    u64 GetLastContextSwitchTicks() const;
+
     /// Adds a new thread to the scheduler
     void AddThread(SharedPtr<Thread> thread, u32 priority);
 
@@ -64,6 +69,19 @@ private:
      */
     void SwitchContext(Thread* new_thread);
 
+    /**
+     * Called on every context switch to update the internal timestamp
+     * This also updates the running time ticks for the given thread and
+     * process using the following difference:
+     *
+     * ticks += most_recent_ticks - last_context_switch_ticks
+     *
+     * The internal tick timestamp for the scheduler is simply the
+     * most recent tick count retrieved. No special arithmetic is
+     * applied to it.
+     */
+    void UpdateLastContextSwitchTime(Thread* thread, Process* process);
+
     /// Lists all thread ids that aren't deleted/etc.
     std::vector<SharedPtr<Thread>> thread_list;
 
@@ -73,6 +91,7 @@ private:
     SharedPtr<Thread> current_thread = nullptr;
 
     Core::ARM_Interface& cpu_core;
+    u64 last_context_switch_time = 0;
 
     static std::mutex scheduler_mutex;
 };
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index a5302d924f..4e490e2b5d 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -467,6 +467,37 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
     LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id,
               info_sub_id, handle);
 
+    enum class GetInfoType : u64 {
+        // 1.0.0+
+        AllowedCpuIdBitmask = 0,
+        AllowedThreadPrioBitmask = 1,
+        MapRegionBaseAddr = 2,
+        MapRegionSize = 3,
+        HeapRegionBaseAddr = 4,
+        HeapRegionSize = 5,
+        TotalMemoryUsage = 6,
+        TotalHeapUsage = 7,
+        IsCurrentProcessBeingDebugged = 8,
+        ResourceHandleLimit = 9,
+        IdleTickCount = 10,
+        RandomEntropy = 11,
+        PerformanceCounter = 0xF0000002,
+        // 2.0.0+
+        ASLRRegionBaseAddr = 12,
+        ASLRRegionSize = 13,
+        NewMapRegionBaseAddr = 14,
+        NewMapRegionSize = 15,
+        // 3.0.0+
+        IsVirtualAddressMemoryEnabled = 16,
+        PersonalMmHeapUsage = 17,
+        TitleId = 18,
+        // 4.0.0+
+        PrivilegedProcessId = 19,
+        // 5.0.0+
+        UserExceptionContextAddr = 20,
+        ThreadTickCount = 0xF0000002,
+    };
+
     const auto* current_process = Core::CurrentProcess();
     const auto& vm_manager = current_process->VMManager();
 
@@ -529,6 +560,36 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
                     "(STUBBED) Attempted to query user exception context address, returned 0");
         *result = 0;
         break;
+    case GetInfoType::ThreadTickCount: {
+        constexpr u64 num_cpus = 4;
+        if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) {
+            return ERR_INVALID_COMBINATION_KERNEL;
+        }
+
+        const auto thread =
+            current_process->GetHandleTable().Get<Thread>(static_cast<Handle>(handle));
+        if (!thread) {
+            return ERR_INVALID_HANDLE;
+        }
+
+        auto& system = Core::System::GetInstance();
+        const auto& scheduler = system.CurrentScheduler();
+        const auto* const current_thread = scheduler.GetCurrentThread();
+        const bool same_thread = current_thread == thread;
+
+        const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTicks();
+        u64 out_ticks = 0;
+        if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) {
+            const u64 thread_ticks = current_thread->GetTotalCPUTimeTicks();
+
+            out_ticks = thread_ticks + (CoreTiming::GetTicks() - prev_ctx_ticks);
+        } else if (same_thread && info_sub_id == system.CurrentCoreIndex()) {
+            out_ticks = CoreTiming::GetTicks() - prev_ctx_ticks;
+        }
+
+        *result = out_ticks;
+        break;
+    }
     default:
         UNIMPLEMENTED();
     }
diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h
index 554a5e328a..b06aac4ec4 100644
--- a/src/core/hle/kernel/svc.h
+++ b/src/core/hle/kernel/svc.h
@@ -24,37 +24,6 @@ struct PageInfo {
     u64 flags;
 };
 
-/// Values accepted by svcGetInfo
-enum class GetInfoType : u64 {
-    // 1.0.0+
-    AllowedCpuIdBitmask = 0,
-    AllowedThreadPrioBitmask = 1,
-    MapRegionBaseAddr = 2,
-    MapRegionSize = 3,
-    HeapRegionBaseAddr = 4,
-    HeapRegionSize = 5,
-    TotalMemoryUsage = 6,
-    TotalHeapUsage = 7,
-    IsCurrentProcessBeingDebugged = 8,
-    ResourceHandleLimit = 9,
-    IdleTickCount = 10,
-    RandomEntropy = 11,
-    PerformanceCounter = 0xF0000002,
-    // 2.0.0+
-    ASLRRegionBaseAddr = 12,
-    ASLRRegionSize = 13,
-    NewMapRegionBaseAddr = 14,
-    NewMapRegionSize = 15,
-    // 3.0.0+
-    IsVirtualAddressMemoryEnabled = 16,
-    PersonalMmHeapUsage = 17,
-    TitleId = 18,
-    // 4.0.0+
-    PrivilegedProcessId = 19,
-    // 5.0.0+
-    UserExceptionContextAddr = 20,
-};
-
 void CallSVC(u32 immediate);
 
 } // namespace Kernel
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index f4d7bd2356..4a6e11239d 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -258,6 +258,14 @@ public:
         return last_running_ticks;
     }
 
+    u64 GetTotalCPUTimeTicks() const {
+        return total_cpu_time_ticks;
+    }
+
+    void UpdateCPUTimeTicks(u64 ticks) {
+        total_cpu_time_ticks += ticks;
+    }
+
     s32 GetProcessorID() const {
         return processor_id;
     }
@@ -378,7 +386,8 @@ private:
     u32 nominal_priority = 0; ///< Nominal thread priority, as set by the emulated application
     u32 current_priority = 0; ///< Current thread priority, can be temporarily changed
 
-    u64 last_running_ticks = 0; ///< CPU tick when thread was last running
+    u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks.
+    u64 last_running_ticks = 0;   ///< CPU tick when thread was last running
 
     s32 processor_id = 0;