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 | ||
1f44a225 MS |
90 | __this_cpu_write(s390_idle.nohz_delay, 1); |
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 | /** | |
189 | * airq_iv_alloc_bit - allocate an irq bit from an interrupt vector | |
190 | * @iv: pointer to an interrupt vector structure | |
191 | * | |
192 | * Returns the bit number of the allocated irq, or -1UL if no bit | |
193 | * is available or the AIRQ_IV_ALLOC flag has not been specified | |
194 | */ | |
195 | unsigned long airq_iv_alloc_bit(struct airq_iv *iv) | |
196 | { | |
a9a6f034 MS |
197 | unsigned long bit; |
198 | ||
199 | if (!iv->avail) | |
200 | return -1UL; | |
201 | spin_lock(&iv->lock); | |
7d7c7b24 | 202 | bit = find_first_bit_inv(iv->avail, iv->bits); |
a9a6f034 | 203 | if (bit < iv->bits) { |
7d7c7b24 | 204 | clear_bit_inv(bit, iv->avail); |
a9a6f034 MS |
205 | if (bit >= iv->end) |
206 | iv->end = bit + 1; | |
207 | } else | |
208 | bit = -1UL; | |
209 | spin_unlock(&iv->lock); | |
210 | return bit; | |
211 | ||
212 | } | |
213 | EXPORT_SYMBOL(airq_iv_alloc_bit); | |
214 | ||
215 | /** | |
216 | * airq_iv_free_bit - free an irq bit of an interrupt vector | |
217 | * @iv: pointer to interrupt vector structure | |
218 | * @bit: number of the irq bit to free | |
219 | */ | |
220 | void airq_iv_free_bit(struct airq_iv *iv, unsigned long bit) | |
221 | { | |
a9a6f034 MS |
222 | if (!iv->avail) |
223 | return; | |
224 | spin_lock(&iv->lock); | |
225 | /* Clear (possibly left over) interrupt bit */ | |
7d7c7b24 | 226 | clear_bit_inv(bit, iv->vector); |
a9a6f034 | 227 | /* Make the bit position available again */ |
7d7c7b24 | 228 | set_bit_inv(bit, iv->avail); |
a9a6f034 MS |
229 | if (bit == iv->end - 1) { |
230 | /* Find new end of bit-field */ | |
231 | while (--iv->end > 0) | |
7d7c7b24 | 232 | if (!test_bit_inv(iv->end - 1, iv->avail)) |
a9a6f034 MS |
233 | break; |
234 | } | |
235 | spin_unlock(&iv->lock); | |
236 | } | |
237 | EXPORT_SYMBOL(airq_iv_free_bit); | |
238 | ||
239 | /** | |
240 | * airq_iv_scan - scan interrupt vector for non-zero bits | |
241 | * @iv: pointer to interrupt vector structure | |
242 | * @start: bit number to start the search | |
243 | * @end: bit number to end the search | |
244 | * | |
245 | * Returns the bit number of the next non-zero interrupt bit, or | |
246 | * -1UL if the scan completed without finding any more any non-zero bits. | |
247 | */ | |
248 | unsigned long airq_iv_scan(struct airq_iv *iv, unsigned long start, | |
249 | unsigned long end) | |
250 | { | |
a9a6f034 MS |
251 | unsigned long bit; |
252 | ||
253 | /* Find non-zero bit starting from 'ivs->next'. */ | |
7d7c7b24 | 254 | bit = find_next_bit_inv(iv->vector, end, start); |
a9a6f034 MS |
255 | if (bit >= end) |
256 | return -1UL; | |
7d7c7b24 | 257 | clear_bit_inv(bit, iv->vector); |
a9a6f034 MS |
258 | return bit; |
259 | } | |
260 | EXPORT_SYMBOL(airq_iv_scan); |