1 /*
2 * Copyright (C) 2009 Citrix Ltd.
3 * Author Vincent Hanquez <vincent.hanquez@eu.citrix.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published
7 * by the Free Software Foundation; version 2.1 only. with the special
8 * exception on linking described in file LICENSE.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 */
15
16 #include "libxl_osdeps.h" /* must come before any other headers */
17
18 #include "libxl_internal.h"
19
20 /*====================== Domain suspend =======================*/
21
libxl__domain_suspend_init(libxl__egc * egc,libxl__domain_suspend_state * dsps,libxl_domain_type type)22 int libxl__domain_suspend_init(libxl__egc *egc,
23 libxl__domain_suspend_state *dsps,
24 libxl_domain_type type)
25 {
26 STATE_AO_GC(dsps->ao);
27 int rc = ERROR_FAIL;
28 int port;
29
30 /* Convenience aliases */
31 const uint32_t domid = dsps->domid;
32
33 libxl__xswait_init(&dsps->pvcontrol);
34 libxl__ev_evtchn_init(&dsps->guest_evtchn);
35 libxl__ev_xswatch_init(&dsps->guest_watch);
36 libxl__ev_time_init(&dsps->guest_timeout);
37
38 if (type == LIBXL_DOMAIN_TYPE_INVALID) goto out;
39 dsps->type = type;
40
41 dsps->guest_evtchn.port = -1;
42 dsps->guest_evtchn_lockfd = -1;
43 dsps->guest_responded = 0;
44 dsps->dm_savefile = libxl__device_model_savefile(gc, domid);
45
46 port = xs_suspend_evtchn_port(domid);
47
48 if (port >= 0) {
49 rc = libxl__ctx_evtchn_init(gc);
50 if (rc) goto out;
51
52 dsps->guest_evtchn.port =
53 xc_suspend_evtchn_init_exclusive(CTX->xch, CTX->xce,
54 domid, port, &dsps->guest_evtchn_lockfd);
55
56 if (dsps->guest_evtchn.port < 0) {
57 LOGD(WARN, domid, "Suspend event channel initialization failed");
58 rc = ERROR_FAIL;
59 goto out;
60 }
61 }
62
63 rc = 0;
64
65 out:
66 return rc;
67 }
68
69 /*----- callbacks, called by xc_domain_save -----*/
70
libxl__domain_suspend_device_model(libxl__gc * gc,libxl__domain_suspend_state * dsps)71 int libxl__domain_suspend_device_model(libxl__gc *gc,
72 libxl__domain_suspend_state *dsps)
73 {
74 int ret = 0;
75 uint32_t const domid = dsps->domid;
76 const char *const filename = dsps->dm_savefile;
77
78 switch (libxl__device_model_version_running(gc, domid)) {
79 case LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN_TRADITIONAL: {
80 LOGD(DEBUG, domid, "Saving device model state to %s", filename);
81 libxl__qemu_traditional_cmd(gc, domid, "save");
82 libxl__wait_for_device_model_deprecated(gc, domid, "paused", NULL, NULL, NULL);
83 break;
84 }
85 case LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN:
86 if (libxl__qmp_stop(gc, domid))
87 return ERROR_FAIL;
88 /* Save DM state into filename */
89 ret = libxl__qmp_save(gc, domid, filename);
90 if (ret)
91 unlink(filename);
92 break;
93 default:
94 return ERROR_INVAL;
95 }
96
97 return ret;
98 }
99
100 static void domain_suspend_common_wait_guest(libxl__egc *egc,
101 libxl__domain_suspend_state *dsps);
102 static void domain_suspend_common_guest_suspended(libxl__egc *egc,
103 libxl__domain_suspend_state *dsps);
104
105 static void domain_suspend_common_pvcontrol_suspending(libxl__egc *egc,
106 libxl__xswait_state *xswa, int rc, const char *state);
107 static void domain_suspend_common_wait_guest_evtchn(libxl__egc *egc,
108 libxl__ev_evtchn *evev);
109 static void suspend_common_wait_guest_watch(libxl__egc *egc,
110 libxl__ev_xswatch *xsw, const char *watch_path, const char *event_path);
111 static void suspend_common_wait_guest_check(libxl__egc *egc,
112 libxl__domain_suspend_state *dsps);
113 static void suspend_common_wait_guest_timeout(libxl__egc *egc,
114 libxl__ev_time *ev, const struct timeval *requested_abs, int rc);
115
116 static void domain_suspend_common_done(libxl__egc *egc,
117 libxl__domain_suspend_state *dsps,
118 int rc);
119
120 static void domain_suspend_callback_common(libxl__egc *egc,
121 libxl__domain_suspend_state *dsps);
122 static void domain_suspend_callback_common_done(libxl__egc *egc,
123 libxl__domain_suspend_state *dsps, int rc);
124
125 /* calls dsps->callback_common_done when done */
libxl__domain_suspend(libxl__egc * egc,libxl__domain_suspend_state * dsps)126 void libxl__domain_suspend(libxl__egc *egc,
127 libxl__domain_suspend_state *dsps)
128 {
129 domain_suspend_callback_common(egc, dsps);
130 }
131
domain_suspend_pvcontrol_acked(const char * state)132 static bool domain_suspend_pvcontrol_acked(const char *state) {
133 /* any value other than "suspend", including ENOENT (i.e. !state), is OK */
134 if (!state) return 1;
135 return strcmp(state,"suspend");
136 }
137
138 /* calls dsps->callback_common_done when done */
domain_suspend_callback_common(libxl__egc * egc,libxl__domain_suspend_state * dsps)139 static void domain_suspend_callback_common(libxl__egc *egc,
140 libxl__domain_suspend_state *dsps)
141 {
142 STATE_AO_GC(dsps->ao);
143 uint64_t hvm_s_state = 0, hvm_pvdrv = 0;
144 int ret, rc;
145
146 /* Convenience aliases */
147 const uint32_t domid = dsps->domid;
148
149 if (dsps->type != LIBXL_DOMAIN_TYPE_PV) {
150 xc_hvm_param_get(CTX->xch, domid, HVM_PARAM_CALLBACK_IRQ, &hvm_pvdrv);
151 xc_hvm_param_get(CTX->xch, domid, HVM_PARAM_ACPI_S_STATE, &hvm_s_state);
152 }
153
154 if ((hvm_s_state == 0) && (dsps->guest_evtchn.port >= 0)) {
155 LOGD(DEBUG, domid, "issuing %s suspend request via event channel",
156 dsps->type != LIBXL_DOMAIN_TYPE_PV ? "PVH/HVM" : "PV");
157 ret = xenevtchn_notify(CTX->xce, dsps->guest_evtchn.port);
158 if (ret < 0) {
159 LOGD(ERROR, domid, "xenevtchn_notify failed ret=%d", ret);
160 rc = ERROR_FAIL;
161 goto err;
162 }
163
164 dsps->guest_evtchn.callback = domain_suspend_common_wait_guest_evtchn;
165 rc = libxl__ev_evtchn_wait(gc, &dsps->guest_evtchn);
166 if (rc) goto err;
167
168 rc = libxl__ev_time_register_rel(ao, &dsps->guest_timeout,
169 suspend_common_wait_guest_timeout,
170 60*1000);
171 if (rc) goto err;
172
173 return;
174 }
175
176 if (dsps->type == LIBXL_DOMAIN_TYPE_HVM && (!hvm_pvdrv || hvm_s_state)) {
177 LOGD(DEBUG, domid, "Calling xc_domain_shutdown on HVM domain");
178 ret = xc_domain_shutdown(CTX->xch, domid, SHUTDOWN_suspend);
179 if (ret < 0) {
180 LOGED(ERROR, domid, "xc_domain_shutdown failed");
181 rc = ERROR_FAIL;
182 goto err;
183 }
184 /* The guest does not (need to) respond to this sort of request. */
185 dsps->guest_responded = 1;
186 domain_suspend_common_wait_guest(egc, dsps);
187 return;
188 }
189
190 LOGD(DEBUG, domid, "issuing %s suspend request via XenBus control node",
191 dsps->type != LIBXL_DOMAIN_TYPE_PV ? "PVH/HVM" : "PV");
192
193 libxl__domain_pvcontrol_write(gc, XBT_NULL, domid, "suspend");
194
195 dsps->pvcontrol.path = libxl__domain_pvcontrol_xspath(gc, domid);
196 if (!dsps->pvcontrol.path) { rc = ERROR_FAIL; goto err; }
197
198 dsps->pvcontrol.ao = ao;
199 dsps->pvcontrol.what = "guest acknowledgement of suspend request";
200 dsps->pvcontrol.timeout_ms = 60 * 1000;
201 dsps->pvcontrol.callback = domain_suspend_common_pvcontrol_suspending;
202 libxl__xswait_start(gc, &dsps->pvcontrol);
203 return;
204
205 err:
206 domain_suspend_common_done(egc, dsps, rc);
207 }
208
domain_suspend_common_wait_guest_evtchn(libxl__egc * egc,libxl__ev_evtchn * evev)209 static void domain_suspend_common_wait_guest_evtchn(libxl__egc *egc,
210 libxl__ev_evtchn *evev)
211 {
212 libxl__domain_suspend_state *dsps = CONTAINER_OF(evev, *dsps, guest_evtchn);
213 STATE_AO_GC(dsps->ao);
214 /* If we should be done waiting, suspend_common_wait_guest_check
215 * will end up calling domain_suspend_common_guest_suspended or
216 * domain_suspend_common_done, both of which cancel the evtchn
217 * wait as needed. So re-enable it now. */
218 libxl__ev_evtchn_wait(gc, &dsps->guest_evtchn);
219 suspend_common_wait_guest_check(egc, dsps);
220 }
221
domain_suspend_common_pvcontrol_suspending(libxl__egc * egc,libxl__xswait_state * xswa,int rc,const char * state)222 static void domain_suspend_common_pvcontrol_suspending(libxl__egc *egc,
223 libxl__xswait_state *xswa, int rc, const char *state)
224 {
225 libxl__domain_suspend_state *dsps = CONTAINER_OF(xswa, *dsps, pvcontrol);
226 STATE_AO_GC(dsps->ao);
227 xs_transaction_t t = 0;
228
229 if (!rc && !domain_suspend_pvcontrol_acked(state))
230 /* keep waiting */
231 return;
232
233 libxl__xswait_stop(gc, &dsps->pvcontrol);
234
235 if (rc == ERROR_TIMEDOUT) {
236 /*
237 * Guest appears to not be responding. Cancel the suspend
238 * request.
239 *
240 * We re-read the suspend node and clear it within a
241 * transaction in order to handle the case where we race
242 * against the guest catching up and acknowledging the request
243 * at the last minute.
244 */
245 for (;;) {
246 rc = libxl__xs_transaction_start(gc, &t);
247 if (rc) goto err;
248
249 rc = libxl__xs_read_checked(gc, t, xswa->path, &state);
250 if (rc) goto err;
251
252 if (domain_suspend_pvcontrol_acked(state))
253 /* last minute ack */
254 break;
255
256 rc = libxl__xs_write_checked(gc, t, xswa->path, "");
257 if (rc) goto err;
258
259 rc = libxl__xs_transaction_commit(gc, &t);
260 if (!rc) {
261 LOGD(ERROR, dsps->domid,
262 "guest didn't acknowledge suspend, cancelling request");
263 goto err;
264 }
265 if (rc<0) goto err;
266 }
267 } else if (rc) {
268 /* some error in xswait's read of xenstore, already logged */
269 goto err;
270 }
271
272 assert(domain_suspend_pvcontrol_acked(state));
273 LOGD(DEBUG, dsps->domid, "guest acknowledged suspend request");
274
275 libxl__xs_transaction_abort(gc, &t);
276 dsps->guest_responded = 1;
277 domain_suspend_common_wait_guest(egc,dsps);
278 return;
279
280 err:
281 libxl__xs_transaction_abort(gc, &t);
282 domain_suspend_common_done(egc, dsps, rc);
283 return;
284 }
285
domain_suspend_common_wait_guest(libxl__egc * egc,libxl__domain_suspend_state * dsps)286 static void domain_suspend_common_wait_guest(libxl__egc *egc,
287 libxl__domain_suspend_state *dsps)
288 {
289 STATE_AO_GC(dsps->ao);
290 int rc;
291
292 LOGD(DEBUG, dsps->domid, "wait for the guest to suspend");
293
294 rc = libxl__ev_xswatch_register(gc, &dsps->guest_watch,
295 suspend_common_wait_guest_watch,
296 "@releaseDomain");
297 if (rc) goto err;
298
299 rc = libxl__ev_time_register_rel(ao, &dsps->guest_timeout,
300 suspend_common_wait_guest_timeout,
301 60*1000);
302 if (rc) goto err;
303 return;
304
305 err:
306 domain_suspend_common_done(egc, dsps, rc);
307 }
308
suspend_common_wait_guest_watch(libxl__egc * egc,libxl__ev_xswatch * xsw,const char * watch_path,const char * event_path)309 static void suspend_common_wait_guest_watch(libxl__egc *egc,
310 libxl__ev_xswatch *xsw, const char *watch_path, const char *event_path)
311 {
312 libxl__domain_suspend_state *dsps = CONTAINER_OF(xsw, *dsps, guest_watch);
313 suspend_common_wait_guest_check(egc, dsps);
314 }
315
suspend_common_wait_guest_check(libxl__egc * egc,libxl__domain_suspend_state * dsps)316 static void suspend_common_wait_guest_check(libxl__egc *egc,
317 libxl__domain_suspend_state *dsps)
318 {
319 STATE_AO_GC(dsps->ao);
320 xc_domaininfo_t info;
321 int ret;
322 int shutdown_reason;
323
324 /* Convenience aliases */
325 const uint32_t domid = dsps->domid;
326
327 ret = xc_domain_getinfolist(CTX->xch, domid, 1, &info);
328 if (ret < 0) {
329 LOGED(ERROR, domid, "unable to check for status of guest");
330 goto err;
331 }
332
333 if (!(ret == 1 && info.domain == domid)) {
334 LOGED(ERROR, domid, "guest we were suspending has been destroyed");
335 goto err;
336 }
337
338 if (!(info.flags & XEN_DOMINF_shutdown))
339 /* keep waiting */
340 return;
341
342 shutdown_reason = (info.flags >> XEN_DOMINF_shutdownshift)
343 & XEN_DOMINF_shutdownmask;
344 if (shutdown_reason != SHUTDOWN_suspend) {
345 LOGD(DEBUG, domid, "guest we were suspending has shut down"
346 " with unexpected reason code %d", shutdown_reason);
347 goto err;
348 }
349
350 LOGD(DEBUG, domid, "guest has suspended");
351 domain_suspend_common_guest_suspended(egc, dsps);
352 return;
353
354 err:
355 domain_suspend_common_done(egc, dsps, ERROR_FAIL);
356 }
357
suspend_common_wait_guest_timeout(libxl__egc * egc,libxl__ev_time * ev,const struct timeval * requested_abs,int rc)358 static void suspend_common_wait_guest_timeout(libxl__egc *egc,
359 libxl__ev_time *ev, const struct timeval *requested_abs, int rc)
360 {
361 libxl__domain_suspend_state *dsps = CONTAINER_OF(ev, *dsps, guest_timeout);
362 STATE_AO_GC(dsps->ao);
363 if (rc == ERROR_TIMEDOUT) {
364 LOGD(ERROR, dsps->domid, "guest did not suspend, timed out");
365 rc = ERROR_GUEST_TIMEDOUT;
366 }
367 domain_suspend_common_done(egc, dsps, rc);
368 }
369
domain_suspend_common_guest_suspended(libxl__egc * egc,libxl__domain_suspend_state * dsps)370 static void domain_suspend_common_guest_suspended(libxl__egc *egc,
371 libxl__domain_suspend_state *dsps)
372 {
373 STATE_AO_GC(dsps->ao);
374 int rc;
375
376 libxl__ev_evtchn_cancel(gc, &dsps->guest_evtchn);
377 libxl__ev_xswatch_deregister(gc, &dsps->guest_watch);
378 libxl__ev_time_deregister(gc, &dsps->guest_timeout);
379
380 if (dsps->type == LIBXL_DOMAIN_TYPE_HVM) {
381 rc = libxl__domain_suspend_device_model(gc, dsps);
382 if (rc) {
383 LOGD(ERROR, dsps->domid,
384 "libxl__domain_suspend_device_model failed ret=%d", rc);
385 domain_suspend_common_done(egc, dsps, rc);
386 return;
387 }
388 }
389 domain_suspend_common_done(egc, dsps, 0);
390 }
391
domain_suspend_common_done(libxl__egc * egc,libxl__domain_suspend_state * dsps,int rc)392 static void domain_suspend_common_done(libxl__egc *egc,
393 libxl__domain_suspend_state *dsps,
394 int rc)
395 {
396 EGC_GC;
397 assert(!libxl__xswait_inuse(&dsps->pvcontrol));
398 libxl__ev_evtchn_cancel(gc, &dsps->guest_evtchn);
399 libxl__ev_xswatch_deregister(gc, &dsps->guest_watch);
400 libxl__ev_time_deregister(gc, &dsps->guest_timeout);
401 dsps->callback_common_done(egc, dsps, rc);
402 }
403
libxl__domain_suspend_callback(void * data)404 void libxl__domain_suspend_callback(void *data)
405 {
406 libxl__save_helper_state *shs = data;
407 libxl__egc *egc = shs->egc;
408 libxl__domain_save_state *dss = shs->caller_state;
409 libxl__domain_suspend_state *dsps = &dss->dsps;
410
411 dsps->callback_common_done = domain_suspend_callback_common_done;
412 domain_suspend_callback_common(egc, dsps);
413 }
414
domain_suspend_callback_common_done(libxl__egc * egc,libxl__domain_suspend_state * dsps,int rc)415 static void domain_suspend_callback_common_done(libxl__egc *egc,
416 libxl__domain_suspend_state *dsps, int rc)
417 {
418 libxl__domain_save_state *dss = CONTAINER_OF(dsps, *dss, dsps);
419 dss->rc = rc;
420 libxl__xc_domain_saverestore_async_callback_done(egc, &dss->sws.shs, !rc);
421 }
422
423 /*======================= Domain resume ========================*/
424
libxl__domain_resume_device_model(libxl__gc * gc,uint32_t domid)425 int libxl__domain_resume_device_model(libxl__gc *gc, uint32_t domid)
426 {
427 const char *path, *state;
428
429 switch (libxl__device_model_version_running(gc, domid)) {
430 case LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN_TRADITIONAL: {
431 uint32_t dm_domid = libxl_get_stubdom_id(CTX, domid);
432
433 path = DEVICE_MODEL_XS_PATH(gc, dm_domid, domid, "/state");
434 state = libxl__xs_read(gc, XBT_NULL, path);
435 if (state != NULL && !strcmp(state, "paused")) {
436 libxl__qemu_traditional_cmd(gc, domid, "continue");
437 libxl__wait_for_device_model_deprecated(gc, domid, "running",
438 NULL, NULL, NULL);
439 }
440 break;
441 }
442 case LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN:
443 if (libxl__qmp_resume(gc, domid))
444 return ERROR_FAIL;
445 break;
446 default:
447 return ERROR_INVAL;
448 }
449
450 return 0;
451 }
452
libxl__domain_resume(libxl__gc * gc,uint32_t domid,int suspend_cancel)453 int libxl__domain_resume(libxl__gc *gc, uint32_t domid, int suspend_cancel)
454 {
455 int rc = 0;
456
457 libxl_domain_type type = libxl__domain_type(gc, domid);
458 if (type == LIBXL_DOMAIN_TYPE_INVALID) {
459 rc = ERROR_FAIL;
460 goto out;
461 }
462
463 if (type == LIBXL_DOMAIN_TYPE_HVM) {
464 rc = libxl__domain_resume_device_model(gc, domid);
465 if (rc) {
466 LOGD(ERROR, domid, "failed to resume device model:%d", rc);
467 goto out;
468 }
469 }
470
471 if (xc_domain_resume(CTX->xch, domid, suspend_cancel)) {
472 LOGED(ERROR, domid, "xc_domain_resume failed");
473 rc = ERROR_FAIL;
474 goto out;
475 }
476
477 if (!xs_resume_domain(CTX->xsh, domid)) {
478 LOGED(ERROR, domid, "xs_resume_domain failed");
479 rc = ERROR_FAIL;
480 }
481 out:
482 return rc;
483 }
484
485 /*
486 * Local variables:
487 * mode: C
488 * c-basic-offset: 4
489 * indent-tabs-mode: nil
490 * End:
491 */
492