; PARKERN.ASM
;
; Lowlevel routines for PARnet
; 03-JUN-93 First PC version <S.A.Pechler@bdk.tue.nl>
; 03-JUL-93 Fixed bug when transferring odd number of bytes.
;           Optimized by putting some counters in registers   <S.A.Pechler>
; 07-JUL-93 Bugfix: ES was not set when reading into overflow buffer.
;           Deleted leading underscores from all data labels.
;           Added ParReadV and ParWriteV functions.           <S.A.Pechler>
; 11-AUG-93 Bugfix: When an error occured in parread(), the errornumber was
;	    set in the wrong register (was BX, had to be AX). <S.A.Pechler>
; 01-SEP-93 Optimized the functions by using block move operations (STOSB).
;           Changed the register usage of read_data() and write_data().
; 02-SEP-93 Rewrote all private routines for usage without the special
;           PARnet interface (connections between 2 machines only).
;
;
;   PARALLEL PORT NETWORK LOW LEVEL ROUTINES
;
;   THE CABLE:
;                  (2-9)   D7-D0  ----------  D7-D0 (2-9)
;        PC        ( 1)    STROBE ----------  BUSY  (11)      AMIGA
;     PARALLEL  <= (14)    AUTOLF ----------  POUT  (12)  => PARALLEL
;       CARD       (16)    INIT   --+----+--  SEL   (13)       PORT
;                  (10)    ACK    -/      \-  ACK   (10)
;                  (18-22) GND    ----------  GND   (18-22)
;
;      Cable connections between PC and Amiga WITHOUT(!) an interface
;
;
;
;                 (2-9)   D7-D0  ----------  D7-D0  (2-9)
;        PC        ( 1)    STROBE ----------  STROBE ( 1)        PC
;     PARALLEL  <= (14)    AUTOLF ----------  AUTOLF (14)  => PARALLEL
;       CARD       (16)    INIT   --+----+--  INIT   (16)       CARD
;                  (10)    ACK    -/      \-  ACK    (10)
;                  (18-22) GND    ----------  GND    (18-22)
;
;       Cable connections between 2 PC's WITHOUT(!) an interface
;
;
;   Parallel port usage:
;
;   DATA PORT   : used for data input & output
;   CONTROL PORT: used for control input & output
;
;   All lines pulled up.  Thus, asserted state is a 0.	Idle
;   state is an undriven (1).  Protocol transfers a byte at
;   a time.  Protocol is ethernet style with a small window
;   of error in the line aquisition routine.
;
;   Note:   Timeouts should be set around a second.  Ideally
;	    defaults should be fixed on faster machines.
;
;   LINE description:
;
;   PARnet: Cable: PC:      Description:
;   ---------------------------------------------------------
;   ~ACK    BUSY   STROBE   hand shake
;   ~REQ    POUT   AUTOLF   hand shake
;    CTL    SEL    INIT     1 = special byte, 0 = data byte.
;			    (for address mark, valid when ~REQ
;			     goes low.	For EOP mark, sample
;			     when ~REQ goes high)
;
;   See the associated routines for the exact low-level protocols.
;
;
;   PROTOCOL DESCRIPTION:
;
;   HandShake	(Reader <- Writer transfer).  A Handshake
;	    sequence transfers TWO bytes of information.
;
;		WRITER			    READER
;	|-> place data, ~REQ->0
;	|				 wait for ~REQ->0
;	|				 read data & store
;	|				 set ~ACK->0
;	|   wait ~ACK->0
;	|   place data, ~REQ->1
;	|				 wait for ~REQ->1
;	|				 read data & store
;	|				 set ~ACK->1
;	|   wait ~ACK->1
;	|<- LOOP  (2 bytes written)      LOOP  (2 bytes read)
;
;
;   Read:   (1) Determine if your machine is being addressed
;		(~REQ=0, data=myaddress, CTL=1)
;	    (1) set ~ACK to output and
;	    (2) Handshake sequence for the address mark, only
;		first byte valid.
;	    (3) Handshake sequence for data util rcv byte
;		with CTL = 1 (EOP), byte must == 0.
;	    (4) Set ~ACK to input
;
;   Write:  (1) AQUIRE THE NETWORK (see below)
;		involves gaining control and then setting the
;		CTL and ~REQ to outputs.
;
;		Also checks if somebody is writing to us,
;		in which case -2 is returned instantaniously
;		indicating we should do a ParRead().
;
;	    (2) Handshake sequence for address mark, send dest
;		address (second byte garbage).  Note that CTL->1
;		*BEFORE* we set ~REQ->0
;	    (3) Handshake sequence for data bytes
;	    (4) Handshake sequence for EOP mark (Note that CTL->1
;		*AFTER* we get the ~ACK->1 and before release
;		~REQ (->1).  Only firstbyte valid and set to 0.
;
;	    (5) Set ~ACK and ~CTL to inputs
;
;   AQUIRE: Line aquisition prevents two people from writing to
;	    the net at the same time.
;
;	    * A line is considered aquired if ANY of the 3
;	      control lines is 0.
;	    * If network is not aquired:
;		- set ~ACK to output and 1
;		- bclr ~ACK to 0 and bne success
;		  (else set ~ACK to input and try again)
;		- set data lines to output and place my address
;		  (Must ]be done before CTL is glitched)
;		- set ~CTL to output and 1
;		- set CTL to 0 and then 1 (glitch it) to cause
;		  FLAG interrupt on all other machines
;		- set ~REQ to output and 0 (beginning of handshake
;		  sequence)
;		- lastly, release ~ACK by setting it to an input
;
;	      Note that at all times at least one line is a 0
;	      so no other machine will attempt to aquire the net.
;
;   Note that the destination address is placed on the data
;   lines after we have aquired the line but before we glitch
;   the CTL line to cause an interrupt.  This allows the
;   other machines to instantaniously determine who is being
;   addressed.
;
;   Note that the CTL line gets glitched at the end of a packet
;   too for the EOP mark.  In this case there is a 0 on the
;   data lines so while an interrupt is generated, nobody
;   thinks they are being addressed.
;
;
;   ASSEMBLING/COMPILING the code:
;
;   When assembling, use the following flags (turbo-assembler V2.0):
;
;   tasm -mx -t -DMEMMOD=large parkern.asm
;   option -zi adds full debug information.
;
;   When including the routines in C, use the following prototypes:
;
;   typedef unsigned short int16;
;   extern void paraddress(int16,int16);
;   extern int pardataready(void);
;   extern int parreadV();
;   extern int parread(unsigned char *,int16);
;   extern int parwriteV();
;   extern int parwrite(int16, unsigned char *,int16);
;

	.MODEL MEMMOD,C
	LOCALS
	%MACS
	.LALL

	.DATA
