Files
Xenia-Canary/src/xenia/kernel/xobject.cc
Herman S. 65b74819aa [Threading] Implement priority boost on wake
When a thread wakes from a kernel wait, the Xenon scheduler boosts its
effective priority by the increment passed to the signaling call
(KeSetEvent, KeReleaseSemaphore, KeReleaseMutant). The boost is clamped
to the per-thread max_dynamic_priority cap, respects the guest
boost_disabled flag, and is drained on the next quantum expiry.
Guest KTHREAD priority fields are now initialized from parent process
defaults, and the previously unknown fields involved have been renamed
to match their identified purpose.
2026-04-11 17:05:05 +09:00

487 lines
15 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/kernel/xobject.h"
#include "xenia/base/byte_stream.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/util/shim_utils.h"
#include "xenia/kernel/xboxkrnl/xboxkrnl_private.h"
#include "xenia/kernel/xenumerator.h"
#include "xenia/kernel/xevent.h"
#include "xenia/kernel/xfile.h"
#include "xenia/kernel/xmodule.h"
#include "xenia/kernel/xmutant.h"
#include "xenia/kernel/xnotifylistener.h"
#include "xenia/kernel/xsemaphore.h"
#include "xenia/kernel/xsymboliclink.h"
#include "xenia/kernel/xthread.h"
#include "xenia/xbox.h"
namespace xe {
namespace kernel {
XObject::XObject(Type type)
: kernel_state_(nullptr), pointer_ref_count_(1), type_(type) {
handles_.reserve(10);
}
XObject::XObject(KernelState* kernel_state, Type type, bool host_object)
: kernel_state_(kernel_state),
type_(type),
pointer_ref_count_(1),
guest_object_ptr_(0),
allocated_guest_object_(false),
host_object_(host_object) {
handles_.reserve(10);
// TODO: Assert kernel_state != nullptr in this constructor.
if (kernel_state) {
kernel_state->object_table()->AddHandle(this, nullptr);
}
}
XObject::~XObject() {
assert_true(handles_.empty());
assert_zero(pointer_ref_count_);
if (allocated_guest_object_) {
uint32_t ptr = guest_object_ptr_ - sizeof(X_OBJECT_HEADER);
auto header = memory()->TranslateVirtual<X_OBJECT_HEADER*>(ptr);
// Free the object creation info
if (header->object_type_ptr) {
memory()->SystemHeapFree(header->object_type_ptr);
}
memory()->SystemHeapFree(ptr);
}
}
Emulator* XObject::emulator() const { return kernel_state_->emulator_; }
KernelState* XObject::kernel_state() const { return kernel_state_; }
Memory* XObject::memory() const { return kernel_state_->memory(); }
XObject::Type XObject::type() const { return type_; }
void XObject::RetainHandle() {
kernel_state_->object_table()->RetainHandle(handles_[0]);
}
bool XObject::ReleaseHandle() {
// FIXME: Return true when handle is actually released.
return kernel_state_->object_table()->ReleaseHandle(handles_[0]) ==
X_STATUS_SUCCESS;
}
void XObject::Retain() { ++pointer_ref_count_; }
void XObject::Release() {
if (--pointer_ref_count_ == 0) {
delete this;
}
}
X_STATUS XObject::Delete() {
if (kernel_state_ == nullptr) {
// Fake return value for api-scanner
return X_STATUS_SUCCESS;
} else {
if (!name_.empty()) {
kernel_state_->object_table()->RemoveNameMapping(name_);
}
return kernel_state_->object_table()->RemoveHandle(handles_[0]);
}
}
bool XObject::SaveObject(ByteStream* stream) {
stream->Write<uint32_t>(allocated_guest_object_);
stream->Write<uint32_t>(guest_object_ptr_);
stream->Write(uint32_t(handles_.size()));
stream->Write(&handles_[0], handles_.size() * sizeof(X_HANDLE));
return true;
}
bool XObject::RestoreObject(ByteStream* stream) {
allocated_guest_object_ = stream->Read<uint32_t>() > 0;
guest_object_ptr_ = stream->Read<uint32_t>();
handles_.resize(stream->Read<uint32_t>());
stream->Read(&handles_[0], handles_.size() * sizeof(X_HANDLE));
// Restore our pointer to our handles in the object table.
for (size_t i = 0; i < handles_.size(); i++) {
kernel_state_->object_table()->RestoreHandle(handles_[i], this);
}
return true;
}
object_ref<XObject> XObject::Restore(KernelState* kernel_state, Type type,
ByteStream* stream) {
switch (type) {
case Type::Enumerator:
break;
case Type::Event:
return XEvent::Restore(kernel_state, stream);
case Type::File:
return XFile::Restore(kernel_state, stream);
case Type::IOCompletion:
break;
case Type::Module:
return XModule::Restore(kernel_state, stream);
case Type::Mutant:
return XMutant::Restore(kernel_state, stream);
case Type::NotifyListener:
return XNotifyListener::Restore(kernel_state, stream);
case Type::Semaphore:
return XSemaphore::Restore(kernel_state, stream);
case Type::Session:
break;
case Type::Socket:
break;
case Type::SymbolicLink:
return XSymbolicLink::Restore(kernel_state, stream);
case Type::Thread:
return XThread::Restore(kernel_state, stream);
case Type::Timer:
break;
case Type::Undefined:
break;
}
assert_always("No restore handler exists for this object!");
return nullptr;
}
void XObject::SetAttributes(uint32_t obj_attributes_ptr) {
if (!obj_attributes_ptr) {
return;
}
auto name = util::TranslateAnsiStringAddress(
memory(), xe::load_and_swap<uint32_t>(
memory()->TranslateVirtual(obj_attributes_ptr + 4)));
if (!name.empty()) {
name_ = std::string(name);
kernel_state_->object_table()->AddNameMapping(name_, handles_[0]);
}
}
uint32_t XObject::TimeoutTicksToMs(int64_t timeout_ticks) {
if (timeout_ticks > 0) {
// NetDll_WSAWaitForMultipleEvents provides timeout in form of MS.
return (uint32_t)timeout_ticks;
} else if (timeout_ticks < 0) {
// Relative time.
return (uint32_t)(-timeout_ticks / 10000); // Ticks -> MS
} else {
return 0;
}
}
X_STATUS XObject::Wait(uint32_t wait_reason, uint32_t processor_mode,
uint32_t alertable, uint64_t* opt_timeout) {
auto wait_handle = GetWaitHandle();
if (!wait_handle) {
// Object doesn't support waiting.
return X_STATUS_SUCCESS;
}
auto timeout_ms =
opt_timeout ? std::chrono::milliseconds(Clock::ScaleGuestDurationMillis(
TimeoutTicksToMs(*opt_timeout)))
: std::chrono::milliseconds::max();
auto result =
xe::threading::Wait(wait_handle, alertable ? true : false, timeout_ms);
switch (result) {
case xe::threading::WaitResult::kSuccess:
case xe::threading::WaitResult::kUserCallback: {
auto current_thread = XThread::GetCurrentThread();
if (current_thread) {
current_thread->BoostOnWake(priority_increment());
}
if (result == xe::threading::WaitResult::kSuccess) {
WaitCallback();
return X_STATUS_SUCCESS;
}
return X_STATUS_USER_APC;
}
case xe::threading::WaitResult::kTimeout:
xe::threading::MaybeYield();
return X_STATUS_TIMEOUT;
default:
case xe::threading::WaitResult::kAbandoned:
case xe::threading::WaitResult::kFailed:
return X_STATUS_ABANDONED_WAIT_0;
}
}
X_STATUS XObject::SignalAndWait(XObject* signal_object, XObject* wait_object,
uint32_t wait_reason, uint32_t processor_mode,
uint32_t alertable, uint64_t* opt_timeout) {
auto timeout_ms =
opt_timeout ? std::chrono::milliseconds(Clock::ScaleGuestDurationMillis(
TimeoutTicksToMs(*opt_timeout)))
: std::chrono::milliseconds::max();
auto result = xe::threading::SignalAndWait(
signal_object->GetWaitHandle(), wait_object->GetWaitHandle(),
alertable ? true : false, timeout_ms);
switch (result) {
case xe::threading::WaitResult::kSuccess:
case xe::threading::WaitResult::kUserCallback: {
auto current_thread = XThread::GetCurrentThread();
if (current_thread) {
current_thread->BoostOnWake(wait_object->priority_increment());
}
if (result == xe::threading::WaitResult::kSuccess) {
wait_object->WaitCallback();
return X_STATUS_SUCCESS;
}
return X_STATUS_USER_APC;
}
case xe::threading::WaitResult::kTimeout:
xe::threading::MaybeYield();
return X_STATUS_TIMEOUT;
default:
case xe::threading::WaitResult::kAbandoned:
case xe::threading::WaitResult::kFailed:
return X_STATUS_ABANDONED_WAIT_0;
}
}
X_STATUS XObject::WaitMultiple(uint32_t count, XObject** objects,
uint32_t wait_type, uint32_t wait_reason,
uint32_t processor_mode, uint32_t alertable,
uint64_t* opt_timeout) {
xe::threading::WaitHandle* wait_handles[64];
for (size_t i = 0; i < count; ++i) {
wait_handles[i] = objects[i]->GetWaitHandle();
assert_not_null(wait_handles[i]);
}
auto timeout_ms =
opt_timeout ? std::chrono::milliseconds(Clock::ScaleGuestDurationMillis(
TimeoutTicksToMs(*opt_timeout)))
: std::chrono::milliseconds::max();
X_STATUS status;
uint32_t boost_increment = 0;
if (wait_type) {
auto result = xe::threading::WaitAny(wait_handles, count,
alertable ? true : false, timeout_ms);
switch (result.first) {
case xe::threading::WaitResult::kSuccess:
objects[result.second]->WaitCallback();
boost_increment = objects[result.second]->priority_increment();
status = X_STATUS(result.second);
break;
case xe::threading::WaitResult::kUserCallback:
status = X_STATUS_USER_APC;
break;
case xe::threading::WaitResult::kTimeout:
xe::threading::MaybeYield();
status = X_STATUS_TIMEOUT;
break;
case xe::threading::WaitResult::kAbandoned:
status = X_STATUS(X_STATUS_ABANDONED_WAIT_0 + result.second);
break;
default:
case xe::threading::WaitResult::kFailed:
status = X_STATUS_UNSUCCESSFUL;
break;
}
} else {
auto result = xe::threading::WaitAll(wait_handles, count,
alertable ? true : false, timeout_ms);
switch (result) {
case xe::threading::WaitResult::kSuccess:
for (uint32_t i = 0; i < count; i++) {
objects[i]->WaitCallback();
// Use the largest increment among the signaled objects.
if (objects[i]->priority_increment() > boost_increment) {
boost_increment = objects[i]->priority_increment();
}
}
status = X_STATUS_SUCCESS;
break;
case xe::threading::WaitResult::kUserCallback:
status = X_STATUS_USER_APC;
break;
case xe::threading::WaitResult::kTimeout:
xe::threading::MaybeYield();
status = X_STATUS_TIMEOUT;
break;
default:
case xe::threading::WaitResult::kAbandoned:
case xe::threading::WaitResult::kFailed:
status = X_STATUS_ABANDONED_WAIT_0;
break;
}
}
// Apply priority boost if the thread actually blocked (not on
// timeout/failure).
if (status != X_STATUS_TIMEOUT && status != X_STATUS_UNSUCCESSFUL &&
status != X_STATUS_ABANDONED_WAIT_0) {
auto current_thread = XThread::GetCurrentThread();
if (current_thread) {
current_thread->BoostOnWake(boost_increment);
}
}
return status;
}
uint8_t* XObject::CreateNative(uint32_t size) {
auto global_lock = xe::global_critical_region::AcquireDirect();
uint32_t total_size = size + sizeof(X_OBJECT_HEADER);
auto mem = memory()->SystemHeapAlloc(total_size);
if (!mem) {
// Out of memory!
return nullptr;
}
allocated_guest_object_ = true;
memory()->Zero(mem, total_size);
SetNativePointer(mem + sizeof(X_OBJECT_HEADER), true);
auto header = memory()->TranslateVirtual<X_OBJECT_HEADER*>(mem);
auto object_type = memory()->SystemHeapAlloc(sizeof(X_OBJECT_TYPE));
if (object_type) {
// Set it up in the header.
// Some kernel method is accessing this struct and dereferencing a member
// @ offset 0x14
header->object_type_ptr = object_type;
}
return memory()->TranslateVirtual(guest_object_ptr_);
}
void XObject::SetNativePointer(uint32_t native_ptr, bool uninitialized) {
auto global_lock = xe::global_critical_region::AcquireDirect();
// If hit: We've already setup the native ptr with CreateNative!
assert_zero(guest_object_ptr_);
auto header =
kernel_state_->memory()->TranslateVirtual<X_DISPATCH_HEADER*>(native_ptr);
// Memory uninitialized, so don't bother with the check.
if (!uninitialized) {
assert_true(!(header->wait_list.blink_ptr & 0x1));
}
// Stash pointer in struct.
// FIXME: This assumes the object has a dispatch header (some don't!)
StashHandle(header, handle());
guest_object_ptr_ = native_ptr;
}
object_ref<XObject> XObject::GetNativeObject(KernelState* kernel_state,
void* native_ptr, int32_t as_type,
bool already_locked) {
assert_not_null(native_ptr);
// Unfortunately the XDK seems to inline some KeInitialize calls, meaning
// we never see it and just randomly start getting passed events/timers/etc.
// Luckily it seems like all other calls (Set/Reset/Wait/etc) are used and
// we don't have to worry about PPC code poking the struct. Because of that,
// we init on first use, store our handle in the struct, and dereference it
// each time.
// We identify this by setting wait_list.flink_ptr to a magic value. When set,
// wait_list.blink_ptr will hold a handle to our object.
if (!already_locked) {
global_critical_region::mutex().lock();
}
XObject* result;
auto header = reinterpret_cast<X_DISPATCH_HEADER*>(native_ptr);
if (as_type == -1) {
as_type = header->type;
}
if (header->wait_list.flink_ptr == kXObjSignature) {
// Already initialized.
// TODO: assert if the type of the object != as_type
uint32_t handle = header->wait_list.blink_ptr;
result = kernel_state->object_table()
->LookupObject<XObject>(handle, true)
.release();
} else {
// First use, create new.
// https://www.nirsoft.net/kernel_struct/vista/KOBJECTS.html
XObject* object = nullptr;
switch (as_type) {
case 0: // EventNotificationObject
case 1: // EventSynchronizationObject
{
auto ev = new XEvent(kernel_state);
ev->InitializeNative(native_ptr, header);
object = ev;
} break;
case 2: // MutantObject
{
auto mutant = new XMutant(kernel_state);
mutant->InitializeNative(native_ptr, header);
object = mutant;
} break;
case 5: // SemaphoreObject
{
auto sem = new XSemaphore(kernel_state);
auto success = sem->InitializeNative(native_ptr, header);
// Can't report failure to the guest at late initialization:
assert_true(success);
object = sem;
} break;
case 3: // ProcessObject
case 4: // QueueObject
case 6: // ThreadObject
case 7: // GateObject
case 8: // TimerNotificationObject
case 9: // TimerSynchronizationObject
case 18: // ApcObject
case 19: // DpcObject
case 20: // DeviceQueueObject
case 21: // EventPairObject
case 22: // InterruptObject
case 23: // ProfileObject
case 24: // ThreadedDpcObject
default:
assert_always();
result = nullptr;
}
// Stash pointer in struct.
// FIXME: This assumes the object contains a dispatch header (some don't!)
if (object) {
StashHandle(header, object->handle());
}
result = object;
}
if (!already_locked) {
global_critical_region::mutex().unlock();
}
return object_ref<XObject>(result);
}
} // namespace kernel
} // namespace xe