diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index 5b76f897a..19be10b0f 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -68,9 +68,6 @@ KernelState::KernelState(Emulator* emulator) InitializeKernelGuestGlobals(); kernel_version_ = KernelVersion(cvars::kernel_build_version); - // Hardcoded maximum of 2048 TLS slots. - tls_bitmap_.Resize(2048); - auto hc_loc_heap = memory_->LookupHeap(strange_hardcoded_page_); bool fixed_alloc_worked = hc_loc_heap->AllocFixed( strange_hardcoded_page_, 65536, 0, @@ -141,18 +138,132 @@ const std::unique_ptr KernelState::module_xdbf( return nullptr; } -uint32_t KernelState::AllocateTLS() { return uint32_t(tls_bitmap_.Acquire()); } +uint32_t KernelState::AllocateTLS(cpu::ppc::PPCContext* context) { + auto globals = + memory()->TranslateVirtual(GetKernelGuestGlobals()); + auto tls_lock = &globals->tls_lock; + auto old_irql = xboxkrnl::xeKeKfAcquireSpinLock(context, tls_lock); -void KernelState::FreeTLS(uint32_t slot) { + int result = -1; + + auto current_thread = XThread::GetCurrentThread(); + if (!current_thread) { + XELOGE("AllocateTLS: No current thread"); + xboxkrnl::xeKeKfReleaseSpinLock(context, tls_lock, old_irql); + return X_TLS_OUT_OF_INDEXES; + } + + auto process_ptr = memory()->TranslateVirtual( + current_thread->guest_object()->process); + if (!process_ptr) { + XELOGE("AllocateTLS: Failed to translate process pointer"); + xboxkrnl::xeKeKfReleaseSpinLock(context, tls_lock, old_irql); + return X_TLS_OUT_OF_INDEXES; + } + + // Search for a free TLS slot in the process bitmap + // Bitmap format: 1 = free, 0 = allocated + // 8 x 32-bit words = 256 total TLS slots + for (xe::be* i = &process_ptr->tls_slot_bitmap[0]; + i < &process_ptr->tls_slot_bitmap[8]; ++i) { + // Read bitmap value (handles big-endian conversion) + uint32_t bitmap_value = static_cast(*i); + + // Find highest free slot using lzcnt (leading zero count) + // Returns 0-31 if a bit is set, 32 if no bits are set + uint32_t leading_zeros = xe::lzcnt(bitmap_value); + + if (leading_zeros != 32) { + // Calculate absolute slot index from bitmap position and bit offset + // Each bitmap word represents 32 slots + size_t bitmap_index = i - &process_ptr->tls_slot_bitmap[0]; + uint32_t base_slot = static_cast(bitmap_index) * 32; + int calculated_slot = base_slot + leading_zeros; + + // Validate slot is within Xbox 360 TLS range + if (calculated_slot >= 0 && calculated_slot < 256) { + result = calculated_slot; + + // Clear the bit to mark as allocated + // lzcnt returns 0 for bit 31, 31 for bit 0 + uint32_t bit_index = 31 - leading_zeros; + *i = bitmap_value & ~(1U << bit_index); + break; + } else { + XELOGE("AllocateTLS: Invalid slot calculation: {}", calculated_slot); + } + } + } + + if (result == -1) { + XELOGW("AllocateTLS: All TLS slots exhausted for current process"); + } + + xboxkrnl::xeKeKfReleaseSpinLock(context, tls_lock, old_irql); + return static_cast(result); +} + +void KernelState::FreeTLS(cpu::ppc::PPCContext* context, uint32_t slot) { + if (slot >= 256) { + XELOGE("FreeTLS: Invalid slot index {}", slot); + return; + } + + auto current_thread = XThread::GetCurrentThread(); + if (!current_thread) { + XELOGE("FreeTLS: No current thread"); + return; + } + + auto current_kthread = current_thread->guest_object(); + if (!current_kthread) { + XELOGE("FreeTLS: Failed to get guest thread object"); + return; + } + + auto process_ptr = memory()->TranslateVirtual(current_kthread->process); + if (!process_ptr) { + XELOGE("FreeTLS: Failed to translate process pointer"); + return; + } + + auto globals = + memory()->TranslateVirtual(GetKernelGuestGlobals()); + auto tls_lock = &globals->tls_lock; + auto old_irql = xboxkrnl::xeKeKfAcquireSpinLock(context, tls_lock); + + uint32_t bitmap_index = slot / 32; + uint32_t bit_mask = 1U << (31 - (slot % 32)); + uint32_t bitmap_value = + static_cast(process_ptr->tls_slot_bitmap[bitmap_index]); + + if (bitmap_value & bit_mask) { + XELOGW("FreeTLS: Slot {} is already free", slot); + xboxkrnl::xeKeKfReleaseSpinLock(context, tls_lock, old_irql); + return; + } + + // Clear TLS values in all threads of this process const std::vector> threads = object_table()->GetObjectsByType(); + uint32_t current_process_ptr = current_kthread->process.m_ptr; for (const object_ref& thread : threads) { - if (thread->is_guest_thread()) { + if (!thread || !thread->is_guest_thread()) { + continue; + } + + auto thread_kthread = thread->guest_object(); + if (thread_kthread && + thread_kthread->process.m_ptr == current_process_ptr) { thread->SetTLSValue(slot, 0); } } - tls_bitmap_.Release(slot); + + // Mark slot as free in bitmap + process_ptr->tls_slot_bitmap[bitmap_index] = bitmap_value | bit_mask; + + xboxkrnl::xeKeKfReleaseSpinLock(context, tls_lock, old_irql); } void KernelState::RegisterTitleTerminateNotification(uint32_t routine, @@ -805,9 +916,6 @@ void KernelState::TerminateTitle() { // Unregister all notify listeners. notify_listeners_.clear(); - // Clear the TLS map. - tls_bitmap_.Reset(); - // Unset the executable module. executable_module_ = nullptr; @@ -1077,12 +1185,6 @@ bool KernelState::Save(ByteStream* stream) { object_table_.Save(stream); // Write the TLS allocation bitmap - auto tls_bitmap = tls_bitmap_.data(); - stream->Write(uint32_t(tls_bitmap.size())); - for (size_t i = 0; i < tls_bitmap.size(); i++) { - stream->Write(tls_bitmap[i]); - } - // We save XThreads absolutely first, as they will execute code upon save // (which could modify the kernel state) auto threads = object_table_.GetObjectsByType(); @@ -1210,12 +1312,11 @@ bool KernelState::Restore(ByteStream* stream) { // Restore the object table object_table_.Restore(stream); - // Read the TLS allocation bitmap + // TLS bitmap is now stored per-process in X_KPROCESS structures (in guest + // memory) Skip reading old global TLS bitmap if present in old save files auto num_bitmap_entries = stream->Read(); - auto& tls_bitmap = tls_bitmap_.data(); - tls_bitmap.resize(num_bitmap_entries); for (uint32_t i = 0; i < num_bitmap_entries; i++) { - tls_bitmap[i] = stream->Read(); + stream->Read(); // Discard old data } uint32_t num_threads = stream->Read(); @@ -1349,12 +1450,13 @@ void KernelState::SetProcessTLSVars(X_KPROCESS* process, int num_slots, process->tls_slot_size = 4 * slots_padded; uint32_t count_div32 = slots_padded / 32; for (unsigned word_index = 0; word_index < count_div32; ++word_index) { - process->bitmap[word_index] = -1; + process->tls_slot_bitmap[word_index] = -1; } // set remainder of bitset if (((num_slots + 3) & 0x1C) != 0) - process->bitmap[count_div32] = -1 << (32 - ((num_slots + 3) & 0x1C)); + process->tls_slot_bitmap[count_div32] = -1 + << (32 - ((num_slots + 3) & 0x1C)); } void AllocateThread(PPCContext* context) { uint32_t thread_mem_size = static_cast(context->r[3]); diff --git a/src/xenia/kernel/kernel_state.h b/src/xenia/kernel/kernel_state.h index 0b5248943..da8cb9066 100644 --- a/src/xenia/kernel/kernel_state.h +++ b/src/xenia/kernel/kernel_state.h @@ -80,7 +80,7 @@ struct X_KPROCESS { uint8_t is_terminating; // one of X_PROCTYPE_ uint8_t process_type; - xe::be bitmap[8]; + xe::be tls_slot_bitmap[8]; xe::be unk_50; X_LIST_ENTRY unk_54; xe::be unk_5C; @@ -140,6 +140,7 @@ struct KernelGuestGlobals { // this lock is only used in some Ob functions. It's odd that it is used at // all, as each table already has its own spinlock. X_KSPINLOCK ob_lock; + X_KSPINLOCK tls_lock; // protects per-process TLS bitmap allocations // if LLE emulating Xam, this is needed or you get an immediate freeze X_KEVENT UsbdBootEnumerationDoneEvent; @@ -219,8 +220,8 @@ class KernelState { return kernel_guest_globals_ + offsetof(KernelGuestGlobals, idle_process); } - uint32_t AllocateTLS(); - void FreeTLS(uint32_t slot); + uint32_t AllocateTLS(cpu::ppc::PPCContext* context); + void FreeTLS(cpu::ppc::PPCContext* context, uint32_t slot); void RegisterTitleTerminateNotification(uint32_t routine, uint32_t priority); void RemoveTitleTerminateNotification(uint32_t routine); @@ -382,7 +383,6 @@ class KernelState { std::condition_variable_any dispatch_cond_; std::list> dispatch_queue_; - BitMap tls_bitmap_; uint32_t ke_timestamp_bundle_ptr_ = 0; std::unique_ptr timestamp_timer_; uint32_t quantum_timer_counter_ = 0; diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc index f4cfac6ee..901f82e2d 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc @@ -469,8 +469,8 @@ void KeQuerySystemTime_entry(lpqword_t time_ptr, const ppc_context_t& ctx) { DECLARE_XBOXKRNL_EXPORT1(KeQuerySystemTime, kThreading, kImplemented); // https://msdn.microsoft.com/en-us/library/ms686801 -dword_result_t KeTlsAlloc_entry() { - uint32_t slot = kernel_state()->AllocateTLS(); +dword_result_t KeTlsAlloc_entry(const ppc_context_t& context) { + uint32_t slot = kernel_state()->AllocateTLS(context); XThread::GetCurrentThread()->SetTLSValue(slot, 0); return slot; @@ -478,12 +478,13 @@ dword_result_t KeTlsAlloc_entry() { DECLARE_XBOXKRNL_EXPORT1(KeTlsAlloc, kThreading, kImplemented); // https://msdn.microsoft.com/en-us/library/ms686804 -dword_result_t KeTlsFree_entry(dword_t tls_index) { +dword_result_t KeTlsFree_entry(dword_t tls_index, + const ppc_context_t& context) { if (tls_index == X_TLS_OUT_OF_INDEXES) { return 0; } - kernel_state()->FreeTLS(tls_index); + kernel_state()->FreeTLS(context, tls_index); return 1; } DECLARE_XBOXKRNL_EXPORT1(KeTlsFree, kThreading, kImplemented);