feat(libkernel): accept the audio coprocessor memory protection bits

A title maps direct memory with protection 0x3f2, which adds bits
0x100 and 0x200 to the CPU, GPU and AMPR bits, and the mapping threw.
They are the audio coprocessor (ACP) read and write bits, as named in
the KytyPS5 and etaHEN headers. The coprocessor work runs on the host
CPU, so they map to host read and read/write access like the GPU bits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
alfalfa-star 2026-10-05 21:19:39 +02:00
parent 9ab937b261
commit b567bb8eed
3 changed files with 22 additions and 3 deletions

View File

@ -194,13 +194,13 @@ void ValidateRange(const void* addr, size_t len, size_t alignment) {
}
int LinuxProtFromSce(int prot) {
if ((prot & ~0xF7) != 0) {
if ((prot & ~0x3F7) != 0) {
// return SCE_KERNEL_ERROR_EINVAL;
throw std::invalid_argument("Unsupported memory protection bits: " + std::to_string(prot));
}
int result = PROT_NONE;
if (prot & 0x13) result |= PROT_READ;
if (prot & 0x22) result |= PROT_READ | PROT_WRITE;
if (prot & 0x113) result |= PROT_READ;
if (prot & 0x222) result |= PROT_READ | PROT_WRITE;
if (prot & 4) result |= PROT_READ | PROT_EXEC;
return result;
}

View File

@ -125,6 +125,23 @@ static void CheckNamedAndHintedMappings() {
Require(sceKernelMunmap(first, length) == 0);
}
static void CheckAudioCoprocessorProtection() {
constexpr std::size_t length = 0x4000;
void* writable = nullptr;
Require(sceKernelMapFlexibleMemory(&writable, length, 0x200, 0) == 0);
static_cast<volatile unsigned char*>(writable)[length - 1] = 7;
Require(static_cast<volatile unsigned char*>(writable)[length - 1] == 7);
Require(sceKernelMprotect(writable, length, 0x100) == 0);
Require(static_cast<volatile unsigned char*>(writable)[length - 1] == 7);
Require(sceKernelMprotect(writable, length, 0x3f2) == 0);
static_cast<volatile unsigned char*>(writable)[0] = 9;
Require(sceKernelMunmap(writable, length) == 0);
bool rejected = false;
void* undefined = nullptr;
try { sceKernelMapFlexibleMemory(&undefined, length, 0x400, 0); } catch (const std::invalid_argument&) { rejected = true; }
Require(rejected && undefined == nullptr);
}
static void CheckInternalNamedFlexibleMapping() {
constexpr std::size_t length = 0x10000;
std::size_t before = 0;
@ -921,6 +938,7 @@ int main() {
CheckInternalNamedFlexibleMapping();
CheckBatchMapStopsAtInvalidEntry();
CheckCheckedReleaseDirectMemory();
CheckAudioCoprocessorProtection();
CheckDirectMemoryFollowsPhysicalPages();
CheckReleaseDirectMemoryClearsMappings();
CheckFixedVirtualReservation();

View File

@ -88,6 +88,7 @@ Throughout the project, every function at every stage either **does exactly what
- [sceKernelInternalMemoryGetModuleSegmentInfo](../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - unknown signature
- [sceKernelGetModuleInfoFromAddr](../../core/libs/prx/libkernel/Module/src/ModuleInfo.cpp) (libkernel) - the 0x1A8-byte info layout follows the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation; a title's `libc.prx` reads only `id` (PPSA14632). The meaning of `flags` is unknown; only 2 is accepted, as is only the full `st_size`. The id is the `sceKernelLoadStartModule` handle of an image opened through the loader and otherwise a stable id from the same range. The name is the host file name without `.guest.prx`. An image with more than 4 load segments (the converted executable has 7) reports its first 4. `tls_offset` is 0 and `ref_count` is 1, as the host loader exposes neither. Not implemented on Windows
- [sceKernelReleaseFlexibleMemory](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - unmaps the range like sceKernelMunmap, as fpPS4 and prosper do; whether the console keeps the virtual range reserved after releasing its pages is unverified
- [Memory protection bits 0x100 and 0x200](../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - taken as audio coprocessor (ACP) read and write, as in the [KytyPS5](https://github.com/KytyPS5/KytyPS5) and [etaHEN](https://github.com/etaHEN/etaHEN) headers; the coprocessor work runs on the host CPU, so they grant host read and read/write access like the GPU bits
- [sceKernelSyncOnAddressWait](../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - the only known caller passes a null timeout and a name string as the fourth argument; the timeout is assumed to point to microseconds like the other kernel waits, and the name is ignored
- [sceKernelSyncOnAddressWait8 / sceKernelSyncOnAddressWait16 / sceKernelSyncOnAddressWait32 / sceKernelSyncOnAddressWait64](../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - libc's `__std_atomic_wait_direct_8/16/32/64` pass a microsecond timeout or null and ignore the result; the by-value comparand, return values and the 2- and 8-byte alignment of Wait16 and Wait64 are assumed
- [sceKernelAioSubmitReadCommandsMultiple / sceKernelAioSubmitWriteCommandsMultiple / sceKernelAioWaitRequests / sceKernelAioCancelRequest / sceKernelAioCancelRequests / sceKernelAioDeleteRequests](../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) - behaviour from shadPS4 (one id per request for the Multiple submits, cancel turns any request aborted and id 0 reports processing, wait mode 2 returns once one request completed); the 128 requests or ids per batch limit is from SharpProspero, with an unknown error, so larger batches throw; wait modes other than 1 (and) and 2 (or) throw; null pointers, negative counts and invalid ids return the codes the single-request siblings use, checked before any state is written