; 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().
;
;
;   PARALLEL PORT NETWORK LOW LEVEL ROUTINES
;
;  THE CABLE:  Connect D7-D0,SEL,POUT, and BUSY across,
;	       Connect ACK to SEL both locally and across,
;	       Connect GND lines indicated:
;
;   (2-9)   D7-D0   ------------    D7-D0  (data)
;   (12)    POUT    ------------    POUT   (~req)
;   (11)    BUSY    ------------    BUSY   (~ack)  PARNET INTERFACE
;   (13)    SEL     --+------+--    SEL    ( ctl)
;   (10)    ACK     -/        \-    ACK    ( irq)
;   (18-22) GND     ------------    GND
;
;
;   The interface has in it's standard layout no pullup resistors
;   on the lines. You don't need them when at least one Amiga is
;   connected on the line. When connecting PC's only, you need to
;   place 1 array of pullup resistors in the interface of the LAST
;   machine on the line. Such a pullup is made by connecting all the
;   lines shown in the cable above (except for GND) with 4.7 KOhm
;   resistors to +5 Volt.
;
;      +--+        +-----+          +--+           +--+
;      |PC|        |Amiga|          |PC|           |PC|
;      +--+        +-----+          +--+           +--+
;        |            |               |              |
;     +-----+         |            +-----+        +-----+    
;     |Iface|   .     |            |Iface|        |Iface|    
;     +-----+   .     |            +-----+        +-----+
;        |      |     |               |       .      | ++-+
;        \------+-----/               |       .      +-|PR|
;  PARnet cable between PC & Amiga    |       |      | +--+
;       (no resistors needed)         \-------+------/
;                                   PARnet cable between PC's
;                                   (add pullup resistors)
;
;   More than 2 machines on the same line are ofcourse possible.
;   Note that you can't connect more than 3 Amiga's (or 2 Amiga's
;   and 1 PC) without additional buffers, otherwise you'll blow up
;   your CIA's. The software can handle 254 machines.
;
;   Parallel port usage:
;
;   DATA PORT   : used for data output only
;   STATUS PORT : used for data & control input
;   CONTROL PORT: used for control 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: Input: Output:  Description:
;   ---------------------------------------------------------
;   ~ACK    SLCT   STROBE   hand shake
;   ~REQ    POUT   AUTOLF   hand shake
;    CTL    ACK    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

; Parnet interface truth table
;
; 0 = line is 0V
; 1 = line is 5V
; X = don't care
;
;SELIN  D3 STROBE | RD RC WD WC | Function
;-----------------+-------------+---------------
; 0      0    X   | 0  1  1  1	| Read Data
; 0      1    X   | 1  1  1  0	| Write Control
; 1      X    0   | 1  1  0  1	| Write Data
; 1      X    1   | 1  0  1  1	| Read Control
;

; Put the interface in a stable mode by switching it into
; 'Read Control' mode.
;
stable: push ax
	push dx
	mov dx,LPTControl	;control register 2 (to clock in data & Read Control)

	mov al,14h	;all outputs to 1 (INIT is not inverted!), enable IRQ7
	out dx,al	;STROBE=1 & SELIN=1 -> read control (stable mode).
	pop dx
	pop ax
	ret

; Set the datalines to 'input' by setting all lines to high.
; (Open collector outputs not active).
;
data_input:
	push ax
	push dx
	mov dx,LPTData	;data register
	mov al,0ffh	;datalines high, so
	out dx,al	;open collector drivers not active.
	mov dx,LPTControl	;control register 2 (to set data)

	mov al,15h	;SELIN=1 & STROBE=0 Enable IRQ7
	out dx,ax	;write data
	call Stable	;be sure to CLOCK it in.
	pop dx
	pop ax
	ret

; Set the control-lines to 'input' by setting all lines to high.
; (Open collector outputs not active).
;
control_input:
	push ax
	push dx
	mov dx,LPTData	;data register
	mov al,0ffh	;d3=1, rest don't care
	out dx,ax
	mov dx,LPTControl ;Control register

	mov al,1ch	;all outputs to 1, SELIN to 0 (INIT is not inverted!)
	out dx,al	;d3=1 & SELIN=0 -> write control
	call Stable	;be sure to CLOCK it in.
	pop dx
	pop ax
	ret