ParNetAddr	db 1		; my PARnet address (default)
LPTData		dw 0378h	; LPT data port address (default)
LPTStatus	dw 0379h	; LPT status port address (default)
LPTControl	dw 037ah	; LPT control port address (default)

ParLLTimeout	dw 65535
	;should be dl 983025	; default timeout value (about 1 second?)
DestAddress	db 5		; Destination address for write
Dummybuf	db 0		; dummy buffer
		db 0		; dummy buffer
		db 0,0		; padding
NextRVector     db 0            ; Flag: vector to next read buffer present
NextWVector	db 0		; Flag: vector to next write buffer present

	.CODE
        PUBLIC paraddress,pardataready,parreadV,parread,parwrite,parwriteV

;============================================================================
;Private Routines
;
;Don't use these in any program.


;----------------------------------------------------------------------------
; Stable
;
; Put the interface in a stable mode by setting the data and control lines
; to high.
;
stable:
	push ax
	push dx
	mov dx,LPTData	  ;data register
	mov al,-1	  ;datalines high
	out dx,al	  ;set it
	mov dx,LPTControl ;Control register
	mov al,14h	  ;all outputs to 1 (INIT is not inverted!), enable IRQ7
	out dx,al	  ;write control
	pop dx
	pop ax
	ret

;----------------------------------------------------------------------------
;Data Input
;
; Set the datalines to 'input' by setting all lines to high
;
data_input:
	push ax
	push dx
	mov dx,LPTData	;data register
	mov al,-1	;datalines high
	out dx,al	;set it
	pop dx
	pop ax
	ret

