2018-12-05 23:39:29 -08:00
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2018 Intel Corporation. All rights reserved. */
# include <linux/libnvdimm.h>
# include <linux/ndctl.h>
# include <linux/acpi.h>
memregion: Add cpu_cache_invalidate_memregion() interface
With CXL security features, and CXL dynamic provisioning, global CPU
cache flushing nvdimm requirements are no longer specific to that
subsystem, even beyond the scope of security_ops. CXL will need such
semantics for features not necessarily limited to persistent memory.
The functionality this is enabling is to be able to instantaneously
secure erase potentially terabytes of memory at once and the kernel
needs to be sure that none of the data from before the erase is still
present in the cache. It is also used when unlocking a memory device
where speculative reads and firmware accesses could have cached poison
from before the device was unlocked. Lastly this facility is used when
mapping new devices, or new capacity into an established physical
address range. I.e. when the driver switches DeviceA mapping AddressX to
DeviceB mapping AddressX then any cached data from DeviceA:AddressX
needs to be invalidated.
This capability is typically only used once per-boot (for unlock), or
once per bare metal provisioning event (secure erase), like when handing
off the system to another tenant or decommissioning a device. It may
also be used for dynamic CXL region provisioning.
Users must first call cpu_cache_has_invalidate_memregion() to know
whether this functionality is available on the architecture. On x86 this
respects the constraints of when wbinvd() is tolerable. It is already
the case that wbinvd() is problematic to allow in VMs due its global
performance impact and KVM, for example, has been known to just trap and
ignore the call. With confidential computing guest execution of wbinvd()
may even trigger an exception. Given guests should not be messing with
the bare metal address map via CXL configuration changes
cpu_cache_has_invalidate_memregion() returns false in VMs.
While this global cache invalidation facility, is exported to modules,
since NVDIMM and CXL support can be built as a module, it is not for
general use. The intent is that this facility is not available outside
of specific "device-memory" use cases. To make that expectation as clear
as possible the API is scoped to a new "DEVMEM" module namespace that
only the NVDIMM and CXL subsystems are expected to import.
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Borislav Petkov <bp@alien8.de>
Cc: x86@kernel.org
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Tested-by: Dave Jiang <dave.jiang@intel.com>
Signed-off-by: Davidlohr Bueso <dave@stgolabs.net>
Acked-by: Dave Hansen <dave.hansen@linux.intel.com>
Co-developed-by: Dan Williams <dan.j.williams@intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
2022-10-28 11:34:04 -07:00
# include <linux/memregion.h>
2018-12-06 12:40:01 -08:00
# include <asm/smp.h>
2018-12-05 23:39:29 -08:00
# include "intel.h"
# include "nfit.h"
2020-07-20 15:08:24 -07:00
static ssize_t firmware_activate_noidle_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 = to_nd_desc ( nvdimm_bus ) ;
struct acpi_nfit_desc * acpi_desc = to_acpi_desc ( nd_desc ) ;
return sprintf ( buf , " %s \n " , acpi_desc - > fwa_noidle ? " Y " : " N " ) ;
}
static ssize_t firmware_activate_noidle_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t size )
{
struct nvdimm_bus * nvdimm_bus = to_nvdimm_bus ( dev ) ;
struct nvdimm_bus_descriptor * nd_desc = to_nd_desc ( nvdimm_bus ) ;
struct acpi_nfit_desc * acpi_desc = to_acpi_desc ( nd_desc ) ;
ssize_t rc ;
bool val ;
rc = kstrtobool ( buf , & val ) ;
if ( rc )
return rc ;
if ( val ! = acpi_desc - > fwa_noidle )
acpi_desc - > fwa_cap = NVDIMM_FWA_CAP_INVALID ;
acpi_desc - > fwa_noidle = val ;
return size ;
}
DEVICE_ATTR_RW ( firmware_activate_noidle ) ;
bool intel_fwa_supported ( struct nvdimm_bus * nvdimm_bus )
{
struct nvdimm_bus_descriptor * nd_desc = to_nd_desc ( nvdimm_bus ) ;
struct acpi_nfit_desc * acpi_desc = to_acpi_desc ( nd_desc ) ;
unsigned long * mask ;
if ( ! test_bit ( NVDIMM_BUS_FAMILY_INTEL , & nd_desc - > bus_family_mask ) )
return false ;
mask = & acpi_desc - > family_dsm_mask [ NVDIMM_BUS_FAMILY_INTEL ] ;
return * mask = = NVDIMM_BUS_INTEL_FW_ACTIVATE_CMDMASK ;
}
2019-08-26 17:54:54 -07:00
static unsigned long intel_security_flags ( struct nvdimm * nvdimm ,
2018-12-10 10:53:22 -07:00
enum nvdimm_passphrase_type ptype )
2018-12-05 23:39:29 -08:00
{
struct nfit_mem * nfit_mem = nvdimm_provider_data ( nvdimm ) ;
2019-08-26 17:54:54 -07:00
unsigned long security_flags = 0 ;
2018-12-05 23:39:29 -08:00
struct {
struct nd_cmd_pkg pkg ;
struct nd_intel_get_security_state cmd ;
} nd_cmd = {
. pkg = {
. nd_command = NVDIMM_INTEL_GET_SECURITY_STATE ,
. nd_family = NVDIMM_FAMILY_INTEL ,
. nd_size_out =
sizeof ( struct nd_intel_get_security_state ) ,
. nd_fw_size =
sizeof ( struct nd_intel_get_security_state ) ,
} ,
} ;
int rc ;
if ( ! test_bit ( NVDIMM_INTEL_GET_SECURITY_STATE , & nfit_mem - > dsm_mask ) )
2019-08-26 17:54:54 -07:00
return 0 ;
2018-12-05 23:39:29 -08:00
2018-12-13 15:36:18 -07:00
/*
* Short circuit the state retrieval while we are doing overwrite .
* The DSM spec states that the security state is indeterminate
* until the overwrite DSM completes .
*/
2018-12-10 10:53:22 -07:00
if ( nvdimm_in_overwrite ( nvdimm ) & & ptype = = NVDIMM_USER )
2019-08-26 17:54:54 -07:00
return BIT ( NVDIMM_SECURITY_OVERWRITE ) ;
2018-12-13 15:36:18 -07:00
2018-12-05 23:39:29 -08:00
rc = nvdimm_ctl ( nvdimm , ND_CMD_CALL , & nd_cmd , sizeof ( nd_cmd ) , NULL ) ;
2019-08-26 17:54:54 -07:00
if ( rc < 0 | | nd_cmd . cmd . status ) {
pr_err ( " %s: security state retrieval failed (%d:%#x) \n " ,
nvdimm_name ( nvdimm ) , rc , nd_cmd . cmd . status ) ;
return 0 ;
}
2018-12-05 23:39:29 -08:00
/* check and see if security is enabled and locked */
2018-12-10 10:53:22 -07:00
if ( ptype = = NVDIMM_MASTER ) {
if ( nd_cmd . cmd . extended_state & ND_INTEL_SEC_ESTATE_ENABLED )
2019-08-26 17:54:54 -07:00
set_bit ( NVDIMM_SECURITY_UNLOCKED , & security_flags ) ;
else
set_bit ( NVDIMM_SECURITY_DISABLED , & security_flags ) ;
if ( nd_cmd . cmd . extended_state & ND_INTEL_SEC_ESTATE_PLIMIT )
set_bit ( NVDIMM_SECURITY_FROZEN , & security_flags ) ;
return security_flags ;
2018-12-05 23:39:29 -08:00
}
2018-12-10 10:53:22 -07:00
2019-08-26 17:54:54 -07:00
if ( nd_cmd . cmd . state & ND_INTEL_SEC_STATE_UNSUPPORTED )
return 0 ;
if ( nd_cmd . cmd . state & ND_INTEL_SEC_STATE_ENABLED ) {
if ( nd_cmd . cmd . state & ND_INTEL_SEC_STATE_FROZEN | |
nd_cmd . cmd . state & ND_INTEL_SEC_STATE_PLIMIT )
set_bit ( NVDIMM_SECURITY_FROZEN , & security_flags ) ;
if ( nd_cmd . cmd . state & ND_INTEL_SEC_STATE_LOCKED )
set_bit ( NVDIMM_SECURITY_LOCKED , & security_flags ) ;
else
set_bit ( NVDIMM_SECURITY_UNLOCKED , & security_flags ) ;
} else
set_bit ( NVDIMM_SECURITY_DISABLED , & security_flags ) ;
return security_flags ;
2018-12-05 23:39:29 -08:00
}
2018-12-06 09:14:08 -08:00
static int intel_security_freeze ( struct nvdimm * nvdimm )
{
struct nfit_mem * nfit_mem = nvdimm_provider_data ( nvdimm ) ;
struct {
struct nd_cmd_pkg pkg ;
struct nd_intel_freeze_lock cmd ;
} nd_cmd = {
. pkg = {
. nd_command = NVDIMM_INTEL_FREEZE_LOCK ,
. nd_family = NVDIMM_FAMILY_INTEL ,
. nd_size_out = ND_INTEL_STATUS_SIZE ,
. nd_fw_size = ND_INTEL_STATUS_SIZE ,
} ,
} ;
int rc ;
if ( ! test_bit ( NVDIMM_INTEL_FREEZE_LOCK , & nfit_mem - > dsm_mask ) )
return - ENOTTY ;
rc = nvdimm_ctl ( nvdimm , ND_CMD_CALL , & nd_cmd , sizeof ( nd_cmd ) , NULL ) ;
if ( rc < 0 )
return rc ;
if ( nd_cmd . cmd . status )
return - EIO ;
return 0 ;
}
2018-12-06 12:40:01 -08:00
static int intel_security_change_key ( struct nvdimm * nvdimm ,
const struct nvdimm_key_data * old_data ,
2018-12-10 10:53:22 -07:00
const struct nvdimm_key_data * new_data ,
enum nvdimm_passphrase_type ptype )
2018-12-06 12:40:01 -08:00
{
struct nfit_mem * nfit_mem = nvdimm_provider_data ( nvdimm ) ;
2018-12-10 10:53:22 -07:00
unsigned int cmd = ptype = = NVDIMM_MASTER ?
NVDIMM_INTEL_SET_MASTER_PASSPHRASE :
NVDIMM_INTEL_SET_PASSPHRASE ;
2018-12-06 12:40:01 -08:00
struct {
struct nd_cmd_pkg pkg ;
struct nd_intel_set_passphrase cmd ;
} nd_cmd = {
. pkg = {
. nd_family = NVDIMM_FAMILY_INTEL ,
. nd_size_in = ND_INTEL_PASSPHRASE_SIZE * 2 ,
. nd_size_out = ND_INTEL_STATUS_SIZE ,
. nd_fw_size = ND_INTEL_STATUS_SIZE ,
2018-12-10 10:53:22 -07:00
. nd_command = cmd ,
2018-12-06 12:40:01 -08:00
} ,
} ;
int rc ;
2018-12-10 10:53:22 -07:00
if ( ! test_bit ( cmd , & nfit_mem - > dsm_mask ) )
2018-12-06 12:40:01 -08:00
return - ENOTTY ;
2019-03-27 11:12:45 -07:00
memcpy ( nd_cmd . cmd . old_pass , old_data - > data ,
sizeof ( nd_cmd . cmd . old_pass ) ) ;
2018-12-06 12:40:01 -08:00
memcpy ( nd_cmd . cmd . new_pass , new_data - > data ,
sizeof ( nd_cmd . cmd . new_pass ) ) ;
rc = nvdimm_ctl ( nvdimm , ND_CMD_CALL , & nd_cmd , sizeof ( nd_cmd ) , NULL ) ;
if ( rc < 0 )
return rc ;
switch ( nd_cmd . cmd . status ) {
case 0 :
return 0 ;
case ND_INTEL_STATUS_INVALID_PASS :
return - EINVAL ;
case ND_INTEL_STATUS_NOT_SUPPORTED :
return - EOPNOTSUPP ;
case ND_INTEL_STATUS_INVALID_STATE :
default :
return - EIO ;
}
}
2019-01-08 22:00:37 -07:00
static int __maybe_unused intel_security_unlock ( struct nvdimm * nvdimm ,
2018-12-06 12:40:01 -08:00
const struct nvdimm_key_data * key_data )
{
struct nfit_mem * nfit_mem = nvdimm_provider_data ( nvdimm ) ;
struct {
struct nd_cmd_pkg pkg ;
struct nd_intel_unlock_unit cmd ;
} nd_cmd = {
. pkg = {
. nd_command = NVDIMM_INTEL_UNLOCK_UNIT ,
. nd_family = NVDIMM_FAMILY_INTEL ,
. nd_size_in = ND_INTEL_PASSPHRASE_SIZE ,
. nd_size_out = ND_INTEL_STATUS_SIZE ,
. nd_fw_size = ND_INTEL_STATUS_SIZE ,
} ,
} ;
int rc ;
if ( ! test_bit ( NVDIMM_INTEL_UNLOCK_UNIT , & nfit_mem - > dsm_mask ) )
return - ENOTTY ;
memcpy ( nd_cmd . cmd . passphrase , key_data - > data ,
sizeof ( nd_cmd . cmd . passphrase ) ) ;
rc = nvdimm_ctl ( nvdimm , ND_CMD_CALL , & nd_cmd , sizeof ( nd_cmd ) , NULL ) ;
if ( rc < 0 )
return rc ;
switch ( nd_cmd . cmd . status ) {
case 0 :
break ;
case ND_INTEL_STATUS_INVALID_PASS :
return - EINVAL ;
default :
return - EIO ;
}
return 0 ;
}
2018-12-07 10:33:30 -07:00
static int intel_security_disable ( struct nvdimm * nvdimm ,
const struct nvdimm_key_data * key_data )
{
int rc ;
struct nfit_mem * nfit_mem = nvdimm_provider_data ( nvdimm ) ;
struct {
struct nd_cmd_pkg pkg ;
struct nd_intel_disable_passphrase cmd ;
} nd_cmd = {
. pkg = {
. nd_command = NVDIMM_INTEL_DISABLE_PASSPHRASE ,
. nd_family = NVDIMM_FAMILY_INTEL ,
. nd_size_in = ND_INTEL_PASSPHRASE_SIZE ,
. nd_size_out = ND_INTEL_STATUS_SIZE ,
. nd_fw_size = ND_INTEL_STATUS_SIZE ,
} ,
} ;
if ( ! test_bit ( NVDIMM_INTEL_DISABLE_PASSPHRASE , & nfit_mem - > dsm_mask ) )
return - ENOTTY ;
memcpy ( nd_cmd . cmd . passphrase , key_data - > data ,
sizeof ( nd_cmd . cmd . passphrase ) ) ;
rc = nvdimm_ctl ( nvdimm , ND_CMD_CALL , & nd_cmd , sizeof ( nd_cmd ) , NULL ) ;
if ( rc < 0 )
return rc ;
switch ( nd_cmd . cmd . status ) {
case 0 :
break ;
case ND_INTEL_STATUS_INVALID_PASS :
return - EINVAL ;
case ND_INTEL_STATUS_INVALID_STATE :
default :
return - ENXIO ;
}
return 0 ;
}
2019-01-08 22:00:37 -07:00
static int __maybe_unused intel_security_erase ( struct nvdimm * nvdimm ,
2018-12-10 10:53:22 -07:00
const struct nvdimm_key_data * key ,
enum nvdimm_passphrase_type ptype )
2018-12-07 14:02:12 -07:00
{
int rc ;
struct nfit_mem * nfit_mem = nvdimm_provider_data ( nvdimm ) ;
2018-12-10 10:53:22 -07:00
unsigned int cmd = ptype = = NVDIMM_MASTER ?
NVDIMM_INTEL_MASTER_SECURE_ERASE : NVDIMM_INTEL_SECURE_ERASE ;
2018-12-07 14:02:12 -07:00
struct {
struct nd_cmd_pkg pkg ;
struct nd_intel_secure_erase cmd ;
} nd_cmd = {
. pkg = {
. nd_family = NVDIMM_FAMILY_INTEL ,
. nd_size_in = ND_INTEL_PASSPHRASE_SIZE ,
. nd_size_out = ND_INTEL_STATUS_SIZE ,
. nd_fw_size = ND_INTEL_STATUS_SIZE ,
2018-12-10 10:53:22 -07:00
. nd_command = cmd ,
2018-12-07 14:02:12 -07:00
} ,
} ;
2018-12-10 10:53:22 -07:00
if ( ! test_bit ( cmd , & nfit_mem - > dsm_mask ) )
2018-12-07 14:02:12 -07:00
return - ENOTTY ;
memcpy ( nd_cmd . cmd . passphrase , key - > data ,
sizeof ( nd_cmd . cmd . passphrase ) ) ;
rc = nvdimm_ctl ( nvdimm , ND_CMD_CALL , & nd_cmd , sizeof ( nd_cmd ) , NULL ) ;
if ( rc < 0 )
return rc ;
switch ( nd_cmd . cmd . status ) {
case 0 :
break ;
case ND_INTEL_STATUS_NOT_SUPPORTED :
return - EOPNOTSUPP ;
case ND_INTEL_STATUS_INVALID_PASS :
return - EINVAL ;
case ND_INTEL_STATUS_INVALID_STATE :
default :
return - ENXIO ;
}
return 0 ;
}
2019-01-08 22:00:37 -07:00
static int __maybe_unused intel_security_query_overwrite ( struct nvdimm * nvdimm )
2018-12-13 15:36:18 -07:00
{
int rc ;
struct nfit_mem * nfit_mem = nvdimm_provider_data ( nvdimm ) ;
struct {
struct nd_cmd_pkg pkg ;
struct nd_intel_query_overwrite cmd ;
} nd_cmd = {
. pkg = {
. nd_command = NVDIMM_INTEL_QUERY_OVERWRITE ,
. nd_family = NVDIMM_FAMILY_INTEL ,
. nd_size_out = ND_INTEL_STATUS_SIZE ,
. nd_fw_size = ND_INTEL_STATUS_SIZE ,
} ,
} ;
if ( ! test_bit ( NVDIMM_INTEL_QUERY_OVERWRITE , & nfit_mem - > dsm_mask ) )
return - ENOTTY ;
rc = nvdimm_ctl ( nvdimm , ND_CMD_CALL , & nd_cmd , sizeof ( nd_cmd ) , NULL ) ;
if ( rc < 0 )
return rc ;
switch ( nd_cmd . cmd . status ) {
case 0 :
break ;
case ND_INTEL_STATUS_OQUERY_INPROGRESS :
return - EBUSY ;
default :
return - ENXIO ;
}
return 0 ;
}
2019-01-08 22:00:37 -07:00
static int __maybe_unused intel_security_overwrite ( struct nvdimm * nvdimm ,
2018-12-13 15:36:18 -07:00
const struct nvdimm_key_data * nkey )
{
int rc ;
struct nfit_mem * nfit_mem = nvdimm_provider_data ( nvdimm ) ;
struct {
struct nd_cmd_pkg pkg ;
struct nd_intel_overwrite cmd ;
} nd_cmd = {
. pkg = {
. nd_command = NVDIMM_INTEL_OVERWRITE ,
. nd_family = NVDIMM_FAMILY_INTEL ,
. nd_size_in = ND_INTEL_PASSPHRASE_SIZE ,
. nd_size_out = ND_INTEL_STATUS_SIZE ,
. nd_fw_size = ND_INTEL_STATUS_SIZE ,
} ,
} ;
if ( ! test_bit ( NVDIMM_INTEL_OVERWRITE , & nfit_mem - > dsm_mask ) )
return - ENOTTY ;
2019-03-27 11:12:45 -07:00
memcpy ( nd_cmd . cmd . passphrase , nkey - > data ,
sizeof ( nd_cmd . cmd . passphrase ) ) ;
2018-12-13 15:36:18 -07:00
rc = nvdimm_ctl ( nvdimm , ND_CMD_CALL , & nd_cmd , sizeof ( nd_cmd ) , NULL ) ;
if ( rc < 0 )
return rc ;
switch ( nd_cmd . cmd . status ) {
case 0 :
return 0 ;
case ND_INTEL_STATUS_OVERWRITE_UNSUPPORTED :
return - ENOTSUPP ;
case ND_INTEL_STATUS_INVALID_PASS :
return - EINVAL ;
case ND_INTEL_STATUS_INVALID_STATE :
default :
return - ENXIO ;
}
}
2018-12-05 23:39:29 -08:00
static const struct nvdimm_security_ops __intel_security_ops = {
2019-08-26 17:54:54 -07:00
. get_flags = intel_security_flags ,
2018-12-06 09:14:08 -08:00
. freeze = intel_security_freeze ,
2018-12-06 12:40:01 -08:00
. change_key = intel_security_change_key ,
2018-12-07 10:33:30 -07:00
. disable = intel_security_disable ,
2018-12-06 12:40:01 -08:00
# ifdef CONFIG_X86
. unlock = intel_security_unlock ,
2018-12-07 14:02:12 -07:00
. erase = intel_security_erase ,
2018-12-13 15:36:18 -07:00
. overwrite = intel_security_overwrite ,
. query_overwrite = intel_security_query_overwrite ,
2018-12-06 12:40:01 -08:00
# endif
2018-12-05 23:39:29 -08:00
} ;
2018-12-06 12:40:01 -08:00
2018-12-05 23:39:29 -08:00
const struct nvdimm_security_ops * intel_security_ops = & __intel_security_ops ;
2020-07-20 15:08:24 -07:00
static int intel_bus_fwa_businfo ( struct nvdimm_bus_descriptor * nd_desc ,
struct nd_intel_bus_fw_activate_businfo * info )
{
struct {
struct nd_cmd_pkg pkg ;
struct nd_intel_bus_fw_activate_businfo cmd ;
} nd_cmd = {
. pkg = {
. nd_command = NVDIMM_BUS_INTEL_FW_ACTIVATE_BUSINFO ,
. nd_family = NVDIMM_BUS_FAMILY_INTEL ,
. nd_size_out =
sizeof ( struct nd_intel_bus_fw_activate_businfo ) ,
. nd_fw_size =
sizeof ( struct nd_intel_bus_fw_activate_businfo ) ,
} ,
} ;
int rc ;
rc = nd_desc - > ndctl ( nd_desc , NULL , ND_CMD_CALL , & nd_cmd , sizeof ( nd_cmd ) ,
NULL ) ;
* info = nd_cmd . cmd ;
return rc ;
}
/* The fw_ops expect to be called with the nvdimm_bus_lock() held */
static enum nvdimm_fwa_state intel_bus_fwa_state (
struct nvdimm_bus_descriptor * nd_desc )
{
struct acpi_nfit_desc * acpi_desc = to_acpi_desc ( nd_desc ) ;
struct nd_intel_bus_fw_activate_businfo info ;
struct device * dev = acpi_desc - > dev ;
enum nvdimm_fwa_state state ;
int rc ;
/*
* It should not be possible for platform firmware to return
* busy because activate is a synchronous operation . Treat it
* similar to invalid , i . e . always refresh / poll the status .
*/
switch ( acpi_desc - > fwa_state ) {
case NVDIMM_FWA_INVALID :
case NVDIMM_FWA_BUSY :
break ;
default :
/* check if capability needs to be refreshed */
if ( acpi_desc - > fwa_cap = = NVDIMM_FWA_CAP_INVALID )
break ;
return acpi_desc - > fwa_state ;
}
/* Refresh with platform firmware */
rc = intel_bus_fwa_businfo ( nd_desc , & info ) ;
if ( rc )
return NVDIMM_FWA_INVALID ;
switch ( info . state ) {
case ND_INTEL_FWA_IDLE :
state = NVDIMM_FWA_IDLE ;
break ;
case ND_INTEL_FWA_BUSY :
state = NVDIMM_FWA_BUSY ;
break ;
case ND_INTEL_FWA_ARMED :
if ( info . activate_tmo > info . max_quiesce_tmo )
state = NVDIMM_FWA_ARM_OVERFLOW ;
else
state = NVDIMM_FWA_ARMED ;
break ;
default :
dev_err_once ( dev , " invalid firmware activate state %d \n " ,
info . state ) ;
return NVDIMM_FWA_INVALID ;
}
/*
* Capability data is available in the same payload as state . It
* is expected to be static .
*/
if ( acpi_desc - > fwa_cap = = NVDIMM_FWA_CAP_INVALID ) {
if ( info . capability & ND_INTEL_BUS_FWA_CAP_FWQUIESCE )
acpi_desc - > fwa_cap = NVDIMM_FWA_CAP_QUIESCE ;
else if ( info . capability & ND_INTEL_BUS_FWA_CAP_OSQUIESCE ) {
/*
* Skip hibernate cycle by default if platform
* indicates that it does not need devices to be
* quiesced .
*/
acpi_desc - > fwa_cap = NVDIMM_FWA_CAP_LIVE ;
} else
acpi_desc - > fwa_cap = NVDIMM_FWA_CAP_NONE ;
}
acpi_desc - > fwa_state = state ;
return state ;
}
static enum nvdimm_fwa_capability intel_bus_fwa_capability (
struct nvdimm_bus_descriptor * nd_desc )
{
struct acpi_nfit_desc * acpi_desc = to_acpi_desc ( nd_desc ) ;
if ( acpi_desc - > fwa_cap > NVDIMM_FWA_CAP_INVALID )
return acpi_desc - > fwa_cap ;
if ( intel_bus_fwa_state ( nd_desc ) > NVDIMM_FWA_INVALID )
return acpi_desc - > fwa_cap ;
return NVDIMM_FWA_CAP_INVALID ;
}
static int intel_bus_fwa_activate ( struct nvdimm_bus_descriptor * nd_desc )
{
struct acpi_nfit_desc * acpi_desc = to_acpi_desc ( nd_desc ) ;
struct {
struct nd_cmd_pkg pkg ;
struct nd_intel_bus_fw_activate cmd ;
} nd_cmd = {
. pkg = {
. nd_command = NVDIMM_BUS_INTEL_FW_ACTIVATE ,
. nd_family = NVDIMM_BUS_FAMILY_INTEL ,
. nd_size_in = sizeof ( nd_cmd . cmd . iodev_state ) ,
. nd_size_out =
sizeof ( struct nd_intel_bus_fw_activate ) ,
. nd_fw_size =
sizeof ( struct nd_intel_bus_fw_activate ) ,
} ,
/*
* Even though activate is run from a suspended context ,
* for safety , still ask platform firmware to force
* quiesce devices by default . Let a module
* parameter override that policy .
*/
. cmd = {
. iodev_state = acpi_desc - > fwa_noidle
? ND_INTEL_BUS_FWA_IODEV_OS_IDLE
: ND_INTEL_BUS_FWA_IODEV_FORCE_IDLE ,
} ,
} ;
int rc ;
switch ( intel_bus_fwa_state ( nd_desc ) ) {
case NVDIMM_FWA_ARMED :
case NVDIMM_FWA_ARM_OVERFLOW :
break ;
default :
return - ENXIO ;
}
rc = nd_desc - > ndctl ( nd_desc , NULL , ND_CMD_CALL , & nd_cmd , sizeof ( nd_cmd ) ,
NULL ) ;
/*
* Whether the command succeeded , or failed , the agent checking
* for the result needs to query the DIMMs individually .
* Increment the activation count to invalidate all the DIMM
* states at once ( it ' s otherwise not possible to take
* acpi_desc - > init_mutex in this context )
*/
acpi_desc - > fwa_state = NVDIMM_FWA_INVALID ;
acpi_desc - > fwa_count + + ;
dev_dbg ( acpi_desc - > dev , " result: %d \n " , rc ) ;
return rc ;
}
static const struct nvdimm_bus_fw_ops __intel_bus_fw_ops = {
. activate_state = intel_bus_fwa_state ,
. capability = intel_bus_fwa_capability ,
. activate = intel_bus_fwa_activate ,
} ;
const struct nvdimm_bus_fw_ops * intel_bus_fw_ops = & __intel_bus_fw_ops ;
static int intel_fwa_dimminfo ( struct nvdimm * nvdimm ,
struct nd_intel_fw_activate_dimminfo * info )
{
struct {
struct nd_cmd_pkg pkg ;
struct nd_intel_fw_activate_dimminfo cmd ;
} nd_cmd = {
. pkg = {
. nd_command = NVDIMM_INTEL_FW_ACTIVATE_DIMMINFO ,
. nd_family = NVDIMM_FAMILY_INTEL ,
. nd_size_out =
sizeof ( struct nd_intel_fw_activate_dimminfo ) ,
. nd_fw_size =
sizeof ( struct nd_intel_fw_activate_dimminfo ) ,
} ,
} ;
int rc ;
rc = nvdimm_ctl ( nvdimm , ND_CMD_CALL , & nd_cmd , sizeof ( nd_cmd ) , NULL ) ;
* info = nd_cmd . cmd ;
return rc ;
}
static enum nvdimm_fwa_state intel_fwa_state ( struct nvdimm * nvdimm )
{
struct nfit_mem * nfit_mem = nvdimm_provider_data ( nvdimm ) ;
struct acpi_nfit_desc * acpi_desc = nfit_mem - > acpi_desc ;
struct nd_intel_fw_activate_dimminfo info ;
int rc ;
/*
* Similar to the bus state , since activate is synchronous the
* busy state should resolve within the context of ' activate ' .
*/
switch ( nfit_mem - > fwa_state ) {
case NVDIMM_FWA_INVALID :
case NVDIMM_FWA_BUSY :
break ;
default :
/* If no activations occurred the old state is still valid */
if ( nfit_mem - > fwa_count = = acpi_desc - > fwa_count )
return nfit_mem - > fwa_state ;
}
rc = intel_fwa_dimminfo ( nvdimm , & info ) ;
if ( rc )
return NVDIMM_FWA_INVALID ;
switch ( info . state ) {
case ND_INTEL_FWA_IDLE :
nfit_mem - > fwa_state = NVDIMM_FWA_IDLE ;
break ;
case ND_INTEL_FWA_BUSY :
nfit_mem - > fwa_state = NVDIMM_FWA_BUSY ;
break ;
case ND_INTEL_FWA_ARMED :
nfit_mem - > fwa_state = NVDIMM_FWA_ARMED ;
break ;
default :
nfit_mem - > fwa_state = NVDIMM_FWA_INVALID ;
break ;
}
switch ( info . result ) {
case ND_INTEL_DIMM_FWA_NONE :
nfit_mem - > fwa_result = NVDIMM_FWA_RESULT_NONE ;
break ;
case ND_INTEL_DIMM_FWA_SUCCESS :
nfit_mem - > fwa_result = NVDIMM_FWA_RESULT_SUCCESS ;
break ;
case ND_INTEL_DIMM_FWA_NOTSTAGED :
nfit_mem - > fwa_result = NVDIMM_FWA_RESULT_NOTSTAGED ;
break ;
case ND_INTEL_DIMM_FWA_NEEDRESET :
nfit_mem - > fwa_result = NVDIMM_FWA_RESULT_NEEDRESET ;
break ;
case ND_INTEL_DIMM_FWA_MEDIAFAILED :
case ND_INTEL_DIMM_FWA_ABORT :
case ND_INTEL_DIMM_FWA_NOTSUPP :
case ND_INTEL_DIMM_FWA_ERROR :
default :
nfit_mem - > fwa_result = NVDIMM_FWA_RESULT_FAIL ;
break ;
}
nfit_mem - > fwa_count = acpi_desc - > fwa_count ;
return nfit_mem - > fwa_state ;
}
static enum nvdimm_fwa_result intel_fwa_result ( struct nvdimm * nvdimm )
{
struct nfit_mem * nfit_mem = nvdimm_provider_data ( nvdimm ) ;
struct acpi_nfit_desc * acpi_desc = nfit_mem - > acpi_desc ;
if ( nfit_mem - > fwa_count = = acpi_desc - > fwa_count
& & nfit_mem - > fwa_result > NVDIMM_FWA_RESULT_INVALID )
return nfit_mem - > fwa_result ;
if ( intel_fwa_state ( nvdimm ) > NVDIMM_FWA_INVALID )
return nfit_mem - > fwa_result ;
return NVDIMM_FWA_RESULT_INVALID ;
}
static int intel_fwa_arm ( struct nvdimm * nvdimm , enum nvdimm_fwa_trigger arm )
{
struct nfit_mem * nfit_mem = nvdimm_provider_data ( nvdimm ) ;
struct acpi_nfit_desc * acpi_desc = nfit_mem - > acpi_desc ;
struct {
struct nd_cmd_pkg pkg ;
struct nd_intel_fw_activate_arm cmd ;
} nd_cmd = {
. pkg = {
. nd_command = NVDIMM_INTEL_FW_ACTIVATE_ARM ,
. nd_family = NVDIMM_FAMILY_INTEL ,
. nd_size_in = sizeof ( nd_cmd . cmd . activate_arm ) ,
. nd_size_out =
sizeof ( struct nd_intel_fw_activate_arm ) ,
. nd_fw_size =
sizeof ( struct nd_intel_fw_activate_arm ) ,
} ,
. cmd = {
. activate_arm = arm = = NVDIMM_FWA_ARM
? ND_INTEL_DIMM_FWA_ARM
: ND_INTEL_DIMM_FWA_DISARM ,
} ,
} ;
int rc ;
switch ( intel_fwa_state ( nvdimm ) ) {
case NVDIMM_FWA_INVALID :
return - ENXIO ;
case NVDIMM_FWA_BUSY :
return - EBUSY ;
case NVDIMM_FWA_IDLE :
if ( arm = = NVDIMM_FWA_DISARM )
return 0 ;
break ;
case NVDIMM_FWA_ARMED :
if ( arm = = NVDIMM_FWA_ARM )
return 0 ;
break ;
default :
return - ENXIO ;
}
/*
* Invalidate the bus - level state , now that we ' re committed to
* changing the ' arm ' state .
*/
acpi_desc - > fwa_state = NVDIMM_FWA_INVALID ;
nfit_mem - > fwa_state = NVDIMM_FWA_INVALID ;
rc = nvdimm_ctl ( nvdimm , ND_CMD_CALL , & nd_cmd , sizeof ( nd_cmd ) , NULL ) ;
dev_dbg ( acpi_desc - > dev , " %s result: %d \n " , arm = = NVDIMM_FWA_ARM
? " arm " : " disarm " , rc ) ;
return rc ;
}
static const struct nvdimm_fw_ops __intel_fw_ops = {
. activate_state = intel_fwa_state ,
. activate_result = intel_fwa_result ,
. arm = intel_fwa_arm ,
} ;
const struct nvdimm_fw_ops * intel_fw_ops = & __intel_fw_ops ;