        TITLE   rngtest.asm
; To make:
; "MASM rngtest;"
; "LINK rngtest;"

.286

YES             equ     1
NO              equ     0
SPACE   	equ     32
TAB		equ     9
LF		equ	10
CR		equ	13

CARD_VECTOR	equ	0Dh		; 13
DATA_PORT	equ	300h
STATUS_PORT	equ	301h

EOI             equ     20h
IMR_8259	equ	21h
CARD_MASK	equ	0DFh		; 11011111

POPFF	MACRO
	jmp	$+3
	iret
	push	cs
	call	cs:$-2
	ENDM

DSKBUF	equ	32768

data    SEGMENT DWORD PUBLIC 'DATA'
        ASSUME  DS:data

useints		db	0		; 0 = polling, 1 = interrupts
copied		db	0		; Current number of buffers written
tocopy		db	32		; Number of buffers per output file

buffercomplete	db	1		; Int handler sets to 1 when done
bufptr		dw	0		; Used by int handler to index buffer
bytecount	dw	0		; If zero, int handler tosses data

filehandle1     dw      0
filename        db      65 dup (0)

header_msg      label   byte
    db "*--------------------------------------------------------*",CR,LF
    db "| RNGTEST                  Use /? for list of switches.  |",CR,LF
    db "| Copyright 2002 Aaron Logue - All rights reversed.      |",CR,LF
    db "| Creates a disk file of random data using the ISA       |",CR,LF
    db "| hardware random number generator adapter described at  |",CR,LF
    db "| http://www.cryogenius.com/hardware/isarng/             |",CR,LF
    db "*--------------------------------------------------------*",CR,LF
    db "$",0

no_file_msg	db "No output file specified.", "$", 0
creat_err_msg	db "Cannot open output file!", "$", 0
write_err_msg	db "Error writing output file.", "$", 0
installed_msg	db "Using interrupt handler.", CR,LF,"$", 0

switch_desc db "Usage:  RNGTEST [switches] FILENAME",CR,LF
            db "The following switches can be used:",CR,LF,LF
	    db "/I  - Enable card interrupts (default is polling)",CR,LF
	    db "/S  - Sample (32K) output file (default is 1M)",CR,LF
	    db "/M  - Medium (2M) output file",CR,LF
	    db "/L  - Large (4M) output file",CR,LF
            db "/?  - Display this list"
crlf_msg    db CR,LF
            db '$'

disk_buffer     db      32768 dup (?)   ; Disk buffer for reading and writing

data    ENDS

code    SEGMENT DWORD PUBLIC
        ASSUME  CS:code

begin:
        jmp     main    		; 3 bytes
filler		db	?		; 1 byte
old_vector	dd  	?		; old interrupt vector
psp             dw      ?               ; our prefix
our_ds		dw	?

;-----------------------------
;  interrupt handler
;
;  When bytecount drops to zero, this handler returns without reading
;  the card.  This shuts off the interrupt stream until the mainline
;  code "primes" the pump by reading the byte that we skipped.
;-----------------------------
int_handler proc far
        pushf
        sti
	push	ds
	push	ax
	push	bx
	push	dx

	mov	ax, cs:[our_ds]		; Find our data	segment
	mov	ds, ax

	mov	al, [buffercomplete]
	or	al, al
	jnz	ih99			; throw it away

	mov	bx, [bufptr]
	mov	dx, DATA_PORT
	in	al, dx			; read the byte
	mov	ds:[bx], al

	inc	word ptr [bufptr]
	dec	word ptr [bytecount]
	mov	ax, [bytecount]
	or	ax, ax
	jnz	ih99
	mov	byte ptr [buffercomplete], 1

ih99:
	mov	al, EOI
	out	20h, al			; End Of Interrupt to 8259 controller
	pop	dx
	pop	bx
	pop	ax
	pop	ds
        popff
        iret
int_handler endp


;-----------------------------
;  main
;-----------------------------
main_100:
	jmp	main100
main proc near
        call    initialize
        jc      main_100                ; error during initialization, dx set

	;
	;--- If interrupts on, install an interrupt handler
	;
	cmp	byte ptr [useints], 0
	je	main10

	cli
	mov	bx, CARD_VECTOR
	mov	dx, cs
	mov	ax, offset int_handler
	call	set_interrupt
	mov	word ptr cs:old_vector[0],ax
	mov	word ptr cs:old_vector[2],dx
        mov     dx, IMR_8259
        in      al, dx                  ; read 8259's interrupt mask register
        and     al, CARD_MASK           ; allow our card's interrupts
        out     dx, al                  ; modify interrupt mask register
	sti

	mov	dx, DATA_PORT
	mov	al, 1
	out	dx, al			; Enable card interrupts

	mov	dx, offset installed_msg
	mov	ah, 9
	int	21h

	;
	;--- Open an output file
	;