;----------------------------------------------------------------------------
; Control Input
;
; Set the control-lines to 'input' by setting all lines to high
;
;control_input:
;	push ax
;	push dx
;	mov dx,LPTControl ;Control register
;	mov al,14h	  ;all outputs to 1 (INIT is not inverted!), enable IRQ7
;	out dx,al	  ;set it
;	pop dx
;	pop ax
;	ret

;----------------------------------------------------------------------------
; Read Data
;
; read data from the cable, put it in AL
;
read_data:		;data line must be set to INPUT first!
	push dx
	mov dx,LPTData	;data register
	in al,dx	;read it
	pop dx
	ret

;----------------------------------------------------------------------------
; Read Control
;
; Read the controlbits from the cable (busy, pout & sel)
;
; BL: control read from control register, represents the REAL line
;     status (so if a line is +5V, then the bit is 1).
;
; BL bits: 7654 3210  centronics:    cable:  parnet:
; value:   XXXX X100 
;          |||| |||`- strobe          busy    ack
;          |||| ||`-- auto LF         pout    req
;          |||| |`--- init            sel     ctl
;          |||| `---- selin            -       -
;          ````------ invalid          -       -
;
read_control:			;control lines must be set to input first!
	push ax
	push dx
	mov dx,LPTControl	;Control register
	in al,dx		;read it
	xor al,0bh		;invert all but init
	and al,07		;discard bits not needed
	mov bl,al		;return value in BL
	pop dx
	pop ax
	ret			;ready, controlbits in BL

;-----------------------------------------------------------------------------
;Clear ACK Only
;
; Clear the parnet ack-bit. Leave other control lines high.
; Warning: I can't read my own control-lines back!
;
clear_ack_only:
	push bx
	mov bl,0feh		; set parnet ack-bit to 0
	call write_control	; write control
	pop bx
	ret

;----------------------------------------------------------------------------
;Set ACK All
;
; Set the parnet ack-bit. Leave other control lines high.
; Warning: I can't read my own control-lines back!
;
Set_ack_all:
	push bx
	mov bl,0fh		; set all bits to 1 (including ACK)
	call write_control	; write control
	pop bx
	ret

;----------------------------------------------------------------------------
; Write control
;
; BL: controlbits to be written
;
; BL bits: 7654 3210  centronics
; value:   XXXX X100  keyword:   cable:  controlbits:
;          |||| |||`- strobe      busy      ack
;          |||| ||`-- auto LF     pout      req
;          |||| |`--- init        sel       ctl
;          |||| `---- selin        -         -
;          ````------ invalid      -         -
;
; The value in BL represents the REAL LINE STATUS, so a bit=0 means the
; line is on low voltage.
;
; The INIT output is on the parallel card not inverted. This bit will be
; inverted in this procedure, so you don't have to care about it.
;
write_control:
	push ax
	push dx
	mov dx,LPTControl ; control register
	mov al,bl	  ; move control bits to AL (for OUT-instruction)
	xor al,0bh	  ; invert all lines except for INIT (=ctl)
	out dx,al	  ; write control
	pop dx
	pop ax
	ret

;----------------------------------------------------------------------------
; Write DATA
;
; Put Data on the line.
;
; AL: data to be written.
;
write_data:			; interface must be in stable mode!
	push dx
	mov dx,LPTData		; data register
	out dx,al		; put data on line
	pop dx
	ret

;============================================================================
;Public routines


