home *** CD-ROM | disk | FTP | other *** search
- ; Lowlevel routines for PARnet
- ; 03-JUN-93 <S.A.Pechler@bdk.tue.nl>
-
- .MODEL MEMMOD,C
- LOCALS
- %MACS
- .LALL
-
- .DATA
- _ParNetAddr db 1 ; default network address
- _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
- ;dl 983025 ; default timeout value (about 1 second?)
- _DestAddress db 5
- _DebugBytes dw 255
- _DummyBuf db 0 ; dummy buffer
- db 0 ; dummy buffer
- db 0,0 ; padding
-
- .CODE
- PUBLIC paraddress,pardataready,parread,parwrite
-
- ; 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
- read_data: ;buffers must be in stable & data_input mode.
- push ax
- push bx
- push dx
- mov dx,_LPTData ;data register
- mov al,00 ;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 it to BL (AL will be changed)
- mov dx,_LPTControl ;control register 2
- in al,dx ;read it
- mov cl,al ;save it to CL
- call stable ;put all buffers in tri-state mode.
- call data_input ;a bug, the change from RD->RC could activate WD.
- call translate_data ; put data into 8 bit CL.
- pop dx
- pop bx
- pop ax
- ret
-
- ; 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:
- ;
- ; CL: data read from control register 2
- ;
- ; CL 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 control register 1
- ;
- ; 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 cl,03h ;invert bits 0 and 1
- and cl,07h ;discards bits not needed in CL.
- xor bl,80h ;invert bit 7 in BL (busy).
- and bl,0f8h ;discard bits not needed in BL.
- or cl,bl ;merge them together (high bits in BL, lower in CL)
- ret ;ready, translated data in CL.
-
-
- ; 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: BL, 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
- ;
- ; CL: data to be written.
- ;
- write_data: ; interface must be in stable mode!
- push ax
- push dx
- mov dx,_LPTData ; data register
- mov al,cl ; move data to AL for OUT-instruction.
- 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
- pop ax
- 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 al,bl ; save it
- call read_data ; read data in CL
- call read_control ; read control in BL
- cmp al,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],cl ; no, does data match anyway?
- je .pdstable ; YES, loop until get ~REQ or
- jmp .pdfail ; data bad.
-
- .pd10:
- test bl,04 ; yes, CTL?
- jz .pdrn ; no, middle of some packet.
- cmp [_ParNetAddr],cl ; yes, my address?
- jne .pdrn ; nope
- mov al,1 ; yes, packet (probably) for us.
- ret
-
- .pdfail:
- test bl,04 ; fail due to ~REQ not asserted.
- jz .pdrn ; CTL=0, line busy
- mov al,0 ; line idle.
- ret
-
- .pdrn:
-
- mov al,-1 ; line busy, packet not for me.
- ret
-
- pardataready ENDP
-
- ;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.
- ;
- parread PROC
- ARG buf:ptr, byts:word
-
- if @Datasize NE 0
- uses es,di
- les di,buf ; ds:di = buf
- else
- uses di
- mov di,buf ; ds:di = buf (ds already set)
- endif
-
- mov ax,byts ; max. number of bytes to read.
- mov _Debugbytes,ax ; can't do a direct move to _DebugBytes.
-
- 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 al,bl ; save it
- call read_data ; read data in CL
- call read_control ; read control in BL
- cmp al,bl ; control lines stable?
- jne .rmstab ; nope
- test al,04 ; expect CTL=1
- jz .rms1 ; nope
- test al,02 ; expect ~REQ=0
- jz .rms2 ; yes
-
- .rms1:
- dec dx ; decrement timeout count
- jnz .rmstab ; no timeout.
- jmp .rmend ; no address mark!
-
- .rms2:
- cmp [_ParNetAddr],CL ; 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 bl,-2 ;~REQ not released?
- jmp .rmend
-
- .rms5: call Set_Ack_All ; release ~ACK
-
- mov ax,0 ; set # of bytes read to 0
- jmp .rms10 ; skip past move
-
-
- ; MAIN READ LOOP
- ;
- ; AX holds count (number of bytes read).
- ; DI buffer pointer (place to store data).
-
- .rms10loop:
- ; es:
- mov es:[di],cl ; store data
- inc di ; next address
- ; (can't do this with a 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
- jmp .rmend ; timeout
-
- .rms20: call read_data ; get data in CL and
- call Clear_Ack_Only ; assert ~ACK
-
- ; note on CTL = 1 end sequence this data item is a dummy
-
- ; es:
- mov es:[di],cl ; store data
- inc di ; next address
- inc ax ; optimized, but not quite true,
- inc ax ; 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 .rmendsub ; sub because D6 is 2 ahead
-
-
- .rms30:
- call read_data ; get data in CL
- 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)
-
- sub _DebugBytes,2 ; # bytes remaining
- jz .rmste ; reached zero
- jno .rms10loop ; continue if >0.
- jnz .rmnlb ; always true?
-
- .rmste:
- ; es:
- mov es:[di],CL ; if _DebugBytes = 0, its's even and we
- ; should only store the last byte.
-
- .rmnlb: cmp _DebugBytes,-1 ; -1 = was odd #
- jne .rmeven ; fixup count
- dec ax
-
- .rmeven:
- .rmsev0:
- jmp .rmovflow
-
- .rmovflow:
- mov di,OFFSET _DummyBuf ; overflow, use dummy buffer
- jmp .rms10
-
- .rmeop:
- cmp CL,0 ; EOP data better be 0!
- je .rmendsub
- mov bx,-3 ; bad protocol
- jmp .rmend
-
- .rmendsub:
- dec ax ; because we were two ahead
- dec ax
- .rmend:
- call data_input
- call Set_Ack_All ; setting ~ACK to input
- call Stable
- ret ; return value in AL
-
- parread ENDP
-
- ;n = parwrite(int16 destadr, 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 to)
- ; 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.
- ;
- parwrite PROC
- ARG dest:word, buf:ptr, byts:word
-
- if @Datasize NE 0
- uses es,si
- les si,buf ; ds:si = source-buffer to be written
- else
- uses si
- mov si,buf ; ds:si = buf (ds already set)
- endif
-
- mov ax,dest ; destination address.
- mov _DestAddress,al ; can't do a direct move to _DestAddress.
- mov ax,byts ; number of bytes to write.
- mov _DebugBytes,ax ; can't do a direct move to _DebugBytes.
-
- 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 al,bl
- call read_data
- call Read_Control ; get stable control
- cmp al,bl
- je .wmstab1
- sti ; set interrupts
- jmp .wmstab
-
- ; Interrupts still disabled
- ; BX 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 CL=my address then
- ; return with -2
-
- test bl,02h ; ~REQ = 0?
- jne .wm01 ; nope
- test bl,04h ; CTL = 1?
- je .wm01 ; nope
- cmp [_ParNetAddr],cl ; 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 .wm01
-
- ; Line now aquired.
-
- .wm05:
- sti ; enable interrupts
- mov cl,_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 ax,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:
- cmp _DebugBytes,0 ; 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:
- ; es:
- mov cl,es:[si] ; get next data byte.
- inc si
- call Write_Data ; store data and ..
- mov bl,01h ; leave ACK=1, CTL=0, but REQ->0
- call Write_Control ; .. assert ~REQ
-
- ; es:
- mov cl,es:[si] ; get next data bytes
- inc si
- inc ax ; 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 ACKasserted
- 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 .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 CL before.
- mov bl,03h ; leave ACK=1, CTL=0, but REQ->1
- call Write_Control ; release ~REQ
- inc ax ; increment number of bytes written
-
- sub _DebugBytes,2 ; two less bytes
- jo .wm50 ; these were the last bytes(s)
-
- jmp .wm20 ; loop back
-
- ; Last byte in buffer has been transmitted.
- ;
- ; Get next buffer in vector (not yet implemented).
-
- .wm50: jmp .wm50a
-
- .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 cl,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 al,-3 ; EOP failed
- jmp .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 _DebugBytes to AX. This handles fixup if an odd number of bytes
- ; were requested written, _DebugBytes will be -1 (odd) or 0 (even) and
- ; AX will be one too large (odd) or perfect (even)
-
- add ax,[_DebugBytes]
-
- jmp .wmend
-
- .wmendsub:
- dec ax ; was ahead in count
- .wmend:
- call data_input ; set data port to input
- call control_input ; set control lines to input
- ret ; return value in AL
-
- parwrite ENDP
-
- END
-