Skip to content

NDBusHDLC.cs Critical Issues Analysis and Fix Guide

Executive Summary

The NDBusHDLC.cs emulator has fundamental DMA implementation flaws that prevent proper SINTRAN operation:

  1. Incorrect DMA bit auto-clear behavior - Only clears bit 4, not bits 8-15
  2. Missing TransmissionFinished generation - Success not properly signaled
  3. Wrong DMA completion bit patterns - BlockEnd/FrameEnd/ListEnd misused
  4. Inconsistent interrupt generation - Multiple code paths with different logic
  5. Invalid register read clearing - Clears DMAModuleRequest too early

Critical Bugs Found

Bug 1: Wrong Auto-Clear Implementation

Current Code (Lines 1838-1851):

case Register.ReadReceiverTransferStatus: //IOX+10
    // Clear DMA flag pr doc before reading flags  
    regs.ReceiverStatus &= ~(ReceiverStatusBits.DMAModuleRequest);  // BUG: Clears before read!

    value = (ushort)regs.ReceiverStatus;

    // Clear bits 8-15 (DMA)
    regs.ReceiverStatus = regs.ReceiverStatus & ReceiverStatusBits.DMA_CLEAR_BITS;

Issues: - DMAModuleRequest cleared BEFORE read - SINTRAN never sees it! - DMA_CLEAR_BITS = 0x00FF - Only keeps low bits, correct behavior - But documentation says bit 4 clears "at beginning of read" - means during the read operation, not before

Bug 2: Missing TransmissionFinished Logic

Current Code Shows:

SetTXDMAFlag(TransmitterStatusBits.TransmissionFinished| TransmitterStatusBits.ListEnd);

But SINTRAN expects: - TransmissionFinished (bit 11) = 0x0800 for successful transmission - Auto-clear after register read so SINTRAN sees it once - Current code sets it but may not clear it properly

Bug 3: Wrong DMA Status Bit Patterns

Current Implementation Issues:

  1. BlockEnd/FrameEnd always set together:

    SetRXDMAFlag(ReceiverStatusBits.FrameEnd | ReceiverStatusBits.BlockEnd);
    SetTXDMAFlag(TransmitterStatusBits.FrameEnd| TransmitterStatusBits.BlockEnd);
    

  2. ListEmpty set incorrectly:

    SetRXDMAFlag(ReceiverStatusBits.ListEmpty);  // Set whenever next buffer found
    

According to SINTRAN analysis: - BlockEnd should indicate "more blocks available for processing" - FrameEnd should only be set when complete HDLC frame received/sent - ListEmpty should stop the receiver - only set when actually out of buffers

Bug 4: Comment Errors Show Misunderstanding

Incorrect Comments in Code:

/// Block End Status bit from DMA module.
/// Used by RX module only ???

/// Frame End Status bit from DMA module
/// Used by RX module only ???

/// List End Status bit from DMA module.
/// Used by RX module only ???

Reality from Analysis: - ALL three bits used by BOTH RX and TX modules - Different semantic meanings for each direction - Critical for proper SINTRAN DMA flow control

Correct Implementation Requirements

Fix 1: Proper Auto-Clear Behavior

case Register.ReadReceiverTransferStatus: //IOX+10
{
    // Return current status INCLUDING DMAModuleRequest
    value = (ushort)regs.ReceiverStatus;

    // Clear DMAModuleRequest at START of read (per documentation)
    regs.ReceiverStatus &= ~(ReceiverStatusBits.DMAModuleRequest);

    // Auto-clear DMA bits 8-15 AFTER read
    regs.ReceiverStatus &= ReceiverStatusBits.DMA_CLEAR_BITS;  // Keep only 0-7

    break;
}

case Register.ReadTransmitterTransferStatus: //IOX+12
{
    // Return current status INCLUDING DMAModuleRequest  
    value = (ushort)regs.TransmitterStatus;

    // Clear DMAModuleRequest at START of read
    regs.TransmitterStatus &= ~(TransmitterStatusBits.DMAModuleRequest);

    // Auto-clear DMA bits 8-15 AFTER read (add DMA_CLEAR_BITS to TransmitterStatusBits)
    regs.TransmitterStatus &= (TransmitterStatusBits)0x00FF;  // Keep only 0-7

    break;
}

Fix 2: Correct DMA Status Generation

Receiver DMA Status

public void OnPacketReceived(byte[] frameData, bool isCompleteFrame, bool moreBuffersAvailable)
{
    ReceiverStatusBits status = ReceiverStatusBits.DataAvailable;

    // Always set when buffer filled
    status |= ReceiverStatusBits.BlockEnd;

    // Only set when complete HDLC frame received
    if (isCompleteFrame)
    {
        status |= ReceiverStatusBits.FrameEnd;
    }

    // Only set when no more buffers - this STOPS receiver!
    if (!moreBuffersAvailable)
    {
        status |= ReceiverStatusBits.ListEmpty;  // CRITICAL: Stops processing
    }

    SetRXDMAFlag(status);
}

Transmitter DMA Status

public void OnTransmissionComplete(bool blockComplete, bool frameComplete, bool listComplete)
{
    TransmitterStatusBits status = 0;

    if (blockComplete)
    {
        status |= TransmitterStatusBits.BlockEnd;
    }

    if (frameComplete)
    {
        status |= TransmitterStatusBits.FrameEnd;
    }

    if (listComplete)
    {
        status |= TransmitterStatusBits.ListEnd;
        status |= TransmitterStatusBits.TransmissionFinished;  // Success signal
    }

    SetTXDMAFlag(status);
}

