Files
dosemu2/src/base/dev/misc/8042.c
geos_one 91736529d5
Some checks failed
Master / Scheduled (FULL) (push) Has been cancelled
Master / Triggered (push) Has been cancelled
Master / Triggered (ASAN) (push) Has been cancelled
Master / Triggered (FULL) (push) Has been cancelled
New upstream version 2.0pre9.2
2025-08-10 12:35:43 +02:00

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;
}