Skip to content
Snippets Groups Projects
io_apic.c 93.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     *	Intel IO-APIC support for multi-Pentium hosts.
     *
     *	Copyright (C) 1997, 1998, 1999, 2000 Ingo Molnar, Hajnalka Szabo
     *
     *	Many thanks to Stig Venaas for trying out countless experimental
     *	patches and reporting/debugging problems patiently!
     *
     *	(c) 1999, Multiple IO-APIC support, developed by
     *	Ken-ichi Yaku <yaku@css1.kbnes.nec.co.jp> and
     *      Hidemi Kishimoto <kisimoto@css1.kbnes.nec.co.jp>,
     *	further tested and cleaned up by Zach Brown <zab@redhat.com>
     *	and Ingo Molnar <mingo@redhat.com>
     *
     *	Fixes
     *	Maciej W. Rozycki	:	Bits for genuine 82489DX APICs;
     *					thanks to Eric Gilmore
     *					and Rolf G. Tews
     *					for testing these extensively
     *	Paul Diefenbaugh	:	Added full ACPI support
     */
    
    #include <linux/mm.h>
    #include <linux/interrupt.h>
    #include <linux/init.h>
    #include <linux/delay.h>
    #include <linux/sched.h>
    
    #include <linux/pci.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <linux/mc146818rtc.h>
    #include <linux/compiler.h>
    #include <linux/acpi.h>
    
    #include <linux/module.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <linux/sysdev.h>
    
    #include <linux/htirq.h>
    
    #include <linux/kthread.h>
    
    #include <linux/jiffies.h>	/* time_after() */
    
    #ifdef CONFIG_ACPI
    #include <acpi/acpi_bus.h>
    #endif
    #include <linux/bootmem.h>
    #include <linux/dmar.h>
    
    #include <asm/idle.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <asm/io.h>
    #include <asm/smp.h>
    #include <asm/desc.h>
    
    #include <asm/proto.h>
    #include <asm/acpi.h>
    #include <asm/dma.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <asm/timer.h>
    
    #include <asm/i8259.h>
    
    #include <asm/nmi.h>
    
    #include <asm/hypertransport.h>
    
    #include <asm/setup.h>
    
    #include <asm/irq_remapping.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    #include <mach_ipi.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <mach_apic.h>
    
    #include <mach_apicdef.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    #define __apicdebuginit(type) static type __init
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /*
    
     *      Is the SiS APIC rmw bug present ?
     *      -1 = don't know, 0 = no, 1 = yes
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    int sis_apic_bug = -1;
    
    
    static DEFINE_SPINLOCK(ioapic_lock);
    static DEFINE_SPINLOCK(vector_lock);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     * # of IRQ routing registers
     */
    int nr_ioapic_registers[MAX_IO_APICS];
    
    
    /* I/O APIC entries */
    
    struct mp_config_ioapic mp_ioapics[MAX_IO_APICS];
    
    /* MP IRQ source entries */
    
    struct mp_config_intsrc mp_irqs[MAX_IRQ_SOURCES];
    
    
    /* # of MP IRQ source entries */
    int mp_irq_entries;
    
    
    #if defined (CONFIG_MCA) || defined (CONFIG_EISA)
    int mp_bus_id_to_type[MAX_MP_BUSSES];
    #endif
    
    DECLARE_BITMAP(mp_bus_not_pci, MAX_MP_BUSSES);
    
    
    int skip_ioapic_setup;
    
    
    static int __init parse_noapic(char *str)
    
    {
    	/* disable IO-APIC */
    	disable_ioapic_setup();
    	return 0;
    }
    early_param("noapic", parse_noapic);
    
    struct irq_cfg;
    
    struct irq_pin_list;
    
    Yinghai Lu's avatar
    Yinghai Lu committed
    struct irq_cfg {
    
    	unsigned int irq;
    
    #ifdef CONFIG_HAVE_SPARSE_IRQ
    
    	struct irq_cfg *next;
    
    	struct irq_pin_list *irq_2_pin;
    
    	cpumask_t domain;
    	cpumask_t old_domain;
    	unsigned move_cleanup_count;
    
    Yinghai Lu's avatar
    Yinghai Lu committed
    	u8 vector;
    
    	u8 move_in_progress : 1;
    
    Yinghai Lu's avatar
    Yinghai Lu committed
    };
    
    /* irq_cfg is indexed by the sum of all RTEs in all I/O APICs. */
    static struct irq_cfg irq_cfg_legacy[] __initdata = {
    
    	[0]  = { .irq =  0, .domain = CPU_MASK_ALL, .vector = IRQ0_VECTOR,  },
    	[1]  = { .irq =  1, .domain = CPU_MASK_ALL, .vector = IRQ1_VECTOR,  },
    	[2]  = { .irq =  2, .domain = CPU_MASK_ALL, .vector = IRQ2_VECTOR,  },
    	[3]  = { .irq =  3, .domain = CPU_MASK_ALL, .vector = IRQ3_VECTOR,  },
    	[4]  = { .irq =  4, .domain = CPU_MASK_ALL, .vector = IRQ4_VECTOR,  },
    	[5]  = { .irq =  5, .domain = CPU_MASK_ALL, .vector = IRQ5_VECTOR,  },
    	[6]  = { .irq =  6, .domain = CPU_MASK_ALL, .vector = IRQ6_VECTOR,  },
    	[7]  = { .irq =  7, .domain = CPU_MASK_ALL, .vector = IRQ7_VECTOR,  },
    	[8]  = { .irq =  8, .domain = CPU_MASK_ALL, .vector = IRQ8_VECTOR,  },
    	[9]  = { .irq =  9, .domain = CPU_MASK_ALL, .vector = IRQ9_VECTOR,  },
    	[10] = { .irq = 10, .domain = CPU_MASK_ALL, .vector = IRQ10_VECTOR, },
    	[11] = { .irq = 11, .domain = CPU_MASK_ALL, .vector = IRQ11_VECTOR, },
    	[12] = { .irq = 12, .domain = CPU_MASK_ALL, .vector = IRQ12_VECTOR, },
    	[13] = { .irq = 13, .domain = CPU_MASK_ALL, .vector = IRQ13_VECTOR, },
    	[14] = { .irq = 14, .domain = CPU_MASK_ALL, .vector = IRQ14_VECTOR, },
    	[15] = { .irq = 15, .domain = CPU_MASK_ALL, .vector = IRQ15_VECTOR, },
    
    static struct irq_cfg irq_cfg_init = { .irq =  -1U, };
    
    static void init_one_irq_cfg(struct irq_cfg *cfg)
    {
    	memcpy(cfg, &irq_cfg_init, sizeof(struct irq_cfg));
    }
    
    static struct irq_cfg *irq_cfgx;
    
    #ifdef CONFIG_HAVE_SPARSE_IRQ
    
    /*
     * Protect the irq_cfgx_free freelist:
     */
    static DEFINE_SPINLOCK(irq_cfg_lock);
    
    
    static struct irq_cfg *irq_cfgx_free;
    
    Yinghai Lu's avatar
    Yinghai Lu committed
    static void __init init_work(void *data)
    {
    
    	struct dyn_array *da = data;
    	struct irq_cfg *cfg;
    	int legacy_count;
    	int i;
    
    	cfg = *da->name;
    
    	memcpy(cfg, irq_cfg_legacy, sizeof(irq_cfg_legacy));
    
    	legacy_count = ARRAY_SIZE(irq_cfg_legacy);
    
    	for (i = legacy_count; i < *da->nr; i++)
    		init_one_irq_cfg(&cfg[i]);
    
    #ifdef CONFIG_HAVE_SPARSE_IRQ
    
    	for (i = 1; i < *da->nr; i++)
    		cfg[i-1].next = &cfg[i];
    
    	irq_cfgx_free = &irq_cfgx[legacy_count];
    	irq_cfgx[legacy_count - 1].next = NULL;
    
    #endif
    }
    
    #ifdef CONFIG_HAVE_SPARSE_IRQ
    /* need to be biger than size of irq_cfg_legacy */
    static int nr_irq_cfg = 32;
    
    static int __init parse_nr_irq_cfg(char *arg)
    {
    	if (arg) {
    		nr_irq_cfg = simple_strtoul(arg, NULL, 0);
    		if (nr_irq_cfg < 32)
    			nr_irq_cfg = 32;
    	}
    	return 0;
    
    early_param("nr_irq_cfg", parse_nr_irq_cfg);
    
    #define for_each_irq_cfg(irqX, cfg)           \
            for (cfg = irq_cfgx, irqX = cfg->irq; cfg; cfg = cfg->next, irqX = cfg ? cfg->irq : -1U)
    
    
    
    DEFINE_DYN_ARRAY(irq_cfgx, sizeof(struct irq_cfg), nr_irq_cfg, PAGE_SIZE, init_work);
    
    Yinghai Lu's avatar
    Yinghai Lu committed
    
    static struct irq_cfg *irq_cfg(unsigned int irq)
    {
    
    	struct irq_cfg *cfg;
    
    	cfg = irq_cfgx;
    	while (cfg) {
    		if (cfg->irq == irq)
    			return cfg;
    
    		cfg = cfg->next;
    	}
    
    	return NULL;
    }
    
    static struct irq_cfg *irq_cfg_alloc(unsigned int irq)
    {
    	struct irq_cfg *cfg, *cfg_pri;
    
    	unsigned long flags;
    
    	int count = 0;
    
    
    	cfg_pri = cfg = irq_cfgx;
    	while (cfg) {
    		if (cfg->irq == irq)
    			return cfg;
    
    		cfg_pri = cfg;
    		cfg = cfg->next;
    		count++;
    	}
    
    
    	spin_lock_irqsave(&irq_cfg_lock, flags);
    
    	if (!irq_cfgx_free) {
    		unsigned long phys;
    		unsigned long total_bytes;
    		/*
    		 *  we run out of pre-allocate ones, allocate more
    		 */
    		printk(KERN_DEBUG "try to get more irq_cfg %d\n", nr_irq_cfg);
    
    		total_bytes = sizeof(struct irq_cfg) * nr_irq_cfg;
    		if (after_bootmem)
    			cfg = kzalloc(total_bytes, GFP_ATOMIC);
    		else
    			cfg = __alloc_bootmem_nopanic(total_bytes, PAGE_SIZE, 0);
    
    		if (!cfg)
    			panic("please boot with nr_irq_cfg= %d\n", count * 2);
    
    		phys = __pa(cfg);
    		printk(KERN_DEBUG "irq_irq ==> [%#lx - %#lx]\n", phys, phys + total_bytes);
    
    		for (i = 0; i < nr_irq_cfg; i++)
    			init_one_irq_cfg(&cfg[i]);
    
    		for (i = 1; i < nr_irq_cfg; i++)
    			cfg[i-1].next = &cfg[i];
    
    		irq_cfgx_free = cfg;
    	}
    
    	cfg = irq_cfgx_free;
    	irq_cfgx_free = irq_cfgx_free->next;
    	cfg->next = NULL;
    	if (cfg_pri)
    		cfg_pri->next = cfg;
    	else
    		irq_cfgx = cfg;
    	cfg->irq = irq;
    
    
    	spin_unlock_irqrestore(&irq_cfg_lock, flags);
    
    
    	printk(KERN_DEBUG "found new irq_cfg for irq %d\n", cfg->irq);
    #ifdef CONFIG_HAVE_SPARSE_IRQ_DEBUG
    	{
    		/* dump the results */
    		struct irq_cfg *cfg;
    		unsigned long phys;
    		unsigned long bytes = sizeof(struct irq_cfg);
    
    		printk(KERN_DEBUG "=========================== %d\n", irq);
    		printk(KERN_DEBUG "irq_cfg dump after get that for %d\n", irq);
    		for_each_irq_cfg(cfg) {
    			phys = __pa(cfg);
    			printk(KERN_DEBUG "irq_cfg %d ==> [%#lx - %#lx]\n", cfg->irq, phys, phys + bytes);
    		}
    		printk(KERN_DEBUG "===========================\n");
    	}
    #endif
    	return cfg;
    
    #else
    
    #define for_each_irq_cfg(irq, cfg)		\
    	for (irq = 0, cfg = &irq_cfgx[irq]; irq < nr_irqs; irq++, cfg = &irq_cfgx[irq])
    
    DEFINE_DYN_ARRAY(irq_cfgx, sizeof(struct irq_cfg), nr_irqs, PAGE_SIZE, init_work);
    
    struct irq_cfg *irq_cfg(unsigned int irq)
    {
            if (irq < nr_irqs)
                    return &irq_cfgx[irq];
    
            return NULL;
    }
    struct irq_cfg *irq_cfg_alloc(unsigned int irq)
    {
            return irq_cfg(irq);
    }
    
    #endif
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     * This is performance-critical, we want to do it O(1)
     *
     * the indexing order of this array favors 1:1 mappings
     * between pins and IRQs.
     */
    
    
    struct irq_pin_list {
    	int apic, pin;
    	struct irq_pin_list *next;
    };
    
    static struct irq_pin_list *irq_2_pin_head;
    /* fill one page ? */
    static int nr_irq_2_pin = 0x100;
    static struct irq_pin_list *irq_2_pin_ptr;
    static void __init irq_2_pin_init_work(void *data)
    {
    	struct dyn_array *da = data;
    	struct irq_pin_list *pin;
    	int i;
    
    	pin = *da->name;
    
    	for (i = 1; i < *da->nr; i++)
    		pin[i-1].next = &pin[i];
    
    	irq_2_pin_ptr = &pin[0];
    }
    DEFINE_DYN_ARRAY(irq_2_pin_head, sizeof(struct irq_pin_list), nr_irq_2_pin, PAGE_SIZE, irq_2_pin_init_work);
    
    static struct irq_pin_list *get_one_free_irq_2_pin(void)
    {
    	struct irq_pin_list *pin;
    	int i;
    
    	pin = irq_2_pin_ptr;
    
    	if (pin) {
    		irq_2_pin_ptr = pin->next;
    		pin->next = NULL;
    		return pin;
    	}
    
    	/*
    	 *  we run out of pre-allocate ones, allocate more
    	 */
    	printk(KERN_DEBUG "try to get more irq_2_pin %d\n", nr_irq_2_pin);
    
    	if (after_bootmem)
    		pin = kzalloc(sizeof(struct irq_pin_list)*nr_irq_2_pin,
    				 GFP_ATOMIC);
    	else
    		pin = __alloc_bootmem_nopanic(sizeof(struct irq_pin_list) *
    				nr_irq_2_pin, PAGE_SIZE, 0);
    
    	if (!pin)
    		panic("can not get more irq_2_pin\n");
    
    	for (i = 1; i < nr_irq_2_pin; i++)
    		pin[i-1].next = &pin[i];
    
    	irq_2_pin_ptr = pin->next;
    	pin->next = NULL;
    
    	return pin;
    }
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    struct io_apic {
    	unsigned int index;
    	unsigned int unused[3];
    	unsigned int data;
    };
    
    static __attribute_const__ struct io_apic __iomem *io_apic_base(int idx)
    {
    	return (void __iomem *) __fix_to_virt(FIX_IO_APIC_BASE_0 + idx)
    
    		+ (mp_ioapics[idx].mp_apicaddr & ~PAGE_MASK);
    
    }
    
    static inline unsigned int io_apic_read(unsigned int apic, unsigned int reg)
    {
    	struct io_apic __iomem *io_apic = io_apic_base(apic);
    	writel(reg, &io_apic->index);
    	return readl(&io_apic->data);
    }
    
    static inline void io_apic_write(unsigned int apic, unsigned int reg, unsigned int value)
    {
    	struct io_apic __iomem *io_apic = io_apic_base(apic);
    	writel(reg, &io_apic->index);
    	writel(value, &io_apic->data);
    }
    
    /*
     * Re-write a value: to be used for read-modify-write
     * cycles where the read already set up the index register.
     *
     * Older SiS APIC requires we rewrite the index register
     */
    static inline void io_apic_modify(unsigned int apic, unsigned int reg, unsigned int value)
    {
    
    	struct io_apic __iomem *io_apic = io_apic_base(apic);
            if (sis_apic_bug)
                    writel(reg, &io_apic->index);
    
    	writel(value, &io_apic->data);
    }
    
    
    static bool io_apic_level_ack_pending(unsigned int irq)
    {
    	struct irq_pin_list *entry;
    	unsigned long flags;
    	struct irq_cfg *cfg = irq_cfg(irq);
    
    	spin_lock_irqsave(&ioapic_lock, flags);
    	entry = cfg->irq_2_pin;
    	for (;;) {
    		unsigned int reg;
    		int pin;
    
    		if (!entry)
    			break;
    		pin = entry->pin;
    		reg = io_apic_read(entry->apic, 0x10 + pin*2);
    		/* Is the remote IRR bit set? */
    		if (reg & IO_APIC_REDIR_REMOTE_IRR) {
    			spin_unlock_irqrestore(&ioapic_lock, flags);
    			return true;
    		}
    		if (!entry->next)
    			break;
    		entry = entry->next;
    	}
    	spin_unlock_irqrestore(&ioapic_lock, flags);
    
    	return false;
    }
    
    
    union entry_union {
    	struct { u32 w1, w2; };
    	struct IO_APIC_route_entry entry;
    };
    
    static struct IO_APIC_route_entry ioapic_read_entry(int apic, int pin)
    {
    	union entry_union eu;
    	unsigned long flags;
    	spin_lock_irqsave(&ioapic_lock, flags);
    	eu.w1 = io_apic_read(apic, 0x10 + 2 * pin);
    	eu.w2 = io_apic_read(apic, 0x11 + 2 * pin);
    	spin_unlock_irqrestore(&ioapic_lock, flags);
    	return eu.entry;
    }
    
    
    /*
     * When we write a new IO APIC routing entry, we need to write the high
     * word first! If the mask bit in the low word is clear, we will enable
     * the interrupt, and we need to make sure the entry is fully populated
     * before that happens.
     */
    
    static void
    __ioapic_write_entry(int apic, int pin, struct IO_APIC_route_entry e)
    
    {
    	union entry_union eu;
    	eu.entry = e;
    
    	io_apic_write(apic, 0x11 + 2*pin, eu.w2);
    	io_apic_write(apic, 0x10 + 2*pin, eu.w1);
    
    }
    
    static void ioapic_write_entry(int apic, int pin, struct IO_APIC_route_entry e)
    {
    	unsigned long flags;
    	spin_lock_irqsave(&ioapic_lock, flags);
    	__ioapic_write_entry(apic, pin, e);
    
    	spin_unlock_irqrestore(&ioapic_lock, flags);
    }
    
    /*
     * When we mask an IO APIC routing entry, we need to write the low
     * word first, in order to set the mask bit before we change the
     * high bits!
     */
    static void ioapic_mask_entry(int apic, int pin)
    {
    	unsigned long flags;
    	union entry_union eu = { .entry.mask = 1 };
    
    
    	spin_lock_irqsave(&ioapic_lock, flags);
    	io_apic_write(apic, 0x10 + 2*pin, eu.w1);
    	io_apic_write(apic, 0x11 + 2*pin, eu.w2);
    	spin_unlock_irqrestore(&ioapic_lock, flags);
    }
    
    
    #ifdef CONFIG_SMP
    static void __target_IO_APIC_irq(unsigned int irq, unsigned int dest, u8 vector)
    {
    	int apic, pin;
    	struct irq_cfg *cfg;
    	struct irq_pin_list *entry;
    
    	cfg = irq_cfg(irq);
    	entry = cfg->irq_2_pin;
    	for (;;) {
    		unsigned int reg;
    
    		if (!entry)
    			break;
    
    		apic = entry->apic;
    		pin = entry->pin;
    
    #ifdef CONFIG_INTR_REMAP
    		/*
    		 * With interrupt-remapping, destination information comes
    		 * from interrupt-remapping table entry.
    		 */
    		if (!irq_remapped(irq))
    			io_apic_write(apic, 0x11 + pin*2, dest);
    #else
    
    		io_apic_write(apic, 0x11 + pin*2, dest);
    
    		reg = io_apic_read(apic, 0x10 + pin*2);
    		reg &= ~IO_APIC_REDIR_VECTOR_MASK;
    		reg |= vector;
    
    		io_apic_modify(apic, 0x10 + pin*2, reg);
    
    		if (!entry->next)
    			break;
    		entry = entry->next;
    	}
    }
    
    
    static int assign_irq_vector(int irq, cpumask_t mask);
    
    
    static void set_ioapic_affinity_irq(unsigned int irq, cpumask_t mask)
    {
    	struct irq_cfg *cfg;
    	unsigned long flags;
    	unsigned int dest;
    	cpumask_t tmp;
    
    	struct irq_desc *desc;
    
    
    	cpus_and(tmp, mask, cpu_online_map);
    	if (cpus_empty(tmp))
    		return;
    
    
    	cfg = irq_cfg(irq);
    
    	if (assign_irq_vector(irq, mask))
    		return;
    
    	cpus_and(tmp, cfg->domain, mask);
    	dest = cpu_mask_to_apicid(tmp);
    	/*
    	 * Only the high 8 bits are valid.
    	 */
    	dest = SET_APIC_LOGICAL_ID(dest);
    
    
    	desc = irq_to_desc(irq);
    
    	spin_lock_irqsave(&ioapic_lock, flags);
    	__target_IO_APIC_irq(irq, dest, cfg->vector);
    
    	desc->affinity = mask;
    
    	spin_unlock_irqrestore(&ioapic_lock, flags);
    }
    #endif /* CONFIG_SMP */
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     * The common case is 1:1 IRQ<->pin mappings. Sometimes there are
     * shared ISA-space IRQs, so we have to support them. We are super
     * fast in the common case, and fast for shared ISA-space IRQs.
     */
    static void add_pin_to_irq(unsigned int irq, int apic, int pin)
    {
    
    	struct irq_cfg *cfg;
    	struct irq_pin_list *entry;
    
    	/* first time to refer irq_cfg, so with new */
    	cfg = irq_cfg_alloc(irq);
    	entry = cfg->irq_2_pin;
    	if (!entry) {
    		entry = get_one_free_irq_2_pin();
    		cfg->irq_2_pin = entry;
    		entry->apic = apic;
    		entry->pin = pin;
    		printk(KERN_DEBUG " 0 add_pin_to_irq: irq %d --> apic %d pin %d\n", irq, apic, pin);
    		return;
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	while (entry->next) {
    		/* not again, please */
    		if (entry->apic == apic && entry->pin == pin)
    			return;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    		entry = entry->next;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    
    	entry->next = get_one_free_irq_2_pin();
    	entry = entry->next;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	entry->apic = apic;
    	entry->pin = pin;
    
    	printk(KERN_DEBUG " x add_pin_to_irq: irq %d --> apic %d pin %d\n", irq, apic, pin);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    /*
     * Reroute an IRQ to a different pin.
     */
    static void __init replace_pin_at_irq(unsigned int irq,
    				      int oldapic, int oldpin,
    				      int newapic, int newpin)
    {
    
    	struct irq_cfg *cfg = irq_cfg(irq);
    	struct irq_pin_list *entry = cfg->irq_2_pin;
    	int replaced = 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	while (entry) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (entry->apic == oldapic && entry->pin == oldpin) {
    			entry->apic = newapic;
    			entry->pin = newpin;
    
    			replaced = 1;
    			/* every one is different, right? */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			break;
    
    		}
    		entry = entry->next;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    
    	/* why? call replace before add? */
    	if (!replaced)
    		add_pin_to_irq(irq, newapic, newpin);
    
    Yinghai Lu's avatar
    Yinghai Lu committed
    #define __DO_ACTION(R, ACTION_ENABLE, ACTION_DISABLE, FINAL)		\
    
    									\
    {									\
    	int pin;							\
    	struct irq_cfg *cfg;						\
    	struct irq_pin_list *entry;					\
    									\
    	cfg = irq_cfg(irq);						\
    	entry = cfg->irq_2_pin;						\
    	for (;;) {							\
    		unsigned int reg;					\
    		if (!entry)						\
    			break;						\
    		pin = entry->pin;					\
    		reg = io_apic_read(entry->apic, 0x10 + R + pin*2);	\
    
    Yinghai Lu's avatar
    Yinghai Lu committed
    		reg ACTION_DISABLE;					\
    		reg ACTION_ENABLE;					\
    
    		io_apic_modify(entry->apic, 0x10 + R + pin*2, reg);	\
    		FINAL;							\
    		if (!entry->next)					\
    			break;						\
    		entry = entry->next;					\
    	}								\
    }
    
    
    Yinghai Lu's avatar
    Yinghai Lu committed
    #define DO_ACTION(name,R, ACTION_ENABLE, ACTION_DISABLE, FINAL)		\
    
    									\
    	static void name##_IO_APIC_irq (unsigned int irq)		\
    
    Yinghai Lu's avatar
    Yinghai Lu committed
    	__DO_ACTION(R, ACTION_ENABLE, ACTION_DISABLE, FINAL)
    
    Yinghai Lu's avatar
    Yinghai Lu committed
    DO_ACTION(__unmask,	0, |= 0, &= ~IO_APIC_REDIR_MASKED, )
    
    Yinghai Lu's avatar
    Yinghai Lu committed
    #ifdef CONFIG_X86_64
    /*
     * Synchronize the IO-APIC and the CPU by doing
     * a dummy read from the IO-APIC
     */
    static inline void io_apic_sync(unsigned int apic)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    Yinghai Lu's avatar
    Yinghai Lu committed
    	struct io_apic __iomem *io_apic = io_apic_base(apic);
    	readl(&io_apic->data);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    /* mask = 1 */
    
    Yinghai Lu's avatar
    Yinghai Lu committed
    DO_ACTION(__mask,	0, |= IO_APIC_REDIR_MASKED, &= ~0, io_apic_sync(entry->apic))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Yinghai Lu's avatar
    Yinghai Lu committed
    #else
    
    /* mask = 1 */
    DO_ACTION(__mask,	0, |= IO_APIC_REDIR_MASKED, &= ~0, )
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    /* mask = 1, trigger = 0 */
    
    Yinghai Lu's avatar
    Yinghai Lu committed
    DO_ACTION(__mask_and_edge, 0, |= IO_APIC_REDIR_MASKED, &= ~IO_APIC_REDIR_LEVEL_TRIGGER, )
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    /* mask = 0, trigger = 1 */
    
    Yinghai Lu's avatar
    Yinghai Lu committed
    DO_ACTION(__unmask_and_level, 0, |= IO_APIC_REDIR_LEVEL_TRIGGER, &= ~IO_APIC_REDIR_MASKED, )
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static void mask_IO_APIC_irq (unsigned int irq)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	unsigned long flags;
    
    	spin_lock_irqsave(&ioapic_lock, flags);
    	__mask_IO_APIC_irq(irq);
    	spin_unlock_irqrestore(&ioapic_lock, flags);
    }
    
    
    static void unmask_IO_APIC_irq (unsigned int irq)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	unsigned long flags;
    
    	spin_lock_irqsave(&ioapic_lock, flags);
    	__unmask_IO_APIC_irq(irq);
    	spin_unlock_irqrestore(&ioapic_lock, flags);
    }
    
    static void clear_IO_APIC_pin(unsigned int apic, unsigned int pin)
    {
    	struct IO_APIC_route_entry entry;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* Check delivery_mode to be sure we're not clearing an SMI pin */
    
    	entry = ioapic_read_entry(apic, pin);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (entry.delivery_mode == dest_SMI)
    		return;
    	/*
    	 * Disable it in the IO-APIC irq-routing table:
    	 */
    
    	ioapic_mask_entry(apic, pin);
    
    static void clear_IO_APIC (void)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int apic, pin;
    
    	for (apic = 0; apic < nr_ioapics; apic++)
    		for (pin = 0; pin < nr_ioapic_registers[apic]; pin++)
    			clear_IO_APIC_pin(apic, pin);
    }
    
    
    #if !defined(CONFIG_SMP) && defined(CONFIG_X86_32)
    
    void send_IPI_self(int vector)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	unsigned int cfg;
    
    	/*
    	 * Wait for idle.
    	 */
    	apic_wait_icr_idle();
    	cfg = APIC_DM_FIXED | APIC_DEST_SELF | vector | APIC_DEST_LOGICAL;
    	/*
    	 * Send the IPI. The write to APIC_ICR fires this off.
    	 */
    
    	apic_write(APIC_ICR, cfg);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    #endif /* !CONFIG_SMP && CONFIG_X86_32*/
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    #ifdef CONFIG_X86_32
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     * support for broken MP BIOSs, enables hand-redirection of PIRQ0-7 to
     * specific CPU-side IRQs.
     */
    
    #define MAX_PIRQS 8
    static int pirq_entries [MAX_PIRQS];
    static int pirqs_enabled;
    
    static int __init ioapic_pirq_setup(char *str)
    {
    	int i, max;
    	int ints[MAX_PIRQS+1];
    
    	get_options(str, ARRAY_SIZE(ints), ints);
    
    	for (i = 0; i < MAX_PIRQS; i++)
    		pirq_entries[i] = -1;
    
    	pirqs_enabled = 1;
    	apic_printk(APIC_VERBOSE, KERN_INFO
    			"PIRQ redirection, working around broken MP-BIOS.\n");
    	max = MAX_PIRQS;
    	if (ints[0] < MAX_PIRQS)
    		max = ints[0];
    
    	for (i = 0; i < max; i++) {
    		apic_printk(APIC_VERBOSE, KERN_DEBUG
    				"... PIRQ%d -> IRQ %d\n", i, ints[i+1]);
    		/*
    		 * PIRQs are mapped upside down, usually.
    		 */
    		pirq_entries[MAX_PIRQS-i-1] = ints[i+1];
    	}
    	return 1;
    }
    
    __setup("pirq=", ioapic_pirq_setup);
    
    #endif /* CONFIG_X86_32 */
    
    #ifdef CONFIG_INTR_REMAP
    /* I/O APIC RTE contents at the OS boot up */
    static struct IO_APIC_route_entry *early_ioapic_entries[MAX_IO_APICS];
    
    /*
     * Saves and masks all the unmasked IO-APIC RTE's
     */
    int save_mask_IO_APIC_setup(void)
    {
    	union IO_APIC_reg_01 reg_01;
    	unsigned long flags;
    	int apic, pin;
    
    	/*
    	 * The number of IO-APIC IRQ registers (== #pins):
    	 */
    	for (apic = 0; apic < nr_ioapics; apic++) {
    		spin_lock_irqsave(&ioapic_lock, flags);
    		reg_01.raw = io_apic_read(apic, 1);
    		spin_unlock_irqrestore(&ioapic_lock, flags);
    		nr_ioapic_registers[apic] = reg_01.bits.entries+1;
    	}
    
    	for (apic = 0; apic < nr_ioapics; apic++) {
    		early_ioapic_entries[apic] =
    			kzalloc(sizeof(struct IO_APIC_route_entry) *
    				nr_ioapic_registers[apic], GFP_KERNEL);
    		if (!early_ioapic_entries[apic])
    			return -ENOMEM;
    	}
    
    	for (apic = 0; apic < nr_ioapics; apic++)
    		for (pin = 0; pin < nr_ioapic_registers[apic]; pin++) {
    			struct IO_APIC_route_entry entry;
    
    			entry = early_ioapic_entries[apic][pin] =
    				ioapic_read_entry(apic, pin);
    			if (!entry.mask) {
    				entry.mask = 1;
    				ioapic_write_entry(apic, pin, entry);
    			}
    		}
    	return 0;
    }
    
    void restore_IO_APIC_setup(void)
    {
    	int apic, pin;
    
    	for (apic = 0; apic < nr_ioapics; apic++)
    		for (pin = 0; pin < nr_ioapic_registers[apic]; pin++)
    			ioapic_write_entry(apic, pin,
    					   early_ioapic_entries[apic][pin]);
    }
    
    void reinit_intr_remapped_IO_APIC(int intr_remapping)
    {
    	/*
    	 * for now plain restore of previous settings.
    	 * TBD: In the case of OS enabling interrupt-remapping,
    	 * IO-APIC RTE's need to be setup to point to interrupt-remapping
    	 * table entries. for now, do a plain restore, and wait for
    	 * the setup_IO_APIC_irqs() to do proper initialization.
    	 */
    	restore_IO_APIC_setup();
    }
    #endif
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    /*
     * Find the IRQ entry number of a certain pin.
     */
    static int find_irq_entry(int apic, int pin, int type)
    {
    	int i;
    
    	for (i = 0; i < mp_irq_entries; i++)
    
    		if (mp_irqs[i].mp_irqtype == type &&
    		    (mp_irqs[i].mp_dstapic == mp_ioapics[apic].mp_apicid ||
    		     mp_irqs[i].mp_dstapic == MP_APIC_ALL) &&
    		    mp_irqs[i].mp_dstirq == pin)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			return i;
    
    	return -1;
    }
    
    /*
     * Find the pin to which IRQ[irq] (ISA) is connected
     */
    
    static int __init find_isa_irq_pin(int irq, int type)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int i;
    
    	for (i = 0; i < mp_irq_entries; i++) {
    
    		int lbus = mp_irqs[i].mp_srcbus;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    		if (test_bit(lbus, mp_bus_not_pci) &&
    
    		    (mp_irqs[i].mp_irqtype == type) &&
    		    (mp_irqs[i].mp_srcbusirq == irq))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    			return mp_irqs[i].mp_dstirq;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    	return -1;
    }
    
    
    static int __init find_isa_irq_apic(int irq, int type)
    {
    	int i;
    
    	for (i = 0; i < mp_irq_entries; i++) {
    
    		int lbus = mp_irqs[i].mp_srcbus;
    
    		if (test_bit(lbus, mp_bus_not_pci) &&
    
    		    (mp_irqs[i].mp_irqtype == type) &&
    		    (mp_irqs[i].mp_srcbusirq == irq))
    
    		for(apic = 0; apic < nr_ioapics; apic++) {
    
    			if (mp_ioapics[apic].mp_apicid == mp_irqs[i].mp_dstapic)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     * Find a specific PCI IRQ entry.
     * Not an __init, possibly needed by modules
     */
    static int pin_2_irq(int idx, int apic, int pin);
    
    int IO_APIC_get_PCI_irq_vector(int bus, int slot, int pin)
    {
    	int apic, i, best_guess = -1;
    
    
    	apic_printk(APIC_DEBUG, "querying PCI -> IRQ mapping bus:%d, slot:%d, pin:%d.\n",
    		bus, slot, pin);
    
    	if (test_bit(bus, mp_bus_not_pci)) {
    
    		apic_printk(APIC_VERBOSE, "PCI BIOS passed nonexistent PCI bus %d!\n", bus);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -1;
    	}
    	for (i = 0; i < mp_irq_entries; i++) {
    
    		int lbus = mp_irqs[i].mp_srcbus;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		for (apic = 0; apic < nr_ioapics; apic++)
    
    			if (mp_ioapics[apic].mp_apicid == mp_irqs[i].mp_dstapic ||
    			    mp_irqs[i].mp_dstapic == MP_APIC_ALL)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				break;
    
    
    		if (!test_bit(lbus, mp_bus_not_pci) &&
    
    		    !mp_irqs[i].mp_irqtype &&
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		    (bus == lbus) &&
    
    		    (slot == ((mp_irqs[i].mp_srcbusirq >> 2) & 0x1f))) {
    
    			int irq = pin_2_irq(i,apic,mp_irqs[i].mp_dstirq);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    			if (!(apic || IO_APIC_IRQ(irq)))
    				continue;
    
    
    			if (pin == (mp_irqs[i].mp_srcbusirq & 3))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				return irq;
    			/*
    			 * Use the first all-but-pin matching entry as a
    			 * best-guess fuzzy result for broken mptables.
    			 */
    			if (best_guess < 0)
    				best_guess = irq;
    		}
    	}
    	return best_guess;
    }
    
    EXPORT_SYMBOL(IO_APIC_get_PCI_irq_vector);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    #if defined(CONFIG_EISA) || defined(CONFIG_MCA)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     * EISA Edge/Level control register, ELCR
     */
    static int EISA_ELCR(unsigned int irq)
    {
    	if (irq < 16) {
    		unsigned int port = 0x4d0 + (irq >> 3);
    		return (inb(port) >> (irq & 7)) & 1;
    	}
    	apic_printk(APIC_VERBOSE, KERN_INFO
    			"Broken MPtable reports ISA irq %d\n", irq);
    	return 0;
    }
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    /* ISA interrupts are always polarity zero edge triggered,
     * when listed as conforming in the MP table. */
    
    #define default_ISA_trigger(idx)	(0)
    #define default_ISA_polarity(idx)	(0)