Commit 4bf5e361 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'libnvdimm-for-5.9' of git://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm

Pull libnvdimm updayes from Vishal Verma:
 "You'd normally receive this pull request from Dan Williams, but he's
  busy watching a newborn (Congrats Dan!), so I'm watching libnvdimm
  this cycle.

  This adds a new feature in libnvdimm - 'Runtime Firmware Activation',
  and a few small cleanups and fixes in libnvdimm and DAX. I'd
  originally intended to make separate topic-based pull requests - one
  for libnvdimm, and one for DAX, but some of the DAX material fell out
  since it wasn't quite ready.

  Summary:

   - add 'Runtime Firmware Activation' support for NVDIMMs that
     advertise the relevant capability

   - misc libnvdimm and DAX cleanups"

* tag 'libnvdimm-for-5.9' of git://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm:
  libnvdimm/security: ensure sysfs poll thread woke up and fetch updated attr
  libnvdimm/security: the 'security' attr never show 'overwrite' state
  libnvdimm/security: fix a typo
  ACPI: NFIT: Fix ARS zero-sized allocation
  dax: Fix incorrect argument passed to xas_set_err()
  ACPI: NFIT: Add runtime firmware activate support
  PM, libnvdimm: Add runtime firmware activation support
  libnvdimm: Convert to DEVICE_ATTR_ADMIN_RO()
  drivers/dax: Expand lock scope to cover the use of addresses
  fs/dax: Remove unused size parameter
  dax: print error message by pr_info() in __generic_fsdax_supported()
  driver-core: Introduce DEVICE_ATTR_ADMIN_{RO,RW}
  tools/testing/nvdimm: Emulate firmware activation commands
  tools/testing/nvdimm: Prepare nfit_ctl_test() for ND_CMD_CALL emulation
  tools/testing/nvdimm: Add command debug messages
  tools/testing/nvdimm: Cleanup dimm index passing
  ACPI: NFIT: Define runtime firmware activation commands
  ACPI: NFIT: Move bus_dsm_mask out of generic nvdimm_bus_descriptor
  libnvdimm: Validate command family indices
