1 // 2 // Copyright (c) 2010-2023 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 using System; 8 using Antmicro.Renode.Core; 9 using Antmicro.Renode.Peripherals.Bus; 10 using Antmicro.Renode.Core.Structure.Registers; 11 using Antmicro.Renode.Time; 12 using Antmicro.Renode.Logging; 13 14 namespace Antmicro.Renode.Peripherals.Timers 15 { 16 public class MPFS_Watchdog : BasicDoubleWordPeripheral, IKnownSize 17 { MPFS_Watchdog(IMachine machine, long frequency)18 public MPFS_Watchdog(IMachine machine, long frequency) : base(machine) 19 { 20 DefineRegisters(); 21 internalTimer = new LimitTimer(machine.ClockSource, frequency, this, String.Empty, workMode: WorkMode.OneShot, eventEnabled: true); 22 internalTimer.LimitReached += TimerLimitReached; 23 24 RefreshEnable = new GPIO(); 25 Trigger = new GPIO(); 26 } 27 Reset()28 public override void Reset() 29 { 30 base.Reset(); 31 Trigger.Unset(); 32 RefreshEnable.Unset(); 33 state = State.ForbiddenRegion; 34 internalTimer.Reset(); 35 } 36 37 public long Size => 0x1000; 38 39 //TODO: Locking of registers. `locked` field has a correct value, but it is not actively used DefineRegisters()40 private void DefineRegisters() 41 { 42 Registers.Refresh.Define(this) 43 .WithValueField(0, 32, writeCallback: (_, value) => 44 { 45 if(!internalTimer.Enabled) 46 { 47 //this `if` is a microoptimisation, but as it may happen very frequently and writing to LimitTimer is expensive, I'd like to keep it 48 this.Log(LogLevel.Debug, "Starting watchdog."); 49 internalTimer.Enabled = true; 50 SetState(State.ForbiddenRegion); 51 } 52 else if(state == State.ForbiddenRegion && forbiddenRangeEnabled.Value) 53 { 54 this.Log(LogLevel.Warning, "Watchdog refreshed in forbidden region, triggering NMI."); 55 SetState(State.AfterTrigger); 56 } 57 else if((state == State.RefreshRegion || (state == State.ForbiddenRegion && !forbiddenRangeEnabled.Value)) && value == WatchdogReset) 58 { 59 this.Log(LogLevel.Noisy, "Refreshing watchdog."); 60 SetState(State.ForbiddenRegion); 61 } 62 }, valueProviderCallback: _ => GetCurrentTimerValue(), name: "REFRESH") 63 .WithWriteCallback((_, __) => locked.Value = true); 64 ; 65 66 Registers.Control.Define(this, 2) 67 .WithFlag(0, out refreshInterruptEnabled, changeCallback: (_, __) => Update(), name: "INTEN_MSVP") 68 .WithFlag(1, FieldMode.Read, valueProviderCallback: _ => true, name: "INTEN_TRIG") 69 .WithTaggedFlag("INTEN_SLEEP", 2) 70 .WithTaggedFlag("ACTIVE_SLEEP", 3) 71 .WithFlag(4, out forbiddenRangeEnabled, name: "ENABLE_FORBIDDEN") 72 .WithWriteCallback((_, __) => locked.Value = true); 73 ; 74 75 Registers.Status.Define(this) 76 .WithFlag(0, out refreshPermittedLevelTripped, FieldMode.Read | FieldMode.WriteOneToClear, name: "MVRP_TRIPPED") 77 .WithFlag(1, out watchdogTripped, FieldMode.Read | FieldMode.WriteOneToClear, name: "WDOG_TRIPPED") 78 .WithFlag(2, FieldMode.Read, valueProviderCallback: _ => forbiddenRangeEnabled.Value && state == State.ForbiddenRegion, name: "FORBIDDEN") 79 .WithFlag(3, FieldMode.Read, valueProviderCallback: _ => state == State.AfterTrigger, name: "TRIGGERED") 80 .WithFlag(4, out locked, FieldMode.Read, name: "LOCKED") 81 .WithTaggedFlag("DEVRST", 5) 82 .WithWriteCallback((_, __) => { locked.Value = true; Update(); }); 83 ; 84 85 Registers.Time.Define(this, TimeDefault) 86 .WithValueField(0, 24, out time, name: "WDOGTIME") 87 .WithWriteCallback((_, __) => locked.Value = true); 88 ; 89 90 Registers.MSVP.Define(this, MSVPDefault) 91 .WithValueField(0, 24, out maximumValueForWhichRefreshIsPermitted, name: "WDOGMVRP") 92 ; 93 94 Registers.Trigger.Define(this, TriggerDefault) 95 .WithValueField(0, 12, out triggerValue, name: "WDOGTRIG") 96 ; 97 98 Registers.Force.Define(this) 99 .WithValueField(0, 16, FieldMode.Write, writeCallback: (_, value) => 100 { 101 if(state == State.AfterTrigger && value == ResetTrigger) 102 { 103 TriggerReset(); 104 } 105 else 106 { 107 SetState(State.AfterTrigger); 108 } 109 }, name: "FORCE") 110 //The line below is controversial, as the docs are self contradictory - "write to any other register" against "write from 0x0 to 0xc"). 111 //Elsewhere it is mentioned that write to TIME blocks MSVP and TRIGGER registers. 112 //Elsewhere it says that the registers are blocked when MVRP is passed for the first time, but the timer is not enabled before the first refresh... 113 .WithWriteCallback((_, __) => locked.Value = true); 114 ; 115 } 116 TimerLimitReached()117 private void TimerLimitReached() 118 { 119 this.Log(LogLevel.Noisy, "Watchdog event reached in state {0}.", state); 120 if(state == State.AfterTrigger) 121 { 122 TriggerReset(); 123 } 124 else 125 { 126 //ForbiddenRegion -> RefreshRegion, RefreshRegion -> AfterTrigger 127 SetState(state + 1); 128 } 129 } 130 TriggerReset()131 private void TriggerReset() 132 { 133 this.Log(LogLevel.Warning, "Watchdog reset triggered!"); 134 machine.RequestReset(); 135 } 136 GetCurrentTimerValue()137 private uint GetCurrentTimerValue() 138 { 139 var rest = 0u; 140 switch(state) 141 { 142 case State.ForbiddenRegion: 143 rest = (uint)maximumValueForWhichRefreshIsPermitted.Value; 144 break; 145 case State.RefreshRegion: 146 rest = (uint)triggerValue.Value; 147 break; 148 //intentionally no State.AfterTrigger, rest = 0 149 } 150 return (uint)internalTimer.Value + rest; 151 } 152 SetState(State state)153 private void SetState(State state) 154 { 155 this.Log(LogLevel.Noisy, "Switching state to {0}.", state); 156 this.state = state; 157 switch(state) 158 { 159 case State.ForbiddenRegion: 160 internalTimer.Limit = time.Value - maximumValueForWhichRefreshIsPermitted.Value; 161 refreshPermittedLevelTripped.Value = false; 162 watchdogTripped.Value = false; 163 break; 164 case State.RefreshRegion: 165 refreshPermittedLevelTripped.Value = true; 166 watchdogTripped.Value = false; 167 internalTimer.Limit = maximumValueForWhichRefreshIsPermitted.Value - triggerValue.Value; 168 break; 169 case State.AfterTrigger: 170 watchdogTripped.Value = true; 171 internalTimer.Limit = triggerValue.Value; 172 break; 173 default: 174 throw new ArgumentException("Trying to set invalid watchdog state.", nameof(state)); 175 } 176 //We do this to get proper info in GetClockSourceInfo (need to set Limit) and because we might change state by writing registers 177 internalTimer.Value = internalTimer.Limit; 178 internalTimer.Enabled = true; 179 Update(); 180 } 181 Update()182 private void Update() 183 { 184 Trigger.Set(watchdogTripped.Value); 185 //not sure about this. Should we set it when we refresh in the forbidden region and jump to AfterTrigger? 186 RefreshEnable.Set(refreshInterruptEnabled.Value && refreshPermittedLevelTripped.Value); 187 this.Log(LogLevel.Noisy, "Sending interrupts, RefreshEnable: {0}, Trigger: {1}", RefreshEnable.IsSet, Trigger.IsSet); 188 } 189 190 public GPIO Trigger { get; } 191 public GPIO RefreshEnable { get; } 192 193 private LimitTimer internalTimer; 194 private State state; 195 196 private IFlagRegisterField locked; 197 private IValueRegisterField time; 198 private IValueRegisterField maximumValueForWhichRefreshIsPermitted; 199 private IValueRegisterField triggerValue; 200 private IFlagRegisterField refreshInterruptEnabled; 201 private IFlagRegisterField forbiddenRangeEnabled; 202 private IFlagRegisterField refreshPermittedLevelTripped; 203 private IFlagRegisterField watchdogTripped; 204 205 private const uint ResetTrigger = 0xDEAD; 206 private const uint WatchdogReset = 0xDEADC0DE; 207 private const uint TimeDefault = 30000; 208 private const uint MSVPDefault = 15000; 209 private const uint TriggerDefault = 1000; 210 211 private enum State 212 { 213 ForbiddenRegion, 214 RefreshRegion, 215 AfterTrigger 216 } 217 218 private enum Registers 219 { 220 Refresh = 0x0, 221 Control = 0x4, 222 Status = 0x8, 223 Time = 0xC, 224 //I'm not sure how to expand this name 225 MSVP = 0x10, 226 Trigger = 0x14, 227 Force = 0x18 228 } 229 } 230 } 231