/* * Linux IRQ management. * Copyright (C) 1995 Shantanu Goel. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * linux/arch/i386/kernel/irq.c * * Copyright (C) 1992 Linus Torvalds */ #include #include #include #include #include #include #include #define MACH_INCLUDE #include #include #include #include #include #include #include #include #include #include #include extern int linux_timer_intr (void); extern spl_t splhigh (void); extern spl_t spl0 (void); extern void form_pic_mask (void); /* * XXX Move this into more suitable place... * Set if the machine has an EISA bus. */ int EISA_bus = 0; /* * Priority at which a Linux handler should be called. * This is used at the time of an IRQ allocation. It is * set by emulation routines for each class of device. */ spl_t linux_intr_pri; /* * Flag indicating an interrupt is being handled. */ unsigned long intr_count = 0; /* * List of Linux interrupt handlers. */ struct linux_action { void (*handler) (int, void *, struct pt_regs *); void *dev_id; struct linux_action *next; unsigned long flags; }; static struct linux_action *irq_action[16] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; extern spl_t curr_ipl; extern int curr_pic_mask; extern int pic_mask[]; extern int intnull (), prtnull (); /* * Generic interrupt handler for Linux devices. * Set up a fake `struct pt_regs' then call the real handler. */ static int linux_intr (int irq) { struct pt_regs regs; struct linux_action *action = *(irq_action + irq); kstat.interrupts[irq]++; intr_count++; while (action) { action->handler (irq, action->dev_id, ®s); action = action->next; } intr_count--; /* Not used. by OKUJI Yoshinori. */ return 0; } /* * Mask an IRQ. */ static inline void mask_irq (unsigned int irq_nr) { int i; for (i = 0; i < intpri[irq_nr]; i++) pic_mask[i] |= 1 << irq_nr; if (curr_pic_mask != pic_mask[curr_ipl]) { curr_pic_mask = pic_mask[curr_ipl]; if (irq_nr < 8) outb (curr_pic_mask & 0xff, PIC_MASTER_OCW); else outb (curr_pic_mask >> 8, PIC_SLAVE_OCW); } } /* * Unmask an IRQ. */ static inline void unmask_irq (unsigned int irq_nr) { int mask, i; mask = 1 << irq_nr; if (irq_nr >= 8) mask |= 1 << 2; for (i = 0; i < intpri[irq_nr]; i++) pic_mask[i] &= ~mask; if (curr_pic_mask != pic_mask[curr_ipl]) { curr_pic_mask = pic_mask[curr_ipl]; if (irq_nr < 8) outb (curr_pic_mask & 0xff, PIC_MASTER_OCW); else outb (curr_pic_mask >> 8, PIC_SLAVE_OCW); } } void disable_irq (unsigned int irq_nr) { unsigned long flags; assert (irq_nr < NR_IRQS); save_flags (flags); cli (); mask_irq (irq_nr); restore_flags (flags); } void enable_irq (unsigned int irq_nr) { unsigned long flags; assert (irq_nr < NR_IRQS); save_flags (flags); cli (); unmask_irq (irq_nr); restore_flags (flags); } /* * Default interrupt handler for Linux. */ int linux_bad_intr (int irq) { mask_irq (irq); return 0; } static int setup_x86_irq (int irq, struct linux_action *new) { int shared = 0; struct linux_action *old, **p; unsigned long flags; p = irq_action + irq; if ((old = *p) != NULL) { /* Can't share interrupts unless both agree to */ if (!(old->flags & new->flags & SA_SHIRQ)) return (-LINUX_EBUSY); /* Can't share interrupts unless both are same type */ if ((old->flags ^ new->flags) & SA_INTERRUPT) return (-LINUX_EBUSY); /* add new interrupt at end of irq queue */ do { p = &old->next; old = *p; } while (old); shared = 1; } save_flags (flags); cli (); *p = new; if (!shared) { ivect[irq] = linux_intr; iunit[irq] = irq; intpri[irq] = linux_intr_pri; unmask_irq (irq); } restore_flags (flags); return 0; } /* * Attach a handler to an IRQ. */ int request_irq (unsigned int irq, void (*handler) (int, void *, struct pt_regs *), unsigned long flags, const char *device, void *dev_id) { struct linux_action *action; int retval; assert (irq < 16); if (!handler) return -LINUX_EINVAL; /* * Hmm... Should I use `kalloc()' ? * By OKUJI Yoshinori. */ action = (struct linux_action *) linux_kmalloc (sizeof (struct linux_action), GFP_KERNEL); if (action == NULL) return -LINUX_ENOMEM; action->handler = handler; action->next = NULL; action->dev_id = dev_id; action->flags = flags; retval = setup_x86_irq (irq, action); if (retval) linux_kfree (action); return retval; } /* * Deallocate an irq. */ void free_irq (unsigned int irq, void *dev_id) { struct linux_action *action, **p; unsigned long flags; if (irq > 15) panic ("free_irq: bad irq number"); for (p = irq_action + irq; (action = *p) != NULL; p = &action->next) { if (action->dev_id != dev_id) continue; save_flags (flags); cli (); *p = action->next; if (!irq_action[irq]) { mask_irq (irq); ivect[irq] = linux_bad_intr; iunit[irq] = irq; intpri[irq] = SPL0; } restore_flags (flags); linux_kfree (action); return; } panic ("free_irq: bad irq number"); } /* * Set for an irq probe. */ unsigned long probe_irq_on (void) { unsigned i, irqs = 0; unsigned long delay; assert (curr_ipl == 0); /* * Allocate all available IRQs. */ for (i = 15; i > 0; i--) { if (!irq_action[i] && ivect[i] == linux_bad_intr) { intpri[i] = linux_intr_pri; enable_irq (i); irqs |= 1 << i; } } /* * Wait for spurious interrupts to mask themselves out. */ for (delay = jiffies + HZ / 10; delay > jiffies;) ; return (irqs & ~curr_pic_mask); } /* * Return the result of an irq probe. */ int probe_irq_off (unsigned long irqs) { unsigned int i; assert (curr_ipl == 0); irqs &= curr_pic_mask; /* * Disable unnecessary IRQs. */ for (i = 15; i > 0; i--) { if (!irq_action[i] && ivect[i] == linux_bad_intr) { disable_irq (i); intpri[i] = SPL0; } } /* * Return IRQ number. */ if (!irqs) return 0; i = ffz (~irqs); if (irqs != (irqs & (1 << i))) i = -i; return i; } /* * Reserve IRQs used by Mach drivers. * Must be called before Linux IRQ detection, after Mach IRQ detection. */ static void reserve_mach_irqs (void) { unsigned int i; for (i = 0; i < 16; i++) { if (ivect[i] != prtnull && ivect[i] != intnull) /* Set non-NULL value. */ irq_action[i] = (struct linux_action *) -1; } } static int (*old_clock_handler) (); static int old_clock_pri; void init_IRQ (void) { int i; char *p; int latch = (CLKNUM + hz / 2) / hz; /* * Ensure interrupts are disabled. */ (void) splhigh (); /* * Program counter 0 of 8253 to interrupt hz times per second. */ outb_p (PIT_C0 | PIT_SQUAREMODE | PIT_READMODE, PITCTL_PORT); outb_p (latch && 0xff, PITCTR0_PORT); outb (latch >> 8, PITCTR0_PORT); /* * Install our clock interrupt handler. */ old_clock_handler = ivect[0]; old_clock_pri = intpri[0]; ivect[0] = linux_timer_intr; intpri[0] = SPLHI; reserve_mach_irqs (); for (i = 1; i < 16; i++) { /* * irq2 and irq13 should be igonored. */ if (i == 2 || i == 13) continue; if (ivect[i] == prtnull || ivect[i] == intnull) { ivect[i] = linux_bad_intr; iunit[i] = i; intpri[i] = SPL0; } } form_pic_mask (); /* * Enable interrupts. */ (void) spl0 (); /* * Check if the machine has an EISA bus. */ p = (char *) 0x0FFFD9; if (*p++ == 'E' && *p++ == 'I' && *p++ == 'S' && *p == 'A') EISA_bus = 1; /* * Permanently allocate standard device ports. */ request_region (0x00, 0x20, "dma1"); request_region (0x20, 0x20, "pic1"); request_region (0x40, 0x20, "timer"); request_region (0x70, 0x10, "rtc"); request_region (0x80, 0x20, "dma page reg"); request_region (0xa0, 0x20, "pic2"); request_region (0xc0, 0x20, "dma2"); request_region (0xf0, 0x10, "npu"); } void restore_IRQ (void) { /* * Disable interrupts. */ (void) splhigh (); /* * Restore clock interrupt handler. */ ivect[0] = old_clock_handler; intpri[0] = old_clock_pri; form_pic_mask (); }