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