From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from cam-admin0.cambridge.arm.com (cam-admin0.cambridge.arm.com [217.140.96.50]) by ozlabs.org (Postfix) with ESMTP id 3E690B70A7 for ; Fri, 1 Oct 2010 22:50:14 +1000 (EST) From: "Lorenzo Pieralisi" To: "'Grant Likely'" , , , References: <20100922203117.10426.69278.stgit@angua> In-Reply-To: <20100922203117.10426.69278.stgit@angua> Subject: RE: [RFC] irq: Migrate powerpc virq subsystem into generic code Date: Fri, 1 Oct 2010 13:44:10 +0100 Message-ID: <000b01cb6166$588cebe0$09a6c3a0$@Pieralisi@arm.com> MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" List-Id: Linux on PowerPC Developers Mail List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Hi Grant, Ben, all > -----Original Message----- > From: devicetree-discuss- > bounces+lorenzo.pieralisi=arm.com@lists.ozlabs.org [mailto:devicetree- > discuss-bounces+lorenzo.pieralisi=arm.com@lists.ozlabs.org] On Behalf > Of Grant Likely > Sent: 22 September 2010 21:33 > To: benh@kernel.crashing.org; devicetree-discuss@lists.ozlabs.org; > linuxppc-dev@lists.ozlabs.org > Subject: [RFC] irq: Migrate powerpc virq subsystem into generic code > > Being able to dynamically manage linux irq ranges is useful. Migrate > the powerpc virq code into common code so that other architectures can > use it. > > This patch also removes the unused irq_early_init() references. > > Signed-off-by: Grant Likely > --- > Only compile tested; but I wanted to get this out for comments. I > think this is the right set of routines to generalize for virq on > other architectures. > > g. I have a question on the PowerPC IRQ layer and how to use it for device drivers and device tree in general. A device such as eth smsc911x (example) requires the platform_data pointer to specify irq sense/level to programme the chip accordingly. As agreed with Grant these pieces of information should be retrieved from the device tree, from the interrupt-specifier. Now: the device driver should be interrupt-controller agnostic, so I cannot just retrieve the interrupts property at probe and decode it in order to get the interrupt sense/level bits (the driver has no clue about the interrupt-controller irq flags encoding). I need to associate a irq_host to the interrupt controller node, with a proper xlate function to correctly decode the interrupt-specifier and set the irq type accordingly (irq_create_of_mapping(), for now 1:1 on ARM, so useless from this standpoint). At platform device init time, of_irq_to_resource() is called to parse and map irqs; if we code the irq_host correctly for the ARM GIC for instance the xlate function gets called and irq type set accordingly (and maybe the function could set platform_device IRQ resource flags as well ?) At driver dt probe, from the hwirq number defined in "interrupts" the driver retrieves the virq, hence sense/level flags and use them, or just use the platform_device IRQ resource flags if set properly by the OF layer. Correct ? If yes I will have a stab at it on a ARM platform with complex IRQ routing. Thank you very much. Cheers, Lorenzo > > arch/microblaze/kernel/setup.c | 2 > arch/powerpc/Kconfig | 3 > arch/powerpc/include/asm/irq.h | 270 ---------------- > arch/powerpc/kernel/irq.c | 659 -------------------------------- > ------ > include/linux/virq.h | 302 ++++++++++++++++++ > kernel/irq/Makefile | 1 > kernel/irq/virq.c | 687 > ++++++++++++++++++++++++++++++++++++++++ > 7 files changed, 995 insertions(+), 929 deletions(-) > create mode 100644 include/linux/virq.h > create mode 100644 kernel/irq/virq.c > > diff --git a/arch/microblaze/kernel/setup.c > b/arch/microblaze/kernel/setup.c > index f5f7688..39cf20d 100644 > --- a/arch/microblaze/kernel/setup.c > +++ b/arch/microblaze/kernel/setup.c > @@ -51,8 +51,6 @@ void __init setup_arch(char **cmdline_p) > > unflatten_device_tree(); > > - /* NOTE I think that this function is not necessary to call */ > - /* irq_early_init(); */ > setup_cpuinfo(); > > microblaze_cache_init(); > diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig > index 631e5a0..cc06e59 100644 > --- a/arch/powerpc/Kconfig > +++ b/arch/powerpc/Kconfig > @@ -146,6 +146,9 @@ config EARLY_PRINTK > bool > default y > > +config VIRQ > + def_bool y > + > config COMPAT > bool > default y if PPC64 > diff --git a/arch/powerpc/include/asm/irq.h > b/arch/powerpc/include/asm/irq.h > index 67ab5fb..6dea0cb 100644 > --- a/arch/powerpc/include/asm/irq.h > +++ b/arch/powerpc/include/asm/irq.h > @@ -17,10 +17,6 @@ > #include > > > -/* Define a way to iterate across irqs. */ > -#define for_each_irq(i) \ > - for ((i) = 0; (i) < NR_IRQS; ++(i)) > - > extern atomic_t ppc_n_lost_interrupts; > > /* This number is used when no interrupt has been assigned */ > @@ -41,270 +37,6 @@ extern atomic_t ppc_n_lost_interrupts; > /* Same thing, used by the generic IRQ code */ > #define NR_IRQS_LEGACY NUM_ISA_INTERRUPTS > > -/* This type is the placeholder for a hardware interrupt number. It > has to > - * be big enough to enclose whatever representation is used by a given > - * platform. > - */ > -typedef unsigned long irq_hw_number_t; > - > -/* Interrupt controller "host" data structure. This could be defined > as a > - * irq domain controller. That is, it handles the mapping between > hardware > - * and virtual interrupt numbers for a given interrupt domain. The > host > - * structure is generally created by the PIC code for a given PIC > instance > - * (though a host can cover more than one PIC if they have a flat > number > - * model). It's the host callbacks that are responsible for setting > the > - * irq_chip on a given irq_desc after it's been mapped. > - * > - * The host code and data structures are fairly agnostic to the fact > that > - * we use an open firmware device-tree. We do have references to > struct > - * device_node in two places: in irq_find_host() to find the host > matching > - * a given interrupt controller node, and of course as an argument to > its > - * counterpart host->ops->match() callback. However, those are treated > as > - * generic pointers by the core and the fact that it's actually a > device-node > - * pointer is purely a convention between callers and implementation. > This > - * code could thus be used on other architectures by replacing those > two > - * by some sort of arch-specific void * "token" used to identify > interrupt > - * controllers. > - */ > -struct irq_host; > -struct radix_tree_root; > - > -/* Functions below are provided by the host and called whenever a new > mapping > - * is created or an old mapping is disposed. The host can then proceed > to > - * whatever internal data structures management is required. It also > needs > - * to setup the irq_desc when returning from map(). > - */ > -struct irq_host_ops { > - /* Match an interrupt controller device node to a host, returns > - * 1 on a match > - */ > - int (*match)(struct irq_host *h, struct device_node *node); > - > - /* Create or update a mapping between a virtual irq number and a > hw > - * irq number. This is called only once for a given mapping. > - */ > - int (*map)(struct irq_host *h, unsigned int virq, irq_hw_number_t > hw); > - > - /* Dispose of such a mapping */ > - void (*unmap)(struct irq_host *h, unsigned int virq); > - > - /* Update of such a mapping */ > - void (*remap)(struct irq_host *h, unsigned int virq, > irq_hw_number_t hw); > - > - /* Translate device-tree interrupt specifier from raw format > coming > - * from the firmware to a irq_hw_number_t (interrupt line number) > and > - * type (sense) that can be passed to set_irq_type(). In the > absence > - * of this callback, irq_create_of_mapping() and > irq_of_parse_and_map() > - * will return the hw number in the first cell and IRQ_TYPE_NONE > for > - * the type (which amount to keeping whatever default value the > - * interrupt controller has for that line) > - */ > - int (*xlate)(struct irq_host *h, struct device_node *ctrler, > - const u32 *intspec, unsigned int intsize, > - irq_hw_number_t *out_hwirq, unsigned int *out_type); > -}; > - > -struct irq_host { > - struct list_head link; > - > - /* type of reverse mapping technique */ > - unsigned int revmap_type; > -#define IRQ_HOST_MAP_LEGACY 0 /* legacy 8259, gets irqs 1..15 */ > -#define IRQ_HOST_MAP_NOMAP 1 /* no fast reverse mapping */ > -#define IRQ_HOST_MAP_LINEAR 2 /* linear map of interrupts */ > -#define IRQ_HOST_MAP_TREE 3 /* radix tree */ > - union { > - struct { > - unsigned int size; > - unsigned int *revmap; > - } linear; > - struct radix_tree_root tree; > - } revmap_data; > - struct irq_host_ops *ops; > - void *host_data; > - irq_hw_number_t inval_irq; > - > - /* Optional device node pointer */ > - struct device_node *of_node; > -}; > - > -/* The main irq map itself is an array of NR_IRQ entries containing > the > - * associate host and irq number. An entry with a host of NULL is > free. > - * An entry can be allocated if it's free, the allocator always then > sets > - * hwirq first to the host's invalid irq number and then fills ops. > - */ > -struct irq_map_entry { > - irq_hw_number_t hwirq; > - struct irq_host *host; > -}; > - > -extern struct irq_map_entry irq_map[NR_IRQS]; > - > -extern irq_hw_number_t virq_to_hw(unsigned int virq); > - > -/** > - * irq_alloc_host - Allocate a new irq_host data structure > - * @of_node: optional device-tree node of the interrupt controller > - * @revmap_type: type of reverse mapping to use > - * @revmap_arg: for IRQ_HOST_MAP_LINEAR linear only: size of the map > - * @ops: map/unmap host callbacks > - * @inval_irq: provide a hw number in that host space that is always > invalid > - * > - * Allocates and initialize and irq_host structure. Note that in the > case of > - * IRQ_HOST_MAP_LEGACY, the map() callback will be called before this > returns > - * for all legacy interrupts except 0 (which is always the invalid irq > for > - * a legacy controller). For a IRQ_HOST_MAP_LINEAR, the map is > allocated by > - * this call as well. For a IRQ_HOST_MAP_TREE, the radix tree will be > allocated > - * later during boot automatically (the reverse mapping will use the > slow path > - * until that happens). > - */ > -extern struct irq_host *irq_alloc_host(struct device_node *of_node, > - unsigned int revmap_type, > - unsigned int revmap_arg, > - struct irq_host_ops *ops, > - irq_hw_number_t inval_irq); > - > - > -/** > - * irq_find_host - Locates a host for a given device node > - * @node: device-tree node of the interrupt controller > - */ > -extern struct irq_host *irq_find_host(struct device_node *node); > - > - > -/** > - * irq_set_default_host - Set a "default" host > - * @host: default host pointer > - * > - * For convenience, it's possible to set a "default" host that will be > used > - * whenever NULL is passed to irq_create_mapping(). It makes life > easier for > - * platforms that want to manipulate a few hard coded interrupt > numbers that > - * aren't properly represented in the device-tree. > - */ > -extern void irq_set_default_host(struct irq_host *host); > - > - > -/** > - * irq_set_virq_count - Set the maximum number of virt irqs > - * @count: number of linux virtual irqs, capped with NR_IRQS > - * > - * This is mainly for use by platforms like iSeries who want to > program > - * the virtual irq number in the controller to avoid the reverse > mapping > - */ > -extern void irq_set_virq_count(unsigned int count); > - > - > -/** > - * irq_create_mapping - Map a hardware interrupt into linux virq space > - * @host: host owning this hardware interrupt or NULL for default host > - * @hwirq: hardware irq number in that host space > - * > - * Only one mapping per hardware interrupt is permitted. Returns a > linux > - * virq number. > - * If the sense/trigger is to be specified, set_irq_type() should be > called > - * on the number returned from that call. > - */ > -extern unsigned int irq_create_mapping(struct irq_host *host, > - irq_hw_number_t hwirq); > - > - > -/** > - * irq_dispose_mapping - Unmap an interrupt > - * @virq: linux virq number of the interrupt to unmap > - */ > -extern void irq_dispose_mapping(unsigned int virq); > - > -/** > - * irq_find_mapping - Find a linux virq from an hw irq number. > - * @host: host owning this hardware interrupt > - * @hwirq: hardware irq number in that host space > - * > - * This is a slow path, for use by generic code. It's expected that an > - * irq controller implementation directly calls the appropriate low > level > - * mapping function. > - */ > -extern unsigned int irq_find_mapping(struct irq_host *host, > - irq_hw_number_t hwirq); > - > -/** > - * irq_create_direct_mapping - Allocate a virq for direct mapping > - * @host: host to allocate the virq for or NULL for default host > - * > - * This routine is used for irq controllers which can choose the > hardware > - * interrupt numbers they generate. In such a case it's simplest to > use > - * the linux virq as the hardware interrupt number. > - */ > -extern unsigned int irq_create_direct_mapping(struct irq_host *host); > - > -/** > - * irq_radix_revmap_insert - Insert a hw irq to linux virq number > mapping. > - * @host: host owning this hardware interrupt > - * @virq: linux irq number > - * @hwirq: hardware irq number in that host space > - * > - * This is for use by irq controllers that use a radix tree reverse > - * mapping for fast lookup. > - */ > -extern void irq_radix_revmap_insert(struct irq_host *host, unsigned > int virq, > - irq_hw_number_t hwirq); > - > -/** > - * irq_radix_revmap_lookup - Find a linux virq from a hw irq number. > - * @host: host owning this hardware interrupt > - * @hwirq: hardware irq number in that host space > - * > - * This is a fast path, for use by irq controller code that uses radix > tree > - * revmaps > - */ > -extern unsigned int irq_radix_revmap_lookup(struct irq_host *host, > - irq_hw_number_t hwirq); > - > -/** > - * irq_linear_revmap - Find a linux virq from a hw irq number. > - * @host: host owning this hardware interrupt > - * @hwirq: hardware irq number in that host space > - * > - * This is a fast path, for use by irq controller code that uses > linear > - * revmaps. It does fallback to the slow path if the revmap doesn't > exist > - * yet and will create the revmap entry with appropriate locking > - */ > - > -extern unsigned int irq_linear_revmap(struct irq_host *host, > - irq_hw_number_t hwirq); > - > - > - > -/** > - * irq_alloc_virt - Allocate virtual irq numbers > - * @host: host owning these new virtual irqs > - * @count: number of consecutive numbers to allocate > - * @hint: pass a hint number, the allocator will try to use a 1:1 > mapping > - * > - * This is a low level function that is used internally by > irq_create_mapping() > - * and that can be used by some irq controllers implementations for > things > - * like allocating ranges of numbers for MSIs. The revmaps are left > untouched. > - */ > -extern unsigned int irq_alloc_virt(struct irq_host *host, > - unsigned int count, > - unsigned int hint); > - > -/** > - * irq_free_virt - Free virtual irq numbers > - * @virq: virtual irq number of the first interrupt to free > - * @count: number of interrupts to free > - * > - * This function is the opposite of irq_alloc_virt. It will not clear > reverse > - * maps, this should be done previously by unmap'ing the interrupt. In > fact, > - * all interrupts covered by the range being freed should have been > unmapped > - * prior to calling this. > - */ > -extern void irq_free_virt(unsigned int virq, unsigned int count); > - > -/** > - * irq_early_init - Init irq remapping subsystem > - */ > -extern void irq_early_init(void); > - > static __inline__ int irq_canonicalize(int irq) > { > return irq; > @@ -342,5 +74,7 @@ extern int call_handle_irq(int irq, void *p1, > struct thread_info *tp, void *func); > extern void do_IRQ(struct pt_regs *regs); > > +#include > + > #endif /* _ASM_IRQ_H */ > #endif /* __KERNEL__ */ > diff --git a/arch/powerpc/kernel/irq.c b/arch/powerpc/kernel/irq.c > index 4a65386..86d8e42 100644 > --- a/arch/powerpc/kernel/irq.c > +++ b/arch/powerpc/kernel/irq.c > @@ -523,553 +523,6 @@ void do_softirq(void) > } > > > -/* > - * IRQ controller and virtual interrupts > - */ > - > -static LIST_HEAD(irq_hosts); > -static DEFINE_RAW_SPINLOCK(irq_big_lock); > -static unsigned int revmap_trees_allocated; > -static DEFINE_MUTEX(revmap_trees_mutex); > -struct irq_map_entry irq_map[NR_IRQS]; > -static unsigned int irq_virq_count = NR_IRQS; > -static struct irq_host *irq_default_host; > - > -irq_hw_number_t virq_to_hw(unsigned int virq) > -{ > - return irq_map[virq].hwirq; > -} > -EXPORT_SYMBOL_GPL(virq_to_hw); > - > -static int default_irq_host_match(struct irq_host *h, struct > device_node *np) > -{ > - return h->of_node != NULL && h->of_node == np; > -} > - > -struct irq_host *irq_alloc_host(struct device_node *of_node, > - unsigned int revmap_type, > - unsigned int revmap_arg, > - struct irq_host_ops *ops, > - irq_hw_number_t inval_irq) > -{ > - struct irq_host *host; > - unsigned int size = sizeof(struct irq_host); > - unsigned int i; > - unsigned int *rmap; > - unsigned long flags; > - > - /* Allocate structure and revmap table if using linear mapping */ > - if (revmap_type == IRQ_HOST_MAP_LINEAR) > - size += revmap_arg * sizeof(unsigned int); > - host = zalloc_maybe_bootmem(size, GFP_KERNEL); > - if (host == NULL) > - return NULL; > - > - /* Fill structure */ > - host->revmap_type = revmap_type; > - host->inval_irq = inval_irq; > - host->ops = ops; > - host->of_node = of_node_get(of_node); > - > - if (host->ops->match == NULL) > - host->ops->match = default_irq_host_match; > - > - raw_spin_lock_irqsave(&irq_big_lock, flags); > - > - /* If it's a legacy controller, check for duplicates and > - * mark it as allocated (we use irq 0 host pointer for that > - */ > - if (revmap_type == IRQ_HOST_MAP_LEGACY) { > - if (irq_map[0].host != NULL) { > - raw_spin_unlock_irqrestore(&irq_big_lock, flags); > - /* If we are early boot, we can't free the structure, > - * too bad... > - * this will be fixed once slab is made available > early > - * instead of the current cruft > - */ > - if (mem_init_done) > - kfree(host); > - return NULL; > - } > - irq_map[0].host = host; > - } > - > - list_add(&host->link, &irq_hosts); > - raw_spin_unlock_irqrestore(&irq_big_lock, flags); > - > - /* Additional setups per revmap type */ > - switch(revmap_type) { > - case IRQ_HOST_MAP_LEGACY: > - /* 0 is always the invalid number for legacy */ > - host->inval_irq = 0; > - /* setup us as the host for all legacy interrupts */ > - for (i = 1; i < NUM_ISA_INTERRUPTS; i++) { > - irq_map[i].hwirq = i; > - smp_wmb(); > - irq_map[i].host = host; > - smp_wmb(); > - > - /* Clear norequest flags */ > - irq_to_desc(i)->status &= ~IRQ_NOREQUEST; > - > - /* Legacy flags are left to default at this point, > - * one can then use irq_create_mapping() to > - * explicitly change them > - */ > - ops->map(host, i, i); > - } > - break; > - case IRQ_HOST_MAP_LINEAR: > - rmap = (unsigned int *)(host + 1); > - for (i = 0; i < revmap_arg; i++) > - rmap[i] = NO_IRQ; > - host->revmap_data.linear.size = revmap_arg; > - smp_wmb(); > - host->revmap_data.linear.revmap = rmap; > - break; > - default: > - break; > - } > - > - pr_debug("irq: Allocated host of type %d @0x%p\n", revmap_type, > host); > - > - return host; > -} > - > -struct irq_host *irq_find_host(struct device_node *node) > -{ > - struct irq_host *h, *found = NULL; > - unsigned long flags; > - > - /* We might want to match the legacy controller last since > - * it might potentially be set to match all interrupts in > - * the absence of a device node. This isn't a problem so far > - * yet though... > - */ > - raw_spin_lock_irqsave(&irq_big_lock, flags); > - list_for_each_entry(h, &irq_hosts, link) > - if (h->ops->match(h, node)) { > - found = h; > - break; > - } > - raw_spin_unlock_irqrestore(&irq_big_lock, flags); > - return found; > -} > -EXPORT_SYMBOL_GPL(irq_find_host); > - > -void irq_set_default_host(struct irq_host *host) > -{ > - pr_debug("irq: Default host set to @0x%p\n", host); > - > - irq_default_host = host; > -} > - > -void irq_set_virq_count(unsigned int count) > -{ > - pr_debug("irq: Trying to set virq count to %d\n", count); > - > - BUG_ON(count < NUM_ISA_INTERRUPTS); > - if (count < NR_IRQS) > - irq_virq_count = count; > -} > - > -static int irq_setup_virq(struct irq_host *host, unsigned int virq, > - irq_hw_number_t hwirq) > -{ > - struct irq_desc *desc; > - > - desc = irq_to_desc_alloc_node(virq, 0); > - if (!desc) { > - pr_debug("irq: -> allocating desc failed\n"); > - goto error; > - } > - > - /* Clear IRQ_NOREQUEST flag */ > - desc->status &= ~IRQ_NOREQUEST; > - > - /* map it */ > - smp_wmb(); > - irq_map[virq].hwirq = hwirq; > - smp_mb(); > - > - if (host->ops->map(host, virq, hwirq)) { > - pr_debug("irq: -> mapping failed, freeing\n"); > - goto error; > - } > - > - return 0; > - > -error: > - irq_free_virt(virq, 1); > - return -1; > -} > - > -unsigned int irq_create_direct_mapping(struct irq_host *host) > -{ > - unsigned int virq; > - > - if (host == NULL) > - host = irq_default_host; > - > - BUG_ON(host == NULL); > - WARN_ON(host->revmap_type != IRQ_HOST_MAP_NOMAP); > - > - virq = irq_alloc_virt(host, 1, 0); > - if (virq == NO_IRQ) { > - pr_debug("irq: create_direct virq allocation failed\n"); > - return NO_IRQ; > - } > - > - pr_debug("irq: create_direct obtained virq %d\n", virq); > - > - if (irq_setup_virq(host, virq, virq)) > - return NO_IRQ; > - > - return virq; > -} > - > -unsigned int irq_create_mapping(struct irq_host *host, > - irq_hw_number_t hwirq) > -{ > - unsigned int virq, hint; > - > - pr_debug("irq: irq_create_mapping(0x%p, 0x%lx)\n", host, hwirq); > - > - /* Look for default host if nececssary */ > - if (host == NULL) > - host = irq_default_host; > - if (host == NULL) { > - printk(KERN_WARNING "irq_create_mapping called for" > - " NULL host, hwirq=%lx\n", hwirq); > - WARN_ON(1); > - return NO_IRQ; > - } > - pr_debug("irq: -> using host @%p\n", host); > - > - /* Check if mapping already exist, if it does, call > - * host->ops->map() to update the flags > - */ > - virq = irq_find_mapping(host, hwirq); > - if (virq != NO_IRQ) { > - if (host->ops->remap) > - host->ops->remap(host, virq, hwirq); > - pr_debug("irq: -> existing mapping on virq %d\n", virq); > - return virq; > - } > - > - /* Get a virtual interrupt number */ > - if (host->revmap_type == IRQ_HOST_MAP_LEGACY) { > - /* Handle legacy */ > - virq = (unsigned int)hwirq; > - if (virq == 0 || virq >= NUM_ISA_INTERRUPTS) > - return NO_IRQ; > - return virq; > - } else { > - /* Allocate a virtual interrupt number */ > - hint = hwirq % irq_virq_count; > - virq = irq_alloc_virt(host, 1, hint); > - if (virq == NO_IRQ) { > - pr_debug("irq: -> virq allocation failed\n"); > - return NO_IRQ; > - } > - } > - > - if (irq_setup_virq(host, virq, hwirq)) > - return NO_IRQ; > - > - printk(KERN_DEBUG "irq: irq %lu on host %s mapped to virtual irq > %u\n", > - hwirq, host->of_node ? host->of_node->full_name : "null", > virq); > - > - return virq; > -} > -EXPORT_SYMBOL_GPL(irq_create_mapping); > - > -unsigned int irq_create_of_mapping(struct device_node *controller, > - const u32 *intspec, unsigned int intsize) > -{ > - struct irq_host *host; > - irq_hw_number_t hwirq; > - unsigned int type = IRQ_TYPE_NONE; > - unsigned int virq; > - > - if (controller == NULL) > - host = irq_default_host; > - else > - host = irq_find_host(controller); > - if (host == NULL) { > - printk(KERN_WARNING "irq: no irq host found for %s !\n", > - controller->full_name); > - return NO_IRQ; > - } > - > - /* If host has no translation, then we assume interrupt line */ > - if (host->ops->xlate == NULL) > - hwirq = intspec[0]; > - else { > - if (host->ops->xlate(host, controller, intspec, intsize, > - &hwirq, &type)) > - return NO_IRQ; > - } > - > - /* Create mapping */ > - virq = irq_create_mapping(host, hwirq); > - if (virq == NO_IRQ) > - return virq; > - > - /* Set type if specified and different than the current one */ > - if (type != IRQ_TYPE_NONE && > - type != (irq_to_desc(virq)->status & IRQF_TRIGGER_MASK)) > - set_irq_type(virq, type); > - return virq; > -} > -EXPORT_SYMBOL_GPL(irq_create_of_mapping); > - > -void irq_dispose_mapping(unsigned int virq) > -{ > - struct irq_host *host; > - irq_hw_number_t hwirq; > - > - if (virq == NO_IRQ) > - return; > - > - host = irq_map[virq].host; > - WARN_ON (host == NULL); > - if (host == NULL) > - return; > - > - /* Never unmap legacy interrupts */ > - if (host->revmap_type == IRQ_HOST_MAP_LEGACY) > - return; > - > - /* remove chip and handler */ > - set_irq_chip_and_handler(virq, NULL, NULL); > - > - /* Make sure it's completed */ > - synchronize_irq(virq); > - > - /* Tell the PIC about it */ > - if (host->ops->unmap) > - host->ops->unmap(host, virq); > - smp_mb(); > - > - /* Clear reverse map */ > - hwirq = irq_map[virq].hwirq; > - switch(host->revmap_type) { > - case IRQ_HOST_MAP_LINEAR: > - if (hwirq < host->revmap_data.linear.size) > - host->revmap_data.linear.revmap[hwirq] = NO_IRQ; > - break; > - case IRQ_HOST_MAP_TREE: > - /* > - * Check if radix tree allocated yet, if not then nothing > to > - * remove. > - */ > - smp_rmb(); > - if (revmap_trees_allocated < 1) > - break; > - mutex_lock(&revmap_trees_mutex); > - radix_tree_delete(&host->revmap_data.tree, hwirq); > - mutex_unlock(&revmap_trees_mutex); > - break; > - } > - > - /* Destroy map */ > - smp_mb(); > - irq_map[virq].hwirq = host->inval_irq; > - > - /* Set some flags */ > - irq_to_desc(virq)->status |= IRQ_NOREQUEST; > - > - /* Free it */ > - irq_free_virt(virq, 1); > -} > -EXPORT_SYMBOL_GPL(irq_dispose_mapping); > - > -unsigned int irq_find_mapping(struct irq_host *host, > - irq_hw_number_t hwirq) > -{ > - unsigned int i; > - unsigned int hint = hwirq % irq_virq_count; > - > - /* Look for default host if nececssary */ > - if (host == NULL) > - host = irq_default_host; > - if (host == NULL) > - return NO_IRQ; > - > - /* legacy -> bail early */ > - if (host->revmap_type == IRQ_HOST_MAP_LEGACY) > - return hwirq; > - > - /* Slow path does a linear search of the map */ > - if (hint < NUM_ISA_INTERRUPTS) > - hint = NUM_ISA_INTERRUPTS; > - i = hint; > - do { > - if (irq_map[i].host == host && > - irq_map[i].hwirq == hwirq) > - return i; > - i++; > - if (i >= irq_virq_count) > - i = NUM_ISA_INTERRUPTS; > - } while(i != hint); > - return NO_IRQ; > -} > -EXPORT_SYMBOL_GPL(irq_find_mapping); > - > - > -unsigned int irq_radix_revmap_lookup(struct irq_host *host, > - irq_hw_number_t hwirq) > -{ > - struct irq_map_entry *ptr; > - unsigned int virq; > - > - WARN_ON(host->revmap_type != IRQ_HOST_MAP_TREE); > - > - /* > - * Check if the radix tree exists and has bee initialized. > - * If not, we fallback to slow mode > - */ > - if (revmap_trees_allocated < 2) > - return irq_find_mapping(host, hwirq); > - > - /* Now try to resolve */ > - /* > - * No rcu_read_lock(ing) needed, the ptr returned can't go under > us > - * as it's referencing an entry in the static irq_map table. > - */ > - ptr = radix_tree_lookup(&host->revmap_data.tree, hwirq); > - > - /* > - * If found in radix tree, then fine. > - * Else fallback to linear lookup - this should not happen in > practice > - * as it means that we failed to insert the node in the radix > tree. > - */ > - if (ptr) > - virq = ptr - irq_map; > - else > - virq = irq_find_mapping(host, hwirq); > - > - return virq; > -} > - > -void irq_radix_revmap_insert(struct irq_host *host, unsigned int virq, > - irq_hw_number_t hwirq) > -{ > - > - WARN_ON(host->revmap_type != IRQ_HOST_MAP_TREE); > - > - /* > - * Check if the radix tree exists yet. > - * If not, then the irq will be inserted into the tree when it > gets > - * initialized. > - */ > - smp_rmb(); > - if (revmap_trees_allocated < 1) > - return; > - > - if (virq != NO_IRQ) { > - mutex_lock(&revmap_trees_mutex); > - radix_tree_insert(&host->revmap_data.tree, hwirq, > - &irq_map[virq]); > - mutex_unlock(&revmap_trees_mutex); > - } > -} > - > -unsigned int irq_linear_revmap(struct irq_host *host, > - irq_hw_number_t hwirq) > -{ > - unsigned int *revmap; > - > - WARN_ON(host->revmap_type != IRQ_HOST_MAP_LINEAR); > - > - /* Check revmap bounds */ > - if (unlikely(hwirq >= host->revmap_data.linear.size)) > - return irq_find_mapping(host, hwirq); > - > - /* Check if revmap was allocated */ > - revmap = host->revmap_data.linear.revmap; > - if (unlikely(revmap == NULL)) > - return irq_find_mapping(host, hwirq); > - > - /* Fill up revmap with slow path if no mapping found */ > - if (unlikely(revmap[hwirq] == NO_IRQ)) > - revmap[hwirq] = irq_find_mapping(host, hwirq); > - > - return revmap[hwirq]; > -} > - > -unsigned int irq_alloc_virt(struct irq_host *host, > - unsigned int count, > - unsigned int hint) > -{ > - unsigned long flags; > - unsigned int i, j, found = NO_IRQ; > - > - if (count == 0 || count > (irq_virq_count - NUM_ISA_INTERRUPTS)) > - return NO_IRQ; > - > - raw_spin_lock_irqsave(&irq_big_lock, flags); > - > - /* Use hint for 1 interrupt if any */ > - if (count == 1 && hint >= NUM_ISA_INTERRUPTS && > - hint < irq_virq_count && irq_map[hint].host == NULL) { > - found = hint; > - goto hint_found; > - } > - > - /* Look for count consecutive numbers in the allocatable > - * (non-legacy) space > - */ > - for (i = NUM_ISA_INTERRUPTS, j = 0; i < irq_virq_count; i++) { > - if (irq_map[i].host != NULL) > - j = 0; > - else > - j++; > - > - if (j == count) { > - found = i - count + 1; > - break; > - } > - } > - if (found == NO_IRQ) { > - raw_spin_unlock_irqrestore(&irq_big_lock, flags); > - return NO_IRQ; > - } > - hint_found: > - for (i = found; i < (found + count); i++) { > - irq_map[i].hwirq = host->inval_irq; > - smp_wmb(); > - irq_map[i].host = host; > - } > - raw_spin_unlock_irqrestore(&irq_big_lock, flags); > - return found; > -} > - > -void irq_free_virt(unsigned int virq, unsigned int count) > -{ > - unsigned long flags; > - unsigned int i; > - > - WARN_ON (virq < NUM_ISA_INTERRUPTS); > - WARN_ON (count == 0 || (virq + count) > irq_virq_count); > - > - raw_spin_lock_irqsave(&irq_big_lock, flags); > - for (i = virq; i < (virq + count); i++) { > - struct irq_host *host; > - > - if (i < NUM_ISA_INTERRUPTS || > - (virq + count) > irq_virq_count) > - continue; > - > - host = irq_map[i].host; > - irq_map[i].hwirq = host->inval_irq; > - smp_wmb(); > - irq_map[i].host = NULL; > - } > - raw_spin_unlock_irqrestore(&irq_big_lock, flags); > -} > - > int arch_early_irq_init(void) > { > struct irq_desc *desc; > @@ -1090,118 +543,6 @@ int arch_init_chip_data(struct irq_desc *desc, > int node) > return 0; > } > > -/* We need to create the radix trees late */ > -static int irq_late_init(void) > -{ > - struct irq_host *h; > - unsigned int i; > - > - /* > - * No mutual exclusion with respect to accessors of the tree is > needed > - * here as the synchronization is done via the state variable > - * revmap_trees_allocated. > - */ > - list_for_each_entry(h, &irq_hosts, link) { > - if (h->revmap_type == IRQ_HOST_MAP_TREE) > - INIT_RADIX_TREE(&h->revmap_data.tree, GFP_KERNEL); > - } > - > - /* > - * Make sure the radix trees inits are visible before setting > - * the flag > - */ > - smp_wmb(); > - revmap_trees_allocated = 1; > - > - /* > - * Insert the reverse mapping for those interrupts already > present > - * in irq_map[]. > - */ > - mutex_lock(&revmap_trees_mutex); > - for (i = 0; i < irq_virq_count; i++) { > - if (irq_map[i].host && > - (irq_map[i].host->revmap_type == IRQ_HOST_MAP_TREE)) > - radix_tree_insert(&irq_map[i].host->revmap_data.tree, > - irq_map[i].hwirq, &irq_map[i]); > - } > - mutex_unlock(&revmap_trees_mutex); > - > - /* > - * Make sure the radix trees insertions are visible before > setting > - * the flag > - */ > - smp_wmb(); > - revmap_trees_allocated = 2; > - > - return 0; > -} > -arch_initcall(irq_late_init); > - > -#ifdef CONFIG_VIRQ_DEBUG > -static int virq_debug_show(struct seq_file *m, void *private) > -{ > - unsigned long flags; > - struct irq_desc *desc; > - const char *p; > - char none[] = "none"; > - int i; > - > - seq_printf(m, "%-5s %-7s %-15s %s\n", "virq", "hwirq", > - "chip name", "host name"); > - > - for (i = 1; i < nr_irqs; i++) { > - desc = irq_to_desc(i); > - if (!desc) > - continue; > - > - raw_spin_lock_irqsave(&desc->lock, flags); > - > - if (desc->action && desc->action->handler) { > - seq_printf(m, "%5d ", i); > - seq_printf(m, "0x%05lx ", virq_to_hw(i)); > - > - if (desc->chip && desc->chip->name) > - p = desc->chip->name; > - else > - p = none; > - seq_printf(m, "%-15s ", p); > - > - if (irq_map[i].host && irq_map[i].host->of_node) > - p = irq_map[i].host->of_node->full_name; > - else > - p = none; > - seq_printf(m, "%s\n", p); > - } > - > - raw_spin_unlock_irqrestore(&desc->lock, flags); > - } > - > - return 0; > -} > - > -static int virq_debug_open(struct inode *inode, struct file *file) > -{ > - return single_open(file, virq_debug_show, inode->i_private); > -} > - > -static const struct file_operations virq_debug_fops = { > - .open = virq_debug_open, > - .read = seq_read, > - .llseek = seq_lseek, > - .release = single_release, > -}; > - > -static int __init irq_debugfs_init(void) > -{ > - if (debugfs_create_file("virq_mapping", S_IRUGO, > powerpc_debugfs_root, > - NULL, &virq_debug_fops) == NULL) > - return -ENOMEM; > - > - return 0; > -} > -__initcall(irq_debugfs_init); > -#endif /* CONFIG_VIRQ_DEBUG */ > - > #ifdef CONFIG_PPC64 > static int __init setup_noirqdistrib(char *str) > { > diff --git a/include/linux/virq.h b/include/linux/virq.h > new file mode 100644 > index 0000000..06035ef > --- /dev/null > +++ b/include/linux/virq.h > @@ -0,0 +1,302 @@ > +/* > + * Virtual IRQ infrastructure > + * > + * Virtual IRQs provides support for dynamically allocating ranges of > IRQ > + * numbers for use by interrupt controllers. > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of version 2 of the GNU General Public > + * License as published by the Free Software Foundation. > + */ > + > + > +#ifdef __KERNEL__ > +#ifndef _LINUX_VIRQ_H > +#define _LINUX_VIRQ_H > + > +#include > + > +#ifdef CONFIG_VIRQ > + > +/* Define a way to iterate across irqs. */ > +#define for_each_irq(i) \ > + for ((i) = 0; (i) < NR_IRQS; ++(i)) > + > +/* This type is the placeholder for a hardware interrupt number. It > has to > + * be big enough to enclose whatever representation is used by a given > + * platform. > + */ > +typedef unsigned long irq_hw_number_t; > + > +/* Interrupt controller "host" data structure. This could be defined > as a > + * irq domain controller. That is, it handles the mapping between > hardware > + * and virtual interrupt numbers for a given interrupt domain. The > host > + * structure is generally created by the PIC code for a given PIC > instance > + * (though a host can cover more than one PIC if they have a flat > number > + * model). It's the host callbacks that are responsible for setting > the > + * irq_chip on a given irq_desc after it's been mapped. > + * > + * The host code and data structures are fairly agnostic to the fact > that > + * we use an open firmware device-tree. We do have references to > struct > + * device_node in two places: in irq_find_host() to find the host > matching > + * a given interrupt controller node, and of course as an argument to > its > + * counterpart host->ops->match() callback. However, those are treated > as > + * generic pointers by the core and the fact that it's actually a > device-node > + * pointer is purely a convention between callers and implementation. > This > + * code could thus be used on other architectures by replacing those > two > + * by some sort of arch-specific void * "token" used to identify > interrupt > + * controllers. > + */ > +struct irq_host; > +struct radix_tree_root; > +struct device_node; > + > +/** > + * struct irq_host_ops - operations for managing per-domain hw irq > numbers > + * > + * Functions below are provided by the host and called whenever a new > mapping > + * is created or an old mapping is disposed. The host can then proceed > to > + * whatever internal data structures management is required. It also > needs > + * to setup the irq_desc when returning from map(). > + */ > +struct irq_host_ops { > + /* Match an interrupt controller device node to a host, returns > + * 1 on a match > + */ > + int (*match)(struct irq_host *h, struct device_node *node); > + > + /* Create or update a mapping between a virtual irq number and a > hw > + * irq number. This is called only once for a given mapping. > + */ > + int (*map)(struct irq_host *h, unsigned int virq, irq_hw_number_t > hw); > + > + /* Dispose of such a mapping */ > + void (*unmap)(struct irq_host *h, unsigned int virq); > + > + /* Update of such a mapping */ > + void (*remap)(struct irq_host *h, unsigned int virq, > irq_hw_number_t hw); > + > + /* Translate device-tree interrupt specifier from raw format > coming > + * from the firmware to a irq_hw_number_t (interrupt line number) > and > + * type (sense) that can be passed to set_irq_type(). In the > absence > + * of this callback, irq_create_of_mapping() and > irq_of_parse_and_map() > + * will return the hw number in the first cell and IRQ_TYPE_NONE > for > + * the type (which amount to keeping whatever default value the > + * interrupt controller has for that line) > + */ > + int (*xlate)(struct irq_host *h, struct device_node *ctrler, > + const u32 *intspec, unsigned int intsize, > + irq_hw_number_t *out_hwirq, unsigned int *out_type); > +}; > + > +/** > + * struct irq_host - a single irq domain. maps hw irq numbers to Linux > irq. > + * @link: entry in global irq_host list > + * @revmap_type: Method of reverse mapping hwirq to Linux irq number > + * @revmap_data: reverse map data > + * @ops: irq domain operations (documented above) > + * @host_data: irq controller driver data; core does not touch this > pointer > + * @inval_irq: hw irq number used for unassigned virqs > + * @of_node: Optional pointer to the irq controllers device tree node. > + * > + * One irq_host is allocated for each range (domain) of Linux irq > numbers > + * allocated. Typically, one irq_host is allocated per controller, > but it > + * is perfectly valid to manage multiple controllers with a single > irq_host > + * instance if need be. > + */ > +struct irq_host { > + struct list_head link; > + > + /* type of reverse mapping technique */ > + unsigned int revmap_type; > +#define IRQ_HOST_MAP_LEGACY 0 /* legacy 8259, gets irqs 1..15 */ > +#define IRQ_HOST_MAP_NOMAP 1 /* no fast reverse mapping */ > +#define IRQ_HOST_MAP_LINEAR 2 /* linear map of interrupts */ > +#define IRQ_HOST_MAP_TREE 3 /* radix tree */ > + union { > + struct { > + unsigned int size; > + unsigned int *revmap; > + } linear; > + struct radix_tree_root tree; > + } revmap_data; > + struct irq_host_ops *ops; > + void *host_data; > + irq_hw_number_t inval_irq; > + > + /* Optional device node pointer */ > + struct device_node *of_node; > +}; > + > +/** > + * irq_alloc_host() - Allocate a new irq_host data structure > + * @of_node: optional device-tree node of the interrupt controller > + * @revmap_type: type of reverse mapping to use > + * @revmap_arg: for IRQ_HOST_MAP_LINEAR linear only: size of the map > + * @ops: map/unmap host callbacks > + * @inval_irq: provide a hw number in that host space that is always > invalid > + * > + * Allocates and initialize and irq_host structure. Note that in the > case of > + * IRQ_HOST_MAP_LEGACY, the map() callback will be called before this > returns > + * for all legacy interrupts except 0 (which is always the invalid irq > for > + * a legacy controller). For a IRQ_HOST_MAP_LINEAR, the map is > allocated by > + * this call as well. For a IRQ_HOST_MAP_TREE, the radix tree will be > allocated > + * later during boot automatically (the reverse mapping will use the > slow path > + * until that happens). > + */ > +extern struct irq_host *irq_alloc_host(struct device_node *of_node, > + unsigned int revmap_type, > + unsigned int revmap_arg, > + struct irq_host_ops *ops, > + irq_hw_number_t inval_irq); > + > +/* The main irq map itself is an array of NR_IRQ entries containing > the > + * associate host and irq number. An entry with a host of NULL is > free. > + * An entry can be allocated if it's free, the allocator always then > sets > + * hwirq first to the host's invalid irq number and then fills ops. > + */ > +struct irq_map_entry { > + irq_hw_number_t hwirq; > + struct irq_host *host; > +}; > +extern struct irq_map_entry irq_map[NR_IRQS]; > + > +extern irq_hw_number_t virq_to_hw(unsigned int virq); > + > +/** > + * irq_find_host - Locates a host for a given device node > + * @node: device-tree node of the interrupt controller > + */ > +extern struct irq_host *irq_find_host(struct device_node *node); > + > +/** > + * irq_set_default_host - Set a "default" host > + * @host: default host pointer > + * > + * For convenience, it's possible to set a "default" host that will be > used > + * whenever NULL is passed to irq_create_mapping(). It makes life > easier for > + * platforms that want to manipulate a few hard coded interrupt > numbers that > + * aren't properly represented in the device-tree. > + */ > +extern void irq_set_default_host(struct irq_host *host); > + > +/** > + * irq_set_virq_count - Set the maximum number of virt irqs > + * @count: number of linux virtual irqs, capped with NR_IRQS > + * > + * This is mainly for use by platforms like iSeries who want to > program > + * the virtual irq number in the controller to avoid the reverse > mapping > + */ > +extern void irq_set_virq_count(unsigned int count); > + > +/** > + * irq_create_mapping - Map a hardware interrupt into linux virq space > + * @host: host owning this hardware interrupt or NULL for default host > + * @hwirq: hardware irq number in that host space > + * > + * Only one mapping per hardware interrupt is permitted. Returns a > linux > + * virq number. > + * If the sense/trigger is to be specified, set_irq_type() should be > called > + * on the number returned from that call. > + */ > +extern unsigned int irq_create_mapping(struct irq_host *host, > + irq_hw_number_t hwirq); > + > +/** > + * irq_dispose_mapping - Unmap an interrupt > + * @virq: linux virq number of the interrupt to unmap > + */ > +extern void irq_dispose_mapping(unsigned int virq); > + > +/** > + * irq_find_mapping - Find a linux virq from an hw irq number. > + * @host: host owning this hardware interrupt > + * @hwirq: hardware irq number in that host space > + * > + * This is a slow path, for use by generic code. It's expected that an > + * irq controller implementation directly calls the appropriate low > level > + * mapping function. > + */ > +extern unsigned int irq_find_mapping(struct irq_host *host, > + irq_hw_number_t hwirq); > + > +/** > + * irq_create_direct_mapping - Allocate a virq for direct mapping > + * @host: host to allocate the virq for or NULL for default host > + * > + * This routine is used for irq controllers which can choose the > hardware > + * interrupt numbers they generate. In such a case it's simplest to > use > + * the linux virq as the hardware interrupt number. > + */ > +extern unsigned int irq_create_direct_mapping(struct irq_host *host); > + > +/** > + * irq_radix_revmap_insert - Insert a hw irq to linux virq number > mapping. > + * @host: host owning this hardware interrupt > + * @virq: linux irq number > + * @hwirq: hardware irq number in that host space > + * > + * This is for use by irq controllers that use a radix tree reverse > + * mapping for fast lookup. > + */ > +extern void irq_radix_revmap_insert(struct irq_host *host, unsigned > int virq, > + irq_hw_number_t hwirq); > + > +/** > + * irq_radix_revmap_lookup - Find a linux virq from a hw irq number. > + * @host: host owning this hardware interrupt > + * @hwirq: hardware irq number in that host space > + * > + * This is a fast path, for use by irq controller code that uses radix > tree > + * revmaps > + */ > +extern unsigned int irq_radix_revmap_lookup(struct irq_host *host, > + irq_hw_number_t hwirq); > + > +/** > + * irq_linear_revmap - Find a linux virq from a hw irq number. > + * @host: host owning this hardware interrupt > + * @hwirq: hardware irq number in that host space > + * > + * This is a fast path, for use by irq controller code that uses > linear > + * revmaps. It does fallback to the slow path if the revmap doesn't > exist > + * yet and will create the revmap entry with appropriate locking > + */ > + > +extern unsigned int irq_linear_revmap(struct irq_host *host, > + irq_hw_number_t hwirq); > + > + > + > +/** > + * irq_alloc_virt - Allocate virtual irq numbers > + * @host: host owning these new virtual irqs > + * @count: number of consecutive numbers to allocate > + * @hint: pass a hint number, the allocator will try to use a 1:1 > mapping > + * > + * This is a low level function that is used internally by > irq_create_mapping() > + * and that can be used by some irq controllers implementations for > things > + * like allocating ranges of numbers for MSIs. The revmaps are left > untouched. > + */ > +extern unsigned int irq_alloc_virt(struct irq_host *host, > + unsigned int count, > + unsigned int hint); > + > +/** > + * irq_free_virt - Free virtual irq numbers > + * @virq: virtual irq number of the first interrupt to free > + * @count: number of interrupts to free > + * > + * This function is the opposite of irq_alloc_virt. It will not clear > reverse > + * maps, this should be done previously by unmap'ing the interrupt. In > fact, > + * all interrupts covered by the range being freed should have been > unmapped > + * prior to calling this. > + */ > +extern void irq_free_virt(unsigned int virq, unsigned int count); > + > + > +#endif /* CONFIG_VIRQ */ > + > +#endif /* _LINUX_VIRQ_H */ > +#endif /* __KERNEL__ */ > + > diff --git a/kernel/irq/Makefile b/kernel/irq/Makefile > index 7d04780..f5207dc 100644 > --- a/kernel/irq/Makefile > +++ b/kernel/irq/Makefile > @@ -1,5 +1,6 @@ > > obj-y := handle.o manage.o spurious.o resend.o chip.o devres.o > +obj-$(CONFIG_VIRQ) += virq.o > obj-$(CONFIG_GENERIC_IRQ_PROBE) += autoprobe.o > obj-$(CONFIG_PROC_FS) += proc.o > obj-$(CONFIG_GENERIC_PENDING_IRQ) += migration.o > diff --git a/kernel/irq/virq.c b/kernel/irq/virq.c > new file mode 100644 > index 0000000..b3c0db3 > --- /dev/null > +++ b/kernel/irq/virq.c > @@ -0,0 +1,687 @@ > +/* > + * Mapping support from per-controller hw irq numbers to linux irqs > + * > + * Derived from arch/i386/kernel/irq.c > + * Copyright (C) 1992 Linus Torvalds > + * Adapted from arch/i386 by Gary Thomas > + * Copyright (C) 1995-1996 Gary Thomas (gdt@linuxppc.org) > + * Updated and modified by Cort Dougan > + * Copyright (C) 1996-2001 Cort Dougan > + * Adapted for Power Macintosh by Paul Mackerras > + * Copyright (C) 1996 Paul Mackerras (paulus@cs.anu.edu.au) > + * Generalized for virtual irq mapping on all platformes by Grant > Likely > + * Copyright (C) 2010 Secret Lab Technologies Ltd. > + * > + * 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. > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +/* > + * IRQ controller and virtual interrupts > + */ > +static LIST_HEAD(irq_hosts); > +static DEFINE_RAW_SPINLOCK(irq_big_lock); > +static unsigned int revmap_trees_allocated; > +static DEFINE_MUTEX(revmap_trees_mutex); > +struct irq_map_entry irq_map[NR_IRQS]; > +static unsigned int irq_virq_count = NR_IRQS; > +static struct irq_host *irq_default_host; > + > +irq_hw_number_t virq_to_hw(unsigned int virq) > +{ > + return irq_map[virq].hwirq; > +} > +EXPORT_SYMBOL_GPL(virq_to_hw); > + > +static int default_irq_host_match(struct irq_host *h, struct > device_node *np) > +{ > + return h->of_node != NULL && h->of_node == np; > +} > + > +struct irq_host *irq_alloc_host(struct device_node *of_node, > + unsigned int revmap_type, > + unsigned int revmap_arg, > + struct irq_host_ops *ops, > + irq_hw_number_t inval_irq) > +{ > + struct irq_host *host; > + unsigned int size = sizeof(struct irq_host); > + unsigned int i; > + unsigned int *rmap; > + unsigned long flags; > + > + /* Allocate structure and revmap table if using linear mapping */ > + if (revmap_type == IRQ_HOST_MAP_LINEAR) > + size += revmap_arg * sizeof(unsigned int); > + host = zalloc_maybe_bootmem(size, GFP_KERNEL); > + if (host == NULL) > + return NULL; > + > + /* Fill structure */ > + host->revmap_type = revmap_type; > + host->inval_irq = inval_irq; > + host->ops = ops; > + host->of_node = of_node_get(of_node); > + > + if (host->ops->match == NULL) > + host->ops->match = default_irq_host_match; > + > + raw_spin_lock_irqsave(&irq_big_lock, flags); > + > + /* If it's a legacy controller, check for duplicates and > + * mark it as allocated (we use irq 0 host pointer for that > + */ > + if (revmap_type == IRQ_HOST_MAP_LEGACY) { > + if (irq_map[0].host != NULL) { > + raw_spin_unlock_irqrestore(&irq_big_lock, flags); > + /* If we are early boot, we can't free the structure, > + * too bad... > + * this will be fixed once slab is made available > early > + * instead of the current cruft > + */ > + if (mem_init_done) > + kfree(host); > + return NULL; > + } > + irq_map[0].host = host; > + } > + > + list_add(&host->link, &irq_hosts); > + raw_spin_unlock_irqrestore(&irq_big_lock, flags); > + > + /* Additional setups per revmap type */ > + switch(revmap_type) { > + case IRQ_HOST_MAP_LEGACY: > + /* 0 is always the invalid number for legacy */ > + host->inval_irq = 0; > + /* setup us as the host for all legacy interrupts */ > + for (i = 1; i < NUM_ISA_INTERRUPTS; i++) { > + irq_map[i].hwirq = i; > + smp_wmb(); > + irq_map[i].host = host; > + smp_wmb(); > + > + /* Clear norequest flags */ > + irq_to_desc(i)->status &= ~IRQ_NOREQUEST; > + > + /* Legacy flags are left to default at this point, > + * one can then use irq_create_mapping() to > + * explicitly change them > + */ > + ops->map(host, i, i); > + } > + break; > + case IRQ_HOST_MAP_LINEAR: > + rmap = (unsigned int *)(host + 1); > + for (i = 0; i < revmap_arg; i++) > + rmap[i] = NO_IRQ; > + host->revmap_data.linear.size = revmap_arg; > + smp_wmb(); > + host->revmap_data.linear.revmap = rmap; > + break; > + default: > + break; > + } > + > + pr_debug("irq: Allocated host of type %d @0x%p\n", revmap_type, > host); > + > + return host; > +} > + > +struct irq_host *irq_find_host(struct device_node *node) > +{ > + struct irq_host *h, *found = NULL; > + unsigned long flags; > + > + /* We might want to match the legacy controller last since > + * it might potentially be set to match all interrupts in > + * the absence of a device node. This isn't a problem so far > + * yet though... > + */ > + raw_spin_lock_irqsave(&irq_big_lock, flags); > + list_for_each_entry(h, &irq_hosts, link) > + if (h->ops->match(h, node)) { > + found = h; > + break; > + } > + raw_spin_unlock_irqrestore(&irq_big_lock, flags); > + return found; > +} > +EXPORT_SYMBOL_GPL(irq_find_host); > + > +void irq_set_default_host(struct irq_host *host) > +{ > + pr_debug("irq: Default host set to @0x%p\n", host); > + > + irq_default_host = host; > +} > + > +void irq_set_virq_count(unsigned int count) > +{ > + pr_debug("irq: Trying to set virq count to %d\n", count); > + > + BUG_ON(count < NUM_ISA_INTERRUPTS); > + if (count < NR_IRQS) > + irq_virq_count = count; > +} > + > +static int irq_setup_virq(struct irq_host *host, unsigned int virq, > + irq_hw_number_t hwirq) > +{ > + struct irq_desc *desc; > + > + desc = irq_to_desc_alloc_node(virq, 0); > + if (!desc) { > + pr_debug("irq: -> allocating desc failed\n"); > + goto error; > + } > + > + /* Clear IRQ_NOREQUEST flag */ > + desc->status &= ~IRQ_NOREQUEST; > + > + /* map it */ > + smp_wmb(); > + irq_map[virq].hwirq = hwirq; > + smp_mb(); > + > + if (host->ops->map(host, virq, hwirq)) { > + pr_debug("irq: -> mapping failed, freeing\n"); > + goto error; > + } > + > + return 0; > + > +error: > + irq_free_virt(virq, 1); > + return -1; > +} > + > +unsigned int irq_create_direct_mapping(struct irq_host *host) > +{ > + unsigned int virq; > + > + if (host == NULL) > + host = irq_default_host; > + > + BUG_ON(host == NULL); > + WARN_ON(host->revmap_type != IRQ_HOST_MAP_NOMAP); > + > + virq = irq_alloc_virt(host, 1, 0); > + if (virq == NO_IRQ) { > + pr_debug("irq: create_direct virq allocation failed\n"); > + return NO_IRQ; > + } > + > + pr_debug("irq: create_direct obtained virq %d\n", virq); > + > + if (irq_setup_virq(host, virq, virq)) > + return NO_IRQ; > + > + return virq; > +} > + > +unsigned int irq_create_mapping(struct irq_host *host, > + irq_hw_number_t hwirq) > +{ > + unsigned int virq, hint; > + > + pr_debug("irq: irq_create_mapping(0x%p, 0x%lx)\n", host, hwirq); > + > + /* Look for default host if nececssary */ > + if (host == NULL) > + host = irq_default_host; > + if (host == NULL) { > + printk(KERN_WARNING "irq_create_mapping called for" > + " NULL host, hwirq=%lx\n", hwirq); > + WARN_ON(1); > + return NO_IRQ; > + } > + pr_debug("irq: -> using host @%p\n", host); > + > + /* Check if mapping already exist, if it does, call > + * host->ops->map() to update the flags > + */ > + virq = irq_find_mapping(host, hwirq); > + if (virq != NO_IRQ) { > + if (host->ops->remap) > + host->ops->remap(host, virq, hwirq); > + pr_debug("irq: -> existing mapping on virq %d\n", virq); > + return virq; > + } > + > + /* Get a virtual interrupt number */ > + if (host->revmap_type == IRQ_HOST_MAP_LEGACY) { > + /* Handle legacy */ > + virq = (unsigned int)hwirq; > + if (virq == 0 || virq >= NUM_ISA_INTERRUPTS) > + return NO_IRQ; > + return virq; > + } else { > + /* Allocate a virtual interrupt number */ > + hint = hwirq % irq_virq_count; > + virq = irq_alloc_virt(host, 1, hint); > + if (virq == NO_IRQ) { > + pr_debug("irq: -> virq allocation failed\n"); > + return NO_IRQ; > + } > + } > + > + if (irq_setup_virq(host, virq, hwirq)) > + return NO_IRQ; > + > + printk(KERN_DEBUG "irq: irq %lu on host %s mapped to virtual irq > %u\n", > + hwirq, host->of_node ? host->of_node->full_name : "null", > virq); > + > + return virq; > +} > +EXPORT_SYMBOL_GPL(irq_create_mapping); > + > +unsigned int irq_create_of_mapping(struct device_node *controller, > + const u32 *intspec, unsigned int intsize) > +{ > + struct irq_host *host; > + irq_hw_number_t hwirq; > + unsigned int type = IRQ_TYPE_NONE; > + unsigned int virq; > + > + if (controller == NULL) > + host = irq_default_host; > + else > + host = irq_find_host(controller); > + if (host == NULL) { > + printk(KERN_WARNING "irq: no irq host found for %s !\n", > + controller->full_name); > + return NO_IRQ; > + } > + > + /* If host has no translation, then we assume interrupt line */ > + if (host->ops->xlate == NULL) > + hwirq = intspec[0]; > + else { > + if (host->ops->xlate(host, controller, intspec, intsize, > + &hwirq, &type)) > + return NO_IRQ; > + } > + > + /* Create mapping */ > + virq = irq_create_mapping(host, hwirq); > + if (virq == NO_IRQ) > + return virq; > + > + /* Set type if specified and different than the current one */ > + if (type != IRQ_TYPE_NONE && > + type != (irq_to_desc(virq)->status & IRQF_TRIGGER_MASK)) > + set_irq_type(virq, type); > + return virq; > +} > +EXPORT_SYMBOL_GPL(irq_create_of_mapping); > + > +void irq_dispose_mapping(unsigned int virq) > +{ > + struct irq_host *host; > + irq_hw_number_t hwirq; > + > + if (virq == NO_IRQ) > + return; > + > + host = irq_map[virq].host; > + WARN_ON (host == NULL); > + if (host == NULL) > + return; > + > + /* Never unmap legacy interrupts */ > + if (host->revmap_type == IRQ_HOST_MAP_LEGACY) > + return; > + > + /* remove chip and handler */ > + set_irq_chip_and_handler(virq, NULL, NULL); > + > + /* Make sure it's completed */ > + synchronize_irq(virq); > + > + /* Tell the PIC about it */ > + if (host->ops->unmap) > + host->ops->unmap(host, virq); > + smp_mb(); > + > + /* Clear reverse map */ > + hwirq = irq_map[virq].hwirq; > + switch(host->revmap_type) { > + case IRQ_HOST_MAP_LINEAR: > + if (hwirq < host->revmap_data.linear.size) > + host->revmap_data.linear.revmap[hwirq] = NO_IRQ; > + break; > + case IRQ_HOST_MAP_TREE: > + /* > + * Check if radix tree allocated yet, if not then nothing > to > + * remove. > + */ > + smp_rmb(); > + if (revmap_trees_allocated < 1) > + break; > + mutex_lock(&revmap_trees_mutex); > + radix_tree_delete(&host->revmap_data.tree, hwirq); > + mutex_unlock(&revmap_trees_mutex); > + break; > + } > + > + /* Destroy map */ > + smp_mb(); > + irq_map[virq].hwirq = host->inval_irq; > + > + /* Set some flags */ > + irq_to_desc(virq)->status |= IRQ_NOREQUEST; > + > + /* Free it */ > + irq_free_virt(virq, 1); > +} > +EXPORT_SYMBOL_GPL(irq_dispose_mapping); > + > +unsigned int irq_find_mapping(struct irq_host *host, > + irq_hw_number_t hwirq) > +{ > + unsigned int i; > + unsigned int hint = hwirq % irq_virq_count; > + > + /* Look for default host if nececssary */ > + if (host == NULL) > + host = irq_default_host; > + if (host == NULL) > + return NO_IRQ; > + > + /* legacy -> bail early */ > + if (host->revmap_type == IRQ_HOST_MAP_LEGACY) > + return hwirq; > + > + /* Slow path does a linear search of the map */ > + if (hint < NUM_ISA_INTERRUPTS) > + hint = NUM_ISA_INTERRUPTS; > + i = hint; > + do { > + if (irq_map[i].host == host && > + irq_map[i].hwirq == hwirq) > + return i; > + i++; > + if (i >= irq_virq_count) > + i = NUM_ISA_INTERRUPTS; > + } while(i != hint); > + return NO_IRQ; > +} > +EXPORT_SYMBOL_GPL(irq_find_mapping); > + > + > +unsigned int irq_radix_revmap_lookup(struct irq_host *host, > + irq_hw_number_t hwirq) > +{ > + struct irq_map_entry *ptr; > + unsigned int virq; > + > + WARN_ON(host->revmap_type != IRQ_HOST_MAP_TREE); > + > + /* > + * Check if the radix tree exists and has bee initialized. > + * If not, we fallback to slow mode > + */ > + if (revmap_trees_allocated < 2) > + return irq_find_mapping(host, hwirq); > + > + /* Now try to resolve */ > + /* > + * No rcu_read_lock(ing) needed, the ptr returned can't go under > us > + * as it's referencing an entry in the static irq_map table. > + */ > + ptr = radix_tree_lookup(&host->revmap_data.tree, hwirq); > + > + /* > + * If found in radix tree, then fine. > + * Else fallback to linear lookup - this should not happen in > practice > + * as it means that we failed to insert the node in the radix > tree. > + */ > + if (ptr) > + virq = ptr - irq_map; > + else > + virq = irq_find_mapping(host, hwirq); > + > + return virq; > +} > + > +void irq_radix_revmap_insert(struct irq_host *host, unsigned int virq, > + irq_hw_number_t hwirq) > +{ > + > + WARN_ON(host->revmap_type != IRQ_HOST_MAP_TREE); > + > + /* > + * Check if the radix tree exists yet. > + * If not, then the irq will be inserted into the tree when it > gets > + * initialized. > + */ > + smp_rmb(); > + if (revmap_trees_allocated < 1) > + return; > + > + if (virq != NO_IRQ) { > + mutex_lock(&revmap_trees_mutex); > + radix_tree_insert(&host->revmap_data.tree, hwirq, > + &irq_map[virq]); > + mutex_unlock(&revmap_trees_mutex); > + } > +} > + > +unsigned int irq_linear_revmap(struct irq_host *host, > + irq_hw_number_t hwirq) > +{ > + unsigned int *revmap; > + > + WARN_ON(host->revmap_type != IRQ_HOST_MAP_LINEAR); > + > + /* Check revmap bounds */ > + if (unlikely(hwirq >= host->revmap_data.linear.size)) > + return irq_find_mapping(host, hwirq); > + > + /* Check if revmap was allocated */ > + revmap = host->revmap_data.linear.revmap; > + if (unlikely(revmap == NULL)) > + return irq_find_mapping(host, hwirq); > + > + /* Fill up revmap with slow path if no mapping found */ > + if (unlikely(revmap[hwirq] == NO_IRQ)) > + revmap[hwirq] = irq_find_mapping(host, hwirq); > + > + return revmap[hwirq]; > +} > + > +unsigned int irq_alloc_virt(struct irq_host *host, > + unsigned int count, > + unsigned int hint) > +{ > + unsigned long flags; > + unsigned int i, j, found = NO_IRQ; > + > + if (count == 0 || count > (irq_virq_count - NUM_ISA_INTERRUPTS)) > + return NO_IRQ; > + > + raw_spin_lock_irqsave(&irq_big_lock, flags); > + > + /* Use hint for 1 interrupt if any */ > + if (count == 1 && hint >= NUM_ISA_INTERRUPTS && > + hint < irq_virq_count && irq_map[hint].host == NULL) { > + found = hint; > + goto hint_found; > + } > + > + /* Look for count consecutive numbers in the allocatable > + * (non-legacy) space > + */ > + for (i = NUM_ISA_INTERRUPTS, j = 0; i < irq_virq_count; i++) { > + if (irq_map[i].host != NULL) > + j = 0; > + else > + j++; > + > + if (j == count) { > + found = i - count + 1; > + break; > + } > + } > + if (found == NO_IRQ) { > + raw_spin_unlock_irqrestore(&irq_big_lock, flags); > + return NO_IRQ; > + } > + hint_found: > + for (i = found; i < (found + count); i++) { > + irq_map[i].hwirq = host->inval_irq; > + smp_wmb(); > + irq_map[i].host = host; > + } > + raw_spin_unlock_irqrestore(&irq_big_lock, flags); > + return found; > +} > + > +void irq_free_virt(unsigned int virq, unsigned int count) > +{ > + unsigned long flags; > + unsigned int i; > + > + WARN_ON (virq < NUM_ISA_INTERRUPTS); > + WARN_ON (count == 0 || (virq + count) > irq_virq_count); > + > + raw_spin_lock_irqsave(&irq_big_lock, flags); > + for (i = virq; i < (virq + count); i++) { > + struct irq_host *host; > + > + if (i < NUM_ISA_INTERRUPTS || > + (virq + count) > irq_virq_count) > + continue; > + > + host = irq_map[i].host; > + irq_map[i].hwirq = host->inval_irq; > + smp_wmb(); > + irq_map[i].host = NULL; > + } > + raw_spin_unlock_irqrestore(&irq_big_lock, flags); > +} > + > +/* We need to create the radix trees late */ > +static int irq_late_init(void) > +{ > + struct irq_host *h; > + unsigned int i; > + > + /* > + * No mutual exclusion with respect to accessors of the tree is > needed > + * here as the synchronization is done via the state variable > + * revmap_trees_allocated. > + */ > + list_for_each_entry(h, &irq_hosts, link) { > + if (h->revmap_type == IRQ_HOST_MAP_TREE) > + INIT_RADIX_TREE(&h->revmap_data.tree, GFP_KERNEL); > + } > + > + /* > + * Make sure the radix trees inits are visible before setting > + * the flag > + */ > + smp_wmb(); > + revmap_trees_allocated = 1; > + > + /* > + * Insert the reverse mapping for those interrupts already > present > + * in irq_map[]. > + */ > + mutex_lock(&revmap_trees_mutex); > + for (i = 0; i < irq_virq_count; i++) { > + if (irq_map[i].host && > + (irq_map[i].host->revmap_type == IRQ_HOST_MAP_TREE)) > + radix_tree_insert(&irq_map[i].host->revmap_data.tree, > + irq_map[i].hwirq, &irq_map[i]); > + } > + mutex_unlock(&revmap_trees_mutex); > + > + /* > + * Make sure the radix trees insertions are visible before > setting > + * the flag > + */ > + smp_wmb(); > + revmap_trees_allocated = 2; > + > + return 0; > +} > +arch_initcall(irq_late_init); > + > +#ifdef CONFIG_VIRQ_DEBUG > +static int virq_debug_show(struct seq_file *m, void *private) > +{ > + unsigned long flags; > + struct irq_desc *desc; > + const char *p; > + char none[] = "none"; > + int i; > + > + seq_printf(m, "%-5s %-7s %-15s %s\n", "virq", "hwirq", > + "chip name", "host name"); > + > + for (i = 1; i < nr_irqs; i++) { > + desc = irq_to_desc(i); > + if (!desc) > + continue; > + > + raw_spin_lock_irqsave(&desc->lock, flags); > + > + if (desc->action && desc->action->handler) { > + seq_printf(m, "%5d ", i); > + seq_printf(m, "0x%05lx ", virq_to_hw(i)); > + > + if (desc->chip && desc->chip->name) > + p = desc->chip->name; > + else > + p = none; > + seq_printf(m, "%-15s ", p); > + > + if (irq_map[i].host && irq_map[i].host->of_node) > + p = irq_map[i].host->of_node->full_name; > + else > + p = none; > + seq_printf(m, "%s\n", p); > + } > + > + raw_spin_unlock_irqrestore(&desc->lock, flags); > + } > + > + return 0; > +} > + > +static int virq_debug_open(struct inode *inode, struct file *file) > +{ > + return single_open(file, virq_debug_show, inode->i_private); > +} > + > +static const struct file_operations virq_debug_fops = { > + .open = virq_debug_open, > + .read = seq_read, > + .llseek = seq_lseek, > + .release = single_release, > +}; > + > +static int __init irq_debugfs_init(void) > +{ > + if (debugfs_create_file("virq_mapping", S_IRUGO, > powerpc_debugfs_root, > + NULL, &virq_debug_fops) == NULL) > + return -ENOMEM; > + > + return 0; > +} > +__initcall(irq_debugfs_init); > +#endif /* CONFIG_VIRQ_DEBUG */ > + > > _______________________________________________ > devicetree-discuss mailing list > devicetree-discuss@lists.ozlabs.org > https://lists.ozlabs.org/listinfo/devicetree-discuss