162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci#define pr_fmt(fmt) "papr-scm: " fmt 462306a36Sopenharmony_ci 562306a36Sopenharmony_ci#include <linux/of.h> 662306a36Sopenharmony_ci#include <linux/kernel.h> 762306a36Sopenharmony_ci#include <linux/module.h> 862306a36Sopenharmony_ci#include <linux/ioport.h> 962306a36Sopenharmony_ci#include <linux/slab.h> 1062306a36Sopenharmony_ci#include <linux/ndctl.h> 1162306a36Sopenharmony_ci#include <linux/sched.h> 1262306a36Sopenharmony_ci#include <linux/libnvdimm.h> 1362306a36Sopenharmony_ci#include <linux/platform_device.h> 1462306a36Sopenharmony_ci#include <linux/delay.h> 1562306a36Sopenharmony_ci#include <linux/seq_buf.h> 1662306a36Sopenharmony_ci#include <linux/nd.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <asm/plpar_wrappers.h> 1962306a36Sopenharmony_ci#include <asm/papr_pdsm.h> 2062306a36Sopenharmony_ci#include <asm/mce.h> 2162306a36Sopenharmony_ci#include <asm/unaligned.h> 2262306a36Sopenharmony_ci#include <linux/perf_event.h> 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#define BIND_ANY_ADDR (~0ul) 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#define PAPR_SCM_DIMM_CMD_MASK \ 2762306a36Sopenharmony_ci ((1ul << ND_CMD_GET_CONFIG_SIZE) | \ 2862306a36Sopenharmony_ci (1ul << ND_CMD_GET_CONFIG_DATA) | \ 2962306a36Sopenharmony_ci (1ul << ND_CMD_SET_CONFIG_DATA) | \ 3062306a36Sopenharmony_ci (1ul << ND_CMD_CALL)) 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci/* DIMM health bitmap indicators */ 3362306a36Sopenharmony_ci/* SCM device is unable to persist memory contents */ 3462306a36Sopenharmony_ci#define PAPR_PMEM_UNARMED (1ULL << (63 - 0)) 3562306a36Sopenharmony_ci/* SCM device failed to persist memory contents */ 3662306a36Sopenharmony_ci#define PAPR_PMEM_SHUTDOWN_DIRTY (1ULL << (63 - 1)) 3762306a36Sopenharmony_ci/* SCM device contents are persisted from previous IPL */ 3862306a36Sopenharmony_ci#define PAPR_PMEM_SHUTDOWN_CLEAN (1ULL << (63 - 2)) 3962306a36Sopenharmony_ci/* SCM device contents are not persisted from previous IPL */ 4062306a36Sopenharmony_ci#define PAPR_PMEM_EMPTY (1ULL << (63 - 3)) 4162306a36Sopenharmony_ci/* SCM device memory life remaining is critically low */ 4262306a36Sopenharmony_ci#define PAPR_PMEM_HEALTH_CRITICAL (1ULL << (63 - 4)) 4362306a36Sopenharmony_ci/* SCM device will be garded off next IPL due to failure */ 4462306a36Sopenharmony_ci#define PAPR_PMEM_HEALTH_FATAL (1ULL << (63 - 5)) 4562306a36Sopenharmony_ci/* SCM contents cannot persist due to current platform health status */ 4662306a36Sopenharmony_ci#define PAPR_PMEM_HEALTH_UNHEALTHY (1ULL << (63 - 6)) 4762306a36Sopenharmony_ci/* SCM device is unable to persist memory contents in certain conditions */ 4862306a36Sopenharmony_ci#define PAPR_PMEM_HEALTH_NON_CRITICAL (1ULL << (63 - 7)) 4962306a36Sopenharmony_ci/* SCM device is encrypted */ 5062306a36Sopenharmony_ci#define PAPR_PMEM_ENCRYPTED (1ULL << (63 - 8)) 5162306a36Sopenharmony_ci/* SCM device has been scrubbed and locked */ 5262306a36Sopenharmony_ci#define PAPR_PMEM_SCRUBBED_AND_LOCKED (1ULL << (63 - 9)) 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci/* Bits status indicators for health bitmap indicating unarmed dimm */ 5562306a36Sopenharmony_ci#define PAPR_PMEM_UNARMED_MASK (PAPR_PMEM_UNARMED | \ 5662306a36Sopenharmony_ci PAPR_PMEM_HEALTH_UNHEALTHY) 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci/* Bits status indicators for health bitmap indicating unflushed dimm */ 5962306a36Sopenharmony_ci#define PAPR_PMEM_BAD_SHUTDOWN_MASK (PAPR_PMEM_SHUTDOWN_DIRTY) 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci/* Bits status indicators for health bitmap indicating unrestored dimm */ 6262306a36Sopenharmony_ci#define PAPR_PMEM_BAD_RESTORE_MASK (PAPR_PMEM_EMPTY) 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci/* Bit status indicators for smart event notification */ 6562306a36Sopenharmony_ci#define PAPR_PMEM_SMART_EVENT_MASK (PAPR_PMEM_HEALTH_CRITICAL | \ 6662306a36Sopenharmony_ci PAPR_PMEM_HEALTH_FATAL | \ 6762306a36Sopenharmony_ci PAPR_PMEM_HEALTH_UNHEALTHY) 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci#define PAPR_SCM_PERF_STATS_EYECATCHER __stringify(SCMSTATS) 7062306a36Sopenharmony_ci#define PAPR_SCM_PERF_STATS_VERSION 0x1 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci/* Struct holding a single performance metric */ 7362306a36Sopenharmony_cistruct papr_scm_perf_stat { 7462306a36Sopenharmony_ci u8 stat_id[8]; 7562306a36Sopenharmony_ci __be64 stat_val; 7662306a36Sopenharmony_ci} __packed; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci/* Struct exchanged between kernel and PHYP for fetching drc perf stats */ 7962306a36Sopenharmony_cistruct papr_scm_perf_stats { 8062306a36Sopenharmony_ci u8 eye_catcher[8]; 8162306a36Sopenharmony_ci /* Should be PAPR_SCM_PERF_STATS_VERSION */ 8262306a36Sopenharmony_ci __be32 stats_version; 8362306a36Sopenharmony_ci /* Number of stats following */ 8462306a36Sopenharmony_ci __be32 num_statistics; 8562306a36Sopenharmony_ci /* zero or more performance matrics */ 8662306a36Sopenharmony_ci struct papr_scm_perf_stat scm_statistic[]; 8762306a36Sopenharmony_ci} __packed; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci/* private struct associated with each region */ 9062306a36Sopenharmony_cistruct papr_scm_priv { 9162306a36Sopenharmony_ci struct platform_device *pdev; 9262306a36Sopenharmony_ci struct device_node *dn; 9362306a36Sopenharmony_ci uint32_t drc_index; 9462306a36Sopenharmony_ci uint64_t blocks; 9562306a36Sopenharmony_ci uint64_t block_size; 9662306a36Sopenharmony_ci int metadata_size; 9762306a36Sopenharmony_ci bool is_volatile; 9862306a36Sopenharmony_ci bool hcall_flush_required; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci uint64_t bound_addr; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci struct nvdimm_bus_descriptor bus_desc; 10362306a36Sopenharmony_ci struct nvdimm_bus *bus; 10462306a36Sopenharmony_ci struct nvdimm *nvdimm; 10562306a36Sopenharmony_ci struct resource res; 10662306a36Sopenharmony_ci struct nd_region *region; 10762306a36Sopenharmony_ci struct nd_interleave_set nd_set; 10862306a36Sopenharmony_ci struct list_head region_list; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci /* Protect dimm health data from concurrent read/writes */ 11162306a36Sopenharmony_ci struct mutex health_mutex; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci /* Last time the health information of the dimm was updated */ 11462306a36Sopenharmony_ci unsigned long lasthealth_jiffies; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci /* Health information for the dimm */ 11762306a36Sopenharmony_ci u64 health_bitmap; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci /* Holds the last known dirty shutdown counter value */ 12062306a36Sopenharmony_ci u64 dirty_shutdown_counter; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci /* length of the stat buffer as expected by phyp */ 12362306a36Sopenharmony_ci size_t stat_buffer_len; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci /* The bits which needs to be overridden */ 12662306a36Sopenharmony_ci u64 health_bitmap_inject_mask; 12762306a36Sopenharmony_ci}; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_cistatic int papr_scm_pmem_flush(struct nd_region *nd_region, 13062306a36Sopenharmony_ci struct bio *bio __maybe_unused) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci struct papr_scm_priv *p = nd_region_provider_data(nd_region); 13362306a36Sopenharmony_ci unsigned long ret_buf[PLPAR_HCALL_BUFSIZE], token = 0; 13462306a36Sopenharmony_ci long rc; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "flush drc 0x%x", p->drc_index); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci do { 13962306a36Sopenharmony_ci rc = plpar_hcall(H_SCM_FLUSH, ret_buf, p->drc_index, token); 14062306a36Sopenharmony_ci token = ret_buf[0]; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci /* Check if we are stalled for some time */ 14362306a36Sopenharmony_ci if (H_IS_LONG_BUSY(rc)) { 14462306a36Sopenharmony_ci msleep(get_longbusy_msecs(rc)); 14562306a36Sopenharmony_ci rc = H_BUSY; 14662306a36Sopenharmony_ci } else if (rc == H_BUSY) { 14762306a36Sopenharmony_ci cond_resched(); 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci } while (rc == H_BUSY); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci if (rc) { 15262306a36Sopenharmony_ci dev_err(&p->pdev->dev, "flush error: %ld", rc); 15362306a36Sopenharmony_ci rc = -EIO; 15462306a36Sopenharmony_ci } else { 15562306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "flush drc 0x%x complete", p->drc_index); 15662306a36Sopenharmony_ci } 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci return rc; 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic LIST_HEAD(papr_nd_regions); 16262306a36Sopenharmony_cistatic DEFINE_MUTEX(papr_ndr_lock); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic int drc_pmem_bind(struct papr_scm_priv *p) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci unsigned long ret[PLPAR_HCALL_BUFSIZE]; 16762306a36Sopenharmony_ci uint64_t saved = 0; 16862306a36Sopenharmony_ci uint64_t token; 16962306a36Sopenharmony_ci int64_t rc; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci /* 17262306a36Sopenharmony_ci * When the hypervisor cannot map all the requested memory in a single 17362306a36Sopenharmony_ci * hcall it returns H_BUSY and we call again with the token until 17462306a36Sopenharmony_ci * we get H_SUCCESS. Aborting the retry loop before getting H_SUCCESS 17562306a36Sopenharmony_ci * leave the system in an undefined state, so we wait. 17662306a36Sopenharmony_ci */ 17762306a36Sopenharmony_ci token = 0; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci do { 18062306a36Sopenharmony_ci rc = plpar_hcall(H_SCM_BIND_MEM, ret, p->drc_index, 0, 18162306a36Sopenharmony_ci p->blocks, BIND_ANY_ADDR, token); 18262306a36Sopenharmony_ci token = ret[0]; 18362306a36Sopenharmony_ci if (!saved) 18462306a36Sopenharmony_ci saved = ret[1]; 18562306a36Sopenharmony_ci cond_resched(); 18662306a36Sopenharmony_ci } while (rc == H_BUSY); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci if (rc) 18962306a36Sopenharmony_ci return rc; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci p->bound_addr = saved; 19262306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "bound drc 0x%x to 0x%lx\n", 19362306a36Sopenharmony_ci p->drc_index, (unsigned long)saved); 19462306a36Sopenharmony_ci return rc; 19562306a36Sopenharmony_ci} 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_cistatic void drc_pmem_unbind(struct papr_scm_priv *p) 19862306a36Sopenharmony_ci{ 19962306a36Sopenharmony_ci unsigned long ret[PLPAR_HCALL_BUFSIZE]; 20062306a36Sopenharmony_ci uint64_t token = 0; 20162306a36Sopenharmony_ci int64_t rc; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "unbind drc 0x%x\n", p->drc_index); 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci /* NB: unbind has the same retry requirements as drc_pmem_bind() */ 20662306a36Sopenharmony_ci do { 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci /* Unbind of all SCM resources associated with drcIndex */ 20962306a36Sopenharmony_ci rc = plpar_hcall(H_SCM_UNBIND_ALL, ret, H_UNBIND_SCOPE_DRC, 21062306a36Sopenharmony_ci p->drc_index, token); 21162306a36Sopenharmony_ci token = ret[0]; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci /* Check if we are stalled for some time */ 21462306a36Sopenharmony_ci if (H_IS_LONG_BUSY(rc)) { 21562306a36Sopenharmony_ci msleep(get_longbusy_msecs(rc)); 21662306a36Sopenharmony_ci rc = H_BUSY; 21762306a36Sopenharmony_ci } else if (rc == H_BUSY) { 21862306a36Sopenharmony_ci cond_resched(); 21962306a36Sopenharmony_ci } 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci } while (rc == H_BUSY); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci if (rc) 22462306a36Sopenharmony_ci dev_err(&p->pdev->dev, "unbind error: %lld\n", rc); 22562306a36Sopenharmony_ci else 22662306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "unbind drc 0x%x complete\n", 22762306a36Sopenharmony_ci p->drc_index); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci return; 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic int drc_pmem_query_n_bind(struct papr_scm_priv *p) 23362306a36Sopenharmony_ci{ 23462306a36Sopenharmony_ci unsigned long start_addr; 23562306a36Sopenharmony_ci unsigned long end_addr; 23662306a36Sopenharmony_ci unsigned long ret[PLPAR_HCALL_BUFSIZE]; 23762306a36Sopenharmony_ci int64_t rc; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci rc = plpar_hcall(H_SCM_QUERY_BLOCK_MEM_BINDING, ret, 24162306a36Sopenharmony_ci p->drc_index, 0); 24262306a36Sopenharmony_ci if (rc) 24362306a36Sopenharmony_ci goto err_out; 24462306a36Sopenharmony_ci start_addr = ret[0]; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci /* Make sure the full region is bound. */ 24762306a36Sopenharmony_ci rc = plpar_hcall(H_SCM_QUERY_BLOCK_MEM_BINDING, ret, 24862306a36Sopenharmony_ci p->drc_index, p->blocks - 1); 24962306a36Sopenharmony_ci if (rc) 25062306a36Sopenharmony_ci goto err_out; 25162306a36Sopenharmony_ci end_addr = ret[0]; 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci if ((end_addr - start_addr) != ((p->blocks - 1) * p->block_size)) 25462306a36Sopenharmony_ci goto err_out; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci p->bound_addr = start_addr; 25762306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "bound drc 0x%x to 0x%lx\n", p->drc_index, start_addr); 25862306a36Sopenharmony_ci return rc; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_cierr_out: 26162306a36Sopenharmony_ci dev_info(&p->pdev->dev, 26262306a36Sopenharmony_ci "Failed to query, trying an unbind followed by bind"); 26362306a36Sopenharmony_ci drc_pmem_unbind(p); 26462306a36Sopenharmony_ci return drc_pmem_bind(p); 26562306a36Sopenharmony_ci} 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci/* 26862306a36Sopenharmony_ci * Query the Dimm performance stats from PHYP and copy them (if returned) to 26962306a36Sopenharmony_ci * provided struct papr_scm_perf_stats instance 'stats' that can hold atleast 27062306a36Sopenharmony_ci * (num_stats + header) bytes. 27162306a36Sopenharmony_ci * - If buff_stats == NULL the return value is the size in bytes of the buffer 27262306a36Sopenharmony_ci * needed to hold all supported performance-statistics. 27362306a36Sopenharmony_ci * - If buff_stats != NULL and num_stats == 0 then we copy all known 27462306a36Sopenharmony_ci * performance-statistics to 'buff_stat' and expect to be large enough to 27562306a36Sopenharmony_ci * hold them. 27662306a36Sopenharmony_ci * - if buff_stats != NULL and num_stats > 0 then copy the requested 27762306a36Sopenharmony_ci * performance-statistics to buff_stats. 27862306a36Sopenharmony_ci */ 27962306a36Sopenharmony_cistatic ssize_t drc_pmem_query_stats(struct papr_scm_priv *p, 28062306a36Sopenharmony_ci struct papr_scm_perf_stats *buff_stats, 28162306a36Sopenharmony_ci unsigned int num_stats) 28262306a36Sopenharmony_ci{ 28362306a36Sopenharmony_ci unsigned long ret[PLPAR_HCALL_BUFSIZE]; 28462306a36Sopenharmony_ci size_t size; 28562306a36Sopenharmony_ci s64 rc; 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci /* Setup the out buffer */ 28862306a36Sopenharmony_ci if (buff_stats) { 28962306a36Sopenharmony_ci memcpy(buff_stats->eye_catcher, 29062306a36Sopenharmony_ci PAPR_SCM_PERF_STATS_EYECATCHER, 8); 29162306a36Sopenharmony_ci buff_stats->stats_version = 29262306a36Sopenharmony_ci cpu_to_be32(PAPR_SCM_PERF_STATS_VERSION); 29362306a36Sopenharmony_ci buff_stats->num_statistics = 29462306a36Sopenharmony_ci cpu_to_be32(num_stats); 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci /* 29762306a36Sopenharmony_ci * Calculate the buffer size based on num-stats provided 29862306a36Sopenharmony_ci * or use the prefetched max buffer length 29962306a36Sopenharmony_ci */ 30062306a36Sopenharmony_ci if (num_stats) 30162306a36Sopenharmony_ci /* Calculate size from the num_stats */ 30262306a36Sopenharmony_ci size = sizeof(struct papr_scm_perf_stats) + 30362306a36Sopenharmony_ci num_stats * sizeof(struct papr_scm_perf_stat); 30462306a36Sopenharmony_ci else 30562306a36Sopenharmony_ci size = p->stat_buffer_len; 30662306a36Sopenharmony_ci } else { 30762306a36Sopenharmony_ci /* In case of no out buffer ignore the size */ 30862306a36Sopenharmony_ci size = 0; 30962306a36Sopenharmony_ci } 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci /* Do the HCALL asking PHYP for info */ 31262306a36Sopenharmony_ci rc = plpar_hcall(H_SCM_PERFORMANCE_STATS, ret, p->drc_index, 31362306a36Sopenharmony_ci buff_stats ? virt_to_phys(buff_stats) : 0, 31462306a36Sopenharmony_ci size); 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci /* Check if the error was due to an unknown stat-id */ 31762306a36Sopenharmony_ci if (rc == H_PARTIAL) { 31862306a36Sopenharmony_ci dev_err(&p->pdev->dev, 31962306a36Sopenharmony_ci "Unknown performance stats, Err:0x%016lX\n", ret[0]); 32062306a36Sopenharmony_ci return -ENOENT; 32162306a36Sopenharmony_ci } else if (rc == H_AUTHORITY) { 32262306a36Sopenharmony_ci dev_info(&p->pdev->dev, 32362306a36Sopenharmony_ci "Permission denied while accessing performance stats"); 32462306a36Sopenharmony_ci return -EPERM; 32562306a36Sopenharmony_ci } else if (rc == H_UNSUPPORTED) { 32662306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "Performance stats unsupported\n"); 32762306a36Sopenharmony_ci return -EOPNOTSUPP; 32862306a36Sopenharmony_ci } else if (rc != H_SUCCESS) { 32962306a36Sopenharmony_ci dev_err(&p->pdev->dev, 33062306a36Sopenharmony_ci "Failed to query performance stats, Err:%lld\n", rc); 33162306a36Sopenharmony_ci return -EIO; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci } else if (!size) { 33462306a36Sopenharmony_ci /* Handle case where stat buffer size was requested */ 33562306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, 33662306a36Sopenharmony_ci "Performance stats size %ld\n", ret[0]); 33762306a36Sopenharmony_ci return ret[0]; 33862306a36Sopenharmony_ci } 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci /* Successfully fetched the requested stats from phyp */ 34162306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, 34262306a36Sopenharmony_ci "Performance stats returned %d stats\n", 34362306a36Sopenharmony_ci be32_to_cpu(buff_stats->num_statistics)); 34462306a36Sopenharmony_ci return 0; 34562306a36Sopenharmony_ci} 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci#ifdef CONFIG_PERF_EVENTS 34862306a36Sopenharmony_ci#define to_nvdimm_pmu(_pmu) container_of(_pmu, struct nvdimm_pmu, pmu) 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_cistatic const char * const nvdimm_events_map[] = { 35162306a36Sopenharmony_ci [1] = "CtlResCt", 35262306a36Sopenharmony_ci [2] = "CtlResTm", 35362306a36Sopenharmony_ci [3] = "PonSecs ", 35462306a36Sopenharmony_ci [4] = "MemLife ", 35562306a36Sopenharmony_ci [5] = "CritRscU", 35662306a36Sopenharmony_ci [6] = "HostLCnt", 35762306a36Sopenharmony_ci [7] = "HostSCnt", 35862306a36Sopenharmony_ci [8] = "HostSDur", 35962306a36Sopenharmony_ci [9] = "HostLDur", 36062306a36Sopenharmony_ci [10] = "MedRCnt ", 36162306a36Sopenharmony_ci [11] = "MedWCnt ", 36262306a36Sopenharmony_ci [12] = "MedRDur ", 36362306a36Sopenharmony_ci [13] = "MedWDur ", 36462306a36Sopenharmony_ci [14] = "CchRHCnt", 36562306a36Sopenharmony_ci [15] = "CchWHCnt", 36662306a36Sopenharmony_ci [16] = "FastWCnt", 36762306a36Sopenharmony_ci}; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_cistatic int papr_scm_pmu_get_value(struct perf_event *event, struct device *dev, u64 *count) 37062306a36Sopenharmony_ci{ 37162306a36Sopenharmony_ci struct papr_scm_perf_stat *stat; 37262306a36Sopenharmony_ci struct papr_scm_perf_stats *stats; 37362306a36Sopenharmony_ci struct papr_scm_priv *p = dev_get_drvdata(dev); 37462306a36Sopenharmony_ci int rc, size; 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci /* Invalid eventcode */ 37762306a36Sopenharmony_ci if (event->attr.config == 0 || event->attr.config >= ARRAY_SIZE(nvdimm_events_map)) 37862306a36Sopenharmony_ci return -EINVAL; 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci /* Allocate request buffer enough to hold single performance stat */ 38162306a36Sopenharmony_ci size = sizeof(struct papr_scm_perf_stats) + 38262306a36Sopenharmony_ci sizeof(struct papr_scm_perf_stat); 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci if (!p) 38562306a36Sopenharmony_ci return -EINVAL; 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci stats = kzalloc(size, GFP_KERNEL); 38862306a36Sopenharmony_ci if (!stats) 38962306a36Sopenharmony_ci return -ENOMEM; 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci stat = &stats->scm_statistic[0]; 39262306a36Sopenharmony_ci memcpy(&stat->stat_id, 39362306a36Sopenharmony_ci nvdimm_events_map[event->attr.config], 39462306a36Sopenharmony_ci sizeof(stat->stat_id)); 39562306a36Sopenharmony_ci stat->stat_val = 0; 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci rc = drc_pmem_query_stats(p, stats, 1); 39862306a36Sopenharmony_ci if (rc < 0) { 39962306a36Sopenharmony_ci kfree(stats); 40062306a36Sopenharmony_ci return rc; 40162306a36Sopenharmony_ci } 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci *count = be64_to_cpu(stat->stat_val); 40462306a36Sopenharmony_ci kfree(stats); 40562306a36Sopenharmony_ci return 0; 40662306a36Sopenharmony_ci} 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_cistatic int papr_scm_pmu_event_init(struct perf_event *event) 40962306a36Sopenharmony_ci{ 41062306a36Sopenharmony_ci struct nvdimm_pmu *nd_pmu = to_nvdimm_pmu(event->pmu); 41162306a36Sopenharmony_ci struct papr_scm_priv *p; 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci if (!nd_pmu) 41462306a36Sopenharmony_ci return -EINVAL; 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci /* test the event attr type for PMU enumeration */ 41762306a36Sopenharmony_ci if (event->attr.type != event->pmu->type) 41862306a36Sopenharmony_ci return -ENOENT; 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ci /* it does not support event sampling mode */ 42162306a36Sopenharmony_ci if (is_sampling_event(event)) 42262306a36Sopenharmony_ci return -EOPNOTSUPP; 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci /* no branch sampling */ 42562306a36Sopenharmony_ci if (has_branch_stack(event)) 42662306a36Sopenharmony_ci return -EOPNOTSUPP; 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci p = (struct papr_scm_priv *)nd_pmu->dev->driver_data; 42962306a36Sopenharmony_ci if (!p) 43062306a36Sopenharmony_ci return -EINVAL; 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci /* Invalid eventcode */ 43362306a36Sopenharmony_ci if (event->attr.config == 0 || event->attr.config > 16) 43462306a36Sopenharmony_ci return -EINVAL; 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci return 0; 43762306a36Sopenharmony_ci} 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_cistatic int papr_scm_pmu_add(struct perf_event *event, int flags) 44062306a36Sopenharmony_ci{ 44162306a36Sopenharmony_ci u64 count; 44262306a36Sopenharmony_ci int rc; 44362306a36Sopenharmony_ci struct nvdimm_pmu *nd_pmu = to_nvdimm_pmu(event->pmu); 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci if (!nd_pmu) 44662306a36Sopenharmony_ci return -EINVAL; 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci if (flags & PERF_EF_START) { 44962306a36Sopenharmony_ci rc = papr_scm_pmu_get_value(event, nd_pmu->dev, &count); 45062306a36Sopenharmony_ci if (rc) 45162306a36Sopenharmony_ci return rc; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci local64_set(&event->hw.prev_count, count); 45462306a36Sopenharmony_ci } 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci return 0; 45762306a36Sopenharmony_ci} 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_cistatic void papr_scm_pmu_read(struct perf_event *event) 46062306a36Sopenharmony_ci{ 46162306a36Sopenharmony_ci u64 prev, now; 46262306a36Sopenharmony_ci int rc; 46362306a36Sopenharmony_ci struct nvdimm_pmu *nd_pmu = to_nvdimm_pmu(event->pmu); 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci if (!nd_pmu) 46662306a36Sopenharmony_ci return; 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci rc = papr_scm_pmu_get_value(event, nd_pmu->dev, &now); 46962306a36Sopenharmony_ci if (rc) 47062306a36Sopenharmony_ci return; 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci prev = local64_xchg(&event->hw.prev_count, now); 47362306a36Sopenharmony_ci local64_add(now - prev, &event->count); 47462306a36Sopenharmony_ci} 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_cistatic void papr_scm_pmu_del(struct perf_event *event, int flags) 47762306a36Sopenharmony_ci{ 47862306a36Sopenharmony_ci papr_scm_pmu_read(event); 47962306a36Sopenharmony_ci} 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_cistatic void papr_scm_pmu_register(struct papr_scm_priv *p) 48262306a36Sopenharmony_ci{ 48362306a36Sopenharmony_ci struct nvdimm_pmu *nd_pmu; 48462306a36Sopenharmony_ci int rc, nodeid; 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_ci nd_pmu = kzalloc(sizeof(*nd_pmu), GFP_KERNEL); 48762306a36Sopenharmony_ci if (!nd_pmu) { 48862306a36Sopenharmony_ci rc = -ENOMEM; 48962306a36Sopenharmony_ci goto pmu_err_print; 49062306a36Sopenharmony_ci } 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci if (!p->stat_buffer_len) { 49362306a36Sopenharmony_ci rc = -ENOENT; 49462306a36Sopenharmony_ci goto pmu_check_events_err; 49562306a36Sopenharmony_ci } 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ci nd_pmu->pmu.task_ctx_nr = perf_invalid_context; 49862306a36Sopenharmony_ci nd_pmu->pmu.name = nvdimm_name(p->nvdimm); 49962306a36Sopenharmony_ci nd_pmu->pmu.event_init = papr_scm_pmu_event_init; 50062306a36Sopenharmony_ci nd_pmu->pmu.read = papr_scm_pmu_read; 50162306a36Sopenharmony_ci nd_pmu->pmu.add = papr_scm_pmu_add; 50262306a36Sopenharmony_ci nd_pmu->pmu.del = papr_scm_pmu_del; 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci nd_pmu->pmu.capabilities = PERF_PMU_CAP_NO_INTERRUPT | 50562306a36Sopenharmony_ci PERF_PMU_CAP_NO_EXCLUDE; 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci /*updating the cpumask variable */ 50862306a36Sopenharmony_ci nodeid = numa_map_to_online_node(dev_to_node(&p->pdev->dev)); 50962306a36Sopenharmony_ci nd_pmu->arch_cpumask = *cpumask_of_node(nodeid); 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci rc = register_nvdimm_pmu(nd_pmu, p->pdev); 51262306a36Sopenharmony_ci if (rc) 51362306a36Sopenharmony_ci goto pmu_check_events_err; 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_ci /* 51662306a36Sopenharmony_ci * Set archdata.priv value to nvdimm_pmu structure, to handle the 51762306a36Sopenharmony_ci * unregistering of pmu device. 51862306a36Sopenharmony_ci */ 51962306a36Sopenharmony_ci p->pdev->archdata.priv = nd_pmu; 52062306a36Sopenharmony_ci return; 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_cipmu_check_events_err: 52362306a36Sopenharmony_ci kfree(nd_pmu); 52462306a36Sopenharmony_cipmu_err_print: 52562306a36Sopenharmony_ci dev_info(&p->pdev->dev, "nvdimm pmu didn't register rc=%d\n", rc); 52662306a36Sopenharmony_ci} 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci#else 52962306a36Sopenharmony_cistatic void papr_scm_pmu_register(struct papr_scm_priv *p) { } 53062306a36Sopenharmony_ci#endif 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci/* 53362306a36Sopenharmony_ci * Issue hcall to retrieve dimm health info and populate papr_scm_priv with the 53462306a36Sopenharmony_ci * health information. 53562306a36Sopenharmony_ci */ 53662306a36Sopenharmony_cistatic int __drc_pmem_query_health(struct papr_scm_priv *p) 53762306a36Sopenharmony_ci{ 53862306a36Sopenharmony_ci unsigned long ret[PLPAR_HCALL_BUFSIZE]; 53962306a36Sopenharmony_ci u64 bitmap = 0; 54062306a36Sopenharmony_ci long rc; 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci /* issue the hcall */ 54362306a36Sopenharmony_ci rc = plpar_hcall(H_SCM_HEALTH, ret, p->drc_index); 54462306a36Sopenharmony_ci if (rc == H_SUCCESS) 54562306a36Sopenharmony_ci bitmap = ret[0] & ret[1]; 54662306a36Sopenharmony_ci else if (rc == H_FUNCTION) 54762306a36Sopenharmony_ci dev_info_once(&p->pdev->dev, 54862306a36Sopenharmony_ci "Hcall H_SCM_HEALTH not implemented, assuming empty health bitmap"); 54962306a36Sopenharmony_ci else { 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci dev_err(&p->pdev->dev, 55262306a36Sopenharmony_ci "Failed to query health information, Err:%ld\n", rc); 55362306a36Sopenharmony_ci return -ENXIO; 55462306a36Sopenharmony_ci } 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_ci p->lasthealth_jiffies = jiffies; 55762306a36Sopenharmony_ci /* Allow injecting specific health bits via inject mask. */ 55862306a36Sopenharmony_ci if (p->health_bitmap_inject_mask) 55962306a36Sopenharmony_ci bitmap = (bitmap & ~p->health_bitmap_inject_mask) | 56062306a36Sopenharmony_ci p->health_bitmap_inject_mask; 56162306a36Sopenharmony_ci WRITE_ONCE(p->health_bitmap, bitmap); 56262306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, 56362306a36Sopenharmony_ci "Queried dimm health info. Bitmap:0x%016lx Mask:0x%016lx\n", 56462306a36Sopenharmony_ci ret[0], ret[1]); 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci return 0; 56762306a36Sopenharmony_ci} 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ci/* Min interval in seconds for assuming stable dimm health */ 57062306a36Sopenharmony_ci#define MIN_HEALTH_QUERY_INTERVAL 60 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci/* Query cached health info and if needed call drc_pmem_query_health */ 57362306a36Sopenharmony_cistatic int drc_pmem_query_health(struct papr_scm_priv *p) 57462306a36Sopenharmony_ci{ 57562306a36Sopenharmony_ci unsigned long cache_timeout; 57662306a36Sopenharmony_ci int rc; 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci /* Protect concurrent modifications to papr_scm_priv */ 57962306a36Sopenharmony_ci rc = mutex_lock_interruptible(&p->health_mutex); 58062306a36Sopenharmony_ci if (rc) 58162306a36Sopenharmony_ci return rc; 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci /* Jiffies offset for which the health data is assumed to be same */ 58462306a36Sopenharmony_ci cache_timeout = p->lasthealth_jiffies + 58562306a36Sopenharmony_ci msecs_to_jiffies(MIN_HEALTH_QUERY_INTERVAL * 1000); 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci /* Fetch new health info is its older than MIN_HEALTH_QUERY_INTERVAL */ 58862306a36Sopenharmony_ci if (time_after(jiffies, cache_timeout)) 58962306a36Sopenharmony_ci rc = __drc_pmem_query_health(p); 59062306a36Sopenharmony_ci else 59162306a36Sopenharmony_ci /* Assume cached health data is valid */ 59262306a36Sopenharmony_ci rc = 0; 59362306a36Sopenharmony_ci 59462306a36Sopenharmony_ci mutex_unlock(&p->health_mutex); 59562306a36Sopenharmony_ci return rc; 59662306a36Sopenharmony_ci} 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_cistatic int papr_scm_meta_get(struct papr_scm_priv *p, 59962306a36Sopenharmony_ci struct nd_cmd_get_config_data_hdr *hdr) 60062306a36Sopenharmony_ci{ 60162306a36Sopenharmony_ci unsigned long data[PLPAR_HCALL_BUFSIZE]; 60262306a36Sopenharmony_ci unsigned long offset, data_offset; 60362306a36Sopenharmony_ci int len, read; 60462306a36Sopenharmony_ci int64_t ret; 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci if ((hdr->in_offset + hdr->in_length) > p->metadata_size) 60762306a36Sopenharmony_ci return -EINVAL; 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci for (len = hdr->in_length; len; len -= read) { 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_ci data_offset = hdr->in_length - len; 61262306a36Sopenharmony_ci offset = hdr->in_offset + data_offset; 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci if (len >= 8) 61562306a36Sopenharmony_ci read = 8; 61662306a36Sopenharmony_ci else if (len >= 4) 61762306a36Sopenharmony_ci read = 4; 61862306a36Sopenharmony_ci else if (len >= 2) 61962306a36Sopenharmony_ci read = 2; 62062306a36Sopenharmony_ci else 62162306a36Sopenharmony_ci read = 1; 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_ci ret = plpar_hcall(H_SCM_READ_METADATA, data, p->drc_index, 62462306a36Sopenharmony_ci offset, read); 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci if (ret == H_PARAMETER) /* bad DRC index */ 62762306a36Sopenharmony_ci return -ENODEV; 62862306a36Sopenharmony_ci if (ret) 62962306a36Sopenharmony_ci return -EINVAL; /* other invalid parameter */ 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_ci switch (read) { 63262306a36Sopenharmony_ci case 8: 63362306a36Sopenharmony_ci *(uint64_t *)(hdr->out_buf + data_offset) = be64_to_cpu(data[0]); 63462306a36Sopenharmony_ci break; 63562306a36Sopenharmony_ci case 4: 63662306a36Sopenharmony_ci *(uint32_t *)(hdr->out_buf + data_offset) = be32_to_cpu(data[0] & 0xffffffff); 63762306a36Sopenharmony_ci break; 63862306a36Sopenharmony_ci 63962306a36Sopenharmony_ci case 2: 64062306a36Sopenharmony_ci *(uint16_t *)(hdr->out_buf + data_offset) = be16_to_cpu(data[0] & 0xffff); 64162306a36Sopenharmony_ci break; 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_ci case 1: 64462306a36Sopenharmony_ci *(uint8_t *)(hdr->out_buf + data_offset) = (data[0] & 0xff); 64562306a36Sopenharmony_ci break; 64662306a36Sopenharmony_ci } 64762306a36Sopenharmony_ci } 64862306a36Sopenharmony_ci return 0; 64962306a36Sopenharmony_ci} 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_cistatic int papr_scm_meta_set(struct papr_scm_priv *p, 65262306a36Sopenharmony_ci struct nd_cmd_set_config_hdr *hdr) 65362306a36Sopenharmony_ci{ 65462306a36Sopenharmony_ci unsigned long offset, data_offset; 65562306a36Sopenharmony_ci int len, wrote; 65662306a36Sopenharmony_ci unsigned long data; 65762306a36Sopenharmony_ci __be64 data_be; 65862306a36Sopenharmony_ci int64_t ret; 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_ci if ((hdr->in_offset + hdr->in_length) > p->metadata_size) 66162306a36Sopenharmony_ci return -EINVAL; 66262306a36Sopenharmony_ci 66362306a36Sopenharmony_ci for (len = hdr->in_length; len; len -= wrote) { 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_ci data_offset = hdr->in_length - len; 66662306a36Sopenharmony_ci offset = hdr->in_offset + data_offset; 66762306a36Sopenharmony_ci 66862306a36Sopenharmony_ci if (len >= 8) { 66962306a36Sopenharmony_ci data = *(uint64_t *)(hdr->in_buf + data_offset); 67062306a36Sopenharmony_ci data_be = cpu_to_be64(data); 67162306a36Sopenharmony_ci wrote = 8; 67262306a36Sopenharmony_ci } else if (len >= 4) { 67362306a36Sopenharmony_ci data = *(uint32_t *)(hdr->in_buf + data_offset); 67462306a36Sopenharmony_ci data &= 0xffffffff; 67562306a36Sopenharmony_ci data_be = cpu_to_be32(data); 67662306a36Sopenharmony_ci wrote = 4; 67762306a36Sopenharmony_ci } else if (len >= 2) { 67862306a36Sopenharmony_ci data = *(uint16_t *)(hdr->in_buf + data_offset); 67962306a36Sopenharmony_ci data &= 0xffff; 68062306a36Sopenharmony_ci data_be = cpu_to_be16(data); 68162306a36Sopenharmony_ci wrote = 2; 68262306a36Sopenharmony_ci } else { 68362306a36Sopenharmony_ci data_be = *(uint8_t *)(hdr->in_buf + data_offset); 68462306a36Sopenharmony_ci data_be &= 0xff; 68562306a36Sopenharmony_ci wrote = 1; 68662306a36Sopenharmony_ci } 68762306a36Sopenharmony_ci 68862306a36Sopenharmony_ci ret = plpar_hcall_norets(H_SCM_WRITE_METADATA, p->drc_index, 68962306a36Sopenharmony_ci offset, data_be, wrote); 69062306a36Sopenharmony_ci if (ret == H_PARAMETER) /* bad DRC index */ 69162306a36Sopenharmony_ci return -ENODEV; 69262306a36Sopenharmony_ci if (ret) 69362306a36Sopenharmony_ci return -EINVAL; /* other invalid parameter */ 69462306a36Sopenharmony_ci } 69562306a36Sopenharmony_ci 69662306a36Sopenharmony_ci return 0; 69762306a36Sopenharmony_ci} 69862306a36Sopenharmony_ci 69962306a36Sopenharmony_ci/* 70062306a36Sopenharmony_ci * Do a sanity checks on the inputs args to dimm-control function and return 70162306a36Sopenharmony_ci * '0' if valid. Validation of PDSM payloads happens later in 70262306a36Sopenharmony_ci * papr_scm_service_pdsm. 70362306a36Sopenharmony_ci */ 70462306a36Sopenharmony_cistatic int is_cmd_valid(struct nvdimm *nvdimm, unsigned int cmd, void *buf, 70562306a36Sopenharmony_ci unsigned int buf_len) 70662306a36Sopenharmony_ci{ 70762306a36Sopenharmony_ci unsigned long cmd_mask = PAPR_SCM_DIMM_CMD_MASK; 70862306a36Sopenharmony_ci struct nd_cmd_pkg *nd_cmd; 70962306a36Sopenharmony_ci struct papr_scm_priv *p; 71062306a36Sopenharmony_ci enum papr_pdsm pdsm; 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_ci /* Only dimm-specific calls are supported atm */ 71362306a36Sopenharmony_ci if (!nvdimm) 71462306a36Sopenharmony_ci return -EINVAL; 71562306a36Sopenharmony_ci 71662306a36Sopenharmony_ci /* get the provider data from struct nvdimm */ 71762306a36Sopenharmony_ci p = nvdimm_provider_data(nvdimm); 71862306a36Sopenharmony_ci 71962306a36Sopenharmony_ci if (!test_bit(cmd, &cmd_mask)) { 72062306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "Unsupported cmd=%u\n", cmd); 72162306a36Sopenharmony_ci return -EINVAL; 72262306a36Sopenharmony_ci } 72362306a36Sopenharmony_ci 72462306a36Sopenharmony_ci /* For CMD_CALL verify pdsm request */ 72562306a36Sopenharmony_ci if (cmd == ND_CMD_CALL) { 72662306a36Sopenharmony_ci /* Verify the envelope and envelop size */ 72762306a36Sopenharmony_ci if (!buf || 72862306a36Sopenharmony_ci buf_len < (sizeof(struct nd_cmd_pkg) + ND_PDSM_HDR_SIZE)) { 72962306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "Invalid pkg size=%u\n", 73062306a36Sopenharmony_ci buf_len); 73162306a36Sopenharmony_ci return -EINVAL; 73262306a36Sopenharmony_ci } 73362306a36Sopenharmony_ci 73462306a36Sopenharmony_ci /* Verify that the nd_cmd_pkg.nd_family is correct */ 73562306a36Sopenharmony_ci nd_cmd = (struct nd_cmd_pkg *)buf; 73662306a36Sopenharmony_ci 73762306a36Sopenharmony_ci if (nd_cmd->nd_family != NVDIMM_FAMILY_PAPR) { 73862306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "Invalid pkg family=0x%llx\n", 73962306a36Sopenharmony_ci nd_cmd->nd_family); 74062306a36Sopenharmony_ci return -EINVAL; 74162306a36Sopenharmony_ci } 74262306a36Sopenharmony_ci 74362306a36Sopenharmony_ci pdsm = (enum papr_pdsm)nd_cmd->nd_command; 74462306a36Sopenharmony_ci 74562306a36Sopenharmony_ci /* Verify if the pdsm command is valid */ 74662306a36Sopenharmony_ci if (pdsm <= PAPR_PDSM_MIN || pdsm >= PAPR_PDSM_MAX) { 74762306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "PDSM[0x%x]: Invalid PDSM\n", 74862306a36Sopenharmony_ci pdsm); 74962306a36Sopenharmony_ci return -EINVAL; 75062306a36Sopenharmony_ci } 75162306a36Sopenharmony_ci 75262306a36Sopenharmony_ci /* Have enough space to hold returned 'nd_pkg_pdsm' header */ 75362306a36Sopenharmony_ci if (nd_cmd->nd_size_out < ND_PDSM_HDR_SIZE) { 75462306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "PDSM[0x%x]: Invalid payload\n", 75562306a36Sopenharmony_ci pdsm); 75662306a36Sopenharmony_ci return -EINVAL; 75762306a36Sopenharmony_ci } 75862306a36Sopenharmony_ci } 75962306a36Sopenharmony_ci 76062306a36Sopenharmony_ci /* Let the command be further processed */ 76162306a36Sopenharmony_ci return 0; 76262306a36Sopenharmony_ci} 76362306a36Sopenharmony_ci 76462306a36Sopenharmony_cistatic int papr_pdsm_fuel_gauge(struct papr_scm_priv *p, 76562306a36Sopenharmony_ci union nd_pdsm_payload *payload) 76662306a36Sopenharmony_ci{ 76762306a36Sopenharmony_ci int rc, size; 76862306a36Sopenharmony_ci u64 statval; 76962306a36Sopenharmony_ci struct papr_scm_perf_stat *stat; 77062306a36Sopenharmony_ci struct papr_scm_perf_stats *stats; 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_ci /* Silently fail if fetching performance metrics isn't supported */ 77362306a36Sopenharmony_ci if (!p->stat_buffer_len) 77462306a36Sopenharmony_ci return 0; 77562306a36Sopenharmony_ci 77662306a36Sopenharmony_ci /* Allocate request buffer enough to hold single performance stat */ 77762306a36Sopenharmony_ci size = sizeof(struct papr_scm_perf_stats) + 77862306a36Sopenharmony_ci sizeof(struct papr_scm_perf_stat); 77962306a36Sopenharmony_ci 78062306a36Sopenharmony_ci stats = kzalloc(size, GFP_KERNEL); 78162306a36Sopenharmony_ci if (!stats) 78262306a36Sopenharmony_ci return -ENOMEM; 78362306a36Sopenharmony_ci 78462306a36Sopenharmony_ci stat = &stats->scm_statistic[0]; 78562306a36Sopenharmony_ci memcpy(&stat->stat_id, "MemLife ", sizeof(stat->stat_id)); 78662306a36Sopenharmony_ci stat->stat_val = 0; 78762306a36Sopenharmony_ci 78862306a36Sopenharmony_ci /* Fetch the fuel gauge and populate it in payload */ 78962306a36Sopenharmony_ci rc = drc_pmem_query_stats(p, stats, 1); 79062306a36Sopenharmony_ci if (rc < 0) { 79162306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "Err(%d) fetching fuel gauge\n", rc); 79262306a36Sopenharmony_ci goto free_stats; 79362306a36Sopenharmony_ci } 79462306a36Sopenharmony_ci 79562306a36Sopenharmony_ci statval = be64_to_cpu(stat->stat_val); 79662306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, 79762306a36Sopenharmony_ci "Fetched fuel-gauge %llu", statval); 79862306a36Sopenharmony_ci payload->health.extension_flags |= 79962306a36Sopenharmony_ci PDSM_DIMM_HEALTH_RUN_GAUGE_VALID; 80062306a36Sopenharmony_ci payload->health.dimm_fuel_gauge = statval; 80162306a36Sopenharmony_ci 80262306a36Sopenharmony_ci rc = sizeof(struct nd_papr_pdsm_health); 80362306a36Sopenharmony_ci 80462306a36Sopenharmony_cifree_stats: 80562306a36Sopenharmony_ci kfree(stats); 80662306a36Sopenharmony_ci return rc; 80762306a36Sopenharmony_ci} 80862306a36Sopenharmony_ci 80962306a36Sopenharmony_ci/* Add the dirty-shutdown-counter value to the pdsm */ 81062306a36Sopenharmony_cistatic int papr_pdsm_dsc(struct papr_scm_priv *p, 81162306a36Sopenharmony_ci union nd_pdsm_payload *payload) 81262306a36Sopenharmony_ci{ 81362306a36Sopenharmony_ci payload->health.extension_flags |= PDSM_DIMM_DSC_VALID; 81462306a36Sopenharmony_ci payload->health.dimm_dsc = p->dirty_shutdown_counter; 81562306a36Sopenharmony_ci 81662306a36Sopenharmony_ci return sizeof(struct nd_papr_pdsm_health); 81762306a36Sopenharmony_ci} 81862306a36Sopenharmony_ci 81962306a36Sopenharmony_ci/* Fetch the DIMM health info and populate it in provided package. */ 82062306a36Sopenharmony_cistatic int papr_pdsm_health(struct papr_scm_priv *p, 82162306a36Sopenharmony_ci union nd_pdsm_payload *payload) 82262306a36Sopenharmony_ci{ 82362306a36Sopenharmony_ci int rc; 82462306a36Sopenharmony_ci 82562306a36Sopenharmony_ci /* Ensure dimm health mutex is taken preventing concurrent access */ 82662306a36Sopenharmony_ci rc = mutex_lock_interruptible(&p->health_mutex); 82762306a36Sopenharmony_ci if (rc) 82862306a36Sopenharmony_ci goto out; 82962306a36Sopenharmony_ci 83062306a36Sopenharmony_ci /* Always fetch upto date dimm health data ignoring cached values */ 83162306a36Sopenharmony_ci rc = __drc_pmem_query_health(p); 83262306a36Sopenharmony_ci if (rc) { 83362306a36Sopenharmony_ci mutex_unlock(&p->health_mutex); 83462306a36Sopenharmony_ci goto out; 83562306a36Sopenharmony_ci } 83662306a36Sopenharmony_ci 83762306a36Sopenharmony_ci /* update health struct with various flags derived from health bitmap */ 83862306a36Sopenharmony_ci payload->health = (struct nd_papr_pdsm_health) { 83962306a36Sopenharmony_ci .extension_flags = 0, 84062306a36Sopenharmony_ci .dimm_unarmed = !!(p->health_bitmap & PAPR_PMEM_UNARMED_MASK), 84162306a36Sopenharmony_ci .dimm_bad_shutdown = !!(p->health_bitmap & PAPR_PMEM_BAD_SHUTDOWN_MASK), 84262306a36Sopenharmony_ci .dimm_bad_restore = !!(p->health_bitmap & PAPR_PMEM_BAD_RESTORE_MASK), 84362306a36Sopenharmony_ci .dimm_scrubbed = !!(p->health_bitmap & PAPR_PMEM_SCRUBBED_AND_LOCKED), 84462306a36Sopenharmony_ci .dimm_locked = !!(p->health_bitmap & PAPR_PMEM_SCRUBBED_AND_LOCKED), 84562306a36Sopenharmony_ci .dimm_encrypted = !!(p->health_bitmap & PAPR_PMEM_ENCRYPTED), 84662306a36Sopenharmony_ci .dimm_health = PAPR_PDSM_DIMM_HEALTHY, 84762306a36Sopenharmony_ci }; 84862306a36Sopenharmony_ci 84962306a36Sopenharmony_ci /* Update field dimm_health based on health_bitmap flags */ 85062306a36Sopenharmony_ci if (p->health_bitmap & PAPR_PMEM_HEALTH_FATAL) 85162306a36Sopenharmony_ci payload->health.dimm_health = PAPR_PDSM_DIMM_FATAL; 85262306a36Sopenharmony_ci else if (p->health_bitmap & PAPR_PMEM_HEALTH_CRITICAL) 85362306a36Sopenharmony_ci payload->health.dimm_health = PAPR_PDSM_DIMM_CRITICAL; 85462306a36Sopenharmony_ci else if (p->health_bitmap & PAPR_PMEM_HEALTH_UNHEALTHY) 85562306a36Sopenharmony_ci payload->health.dimm_health = PAPR_PDSM_DIMM_UNHEALTHY; 85662306a36Sopenharmony_ci 85762306a36Sopenharmony_ci /* struct populated hence can release the mutex now */ 85862306a36Sopenharmony_ci mutex_unlock(&p->health_mutex); 85962306a36Sopenharmony_ci 86062306a36Sopenharmony_ci /* Populate the fuel gauge meter in the payload */ 86162306a36Sopenharmony_ci papr_pdsm_fuel_gauge(p, payload); 86262306a36Sopenharmony_ci /* Populate the dirty-shutdown-counter field */ 86362306a36Sopenharmony_ci papr_pdsm_dsc(p, payload); 86462306a36Sopenharmony_ci 86562306a36Sopenharmony_ci rc = sizeof(struct nd_papr_pdsm_health); 86662306a36Sopenharmony_ci 86762306a36Sopenharmony_ciout: 86862306a36Sopenharmony_ci return rc; 86962306a36Sopenharmony_ci} 87062306a36Sopenharmony_ci 87162306a36Sopenharmony_ci/* Inject a smart error Add the dirty-shutdown-counter value to the pdsm */ 87262306a36Sopenharmony_cistatic int papr_pdsm_smart_inject(struct papr_scm_priv *p, 87362306a36Sopenharmony_ci union nd_pdsm_payload *payload) 87462306a36Sopenharmony_ci{ 87562306a36Sopenharmony_ci int rc; 87662306a36Sopenharmony_ci u32 supported_flags = 0; 87762306a36Sopenharmony_ci u64 inject_mask = 0, clear_mask = 0; 87862306a36Sopenharmony_ci u64 mask; 87962306a36Sopenharmony_ci 88062306a36Sopenharmony_ci /* Check for individual smart error flags and update inject/clear masks */ 88162306a36Sopenharmony_ci if (payload->smart_inject.flags & PDSM_SMART_INJECT_HEALTH_FATAL) { 88262306a36Sopenharmony_ci supported_flags |= PDSM_SMART_INJECT_HEALTH_FATAL; 88362306a36Sopenharmony_ci if (payload->smart_inject.fatal_enable) 88462306a36Sopenharmony_ci inject_mask |= PAPR_PMEM_HEALTH_FATAL; 88562306a36Sopenharmony_ci else 88662306a36Sopenharmony_ci clear_mask |= PAPR_PMEM_HEALTH_FATAL; 88762306a36Sopenharmony_ci } 88862306a36Sopenharmony_ci 88962306a36Sopenharmony_ci if (payload->smart_inject.flags & PDSM_SMART_INJECT_BAD_SHUTDOWN) { 89062306a36Sopenharmony_ci supported_flags |= PDSM_SMART_INJECT_BAD_SHUTDOWN; 89162306a36Sopenharmony_ci if (payload->smart_inject.unsafe_shutdown_enable) 89262306a36Sopenharmony_ci inject_mask |= PAPR_PMEM_SHUTDOWN_DIRTY; 89362306a36Sopenharmony_ci else 89462306a36Sopenharmony_ci clear_mask |= PAPR_PMEM_SHUTDOWN_DIRTY; 89562306a36Sopenharmony_ci } 89662306a36Sopenharmony_ci 89762306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "[Smart-inject] inject_mask=%#llx clear_mask=%#llx\n", 89862306a36Sopenharmony_ci inject_mask, clear_mask); 89962306a36Sopenharmony_ci 90062306a36Sopenharmony_ci /* Prevent concurrent access to dimm health bitmap related members */ 90162306a36Sopenharmony_ci rc = mutex_lock_interruptible(&p->health_mutex); 90262306a36Sopenharmony_ci if (rc) 90362306a36Sopenharmony_ci return rc; 90462306a36Sopenharmony_ci 90562306a36Sopenharmony_ci /* Use inject/clear masks to set health_bitmap_inject_mask */ 90662306a36Sopenharmony_ci mask = READ_ONCE(p->health_bitmap_inject_mask); 90762306a36Sopenharmony_ci mask = (mask & ~clear_mask) | inject_mask; 90862306a36Sopenharmony_ci WRITE_ONCE(p->health_bitmap_inject_mask, mask); 90962306a36Sopenharmony_ci 91062306a36Sopenharmony_ci /* Invalidate cached health bitmap */ 91162306a36Sopenharmony_ci p->lasthealth_jiffies = 0; 91262306a36Sopenharmony_ci 91362306a36Sopenharmony_ci mutex_unlock(&p->health_mutex); 91462306a36Sopenharmony_ci 91562306a36Sopenharmony_ci /* Return the supported flags back to userspace */ 91662306a36Sopenharmony_ci payload->smart_inject.flags = supported_flags; 91762306a36Sopenharmony_ci 91862306a36Sopenharmony_ci return sizeof(struct nd_papr_pdsm_health); 91962306a36Sopenharmony_ci} 92062306a36Sopenharmony_ci 92162306a36Sopenharmony_ci/* 92262306a36Sopenharmony_ci * 'struct pdsm_cmd_desc' 92362306a36Sopenharmony_ci * Identifies supported PDSMs' expected length of in/out payloads 92462306a36Sopenharmony_ci * and pdsm service function. 92562306a36Sopenharmony_ci * 92662306a36Sopenharmony_ci * size_in : Size of input payload if any in the PDSM request. 92762306a36Sopenharmony_ci * size_out : Size of output payload if any in the PDSM request. 92862306a36Sopenharmony_ci * service : Service function for the PDSM request. Return semantics: 92962306a36Sopenharmony_ci * rc < 0 : Error servicing PDSM and rc indicates the error. 93062306a36Sopenharmony_ci * rc >=0 : Serviced successfully and 'rc' indicate number of 93162306a36Sopenharmony_ci * bytes written to payload. 93262306a36Sopenharmony_ci */ 93362306a36Sopenharmony_cistruct pdsm_cmd_desc { 93462306a36Sopenharmony_ci u32 size_in; 93562306a36Sopenharmony_ci u32 size_out; 93662306a36Sopenharmony_ci int (*service)(struct papr_scm_priv *dimm, 93762306a36Sopenharmony_ci union nd_pdsm_payload *payload); 93862306a36Sopenharmony_ci}; 93962306a36Sopenharmony_ci 94062306a36Sopenharmony_ci/* Holds all supported PDSMs' command descriptors */ 94162306a36Sopenharmony_cistatic const struct pdsm_cmd_desc __pdsm_cmd_descriptors[] = { 94262306a36Sopenharmony_ci [PAPR_PDSM_MIN] = { 94362306a36Sopenharmony_ci .size_in = 0, 94462306a36Sopenharmony_ci .size_out = 0, 94562306a36Sopenharmony_ci .service = NULL, 94662306a36Sopenharmony_ci }, 94762306a36Sopenharmony_ci /* New PDSM command descriptors to be added below */ 94862306a36Sopenharmony_ci 94962306a36Sopenharmony_ci [PAPR_PDSM_HEALTH] = { 95062306a36Sopenharmony_ci .size_in = 0, 95162306a36Sopenharmony_ci .size_out = sizeof(struct nd_papr_pdsm_health), 95262306a36Sopenharmony_ci .service = papr_pdsm_health, 95362306a36Sopenharmony_ci }, 95462306a36Sopenharmony_ci 95562306a36Sopenharmony_ci [PAPR_PDSM_SMART_INJECT] = { 95662306a36Sopenharmony_ci .size_in = sizeof(struct nd_papr_pdsm_smart_inject), 95762306a36Sopenharmony_ci .size_out = sizeof(struct nd_papr_pdsm_smart_inject), 95862306a36Sopenharmony_ci .service = papr_pdsm_smart_inject, 95962306a36Sopenharmony_ci }, 96062306a36Sopenharmony_ci /* Empty */ 96162306a36Sopenharmony_ci [PAPR_PDSM_MAX] = { 96262306a36Sopenharmony_ci .size_in = 0, 96362306a36Sopenharmony_ci .size_out = 0, 96462306a36Sopenharmony_ci .service = NULL, 96562306a36Sopenharmony_ci }, 96662306a36Sopenharmony_ci}; 96762306a36Sopenharmony_ci 96862306a36Sopenharmony_ci/* Given a valid pdsm cmd return its command descriptor else return NULL */ 96962306a36Sopenharmony_cistatic inline const struct pdsm_cmd_desc *pdsm_cmd_desc(enum papr_pdsm cmd) 97062306a36Sopenharmony_ci{ 97162306a36Sopenharmony_ci if (cmd >= 0 || cmd < ARRAY_SIZE(__pdsm_cmd_descriptors)) 97262306a36Sopenharmony_ci return &__pdsm_cmd_descriptors[cmd]; 97362306a36Sopenharmony_ci 97462306a36Sopenharmony_ci return NULL; 97562306a36Sopenharmony_ci} 97662306a36Sopenharmony_ci 97762306a36Sopenharmony_ci/* 97862306a36Sopenharmony_ci * For a given pdsm request call an appropriate service function. 97962306a36Sopenharmony_ci * Returns errors if any while handling the pdsm command package. 98062306a36Sopenharmony_ci */ 98162306a36Sopenharmony_cistatic int papr_scm_service_pdsm(struct papr_scm_priv *p, 98262306a36Sopenharmony_ci struct nd_cmd_pkg *pkg) 98362306a36Sopenharmony_ci{ 98462306a36Sopenharmony_ci /* Get the PDSM header and PDSM command */ 98562306a36Sopenharmony_ci struct nd_pkg_pdsm *pdsm_pkg = (struct nd_pkg_pdsm *)pkg->nd_payload; 98662306a36Sopenharmony_ci enum papr_pdsm pdsm = (enum papr_pdsm)pkg->nd_command; 98762306a36Sopenharmony_ci const struct pdsm_cmd_desc *pdsc; 98862306a36Sopenharmony_ci int rc; 98962306a36Sopenharmony_ci 99062306a36Sopenharmony_ci /* Fetch corresponding pdsm descriptor for validation and servicing */ 99162306a36Sopenharmony_ci pdsc = pdsm_cmd_desc(pdsm); 99262306a36Sopenharmony_ci 99362306a36Sopenharmony_ci /* Validate pdsm descriptor */ 99462306a36Sopenharmony_ci /* Ensure that reserved fields are 0 */ 99562306a36Sopenharmony_ci if (pdsm_pkg->reserved[0] || pdsm_pkg->reserved[1]) { 99662306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "PDSM[0x%x]: Invalid reserved field\n", 99762306a36Sopenharmony_ci pdsm); 99862306a36Sopenharmony_ci return -EINVAL; 99962306a36Sopenharmony_ci } 100062306a36Sopenharmony_ci 100162306a36Sopenharmony_ci /* If pdsm expects some input, then ensure that the size_in matches */ 100262306a36Sopenharmony_ci if (pdsc->size_in && 100362306a36Sopenharmony_ci pkg->nd_size_in != (pdsc->size_in + ND_PDSM_HDR_SIZE)) { 100462306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "PDSM[0x%x]: Mismatched size_in=%d\n", 100562306a36Sopenharmony_ci pdsm, pkg->nd_size_in); 100662306a36Sopenharmony_ci return -EINVAL; 100762306a36Sopenharmony_ci } 100862306a36Sopenharmony_ci 100962306a36Sopenharmony_ci /* If pdsm wants to return data, then ensure that size_out matches */ 101062306a36Sopenharmony_ci if (pdsc->size_out && 101162306a36Sopenharmony_ci pkg->nd_size_out != (pdsc->size_out + ND_PDSM_HDR_SIZE)) { 101262306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "PDSM[0x%x]: Mismatched size_out=%d\n", 101362306a36Sopenharmony_ci pdsm, pkg->nd_size_out); 101462306a36Sopenharmony_ci return -EINVAL; 101562306a36Sopenharmony_ci } 101662306a36Sopenharmony_ci 101762306a36Sopenharmony_ci /* Service the pdsm */ 101862306a36Sopenharmony_ci if (pdsc->service) { 101962306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "PDSM[0x%x]: Servicing..\n", pdsm); 102062306a36Sopenharmony_ci 102162306a36Sopenharmony_ci rc = pdsc->service(p, &pdsm_pkg->payload); 102262306a36Sopenharmony_ci 102362306a36Sopenharmony_ci if (rc < 0) { 102462306a36Sopenharmony_ci /* error encountered while servicing pdsm */ 102562306a36Sopenharmony_ci pdsm_pkg->cmd_status = rc; 102662306a36Sopenharmony_ci pkg->nd_fw_size = ND_PDSM_HDR_SIZE; 102762306a36Sopenharmony_ci } else { 102862306a36Sopenharmony_ci /* pdsm serviced and 'rc' bytes written to payload */ 102962306a36Sopenharmony_ci pdsm_pkg->cmd_status = 0; 103062306a36Sopenharmony_ci pkg->nd_fw_size = ND_PDSM_HDR_SIZE + rc; 103162306a36Sopenharmony_ci } 103262306a36Sopenharmony_ci } else { 103362306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "PDSM[0x%x]: Unsupported PDSM request\n", 103462306a36Sopenharmony_ci pdsm); 103562306a36Sopenharmony_ci pdsm_pkg->cmd_status = -ENOENT; 103662306a36Sopenharmony_ci pkg->nd_fw_size = ND_PDSM_HDR_SIZE; 103762306a36Sopenharmony_ci } 103862306a36Sopenharmony_ci 103962306a36Sopenharmony_ci return pdsm_pkg->cmd_status; 104062306a36Sopenharmony_ci} 104162306a36Sopenharmony_ci 104262306a36Sopenharmony_cistatic int papr_scm_ndctl(struct nvdimm_bus_descriptor *nd_desc, 104362306a36Sopenharmony_ci struct nvdimm *nvdimm, unsigned int cmd, void *buf, 104462306a36Sopenharmony_ci unsigned int buf_len, int *cmd_rc) 104562306a36Sopenharmony_ci{ 104662306a36Sopenharmony_ci struct nd_cmd_get_config_size *get_size_hdr; 104762306a36Sopenharmony_ci struct nd_cmd_pkg *call_pkg = NULL; 104862306a36Sopenharmony_ci struct papr_scm_priv *p; 104962306a36Sopenharmony_ci int rc; 105062306a36Sopenharmony_ci 105162306a36Sopenharmony_ci rc = is_cmd_valid(nvdimm, cmd, buf, buf_len); 105262306a36Sopenharmony_ci if (rc) { 105362306a36Sopenharmony_ci pr_debug("Invalid cmd=0x%x. Err=%d\n", cmd, rc); 105462306a36Sopenharmony_ci return rc; 105562306a36Sopenharmony_ci } 105662306a36Sopenharmony_ci 105762306a36Sopenharmony_ci /* Use a local variable in case cmd_rc pointer is NULL */ 105862306a36Sopenharmony_ci if (!cmd_rc) 105962306a36Sopenharmony_ci cmd_rc = &rc; 106062306a36Sopenharmony_ci 106162306a36Sopenharmony_ci p = nvdimm_provider_data(nvdimm); 106262306a36Sopenharmony_ci 106362306a36Sopenharmony_ci switch (cmd) { 106462306a36Sopenharmony_ci case ND_CMD_GET_CONFIG_SIZE: 106562306a36Sopenharmony_ci get_size_hdr = buf; 106662306a36Sopenharmony_ci 106762306a36Sopenharmony_ci get_size_hdr->status = 0; 106862306a36Sopenharmony_ci get_size_hdr->max_xfer = 8; 106962306a36Sopenharmony_ci get_size_hdr->config_size = p->metadata_size; 107062306a36Sopenharmony_ci *cmd_rc = 0; 107162306a36Sopenharmony_ci break; 107262306a36Sopenharmony_ci 107362306a36Sopenharmony_ci case ND_CMD_GET_CONFIG_DATA: 107462306a36Sopenharmony_ci *cmd_rc = papr_scm_meta_get(p, buf); 107562306a36Sopenharmony_ci break; 107662306a36Sopenharmony_ci 107762306a36Sopenharmony_ci case ND_CMD_SET_CONFIG_DATA: 107862306a36Sopenharmony_ci *cmd_rc = papr_scm_meta_set(p, buf); 107962306a36Sopenharmony_ci break; 108062306a36Sopenharmony_ci 108162306a36Sopenharmony_ci case ND_CMD_CALL: 108262306a36Sopenharmony_ci call_pkg = (struct nd_cmd_pkg *)buf; 108362306a36Sopenharmony_ci *cmd_rc = papr_scm_service_pdsm(p, call_pkg); 108462306a36Sopenharmony_ci break; 108562306a36Sopenharmony_ci 108662306a36Sopenharmony_ci default: 108762306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "Unknown command = %d\n", cmd); 108862306a36Sopenharmony_ci return -EINVAL; 108962306a36Sopenharmony_ci } 109062306a36Sopenharmony_ci 109162306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "returned with cmd_rc = %d\n", *cmd_rc); 109262306a36Sopenharmony_ci 109362306a36Sopenharmony_ci return 0; 109462306a36Sopenharmony_ci} 109562306a36Sopenharmony_ci 109662306a36Sopenharmony_cistatic ssize_t health_bitmap_inject_show(struct device *dev, 109762306a36Sopenharmony_ci struct device_attribute *attr, 109862306a36Sopenharmony_ci char *buf) 109962306a36Sopenharmony_ci{ 110062306a36Sopenharmony_ci struct nvdimm *dimm = to_nvdimm(dev); 110162306a36Sopenharmony_ci struct papr_scm_priv *p = nvdimm_provider_data(dimm); 110262306a36Sopenharmony_ci 110362306a36Sopenharmony_ci return sprintf(buf, "%#llx\n", 110462306a36Sopenharmony_ci READ_ONCE(p->health_bitmap_inject_mask)); 110562306a36Sopenharmony_ci} 110662306a36Sopenharmony_ci 110762306a36Sopenharmony_cistatic DEVICE_ATTR_ADMIN_RO(health_bitmap_inject); 110862306a36Sopenharmony_ci 110962306a36Sopenharmony_cistatic ssize_t perf_stats_show(struct device *dev, 111062306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 111162306a36Sopenharmony_ci{ 111262306a36Sopenharmony_ci int index; 111362306a36Sopenharmony_ci ssize_t rc; 111462306a36Sopenharmony_ci struct seq_buf s; 111562306a36Sopenharmony_ci struct papr_scm_perf_stat *stat; 111662306a36Sopenharmony_ci struct papr_scm_perf_stats *stats; 111762306a36Sopenharmony_ci struct nvdimm *dimm = to_nvdimm(dev); 111862306a36Sopenharmony_ci struct papr_scm_priv *p = nvdimm_provider_data(dimm); 111962306a36Sopenharmony_ci 112062306a36Sopenharmony_ci if (!p->stat_buffer_len) 112162306a36Sopenharmony_ci return -ENOENT; 112262306a36Sopenharmony_ci 112362306a36Sopenharmony_ci /* Allocate the buffer for phyp where stats are written */ 112462306a36Sopenharmony_ci stats = kzalloc(p->stat_buffer_len, GFP_KERNEL); 112562306a36Sopenharmony_ci if (!stats) 112662306a36Sopenharmony_ci return -ENOMEM; 112762306a36Sopenharmony_ci 112862306a36Sopenharmony_ci /* Ask phyp to return all dimm perf stats */ 112962306a36Sopenharmony_ci rc = drc_pmem_query_stats(p, stats, 0); 113062306a36Sopenharmony_ci if (rc) 113162306a36Sopenharmony_ci goto free_stats; 113262306a36Sopenharmony_ci /* 113362306a36Sopenharmony_ci * Go through the returned output buffer and print stats and 113462306a36Sopenharmony_ci * values. Since stat_id is essentially a char string of 113562306a36Sopenharmony_ci * 8 bytes, simply use the string format specifier to print it. 113662306a36Sopenharmony_ci */ 113762306a36Sopenharmony_ci seq_buf_init(&s, buf, PAGE_SIZE); 113862306a36Sopenharmony_ci for (index = 0, stat = stats->scm_statistic; 113962306a36Sopenharmony_ci index < be32_to_cpu(stats->num_statistics); 114062306a36Sopenharmony_ci ++index, ++stat) { 114162306a36Sopenharmony_ci seq_buf_printf(&s, "%.8s = 0x%016llX\n", 114262306a36Sopenharmony_ci stat->stat_id, 114362306a36Sopenharmony_ci be64_to_cpu(stat->stat_val)); 114462306a36Sopenharmony_ci } 114562306a36Sopenharmony_ci 114662306a36Sopenharmony_cifree_stats: 114762306a36Sopenharmony_ci kfree(stats); 114862306a36Sopenharmony_ci return rc ? rc : (ssize_t)seq_buf_used(&s); 114962306a36Sopenharmony_ci} 115062306a36Sopenharmony_cistatic DEVICE_ATTR_ADMIN_RO(perf_stats); 115162306a36Sopenharmony_ci 115262306a36Sopenharmony_cistatic ssize_t flags_show(struct device *dev, 115362306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 115462306a36Sopenharmony_ci{ 115562306a36Sopenharmony_ci struct nvdimm *dimm = to_nvdimm(dev); 115662306a36Sopenharmony_ci struct papr_scm_priv *p = nvdimm_provider_data(dimm); 115762306a36Sopenharmony_ci struct seq_buf s; 115862306a36Sopenharmony_ci u64 health; 115962306a36Sopenharmony_ci int rc; 116062306a36Sopenharmony_ci 116162306a36Sopenharmony_ci rc = drc_pmem_query_health(p); 116262306a36Sopenharmony_ci if (rc) 116362306a36Sopenharmony_ci return rc; 116462306a36Sopenharmony_ci 116562306a36Sopenharmony_ci /* Copy health_bitmap locally, check masks & update out buffer */ 116662306a36Sopenharmony_ci health = READ_ONCE(p->health_bitmap); 116762306a36Sopenharmony_ci 116862306a36Sopenharmony_ci seq_buf_init(&s, buf, PAGE_SIZE); 116962306a36Sopenharmony_ci if (health & PAPR_PMEM_UNARMED_MASK) 117062306a36Sopenharmony_ci seq_buf_printf(&s, "not_armed "); 117162306a36Sopenharmony_ci 117262306a36Sopenharmony_ci if (health & PAPR_PMEM_BAD_SHUTDOWN_MASK) 117362306a36Sopenharmony_ci seq_buf_printf(&s, "flush_fail "); 117462306a36Sopenharmony_ci 117562306a36Sopenharmony_ci if (health & PAPR_PMEM_BAD_RESTORE_MASK) 117662306a36Sopenharmony_ci seq_buf_printf(&s, "restore_fail "); 117762306a36Sopenharmony_ci 117862306a36Sopenharmony_ci if (health & PAPR_PMEM_ENCRYPTED) 117962306a36Sopenharmony_ci seq_buf_printf(&s, "encrypted "); 118062306a36Sopenharmony_ci 118162306a36Sopenharmony_ci if (health & PAPR_PMEM_SMART_EVENT_MASK) 118262306a36Sopenharmony_ci seq_buf_printf(&s, "smart_notify "); 118362306a36Sopenharmony_ci 118462306a36Sopenharmony_ci if (health & PAPR_PMEM_SCRUBBED_AND_LOCKED) 118562306a36Sopenharmony_ci seq_buf_printf(&s, "scrubbed locked "); 118662306a36Sopenharmony_ci 118762306a36Sopenharmony_ci if (seq_buf_used(&s)) 118862306a36Sopenharmony_ci seq_buf_printf(&s, "\n"); 118962306a36Sopenharmony_ci 119062306a36Sopenharmony_ci return seq_buf_used(&s); 119162306a36Sopenharmony_ci} 119262306a36Sopenharmony_ciDEVICE_ATTR_RO(flags); 119362306a36Sopenharmony_ci 119462306a36Sopenharmony_cistatic ssize_t dirty_shutdown_show(struct device *dev, 119562306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 119662306a36Sopenharmony_ci{ 119762306a36Sopenharmony_ci struct nvdimm *dimm = to_nvdimm(dev); 119862306a36Sopenharmony_ci struct papr_scm_priv *p = nvdimm_provider_data(dimm); 119962306a36Sopenharmony_ci 120062306a36Sopenharmony_ci return sysfs_emit(buf, "%llu\n", p->dirty_shutdown_counter); 120162306a36Sopenharmony_ci} 120262306a36Sopenharmony_ciDEVICE_ATTR_RO(dirty_shutdown); 120362306a36Sopenharmony_ci 120462306a36Sopenharmony_cistatic umode_t papr_nd_attribute_visible(struct kobject *kobj, 120562306a36Sopenharmony_ci struct attribute *attr, int n) 120662306a36Sopenharmony_ci{ 120762306a36Sopenharmony_ci struct device *dev = kobj_to_dev(kobj); 120862306a36Sopenharmony_ci struct nvdimm *nvdimm = to_nvdimm(dev); 120962306a36Sopenharmony_ci struct papr_scm_priv *p = nvdimm_provider_data(nvdimm); 121062306a36Sopenharmony_ci 121162306a36Sopenharmony_ci /* For if perf-stats not available remove perf_stats sysfs */ 121262306a36Sopenharmony_ci if (attr == &dev_attr_perf_stats.attr && p->stat_buffer_len == 0) 121362306a36Sopenharmony_ci return 0; 121462306a36Sopenharmony_ci 121562306a36Sopenharmony_ci return attr->mode; 121662306a36Sopenharmony_ci} 121762306a36Sopenharmony_ci 121862306a36Sopenharmony_ci/* papr_scm specific dimm attributes */ 121962306a36Sopenharmony_cistatic struct attribute *papr_nd_attributes[] = { 122062306a36Sopenharmony_ci &dev_attr_flags.attr, 122162306a36Sopenharmony_ci &dev_attr_perf_stats.attr, 122262306a36Sopenharmony_ci &dev_attr_dirty_shutdown.attr, 122362306a36Sopenharmony_ci &dev_attr_health_bitmap_inject.attr, 122462306a36Sopenharmony_ci NULL, 122562306a36Sopenharmony_ci}; 122662306a36Sopenharmony_ci 122762306a36Sopenharmony_cistatic const struct attribute_group papr_nd_attribute_group = { 122862306a36Sopenharmony_ci .name = "papr", 122962306a36Sopenharmony_ci .is_visible = papr_nd_attribute_visible, 123062306a36Sopenharmony_ci .attrs = papr_nd_attributes, 123162306a36Sopenharmony_ci}; 123262306a36Sopenharmony_ci 123362306a36Sopenharmony_cistatic const struct attribute_group *papr_nd_attr_groups[] = { 123462306a36Sopenharmony_ci &papr_nd_attribute_group, 123562306a36Sopenharmony_ci NULL, 123662306a36Sopenharmony_ci}; 123762306a36Sopenharmony_ci 123862306a36Sopenharmony_cistatic int papr_scm_nvdimm_init(struct papr_scm_priv *p) 123962306a36Sopenharmony_ci{ 124062306a36Sopenharmony_ci struct device *dev = &p->pdev->dev; 124162306a36Sopenharmony_ci struct nd_mapping_desc mapping; 124262306a36Sopenharmony_ci struct nd_region_desc ndr_desc; 124362306a36Sopenharmony_ci unsigned long dimm_flags; 124462306a36Sopenharmony_ci int target_nid, online_nid; 124562306a36Sopenharmony_ci 124662306a36Sopenharmony_ci p->bus_desc.ndctl = papr_scm_ndctl; 124762306a36Sopenharmony_ci p->bus_desc.module = THIS_MODULE; 124862306a36Sopenharmony_ci p->bus_desc.of_node = p->pdev->dev.of_node; 124962306a36Sopenharmony_ci p->bus_desc.provider_name = kstrdup(p->pdev->name, GFP_KERNEL); 125062306a36Sopenharmony_ci 125162306a36Sopenharmony_ci /* Set the dimm command family mask to accept PDSMs */ 125262306a36Sopenharmony_ci set_bit(NVDIMM_FAMILY_PAPR, &p->bus_desc.dimm_family_mask); 125362306a36Sopenharmony_ci 125462306a36Sopenharmony_ci if (!p->bus_desc.provider_name) 125562306a36Sopenharmony_ci return -ENOMEM; 125662306a36Sopenharmony_ci 125762306a36Sopenharmony_ci p->bus = nvdimm_bus_register(NULL, &p->bus_desc); 125862306a36Sopenharmony_ci if (!p->bus) { 125962306a36Sopenharmony_ci dev_err(dev, "Error creating nvdimm bus %pOF\n", p->dn); 126062306a36Sopenharmony_ci kfree(p->bus_desc.provider_name); 126162306a36Sopenharmony_ci return -ENXIO; 126262306a36Sopenharmony_ci } 126362306a36Sopenharmony_ci 126462306a36Sopenharmony_ci dimm_flags = 0; 126562306a36Sopenharmony_ci set_bit(NDD_LABELING, &dimm_flags); 126662306a36Sopenharmony_ci 126762306a36Sopenharmony_ci /* 126862306a36Sopenharmony_ci * Check if the nvdimm is unarmed. No locking needed as we are still 126962306a36Sopenharmony_ci * initializing. Ignore error encountered if any. 127062306a36Sopenharmony_ci */ 127162306a36Sopenharmony_ci __drc_pmem_query_health(p); 127262306a36Sopenharmony_ci 127362306a36Sopenharmony_ci if (p->health_bitmap & PAPR_PMEM_UNARMED_MASK) 127462306a36Sopenharmony_ci set_bit(NDD_UNARMED, &dimm_flags); 127562306a36Sopenharmony_ci 127662306a36Sopenharmony_ci p->nvdimm = nvdimm_create(p->bus, p, papr_nd_attr_groups, 127762306a36Sopenharmony_ci dimm_flags, PAPR_SCM_DIMM_CMD_MASK, 0, NULL); 127862306a36Sopenharmony_ci if (!p->nvdimm) { 127962306a36Sopenharmony_ci dev_err(dev, "Error creating DIMM object for %pOF\n", p->dn); 128062306a36Sopenharmony_ci goto err; 128162306a36Sopenharmony_ci } 128262306a36Sopenharmony_ci 128362306a36Sopenharmony_ci if (nvdimm_bus_check_dimm_count(p->bus, 1)) 128462306a36Sopenharmony_ci goto err; 128562306a36Sopenharmony_ci 128662306a36Sopenharmony_ci /* now add the region */ 128762306a36Sopenharmony_ci 128862306a36Sopenharmony_ci memset(&mapping, 0, sizeof(mapping)); 128962306a36Sopenharmony_ci mapping.nvdimm = p->nvdimm; 129062306a36Sopenharmony_ci mapping.start = 0; 129162306a36Sopenharmony_ci mapping.size = p->blocks * p->block_size; // XXX: potential overflow? 129262306a36Sopenharmony_ci 129362306a36Sopenharmony_ci memset(&ndr_desc, 0, sizeof(ndr_desc)); 129462306a36Sopenharmony_ci target_nid = dev_to_node(&p->pdev->dev); 129562306a36Sopenharmony_ci online_nid = numa_map_to_online_node(target_nid); 129662306a36Sopenharmony_ci ndr_desc.numa_node = online_nid; 129762306a36Sopenharmony_ci ndr_desc.target_node = target_nid; 129862306a36Sopenharmony_ci ndr_desc.res = &p->res; 129962306a36Sopenharmony_ci ndr_desc.of_node = p->dn; 130062306a36Sopenharmony_ci ndr_desc.provider_data = p; 130162306a36Sopenharmony_ci ndr_desc.mapping = &mapping; 130262306a36Sopenharmony_ci ndr_desc.num_mappings = 1; 130362306a36Sopenharmony_ci ndr_desc.nd_set = &p->nd_set; 130462306a36Sopenharmony_ci 130562306a36Sopenharmony_ci if (p->hcall_flush_required) { 130662306a36Sopenharmony_ci set_bit(ND_REGION_ASYNC, &ndr_desc.flags); 130762306a36Sopenharmony_ci ndr_desc.flush = papr_scm_pmem_flush; 130862306a36Sopenharmony_ci } 130962306a36Sopenharmony_ci 131062306a36Sopenharmony_ci if (p->is_volatile) 131162306a36Sopenharmony_ci p->region = nvdimm_volatile_region_create(p->bus, &ndr_desc); 131262306a36Sopenharmony_ci else { 131362306a36Sopenharmony_ci set_bit(ND_REGION_PERSIST_MEMCTRL, &ndr_desc.flags); 131462306a36Sopenharmony_ci p->region = nvdimm_pmem_region_create(p->bus, &ndr_desc); 131562306a36Sopenharmony_ci } 131662306a36Sopenharmony_ci if (!p->region) { 131762306a36Sopenharmony_ci dev_err(dev, "Error registering region %pR from %pOF\n", 131862306a36Sopenharmony_ci ndr_desc.res, p->dn); 131962306a36Sopenharmony_ci goto err; 132062306a36Sopenharmony_ci } 132162306a36Sopenharmony_ci if (target_nid != online_nid) 132262306a36Sopenharmony_ci dev_info(dev, "Region registered with target node %d and online node %d", 132362306a36Sopenharmony_ci target_nid, online_nid); 132462306a36Sopenharmony_ci 132562306a36Sopenharmony_ci mutex_lock(&papr_ndr_lock); 132662306a36Sopenharmony_ci list_add_tail(&p->region_list, &papr_nd_regions); 132762306a36Sopenharmony_ci mutex_unlock(&papr_ndr_lock); 132862306a36Sopenharmony_ci 132962306a36Sopenharmony_ci return 0; 133062306a36Sopenharmony_ci 133162306a36Sopenharmony_cierr: nvdimm_bus_unregister(p->bus); 133262306a36Sopenharmony_ci kfree(p->bus_desc.provider_name); 133362306a36Sopenharmony_ci return -ENXIO; 133462306a36Sopenharmony_ci} 133562306a36Sopenharmony_ci 133662306a36Sopenharmony_cistatic void papr_scm_add_badblock(struct nd_region *region, 133762306a36Sopenharmony_ci struct nvdimm_bus *bus, u64 phys_addr) 133862306a36Sopenharmony_ci{ 133962306a36Sopenharmony_ci u64 aligned_addr = ALIGN_DOWN(phys_addr, L1_CACHE_BYTES); 134062306a36Sopenharmony_ci 134162306a36Sopenharmony_ci if (nvdimm_bus_add_badrange(bus, aligned_addr, L1_CACHE_BYTES)) { 134262306a36Sopenharmony_ci pr_err("Bad block registration for 0x%llx failed\n", phys_addr); 134362306a36Sopenharmony_ci return; 134462306a36Sopenharmony_ci } 134562306a36Sopenharmony_ci 134662306a36Sopenharmony_ci pr_debug("Add memory range (0x%llx - 0x%llx) as bad range\n", 134762306a36Sopenharmony_ci aligned_addr, aligned_addr + L1_CACHE_BYTES); 134862306a36Sopenharmony_ci 134962306a36Sopenharmony_ci nvdimm_region_notify(region, NVDIMM_REVALIDATE_POISON); 135062306a36Sopenharmony_ci} 135162306a36Sopenharmony_ci 135262306a36Sopenharmony_cistatic int handle_mce_ue(struct notifier_block *nb, unsigned long val, 135362306a36Sopenharmony_ci void *data) 135462306a36Sopenharmony_ci{ 135562306a36Sopenharmony_ci struct machine_check_event *evt = data; 135662306a36Sopenharmony_ci struct papr_scm_priv *p; 135762306a36Sopenharmony_ci u64 phys_addr; 135862306a36Sopenharmony_ci bool found = false; 135962306a36Sopenharmony_ci 136062306a36Sopenharmony_ci if (evt->error_type != MCE_ERROR_TYPE_UE) 136162306a36Sopenharmony_ci return NOTIFY_DONE; 136262306a36Sopenharmony_ci 136362306a36Sopenharmony_ci if (list_empty(&papr_nd_regions)) 136462306a36Sopenharmony_ci return NOTIFY_DONE; 136562306a36Sopenharmony_ci 136662306a36Sopenharmony_ci /* 136762306a36Sopenharmony_ci * The physical address obtained here is PAGE_SIZE aligned, so get the 136862306a36Sopenharmony_ci * exact address from the effective address 136962306a36Sopenharmony_ci */ 137062306a36Sopenharmony_ci phys_addr = evt->u.ue_error.physical_address + 137162306a36Sopenharmony_ci (evt->u.ue_error.effective_address & ~PAGE_MASK); 137262306a36Sopenharmony_ci 137362306a36Sopenharmony_ci if (!evt->u.ue_error.physical_address_provided || 137462306a36Sopenharmony_ci !is_zone_device_page(pfn_to_page(phys_addr >> PAGE_SHIFT))) 137562306a36Sopenharmony_ci return NOTIFY_DONE; 137662306a36Sopenharmony_ci 137762306a36Sopenharmony_ci /* mce notifier is called from a process context, so mutex is safe */ 137862306a36Sopenharmony_ci mutex_lock(&papr_ndr_lock); 137962306a36Sopenharmony_ci list_for_each_entry(p, &papr_nd_regions, region_list) { 138062306a36Sopenharmony_ci if (phys_addr >= p->res.start && phys_addr <= p->res.end) { 138162306a36Sopenharmony_ci found = true; 138262306a36Sopenharmony_ci break; 138362306a36Sopenharmony_ci } 138462306a36Sopenharmony_ci } 138562306a36Sopenharmony_ci 138662306a36Sopenharmony_ci if (found) 138762306a36Sopenharmony_ci papr_scm_add_badblock(p->region, p->bus, phys_addr); 138862306a36Sopenharmony_ci 138962306a36Sopenharmony_ci mutex_unlock(&papr_ndr_lock); 139062306a36Sopenharmony_ci 139162306a36Sopenharmony_ci return found ? NOTIFY_OK : NOTIFY_DONE; 139262306a36Sopenharmony_ci} 139362306a36Sopenharmony_ci 139462306a36Sopenharmony_cistatic struct notifier_block mce_ue_nb = { 139562306a36Sopenharmony_ci .notifier_call = handle_mce_ue 139662306a36Sopenharmony_ci}; 139762306a36Sopenharmony_ci 139862306a36Sopenharmony_cistatic int papr_scm_probe(struct platform_device *pdev) 139962306a36Sopenharmony_ci{ 140062306a36Sopenharmony_ci struct device_node *dn = pdev->dev.of_node; 140162306a36Sopenharmony_ci u32 drc_index, metadata_size; 140262306a36Sopenharmony_ci u64 blocks, block_size; 140362306a36Sopenharmony_ci struct papr_scm_priv *p; 140462306a36Sopenharmony_ci u8 uuid_raw[UUID_SIZE]; 140562306a36Sopenharmony_ci const char *uuid_str; 140662306a36Sopenharmony_ci ssize_t stat_size; 140762306a36Sopenharmony_ci uuid_t uuid; 140862306a36Sopenharmony_ci int rc; 140962306a36Sopenharmony_ci 141062306a36Sopenharmony_ci /* check we have all the required DT properties */ 141162306a36Sopenharmony_ci if (of_property_read_u32(dn, "ibm,my-drc-index", &drc_index)) { 141262306a36Sopenharmony_ci dev_err(&pdev->dev, "%pOF: missing drc-index!\n", dn); 141362306a36Sopenharmony_ci return -ENODEV; 141462306a36Sopenharmony_ci } 141562306a36Sopenharmony_ci 141662306a36Sopenharmony_ci if (of_property_read_u64(dn, "ibm,block-size", &block_size)) { 141762306a36Sopenharmony_ci dev_err(&pdev->dev, "%pOF: missing block-size!\n", dn); 141862306a36Sopenharmony_ci return -ENODEV; 141962306a36Sopenharmony_ci } 142062306a36Sopenharmony_ci 142162306a36Sopenharmony_ci if (of_property_read_u64(dn, "ibm,number-of-blocks", &blocks)) { 142262306a36Sopenharmony_ci dev_err(&pdev->dev, "%pOF: missing number-of-blocks!\n", dn); 142362306a36Sopenharmony_ci return -ENODEV; 142462306a36Sopenharmony_ci } 142562306a36Sopenharmony_ci 142662306a36Sopenharmony_ci if (of_property_read_string(dn, "ibm,unit-guid", &uuid_str)) { 142762306a36Sopenharmony_ci dev_err(&pdev->dev, "%pOF: missing unit-guid!\n", dn); 142862306a36Sopenharmony_ci return -ENODEV; 142962306a36Sopenharmony_ci } 143062306a36Sopenharmony_ci 143162306a36Sopenharmony_ci /* 143262306a36Sopenharmony_ci * open firmware platform device create won't update the NUMA 143362306a36Sopenharmony_ci * distance table. For PAPR SCM devices we use numa_map_to_online_node() 143462306a36Sopenharmony_ci * to find the nearest online NUMA node and that requires correct 143562306a36Sopenharmony_ci * distance table information. 143662306a36Sopenharmony_ci */ 143762306a36Sopenharmony_ci update_numa_distance(dn); 143862306a36Sopenharmony_ci 143962306a36Sopenharmony_ci p = kzalloc(sizeof(*p), GFP_KERNEL); 144062306a36Sopenharmony_ci if (!p) 144162306a36Sopenharmony_ci return -ENOMEM; 144262306a36Sopenharmony_ci 144362306a36Sopenharmony_ci /* Initialize the dimm mutex */ 144462306a36Sopenharmony_ci mutex_init(&p->health_mutex); 144562306a36Sopenharmony_ci 144662306a36Sopenharmony_ci /* optional DT properties */ 144762306a36Sopenharmony_ci of_property_read_u32(dn, "ibm,metadata-size", &metadata_size); 144862306a36Sopenharmony_ci 144962306a36Sopenharmony_ci p->dn = dn; 145062306a36Sopenharmony_ci p->drc_index = drc_index; 145162306a36Sopenharmony_ci p->block_size = block_size; 145262306a36Sopenharmony_ci p->blocks = blocks; 145362306a36Sopenharmony_ci p->is_volatile = !of_property_read_bool(dn, "ibm,cache-flush-required"); 145462306a36Sopenharmony_ci p->hcall_flush_required = of_property_read_bool(dn, "ibm,hcall-flush-required"); 145562306a36Sopenharmony_ci 145662306a36Sopenharmony_ci if (of_property_read_u64(dn, "ibm,persistence-failed-count", 145762306a36Sopenharmony_ci &p->dirty_shutdown_counter)) 145862306a36Sopenharmony_ci p->dirty_shutdown_counter = 0; 145962306a36Sopenharmony_ci 146062306a36Sopenharmony_ci /* We just need to ensure that set cookies are unique across */ 146162306a36Sopenharmony_ci uuid_parse(uuid_str, &uuid); 146262306a36Sopenharmony_ci 146362306a36Sopenharmony_ci /* 146462306a36Sopenharmony_ci * The cookie1 and cookie2 are not really little endian. 146562306a36Sopenharmony_ci * We store a raw buffer representation of the 146662306a36Sopenharmony_ci * uuid string so that we can compare this with the label 146762306a36Sopenharmony_ci * area cookie irrespective of the endian configuration 146862306a36Sopenharmony_ci * with which the kernel is built. 146962306a36Sopenharmony_ci * 147062306a36Sopenharmony_ci * Historically we stored the cookie in the below format. 147162306a36Sopenharmony_ci * for a uuid string 72511b67-0b3b-42fd-8d1d-5be3cae8bcaa 147262306a36Sopenharmony_ci * cookie1 was 0xfd423b0b671b5172 147362306a36Sopenharmony_ci * cookie2 was 0xaabce8cae35b1d8d 147462306a36Sopenharmony_ci */ 147562306a36Sopenharmony_ci export_uuid(uuid_raw, &uuid); 147662306a36Sopenharmony_ci p->nd_set.cookie1 = get_unaligned_le64(&uuid_raw[0]); 147762306a36Sopenharmony_ci p->nd_set.cookie2 = get_unaligned_le64(&uuid_raw[8]); 147862306a36Sopenharmony_ci 147962306a36Sopenharmony_ci /* might be zero */ 148062306a36Sopenharmony_ci p->metadata_size = metadata_size; 148162306a36Sopenharmony_ci p->pdev = pdev; 148262306a36Sopenharmony_ci 148362306a36Sopenharmony_ci /* request the hypervisor to bind this region to somewhere in memory */ 148462306a36Sopenharmony_ci rc = drc_pmem_bind(p); 148562306a36Sopenharmony_ci 148662306a36Sopenharmony_ci /* If phyp says drc memory still bound then force unbound and retry */ 148762306a36Sopenharmony_ci if (rc == H_OVERLAP) 148862306a36Sopenharmony_ci rc = drc_pmem_query_n_bind(p); 148962306a36Sopenharmony_ci 149062306a36Sopenharmony_ci if (rc != H_SUCCESS) { 149162306a36Sopenharmony_ci dev_err(&p->pdev->dev, "bind err: %d\n", rc); 149262306a36Sopenharmony_ci rc = -ENXIO; 149362306a36Sopenharmony_ci goto err; 149462306a36Sopenharmony_ci } 149562306a36Sopenharmony_ci 149662306a36Sopenharmony_ci /* setup the resource for the newly bound range */ 149762306a36Sopenharmony_ci p->res.start = p->bound_addr; 149862306a36Sopenharmony_ci p->res.end = p->bound_addr + p->blocks * p->block_size - 1; 149962306a36Sopenharmony_ci p->res.name = pdev->name; 150062306a36Sopenharmony_ci p->res.flags = IORESOURCE_MEM; 150162306a36Sopenharmony_ci 150262306a36Sopenharmony_ci /* Try retrieving the stat buffer and see if its supported */ 150362306a36Sopenharmony_ci stat_size = drc_pmem_query_stats(p, NULL, 0); 150462306a36Sopenharmony_ci if (stat_size > 0) { 150562306a36Sopenharmony_ci p->stat_buffer_len = stat_size; 150662306a36Sopenharmony_ci dev_dbg(&p->pdev->dev, "Max perf-stat size %lu-bytes\n", 150762306a36Sopenharmony_ci p->stat_buffer_len); 150862306a36Sopenharmony_ci } 150962306a36Sopenharmony_ci 151062306a36Sopenharmony_ci rc = papr_scm_nvdimm_init(p); 151162306a36Sopenharmony_ci if (rc) 151262306a36Sopenharmony_ci goto err2; 151362306a36Sopenharmony_ci 151462306a36Sopenharmony_ci platform_set_drvdata(pdev, p); 151562306a36Sopenharmony_ci papr_scm_pmu_register(p); 151662306a36Sopenharmony_ci 151762306a36Sopenharmony_ci return 0; 151862306a36Sopenharmony_ci 151962306a36Sopenharmony_cierr2: drc_pmem_unbind(p); 152062306a36Sopenharmony_cierr: kfree(p); 152162306a36Sopenharmony_ci return rc; 152262306a36Sopenharmony_ci} 152362306a36Sopenharmony_ci 152462306a36Sopenharmony_cistatic int papr_scm_remove(struct platform_device *pdev) 152562306a36Sopenharmony_ci{ 152662306a36Sopenharmony_ci struct papr_scm_priv *p = platform_get_drvdata(pdev); 152762306a36Sopenharmony_ci 152862306a36Sopenharmony_ci mutex_lock(&papr_ndr_lock); 152962306a36Sopenharmony_ci list_del(&p->region_list); 153062306a36Sopenharmony_ci mutex_unlock(&papr_ndr_lock); 153162306a36Sopenharmony_ci 153262306a36Sopenharmony_ci nvdimm_bus_unregister(p->bus); 153362306a36Sopenharmony_ci drc_pmem_unbind(p); 153462306a36Sopenharmony_ci 153562306a36Sopenharmony_ci if (pdev->archdata.priv) 153662306a36Sopenharmony_ci unregister_nvdimm_pmu(pdev->archdata.priv); 153762306a36Sopenharmony_ci 153862306a36Sopenharmony_ci pdev->archdata.priv = NULL; 153962306a36Sopenharmony_ci kfree(p->bus_desc.provider_name); 154062306a36Sopenharmony_ci kfree(p); 154162306a36Sopenharmony_ci 154262306a36Sopenharmony_ci return 0; 154362306a36Sopenharmony_ci} 154462306a36Sopenharmony_ci 154562306a36Sopenharmony_cistatic const struct of_device_id papr_scm_match[] = { 154662306a36Sopenharmony_ci { .compatible = "ibm,pmemory" }, 154762306a36Sopenharmony_ci { .compatible = "ibm,pmemory-v2" }, 154862306a36Sopenharmony_ci { }, 154962306a36Sopenharmony_ci}; 155062306a36Sopenharmony_ci 155162306a36Sopenharmony_cistatic struct platform_driver papr_scm_driver = { 155262306a36Sopenharmony_ci .probe = papr_scm_probe, 155362306a36Sopenharmony_ci .remove = papr_scm_remove, 155462306a36Sopenharmony_ci .driver = { 155562306a36Sopenharmony_ci .name = "papr_scm", 155662306a36Sopenharmony_ci .of_match_table = papr_scm_match, 155762306a36Sopenharmony_ci }, 155862306a36Sopenharmony_ci}; 155962306a36Sopenharmony_ci 156062306a36Sopenharmony_cistatic int __init papr_scm_init(void) 156162306a36Sopenharmony_ci{ 156262306a36Sopenharmony_ci int ret; 156362306a36Sopenharmony_ci 156462306a36Sopenharmony_ci ret = platform_driver_register(&papr_scm_driver); 156562306a36Sopenharmony_ci if (!ret) 156662306a36Sopenharmony_ci mce_register_notifier(&mce_ue_nb); 156762306a36Sopenharmony_ci 156862306a36Sopenharmony_ci return ret; 156962306a36Sopenharmony_ci} 157062306a36Sopenharmony_cimodule_init(papr_scm_init); 157162306a36Sopenharmony_ci 157262306a36Sopenharmony_cistatic void __exit papr_scm_exit(void) 157362306a36Sopenharmony_ci{ 157462306a36Sopenharmony_ci mce_unregister_notifier(&mce_ue_nb); 157562306a36Sopenharmony_ci platform_driver_unregister(&papr_scm_driver); 157662306a36Sopenharmony_ci} 157762306a36Sopenharmony_cimodule_exit(papr_scm_exit); 157862306a36Sopenharmony_ci 157962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, papr_scm_match); 158062306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 158162306a36Sopenharmony_ciMODULE_AUTHOR("IBM Corporation"); 1582