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