1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2020 Adam Fenn <adam@fenn.io>
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 AUTHORS 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 AUTHORS 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 * Emulation of selected legacy test/debug interfaces expected by KVM-unit-tests
30 */
31
32#include <sys/cdefs.h>
33__FBSDID("$FreeBSD$");
34
35#include <sys/types.h>
36#include <sys/mman.h>
37#include <machine/vmm.h>
38
39#include <assert.h>
40#include <stdbool.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44
45#include <vmmapi.h>
46
47#include "debug.h"
48#include "inout.h"
49#include "mem.h"
50#include "pctestdev.h"
51
52#define	DEBUGEXIT_BASE		0xf4
53#define	DEBUGEXIT_LEN		4
54#define	DEBUGEXIT_NAME		"isa-debug-exit"
55
56#define	IOMEM_BASE		0xff000000
57#define	IOMEM_LEN		0x10000
58#define	IOMEM_NAME		"pc-testdev-iomem"
59
60#define	IOPORT_BASE		0xe0
61#define	IOPORT_LEN		4
62#define	IOPORT_NAME		"pc-testdev-ioport"
63
64#define	IRQ_BASE		0x2000
65#define	IRQ_IOAPIC_PINCOUNT_MIN	24
66#define	IRQ_IOAPIC_PINCOUNT_MAX	32
67#define	IRQ_NAME		"pc-testdev-irq-line"
68
69#define	PCTESTDEV_NAME		"pc-testdev"
70
71static bool	pctestdev_inited;
72static uint8_t	pctestdev_iomem_buf[IOMEM_LEN];
73static uint32_t	pctestdev_ioport_data;
74
75static int	pctestdev_debugexit_io(struct vmctx *ctx, int vcpu, int in,
76		    int port, int bytes, uint32_t *eax, void *arg);
77static int	pctestdev_iomem_io(struct vmctx *ctx, int vcpu, int dir,
78		    uint64_t addr, int size, uint64_t *val, void *arg1,
79		    long arg2);
80static int	pctestdev_ioport_io(struct vmctx *ctx, int vcpu, int in,
81		    int port, int bytes, uint32_t *eax, void *arg);
82static int	pctestdev_irq_io(struct vmctx *ctx, int vcpu, int in,
83		    int port, int bytes, uint32_t *eax, void *arg);
84
85const char *
86pctestdev_getname(void)
87{
88	return (PCTESTDEV_NAME);
89}
90
91int
92pctestdev_parse(const char *opts)
93{
94	if (opts != NULL && *opts != '\0')
95		return (-1);
96
97	return (0);
98}
99
100int
101pctestdev_init(struct vmctx *ctx)
102{
103	struct mem_range iomem;
104	struct inout_port debugexit, ioport, irq;
105	int err, pincount;
106
107	if (pctestdev_inited) {
108		EPRINTLN("Only one pc-testdev device is allowed.");
109
110		return (-1);
111	}
112
113	err = vm_ioapic_pincount(ctx, &pincount);
114	if (err != 0) {
115		EPRINTLN("pc-testdev: Failed to obtain IOAPIC pin count.");
116
117		return (-1);
118	}
119	if (pincount < IRQ_IOAPIC_PINCOUNT_MIN ||
120	    pincount > IRQ_IOAPIC_PINCOUNT_MAX) {
121		EPRINTLN("pc-testdev: Unsupported IOAPIC pin count: %d.",
122		    pincount);
123
124		return (-1);
125	}
126
127	debugexit.name = DEBUGEXIT_NAME;
128	debugexit.port = DEBUGEXIT_BASE;
129	debugexit.size = DEBUGEXIT_LEN;
130	debugexit.flags = IOPORT_F_INOUT;
131	debugexit.handler = pctestdev_debugexit_io;
132	debugexit.arg = NULL;
133
134	iomem.name = IOMEM_NAME;
135	iomem.flags = MEM_F_RW | MEM_F_IMMUTABLE;
136	iomem.handler = pctestdev_iomem_io;
137	iomem.arg1 = NULL;
138	iomem.arg2 = 0;
139	iomem.base = IOMEM_BASE;
140	iomem.size = IOMEM_LEN;
141
142	ioport.name = IOPORT_NAME;
143	ioport.port = IOPORT_BASE;
144	ioport.size = IOPORT_LEN;
145	ioport.flags = IOPORT_F_INOUT;
146	ioport.handler = pctestdev_ioport_io;
147	ioport.arg = NULL;
148
149	irq.name = IRQ_NAME;
150	irq.port = IRQ_BASE;
151	irq.size = pincount;
152	irq.flags = IOPORT_F_INOUT;
153	irq.handler = pctestdev_irq_io;
154	irq.arg = NULL;
155
156	err = register_inout(&debugexit);
157	if (err != 0)
158		goto fail;
159
160	err = register_inout(&ioport);
161	if (err != 0)
162		goto fail_after_debugexit_reg;
163
164	err = register_inout(&irq);
165	if (err != 0)
166		goto fail_after_ioport_reg;
167
168	err = register_mem(&iomem);
169	if (err != 0)
170		goto fail_after_irq_reg;
171
172	pctestdev_inited = true;
173
174	return (0);
175
176fail_after_irq_reg:
177	(void)unregister_inout(&irq);
178
179fail_after_ioport_reg:
180	(void)unregister_inout(&ioport);
181
182fail_after_debugexit_reg:
183	(void)unregister_inout(&debugexit);
184
185fail:
186	return (err);
187}
188
189static int
190pctestdev_debugexit_io(struct vmctx *ctx, int vcpu, int in, int port,
191    int bytes, uint32_t *eax, void *arg)
192{
193	if (in)
194		*eax = 0;
195	else
196		exit((*eax << 1) | 1);
197
198	return (0);
199}
200
201static int
202pctestdev_iomem_io(struct vmctx *ctx, int vcpu, int dir, uint64_t addr,
203    int size, uint64_t *val, void *arg1, long arg2)
204{
205	uint64_t offset;
206
207	if (addr + size > IOMEM_BASE + IOMEM_LEN)
208		return (-1);
209
210	offset = addr - IOMEM_BASE;
211	if (dir == MEM_F_READ) {
212		(void)memcpy(val, pctestdev_iomem_buf + offset, size);
213	} else {
214		assert(dir == MEM_F_WRITE);
215		(void)memcpy(pctestdev_iomem_buf + offset, val, size);
216	}
217
218	return (0);
219}
220
221static int
222pctestdev_ioport_io(struct vmctx *ctx, int vcpu, int in, int port,
223    int bytes, uint32_t *eax, void *arg)
224{
225	uint32_t mask;
226	int lsb;
227
228	if (port + bytes > IOPORT_BASE + IOPORT_LEN)
229		return (-1);
230
231	lsb = (port & 0x3) * 8;
232	mask = (-1UL >> (32 - (bytes * 8))) << lsb;
233
234	if (in)
235		*eax = (pctestdev_ioport_data & mask) >> lsb;
236	else {
237		pctestdev_ioport_data &= ~mask;
238		pctestdev_ioport_data |= *eax << lsb;
239	}
240
241	return (0);
242}
243
244static int
245pctestdev_irq_io(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
246    uint32_t *eax, void *arg)
247{
248	int irq;
249
250	if (bytes != 1)
251		return (-1);
252
253	if (in) {
254		*eax = 0;
255		return (0);
256	} else {
257		irq = port - IRQ_BASE;
258		if (irq < 16) {
259			if (*eax)
260				return (vm_isa_assert_irq(ctx, irq, irq));
261			else
262				return (vm_isa_deassert_irq(ctx, irq, irq));
263		} else {
264			if (*eax)
265				return (vm_ioapic_assert_irq(ctx, irq));
266			else
267				return (vm_ioapic_deassert_irq(ctx, irq));
268		}
269	}
270}
271