; (void) paraddress(int16 myaddr,int16 LPTAddress)
;
; Set my ParNet address (1-254) and LPT port address (0378h,03bch or 0278h)
;
; ParNet addresses 0 and 255 are reserved!
;
paraddress	PROC
		ARG myad:word,lptad:word

	mov ax,myad		; my parnet address
	mov ParNetAddr,al	; store address
	mov ax,lptad		; LPT port address
	mov LPTData,ax		; Place data port address
	inc ax			; next register is status port
	mov LPTStatus,ax
	inc ax			; next register is control port
	mov LPTControl,ax
	call stable		; all lines high
	ret

paraddress     ENDP

; int = pardataready(void)
;
; Check for data present (e.g. after an IRQ7).
;
; Returns:  1 if packet is probably pending for you
;           0 if line is currently idle
;          -1 if packet isn't for you
;
; If line has been aquired but no control address has been
; put on it yet, pardataready() will wait for a control
; address.  Thus, after a signal, a single call to
; pardataready() should suffice.
;
pardataready	PROC
	call stable		; be sure all lines are set to input
.pdstable:
	call read_control	; read control in BL
	mov cl,bl		; save it
	call read_data		; read data in AL
	call read_control	; read control in BL
	cmp cl,bl
	jne .pdstable

	;   Now, pardataready might be called after the sending machine
	;   has aquired but before it can assert REQ.  However, the
	;   sending machine has already (guarenteed) placed its address
	;   on the data port.  So while the address matches, loop while
	;   REQ not asserted.

	test bl,02		; ~REQ asserted?
	jz .pd10		; yes
	cmp  [ParNetAddr],al	; no, does data match anyway?
	je .pdstable		; YES, loop until get ~REQ or
	jmp short .pdfail	; data bad.

.pd10:
	test bl,04		; ~REQ is asserted, CTL=1 ?
	jz .pdrn		; no, middle of some packet
	cmp [ParNetAddr],al	; yes, my address?
	jne .pdrn		; nope
	mov ax,1		; yes, packet (probably) for us
	jmp short .pdend

.pdfail:
	test bl,04		; fail due to ~REQ not asserted
	jz .pdrn		; CTL=0, line busy
	xor ax,ax		; line idle.
	jmp short .pdend

.pdrn:
	mov ax,-1		; line busy, packet not for me
.pdend:
	ret

pardataready	ENDP

;n = parreadV(unsigned char *buf,int16 bytes, buf2,bytes2, ..., NULL, NULL)
;n = parread(unsigned char *buf,int16 bytes)
;
;Read a pending packet.
;
;Returns: n = -1 (1 second timeout, no packet pending)
; 	  n = 0 to bytes-1 (1 second timeout after transmission interrupted)
;	  n = bytes (success), or n > bytes (transmitting machine's packet
;	      was larger than we can handle, extra bytes thrown out)
;
;NOTE:	Requesting an odd number of bytes is O.K. but if you request N where
;	N is odd and the writer sends N + 1 you will never know (N will be
;	returned). See also parwrite() below.
;

parreadV PROC
	 ARG buf:ptr, byts:word, nextbuf: ptr, nextbyts:word

         mov al,1
         mov NextRVector,al      ; secondary buffer(s) present
         jmp short parRbegin

parreadV ENDP

parread	PROC
        ARG buf:ptr, byts:word, nbuf:ptr, nextbyts:word
                                ; ^these fake params are needed for reference.
        mov al,0                ; no secondary buffer(s)
        mov NextRVector,al

parRbegin:
	if	@Datasize NE 0
                push ds         ; the 'USES' macro doesn't work here
                push es
		push si
		push di
                les     di,buf  ; es:di = buf, my data segment needs to
                                ; stay intact.
	else
                push ds
                push es
		push si
		push di
                mov ax,ds
                mov es,ax       ; to simulate a mov es,ds
                mov di,buf      ; es:di = buf (ds already set)
	endif

	cld			; all moves will be forward
	mov si,byts		; maximum number of bytes to read
	call stable		; ensure line not asserted
	mov DX,[ParLLTimeout]	; DX = timeout load

	;  Wait loop for address mark
	;  Ctl = 1, ~DReq = 0
