autoprobe.c 4.56 KB
Newer Older
1
// SPDX-License-Identifier: GPL-2.0
Linus Torvalds's avatar
Linus Torvalds committed
2
3
4
5
6
7
8
9
10
11
12
/*
 * linux/kernel/irq/autoprobe.c
 *
 * Copyright (C) 1992, 1998-2004 Linus Torvalds, Ingo Molnar
 *
 * This file contains the interrupt probing code and driver APIs.
 */

#include <linux/irq.h>
#include <linux/module.h>
#include <linux/interrupt.h>
13
#include <linux/delay.h>
14
#include <linux/async.h>
Linus Torvalds's avatar
Linus Torvalds committed
15

16
17
#include "internals.h"

Linus Torvalds's avatar
Linus Torvalds committed
18
19
20
/*
 * Autodetection depends on the fact that any interrupt that
 * comes in on to an unassigned handler will get stuck with
21
 * "IRQS_WAITING" cleared and the interrupt disabled.
Linus Torvalds's avatar
Linus Torvalds committed
22
 */
23
static DEFINE_MUTEX(probing_active);
Linus Torvalds's avatar
Linus Torvalds committed
24
25
26
27
28
29
30
31
32
33

/**
 *	probe_irq_on	- begin an interrupt autodetect
 *
 *	Commence probing for an interrupt. The interrupts are scanned
 *	and a mask of potential interrupt lines is returned.
 *
 */
unsigned long probe_irq_on(void)
{
34
	struct irq_desc *desc;
35
36
	unsigned long mask = 0;
	int i;
Linus Torvalds's avatar
Linus Torvalds committed
37

38
39
40
41
	/*
	 * quiesce the kernel, or at least the asynchronous portion
	 */
	async_synchronize_full();
42
	mutex_lock(&probing_active);
Linus Torvalds's avatar
Linus Torvalds committed
43
44
45
46
	/*
	 * something may have generated an irq long ago and we want to
	 * flush such a longstanding irq before considering it as spurious.
	 */
47
	for_each_irq_desc_reverse(i, desc) {
48
		raw_spin_lock_irq(&desc->lock);
49
		if (!desc->action && irq_settings_can_probe(desc)) {
Thomas Gleixner's avatar
Thomas Gleixner committed
50
51
52
53
			/*
			 * Some chips need to know about probing in
			 * progress:
			 */
54
55
56
			if (desc->irq_data.chip->irq_set_type)
				desc->irq_data.chip->irq_set_type(&desc->irq_data,
							 IRQ_TYPE_PROBE);
57
			irq_activate_and_startup(desc, IRQ_NORESEND);
Thomas Gleixner's avatar
Thomas Gleixner committed
58
		}
59
		raw_spin_unlock_irq(&desc->lock);
Linus Torvalds's avatar
Linus Torvalds committed
60
61
62
	}

	/* Wait for longstanding interrupts to trigger. */
63
	msleep(20);
Linus Torvalds's avatar
Linus Torvalds committed
64
65
66
67
68
69

	/*
	 * enable any unassigned irqs
	 * (we must startup again here because if a longstanding irq
	 * happened in the previous stage, it may have masked itself)
	 */
70
	for_each_irq_desc_reverse(i, desc) {
71
		raw_spin_lock_irq(&desc->lock);
72
		if (!desc->action && irq_settings_can_probe(desc)) {
73
			desc->istate |= IRQS_AUTODETECT | IRQS_WAITING;
74
			if (irq_activate_and_startup(desc, IRQ_NORESEND))
75
				desc->istate |= IRQS_PENDING;
Linus Torvalds's avatar
Linus Torvalds committed
76
		}
77
		raw_spin_unlock_irq(&desc->lock);
Linus Torvalds's avatar
Linus Torvalds committed
78
79
80
81
82
	}

	/*
	 * Wait for spurious interrupts to trigger
	 */
83
	msleep(100);
Linus Torvalds's avatar
Linus Torvalds committed
84
85
86
87

	/*
	 * Now filter out any obviously spurious interrupts
	 */
88
	for_each_irq_desc(i, desc) {
89
		raw_spin_lock_irq(&desc->lock);
Linus Torvalds's avatar
Linus Torvalds committed
90

91
		if (desc->istate & IRQS_AUTODETECT) {
Linus Torvalds's avatar
Linus Torvalds committed
92
			/* It triggered already - consider it spurious. */
93
			if (!(desc->istate & IRQS_WAITING)) {
94
				desc->istate &= ~IRQS_AUTODETECT;
95
				irq_shutdown(desc);
Linus Torvalds's avatar
Linus Torvalds committed
96
97
			} else
				if (i < 32)
98
					mask |= 1 << i;
Linus Torvalds's avatar
Linus Torvalds committed
99
		}
100
		raw_spin_unlock_irq(&desc->lock);
Linus Torvalds's avatar
Linus Torvalds committed
101
102
	}

103
	return mask;
Linus Torvalds's avatar
Linus Torvalds committed
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
}
EXPORT_SYMBOL(probe_irq_on);

