summaryrefslogtreecommitdiff
path: root/drivers/char/snsc_event.c
blob: 8b2210b633dfc2845396451d573507d69f566fd2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
/*
 * SN Platform system controller communication support
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 *
 * Copyright (C) 2004-2006 Silicon Graphics, Inc. All rights reserved.
 */

/*
 * System controller event handler
 *
 * These routines deal with environmental events arriving from the
 * system controllers.
 */

#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/byteorder/generic.h>
#include <asm/sn/sn_sal.h>
#include <asm/unaligned.h>
#include "snsc.h"

static struct subch_data_s *event_sd;

void scdrv_event(unsigned long);
DECLARE_TASKLET(sn_sysctl_event, scdrv_event, 0);

/*
 * scdrv_event_interrupt
 *
 * Pull incoming environmental events off the physical link to the
 * system controller and put them in a temporary holding area in SAL.
 * Schedule scdrv_event() to move them along to their ultimate
 * destination.
 */
static irqreturn_t
scdrv_event_interrupt(int irq, void *subch_data, struct pt_regs *regs)
{
	struct subch_data_s *sd = subch_data;
	unsigned long flags;
	int status;

	spin_lock_irqsave(&sd->sd_rlock, flags);
	status = ia64_sn_irtr_intr(sd->sd_nasid, sd->sd_subch);

	if ((status > 0) && (status & SAL_IROUTER_INTR_RECV)) {
		tasklet_schedule(&sn_sysctl_event);
	}
	spin_unlock_irqrestore(&sd->sd_rlock, flags);
	return IRQ_HANDLED;
}


/*
 * scdrv_parse_event
 *
 * Break an event (as read from SAL) into useful pieces so we can decide
 * what to do with it.
 */
static int
scdrv_parse_event(char *event, int *src, int *code, int *esp_code, char *desc)
{
	char *desc_end;
	__be32 from_buf;

	/* record event source address */
	from_buf = get_unaligned((__be32 *)event);
	*src = be32_to_cpup(&from_buf);
	event += 4; 			/* move on to event code */

	/* record the system controller's event code */
	from_buf = get_unaligned((__be32 *)event);
	*code = be32_to_cpup(&from_buf);
	event += 4;			/* move on to event arguments */

	/* how many arguments are in the packet? */
	if (*event++ != 2) {
		/* if not 2, give up */
		return -1;
	}

	/* parse out the ESP code */
	if (*event++ != IR_ARG_INT) {
		/* not an integer argument, so give up */
		return -1;
	}
	from_buf = get_unaligned((__be32 *)event);
	*esp_code = be32_to_cpup(&from_buf);
	event += 4;

	/* parse out the event description */
	if (*event++ != IR_ARG_ASCII) {
		/* not an ASCII string, so give up */
		return -1;
	}
	event[CHUNKSIZE-1] = '\0';	/* ensure this string ends! */
	event += 2; 			/* skip leading CR/LF */
	desc_end = desc + sprintf(desc, "%s", event);

	/* strip trailing CR/LF (if any) */
	for (desc_end--;
	     (desc_end != desc) && ((*desc_end == 0xd) || (*desc_end == 0xa));
	     desc_end--) {
		*desc_end = '\0';
	}

	return 0;
}


/*
 * scdrv_event_severity
 *
 * Figure out how urgent a message we should write to the console/syslog
 * via printk.
 */
static char *
scdrv_event_severity(int code)
{
	int ev_class = (code & EV_CLASS_MASK);
	int ev_severity = (code & EV_SEVERITY_MASK);
	char *pk_severity = KERN_NOTICE;

	switch (ev_class) {
	case EV_CLASS_POWER:
		switch (ev_severity) {
		case EV_SEVERITY_POWER_LOW_WARNING:
		case EV_SEVERITY_POWER_HIGH_WARNING:
			pk_severity = KERN_WARNING;
			break;
		case EV_SEVERITY_POWER_HIGH_FAULT:
		case EV_SEVERITY_POWER_LOW_FAULT:
			pk_severity = KERN_ALERT;
			break;
		}
		break;
	case EV_CLASS_FAN:
		switch (ev_severity) {
		case EV_SEVERITY_FAN_WARNING:
			pk_severity = KERN_WARNING;
			break;
		case EV_SEVERITY_FAN_FAULT:
			pk_severity = KERN_CRIT;
			break;
		}
		break;
	case EV_CLASS_TEMP:
		switch (ev_severity) {
		case EV_SEVERITY_TEMP_ADVISORY:
			pk_severity = KERN_WARNING;
			break;
		case EV_SEVERITY_TEMP_CRITICAL:
			pk_severity = KERN_CRIT;
			break;
		case EV_SEVERITY_TEMP_FAULT:
			pk_severity = KERN_ALERT;
			break;
		}
		break;
	case EV_CLASS_ENV:
		pk_severity = KERN_ALERT;
		break;
	case EV_CLASS_TEST_FAULT:
		pk_severity = KERN_ALERT;
		break;
	case EV_CLASS_TEST_WARNING:
		pk_severity = KERN_WARNING;
		break;
	case EV_CLASS_PWRD_NOTIFY:
		pk_severity = KERN_ALERT;
		break;
	}

	return pk_severity;
}


