pci_lpc.c revision 268887
1/*-
2 * Copyright (c) 2013 Neel Natu <neel@freebsd.org>
3 * Copyright (c) 2013 Tycho Nightingale <tycho.nightingale@pluribusnetworks.com>
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 NETAPP, INC ``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 NETAPP, INC 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 * $FreeBSD: stable/10/usr.sbin/bhyve/pci_lpc.c 268887 2014-07-19 20:13:01Z jhb $
28 */
29
30#include <sys/cdefs.h>
31__FBSDID("$FreeBSD: stable/10/usr.sbin/bhyve/pci_lpc.c 268887 2014-07-19 20:13:01Z jhb $");
32
33#include <sys/types.h>
34#include <machine/vmm.h>
35#include <machine/vmm_dev.h>
36
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40
41#include <vmmapi.h>
42
43#include "acpi.h"
44#include "inout.h"
45#include "pci_emul.h"
46#include "pci_lpc.h"
47#include "uart_emul.h"
48
49SET_DECLARE(lpc_dsdt_set, struct lpc_dsdt);
50SET_DECLARE(lpc_sysres_set, struct lpc_sysres);
51
52static struct pci_devinst *lpc_bridge;
53
54#define	LPC_UART_NUM	2
55static struct lpc_uart_softc {
56	struct uart_softc *uart_softc;
57	const char *opts;
58	int	iobase;
59	int	irq;
60	int	enabled;
61} lpc_uart_softc[LPC_UART_NUM];
62
63static const char *lpc_uart_names[LPC_UART_NUM] = { "COM1", "COM2" };
64
65/*
66 * LPC device configuration is in the following form:
67 * <lpc_device_name>[,<options>]
68 * For e.g. "com1,stdio"
69 */
70int
71lpc_device_parse(const char *opts)
72{
73	int unit, error;
74	char *str, *cpy, *lpcdev;
75
76	error = -1;
77	str = cpy = strdup(opts);
78	lpcdev = strsep(&str, ",");
79	if (lpcdev != NULL) {
80		for (unit = 0; unit < LPC_UART_NUM; unit++) {
81			if (strcasecmp(lpcdev, lpc_uart_names[unit]) == 0) {
82				lpc_uart_softc[unit].opts = str;
83				error = 0;
84				goto done;
85			}
86		}
87	}
88
89done:
90	if (error)
91		free(cpy);
92
93	return (error);
94}
95
96static void
97lpc_uart_intr_assert(void *arg)
98{
99	struct lpc_uart_softc *sc = arg;
100
101	assert(sc->irq >= 0);
102
103	vm_ioapic_pulse_irq(lpc_bridge->pi_vmctx, sc->irq);
104}
105
106static void
107lpc_uart_intr_deassert(void *arg)
108{
109	/*
110	 * The COM devices on the LPC bus generate edge triggered interrupts,
111	 * so nothing more to do here.
112	 */
113}
114
115static int
116lpc_uart_io_handler(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
117		    uint32_t *eax, void *arg)
118{
119	int offset;
120	struct lpc_uart_softc *sc = arg;
121
122	if (bytes != 1)
123		return (-1);
124
125	offset = port - sc->iobase;
126
127	if (in)
128		*eax = uart_read(sc->uart_softc, offset);
129	else
130		uart_write(sc->uart_softc, offset, *eax);
131
132	return (0);
133}
134
135static int
136lpc_init(void)
137{
138	struct lpc_uart_softc *sc;
139	struct inout_port iop;
140	const char *name;
141	int unit, error;
142
143	/* COM1 and COM2 */
144	for (unit = 0; unit < LPC_UART_NUM; unit++) {
145		sc = &lpc_uart_softc[unit];
146		name = lpc_uart_names[unit];
147
148		if (uart_legacy_alloc(unit, &sc->iobase, &sc->irq) != 0) {
149			fprintf(stderr, "Unable to allocate resources for "
150			    "LPC device %s\n", name);
151			return (-1);
152		}
153
154		sc->uart_softc = uart_init(lpc_uart_intr_assert,
155				    lpc_uart_intr_deassert, sc);
156
157		if (uart_set_backend(sc->uart_softc, sc->opts) != 0) {
158			fprintf(stderr, "Unable to initialize backend '%s' "
159			    "for LPC device %s\n", sc->opts, name);
160			return (-1);
161		}
162
163		bzero(&iop, sizeof(struct inout_port));
164		iop.name = name;
165		iop.port = sc->iobase;
166		iop.size = UART_IO_BAR_SIZE;
167		iop.flags = IOPORT_F_INOUT;
168		iop.handler = lpc_uart_io_handler;
169		iop.arg = sc;
170
171		error = register_inout(&iop);
172		assert(error == 0);
173		sc->enabled = 1;
174	}
175
176	return (0);
177}
178
179static void
180pci_lpc_write_dsdt(struct pci_devinst *pi)
181{
182	struct lpc_dsdt **ldpp, *ldp;
183
184	dsdt_line("");
185	dsdt_line("Device (ISA)");
186	dsdt_line("{");
187	dsdt_line("  Name (_ADR, 0x%04X%04X)", pi->pi_slot, pi->pi_func);
188	dsdt_line("  OperationRegion (P40C, PCI_Config, 0x60, 0x04)");
189
190	dsdt_indent(1);
191	SET_FOREACH(ldpp, lpc_dsdt_set) {
192		ldp = *ldpp;
193		ldp->handler();
194	}
195	dsdt_unindent(1);
196
197	dsdt_line("}");
198}
199
200static void
201pci_lpc_sysres_dsdt(void)
202{
203	struct lpc_sysres **lspp, *lsp;
204
205	dsdt_line("");
206	dsdt_line("Device (SIO)");
207	dsdt_line("{");
208	dsdt_line("  Name (_HID, EisaId (\"PNP0C02\"))");
209	dsdt_line("  Name (_CRS, ResourceTemplate ()");
210	dsdt_line("  {");
211
212	dsdt_indent(2);
213	SET_FOREACH(lspp, lpc_sysres_set) {
214		lsp = *lspp;
215		switch (lsp->type) {
216		case LPC_SYSRES_IO:
217			dsdt_fixed_ioport(lsp->base, lsp->length);
218			break;
219		case LPC_SYSRES_MEM:
220			dsdt_fixed_mem32(lsp->base, lsp->length);
221			break;
222		}
223	}
224	dsdt_unindent(2);
225
226	dsdt_line("  })");
227	dsdt_line("}");
228}
229LPC_DSDT(pci_lpc_sysres_dsdt);
230
231static void
232pci_lpc_uart_dsdt(void)
233{
234	struct lpc_uart_softc *sc;
235	int unit;
236
237	for (unit = 0; unit < LPC_UART_NUM; unit++) {
238		sc = &lpc_uart_softc[unit];
239		if (!sc->enabled)
240			continue;
241		dsdt_line("");
242		dsdt_line("Device (%s)", lpc_uart_names[unit]);
243		dsdt_line("{");
244		dsdt_line("  Name (_HID, EisaId (\"PNP0501\"))");
245		dsdt_line("  Name (_UID, %d)", unit + 1);
246		dsdt_line("  Name (_CRS, ResourceTemplate ()");
247		dsdt_line("  {");
248		dsdt_indent(2);
249		dsdt_fixed_ioport(sc->iobase, UART_IO_BAR_SIZE);
250		dsdt_fixed_irq(sc->irq);
251		dsdt_unindent(2);
252		dsdt_line("  })");
253		dsdt_line("}");
254	}
255}
256LPC_DSDT(pci_lpc_uart_dsdt);
257
258static void
259pci_lpc_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
260	       int baridx, uint64_t offset, int size, uint64_t value)
261{
262}
263
264uint64_t
265pci_lpc_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
266	      int baridx, uint64_t offset, int size)
267{
268	return (0);
269}
270
271#define	LPC_DEV		0x7000
272#define	LPC_VENDOR	0x8086
273
274static int
275pci_lpc_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts)
276{
277	/*
278	 * Do not allow more than one LPC bridge to be configured.
279	 */
280	if (lpc_bridge != NULL) {
281		fprintf(stderr, "Only one LPC bridge is allowed.\n");
282		return (-1);
283	}
284
285	/*
286	 * Enforce that the LPC can only be configured on bus 0. This
287	 * simplifies the ACPI DSDT because it can provide a decode for
288	 * all legacy i/o ports behind bus 0.
289	 */
290	if (pi->pi_bus != 0) {
291		fprintf(stderr, "LPC bridge can be present only on bus 0.\n");
292		return (-1);
293	}
294
295	if (lpc_init() != 0)
296		return (-1);
297
298	/* initialize config space */
299	pci_set_cfgdata16(pi, PCIR_DEVICE, LPC_DEV);
300	pci_set_cfgdata16(pi, PCIR_VENDOR, LPC_VENDOR);
301	pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_BRIDGE);
302	pci_set_cfgdata8(pi, PCIR_SUBCLASS, PCIS_BRIDGE_ISA);
303
304	lpc_bridge = pi;
305
306	return (0);
307}
308
309struct pci_devemu pci_de_lpc = {
310	.pe_emu =	"lpc",
311	.pe_init =	pci_lpc_init,
312	.pe_write_dsdt = pci_lpc_write_dsdt,
313	.pe_barwrite =	pci_lpc_write,
314	.pe_barread =	pci_lpc_read
315};
316PCI_EMUL_SET(pci_de_lpc);
317