]>
Commit | Line | Data |
---|---|---|
81b205ce AK |
1 | /* |
2 | * This library is free software; you can redistribute it and/or | |
3 | * modify it under the terms of the GNU Lesser General Public | |
4 | * License as published by the Free Software Foundation; either | |
5 | * version 2.1 of the License, or (at your option) any later version. | |
6 | * | |
7 | * This library is distributed in the hope that it will be useful, | |
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
10 | * Lesser General Public License for more details. | |
11 | * | |
12 | * You should have received a copy of the GNU Lesser General Public | |
13 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. | |
14 | */ | |
15 | ||
16 | #include "qemu/osdep.h" | |
17 | #include "qapi/error.h" | |
18 | #include "target/ppc/cpu.h" | |
19 | #include "migration/vmstate.h" | |
20 | #include "trace.h" | |
21 | ||
22 | #include "hw/ppc/spapr.h" | |
23 | ||
24 | #define FIELD_BE(reg, field, start, len) \ | |
25 | FIELD(reg, field, 64 - (start + len), len) | |
26 | ||
27 | /* | |
28 | * Bits 47: "leaveOtherWatchdogsRunningOnTimeout", specified on | |
29 | * the "Start watchdog" operation, | |
30 | * 0 - stop out-standing watchdogs on timeout, | |
31 | * 1 - leave outstanding watchdogs running on timeout | |
32 | */ | |
33 | FIELD_BE(PSERIES_WDTF, LEAVE_OTHER, 47, 1) | |
34 | ||
35 | /* Bits 48-55: "operation" */ | |
36 | FIELD_BE(PSERIES_WDTF, OP, 48, 8) | |
37 | #define PSERIES_WDTF_OP_START 0x1 | |
38 | #define PSERIES_WDTF_OP_STOP 0x2 | |
39 | #define PSERIES_WDTF_OP_QUERY 0x3 | |
40 | #define PSERIES_WDTF_OP_QUERY_LPM 0x4 | |
41 | ||
42 | /* Bits 56-63: "timeoutAction" */ | |
43 | FIELD_BE(PSERIES_WDTF, ACTION, 56, 8) | |
44 | #define PSERIES_WDTF_ACTION_HARD_POWER_OFF 0x1 | |
45 | #define PSERIES_WDTF_ACTION_HARD_RESTART 0x2 | |
46 | #define PSERIES_WDTF_ACTION_DUMP_RESTART 0x3 | |
47 | ||
48 | FIELD_BE(PSERIES_WDTF, RESERVED, 0, 47) | |
49 | ||
50 | /* Special watchdogNumber for the "stop all watchdogs" operation */ | |
51 | #define PSERIES_WDT_STOP_ALL ((uint64_t)~0) | |
52 | ||
53 | /* | |
54 | * For the "Query watchdog capabilities" operation, a uint64 structure | |
55 | * defined as: | |
56 | * Bits 0-15: The minimum supported timeout in milliseconds | |
57 | * Bits 16-31: The number of watchdogs supported | |
58 | * Bits 32-63: Reserved | |
59 | */ | |
60 | FIELD_BE(PSERIES_WDTQ, MIN_TIMEOUT, 0, 16) | |
61 | FIELD_BE(PSERIES_WDTQ, NUM, 16, 16) | |
62 | ||
63 | /* | |
64 | * For the "Query watchdog LPM requirement" operation: | |
65 | * 1 = The given "watchdogNumber" must be stopped prior to suspending | |
66 | * 2 = The given "watchdogNumber" does not have to be stopped prior to | |
67 | * suspending | |
68 | */ | |
69 | #define PSERIES_WDTQL_STOPPED 1 | |
70 | #define PSERIES_WDTQL_QUERY_NOT_STOPPED 2 | |
71 | ||
72 | #define WDT_MIN_TIMEOUT 1 /* 1ms */ | |
73 | ||
74 | static target_ulong watchdog_stop(unsigned watchdogNumber, SpaprWatchdog *w) | |
75 | { | |
76 | target_ulong ret = H_NOOP; | |
77 | ||
78 | if (timer_pending(&w->timer)) { | |
79 | timer_del(&w->timer); | |
80 | ret = H_SUCCESS; | |
81 | } | |
82 | trace_spapr_watchdog_stop(watchdogNumber, ret); | |
83 | ||
84 | return ret; | |
85 | } | |
86 | ||
87 | static target_ulong watchdog_stop_all(SpaprMachineState *spapr) | |
88 | { | |
89 | target_ulong ret = H_NOOP; | |
90 | int i; | |
91 | ||
92 | for (i = 1; i <= ARRAY_SIZE(spapr->wds); ++i) { | |
93 | target_ulong r = watchdog_stop(i, &spapr->wds[i - 1]); | |
94 | ||
95 | if (r != H_NOOP && r != H_SUCCESS) { | |
96 | ret = r; | |
97 | } | |
98 | } | |
99 | ||
100 | return ret; | |
101 | } | |
102 | ||
103 | static void watchdog_expired(void *pw) | |
104 | { | |
105 | SpaprWatchdog *w = pw; | |
106 | CPUState *cs; | |
107 | SpaprMachineState *spapr = SPAPR_MACHINE(qdev_get_machine()); | |
108 | unsigned num = w - spapr->wds; | |
109 | ||
110 | g_assert(num < ARRAY_SIZE(spapr->wds)); | |
111 | trace_spapr_watchdog_expired(num, w->action); | |
112 | switch (w->action) { | |
113 | case PSERIES_WDTF_ACTION_HARD_POWER_OFF: | |
114 | qemu_system_vmstop_request(RUN_STATE_SHUTDOWN); | |
115 | break; | |
116 | case PSERIES_WDTF_ACTION_HARD_RESTART: | |
117 | qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); | |
118 | break; | |
119 | case PSERIES_WDTF_ACTION_DUMP_RESTART: | |
120 | CPU_FOREACH(cs) { | |
121 | async_run_on_cpu(cs, spapr_do_system_reset_on_cpu, RUN_ON_CPU_NULL); | |
122 | } | |
123 | break; | |
124 | } | |
125 | if (!w->leave_others) { | |
126 | watchdog_stop_all(spapr); | |
127 | } | |
128 | } | |
129 | ||
130 | static target_ulong h_watchdog(PowerPCCPU *cpu, | |
131 | SpaprMachineState *spapr, | |
132 | target_ulong opcode, target_ulong *args) | |
133 | { | |
134 | target_ulong ret = H_SUCCESS; | |
135 | target_ulong flags = args[0]; | |
136 | target_ulong watchdogNumber = args[1]; /* 1-Based per PAPR */ | |
137 | target_ulong timeoutInMs = args[2]; | |
138 | unsigned operation = FIELD_EX64(flags, PSERIES_WDTF, OP); | |
139 | unsigned timeoutAction = FIELD_EX64(flags, PSERIES_WDTF, ACTION); | |
140 | SpaprWatchdog *w; | |
141 | ||
142 | if (FIELD_EX64(flags, PSERIES_WDTF, RESERVED)) { | |
143 | return H_PARAMETER; | |
144 | } | |
145 | ||
146 | switch (operation) { | |
147 | case PSERIES_WDTF_OP_START: | |
148 | if (watchdogNumber > ARRAY_SIZE(spapr->wds)) { | |
149 | return H_P2; | |
150 | } | |
151 | if (timeoutInMs <= WDT_MIN_TIMEOUT) { | |
152 | return H_P3; | |
153 | } | |
154 | ||
155 | w = &spapr->wds[watchdogNumber - 1]; | |
156 | switch (timeoutAction) { | |
157 | case PSERIES_WDTF_ACTION_HARD_POWER_OFF: | |
158 | case PSERIES_WDTF_ACTION_HARD_RESTART: | |
159 | case PSERIES_WDTF_ACTION_DUMP_RESTART: | |
160 | w->action = timeoutAction; | |
161 | break; | |
162 | default: | |
163 | return H_PARAMETER; | |
164 | } | |
165 | w->leave_others = FIELD_EX64(flags, PSERIES_WDTF, LEAVE_OTHER); | |
166 | timer_mod(&w->timer, | |
167 | qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + timeoutInMs); | |
168 | trace_spapr_watchdog_start(flags, watchdogNumber, timeoutInMs); | |
169 | break; | |
170 | case PSERIES_WDTF_OP_STOP: | |
171 | if (watchdogNumber == PSERIES_WDT_STOP_ALL) { | |
172 | ret = watchdog_stop_all(spapr); | |
173 | } else if (watchdogNumber <= ARRAY_SIZE(spapr->wds)) { | |
174 | ret = watchdog_stop(watchdogNumber, | |
175 | &spapr->wds[watchdogNumber - 1]); | |
176 | } else { | |
177 | return H_P2; | |
178 | } | |
179 | break; | |
180 | case PSERIES_WDTF_OP_QUERY: | |
181 | args[0] = FIELD_DP64(0, PSERIES_WDTQ, MIN_TIMEOUT, WDT_MIN_TIMEOUT); | |
182 | args[0] = FIELD_DP64(args[0], PSERIES_WDTQ, NUM, | |
183 | ARRAY_SIZE(spapr->wds)); | |
184 | trace_spapr_watchdog_query(args[0]); | |
185 | break; | |
186 | case PSERIES_WDTF_OP_QUERY_LPM: | |
187 | if (watchdogNumber > ARRAY_SIZE(spapr->wds)) { | |
188 | return H_P2; | |
189 | } | |
190 | args[0] = PSERIES_WDTQL_QUERY_NOT_STOPPED; | |
191 | trace_spapr_watchdog_query_lpm(args[0]); | |
192 | break; | |
193 | default: | |
194 | return H_PARAMETER; | |
195 | } | |
196 | ||
197 | return ret; | |
198 | } | |
199 | ||
200 | void spapr_watchdog_init(SpaprMachineState *spapr) | |
201 | { | |
202 | int i; | |
203 | ||
204 | for (i = 0; i < ARRAY_SIZE(spapr->wds); ++i) { | |
205 | char name[16]; | |
206 | SpaprWatchdog *w = &spapr->wds[i]; | |
207 | ||
208 | snprintf(name, sizeof(name) - 1, "wdt%d", i + 1); | |
209 | object_initialize_child_with_props(OBJECT(spapr), name, w, | |
210 | sizeof(SpaprWatchdog), | |
211 | TYPE_SPAPR_WDT, | |
212 | &error_fatal, NULL); | |
213 | qdev_realize(DEVICE(w), NULL, &error_fatal); | |
214 | } | |
215 | } | |
216 | ||
217 | static bool watchdog_needed(void *opaque) | |
218 | { | |
219 | SpaprWatchdog *w = opaque; | |
220 | ||
221 | return timer_pending(&w->timer); | |
222 | } | |
223 | ||
224 | static const VMStateDescription vmstate_wdt = { | |
225 | .name = "spapr_watchdog", | |
226 | .version_id = 1, | |
227 | .minimum_version_id = 1, | |
228 | .needed = watchdog_needed, | |
229 | .fields = (VMStateField[]) { | |
230 | VMSTATE_TIMER(timer, SpaprWatchdog), | |
231 | VMSTATE_UINT8(action, SpaprWatchdog), | |
232 | VMSTATE_UINT8(leave_others, SpaprWatchdog), | |
233 | VMSTATE_END_OF_LIST() | |
234 | } | |
235 | }; | |
236 | ||
237 | static void spapr_wdt_realize(DeviceState *dev, Error **errp) | |
238 | { | |
239 | SpaprWatchdog *w = SPAPR_WDT(dev); | |
240 | Object *o = OBJECT(dev); | |
241 | ||
242 | timer_init_ms(&w->timer, QEMU_CLOCK_VIRTUAL, watchdog_expired, w); | |
243 | ||
244 | object_property_add_uint64_ptr(o, "expire", | |
245 | (uint64_t *)&w->timer.expire_time, | |
246 | OBJ_PROP_FLAG_READ); | |
247 | object_property_add_uint8_ptr(o, "action", &w->action, OBJ_PROP_FLAG_READ); | |
248 | object_property_add_uint8_ptr(o, "leaveOtherWatchdogsRunningOnTimeout", | |
249 | &w->leave_others, OBJ_PROP_FLAG_READ); | |
250 | } | |
251 | ||
252 | static void spapr_wdt_class_init(ObjectClass *oc, void *data) | |
253 | { | |
254 | DeviceClass *dc = DEVICE_CLASS(oc); | |
255 | ||
256 | dc->realize = spapr_wdt_realize; | |
257 | dc->vmsd = &vmstate_wdt; | |
258 | dc->user_creatable = false; | |
259 | } | |
260 | ||
261 | static const TypeInfo spapr_wdt_info = { | |
262 | .name = TYPE_SPAPR_WDT, | |
263 | .parent = TYPE_DEVICE, | |
264 | .instance_size = sizeof(SpaprWatchdog), | |
265 | .class_init = spapr_wdt_class_init, | |
266 | }; | |
267 | ||
268 | static void spapr_watchdog_register_types(void) | |
269 | { | |
270 | spapr_register_hypercall(H_WATCHDOG, h_watchdog); | |
271 | type_register_static(&spapr_wdt_info); | |
272 | } | |
273 | ||
274 | type_init(spapr_watchdog_register_types) |