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 71 static bool pctestdev_inited; 72 static uint8_t pctestdev_iomem_buf[IOMEM_LEN]; 73 static uint32_t pctestdev_ioport_data; 74 75 static int pctestdev_debugexit_io(struct vmctx *ctx, int in, 76 int port, int bytes, uint32_t *eax, void *arg); 77 static 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); 80 static int pctestdev_ioport_io(struct vmctx *ctx, int in, 81 int port, int bytes, uint32_t *eax, void *arg); 82 static int pctestdev_irq_io(struct vmctx *ctx, int in, 83 int port, int bytes, uint32_t *eax, void *arg); 84 85 const char * 86 pctestdev_getname(void) 87 { 88 return (PCTESTDEV_NAME); 89 } 90 91 int 92 pctestdev_init(struct vmctx *ctx) 93 { 94 struct mem_range iomem; 95 struct inout_port debugexit, ioport, irq; 96 int err, pincount; 97 98 if (pctestdev_inited) { 99 EPRINTLN("Only one pc-testdev device is allowed."); 100 101 return (-1); 102 } 103 104 err = vm_ioapic_pincount(ctx, &pincount); 105 if (err != 0) { 106 EPRINTLN("pc-testdev: Failed to obtain IOAPIC pin count."); 107 108 return (-1); 109 } 110 if (pincount < IRQ_IOAPIC_PINCOUNT_MIN || 111 pincount > IRQ_IOAPIC_PINCOUNT_MAX) { 112 EPRINTLN("pc-testdev: Unsupported IOAPIC pin count: %d.", 113 pincount); 114 115 return (-1); 116 } 117 118 debugexit.name = DEBUGEXIT_NAME; 119 debugexit.port = DEBUGEXIT_BASE; 120 debugexit.size = DEBUGEXIT_LEN; 121 debugexit.flags = IOPORT_F_INOUT; 122 debugexit.handler = pctestdev_debugexit_io; 123 debugexit.arg = NULL; 124 125 iomem.name = IOMEM_NAME; 126 iomem.flags = MEM_F_RW | MEM_F_IMMUTABLE; 127 iomem.handler = pctestdev_iomem_io; 128 iomem.arg1 = NULL; 129 iomem.arg2 = 0; 130 iomem.base = IOMEM_BASE; 131 iomem.size = IOMEM_LEN; 132 133 ioport.name = IOPORT_NAME; 134 ioport.port = IOPORT_BASE; 135 ioport.size = IOPORT_LEN; 136 ioport.flags = IOPORT_F_INOUT; 137 ioport.handler = pctestdev_ioport_io; 138 ioport.arg = NULL; 139 140 irq.name = IRQ_NAME; 141 irq.port = IRQ_BASE; 142 irq.size = pincount; 143 irq.flags = IOPORT_F_INOUT; 144 irq.handler = pctestdev_irq_io; 145 irq.arg = NULL; 146 147 err = register_inout(&debugexit); 148 if (err != 0) 149 goto fail; 150 151 err = register_inout(&ioport); 152 if (err != 0) 153 goto fail_after_debugexit_reg; 154 155 err = register_inout(&irq); 156 if (err != 0) 157 goto fail_after_ioport_reg; 158 159 err = register_mem(&iomem); 160 if (err != 0) 161 goto fail_after_irq_reg; 162 163 pctestdev_inited = true; 164 165 return (0); 166 167 fail_after_irq_reg: 168 (void)unregister_inout(&irq); 169 170 fail_after_ioport_reg: 171 (void)unregister_inout(&ioport); 172 173 fail_after_debugexit_reg: 174 (void)unregister_inout(&debugexit); 175 176 fail: 177 return (err); 178 } 179 180 static int 181 pctestdev_debugexit_io(struct vmctx *ctx __unused, int in, 182 int port __unused, int bytes __unused, uint32_t *eax, void *arg __unused) 183 { 184 if (in) 185 *eax = 0; 186 else 187 exit((*eax << 1) | 1); 188 189 return (0); 190 } 191 192 static int 193 pctestdev_iomem_io(struct vmctx *ctx __unused, int vcpu __unused, int dir, 194 uint64_t addr, int size, uint64_t *val, void *arg1 __unused, 195 long arg2 __unused) 196 { 197 uint64_t offset; 198 199 if (addr + size > IOMEM_BASE + IOMEM_LEN) 200 return (-1); 201 202 offset = addr - IOMEM_BASE; 203 if (dir == MEM_F_READ) { 204 (void)memcpy(val, pctestdev_iomem_buf + offset, size); 205 } else { 206 assert(dir == MEM_F_WRITE); 207 (void)memcpy(pctestdev_iomem_buf + offset, val, size); 208 } 209 210 return (0); 211 } 212 213 static int 214 pctestdev_ioport_io(struct vmctx *ctx __unused, int in, 215 int port, int bytes, uint32_t *eax, void *arg __unused) 216 { 217 uint32_t mask; 218 int lsb; 219 220 if (port + bytes > IOPORT_BASE + IOPORT_LEN) 221 return (-1); 222 223 lsb = (port & 0x3) * 8; 224 mask = (-1UL >> (32 - (bytes * 8))) << lsb; 225 226 if (in) 227 *eax = (pctestdev_ioport_data & mask) >> lsb; 228 else { 229 pctestdev_ioport_data &= ~mask; 230 pctestdev_ioport_data |= *eax << lsb; 231 } 232 233 return (0); 234 } 235 236 static int 237 pctestdev_irq_io(struct vmctx *ctx, int in, int port, 238 int bytes, uint32_t *eax, void *arg __unused) 239 { 240 int irq; 241 242 if (bytes != 1) 243 return (-1); 244 245 if (in) { 246 *eax = 0; 247 return (0); 248 } else { 249 irq = port - IRQ_BASE; 250 if (irq < 16) { 251 if (*eax) 252 return (vm_isa_assert_irq(ctx, irq, irq)); 253 else 254 return (vm_isa_deassert_irq(ctx, irq, irq)); 255 } else { 256 if (*eax) 257 return (vm_ioapic_assert_irq(ctx, irq)); 258 else 259 return (vm_ioapic_deassert_irq(ctx, irq)); 260 } 261 } 262 } 263