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