Ah, OK. This should follow style(9): —
Ah, OK. This should follow style(9): --- diff --git a/sys/arch/amd64/amd64/vmm.c b/sys/arch/amd64/amd64/vmm.c index 4ffb2ff899f..7de38facc78 100644 --- a/sys/arch/amd64/amd64/vmm.c +++ b/sys/arch/amd64/amd64/vmm.c @@ -5359,6 +5359,8 @@ svm_handle_inout(struct vcpu *vcpu) case IO_ICU1 ... IO_ICU1 + 1: case 0x40 ... 0x43: case PCKBC_AUX: + case 0x60: + case 0x64: case IO_RTC ... IO_RTC + 1: case IO_ICU2 ... IO_ICU2 + 1: case 0x3f8 ... 0x3ff: diff --git a/usr.sbin/vmd/Makefile b/usr.sbin/vmd/Makefile index 8645df7aecf..f39d85b1b14 100644 --- a/usr.sbin/vmd/Makefile +++ b/usr.sbin/vmd/Makefile @@ -4,7 +4,7 @@ PROG= vmd SRCS= vmd.c control.c log.c priv.c proc.c config.c vmm.c -SRCS+= vm.c loadfile_elf.c pci.c virtio.c i8259.c mc146818.c +SRCS+= vm.c loadfile_elf.c pci.c virtio.c i8259.c mc146818.c i8042.c SRCS+= ns8250.c i8253.c vmboot.c ufs.c disklabel.c dhcp.c packet.c SRCS+= parse.y atomicio.c vioscsi.c vioraw.c vioqcow2.c fw_cfg.c diff --git a/usr.sbin/vmd/i8042.c b/usr.sbin/vmd/i8042.c new file mode 100644 index 00000000000..a62386b79ff --- /dev/null +++ b/usr.sbin/vmd/i8042.c @@ -0,0 +1,421 @@ +/* $OpenBSD$ */ +/* + * Copyright (c) 2019 Katherine Rohl <luig...@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> + +#include <dev/ic/i8042reg.h> + +#include <machine/vmmvar.h> + +#include <event.h> +#include <pthread.h> +#include <string.h> +#include <stddef.h> +#include <unistd.h> + +#include "i8042.h" +#include "proc.h" +#include "vmm.h" +#include "atomicio.h" +#include "vmd.h" + +struct i8042 { + uint8_t data; + uint8_t command; + uint8_t status; + uint8_t internal_ram[16]; + + uint8_t input_port; + uint8_t output_port; + + /* Port 0 is keyboard, Port 1 is mouse */ + uint8_t ps2_enabled[2]; + + uint32_t vm_id; + + /* Is the 8042 awaiting a command argument byte? */ + uint8_t awaiting_argbyte; +}; + +struct i8042 kbc; +pthread_mutex_t kbc_mtx; + +/* + * i8042_init + * + * Initialize the emulated i8042 KBC. + * + * Parameters: + * vm_id: vmm(4) assigned ID of the VM + */ +void +i8042_init(uint32_t vm_id) +{ + memset(&kbc, 0, sizeof(kbc)); + + if(pthread_mutex_init(&kbc_mtx, NULL) != 0) + fatalx("unable to create kbc mutex"); + + kbc.vm_id = vm_id; + + /* Always initialize the reset bit to 1 for proper operation. */ + kbc.output_port = KBC_DEFOUTPORT; + kbc.input_port = KBC_DEFINPORT; +} + +/* + * i8042_set_output_port + * + * Set the output port of the KBC. + * Many bits have side effects, so handle their on/off + * transition effects as required. + */ +static void +i8042_set_output_port(uint8_t byte) +{ + /* 0x01: reset line + * 0x02: A20 gate (not implemented) + * 0x04: aux port clock + * 0x08: aux port data + * 0x10: data in output buffer is from kb port (IRQ1) + * 0x20: data in output buffer is from aux port (IRQ12) + * 0x40: kb port clock + * 0x80: kb port data + */ + + uint8_t old_output_port = kbc.output_port; + kbc.output_port = byte; + + // 0 -> 1 transition + if (((old_output_port & 0x10) == 0) && + ((kbc.output_port & 0x10) == 1)) + vcpu_assert_pic_irq(kbc.vm_id, 0, KBC_KBDIRQ); + if (((old_output_port & 0x20) == 0) && + ((kbc.output_port & 0x20) == 1)) + vcpu_assert_pic_irq(kbc.vm_id, 0, KBC_AUXIRQ); + + // 1 -> 0 transition + if (((old_output_port & 0x10) == 1) && + ((kbc.output_port & 0x10) == 0)) + vcpu_deassert_pic_irq(kbc.vm_id, 0, KBC_KBDIRQ); + if (((old_output_port & 0x20) == 1) && + ((kbc.output_port & 0x20) == 0)) + vcpu_deassert_pic_irq(kbc.vm_id, 0, KBC_AUXIRQ); +} + +/* + * i8042_perform_twobyte_command + * + * Handles the two-byte command (one byte of command and + * one byte of data) after the data has been received. + */ +static void +i8042_perform_twobyte_command(void) +{ + // The command is in kbc.command and the argument + // is in kbc.data. + + switch (kbc.command) { + case 0x60 ... 0x7F: // KBC_RAMWRITE + kbc.internal_ram[kbc.command - 0x60] = kbc.data; + break; + case KBC_CMDWOUT: + i8042_set_output_port(kbc.data); + break; + case KBC_KBDECHO: + i8042_set_output_port(kbc.output_port|0x10); + break; + case KBC_AUXECHO: + i8042_set_output_port(kbc.output_port|0x20); + break; + case KBC_AUXWRITE: + // No auxiliary device for this to go to. + break; + } + + kbc.awaiting_argbyte = 0; +} + +/* + * i8042_process_command + * + * Handles the command byte currently in the KBC's command + * register, setting the data register accordingly. + */ +static void +i8042_process_command(void) +{ +#define SET_DIB_BIT (kbc.status |= KBS_DIB) + kbc.awaiting_argbyte = 0; + + switch (kbc.command) { + case 0x20 ... 0x3F: // KBC_RAMREAD + kbc.data = kbc.internal_ram[kbc.command - 0x20]; + SET_DIB_BIT; + break; + case 0x60 ... 0x7F: // KBC_RAMWRITE + case KBC_CMDWOUT: + case KBC_KBDECHO: + case KBC_AUXECHO: + case KBC_AUXWRITE: + kbc.awaiting_argbyte = 1; + break; + case KBC_AUXENABLE: + kbc.ps2_enabled[KBC_AUXPORT] = 1; + break; + case KBC_AUXDISABLE: + kbc.ps2_enabled[KBC_AUXPORT] = 0; + break; + case KBC_AUXTEST: + kbc.data = 0; + SET_DIB_BIT; + break; + case KBC_KBDTEST: + kbc.data = 0; + SET_DIB_BIT; + break; + case KBC_KBDENABLE: + kbc.ps2_enabled[KBC_KBDPORT] = 1; + break; + case KBC_KBDDISABLE: + kbc.ps2_enabled[KBC_KBDPORT] = 0; + break; + case KBC_READINPORT: + kbc.data = kbc.input_port; + SET_DIB_BIT; + break; + case KBC_POLLINLO: + kbc.status &= 0x0F; + kbc.status |= (kbc.input_port << 4); + break; + case KBC_POLLINHI: + kbc.status &= 0x0F; + kbc.status |= (kbc.input_port & 0xF0); + break; + case KBC_CMDROUT: + kbc.data = kbc.output_port; + SET_DIB_BIT; + break; + case KBC_SELFTEST: + kbc.data = 0x55; + SET_DIB_BIT; + break; + case KBC_PULSE0: + kbc.output_port &= 0xFE; + break; + case KBC_PULSE1: + case KBC_PULSE2: + case KBC_PULSE3: + // Ignore these commands. + break; + } +} + +/* + * i8042_read_status_register + * + * Handles status register reads. + * + * Parameters: + * + * Return value: + * Value of the KBC status register. + */ +static uint8_t +i8042_read_status_register(void) +{ + return kbc.status; +} + +/* + * i8042_read_data_register + * + * Reads the data register of the KBC. + * + * Parameters: + * + * Return value: + * Value of the KBC data register. + */ +static uint8_t +i8042_read_data_register(void) +{ + // Clear the output buffer full bit. + kbc.status &= 0xFE; + + return kbc.data; +} + +/* + * i8042_write_command_register + * + * Writes a byte to the 8042 command register. + * + * Parameters: + * data: The byte to write. + */ +static void +i8042_write_command_register(uint8_t data) +{ + kbc.command = data; + i8042_process_command(); +} + +/* + * i8042_write_data_register + * + * Write a byte to the KBC data register. + * + * Parameters: + * data: The byte to write. + */ +static void +i8042_write_data_register(uint8_t data) +{ + kbc.data = data; + + if (kbc.awaiting_argbyte) { + i8042_perform_twobyte_command(); + } +} + +/* + * i8042_io_read + * + * Handles emulated reads of the KBC's I/O ports. + * + * Parameters: + * vei: + * + * Return value: + * The byte read from the addressed port. + * + */ +static uint8_t +i8042_io_read(struct vm_exit *vei) +{ + uint16_t port = vei->vei.vei_port; + uint8_t rv; + + mutex_lock(&kbc_mtx); + + switch (port) { + case KBC_CMD: + rv = i8042_read_status_register();; + break; + case KBC_DATA: + rv = i8042_read_data_register(); + break; + default: + fatal("%s: invalid port 0x%x", __func__, port); + } + + mutex_unlock(&kbc_mtx); + return(rv); +} + +/* + * i8042_io_write + * + * Handles writes to the emulated KBC device, setting the + * value of the selected register. + * + * Parameters: + * vei: + * + */ +static void +i8042_io_write(struct vm_exit *vei) +{ + uint16_t port = vei->vei.vei_port; + uint32_t data; + + get_input_data(vei, &data); + + mutex_lock(&kbc_mtx); + switch (port) { + case KBC_DATA: + i8042_write_data_register(data); + break; + case KBC_CMD: + i8042_write_command_register(data); + break; + default: + fatal("%s: invalid port 0x%x", __func__, port); + } + + if ((kbc.output_port & 0x01) == 0) /* Deasserted the reset line? */ + { + /* TODO: Reset the CPU. */ + } + + mutex_unlock(&kbc_mtx); +} + +/* + * vcpu_exit_i8042 + * + * Handles the 0x60 and 0x64 i8042 KBC in/out exits. + * + * Parameters: + * vrp: vm run parameters containing exit information for the I/O + * instruction being performed. + * + * Return value: + * 0xFF (?) + * + */ +uint8_t +vcpu_exit_i8042(struct vm_run_params *vrp) +{ + struct vm_exit *vei = vrp->vrp_exit; + + if (vei->vei.vei_dir == VEI_DIR_OUT) + i8042_io_write(vei); + else + set_return_data(vei, i8042_io_read(vei)); + + return (0xFF); +} + +int +i8042_restore(int fd) +{ + log_debug("%s: restoring KBC", __func__); + if (atomicio(read, fd, &kbc, sizeof(kbc)) != sizeof(kbc)) { + log_warnx("%s: error reading KBC from fd", __func__); + return (-1); + } + + if (pthread_mutex_init(&kbc_mtx, NULL) != 0) + fatalx("unable to create kbc mutex"); + + return (0); +} + +int +i8042_dump(int fd) +{ + log_debug("%s: sending KBC", __func__); + if (atomicio(vwrite, fd, &kbc, sizeof(kbc)) != sizeof(kbc)) { + log_warnx("%s: error writing KBC to fd", __func__); + return (-1); + } + + return (0); + +} diff --git a/usr.sbin/vmd/i8042.h b/usr.sbin/vmd/i8042.h new file mode 100644 index 00000000000..67af8f54f2a --- /dev/null +++ b/usr.sbin/vmd/i8042.h @@ -0,0 +1,45 @@ +/* $OpenBSD: i8042.c,v 1.00 2019/05/25 18:18:00 rohl Exp $ */ +/* + * Copyright (c) 2019 Katherine Rohl <luig...@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Emulated i8042 (keyboard controller, based on 8048) + */ + +#define KBC_DEFINPORT 0xB0 /* Default input port value. */ +#define KBC_DEFOUTPORT 0x01 /* Default output port value. */ + +#define KBC_KBDPORT 0 /* Keyboard is port 0. */ +#define KBC_AUXPORT 1 /* Auxiliary is port 1. */ + +#define KBC_KBDIRQ 1 /* Keyboard IRQ. */ +#define KBC_AUXIRQ 12 /* Secondary IRQ. */ + +#define KBC_DATA 0x60 /* Data is I/O port 60h. */ +#define KBC_CMD 0x64 /* Command is I/O port 64h. */ + +/* Commands that aren't useful for public includes. */ +#define KBC_READINPORT 0xC0 /* Read the input port, place in data. */ +#define KBC_POLLINLO 0xC1 /* Poll low 4 bits of input, place in status. */ +#define KBC_POLLINHI 0xC2 /* Poll high 4 bits of input, place in status. */ +#define KBC_CMDROUT 0xD0 /* Read the output port. */ + +uint8_t vcpu_exit_i8042(struct vm_run_params *); + +void i8042_init(uint32_t); + +int i8042_restore(int); +int i8042_dump(int); diff --git a/usr.sbin/vmd/vm.c b/usr.sbin/vmd/vm.c index 72b2e379ac3..35adf25c1cf 100644 --- a/usr.sbin/vmd/vm.c +++ b/usr.sbin/vmd/vm.c @@ -61,6 +61,7 @@ #include "i8259.h" #include "ns8250.h" #include "mc146818.h" +#include "i8042.h" #include "fw_cfg.h" #include "atomicio.h" @@ -949,6 +950,11 @@ init_emulated_hw(struct vmop_create_params *vmc, int child_cdrom, ioports_map[ELCR0] = vcpu_exit_elcr; ioports_map[ELCR1] = vcpu_exit_elcr; + /* Init i8042 keyboard controller */ + i8042_init(vcp->vcp_id); + ioports_map[0x64] = vcpu_exit_i8042; + ioports_map[0x60] = vcpu_exit_i8042; + /* Init ns8250 UART */ ns8250_init(con_fd, vcp->vcp_id); for (i = COM1_DATA; i <= COM1_SCR; i++) @@ -1002,6 +1008,11 @@ restore_emulated_hw(struct vm_create_params *vcp, int fd, ioports_map[IO_ICU2] = vcpu_exit_i8259; ioports_map[IO_ICU2 + 1] = vcpu_exit_i8259; + /* Init i8042 keyboard controller */ + i8042_restore(fd); + ioports_map[0x64] = vcpu_exit_i8042; + ioports_map[0x60]= vcpu_exit_i8042; + /* Init ns8250 UART */ ns8250_restore(fd, con_fd, vcp->vcp_id); for (i = COM1_DATA; i <= COM1_SCR; i++) @@ -1291,7 +1302,7 @@ vcpu_run_loop(void *arg) for (;;) { ret = pthread_mutex_lock(&vcpu_run_mtx[n]); - + if (ret) { log_warnx("%s: can't lock vcpu run mtx (%d)", __func__, (int)ret);