/* * 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. * * Intel 8042 (AT keyboard controller) emulation. * * * * Authors: Sarah Walker, * Miran Grca, * Fred N. van Kempen, * EngiNerd * * Copyright 2008-2020 Sarah Walker. * Copyright 2016-2020 Miran Grca. * Copyright 2017-2020 Fred N. van Kempen. * Copyright 2020 EngiNerd. */ #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_xt_xi8088.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> #define STAT_PARITY 0x80 #define STAT_RTIMEOUT 0x40 #define STAT_TTIMEOUT 0x20 #define STAT_MFULL 0x20 #define STAT_UNLOCKED 0x10 #define STAT_CD 0x08 #define STAT_SYSFLAG 0x04 #define STAT_IFULL 0x02 #define STAT_OFULL 0x01 #define RESET_DELAY_TIME 1000 /* 100 ms */ #define CCB_UNUSED 0x80 #define CCB_TRANSLATE 0x40 #define CCB_PCMODE 0x20 #define CCB_ENABLEKBD 0x10 #define CCB_IGNORELOCK 0x08 #define CCB_SYSTEM 0x04 #define CCB_ENABLEMINT 0x02 #define CCB_ENABLEKINT 0x01 #define CCB_MASK 0x68 #define MODE_MASK 0x6c #define KBC_TYPE_ISA 0x00 /* AT ISA-based chips */ #define KBC_TYPE_PS2_1 0x04 /* PS2 type, no refresh */ /* This only differs in that translation is forced off. */ #define KBC_TYPE_PS2_2 0x05 /* PS2 on PS/2, type 2 */ #define KBC_TYPE_MASK 0x07 #define KBC_FLAG_PS2 0x04 /* We need to redefine this: Currently, we use bits 3-7 for vendor, we should instead use bits 4-7 for vendor, 0-3 for revision/variant, and have a dev->ps2 flag controlling controller mode, normally set according to the flags, but togglable on AMIKey: 0000 0000 0x00 IBM, AT 0000 0001 0x01 MR 0000 0010 0x02 Xi8088, clone of IBM PS/2 type 1 0001 0000 0x10 Olivetti 0010 0000 0x20 Toshiba 0011 0000 0x30 Quadtel 0100 0000 0x40 Phoenix MultiKey/42 0101 0000 0x50 AMI KF 0101 0001 0x51 AMI KH 0101 0010 0x52 AMIKey 0101 0011 0x53 AMIKey-2 0101 0100 0x54 JetKey (clone of AMI KF/AMIKey) 0110 0000 0x60 Award 0110 0001 0x61 Award 286 (has some AMI commands apparently) 0111 0000 0x70 Siemens */ /* Standard IBM controller */ #define KBC_VEN_GENERIC 0x00 /* All commands are standard PS/2 */ #define KBC_VEN_IBM_MCA 0x08 /* Standard IBM commands, differs in input port bits */ #define KBC_VEN_IBM_PS1 0x10 /* Olivetti - proprietary commands and port 62h with switches readout */ #define KBC_VEN_OLIVETTI 0x20 /* Toshiba T3100e - has a bunch of proprietary commands, also sets IFULL on command AA */ #define KBC_VEN_TOSHIBA 0x28 /* Standard IBM commands, uses input port as a switches readout */ #define KBC_VEN_NCR 0x30 /* Xi8088 - standard IBM commands, has a turbo bit on port 61h, and the polarity of the video type bit in the input port is inverted */ #define KBC_VEN_XI8088 0x38 /* QuadtelKey - currently guesswork */ #define KBC_VEN_QUADTEL 0x40 /* Phoenix MultiKey/42 - not yet implemented */ #define KBC_VEN_PHOENIX 0x48 /* Generic commands, XI8088-like input port handling of video type, maybe we just need a flag for that? */ #define KBC_VEN_ACER 0x50 /* AMI KF/KH/AMIKey/AMIKey-2 */ #define KBC_VEN_AMI 0xf0 /* Standard AMI commands, differs in input port bits */ #define KBC_VEN_INTEL_AMI 0xf8 #define KBC_VEN_MASK 0xf8 /* Flags should be fully 32-bit: Bits 7- 0: Vendor and revision/variant; Bits 15- 8: Input port mask; Bits 23-16: Input port bits that are always on; Bits 31-24: Flags: Bit 0: Invert P1 video type bit polarity; Bit 1: Is PS/2; Bit 2: Translation forced always off. So for example, the IBM PS/2 type 1 controller flags would be: 00000010 00000000 11111111 00000000 = 0200ff00 . */ typedef struct { uint8_t *c_in, *c_data, /* Data to controller */ *d_in, *d_data, /* Data to device */ *inhibit; void (*process)(void *priv); void *priv; } kbc_dev_t; typedef struct { uint8_t status, ib, ob, p1, p2, old_p2, p2_locked, fast_a20_phase, secr_phase, mem_index, ami_stat, ami_mode, kbc_in, kbc_cmd, kbc_in_cmd, kbc_poll_phase, kbc_to_send, kbc_send_pending, kbc_channel, kbc_stat_hi, kbc_wait_for_response, inhibit; uint8_t mem_int[0x40], mem[0x240]; uint16_t last_irq, kbc_phase; uint32_t flags; kbc_dev_t * kbc_devs[2]; pc_timer_t pulse_cb, send_delay_timer; uint8_t (*write60_ven)(void *p, uint8_t val); uint8_t (*write64_ven)(void *p, uint8_t val); void * log; } atkbc_t; enum { CHANNEL_KBC = 0, CHANNEL_KBD, CHANNEL_MOUSE }; enum { KBD_MAIN_LOOP = 0, KBD_CMD_PROCESS }; enum { MOUSE_MAIN_LOOP_1 = 0, MOUSE_CMD_PROCESS, MOUSE_CMD_END, MOUSE_MAIN_LOOP_2 }; enum { KBC_MAIN_LOOP = 0, KBC_RESET = 1, KBC_WAIT = 4, KBC_WAIT_FOR_KBD, KBC_WAIT_FOR_MOUSE, KBC_WAIT_FOR_BOTH }; static void kbc_wait(atkbc_t *dev, uint8_t flags); /* Bits 0 - 1 = scan code set, bit 6 = translate or not. */ uint8_t keyboard_mode = 0x42; uint8_t * ami_copr = (uint8_t *) "(C)1994 AMI"; uint8_t mouse_queue[16]; int mouse_queue_start = 0, mouse_queue_end = 0; static void (*mouse_write)(uint8_t val, void *priv) = NULL; static void *mouse_p = NULL; static uint8_t sc_or = 0; static atkbc_t *saved_kbc = NULL; /* Non-translated to translated scan codes. */ static const uint8_t nont_to_t[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 }; #define UISTR_LEN 256 static char kbc_str[UISTR_LEN]; /* UI output string */ extern void ui_sb_bugui(char *__str); static void kbc_status(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsprintf(kbc_str, fmt, ap); ui_sb_bugui(kbc_str); va_end(ap); } #define ENABLE_KBC_AT_LOG 1 #if (!defined(RELEASE_BUILD) && defined(ENABLE_KBC_AT_LOG)) int kbc_at_do_log = ENABLE_KBC_AT_LOG; static void kbc_log(atkbc_t *dev, const char *fmt, ...) { va_list ap; if ((dev == NULL) || (dev->log == NULL)) return; if (kbc_at_do_log) { va_start(ap, fmt); log_out(dev->log, fmt, ap); va_end(ap); } } #else #define kbc_log(dev, fmt, ...) #endif static void kbc_send_to_ob(atkbc_t *dev, uint8_t val, uint8_t channel, uint8_t stat_hi) { uint8_t ch = (channel > 0) ? channel : 1; uint8_t do_irq = (dev->mem[0x20] & ch); int translate = (channel == 1) && (keyboard_mode & 0x60); if ((channel == 2) && !(dev->flags & KBC_FLAG_PS2)) return; stat_hi |= dev->inhibit; if (!dev->kbc_send_pending) { dev->kbc_send_pending = 1; dev->kbc_to_send = val; dev->kbc_channel = channel; dev->kbc_stat_hi = stat_hi; return; } if (translate) { /* Allow for scan code translation. */ if (val == 0xf0) { kbc_log(dev, "Translate is on, F0 prefix detected\n"); sc_or = 0x80; return; } /* Skip break code if translated make code has bit 7 set. */ if ((sc_or == 0x80) && (val & 0x80)) { kbc_log(dev, "Translate is on, skipping scan code: %02X (original: F0 %02X)\n", nont_to_t[val], val); sc_or = 0; return; } } dev->last_irq = (ch == 2) ? 0x1000 : 0x0002; if (do_irq) { kbc_log(dev, "[%04X:%08X] IRQ %i\n", CS, cpu_state.pc, (ch == 2) ? 12 : 1); picint(dev->last_irq); } kbc_log(dev, "%02X coming from channel %i (%i)\n", val, channel, do_irq); dev->ob = translate ? (nont_to_t[val] | sc_or) : val; dev->status = (dev->status & 0x0f) | (stat_hi | (dev->mem[0x20] & STAT_SYSFLAG) | STAT_OFULL); if (ch == 2) dev->status |= STAT_MFULL; if (translate && (sc_or == 0x80)) sc_or = 0; } static void write_output(atkbc_t *dev, uint8_t val) { uint8_t kbc_ven = dev->flags & KBC_VEN_MASK; kbc_log(dev, "Write output port: %02X (old: %02X)\n", val, dev->p2); if ((kbc_ven == KBC_VEN_AMI) || (dev->flags & KBC_FLAG_PS2)) val |= ((dev->mem[0x20] << 4) & 0x30); dev->kbc_devs[0]->inhibit = (val & 0x40); dev->kbc_devs[1]->inhibit = (val & 0x08); if ((dev->p2 ^ val) & 0x20) { /*IRQ 12*/ if (val & 0x20) { kbc_log(dev, "write_output(): IRQ 12\n"); picint(1 << 12); } else picintc(1 << 12); } if ((dev->p2 ^ val) & 0x10) { /*IRQ 1*/ if (val & 0x10) { kbc_log(dev, "write_output(): IRQ 1\n"); picint(1 << 1); } else picintc(1 << 1); } if ((dev->p2 ^ val) & 0x02) { /*A20 enable change*/ mem_a20_key = val & 0x02; mem_a20_recalc(); flushmmucache(); } if ((dev->p2 ^ val) & 0x01) { /*Reset*/ if (! (val & 0x01)) { /* Pin 0 selected. */ softresetx86(); /*Pulse reset!*/ cpu_set_edx(); smbase = is_am486dxl ? 0x00060000 : 0x00030000; } } /* Mask off the A20 stuff because we use mem_a20_key directly for that. */ dev->p2 = val; } static void write_cmd(atkbc_t *dev, uint8_t val) { uint8_t kbc_ven = dev->flags & KBC_VEN_MASK; kbc_log(dev, "Write command byte: %02X (old: %02X)\n", val, dev->mem[0x20]); /* PS/2 type 2 keyboard controllers always force the XLAT bit to 0. */ if ((dev->flags & KBC_TYPE_MASK) == KBC_TYPE_PS2_2) val &= ~CCB_TRANSLATE; dev->mem[0x20] = val; /* Scan code translate ON/OFF. */ keyboard_mode &= 0x93; keyboard_mode |= (val & MODE_MASK); kbc_log(dev, "Keyboard interrupt is now %s\n", (val & 0x01) ? "enabled" : "disabled"); /* ISA AT keyboard controllers use bit 5 for keyboard mode (1 = PC/XT, 2 = AT); PS/2 (and EISA/PCI) keyboard controllers use it as the PS/2 mouse enable switch. The AMIKEY firmware apparently uses this bit for something else. */ if ((kbc_ven == KBC_VEN_AMI) || (dev->flags & KBC_FLAG_PS2)) { keyboard_mode &= ~CCB_PCMODE; /* Update the output port to mirror the KBD DIS and AUX DIS bits, if active. */ write_output(dev, dev->p2); kbc_log(dev, "Mouse interrupt is now %s\n", (val & 0x02) ? "enabled" : "disabled"); } kbc_log(dev, "Command byte now: %02X (%02X)\n", dev->mem[0x20], val); dev->status = (dev->status & ~STAT_SYSFLAG) | (val & STAT_SYSFLAG); } static void pulse_output(atkbc_t *dev, uint8_t mask) { if (mask != 0x0f) { dev->old_p2 = dev->p2 & ~(0xf0 | mask); kbc_log(dev, "pulse_output(): Output port now: %02X\n", dev->p2 & (0xf0 | mask | (dev->mem[0x20] & 0x30))); write_output(dev, dev->p2 & (0xf0 | mask | (dev->mem[0x20] & 0x30))); timer_set_delay_u64(&dev->pulse_cb, 6ULL * TIMER_USEC); } } static void set_enable_kbd(atkbc_t *dev, uint8_t enable) { dev->mem[0x20] &= 0xef; dev->mem[0x20] |= (enable ? 0x00 : 0x10); } static void set_enable_mouse(atkbc_t *dev, uint8_t enable) { dev->mem[0x20] &= 0xdf; dev->mem[0x20] |= (enable ? 0x00 : 0x20); } static void kbc_transmit(atkbc_t *dev, uint8_t val) { kbc_send_to_ob(dev, val, 0, 0x00); } static void kbc_command(atkbc_t *dev) { uint8_t mask, val = dev->ib; uint8_t kbc_ven = dev->flags & KBC_VEN_MASK; int bad = 1; if ((dev->kbc_phase > 0) && (dev->kbc_cmd == 0xac)) { if (dev-> kbc_phase < 16) kbc_transmit(dev, dev->mem[dev->kbc_phase]); else if (dev-> kbc_phase == 16) kbc_transmit(dev, (dev->p1 & 0xf0) | 0x80); else if (dev-> kbc_phase == 17) kbc_transmit(dev, dev->p2); else if (dev-> kbc_phase == 18) kbc_transmit(dev, dev->status); dev->kbc_phase++; if (dev->kbc_phase == 19) { dev->kbc_phase = 0; dev->kbc_cmd = 0x00; } return; } else if ((dev->kbc_phase > 0) && (dev->kbc_cmd == 0xa0) && (kbc_ven >= KBC_VEN_AMI)) { val = ami_copr[dev->kbc_phase]; kbc_transmit(dev, val); if (val == 0x00) { dev->kbc_phase = 0; dev->kbc_cmd = 0x00; } else dev->kbc_phase++; return; } else if ((dev->kbc_in > 0) && (dev->kbc_cmd == 0xa5) && (dev->flags & KBC_FLAG_PS2)) { /* load security */ kbc_log(dev, "Load security\n"); dev->mem[0x50 + dev->kbc_in - 0x01] = val; if ((dev->kbc_in == 0x80) && (val != 0x00)) { /* Security string too long, set it to 0x00. */ dev->mem[0x50] = 0x00; dev->kbc_in = 0; dev->kbc_cmd = 0; } else if (val == 0x00) { /* Security string finished. */ dev->kbc_in = 0; dev->kbc_cmd = 0; } else /* Increase pointer and request another byte. */ dev->kbc_in++; return; } /* If the written port is 64, go straight to the beginning of the command. */ if (!(dev->status & STAT_CD) && dev->kbc_in) { /* Write data to controller. */ dev->kbc_in = 0; dev->kbc_phase = 0; switch (dev->kbc_cmd) { case 0x60 ... 0x7f: if (dev->kbc_cmd == 0x60) write_cmd(dev, val); else dev->mem[(dev->kbc_cmd & 0x1f) + 0x20] = val; break; case 0xc7: /* or input port with system data */ dev->p1 |= val; break; case 0xcb: /* set keyboard mode */ kbc_log(dev, "New AMIKey mode: %02X\n", val); dev->ami_mode = val; dev->flags &= ~KBC_FLAG_PS2; if (val & 1) dev->flags |= KBC_FLAG_PS2; #if (!defined(RELEASE_BUILD) && defined(ENABLE_KBD_AT_LOG)) log_set_dev_name(dev->kbc_log, (dev->flags & KBC_FLAG_PS2) ? "AT KBC" : "PS/2 KBC"); #endif break; case 0xd1: /* write output port */ if (dev->p2_locked) { /*If keyboard controller lines P22-P23 are blocked, we force them to remain unchanged.*/ val &= ~0x0c; val |= (dev->p2 & 0x0c); } kbc_log(dev, "Write %02X to output port\n", val); write_output(dev, val); break; case 0xd2: /* write to keyboard output buffer */ kbc_log(dev, "Write %02X to keyboard output buffer\n", val); /* Should be channel 1, but we send to 0 to avoid translation, since bytes output using this command do *NOT* get translated. */ kbc_send_to_ob(dev, val, 0, 0x00); break; case 0xd3: /* write to mouse output buffer */ kbc_log(dev, "Write %02X to mouse output buffer\n", val); if (dev->flags & KBC_FLAG_PS2) kbc_send_to_ob(dev, val, 2, 0x00); break; case 0xd4: /* write to mouse */ kbc_log(dev, "Write %02X to mouse\n", val); if (dev->flags & KBC_FLAG_PS2) { set_enable_mouse(dev, 1); dev->mem[0x20] &= ~0x20; if (dev->kbc_devs[1] && !dev->kbc_devs[1]->c_in) { kbc_log(dev, "Transmitting %02X to mouse...\n", dev->ib); dev->kbc_devs[1]->d_data = val; dev->kbc_devs[1]->d_in = 1; dev->kbc_wait_for_response = 2; } else kbc_send_to_ob(dev, 0xfe, 2, 0x40); } break; default: /* * Run the vendor-specific handler * if we have one. Otherwise, or if * it returns an error, log a bad * controller command. */ if (dev->write60_ven) bad = dev->write60_ven(dev, val); if (bad) kbc_log(dev, "Bad controller command %02x data %02x\n", dev->kbc_cmd, val); } } else { /* Controller command. */ kbc_log(dev, "Controller command: %02X\n", val); dev->kbc_in = 0; dev->kbc_phase = 0; switch (val) { /* Read data from KBC memory. */ case 0x20 ... 0x3f: kbc_transmit(dev, dev->mem[(val & 0x1f) + 0x20]); break; /* Write data to KBC memory. */ case 0x60 ... 0x7f: dev->kbc_in = 1; break; case 0xaa: /* self-test */ kbc_log(dev, "Self-test\n"); write_output(dev, (dev->flags & KBC_FLAG_PS2) ? 0x4b : 0xcf); /* Always reinitialize all queues - the real hardware pulls keyboard and mouse clocks high, which stops keyboard scanning. */ dev->in_cmd = dev->mouse_in_cmd = 0; dev->status &= ~STAT_OFULL; dev->last_irq = 0; dev->kbc_phase = 0; /* Phoenix MultiKey should have 0x60 | STAT_SYSFLAG. */ if (dev->flags & KBC_FLAG_PS2) write_cmd(dev, 0x30 | STAT_SYSFLAG); else write_cmd(dev, 0x10 | STAT_SYSFLAG); kbc_transmit(dev, 0x55); break; case 0xab: /* interface test */ kbc_log(dev, "Interface test\n"); /* No error. */ kbc_transmit(dev, 0x00); break; case 0xac: /* diagnostic dump */ kbc_log(dev, "Diagnostic dump\n"); kbc_transmit(dev, dev->mem[0x20]); dev->kbc_phase = 1; break; case 0xad: /* disable keyboard */ kbc_log(dev, "Disable keyboard\n"); set_enable_kbd(dev, 0); break; case 0xae: /* enable keyboard */ kbc_log(dev, "Enable keyboard\n"); set_enable_kbd(dev, 1); break; case 0xc7: /* or input port with system data */ kbc_log(dev, "Phoenix - or input port with system data\n"); dev->kbc_in = 1; break; case 0xca: /* read keyboard mode */ kbc_log(dev, "AMI - Read keyboard mode\n"); kbc_transmit(dev, dev->ami_mode); break; case 0xcb: /* set keyboard mode */ kbc_log(dev, "ATkbc: AMI - Set keyboard mode\n"); dev->kbc_in = 1; break; case 0xd0: /* read output port */ kbc_log(dev, "Read output port\n"); mask = 0xff; if (dev->mem[0x20] & 0x10) mask &= 0xbf; if ((dev->flags & KBC_FLAG_PS2) && (dev->mem[0x20] & 0x20)) mask &= 0xf7; kbc_transmit(dev, dev->p2 & mask); break; case 0xd1: /* write output port */ kbc_log(dev, "Write output port\n"); dev->kbc_in = 1; break; case 0xd2: /* write keyboard output buffer */ kbc_log(dev, "Write keyboard output buffer\n"); if (dev->flags & KBC_FLAG_PS2) dev->kbc_in = 1; else kbc_transmit(dev, 0x00); /* NCR */ break; case 0xdd: /* disable A20 address line */ case 0xdf: /* enable A20 address line */ kbc_log(dev, "%sable A20\n", (val == 0xdd) ? "Dis": "En"); write_output(dev, (dev->p2 & 0xfd) | (val & 0x02)); break; case 0xe0: /* read test inputs */ kbc_log(dev, "Read test inputs\n"); kbc_transmit(dev, 0x00); break; default: /* * Unrecognized controller command. * * If we have a vendor-specific handler, run * that. Otherwise, or if that handler fails, * log a bad command. */ if (dev->write64_ven) bad = dev->write64_ven(dev, val); if (bad) kbc_log(dev, "Bad controller command %02X\n", val); } /* If the command needs data, remember the command. */ if (dev->kbc_in || (dev->kbc_phase > 0)) dev->kbc_cmd = val; } } static void kbc_dev_data_to_ob(atkbc_t *dev, uint8_t channel) { if (channel == 0) return; dev->kbc_devs[channel - 1]->c_in = 0; kbc_log(dev, "Forwarding %02X from channel %i...\n", dev->kbc_devs[channel - 1]->c_data, channel); kbc_send_to_ob(dev, dev->kbc_devs[channel - 1]->c_data, channel, 0x00); } static void kbc_main_loop_scan(atkbc_t *dev) { uint8_t port_dis = dev->mem[0x20] & 0x30; uint8_t ps2 = (dev->flags & KBC_FLAG_PS2); if (!ps2) port_dis |= 0x20; if (!(dev->status & STAT_OFULL)) { if (port_dis & 0x20) { if (!(port_dis & 0x10)) { kbc_log(dev, "kbc_process(): Main loop, Scan: AUX DIS, KBD EN\n"); /* Enable communication with keyboard. */ dev->p2 &= 0xbf; dev->kbc_devs[0]->inhibit = 0; kbc_wait(dev, 1); } else kbc_log(dev, "kbc_process(): Main loop, Scan: AUX DIS, KBD DIS\n"); } else { /* Enable communication with mouse. */ dev->p2 &= 0xf7; dev->kbc_devs[1]->inhibit = 0; if (dev->mem[0x20] & 0x10) { kbc_log(dev, "kbc_process(): Main loop, Scan: AUX EN , KBD DIS\n"); kbc_wait(dev, 2); } else { /* Enable communication with keyboard. */ kbc_log(dev, "kbc_process(): Main loop, Scan: AUX EN , KBD EN\n"); dev->p2 &= 0xbf; dev->kbc_devs[0]->inhibit = 0; kbc_wait(dev, 3); } } } else kbc_log(dev, "kbc_process(): Main loop, Scan: IBF not full and OBF full, do nothing\n"); } static uint8_t kbc_reset_cmd(atkbc_t *dev) { uint8_t ret = 0; if ((dev->status & STAT_CD) || (dev->kbc_poll_phase == KBC_WAIT_FOR_NOBF)) { kbc_log(dev, " Resetting command\n"); dev->kbc_phase = 0; dev->kbc_in = 0; dev->kbc_in_cmd = 0; dev->kbc_poll_phase = KBC_MAIN_LOOP; ret = 1; } return ret; } static uint8_t kbc_process_cmd(atkbdt_t *dev, uint8_t restart) { uint8_t ret = 0; if (restart) dev->kbc_in_cmd = 1; kbc_command(dev); if ((dev->kbc_phase == 0) && !dev->kbc_in) dev->kbc_in_cmd = 0; else ret = 1; dev->kbc_poll_phase = KBC_MAIN_LOOP; if (!dev->kbc_wait_for_response && !(dev->status & STAT_OFULL)) kbc_main_loop_scan(dev); return ret; } static void kbc_process_ib(atkbc_t *dev) { if ((dev->status & STAT_CD) || (kbc->flags & KBC_FLAG_PS2) || !(dev->status & STAT_OFULL)) dev->status &= ~STAT_IFULL; if (dev->status & STAT_CD) (void) kbc_process_cmd(dev, 1); else if ((kbc->flags & KBC_FLAG_PS2) || !(dev->status & STAT_OFULL)) /* The AT KBC does *NOT* send data to the keyboard if OBF. */ set_enable_mouse(dev, 1); dev->mem[0x20] &= ~0x10; if (dev->kbc_devs[0] && !dev->kbc_devs[0]->c_in) { dev->kbc_devs[0]->d_data = val; dev->kbc_devs[0]->d_in = 1; dev->kbc_wait_for_response = 1; } else kbc_send_to_ob(dev, 0xfe, 1, 0x40); dev->kbc_poll_phase = KBC_MAIN_LOOP; if (!dev->kbc_wait_for_response && !(dev->status & STAT_OFULL)) kbc_main_loop_scan(dev); } } static void kbc_wait(atkbc_t *dev, uint8_t flags) { if ((flags & 1) && dev->kbc_devs[0]->c_in) { /* Disable communication with mouse. */ dev->p2 |= 0x08; dev->kbc_devs[1]->inhibit = 1; /* Send keyboard byte to host. */ kbc_dev_data_to_ob(dev, CHANNEL_KBD); dev->kbc_poll_phase = KBC_MAIN_LOOP; } else if ((flags & 2) && dev->kbc_devs[1]->c_in) { /* Disable communication with keyboard. */ dev->p2 |= 0x40; dev->kbc_devs[0]->inhibit = 1; /* Send mouse byte to host. */ kbc_dev_data_to_ob(dev, CHANNEL_MOUSE); dev->kbc_poll_phase = KBC_MAIN_LOOP; } else if (dev->status & STAT_IFULL) { /* Disable communication with keyboard and mouse. */ dev->p2 |= 0x48; dev->kbc_devs[0]->inhibit = dev->kbc_devs[1]->inhibit = 1; kbc_process_ib(dev); } else dev->kbc_poll_phase = KBC_WAIT | flags; } /* Controller processing */ static void kbc_process(atkbc_t *dev) { /* If we're waiting for the response from the keyboard or mouse, do nothing until the device has repsonded back. */ if (dev->kbc_wait_for_response > 0) { if (dev->kbc_devs[dev->kbc_wait_for_response - 1]->c_in) dev->kbc_wait_for_response = 0; else return; } if (dev->kbc_send_pending) { kbc_log(dev, "Sending delayed %02X on channel %i with high status %02X\n", dev->kbc_to_send, dev->kbc_channel, dev->kbc_stat_hi); kbc_send_to_ob(dev, dev->kbc_to_send, dev->kbc_channel, dev->kbc_stat_hi); dev->kbc_send_pending = 0; } /* Make absolutely sure to do nothing if OBF is full and IBF is empty. */ if ((dev->kbc_poll_phase == KBC_RESET) || (dev->kbc_poll_phase >= KBC_WAIT_FOR_NIBF) || !(dev->status & STAT_OFULL) || (dev->status & STAT_IFULL)) switch (dev->kbc_poll_phase) { case KBC_RESET: kbc_log(dev, "kbc_process(): Reset loop()\n"); if (dev->status & STAT_IFULL) { dev->status &= ~STAT_IFULL; if ((dev->status & STAT_CD) && (dev->ib == 0xaa)) { (void) kbc_process_cmd(dev, 1); dev->kbc_poll_phase = KBC_MAIN_LOOP; } } break; case KBC_MAIN_LOOP: if (dev->status & STAT_IFULL) { kbc_log(dev, "kbc_process(): Main loop, IBF full, process\n"); kbc_process_ib(dev); } else kbc_main_loop_scan(dev); break; case KBC_SCAN_KBD: case KBC_SCAN_MOUSE: case KBC_SCAN_BOTH: kbc_log(dev, "kbc_process(): Scan: Phase %i\n", dev->kbc_poll_phase); kbc_wait(dev, dev->kbc_poll_phase & 3); break; case KBC_WAIT_FOR_NOBF: kbc_log(dev, "kbc_process(): Waiting for !OBF\n"); if (dev->status & STAT_IFULL) { /* Host writing a command aborts the current command. */ (void) !kbc_reset_cmd(dev); /* Process the input buffer. */ kbc_process_ib(dev); } else if (!dev->status & STAT_OFULL) { /* Not aborted and OBF cleared - process command. */ kbc_log(dev, " Continuing commmand\n"); if (kbc_process_cmd(dev, 0)) return; } break; case KBC_WAIT_FOR_IBF: kbc_log(dev, "kbc_process(): Waiting for IBF\n"); if (dev->status & STAT_IFULL) { /* IBF, process if port 60h, otherwise abort the current command. */ dev->status &= ~STAT_IFULL; if (!kbc_reset_cmd(dev)) kbc_log(dev, " Continuing commmand\n"); /* Process command. */ if (kbc_process_cmd(dev, 0)) return; } break; default: kbc_log(dev, "kbc_process(): Invalid phase %i\n", dev->kbc_poll_phase); break; } } static void kbd_poll(void *priv) { atkbc_t *dev = (atkbc_t *) priv; uint8_t i; if (dev == NULL) return; timer_advance_u64(&dev->send_delay_timer, (100ULL * TIMER_USEC)); /* Device processing */ for (i = 0; i < 2; i++) { if (dev->kbc_devs[i] && dev->kbd_devs[i]->priv && dev->kbd_devs[i]->process) dev->kbc_devs[i]->process(dev->kbc_devs[i]->priv); } /* Controller processing */ kbc_process(dev); } static void pulse_poll(void *priv) { atkbc_t *dev = (atkbc_t *)priv; kbc_log(dev, "pulse_poll(): Output port now: %02X\n", dev->p2 | dev->old_p2); write_output(dev, dev->p2 | dev->old_p2); } static uint8_t write64_generic(void *priv, uint8_t val) { atkbc_t *dev = (atkbc_t *)priv; uint8_t current_drive, fixed_bits; uint8_t kbc_ven = 0x0; kbc_ven = dev->flags & KBC_VEN_MASK; switch (val) { case 0xa4: /* check if password installed */ if (dev->flags & KBC_FLAG_PS2) { kbc_log(dev, "Check if password installed\n"); kbc_transmit(dev, (dev->mem[0x50] == 0x00) ? 0xf1 : 0xfa); return 0; } break; case 0xa5: /* load security */ if (dev->flags & KBC_FLAG_PS2) { kbc_log(dev, "Load security\n"); dev->kbc_in = 1; return 0; } break; case 0xa7: /* disable mouse port */ if (dev->flags & KBC_FLAG_PS2) { kbc_log(dev, "Disable mouse port\n"); return 0; } break; case 0xa8: /*Enable mouse port*/ if (dev->flags & KBC_FLAG_PS2) { kbc_log(dev, "Enable mouse port\n"); return 0; } break; case 0xa9: /*Test mouse port*/ kbc_log(dev, "Test mouse port\n"); if (dev->flags & KBC_FLAG_PS2) { /* No error, this is testing the channel 2 interface. */ kbc_transmit(dev, 0x00); return 0; } break; case 0xaf: /* read keyboard version */ kbc_log(dev, "Read keyboard version\n"); kbc_transmit(dev, 0x00); return 0; case 0xc0: /* read input port */ /* IBM PS/1: Bit 2 and 4 ignored (we return always 0), Bit 6 must 1 for 5.25" floppy drive, 0 for 3.5". Intel AMI: Bit 2 ignored (we return always 1), Bit 4 must be 1, Bit 6 must be 1 or else error in SMM. Acer: Bit 2 must be 0, Bit 4 must be 0, Bit 6 ignored. P6RP4: Bit 2 must be 1 or CMOS setup is disabled. */ kbc_log(dev, "Read input port\n"); fixed_bits = 4; /* The SMM handlers of Intel AMI Pentium BIOS'es expect bit 6 to be set. */ if (kbc_ven == KBC_VEN_INTEL_AMI) fixed_bits |= 0x40; if (kbc_ven == KBC_VEN_IBM_PS1) { current_drive = fdc_get_current_drive(); kbc_transmit(dev, dev->p1 | fixed_bits | (fdd_is_525(current_drive) ? 0x40 : 0x00)); dev->p1 = ((dev->p1 + 1) & 3) | (dev->p1 & 0xfc) | (fdd_is_525(current_drive) ? 0x40 : 0x00); } else if (kbc_ven == KBC_VEN_NCR) { /* switch settings * bit 7: keyboard disable * bit 6: display type (0 color, 1 mono) * bit 5: power-on default speed (0 high, 1 low) * bit 4: sense RAM size (0 unsupported, 1 512k on system board) * bit 3: coprocessor detect * bit 2: unused * bit 1: high/auto speed * bit 0: dma mode */ kbc_transmit(dev, (dev->p1 | fixed_bits | (video_is_mda() ? 0x40 : 0x00) | (hasfpu ? 0x08 : 0x00)) & 0xdf); dev->p1 = ((dev->p1 + 1) & 3) | (dev->p1 & 0xfc); } else { if ((dev->flags & KBC_FLAG_PS2) && ((dev->flags & KBC_VEN_MASK) != KBC_VEN_INTEL_AMI)) kbc_transmit(dev, (dev->p1 | fixed_bits) & (((dev->flags & KBC_VEN_MASK) == KBC_VEN_ACER) ? 0xeb : 0xef)); else kbc_transmit(dev, dev->p1 | fixed_bits); dev->p1 = ((dev->p1 + 1) & 3) | (dev->p1 & 0xfc); } return 0; case 0xd3: /* write mouse output buffer */ if (dev->flags & KBC_FLAG_PS2) { kbc_log(dev, "Write mouse output buffer\n"); dev->kbc_in = 1; return 0; } break; case 0xd4: /* write to mouse */ kbc_log(dev, "Write to mouse\n"); dev->kbc_in = 1; return 0; case 0xf0 ... 0xff: kbc_log(dev, "Pulse %01X\n", val & 0x0f); pulse_output(dev, val & 0x0f); return 0; } kbc_log(dev, "Bad command %02X\n", val); return 1; } static uint8_t write60_ami(void *priv, uint8_t val) { atkbc_t *dev = (atkbc_t *)priv; uint16_t index = 0x00c0; switch(dev->kbc_cmd) { /* 0x40 - 0x5F are aliases for 0x60 - 0x7F */ case 0x40 ... 0x5f: kbc_log(dev, "AMI - Alias write to %08X\n", dev->kbc_cmd); if (dev->kbc_cmd == 0x40) write_cmd(dev, val); else dev->mem[(dev->kbc_cmd & 0x1f) + 0x20] = val; return 0; case 0xaf: /* set extended controller RAM */ kbc_log(dev, "AMI - Set extended controller RAM, input phase %i\n", dev->secr_phase); if (dev->secr_phase == 0) { dev->mem_index = val; dev->kbc_in = 1; dev->secr_phase++; } else if (dev->secr_phase == 1) { if (dev->mem_index == 0x20) write_cmd(dev, val); else dev->mem[dev->mem_index] = val; dev->secr_phase = 0; } return 0; case 0xb8: kbc_log(dev, "AMIKey-3 - Memory index %02X\n", val); dev->mem_index = val; return 0; case 0xbb: kbc_log(dev, "AMIKey-3 - write %02X to memory index %02X\n", val, dev->mem_index); if (dev->mem_index >= 0x80) { switch (dev->mem[0x9b] & 0xc0) { case 0x00: index = 0x0080; break; case 0x40: case 0x80: index = 0x0000; break; case 0xc0: index = 0x0100; break; } dev->mem[index + dev->mem_index] = val; } else if (dev->mem_index == 0x60) write_cmd(dev, val); else if (dev->mem_index == 0x42) dev->status = val; else if (dev->mem_index >= 0x40) dev->mem[dev->mem_index - 0x40] = val; else dev->mem_int[dev->mem_index] = val; return 0; case 0xbd: kbc_log(dev, "AMIKey-3 - write %02X to config index %02X\n", val, dev->mem_index); switch (dev->mem_index) { case 0x00: /* STAT8042 */ dev->status = val; break; case 0x01: /* Password_ptr */ dev->mem[0x1c] = val; break; case 0x02: /* Wakeup_Tsk_Reg */ dev->mem[0x1e] = val; break; case 0x03: /* CCB */ write_cmd(dev, val); break; case 0x04: /* Debounce_time */ dev->mem[0x4d] = val; break; case 0x05: /* Pulse_Width */ dev->mem[0x4e] = val; break; case 0x06: /* Pk_sel_byte */ dev->mem[0x4c] = val; break; case 0x07: /* Func_Tsk_Reg */ dev->mem[0x7e] = val; break; case 0x08: /* TypematicRate */ dev->mem[0x80] = val; break; case 0x09: /* Led_Flag_Byte */ dev->mem[0x81] = val; break; case 0x0a: /* Kbms_Command_St */ dev->mem[0x87] = val; break; case 0x0b: /* Delay_Count_Byte */ dev->mem[0x86] = val; break; case 0x0c: /* KBC_Flags */ dev->mem[0x9b] = val; break; case 0x0d: /* SCODE_HK1 */ dev->mem[0x50] = val; break; case 0x0e: /* SCODE_HK2 */ dev->mem[0x51] = val; break; case 0x0f: /* SCODE_HK3 */ dev->mem[0x52] = val; break; case 0x10: /* SCODE_HK4 */ dev->mem[0x53] = val; break; case 0x11: /* SCODE_HK5 */ dev->mem[0x54] = val; break; case 0x12: /* SCODE_HK6 */ dev->mem[0x55] = val; break; case 0x13: /* TASK_HK1 */ dev->mem[0x56] = val; break; case 0x14: /* TASK_HK2 */ dev->mem[0x57] = val; break; case 0x15: /* TASK_HK3 */ dev->mem[0x58] = val; break; case 0x16: /* TASK_HK4 */ dev->mem[0x59] = val; break; case 0x17: /* TASK_HK5 */ dev->mem[0x5a] = val; break; /* The next 4 bytes have uncertain correspondences. */ case 0x18: /* Batt_Poll_delay_Time */ dev->mem[0x5b] = val; break; case 0x19: /* Batt_Alarm_Reg1 */ dev->mem[0x5c] = val; break; case 0x1a: /* Batt_Alarm_Reg2 */ dev->mem[0x5d] = val; break; case 0x1b: /* Batt_Alarm_Tsk_Reg */ dev->mem[0x5e] = val; break; case 0x1c: /* Kbc_State1 */ dev->mem[0x9d] = val; break; case 0x1d: /* Aux_Config */ dev->mem[0x75] = val; break; case 0x1e: /* Kbc_State3 */ dev->mem[0x73] = val; break; } return 0; case 0xc1: /* write input port */ kbc_log(dev, "AMI MegaKey - write %02X to input port\n", val); dev->p1 = val; return 0; case 0xcb: /* set keyboard mode */ kbc_log(dev, "AMI - Set keyboard mode\n"); return 0; } return 1; } static uint8_t write64_ami(void *priv, uint8_t val) { atkbc_t *dev = (atkbc_t *)priv; uint16_t index = 0x00c0; switch (val) { case 0x00 ... 0x1f: kbc_log(dev, "AMI - Alias read from %08X\n", val); kbc_transmit(dev, dev->mem[val + 0x20]); return 0; case 0x40 ... 0x5f: kbc_log(dev, "AMI - Alias write to %08X\n", dev->kbc_cmd); dev->kbc_in = 1; return 0; case 0xa0: /* copyright message */ kbc_log(dev, "AMI - Get copyright message\n"); kbc_transmit(dev, ami_copr[0]); dev->kbc_phase = 1; return 0; case 0xa1: /* get controller version */ kbc_log(dev, "AMI - Get controller version\n"); // kbc_transmit(dev, 'H'); kbc_transmit(dev, '5'); return 0; case 0xa2: /* clear keyboard controller lines P22/P23 */ if (!(dev->flags & KBC_FLAG_PS2)) { kbc_log(dev, "AMI - Clear KBC lines P22 and P23\n"); write_output(dev, dev->p2 & 0xf3); kbc_transmit(dev, 0x00); return 0; } break; case 0xa3: /* set keyboard controller lines P22/P23 */ if (!(dev->flags & KBC_FLAG_PS2)) { kbc_log(dev, "AMI - Set KBC lines P22 and P23\n"); write_output(dev, dev->p2 | 0x0c); kbc_transmit(dev, 0x00); return 0; } break; case 0xa4: /* write clock = low */ if (!(dev->flags & KBC_FLAG_PS2)) { kbc_log(dev, "AMI - Write clock = low\n"); dev->ami_stat &= 0xfe; return 0; } break; case 0xa5: /* write clock = high */ if (!(dev->flags & KBC_FLAG_PS2)) { kbc_log(dev, "AMI - Write clock = high\n"); dev->ami_stat |= 0x01; return 0; } break; case 0xa6: /* read clock */ if (!(dev->flags & KBC_FLAG_PS2)) { kbc_log(dev, "AMI - Read clock\n"); kbc_transmit(dev, !!(dev->ami_stat & 1)); return 0; } break; case 0xa7: /* write cache bad */ if (!(dev->flags & KBC_FLAG_PS2)) { kbc_log(dev, "AMI - Write cache bad\n"); dev->ami_stat &= 0xfd; return 0; } break; case 0xa8: /* write cache good */ if (!(dev->flags & KBC_FLAG_PS2)) { kbc_log(dev, "AMI - Write cache good\n"); dev->ami_stat |= 0x02; return 0; } break; case 0xa9: /* read cache */ if (!(dev->flags & KBC_FLAG_PS2)) { kbc_log(dev, "AMI - Read cache\n"); kbc_transmit(dev, !!(dev->ami_stat & 2)); return 0; } break; case 0xaf: /* set extended controller RAM */ kbc_log(dev, "AMI - Set extended controller RAM\n"); dev->kbc_in = 1; return 0; case 0xb0 ... 0xb3: /* set KBC lines P10-P13 (input port bits 0-3) low */ kbc_log(dev, "AMI - Set KBC lines P10-P13 (input port bits 0-3) low\n"); if (!(dev->flags & KBC_FLAG_PS2) || (val > 0xb1)) { dev->p1 &= ~(1 << (val & 0x03)); } kbc_transmit(dev, 0x00); return 0; case 0xb4: case 0xb5: /* set KBC lines P22-P23 (output port bits 2-3) low */ kbc_log(dev, "AMI - Set KBC lines P22-P23 (output port bits 2-3) low\n"); if (!(dev->flags & KBC_FLAG_PS2)) write_output(dev, dev->p2 & ~(4 << (val & 0x01))); kbc_transmit(dev, 0x00); return 0; #if 0 case 0xb8 ... 0xbb: #else case 0xb9: #endif /* set KBC lines P10-P13 (input port bits 0-3) high */ kbc_log(dev, "AMI - Set KBC lines P10-P13 (input port bits 0-3) high\n"); if (!(dev->flags & KBC_FLAG_PS2) || (val > 0xb9)) { dev->p1 |= (1 << (val & 0x03)); kbc_transmit(dev, 0x00); } return 0; case 0xb8: kbc_log(dev, "AMIKey-3 - memory index\n"); dev->kbc_in = 1; return 0; case 0xba: kbc_log(dev, "AMIKey-3 - read %02X memory from index %02X\n", dev->mem[dev->mem_index], dev->mem_index); if (dev->mem_index >= 0x80) { switch (dev->mem[0x9b] & 0xc0) { case 0x00: index = 0x0080; break; case 0x40: case 0x80: index = 0x0000; break; case 0xc0: index = 0x0100; break; } kbc_transmit(dev, dev->mem[index + dev->mem_index]); } else if (dev->mem_index == 0x42) kbc_transmit(dev, dev->status); else if (dev->mem_index >= 0x40) kbc_transmit(dev, dev->mem[dev->mem_index - 0x40]); else kbc_transmit(dev, dev->mem_int[dev->mem_index]); return 0; case 0xbb: kbc_log(dev, "AMIKey-3 - write to memory index %02X\n", dev->mem_index); dev->kbc_in = 1; return 0; #if 0 case 0xbc: case 0xbd: /* set KBC lines P22-P23 (output port bits 2-3) high */ kbc_log(dev, "AMI - Set KBC lines P22-P23 (output port bits 2-3) high\n"); if (!(dev->flags & KBC_FLAG_PS2)) write_output(dev, dev->p2 | (4 << (val & 0x01))); kbc_transmit(dev, 0x00); return 0; #endif case 0xbc: switch (dev->mem_index) { case 0x00: /* STAT8042 */ kbc_transmit(dev, dev->status); break; case 0x01: /* Password_ptr */ kbc_transmit(dev, dev->mem[0x1c]); break; case 0x02: /* Wakeup_Tsk_Reg */ kbc_transmit(dev, dev->mem[0x1e]); break; case 0x03: /* CCB */ kbc_transmit(dev, dev->mem[0x20]); break; case 0x04: /* Debounce_time */ kbc_transmit(dev, dev->mem[0x4d]); break; case 0x05: /* Pulse_Width */ kbc_transmit(dev, dev->mem[0x4e]); break; case 0x06: /* Pk_sel_byte */ kbc_transmit(dev, dev->mem[0x4c]); break; case 0x07: /* Func_Tsk_Reg */ kbc_transmit(dev, dev->mem[0x7e]); break; case 0x08: /* TypematicRate */ kbc_transmit(dev, dev->mem[0x80]); break; case 0x09: /* Led_Flag_Byte */ kbc_transmit(dev, dev->mem[0x81]); break; case 0x0a: /* Kbms_Command_St */ kbc_transmit(dev, dev->mem[0x87]); break; case 0x0b: /* Delay_Count_Byte */ kbc_transmit(dev, dev->mem[0x86]); break; case 0x0c: /* KBC_Flags */ kbc_transmit(dev, dev->mem[0x9b]); break; case 0x0d: /* SCODE_HK1 */ kbc_transmit(dev, dev->mem[0x50]); break; case 0x0e: /* SCODE_HK2 */ kbc_transmit(dev, dev->mem[0x51]); break; case 0x0f: /* SCODE_HK3 */ kbc_transmit(dev, dev->mem[0x52]); break; case 0x10: /* SCODE_HK4 */ kbc_transmit(dev, dev->mem[0x53]); break; case 0x11: /* SCODE_HK5 */ kbc_transmit(dev, dev->mem[0x54]); break; case 0x12: /* SCODE_HK6 */ kbc_transmit(dev, dev->mem[0x55]); break; case 0x13: /* TASK_HK1 */ kbc_transmit(dev, dev->mem[0x56]); break; case 0x14: /* TASK_HK2 */ kbc_transmit(dev, dev->mem[0x57]); break; case 0x15: /* TASK_HK3 */ kbc_transmit(dev, dev->mem[0x58]); break; case 0x16: /* TASK_HK4 */ kbc_transmit(dev, dev->mem[0x59]); break; case 0x17: /* TASK_HK5 */ kbc_transmit(dev, dev->mem[0x5a]); break; /* The next 4 bytes have uncertain correspondences. */ case 0x18: /* Batt_Poll_delay_Time */ kbc_transmit(dev, dev->mem[0x5b]); break; case 0x19: /* Batt_Alarm_Reg1 */ kbc_transmit(dev, dev->mem[0x5c]); break; case 0x1a: /* Batt_Alarm_Reg2 */ kbc_transmit(dev, dev->mem[0x5d]); break; case 0x1b: /* Batt_Alarm_Tsk_Reg */ kbc_transmit(dev, dev->mem[0x5e]); break; case 0x1c: /* Kbc_State1 */ kbc_transmit(dev, dev->mem[0x9d]); break; case 0x1d: /* Aux_Config */ kbc_transmit(dev, dev->mem[0x75]); break; case 0x1e: /* Kbc_State3 */ kbc_transmit(dev, dev->mem[0x73]); break; default: kbc_transmit(dev, 0x00); break; } kbc_log(dev, "AMIKey-3 - read from config index %02X\n", dev->mem_index); return 0; case 0xbd: kbc_log(dev, "AMIKey-3 - write to config index %02X\n", dev->mem_index); dev->kbc_in = 1; return 0; case 0xc1: /* write input port */ kbc_log(dev, "AMIKey-3 - write input port\n"); dev->kbc_in = 1; return 0; case 0xc8: case 0xc9: /* * (un)block KBC lines P22/P23 * (allow command D1 to change bits 2/3 of the output port) */ kbc_log(dev, "AMI - %slock KBC lines P22 and P23\n", (val & 1) ? "B" : "Unb"); dev->p2_locked = (val & 1); return 0; case 0xef: /* ??? - sent by AMI486 */ kbc_log(dev, "??? - sent by AMI486\n"); return 0; } return write64_generic(dev, val); } static uint8_t write64_ibm_mca(void *priv, uint8_t val) { atkbc_t *dev = (atkbc_t *)priv; switch (val) { case 0xc1: /*Copy bits 0 to 3 of input port to status bits 4 to 7*/ kbc_log(dev, "Copy bits 0 to 3 of input port to status bits 4 to 7\n"); dev->status &= 0x0f; dev->status |= ((((dev->p1 & 0xfc) | 0x84) & 0x0f) << 4); return 0; case 0xc2: /*Copy bits 4 to 7 of input port to status bits 4 to 7*/ kbc_log(dev, "Copy bits 4 to 7 of input port to status bits 4 to 7\n"); dev->status &= 0x0f; dev->status |= (((dev->p1 & 0xfc) | 0x84) & 0xf0); return 0; case 0xaf: kbc_log(dev, "Bad KBC command AF\n"); return 1; case 0xf0 ... 0xff: kbc_log(dev, "Pulse: %01X\n", (val & 0x03) | 0x0c); pulse_output(dev, (val & 0x03) | 0x0c); return 0; } return write64_generic(dev, val); } static uint8_t write60_quadtel(void *priv, uint8_t val) { atkbc_t *dev = (atkbc_t *)priv; switch(dev->kbc_cmd) { case 0xcf: /*??? - sent by MegaPC BIOS*/ kbc_log(dev, "??? - sent by MegaPC BIOS\n"); return 0; } return 1; } static uint8_t write64_olivetti(void *priv, uint8_t val) { atkbc_t *dev = (atkbc_t *)priv; switch (val) { /* This appears to be a clone of "Read input port", in which case, the bis would be: 7: M290 (AT KBC): Keyboard lock (1 = unlocked, 0 = locked); M300 (PS/2 KBC): Bus expansion board present (1 = present, 0 = not present); 6: Usually: Display (1 = MDA, 0 = CGA, but can have its polarity inverted); 5: Manufacturing jumper (1 = not installed, 0 = installed (infinite loop)); 4: RAM on motherboard (1 = 256 kB, 0 = 512 kB - which machine actually uses this?); 3: Fast Ram check (if inactive keyboard works erratically); 2: Keyboard fuse present This appears to be in-line with PS/2: 1 = no power, 0 = keyboard power normal; 1: M290 (AT KBC): Unused; M300 (PS/2 KBC): Mouse data in; 0: M290 (AT KBC): Unused; M300 (PS/2 KBC): Key data in. */ case 0x80: /* Olivetti-specific command */ /* * bit 7: bus expansion board present (M300) / keyboard unlocked (M290) * bits 4-6: ??? * bit 3: fast ram check (if inactive keyboard works erratically) * bit 2: keyboard fuse present * bits 0-1: ??? */ kbc_transmit(dev, 0x0c | (is386 ? 0x00 : 0x80)); return 0; } return write64_generic(dev, val); } static uint8_t write64_quadtel(void *priv, uint8_t val) { atkbc_t *dev = (atkbc_t *)priv; switch (val) { case 0xaf: kbc_log(dev, "Bad KBC command AF\n"); return 1; case 0xcf: /*??? - sent by MegaPC BIOS*/ kbc_log(dev, "??? - sent by MegaPC BIOS\n"); dev->kbc_in = 1; return 0; } return write64_generic(dev, val); } static uint8_t write60_toshiba(void *priv, uint8_t val) { atkbc_t *dev = (atkbc_t *)priv; switch(dev->kbc_cmd) { case 0xb6: /* T3100e - set color/mono switch */ kbc_log(dev, "T3100e - Set color/mono switch\n"); t3100e_mono_set(val); return 0; } return 1; } static uint8_t write64_toshiba(void *priv, uint8_t val) { atkbc_t *dev = (atkbc_t *)priv; switch (val) { case 0xaf: kbc_log(dev, "Bad KBC command AF\n"); return 1; case 0xb0: /* T3100e: Turbo on */ kbc_log(dev, "T3100e: Turbo on\n"); t3100e_turbo_set(1); return 0; case 0xb1: /* T3100e: Turbo off */ kbc_log(dev, "T3100e: Turbo off\n"); t3100e_turbo_set(0); return 0; case 0xb2: /* T3100e: Select external display */ kbc_log(dev, "T3100e: Select external display\n"); t3100e_display_set(0x00); return 0; case 0xb3: /* T3100e: Select internal display */ kcd_log("T3100e: Select internal display\n"); t3100e_display_set(0x01); return 0; case 0xb4: /* T3100e: Get configuration / status */ kbc_log(dev, "T3100e: Get configuration / status\n"); kbc_transmit(dev, t3100e_config_get()); return 0; case 0xb5: /* T3100e: Get colour / mono byte */ kbc_log(dev, "T3100e: Get colour / mono byte\n"); kbc_transmit(dev, t3100e_mono_get()); return 0; case 0xb6: /* T3100e: Set colour / mono byte */ kbc_log(dev, "T3100e: Set colour / mono byte\n"); dev->kbc_in = 1; return 0; case 0xb7: /* T3100e: Emulate PS/2 keyboard */ case 0xb8: /* T3100e: Emulate AT keyboard */ dev->flags &= ~KBC_FLAG_PS2; if (val == 0xb7) { kbc_log(dev, "T3100e: Emulate PS/2 keyboard\n"); dev->flags |= KBC_FLAG_PS2; } else kbc_log(dev, "T3100e: Emulate AT keyboard\n"); #if (!defined(RELEASE_BUILD) && defined(ENABLE_KBD_AT_LOG)) log_set_dev_name(dev->kbc_log, (dev->flags & KBC_FLAG_PS2) ? "AT KBC" : "PS/2 KBC"); #endif return 0; case 0xbb: /* T3100e: Read 'Fn' key. Return it for right Ctrl and right Alt; on the real T3100e, these keystrokes could only be generated using 'Fn'. */ kbc_log(dev, "T3100e: Read 'Fn' key\n"); if (keyboard_recv(0xb8) || /* Right Alt */ keyboard_recv(0x9d)) /* Right Ctrl */ kbc_transmit(dev, 0x04); else kbc_transmit(dev, 0x00); return 0; case 0xbc: /* T3100e: Reset Fn+Key notification */ kbc_log(dev, "T3100e: Reset Fn+Key notification\n"); t3100e_notify_set(0x00); return 0; case 0xc0: /*Read input port*/ kbc_log(dev, "Read input port\n"); /* The T3100e returns all bits set except bit 6 which * is set by t3100e_mono_set() */ dev->p1 = (t3100e_mono_get() & 1) ? 0xff : 0xbf; kbc_transmit(dev, dev->p1); return 0; } return write64_generic(dev, val); } static void kbc_write(uint16_t port, uint8_t val, void *priv) { atkbc_t *dev = (atkbc_t *)priv; kbc_log(dev, "[%04X:%08X] write(%04X, %02X)\n", CS, cpu_state.pc, port, val); switch (port) { case 0x60: dev->status = (dev->status & ~STAT_CD) | STAT_IFULL; dev->ib = val; // kbd_status("Write %02X: %02X, Status = %02X\n", port, val, dev->status); #if 0 if ((dev->fast_a20_phase == 1)/* && ((val == 0xdd) || (val == 0xdf))*/) { dev->status &= ~STAT_IFULL; write_output(dev, val); dev->fast_a20_phase = 0; } #endif break; case 0x64: dev->status |= (STAT_CD | STAT_IFULL); dev->ib = val; // kbd_status("Write %02X: %02X, Status = %02X\n", port, val, dev->status); #if 0 if (val == 0xd1) { dev->status &= ~STAT_IFULL; dev->fast_a20_phase = 1; } else if (val == 0xfe) { dev->status &= ~STAT_IFULL; pulse_output(dev, 0x0e); } else if ((val == 0xad) || (val == 0xae)) { dev->status &= ~STAT_IFULL; if (val & 0x01) dev->mem[0x20] |= 0x10; else dev->mem[0x20] &= ~0x10; } else if (val == 0xa1) { dev->status &= ~STAT_IFULL; kbc_send_to_ob(dev, 'H', 0, 0x00); } #endif break; } } static uint8_t kbc_read(uint16_t port, void *priv) { atkbc_t *dev = (atkbc_t *)priv; uint8_t ret = 0xff; // if (dev->flags & KBC_FLAG_PS2) // cycles -= ISA_CYCLES(8); switch (port) { case 0x60: ret = dev->ob; dev->status &= ~STAT_OFULL; picintc(dev->last_irq); dev->last_irq = 0; break; case 0x64: ret = dev->status; break; default: kbc_log(dev, "Reading unknown port %02X\n", port); break; } kbc_log(dev, "[%04X:%08X] read(%04X) = %02X\n",CS, cpu_state.pc, port, ret); return(ret); } static void kbc_reset(void *priv) { atkbc_t *dev = (atkbc_t *)priv; int i; uint8_t kbc_ven = 0x0; kbc_ven = dev->flags & KBC_VEN_MASK; dev->status = STAT_UNLOCKED; dev->mem[0x20] = 0x01; dev->mem[0x20] |= CCB_TRANSLATE; write_output(dev, 0xcf); dev->last_irq = 0; dev->secr_phase = 0; dev->in = 0; dev->ami_mode = !!(dev->flags & KBC_FLAG_PS2); /* Set up the correct Video Type bits. */ dev->p1 = video_is_mda() ? 0xf0 : 0xb0; if ((kbc_ven == KBC_VEN_XI8088) || (kbc_ven == KBC_VEN_ACER)) dev->p1 ^= 0x40; if ((kbc_ven == KBC_VEN_AMI) || (dev->flags & KBC_FLAG_PS2)) dev->inhibit = ((dev->p1 & 0x80) >> 3); else dev->inhibit = 0x10; kbc_log(dev, "Input port = %02x\n", dev->p1); keyboard_mode = 0x02 | (dev->mem[0x20] & CCB_TRANSLATE); /* Enable keyboard, disable mouse. */ set_enable_kbd(dev, 1); keyboard_scan = 1; set_enable_mouse(dev, 0); mouse_scan = 0; dev->ob = 0xff; sc_or = 0; dev->mem[0x31] = 0xfe; } /* Reset the AT keyboard - this is needed for the PCI TRC and is done until a better solution is found. */ void keyboard_at_reset(void) { kbc_reset(SavedKbd); } void kbc_dev_attach(kbc_dev_t *kbc_dev, int channel) { if ((channel < 1) || (channel > 2)) log_fatal(saved_kbc->log, "Attaching device to invalid channel %i\n", channel); else { kbc_log(saved_kbc, "Attaching device to channel %i\n", channel); saved_kbc->kbc_devs[channel - 1] = kbc_dev; } } static void kbc_close(void *priv) { atkbc_t *dev = (atkbc_t *)priv; kbc_reset(dev); /* Stop timers. */ timer_disable(&dev->send_delay_timer); #if (!defined(RELEASE_BUILD) && defined(ENABLE_KBC_AT_LOG)) log_close(dev->log); #endif free(dev); } static void * kbc_init(const device_t *info) { atkbc_t *dev; dev = (atkbc_t *)malloc(sizeof(atkbc_t)); memset(dev, 0x00, sizeof(atkbc_t)); dev->flags = info->local; video_reset(gfxcard); dev->kbc_poll_phase = KBC_RESET; io_sethandler(0x0060, 1, kbc_read, NULL, NULL, kbc_write, NULL, NULL, dev); io_sethandler(0x0064, 1, kbc_read, NULL, NULL, kbc_write, NULL, NULL, dev); timer_add(&dev->send_delay_timer, kbd_poll, dev, 1); timer_add(&dev->pulse_cb, pulse_poll, dev, 0); #if (!defined(RELEASE_BUILD) && defined(ENABLE_KBC_AT_LOG)) dev->kbc_log = log_open((dev->flags & KBC_FLAG_PS2) ? "AT KBC" : "PS/2 KBC"); #endif dev->write60_ven = NULL; dev->write64_ven = NULL; switch(dev->flags & KBC_VEN_MASK) { case KBC_VEN_ACER: case KBC_VEN_GENERIC: case KBC_VEN_NCR: case KBC_VEN_IBM_PS1: case KBC_VEN_XI8088: dev->write64_ven = write64_generic; break; case KBC_VEN_OLIVETTI: /* The Olivetti controller is a special case - starts directly in the main loop instead of the reset loop. */ dev->kbc_poll_phase = KBC_MAIN_LOOP; dev->write64_ven = write64_olivetti; break; case KBC_VEN_AMI: case KBC_VEN_INTEL_AMI: dev->write60_ven = write60_ami; dev->write64_ven = write64_ami; break; case KBC_VEN_IBM_MCA: dev->write64_ven = write64_ibm_mca; break; case KBC_VEN_QUADTEL: dev->write60_ven = write60_quadtel; dev->write64_ven = write64_quadtel; break; case KBC_VEN_TOSHIBA: dev->write60_ven = write60_toshiba; dev->write64_ven = write64_toshiba; break; } kbc_reset(dev); /* Local variable, needed for device attaching. */ saved_kbc = dev; /* Add the actual keyboard. */ device_add(&keyboard_at_kbd_device); return(dev); } const device_t keyboard_at_device = { "PC/AT Keyboard", 0, KBC_TYPE_ISA | KBC_VEN_GENERIC, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; const device_t keyboard_at_ami_device = { "PC/AT Keyboard (AMI)", 0, KBC_TYPE_ISA | KBC_VEN_AMI, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; const device_t keyboard_at_toshiba_device = { "PC/AT Keyboard (Toshiba)", 0, KBC_TYPE_ISA | KBC_VEN_TOSHIBA, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; const device_t keyboard_at_olivetti_device = { "PC/AT Keyboard (Olivetti)", 0, KBC_TYPE_ISA | KBC_VEN_OLIVETTI, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; const device_t keyboard_at_ncr_device = { "PC/AT Keyboard (NCR)", 0, KBC_TYPE_ISA | KBC_VEN_NCR, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; const device_t keyboard_ps2_device = { "PS/2 Keyboard", 0, KBC_TYPE_PS2_1 | KBC_VEN_GENERIC, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; const device_t keyboard_ps2_ps1_device = { "PS/2 Keyboard (IBM PS/1)", 0, KBC_TYPE_PS2_1 | KBC_VEN_IBM_PS1, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; const device_t keyboard_ps2_ps1_pci_device = { "PS/2 Keyboard (IBM PS/1)", DEVICE_PCI, KBC_TYPE_PS2_1 | KBC_VEN_IBM_PS1, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; const device_t keyboard_ps2_xi8088_device = { "PS/2 Keyboard (Xi8088)", 0, KBC_TYPE_PS2_1 | KBC_VEN_XI8088, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; const device_t keyboard_ps2_ami_device = { "PS/2 Keyboard (AMI)", 0, KBC_TYPE_PS2_1 | KBC_VEN_AMI, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; const device_t keyboard_ps2_olivetti_device = { "PS/2 Keyboard (Olivetti)", 0, KBC_TYPE_PS2_1 | KBC_VEN_OLIVETTI, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; const device_t keyboard_ps2_mca_device = { "PS/2 Keyboard", 0, KBC_TYPE_PS2_1 | KBC_VEN_IBM_MCA, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; const device_t keyboard_ps2_mca_2_device = { "PS/2 Keyboard", 0, KBC_TYPE_PS2_2 | KBC_VEN_IBM_MCA, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; const device_t keyboard_ps2_quadtel_device = { "PS/2 Keyboard (Quadtel/MegaPC)", 0, KBC_TYPE_PS2_1 | KBC_VEN_QUADTEL, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; const device_t keyboard_ps2_pci_device = { "PS/2 Keyboard", DEVICE_PCI, KBC_TYPE_PS2_1 | KBC_VEN_GENERIC, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; const device_t keyboard_ps2_ami_pci_device = { "PS/2 Keyboard (AMI)", DEVICE_PCI, KBC_TYPE_PS2_1 | KBC_VEN_AMI, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; const device_t keyboard_ps2_intel_ami_pci_device = { "PS/2 Keyboard (AMI)", DEVICE_PCI, KBC_TYPE_PS2_1 | KBC_VEN_INTEL_AMI, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; const device_t keyboard_ps2_acer_pci_device = { "PS/2 Keyboard (Acer 90M002A)", DEVICE_PCI, KBC_TYPE_PS2_1 | KBC_VEN_ACER, kbc_init, kbc_close, kbc_reset, { NULL }, NULL, NULL, NULL }; void keyboard_at_set_mouse(void (*func)(uint8_t val, void *priv), void *priv) { } void keyboard_at_adddata_mouse(uint8_t val) { return; } void keyboard_at_adddata_mouse_direct(uint8_t val) { return; } void keyboard_at_adddata_mouse_cmd(uint8_t val) { return; } void keyboard_at_mouse_reset(void) { return; } uint8_t keyboard_at_mouse_pos(void) { return ((mouse_queue_end - mouse_queue_start) & 0xf); } int keyboard_at_fixed_channel(void) { return 0x000; } void keyboard_at_set_mouse_scan(uint8_t val) { atkbc_t *dev = SavedKbd; uint8_t temp_mouse_scan = val ? 1 : 0; if (temp_mouse_scan == !(dev->mem[0x20] & 0x20)) return; set_enable_mouse(dev, val ? 1 : 0); kbc_log(dev, "Mouse scan %sabled via PCI\n", mouse_scan ? "en" : "dis"); } uint8_t keyboard_at_get_mouse_scan(void) { atkbc_t *dev = SavedKbd; return((dev->mem[0x20] & 0x20) ? 0x00 : 0x10); } void keyboard_at_set_a20_key(int state) { atkbc_t *dev = SavedKbd; write_output(dev, (dev->p2 & 0xfd) | ((!!state) << 1)); } void keyboard_at_set_mode(int ps2) { atkbc_t *dev = SavedKbd; if (ps2) dev->flags |= KBC_FLAG_PS2; else dev->flags &= ~KBC_FLAG_PS2; }