1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2014 Hudson River Trading LLC
5 * Written by: John H. Baldwin <jhb@FreeBSD.org>
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30 #include <sys/param.h>
31 #include <machine/vmm.h>
32
33 #include <assert.h>
34 #include <pthread.h>
35 #include <stdbool.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <vmmapi.h>
39
40 #include "acpi.h"
41 #include "bootrom.h"
42 #include "inout.h"
43 #include "ioapic.h"
44 #include "pci_emul.h"
45 #include "pci_irq.h"
46 #include "pci_lpc.h"
47
48 /*
49 * Implement an 8 pin PCI interrupt router compatible with the router
50 * present on Intel's ICH10 chip.
51 */
52
53 /* Fields in each PIRQ register. */
54 #define PIRQ_DIS 0x80
55 #define PIRQ_IRQ 0x0f
56
57 /* Only IRQs 3-7, 9-12, and 14-15 are permitted. */
58 #define PERMITTED_IRQS 0xdef8
59 #define IRQ_PERMITTED(irq) (((1U << (irq)) & PERMITTED_IRQS) != 0)
60
61 /* IRQ count to disable an IRQ. */
62 #define IRQ_DISABLED 0xff
63
64 #define NPIRQS 8
65 static struct pirq {
66 uint8_t reg;
67 int use_count;
68 int active_count;
69 pthread_mutex_t lock;
70 } pirqs[NPIRQS];
71
72 #define NIRQ_COUNTS 16
73 static u_char irq_counts[NIRQ_COUNTS];
74 static int pirq_cold = 1;
75
76 /*
77 * Returns true if this pin is enabled with a valid IRQ. Setting the
78 * register to a reserved IRQ causes interrupts to not be asserted as
79 * if the pin was disabled.
80 */
81 static bool
pirq_valid_irq(int reg)82 pirq_valid_irq(int reg)
83 {
84
85 if (reg & PIRQ_DIS)
86 return (false);
87 return (IRQ_PERMITTED(reg & PIRQ_IRQ));
88 }
89
90 uint8_t
pirq_read(int pin)91 pirq_read(int pin)
92 {
93
94 assert(pin > 0 && pin <= NPIRQS);
95 return (pirqs[pin - 1].reg);
96 }
97
98 void
pirq_write(struct vmctx * ctx,int pin,uint8_t val)99 pirq_write(struct vmctx *ctx, int pin, uint8_t val)
100 {
101 struct pirq *pirq;
102
103 assert(pin > 0 && pin <= NPIRQS);
104 pirq = &pirqs[pin - 1];
105 pthread_mutex_lock(&pirq->lock);
106 if (pirq->reg != (val & (PIRQ_DIS | PIRQ_IRQ))) {
107 if (pirq->active_count != 0 && pirq_valid_irq(pirq->reg))
108 vm_isa_deassert_irq(ctx, pirq->reg & PIRQ_IRQ, -1);
109 pirq->reg = val & (PIRQ_DIS | PIRQ_IRQ);
110 if (pirq->active_count != 0 && pirq_valid_irq(pirq->reg))
111 vm_isa_assert_irq(ctx, pirq->reg & PIRQ_IRQ, -1);
112 }
113 pthread_mutex_unlock(&pirq->lock);
114 }
115
116 void
pci_irq_reserve(int irq)117 pci_irq_reserve(int irq)
118 {
119
120 assert(irq >= 0 && irq < NIRQ_COUNTS);
121 assert(pirq_cold);
122 assert(irq_counts[irq] == 0 || irq_counts[irq] == IRQ_DISABLED);
123 irq_counts[irq] = IRQ_DISABLED;
124 }
125
126 void
pci_irq_use(int irq)127 pci_irq_use(int irq)
128 {
129
130 assert(irq >= 0 && irq < NIRQ_COUNTS);
131 assert(pirq_cold);
132 assert(irq_counts[irq] != IRQ_DISABLED);
133 irq_counts[irq]++;
134 }
135
136 void
pci_irq_init(struct vmctx * ctx __unused)137 pci_irq_init(struct vmctx *ctx __unused)
138 {
139 int i;
140
141 for (i = 0; i < NPIRQS; i++) {
142 pirqs[i].reg = PIRQ_DIS;
143 pirqs[i].use_count = 0;
144 pirqs[i].active_count = 0;
145 pthread_mutex_init(&pirqs[i].lock, NULL);
146 }
147 for (i = 0; i < NIRQ_COUNTS; i++) {
148 if (IRQ_PERMITTED(i))
149 irq_counts[i] = 0;
150 else
151 irq_counts[i] = IRQ_DISABLED;
152 }
153 }
154
155 void
pci_irq_assert(struct pci_devinst * pi)156 pci_irq_assert(struct pci_devinst *pi)
157 {
158 struct pirq *pirq;
159 int pin;
160
161 pin = pi->pi_lintr.irq.pirq_pin;
162 if (pin > 0) {
163 assert(pin <= NPIRQS);
164 pirq = &pirqs[pin - 1];
165 pthread_mutex_lock(&pirq->lock);
166 pirq->active_count++;
167 if (pirq->active_count == 1 && pirq_valid_irq(pirq->reg)) {
168 vm_isa_assert_irq(pi->pi_vmctx, pirq->reg & PIRQ_IRQ,
169 pi->pi_lintr.irq.ioapic_irq);
170 pthread_mutex_unlock(&pirq->lock);
171 return;
172 }
173 pthread_mutex_unlock(&pirq->lock);
174 }
175 vm_ioapic_assert_irq(pi->pi_vmctx, pi->pi_lintr.irq.ioapic_irq);
176 }
177
178 void
pci_irq_deassert(struct pci_devinst * pi)179 pci_irq_deassert(struct pci_devinst *pi)
180 {
181 struct pirq *pirq;
182 int pin;
183
184 pin = pi->pi_lintr.irq.pirq_pin;
185 if (pin > 0) {
186 assert(pin <= NPIRQS);
187 pirq = &pirqs[pin - 1];
188 pthread_mutex_lock(&pirq->lock);
189 pirq->active_count--;
190 if (pirq->active_count == 0 && pirq_valid_irq(pirq->reg)) {
191 vm_isa_deassert_irq(pi->pi_vmctx, pirq->reg & PIRQ_IRQ,
192 pi->pi_lintr.irq.ioapic_irq);
193 pthread_mutex_unlock(&pirq->lock);
194 return;
195 }
196 pthread_mutex_unlock(&pirq->lock);
197 }
198 vm_ioapic_deassert_irq(pi->pi_vmctx, pi->pi_lintr.irq.ioapic_irq);
199 }
200
201 static int
pirq_alloc_pin(struct pci_devinst * pi)202 pirq_alloc_pin(struct pci_devinst *pi)
203 {
204 struct vmctx *ctx = pi->pi_vmctx;
205 int best_count, best_irq, best_pin, irq, pin;
206
207 pirq_cold = 0;
208
209 if (bootrom_boot()) {
210 /* For external bootrom use fixed mapping. */
211 best_pin = (4 + pi->pi_slot + pi->pi_lintr.pin) % 8;
212 } else {
213 /* Find the least-used PIRQ pin. */
214 best_pin = 0;
215 best_count = pirqs[0].use_count;
216 for (pin = 1; pin < NPIRQS; pin++) {
217 if (pirqs[pin].use_count < best_count) {
218 best_pin = pin;
219 best_count = pirqs[pin].use_count;
220 }
221 }
222 }
223 pirqs[best_pin].use_count++;
224
225 /* Second, route this pin to an IRQ. */
226 if (pirqs[best_pin].reg == PIRQ_DIS) {
227 best_irq = -1;
228 best_count = 0;
229 for (irq = 0; irq < NIRQ_COUNTS; irq++) {
230 if (irq_counts[irq] == IRQ_DISABLED)
231 continue;
232 if (best_irq == -1 || irq_counts[irq] < best_count) {
233 best_irq = irq;
234 best_count = irq_counts[irq];
235 }
236 }
237 assert(best_irq >= 0);
238 irq_counts[best_irq]++;
239 pirqs[best_pin].reg = best_irq;
240 vm_isa_set_irq_trigger(ctx, best_irq, LEVEL_TRIGGER);
241 }
242
243 return (best_pin + 1);
244 }
245
246 int
pirq_irq(int pin)247 pirq_irq(int pin)
248 {
249 assert(pin > 0 && pin <= NPIRQS);
250 return (pirqs[pin - 1].reg & PIRQ_IRQ);
251 }
252
253 void
pci_irq_route(struct pci_devinst * pi,struct pci_irq * irq)254 pci_irq_route(struct pci_devinst *pi, struct pci_irq *irq)
255 {
256 /*
257 * Attempt to allocate an I/O APIC pin for this intpin if one
258 * is not yet assigned.
259 */
260 if (irq->ioapic_irq == 0)
261 irq->ioapic_irq = ioapic_pci_alloc_irq(pi);
262 assert(irq->ioapic_irq > 0);
263
264 /*
265 * Attempt to allocate a PIRQ pin for this intpin if one is
266 * not yet assigned.
267 */
268 if (irq->pirq_pin == 0)
269 irq->pirq_pin = pirq_alloc_pin(pi);
270 assert(irq->pirq_pin > 0);
271 }
272
273 /* XXX: Generate $PIR table. */
274
275 static void
pirq_dsdt(void)276 pirq_dsdt(void)
277 {
278 char *irq_prs, *old;
279 int irq, pin;
280
281 irq_prs = NULL;
282 for (irq = 0; irq < NIRQ_COUNTS; irq++) {
283 if (!IRQ_PERMITTED(irq))
284 continue;
285 if (irq_prs == NULL)
286 asprintf(&irq_prs, "%d", irq);
287 else {
288 old = irq_prs;
289 asprintf(&irq_prs, "%s,%d", old, irq);
290 free(old);
291 }
292 }
293
294 /*
295 * A helper method to validate a link register's value. This
296 * duplicates pirq_valid_irq().
297 */
298 dsdt_line("");
299 dsdt_line("Method (PIRV, 1, NotSerialized)");
300 dsdt_line("{");
301 dsdt_line(" If (And (Arg0, 0x%02X))", PIRQ_DIS);
302 dsdt_line(" {");
303 dsdt_line(" Return (0x00)");
304 dsdt_line(" }");
305 dsdt_line(" And (Arg0, 0x%02X, Local0)", PIRQ_IRQ);
306 dsdt_line(" If (LLess (Local0, 0x03))");
307 dsdt_line(" {");
308 dsdt_line(" Return (0x00)");
309 dsdt_line(" }");
310 dsdt_line(" If (LEqual (Local0, 0x08))");
311 dsdt_line(" {");
312 dsdt_line(" Return (0x00)");
313 dsdt_line(" }");
314 dsdt_line(" If (LEqual (Local0, 0x0D))");
315 dsdt_line(" {");
316 dsdt_line(" Return (0x00)");
317 dsdt_line(" }");
318 dsdt_line(" Return (0x01)");
319 dsdt_line("}");
320
321 for (pin = 0; pin < NPIRQS; pin++) {
322 dsdt_line("");
323 dsdt_line("Device (LNK%c)", 'A' + pin);
324 dsdt_line("{");
325 dsdt_line(" Name (_HID, EisaId (\"PNP0C0F\"))");
326 dsdt_line(" Name (_UID, 0x%02X)", pin + 1);
327 dsdt_line(" Method (_STA, 0, NotSerialized)");
328 dsdt_line(" {");
329 dsdt_line(" If (PIRV (PIR%c))", 'A' + pin);
330 dsdt_line(" {");
331 dsdt_line(" Return (0x0B)");
332 dsdt_line(" }");
333 dsdt_line(" Else");
334 dsdt_line(" {");
335 dsdt_line(" Return (0x09)");
336 dsdt_line(" }");
337 dsdt_line(" }");
338 dsdt_line(" Name (_PRS, ResourceTemplate ()");
339 dsdt_line(" {");
340 dsdt_line(" IRQ (Level, ActiveLow, Shared, )");
341 dsdt_line(" {%s}", irq_prs);
342 dsdt_line(" })");
343 dsdt_line(" Name (CB%02X, ResourceTemplate ()", pin + 1);
344 dsdt_line(" {");
345 dsdt_line(" IRQ (Level, ActiveLow, Shared, )");
346 dsdt_line(" {}");
347 dsdt_line(" })");
348 dsdt_line(" CreateWordField (CB%02X, 0x01, CIR%c)",
349 pin + 1, 'A' + pin);
350 dsdt_line(" Method (_CRS, 0, NotSerialized)");
351 dsdt_line(" {");
352 dsdt_line(" And (PIR%c, 0x%02X, Local0)", 'A' + pin,
353 PIRQ_DIS | PIRQ_IRQ);
354 dsdt_line(" If (PIRV (Local0))");
355 dsdt_line(" {");
356 dsdt_line(" ShiftLeft (0x01, Local0, CIR%c)", 'A' + pin);
357 dsdt_line(" }");
358 dsdt_line(" Else");
359 dsdt_line(" {");
360 dsdt_line(" Store (0x00, CIR%c)", 'A' + pin);
361 dsdt_line(" }");
362 dsdt_line(" Return (CB%02X)", pin + 1);
363 dsdt_line(" }");
364 dsdt_line(" Method (_DIS, 0, NotSerialized)");
365 dsdt_line(" {");
366 dsdt_line(" Store (0x80, PIR%c)", 'A' + pin);
367 dsdt_line(" }");
368 dsdt_line(" Method (_SRS, 1, NotSerialized)");
369 dsdt_line(" {");
370 dsdt_line(" CreateWordField (Arg0, 0x01, SIR%c)", 'A' + pin);
371 dsdt_line(" FindSetRightBit (SIR%c, Local0)", 'A' + pin);
372 dsdt_line(" Store (Decrement (Local0), PIR%c)", 'A' + pin);
373 dsdt_line(" }");
374 dsdt_line("}");
375 }
376 free(irq_prs);
377 }
378 LPC_DSDT(pirq_dsdt);
379