2017-11-14 20:38:02 +03:00
// SPDX-License-Identifier: GPL-2.0
2012-08-28 18:46:26 +04:00
/*
* Recognize and maintain s390 storage class memory .
*
* Copyright IBM Corp . 2012
* Author ( s ) : Sebastian Ott < sebott @ linux . vnet . ibm . com >
*/
# include <linux/device.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/slab.h>
# include <linux/init.h>
# include <linux/err.h>
# include <asm/eadm.h>
# include "chsc.h"
static struct device * scm_root ;
# define to_scm_dev(n) container_of(n, struct scm_device, dev)
# define to_scm_drv(d) container_of(d, struct scm_driver, drv)
static int scmdev_probe ( struct device * dev )
{
struct scm_device * scmdev = to_scm_dev ( dev ) ;
struct scm_driver * scmdrv = to_scm_drv ( dev - > driver ) ;
return scmdrv - > probe ? scmdrv - > probe ( scmdev ) : - ENODEV ;
}
static int scmdev_remove ( struct device * dev )
{
struct scm_device * scmdev = to_scm_dev ( dev ) ;
struct scm_driver * scmdrv = to_scm_drv ( dev - > driver ) ;
return scmdrv - > remove ? scmdrv - > remove ( scmdev ) : - ENODEV ;
}
static int scmdev_uevent ( struct device * dev , struct kobj_uevent_env * env )
{
return add_uevent_var ( env , " MODALIAS=scm:scmdev " ) ;
}
static struct bus_type scm_bus_type = {
. name = " scm " ,
. probe = scmdev_probe ,
. remove = scmdev_remove ,
. uevent = scmdev_uevent ,
} ;
/**
* scm_driver_register ( ) - register a scm driver
* @ scmdrv : driver to be registered
*/
int scm_driver_register ( struct scm_driver * scmdrv )
{
struct device_driver * drv = & scmdrv - > drv ;
drv - > bus = & scm_bus_type ;
return driver_register ( drv ) ;
}
EXPORT_SYMBOL_GPL ( scm_driver_register ) ;
/**
* scm_driver_unregister ( ) - deregister a scm driver
* @ scmdrv : driver to be deregistered
*/
void scm_driver_unregister ( struct scm_driver * scmdrv )
{
driver_unregister ( & scmdrv - > drv ) ;
}
EXPORT_SYMBOL_GPL ( scm_driver_unregister ) ;
2017-06-03 10:38:04 +03:00
void scm_irq_handler ( struct aob * aob , blk_status_t error )
2012-08-28 18:46:26 +04:00
{
struct aob_rq_header * aobrq = ( void * ) aob - > request . data ;
struct scm_device * scmdev = aobrq - > scmdev ;
struct scm_driver * scmdrv = to_scm_drv ( scmdev - > dev . driver ) ;
scmdrv - > handler ( scmdev , aobrq - > data , error ) ;
}
EXPORT_SYMBOL_GPL ( scm_irq_handler ) ;
# define scm_attr(name) \
static ssize_t show_ # # name ( struct device * dev , \
struct device_attribute * attr , char * buf ) \
{ \
struct scm_device * scmdev = to_scm_dev ( dev ) ; \
int ret ; \
\
2012-09-04 21:36:41 +04:00
device_lock ( dev ) ; \
2012-08-28 18:46:26 +04:00
ret = sprintf ( buf , " %u \n " , scmdev - > attrs . name ) ; \
2012-09-04 21:36:41 +04:00
device_unlock ( dev ) ; \
2012-08-28 18:46:26 +04:00
\
return ret ; \
} \
static DEVICE_ATTR ( name , S_IRUGO , show_ # # name , NULL ) ;
scm_attr ( persistence ) ;
scm_attr ( oper_state ) ;
scm_attr ( data_state ) ;
scm_attr ( rank ) ;
scm_attr ( release ) ;
scm_attr ( res_id ) ;
static struct attribute * scmdev_attrs [ ] = {
& dev_attr_persistence . attr ,
& dev_attr_oper_state . attr ,
& dev_attr_data_state . attr ,
& dev_attr_rank . attr ,
& dev_attr_release . attr ,
& dev_attr_res_id . attr ,
NULL ,
} ;
static struct attribute_group scmdev_attr_group = {
. attrs = scmdev_attrs ,
} ;
static const struct attribute_group * scmdev_attr_groups [ ] = {
& scmdev_attr_group ,
NULL ,
} ;
static void scmdev_release ( struct device * dev )
{
struct scm_device * scmdev = to_scm_dev ( dev ) ;
kfree ( scmdev ) ;
}
static void scmdev_setup ( struct scm_device * scmdev , struct sale * sale ,
unsigned int size , unsigned int max_blk_count )
{
dev_set_name ( & scmdev - > dev , " %016llx " , ( unsigned long long ) sale - > sa ) ;
scmdev - > nr_max_block = max_blk_count ;
scmdev - > address = sale - > sa ;
scmdev - > size = 1UL < < size ;
scmdev - > attrs . rank = sale - > rank ;
scmdev - > attrs . persistence = sale - > p ;
scmdev - > attrs . oper_state = sale - > op_state ;
scmdev - > attrs . data_state = sale - > data_state ;
scmdev - > attrs . rank = sale - > rank ;
scmdev - > attrs . release = sale - > r ;
scmdev - > attrs . res_id = sale - > rid ;
scmdev - > dev . parent = scm_root ;
scmdev - > dev . bus = & scm_bus_type ;
scmdev - > dev . release = scmdev_release ;
scmdev - > dev . groups = scmdev_attr_groups ;
}
2012-08-28 18:47:02 +04:00
/*
* Check for state - changes , notify the driver and userspace .
*/
static void scmdev_update ( struct scm_device * scmdev , struct sale * sale )
{
struct scm_driver * scmdrv ;
bool changed ;
device_lock ( & scmdev - > dev ) ;
changed = scmdev - > attrs . rank ! = sale - > rank | |
scmdev - > attrs . oper_state ! = sale - > op_state ;
scmdev - > attrs . rank = sale - > rank ;
scmdev - > attrs . oper_state = sale - > op_state ;
if ( ! scmdev - > dev . driver )
goto out ;
scmdrv = to_scm_drv ( scmdev - > dev . driver ) ;
if ( changed & & scmdrv - > notify )
2013-02-28 15:07:38 +04:00
scmdrv - > notify ( scmdev , SCM_CHANGE ) ;
2012-08-28 18:47:02 +04:00
out :
device_unlock ( & scmdev - > dev ) ;
if ( changed )
kobject_uevent ( & scmdev - > dev . kobj , KOBJ_CHANGE ) ;
}
2019-06-14 20:53:59 +03:00
static int check_address ( struct device * dev , const void * data )
2012-08-28 18:47:02 +04:00
{
struct scm_device * scmdev = to_scm_dev ( dev ) ;
2019-06-14 20:53:59 +03:00
const struct sale * sale = data ;
2012-08-28 18:47:02 +04:00
return scmdev - > address = = sale - > sa ;
}
static struct scm_device * scmdev_find ( struct sale * sale )
{
struct device * dev ;
dev = bus_find_device ( & scm_bus_type , NULL , sale , check_address ) ;
return dev ? to_scm_dev ( dev ) : NULL ;
}
2012-08-28 18:46:26 +04:00
static int scm_add ( struct chsc_scm_info * scm_info , size_t num )
{
struct sale * sale , * scmal = scm_info - > scmal ;
struct scm_device * scmdev ;
int ret ;
for ( sale = scmal ; sale < scmal + num ; sale + + ) {
2012-08-28 18:47:02 +04:00
scmdev = scmdev_find ( sale ) ;
if ( scmdev ) {
scmdev_update ( scmdev , sale ) ;
/* Release reference from scm_find(). */
put_device ( & scmdev - > dev ) ;
continue ;
}
2012-08-28 18:46:26 +04:00
scmdev = kzalloc ( sizeof ( * scmdev ) , GFP_KERNEL ) ;
if ( ! scmdev )
return - ENODEV ;
scmdev_setup ( scmdev , sale , scm_info - > is , scm_info - > mbc ) ;
ret = device_register ( & scmdev - > dev ) ;
if ( ret ) {
/* Release reference from device_initialize(). */
put_device ( & scmdev - > dev ) ;
return ret ;
}
}
return 0 ;
}
2012-08-28 18:47:02 +04:00
int scm_update_information ( void )
2012-08-28 18:46:26 +04:00
{
struct chsc_scm_info * scm_info ;
u64 token = 0 ;
size_t num ;
int ret ;
scm_info = ( void * ) __get_free_page ( GFP_KERNEL | GFP_DMA ) ;
if ( ! scm_info )
return - ENOMEM ;
do {
ret = chsc_scm_info ( scm_info , token ) ;
if ( ret )
break ;
num = ( scm_info - > response . length -
( offsetof ( struct chsc_scm_info , scmal ) -
offsetof ( struct chsc_scm_info , response ) )
) / sizeof ( struct sale ) ;
ret = scm_add ( scm_info , num ) ;
if ( ret )
break ;
token = scm_info - > restok ;
} while ( token ) ;
free_page ( ( unsigned long ) scm_info ) ;
return ret ;
}
2013-02-28 15:07:55 +04:00
static int scm_dev_avail ( struct device * dev , void * unused )
{
struct scm_driver * scmdrv = to_scm_drv ( dev - > driver ) ;
struct scm_device * scmdev = to_scm_dev ( dev ) ;
if ( dev - > driver & & scmdrv - > notify )
scmdrv - > notify ( scmdev , SCM_AVAIL ) ;
return 0 ;
}
int scm_process_availability_information ( void )
{
return bus_for_each_dev ( & scm_bus_type , NULL , NULL , scm_dev_avail ) ;
}
2012-08-28 18:46:26 +04:00
static int __init scm_init ( void )
{
int ret ;
ret = bus_register ( & scm_bus_type ) ;
if ( ret )
return ret ;
scm_root = root_device_register ( " scm " ) ;
if ( IS_ERR ( scm_root ) ) {
bus_unregister ( & scm_bus_type ) ;
return PTR_ERR ( scm_root ) ;
}
scm_update_information ( ) ;
return 0 ;
}
subsys_initcall_sync ( scm_init ) ;