437 lines
11 KiB
C
437 lines
11 KiB
C
/*
|
|
* (C) Copyright 1992, ..., 2014 the "DOSEMU-Development-Team".
|
|
*
|
|
* for details see file COPYING in the DOSEMU distribution
|
|
*/
|
|
|
|
/*
|
|
* DANG_BEGIN_MODULE
|
|
*
|
|
* Description: 8042 Keyboard controller chip emulation for DOSEMU.
|
|
*
|
|
* Exports: keyb_8042_init(void), keyb_8042_reset(void)
|
|
*
|
|
* Maintainers: Scott Buchholz, Rainer Zimmermann
|
|
*
|
|
* REMARK
|
|
* This code provides truly rudimentary 8042 controller emulation.
|
|
* Not having any documentation on the 8042 makes it hard to improve. :)
|
|
*
|
|
* /REMARK
|
|
* DANG_END_MODULE
|
|
*
|
|
*/
|
|
|
|
#include "emu.h"
|
|
#include "iodev.h"
|
|
#include "int.h"
|
|
#include "port.h"
|
|
#include "memory.h"
|
|
#include "keyboard/keyboard.h"
|
|
#include "keyboard/keyb_server.h"
|
|
#include "keyboard/keyb_clients.h"
|
|
#include "speaker.h"
|
|
#include "hma.h"
|
|
|
|
/* bios-assisted keyboard read hack */
|
|
#define KBD_READ_HACK 0
|
|
|
|
#define RESET_LINE_MASK 1
|
|
|
|
/* accurate emulation of special 8042 and keyboard commands - currently untested...
|
|
*/
|
|
#define KEYB_CMD 1
|
|
|
|
Bit8u port60_buffer = 0;
|
|
Boolean port60_ready = 0;
|
|
#if KBD_READ_HACK
|
|
static Bit8u last_read_data;
|
|
static Boolean last_read_valid;
|
|
#endif
|
|
static Boolean kbd_disabled;
|
|
|
|
#if KEYB_CMD
|
|
|
|
/* variable indicating the command status of the keyboard/8042.
|
|
* if non-zero, e.g. a parameter byte to a command is expected.
|
|
*/
|
|
static int wstate = 0;
|
|
static int rstate = 0;
|
|
|
|
static int keyb_ctrl_scanmap = 1;
|
|
static int keyb_ctrl_typematic = 0x23;
|
|
static int keyb_ctrl_enable = 1;
|
|
static int keyb_ctrl_isdata = 0;
|
|
static Bit8u keyb_ctrl_command = 0x01;
|
|
|
|
static inline void keyb_ctrl_clearbuf(void)
|
|
{
|
|
/* this probably ought to do something :) */
|
|
}
|
|
|
|
/* write byte to the 8042's output buffer */
|
|
void output_byte_8042(Bit8u value)
|
|
{
|
|
port60_buffer=value;
|
|
port60_ready=1;
|
|
if (keyb_ctrl_command & 0x01) { /* if interrupt enabled */
|
|
k_printf("8042: scheduling IRQ1\n");
|
|
pic_request(1);
|
|
}
|
|
else
|
|
k_printf("8042: interrupt flag OFF!\n");
|
|
}
|
|
|
|
static void ack(void)
|
|
{
|
|
write_queue(&keyb_queue, 0xfa);
|
|
}
|
|
|
|
static void write_port60(Bit8u value)
|
|
{
|
|
switch (wstate) {
|
|
case 0x00:
|
|
switch (value) {
|
|
case 0xed: /* set mode indicators */
|
|
h_printf("8042: write port 0x60 set mode indicators\n");
|
|
ack();
|
|
wstate=0xed;
|
|
break;
|
|
case 0xee: /* port check */
|
|
h_printf("8042: write port 0x60 test mode 0xee\n");
|
|
write_queue(&keyb_queue, 0xee);
|
|
break;
|
|
case 0xf0: /* set keyb scan byte */
|
|
h_printf("8042: write port 0x60 set keyb scan type\n");
|
|
ack();
|
|
wstate=0xf0;
|
|
break;
|
|
case 0xf2: /* get keyb type */
|
|
h_printf("8042: write port 0x60 get keyb type\n");
|
|
ack();
|
|
rstate=0xf2;
|
|
break;
|
|
case 0xf3: /* set typematic speed */
|
|
h_printf("8042: write port 0x60 set typematic speed\n");
|
|
ack();
|
|
wstate=0xf3;
|
|
break;
|
|
case 0xf4: /* clear buffer */
|
|
h_printf("8042: write port 0x60 clear buffer\n");
|
|
keyb_ctrl_clearbuf();
|
|
keyb_ctrl_enable=1;
|
|
ack();
|
|
break;
|
|
case 0xf5: /* default, w/disable */
|
|
h_printf("8042: write port 0x60 set default, w/disable\n");
|
|
keyb_8042_reset();
|
|
keyb_ctrl_enable = 0;
|
|
ack();
|
|
break;
|
|
case 0xf6: /* set default */
|
|
h_printf("8042: write port 0x60 set default\n");
|
|
keyb_8042_reset();
|
|
ack();
|
|
break;
|
|
case 0xf7: /* set all keys to typematic */
|
|
case 0xf8: /* set all keys to make/break */
|
|
case 0xf9: /* set all keys to make */
|
|
case 0xfa: /* set all keys to typematic make/break */
|
|
h_printf("8042: write port 0x60 set mode (0x%02x)\n", value);
|
|
keyb_ctrl_clearbuf();
|
|
ack();
|
|
/* set mode ??? */
|
|
break;
|
|
case 0xfb: /* set single key to typematic & wait */
|
|
case 0xfc: /* set to make/break & wait */
|
|
case 0xfd: /* set to make & wait */
|
|
h_printf("8042: write port 0x60 set mod (0x%02x)\n", value);
|
|
keyb_ctrl_clearbuf();
|
|
ack();
|
|
wstate=value;
|
|
break;
|
|
case 0xfe: /* resend */
|
|
h_printf("8042: write port 0x60 resend\n");
|
|
write_queue(&keyb_queue, port60_buffer);
|
|
break;
|
|
case 0xff: /* reset */
|
|
h_printf("8042: write port 0x60 reset\n");
|
|
ack();
|
|
rstate=0xff; /* wait for port 60h read */
|
|
break;
|
|
default:
|
|
h_printf("8042: write port 0x60 unsupported command 0x%02x =>Error\n", value);
|
|
write_queue(&keyb_queue, 0xfe);
|
|
break;
|
|
}
|
|
break;
|
|
case 0x60:
|
|
h_printf("8042: write 8042 command byte 0x%02x\n", value);
|
|
keyb_ctrl_command=value;
|
|
/* no ack() */
|
|
wstate=0;
|
|
break;
|
|
case 0xd1:
|
|
h_printf("8042: drive output port lines, value=0x%02x\n", value);
|
|
switch (value) {
|
|
case 0xdf: /* enable A20 */
|
|
h_printf("8042: enable A20 line\n");
|
|
set_a20(1);
|
|
break;
|
|
case 0xdd: /* disable A20) */
|
|
h_printf("8042: disable A20 line\n");
|
|
set_a20(0);
|
|
break;
|
|
}
|
|
port60_ready=0;
|
|
wstate=0;
|
|
break;
|
|
case 0xed: /* set LED mode */
|
|
{
|
|
t_modifiers modifiers = 0;
|
|
h_printf("8042: write port 0x60 set LED mode to 0x%02x\n", value);
|
|
/* TESTME this mapping is an educated guess */
|
|
if (value & 0x01) modifiers |= MODIFIER_SCR;
|
|
if (value & 0x02) modifiers |= MODIFIER_NUM;
|
|
if (value & 0x04) modifiers |= MODIFIER_CAPS;
|
|
keyb_client_set_leds(modifiers);
|
|
ack();
|
|
wstate=0;
|
|
break;
|
|
}
|
|
case 0xf0: /* get/set keyboard scan map */
|
|
h_printf("8042: write port 0x60 get/set keyboard scan map 0x%02x\n", value);
|
|
ack();
|
|
if (value == 0)
|
|
write_queue(&keyb_queue, keyb_ctrl_scanmap);
|
|
else
|
|
keyb_ctrl_scanmap=value;
|
|
wstate=0;
|
|
break;
|
|
|
|
case 0xf3: /* set typematic rate */
|
|
h_printf("8042: write port 0x60 set typematic rate 0x%02x\n", value);
|
|
keyb_ctrl_typematic = value;
|
|
ack();
|
|
wstate=0;
|
|
break;
|
|
default:
|
|
h_printf("8042: write port 0x60 illegal state (0x%02x), resending\n",
|
|
wstate);
|
|
wstate=0;
|
|
write_port60(value);
|
|
}
|
|
}
|
|
|
|
/* write to port 64h (8042 command register) */
|
|
static void write_port64(Bit8u value) {
|
|
k_printf("8042: write port64h, =%02x\n",value);
|
|
|
|
switch(value) {
|
|
case 0x20: /* read 8042 command byte */
|
|
output_byte_8042(keyb_ctrl_command);
|
|
break;
|
|
|
|
case 0x60: /* write 8042 command byte */
|
|
wstate=0x60;
|
|
break;
|
|
|
|
#if 0 /* not sure if these are ok and/or needed. */
|
|
|
|
case 0xa4: /* passwort installed test */
|
|
output_byte_8042(0xfa); /* no password */
|
|
break;
|
|
|
|
case 0xa5: /* load password */
|
|
/* XXX ... we should read bytes from port60 until 0 is found */
|
|
break;
|
|
|
|
case 0xa9: /* aux interface test */
|
|
output_byte_8042(0x00); /* ok */
|
|
break;
|
|
|
|
case 0xaa: /* 8042 self test */
|
|
output_byte_8042(0x55); /* ok */
|
|
break;
|
|
|
|
case 0xab: /* keyboard interface test */
|
|
output_byte_8042(0x00); /* ok */
|
|
break;
|
|
|
|
case 0xc0: /* read 8042 input port */
|
|
output_byte_8042(0xff); /* just send _something_... */
|
|
break;
|
|
#endif
|
|
case 0xad:
|
|
kbd_disabled = 1;
|
|
break;
|
|
case 0xae:
|
|
kbd_disabled = 0;
|
|
#if KBD_READ_HACK
|
|
last_read_valid = 0;
|
|
#endif
|
|
break;
|
|
case 0xd1: /* next write to port 0x60 drives hardware port */
|
|
wstate=0xd1;
|
|
break;
|
|
|
|
case 0xf0 ... 0xff: /* produce 6ms pulse on hardware port */
|
|
wstate=0;
|
|
port60_ready=0;
|
|
if (!(value & RESET_LINE_MASK)) {
|
|
h_printf("8042: produce 6ms pulse on cpu reset line\n");
|
|
cpu_reset();
|
|
}
|
|
else
|
|
h_printf("8042: produce 6ms pulse on hardware port, ignored\n");
|
|
break;
|
|
|
|
default:
|
|
h_printf("8042: write port 0x64 unsupported command 0x%02x, ignored\n",
|
|
value);
|
|
/* various other commands... ignore */
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static Bit8u read_port60(void)
|
|
{
|
|
Bit8u r;
|
|
|
|
#if KBD_READ_HACK
|
|
if (kbd_disabled && last_read_valid) {
|
|
r = last_read_data;
|
|
if (port60_ready && (keyb_ctrl_command & 0x01)) /* if interrupt enabled */
|
|
pic_request(1);
|
|
} else {
|
|
r = port60_buffer;
|
|
port60_ready = 0;
|
|
last_read_data = r;
|
|
last_read_valid = 1;
|
|
}
|
|
#else
|
|
r = port60_buffer;
|
|
port60_ready = 0;
|
|
#endif
|
|
|
|
h_printf("8042: read port 0x60 = 0x%02x\n", r);
|
|
|
|
#if KEYB_CMD
|
|
switch (rstate) {
|
|
case 0xf2: /* get keyboard type, MSB */
|
|
h_printf("8042: read port 0x60, getting keyboard type (MSB)\n");
|
|
output_byte_8042(0x83);
|
|
rstate = 0x72;
|
|
break;
|
|
case 0x72: /* get keyboard type, LSB */
|
|
h_printf("8042: read port 0x60, getting keyboard type (LSB)\n");
|
|
output_byte_8042(0xab);
|
|
rstate = 0;
|
|
break;
|
|
case 0xff: /* reset keyboard */
|
|
h_printf("8042: read port 0x60, resetting\n");
|
|
output_byte_8042(0xaa); /* BAT completion code */
|
|
rstate = 0;
|
|
break;
|
|
default: /* invalid state ?! */
|
|
rstate = 0;
|
|
break;
|
|
}
|
|
#endif
|
|
return r;
|
|
}
|
|
|
|
|
|
Bit8u keyb_io_read(ioport_t port, void *arg)
|
|
{
|
|
Bit8u r = 0;
|
|
|
|
switch (port) {
|
|
case 0x60:
|
|
r = read_port60();
|
|
if (!port60_ready)
|
|
pic_untrigger(1);
|
|
k_printf("8042: read port 0x60 read=0x%02x\n",r);
|
|
break;
|
|
|
|
case 0x61:
|
|
/* Handle only PC-Speaker right now */
|
|
r = spkr_io_read(port);
|
|
break;
|
|
|
|
case 0x64:
|
|
r= 0x1c | (port60_ready ? 0x01 : 0x00);
|
|
k_printf("8042: read port 0x64 status check=0x%02x, port60_ready=%d\n",
|
|
r, port60_ready);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
void keyb_io_write(ioport_t port, Bit8u value, void *arg)
|
|
{
|
|
switch (port) {
|
|
case 0x60:
|
|
k_printf("8042: write port 0x60 outb = 0x%x\n", value);
|
|
#if KEYB_CMD
|
|
write_port60(value);
|
|
#endif
|
|
break;
|
|
|
|
case 0x61:
|
|
if (value & 0x80) {
|
|
k_printf("8042: IRQ ACK, %i\n", port60_ready);
|
|
int_check_queue(); /* reschedule irq1 if appropriate */
|
|
}
|
|
spkr_io_write(port, value);
|
|
break;
|
|
|
|
case 0x64:
|
|
k_printf("8042: write port 0x64 outb = 0x%x\n", value);
|
|
write_port64(value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void keyb_8042_init(void)
|
|
{
|
|
emu_iodev_t io_device;
|
|
|
|
/* 8042 keyboard controller */
|
|
io_device.read_portb = keyb_io_read;
|
|
io_device.write_portb = keyb_io_write;
|
|
io_device.read_portw = NULL;
|
|
io_device.write_portw = NULL;
|
|
io_device.read_portd = NULL;
|
|
io_device.write_portd = NULL;
|
|
io_device.handler_name = "8042 Keyboard data";
|
|
io_device.start_addr = 0x0060;
|
|
io_device.end_addr = 0x0060;
|
|
port_register_handler(io_device, 0);
|
|
|
|
io_device.handler_name = "8042 Keyboard command";
|
|
io_device.start_addr = 0x0064;
|
|
io_device.end_addr = 0x0064;
|
|
port_register_handler(io_device, 0);
|
|
|
|
io_device.handler_name = "Keyboard controller port B";
|
|
io_device.start_addr = 0x0061;
|
|
io_device.end_addr = 0x0061;
|
|
port_register_handler(io_device, 0);
|
|
}
|
|
|
|
void keyb_8042_reset(void)
|
|
{
|
|
#if KEYB_CMD
|
|
rstate = 0;
|
|
wstate = 0;
|
|
keyb_ctrl_scanmap = 1;
|
|
keyb_ctrl_typematic = 0x23;
|
|
keyb_ctrl_enable = 1;
|
|
keyb_ctrl_isdata = 0;
|
|
keyb_ctrl_clearbuf();
|
|
#endif
|
|
port60_buffer = 0;
|
|
port60_ready = 0;
|
|
}
|