1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2016 ARM Limited, All Rights Reserved. |
4 | * Author: Marc Zyngier <marc.zyngier@arm.com> |
5 | */ |
6 | |
7 | #include <linux/bitops.h> |
8 | #include <linux/interrupt.h> |
9 | #include <linux/irqchip.h> |
10 | #include <linux/irqchip/chained_irq.h> |
11 | #include <linux/irqchip/irq-partition-percpu.h> |
12 | #include <linux/irqdomain.h> |
13 | #include <linux/seq_file.h> |
14 | #include <linux/slab.h> |
15 | |
16 | struct partition_desc { |
17 | int nr_parts; |
18 | struct partition_affinity *parts; |
19 | struct irq_domain *domain; |
20 | struct irq_desc *chained_desc; |
21 | unsigned long *bitmap; |
22 | struct irq_domain_ops ops; |
23 | }; |
24 | |
25 | static bool partition_check_cpu(struct partition_desc *part, |
26 | unsigned int cpu, unsigned int hwirq) |
27 | { |
28 | return cpumask_test_cpu(cpu, cpumask: &part->parts[hwirq].mask); |
29 | } |
30 | |
31 | static void partition_irq_mask(struct irq_data *d) |
32 | { |
33 | struct partition_desc *part = irq_data_get_irq_chip_data(d); |
34 | struct irq_chip *chip = irq_desc_get_chip(desc: part->chained_desc); |
35 | struct irq_data *data = irq_desc_get_irq_data(desc: part->chained_desc); |
36 | |
37 | if (partition_check_cpu(part, smp_processor_id(), hwirq: d->hwirq) && |
38 | chip->irq_mask) |
39 | chip->irq_mask(data); |
40 | } |
41 | |
42 | static void partition_irq_unmask(struct irq_data *d) |
43 | { |
44 | struct partition_desc *part = irq_data_get_irq_chip_data(d); |
45 | struct irq_chip *chip = irq_desc_get_chip(desc: part->chained_desc); |
46 | struct irq_data *data = irq_desc_get_irq_data(desc: part->chained_desc); |
47 | |
48 | if (partition_check_cpu(part, smp_processor_id(), hwirq: d->hwirq) && |
49 | chip->irq_unmask) |
50 | chip->irq_unmask(data); |
51 | } |
52 | |
53 | static int partition_irq_set_irqchip_state(struct irq_data *d, |
54 | enum irqchip_irq_state which, |
55 | bool val) |
56 | { |
57 | struct partition_desc *part = irq_data_get_irq_chip_data(d); |
58 | struct irq_chip *chip = irq_desc_get_chip(desc: part->chained_desc); |
59 | struct irq_data *data = irq_desc_get_irq_data(desc: part->chained_desc); |
60 | |
61 | if (partition_check_cpu(part, smp_processor_id(), hwirq: d->hwirq) && |
62 | chip->irq_set_irqchip_state) |
63 | return chip->irq_set_irqchip_state(data, which, val); |
64 | |
65 | return -EINVAL; |
66 | } |
67 | |
68 | static int partition_irq_get_irqchip_state(struct irq_data *d, |
69 | enum irqchip_irq_state which, |
70 | bool *val) |
71 | { |
72 | struct partition_desc *part = irq_data_get_irq_chip_data(d); |
73 | struct irq_chip *chip = irq_desc_get_chip(desc: part->chained_desc); |
74 | struct irq_data *data = irq_desc_get_irq_data(desc: part->chained_desc); |
75 | |
76 | if (partition_check_cpu(part, smp_processor_id(), hwirq: d->hwirq) && |
77 | chip->irq_get_irqchip_state) |
78 | return chip->irq_get_irqchip_state(data, which, val); |
79 | |
80 | return -EINVAL; |
81 | } |
82 | |
83 | static int partition_irq_set_type(struct irq_data *d, unsigned int type) |
84 | { |
85 | struct partition_desc *part = irq_data_get_irq_chip_data(d); |
86 | struct irq_chip *chip = irq_desc_get_chip(desc: part->chained_desc); |
87 | struct irq_data *data = irq_desc_get_irq_data(desc: part->chained_desc); |
88 | |
89 | if (chip->irq_set_type) |
90 | return chip->irq_set_type(data, type); |
91 | |
92 | return -EINVAL; |
93 | } |
94 | |
95 | static void partition_irq_print_chip(struct irq_data *d, struct seq_file *p) |
96 | { |
97 | struct partition_desc *part = irq_data_get_irq_chip_data(d); |
98 | struct irq_chip *chip = irq_desc_get_chip(desc: part->chained_desc); |
99 | struct irq_data *data = irq_desc_get_irq_data(desc: part->chained_desc); |
100 | |
101 | seq_printf(m: p, fmt: " %5s-%lu" , chip->name, data->hwirq); |
102 | } |
103 | |
104 | static struct irq_chip partition_irq_chip = { |
105 | .irq_mask = partition_irq_mask, |
106 | .irq_unmask = partition_irq_unmask, |
107 | .irq_set_type = partition_irq_set_type, |
108 | .irq_get_irqchip_state = partition_irq_get_irqchip_state, |
109 | .irq_set_irqchip_state = partition_irq_set_irqchip_state, |
110 | .irq_print_chip = partition_irq_print_chip, |
111 | }; |
112 | |
113 | static void partition_handle_irq(struct irq_desc *desc) |
114 | { |
115 | struct partition_desc *part = irq_desc_get_handler_data(desc); |
116 | struct irq_chip *chip = irq_desc_get_chip(desc); |
117 | int cpu = smp_processor_id(); |
118 | int hwirq; |
119 | |
120 | chained_irq_enter(chip, desc); |
121 | |
122 | for_each_set_bit(hwirq, part->bitmap, part->nr_parts) { |
123 | if (partition_check_cpu(part, cpu, hwirq)) |
124 | break; |
125 | } |
126 | |
127 | if (unlikely(hwirq == part->nr_parts)) |
128 | handle_bad_irq(desc); |
129 | else |
130 | generic_handle_domain_irq(domain: part->domain, hwirq); |
131 | |
132 | chained_irq_exit(chip, desc); |
133 | } |
134 | |
135 | static int partition_domain_alloc(struct irq_domain *domain, unsigned int virq, |
136 | unsigned int nr_irqs, void *arg) |
137 | { |
138 | int ret; |
139 | irq_hw_number_t hwirq; |
140 | unsigned int type; |
141 | struct irq_fwspec *fwspec = arg; |
142 | struct partition_desc *part; |
143 | |
144 | BUG_ON(nr_irqs != 1); |
145 | ret = domain->ops->translate(domain, fwspec, &hwirq, &type); |
146 | if (ret) |
147 | return ret; |
148 | |
149 | part = domain->host_data; |
150 | |
151 | set_bit(nr: hwirq, addr: part->bitmap); |
152 | irq_set_chained_handler_and_data(irq: irq_desc_get_irq(desc: part->chained_desc), |
153 | handle: partition_handle_irq, data: part); |
154 | irq_set_percpu_devid_partition(irq: virq, affinity: &part->parts[hwirq].mask); |
155 | irq_domain_set_info(domain, virq, hwirq, chip: &partition_irq_chip, chip_data: part, |
156 | handler: handle_percpu_devid_irq, NULL, NULL); |
157 | irq_set_status_flags(irq: virq, set: IRQ_NOAUTOEN); |
158 | |
159 | return 0; |
160 | } |
161 | |
162 | static void partition_domain_free(struct irq_domain *domain, unsigned int virq, |
163 | unsigned int nr_irqs) |
164 | { |
165 | struct irq_data *d; |
166 | |
167 | BUG_ON(nr_irqs != 1); |
168 | |
169 | d = irq_domain_get_irq_data(domain, virq); |
170 | irq_set_handler(irq: virq, NULL); |
171 | irq_domain_reset_irq_data(irq_data: d); |
172 | } |
173 | |
174 | int partition_translate_id(struct partition_desc *desc, void *partition_id) |
175 | { |
176 | struct partition_affinity *part = NULL; |
177 | int i; |
178 | |
179 | for (i = 0; i < desc->nr_parts; i++) { |
180 | if (desc->parts[i].partition_id == partition_id) { |
181 | part = &desc->parts[i]; |
182 | break; |
183 | } |
184 | } |
185 | |
186 | if (WARN_ON(!part)) { |
187 | pr_err("Failed to find partition\n" ); |
188 | return -EINVAL; |
189 | } |
190 | |
191 | return i; |
192 | } |
193 | |
194 | struct partition_desc *partition_create_desc(struct fwnode_handle *fwnode, |
195 | struct partition_affinity *parts, |
196 | int nr_parts, |
197 | int chained_irq, |
198 | const struct irq_domain_ops *ops) |
199 | { |
200 | struct partition_desc *desc; |
201 | struct irq_domain *d; |
202 | |
203 | BUG_ON(!ops->select || !ops->translate); |
204 | |
205 | desc = kzalloc(size: sizeof(*desc), GFP_KERNEL); |
206 | if (!desc) |
207 | return NULL; |
208 | |
209 | desc->ops = *ops; |
210 | desc->ops.free = partition_domain_free; |
211 | desc->ops.alloc = partition_domain_alloc; |
212 | |
213 | d = irq_domain_create_linear(fwnode, size: nr_parts, ops: &desc->ops, host_data: desc); |
214 | if (!d) |
215 | goto out; |
216 | desc->domain = d; |
217 | |
218 | desc->bitmap = bitmap_zalloc(nbits: nr_parts, GFP_KERNEL); |
219 | if (WARN_ON(!desc->bitmap)) |
220 | goto out; |
221 | |
222 | desc->chained_desc = irq_to_desc(irq: chained_irq); |
223 | desc->nr_parts = nr_parts; |
224 | desc->parts = parts; |
225 | |
226 | return desc; |
227 | out: |
228 | if (d) |
229 | irq_domain_remove(host: d); |
230 | kfree(objp: desc); |
231 | |
232 | return NULL; |
233 | } |
234 | |
235 | struct irq_domain *partition_get_domain(struct partition_desc *dsc) |
236 | { |
237 | if (dsc) |
238 | return dsc->domain; |
239 | |
240 | return NULL; |
241 | } |
242 | |