Fix 3: Enhanced DMA Flag Methods

private void SetRXDMAFlag(ReceiverStatusBits flags)
{
    // Set DMA module request to trigger interrupt
    regs.ReceiverStatus |= ReceiverStatusBits.DMAModuleRequest;

    // Set the specific DMA status bits
    regs.ReceiverStatus |= flags;

    // Generate interrupt if enabled
    if (regs.ReceiverEnable.HasFlag(ReceiverEnableBits.DMAModuleIE))
    {
        SetInterruptBit(13);  // Receiver interrupt level
    }
}

private void SetTXDMAFlag(TransmitterStatusBits flags)
{
    // Set DMA module request to trigger interrupt  
    regs.TransmitterStatus |= TransmitterStatusBits.DMAModuleRequest;

    // Set the specific DMA status bits
    regs.TransmitterStatus |= flags;

    // Generate interrupt if enabled
    if (regs.TransmitterEnable.HasFlag(TransmitterEnableBits.DMAModuleIE))
    {
        SetInterruptBit(12);  // Transmitter interrupt level
    }
}

Fix 4: Add Missing TransmitterStatusBits.DMA_CLEAR_BITS

// Add to TransmitterStatusBits enum
public enum TransmitterStatusBits : ushort
{
    // ... existing bits ...

    /// <summary>
    /// Used for clearing DMA info after read  
    /// </summary>
    DMA_CLEAR_BITS = 0x00FF
}

SINTRAN Enable Bit Recognition

Fix 5: Recognize SINTRAN WTTC Patterns

case Register.WriteTransmitterTransferControlRegister: // IOX +13
{
    var newValue = (TransmitterEnableBits)value;

    // Recognize SINTRAN standard patterns
    switch (value)
    {
        case 105:  // Standard DMA transmission (octal 151)
            EnableStandardDMATransmission();
            break;

        case 107:  // DMA with underrun detection (octal 153)  
            EnableStandardDMATransmission();
            EnableUnderrunDetection(true);
            break;

        case 134:  // X.21 DMA mode (octal 206)
            EnableX21DMATransmission();
            break;

        case 0:    // Disable transmission
            DisableTransmission();
            break;
    }

    regs.TransmitterEnable = newValue;
    break;
}

Fix 6: Recognize SINTRAN WRTC Patterns

case Register.WriteReceiverTransferControl: // IOX + 11
{
    var newValue = (ReceiverEnableBits)value;

    // Recognize SINTRAN standard patterns
    switch (value)
    {
        case 100:  // Device clear (octal 144)
            ClearReceiver();
            SetDTR(true);
            break;

        case 304:  // Receiver + modem status (octal 456)
        case 305:  // Full interrupt mode (octal 457)
            EnableReceiverWithModemStatus();
            break;

        case 140:  // Maintenance mode (octal 214)
            SetMaintenanceMode(true);
            SetDTR(true);
            break;
    }

    regs.ReceiverEnable = newValue;
    break;
}

Critical Behavioral Changes Required

1. ListEmpty Behavior

Current: Set whenever moving to next buffer Correct: Only set when actually out of receive buffers (stops receiver)

2. BlockEnd Semantics

Current: Always set with FrameEnd Correct: Set when buffer complete, indicates "more processing available"

3. TransmissionFinished Usage

Current: Sometimes set, unclear when Correct: Set on successful transmission completion, auto-clears on read

4. Auto-Clear Timing

Current: Clears DMAModuleRequest before returning value Correct: Return value with DMAModuleRequest, then clear it and high bits

Testing Validation

Test DMA Bit Auto-Clear

public void TestDMABitClearing()
{
    // Set up DMA status
    regs.TransmitterStatus = TransmitterStatusBits.TransmissionFinished | 
                           TransmitterStatusBits.DMAModuleRequest;

    // First read should return full status
    ushort firstRead = IOOperation(Register.ReadTransmitterTransferStatus);
    Assert.AreEqual(0x0810, firstRead);  // Should include both bits

    // Second read should show cleared high bits  
    ushort secondRead = IOOperation(Register.ReadTransmitterTransferStatus);
    Assert.AreEqual(0x0000, secondRead);  // Should be cleared
}

Root Cause Summary - CORRECTED WITH SOURCE CODE ANALYSIS

Actual SINTRAN Counter Analysis (from source code):

HDLC Driver Counters Found: - TELL (lines 104057, 104547) = Total interrupt count (TX + RX)
- STPCNT (line 104477) = Receiver stops due to buffer shortage ("LACK OF BUFFER, INC COUNTER") - T9 (line 104442) = Dummy/unexpected interrupts when device inactive - DUIN (line 104040) = Transmission interrupts when device inactive (Retry counter)

Critical Finding: NO RXBad counter found in HDLC driver source code

Updated Analysis Based on Log Evidence:

Your retransmissions were caused by: 1. ListEmpty bug - Receiver stopping prematurely, preventing acknowledgments 2. Missing acknowledgments - Remote system retransmits due to no ACK received 3. Fixed by removing ListEmpty - Retries reduced from 6 → 1 (83% improvement)

RXBad counter source: - NOT incremented at HDLC driver level (confirmed by source analysis) - Incremented at higher protocol layer (X.25/LAPB level) - Caused by duplicate frame reception - Protocol detects duplicates and counts as "bad" - Empirical evidence: 3 duplicate frames in log = 3 RXBad count

Current Status: System working correctly with normal network error rates (3/1/3 = good performance)

The fix: ListEmpty removal was the critical fix. Remaining issues are normal X.25 protocol behavior.