diff options
Diffstat (limited to 'arch/m68k/amiga/amiints.c')
-rw-r--r-- | arch/m68k/amiga/amiints.c | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/arch/m68k/amiga/amiints.c b/arch/m68k/amiga/amiints.c new file mode 100644 index 000000000000..d9edf2d1a492 --- /dev/null +++ b/arch/m68k/amiga/amiints.c | |||
@@ -0,0 +1,520 @@ | |||
1 | /* | ||
2 | * linux/arch/m68k/amiga/amiints.c -- Amiga Linux interrupt handling code | ||
3 | * | ||
4 | * This file is subject to the terms and conditions of the GNU General Public | ||
5 | * License. See the file COPYING in the main directory of this archive | ||
6 | * for more details. | ||
7 | * | ||
8 | * 11/07/96: rewritten interrupt handling, irq lists are exists now only for | ||
9 | * this sources where it makes sense (VERTB/PORTS/EXTER) and you must | ||
10 | * be careful that dev_id for this sources is unique since this the | ||
11 | * only possibility to distinguish between different handlers for | ||
12 | * free_irq. irq lists also have different irq flags: | ||
13 | * - IRQ_FLG_FAST: handler is inserted at top of list (after other | ||
14 | * fast handlers) | ||
15 | * - IRQ_FLG_SLOW: handler is inserted at bottom of list and before | ||
16 | * they're executed irq level is set to the previous | ||
17 | * one, but handlers don't need to be reentrant, if | ||
18 | * reentrance occurred, slow handlers will be just | ||
19 | * called again. | ||
20 | * The whole interrupt handling for CIAs is moved to cia.c | ||
21 | * /Roman Zippel | ||
22 | * | ||
23 | * 07/08/99: rewamp of the interrupt handling - we now have two types of | ||
24 | * interrupts, normal and fast handlers, fast handlers being | ||
25 | * marked with SA_INTERRUPT and runs with all other interrupts | ||
26 | * disabled. Normal interrupts disable their own source but | ||
27 | * run with all other interrupt sources enabled. | ||
28 | * PORTS and EXTER interrupts are always shared even if the | ||
29 | * drivers do not explicitly mark this when calling | ||
30 | * request_irq which they really should do. | ||
31 | * This is similar to the way interrupts are handled on all | ||
32 | * other architectures and makes a ton of sense besides | ||
33 | * having the advantage of making it easier to share | ||
34 | * drivers. | ||
35 | * /Jes | ||
36 | */ | ||
37 | |||
38 | #include <linux/types.h> | ||
39 | #include <linux/kernel.h> | ||
40 | #include <linux/sched.h> | ||
41 | #include <linux/kernel_stat.h> | ||
42 | #include <linux/init.h> | ||
43 | #include <linux/errno.h> | ||
44 | #include <linux/seq_file.h> | ||
45 | |||
46 | #include <asm/system.h> | ||
47 | #include <asm/irq.h> | ||
48 | #include <asm/traps.h> | ||
49 | #include <asm/amigahw.h> | ||
50 | #include <asm/amigaints.h> | ||
51 | #include <asm/amipcmcia.h> | ||
52 | |||
53 | extern int cia_request_irq(struct ciabase *base,int irq, | ||
54 | irqreturn_t (*handler)(int, void *, struct pt_regs *), | ||
55 | unsigned long flags, const char *devname, void *dev_id); | ||
56 | extern void cia_free_irq(struct ciabase *base, unsigned int irq, void *dev_id); | ||
57 | extern void cia_init_IRQ(struct ciabase *base); | ||
58 | extern int cia_get_irq_list(struct ciabase *base, struct seq_file *p); | ||
59 | |||
60 | /* irq node variables for amiga interrupt sources */ | ||
61 | static irq_node_t *ami_irq_list[AMI_STD_IRQS]; | ||
62 | |||
63 | static unsigned short amiga_intena_vals[AMI_STD_IRQS] = { | ||
64 | [IRQ_AMIGA_VERTB] = IF_VERTB, | ||
65 | [IRQ_AMIGA_COPPER] = IF_COPER, | ||
66 | [IRQ_AMIGA_AUD0] = IF_AUD0, | ||
67 | [IRQ_AMIGA_AUD1] = IF_AUD1, | ||
68 | [IRQ_AMIGA_AUD2] = IF_AUD2, | ||
69 | [IRQ_AMIGA_AUD3] = IF_AUD3, | ||
70 | [IRQ_AMIGA_BLIT] = IF_BLIT, | ||
71 | [IRQ_AMIGA_DSKSYN] = IF_DSKSYN, | ||
72 | [IRQ_AMIGA_DSKBLK] = IF_DSKBLK, | ||
73 | [IRQ_AMIGA_RBF] = IF_RBF, | ||
74 | [IRQ_AMIGA_TBE] = IF_TBE, | ||
75 | [IRQ_AMIGA_SOFT] = IF_SOFT, | ||
76 | [IRQ_AMIGA_PORTS] = IF_PORTS, | ||
77 | [IRQ_AMIGA_EXTER] = IF_EXTER | ||
78 | }; | ||
79 | static const unsigned char ami_servers[AMI_STD_IRQS] = { | ||
80 | [IRQ_AMIGA_VERTB] = 1, | ||
81 | [IRQ_AMIGA_PORTS] = 1, | ||
82 | [IRQ_AMIGA_EXTER] = 1 | ||
83 | }; | ||
84 | |||
85 | static short ami_ablecount[AMI_IRQS]; | ||
86 | |||
87 | static irqreturn_t ami_badint(int irq, void *dev_id, struct pt_regs *fp) | ||
88 | { | ||
89 | num_spurious += 1; | ||
90 | return IRQ_NONE; | ||
91 | } | ||
92 | |||
93 | /* | ||
94 | * void amiga_init_IRQ(void) | ||
95 | * | ||
96 | * Parameters: None | ||
97 | * | ||
98 | * Returns: Nothing | ||
99 | * | ||
100 | * This function should be called during kernel startup to initialize | ||
101 | * the amiga IRQ handling routines. | ||
102 | */ | ||
103 | |||
104 | void __init amiga_init_IRQ(void) | ||
105 | { | ||
106 | int i; | ||
107 | |||
108 | /* initialize handlers */ | ||
109 | for (i = 0; i < AMI_STD_IRQS; i++) { | ||
110 | if (ami_servers[i]) { | ||
111 | ami_irq_list[i] = NULL; | ||
112 | } else { | ||
113 | ami_irq_list[i] = new_irq_node(); | ||
114 | ami_irq_list[i]->handler = ami_badint; | ||
115 | ami_irq_list[i]->flags = 0; | ||
116 | ami_irq_list[i]->dev_id = NULL; | ||
117 | ami_irq_list[i]->devname = NULL; | ||
118 | ami_irq_list[i]->next = NULL; | ||
119 | } | ||
120 | } | ||
121 | for (i = 0; i < AMI_IRQS; i++) | ||
122 | ami_ablecount[i] = 0; | ||
123 | |||
124 | /* turn off PCMCIA interrupts */ | ||
125 | if (AMIGAHW_PRESENT(PCMCIA)) | ||
126 | gayle.inten = GAYLE_IRQ_IDE; | ||
127 | |||
128 | /* turn off all interrupts and enable the master interrupt bit */ | ||
129 | custom.intena = 0x7fff; | ||
130 | custom.intreq = 0x7fff; | ||
131 | custom.intena = IF_SETCLR | IF_INTEN; | ||
132 | |||
133 | cia_init_IRQ(&ciaa_base); | ||
134 | cia_init_IRQ(&ciab_base); | ||
135 | } | ||
136 | |||
137 | static inline int amiga_insert_irq(irq_node_t **list, irq_node_t *node) | ||
138 | { | ||
139 | unsigned long flags; | ||
140 | irq_node_t *cur; | ||
141 | |||
142 | if (!node->dev_id) | ||
143 | printk("%s: Warning: dev_id of %s is zero\n", | ||
144 | __FUNCTION__, node->devname); | ||
145 | |||
146 | local_irq_save(flags); | ||
147 | |||
148 | cur = *list; | ||
149 | |||
150 | if (node->flags & SA_INTERRUPT) { | ||
151 | if (node->flags & SA_SHIRQ) | ||
152 | return -EBUSY; | ||
153 | /* | ||
154 | * There should never be more than one | ||
155 | */ | ||
156 | while (cur && cur->flags & SA_INTERRUPT) { | ||
157 | list = &cur->next; | ||
158 | cur = cur->next; | ||
159 | } | ||
160 | } else { | ||
161 | while (cur) { | ||
162 | list = &cur->next; | ||
163 | cur = cur->next; | ||
164 | } | ||
165 | } | ||
166 | |||
167 | node->next = cur; | ||
168 | *list = node; | ||
169 | |||
170 | local_irq_restore(flags); | ||
171 | return 0; | ||
172 | } | ||
173 | |||
174 | static inline void amiga_delete_irq(irq_node_t **list, void *dev_id) | ||
175 | { | ||
176 | unsigned long flags; | ||
177 | irq_node_t *node; | ||
178 | |||
179 | local_irq_save(flags); | ||
180 | |||
181 | for (node = *list; node; list = &node->next, node = *list) { | ||
182 | if (node->dev_id == dev_id) { | ||
183 | *list = node->next; | ||
184 | /* Mark it as free. */ | ||
185 | node->handler = NULL; | ||
186 | local_irq_restore(flags); | ||
187 | return; | ||
188 | } | ||
189 | } | ||
190 | local_irq_restore(flags); | ||
191 | printk ("%s: tried to remove invalid irq\n", __FUNCTION__); | ||
192 | } | ||
193 | |||
194 | /* | ||
195 | * amiga_request_irq : add an interrupt service routine for a particular | ||
196 | * machine specific interrupt source. | ||
197 | * If the addition was successful, it returns 0. | ||
198 | */ | ||
199 | |||
200 | int amiga_request_irq(unsigned int irq, | ||
201 | irqreturn_t (*handler)(int, void *, struct pt_regs *), | ||
202 | unsigned long flags, const char *devname, void *dev_id) | ||
203 | { | ||
204 | irq_node_t *node; | ||
205 | int error = 0; | ||
206 | |||
207 | if (irq >= AMI_IRQS) { | ||
208 | printk ("%s: Unknown IRQ %d from %s\n", __FUNCTION__, | ||
209 | irq, devname); | ||
210 | return -ENXIO; | ||
211 | } | ||
212 | |||
213 | if (irq >= IRQ_AMIGA_AUTO) | ||
214 | return cpu_request_irq(irq - IRQ_AMIGA_AUTO, handler, | ||
215 | flags, devname, dev_id); | ||
216 | |||
217 | if (irq >= IRQ_AMIGA_CIAB) | ||
218 | return cia_request_irq(&ciab_base, irq - IRQ_AMIGA_CIAB, | ||
219 | handler, flags, devname, dev_id); | ||
220 | |||
221 | if (irq >= IRQ_AMIGA_CIAA) | ||
222 | return cia_request_irq(&ciaa_base, irq - IRQ_AMIGA_CIAA, | ||
223 | handler, flags, devname, dev_id); | ||
224 | |||
225 | /* | ||
226 | * IRQ_AMIGA_PORTS & IRQ_AMIGA_EXTER defaults to shared, | ||
227 | * we could add a check here for the SA_SHIRQ flag but all drivers | ||
228 | * should be aware of sharing anyway. | ||
229 | */ | ||
230 | if (ami_servers[irq]) { | ||
231 | if (!(node = new_irq_node())) | ||
232 | return -ENOMEM; | ||
233 | node->handler = handler; | ||
234 | node->flags = flags; | ||
235 | node->dev_id = dev_id; | ||
236 | node->devname = devname; | ||
237 | node->next = NULL; | ||
238 | error = amiga_insert_irq(&ami_irq_list[irq], node); | ||
239 | } else { | ||
240 | ami_irq_list[irq]->handler = handler; | ||
241 | ami_irq_list[irq]->flags = flags; | ||
242 | ami_irq_list[irq]->dev_id = dev_id; | ||
243 | ami_irq_list[irq]->devname = devname; | ||
244 | } | ||
245 | |||
246 | /* enable the interrupt */ | ||
247 | if (irq < IRQ_AMIGA_PORTS && !ami_ablecount[irq]) | ||
248 | custom.intena = IF_SETCLR | amiga_intena_vals[irq]; | ||
249 | |||
250 | return error; | ||
251 | } | ||
252 | |||
253 | void amiga_free_irq(unsigned int irq, void *dev_id) | ||
254 | { | ||
255 | if (irq >= AMI_IRQS) { | ||
256 | printk ("%s: Unknown IRQ %d\n", __FUNCTION__, irq); | ||
257 | return; | ||
258 | } | ||
259 | |||
260 | if (irq >= IRQ_AMIGA_AUTO) | ||
261 | cpu_free_irq(irq - IRQ_AMIGA_AUTO, dev_id); | ||
262 | |||
263 | if (irq >= IRQ_AMIGA_CIAB) { | ||
264 | cia_free_irq(&ciab_base, irq - IRQ_AMIGA_CIAB, dev_id); | ||
265 | return; | ||
266 | } | ||
267 | |||
268 | if (irq >= IRQ_AMIGA_CIAA) { | ||
269 | cia_free_irq(&ciaa_base, irq - IRQ_AMIGA_CIAA, dev_id); | ||
270 | return; | ||
271 | } | ||
272 | |||
273 | if (ami_servers[irq]) { | ||
274 | amiga_delete_irq(&ami_irq_list[irq], dev_id); | ||
275 | /* if server list empty, disable the interrupt */ | ||
276 | if (!ami_irq_list[irq] && irq < IRQ_AMIGA_PORTS) | ||
277 | custom.intena = amiga_intena_vals[irq]; | ||
278 | } else { | ||
279 | if (ami_irq_list[irq]->dev_id != dev_id) | ||
280 | printk("%s: removing probably wrong IRQ %d from %s\n", | ||
281 | __FUNCTION__, irq, ami_irq_list[irq]->devname); | ||
282 | ami_irq_list[irq]->handler = ami_badint; | ||
283 | ami_irq_list[irq]->flags = 0; | ||
284 | ami_irq_list[irq]->dev_id = NULL; | ||
285 | ami_irq_list[irq]->devname = NULL; | ||
286 | custom.intena = amiga_intena_vals[irq]; | ||
287 | } | ||
288 | } | ||
289 | |||
290 | /* | ||
291 | * Enable/disable a particular machine specific interrupt source. | ||
292 | * Note that this may affect other interrupts in case of a shared interrupt. | ||
293 | * This function should only be called for a _very_ short time to change some | ||
294 | * internal data, that may not be changed by the interrupt at the same time. | ||
295 | * ami_(enable|disable)_irq calls may also be nested. | ||
296 | */ | ||
297 | |||
298 | void amiga_enable_irq(unsigned int irq) | ||
299 | { | ||
300 | if (irq >= AMI_IRQS) { | ||
301 | printk("%s: Unknown IRQ %d\n", __FUNCTION__, irq); | ||
302 | return; | ||
303 | } | ||
304 | |||
305 | if (--ami_ablecount[irq]) | ||
306 | return; | ||
307 | |||
308 | /* No action for auto-vector interrupts */ | ||
309 | if (irq >= IRQ_AMIGA_AUTO){ | ||
310 | printk("%s: Trying to enable auto-vector IRQ %i\n", | ||
311 | __FUNCTION__, irq - IRQ_AMIGA_AUTO); | ||
312 | return; | ||
313 | } | ||
314 | |||
315 | if (irq >= IRQ_AMIGA_CIAB) { | ||
316 | cia_set_irq(&ciab_base, (1 << (irq - IRQ_AMIGA_CIAB))); | ||
317 | cia_able_irq(&ciab_base, CIA_ICR_SETCLR | | ||
318 | (1 << (irq - IRQ_AMIGA_CIAB))); | ||
319 | return; | ||
320 | } | ||
321 | |||
322 | if (irq >= IRQ_AMIGA_CIAA) { | ||
323 | cia_set_irq(&ciaa_base, (1 << (irq - IRQ_AMIGA_CIAA))); | ||
324 | cia_able_irq(&ciaa_base, CIA_ICR_SETCLR | | ||
325 | (1 << (irq - IRQ_AMIGA_CIAA))); | ||
326 | return; | ||
327 | } | ||
328 | |||
329 | /* enable the interrupt */ | ||
330 | custom.intena = IF_SETCLR | amiga_intena_vals[irq]; | ||
331 | } | ||
332 | |||
333 | void amiga_disable_irq(unsigned int irq) | ||
334 | { | ||
335 | if (irq >= AMI_IRQS) { | ||
336 | printk("%s: Unknown IRQ %d\n", __FUNCTION__, irq); | ||
337 | return; | ||
338 | } | ||
339 | |||
340 | if (ami_ablecount[irq]++) | ||
341 | return; | ||
342 | |||
343 | /* No action for auto-vector interrupts */ | ||
344 | if (irq >= IRQ_AMIGA_AUTO) { | ||
345 | printk("%s: Trying to disable auto-vector IRQ %i\n", | ||
346 | __FUNCTION__, irq - IRQ_AMIGA_AUTO); | ||
347 | return; | ||
348 | } | ||
349 | |||
350 | if (irq >= IRQ_AMIGA_CIAB) { | ||
351 | cia_able_irq(&ciab_base, 1 << (irq - IRQ_AMIGA_CIAB)); | ||
352 | return; | ||
353 | } | ||
354 | |||
355 | if (irq >= IRQ_AMIGA_CIAA) { | ||
356 | cia_able_irq(&ciaa_base, 1 << (irq - IRQ_AMIGA_CIAA)); | ||
357 | return; | ||
358 | } | ||
359 | |||
360 | /* disable the interrupt */ | ||
361 | custom.intena = amiga_intena_vals[irq]; | ||
362 | } | ||
363 | |||
364 | inline void amiga_do_irq(int irq, struct pt_regs *fp) | ||
365 | { | ||
366 | kstat_cpu(0).irqs[SYS_IRQS + irq]++; | ||
367 | ami_irq_list[irq]->handler(irq, ami_irq_list[irq]->dev_id, fp); | ||
368 | } | ||
369 | |||
370 | void amiga_do_irq_list(int irq, struct pt_regs *fp) | ||
371 | { | ||
372 | irq_node_t *node; | ||
373 | |||
374 | kstat_cpu(0).irqs[SYS_IRQS + irq]++; | ||
375 | |||
376 | custom.intreq = amiga_intena_vals[irq]; | ||
377 | |||
378 | for (node = ami_irq_list[irq]; node; node = node->next) | ||
379 | node->handler(irq, node->dev_id, fp); | ||
380 | } | ||
381 | |||
382 | /* | ||
383 | * The builtin Amiga hardware interrupt handlers. | ||
384 | */ | ||
385 | |||
386 | static irqreturn_t ami_int1(int irq, void *dev_id, struct pt_regs *fp) | ||
387 | { | ||
388 | unsigned short ints = custom.intreqr & custom.intenar; | ||
389 | |||
390 | /* if serial transmit buffer empty, interrupt */ | ||
391 | if (ints & IF_TBE) { | ||
392 | custom.intreq = IF_TBE; | ||
393 | amiga_do_irq(IRQ_AMIGA_TBE, fp); | ||
394 | } | ||
395 | |||
396 | /* if floppy disk transfer complete, interrupt */ | ||
397 | if (ints & IF_DSKBLK) { | ||
398 | custom.intreq = IF_DSKBLK; | ||
399 | amiga_do_irq(IRQ_AMIGA_DSKBLK, fp); | ||
400 | } | ||
401 | |||
402 | /* if software interrupt set, interrupt */ | ||
403 | if (ints & IF_SOFT) { | ||
404 | custom.intreq = IF_SOFT; | ||
405 | amiga_do_irq(IRQ_AMIGA_SOFT, fp); | ||
406 | } | ||
407 | return IRQ_HANDLED; | ||
408 | } | ||
409 | |||
410 | static irqreturn_t ami_int3(int irq, void *dev_id, struct pt_regs *fp) | ||
411 | { | ||
412 | unsigned short ints = custom.intreqr & custom.intenar; | ||
413 | |||
414 | /* if a blitter interrupt */ | ||
415 | if (ints & IF_BLIT) { | ||
416 | custom.intreq = IF_BLIT; | ||
417 | amiga_do_irq(IRQ_AMIGA_BLIT, fp); | ||
418 | } | ||
419 | |||
420 | /* if a copper interrupt */ | ||
421 | if (ints & IF_COPER) { | ||
422 | custom.intreq = IF_COPER; | ||
423 | amiga_do_irq(IRQ_AMIGA_COPPER, fp); | ||
424 | } | ||
425 | |||
426 | /* if a vertical blank interrupt */ | ||
427 | if (ints & IF_VERTB) | ||
428 | amiga_do_irq_list(IRQ_AMIGA_VERTB, fp); | ||
429 | return IRQ_HANDLED; | ||
430 | } | ||
431 | |||
432 | static irqreturn_t ami_int4(int irq, void *dev_id, struct pt_regs *fp) | ||
433 | { | ||
434 | unsigned short ints = custom.intreqr & custom.intenar; | ||
435 | |||
436 | /* if audio 0 interrupt */ | ||
437 | if (ints & IF_AUD0) { | ||
438 | custom.intreq = IF_AUD0; | ||
439 | amiga_do_irq(IRQ_AMIGA_AUD0, fp); | ||
440 | } | ||
441 | |||
442 | /* if audio 1 interrupt */ | ||
443 | if (ints & IF_AUD1) { | ||
444 | custom.intreq = IF_AUD1; | ||
445 | amiga_do_irq(IRQ_AMIGA_AUD1, fp); | ||
446 | } | ||
447 | |||
448 | /* if audio 2 interrupt */ | ||
449 | if (ints & IF_AUD2) { | ||
450 | custom.intreq = IF_AUD2; | ||
451 | amiga_do_irq(IRQ_AMIGA_AUD2, fp); | ||
452 | } | ||
453 | |||
454 | /* if audio 3 interrupt */ | ||
455 | if (ints & IF_AUD3) { | ||
456 | custom.intreq = IF_AUD3; | ||
457 | amiga_do_irq(IRQ_AMIGA_AUD3, fp); | ||
458 | } | ||
459 | return IRQ_HANDLED; | ||
460 | } | ||
461 | |||
462 | static irqreturn_t ami_int5(int irq, void *dev_id, struct pt_regs *fp) | ||
463 | { | ||
464 | unsigned short ints = custom.intreqr & custom.intenar; | ||
465 | |||
466 | /* if serial receive buffer full interrupt */ | ||
467 | if (ints & IF_RBF) { | ||
468 | /* acknowledge of IF_RBF must be done by the serial interrupt */ | ||
469 | amiga_do_irq(IRQ_AMIGA_RBF, fp); | ||
470 | } | ||
471 | |||
472 | /* if a disk sync interrupt */ | ||
473 | if (ints & IF_DSKSYN) { | ||
474 | custom.intreq = IF_DSKSYN; | ||
475 | amiga_do_irq(IRQ_AMIGA_DSKSYN, fp); | ||
476 | } | ||
477 | return IRQ_HANDLED; | ||
478 | } | ||
479 | |||
480 | static irqreturn_t ami_int7(int irq, void *dev_id, struct pt_regs *fp) | ||
481 | { | ||
482 | panic ("level 7 interrupt received\n"); | ||
483 | } | ||
484 | |||
485 | irqreturn_t (*amiga_default_handler[SYS_IRQS])(int, void *, struct pt_regs *) = { | ||
486 | [0] = ami_badint, | ||
487 | [1] = ami_int1, | ||
488 | [2] = ami_badint, | ||
489 | [3] = ami_int3, | ||
490 | [4] = ami_int4, | ||
491 | [5] = ami_int5, | ||
492 | [6] = ami_badint, | ||
493 | [7] = ami_int7 | ||
494 | }; | ||
495 | |||
496 | int show_amiga_interrupts(struct seq_file *p, void *v) | ||
497 | { | ||
498 | int i; | ||
499 | irq_node_t *node; | ||
500 | |||
501 | for (i = 0; i < AMI_STD_IRQS; i++) { | ||
502 | if (!(node = ami_irq_list[i])) | ||
503 | continue; | ||
504 | seq_printf(p, "ami %2d: %10u ", i, | ||
505 | kstat_cpu(0).irqs[SYS_IRQS + i]); | ||
506 | do { | ||
507 | if (node->flags & SA_INTERRUPT) | ||
508 | seq_puts(p, "F "); | ||
509 | else | ||
510 | seq_puts(p, " "); | ||
511 | seq_printf(p, "%s\n", node->devname); | ||
512 | if ((node = node->next)) | ||
513 | seq_puts(p, " "); | ||
514 | } while (node); | ||
515 | } | ||
516 | |||
517 | cia_get_irq_list(&ciaa_base, p); | ||
518 | cia_get_irq_list(&ciab_base, p); | ||
519 | return 0; | ||
520 | } | ||