; read data from the cable, put it in AL
read_data:		;buffers must be in stable & data_input mode.
	push bx
	push dx
	mov dx,LPTData	;data register
	xor al,al	;bit 3 of AL will set line d3=0
	out dx,al	;all datalines to zero.
	mov dx,LPTControl ;Control register

	mov al,1ch	;SELIN=0, rest high (INIT is not inverted!).
	out dx,al	;d3=0 & SELIN=0 -> Read Data.
	mov dx,LPTStatus ;status register
	in al,dx	;read it
	mov bl,al	;save higher bits of data to BL (AL will be changed)
	mov dx,LPTControl	;control register
	in al,dx	;read it (lower bits of data now in AL)
	call stable	;put all buffers in tri-state mode.
	call data_input ;a bug, the change from RD->RC could activate WD.

; Translation incomming data via control lines.
; (some lines are inverted, and I can't read 8 bits at once)
;
; Let's say, all datalines on the cable are high:
;
; databits: 7654 3210
; value:    1111 1111
;
; Then the input from the control registers would look like this:
;
; AL: data read from control register
;
; AL bits: 7654 3210  centronics  corresponding
; value:   XXXX X100   keyword:    databits:
;          |||| |||`- strobe         d0
;          |||| ||`-- auto LF        d1
;          |||| |`--- init           d2
;          |||| `---- invalid        x
;          ````------ invalid        x
;
; BL: data read from status register
;
; BL bits: 7654 3210  centronics  corresponding
; value:   0111 1XXX   keyword:     databits:
;          |||| |```- invalid          x
;          |||| `---- error           d3
;          |||`------ select          d4
;          ||`------- paper empty     d5
;          |`-------- ack             d6
;          `--------- busy            d7
;
translate_data:
	xor al,03h	;invert bits 0 and 1
	and al,07h	;discards bits not needed in AL.
	xor bl,80h	;invert bit 7 in BL (busy).
	and bl,0f8h	;discard bits not needed in BL.
	or al,bl	;merge them together (high bits in BL, lower in AL)

	pop dx
	pop bx
	ret



; Read the controlbits from the cable (busy, pout & sel)
;
; BL: control read from control register 1, represents the REAL line
;     status.
;
; BL bits: 7654 3210  centronics:    cable:  parnet:
; value:   0111 1XXX   
;          |||| |```- invalid          x
;          |||| `---- error            x
;          |||`------ select          busy    ack
;          ||`------- paper empty     pout    req
;          |`-------- ack             sel     ctl
;          `--------- busy             x
;
;Output: like BL above, but shifted 4 bits to the right.
;
read_control:			;control lines must be set to input first.
	push ax
	push cx
	push dx
	call Stable		;Stable mode = Read_Control mode.
	mov dx,LPTStatus	;Status register
	in al,dx		;read it
	mov cl,04		;need CL to shift AL
	shr al,cl		;shift 4 bits to the right.
	mov bl,al		;save it to BL
	and bl,07		;discard bits not needed.
	pop dx
	pop cx
	pop ax
	ret			;ready, controlbits in BL.

;-----------------------------------------------------------------------------
; 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
	push cx
	mov bl,0feh		; set parnet ack-bit to 0
	call write_control	; write control
	pop cx
	pop bx
	ret

;-----------------------------------------------------------------------------
; 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
	push cx
	mov bl,0fh		; set all bits to 1 (including ACK)
	call write_control	; write control
	pop cx
	pop bx
	ret

; Write control
;
; BL: controlbits to be written
;
; BL bits: 7654 3210                centronics
; value:   XXXX X100  controlbits:  keyword:
;          |||| |||`-    ack        strobe
;          |||| ||`--    req        auto LF
;          |||| |`---    ctl        init
;          |||| `----     x         selin
;          ````------     x         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:		; Interface must be in stable mode!
	push ax
	push dx
	mov dx,LPTData	; data register
	mov al,0ffh	; d3=1, rest don't care.
	out dx,al	;
	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)
	or al,08h	; SELIN = 0 (inverted!)
	out dx,al	; SELIN = 0 & d3=1 -> write control.
	call Stable
	pop dx
	pop ax
	ret

; Write DATA
;
; AL: data to be written.
; WARNING: AL will be destroyed after write.
;
write_data:			; interface must be in stable mode!
	push dx
	mov dx,LPTData		; data register
	out dx,al		; put it on (not on cable yet).
	mov dx,LPTControl	; control register
	mov al,15h		; SELIN=1, strobe=0, enable IRQ7
	out dx,al		; write data
	call Stable		; be sure to 'CLOCK' it in.
	pop dx
	ret


; (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 data_input		; data lines high
	call control_input	; control 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 data_input		; be sure all lines
	call control_input	; 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 data_input		; ensure all
	call control_input	; are inputs.
	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
.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 AL

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 data_input		; ensure all
	call control_input	; are inputs.
	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
	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 data_input		; set data port to input
	call control_input	; set control 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
