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 | #include <linux/libnvdimm.h> | 5 | #include <linux/libnvdimm.h> |
| 6 | #include <linux/badblocks.h> | 6 | #include <linux/badblocks.h> |
| 7 | #include <linux/suspend.h> | ||
| 7 | #include <linux/export.h> | 8 | #include <linux/export.h> |
| 8 | #include <linux/module.h> | 9 | #include <linux/module.h> |
| 9 | #include <linux/blkdev.h> | 10 | #include <linux/blkdev.h> |
| @@ -389,8 +390,156 @@ static const struct attribute_group nvdimm_bus_attribute_group = { · 379 unmodified lines | |||
| 389 | .attrs = nvdimm_bus_attributes, | 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 | const struct attribute_group *nvdimm_bus_attribute_groups[] = { | 540 | const struct attribute_group *nvdimm_bus_attribute_groups[] = { |
| 393 | &nvdimm_bus_attribute_group, | 541 | &nvdimm_bus_attribute_group, |
| 542 | &nvdimm_bus_firmware_attribute_group, | ||
| 394 | NULL, | 543 | NULL, |
| 395 | }; | 544 | }; |
| 396 | 545 | ||
| @@ -446,9 +446,124 @@ static const struct attribute_group nvdimm_attribute_group = { · 445 unmodified lines | |||
| 446 | .is_visible = nvdimm_visible, | 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 | static const struct attribute_group *nvdimm_attribute_groups[] = { | 563 | static const struct attribute_group *nvdimm_attribute_groups[] = { |
| 450 | &nd_device_attribute_group, | 564 | &nd_device_attribute_group, |
| 451 | &nvdimm_attribute_group, | 565 | &nvdimm_attribute_group, |
| 566 | &nvdimm_firmware_attribute_group, | ||
| 452 | NULL, | 567 | NULL, |
| 453 | }; | 568 | }; |
| 454 | 569 | ||
| @@ -45,6 +45,7 @@ struct nvdimm { · 44 unmodified lines | |||
| 45 | struct kernfs_node *overwrite_state; | 45 | struct kernfs_node *overwrite_state; |
| 46 | } sec; | 46 | } sec; |
| 47 | struct delayed_work dwork; | 47 | struct delayed_work dwork; |
| 48 | const struct nvdimm_fw_ops *fw_ops; | ||
| 48 | }; | 49 | }; |
| 49 | 50 | ||
| 50 | static inline unsigned long nvdimm_security_flags( | 51 | static inline unsigned long nvdimm_security_flags( |
| @@ -86,6 +86,7 @@ struct nvdimm_bus_descriptor { · 85 unmodified lines | |||
| 86 | int (*flush_probe)(struct nvdimm_bus_descriptor *nd_desc); | 86 | int (*flush_probe)(struct nvdimm_bus_descriptor *nd_desc); |
| 87 | int (*clear_to_send)(struct nvdimm_bus_descriptor *nd_desc, | 87 | int (*clear_to_send)(struct nvdimm_bus_descriptor *nd_desc, |
| 88 | struct nvdimm *nvdimm, unsigned int cmd, void *data); | 88 | struct nvdimm *nvdimm, unsigned int cmd, void *data); |
| 89 | const struct nvdimm_bus_fw_ops *fw_ops; | ||
| 89 | }; | 90 | }; |
| 90 | 91 | ||
| 91 | struct nd_cmd_desc { | 92 | struct nd_cmd_desc { |
| @@ -200,6 +201,49 @@ struct nvdimm_security_ops { · 108 unmodified lines | |||
| 200 | int (*query_overwrite)(struct nvdimm *nvdimm); | 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 | void badrange_init(struct badrange *badrange); | 247 | void badrange_init(struct badrange *badrange); |
| 204 | int badrange_add(struct badrange *badrange, u64 addr, u64 length); | 248 | int badrange_add(struct badrange *badrange, u64 addr, u64 length); |
| 205 | void badrange_forget(struct badrange *badrange, phys_addr_t start, | 249 | void badrange_forget(struct badrange *badrange, phys_addr_t start, |
| @@ -453,6 +453,8 @@ extern bool hibernation_available(void); · 452 unmodified lines | |||
| 453 | asmlinkage int swsusp_save(void); | 453 | asmlinkage int swsusp_save(void); |
| 454 | extern struct pbe *restore_pblist; | 454 | extern struct pbe *restore_pblist; |
| 455 | int pfn_is_nosave(unsigned long pfn); | 455 | int pfn_is_nosave(unsigned long pfn); |
| 456 | |||
| 457 | int hibernate_quiet_exec(int (*func)(void *data), void *data); | ||
| 456 | #else /* CONFIG_HIBERNATION */ | 458 | #else /* CONFIG_HIBERNATION */ |
| 457 | static inline void register_nosave_region(unsigned long b, unsigned long e) {} | 459 | static inline void register_nosave_region(unsigned long b, unsigned long e) {} |
| 458 | static inline void register_nosave_region_late(unsigned long b, unsigned long e) {} | 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 | static inline int hibernate(void) { return -ENOSYS; } | 466 | static inline int hibernate(void) { return -ENOSYS; } |
| 465 | static inline bool system_entering_hibernation(void) { return false; } | 467 | static inline bool system_entering_hibernation(void) { return false; } |
| 466 | static inline bool hibernation_available(void) { return false; } | 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 | #endif /* CONFIG_HIBERNATION */ | 473 | #endif /* CONFIG_HIBERNATION */ |
| 468 | 474 | ||
| 469 | #ifdef CONFIG_HIBERNATION_SNAPSHOT_DEV | 475 | #ifdef CONFIG_HIBERNATION_SNAPSHOT_DEV |
| @@ -795,6 +795,103 @@ int hibernate(void) · 794 unmodified lines | |||
| 795 | return error; | 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 | * software_resume - Resume from a saved hibernation image. | 897 | * software_resume - Resume from a saved hibernation image. |