CIO-DAS08 ISA card with DFI mother board MBATX-CS620-H310C for MIFE

Posted by John Liu on Saturday, June 27, 2026

Legacy ISA Hardware Pass-Through via Custom QEMU/KVM Proxy

This documentation outlines the complete engineering pipeline required to bridge a legacy CIO-DAS08 ISA Data Acquisition Card into a virtualized Windows 98 SE / MS-DOS environment running on a modern Linux host machine with MBATX-CS620-H310C motherboard from DFI. It details the underlying hardware mapping architecture, the build process for a custom QEMU binary, deployment strategies, and validation protocols.


1. Hardware Architecture & Electrical Blueprint Map

Modern motherboards lack physical ISA slots. To bridge this gap, the target host machine utilizes a PCIe-to-ISA hardware bridge adapter (e.g., using an ITE or Winbond bridge controller).

The bridge maps the legacy 10-bit ISA I/O space into the host’s modern PCIe I/O Base Address Register (BAR) window.

+-----------------------------------------------------------------------+
|                       WINDOWS 98 GUEST VM                             |
|  Accesses legacy ISA Ports: 0x390 - 0x39F                            |
+------------------------------------+----------------------------------+
                                     | (Intercepted by QEMU Memory Region)
                                     v
+-----------------------------------------------------------------------+
|                       CUSTOM QEMU ISA PROXY                           |
|  - Tracks memory region offset: addr (0 to 15)                        |
|  - Dynamically calculates host port: 0x5390 + addr                    |
|  - Executes raw hardware assembly: inb / outb                         |
+------------------------------------+----------------------------------+
                                     | (Kernel space execution via iopl)
                                     v
+-----------------------------------------------------------------------+
|                       UBUNTU LINUX HOST KERNEL                        |
|  Directly routes outb/inb commands down the PCIe Bus BAR Window       |
+------------------------------------+----------------------------------+
                                     |
                                     v
+-----------------------------------------------------------------------+
|                    PHYSICAL PCIe-TO-ISA BRIDGE                        |
|  Translates Host Port 0x5390 directly into physical ISA Bus 0x390     |
+------------------------------------+----------------------------------+
                                     |
                                     v
+-----------------------------------------------------------------------+
|                    CIO-DAS08 PHYSICAL ISA CARD                         |
|  Responds across 8-port register layout block                        |
+-----------------------------------------------------------------------+

Physical-to-Virtual Address Mapping Layout

  • Guest Base Address: 0x390 (Configured via the card’s physical DIP switches).
  • Host Translation Base: 0x5390 (Assigned by the host PCIe bus architecture).
  • Address Space Window: 16 Bytes (0x390 to 0x39F).

2. Environment Setup & Dependency Workarounds

Building older or custom versions of QEMU on clean, modern Ubuntu machines requires resolving deprecated dependency packages and handling obsolete git registry submodules.

Step 2.1: Install Core Build Toolchain & Dependencies

Run the following commands to provision the build environment:

sudo apt update
sudo apt install -y git build-essential python3-ninja ninja-build \
    libglib2.0-dev libpixman-1-dev libpciaccess-dev libusb-1.0-0-dev \
    libsdl2-dev libgtk-3-dev libspice-server-dev libcap-ng-dev \
    libattr1-dev libnuma-dev bison flex

Step 2.2: Mitigate “Old Git Registry” / Submodule Errors

Older checkouts of QEMU source code often point to dead or migrated Git URLs for mandatory third-party submodules (like dtc, slirp, or keycodemapdb).

If git submodule update --init --recursive fails due to broken URLs, apply this structural workaround:

  1. Open the project’s root .gitmodules configuration file.
  2. Replace any instances of git://git.qemu.org/ with the modern HTTPS mirror: https://gitlab.com/qemu-project/.
  3. Force a sync and update:
git submodule sync
git submodule update --init --recursive

3. Custom QEMU Code Implementation

The custom device model intercepts I/O commands destined for guest ports 0x390–0x39F, dynamically calculates the matching 0x5390 host offset, and safely drops into ring-0 hardware privilege using iopl(3) to execute the instructions via inline x86 assembly.

Locate your custom C device file or implementation block within the QEMU architecture tree (e.g., hw/misc/host-isa-proxy.c) and structure the implementation exactly as follows:

#include "qemu/osdep.h"
#include "hw/sysbus.h"
#include "exec/memory.h"
#include <sys/io.h>
#include <stdbool.h>

/* Dynamic Read Callback */
static uint64_t host_isa_proxy_read(void *opaque, hwaddr addr, unsigned size)
{
    static __thread bool iopl_unlocked = false;
    if (!iopl_unlocked) {
        if (iopl(3) < 0) {
            perror("ISA PROXY ERROR: iopl privilege escalation failed");
            return 0xFF;
        }
        iopl_unlocked = true;
    }

    uint8_t value;
    uint16_t host_port = 0x5390 + addr; // Map guest offset to physical host register
    
    // Execute hardware level x86 assembly read instruction
    asm volatile("inb %%dx, %0" : "=a"(value) : "d"(host_port)); 
    
    // Log explicitly to standard error stream
    fprintf(stderr, "ISA PROXY: Guest read at offset 0x%lx -> Target Host Port: 0x%x -> Value: 0x%02X\n", 
            (unsigned long)addr, host_port, value);
    fflush(stderr); // Force immediate flush past daemon buffering layers
    
    return value;
}

