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