Emulator: parity-safe EMU8000 sample counter -- AWEUTIL runs; wire the

VGL_LABS cockpit test suite as pod-launch 'test' mode

The WC hang, root-caused in three layers (each necessary):
1. WC was rendered-samples + 100ms-capped interpolation; AWEUTIL's poll
   storm (~550k port-ops/s) starved the render thread of awe_lock, so the
   clock crawled ~90x slow. Fixed: free-running host-clock derivation +
   a fairness gate so the render thread can always take the lock.
2. Free-running at true 44.1kHz still failed: trapped port reads cost
   ~30us -- MORE than one 22.7us tick -- so consecutive reads skipped
   counter values, and AWEUTIL's WaitUntilWC (decoded at COM offset
   0x5F42) exits only on EQUALITY with a target tick: skipped value =
   missed target = 1.49s wrap penalty, or forever.
3. Advancing +1 per read still failed: WaitUntilWC reads WC TWICE per
   iteration, so its equality sample saw only every 2nd value -- wrong
   parity = infinite loop. Final semantics: during a poll storm the
   counter advances once per FOUR reads (every value observable by all
   of AWEUTIL's loop shapes; its 8192-unchanged-reads dead-clock bailout
   never trips), and resyncs to true wall time after any 50ms idle gap.
   Result: the full stock TEST.BAT (DIAGNOSE + AWEUTIL /S on both cards)
   completes in seconds.