/* Dynamic Write Callback */
static void host_isa_proxy_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
{
    static __thread bool iopl_unlocked = false;
    if (!iopl_unlocked) {
        if (iopl(3) < 0) {
            perror("ISA PROXY ERROR: iopl privilege escalation failed");
            return;
        }
        iopl_unlocked = true;
    }

    uint8_t value = (uint8_t)val;
    uint16_t host_port = 0x5390 + addr; // Map guest offset to physical host register

    // Execute hardware level x86 assembly write instruction
    asm volatile("outb %0, %%dx" : : "a"(value), "d"(host_port));

    fprintf(stderr, "ISA PROXY: Guest write at offset 0x%lx -> Target Host Port: 0x%x -> Data: 0x%02X\n", 
            (unsigned long)addr, host_port, value);
    fflush(stderr); // Force immediate flush past daemon buffering layers
}

/* Bind Read/Write Callbacks */
static const MemoryRegionOps host_isa_proxy_ops = {
    .read = host_isa_proxy_read,
    .write = host_isa_proxy_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
    .impl = {
        .min_access_size = 1,
        .max_access_size = 1,
    },
};

/* Device Initialization & Mapping Window Size Definition */
void host_isa_proxy_init(MemoryRegion *raw_isa)
{
    // CRITICAL: Window size must be initialized to 16 bytes to capture full 0x390-0x39F block
    memory_region_init_io(raw_isa, NULL, &host_isa_proxy_ops, NULL, "physical-isa-card", 16);
}

4. Compilation & Deployment

Step 4.1: Compile the Custom System Binary

Configure and build only the required x86_64 virtualization targets to speed up compilation time:

mkdir build
cd build
../configure --target-list=x86_64-softmmu --enable-kvm --enable-gtk --enable-sdl
make -j$(nproc)

Step 4.2: Move and Register the Binary on the Target Host

  1. Transfer the compiled qemu-system-x86_64 binary to the host machine via secure network copy or standard USB media storage.
  2. Store the binary in a distinct local directory to prevent accidental package manager overwrites:
sudo cp qemu-system-x86_64 /usr/local/bin/qemu-system-x86_64-custom
sudo chmod +x /usr/local/bin/qemu-system-x86_64-custom

Step 4.3: Bind Custom Path within Libvirt (Virtual Machine Manager)

If utilizing Libvirt/VMM to manage the Windows 98 guest machine, alter the execution configuration:

sudo virsh edit Win98_VM_Name

Locate the <devices> element block and explicitly update the <emulator> tag to prioritize your custom proxy pipeline:

<devices>
  <emulator>/usr/local/bin/qemu-system-x86_64-custom</emulator>
  ...
</devices>

Verify the active runtime parameters once execution begins:

ps -ef | grep qemu-system-x86_64-custom

5. Verification & Testing Protocol

To ensure full bi-directional pass-through behavior without loading external driver payloads, use the vintage native MS-DOS debug environment.

Step 5.1: Host Side Live Log Monitoring

Before issuing commands inside the guest machine, establish a live trace interface on the Ubuntu Host to bypass systemd tracking blocks:

sudo tail -f /var/log/libvirt/qemu/*.log

Step 5.2: Executing MS-DOS Debug Sweeps

Boot the Windows 98 guest environment into an MS-DOS prompt window. Initialize the low-level utility tool:

C:\> debug

CRITICAL SYNTAX NOTE: Vintage versions of debug.exe parse spaces strictly. Separate the target I/O port address and data byte payload using a comma (o port,data).

Execute an sequential sweeping check of the card’s assigned registers:

-i 390
-i 391
-i 392
-i 394
-i 396
-i 397

Step 5.3: Interpreting the Diagnostics Data Profile

If the architecture pipeline is fully intact, the hardware registers will match the factory profile shown below:

-i 390   --> Returns 00  (ADC Low Byte: Expected idle condition state)
-i 391   --> Returns 00  (ADC High Byte: Expected idle condition state)
-i 392   --> Returns 77  (Status Flag: Binary 01110111, indicating steady digital inputs)
-i 394   --> Returns FD  (Intel 8254 Counter 0: Live fluctuating value proving clock ticks)
-i 396   --> Returns 00  (Intel 8254 Counter 2: Stable, unassigned background counter clock)
-i 397   --> Returns FF  (8254 Control Register: Write-only register; reading forces open-bus floating)

Step 5.4: Verifying Complete Hardware Write Execution

To verify full outbound control pathing to the physical card, attempt to cycle the internal Multiplexer (MUX) configuration lines:

-o 392,00   <-- Sets MUX targeting Analog Channel 0
-i 392      <-- Read status line results back
-o 392,07   <-- Sets MUX targeting Analog Channel 7
-i 392      <-- Confirm changes register on the board hardware

Your host log output will capture every single execution step simultaneously, verifying that your software logic and hardware connection are fully operational.