2018-10-15 10:18:28 +11:00
// SPDX-License-Identifier: GPL-2.0
# define pr_fmt(fmt) "papr-scm: " fmt
# include <linux/of.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/ioport.h>
# include <linux/slab.h>
# include <linux/ndctl.h>
# include <linux/sched.h>
# include <linux/libnvdimm.h>
# include <linux/platform_device.h>
# include <asm/plpar_wrappers.h>
# define BIND_ANY_ADDR (~0ul)
# define PAPR_SCM_DIMM_CMD_MASK \
( ( 1ul < < ND_CMD_GET_CONFIG_SIZE ) | \
( 1ul < < ND_CMD_GET_CONFIG_DATA ) | \
( 1ul < < ND_CMD_SET_CONFIG_DATA ) )
struct papr_scm_priv {
struct platform_device * pdev ;
struct device_node * dn ;
uint32_t drc_index ;
uint64_t blocks ;
uint64_t block_size ;
int metadata_size ;
uint64_t bound_addr ;
struct nvdimm_bus_descriptor bus_desc ;
struct nvdimm_bus * bus ;
struct nvdimm * nvdimm ;
struct resource res ;
struct nd_region * region ;
struct nd_interleave_set nd_set ;
} ;
static int drc_pmem_bind ( struct papr_scm_priv * p )
{
unsigned long ret [ PLPAR_HCALL_BUFSIZE ] ;
uint64_t rc , token ;
/*
* When the hypervisor cannot map all the requested memory in a single
* hcall it returns H_BUSY and we call again with the token until
* we get H_SUCCESS . Aborting the retry loop before getting H_SUCCESS
* leave the system in an undefined state , so we wait .
*/
token = 0 ;
do {
rc = plpar_hcall ( H_SCM_BIND_MEM , ret , p - > drc_index , 0 ,
p - > blocks , BIND_ANY_ADDR , token ) ;
2018-12-07 02:17:11 +11:00
token = ret [ 0 ] ;
2018-10-15 10:18:28 +11:00
cond_resched ( ) ;
} while ( rc = = H_BUSY ) ;
if ( rc ) {
dev_err ( & p - > pdev - > dev , " bind err: %lld \n " , rc ) ;
return - ENXIO ;
}
2018-12-07 02:17:11 +11:00
p - > bound_addr = ret [ 1 ] ;
2018-10-15 10:18:28 +11:00
dev_dbg ( & p - > pdev - > dev , " bound drc %x to %pR \n " , p - > drc_index , & p - > res ) ;
return 0 ;
}
static int drc_pmem_unbind ( struct papr_scm_priv * p )
{
unsigned long ret [ PLPAR_HCALL_BUFSIZE ] ;
uint64_t rc , token ;
token = 0 ;
/* NB: unbind has the same retry requirements mentioned above */
do {
rc = plpar_hcall ( H_SCM_UNBIND_MEM , ret , p - > drc_index ,
p - > bound_addr , p - > blocks , token ) ;
2018-12-07 02:17:11 +11:00
token = ret [ 0 ] ;
2018-10-15 10:18:28 +11:00
cond_resched ( ) ;
} while ( rc = = H_BUSY ) ;
if ( rc )
dev_err ( & p - > pdev - > dev , " unbind error: %lld \n " , rc ) ;
return ! ! rc ;
}
static int papr_scm_meta_get ( struct papr_scm_priv * p ,
struct nd_cmd_get_config_data_hdr * hdr )
{
unsigned long data [ PLPAR_HCALL_BUFSIZE ] ;
int64_t ret ;
if ( hdr - > in_offset > = p - > metadata_size | | hdr - > in_length ! = 1 )
return - EINVAL ;
ret = plpar_hcall ( H_SCM_READ_METADATA , data , p - > drc_index ,
hdr - > in_offset , 1 ) ;
if ( ret = = H_PARAMETER ) /* bad DRC index */
return - ENODEV ;
if ( ret )
return - EINVAL ; /* other invalid parameter */
hdr - > out_buf [ 0 ] = data [ 0 ] & 0xff ;
return 0 ;
}
static int papr_scm_meta_set ( struct papr_scm_priv * p ,
struct nd_cmd_set_config_hdr * hdr )
{
int64_t ret ;
if ( hdr - > in_offset > = p - > metadata_size | | hdr - > in_length ! = 1 )
return - EINVAL ;
ret = plpar_hcall_norets ( H_SCM_WRITE_METADATA ,
p - > drc_index , hdr - > in_offset , hdr - > in_buf [ 0 ] , 1 ) ;
if ( ret = = H_PARAMETER ) /* bad DRC index */
return - ENODEV ;
if ( ret )
return - EINVAL ; /* other invalid parameter */
return 0 ;
}
int papr_scm_ndctl ( struct nvdimm_bus_descriptor * nd_desc , struct nvdimm * nvdimm ,
unsigned int cmd , void * buf , unsigned int buf_len , int * cmd_rc )
{
struct nd_cmd_get_config_size * get_size_hdr ;
struct papr_scm_priv * p ;
/* Only dimm-specific calls are supported atm */
if ( ! nvdimm )
return - EINVAL ;
p = nvdimm_provider_data ( nvdimm ) ;
switch ( cmd ) {
case ND_CMD_GET_CONFIG_SIZE :
get_size_hdr = buf ;
get_size_hdr - > status = 0 ;
get_size_hdr - > max_xfer = 1 ;
get_size_hdr - > config_size = p - > metadata_size ;
* cmd_rc = 0 ;
break ;
case ND_CMD_GET_CONFIG_DATA :
* cmd_rc = papr_scm_meta_get ( p , buf ) ;
break ;
case ND_CMD_SET_CONFIG_DATA :
* cmd_rc = papr_scm_meta_set ( p , buf ) ;
break ;
default :
return - EINVAL ;
}
dev_dbg ( & p - > pdev - > dev , " returned with cmd_rc = %d \n " , * cmd_rc ) ;
return 0 ;
}
static const struct attribute_group * region_attr_groups [ ] = {
& nd_region_attribute_group ,
& nd_device_attribute_group ,
& nd_mapping_attribute_group ,
& nd_numa_attribute_group ,
NULL ,
} ;
static const struct attribute_group * bus_attr_groups [ ] = {
& nvdimm_bus_attribute_group ,
NULL ,
} ;
static const struct attribute_group * papr_scm_dimm_groups [ ] = {
& nvdimm_attribute_group ,
& nd_device_attribute_group ,
NULL ,
} ;
static int papr_scm_nvdimm_init ( struct papr_scm_priv * p )
{
struct device * dev = & p - > pdev - > dev ;
struct nd_mapping_desc mapping ;
struct nd_region_desc ndr_desc ;
unsigned long dimm_flags ;
p - > bus_desc . ndctl = papr_scm_ndctl ;
p - > bus_desc . module = THIS_MODULE ;
p - > bus_desc . of_node = p - > pdev - > dev . of_node ;
p - > bus_desc . attr_groups = bus_attr_groups ;
p - > bus_desc . provider_name = kstrdup ( p - > pdev - > name , GFP_KERNEL ) ;
if ( ! p - > bus_desc . provider_name )
return - ENOMEM ;
p - > bus = nvdimm_bus_register ( NULL , & p - > bus_desc ) ;
if ( ! p - > bus ) {
dev_err ( dev , " Error creating nvdimm bus %pOF \n " , p - > dn ) ;
return - ENXIO ;
}
dimm_flags = 0 ;
set_bit ( NDD_ALIASING , & dimm_flags ) ;
p - > nvdimm = nvdimm_create ( p - > bus , p , papr_scm_dimm_groups ,
dimm_flags , PAPR_SCM_DIMM_CMD_MASK , 0 , NULL ) ;
if ( ! p - > nvdimm ) {
dev_err ( dev , " Error creating DIMM object for %pOF \n " , p - > dn ) ;
goto err ;
}
2018-12-07 02:17:12 +11:00
if ( nvdimm_bus_check_dimm_count ( p - > bus , 1 ) )
goto err ;
2018-10-15 10:18:28 +11:00
/* now add the region */
memset ( & mapping , 0 , sizeof ( mapping ) ) ;
mapping . nvdimm = p - > nvdimm ;
mapping . start = 0 ;
mapping . size = p - > blocks * p - > block_size ; // XXX: potential overflow?
memset ( & ndr_desc , 0 , sizeof ( ndr_desc ) ) ;
ndr_desc . attr_groups = region_attr_groups ;
ndr_desc . numa_node = dev_to_node ( & p - > pdev - > dev ) ;
ndr_desc . res = & p - > res ;
ndr_desc . of_node = p - > dn ;
ndr_desc . provider_data = p ;
ndr_desc . mapping = & mapping ;
ndr_desc . num_mappings = 1 ;
ndr_desc . nd_set = & p - > nd_set ;
set_bit ( ND_REGION_PAGEMAP , & ndr_desc . flags ) ;
p - > region = nvdimm_pmem_region_create ( p - > bus , & ndr_desc ) ;
if ( ! p - > region ) {
dev_err ( dev , " Error registering region %pR from %pOF \n " ,
ndr_desc . res , p - > dn ) ;
goto err ;
}
return 0 ;
err : nvdimm_bus_unregister ( p - > bus ) ;
kfree ( p - > bus_desc . provider_name ) ;
return - ENXIO ;
}
static int papr_scm_probe ( struct platform_device * pdev )
{
struct device_node * dn = pdev - > dev . of_node ;
2018-12-07 02:17:10 +11:00
u32 drc_index , metadata_size ;
u64 blocks , block_size ;
2018-10-15 10:18:28 +11:00
struct papr_scm_priv * p ;
int rc ;
/* check we have all the required DT properties */
if ( of_property_read_u32 ( dn , " ibm,my-drc-index " , & drc_index ) ) {
dev_err ( & pdev - > dev , " %pOF: missing drc-index! \n " , dn ) ;
return - ENODEV ;
}
2018-12-07 02:17:10 +11:00
if ( of_property_read_u64 ( dn , " ibm,block-size " , & block_size ) ) {
dev_err ( & pdev - > dev , " %pOF: missing block-size! \n " , dn ) ;
return - ENODEV ;
}
if ( of_property_read_u64 ( dn , " ibm,number-of-blocks " , & blocks ) ) {
dev_err ( & pdev - > dev , " %pOF: missing number-of-blocks! \n " , dn ) ;
2018-10-15 10:18:28 +11:00
return - ENODEV ;
}
p = kzalloc ( sizeof ( * p ) , GFP_KERNEL ) ;
if ( ! p )
return - ENOMEM ;
/* optional DT properties */
of_property_read_u32 ( dn , " ibm,metadata-size " , & metadata_size ) ;
p - > dn = dn ;
p - > drc_index = drc_index ;
2018-12-07 02:17:10 +11:00
p - > block_size = block_size ;
p - > blocks = blocks ;
2018-10-15 10:18:28 +11:00
/* might be zero */
p - > metadata_size = metadata_size ;
p - > pdev = pdev ;
/* request the hypervisor to bind this region to somewhere in memory */
rc = drc_pmem_bind ( p ) ;
if ( rc )
goto err ;
/* setup the resource for the newly bound range */
p - > res . start = p - > bound_addr ;
2018-12-07 02:17:09 +11:00
p - > res . end = p - > bound_addr + p - > blocks * p - > block_size - 1 ;
2018-10-15 10:18:28 +11:00
p - > res . name = pdev - > name ;
p - > res . flags = IORESOURCE_MEM ;
rc = papr_scm_nvdimm_init ( p ) ;
if ( rc )
goto err2 ;
platform_set_drvdata ( pdev , p ) ;
return 0 ;
err2 : drc_pmem_unbind ( p ) ;
err : kfree ( p ) ;
return rc ;
}
static int papr_scm_remove ( struct platform_device * pdev )
{
struct papr_scm_priv * p = platform_get_drvdata ( pdev ) ;
nvdimm_bus_unregister ( p - > bus ) ;
drc_pmem_unbind ( p ) ;
kfree ( p ) ;
return 0 ;
}
static const struct of_device_id papr_scm_match [ ] = {
{ . compatible = " ibm,pmemory " } ,
{ } ,
} ;
static struct platform_driver papr_scm_driver = {
. probe = papr_scm_probe ,
. remove = papr_scm_remove ,
. driver = {
. name = " papr_scm " ,
. owner = THIS_MODULE ,
. of_match_table = papr_scm_match ,
} ,
} ;
module_platform_driver ( papr_scm_driver ) ;
MODULE_DEVICE_TABLE ( of , papr_scm_match ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " IBM Corporation " ) ;