Also: pod-launch 'test' mode -> vwetest.conf (stock TEST.BAT -> TSTALL),
DOSBox window defaults to 900,600 in test mode (the DOS screen is the
suite's UI), VWE_AWE_LOG gains storm bursts with guest cs:ip + caller and
rare-read tracing (the instrumentation that cracked this).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-16 23:51:52 -05:00
co-authored by Claude Fable 5
parent 6dc017cb3d
commit 72da9b2f14
7 changed files with 184 additions and 33 deletions
+54
View File
@@ -0,0 +1,54 @@
# DEPLOY TEMPLATE -- rendered to vwetest.conf by deploy/configure.ps1 at install.
# Tokens (filled by postinstall): @@ROOT@@ = package dir (C:\games\<folder>),
# @@REALNIC@@ = this pod's NIC GUID fragment, @@MACADDR@@ = stable derived MAC.
# VGL_LABS factory cockpit test suite: the stock C:\VWETEST\TEST.BAT chain
# (DIAGNOSE + AWEUTIL /S on both AWE cards, SETSVGA, VGLTEST\TSTALL.EXE --
# RIO, plasma, audio, video/Division calibration, Munga-net). Launch:
# pod-launch test. AWEUTIL's WC-timed init needs the parity-safe EMU8000
# sample-counter readback (vweawe.cpp, 2026-07-16) -- on older dosbox-x.exe
# builds it hangs at AWEUTIL. Drops to a DOS prompt when the suite exits.
[sdl]
output=opengl
priority=highest,highest
[dosbox]
memsize=32
machine=svga_s3
[cpu]
core=dynamic
cputype=pentium
cycles=max
[ne2000]
ne2000=true
nicbase=340
# IRQ 10, NOT 3: COM2 (plasma) owns IRQ 3 in the suite, same as in-game.
nicirq=10
macaddr=@@MACADDR@@
backend=pcap
[ethernet, pcap]
realnic=@@REALNIC@@
[sblaster]
sbtype=sb16
sbbase=220
irq=5
dma=1
hdma=5
[mixer]
rate=44100
blocksize=1024
prebuffer=60
[serial]
serial1=directserial realport:COM1 rxpollus:100 rxburst:16
serial2=directserial realport:COM2
[autoexec]
mount c "@@ROOT@@\ALPHA_1"
mount d "@@ROOT@@\net-boot"
d:
echo === loading NE2000 packet-driver stack (for the Munga-net tests) ===
d:\lsl
d:\ne2000
d:\odipkt
c:
cd \vwetest
echo === VGL_LABS cockpit test suite (stock TEST.BAT) ===
call test.bat
echo === test suite exited -- DOS prompt ===
+2
View File
@@ -30,6 +30,8 @@ namespace VwePod
Note = "BattleTech, networked cockpit. Also serves the camera ship and LIVE mission-review -- the console sets the role per-IP via the egg hostType, so no separate mode is needed." }, Note = "BattleTech, networked cockpit. Also serves the camera ship and LIVE mission-review -- the console sets the role per-IP via the egg hostType, so no separate mode is needed." },
new Mode { Name = "rp", ConfBase = "net_rp", Supported = true, new Mode { Name = "rp", ConfBase = "net_rp", Supported = true,
Note = "Red Planet, networked cockpit (also camera / live mission-review via egg hostType)." }, Note = "Red Planet, networked cockpit (also camera / live mission-review via egg hostType)." },
new Mode { Name = "test", ConfBase = "vwetest", Supported = true,
Note = "VGL_LABS factory cockpit test suite (C:\\VWETEST TEST.BAT -> TSTALL.EXE): RIO, plasma, audio (both AWE cards), video/Division calibration, Munga-net diagnostics." },
// Distinct launches, mechanics not yet wired on the emulator: // Distinct launches, mechanics not yet wired on the emulator:
new Mode { Name = "review", ConfBase = null, Supported = false, new Mode { Name = "review", ConfBase = null, Supported = false,
+12 -6
View File
@@ -49,6 +49,7 @@ namespace VwePod
var o = new Options(); var o = new Options();
string modeName = null, confArg = null, rootArg = null, dosboxArg = null, string modeName = null, confArg = null, rootArg = null, dosboxArg = null,
bridgeScriptArg = null, rendererArg = null, aweArg = null; bridgeScriptArg = null, rendererArg = null, aweArg = null;
bool dosboxXySet = false;
for (int i = 0; i < args.Length; i++) for (int i = 0; i < args.Length; i++)
{ {
@@ -72,7 +73,7 @@ namespace VwePod
case "--layout": o.Layout = Next(a).Equals("explode", StringComparison.OrdinalIgnoreCase) ? LayoutMode.Explode : LayoutMode.Cockpit; break; case "--layout": o.Layout = Next(a).Equals("explode", StringComparison.OrdinalIgnoreCase) ? LayoutMode.Explode : LayoutMode.Cockpit; break;
case "--bridge-pos": o.BridgePos = Next(a); break; case "--bridge-pos": o.BridgePos = Next(a); break;
case "--bridge-size": ParseWH(Next(a), out o.BridgeW, out o.BridgeH); break; case "--bridge-size": ParseWH(Next(a), out o.BridgeW, out o.BridgeH); break;
case "--dosbox-xy": ParseWH(Next(a), out o.DosBoxX, out o.DosBoxY); break; case "--dosbox-xy": ParseWH(Next(a), out o.DosBoxX, out o.DosBoxY); dosboxXySet = true; break;
case "--no-bridge": o.NoBridge = true; break; case "--no-bridge": o.NoBridge = true; break;
case "--no-sound": o.NoSound = true; break; case "--no-sound": o.NoSound = true; break;
case "--mipmap": o.Mipmap = true; break; case "--mipmap": o.Mipmap = true; break;
@@ -92,6 +93,10 @@ namespace VwePod
// reason and exits without touching the launch env. // reason and exits without touching the launch env.
if (!o.Mode.Supported) return o; if (!o.Mode.Supported) return o;
// test mode: the DOS screen IS the suite's UI -- default it out from
// under the render window unless the caller placed it explicitly.
if (!dosboxXySet && o.Mode.Name == "test") { o.DosBoxX = 900; o.DosBoxY = 600; }
o.DosBox = dosboxArg ?? FirstExisting( o.DosBox = dosboxArg ?? FirstExisting(
Path.Combine(o.Root, "dosbox-x.exe"), Path.Combine(o.Root, "dosbox-x.exe"),
Path.Combine(o.Root, "src", "src", "dosbox-x.exe")) Path.Combine(o.Root, "src", "src", "dosbox-x.exe"))
@@ -155,11 +160,12 @@ namespace VwePod
Supervising entry-point: launches DOSBox-X + the render bridge into a job Supervising entry-point: launches DOSBox-X + the render bridge into a job
object and blocks; killing this process kills them too (no orphans). object and blocks; killing this process kills them too (no orphans).
mode bt | rp (default bt). bt/rp also serve the camera ship mode bt | rp | test (default bt). bt/rp also serve the
+ live mission-review roles -- the console picks the camera ship + live mission-review roles -- the console
role via the egg hostType, so no separate mode. Also picks the role via the egg hostType, so no separate
recognized (not yet wired): review, test-plasma, mode. test = the VGL_LABS factory cockpit test suite
reset-rio, audio-test. (C:\VWETEST -> TSTALL.EXE). Also recognized (not yet
wired): review, test-plasma, reset-rio, audio-test.
--mode <name> same as the positional mode --mode <name> same as the positional mode
--conf <path> explicit conf (overrides the mode's conf) --conf <path> explicit conf (overrides the mode's conf)
--root <dir> base dir for dosbox/conf/bridge (default: exe dir) --root <dir> base dir for dosbox/conf/bridge (default: exe dir)
+1
View File
@@ -51,6 +51,7 @@ layout is fixed at `postinstall` time, so a typical call is just `pod-launch bt`
|---|---|---| |---|---|---|
| `bt` | BattleTech, networked cockpit (`net_loop.conf`, looped) | wired | | `bt` | BattleTech, networked cockpit (`net_loop.conf`, looped) | wired |
| `rp` | Red Planet, networked cockpit (`net_rp.conf`, looped) | wired | | `rp` | Red Planet, networked cockpit (`net_rp.conf`, looped) | wired |
| `test` | VGL_LABS factory cockpit test suite (`vwetest.conf``C:\VWETEST``TSTALL.EXE`): RIO, plasma, audio, video/Division calibration, Munga-net. DOSBox window defaults to 900,600 (the DOS screen is the suite's UI) | wired |
| `review` | mission-review PLAYBACK of `last.spl` | recognized, exits 3 (DOS arg TBD) | | `review` | mission-review PLAYBACK of `last.spl` | recognized, exits 3 (DOS arg TBD) |
| `test-plasma` | plasma-display diagnostic | recognized, exits 3 | | `test-plasma` | plasma-display diagnostic | recognized, exits 3 |
| `reset-rio` | RIO reset diagnostic | recognized, exits 3 | | `reset-rio` | RIO reset diagnostic | recognized, exits 3 |
+5 -5
View File
@@ -712,11 +712,11 @@ emu8k_inw(uint16_t addr, void *priv)
it is used by programs to wait. Not critical, but should help reduce it is used by programs to wait. Not critical, but should help reduce
the amount of calls and wait time */ the amount of calls and wait time */
case 27: /*Sample Counter ( 44Khz clock) */ case 27: /*Sample Counter ( 44Khz clock) */
/* VWE: interpolate between mixer blocks so /* VWE: free-running host-clock counter, like the
* driver busy-waits on the clock resolve at * silicon. Software (AWEUTIL) busy-waits on this;
* sample granularity instead of block steps */ * tying it to render progress made those waits
return (uint16_t)(emu8k->wc + * crawl under lock contention. */
emu8k_shim_wc_extra()); return emu8k_shim_wc_read();
default: default:
break; break;
+4 -2
View File
@@ -37,8 +37,10 @@ void io_removehandler(uint16_t base, int size,
extern int wavetable_pos_global; extern int wavetable_pos_global;
FILE *emu8k_shim_rom_fopen(void); FILE *emu8k_shim_rom_fopen(void);
/* samples elapsed since the last mixer render (WC interpolation) */ /* the WC sample counter readback: free-running 44100Hz off the host clock,
unsigned emu8k_shim_wc_extra(void); * exactly like the silicon (guest software busy-waits on it; deriving it
* from render progress made those waits crawl under lock contention) */
uint16_t emu8k_shim_wc_read(void);
#define pclog(...) fprintf(stderr, __VA_ARGS__) #define pclog(...) fprintf(stderr, __VA_ARGS__)
#define pclog_ex(fmt, ap) vfprintf(stderr, fmt, ap) #define pclog_ex(fmt, ap) vfprintf(stderr, fmt, ap)
+106 -20
View File
@@ -46,6 +46,7 @@
#include "dosbox.h" #include "dosbox.h"
#include "inout.h" #include "inout.h"
#include "regs.h" /* guest CS:IP in the storm trace */
#ifndef WIN32_LEAN_AND_MEAN #ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
@@ -63,21 +64,71 @@ int wavetable_pos_global = 0;
* per-chunk renders, so contention stays in the microseconds. */ * per-chunk renders, so contention stays in the microseconds. */
static CRITICAL_SECTION awe_lock; static CRITICAL_SECTION awe_lock;
/* WC interpolation: real-time samples elapsed since the render thread's /* WC: the EMU8000 sample counter is a FREE-RUNNING 44100Hz 16-bit counter on
* last pass, so guest busy-waits on the 44kHz sample counter see smooth * silicon -- it ticks regardless of what the host, driver, or synth is doing,
* advancement between render chunks */ * and software (AWEUTIL /S in particular) busy-waits on it for every timed
static volatile LONGLONG awe_last_render_qpc = 0; * init step. Derive it from QPC alone. The old render-coupled version
* (rendered samples + interpolation capped at 100ms) crawled ~90x slow when
* a guest poll storm starved the render thread of the shared lock -- AWEUTIL
* "hang", root-caused 2026-07-16. See also awe_render_waiting below. */
static LONGLONG awe_qpc_freq = 1; static LONGLONG awe_qpc_freq = 1;
unsigned emu8k_shim_wc_extra(void) { static LONGLONG awe_wc_origin_qpc = 0;
uint16_t emu8k_shim_wc_read(void) {
/* Free-running 44100Hz -- but with one more piece of silicon truth: on
* real ISA a poll loop reads the port many times per tick, so software
* NEVER observes the counter skipping a value, and AWEUTIL's timed waits
* exit on EQUALITY with a target tick. Our trapped port reads cost ~30us
* (heavier than one 22.7us tick), so a raw time-derived counter jumps
* +2..+4 per poll and equality targets get skipped -- each miss costs a
* full 1.49s wrap (the "AWEUTIL hang", 2026-07-16). So: while the guest
* is polling TIGHTLY (<200us between reads), advance at most one tick
* per read -- every value is observed, waits terminate, and the counter
* runs at worst ~25% slow during the poll itself. Any idle gap snaps it
* back to true wall-clock time. Single caller thread (emulation). */
LARGE_INTEGER now; LARGE_INTEGER now;
QueryPerformanceCounter(&now); QueryPerformanceCounter(&now);
LONGLONG d = now.QuadPart - awe_last_render_qpc; static uint16_t last_ret = 0;
if (d < 0) d = 0; static LONGLONG last_qpc = 0;
LONGLONG smp = (d * 44100) / awe_qpc_freq; static unsigned reads_since_adv = 0;
if (smp > 4410) smp = 4410; LONGLONG gap_us = ((now.QuadPart - last_qpc) * 1000000) / awe_qpc_freq;
return (unsigned)smp; uint16_t ret;
if (gap_us < 50000) {
/* Poll storm. The hard constraint (AWEUTIL WaitUntilWC @5F42,
* decoded 2026-07-16): its wait exits on EQUALITY with a target
* tick, and each loop iteration reads WC TWICE -- so the counter
* must advance SLOWER than the guest reads, or the equality sample
* only sees every 2nd value and misses targets of the wrong parity
* forever. Real silicon polls at ~1us vs the 22.7us tick, so every
* value is observed many times; our trapped reads cost ~30us, more
* than a real tick, so we peg the storm-time counter to the READ
* rate instead: +1 per 4 reads. Every value is then observable by
* loops that re-check at least once per 4 reads (all of AWEUTIL's
* do), waits stretch ~5x (seconds, not minutes), and the dead-clock
* bailouts (8192 unchanged reads) never trip. Never consult real
* time mid-storm. */
if (++reads_since_adv >= 4) {
last_ret = (uint16_t)(last_ret + 1);
reads_since_adv = 0;
}
ret = last_ret;
} else {
/* genuinely idle since the last read: resync to true wall time */
ret = (uint16_t)(((unsigned long long)
(now.QuadPart - awe_wc_origin_qpc)
* 44100ull) / (unsigned long long)awe_qpc_freq);
last_ret = ret;
reads_since_adv = 0;
}
last_qpc = now.QuadPart;
return ret;
} }
/* Fairness gate: guest port I/O at storm rates (AWEUTIL polls WC ~550k/s)
* re-acquires awe_lock so fast that the render thread starves for seconds.
* The render thread raises this flag while it waits for the lock; the guest
* side backs off until it's through. */
static volatile LONG awe_render_waiting = 0;
/* activity stats (reported every ~10s of rendered audio per card) */ /* activity stats (reported every ~10s of rendered audio per card) */
struct AweStats { unsigned long wr, rd_wc, rd, smld_w; }; struct AweStats { unsigned long wr, rd_wc, rd, smld_w; };
static AweStats awe_stats[2]; /* 0 = front, 1 = rear */ static AweStats awe_stats[2]; /* 0 = front, 1 = rear */
@@ -100,21 +151,35 @@ static AweIoRange *awe_io_find(Bitu port) {
return NULL; return NULL;
} }
/* VWE_AWE_LOG=1: trace the first accesses + growth-of-count milestones */ /* VWE_AWE_LOG=1: trace the first accesses + growth-of-count milestones.
* VWE_AWE_LOG=2: additionally dump a 64-op CONSECUTIVE burst (with us
* timestamps) every 4M ops -- shows the guest's actual poll-loop structure
* instead of isolated samples. */
static int awe_log_on = -1; static int awe_log_on = -1;
static unsigned long awe_access_count = 0; static unsigned long awe_access_count = 0;
static void awe_io_trace(const char *dir, Bitu port, Bitu val, Bitu iolen) { static void awe_io_trace(const char *dir, Bitu port, Bitu val, Bitu iolen) {
if (awe_log_on < 0) { if (awe_log_on < 0) {
const char *l = getenv("VWE_AWE_LOG"); const char *l = getenv("VWE_AWE_LOG");
awe_log_on = (l && l[0] && l[0] != '0') ? 1 : 0; awe_log_on = (l && l[0] && l[0] != '0') ? (l[0] == '2' ? 2 : 1) : 0;
} }
if (!awe_log_on) return; if (!awe_log_on) return;
awe_access_count++; awe_access_count++;
if (awe_access_count <= 64 || bool burst = awe_log_on >= 2 && (awe_access_count & 0x3FFFFF) < 64;
(awe_access_count & (awe_access_count - 1)) == 0) if (awe_access_count <= 64 || burst ||
fprintf(stderr, "VWE AWE32 io[%lu]: %s %03lx val=%04lx len=%lu\n", (awe_access_count & (awe_access_count - 1)) == 0) {
awe_access_count, dir, (unsigned long)port, LARGE_INTEGER t;
QueryPerformanceCounter(&t);
/* caller of the port-access primitive: real-mode [ss:bp+2] */
unsigned ret_ip = mem_readw(((PhysPt)SegValue(ss) << 4) +
((reg_bp + 2) & 0xFFFF));
fprintf(stderr, "VWE AWE32 io[%lu @%lluus cs:ip=%04x:%04x ret=%04x]: %s %03lx val=%04lx len=%lu\n",
awe_access_count,
(unsigned long long)((t.QuadPart - awe_wc_origin_qpc) * 1000000
/ awe_qpc_freq),
(unsigned)SegValue(cs), (unsigned)reg_ip, ret_ip,
dir, (unsigned long)port,
(unsigned long)val, (unsigned long)iolen); (unsigned long)val, (unsigned long)iolen);
}
} }
/* PTR shadow per card: mirror of the core's cur_reg/cur_voice, so the glue /* PTR shadow per card: mirror of the core's cur_reg/cur_voice, so the glue
@@ -141,6 +206,7 @@ static Bitu awe_io_read(Bitu port, Bitu iolen) {
AweIoRange *r = awe_io_find(port); AweIoRange *r = awe_io_find(port);
if (!r) return ~0ul; if (!r) return ~0ul;
Bitu v; Bitu v;
while (awe_render_waiting) Sleep(1); /* let the render thread through */
EnterCriticalSection(&awe_lock); EnterCriticalSection(&awe_lock);
if (iolen >= 2 && r->inw) v = r->inw((uint16_t)port, r->priv); if (iolen >= 2 && r->inw) v = r->inw((uint16_t)port, r->priv);
else if (r->inb) v = r->inb((uint16_t)port, r->priv); else if (r->inb) v = r->inb((uint16_t)port, r->priv);
@@ -148,6 +214,22 @@ static Bitu awe_io_read(Bitu port, Bitu iolen) {
LeaveCriticalSection(&awe_lock); LeaveCriticalSection(&awe_lock);
AweStats &s = awe_stats[(port & 0x40) ? 1 : 0]; AweStats &s = awe_stats[(port & 0x40) ? 1 : 0];
if ((port & 0xF9F) == 0xA02) s.rd_wc++; else s.rd++; /* A22/A42 */ if ((port & 0xF9F) == 0xA02) s.rd_wc++; else s.rd++; /* A22/A42 */
/* the RARE reads are the interesting ones during a WC-delay storm: log
* every read that isn't a WC poll, with the PTR shadow, to expose the
* never-satisfied outer wait condition (VWE_AWE_LOG>=1, first 200) */
{
const int ci2 = (port & 0x40) ? 1 : 0;
if (awe_log_on > 0 && !((port & 0xF9F) == 0xA02 &&
(awe_ptr[ci2] & 0xFF) == 0x3B)) {
static int rare_n = 0;
if (rare_n < 200) { rare_n++;
fprintf(stderr,
"VWE AWE32 rareR[%d]: port=%03lx ptr=%04x val=%04lx len=%lu\n",
rare_n, (unsigned long)port, awe_ptr[ci2],
(unsigned long)v, (unsigned long)iolen);
}
}
}
if ((port & 0xF1C) == 0xA00) /* A20-A23/A40-A43 */ if ((port & 0xF1C) == 0xA00) /* A20-A23/A40-A43 */
awe_sm_trace("R", (port & 0x40) ? 1 : 0, port, v); awe_sm_trace("R", (port & 0x40) ? 1 : 0, port, v);
awe_io_trace("R", port, v, iolen); awe_io_trace("R", port, v, iolen);
@@ -167,6 +249,7 @@ static void awe_io_write(Bitu port, Bitu val, Bitu iolen) {
if ((port & 0xF1C) == 0xA00) if ((port & 0xF1C) == 0xA00)
awe_sm_trace("W", ci, port, val); awe_sm_trace("W", ci, port, val);
awe_io_trace("W", port, val, iolen); awe_io_trace("W", port, val, iolen);
while (awe_render_waiting) Sleep(1); /* let the render thread through */
EnterCriticalSection(&awe_lock); EnterCriticalSection(&awe_lock);
if (iolen >= 2 && r->outw) r->outw((uint16_t)port, (uint16_t)val, r->priv); if (iolen >= 2 && r->outw) r->outw((uint16_t)port, (uint16_t)val, r->priv);
else if (r->outb) r->outb((uint16_t)port, (uint8_t)val, r->priv); else if (r->outb) r->outb((uint16_t)port, (uint8_t)val, r->priv);
@@ -323,13 +406,14 @@ static DWORD WINAPI awe_thread_proc(LPVOID) {
Sleep(2); Sleep(2);
continue; continue;
} }
/* render one 10ms chunk from both cards, sum into the slot */ /* render one 10ms chunk from both cards, sum into the slot. The
LARGE_INTEGER now; * waiting flag holds off guest port-I/O storms (see above) so this
* acquire can't be starved. */
awe_render_waiting = 1;
EnterCriticalSection(&awe_lock); EnterCriticalSection(&awe_lock);
awe_render_waiting = 0;
awe_render_chunk(&awe_front, 0, AWE_SLOT_FRAMES); awe_render_chunk(&awe_front, 0, AWE_SLOT_FRAMES);
awe_render_chunk(&awe_rear, 1, AWE_SLOT_FRAMES); awe_render_chunk(&awe_rear, 1, AWE_SLOT_FRAMES);
QueryPerformanceCounter(&now);
awe_last_render_qpc = now.QuadPart;
LeaveCriticalSection(&awe_lock); LeaveCriticalSection(&awe_lock);
int16_t *out = awe_slotbuf[free_slot]; int16_t *out = awe_slotbuf[free_slot];
for (unsigned i = 0; i < AWE_SLOT_FRAMES; i++) { for (unsigned i = 0; i < AWE_SLOT_FRAMES; i++) {
@@ -496,6 +580,8 @@ void VWEAWE_Init(void) {
LARGE_INTEGER f; LARGE_INTEGER f;
QueryPerformanceFrequency(&f); QueryPerformanceFrequency(&f);
awe_qpc_freq = f.QuadPart ? f.QuadPart : 1; awe_qpc_freq = f.QuadPart ? f.QuadPart : 1;
QueryPerformanceCounter(&f);
awe_wc_origin_qpc = f.QuadPart; /* WC free-runs from power-on */
emu8k_init(&awe_front, 0x620, ram_kb); emu8k_init(&awe_front, 0x620, ram_kb);
emu8k_init(&awe_rear, 0x640, ram_kb); emu8k_init(&awe_rear, 0x640, ram_kb);