.rmstab:
	call read_control	; read control in BL
	mov cl,bl		; save it
	call read_data		; read data in AL
	call read_control	; read control in BL
	cmp cl,bl		; control lines stable?
	jne .rmstab		; nope
	test bl,04		; expect CTL=1
	jz .rms1		; nope
	test bl,02		; expect ~REQ=0
	jz .rms2		; yes

.rms1:
	dec dx			; decrement timeout count
	jnz .rmstab		; no timeout.
	mov ax,-1		; return value: read timeout
	jmp .rmend		; no address mark!

.rms2:
	cmp [ParNetAddr],al	; My address?
	jne .rms1		; no, timeout loop

	; Got my address, ~Ack byte.

	mov DX,[ParLLTimeout]	; reset timeout
	call Clear_Ack_Only	; set ~Ack to 0

.rms4:  call read_control	; get control
	test bl,02		; wait for ~REQ to go away
	jnz .rms5
	dec dx			; decrement timeout count
	jnz .rms4
	mov ax,-2		;~REQ not released?
	jmp .rmend

.rms5:  call Set_Ack_All	; release ~ACK

	mov cx,0		; set # of bytes read to 0
	jmp .rms10		; skip past move


	; MAIN READ LOOP
	;
	; cx holds count (number of bytes read).
	; DI buffer pointer (place to store data).

.rms10loop:
;	mov es:[di],al		; store data
;	inc di			; next address
	stosb

.rms10:
	call Read_Control
	test bl,02		; wait for ~REQ asserted
	jz .rms20
	call Read_control
	test bl,02		; again
	jz .rms20
	mov DX,[ParLLTimeout]	; reset timeout

.rms11: call Read_Control
	test bl,02		; wait for ~REQ asserted with timeout
	jz .rms20
	dec dx			; decrement timeout count
	jnz .rms11
	mov ax,-1
	jmp short .rmend	; timeout

.rms20: call read_data		; get data in al and
	call Clear_Ack_Only	; assert ~ACK

	; note on CTL = 1 end sequence this data item is a dummy

;	mov es:[di],al          ; store data
;	inc di			; next address
	stosb

	inc cx			; optimized, but not quite true,
	inc cx			; we've only written one 1 sf

	call Read_Control
	test bl,02		; wait for ~REQ released
	jnz .rms30
	call Read_Control
	test bl,02		; again
	jnz .rms30
	mov DX,[ParLLTimeout]	; reset timeout
.rms21:
	call Read_Control
	test bl,02		; wait for ~REQ released with timeout
	jnz .rms30
	dec dx			; decrement timeout count
	jnz .rms21		; no timeout yet?
	jmp short .rmendsub	; sub because CX is 2 ahead


.rms30:
	call read_data		; get data in al
	call Read_Control	; get CTL status in BL
	call Set_Ack_All	; release ~ACK	
	test bl,04		; EOP if CTL=1
	jnz .rmeop

	; CANNOT STORE DATA HERE! In case odd # bytes requested,
	; second byte would overflow buffer (each handshake sequence ALWAYS
	; transfers 2 bytes of information).

	dec  si			; # of bytes remaining
	jz   .rmodd		; already zero, # of bytes were odd
	dec  si

	jz  .rmste		; reached zero (even bytes)
	jmp .rms10loop		; continue if >0.

.rmodd: dec si			; si must be -1 when odd # bytes requested
	dec cx			; fixup count (was one ahead)
	jmp short .rmeven

.rmste:
        mov es:[di],al		; if si = 0, its's even and we
				; should store the last byte.
.rmeven:
        cmp NextRVector,0        ; if next buffer does not exist
        je  .rmovflow           ; overflow
