Implement PatrolReader

This commit is contained in:
Alex Barney 2022-07-07 19:57:53 -07:00
parent 4ffc834427
commit 0964ecb2ee
6 changed files with 523 additions and 18 deletions

View file

@ -25,7 +25,7 @@ public class DefaultFsServerObjects
var sdCard = new EmulatedSdCard();
var gameCardNew = new GameCardDummy();
var sdmmcNew = new SdmmcApi();
var sdmmcNew = new SdmmcApi(fsServer);
var gcStorageCreator = new EmulatedGameCardStorageCreator(gameCard);

73
src/LibHac/Os/Event.cs Normal file
View file

@ -0,0 +1,73 @@
using System;
namespace LibHac.Os;
public struct EventType
{
}
public class Event : IDisposable
{
private EventType _event;
private readonly OsState _os;
public Event(OsState os, EventClearMode clearMode)
{
_os = os;
_os.InitializeEvent(ref _event, signaled: false, clearMode);
}
public void Dispose() => _os.FinalizeEvent(ref _event);
public void Wait() => _os.WaitEvent(ref _event);
public bool TryWait() => _os.TryWaitEvent(ref _event);
public bool TimedWait(TimeSpan timeout) => _os.TimedWaitEvent(ref _event, timeout);
public void Signal() => _os.SignalEvent(ref _event);
public void Clear() => _os.ClearEvent(ref _event);
public ref EventType GetBase() => ref _event;
}
public static class EventApi
{
public static void InitializeEvent(this OsState os, ref EventType eventType, bool signaled,
EventClearMode clearMode)
{
throw new NotImplementedException();
}
public static void FinalizeEvent(this OsState os, ref EventType eventType)
{
throw new NotImplementedException();
}
public static void SignalEvent(this OsState os, ref EventType eventType)
{
throw new NotImplementedException();
}
public static void WaitEvent(this OsState os, ref EventType eventType)
{
throw new NotImplementedException();
}
public static bool TryWaitEvent(this OsState os, ref EventType eventType)
{
throw new NotImplementedException();
}
public static bool TimedWaitEvent(this OsState os, ref EventType eventType, TimeSpan timeout)
{
throw new NotImplementedException();
}
public static void ClearEvent(this OsState os, ref EventType eventType)
{
throw new NotImplementedException();
}
public static void InitializeMultiWaitHolder(this OsState os, ref MultiWaitHolderType multiWaitHolder,
ref EventType eventType)
{
throw new NotImplementedException();
}
}

View file

@ -0,0 +1,7 @@
namespace LibHac.Os;
public enum EventClearMode
{
ManualClear = 0,
AutoClear = 1
}

View file

@ -0,0 +1,85 @@
using System;
namespace LibHac.Os;
public struct TimerEventType
{
}
public class TimerEvent : IDisposable
{
private TimerEventType _event;
private readonly OsState _os;
public TimerEvent(OsState os, EventClearMode clearMode)
{
_os = os;
_os.InitializeTimerEvent(ref _event, clearMode);
}
public void Dispose() => _os.FinalizeTimerEvent(ref _event);
public void StartOneShot(TimeSpan firstTime) => _os.StartOneShotTimerEvent(ref _event, firstTime);
public void StartPeriodic(TimeSpan firstTime, TimeSpan interval) => _os.StartPeriodicTimerEvent(ref _event, firstTime, interval);
public void Stop() => _os.StopTimerEvent(ref _event);
public void Wait() => _os.WaitTimerEvent(ref _event);
public bool TryWait() => _os.TryWaitTimerEvent(ref _event);
public void Signal() => _os.SignalTimerEvent(ref _event);
public void Clear() => _os.ClearTimerEvent(ref _event);
public ref TimerEventType GetBase() => ref _event;
}
public static class TimerEventApi
{
public static void InitializeTimerEvent(this OsState os, ref TimerEventType eventType, EventClearMode clearMode)
{
throw new NotImplementedException();
}
public static void FinalizeTimerEvent(this OsState os, ref TimerEventType eventType)
{
throw new NotImplementedException();
}
public static void StartOneShotTimerEvent(this OsState os, ref TimerEventType eventType, TimeSpan firstTime)
{
throw new NotImplementedException();
}
public static void StartPeriodicTimerEvent(this OsState os, ref TimerEventType eventType, TimeSpan firstTime,
TimeSpan interval)
{
throw new NotImplementedException();
}
public static void StopTimerEvent(this OsState os, ref TimerEventType eventType)
{
throw new NotImplementedException();
}
public static void SignalTimerEvent(this OsState os, ref TimerEventType eventType)
{
throw new NotImplementedException();
}
public static void ClearTimerEvent(this OsState os, ref TimerEventType eventType)
{
throw new NotImplementedException();
}
public static void WaitTimerEvent(this OsState os, ref TimerEventType eventType)
{
throw new NotImplementedException();
}
public static bool TryWaitTimerEvent(this OsState os, ref TimerEventType eventType)
{
throw new NotImplementedException();
}
public static void InitializeMultiWaitHolder(this OsState os, ref MultiWaitHolderType multiWaitHolder,
ref TimerEventType eventType)
{
throw new NotImplementedException();
}
}

