|
| 1 | +/* test.c |
| 2 | + * |
| 3 | + * 8254 programmable interrupt timer test program. |
| 4 | + * (C) 2008-2012 Jonathan Campbell. |
| 5 | + * Hackipedia DOS library. |
| 6 | + * |
| 7 | + * This code is licensed under the LGPL. |
| 8 | + * <insert LGPL legal text here> |
| 9 | + * |
| 10 | + * Compiles for intended target environments: |
| 11 | + * - MS-DOS [pure DOS mode, or Windows or OS/2 DOS Box] */ |
| 12 | + |
| 13 | +/* NTS: As of 2011/02/27 the 8254 routines no longer do cli/sti for us, we are expected |
| 14 | + * to do them ourself. This is for performance reasons as well as for sanity reasons |
| 15 | + * should we ever need to use the subroutines from within an interrupt handler */ |
| 16 | + |
| 17 | +#include <stdio.h> |
| 18 | +#include <conio.h> /* this is where Open Watcom hides the outp() etc. functions */ |
| 19 | +#include <stdlib.h> |
| 20 | +#include <unistd.h> |
| 21 | +#include <fcntl.h> |
| 22 | +#include <dos.h> |
| 23 | + |
| 24 | +#include <hw/8254/8254.h> |
| 25 | + |
| 26 | +#include <hw/dos/dos.h> |
| 27 | +#include <hw/dos/doswin.h> |
| 28 | +#include <hw/dos/dosbox.h> |
| 29 | +#include <hw/dos/dosntvdm.h> |
| 30 | + |
| 31 | +#ifdef TARGET_WINDOWS |
| 32 | +# define WINFCON_STOCK_WIN_MAIN |
| 33 | +# include <hw/dos/winfcon.h> |
| 34 | +#endif |
| 35 | + |
| 36 | +#ifdef TARGET_WINDOWS |
| 37 | +/* no interrupts */ |
| 38 | +#elif defined(__cplusplus) |
| 39 | +/* why are dos_getvect() and dos_setvect() not available in C++ mode? */ |
| 40 | +#else |
| 41 | +# define HOOK_IRQ |
| 42 | +#endif |
| 43 | + |
| 44 | +#ifdef HOOK_IRQ |
| 45 | +# include <hw/8259/8259.h> |
| 46 | +#endif |
| 47 | + |
| 48 | +static volatile unsigned int counter = 0; |
| 49 | +static unsigned int speaker_rate = 0; |
| 50 | +static unsigned int max = 0xFFFF; |
| 51 | + |
| 52 | +#ifdef HOOK_IRQ |
| 53 | +void (__interrupt __far *prev_irq0)() = NULL; |
| 54 | +static void __interrupt __far irq0() { |
| 55 | + counter++; |
| 56 | + |
| 57 | +#if T8254_IRQ >= 8 |
| 58 | + p8259_OCW2(8,P8259_OCW2_NON_SPECIFIC_EOI); |
| 59 | +#endif |
| 60 | + p8259_OCW2(0,P8259_OCW2_NON_SPECIFIC_EOI); |
| 61 | +} |
| 62 | +#endif |
| 63 | + |
| 64 | +#if TARGET_MSDOS == 32 |
| 65 | +/* 32-bit DOS flat mode doesn't impose restrictions */ |
| 66 | +unsigned char tmp[238617]; |
| 67 | +#else |
| 68 | +/* we're probably limited by 16-bit real mode and near pointers */ |
| 69 | +unsigned char tmp[32768]; |
| 70 | +#endif |
| 71 | + |
| 72 | +void pulse_width_test() { |
| 73 | + int fd; |
| 74 | + unsigned char cc; |
| 75 | + unsigned int play; |
| 76 | + unsigned char plan_b=0; |
| 77 | + unsigned int patience = 10000; |
| 78 | + |
| 79 | + _cli(); |
| 80 | + write_8254_system_timer(0xFFFF); /* BUGFIX: Personal experience tells me BIOSes would fail reading the floppy if the IRQ 0 timer isn't ticking along at 18Hz */ |
| 81 | + _sti(); |
| 82 | + |
| 83 | + fd = open("..\\test1_22.wav",O_RDONLY|O_BINARY); |
| 84 | + if (fd < 0) fd = open("test1_22.wav",O_RDONLY|O_BINARY); |
| 85 | + if (fd < 0) { |
| 86 | + printf("Cannot open test WAV\n"); |
| 87 | + return; |
| 88 | + } |
| 89 | + lseek(fd,44,SEEK_SET); |
| 90 | + read(fd,tmp,sizeof(tmp)); |
| 91 | + for (play=0;play < sizeof(tmp);play++) tmp[play] = (((tmp[play]) * 53) / 255) + 1; /* add "noise" to dither */ |
| 92 | + close(fd); |
| 93 | + |
| 94 | + /* set timer 0 to 54 ticks (1.191MHz / 54 ~ 22050Hz) */ |
| 95 | + _cli(); |
| 96 | + t8254_pc_speaker_set_gate(0); |
| 97 | + write_8254_pc_speaker(1); |
| 98 | + write_8254_system_timer(54); |
| 99 | + _sti(); |
| 100 | + |
| 101 | + _cli(); |
| 102 | + { |
| 103 | + outp(T8254_CONTROL_PORT,(0 << 6) | (0 << 4) | 0); /* latch counter N, counter latch read */ |
| 104 | + do { |
| 105 | + if (--patience == 1) break; |
| 106 | + cc = inp(T8254_TIMER_PORT(0)); |
| 107 | + inp(T8254_TIMER_PORT(0)); |
| 108 | + } while (cc < (54/2)); |
| 109 | + do { |
| 110 | + if (--patience == 0) break; |
| 111 | + cc = inp(T8254_TIMER_PORT(0)); |
| 112 | + inp(T8254_TIMER_PORT(0)); |
| 113 | + } while (cc >= (54/2)); |
| 114 | + if (patience <= 2) { |
| 115 | + write_8254_system_timer(0xFFFF); /* BUGFIX: on very old slow machines, the 54-count tick can easily cause the printf() below to absolutely CRAWL... */ |
| 116 | + _sti(); |
| 117 | + printf("Oops! Either your CPU is too fast or the timer countdown trick doesn't work.\n"); |
| 118 | + _cli(); |
| 119 | + write_8254_system_timer(54); |
| 120 | + plan_b = 1; |
| 121 | + } |
| 122 | + } |
| 123 | + _sti(); |
| 124 | + |
| 125 | + while (1) { |
| 126 | + if (kbhit()) { |
| 127 | + int c = getch(); |
| 128 | + if (c == 27) break; |
| 129 | + } |
| 130 | + |
| 131 | + if (plan_b) { |
| 132 | + /* run with interrupts enabled, use IRQ0 to know when to tick */ |
| 133 | + _cli(); |
| 134 | + counter = 0; |
| 135 | + t8254_pc_speaker_set_gate(3); |
| 136 | + for (play=0;play < sizeof(tmp);) { |
| 137 | + outp(T8254_CONTROL_PORT,(2 << 6) | (1 << 4) | (T8254_MODE_0_INT_ON_TERMINAL_COUNT << 1)); /* MODE 0, low byte only, counter 2 */ |
| 138 | + outp(T8254_TIMER_PORT(2),tmp[play]); |
| 139 | + _sti(); |
| 140 | + while (counter == 0); |
| 141 | + _cli(); |
| 142 | + play += counter; |
| 143 | + counter = 0; |
| 144 | + } |
| 145 | + _sti(); |
| 146 | + } |
| 147 | + else { |
| 148 | + _cli(); |
| 149 | + t8254_pc_speaker_set_gate(3); |
| 150 | + outp(T8254_CONTROL_PORT,(0 << 6) | (0 << 4) | 0); /* latch counter N, counter latch read */ |
| 151 | + for (play=0;play < sizeof(tmp);play++) { |
| 152 | + outp(T8254_CONTROL_PORT,(2 << 6) | (1 << 4) | (T8254_MODE_0_INT_ON_TERMINAL_COUNT << 1)); /* MODE 0, low byte only, counter 2 */ |
| 153 | + outp(T8254_TIMER_PORT(2),tmp[play]); |
| 154 | + |
| 155 | + do { |
| 156 | + cc = inp(T8254_TIMER_PORT(0)); |
| 157 | + inp(T8254_TIMER_PORT(0)); |
| 158 | + } while (cc < (54/2)); |
| 159 | + |
| 160 | + do { |
| 161 | + cc = inp(T8254_TIMER_PORT(0)); |
| 162 | + inp(T8254_TIMER_PORT(0)); |
| 163 | + } while (cc >= (54/2)); |
| 164 | + } |
| 165 | + _sti(); |
| 166 | + } |
| 167 | + } |
| 168 | + |
| 169 | + t8254_pc_speaker_set_gate(0); |
| 170 | +} |
| 171 | + |
| 172 | +int main() { |
| 173 | + struct t8254_readback_t readback; |
| 174 | + t8254_time_t tick[3]; |
| 175 | + unsigned int i; |
| 176 | + |
| 177 | +#if defined(TARGET_WINDOWS) && TARGET_WINDOWS == 32 && !defined(WIN386) |
| 178 | + /* As a 32-bit Windows application, we *can* talk directly to the 8254 but only if running under Windows 3.1/95/98/ME. |
| 179 | + * Windows NT would never permit us to directly talk to IO. |
| 180 | + * |
| 181 | + * However if we're a Win16 program or Watcom 386 application, Windows NT will trap and emulate the 8254 through NTVDM.EXE */ |
| 182 | + detect_windows(); |
| 183 | + if (!(windows_mode == WINDOWS_REAL || windows_mode == WINDOWS_STANDARD || windows_mode == WINDOWS_ENHANCED)) { |
| 184 | + printf("This test program is only applicable to Windows 3.x/9x\n"); |
| 185 | + return 1; |
| 186 | + } |
| 187 | +#endif |
| 188 | + |
| 189 | + printf("8254 library test program\n"); |
| 190 | + if (!probe_8254()) { |
| 191 | + printf("Chip not present. Your computer might be 2010-era hardware that dropped support for it.\n"); |
| 192 | + return 1; |
| 193 | + } |
| 194 | +#ifdef HOOK_IRQ |
| 195 | + if (!probe_8259()) { |
| 196 | + printf("8259 interrupt controller not present. Your computer might be 2010-era hardware that dropped support for it.\n"); |
| 197 | + return 1; |
| 198 | + } |
| 199 | +#endif |
| 200 | + |
| 201 | + printf("8254 base clock: %luHz\n",T8254_REF_CLOCK_HZ); |
| 202 | + |
| 203 | + speaker_rate = T8254_REF_CLOCK_HZ / 400UL; /* 400Hz */ |
| 204 | + |
| 205 | +#ifdef HOOK_IRQ |
| 206 | + prev_irq0 = _dos_getvect(T8254_IRQ+0x08); |
| 207 | + _dos_setvect(T8254_IRQ+0x8,irq0); |
| 208 | +#endif |
| 209 | + |
| 210 | + _cli(); |
| 211 | + write_8254_pc_speaker(speaker_rate); |
| 212 | + write_8254_system_timer(max); |
| 213 | + _sti(); |
| 214 | + |
| 215 | +#ifdef HOOK_IRQ |
| 216 | +#ifdef TARGET_PC98 |
| 217 | + /* PC-98 does not have IRQ0 running by default */ |
| 218 | + p8259_unmask(T8254_IRQ); |
| 219 | +#endif |
| 220 | +#endif |
| 221 | + |
| 222 | + while (1) { |
| 223 | + if (kbhit()) { |
| 224 | + int c = getch(); |
| 225 | + if (c == 27) |
| 226 | + break; |
| 227 | + else if (c == '-') { |
| 228 | + max -= 80; |
| 229 | + if (max > (0xFFFF-80)) |
| 230 | + max = 0xFFFF; |
| 231 | + |
| 232 | + _cli(); |
| 233 | + write_8254_system_timer(max); |
| 234 | + _sti(); |
| 235 | + } |
| 236 | + else if (c == '=') { |
| 237 | + max += 110; |
| 238 | + if (max < 110 || max > (0xFFFF-110)) |
| 239 | + max = 0xFFFF; |
| 240 | + |
| 241 | + _cli(); |
| 242 | + write_8254_system_timer(max); |
| 243 | + _sti(); |
| 244 | + } |
| 245 | + /* play with timer 2 and the PC speaker gate */ |
| 246 | + else if (c == 'p') { |
| 247 | + unsigned char on = (t8254_pc_speaker_read_gate() != 0) ? 1 : 0; |
| 248 | + if (on) t8254_pc_speaker_set_gate(0); |
| 249 | + else t8254_pc_speaker_set_gate(3); |
| 250 | + } |
| 251 | + else if (c == '1') { |
| 252 | +#ifndef TARGET_PC98 |
| 253 | + unsigned char v = t8254_pc_speaker_read_gate(); |
| 254 | + t8254_pc_speaker_set_gate(v ^ PC_SPEAKER_COUNTER_2_GATE); |
| 255 | +#endif |
| 256 | + } |
| 257 | + else if (c == '2') { |
| 258 | +#ifndef TARGET_PC98 |
| 259 | + unsigned char v = t8254_pc_speaker_read_gate(); |
| 260 | + t8254_pc_speaker_set_gate(v ^ PC_SPEAKER_OUTPUT_TTL_AND_GATE); |
| 261 | +#endif |
| 262 | + } |
| 263 | + else if (c == '[') { |
| 264 | + speaker_rate += 110; |
| 265 | + if (speaker_rate > (0xFFFF-110) || speaker_rate < 110) |
| 266 | + speaker_rate = 0xFFFF; |
| 267 | + |
| 268 | + write_8254_pc_speaker(speaker_rate); |
| 269 | + } |
| 270 | + else if (c == ']') { |
| 271 | + speaker_rate -= 110; |
| 272 | + if (speaker_rate > (0xFFFF-110)) |
| 273 | + speaker_rate = 0; |
| 274 | + |
| 275 | + write_8254_pc_speaker(speaker_rate); |
| 276 | + } |
| 277 | + else if (c == 'w') { |
| 278 | + printf("\n"); |
| 279 | + pulse_width_test(); |
| 280 | + _cli(); |
| 281 | + write_8254_system_timer(max); |
| 282 | + _sti(); |
| 283 | + printf("\n"); |
| 284 | + } |
| 285 | + else if (c == 'z') { |
| 286 | + /* sleep-wait loop test */ |
| 287 | + unsigned long delay_ticks; |
| 288 | + unsigned long z; |
| 289 | + unsigned int c,cmax; |
| 290 | + |
| 291 | + printf("\nDelay interval in us? "); |
| 292 | + z = 1000000; scanf("%lu",&z); |
| 293 | + |
| 294 | + delay_ticks = t8254_us2ticks(z); |
| 295 | + printf(" %lu = %lu ticks\n",z,delay_ticks); |
| 296 | + |
| 297 | + if (delay_ticks == 0UL) cmax = (unsigned int)(T8254_REF_CLOCK_HZ / 20UL); |
| 298 | + else cmax = (unsigned int)(T8254_REF_CLOCK_HZ / 20UL / (unsigned long)delay_ticks); |
| 299 | + if (cmax == 0) cmax = 1; |
| 300 | + |
| 301 | + write_8254_pc_speaker(T8254_REF_CLOCK_HZ / 400UL); /* tick as fast as possible */ |
| 302 | + while (1) { |
| 303 | + if (kbhit()) { |
| 304 | + if (getch() == 27) break; |
| 305 | + } |
| 306 | + |
| 307 | + for (c=0;c < cmax;c++) { |
| 308 | + t8254_pc_speaker_set_gate(3); |
| 309 | + t8254_wait(delay_ticks); |
| 310 | + t8254_pc_speaker_set_gate(0); |
| 311 | + t8254_wait(delay_ticks); |
| 312 | + } |
| 313 | + } |
| 314 | + } |
| 315 | + else if (c == 'd') { |
| 316 | + printf("\n \nDetail mode, hit 'd' again to exit: [WARNING: 8254 only]\n"); |
| 317 | + while (1) { |
| 318 | + if (kbhit()) { |
| 319 | + int c = getch(); |
| 320 | + if (c == 'd') { |
| 321 | + break; |
| 322 | + } |
| 323 | + } |
| 324 | + |
| 325 | + _cli(); |
| 326 | + readback_8254(T8254_READBACK_ALL,&readback); |
| 327 | + _sti(); |
| 328 | + printf("\x0D"); |
| 329 | + for (i=0;i <= 2;i++) { |
| 330 | + printf("[%u] stat=%02x count=%04x ",i, |
| 331 | + readback.timer[i].status, |
| 332 | + readback.timer[i].count); |
| 333 | + } |
| 334 | + fflush(stdout); |
| 335 | + } |
| 336 | + printf("\n"); |
| 337 | + } |
| 338 | + } |
| 339 | + |
| 340 | + for (i=0;i <= 2;i++) tick[i] = read_8254(i); |
| 341 | + |
| 342 | + /* BUG: DOSBox/DOSBox-X appear to have a bug where the PC speaker readback toggles |
| 343 | + * regardless of the GATE input to Counter 2. Bring GATE low (setting bit 1 |
| 344 | + * of port 61h to 0) is supposed to cause Counter 2 to stop. The AND gate |
| 345 | + * after the output (bit 0 of port 61h) is not supposed to affect the readback. */ |
| 346 | + printf("\x0D %04x %04x %04x max=%04x count=%04x SPKR=%u",tick[0],tick[1],tick[2], |
| 347 | + max,counter,read_8254_pc_speaker_output()!=0?1:0); |
| 348 | + fflush(stdout); |
| 349 | + } |
| 350 | + printf("\n"); |
| 351 | + |
| 352 | +#ifdef HOOK_IRQ |
| 353 | +#ifdef TARGET_PC98 |
| 354 | + /* PC-98 does not have IRQ0 running by default */ |
| 355 | + p8259_mask(T8254_IRQ); |
| 356 | +#endif |
| 357 | +#endif |
| 358 | + |
| 359 | + _cli(); |
| 360 | + write_8254_pc_speaker(0); |
| 361 | + t8254_pc_speaker_set_gate(0); |
| 362 | +#ifdef HOOK_IRQ |
| 363 | + _dos_setvect(T8254_IRQ+0x8,prev_irq0); |
| 364 | +#endif |
| 365 | + _sti(); |
| 366 | + |
| 367 | + write_8254_system_timer(0xFFFF); /* restore normal function to prevent BIOS from going crazy */ |
| 368 | + return 0; |
| 369 | +} |
| 370 | + |
0 commit comments