1/*
2 * Enable Asynchronous Notification via SCLP.
3 *
4 * Copyright IBM Corp. 2009
5 * Author(s): Hans-Joachim Picht <hans@linux.vnet.ibm.com>
6 *
7 */
8
9#include <linux/init.h>
10#include <linux/module.h>
11#include <linux/device.h>
12#include <linux/stat.h>
13#include <linux/string.h>
14#include <linux/slab.h>
15#include <linux/ctype.h>
16#include <linux/kmod.h>
17#include <linux/err.h>
18#include <linux/errno.h>
19#include <linux/proc_fs.h>
20#include <linux/sysctl.h>
21#include <linux/utsname.h>
22#include "sclp.h"
23
24static int callhome_enabled;
25static struct sclp_req *request;
26static struct sclp_async_sccb *sccb;
27static int sclp_async_send_wait(char *message);
28static struct ctl_table_header *callhome_sysctl_header;
29static DEFINE_SPINLOCK(sclp_async_lock);
30#define SCLP_NORMAL_WRITE	0x00
31
32struct async_evbuf {
33	struct evbuf_header header;
34	u64 reserved;
35	u8 rflags;
36	u8 empty;
37	u8 rtype;
38	u8 otype;
39	char comp_id[12];
40	char data[3000]; /* there is still some space left */
41} __attribute__((packed));
42
43struct sclp_async_sccb {
44	struct sccb_header header;
45	struct async_evbuf evbuf;
46} __attribute__((packed));
47
48static struct sclp_register sclp_async_register = {
49	.send_mask = EVTYP_ASYNC_MASK,
50};
51
52static int call_home_on_panic(struct notifier_block *self,
53			      unsigned long event, void *data)
54{
55	strncat(data, init_utsname()->nodename,
56		sizeof(init_utsname()->nodename));
57	sclp_async_send_wait(data);
58	return NOTIFY_DONE;
59}
60
61static struct notifier_block call_home_panic_nb = {
62	.notifier_call = call_home_on_panic,
63	.priority = INT_MAX,
64};
65
66static int proc_handler_callhome(struct ctl_table *ctl, int write,
67				 void __user *buffer, size_t *count,
68				 loff_t *ppos)
69{
70	unsigned long val;
71	int len, rc;
72	char buf[3];
73
74	if (!*count || (*ppos && !write)) {
75		*count = 0;
76		return 0;
77	}
78	if (!write) {
79		len = snprintf(buf, sizeof(buf), "%d\n", callhome_enabled);
80		rc = copy_to_user(buffer, buf, sizeof(buf));
81		if (rc != 0)
82			return -EFAULT;
83	} else {
84		len = *count;
85		rc = kstrtoul_from_user(buffer, len, 0, &val);
86		if (rc)
87			return rc;
88		if (val != 0 && val != 1)
89			return -EINVAL;
90		callhome_enabled = val;
91	}
92	*count = len;
93	*ppos += len;
94	return 0;
95}
96
97static struct ctl_table callhome_table[] = {
98	{
99		.procname	= "callhome",
100		.mode		= 0644,
101		.proc_handler	= proc_handler_callhome,
102	},
103	{}
104};
105
106static struct ctl_table kern_dir_table[] = {
107	{
108		.procname	= "kernel",
109		.maxlen		= 0,
110		.mode		= 0555,
111		.child		= callhome_table,
112	},
113	{}
114};
115
116/*
117 * Function used to transfer asynchronous notification
118 * records which waits for send completion
119 */
120static int sclp_async_send_wait(char *message)
121{
122	struct async_evbuf *evb;
123	int rc;
124	unsigned long flags;
125
126	if (!callhome_enabled)
127		return 0;
128	sccb->evbuf.header.type = EVTYP_ASYNC;
129	sccb->evbuf.rtype = 0xA5;
130	sccb->evbuf.otype = 0x00;
131	evb = &sccb->evbuf;
132	request->command = SCLP_CMDW_WRITE_EVENT_DATA;
133	request->sccb = sccb;
134	request->status = SCLP_REQ_FILLED;
135	strncpy(sccb->evbuf.data, message, sizeof(sccb->evbuf.data));
136	/*
137	 * Retain Queue
138	 * e.g. 5639CC140 500 Red Hat RHEL5 Linux for zSeries (RHEL AS)
139	 */
140	strncpy(sccb->evbuf.comp_id, CONFIG_SCLP_ASYNC_ID,
141		sizeof(sccb->evbuf.comp_id));
142	sccb->evbuf.header.length = sizeof(sccb->evbuf);
143	sccb->header.length = sizeof(sccb->evbuf) + sizeof(sccb->header);
144	sccb->header.function_code = SCLP_NORMAL_WRITE;
145	rc = sclp_add_request(request);
146	if (rc)
147		return rc;
148	spin_lock_irqsave(&sclp_async_lock, flags);
149	while (request->status != SCLP_REQ_DONE &&
150		request->status != SCLP_REQ_FAILED) {
151		 sclp_sync_wait();
152	}
153	spin_unlock_irqrestore(&sclp_async_lock, flags);
154	if (request->status != SCLP_REQ_DONE)
155		return -EIO;
156	rc = ((struct sclp_async_sccb *)
157	       request->sccb)->header.response_code;
158	if (rc != 0x0020)
159		return -EIO;
160	if (evb->header.flags != 0x80)
161		return -EIO;
162	return rc;
163}
164
165static int __init sclp_async_init(void)
166{
167	int rc;
168
169	rc = sclp_register(&sclp_async_register);
170	if (rc)
171		return rc;
172	rc = -EOPNOTSUPP;
173	if (!(sclp_async_register.sclp_receive_mask & EVTYP_ASYNC_MASK))
174		goto out_sclp;
175	rc = -ENOMEM;
176	callhome_sysctl_header = register_sysctl_table(kern_dir_table);
177	if (!callhome_sysctl_header)
178		goto out_sclp;
179	request = kzalloc(sizeof(struct sclp_req), GFP_KERNEL);
180	sccb = (struct sclp_async_sccb *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
181	if (!request || !sccb)
182		goto out_mem;
183	rc = atomic_notifier_chain_register(&panic_notifier_list,
184					    &call_home_panic_nb);
185	if (!rc)
186		goto out;
187out_mem:
188	kfree(request);
189	free_page((unsigned long) sccb);
190	unregister_sysctl_table(callhome_sysctl_header);
191out_sclp:
192	sclp_unregister(&sclp_async_register);
193out:
194	return rc;
195}
196module_init(sclp_async_init);
197
198static void __exit sclp_async_exit(void)
199{
200	atomic_notifier_chain_unregister(&panic_notifier_list,
201					 &call_home_panic_nb);
202	unregister_sysctl_table(callhome_sysctl_header);
203	sclp_unregister(&sclp_async_register);
204	free_page((unsigned long) sccb);
205	kfree(request);
206}
207module_exit(sclp_async_exit);
208
209MODULE_AUTHOR("Copyright IBM Corp. 2009");
210MODULE_AUTHOR("Hans-Joachim Picht <hans@linux.vnet.ibm.com>");
211MODULE_LICENSE("GPL");
212MODULE_DESCRIPTION("SCLP Asynchronous Notification Records");
213