145 lines
		
	
	
	
		
			3.5 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			145 lines
		
	
	
	
		
			3.5 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*
 | ||
|  |  *    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]; | ||
|  | static char cpc_name[OCF_LENGTH_CPC_NAME + 1]; | ||
|  | 
 | ||
|  | 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); | ||
|  | 		memcpy(cpc_name, cpc + 1, size); | ||
|  | 		EBCASC(cpc_name, size); | ||
|  | 		cpc_name[size] = 0; | ||
|  | 	} | ||
|  | 	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, | ||
|  | }; | ||
|  | 
 | ||
|  | static ssize_t cpc_name_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", cpc_name); | ||
|  | 	spin_unlock_irq(&sclp_ocf_lock); | ||
|  | 	return rc; | ||
|  | } | ||
|  | 
 | ||
|  | 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); |