| /* |
| * This file is subject to the terms and conditions of the GNU General Public |
| * License. See the file "COPYING" in the main directory of this archive |
| * for more details. |
| * |
| * SGI UV IRQ functions |
| * |
| * Copyright (C) 2008 Silicon Graphics, Inc. All rights reserved. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/rbtree.h> |
| #include <linux/slab.h> |
| #include <linux/irq.h> |
| |
| #include <asm/apic.h> |
| #include <asm/uv/uv_irq.h> |
| #include <asm/uv/uv_hub.h> |
| |
| /* MMR offset and pnode of hub sourcing interrupts for a given irq */ |
| struct uv_irq_2_mmr_pnode{ |
| struct rb_node list; |
| unsigned long offset; |
| int pnode; |
| int irq; |
| }; |
| |
| static spinlock_t uv_irq_lock; |
| static struct rb_root uv_irq_root; |
| |
| static int uv_set_irq_affinity(unsigned int, const struct cpumask *); |
| |
| static void uv_noop(unsigned int irq) |
| { |
| } |
| |
| static unsigned int uv_noop_ret(unsigned int irq) |
| { |
| return 0; |
| } |
| |
| static void uv_ack_apic(unsigned int irq) |
| { |
| ack_APIC_irq(); |
| } |
| |
| static struct irq_chip uv_irq_chip = { |
| .name = "UV-CORE", |
| .startup = uv_noop_ret, |
| .shutdown = uv_noop, |
| .enable = uv_noop, |
| .disable = uv_noop, |
| .ack = uv_noop, |
| .mask = uv_noop, |
| .unmask = uv_noop, |
| .eoi = uv_ack_apic, |
| .end = uv_noop, |
| .set_affinity = uv_set_irq_affinity, |
| }; |
| |
| /* |
| * Add offset and pnode information of the hub sourcing interrupts to the |
| * rb tree for a specific irq. |
| */ |
| static int uv_set_irq_2_mmr_info(int irq, unsigned long offset, unsigned blade) |
| { |
| struct rb_node **link = &uv_irq_root.rb_node; |
| struct rb_node *parent = NULL; |
| struct uv_irq_2_mmr_pnode *n; |
| struct uv_irq_2_mmr_pnode *e; |
| unsigned long irqflags; |
| |
| n = kmalloc_node(sizeof(struct uv_irq_2_mmr_pnode), GFP_KERNEL, |
| uv_blade_to_memory_nid(blade)); |
| if (!n) |
| return -ENOMEM; |
| |
| n->irq = irq; |
| n->offset = offset; |
| n->pnode = uv_blade_to_pnode(blade); |
| spin_lock_irqsave(&uv_irq_lock, irqflags); |
| /* Find the right place in the rbtree: */ |
| while (*link) { |
| parent = *link; |
| e = rb_entry(parent, struct uv_irq_2_mmr_pnode, list); |
| |
| if (unlikely(irq == e->irq)) { |
| /* irq entry exists */ |
| e->pnode = uv_blade_to_pnode(blade); |
| e->offset = offset; |
| spin_unlock_irqrestore(&uv_irq_lock, irqflags); |
| kfree(n); |
| return 0; |
| } |
| |
| if (irq < e->irq) |
| link = &(*link)->rb_left; |
| else |
| link = &(*link)->rb_right; |
| } |
| |
| /* Insert the node into the rbtree. */ |
| rb_link_node(&n->list, parent, link); |
| rb_insert_color(&n->list, &uv_irq_root); |
| |
| spin_unlock_irqrestore(&uv_irq_lock, irqflags); |
| return 0; |
| } |
| |
| /* Retrieve offset and pnode information from the rb tree for a specific irq */ |
| int uv_irq_2_mmr_info(int irq, unsigned long *offset, int *pnode) |
| { |
| struct uv_irq_2_mmr_pnode *e; |
| struct rb_node *n; |
| unsigned long irqflags; |
| |
| spin_lock_irqsave(&uv_irq_lock, irqflags); |
| n = uv_irq_root.rb_node; |
| while (n) { |
| e = rb_entry(n, struct uv_irq_2_mmr_pnode, list); |
| |
| if (e->irq == irq) { |
| *offset = e->offset; |
| *pnode = e->pnode; |
| spin_unlock_irqrestore(&uv_irq_lock, irqflags); |
| return 0; |
| } |
| |
| if (irq < e->irq) |
| n = n->rb_left; |
| else |
| n = n->rb_right; |
| } |
| spin_unlock_irqrestore(&uv_irq_lock, irqflags); |
| return -1; |
| } |
| |
| /* |
| * Re-target the irq to the specified CPU and enable the specified MMR located |
| * on the specified blade to allow the sending of MSIs to the specified CPU. |
| */ |
| static int |
| arch_enable_uv_irq(char *irq_name, unsigned int irq, int cpu, int mmr_blade, |
| unsigned long mmr_offset, int limit) |
| { |
| const struct cpumask *eligible_cpu = cpumask_of(cpu); |
| struct irq_desc *desc = irq_to_desc(irq); |
| struct irq_cfg *cfg; |
| int mmr_pnode; |
| unsigned long mmr_value; |
| struct uv_IO_APIC_route_entry *entry; |
| int err; |
| |
| BUILD_BUG_ON(sizeof(struct uv_IO_APIC_route_entry) != |
| sizeof(unsigned long)); |
| |
| cfg = irq_cfg(irq); |
| |
| err = assign_irq_vector(irq, cfg, eligible_cpu); |
| if (err != 0) |
| return err; |
| |
| if (limit == UV_AFFINITY_CPU) |
| desc->status |= IRQ_NO_BALANCING; |
| else |
| desc->status |= IRQ_MOVE_PCNTXT; |
| |
| set_irq_chip_and_handler_name(irq, &uv_irq_chip, handle_percpu_irq, |
| irq_name); |
| |
| mmr_value = 0; |
| entry = (struct uv_IO_APIC_route_entry *)&mmr_value; |
| entry->vector = cfg->vector; |
| entry->delivery_mode = apic->irq_delivery_mode; |
| entry->dest_mode = apic->irq_dest_mode; |
| entry->polarity = 0; |
| entry->trigger = 0; |
| entry->mask = 0; |
| entry->dest = apic->cpu_mask_to_apicid(eligible_cpu); |
| |
| mmr_pnode = uv_blade_to_pnode(mmr_blade); |
| uv_write_global_mmr64(mmr_pnode, mmr_offset, mmr_value); |
| |
| if (cfg->move_in_progress) |
| send_cleanup_vector(cfg); |
| |
| return irq; |
| } |
| |
| /* |
| * Disable the specified MMR located on the specified blade so that MSIs are |
| * longer allowed to be sent. |
| */ |
| static void arch_disable_uv_irq(int mmr_pnode, unsigned long mmr_offset) |
| { |
| unsigned long mmr_value; |
| struct uv_IO_APIC_route_entry *entry; |
| |
| BUILD_BUG_ON(sizeof(struct uv_IO_APIC_route_entry) != |
| sizeof(unsigned long)); |
| |
| mmr_value = 0; |
| entry = (struct uv_IO_APIC_route_entry *)&mmr_value; |
| entry->mask = 1; |
| |
| uv_write_global_mmr64(mmr_pnode, mmr_offset, mmr_value); |
| } |
| |
| static int uv_set_irq_affinity(unsigned int irq, const struct cpumask *mask) |
| { |
| struct irq_desc *desc = irq_to_desc(irq); |
| struct irq_cfg *cfg = desc->chip_data; |
| unsigned int dest; |
| unsigned long mmr_value; |
| struct uv_IO_APIC_route_entry *entry; |
| unsigned long mmr_offset; |
| int mmr_pnode; |
| |
| if (set_desc_affinity(desc, mask, &dest)) |
| return -1; |
| |
| mmr_value = 0; |
| entry = (struct uv_IO_APIC_route_entry *)&mmr_value; |
| |
| entry->vector = cfg->vector; |
| entry->delivery_mode = apic->irq_delivery_mode; |
| entry->dest_mode = apic->irq_dest_mode; |
| entry->polarity = 0; |
| entry->trigger = 0; |
| entry->mask = 0; |
| entry->dest = dest; |
| |
| /* Get previously stored MMR and pnode of hub sourcing interrupts */ |
| if (uv_irq_2_mmr_info(irq, &mmr_offset, &mmr_pnode)) |
| return -1; |
| |
| uv_write_global_mmr64(mmr_pnode, mmr_offset, mmr_value); |
| |
| if (cfg->move_in_progress) |
| send_cleanup_vector(cfg); |
| |
| return 0; |
| } |
| |
| /* |
| * Set up a mapping of an available irq and vector, and enable the specified |
| * MMR that defines the MSI that is to be sent to the specified CPU when an |
| * interrupt is raised. |
| */ |
| int uv_setup_irq(char *irq_name, int cpu, int mmr_blade, |
| unsigned long mmr_offset, int limit) |
| { |
| int irq, ret; |
| |
| irq = create_irq_nr(NR_IRQS_LEGACY, uv_blade_to_memory_nid(mmr_blade)); |
| |
| if (irq <= 0) |
| return -EBUSY; |
| |
| ret = arch_enable_uv_irq(irq_name, irq, cpu, mmr_blade, mmr_offset, |
| limit); |
| if (ret == irq) |
| uv_set_irq_2_mmr_info(irq, mmr_offset, mmr_blade); |
| else |
| destroy_irq(irq); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(uv_setup_irq); |
| |
| /* |
| * Tear down a mapping of an irq and vector, and disable the specified MMR that |
| * defined the MSI that was to be sent to the specified CPU when an interrupt |
| * was raised. |
| * |
| * Set mmr_blade and mmr_offset to what was passed in on uv_setup_irq(). |
| */ |
| void uv_teardown_irq(unsigned int irq) |
| { |
| struct uv_irq_2_mmr_pnode *e; |
| struct rb_node *n; |
| unsigned long irqflags; |
| |
| spin_lock_irqsave(&uv_irq_lock, irqflags); |
| n = uv_irq_root.rb_node; |
| while (n) { |
| e = rb_entry(n, struct uv_irq_2_mmr_pnode, list); |
| if (e->irq == irq) { |
| arch_disable_uv_irq(e->pnode, e->offset); |
| rb_erase(n, &uv_irq_root); |
| kfree(e); |
| break; |
| } |
| if (irq < e->irq) |
| n = n->rb_left; |
| else |
| n = n->rb_right; |
| } |
| spin_unlock_irqrestore(&uv_irq_lock, irqflags); |
| destroy_irq(irq); |
| } |
| EXPORT_SYMBOL_GPL(uv_teardown_irq); |