home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Columbia Kermit
/
kermit.zip
/
archives
/
mskermit.tar.gz
/
mskermit.tar
/
msnpdi.asm
< prev
next >
Wrap
Assembly Source File
|
1998-05-28
|
66KB
|
2,021 lines
NAME MSNPDI
; File MSNPDI.ASM
; Packet Driver and ODI interface
;
; Copyright (C) 1982, 1997, Trustees of Columbia University in the
; City of New York. The MS-DOS Kermit software may not be, in whole
; or in part, licensed or sold for profit as a software product itself,
; nor may it be included in or distributed with commercial products
; or otherwise distributed by commercial concerns to their clients
; or customers without written permission of the Office of Kermit
; Development and Distribution, Columbia University. This copyright
; notice must not be removed, altered, or obscured.
;
; Written by Joe R. Doupnik, Utah State University, Logan Utah 84322
; jrd@cc.usu.edu, jrd@usu.Bitnet.
;
; Packet Driver reference:
; "PC/TCP Version 1.09 Packet Driver Specification", FTP Software, Inc.,
; September-14-1989.
;
; ODI references:
; "Open Data-Link Interface Developer's Guide for DOS Network Layer Protocol
; Stacks", Novell Inc, document number 100-001218-001 (1992, but no printed
; date).
; "Open Data-Link Interface Developer's Guide for NetWare v3.1x Server
; Driver Protocol Stacks", Novell Inc, document number 100-001196-00, v1.0,
; 19 Sept 1991.
; "Open Data-Link Interface LAN Driver Developer's Guide for DOS", Novell Inc,
; document number 107-000010-001, Revision i, 13 Nov 1990.
;
; C language interface presumes the Small memory model and Microsoft C.
; Assembler is MS MASM v6.
;
; These procedures interface between ODI or a Packet Driver and the main
; protocol stack to both send and receive packets. The upper levels provide
; and receive packets framed as Ethernet_II (Blue Book/DIX), and receive
; ARP information particular to the physical frames involved. Conversion of
; this internal form to the actual framing method on the wire is done by ODI.
; The receive buffer is external to this routine. Received packets are linked
; into the buffer on an order of arrival basis (and sized to fit). High level
; reception has to poll the receive buffer queue. Transmitted packets are
; operated with a single external buffer, and sending blocks until the lower
; level driver material is ready for us. Initialization, status, attachment,
; and disengagment procedures are here.
; External int kpdint is used by pdinit() to select between Packet Driver
; and ODI interfaces. Call pdinit() to initialize the system, then call
; pdaccess() to register each desired frame TYPE, call pdclose() to release
; each frame TYPE.
; Packet senders call pkt_send() directly.
; Packet receivers examine the external packet buffer for packets.
; ARP information is returned in external ints arp_hardware (hardware ARP
; type code) and MAC_len (length of MAC address, in bytes). See table below
; for the possible pairs.
; Packet Driver usage is limited to Ethernet_II/DIX and SLIP. ODI usage is
; at least Ethernets, Token Ring, Arcnet, and others untested here.
;
; Edit history
; 12 Jan 1995 version 3.14
; Last edit
; 23 Dec 1995
INCLUDE MSSDEF.H
getintv equ 35h ; DOS get interrupt vector to es:bx
dos equ 21h
lf equ 0ah
fopen equ 3dh ; DOS file operations
fclose equ 3eh
fread equ 3fh
pdgetinfo equ 1 ; Packet Driver functions
pd_access equ 2
pd_release equ 3
pd_send equ 4
pd_get_address equ 6
eaddr_len equ 6 ; length of an Ethernet address
IF_EII equ 1 ; Ethernet II interface type
IF_SLIP equ 6 ; SLIP interface type, RFC1055
link struc ; receiver buffer link structure
flag db 0 ; buffer use flag
bufnum db 0 ; buffer write sequence number
count dw 0 ; count of bytes to follow
link ends
linksize equ 4 ; bytes in link structure
; Novell ODI material, based on LSL v1.2.
; Most of these header structures were taken from Novell file ODI.INC,
; and parts have been tailored for Kermit. Information on the "new" item in
; the lookahead structure is compliments of Novell, private correspondence.
; Event Control Block Structure
ECBstruct struc
nextlink dd 0 ; leave intact
prevlink dd 0 ; leave intact
status dw 0 ; general status
esr dd 0 ; addr of event service routine
stackid dw 0 ; protocol stack ident
protid db 6 dup (0) ; sending only
boardnum dw 0 ; sending, MILD board number
immaddr db 6 dup (0) ; MAC destination address
driverws db 4 dup (0) ; their work space
protocolws dw 4 dup (0) ; our work space
datalen dw 0 ; total length of sent buffer
fragcount dw 1 ; number of buffer pieces
frag1addr dw 0,0 ; seg:offset of first buffer
frag1len dw 0 ; length of first buf frag
ECBstruct ends ; 26 words
; Look Ahead Structure
LookAheadStruc struc
LMediaHeaderPtr dd 0 ; pointer to MAC header
LookAheadPtr dd 0 ; pointer to pkt Data
LookAheadLen dw 0 ; length of pkt Data field
LProtID db 6 dup (0) ; protocol ident
LBoardNum dw -1 ; logical board of rcv'd pkt
DataLookAheadDataSize dd 0 ; new field, exists if bit 7 of the
; Driver's Configuration Table Mode Flags is set.
LookAheadStruc ends
; Rx Destination Address Type (First byte of ECB.DriverWS)
ECB_DIRECT equ 00h ;Physical destination address
ECB_MULTICAST equ 01h ;Multicast destination address
ECB_BROADCAST equ 03h ;Broadcast destination address
; System Error Code Definitions
LSLERR_OUT_OF_RESOURCES equ 8001h
LSLERR_BAD_PARAMETER equ 8002h
LSLERR_NO_MORE_ITEMS equ 8003h
LSLERR_ITEM_NOT_PRESENT equ 8004h
LSLERR_FAIL equ 8005h
LSLERR_RX_OVERFLOW equ 8006h
LSLERR_CANCELLED equ 8007h
LSLERR_BAD_COMMAND equ 8008h
LSLERR_DUPLICATE_ENTRY equ 8009h
LSLERR_NO_SUCH_HANDLER equ 800ah
LSLERR_NO_SUCH_DRIVER equ 800bh
; LSL MLID Services Function Codes
MLIDSUP_GET_ECB equ 0
MLIDSUP_RETURN_ECB equ 1
MLIDSUP_DEFRAG_ECB equ 2
MLIDSUP_SCHEDULE_AES_EVENT equ 3
MLIDSUP_CANCEL_AES_EVENT equ 4
MLIDSUP_GET_INTERVAL_MARKER equ 5
MLIDSUP_DEREGISTER_MLID equ 6
MLIDSUP_HOLD_RECV_EVENT equ 7
MLIDSUP_START_CRITICAL_SECTION equ 8
MLIDSUP_END_CRITICAL_SECTION equ 9
MLIDSUP_CRITICAL_SECTION_STATUS equ 10
MLIDSUP_SERVICE_EVENTS equ 11
MLIDSUP_SEND_COMPLETE equ 14
MLIDSUP_ADD_PID equ 15
MLIDSUP_GET_STACK_ECB equ 16
; LSL Protocol Stack Services Function Codes
PROTSUP_GET_ECB equ 0
PROTSUP_RETURN_ECB equ 1
PROTSUP_SCHEDULE_AES_EVENT equ 3
PROTSUP_CANCEL_EVENT equ 4
PROTSUP_GET_INTERVAL_MARK equ 5
PROTSUP_REGISTER_STACK equ 6
PROTSUP_DEREGISTER_STACK equ 7
PROTSUP_REGISTER_DEFAULT_STACK equ 8
PROTSUP_DEREGISTER_DEFAULT_STACK equ 9
PROTSUP_REGISTER_PRESCAN_STACK equ 10
PROTSUP_DEREGISTER_PRESCAN_STACK equ 11
PROTSUP_SEND_PACKET equ 12
PROTSUP_GET_PROTNUM_FROM_NAME equ 16
PROTSUP_GET_PID_PROTNUM_MLIDNUM equ 17
PROTSUP_GET_MLID_CTL_ENTRY equ 18
PROTSUP_GET_PROTO_CTL_ENTRY equ 19
PROTSUP_GET_LSL_STATS equ 20
PROTSUP_BIND_STACK_TO_MLID equ 21
PROTSUP_UNBIND_STACK_FROM_MLID equ 22
PROTSUP_ADD_PID equ 23
PROTSUP_RELINQUISH_CONTROL equ 24
PROTSUP_GET_LSL_CONFIG equ 25
; LSL General Services Function Codes
GENSERV_ALLOC_MEMORY equ 0
GENSERV_FREE_MEMORY equ 1
GENSERV_REALLOC_MEMORY equ 2
GENSERV_MEMORY_STATISTICS equ 3
GENSERV_ADD_MEMORY_TO_POOL equ 4
GENSERV_ADD_GENERAL_SERVICE equ 5
GENSERV_REMOVE_GENERAL_SERVICE equ 6
GENSERV_GET_NETCFG_PATH equ 7
; LSL Configuration Table
LSLConfigurationStructure struc
LConfigTableMajorVer db 1
LConfigTableMinorVer db 0
LNumLSLRxBuffers dd 0
LRxBufferSize dd 0 ;Buffer size NOT including ECB struc size
LMajorVersion db 0
LMinorVersion db 0
LConfigTableReserved db 16 dup (0)
LSLConfigurationStructure ends
; MLID Control Commands
GET_MLID_CONFIGURATION equ 0
GET_MLID_STATISTICS equ 1
ADD_MULTICAST_ADDRESS equ 2
DELETE_MULTICAST_ADDRESS equ 3
MLID_SHUTDOWN equ 5
MLID_RESET equ 6
CREATE_CONNECTION equ 7
REMOVE_CONNECTION equ 8
SET_LOOK_AHEAD_SIZE equ 9
DRIVER_POLL equ 12
; MLID Configuration Table Structure
MLIDConfigurationStructure struc
MSignature db 'HardwareDriverMLID',8 dup (' ')
MConfigTableMajorVer db 1
MConfigTableMinorVer db 11
MNodeAddress db 6 dup (?)
MModeFlags dw ?
MBoardNumber dw ?
MBoardInstance dw ?
MMaxPacketSize dw ?
MBestDataSize dw ?
MWorstDataSize dw ?
MCardLongName dd ?
MCardShortName dd ? ; visible board name
MFrameString dd ?
MReserved0 dw 0 ;Must be set to 0
MFrameID dw ?
MTransportTime dw ?
MRouteHandler dd ? ;Only for Token-Ring
MLookAheadSize dw ?
MLineSpeed dw ? ;In Mbps or Kbps
MReserved1 db 8 dup (0) ;Must be set to 0
MMLIDMajorVer db ?
MMLIDMinorVer db ?
MFlags dw ?
MSendRetries dw ?
MLink dd ?
MSharingFlags dw ?
MSlot dw ?
MIOAddress1 dw ?
MIORange1 dw ?
MIOAddress2 dw ?
MIORange2 dw ?
MMemoryAddress1 dd ?
MMemorySize1 dw ?
MMemoryAddress2 dd ?
MMemorySize2 dw ?
MIntLine1 db ?
MIntLine2 db ?
MDMALine1 db ?
MDMALine2 db ?
MLIDConfigurationStructure ends
; MLID Config Table 'MFlags' bit definitions.
EISA equ 01h ;EISA Bus
ISA equ 02h ;PC/AT Bus
MCA equ 04h ;PS/2 MCA Bus
Len_Info equ 40h ; pkt data length in lookahead info
; MLID Config Table 'MModeFlags' bit definitions (no promiscuous mode).
MRealDriverBit equ 0001h
MUsesDMABit equ 0002h
MGuaranteedDeliveryBit equ 0004h ;100% reliable on transmits
MMulticastBit equ 0008h
MNeedsPollingBit equ 0020h
MRawSendBit equ 0040h
; Registered Stack structure, used during registration only
StackInfoStruc struc
StackNamePtr dd ip_string ; ptr to short name
StackReceiveHandler dd ip_rcvr ; rcv routine
StackControlHandler dd pcontrol ; control routine
StackInfoStruc ends
; Protocol Control Commands
GET_STACK_CONFIGURATION equ 0
GET_STACK_STATISTICS equ 1
BIND_TO_MLID equ 2
UNBIND_FROM_MLID equ 3
INFORM_MLID_DEREGISTERED equ 4
; Protocol Configuration Table
ProtocolConfigStructure struc
PConfigTableMajorVer db 1
PConfigTableMinorVer db 0
PProtocolLongName dd plname
PProtocolShortName dd psname ; "KERMIT"
PProtocolMajorVer db 3 ; MSK v3.15
PProtocolMinorVer db 15
PConfigTableReserved db 16 dup (0)
ProtocolConfigStructure ends
; Protocol Statistics Table
ProtocolStatStructure struc
PStatTableMajorVer db 1
PStatTableMinorVer db 0
PNumGenericCounters dw 3 ; just those below
PValidCounterMask dd 111b ; bitfield, 3 valids
PTotalTxPackets dw 2 dup (0)
PTotalRxPackets dw 2 dup (0)
PIgnoredRxPackets dw 2 dup (0)
PNumCustomCounters dw 0 ; none
ProtocolStatStructure ends
pinfo struc ; per protocol local data for ecb
pstack dw 0 ; StackID
pprotid db 6 dup (0) ; ProtID
pboard dw 0 ; boardnum
pinfo ends
_TEXT SEGMENT WORD PUBLIC 'CODE'
_TEXT ENDS
_DATA SEGMENT WORD PUBLIC 'DATA'
_DATA ENDS
CONST SEGMENT WORD PUBLIC 'CONST'
CONST ENDS
_BSS SEGMENT WORD PUBLIC 'BSS'
_BSS ENDS
DGROUP GROUP CONST, _BSS, _DATA
ASSUME CS: _TEXT, DS: DGROUP, SS: DGROUP, ES:NOTHING
_DATA SEGMENT
extrn _pktbuf_wrote:word, _pktwnum:byte, _kpdint:word
extrn _eth_addr:byte, _arp_hardware:word, _MAC_len:word
extrn _mss:word, _tempip:byte, _kdebug:byte
pdsignature db 'PKT DRVR' ; signature of a Packet Driver
pdslen equ $-pdsignature
if_type dw 0 ; interface type
if_class db 0 ; interface class
if_num db 0 ; interface number
if_func db 0 ; interface functionality
if_version dw 0 ; interface version
iptype db 8,0 ; IP packet type
iptypelen equ $-iptype ; length of type field for iptype
pktbufoff dw 0 ; offset of packet buffer
SLIPmac dw 0,0,2 ; fake SLIP dest Ethernet address
; ODI material
useodi db 0 ; non-zero if using ODI for transport
lslsig db 'LINKSUP$' ; LSL presence signature
lslsiglen equ $-lslsig
lslinit dd 0 ; LSL init entry point
; LSL entry structure, do not separate
lslsupport dd 0 ; LSL protocol support API entry point
lslservice dd 0 ; LSL general services API entry point
mlidcont dd 0 ; MLID Control entry point
ecbr_qty equ 4 ; number of receive ECB's to allocate
maketab MACRO ; macro to make receiver ecbs
cnt = 0
rept ecbr_qty - 1
ecbstruct <,,,odircmp>
cnt = cnt + 1
endm
ENDM
ecbr ecbstruct <,,,odircmp> ; first receiver ECB
maketab ; make table of the other ecbr's
ecbx ecbstruct <,,,odixcmp> ; one ECB for transmission
ecbr_busy db ecbr_qty dup (0) ; our ecbr locks
ecbx_busy db 0 ; non-zero if ECBx owned by ODI
ecbr_num dw 0 ; temp to hold index of ecbr/ecbr_busy
rcvtype dw 0 ; temp, holds protocol TYPE for rcv
pconfig ProtocolConfigStructure <> ; as the name says
pstats ProtocolStatStructure <> ; protocol statistics
registerstk StackInfoStruc <> ; bound stack setup structure
plname db 13,'MS-DOS Kermit',0 ; cnt, protocol stack long name, null
protword db 8,'PROTOCOL',0 ; four NET.CFG keywords for Kermit
psname db 6,'KERMIT',0 ; cnt, protocol stack short name, null
bindword db 4,'BIND',0 ; board to which to bind
myipword db 4,'MYIP',0 ; local IP from Telebit PPP driver
ip_type equ 0008h ; Protocol TYPEs, big endian/net order
arp_type equ 0608h
rarp_type equ 3580h
ip_string db 2,'IP',0 ; strings to match in NET.CFG file
arp_string db 3,'ARP',0 ; to select pkt TYPEs
rarp_string db 4,'RARP',0 ; RARP is optional
ip_stackid pinfo <> ; StackID, Protid, boardnum
arp_stackid pinfo <> ; for each protocol
rarp_stackid pinfo <>
bcast db 6 dup (0ffh) ; Broadcast address, for reception
readnetcfg db 0 ; non-zero if have read NET.CFG
useboard dw -1 ; board to be used, -1 = not inited
bdname db 0,16 dup (0) ; length, 15 text, null, bound board
tells_len db 0 ; if MLID tells pkt len for lookahead
tempb db 0
temp dw 0
; parallel lists of NetWare ODI frame types, address lengths, ARP idents
frame_type db 2,3,4,5,6,7, 9,10,11,14,15,16,23,27,28
num_frames equ ($ - frame_type)
frame_adlen db 6,6,6,6,6,6, 1,6, 6, 1, 6, 6, 6, 0, 0
hardware_type db 1,6,6,6,6,12,4,6, 6, 7, 6, 6, 0, 0, 0
startttic dw 0 ; my transmit timer storage spot
startrtic dw 0 ; my receive timer storage spot
stoprtic dw 0
stopttic dw 0
tickind db 0 ; 0 for receive, 1 for transmit
overhead dw 0 ; timer measurement overhead
t0count db 0
_DATA ENDS
; ODI Frame types, frame strings, length (bytes) of a MAC level address:
; type frame string MAC_len hardware comments
; 0 VIRTUAL_LAN 0 0 no MAC header used
; 1 LOCALTALK 6 11 Apple (Ether/Tokentalk is 802)
; 2 ETHERNET_II 6 1 Blue Book
; 3 ETHERNET_802.2 6 6 802.3 with 802.2 wrap
; 4 TOKEN-RING 6 4 802.5 with 802.2 wrap
; 5 ETHERNET_802.3 6 6 802.3 "raw", old Novell
; 6 802.4 6 6 Token Bus
; 7 NOVELL_PCN2 6 12 Novell's IBM PCnet2
; 8 GNET 6 4 Gateway, assumed TRN-like
; 9 PRONET-10 1 4 Proteon TRN-like
; 10 ETHERNET_SNAP 6 1 802.3 with 802.2+SNAP
; 11 TOKEN-RING_SNAP 6 6 802.5 with 802.2+SNAP
; 12 LANPAC_II 6 ? Racore
; 13 ISDN 6 ? telco
; 14 NOVELL_RX-NET 1 7 Arcnet-like
; 15 IBM_PCN2_802.2 6 12 IBM PCnet2 with 802.2
; 16 IBM_PCN2_SNAP 6 12 IBM PCnet2,802.2+SNAP
; 17 OMNINET/4 ? ? Corvus
; 18 3270_COAXA ? ? Harris
; 19 IP ? ? tunneled
; 20 FDDI_802.2 6 ?
; 21 IVDLAN_802.9 6 ? Commtex
; 22 DATACO_OSI ? ? Dataco
; 23 FDDI_SNAP 6 6 802.7, with 802.2+SNAP
; 27 SLIP 0 0 SLIP, IP over serial link
; 28 PPP 0 0 PPP, IP over PPP serial link
;
; ARP hardware field, from RFC 1060
; Type Description
; ---- -----------
; 1 Ethernet (10Mb)
; 2 Experimental Ethernet (3Mb)
; 3 Amateur Radio AX.25
; 4 Proteon ProNET Token Ring
; 5 Chaos
; 6 IEEE 802 Networks
; 7 ARCNET
; 8 Hyperchannel
; 9 Lanstar
; 10 Autonet Short Address
; 11 LocalTalk
; 12 LocalNet (IBM PCNet or SYTEK LocalNET)
data segment
extrn tv_segs:word, tv_sego:word, crt_lins:byte
extrn crt_cols:byte
data ends
code segment
extrn pcwait:far
code ends
_TEXT segment
pktdrvr proc near ; Packet Driver interrupt invokation
PKTDRVI:int 60h ; Interrupt number, modified by startup code
ret
pktdrvr endp
; pdinit(ðeraddress)
; Initialize Packet Driver or ODI for use by this program. Stores Ethernet
; address (or MAC address). _kpdint is 0 to scan first for a Packet Driver
; interrupt and fall back to search for ODI, or is a number 60h..7fh to
; target only that PD interrupt, or is 'DO' to target only ODI. If a PD is
; used then _kpdint is modified to be the found interrupt value.
; A 6 byte MAC level address is returned for convenience, even for SLIP.
; Returns 1 for success, 0 for failure.
public _pdinit
_pdinit proc near
push bp
mov bp,sp
push es
push si
push di
push ds
mov ax,DGROUP
mov ds,ax
inc _kdebug ; turn on to get overhead
cli ; compute timing overhead
call rstart
call rstop
mov ax,startrtic
sub ax,stoprtic
jns pdinit8
neg ax
pdinit8:mov overhead,ax ; overhead, ticks
in al,40h ; 8254 timer channel T0, read LSB
xchg ah,al
in al,40h ; read MSB
xchg al,ah ; order properly
mov bx,ax
mov ax,1 ; number of milliseconds to delay
call pcwait ; delay AX milliseconds
mov ax,1
call pcwait
mov ax,1
call pcwait
mov ax,1
call pcwait
in al,40h ; 8254 timer channel T0, read LSB
xchg ah,al
in al,40h ; read MSB
xchg al,ah ; order properly
sti
dec _kdebug ; restore value
sub ax,bx
jns pdinit9
neg ax
pdinit9:add ax,500 ; round up to 4+millisec
xor dx,dx
mov cx,1000 ; micro to millisec
div cx
shr ax,1 ; divide by four timings
shr ax,1 ; get 1 or 2 for 1 millisec
and al,3 ; keep only two lower bits
mov t0count,al ; remember for msnpdi.asm
mov ax,[bp+4+0] ; get offset of pktbuf
mov pktbufoff,ax ; save locally
cmp _kpdint,'DO' ; special indicator to use ODI?
je short pdinit2 ; e = yes, do ODI
mov cx,60h ; interrupt range
cmp _kpdint,0 ; user value given for PD Int?
je pdinit1 ; e = no
mov cx,_kpdint ; use it
mov _kpdint,0 ; assume no user value
pdinit1:mov ah,getintv ; get interrupt vector to es:bx
mov al,cl ; vector number
int dos
mov si,offset dgroup:pdsignature ; look for signature
push cx
mov cx,pdslen ; length of string
mov di,bx
add di,3 ; sig starts 3 bytes from entry point
cld
repe cmpsb ; compare bytes
pop cx
je pdinit3 ; e = found a match
cmp _kpdint,0 ; user value given?
jne pdinit2 ; ne = yes, so fail
inc cx
cmp cx,80h ; at end of range?
jna pdinit1 ; na = not yet, try another int
; no Packet Driver found or use ODI
pdinit2:call odichk ; see if ODI is available now
jnc pdinit6 ; nc = yes
cmp _kpdint,'DO' ; special indicator to use ODI?
jne pdinit5 ; ne = no, have tried Packet Drivers
mov _kpdint,0 ; setup for Packet Driver test
mov cx,60h ; scan from this interrupt
jmp pdinit1
pdinit6:mov useodi,1 ; say using ODI
mov _kpdint,'DO' ; signal ODI via PD interrupt variable
mov if_class,1 ; say Ethernet_II for internal work
mov di,[bp+4+2] ; get offset of user's buffer
mov cx,eaddr_len ; length of address provided
mov si,offset DGROUP:SLIPmac ; get fake Ethernet address
cld ; in case code wants it early
push ds
pop es
push di
rep movsb ; copy to user buffer
mov cx,ecbr_qty
mov di,offset DGROUP:ecbr_busy ; clear receive ecb busy flags
xor al,al
rep stosb
mov ecbx_busy,al ; and transmitter busy lock
pop di
clc
pdinit5:jmp pdret ; exit (carry set is failure)
; Packet Driver details
pdinit3:mov byte ptr PKTDRVI+1,cl ; force in new PD interrupt, code mod
mov _kpdint,cx ; remember interrupt number
; find Ethernet address
mov ah,pdgetinfo ; get Packet Driver information
mov al,0ffh
xor bx,bx ; optional handle
push ds ; this call changes ds and si
push si
call pktdrvr ; call the Packet Driver
pop si
pop ds
jc pdret ; c = failure
mov if_type,dx ; save details for access calls
mov if_class,ch
mov if_num,cl
mov if_func,al
mov if_version,bx
mov ah,pd_access ; access packets
mov al,ch ; Ethernet class
mov bx,dx ; type
mov dl,cl ; interface number
mov cx,iptypelen ; type length for iptype
mov si,offset dgroup:iptype ; address of TYPE
mov di,cs
mov es,di
mov di,offset pdrcvr ; ES:DI is our Packet Driver receiver
call pktdrvr
jc pdret ; c = failure
mov bx,ax ; put returned handle in BX
mov _arp_hardware,0001h ; Type 1 hardware, Ethernet
mov _MAC_len,6 ; 6 bytes of MAC level address
mov ax,DGROUP ; our data segment
mov es,ax ; segment of Ethernet address buffer
mov di,[bp+4+2] ; get offset of user's buffer
mov cx,eaddr_len ; length of address wanted
cmp if_class,IF_SLIP ; interface class of SLIP?
jne pdinit4 ; ne = no
mov si,offset DGROUP:SLIPmac ; get fake Ethernet address
cld
push di ; save in case PD actually uses it
rep movsb ; copy to user buffer
pop di
mov _arp_hardware,0 ; no hardware type
mov _MAC_len,0 ; no MAC level address
mov _mss,1006-44 ; set SLIP max frame size too
pdinit4:mov ah,pd_get_address ; get the Ethernet address
push bx ; save handle
call pktdrvr ; get Ethernet address to es:di buf
pop bx
pushf ; save carry flag
jnc pdinit7
cmp if_class,IF_SLIP ; interface class of SLIP?
jne pdinit7 ; ne = no
popf
clc ; forgive error of SLIP8250 v11.x
pushf
pdinit7:mov ah,pd_release ; release this Type, bx has handle
call pktdrvr
popf ; recover carry flag
pdret: mov ax,1 ; return C status, 1 for success
jnc pdret1
xor ax,ax ; 0 for failure
pdret1: pop ds ; success
pop di
pop si
pop es
mov sp,bp ; restore stack
pop bp ; recover bp reg
ret
_pdinit endp
; int pdinfo(& int version, & int class, & int pdtype, & int number,
; & int functionality)
; Get Packet Driver pedigree
public _pdinfo
_pdinfo proc near
push bp
mov bp,sp
push di
push cx
push bx
push ax ; save al for later use
mov di,[bp+4+0]
mov bx,if_version
mov [di],bx ; return version
mov al,if_class ; class
xor ah,ah
mov di,[bp+4+2]
mov [di],ax ; return as an int
mov di,[bp+4+4]
mov dx,if_type
mov [di],dx ; type
mov di,[bp+4+6]
xor ch,ch
mov cl,if_num
mov [di],cx ; interface number, as an int
pop ax ; recover al
mov di,[bp+4+8]
xor ah,ah
mov al,if_func
mov [di],ax ; functionality, as an int
mov ax,1 ; C style exit status, 1 = success
pop bx
pop cx
pop di
pop bp
ret
_pdinfo endp
; int pdclose(int handle)
; Close a Packet Driver or ODI handle.
; Returns (in AX) 1 if successful, else 0.
public _pdclose
_pdclose proc near
push bp
mov bp,sp
push bx
mov bx,[bp+4+0] ; handle
cmp useodi,0 ; using ODI?
je pdclos2 ; e = no
mov ax,bx ; get handle
call odiunbind ; unbind from LSL and MLID
jmp short pdclos3
pdclos2:mov ah,pd_release ; release_type
call pktdrvr
pdclos3:mov ax,1 ; assume success
jnc pdclos1 ; nc = success
xor ax,ax ; 0 for failure
pdclos1:pop bx
pop bp
ret
_pdclose endp
; int pdaccess(char *type, int typelen, int *handle)
; Register access for packet TYPE with the Packet Driver or ODI
; Provides a handle for the TYPE.
; Returns 1 for success, 0 for failure.
public _pdaccess
_pdaccess proc near
push bp
mov bp,sp
push es
push si
push di
push ds
push es
mov ax,dgroup ; set up data segment addressibility
mov ds,ax
mov al,if_class ; interface class (frame)
mov bx,if_type ; interface type (vendor)
mov dl,if_num ; interface number (board number)
xor dh,dh
mov si,[bp+4+0] ; get offset of packet TYPE buffer
mov cx,[bp+4+2] ; typelen (length of buf contents)
cmp useodi,0 ; using ODI?
je pdacc8 ; e = no
mov ax,[si] ; provide TYPE
call odibind ; Bind to a virtual board
jc pdacc1 ; c = fail, error code in AX
jmp short pdacc9 ; store handle returned in AX
pdacc8: cmp if_class,IF_SLIP ; SLIP?
jne pdacc3 ; ne = no
xor cx,cx ; TYPE len = 0 means accept all types
pdacc3: mov di,cs ; ES:DI is our Packet Driver receiver
mov es,di
mov di,offset pdrcvr ; local receiver
mov ah,pd_access ; set access
call pktdrvr
jc pdacc1 ; c = failure
pdacc9: mov si,[bp+4+4] ; offset of handle
mov [si],ax ; return the handle
pdacc1: mov ax,1 ; C level status, 1 = success
jnc pdacc2 ; nc = success
xor ax,ax ; 0 = failure
pdacc2: pop es
pop ds
pop di
pop si
pop es
pop bp
ret
_pdaccess endp
; int pkt_send(char *buffer, int length)
; returns 1 on success, 0 on failure
; Send a packet.
public _pkt_send
_pkt_send proc near
push bp
mov bp,sp
push es
push ds
push si
push di ; don't trust lower levels on regs
push cx
push dx
mov ax,DGROUP ; segment of outgoing buffer
mov ds,ax ; will be DS:SI for Packet Driver
cmp useodi,0 ; using ODI?
je pktsen5 ; e =no, use PD
call _odi_busy ; is transmitter busy?
or ax,ax ; returned response
jz pktsen4 ; z = not busy
stc ; fail
jmp short pktsen1
pktsen4:mov si,[bp+4+0] ; buffer's offset (seg is dgroup)
mov cx,[bp+4+2] ; buffer's length
call odixmt ; do LSL transmit with DS:SI and CX
sti ; interrupts on (odixmt turns off)
jc pktsen1 ; c = internal failure to send
or ax,ax ; LSL status
jz pktsen1 ; z = success
stc ; set carry for failure
jmp short pktsen1 ; done (AX non-zero if error)
; Note that checking for transmission errors is not readily done with async
; sending, so we cross our fingers and hope for the best.
;
; Packet Driver sending
pktsen5:mov si,[bp+4+0] ; buffer's offset (seg is dgroup)
mov cx,[bp+4+2] ; buffer's length
mov ah,pd_send ; send packet (buffer = ds:si)
call tstart ; start transmit timer
call pktdrvr ; invoke Packet Driver
call tstop ; stop transmit timer
; common exit
pktsen1:mov ax,1 ; return C level success (1)
jnc pktsen2 ; nc = success
xor ax,ax ; else C level failure (0)
pktsen2:pop dx
pop cx
pop di
pop si
pop ds
pop es
pop bp
ret
_pkt_send endp
; int odi_busy(void)
; Return non-zero if ODI is unable to accept new transmission at this time.
public _odi_busy
_odi_busy proc near
xor ax,ax ; prepare not-busy/false (0) response
cmp useodi,0 ; using ODI?
je odi_bsx ; e = no, using Packet Driver
mov cx,3 ; loop counter
odi_bs1:cmp ecbx_busy,0 ; is ODI transmit done yet?
je odi_bs2 ; e = yes, else we can't touch it yet
push bx
push cx
push bp ; playing safe
push es
mov bx,PROTSUP_RELINQUISH_CONTROL
call lslsupport ; give time to LSL and MLID
pop es
pop bp
pop cx
pop bx
loop odi_bs1 ; retry til ready or exhausted
odi_bs2:mov al,ecbx_busy ; return 0 for not-busy
xor ah,ah
odi_bsx:ret
_odi_busy endp
; Our Packet Driver receiver, far called only by the Packet Driver and our
; local ODI code (odircvr and odircmp).
; Packet buffer linked list -
; each link is db flag ; 1 = free, 2 = in use, 4 = allocated
; ; but not in use yet, 0 = end of buf,
; ; 8 = read but not freed.
; db _pktwnum ; sequential number of pkt written
; dw count ; length of data field
; db count dup (?) ; the allocated data field
; The head of the chain has a link like all others.
; The end of the chain has a link with flag == 0 and count = -BUFISZE
; to point to the beginning of the buffer (circular).
; Packet buffer garbage collection is done after a buffer has been
; transferred to us, and does so by relinking adjacent free blocks.
; _pktbuf_wrote is used to remember the link where the last write occurred
; and should be initialized to the tail link to point the next write to
; the beginning of the buffer.
; The Packet Driver and our ODI routines call this first with AX = 0 to
; obtain a buffer pointer in ES:DI from us (0:0 if we refuse the pkt) with
; CX = packet size, and again later with AX = 1 to post completion.
pdrcvr proc far ; Packet Driver receiver
or ax,ax ; kind of request (0, 1)
jz pdrcvr1 ; z = first, get-a-buffer
; Second upcall, packet has xfered, DS:SI set by caller to buffer
push ds
push si
push cx
push bx
push ax ; assume DS:SI is one of our buffers
mov ax,DGROUP
mov ds,ax ; set ds to our data segment
call rstop
or si,si ; is it legal (from first upcall)?
jz pdrcvr11 ; z = no, ignore this call
sub si,linksize ; backup to link info
mov cx,100 ; number of trials to find space
cmp byte ptr [si].flag,4 ; is this buffer allocated (4)?
jne pdrcvr8 ; ne = no, do cleanups and quit
mov byte ptr [si].flag,2 ; flag = 2 for buffer is now ready
mov si,pktbufoff ; start of packet buffer
; join contiguous free links
pdrcvr8:mov al,[si].flag ; flags byte
cmp al,1 ; is link free?
jne pdrcvr10 ; ne = no, look for a free link
pdrcvr9:mov bx,[si].count ; count (length) of this link
mov al,[bx+si+linksize].flag; flag of following link
cmp al,1 ; is next link free?
jne pdrcvr10 ; ne = no, look for free link
mov ax,[bx+si+linksize].count ; count taken from next link
add ax,linksize ; plus the next link's info field
add [si].count,ax ; add it to this count (merge links)
loop pdrcvr9 ; re-examine this new longer link
jmp short pdrcvr11 ; too many trials, abandon effort
pdrcvr10:or al,al ; end of list?
jz pdrcvr11 ; z = yes
add si,[si].count ; look at next link (add count)
add si,linksize ; and link info
loop pdrcvr8 ; keep looking
pdrcvr11:pop ax
pop bx
pop cx
pop si
pop ds
ret
pdrcvr1:push ds ; First upcall, provide buffer ptr
push dx ; return buffer in ES:SI
mov di,dgroup ; get local addressibility
mov ds,di
mov es,di ; packet buffer is in same group
cmp useodi,0 ; using Packet Driver?
jne pdrcvr1a ; ne = no, ODI, has separate rstart
call rstart ; start receive timer
pdrcvr1a:mov di,_pktbuf_wrote ; where last write occurred
or di,di ; NULL?
jz pdrcvr4 ; z = yes, write nothing
mov dl,100 ; retry counter, breaks endless loops
cmp [di].flag,1 ; is this link free?
je pdrcvr5 ; e = yes, use it
pdrcvr2:add di,[di].count ; point at next link (add count and
add di,linksize ; link overhead)
dec dl ; loop breaker count down
jz pdrcvr4 ; z = in an endless loop, exit
cmp [di].flag,1 ; is this link free (1)?
je pdrcvr5 ; e = yes, setup storage
cmp di,_pktbuf_wrote ; have we come full circle?
jne pdrcvr2 ; ne = no, keep looking
pdrcvr4:pop dx
pop ds ; failure or buffer not available (0)
xor ax,ax ; return what we received in ax
xor di,di ; return ES:DI as null to reject
mov es,di
ret
; this link is free
pdrcvr5:add cx,2 ; defense for 8/16 bit xfr mistakes
mov ax,[di].count ; length of available data space
cmp ax,cx ; cx is incoming size, enough space?
jl pdrcvr2 ; l = no, go to next link
mov [di].flag,4 ; mark link flag as being alloc'd (4)
mov dh,_pktwnum ; write pkt sequencer number
mov [di].bufnum,dh ; store in buffer, permits out of
inc _pktwnum ; temporal order deliveries
mov _pktbuf_wrote,di ; remember where we wrote last
sub ax,cx ; allocated minus incoming packet
cmp ax,60+linksize ; enough for new link and miminal pkt?
jl pdrcvr6 ; l = not enough for next pkt
mov [di].count,cx ; update space really used
push di ; save this link pointer
add di,linksize ; plus current link info
add di,cx ; plus space used = new link point
sub ax,linksize ; available minus new link info
mov [di].flag,1 ; mark new link as free (1)
mov [di].count,ax ; size of new free data area
pop di ; return to current link
pdrcvr6:add di,linksize ; point at data portion
pdrcvr7:xor ax,ax ; return what we received in ax
pop dx ; CX is size of requested buffer
pop ds ; ES:DI is the pkt buffer address
ret
pdrcvr endp
; Check for Windows enhanced mode. Return carry set if true, else carry clear.
chkwin proc near
push es
mov ah,getintv ; check for valid Int 2Fh handler
mov al,2fh ; vector 2fh
int dos ; to es:bx
mov ax,es
pop es
or ax,bx ; check if vector exists
jnz chkwin2 ; nz = yes
stc
ret
chkwin2:mov ax,1683h ; Windows 3, get current virt machine
int 2fh
cmp ax,1683h ; virtual machine, if any
je chkwin3 ; e = no Windows, ok to proceed
stc
ret
chkwin3:clc ; not Windows enhanced mode
ret
chkwin endp
; Begin Novell ODI support routines
; Note that while we use Ethernet_II (6 dest, 6 source, 2 TYPE bytes) to/from
; internal consumers the frame format to/from ODI is in the hands of ODI.
; Hopefully this will permit TCP/IP operation over all supported frame types.
; ARP/RARP packets are sized to the frame in use.
;
; Check for LSL presence, and if present then get entry points.
; Returns carry set if failure, else carry clear.
; This procedure is closely modeled upon the Novell example.
odichk proc near
cmp useodi,0 ; already inited?
je odichk0 ; e = no
clc
ret
odichk0:call chkwin ; check for Windows enhanced mode
jnc odichk5 ; nc = not, continue
ret ; return failure
odichk5:push es
mov ah,getintv ; get LSL via multiplexer interrupt
mov al,2fh ; vector 2fh
int dos ; to es:bx
mov ax,es
or ax,bx ; check if vector exists
jnz odichk1 ; nz = yes
pop es
stc
ret
odichk1:mov ax,0c000h ; look at multiplexer slots c0 et seq
push si
push di
odichk2:push ax
int 2fh
cmp al,0ffh ; is slot in use?
pop ax
je odichk4 ; e = yes, check for LSL being there
odichk3:inc ah ; next slot
or ah,ah ; wrapped?
jnz odichk2 ; nz = no, keep looking
pop di
pop si
pop es
stc ; not found, fail
ret
odichk4:mov di,si ; es:si should point to "LINKSUP$"
mov si,offset DGROUP:lslsig ; expected signature
mov cx,lslsiglen ; length
cld
repe cmpsb ; check for signature
jne odichk3 ; ne = no match, try next Int 2fh slot
mov word ptr lslinit,bx ; found entry, save init entry point
mov ax,es ; returned in es:bx
mov word ptr lslinit+2,ax
mov ax,ds
mov es,ax ; get LSL main support/service addrs
mov si,offset DGROUP:lslsupport ; address of LSL entry point array
mov bx,2 ; request support/service entry points
; fills in far addresses of lslsupport and lslservice routines
call lslinit ; call LSL initialization routine
pop di
pop si
pop es
clc ; success
ret
odichk endp
; Bind a protocol TYPE to an ODI virtual board.
; Enter with TYPE (big endian/network order) in AX.
; Packet reception begins immediately upon a successful bind.
; Uses NET.CFG if information is available.
; Obtain StackID (our ident to the LSL), ProtID (ident of LSL's decoder),
; and boardnumber (the logical board), then bind to start reception. Do for
; one of our protocols.
; Returns PD handle (TYPE) in AX and carry clear upon success, else carry set.
odibind proc near
push ax
push bx
push si
push di
push es
mov bx,DGROUP
mov es,bx
cmp ax,ip_type ; IP, 0x0008h?
jne odibind1 ; ne = no
mov ax,offset DGROUP:ip_string ; put IP string in request
mov bx,offset ip_rcvr ; set address of receiver esr
mov di,offset DGROUP:ip_stackid ; set address of stackid struc
jmp short odibind3
odibind1:cmp ax,arp_type ; ARP, 0x0608?
jne odibind2 ; ne = no
mov ax,offset DGROUP:arp_string
mov bx,offset arp_rcvr
mov di,offset DGROUP:arp_stackid
jmp short odibind3
odibind2:cmp ax,rarp_type ; RARP, 0x3580?
je odibind2a ; e = yes
jmp odibindx ; ne = no, fail
odibind2a:mov ax,offset DGROUP:rarp_string
mov bx,offset rarp_rcvr
mov di,offset DGROUP:rarp_stackid
odibind3:mov word ptr registerstk.StackNamePtr,ax ; insert ptr to string
mov word ptr registerstk.StackReceiveHandler,bx ; setup esr addr
; Note: to use Prescan or Default registrations delete StackNamePtr & StackID.
; StackID is not used with these latter methods, and their reception begins
; at registration rather than at bind (so this area would be redesigned).
mov bx,PROTSUP_REGISTER_STACK ; register the protocol by name
mov si,offset DGROUP:registerstk ; registration form pointer
push di ; save ptr to xxx_stackid storage
call lslsupport ; call LSL with the address in es:si
pop di
jz odibind3a ; z = success
jmp odibindx ; nz = failure
odibind3a:mov [di].pstack,bx ; save returned StackID (LSL's handle
; for our protocol stack)
cmp readnetcfg,0 ; have read NET.CFG for BIND info?
jne odibind4 ; ne = yes
mov useboard,-1 ; clear board-to-use word
call getbind ; find Kermit's bind board in NET.CFG
inc readnetcfg ; say have read the file
cmp word ptr bdname,256*'#'+2 ; is board name #<digit>?
jne odibind4 ; ne = no, assume regular driver name
mov al,bdname+2 ; get ascii digit
sub al,'1' ; remove ascii bias (external=1 based)
xor ah,ah ; but we are zero based internally
cmp al,8 ; arbitrary limit of 8 boards
ja odibind4 ; a = out of range, ignore value
mov useboard,ax ; and make this the board number
mov bdname,0 ; and don't use bdname as a name
odibind4:mov [di].pboard,0 ; assume board zero to start loop
mov ax,useboard ; board to be used, if any
or ax,ax ; boards 0 and up are legal
jl odibind5 ; l = no board found yet, search
mov [di].pboard,ax ; specify board, get ProtID
odibind5:mov bx,PROTSUP_GET_MLID_CTL_ENTRY ; get MLID control entry
mov ax,[di].pboard ; for this board
push di
call lslsupport ; call LSL for the address to es:si
pop di
mov word ptr mlidcont,si
mov word ptr mlidcont+2,es ; MLID control routine
jz odibind7 ; z=success, have a board to work with
cmp ax,LSLERR_NO_MORE_ITEMS ; out of items?
je odibind5a ; e = yes, no more boards
cmp ax,LSLERR_ITEM_NOT_PRESENT ; other boards may exist?
je odibind7 ; e = yes
odibind5a:jmp odibindx ; fail
odibind7:mov bx,PROTSUP_GET_PID_PROTNUM_MLIDNUM ; get ProtID from StackID
mov ax,[di].pstack ; StackID
mov cx,[di].pboard ; and assumed board number
mov si,dgroup
mov es,si ; set es:di to the ProtID buffer
lea si,[di].pprotid ; in our storage slot per protocol
push di
call lslsupport ; ask LSL for the ProtID string
pop di ; to that 6-byte buffer
jz odibind9 ; z = success, found a recognizer
cmp useboard,0 ; has a board been pre-identified?
jge odibind5a ; ge = yes, so the matchup failed
inc [di].pboard ; next board
jmp short odibind5 ; keep looking for a board
odibind9:mov bx,GET_MLID_CONFIGURATION ; get MLID config ptr to es:si
mov ax,[di].pboard
call mlidcont ; call MLID control routine
jnz odibindx ; nz = failure
cmp bdname,0 ; was a board name bound via BIND?
je odibin10 ; e = no, don't check on it
push es ; save pointer to MLID config table
push di
push si
les di,es:[si].MCardShortName ; get short name of this board
lea si,bdname ; desired board name string
mov cl,bdname ; length of desired board name
inc cl ; include length byte
xor ch,ch
cld
repe cmpsb ; compare len,string for both
pop si
pop di
pop es
je odibin10 ; e = found desired board
inc [di].pboard ; try next board
jmp short odibind5 ; keep looking for the desired board
odibin10:mov ax,[di].pboard ; get current board number
mov useboard,ax ; remember for next protocol
mov ax,es:[si].MWorstDataSize ; max header, leaving this size
sub ax,20+20 ; minus IP and TCP headers
mov _mss,ax ; set new operational value
mov bx,es:[si].MFrameID ; frame ident, for get_hwd
call get_hwd ; get hardware specifics
push es
push si ; save config pointer
lea si,es:[si].MNodeAddress ; point to address in config struct
push ds ; save ds
push di
mov di,offset DGROUP:_eth_addr; where our MAC address is stored
mov ax,ds
mov cx,es
mov es,ax
mov ds,cx
cld
mov cx,6 ; MAC address length, bytes, fixed
rep movsb ; copy MAC address to global array
pop di
pop ds
pop si ; recover configuration table pointer
pop es
mov tells_len,0 ; presume no lookahead data length
test es:[si].MFlags,Len_Info ; capas bit for length provided (new)
jz odibin12 ; z = does not provide
inc tells_len ; say provides length
odibin12:mov bx,PROTSUP_BIND_STACK_TO_MLID ; Bind stack to MLID
mov ax,[di].pstack ; StackID
mov cx,[di].pboard ; board number
call lslsupport ; bind our protocol stack to board
jnz odibindx ; nz = failure
pop es ; received packets can interrupt now
pop di
pop si
pop bx
pop ax
clc
ret
odibindx:pop es
pop di
pop si
pop bx
pop ax
stc ; say failure
ret
odibind endp
; Worker for odibind. Find NET.CFG, extract name of board driver from pair of
; lines reading as below (Protocol must be in column 1, bind must be indented)
; Protocol Kermit Kermit's main section header
; bind <board_driver_name> indented, without the <> signs
;or
; Protocol Kermit
; bind #<digit> selects DOS driver load order (from 1)
;
; Examples -
; Protocol Kermit
; bind exos
;or
; Protocol Kermit
; bind #2
; and elsewhere there is the board driver section:
; Link Driver exos
;
; If found put the board driver name in array bdname, as length byte, string,
; then a null. If not found make length byte bdname be zero. We treat NET.CFG
; as case insensitive.
; Unless we use the special Kermit section then LSL will assign to us the
; first board loaded by DOS supporting the frame kind of our protocol.
; Link Driver section line "Protocol name type frame" simply associates a
; frame kind with the name and type, but not with a board. L.D. section line
; frame <frame kind> attaches that frame kind to the board, if it fits.
; Kermit uses "name" in the above line to pinpoint a protocol, not a board.
; Add keyword MYIP to obtain a dynamically assigned IP value from Telebit's
; ODI PPP driver. The user must say "Set TCP Address Telebit-PPP" for this
; to have effect.
getbind proc near
mov bdname,0 ; clear board name length
mov _tempip,0 ; clear dynamic IP string count
push ds
mov bx,GENSERV_GET_NETCFG_PATH ; get fully formed NET.CFG name
call lslservice ; from LSL general services to ds:dx
jz getbin1 ; z = success
pop ds ; fail
ret
getbin1:mov ah,fopen ; open file NET.CFG
mov al,40h ; for reading, deny none
int dos ; returns file handle in ax
pop ds
mov temp,ax ; save handle for getbyte
jnc getbin2 ; nc = success
ret ; carry set for failure
getbin2:mov bx,1 ; subscript, at start of a line
getbin3:call getbyte ; read a byte, uppercased
jnc getbin4 ; nc = success
ret ; c = end of file
getbin4:cmp protword[bx],al ; compare to "PROTOCOL"
jne getbin5 ; ne = failure, scan for end of line
inc bx
cmp bl,protword ; length, matched all bytes?
jbe getbin3 ; be = no, match more
jmp short getbin6 ; ae = yes, next phrase
ret ; fail out
getbin5:cmp al,LF ; end of a line?
je getbin2 ; e = yes, scan for PROTOCOL again
call getbyte
jnc getbin5 ; keep consuming line material
ret ; fail out at end of file
; Short Name following "PROTOCOL"
getbin6:call getbyte ; get separator char, discard
jnc getbin7
ret ; c = eof
getbin7:call getbyte ; read short name of protocol
jnc getbin7a ; nc = text
ret ; return on eof
getbin7a:cmp al,' ' ; white space?
jbe getbin7 ; be = yes, stay in this state
mov bx,1 ; subscript
getbin8:cmp psname[bx],al ; compare to our protocol short name
jne getbin5 ; ne = failure, scan for end of line
cmp bl,psname ; matched all bytes?
jae getbin9 ; ae = yes, next phrase
inc bx
call getbyte ; get next byte to match
jnc getbin8 ; nc = not eof yet
ret
getbin9:call getbyte ; go to next line, enforce whitespace
jc getbin20 ; c = eof
cmp al,LF ; end of a line?
jne getbin9 ; ne = no, scan for end of line
call getbyte ; look for whitespace
jc getbin20 ; c = eof
cmp al,'#' ; comment line?
je getbin9 ; e = yes, get next line
cmp al,';' ; comment line?
je getbin9 ; e = yes, get next line
cmp al,' ' ; required whitespace?
ja getbin5 ; a = no, start over
getbin10:call getbyte ; look for keyword "BIND"
jc getbin20
cmp al,' ' ; white space?
jbe getbin10 ; be = yes, stay in this state
mov bx,1 ; subscript
cmp al,'M' ; M for MYIP?
je getbin30 ; e = yes
cmp al,'B' ; B for BIND?
jne getbin9 ; ne = no, next line
getbin12:cmp bdname,0 ; have bind name yet?
je getbin13 ; e = no
jmp getbin9 ; else get next line
getbin13:cmp bindword[bx],al ; compare to "BIND"
jne getbin9
cmp bl,bindword ; matched all bytes?
jae getbin14 ; ae = yes, next phrase
call getbyte
jc getbin20 ; c = eof
inc bx
jmp short getbin13 ; keep reading
getbin14:call getbyte ; skip white space before board name
jc getbin20
cmp al,' ' ; white space?
jbe getbin14 ; be = yes, skip it
getbin15:mov bl,bdname ; board name, length byte, starts at 0
xor bh,bh
inc bl
mov bdname,bl ; update length of board driver name
xor ah,ah ; get a null
mov word ptr bdname[bx],ax ; store as board short name,null
cmp bx,15 ; legal limit on short name?
jbe getbin16 ; be = ok
mov bdname,ah ; illegal, clear board name length
jmp getbin9 ; get next line
getbin16:call getbyte
jc getbin20 ; reached eof, is ok
cmp al,' ' ; usable text?
ja getbin15 ; a = yes, else stop storing name
jmp getbin9 ; get next line
getbin20:ret
getbin30:cmp _tempip,0 ; have IP word already?
je getbin31 ; e = no
jmp getbin9 ; get new line
getbin31:cmp myipword[bx],al ; compare to "MYIP"
jne getbin9 ; ne = failure, start over
cmp bl,myipword ; matched all bytes?
jb getbin32 ; b = no
jmp short getbin33
getbin32:call getbyte
jc getbin20 ; c = eof
inc bx
jmp getbin31 ; keep reading
getbin33:call getbyte ; skip white space before IP address
jc getbin20
cmp al,' ' ; white space?
jbe getbin33 ; be = yes, skip it
getbin34:mov bl,_tempip ; our IP, length byte, starts at 0
xor bh,bh
inc bl
mov _tempip,bl ; update length of IP string
xor ah,ah ; get a null
mov word ptr _tempip[bx],ax ; store as IP, null
cmp bx,15 ; legal limit on IP?
jbe getbin35 ; be = ok
mov _tempip+1,ah ; illegal, clear IP
jmp getbin9 ; and quit
getbin35:call getbyte
jc getbin36 ; reached eof, is ok
cmp al,' ' ; usable text?
ja getbin34 ; a = yes, else stop storing name
jmp getbin9 ; get next line
getbin36:ret
getbind endp
; Worker for getbind. Delivers one byte per call from NET.CFG, upper cased.
; Returns carry set and NET.CFG file closed at end of file.
; Temp has NET.CFG file handle, tempb is our one byte buffer for disk i/o.
getbyte proc near
mov dx,offset tempb ; ds:dx points to start of buffer
mov ah,fread ; read from file to buffer
mov cx,1 ; this many bytes
push bx
mov bx,temp ; get file handle
int dos
pop bx
jc getbyt2 ; c = failure
cmp ax,1 ; got the single byte?
jb getbyt2 ; b = no, failure
mov al,tempb ; return read byte
cmp al,'z' ; in lower case range?
ja getbyt1 ; a = no
cmp al,'a' ; in lower case range?
jb getbyt1 ; b = no
and al,not 20h ; lower to upper case
getbyt1:clc ; carry clear for success
ret ; return char in AL
getbyt2:push bx
mov bx,temp ; file handle
mov ah,fclose ; close the file
int dos
pop bx
stc ; say EOF or other failure
ret
getbyte endp
; Worker for odibind.
; Enter with BX holding Novell frame type from the MLID configuration table.
; Set _arp_hardware and _MAC_len and return BX holding _MAC_len value, else
; if frame is not supported return BX = 0. These two values are needed by the
; ARP functions. This list searching method is to accomodate the ever
; expanding quantity of frame types appearing with ODI; we deal with those we
; understand (sic).
get_hwd proc near
push es
push di
mov ax,DGROUP
mov es,ax
mov al,bl ; get frame value (MLID config)
xor bx,bx ; prepare no-match return value
mov di,offset frame_type ; list to search
mov cx,num_frames ; number of elements in the list
cld
repne scasb ; byte search
jne get_hwd1 ; ne = no match, fail
sub di,offset frame_type+1 ; make di be an index along the list
mov al,hardware_type[di] ; ARP/RARP hardware type ident
xor ah,ah ; return in local (host) order
mov _arp_hardware,ax ; hardware type for ARP/RARP pkts
mov bl,frame_adlen[di] ; array of MAC lengths for frame types
xor bh,bh
mov _MAC_len,bx ; save MAC address length (1..6 bytes)
pop di
pop es
ret
get_hwd1:mov _arp_hardware,bx ; hardware type 0 for ARP/RARP pkts
mov _MAC_len,bx ; save MAC address length (0 bytes)
pop di
pop es
ret ; return _MAC_len in BX
get_hwd endp
; Unbind a protocol TYPE from an ODI virtual board
; Enter with protocol TYPE (net order) in AX, return carry set if failure.
; The TYPE is used as our handle to the application.
; Prescan and Default methods call lslsupport with the board number in AX
; rather than StackID and use matching PROTSUP_DEREGISTER_* code.
odiunbind proc near
cmp ax,ip_type ; IP, 0x0008h?
jne odiunb1 ; ne = no
mov ax,ip_stackid.pstack ; StackID
jmp short odiunb3
odiunb1:cmp ax,arp_type ; ARP, 0x0608?
jne odiunb2 ; ne = no
mov ax,arp_stackid.pstack
jmp short odiunb3
odiunb2:cmp ax,rarp_type ; RARP, 0x3580?
jne odiunb4 ; ne = no
mov ax,rarp_stackid.pstack
odiunb3:mov bx,PROTSUP_DEREGISTER_STACK ; deregister stack (StackID in AX)
call lslsupport ; stops reception now
jnz odiunb4 ; nz = failure
clc ; success
ret
odiunb4:stc ; failure
ret
odiunbind endp
; ODI receive interrupt handler, for use only by the LSL.
; Called with DS:DI pointing to lookahead structure, interrupts are off.
; Returns ES:SI pointing at ECB, AX = 0 if we want pkt, AX = 8001h if decline.
; There are three of these, one each for IP, ARP, and RARP. All have the
; same calling convention and all jump to odircv to do the real work.
; The length of the arriving packet is available if the MLID supports the
; new (mid-May 1992) capability, our "tells_len"; otherwise we make an
; intelligent guess based on the protocol header. These entry points can be
; called multiple times before receive-completion, and likely will be, so we
; use several ecb's to accept requests.
ip_rcvr proc far
push bx
push cx
push di
push ds
mov cx,ds ; DS:DI from LSL
mov es,cx ; use ES for LSL items
mov ax,DGROUP ; set DS to our data segment
mov ds,ax
call rstart ; start timer
push es
push si
cmp tells_len,0 ; have data length available?
je ip_rec1 ; e = no
les si,es:[di].DataLookAheadDataSize ; ptr to what it says
mov cx,word ptr es:[si] ; get length of data field
add cx,4 ; for overzealous board transfers
pop si
pop es
jmp far ptr odircv
ip_rec1:les si,es:[di].LookAheadPtr ; point at data lookahead ptr
mov cx,word ptr es:[si+2] ; IP pkt header, length word
cmp byte ptr es:[si],45h ; validate IP pkt kind (ver/hlen)?
pop si
pop es
jne ip_rcvr1 ; ne = invalid, decline
xchg ch,cl ; net to local order
add cx,14+2 ; our MAC level addressing + 2 safety
ip_rec2:mov ax,ip_stackid.pstack ; StackID for ecb structure
mov rcvtype,ip_type ; store protocol TYPE int
jmp odircv
ip_rcvr1:add pstats.PIgnoredRxPackets,1 ; update ODI statistics counter
adc pstats.PIgnoredRxPackets+2,0
mov ax,LSLERR_OUT_OF_RESOURCES ; decline the packet
or ax,ax ; set Z flag to match AX
pop ds
pop di
pop cx
pop bx
ret
ip_rcvr endp
; RARP protocol receive service routine, similar to ip_rcvr.
rarp_rcvr proc far
push bx
push cx
push di
push ds
mov cx,ds ; DS:SI from LSL
mov es,cx
mov ax,DGROUP ; set DS to our data segment
mov ds,ax
mov ax,rarp_stackid.pstack ; StackID for ecb structure
mov rcvtype,rarp_type ; store protocol TYPE int
jmp short arp_common ; do ARP/RARP common code
rarp_rcvr endp
; ARP protocol receive service routine, similar to ip_rcvr.
arp_rcvr proc far
push bx
push cx
push di
push ds
mov cx,ds ; DS:SI from LSL
mov es,cx
mov ax,DGROUP ; set DS to our data segment
mov ds,ax
mov ax,arp_stackid.pstack ; StackID for ecb structure
mov rcvtype,arp_type ; store protocol TYPE int
arp_common: ; common code for ARP/RARP
push es
push si
cmp tells_len,0 ; have data length available?
je arp_com1 ; e = no
les si,es:[di].DataLookAheadDataSize ; ptr to what it says
mov cx,word ptr es:[si] ; get length of data field
add cx,4 ; for overzealous board transfers
pop si
pop es
jmp short odircv
arp_com1:les si,es:[di].LookAheadPtr ; point at lookahead ptr for Data
mov cx,word ptr es:[si+4] ; ARP/RARP pkt header, length bytes
add cl,ch ; add HA and IP address lengths
xor ch,ch
add cl,cl ; for host and target
adc ch,0
add cx,8 ; plus ARP/RARP main header
cmp word ptr es:[si+2],ip_type ; ARP/RARP Protocol type of IP?
pop si
pop es
jne ip_rcvr1 ; ne = invalid, decline
; fall through to odircv
arp_rcvr endp
; General worker for ip_rcvr, arp_rcvr, rarp_rcvr. These are invoked by the
; LSL when their kind of packet arrives. This module creates the ECB and
; dispatches it to the LSL. Operating at LSL interrupt level.
; ES:DI is ptr to ODI Lookahead structure, DS is our data seg (DGROUP).
; AX is stackid for invoked protocol kind, CX is (guessed) pkt length overall.
; Rcvtype is the current invoked protocol TYPE (0008h is IP etc).
; When done store in the ecb's protocolws array:
; dw protocol TYPE (0008h for IP etc)
; dw subscript of this ecbr item (for use by odircmp)
; dw <unused>,<unused>
; Return ES:SI as pointer to a free ecb and AX = 0 to accept pkt, else
; return AX = 8001h to decline pkt (and to ignore ES:SI). Set Z flag to
; match AX value.
odircv proc far
add pstats.PtotalRxPackets,1 ; update ODI statistics counter
adc pstats.PtotalRxPackets+2,0
push ax
push cx
mov cx,ecbr_qty ; number of receive ecb's
xor bx,bx ; find a free ecb for this packet
mov ax,offset DGROUP:ecbr ; start of receive ecbs
odircv8:cmp ecbr_busy[bx],0 ; is this ECB free?
jne odircv9 ; ne = no, try next
mov ecbr_num,bx ; remember index for end of proc
mov bx,ax ; offset of free ecb
pop cx
pop ax
jmp short odircv2 ; use ds:[bx] for address of ecb
odircv9:inc bx ; next byte in busy array
add ax,size ecbstruct ; size of an ecb
loop odircv8
pop cx ; failed to find a free ecbr
pop ax
odircv1:add pstats.PIgnoredRxPackets,1 ; update ODI statistics counter
adc pstats.PIgnoredRxPackets+2,0
mov ax,LSLERR_OUT_OF_RESOURCES ; decline the packet
or ax,ax ; set Z flag for ODI
pop ds
pop di
pop cx
pop bx
ret
; ds:[bx] is ptr to a free ecbr
odircv2:mov [bx].stackid,ax ; StackID from odircv entry points
mov ax,es:[di].LBoardNum ; boardnum from ProtocolID lookahead
mov [bx].boardnum,ax ; store in ecbr
mov ax,rcvtype ; get TYPE from odircv entry points
mov word ptr [bx].protocolws,ax ; save TYPE for odircmp
mov ax,ecbr_num ; ecbr index
mov word ptr [bx].protocolws+2,ax ; save index for odircmp
cmp cx,46 ; min packet for Ethernet + 4 spare
jae odircv3 ; ae = no padding needed here
mov cx,46 ; padded min pkt plus 4 spare bytes
odircv3:push bx ; get a buffer of length CX bytes
xor ax,ax ; set AX = 0 for PD "get buf" call
call pdrcvr ; use PD buffer allocator code
pop bx ; ES:DI = buffer pointer, CX = length
mov ax,es
or ax,di ; check for refused pkt (es:di = NULL)
jz odircv1 ; z = pkt refused (no buffer space)
add di,6+6+2 ; skip our MAC header for ecb use
sub cx,6+6+2 ; less same length for MLID
mov [bx].frag1addr,di ; offset of buffer which MLID sees
mov ax,es
mov [bx].frag1addr+2,ax ; seg of buffer
mov [bx].datalen,cx ; length of buffer for MLID/LSL use
mov [bx].frag1len,cx ; ditto
mov ax,DGROUP ; segment of our ecb's
mov es,ax
mov si,bx ; return ES:SI pointing to ECB
mov bx,ecbr_num ; get ecbr index
mov ecbr_busy[bx],1 ; mark this ecbr as busy
pop ds
pop di
pop cx
pop bx
xor ax,ax ; return AX = 0 to accept
ret
odircv endp
; ODI receive-complete call-back routine for use only by the LSL.
; Enter with ES:SI pointing at ECB, interrupts are off.
; Returns nothing.
; There is no guarantee that this routine will be called in the sequence
; which packets arrived, so we carry the bookkeeping in the delivered ECB:
; TYPE is for Ethernet_II struct results, ecbr_busy is don't-touch interlock.
; Note that we have to construct our own "destination" MAC address.
; es:[si].status is 0 (success), 8006h (buffer overrun), 8007h (canceled).
; StackID field is from LSL, and it's 0ffffh if using Prescan, and undefined
; if using Default. The manual says LSL, but not MLID, calls are ok in here.
odircmp proc far
push ds
push ax
push bx
push cx
mov ax,DGROUP
mov ds,ax ; set ds to our data segment
cmp es:[si].frag1addr+2,0 ; segment of pkt being confirmed
je odircmp6 ; e = illegal, ignore this call
cmp es:[si].status,0 ; check ECB status for failure
je odircmp1 ; e = success
mov es:[si].protocolws,0 ; write TYPE of 0 to permit queueing
odircmp1:push di ; put dest,src,TYPE into pkt buffer
push es
push si ; save ecbr's si
mov cl,byte ptr es:[si].driverws ; kind of destination, from LSL
mov di,es:[si].frag1addr ; start of our pkt buffer + 6+6+2
sub di,6+6+2 ; back to start, for our MAC header
push ds ; WATCH this, presumes ES == DS!
pop es ; set ES to DS (where pkt buffer is)
mov si,offset DGROUP:_eth_addr ; our hardware address
cmp cl,ECB_BROADCAST ; a broadcast?
jne odircmp2 ; ne = no, use our address
mov si,offset DGROUP:bcast ; fill with all 1's
odircmp2:mov cx,3 ; 6 byte addresses to our application
cld
rep movsw ; store source address in pkt buffer
pop si ; recover ecb si
push si ; save it again
mov ax,es:[si].protocolws ; get TYPE, from odircv
lea si,es:[si].immaddr ; offset to MAC address of sender
mov cx,3 ; three words worth, garbage and all
rep movsw ; copy to packet buffer
stosw ; and write TYPE to packet buffer
pop si
pop es
pop di
mov cx,es:[si].datalen ; length of data field from ecb
add cx,6+6+2 ; plus space for dest,src,TYPE
push es ; save ecb's es:si
push si
mov si,es:[si].frag1addr ; offset of pkt being confirmed
sub si,6+6+2 ; adj to beginning, for our MAC header
mov ax,1 ; set AX = 1 for buffer done call
call pdrcvr ; do post processing of buffer
pop si
pop es
xor ax,ax
mov es:[si].frag1addr+2,ax ; clear pkt buffer pointer (seg)
mov es:[si].protocolws,ax ; clear packet TYPE
mov bx,es:[si].protocolws+2 ; point to ecb index
mov ecbr_busy[bx],al ; say this ecb is free now
odircmp6:
call rstop ; stop receive timer
pop cx
pop bx
pop ax
pop ds
ret
odircmp endp
; ODI transmission routine
; Enter with ds:si pointing at full Ethernet_II packet, cx = length (bytes)
; Once sent the ecb belongs to ODI until the xmt-complete routine is called.
odixmt proc near
push si
push di
push es
mov ax,ds
mov es,ax
mov ax,cx ; overall Ethernet_II length
sub ax,6+6+2 ; omit our MAC header
jc odixmt6 ; c = failure, abandon
mov ecbx.datalen,ax ; setup ECB overall data
mov ecbx.frag1len,ax ; and fragment length
mov cx,3 ; three words of dest MAC address
mov di,offset DGROUP:ecbx.immaddr ; destination address in ecb
rep movsw ; copy destination MAC address
add si,6 ; skip source Ethernet address
lodsw ; get protocol TYPE, move it to AX
mov ecbx.frag1addr,si ; offset of packet data
mov si,ds
mov ecbx.frag1addr+2,si ; segment of packet data
; Note: Prescan and Default methods use Raw Send: put 0ffffh in StackID
; and include the full frame header in the data field. Check MLID
; configuration word ModeFlags, MRawSendBit, for Raw Send capability.
cmp ax,ip_type ; IP, 0x0008h?
jne odixmt1 ; ne = no
mov si,offset DGROUP:ip_stackid
jmp short odixmt3
odixmt1:cmp ax,arp_type ; ARP, 0x0608?
jne odixmt2 ; ne = no
mov si,offset DGROUP:arp_stackid
jmp short odixmt3
odixmt2:cmp ax,rarp_type ; RARP, 0x3580?
jne odixmt6 ; ne = error, do not send
mov si,offset DGROUP:rarp_stackid
odixmt3:mov cx,5 ; stackid, protid, boardnum
mov di,offset DGROUP:ecbx.stackid ; get stack ident area
rep movsw ; copy to ecbx
mov ecbx_busy,1 ; set ECBx busy flag to busy state
add pstats.PtotalTxPackets,1 ; update ODI statistics counter
adc pstats.PtotalTxPackets+2,0
mov si,offset DGROUP:ecbx ; set es:si to ECB
mov bx,PROTSUP_SEND_PACKET ; send it
call tstart ; start transmit timer
call lslsupport ; call LSL with ecbx ptr in es:si
call tstop ; stop transmit timer
clc ; success so far, ints are still off
odixmt6:pop es
pop di
pop si
ret
odixmt endp
; ODI transmission-complete processor, for use only by the LSL.
; Returns nothing. Unlocks ECB busy flag.
odixcmp proc far
push ds
push ax
mov ax,DGROUP ; set addressibility
mov ds,ax
mov ecbx_busy,0 ; set ECB busy flag to not-busy
pop ax
pop ds
ret
odixcmp endp
; ODI Protocol (that's us) Control routine, required, called from outside.
; In principle we should have one of these for each protocol (IP, ARP, RARP)
; by putting a different StackControlHandler address in registerstk, but I
; doubt that anyone is that interested in such detailed counts.
; Return AX clear, Z flag set, and ES:SI as table pointer if success, else
; return AX with error code and Z flag clear.
pcontrol proc far
cmp bx,GET_STACK_CONFIGURATION ; get stack configuration?
jne pcont1 ; ne = no
mov si,DGROUP
mov es,si ; es:si points to configuration table
mov si,offset DGROUP:pconfig; the table
xor ax,ax ; set Z flag
ret
pcont1: cmp bx,GET_STACK_STATISTICS ; get stack statistics?
jne pcont2 ; ne = no
mov si,DGROUP
mov es,si ; es:si points to statistics table
mov si,offset DGROUP:pstats ; the table
xor ax,ax ; set Z flag
ret
pcont2: mov ax,LSLERR_OUT_OF_RESOURCES ; other functions, report error
or ax,ax ; clear Z flag
ret
pcontrol endp
; timer support routines
rstart proc near
pushf
cmp _kdebug,0
je rstart1
push ax
in al,40h ; 8254 timer channel T0, read LSB
xchg ah,al
in al,40h ; read MSB
xchg al,ah ; order properly
mov startrtic,ax
pop ax
rstart1:popf
ret
rstart endp
rstop proc near
pushf
cmp _kdebug,0
je rstop1
push ax
in al,40h ; 8254 timer channel T0, read LSB
xchg ah,al
in al,40h ; read MSB
xchg al,ah ; order properly
mov stoprtic,ax
pop ax
rstop1: popf
ret
rstop endp
tstart proc near
pushf
cmp _kdebug,0
je tstart1
push ax
in al,40h ; 8254 timer channel T0, read LSB
xchg ah,al
in al,40h ; read MSB
xchg al,ah ; order properly
mov startttic,ax
pop ax
tstart1:popf
ret
tstart endp
tstop proc near
pushf
cmp _kdebug,0
je tstop1
push ax
in al,40h ; 8254 timer channel T0, read LSB
xchg ah,al
in al,40h ; read MSB
xchg al,ah ; order properly
sti ; for odixmt
mov stopttic,ax
call showtime ; display timing results
pop ax
tstop1: popf
ret
tstop endp
showtime proc near
push es
push di
push bx
mov bx,data
mov es,bx
mov al,es:crt_cols ; screen columns
mul es:crt_lins ; screen lines
mov bx,ax
add bx,40 ; column 40
shl bx,1
mov di,es:tv_sego ; current screen offset
add bx,di
mov di,es:tv_segs ; current screen segment
mov es,di
mov ax,startrtic
sub ax,stoprtic
jns showti1
neg ax
showti1:sub ax,overhead
mov di,bx
call decout
mov byte ptr es:[di],'R'
mov ax,startttic
sub ax,stopttic
jns showti2
neg ax
showti2:sub ax,overhead
mov di,bx
add di,5*2
call decout
mov byte ptr es:[di],'T'
pop bx
pop di
pop es
ret
showtime endp
decout proc near ; display decimal number in ax
push cx
push dx
mov byte ptr es:[di],' ' ; clear screen of old info
mov byte ptr es:[di+2],' '
mov byte ptr es:[di+4],' '
mov byte ptr es:[di+6],' '
mov byte ptr es:[di+8],' '
test ax,8000h ; negative?
jz decout1 ; z = no
neg ax
mov byte ptr es:[di],'-' ; minus sign
inc di
inc di
decout1:mov cl,t0count ; timer chip T0 count factor
mov dx,2000
shr dx,cl ; t0count compensation (by 1 or 2 shifts)
mul dx
mov cx,1193
div cx
mov cx,10 ; set the numeric base
call mvalout ; convert and output value
pop dx
pop cx
ret
decout endp
mvalout proc near ; output number in ax using base in cx
; corrupts ax and dx
xor dx,dx ; clear high word of numerator
div cx ; (ax / cx), remainder = dx, quotient = ax
push dx ; save remainder for outputting later
or ax,ax ; any quotient left?
jz mvalout1 ; z = no
call mvalout ; yes, recurse
mvalout1:pop dx ; get remainder
add dl,'0' ; make digit printable
cmp dl,'9' ; above 9?
jbe mvalout2 ; be = no
add dl,'A'-1-'9' ; use 'A'--'F' for values above 9
mvalout2:mov es:[di],dl
inc di
inc di
ret
mvalout endp
_TEXT ends
end