/* * 86Box A hypervisor and IBM PC system emulator that specializes in * running old operating systems and software designed for IBM * PC systems and compatibles from 1981 through fairly recent * system designs based on the PCI bus. * * This file is part of the 86Box distribution. * * AT / PS/2 attached device emulation. * * * * Authors: Miran Grca, * * Copyright 2023 Miran Grca. */ #include #include #include #include #include #define HAVE_STDARG_H #include #include <86box/86box.h> #include "cpu.h" #include <86box/timer.h> #include <86box/io.h> #include <86box/pic.h> #include <86box/pit.h> #include <86box/ppi.h> #include <86box/mem.h> #include <86box/device.h> #include <86box/machine.h> #include <86box/m_at_t3100e.h> #include <86box/fdd.h> #include <86box/fdc.h> #include <86box/sound.h> #include <86box/snd_speaker.h> #include <86box/video.h> #include <86box/keyboard.h> #ifdef ENABLE_KBC_AT_DEV_LOG int kbc_at_dev_do_log = ENABLE_KBC_AT_DEV_LOG; static void kbc_at_dev_log(const char *fmt, ...) { va_list ap; if (kbc_at_dev_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } #else # define kbc_at_dev_log(fmt, ...) #endif static void kbc_at_dev_queue_reset(atkbc_dev_t *dev, uint8_t reset_main) { if (reset_main) { dev->queue_start = dev->queue_end = 0; memset(dev->queue, 0x00, sizeof(dev->queue)); } dev->cmd_queue_start = dev->cmd_queue_end = 0; memset(dev->cmd_queue, 0x00, sizeof(dev->cmd_queue)); } uint8_t kbc_at_dev_queue_pos(atkbc_dev_t *dev, uint8_t main) { uint8_t ret; if (main) ret = ((dev->queue_end - dev->queue_start) & dev->fifo_mask); else ret = ((dev->cmd_queue_end - dev->cmd_queue_start) & 0xf); return ret; } void kbc_at_dev_queue_add(atkbc_dev_t *dev, uint8_t val, uint8_t main) { if (main) { kbc_at_dev_log("%s: dev->queue[%02X] = %02X;\n", dev->name, dev->queue_end, val); dev->queue[dev->queue_end] = val; dev->queue_end = (dev->queue_end + 1) & dev->fifo_mask; } else { kbc_at_dev_log("%s: dev->cmd_queue[%02X] = %02X;\n", dev->name, dev->cmd_queue_end, val); dev->cmd_queue[dev->cmd_queue_end] = val; dev->cmd_queue_end = (dev->cmd_queue_end + 1) & 0xf; } /* TODO: This should be done on actual send to host. */ if (val != 0xfe) dev->last_scan_code = val; } static void kbc_at_dev_poll(void *priv) { atkbc_dev_t *dev = (atkbc_dev_t *) priv; switch (dev->state) { case DEV_STATE_MAIN_1: /* Process the command if needed and then return to main loop #2. */ if (dev->port->wantcmd) { kbc_at_dev_log("%s: Processing keyboard command %02X...\n", dev->name, dev->port->dat); kbc_at_dev_queue_reset(dev, 0); dev->process_cmd(dev); dev->port->wantcmd = 0; } else dev->state = DEV_STATE_MAIN_2; break; case DEV_STATE_MAIN_2: /* Output from scan queue if needed and then return to main loop #1. */ if (*dev->scan && (dev->port->out_new == -1) && (dev->queue_start != dev->queue_end)) { kbc_at_dev_log("%s: %02X (DATA) on channel 1\n", dev->name, dev->queue[dev->queue_start]); dev->port->out_new = dev->queue[dev->queue_start]; dev->queue_start = (dev->queue_start + 1) & dev->fifo_mask; } if (!(*dev->scan) || dev->port->wantcmd) dev->state = DEV_STATE_MAIN_1; break; case DEV_STATE_MAIN_OUT: /* If host wants to send command while we're sending a byte to host, process the command. */ if (dev->port->wantcmd) { kbc_at_dev_log("%s: Processing keyboard command %02X...\n", dev->name, dev->port->dat); kbc_at_dev_queue_reset(dev, 0); dev->process_cmd(dev); dev->port->wantcmd = 0; break; } [[fallthrough]]; case DEV_STATE_MAIN_WANT_IN: /* Output command response and then return to main loop #2. */ if ((dev->port->out_new == -1) && (dev->cmd_queue_start != dev->cmd_queue_end)) { kbc_at_dev_log("%s: %02X (CMD ) on channel 1\n", dev->name, dev->cmd_queue[dev->cmd_queue_start]); dev->port->out_new = dev->cmd_queue[dev->cmd_queue_start]; dev->cmd_queue_start = (dev->cmd_queue_start + 1) & 0xf; } if (dev->cmd_queue_start == dev->cmd_queue_end) dev->state++; break; case DEV_STATE_MAIN_IN: /* Wait for host data. */ if (dev->port->wantcmd) { kbc_at_dev_log("%s: Processing keyboard command %02X parameter %02X...\n", dev->name, dev->command, dev->port->dat); kbc_at_dev_queue_reset(dev, 0); dev->process_cmd(dev); dev->port->wantcmd = 0; } break; case DEV_STATE_EXECUTE_BAT: dev->state = DEV_STATE_MAIN_OUT; dev->execute_bat(dev); break; case DEV_STATE_MAIN_WANT_EXECUTE_BAT: /* Output command response and then return to main loop #2. */ if ((dev->port->out_new == -1) && (dev->cmd_queue_start != dev->cmd_queue_end)) { kbc_at_dev_log("%s: %02X (CMD ) on channel 1\n", dev->name, dev->cmd_queue[dev->cmd_queue_start]); dev->port->out_new = dev->cmd_queue[dev->cmd_queue_start]; dev->cmd_queue_start = (dev->cmd_queue_start + 1) & 0xf; } if (dev->cmd_queue_start == dev->cmd_queue_end) dev->state = DEV_STATE_EXECUTE_BAT; break; default: break; } } void kbc_at_dev_reset(atkbc_dev_t *dev, int do_fa) { dev->port->out_new = -1; dev->port->wantcmd = 0; kbc_at_dev_queue_reset(dev, 1); dev->last_scan_code = 0x00; *dev->scan = 1; if (do_fa) { kbc_at_dev_queue_add(dev, 0xfa, 0); dev->state = DEV_STATE_MAIN_WANT_EXECUTE_BAT; } else dev->state = DEV_STATE_EXECUTE_BAT; } atkbc_dev_t * kbc_at_dev_init(uint8_t inst) { atkbc_dev_t *dev; dev = (atkbc_dev_t *) malloc(sizeof(atkbc_dev_t)); memset(dev, 0x00, sizeof(atkbc_dev_t)); dev->port = kbc_at_ports[inst]; if (dev->port != NULL) { dev->port->priv = dev; dev->port->poll = kbc_at_dev_poll; } /* Return our private data to the I/O layer. */ return dev; }