View file

@ -1,4 +1,5 @@
using System;
using LibHac.FsSrv;
namespace LibHac.Sdmmc;
@ -60,6 +61,14 @@ public partial class SdmmcApi
public const int DeviceCidSize = 0x10;
public const int DeviceCsdSize = 0x10;
private FileSystemServer _fsServer;
internal HorizonClient Hos => _fsServer.Hos;
public SdmmcApi(FileSystemServer fsServer)
{
_fsServer = fsServer;
}
public void SwitchToPcvClockResetControl()
{
throw new NotImplementedException();

View file

@ -1,73 +1,404 @@
using System;
using LibHac.Common;
using LibHac.Crypto;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.FsSystem;
using LibHac.Os;
using LibHac.Sdmmc;
using static LibHac.SdmmcSrv.Common;
using MmcPartition = LibHac.Sdmmc.MmcPartition;
namespace LibHac.SdmmcSrv;
/// <summary>
/// Performs regular patrol reads on the internal MMC.
/// </summary>
/// <remarks><para>The patrol reader will read sequential half-megabyte blocks of data from the internal MMC,
/// starting at the beginning of the storage, progressing to the end, and then looping back to the beginning.
/// This helps with data integrity by ensuring the entire MMC is regularly read.</para>
/// <para>Every 6 seconds the patrol reader will read a half-megabyte block from the MMC.
/// Every 2 hours it will save the current state of the patrol read to Boot Partition 1 on the MMC.
/// This state contains the next sector index to be read and the number of times the MMC has been patrolled
/// from start to finish.</para>
/// <para>Based on nnSdk 14.3.0 (FS 14.1.0)</para></remarks>
internal class PatrolReader
{
// Note: This class won't work until events and timer events are properly implemented.
private const int MacSize = HmacSha256.HashSize;
/// <summary>
/// The patrol reader state that's stored in Boot Partition 1.
/// </summary>
public struct State
{
public uint CurrentSector;
public uint PatrolCount;
}
public enum ThreadState
{
Stop,
Active,
Sleep
}
private readonly int _macSize;
private readonly MmcPartition _statePartition;
private readonly int _patrolStateOffset;
private readonly int _patrolStateSize;
private readonly int _saveStateIntervalSeconds;
private readonly int _patrolReadSize;
private readonly int _waitTimeAfterAllocationSuccess;
private readonly int _waitTimeAfterAllocationFailure;
private readonly int _firstRunDelaySeconds;
private uint _deviceCapacitySectors;
private State _patrolState;
private bool _isPatrolStateLoaded;
private SdkMutex _mutex;
private bool _areEventsInitialized;
private ThreadState _threadState;
// private Thread _patrolReaderThread;
private Event _stateChangeRequestedEvent;
private Event _stateChangeCompletedEvent;
private ulong _allocateSuccessCount;
private ulong _allocateFailureCount;
// LibHac addition
private SdmmcApi _sdmmc;
private static ReadOnlySpan<byte> PatrolStateKey => "U{W5>1Kq#Gt`f6r86o`9|*||hTy9U2C\0"u8;
public PatrolReader(SdkMutex mutex, SdmmcApi sdmmc)
{
throw new NotImplementedException();
_macSize = MacSize;
_statePartition = MmcPartition.BootPartition1;
_patrolStateOffset = 0x184000;
_patrolStateSize = 0x200;
_saveStateIntervalSeconds = 7200;
_patrolReadSize = 0x80000;
_waitTimeAfterAllocationSuccess = 6;
_waitTimeAfterAllocationFailure = 1;
_firstRunDelaySeconds = 18;
_isPatrolStateLoaded = false;
_mutex = mutex;
_areEventsInitialized = false;
_threadState = ThreadState.Stop;
_allocateSuccessCount = 0;
_allocateFailureCount = 0;
_sdmmc = sdmmc;
}
public void Dispose()
{
throw new NotImplementedException();
FinalizeObject();
}
public static void PatrolReaderThreadEntry(object args)
{
throw new NotImplementedException();
if (args is PatrolReader reader)
{
reader.PatrolReaderThread();
}
else
{
Abort.DoAbort($"Expected an argument of type {nameof(PatrolReader)} in {nameof(PatrolReaderThreadEntry)}");
}
}
public void FinalizeObject()
private void FinalizeObject()
{
throw new NotImplementedException();
if (_areEventsInitialized)
{
if (_threadState == ThreadState.Sleep)
{
Resume();
}
if (_threadState == ThreadState.Active)
{
Stop();
}
_stateChangeRequestedEvent.Dispose();
_stateChangeCompletedEvent.Dispose();
_areEventsInitialized = false;
}
}
public Result GetPatrolCount(out uint outCount)
{
throw new NotImplementedException();
UnsafeHelpers.SkipParamInit(out outCount);
if (!_isPatrolStateLoaded)
return ResultFs.HasNotGottenPatrolCount.Log();
outCount = _patrolState.PatrolCount;
return Result.Success;
}
public void GetAndClearAllocateCount(out ulong outSuccessCount, out ulong outFailureCount)
{
throw new NotImplementedException();
using ScopedLock<SdkMutex> scopedLock = ScopedLock.Lock(ref _mutex);
outSuccessCount = _allocateSuccessCount;
outFailureCount = _allocateFailureCount;
_allocateSuccessCount = 0;
_allocateFailureCount = 0;
}
public bool LoadState()
private bool LoadState()
{
throw new NotImplementedException();
using var pooledBuffer = new PooledBuffer(_patrolStateSize, 1);
if (pooledBuffer.GetSize() < _patrolStateSize)
return false;
using ScopedLock<SdkMutex> scopedLock = ScopedLock.Lock(ref _mutex);
Abort.DoAbortUnlessSuccess(_sdmmc.SelectMmcPartition(Port.Mmc0, _statePartition));
try
{
Result res = _sdmmc.GetDeviceMemoryCapacity(out _deviceCapacitySectors, Port.Mmc0);
if (res.IsFailure())
{
return false;
}
Span<byte> patrolStateBuffer = pooledBuffer.GetBuffer().Slice(0, _patrolStateSize);
res = _sdmmc.Read(patrolStateBuffer, Port.Mmc0, BytesToSectors(_patrolStateOffset),
BytesToSectors(_patrolStateSize));
if (res.IsFailure())
{
return false;
}
Span<byte> mac = stackalloc byte[MacSize];
// Load an empty state if the verification fails.
_patrolState = default;
HmacSha256.GenerateHmacSha256(mac, patrolStateBuffer.Slice(_macSize), PatrolStateKey);
if (CryptoUtil.IsSameBytes(mac, patrolStateBuffer, _macSize))
{
ref State readState = ref SpanHelpers.AsStruct<State>(patrolStateBuffer.Slice(_macSize));
if (readState.CurrentSector + BytesToSectors(_patrolReadSize) <= _deviceCapacitySectors)
{
_patrolState = readState;
}
}
return true;
}
finally
{
Abort.DoAbortUnlessSuccess(_sdmmc.SelectMmcPartition(Port.Mmc0, MmcPartition.UserData));
}
}
public bool SaveState()
private void SaveState()
{
throw new NotImplementedException();
using var pooledBuffer = new PooledBuffer(_patrolStateSize, 1);
if (pooledBuffer.GetSize() < _patrolStateSize)
return;
using ScopedLock<SdkMutex> scopedLock = ScopedLock.Lock(ref _mutex);
Span<byte> patrolStateBuffer = pooledBuffer.GetBuffer().Slice(0, _patrolStateSize);
patrolStateBuffer.Clear();
SpanHelpers.AsStruct<State>(patrolStateBuffer) = _patrolState;
HmacSha256.GenerateHmacSha256(patrolStateBuffer.Slice(0, _macSize), patrolStateBuffer.Slice(_macSize),
PatrolStateKey);
Abort.DoAbortUnlessSuccess(_sdmmc.SelectMmcPartition(Port.Mmc0, _statePartition));
try
{
_sdmmc.Write(Port.Mmc0, BytesToSectors(_patrolStateOffset), BytesToSectors(_patrolStateSize),
patrolStateBuffer);
}
finally
{
Abort.DoAbortUnlessSuccess(_sdmmc.SelectMmcPartition(Port.Mmc0, MmcPartition.UserData));
}
}
private bool DoPatrolRead()
{
using var pooledBuffer = new PooledBuffer(_patrolReadSize, 1);
using ScopedLock<SdkMutex> scopedLock = ScopedLock.Lock(ref _mutex);
if (pooledBuffer.GetSize() < _patrolReadSize)
{
if (_allocateFailureCount != ulong.MaxValue)
_allocateFailureCount++;
return false;
}
if (_allocateSuccessCount != ulong.MaxValue)
_allocateSuccessCount++;
_sdmmc.Read(pooledBuffer.GetBuffer().Slice(0, _patrolReadSize), Port.Mmc0, _patrolState.CurrentSector,
BytesToSectors(_patrolReadSize)).IgnoreResult();
_patrolState.CurrentSector += BytesToSectors(_patrolReadSize);
if (_patrolState.CurrentSector + BytesToSectors(_patrolReadSize) > _deviceCapacitySectors)
{
_patrolState.CurrentSector = 0;
_patrolState.PatrolCount++;
}
return true;
}
public void Start()
{
throw new NotImplementedException();
Assert.SdkAssert(_threadState is ThreadState.Active or ThreadState.Stop);
if (_threadState == ThreadState.Stop)
{
if (!_areEventsInitialized)
{
_areEventsInitialized = true;
_threadState = ThreadState.Stop;
_stateChangeRequestedEvent = new Event(_sdmmc.Hos.Os, EventClearMode.AutoClear);
_stateChangeCompletedEvent = new Event(_sdmmc.Hos.Os, EventClearMode.AutoClear);
}
_threadState = ThreadState.Active;
// CreateThread(_patrolReaderThread, PatrolReaderThreadEntry, this, pPatrolReaderStack, priority);
// SetThreadNamePointer(_patrolReaderThread, "nn.fs.PatrolReader"u8);
// StartThread(_patrolReaderThread);
}
}
public void Stop()
{
throw new NotImplementedException();
Assert.SdkAssert(_threadState is ThreadState.Active or ThreadState.Stop or ThreadState.Sleep);
if (_threadState != ThreadState.Stop)
{
_threadState = ThreadState.Stop;
_stateChangeRequestedEvent.Signal();
// WaitThread(_patrolReaderThread);
// DestroyThread(_patrolReaderThread);
}
}
public void Sleep()
{
throw new NotImplementedException();
Assert.SdkAssert(_threadState is ThreadState.Active or ThreadState.Sleep);
if (_threadState == ThreadState.Active)
{
_threadState = ThreadState.Sleep;
_stateChangeRequestedEvent.Signal();
_stateChangeCompletedEvent.Wait();
}
}
public void Resume()
{
throw new NotImplementedException();
Assert.SdkAssert(_threadState is ThreadState.Active or ThreadState.Sleep);
if (_threadState == ThreadState.Sleep)
{
_threadState = ThreadState.Active;
_stateChangeRequestedEvent.Signal();
_stateChangeCompletedEvent.Wait();
}
}
public void PatrolReaderThread()
private void PatrolReaderThread()
{
throw new NotImplementedException();
// Missing: SetServiceContext()
using var timer = new TimerEvent(_sdmmc.Hos.Os, EventClearMode.AutoClear);
timer.StartPeriodic(TimeSpan.FromSeconds(_firstRunDelaySeconds), TimeSpan.FromSeconds(_saveStateIntervalSeconds));
int currentWaitTime = _waitTimeAfterAllocationSuccess;
while (true)
{
// Wait until the next thread state change or until the next patrol read should be done.
if (_stateChangeRequestedEvent.TimedWait(TimeSpan.FromSeconds(currentWaitTime)))
{
if (_threadState == ThreadState.Stop)
{
break;
}
if (_threadState == ThreadState.Sleep)
{
// Acknowledge the request to sleep and wait for the next state change.
_stateChangeCompletedEvent.Signal();
_stateChangeRequestedEvent.Wait();
if (_threadState == ThreadState.Stop)
{
break;
}
if (_threadState == ThreadState.Active)
{
_stateChangeCompletedEvent.Signal();
continue;
}
Assert.SdkAssert(false);
}
}
if (!_isPatrolStateLoaded)
{
// The patrol state will be loaded a single time when the console is booted.
// Don't load the patrol state or do patrol reads until the specified amount
// of time has past since creating the Patrol Reader.
if (!timer.TryWait() || !LoadState())
{
continue;
}
_isPatrolStateLoaded = true;
}
if (DoPatrolRead())
{
currentWaitTime = _waitTimeAfterAllocationSuccess;
}
else
{
currentWaitTime = _waitTimeAfterAllocationFailure;
}
// Save the patrol state periodically once the specified amount of time has passed since the last save.
if (timer.TryWait())
{
SaveState();
}
}
// Save the patrol state if necessary when shutting down.
if (_isPatrolStateLoaded)
SaveState();
}
}