Abstract platform specific mechanics for nvdimm firmware activation behind a handful of generic ops. At the bus level ->activate_state() indicates the unified state (idle, busy, armed) of all DIMMs on the bus, and ->capability() indicates the system state expectations for activate. At the DIMM level ->activate_state() indicates the per-DIMM state, ->activate_result() indicates the outcome of the last activation attempt, and ->arm() attempts to transition the DIMM from 'idle' to 'armed'. A new hibernate_quiet_exec() facility is added to support firmware activation in an OS defined system quiesce state. It leverages the fact that the hibernate-freeze state wants to assert that a memory hibernation snapshot can be taken. This is in contrast to a platform firmware defined quiesce state that may forcefully quiet the memory controller independent of whether an individual device-driver properly supports hibernate-freeze. The libnvdimm sysfs interface is extended to support detection of a firmware activate capability. The mechanism supports enumeration and triggering of firmware activate, optionally in the hibernate_quiet_exec() context. [rafael: hibernate_quiet_exec() proposal] [vishal: fix up sparse warning, grammar in Documentation/]
Unified · Split
| @@ -0,0 +1,2 @@ | ||
| 1 | +The libnvdimm sub-system implements a common sysfs interface for | |
| 2 | +platform nvdimm resources. See Documentation/driver-api/nvdimm/. | |
| @@ -0,0 +1,86 @@ | ||
| 1 | +.. SPDX-License-Identifier: GPL-2.0 | |
| 2 | + | |
| 3 | +================================== | |
| 4 | +NVDIMM Runtime Firmware Activation | |
| 5 | +================================== | |
| 6 | + | |
| 7 | +Some persistent memory devices run a firmware locally on the device / | |
| 8 | +"DIMM" to perform tasks like media management, capacity provisioning, | |
| 9 | +and health monitoring. The process of updating that firmware typically | |
| 10 | +involves a reboot because it has implications for in-flight memory | |
| 11 | +transactions. However, reboots are disruptive and at least the Intel | |
| 12 | +persistent memory platform implementation, described by the Intel ACPI | |
| 13 | +DSM specification [1], has added support for activating firmware at | |
| 14 | +runtime. | |
| 15 | + | |
| 16 | +A native sysfs interface is implemented in libnvdimm to allow platform | |
| 17 | +to advertise and control their local runtime firmware activation | |
| 18 | +capability. | |
| 19 | + | |
| 20 | +The libnvdimm bus object, ndbusX, implements an ndbusX/firmware/activate | |
| 21 | +attribute that shows the state of the firmware activation as one of 'idle', | |
| 22 | +'armed', 'overflow', and 'busy'. | |
| 23 | + | |
| 24 | +- idle: | |
| 25 | + No devices are set / armed to activate firmware | |
| 26 | + | |
| 27 | +- armed: | |
| 28 | + At least one device is armed | |
| 29 | + | |
| 30 | +- busy: | |
| 31 | + In the busy state armed devices are in the process of transitioning | |
| 32 | + back to idle and completing an activation cycle. | |
| 33 | + | |
| 34 | +- overflow: | |
| 35 | + If the platform has a concept of incremental work needed to perform | |
| 36 | + the activation it could be the case that too many DIMMs are armed for | |
| 37 | + activation. In that scenario the potential for firmware activation to | |
| 38 | + timeout is indicated by the 'overflow' state. | |
| 39 | + | |
| 40 | +The 'ndbusX/firmware/activate' property can be written with a value of | |
| 41 | +either 'live', or 'quiesce'. A value of 'quiesce' triggers the kernel to | |
| 42 | +run firmware activation from within the equivalent of the hibernation | |
| 43 | +'freeze' state where drivers and applications are notified to stop their | |
| 44 | +modifications of system memory. A value of 'live' attempts | |
| 45 | +firmware activation without this hibernation cycle. The | |
| 46 | +'ndbusX/firmware/activate' property will be elided completely if no | |
| 47 | +firmware activation capability is detected. | |
| 48 | + | |
| 49 | +Another property 'ndbusX/firmware/capability' indicates a value of | |
| 50 | +'live' or 'quiesce', where 'live' indicates that the firmware | |
| 51 | +does not require or inflict any quiesce period on the system to update | |
| 52 | +firmware. A capability value of 'quiesce' indicates that firmware does | |
| 53 | +expect and injects a quiet period for the memory controller, but 'live' | |
| 54 | +may still be written to 'ndbusX/firmware/activate' as an override to | |
| 55 | +assume the risk of racing firmware update with in-flight device and | |
| 56 | +application activity. The 'ndbusX/firmware/capability' property will be | |
| 57 | +elided completely if no firmware activation capability is detected. | |
| 58 | + | |
| 59 | +The libnvdimm memory-device / DIMM object, nmemX, implements | |
| 60 | +'nmemX/firmware/activate' and 'nmemX/firmware/result' attributes to | |
| 61 | +communicate the per-device firmware activation state. Similar to the | |
| 62 | +'ndbusX/firmware/activate' attribute, the 'nmemX/firmware/activate' | |
| 63 | +attribute indicates 'idle', 'armed', or 'busy'. The state transitions | |
| 64 | +from 'armed' to 'idle' when the system is prepared to activate firmware, | |
| 65 | +firmware staged + state set to armed, and 'ndbusX/firmware/activate' is | |
| 66 | +triggered. After that activation event the nmemX/firmware/result | |
| 67 | +attribute reflects the state of the last activation as one of: | |
| 68 | + | |
| 69 | +- none: | |
| 70 | + No runtime activation triggered since the last time the device was reset | |
| 71 | + | |
| 72 | +- success: | |
| 73 | + The last runtime activation completed successfully. | |
| 74 | + | |
| 75 | +- fail: | |
| 76 | + The last runtime activation failed for device-specific reasons. | |
| 77 | + | |
| 78 | +- not_staged: | |
| 79 | + The last runtime activation failed due to a sequencing error of the | |
| 80 | + firmware image not being staged. | |
| 81 | + | |
| 82 | +- need_reset: | |
| 83 | + Runtime firmware activation failed, but the firmware can still be | |
| 84 | + activated via the legacy method of power-cycling the system. | |
| 85 | + | |
| 86 | +[1]: https://docs.pmem.io/persistent-memory/ | |
| @@ -4,6 +4,7 @@ · 3 unmodified lines | ||
| 4 | 4 | */ |
| 5 | 5 | #include <linux/libnvdimm.h> |
| 6 | 6 | #include <linux/badblocks.h> |
| 7 | +#include <linux/suspend.h> | |
| 7 | 8 | #include <linux/export.h> |
| 8 | 9 | #include <linux/module.h> |
| 9 | 10 | #include <linux/blkdev.h> |
| @@ -389,8 +390,156 @@ static const struct attribute_group nvdimm_bus_attribute_group = { · 379 unmodified lines | ||
| 389 | 390 | .attrs = nvdimm_bus_attributes, |
| 390 | 391 | }; |
| 391 | 392 | |
| 393 | +static ssize_t capability_show(struct device *dev, | |
| 394 | + struct device_attribute *attr, char *buf) | |
| 395 | +{ | |
| 396 | + struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev); | |
| 397 | + struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc; | |
| 398 | + enum nvdimm_fwa_capability cap; | |
| 399 | + | |
| 400 | + if (!nd_desc->fw_ops) | |
| 401 | + return -EOPNOTSUPP; | |
| 402 | + | |
| 403 | + nvdimm_bus_lock(dev); | |
| 404 | + cap = nd_desc->fw_ops->capability(nd_desc); | |
| 405 | + nvdimm_bus_unlock(dev); | |
| 406 | + | |
| 407 | + switch (cap) { | |
| 408 | + case NVDIMM_FWA_CAP_QUIESCE: | |
| 409 | + return sprintf(buf, "quiesce\n"); | |
| 410 | + case NVDIMM_FWA_CAP_LIVE: | |
| 411 | + return sprintf(buf, "live\n"); | |
| 412 | + default: | |
| 413 | + return -EOPNOTSUPP; | |
| 414 | + } | |
| 415 | +} | |
| 416 | + | |
| 417 | +static DEVICE_ATTR_RO(capability); | |
| 418 | + | |
| 419 | +static ssize_t activate_show(struct device *dev, | |
| 420 | + struct device_attribute *attr, char *buf) | |
| 421 | +{ | |
| 422 | + struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev); | |
| 423 | + struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc; | |
| 424 | + enum nvdimm_fwa_capability cap; | |
| 425 | + enum nvdimm_fwa_state state; | |
| 426 | + | |
| 427 | + if (!nd_desc->fw_ops) | |
| 428 | + return -EOPNOTSUPP; | |
| 429 | + | |
| 430 | + nvdimm_bus_lock(dev); | |
| 431 | + cap = nd_desc->fw_ops->capability(nd_desc); | |
| 432 | + state = nd_desc->fw_ops->activate_state(nd_desc); | |
| 433 | + nvdimm_bus_unlock(dev); | |
| 434 | + | |
| 435 | + if (cap < NVDIMM_FWA_CAP_QUIESCE) | |
| 436 | + return -EOPNOTSUPP; | |
| 437 | + | |
| 438 | + switch (state) { | |
| 439 | + case NVDIMM_FWA_IDLE: | |
| 440 | + return sprintf(buf, "idle\n"); | |
| 441 | + case NVDIMM_FWA_BUSY: | |
| 442 | + return sprintf(buf, "busy\n"); | |
| 443 | + case NVDIMM_FWA_ARMED: | |
| 444 | + return sprintf(buf, "armed\n"); | |
| 445 | + case NVDIMM_FWA_ARM_OVERFLOW: | |
| 446 | + return sprintf(buf, "overflow\n"); | |
| 447 | + default: | |
| 448 | + return -ENXIO; | |
| 449 | + } | |
| 450 | +} | |
| 451 | + | |
| 452 | +static int exec_firmware_activate(void *data) | |
| 453 | +{ | |
| 454 | + struct nvdimm_bus_descriptor *nd_desc = data; | |
| 455 | + | |
| 456 | + return nd_desc->fw_ops->activate(nd_desc); | |
| 457 | +} | |
| 458 | + | |
| 459 | +static ssize_t activate_store(struct device *dev, | |
| 460 | + struct device_attribute *attr, const char *buf, size_t len) | |
| 461 | +{ | |
| 462 | + struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev); | |
| 463 | + struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc; | |
| 464 | + enum nvdimm_fwa_state state; | |
| 465 | + bool quiesce; | |
| 466 | + ssize_t rc; | |
| 467 | + | |
| 468 | + if (!nd_desc->fw_ops) | |
| 469 | + return -EOPNOTSUPP; | |
| 470 | + | |
| 471 | + if (sysfs_streq(buf, "live")) | |
| 472 | + quiesce = false; | |
| 473 | + else if (sysfs_streq(buf, "quiesce")) | |
| 474 | + quiesce = true; | |
| 475 | + else | |
| 476 | + return -EINVAL; | |
| 477 | + | |
| 478 | + nvdimm_bus_lock(dev); | |
| 479 | + state = nd_desc->fw_ops->activate_state(nd_desc); | |
| 480 | + | |
| 481 | + switch (state) { | |
| 482 | + case NVDIMM_FWA_BUSY: | |
| 483 | + rc = -EBUSY; | |
| 484 | + break; | |
| 485 | + case NVDIMM_FWA_ARMED: | |
| 486 | + case NVDIMM_FWA_ARM_OVERFLOW: | |
| 487 | + if (quiesce) | |
| 488 | + rc = hibernate_quiet_exec(exec_firmware_activate, nd_desc); | |
| 489 | + else | |
| 490 | + rc = nd_desc->fw_ops->activate(nd_desc); | |
| 491 | + break; | |
| 492 | + case NVDIMM_FWA_IDLE: | |
| 493 | + default: | |
| 494 | + rc = -ENXIO; | |
| 495 | + } | |
| 496 | + nvdimm_bus_unlock(dev); | |
| 497 | + | |
| 498 | + if (rc == 0) | |
| 499 | + rc = len; | |
| 500 | + return rc; | |
| 501 | +} | |
| 502 | + | |
| 503 | +static DEVICE_ATTR_ADMIN_RW(activate); | |
| 504 | + | |
| 505 | +static umode_t nvdimm_bus_firmware_visible(struct kobject *kobj, struct attribute *a, int n) | |
| 506 | +{ | |
| 507 | + struct device *dev = container_of(kobj, typeof(*dev), kobj); | |
| 508 | + struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev); | |
| 509 | + struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc; | |
| 510 | + enum nvdimm_fwa_capability cap; | |
| 511 | + | |
| 512 | + /* | |
| 513 | + * Both 'activate' and 'capability' disappear when no ops | |
| 514 | + * detected, or a negative capability is indicated. | |
| 515 | + */ | |
| 516 | + if (!nd_desc->fw_ops) | |
| 517 | + return 0; | |
| 518 | + | |
| 519 | + nvdimm_bus_lock(dev); | |
| 520 | + cap = nd_desc->fw_ops->capability(nd_desc); | |
| 521 | + nvdimm_bus_unlock(dev); | |
| 522 | + | |
| 523 | + if (cap < NVDIMM_FWA_CAP_QUIESCE) | |
| 524 | + return 0; | |
| 525 | + | |
| 526 | + return a->mode; | |
| 527 | +} | |
| 528 | +static struct attribute *nvdimm_bus_firmware_attributes[] = { | |
| 529 | + &dev_attr_activate.attr, | |
| 530 | + &dev_attr_capability.attr, | |
| 531 | + NULL, | |
| 532 | +}; | |
| 533 | + | |
| 534 | +static const struct attribute_group nvdimm_bus_firmware_attribute_group = { | |
| 535 | + .name = "firmware", | |
| 536 | + .attrs = nvdimm_bus_firmware_attributes, | |
| 537 | + .is_visible = nvdimm_bus_firmware_visible, | |
| 538 | +}; | |
| 539 | + | |
| 392 | 540 | const struct attribute_group *nvdimm_bus_attribute_groups[] = { |
| 393 | 541 | &nvdimm_bus_attribute_group, |
| 542 | + &nvdimm_bus_firmware_attribute_group, | |
| 394 | 543 | NULL, |
| 395 | 544 | }; |
| 396 | 545 | |
| @@ -446,9 +446,124 @@ static const struct attribute_group nvdimm_attribute_group = { · 445 unmodified lines | ||
| 446 | 446 | .is_visible = nvdimm_visible, |
| 447 | 447 | }; |
| 448 | 448 | |
| 449 | +static ssize_t result_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| 450 | +{ | |
| 451 | + struct nvdimm *nvdimm = to_nvdimm(dev); | |
| 452 | + enum nvdimm_fwa_result result; | |
| 453 | + | |
| 454 | + if (!nvdimm->fw_ops) | |
| 455 | + return -EOPNOTSUPP; | |
| 456 | + | |
| 457 | + nvdimm_bus_lock(dev); | |
| 458 | + result = nvdimm->fw_ops->activate_result(nvdimm); | |
| 459 | + nvdimm_bus_unlock(dev); | |
| 460 | + | |
| 461 | + switch (result) { | |
| 462 | + case NVDIMM_FWA_RESULT_NONE: | |
| 463 | + return sprintf(buf, "none\n"); | |
| 464 | + case NVDIMM_FWA_RESULT_SUCCESS: | |
| 465 | + return sprintf(buf, "success\n"); | |
| 466 | + case NVDIMM_FWA_RESULT_FAIL: | |
| 467 | + return sprintf(buf, "fail\n"); | |
| 468 | + case NVDIMM_FWA_RESULT_NOTSTAGED: | |
| 469 | + return sprintf(buf, "not_staged\n"); | |
| 470 | + case NVDIMM_FWA_RESULT_NEEDRESET: | |
| 471 | + return sprintf(buf, "need_reset\n"); | |
| 472 | + default: | |
| 473 | + return -ENXIO; | |
| 474 | + } | |
| 475 | +} | |
| 476 | +static DEVICE_ATTR_ADMIN_RO(result); | |
| 477 | + | |
| 478 | +static ssize_t activate_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| 479 | +{ | |
| 480 | + struct nvdimm *nvdimm = to_nvdimm(dev); | |
| 481 | + enum nvdimm_fwa_state state; | |
| 482 | + | |
| 483 | + if (!nvdimm->fw_ops) | |
| 484 | + return -EOPNOTSUPP; | |
| 485 | + | |
| 486 | + nvdimm_bus_lock(dev); | |
| 487 | + state = nvdimm->fw_ops->activate_state(nvdimm); | |
| 488 | + nvdimm_bus_unlock(dev); | |
| 489 | + | |
| 490 | + switch (state) { | |
| 491 | + case NVDIMM_FWA_IDLE: | |
| 492 | + return sprintf(buf, "idle\n"); | |
| 493 | + case NVDIMM_FWA_BUSY: | |
| 494 | + return sprintf(buf, "busy\n"); | |
| 495 | + case NVDIMM_FWA_ARMED: | |
| 496 | + return sprintf(buf, "armed\n"); | |
| 497 | + default: | |
| 498 | + return -ENXIO; | |
| 499 | + } | |
| 500 | +} | |
| 501 | + | |
| 502 | +static ssize_t activate_store(struct device *dev, struct device_attribute *attr, | |
| 503 | + const char *buf, size_t len) | |
| 504 | +{ | |
| 505 | + struct nvdimm *nvdimm = to_nvdimm(dev); | |
| 506 | + enum nvdimm_fwa_trigger arg; | |
| 507 | + int rc; | |
| 508 | + | |
| 509 | + if (!nvdimm->fw_ops) | |
| 510 | + return -EOPNOTSUPP; | |
| 511 | + | |
| 512 | + if (sysfs_streq(buf, "arm")) | |
| 513 | + arg = NVDIMM_FWA_ARM; | |
| 514 | + else if (sysfs_streq(buf, "disarm")) | |
| 515 | + arg = NVDIMM_FWA_DISARM; | |
| 516 | + else | |
| 517 | + return -EINVAL; | |
| 518 | + | |
| 519 | + nvdimm_bus_lock(dev); | |
| 520 | + rc = nvdimm->fw_ops->arm(nvdimm, arg); | |
| 521 | + nvdimm_bus_unlock(dev); | |
| 522 | + | |
| 523 | + if (rc < 0) | |
| 524 | + return rc; | |
| 525 | + return len; | |
| 526 | +} | |
| 527 | +static DEVICE_ATTR_ADMIN_RW(activate); | |
| 528 | + | |
| 529 | +static struct attribute *nvdimm_firmware_attributes[] = { | |
| 530 | + &dev_attr_activate.attr, | |
| 531 | + &dev_attr_result.attr, | |
| 532 | +}; | |
| 533 | + | |
| 534 | +static umode_t nvdimm_firmware_visible(struct kobject *kobj, struct attribute *a, int n) | |
| 535 | +{ | |
| 536 | + struct device *dev = container_of(kobj, typeof(*dev), kobj); | |
| 537 | + struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev); | |
| 538 | + struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc; | |
| 539 | + struct nvdimm *nvdimm = to_nvdimm(dev); | |
| 540 | + enum nvdimm_fwa_capability cap; | |
| 541 | + | |
| 542 | + if (!nd_desc->fw_ops) | |
| 543 | + return 0; | |
| 544 | + if (!nvdimm->fw_ops) | |
| 545 | + return 0; | |
| 546 | + | |
| 547 | + nvdimm_bus_lock(dev); | |
| 548 | + cap = nd_desc->fw_ops->capability(nd_desc); | |
| 549 | + nvdimm_bus_unlock(dev); | |
| 550 | + | |
| 551 | + if (cap < NVDIMM_FWA_CAP_QUIESCE) | |
| 552 | + return 0; | |
| 553 | + | |
| 554 | + return a->mode; | |
| 555 | +} | |
| 556 | + | |
| 557 | +static const struct attribute_group nvdimm_firmware_attribute_group = { | |
| 558 | + .name = "firmware", | |
| 559 | + .attrs = nvdimm_firmware_attributes, | |
| 560 | + .is_visible = nvdimm_firmware_visible, | |
| 561 | +}; | |
| 562 | + | |
| 449 | 563 | static const struct attribute_group *nvdimm_attribute_groups[] = { |
| 450 | 564 | &nd_device_attribute_group, |
| 451 | 565 | &nvdimm_attribute_group, |
| 566 | + &nvdimm_firmware_attribute_group, | |
| 452 | 567 | NULL, |
| 453 | 568 | }; |
| 454 | 569 | |
| @@ -45,6 +45,7 @@ struct nvdimm { · 44 unmodified lines | ||
| 45 | 45 | struct kernfs_node *overwrite_state; |
| 46 | 46 | } sec; |
| 47 | 47 | struct delayed_work dwork; |
| 48 | + const struct nvdimm_fw_ops *fw_ops; | |
| 48 | 49 | }; |
| 49 | 50 | |
| 50 | 51 | static inline unsigned long nvdimm_security_flags( |
| @@ -86,6 +86,7 @@ struct nvdimm_bus_descriptor { · 85 unmodified lines | ||
| 86 | 86 | int (*flush_probe)(struct nvdimm_bus_descriptor *nd_desc); |
| 87 | 87 | int (*clear_to_send)(struct nvdimm_bus_descriptor *nd_desc, |
| 88 | 88 | struct nvdimm *nvdimm, unsigned int cmd, void *data); |
| 89 | + const struct nvdimm_bus_fw_ops *fw_ops; | |
| 89 | 90 | }; |
| 90 | 91 | |
| 91 | 92 | struct nd_cmd_desc { |
| @@ -200,6 +201,49 @@ struct nvdimm_security_ops { · 108 unmodified lines | ||
| 200 | 201 | int (*query_overwrite)(struct nvdimm *nvdimm); |
| 201 | 202 | }; |
| 202 | 203 | |
| 204 | +enum nvdimm_fwa_state { | |
| 205 | + NVDIMM_FWA_INVALID, | |
| 206 | + NVDIMM_FWA_IDLE, | |
| 207 | + NVDIMM_FWA_ARMED, | |
| 208 | + NVDIMM_FWA_BUSY, | |
| 209 | + NVDIMM_FWA_ARM_OVERFLOW, | |
| 210 | +}; | |
| 211 | + | |
| 212 | +enum nvdimm_fwa_trigger { | |
| 213 | + NVDIMM_FWA_ARM, | |
| 214 | + NVDIMM_FWA_DISARM, | |
| 215 | +}; | |
| 216 | + | |
| 217 | +enum nvdimm_fwa_capability { | |
| 218 | + NVDIMM_FWA_CAP_INVALID, | |
| 219 | + NVDIMM_FWA_CAP_NONE, | |
| 220 | + NVDIMM_FWA_CAP_QUIESCE, | |
| 221 | + NVDIMM_FWA_CAP_LIVE, | |
| 222 | +}; | |
| 223 | + | |
| 224 | +enum nvdimm_fwa_result { | |
| 225 | + NVDIMM_FWA_RESULT_INVALID, | |
| 226 | + NVDIMM_FWA_RESULT_NONE, | |
| 227 | + NVDIMM_FWA_RESULT_SUCCESS, | |
| 228 | + NVDIMM_FWA_RESULT_NOTSTAGED, | |
| 229 | + NVDIMM_FWA_RESULT_NEEDRESET, | |
| 230 | + NVDIMM_FWA_RESULT_FAIL, | |
| 231 | +}; | |
| 232 | + | |
| 233 | +struct nvdimm_bus_fw_ops { | |
| 234 | + enum nvdimm_fwa_state (*activate_state) | |
| 235 | + (struct nvdimm_bus_descriptor *nd_desc); | |
| 236 | + enum nvdimm_fwa_capability (*capability) | |
| 237 | + (struct nvdimm_bus_descriptor *nd_desc); | |
| 238 | + int (*activate)(struct nvdimm_bus_descriptor *nd_desc); | |
| 239 | +}; | |
| 240 | + | |
| 241 | +struct nvdimm_fw_ops { | |
| 242 | + enum nvdimm_fwa_state (*activate_state)(struct nvdimm *nvdimm); | |
| 243 | + enum nvdimm_fwa_result (*activate_result)(struct nvdimm *nvdimm); | |
| 244 | + int (*arm)(struct nvdimm *nvdimm, enum nvdimm_fwa_trigger arg); | |
| 245 | +}; | |
| 246 | + | |
| 203 | 247 | void badrange_init(struct badrange *badrange); |
| 204 | 248 | int badrange_add(struct badrange *badrange, u64 addr, u64 length); |
| 205 | 249 | void badrange_forget(struct badrange *badrange, phys_addr_t start, |
| @@ -453,6 +453,8 @@ extern bool hibernation_available(void); · 452 unmodified lines | ||
| 453 | 453 | asmlinkage int swsusp_save(void); |
| 454 | 454 | extern struct pbe *restore_pblist; |
| 455 | 455 | int pfn_is_nosave(unsigned long pfn); |
| 456 | + | |
| 457 | +int hibernate_quiet_exec(int (*func)(void *data), void *data); | |
| 456 | 458 | #else /* CONFIG_HIBERNATION */ |
| 457 | 459 | static inline void register_nosave_region(unsigned long b, unsigned long e) {} |
| 458 | 460 | static inline void register_nosave_region_late(unsigned long b, unsigned long e) {} |
| @@ -464,6 +466,10 @@ static inline void hibernation_set_ops(const struct platform_hibernation_ops *op · 5 unmodified lines | ||
| 464 | 466 | static inline int hibernate(void) { return -ENOSYS; } |
| 465 | 467 | static inline bool system_entering_hibernation(void) { return false; } |
| 466 | 468 | static inline bool hibernation_available(void) { return false; } |
| 469 | + | |
| 470 | +static inline int hibernate_quiet_exec(int (*func)(void *data), void *data) { | |
| 471 | + return -ENOTSUPP; | |
| 472 | +} | |
| 467 | 473 | #endif /* CONFIG_HIBERNATION */ |
| 468 | 474 | |
| 469 | 475 | #ifdef CONFIG_HIBERNATION_SNAPSHOT_DEV |
| @@ -795,6 +795,103 @@ int hibernate(void) · 794 unmodified lines | ||
| 795 | 795 | return error; |
| 796 | 796 | } |
| 797 | 797 | |
| 798 | +/** | |
| 799 | + * hibernate_quiet_exec - Execute a function with all devices frozen. | |
| 800 | + * @func: Function to execute. | |
| 801 | + * @data: Data pointer to pass to @func. | |
| 802 | + * | |
| 803 | + * Return the @func return value or an error code if it cannot be executed. | |
| 804 | + */ | |
| 805 | +int hibernate_quiet_exec(int (*func)(void *data), void *data) | |
| 806 | +{ | |
| 807 | + int error, nr_calls = 0; | |
| 808 | + | |
| 809 | + lock_system_sleep(); | |
| 810 | + | |
| 811 | + if (!hibernate_acquire()) { | |
| 812 | + error = -EBUSY; | |
| 813 | + goto unlock; | |
| 814 | + } | |
| 815 | + | |
| 816 | + pm_prepare_console(); | |
| 817 | + | |
| 818 | + error = __pm_notifier_call_chain(PM_HIBERNATION_PREPARE, -1, &nr_calls); | |
| 819 | + if (error) { | |
| 820 | + nr_calls--; | |
| 821 | + goto exit; | |
| 822 | + } | |
| 823 | + | |
| 824 | + error = freeze_processes(); | |
| 825 | + if (error) | |
| 826 | + goto exit; | |
| 827 | + | |
| 828 | + lock_device_hotplug(); | |
| 829 | + | |
| 830 | + pm_suspend_clear_flags(); | |
| 831 | + | |
| 832 | + error = platform_begin(true); | |
| 833 | + if (error) | |
| 834 | + goto thaw; | |
| 835 | + | |
| 836 | + error = freeze_kernel_threads(); | |
| 837 | + if (error) | |
| 838 | + goto thaw; | |
| 839 | + | |
| 840 | + error = dpm_prepare(PMSG_FREEZE); | |
| 841 | + if (error) | |
| 842 | + goto dpm_complete; | |
| 843 | + | |
| 844 | + suspend_console(); | |
| 845 | + | |
| 846 | + error = dpm_suspend(PMSG_FREEZE); | |
| 847 | + if (error) | |
| 848 | + goto dpm_resume; | |
| 849 | + | |
| 850 | + error = dpm_suspend_end(PMSG_FREEZE); | |
| 851 | + if (error) | |
| 852 | + goto dpm_resume; | |
| 853 | + | |
| 854 | + error = platform_pre_snapshot(true); | |
| 855 | + if (error) | |
| 856 | + goto skip; | |
| 857 | + | |
| 858 | + error = func(data); | |
| 859 | + | |
| 860 | +skip: | |
| 861 | + platform_finish(true); | |
| 862 | + | |
| 863 | + dpm_resume_start(PMSG_THAW); | |
| 864 | + | |
| 865 | +dpm_resume: | |
| 866 | + dpm_resume(PMSG_THAW); | |
| 867 | + | |
| 868 | + resume_console(); | |
| 869 | + | |
| 870 | +dpm_complete: | |
| 871 | + dpm_complete(PMSG_THAW); | |
| 872 | + | |
| 873 | + thaw_kernel_threads(); | |
| 874 | + | |
| 875 | +thaw: | |
| 876 | + platform_end(true); | |
| 877 | + | |
| 878 | + unlock_device_hotplug(); | |
| 879 | + | |
| 880 | + thaw_processes(); | |
| 881 | + | |
| 882 | +exit: | |
| 883 | + __pm_notifier_call_chain(PM_POST_HIBERNATION, nr_calls, NULL); | |
| 884 | + | |
| 885 | + pm_restore_console(); | |
| 886 | + | |
| 887 | + hibernate_release(); | |
| 888 | + | |
| 889 | +unlock: | |
| 890 | + unlock_system_sleep(); | |
| 891 | + | |
| 892 | + return error; | |
| 893 | +} | |
| 894 | +EXPORT_SYMBOL_GPL(hibernate_quiet_exec); | |
| 798 | 895 | |
| 799 | 896 | /** |
| 800 | 897 | * software_resume - Resume from a saved hibernation image. |