From edbd4885542e4545439906df950a68dc48e70839 Mon Sep 17 00:00:00 2001 From: Florian Tobias Schandinat Date: Tue, 3 Feb 2009 18:26:04 +0100 Subject: [PATCH] winedos: Improve PIT emulation. --- dlls/winedos/ioports.c | 195 +++++++++++++++++++++++++++++------------ 1 file changed, 140 insertions(+), 55 deletions(-) diff --git a/dlls/winedos/ioports.c b/dlls/winedos/ioports.c index 2de1b1587e3..e9a825fdd0b 100644 --- a/dlls/winedos/ioports.c +++ b/dlls/winedos/ioports.c @@ -50,18 +50,26 @@ WINE_DEFAULT_DEBUG_CHANNEL(int); static struct { WORD countmax; - BOOL16 byte_toggle; /* if TRUE, then hi byte has already been written */ WORD latch; - BOOL16 latched; BYTE ctrlbyte_ch; - WORD oldval; + BYTE flags; + LONG64 start_time; } tmr_8253[3] = { - {0xFFFF, FALSE, 0, FALSE, 0x36, 0}, - {0x0012, FALSE, 0, FALSE, 0x74, 0}, - {0x0001, FALSE, 0, FALSE, 0xB6, 0}, + {0xFFFF, 0, 0x36, 0, 0}, + {0x0012, 0, 0x74, 0, 0}, + {0x0001, 0, 0xB6, 0, 0}, }; +/* two byte read in progress */ +#define TMR_RTOGGLE 0x01 +/* two byte write in progress */ +#define TMR_WTOGGLE 0x02 +/* latch contains data */ +#define TMR_LATCHED 0x04 +/* counter is in update phase */ +#define TMR_UPDATE 0x08 +/* readback status request */ +#define TMR_STATUS 0x10 -static int dummy_ctr = 0; static BYTE parport_8255[4] = {0x4f, 0x20, 0xff, 0xff}; @@ -165,11 +173,26 @@ static int do_pp_port_access = -1; /* -1: uninitialized, 1: not available 0: available);*/ #endif -static void set_timer_maxval(unsigned timer, unsigned maxval) +#define BCD2BIN(a) \ +((a)%10 + ((a)>>4)%10*10 + ((a)>>8)%10*100 + ((a)>>12)%10*1000) +#define BIN2BCD(a) \ +((a)%10 | (a)/10%10<<4 | (a)/100%10<<8 | (a)/1000%10<<12) + + +static void set_timer(unsigned timer) { + DWORD val = tmr_8253[timer].countmax; + + if (tmr_8253[timer].ctrlbyte_ch & 0x01) + val = BCD2BIN(val); + + tmr_8253[timer].flags &= ~TMR_UPDATE; + if (!QueryPerformanceCounter((LARGE_INTEGER*)&tmr_8253[timer].start_time)) + WARN("QueryPerformanceCounter should not fail!\n"); + switch (timer) { case 0: /* System timer counter divisor */ - DOSVM_SetTimer(maxval); + DOSVM_SetTimer(val); break; case 1: /* RAM refresh */ FIXME("RAM refresh counter handling not implemented !\n"); @@ -178,14 +201,60 @@ static void set_timer_maxval(unsigned timer, unsigned maxval) /* speaker on ? */ if ((parport_8255[1] & 3) == 3) { - TRACE("Beep (freq: %d) !\n", 1193180 / maxval ); - Beep(1193180 / maxval, 20); + TRACE("Beep (freq: %d) !\n", 1193180 / val); + Beep(1193180 / val, 20); } break; } } +static WORD get_timer_val(unsigned timer) +{ + LARGE_INTEGER time; + WORD maxval, val = tmr_8253[timer].countmax; + BYTE mode = tmr_8253[timer].ctrlbyte_ch >> 1 & 0x07; + + /* This is not strictly correct. In most cases the old countdown should + * finish normally (by counting down to 0) or halt and not jump to 0. + * But we are calculating and not countig, so this seems to be a good + * solution and should work well with most (all?) programs + */ + if (tmr_8253[timer].flags & TMR_UPDATE) + return 0; + + if (!QueryPerformanceCounter(&time)) + WARN("QueryPerformanceCounter should not fail!\n"); + + time.QuadPart -= tmr_8253[timer].start_time; + if (tmr_8253[timer].ctrlbyte_ch & 0x01) + val = BCD2BIN(val); + + switch ( mode ) + { + case 0: + case 1: + case 4: + case 5: + maxval = tmr_8253[timer].ctrlbyte_ch & 0x01 ? 9999 : 0xFFFF; + break; + case 2: + case 3: + maxval = val; + break; + default: + ERR("Invalid PIT mode: %d\n", mode); + return 0; + } + + val = (val - time.QuadPart) % (maxval + 1); + if (tmr_8253[timer].ctrlbyte_ch & 0x01) + val = BIN2BCD(val); + + return val; +} + + /********************************************************************** * IO_port_init */ @@ -401,24 +470,22 @@ DWORD WINAPI DOSVM_inport( int port, int size ) case 0x42: { BYTE chan = port & 3; - WORD tempval = 0; - if (tmr_8253[chan].latched) - tempval = tmr_8253[chan].latch; - else - { - dummy_ctr -= 1 + (int)(10.0 * rand() / (RAND_MAX + 1.0)); - if (chan == 0) /* System timer counter divisor */ - { - /* FIXME: DOSVM_GetTimer() returns quite rigid values */ - tempval = dummy_ctr + (WORD)DOSVM_GetTimer(); - } - else - { - /* FIXME: intelligent hardware timer emulation needed */ - tempval = dummy_ctr; - } - } + WORD tempval = tmr_8253[chan].flags & TMR_LATCHED + ? tmr_8253[chan].latch : get_timer_val(chan); + if (tmr_8253[chan].flags & TMR_STATUS) + { + WARN("Read-back status\n"); + /* We differ slightly from the spec: + * - TMR_UPDATE is already set with the first write + * of a two byte counter update + * - 0x80 should be set if OUT signal is 1 (high) + */ + tmr_8253[chan].flags &= ~TMR_STATUS; + res = (tmr_8253[chan].ctrlbyte_ch & 0x3F) | + (tmr_8253[chan].flags & TMR_UPDATE ? 0x40 : 0x00); + break; + } switch ((tmr_8253[chan].ctrlbyte_ch & 0x30) >> 4) { case 0: @@ -426,11 +493,11 @@ DWORD WINAPI DOSVM_inport( int port, int size ) break; case 1: /* read lo byte */ res = (BYTE)tempval; - tmr_8253[chan].latched = FALSE; + tmr_8253[chan].flags &= ~TMR_LATCHED; break; case 3: /* read lo byte, then hi byte */ - tmr_8253[chan].byte_toggle ^= 1; /* toggle */ - if (tmr_8253[chan].byte_toggle) + tmr_8253[chan].flags ^= TMR_RTOGGLE; /* toggle */ + if (tmr_8253[chan].flags & TMR_RTOGGLE) { res = (BYTE)tempval; break; @@ -438,7 +505,7 @@ DWORD WINAPI DOSVM_inport( int port, int size ) /* else [fall through if read hi byte !] */ case 2: /* read hi byte */ res = (BYTE)(tempval >> 8); - tmr_8253[chan].latched = FALSE; + tmr_8253[chan].flags &= ~TMR_LATCHED; break; } } @@ -601,10 +668,7 @@ void WINAPI DOSVM_outport( int port, int size, DWORD value ) { BYTE chan = port & 3; - /* we need to get the oldval before any lo/hi byte change has been made */ - if (((tmr_8253[chan].ctrlbyte_ch & 0x30) != 0x30) || - !tmr_8253[chan].byte_toggle) - tmr_8253[chan].oldval = tmr_8253[chan].countmax; + tmr_8253[chan].flags |= TMR_UPDATE; switch ((tmr_8253[chan].ctrlbyte_ch & 0x30) >> 4) { case 0: @@ -614,8 +678,8 @@ void WINAPI DOSVM_outport( int port, int size, DWORD value ) (tmr_8253[chan].countmax & 0xff00) | (BYTE)value; break; case 3: /* write lo byte, then hi byte */ - tmr_8253[chan].byte_toggle ^= TRUE; /* toggle */ - if (tmr_8253[chan].byte_toggle) + tmr_8253[chan].flags ^= TMR_WTOGGLE; /* toggle */ + if (tmr_8253[chan].flags & TMR_WTOGGLE) { tmr_8253[chan].countmax = (tmr_8253[chan].countmax & 0xff00) | (BYTE)value; @@ -627,12 +691,10 @@ void WINAPI DOSVM_outport( int port, int size, DWORD value ) (tmr_8253[chan].countmax & 0x00ff) | ((BYTE)value << 8); break; } - /* if programming is finished and value has changed - then update to new value */ - if ((((tmr_8253[chan].ctrlbyte_ch & 0x30) != 0x30) || - !tmr_8253[chan].byte_toggle) && - (tmr_8253[chan].countmax != tmr_8253[chan].oldval)) - set_timer_maxval(chan, tmr_8253[chan].countmax); + /* if programming is finished, update to new value */ + if ((tmr_8253[chan].ctrlbyte_ch & 0x30) && + !(tmr_8253[chan].flags & TMR_WTOGGLE)) + set_timer(chan); } break; case 0x43: @@ -641,28 +703,51 @@ void WINAPI DOSVM_outport( int port, int size, DWORD value ) /* ctrl byte for specific timer channel */ if (chan == 3) { - FIXME("8254 timer readback not implemented yet\n"); + if ( !(value & 0x20) ) + { + if (value & 0x02 && !(tmr_8253[0].flags & TMR_LATCHED)) + { + tmr_8253[0].flags |= TMR_LATCHED; + tmr_8253[0].latch = get_timer_val(chan); + } + if (value & 0x04 && !(tmr_8253[1].flags & TMR_LATCHED)) + { + tmr_8253[1].flags |= TMR_LATCHED; + tmr_8253[1].latch = get_timer_val(chan); + } + if (value & 0x08 && !(tmr_8253[2].flags & TMR_LATCHED)) + { + tmr_8253[2].flags |= TMR_LATCHED; + tmr_8253[2].latch = get_timer_val(chan); + } + } + + if ( !(value & 0x10) ) + { + if (value & 0x02) + tmr_8253[0].flags |= TMR_STATUS; + if (value & 0x04) + tmr_8253[1].flags |= TMR_STATUS; + if (value & 0x08) + tmr_8253[2].flags |= TMR_STATUS; + } break; } switch (((BYTE)value & 0x30) >> 4) { case 0: /* latch timer */ - tmr_8253[chan].latched = TRUE; - dummy_ctr -= 1 + (int)(10.0 * rand() / (RAND_MAX + 1.0)); - if (chan == 0) /* System timer divisor */ - tmr_8253[chan].latch = dummy_ctr + (WORD)DOSVM_GetTimer(); - else + if ( !(tmr_8253[chan].flags & TMR_LATCHED) ) { - /* FIXME: intelligent hardware timer emulation needed */ - tmr_8253[chan].latch = dummy_ctr; + tmr_8253[chan].flags |= TMR_LATCHED; + tmr_8253[chan].latch = get_timer_val(chan); } break; - case 3: /* write lo byte, then hi byte */ - tmr_8253[chan].byte_toggle = FALSE; /* init */ - /* fall through */ case 1: /* write lo byte only */ case 2: /* write hi byte only */ + case 3: /* write lo byte, then hi byte */ tmr_8253[chan].ctrlbyte_ch = (BYTE)value; + tmr_8253[chan].countmax = 0; + tmr_8253[chan].flags = TMR_UPDATE; break; } }