.rmsev0:
        cmp WORD PTR nbuf,0     ; next buffer points to somewhere?
       if @Datasize NE 0
        jne .rmsev1
        cmp WORD PTR nbuf+2,0   ; check also the segment
       else
        je .rmovflow
       endif
.rmsev1:
       mov si,nextbyts          ; length of next buffer
       if @Datasize NE 0
        les di,nbuf             ; load pointer to next buffer
        add bp,6                ; this is VERY tricky  = sizeof(nbuf+nbyts)
       else
        mov di,nbuf             ; load pointer to next buffer
        add bp,4                ; this one is also VERY tricky
       endif
        or si,si                ; size of this buffer = 0 ?
        jz .rmsev0              ; yes, then check for next buffer
        jmp .rms10              ; proceed reading

.rmovflow:
        if @Datasize NE 0
         mov di,ds
         mov es,di              ; to simulate move es,ds
        endif

	mov di,OFFSET Dummybuf	; overflow, use dummy buffer

	jmp .rms10

.rmeop:
	cmp al,0		; EOP data better be 0!
	je .rmendsub
	mov ax,-3		; bad protocol
	jmp short .rmend

.rmendsub:
	dec cx			; because we were two ahead
	dec cx
        mov ax,cx		; return value in AX
.rmend:
	call data_input
	call Set_Ack_All	; setting ~ACK to input
	call stable
	pop di
	pop si
        pop es                  ; normally the 'USES' macro inserts these
        pop ds
	ret			; return value in AX

parread	ENDP

;n = parwriteV(int 16 destaddr, unsigned char *buf, int16 bytes,
;              buf2, bytes2, ..., NULL, NULL)
;n = parwrite(int16 destaddr, unsigned char *buf, int16 bytes)
;
;Write a packet.
;
;Returns: n = -2	Cannot write anything, a packet is pending
;			(instantanious)
;	  n = -1	Destination machine does not respond (1 sec timeout)
;	  n = N		N bytes written ok (success if n == bytes)
;
; NOTE: sending an odd number of bytes is O.K. but if you write N where
;	N is odd and the reader requests N + 1 he will get N + 1 the last
;	byte being garbage.
;
parwriteV PROC
	  ARG dest:word, buf:ptr, byts:word, nextbuf:ptr, nextbyts:word

	  mov al,1
	  mov NextWVector,al
	  jmp short parWbegin

parwriteV ENDP

parwrite  PROC
	  ARG dest:word, buf:ptr, byts:word, nbuf:ptr, nextbyts:word
				;	     ^these fake params are needed for
				;	      reference.
	  mov al,0
	  mov NextWVector,al	; no secondary buffer(s)

parWbegin:
	if @Datasize NE 0
		push ds		; the 'USES' macro doesn't work here
		push es
		push si
		push di
                les si,buf      ; es:si = source-buffer to be written
	else
                push ds
		push es
		push si
		push di
                mov ax,ds
                mov es,ax       ; to simulate a mov es,ds
                mov si,buf      ; es:si = buf (ds already set)
	endif

	cld			; all moves will be forward
	mov ax,dest		; destination address
	mov DestAddress,al	; can't do a direct move to DestAddress
	mov di,byts		; number of bytes to write


	call stable		; ensure line not asserted
	mov DX,[ParLLTimeout]	; DX = timeout load

.wmstab:
	cli			; disable interrupts
	call Read_Control
	mov cl,bl
	call read_data	
	call Read_Control	; get stable control
	cmp cl,bl
	je .wmstab1
	sti			; set interrupts
	jmp short .wmstab

	; Interrupts still disabled
	; BL holds ~ACK ~REQ and CTL status

.wmstab1:
	mov ax,-2		; number of bytes written yet (=none)
	cmp bl,07h		; ~ACK=1, ~REQ=1, CTL=1 ?
	je .wm02

	; if CTL=1, ~REQ=0 and AL=my address then
	; return with -2

	test bl,02h		; ~REQ = 0?
	jne .wm01		; nope
	test bl,04h		; CTL = 1?
	je .wm01		; nope
	cmp [ParNetAddr],al	; somebody is calling me?
	jne .wm01		; nope

	sti			; enable interrupts
	jmp .wmend