/**
 *	probe_irq_mask - scan a bitmap of interrupt lines
 *	@val:	mask of interrupts to consider
 *
 *	Scan the interrupt lines and return a bitmap of active
 *	autodetect interrupts. The interrupt probe logic state
 *	is then returned to its previous value.
 *
 *	Note: we need to scan all the irq's even though we will
 *	only return autodetect irq numbers - just so that we reset
 *	them all to a known state.
 */
unsigned int probe_irq_mask(unsigned long val)
{
121
	unsigned int mask = 0;
122
	struct irq_desc *desc;
Linus Torvalds's avatar
Linus Torvalds committed
123
124
	int i;

125
	for_each_irq_desc(i, desc) {
126
		raw_spin_lock_irq(&desc->lock);
127
		if (desc->istate & IRQS_AUTODETECT) {
128
			if (i < 16 && !(desc->istate & IRQS_WAITING))
Linus Torvalds's avatar
Linus Torvalds committed
129
130
				mask |= 1 << i;

131
			desc->istate &= ~IRQS_AUTODETECT;
132
			irq_shutdown(desc);
Linus Torvalds's avatar
Linus Torvalds committed
133
		}
134
		raw_spin_unlock_irq(&desc->lock);
Linus Torvalds's avatar
Linus Torvalds committed
135
	}
136
	mutex_unlock(&probing_active);
Linus Torvalds's avatar
Linus Torvalds committed
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160

	return mask & val;
}
EXPORT_SYMBOL(probe_irq_mask);

/**
 *	probe_irq_off	- end an interrupt autodetect
 *	@val: mask of potential interrupts (unused)
 *
 *	Scans the unused interrupt lines and returns the line which
 *	appears to have triggered the interrupt. If no interrupt was
 *	found then zero is returned. If more than one interrupt is
 *	found then minus the first candidate is returned to indicate
 *	their is doubt.
 *
 *	The interrupt probe logic state is returned to its previous
 *	value.
 *
 *	BUGS: When used in a module (which arguably shouldn't happen)
 *	nothing prevents two IRQ probe callers from overlapping. The
 *	results of this are non-optimal.
 */
int probe_irq_off(unsigned long val)
{
161
	int i, irq_found = 0, nr_of_irqs = 0;
162
	struct irq_desc *desc;
Linus Torvalds's avatar
Linus Torvalds committed
163

164
	for_each_irq_desc(i, desc) {
165
		raw_spin_lock_irq(&desc->lock);
Linus Torvalds's avatar
Linus Torvalds committed
166

167
		if (desc->istate & IRQS_AUTODETECT) {
168
			if (!(desc->istate & IRQS_WAITING)) {
169
				if (!nr_of_irqs)
Linus Torvalds's avatar
Linus Torvalds committed
170
					irq_found = i;
171
				nr_of_irqs++;
Linus Torvalds's avatar
Linus Torvalds committed
172
			}
173
			desc->istate &= ~IRQS_AUTODETECT;
174
			irq_shutdown(desc);
Linus Torvalds's avatar
Linus Torvalds committed
175
		}
176
		raw_spin_unlock_irq(&desc->lock);
Linus Torvalds's avatar
Linus Torvalds committed
177
	}
178
	mutex_unlock(&probing_active);
Linus Torvalds's avatar
Linus Torvalds committed
179

180
	if (nr_of_irqs > 1)
Linus Torvalds's avatar
Linus Torvalds committed
181
		irq_found = -irq_found;
182

Linus Torvalds's avatar
Linus Torvalds committed
183
184
185
186
	return irq_found;
}
EXPORT_SYMBOL(probe_irq_off);