aboutsummaryrefslogtreecommitdiffstats
path: root/arch/s390
diff options
context:
space:
mode:
authorJan Glauber <jang@linux.vnet.ibm.com>2011-07-24 04:48:27 -0400
committerMartin Schwidefsky <schwidefsky@de.ibm.com>2011-07-24 04:48:22 -0400
commit89c9b66b104549a8698e412bf6f4140c1d0786fb (patch)
treed7c62f412b1e99b2ebb5d37e8f86539b7dc03d9d /arch/s390
parent9e280f6693083baf1b7741c2b820be8d4109509e (diff)
[S390] race safe external interrupt registration
The (un-)register_external_interrupt functions are not race safe if more than one interrupt handler is added or deleted for an external interrupt concurrently. Make the registration / unregistration of external interrupts race safe by using RCU and a spinlock. RCU is used to avoid a performance penalty in the external interrupt handler, the register and unregister functions are protected by the spinlock and are not performance critical. call_rcu must be used since the SCLP driver uses the interface with IRQs disabled. Also use the generic list implementation rather than homebrewn list code. Signed-off-by: Jan Glauber <jang@linux.vnet.ibm.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Diffstat (limited to 'arch/s390')
-rw-r--r--arch/s390/kernel/irq.c83
1 files changed, 46 insertions, 37 deletions
diff --git a/arch/s390/kernel/irq.c b/arch/s390/kernel/irq.c
index e3264f6a9720..1f4050d45f78 100644
--- a/arch/s390/kernel/irq.c
+++ b/arch/s390/kernel/irq.c
@@ -88,15 +88,6 @@ int show_interrupts(struct seq_file *p, void *v)
88} 88}
89 89
90/* 90/*
91 * For compatibilty only. S/390 specific setup of interrupts et al. is done
92 * much later in init_channel_subsystem().
93 */
94void __init init_IRQ(void)
95{
96 /* nothing... */
97}
98
99/*
100 * Switch to the asynchronous interrupt stack for softirq execution. 91 * Switch to the asynchronous interrupt stack for softirq execution.
101 */ 92 */
102asmlinkage void do_softirq(void) 93asmlinkage void do_softirq(void)
@@ -144,28 +135,45 @@ void init_irq_proc(void)
144#endif 135#endif
145 136
146/* 137/*
147 * ext_int_hash[index] is the start of the list for all external interrupts 138 * ext_int_hash[index] is the list head for all external interrupts that hash
148 * that hash to this index. With the current set of external interrupts 139 * to this index.
149 * (0x1202 external call, 0x1004 cpu timer, 0x2401 hwc console, 0x4000
150 * iucv and 0x2603 pfault) this is always the first element.
151 */ 140 */
141static struct list_head ext_int_hash[256];
152 142
153struct ext_int_info { 143struct ext_int_info {
154 struct ext_int_info *next;
155 ext_int_handler_t handler; 144 ext_int_handler_t handler;
156 u16 code; 145 u16 code;
146 struct list_head entry;
147 struct rcu_head rcu;
157}; 148};
158 149
159static struct ext_int_info *ext_int_hash[256]; 150/* ext_int_hash_lock protects the handler lists for external interrupts */
151DEFINE_SPINLOCK(ext_int_hash_lock);
152
153static void __init init_external_interrupts(void)
154{
155 int idx;
156
157 for (idx = 0; idx < ARRAY_SIZE(ext_int_hash); idx++)
158 INIT_LIST_HEAD(&ext_int_hash[idx]);
159}
160 160
161static inline int ext_hash(u16 code) 161static inline int ext_hash(u16 code)
162{ 162{
163 return (code + (code >> 9)) & 0xff; 163 return (code + (code >> 9)) & 0xff;
164} 164}
165 165
166static void ext_int_hash_update(struct rcu_head *head)
167{
168 struct ext_int_info *p = container_of(head, struct ext_int_info, rcu);
169
170 kfree(p);
171}
172
166int register_external_interrupt(u16 code, ext_int_handler_t handler) 173int register_external_interrupt(u16 code, ext_int_handler_t handler)
167{ 174{
168 struct ext_int_info *p; 175 struct ext_int_info *p;
176 unsigned long flags;
169 int index; 177 int index;
170 178
171 p = kmalloc(sizeof(*p), GFP_ATOMIC); 179 p = kmalloc(sizeof(*p), GFP_ATOMIC);
@@ -174,33 +182,27 @@ int register_external_interrupt(u16 code, ext_int_handler_t handler)
174 p->code = code; 182 p->code = code;
175 p->handler = handler; 183 p->handler = handler;
176 index = ext_hash(code); 184 index = ext_hash(code);
177 p->next = ext_int_hash[index]; 185
178 ext_int_hash[index] = p; 186 spin_lock_irqsave(&ext_int_hash_lock, flags);
187 list_add_rcu(&p->entry, &ext_int_hash[index]);
188 spin_unlock_irqrestore(&ext_int_hash_lock, flags);
179 return 0; 189 return 0;
180} 190}
181EXPORT_SYMBOL(register_external_interrupt); 191EXPORT_SYMBOL(register_external_interrupt);
182 192
183int unregister_external_interrupt(u16 code, ext_int_handler_t handler) 193int unregister_external_interrupt(u16 code, ext_int_handler_t handler)
184{ 194{
185 struct ext_int_info *p, *q; 195 struct ext_int_info *p;
186 int index; 196 unsigned long flags;
197 int index = ext_hash(code);
187 198
188 index = ext_hash(code); 199 spin_lock_irqsave(&ext_int_hash_lock, flags);
189 q = NULL; 200 list_for_each_entry_rcu(p, &ext_int_hash[index], entry)
190 p = ext_int_hash[index]; 201 if (p->code == code && p->handler == handler) {
191 while (p) { 202 list_del_rcu(&p->entry);
192 if (p->code == code && p->handler == handler) 203 call_rcu(&p->rcu, ext_int_hash_update);
193 break; 204 }
194 q = p; 205 spin_unlock_irqrestore(&ext_int_hash_lock, flags);
195 p = p->next;
196 }
197 if (!p)
198 return -ENOENT;
199 if (q)
200 q->next = p->next;
201 else
202 ext_int_hash[index] = p->next;
203 kfree(p);
204 return 0; 206 return 0;
205} 207}
206EXPORT_SYMBOL(unregister_external_interrupt); 208EXPORT_SYMBOL(unregister_external_interrupt);
@@ -224,15 +226,22 @@ void __irq_entry do_extint(struct pt_regs *regs, unsigned int ext_int_code,
224 kstat_cpu(smp_processor_id()).irqs[EXTERNAL_INTERRUPT]++; 226 kstat_cpu(smp_processor_id()).irqs[EXTERNAL_INTERRUPT]++;
225 if (code != 0x1004) 227 if (code != 0x1004)
226 __get_cpu_var(s390_idle).nohz_delay = 1; 228 __get_cpu_var(s390_idle).nohz_delay = 1;
229
227 index = ext_hash(code); 230 index = ext_hash(code);
228 for (p = ext_int_hash[index]; p; p = p->next) { 231 rcu_read_lock();
232 list_for_each_entry_rcu(p, &ext_int_hash[index], entry)
229 if (likely(p->code == code)) 233 if (likely(p->code == code))
230 p->handler(ext_int_code, param32, param64); 234 p->handler(ext_int_code, param32, param64);
231 } 235 rcu_read_unlock();
232 irq_exit(); 236 irq_exit();
233 set_irq_regs(old_regs); 237 set_irq_regs(old_regs);
234} 238}
235 239
240void __init init_IRQ(void)
241{
242 init_external_interrupts();
243}
244
236static DEFINE_SPINLOCK(sc_irq_lock); 245static DEFINE_SPINLOCK(sc_irq_lock);
237static int sc_irq_refcount; 246static int sc_irq_refcount;
238 247