.wm01:
	sti			; enable interrupts
	dec dx			; decrement timeout count
	jnz .wmstab
	jmp .wmend

	; interrupts still disabled
	; we almost own the line

.wm02:
	call Read_Control
	call Clear_Ack_Only	; assert ~ACK
	test bl,01
	jne .wm05		; ACK was released before, have line!

	call Set_Ack_All	; release ~ACK
	jmp short .wm01

	; Line now aquired.

.wm05:
	sti			; enable interrupts
	mov al,DestAddress
	call Write_Data		; set destination address on datalines

	; Before asserting ~REQ, pulse CTL to cause interrupt on remote
	; machines. Note that our address is already on the datalines.

	mov bl,02h		; leave ~ACK=0 and ~REQ=1, but CTL->0
	call Write_Control
	mov bl,06h		; leave ~ACK=0 and ~REQ=1, but CTL->1
	call Write_Control
	mov bl,04h		; leave ~ACK=0 and CTL=1, but ~REQ->0  
	call Write_Control	; assert REQ
	mov bl,05h		; leave CTL=1 and ~REQ=0, but ~ACK->1
	call Write_Control	; release ACK
				; (note that REQ->0 before ACK->released)

	mov ax,-1		; number of bytes written yet (none)

	; interrupts enabled for transfer (fully handshaked)
	;
	; Address mark ~ACK, wait for ~ACK asserted.
.wm10:
	call Read_Control
	test bl,01		; ~ACK asserted?
	jz .wm15		; yes, remote machine got my address mark
	mov DX,[ParLLTimeout]	; DX = timeout count
.wm11:
	call Read_Control
	test bl,01
	jz .wm15
	dec dx			; decrement timeout count
	jnz .wm11		; timeout?
	jmp .wmend		; yes.

	; got ack, now set CTL = 0 (leaves at least one line 0 so
	; nobody else thinks the bus is idle!)
	;
	; note:  Since this is the address mark, and is sampled by
	;	 the reader before it asserts ~ACK, I can set CTL
	;	 = 0 now instead of waiting till after ~ACK is
	;	 released.

.wm15:
	mov bl,01h		; leave ACK=1, REQ=0, but CTL->0
	call Write_Control	; set CTL = 0 for duration of packet
	mov bl,03h		; leave ACK=1, CTL=0, but REQ->1
	call Write_Control	; release ~REQ

	mov cx,0		; number of bytes written (none)

	; Data transfer loop
	;
	; wait for ~ACK to be released (-> 1). If no more bytes to
	; write, then skip to .wm50

.wm20:
	or  di,di		; more data in this buffer?
	jz .wm50		; nope

	call Read_Control
	test bl,01		; wait for ~ACK to be released
	jnz .wm30
	mov DX,[ParLLTimeout]	; DX = timeout countdown
.wm21:
	call Read_Control
	test bl,01
	jnz .wm30		; need the timeout here?
	call Read_control
	test bl,01		; check ACK again
	jnz .wm30
	dec dx
	jnz .wm21
	mov ax,cx		; return value in AX
	jmp .wmend		; timeout

	; Assert ~REQ for this data byte and wait for ~ACK

.wm30:
        mov al,es:[si]		; get next data byte.
	inc si			; this can't be done with a LODSB :-(

	call Write_Data		; store data and ..
	mov bl,01h		; leave ACK=1, CTL=0, but REQ->0
	call Write_Control	; .. assert ~REQ

        mov al,es:[si]          ; get next data bytes
	inc si			; this can't be done with a LODSB :-(

	inc cx			; number of bytes written (this only)
				; (not valid until we get ACK which
				;  is why the wmendsub is included)

	call Read_Control
	test bl,01		; wait for ACK asserted
	jz .wm40
	call Read_Control
	test bl,01		; look again
	jz .wm40
	mov DX,[ParLLTimeout]	; DX = timeout count
