# # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2024 OmniOS Community Edition (OmniOSce) Association. # BHYVE_OBJS = $(COMMON_OBJS) \ atkbdc.o \ bhyverun_machdep.o \ e820.o \ inout.o \ ioapic.o \ fwctl.o \ mptbl.o \ pci_fbuf.o \ pci_lpc.o \ pci_passthru.o \ pctestdev.o \ post.o \ pm.o \ ps2kbd.o \ ps2mouse.o \ rfb.o \ rtc.o \ spinup_ap.o \ task_switch.o \ vga.o \ vmexit.o \ xmsr.o include ../Makefile.com CPPFLAGS += -I. install: all $(ROOTUSRSBINPROG) /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2014 Tycho Nightingale * Copyright (c) 2015 Nahanni Systems Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "acpi.h" #include "atkbdc.h" #include "inout.h" #include "pci_emul.h" #include "pci_irq.h" #include "pci_lpc.h" #include "ps2kbd.h" #include "ps2mouse.h" #define KBD_DATA_PORT 0x60 #define KBD_STS_CTL_PORT 0x64 #define KBDC_RESET 0xfe #define KBD_DEV_IRQ 1 #define AUX_DEV_IRQ 12 /* controller commands */ #define KBDC_SET_COMMAND_BYTE 0x60 #define KBDC_GET_COMMAND_BYTE 0x20 #define KBDC_DISABLE_AUX_PORT 0xa7 #define KBDC_ENABLE_AUX_PORT 0xa8 #define KBDC_TEST_AUX_PORT 0xa9 #define KBDC_TEST_CTRL 0xaa #define KBDC_TEST_KBD_PORT 0xab #define KBDC_DISABLE_KBD_PORT 0xad #define KBDC_ENABLE_KBD_PORT 0xae #define KBDC_READ_INPORT 0xc0 #define KBDC_READ_OUTPORT 0xd0 #define KBDC_WRITE_OUTPORT 0xd1 #define KBDC_WRITE_KBD_OUTBUF 0xd2 #define KBDC_WRITE_AUX_OUTBUF 0xd3 #define KBDC_WRITE_TO_AUX 0xd4 /* controller command byte (set by KBDC_SET_COMMAND_BYTE) */ #define KBD_TRANSLATION 0x40 #define KBD_SYS_FLAG_BIT 0x04 #define KBD_DISABLE_KBD_PORT 0x10 #define KBD_DISABLE_AUX_PORT 0x20 #define KBD_ENABLE_AUX_INT 0x02 #define KBD_ENABLE_KBD_INT 0x01 #define KBD_KBD_CONTROL_BITS (KBD_DISABLE_KBD_PORT | KBD_ENABLE_KBD_INT) #define KBD_AUX_CONTROL_BITS (KBD_DISABLE_AUX_PORT | KBD_ENABLE_AUX_INT) /* controller status bits */ #define KBDS_KBD_BUFFER_FULL 0x01 #define KBDS_SYS_FLAG 0x04 #define KBDS_CTRL_FLAG 0x08 #define KBDS_AUX_BUFFER_FULL 0x20 /* controller output port */ #define KBDO_KBD_OUTFULL 0x10 #define KBDO_AUX_OUTFULL 0x20 #define RAMSZ 32 #define FIFOSZ 15 #define CTRL_CMD_FLAG 0x8000 struct kbd_dev { bool irq_active; int irq; uint8_t buffer[FIFOSZ]; int brd, bwr; int bcnt; }; struct aux_dev { bool irq_active; int irq; }; struct atkbdc_softc { struct vmctx *ctx; pthread_mutex_t mtx; struct ps2kbd_softc *ps2kbd_sc; struct ps2mouse_softc *ps2mouse_sc; uint8_t status; /* status register */ uint8_t outport; /* controller output port */ uint8_t ram[RAMSZ]; /* byte0 = controller config */ uint32_t curcmd; /* current command for next byte */ uint32_t ctrlbyte; struct kbd_dev kbd; struct aux_dev aux; }; static void atkbdc_assert_kbd_intr(struct atkbdc_softc *sc) { if ((sc->ram[0] & KBD_ENABLE_KBD_INT) != 0) { sc->kbd.irq_active = true; vm_isa_pulse_irq(sc->ctx, sc->kbd.irq, sc->kbd.irq); } } static void atkbdc_assert_aux_intr(struct atkbdc_softc *sc) { if ((sc->ram[0] & KBD_ENABLE_AUX_INT) != 0) { sc->aux.irq_active = true; vm_isa_pulse_irq(sc->ctx, sc->aux.irq, sc->aux.irq); } } static int atkbdc_kbd_queue_data(struct atkbdc_softc *sc, uint8_t val) { assert(pthread_mutex_isowned_np(&sc->mtx)); if (sc->kbd.bcnt < FIFOSZ) { sc->kbd.buffer[sc->kbd.bwr] = val; sc->kbd.bwr = (sc->kbd.bwr + 1) % FIFOSZ; sc->kbd.bcnt++; sc->status |= KBDS_KBD_BUFFER_FULL; sc->outport |= KBDO_KBD_OUTFULL; } else { printf("atkbd data buffer full\n"); } return (sc->kbd.bcnt < FIFOSZ); } static void atkbdc_kbd_read(struct atkbdc_softc *sc) { const uint8_t translation[256] = { 0xff, 0x43, 0x41, 0x3f, 0x3d, 0x3b, 0x3c, 0x58, 0x64, 0x44, 0x42, 0x40, 0x3e, 0x0f, 0x29, 0x59, 0x65, 0x38, 0x2a, 0x70, 0x1d, 0x10, 0x02, 0x5a, 0x66, 0x71, 0x2c, 0x1f, 0x1e, 0x11, 0x03, 0x5b, 0x67, 0x2e, 0x2d, 0x20, 0x12, 0x05, 0x04, 0x5c, 0x68, 0x39, 0x2f, 0x21, 0x14, 0x13, 0x06, 0x5d, 0x69, 0x31, 0x30, 0x23, 0x22, 0x15, 0x07, 0x5e, 0x6a, 0x72, 0x32, 0x24, 0x16, 0x08, 0x09, 0x5f, 0x6b, 0x33, 0x25, 0x17, 0x18, 0x0b, 0x0a, 0x60, 0x6c, 0x34, 0x35, 0x26, 0x27, 0x19, 0x0c, 0x61, 0x6d, 0x73, 0x28, 0x74, 0x1a, 0x0d, 0x62, 0x6e, 0x3a, 0x36, 0x1c, 0x1b, 0x75, 0x2b, 0x63, 0x76, 0x55, 0x56, 0x77, 0x78, 0x79, 0x7a, 0x0e, 0x7b, 0x7c, 0x4f, 0x7d, 0x4b, 0x47, 0x7e, 0x7f, 0x6f, 0x52, 0x53, 0x50, 0x4c, 0x4d, 0x48, 0x01, 0x45, 0x57, 0x4e, 0x51, 0x4a, 0x37, 0x49, 0x46, 0x54, 0x80, 0x81, 0x82, 0x41, 0x54, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff }; uint8_t val; uint8_t release = 0; assert(pthread_mutex_isowned_np(&sc->mtx)); if (sc->ram[0] & KBD_TRANSLATION) { while (ps2kbd_read(sc->ps2kbd_sc, &val) != -1) { if (val == 0xf0) { release = 0x80; continue; } else { val = translation[val] | release; } atkbdc_kbd_queue_data(sc, val); break; } } else { while (sc->kbd.bcnt < FIFOSZ) { if (ps2kbd_read(sc->ps2kbd_sc, &val) != -1) atkbdc_kbd_queue_data(sc, val); else break; } } if (((sc->ram[0] & KBD_DISABLE_AUX_PORT) || ps2mouse_fifocnt(sc->ps2mouse_sc) == 0) && sc->kbd.bcnt > 0) atkbdc_assert_kbd_intr(sc); } static void atkbdc_aux_poll(struct atkbdc_softc *sc) { if (ps2mouse_fifocnt(sc->ps2mouse_sc) > 0) { sc->status |= KBDS_AUX_BUFFER_FULL | KBDS_KBD_BUFFER_FULL; sc->outport |= KBDO_AUX_OUTFULL; atkbdc_assert_aux_intr(sc); } } static void atkbdc_kbd_poll(struct atkbdc_softc *sc) { assert(pthread_mutex_isowned_np(&sc->mtx)); atkbdc_kbd_read(sc); } static void atkbdc_poll(struct atkbdc_softc *sc) { atkbdc_aux_poll(sc); atkbdc_kbd_poll(sc); } static void atkbdc_dequeue_data(struct atkbdc_softc *sc, uint8_t *buf) { assert(pthread_mutex_isowned_np(&sc->mtx)); if (ps2mouse_read(sc->ps2mouse_sc, buf) == 0) { if (ps2mouse_fifocnt(sc->ps2mouse_sc) == 0) { if (sc->kbd.bcnt == 0) sc->status &= ~(KBDS_AUX_BUFFER_FULL | KBDS_KBD_BUFFER_FULL); else sc->status &= ~(KBDS_AUX_BUFFER_FULL); sc->outport &= ~KBDO_AUX_OUTFULL; } atkbdc_poll(sc); return; } if (sc->kbd.bcnt > 0) { *buf = sc->kbd.buffer[sc->kbd.brd]; sc->kbd.brd = (sc->kbd.brd + 1) % FIFOSZ; sc->kbd.bcnt--; if (sc->kbd.bcnt == 0) { sc->status &= ~KBDS_KBD_BUFFER_FULL; sc->outport &= ~KBDO_KBD_OUTFULL; } atkbdc_poll(sc); } if (ps2mouse_fifocnt(sc->ps2mouse_sc) == 0 && sc->kbd.bcnt == 0) { sc->status &= ~(KBDS_AUX_BUFFER_FULL | KBDS_KBD_BUFFER_FULL); } } static int atkbdc_data_handler(struct vmctx *ctx __unused, int in, int port __unused, int bytes, uint32_t *eax, void *arg) { struct atkbdc_softc *sc; uint8_t buf; int retval; if (bytes != 1) return (-1); sc = arg; retval = 0; pthread_mutex_lock(&sc->mtx); if (in) { sc->curcmd = 0; if (sc->ctrlbyte != 0) { *eax = sc->ctrlbyte & 0xff; sc->ctrlbyte = 0; } else { /* read device buffer; includes kbd cmd responses */ atkbdc_dequeue_data(sc, &buf); *eax = buf; } sc->status &= ~KBDS_CTRL_FLAG; pthread_mutex_unlock(&sc->mtx); return (retval); } if (sc->status & KBDS_CTRL_FLAG) { /* * Command byte for the controller. */ switch (sc->curcmd) { case KBDC_SET_COMMAND_BYTE: sc->ram[0] = *eax; if (sc->ram[0] & KBD_SYS_FLAG_BIT) sc->status |= KBDS_SYS_FLAG; else sc->status &= ~KBDS_SYS_FLAG; break; case KBDC_WRITE_OUTPORT: sc->outport = *eax; break; case KBDC_WRITE_TO_AUX: ps2mouse_write(sc->ps2mouse_sc, *eax, 0); atkbdc_poll(sc); break; case KBDC_WRITE_KBD_OUTBUF: atkbdc_kbd_queue_data(sc, *eax); break; case KBDC_WRITE_AUX_OUTBUF: ps2mouse_write(sc->ps2mouse_sc, *eax, 1); sc->status |= (KBDS_AUX_BUFFER_FULL | KBDS_KBD_BUFFER_FULL); atkbdc_aux_poll(sc); break; default: /* write to particular RAM byte */ if (sc->curcmd >= 0x61 && sc->curcmd <= 0x7f) { int byten; byten = (sc->curcmd - 0x60) & 0x1f; sc->ram[byten] = *eax & 0xff; } break; } sc->curcmd = 0; sc->status &= ~KBDS_CTRL_FLAG; pthread_mutex_unlock(&sc->mtx); return (retval); } /* * Data byte for the device. */ ps2kbd_write(sc->ps2kbd_sc, *eax); atkbdc_poll(sc); pthread_mutex_unlock(&sc->mtx); return (retval); } static int atkbdc_sts_ctl_handler(struct vmctx *ctx, int in, int port __unused, int bytes, uint32_t *eax, void *arg) { struct atkbdc_softc *sc; int error, retval; if (bytes != 1) return (-1); sc = arg; retval = 0; pthread_mutex_lock(&sc->mtx); if (in) { /* read status register */ *eax = sc->status; pthread_mutex_unlock(&sc->mtx); return (retval); } sc->curcmd = 0; sc->status |= KBDS_CTRL_FLAG; sc->ctrlbyte = 0; switch (*eax) { case KBDC_GET_COMMAND_BYTE: sc->ctrlbyte = CTRL_CMD_FLAG | sc->ram[0]; break; case KBDC_TEST_CTRL: sc->ctrlbyte = CTRL_CMD_FLAG | 0x55; break; case KBDC_TEST_AUX_PORT: case KBDC_TEST_KBD_PORT: sc->ctrlbyte = CTRL_CMD_FLAG | 0; break; case KBDC_READ_INPORT: sc->ctrlbyte = CTRL_CMD_FLAG | 0; break; case KBDC_READ_OUTPORT: sc->ctrlbyte = CTRL_CMD_FLAG | sc->outport; break; case KBDC_SET_COMMAND_BYTE: case KBDC_WRITE_OUTPORT: case KBDC_WRITE_KBD_OUTBUF: case KBDC_WRITE_AUX_OUTBUF: sc->curcmd = *eax; break; case KBDC_DISABLE_KBD_PORT: sc->ram[0] |= KBD_DISABLE_KBD_PORT; break; case KBDC_ENABLE_KBD_PORT: sc->ram[0] &= ~KBD_DISABLE_KBD_PORT; if (sc->kbd.bcnt > 0) sc->status |= KBDS_KBD_BUFFER_FULL; atkbdc_poll(sc); break; case KBDC_WRITE_TO_AUX: sc->curcmd = *eax; break; case KBDC_DISABLE_AUX_PORT: sc->ram[0] |= KBD_DISABLE_AUX_PORT; ps2mouse_toggle(sc->ps2mouse_sc, 0); sc->status &= ~(KBDS_AUX_BUFFER_FULL | KBDS_KBD_BUFFER_FULL); sc->outport &= ~KBDS_AUX_BUFFER_FULL; break; case KBDC_ENABLE_AUX_PORT: sc->ram[0] &= ~KBD_DISABLE_AUX_PORT; ps2mouse_toggle(sc->ps2mouse_sc, 1); if (ps2mouse_fifocnt(sc->ps2mouse_sc) > 0) sc->status |= KBDS_AUX_BUFFER_FULL | KBDS_KBD_BUFFER_FULL; break; case KBDC_RESET: /* Pulse "reset" line */ error = vm_suspend(ctx, VM_SUSPEND_RESET); assert(error == 0 || errno == EALREADY); break; default: if (*eax >= 0x21 && *eax <= 0x3f) { /* read "byte N" from RAM */ int byten; byten = (*eax - 0x20) & 0x1f; sc->ctrlbyte = CTRL_CMD_FLAG | sc->ram[byten]; } break; } pthread_mutex_unlock(&sc->mtx); if (sc->ctrlbyte != 0) { sc->status |= KBDS_KBD_BUFFER_FULL; sc->status &= ~KBDS_AUX_BUFFER_FULL; atkbdc_assert_kbd_intr(sc); } else if (ps2mouse_fifocnt(sc->ps2mouse_sc) > 0 && (sc->ram[0] & KBD_DISABLE_AUX_PORT) == 0) { sc->status |= KBDS_AUX_BUFFER_FULL | KBDS_KBD_BUFFER_FULL; atkbdc_assert_aux_intr(sc); } else if (sc->kbd.bcnt > 0 && (sc->ram[0] & KBD_DISABLE_KBD_PORT) == 0) { sc->status |= KBDS_KBD_BUFFER_FULL; atkbdc_assert_kbd_intr(sc); } return (retval); } void atkbdc_event(struct atkbdc_softc *sc, int iskbd) { pthread_mutex_lock(&sc->mtx); if (iskbd) atkbdc_kbd_poll(sc); else atkbdc_aux_poll(sc); pthread_mutex_unlock(&sc->mtx); } void atkbdc_init(struct vmctx *ctx) { struct inout_port iop; struct atkbdc_softc *sc; int error; sc = calloc(1, sizeof(struct atkbdc_softc)); sc->ctx = ctx; pthread_mutex_init(&sc->mtx, NULL); bzero(&iop, sizeof(struct inout_port)); iop.name = "atkdbc"; iop.port = KBD_STS_CTL_PORT; iop.size = 1; iop.flags = IOPORT_F_INOUT; iop.handler = atkbdc_sts_ctl_handler; iop.arg = sc; error = register_inout(&iop); assert(error == 0); bzero(&iop, sizeof(struct inout_port)); iop.name = "atkdbc"; iop.port = KBD_DATA_PORT; iop.size = 1; iop.flags = IOPORT_F_INOUT; iop.handler = atkbdc_data_handler; iop.arg = sc; error = register_inout(&iop); assert(error == 0); pci_irq_reserve(KBD_DEV_IRQ); sc->kbd.irq = KBD_DEV_IRQ; pci_irq_reserve(AUX_DEV_IRQ); sc->aux.irq = AUX_DEV_IRQ; sc->ps2kbd_sc = ps2kbd_init(sc); sc->ps2mouse_sc = ps2mouse_init(sc); } static void atkbdc_dsdt(void) { dsdt_line(""); dsdt_line("Device (KBD)"); dsdt_line("{"); dsdt_line(" Name (_HID, EisaId (\"PNP0303\"))"); dsdt_line(" Name (_CRS, ResourceTemplate ()"); dsdt_line(" {"); dsdt_indent(2); dsdt_fixed_ioport(KBD_DATA_PORT, 1); dsdt_fixed_ioport(KBD_STS_CTL_PORT, 1); dsdt_fixed_irq(1); dsdt_unindent(2); dsdt_line(" })"); dsdt_line("}"); dsdt_line(""); dsdt_line("Device (MOU)"); dsdt_line("{"); dsdt_line(" Name (_HID, EisaId (\"PNP0F13\"))"); dsdt_line(" Name (_CRS, ResourceTemplate ()"); dsdt_line(" {"); dsdt_indent(2); dsdt_fixed_ioport(KBD_DATA_PORT, 1); dsdt_fixed_ioport(KBD_STS_CTL_PORT, 1); dsdt_fixed_irq(12); dsdt_unindent(2); dsdt_line(" })"); dsdt_line("}"); } LPC_DSDT(atkbdc_dsdt); /*- * Copyright (c) 2015 Tycho Nightingale * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _ATKBDC_H_ #define _ATKBDC_H_ struct atkbdc_softc; struct vmctx; void atkbdc_init(struct vmctx *ctx); void atkbdc_event(struct atkbdc_softc *sc, int iskbd); #endif /* _ATKBDC_H_ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 NetApp, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. * * Copyright 2015 Pluribus Networks Inc. * Copyright 2018 Joyent, Inc. * Copyright 2025 Oxide Computer Company * Copyright 2022 OmniOS Community Edition (OmniOSce) Association. */ #include #include #include #include #include #include #include #include #include #include #include "bhyverun.h" #include "bootrom.h" #include "acpi.h" #include "atkbdc.h" #include "config.h" #include "debug.h" #include "e820.h" #include "fwctl.h" #include "ioapic.h" #include "inout.h" #ifndef __FreeBSD__ #include "kernemu_dev.h" #endif #include "mptbl.h" #include "pci_emul.h" #include "pci_irq.h" #include "spinup_ap.h" #include "pci_lpc.h" #include "rtc.h" #include "smbiostbl.h" #include "xmsr.h" void bhyve_usage(int code) { const char *progname = getprogname(); fprintf(stderr, #ifdef __FreeBSD__ "Usage: %s [-AaCDeHhPSuWwxY]\n" #else "Usage: %s [-aCDdeHhPSuWwxY]\n" #endif " %2$.*3$s [-c [[cpus=]numcpus][,sockets=n][,cores=n][,threads=n]]\n" #ifdef __FreeBSD__ " %2$.*3$s [-G port] [-k config_file] [-l lpc] [-m mem] [-o var=value]\n" " %2$.*3$s [-p vcpu:hostcpu] [-r file] [-s pci] [-U uuid] vmname\n" " -A: create ACPI tables\n" #else " %2$.*3$s [-k ] [-l ] [-m mem] [-o =]\n" " %2$.*3$s [-s ] [-U uuid] vmname\n" #endif " -a: local apic is in xAPIC mode (deprecated)\n" #ifndef __FreeBSD__ " -B type,key=value,...: set SMBIOS information\n" #endif " -C: include guest memory in core file\n" " -c: number of CPUs and/or topology specification\n" " -D: destroy on power-off\n" #ifndef __FreeBSD__ " -d: suspend cpu at boot\n" #endif " -e: exit on unhandled I/O access\n" #ifdef __FreeBSD__ " -G: start a debug server\n" #endif " -H: vmexit from the guest on HLT\n" " -h: help\n" " -k: key=value flat config file\n" " -K: PS2 keyboard layout\n" " -l: LPC device configuration\n" " -m: memory size\n" " -o: set config 'var' to 'value'\n" " -P: vmexit from the guest on pause\n" #ifdef __FreeBSD__ " -p: pin 'vcpu' to 'hostcpu'\n" #endif " -S: guest memory cannot be swapped\n" " -s: PCI slot config\n" " -U: UUID\n" " -u: RTC keeps UTC time\n" " -W: force virtio to use single-vector MSI\n" " -w: ignore unimplemented MSRs\n" " -x: local APIC is in x2APIC mode\n" " -Y: disable MPtable generation\n", progname, "", (int)strlen(progname)); exit(code); } void bhyve_optparse(int argc, char **argv) { const char *optstr; int c; #ifdef __FreeBSD__ optstr = "aehuwxACDHIPSWYk:f:o:p:G:c:s:m:l:K:U:"; #else /* +d, +B, -p */ optstr = "adehuwxACDHIPSWYk:f:o:G:c:s:m:l:B:K:U:"; #endif while ((c = getopt(argc, argv, optstr)) != -1) { switch (c) { case 'a': set_config_bool("x86.x2apic", false); break; case 'A': set_config_bool("acpi_tables", true); break; case 'D': set_config_bool("destroy_on_poweroff", true); break; #ifndef __FreeBSD__ case 'B': if (smbios_parse(optarg) != 0) { errx(EX_USAGE, "invalid SMBIOS " "configuration '%s'", optarg); } break; case 'd': set_config_bool("suspend_at_boot", true); break; #endif #ifdef __FreeBSD__ case 'p': if (pincpu_parse(optarg) != 0) { errx(EX_USAGE, "invalid vcpu pinning " "configuration '%s'", optarg); } break; #endif case 'c': if (bhyve_topology_parse(optarg) != 0) { errx(EX_USAGE, "invalid cpu topology " "'%s'", optarg); } break; case 'C': set_config_bool("memory.guest_in_core", true); break; case 'f': if (qemu_fwcfg_parse_cmdline_arg(optarg) != 0) { errx(EX_USAGE, "invalid fwcfg item '%s'", optarg); } break; case 'G': bhyve_parse_gdb_options(optarg); break; case 'k': bhyve_parse_simple_config_file(optarg); break; case 'K': set_config_value("keyboard.layout", optarg); break; case 'l': if (strncmp(optarg, "help", strlen(optarg)) == 0) { lpc_print_supported_devices(); exit(0); } else if (lpc_device_parse(optarg) != 0) { errx(EX_USAGE, "invalid lpc device " "configuration '%s'", optarg); } break; case 's': if (strncmp(optarg, "help", strlen(optarg)) == 0) { pci_print_supported_devices(); exit(0); } else if (pci_parse_slot(optarg) != 0) exit(4); else break; case 'S': set_config_bool("memory.wired", true); break; case 'm': set_config_value("memory.size", optarg); break; case 'o': if (!bhyve_parse_config_option(optarg)) errx(EX_USAGE, "invalid configuration option '%s'", optarg); break; case 'H': set_config_bool("x86.vmexit_on_hlt", true); break; case 'I': /* * The "-I" option was used to add an ioapic to the * virtual machine. * * An ioapic is now provided unconditionally for each * virtual machine and this option is now deprecated. */ break; case 'P': set_config_bool("x86.vmexit_on_pause", true); break; case 'e': set_config_bool("x86.strictio", true); break; case 'u': set_config_bool("rtc.use_localtime", false); break; case 'U': set_config_value("uuid", optarg); break; case 'w': set_config_bool("x86.strictmsr", false); break; case 'W': set_config_bool("virtio.msix", false); break; case 'x': set_config_bool("x86.x2apic", true); break; case 'Y': set_config_bool("x86.mptable", false); break; case 'h': bhyve_usage(0); default: bhyve_usage(1); } } /* Handle backwards compatibility aliases in config options. */ if (get_config_value("lpc.bootrom") != NULL && get_config_value("bootrom") == NULL) { warnx("lpc.bootrom is deprecated, use '-o bootrom' instead"); set_config_value("bootrom", get_config_value("lpc.bootrom")); } if (get_config_value("lpc.bootvars") != NULL && get_config_value("bootvars") == NULL) { warnx("lpc.bootvars is deprecated, use '-o bootvars' instead"); set_config_value("bootvars", get_config_value("lpc.bootvars")); } } void bhyve_init_config(void) { init_config(); /* Set default values prior to option parsing. */ set_config_bool("acpi_tables", false); set_config_bool("acpi_tables_in_memory", true); set_config_value("memory.size", "256M"); set_config_bool("x86.strictmsr", true); set_config_value("lpc.fwcfg", "bhyve"); } void bhyve_init_vcpu(struct vcpu *vcpu) { int err, tmp; #ifdef __FreeBSD__ if (get_config_bool_default("x86.vmexit_on_hlt", false)) { err = vm_get_capability(vcpu, VM_CAP_HALT_EXIT, &tmp); if (err < 0) { EPRINTLN("VM exit on HLT not supported"); exit(4); } vm_set_capability(vcpu, VM_CAP_HALT_EXIT, 1); } #else /* * We insist that vmexit-on-hlt is available on the host CPU, and enable * it by default. Configuration of that feature is done with both of * those facts in mind. */ tmp = (int)get_config_bool_default("x86.vmexit_on_hlt", true); err = vm_set_capability(vcpu, VM_CAP_HALT_EXIT, tmp); if (err < 0) { fprintf(stderr, "VM exit on HLT not supported\n"); exit(4); } #endif /* __FreeBSD__ */ if (get_config_bool_default("x86.vmexit_on_pause", false)) { /* * pause exit support required for this mode */ err = vm_get_capability(vcpu, VM_CAP_PAUSE_EXIT, &tmp); if (err < 0) { EPRINTLN("SMP mux requested, no pause support"); exit(4); } vm_set_capability(vcpu, VM_CAP_PAUSE_EXIT, 1); } if (get_config_bool_default("x86.x2apic", false)) err = vm_set_x2apic_state(vcpu, X2APIC_ENABLED); else err = vm_set_x2apic_state(vcpu, X2APIC_DISABLED); if (err) { EPRINTLN("Unable to set x2apic state (%d)", err); exit(4); } #ifdef __FreeBSD__ vm_set_capability(vcpu, VM_CAP_ENABLE_INVPCID, 1); err = vm_set_capability(vcpu, VM_CAP_IPI_EXIT, 1); assert(err == 0); #endif } void bhyve_start_vcpu(struct vcpu *vcpu, bool bsp, bool suspend) { int error; if (!bsp) { #ifndef __FreeBSD__ /* * On illumos, all APs are spun up halted and run-state * transitions (INIT, SIPI, etc) are handled in-kernel. */ spinup_ap(vcpu, 0); #endif bhyve_init_vcpu(vcpu); #ifdef __FreeBSD__ /* * Enable the 'unrestricted guest' mode for APs. * * APs startup in power-on 16-bit mode. */ error = vm_set_capability(vcpu, VM_CAP_UNRESTRICTED_GUEST, 1); assert(error == 0); #endif } #ifndef __FreeBSD__ /* * The value of 'suspend' for the BSP depends on whether the -d * (suspend_at_boot) flag was given to bhyve. Regardless of that * value we always want to set the BSP to VRS_RUN and all others to * VRS_HALT. */ error = vm_set_run_state(vcpu, bsp ? VRS_RUN : VRS_HALT, 0); assert(error == 0); #endif fbsdrun_addcpu(vcpu_id(vcpu), suspend); } int bhyve_init_platform(struct vmctx *ctx, struct vcpu *bsp __unused) { int error; error = init_msr(); if (error != 0) return (error); init_inout(); #ifdef __FreeBSD__ kernemu_dev_init(); #endif atkbdc_init(ctx); pci_irq_init(ctx); ioapic_init(ctx); rtc_init(ctx); sci_init(ctx); #ifndef __FreeBSD__ pmtmr_init(ctx); #endif error = e820_init(ctx); if (error != 0) return (error); error = bootrom_loadrom(ctx); if (error != 0) return (error); #ifndef __FreeBSD__ if (get_config_bool_default("e820.debug", false)) e820_dump_table(); #endif return (0); } int bhyve_init_platform_late(struct vmctx *ctx, struct vcpu *bsp __unused) { int error; if (get_config_bool_default("x86.mptable", true)) { error = mptable_build(ctx, guest_ncpus); if (error != 0) return (error); } error = smbios_build(ctx); if (error != 0) return (error); error = e820_finalize(); if (error != 0) return (error); if (bootrom_boot() && strcmp(lpc_fwcfg(), "bhyve") == 0) fwctl_init(); if (get_config_bool("acpi_tables")) { error = acpi_build(ctx, guest_ncpus); assert(error == 0); } return (0); } /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021 Beckhoff Automation GmbH & Co. KG * Author: Corvin Köhne */ #include #include #include #include #include #include #include #include #include #include "debug.h" #include "e820.h" #include "qemu_fwcfg.h" /* * E820 always uses 64 bit entries. Emulation code will use vm_paddr_t since it * works on physical addresses. If vm_paddr_t is larger than uint64_t E820 can't * hold all possible physical addresses and we can get into trouble. */ static_assert(sizeof(vm_paddr_t) <= sizeof(uint64_t), "Unable to represent physical memory by E820 table"); #define E820_FWCFG_FILE_NAME "etc/e820" #define KB (1024UL) #define MB (1024 * KB) #define GB (1024 * MB) /* * Fix E820 memory holes: * [ A0000, C0000) VGA * [ C0000, 100000) ROM */ #define E820_VGA_MEM_BASE 0xA0000 #define E820_VGA_MEM_END 0xC0000 #define E820_ROM_MEM_BASE 0xC0000 #define E820_ROM_MEM_END 0x100000 struct e820_element { TAILQ_ENTRY(e820_element) chain; uint64_t base; uint64_t end; enum e820_memory_type type; }; static TAILQ_HEAD(e820_table, e820_element) e820_table = TAILQ_HEAD_INITIALIZER( e820_table); static struct e820_element * e820_element_alloc(uint64_t base, uint64_t end, enum e820_memory_type type) { struct e820_element *element; element = calloc(1, sizeof(*element)); if (element == NULL) { return (NULL); } element->base = base; element->end = end; element->type = type; return (element); } static const char * e820_get_type_name(const enum e820_memory_type type) { switch (type) { case E820_TYPE_MEMORY: return ("RAM"); case E820_TYPE_RESERVED: return ("Reserved"); case E820_TYPE_ACPI: return ("ACPI"); case E820_TYPE_NVS: return ("NVS"); default: return ("Unknown"); } } void e820_dump_table(void) { struct e820_element *element; uint64_t i; EPRINTLN("E820 map:"); i = 0; TAILQ_FOREACH(element, &e820_table, chain) { EPRINTLN(" (%4lu) [%16lx, %16lx] %s", i, element->base, element->end, e820_get_type_name(element->type)); ++i; } } static struct qemu_fwcfg_item * e820_get_fwcfg_item(void) { struct qemu_fwcfg_item *fwcfg_item; struct e820_element *element; struct e820_entry *entries; int count, i; count = 0; TAILQ_FOREACH(element, &e820_table, chain) { ++count; } if (count == 0) { warnx("%s: E820 table empty", __func__); return (NULL); } fwcfg_item = calloc(1, sizeof(struct qemu_fwcfg_item)); if (fwcfg_item == NULL) { return (NULL); } fwcfg_item->size = count * sizeof(struct e820_entry); fwcfg_item->data = calloc(count, sizeof(struct e820_entry)); if (fwcfg_item->data == NULL) { free(fwcfg_item); return (NULL); } i = 0; entries = (struct e820_entry *)fwcfg_item->data; TAILQ_FOREACH(element, &e820_table, chain) { struct e820_entry *entry = &entries[i]; entry->base = element->base; entry->length = element->end - element->base; entry->type = element->type; ++i; } return (fwcfg_item); } static int e820_add_entry(const uint64_t base, const uint64_t end, const enum e820_memory_type type) { struct e820_element *new_element; struct e820_element *element; struct e820_element *sib_element; struct e820_element *ram_element; assert(end >= base); new_element = e820_element_alloc(base, end, type); if (new_element == NULL) { return (ENOMEM); } /* * E820 table should always be sorted in ascending order. Therefore, * search for a range whose end is larger than the base parameter. */ TAILQ_FOREACH(element, &e820_table, chain) { if (element->end > base) { break; } } /* * System memory requires special handling. */ if (type == E820_TYPE_MEMORY) { /* * base is larger than of any existing element. Add new system * memory at the end of the table. */ if (element == NULL) { TAILQ_INSERT_TAIL(&e820_table, new_element, chain); return (0); } /* * System memory shouldn't overlap with any existing element. */ assert(end >= element->base); TAILQ_INSERT_BEFORE(element, new_element, chain); return (0); } assert(element != NULL); /* Non system memory should be allocated inside system memory. */ assert(element->type == E820_TYPE_MEMORY); /* New element should fit into existing system memory element. */ assert(base >= element->base && end <= element->end); if (base == element->base && end == element->end) { /* * The new entry replaces an existing one. * * Old table: * [ 0x1000, 0x4000] RAM <-- element * New table: * [ 0x1000, 0x4000] Reserved */ TAILQ_INSERT_BEFORE(element, new_element, chain); TAILQ_REMOVE(&e820_table, element, chain); free(element); } else if (base == element->base) { /* * New element at system memory base boundary. Add new * element before current and adjust the base of the old * element. * * Old table: * [ 0x1000, 0x4000] RAM <-- element * New table: * [ 0x1000, 0x2000] Reserved * [ 0x2000, 0x4000] RAM <-- element */ TAILQ_INSERT_BEFORE(element, new_element, chain); element->base = end; } else if (end == element->end) { /* * New element at system memory end boundary. Add new * element after current and adjust the end of the * current element. * * Old table: * [ 0x1000, 0x4000] RAM <-- element * New table: * [ 0x1000, 0x3000] RAM <-- element * [ 0x3000, 0x4000] Reserved */ TAILQ_INSERT_AFTER(&e820_table, element, new_element, chain); element->end = base; } else { /* * New element inside system memory entry. Split it by * adding a system memory element and the new element * before current. * * Old table: * [ 0x1000, 0x4000] RAM <-- element * New table: * [ 0x1000, 0x2000] RAM * [ 0x2000, 0x3000] Reserved * [ 0x3000, 0x4000] RAM <-- element */ ram_element = e820_element_alloc(element->base, base, E820_TYPE_MEMORY); if (ram_element == NULL) { return (ENOMEM); } TAILQ_INSERT_BEFORE(element, ram_element, chain); TAILQ_INSERT_BEFORE(element, new_element, chain); element->base = end; } /* * If the previous element has the same type and ends at our base * boundary, we can merge both entries. */ sib_element = TAILQ_PREV(new_element, e820_table, chain); if (sib_element != NULL && sib_element->type == new_element->type && sib_element->end == new_element->base) { new_element->base = sib_element->base; TAILQ_REMOVE(&e820_table, sib_element, chain); free(sib_element); } /* * If the next element has the same type and starts at our end * boundary, we can merge both entries. */ sib_element = TAILQ_NEXT(new_element, chain); if (sib_element != NULL && sib_element->type == new_element->type && sib_element->base == new_element->end) { /* Merge new element into subsequent one. */ new_element->end = sib_element->end; TAILQ_REMOVE(&e820_table, sib_element, chain); free(sib_element); } return (0); } static int e820_add_memory_hole(const uint64_t base, const uint64_t end) { struct e820_element *element; struct e820_element *ram_element; assert(end >= base); /* * E820 table should be always sorted in ascending order. Therefore, * search for an element which end is larger than the base parameter. */ TAILQ_FOREACH(element, &e820_table, chain) { if (element->end > base) { break; } } if (element == NULL || end <= element->base) { /* Nothing to do. Hole already exists */ return (0); } /* Memory holes are only allowed in system memory */ assert(element->type == E820_TYPE_MEMORY); if (base == element->base) { /* * New hole at system memory base boundary. * * Old table: * [ 0x1000, 0x4000] RAM * New table: * [ 0x2000, 0x4000] RAM */ element->base = end; } else if (end == element->end) { /* * New hole at system memory end boundary. * * Old table: * [ 0x1000, 0x4000] RAM * New table: * [ 0x1000, 0x3000] RAM */ element->end = base; } else { /* * New hole inside system memory entry. Split the system memory. * * Old table: * [ 0x1000, 0x4000] RAM <-- element * New table: * [ 0x1000, 0x2000] RAM * [ 0x3000, 0x4000] RAM <-- element */ ram_element = e820_element_alloc(element->base, base, E820_TYPE_MEMORY); if (ram_element == NULL) { return (ENOMEM); } TAILQ_INSERT_BEFORE(element, ram_element, chain); element->base = end; } return (0); } static uint64_t e820_alloc_highest(const uint64_t max_address, const uint64_t length, const uint64_t alignment, const enum e820_memory_type type) { struct e820_element *element; TAILQ_FOREACH_REVERSE(element, &e820_table, e820_table, chain) { uint64_t address, base, end; end = MIN(max_address, element->end); base = roundup2(element->base, alignment); /* * If end - length == 0, we would allocate memory at address 0. This * address is mostly unusable and we should avoid allocating it. * Therefore, search for another block in that case. */ if (element->type != E820_TYPE_MEMORY || end < base || end - base < length || end - length == 0) { continue; } address = rounddown2(end - length, alignment); if (e820_add_entry(address, address + length, type) != 0) { return (0); } return (address); } return (0); } static uint64_t e820_alloc_lowest(const uint64_t min_address, const uint64_t length, const uint64_t alignment, const enum e820_memory_type type) { struct e820_element *element; TAILQ_FOREACH(element, &e820_table, chain) { uint64_t base, end; end = element->end; base = MAX(min_address, roundup2(element->base, alignment)); /* * If base == 0, we would allocate memory at address 0. This * address is mostly unusable and we should avoid allocating it. * Therefore, search for another block in that case. */ if (element->type != E820_TYPE_MEMORY || end < base || end - base < length || base == 0) { continue; } if (e820_add_entry(base, base + length, type) != 0) { return (0); } return (base); } return (0); } uint64_t e820_alloc(const uint64_t address, const uint64_t length, const uint64_t alignment, const enum e820_memory_type type, const enum e820_allocation_strategy strategy) { assert(powerof2(alignment)); assert((address & (alignment - 1)) == 0); switch (strategy) { case E820_ALLOCATE_ANY: /* * Allocate any address. Therefore, ignore the address parameter * and reuse the code path for allocating the lowest address. */ return (e820_alloc_lowest(0, length, alignment, type)); case E820_ALLOCATE_LOWEST: return (e820_alloc_lowest(address, length, alignment, type)); case E820_ALLOCATE_HIGHEST: return (e820_alloc_highest(address, length, alignment, type)); case E820_ALLOCATE_SPECIFIC: if (e820_add_entry(address, address + length, type) != 0) { return (0); } return (address); } return (0); } int e820_init(struct vmctx *const ctx) { uint64_t lowmem_size, highmem_size; int error; TAILQ_INIT(&e820_table); lowmem_size = vm_get_lowmem_size(ctx); error = e820_add_entry(0, lowmem_size, E820_TYPE_MEMORY); if (error) { warnx("%s: Could not add lowmem", __func__); return (error); } highmem_size = vm_get_highmem_size(ctx); if (highmem_size != 0) { error = e820_add_entry(4 * GB, 4 * GB + highmem_size, E820_TYPE_MEMORY); if (error) { warnx("%s: Could not add highmem", __func__); return (error); } } error = e820_add_memory_hole(E820_VGA_MEM_BASE, E820_VGA_MEM_END); if (error) { warnx("%s: Could not add VGA memory", __func__); return (error); } error = e820_add_memory_hole(E820_ROM_MEM_BASE, E820_ROM_MEM_END); if (error) { warnx("%s: Could not add ROM area", __func__); return (error); } return (0); } int e820_finalize(void) { struct qemu_fwcfg_item *e820_fwcfg_item; int error; e820_fwcfg_item = e820_get_fwcfg_item(); if (e820_fwcfg_item == NULL) { warnx("invalid e820 table"); return (ENOMEM); } error = qemu_fwcfg_add_file("etc/e820", e820_fwcfg_item->size, e820_fwcfg_item->data); if (error != 0) { warnx("could not add qemu fwcfg etc/e820"); free(e820_fwcfg_item->data); free(e820_fwcfg_item); return (error); } free(e820_fwcfg_item); return (0); } /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021 Beckhoff Automation GmbH & Co. KG * Author: Corvin Köhne */ #pragma once #include #include "qemu_fwcfg.h" enum e820_memory_type { E820_TYPE_MEMORY = 1, E820_TYPE_RESERVED = 2, E820_TYPE_ACPI = 3, E820_TYPE_NVS = 4 }; enum e820_allocation_strategy { /* allocate any address */ E820_ALLOCATE_ANY, /* allocate lowest address larger than address */ E820_ALLOCATE_LOWEST, /* allocate highest address lower than address */ E820_ALLOCATE_HIGHEST, /* allocate a specific address */ E820_ALLOCATE_SPECIFIC }; struct e820_entry { uint64_t base; uint64_t length; uint32_t type; } __packed; #define E820_ALIGNMENT_NONE 1 uint64_t e820_alloc(const uint64_t address, const uint64_t length, const uint64_t alignment, const enum e820_memory_type type, const enum e820_allocation_strategy strategy); void e820_dump_table(void); int e820_init(struct vmctx *const ctx); int e820_finalize(void); /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2015 Peter Grehan * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Guest firmware interface. Uses i/o ports x510/x511 as Qemu does, * but with a request/response messaging protocol. */ #include #include #include #include #include #include #include #include #include "bhyverun.h" #include "inout.h" #include "fwctl.h" /* * Messaging protocol base operations */ #define OP_NULL 1 #define OP_ECHO 2 #define OP_GET 3 #define OP_GET_LEN 4 #define OP_SET 5 #define OP_MAX OP_SET /* I/O ports */ #define FWCTL_OUT 0x510 #define FWCTL_IN 0x511 /* * Back-end state-machine */ static enum state { IDENT, REQ, RESP } be_state; static uint8_t sig[] = { 'B', 'H', 'Y', 'V' }; static u_int ident_idx; struct op_info { int op; int (*op_start)(uint32_t len); void (*op_data)(uint32_t data); int (*op_result)(struct iovec **data); void (*op_done)(struct iovec *data); }; static struct op_info *ops[OP_MAX+1]; /* Return 0-padded uint32_t */ static uint32_t fwctl_send_rest(uint8_t *data, size_t len) { union { uint8_t c[4]; uint32_t w; } u; size_t i; u.w = 0; for (i = 0; i < len; i++) u.c[i] = *data++; return (u.w); } /* * error op dummy proto - drop all data sent and return an error */ static int errop_code; static void errop_set(int err) { errop_code = err; } static int errop_start(uint32_t len __unused) { errop_code = ENOENT; /* accept any length */ return (errop_code); } static void errop_data(uint32_t data __unused) { /* ignore */ } static int errop_result(struct iovec **data) { /* no data to send back; always successful */ *data = NULL; return (errop_code); } static void errop_done(struct iovec *data __unused) { /* assert data is NULL */ } static struct op_info errop_info = { .op_start = errop_start, .op_data = errop_data, .op_result = errop_result, .op_done = errop_done }; /* OID search */ SET_DECLARE(ctl_set, struct ctl); CTL_NODE("hw.ncpu", &guest_ncpus, sizeof(guest_ncpus)); static struct ctl * ctl_locate(const char *str, int maxlen) { struct ctl *cp, **cpp; SET_FOREACH(cpp, ctl_set) { cp = *cpp; if (!strncmp(str, cp->c_oid, maxlen)) return (cp); } return (NULL); } /* uefi-sysctl get-len */ #define FGET_STRSZ 80 static struct iovec fget_biov[2]; static char fget_str[FGET_STRSZ]; static struct { size_t f_sz; uint32_t f_data[1024]; } fget_buf; static int fget_cnt; static size_t fget_size; static int fget_start(uint32_t len) { if (len > FGET_STRSZ) return(E2BIG); fget_cnt = 0; return (0); } static void fget_data(uint32_t data) { assert(fget_cnt + sizeof(uint32_t) <= sizeof(fget_str)); memcpy(&fget_str[fget_cnt], &data, sizeof(data)); fget_cnt += sizeof(uint32_t); } static int fget_result(struct iovec **data, int val) { struct ctl *cp; int err; err = 0; /* Locate the OID */ cp = ctl_locate(fget_str, fget_cnt); if (cp == NULL) { *data = NULL; err = ENOENT; } else { if (val) { /* For now, copy the len/data into a buffer */ memset(&fget_buf, 0, sizeof(fget_buf)); fget_buf.f_sz = cp->c_len; memcpy(fget_buf.f_data, cp->c_data, cp->c_len); fget_biov[0].iov_base = (char *)&fget_buf; fget_biov[0].iov_len = sizeof(fget_buf.f_sz) + cp->c_len; } else { fget_size = cp->c_len; fget_biov[0].iov_base = (char *)&fget_size; fget_biov[0].iov_len = sizeof(fget_size); } fget_biov[1].iov_base = NULL; fget_biov[1].iov_len = 0; *data = fget_biov; } return (err); } static void fget_done(struct iovec *data __unused) { /* nothing needs to be freed */ } static int fget_len_result(struct iovec **data) { return (fget_result(data, 0)); } static int fget_val_result(struct iovec **data) { return (fget_result(data, 1)); } static struct op_info fgetlen_info = { .op_start = fget_start, .op_data = fget_data, .op_result = fget_len_result, .op_done = fget_done }; static struct op_info fgetval_info = { .op_start = fget_start, .op_data = fget_data, .op_result = fget_val_result, .op_done = fget_done }; static struct req_info { int req_error; u_int req_count; uint32_t req_size; uint32_t req_type; uint32_t req_txid; struct op_info *req_op; int resp_error; int resp_count; size_t resp_size; size_t resp_off; struct iovec *resp_biov; } rinfo; static void fwctl_response_done(void) { (*rinfo.req_op->op_done)(rinfo.resp_biov); /* reinit the req data struct */ memset(&rinfo, 0, sizeof(rinfo)); } static void fwctl_request_done(void) { rinfo.resp_error = (*rinfo.req_op->op_result)(&rinfo.resp_biov); /* XXX only a single vector supported at the moment */ rinfo.resp_off = 0; if (rinfo.resp_biov == NULL) { rinfo.resp_size = 0; } else { rinfo.resp_size = rinfo.resp_biov[0].iov_len; } } static int fwctl_request_start(void) { int err; /* Data size doesn't include header */ rinfo.req_size -= 12; rinfo.req_op = &errop_info; if (rinfo.req_type <= OP_MAX && ops[rinfo.req_type] != NULL) rinfo.req_op = ops[rinfo.req_type]; err = (*rinfo.req_op->op_start)(rinfo.req_size); if (err) { errop_set(err); rinfo.req_op = &errop_info; } /* Catch case of zero-length message here */ if (rinfo.req_size == 0) { fwctl_request_done(); return (1); } return (0); } static int fwctl_request_data(uint32_t value) { /* Make sure remaining size is > 0 */ assert(rinfo.req_size > 0); if (rinfo.req_size <= sizeof(uint32_t)) rinfo.req_size = 0; else rinfo.req_size -= sizeof(uint32_t); (*rinfo.req_op->op_data)(value); if (rinfo.req_size < sizeof(uint32_t)) { fwctl_request_done(); return (1); } return (0); } static int fwctl_request(uint32_t value) { int ret; ret = 0; switch (rinfo.req_count) { case 0: /* Verify size */ if (value < 12) { printf("msg size error"); exit(4); } rinfo.req_size = value; rinfo.req_count = 1; break; case 1: rinfo.req_type = value; rinfo.req_count++; break; case 2: rinfo.req_txid = value; rinfo.req_count++; ret = fwctl_request_start(); break; default: ret = fwctl_request_data(value); break; } return (ret); } static int fwctl_response(uint32_t *retval) { uint8_t *dp; ssize_t remlen; switch(rinfo.resp_count) { case 0: /* 4 x u32 header len + data */ *retval = 4*sizeof(uint32_t) + roundup(rinfo.resp_size, sizeof(uint32_t)); rinfo.resp_count++; break; case 1: *retval = rinfo.req_type; rinfo.resp_count++; break; case 2: *retval = rinfo.req_txid; rinfo.resp_count++; break; case 3: *retval = rinfo.resp_error; rinfo.resp_count++; break; default: remlen = rinfo.resp_size - rinfo.resp_off; dp = (uint8_t *)rinfo.resp_biov->iov_base + rinfo.resp_off; if (remlen >= (ssize_t)sizeof(uint32_t)) { memcpy(retval, dp, sizeof(uint32_t)); } else if (remlen > 0) { *retval = fwctl_send_rest(dp, remlen); } rinfo.resp_off += sizeof(uint32_t); break; } if (rinfo.resp_count > 3 && rinfo.resp_off >= rinfo.resp_size) { fwctl_response_done(); return (1); } return (0); } static void fwctl_reset(void) { switch (be_state) { case RESP: /* If a response was generated but not fully read, discard it. */ fwctl_response_done(); break; case REQ: /* Discard partially-received request. */ memset(&rinfo, 0, sizeof(rinfo)); break; case IDENT: break; } be_state = IDENT; ident_idx = 0; } /* * i/o port handling. */ static uint8_t fwctl_inb(void) { uint8_t retval; retval = 0xff; switch (be_state) { case IDENT: retval = sig[ident_idx++]; if (ident_idx >= sizeof(sig)) be_state = REQ; break; default: break; } return (retval); } static void fwctl_outw(uint16_t val) { if (val == 0) { /* * The guest wants to read the signature. It's possible that the * guest is unaware of the fwctl state at this moment. For that * reason, reset the state machine unconditionally. */ fwctl_reset(); } } static uint32_t fwctl_inl(void) { uint32_t retval; switch (be_state) { case RESP: if (fwctl_response(&retval)) be_state = REQ; break; default: retval = 0xffffffff; break; } return (retval); } static void fwctl_outl(uint32_t val) { switch (be_state) { case REQ: if (fwctl_request(val)) be_state = RESP; default: break; } } static int fwctl_handler(struct vmctx *ctx __unused, int in, int port __unused, int bytes, uint32_t *eax, void *arg __unused) { if (in) { if (bytes == 1) *eax = fwctl_inb(); else if (bytes == 4) *eax = fwctl_inl(); else *eax = 0xffff; } else { if (bytes == 2) fwctl_outw(*eax); else if (bytes == 4) fwctl_outl(*eax); } return (0); } void fwctl_init(void) { struct inout_port iop; int error; bzero(&iop, sizeof(iop)); iop.name = "fwctl_wreg"; iop.port = FWCTL_OUT; iop.size = 1; iop.flags = IOPORT_F_INOUT; iop.handler = fwctl_handler; error = register_inout(&iop); assert(error == 0); bzero(&iop, sizeof(iop)); iop.name = "fwctl_rreg"; iop.port = FWCTL_IN; iop.size = 1; iop.flags = IOPORT_F_IN; iop.handler = fwctl_handler; error = register_inout(&iop); assert(error == 0); ops[OP_GET_LEN] = &fgetlen_info; ops[OP_GET] = &fgetval_info; be_state = IDENT; } /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2015 Peter Grehan * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _FWCTL_H_ #define _FWCTL_H_ #include /* * Linker set api for export of information to guest firmware via * a sysctl-like OID interface */ struct ctl { const char *c_oid; const void *c_data; const int c_len; }; #define CTL_NODE(oid, data, len) \ static struct ctl __CONCAT(__ctl, __LINE__) = { \ oid, \ (data), \ (len), \ }; \ DATA_SET(ctl_set, __CONCAT(__ctl, __LINE__)) void fwctl_init(void); #endif /* _FWCTL_H_ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 NetApp, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. * * Copyright 2020 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include "bhyverun.h" #include "config.h" #include "inout.h" SET_DECLARE(inout_port_set, struct inout_port); #define MAX_IOPORTS (1 << 16) #define VERIFY_IOPORT(port, size) \ assert((port) >= 0 && (size) > 0 && ((port) + (size)) <= MAX_IOPORTS) struct inout_handler { const char *name; int flags; inout_func_t handler; void *arg; }; static struct inout_handler inout_handlers[MAX_IOPORTS]; static int default_inout(struct vmctx *ctx __unused, int in, int port __unused, int bytes, uint32_t *eax, void *arg __unused) { if (in) { switch (bytes) { case 4: *eax = 0xffffffff; break; case 2: *eax = 0xffff; break; case 1: *eax = 0xff; break; } } return (0); } static void register_default_iohandler(int start, int size) { struct inout_port iop; VERIFY_IOPORT(start, size); bzero(&iop, sizeof(iop)); iop.name = "default"; iop.port = start; iop.size = size; iop.flags = IOPORT_F_INOUT | IOPORT_F_DEFAULT; iop.handler = default_inout; register_inout(&iop); } int emulate_inout(struct vmctx *ctx, struct vcpu *vcpu, struct vm_inout *inout) { struct inout_handler handler; inout_func_t hfunc; void *harg; int error; uint8_t bytes; bool in; bytes = inout->bytes; in = (inout->flags & INOUT_IN) != 0; assert(bytes == 1 || bytes == 2 || bytes == 4); handler = inout_handlers[inout->port]; hfunc = handler.handler; harg = handler.arg; if (hfunc == default_inout && get_config_bool_default("x86.strictio", false)) return (-1); if (in) { if (!(handler.flags & IOPORT_F_IN)) return (-1); } else { if (!(handler.flags & IOPORT_F_OUT)) return (-1); } error = hfunc(ctx, in, inout->port, bytes, &inout->eax, harg); return (error); } void init_inout(void) { struct inout_port **iopp, *iop; /* * Set up the default handler for all ports */ register_default_iohandler(0, MAX_IOPORTS); /* * Overwrite with specified handlers */ SET_FOREACH(iopp, inout_port_set) { iop = *iopp; assert(iop->port < MAX_IOPORTS); inout_handlers[iop->port].name = iop->name; inout_handlers[iop->port].flags = iop->flags; inout_handlers[iop->port].handler = iop->handler; inout_handlers[iop->port].arg = NULL; } } int register_inout(struct inout_port *iop) { int i; VERIFY_IOPORT(iop->port, iop->size); /* * Verify that the new registration is not overwriting an already * allocated i/o range. */ if ((iop->flags & IOPORT_F_DEFAULT) == 0) { for (i = iop->port; i < iop->port + iop->size; i++) { if ((inout_handlers[i].flags & IOPORT_F_DEFAULT) == 0) return (-1); } } for (i = iop->port; i < iop->port + iop->size; i++) { inout_handlers[i].name = iop->name; inout_handlers[i].flags = iop->flags; inout_handlers[i].handler = iop->handler; inout_handlers[i].arg = iop->arg; } return (0); } int unregister_inout(struct inout_port *iop) { VERIFY_IOPORT(iop->port, iop->size); assert(inout_handlers[iop->port].name == iop->name); register_default_iohandler(iop->port, iop->size); return (0); } /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 NetApp, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. * * Copyright 2014 Pluribus Networks Inc. */ #ifndef _INOUT_H_ #define _INOUT_H_ #include struct vcpu; struct vmctx; struct vm_exit; #ifndef __FreeBSD__ struct vm_inout; #endif /* * inout emulation handlers return 0 on success and -1 on failure. */ typedef int (*inout_func_t)(struct vmctx *ctx, int in, int port, int bytes, uint32_t *eax, void *arg); struct inout_port { const char *name; int port; int size; int flags; inout_func_t handler; void *arg; }; #define IOPORT_F_IN 0x1 #define IOPORT_F_OUT 0x2 #define IOPORT_F_INOUT (IOPORT_F_IN | IOPORT_F_OUT) /* * The following flags are used internally and must not be used by * device models. */ #define IOPORT_F_DEFAULT 0x80000000 /* claimed by default handler */ #define INOUT_PORT(name, port, flags, handler) \ static struct inout_port __CONCAT(__inout_port, __LINE__) = { \ #name, \ (port), \ 1, \ (flags), \ (handler), \ 0 \ }; \ DATA_SET(inout_port_set, __CONCAT(__inout_port, __LINE__)) void init_inout(void); #ifdef __FreeBSD__ int emulate_inout(struct vmctx *, struct vcpu *vcpu, struct vm_exit *vmexit); #else int emulate_inout(struct vmctx *, struct vcpu *vcpu, struct vm_inout *inout); #endif int register_inout(struct inout_port *iop); int unregister_inout(struct inout_port *iop); #endif /* _INOUT_H_ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2014 Hudson River Trading LLC * Written by: John H. Baldwin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include "bootrom.h" #include "ioapic.h" #include "pci_emul.h" #include "pci_lpc.h" /* * Assign PCI INTx interrupts to I/O APIC pins in a round-robin * fashion. Note that we have no idea what the HPET is using, but the * HPET is also programmable whereas this is intended for hardwired * PCI interrupts. * * This assumes a single I/O APIC where pins >= 16 are permitted for * PCI devices. */ static int pci_pins; void ioapic_init(struct vmctx *ctx) { if (vm_ioapic_pincount(ctx, &pci_pins) < 0) { pci_pins = 0; return; } /* Ignore the first 16 pins. */ if (pci_pins <= 16) { pci_pins = 0; return; } pci_pins -= 16; } int ioapic_pci_alloc_irq(struct pci_devinst *pi) { static int last_pin; if (pci_pins == 0) return (-1); if (bootrom_boot()) { /* For external bootrom use fixed mapping. */ return (16 + (4 + pi->pi_slot + pi->pi_lintr.pin) % 8); } return (16 + (last_pin++ % pci_pins)); } /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2014 Hudson River Trading LLC * Written by: John H. Baldwin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _IOAPIC_H_ #define _IOAPIC_H_ struct pci_devinst; /* * Allocate a PCI IRQ from the I/O APIC. */ void ioapic_init(struct vmctx *ctx); int ioapic_pci_alloc_irq(struct pci_devinst *pi); #endif /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright 2020 Conrad Meyer . All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include struct vm; struct vm_hpet_cap; #include #include #include #include #include #include "kernemu_dev.h" #include "mem.h" static int apic_handler(struct vcpu *vcpu, int dir, uint64_t addr, int size, uint64_t *val, void *arg1 __unused, long arg2 __unused) { if (vm_readwrite_kernemu_device(vcpu, addr, (dir == MEM_F_WRITE), size, val) != 0) return (errno); return (0); } static struct mem_range lapic_mmio = { .name = "kern-lapic-mmio", .base = DEFAULT_APIC_BASE, .size = PAGE_SIZE, .flags = MEM_F_RW | MEM_F_IMMUTABLE, .handler = apic_handler, }; static struct mem_range ioapic_mmio = { .name = "kern-ioapic-mmio", .base = VIOAPIC_BASE, .size = VIOAPIC_SIZE, .flags = MEM_F_RW | MEM_F_IMMUTABLE, .handler = apic_handler, }; static struct mem_range hpet_mmio = { .name = "kern-hpet-mmio", .base = VHPET_BASE, .size = VHPET_SIZE, .flags = MEM_F_RW | MEM_F_IMMUTABLE, .handler = apic_handler, }; void kernemu_dev_init(void) { int rc; rc = register_mem(&lapic_mmio); if (rc != 0) errc(4, rc, "register_mem: LAPIC (0x%08x)", (unsigned)lapic_mmio.base); rc = register_mem(&ioapic_mmio); if (rc != 0) errc(4, rc, "register_mem: IOAPIC (0x%08x)", (unsigned)ioapic_mmio.base); rc = register_mem(&hpet_mmio); if (rc != 0) errc(4, rc, "register_mem: HPET (0x%08x)", (unsigned)hpet_mmio.base); } /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright 2020 Conrad Meyer . All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #pragma once void kernemu_dev_init(void); /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012 NetApp, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include "acpi.h" #include "debug.h" #include "bhyverun.h" #include "mptbl.h" #include "pci_emul.h" #define MPTABLE_BASE 0xE0000 /* floating pointer length + maximum length of configuration table */ #define MPTABLE_MAX_LENGTH (65536 + 16) #define LAPIC_PADDR 0xFEE00000 #define LAPIC_VERSION 16 #define IOAPIC_PADDR 0xFEC00000 #define IOAPIC_VERSION 0x11 #define MP_SPECREV 4 #define MPFP_SIG "_MP_" /* Configuration header defines */ #define MPCH_SIG "PCMP" #define MPCH_OEMID "BHyVe " #define MPCH_OEMID_LEN 8 #define MPCH_PRODID "Hypervisor " #define MPCH_PRODID_LEN 12 /* Processor entry defines */ #define MPEP_SIG_FAMILY 6 /* XXX bhyve should supply this */ #define MPEP_SIG_MODEL 26 #define MPEP_SIG_STEPPING 5 #define MPEP_SIG \ ((MPEP_SIG_FAMILY << 8) | \ (MPEP_SIG_MODEL << 4) | \ (MPEP_SIG_STEPPING)) #define MPEP_FEATURES (0xBFEBFBFF) /* XXX Intel i7 */ /* Number of local intr entries */ #define MPEII_NUM_LOCAL_IRQ 2 /* Bus entry defines */ #define MPE_NUM_BUSES 2 #define MPE_BUSNAME_LEN 6 #define MPE_BUSNAME_ISA "ISA " #define MPE_BUSNAME_PCI "PCI " static void *oem_tbl_start; static int oem_tbl_size; static uint8_t mpt_compute_checksum(void *base, size_t len) { uint8_t *bytes; uint8_t sum; for(bytes = base, sum = 0; len > 0; len--) { sum += *bytes++; } return (256 - sum); } static void mpt_build_mpfp(mpfps_t mpfp, vm_paddr_t gpa) { memset(mpfp, 0, sizeof(*mpfp)); memcpy(mpfp->signature, MPFP_SIG, 4); mpfp->pap = gpa + sizeof(*mpfp); mpfp->length = 1; mpfp->spec_rev = MP_SPECREV; mpfp->checksum = mpt_compute_checksum(mpfp, sizeof(*mpfp)); } static void mpt_build_mpch(mpcth_t mpch) { memset(mpch, 0, sizeof(*mpch)); memcpy(mpch->signature, MPCH_SIG, 4); mpch->spec_rev = MP_SPECREV; memcpy(mpch->oem_id, MPCH_OEMID, MPCH_OEMID_LEN); memcpy(mpch->product_id, MPCH_PRODID, MPCH_PRODID_LEN); mpch->apic_address = LAPIC_PADDR; } static void mpt_build_proc_entries(proc_entry_ptr mpep, int ncpu) { int i; for (i = 0; i < ncpu; i++) { memset(mpep, 0, sizeof(*mpep)); mpep->type = MPCT_ENTRY_PROCESSOR; mpep->apic_id = i; // XXX mpep->apic_version = LAPIC_VERSION; mpep->cpu_flags = PROCENTRY_FLAG_EN; if (i == 0) mpep->cpu_flags |= PROCENTRY_FLAG_BP; mpep->cpu_signature = MPEP_SIG; mpep->feature_flags = MPEP_FEATURES; mpep++; } } static void mpt_build_localint_entries(int_entry_ptr mpie) { /* Hardcode LINT0 as ExtINT on all CPUs. */ memset(mpie, 0, sizeof(*mpie)); mpie->type = MPCT_ENTRY_LOCAL_INT; mpie->int_type = INTENTRY_TYPE_EXTINT; mpie->int_flags = INTENTRY_FLAGS_POLARITY_CONFORM | INTENTRY_FLAGS_TRIGGER_CONFORM; mpie->dst_apic_id = 0xff; mpie->dst_apic_int = 0; mpie++; /* Hardcode LINT1 as NMI on all CPUs. */ memset(mpie, 0, sizeof(*mpie)); mpie->type = MPCT_ENTRY_LOCAL_INT; mpie->int_type = INTENTRY_TYPE_NMI; mpie->int_flags = INTENTRY_FLAGS_POLARITY_CONFORM | INTENTRY_FLAGS_TRIGGER_CONFORM; mpie->dst_apic_id = 0xff; mpie->dst_apic_int = 1; } static void mpt_build_bus_entries(bus_entry_ptr mpeb) { memset(mpeb, 0, sizeof(*mpeb)); mpeb->type = MPCT_ENTRY_BUS; mpeb->bus_id = 0; memcpy(mpeb->bus_type, MPE_BUSNAME_PCI, MPE_BUSNAME_LEN); mpeb++; memset(mpeb, 0, sizeof(*mpeb)); mpeb->type = MPCT_ENTRY_BUS; mpeb->bus_id = 1; memcpy(mpeb->bus_type, MPE_BUSNAME_ISA, MPE_BUSNAME_LEN); } static void mpt_build_ioapic_entries(io_apic_entry_ptr mpei, int id) { memset(mpei, 0, sizeof(*mpei)); mpei->type = MPCT_ENTRY_IOAPIC; mpei->apic_id = id; mpei->apic_version = IOAPIC_VERSION; mpei->apic_flags = IOAPICENTRY_FLAG_EN; mpei->apic_address = IOAPIC_PADDR; } static int mpt_count_ioint_entries(void) { int bus, count; count = 0; for (bus = 0; bus <= PCI_BUSMAX; bus++) count += pci_count_lintr(bus); /* * Always include entries for the first 16 pins along with a entry * for each active PCI INTx pin. */ return (16 + count); } static void mpt_generate_pci_int(int bus, int slot, int pin, int pirq_pin __unused, int ioapic_irq, void *arg) { int_entry_ptr *mpiep, mpie; mpiep = arg; mpie = *mpiep; memset(mpie, 0, sizeof(*mpie)); /* * This is always after another I/O interrupt entry, so cheat * and fetch the I/O APIC ID from the prior entry. */ mpie->type = MPCT_ENTRY_INT; mpie->int_type = INTENTRY_TYPE_INT; mpie->src_bus_id = bus; mpie->src_bus_irq = slot << 2 | (pin - 1); mpie->dst_apic_id = mpie[-1].dst_apic_id; mpie->dst_apic_int = ioapic_irq; *mpiep = mpie + 1; } static void mpt_build_ioint_entries(int_entry_ptr mpie, int id) { int pin, bus; /* * The following config is taken from kernel mptable.c * mptable_parse_default_config_ints(...), for now * just use the default config, tweek later if needed. */ /* First, generate the first 16 pins. */ for (pin = 0; pin < 16; pin++) { memset(mpie, 0, sizeof(*mpie)); mpie->type = MPCT_ENTRY_INT; mpie->src_bus_id = 1; mpie->dst_apic_id = id; /* * All default configs route IRQs from bus 0 to the first 16 * pins of the first I/O APIC with an APIC ID of 2. */ mpie->dst_apic_int = pin; switch (pin) { case 0: /* Pin 0 is an ExtINT pin. */ mpie->int_type = INTENTRY_TYPE_EXTINT; break; case 2: /* IRQ 0 is routed to pin 2. */ mpie->int_type = INTENTRY_TYPE_INT; mpie->src_bus_irq = 0; break; case SCI_INT: /* ACPI SCI is level triggered and active-lo. */ mpie->int_flags = INTENTRY_FLAGS_POLARITY_ACTIVELO | INTENTRY_FLAGS_TRIGGER_LEVEL; mpie->int_type = INTENTRY_TYPE_INT; mpie->src_bus_irq = SCI_INT; break; default: /* All other pins are identity mapped. */ mpie->int_type = INTENTRY_TYPE_INT; mpie->src_bus_irq = pin; break; } mpie++; } /* Next, generate entries for any PCI INTx interrupts. */ for (bus = 0; bus <= PCI_BUSMAX; bus++) pci_walk_lintr(bus, mpt_generate_pci_int, &mpie); } void mptable_add_oemtbl(void *tbl, int tblsz) { oem_tbl_start = tbl; oem_tbl_size = tblsz; } int mptable_build(struct vmctx *ctx, int ncpu) { mpcth_t mpch; bus_entry_ptr mpeb; io_apic_entry_ptr mpei; proc_entry_ptr mpep; mpfps_t mpfp; int_entry_ptr mpie; int ioints, bus; char *curraddr; char *startaddr; startaddr = paddr_guest2host(ctx, MPTABLE_BASE, MPTABLE_MAX_LENGTH); if (startaddr == NULL) { EPRINTLN("mptable requires mapped mem"); return (ENOMEM); } /* * There is no way to advertise multiple PCI hierarchies via MPtable * so require that there is no PCI hierarchy with a non-zero bus * number. */ for (bus = 1; bus <= PCI_BUSMAX; bus++) { if (pci_bus_configured(bus)) { EPRINTLN("MPtable is incompatible with " "multiple PCI hierarchies."); EPRINTLN("MPtable generation can be disabled " "by passing the -Y option to bhyve(8)."); return (EINVAL); } } curraddr = startaddr; mpfp = (mpfps_t)curraddr; mpt_build_mpfp(mpfp, MPTABLE_BASE); curraddr += sizeof(*mpfp); mpch = (mpcth_t)curraddr; mpt_build_mpch(mpch); curraddr += sizeof(*mpch); mpep = (proc_entry_ptr)curraddr; mpt_build_proc_entries(mpep, ncpu); curraddr += sizeof(*mpep) * ncpu; mpch->entry_count += ncpu; mpeb = (bus_entry_ptr) curraddr; mpt_build_bus_entries(mpeb); curraddr += sizeof(*mpeb) * MPE_NUM_BUSES; mpch->entry_count += MPE_NUM_BUSES; mpei = (io_apic_entry_ptr)curraddr; mpt_build_ioapic_entries(mpei, 0); curraddr += sizeof(*mpei); mpch->entry_count++; mpie = (int_entry_ptr) curraddr; ioints = mpt_count_ioint_entries(); mpt_build_ioint_entries(mpie, 0); curraddr += sizeof(*mpie) * ioints; mpch->entry_count += ioints; mpie = (int_entry_ptr)curraddr; mpt_build_localint_entries(mpie); curraddr += sizeof(*mpie) * MPEII_NUM_LOCAL_IRQ; mpch->entry_count += MPEII_NUM_LOCAL_IRQ; if (oem_tbl_start) { mpch->oem_table_pointer = curraddr - startaddr + MPTABLE_BASE; mpch->oem_table_size = oem_tbl_size; memcpy(curraddr, oem_tbl_start, oem_tbl_size); } mpch->base_table_length = curraddr - (char *)mpch; mpch->checksum = mpt_compute_checksum(mpch, mpch->base_table_length); return (0); } /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012 NetApp, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _MPTBL_H_ #define _MPTBL_H_ int mptable_build(struct vmctx *ctx, int ncpu); void mptable_add_oemtbl(void *tbl, int tblsz); #endif /* _MPTBL_H_ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2013 Neel Natu * Copyright (c) 2013 Tycho Nightingale * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright 2018 Joyent, Inc. */ #include #include #include #include #include #include #include #include "acpi.h" #include "debug.h" #include "bootrom.h" #include "config.h" #include "inout.h" #include "pci_emul.h" #include "pci_irq.h" #include "pci_lpc.h" #include "pci_passthru.h" #include "pctestdev.h" #include "tpm_device.h" #include "uart_emul.h" #define IO_ICU1 0x20 #define IO_ICU2 0xA0 SET_DECLARE(lpc_dsdt_set, struct lpc_dsdt); SET_DECLARE(lpc_sysres_set, struct lpc_sysres); #define ELCR_PORT 0x4d0 SYSRES_IO(ELCR_PORT, 2); #define IO_TIMER1_PORT 0x40 #define NMISC_PORT 0x61 SYSRES_IO(NMISC_PORT, 1); static struct pci_devinst *lpc_bridge; #define LPC_UART_NUM 4 static struct lpc_uart_softc { struct uart_ns16550_softc *uart_softc; int iobase; int irq; int enabled; } lpc_uart_softc[LPC_UART_NUM]; static const char *lpc_uart_names[LPC_UART_NUM] = { "com1", "com2", "com3", "com4" }; static const char *lpc_uart_acpi_names[LPC_UART_NUM] = { "COM1", "COM2", "COM3", "COM4" }; /* * LPC device configuration is in the following form: * [,] * For e.g. "com1,stdio" or "bootrom,/var/romfile" */ int lpc_device_parse(const char *opts) { int unit, error; char *str, *cpy, *lpcdev, *node_name; const char *romfile, *varfile, *tpm_type, *tpm_path; error = -1; str = cpy = strdup(opts); lpcdev = strsep(&str, ","); if (lpcdev != NULL) { if (strcasecmp(lpcdev, "bootrom") == 0) { romfile = strsep(&str, ","); if (romfile == NULL) { errx(4, "invalid bootrom option \"%s\"", opts); } set_config_value("bootrom", romfile); varfile = strsep(&str, ","); if (varfile == NULL) { error = 0; goto done; } if (strchr(varfile, '=') == NULL) { set_config_value("bootvars", varfile); } else { /* varfile doesn't exist, it's another config * option */ pci_parse_legacy_config(find_config_node("lpc"), varfile); } pci_parse_legacy_config(find_config_node("lpc"), str); error = 0; goto done; } if (strcasecmp(lpcdev, "tpm") == 0) { nvlist_t *nvl = create_config_node("tpm"); tpm_type = strsep(&str, ","); if (tpm_type == NULL) { errx(4, "invalid tpm type \"%s\"", opts); } set_config_value_node(nvl, "type", tpm_type); tpm_path = strsep(&str, ","); if (tpm_path == NULL) { errx(4, "invalid tpm path \"%s\"", opts); } set_config_value_node(nvl, "path", tpm_path); pci_parse_legacy_config(find_config_node("tpm"), str); set_config_value_node_if_unset(nvl, "version", "2.0"); error = 0; goto done; } for (unit = 0; unit < LPC_UART_NUM; unit++) { if (strcasecmp(lpcdev, lpc_uart_names[unit]) == 0) { asprintf(&node_name, "lpc.%s.path", lpc_uart_names[unit]); set_config_value(node_name, str); free(node_name); error = 0; goto done; } } if (strcasecmp(lpcdev, pctestdev_getname()) == 0) { asprintf(&node_name, "lpc.%s", pctestdev_getname()); set_config_bool(node_name, true); free(node_name); error = 0; goto done; } } done: free(cpy); return (error); } void lpc_print_supported_devices(void) { size_t i; printf("bootrom\n"); for (i = 0; i < LPC_UART_NUM; i++) printf("%s\n", lpc_uart_names[i]); printf("tpm\n"); printf("%s\n", pctestdev_getname()); } const char * lpc_fwcfg(void) { return (get_config_value("lpc.fwcfg")); } static void lpc_uart_intr_assert(void *arg) { struct lpc_uart_softc *sc = arg; assert(sc->irq >= 0); vm_isa_pulse_irq(lpc_bridge->pi_vmctx, sc->irq, sc->irq); } static void lpc_uart_intr_deassert(void *arg __unused) { /* * The COM devices on the LPC bus generate edge triggered interrupts, * so nothing more to do here. */ } static int lpc_uart_io_handler(struct vmctx *ctx __unused, int in, int port, int bytes, uint32_t *eax, void *arg) { int offset; struct lpc_uart_softc *sc = arg; offset = port - sc->iobase; switch (bytes) { case 1: if (in) *eax = uart_ns16550_read(sc->uart_softc, offset); else uart_ns16550_write(sc->uart_softc, offset, *eax); break; case 2: if (in) { *eax = uart_ns16550_read(sc->uart_softc, offset); *eax |= uart_ns16550_read(sc->uart_softc, offset + 1) << 8; } else { uart_ns16550_write(sc->uart_softc, offset, *eax); uart_ns16550_write(sc->uart_softc, offset + 1, *eax >> 8); } break; #ifndef __FreeBSD__ case 4: if (in) { *eax = uart_ns16550_read(sc->uart_softc, offset); *eax |= uart_ns16550_read(sc->uart_softc, offset + 1) << 8; *eax |= uart_ns16550_read(sc->uart_softc, offset + 2) << 16; *eax |= uart_ns16550_read(sc->uart_softc, offset + 3) << 24; } else { uart_ns16550_write(sc->uart_softc, offset, *eax); uart_ns16550_write(sc->uart_softc, offset + 1, *eax >> 8); uart_ns16550_write(sc->uart_softc, offset + 2, *eax >> 16); uart_ns16550_write(sc->uart_softc, offset + 3, *eax >> 24); } break; #endif default: return (-1); } return (0); } static int lpc_init(struct vmctx *ctx) { struct lpc_uart_softc *sc; struct inout_port iop; const char *backend, *name; char *node_name; int unit, error; /* COM1 and COM2 */ for (unit = 0; unit < LPC_UART_NUM; unit++) { sc = &lpc_uart_softc[unit]; name = lpc_uart_names[unit]; if (uart_legacy_alloc(unit, &sc->iobase, &sc->irq) != 0) { EPRINTLN("Unable to allocate resources for " "LPC device %s", name); return (-1); } pci_irq_reserve(sc->irq); sc->uart_softc = uart_ns16550_init(lpc_uart_intr_assert, lpc_uart_intr_deassert, sc); asprintf(&node_name, "lpc.%s.path", name); backend = get_config_value(node_name); free(node_name); if (backend != NULL && uart_ns16550_tty_open(sc->uart_softc, backend) != 0) { EPRINTLN("Unable to initialize backend '%s' " "for LPC device %s", backend, name); return (-1); } bzero(&iop, sizeof(struct inout_port)); iop.name = name; iop.port = sc->iobase; iop.size = UART_NS16550_IO_BAR_SIZE; iop.flags = IOPORT_F_INOUT; iop.handler = lpc_uart_io_handler; iop.arg = sc; error = register_inout(&iop); assert(error == 0); sc->enabled = 1; } /* pc-testdev */ asprintf(&node_name, "lpc.%s", pctestdev_getname()); if (get_config_bool_default(node_name, false)) { error = pctestdev_init(ctx); if (error) return (error); } free(node_name); return (0); } static void pci_lpc_write_dsdt(struct pci_devinst *pi) { struct lpc_dsdt **ldpp, *ldp; dsdt_line(""); dsdt_line("Device (ISA)"); dsdt_line("{"); dsdt_line(" Name (_ADR, 0x%04X%04X)", pi->pi_slot, pi->pi_func); dsdt_line(" OperationRegion (LPCR, PCI_Config, 0x00, 0x100)"); dsdt_line(" Field (LPCR, AnyAcc, NoLock, Preserve)"); dsdt_line(" {"); dsdt_line(" Offset (0x60),"); dsdt_line(" PIRA, 8,"); dsdt_line(" PIRB, 8,"); dsdt_line(" PIRC, 8,"); dsdt_line(" PIRD, 8,"); dsdt_line(" Offset (0x68),"); dsdt_line(" PIRE, 8,"); dsdt_line(" PIRF, 8,"); dsdt_line(" PIRG, 8,"); dsdt_line(" PIRH, 8"); dsdt_line(" }"); dsdt_line(""); dsdt_indent(1); SET_FOREACH(ldpp, lpc_dsdt_set) { ldp = *ldpp; ldp->handler(); } dsdt_line(""); dsdt_line("Device (PIC)"); dsdt_line("{"); dsdt_line(" Name (_HID, EisaId (\"PNP0000\"))"); dsdt_line(" Name (_CRS, ResourceTemplate ()"); dsdt_line(" {"); dsdt_indent(2); dsdt_fixed_ioport(IO_ICU1, 2); dsdt_fixed_ioport(IO_ICU2, 2); dsdt_fixed_irq(2); dsdt_unindent(2); dsdt_line(" })"); dsdt_line("}"); dsdt_line(""); dsdt_line("Device (TIMR)"); dsdt_line("{"); dsdt_line(" Name (_HID, EisaId (\"PNP0100\"))"); dsdt_line(" Name (_CRS, ResourceTemplate ()"); dsdt_line(" {"); dsdt_indent(2); dsdt_fixed_ioport(IO_TIMER1_PORT, 4); dsdt_fixed_irq(0); dsdt_unindent(2); dsdt_line(" })"); dsdt_line("}"); dsdt_unindent(1); dsdt_line("}"); } static void pci_lpc_sysres_dsdt(void) { struct lpc_sysres **lspp, *lsp; dsdt_line(""); dsdt_line("Device (SIO)"); dsdt_line("{"); dsdt_line(" Name (_HID, EisaId (\"PNP0C02\"))"); dsdt_line(" Name (_CRS, ResourceTemplate ()"); dsdt_line(" {"); dsdt_indent(2); SET_FOREACH(lspp, lpc_sysres_set) { lsp = *lspp; switch (lsp->type) { case LPC_SYSRES_IO: dsdt_fixed_ioport(lsp->base, lsp->length); break; case LPC_SYSRES_MEM: dsdt_fixed_mem32(lsp->base, lsp->length); break; } } dsdt_unindent(2); dsdt_line(" })"); dsdt_line("}"); } LPC_DSDT(pci_lpc_sysres_dsdt); static void pci_lpc_uart_dsdt(void) { struct lpc_uart_softc *sc; int unit; for (unit = 0; unit < LPC_UART_NUM; unit++) { sc = &lpc_uart_softc[unit]; if (!sc->enabled) continue; dsdt_line(""); dsdt_line("Device (%s)", lpc_uart_acpi_names[unit]); dsdt_line("{"); dsdt_line(" Name (_HID, EisaId (\"PNP0501\"))"); dsdt_line(" Name (_UID, %d)", unit + 1); dsdt_line(" Name (_CRS, ResourceTemplate ()"); dsdt_line(" {"); dsdt_indent(2); dsdt_fixed_ioport(sc->iobase, UART_NS16550_IO_BAR_SIZE); dsdt_fixed_irq(sc->irq); dsdt_unindent(2); dsdt_line(" })"); dsdt_line("}"); } } LPC_DSDT(pci_lpc_uart_dsdt); static int pci_lpc_cfgwrite(struct pci_devinst *pi, int coff, int bytes, uint32_t val) { int pirq_pin; if (bytes == 1) { pirq_pin = 0; if (coff >= 0x60 && coff <= 0x63) pirq_pin = coff - 0x60 + 1; if (coff >= 0x68 && coff <= 0x6b) pirq_pin = coff - 0x68 + 5; if (pirq_pin != 0) { pirq_write(pi->pi_vmctx, pirq_pin, val); pci_set_cfgdata8(pi, coff, pirq_read(pirq_pin)); return (0); } } return (-1); } static void pci_lpc_write(struct pci_devinst *pi __unused, int baridx __unused, uint64_t offset __unused, int size __unused, uint64_t value __unused) { } static uint64_t pci_lpc_read(struct pci_devinst *pi __unused, int baridx __unused, uint64_t offset __unused, int size __unused) { return (0); } #define LPC_DEV 0x7000 #define LPC_VENDOR 0x8086 #define LPC_REVID 0x00 #define LPC_SUBVEND_0 0x0000 #define LPC_SUBDEV_0 0x0000 #ifdef __FreeBSD__ static int pci_lpc_get_sel(struct pcisel *const sel) { assert(sel != NULL); memset(sel, 0, sizeof(*sel)); for (uint8_t slot = 0; slot <= PCI_SLOTMAX; ++slot) { uint8_t max_func = 0; sel->pc_dev = slot; sel->pc_func = 0; if (pci_host_read_config(sel, PCIR_HDRTYPE, 1) & PCIM_MFDEV) max_func = PCI_FUNCMAX; for (uint8_t func = 0; func <= max_func; ++func) { sel->pc_func = func; if (pci_host_read_config(sel, PCIR_CLASS, 1) == PCIC_BRIDGE && pci_host_read_config(sel, PCIR_SUBCLASS, 1) == PCIS_BRIDGE_ISA) { return (0); } } } warnx("%s: Unable to find host selector of LPC bridge.", __func__); return (-1); } #else /* * This function is used to find the PCI selector for the host's LPC so that * its various IDs can be used to configure the guest LPC with the same values * when the `host` keyword is used in the configuration. * On illumos we always just report that we cannot find the host LPC. This is * likely to be true in the case that we're running in a zone anyway. */ static int pci_lpc_get_sel(struct pcisel *const sel __unused) { return (-1); } #endif static int pci_lpc_init(struct pci_devinst *pi, nvlist_t *nvl) { struct pcisel sel = { 0 }; struct pcisel *selp = NULL; uint16_t device, subdevice, subvendor, vendor; uint8_t revid; /* * Do not allow more than one LPC bridge to be configured. */ if (lpc_bridge != NULL) { EPRINTLN("Only one LPC bridge is allowed."); return (-1); } /* * Enforce that the LPC can only be configured on bus 0. This * simplifies the ACPI DSDT because it can provide a decode for * all legacy i/o ports behind bus 0. */ if (pi->pi_bus != 0) { EPRINTLN("LPC bridge can be present only on bus 0."); return (-1); } if (lpc_init(pi->pi_vmctx) != 0) return (-1); if (pci_lpc_get_sel(&sel) == 0) selp = &sel; vendor = pci_config_read_reg(selp, nvl, PCIR_VENDOR, 2, LPC_VENDOR); device = pci_config_read_reg(selp, nvl, PCIR_DEVICE, 2, LPC_DEV); revid = pci_config_read_reg(selp, nvl, PCIR_REVID, 1, LPC_REVID); subvendor = pci_config_read_reg(selp, nvl, PCIR_SUBVEND_0, 2, LPC_SUBVEND_0); subdevice = pci_config_read_reg(selp, nvl, PCIR_SUBDEV_0, 2, LPC_SUBDEV_0); /* initialize config space */ pci_set_cfgdata16(pi, PCIR_VENDOR, vendor); pci_set_cfgdata16(pi, PCIR_DEVICE, device); pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_BRIDGE); pci_set_cfgdata8(pi, PCIR_SUBCLASS, PCIS_BRIDGE_ISA); pci_set_cfgdata8(pi, PCIR_REVID, revid); pci_set_cfgdata16(pi, PCIR_SUBVEND_0, subvendor); pci_set_cfgdata16(pi, PCIR_SUBDEV_0, subdevice); lpc_bridge = pi; return (0); } char * lpc_pirq_name(int pin) { char *name; if (lpc_bridge == NULL) return (NULL); asprintf(&name, "\\_SB.PC00.ISA.LNK%c,", 'A' + pin - 1); return (name); } void lpc_pirq_routed(void) { int pin; if (lpc_bridge == NULL) return; for (pin = 0; pin < 4; pin++) pci_set_cfgdata8(lpc_bridge, 0x60 + pin, pirq_read(pin + 1)); for (pin = 0; pin < 4; pin++) pci_set_cfgdata8(lpc_bridge, 0x68 + pin, pirq_read(pin + 5)); } static const struct pci_devemu pci_de_lpc = { .pe_emu = "lpc", .pe_init = pci_lpc_init, .pe_write_dsdt = pci_lpc_write_dsdt, .pe_cfgwrite = pci_lpc_cfgwrite, .pe_barwrite = pci_lpc_write, .pe_barread = pci_lpc_read }; PCI_EMUL_SET(pci_de_lpc); /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2013 Neel Natu * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _LPC_H_ #define _LPC_H_ #include typedef void (*lpc_write_dsdt_t)(void); struct lpc_dsdt { lpc_write_dsdt_t handler; }; #define LPC_DSDT(handler) \ static struct lpc_dsdt __CONCAT(__lpc_dsdt, __LINE__) = { \ (handler), \ }; \ DATA_SET(lpc_dsdt_set, __CONCAT(__lpc_dsdt, __LINE__)) enum lpc_sysres_type { LPC_SYSRES_IO, LPC_SYSRES_MEM }; struct lpc_sysres { enum lpc_sysres_type type; uint32_t base; uint32_t length; }; #define LPC_SYSRES(type, base, length) \ static struct lpc_sysres __CONCAT(__lpc_sysres, __LINE__) = { \ (type), \ (base), \ (length) \ }; \ DATA_SET(lpc_sysres_set, __CONCAT(__lpc_sysres, __LINE__)) #define SYSRES_IO(base, length) LPC_SYSRES(LPC_SYSRES_IO, base, length) #define SYSRES_MEM(base, length) LPC_SYSRES(LPC_SYSRES_MEM, base, length) int lpc_device_parse(const char *opt); void lpc_print_supported_devices(void); char *lpc_pirq_name(int pin); void lpc_pirq_routed(void); const char *lpc_fwcfg(void); #endif /* _LPC_H_ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2013 Hudson River Trading LLC * Written by: John H. Baldwin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright 2018 Joyent, Inc. * Copyright 2020 Oxide Computer Company */ #include #include #include #include #include #ifndef __FreeBSD__ #include #endif #include #include #include "acpi.h" #include "inout.h" #ifdef __FreeBSD__ #include "mevent.h" #endif #include "pci_irq.h" #include "pci_lpc.h" static pthread_mutex_t pm_lock = PTHREAD_MUTEX_INITIALIZER; #ifdef __FreeBSD__ static struct mevent *power_button; static sig_t old_power_handler; #else struct vmctx *pwr_ctx; #endif static unsigned gpe0_active; static unsigned gpe0_enabled; static const unsigned gpe0_valid = (1u << GPE_VMGENC); /* * Reset Control register at I/O port 0xcf9. Bit 2 forces a system * reset when it transitions from 0 to 1. Bit 1 selects the type of * reset to attempt: 0 selects a "soft" reset, and 1 selects a "hard" * reset. */ static int reset_handler(struct vmctx *ctx __unused, int in, int port __unused, int bytes, uint32_t *eax, void *arg __unused) { int error; static uint8_t reset_control; if (bytes != 1) return (-1); if (in) *eax = reset_control; else { reset_control = *eax; /* Treat hard and soft resets the same. */ if (reset_control & 0x4) { error = vm_suspend(ctx, VM_SUSPEND_RESET); assert(error == 0 || errno == EALREADY); } } return (0); } INOUT_PORT(reset_reg, 0xCF9, IOPORT_F_INOUT, reset_handler); /* * ACPI's SCI is a level-triggered interrupt. */ static int sci_active; static void sci_assert(struct vmctx *ctx) { if (sci_active) return; vm_isa_assert_irq(ctx, SCI_INT, SCI_INT); sci_active = 1; } static void sci_deassert(struct vmctx *ctx) { if (!sci_active) return; vm_isa_deassert_irq(ctx, SCI_INT, SCI_INT); sci_active = 0; } /* * Power Management 1 Event Registers * * The only power management event supported is a power button upon * receiving SIGTERM. */ static uint16_t pm1_enable, pm1_status; #define PM1_TMR_STS 0x0001 #define PM1_BM_STS 0x0010 #define PM1_GBL_STS 0x0020 #define PM1_PWRBTN_STS 0x0100 #define PM1_SLPBTN_STS 0x0200 #define PM1_RTC_STS 0x0400 #define PM1_WAK_STS 0x8000 #define PM1_TMR_EN 0x0001 #define PM1_GBL_EN 0x0020 #define PM1_PWRBTN_EN 0x0100 #define PM1_SLPBTN_EN 0x0200 #define PM1_RTC_EN 0x0400 static void sci_update(struct vmctx *ctx) { int need_sci; /* See if the SCI should be active or not. */ need_sci = 0; if ((pm1_enable & PM1_TMR_EN) && (pm1_status & PM1_TMR_STS)) need_sci = 1; if ((pm1_enable & PM1_GBL_EN) && (pm1_status & PM1_GBL_STS)) need_sci = 1; if ((pm1_enable & PM1_PWRBTN_EN) && (pm1_status & PM1_PWRBTN_STS)) need_sci = 1; if ((pm1_enable & PM1_SLPBTN_EN) && (pm1_status & PM1_SLPBTN_STS)) need_sci = 1; if ((pm1_enable & PM1_RTC_EN) && (pm1_status & PM1_RTC_STS)) need_sci = 1; if ((gpe0_enabled & gpe0_active) != 0) need_sci = 1; if (need_sci) sci_assert(ctx); else sci_deassert(ctx); } static int pm1_status_handler(struct vmctx *ctx, int in, int port __unused, int bytes, uint32_t *eax, void *arg __unused) { if (bytes != 2) return (-1); pthread_mutex_lock(&pm_lock); if (in) *eax = pm1_status; else { /* * Writes are only permitted to clear certain bits by * writing 1 to those flags. */ pm1_status &= ~(*eax & (PM1_WAK_STS | PM1_RTC_STS | PM1_SLPBTN_STS | PM1_PWRBTN_STS | PM1_BM_STS)); sci_update(ctx); } pthread_mutex_unlock(&pm_lock); return (0); } static int pm1_enable_handler(struct vmctx *ctx, int in, int port __unused, int bytes, uint32_t *eax, void *arg __unused) { if (bytes != 2) return (-1); pthread_mutex_lock(&pm_lock); if (in) *eax = pm1_enable; else { /* * Only permit certain bits to be set. We never use * the global lock, but ACPI-CA whines profusely if it * can't set GBL_EN. */ pm1_enable = *eax & (PM1_RTC_EN | PM1_PWRBTN_EN | PM1_GBL_EN); sci_update(ctx); } pthread_mutex_unlock(&pm_lock); return (0); } INOUT_PORT(pm1_status, PM1A_EVT_ADDR, IOPORT_F_INOUT, pm1_status_handler); INOUT_PORT(pm1_enable, PM1A_EVT_ADDR + 2, IOPORT_F_INOUT, pm1_enable_handler); #ifdef __FreeBSD__ static void power_button_handler(int signal __unused, enum ev_type type __unused, void *arg) { struct vmctx *ctx; ctx = arg; pthread_mutex_lock(&pm_lock); if (!(pm1_status & PM1_PWRBTN_STS)) { pm1_status |= PM1_PWRBTN_STS; sci_update(ctx); } pthread_mutex_unlock(&pm_lock); } #else /* * Initiate graceful power off. */ /*ARGSUSED*/ static void power_button_handler(int signal, siginfo_t *type, void *cp) { /* * In theory, taking the 'pm_lock' mutex from within this signal * handler could lead to deadlock if the main thread already held this * mutex. In reality, this mutex is local to this file and all of the * other usage in this file only occurs in functions which are FreeBSD * specific (and thus currently not used). Thus, for consistency with * the other code in this file, we take the mutex, but in the future, * if these other functions are ever enabled for use on non-FreeBSD * systems and these functions could be called directly by a thread * (which would then hold the mutex), then we need to revisit the use * of this mutex in this signal handler. */ pthread_mutex_lock(&pm_lock); if (!(pm1_status & PM1_PWRBTN_STS)) { pm1_status |= PM1_PWRBTN_STS; sci_update(pwr_ctx); } pthread_mutex_unlock(&pm_lock); } #endif /* * Power Management 1 Control Register * * This is mostly unimplemented except that we wish to handle writes that * set SPL_EN to handle S5 (soft power off). */ static uint16_t pm1_control; #define PM1_SCI_EN 0x0001 #define PM1_SLP_TYP 0x1c00 #define PM1_SLP_EN 0x2000 #define PM1_ALWAYS_ZERO 0xc003 static int pm1_control_handler(struct vmctx *ctx, int in, int port __unused, int bytes, uint32_t *eax, void *arg __unused) { int error; if (bytes != 2) return (-1); if (in) *eax = pm1_control; else { /* * Various bits are write-only or reserved, so force them * to zero in pm1_control. Always preserve SCI_EN as OSPM * can never change it. */ pm1_control = (pm1_control & PM1_SCI_EN) | (*eax & ~(PM1_SLP_EN | PM1_ALWAYS_ZERO)); /* * If SLP_EN is set, check for S5. Bhyve's _S5_ method * says that '5' should be stored in SLP_TYP for S5. */ if (*eax & PM1_SLP_EN) { if ((pm1_control & PM1_SLP_TYP) >> 10 == 5) { error = vm_suspend(ctx, VM_SUSPEND_POWEROFF); assert(error == 0 || errno == EALREADY); } } } return (0); } INOUT_PORT(pm1_control, PM1A_CNT_ADDR, IOPORT_F_INOUT, pm1_control_handler); #ifdef __FreeBSD__ SYSRES_IO(PM1A_EVT_ADDR, 8); #endif void acpi_raise_gpe(struct vmctx *ctx, unsigned bit) { unsigned mask; assert(bit < (IO_GPE0_LEN * (8 / 2))); mask = (1u << bit); assert((mask & ~gpe0_valid) == 0); pthread_mutex_lock(&pm_lock); gpe0_active |= mask; sci_update(ctx); pthread_mutex_unlock(&pm_lock); } static int gpe0_sts(struct vmctx *ctx, int in, int port __unused, int bytes, uint32_t *eax, void *arg __unused) { /* * ACPI 6.2 specifies the GPE register blocks are accessed * byte-at-a-time. */ if (bytes != 1) return (-1); pthread_mutex_lock(&pm_lock); if (in) *eax = gpe0_active; else { /* W1C */ gpe0_active &= ~(*eax & gpe0_valid); sci_update(ctx); } pthread_mutex_unlock(&pm_lock); return (0); } INOUT_PORT(gpe0_sts, IO_GPE0_STS, IOPORT_F_INOUT, gpe0_sts); static int gpe0_en(struct vmctx *ctx, int in, int port __unused, int bytes, uint32_t *eax, void *arg __unused) { if (bytes != 1) return (-1); pthread_mutex_lock(&pm_lock); if (in) *eax = gpe0_enabled; else { gpe0_enabled = (*eax & gpe0_valid); sci_update(ctx); } pthread_mutex_unlock(&pm_lock); return (0); } INOUT_PORT(gpe0_en, IO_GPE0_EN, IOPORT_F_INOUT, gpe0_en); /* * ACPI SMI Command Register * * This write-only register is used to enable and disable ACPI. */ static int smi_cmd_handler(struct vmctx *ctx, int in, int port __unused, int bytes, uint32_t *eax, void *arg __unused) { assert(!in); if (bytes != 1) return (-1); pthread_mutex_lock(&pm_lock); switch (*eax) { case BHYVE_ACPI_ENABLE: pm1_control |= PM1_SCI_EN; #ifdef __FreeBSD__ if (power_button == NULL) { power_button = mevent_add(SIGTERM, EVF_SIGNAL, power_button_handler, ctx); old_power_handler = signal(SIGTERM, SIG_IGN); } #endif break; case BHYVE_ACPI_DISABLE: pm1_control &= ~PM1_SCI_EN; #ifdef __FreeBSD__ if (power_button != NULL) { mevent_delete(power_button); power_button = NULL; signal(SIGTERM, old_power_handler); } #endif break; } pthread_mutex_unlock(&pm_lock); return (0); } INOUT_PORT(smi_cmd, SMI_CMD, IOPORT_F_OUT, smi_cmd_handler); #ifdef __FreeBSD__ SYSRES_IO(SMI_CMD, 1); #endif void sci_init(struct vmctx *ctx) { /* * Mark ACPI's SCI as level trigger and bump its use count * in the PIRQ router. */ pci_irq_use(SCI_INT); vm_isa_set_irq_trigger(ctx, SCI_INT, LEVEL_TRIGGER); #ifndef __FreeBSD__ { /* * Install SIGTERM signal handler for graceful power off. */ struct sigaction act; pwr_ctx = ctx; act.sa_flags = 0; act.sa_sigaction = power_button_handler; (void) sigaction(SIGTERM, &act, NULL); } #endif } #ifndef __FreeBSD__ void pmtmr_init(struct vmctx *ctx) { int err; /* Attach in-kernel PM timer emulation to correct IO port */ err = vm_pmtmr_set_location(ctx, IO_PMTMR); assert(err == 0); } #endif /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 NetApp, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include "inout.h" #include "pci_lpc.h" static int post_data_handler(struct vmctx *ctx __unused, int in, int port __unused, int bytes, uint32_t *eax, void *arg __unused) { assert(in == 1); if (bytes != 1) return (-1); *eax = 0xff; /* return some garbage */ return (0); } INOUT_PORT(post, 0x84, IOPORT_F_IN, post_data_handler); SYSRES_IO(0x84, 1); /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2015 Tycho Nightingale * Copyright (c) 2015 Nahanni Systems Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include "atkbdc.h" #include "bhyverun.h" #include "config.h" #include "console.h" #include "debug.h" #include "ps2kbd.h" /* keyboard device commands */ #define PS2KC_RESET_DEV 0xff #define PS2KC_SET_DEFAULTS 0xf6 #define PS2KC_DISABLE 0xf5 #define PS2KC_ENABLE 0xf4 #define PS2KC_SET_TYPEMATIC 0xf3 #define PS2KC_SEND_DEV_ID 0xf2 #define PS2KC_SET_SCANCODE_SET 0xf0 #define PS2KC_ECHO 0xee #define PS2KC_SET_LEDS 0xed #define PS2KC_BAT_SUCCESS 0xaa #define PS2KC_ACK 0xfa #define PS2KBD_FIFOSZ 16 #define PS2KBD_LAYOUT_BASEDIR "/usr/share/bhyve/kbdlayout/" #define MAX_PATHNAME 256 struct fifo { uint8_t buf[PS2KBD_FIFOSZ]; int rindex; /* index to read from */ int windex; /* index to write to */ int num; /* number of bytes in the fifo */ int size; /* size of the fifo */ }; struct ps2kbd_softc { struct atkbdc_softc *atkbdc_sc; pthread_mutex_t mtx; bool enabled; struct fifo fifo; uint8_t curcmd; /* current command for next byte */ }; #define SCANCODE_E0_PREFIX 1 struct extended_translation { uint32_t keysym; uint8_t scancode; int flags; }; /* * FIXME: Pause/break and Print Screen/SysRq require special handling. */ static struct extended_translation extended_translations[128] = { {0xff08, 0x66, 0}, /* Back space */ {0xff09, 0x0d, 0}, /* Tab */ {0xff0d, 0x5a, 0}, /* Return */ {0xff1b, 0x76, 0}, /* Escape */ {0xff50, 0x6c, SCANCODE_E0_PREFIX}, /* Home */ {0xff51, 0x6b, SCANCODE_E0_PREFIX}, /* Left arrow */ {0xff52, 0x75, SCANCODE_E0_PREFIX}, /* Up arrow */ {0xff53, 0x74, SCANCODE_E0_PREFIX}, /* Right arrow */ {0xff54, 0x72, SCANCODE_E0_PREFIX}, /* Down arrow */ {0xff55, 0x7d, SCANCODE_E0_PREFIX}, /* PgUp */ {0xff56, 0x7a, SCANCODE_E0_PREFIX}, /* PgDown */ {0xff57, 0x69, SCANCODE_E0_PREFIX}, /* End */ {0xff63, 0x70, SCANCODE_E0_PREFIX}, /* Ins */ {0xff8d, 0x5a, SCANCODE_E0_PREFIX}, /* Keypad Enter */ {0xffe1, 0x12, 0}, /* Left shift */ {0xffe2, 0x59, 0}, /* Right shift */ {0xffe3, 0x14, 0}, /* Left control */ {0xffe4, 0x14, SCANCODE_E0_PREFIX}, /* Right control */ /* {0xffe7, XXX}, Left meta */ /* {0xffe8, XXX}, Right meta */ {0xffe9, 0x11, 0}, /* Left alt */ {0xfe03, 0x11, SCANCODE_E0_PREFIX}, /* AltGr */ {0xffea, 0x11, SCANCODE_E0_PREFIX}, /* Right alt */ {0xffeb, 0x1f, SCANCODE_E0_PREFIX}, /* Left Windows */ {0xffec, 0x27, SCANCODE_E0_PREFIX}, /* Right Windows */ {0xffbe, 0x05, 0}, /* F1 */ {0xffbf, 0x06, 0}, /* F2 */ {0xffc0, 0x04, 0}, /* F3 */ {0xffc1, 0x0c, 0}, /* F4 */ {0xffc2, 0x03, 0}, /* F5 */ {0xffc3, 0x0b, 0}, /* F6 */ {0xffc4, 0x83, 0}, /* F7 */ {0xffc5, 0x0a, 0}, /* F8 */ {0xffc6, 0x01, 0}, /* F9 */ {0xffc7, 0x09, 0}, /* F10 */ {0xffc8, 0x78, 0}, /* F11 */ {0xffc9, 0x07, 0}, /* F12 */ {0xffff, 0x71, SCANCODE_E0_PREFIX}, /* Del */ {0xff14, 0x7e, 0}, /* ScrollLock */ /* NumLock and Keypads*/ {0xff7f, 0x77, 0}, /* NumLock */ {0xffaf, 0x4a, SCANCODE_E0_PREFIX}, /* Keypad slash */ {0xffaa, 0x7c, 0}, /* Keypad asterisk */ {0xffad, 0x7b, 0}, /* Keypad minus */ {0xffab, 0x79, 0}, /* Keypad plus */ {0xffb7, 0x6c, 0}, /* Keypad 7 */ {0xff95, 0x6c, 0}, /* Keypad home */ {0xffb8, 0x75, 0}, /* Keypad 8 */ {0xff97, 0x75, 0}, /* Keypad up arrow */ {0xffb9, 0x7d, 0}, /* Keypad 9 */ {0xff9a, 0x7d, 0}, /* Keypad PgUp */ {0xffb4, 0x6b, 0}, /* Keypad 4 */ {0xff96, 0x6b, 0}, /* Keypad left arrow */ {0xffb5, 0x73, 0}, /* Keypad 5 */ {0xff9d, 0x73, 0}, /* Keypad empty */ {0xffb6, 0x74, 0}, /* Keypad 6 */ {0xff98, 0x74, 0}, /* Keypad right arrow */ {0xffb1, 0x69, 0}, /* Keypad 1 */ {0xff9c, 0x69, 0}, /* Keypad end */ {0xffb2, 0x72, 0}, /* Keypad 2 */ {0xff99, 0x72, 0}, /* Keypad down arrow */ {0xffb3, 0x7a, 0}, /* Keypad 3 */ {0xff9b, 0x7a, 0}, /* Keypad PgDown */ {0xffb0, 0x70, 0}, /* Keypad 0 */ {0xff9e, 0x70, 0}, /* Keypad ins */ {0xffae, 0x71, 0}, /* Keypad . */ {0xff9f, 0x71, 0}, /* Keypad del */ {0, 0, 0} /* Terminator */ }; /* ASCII to type 2 scancode lookup table */ static uint8_t ascii_translations[128] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x16, 0x52, 0x26, 0x25, 0x2e, 0x3d, 0x52, 0x46, 0x45, 0x3e, 0x55, 0x41, 0x4e, 0x49, 0x4a, 0x45, 0x16, 0x1e, 0x26, 0x25, 0x2e, 0x36, 0x3d, 0x3e, 0x46, 0x4c, 0x4c, 0x41, 0x55, 0x49, 0x4a, 0x1e, 0x1c, 0x32, 0x21, 0x23, 0x24, 0x2b, 0x34, 0x33, 0x43, 0x3b, 0x42, 0x4b, 0x3a, 0x31, 0x44, 0x4d, 0x15, 0x2d, 0x1b, 0x2c, 0x3c, 0x2a, 0x1d, 0x22, 0x35, 0x1a, 0x54, 0x5d, 0x5b, 0x36, 0x4e, 0x0e, 0x1c, 0x32, 0x21, 0x23, 0x24, 0x2b, 0x34, 0x33, 0x43, 0x3b, 0x42, 0x4b, 0x3a, 0x31, 0x44, 0x4d, 0x15, 0x2d, 0x1b, 0x2c, 0x3c, 0x2a, 0x1d, 0x22, 0x35, 0x1a, 0x54, 0x5d, 0x5b, 0x0e, 0x00, }; /* ScanCode set1 to set2 lookup table */ static const uint8_t keyset1to2_translations[128] = { 0, 0x76, 0x16, 0x1E, 0x26, 0x25, 0x2e, 0x36, 0x3d, 0x3e, 0x46, 0x45, 0x4e, 0x55, 0x66, 0x0d, 0x15, 0x1d, 0x24, 0x2d, 0x2c, 0x35, 0x3c, 0x43, 0x44, 0x4d, 0x54, 0x5b, 0x5a, 0x14, 0x1c, 0x1b, 0x23, 0x2b, 0x34, 0x33, 0x3b, 0x42, 0x4b, 0x4c, 0x52, 0x0e, 0x12, 0x5d, 0x1a, 0x22, 0x21, 0x2a, 0x32, 0x31, 0x3a, 0x41, 0x49, 0x4a, 0x59, 0x7c, 0x11, 0x29, 0x58, 0x05, 0x06, 0x04, 0x0c, 0x03, 0x0b, 0x83, 0x0a, 0x01, 0x09, 0x77, 0x7e, 0x6c, 0x75, 0x7d, 0x7b, 0x6b, 0x73, 0x74, 0x79, 0x69, 0x72, 0x7a, 0x70, 0x71, 0x84, 0x60, 0x61, 0x78, 0x07, 0x0f, 0x17, 0x1f, 0x27, 0x2f, 0x37, 0x3f, 0x47, 0x4f, 0x56, 0x5e, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x48, 0x50, 0x57, 0x6f, 0x13, 0x19, 0x39, 0x51, 0x53, 0x5c, 0x5f, 0x62, 0x63, 0x64, 0x65, 0x67, 0x68, 0x6a, 0x6d, 0x6e, }; static void fifo_init(struct ps2kbd_softc *sc) { struct fifo *fifo; fifo = &sc->fifo; fifo->size = sizeof(((struct fifo *)0)->buf); } static void fifo_reset(struct ps2kbd_softc *sc) { struct fifo *fifo; fifo = &sc->fifo; bzero(fifo, sizeof(struct fifo)); fifo->size = sizeof(((struct fifo *)0)->buf); } static void fifo_put(struct ps2kbd_softc *sc, uint8_t val) { struct fifo *fifo; fifo = &sc->fifo; if (fifo->num < fifo->size) { fifo->buf[fifo->windex] = val; fifo->windex = (fifo->windex + 1) % fifo->size; fifo->num++; } } static int fifo_get(struct ps2kbd_softc *sc, uint8_t *val) { struct fifo *fifo; fifo = &sc->fifo; if (fifo->num > 0) { *val = fifo->buf[fifo->rindex]; fifo->rindex = (fifo->rindex + 1) % fifo->size; fifo->num--; return (0); } return (-1); } int ps2kbd_read(struct ps2kbd_softc *sc, uint8_t *val) { int retval; pthread_mutex_lock(&sc->mtx); retval = fifo_get(sc, val); pthread_mutex_unlock(&sc->mtx); return (retval); } void ps2kbd_write(struct ps2kbd_softc *sc, uint8_t val) { pthread_mutex_lock(&sc->mtx); if (sc->curcmd) { switch (sc->curcmd) { case PS2KC_SET_TYPEMATIC: fifo_put(sc, PS2KC_ACK); break; case PS2KC_SET_SCANCODE_SET: fifo_put(sc, PS2KC_ACK); break; case PS2KC_SET_LEDS: fifo_put(sc, PS2KC_ACK); break; default: EPRINTLN("Unhandled ps2 keyboard current " "command byte 0x%02x", val); break; } sc->curcmd = 0; } else { switch (val) { case 0x00: fifo_put(sc, PS2KC_ACK); break; case PS2KC_RESET_DEV: fifo_reset(sc); fifo_put(sc, PS2KC_ACK); fifo_put(sc, PS2KC_BAT_SUCCESS); break; case PS2KC_SET_DEFAULTS: fifo_reset(sc); fifo_put(sc, PS2KC_ACK); break; case PS2KC_DISABLE: sc->enabled = false; fifo_put(sc, PS2KC_ACK); break; case PS2KC_ENABLE: sc->enabled = true; fifo_reset(sc); fifo_put(sc, PS2KC_ACK); break; case PS2KC_SET_TYPEMATIC: sc->curcmd = val; fifo_put(sc, PS2KC_ACK); break; case PS2KC_SEND_DEV_ID: fifo_put(sc, PS2KC_ACK); fifo_put(sc, 0xab); fifo_put(sc, 0x83); break; case PS2KC_SET_SCANCODE_SET: sc->curcmd = val; fifo_put(sc, PS2KC_ACK); break; case PS2KC_ECHO: fifo_put(sc, PS2KC_ECHO); break; case PS2KC_SET_LEDS: sc->curcmd = val; fifo_put(sc, PS2KC_ACK); break; default: EPRINTLN("Unhandled ps2 keyboard command " "0x%02x", val); break; } } pthread_mutex_unlock(&sc->mtx); } /* * Translate keysym to type 2 scancode and insert into keyboard buffer. */ static void ps2kbd_keysym_queue(struct ps2kbd_softc *sc, int down, uint32_t keysym, uint32_t keycode) { const struct extended_translation *trans; int e0_prefix, found; uint8_t code; assert(pthread_mutex_isowned_np(&sc->mtx)); if (keycode) { code = keyset1to2_translations[(uint8_t)(keycode & 0x7f)]; e0_prefix = ((keycode & 0x80) ? SCANCODE_E0_PREFIX : 0); found = 1; } else { found = 0; if (keysym < 0x80) { code = ascii_translations[keysym]; e0_prefix = 0; found = 1; } else { for (trans = &extended_translations[0]; trans->keysym != 0; trans++) { if (keysym == trans->keysym) { code = trans->scancode; e0_prefix = trans->flags & SCANCODE_E0_PREFIX; found = 1; break; } } } } if (!found) { EPRINTLN("Unhandled ps2 keyboard keysym 0x%x", keysym); return; } if (e0_prefix) fifo_put(sc, 0xe0); if (!down) fifo_put(sc, 0xf0); fifo_put(sc, code); } static void ps2kbd_event(int down, uint32_t keysym, uint32_t keycode, void *arg) { struct ps2kbd_softc *sc = arg; int fifo_full; pthread_mutex_lock(&sc->mtx); if (!sc->enabled) { pthread_mutex_unlock(&sc->mtx); return; } fifo_full = sc->fifo.num == PS2KBD_FIFOSZ; ps2kbd_keysym_queue(sc, down, keysym, keycode); pthread_mutex_unlock(&sc->mtx); if (!fifo_full) atkbdc_event(sc->atkbdc_sc, 1); } static void ps2kbd_update_extended_translation(uint32_t keycode, uint32_t scancode, uint32_t prefix) { int i = 0; do { if (extended_translations[i].keysym == keycode) break; } while (extended_translations[++i].keysym); if (i == (sizeof(extended_translations) / sizeof(struct extended_translation) - 1)) return; if (!extended_translations[i].keysym) { extended_translations[i].keysym = keycode; extended_translations[i+1].keysym = 0; extended_translations[i+1].scancode = 0; extended_translations[i+1].flags = 0; } extended_translations[i].scancode = (uint8_t)(scancode & 0xff); extended_translations[i].flags = (prefix ? SCANCODE_E0_PREFIX : 0); } static void ps2kbd_setkbdlayout(void) { int err; int fd; char path[MAX_PATHNAME]; char *buf, *next, *line; struct stat sb; ssize_t sz; uint8_t ascii; uint32_t keycode, scancode, prefix; snprintf(path, MAX_PATHNAME, PS2KBD_LAYOUT_BASEDIR"%s", get_config_value("keyboard.layout") ); err = stat(path, &sb); if (err) return; buf = (char *)malloc(sizeof(char) * sb.st_size); if (buf == NULL) return; fd = open(path, O_RDONLY); if (fd == -1) goto out; sz = read(fd, buf, sb.st_size); close(fd); if (sz < 0 || sz != sb.st_size) goto out; next = buf; while ((line = strsep(&next, "\n")) != NULL) { if (sscanf(line, "'%c',%x;", &ascii, &scancode) == 2) { if (ascii < 0x80) ascii_translations[ascii] = (uint8_t)(scancode & 0xff); } else if (sscanf(line, "%x,%x,%x;", &keycode, &scancode, &prefix) == 3 ) { ps2kbd_update_extended_translation(keycode, scancode, prefix); } else if (sscanf(line, "%x,%x;", &keycode, &scancode) == 2) { if (keycode < 0x80) ascii_translations[(uint8_t)(keycode & 0xff)] = (uint8_t)(scancode & 0xff); else ps2kbd_update_extended_translation(keycode, scancode, 0); } } out: free(buf); } struct ps2kbd_softc * ps2kbd_init(struct atkbdc_softc *atkbdc_sc) { struct ps2kbd_softc *sc; if (get_config_value("keyboard.layout") != NULL) ps2kbd_setkbdlayout(); sc = calloc(1, sizeof (struct ps2kbd_softc)); pthread_mutex_init(&sc->mtx, NULL); fifo_init(sc); sc->atkbdc_sc = atkbdc_sc; console_kbd_register(ps2kbd_event, sc, 1); return (sc); } /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2015 Tycho Nightingale * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _PS2KBD_H_ #define _PS2KBD_H_ struct atkbdc_softc; struct ps2kbd_softc *ps2kbd_init(struct atkbdc_softc *sc); int ps2kbd_read(struct ps2kbd_softc *sc, uint8_t *val); void ps2kbd_write(struct ps2kbd_softc *sc, uint8_t val); #endif /* _PS2KBD_H_ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2015 Tycho Nightingale * Copyright (c) 2015 Nahanni Systems Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include "atkbdc.h" #include "console.h" #include "debug.h" #include "ps2mouse.h" /* mouse device commands */ #define PS2MC_RESET_DEV 0xff #define PS2MC_SET_DEFAULTS 0xf6 #define PS2MC_DISABLE 0xf5 #define PS2MC_ENABLE 0xf4 #define PS2MC_SET_SAMPLING_RATE 0xf3 #define PS2MC_SEND_DEV_ID 0xf2 #define PS2MC_SET_REMOTE_MODE 0xf0 #define PS2MC_SEND_DEV_DATA 0xeb #define PS2MC_SET_STREAM_MODE 0xea #define PS2MC_SEND_DEV_STATUS 0xe9 #define PS2MC_SET_RESOLUTION 0xe8 #define PS2MC_SET_SCALING1 0xe7 #define PS2MC_SET_SCALING2 0xe6 #define PS2MC_BAT_SUCCESS 0xaa #define PS2MC_ACK 0xfa /* mouse device id */ #define PS2MOUSE_DEV_ID 0x0 /* mouse data bits */ #define PS2M_DATA_Y_OFLOW 0x80 #define PS2M_DATA_X_OFLOW 0x40 #define PS2M_DATA_Y_SIGN 0x20 #define PS2M_DATA_X_SIGN 0x10 #define PS2M_DATA_AONE 0x08 #define PS2M_DATA_MID_BUTTON 0x04 #define PS2M_DATA_RIGHT_BUTTON 0x02 #define PS2M_DATA_LEFT_BUTTON 0x01 /* mouse status bits */ #define PS2M_STS_REMOTE_MODE 0x40 #define PS2M_STS_ENABLE_DEV 0x20 #define PS2M_STS_SCALING_21 0x10 #define PS2M_STS_MID_BUTTON 0x04 #define PS2M_STS_RIGHT_BUTTON 0x02 #define PS2M_STS_LEFT_BUTTON 0x01 #define PS2MOUSE_FIFOSZ 16 struct fifo { uint8_t buf[PS2MOUSE_FIFOSZ]; int rindex; /* index to read from */ int windex; /* index to write to */ int num; /* number of bytes in the fifo */ int size; /* size of the fifo */ }; struct ps2mouse_softc { struct atkbdc_softc *atkbdc_sc; pthread_mutex_t mtx; uint8_t status; uint8_t resolution; uint8_t sampling_rate; int ctrlenable; struct fifo fifo; uint8_t curcmd; /* current command for next byte */ int cur_x, cur_y; int delta_x, delta_y; }; static void fifo_init(struct ps2mouse_softc *sc) { struct fifo *fifo; fifo = &sc->fifo; fifo->size = sizeof(((struct fifo *)0)->buf); } static void fifo_reset(struct ps2mouse_softc *sc) { struct fifo *fifo; fifo = &sc->fifo; bzero(fifo, sizeof(struct fifo)); fifo->size = sizeof(((struct fifo *)0)->buf); } static void fifo_put(struct ps2mouse_softc *sc, uint8_t val) { struct fifo *fifo; fifo = &sc->fifo; if (fifo->num < fifo->size) { fifo->buf[fifo->windex] = val; fifo->windex = (fifo->windex + 1) % fifo->size; fifo->num++; } } static int fifo_get(struct ps2mouse_softc *sc, uint8_t *val) { struct fifo *fifo; fifo = &sc->fifo; if (fifo->num > 0) { *val = fifo->buf[fifo->rindex]; fifo->rindex = (fifo->rindex + 1) % fifo->size; fifo->num--; return (0); } return (-1); } static void movement_reset(struct ps2mouse_softc *sc) { assert(pthread_mutex_isowned_np(&sc->mtx)); sc->delta_x = 0; sc->delta_y = 0; } static void movement_update(struct ps2mouse_softc *sc, int x, int y) { sc->delta_x += x - sc->cur_x; sc->delta_y += sc->cur_y - y; sc->cur_x = x; sc->cur_y = y; } static void movement_get(struct ps2mouse_softc *sc) { uint8_t val0, val1, val2; assert(pthread_mutex_isowned_np(&sc->mtx)); val0 = PS2M_DATA_AONE; val0 |= sc->status & (PS2M_DATA_LEFT_BUTTON | PS2M_DATA_RIGHT_BUTTON | PS2M_DATA_MID_BUTTON); if (sc->delta_x >= 0) { if (sc->delta_x > 255) { val0 |= PS2M_DATA_X_OFLOW; val1 = 255; } else val1 = sc->delta_x; } else { val0 |= PS2M_DATA_X_SIGN; if (sc->delta_x < -255) { val0 |= PS2M_DATA_X_OFLOW; val1 = 255; } else val1 = sc->delta_x; } sc->delta_x = 0; if (sc->delta_y >= 0) { if (sc->delta_y > 255) { val0 |= PS2M_DATA_Y_OFLOW; val2 = 255; } else val2 = sc->delta_y; } else { val0 |= PS2M_DATA_Y_SIGN; if (sc->delta_y < -255) { val0 |= PS2M_DATA_Y_OFLOW; val2 = 255; } else val2 = sc->delta_y; } sc->delta_y = 0; if (sc->fifo.num < (sc->fifo.size - 3)) { fifo_put(sc, val0); fifo_put(sc, val1); fifo_put(sc, val2); } } static void ps2mouse_reset(struct ps2mouse_softc *sc) { assert(pthread_mutex_isowned_np(&sc->mtx)); fifo_reset(sc); movement_reset(sc); sc->status = PS2M_STS_ENABLE_DEV; sc->resolution = 4; sc->sampling_rate = 100; sc->cur_x = 0; sc->cur_y = 0; sc->delta_x = 0; sc->delta_y = 0; } int ps2mouse_read(struct ps2mouse_softc *sc, uint8_t *val) { int retval; pthread_mutex_lock(&sc->mtx); retval = fifo_get(sc, val); pthread_mutex_unlock(&sc->mtx); return (retval); } int ps2mouse_fifocnt(struct ps2mouse_softc *sc) { return (sc->fifo.num); } void ps2mouse_toggle(struct ps2mouse_softc *sc, int enable) { pthread_mutex_lock(&sc->mtx); if (enable) sc->ctrlenable = 1; else { sc->ctrlenable = 0; sc->fifo.rindex = 0; sc->fifo.windex = 0; sc->fifo.num = 0; } pthread_mutex_unlock(&sc->mtx); } void ps2mouse_write(struct ps2mouse_softc *sc, uint8_t val, int insert) { pthread_mutex_lock(&sc->mtx); fifo_reset(sc); if (sc->curcmd) { switch (sc->curcmd) { case PS2MC_SET_SAMPLING_RATE: sc->sampling_rate = val; fifo_put(sc, PS2MC_ACK); break; case PS2MC_SET_RESOLUTION: sc->resolution = val; fifo_put(sc, PS2MC_ACK); break; default: EPRINTLN("Unhandled ps2 mouse current " "command byte 0x%02x", val); break; } sc->curcmd = 0; } else if (insert) { fifo_put(sc, val); } else { switch (val) { case 0x00: fifo_put(sc, PS2MC_ACK); break; case PS2MC_RESET_DEV: ps2mouse_reset(sc); fifo_put(sc, PS2MC_ACK); fifo_put(sc, PS2MC_BAT_SUCCESS); fifo_put(sc, PS2MOUSE_DEV_ID); break; case PS2MC_SET_DEFAULTS: ps2mouse_reset(sc); fifo_put(sc, PS2MC_ACK); break; case PS2MC_DISABLE: fifo_reset(sc); sc->status &= ~PS2M_STS_ENABLE_DEV; fifo_put(sc, PS2MC_ACK); break; case PS2MC_ENABLE: fifo_reset(sc); sc->status |= PS2M_STS_ENABLE_DEV; fifo_put(sc, PS2MC_ACK); break; case PS2MC_SET_SAMPLING_RATE: sc->curcmd = val; fifo_put(sc, PS2MC_ACK); break; case PS2MC_SEND_DEV_ID: fifo_put(sc, PS2MC_ACK); fifo_put(sc, PS2MOUSE_DEV_ID); break; case PS2MC_SET_REMOTE_MODE: sc->status |= PS2M_STS_REMOTE_MODE; fifo_put(sc, PS2MC_ACK); break; case PS2MC_SEND_DEV_DATA: fifo_put(sc, PS2MC_ACK); movement_get(sc); break; case PS2MC_SET_STREAM_MODE: sc->status &= ~PS2M_STS_REMOTE_MODE; fifo_put(sc, PS2MC_ACK); break; case PS2MC_SEND_DEV_STATUS: fifo_put(sc, PS2MC_ACK); fifo_put(sc, sc->status); fifo_put(sc, sc->resolution); fifo_put(sc, sc->sampling_rate); break; case PS2MC_SET_RESOLUTION: sc->curcmd = val; fifo_put(sc, PS2MC_ACK); break; case PS2MC_SET_SCALING1: case PS2MC_SET_SCALING2: fifo_put(sc, PS2MC_ACK); break; default: fifo_put(sc, PS2MC_ACK); EPRINTLN("Unhandled ps2 mouse command " "0x%02x", val); break; } } pthread_mutex_unlock(&sc->mtx); } static void ps2mouse_event(uint8_t button, int x, int y, void *arg) { struct ps2mouse_softc *sc = arg; pthread_mutex_lock(&sc->mtx); movement_update(sc, x, y); sc->status &= ~(PS2M_STS_LEFT_BUTTON | PS2M_STS_RIGHT_BUTTON | PS2M_STS_MID_BUTTON); if (button & (1 << 0)) sc->status |= PS2M_STS_LEFT_BUTTON; if (button & (1 << 1)) sc->status |= PS2M_STS_MID_BUTTON; if (button & (1 << 2)) sc->status |= PS2M_STS_RIGHT_BUTTON; if ((sc->status & PS2M_STS_ENABLE_DEV) == 0 || !sc->ctrlenable) { /* no data reporting */ pthread_mutex_unlock(&sc->mtx); return; } movement_get(sc); pthread_mutex_unlock(&sc->mtx); if (sc->fifo.num > 0) atkbdc_event(sc->atkbdc_sc, 0); } struct ps2mouse_softc * ps2mouse_init(struct atkbdc_softc *atkbdc_sc) { struct ps2mouse_softc *sc; sc = calloc(1, sizeof (struct ps2mouse_softc)); pthread_mutex_init(&sc->mtx, NULL); fifo_init(sc); sc->atkbdc_sc = atkbdc_sc; pthread_mutex_lock(&sc->mtx); ps2mouse_reset(sc); pthread_mutex_unlock(&sc->mtx); console_ptr_register(ps2mouse_event, sc, 1); return (sc); } /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2015 Tycho Nightingale * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _PS2MOUSE_H_ #define _PS2MOUSE_H_ struct atkbdc_softc; struct ps2mouse_softc *ps2mouse_init(struct atkbdc_softc *sc); int ps2mouse_read(struct ps2mouse_softc *sc, uint8_t *val); void ps2mouse_write(struct ps2mouse_softc *sc, uint8_t val, int insert); void ps2mouse_toggle(struct ps2mouse_softc *sc, int enable); int ps2mouse_fifocnt(struct ps2mouse_softc *sc); #endif /* _PS2MOUSE_H_ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 NetApp, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include "acpi.h" #include "config.h" #include "pci_lpc.h" #include "rtc.h" #define IO_RTC 0x70 #define RTC_LMEM_LSB 0x34 #define RTC_LMEM_MSB 0x35 #define RTC_HMEM_LSB 0x5b #define RTC_HMEM_SB 0x5c #define RTC_HMEM_MSB 0x5d #define m_64KB (64*1024) #define m_16MB (16*1024*1024) #define m_4GB (4ULL*1024*1024*1024) /* * Returns the current RTC time as number of seconds since 00:00:00 Jan 1, 1970 */ #ifdef __FreeBSD__ static time_t rtc_time(void) { struct tm tm; time_t t; time(&t); if (get_config_bool_default("rtc.use_localtime", true)) { localtime_r(&t, &tm); t = timegm(&tm); } return (t); } #else /* __FreeBSD__ */ static void rtc_time(timespec_t *ts) { (void) clock_gettime(CLOCK_REALTIME, ts); if (get_config_bool_default("rtc.use_localtime", true)) { struct tm tm; localtime_r(&ts->tv_sec, &tm); ts->tv_sec = timegm(&tm); } } #endif /* __FreeBSD__ */ void rtc_init(struct vmctx *ctx) { size_t himem; size_t lomem; int err; /* XXX init diag/reset code/equipment/checksum ? */ /* * Report guest memory size in nvram cells as required by UEFI. * Little-endian encoding. * 0x34/0x35 - 64KB chunks above 16MB, below 4GB * 0x5b/0x5c/0x5d - 64KB chunks above 4GB */ lomem = (vm_get_lowmem_size(ctx) - m_16MB) / m_64KB; err = vm_rtc_write(ctx, RTC_LMEM_LSB, lomem); assert(err == 0); err = vm_rtc_write(ctx, RTC_LMEM_MSB, lomem >> 8); assert(err == 0); himem = vm_get_highmem_size(ctx) / m_64KB; err = vm_rtc_write(ctx, RTC_HMEM_LSB, himem); assert(err == 0); err = vm_rtc_write(ctx, RTC_HMEM_SB, himem >> 8); assert(err == 0); err = vm_rtc_write(ctx, RTC_HMEM_MSB, himem >> 16); assert(err == 0); #ifdef __FreeBSD__ err = vm_rtc_settime(ctx, rtc_time()); #else timespec_t ts; rtc_time(&ts); err = vm_rtc_settime(ctx, &ts); #endif assert(err == 0); } static void rtc_dsdt(void) { dsdt_line(""); dsdt_line("Device (RTC)"); dsdt_line("{"); dsdt_line(" Name (_HID, EisaId (\"PNP0B00\"))"); dsdt_line(" Name (_CRS, ResourceTemplate ()"); dsdt_line(" {"); dsdt_indent(2); dsdt_fixed_ioport(IO_RTC, 2); dsdt_fixed_irq(8); dsdt_unindent(2); dsdt_line(" })"); dsdt_line("}"); } LPC_DSDT(rtc_dsdt); /* * Reserve the extended RTC I/O ports although they are not emulated at this * time. */ SYSRES_IO(0x72, 6); /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2013 Peter Grehan * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _RTC_H_ #define _RTC_H_ void rtc_init(struct vmctx *ctx); #endif /* _RTC_H_ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012 NetApp, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. * * Copyright 2020 Oxide Computer Company */ #include #include #include #include #include #include #include #include "bhyverun.h" #include "spinup_ap.h" #ifdef __FreeBSD__ static void spinup_ap_realmode(struct vcpu *newcpu, uint64_t rip) { int vector, error; uint16_t cs; uint64_t desc_base; uint32_t desc_limit, desc_access; vector = rip >> PAGE_SHIFT; /* * Update the %cs and %rip of the guest so that it starts * executing real mode code at 'vector << 12'. */ error = vm_set_register(newcpu, VM_REG_GUEST_RIP, 0); assert(error == 0); error = vm_get_desc(newcpu, VM_REG_GUEST_CS, &desc_base, &desc_limit, &desc_access); assert(error == 0); desc_base = vector << PAGE_SHIFT; error = vm_set_desc(newcpu, VM_REG_GUEST_CS, desc_base, desc_limit, desc_access); assert(error == 0); cs = (vector << PAGE_SHIFT) >> 4; error = vm_set_register(newcpu, VM_REG_GUEST_CS, cs); assert(error == 0); } #endif /* __FreeBSD__ */ void spinup_ap(struct vcpu *newcpu, uint64_t rip) { int error; error = vcpu_reset(newcpu); assert(error == 0); #ifdef __FreeBSD__ spinup_ap_realmode(newcpu, rip); vm_resume_cpu(newcpu); #endif } /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012 NetApp, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _SPINUP_AP_H_ #define _SPINUP_AP_H_ void spinup_ap(struct vcpu *newcpu, uint64_t rip); #endif /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2014 Neel Natu * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. * * Copyright 2020 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bhyverun.h" #include "debug.h" /* * Using 'struct i386tss' is tempting but causes myriad sign extension * issues because all of its fields are defined as signed integers. */ struct tss32 { uint16_t tss_link; uint16_t rsvd1; uint32_t tss_esp0; uint16_t tss_ss0; uint16_t rsvd2; uint32_t tss_esp1; uint16_t tss_ss1; uint16_t rsvd3; uint32_t tss_esp2; uint16_t tss_ss2; uint16_t rsvd4; uint32_t tss_cr3; uint32_t tss_eip; uint32_t tss_eflags; uint32_t tss_eax; uint32_t tss_ecx; uint32_t tss_edx; uint32_t tss_ebx; uint32_t tss_esp; uint32_t tss_ebp; uint32_t tss_esi; uint32_t tss_edi; uint16_t tss_es; uint16_t rsvd5; uint16_t tss_cs; uint16_t rsvd6; uint16_t tss_ss; uint16_t rsvd7; uint16_t tss_ds; uint16_t rsvd8; uint16_t tss_fs; uint16_t rsvd9; uint16_t tss_gs; uint16_t rsvd10; uint16_t tss_ldt; uint16_t rsvd11; uint16_t tss_trap; uint16_t tss_iomap; }; static_assert(sizeof(struct tss32) == 104, "compile-time assertion failed"); #define SEL_START(sel) (((sel) & ~0x7)) #define SEL_LIMIT(sel) (((sel) | 0x7)) #define TSS_BUSY(type) (((type) & 0x2) != 0) static uint64_t GETREG(struct vcpu *vcpu, int reg) { uint64_t val; int error; error = vm_get_register(vcpu, reg, &val); assert(error == 0); return (val); } static void SETREG(struct vcpu *vcpu, int reg, uint64_t val) { int error; error = vm_set_register(vcpu, reg, val); assert(error == 0); } static struct seg_desc usd_to_seg_desc(struct user_segment_descriptor *usd) { struct seg_desc seg_desc; seg_desc.base = (u_int)USD_GETBASE(usd); if (usd->sd_gran) seg_desc.limit = (u_int)(USD_GETLIMIT(usd) << 12) | 0xfff; else seg_desc.limit = (u_int)USD_GETLIMIT(usd); seg_desc.access = usd->sd_type | usd->sd_dpl << 5 | usd->sd_p << 7; seg_desc.access |= usd->sd_xx << 12; seg_desc.access |= usd->sd_def32 << 14; seg_desc.access |= usd->sd_gran << 15; return (seg_desc); } /* * Inject an exception with an error code that is a segment selector. * The format of the error code is described in section 6.13, "Error Code", * Intel SDM volume 3. * * Bit 0 (EXT) denotes whether the exception occurred during delivery * of an external event like an interrupt. * * Bit 1 (IDT) indicates whether the selector points to a gate descriptor * in the IDT. * * Bit 2(GDT/LDT) has the usual interpretation of Table Indicator (TI). */ static void sel_exception(struct vcpu *vcpu, int vector, uint16_t sel, int ext) { /* * Bit 2 from the selector is retained as-is in the error code. * * Bit 1 can be safely cleared because none of the selectors * encountered during task switch emulation refer to a task * gate in the IDT. * * Bit 0 is set depending on the value of 'ext'. */ sel &= ~0x3; if (ext) sel |= 0x1; vm_inject_fault(vcpu, vector, 1, sel); } /* * Return 0 if the selector 'sel' in within the limits of the GDT/LDT * and non-zero otherwise. */ static int desc_table_limit_check(struct vcpu *vcpu, uint16_t sel) { uint64_t base; uint32_t limit, access; int error, reg; reg = ISLDT(sel) ? VM_REG_GUEST_LDTR : VM_REG_GUEST_GDTR; error = vm_get_desc(vcpu, reg, &base, &limit, &access); assert(error == 0); if (reg == VM_REG_GUEST_LDTR) { if (SEG_DESC_UNUSABLE(access) || !SEG_DESC_PRESENT(access)) return (-1); } if (limit < SEL_LIMIT(sel)) return (-1); else return (0); } /* * Read/write the segment descriptor 'desc' into the GDT/LDT slot referenced * by the selector 'sel'. * * Returns 0 on success. * Returns 1 if an exception was injected into the guest. * Returns -1 otherwise. */ static int desc_table_rw(struct vcpu *vcpu, struct vm_guest_paging *paging, uint16_t sel, struct user_segment_descriptor *desc, bool doread, int *faultptr) { struct iovec iov[2]; uint64_t base; uint32_t limit, access; int error, reg; reg = ISLDT(sel) ? VM_REG_GUEST_LDTR : VM_REG_GUEST_GDTR; error = vm_get_desc(vcpu, reg, &base, &limit, &access); assert(error == 0); assert(limit >= SEL_LIMIT(sel)); error = vm_copy_setup(vcpu, paging, base + SEL_START(sel), sizeof(*desc), doread ? PROT_READ : PROT_WRITE, iov, nitems(iov), faultptr); if (error || *faultptr) return (error); if (doread) vm_copyin(iov, desc, sizeof(*desc)); else vm_copyout(desc, iov, sizeof(*desc)); return (0); } static int desc_table_read(struct vcpu *vcpu, struct vm_guest_paging *paging, uint16_t sel, struct user_segment_descriptor *desc, int *faultptr) { return (desc_table_rw(vcpu, paging, sel, desc, true, faultptr)); } static int desc_table_write(struct vcpu *vcpu, struct vm_guest_paging *paging, uint16_t sel, struct user_segment_descriptor *desc, int *faultptr) { return (desc_table_rw(vcpu, paging, sel, desc, false, faultptr)); } /* * Read the TSS descriptor referenced by 'sel' into 'desc'. * * Returns 0 on success. * Returns 1 if an exception was injected into the guest. * Returns -1 otherwise. */ static int read_tss_descriptor(struct vcpu *vcpu, struct vm_task_switch *ts, uint16_t sel, struct user_segment_descriptor *desc, int *faultptr) { struct vm_guest_paging sup_paging; int error; assert(!ISLDT(sel)); assert(IDXSEL(sel) != 0); /* Fetch the new TSS descriptor */ if (desc_table_limit_check(vcpu, sel)) { if (ts->reason == TSR_IRET) sel_exception(vcpu, IDT_TS, sel, ts->ext); else sel_exception(vcpu, IDT_GP, sel, ts->ext); return (1); } sup_paging = ts->paging; sup_paging.cpl = 0; /* implicit supervisor mode */ error = desc_table_read(vcpu, &sup_paging, sel, desc, faultptr); return (error); } static bool code_desc(int sd_type) { /* code descriptor */ return ((sd_type & 0x18) == 0x18); } static bool stack_desc(int sd_type) { /* writable data descriptor */ return ((sd_type & 0x1A) == 0x12); } static bool data_desc(int sd_type) { /* data descriptor or a readable code descriptor */ return ((sd_type & 0x18) == 0x10 || (sd_type & 0x1A) == 0x1A); } static bool ldt_desc(int sd_type) { return (sd_type == SDT_SYSLDT); } /* * Validate the descriptor 'seg_desc' associated with 'segment'. */ static int validate_seg_desc(struct vcpu *vcpu, struct vm_task_switch *ts, int segment, struct seg_desc *seg_desc, int *faultptr) { struct vm_guest_paging sup_paging; struct user_segment_descriptor usd; int error, idtvec; int cpl, dpl, rpl; uint16_t sel, cs; bool ldtseg, codeseg, stackseg, dataseg, conforming; ldtseg = codeseg = stackseg = dataseg = false; switch (segment) { case VM_REG_GUEST_LDTR: ldtseg = true; break; case VM_REG_GUEST_CS: codeseg = true; break; case VM_REG_GUEST_SS: stackseg = true; break; case VM_REG_GUEST_DS: case VM_REG_GUEST_ES: case VM_REG_GUEST_FS: case VM_REG_GUEST_GS: dataseg = true; break; default: assert(0); } /* Get the segment selector */ sel = GETREG(vcpu, segment); /* LDT selector must point into the GDT */ if (ldtseg && ISLDT(sel)) { sel_exception(vcpu, IDT_TS, sel, ts->ext); return (1); } /* Descriptor table limit check */ if (desc_table_limit_check(vcpu, sel)) { sel_exception(vcpu, IDT_TS, sel, ts->ext); return (1); } /* NULL selector */ if (IDXSEL(sel) == 0) { /* Code and stack segment selectors cannot be NULL */ if (codeseg || stackseg) { sel_exception(vcpu, IDT_TS, sel, ts->ext); return (1); } seg_desc->base = 0; seg_desc->limit = 0; seg_desc->access = 0x10000; /* unusable */ return (0); } /* Read the descriptor from the GDT/LDT */ sup_paging = ts->paging; sup_paging.cpl = 0; /* implicit supervisor mode */ error = desc_table_read(vcpu, &sup_paging, sel, &usd, faultptr); if (error || *faultptr) return (error); /* Verify that the descriptor type is compatible with the segment */ if ((ldtseg && !ldt_desc(usd.sd_type)) || (codeseg && !code_desc(usd.sd_type)) || (dataseg && !data_desc(usd.sd_type)) || (stackseg && !stack_desc(usd.sd_type))) { sel_exception(vcpu, IDT_TS, sel, ts->ext); return (1); } /* Segment must be marked present */ if (!usd.sd_p) { if (ldtseg) idtvec = IDT_TS; else if (stackseg) idtvec = IDT_SS; else idtvec = IDT_NP; sel_exception(vcpu, idtvec, sel, ts->ext); return (1); } cs = GETREG(vcpu, VM_REG_GUEST_CS); cpl = cs & SEL_RPL_MASK; rpl = sel & SEL_RPL_MASK; dpl = usd.sd_dpl; if (stackseg && (rpl != cpl || dpl != cpl)) { sel_exception(vcpu, IDT_TS, sel, ts->ext); return (1); } if (codeseg) { conforming = (usd.sd_type & 0x4) ? true : false; if ((conforming && (cpl < dpl)) || (!conforming && (cpl != dpl))) { sel_exception(vcpu, IDT_TS, sel, ts->ext); return (1); } } if (dataseg) { /* * A data segment is always non-conforming except when it's * descriptor is a readable, conforming code segment. */ if (code_desc(usd.sd_type) && (usd.sd_type & 0x4) != 0) conforming = true; else conforming = false; if (!conforming && (rpl > dpl || cpl > dpl)) { sel_exception(vcpu, IDT_TS, sel, ts->ext); return (1); } } *seg_desc = usd_to_seg_desc(&usd); return (0); } static void tss32_save(struct vcpu *vcpu, struct vm_task_switch *task_switch, uint32_t eip, struct tss32 *tss, struct iovec *iov) { /* General purpose registers */ tss->tss_eax = GETREG(vcpu, VM_REG_GUEST_RAX); tss->tss_ecx = GETREG(vcpu, VM_REG_GUEST_RCX); tss->tss_edx = GETREG(vcpu, VM_REG_GUEST_RDX); tss->tss_ebx = GETREG(vcpu, VM_REG_GUEST_RBX); tss->tss_esp = GETREG(vcpu, VM_REG_GUEST_RSP); tss->tss_ebp = GETREG(vcpu, VM_REG_GUEST_RBP); tss->tss_esi = GETREG(vcpu, VM_REG_GUEST_RSI); tss->tss_edi = GETREG(vcpu, VM_REG_GUEST_RDI); /* Segment selectors */ tss->tss_es = GETREG(vcpu, VM_REG_GUEST_ES); tss->tss_cs = GETREG(vcpu, VM_REG_GUEST_CS); tss->tss_ss = GETREG(vcpu, VM_REG_GUEST_SS); tss->tss_ds = GETREG(vcpu, VM_REG_GUEST_DS); tss->tss_fs = GETREG(vcpu, VM_REG_GUEST_FS); tss->tss_gs = GETREG(vcpu, VM_REG_GUEST_GS); /* eflags and eip */ tss->tss_eflags = GETREG(vcpu, VM_REG_GUEST_RFLAGS); if (task_switch->reason == TSR_IRET) tss->tss_eflags &= ~PSL_NT; tss->tss_eip = eip; /* Copy updated old TSS into guest memory */ vm_copyout(tss, iov, sizeof(struct tss32)); } static void update_seg_desc(struct vcpu *vcpu, int reg, struct seg_desc *sd) { int error; error = vm_set_desc(vcpu, reg, sd->base, sd->limit, sd->access); assert(error == 0); } /* * Update the vcpu registers to reflect the state of the new task. */ static int tss32_restore(struct vmctx *ctx, struct vcpu *vcpu, struct vm_task_switch *ts, uint16_t ot_sel, struct tss32 *tss, struct iovec *iov, int *faultptr) { struct seg_desc seg_desc, seg_desc2; uint64_t *pdpte, maxphyaddr, reserved; uint32_t eflags; int error, i; bool nested; nested = false; if (ts->reason != TSR_IRET && ts->reason != TSR_JMP) { tss->tss_link = ot_sel; nested = true; } eflags = tss->tss_eflags; if (nested) eflags |= PSL_NT; /* LDTR */ SETREG(vcpu, VM_REG_GUEST_LDTR, tss->tss_ldt); /* PBDR */ if (ts->paging.paging_mode != PAGING_MODE_FLAT) { if (ts->paging.paging_mode == PAGING_MODE_PAE) { /* * XXX Assuming 36-bit MAXPHYADDR. */ maxphyaddr = (1UL << 36) - 1; pdpte = paddr_guest2host(ctx, tss->tss_cr3 & ~0x1f, 32); for (i = 0; i < 4; i++) { /* Check reserved bits if the PDPTE is valid */ if (!(pdpte[i] & 0x1)) continue; /* * Bits 2:1, 8:5 and bits above the processor's * maximum physical address are reserved. */ reserved = ~maxphyaddr | 0x1E6; if (pdpte[i] & reserved) { vm_inject_gp(vcpu); return (1); } } SETREG(vcpu, VM_REG_GUEST_PDPTE0, pdpte[0]); SETREG(vcpu, VM_REG_GUEST_PDPTE1, pdpte[1]); SETREG(vcpu, VM_REG_GUEST_PDPTE2, pdpte[2]); SETREG(vcpu, VM_REG_GUEST_PDPTE3, pdpte[3]); } SETREG(vcpu, VM_REG_GUEST_CR3, tss->tss_cr3); ts->paging.cr3 = tss->tss_cr3; } /* eflags and eip */ SETREG(vcpu, VM_REG_GUEST_RFLAGS, eflags); SETREG(vcpu, VM_REG_GUEST_RIP, tss->tss_eip); /* General purpose registers */ SETREG(vcpu, VM_REG_GUEST_RAX, tss->tss_eax); SETREG(vcpu, VM_REG_GUEST_RCX, tss->tss_ecx); SETREG(vcpu, VM_REG_GUEST_RDX, tss->tss_edx); SETREG(vcpu, VM_REG_GUEST_RBX, tss->tss_ebx); SETREG(vcpu, VM_REG_GUEST_RSP, tss->tss_esp); SETREG(vcpu, VM_REG_GUEST_RBP, tss->tss_ebp); SETREG(vcpu, VM_REG_GUEST_RSI, tss->tss_esi); SETREG(vcpu, VM_REG_GUEST_RDI, tss->tss_edi); /* Segment selectors */ SETREG(vcpu, VM_REG_GUEST_ES, tss->tss_es); SETREG(vcpu, VM_REG_GUEST_CS, tss->tss_cs); SETREG(vcpu, VM_REG_GUEST_SS, tss->tss_ss); SETREG(vcpu, VM_REG_GUEST_DS, tss->tss_ds); SETREG(vcpu, VM_REG_GUEST_FS, tss->tss_fs); SETREG(vcpu, VM_REG_GUEST_GS, tss->tss_gs); /* * If this is a nested task then write out the new TSS to update * the previous link field. */ if (nested) vm_copyout(tss, iov, sizeof(*tss)); /* Validate segment descriptors */ error = validate_seg_desc(vcpu, ts, VM_REG_GUEST_LDTR, &seg_desc, faultptr); if (error || *faultptr) return (error); update_seg_desc(vcpu, VM_REG_GUEST_LDTR, &seg_desc); /* * Section "Checks on Guest Segment Registers", Intel SDM, Vol 3. * * The SS and CS attribute checks on VM-entry are inter-dependent so * we need to make sure that both segments are valid before updating * either of them. This ensures that the VMCS state can pass the * VM-entry checks so the guest can handle any exception injected * during task switch emulation. */ error = validate_seg_desc(vcpu, ts, VM_REG_GUEST_CS, &seg_desc, faultptr); if (error || *faultptr) return (error); error = validate_seg_desc(vcpu, ts, VM_REG_GUEST_SS, &seg_desc2, faultptr); if (error || *faultptr) return (error); update_seg_desc(vcpu, VM_REG_GUEST_CS, &seg_desc); update_seg_desc(vcpu, VM_REG_GUEST_SS, &seg_desc2); ts->paging.cpl = tss->tss_cs & SEL_RPL_MASK; error = validate_seg_desc(vcpu, ts, VM_REG_GUEST_DS, &seg_desc, faultptr); if (error || *faultptr) return (error); update_seg_desc(vcpu, VM_REG_GUEST_DS, &seg_desc); error = validate_seg_desc(vcpu, ts, VM_REG_GUEST_ES, &seg_desc, faultptr); if (error || *faultptr) return (error); update_seg_desc(vcpu, VM_REG_GUEST_ES, &seg_desc); error = validate_seg_desc(vcpu, ts, VM_REG_GUEST_FS, &seg_desc, faultptr); if (error || *faultptr) return (error); update_seg_desc(vcpu, VM_REG_GUEST_FS, &seg_desc); error = validate_seg_desc(vcpu, ts, VM_REG_GUEST_GS, &seg_desc, faultptr); if (error || *faultptr) return (error); update_seg_desc(vcpu, VM_REG_GUEST_GS, &seg_desc); return (0); } /* * Copy of vie_alignment_check() from vmm_instruction_emul.c */ static int alignment_check(int cpl, int size, uint64_t cr0, uint64_t rf, uint64_t gla) { assert(size == 1 || size == 2 || size == 4 || size == 8); assert(cpl >= 0 && cpl <= 3); if (cpl != 3 || (cr0 & CR0_AM) == 0 || (rf & PSL_AC) == 0) return (0); return ((gla & (size - 1)) ? 1 : 0); } /* * Copy of vie_size2mask() from vmm_instruction_emul.c */ static uint64_t size2mask(int size) { switch (size) { case 1: return (0xff); case 2: return (0xffff); case 4: return (0xffffffff); case 8: return (0xffffffffffffffff); default: assert(0); /* not reached */ return (0); } } /* * Copy of vie_calculate_gla() from vmm_instruction_emul.c */ static int calculate_gla(enum vm_cpu_mode cpu_mode, enum vm_reg_name seg, struct seg_desc *desc, uint64_t offset, int length, int addrsize, int prot, uint64_t *gla) { uint64_t firstoff, low_limit, high_limit, segbase; int glasize, type; assert(seg >= VM_REG_GUEST_ES && seg <= VM_REG_GUEST_GS); assert((length == 1 || length == 2 || length == 4 || length == 8)); assert((prot & ~(PROT_READ | PROT_WRITE)) == 0); firstoff = offset; if (cpu_mode == CPU_MODE_64BIT) { assert(addrsize == 4 || addrsize == 8); glasize = 8; } else { assert(addrsize == 2 || addrsize == 4); glasize = 4; /* * If the segment selector is loaded with a NULL selector * then the descriptor is unusable and attempting to use * it results in a #GP(0). */ if (SEG_DESC_UNUSABLE(desc->access)) return (-1); /* * The processor generates a #NP exception when a segment * register is loaded with a selector that points to a * descriptor that is not present. If this was the case then * it would have been checked before the VM-exit. */ assert(SEG_DESC_PRESENT(desc->access)); /* * The descriptor type must indicate a code/data segment. */ type = SEG_DESC_TYPE(desc->access); assert(type >= 16 && type <= 31); if (prot & PROT_READ) { /* #GP on a read access to a exec-only code segment */ if ((type & 0xA) == 0x8) return (-1); } if (prot & PROT_WRITE) { /* * #GP on a write access to a code segment or a * read-only data segment. */ if (type & 0x8) /* code segment */ return (-1); if ((type & 0xA) == 0) /* read-only data seg */ return (-1); } /* * 'desc->limit' is fully expanded taking granularity into * account. */ if ((type & 0xC) == 0x4) { /* expand-down data segment */ low_limit = desc->limit + 1; high_limit = SEG_DESC_DEF32(desc->access) ? 0xffffffff : 0xffff; } else { /* code segment or expand-up data segment */ low_limit = 0; high_limit = desc->limit; } while (length > 0) { offset &= size2mask(addrsize); if (offset < low_limit || offset > high_limit) return (-1); offset++; length--; } } /* * In 64-bit mode all segments except %fs and %gs have a segment * base address of 0. */ if (cpu_mode == CPU_MODE_64BIT && seg != VM_REG_GUEST_FS && seg != VM_REG_GUEST_GS) { segbase = 0; } else { segbase = desc->base; } /* * Truncate 'firstoff' to the effective address size before adding * it to the segment base. */ firstoff &= size2mask(addrsize); *gla = (segbase + firstoff) & size2mask(glasize); return (0); } /* * Push an error code on the stack of the new task. This is needed if the * task switch was triggered by a hardware exception that causes an error * code to be saved (e.g. #PF). */ static int push_errcode(struct vcpu *vcpu, struct vm_guest_paging *paging, int task_type, uint32_t errcode, int *faultptr) { struct iovec iov[2]; struct seg_desc seg_desc; int stacksize, bytes, error; uint64_t gla, cr0, rflags; uint32_t esp; uint16_t stacksel; *faultptr = 0; cr0 = GETREG(vcpu, VM_REG_GUEST_CR0); rflags = GETREG(vcpu, VM_REG_GUEST_RFLAGS); stacksel = GETREG(vcpu, VM_REG_GUEST_SS); error = vm_get_desc(vcpu, VM_REG_GUEST_SS, &seg_desc.base, &seg_desc.limit, &seg_desc.access); assert(error == 0); /* * Section "Error Code" in the Intel SDM vol 3: the error code is * pushed on the stack as a doubleword or word (depending on the * default interrupt, trap or task gate size). */ if (task_type == SDT_SYS386BSY || task_type == SDT_SYS386TSS) bytes = 4; else bytes = 2; /* * PUSH instruction from Intel SDM vol 2: the 'B' flag in the * stack-segment descriptor determines the size of the stack * pointer outside of 64-bit mode. */ if (SEG_DESC_DEF32(seg_desc.access)) stacksize = 4; else stacksize = 2; esp = GETREG(vcpu, VM_REG_GUEST_RSP); esp -= bytes; if (calculate_gla(paging->cpu_mode, VM_REG_GUEST_SS, &seg_desc, esp, bytes, stacksize, PROT_WRITE, &gla)) { sel_exception(vcpu, IDT_SS, stacksel, 1); *faultptr = 1; return (0); } if (alignment_check(paging->cpl, bytes, cr0, rflags, gla)) { vm_inject_ac(vcpu, 1); *faultptr = 1; return (0); } error = vm_copy_setup(vcpu, paging, gla, bytes, PROT_WRITE, iov, nitems(iov), faultptr); if (error || *faultptr) return (error); vm_copyout(&errcode, iov, bytes); SETREG(vcpu, VM_REG_GUEST_RSP, esp); return (0); } /* * Evaluate return value from helper functions and potentially return to * the VM run loop. */ #define CHKERR(error,fault) \ do { \ assert((error == 0) || (error == EFAULT)); \ if (error) \ return (VMEXIT_ABORT); \ else if (fault) \ return (VMEXIT_CONTINUE); \ } while (0) int vmexit_task_switch(struct vmctx *ctx, struct vcpu *vcpu, struct vm_exit *vmexit) { struct seg_desc nt; struct tss32 oldtss, newtss; struct vm_task_switch *task_switch; struct vm_guest_paging *paging, sup_paging; struct user_segment_descriptor nt_desc, ot_desc; struct iovec nt_iov[2], ot_iov[2]; uint64_t cr0, ot_base; uint32_t eip, ot_lim, access; int error, ext, fault, minlimit, nt_type, ot_type; enum task_switch_reason reason; uint16_t nt_sel, ot_sel; task_switch = &vmexit->u.task_switch; nt_sel = task_switch->tsssel; ext = vmexit->u.task_switch.ext; reason = vmexit->u.task_switch.reason; paging = &vmexit->u.task_switch.paging; assert(paging->cpu_mode == CPU_MODE_PROTECTED); /* * Calculate the instruction pointer to store in the old TSS. */ eip = vmexit->rip + vmexit->inst_length; /* * Section 4.6, "Access Rights" in Intel SDM Vol 3. * The following page table accesses are implicitly supervisor mode: * - accesses to GDT or LDT to load segment descriptors * - accesses to the task state segment during task switch */ sup_paging = *paging; sup_paging.cpl = 0; /* implicit supervisor mode */ /* Fetch the new TSS descriptor */ error = read_tss_descriptor(vcpu, task_switch, nt_sel, &nt_desc, &fault); CHKERR(error, fault); nt = usd_to_seg_desc(&nt_desc); /* Verify the type of the new TSS */ nt_type = SEG_DESC_TYPE(nt.access); if (nt_type != SDT_SYS386BSY && nt_type != SDT_SYS386TSS && nt_type != SDT_SYS286BSY && nt_type != SDT_SYS286TSS) { sel_exception(vcpu, IDT_TS, nt_sel, ext); goto done; } /* TSS descriptor must have present bit set */ if (!SEG_DESC_PRESENT(nt.access)) { sel_exception(vcpu, IDT_NP, nt_sel, ext); goto done; } /* * TSS must have a minimum length of 104 bytes for a 32-bit TSS and * 44 bytes for a 16-bit TSS. */ if (nt_type == SDT_SYS386BSY || nt_type == SDT_SYS386TSS) minlimit = 104 - 1; else if (nt_type == SDT_SYS286BSY || nt_type == SDT_SYS286TSS) minlimit = 44 - 1; else minlimit = 0; assert(minlimit > 0); if (nt.limit < (unsigned int)minlimit) { sel_exception(vcpu, IDT_TS, nt_sel, ext); goto done; } /* TSS must be busy if task switch is due to IRET */ if (reason == TSR_IRET && !TSS_BUSY(nt_type)) { sel_exception(vcpu, IDT_TS, nt_sel, ext); goto done; } /* * TSS must be available (not busy) if task switch reason is * CALL, JMP, exception or interrupt. */ if (reason != TSR_IRET && TSS_BUSY(nt_type)) { sel_exception(vcpu, IDT_GP, nt_sel, ext); goto done; } /* Fetch the new TSS */ error = vm_copy_setup(vcpu, &sup_paging, nt.base, minlimit + 1, PROT_READ | PROT_WRITE, nt_iov, nitems(nt_iov), &fault); CHKERR(error, fault); vm_copyin(nt_iov, &newtss, minlimit + 1); /* Get the old TSS selector from the guest's task register */ ot_sel = GETREG(vcpu, VM_REG_GUEST_TR); if (ISLDT(ot_sel) || IDXSEL(ot_sel) == 0) { /* * This might happen if a task switch was attempted without * ever loading the task register with LTR. In this case the * TR would contain the values from power-on: * (sel = 0, base = 0, limit = 0xffff). */ sel_exception(vcpu, IDT_TS, ot_sel, task_switch->ext); goto done; } /* Get the old TSS base and limit from the guest's task register */ error = vm_get_desc(vcpu, VM_REG_GUEST_TR, &ot_base, &ot_lim, &access); assert(error == 0); assert(!SEG_DESC_UNUSABLE(access) && SEG_DESC_PRESENT(access)); ot_type = SEG_DESC_TYPE(access); assert(ot_type == SDT_SYS386BSY || ot_type == SDT_SYS286BSY); /* Fetch the old TSS descriptor */ error = read_tss_descriptor(vcpu, task_switch, ot_sel, &ot_desc, &fault); CHKERR(error, fault); /* Get the old TSS */ error = vm_copy_setup(vcpu, &sup_paging, ot_base, minlimit + 1, PROT_READ | PROT_WRITE, ot_iov, nitems(ot_iov), &fault); CHKERR(error, fault); vm_copyin(ot_iov, &oldtss, minlimit + 1); /* * Clear the busy bit in the old TSS descriptor if the task switch * due to an IRET or JMP instruction. */ if (reason == TSR_IRET || reason == TSR_JMP) { ot_desc.sd_type &= ~0x2; error = desc_table_write(vcpu, &sup_paging, ot_sel, &ot_desc, &fault); CHKERR(error, fault); } if (nt_type == SDT_SYS286BSY || nt_type == SDT_SYS286TSS) { EPRINTLN("Task switch to 16-bit TSS not supported"); return (VMEXIT_ABORT); } /* Save processor state in old TSS */ tss32_save(vcpu, task_switch, eip, &oldtss, ot_iov); /* * If the task switch was triggered for any reason other than IRET * then set the busy bit in the new TSS descriptor. */ if (reason != TSR_IRET) { nt_desc.sd_type |= 0x2; error = desc_table_write(vcpu, &sup_paging, nt_sel, &nt_desc, &fault); CHKERR(error, fault); } /* Update task register to point at the new TSS */ SETREG(vcpu, VM_REG_GUEST_TR, nt_sel); /* Update the hidden descriptor state of the task register */ nt = usd_to_seg_desc(&nt_desc); update_seg_desc(vcpu, VM_REG_GUEST_TR, &nt); /* Set CR0.TS */ cr0 = GETREG(vcpu, VM_REG_GUEST_CR0); SETREG(vcpu, VM_REG_GUEST_CR0, cr0 | CR0_TS); /* * We are now committed to the task switch. Any exceptions encountered * after this point will be handled in the context of the new task and * the saved instruction pointer will belong to the new task. */ error = vm_set_register(vcpu, VM_REG_GUEST_RIP, newtss.tss_eip); assert(error == 0); /* Load processor state from new TSS */ error = tss32_restore(ctx, vcpu, task_switch, ot_sel, &newtss, nt_iov, &fault); CHKERR(error, fault); /* * Section "Interrupt Tasks" in Intel SDM, Vol 3: if an exception * caused an error code to be generated, this error code is copied * to the stack of the new task. */ if (task_switch->errcode_valid) { assert(task_switch->ext); assert(task_switch->reason == TSR_IDT_GATE); error = push_errcode(vcpu, &task_switch->paging, nt_type, task_switch->errcode, &fault); CHKERR(error, fault); } /* * Treatment of virtual-NMI blocking if NMI is delivered through * a task gate. * * Section "Architectural State Before A VM Exit", Intel SDM, Vol3: * If the virtual NMIs VM-execution control is 1, VM entry injects * an NMI, and delivery of the NMI causes a task switch that causes * a VM exit, virtual-NMI blocking is in effect before the VM exit * commences. * * Thus, virtual-NMI blocking is in effect at the time of the task * switch VM exit. */ /* * Treatment of virtual-NMI unblocking on IRET from NMI handler task. * * Section "Changes to Instruction Behavior in VMX Non-Root Operation" * If "virtual NMIs" control is 1 IRET removes any virtual-NMI blocking. * This unblocking of virtual-NMI occurs even if IRET causes a fault. * * Thus, virtual-NMI blocking is cleared at the time of the task switch * VM exit. */ /* * If the task switch was triggered by an event delivered through * the IDT then extinguish the pending event from the vcpu's * exitintinfo. */ if (task_switch->reason == TSR_IDT_GATE) { error = vm_set_intinfo(vcpu, 0); assert(error == 0); } /* * XXX should inject debug exception if 'T' bit is 1 */ done: return (VMEXIT_CONTINUE); } /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2015 Tycho Nightingale * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright 2018 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include "bhyvegc.h" #include "console.h" #include "inout.h" #include "mem.h" #include "vga.h" #define KB (1024UL) #define MB (1024 * 1024UL) struct vga_softc { struct mem_range mr; struct bhyvegc *gc; int gc_width; int gc_height; struct bhyvegc_image *gc_image; uint8_t *vga_ram; /* * General registers */ uint8_t vga_misc; uint8_t vga_sts1; /* * Sequencer */ struct { int seq_index; uint8_t seq_reset; uint8_t seq_clock_mode; int seq_cm_dots; uint8_t seq_map_mask; uint8_t seq_cmap_sel; int seq_cmap_pri_off; int seq_cmap_sec_off; uint8_t seq_mm; } vga_seq; /* * CRT Controller */ struct { int crtc_index; uint8_t crtc_mode_ctrl; uint8_t crtc_horiz_total; uint8_t crtc_horiz_disp_end; uint8_t crtc_start_horiz_blank; uint8_t crtc_end_horiz_blank; uint8_t crtc_start_horiz_retrace; uint8_t crtc_end_horiz_retrace; uint8_t crtc_vert_total; uint8_t crtc_overflow; uint8_t crtc_present_row_scan; uint8_t crtc_max_scan_line; uint8_t crtc_cursor_start; uint8_t crtc_cursor_on; uint8_t crtc_cursor_end; uint8_t crtc_start_addr_high; uint8_t crtc_start_addr_low; uint16_t crtc_start_addr; uint8_t crtc_cursor_loc_low; uint8_t crtc_cursor_loc_high; uint16_t crtc_cursor_loc; uint8_t crtc_vert_retrace_start; uint8_t crtc_vert_retrace_end; uint8_t crtc_vert_disp_end; uint8_t crtc_offset; uint8_t crtc_underline_loc; uint8_t crtc_start_vert_blank; uint8_t crtc_end_vert_blank; uint8_t crtc_line_compare; } vga_crtc; /* * Graphics Controller */ struct { int gc_index; uint8_t gc_set_reset; uint8_t gc_enb_set_reset; uint8_t gc_color_compare; uint8_t gc_rotate; uint8_t gc_op; uint8_t gc_read_map_sel; uint8_t gc_mode; bool gc_mode_c4; /* chain 4 */ bool gc_mode_oe; /* odd/even */ uint8_t gc_mode_rm; /* read mode */ uint8_t gc_mode_wm; /* write mode */ uint8_t gc_misc; uint8_t gc_misc_gm; /* graphics mode */ uint8_t gc_misc_mm; /* memory map */ uint8_t gc_color_dont_care; uint8_t gc_bit_mask; uint8_t gc_latch0; uint8_t gc_latch1; uint8_t gc_latch2; uint8_t gc_latch3; } vga_gc; /* * Attribute Controller */ struct { int atc_flipflop; int atc_index; uint8_t atc_palette[16]; uint8_t atc_mode; uint8_t atc_overscan_color; uint8_t atc_color_plane_enb; uint8_t atc_horiz_pixel_panning; uint8_t atc_color_select; uint8_t atc_color_select_45; uint8_t atc_color_select_67; } vga_atc; /* * DAC */ struct { uint8_t dac_state; uint8_t dac_rd_index; uint8_t dac_rd_subindex; uint8_t dac_wr_index; uint8_t dac_wr_subindex; uint8_t dac_palette[3 * 256]; uint32_t dac_palette_rgb[256]; } vga_dac; }; static bool vga_in_reset(struct vga_softc *sc) { return (((sc->vga_seq.seq_clock_mode & SEQ_CM_SO) != 0) || ((sc->vga_seq.seq_reset & SEQ_RESET_ASYNC) == 0) || ((sc->vga_seq.seq_reset & SEQ_RESET_SYNC) == 0) || ((sc->vga_crtc.crtc_mode_ctrl & CRTC_MC_TE) == 0)); } static void vga_check_size(struct bhyvegc *gc, struct vga_softc *sc) { int old_width, old_height; if (vga_in_reset(sc)) return; //old_width = sc->gc_width; //old_height = sc->gc_height; old_width = sc->gc_image->width; old_height = sc->gc_image->height; /* * Horizontal Display End: For text modes this is the number * of characters. For graphics modes this is the number of * pixels per scanlines divided by the number of pixels per * character clock. */ sc->gc_width = (sc->vga_crtc.crtc_horiz_disp_end + 1) * sc->vga_seq.seq_cm_dots; sc->gc_height = (sc->vga_crtc.crtc_vert_disp_end | (((sc->vga_crtc.crtc_overflow & CRTC_OF_VDE8) >> CRTC_OF_VDE8_SHIFT) << 8) | (((sc->vga_crtc.crtc_overflow & CRTC_OF_VDE9) >> CRTC_OF_VDE9_SHIFT) << 9)) + 1; if (old_width != sc->gc_width || old_height != sc->gc_height) bhyvegc_resize(gc, sc->gc_width, sc->gc_height); } static uint32_t vga_get_pixel(struct vga_softc *sc, int x, int y) { int offset; int bit; uint8_t data; uint8_t idx; offset = (y * sc->gc_width / 8) + (x / 8); bit = 7 - (x % 8); data = (((sc->vga_ram[offset + 0 * 64*KB] >> bit) & 0x1) << 0) | (((sc->vga_ram[offset + 1 * 64*KB] >> bit) & 0x1) << 1) | (((sc->vga_ram[offset + 2 * 64*KB] >> bit) & 0x1) << 2) | (((sc->vga_ram[offset + 3 * 64*KB] >> bit) & 0x1) << 3); data &= sc->vga_atc.atc_color_plane_enb; if (sc->vga_atc.atc_mode & ATC_MC_IPS) { idx = sc->vga_atc.atc_palette[data] & 0x0f; idx |= sc->vga_atc.atc_color_select_45; } else { idx = sc->vga_atc.atc_palette[data]; } idx |= sc->vga_atc.atc_color_select_67; return (sc->vga_dac.dac_palette_rgb[idx]); } static void vga_render_graphics(struct vga_softc *sc) { int x, y; for (y = 0; y < sc->gc_height; y++) { for (x = 0; x < sc->gc_width; x++) { int offset; offset = y * sc->gc_width + x; sc->gc_image->data[offset] = vga_get_pixel(sc, x, y); } } } static uint32_t vga_get_text_pixel(struct vga_softc *sc, int x, int y) { int dots, offset, bit, font_offset; uint8_t ch, attr, font; uint8_t idx; dots = sc->vga_seq.seq_cm_dots; offset = 2 * sc->vga_crtc.crtc_start_addr; offset += (y / 16 * sc->gc_width / dots) * 2 + (x / dots) * 2; bit = 7 - (x % dots > 7 ? 7 : x % dots); ch = sc->vga_ram[offset + 0 * 64*KB]; attr = sc->vga_ram[offset + 1 * 64*KB]; if (sc->vga_crtc.crtc_cursor_on && (offset == (sc->vga_crtc.crtc_cursor_loc * 2)) && ((y % 16) >= (sc->vga_crtc.crtc_cursor_start & CRTC_CS_CS)) && ((y % 16) <= (sc->vga_crtc.crtc_cursor_end & CRTC_CE_CE))) { idx = sc->vga_atc.atc_palette[attr & 0xf]; return (sc->vga_dac.dac_palette_rgb[idx]); } if ((sc->vga_seq.seq_mm & SEQ_MM_EM) && sc->vga_seq.seq_cmap_pri_off != sc->vga_seq.seq_cmap_sec_off) { if (attr & 0x8) font_offset = sc->vga_seq.seq_cmap_pri_off + (ch << 5) + y % 16; else font_offset = sc->vga_seq.seq_cmap_sec_off + (ch << 5) + y % 16; attr &= ~0x8; } else { font_offset = (ch << 5) + y % 16; } font = sc->vga_ram[font_offset + 2 * 64*KB]; if (font & (1 << bit)) idx = sc->vga_atc.atc_palette[attr & 0xf]; else idx = sc->vga_atc.atc_palette[attr >> 4]; return (sc->vga_dac.dac_palette_rgb[idx]); } static void vga_render_text(struct vga_softc *sc) { int x, y; for (y = 0; y < sc->gc_height; y++) { for (x = 0; x < sc->gc_width; x++) { int offset; offset = y * sc->gc_width + x; sc->gc_image->data[offset] = vga_get_text_pixel(sc, x, y); } } } void vga_render(struct bhyvegc *gc, void *arg) { struct vga_softc *sc = arg; vga_check_size(gc, sc); if (vga_in_reset(sc)) { memset(sc->gc_image->data, 0, sc->gc_image->width * sc->gc_image->height * sizeof (uint32_t)); return; } if (sc->vga_gc.gc_misc_gm && (sc->vga_atc.atc_mode & ATC_MC_GA)) vga_render_graphics(sc); else vga_render_text(sc); } static uint64_t vga_mem_rd_handler(uint64_t addr, void *arg1) { struct vga_softc *sc = arg1; uint8_t map_sel; int offset; offset = addr; switch (sc->vga_gc.gc_misc_mm) { case 0x0: /* * extended mode: base 0xa0000 size 128k */ offset -=0xa0000; offset &= (128 * KB - 1); break; case 0x1: /* * EGA/VGA mode: base 0xa0000 size 64k */ offset -=0xa0000; offset &= (64 * KB - 1); break; case 0x2: /* * monochrome text mode: base 0xb0000 size 32kb */ #ifdef __FreeBSD__ assert(0); #else abort(); #endif case 0x3: /* * color text mode and CGA: base 0xb8000 size 32kb */ offset -=0xb8000; offset &= (32 * KB - 1); break; } /* Fill latches. */ sc->vga_gc.gc_latch0 = sc->vga_ram[offset + 0*64*KB]; sc->vga_gc.gc_latch1 = sc->vga_ram[offset + 1*64*KB]; sc->vga_gc.gc_latch2 = sc->vga_ram[offset + 2*64*KB]; sc->vga_gc.gc_latch3 = sc->vga_ram[offset + 3*64*KB]; if (sc->vga_gc.gc_mode_rm) { /* read mode 1 */ assert(0); } map_sel = sc->vga_gc.gc_read_map_sel; if (sc->vga_gc.gc_mode_oe) { map_sel |= (offset & 1); offset &= ~1; } /* read mode 0: return the byte from the selected plane. */ offset += map_sel * 64*KB; return (sc->vga_ram[offset]); } static void vga_mem_wr_handler(uint64_t addr, uint8_t val, void *arg1) { struct vga_softc *sc = arg1; uint8_t c0, c1, c2, c3; uint8_t m0, m1, m2, m3; uint8_t set_reset; uint8_t enb_set_reset; uint8_t mask; int offset; offset = addr; switch (sc->vga_gc.gc_misc_mm) { case 0x0: /* * extended mode: base 0xa0000 size 128kb */ offset -=0xa0000; offset &= (128 * KB - 1); break; case 0x1: /* * EGA/VGA mode: base 0xa0000 size 64kb */ offset -=0xa0000; offset &= (64 * KB - 1); break; case 0x2: /* * monochrome text mode: base 0xb0000 size 32kb */ #ifdef __FreeBSD__ assert(0); #else abort(); #endif case 0x3: /* * color text mode and CGA: base 0xb8000 size 32kb */ offset -=0xb8000; offset &= (32 * KB - 1); break; } set_reset = sc->vga_gc.gc_set_reset; enb_set_reset = sc->vga_gc.gc_enb_set_reset; c0 = sc->vga_gc.gc_latch0; c1 = sc->vga_gc.gc_latch1; c2 = sc->vga_gc.gc_latch2; c3 = sc->vga_gc.gc_latch3; switch (sc->vga_gc.gc_mode_wm) { case 0: /* write mode 0 */ mask = sc->vga_gc.gc_bit_mask; val = (val >> sc->vga_gc.gc_rotate) | (val << (8 - sc->vga_gc.gc_rotate)); switch (sc->vga_gc.gc_op) { case 0x00: /* replace */ m0 = (set_reset & 1) ? mask : 0x00; m1 = (set_reset & 2) ? mask : 0x00; m2 = (set_reset & 4) ? mask : 0x00; m3 = (set_reset & 8) ? mask : 0x00; c0 = (enb_set_reset & 1) ? (c0 & ~mask) : (val & mask); c1 = (enb_set_reset & 2) ? (c1 & ~mask) : (val & mask); c2 = (enb_set_reset & 4) ? (c2 & ~mask) : (val & mask); c3 = (enb_set_reset & 8) ? (c3 & ~mask) : (val & mask); c0 |= m0; c1 |= m1; c2 |= m2; c3 |= m3; break; case 0x08: /* AND */ m0 = set_reset & 1 ? 0xff : ~mask; m1 = set_reset & 2 ? 0xff : ~mask; m2 = set_reset & 4 ? 0xff : ~mask; m3 = set_reset & 8 ? 0xff : ~mask; c0 = enb_set_reset & 1 ? c0 & m0 : val & m0; c1 = enb_set_reset & 2 ? c1 & m1 : val & m1; c2 = enb_set_reset & 4 ? c2 & m2 : val & m2; c3 = enb_set_reset & 8 ? c3 & m3 : val & m3; break; case 0x10: /* OR */ m0 = set_reset & 1 ? mask : 0x00; m1 = set_reset & 2 ? mask : 0x00; m2 = set_reset & 4 ? mask : 0x00; m3 = set_reset & 8 ? mask : 0x00; c0 = enb_set_reset & 1 ? c0 | m0 : val | m0; c1 = enb_set_reset & 2 ? c1 | m1 : val | m1; c2 = enb_set_reset & 4 ? c2 | m2 : val | m2; c3 = enb_set_reset & 8 ? c3 | m3 : val | m3; break; case 0x18: /* XOR */ m0 = set_reset & 1 ? mask : 0x00; m1 = set_reset & 2 ? mask : 0x00; m2 = set_reset & 4 ? mask : 0x00; m3 = set_reset & 8 ? mask : 0x00; c0 = enb_set_reset & 1 ? c0 ^ m0 : val ^ m0; c1 = enb_set_reset & 2 ? c1 ^ m1 : val ^ m1; c2 = enb_set_reset & 4 ? c2 ^ m2 : val ^ m2; c3 = enb_set_reset & 8 ? c3 ^ m3 : val ^ m3; break; } break; case 1: /* write mode 1 */ break; case 2: /* write mode 2 */ mask = sc->vga_gc.gc_bit_mask; switch (sc->vga_gc.gc_op) { case 0x00: /* replace */ m0 = (val & 1 ? 0xff : 0x00) & mask; m1 = (val & 2 ? 0xff : 0x00) & mask; m2 = (val & 4 ? 0xff : 0x00) & mask; m3 = (val & 8 ? 0xff : 0x00) & mask; c0 &= ~mask; c1 &= ~mask; c2 &= ~mask; c3 &= ~mask; c0 |= m0; c1 |= m1; c2 |= m2; c3 |= m3; break; case 0x08: /* AND */ m0 = (val & 1 ? 0xff : 0x00) | ~mask; m1 = (val & 2 ? 0xff : 0x00) | ~mask; m2 = (val & 4 ? 0xff : 0x00) | ~mask; m3 = (val & 8 ? 0xff : 0x00) | ~mask; c0 &= m0; c1 &= m1; c2 &= m2; c3 &= m3; break; case 0x10: /* OR */ m0 = (val & 1 ? 0xff : 0x00) & mask; m1 = (val & 2 ? 0xff : 0x00) & mask; m2 = (val & 4 ? 0xff : 0x00) & mask; m3 = (val & 8 ? 0xff : 0x00) & mask; c0 |= m0; c1 |= m1; c2 |= m2; c3 |= m3; break; case 0x18: /* XOR */ m0 = (val & 1 ? 0xff : 0x00) & mask; m1 = (val & 2 ? 0xff : 0x00) & mask; m2 = (val & 4 ? 0xff : 0x00) & mask; m3 = (val & 8 ? 0xff : 0x00) & mask; c0 ^= m0; c1 ^= m1; c2 ^= m2; c3 ^= m3; break; } break; case 3: /* write mode 3 */ mask = sc->vga_gc.gc_bit_mask & val; val = (val >> sc->vga_gc.gc_rotate) | (val << (8 - sc->vga_gc.gc_rotate)); switch (sc->vga_gc.gc_op) { case 0x00: /* replace */ m0 = (set_reset & 1 ? 0xff : 0x00) & mask; m1 = (set_reset & 2 ? 0xff : 0x00) & mask; m2 = (set_reset & 4 ? 0xff : 0x00) & mask; m3 = (set_reset & 8 ? 0xff : 0x00) & mask; c0 &= ~mask; c1 &= ~mask; c2 &= ~mask; c3 &= ~mask; c0 |= m0; c1 |= m1; c2 |= m2; c3 |= m3; break; case 0x08: /* AND */ m0 = (set_reset & 1 ? 0xff : 0x00) | ~mask; m1 = (set_reset & 2 ? 0xff : 0x00) | ~mask; m2 = (set_reset & 4 ? 0xff : 0x00) | ~mask; m3 = (set_reset & 8 ? 0xff : 0x00) | ~mask; c0 &= m0; c1 &= m1; c2 &= m2; c3 &= m3; break; case 0x10: /* OR */ m0 = (set_reset & 1 ? 0xff : 0x00) & mask; m1 = (set_reset & 2 ? 0xff : 0x00) & mask; m2 = (set_reset & 4 ? 0xff : 0x00) & mask; m3 = (set_reset & 8 ? 0xff : 0x00) & mask; c0 |= m0; c1 |= m1; c2 |= m2; c3 |= m3; break; case 0x18: /* XOR */ m0 = (set_reset & 1 ? 0xff : 0x00) & mask; m1 = (set_reset & 2 ? 0xff : 0x00) & mask; m2 = (set_reset & 4 ? 0xff : 0x00) & mask; m3 = (set_reset & 8 ? 0xff : 0x00) & mask; c0 ^= m0; c1 ^= m1; c2 ^= m2; c3 ^= m3; break; } break; } if (sc->vga_gc.gc_mode_oe) { if (offset & 1) { offset &= ~1; if (sc->vga_seq.seq_map_mask & 2) sc->vga_ram[offset + 1*64*KB] = c1; if (sc->vga_seq.seq_map_mask & 8) sc->vga_ram[offset + 3*64*KB] = c3; } else { if (sc->vga_seq.seq_map_mask & 1) sc->vga_ram[offset + 0*64*KB] = c0; if (sc->vga_seq.seq_map_mask & 4) sc->vga_ram[offset + 2*64*KB] = c2; } } else { if (sc->vga_seq.seq_map_mask & 1) sc->vga_ram[offset + 0*64*KB] = c0; if (sc->vga_seq.seq_map_mask & 2) sc->vga_ram[offset + 1*64*KB] = c1; if (sc->vga_seq.seq_map_mask & 4) sc->vga_ram[offset + 2*64*KB] = c2; if (sc->vga_seq.seq_map_mask & 8) sc->vga_ram[offset + 3*64*KB] = c3; } } static int vga_mem_handler(struct vcpu *vcpu __unused, int dir, uint64_t addr, int size, uint64_t *val, void *arg1, long arg2 __unused) { if (dir == MEM_F_WRITE) { switch (size) { case 1: vga_mem_wr_handler(addr, *val, arg1); break; case 2: vga_mem_wr_handler(addr, *val, arg1); vga_mem_wr_handler(addr + 1, *val >> 8, arg1); break; case 4: vga_mem_wr_handler(addr, *val, arg1); vga_mem_wr_handler(addr + 1, *val >> 8, arg1); vga_mem_wr_handler(addr + 2, *val >> 16, arg1); vga_mem_wr_handler(addr + 3, *val >> 24, arg1); break; case 8: vga_mem_wr_handler(addr, *val, arg1); vga_mem_wr_handler(addr + 1, *val >> 8, arg1); vga_mem_wr_handler(addr + 2, *val >> 16, arg1); vga_mem_wr_handler(addr + 3, *val >> 24, arg1); vga_mem_wr_handler(addr + 4, *val >> 32, arg1); vga_mem_wr_handler(addr + 5, *val >> 40, arg1); vga_mem_wr_handler(addr + 6, *val >> 48, arg1); vga_mem_wr_handler(addr + 7, *val >> 56, arg1); break; } } else { switch (size) { case 1: *val = vga_mem_rd_handler(addr, arg1); break; case 2: *val = vga_mem_rd_handler(addr, arg1); *val |= vga_mem_rd_handler(addr + 1, arg1) << 8; break; case 4: *val = vga_mem_rd_handler(addr, arg1); *val |= vga_mem_rd_handler(addr + 1, arg1) << 8; *val |= vga_mem_rd_handler(addr + 2, arg1) << 16; *val |= vga_mem_rd_handler(addr + 3, arg1) << 24; break; case 8: *val = vga_mem_rd_handler(addr, arg1); *val |= vga_mem_rd_handler(addr + 1, arg1) << 8; *val |= vga_mem_rd_handler(addr + 2, arg1) << 16; *val |= vga_mem_rd_handler(addr + 3, arg1) << 24; *val |= vga_mem_rd_handler(addr + 4, arg1) << 32; *val |= vga_mem_rd_handler(addr + 5, arg1) << 40; *val |= vga_mem_rd_handler(addr + 6, arg1) << 48; *val |= vga_mem_rd_handler(addr + 7, arg1) << 56; break; } } return (0); } static int vga_port_in_handler(struct vmctx *ctx, int in, int port, int bytes, uint8_t *val, void *arg) { struct vga_softc *sc = arg; switch (port) { case CRTC_IDX_MONO_PORT: case CRTC_IDX_COLOR_PORT: *val = sc->vga_crtc.crtc_index; break; case CRTC_DATA_MONO_PORT: case CRTC_DATA_COLOR_PORT: switch (sc->vga_crtc.crtc_index) { case CRTC_HORIZ_TOTAL: *val = sc->vga_crtc.crtc_horiz_total; break; case CRTC_HORIZ_DISP_END: *val = sc->vga_crtc.crtc_horiz_disp_end; break; case CRTC_START_HORIZ_BLANK: *val = sc->vga_crtc.crtc_start_horiz_blank; break; case CRTC_END_HORIZ_BLANK: *val = sc->vga_crtc.crtc_end_horiz_blank; break; case CRTC_START_HORIZ_RETRACE: *val = sc->vga_crtc.crtc_start_horiz_retrace; break; case CRTC_END_HORIZ_RETRACE: *val = sc->vga_crtc.crtc_end_horiz_retrace; break; case CRTC_VERT_TOTAL: *val = sc->vga_crtc.crtc_vert_total; break; case CRTC_OVERFLOW: *val = sc->vga_crtc.crtc_overflow; break; case CRTC_PRESET_ROW_SCAN: *val = sc->vga_crtc.crtc_present_row_scan; break; case CRTC_MAX_SCAN_LINE: *val = sc->vga_crtc.crtc_max_scan_line; break; case CRTC_CURSOR_START: *val = sc->vga_crtc.crtc_cursor_start; break; case CRTC_CURSOR_END: *val = sc->vga_crtc.crtc_cursor_end; break; case CRTC_START_ADDR_HIGH: *val = sc->vga_crtc.crtc_start_addr_high; break; case CRTC_START_ADDR_LOW: *val = sc->vga_crtc.crtc_start_addr_low; break; case CRTC_CURSOR_LOC_HIGH: *val = sc->vga_crtc.crtc_cursor_loc_high; break; case CRTC_CURSOR_LOC_LOW: *val = sc->vga_crtc.crtc_cursor_loc_low; break; case CRTC_VERT_RETRACE_START: *val = sc->vga_crtc.crtc_vert_retrace_start; break; case CRTC_VERT_RETRACE_END: *val = sc->vga_crtc.crtc_vert_retrace_end; break; case CRTC_VERT_DISP_END: *val = sc->vga_crtc.crtc_vert_disp_end; break; case CRTC_OFFSET: *val = sc->vga_crtc.crtc_offset; break; case CRTC_UNDERLINE_LOC: *val = sc->vga_crtc.crtc_underline_loc; break; case CRTC_START_VERT_BLANK: *val = sc->vga_crtc.crtc_start_vert_blank; break; case CRTC_END_VERT_BLANK: *val = sc->vga_crtc.crtc_end_vert_blank; break; case CRTC_MODE_CONTROL: *val = sc->vga_crtc.crtc_mode_ctrl; break; case CRTC_LINE_COMPARE: *val = sc->vga_crtc.crtc_line_compare; break; default: //printf("XXX VGA CRTC: inb 0x%04x at index %d\n", port, sc->vga_crtc.crtc_index); assert(0); break; } break; case ATC_IDX_PORT: *val = sc->vga_atc.atc_index; break; case ATC_DATA_PORT: switch (sc->vga_atc.atc_index) { case ATC_PALETTE0 ... ATC_PALETTE15: *val = sc->vga_atc.atc_palette[sc->vga_atc.atc_index]; break; case ATC_MODE_CONTROL: *val = sc->vga_atc.atc_mode; break; case ATC_OVERSCAN_COLOR: *val = sc->vga_atc.atc_overscan_color; break; case ATC_COLOR_PLANE_ENABLE: *val = sc->vga_atc.atc_color_plane_enb; break; case ATC_HORIZ_PIXEL_PANNING: *val = sc->vga_atc.atc_horiz_pixel_panning; break; case ATC_COLOR_SELECT: *val = sc->vga_atc.atc_color_select; break; default: //printf("XXX VGA ATC inb 0x%04x at index %d\n", port , sc->vga_atc.atc_index); assert(0); break; } break; case SEQ_IDX_PORT: *val = sc->vga_seq.seq_index; break; case SEQ_DATA_PORT: switch (sc->vga_seq.seq_index) { case SEQ_RESET: *val = sc->vga_seq.seq_reset; break; case SEQ_CLOCKING_MODE: *val = sc->vga_seq.seq_clock_mode; break; case SEQ_MAP_MASK: *val = sc->vga_seq.seq_map_mask; break; case SEQ_CHAR_MAP_SELECT: *val = sc->vga_seq.seq_cmap_sel; break; case SEQ_MEMORY_MODE: *val = sc->vga_seq.seq_mm; break; default: //printf("XXX VGA SEQ: inb 0x%04x at index %d\n", port, sc->vga_seq.seq_index); assert(0); break; } break; case DAC_DATA_PORT: *val = sc->vga_dac.dac_palette[3 * sc->vga_dac.dac_rd_index + sc->vga_dac.dac_rd_subindex]; sc->vga_dac.dac_rd_subindex++; if (sc->vga_dac.dac_rd_subindex == 3) { sc->vga_dac.dac_rd_index++; sc->vga_dac.dac_rd_subindex = 0; } break; case GC_IDX_PORT: *val = sc->vga_gc.gc_index; break; case GC_DATA_PORT: switch (sc->vga_gc.gc_index) { case GC_SET_RESET: *val = sc->vga_gc.gc_set_reset; break; case GC_ENABLE_SET_RESET: *val = sc->vga_gc.gc_enb_set_reset; break; case GC_COLOR_COMPARE: *val = sc->vga_gc.gc_color_compare; break; case GC_DATA_ROTATE: *val = sc->vga_gc.gc_rotate; break; case GC_READ_MAP_SELECT: *val = sc->vga_gc.gc_read_map_sel; break; case GC_MODE: *val = sc->vga_gc.gc_mode; break; case GC_MISCELLANEOUS: *val = sc->vga_gc.gc_misc; break; case GC_COLOR_DONT_CARE: *val = sc->vga_gc.gc_color_dont_care; break; case GC_BIT_MASK: *val = sc->vga_gc.gc_bit_mask; break; default: //printf("XXX VGA GC: inb 0x%04x at index %d\n", port, sc->vga_crtc.crtc_index); assert(0); break; } break; case GEN_MISC_OUTPUT_PORT: *val = sc->vga_misc; break; case GEN_INPUT_STS0_PORT: assert(0); break; case GEN_INPUT_STS1_MONO_PORT: case GEN_INPUT_STS1_COLOR_PORT: sc->vga_atc.atc_flipflop = 0; #ifdef __FreeBSD__ sc->vga_sts1 = GEN_IS1_VR | GEN_IS1_DE; //sc->vga_sts1 ^= (GEN_IS1_VR | GEN_IS1_DE); #else /* * During the bhyve bring-up process, a guest image was failing * to successfully boot. It appeared to be spinning, waiting * for this value to be toggled. Until it can be ruled out * that this is unnecessary (and documentation seems to * indicate that it should be present), the toggle should * remain. */ sc->vga_sts1 ^= (GEN_IS1_VR | GEN_IS1_DE); #endif *val = sc->vga_sts1; break; case GEN_FEATURE_CTRL_PORT: // OpenBSD calls this with bytes = 1 //assert(0); *val = 0; break; case 0x3c3: *val = 0; break; default: printf("XXX vga_port_in_handler() unhandled port 0x%x\n", port); //assert(0); return (-1); } return (0); } static int vga_port_out_handler(struct vmctx *ctx, int in, int port, int bytes, uint8_t val, void *arg) { struct vga_softc *sc = arg; switch (port) { case CRTC_IDX_MONO_PORT: case CRTC_IDX_COLOR_PORT: sc->vga_crtc.crtc_index = val; break; case CRTC_DATA_MONO_PORT: case CRTC_DATA_COLOR_PORT: switch (sc->vga_crtc.crtc_index) { case CRTC_HORIZ_TOTAL: sc->vga_crtc.crtc_horiz_total = val; break; case CRTC_HORIZ_DISP_END: sc->vga_crtc.crtc_horiz_disp_end = val; break; case CRTC_START_HORIZ_BLANK: sc->vga_crtc.crtc_start_horiz_blank = val; break; case CRTC_END_HORIZ_BLANK: sc->vga_crtc.crtc_end_horiz_blank = val; break; case CRTC_START_HORIZ_RETRACE: sc->vga_crtc.crtc_start_horiz_retrace = val; break; case CRTC_END_HORIZ_RETRACE: sc->vga_crtc.crtc_end_horiz_retrace = val; break; case CRTC_VERT_TOTAL: sc->vga_crtc.crtc_vert_total = val; break; case CRTC_OVERFLOW: sc->vga_crtc.crtc_overflow = val; break; case CRTC_PRESET_ROW_SCAN: sc->vga_crtc.crtc_present_row_scan = val; break; case CRTC_MAX_SCAN_LINE: sc->vga_crtc.crtc_max_scan_line = val; break; case CRTC_CURSOR_START: sc->vga_crtc.crtc_cursor_start = val; sc->vga_crtc.crtc_cursor_on = (val & CRTC_CS_CO) == 0; break; case CRTC_CURSOR_END: sc->vga_crtc.crtc_cursor_end = val; break; case CRTC_START_ADDR_HIGH: sc->vga_crtc.crtc_start_addr_high = val; sc->vga_crtc.crtc_start_addr &= 0x00ff; sc->vga_crtc.crtc_start_addr |= (val << 8); break; case CRTC_START_ADDR_LOW: sc->vga_crtc.crtc_start_addr_low = val; sc->vga_crtc.crtc_start_addr &= 0xff00; sc->vga_crtc.crtc_start_addr |= (val & 0xff); break; case CRTC_CURSOR_LOC_HIGH: sc->vga_crtc.crtc_cursor_loc_high = val; sc->vga_crtc.crtc_cursor_loc &= 0x00ff; sc->vga_crtc.crtc_cursor_loc |= (val << 8); break; case CRTC_CURSOR_LOC_LOW: sc->vga_crtc.crtc_cursor_loc_low = val; sc->vga_crtc.crtc_cursor_loc &= 0xff00; sc->vga_crtc.crtc_cursor_loc |= (val & 0xff); break; case CRTC_VERT_RETRACE_START: sc->vga_crtc.crtc_vert_retrace_start = val; break; case CRTC_VERT_RETRACE_END: sc->vga_crtc.crtc_vert_retrace_end = val; break; case CRTC_VERT_DISP_END: sc->vga_crtc.crtc_vert_disp_end = val; break; case CRTC_OFFSET: sc->vga_crtc.crtc_offset = val; break; case CRTC_UNDERLINE_LOC: sc->vga_crtc.crtc_underline_loc = val; break; case CRTC_START_VERT_BLANK: sc->vga_crtc.crtc_start_vert_blank = val; break; case CRTC_END_VERT_BLANK: sc->vga_crtc.crtc_end_vert_blank = val; break; case CRTC_MODE_CONTROL: sc->vga_crtc.crtc_mode_ctrl = val; break; case CRTC_LINE_COMPARE: sc->vga_crtc.crtc_line_compare = val; break; default: //printf("XXX VGA CRTC: outb 0x%04x, 0x%02x at index %d\n", port, val, sc->vga_crtc.crtc_index); assert(0); break; } break; case ATC_IDX_PORT: if (sc->vga_atc.atc_flipflop == 0) { if (sc->vga_atc.atc_index & 0x20) assert(0); sc->vga_atc.atc_index = val & ATC_IDX_MASK; } else { switch (sc->vga_atc.atc_index) { case ATC_PALETTE0 ... ATC_PALETTE15: sc->vga_atc.atc_palette[sc->vga_atc.atc_index] = val & 0x3f; break; case ATC_MODE_CONTROL: sc->vga_atc.atc_mode = val; break; case ATC_OVERSCAN_COLOR: sc->vga_atc.atc_overscan_color = val; break; case ATC_COLOR_PLANE_ENABLE: sc->vga_atc.atc_color_plane_enb = val; break; case ATC_HORIZ_PIXEL_PANNING: sc->vga_atc.atc_horiz_pixel_panning = val; break; case ATC_COLOR_SELECT: sc->vga_atc.atc_color_select = val; sc->vga_atc.atc_color_select_45 = (val & ATC_CS_C45) << 4; sc->vga_atc.atc_color_select_67 = ((val & ATC_CS_C67) >> 2) << 6; break; default: //printf("XXX VGA ATC: outb 0x%04x, 0x%02x at index %d\n", port, val, sc->vga_atc.atc_index); assert(0); break; } } sc->vga_atc.atc_flipflop ^= 1; break; case ATC_DATA_PORT: break; case SEQ_IDX_PORT: sc->vga_seq.seq_index = val & 0x1f; break; case SEQ_DATA_PORT: switch (sc->vga_seq.seq_index) { case SEQ_RESET: sc->vga_seq.seq_reset = val; break; case SEQ_CLOCKING_MODE: sc->vga_seq.seq_clock_mode = val; sc->vga_seq.seq_cm_dots = (val & SEQ_CM_89) ? 8 : 9; break; case SEQ_MAP_MASK: sc->vga_seq.seq_map_mask = val; break; case SEQ_CHAR_MAP_SELECT: sc->vga_seq.seq_cmap_sel = val; sc->vga_seq.seq_cmap_pri_off = ((((val & SEQ_CMS_SA) >> SEQ_CMS_SA_SHIFT) * 2) + ((val & SEQ_CMS_SAH) >> SEQ_CMS_SAH_SHIFT)) * 8 * KB; sc->vga_seq.seq_cmap_sec_off = ((((val & SEQ_CMS_SB) >> SEQ_CMS_SB_SHIFT) * 2) + ((val & SEQ_CMS_SBH) >> SEQ_CMS_SBH_SHIFT)) * 8 * KB; break; case SEQ_MEMORY_MODE: sc->vga_seq.seq_mm = val; /* Windows queries Chain4 */ //assert((sc->vga_seq.seq_mm & SEQ_MM_C4) == 0); break; default: //printf("XXX VGA SEQ: outb 0x%04x, 0x%02x at index %d\n", port, val, sc->vga_seq.seq_index); assert(0); break; } break; case DAC_MASK: break; case DAC_IDX_RD_PORT: sc->vga_dac.dac_rd_index = val; sc->vga_dac.dac_rd_subindex = 0; break; case DAC_IDX_WR_PORT: sc->vga_dac.dac_wr_index = val; sc->vga_dac.dac_wr_subindex = 0; break; case DAC_DATA_PORT: sc->vga_dac.dac_palette[3 * sc->vga_dac.dac_wr_index + sc->vga_dac.dac_wr_subindex] = val; sc->vga_dac.dac_wr_subindex++; if (sc->vga_dac.dac_wr_subindex == 3) { sc->vga_dac.dac_palette_rgb[sc->vga_dac.dac_wr_index] = ((((sc->vga_dac.dac_palette[3*sc->vga_dac.dac_wr_index + 0] << 2) | ((sc->vga_dac.dac_palette[3*sc->vga_dac.dac_wr_index + 0] & 0x1) << 1) | (sc->vga_dac.dac_palette[3*sc->vga_dac.dac_wr_index + 0] & 0x1)) << 16) | (((sc->vga_dac.dac_palette[3*sc->vga_dac.dac_wr_index + 1] << 2) | ((sc->vga_dac.dac_palette[3*sc->vga_dac.dac_wr_index + 1] & 0x1) << 1) | (sc->vga_dac.dac_palette[3*sc->vga_dac.dac_wr_index + 1] & 0x1)) << 8) | (((sc->vga_dac.dac_palette[3*sc->vga_dac.dac_wr_index + 2] << 2) | ((sc->vga_dac.dac_palette[3*sc->vga_dac.dac_wr_index + 2] & 0x1) << 1) | (sc->vga_dac.dac_palette[3*sc->vga_dac.dac_wr_index + 2] & 0x1)) << 0)); sc->vga_dac.dac_wr_index++; sc->vga_dac.dac_wr_subindex = 0; } break; case GC_IDX_PORT: sc->vga_gc.gc_index = val; break; case GC_DATA_PORT: switch (sc->vga_gc.gc_index) { case GC_SET_RESET: sc->vga_gc.gc_set_reset = val; break; case GC_ENABLE_SET_RESET: sc->vga_gc.gc_enb_set_reset = val; break; case GC_COLOR_COMPARE: sc->vga_gc.gc_color_compare = val; break; case GC_DATA_ROTATE: sc->vga_gc.gc_rotate = val; sc->vga_gc.gc_op = (val >> 3) & 0x3; break; case GC_READ_MAP_SELECT: sc->vga_gc.gc_read_map_sel = val; break; case GC_MODE: sc->vga_gc.gc_mode = val; sc->vga_gc.gc_mode_c4 = (val & GC_MODE_C4) != 0; assert(!sc->vga_gc.gc_mode_c4); sc->vga_gc.gc_mode_oe = (val & GC_MODE_OE) != 0; sc->vga_gc.gc_mode_rm = (val >> 3) & 0x1; sc->vga_gc.gc_mode_wm = val & 0x3; if (sc->gc_image) sc->gc_image->vgamode = 1; break; case GC_MISCELLANEOUS: sc->vga_gc.gc_misc = val; sc->vga_gc.gc_misc_gm = val & GC_MISC_GM; sc->vga_gc.gc_misc_mm = (val & GC_MISC_MM) >> GC_MISC_MM_SHIFT; break; case GC_COLOR_DONT_CARE: sc->vga_gc.gc_color_dont_care = val; break; case GC_BIT_MASK: sc->vga_gc.gc_bit_mask = val; break; default: //printf("XXX VGA GC: outb 0x%04x, 0x%02x at index %d\n", port, val, sc->vga_gc.gc_index); assert(0); break; } break; case GEN_INPUT_STS0_PORT: /* write to Miscellaneous Output Register */ sc->vga_misc = val; break; case GEN_INPUT_STS1_MONO_PORT: case GEN_INPUT_STS1_COLOR_PORT: /* write to Feature Control Register */ break; // case 0x3c3: // break; default: printf("XXX vga_port_out_handler() unhandled port 0x%x, val 0x%x\n", port, val); //assert(0); return (-1); } return (0); } static int vga_port_handler(struct vmctx *ctx, int in, int port, int bytes, uint32_t *eax, void *arg) { uint8_t val; int error; switch (bytes) { case 1: if (in) { *eax &= ~0xff; error = vga_port_in_handler(ctx, in, port, 1, &val, arg); if (!error) { *eax |= val & 0xff; } } else { val = *eax & 0xff; error = vga_port_out_handler(ctx, in, port, 1, val, arg); } break; case 2: if (in) { *eax &= ~0xffff; error = vga_port_in_handler(ctx, in, port, 1, &val, arg); if (!error) { *eax |= val & 0xff; } error = vga_port_in_handler(ctx, in, port + 1, 1, &val, arg); if (!error) { *eax |= (val & 0xff) << 8; } } else { val = *eax & 0xff; error = vga_port_out_handler(ctx, in, port, 1, val, arg); val = (*eax >> 8) & 0xff; error =vga_port_out_handler(ctx, in, port + 1, 1, val, arg); } break; default: assert(0); return (-1); } return (error); } void * vga_init(int io_only) { struct inout_port iop; struct vga_softc *sc; int port, error; sc = calloc(1, sizeof(struct vga_softc)); bzero(&iop, sizeof(struct inout_port)); iop.name = "VGA"; for (port = VGA_IOPORT_START; port <= VGA_IOPORT_END; port++) { iop.port = port; iop.size = 1; iop.flags = IOPORT_F_INOUT; iop.handler = vga_port_handler; iop.arg = sc; error = register_inout(&iop); assert(error == 0); } sc->gc_image = console_get_image(); /* only handle io ports; vga graphics is disabled */ if (io_only) return(sc); sc->mr.name = "VGA memory"; sc->mr.flags = MEM_F_RW; sc->mr.base = 640 * KB; sc->mr.size = 128 * KB; sc->mr.handler = vga_mem_handler; sc->mr.arg1 = sc; error = register_mem_fallback(&sc->mr); assert(error == 0); sc->vga_ram = malloc(256 * KB); memset(sc->vga_ram, 0, 256 * KB); { static uint8_t palette[] = { 0x00,0x00,0x00, 0x00,0x00,0x2a, 0x00,0x2a,0x00, 0x00,0x2a,0x2a, 0x2a,0x00,0x00, 0x2a,0x00,0x2a, 0x2a,0x2a,0x00, 0x2a,0x2a,0x2a, 0x00,0x00,0x15, 0x00,0x00,0x3f, 0x00,0x2a,0x15, 0x00,0x2a,0x3f, 0x2a,0x00,0x15, 0x2a,0x00,0x3f, 0x2a,0x2a,0x15, 0x2a,0x2a,0x3f, }; int i; memcpy(sc->vga_dac.dac_palette, palette, 16 * 3 * sizeof (uint8_t)); for (i = 0; i < 16; i++) { sc->vga_dac.dac_palette_rgb[i] = ((((sc->vga_dac.dac_palette[3*i + 0] << 2) | ((sc->vga_dac.dac_palette[3*i + 0] & 0x1) << 1) | (sc->vga_dac.dac_palette[3*i + 0] & 0x1)) << 16) | (((sc->vga_dac.dac_palette[3*i + 1] << 2) | ((sc->vga_dac.dac_palette[3*i + 1] & 0x1) << 1) | (sc->vga_dac.dac_palette[3*i + 1] & 0x1)) << 8) | (((sc->vga_dac.dac_palette[3*i + 2] << 2) | ((sc->vga_dac.dac_palette[3*i + 2] & 0x1) << 1) | (sc->vga_dac.dac_palette[3*i + 2] & 0x1)) << 0)); } } return (sc); } /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2015 Tycho Nightingale * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _VGA_H_ #define _VGA_H_ #define VGA_IOPORT_START 0x3c0 #define VGA_IOPORT_END 0x3df /* General registers */ #define GEN_INPUT_STS0_PORT 0x3c2 #define GEN_FEATURE_CTRL_PORT 0x3ca #define GEN_MISC_OUTPUT_PORT 0x3cc #define GEN_INPUT_STS1_MONO_PORT 0x3ba #define GEN_INPUT_STS1_COLOR_PORT 0x3da #define GEN_IS1_VR 0x08 /* Vertical retrace */ #define GEN_IS1_DE 0x01 /* Display enable not */ /* Attribute controller registers. */ #define ATC_IDX_PORT 0x3c0 #define ATC_DATA_PORT 0x3c1 #define ATC_IDX_MASK 0x1f #define ATC_PALETTE0 0 #define ATC_PALETTE15 15 #define ATC_MODE_CONTROL 16 #define ATC_MC_IPS 0x80 /* Internal palette size */ #define ATC_MC_GA 0x01 /* Graphics/alphanumeric */ #define ATC_OVERSCAN_COLOR 17 #define ATC_COLOR_PLANE_ENABLE 18 #define ATC_HORIZ_PIXEL_PANNING 19 #define ATC_COLOR_SELECT 20 #define ATC_CS_C67 0x0c /* Color select bits 6+7 */ #define ATC_CS_C45 0x03 /* Color select bits 4+5 */ /* Sequencer registers. */ #define SEQ_IDX_PORT 0x3c4 #define SEQ_DATA_PORT 0x3c5 #define SEQ_RESET 0 #define SEQ_RESET_ASYNC 0x1 #define SEQ_RESET_SYNC 0x2 #define SEQ_CLOCKING_MODE 1 #define SEQ_CM_SO 0x20 /* Screen off */ #define SEQ_CM_89 0x01 /* 8/9 dot clock */ #define SEQ_MAP_MASK 2 #define SEQ_CHAR_MAP_SELECT 3 #define SEQ_CMS_SAH 0x20 /* Char map A bit 2 */ #define SEQ_CMS_SAH_SHIFT 5 #define SEQ_CMS_SA 0x0c /* Char map A bits 0+1 */ #define SEQ_CMS_SA_SHIFT 2 #define SEQ_CMS_SBH 0x10 /* Char map B bit 2 */ #define SEQ_CMS_SBH_SHIFT 4 #define SEQ_CMS_SB 0x03 /* Char map B bits 0+1 */ #define SEQ_CMS_SB_SHIFT 0 #define SEQ_MEMORY_MODE 4 #define SEQ_MM_C4 0x08 /* Chain 4 */ #define SEQ_MM_OE 0x04 /* Odd/even */ #define SEQ_MM_EM 0x02 /* Extended memory */ /* Graphics controller registers. */ #define GC_IDX_PORT 0x3ce #define GC_DATA_PORT 0x3cf #define GC_SET_RESET 0 #define GC_ENABLE_SET_RESET 1 #define GC_COLOR_COMPARE 2 #define GC_DATA_ROTATE 3 #define GC_READ_MAP_SELECT 4 #define GC_MODE 5 #define GC_MODE_OE 0x10 /* Odd/even */ #define GC_MODE_C4 0x04 /* Chain 4 */ #define GC_MISCELLANEOUS 6 #define GC_MISC_GM 0x01 /* Graphics/alphanumeric */ #define GC_MISC_MM 0x0c /* memory map */ #define GC_MISC_MM_SHIFT 2 #define GC_COLOR_DONT_CARE 7 #define GC_BIT_MASK 8 /* CRT controller registers. */ #define CRTC_IDX_MONO_PORT 0x3b4 #define CRTC_DATA_MONO_PORT 0x3b5 #define CRTC_IDX_COLOR_PORT 0x3d4 #define CRTC_DATA_COLOR_PORT 0x3d5 #define CRTC_HORIZ_TOTAL 0 #define CRTC_HORIZ_DISP_END 1 #define CRTC_START_HORIZ_BLANK 2 #define CRTC_END_HORIZ_BLANK 3 #define CRTC_START_HORIZ_RETRACE 4 #define CRTC_END_HORIZ_RETRACE 5 #define CRTC_VERT_TOTAL 6 #define CRTC_OVERFLOW 7 #define CRTC_OF_VRS9 0x80 /* VRS bit 9 */ #define CRTC_OF_VRS9_SHIFT 7 #define CRTC_OF_VDE9 0x40 /* VDE bit 9 */ #define CRTC_OF_VDE9_SHIFT 6 #define CRTC_OF_VRS8 0x04 /* VRS bit 8 */ #define CRTC_OF_VRS8_SHIFT 2 #define CRTC_OF_VDE8 0x02 /* VDE bit 8 */ #define CRTC_OF_VDE8_SHIFT 1 #define CRTC_PRESET_ROW_SCAN 8 #define CRTC_MAX_SCAN_LINE 9 #define CRTC_MSL_MSL 0x1f #define CRTC_CURSOR_START 10 #define CRTC_CS_CO 0x20 /* Cursor off */ #define CRTC_CS_CS 0x1f /* Cursor start */ #define CRTC_CURSOR_END 11 #define CRTC_CE_CE 0x1f /* Cursor end */ #define CRTC_START_ADDR_HIGH 12 #define CRTC_START_ADDR_LOW 13 #define CRTC_CURSOR_LOC_HIGH 14 #define CRTC_CURSOR_LOC_LOW 15 #define CRTC_VERT_RETRACE_START 16 #define CRTC_VERT_RETRACE_END 17 #define CRTC_VRE_MASK 0xf #define CRTC_VERT_DISP_END 18 #define CRTC_OFFSET 19 #define CRTC_UNDERLINE_LOC 20 #define CRTC_START_VERT_BLANK 21 #define CRTC_END_VERT_BLANK 22 #define CRTC_MODE_CONTROL 23 #define CRTC_MC_TE 0x80 /* Timing enable */ #define CRTC_LINE_COMPARE 24 /* DAC registers */ #define DAC_MASK 0x3c6 #define DAC_IDX_RD_PORT 0x3c7 #define DAC_IDX_WR_PORT 0x3c8 #define DAC_DATA_PORT 0x3c9 void *vga_init(int io_only); #endif /* _VGA_H_ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 NetApp, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. * * Copyright 2015 Pluribus Networks Inc. * Copyright 2018 Joyent, Inc. * Copyright 2022 Oxide Computer Company * Copyright 2022 OmniOS Community Edition (OmniOSce) Association. */ #include #ifndef __FreeBSD__ #include #include #endif #include #ifndef WITHOUT_CAPSICUM #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bhyverun.h" #include "config.h" #include "debug.h" #include "gdb.h" #include "inout.h" #include "mem.h" #include "spinup_ap.h" #include "vmexit.h" #include "xmsr.h" #ifndef __FreeBSD__ static struct vm_entry *vmentry; int vmentry_init(int ncpus) { vmentry = calloc(ncpus, sizeof(*vmentry)); return (vmentry == NULL ? -1 : 0); } struct vm_entry * vmentry_vcpu(int vcpuid) { return (&vmentry[vcpuid]); } static void vmentry_mmio_read(struct vcpu *vcpu, uint64_t gpa, uint8_t bytes, uint64_t data) { struct vm_entry *entry = &vmentry[vcpu_id(vcpu)]; struct vm_mmio *mmio = &entry->u.mmio; assert(entry->cmd == VEC_DEFAULT); entry->cmd = VEC_FULFILL_MMIO; mmio->bytes = bytes; mmio->read = 1; mmio->gpa = gpa; mmio->data = data; } static void vmentry_mmio_write(struct vcpu *vcpu, uint64_t gpa, uint8_t bytes) { struct vm_entry *entry = &vmentry[vcpu_id(vcpu)]; struct vm_mmio *mmio = &entry->u.mmio; assert(entry->cmd == VEC_DEFAULT); entry->cmd = VEC_FULFILL_MMIO; mmio->bytes = bytes; mmio->read = 0; mmio->gpa = gpa; mmio->data = 0; } static void vmentry_inout_read(struct vcpu *vcpu, uint16_t port, uint8_t bytes, uint32_t data) { struct vm_entry *entry = &vmentry[vcpu_id(vcpu)]; struct vm_inout *inout = &entry->u.inout; assert(entry->cmd == VEC_DEFAULT); entry->cmd = VEC_FULFILL_INOUT; inout->bytes = bytes; inout->flags = INOUT_IN; inout->port = port; inout->eax = data; } static void vmentry_inout_write(struct vcpu *vcpu, uint16_t port, uint8_t bytes) { struct vm_entry *entry = &vmentry[vcpu_id(vcpu)]; struct vm_inout *inout = &entry->u.inout; assert(entry->cmd == VEC_DEFAULT); entry->cmd = VEC_FULFILL_INOUT; inout->bytes = bytes; inout->flags = 0; inout->port = port; inout->eax = 0; } #endif #ifdef __FreeBSD__ void vm_inject_fault(struct vcpu *vcpu, int vector, int errcode_valid, int errcode) { int error, restart_instruction; restart_instruction = 1; error = vm_inject_exception(vcpu, vector, errcode_valid, errcode, restart_instruction); assert(error == 0); } #endif static int vmexit_inout(struct vmctx *ctx, struct vcpu *vcpu, struct vm_exit *vme) { int error; struct vm_inout inout; bool in; uint8_t bytes; inout = vme->u.inout; in = (inout.flags & INOUT_IN) != 0; bytes = inout.bytes; error = emulate_inout(ctx, vcpu, &inout); if (error) { EPRINTLN("Unhandled %s%c 0x%04x at 0x%lx", in ? "in" : "out", bytes == 1 ? 'b' : (bytes == 2 ? 'w' : 'l'), inout.port, vme->rip); return (VMEXIT_ABORT); } else { /* * Communicate the status of the inout operation back to the * in-kernel instruction emulation. */ if (in) { vmentry_inout_read(vcpu, inout.port, bytes, inout.eax); } else { vmentry_inout_write(vcpu, inout.port, bytes); } return (VMEXIT_CONTINUE); } } static int vmexit_rdmsr(struct vmctx *ctx __unused, struct vcpu *vcpu, struct vm_exit *vme) { uint64_t val; uint32_t eax, edx; int error; val = 0; error = emulate_rdmsr(vcpu, vme->u.msr.code, &val); if (error != 0) { EPRINTLN("rdmsr to register %#x on vcpu %d", vme->u.msr.code, vcpu_id(vcpu)); if (get_config_bool("x86.strictmsr")) { vm_inject_gp(vcpu); return (VMEXIT_CONTINUE); } } eax = val; error = vm_set_register(vcpu, VM_REG_GUEST_RAX, eax); assert(error == 0); edx = val >> 32; error = vm_set_register(vcpu, VM_REG_GUEST_RDX, edx); assert(error == 0); return (VMEXIT_CONTINUE); } static int vmexit_wrmsr(struct vmctx *ctx __unused, struct vcpu *vcpu, struct vm_exit *vme) { int error; error = emulate_wrmsr(vcpu, vme->u.msr.code, vme->u.msr.wval); if (error != 0) { EPRINTLN("wrmsr to register %#x(%#lx) on vcpu %d", vme->u.msr.code, vme->u.msr.wval, vcpu_id(vcpu)); if (get_config_bool("x86.strictmsr")) { vm_inject_gp(vcpu); return (VMEXIT_CONTINUE); } } return (VMEXIT_CONTINUE); } static const char * const vmx_exit_reason_desc[] = { [EXIT_REASON_EXCEPTION] = "Exception or non-maskable interrupt (NMI)", [EXIT_REASON_EXT_INTR] = "External interrupt", [EXIT_REASON_TRIPLE_FAULT] = "Triple fault", [EXIT_REASON_INIT] = "INIT signal", [EXIT_REASON_SIPI] = "Start-up IPI (SIPI)", [EXIT_REASON_IO_SMI] = "I/O system-management interrupt (SMI)", [EXIT_REASON_SMI] = "Other SMI", [EXIT_REASON_INTR_WINDOW] = "Interrupt window", [EXIT_REASON_NMI_WINDOW] = "NMI window", [EXIT_REASON_TASK_SWITCH] = "Task switch", [EXIT_REASON_CPUID] = "CPUID", [EXIT_REASON_GETSEC] = "GETSEC", [EXIT_REASON_HLT] = "HLT", [EXIT_REASON_INVD] = "INVD", [EXIT_REASON_INVLPG] = "INVLPG", [EXIT_REASON_RDPMC] = "RDPMC", [EXIT_REASON_RDTSC] = "RDTSC", [EXIT_REASON_RSM] = "RSM", [EXIT_REASON_VMCALL] = "VMCALL", [EXIT_REASON_VMCLEAR] = "VMCLEAR", [EXIT_REASON_VMLAUNCH] = "VMLAUNCH", [EXIT_REASON_VMPTRLD] = "VMPTRLD", [EXIT_REASON_VMPTRST] = "VMPTRST", [EXIT_REASON_VMREAD] = "VMREAD", [EXIT_REASON_VMRESUME] = "VMRESUME", [EXIT_REASON_VMWRITE] = "VMWRITE", [EXIT_REASON_VMXOFF] = "VMXOFF", [EXIT_REASON_VMXON] = "VMXON", [EXIT_REASON_CR_ACCESS] = "Control-register accesses", [EXIT_REASON_DR_ACCESS] = "MOV DR", [EXIT_REASON_INOUT] = "I/O instruction", [EXIT_REASON_RDMSR] = "RDMSR", [EXIT_REASON_WRMSR] = "WRMSR", [EXIT_REASON_INVAL_VMCS] = "VM-entry failure due to invalid guest state", [EXIT_REASON_INVAL_MSR] = "VM-entry failure due to MSR loading", [EXIT_REASON_MWAIT] = "MWAIT", [EXIT_REASON_MTF] = "Monitor trap flag", [EXIT_REASON_MONITOR] = "MONITOR", [EXIT_REASON_PAUSE] = "PAUSE", [EXIT_REASON_MCE_DURING_ENTRY] = "VM-entry failure due to machine-check event", [EXIT_REASON_TPR] = "TPR below threshold", [EXIT_REASON_APIC_ACCESS] = "APIC access", [EXIT_REASON_VIRTUALIZED_EOI] = "Virtualized EOI", [EXIT_REASON_GDTR_IDTR] = "Access to GDTR or IDTR", [EXIT_REASON_LDTR_TR] = "Access to LDTR or TR", [EXIT_REASON_EPT_FAULT] = "EPT violation", [EXIT_REASON_EPT_MISCONFIG] = "EPT misconfiguration", [EXIT_REASON_INVEPT] = "INVEPT", [EXIT_REASON_RDTSCP] = "RDTSCP", [EXIT_REASON_VMX_PREEMPT] = "VMX-preemption timer expired", [EXIT_REASON_INVVPID] = "INVVPID", [EXIT_REASON_WBINVD] = "WBINVD", [EXIT_REASON_XSETBV] = "XSETBV", [EXIT_REASON_APIC_WRITE] = "APIC write", [EXIT_REASON_RDRAND] = "RDRAND", [EXIT_REASON_INVPCID] = "INVPCID", [EXIT_REASON_VMFUNC] = "VMFUNC", [EXIT_REASON_ENCLS] = "ENCLS", [EXIT_REASON_RDSEED] = "RDSEED", [EXIT_REASON_PM_LOG_FULL] = "Page-modification log full", [EXIT_REASON_XSAVES] = "XSAVES", [EXIT_REASON_XRSTORS] = "XRSTORS" }; #ifndef __FreeBSD__ static int vmexit_run_state(struct vmctx *ctx __unused, struct vcpu *vcpu __unused, struct vm_exit *vme __unused) { /* * Run-state transitions (INIT, SIPI, etc) are handled in-kernel, so an * exit to userspace with that code is not expected. */ fprintf(stderr, "unexpected run-state VM exit"); return (VMEXIT_ABORT); } static int vmexit_paging(struct vmctx *ctx __unused, struct vcpu *vcpu, struct vm_exit *vme) { fprintf(stderr, "vm exit[%d]\n", vcpu_id(vcpu)); fprintf(stderr, "\treason\t\tPAGING\n"); fprintf(stderr, "\trip\t\t0x%016lx\n", vme->rip); fprintf(stderr, "\tgpa\t\t0x%016lx\n", vme->u.paging.gpa); fprintf(stderr, "\tfault_type\t\t%d\n", vme->u.paging.fault_type); return (VMEXIT_ABORT); } #endif /* __FreeBSD__ */ #ifdef __FreeBSD__ #define DEBUG_EPT_MISCONFIG #else /* EPT misconfig debugging not possible now that raw VMCS access is gone */ #endif #ifdef DEBUG_EPT_MISCONFIG #define VMCS_GUEST_PHYSICAL_ADDRESS 0x00002400 static uint64_t ept_misconfig_gpa, ept_misconfig_pte[4]; static int ept_misconfig_ptenum; #endif static const char * vmexit_vmx_desc(uint32_t exit_reason) { if (exit_reason >= nitems(vmx_exit_reason_desc) || vmx_exit_reason_desc[exit_reason] == NULL) return ("Unknown"); return (vmx_exit_reason_desc[exit_reason]); } static int vmexit_vmx(struct vmctx *ctx, struct vcpu *vcpu, struct vm_exit *vme) { EPRINTLN("vm exit[%d]", vcpu_id(vcpu)); EPRINTLN("\treason\t\tVMX"); EPRINTLN("\trip\t\t0x%016lx", vme->rip); EPRINTLN("\tinst_length\t%d", vme->inst_length); EPRINTLN("\tstatus\t\t%d", vme->u.vmx.status); EPRINTLN("\texit_reason\t%u (%s)", vme->u.vmx.exit_reason, vmexit_vmx_desc(vme->u.vmx.exit_reason)); EPRINTLN("\tqualification\t0x%016lx", vme->u.vmx.exit_qualification); EPRINTLN("\tinst_type\t\t%d", vme->u.vmx.inst_type); EPRINTLN("\tinst_error\t\t%d", vme->u.vmx.inst_error); #ifdef DEBUG_EPT_MISCONFIG if (vme->u.vmx.exit_reason == EXIT_REASON_EPT_MISCONFIG) { vm_get_register(vcpu, VMCS_IDENT(VMCS_GUEST_PHYSICAL_ADDRESS), &ept_misconfig_gpa); vm_get_gpa_pmap(ctx, ept_misconfig_gpa, ept_misconfig_pte, &ept_misconfig_ptenum); EPRINTLN("\tEPT misconfiguration:"); EPRINTLN("\t\tGPA: %#lx", ept_misconfig_gpa); EPRINTLN("\t\tPTE(%d): %#lx %#lx %#lx %#lx", ept_misconfig_ptenum, ept_misconfig_pte[0], ept_misconfig_pte[1], ept_misconfig_pte[2], ept_misconfig_pte[3]); } #endif /* DEBUG_EPT_MISCONFIG */ return (VMEXIT_ABORT); } static int vmexit_svm(struct vmctx *ctx __unused, struct vcpu *vcpu, struct vm_exit *vme) { EPRINTLN("vm exit[%d]", vcpu_id(vcpu)); EPRINTLN("\treason\t\tSVM"); EPRINTLN("\trip\t\t0x%016lx", vme->rip); EPRINTLN("\tinst_length\t%d", vme->inst_length); EPRINTLN("\texitcode\t%#lx", vme->u.svm.exitcode); EPRINTLN("\texitinfo1\t%#lx", vme->u.svm.exitinfo1); EPRINTLN("\texitinfo2\t%#lx", vme->u.svm.exitinfo2); return (VMEXIT_ABORT); } static int vmexit_bogus(struct vmctx *ctx __unused, struct vcpu *vcpu __unused, struct vm_exit *vme) { assert(vme->inst_length == 0); return (VMEXIT_CONTINUE); } static int vmexit_hlt(struct vmctx *ctx __unused, struct vcpu *vcpu __unused, struct vm_exit *vme __unused) { /* * Just continue execution with the next instruction. We use * the HLT VM exit as a way to be friendly with the host * scheduler. */ return (VMEXIT_CONTINUE); } static int vmexit_pause(struct vmctx *ctx __unused, struct vcpu *vcpu __unused, struct vm_exit *vme __unused) { return (VMEXIT_CONTINUE); } static int vmexit_mtrap(struct vmctx *ctx __unused, struct vcpu *vcpu, struct vm_exit *vme) { assert(vme->inst_length == 0); gdb_cpu_mtrap(vcpu); return (VMEXIT_CONTINUE); } static int vmexit_inst_emul(struct vmctx *ctx __unused, struct vcpu *vcpu, struct vm_exit *vme) { uint8_t i, valid; fprintf(stderr, "Failed to emulate instruction sequence "); valid = vme->u.inst_emul.num_valid; if (valid != 0) { assert(valid <= sizeof (vme->u.inst_emul.inst)); fprintf(stderr, "["); for (i = 0; i < valid; i++) { if (i == 0) { fprintf(stderr, "%02x", vme->u.inst_emul.inst[i]); } else { fprintf(stderr, ", %02x", vme->u.inst_emul.inst[i]); } } fprintf(stderr, "] "); } fprintf(stderr, "@ %rip = %x\n", vme->rip); return (VMEXIT_ABORT); } #ifndef __FreeBSD__ static int vmexit_mmio(struct vmctx *ctx __unused, struct vcpu *vcpu, struct vm_exit *vme) { int err; struct vm_mmio mmio; bool is_read; mmio = vme->u.mmio; is_read = (mmio.read != 0); err = emulate_mem(vcpu, &mmio); if (err == ESRCH) { fprintf(stderr, "Unhandled memory access to 0x%lx\n", mmio.gpa); /* * Access to non-existent physical addresses is not likely to * result in fatal errors on hardware machines, but rather reads * of all-ones or discarded-but-acknowledged writes. */ mmio.data = ~0UL; err = 0; } if (err == 0) { if (is_read) { vmentry_mmio_read(vcpu, mmio.gpa, mmio.bytes, mmio.data); } else { vmentry_mmio_write(vcpu, mmio.gpa, mmio.bytes); } return (VMEXIT_CONTINUE); } fprintf(stderr, "Unhandled mmio error to 0x%lx: %d\n", mmio.gpa, err); return (VMEXIT_ABORT); } #endif /* !__FreeBSD__ */ static int vmexit_suspend(struct vmctx *ctx, struct vcpu *vcpu, struct vm_exit *vme) { enum vm_suspend_how how; int vcpuid = vcpu_id(vcpu); how = vme->u.suspended.how; fbsdrun_deletecpu(vcpuid); switch (how) { case VM_SUSPEND_RESET: exit(0); case VM_SUSPEND_POWEROFF: if (get_config_bool_default("destroy_on_poweroff", false)) vm_destroy(ctx); exit(1); case VM_SUSPEND_HALT: exit(2); case VM_SUSPEND_TRIPLEFAULT: exit(3); default: EPRINTLN("vmexit_suspend: invalid reason %d", how); exit(100); } return (0); /* NOTREACHED */ } static int vmexit_debug(struct vmctx *ctx __unused, struct vcpu *vcpu, struct vm_exit *vme __unused) { gdb_cpu_suspend(vcpu); /* * Sleep for a short period to avoid chewing up the CPU in the * window between activation of the vCPU thread and the STARTUP IPI. */ usleep(1000); return (VMEXIT_CONTINUE); } static int vmexit_breakpoint(struct vmctx *ctx __unused, struct vcpu *vcpu, struct vm_exit *vme) { gdb_cpu_breakpoint(vcpu, vme); return (VMEXIT_CONTINUE); } #ifdef __FreeBSD__ static int vmexit_ipi(struct vmctx *ctx __unused, struct vcpu *vcpu __unused, struct vm_exit *vme) { int error = -1; int i; switch (vme->u.ipi.mode) { case APIC_DELMODE_INIT: CPU_FOREACH_ISSET(i, &vme->u.ipi.dmask) { error = vm_suspend_cpu(vcpu_info[i].vcpu); if (error) { warnx("%s: failed to suspend cpu %d\n", __func__, i); break; } } break; case APIC_DELMODE_STARTUP: CPU_FOREACH_ISSET(i, &vme->u.ipi.dmask) { spinup_ap(vcpu_info[i].vcpu, vme->u.ipi.vector << PAGE_SHIFT); } error = 0; break; default: break; } return (error); } #endif const vmexit_handler_t vmexit_handlers[VM_EXITCODE_MAX] = { [VM_EXITCODE_INOUT] = vmexit_inout, #ifndef __FreeBSD__ [VM_EXITCODE_MMIO] = vmexit_mmio, #endif [VM_EXITCODE_VMX] = vmexit_vmx, [VM_EXITCODE_SVM] = vmexit_svm, [VM_EXITCODE_BOGUS] = vmexit_bogus, [VM_EXITCODE_RDMSR] = vmexit_rdmsr, [VM_EXITCODE_WRMSR] = vmexit_wrmsr, [VM_EXITCODE_MTRAP] = vmexit_mtrap, [VM_EXITCODE_INST_EMUL] = vmexit_inst_emul, #ifndef __FreeBSD__ [VM_EXITCODE_RUN_STATE] = vmexit_run_state, [VM_EXITCODE_PAGING] = vmexit_paging, #endif [VM_EXITCODE_SUSPENDED] = vmexit_suspend, [VM_EXITCODE_TASK_SWITCH] = vmexit_task_switch, [VM_EXITCODE_DEBUG] = vmexit_debug, [VM_EXITCODE_BPT] = vmexit_breakpoint, #ifdef __FreeBSD__ [VM_EXITCODE_IPI] = vmexit_ipi, #endif [VM_EXITCODE_HLT] = vmexit_hlt, [VM_EXITCODE_PAUSE] = vmexit_pause, }; /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 NetApp, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include "debug.h" #include "xmsr.h" static int cpu_vendor_intel, cpu_vendor_amd, cpu_vendor_hygon; int emulate_wrmsr(struct vcpu *vcpu __unused, uint32_t num, uint64_t val __unused) { if (cpu_vendor_intel) { switch (num) { #ifndef __FreeBSD__ case MSR_PERFCTR0: case MSR_PERFCTR1: case MSR_EVNTSEL0: case MSR_EVNTSEL1: return (0); #endif case 0xd04: /* Sandy Bridge uncore PMCs */ case 0xc24: return (0); case MSR_BIOS_UPDT_TRIG: return (0); case MSR_BIOS_SIGN: return (0); default: break; } } else if (cpu_vendor_amd || cpu_vendor_hygon) { switch (num) { case MSR_HWCR: /* * Ignore writes to hardware configuration MSR. */ return (0); case MSR_NB_CFG1: case MSR_LS_CFG: case MSR_IC_CFG: return (0); /* Ignore writes */ case MSR_PERFEVSEL0: case MSR_PERFEVSEL1: case MSR_PERFEVSEL2: case MSR_PERFEVSEL3: /* Ignore writes to the PerfEvtSel MSRs */ return (0); case MSR_K7_PERFCTR0: case MSR_K7_PERFCTR1: case MSR_K7_PERFCTR2: case MSR_K7_PERFCTR3: /* Ignore writes to the PerfCtr MSRs */ return (0); case MSR_P_STATE_CONTROL: /* Ignore write to change the P-state */ return (0); default: break; } } return (-1); } int emulate_rdmsr(struct vcpu *vcpu __unused, uint32_t num, uint64_t *val) { int error = 0; if (cpu_vendor_intel) { switch (num) { case MSR_BIOS_SIGN: case MSR_IA32_PLATFORM_ID: case MSR_PKG_ENERGY_STATUS: case MSR_PP0_ENERGY_STATUS: case MSR_PP1_ENERGY_STATUS: case MSR_DRAM_ENERGY_STATUS: case MSR_MISC_FEATURE_ENABLES: *val = 0; break; case MSR_RAPL_POWER_UNIT: /* * Use the default value documented in section * "RAPL Interfaces" in Intel SDM vol3. */ *val = 0x000a1003; break; case MSR_IA32_FEATURE_CONTROL: /* * Windows guests check this MSR. * Set the lock bit to avoid writes * to this MSR. */ *val = IA32_FEATURE_CONTROL_LOCK; break; default: error = -1; break; } } else if (cpu_vendor_amd || cpu_vendor_hygon) { switch (num) { case MSR_BIOS_SIGN: *val = 0; break; case MSR_HWCR: /* * Bios and Kernel Developer's Guides for AMD Families * 12H, 14H, 15H and 16H. */ *val = 0x01000010; /* Reset value */ *val |= 1 << 9; /* MONITOR/MWAIT disable */ break; case MSR_NB_CFG1: case MSR_LS_CFG: case MSR_IC_CFG: /* * The reset value is processor family dependent so * just return 0. */ *val = 0; break; case MSR_PERFEVSEL0: case MSR_PERFEVSEL1: case MSR_PERFEVSEL2: case MSR_PERFEVSEL3: /* * PerfEvtSel MSRs are not properly virtualized so just * return zero. */ *val = 0; break; case MSR_K7_PERFCTR0: case MSR_K7_PERFCTR1: case MSR_K7_PERFCTR2: case MSR_K7_PERFCTR3: /* * PerfCtr MSRs are not properly virtualized so just * return zero. */ *val = 0; break; case MSR_SMM_ADDR: case MSR_SMM_MASK: /* * Return the reset value defined in the AMD Bios and * Kernel Developer's Guide. */ *val = 0; break; case MSR_P_STATE_LIMIT: case MSR_P_STATE_CONTROL: case MSR_P_STATE_STATUS: case MSR_P_STATE_CONFIG(0): /* P0 configuration */ *val = 0; break; /* * OpenBSD guests test bit 0 of this MSR to detect if the * workaround for erratum 721 is already applied. * https://support.amd.com/TechDocs/41322_10h_Rev_Gd.pdf */ case 0xC0011029: *val = 1; break; #ifndef __FreeBSD__ case MSR_VM_CR: /* * We currently don't support nested virt. * Windows seems to ignore the cpuid bits and reads this * MSR anyways. */ *val = VM_CR_SVMDIS; break; #endif default: error = -1; break; } } else { error = -1; } return (error); } int init_msr(void) { int error; u_int regs[4]; char cpu_vendor[13]; do_cpuid(0, regs); ((u_int *)&cpu_vendor)[0] = regs[1]; ((u_int *)&cpu_vendor)[1] = regs[3]; ((u_int *)&cpu_vendor)[2] = regs[2]; cpu_vendor[12] = '\0'; error = 0; if (strcmp(cpu_vendor, "AuthenticAMD") == 0) { cpu_vendor_amd = 1; } else if (strcmp(cpu_vendor, "HygonGenuine") == 0) { cpu_vendor_hygon = 1; } else if (strcmp(cpu_vendor, "GenuineIntel") == 0) { cpu_vendor_intel = 1; } else { EPRINTLN("Unknown cpu vendor \"%s\"", cpu_vendor); error = ENOENT; } return (error); } /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 NetApp, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _XMSR_H_ #define _XMSR_H_ int init_msr(void); int emulate_wrmsr(struct vcpu *vcpu, uint32_t code, uint64_t val); int emulate_rdmsr(struct vcpu *vcpu, uint32_t code, uint64_t *val); #endif