2011-05-23 10:24:43 +02:00
/*
* SCLP OCF communication parameters sysfs interface
*
* Copyright IBM Corp . 2011
* Author ( s ) : Martin Schwidefsky < schwidefsky @ de . ibm . com >
*/
# define KMSG_COMPONENT "sclp_ocf"
# define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/stat.h>
# include <linux/device.h>
# include <linux/string.h>
# include <linux/ctype.h>
# include <linux/kmod.h>
# include <linux/timer.h>
# include <linux/err.h>
# include <asm/ebcdic.h>
# include <asm/sclp.h>
# include "sclp.h"
# define OCF_LENGTH_HMC_NETWORK 8UL
# define OCF_LENGTH_CPC_NAME 8UL
static char hmc_network [ OCF_LENGTH_HMC_NETWORK + 1 ] ;
2016-02-08 13:36:22 +01:00
static char cpc_name [ OCF_LENGTH_CPC_NAME ] ; /* in EBCDIC */
2011-05-23 10:24:43 +02:00
static DEFINE_SPINLOCK ( sclp_ocf_lock ) ;
static struct work_struct sclp_ocf_change_work ;
static struct kset * ocf_kset ;
static void sclp_ocf_change_notify ( struct work_struct * work )
{
kobject_uevent ( & ocf_kset - > kobj , KOBJ_CHANGE ) ;
}
/* Handler for OCF event. Look for the CPC image name. */
static void sclp_ocf_handler ( struct evbuf_header * evbuf )
{
struct gds_vector * v ;
struct gds_subvector * sv , * netid , * cpc ;
size_t size ;
/* Find the 0x9f00 block. */
v = sclp_find_gds_vector ( evbuf + 1 , ( void * ) evbuf + evbuf - > length ,
0x9f00 ) ;
if ( ! v )
return ;
/* Find the 0x9f22 block inside the 0x9f00 block. */
v = sclp_find_gds_vector ( v + 1 , ( void * ) v + v - > length , 0x9f22 ) ;
if ( ! v )
return ;
/* Find the 0x81 block inside the 0x9f22 block. */
sv = sclp_find_gds_subvector ( v + 1 , ( void * ) v + v - > length , 0x81 ) ;
if ( ! sv )
return ;
/* Find the 0x01 block inside the 0x81 block. */
netid = sclp_find_gds_subvector ( sv + 1 , ( void * ) sv + sv - > length , 1 ) ;
/* Find the 0x02 block inside the 0x81 block. */
cpc = sclp_find_gds_subvector ( sv + 1 , ( void * ) sv + sv - > length , 2 ) ;
/* Copy network name and cpc name. */
spin_lock ( & sclp_ocf_lock ) ;
if ( netid ) {
size = min ( OCF_LENGTH_HMC_NETWORK , ( size_t ) netid - > length ) ;
memcpy ( hmc_network , netid + 1 , size ) ;
EBCASC ( hmc_network , size ) ;
hmc_network [ size ] = 0 ;
}
if ( cpc ) {
size = min ( OCF_LENGTH_CPC_NAME , ( size_t ) cpc - > length ) ;
2016-02-08 13:36:22 +01:00
memset ( cpc_name , 0 , OCF_LENGTH_CPC_NAME ) ;
2011-05-23 10:24:43 +02:00
memcpy ( cpc_name , cpc + 1 , size ) ;
}
spin_unlock ( & sclp_ocf_lock ) ;
schedule_work ( & sclp_ocf_change_work ) ;
}
static struct sclp_register sclp_ocf_event = {
. receive_mask = EVTYP_OCF_MASK ,
. receiver_fn = sclp_ocf_handler ,
} ;
2016-02-08 13:36:22 +01:00
void sclp_ocf_cpc_name_copy ( char * dst )
{
spin_lock_irq ( & sclp_ocf_lock ) ;
memcpy ( dst , cpc_name , OCF_LENGTH_CPC_NAME ) ;
spin_unlock_irq ( & sclp_ocf_lock ) ;
}
EXPORT_SYMBOL ( sclp_ocf_cpc_name_copy ) ;
2011-05-23 10:24:43 +02:00
static ssize_t cpc_name_show ( struct kobject * kobj ,
struct kobj_attribute * attr , char * page )
{
2016-02-08 13:36:22 +01:00
char name [ OCF_LENGTH_CPC_NAME + 1 ] ;
2011-05-23 10:24:43 +02:00
2016-02-08 13:36:22 +01:00
sclp_ocf_cpc_name_copy ( name ) ;
name [ OCF_LENGTH_CPC_NAME ] = 0 ;
EBCASC ( name , OCF_LENGTH_CPC_NAME ) ;
return snprintf ( page , PAGE_SIZE , " %s \n " , name ) ;
2011-05-23 10:24:43 +02:00
}
static struct kobj_attribute cpc_name_attr =
__ATTR ( cpc_name , 0444 , cpc_name_show , NULL ) ;
static ssize_t hmc_network_show ( struct kobject * kobj ,
struct kobj_attribute * attr , char * page )
{
int rc ;
spin_lock_irq ( & sclp_ocf_lock ) ;
rc = snprintf ( page , PAGE_SIZE , " %s \n " , hmc_network ) ;
spin_unlock_irq ( & sclp_ocf_lock ) ;
return rc ;
}
static struct kobj_attribute hmc_network_attr =
__ATTR ( hmc_network , 0444 , hmc_network_show , NULL ) ;
static struct attribute * ocf_attrs [ ] = {
& cpc_name_attr . attr ,
& hmc_network_attr . attr ,
NULL ,
} ;
static struct attribute_group ocf_attr_group = {
. attrs = ocf_attrs ,
} ;
static int __init ocf_init ( void )
{
int rc ;
INIT_WORK ( & sclp_ocf_change_work , sclp_ocf_change_notify ) ;
ocf_kset = kset_create_and_add ( " ocf " , NULL , firmware_kobj ) ;
if ( ! ocf_kset )
return - ENOMEM ;
rc = sysfs_create_group ( & ocf_kset - > kobj , & ocf_attr_group ) ;
if ( rc ) {
kset_unregister ( ocf_kset ) ;
return rc ;
}
return sclp_register ( & sclp_ocf_event ) ;
}
device_initcall ( ocf_init ) ;