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
|
|