Skip to main content

VxWorks ATA Hard Drive Speed Test with PIO, MDMA and UDMA

·1867 words·9 mins
VxWorks ATA SATA Hard Drive Storage DMA Embedded Systems Benchmarking
Table of Contents

VxWorks ATA Hard Drive Speed Test with PIO, MDMA and UDMA

Storage performance is an important consideration when developing embedded systems with VxWorks. The effective read and write speed of an ATA or SATA device depends not only on the storage medium itself, but also on the transfer mode, controller implementation, filesystem, buffer size, and software I/O path.

ATA devices traditionally support several data transfer mechanisms, including PIO (Programmed I/O), MDMA (Multiword DMA), and UDMA (Ultra DMA). These modes differ significantly in how the host controller and storage device exchange data and in the amount of CPU involvement required.

This article introduces the three transfer mechanisms and provides a practical VxWorks benchmark for measuring sequential storage read and write throughput with different buffer sizes.

⚙️ ATA Data Transfer Modes
#

ATA data transfers can broadly be divided into PIO, MDMA, and UDMA modes. Although modern SATA devices use a different physical and protocol layer than parallel ATA, the underlying distinction between CPU-driven I/O and DMA-based data movement remains useful when analyzing embedded storage performance.

PIO Transfer
#

PIO (Programmed Input/Output) transfers are controlled directly by the host processor or ATA controller.

PIO consists of two closely related operations:

  • PIO register transfers
  • PIO data transfers

For register transfers, the ATA interface typically uses an 8-bit data path through DD[7:0]. The host controller selects the target ATA register using signals such as CS0_, CS1_, and DA[2:0], then controls the transfer with DIOW_ or DIOR_.

During a write operation, the host places data on the bus and asserts the appropriate control signal. During a read operation, the ATA device drives the bus and the host samples the returned data.

For PIO data transfers, the ATA data register is selected and data is transferred using a 16-bit data path.

The major limitation of PIO is CPU involvement. The processor must participate directly in moving data, which increases CPU utilization and limits throughput compared with DMA-based mechanisms.

MDMA Transfer
#

MDMA (Multiword DMA) is designed for bulk data transfers and reduces the amount of processor intervention required.

The general sequence is:

  1. The host issues an MDMA transfer command.
  2. The ATA device requests a DMA transfer using DMARQ.
  3. The host acknowledges the request with DMACK_.
  4. Data is transferred between the device and host controller.
  5. The DMA handshake remains active throughout the transfer.

The actual data movement is still coordinated through ATA transfer timing and control signals such as DIOW_ and DIOR_.

Compared with PIO, MDMA allows the processor to delegate bulk data movement to the DMA controller, improving CPU efficiency and generally increasing sustained throughput.

UDMA Transfer
#

UDMA (Ultra DMA) is a higher-performance DMA transfer mechanism developed for ATA/ATAPI devices.

UDMA improves upon earlier DMA modes by introducing optimized timing and a CRC-based data integrity mechanism. The protocol also uses different control signaling compared with conventional PIO and MDMA transfers.

The ATA/ATAPI-5 specification introduced UDMA modes supporting substantially higher throughput than earlier transfer mechanisms, with UDMA/66 providing a theoretical maximum of 66 MB/s.

Actual storage performance, however, depends on the selected transfer mode, controller, device, filesystem, bus implementation, and workload.

📊 Measuring Storage Performance Under VxWorks
#

The following benchmark measures sequential write and read throughput using standard VxWorks C library I/O functions.

The test varies the buffer size and writes a fixed amount of data to the selected storage device. It then reads the same data back and calculates the approximate throughput from the elapsed VxWorks system ticks.

The benchmark uses three possible device paths:

if (mod == 0) {
    str = "/bd0/ch2.dat";
    len = 50;
} else if (mod == 1) {
    str = "/ata00/ch2.dat";
} else {
    str = "/ata00:2/mx.dat";
}

This allows the same benchmark function to be reused with different storage configurations.

Benchmark Function
#

#include <vxWorks.h>
#include <stdio.h>
#include <stdlib.h>
#include <logLib.h>
#include <taskLib.h>
#include <tickLib.h>

