using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.Horizon.Common; using System; using System.Numerics; namespace Ryujinx.HLE.HOS.Kernel.Process { class KProcessCapabilities { public byte[] SvcAccessMask { get; } public byte[] IrqAccessMask { get; } public ulong AllowedCpuCoresMask { get; private set; } public ulong AllowedThreadPriosMask { get; private set; } public uint DebuggingFlags { get; private set; } public uint HandleTableSize { get; private set; } public uint KernelReleaseVersion { get; private set; } public uint ApplicationType { get; private set; } public KProcessCapabilities() { // length / number of bits of the underlying type SvcAccessMask = new byte[KernelConstants.SupervisorCallCount / 8]; IrqAccessMask = new byte[0x80]; } public Result InitializeForKernel(ReadOnlySpan capabilities, KPageTableBase memoryManager) { AllowedCpuCoresMask = 0xf; AllowedThreadPriosMask = ulong.MaxValue; DebuggingFlags &= ~3u; KernelReleaseVersion = KProcess.KernelVersionPacked; return Parse(capabilities, memoryManager); } public Result InitializeForUser(ReadOnlySpan capabilities, KPageTableBase memoryManager) { return Parse(capabilities, memoryManager); } private Result Parse(ReadOnlySpan capabilities, KPageTableBase memoryManager) { int mask0 = 0; int mask1 = 0; for (int index = 0; index < capabilities.Length; index++) { uint cap = capabilities[index]; if (cap.GetCapabilityType() != CapabilityType.MapRange) { Result result = ParseCapability(cap, ref mask0, ref mask1, memoryManager); if (result != Result.Success) { return result; } } else { if ((uint)index + 1 >= capabilities.Length) { return KernelResult.InvalidCombination; } uint prevCap = cap; cap = capabilities[++index]; if (((cap + 1) & ~cap) != 0x40) { return KernelResult.InvalidCombination; } if ((cap & 0x78000000) != 0) { return KernelResult.MaximumExceeded; } if ((cap & 0x7ffff80) == 0) { return KernelResult.InvalidSize; } long address = ((long)prevCap << 5) & 0xffffff000; long size = ((long)cap << 5) & 0xfffff000; if (((ulong)(address + size - 1) >> 36) != 0) { return KernelResult.InvalidAddress; } KMemoryPermission perm = (prevCap >> 31) != 0 ? KMemoryPermission.Read : KMemoryPermission.ReadAndWrite; Result result; if ((cap >> 31) != 0) { result = memoryManager.MapNormalMemory(address, size, perm); } else { result = memoryManager.MapIoMemory(address, size, perm); } if (result != Result.Success) { return result; } } } return Result.Success; } private Result ParseCapability(uint cap, ref int mask0, ref int mask1, KPageTableBase memoryManager) { CapabilityType code = cap.GetCapabilityType(); if (code == CapabilityType.Invalid) { return KernelResult.InvalidCapability; } else if (code == CapabilityType.Padding) { return Result.Success; } int codeMask = 1 << (32 - BitOperations.LeadingZeroCount(code.GetFlag() + 1)); // Check if the property was already set. if (((mask0 & codeMask) & 0x1e008) != 0) { return KernelResult.InvalidCombination; } mask0 |= codeMask; switch (code) { case CapabilityType.CorePriority: { if (AllowedCpuCoresMask != 0 || AllowedThreadPriosMask != 0) { return KernelResult.InvalidCapability; } uint lowestCpuCore = (cap >> 16) & 0xff; uint highestCpuCore = (cap >> 24) & 0xff; if (lowestCpuCore > highestCpuCore) { return KernelResult.InvalidCombination; } uint highestThreadPrio = (cap >> 4) & 0x3f; uint lowestThreadPrio = (cap >> 10) & 0x3f; if (lowestThreadPrio > highestThreadPrio) { return KernelResult.InvalidCombination; } if (highestCpuCore >= KScheduler.CpuCoresCount) { return KernelResult.InvalidCpuCore; } AllowedCpuCoresMask = GetMaskFromMinMax(lowestCpuCore, highestCpuCore); AllowedThreadPriosMask = GetMaskFromMinMax(lowestThreadPrio, highestThreadPrio); break; } case CapabilityType.SyscallMask: { int slot = ((int)cap >> 29) & 7; int svcSlotMask = 1 << slot; if ((mask1 & svcSlotMask) != 0) { return KernelResult.InvalidCombination; } mask1 |= svcSlotMask; uint svcMask = (cap >> 5) & 0xffffff; int baseSvc = slot * 24; for (int index = 0; index < 24; index++) { if (((svcMask >> index) & 1) == 0) { continue; } int svcId = baseSvc + index; if (svcId >= KernelConstants.SupervisorCallCount) { return KernelResult.MaximumExceeded; } SvcAccessMask[svcId / 8] |= (byte)(1 << (svcId & 7)); } break; } case CapabilityType.MapIoPage: { long address = ((long)cap << 4) & 0xffffff000; memoryManager.MapIoMemory(address, KPageTableBase.PageSize, KMemoryPermission.ReadAndWrite); break; } case CapabilityType.MapRegion: { // TODO: Implement capabilities for MapRegion break; } case CapabilityType.InterruptPair: { // TODO: GIC distributor check. int irq0 = ((int)cap >> 12) & 0x3ff; int irq1 = ((int)cap >> 22) & 0x3ff; if (irq0 != 0x3ff) { IrqAccessMask[irq0 / 8] |= (byte)(1 << (irq0 & 7)); } if (irq1 != 0x3ff) { IrqAccessMask[irq1 / 8] |= (byte)(1 << (irq1 & 7)); } break; } case CapabilityType.ProgramType: { uint applicationType = (cap >> 14); if (applicationType > 7) { return KernelResult.ReservedValue; } ApplicationType = applicationType; break; } case CapabilityType.KernelVersion: { // Note: This check is bugged on kernel too, we are just replicating the bug here. if ((KernelReleaseVersion >> 17) != 0 || cap < 0x80000) { return KernelResult.ReservedValue; } KernelReleaseVersion = cap; break; } case CapabilityType.HandleTable: { uint handleTableSize = cap >> 26; if (handleTableSize > 0x3ff) { return KernelResult.ReservedValue; } HandleTableSize = handleTableSize; break; } case CapabilityType.DebugFlags: { uint debuggingFlags = cap >> 19; if (debuggingFlags > 3) { return KernelResult.ReservedValue; } DebuggingFlags &= ~3u; DebuggingFlags |= debuggingFlags; break; } default: return KernelResult.InvalidCapability; } return Result.Success; } private static ulong GetMaskFromMinMax(uint min, uint max) { uint range = max - min + 1; if (range == 64) { return ulong.MaxValue; } ulong mask = (1UL << (int)range) - 1; return mask << (int)min; } } }