xref: /qemu/hw/watchdog/sbsa_gwdt.c (revision 4204c5f70360dc1e527e65eb225d0688993fdcef)
1*4204c5f7SShashi Mallela /*
2*4204c5f7SShashi Mallela  * Generic watchdog device model for SBSA
3*4204c5f7SShashi Mallela  *
4*4204c5f7SShashi Mallela  * The watchdog device has been implemented as revision 1 variant of
5*4204c5f7SShashi Mallela  * the ARM SBSA specification v6.0
6*4204c5f7SShashi Mallela  * (https://developer.arm.com/documentation/den0029/d?lang=en)
7*4204c5f7SShashi Mallela  *
8*4204c5f7SShashi Mallela  * Copyright Linaro.org 2020
9*4204c5f7SShashi Mallela  *
10*4204c5f7SShashi Mallela  * Authors:
11*4204c5f7SShashi Mallela  *  Shashi Mallela <shashi.mallela@linaro.org>
12*4204c5f7SShashi Mallela  *
13*4204c5f7SShashi Mallela  * This work is licensed under the terms of the GNU GPL, version 2 or (at your
14*4204c5f7SShashi Mallela  * option) any later version.  See the COPYING file in the top-level directory.
15*4204c5f7SShashi Mallela  *
16*4204c5f7SShashi Mallela  */
17*4204c5f7SShashi Mallela 
18*4204c5f7SShashi Mallela #include "qemu/osdep.h"
19*4204c5f7SShashi Mallela #include "sysemu/reset.h"
20*4204c5f7SShashi Mallela #include "sysemu/watchdog.h"
21*4204c5f7SShashi Mallela #include "hw/watchdog/sbsa_gwdt.h"
22*4204c5f7SShashi Mallela #include "qemu/timer.h"
23*4204c5f7SShashi Mallela #include "migration/vmstate.h"
24*4204c5f7SShashi Mallela #include "qemu/log.h"
25*4204c5f7SShashi Mallela #include "qemu/module.h"
26*4204c5f7SShashi Mallela 
27*4204c5f7SShashi Mallela static WatchdogTimerModel model = {
28*4204c5f7SShashi Mallela     .wdt_name = TYPE_WDT_SBSA,
29*4204c5f7SShashi Mallela     .wdt_description = "SBSA-compliant generic watchdog device",
30*4204c5f7SShashi Mallela };
31*4204c5f7SShashi Mallela 
32*4204c5f7SShashi Mallela static const VMStateDescription vmstate_sbsa_gwdt = {
33*4204c5f7SShashi Mallela     .name = "sbsa-gwdt",
34*4204c5f7SShashi Mallela     .version_id = 1,
35*4204c5f7SShashi Mallela     .minimum_version_id = 1,
36*4204c5f7SShashi Mallela     .fields = (VMStateField[]) {
37*4204c5f7SShashi Mallela         VMSTATE_TIMER_PTR(timer, SBSA_GWDTState),
38*4204c5f7SShashi Mallela         VMSTATE_UINT32(wcs, SBSA_GWDTState),
39*4204c5f7SShashi Mallela         VMSTATE_UINT32(worl, SBSA_GWDTState),
40*4204c5f7SShashi Mallela         VMSTATE_UINT32(woru, SBSA_GWDTState),
41*4204c5f7SShashi Mallela         VMSTATE_UINT32(wcvl, SBSA_GWDTState),
42*4204c5f7SShashi Mallela         VMSTATE_UINT32(wcvu, SBSA_GWDTState),
43*4204c5f7SShashi Mallela         VMSTATE_END_OF_LIST()
44*4204c5f7SShashi Mallela     }
45*4204c5f7SShashi Mallela };
46*4204c5f7SShashi Mallela 
47*4204c5f7SShashi Mallela typedef enum WdtRefreshType {
48*4204c5f7SShashi Mallela     EXPLICIT_REFRESH = 0,
49*4204c5f7SShashi Mallela     TIMEOUT_REFRESH = 1,
50*4204c5f7SShashi Mallela } WdtRefreshType;
51*4204c5f7SShashi Mallela 
52*4204c5f7SShashi Mallela static uint64_t sbsa_gwdt_rread(void *opaque, hwaddr addr, unsigned int size)
53*4204c5f7SShashi Mallela {
54*4204c5f7SShashi Mallela     SBSA_GWDTState *s = SBSA_GWDT(opaque);
55*4204c5f7SShashi Mallela     uint32_t ret = 0;
56*4204c5f7SShashi Mallela 
57*4204c5f7SShashi Mallela     switch (addr) {
58*4204c5f7SShashi Mallela     case SBSA_GWDT_WRR:
59*4204c5f7SShashi Mallela         /* watch refresh read has no effect and returns 0 */
60*4204c5f7SShashi Mallela         ret = 0;
61*4204c5f7SShashi Mallela         break;
62*4204c5f7SShashi Mallela     case SBSA_GWDT_W_IIDR:
63*4204c5f7SShashi Mallela         ret = s->id;
64*4204c5f7SShashi Mallela         break;
65*4204c5f7SShashi Mallela     default:
66*4204c5f7SShashi Mallela         qemu_log_mask(LOG_GUEST_ERROR, "bad address in refresh frame read :"
67*4204c5f7SShashi Mallela                         " 0x%x\n", (int)addr);
68*4204c5f7SShashi Mallela     }
69*4204c5f7SShashi Mallela     return ret;
70*4204c5f7SShashi Mallela }
71*4204c5f7SShashi Mallela 
72*4204c5f7SShashi Mallela static uint64_t sbsa_gwdt_read(void *opaque, hwaddr addr, unsigned int size)
73*4204c5f7SShashi Mallela {
74*4204c5f7SShashi Mallela     SBSA_GWDTState *s = SBSA_GWDT(opaque);
75*4204c5f7SShashi Mallela     uint32_t ret = 0;
76*4204c5f7SShashi Mallela 
77*4204c5f7SShashi Mallela     switch (addr) {
78*4204c5f7SShashi Mallela     case SBSA_GWDT_WCS:
79*4204c5f7SShashi Mallela         ret = s->wcs;
80*4204c5f7SShashi Mallela         break;
81*4204c5f7SShashi Mallela     case SBSA_GWDT_WOR:
82*4204c5f7SShashi Mallela         ret = s->worl;
83*4204c5f7SShashi Mallela         break;
84*4204c5f7SShashi Mallela     case SBSA_GWDT_WORU:
85*4204c5f7SShashi Mallela          ret = s->woru;
86*4204c5f7SShashi Mallela          break;
87*4204c5f7SShashi Mallela     case SBSA_GWDT_WCV:
88*4204c5f7SShashi Mallela         ret = s->wcvl;
89*4204c5f7SShashi Mallela         break;
90*4204c5f7SShashi Mallela     case SBSA_GWDT_WCVU:
91*4204c5f7SShashi Mallela         ret = s->wcvu;
92*4204c5f7SShashi Mallela         break;
93*4204c5f7SShashi Mallela     case SBSA_GWDT_W_IIDR:
94*4204c5f7SShashi Mallela         ret = s->id;
95*4204c5f7SShashi Mallela         break;
96*4204c5f7SShashi Mallela     default:
97*4204c5f7SShashi Mallela         qemu_log_mask(LOG_GUEST_ERROR, "bad address in control frame read :"
98*4204c5f7SShashi Mallela                         " 0x%x\n", (int)addr);
99*4204c5f7SShashi Mallela     }
100*4204c5f7SShashi Mallela     return ret;
101*4204c5f7SShashi Mallela }
102*4204c5f7SShashi Mallela 
103*4204c5f7SShashi Mallela static void sbsa_gwdt_update_timer(SBSA_GWDTState *s, WdtRefreshType rtype)
104*4204c5f7SShashi Mallela {
105*4204c5f7SShashi Mallela     uint64_t timeout = 0;
106*4204c5f7SShashi Mallela 
107*4204c5f7SShashi Mallela     timer_del(s->timer);
108*4204c5f7SShashi Mallela 
109*4204c5f7SShashi Mallela     if (s->wcs & SBSA_GWDT_WCS_EN) {
110*4204c5f7SShashi Mallela         /*
111*4204c5f7SShashi Mallela          * Extract the upper 16 bits from woru & 32 bits from worl
112*4204c5f7SShashi Mallela          * registers to construct the 48 bit offset value
113*4204c5f7SShashi Mallela          */
114*4204c5f7SShashi Mallela         timeout = s->woru;
115*4204c5f7SShashi Mallela         timeout <<= 32;
116*4204c5f7SShashi Mallela         timeout |= s->worl;
117*4204c5f7SShashi Mallela         timeout = muldiv64(timeout, NANOSECONDS_PER_SECOND, SBSA_TIMER_FREQ);
118*4204c5f7SShashi Mallela         timeout += qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
119*4204c5f7SShashi Mallela 
120*4204c5f7SShashi Mallela         if ((rtype == EXPLICIT_REFRESH) || ((rtype == TIMEOUT_REFRESH) &&
121*4204c5f7SShashi Mallela                 (!(s->wcs & SBSA_GWDT_WCS_WS0)))) {
122*4204c5f7SShashi Mallela             /* store the current timeout value into compare registers */
123*4204c5f7SShashi Mallela             s->wcvu = timeout >> 32;
124*4204c5f7SShashi Mallela             s->wcvl = timeout;
125*4204c5f7SShashi Mallela         }
126*4204c5f7SShashi Mallela         timer_mod(s->timer, timeout);
127*4204c5f7SShashi Mallela     }
128*4204c5f7SShashi Mallela }
129*4204c5f7SShashi Mallela 
130*4204c5f7SShashi Mallela static void sbsa_gwdt_rwrite(void *opaque, hwaddr offset, uint64_t data,
131*4204c5f7SShashi Mallela                              unsigned size) {
132*4204c5f7SShashi Mallela     SBSA_GWDTState *s = SBSA_GWDT(opaque);
133*4204c5f7SShashi Mallela 
134*4204c5f7SShashi Mallela     if (offset == SBSA_GWDT_WRR) {
135*4204c5f7SShashi Mallela         s->wcs &= ~(SBSA_GWDT_WCS_WS0 | SBSA_GWDT_WCS_WS1);
136*4204c5f7SShashi Mallela 
137*4204c5f7SShashi Mallela         sbsa_gwdt_update_timer(s, EXPLICIT_REFRESH);
138*4204c5f7SShashi Mallela     } else {
139*4204c5f7SShashi Mallela         qemu_log_mask(LOG_GUEST_ERROR, "bad address in refresh frame write :"
140*4204c5f7SShashi Mallela                         " 0x%x\n", (int)offset);
141*4204c5f7SShashi Mallela     }
142*4204c5f7SShashi Mallela }
143*4204c5f7SShashi Mallela 
144*4204c5f7SShashi Mallela static void sbsa_gwdt_write(void *opaque, hwaddr offset, uint64_t data,
145*4204c5f7SShashi Mallela                              unsigned size) {
146*4204c5f7SShashi Mallela     SBSA_GWDTState *s = SBSA_GWDT(opaque);
147*4204c5f7SShashi Mallela 
148*4204c5f7SShashi Mallela     switch (offset) {
149*4204c5f7SShashi Mallela     case SBSA_GWDT_WCS:
150*4204c5f7SShashi Mallela         s->wcs = data & SBSA_GWDT_WCS_EN;
151*4204c5f7SShashi Mallela         qemu_set_irq(s->irq, 0);
152*4204c5f7SShashi Mallela         sbsa_gwdt_update_timer(s, EXPLICIT_REFRESH);
153*4204c5f7SShashi Mallela         break;
154*4204c5f7SShashi Mallela 
155*4204c5f7SShashi Mallela     case SBSA_GWDT_WOR:
156*4204c5f7SShashi Mallela         s->worl = data;
157*4204c5f7SShashi Mallela         s->wcs &= ~(SBSA_GWDT_WCS_WS0 | SBSA_GWDT_WCS_WS1);
158*4204c5f7SShashi Mallela         qemu_set_irq(s->irq, 0);
159*4204c5f7SShashi Mallela         sbsa_gwdt_update_timer(s, EXPLICIT_REFRESH);
160*4204c5f7SShashi Mallela         break;
161*4204c5f7SShashi Mallela 
162*4204c5f7SShashi Mallela     case SBSA_GWDT_WORU:
163*4204c5f7SShashi Mallela         s->woru = data & SBSA_GWDT_WOR_MASK;
164*4204c5f7SShashi Mallela         s->wcs &= ~(SBSA_GWDT_WCS_WS0 | SBSA_GWDT_WCS_WS1);
165*4204c5f7SShashi Mallela         qemu_set_irq(s->irq, 0);
166*4204c5f7SShashi Mallela         sbsa_gwdt_update_timer(s, EXPLICIT_REFRESH);
167*4204c5f7SShashi Mallela         break;
168*4204c5f7SShashi Mallela 
169*4204c5f7SShashi Mallela     case SBSA_GWDT_WCV:
170*4204c5f7SShashi Mallela         s->wcvl = data;
171*4204c5f7SShashi Mallela         break;
172*4204c5f7SShashi Mallela 
173*4204c5f7SShashi Mallela     case SBSA_GWDT_WCVU:
174*4204c5f7SShashi Mallela         s->wcvu = data;
175*4204c5f7SShashi Mallela         break;
176*4204c5f7SShashi Mallela 
177*4204c5f7SShashi Mallela     default:
178*4204c5f7SShashi Mallela         qemu_log_mask(LOG_GUEST_ERROR, "bad address in control frame write :"
179*4204c5f7SShashi Mallela                 " 0x%x\n", (int)offset);
180*4204c5f7SShashi Mallela     }
181*4204c5f7SShashi Mallela     return;
182*4204c5f7SShashi Mallela }
183*4204c5f7SShashi Mallela 
184*4204c5f7SShashi Mallela static void wdt_sbsa_gwdt_reset(DeviceState *dev)
185*4204c5f7SShashi Mallela {
186*4204c5f7SShashi Mallela     SBSA_GWDTState *s = SBSA_GWDT(dev);
187*4204c5f7SShashi Mallela 
188*4204c5f7SShashi Mallela     timer_del(s->timer);
189*4204c5f7SShashi Mallela 
190*4204c5f7SShashi Mallela     s->wcs  = 0;
191*4204c5f7SShashi Mallela     s->wcvl = 0;
192*4204c5f7SShashi Mallela     s->wcvu = 0;
193*4204c5f7SShashi Mallela     s->worl = 0;
194*4204c5f7SShashi Mallela     s->woru = 0;
195*4204c5f7SShashi Mallela     s->id = SBSA_GWDT_ID;
196*4204c5f7SShashi Mallela }
197*4204c5f7SShashi Mallela 
198*4204c5f7SShashi Mallela static void sbsa_gwdt_timer_sysinterrupt(void *opaque)
199*4204c5f7SShashi Mallela {
200*4204c5f7SShashi Mallela     SBSA_GWDTState *s = SBSA_GWDT(opaque);
201*4204c5f7SShashi Mallela 
202*4204c5f7SShashi Mallela     if (!(s->wcs & SBSA_GWDT_WCS_WS0)) {
203*4204c5f7SShashi Mallela         s->wcs |= SBSA_GWDT_WCS_WS0;
204*4204c5f7SShashi Mallela         sbsa_gwdt_update_timer(s, TIMEOUT_REFRESH);
205*4204c5f7SShashi Mallela         qemu_set_irq(s->irq, 1);
206*4204c5f7SShashi Mallela     } else {
207*4204c5f7SShashi Mallela         s->wcs |= SBSA_GWDT_WCS_WS1;
208*4204c5f7SShashi Mallela         qemu_log_mask(CPU_LOG_RESET, "Watchdog timer expired.\n");
209*4204c5f7SShashi Mallela         /*
210*4204c5f7SShashi Mallela          * Reset the watchdog only if the guest gets notified about
211*4204c5f7SShashi Mallela          * expiry. watchdog_perform_action() may temporarily relinquish
212*4204c5f7SShashi Mallela          * the BQL; reset before triggering the action to avoid races with
213*4204c5f7SShashi Mallela          * sbsa_gwdt instructions.
214*4204c5f7SShashi Mallela          */
215*4204c5f7SShashi Mallela         switch (get_watchdog_action()) {
216*4204c5f7SShashi Mallela         case WATCHDOG_ACTION_DEBUG:
217*4204c5f7SShashi Mallela         case WATCHDOG_ACTION_NONE:
218*4204c5f7SShashi Mallela         case WATCHDOG_ACTION_PAUSE:
219*4204c5f7SShashi Mallela             break;
220*4204c5f7SShashi Mallela         default:
221*4204c5f7SShashi Mallela             wdt_sbsa_gwdt_reset(DEVICE(s));
222*4204c5f7SShashi Mallela         }
223*4204c5f7SShashi Mallela         watchdog_perform_action();
224*4204c5f7SShashi Mallela     }
225*4204c5f7SShashi Mallela }
226*4204c5f7SShashi Mallela 
227*4204c5f7SShashi Mallela static const MemoryRegionOps sbsa_gwdt_rops = {
228*4204c5f7SShashi Mallela     .read = sbsa_gwdt_rread,
229*4204c5f7SShashi Mallela     .write = sbsa_gwdt_rwrite,
230*4204c5f7SShashi Mallela     .endianness = DEVICE_LITTLE_ENDIAN,
231*4204c5f7SShashi Mallela     .valid.min_access_size = 4,
232*4204c5f7SShashi Mallela     .valid.max_access_size = 4,
233*4204c5f7SShashi Mallela     .valid.unaligned = false,
234*4204c5f7SShashi Mallela };
235*4204c5f7SShashi Mallela 
236*4204c5f7SShashi Mallela static const MemoryRegionOps sbsa_gwdt_ops = {
237*4204c5f7SShashi Mallela     .read = sbsa_gwdt_read,
238*4204c5f7SShashi Mallela     .write = sbsa_gwdt_write,
239*4204c5f7SShashi Mallela     .endianness = DEVICE_LITTLE_ENDIAN,
240*4204c5f7SShashi Mallela     .valid.min_access_size = 4,
241*4204c5f7SShashi Mallela     .valid.max_access_size = 4,
242*4204c5f7SShashi Mallela     .valid.unaligned = false,
243*4204c5f7SShashi Mallela };
244*4204c5f7SShashi Mallela 
245*4204c5f7SShashi Mallela static void wdt_sbsa_gwdt_realize(DeviceState *dev, Error **errp)
246*4204c5f7SShashi Mallela {
247*4204c5f7SShashi Mallela     SBSA_GWDTState *s = SBSA_GWDT(dev);
248*4204c5f7SShashi Mallela     SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
249*4204c5f7SShashi Mallela 
250*4204c5f7SShashi Mallela     memory_region_init_io(&s->rmmio, OBJECT(dev),
251*4204c5f7SShashi Mallela                           &sbsa_gwdt_rops, s,
252*4204c5f7SShashi Mallela                           "sbsa_gwdt.refresh",
253*4204c5f7SShashi Mallela                           SBSA_GWDT_RMMIO_SIZE);
254*4204c5f7SShashi Mallela 
255*4204c5f7SShashi Mallela     memory_region_init_io(&s->cmmio, OBJECT(dev),
256*4204c5f7SShashi Mallela                           &sbsa_gwdt_ops, s,
257*4204c5f7SShashi Mallela                           "sbsa_gwdt.control",
258*4204c5f7SShashi Mallela                           SBSA_GWDT_CMMIO_SIZE);
259*4204c5f7SShashi Mallela 
260*4204c5f7SShashi Mallela     sysbus_init_mmio(sbd, &s->rmmio);
261*4204c5f7SShashi Mallela     sysbus_init_mmio(sbd, &s->cmmio);
262*4204c5f7SShashi Mallela 
263*4204c5f7SShashi Mallela     sysbus_init_irq(sbd, &s->irq);
264*4204c5f7SShashi Mallela 
265*4204c5f7SShashi Mallela     s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, sbsa_gwdt_timer_sysinterrupt,
266*4204c5f7SShashi Mallela                 dev);
267*4204c5f7SShashi Mallela }
268*4204c5f7SShashi Mallela 
269*4204c5f7SShashi Mallela static void wdt_sbsa_gwdt_class_init(ObjectClass *klass, void *data)
270*4204c5f7SShashi Mallela {
271*4204c5f7SShashi Mallela     DeviceClass *dc = DEVICE_CLASS(klass);
272*4204c5f7SShashi Mallela 
273*4204c5f7SShashi Mallela     dc->realize = wdt_sbsa_gwdt_realize;
274*4204c5f7SShashi Mallela     dc->reset = wdt_sbsa_gwdt_reset;
275*4204c5f7SShashi Mallela     dc->hotpluggable = false;
276*4204c5f7SShashi Mallela     set_bit(DEVICE_CATEGORY_MISC, dc->categories);
277*4204c5f7SShashi Mallela     dc->vmsd = &vmstate_sbsa_gwdt;
278*4204c5f7SShashi Mallela }
279*4204c5f7SShashi Mallela 
280*4204c5f7SShashi Mallela static const TypeInfo wdt_sbsa_gwdt_info = {
281*4204c5f7SShashi Mallela     .class_init = wdt_sbsa_gwdt_class_init,
282*4204c5f7SShashi Mallela     .parent = TYPE_SYS_BUS_DEVICE,
283*4204c5f7SShashi Mallela     .name  = TYPE_WDT_SBSA,
284*4204c5f7SShashi Mallela     .instance_size  = sizeof(SBSA_GWDTState),
285*4204c5f7SShashi Mallela };
286*4204c5f7SShashi Mallela 
287*4204c5f7SShashi Mallela static void wdt_sbsa_gwdt_register_types(void)
288*4204c5f7SShashi Mallela {
289*4204c5f7SShashi Mallela     watchdog_add_model(&model);
290*4204c5f7SShashi Mallela     type_register_static(&wdt_sbsa_gwdt_info);
291*4204c5f7SShashi Mallela }
292*4204c5f7SShashi Mallela 
293*4204c5f7SShashi Mallela type_init(wdt_sbsa_gwdt_register_types)
294