Commit | Line | Data |
---|---|---|
1da177e4 | 1 | /* |
4e8e56c6 | 2 | * Support for adapter interruptions |
1da177e4 | 3 | * |
a53c8fab | 4 | * Copyright IBM Corp. 1999, 2007 |
4e8e56c6 PO |
5 | * Author(s): Ingo Adlung <adlung@de.ibm.com> |
6 | * Cornelia Huck <cornelia.huck@de.ibm.com> | |
7 | * Arnd Bergmann <arndb@de.ibm.com> | |
8 | * Peter Oberparleiter <peter.oberparleiter@de.ibm.com> | |
1da177e4 LT |
9 | */ |
10 | ||
11 | #include <linux/init.h> | |
f4eae94f MS |
12 | #include <linux/irq.h> |
13 | #include <linux/kernel_stat.h> | |
1da177e4 | 14 | #include <linux/module.h> |
f4eae94f MS |
15 | #include <linux/mutex.h> |
16 | #include <linux/rculist.h> | |
1da177e4 | 17 | #include <linux/slab.h> |
1da177e4 | 18 | |
4e8e56c6 | 19 | #include <asm/airq.h> |
da7c5af8 | 20 | #include <asm/isc.h> |
4e8e56c6 PO |
21 | |
22 | #include "cio.h" | |
1da177e4 | 23 | #include "cio_debug.h" |
f4eae94f | 24 | #include "ioasm.h" |
1da177e4 | 25 | |
f4eae94f MS |
26 | static DEFINE_SPINLOCK(airq_lists_lock); |
27 | static struct hlist_head airq_lists[MAX_ISC+1]; | |
1da177e4 | 28 | |
4e8e56c6 | 29 | /** |
f4eae94f MS |
30 | * register_adapter_interrupt() - register adapter interrupt handler |
31 | * @airq: pointer to adapter interrupt descriptor | |
4e8e56c6 | 32 | * |
f4eae94f | 33 | * Returns 0 on success, or -EINVAL. |
4e8e56c6 | 34 | */ |
f4eae94f | 35 | int register_adapter_interrupt(struct airq_struct *airq) |
1da177e4 | 36 | { |
f4eae94f MS |
37 | char dbf_txt[32]; |
38 | ||
39 | if (!airq->handler || airq->isc > MAX_ISC) | |
40 | return -EINVAL; | |
41 | if (!airq->lsi_ptr) { | |
42 | airq->lsi_ptr = kzalloc(1, GFP_KERNEL); | |
43 | if (!airq->lsi_ptr) | |
44 | return -ENOMEM; | |
45 | airq->flags |= AIRQ_PTR_ALLOCATED; | |
1da177e4 | 46 | } |
f4eae94f MS |
47 | if (!airq->lsi_mask) |
48 | airq->lsi_mask = 0xff; | |
49 | snprintf(dbf_txt, sizeof(dbf_txt), "rairq:%p", airq); | |
4e8e56c6 | 50 | CIO_TRACE_EVENT(4, dbf_txt); |
f4eae94f MS |
51 | isc_register(airq->isc); |
52 | spin_lock(&airq_lists_lock); | |
53 | hlist_add_head_rcu(&airq->list, &airq_lists[airq->isc]); | |
54 | spin_unlock(&airq_lists_lock); | |
55 | return 0; | |
1da177e4 | 56 | } |
f4eae94f | 57 | EXPORT_SYMBOL(register_adapter_interrupt); |
1da177e4 | 58 | |
4e8e56c6 | 59 | /** |
f4eae94f MS |
60 | * unregister_adapter_interrupt - unregister adapter interrupt handler |
61 | * @airq: pointer to adapter interrupt descriptor | |
4e8e56c6 | 62 | */ |
f4eae94f | 63 | void unregister_adapter_interrupt(struct airq_struct *airq) |
1da177e4 | 64 | { |
f4eae94f | 65 | char dbf_txt[32]; |
1da177e4 | 66 | |
f4eae94f MS |
67 | if (hlist_unhashed(&airq->list)) |
68 | return; | |
69 | snprintf(dbf_txt, sizeof(dbf_txt), "urairq:%p", airq); | |
4e8e56c6 | 70 | CIO_TRACE_EVENT(4, dbf_txt); |
f4eae94f MS |
71 | spin_lock(&airq_lists_lock); |
72 | hlist_del_rcu(&airq->list); | |
73 | spin_unlock(&airq_lists_lock); | |
74 | synchronize_rcu(); | |
75 | isc_unregister(airq->isc); | |
76 | if (airq->flags & AIRQ_PTR_ALLOCATED) { | |
77 | kfree(airq->lsi_ptr); | |
78 | airq->lsi_ptr = NULL; | |
79 | airq->flags &= ~AIRQ_PTR_ALLOCATED; | |
80 | } | |
1da177e4 | 81 | } |
f4eae94f | 82 | EXPORT_SYMBOL(unregister_adapter_interrupt); |
1da177e4 | 83 | |
1f44a225 | 84 | static irqreturn_t do_airq_interrupt(int irq, void *dummy) |
4e8e56c6 | 85 | { |
1f44a225 | 86 | struct tpi_info *tpi_info; |
f4eae94f MS |
87 | struct airq_struct *airq; |
88 | struct hlist_head *head; | |
89 | ||
fe0f4976 | 90 | set_cpu_flag(CIF_NOHZ_DELAY); |
1f44a225 MS |
91 | tpi_info = (struct tpi_info *) &get_irq_regs()->int_code; |
92 | head = &airq_lists[tpi_info->isc]; | |
f4eae94f MS |
93 | rcu_read_lock(); |
94 | hlist_for_each_entry_rcu(airq, head, list) | |
95 | if ((*airq->lsi_ptr & airq->lsi_mask) != 0) | |
96 | airq->handler(airq); | |
97 | rcu_read_unlock(); | |
1f44a225 MS |
98 | |
99 | return IRQ_HANDLED; | |
100 | } | |
101 | ||
102 | static struct irqaction airq_interrupt = { | |
103 | .name = "AIO", | |
104 | .handler = do_airq_interrupt, | |
105 | }; | |
106 | ||
107 | void __init init_airq_interrupts(void) | |
108 | { | |
109 | irq_set_chip_and_handler(THIN_INTERRUPT, | |
110 | &dummy_irq_chip, handle_percpu_irq); | |
111 | setup_irq(THIN_INTERRUPT, &airq_interrupt); | |
4e8e56c6 | 112 | } |
a9a6f034 MS |
113 | |
114 | /** | |
115 | * airq_iv_create - create an interrupt vector | |
116 | * @bits: number of bits in the interrupt vector | |
117 | * @flags: allocation flags | |
118 | * | |
119 | * Returns a pointer to an interrupt vector structure | |
120 | */ | |
121 | struct airq_iv *airq_iv_create(unsigned long bits, unsigned long flags) | |
122 | { | |
123 | struct airq_iv *iv; | |
124 | unsigned long size; | |
125 | ||
126 | iv = kzalloc(sizeof(*iv), GFP_KERNEL); | |
127 | if (!iv) | |
128 | goto out; | |
129 | iv->bits = bits; | |
130 | size = BITS_TO_LONGS(bits) * sizeof(unsigned long); | |
131 | iv->vector = kzalloc(size, GFP_KERNEL); | |
132 | if (!iv->vector) | |
133 | goto out_free; | |
134 | if (flags & AIRQ_IV_ALLOC) { | |
135 | iv->avail = kmalloc(size, GFP_KERNEL); | |
136 | if (!iv->avail) | |
137 | goto out_free; | |
138 | memset(iv->avail, 0xff, size); | |
139 | iv->end = 0; | |
140 | } else | |
141 | iv->end = bits; | |
142 | if (flags & AIRQ_IV_BITLOCK) { | |
143 | iv->bitlock = kzalloc(size, GFP_KERNEL); | |
144 | if (!iv->bitlock) | |
145 | goto out_free; | |
146 | } | |
147 | if (flags & AIRQ_IV_PTR) { | |
148 | size = bits * sizeof(unsigned long); | |
149 | iv->ptr = kzalloc(size, GFP_KERNEL); | |
150 | if (!iv->ptr) | |
151 | goto out_free; | |
152 | } | |
153 | if (flags & AIRQ_IV_DATA) { | |
154 | size = bits * sizeof(unsigned int); | |
155 | iv->data = kzalloc(size, GFP_KERNEL); | |
156 | if (!iv->data) | |
157 | goto out_free; | |
158 | } | |
159 | spin_lock_init(&iv->lock); | |
160 | return iv; | |
161 | ||
162 | out_free: | |
163 | kfree(iv->ptr); | |
164 | kfree(iv->bitlock); | |
165 | kfree(iv->avail); | |
166 | kfree(iv->vector); | |
167 | kfree(iv); | |
168 | out: | |
169 | return NULL; | |
170 | } | |
171 | EXPORT_SYMBOL(airq_iv_create); | |
172 | ||
173 | /** | |
174 | * airq_iv_release - release an interrupt vector | |
175 | * @iv: pointer to interrupt vector structure | |
176 | */ | |
177 | void airq_iv_release(struct airq_iv *iv) | |
178 | { | |
179 | kfree(iv->data); | |
180 | kfree(iv->ptr); | |
181 | kfree(iv->bitlock); | |
182 | kfree(iv->vector); | |
183 | kfree(iv->avail); | |
184 | kfree(iv); | |
185 | } | |
186 | EXPORT_SYMBOL(airq_iv_release); | |
187 | ||
188 | /** | |
fe7c30a4 | 189 | * airq_iv_alloc - allocate irq bits from an interrupt vector |
a9a6f034 | 190 | * @iv: pointer to an interrupt vector structure |
fe7c30a4 | 191 | * @num: number of consecutive irq bits to allocate |
a9a6f034 | 192 | * |
fe7c30a4 MS |
193 | * Returns the bit number of the first irq in the allocated block of irqs, |
194 | * or -1UL if no bit is available or the AIRQ_IV_ALLOC flag has not been | |
195 | * specified | |
a9a6f034 | 196 | */ |
fe7c30a4 | 197 | unsigned long airq_iv_alloc(struct airq_iv *iv, unsigned long num) |
a9a6f034 | 198 | { |
0eb69a0c | 199 | unsigned long bit, i, flags; |
a9a6f034 | 200 | |
fe7c30a4 | 201 | if (!iv->avail || num == 0) |
a9a6f034 | 202 | return -1UL; |
0eb69a0c | 203 | spin_lock_irqsave(&iv->lock, flags); |
7d7c7b24 | 204 | bit = find_first_bit_inv(iv->avail, iv->bits); |
fe7c30a4 MS |
205 | while (bit + num <= iv->bits) { |
206 | for (i = 1; i < num; i++) | |
207 | if (!test_bit_inv(bit + i, iv->avail)) | |
208 | break; | |
209 | if (i >= num) { | |
210 | /* Found a suitable block of irqs */ | |
211 | for (i = 0; i < num; i++) | |
212 | clear_bit_inv(bit + i, iv->avail); | |
213 | if (bit + num >= iv->end) | |
214 | iv->end = bit + num + 1; | |
215 | break; | |
216 | } | |
217 | bit = find_next_bit_inv(iv->avail, iv->bits, bit + i + 1); | |
218 | } | |
219 | if (bit + num > iv->bits) | |
a9a6f034 | 220 | bit = -1UL; |
0eb69a0c | 221 | spin_unlock_irqrestore(&iv->lock, flags); |
a9a6f034 | 222 | return bit; |
a9a6f034 | 223 | } |
fe7c30a4 | 224 | EXPORT_SYMBOL(airq_iv_alloc); |
a9a6f034 MS |
225 | |
226 | /** | |
fe7c30a4 | 227 | * airq_iv_free - free irq bits of an interrupt vector |
a9a6f034 | 228 | * @iv: pointer to interrupt vector structure |
fe7c30a4 MS |
229 | * @bit: number of the first irq bit to free |
230 | * @num: number of consecutive irq bits to free | |
a9a6f034 | 231 | */ |
fe7c30a4 | 232 | void airq_iv_free(struct airq_iv *iv, unsigned long bit, unsigned long num) |
a9a6f034 | 233 | { |
0eb69a0c | 234 | unsigned long i, flags; |
fe7c30a4 MS |
235 | |
236 | if (!iv->avail || num == 0) | |
a9a6f034 | 237 | return; |
0eb69a0c | 238 | spin_lock_irqsave(&iv->lock, flags); |
fe7c30a4 MS |
239 | for (i = 0; i < num; i++) { |
240 | /* Clear (possibly left over) interrupt bit */ | |
241 | clear_bit_inv(bit + i, iv->vector); | |
242 | /* Make the bit positions available again */ | |
243 | set_bit_inv(bit + i, iv->avail); | |
244 | } | |
245 | if (bit + num >= iv->end) { | |
a9a6f034 | 246 | /* Find new end of bit-field */ |
fe7c30a4 MS |
247 | while (iv->end > 0 && !test_bit_inv(iv->end - 1, iv->avail)) |
248 | iv->end--; | |
a9a6f034 | 249 | } |
0eb69a0c | 250 | spin_unlock_irqrestore(&iv->lock, flags); |
a9a6f034 | 251 | } |
fe7c30a4 | 252 | EXPORT_SYMBOL(airq_iv_free); |
a9a6f034 MS |
253 | |
254 | /** | |
255 | * airq_iv_scan - scan interrupt vector for non-zero bits | |
256 | * @iv: pointer to interrupt vector structure | |
257 | * @start: bit number to start the search | |
258 | * @end: bit number to end the search | |
259 | * | |
260 | * Returns the bit number of the next non-zero interrupt bit, or | |
261 | * -1UL if the scan completed without finding any more any non-zero bits. | |
262 | */ | |
263 | unsigned long airq_iv_scan(struct airq_iv *iv, unsigned long start, | |
264 | unsigned long end) | |
265 | { | |
a9a6f034 MS |
266 | unsigned long bit; |
267 | ||
268 | /* Find non-zero bit starting from 'ivs->next'. */ | |
7d7c7b24 | 269 | bit = find_next_bit_inv(iv->vector, end, start); |
a9a6f034 MS |
270 | if (bit >= end) |
271 | return -1UL; | |
7d7c7b24 | 272 | clear_bit_inv(bit, iv->vector); |
a9a6f034 MS |
273 | return bit; |
274 | } | |
275 | EXPORT_SYMBOL(airq_iv_scan); |