main10:
	mov	dx, offset filename
        mov     ax, 3C00H		; DS:DX points at file to create
	xor	cx, cx
	int	21h			; open new output file
	mov	dx, offset creat_err_msg
	jc	main_100
        mov     [filehandle1], ax	; Save output file handle

	;
	;--- Read a buffer
	;
main20:
	cmp	byte ptr [useints], 0
	je	main40
	;
	; load disk_buffer with DSKBUF values using interrupt handler
	;
	mov	[bufptr], offset disk_buffer
	mov	[bytecount], DSKBUF
	mov	[buffercomplete], 0	; Tell handler to fill another buffer

	mov	dx, DATA_PORT
	in	al, dx			; Prime interrupt pump
main30:
	mov	al, [buffercomplete]
	or	al, al
	jz	main30
	jmp	main50

main40:
	;
	; load disk_buffer with DSKBUF random values using polling
	;
	call	fill_disk_buffer
main50:

	;
	;--- Write buffer to output file
	;
	mov	cx, DSKBUF		; Number of bytes read
	mov	ah, 40h                 ; Write to output file
	mov	bx, [filehandle1]
        mov     dx, offset disk_buffer
        int     21h
	mov	dx, offset write_err_msg
	jc	main100
	;
	;--- check to see if we've processed the requisite number of buffers
	;
	mov	al, [copied]
	inc	al
	mov	[copied], al
	cmp	al, [tocopy]
	jnz	main20
	mov	[copied], 0

        mov     bx, [filehandle1]       ; Close output file
        mov     ax, 3E00H
        int     21H

	xor	al, al			; Zero error-code

	;
	;--- If interrupts on, deinstall our interrupt handler
	;
	cmp	byte ptr [useints], 0
	je	main99

	mov	dx, DATA_PORT
	mov	al, 0
	out	dx, al			; Disable card interrupts

	cli
        mov     dx, IMR_8259
        in      al, dx                  ; read 8259's interrupt mask register
        mov     ah, CARD_MASK
        not     ah
        or      al, ah                  ; disallow our interrupts
        out     dx, al                  ; modify interrupt mask register
	mov	bx, CARD_VECTOR
	mov	ax, word ptr cs:old_vector[0]
	mov	dx, word ptr cs:old_vector[2]
	call	set_interrupt
	sti

main99:
        mov     ah, 4Ch
        int     21H                     ; return to DOS
main100:
        mov     ah, 9			; print message
        int     21H
        mov     al, 1			; Return error code
        jmp     main99
main endp


;--------------------------------------------------------------------------

;-------------------
; fill_disk_buffer
; Poll ISA RNG card for a bunch of data
;-------------------
fill_disk_buffer proc near
	push	di
	cld
	mov	di, offset disk_buffer
	mov	cx, DSKBUF
fdb10:

	mov	dx, STATUS_PORT		; Poll for a byte
fdb20:
	in	al, dx
	and	al, 1			; Is there data yet?
	jz	fdb20			;   no - keep polling

	mov	dx, DATA_PORT
	in	al, dx			; Read a byte of randomness
	stosb				; Put it in the buffer

	dec	cx
	or	cx, cx			; Is the buffer full?
	jnz	fdb10			;   no - keep reading

fdb99:
	pop	di
	ret
fill_disk_buffer endp


;==========================================================================

;-------------------
; initialize
;       CY is set and DX points to error message if error encountered
;-------------------
initialize proc near
        mov     cs:[psp], ds
	call	read_switches		; Get command line switches
        mov     ax, seg data
        mov     ds, ax                  ; point DS at the right stuff
        mov     es, ax
	mov	cs:[our_ds], ax
;
;--- Display header
;
        mov     dx, offset header_msg
        mov     ah, 09H
        int     21H
;
;--- Check for no output filename
;
	cmp	byte ptr [filename], 0
	jz	ini100
ini99:
        ret
ini100:
	stc
	mov	dx, offset no_file_msg
	jmp	ini99
initialize endp


;-------------------
;  read_switches
;	Reads switches directly out of the PSP
;	Registers clobbered
;-------------------
switch_jump_table	label	word
		dw	offset	rs_A
		dw	offset	rs_B
		dw	offset	rs_C
		dw	offset	rs_D
		dw	offset	rs_E
		dw	offset	rs_F
		dw	offset	rs_G
		dw	offset	rs_H
		dw	offset	rs_I
		dw	offset	rs_J
		dw	offset	rs_K
		dw	offset	rs_L
		dw	offset	rs_M
		dw	offset	rs_N
		dw	offset	rs_O
		dw	offset	rs_P
		dw	offset	rs_Q
		dw	offset	rs_R
		dw	offset	rs_S
		dw	offset	rs_T
		dw	offset	rs_U
		dw	offset	rs_V
		dw	offset	rs_W
		dw	offset	rs_X
		dw	offset	rs_Y
		dw	offset	rs_Z
read_switches proc near
	call	push_regs
	mov	cx, 81h
	mov	si, cx
	add	cl, ds:[80H]		; cx = length of command tail
	adc	ch, ch
