📄 iosapic.c
字号:
/*** I/O Sapic Driver - PCI interrupt line support**** (c) Copyright 1999 Grant Grundler** (c) Copyright 1999 Hewlett-Packard Company**** 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 of the License, or** (at your option) any later version.**** The I/O sapic driver manages the Interrupt Redirection Table which is** the control logic to convert PCI line based interrupts into a Message** Signaled Interrupt (aka Transaction Based Interrupt, TBI).**** Acronyms** --------** HPA Hard Physical Address (aka MMIO address)** IRQ Interrupt ReQuest. Implies Line based interrupt.** IRT Interrupt Routing Table (provided by PAT firmware)** IRdT Interrupt Redirection Table. IRQ line to TXN ADDR/DATA** table which is implemented in I/O SAPIC.** ISR Interrupt Service Routine. aka Interrupt handler.** MSI Message Signaled Interrupt. PCI 2.2 functionality.** aka Transaction Based Interrupt (or TBI).** PA Precision Architecture. HP's RISC architecture.** RISC Reduced Instruction Set Computer.****** What's a Message Signalled Interrupt?** -------------------------------------** MSI is a write transaction which targets a processor and is similar** to a processor write to memory or MMIO. MSIs can be generated by I/O** devices as well as processors and require *architecture* to work.**** PA only supports MSI. So I/O subsystems must either natively generate** MSIs (e.g. GSC or HP-PB) or convert line based interrupts into MSIs** (e.g. PCI and EISA). IA64 supports MSIs via a "local SAPIC" which** acts on behalf of a processor.**** MSI allows any I/O device to interrupt any processor. This makes** load balancing of the interrupt processing possible on an SMP platform.** Interrupts are also ordered WRT to DMA data. It's possible on I/O** coherent systems to completely eliminate PIO reads from the interrupt** path. The device and driver must be designed and implemented to** guarantee all DMA has been issued (issues about atomicity here)** before the MSI is issued. I/O status can then safely be read from** DMA'd data by the ISR.****** PA Firmware** -----------** PA-RISC platforms have two fundementally different types of firmware.** For PCI devices, "Legacy" PDC initializes the "INTERRUPT_LINE" register** and BARs similar to a traditional PC BIOS.** The newer "PAT" firmware supports PDC calls which return tables.** PAT firmware only initializes PCI Console and Boot interface.** With these tables, the OS can progam all other PCI devices.**** One such PAT PDC call returns the "Interrupt Routing Table" (IRT).** The IRT maps each PCI slot's INTA-D "output" line to an I/O SAPIC** input line. If the IRT is not available, this driver assumes** INTERRUPT_LINE register has been programmed by firmware. The latter** case also means online addition of PCI cards can NOT be supported** even if HW support is present.**** All platforms with PAT firmware to date (Oct 1999) use one Interrupt** Routing Table for the entire platform.**** Where's the iosapic?** --------------------** I/O sapic is part of the "Core Electronics Complex". And on HP platforms** it's integrated as part of the PCI bus adapter, "lba". So no bus walk** will discover I/O Sapic. I/O Sapic driver learns about each device** when lba driver advertises the presence of the I/O sapic by calling** iosapic_register().****** IRQ region notes** ----------------** The data passed to iosapic_interrupt() is per IRQ line.** Each IRQ line will get one txn_addr/data pair. Thus each IRQ region,** will have several txn_addr/data pairs (up to 7 for current I/O SAPIC** implementations). The IRQ region "sysdata" will NOT be directly passed** to the interrupt handler like GSCtoPCI (dino.c).**** iosapic interrupt handler will NOT call do_irq_mask().** It doesn't need to read a bit mask to determine which IRQ line was pulled** since it already knows based on vector_info passed to iosapic_interrupt().**** One IRQ number represents both an IRQ line and a driver ISR.** The I/O sapic driver can't manage shared IRQ lines because** additional data besides the IRQ number must be passed via** irq_region_ops. do_irq() and request_irq() must manage** a sharing a bit in the mask.**** iosapic_interrupt() replaces do_irq_mask() and calls do_irq().** Which IRQ line was asserted is already known since each** line has unique data associated with it. We could omit** iosapic_interrupt() from the calling path if it did NOT need** to write EOI. For unshared lines, it really doesn't.**** Unfortunately, can't optimize out EOI if IRQ line isn't "shared".** N-class console "device" and some sort of heartbeat actually share** one line though only one driver is registered...<sigh>...this was** true for HP-UX at least. May not be true for parisc-linux.****** Overview of exported iosapic functions** --------------------------------------** (caveat: code isn't finished yet - this is just the plan)**** iosapic_init:** o initialize globals (lock, etc)** o try to read IRT. Presence of IRT determines if this is** a PAT platform or not.**** iosapic_register():** o create iosapic_info instance data structure** o allocate vector_info array for this iosapic** o initialize vector_info - read corresponding IRdT?**** iosapic_xlate_pin: (only called by fixup_irq for PAT platform)** o intr_pin = read cfg (INTERRUPT_PIN);** o if (device under PCI-PCI bridge)** translate slot/pin**** iosapic_fixup_irq:** o if PAT platform (IRT present)** intr_pin = iosapic_xlate_pin(isi,pcidev):** intr_line = find IRT entry(isi, PCI_SLOT(pcidev), intr_pin)** save IRT entry into vector_info later** write cfg INTERRUPT_LINE (with intr_line)?** else** intr_line = pcidev->irq** IRT pointer = NULL** endif** o locate vector_info (needs: isi, intr_line)** o allocate processor "irq" and get txn_addr/data** o request_irq(processor_irq, iosapic_interrupt, vector_info,...)** o pcidev->irq = isi->isi_region...base + intr_line;**** iosapic_interrupt:** o call do_irq(vector->isi->irq_region, vector->irq_line, regs)** o assume level triggered and write EOI**** iosapic_enable_irq:** o clear any pending IRQ on that line** o enable IRdT - call enable_irq(vector[line]->processor_irq)** o write EOI in case line is already asserted.**** iosapic_disable_irq:** o disable IRdT - call disable_irq(vector[line]->processor_irq)**** FIXME: mask/unmask*//* FIXME: determine which include files are really needed */#include <linux/types.h>#include <linux/kernel.h>#include <linux/spinlock.h>#include <linux/pci.h> /* pci cfg accessor functions */#include <linux/init.h>#include <linux/slab.h>#include <linux/smp_lock.h>#include <linux/interrupt.h> /* irqaction */#include <linux/irq.h> /* irq_region support */#include <asm/byteorder.h> /* get in-line asm for swab */#include <asm/pdc.h>#include <asm/pdcpat.h>#include <asm/page.h>#include <asm/segment.h>#include <asm/system.h>#include <asm/gsc.h> /* gsc_read/write functions */#include <asm/iosapic.h>#include "./iosapic_private.h"#define MODULE_NAME "iosapic"/* "local" compile flags */#undef IOSAPIC_CALLBACK#undef PCI_BRIDGE_FUNCS#undef DEBUG_IOSAPIC#undef DEBUG_IOSAPIC_IRT#ifdef DEBUG_IOSAPICstatic char assert_buf[128];static intassert_failed (char *a, char *f, int l){ sprintf(assert_buf, "ASSERT(%s) failed!\nline %d in %s\n", a, /* assertion text */ l, /* line number */ f); /* file name */ panic(assert_buf); return 0;}#undef ASSERT#define ASSERT(EX) { if (!(EX)) assert_failed(# EX, __FILE__, __LINE__); }#define DBG(x...) printk(x)#else /* DEBUG_IOSAPIC */#define DBG(x...)#define ASSERT(EX)#endif /* DEBUG_IOSAPIC */#ifdef DEBUG_IOSAPIC_IRT#define DBG_IRT(x...) printk(x)#else#define DBG_IRT(x...)#endif#define READ_U8(addr) gsc_readb(addr)#define READ_U16(addr) le16_to_cpu(gsc_readw((u16 *) (addr)))#define READ_U32(addr) le32_to_cpu(gsc_readl((u32 *) (addr)))#define READ_REG16(addr) gsc_readw((u16 *) (addr))#define READ_REG32(addr) gsc_readl((u32 *) (addr))#define WRITE_U8(value, addr) gsc_writeb(value, addr)#define WRITE_U16(value, addr) gsc_writew(cpu_to_le16(value), (u16 *) (addr))#define WRITE_U32(value, addr) gsc_writel(cpu_to_le32(value), (u32 *) (addr))#define WRITE_REG16(value, addr) gsc_writew(value, (u16 *) (addr))#define WRITE_REG32(value, addr) gsc_writel(value, (u32 *) (addr))#define IOSAPIC_REG_SELECT 0#define IOSAPIC_REG_WINDOW 0x10#define IOSAPIC_REG_EOI 0x40#define IOSAPIC_REG_VERSION 0x1#define IOSAPIC_IRDT_ENTRY(idx) (0x10+(idx)*2)#define IOSAPIC_IRDT_ENTRY_HI(idx) (0x11+(idx)*2)/*** FIXME: revisit which GFP flags we should really be using.** GFP_KERNEL includes __GFP_WAIT flag and that may not** be acceptable. Since this is boot time, we shouldn't have** to wait ever and this code should (will?) never get called** from the interrrupt context.*/#define IOSAPIC_KALLOC(a_type, cnt) \ (a_type *) kmalloc(sizeof(a_type)*(cnt), GFP_KERNEL)#define IOSAPIC_FREE(addr, f_type, cnt) kfree((void *)addr)#define IOSAPIC_LOCK(lck) spin_lock_irqsave(lck, irqflags)#define IOSAPIC_UNLOCK(lck) spin_unlock_irqrestore(lck, irqflags)#define IOSAPIC_VERSION_MASK 0x000000ff#define IOSAPIC_VERSION_SHIFT 0x0#define IOSAPIC_VERSION(ver) \ (int) ((ver & IOSAPIC_VERSION_MASK) >> IOSAPIC_VERSION_SHIFT)#define IOSAPIC_MAX_ENTRY_MASK 0x00ff0000#define IOSAPIC_MAX_ENTRY_SHIFT 0x10#define IOSAPIC_IRDT_MAX_ENTRY(ver) \ (int) ((ver&IOSAPIC_MAX_ENTRY_MASK) >> IOSAPIC_MAX_ENTRY_SHIFT)/* bits in the "low" I/O Sapic IRdT entry */#define IOSAPIC_IRDT_ENABLE 0x10000#define IOSAPIC_IRDT_PO_LOW 0x02000#define IOSAPIC_IRDT_LEVEL_TRIG 0x08000#define IOSAPIC_IRDT_MODE_LPRI 0x00100/* bits in the "high" I/O Sapic IRdT entry */#define IOSAPIC_IRDT_ID_EID_SHIFT 0x10#define IOSAPIC_EOI(eoi_addr, eoi_data) gsc_writel(eoi_data, eoi_addr)#if IOSAPIC_CALLBACK/*** Shouldn't use callback since SAPIC doesn't have an officially assigned** H or S version numbers. Slight long term risk the number chosen would** collide with something else.** But benefit is cleaner lba/sapic interface.** Might be worth it but for just use direct calls for now.**** Entry below is copied from lba driver.** Only thing different is hw_type.*/static struct pa_iodc_driver iosapic_driver_for[] = { {HPHW_OTHER, 0x782, 0, 0x0000A, 0, 0x00, DRIVER_CHECK_HWTYPE + DRIVER_CHECK_HVERSION + DRIVER_CHECK_SVERSION, "I/O Sapic", "",(void *) iosapic_callback}, {0,0,0,0,0,0, 0, (char *) NULL,(char *) NULL,(void *) NULL} };#endif /* IOSAPIO_CALLBACK */static struct iosapic_info *iosapic_list;static spinlock_t iosapic_lock;static int iosapic_count;/*** REVISIT: future platforms may have more than one IRT.** If so, the following three fields form a structure which** then be linked into a list. Names are chosen to make searching** for them easy - not necessarily accurate (eg "cell").**** Alternative: iosapic_info could point to the IRT it's in.** iosapic_register() could search a list of IRT's.*/static struct irt_entry *irt_cell;static size_t irt_num_entry;/*** iosapic_load_irt**** The "Get PCI INT Routing Table Size" option returns the number of ** entries in the PCI interrupt routing table for the cell specified ** in the cell_number argument. The cell number must be for a cell ** within the caller's protection domain.**** The "Get PCI INT Routing Table" option returns, for the cell ** specified in the cell_number argument, the PCI interrupt routing ** table in the caller allocated memory pointed to by mem_addr.** We assume the IRT only contains entries for I/O SAPIC and** calculate the size based on the size of I/O sapic entries.**** The PCI interrupt routing table entry format is derived from the** IA64 SAL Specification 2.4. The PCI interrupt routing table defines** the routing of PCI interrupt signals between the PCI device output** "pins" and the IO SAPICs' input "lines" (including core I/O PCI** devices). This table does NOT include information for devices/slots** behind PCI to PCI bridges. See PCI to PCI Bridge Architecture Spec.** for the architected method of routing of IRQ's behind PPB's.*/static int __init /* return number of entries as success/fail flag */iosapic_load_irt(unsigned long cell_num, struct irt_entry **irt){ struct pdc_pat_io_num pdc_io_num; /* PAT PDC return block */ long status; /* PDC return value status */ struct irt_entry *table = NULL; /* start of interrupt routing tbl */ unsigned long num_entries = 0UL; ASSERT(NULL != irt); /* FIXME ASSERT(((&pdc_io_num) & (0x3f)) == 0); enforce 32-byte alignment */ /* Try PAT_PDC to get interrupt routing table size */ DBG(KERN_DEBUG "calling get_irt_size\n"); status = pdc_pat_get_irt_size( &pdc_io_num, cell_num); DBG(KERN_DEBUG "get_irt_size: %ld\n", status); switch(status) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -