diff options
Diffstat (limited to 'arch/i386/kernel/io_apic.c')
-rw-r--r-- | arch/i386/kernel/io_apic.c | 609 |
1 files changed, 400 insertions, 209 deletions
diff --git a/arch/i386/kernel/io_apic.c b/arch/i386/kernel/io_apic.c index 6e422614e..13ca35d2d 100644 --- a/arch/i386/kernel/io_apic.c +++ b/arch/i386/kernel/io_apic.c @@ -1,7 +1,7 @@ /* * Intel IO-APIC support for multi-pentium hosts. * - * (c) 1997 Ingo Molnar, Hajnalka Szabo + * Copyright (C) 1997, 1998 Ingo Molnar, Hajnalka Szabo * * Many thanks to Stig Venaas for trying out countless experimental * patches and reporting/debugging problems patiently! @@ -34,6 +34,19 @@ */ #define IO_APIC_BASE ((volatile int *)0xfec00000) +enum mp_irq_source_types { + mp_INT = 0, + mp_NMI = 1, + mp_SMI = 2, + mp_ExtINT = 3 +}; + +enum ioapic_irq_destination_types { + dest_Fixed = 0, + dest_LowestPrio = 1, + dest_ExtINT = 7 +}; + /* * The structure of the IO-APIC: */ @@ -60,7 +73,7 @@ struct IO_APIC_route_entry { __u32 vector : 8, delivery_mode : 3, /* 000: FIXED * 001: lowest prio - * 111: ExtInt + * 111: ExtINT */ dest_mode : 1, /* 0: physical, 1: logical */ delivery_status : 1, @@ -137,7 +150,7 @@ void disable_IO_APIC_irq (unsigned int irq) io_apic_write(0x10+2*irq, *(((int *)&entry)+0)); } -void clear_IO_APIC_irq (unsigned int irq) +void clear_IO_APIC_pin (unsigned int pin) { struct IO_APIC_route_entry entry; @@ -146,12 +159,12 @@ void clear_IO_APIC_irq (unsigned int irq) */ memset(&entry, 0, sizeof(entry)); entry.mask = 1; - io_apic_write(0x10+2*irq, *(((int *)&entry)+0)); - io_apic_write(0x11+2*irq, *(((int *)&entry)+1)); + io_apic_write(0x10+2*pin, *(((int *)&entry)+0)); + io_apic_write(0x11+2*pin, *(((int *)&entry)+1)); } /* - * support for broken MP BIOSes, enables hand-redirection of PIRQ0-3 to + * support for broken MP BIOSes, enables hand-redirection of PIRQ0-7 to * specific CPU-side IRQs. */ @@ -159,7 +172,7 @@ void clear_IO_APIC_irq (unsigned int irq) int pirq_entries [MAX_PIRQS]; int pirqs_enabled; -void ioapic_pirq_setup(char *str, int *ints) +__initfunc(void ioapic_pirq_setup(char *str, int *ints)) { int i, max; @@ -187,12 +200,15 @@ void ioapic_pirq_setup(char *str, int *ints) } } -int find_irq_entry(int pin) +/* + * Find the irq entry nr of a certain pin. + */ +__initfunc(static int find_irq_entry(int pin, int type)) { int i; for (i=0; i<mp_irq_entries; i++) - if ( (mp_irqs[i].mpc_irqtype == 0x00) && + if ( (mp_irqs[i].mpc_irqtype == type) && (mp_irqs[i].mpc_dstirq == pin)) return i; @@ -200,7 +216,227 @@ int find_irq_entry(int pin) return -1; } -void setup_IO_APIC_irqs (void) +/* + * Find the pin to which IRQ0 (ISA) is connected + */ +__initfunc(int find_timer_pin (int type)) +{ + int i; + + for (i=0; i<mp_irq_entries; i++) { + int lbus = mp_irqs[i].mpc_srcbus; + + if ((mp_bus_id_to_type[lbus] == MP_BUS_ISA) && + (mp_irqs[i].mpc_irqtype == type) && + (mp_irqs[i].mpc_srcbusirq == 0x00)) + + return mp_irqs[i].mpc_dstirq; + } + return -1; +} + +/* + * Find a specific PCI IRQ entry. + * Not an initfunc, possibly needed by modules + */ +int IO_APIC_get_PCI_irq_vector (int bus, int slot, int pci_pin) +{ + int i; + + for (i=0; i<mp_irq_entries; i++) { + int lbus = mp_irqs[i].mpc_srcbus; + + if (IO_APIC_IRQ(mp_irqs[i].mpc_dstirq) && + (mp_bus_id_to_type[lbus] == MP_BUS_PCI) && + !mp_irqs[i].mpc_irqtype && + (bus == mp_bus_id_to_pci_bus[mp_irqs[i].mpc_srcbus]) && + (slot == ((mp_irqs[i].mpc_srcbusirq >> 2) & 0x1f)) && + (pci_pin == (mp_irqs[i].mpc_srcbusirq & 3))) + + return mp_irqs[i].mpc_dstirq; + } + return -1; +} + +static int irq_trigger(int idx) +{ + int bus = mp_irqs[idx].mpc_srcbus; + int trigger; + + /* + * Determine IRQ trigger mode (edge or level sensitive): + */ + switch ((mp_irqs[idx].mpc_irqflag>>2) & 3) + { + case 0: /* conforms, ie. bus-type dependent */ + { + switch (mp_bus_id_to_type[bus]) + { + case MP_BUS_ISA: /* ISA pin, edge */ + { + trigger = 0; + break; + } + case MP_BUS_PCI: /* PCI pin, level */ + { + trigger = 1; + break; + } + default: + { + printk("broken BIOS!!\n"); + trigger = 1; + break; + } + } + break; + } + case 1: /* edge */ + { + trigger = 0; + break; + } + case 2: /* reserved */ + { + printk("broken BIOS!!\n"); + trigger = 1; + break; + } + case 3: /* level */ + { + trigger = 1; + break; + } + default: /* invalid */ + { + printk("broken BIOS!!\n"); + trigger = 0; + break; + } + } + return trigger; +} + +__initfunc(static int irq_polarity(int idx)) +{ + int bus = mp_irqs[idx].mpc_srcbus; + int polarity; + + /* + * Determine IRQ line polarity (high active or low active): + */ + switch (mp_irqs[idx].mpc_irqflag & 3) + { + case 0: /* conforms, ie. bus-type dependent polarity */ + { + switch (mp_bus_id_to_type[bus]) + { + case MP_BUS_ISA: /* ISA pin */ + { + polarity = 0; + break; + } + case MP_BUS_PCI: /* PCI pin */ + { + polarity = 1; + break; + } + default: + { + printk("broken BIOS!!\n"); + polarity = 1; + break; + } + } + break; + } + case 1: /* high active */ + { + polarity = 0; + break; + } + case 2: /* reserved */ + { + printk("broken BIOS!!\n"); + polarity = 1; + break; + } + case 3: /* low active */ + { + polarity = 1; + break; + } + default: /* invalid */ + { + printk("broken BIOS!!\n"); + polarity = 1; + break; + } + } + return polarity; +} + +__initfunc(static int pin_2_irq (int idx, int pin)) +{ + int irq; + int bus = mp_irqs[idx].mpc_srcbus; + + switch (mp_bus_id_to_type[bus]) + { + case MP_BUS_ISA: /* ISA pin */ + { + irq = mp_irqs[idx].mpc_srcbusirq; + break; + } + case MP_BUS_PCI: /* PCI pin */ + { + /* + * PCI IRQs are 'directly mapped' + */ + irq = pin; + break; + } + default: + { + printk("unknown bus type %d.\n",bus); + irq = 0; + break; + } + } + + /* + * PCI IRQ command line redirection. Yes, limits are hardcoded. + */ + if ((pin>=16) && (pin<=23)) { + if (pirq_entries[pin-16] != -1) { + if (!pirq_entries[pin-16]) { + printk("disabling PIRQ%d\n", pin-16); + } else { + irq = pirq_entries[pin-16]; + printk("using PIRQ%d -> IRQ %d\n", + pin-16, irq); + } + } + } + return irq; +} + +int IO_APIC_irq_trigger (int irq) +{ + int idx, i; + + for (i=0; i<nr_ioapic_registers; i++) { + idx = find_irq_entry(i,mp_INT); + if (irq == pin_2_irq(idx,i)) + return irq_trigger(idx); + } + /* + * nonexistant IRQs are edge default + */ + return 0; +} + +__initfunc(void setup_IO_APIC_irqs (void)) { struct IO_APIC_route_entry entry; int i, idx, bus, irq, first_notcon=1; @@ -214,12 +450,12 @@ void setup_IO_APIC_irqs (void) */ memset(&entry,0,sizeof(entry)); - entry.delivery_mode = 1; /* lowest prio */ + entry.delivery_mode = dest_LowestPrio; entry.dest_mode = 1; /* logical delivery */ entry.mask = 0; /* enable IRQ */ entry.dest.logical.logical_dest = 0xff; /* all CPUs */ - idx = find_irq_entry(i); + idx = find_irq_entry(i,mp_INT); if (idx == -1) { if (first_notcon) { printk(" IO-APIC pin %d", i); @@ -228,137 +464,32 @@ void setup_IO_APIC_irqs (void) printk(", %d", i); continue; } - bus = mp_irqs[idx].mpc_srcbus; - switch (mp_bus_id_to_type[bus]) - { - case MP_BUS_ISA: /* ISA pin */ - { - irq = mp_irqs[idx].mpc_srcbusirq; - break; - } - case MP_BUS_PCI: /* PCI pin */ - { - /* - * PCI IRQs are 'directly mapped' - */ - irq = i; - break; - } - default: - { - printk("unknown bus type %d.\n",bus); - irq = 0; - break; - } - } + entry.trigger = irq_trigger(idx); + entry.polarity = irq_polarity(idx); - /* - * PCI IRQ redirection. Yes, limits are hardcoded. - */ - if ((i>=16) && (i<=23)) { - if (pirq_entries[i-16] != -1) { - if (!pirq_entries[i-16]) { - printk("disabling PIRQ%d\n", i-16); - } else { - irq = pirq_entries[i-16]; - printk("using PIRQ%d -> IRQ %d\n", - i-16, irq); - } - } - } + irq = pin_2_irq(idx,i); if (!IO_APIC_IRQ(irq)) continue; entry.vector = IO_APIC_VECTOR(irq); - /* - * Determine IRQ line polarity (high active or low active): - */ - switch (mp_irqs[idx].mpc_irqflag & 3) - { - case 0: /* conforms, ie. bus-type dependent polarity */ - { - switch (mp_bus_id_to_type[bus]) - { - case MP_BUS_ISA: /* ISA pin */ - { - entry.polarity = 0; - break; - } - case MP_BUS_PCI: /* PCI pin */ - { - entry.polarity = 1; - break; - } - default: - { - printk("broken BIOS!!\n"); - break; - } - } - break; - } - case 1: /* high active */ - { - entry.polarity = 0; - break; - } - case 2: /* reserved */ - { - printk("broken BIOS!!\n"); - break; - } - case 3: /* low active */ - { - entry.polarity = 1; - break; - } - } + /* + * There are broken mptables which register ISA+high-active+level IRQs, + * these are illegal and are converted here to ISA+high-active+edge + * IRQ sources. Careful, ISA+low-active+level is another broken entry + * type, it represents PCI IRQs 'embedded into an ISA bus', they have + * to be accepted. Yes, ugh. + */ + bus = mp_irqs[idx].mpc_srcbus; - /* - * Determine IRQ trigger mode (edge or level sensitive): - */ - switch ((mp_irqs[idx].mpc_irqflag>>2) & 3) + if ( (mp_bus_id_to_type[bus] == MP_BUS_ISA) && + (entry.polarity == 0) /* active-high */ && + (entry.trigger == 1) /* level */ ) { - case 0: /* conforms, ie. bus-type dependent */ - { - switch (mp_bus_id_to_type[bus]) - { - case MP_BUS_ISA: /* ISA pin, edge */ - { - entry.trigger = 0; - break; - } - case MP_BUS_PCI: /* PCI pin, level */ - { - entry.trigger = 1; - break; - } - default: - { - printk("broken BIOS!!\n"); - break; - } - } - break; - } - case 1: /* edge */ - { - entry.trigger = 0; - break; - } - case 2: /* reserved */ - { - printk("broken BIOS!!\n"); - break; - } - case 3: /* level */ - { - entry.trigger = 1; - break; - } + printk("broken BIOS, changing pin %d to edge\n", i); + entry.trigger = 0; } io_apic_write(0x10+2*i, *(((int *)&entry)+0)); @@ -369,7 +500,7 @@ void setup_IO_APIC_irqs (void) printk(" not connected.\n"); } -void setup_IO_APIC_irq_ISA_default (unsigned int irq) +__initfunc(void setup_IO_APIC_irq_ISA_default (unsigned int irq)) { struct IO_APIC_route_entry entry; @@ -378,9 +509,9 @@ void setup_IO_APIC_irq_ISA_default (unsigned int irq) */ memset(&entry,0,sizeof(entry)); - entry.delivery_mode = 1; /* lowest prio */ + entry.delivery_mode = dest_LowestPrio; /* lowest prio */ entry.dest_mode = 1; /* logical delivery */ - entry.mask = 1; /* unmask IRQ now */ + entry.mask = 0; /* unmask IRQ now */ entry.dest.logical.logical_dest = 0xff; /* all CPUs */ entry.vector = IO_APIC_VECTOR(irq); @@ -392,56 +523,42 @@ void setup_IO_APIC_irq_ISA_default (unsigned int irq) io_apic_write(0x11+2*irq, *(((int *)&entry)+1)); } -int IO_APIC_get_PCI_irq_vector (int bus, int slot, int pci_pin) -{ - int i; - - for (i=0; i<mp_irq_entries; i++) { - int lbus = mp_irqs[i].mpc_srcbus; - - if (IO_APIC_IRQ(mp_irqs[i].mpc_dstirq) && - (mp_bus_id_to_type[lbus] == MP_BUS_PCI) && - !mp_irqs[i].mpc_irqtype && - (bus == mp_bus_id_to_pci_bus[mp_irqs[i].mpc_srcbus]) && - (slot == ((mp_irqs[i].mpc_srcbusirq >> 2) & 0x1f)) && - (pci_pin == (mp_irqs[i].mpc_srcbusirq & 3))) - - return mp_irqs[i].mpc_dstirq; - } - return -1; -} - /* - * There is a nasty bug in some older SMP boards, their mptable lies - * about the timer IRQ. We do the following to work around the situation: - * - * - timer IRQ defaults to IO-APIC IRQ - * - if this function detects that timer IRQs are defunct, then we fall - * back to ISA timer IRQs + * Set up a certain pin as ExtINT delivered interrupt */ -static int timer_irq_works (void) +__initfunc(void setup_ExtINT_pin (unsigned int pin)) { - unsigned int t1=jiffies; - unsigned long flags; + struct IO_APIC_route_entry entry; - save_flags(flags); - sti(); + /* + * add it to the IO-APIC irq-routing table: + */ + memset(&entry,0,sizeof(entry)); - udelay(100*1000); + entry.delivery_mode = dest_ExtINT; + entry.dest_mode = 1; /* logical delivery */ + entry.mask = 0; /* unmask IRQ now */ + entry.dest.logical.logical_dest = 0xff; /* all CPUs */ - if (jiffies-t1>1) - return 1; + entry.vector = IO_APIC_VECTOR(pin); /* it's ignored */ - return 0; + entry.polarity=0; + entry.trigger=0; + + io_apic_write(0x10+2*pin, *(((int *)&entry)+0)); + io_apic_write(0x11+2*pin, *(((int *)&entry)+1)); } -void print_IO_APIC (void) +__initfunc(void print_IO_APIC (void)) { int i; struct IO_APIC_reg_00 reg_00; struct IO_APIC_reg_01 reg_01; struct IO_APIC_reg_02 reg_02; + printk("nr of MP irq sources: %d.\n", mp_irq_entries); + printk("nr of IO-APIC registers: %d.\n", nr_ioapic_registers); + *(int *)®_00 = io_apic_read(0); *(int *)®_01 = io_apic_read(1); *(int *)®_02 = io_apic_read(2); @@ -513,14 +630,41 @@ void print_IO_APIC (void) return; } -static void init_sym_mode (void) +__initfunc(static void init_sym_mode (void)) { + int i; + + if (!pirqs_enabled) + for (i=0; i<MAX_PIRQS; i++) + pirq_entries[i]=-1; + printk("enabling Symmetric IO mode ... "); - outb_p (0x70, 0x22); - outb_p (0x01, 0x23); + + outb(0x70, 0x22); + outb(0x01, 0x23); + printk("...done.\n"); + + /* + * The number of IO-APIC irq-registers (== #pins): + */ + { + struct IO_APIC_reg_01 reg_01; + + *(int *)®_01 = io_apic_read(1); + nr_ioapic_registers = reg_01.entries+1; + } + + /* + * Do not trust the IO-APIC being empty at bootup + */ + for (i=0; i<nr_ioapic_registers; i++) + clear_IO_APIC_pin (i); } +/* + * Not an initfunc, needed by the reboot code + */ void init_pic_mode (void) { printk("disabling Symmetric IO mode ... "); @@ -551,8 +695,7 @@ struct ioapic_list_entry ioapic_blacklist [] = { { 0 , 0 } }; - -static int in_ioapic_list (struct ioapic_list_entry * table) +__initfunc(static int in_ioapic_list (struct ioapic_list_entry * table)) { for (;table->oem_id; table++) if ((!strcmp(table->oem_id,ioapic_OEM_ID)) && @@ -561,7 +704,7 @@ static int in_ioapic_list (struct ioapic_list_entry * table) return 0; } -static int ioapic_whitelisted (void) +__initfunc(static int ioapic_whitelisted (void)) { /* * Right now, whitelist everything to see whether the new parsing @@ -574,12 +717,12 @@ static int ioapic_whitelisted (void) #endif } -static int ioapic_blacklisted (void) +__initfunc(static int ioapic_blacklisted (void)) { return in_ioapic_list(ioapic_blacklist); } -static void setup_ioapic_id (void) +__initfunc(static void setup_ioapic_id (void)) { struct IO_APIC_reg_00 reg_00; @@ -598,7 +741,7 @@ static void setup_ioapic_id (void) panic("APIC ID 2 already used"); /* - * set the ID + * Set the ID */ *(int *)®_00 = io_apic_read(0); printk("... changing IO-APIC physical APIC ID to 2 ...\n"); @@ -613,7 +756,7 @@ static void setup_ioapic_id (void) panic("could not set ID"); } -static void construct_default_ISA_mptable (void) +__initfunc(static void construct_default_ISA_mptable (void)) { int i, pos=0; @@ -650,31 +793,84 @@ static void construct_default_ISA_mptable (void) setup_ioapic_id(); } - -void setup_IO_APIC (void) + +/* + * There is a nasty bug in some older SMP boards, their mptable lies + * about the timer IRQ. We do the following to work around the situation: + * + * - timer IRQ defaults to IO-APIC IRQ + * - if this function detects that timer IRQs are defunct, then we fall + * back to ISA timer IRQs + */ +__initfunc(static int timer_irq_works (void)) { - int i; + unsigned int t1=jiffies; + unsigned long flags; - if (!pirqs_enabled) - for (i=0; i<MAX_PIRQS; i++) - pirq_entries[i]=-1; + save_flags(flags); + sti(); - init_sym_mode(); - { - struct IO_APIC_reg_01 reg_01; + udelay(10*10000); - *(int *)®_01 = io_apic_read(1); - nr_ioapic_registers = reg_01.entries+1; + if (jiffies-t1>1) + return 1; + + return 0; +} + +/* + * This code may look a bit paranoid, but it's supposed to cooperate with + * a wide range of boards and BIOS bugs ... fortunately only the timer IRQ + * is so screwy. Thanks to Brian Perkins for testing/hacking this beast + * fanatically on his truly bugged board. + */ +__initfunc(static void check_timer (void)) +{ + int pin1, pin2; + + pin1 = find_timer_pin (mp_INT); + pin2 = find_timer_pin (mp_ExtINT); + + if (!timer_irq_works ()) { + if (pin1 != -1) + printk("..MP-BIOS bug: 8254 timer not connected to IO-APIC\n"); + printk("..trying to set up timer as ExtINT ... "); + + if (pin2 != -1) { + printk(".. (found pin %d) ...", pin2); + setup_ExtINT_pin (pin2); + make_8259A_irq(0); + } + + if (!timer_irq_works ()) { + printk(" failed.\n"); + printk("..trying to set up timer as BP irq ..."); + /* + * Just in case ... + */ + if (pin1 != -1) + clear_IO_APIC_pin (pin1); + if (pin2 != -1) + clear_IO_APIC_pin (pin2); + + make_8259A_irq(0); + + if (!timer_irq_works ()) { + printk(" failed.\n"); + panic("IO-APIC + timer doesnt work!"); + } + } + printk(" works.\n"); } +} - /* - * do not trust the IO-APIC being empty at bootup - */ - for (i=0; i<nr_ioapic_registers; i++) - clear_IO_APIC_irq (i); +__initfunc(void setup_IO_APIC (void)) +{ + init_sym_mode(); /* - * the following IO-APIC's can be enabled: + * Determine the range of IRQs handled by the IO-APIC. The + * following boards can be fully enabled: * * - whitelisted ones * - those which have no PCI pins connected @@ -699,7 +895,7 @@ void setup_IO_APIC (void) /* * If there are no explicit mp irq entries: it's either one of the * default configuration types or we are broken. In both cases it's - * fine to set up most of the low 16 IOAPIC pins to ISA defaults. + * fine to set up most of the low 16 IO-APIC pins to ISA defaults. */ if (!mp_irq_entries) { printk("no explicit IRQ entries, using default mptable\n"); @@ -708,18 +904,13 @@ void setup_IO_APIC (void) init_IO_APIC_traps(); + /* + * Set up the IO-APIC irq routing table by parsing the MP-BIOS + * mptable: + */ setup_IO_APIC_irqs (); - - if (!timer_irq_works ()) { - make_8259A_irq(0); - if (!timer_irq_works ()) - panic("IO-APIC + timer doesnt work!"); - printk("..MP-BIOS bug: i8254 timer not connected to IO-APIC\n"); - printk("..falling back to 8259A-based timer interrupt\n"); - } + check_timer(); - printk("nr of MP irq sources: %d.\n", mp_irq_entries); - printk("nr of IOAPIC registers: %d.\n", nr_ioapic_registers); print_IO_APIC(); } |