2018-10-15 10:18:27 +11:00
// SPDX-License-Identifier: GPL-2.0
/*
* Handles hot and cold plug of persistent memory regions on pseries .
*/
# define pr_fmt(fmt) "pseries-pmem: " fmt
# include <linux/kernel.h>
# include <linux/interrupt.h>
# include <linux/delay.h>
# include <linux/sched.h> /* for idle_task_exit */
# include <linux/sched/hotplug.h>
# include <linux/cpu.h>
# include <linux/of.h>
# include <linux/of_platform.h>
# include <linux/slab.h>
# include <asm/prom.h>
# include <asm/rtas.h>
# include <asm/firmware.h>
# include <asm/machdep.h>
# include <asm/vdso_datapage.h>
# include <asm/plpar_wrappers.h>
# include <asm/topology.h>
# include "pseries.h"
static struct device_node * pmem_node ;
static ssize_t pmem_drc_add_node ( u32 drc_index )
{
struct device_node * dn ;
int rc ;
pr_debug ( " Attempting to add pmem node, drc index: %x \n " , drc_index ) ;
rc = dlpar_acquire_drc ( drc_index ) ;
if ( rc ) {
pr_err ( " Failed to acquire DRC, rc: %d, drc index: %x \n " ,
rc , drc_index ) ;
return - EINVAL ;
}
dn = dlpar_configure_connector ( cpu_to_be32 ( drc_index ) , pmem_node ) ;
if ( ! dn ) {
pr_err ( " configure-connector failed for drc %x \n " , drc_index ) ;
dlpar_release_drc ( drc_index ) ;
return - EINVAL ;
}
/* NB: The of reconfig notifier creates platform device from the node */
rc = dlpar_attach_node ( dn , pmem_node ) ;
if ( rc ) {
2018-12-05 13:50:17 -06:00
pr_err ( " Failed to attach node %pOF, rc: %d, drc index: %x \n " ,
dn , rc , drc_index ) ;
2018-10-15 10:18:27 +11:00
if ( dlpar_release_drc ( drc_index ) )
dlpar_free_cc_nodes ( dn ) ;
return rc ;
}
pr_info ( " Successfully added %pOF, drc index: %x \n " , dn , drc_index ) ;
return 0 ;
}
static ssize_t pmem_drc_remove_node ( u32 drc_index )
{
struct device_node * dn ;
uint32_t index ;
int rc ;
for_each_child_of_node ( pmem_node , dn ) {
if ( of_property_read_u32 ( dn , " ibm,my-drc-index " , & index ) )
continue ;
if ( index = = drc_index )
break ;
}
if ( ! dn ) {
pr_err ( " Attempting to remove unused DRC index %x \n " , drc_index ) ;
return - ENODEV ;
}
pr_debug ( " Attempting to remove %pOF, drc index: %x \n " , dn , drc_index ) ;
/* * NB: tears down the ibm,pmemory device as a side-effect */
rc = dlpar_detach_node ( dn ) ;
if ( rc )
return rc ;
rc = dlpar_release_drc ( drc_index ) ;
if ( rc ) {
2018-12-05 13:50:17 -06:00
pr_err ( " Failed to release drc (%x) for CPU %pOFn, rc: %d \n " ,
drc_index , dn , rc ) ;
2018-10-15 10:18:27 +11:00
dlpar_attach_node ( dn , pmem_node ) ;
return rc ;
}
pr_info ( " Successfully removed PMEM with drc index: %x \n " , drc_index ) ;
return 0 ;
}
int dlpar_hp_pmem ( struct pseries_hp_errorlog * hp_elog )
{
2019-04-06 21:54:47 -04:00
u32 drc_index ;
2018-10-15 10:18:27 +11:00
int rc ;
/* slim chance, but we might get a hotplug event while booting */
if ( ! pmem_node )
pmem_node = of_find_node_by_type ( NULL , " ibm,persistent-memory " ) ;
if ( ! pmem_node ) {
pr_err ( " Hotplug event for a pmem device, but none exists \n " ) ;
return - ENODEV ;
}
if ( hp_elog - > id_type ! = PSERIES_HP_ELOG_ID_DRC_INDEX ) {
pr_err ( " Unsupported hotplug event type %d \n " ,
hp_elog - > id_type ) ;
return - EINVAL ;
}
drc_index = hp_elog - > _drc_u . drc_index ;
lock_device_hotplug ( ) ;
if ( hp_elog - > action = = PSERIES_HP_ELOG_ACTION_ADD ) {
rc = pmem_drc_add_node ( drc_index ) ;
} else if ( hp_elog - > action = = PSERIES_HP_ELOG_ACTION_REMOVE ) {
rc = pmem_drc_remove_node ( drc_index ) ;
} else {
pr_err ( " Unsupported hotplug action (%d) \n " , hp_elog - > action ) ;
rc = - EINVAL ;
}
unlock_device_hotplug ( ) ;
return rc ;
}
const struct of_device_id drc_pmem_match [ ] = {
{ . type = " ibm,persistent-memory " , } ,
{ }
} ;
static int pseries_pmem_init ( void )
{
2020-07-01 12:52:29 +05:30
/*
* Only supported on POWER8 and above .
*/
if ( ! cpu_has_feature ( CPU_FTR_ARCH_207S ) )
return 0 ;
2018-10-15 10:18:27 +11:00
pmem_node = of_find_node_by_type ( NULL , " ibm,persistent-memory " ) ;
if ( ! pmem_node )
return 0 ;
/*
* The generic OF bus probe / populate handles creating platform devices
* from the child ( ibm , pmemory ) nodes . The generic code registers an of
* reconfig notifier to handle the hot - add / remove cases too .
*/
of_platform_bus_probe ( pmem_node , drc_pmem_match , NULL ) ;
return 0 ;
}
machine_arch_initcall ( pseries , pseries_pmem_init ) ;