Skip to content

Commit 48001ea

Browse files
djbwstellarhopper
authored andcommitted
PM, libnvdimm: Add runtime firmware activation support
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/] Cc: Pavel Machek <[email protected]> Cc: Ira Weiny <[email protected]> Cc: Len Brown <[email protected]> Cc: Jonathan Corbet <[email protected]> Cc: Dave Jiang <[email protected]> Cc: Vishal Verma <[email protected]> Reported-by: kernel test robot <[email protected]> Co-developed-by: "Rafael J. Wysocki" <[email protected]> Signed-off-by: "Rafael J. Wysocki" <[email protected]> Signed-off-by: Dan Williams <[email protected]> Signed-off-by: Vishal Verma <[email protected]>
1 parent 5cf81ce commit 48001ea

File tree

8 files changed

+500
-0
lines changed

8 files changed

+500
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The libnvdimm sub-system implements a common sysfs interface for
2+
platform nvdimm resources. See Documentation/driver-api/nvdimm/.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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/

drivers/nvdimm/core.c

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55
#include <linux/libnvdimm.h>
66
#include <linux/badblocks.h>
7+
#include <linux/suspend.h>
78
#include <linux/export.h>
89
#include <linux/module.h>
910
#include <linux/blkdev.h>
@@ -389,8 +390,156 @@ static const struct attribute_group nvdimm_bus_attribute_group = {
389390
.attrs = nvdimm_bus_attributes,
390391
};
391392

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+
392540
const struct attribute_group *nvdimm_bus_attribute_groups[] = {
393541
&nvdimm_bus_attribute_group,
542+
&nvdimm_bus_firmware_attribute_group,
394543
NULL,
395544
};
396545

drivers/nvdimm/dimm_devs.c

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,9 +446,124 @@ static const struct attribute_group nvdimm_attribute_group = {
446446
.is_visible = nvdimm_visible,
447447
};
448448

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+
449563
static const struct attribute_group *nvdimm_attribute_groups[] = {
450564
&nd_device_attribute_group,
451565
&nvdimm_attribute_group,
566+
&nvdimm_firmware_attribute_group,
452567
NULL,
453568
};
454569

drivers/nvdimm/nd-core.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ struct nvdimm {
4545
struct kernfs_node *overwrite_state;
4646
} sec;
4747
struct delayed_work dwork;
48+
const struct nvdimm_fw_ops *fw_ops;
4849
};
4950

5051
static inline unsigned long nvdimm_security_flags(

0 commit comments

Comments
 (0)