]>
Commit | Line | Data |
---|---|---|
4173a0e7 DN |
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 | ||
11 | #include <linux/module.h> | |
6c2c5029 | 12 | #include <linux/rbtree.h> |
5a0e3ad6 | 13 | #include <linux/slab.h> |
4173a0e7 | 14 | #include <linux/irq.h> |
37762b6f | 15 | |
d746d1eb | 16 | #include <asm/irqdomain.h> |
37762b6f | 17 | #include <asm/apic.h> |
4173a0e7 | 18 | #include <asm/uv/uv_irq.h> |
6c2c5029 DS |
19 | #include <asm/uv/uv_hub.h> |
20 | ||
21 | /* MMR offset and pnode of hub sourcing interrupts for a given irq */ | |
43fe1abc | 22 | struct uv_irq_2_mmr_pnode { |
9338ad6f DS |
23 | unsigned long offset; |
24 | int pnode; | |
6c2c5029 | 25 | }; |
9338ad6f | 26 | |
43fe1abc JL |
27 | static void uv_program_mmr(struct irq_cfg *cfg, struct uv_irq_2_mmr_pnode *info) |
28 | { | |
29 | unsigned long mmr_value; | |
30 | struct uv_IO_APIC_route_entry *entry; | |
31 | ||
32 | BUILD_BUG_ON(sizeof(struct uv_IO_APIC_route_entry) != | |
33 | sizeof(unsigned long)); | |
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; | |
43 | entry->dest = cfg->dest_apicid; | |
9338ad6f | 44 | |
43fe1abc JL |
45 | uv_write_global_mmr64(info->pnode, info->offset, mmr_value); |
46 | } | |
4173a0e7 | 47 | |
48b26501 | 48 | static void uv_noop(struct irq_data *data) { } |
4173a0e7 | 49 | |
48b26501 | 50 | static void uv_ack_apic(struct irq_data *data) |
4173a0e7 DN |
51 | { |
52 | ack_APIC_irq(); | |
53 | } | |
54 | ||
43fe1abc JL |
55 | static int |
56 | uv_set_irq_affinity(struct irq_data *data, const struct cpumask *mask, | |
57 | bool force) | |
58 | { | |
59 | struct irq_data *parent = data->parent_data; | |
60 | struct irq_cfg *cfg = irqd_cfg(data); | |
61 | int ret; | |
62 | ||
63 | ret = parent->chip->irq_set_affinity(parent, mask, force); | |
64 | if (ret >= 0) { | |
65 | uv_program_mmr(cfg, data->chip_data); | |
c6c2002b | 66 | send_cleanup_vector(cfg); |
43fe1abc JL |
67 | } |
68 | ||
69 | return ret; | |
70 | } | |
71 | ||
a289cc7c | 72 | static struct irq_chip uv_irq_chip = { |
48b26501 TG |
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, | |
4173a0e7 DN |
78 | }; |
79 | ||
43fe1abc JL |
80 | static int uv_domain_alloc(struct irq_domain *domain, unsigned int virq, |
81 | unsigned int nr_irqs, void *arg) | |
6c2c5029 | 82 | { |
43fe1abc JL |
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, | |
92 | irq_data->node); | |
93 | if (!chip_data) | |
6c2c5029 DS |
94 | return -ENOMEM; |
95 | ||
43fe1abc JL |
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); | |
6c2c5029 | 100 | else |
43fe1abc JL |
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); | |
6c2c5029 DS |
109 | } |
110 | ||
43fe1abc | 111 | return ret; |
6c2c5029 DS |
112 | } |
113 | ||
43fe1abc JL |
114 | static void uv_domain_free(struct irq_domain *domain, unsigned int virq, |
115 | unsigned int nr_irqs) | |
6c2c5029 | 116 | { |
43fe1abc JL |
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); | |
6c2c5029 DS |
124 | } |
125 | ||
9338ad6f DS |
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 | */ | |
43fe1abc JL |
130 | static void uv_domain_activate(struct irq_domain *domain, |
131 | struct irq_data *irq_data) | |
9338ad6f | 132 | { |
43fe1abc | 133 | uv_program_mmr(irqd_cfg(irq_data), irq_data->chip_data); |
9338ad6f DS |
134 | } |
135 | ||
136 | /* | |
137 | * Disable the specified MMR located on the specified blade so that MSIs are | |
138 | * longer allowed to be sent. | |
139 | */ | |
43fe1abc JL |
140 | static void uv_domain_deactivate(struct irq_domain *domain, |
141 | struct irq_data *irq_data) | |
9338ad6f DS |
142 | { |
143 | unsigned long mmr_value; | |
144 | struct uv_IO_APIC_route_entry *entry; | |
145 | ||
9338ad6f DS |
146 | mmr_value = 0; |
147 | entry = (struct uv_IO_APIC_route_entry *)&mmr_value; | |
148 | entry->mask = 1; | |
43fe1abc | 149 | uv_program_mmr(irqd_cfg(irq_data), irq_data->chip_data); |
9338ad6f DS |
150 | } |
151 | ||
eb18cf55 TG |
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, | |
43fe1abc | 157 | }; |
9338ad6f | 158 | |
43fe1abc JL |
159 | static struct irq_domain *uv_get_irq_domain(void) |
160 | { | |
161 | static struct irq_domain *uv_domain; | |
162 | static DEFINE_MUTEX(uv_lock); | |
163 | ||
164 | mutex_lock(&uv_lock); | |
165 | if (uv_domain == NULL) { | |
166 | uv_domain = irq_domain_add_tree(NULL, &uv_domain_ops, NULL); | |
167 | if (uv_domain) | |
168 | uv_domain->parent = x86_vector_domain; | |
169 | } | |
170 | mutex_unlock(&uv_lock); | |
9338ad6f | 171 | |
43fe1abc | 172 | return uv_domain; |
9338ad6f DS |
173 | } |
174 | ||
4173a0e7 DN |
175 | /* |
176 | * Set up a mapping of an available irq and vector, and enable the specified | |
177 | * MMR that defines the MSI that is to be sent to the specified CPU when an | |
178 | * interrupt is raised. | |
179 | */ | |
180 | int uv_setup_irq(char *irq_name, int cpu, int mmr_blade, | |
a289cc7c | 181 | unsigned long mmr_offset, int limit) |
4173a0e7 | 182 | { |
331dd19e | 183 | struct irq_alloc_info info; |
43fe1abc | 184 | struct irq_domain *domain = uv_get_irq_domain(); |
6c2c5029 | 185 | |
43fe1abc JL |
186 | if (!domain) |
187 | return -ENOMEM; | |
4173a0e7 | 188 | |
43fe1abc JL |
189 | init_irq_alloc_info(&info, cpumask_of(cpu)); |
190 | info.type = X86_IRQ_ALLOC_TYPE_UV; | |
191 | info.uv_limit = limit; | |
192 | info.uv_blade = mmr_blade; | |
193 | info.uv_offset = mmr_offset; | |
194 | info.uv_name = irq_name; | |
195 | ||
196 | return irq_domain_alloc_irqs(domain, 1, | |
197 | uv_blade_to_memory_nid(mmr_blade), &info); | |
4173a0e7 DN |
198 | } |
199 | EXPORT_SYMBOL_GPL(uv_setup_irq); | |
200 | ||
201 | /* | |
202 | * Tear down a mapping of an irq and vector, and disable the specified MMR that | |
203 | * defined the MSI that was to be sent to the specified CPU when an interrupt | |
204 | * was raised. | |
205 | * | |
206 | * Set mmr_blade and mmr_offset to what was passed in on uv_setup_irq(). | |
207 | */ | |
6c2c5029 | 208 | void uv_teardown_irq(unsigned int irq) |
4173a0e7 | 209 | { |
331dd19e | 210 | irq_domain_free_irqs(irq, 1); |
4173a0e7 DN |
211 | } |
212 | EXPORT_SYMBOL_GPL(uv_teardown_irq); |