parents 00e4db51 7f674025
...@@ -202,6 +202,25 @@ Description: ...@@ -202,6 +202,25 @@ Description:
functions. See the section named 'NVDIMM Root Device _DSMs' in functions. See the section named 'NVDIMM Root Device _DSMs' in
the ACPI specification. the ACPI specification.
What: /sys/bus/nd/devices/ndbusX/nfit/firmware_activate_noidle
Date: Apr, 2020
KernelVersion: v5.8
Contact: linux-nvdimm@lists.01.org
Description:
(RW) The Intel platform implementation of firmware activate
support exposes an option let the platform force idle devices in
the system over the activation event, or trust that the OS will
do it. The safe default is to let the platform force idle
devices since the kernel is already in a suspend state, and on
the chance that a driver does not properly quiesce bus-mastering
after a suspend callback the platform will handle it. However,
the activation might abort if, for example, platform firmware
determines that the activation time exceeds the max PCI-E
completion timeout. Since the platform does not know whether the
OS is running the activation from a suspend context it aborts,
but if the system owner trusts driver suspend callback to be
sufficient then 'firmware_activation_noidle' can be
enabled to bypass the activation abort.
What: /sys/bus/nd/devices/regionX/nfit/range_index What: /sys/bus/nd/devices/regionX/nfit/range_index
Date: Jun, 2015 Date: Jun, 2015
......
The libnvdimm sub-system implements a common sysfs interface for
platform nvdimm resources. See Documentation/driver-api/nvdimm/.
.. SPDX-License-Identifier: GPL-2.0
==================================
NVDIMM Runtime Firmware Activation
==================================
Some persistent memory devices run a firmware locally on the device /
"DIMM" to perform tasks like media management, capacity provisioning,
and health monitoring. The process of updating that firmware typically
involves a reboot because it has implications for in-flight memory
transactions. However, reboots are disruptive and at least the Intel
persistent memory platform implementation, described by the Intel ACPI
DSM specification [1], has added support for activating firmware at
runtime.
A native sysfs interface is implemented in libnvdimm to allow platform
to advertise and control their local runtime firmware activation
capability.
The libnvdimm bus object, ndbusX, implements an ndbusX/firmware/activate
attribute that shows the state of the firmware activation as one of 'idle',
'armed', 'overflow', and 'busy'.
- idle:
No devices are set / armed to activate firmware
- armed:
At least one device is armed
- busy:
In the busy state armed devices are in the process of transitioning
back to idle and completing an activation cycle.
- overflow:
If the platform has a concept of incremental work needed to perform
the activation it could be the case that too many DIMMs are armed for
activation. In that scenario the potential for firmware activation to
timeout is indicated by the 'overflow' state.
The 'ndbusX/firmware/activate' property can be written with a value of
either 'live', or 'quiesce'. A value of 'quiesce' triggers the kernel to
run firmware activation from within the equivalent of the hibernation
'freeze' state where drivers and applications are notified to stop their
modifications of system memory. A value of 'live' attempts
firmware activation without this hibernation cycle. The
'ndbusX/firmware/activate' property will be elided completely if no
firmware activation capability is detected.
Another property 'ndbusX/firmware/capability' indicates a value of
'live' or 'quiesce', where 'live' indicates that the firmware
does not require or inflict any quiesce period on the system to update
firmware. A capability value of 'quiesce' indicates that firmware does
expect and injects a quiet period for the memory controller, but 'live'
may still be written to 'ndbusX/firmware/activate' as an override to
assume the risk of racing firmware update with in-flight device and
application activity. The 'ndbusX/firmware/capability' property will be
elided completely if no firmware activation capability is detected.
The libnvdimm memory-device / DIMM object, nmemX, implements
'nmemX/firmware/activate' and 'nmemX/firmware/result' attributes to
communicate the per-device firmware activation state. Similar to the
'ndbusX/firmware/activate' attribute, the 'nmemX/firmware/activate'
attribute indicates 'idle', 'armed', or 'busy'. The state transitions
from 'armed' to 'idle' when the system is prepared to activate firmware,
firmware staged + state set to armed, and 'ndbusX/firmware/activate' is
triggered. After that activation event the nmemX/firmware/result
attribute reflects the state of the last activation as one of:
- none:
No runtime activation triggered since the last time the device was reset
- success:
The last runtime activation completed successfully.
- fail:
The last runtime activation failed for device-specific reasons.
- not_staged:
The last runtime activation failed due to a sequencing error of the
firmware image not being staged.
- need_reset:
Runtime firmware activation failed, but the firmware can still be
activated via the legacy method of power-cycling the system.
[1]: https://docs.pmem.io/persistent-memory/
This diff is collapsed.
This diff is collapsed.
...@@ -111,4 +111,65 @@ struct nd_intel_master_secure_erase { ...@@ -111,4 +111,65 @@ struct nd_intel_master_secure_erase {
u8 passphrase[ND_INTEL_PASSPHRASE_SIZE]; u8 passphrase[ND_INTEL_PASSPHRASE_SIZE];
u32 status; u32 status;
} __packed; } __packed;
#define ND_INTEL_FWA_IDLE 0
#define ND_INTEL_FWA_ARMED 1
#define ND_INTEL_FWA_BUSY 2
#define ND_INTEL_DIMM_FWA_NONE 0
#define ND_INTEL_DIMM_FWA_NOTSTAGED 1
#define ND_INTEL_DIMM_FWA_SUCCESS 2
#define ND_INTEL_DIMM_FWA_NEEDRESET 3
#define ND_INTEL_DIMM_FWA_MEDIAFAILED 4
#define ND_INTEL_DIMM_FWA_ABORT 5
#define ND_INTEL_DIMM_FWA_NOTSUPP 6
#define ND_INTEL_DIMM_FWA_ERROR 7
struct nd_intel_fw_activate_dimminfo {
u32 status;
u16 result;
u8 state;
u8 reserved[7];
} __packed;
#define ND_INTEL_DIMM_FWA_ARM 1
#define ND_INTEL_DIMM_FWA_DISARM 0
struct nd_intel_fw_activate_arm {
u8 activate_arm;
u32 status;
} __packed;
/* Root device command payloads */
#define ND_INTEL_BUS_FWA_CAP_FWQUIESCE (1 << 0)
#define ND_INTEL_BUS_FWA_CAP_OSQUIESCE (1 << 1)
#define ND_INTEL_BUS_FWA_CAP_RESET (1 << 2)
struct nd_intel_bus_fw_activate_businfo {
u32 status;
u16 reserved;
u8 state;
u8 capability;
u64 activate_tmo;
u64 cpu_quiesce_tmo;
u64 io_quiesce_tmo;
u64 max_quiesce_tmo;
} __packed;
#define ND_INTEL_BUS_FWA_STATUS_NOARM (6 | 1 << 16)
#define ND_INTEL_BUS_FWA_STATUS_BUSY (6 | 2 << 16)
#define ND_INTEL_BUS_FWA_STATUS_NOFW (6 | 3 << 16)
#define ND_INTEL_BUS_FWA_STATUS_TMO (6 | 4 << 16)
#define ND_INTEL_BUS_FWA_STATUS_NOIDLE (6 | 5 << 16)
#define ND_INTEL_BUS_FWA_STATUS_ABORT (6 | 6 << 16)
#define ND_INTEL_BUS_FWA_IODEV_FORCE_IDLE (0)
#define ND_INTEL_BUS_FWA_IODEV_OS_IDLE (1)
struct nd_intel_bus_fw_activate {
u8 iodev_state;
u32 status;
} __packed;
extern const struct nvdimm_fw_ops *intel_fw_ops;
extern const struct nvdimm_bus_fw_ops *intel_bus_fw_ops;
#endif #endif
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
/* https://pmem.io/documents/NVDIMM_DSM_Interface-V1.6.pdf */ /* https://pmem.io/documents/NVDIMM_DSM_Interface-V1.6.pdf */
#define UUID_NFIT_DIMM "4309ac30-0d11-11e4-9191-0800200c9a66" #define UUID_NFIT_DIMM "4309ac30-0d11-11e4-9191-0800200c9a66"
#define UUID_INTEL_BUS "c7d8acd4-2df8-4b82-9f65-a325335af149"
/* https://github.com/HewlettPackard/hpe-nvm/blob/master/Documentation/ */ /* https://github.com/HewlettPackard/hpe-nvm/blob/master/Documentation/ */
#define UUID_NFIT_DIMM_N_HPE1 "9002c334-acf3-4c0e-9642-a235f0d53bc6" #define UUID_NFIT_DIMM_N_HPE1 "9002c334-acf3-4c0e-9642-a235f0d53bc6"
...@@ -33,7 +34,6 @@ ...@@ -33,7 +34,6 @@
| ACPI_NFIT_MEM_RESTORE_FAILED | ACPI_NFIT_MEM_FLUSH_FAILED \ | ACPI_NFIT_MEM_RESTORE_FAILED | ACPI_NFIT_MEM_FLUSH_FAILED \
| ACPI_NFIT_MEM_NOT_ARMED | ACPI_NFIT_MEM_MAP_FAILED) | ACPI_NFIT_MEM_NOT_ARMED | ACPI_NFIT_MEM_MAP_FAILED)
#define NVDIMM_FAMILY_MAX NVDIMM_FAMILY_HYPERV
#define NVDIMM_CMD_MAX 31 #define NVDIMM_CMD_MAX 31
#define NVDIMM_STANDARD_CMDMASK \ #define NVDIMM_STANDARD_CMDMASK \
...@@ -66,6 +66,13 @@ enum nvdimm_family_cmds { ...@@ -66,6 +66,13 @@ enum nvdimm_family_cmds {
NVDIMM_INTEL_QUERY_OVERWRITE = 26, NVDIMM_INTEL_QUERY_OVERWRITE = 26,
NVDIMM_INTEL_SET_MASTER_PASSPHRASE = 27, NVDIMM_INTEL_SET_MASTER_PASSPHRASE = 27,
NVDIMM_INTEL_MASTER_SECURE_ERASE = 28, NVDIMM_INTEL_MASTER_SECURE_ERASE = 28,
NVDIMM_INTEL_FW_ACTIVATE_DIMMINFO = 29,
NVDIMM_INTEL_FW_ACTIVATE_ARM = 30,
};
enum nvdimm_bus_family_cmds {
NVDIMM_BUS_INTEL_FW_ACTIVATE_BUSINFO = 1,
NVDIMM_BUS_INTEL_FW_ACTIVATE = 2,
}; };
#define NVDIMM_INTEL_SECURITY_CMDMASK \ #define NVDIMM_INTEL_SECURITY_CMDMASK \
...@@ -76,13 +83,22 @@ enum nvdimm_family_cmds { ...@@ -76,13 +83,22 @@ enum nvdimm_family_cmds {
| 1 << NVDIMM_INTEL_SET_MASTER_PASSPHRASE \ | 1 << NVDIMM_INTEL_SET_MASTER_PASSPHRASE \
| 1 << NVDIMM_INTEL_MASTER_SECURE_ERASE) | 1 << NVDIMM_INTEL_MASTER_SECURE_ERASE)
#define NVDIMM_INTEL_FW_ACTIVATE_CMDMASK \
(1 << NVDIMM_INTEL_FW_ACTIVATE_DIMMINFO | 1 << NVDIMM_INTEL_FW_ACTIVATE_ARM)
#define NVDIMM_BUS_INTEL_FW_ACTIVATE_CMDMASK \
(1 << NVDIMM_BUS_INTEL_FW_ACTIVATE_BUSINFO | 1 << NVDIMM_BUS_INTEL_FW_ACTIVATE)
#define NVDIMM_INTEL_CMDMASK \ #define NVDIMM_INTEL_CMDMASK \
(NVDIMM_STANDARD_CMDMASK | 1 << NVDIMM_INTEL_GET_MODES \ (NVDIMM_STANDARD_CMDMASK | 1 << NVDIMM_INTEL_GET_MODES \
| 1 << NVDIMM_INTEL_GET_FWINFO | 1 << NVDIMM_INTEL_START_FWUPDATE \ | 1 << NVDIMM_INTEL_GET_FWINFO | 1 << NVDIMM_INTEL_START_FWUPDATE \
| 1 << NVDIMM_INTEL_SEND_FWUPDATE | 1 << NVDIMM_INTEL_FINISH_FWUPDATE \ | 1 << NVDIMM_INTEL_SEND_FWUPDATE | 1 << NVDIMM_INTEL_FINISH_FWUPDATE \
| 1 << NVDIMM_INTEL_QUERY_FWUPDATE | 1 << NVDIMM_INTEL_SET_THRESHOLD \ | 1 << NVDIMM_INTEL_QUERY_FWUPDATE | 1 << NVDIMM_INTEL_SET_THRESHOLD \
| 1 << NVDIMM_INTEL_INJECT_ERROR | 1 << NVDIMM_INTEL_LATCH_SHUTDOWN \ | 1 << NVDIMM_INTEL_INJECT_ERROR | 1 << NVDIMM_INTEL_LATCH_SHUTDOWN \
| NVDIMM_INTEL_SECURITY_CMDMASK) | NVDIMM_INTEL_SECURITY_CMDMASK | NVDIMM_INTEL_FW_ACTIVATE_CMDMASK)
#define NVDIMM_INTEL_DENY_CMDMASK \
(NVDIMM_INTEL_SECURITY_CMDMASK | NVDIMM_INTEL_FW_ACTIVATE_CMDMASK)
enum nfit_uuids { enum nfit_uuids {
/* for simplicity alias the uuid index with the family id */ /* for simplicity alias the uuid index with the family id */
...@@ -91,6 +107,11 @@ enum nfit_uuids { ...@@ -91,6 +107,11 @@ enum nfit_uuids {
NFIT_DEV_DIMM_N_HPE2 = NVDIMM_FAMILY_HPE2, NFIT_DEV_DIMM_N_HPE2 = NVDIMM_FAMILY_HPE2,
NFIT_DEV_DIMM_N_MSFT = NVDIMM_FAMILY_MSFT, NFIT_DEV_DIMM_N_MSFT = NVDIMM_FAMILY_MSFT,
NFIT_DEV_DIMM_N_HYPERV = NVDIMM_FAMILY_HYPERV, NFIT_DEV_DIMM_N_HYPERV = NVDIMM_FAMILY_HYPERV,
/*
* to_nfit_bus_uuid() expects to translate bus uuid family ids
* to a UUID index using NVDIMM_FAMILY_MAX as an offset
*/
NFIT_BUS_INTEL = NVDIMM_FAMILY_MAX + NVDIMM_BUS_FAMILY_INTEL,
NFIT_SPA_VOLATILE, NFIT_SPA_VOLATILE,
NFIT_SPA_PM, NFIT_SPA_PM,
NFIT_SPA_DCR, NFIT_SPA_DCR,
...@@ -199,6 +220,9 @@ struct nfit_mem { ...@@ -199,6 +220,9 @@ struct nfit_mem {
struct list_head list; struct list_head list;
struct acpi_device *adev; struct acpi_device *adev;
struct acpi_nfit_desc *acpi_desc; struct acpi_nfit_desc *acpi_desc;
enum nvdimm_fwa_state fwa_state;
enum nvdimm_fwa_result fwa_result;
int fwa_count;
char id[NFIT_DIMM_ID_LEN+1]; char id[NFIT_DIMM_ID_LEN+1];
struct resource *flush_wpq; struct resource *flush_wpq;
unsigned long dsm_mask; unsigned long dsm_mask;
...@@ -238,11 +262,17 @@ struct acpi_nfit_desc { ...@@ -238,11 +262,17 @@ struct acpi_nfit_desc {
unsigned long scrub_flags; unsigned long scrub_flags;
unsigned long dimm_cmd_force_en; unsigned long dimm_cmd_force_en;
unsigned long bus_cmd_force_en; unsigned long bus_cmd_force_en;
unsigned long bus_nfit_cmd_force_en; unsigned long bus_dsm_mask;
unsigned long family_dsm_mask[NVDIMM_BUS_FAMILY_MAX + 1];
unsigned int platform_cap; unsigned int platform_cap;
unsigned int scrub_tmo; unsigned int scrub_tmo;
int (*blk_do_io)(struct nd_blk_region *ndbr, resource_size_t dpa, int (*blk_do_io)(struct nd_blk_region *ndbr, resource_size_t dpa,
void *iobuf, u64 len, int rw); void *iobuf, u64 len, int rw);
enum nvdimm_fwa_state fwa_state;
enum nvdimm_fwa_capability fwa_cap;
int fwa_count;
bool fwa_noidle;
bool fwa_nosuspend;
}; };
enum scrub_mode { enum scrub_mode {
...@@ -345,4 +375,6 @@ void __acpi_nvdimm_notify(struct device *dev, u32 event); ...@@ -345,4 +375,6 @@ void __acpi_nvdimm_notify(struct device *dev, u32 event);
int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc, struct nvdimm *nvdimm, int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc, struct nvdimm *nvdimm,
unsigned int cmd, void *buf, unsigned int buf_len, int *cmd_rc); unsigned int cmd, void *buf, unsigned int buf_len, int *cmd_rc);
void acpi_nfit_desc_init(struct acpi_nfit_desc *acpi_desc, struct device *dev); void acpi_nfit_desc_init(struct acpi_nfit_desc *acpi_desc, struct device *dev);
bool intel_fwa_supported(struct nvdimm_bus *nvdimm_bus);
extern struct device_attribute dev_attr_firmware_activate_noidle;
#endif /* __NFIT_H__ */ #endif /* __NFIT_H__ */
...@@ -80,14 +80,14 @@ bool __generic_fsdax_supported(struct dax_device *dax_dev, ...@@ -80,14 +80,14 @@ bool __generic_fsdax_supported(struct dax_device *dax_dev,
int err, id; int err, id;
if (blocksize != PAGE_SIZE) { if (blocksize != PAGE_SIZE) {
pr_debug("%s: error: unsupported blocksize for dax\n", pr_info("%s: error: unsupported blocksize for dax\n",
bdevname(bdev, buf)); bdevname(bdev, buf));
return false; return false;
} }
err = bdev_dax_pgoff(bdev, start, PAGE_SIZE, &pgoff); err = bdev_dax_pgoff(bdev, start, PAGE_SIZE, &pgoff);
if (err) { if (err) {
pr_debug("%s: error: unaligned partition for dax\n", pr_info("%s: error: unaligned partition for dax\n",
bdevname(bdev, buf)); bdevname(bdev, buf));
return false; return false;
} }
...@@ -95,7 +95,7 @@ bool __generic_fsdax_supported(struct dax_device *dax_dev, ...@@ -95,7 +95,7 @@ bool __generic_fsdax_supported(struct dax_device *dax_dev,
last_page = PFN_DOWN((start + sectors - 1) * 512) * PAGE_SIZE / 512; last_page = PFN_DOWN((start + sectors - 1) * 512) * PAGE_SIZE / 512;
err = bdev_dax_pgoff(bdev, last_page, PAGE_SIZE, &pgoff_end); err = bdev_dax_pgoff(bdev, last_page, PAGE_SIZE, &pgoff_end);
if (err) { if (err) {
pr_debug("%s: error: unaligned partition for dax\n", pr_info("%s: error: unaligned partition for dax\n",
bdevname(bdev, buf)); bdevname(bdev, buf));
return false; return false;
} }
...@@ -103,11 +103,11 @@ bool __generic_fsdax_supported(struct dax_device *dax_dev, ...@@ -103,11 +103,11 @@ bool __generic_fsdax_supported(struct dax_device *dax_dev,
id = dax_read_lock(); id = dax_read_lock();
len = dax_direct_access(dax_dev, pgoff, 1, &kaddr, &pfn); len = dax_direct_access(dax_dev, pgoff, 1, &kaddr, &pfn);
len2 = dax_direct_access(dax_dev, pgoff_end, 1, &end_kaddr, &end_pfn); len2 = dax_direct_access(dax_dev, pgoff_end, 1, &end_kaddr, &end_pfn);
dax_read_unlock(id);
if (len < 1 || len2 < 1) { if (len < 1 || len2 < 1) {
pr_debug("%s: error: dax access failed (%ld)\n", pr_info("%s: error: dax access failed (%ld)\n",
bdevname(bdev, buf), len < 1 ? len : len2); bdevname(bdev, buf), len < 1 ? len : len2);
dax_read_unlock(id);
return false; return false;
} }
...@@ -137,9 +137,10 @@ bool __generic_fsdax_supported(struct dax_device *dax_dev, ...@@ -137,9 +137,10 @@ bool __generic_fsdax_supported(struct dax_device *dax_dev,
put_dev_pagemap(end_pgmap); put_dev_pagemap(end_pgmap);
} }
dax_read_unlock(id);
if (!dax_enabled) { if (!dax_enabled) {
pr_debug("%s: error: dax support not enabled\n", pr_info("%s: error: dax support not enabled\n",
bdevname(bdev, buf)); bdevname(bdev, buf));
return false; return false;
} }
......
...@@ -1037,9 +1037,25 @@ static int __nd_ioctl(struct nvdimm_bus *nvdimm_bus, struct nvdimm *nvdimm, ...@@ -1037,9 +1037,25 @@ static int __nd_ioctl(struct nvdimm_bus *nvdimm_bus, struct nvdimm *nvdimm,
dimm_name = "bus"; dimm_name = "bus";
} }
/* Validate command family support against bus declared support */
if (cmd == ND_CMD_CALL) { if (cmd == ND_CMD_CALL) {
unsigned long *mask;
if (copy_from_user(&pkg, p, sizeof(pkg))) if (copy_from_user(&pkg, p, sizeof(pkg)))
return -EFAULT; return -EFAULT;
if (nvdimm) {
if (pkg.nd_family > NVDIMM_FAMILY_MAX)
return -EINVAL;
mask = &nd_desc->dimm_family_mask;
} else {
if (pkg.nd_family > NVDIMM_BUS_FAMILY_MAX)
return -EINVAL;
mask = &nd_desc->bus_family_mask;
}
if (!test_bit(pkg.nd_family, mask))
return -EINVAL;
} }
if (!desc || if (!desc ||
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
*/ */
#include <linux/libnvdimm.h> #include <linux/libnvdimm.h>
#include <linux/badblocks.h> #include <linux/badblocks.h>
#include <linux/suspend.h>
#include <linux/export.h> #include <linux/export.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/blkdev.h> #include <linux/blkdev.h>
...@@ -389,8 +390,156 @@ static const struct attribute_group nvdimm_bus_attribute_group = { ...@@ -389,8 +390,156 @@ static const struct attribute_group nvdimm_bus_attribute_group = {
.attrs = nvdimm_bus_attributes, .attrs = nvdimm_bus_attributes,
}; };
static ssize_t capability_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev);
struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc;
enum nvdimm_fwa_capability cap;
if (!nd_desc->fw_ops)
return -EOPNOTSUPP;
nvdimm_bus_lock(dev);
cap = nd_desc->fw_ops->capability(nd_desc);
nvdimm_bus_unlock(dev);
switch (cap) {
case NVDIMM_FWA_CAP_QUIESCE:
return sprintf(buf, "quiesce\n");
case NVDIMM_FWA_CAP_LIVE:
return sprintf(buf, "live\n");
default:
return -EOPNOTSUPP;
}
}
static DEVICE_ATTR_RO(capability);
static ssize_t activate_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev);
struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc;
enum nvdimm_fwa_capability cap;
enum nvdimm_fwa_state state;
if (!nd_desc->fw_ops)
return -EOPNOTSUPP;
nvdimm_bus_lock(dev);
cap = nd_desc->fw_ops->capability(nd_desc);
state = nd_desc->fw_ops->activate_state(nd_desc);
nvdimm_bus_unlock(dev);
if (cap < NVDIMM_FWA_CAP_QUIESCE)
return -EOPNOTSUPP;
switch (state) {
case NVDIMM_FWA_IDLE:
return sprintf(buf, "idle\n");
case NVDIMM_FWA_BUSY:
return sprintf(buf, "busy\n");
case NVDIMM_FWA_ARMED:
return sprintf(buf, "armed\n");
case NVDIMM_FWA_ARM_OVERFLOW:
return sprintf(buf, "overflow\n");
default:
return -ENXIO;
}
}
static int exec_firmware_activate(void *data)
{
struct nvdimm_bus_descriptor *nd_desc = data;
return nd_desc->fw_ops->activate(nd_desc);
}
static ssize_t activate_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t len)
{
struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev);
struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc;
enum nvdimm_fwa_state state;
bool quiesce;
ssize_t rc;
if (!nd_desc->fw_ops)
return -EOPNOTSUPP;
if (sysfs_streq(buf, "live"))
quiesce = false;
else if (sysfs_streq(buf, "quiesce"))
quiesce = true;
else
return -EINVAL;
nvdimm_bus_lock(dev);
state = nd_desc->fw_ops->activate_state(nd_desc);
switch (state) {
case NVDIMM_FWA_BUSY:
rc = -EBUSY;
break;
case NVDIMM_FWA_ARMED:
case NVDIMM_FWA_ARM_OVERFLOW:
if (quiesce)
rc = hibernate_quiet_exec(exec_firmware_activate, nd_desc);
else
rc = nd_desc->fw_ops->activate(nd_desc);
break;
case NVDIMM_FWA_IDLE:
default:
rc = -ENXIO;
}
nvdimm_bus_unlock(dev);
if (rc == 0)
rc = len;
return rc;
}
static DEVICE_ATTR_ADMIN_RW(activate);
static umode_t nvdimm_bus_firmware_visible(struct kobject *kobj, struct attribute *a, int n)
{
struct device *dev = container_of(kobj, typeof(*dev), kobj);
struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev);
struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc;
enum nvdimm_fwa_capability cap;
/*
* Both 'activate' and 'capability' disappear when no ops
* detected, or a negative capability is indicated.
*/
if (!nd_desc->fw_ops)
return 0;
nvdimm_bus_lock(dev);
cap = nd_desc->fw_ops->capability(nd_desc);
nvdimm_bus_unlock(dev);
if (cap < NVDIMM_FWA_CAP_QUIESCE)
return 0;
return a->mode;
}
static struct attribute *nvdimm_bus_firmware_attributes[] = {
&dev_attr_activate.attr,
&dev_attr_capability.attr,
NULL,
};
static const struct attribute_group nvdimm_bus_firmware_attribute_group = {
.name = "firmware",
.attrs = nvdimm_bus_firmware_attributes,
.is_visible = nvdimm_bus_firmware_visible,
};
const struct attribute_group *nvdimm_bus_attribute_groups[] = { const struct attribute_group *nvdimm_bus_attribute_groups[] = {
&nvdimm_bus_attribute_group, &nvdimm_bus_attribute_group,
&nvdimm_bus_firmware_attribute_group,
NULL, NULL,
}; };
......
...@@ -363,14 +363,14 @@ __weak ssize_t security_show(struct device *dev, ...@@ -363,14 +363,14 @@ __weak ssize_t security_show(struct device *dev,
{ {
struct nvdimm *nvdimm = to_nvdimm(dev); struct nvdimm *nvdimm = to_nvdimm(dev);
if (test_bit(NVDIMM_SECURITY_OVERWRITE, &nvdimm->sec.flags))
return sprintf(buf, "overwrite\n");
if (test_bit(NVDIMM_SECURITY_DISABLED, &nvdimm->sec.flags)) if (test_bit(NVDIMM_SECURITY_DISABLED, &nvdimm->sec.flags))
return sprintf(buf, "disabled\n"); return sprintf(buf, "disabled\n");
if (test_bit(NVDIMM_SECURITY_UNLOCKED, &nvdimm->sec.flags)) if (test_bit(NVDIMM_SECURITY_UNLOCKED, &nvdimm->sec.flags))
return sprintf(buf, "unlocked\n"); return sprintf(buf, "unlocked\n");
if (test_bit(NVDIMM_SECURITY_LOCKED, &nvdimm->sec.flags)) if (test_bit(NVDIMM_SECURITY_LOCKED, &nvdimm->sec.flags))
return sprintf(buf, "locked\n"); return sprintf(buf, "locked\n");
if (test_bit(NVDIMM_SECURITY_OVERWRITE, &nvdimm->sec.flags))
return sprintf(buf, "overwrite\n");
return -ENOTTY; return -ENOTTY;
} }
...@@ -446,9 +446,124 @@ static const struct attribute_group nvdimm_attribute_group = { ...@@ -446,9 +446,124 @@ static const struct attribute_group nvdimm_attribute_group = {
.is_visible = nvdimm_visible, .is_visible = nvdimm_visible,
}; };
static ssize_t result_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct nvdimm *nvdimm = to_nvdimm(dev);
enum nvdimm_fwa_result result;
if (!nvdimm->fw_ops)
return -EOPNOTSUPP;
nvdimm_bus_lock(dev);
result = nvdimm->fw_ops->activate_result(nvdimm);
nvdimm_bus_unlock(dev);
switch (result) {
case NVDIMM_FWA_RESULT_NONE:
return sprintf(buf, "none\n");
case NVDIMM_FWA_RESULT_SUCCESS:
return sprintf(buf, "success\n");
case NVDIMM_FWA_RESULT_FAIL:
return sprintf(buf, "fail\n");
case NVDIMM_FWA_RESULT_NOTSTAGED:
return sprintf(buf, "not_staged\n");
case NVDIMM_FWA_RESULT_NEEDRESET:
return sprintf(buf, "need_reset\n");
default:
return -ENXIO;
}
}
static DEVICE_ATTR_ADMIN_RO(result);
static ssize_t activate_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct nvdimm *nvdimm = to_nvdimm(dev);
enum nvdimm_fwa_state state;
if (!nvdimm->fw_ops)
return -EOPNOTSUPP;
nvdimm_bus_lock(dev);
state = nvdimm->fw_ops->activate_state(nvdimm);
nvdimm_bus_unlock(dev);
switch (state) {
case NVDIMM_FWA_IDLE:
return sprintf(buf, "idle\n");
case NVDIMM_FWA_BUSY:
return sprintf(buf, "busy\n");
case NVDIMM_FWA_ARMED:
return sprintf(buf, "armed\n");
default:
return -ENXIO;
}
}
static ssize_t activate_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t len)
{
struct nvdimm *nvdimm = to_nvdimm(dev);
enum nvdimm_fwa_trigger arg;
int rc;
if (!nvdimm->fw_ops)
return -EOPNOTSUPP;
if (sysfs_streq(buf, "arm"))
arg = NVDIMM_FWA_ARM;
else if (sysfs_streq(buf, "disarm"))
arg = NVDIMM_FWA_DISARM;
else
return -EINVAL;
nvdimm_bus_lock(dev);
rc = nvdimm->fw_ops->arm(nvdimm, arg);
nvdimm_bus_unlock(dev);
if (rc < 0)
return rc;
return len;
}
static DEVICE_ATTR_ADMIN_RW(activate);
static struct attribute *nvdimm_firmware_attributes[] = {
&dev_attr_activate.attr,
&dev_attr_result.attr,
};
static umode_t nvdimm_firmware_visible(struct kobject *kobj, struct attribute *a, int n)
{
struct device *dev = container_of(kobj, typeof(*dev), kobj);
struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev);
struct nvdimm_bus_descriptor *nd_desc = nvdimm_bus->nd_desc;
struct nvdimm *nvdimm = to_nvdimm(dev);
enum nvdimm_fwa_capability cap;
if (!nd_desc->fw_ops)
return 0;
if (!nvdimm->fw_ops)
return 0;
nvdimm_bus_lock(dev);
cap = nd_desc->fw_ops->capability(nd_desc);
nvdimm_bus_unlock(dev);
if (cap < NVDIMM_FWA_CAP_QUIESCE)
return 0;
return a->mode;
}
static const struct attribute_group nvdimm_firmware_attribute_group = {
.name = "firmware",
.attrs = nvdimm_firmware_attributes,
.is_visible = nvdimm_firmware_visible,
};
static const struct attribute_group *nvdimm_attribute_groups[] = { static const struct attribute_group *nvdimm_attribute_groups[] = {
&nd_device_attribute_group, &nd_device_attribute_group,
&nvdimm_attribute_group, &nvdimm_attribute_group,
&nvdimm_firmware_attribute_group,
NULL, NULL,
}; };
...@@ -467,7 +582,8 @@ struct nvdimm *__nvdimm_create(struct nvdimm_bus *nvdimm_bus, ...@@ -467,7 +582,8 @@ struct nvdimm *__nvdimm_create(struct nvdimm_bus *nvdimm_bus,
void *provider_data, const struct attribute_group **groups, void *provider_data, const struct attribute_group **groups,
unsigned long flags, unsigned long cmd_mask, int num_flush, unsigned long flags, unsigned long cmd_mask, int num_flush,
struct resource *flush_wpq, const char *dimm_id, struct resource *flush_wpq, const char *dimm_id,
const struct nvdimm_security_ops *sec_ops) const struct nvdimm_security_ops *sec_ops,
const struct nvdimm_fw_ops *fw_ops)
{ {
struct nvdimm *nvdimm = kzalloc(sizeof(*nvdimm), GFP_KERNEL); struct nvdimm *nvdimm = kzalloc(sizeof(*nvdimm), GFP_KERNEL);
struct device *dev; struct device *dev;
...@@ -497,6 +613,7 @@ struct nvdimm *__nvdimm_create(struct nvdimm_bus *nvdimm_bus, ...@@ -497,6 +613,7 @@ struct nvdimm *__nvdimm_create(struct nvdimm_bus *nvdimm_bus,
dev->devt = MKDEV(nvdimm_major, nvdimm->id); dev->devt = MKDEV(nvdimm_major, nvdimm->id);
dev->groups = groups; dev->groups = groups;
nvdimm->sec.ops = sec_ops; nvdimm->sec.ops = sec_ops;
nvdimm->fw_ops = fw_ops;
nvdimm->sec.overwrite_tmo = 0; nvdimm->sec.overwrite_tmo = 0;
INIT_DELAYED_WORK(&nvdimm->dwork, nvdimm_security_overwrite_query); INIT_DELAYED_WORK(&nvdimm->dwork, nvdimm_security_overwrite_query);
/* /*
......
...@@ -1309,7 +1309,7 @@ static ssize_t resource_show(struct device *dev, ...@@ -1309,7 +1309,7 @@ static ssize_t resource_show(struct device *dev,
return -ENXIO; return -ENXIO;
return sprintf(buf, "%#llx\n", (unsigned long long) res->start); return sprintf(buf, "%#llx\n", (unsigned long long) res->start);
} }
static DEVICE_ATTR(resource, 0400, resource_show, NULL); static DEVICE_ATTR_ADMIN_RO(resource);
static const unsigned long blk_lbasize_supported[] = { 512, 520, 528, static const unsigned long blk_lbasize_supported[] = { 512, 520, 528,
4096, 4104, 4160, 4224, 0 }; 4096, 4104, 4160, 4224, 0 };
......
...@@ -45,6 +45,7 @@ struct nvdimm { ...@@ -45,6 +45,7 @@ struct nvdimm {
struct kernfs_node *overwrite_state; struct kernfs_node *overwrite_state;
} sec; } sec;
struct delayed_work dwork; struct delayed_work dwork;
const struct nvdimm_fw_ops *fw_ops;
}; };
static inline unsigned long nvdimm_security_flags( static inline unsigned long nvdimm_security_flags(
......
...@@ -218,7 +218,7 @@ static ssize_t resource_show(struct device *dev, ...@@ -218,7 +218,7 @@ static ssize_t resource_show(struct device *dev,
return rc; return rc;
} }
static DEVICE_ATTR(resource, 0400, resource_show, NULL); static DEVICE_ATTR_ADMIN_RO(resource);
static ssize_t size_show(struct device *dev, static ssize_t size_show(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
......
...@@ -605,7 +605,7 @@ static ssize_t resource_show(struct device *dev, ...@@ -605,7 +605,7 @@ static ssize_t resource_show(struct device *dev,
return sprintf(buf, "%#llx\n", nd_region->ndr_start); return sprintf(buf, "%#llx\n", nd_region->ndr_start);
} }
static DEVICE_ATTR(resource, 0400, resource_show, NULL); static DEVICE_ATTR_ADMIN_RO(resource);
static ssize_t persistence_domain_show(struct device *dev, static ssize_t persistence_domain_show(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
......
...@@ -450,14 +450,19 @@ void __nvdimm_security_overwrite_query(struct nvdimm *nvdimm) ...@@ -450,14 +450,19 @@ void __nvdimm_security_overwrite_query(struct nvdimm *nvdimm)
else else
dev_dbg(&nvdimm->dev, "overwrite completed\n"); dev_dbg(&nvdimm->dev, "overwrite completed\n");
if (nvdimm->sec.overwrite_state) /*
sysfs_notify_dirent(nvdimm->sec.overwrite_state); * Mark the overwrite work done and update dimm security flags,
* then send a sysfs event notification to wake up userspace
* poll threads to picked up the changed state.
*/
nvdimm->sec.overwrite_tmo = 0; nvdimm->sec.overwrite_tmo = 0;
clear_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags); clear_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags);
clear_bit(NDD_WORK_PENDING, &nvdimm->flags); clear_bit(NDD_WORK_PENDING, &nvdimm->flags);
put_device(&nvdimm->dev);
nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER); nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER);
nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_MASTER); nvdimm->sec.ext_flags = nvdimm_security_flags(nvdimm, NVDIMM_MASTER);
if (nvdimm->sec.overwrite_state)
sysfs_notify_dirent(nvdimm->sec.overwrite_state);
put_device(&nvdimm->dev);
} }
void nvdimm_security_overwrite_query(struct work_struct *work) void nvdimm_security_overwrite_query(struct work_struct *work)
......
...@@ -488,7 +488,7 @@ static void *grab_mapping_entry(struct xa_state *xas, ...@@ -488,7 +488,7 @@ static void *grab_mapping_entry(struct xa_state *xas,
if (dax_is_conflict(entry)) if (dax_is_conflict(entry))
goto fallback; goto fallback;
if (!xa_is_value(entry)) { if (!xa_is_value(entry)) {
xas_set_err(xas, EIO); xas_set_err(xas, -EIO);
goto out_unlock; goto out_unlock;
} }
...@@ -680,21 +680,20 @@ int dax_invalidate_mapping_entry_sync(struct address_space *mapping, ...@@ -680,21 +680,20 @@ int dax_invalidate_mapping_entry_sync(struct address_space *mapping,
return __dax_invalidate_entry(mapping, index, false); return __dax_invalidate_entry(mapping, index, false);
} }
static int copy_user_dax(struct block_device *bdev, struct dax_device *dax_dev, static int copy_cow_page_dax(struct block_device *bdev, struct dax_device *dax_dev,
sector_t sector, size_t size, struct page *to, sector_t sector, struct page *to, unsigned long vaddr)
unsigned long vaddr)
{ {
void *vto, *kaddr; void *vto, *kaddr;
pgoff_t pgoff; pgoff_t pgoff;
long rc; long rc;
int id; int id;
rc = bdev_dax_pgoff(bdev, sector, size, &pgoff); rc = bdev_dax_pgoff(bdev, sector, PAGE_SIZE, &pgoff);
if (rc) if (rc)
return rc; return rc;
id = dax_read_lock(); id = dax_read_lock();
rc = dax_direct_access(dax_dev, pgoff, PHYS_PFN(size), &kaddr, NULL); rc = dax_direct_access(dax_dev, pgoff, PHYS_PFN(PAGE_SIZE), &kaddr, NULL);
if (rc < 0) { if (rc < 0) {
dax_read_unlock(id); dax_read_unlock(id);
return rc; return rc;
...@@ -1305,8 +1304,8 @@ static vm_fault_t dax_iomap_pte_fault(struct vm_fault *vmf, pfn_t *pfnp, ...@@ -1305,8 +1304,8 @@ static vm_fault_t dax_iomap_pte_fault(struct vm_fault *vmf, pfn_t *pfnp,
clear_user_highpage(vmf->cow_page, vaddr); clear_user_highpage(vmf->cow_page, vaddr);
break; break;
case IOMAP_MAPPED: case IOMAP_MAPPED:
error = copy_user_dax(iomap.bdev, iomap.dax_dev, error = copy_cow_page_dax(iomap.bdev, iomap.dax_dev,
sector, PAGE_SIZE, vmf->cow_page, vaddr); sector, vmf->cow_page, vaddr);
break; break;
default: default:
WARN_ON_ONCE(1); WARN_ON_ONCE(1);
......
...@@ -76,8 +76,9 @@ typedef int (*ndctl_fn)(struct nvdimm_bus_descriptor *nd_desc, ...@@ -76,8 +76,9 @@ typedef int (*ndctl_fn)(struct nvdimm_bus_descriptor *nd_desc,
struct device_node; struct device_node;
struct nvdimm_bus_descriptor { struct nvdimm_bus_descriptor {
const struct attribute_group **attr_groups; const struct attribute_group **attr_groups;
unsigned long bus_dsm_mask;
unsigned long cmd_mask; unsigned long cmd_mask;
unsigned long dimm_family_mask;
unsigned long bus_family_mask;
struct module *module; struct module *module;
char *provider_name; char *provider_name;
struct device_node *of_node; struct device_node *of_node;
...@@ -85,6 +86,7 @@ struct nvdimm_bus_descriptor { ...@@ -85,6 +86,7 @@ struct nvdimm_bus_descriptor {
int (*flush_probe)(struct nvdimm_bus_descriptor *nd_desc); int (*flush_probe)(struct nvdimm_bus_descriptor *nd_desc);
int (*clear_to_send)(struct nvdimm_bus_descriptor *nd_desc, int (*clear_to_send)(struct nvdimm_bus_descriptor *nd_desc,
struct nvdimm *nvdimm, unsigned int cmd, void *data); struct nvdimm *nvdimm, unsigned int cmd, void *data);
const struct nvdimm_bus_fw_ops *fw_ops;
}; };
struct nd_cmd_desc { struct nd_cmd_desc {
...@@ -199,6 +201,49 @@ struct nvdimm_security_ops { ...@@ -199,6 +201,49 @@ struct nvdimm_security_ops {
int (*query_overwrite)(struct nvdimm *nvdimm); int (*query_overwrite)(struct nvdimm *nvdimm);
}; };
enum nvdimm_fwa_state {
NVDIMM_FWA_INVALID,
NVDIMM_FWA_IDLE,
NVDIMM_FWA_ARMED,
NVDIMM_FWA_BUSY,
NVDIMM_FWA_ARM_OVERFLOW,
};
enum nvdimm_fwa_trigger {
NVDIMM_FWA_ARM,
NVDIMM_FWA_DISARM,
};
enum nvdimm_fwa_capability {
NVDIMM_FWA_CAP_INVALID,
NVDIMM_FWA_CAP_NONE,
NVDIMM_FWA_CAP_QUIESCE,
NVDIMM_FWA_CAP_LIVE,
};
enum nvdimm_fwa_result {
NVDIMM_FWA_RESULT_INVALID,
NVDIMM_FWA_RESULT_NONE,
NVDIMM_FWA_RESULT_SUCCESS,
NVDIMM_FWA_RESULT_NOTSTAGED,
NVDIMM_FWA_RESULT_NEEDRESET,
NVDIMM_FWA_RESULT_FAIL,
};
struct nvdimm_bus_fw_ops {
enum nvdimm_fwa_state (*activate_state)
(struct nvdimm_bus_descriptor *nd_desc);
enum nvdimm_fwa_capability (*capability)
(struct nvdimm_bus_descriptor *nd_desc);
int (*activate)(struct nvdimm_bus_descriptor *nd_desc);
};
struct nvdimm_fw_ops {
enum nvdimm_fwa_state (*activate_state)(struct nvdimm *nvdimm);
enum nvdimm_fwa_result (*activate_result)(struct nvdimm *nvdimm);
int (*arm)(struct nvdimm *nvdimm, enum nvdimm_fwa_trigger arg);
};
void badrange_init(struct badrange *badrange); void badrange_init(struct badrange *badrange);
int badrange_add(struct badrange *badrange, u64 addr, u64 length); int badrange_add(struct badrange *badrange, u64 addr, u64 length);
void badrange_forget(struct badrange *badrange, phys_addr_t start, void badrange_forget(struct badrange *badrange, phys_addr_t start,
...@@ -224,14 +269,15 @@ struct nvdimm *__nvdimm_create(struct nvdimm_bus *nvdimm_bus, ...@@ -224,14 +269,15 @@ struct nvdimm *__nvdimm_create(struct nvdimm_bus *nvdimm_bus,
void *provider_data, const struct attribute_group **groups, void *provider_data, const struct attribute_group **groups,
unsigned long flags, unsigned long cmd_mask, int num_flush, unsigned long flags, unsigned long cmd_mask, int num_flush,
struct resource *flush_wpq, const char *dimm_id, struct resource *flush_wpq, const char *dimm_id,
const struct nvdimm_security_ops *sec_ops); const struct nvdimm_security_ops *sec_ops,
const struct nvdimm_fw_ops *fw_ops);
static inline struct nvdimm *nvdimm_create(struct nvdimm_bus *nvdimm_bus, static inline struct nvdimm *nvdimm_create(struct nvdimm_bus *nvdimm_bus,
void *provider_data, const struct attribute_group **groups, void *provider_data, const struct attribute_group **groups,
unsigned long flags, unsigned long cmd_mask, int num_flush, unsigned long flags, unsigned long cmd_mask, int num_flush,
struct resource *flush_wpq) struct resource *flush_wpq)
{ {
return __nvdimm_create(nvdimm_bus, provider_data, groups, flags, return __nvdimm_create(nvdimm_bus, provider_data, groups, flags,
cmd_mask, num_flush, flush_wpq, NULL, NULL); cmd_mask, num_flush, flush_wpq, NULL, NULL, NULL);
} }
const struct nd_cmd_desc *nd_cmd_dimm_desc(int cmd); const struct nd_cmd_desc *nd_cmd_dimm_desc(int cmd);
......
...@@ -453,6 +453,8 @@ extern bool hibernation_available(void); ...@@ -453,6 +453,8 @@ extern bool hibernation_available(void);
asmlinkage int swsusp_save(void); asmlinkage int swsusp_save(void);
extern struct pbe *restore_pblist; extern struct pbe *restore_pblist;
int pfn_is_nosave(unsigned long pfn); int pfn_is_nosave(unsigned long pfn);
int hibernate_quiet_exec(int (*func)(void *data), void *data);
#else /* CONFIG_HIBERNATION */ #else /* CONFIG_HIBERNATION */
static inline void register_nosave_region(unsigned long b, unsigned long e) {} static inline void register_nosave_region(unsigned long b, unsigned long e) {}
static inline void register_nosave_region_late(unsigned long b, unsigned long e) {} 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 ...@@ -464,6 +466,10 @@ static inline void hibernation_set_ops(const struct platform_hibernation_ops *op
static inline int hibernate(void) { return -ENOSYS; } static inline int hibernate(void) { return -ENOSYS; }
static inline bool system_entering_hibernation(void) { return false; } static inline bool system_entering_hibernation(void) { return false; }
static inline bool hibernation_available(void) { return false; } static inline bool hibernation_available(void) { return false; }
static inline int hibernate_quiet_exec(int (*func)(void *data), void *data) {
return -ENOTSUPP;
}
#endif /* CONFIG_HIBERNATION */ #endif /* CONFIG_HIBERNATION */
#ifdef CONFIG_HIBERNATION_SNAPSHOT_DEV #ifdef CONFIG_HIBERNATION_SNAPSHOT_DEV
......
...@@ -245,6 +245,11 @@ struct nd_cmd_pkg { ...@@ -245,6 +245,11 @@ struct nd_cmd_pkg {
#define NVDIMM_FAMILY_MSFT 3 #define NVDIMM_FAMILY_MSFT 3
#define NVDIMM_FAMILY_HYPERV 4 #define NVDIMM_FAMILY_HYPERV 4
#define NVDIMM_FAMILY_PAPR 5 #define NVDIMM_FAMILY_PAPR 5
#define NVDIMM_FAMILY_MAX NVDIMM_FAMILY_PAPR
#define NVDIMM_BUS_FAMILY_NFIT 0
#define NVDIMM_BUS_FAMILY_INTEL 1
#define NVDIMM_BUS_FAMILY_MAX NVDIMM_BUS_FAMILY_INTEL
#define ND_IOCTL_CALL _IOWR(ND_IOCTL, ND_CMD_CALL,\ #define ND_IOCTL_CALL _IOWR(ND_IOCTL, ND_CMD_CALL,\
struct nd_cmd_pkg) struct nd_cmd_pkg)
......
...@@ -795,6 +795,103 @@ int hibernate(void) ...@@ -795,6 +795,103 @@ int hibernate(void)
return error; return error;
} }
/**
* hibernate_quiet_exec - Execute a function with all devices frozen.
* @func: Function to execute.
* @data: Data pointer to pass to @func.
*
* Return the @func return value or an error code if it cannot be executed.
*/
int hibernate_quiet_exec(int (*func)(void *data), void *data)
{
int error, nr_calls = 0;
lock_system_sleep();
if (!hibernate_acquire()) {
error = -EBUSY;
goto unlock;
}
pm_prepare_console();
error = __pm_notifier_call_chain(PM_HIBERNATION_PREPARE, -1, &nr_calls);
if (error) {
nr_calls--;
goto exit;
}
error = freeze_processes();
if (error)
goto exit;
lock_device_hotplug();
pm_suspend_clear_flags();
error = platform_begin(true);
if (error)
goto thaw;
error = freeze_kernel_threads();
if (error)
goto thaw;
error = dpm_prepare(PMSG_FREEZE);
if (error)
goto dpm_complete;
suspend_console();
error = dpm_suspend(PMSG_FREEZE);
if (error)
goto dpm_resume;
error = dpm_suspend_end(PMSG_FREEZE);
if (error)
goto dpm_resume;
error = platform_pre_snapshot(true);
if (error)
goto skip;
error = func(data);
skip:
platform_finish(true);
dpm_resume_start(PMSG_THAW);
dpm_resume:
dpm_resume(PMSG_THAW);
resume_console();
dpm_complete:
dpm_complete(PMSG_THAW);
thaw_kernel_threads();
thaw:
platform_end(true);
unlock_device_hotplug();
thaw_processes();
exit:
__pm_notifier_call_chain(PM_POST_HIBERNATION, nr_calls, NULL);
pm_restore_console();
hibernate_release();
unlock:
unlock_system_sleep();
return error;
}
EXPORT_SYMBOL_GPL(hibernate_quiet_exec);
/** /**
* software_resume - Resume from a saved hibernation image. * software_resume - Resume from a saved hibernation image.
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment