xref: /freebsd/usr.sbin/bhyve/amd64/pci_irq.c (revision 5b56413d04e608379c9a306373554a8e4d321bc0)
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 "inout.h"
42 #include "ioapic.h"
43 #include "pci_emul.h"
44 #include "pci_irq.h"
45 #include "pci_lpc.h"
46 
47 /*
48  * Implement an 8 pin PCI interrupt router compatible with the router
49  * present on Intel's ICH10 chip.
50  */
51 
52 /* Fields in each PIRQ register. */
53 #define	PIRQ_DIS	0x80
54 #define	PIRQ_IRQ	0x0f
55 
56 /* Only IRQs 3-7, 9-12, and 14-15 are permitted. */
57 #define	PERMITTED_IRQS	0xdef8
58 #define	IRQ_PERMITTED(irq)	(((1U << (irq)) & PERMITTED_IRQS) != 0)
59 
60 /* IRQ count to disable an IRQ. */
61 #define	IRQ_DISABLED	0xff
62 
63 #define	NPIRQS		8
64 static struct pirq {
65 	uint8_t	reg;
66 	int	use_count;
67 	int	active_count;
68 	pthread_mutex_t lock;
69 } pirqs[NPIRQS];
70 
71 #define	NIRQ_COUNTS	16
72 static u_char irq_counts[NIRQ_COUNTS];
73 static int pirq_cold = 1;
74 
75 /*
76  * Returns true if this pin is enabled with a valid IRQ.  Setting the
77  * register to a reserved IRQ causes interrupts to not be asserted as
78  * if the pin was disabled.
79  */
80 static bool
81 pirq_valid_irq(int reg)
82 {
83 
84 	if (reg & PIRQ_DIS)
85 		return (false);
86 	return (IRQ_PERMITTED(reg & PIRQ_IRQ));
87 }
88 
89 uint8_t
90 pirq_read(int pin)
91 {
92 
93 	assert(pin > 0 && pin <= NPIRQS);
94 	return (pirqs[pin - 1].reg);
95 }
96 
97 void
98 pirq_write(struct vmctx *ctx, int pin, uint8_t val)
99 {
100 	struct pirq *pirq;
101 
102 	assert(pin > 0 && pin <= NPIRQS);
103 	pirq = &pirqs[pin - 1];
104 	pthread_mutex_lock(&pirq->lock);
105 	if (pirq->reg != (val & (PIRQ_DIS | PIRQ_IRQ))) {
106 		if (pirq->active_count != 0 && pirq_valid_irq(pirq->reg))
107 			vm_isa_deassert_irq(ctx, pirq->reg & PIRQ_IRQ, -1);
108 		pirq->reg = val & (PIRQ_DIS | PIRQ_IRQ);
109 		if (pirq->active_count != 0 && pirq_valid_irq(pirq->reg))
110 			vm_isa_assert_irq(ctx, pirq->reg & PIRQ_IRQ, -1);
111 	}
112 	pthread_mutex_unlock(&pirq->lock);
113 }
114 
115 void
116 pci_irq_reserve(int irq)
117 {
118 
119 	assert(irq >= 0 && irq < NIRQ_COUNTS);
120 	assert(pirq_cold);
121 	assert(irq_counts[irq] == 0 || irq_counts[irq] == IRQ_DISABLED);
122 	irq_counts[irq] = IRQ_DISABLED;
123 }
124 
125 void
126 pci_irq_use(int irq)
127 {
128 
129 	assert(irq >= 0 && irq < NIRQ_COUNTS);
130 	assert(pirq_cold);
131 	assert(irq_counts[irq] != IRQ_DISABLED);
132 	irq_counts[irq]++;
133 }
134 
135 void
136 pci_irq_init(struct vmctx *ctx __unused)
137 {
138 	int i;
139 
140 	for (i = 0; i < NPIRQS; i++) {
141 		pirqs[i].reg = PIRQ_DIS;
142 		pirqs[i].use_count = 0;
143 		pirqs[i].active_count = 0;
144 		pthread_mutex_init(&pirqs[i].lock, NULL);
145 	}
146 	for (i = 0; i < NIRQ_COUNTS; i++) {
147 		if (IRQ_PERMITTED(i))
148 			irq_counts[i] = 0;
149 		else
150 			irq_counts[i] = IRQ_DISABLED;
151 	}
152 }
153 
154 void
155 pci_irq_assert(struct pci_devinst *pi)
156 {
157 	struct pirq *pirq;
158 	int pin;
159 
160 	pin = pi->pi_lintr.irq.pirq_pin;
161 	if (pin > 0) {
162 		assert(pin <= NPIRQS);
163 		pirq = &pirqs[pin - 1];
164 		pthread_mutex_lock(&pirq->lock);
165 		pirq->active_count++;
166 		if (pirq->active_count == 1 && pirq_valid_irq(pirq->reg)) {
167 			vm_isa_assert_irq(pi->pi_vmctx, pirq->reg & PIRQ_IRQ,
168 			    pi->pi_lintr.irq.ioapic_irq);
169 			pthread_mutex_unlock(&pirq->lock);
170 			return;
171 		}
172 		pthread_mutex_unlock(&pirq->lock);
173 	}
174 	vm_ioapic_assert_irq(pi->pi_vmctx, pi->pi_lintr.irq.ioapic_irq);
175 }
176 
177 void
178 pci_irq_deassert(struct pci_devinst *pi)
179 {
180 	struct pirq *pirq;
181 	int pin;
182 
183 	pin = pi->pi_lintr.irq.pirq_pin;
184 	if (pin > 0) {
185 		assert(pin <= NPIRQS);
186 		pirq = &pirqs[pin - 1];
187 		pthread_mutex_lock(&pirq->lock);
188 		pirq->active_count--;
189 		if (pirq->active_count == 0 && pirq_valid_irq(pirq->reg)) {
190 			vm_isa_deassert_irq(pi->pi_vmctx, pirq->reg & PIRQ_IRQ,
191 			    pi->pi_lintr.irq.ioapic_irq);
192 			pthread_mutex_unlock(&pirq->lock);
193 			return;
194 		}
195 		pthread_mutex_unlock(&pirq->lock);
196 	}
197 	vm_ioapic_deassert_irq(pi->pi_vmctx, pi->pi_lintr.irq.ioapic_irq);
198 }
199 
200 static int
201 pirq_alloc_pin(struct pci_devinst *pi)
202 {
203 	struct vmctx *ctx = pi->pi_vmctx;
204 	int best_count, best_irq, best_pin, irq, pin;
205 
206 	pirq_cold = 0;
207 
208 	if (lpc_bootrom()) {
209 		/* For external bootrom use fixed mapping. */
210 		best_pin = (4 + pi->pi_slot + pi->pi_lintr.pin) % 8;
211 	} else {
212 		/* Find the least-used PIRQ pin. */
213 		best_pin = 0;
214 		best_count = pirqs[0].use_count;
215 		for (pin = 1; pin < NPIRQS; pin++) {
216 			if (pirqs[pin].use_count < best_count) {
217 				best_pin = pin;
218 				best_count = pirqs[pin].use_count;
219 			}
220 		}
221 	}
222 	pirqs[best_pin].use_count++;
223 
224 	/* Second, route this pin to an IRQ. */
225 	if (pirqs[best_pin].reg == PIRQ_DIS) {
226 		best_irq = -1;
227 		best_count = 0;
228 		for (irq = 0; irq < NIRQ_COUNTS; irq++) {
229 			if (irq_counts[irq] == IRQ_DISABLED)
230 				continue;
231 			if (best_irq == -1 || irq_counts[irq] < best_count) {
232 				best_irq = irq;
233 				best_count = irq_counts[irq];
234 			}
235 		}
236 		assert(best_irq >= 0);
237 		irq_counts[best_irq]++;
238 		pirqs[best_pin].reg = best_irq;
239 		vm_isa_set_irq_trigger(ctx, best_irq, LEVEL_TRIGGER);
240 	}
241 
242 	return (best_pin + 1);
243 }
244 
245 int
246 pirq_irq(int pin)
247 {
248 	assert(pin > 0 && pin <= NPIRQS);
249 	return (pirqs[pin - 1].reg & PIRQ_IRQ);
250 }
251 
252 void
253 pci_irq_route(struct pci_devinst *pi, struct pci_irq *irq)
254 {
255 	/*
256 	 * Attempt to allocate an I/O APIC pin for this intpin if one
257 	 * is not yet assigned.
258 	 */
259 	if (irq->ioapic_irq == 0)
260 		irq->ioapic_irq = ioapic_pci_alloc_irq(pi);
261 	assert(irq->ioapic_irq > 0);
262 
263 	/*
264 	 * Attempt to allocate a PIRQ pin for this intpin if one is
265 	 * not yet assigned.
266 	 */
267 	if (irq->pirq_pin == 0)
268 		irq->pirq_pin = pirq_alloc_pin(pi);
269 	assert(irq->pirq_pin > 0);
270 }
271 
272 /* XXX: Generate $PIR table. */
273 
274 static void
275 pirq_dsdt(void)
276 {
277 	char *irq_prs, *old;
278 	int irq, pin;
279 
280 	irq_prs = NULL;
281 	for (irq = 0; irq < NIRQ_COUNTS; irq++) {
282 		if (!IRQ_PERMITTED(irq))
283 			continue;
284 		if (irq_prs == NULL)
285 			asprintf(&irq_prs, "%d", irq);
286 		else {
287 			old = irq_prs;
288 			asprintf(&irq_prs, "%s,%d", old, irq);
289 			free(old);
290 		}
291 	}
292 
293 	/*
294 	 * A helper method to validate a link register's value.  This
295 	 * duplicates pirq_valid_irq().
296 	 */
297 	dsdt_line("");
298 	dsdt_line("Method (PIRV, 1, NotSerialized)");
299 	dsdt_line("{");
300 	dsdt_line("  If (And (Arg0, 0x%02X))", PIRQ_DIS);
301 	dsdt_line("  {");
302 	dsdt_line("    Return (0x00)");
303 	dsdt_line("  }");
304 	dsdt_line("  And (Arg0, 0x%02X, Local0)", PIRQ_IRQ);
305 	dsdt_line("  If (LLess (Local0, 0x03))");
306 	dsdt_line("  {");
307 	dsdt_line("    Return (0x00)");
308 	dsdt_line("  }");
309 	dsdt_line("  If (LEqual (Local0, 0x08))");
310 	dsdt_line("  {");
311 	dsdt_line("    Return (0x00)");
312 	dsdt_line("  }");
313 	dsdt_line("  If (LEqual (Local0, 0x0D))");
314 	dsdt_line("  {");
315 	dsdt_line("    Return (0x00)");
316 	dsdt_line("  }");
317 	dsdt_line("  Return (0x01)");
318 	dsdt_line("}");
319 
320 	for (pin = 0; pin < NPIRQS; pin++) {
321 		dsdt_line("");
322 		dsdt_line("Device (LNK%c)", 'A' + pin);
323 		dsdt_line("{");
324 		dsdt_line("  Name (_HID, EisaId (\"PNP0C0F\"))");
325 		dsdt_line("  Name (_UID, 0x%02X)", pin + 1);
326 		dsdt_line("  Method (_STA, 0, NotSerialized)");
327 		dsdt_line("  {");
328 		dsdt_line("    If (PIRV (PIR%c))", 'A' + pin);
329 		dsdt_line("    {");
330 		dsdt_line("       Return (0x0B)");
331 		dsdt_line("    }");
332 		dsdt_line("    Else");
333 		dsdt_line("    {");
334 		dsdt_line("       Return (0x09)");
335 		dsdt_line("    }");
336 		dsdt_line("  }");
337 		dsdt_line("  Name (_PRS, ResourceTemplate ()");
338 		dsdt_line("  {");
339 		dsdt_line("    IRQ (Level, ActiveLow, Shared, )");
340 		dsdt_line("      {%s}", irq_prs);
341 		dsdt_line("  })");
342 		dsdt_line("  Name (CB%02X, ResourceTemplate ()", pin + 1);
343 		dsdt_line("  {");
344 		dsdt_line("    IRQ (Level, ActiveLow, Shared, )");
345 		dsdt_line("      {}");
346 		dsdt_line("  })");
347 		dsdt_line("  CreateWordField (CB%02X, 0x01, CIR%c)",
348 		    pin + 1, 'A' + pin);
349 		dsdt_line("  Method (_CRS, 0, NotSerialized)");
350 		dsdt_line("  {");
351 		dsdt_line("    And (PIR%c, 0x%02X, Local0)", 'A' + pin,
352 		    PIRQ_DIS | PIRQ_IRQ);
353 		dsdt_line("    If (PIRV (Local0))");
354 		dsdt_line("    {");
355 		dsdt_line("      ShiftLeft (0x01, Local0, CIR%c)", 'A' + pin);
356 		dsdt_line("    }");
357 		dsdt_line("    Else");
358 		dsdt_line("    {");
359 		dsdt_line("      Store (0x00, CIR%c)", 'A' + pin);
360 		dsdt_line("    }");
361 		dsdt_line("    Return (CB%02X)", pin + 1);
362 		dsdt_line("  }");
363 		dsdt_line("  Method (_DIS, 0, NotSerialized)");
364 		dsdt_line("  {");
365 		dsdt_line("    Store (0x80, PIR%c)", 'A' + pin);
366 		dsdt_line("  }");
367 		dsdt_line("  Method (_SRS, 1, NotSerialized)");
368 		dsdt_line("  {");
369 		dsdt_line("    CreateWordField (Arg0, 0x01, SIR%c)", 'A' + pin);
370 		dsdt_line("    FindSetRightBit (SIR%c, Local0)", 'A' + pin);
371 		dsdt_line("    Store (Decrement (Local0), PIR%c)", 'A' + pin);
372 		dsdt_line("  }");
373 		dsdt_line("}");
374 	}
375 	free(irq_prs);
376 }
377 LPC_DSDT(pirq_dsdt);
378