HotBits Driver Program


The following C program monitors the serial or parallel port to which the HotBits detector is attached, measures the delay for a pair of counts, and returns the resulting random bits. This module is intended to run under Linux on an Intel Pentium architecture processor. It has been tested on Fedora Core 5 Linux, compiled with GCC 4.1.1.

The driver uses in-line assembly language to access the serial or parallel port and poll the detector. It must be run as the super-user to permit direct access to the I/O ports and allow the interval timing code to prevent interrupts while a measurement is in progress.

This code is intended to be run on a dedicated machine, not a server or workstation running other applications. In order to accurately time the interval between counts in software, it's necessary to lock out interrupts for the duration of the measurement. If a large number of bits are being generated, this essentially freezes up the machine, which is fine on a dedicated machine but intolerable if you're running other programs. Also, note that no emergency escape is provided in case the detector stops sending pulses—the machine will simply hang. An emergency escape timeout runs the risk of polluting the data since it is theoretically possible (although of vanishing probability if you're using an artificial radiation source) for an arbitrarily long interval to separate two counts. But if you're using background radiation as your source, the risk of an escape timeout long enough to be useful becomes significant. If you want to add an escape, it's easy enough to do.

The driver program and its companion header file containing function prototypes is available for downloading as a Gzipped TAR archive; it is also included in the HotBits Server source distribution.


/*

        Quantum Random Number Generator Driver
        
        This driver is compatible with the Aware Electronics RM-xx
        computer interfaced Geiger-Müller counters, attached to a COMn
        serial or LPTn parallel port.

*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/io.h>
#include <sys/mman.h>

#ifdef GENERATOR
#ifndef PSEUDO

/*      Serial I/O port properties  */

#define COM1            "$0x3F8"
#define COM2            "$0x2F8"
#define COM3            "$0x3E8"
#define COM4            "$0x2E8"

#define BUFFER          "$0x0"                  /* I/O buffer */
#define IENABLE         "$0x1"                  /* Interrupt enable */
#define LINECTL         "$0x3"                  /* Line control register */
#define MODEMCTL        "$0x4"                  /* Modem control register */
#define MODEMSTAT       "$0x6"                  /* Modem status word */

/*      Parallel I/O port properties  */

#define LPT1            "$0x378"
#define LPT2            "$0x278"
#define LPT3            "$0x3BC"

#define DATA            "$0x0"                  /* Data register */
#define STATUS          "$0x1"                  /* Status register */
#define     nAck        "$0x40"                     /* nAck status: count indicator */
#define CONTROL         "$0x2"                  /* Control register */


#define commbase COM1                           /* I/O port base address */
/* #define PARALLEL  */                         /* Define if commbase is a parallel port */


/*  HOTBITSINIT  --  Initialise the HotBits data source.  */

void hotbitsInit(void)
{
    int i = iopl(3);
    if (i < 0) {
        perror("setting iopl(3)");
        abort();
    }
    
    /*  The following applies power to an Aware sensor
        powered by the serial or parallel port.  Interrupts
        from the port are disabled, as we will be polling it
        in a loop.  */

    asm volatile (
#ifdef PARALLEL
        "movw " commbase ",%dx\n\t"     /* Set control port for data output, no interrupts */
        "addw " CONTROL ",%dx\n\t"
        "movb $0x00,%al\n\t"
        "outb %al,%dx\n\t"
        
        "movw " commbase ",%dx\n\t"     /* Raise data signal pins to power detector */
        "addw " DATA ",%dx\n\t"
        "movb $0xFF,%al\n\t"
        "outb %al,%dx\n\t"
#else
        "movw " commbase ",%dx\n\t"     /* Make sure we're not setting baud rate */
        "addw " LINECTL ",%dx\n\t"
        "movb $0x03,%al\n\t"
        "outb %al,%dx\n\t"
        
        "movw " commbase ",%dx\n\t"     /* Disable all interrupts */
        "addw " IENABLE ",%dx\n\t"
        "xor %al,%al\n\t"
        "outb %al,%dx\n\t"
        
        "movw " commbase ",%dx\n\t"     /* Set DTR high, RTS low to power detector */
        "addw " MODEMCTL ",%dx\n\t"
        "movb $0x01,%al\n\t"
        "outb %al,%dx\n\t"
#endif
    );
    
    i = iopl(0);    
    if (i < 0) {
        perror("setting iopl(0)");
        abort();
    }
    
    sleep(15);                          /* Allow time for high voltage start-up */
}

/*  COUNTINTERVAL  --  Measure interval between two successive counts.  */

/*  If SERIALISE is defined, CPUID instructions will be executed
    to prevent out-of-order execution around the RTDSC instructions
    used to time the intervals.  This improves the consistency of
    timing but, in fact, the speed of the processor clock and the
    timing loop itself is so vastly greater than the rise time and
    response to a signal from a serial or parallel port that the
    jitter in the timing due to instruction re-ordering may be
    neglected.  */
/* #define SERIALISE */

/*  If ALLOW_INTERRUPTS is defined, this code will not lock out
    interrupts during measurement of the interval.  The only reason
    you should consider defining this symbol is if you want to step
    through this function with a debugger or you're worried that a
    change you made might hang in a loop, which would require a
    reboot if it occurred with interrupts prevented.  DO NOT DEFINE
    THIS SYMBOL IN PRODUCTION MODE--an interrupt during the interval
    measurement code WILL cause a grossly wrong duration to be reported
    which will corrupt the generated bit stream.  */
/* #define ALLOW_INTERRUPTS */

/*  If SHIFT_CLOCK is defined, the time stamp counter will be
    shifted to the right the number of bits given by the definition.
    You can use this to reduce the amount of jitter in the values
    returned and/or limit their magnitude.  The latter is dangerous,
    though, if you're assuming the shifted value will fit in a
    variable shorter than 64 bits, since there's no guarantee on
    what the time stamp value will be or how quickly it may
    increment on processors in the future.   If you don't want to
    shift the clock (which is the recommendation, don't define
    SHIFT_CLOCK--defining it as zero will assemble in an idiotic
    shift by zero bits.  Note that you have to quote the number
    of bits.  For some reason you can't stringify a symbol inside
    inline assembly.  */
/* #define SHIFT_CLOCK "8" */

static unsigned long long countInterval(void)
{

#ifdef PARALLEL
    asm volatile (
#ifdef SERIALISE
        "pushl %ebx\n\t"                // Save EBX, which CPUID will wipe
#endif
        "movw " commbase ",%dx\n\t"     // Load parallel port base address
        "addw " STATUS ",%dx\n\t"       // Advance to status register
        "movw %dx,%cx\n\t"              // Save status register address
#ifndef ALLOW_INTERRUPTS
        "cli\n\t"                       // Lock out interrupts for consistent timing
#endif

        //  Wait for the end of an in-progress pulse, if any
        
        "wait0:\n\t"
        "inb %dx,%al\n\t"               // Sense current detector status
        "testb " nAck ",%al\n\t"        // Are we already within a pulse ?
        "jz wait0\n\t"                  // Yes.  Spin until it's over
        
        //  Wait until the start of the next pulse

        "wait1:\tinb %dx,%al\n\t"       // Sense for first pulse
        "testb " nAck ",%al\n\t"        // Is a pulse in progress ?
        "jnz wait1\n\t"                 // No.  First pulse hasn't begun
        
        //  Record the time the pulse began

#ifdef SERIALISE
        "pushw %cx\n\t"                 // Save I/O port address
        "movl $0,%eax\n\t"              // Request base CPU identity
        "cpuid\n\t"                     // Force serialisation of execution
#endif
        "rdtsc\n\t"                     // Read time stamp at start of interval
#ifdef SHIFT_CLOCK
        "shrdl $" SHIFT_CLOCK ",%edx,%eax\n\t"  // Shift high 32 bits into lower
        "shrl $" SHIFT_CLOCK ",%edx\n\t" // Zero fill the most significant bits
#endif
        "movl %edx,%esi\n\t"            // Save in ESI and EDI
        "movl %eax,%edi\n\t"
#ifdef SERIALISE
        "mov $0,%eax\n\t"               // Request base CPU identity
        "cpuid\n\t"                     // Force serialisation of execution
        "popw %cx\n\t"                  // Restore serial port address
#endif
        "movw %cx,%dx\n"                // Restore I/O port address
        
        //  Spin until the first pulse ends
        
        "wait2:\n\t"
#ifdef SERIALISE
        "pushw %dx\n\t"                 // Save I/O port address
        "mov $0,%eax\n\t"               // Request base CPU identity
        "cpuid\n\t"                     // Force serialisation of execution
        "popw %dx\n\t"                  // Restore I/O port address
#endif
        "inb %dx,%al\n\t"               // Sense current detector status
        "testb " nAck ",%al\n\t"        // Are we still within the pulse ?
        "jz wait2\n\t"                  // Yes.  Spin until it's over

        //  Wait until the start of the next pulse

        "wait3:\n\t"
        "inb %dx,%al\n\t"               // Sense detector status
        "testb " nAck ",%al\n\t"        // Has second pulse begun ?
        "jnz wait3\n\t"                 // No.  Spin until it is

#ifdef SERIALISE
        "movl $0,%eax\n\t"              // Request base CPU identity
        "cpuid\n\t"                     // Force serialisation of execution
#endif
        "rdtsc\n\t"                     // Capture time stamp after second pulse
#ifndef ALLOW_INTERRUPTS
        "sti\n\t"                       // Re-enable interrupts
#endif
#ifdef SHIFT_CLOCK
        "shrdl $" SHIFT_CLOCK ",%edx,%eax\n\t"  // Shift high 32 bits into lower
        "shrl $" SHIFT_CLOCK ",%edx\n\t" // Zero fill the most significant bits
#endif

        "subl %edi,%eax\n\t"            // Compute 64-bit interval in ticks
        "sbbl %esi,%edx\n\t"

#ifdef SERIALISE
        "popl %ebx\n\t"
#endif
        "leave\n\t"
        "ret\n"
    );
    
#else

    asm volatile (
#ifdef SERIALISE
        "pushl %ebx\n\t"                // Save EBX, which CPUID will wipe
#endif
        "movw " commbase ",%dx\n\t"     // Load serial port base address
        "addw " MODEMSTAT ",%dx\n\t"    // Advance to modem status register
        "movw %dx,%cx\n\t"              // Save modem status register address
#ifndef ALLOW_INTERRUPTS
        "cli\n\t"                       // Lock out interrupts for consistent timing
#endif
        "inb %dx,%al\n\t"               // Clear possible previous pulse

        "wait0:\tinb %dx,%al\n\t"       // Sense for first pulse
        "testb $4,%al\n\t"              // Ring indicator status changed ?
        "jz wait0\n\t"                  // No.  First pulse hasn't arrived

#ifdef SERIALISE
        "pushw %cx\n\t"                 // Save I/O port address
        "movl $0,%eax\n\t"              // Request base CPU identity
        "cpuid\n\t"                     // Force serialisation of execution
#endif
        "rdtsc\n\t"                     // Read time stamp at start of interval
#ifdef SHIFT_CLOCK
        "shrdl $" SHIFT_CLOCK ",%edx,%eax\n\t"  // Shift high 32 bits into lower
        "shrl $" SHIFT_CLOCK ",%edx\n\t" // Zero fill the most significant bits
#endif
        "movl %edx,%esi\n\t"            // Save in ESI and EDI
        "movl %eax,%edi\n\t"
#ifdef SERIALISE
        "mov $0,%eax\n\t"               // Request base CPU identity
        "cpuid\n\t"                     // Force serialisation of execution
        "popw %cx\n\t"                  // Restore serial port address
#endif
        "movw %cx,%dx\n"                // Restore I/O port address

        "wait1:\n\t"
#ifdef SERIALISE
        "pushw %dx\n\t"                 // Save I/O port address
        "mov $0,%eax\n\t"               // Request base CPU identity
        "cpuid\n\t"                     // Force serialisation of execution
        "popw %dx\n\t"                  // Restore I/O port address
#endif
        "inb %dx,%al\n\t"               // Sense modem status register
        "testb $4,%al\n\t"              // Is delta ring indicator set ?
        "jz wait1\n\t"                  // No.  Spin until it is

#ifdef SERIALISE
        "movl $0,%eax\n\t"              // Request base CPU identity
        "cpuid\n\t"                     // Force serialisation of execution
#endif
        "rdtsc\n\t"                     // Capture time stamp after second pulse
#ifndef ALLOW_INTERRUPTS
        "sti\n\t"                       // Re-enable interrupts
#endif
#ifdef SHIFT_CLOCK
        "shrdl $" SHIFT_CLOCK ",%edx,%eax\n\t"  // Shift high 32 bits into lower
        "shrl $" SHIFT_CLOCK ",%edx\n\t" // Zero fill the most significant bits
#endif

        "subl %edi,%eax\n\t"            // Compute 64-bit interval in ticks
        "sbbl %esi,%edx\n\t"
        
#ifdef SERIALISE
        "popl %ebx\n\t"
#endif
        "leave\n\t"
        "ret\n"
    );
#endif
    
    return 0;                           // Dummy--we actually return from assembly above
}

/*  HOTBITSBIT  --  Obtain next random bit from the generator.  */

static int hotbitsBit(void)
{
    int i;
    static int flipper = 0;
    unsigned long long length[2];
    
    while (1) {
        for (i = 0; i < 2; i++) {           
            length[i] = countInterval();
        }

        if (length[0] != length[1]) {
            /* There remains the possibility of a very slight bias due
               to long-term effects such as ionisation of a Geiger tube,
               poisoning of a silicon detector, or (absurdly small for
               any practical source) decrease in radioactivity of the source
               over time.  To mitigate this, we invert the sense of the
               magnitude test between the first and second samples for
               alternate samples.  This pushes the effect of any long-term
               bias to a much higher order effect. */ 
            flipper ^= 1;
            return flipper ^ (length[0] > length[1]);
        }
    }
}

/*  HOTBITSBYTE  --  Return a byte of 8 random bits.  */

static unsigned char hotbitsByte(void)
{
    unsigned char ch;
    int i;
    
    for (i = 0; i < 8; i++) {
        ch = (ch << 1) | hotbitsBit();
    }
    return ch;
}

/*  HOTBITSFILL  --  Fill a buffer with data from the HotBits
                     generator and return the number of bytes
                     obtained.  */

int hotbitsFill(unsigned char *buf, const int len)
{
    int i = iopl(3);
    if (i < 0) {
        perror("setting iopl(3)");
        return 0;
    }
    i = mlockall(MCL_CURRENT | MCL_FUTURE);
    if (i < 0) {
        perror("locking memory");
        return 0;
    }
    
    for (i = 0; i < len; i++) {
        *buf++ = hotbitsByte();
    }
    
    i = iopl(0);    
    if (i < 0) {
        perror("setting iopl(0)");
        return 0;
    }
    munlockall();
    
    return len;
}
#endif
#endif

#ifdef TEST_PROGRAM
int main()
{
#ifdef GENERATOR
    unsigned long long dint;
    int i;
    
    printf("Initialising %s port at address %s (15 seconds)...\n",
#ifdef PARALLEL
        "parallel",
#else
        "serial",
#endif
        commbase
    );
    hotbitsInit();
    printf("Initialised.\n");
    int s = iopl(3);
    if (s < 0) {
        perror("setting iopl(3)");
        return 0;
    }
    s = mlockall(MCL_CURRENT | MCL_FUTURE);
    if (s < 0) {
        perror("locking memory");
        return 0;
    }

    for (i = 0; i < 1000000; i++) {
        dint = countInterval();
        printf("%d:  %lld\n", i, dint);
    }

    s = iopl(0);    
    if (s < 0) {
        perror("setting iopl(0)");
        return 0;
    }
    munlockall();
#endif
 
    return 0;
}
#endif

Download Driver Source Code Archive

Download Complete HotBits Server Source Code

HotBits Main Page

How HotBits Works

HotBits Hardware Description

Statistical Tests of HotBits Data


Valid XHTML 1.0
by John Walker
October, 2006