New upstream version 2.0pre9.2
This commit is contained in:
436
src/base/dev/misc/8042.c
Normal file
436
src/base/dev/misc/8042.c
Normal file
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* (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;
|
||||
}
|
||||
19
src/base/dev/misc/Makefile
Normal file
19
src/base/dev/misc/Makefile
Normal file
@@ -0,0 +1,19 @@
|
||||
top_builddir=../../../..
|
||||
include $(top_builddir)/Makefile.conf
|
||||
|
||||
# src/base/misc/cmos.c -> ../dev/misc/cmos.c
|
||||
# src/base/misc/lpt.c -> ../dev/misc/lpt.c
|
||||
# src/base/misc/timers.c -> ../dev/misc/timers.c
|
||||
|
||||
|
||||
CFILES = cmos.c timers.c lpt.c rtc.c pci.c chipset.c 8042.c kbd.c \
|
||||
virq.c vtmr.c
|
||||
ifeq ($(OS),Linux)
|
||||
CFILES += joystick.c
|
||||
endif
|
||||
|
||||
include $(REALTOPDIR)/src/Makefile.common
|
||||
|
||||
all: lib
|
||||
|
||||
install: all
|
||||
70
src/base/dev/misc/chipset.c
Normal file
70
src/base/dev/misc/chipset.c
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* (C) Copyright 2013 the "DOSEMU-Development-Team".
|
||||
*
|
||||
* for details see file COPYING.DOSEMU in the DOSEMU distribution
|
||||
*/
|
||||
|
||||
#include "emu.h"
|
||||
#include "port.h"
|
||||
#include "hma.h"
|
||||
#include "chipset.h"
|
||||
|
||||
#define CONTROL_RESET_MASK 1
|
||||
#define CONTROL_A20GATE_MASK 2
|
||||
|
||||
|
||||
static Bit8u port92h_io_read(ioport_t port, void *arg)
|
||||
{
|
||||
Bit8u ret = 0;
|
||||
if (a20)
|
||||
ret |= CONTROL_A20GATE_MASK;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void port92h_io_write(ioport_t port, Bit8u val, void *arg)
|
||||
{
|
||||
int enA20 = (val & CONTROL_A20GATE_MASK) ? 1 : 0;
|
||||
if (val & CONTROL_RESET_MASK) cpu_reset();
|
||||
set_a20(enA20);
|
||||
}
|
||||
|
||||
static Bit8u picext_io_read(ioport_t port, void *arg)
|
||||
{
|
||||
Bit8u val = 0xff;
|
||||
|
||||
switch (port) {
|
||||
case PIC0_VECBASE_PORT:
|
||||
val = pic0_get_base();
|
||||
break;
|
||||
case PIC1_VECBASE_PORT:
|
||||
val = pic1_get_base();
|
||||
break;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
void chipset_init(void)
|
||||
{
|
||||
emu_iodev_t io_dev = {};
|
||||
|
||||
io_dev.read_portb = port92h_io_read;
|
||||
io_dev.write_portb = port92h_io_write;
|
||||
io_dev.start_addr = 0x92;
|
||||
io_dev.end_addr = 0x92;
|
||||
io_dev.handler_name = "Chipset Control Port A";
|
||||
port_register_handler(io_dev, 0);
|
||||
|
||||
memset(&io_dev, 0, sizeof(io_dev));
|
||||
io_dev.read_portb = picext_io_read;
|
||||
io_dev.start_addr = PIC0_EXTPORT_START;
|
||||
io_dev.end_addr = PIC0_EXTPORT_START + PICx_EXT_PORTS - 1;
|
||||
io_dev.handler_name = "PIC0 extensions";
|
||||
port_register_handler(io_dev, 0);
|
||||
|
||||
memset(&io_dev, 0, sizeof(io_dev));
|
||||
io_dev.read_portb = picext_io_read;
|
||||
io_dev.start_addr = PIC1_EXTPORT_START;
|
||||
io_dev.end_addr = PIC1_EXTPORT_START + PICx_EXT_PORTS - 1;
|
||||
io_dev.handler_name = "PIC1 extensions";
|
||||
port_register_handler(io_dev, 0);
|
||||
}
|
||||
168
src/base/dev/misc/cmos.c
Normal file
168
src/base/dev/misc/cmos.c
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* SIDOC_BEGIN_MODULE
|
||||
*
|
||||
* Description: CMOS handling routines
|
||||
*
|
||||
* Originally by Robert Sanders, gt8134b@prism.gatech.edu
|
||||
* New CMOS code by vignani@mail.tin.it 1997-98
|
||||
*
|
||||
* SIDOC_END_MODULE
|
||||
*
|
||||
*/
|
||||
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "emu.h"
|
||||
#include "port.h"
|
||||
#include "iodev.h"
|
||||
#include "disks.h"
|
||||
|
||||
|
||||
#define PEXTMEM_SIZE EXTMEM_SIZE
|
||||
|
||||
struct CMOS cmos;
|
||||
|
||||
static int
|
||||
cmos_chksum(void)
|
||||
{
|
||||
int i, sum = 0;
|
||||
|
||||
/* return the checksum over bytes 0x10-0x20. These are static values,
|
||||
* so no need to call cmos_read()
|
||||
*/
|
||||
|
||||
for (i = 0x10; i < 0x21; i++)
|
||||
sum += GET_CMOS(i);
|
||||
return sum;
|
||||
}
|
||||
|
||||
Bit8u cmos_read(ioport_t port, void *arg)
|
||||
{
|
||||
unsigned char holder = 0;
|
||||
|
||||
if (port != 0x71)
|
||||
return 0xff;
|
||||
|
||||
switch (cmos.address) {
|
||||
case 0 ... 0x0d:
|
||||
holder = rtc_read(cmos.address);
|
||||
break;
|
||||
|
||||
case CMOS_CHKSUML:
|
||||
holder = cmos_chksum() & 0xff;
|
||||
break;
|
||||
|
||||
case CMOS_CHKSUMM:
|
||||
holder = cmos_chksum() >> 8;
|
||||
break;
|
||||
|
||||
default:
|
||||
holder = GET_CMOS(cmos.address);
|
||||
if (!cmos.flag[cmos.address])
|
||||
h_printf("CMOS: unknown CMOS read 0x%x\n", cmos.address);
|
||||
}
|
||||
|
||||
h_printf("CMOS: read addr 0x%02x = 0x%02x\n", cmos.address, holder);
|
||||
return holder;
|
||||
}
|
||||
|
||||
void cmos_write(ioport_t port, Bit8u byte, void *arg)
|
||||
{
|
||||
if (port == 0x70)
|
||||
cmos.address = byte & ~0xc0;/* get true address */
|
||||
else {
|
||||
h_printf("CMOS: set address 0x%02x to 0x%02x\n", cmos.address, byte);
|
||||
switch (cmos.address) {
|
||||
case 0 ... 0x0d:
|
||||
rtc_write(cmos.address, byte);
|
||||
break;
|
||||
|
||||
default:
|
||||
SET_CMOS(cmos.address, byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cmos_init(void)
|
||||
{
|
||||
emu_iodev_t io_device;
|
||||
int i;
|
||||
|
||||
/* CMOS RAM & RTC */
|
||||
io_device.read_portb = cmos_read;
|
||||
io_device.write_portb = cmos_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 = "CMOS RAM";
|
||||
io_device.start_addr = 0x0070;
|
||||
io_device.end_addr = 0x0071;
|
||||
port_register_handler(io_device, 0);
|
||||
|
||||
for (i = 0; i < 64; i++)
|
||||
cmos.subst[i] = cmos.flag[i] = 0;
|
||||
|
||||
rtc_setup();
|
||||
|
||||
/* CMOS floppies...is this correct? */
|
||||
SET_CMOS(CMOS_DISKTYPE,
|
||||
(config.fdisks ? (disktab[0].default_cmos << 4) : 0) |
|
||||
((config.fdisks > 1) ? disktab[1].default_cmos & 0xf : 0));
|
||||
|
||||
/* CMOS equipment byte..top 2 bits are 01 for 2 drives, 00 for 1
|
||||
* bit 1 is 1 for math coprocessor installed
|
||||
* bit 0 is 1 for floppies installed, 0 for none */
|
||||
|
||||
cmos.subst[0x14] = ((config.fdisks ? config.fdisks - 1 : 0) << 6) +
|
||||
(config.fdisks ? 1 : 0);
|
||||
if (config.mathco)
|
||||
cmos.subst[0x14] |= 2;
|
||||
cmos.flag[0x14] = 1;
|
||||
|
||||
/* CMOS hard disks...type 47 for both. */
|
||||
SET_CMOS(CMOS_HDTYPE, (config.hdisks ? 0xf0 : 0) +
|
||||
((config.hdisks > 1) ? 0xf : 0));
|
||||
SET_CMOS(CMOS_HD1EXT, 47);
|
||||
if (config.hdisks == 2)
|
||||
SET_CMOS(CMOS_HD2EXT, 47);
|
||||
else
|
||||
SET_CMOS(CMOS_HD2EXT, 0);
|
||||
|
||||
/* this is the CMOS status */
|
||||
SET_CMOS(CMOS_STATUSA, 0x26);
|
||||
/* default id BCD,24h,no DST */
|
||||
SET_CMOS(CMOS_STATUSB, 2);
|
||||
|
||||
/* 0xc and 0xd are read only */
|
||||
SET_CMOS(CMOS_STATUSC, 0);
|
||||
SET_CMOS(CMOS_STATUSD, 0x80);
|
||||
|
||||
SET_CMOS(CMOS_DIAG, 0);
|
||||
|
||||
/* memory counts */
|
||||
SET_CMOS(CMOS_BASEMEML, config.mem_size & 0xff); /* base mem LSB */
|
||||
SET_CMOS(CMOS_BASEMEMM, config.mem_size >> 8); /* base mem MSB */
|
||||
|
||||
SET_CMOS(CMOS_EXTMEML, EXTMEM_SIZE & 0xff);
|
||||
SET_CMOS(CMOS_EXTMEMM, EXTMEM_SIZE >> 8);
|
||||
|
||||
SET_CMOS(CMOS_PEXTMEML, PEXTMEM_SIZE & 0xff);
|
||||
SET_CMOS(CMOS_PEXTMEMM, PEXTMEM_SIZE >> 8);
|
||||
|
||||
/* say protected mode test 7 passed (?) */
|
||||
SET_CMOS(CMOS_SHUTDOWN, 6);
|
||||
|
||||
/* information flags...my CMOS returns this */
|
||||
SET_CMOS(CMOS_INFO, 0xe1);
|
||||
|
||||
/* system operational flags (for fast A20 gate) */
|
||||
SET_CMOS(CMOS_SYSOP, 0x3f);
|
||||
|
||||
g_printf("CMOS initialized\n");
|
||||
}
|
||||
|
||||
void cmos_reset(void)
|
||||
{
|
||||
}
|
||||
1401
src/base/dev/misc/joystick.c
Normal file
1401
src/base/dev/misc/joystick.c
Normal file
File diff suppressed because it is too large
Load Diff
252
src/base/dev/misc/kbd.c
Normal file
252
src/base/dev/misc/kbd.c
Normal file
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* (C) Copyright 1992, ..., 2014 the "DOSEMU-Development-Team".
|
||||
*
|
||||
* for details see file COPYING in the DOSEMU distribution
|
||||
*/
|
||||
|
||||
/*
|
||||
* DANG_BEGIN_MODULE
|
||||
*
|
||||
* Description: Keyboard interface
|
||||
*
|
||||
* Maintainer: Eric Biederman
|
||||
*
|
||||
* REMARK
|
||||
* This module handles interfacing to the DOS side both on int9/port60h level,
|
||||
* or on the bios buffer level.
|
||||
* Keycodes are buffered in a queue, which, however, has limited depth, so it
|
||||
* shouldn't be used for pasting.
|
||||
*
|
||||
* More information about this module is in doc/README.newkbd
|
||||
*
|
||||
* /REMARK
|
||||
* DANG_END_MODULE
|
||||
*
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "emu.h"
|
||||
#include "types.h"
|
||||
#include "keyboard/keyboard.h"
|
||||
#include "keyboard/keyb_server.h"
|
||||
#include "keyboard/keyb_clients.h"
|
||||
#include "bios.h"
|
||||
#include "pic.h"
|
||||
#include "cpu.h"
|
||||
#include "timers.h"
|
||||
|
||||
/*
|
||||
* Our keyboard clock rate is 27.5KHz. This looks optimal for dosemu,
|
||||
* even though the real keyboards are usually clocked to <= 20KHz.
|
||||
* Anyway, 8042 should give an extra delay.
|
||||
*/
|
||||
#define KBD_CHAR_PERIOD 400
|
||||
|
||||
/* If this is set to 1, the server will check whether the BIOS keyboard buffer is
|
||||
* full.
|
||||
* This is somewhat inaccurate emulation, as the state of BIOS variables really
|
||||
* shouldn't affect 'hardware' behaviour, but this seems the only way of knowing how
|
||||
* fast DOS is processing keystrokes, in particular for pasting.
|
||||
*/
|
||||
#define KEYBUF_HACK 0
|
||||
|
||||
/* the below hack helps MOS to read arrow keys */
|
||||
#define USE_KBD_DELAY 1
|
||||
#define KBD_PIC_HACK 1
|
||||
|
||||
/********** QUEUE ***********/
|
||||
|
||||
/*
|
||||
* This is the dosemu keyboard queue.
|
||||
* Each queue entry holds a data structure corresponding to (mostly)
|
||||
* one keypress or release event. [The exception are the braindead
|
||||
* 0xe02a / 0xe0aa shift key emulation codes the keyboard processor
|
||||
* 'decorates' some kinds of keyboard events, which for convenience
|
||||
* are treated as separate events.]
|
||||
* Each queue entry holds a up to 4 bytes of raw keycodes for the
|
||||
* port 60h emulation, along with a 2-byte translated int16h keycode
|
||||
* and the shift state after this event was processed.
|
||||
* Note that the bios_key field can be empty (=0), e.g. for shift keys,
|
||||
* while the raw field should always contain something.
|
||||
*/
|
||||
|
||||
struct keyboard_queue keyb_queue = {
|
||||
0, 0, 0, 0
|
||||
};
|
||||
|
||||
static inline Boolean queue_empty(struct keyboard_queue *q)
|
||||
{
|
||||
return (q->head == q->tail);
|
||||
}
|
||||
|
||||
|
||||
int queue_level(struct keyboard_queue *q)
|
||||
{
|
||||
int n;
|
||||
/* q->tail is the first item to pop
|
||||
* q->head is the place to write the next item
|
||||
*/
|
||||
n = q->head - q->tail;
|
||||
return (n < 0) ? n + q->size : n;
|
||||
}
|
||||
|
||||
static inline Boolean queue_full(struct keyboard_queue *q)
|
||||
{
|
||||
return (q->size == 0) || (queue_level(q) == (q->size - 1));
|
||||
}
|
||||
|
||||
/*
|
||||
* this has to work even if the variables are uninitialized!
|
||||
*/
|
||||
void clear_queue(struct keyboard_queue *q)
|
||||
{
|
||||
q->head = q->tail = 0;
|
||||
k_printf("KBD: clear_queue() queuelevel=0\n");
|
||||
}
|
||||
|
||||
void write_queue(struct keyboard_queue *q, t_rawkeycode raw)
|
||||
{
|
||||
int qh;
|
||||
|
||||
k_printf("KBD: writing to queue: scan=%08x\n",
|
||||
(unsigned int)raw);
|
||||
|
||||
if (queue_full(q)) {
|
||||
/* If the queue is full grow it */
|
||||
t_rawkeycode *_new;
|
||||
int sweep1, sweep2;
|
||||
_new = malloc(q->size + KEYB_QUEUE_LENGTH);
|
||||
if (!_new) {
|
||||
k_printf("KBD: queue overflow!\n");
|
||||
return;
|
||||
}
|
||||
k_printf("KBD: resize queue %d->%d head=%d tail=%d level=%d\n",
|
||||
q->size, q->size + KEYB_QUEUE_LENGTH, q->head, q->tail, queue_level(q));
|
||||
if (q->tail <= q->head) {
|
||||
sweep1 = q->head - q->tail;
|
||||
sweep2 = 0;
|
||||
} else {
|
||||
sweep1 = q->size - q->tail;
|
||||
sweep2 = q->head;
|
||||
|
||||
}
|
||||
if (q->queue) {
|
||||
memcpy(_new, q->queue + q->tail, sweep1);
|
||||
memcpy(_new + sweep1, q->queue, sweep2);
|
||||
free(q->queue);
|
||||
}
|
||||
q->tail = 0;
|
||||
q->head = sweep1 + sweep2;
|
||||
q->size += KEYB_QUEUE_LENGTH;
|
||||
q->queue = _new;
|
||||
}
|
||||
qh = q->head;
|
||||
if (++qh == q->size)
|
||||
qh = 0;
|
||||
if (qh == q->tail) {
|
||||
k_printf("KBD: queue overflow!\n");
|
||||
return;
|
||||
}
|
||||
q->queue[q->head] = raw;
|
||||
q->head = qh;
|
||||
k_printf("KBD: queuelevel=%d\n", queue_level(q));
|
||||
}
|
||||
|
||||
|
||||
|
||||
t_rawkeycode read_queue(struct keyboard_queue *q)
|
||||
{
|
||||
t_rawkeycode *qp;
|
||||
t_rawkeycode raw = 0;
|
||||
|
||||
if (!queue_empty(q)) {
|
||||
qp = &q->queue[q->tail];
|
||||
|
||||
raw = *qp;
|
||||
if (++q->tail == q->size) q->tail = 0;
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
/****************** END QUEUE *******************/
|
||||
|
||||
#if USE_KBD_DELAY
|
||||
static int kbd_period_elapsed(void)
|
||||
{
|
||||
static hitimer_t kbd_time = 0;
|
||||
hitimer_t delta = GETusTIME(0) - kbd_time;
|
||||
if (delta >= KBD_CHAR_PERIOD) {
|
||||
kbd_time = GETusTIME(0);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/****************** KEYBINT MODE BACKEND *******************/
|
||||
|
||||
/* run the queue backend in keybint=on mode
|
||||
* called either periodically from keyb_server_run or, for faster response,
|
||||
* when writing to the queue and after the IRQ1 handler is finished.
|
||||
*/
|
||||
void int_check_queue(void)
|
||||
{
|
||||
t_rawkeycode rawscan;
|
||||
|
||||
#if 0
|
||||
k_printf("KBD: int_check_queue(): queue_empty=%d port60_ready=%d\n",
|
||||
queue_empty(&keyb_queue), port60_ready);
|
||||
#endif
|
||||
|
||||
if (queue_empty(&keyb_queue))
|
||||
return;
|
||||
|
||||
#if 1
|
||||
if (port60_ready) {
|
||||
k_printf("KBD: port60 still has data\n");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if KEYBUF_HACK
|
||||
if (bios_keybuf_full() && !(READ_BYTE(BIOS_KEYBOARD_FLAGS2) & PAUSE_MASK))
|
||||
return;
|
||||
#endif
|
||||
|
||||
#if USE_KBD_DELAY
|
||||
if (!kbd_period_elapsed())
|
||||
return;
|
||||
#endif
|
||||
|
||||
#if KBD_PIC_HACK
|
||||
/* HACK - extra sentinel needed, timing is not
|
||||
* a reliable measure under heavy loads */
|
||||
if (pic_irq_active(1))
|
||||
return;
|
||||
#endif
|
||||
|
||||
rawscan = read_queue(&keyb_queue);
|
||||
k_printf("KBD: read queue: raw=%02x, queuelevel=%d\n",
|
||||
rawscan, queue_level(&keyb_queue));
|
||||
output_byte_8042(rawscan);
|
||||
}
|
||||
|
||||
/******************* GENERAL ********************************/
|
||||
|
||||
|
||||
void backend_run(void)
|
||||
{
|
||||
int_check_queue();
|
||||
}
|
||||
|
||||
|
||||
void backend_reset(void)
|
||||
{
|
||||
clear_queue(&keyb_queue);
|
||||
|
||||
/* initialise keyboard-related BIOS variables */
|
||||
|
||||
clear_bios_keybuf();
|
||||
}
|
||||
359
src/base/dev/misc/lpt.c
Normal file
359
src/base/dev/misc/lpt.c
Normal file
@@ -0,0 +1,359 @@
|
||||
/* for the Linux dos emulator versions 0.49 and newer
|
||||
*
|
||||
*/
|
||||
#define LPT_C 1
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "emu.h"
|
||||
#include "bios.h"
|
||||
#include "port.h"
|
||||
#include "timers.h"
|
||||
#include "lpt.h"
|
||||
#include "utilities.h"
|
||||
#include "dos2linux.h"
|
||||
#include "ioselect.h"
|
||||
|
||||
/* status bits, Centronics */
|
||||
#define CTS_STAT_NOIOERR LPT_STAT_NOIOERR
|
||||
#define CTS_STAT_ONLINE LPT_STAT_ONLINE
|
||||
#define CTS_STAT_NOPAPER LPT_STAT_NOPAPER
|
||||
#define CTS_STAT_NOT_ACKing LPT_STAT_NOT_ACK
|
||||
#define CTS_STAT_BUSY LPT_STAT_NOT_BUSY
|
||||
|
||||
/* control bits, Centronics */
|
||||
#define CTS_CTRL_NOT_SELECT LPT_CTRL_SELECT
|
||||
#define CTS_CTRL_NOT_INIT LPT_CTRL_NOT_INIT
|
||||
#define CTS_CTRL_NOT_AUTOLF LPT_CTRL_AUTOLF
|
||||
#define CTS_CTRL_NOT_STROBE LPT_CTRL_STROBE
|
||||
|
||||
/* inversion masks to convert LPT<-->Centronics */
|
||||
#define LPT_STAT_INV_MASK (CTS_STAT_BUSY)
|
||||
#define LPT_CTRL_INV_MASK (CTS_CTRL_NOT_STROBE | CTS_CTRL_NOT_AUTOLF | \
|
||||
CTS_CTRL_NOT_SELECT)
|
||||
|
||||
#define DEFAULT_STAT (CTS_STAT_ONLINE | CTS_STAT_NOIOERR | \
|
||||
CTS_STAT_NOT_ACKing | LPT_STAT_NOT_IRQ)
|
||||
#define DEFAULT_CTRL (CTS_CTRL_NOT_INIT | CTS_CTRL_NOT_AUTOLF | \
|
||||
CTS_CTRL_NOT_STROBE)
|
||||
|
||||
#define NUM_PRINTERS 9
|
||||
static struct printer lpt[NUM_PRINTERS] =
|
||||
{
|
||||
{NULL, NULL, 5, 0x378, .control = DEFAULT_CTRL, .status = DEFAULT_STAT},
|
||||
{NULL, NULL, 5, 0x278, .control = DEFAULT_CTRL, .status = DEFAULT_STAT},
|
||||
{NULL, NULL, 10, 0x3bc, .control = DEFAULT_CTRL, .status = DEFAULT_STAT}
|
||||
};
|
||||
|
||||
ioport_t get_lpt_base(int lptnum)
|
||||
{
|
||||
if (lptnum >= NUM_LPTS)
|
||||
return -1;
|
||||
return lpt[lptnum].base_port;
|
||||
}
|
||||
|
||||
static int get_printer(ioport_t port)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < NUM_LPTS; i++)
|
||||
if (lpt[i].base_port <= port && port <= lpt[i].base_port + 2)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static Bit8u printer_io_read(ioport_t port, void *arg)
|
||||
{
|
||||
int i = get_printer(port);
|
||||
Bit8u val;
|
||||
|
||||
if (i == -1)
|
||||
return 0xff;
|
||||
|
||||
switch (port - lpt[i].base_port) {
|
||||
case 0:
|
||||
val = lpt[i].data; /* simple unidirectional port */
|
||||
if (debug_level('p') >= 5)
|
||||
p_printf("LPT%d: Reading data byte %#x\n", i+1, val);
|
||||
break;
|
||||
case 1: /* status port, r/o */
|
||||
val = lpt[i].status ^ LPT_STAT_INV_MASK;
|
||||
/* we should really set ACK after 5 us but here we just
|
||||
use the fact that the BIOS only checks this once */
|
||||
lpt[i].status |= CTS_STAT_NOT_ACKing | LPT_STAT_NOT_IRQ;
|
||||
lpt[i].status &= ~CTS_STAT_BUSY;
|
||||
if (debug_level('p') >= 5)
|
||||
p_printf("LPT%d: Reading status byte %#x\n", i+1, val);
|
||||
break;
|
||||
case 2:
|
||||
val = lpt[i].control ^ LPT_CTRL_INV_MASK;
|
||||
if (debug_level('p') >= 5)
|
||||
p_printf("LPT%d: Reading control byte %#x\n", i+1, val);
|
||||
break;
|
||||
default:
|
||||
val = 0xff;
|
||||
break;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static void printer_io_write(ioport_t port, Bit8u value, void *arg)
|
||||
{
|
||||
int i = get_printer(port);
|
||||
if (i == -1)
|
||||
return;
|
||||
switch (port - lpt[i].base_port) {
|
||||
case 0:
|
||||
if (debug_level('p') >= 5)
|
||||
p_printf("LPT%d: Writing data byte %#x\n", i+1, value);
|
||||
lpt[i].data = value;
|
||||
break;
|
||||
case 1: /* status port, r/o */
|
||||
break;
|
||||
case 2:
|
||||
if (debug_level('p') >= 5)
|
||||
p_printf("LPT%d: Writing control byte %#x\n", i+1, value);
|
||||
value ^= LPT_CTRL_INV_MASK; // convert to Centronics
|
||||
if (((lpt[i].control & (CTS_CTRL_NOT_STROBE | CTS_CTRL_NOT_SELECT)) == 0)
|
||||
&& (value & CTS_CTRL_NOT_STROBE)) {
|
||||
/* STROBE rising */
|
||||
if (debug_level('p') >= 9)
|
||||
p_printf("LPT%d: STROBE, sending %#x (%c)\n", i+1, lpt[i].data,
|
||||
lpt[i].data);
|
||||
printer_write(i, lpt[i].data);
|
||||
lpt[i].status &= ~CTS_STAT_NOT_ACKing;
|
||||
lpt[i].status |= CTS_STAT_BUSY;
|
||||
}
|
||||
lpt[i].control = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int dev_printer_open(int prnum)
|
||||
{
|
||||
lpt[prnum].dev_fd = open(lpt[prnum].dev, O_WRONLY);
|
||||
if (lpt[prnum].dev_fd == -1) {
|
||||
error("LPT%i: error opening %s: %s\n", prnum+1, lpt[prnum].dev,
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
p_printf("LPT: opened printer %d to %s\n", prnum, lpt[prnum].dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pipe_callback(int fd, void *arg)
|
||||
{
|
||||
char buf[1024];
|
||||
int num = (long)arg;
|
||||
int n = read(lpt[num].file.from_child, buf, sizeof(buf));
|
||||
if (n > 0) {
|
||||
buf[n] = 0;
|
||||
error("LPT%i: %s\n", num+1, buf);
|
||||
}
|
||||
ioselect_complete(fd);
|
||||
}
|
||||
|
||||
static int pipe_printer_open(int prnum)
|
||||
{
|
||||
int err;
|
||||
err = popen2(lpt[prnum].prtcmd, &lpt[prnum].file);
|
||||
if (err) {
|
||||
error("system(\"%s\") in lpt.c failed, cannot print! "
|
||||
"Command returned error %s\n", lpt[prnum].prtcmd, strerror(errno));
|
||||
return err;
|
||||
}
|
||||
p_printf("LPT: doing printer command ..%s..\n", lpt[prnum].prtcmd);
|
||||
add_to_io_select(lpt[prnum].file.from_child, pipe_callback, (void *)(long)prnum);
|
||||
return err;
|
||||
}
|
||||
|
||||
int printer_open(int prnum)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (!lpt[prnum].initialized)
|
||||
return -1;
|
||||
if (lpt[prnum].opened) {
|
||||
dosemu_error("opening printer %i twice\n", prnum);
|
||||
return 0;
|
||||
}
|
||||
|
||||
rc = lpt[prnum].fops.open(prnum);
|
||||
if (!rc)
|
||||
lpt[prnum].opened = 1;
|
||||
else
|
||||
error("Error opening printer %i\n", prnum);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int dev_printer_close(int prnum)
|
||||
{
|
||||
return close(lpt[prnum].dev_fd);
|
||||
}
|
||||
|
||||
static int pipe_printer_close(int prnum)
|
||||
{
|
||||
remove_from_io_select(lpt[prnum].file.from_child);
|
||||
return pclose2(&lpt[prnum].file);
|
||||
}
|
||||
|
||||
int printer_close(int prnum)
|
||||
{
|
||||
if (lpt[prnum].opened && lpt[prnum].fops.close) {
|
||||
p_printf("LPT%i: closing printer\n", prnum+1);
|
||||
lpt[prnum].fops.close(prnum);
|
||||
lpt[prnum].remaining = 0;
|
||||
}
|
||||
lpt[prnum].opened = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dev_printer_write(int prnum, Bit8u outchar)
|
||||
{
|
||||
return write(lpt[prnum].dev_fd, &outchar, 1);
|
||||
}
|
||||
|
||||
static int pipe_printer_write(int prnum, Bit8u outchar)
|
||||
{
|
||||
return write(lpt[prnum].file.to_child, &outchar, 1);
|
||||
}
|
||||
|
||||
int printer_write(int prnum, Bit8u outchar)
|
||||
{
|
||||
if (!lpt[prnum].initialized)
|
||||
return -1;
|
||||
if (!lpt[prnum].opened)
|
||||
printer_open(prnum);
|
||||
lpt[prnum].remaining = lpt[prnum].delay;
|
||||
if (debug_level('p') >= 9)
|
||||
p_printf("LPT%d: writing %#x (%c)\n", prnum+1, outchar, outchar);
|
||||
return lpt[prnum].fops.write(prnum, outchar);
|
||||
}
|
||||
|
||||
/* DANG_BEGIN_FUNCTION printer_init
|
||||
*
|
||||
* description:
|
||||
* Initialize printer control structures
|
||||
*
|
||||
* DANG_END_FUNCTIONS
|
||||
*/
|
||||
static struct p_fops dev_pfops =
|
||||
{
|
||||
dev_printer_open,
|
||||
dev_printer_write,
|
||||
dev_printer_close,
|
||||
};
|
||||
static struct p_fops pipe_pfops =
|
||||
{
|
||||
pipe_printer_open,
|
||||
pipe_printer_write,
|
||||
pipe_printer_close,
|
||||
};
|
||||
|
||||
void
|
||||
printer_init(void)
|
||||
{
|
||||
int i;
|
||||
emu_iodev_t io_device;
|
||||
|
||||
io_device.read_portb = printer_io_read;
|
||||
io_device.write_portb = printer_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 = "Parallel printer";
|
||||
|
||||
for (i = 0; i < NUM_PRINTERS; i++) {
|
||||
lpt[i].initialized = 0;
|
||||
lpt[i].opened = 0;
|
||||
lpt[i].remaining = 0; /* mark not accessed yet */
|
||||
if (!lpt[i].dev && !lpt[i].prtcmd)
|
||||
continue;
|
||||
p_printf("LPT%i: initializing printer %s\n", i+1,
|
||||
lpt[i].dev ? lpt[i].dev : lpt[i].prtcmd);
|
||||
if (lpt[i].dev)
|
||||
lpt[i].fops = dev_pfops;
|
||||
else if (lpt[i].prtcmd)
|
||||
lpt[i].fops = pipe_pfops;
|
||||
if (i >= _min(config.num_lpt, NUM_LPTS)) lpt[i].base_port = 0;
|
||||
|
||||
if (lpt[i].base_port != 0) {
|
||||
io_device.start_addr = lpt[i].base_port;
|
||||
io_device.end_addr = lpt[i].base_port + 2;
|
||||
port_register_handler(io_device, 0);
|
||||
}
|
||||
lpt[i].initialized = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
close_all_printers(void)
|
||||
{
|
||||
int loop;
|
||||
|
||||
for (loop = 0; loop < NUM_PRINTERS; loop++) {
|
||||
if (!lpt[loop].opened)
|
||||
continue;
|
||||
p_printf("LPT: closing printer %d (%s)\n", loop,
|
||||
lpt[loop].dev ? lpt[loop].dev : lpt[loop].prtcmd);
|
||||
printer_close(loop);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
printer_tick(u_long secno)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NUM_PRINTERS; i++) {
|
||||
if (lpt[i].remaining > 0) {
|
||||
if (debug_level('p') >= 9)
|
||||
p_printf("LPT%i: doing tick %d\n", i+1, lpt[i].remaining);
|
||||
if (lpt[i].remaining) {
|
||||
reset_idle(2);
|
||||
lpt[i].remaining--;
|
||||
if (!lpt[i].remaining)
|
||||
printer_close(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void printer_config(int prnum, struct printer *pptr)
|
||||
{
|
||||
struct printer *destptr;
|
||||
|
||||
if (prnum < NUM_PRINTERS) {
|
||||
destptr = &lpt[prnum];
|
||||
destptr->prtcmd = pptr->prtcmd;
|
||||
destptr->dev = pptr->dev;
|
||||
destptr->delay = pptr->delay;
|
||||
}
|
||||
}
|
||||
|
||||
void printer_print_config(int prnum, void (*print)(const char *, ...))
|
||||
{
|
||||
struct printer *pptr = &lpt[prnum];
|
||||
(*print)("LPT%d command \"%s\" timeout %d device \"%s\" baseport 0x%03x\n",
|
||||
prnum+1, (pptr->prtcmd ? pptr->prtcmd : ""), pptr->delay,
|
||||
(pptr->dev ? pptr->dev : ""), pptr->base_port);
|
||||
}
|
||||
|
||||
int lpt_get_max(void)
|
||||
{
|
||||
return NUM_PRINTERS;
|
||||
}
|
||||
|
||||
int lpt_is_configured(int num)
|
||||
{
|
||||
return lpt[num].initialized;
|
||||
}
|
||||
608
src/base/dev/misc/pci.c
Normal file
608
src/base/dev/misc/pci.c
Normal file
@@ -0,0 +1,608 @@
|
||||
/*
|
||||
* SIDOC_BEGIN_MODULE
|
||||
*
|
||||
* Description: PCI configuration space access
|
||||
* (only configuration mechanism 1 is fully implemented so far)
|
||||
* used by the console video drivers and the PCI BIOS
|
||||
*
|
||||
* SIDOC_END_MODULE
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include "emu.h"
|
||||
#include "port.h"
|
||||
#include "pci.h"
|
||||
|
||||
/* SIDOC_BEGIN_FUNCTION pci_read_header
|
||||
*
|
||||
* Use standard 32-bit (type 1) access method to read PCI
|
||||
* configuration space data
|
||||
*
|
||||
* SIDOC_END_FUNCTION
|
||||
*/
|
||||
/*
|
||||
* from PCIUTILS and Linux kernel arch/i386/pci/direct.c:
|
||||
*
|
||||
* Before we decide to use direct hardware access mechanisms, we try to do some
|
||||
* trivial checks to ensure it at least _seems_ to be working -- we just test
|
||||
* whether bus 00 contains a host bridge (this is similar to checking
|
||||
* techniques used in XFree86, but ours should be more reliable since we
|
||||
* attempt to make use of direct access hints provided by the PCI BIOS).
|
||||
*
|
||||
* This should be close to trivial, but it isn't, because there are buggy
|
||||
* chipsets (yes, you guessed it, by Intel and Compaq) that have no class ID.
|
||||
*/
|
||||
|
||||
static int
|
||||
intel_sanity_check(struct pci_funcs *m)
|
||||
{
|
||||
int dev;
|
||||
|
||||
Z_printf("PCI: ...sanity check for mechanism %s ", m->name);
|
||||
for(dev = 0; dev < 32; dev++) {
|
||||
uint16_t cls, vendor;
|
||||
cls = m->read(0, dev, 0, PCI_CLASS_DEVICE, 2);
|
||||
if (cls == PCI_CLASS_BRIDGE_HOST || cls == PCI_CLASS_DISPLAY_VGA)
|
||||
break;
|
||||
vendor = m->read(0, dev, 0, PCI_VENDOR_ID, 2);
|
||||
if (vendor == PCI_VENDOR_ID_INTEL || vendor == PCI_VENDOR_ID_COMPAQ)
|
||||
break;
|
||||
}
|
||||
if (dev < 32) {
|
||||
Z_printf("succeeded for dev=%x\n", dev);
|
||||
return 1;
|
||||
}
|
||||
Z_printf("not succeeded\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pci_no_open(unsigned char bus, unsigned char device,
|
||||
unsigned char fn)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* only called from pci bios init */
|
||||
static int pci_read_header_cfg1 (unsigned char bus, unsigned char device,
|
||||
unsigned char fn, unsigned int *buf)
|
||||
{
|
||||
int i;
|
||||
unsigned long bx = ((fn&7)<<8) | ((device&31)<<11) | (bus<<16) |
|
||||
PCI_EN;
|
||||
|
||||
|
||||
if (priv_iopl(3)) {
|
||||
error("iopl(): %s\n", strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
/* Get only first 64 bytes: See src/linux/drivers/pci/proc.c for
|
||||
why. They are not joking. My NCR810 crashes the machine on read
|
||||
of register 0xd8 */
|
||||
for (i=0; i<16; i++) {
|
||||
port_real_outd (PCI_CONF_ADDR, bx|(i<<2));
|
||||
buf[i] = port_real_ind (PCI_CONF_DATA);
|
||||
}
|
||||
port_real_outd (PCI_CONF_ADDR, 0);
|
||||
priv_iopl(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned long pci_read_cfg1 (unsigned char bus, unsigned char device,
|
||||
unsigned char fn, unsigned long reg, int len)
|
||||
{
|
||||
unsigned long val;
|
||||
unsigned long bx = ((fn&7)<<8) | ((device&31)<<11) | (bus<<16) |
|
||||
PCI_EN;
|
||||
|
||||
if (priv_iopl(3)) {
|
||||
error("iopl(): %s\n", strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
port_real_outd (PCI_CONF_ADDR, bx | (reg & ~3));
|
||||
if (len == 1)
|
||||
val = port_real_inb (PCI_CONF_DATA + (reg & 3));
|
||||
else if (len == 2)
|
||||
val = port_real_inw (PCI_CONF_DATA + (reg & 2));
|
||||
else
|
||||
val = port_real_ind (PCI_CONF_DATA);
|
||||
port_real_outd (PCI_CONF_ADDR, 0);
|
||||
priv_iopl(0);
|
||||
return val;
|
||||
}
|
||||
|
||||
static void pci_write_cfg1 (unsigned char bus, unsigned char device,
|
||||
unsigned char fn, unsigned long reg, unsigned long val, int len)
|
||||
{
|
||||
unsigned long bx = ((fn&7)<<8) | ((device&31)<<11) | (bus<<16) |
|
||||
PCI_EN;
|
||||
|
||||
if (priv_iopl(3)) {
|
||||
error("iopl(): %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
port_real_outd (PCI_CONF_ADDR, bx | (reg & ~3));
|
||||
if (len == 1)
|
||||
port_real_outb (PCI_CONF_DATA + (reg & 3), val);
|
||||
else if (len == 2)
|
||||
port_real_outw (PCI_CONF_DATA + (reg & 2), val);
|
||||
else
|
||||
port_real_outd (PCI_CONF_DATA, val);
|
||||
port_real_outd (PCI_CONF_ADDR, 0);
|
||||
priv_iopl(0);
|
||||
}
|
||||
|
||||
/* only called from pci bios init */
|
||||
static int pci_check_device_present_cfg1(unsigned char bus, unsigned char device,
|
||||
unsigned char fn)
|
||||
{
|
||||
unsigned long val;
|
||||
unsigned long bx = ((fn&7)<<8) | ((device&31)<<11) | (bus<<16) |
|
||||
PCI_EN;
|
||||
|
||||
if (priv_iopl(3)) {
|
||||
error("iopl(): %s\n", strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
port_real_outd (PCI_CONF_ADDR, bx);
|
||||
val = port_real_ind (PCI_CONF_DATA);
|
||||
port_real_outd (PCI_CONF_ADDR, 0);
|
||||
priv_iopl(0);
|
||||
|
||||
if (val == 0xFFFFFFFF)
|
||||
return 0 ;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* only called from pci bios init */
|
||||
static int pci_read_header_cfg2 (unsigned char bus, unsigned char device,
|
||||
unsigned char fn, unsigned int *buf)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (priv_iopl(3)) {
|
||||
error("iopl(): %s\n", strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
port_real_outb(PCI_MODE2_ENABLE_REG, (fn << 1) | 0xF0);
|
||||
port_real_outb(PCI_MODE2_FORWARD_REG, bus);
|
||||
for (i=0; i<16; i++) {
|
||||
buf[i] = port_real_ind (0xc000 | (device << 8) | (i << 2));
|
||||
}
|
||||
port_real_outb(PCI_MODE2_ENABLE_REG, 0x00);
|
||||
priv_iopl(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned long pci_read_cfg2 (unsigned char bus, unsigned char device,
|
||||
unsigned char fn, unsigned long num, int len)
|
||||
{
|
||||
unsigned long val;
|
||||
|
||||
if (priv_iopl(3)) {
|
||||
error("iopl(): %s\n", strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
port_real_outb(PCI_MODE2_ENABLE_REG, (fn << 1) | 0xF0);
|
||||
port_real_outb(PCI_MODE2_FORWARD_REG, bus);
|
||||
if (len == 1)
|
||||
val = port_real_inb (0xc000 | (device << 8) | num);
|
||||
else if (len == 2)
|
||||
val = port_real_inw (0xc000 | (device << 8) | num);
|
||||
else
|
||||
val = port_real_ind (0xc000 | (device << 8) | num);
|
||||
port_real_outb(PCI_MODE2_ENABLE_REG, 0x00);
|
||||
priv_iopl(0);
|
||||
return val;
|
||||
}
|
||||
|
||||
static void pci_write_cfg2 (unsigned char bus, unsigned char device,
|
||||
unsigned char fn, unsigned long num, unsigned long val, int len)
|
||||
{
|
||||
if (priv_iopl(3)) {
|
||||
error("iopl(): %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
port_real_outb(PCI_MODE2_ENABLE_REG, (fn << 1) | 0xF0);
|
||||
port_real_outb(PCI_MODE2_FORWARD_REG, bus);
|
||||
if (len == 1)
|
||||
port_real_outb (0xc000 | (device << 8) | num, val);
|
||||
else if (len == 2)
|
||||
port_real_outw (0xc000 | (device << 8) | num, val);
|
||||
else
|
||||
port_real_outd (0xc000 | (device << 8) | num, val);
|
||||
port_real_outb(PCI_MODE2_ENABLE_REG, 0x00);
|
||||
priv_iopl(0);
|
||||
}
|
||||
|
||||
/* only called from pci bios init */
|
||||
static int pci_check_device_present_cfg2(unsigned char bus, unsigned char device,
|
||||
unsigned char fn)
|
||||
{
|
||||
unsigned long val;
|
||||
|
||||
if (priv_iopl(3)) {
|
||||
error("iopl(): %s\n", strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
port_real_outb(PCI_MODE2_ENABLE_REG,0xF1);
|
||||
port_real_outb(PCI_MODE2_FORWARD_REG,bus);
|
||||
val = port_real_ind ((device << 8));
|
||||
port_real_outb(PCI_MODE2_ENABLE_REG, 0x00);
|
||||
priv_iopl(0);
|
||||
|
||||
if (val == 0xFFFFFFFF || val == 0xf0f0f0f0)
|
||||
return 0 ;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int pci_open_proc(unsigned char bus, unsigned char device,
|
||||
unsigned char fn)
|
||||
{
|
||||
static char proc_pci_name_buf[] = "/proc/bus/pci/00/00.0";
|
||||
int fd;
|
||||
|
||||
PRIV_SAVE_AREA
|
||||
snprintf(proc_pci_name_buf + 14, sizeof(proc_pci_name_buf) - 14,
|
||||
"%02hhx/%02hhx.%.1hhx", bus, device, fn);
|
||||
Z_printf("PCI: opening %s\n", proc_pci_name_buf);
|
||||
enter_priv_on();
|
||||
fd = open(proc_pci_name_buf, O_RDWR);
|
||||
if (fd == -1)
|
||||
fd = open(proc_pci_name_buf, O_RDONLY);
|
||||
leave_priv_setting();
|
||||
return fd;
|
||||
}
|
||||
|
||||
/* only called from pci bios init */
|
||||
static int pci_read_header_proc (unsigned char bus, unsigned char device,
|
||||
unsigned char fn, unsigned int *buf)
|
||||
{
|
||||
int fd = pci_open_proc(bus, device, fn);
|
||||
if (fd == -1)
|
||||
return 0;
|
||||
|
||||
/* Get only first 64 bytes: See src/linux/drivers/pci/proc.c for
|
||||
why. They are not joking. My NCR810 crashes the machine on read
|
||||
of register 0xd8 */
|
||||
|
||||
read(fd, buf, 64);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned long pci_read_proc (unsigned char bus, unsigned char device,
|
||||
unsigned char fn, unsigned long reg, int len)
|
||||
{
|
||||
unsigned long val;
|
||||
int fd = pci_open_proc(bus, device, fn);
|
||||
if (fd == -1)
|
||||
return 0;
|
||||
Z_printf("PCI: reading reg %ld\n", reg);
|
||||
pread(fd, &val, len, reg);
|
||||
close(fd);
|
||||
return val;
|
||||
}
|
||||
|
||||
static void pci_write_proc (unsigned char bus, unsigned char device,
|
||||
unsigned char fn, unsigned long reg, unsigned long val, int len)
|
||||
{
|
||||
int fd = pci_open_proc(bus, device, fn);
|
||||
if (fd == -1)
|
||||
return;
|
||||
Z_printf("PCI: writing reg %ld\n", reg);
|
||||
pwrite(fd, &val, len, reg);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/* only called from pci bios init */
|
||||
static int pci_check_device_present_proc(unsigned char bus, unsigned char device,
|
||||
unsigned char fn)
|
||||
{
|
||||
int fd = pci_open_proc(bus, device, fn);
|
||||
if (fd == -1)
|
||||
return 0;
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct pci_funcs pci_cfg1 = {
|
||||
"1",
|
||||
pci_no_open,
|
||||
pci_read_cfg1,
|
||||
pci_write_cfg1,
|
||||
pci_read_header_cfg1,
|
||||
pci_check_device_present_cfg1
|
||||
};
|
||||
|
||||
static struct pci_funcs pci_cfg2 = {
|
||||
"2",
|
||||
pci_no_open,
|
||||
pci_read_cfg2,
|
||||
pci_write_cfg2,
|
||||
pci_read_header_cfg2,
|
||||
pci_check_device_present_cfg2
|
||||
};
|
||||
|
||||
static struct pci_funcs pci_proc = {
|
||||
"/proc",
|
||||
pci_open_proc,
|
||||
pci_read_proc,
|
||||
pci_write_proc,
|
||||
pci_read_header_proc,
|
||||
pci_check_device_present_proc
|
||||
};
|
||||
|
||||
/*
|
||||
* Return NULL if no PCI is present.
|
||||
*/
|
||||
struct pci_funcs *pci_check_conf(void)
|
||||
{
|
||||
unsigned long val;
|
||||
struct pci_funcs *m;
|
||||
|
||||
if (!config.pci && access("/proc/bus/pci", R_OK) == 0)
|
||||
return &pci_proc;
|
||||
|
||||
if (!can_do_root_stuff)
|
||||
return NULL;
|
||||
if (priv_iopl(3)) {
|
||||
error("iopl(): %s\n", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
m = &pci_cfg1;
|
||||
port_real_outd(PCI_CONF_ADDR,PCI_EN);
|
||||
val = port_real_ind(PCI_CONF_ADDR);
|
||||
port_real_outd(PCI_CONF_ADDR, 0);
|
||||
|
||||
if (val != PCI_EN) {
|
||||
/* from PCIUTILS: */
|
||||
/* This is ugly and tends to produce false positives. Beware. */
|
||||
|
||||
port_real_outb(PCI_MODE2_ENABLE_REG, 0x00);
|
||||
port_real_outb(PCI_MODE2_FORWARD_REG, 0x00);
|
||||
m = (port_inb(PCI_MODE2_ENABLE_REG) == 0x00 &&
|
||||
port_inb(PCI_MODE2_FORWARD_REG) == 0x00) ? &pci_cfg2 : NULL;
|
||||
}
|
||||
|
||||
priv_iopl(0);
|
||||
if (m && intel_sanity_check(m))
|
||||
return m;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static Bit8u pci_port_inb(ioport_t port, void *arg)
|
||||
{
|
||||
if (port != 0xcf9)
|
||||
return std_port_inb(port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pci_port_outb(ioport_t port, Bit8u byte, void *arg)
|
||||
{
|
||||
/* don't allow DOSEMU to reset the CPU */
|
||||
if (port != 0xcf9)
|
||||
std_port_outb(port, byte);
|
||||
}
|
||||
|
||||
/* SIDOC_BEGIN_FUNCTION pci_setup
|
||||
*
|
||||
* Register standard PCI ports 0xcf8-0xcff
|
||||
*
|
||||
* SIDOC_END_FUNCTION
|
||||
*/
|
||||
int pci_setup (void)
|
||||
{
|
||||
emu_iodev_t io_device = {};
|
||||
|
||||
if (config.pci) {
|
||||
pcibios_init();
|
||||
/* register PCI ports */
|
||||
io_device.read_portb = pci_port_inb;
|
||||
io_device.write_portb = pci_port_outb;
|
||||
|
||||
if (pciConfigType->name[0] == '1') {
|
||||
io_device.handler_name = "PCI Config Type 1";
|
||||
io_device.start_addr = PCI_CONF_ADDR;
|
||||
io_device.end_addr = PCI_CONF_DATA+3;
|
||||
port_register_handler(io_device, 0);
|
||||
} else {
|
||||
io_device.handler_name = "PCI Config Type 2";
|
||||
io_device.start_addr = PCI_MODE2_ENABLE_REG;
|
||||
io_device.end_addr = PCI_MODE2_FORWARD_REG + 1;
|
||||
port_register_handler(io_device, 0);
|
||||
io_device.start_addr = 0xc000;
|
||||
io_device.end_addr = 0xcfff;
|
||||
port_register_handler(io_device, 0);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* basic r/o PCI emulation, enough for VGA-style classes */
|
||||
|
||||
/* sets the pci record if the bdf changes */
|
||||
static pciRec *set_pcirec(unsigned short bdf)
|
||||
{
|
||||
/* cached pci record */
|
||||
static pciRec *pcirec;
|
||||
pciRec *pci = pcirec;
|
||||
|
||||
if (pci == NULL || bdf != pci->bdf) {
|
||||
pci = pcibios_find_bdf(bdf);
|
||||
if(pci != NULL)
|
||||
pcirec = pci;
|
||||
}
|
||||
return pci;
|
||||
}
|
||||
|
||||
static unsigned long current_pci_reg;
|
||||
|
||||
static unsigned long pciemu_port_read(ioport_t port, int len)
|
||||
{
|
||||
unsigned long val;
|
||||
unsigned short bdf;
|
||||
pciRec *pci;
|
||||
unsigned char num;
|
||||
|
||||
if (!(current_pci_reg & PCI_EN))
|
||||
return 0xffffffff;
|
||||
|
||||
bdf = (current_pci_reg >> 8) & 0xffff;
|
||||
pci = set_pcirec(bdf);
|
||||
if (pci == NULL)
|
||||
return 0xffffffff;
|
||||
/* values >= 0x40 have to go directly to the hardware.
|
||||
They can be dangerous so are only allowed if pci->ext_enabled
|
||||
the Linux kernel only lets us read if num < 64
|
||||
unless we're root (even if the fd was opened as root)
|
||||
*/
|
||||
num = current_pci_reg & 0xff;
|
||||
if (num < 0x40) {
|
||||
val = pci->header[num >> 2];
|
||||
if (len == 1)
|
||||
val = (val >> ((port & 3) << 3)) & 0xff;
|
||||
else if (len == 2)
|
||||
val = (val >> ((port & 2) << 3)) & 0xffff;
|
||||
} else if (pci->ext_enabled) {
|
||||
pci_port_outd(PCI_CONF_ADDR, current_pci_reg);
|
||||
val = len == 1 ? std_port_inb(port) : len == 2 ? std_port_inw(port) :
|
||||
std_port_ind(port);
|
||||
} else
|
||||
val = 0xffffffff;
|
||||
Z_printf("PCIEMU: reading 0x%lx from %#x, len=%d\n",val,num,len);
|
||||
return val;
|
||||
}
|
||||
|
||||
static void pciemu_port_write(ioport_t port, unsigned long val, int len)
|
||||
{
|
||||
unsigned char num;
|
||||
unsigned short bdf;
|
||||
pciRec *pci;
|
||||
|
||||
if (!(current_pci_reg & PCI_EN) || current_pci_reg == PCI_EN)
|
||||
return;
|
||||
|
||||
bdf = (current_pci_reg >> 8) & 0xffff;
|
||||
pci = set_pcirec(bdf);
|
||||
if (pci == NULL)
|
||||
return;
|
||||
/* values >= 0x40 have to go directly to the hardware.
|
||||
They can be dangerous so are only allowed if pci->ext_enabled */
|
||||
num = current_pci_reg & 0xff;
|
||||
if (num < 0x40) {
|
||||
unsigned long value = pci->header[num >> 2];
|
||||
int shift = (port & (4 - len)) << 3;
|
||||
if (len == 1)
|
||||
value &= ~(unsigned long)(0xff << shift);
|
||||
else if (len == 2)
|
||||
value &= ~(unsigned long)(0xffff << shift);
|
||||
val = value | (val << shift);
|
||||
if ((pci->header[3] & 0x007f0000) == 0) {
|
||||
if (num >= PCI_BASE_ADDRESS_0 && num <= PCI_BASE_ADDRESS_5)
|
||||
val &= pci->region[num - PCI_BASE_ADDRESS_0].rawsize;
|
||||
if (num == PCI_ROM_ADDRESS)
|
||||
val &= pci->region[6].rawsize;
|
||||
}
|
||||
pci->header[num >> 2] = val;
|
||||
} else if (pci->ext_enabled) {
|
||||
pci_port_outd(PCI_CONF_ADDR, current_pci_reg);
|
||||
if (len == 1)
|
||||
std_port_outb(port, val);
|
||||
else if (len == 2)
|
||||
std_port_outw(port, val);
|
||||
else
|
||||
std_port_outd(port, val);
|
||||
}
|
||||
Z_printf("PCIEMU: writing 0x%lx to %#x, len=%d\n",val,num,len);
|
||||
}
|
||||
|
||||
static Bit8u pciemu_port_inb(ioport_t port, void *arg)
|
||||
{
|
||||
/* 0xcf8 -- 0xcfb as bytes or words don't access sub-parts;
|
||||
for instance writing to 0xcf9 as a byte may reset the CPU */
|
||||
if (port == 0xcf9) /* TURBO/RESET control register */
|
||||
return 0;
|
||||
if (port >= PCI_CONF_DATA)
|
||||
return pciemu_port_read(port, 1);
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
static void pciemu_port_outb(ioport_t port, Bit8u byte, void *arg)
|
||||
{
|
||||
if (port >= PCI_CONF_DATA)
|
||||
pciemu_port_write(port, byte, 1);
|
||||
}
|
||||
|
||||
static Bit16u pciemu_port_inw(ioport_t port, void *arg)
|
||||
{
|
||||
if (port == PCI_CONF_DATA || port == PCI_CONF_DATA + 2)
|
||||
return pciemu_port_read(port, 2);
|
||||
return 0xffff;
|
||||
}
|
||||
|
||||
static void pciemu_port_outw(ioport_t port, Bit16u value, void *arg)
|
||||
{
|
||||
if (port == PCI_CONF_DATA || port == PCI_CONF_DATA + 2)
|
||||
pciemu_port_write(port, value, 2);
|
||||
}
|
||||
|
||||
static Bit32u pciemu_port_ind(ioport_t port, void *arg)
|
||||
{
|
||||
if (port == PCI_CONF_DATA)
|
||||
return pciemu_port_read(port, 4);
|
||||
if (port == PCI_CONF_ADDR)
|
||||
return current_pci_reg;
|
||||
return 0xffffffff;
|
||||
}
|
||||
|
||||
static void pciemu_port_outd(ioport_t port, Bit32u value, void *arg)
|
||||
{
|
||||
if (port == PCI_CONF_ADDR)
|
||||
current_pci_reg = value & 0x80fffffc;
|
||||
else if (port == PCI_CONF_DATA)
|
||||
pciemu_port_write(port, value, 4);
|
||||
}
|
||||
|
||||
/* set up emulated r/o PCI config space for the given class */
|
||||
pciRec *pciemu_setup(unsigned long cls)
|
||||
{
|
||||
static int pciemu_initialized = 0;
|
||||
pciRec *pci;
|
||||
|
||||
if (!pciemu_initialized) {
|
||||
Z_printf("PCI: initializing, class=%lx\n", cls);
|
||||
pcibios_init();
|
||||
}
|
||||
// pci = pcibios_find_class(cls, 0);
|
||||
pci = pcibios_find_primary_vga(); // XXX
|
||||
if (pci == NULL) {
|
||||
Z_printf("PCI: class %lx not found\n", cls);
|
||||
return pci;
|
||||
}
|
||||
pci->enabled = pci->ext_enabled = 1;
|
||||
if (!pciemu_initialized) {
|
||||
emu_iodev_t io_device;
|
||||
|
||||
/* register PCI ports */
|
||||
io_device.read_portb = pciemu_port_inb;
|
||||
io_device.write_portb = pciemu_port_outb;
|
||||
io_device.read_portw = pciemu_port_inw;
|
||||
io_device.write_portw = pciemu_port_outw;
|
||||
io_device.read_portd = pciemu_port_ind;
|
||||
io_device.write_portd = pciemu_port_outd;
|
||||
|
||||
io_device.handler_name = "PCI Emulated Config";
|
||||
io_device.start_addr = PCI_CONF_ADDR;
|
||||
io_device.end_addr = PCI_CONF_DATA+3;
|
||||
port_register_handler(io_device, 0);
|
||||
|
||||
pciemu_initialized = 1;
|
||||
}
|
||||
return pci;
|
||||
}
|
||||
451
src/base/dev/misc/rtc.c
Normal file
451
src/base/dev/misc/rtc.c
Normal file
@@ -0,0 +1,451 @@
|
||||
/*
|
||||
* SIDOC_BEGIN_MODULE
|
||||
*
|
||||
* Description: CMOS handling and RTC emulation
|
||||
*
|
||||
* Maintainers: Alberto Vignani (vignani@tin.it)
|
||||
*
|
||||
* SIDOC_END_MODULE
|
||||
*
|
||||
*/
|
||||
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "emu.h"
|
||||
#include "cmos.h"
|
||||
#include "disks.h"
|
||||
#include "timers.h"
|
||||
#include "int.h"
|
||||
#include "vtmr.h"
|
||||
#include "iodev.h"
|
||||
|
||||
long sys_base_ticks = 0;
|
||||
long usr_delta_ticks = 0;
|
||||
unsigned long last_ticks = 0;
|
||||
static unsigned long long q_ticks_m = 0;
|
||||
|
||||
static int rtc_get_rate(Bit8u div)
|
||||
{
|
||||
if (!div)
|
||||
return 0;
|
||||
if (div < 3)
|
||||
div += 7;
|
||||
return (65536 >> div);
|
||||
}
|
||||
|
||||
void rtc_run(void)
|
||||
{
|
||||
static hitimer_t last_time = -1;
|
||||
int rate;
|
||||
hitimer_t ticks_m, cur_time = GETusTIME(0);
|
||||
if (last_time == -1 || last_time > cur_time ||
|
||||
!(GET_CMOS(CMOS_STATUSB) & 0x40)) {
|
||||
last_time = cur_time;
|
||||
return;
|
||||
}
|
||||
rate = rtc_get_rate(GET_CMOS(CMOS_STATUSA) & 0x0f);
|
||||
ticks_m = (cur_time - last_time) * rate;
|
||||
q_ticks_m += ticks_m;
|
||||
last_time = cur_time;
|
||||
if (debug_level('h') > 8)
|
||||
h_printf("RTC: A=%hhx B=%hhx C=%hhx rate=%i queued=%lli added=%lli\n",
|
||||
GET_CMOS(CMOS_STATUSA), GET_CMOS(CMOS_STATUSB), GET_CMOS(CMOS_STATUSC),
|
||||
rate, (long long)q_ticks_m, (long long)ticks_m);
|
||||
if (q_ticks_m >= 1000000) {
|
||||
Bit8u old_c = GET_CMOS(CMOS_STATUSC);
|
||||
SET_CMOS(CMOS_STATUSC, old_c | 0x40);
|
||||
if ((GET_CMOS(CMOS_STATUSB) & 0x40) && !(GET_CMOS(CMOS_STATUSC) & 0x80)) {
|
||||
SET_CMOS(CMOS_STATUSC, GET_CMOS(CMOS_STATUSC) | 0x80);
|
||||
if (debug_level('h') > 7)
|
||||
h_printf("RTC: periodic IRQ, queued=%lli, added=%lli\n",
|
||||
(long long)q_ticks_m, (long long)ticks_m);
|
||||
/* we only use vtmr in tweaked mode */
|
||||
if (config.timer_tweaks)
|
||||
vtmr_raise(VTMR_RTC);
|
||||
else
|
||||
pic_request(8);
|
||||
}
|
||||
if (!(old_c & 0x40))
|
||||
q_ticks_m -= 1000000;
|
||||
}
|
||||
}
|
||||
|
||||
Bit8u rtc_read(Bit8u reg)
|
||||
{
|
||||
Bit8u ret = GET_CMOS(reg);
|
||||
|
||||
switch (reg) {
|
||||
case CMOS_SEC:
|
||||
case CMOS_SECALRM:
|
||||
case CMOS_MIN:
|
||||
case CMOS_MINALRM:
|
||||
case CMOS_DOW:
|
||||
case CMOS_DOM:
|
||||
case CMOS_MONTH:
|
||||
case CMOS_YEAR:
|
||||
case CMOS_CENTURY:
|
||||
/* Note - the inline function BCD() in cmos.h will check bit 2 of
|
||||
* status reg B for proper output format */
|
||||
ret = BCD(ret);
|
||||
break;
|
||||
|
||||
case CMOS_HOUR: /* RTC hour...bit 1 of 0xb set=24 hour mode, clear 12 hour */
|
||||
case CMOS_HOURALRM:
|
||||
if (!(GET_CMOS(CMOS_STATUSB) & 2)) { /* 12-hour mode */
|
||||
if (ret == 0)
|
||||
ret = 12;
|
||||
else if (ret > 12)
|
||||
ret -= 12;
|
||||
}
|
||||
ret = BCD(ret);
|
||||
break;
|
||||
|
||||
case CMOS_STATUSC:
|
||||
if (debug_level('h') > 8)
|
||||
h_printf("RTC: Read C=%hhx\n", ret);
|
||||
SET_CMOS(CMOS_STATUSC, 0);
|
||||
pic_untrigger(8);
|
||||
rtc_run();
|
||||
break;
|
||||
}
|
||||
|
||||
/* below slows down streetfighter2 game so commented out */
|
||||
// vtmr_sync(VTMR_RTC);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void rtc_write(Bit8u reg, Bit8u byte)
|
||||
{
|
||||
switch (reg) {
|
||||
case CMOS_SEC:
|
||||
case CMOS_MIN:
|
||||
case CMOS_HOUR:
|
||||
case CMOS_SECALRM:
|
||||
case CMOS_MINALRM:
|
||||
case CMOS_HOURALRM:
|
||||
case CMOS_DOW:
|
||||
case CMOS_DOM:
|
||||
case CMOS_MONTH:
|
||||
case CMOS_YEAR:
|
||||
case CMOS_CENTURY:
|
||||
SET_CMOS(reg, BIN(byte));
|
||||
break;
|
||||
|
||||
/* b7=r/o and unused
|
||||
* b4-6=always 010 (AT standard 32.768kHz)
|
||||
* b0-3=rate [65536/2^v], default 6, min 3, 0=disable
|
||||
*/
|
||||
case CMOS_STATUSA:
|
||||
h_printf("RTC: Write %hhx to A\n", byte);
|
||||
SET_CMOS(reg, byte & 0x7f);
|
||||
break;
|
||||
|
||||
case CMOS_STATUSB:
|
||||
h_printf("RTC: Write %hhx to B\n", byte);
|
||||
SET_CMOS(reg, byte);
|
||||
break;
|
||||
|
||||
case CMOS_STATUSC:
|
||||
case CMOS_STATUSD:
|
||||
h_printf("RTC: attempt to write %hhx to %hhx\n", byte, reg);
|
||||
break;
|
||||
|
||||
default:
|
||||
SET_CMOS(reg, byte);
|
||||
}
|
||||
q_ticks_m = 0;
|
||||
}
|
||||
|
||||
static void rtc_alarm_check (void)
|
||||
{
|
||||
static u_char last_sec = 0xff; /* any invalid value to begin with */
|
||||
u_char h0,m0,s0,h,m,s;
|
||||
|
||||
s0=GET_CMOS(CMOS_SEC);
|
||||
m0=GET_CMOS(CMOS_MIN);
|
||||
h0=GET_CMOS(CMOS_HOUR);
|
||||
|
||||
/* this is a test for equality - we can't just call a lib time
|
||||
* function because a second could be skipped */
|
||||
h = GET_CMOS(CMOS_HOURALRM);
|
||||
if (h>=0xc0 || h0==h) { /* Eric: all c0-ff are don't care */
|
||||
m = GET_CMOS(CMOS_MINALRM);
|
||||
if (m>=0xc0 || m0==m) {
|
||||
s = GET_CMOS(CMOS_SECALRM);
|
||||
if (s>=0xc0 || (s0==s && s0!=last_sec)) {
|
||||
last_sec = s0;
|
||||
h_printf("RTC: got alarm at %02d:%02d:%02d\n",h,m,s);
|
||||
SET_CMOS(CMOS_STATUSC, GET_CMOS(CMOS_STATUSC) | 0x20);
|
||||
if ((GET_CMOS(CMOS_STATUSB) & 0x20) && !(GET_CMOS(CMOS_STATUSC) & 0x80)) {
|
||||
SET_CMOS(CMOS_STATUSC, GET_CMOS(CMOS_STATUSC) | 0x80);
|
||||
h_printf("RTC: alarm IRQ\n");
|
||||
pic_request(8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void rtc_update (void) /* called every 1s from SIGALRM */
|
||||
{
|
||||
u_char h0,m0,s0,D0,M0,Y0,C0,days;
|
||||
static const u_char dpm[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
|
||||
if (GET_CMOS(CMOS_STATUSA) & 0x80)
|
||||
error("RTC: A/UIP set on update\n");
|
||||
|
||||
if (GET_CMOS(CMOS_STATUSB) & 0x80) {
|
||||
h_printf("RTC updates inhibited\n");
|
||||
return;
|
||||
}
|
||||
|
||||
SET_CMOS(CMOS_STATUSA, GET_CMOS(CMOS_STATUSA)|0x80);
|
||||
s0=GET_CMOS(CMOS_SEC);
|
||||
m0=GET_CMOS(CMOS_MIN);
|
||||
h0=GET_CMOS(CMOS_HOUR);
|
||||
D0=GET_CMOS(CMOS_DOM);
|
||||
M0=GET_CMOS(CMOS_MONTH);
|
||||
Y0=GET_CMOS(CMOS_YEAR);
|
||||
C0=GET_CMOS(CMOS_CENTURY);
|
||||
|
||||
if ((++s0)>59) {
|
||||
s0=0;
|
||||
if ((++m0)>59) {
|
||||
m0=0;
|
||||
if ((++h0)>23) {
|
||||
h0=0;
|
||||
|
||||
/* Compute days in current month. */
|
||||
if (M0>12 || M0<1) M0=1; /* Error! */
|
||||
days = dpm[M0];
|
||||
if ((Y0&3) == 0 && M0 == 2) days++; /* Leap year & Feb */
|
||||
|
||||
if (++D0>days) {
|
||||
D0=1;
|
||||
if (++M0>12) {
|
||||
M0=1;
|
||||
if (++Y0>99) {
|
||||
Y0=0;
|
||||
++C0; /* Only 19->20 transition realistic. */
|
||||
SET_CMOS(CMOS_CENTURY, C0);
|
||||
}
|
||||
SET_CMOS(CMOS_YEAR, Y0);
|
||||
}
|
||||
SET_CMOS(CMOS_MONTH, M0);
|
||||
}
|
||||
SET_CMOS(CMOS_DOM, D0);
|
||||
|
||||
/* As well as day-of-month, do day-of-week */
|
||||
days=GET_CMOS(CMOS_DOW);
|
||||
days++;
|
||||
if(days > 7 || days < 1) days=1;
|
||||
SET_CMOS(CMOS_DOW, days);
|
||||
}
|
||||
SET_CMOS(CMOS_HOUR, h0);
|
||||
}
|
||||
SET_CMOS(CMOS_MIN, m0);
|
||||
}
|
||||
SET_CMOS(CMOS_SEC, s0);
|
||||
|
||||
h_printf("RTC: cmos update %02d:%02d:%02d, B=%#x\n",h0,m0,s0,GET_CMOS(CMOS_STATUSB));
|
||||
|
||||
rtc_alarm_check();
|
||||
|
||||
/* per-second IRQ */
|
||||
SET_CMOS(CMOS_STATUSC, GET_CMOS(CMOS_STATUSC) | 0x10);
|
||||
if ((GET_CMOS(CMOS_STATUSB) & 0x10) && !(GET_CMOS(CMOS_STATUSC) & 0x80)) {
|
||||
SET_CMOS(CMOS_STATUSC, GET_CMOS(CMOS_STATUSC) | 0x80);
|
||||
h_printf("RTC: update IRQ\n");
|
||||
pic_request(8);
|
||||
}
|
||||
|
||||
SET_CMOS(CMOS_STATUSA, GET_CMOS(CMOS_STATUSA)&~0x80);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialise the RTC. Key work is now in get_linux_ticks() where we initialise
|
||||
* all of the RTC time/date fields, and only zero the alarm stuff here.
|
||||
*/
|
||||
void rtc_setup(void)
|
||||
{
|
||||
SET_CMOS(CMOS_HOURALRM, 0);
|
||||
SET_CMOS(CMOS_MINALRM, 0);
|
||||
SET_CMOS(CMOS_SECALRM, 0);
|
||||
}
|
||||
|
||||
static int rtc_vint(int masked)
|
||||
{
|
||||
if (masked)
|
||||
pic_request(8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rtc_init(void)
|
||||
{
|
||||
usr_delta_ticks = 0;
|
||||
last_ticks = sys_base_ticks = get_linux_ticks(1, NULL);
|
||||
|
||||
vtmr_register(VTMR_RTC, rtc_vint);
|
||||
/* NOTE: vtmr is only used in tweaked mode, so we set it to 1 beforehands,
|
||||
* but may not actually use */
|
||||
vtmr_set_tweaked(VTMR_RTC, 1, 0);
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
|
||||
/* > get_linux_ticks.c
|
||||
*
|
||||
* 1.03 arb Fri Jul 22 19:13:03 BST 2005 - removed config options for logging
|
||||
* suspicious time jumps and using re-entrant-safe calls.
|
||||
* 1.02 arb Fri Aug 6 13:08:21 BST 2004 - tidied up ifdefs
|
||||
* 1.01 arb Mon Jun 14 17:50:00 BST 2004 - added timelog
|
||||
*
|
||||
* Copyright (c) 2004 Paul S. Crawford, All Rights Reserved
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* DANG_BEGIN_MODULE
|
||||
*
|
||||
* Description: Convert LINUX time to DOS / RTC time.
|
||||
*
|
||||
* Maintainers: Paul Crawford
|
||||
*
|
||||
* REMARK
|
||||
* This reads the LINUX time and converts it to the DOS ticks per day
|
||||
* value, and if necessary sets the RTC registers to match. It attempts
|
||||
* to put all LINUX->DOS time conversion to one function. Used for RTC init
|
||||
* and for the INT-1A time calls.
|
||||
* /REMARK
|
||||
* DANG_END_MODULE
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* Configuration:
|
||||
* Define USE_PIT_TICK_RATE to use the PIT tick rate.
|
||||
*/
|
||||
#undef USE_PIT_TICK_RATE
|
||||
|
||||
/*
|
||||
* Safety check to make sure 'freedos' is not supplied with a tick value greater than one
|
||||
* day (max = 86399.99 seconds) with its not quite correct 1193180 PIC rate (should be
|
||||
* 1193182 I gather). Correct value should be 1573042, so a difference of about 0.15s
|
||||
* just before midnight.
|
||||
*
|
||||
*/
|
||||
|
||||
#define FREEDOS_TICKS_IN_A_DAY 1573039
|
||||
|
||||
/*
|
||||
* This function attempts to place all code for converting LINUX system time to DOS 'ticks'
|
||||
* and/or the CMOS RTC time in one place. As DOS is based on local time (stupid, I know) it
|
||||
* converts the LINUX/UNIX linear UTC time_t in to local time values and from these it computes
|
||||
* the corresponding number of DOS ~18.2Hz interrupt 'ticks' in the current day. If you want/need
|
||||
* UTC in DOS then start dosemu with a TZ=GMT0 environment variable.
|
||||
*
|
||||
* As the DOS notion of time is based on interrupts per day, and a day counter when this
|
||||
* overflows at midnight, this function can also supply the roll-over flag based on the day
|
||||
* having changed since the last time it was called WITH THE FLAG REQUESTED. This allows it
|
||||
* to be used for both the INT-1A AH=0 function (get ticks) and the AH=2 or 4 (get RTC time/date)
|
||||
* without confusing the once per day roll-over count.
|
||||
*
|
||||
* In addition, should the LINUX time go backwards over midnight (very unfortunate occurrence, but
|
||||
* it is just possible if ntpdate was used to get things correct at once, rather then the ntm daemon
|
||||
* running to slew time to correctness slowly) it will hold the DOS notion of time at 00:00:00
|
||||
* until the LINUX time crosses the midnight boundary again, but without producing two day counts.
|
||||
*
|
||||
* The calling arguments are:
|
||||
*
|
||||
* set_cmos : Non-zero to initialise the CMOS RTC date/time fields. Otherwise RTC unchanged.
|
||||
*
|
||||
* day_rollover : Non-NULL pointer if checking for the day crossing for INT-1A AH=0 use.
|
||||
*
|
||||
* The return value is the DOS 'ticks' from 00:00:00 for this day.
|
||||
* An error results in -1 return (e.g. from problems converting time, perhaps bad TZ environment).
|
||||
*
|
||||
* NOTE: A check in the freedos source shows a *slightly* different value for the nominal tick count
|
||||
* spanning one day, compared to the PIT_TICK_RATE value in timers.h (which is correct, I think). So
|
||||
* if you compile with the freedos values for 100% time conversion compatibility with it, that is fine
|
||||
* but if you compile with the 'correct' PIT_TICK_RATE value, it limits it near midnight so freedos
|
||||
* never gets a value corresponding equal to, or more than, 24 hours.
|
||||
*
|
||||
* NOTE: When setting the RTC it will do this in local time, with any "daylight saving" shift applied,
|
||||
* but it makes no attempt to set the DST flag in the RTC. That is a horrible idea anyway, and I do
|
||||
* not see this as being a big problem for anyone.
|
||||
*
|
||||
* Finally, if time matters for anything important - set it to UTC and set TZ=GMT0 !
|
||||
*
|
||||
*/
|
||||
unsigned long get_linux_ticks(int set_cmos, int *day_rollover)
|
||||
{
|
||||
static long last_ds70 = 0; /* For day changing test. */
|
||||
|
||||
long ds70, isec;
|
||||
long long tt; /* Needs > 32 bits for intermediate product! */
|
||||
struct timeval tv;
|
||||
int year;
|
||||
struct tm *tm;
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
|
||||
tm = localtime(&tv.tv_sec);
|
||||
if(tm == NULL) return -1;
|
||||
|
||||
year = 1900 + tm->tm_year;
|
||||
isec = tm->tm_sec + 60 * (tm->tm_min + 60 * tm->tm_hour); /* Seconds in day. */
|
||||
ds70 = tm->tm_yday + (year-1970)*365L + (year-1969)/4; /* Days from 1970 */
|
||||
if(last_ds70 == 0) last_ds70 = ds70;
|
||||
|
||||
/* Compute time of day in 1/100 second units, then to DOS ticks. */
|
||||
tt = isec * 100 + (tv.tv_usec / 10000);
|
||||
|
||||
#ifdef USE_PIT_TICK_RATE
|
||||
tt = (tt * PIT_TICK_RATE) / 6553600; /* Correct as per LINUX code, 44-bit intermediate product.*/
|
||||
if(tt > FREEDOS_TICKS_IN_A_DAY) tt = FREEDOS_TICKS_IN_A_DAY;
|
||||
#else
|
||||
tt = (tt * 59659) / 327680; /* Use same magic numbers as freedos: ke2033/kernel/sysclk.c */
|
||||
#endif
|
||||
|
||||
if(day_rollover != NULL)
|
||||
{
|
||||
/* Here we deal with INT-1A AH=0 calls. */
|
||||
long day_diff = ds70 - last_ds70;
|
||||
*day_rollover = 0;
|
||||
|
||||
if(day_diff < 0)
|
||||
{
|
||||
/* Day change has us go back over midnight - keep 00:00:00 on same day until time caught up */
|
||||
tt = 0;
|
||||
}
|
||||
else if(day_diff > 0)
|
||||
{
|
||||
/* Some DOS assume incrementing flag, others only set. Here we return days passed. */
|
||||
if(day_diff > 255) day_diff = 255;
|
||||
*day_rollover = (int)day_diff;
|
||||
last_ds70 = ds70; /* Re-set day only if flag requested & forward time. */
|
||||
}
|
||||
}
|
||||
|
||||
if(set_cmos)
|
||||
{
|
||||
/* Initialise the date settings of the RTC (CMOS clock) */
|
||||
int cent;
|
||||
SET_CMOS(CMOS_HOUR, tm->tm_hour);
|
||||
SET_CMOS(CMOS_MIN, tm->tm_min);
|
||||
SET_CMOS(CMOS_SEC, tm->tm_sec);
|
||||
|
||||
SET_CMOS(CMOS_DOW, tm->tm_wday + 1);
|
||||
SET_CMOS(CMOS_DOM, tm->tm_mday);
|
||||
SET_CMOS(CMOS_MONTH, tm->tm_mon + 1);
|
||||
|
||||
cent = year/100;
|
||||
SET_CMOS(CMOS_YEAR, year - 100*cent); /* mod 100 */
|
||||
SET_CMOS(CMOS_CENTURY, cent);
|
||||
/* Really - we don't want to consider daylight saving in the RTC! */
|
||||
}
|
||||
|
||||
return (unsigned long)tt;
|
||||
}
|
||||
717
src/base/dev/misc/timers.c
Normal file
717
src/base/dev/misc/timers.c
Normal file
@@ -0,0 +1,717 @@
|
||||
/*
|
||||
* DANG_BEGIN_MODULE
|
||||
*
|
||||
* Description: Timer emulation for DOSEMU.
|
||||
*
|
||||
* Maintainers: J. Lawrence Stephan
|
||||
* Scott Buchholz
|
||||
*
|
||||
* REMARK
|
||||
* This is the timer emulation for DOSEMU. It emulates the Programmable
|
||||
* Interval Timer (PIT), and also handles IRQ0 interrupt events.
|
||||
* A lot of animation and video game software are dependent on this module
|
||||
* for high frequency timer interrupts (IRQ0).
|
||||
*
|
||||
* This code will actually generate 18.2 DOS interrupts/second (the code
|
||||
* here itself will be triggered about 100 times per second). It will even
|
||||
* happily attempt to generate faster clocks, right up to the point where
|
||||
* it chokes. Since the absolute best case timing we can get out of Linux
|
||||
* is 100Hz, figure that anything approaching or exceeding that isn't going
|
||||
* to work well. (The code will attempt to generate up to 10Khz interrupts
|
||||
* per second at the moment. Too bad that would probably overflow all
|
||||
* internal queues really fast. :)
|
||||
*
|
||||
* Speaker emulation, now including port 61h, is also in here. [rz]
|
||||
*
|
||||
* /REMARK
|
||||
* DANG_END_MODULE
|
||||
*
|
||||
*/
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include "emu.h"
|
||||
#include "port.h"
|
||||
#include "iodev.h"
|
||||
#include "int.h"
|
||||
#include "emudpmi.h"
|
||||
#include "vtmr.h"
|
||||
#include "evtimer.h"
|
||||
#include "timers.h"
|
||||
|
||||
#undef DEBUG_PIT
|
||||
#undef ONE_MINUTE_TEST
|
||||
|
||||
/*******************************************************************
|
||||
* Programmable Interrupt Timer (PIT) chip *
|
||||
*******************************************************************/
|
||||
|
||||
typedef struct {
|
||||
Bit16u read_state;
|
||||
Bit16u write_state;
|
||||
Bit8u mode, outpin;
|
||||
Bit32u read_latch;
|
||||
Bit16u write_latch;
|
||||
Bit32s cntr;
|
||||
hitimer_u time;
|
||||
uint32_t q_ticks;
|
||||
void *evtmr;
|
||||
int tmr_skip;
|
||||
} pit_latch_struct;
|
||||
|
||||
static pit_latch_struct pit[PIT_TIMERS]; /* values of 3 PIT counters */
|
||||
|
||||
hitimer_t pic_sys_time; /* system time set by pic_watch */
|
||||
static int irq0_cnt;
|
||||
|
||||
#define NEVER -1
|
||||
static hitimer_t pic_itime[33] = /* time to trigger next interrupt */
|
||||
{NEVER, NEVER, NEVER, NEVER, NEVER, NEVER, NEVER, NEVER,
|
||||
NEVER, NEVER, NEVER, NEVER, NEVER, NEVER, NEVER, NEVER,
|
||||
NEVER, NEVER, NEVER, NEVER, NEVER, NEVER, NEVER, NEVER,
|
||||
NEVER, NEVER, NEVER, NEVER, NEVER, NEVER, NEVER, NEVER,
|
||||
NEVER};
|
||||
|
||||
static inline uint64_t muldiv64(uint64_t a, uint32_t b, uint32_t c)
|
||||
{
|
||||
return /*(__int128_t)*/a * b / c;
|
||||
}
|
||||
|
||||
#define TICKS_TO_NS(t) muldiv64(t, NANOSECONDS_PER_SECOND, PIT_TICK_RATE)
|
||||
#define NS_TO_TICKS(n) muldiv64(n, PIT_TICK_RATE, NANOSECONDS_PER_SECOND)
|
||||
|
||||
static Bit8u port61 = 0x0c;
|
||||
|
||||
int is_cli;
|
||||
|
||||
/*
|
||||
* DANG_BEGIN_FUNCTION timer_tick
|
||||
*
|
||||
* description:
|
||||
* Every time we get a TIMER signal from Linux, this procedure is called.
|
||||
* It checks to see if we should queue a timer interrupt based on the
|
||||
* current values.
|
||||
*
|
||||
* DANG_END_FUNCTION
|
||||
*/
|
||||
void timer_tick(void)
|
||||
{
|
||||
pic_sys_time = GETtickTIME(0);
|
||||
|
||||
if (config.cli_timeout && is_cli) {
|
||||
if (isset_IF()) {
|
||||
is_cli = 0;
|
||||
} else if (is_cli++ >= config.cli_timeout) {
|
||||
g_printf("Warning: Interrupts were disabled for too long, "
|
||||
"re-enabling.\n");
|
||||
set_IF();
|
||||
}
|
||||
}
|
||||
dpmi_timer();
|
||||
}
|
||||
|
||||
|
||||
#include "speaker.h"
|
||||
|
||||
/* DANG_BEGIN_FUNCTION do_sound
|
||||
*
|
||||
* do_sound handles the _emulated_ mode pc-speaker emulation.
|
||||
*
|
||||
* As far as I can determine all cases of the pc-speaker are now
|
||||
* emulated. But I am not sure where Rainer Zimmerman got his
|
||||
* (pit[2].mode == 2) || (pit[2].mode == 3) test in the original
|
||||
* implementation, it doesn't seem to cause problems though.
|
||||
*
|
||||
* The implementation of speaker_on & speaker_off can be found in
|
||||
* src/base/speaker.c
|
||||
*
|
||||
* Major Changes from version written by Rainter Zimmerman.
|
||||
*
|
||||
* o Added support for programs that control the directly through bit 1
|
||||
* of port61.
|
||||
*
|
||||
* o Added a generic interface to allow multiple speaker backends.
|
||||
*
|
||||
* o Implemented X speaker code so the emulated speaker now works in X.
|
||||
*
|
||||
* --EB 21 September 1997
|
||||
* DANG_END_FUNCTION
|
||||
*/
|
||||
void do_sound(Bit16u period)
|
||||
{
|
||||
/* Note I assume that a sound before after another will kill
|
||||
* the previous sound I had a hard time getting X to do that.
|
||||
* If it becomes a problem possibly tuning sound_duration is
|
||||
* the answer. I suggest 200ms but as I don't have that
|
||||
* problem now I'm not worring about it.
|
||||
*
|
||||
* But if you set it too low, then sounds can be cut off - clarence
|
||||
*/
|
||||
static const unsigned sound_duration = 30000; /* in milliseconds */
|
||||
switch (port61 & 3) {
|
||||
case 3: /* speaker on & speaker control through timer channel 2 */
|
||||
if ((pit[2].mode == 2) || (pit[2].mode == 3)) { /* is this test needed? */
|
||||
speaker_on(sound_duration,period);
|
||||
}
|
||||
else {
|
||||
speaker_off(); /* is this correct? */
|
||||
}
|
||||
break;
|
||||
case 2: /* speaker on & direct speaker through bit 1 */
|
||||
speaker_on(sound_duration, 0xfff); /* on as long as possible */
|
||||
break;
|
||||
case 1: /* speaker off & speaker control through timer channel 2 */
|
||||
case 0: /* speaker off & direct speaker through bit 1 */
|
||||
speaker_off();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int _pit_latch(int latch, uint64_t cur)
|
||||
{
|
||||
int ret = 0;
|
||||
hitimer_u cur_time;
|
||||
long ticks=0;
|
||||
pit_latch_struct *p = &pit[latch];
|
||||
|
||||
/* check for special 'read latch status' mode */
|
||||
if (p->mode & 0x80) {
|
||||
/*
|
||||
* Latch status:
|
||||
* bit 7 = state of OUT pin
|
||||
* bit 6 = null count flag (1 == no cntr set, 0 == cntr available)
|
||||
* bit 4-5 = read latch format
|
||||
* bit 1-3 = read latch mode
|
||||
* bit 0 = BCD flag (1 == BCD, 0 == 16-bit -- always 0)
|
||||
*/
|
||||
p->read_latch = (p->read_state << 4) |
|
||||
((p->mode & 7) << 1) |
|
||||
p->outpin;
|
||||
if (p->cntr == -1)
|
||||
p->read_latch |= 0x40;
|
||||
p->mode &= ~0x80;
|
||||
return ret; /* let bit 7 on */
|
||||
}
|
||||
|
||||
cur_time.td = cur;
|
||||
|
||||
if ((p->mode & 2)==0) {
|
||||
/* non-periodical modes 0,1,4,5 - used mainly by games
|
||||
* count down to 0, then set output, wrap and continue counting (gate=1)
|
||||
* only modes 0,4 allow reloading of the counter on the fly
|
||||
* (thus modes 1,5 are not correctly implemented)
|
||||
* we are just not interested in the gate/output pins...
|
||||
*/
|
||||
/* mode 0 -- interrupt on terminal count, ctr reload=Y */
|
||||
/* mode 4 -- software triggered pulse, ctr reload=Y */
|
||||
/* mode 1 -- programmable monoflop, ctr reload=N */
|
||||
/* mode 5 -- hardware triggered pulse, ctr reload=N */
|
||||
/* should have been initialized to the value in write_latch */
|
||||
|
||||
if (p->cntr != -1) {
|
||||
ticks = NS_TO_TICKS(cur_time.td - p->time.td);
|
||||
|
||||
if (ticks > p->cntr) { /* time has elapsed */
|
||||
if ((p->mode&0x40)==0)
|
||||
p->cntr = -1;
|
||||
p->outpin = (p->mode&4? 0x00: 0x80);
|
||||
} else {
|
||||
p->read_latch = p->cntr - ticks;
|
||||
p->outpin = (p->mode&4? 0x80: 0x00);
|
||||
}
|
||||
}
|
||||
if (p->cntr == -1) {
|
||||
p->read_latch = p->write_latch;
|
||||
p->outpin = (p->mode&4? 0x00: 0x80);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* mode 2 -- rate generator */
|
||||
/* mode 6 -- ??? */
|
||||
/* mode 3 -- square-wave generator, countdown by 2 */
|
||||
/* mode 7 -- ??? */
|
||||
if (latch == 0) {
|
||||
#if 0
|
||||
/* when current time is greater than irq time, call pic_request
|
||||
which will then point pic_itime to next interrupt */
|
||||
if (((p->mode&0x40)==0) && (pic_sys_time > pic_itime[PIC_IRQ0])) {
|
||||
if (pic_request(PIC_IRQ0)==PIC_REQ_OK)
|
||||
{ r_printf("PIT: pit_latch, pic_request IRQ0 mode 2/3\n"); }
|
||||
}
|
||||
#endif
|
||||
/* while current time is less than next irq time, ticks decrease;
|
||||
* ticks can go out of bounds or negative when the interrupt
|
||||
* is lost or pending */
|
||||
if (cur > pic_itime[latch]) {
|
||||
ticks = 1;
|
||||
ret++; // underflow seen
|
||||
} else {
|
||||
ticks = NS_TO_TICKS(pic_itime[latch] - cur) + 1;
|
||||
if (ticks > p->cntr)
|
||||
ticks = 0; // should not be here
|
||||
}
|
||||
} else {
|
||||
ticks = p->cntr - (cur % p->cntr);
|
||||
}
|
||||
|
||||
if ((p->mode & 3)==3) {
|
||||
/* ticks is now a value which decreases from cntr to 0, and
|
||||
is greater than cntr/2 in the first half of the cycle */
|
||||
ticks *= 2;
|
||||
if (ticks >= p->cntr) {
|
||||
p->outpin = 0x00; p->read_latch = (ticks-p->cntr) & 0xfffe;
|
||||
}
|
||||
else {
|
||||
p->outpin = 0x80; p->read_latch = ticks;
|
||||
}
|
||||
}
|
||||
else {
|
||||
p->read_latch = ticks;
|
||||
p->outpin = (p->read_latch? 0x80: 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_PIT
|
||||
i_printf("PIT%d: ticks=%lx latch=%x pin=%d\n",latch,ticks,
|
||||
p->read_latch,(p->outpin!=0));
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int do_pit_latch(int latch)
|
||||
{
|
||||
int ret;
|
||||
uint64_t cur_time;
|
||||
|
||||
evtimer_block(pit[latch].evtmr);
|
||||
cur_time = evtimer_gettime(pit[latch].evtmr);
|
||||
/* if timer is lagging we run it by hands */
|
||||
if (cur_time > pic_itime[latch] &&
|
||||
__sync_bool_compare_and_swap(&pit[latch].q_ticks, 0, 1)) {
|
||||
/* timer thread blocked, we can increment vars w/o sync/atomics */
|
||||
pit[latch].tmr_skip++;
|
||||
if (!latch)
|
||||
vtmr_raise(VTMR_PIT);
|
||||
else
|
||||
pit[latch].q_ticks--;
|
||||
pit[latch].time.td = pic_itime[latch];
|
||||
pic_itime[latch] += TICKS_TO_NS(pit[latch].cntr);
|
||||
}
|
||||
ret = _pit_latch(latch, cur_time);
|
||||
vtmr_sync(VTMR_PIT);
|
||||
evtimer_unblock(pit[latch].evtmr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pit_latch_hndl(void)
|
||||
{
|
||||
return do_pit_latch(0);
|
||||
}
|
||||
|
||||
static void pit_latch(int latch)
|
||||
{
|
||||
if (latch == 0)
|
||||
vtmr_latch(VTMR_PIT);
|
||||
else
|
||||
do_pit_latch(latch);
|
||||
}
|
||||
|
||||
/* This is called also by port 0x61 - some programs can use timer #2
|
||||
* as a GP timer and read bit 5 of port 0x61 (e.g. Matrox BIOS)
|
||||
*/
|
||||
Bit8u pit_inp(ioport_t port, void *arg)
|
||||
{
|
||||
int ret = 0;
|
||||
port -= 0x40;
|
||||
|
||||
if ((port == 2) && (config.speaker == SPKR_NATIVE))
|
||||
return std_port_inb(0x42);
|
||||
else if (port == 1)
|
||||
i_printf("PIT: someone is reading the CMOS refresh time?!?");
|
||||
|
||||
if (pit[port].read_latch == -1)
|
||||
pit_latch(port);
|
||||
|
||||
switch (pit[port].read_state) {
|
||||
case 0: /* read MSB & return to state 3 */
|
||||
ret = (pit[port].read_latch >> 8) & 0xff;
|
||||
pit[port].read_state = 3;
|
||||
pit[port].read_latch = -1;
|
||||
break;
|
||||
case 3: /* read LSB followed by MSB */
|
||||
ret = (pit[port].read_latch & 0xff);
|
||||
if (pit[port].mode & 0x80) pit[port].mode &= 7; /* moved here */
|
||||
else
|
||||
pit[port].read_state = 0;
|
||||
break;
|
||||
case 1: /* read MSB */
|
||||
ret = (pit[port].read_latch >> 8) & 0xff;
|
||||
pit[port].read_latch = -1;
|
||||
break;
|
||||
case 2: /* read LSB */
|
||||
ret = (pit[port].read_latch & 0xff);
|
||||
pit[port].read_latch = -1;
|
||||
break;
|
||||
}
|
||||
#ifdef DEBUG_PIT
|
||||
i_printf("PORT: pit_inp(0x%x) = 0x%x\n", port+0x40, ret);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
void pit_outp(ioport_t port, Bit8u val, void *arg)
|
||||
{
|
||||
|
||||
port -= 0x40;
|
||||
if (port == 1)
|
||||
i_printf("PORT: someone is writing the CMOS refresh time?!?");
|
||||
else if (port == 2 && config.speaker == SPKR_NATIVE) {
|
||||
std_port_outb(0x42, val);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (pit[port].write_state) {
|
||||
case 0:
|
||||
pit[port].write_latch = pit[port].write_latch | ((val & 0xff) << 8);
|
||||
/*
|
||||
* TRICK: some graphics apps use the vertical retrace bit to
|
||||
* calibrate themselves. AFAIK this is done by starting PIT#0
|
||||
* with a value of 0 after detecting the sync, then reading it
|
||||
* again at next sync pulse. In this special case, synchronizing
|
||||
* the emulated vertical retrace with the PIT write yields some
|
||||
* better timing results. It works under X and with emuretrace -- AV
|
||||
*/
|
||||
if (pit[port].write_latch==0) t_vretrace = pic_sys_time;
|
||||
pit[port].write_state = 3;
|
||||
break;
|
||||
case 3:
|
||||
pit[port].write_latch = val & 0xff;
|
||||
pit[port].write_state = 0;
|
||||
break;
|
||||
case 1:
|
||||
pit[port].write_latch = val & 0xff;
|
||||
break;
|
||||
case 2:
|
||||
pit[port].write_latch = (val & 0xff) << 8;
|
||||
break;
|
||||
}
|
||||
#ifdef DEBUG_PIT
|
||||
i_printf("PORT: pit_outp(0x%x, 0x%x)\n", port+0x40, val);
|
||||
#endif
|
||||
|
||||
if (pit[port].write_state != 0) {
|
||||
if (pit[port].write_latch == 0)
|
||||
pit[port].cntr = 0xffff;
|
||||
else
|
||||
pit[port].cntr = pit[port].write_latch;
|
||||
|
||||
if (!port)
|
||||
evtimer_set_rel(pit[port].evtmr, TICKS_TO_NS(pit[port].cntr), 1);
|
||||
else
|
||||
evtimer_stop(pit[port].evtmr);
|
||||
h_printf("PIT: timer %i set to %i ticks\n", port, pit[port].cntr);
|
||||
pit[port].time.td = 0;
|
||||
pic_itime[port] = TICKS_TO_NS(pit[port].cntr);
|
||||
if (port == 2 && (port61 & 3) == 3)
|
||||
do_sound(pit[port].cntr);
|
||||
}
|
||||
}
|
||||
|
||||
Bit8u pit_control_inp(ioport_t port, void *arg)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pit_control_outp(ioport_t port, Bit8u val, void *arg)
|
||||
{
|
||||
int latch = (val >> 6) & 0x03;
|
||||
|
||||
#ifdef DEBUG_PIT
|
||||
i_printf("PORT: outp(0x43, 0x%x)\n",val);
|
||||
#endif
|
||||
|
||||
/* Timer commands (00-BF):
|
||||
* bit 6-7 = Timer (0,1,2)
|
||||
* 3 is not a timer but a special read-back command
|
||||
*
|
||||
* bit 4-5 = command 00=latch 01=LSB mode 10=MSB mode 11=16bit mode
|
||||
* bit 1-3 = mode selection
|
||||
* mode 0(000) 1(001) 2(010 or 110) 3(011 or 111)
|
||||
* 4(100) 5(101)
|
||||
* 6(110) and 7(111) undefined?
|
||||
* bit 0 = binary(0), BCD(1)
|
||||
*
|
||||
* 0xh counter latch timer 0
|
||||
* 1xh timer 0 LSB mode
|
||||
* modes: 0,2,3,4 - 1,5 not applicable
|
||||
* 2xh timer 0 MSB mode
|
||||
* modes: 0,2,3,4 - 1,5 not applicable
|
||||
* 3xh timer 0 16bit mode
|
||||
* modes: 0,2,4 - 3 typical - 1,5 not applicable
|
||||
* modes 1,5 applicable only to timer 2 [van Gilluwe,1994]
|
||||
*/
|
||||
switch (latch) {
|
||||
case 2:
|
||||
if (config.speaker == SPKR_NATIVE) {
|
||||
std_port_outb(0x43, val);
|
||||
break;
|
||||
}
|
||||
/* nobreak; */
|
||||
case 0:
|
||||
case 1:
|
||||
if ((val & 0x30) == 0)
|
||||
pit_latch(latch);
|
||||
else {
|
||||
pit[latch].read_state = (val >> 4) & 0x03;
|
||||
pit[latch].write_state = (val >> 4) & 0x03;
|
||||
pit[latch].mode = (val >> 1) & 0x07;
|
||||
if ((val & 4)==0) { /* modes 0,1,4,5 */
|
||||
/* set the time base for the counter - safety code for programs
|
||||
* which use a non-periodical mode without reloading the counter
|
||||
*/
|
||||
pit[latch].time.td = evtimer_gettime(pit[latch].evtmr);
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG_PIT
|
||||
i_printf("PORT: writing outp(0x43, 0x%x)\n", val);
|
||||
#endif
|
||||
break;
|
||||
case 3:
|
||||
/* I think this code is more or less correct */
|
||||
if ((val & 0x20) == 0) { /* latch counts? */
|
||||
if (val & 0x02) pit_latch(0);
|
||||
if (val & 0x04) pit_latch(1);
|
||||
if (val & 0x08) pit_latch(2);
|
||||
}
|
||||
else if ((val & 0x10) == 0) { /* latch status words? */
|
||||
int or_mask = ((val & 0x20) == 0 ? 0xc0 : 0x80);
|
||||
if (val & 0x02) { pit[0].mode |= or_mask; pit_latch(0); }
|
||||
if (val & 0x04) { pit[1].mode |= or_mask; pit_latch(1); }
|
||||
if (val & 0x08) { pit[2].mode |= or_mask; pit_latch(2); }
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void timer_activate(int ticks, void *arg)
|
||||
{
|
||||
int pit_num = (uintptr_t)arg;
|
||||
uint32_t q;
|
||||
|
||||
if (pit[pit_num].tmr_skip) {
|
||||
pit[pit_num].tmr_skip--;
|
||||
return;
|
||||
}
|
||||
if (!ticks) {
|
||||
error("0 ticks on PIT\n");
|
||||
return;
|
||||
}
|
||||
q = __sync_fetch_and_add(&pit[pit_num].q_ticks, ticks);
|
||||
h_printf("PIT: timer %i expired, %i\n", pit_num, q);
|
||||
if (pit_num) {
|
||||
pit[pit_num].time.td = evtimer_gettime(pit[pit_num].evtmr);
|
||||
return;
|
||||
}
|
||||
if (!q) {
|
||||
vtmr_raise(VTMR_PIT);
|
||||
pit[0].time.td = pic_itime[0];
|
||||
pic_itime[0] += TICKS_TO_NS(pit[0].cntr);
|
||||
}
|
||||
}
|
||||
|
||||
static int timer_irq_ack(int masked)
|
||||
{
|
||||
uint32_t q = __sync_sub_and_fetch(&pit[0].q_ticks, 1);
|
||||
int ret = 0;
|
||||
|
||||
h_printf("PIT: timer 0 acknowledged, %i\n", q);
|
||||
|
||||
if (q) {
|
||||
pit[0].time.td = pic_itime[0];
|
||||
pic_itime[0] += TICKS_TO_NS(pit[0].cntr);
|
||||
ret = 1;
|
||||
}
|
||||
if (!masked)
|
||||
irq0_cnt++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* reads/writes to the speaker control port (0x61)
|
||||
* Port 0x61 is really more complex than a speaker enable bit... look here:
|
||||
* [output]:
|
||||
* Bit(s) Description
|
||||
* 7 pulse to 1 for IRQ1 reset (PC,XT)
|
||||
* 6-4 reserved
|
||||
* 3 I/O channel parity check disable
|
||||
* 2 RAM parity check disable
|
||||
* 1 speaker data enable
|
||||
* 0 timer 2 gate to speaker enable
|
||||
*
|
||||
* [input]:
|
||||
* Bit(s) Description
|
||||
* 7 RAM parity error occurred
|
||||
* 6 I/O channel parity error occurred
|
||||
* 5 mirrors timer 2 output condition
|
||||
* 4 toggles with each refresh request
|
||||
* 3 NMI I/O channel check status
|
||||
* 2 NMI parity check status
|
||||
* 1 speaker data status
|
||||
* 0 timer 2 clock gate to speaker status
|
||||
*/
|
||||
Bit8u spkr_io_read(ioport_t port) {
|
||||
if (port==0x61) {
|
||||
if (config.speaker == SPKR_NATIVE)
|
||||
return std_port_inb(0x61);
|
||||
else {
|
||||
/* keep the connection between port 0x61 and PIT timer#2 */
|
||||
pit_latch(2);
|
||||
return ((*((Bit8u *)&pic_sys_time)&0x10) | /* or anything that toggles quick enough */
|
||||
(pit[2].outpin? 0x20:0) | /* outpin: 00 or 80 */
|
||||
(port61&0xcf));
|
||||
}
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void spkr_io_write(ioport_t port, Bit8u value) {
|
||||
if (port==0x61) {
|
||||
switch (config.speaker) {
|
||||
case SPKR_NATIVE:
|
||||
std_port_outb(0x61, value & 0x03);
|
||||
break;
|
||||
|
||||
case SPKR_EMULATED:
|
||||
if ((value & 3) == (port61 & 3))
|
||||
break;
|
||||
port61 = value & 0x0f;
|
||||
do_sound(pit[2].write_latch & 0xffff);
|
||||
break;
|
||||
|
||||
case SPKR_OFF:
|
||||
port61 = value & 0x0c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pit_init(void)
|
||||
{
|
||||
emu_iodev_t io_device;
|
||||
|
||||
/* 8254 PIT (Programmable Interval Timer) */
|
||||
io_device.read_portb = pit_inp;
|
||||
io_device.write_portb = pit_outp;
|
||||
io_device.read_portw = NULL;
|
||||
io_device.write_portw = NULL;
|
||||
io_device.read_portd = NULL;
|
||||
io_device.write_portd = NULL;
|
||||
io_device.handler_name = "8254 Timer0";
|
||||
io_device.start_addr = 0x0040;
|
||||
io_device.end_addr = 0x0040;
|
||||
port_register_handler(io_device, 0);
|
||||
|
||||
io_device.handler_name = "8254 Timer1";
|
||||
io_device.start_addr = 0x0041;
|
||||
io_device.end_addr = 0x0041;
|
||||
port_register_handler(io_device, 0);
|
||||
|
||||
io_device.handler_name = "8254 Timer2";
|
||||
io_device.start_addr = 0x0042;
|
||||
io_device.end_addr = 0x0042;
|
||||
port_register_handler(io_device, config.speaker==SPKR_NATIVE? PORT_FAST:0);
|
||||
|
||||
io_device.read_portb = pit_control_inp;
|
||||
io_device.write_portb = pit_control_outp;
|
||||
io_device.handler_name = "8254 Ctrl02";
|
||||
io_device.start_addr = 0x0043;
|
||||
io_device.end_addr = 0x0043;
|
||||
port_register_handler(io_device, 0);
|
||||
|
||||
/* register_handler for port 0x61 is in keyboard code */
|
||||
port61 = 0x0c;
|
||||
#if 0
|
||||
io_device.start_addr = 0x0047;
|
||||
io_device.end_addr = 0x0047;
|
||||
port_register_handler(io_device, 0);
|
||||
#endif
|
||||
|
||||
vtmr_register(VTMR_PIT, timer_irq_ack);
|
||||
vtmr_register_latch(VTMR_PIT, pit_latch_hndl);
|
||||
vtmr_set_tweaked(VTMR_PIT, config.timer_tweaks, 0);
|
||||
|
||||
pit[0].evtmr = evtimer_create(timer_activate, (void *)(uintptr_t)0);
|
||||
pit[1].evtmr = evtimer_create(timer_activate, (void *)(uintptr_t)1);
|
||||
pit[2].evtmr = evtimer_create(timer_activate, (void *)(uintptr_t)2);
|
||||
}
|
||||
|
||||
void pit_done(void)
|
||||
{
|
||||
evtimer_delete(pit[0].evtmr);
|
||||
evtimer_delete(pit[1].evtmr);
|
||||
evtimer_delete(pit[2].evtmr);
|
||||
}
|
||||
|
||||
void pit_reset(void)
|
||||
{
|
||||
pit[0].mode = 3;
|
||||
pit[0].outpin = 0;
|
||||
pit[0].cntr = 0xffff;
|
||||
pit[0].time.td = 0;
|
||||
pit[0].read_latch = 0xffffffff;
|
||||
pit[0].write_latch = 0;
|
||||
pit[0].read_state = 3;
|
||||
pit[0].write_state = 3;
|
||||
pit[0].q_ticks = 0;
|
||||
evtimer_stop(pit[0].evtmr);
|
||||
|
||||
pit[1].mode = 2;
|
||||
pit[1].outpin = 0;
|
||||
pit[1].cntr = 18;
|
||||
pit[1].time.td = 0;
|
||||
pit[1].read_latch = 0xffffffff;
|
||||
pit[1].write_latch = 18;
|
||||
pit[1].read_state = 3;
|
||||
pit[1].write_state = 3;
|
||||
pit[1].q_ticks = 0;
|
||||
evtimer_stop(pit[1].evtmr);
|
||||
|
||||
pit[2].mode = 0;
|
||||
pit[2].outpin = 0;
|
||||
pit[2].cntr = 0xffff;
|
||||
pit[2].time.td = 0;
|
||||
pit[2].read_latch = 0xffffffff;
|
||||
pit[2].write_latch = 0;
|
||||
pit[2].read_state = 3;
|
||||
pit[2].write_state = 3;
|
||||
pit[2].q_ticks = 0;
|
||||
evtimer_stop(pit[2].evtmr);
|
||||
|
||||
pit[3].mode = 0;
|
||||
pit[3].outpin = 0;
|
||||
pit[3].cntr = 0xffff;
|
||||
pit[3].time.td = 0;
|
||||
pit[3].read_latch = 0xffffffff;
|
||||
pit[3].write_latch = 0;
|
||||
pit[3].read_state = 3;
|
||||
pit[3].write_state = 3;
|
||||
|
||||
port61 = 0x0c;
|
||||
|
||||
pic_sys_time = GETtickTIME(0);
|
||||
}
|
||||
|
||||
void pit_late_init(void)
|
||||
{
|
||||
evtimer_set_rel(pit[0].evtmr, TICKS_TO_NS(pit[0].cntr), 1);
|
||||
pit[0].time.td = 0;
|
||||
pic_itime[0] = TICKS_TO_NS(pit[0].cntr);
|
||||
}
|
||||
|
||||
#define TIMER0_FLOOD_THRESHOLD 50
|
||||
|
||||
int CAN_SLEEP(void)
|
||||
{
|
||||
return (!(pic_get_isr() || (REG(eflags) & VIP) || signal_pending() ||
|
||||
(pit[0].q_ticks > TIMER0_FLOOD_THRESHOLD) || in_leavedos));
|
||||
}
|
||||
196
src/base/dev/misc/virq.c
Normal file
196
src/base/dev/misc/virq.c
Normal file
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Purpose: virtual IRQ router
|
||||
*
|
||||
* Author: Stas Sergeev.
|
||||
*
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include "port.h"
|
||||
#include "pic.h"
|
||||
#include "hlt.h"
|
||||
#include "cpu.h"
|
||||
#include "int.h"
|
||||
#include "bitops.h"
|
||||
#include "emu.h"
|
||||
#include "virq.h"
|
||||
|
||||
#define VIRQ_IRR_PORT 0x50a
|
||||
#define VIRQ_HWC_PORT (VIRQ_IRR_PORT + 2)
|
||||
#define VIRQ_RST_PORT (VIRQ_IRR_PORT + 3)
|
||||
#define VIRQ_TOTAL_PORTS 4
|
||||
#define VIRQ_IRQ_NUM 0xf
|
||||
#define VIRQ_INTERRUPT (VIRQ_IRQ_NUM - 8 + 0x70)
|
||||
|
||||
static uint16_t virq_irr;
|
||||
static pthread_mutex_t irr_mtx = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_mutex_t hndl_mtx = PTHREAD_MUTEX_INITIALIZER;
|
||||
static uint16_t virq_hlt;
|
||||
|
||||
struct vhandler_s {
|
||||
enum VirqHwRet (*hw_handler)(void *);
|
||||
enum VirqSwRet (*sw_handler)(void *);
|
||||
void *arg;
|
||||
};
|
||||
struct vhandler_s vhandlers[VIRQ_MAX];
|
||||
|
||||
static void virq_lower(int virq_num);
|
||||
|
||||
static Bit16u virq_irr_read(ioport_t port, void *arg)
|
||||
{
|
||||
uint16_t irr;
|
||||
|
||||
pthread_mutex_lock(&irr_mtx);
|
||||
irr = virq_irr;
|
||||
pthread_mutex_unlock(&irr_mtx);
|
||||
return irr;
|
||||
}
|
||||
|
||||
static void virq_hwc_write(ioport_t port, Bit8u value, void *arg)
|
||||
{
|
||||
struct vhandler_s *vh;
|
||||
enum VirqHwRet rc = VIRQ_HWRET_DONE;
|
||||
|
||||
switch (port) {
|
||||
case VIRQ_RST_PORT:
|
||||
switch (value) {
|
||||
case 1:
|
||||
/* re-assert irqs */
|
||||
if (virq_irr)
|
||||
pic_request(VIRQ_IRQ_NUM);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case VIRQ_HWC_PORT:
|
||||
assert(value < VIRQ_MAX);
|
||||
vh = &vhandlers[value];
|
||||
pthread_mutex_lock(&hndl_mtx);
|
||||
if (vh->hw_handler)
|
||||
rc = vh->hw_handler(vh->arg);
|
||||
if (rc == VIRQ_HWRET_DONE)
|
||||
virq_lower(value);
|
||||
pthread_mutex_unlock(&hndl_mtx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void virq_handler(uint16_t idx, HLT_ARG(arg))
|
||||
{
|
||||
uint16_t irr;
|
||||
|
||||
while ((irr = port_inw(VIRQ_IRR_PORT))) {
|
||||
struct vhandler_s *vh;
|
||||
int inum = find_bit(irr);
|
||||
|
||||
assert(inum < VIRQ_MAX);
|
||||
port_outb(VIRQ_HWC_PORT, inum);
|
||||
vh = &vhandlers[inum];
|
||||
if (vh->sw_handler) {
|
||||
enum VirqSwRet rc = vh->sw_handler(vh->arg);
|
||||
if (rc == VIRQ_SWRET_BH) {
|
||||
assert(_IP != virq_hlt);
|
||||
/* If BH is scheduled, we just return and switch back later. */
|
||||
set_IF();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
error("virq: no handler for %i\n", inum);
|
||||
}
|
||||
}
|
||||
assert(_IP == virq_hlt);
|
||||
do_eoi2_iret();
|
||||
}
|
||||
|
||||
void virq_init(void)
|
||||
{
|
||||
emu_iodev_t io_dev = {};
|
||||
emu_hlt_t hlt_hdlr = HLT_INITIALIZER;
|
||||
|
||||
io_dev.write_portb = virq_hwc_write;
|
||||
io_dev.read_portw = virq_irr_read;
|
||||
io_dev.start_addr = VIRQ_IRR_PORT;
|
||||
io_dev.end_addr = VIRQ_IRR_PORT + VIRQ_TOTAL_PORTS - 1;
|
||||
io_dev.handler_name = "virtual IRQ router";
|
||||
port_register_handler(io_dev, 0);
|
||||
|
||||
hlt_hdlr.name = "virq";
|
||||
hlt_hdlr.func = virq_handler;
|
||||
virq_hlt = hlt_register_handler_vm86(hlt_hdlr);
|
||||
}
|
||||
|
||||
void virq_reset(void)
|
||||
{
|
||||
pic_untrigger(VIRQ_IRQ_NUM);
|
||||
}
|
||||
|
||||
void virq_setup(void)
|
||||
{
|
||||
SETIVEC(VIRQ_INTERRUPT, BIOS_HLT_BLK_SEG, virq_hlt);
|
||||
port_outb(VIRQ_RST_PORT, 1);
|
||||
}
|
||||
|
||||
void virq_raise(int virq_num)
|
||||
{
|
||||
uint16_t irr;
|
||||
uint16_t mask = 1 << virq_num;
|
||||
|
||||
assert(virq_num < VIRQ_MAX);
|
||||
pthread_mutex_lock(&hndl_mtx);
|
||||
pthread_mutex_lock(&irr_mtx);
|
||||
/* __sync_fetch_and_or() */
|
||||
irr = virq_irr;
|
||||
virq_irr |= mask;
|
||||
if (!irr)
|
||||
pic_request(VIRQ_IRQ_NUM);
|
||||
pthread_mutex_unlock(&irr_mtx);
|
||||
pthread_mutex_unlock(&hndl_mtx);
|
||||
}
|
||||
|
||||
static void virq_lower(int virq_num)
|
||||
{
|
||||
uint16_t irr;
|
||||
uint16_t mask = 1 << virq_num;
|
||||
|
||||
assert(virq_num < VIRQ_MAX);
|
||||
pthread_mutex_lock(&irr_mtx);
|
||||
/* __sync_and_and_fetch() */
|
||||
virq_irr &= ~mask;
|
||||
irr = virq_irr;
|
||||
if (!irr)
|
||||
pic_untrigger(VIRQ_IRQ_NUM);
|
||||
pthread_mutex_unlock(&irr_mtx);
|
||||
}
|
||||
|
||||
void virq_register(int virq_num, enum VirqHwRet (*hw_handler)(void *),
|
||||
enum VirqSwRet (*sw_handler)(void *), void *arg)
|
||||
{
|
||||
if (virq_num >= VIRQ_MAX)
|
||||
return;
|
||||
vhandlers[virq_num].hw_handler = hw_handler;
|
||||
vhandlers[virq_num].sw_handler = sw_handler;
|
||||
vhandlers[virq_num].arg = arg;
|
||||
}
|
||||
|
||||
void virq_unregister(int virq_num)
|
||||
{
|
||||
vhandlers[virq_num].hw_handler = NULL;
|
||||
vhandlers[virq_num].sw_handler = NULL;
|
||||
}
|
||||
496
src/base/dev/misc/vtmr.c
Normal file
496
src/base/dev/misc/vtmr.c
Normal file
@@ -0,0 +1,496 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Purpose: virtual timer device extension
|
||||
*
|
||||
* Author: Stas Sergeev.
|
||||
*
|
||||
* Note: this code shows how to run coopth in a separate thread.
|
||||
* Many things are written here just as an example.
|
||||
* Perhaps in the future the example should be moved elsewhere.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <semaphore.h>
|
||||
#include "port.h"
|
||||
#include "pic.h"
|
||||
#include "cpu.h"
|
||||
#include "int.h"
|
||||
#include "bitops.h"
|
||||
#include "emudpmi.h"
|
||||
#include "emu.h"
|
||||
#include "coopth.h"
|
||||
#if MULTICORE_EXAMPLE
|
||||
#include "lowmem.h"
|
||||
#include "hlt.h"
|
||||
#endif
|
||||
#include "utilities.h"
|
||||
#include "timers.h"
|
||||
#include "chipset.h"
|
||||
#include "vint.h"
|
||||
#include "vtmr.h"
|
||||
|
||||
#define VTMR_FIRST_PORT 0x550
|
||||
#define VTMR_VPEND_PORT VTMR_FIRST_PORT
|
||||
#define VTMR_IRR_PORT (VTMR_FIRST_PORT + 2)
|
||||
#define VTMR_ACK_PORT (VTMR_FIRST_PORT + 3)
|
||||
#define VTMR_REQUEST_PORT (VTMR_FIRST_PORT + 4)
|
||||
#define VTMR_MASK_PORT (VTMR_FIRST_PORT + 5)
|
||||
#define VTMR_UNMASK_PORT (VTMR_FIRST_PORT + 6)
|
||||
#define VTMR_LATCH_PORT (VTMR_FIRST_PORT + 7)
|
||||
#define VTMR_TOTAL_PORTS 8
|
||||
|
||||
static uint16_t vtmr_irr;
|
||||
static uint16_t vtmr_imr;
|
||||
static uint16_t vtmr_pirr;
|
||||
|
||||
static pthread_t vtmr_thr;
|
||||
static sem_t vtmr_sem;
|
||||
static int latch_tid;
|
||||
static pthread_mutex_t irr_mtx = PTHREAD_MUTEX_INITIALIZER;
|
||||
#if MULTICORE_EXAMPLE
|
||||
static int smi_tid;
|
||||
static char *rmstack;
|
||||
static uint16_t hlt_off;
|
||||
#endif
|
||||
|
||||
struct vthandler {
|
||||
int (*handler)(int);
|
||||
int (*latch)(void);
|
||||
int vint;
|
||||
int done_pred;
|
||||
pthread_mutex_t done_mtx;
|
||||
pthread_cond_t done_cnd;
|
||||
};
|
||||
struct vthandler vth[VTMR_MAX];
|
||||
|
||||
struct vint_presets {
|
||||
uint8_t irq;
|
||||
uint8_t orig_irq;
|
||||
uint8_t interrupt;
|
||||
};
|
||||
static struct vint_presets vip[VTMR_MAX] = {
|
||||
[VTMR_PIT] = { .irq = VTMR_IRQ, .orig_irq = 0,
|
||||
.interrupt = VTMR_INTERRUPT },
|
||||
[VTMR_RTC] = { .irq = VRTC_IRQ, .orig_irq = 8,
|
||||
.interrupt = VRTC_INTERRUPT },
|
||||
};
|
||||
|
||||
static int do_vtmr_raise(int timer);
|
||||
|
||||
static Bit8u vtmr_irr_read(ioport_t port, void *arg)
|
||||
{
|
||||
return vtmr_irr;
|
||||
}
|
||||
|
||||
static Bit16u vtmr_vpend_read(ioport_t port, void *arg)
|
||||
{
|
||||
/* clang has __atomic_swap() */
|
||||
return __atomic_exchange_n(&vtmr_pirr, 0, __ATOMIC_ACQ_REL);
|
||||
}
|
||||
|
||||
static void post_req(int timer)
|
||||
{
|
||||
if (vth[timer].handler) {
|
||||
int rc = vth[timer].handler(0);
|
||||
if (rc)
|
||||
do_vtmr_raise(timer);
|
||||
}
|
||||
h_printf("vtmr: post-REQ on %i, irr=%x\n", timer, vtmr_irr);
|
||||
}
|
||||
|
||||
static void vtmr_io_write(ioport_t port, Bit8u value, void *arg)
|
||||
{
|
||||
int masked = (value >> 7) & 1;
|
||||
int timer = value & 0x7f;
|
||||
uint16_t msk = 1 << timer;
|
||||
|
||||
if (timer >= VTMR_MAX)
|
||||
return;
|
||||
switch (port) {
|
||||
case VTMR_REQUEST_PORT:
|
||||
if (!masked) {
|
||||
uint16_t irr;
|
||||
pthread_mutex_lock(&irr_mtx);
|
||||
irr = __sync_fetch_and_or(&vtmr_irr, msk);
|
||||
if (!(irr & msk)) {
|
||||
if (!(vtmr_imr & msk))
|
||||
pic_request(vip[timer].irq);
|
||||
} else {
|
||||
error("vtmr %i already requested\n", timer);
|
||||
}
|
||||
pthread_mutex_unlock(&irr_mtx);
|
||||
} else {
|
||||
pic_untrigger(vip[timer].orig_irq);
|
||||
pic_request(vip[timer].orig_irq);
|
||||
post_req(timer);
|
||||
}
|
||||
h_printf("vtmr: REQ on %i, irr=%x, pirr=%x masked=%i\n", timer,
|
||||
vtmr_irr, vtmr_pirr, masked);
|
||||
break;
|
||||
case VTMR_MASK_PORT: {
|
||||
uint16_t imr = __sync_fetch_and_or(&vtmr_imr, msk);
|
||||
if (!(imr & msk)) {
|
||||
if (vtmr_irr & msk)
|
||||
pic_untrigger(vip[timer].irq);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VTMR_UNMASK_PORT: {
|
||||
uint16_t imr = __sync_fetch_and_and(&vtmr_imr, ~msk);
|
||||
if (imr & msk) {
|
||||
if (vtmr_irr & msk)
|
||||
pic_request(vip[timer].irq);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VTMR_ACK_PORT: {
|
||||
uint16_t irr;
|
||||
pthread_mutex_lock(&irr_mtx);
|
||||
irr = __sync_fetch_and_and(&vtmr_irr, ~msk);
|
||||
if (irr & msk) {
|
||||
pic_untrigger(vip[timer].irq);
|
||||
if (vth[timer].handler) {
|
||||
int rc = vth[timer].handler(masked);
|
||||
if (rc)
|
||||
do_vtmr_raise(timer);
|
||||
}
|
||||
} else {
|
||||
error("vtmr %i not requested\n", timer);
|
||||
}
|
||||
pthread_mutex_unlock(&irr_mtx);
|
||||
h_printf("vtmr: ACK on %i, irr=%x pirr=%x\n", timer, vtmr_irr,
|
||||
vtmr_pirr);
|
||||
break;
|
||||
}
|
||||
case VTMR_LATCH_PORT: {
|
||||
int from_irq = masked;
|
||||
if (vth[timer].latch) {
|
||||
int rc = vth[timer].latch();
|
||||
if (rc && !from_irq) { // underflow seen not from IRQ
|
||||
uint16_t irr;
|
||||
pthread_mutex_lock(&irr_mtx);
|
||||
irr = __sync_fetch_and_and(&vtmr_irr, ~msk);
|
||||
if (irr & msk) {
|
||||
pic_untrigger(vip[timer].irq);
|
||||
if (vth[timer].handler) {
|
||||
rc = vth[timer].handler(1);
|
||||
if (rc)
|
||||
do_vtmr_raise(timer);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&irr_mtx);
|
||||
}
|
||||
}
|
||||
h_printf("vtmr: LATCH on %i, irr=%x pirr=%x\n", timer, vtmr_irr,
|
||||
vtmr_pirr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void do_ack(int timer, int masked)
|
||||
{
|
||||
port_outb(VTMR_ACK_PORT, timer | (masked << 7));
|
||||
}
|
||||
|
||||
static void ack_handler(int vint, int masked)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < VTMR_MAX; i++) {
|
||||
if (vth[i].vint == vint) {
|
||||
do_ack(i, masked);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void do_mask(int timer)
|
||||
{
|
||||
port_outb(VTMR_MASK_PORT, timer);
|
||||
}
|
||||
|
||||
static void do_unmask(int timer)
|
||||
{
|
||||
port_outb(VTMR_UNMASK_PORT, timer);
|
||||
}
|
||||
|
||||
static void mask_handler(int vint, int masked)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < VTMR_MAX; i++) {
|
||||
if (vth[i].vint == vint) {
|
||||
if (masked)
|
||||
do_mask(i);
|
||||
else
|
||||
do_unmask(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int vtmr_pre_irq_dpmi(uint8_t *imr)
|
||||
{
|
||||
int masked = vint_is_masked(vth[VTMR_PIT].vint, imr);
|
||||
do_mask(VTMR_PIT);
|
||||
do_ack(VTMR_PIT, masked);
|
||||
vint_post_irq_dpmi(vth[VTMR_PIT].vint, masked);
|
||||
return masked;
|
||||
}
|
||||
|
||||
void vtmr_post_irq_dpmi(int masked)
|
||||
{
|
||||
do_unmask(VTMR_PIT);
|
||||
}
|
||||
|
||||
int vrtc_pre_irq_dpmi(uint8_t *imr)
|
||||
{
|
||||
int masked = vint_is_masked(vth[VTMR_RTC].vint, imr);
|
||||
do_mask(VTMR_RTC);
|
||||
do_ack(VTMR_RTC, masked);
|
||||
vint_post_irq_dpmi(vth[VTMR_RTC].vint, masked);
|
||||
return masked;
|
||||
}
|
||||
|
||||
void vrtc_post_irq_dpmi(int masked)
|
||||
{
|
||||
do_unmask(VTMR_RTC);
|
||||
}
|
||||
|
||||
static int vtmr_is_masked(int timer)
|
||||
{
|
||||
uint8_t imr[2] = { [0] = port_inb(0x21), [1] = port_inb(0xa1) };
|
||||
uint16_t real_imr = (imr[1] << 8) | imr[0];
|
||||
return ((imr[0] & 4) || !!(real_imr & (1 << vip[timer].irq)));
|
||||
}
|
||||
|
||||
static void vtmr_smi(void *arg)
|
||||
{
|
||||
int timer;
|
||||
uint16_t pirr = port_inw(VTMR_VPEND_PORT);
|
||||
|
||||
while ((timer = find_bit(pirr)) != -1) {
|
||||
int masked = vtmr_is_masked(timer);
|
||||
pirr &= ~(1 << timer);
|
||||
port_outb(VTMR_REQUEST_PORT, timer | (masked << 7));
|
||||
if (!masked)
|
||||
port_outb(0x4d2, 1); // set fake IRR
|
||||
pthread_mutex_lock(&vth[timer].done_mtx);
|
||||
vth[timer].done_pred = 1;
|
||||
pthread_mutex_unlock(&vth[timer].done_mtx);
|
||||
pthread_cond_signal(&vth[timer].done_cnd);
|
||||
}
|
||||
}
|
||||
|
||||
static void vtmr_latch_smi(void *arg)
|
||||
{
|
||||
uint16_t isr;
|
||||
int from_irq;
|
||||
int timer = (uintptr_t)arg;
|
||||
|
||||
assert(timer < VTMR_MAX);
|
||||
port_outb(0x20, 0xb);
|
||||
isr = port_inb(0x20);
|
||||
port_outb(0xa0, 0xb);
|
||||
isr = (port_inb(0xa0) << 8);
|
||||
from_irq = !!(isr & (1 << vip[timer].orig_irq));
|
||||
port_outb(VTMR_LATCH_PORT, timer | (from_irq << 7));
|
||||
}
|
||||
|
||||
#if MULTICORE_EXAMPLE
|
||||
static void thr_cleanup(void *arg)
|
||||
{
|
||||
coopth_done();
|
||||
lowmem_free(rmstack);
|
||||
}
|
||||
#endif
|
||||
|
||||
#define RMSTACK_SIZE 32
|
||||
|
||||
static void *vtmr_thread(void *arg)
|
||||
{
|
||||
#if MULTICORE_EXAMPLE
|
||||
/* init coopth in new thread */
|
||||
coopth_init();
|
||||
pthread_cleanup_push(thr_cleanup, NULL);
|
||||
smi_tid = coopth_create("vtmr smi", vtmr_smi);
|
||||
cpu_reset();
|
||||
_SS = DOSEMU_LMHEAP_SEG;
|
||||
_SP = DOSEMU_LMHEAP_OFFS_OF(rmstack) + RMSTACK_SIZE;
|
||||
_CS = BIOS_HLT_BLK_SEG;
|
||||
_IP = hlt_off;
|
||||
clear_IF();
|
||||
|
||||
/* run our fake core */
|
||||
while (1)
|
||||
run_vm86();
|
||||
pthread_cleanup_pop(1);
|
||||
#else
|
||||
while (1) {
|
||||
sem_wait(&vtmr_sem);
|
||||
vtmr_smi(NULL);
|
||||
}
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if MULTICORE_EXAMPLE
|
||||
static void vtmr_hlt(Bit16u idx, HLT_ARG(arg))
|
||||
{
|
||||
sem_wait(&vtmr_sem);
|
||||
coopth_start(smi_tid, NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
void vtmr_init(void)
|
||||
{
|
||||
emu_iodev_t io_dev = {};
|
||||
#if MULTICORE_EXAMPLE
|
||||
emu_hlt_t hlt_hdlr = HLT_INITIALIZER;
|
||||
#endif
|
||||
int i;
|
||||
|
||||
io_dev.write_portb = vtmr_io_write;
|
||||
io_dev.read_portb = vtmr_irr_read;
|
||||
io_dev.read_portw = vtmr_vpend_read;
|
||||
io_dev.start_addr = VTMR_FIRST_PORT;
|
||||
io_dev.end_addr = VTMR_FIRST_PORT + VTMR_TOTAL_PORTS - 1;
|
||||
io_dev.handler_name = "virtual timer";
|
||||
port_register_handler(io_dev, 0);
|
||||
|
||||
#if MULTICORE_EXAMPLE
|
||||
rmstack = lowmem_alloc(RMSTACK_SIZE);
|
||||
hlt_hdlr.name = "vtmr sleep";
|
||||
hlt_hdlr.func = vtmr_hlt;
|
||||
hlt_off = hlt_register_handler_vm86(hlt_hdlr);
|
||||
#endif
|
||||
|
||||
latch_tid = coopth_create("vtmr latch smi", vtmr_latch_smi);
|
||||
coopth_set_ctx_handlers(latch_tid, sig_ctx_prepare, sig_ctx_restore, NULL);
|
||||
|
||||
sem_init(&vtmr_sem, 0, 0);
|
||||
for (i = 0; i < VTMR_MAX; i++) {
|
||||
pthread_mutex_init(&vth[i].done_mtx, NULL);
|
||||
pthread_cond_init(&vth[i].done_cnd, NULL);
|
||||
vth[i].done_pred = 1;
|
||||
}
|
||||
pthread_create(&vtmr_thr, NULL, vtmr_thread, NULL);
|
||||
#if defined(HAVE_PTHREAD_SETNAME_NP) && defined(__GLIBC__)
|
||||
pthread_setname_np(vtmr_thr, "dosemu: vtmr");
|
||||
#endif
|
||||
}
|
||||
|
||||
void vtmr_done(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
pthread_cancel(vtmr_thr);
|
||||
pthread_join(vtmr_thr, NULL);
|
||||
sem_destroy(&vtmr_sem);
|
||||
for (i = 0; i < VTMR_MAX; i++) {
|
||||
pthread_mutex_destroy(&vth[i].done_mtx);
|
||||
pthread_cond_destroy(&vth[i].done_cnd);
|
||||
}
|
||||
#if MULTICORE_EXAMPLE
|
||||
lowmem_free(rmstack);
|
||||
#endif
|
||||
}
|
||||
|
||||
void vtmr_reset(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
vtmr_irr = 0;
|
||||
vtmr_imr = 0;
|
||||
vtmr_pirr = 0;
|
||||
for (i = 0; i < VTMR_MAX; i++)
|
||||
pic_untrigger(vip[i].irq);
|
||||
}
|
||||
|
||||
static int do_vtmr_raise(int timer)
|
||||
{
|
||||
uint16_t pirr;
|
||||
uint16_t mask = 1 << timer;
|
||||
|
||||
assert(timer < VTMR_MAX);
|
||||
h_printf("vtmr: raise timer %i\n", timer);
|
||||
pirr = __sync_fetch_and_or(&vtmr_pirr, mask);
|
||||
if (!(pirr & mask)) {
|
||||
h_printf("vtmr: posting timer event\n");
|
||||
sem_post(&vtmr_sem);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void vtmr_raise(int timer)
|
||||
{
|
||||
int rc;
|
||||
|
||||
pthread_mutex_lock(&vth[timer].done_mtx);
|
||||
vth[timer].done_pred = 0;
|
||||
pthread_mutex_unlock(&vth[timer].done_mtx);
|
||||
|
||||
rc = do_vtmr_raise(timer);
|
||||
if (!rc) {
|
||||
pthread_mutex_lock(&vth[timer].done_mtx);
|
||||
vth[timer].done_pred = 1;
|
||||
pthread_mutex_unlock(&vth[timer].done_mtx);
|
||||
pthread_cond_signal(&vth[timer].done_cnd);
|
||||
}
|
||||
}
|
||||
|
||||
void vtmr_latch(int timer)
|
||||
{
|
||||
if (in_dpmi_pm())
|
||||
fake_pm_int();
|
||||
coopth_start(latch_tid, (void *)(uintptr_t)timer);
|
||||
}
|
||||
|
||||
void vtmr_sync(int timer)
|
||||
{
|
||||
pthread_mutex_lock(&vth[timer].done_mtx);
|
||||
while (!vth[timer].done_pred)
|
||||
cond_wait(&vth[timer].done_cnd, &vth[timer].done_mtx);
|
||||
pthread_mutex_unlock(&vth[timer].done_mtx);
|
||||
}
|
||||
|
||||
void vtmr_register(int timer, int (*handler)(int))
|
||||
{
|
||||
struct vthandler *vt = &vth[timer];
|
||||
struct vint_presets *vp = &vip[timer];
|
||||
assert(timer < VTMR_MAX);
|
||||
vt->handler = handler;
|
||||
vt->vint = vint_register(ack_handler, mask_handler, vp->irq,
|
||||
vp->orig_irq, vp->interrupt);
|
||||
}
|
||||
|
||||
void vtmr_register_latch(int timer, int (*handler)(void))
|
||||
{
|
||||
struct vthandler *vt = &vth[timer];
|
||||
assert(timer < VTMR_MAX);
|
||||
vt->latch = handler;
|
||||
}
|
||||
|
||||
void vtmr_set_tweaked(int timer, int on, unsigned flags)
|
||||
{
|
||||
vint_set_tweaked(vth[timer].vint, on, flags);
|
||||
}
|
||||
Reference in New Issue
Block a user