Dean Nelson | 4173a0e | 2008-10-02 12:18:21 -0500 | [diff] [blame] | 1 | /* |
| 2 | * This file is subject to the terms and conditions of the GNU General Public |
| 3 | * License. See the file "COPYING" in the main directory of this archive |
| 4 | * for more details. |
| 5 | * |
| 6 | * SGI UV IRQ functions |
| 7 | * |
| 8 | * Copyright (C) 2008 Silicon Graphics, Inc. All rights reserved. |
| 9 | */ |
| 10 | |
Paul Gortmaker | cc3ae7b | 2016-07-13 20:18:58 -0400 | [diff] [blame] | 11 | #include <linux/export.h> |
Dimitri Sivanich | 6c2c502 | 2009-09-30 11:02:59 -0500 | [diff] [blame] | 12 | #include <linux/rbtree.h> |
Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame] | 13 | #include <linux/slab.h> |
Dean Nelson | 4173a0e | 2008-10-02 12:18:21 -0500 | [diff] [blame] | 14 | #include <linux/irq.h> |
Ingo Molnar | 37762b6 | 2008-10-03 11:38:37 +0200 | [diff] [blame] | 15 | |
Jiang Liu | d746d1e | 2015-04-14 10:30:09 +0800 | [diff] [blame] | 16 | #include <asm/irqdomain.h> |
Ingo Molnar | 37762b6 | 2008-10-03 11:38:37 +0200 | [diff] [blame] | 17 | #include <asm/apic.h> |
Dean Nelson | 4173a0e | 2008-10-02 12:18:21 -0500 | [diff] [blame] | 18 | #include <asm/uv/uv_irq.h> |
Dimitri Sivanich | 6c2c502 | 2009-09-30 11:02:59 -0500 | [diff] [blame] | 19 | #include <asm/uv/uv_hub.h> |
| 20 | |
| 21 | /* MMR offset and pnode of hub sourcing interrupts for a given irq */ |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 22 | struct uv_irq_2_mmr_pnode { |
Dimitri Sivanich | 9338ad6 | 2009-10-13 15:32:36 -0500 | [diff] [blame] | 23 | unsigned long offset; |
| 24 | int pnode; |
Dimitri Sivanich | 6c2c502 | 2009-09-30 11:02:59 -0500 | [diff] [blame] | 25 | }; |
Dimitri Sivanich | 9338ad6 | 2009-10-13 15:32:36 -0500 | [diff] [blame] | 26 | |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 27 | static void uv_program_mmr(struct irq_cfg *cfg, struct uv_irq_2_mmr_pnode *info) |
Dean Nelson | 4173a0e | 2008-10-02 12:18:21 -0500 | [diff] [blame] | 28 | { |
Dimitri Sivanich | 9338ad6 | 2009-10-13 15:32:36 -0500 | [diff] [blame] | 29 | unsigned long mmr_value; |
| 30 | struct uv_IO_APIC_route_entry *entry; |
Dimitri Sivanich | 9338ad6 | 2009-10-13 15:32:36 -0500 | [diff] [blame] | 31 | |
| 32 | BUILD_BUG_ON(sizeof(struct uv_IO_APIC_route_entry) != |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 33 | sizeof(unsigned long)); |
Dimitri Sivanich | 9338ad6 | 2009-10-13 15:32:36 -0500 | [diff] [blame] | 34 | |
| 35 | mmr_value = 0; |
| 36 | entry = (struct uv_IO_APIC_route_entry *)&mmr_value; |
| 37 | entry->vector = cfg->vector; |
| 38 | entry->delivery_mode = apic->irq_delivery_mode; |
| 39 | entry->dest_mode = apic->irq_dest_mode; |
| 40 | entry->polarity = 0; |
| 41 | entry->trigger = 0; |
| 42 | entry->mask = 0; |
Jiang Liu | 331dd19 | 2015-04-13 14:11:27 +0800 | [diff] [blame] | 43 | entry->dest = cfg->dest_apicid; |
Dimitri Sivanich | 9338ad6 | 2009-10-13 15:32:36 -0500 | [diff] [blame] | 44 | |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 45 | uv_write_global_mmr64(info->pnode, info->offset, mmr_value); |
Dimitri Sivanich | 9338ad6 | 2009-10-13 15:32:36 -0500 | [diff] [blame] | 46 | } |
| 47 | |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 48 | static void uv_noop(struct irq_data *data) { } |
| 49 | |
| 50 | static void uv_ack_apic(struct irq_data *data) |
Dimitri Sivanich | 9338ad6 | 2009-10-13 15:32:36 -0500 | [diff] [blame] | 51 | { |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 52 | ack_APIC_irq(); |
Dimitri Sivanich | 9338ad6 | 2009-10-13 15:32:36 -0500 | [diff] [blame] | 53 | } |
| 54 | |
Thomas Gleixner | 48b2650 | 2010-09-30 11:43:08 +0200 | [diff] [blame] | 55 | static int |
| 56 | uv_set_irq_affinity(struct irq_data *data, const struct cpumask *mask, |
| 57 | bool force) |
Dimitri Sivanich | 9338ad6 | 2009-10-13 15:32:36 -0500 | [diff] [blame] | 58 | { |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 59 | struct irq_data *parent = data->parent_data; |
Jiang Liu | a978609 | 2014-10-27 16:12:07 +0800 | [diff] [blame] | 60 | struct irq_cfg *cfg = irqd_cfg(data); |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 61 | int ret; |
Dimitri Sivanich | 9338ad6 | 2009-10-13 15:32:36 -0500 | [diff] [blame] | 62 | |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 63 | ret = parent->chip->irq_set_affinity(parent, mask, force); |
| 64 | if (ret >= 0) { |
| 65 | uv_program_mmr(cfg, data->chip_data); |
Jiang Liu | c6c2002 | 2015-04-14 10:30:02 +0800 | [diff] [blame] | 66 | send_cleanup_vector(cfg); |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 67 | } |
| 68 | |
| 69 | return ret; |
| 70 | } |
| 71 | |
| 72 | static struct irq_chip uv_irq_chip = { |
| 73 | .name = "UV-CORE", |
| 74 | .irq_mask = uv_noop, |
| 75 | .irq_unmask = uv_noop, |
| 76 | .irq_eoi = uv_ack_apic, |
| 77 | .irq_set_affinity = uv_set_irq_affinity, |
| 78 | }; |
| 79 | |
| 80 | static int uv_domain_alloc(struct irq_domain *domain, unsigned int virq, |
| 81 | unsigned int nr_irqs, void *arg) |
| 82 | { |
| 83 | struct uv_irq_2_mmr_pnode *chip_data; |
| 84 | struct irq_alloc_info *info = arg; |
| 85 | struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq); |
| 86 | int ret; |
| 87 | |
| 88 | if (nr_irqs > 1 || !info || info->type != X86_IRQ_ALLOC_TYPE_UV) |
| 89 | return -EINVAL; |
| 90 | |
| 91 | chip_data = kmalloc_node(sizeof(*chip_data), GFP_KERNEL, |
Jiang Liu | 5f2dbbc | 2015-06-01 16:05:14 +0800 | [diff] [blame] | 92 | irq_data_get_node(irq_data)); |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 93 | if (!chip_data) |
| 94 | return -ENOMEM; |
| 95 | |
| 96 | ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, arg); |
| 97 | if (ret >= 0) { |
| 98 | if (info->uv_limit == UV_AFFINITY_CPU) |
| 99 | irq_set_status_flags(virq, IRQ_NO_BALANCING); |
| 100 | else |
| 101 | irq_set_status_flags(virq, IRQ_MOVE_PCNTXT); |
| 102 | |
| 103 | chip_data->pnode = uv_blade_to_pnode(info->uv_blade); |
| 104 | chip_data->offset = info->uv_offset; |
| 105 | irq_domain_set_info(domain, virq, virq, &uv_irq_chip, chip_data, |
| 106 | handle_percpu_irq, NULL, info->uv_name); |
| 107 | } else { |
| 108 | kfree(chip_data); |
| 109 | } |
| 110 | |
| 111 | return ret; |
| 112 | } |
| 113 | |
| 114 | static void uv_domain_free(struct irq_domain *domain, unsigned int virq, |
| 115 | unsigned int nr_irqs) |
| 116 | { |
| 117 | struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq); |
| 118 | |
| 119 | BUG_ON(nr_irqs != 1); |
| 120 | kfree(irq_data->chip_data); |
| 121 | irq_clear_status_flags(virq, IRQ_MOVE_PCNTXT); |
| 122 | irq_clear_status_flags(virq, IRQ_NO_BALANCING); |
| 123 | irq_domain_free_irqs_top(domain, virq, nr_irqs); |
| 124 | } |
| 125 | |
| 126 | /* |
| 127 | * Re-target the irq to the specified CPU and enable the specified MMR located |
| 128 | * on the specified blade to allow the sending of MSIs to the specified CPU. |
| 129 | */ |
| 130 | static void uv_domain_activate(struct irq_domain *domain, |
| 131 | struct irq_data *irq_data) |
| 132 | { |
| 133 | uv_program_mmr(irqd_cfg(irq_data), irq_data->chip_data); |
| 134 | } |
| 135 | |
| 136 | /* |
| 137 | * Disable the specified MMR located on the specified blade so that MSIs are |
| 138 | * longer allowed to be sent. |
| 139 | */ |
| 140 | static void uv_domain_deactivate(struct irq_domain *domain, |
| 141 | struct irq_data *irq_data) |
| 142 | { |
| 143 | unsigned long mmr_value; |
| 144 | struct uv_IO_APIC_route_entry *entry; |
Dimitri Sivanich | 9338ad6 | 2009-10-13 15:32:36 -0500 | [diff] [blame] | 145 | |
| 146 | mmr_value = 0; |
| 147 | entry = (struct uv_IO_APIC_route_entry *)&mmr_value; |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 148 | entry->mask = 1; |
| 149 | uv_program_mmr(irqd_cfg(irq_data), irq_data->chip_data); |
| 150 | } |
Dimitri Sivanich | 9338ad6 | 2009-10-13 15:32:36 -0500 | [diff] [blame] | 151 | |
Thomas Gleixner | eb18cf5 | 2015-05-05 11:10:11 +0200 | [diff] [blame] | 152 | static const struct irq_domain_ops uv_domain_ops = { |
| 153 | .alloc = uv_domain_alloc, |
| 154 | .free = uv_domain_free, |
| 155 | .activate = uv_domain_activate, |
| 156 | .deactivate = uv_domain_deactivate, |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 157 | }; |
Dimitri Sivanich | 9338ad6 | 2009-10-13 15:32:36 -0500 | [diff] [blame] | 158 | |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 159 | static struct irq_domain *uv_get_irq_domain(void) |
| 160 | { |
| 161 | static struct irq_domain *uv_domain; |
| 162 | static DEFINE_MUTEX(uv_lock); |
Thomas Gleixner | f8409a6 | 2017-06-20 01:37:09 +0200 | [diff] [blame] | 163 | struct fwnode_handle *fn; |
Dimitri Sivanich | 9338ad6 | 2009-10-13 15:32:36 -0500 | [diff] [blame] | 164 | |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 165 | mutex_lock(&uv_lock); |
Thomas Gleixner | f8409a6 | 2017-06-20 01:37:09 +0200 | [diff] [blame] | 166 | if (uv_domain) |
| 167 | goto out; |
| 168 | |
| 169 | fn = irq_domain_alloc_named_fwnode("UV-CORE"); |
| 170 | if (!fn) |
| 171 | goto out; |
| 172 | |
| 173 | uv_domain = irq_domain_create_tree(fn, &uv_domain_ops, NULL); |
| 174 | irq_domain_free_fwnode(fn); |
| 175 | if (uv_domain) |
| 176 | uv_domain->parent = x86_vector_domain; |
| 177 | out: |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 178 | mutex_unlock(&uv_lock); |
Dimitri Sivanich | 9338ad6 | 2009-10-13 15:32:36 -0500 | [diff] [blame] | 179 | |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 180 | return uv_domain; |
Dimitri Sivanich | 9338ad6 | 2009-10-13 15:32:36 -0500 | [diff] [blame] | 181 | } |
| 182 | |
| 183 | /* |
Dean Nelson | 4173a0e | 2008-10-02 12:18:21 -0500 | [diff] [blame] | 184 | * Set up a mapping of an available irq and vector, and enable the specified |
| 185 | * MMR that defines the MSI that is to be sent to the specified CPU when an |
| 186 | * interrupt is raised. |
| 187 | */ |
| 188 | int uv_setup_irq(char *irq_name, int cpu, int mmr_blade, |
Randy Dunlap | a289cc7 | 2010-04-16 17:51:42 -0700 | [diff] [blame] | 189 | unsigned long mmr_offset, int limit) |
Dean Nelson | 4173a0e | 2008-10-02 12:18:21 -0500 | [diff] [blame] | 190 | { |
Jiang Liu | 331dd19 | 2015-04-13 14:11:27 +0800 | [diff] [blame] | 191 | struct irq_alloc_info info; |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 192 | struct irq_domain *domain = uv_get_irq_domain(); |
| 193 | |
| 194 | if (!domain) |
| 195 | return -ENOMEM; |
Dean Nelson | 4173a0e | 2008-10-02 12:18:21 -0500 | [diff] [blame] | 196 | |
Jiang Liu | 331dd19 | 2015-04-13 14:11:27 +0800 | [diff] [blame] | 197 | init_irq_alloc_info(&info, cpumask_of(cpu)); |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 198 | info.type = X86_IRQ_ALLOC_TYPE_UV; |
| 199 | info.uv_limit = limit; |
| 200 | info.uv_blade = mmr_blade; |
| 201 | info.uv_offset = mmr_offset; |
| 202 | info.uv_name = irq_name; |
Dean Nelson | 4173a0e | 2008-10-02 12:18:21 -0500 | [diff] [blame] | 203 | |
Jiang Liu | 43fe1ab | 2015-04-13 14:11:44 +0800 | [diff] [blame] | 204 | return irq_domain_alloc_irqs(domain, 1, |
| 205 | uv_blade_to_memory_nid(mmr_blade), &info); |
Dean Nelson | 4173a0e | 2008-10-02 12:18:21 -0500 | [diff] [blame] | 206 | } |
| 207 | EXPORT_SYMBOL_GPL(uv_setup_irq); |
| 208 | |
| 209 | /* |
| 210 | * Tear down a mapping of an irq and vector, and disable the specified MMR that |
| 211 | * defined the MSI that was to be sent to the specified CPU when an interrupt |
| 212 | * was raised. |
| 213 | * |
| 214 | * Set mmr_blade and mmr_offset to what was passed in on uv_setup_irq(). |
| 215 | */ |
Dimitri Sivanich | 6c2c502 | 2009-09-30 11:02:59 -0500 | [diff] [blame] | 216 | void uv_teardown_irq(unsigned int irq) |
Dean Nelson | 4173a0e | 2008-10-02 12:18:21 -0500 | [diff] [blame] | 217 | { |
Jiang Liu | 331dd19 | 2015-04-13 14:11:27 +0800 | [diff] [blame] | 218 | irq_domain_free_irqs(irq, 1); |
Dean Nelson | 4173a0e | 2008-10-02 12:18:21 -0500 | [diff] [blame] | 219 | } |
| 220 | EXPORT_SYMBOL_GPL(uv_teardown_irq); |