New upstream version 2.0pre9.2
Some checks failed
Master / Scheduled (FULL) (push) Has been cancelled
Master / Triggered (push) Has been cancelled
Master / Triggered (ASAN) (push) Has been cancelled
Master / Triggered (FULL) (push) Has been cancelled

This commit is contained in:
geos_one
2025-08-10 12:35:43 +02:00
commit 91736529d5
1056 changed files with 370820 additions and 0 deletions

436
src/base/dev/misc/8042.c Normal file
View 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;
}

View 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

View 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
View 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

File diff suppressed because it is too large Load Diff

252
src/base/dev/misc/kbd.c Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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);
}