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