summaryrefslogtreecommitdiffstats
path: root/arch/s390/kernel/irq.c
blob: 989aa07204141de0909d3ad74ddcb29250fd2d15 (plain)
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
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
/*
 *  arch/s390/kernel/irq.c
 *
 *  S390 version
 *    Copyright (C) 1999,2000 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *    Author(s): Ingo Adlung (adlung@de.ibm.com)
 *
 *  Derived from "arch/i386/kernel/irq.c"
 *    Copyright (C) 1992, 1999 Linus Torvalds, Ingo Molnar
 *
 *  S/390 I/O interrupt processing and I/O request processing is
 *   implemented in arch/s390/kernel/s390io.c
 */
#include <linux/config.h>
#include <linux/ptrace.h>
#include <linux/errno.h>
#include <linux/kernel_stat.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/timex.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/random.h>
#include <linux/smp.h>
#include <linux/threads.h>
#include <linux/smp_lock.h>
#include <linux/init.h>

#include <asm/system.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/bitops.h>
#include <asm/smp.h>
#include <asm/pgtable.h>
#include <asm/delay.h>
#include <asm/lowcore.h>

void          s390_init_IRQ   ( void );
void          s390_free_irq   ( unsigned int irq, void *dev_id);
int           s390_request_irq( unsigned int irq,
                     void           (*handler)(int, void *, struct pt_regs *),
                     unsigned long  irqflags,
                     const char    *devname,
                     void          *dev_id);

#if 0
/*
 * The following vectors are part of the Linux architecture, there
 * is no hardware IRQ pin equivalent for them, they are triggered
 * through the ICC by us (IPIs), via smp_message_pass():
 */
BUILD_SMP_INTERRUPT(reschedule_interrupt)
BUILD_SMP_INTERRUPT(invalidate_interrupt)
BUILD_SMP_INTERRUPT(stop_cpu_interrupt)
BUILD_SMP_INTERRUPT(mtrr_interrupt)
BUILD_SMP_INTERRUPT(spurious_interrupt)
#endif

#if 0
int get_irq_list(char *buf)
{
	int i, j;
	struct irqaction * action;
	char *p = buf;

	p += sprintf(p, "           ");

	for (j=0; j<smp_num_cpus; j++)
		p += sprintf(p, "CPU%d       ",j);

	*p++ = '\n';

	for (i = 0 ; i < NR_IRQS ; i++)
	{
		if (ioinfo[i] == INVALID_STORAGE_AREA)
			continue;

		action = ioinfo[i]->irq_desc.action;
		
  		if (!action)
			continue;

		p += sprintf(p, "%3d: ",i);
#ifndef CONFIG_SMP
		p += sprintf(p, "%10u ", kstat_irqs(i));
#else
		for (j=0; j<smp_num_cpus; j++)
			p += sprintf( p, "%10u ",
			              kstat.irqs[cpu_logical_map(j)][i]);
#endif
		p += sprintf(p, " %14s", ioinfo[i]->irq_desc.handler->typename);
		p += sprintf(p, "  %s", action->name);

		for (action=action->next; action; action = action->next)
		{
			p += sprintf(p, ", %s", action->name);

		} /* endfor */

		*p++ = '\n';
	
	} /* endfor */

	p += sprintf(p, "NMI: %10u\n", nmi_counter);
#ifdef CONFIG_SMP
	p += sprintf(p, "IPI: %10u\n", atomic_read(&ipi_count));
#endif

	return p - buf;
}
#endif

/*
 * Global interrupt locks for SMP. Allow interrupts to come in on any
 * CPU, yet make cli/sti act globally to protect critical regions..
 */
#ifdef CONFIG_SMP
atomic_t global_irq_holder = ATOMIC_INIT(NO_PROC_ID);
atomic_t global_irq_lock;
atomic_t global_irq_count = ATOMIC_INIT(0);
atomic_t global_bh_count;

/*
 * "global_cli()" is a special case, in that it can hold the
 * interrupts disabled for a longish time, and also because
 * we may be doing TLB invalidates when holding the global
 * IRQ lock for historical reasons. Thus we may need to check
 * SMP invalidate events specially by hand here (but not in
 * any normal spinlocks)
 *
 * Thankfully we don't need this as we can deliver flush tlbs with
 * interrupts disabled DJB :-)
 */
#define check_smp_invalidate(cpu)

static void show(char * str)
{
	int i;
	unsigned long *stack;
	int cpu = smp_processor_id();

	printk("\n%s, CPU %d:\n", str, cpu);
	printk("irq:  %d [%d]\n",
	       atomic_read(&global_irq_count),local_irq_count(smp_processor_id()));
	printk("bh:   %d [%d]\n",
	       atomic_read(&global_bh_count),local_bh_count(smp_processor_id()));
	stack = (unsigned long *) &str;
	for (i = 40; i ; i--) {
		unsigned long x = *++stack;
		if (x > (unsigned long) &init_task_union && x < (unsigned long) &vsprintf) {
			printk("<[%08lx]> ", x);
		}
	}
}

#define MAXCOUNT 100000000

static inline void wait_on_bh(void)
{
	int count = MAXCOUNT;
	do {
		if (!--count) {
			show("wait_on_bh");
			count = ~0;
		}
		/* nothing .. wait for the other bh's to go away */
	} while (atomic_read(&global_bh_count) != 0);
}

