diff options
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs')
| -rw-r--r-- | src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs | 540 |
1 files changed, 540 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs new file mode 100644 index 00000000..f130c455 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs @@ -0,0 +1,540 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Synchronization; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.Memory; +using System; +using System.Text; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl +{ + internal class NvHostCtrlDeviceFile : NvDeviceFile + { + public const int EventsCount = 64; + + private bool _isProductionMode; + private Switch _device; + private NvHostEvent[] _events; + + public NvHostCtrlDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) + { + if (NxSettings.Settings.TryGetValue("nv!rmos_set_production_mode", out object productionModeSetting)) + { + _isProductionMode = ((string)productionModeSetting) != "0"; // Default value is "" + } + else + { + _isProductionMode = true; + } + + _device = context.Device; + + _events = new NvHostEvent[EventsCount]; + } + + public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvHostCustomMagic) + { + switch (command.Number) + { + case 0x14: + result = CallIoctlMethod<NvFence>(SyncptRead, arguments); + break; + case 0x15: + result = CallIoctlMethod<uint>(SyncptIncr, arguments); + break; + case 0x16: + result = CallIoctlMethod<SyncptWaitArguments>(SyncptWait, arguments); + break; + case 0x19: + result = CallIoctlMethod<SyncptWaitExArguments>(SyncptWaitEx, arguments); + break; + case 0x1a: + result = CallIoctlMethod<NvFence>(SyncptReadMax, arguments); + break; + case 0x1b: + // As Marshal cannot handle unaligned arrays, we do everything by hand here. + GetConfigurationArguments configArgument = GetConfigurationArguments.FromSpan(arguments); + result = GetConfig(configArgument); + + if (result == NvInternalResult.Success) + { + configArgument.CopyTo(arguments); + } + break; + case 0x1c: + result = CallIoctlMethod<uint>(EventSignal, arguments); + break; + case 0x1d: + result = CallIoctlMethod<EventWaitArguments>(EventWait, arguments); + break; + case 0x1e: + result = CallIoctlMethod<EventWaitArguments>(EventWaitAsync, arguments); + break; + case 0x1f: + result = CallIoctlMethod<uint>(EventRegister, arguments); + break; + case 0x20: + result = CallIoctlMethod<uint>(EventUnregister, arguments); + break; + case 0x21: + result = CallIoctlMethod<ulong>(EventKill, arguments); + break; + } + } + + return result; + } + + private int QueryEvent(uint eventId) + { + lock (_events) + { + uint eventSlot; + uint syncpointId; + + if ((eventId >> 28) == 1) + { + eventSlot = eventId & 0xFFFF; + syncpointId = (eventId >> 16) & 0xFFF; + } + else + { + eventSlot = eventId & 0xFF; + syncpointId = eventId >> 4; + } + + if (eventSlot >= EventsCount || _events[eventSlot] == null || _events[eventSlot].Fence.Id != syncpointId) + { + return 0; + } + + return _events[eventSlot].EventHandle; + } + } + + public override NvInternalResult QueryEvent(out int eventHandle, uint eventId) + { + eventHandle = QueryEvent(eventId); + + return eventHandle != 0 ? NvInternalResult.Success : NvInternalResult.InvalidInput; + } + + private NvInternalResult SyncptRead(ref NvFence arguments) + { + return SyncptReadMinOrMax(ref arguments, max: false); + } + + private NvInternalResult SyncptIncr(ref uint id) + { + if (id >= SynchronizationManager.MaxHardwareSyncpoints) + { + return NvInternalResult.InvalidInput; + } + + _device.System.HostSyncpoint.Increment(id); + + return NvInternalResult.Success; + } + + private NvInternalResult SyncptWait(ref SyncptWaitArguments arguments) + { + uint dummyValue = 0; + + return EventWait(ref arguments.Fence, ref dummyValue, arguments.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: false); + } + + private NvInternalResult SyncptWaitEx(ref SyncptWaitExArguments arguments) + { + return EventWait(ref arguments.Input.Fence, ref arguments.Value, arguments.Input.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: false); + } + + private NvInternalResult SyncptReadMax(ref NvFence arguments) + { + return SyncptReadMinOrMax(ref arguments, max: true); + } + + private NvInternalResult GetConfig(GetConfigurationArguments arguments) + { + if (!_isProductionMode && NxSettings.Settings.TryGetValue($"{arguments.Domain}!{arguments.Parameter}".ToLower(), out object nvSetting)) + { + byte[] settingBuffer = new byte[0x101]; + + if (nvSetting is string stringValue) + { + if (stringValue.Length > 0x100) + { + Logger.Error?.Print(LogClass.ServiceNv, $"{arguments.Domain}!{arguments.Parameter} String value size is too big!"); + } + else + { + settingBuffer = Encoding.ASCII.GetBytes(stringValue + "\0"); + } + } + else if (nvSetting is int intValue) + { + settingBuffer = BitConverter.GetBytes(intValue); + } + else if (nvSetting is bool boolValue) + { + settingBuffer[0] = boolValue ? (byte)1 : (byte)0; + } + else + { + throw new NotImplementedException(nvSetting.GetType().Name); + } + + Logger.Debug?.Print(LogClass.ServiceNv, $"Got setting {arguments.Domain}!{arguments.Parameter}"); + + arguments.Configuration = settingBuffer; + + return NvInternalResult.Success; + } + + // NOTE: This actually return NotAvailableInProduction but this is directly translated as a InvalidInput before returning the ioctl. + //return NvInternalResult.NotAvailableInProduction; + return NvInternalResult.InvalidInput; + } + + private NvInternalResult EventWait(ref EventWaitArguments arguments) + { + return EventWait(ref arguments.Fence, ref arguments.Value, arguments.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: true); + } + + private NvInternalResult EventWaitAsync(ref EventWaitArguments arguments) + { + return EventWait(ref arguments.Fence, ref arguments.Value, arguments.Timeout, isWaitEventAsyncCmd: true, isWaitEventCmd: false); + } + + private NvInternalResult EventRegister(ref uint userEventId) + { + lock (_events) + { + NvInternalResult result = EventUnregister(ref userEventId); + + if (result == NvInternalResult.Success) + { + _events[userEventId] = new NvHostEvent(_device.System.HostSyncpoint, userEventId, _device.System); + } + + return result; + } + + } + + private NvInternalResult EventUnregister(ref uint userEventId) + { + lock (_events) + { + if (userEventId >= EventsCount) + { + return NvInternalResult.InvalidInput; + } + + NvHostEvent hostEvent = _events[userEventId]; + + if (hostEvent == null) + { + return NvInternalResult.Success; + } + + if (hostEvent.State == NvHostEventState.Available || + hostEvent.State == NvHostEventState.Cancelled || + hostEvent.State == NvHostEventState.Signaled) + { + _events[userEventId].CloseEvent(Context); + _events[userEventId] = null; + + return NvInternalResult.Success; + } + + return NvInternalResult.Busy; + } + } + + private NvInternalResult EventKill(ref ulong eventMask) + { + lock (_events) + { + NvInternalResult result = NvInternalResult.Success; + + for (uint eventId = 0; eventId < EventsCount; eventId++) + { + if ((eventMask & (1UL << (int)eventId)) != 0) + { + NvInternalResult tmp = EventUnregister(ref eventId); + + if (tmp != NvInternalResult.Success) + { + result = tmp; + } + } + } + + return result; + } + } + + private NvInternalResult EventSignal(ref uint userEventId) + { + uint eventId = userEventId & ushort.MaxValue; + + if (eventId >= EventsCount) + { + return NvInternalResult.InvalidInput; + } + + lock (_events) + { + NvHostEvent hostEvent = _events[eventId]; + + if (hostEvent == null) + { + return NvInternalResult.InvalidInput; + } + + hostEvent.Cancel(_device.Gpu); + + _device.System.HostSyncpoint.UpdateMin(hostEvent.Fence.Id); + + return NvInternalResult.Success; + } + } + + private NvInternalResult SyncptReadMinOrMax(ref NvFence arguments, bool max) + { + if (arguments.Id >= SynchronizationManager.MaxHardwareSyncpoints) + { + return NvInternalResult.InvalidInput; + } + + if (max) + { + arguments.Value = _device.System.HostSyncpoint.ReadSyncpointMaxValue(arguments.Id); + } + else + { + arguments.Value = _device.System.HostSyncpoint.ReadSyncpointValue(arguments.Id); + } + + return NvInternalResult.Success; + } + + private NvInternalResult EventWait(ref NvFence fence, ref uint value, int timeout, bool isWaitEventAsyncCmd, bool isWaitEventCmd) + { + if (fence.Id >= SynchronizationManager.MaxHardwareSyncpoints) + { + return NvInternalResult.InvalidInput; + } + + // First try to check if the syncpoint is already expired on the CPU side + if (_device.System.HostSyncpoint.IsSyncpointExpired(fence.Id, fence.Value)) + { + value = _device.System.HostSyncpoint.ReadSyncpointMinValue(fence.Id); + + return NvInternalResult.Success; + } + + // Try to invalidate the CPU cache and check for expiration again. + uint newCachedSyncpointValue = _device.System.HostSyncpoint.UpdateMin(fence.Id); + + // Has the fence already expired? + if (_device.System.HostSyncpoint.IsSyncpointExpired(fence.Id, fence.Value)) + { + value = newCachedSyncpointValue; + + return NvInternalResult.Success; + } + + // If the timeout is 0, directly return. + if (timeout == 0) + { + return NvInternalResult.TryAgain; + } + + // The syncpoint value isn't at the fence yet, we need to wait. + + if (!isWaitEventAsyncCmd) + { + value = 0; + } + + NvHostEvent hostEvent; + + NvInternalResult result; + + uint eventIndex; + + lock (_events) + { + if (isWaitEventAsyncCmd) + { + eventIndex = value; + + if (eventIndex >= EventsCount) + { + return NvInternalResult.InvalidInput; + } + + hostEvent = _events[eventIndex]; + } + else + { + hostEvent = GetFreeEventLocked(fence.Id, out eventIndex); + } + + if (hostEvent != null) + { + lock (hostEvent.Lock) + { + if (hostEvent.State == NvHostEventState.Available || + hostEvent.State == NvHostEventState.Signaled || + hostEvent.State == NvHostEventState.Cancelled) + { + bool timedOut = hostEvent.Wait(_device.Gpu, fence); + + if (timedOut) + { + if (isWaitEventCmd) + { + value = ((fence.Id & 0xfff) << 16) | 0x10000000; + } + else + { + value = fence.Id << 4; + } + + value |= eventIndex; + + result = NvInternalResult.TryAgain; + } + else + { + value = fence.Value; + + return NvInternalResult.Success; + } + } + else + { + Logger.Error?.Print(LogClass.ServiceNv, $"Invalid Event at index {eventIndex} (isWaitEventAsyncCmd: {isWaitEventAsyncCmd}, isWaitEventCmd: {isWaitEventCmd})"); + + if (hostEvent != null) + { + Logger.Error?.Print(LogClass.ServiceNv, hostEvent.DumpState(_device.Gpu)); + } + + result = NvInternalResult.InvalidInput; + } + } + } + else + { + Logger.Error?.Print(LogClass.ServiceNv, $"Invalid Event at index {eventIndex} (isWaitEventAsyncCmd: {isWaitEventAsyncCmd}, isWaitEventCmd: {isWaitEventCmd})"); + + result = NvInternalResult.InvalidInput; + } + } + + return result; + } + + private NvHostEvent GetFreeEventLocked(uint id, out uint eventIndex) + { + eventIndex = EventsCount; + + uint nullIndex = EventsCount; + + for (uint index = 0; index < EventsCount; index++) + { + NvHostEvent Event = _events[index]; + + if (Event != null) + { + if (Event.State == NvHostEventState.Available || + Event.State == NvHostEventState.Signaled || + Event.State == NvHostEventState.Cancelled) + { + eventIndex = index; + + if (Event.Fence.Id == id) + { + return Event; + } + } + } + else if (nullIndex == EventsCount) + { + nullIndex = index; + } + } + + if (nullIndex < EventsCount) + { + eventIndex = nullIndex; + + EventRegister(ref eventIndex); + + return _events[nullIndex]; + } + + if (eventIndex < EventsCount) + { + return _events[eventIndex]; + } + + return null; + } + + public override void Close() + { + Logger.Warning?.Print(LogClass.ServiceNv, "Closing channel"); + + lock (_events) + { + // If the device file need to be closed, cancel all user events and dispose events. + for (int i = 0; i < _events.Length; i++) + { + NvHostEvent evnt = _events[i]; + + if (evnt != null) + { + lock (evnt.Lock) + { + if (evnt.State == NvHostEventState.Waiting) + { + evnt.State = NvHostEventState.Cancelling; + + evnt.Cancel(_device.Gpu); + } + else if (evnt.State == NvHostEventState.Signaling) + { + // Wait at max 9ms if the guest app is trying to signal the event while closing it.. + int retryCount = 0; + do + { + if (retryCount++ > 9) + { + break; + } + + // TODO: This should be handled by the kernel (reschedule the current thread ect), waiting for Kernel decoupling work. + Thread.Sleep(1); + } while (evnt.State != NvHostEventState.Signaled); + } + + evnt.CloseEvent(Context); + + _events[i] = null; + } + } + } + } + } + } +} |