/*
 * scdrv_dispatch_event
 *
 * Do the right thing with an incoming event.  That's often nothing
 * more than printing it to the system log.  For power-down notifications
 * we start a graceful shutdown.
 */
static void
scdrv_dispatch_event(char *event, int len)
{
	static int snsc_shutting_down = 0;
	int code, esp_code, src, class;
	char desc[CHUNKSIZE];
	char *severity;

	if (scdrv_parse_event(event, &src, &code, &esp_code, desc) < 0) {
		/* ignore uninterpretible event */
		return;
	}

	/* how urgent is the message? */
	severity = scdrv_event_severity(code);

	class = (code & EV_CLASS_MASK);

	if (class == EV_CLASS_PWRD_NOTIFY || code == ENV_PWRDN_PEND) {
		struct task_struct *p;

		if (snsc_shutting_down)
			return;

		snsc_shutting_down = 1;

		/* give a message for each type of event */
		if (class == EV_CLASS_PWRD_NOTIFY)
			printk(KERN_NOTICE "Power off indication received."
			       " Sending SIGPWR to init...\n");
		else if (code == ENV_PWRDN_PEND)
			printk(KERN_CRIT "WARNING: Shutting down the system"
			       " due to a critical environmental condition."
			       " Sending SIGPWR to init...\n");

		/* give a SIGPWR signal to init proc */

		/* first find init's task */
		read_lock(&tasklist_lock);
		for_each_process(p) {
			if (p->pid == 1)
				break;
		}
		if (p) {
			force_sig(SIGPWR, p);
		} else {
			printk(KERN_ERR "Failed to signal init!\n");
			snsc_shutting_down = 0; /* so can try again (?) */
		}
		read_unlock(&tasklist_lock);
	} else {
		/* print to system log */
		printk("%s|$(0x%x)%s\n", severity, esp_code, desc);
	}
}


/*
 * scdrv_event
 *
 * Called as a tasklet when an event arrives from the L1.  Read the event
 * from where it's temporarily stored in SAL and call scdrv_dispatch_event()
 * to send it on its way.  Keep trying to read events until SAL indicates
 * that there are no more immediately available.
 */
void
scdrv_event(unsigned long dummy)
{
	int status;
	int len;
	unsigned long flags;
	struct subch_data_s *sd = event_sd;

	/* anything to read? */
	len = CHUNKSIZE;
	spin_lock_irqsave(&sd->sd_rlock, flags);
	status = ia64_sn_irtr_recv(sd->sd_nasid, sd->sd_subch,
				   sd->sd_rb, &len);

	while (!(status < 0)) {
		spin_unlock_irqrestore(&sd->sd_rlock, flags);
		scdrv_dispatch_event(sd->sd_rb, len);
		len = CHUNKSIZE;
		spin_lock_irqsave(&sd->sd_rlock, flags);
		status = ia64_sn_irtr_recv(sd->sd_nasid, sd->sd_subch,
					   sd->sd_rb, &len);
	}
	spin_unlock_irqrestore(&sd->sd_rlock, flags);
}


/*
 * scdrv_event_init
 *
 * Sets up a system controller subchannel to begin receiving event
 * messages. This is sort of a specialized version of scdrv_open()
 * in drivers/char/sn_sysctl.c.
 */
void
scdrv_event_init(struct sysctl_data_s *scd)
{
	int rv;

	event_sd = kzalloc(sizeof (struct subch_data_s), GFP_KERNEL);
	if (event_sd == NULL) {
		printk(KERN_WARNING "%s: couldn't allocate subchannel info"
		       " for event monitoring\n", __FUNCTION__);
		return;
	}

	/* initialize subch_data_s fields */
	event_sd->sd_nasid = scd->scd_nasid;
	spin_lock_init(&event_sd->sd_rlock);

	/* ask the system controllers to send events to this node */
	event_sd->sd_subch = ia64_sn_sysctl_event_init(scd->scd_nasid);

	if (event_sd->sd_subch < 0) {
		kfree(event_sd);
		printk(KERN_WARNING "%s: couldn't open event subchannel\n",
		       __FUNCTION__);
		return;
	}

	/* hook event subchannel up to the system controller interrupt */
	rv = request_irq(SGI_UART_VECTOR, scdrv_event_interrupt,
			 IRQF_SHARED | IRQF_DISABLED,
			 "system controller events", event_sd);
	if (rv) {
		printk(KERN_WARNING "%s: irq request failed (%d)\n",
		       __FUNCTION__, rv);
		ia64_sn_irtr_close(event_sd->sd_nasid, event_sd->sd_subch);
		kfree(event_sd);
		return;
	}
}