static inline void wait_on_irq(int cpu)
{
	int count = MAXCOUNT;

	for (;;) {

		/*
		 * Wait until all interrupts are gone. Wait
		 * for bottom half handlers unless we're
		 * already executing in one..
		 */
		if (!atomic_read(&global_irq_count)) {
			if (local_bh_count(cpu)||
			    !atomic_read(&global_bh_count))
				break;
		}

		/* Duh, we have to loop. Release the lock to avoid deadlocks */
		clear_bit(0,&global_irq_lock);

		for (;;) {
			if (!--count) {
				show("wait_on_irq");
				count = ~0;
			}
			__sti();
			SYNC_OTHER_CORES(cpu);
			__cli();
			check_smp_invalidate(cpu);
			if (atomic_read(&global_irq_count))
				continue;
			if (atomic_read(&global_irq_lock))
				continue;
			if (!local_bh_count(cpu)
			    && atomic_read(&global_bh_count))
				continue;
			/* this works even though global_irq_lock not
                           a long, but is arch-specific --RR */
			if (!test_and_set_bit(0,&global_irq_lock))
				break;
		}
	}
}

/*
 * This is called when we want to synchronize with
 * bottom half handlers. We need to wait until
 * no other CPU is executing any bottom half handler.
 *
 * Don't wait if we're already running in an interrupt
 * context or are inside a bh handler.
 */
void synchronize_bh(void)
{
	if (atomic_read(&global_bh_count) && !in_interrupt())
		wait_on_bh();
}

/*
 * This is called when we want to synchronize with
 * interrupts. We may for example tell a device to
 * stop sending interrupts: but to make sure there
 * are no interrupts that are executing on another
 * CPU we need to call this function.
 */
void synchronize_irq(void)
{
	if (atomic_read(&global_irq_count)) {
		/* Stupid approach */
		cli();
		sti();
	}
}

static inline void get_irqlock(int cpu)
{
	/* this works even though global_irq_lock not a long, but is
	   arch-specific --RR */
	if (test_and_set_bit(0,&global_irq_lock)) {
		/* do we already hold the lock? */
		if ( cpu == atomic_read(&global_irq_holder))
			return;
		/* Uhhuh.. Somebody else got it. Wait.. */
		do {
			do {
				check_smp_invalidate(cpu);
			} while (test_bit(0,&global_irq_lock));
		} while (test_and_set_bit(0,&global_irq_lock));
	}
	/*
	 * We also to make sure that nobody else is running
	 * in an interrupt context.
	 */
	wait_on_irq(cpu);

	/*
	 * Ok, finally..
	 */
	atomic_set(&global_irq_holder,cpu);
}

#define EFLAGS_I_SHIFT 25

/*
 * A global "cli()" while in an interrupt context
 * turns into just a local cli(). Interrupts
 * should use spinlocks for the (very unlikely)
 * case that they ever want to protect against
 * each other.
 *
 * If we already have local interrupts disabled,
 * this will not turn a local disable into a
 * global one (problems with spinlocks: this makes
 * save_flags+cli+sti usable inside a spinlock).
 */
void __global_cli(void)
{
	unsigned int flags;

	__save_flags(flags);
	if (flags & (1 << EFLAGS_I_SHIFT)) {
		int cpu = smp_processor_id();
		__cli();
		if (!in_irq())
			get_irqlock(cpu);
	}
}

void __global_sti(void)
{

	if (!in_irq())
		release_irqlock(smp_processor_id());
	__sti();
}

/*
 * SMP flags value to restore to:
 * 0 - global cli
 * 1 - global sti
 * 2 - local cli
 * 3 - local sti
 */
unsigned long __global_save_flags(void)
{
	int retval;
	int local_enabled;
	unsigned long flags;

	__save_flags(flags);
	local_enabled = (flags >> EFLAGS_I_SHIFT) & 1;
	/* default to local */
	retval = 2 + local_enabled;

	/* check for global flags if we're not in an interrupt */
	if (!in_irq())
	{
		if (local_enabled)
			retval = 1;
		if (atomic_read(&global_irq_holder)== smp_processor_id())
			retval = 0;
	}
	return retval;
}

void __global_restore_flags(unsigned long flags)
{
	switch (flags) {
	case 0:
		__global_cli();
		break;
	case 1:
		__global_sti();
		break;
	case 2:
		__cli();
		break;
	case 3:
		__sti();
		break;
	default:
		printk("global_restore_flags: %08lx (%08lx)\n",
		       flags, (&flags)[-1]);
	}
}

#endif

/*
 * Note : This fuction should be eliminated as it doesn't comply with the
 *         S/390 irq scheme we have implemented ...
 */
int handle_IRQ_event( unsigned int irq, int cpu, struct pt_regs * regs)
{
	struct irqaction * action;
	int                status;

	status = 0;

	if ( ioinfo[irq] == INVALID_STORAGE_AREA )
		return( -ENODEV);

	action = ioinfo[irq]->irq_desc.action;

	if (action)
	{
		status |= 1;

		if (!(action->flags & SA_INTERRUPT))
			__sti();

		do
		{
			status |= action->flags;
			action->handler(irq, action->dev_id, regs);
			action = action->next;
		} while (action);

		if (status & SA_SAMPLE_RANDOM)
			add_interrupt_randomness(irq);
		__cli();

	} /* endif */

	return status;
}

void enable_nop(int irq)
{
}

void __init init_IRQ(void)
{
        s390_init_IRQ();
}


void free_irq(unsigned int irq, void *dev_id)
{
   s390_free_irq( irq, dev_id);
}


int request_irq( unsigned int   irq,
                 void           (*handler)(int, void *, struct pt_regs *),
                 unsigned long  irqflags,
                 const char    *devname,
                 void          *dev_id)
{
   return( s390_request_irq( irq, handler, irqflags, devname, dev_id ) );

}

void init_irq_proc(void)
{
        /* For now, nothing... */
}