diff options
Diffstat (limited to 'drivers/s390/char/sclp_async.c')
| -rw-r--r-- | drivers/s390/char/sclp_async.c | 224 | 
1 files changed, 224 insertions, 0 deletions
diff --git a/drivers/s390/char/sclp_async.c b/drivers/s390/char/sclp_async.c new file mode 100644 index 00000000000..daaec185ed3 --- /dev/null +++ b/drivers/s390/char/sclp_async.c @@ -0,0 +1,224 @@ +/* + * Enable Asynchronous Notification via SCLP. + * + * Copyright IBM Corp. 2009 + * Author(s): Hans-Joachim Picht <hans@linux.vnet.ibm.com> + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <linux/kmod.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/proc_fs.h> +#include <linux/sysctl.h> +#include <linux/utsname.h> +#include "sclp.h" + +static int callhome_enabled; +static struct sclp_req *request; +static struct sclp_async_sccb *sccb; +static int sclp_async_send_wait(char *message); +static struct ctl_table_header *callhome_sysctl_header; +static DEFINE_SPINLOCK(sclp_async_lock); +static char nodename[64]; +#define SCLP_NORMAL_WRITE	0x00 + +struct async_evbuf { +	struct evbuf_header header; +	u64 reserved; +	u8 rflags; +	u8 empty; +	u8 rtype; +	u8 otype; +	char comp_id[12]; +	char data[3000]; /* there is still some space left */ +} __attribute__((packed)); + +struct sclp_async_sccb { +	struct sccb_header header; +	struct async_evbuf evbuf; +} __attribute__((packed)); + +static struct sclp_register sclp_async_register = { +	.send_mask = EVTYP_ASYNC_MASK, +}; + +static int call_home_on_panic(struct notifier_block *self, +			      unsigned long event, void *data) +{ +		strncat(data, nodename, strlen(nodename)); +		sclp_async_send_wait(data); +		return NOTIFY_DONE; +} + +static struct notifier_block call_home_panic_nb = { +	.notifier_call = call_home_on_panic, +	.priority = INT_MAX, +}; + +static int proc_handler_callhome(ctl_table *ctl, int write, struct file *filp, +				 void __user *buffer, size_t *count, +				 loff_t *ppos) +{ +	unsigned long val; +	int len, rc; +	char buf[2]; + +	if (!*count | (*ppos && !write)) { +		*count = 0; +		return 0; +	} +	if (!write) { +		len =  sprintf(buf, "%d\n", callhome_enabled); +		buf[len] = '\0'; +		rc = copy_to_user(buffer, buf, sizeof(buf)); +		if (rc != 0) +			return -EFAULT; +	} else { +		len = *count; +		rc = copy_from_user(buf, buffer, sizeof(buf)); +		if (rc != 0) +			return -EFAULT; +		if (strict_strtoul(buf, 0, &val) != 0) +			return -EINVAL; +		if (val != 0 && val != 1) +			return -EINVAL; +		callhome_enabled = val; +	} +	*count = len; +	*ppos += len; +	return 0; +} + +static struct ctl_table callhome_table[] = { +	{ +		.procname	= "callhome", +		.mode		= 0644, +		.proc_handler	= &proc_handler_callhome, +	}, +	{ .ctl_name = 0 } +}; + +static struct ctl_table kern_dir_table[] = { +	{ +		.ctl_name	= CTL_KERN, +		.procname	= "kernel", +		.maxlen		= 0, +		.mode		= 0555, +		.child		= callhome_table, +	}, +	{ .ctl_name = 0 } +}; + +/* + * Function used to transfer asynchronous notification + * records which waits for send completion + */ +static int sclp_async_send_wait(char *message) +{ +	struct async_evbuf *evb; +	int rc; +	unsigned long flags; + +	if (!callhome_enabled) +		return 0; +	sccb->evbuf.header.type = EVTYP_ASYNC; +	sccb->evbuf.rtype = 0xA5; +	sccb->evbuf.otype = 0x00; +	evb = &sccb->evbuf; +	request->command = SCLP_CMDW_WRITE_EVENT_DATA; +	request->sccb = sccb; +	request->status = SCLP_REQ_FILLED; +	strncpy(sccb->evbuf.data, message, sizeof(sccb->evbuf.data)); +	/* +	 * Retain Queue +	 * e.g. 5639CC140 500 Red Hat RHEL5 Linux for zSeries (RHEL AS) +	 */ +	strncpy(sccb->evbuf.comp_id, "000000000", sizeof(sccb->evbuf.comp_id)); +	sccb->evbuf.header.length = sizeof(sccb->evbuf); +	sccb->header.length = sizeof(sccb->evbuf) + sizeof(sccb->header); +	sccb->header.function_code = SCLP_NORMAL_WRITE; +	rc = sclp_add_request(request); +	if (rc) +		return rc; +	spin_lock_irqsave(&sclp_async_lock, flags); +	while (request->status != SCLP_REQ_DONE && +		request->status != SCLP_REQ_FAILED) { +		 sclp_sync_wait(); +	} +	spin_unlock_irqrestore(&sclp_async_lock, flags); +	if (request->status != SCLP_REQ_DONE) +		return -EIO; +	rc = ((struct sclp_async_sccb *) +	       request->sccb)->header.response_code; +	if (rc != 0x0020) +		return -EIO; +	if (evb->header.flags != 0x80) +		return -EIO; +	return rc; +} + +static int __init sclp_async_init(void) +{ +	int rc; + +	rc = sclp_register(&sclp_async_register); +	if (rc) +		return rc; +	callhome_sysctl_header = register_sysctl_table(kern_dir_table); +	if (!callhome_sysctl_header) { +		rc = -ENOMEM; +		goto out_sclp; +	} +	if (!(sclp_async_register.sclp_receive_mask & EVTYP_ASYNC_MASK)) { +		rc = -EOPNOTSUPP; +		goto out_sclp; +	} +	rc = -ENOMEM; +	request = kzalloc(sizeof(struct sclp_req), GFP_KERNEL); +	if (!request) +		goto out_sys; +	sccb = (struct sclp_async_sccb *) get_zeroed_page(GFP_KERNEL | GFP_DMA); +	if (!sccb) +		goto out_mem; +	rc =  atomic_notifier_chain_register(&panic_notifier_list, +					     &call_home_panic_nb); +	if (rc) +		goto out_mem; + +	strncpy(nodename, init_utsname()->nodename, 64); +	return 0; + +out_mem: +	kfree(request); +	free_page((unsigned long) sccb); +out_sys: +	unregister_sysctl_table(callhome_sysctl_header); +out_sclp: +	sclp_unregister(&sclp_async_register); +	return rc; + +} +module_init(sclp_async_init); + +static void __exit sclp_async_exit(void) +{ +	atomic_notifier_chain_unregister(&panic_notifier_list, +					 &call_home_panic_nb); +	unregister_sysctl_table(callhome_sysctl_header); +	sclp_unregister(&sclp_async_register); +	free_page((unsigned long) sccb); +	kfree(request); +} +module_exit(sclp_async_exit); + +MODULE_AUTHOR("Copyright IBM Corp. 2009"); +MODULE_AUTHOR("Hans-Joachim Picht <hans@linux.vnet.ibm.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SCLP Asynchronous Notification Records");  |