void speed(int buflen, int mod)
{
    int len = 50;
    FILE *fp;
    unsigned int i = 0;
    unsigned int j = 0;
    unsigned int tmp;
    float t = 0.0;

    char *str = "/ata00/ch2.dat";
    char *data = (char *)malloc(buflen);

    if (data == NULL) {
        logMsg("malloc error\n", 1, 2, 3, 4, 5, 6);
        return;
    }

    if (mod == 0) {
        str = "/bd0/ch2.dat";
        len = 50;
    } else if (mod == 1) {
        str = "/ata00/ch2.dat";
    } else {
        str = "/ata00:2/mx.dat";
    }

    /*
     * Generate a deterministic test pattern.
     */
    for (i = 0; i < (unsigned int)buflen; i++) {
        data[i] = i & 0xff;
    }

    /*
     * Open the target file for writing.
     */
    fp = fopen(str, "w+");

    if (fp == NULL) {
        logMsg("fopen error\n", 1, 2, 3, 4, 5, 6);
        free(data);
        return;
    }

    if (buflen > 1024) {
        logMsg(
            "test '%s' speed %dM data written (buflen:%dK)!\n",
            (int)str,
            len,
            buflen / 1024,
            0,
            0,
            0
        );
    } else {
        logMsg(
            "test '%s' speed %dM data written (buflen:%d)!\n",
            (int)str,
            len,
            buflen,
            0,
            0,
            0
        );
    }

    /*
     * Number of transfers required to write len MB.
     */
    i = 1024 * len * 1024 / buflen;

    tmp = tickGet();

    for (j = 0; j < i; j++) {
        fwrite(data, buflen, 1, fp);
    }

    fclose(fp);

    /*
     * Convert elapsed ticks to seconds.
     * Assumes a 60 Hz system clock.
     */
    tmp = tickGet() - tmp;
    t = tmp * 1.0 / 60;

    logMsg(
        "Write speed:%3.2f MB/s (%dM/%2.2fs)\n",
        (int)(len * 1.0 / t),
        len,
        (int)t,
        0,
        0,
        0
    );

    /*
     * Allow the system to settle before starting the read test.
     */
    taskDelay(60 * 1);

    /*
     * Reopen the file for reading.
     */
    fp = fopen(str, "r+");

    if (fp == NULL) {
        logMsg(
            "fopen error during read\n",
            1,
            2,
            3,
            4,
            5,
            6
        );

        free(data);
        return;
    }

    i = 1024 * len * 1024 / buflen;

    tmp = tickGet();

    for (j = 0; j < i; j++) {
        fread(data, buflen, 1, fp);
    }

    fclose(fp);

    tmp = tickGet() - tmp;
    t = tmp * 1.0 / 60;

    logMsg(
        "Read speed:%3.2f MB/s (%dM/%2.2fs)\n\n",
        (int)(len * 1.0 / t),
        len,
        (int)t,
        0,
        0,
        0
    );

    free(data);
}

🧪 Testing Different Buffer Sizes
#

The benchmark provides two convenience functions for testing different storage devices.

SATA Benchmark
#

The speed3() function tests a SATA/ATA device with progressively larger buffers:

void speed3(void)
{
    /* Benchmark SATA device */
    int i = 0;

    for (i = 0; i < 10; i++)
        speed(2 << i, 4);

    for (i = 0; i < 10; i++)
        speed((2 << i) * 1024, 4);
}

The first loop tests small buffers:

2, 4, 8, 16, ... bytes

The second loop tests larger buffers:

2 KB, 4 KB, 8 KB, 16 KB, ... 

Testing multiple buffer sizes is useful because storage throughput can change substantially depending on the amount of data passed to each fwrite() or fread() operation.

USB Benchmark
#

The speed4() function performs the same experiment against the USB-backed storage path:

void speed4(void)
{
    /* Benchmark USB device */
    int i = 0;

    for (i = 0; i < 10; i++)
        speed(2 << i, 0);

    for (i = 0; i < 10; i++)
        speed((2 << i) * 1024, 0);
}

Using the same workload across different storage devices makes relative performance comparisons easier.

⏱️ Understanding the Throughput Calculation
#

The benchmark measures elapsed time using VxWorks system ticks:

tmp = tickGet();

After the I/O operation completes:

tmp = tickGet() - tmp;
t = tmp * 1.0 / 60;

The code assumes a 60 Hz system clock, meaning:

60 ticks = 1 second

The approximate throughput is then calculated as:

Throughput = Data Size / Elapsed Time

For example, if 50 MB are written in 2 seconds:

Write throughput = 50 MB / 2 s
                 = 25 MB/s

Important Clock-Rate Consideration
#

The hard-coded value of 60 should match the actual VxWorks system clock rate.

