From 4b13ecb7524c0d50e44e7219fa596d90257bbbc2 Mon Sep 17 00:00:00 2001 From: emoose Date: Mon, 28 Dec 2020 23:32:30 +0000 Subject: [PATCH] [Kernel] Implement NtReadFileScatter via adding XFile::ReadScatter --- src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc | 87 ++++++++++++++++++++++++ src/xenia/kernel/xfile.cc | 56 +++++++++++++++ src/xenia/kernel/xfile.h | 4 ++ 3 files changed, 147 insertions(+) diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc index fc5d6ef16..7a2d797e8 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc @@ -265,6 +265,93 @@ dword_result_t NtReadFile(dword_t file_handle, dword_t event_handle, } DECLARE_XBOXKRNL_EXPORT2(NtReadFile, kFileSystem, kImplemented, kHighFrequency); +dword_result_t NtReadFileScatter(dword_t file_handle, dword_t event_handle, + lpvoid_t apc_routine_ptr, lpvoid_t apc_context, + pointer_t io_status_block, + lpdword_t segment_array, dword_t length, + lpqword_t byte_offset_ptr) { + X_STATUS result = X_STATUS_SUCCESS; + + bool signal_event = false; + auto ev = kernel_state()->object_table()->LookupObject(event_handle); + if (event_handle && !ev) { + result = X_STATUS_INVALID_HANDLE; + } + + auto file = kernel_state()->object_table()->LookupObject(file_handle); + if (!file) { + result = X_STATUS_INVALID_HANDLE; + } + + if (XSUCCEEDED(result)) { + if (true || file->is_synchronous()) { + // Synchronous. + uint32_t bytes_read = 0; + result = file->ReadScatter( + segment_array.guest_address(), length, + byte_offset_ptr ? static_cast(*byte_offset_ptr) : -1, + &bytes_read, apc_context); + if (io_status_block) { + io_status_block->status = result; + io_status_block->information = bytes_read; + } + + // Queue the APC callback. It must be delivered via the APC mechanism even + // though were are completing immediately. + // Low bit probably means do not queue to IO ports. + if ((uint32_t)apc_routine_ptr & ~1) { + if (apc_context) { + auto thread = XThread::GetCurrentThread(); + thread->EnqueueApc(static_cast(apc_routine_ptr) & ~1u, + apc_context, io_status_block, 0); + } + } + + if (!file->is_synchronous()) { + result = X_STATUS_PENDING; + } + + // Mark that we should signal the event now. We do this after + // we have written the info out. + signal_event = true; + } else { + // TODO(benvanik): async. + + // TODO: On Windows it might be worth trying to use Win32 ReadFileScatter + // here instead of handling it ourselves + + // X_STATUS_PENDING if not returning immediately. + // XFile is waitable and signalled after each async req completes. + // reset the input event (->Reset()) + /*xeNtReadFileState* call_state = new xeNtReadFileState(); + XAsyncRequest* request = new XAsyncRequest( + state, file, + (XAsyncRequest::CompletionCallback)xeNtReadFileCompleted, + call_state);*/ + // result = file->Read(buffer.guest_address(), buffer_length, byte_offset, + // request); + if (io_status_block) { + io_status_block->status = X_STATUS_PENDING; + io_status_block->information = 0; + } + + result = X_STATUS_PENDING; + } + } + + if (XFAILED(result) && io_status_block) { + io_status_block->status = result; + io_status_block->information = 0; + } + + if (ev && signal_event) { + ev->Set(0, false); + } + + return result; +} +DECLARE_XBOXKRNL_EXPORT1(NtReadFileScatter, kFileSystem, kImplemented); + dword_result_t NtWriteFile(dword_t file_handle, dword_t event_handle, function_t apc_routine, lpvoid_t apc_context, pointer_t io_status_block, diff --git a/src/xenia/kernel/xfile.cc b/src/xenia/kernel/xfile.cc index 24ded0619..b9c134c01 100644 --- a/src/xenia/kernel/xfile.cc +++ b/src/xenia/kernel/xfile.cc @@ -178,6 +178,62 @@ X_STATUS XFile::Read(uint32_t buffer_guest_address, uint32_t buffer_length, return result; } +X_STATUS XFile::ReadScatter(uint32_t segments_guest_address, uint32_t length, + uint64_t byte_offset, uint32_t* out_bytes_read, + uint32_t apc_context) { + X_STATUS result = X_STATUS_SUCCESS; + + // segments points to an array of buffer pointers of type + // "FILE_SEGMENT_ELEMENT", but they can just be treated as normal pointers + xe::be* segments = reinterpret_cast*>( + memory()->TranslateVirtual(segments_guest_address)); + + // TODO: not sure if this is meant to change depending on buffer address? + // (only game seen using this always seems to use 4096-byte buffers) + uint32_t page_size = 4096; + + uint32_t read_total = 0; + uint32_t read_remain = length; + while (read_remain) { + uint32_t read_length = read_remain; + uint32_t read_buffer = *segments; + if (read_length > page_size) { + read_length = page_size; + segments++; + } + + uint32_t bytes_read = 0; + result = Read(read_buffer, read_length, + byte_offset ? ((byte_offset != -1 && byte_offset != -2) + ? byte_offset + read_total + : byte_offset) + : -1, + &bytes_read, apc_context, false); + + if (result != X_STATUS_SUCCESS) { + break; + } + + read_total += bytes_read; + read_remain -= read_length; + } + + if (out_bytes_read) { + *out_bytes_read = uint32_t(read_total); + } + + XIOCompletion::IONotification notify; + notify.apc_context = apc_context; + notify.num_bytes = uint32_t(read_total); + notify.status = result; + + NotifyIOCompletionPorts(notify); + + async_event_->Set(); + + return result; +} + X_STATUS XFile::Write(uint32_t buffer_guest_address, uint32_t buffer_length, uint64_t byte_offset, uint32_t* out_bytes_written, uint32_t apc_context) { diff --git a/src/xenia/kernel/xfile.h b/src/xenia/kernel/xfile.h index a63a4e6cf..a5130da8d 100644 --- a/src/xenia/kernel/xfile.h +++ b/src/xenia/kernel/xfile.h @@ -101,6 +101,10 @@ class XFile : public XObject { uint64_t byte_offset, uint32_t* out_bytes_read, uint32_t apc_context, bool notify_completion = true); + X_STATUS ReadScatter(uint32_t segments_guest_address, uint32_t length, + uint64_t byte_offset, uint32_t* out_bytes_read, + uint32_t apc_context); + X_STATUS Write(uint32_t buffer_guess_address, uint32_t buffer_length, uint64_t byte_offset, uint32_t* out_bytes_written, uint32_t apc_context);