.wm31:
	call Read_Control
	test bl,01		; wait for ACK asserted with timeout
	jz .wm40
	dec dx			; decrement timeout count
	jnz .wm31
	jmp short .wmendsub	; timeout, no bytes written!

	; have ~ACK, so byte transmitted. increment bytes written,
	; decr. of bytes left was already done before.
	; now send second byte and loop back.

.wm40:	call Write_Data		; data was loaded in AL before.
	mov bl,03h		; leave ACK=1, CTL=0, but REQ->1
	call Write_Control	; release ~REQ
	inc cx			; increment number of bytes written

	dec di			; one less bytes
	jz .wm40a		; odd number of bytes were written
	dec di			; one less bytes
	jz .wm50		; these were the last even bytes(s)

	jmp .wm20		; loop back

.wm40a: dec di			; fixup, di = -1 when odd bytes requested
	jmp short .wm50		; last odd byte, go send an EOP

	; Last byte in buffer has been transmitted.
	;
	; Get next buffer in vector.

.wm50:	cmp NextWVector,0	; if next buffer does not exist
	je .wm50a		; yes.
        cmp WORD PTR nbuf,0     ; next buffer points to somewhere?
       if @Datasize NE 0
        jne .wmnext
        cmp WORD PTR nbuf+2,0   ; check also the segment
       endif
        je .wm50a		; is zero, so no next buffer.
.wmnext:
       mov di,nextbyts          ; length of next buffer
       if @Datasize NE 0
        les si,nbuf             ; load pointer to next buffer
        add bp,6                ; this is VERY tricky  = sizeof(nbuf+nbyts)
       else
        mov si,nbuf             ; load pointer to next buffer
        add bp,4                ; this one is also VERY tricky
       endif
	jmp .wm20		; proceed write.

.wm50a:
	;   Last byte has been transmitted,
	;
	;   Wait for ~ACK to be released and then assert ~REQ with
	;   EOP & CTL = 1
	;
	;   (timing on read is that CTL is sampled when ~REQ is
	;   RELEASED so no timing window here)

	call Read_Control	; Wait ~ACK released
	test bl,01
	jz .wm50

	mov al,0
	call Write_Data		; EOP mark (=0)
	mov bl,01h		; leave ACK=1 and CTL=0, but REQ->0
	call Write_Control	; assert ~REQ

	; Wait for ~ACK asserted

	call Read_Control
	test bl,01		; ACK asserted?
	jz .wm60		; yes
	mov DX,[ParLLTimeout]	; DX = timeout count
.wm51:
	call Read_Control
	test bl,01		; wait for ACK asserted with timeout
	jz .wm60
	dec dx			; decrement timeout count
	jnz .wm51
	mov ax,-3		; EOP failed
	jmp short .wmend

	; Set CTL=1 then release ~REQ, then wait for ~ACK released
.wm60:
	mov bl,05h		; leave ACK=1, REQ=0, but CTL->1
	call Write_Control	; release CTL
	mov bl,07h		; leave ACK=1, CTL=1, but REQ->1
	call Write_Control	; release ~REQ
	
	; Wait ~ACK released ?

.wm61:	call Read_Control
	test bl,01
	jz .wm61

	; Add DI to CX. This handles fixup if an odd number of bytes were
	; requested written, DI will be -1 (odd) or 0 (even) and CX will 
	; be one too large (odd) or perfect (even)

	add cx,di
	jmp short .wmend1

.wmendsub:
	dec cx			; was ahead in count
.wmend1:
	mov ax,cx		; return value in AX
.wmend:
	call stable		; set all lines to input
	pop di
	pop si
	pop es
	pop ds			; normally done by the 'USES' macro
	ret			; return value in AX

parwrite	ENDP

	END