rs10:
        mov     ds, cs:[psp]            ; make ds point at psp
	call	skip_white_space	; scan for /
	cmp	si, cx			; are we done?
	ja	rs99
	cmp	al, "?"			; Was it help switch?
	je	rs_bag_it
	cmp	al, "/"			; Is it a switch?
	je	rs30			;   yes - go process it
        ;
        ;--- assume it's a filename
        ;
        ; make es point at data segment
        mov     bx, seg data
        mov     es, bx
        mov     di, offset filename
        stosb
rs25:
        lodsb
        cmp     al, CR
        je      rs26
        cmp     al, SPACE
        je      rs26
        cmp     al, TAB
        je      rs26
        stosb
        or      al, al
        jnz     rs25
rs26:
        xor     al, al
        stosb
	jmp short rs10                  ; Go process more switches
;
;--- Process a switch
;
rs30:
	call	skip_white_space
	cmp	si, cx
	ja	rs99
	and	al, 11011111b		; Upshift it
	cmp	al, "A"
	jb	rs_bag_it
	cmp	al, "Z"
	ja	rs_bag_it
	sub	al, "A"
	cbw				; let AH = 0
	shl	al, 1
	mov	bx, ax
        mov     ax, seg data            ; make ds point at data segment
        mov     ds, ax
	jmp	cs:switch_jump_table[bx]
rs99:
        mov     ax, seg data
        mov     ds, ax
	jmp	pop_regs
;--------------------------------------------------------------------------
; Display list of available switches and quit.
;--------------------------------------------------------------------------
rs_A:
rs_B:
rs_C:
rs_D:
rs_E:
rs_F:
rs_G:
rs_H:
rs_J:
rs_K:
rs_N:
rs_O:
rs_P:
rs_Q:
rs_R:
rs_T:
rs_U:
rs_V:
rs_W:
rs_X:
rs_Y:
rs_Z:
rs_bag_it:
        mov     ax, seg data
        mov     ds, ax
	mov	dx, offset switch_desc
	mov	ah, 9
	int	21H
	mov	ax, 4C00H
	int	21H			; exit to dos
rs_I:
	mov	[useints], 1		; Use interrupts
	jmp	rs10
rs_S:
        mov	[tocopy], 1		; Number of buffers for 32K file
        jmp     rs10
rs_M:
        mov	[tocopy], 64		; Number of buffers for 2M file
        jmp     rs10
rs_L:
        mov	[tocopy], 128		; Number of buffers for 4M file
        jmp     rs10
read_switches endp


;-------------------------------------------------------------
;  pop_regs
;       Restores all registers on the stack, inverse of save_env
;       however, this version is jumped to -- ie no ret addr on stack
;       Input   none
;       Output  ax,bp,bx,cx,dx,si,di,es
;       All other registers unaffected
;-------------------------------------------------------------
pop_regs proc near
        pop     es
        pop     di
        pop     si
        pop     dx
        pop     cx
        pop     bx
        pop     bp
        pop     ax
        ret
pop_regs endp


;-------------------------------------------------------------
;  push_regs
;       Saves all registers on the stack, inverse of restore_env
;       Input   ax,bp,bx,cx,dx,si,di,es
;       Output  none
;       All other registers unaffected
;-------------------------------------------------------------
push_regs proc near
        push    bp
        mov     bp,sp
        push    bx
        push    cx
        push    dx
        push    si
        push    di
        push    es
        push    2[bp]                   ; push return address
        mov     2[bp],ax                ; save ax
        mov     bp,[bp]                 ; reset bp
        ret
push_regs endp


;--------------------------------------------------
;  skip_white_space
;       Input   SI - Points at string to be scanned.
;       Output  SI - Points just past 1st non blank char.
;               AL - 1st non blank char.
;               CLD
;       All other registers are unaffected
;--------------------------------------------------
skip_white_space proc near
        cld
sws10:
        lodsb
        cmp     al, SPACE
        je      sws10
        cmp     al, TAB
        je      sws10
        ret
skip_white_space endp

;----------------
;  set_interrupt
;	Sets specified interrupt to specified routine
;	Input	bx - Interrupt vector number
;		ax - Interrupt routine CS relative address
;		dx - Interrupt routine CS segment value
;	Output	ax - Old Interrupt routine CS relative address
;		dx - Old Interrupt routine CS segment value
;	Other Registers are unaffected
;----------------
set_interrupt proc near
	push	bx
	push	cx
	push	ds
	xor	cx,cx
	mov	ds,cx		; make ds=0
	shl	bx,1
	shl	bx,1
	pushf
	cli
	xchg	ax,ds:word ptr [bx]
	xchg	dx,ds:word ptr [bx+2]
	POPFF
	pop	ds
	pop	cx
	pop	bx
	ret
set_interrupt endp

code    ENDS

stack   SEGMENT STACK 'stack'
        dw      512     dup (?)
stack   ENDS

        END     begin