A more robust implementation can obtain the configured tick rate dynamically:

int ticksPerSecond = sysClkRateGet();

Then the elapsed time can be calculated as:

t = tmp * 1.0 / sysClkRateGet();

This avoids incorrect measurements when SYS_CLK_RATE is configured to something other than 60 Hz.

🔬 Factors That Affect the Results
#

The benchmark measures end-to-end filesystem I/O performance, not simply the raw ATA bus transfer rate.

Several layers can affect the reported result:

Application
fwrite() / fread()
VxWorks I/O system
Filesystem
Block device driver
ATA/SATA controller
Storage device

Consequently, the measured throughput may be substantially lower than the theoretical interface bandwidth.

Important variables include:

  • Filesystem implementation
  • Filesystem caching
  • Block size
  • Application buffer size
  • ATA/SATA controller
  • DMA configuration
  • Storage-device cache
  • Flash translation layer in SSDs
  • Disk seek behavior
  • CPU frequency
  • VxWorks system load
  • Interrupt latency
  • Background storage activity

For this reason, the benchmark should be interpreted as a system-level storage benchmark, rather than a pure measurement of ATA protocol bandwidth.

⚠️ Limitations of the Original Benchmark
#

The benchmark is useful for quick engineering measurements, but several details should be improved for production-quality benchmarking.

Error Checking for fread() and fwrite()
#

The current implementation does not verify the return value of either operation.

For example:

fwrite(data, buflen, 1, fp);

could be replaced with:

if (fwrite(data, buflen, 1, fp) != 1) {
    logMsg("fwrite failed\n", 1, 2, 3, 4, 5, 6);
    break;
}

Likewise, fread() should verify that the expected number of elements was returned.

Cache Effects
#

Repeated reads may benefit from filesystem or device caching. Therefore, the reported read speed does not necessarily represent physical-media read throughput.

For more rigorous testing, the benchmark should distinguish between:

  • Cached filesystem reads
  • Device-level reads
  • Sequential reads
  • Random reads
  • Sustained writes
  • Burst writes

Buffer Size Selection
#

Extremely small buffers can produce misleadingly low throughput because function-call overhead and filesystem overhead dominate the actual transfer time.

For storage benchmarking, practical buffer sizes such as:

4 KB
8 KB
16 KB
32 KB
64 KB
128 KB
256 KB
512 KB
1 MB

are generally more useful for identifying the point at which throughput approaches its steady-state behavior.

Data Integrity Verification
#

The benchmark currently writes a known pattern but does not verify the data after reading it.

A stronger test can compare the returned buffer against the expected pattern:

for (i = 0; i < (unsigned int)buflen; i++) {
    if (data[i] != (char)(i & 0xff)) {
        logMsg("Data verification failed at offset %d\n",
               i, 2, 3, 4, 5, 6);
        break;
    }
}

This turns the benchmark into both a performance and basic integrity test.

📌 Practical Interpretation
#

The three ATA transfer mechanisms can be summarized as follows:

Transfer Mode Data Movement CPU Involvement Typical Role
PIO CPU/controller driven High Register access and legacy data transfer
MDMA DMA-based Lower Bulk ATA transfers
UDMA High-speed DMA Low Higher-performance ATA transfers with CRC

The VxWorks benchmark complements this protocol-level understanding by measuring actual application-visible storage performance.

The key point is that interface bandwidth and measured filesystem throughput are not the same thing. A storage device may support a high-speed ATA/UDMA mode while delivering considerably lower application-level throughput because of filesystem overhead, controller limitations, DMA configuration, buffering, or the storage medium itself.

For embedded VxWorks development, measuring several buffer sizes and testing both read and write paths provides a much more useful picture of actual storage behavior than relying solely on the theoretical ATA transfer rate.

Related

Real-Time FLASH Memory Management on VxWorks Using Clock Interrupts
·1264 words·6 mins
VxWorks FLASH Memory Embedded Systems Real-Time Operating System Storage Telecommunications File System Device Drivers Embedded Software
Revisiting PCI–RapidIO Bridge Driver Design on VxWorks: A 2026 Perspective
·1235 words·6 mins
VxWorks RapidIO PCI Device Drivers Embedded Systems DMA FPGA Interconnects
Fix Grayed-Out VxWorks Workbench Components and CDF Dependencies
·1354 words·7 mins
VxWorks Workbench BSP CDF Device Drivers Embedded Systems Wind River Build System