;---------------------------------------------------------------------------
;                  PCMCIA ethernet card driver for A600/A1200
;---------------------------------------------------------------------------
;
; HISTORY:
;
; 10-4-97  v0.1 - Created by Bruce Abbott (bhabbott@inhb.co.nz)
;                      *** First Aminet Release ***
;
; 29-4-97  v0.2 - Implemented CMD_ONLINE/OFFLINE/FLUSH (for Miami).
;
;               - Enabled interrupts during RemoteWrite (no more serial
;                 port overruns!).
;
; 6-5-97   v0.3 - Added a flag so that we won't try to ReleaseCard()
;                 unless there was a successful OwnCard()!
;
; 17-5-97  v0.4 - CMD_CONFIGINTERFACE now overrides the default hardware
;                 address (for Miami).
;
;               - Device now goes offline if the PCMCIA card is removed.
;
;               - CMD_ONEVENT implemented.
;
;               - Loosened hardware address verification to accept the
;                 Accton EN2216.
;
;               - Unrolled loops to improve data transfer speed. Now
;                 about 20% faster on an unexpanded A600.
;
;               - Hack to fix problem with missed interrupts. Now we
;                 clear the Gayle interrupt bits instead of letting
;                 card.resource do it for us.
;
;                        *** Second Aminet Release ***
;
; 29-7-97  v0.5 - Accepts 802.3 packets (untested).
;
;               - Sets BROADCAST bit in io_flags when appropriate.
;
;               - Now examines PCMCIA attribute memory to determine
;                 value to write into Card Configuration Register.
;
;               - If attribute memory not found, tries to open the file
;                 "s:cnetdev.config" to get Card Configuration Register
;                 offset, Configuration ID, and ROM Station Address.
;
;                        *** Third Aminet Release ***
;
;22-12-97 v0.6 - I/O addressing now supports 64KByte range (was 1KB).
;
;              - Tighter data transfer loops, slightly faster on '030.
;
;              - Accepts all Card Configuration Register address sizes.
;
;              - Removed cnet.config stuff (replaced with hardware mod).
;
;              - Now cards are hot-swappable.
;
;              - Un-supported commands now return S2ERR_NOT_SUPPORTED.
;
;                        *** 4th Aminet Release ***
;
;21-4-98 v0.7  - fixed harmless bug in init_nic - remote DMA timeout test
;                could not fail!
;
;              - Now remembers copytobuf/copyfrombuf tags for each opener.
;                Allows simultaneous use of eg. AmiTCP and Envoy.
;
;              - OnEvent types ONLINE and OFFLINE now return immediately
;                if the device is already in the wanted state.
;
;                        *** 5th Aminet Release ***
;
;17-12-98 v0.8 - fixed bug in OnEvent: was using wrong register to test
;                ONLINE flag.
;
; 1-6-99 v0.9  - Added support for multicasting, statistics, orphan packets,
;                exclusive and promiscuous modes.
;
;                        *** 6th Aminet Release ***
;
; 5-7-99 v1.0  - Fixed bug in readpacket: MCAST flag was also set when packet
;                was BCAST.
;
;                        *** 7th Aminet Release ***
;
; 7-7-99 v1.1  - Fixed bug: delmulticast always failed due to bad testing
;                for multicast address match.
;
;              - Fixed bug: readpacket was getting incorrect src and dst
;                addresses (A4 was pointing to prhdr instead of etherhdr).
;
;              - Included put_multi in init_nic (need to reload multicast
;                bits if card is removed and re-inserted)
;
;              - Added 4 to rx ios2_datalength in raw mode. Shapeshifter
;                seems to need this (why?)
;
;                        *** 8th Aminet release ***
;
;7-5-00 v1.2b1 - Since there has been almost ten months without an update,
;                I, Harry Sintonen <sintonen@iki.fi>, decided to have a go.
;
;              - Reformatted this source code: Now registers are always in
;                lowercase, for better readability.
;
;              - Added MN_LENGTH check for OpenDevice() (S2R3 and NSD require
;                this).
;
;              - Fixed a bug: If TAG_IGNORE, TAG_MORE or TAG_SKIP were used
;                in the IOSana2Req's taglist Open_Unit got really confused,
;                throwing enforcer hits, and in a worst case, crashing the
;                machine. Now uses utility.library GetTagData().
;
;              - Optimized dbf loops to subq + branch for better 020+
;                performance. Made several other miscallenous optimizations
;                thruout the code.
;
;              - More bugs fixed: DoEvent examined dd_eventlist before
;                Disable(). devcmd_addmulticast and devcmd_delmulticast
;                didn't lock the list, devcmd_delmulticast called Enable()
;                without Disable() when no multicast was found. Protected
;                dd_multicasts list with semaphore. dd_bufmanlist was
;                accessed without list arbitration, added Disable()/Enable()
;                pair when needed.
;
;              - Added support for S2_PacketFilter tag. Speedup: replaced
;                several list related calls with macros.
;
;              - Rewrote CMD_READ handling: Added proper Multiple Protocol
;                Stack Support, it was really broken before. Basically this
;                means that each opener gets all the packets necessary. If a
;                packet comes in that fills a request for more than one
;                opener of the device, *all* of them will get a copy of the
;                packet. Of course each caller can ask RAW or non-RAW version
;                of the packet. cnet.device fully comply to S2R2 now!
;
;              - Since complies to S2R2, added NewStyleDevice (NSD) support.
;                Also added S2R3 S2_DMACopyFromBuff32 tag support, however
;                this feature is currently disabled. ***
;                S2_DMACopyToBuff32 would only pay off if: 1) there is only
;                one caller for this packet, and 2) there is no packet filter
;                for this caller.
;
;              - More speedups: Replaced exec Disable() and Enable() calls
;                with macros. Yet more list related calls replaced with
;                macros.
;
;              - Yet more bugs fixed: CMD_FLUSH didn't abort S2_READORPHANs.
;                CMD_OFFLINE didn't abort pending CMD_READs, S2_READORPHANs
;                and CMD_WRITEs. CMD_READ and S2_READORPHAN didn't check for
;                card being online. S2_ONEVENT didn't check if it knows the
;                event condition(s) specified, instead requests for unknown
;                event(s) would never have got returned. Also S2_ONEVENT
;                returned the ioreq even if it was queued. S2_CONFIGINTERFACE
;                worked more than once, it *must* only work once.
;                OpenDevice() incorrectly required S2_CopyFromBuff and
;                S2_CopyToBuff tags to be present.
;
;              - Yet more speedups: DoEvent uses global cached device
;                pointer.
;
;              - init_card and init_nic fire S2EVENT_ERROR|S2EVENT_HARDWARE
;                event if something goes wrong. Trying to send too big of a
;                packet trigger S2EVENT_TX. If packet gets dropped during
;                receive S2EVENT_ERROR|S2EVENT_RX event is triggered. If
;                copyfrombuf while transmitting fails S2EVENT_TX|S2EVENT_BUFF
;                event is triggered, and similarily if copytobuf while
;                receiving fails S2EVENT_RX|S2EVENT_BUFF event is triggered.
;
;              - Extensively debugged for 18+6 hours to make 1st early beta
;                possible...
;
;              - Currently at least following should work: CMD_READ,
;                CMD_WRITE, CMD_FLUSH, S2_DEVICEQUERY, S2_GETSTATIONADDRESS,
;                S2_CONFIGINTERFACE, S2_ADDMULTICASTADDRESS,
;                S2_DELMULTICASTADDRESS, S2_MULTICAST, S2_BROADCAST,
;                S2_TRACKTYPE, S2_UNTRACKTYPE, S2_GETTYPESTATS,
;                S2_GETSPECIALSTATS, S2_GETGLOBALSTATS, S2_ONEVENT,
;                S2_READORPHAN, S2_ONLINE, S2_OFFLINE and NSCMD_DEVICEQUERY.
;
;              - Tested with sanamon, sanautil, miamidx and genesis.
;
;                        *** internal 1.2beta1 release ***
;
;9-5-00 v1.2b2 - initRoutine trashed a6 by leaving execbase in it, but since
;                it *is* execbase originally it was harmless. Silly. :)
;
;              - Misc minor changes here and there.
;
;              - Added device expunge. esp. cool for debugging, no more those
;                damn annoying reboots. Also someone might want to quit
;                protocol stack, avail flush, and use some other card without
;                the need to reboot. Now DDF_CONFIGURED is reset only on
;                device flush, this means that pulling the card out doesn't
;                cut the connection anymore, and you can continue by plugging
;                the card back in. v1.2b1 didn't allow this hot swapping
;                trick, but v1.1 did.
;
;              - Bugfixes: initRoutine crashed if utility.library v36 failed
;                to open. Partially successful OpenDevice ate some resources,
;                now it should be able to back out in all cases. S2_OFFLINE
;                should, I quote: "Aborts all pending reads and writes with
;                ios2_Error set to S2ERR_OUTOFSERVICE.". However this makes
;                MiamiDx die a horrible death. Took me 10 hours to find why.
;                Damn it! Disabled IO aborting and now it works. D'oh!
;
;              - Speedup: Implemented turbo RemoteRead & RemoteWrite loops,
;                they use longword access to 16bit register. Don't worry,
;                this works, but honestly I have no clue why. Enabled DMA for
;                both regular and raw CMD_WRITEs. At least MiamiDx support
;                S2_DMACopyFromBuff32. Moving 30mb mpg between my amiga and
;                laptop result about 450kb/s upload and 500kb/s download
;                speed. v1.1 and the same environment gave about 420kb/s
;                upload and 450kb/s download speed.
;                (ftp client AmFTP 1.92, ftpd Version 6.2/OpenBSD/Linux-0.10)
;
;              - Made Makefile generate 68000 version of the device. No real
;                differences yet tho. this version is untested!
;
;                        *** internal 1.2beta2 release ***
;
;16-5-00 v1.2b3  Got a report that v1.2b2 is actually slower than v1.2b1.
;                Well I wrote a simple AmigaE program that I can use to
;                benchmark cnet.device CMD_WRITE. Turned out that turboio
;                transfer ~679kb/s and non-turboio 697kb/s without DMA
;                transfer, in DMA mode the same figures are 694kb/s vs. 713
;                kb/s. Oh dear, this is it, I disabled turboio. :) Thanks go
;                to Tundrah for the tip. BTW: it seems we're finally getting
;                there, he get ~770kb/s real life transfer speed using FTP
;                (Apollo ethernet, 060 @ 66). Not bad. Btw benchmarking
;                CMD_READ is a bit problematic for me as I don't have really
;                state of the art machine on the other end and it can't feed
;                the card at the full speed. However Tundrah also told me
;                that for some reason MiamiDx is slow compared to Genesis. I
;                did some tests with both MiamiDx and Genesis, used ftp
;                client AmFTP 1.92 and ftpd Version 6.2/OpenBSD/Linux-0.10.
;                Download speed with Genesis was 720kb/s and with MiamiDx 530
;                kb/s. Genesis is %33 faster. For fucks sake wtf Miami is
;                doing? busyloops or what? :) This probably means that
;                someone should make a MNI driver. Any volunteers?
;
;              - Forget that mumbo jumbo about MMU speedup hack that was
;                mentioned in v1.2b2. 1) no real speedup due the way I/O
;                is accessed. 2) many amiga 060 turbo cards don't have
;                special hardware to handle writebuffer burst write ->
;                undefined behavior. (Thanks to Ralph Schmidt for helping me
;                understand this)
;
;              - Bugfix: AbortIO failed to abort S2_MULTICAST or S2_BROADCAST
;                requests.
;                
;                        *** internal 1.2beta3 release ***
;
;19-5-00 v1.2b4  Misc little changes here and there, nothing major.
;
;              - Speedup: inlined optimized version of RemoteRead for reading
;                prhdr and etherheader. Joined rx_header and rxbuffer. As a
;                result code simplified and sped up. For me the speedup is
;                +2kb/s. Inlined couple of other subroutines that are called
;                only once.
;
;              - Speedup: Got a report from darza saying that turboio indeed
;                is faster with `cnet' card. He got 720kb/s with v1.1,
;                v1.2beta1 and v1.2beta3, but 750kb/s with v1.2beta2. Eek! :)
;                Tweaked makefile to compile separate turboio version. It is
;                up to user to find out which one is faster. Currently non-
;                turboio ftp download peaked 754kb/s. Turboio version peaked
;                771kb/s. Could be that turboio is faster on 060 @ 50 systems
;                and non-turboio on overclocked 060 systems. Why? Maybe some
;                I/O timing issues... No idea really, just guessing. Need to
;                ask Tundrah to do more benchmarking. :)
;
;              - Improvements: added support for S2_ADDMULTICASTADDRESSES and
;                S2_DELMULTICASTADDRESSES (Multicast address range extensions
;                to SANA-II Rev 3). Implemented S2_ADDMULTICASTADDRESS and
;                S2_DELMULTICASTADDRESS using range of addr -> addr. Added
;                CPU type to version string. Added 68020 CPU check to 68020+
;                version. Device IDString no longer has '$VER: ' included.
;                Now uses CARDF_POSTSTATUS instead of a hack if card.resource
;                V39+ is available (note that KS 3.0 has *V37* card.resource,
;                and KS 3.1 has V40). Added workaround for Genesis bug, it
;                calls S2_ONLINE before S2_CONFIGINTERFACE.
;
;                        *** internal 1.2beta4 release ***
;
;25-5-00 v1.2  - Made compile with standard os-includes.
;
;                        *** 9th Aminet release ***
;
;19-8-00 v1.3b1  Disabled some unused code. Size optimized some non-time
;                critical code.
;
;              - Speedup: Implemented one obvious 020 optimization. Thanks to
;                Ilkka Lehtoranta for pointing it out.
;
;              - Improvement: Now uses memory pool for all memory allocations
;                to prevent memory fragmentation. Also a bit faster than
;                using exec AllocMem/FreeMem since uses semaphore for access
;                arbitration (exec memroutines use Forbid/Permit).
;
;                        *** internal 1.3beta1 release ***
;
; 1-9-00 v1.3  - Aminet release recompile.
;
;                        *** 10th Aminet release ***
;
; 7-9-00 v1.4  - Fixed serious memory trashing bug from OpenDevice: two long-
;                words pointed by a2 register were trashed! The actual bug
;                was in get_time that is called by init_nic that is called by
;                Open_Device. This bug was introduced in v1.2b2. Special
;                thanks and hellos go to Ralph Schmidt for hunting down this
;                bug (he had to do lots and lots of extra work just to locate
;                it), and please, don't kill me :-) This bug was very nasty
;                and totally, utterly my fault. But hey, shit happens.
;
;                        *** 11th Aminet release ***


; todo: Do some more 020+ optimizations (the ones that really pay off!)
;       CopyTo/CopyFrom 16/32 support? (ADCD_2.1:DevInfo/Networking/SANA2Revision3)
;       Add support for more cards? (not in this device in this life, unless easily implemented)
;       Rewrite in C and make native MorphOS version (any volunteers?)
;


; configured from makefile
;debug = 1
;verbose=1
;turboio = 1


; misc flags
;realdisable=1   ; use exec functions or macros ?


 include exec/types.i
 include exec/nodes.i
 include exec/lists.i
 include exec/libraries.i
 include exec/errors.i
 include exec/initializers.i
 include exec/resident.i
 include exec/memory.i
 include exec/interrupts.i
 include exec/semaphores.i
 include exec/ports.i
 include exec/io.i
 include exec/devices.i
 include devices/timer.i
 IFND realdisable
 include exec/ables.i
 ENDC

 include funcdef.i
 include exec/exec_lib.i

 IFND _LVOGetSysTime
_LVOGetSysTime = -66          ; timer lib function
 ENDC
 IFND _LVOGetTagData
_LVOGetTagData = -36          ; utility lib function
 ENDC

 include pcmcia.i     ; card.resource etc.
 include sanaii.i     ; the essential network stuff
 include cnet.i       ; hardware specific stuff


VERSION  = 1
REVISION = 4

VERSTR macro
 dc.b   "1.4 (7.9.2000)"
 endm


; 1uS delay before nic register access
; May not be required with slower CPUs.

delay MACRO
; tst.b   $bfe001             ; at least 1uS, even on fast machines
 ENDM


MYDISABLE MACRO
 IFD     realdisable
 jsr     _LVODisable(a6)
 ELSE
 MOVE.W  #$4000,$DFF09A
 ADDQ.B  #1,IDNestCnt(A6)
 ENDC
 ENDM

MYENABLE MACRO
 IFD     realdisable
 jsr     _LVOEnable(a6)
 ELSE
 SUBQ.B  #1,IDNestCnt(A6)
 BGE.S   .MYENABLE\@
 MOVE.W  #$C000,$DFF09A
.MYENABLE\@:
 ENDC
 ENDM

MYREMHEAD MACRO
 MOVE.L  (A0),A1
 MOVE.L  (A1),D0
 BEQ.S   .MYREMHEAD\@
 MOVE.L  D0,(A0)
 EXG.L   D0,A1
 MOVE.L  A0,LN_PRED(A1)
.MYREMHEAD\@
 ENDM

;===========================================================================


    section 0,CODE


;-----------------------------------------------------------------------
; The first executable location.  This should return an error
; in case someone tried to run you as a program (instead of
; loading you as a device).

start_exe:
 moveq   #-1,d0   ; it's a device, not an application!
 rts


;-----------------------------------------------------------------------
; A romtag structure.  After your driver is brought in from disk, the
; disk image will be scanned for this structure to discover magic constants
; about you (such as where to start running you from...).
;-----------------------------------------------------------------------

romtag:
 dc.w    RTC_MATCHWORD   ; RT_MATCHWORD
 dc.l    romtag          ; RT_MATCHTAG
 dc.l    Endcode         ; RT_ENDSKIP
 dc.b    RTF_AUTOINIT    ; RT_FLAGS
 dc.b    VERSION         ; RT_VERSION
 dc.b    NT_DEVICE       ; RT_TYPE
 dc.b    0               ; RT_PRI
 dc.l    DeviceName      ; RT_NAME
 dc.l    IDString        ; RT_IDSTRING
 dc.l    Init            ; RT_INIT

Init:
 dc.l    dd_extsize      ; data space size
 dc.l    funcTable       ; pointer to function initializers
 dc.l    dataTable       ; pointer to data initializers
 dc.l    initRoutine     ; routine to run at startup

funcTable:
 dc.w  -1
 dc.w  Open_Device-funcTable
 dc.w  Close_Device-funcTable
 dc.w  Expunge_Device-funcTable
 dc.w  _Null-funcTable
 dc.w  _DevBeginIO-funcTable
 dc.w  _DevAbortIO-funcTable
 dc.w  -1

dataTable:
 INITBYTE LN_TYPE,NT_DEVICE
 INITLONG LN_NAME,DeviceName
 INITBYTE LIB_FLAGS,LIBF_SUMUSED|LIBF_CHANGED
 INITWORD LIB_VERSION,VERSION
 INITWORD LIB_REVISION,REVISION
 INITLONG LIB_IDSTRING,idString
 dc.w   0


 include debugs.i



;=======================================================
;                      initRoutine
;=======================================================
;
; This routine gets called after the device has been allocated.
; The device pointer is in d0.  The AmigaDOS segment list is in a0.
; If it returns the device pointer, then the device will be linked
; into the device list. If it returns NULL, then the device will
; be unloaded. This call is single-threaded.
;
; input:   a0 = seglist
;          d0 = device
;          a6 = execbase
;
initRoutine:
 movem.l d1-d7/a0-a5,-(sp)
 move.l  d0,a5
 move.l  a0,a4

 ifge    __CPU-68020
 btst    #AFB_68020,AttnFlags+1(a6)
 beq.s   .no020
 endc

 lea     utilityName(pc),a1
 moveq   #36,d0
 jsr     _LVOOpenLibrary(a6)
 move.l  d0,utility_base     ; store utilitybase
 beq.s   .no_utility         ; oops failed, return failure

 move.l  #MEMF_PUBLIC|MEMF_CLEAR,d0 ; memFlags
 move.l  #2048,d1            ; puddleSize
 move.l  d1,d2               ; threshSize
 bsr     createpool
 move.l  d0,poolptr          ; store poolheader ptr
 beq.s   .nopool             ; oh crap, failed to create!

 lea     poolsema(pc),a0     ; init mempool-semaphore
 jsr     _LVOInitSemaphore(a6)

 move.l  a4,dd_SegList(a5)   ; seglist for expunge
 move.l  a6,exec_base        ; local copy of execbase
 move.l  a5,me_myself        ; for speedup

 move.l  a5,d0
.pop:
 movem.l (sp)+,d1-d7/a0-a5
 rts

 ifge    __CPU-68020
.no020
 bug     <"cnet: initRoutine failed, no 68020+ CPU",10>
 bra.s   .err
 endc

.no_utility
 bug     <"cnet: initRoutine could not open utility.library V36",10>

.err:
 moveq   #0,d0
 bra.s   .pop

.nopool:
 bug     <"cnet: initRoutine could not create memory pool",10>
 move.l  utility_base(pc),a1
 jsr     _LVOCloseLibrary(a6)
 bra.s   .err




_Null:
 moveq   #0,d0
 rts




;----------------------------------------------------------------------
;
; Here begins the system interface commands. When the user calls
; OpenDevice/CloseDevice/RemDevice, this eventually gets translated
; into a call to the following routines (Open_Device/Close_Device/
; Expunge_Device). Exec has already put our device pointer in a6 for
; us.



;=================================================================
;                         Open Device
;=================================================================
;
; error = Open_Device(device, ioreq, unitnum, flags)
;  d0                   a6      a1     d0      d1
;
; Open sets the IO_ERROR field on an error. If it was successfull,
; we should also set up the IO_UNIT and LN_TYPE fields. Exec takes
; care of setting up IO_DEVICE.
;
Open_Device:
;
;** Subtle point: any AllocMem() call can cause a call to this device's
;** expunge vector. If LIB_OPENCNT is zero, the device might get expunged.
;
 addq.w  #1,lib_opencnt(a6)          ; Fake an opener for duration of call <|>

 bug     <10,"cnet: OpenDevice(ioreq $%lx, unit %ld, flags $%02lx)",10>,a1,d0,d1

 ifgt    0
 ifd     debug
 ifd     verbose
 move.l  #100000,d0
.wait:
 tst.b   $bfe001
 subq.l  #1,d0
 bne.s   .wait
 endc
 endc
 endc

 movem.l d2-d4/a2-a4/a6,-(sp)
 move.l  a6,a3                       ; a3 = device
 move.l  a1,a4                       ; a4 = ioreq
 move.l  d0,d4                       ; d4 = unit

 cmp.w   #IOS2_SIZE,mn_length(a4)    ; reject invalid requests
 blt     .badcaller

 btst    #DDB_MINE,dd_flags2(a3)
 bne     .isexclusive

 btst    #SANA2OPB_MINE,d1           ; want exclusive mode ?
 beq.s   .init

 cmp.w   #1,lib_opencnt(a3)          ; must not be any other openers
 bne     .isnonexcl
 bset    #DDB_MINE,dd_flags2(a3)     ; set exclusive mode
 btst    #SANA2OPB_PROM,d1
 beq.s   .init
 bset    #DDB_PROM,dd_flags2(a3)     ; set promiscuous mode
.init:


 move.l  a3,a1
 bsr     init_device                 ; init device data structures

 move.l  a3,a0
 move.l  a4,a1
 move.l  d4,d0
 bsr     Open_Unit                   ; open unit
 move.l  d0,io_unit(a4)
 beq     .openerr


 btst    #DDB_NICUP,dd_flags(a3)     ; nic already initialised ?
 bne.s   .nickdone
 move.l  a3,a1
 bsr     init_card                   ; init PCMCIA card
 tst.l   d0
 bne.s   .error
 move.l  a3,a1
 bsr     init_nic                    ; set up nic
 tst.l   d0
 bne.s   .error
.nickdone:



 btst    #DDB_OFFLINE,dd_flags(a3)   ; previously put offline ?
 bne.s   .opened
 bset    #DDB_ONLINE,dd_flags(a3)    ; ready to accept packets
.opened:


 clr.b   io_error(a4)                ; IMPORTANT: Mark IORequest as "complete"
 move.b  #NT_REPLYMSG,ln_type(a4)
 addq.w  #1,lib_OpenCnt(a3)          ; opened successfully
 bclr    #LIBB_DELEXP,lib_flags(a3)  ; prevent delayed expunges

 moveq   #0,d0
 bug     <"cnet: OpenDevice OK",10,10>


.done:
 movem.l (sp)+,d2-d4/a2-a4/a6
 subq.w  #1,lib_opencnt(a6)          ; ** End of expunge protection <|>
 rts



.badcaller
 bug     <"cnet: OpenDevice invalid MN_LENGTH!",10>
 bra.s   .openerr

.isexclusive:
 bug     <"cnet: device already in use exclusively!",10>
 bra.s   .openerr

.isnonexcl:
 bug     <"cnet: device already in use non-exclusively!",10>
 bra.s   .openerr

.error:
 move.l  dd_cardres(a3),d0           ; did card.resource open ?
 beq.s   .nocard
 move.l  d0,a6
 bclr    #DDB_OWNED,dd_flags(a3)     ; did we own card ?
 beq.s   .nocard
 lea     dd_cardhandle(a3),a1
 moveq   #CARDF_REMOVEHANDLE,d0
 jsr     _LVOReleaseCard(a6)         ; release card
.nocard:


 move.l  ios2_buffermanagement(a4),d0
 beq.s   .nobufman                    ; got a buffermanagement pointer ?
 clr.l   ios2_buffermanagement(a4)

 move.l  exec_base(pc),a6

 move.l  d0,a1
 move.l  d0,a2
 bug     <"cnet: OpenDevice remove bufman at $%lx",10>,a1
 MYDISABLE                            ; lock access to list
 REMOVE                               ; remove bufman struct from list
 MYENABLE                             ; release lock on list
 move.l  a2,a1
 moveq   #bufman_sizeof,d0            ; discard buffer management struct
 bsr     free
.nobufman:


.openerr:
 moveq   #IOERR_OPENFAIL,d0          ; error, opendevice failed!
 move.b  d0,io_error(a4)
 move.l  d0,io_unit(a4)
 move.l  d0,io_device(a4)            ; IMPORTANT: trash IO_DEVICE on open failure
 bug     <"cnet: OpenDevice failed!",10,10>
 bra.s   .done



;===============================================================
;                 unit=Open Unit(device, ioreq, unitnum)
;                  d0              a0      a1     d0
;===============================================================
;
; Get the caller's buffer copy callback vectors
;
Open_Unit:
 bug     <"cnet: Open_Unit(ioreq $%08lx) taglist: $%08lx",10>,a1,ios2_buffermanagement(a1)

 movem.l d2-d6/a2-a5,-(sp)
 move.l  a0,a4                    ; a4 = device
 move.l  a1,a5                    ; a5 = ioreq
 tst.l   d0                       ; only unit 0 is supported
 bne     .error

; If null taglist is supplied we don't fail!

 move.l  ios2_buffermanagement(a5),d0  ; tag list supplied?
 beq     .ok
 moveq   #0,d2                    ; d2 = tags found
 move.l  d0,a2                    ; a2 = tag list

; Note: I don't complain if I can't find pointers to the callback
; routines. This is because there are some programs that may need to
; open the device, but will never use any device commands that
; require the callbacks. Instead I check on CMD_READ, CMD_READORPHAN
; and CMD_WRITE for missing routines.

 move.l  exec_base(pc),a6

 moveq   #bufman_sizeof,d0        ; allocate memory for bufman vectors
 bsr     alloc
 move.l  d0,ios2_buffermanagement(a5)  ; return bufman node
 beq     .error

 move.l  d0,a3

 move.l  utility_base(pc),a6

 ; get copyfrom functions:

 move.l  #S2_COPYFROMBUFF,d0
 moveq   #0,d1
 move.l  a2,a0
 jsr     _LVOGetTagData(a6)
 move.l  d0,bufman_copyfrombuf(a3)

; This is new SANA-II V3 Addition

 move.l  #S2_DMACopyFromBuff32,d0
 moveq   #0,d1
 move.l  a2,a0
 jsr     _LVOGetTagData(a6)
 move.l  d0,bufman_dmacopyfrombuf32(a3)

 bug     <"cnet: S2_DMACopyFromBuff32 = $%08lx",10>,d0

 ; get copyto functions:

 move.l  #S2_COPYTOBUFF,d0
 moveq   #0,d1
 move.l  a2,a0
 jsr     _LVOGetTagData(a6)
 move.l  d0,bufman_copytobuf(a3)

; This is new for SANA-II V3 Addition

 move.l  #S2_DMACopyToBuff32,d0
 moveq   #0,d1
 move.l  a2,a0
 jsr     _LVOGetTagData(a6)
 move.l  d0,bufman_dmacopytobuf32(a3)

 bug     <"cnet: S2_DMACopyToBuff32 = $%08lx",10>,d0

; And this is new SANA-II V2 Addition
 move.l  #S2_PACKETFILTER,d0
 moveq   #0,d1
 move.l  a2,a0
 jsr     _LVOGetTagData(a6)
 move.l  d0,bufman_packetfilter(a3)    ; if null everything get thru

 ifd     debug
 bug     <"cnet: S2_PacketFilter = $%08lx",10>,d0
 ifd     verbose
 tst.l   d0
 beq.s   .v1
 move.l  d0,a0
 bug     <"cnet: PacketFilter h_Entry = $%08lx h_SubEntry = $%08lx h_Data = $%08lx",10>,8(a0),12(a0),16(a0)
.v1:

 move.l  #S2_COPYFROMBUFF16,d0
 moveq   #0,d1
 move.l  a2,a0
 jsr     _LVOGetTagData(a6)
 bug     <"cnet: S2_CopyFromBuff16 = $%08lx",10>,d0

 move.l  #S2_COPYTOBUFF16,d0
 moveq   #0,d1
 move.l  a2,a0
 jsr     _LVOGetTagData(a6)
 bug     <"cnet: S2_CopyToBuff16 = $%08lx",10>,d0

 move.l  #S2_COPYFROMBUFF32,d0
 moveq   #0,d1
 move.l  a2,a0
 jsr     _LVOGetTagData(a6)
 bug     <"cnet: S2_CopyFromBuff32 = $%08lx",10>,d0

 move.l  #S2_COPYTOBUFF32,d0
 moveq   #0,d1
 move.l  a2,a0
 jsr     _LVOGetTagData(a6)
 bug     <"cnet: S2_CopyToBuff32 = $%08lx",10>,d0

 endc
 endc

 move.l  exec_base(pc),a6

 ; Init the list for CMD_READ requests
 lea     bufman_rxqueue(a3),a0
 bsr     _newlist

 bug     <"cnet: addbufman at $%lx",10>,a3
 lea     dd_bufmanlist(a4),a0
 move.l  a3,a1
 MYDISABLE                        ; lock access to list
 ADDHEAD                          ; add to buffer management list
 MYENABLE                         ; release lock on list
 bra.s   .ok

.error:
 moveq   #0,d0                    ; return error
 bra.s   .done

.ok:
 moveq   #1,d0                    ; return OK
.done:
 movem.l (sp)+,d2-d6/a2-a5
 rts



;============================================================
;                      Close Device
;============================================================
;
;  Seglist = CloseDevice(ioreq, device)
;    d0                    a1    a6
;
;
; There are two different things that might be returned from the Close
; routine. If the device wishes to be unloaded, then Close must return
; the segment list (as given to Init). Otherwise close MUST return NULL.
;
;
Close_Device:
 bug     <10,"cnet: CloseDevice(ioreq $%08lx)",10>,a1

 ifgt    0
 ifd     debug
 ifd     verbose
 move.l  #100000,d0
.wait:
 tst.b   $bfe001
 subq.l  #1,d0
 bne.s   .wait
 endc
 endc
 endc

 movem.l a2-a4/a6,-(sp)
 move.l  a1,a3                        ; a3 = ioreq
 move.l  a6,a4                        ; a4 = device

 moveq   #0,d0                        ; default: no unload
 tst.w   lib_OpenCnt(a4)
 beq     .done                        ; invalid state!

 move.l  exec_base(pc),a6

 moveq   #-1,d0
 move.l  d0,io_unit(a3)               ; We're closed...
 move.l  d0,io_device(a3)             ; customers not welcome at this IORequest!!

 move.l  ios2_buffermanagement(a3),d0
 beq.s   .nobufman                    ; got a buffermanagement pointer ?
 clr.l   ios2_buffermanagement(a3)

 move.l  d0,a1
 move.l  d0,a2
 bug     <"cnet: remove bufman at $%lx",10>,a1

 MYDISABLE                            ; lock access to list
 REMOVE                               ; remove bufman struct from list
 MYENABLE                             ; release lock on list

 move.l  a2,a1
 moveq   #bufman_sizeof,d0            ; discard buffer management struct
 bsr     free
.nobufman:


 moveq   #0,d0                        ; default: no unload
 subq.w  #1,lib_OpenCnt(a4)           ; one less opener
 bne.s   .done                        ; still open ?

 bug     <"cnet: devclosedown",10>

; close down the devive
 move.l  a4,a1
; a6 = execbase
 bsr     devclosedown

 moveq   #0,d0                        ; default: no unload
 btst    #LIBB_DELEXP,lib_flags(a4)
 beq.s   .done                        ; delayed expunge pending ?

;  do the expunge
 bug     <"cnet: LIBF_DELEXP calling Expunge!",10>
 move.l  a4,a6
 bsr.s   Expunge_Device

.done:
 movem.l (sp)+,a2-a4/a6
 bug     <"cnet: CloseDevice return: $%08lx",10,10>,d0
 rts                                  ; MUST return either zero or the SegList!!!



;============================================================
;                      Expunge Device
;============================================================
;
;  Seglist = ExpungeDevice(device)
;    d0                      a6
;
; Expunge is called by the memory allocator when the system is low on
; memory (or by us from the CloseDevice above!).
;
; There are two different things that might be returned from the Expunge
; routine.  If the device is no longer open then Expunge may return the
; segment list (as given to Init). Otherwise Expunge may set the delayed
; expunge flag and return NULL.
;
; One other important note: because Expunge is called from the memory
; allocator, it may NEVER Wait() or otherwise take long time to complete.
;
;
Expunge_Device:
 bug     <10,"cnet: Expunge_Device $%08lx",10>,a6

 ifgt    0
 ifd     debug
 ifd     verbose
 move.l  #100000,d0
.wait:
 tst.b   $bfe001
 subq.l  #1,d0
 bne.s   .wait
 endc
 endc
 endc

 tst.w   lib_opencnt(a6)
 beq.s   .unload                      ; anyone has us open ?

; it is still open. set the delayed expunge flag

 bug     <"cnet: Could not expunge, LIBF_DELEXP set!",10>
 bset    #LIBB_DELEXP,lib_flags(a6)
 moveq   #0,d0                        ; do not unload the seg
 rts


.unload
 bug     <"cnet: Unloading...",10>

 movem.l a4/a6,-(sp)
 move.l  dd_SegList(a6),-(sp)         ; seglist to return in d0

 move.l  a6,a4
 move.l  exec_base(pc),a6

; put the hardware offline
 move.l  a4,a1
; a6=execbase
 bug     <"cnet: devclosedown",10>
 bsr     devclosedown


; get rid of the timer.device stuff we might have allocated:
 lea     timereq(pc),a1
 move.l  io_device(a1),d0
 beq.s   .notimerdev                  ; was timer ever opened?
 addq.l  #1,d0
 beq.s   .notimerdev                  ; error can be also indicated by -1!
 bug     <"cnet: closedevice timer ior $%08lx",10>,a1
 jsr     _LVOCloseDevice(a6)          ; close it
.notimerdev


; release the card, if we own it. note that there is
; no CloseResource() call.
 move.l  dd_cardres(a4),d0           ; did we open card.resource at some point ?
 beq.s   .nocard
 bclr    #DDB_OWNED,dd_flags(a4)     ; do we own card ?
 beq.s   .nocard
 move.l  a6,-(sp)
 move.l  d0,a6
 lea     dd_cardhandle(a4),a1
 moveq   #CARDF_REMOVEHANDLE,d0
 bug     <"cnet: releasing cardhandle $%08lx",10>,a1
 jsr     _LVOReleaseCard(a6)         ; release card
 move.l  (sp)+,a6
.nocard:


; free memorypool
 move.l   poolptr(pc),a0
 bug     <"cnet: freeing memorypool $%08lx",10>,a0
 bsr     deletepool


; close utilitybase
 move.l  utility_base(pc),a1
 bug     <"cnet: closing utilitybase $%08lx",10>,a1
 jsr     _LVOCloseLibrary(a6)


 ; unlink from device list
 move.l  a4,a1
 bug     <"cnet: removing device node $%08lx",10>,a1
 REMOVE


; free our memory (must calculate from LIB_POSSIZE & LIB_NEGSIZE)
 moveq   #0,d0
 move.l  a4,a1                        ; devicebase
 move.w  lib_negsize(a4),d0
 sub.l   d0,a1                        ; calculate base of functions
 add.w   lib_possize(a4),d0           ; calculate size of functions + data area
 jsr     _LVOFreeMem(a6)              ; must use FreeMem here!

 bug     <"cnet: Unload done! cnet.device expunged!",10,10>

 movem.l (sp)+,d0/a4/a6               ; pop regs and seglist pointer
 rts



;======================================================================
;                  devclosedown(device, execbase)
;                                a1       a6
;======================================================================
;
; Put the nic into sleep... also do whatever nececcary hw magic
; before device closedown. mark the device offline and unconfigured.
;
devclosedown:
;and.b   #~(DDF_ONLINE|DDF_CONFIGURED),dd_flags(a1) ; not online, not configured
 and.b   #~DDF_ONLINE,dd_flags(a1)                  ; not online
 and.b   #~(DDF_MINE|DDF_PROM),dd_flags2(a1)        ; cancel exclusive mode, cancel promiscuous mode
 bra     kill_nic                                   ; stop controller



;===============================================================
;                       Dev_BeginIO
;===============================================================
; the entry point for all device commands
;

 CNOP 0,4

_DevBeginIO:
 move.w  io_command(a1),d0       ; get command number
 move.b  #NT_MESSAGE,ln_type(a1) ; make sure type is message
 cmp.w   #S2_END,d0
 bhs     .berror                 ; valid table command?
 ifge    __CPU-68020
 move.l  .cmds(pc,d0.w*4),d0      ; get command vector (020+)
 else
 lsl.w   #2,d0
 move.l  .cmds(pc,d0.w),d0        ; get command vector
 endc
 bne.s   .ok
.error:
 move.b  #IOERR_NOCMD,io_error(a1)
 bra     TermIO                  ; return invalid command
.ok:
 move.l  d0,a0
 clr.b   io_error(a1)            ; no errors yet

 ifgt    0
 ifd     debug
 ifd     verbose
 moveq   #0,d0
 move.w  io_command(a1),d0
 bug <"cnet: BeginIO ioreq $%08lx cmd %ld",10>,a1,d0
 move.l  #300000,d0
.wait:
 tst.b   $bfe001
 subq.l  #1,d0
 bne.s   .wait
 endc
 endc
 endc

 jmp     (a0)                    ; jump to command


; command vectors

 CNOP 0,4

.cmds:
 dc.l    0                           ;  0
 dc.l    0                           ;  1
 dc.l    devcmd_read                 ;  2 = cmd_read
 dc.l    devcmd_write                ;  3 = cmd_write
 dc.l    0                           ;  4
 dc.l    0                           ;  5
 dc.l    0                           ;  6
 dc.l    0                           ;  7
 dc.l    devcmd_flush                ;  8 = cmd_flush
 dc.l    devcmd_devicequery          ;  9 = S2_DEVICEQUERY
 dc.l    devcmd_getstationaddress    ;  10= S2_GETSTATIONADDRESS
 dc.l    devcmd_configinterface      ;  11= S2_CONFIGINTERFACE
 dc.l    0                           ;  12
 dc.l    0                           ;  13
 dc.l    devcmd_addmulticast         ;  14= S2_ADDMULTICASTADDRESS
 dc.l    devcmd_delmulticast         ;  15= S2_DELMULTICASTADDRESS
 dc.l    devcmd_multicast            ;  16= S2_MULTICAST
 dc.l    devcmd_broadcast            ;  17= S2_BROADCAST
 dc.l    devcmd_tracktype            ;  18= S2_TRACKTYPE
 dc.l    devcmd_untracktype          ;  19= S2_UNTRACKTYPE
 dc.l    devcmd_gettypestats         ;  20= S2_GETTYPESTATS
 dc.l    devcmd_getspecialstats      ;  21= S2_GETSPECIALSTATS
 dc.l    devcmd_getglobalstats       ;  22= S2_GETGLOBALSTATS
 dc.l    devcmd_onevent              ;  23= S2_ONEVENT
 dc.l    devcmd_readorphan           ;  24= S2_READORPHAN
 dc.l    devcmd_online               ;  25= S2_ONLINE
 dc.l    devcmd_offline              ;  26= S2_OFFLINE

.nsd_supported:
 dc.w    2,3
 dc.w    8,9,10,11
 dc.w    14,15,16,17,18,19,20,21,22,23,24,25,26
 dc.w    NSCMD_DEVICEQUERY
 dc.w    S2_ADDMULTICASTADDRESSES
 dc.w    S2_DELMULTICASTADDRESSES
 dc.w    0

.berror:
 clr.b   io_error(a1)                ; no error
 cmp.w   #S2_ADDMULTICASTADDRESSES,d0
 beq     devcmd_addmulticasts
 cmp.w   #S2_DELMULTICASTADDRESSES,d0
 beq     devcmd_delmulticasts

 cmp.w   #NSCMD_DEVICEQUERY,d0       ; is it NSCMD_DEVICEQUERY?
 bne     .error

 ifd     verbose
 bug     <"cnet: NSCMD_DEVICEQUERY ior $%08lx",10>,a1
 endc
 cmp.l   #16,io_length(a1)
 blt     .error
 move.l  io_data(a1),d0
 beq     .error
 move.l  d0,a0
 tst.l   (a0)+
 bne     .error
 tst.l   (a0)
 bne     .error

 ifd     verbose
 bug     <"cnet: replied with NSDEVTYPE_SANA2!",10>
 endc
 moveq   #16,d0
 move.l  d0,(a0)+
 move.l  #$00070000,(a0)+            ; NSDEVTYPE_SANA2<<16
 move.l  #.nsd_supported,(a0)
 move.l  d0,io_actual(a1)
 bra     TermIO



;====================================================================
;                              Abort_IO
;====================================================================
;
;                   try to cancel a pending ioreq
;
_DevAbortIO:

 bug <"cnet: AbortIO ioreq $%08lx",10>,a1

 movem.l d2/a2-a4/a6,-(sp)
 move.l  a6,a4                            ; a4=device
 move.l  exec_base(pc),a6
 move.l  a1,a2                            ; a2=ioreq

 moveq   #0,d0                            ; if already replied silentry ignore
 MYDISABLE                                ; lock access to lists
 cmp.b   #NT_MESSAGE,ln_type(a2)          ; only cancel queued ioreq's
 bne.s   .done

 move.w  io_command(a2),d0
 cmp.w   #CMD_READ,d0
 beq.s   .read

 lea     dd_orphanlist(a4),a0
 cmp.w   #S2_READORPHAN,d0
 beq.s   .thisone

 lea     dd_writelist(a4),a0
 cmp.w   #CMD_WRITE,d0
 beq.s   .thisone
 cmp.w   #S2_MULTICAST,d0
 beq.s   .thisone
 cmp.w   #S2_BROADCAST,d0
 beq.s   .thisone

 lea     dd_eventlist(a4),a0
 cmp.w   #S2_ONEVENT,d0
 beq.s   .thisone

 bug <"cnet: AbortIO not READ, READORPHAN, WRITE, MULTICAST, BROADCAST or ONEVENT",10>
 moveq   #IOERR_NOCMD,d0
 bra.s   .done

.read:
; Check all nodes in dd_bufmanlist until we succeed to abort:
; if we can't find the ioreq in any list, return IOERR_NOCMD.

 move.l  dd_bufmanlist(a4),a3
;bug <"cnet: AbortIO READ bufmanlist 1st: $%08lx",10>,a3
.readhandle
 moveq   #IOERR_NOCMD,d0                  ; return failure if we can't find it
 move.l  (a3),d2
 beq.s   .done                            ; end of list?
 lea     bufman_rxqueue(a3),a0
 ; a2=ioreq
 bsr     AbortReq
 tst.l   d0
 beq.s   .done                            ; succeeded to abort?
 move.l  d2,a3
 bra.s   .readhandle

.thisone:
 ; a0=list where the ioreqs should be in
 ; a2=ioreq
 bsr     AbortReq

.done:
 MYENABLE                                 ; release lock on lists

 movem.l (sp)+,d2/a2-a4/a6
 rts



;======================================================================
;                  AbortReq(minlist, ioreq, execbase)
;                             a0      a2      a6
;======================================================================
;
; locate an IO request in a linked list and abort it if found.
;
AbortReq:
 moveq   #IOERR_NOCMD,d0

 move.l  (a0),a0
.search:
 move.l  (a0),d1
 beq.s   .done                            ; end of list ?
 cmp.l   a2,a0                            ; = ioreq ?
 move.l  d1,a0                            ; note: movea doesn't change ccs!
 bne.s   .search

 move.l  a2,a1
;bug <"cnet: AbortReq aborted $%08lx",10>,a1
 REMOVE                                   ; remove ioreq from list
 move.b  #NT_REPLYMSG,ln_type(a2)
 move.b  #IOERR_ABORTED,io_error(a2)
 move.l  a2,a1
 jsr     _LVOReplyMsg(a6)                 ; reply to originator's message
 moveq   #0,d0                            ; aborted OK
.done:
 rts



;===========================================================
;                      termio(ioreq)
;                               a1
;===========================================================
;
; return completed ioreq to sender.
;
TermIO:
 move.b  #NT_REPLYMSG,ln_type(a1)
 btst    #IOB_QUICK,io_flags(a1)
 bne.s   .done                       ; does sender need a reply ?
 move.l  a6,-(sp)
 move.l  exec_base(pc),a6
 jsr     _LVOReplyMsg(a6)            ; send reply
 move.l  (sp)+,a6
.done:
 rts



;====================================================
;                     CMD_READ
;====================================================
;
; if card is configured and online
;
 CNOP 0,4
devcmd_read:
 ifd  verbose
 bug  <"cnet: cmd_read(ior $%08lx, type %ld)",10>,a1,ios2_packettype(a1)
 endc

 movem.l a2/a3/a6,-(sp)
 move.l  a1,a2                           ; a2 = ioreq
 move.l  io_device(a1),a3                ; a3 = device

 btst    #DDB_CONFIGURED,dd_flags(a3)    ; configured ?
 beq     .unconfigured
 btst    #DDB_ONLINE,dd_flags(a3)        ; online ?
 beq     .offline

 move.l  ios2_buffermanagement(a2),d0
 beq.s   .bad                            ; eeeekh, no bufman ?
 move.l  d0,a0
 tst.l   bufman_copytobuf(a0)
 beq.s   .bad                            ; this is needed for reading.. :)

 bclr    #IOB_QUICK,io_flags(a2)         ; must be queued
 move.l  exec_base(pc),a6

; add to this ioreq's bufman_rxqueue

 lea     bufman_rxqueue(a0),a0
 move.l  a2,a1
 ifd     verbose
 bug     <"cnet: CMD_READ ior $%08lx added to bufman_rxqueue $%08lx",10>,a1,a0
 endc
 MYDISABLE
 ADDTAIL                                 ; add ioreq to read queue
 MYENABLE

.done:
 movem.l (sp)+,a2/a3/a6
 rts

.unconfigured:
 moveq   #S2WERR_NOT_CONFIGURED,d0       ; error, device is not configured
 move.b  #S2ERR_BAD_STATE,io_error(a2)
 bra.s   .error

.offline:
 moveq   #S2WERR_UNIT_OFFLINE,d0         ; error, device is offline
 move.b  #S2ERR_OUTOFSERVICE,io_error(a2)
 bra.s   .error

.bad:
 moveq   #S2WERR_GENERIC_ERROR,d0        ; error, bad argument
 move.b  #S2ERR_BAD_ARGUMENT,io_error(a2)

.error:
 ifd     verbose
 bug     <"cnet: read ior $%08lx error %ld",10>,a2,d0
 endc

 move.l  d0,ios2_wireerror(a2)
 move.l  a2,a1
 bsr     TermIO
 bra.s   .done



;====================================================
;                     S2_READORPHAN
;====================================================
;
; if card is configured and online
;
 CNOP 0,4
devcmd_readorphan:
 bug     <"cnet: readorphan(ior $%08lx, type %ld)",10>,a1,ios2_packettype(a1)
 
 movem.l a2/a3/a6,-(sp)
 move.l  a1,a2                           ; a2 = ioreq
 move.l  io_device(a2),a3

 btst    #DDB_CONFIGURED,dd_flags(a3)    ; configured ?
 beq     .unconfigured
 btst    #DDB_ONLINE,dd_flags(a3)        ; online ?
 beq     .offline

 move.l  ios2_buffermanagement(a2),d0
 beq.s   .bad                            ; eeeekh, no bufman ?
 move.l  d0,a0
 tst.l   bufman_copytobuf(a0)
 beq.s   .bad                            ; this is needed for reading.. :)

 bclr    #IOB_QUICK,io_flags(a2)         ; must be queued
 move.l  exec_base(pc),a6
 lea     dd_orphanlist(a3),a0
 move.l  a2,a1
 bug     <"cnet: S2_READORPHAN ior $%08lx added to orphanlist $%08lx",10>,a1,a0
 MYDISABLE
 ADDTAIL                                 ; add ioreq to orphan queue
 MYENABLE

.done:
 movem.l (sp)+,a2/a3/a6
 rts

.unconfigured:
 moveq   #S2WERR_NOT_CONFIGURED,d0       ; error, device is not configured
 move.b  #S2ERR_BAD_STATE,io_error(a2)
 bra.s   .error

.offline:
 moveq   #S2WERR_UNIT_OFFLINE,d0         ; error, device is offline
 move.b  #S2ERR_OUTOFSERVICE,io_error(a2)
 bra.s   .error

.bad:
 moveq   #S2WERR_GENERIC_ERROR,d0        ; error, bad argument
 move.b  #S2ERR_BAD_ARGUMENT,io_error(a2)

.error:
 ifd     verbose
 bug     <"cnet: readorphan ior $%08lx error %ld",10>,a2,d0
 endc

 move.l  d0,ios2_wireerror(a2)
 move.l  a2,a1
 bsr     TermIO
 bra.s   .done



;======================================================
;                      CMD_WRITE
;======================================================
;
; if card is configured and online
;
 CNOP 0,4
devcmd_write:
 ifd  verbose
 bug  <"cnet: cmd_write(ior $%08lx, type %ld)",10>,a1,ios2_packettype(a1)
 endc

 movem.l a2/a3/a6,-(sp)
 move.l  a1,a2                            ; a2 = ioreq
 move.l  io_device(a2),a3                 ; a3 = device

 btst    #DDB_CONFIGURED,dd_flags(a3)     ; configured ?
 beq     .unconfigured
 btst    #DDB_ONLINE,dd_flags(a3)         ; online ?
 beq     .offline

; check ioreq's bufman validity

 move.l  ios2_buffermanagement(a2),d0
 beq.s   .bad                             ; eeeekh, no bufman ?
 move.l  d0,a0
 tst.l   bufman_copyfrombuf(a0)
 beq.s   .bad                             ; no routine for writing ?

 move.l  ios2_DataLength(a2),d1
 tst.b   io_flags(a2)                     ; (test SANA2IOB_RAW) raw packets ?
 bpl.s   .cooked

 cmp.l   #RAWPKT_SIZE,d1
 bls.s   .goodlen                         ; check packet size
 bra.s   .toobig

.cooked:
 cmp.l   #ETHERPKT_SIZE,d1
 bhi.s   .toobig

.goodlen:
 bclr    #IOB_QUICK,io_flags(a2)          ; must be queued
 move.l  exec_base(pc),a6
 lea     dd_writelist(a3),a0
 move.l  a2,a1
 ifd     verbose
 bug     <"cnet: CMD_WRITE ior $%08lx added to writelist $%08lx",10>,a1,a0
 endc
 MYDISABLE
 ADDTAIL                                  ; add ioreq to write queue
 MYENABLE

 lea     dd_txint(a3),a1
 jsr     _LVOCause(a6)                    ; start tx

.done:
 movem.l (sp)+,a2/a3/a6
 rts


.unconfigured:
 moveq   #S2WERR_NOT_CONFIGURED,d0        ; error, not configured
 move.b  #S2ERR_BAD_STATE,io_error(a2)
 bra.s   .error

.offline:
 moveq   #S2WERR_UNIT_OFFLINE,d0          ; error, device is offline
 move.b  #S2ERR_OUTOFSERVICE,io_error(a2)
 bra.s   .error

.bad:
 moveq   #S2WERR_GENERIC_ERROR,d0         ; error, bad argument
 move.b  #S2ERR_BAD_ARGUMENT,io_error(a2)
 bra.s   .error

.toobig:
 moveq   #S2EVENT_TX,d0                   ; return any TX events
 bsr     DoEvent
 moveq   #0,d0
 move.b  #S2ERR_MTU_EXCEEDED,io_error(a2) ; oops! packet too big

.error:
 ifd     verbose
 bug     <"cnet: write ior $%08lx error %ld",10>,a2,d0
 endc

 move.l  d0,ios2_wireerror(a2)
 move.l  a2,a1
 bsr     TermIO
 bra.s   .done



;==============================================
;                CMD_FLUSH
;==============================================
;
devcmd_flush:
 movem.l a1-a2/a6,-(sp)
 move.l  exec_base(pc),a6
 move.l  io_device(a1),a2             ; a2 = device

 bug     <"cnet: CMD_FLUSH ior $%08lx device $%08lx",10>,a1,a2

 ifgt    0
 ifd     debug
 ifd     verbose
 bug <"cnet: delaying...",10>
 move.l  #500000,d0
.wait:
 tst.b   $bfe001
 subq.l  #1,d0
 bne.s   .wait
 endc
 endc
 endc

 MYDISABLE

 move.l  a2,a0
 moveq   #IOERR_ABORTED,d0
 moveq   #0,d1
 bsr     AbortRW

 lea     dd_eventlist(a2),a0          ; abort all Event requests
 moveq   #IOERR_ABORTED,d0
 moveq   #0,d1
 bsr     AbortList

 MYENABLE
 bug     <"cnet: CMD_FLUSH done",10>

 movem.l (sp)+,a1-a2/a6
 bra     TermIO



;======================================================================
;                  abortlist(list, errcode, wireerror, execbase)
;                             a0     d0.b      d1.l      a6
;======================================================================
;
; Aborts all ios2requs in given list with ios2_Error set to errcode
; and ios2_wireerror set to wireerror.
;
AbortList:
 movem.l d2-d3/a2,-(sp)
 move.l  a0,a2
 move.b  d0,d2
 move.l  d1,d3
 bra.s   .flush

.loop:
 move.l  d0,a1
;move.b  #NT_REPLYMSG,ln_type(a1)     ; DON'T!
 move.b  d2,io_error(a1)
 move.l  d3,ios2_wireerror(a1)
;bug     <"cnet: AbortList ior $%08lx",10>,a1
 jsr     _LVOReplyMsg(a6)
.flush:
 move.l  a2,a0
 MYREMHEAD
 bne.s   .loop

 movem.l (sp)+,d2-d3/a2
 rts



;======================================================================
;                  abortrw(device, errcode, wireerror, execbase)
;                            a0      d0.b      d1.l      a6
;======================================================================
;
; Aborts all pending reads and writes with ios2_Error set to errcode
; and ios2_wireerror set to wireerror.
;
AbortRW:
 movem.l d2-d4/a2-a3,-(sp)
 move.l  a0,a3
 move.b  d0,d2
 move.l  d1,d3

 move.l  dd_bufmanlist(a3),a2
.reado:
 move.l  (a2),d4
 beq.s   .flushrest
 bra.s   .flushreads

.readloop:
 move.l  d0,a1
;move.b  #NT_REPLYMSG,ln_type(a1)     ; DON'T!
 move.b  d2,io_error(a1)
 move.l  d3,ios2_wireerror(a1)
;bug     <"cnet: AbortRead ior $%08lx",10>,a1
 jsr     _LVOReplyMsg(a6)             ; abort all Read requests
.flushreads:
 lea     bufman_rxqueue(a2),a0
 MYREMHEAD
 bne.s   .readloop

 move.l  d4,a2
 bra.s   .reado
.flushrest:

 lea     dd_orphanlist(a3),a0         ; abort all ReadOrphan requests
 move.b  d2,d0
 move.l  d3,d1
 bsr     AbortList

 lea     dd_writelist(a3),a0          ; abort all Write requests
 move.b  d2,d0
 move.l  d3,d1
 bsr     AbortList

 movem.l (sp)+,d2-d4/a2-a3
 rts



;==============================================
;               S2_ONLINE
;==============================================
;
;         Try to put device online
;
devcmd_online:
 bug     <"cnet: s2_online",10>

 move.l  a1,-(sp)
 move.l  io_device(a1),a0               ; a0 = device

; This is a workaround for Genesis.. it calls
; S2_ONLINE before S2_CONFIGINTERFACE.
 btst    #DDB_ONLINE,dd_flags(a0)       ; already online ?
 bne.s   .done

 btst    #DDB_CONFIGURED,dd_flags(a0)   ; won't go online unless configured!
 beq.s   .unconfigured
 btst    #DDB_NICUP,dd_flags(a0)        ; nic initialised ?
 beq.s   .uninit

 bclr    #DDB_OFFLINE,dd_flags(a0)
 bset    #DDB_ONLINE,dd_flags(a0)       ; mark being online

 move.b  dd_rcr(a0),nic_rcr             ; set receiver to normal mode

 moveq   #S2EVENT_ONLINE,D0
 bsr     DoEvent                        ; return any ONLINE events

.done:
 move.l  (sp)+,a1
 bra     TermIO


.unconfigured:
 moveq   #S2WERR_NOT_CONFIGURED,d0       ; error, device is not configured
 move.b  #S2ERR_BAD_STATE,io_error(a2)
 bra.s   .error

.uninit:
 moveq   #S2WERR_UNIT_OFFLINE,d0
 move.b  #S2ERR_OUTOFSERVICE,io_error(a1)
.error:
 move.l  d0,ios2_wireerror(a1)
 bra.s   .done




;==============================================
;               S2_OFFLINE
;==============================================
;
;           take device offline
;
;
devcmd_offline:
 bug     <"cnet: s2_offline",10>

 movem.l a1/a6,-(sp)
 move.l  io_device(a1),a0               ; a0 = device
 bset    #DDB_OFFLINE,dd_flags(a0)
 bclr    #DDB_ONLINE,dd_flags(a0)       ; already offline ?
 beq.s   .done

 btst    #DDB_NICUP,dd_flags(a0)
 beq.s   .nicoff
 move.b  #DSRC_MON,nic_rcr              ; set receiver to monitor mode
.nicoff:

 move.l  exec_base(pc),a6
 bug     <"cnet: s2_offline **NOT** aborting read/readorphan/write requests!",10>
 ifgt    0
 MYDISABLE
; a0=device
 moveq   #S2ERR_OUTOFSERVICE,d0
 moveq   #S2WERR_UNIT_OFFLINE,d1
 bsr     AbortRW
 MYENABLE
 endc

 moveq   #S2EVENT_OFFLINE,d0
 bsr     DoEvent                        ; return any OFFLINE events

.done:
 movem.l (sp)+,a1/a6
 bra     TermIO



;==============================================
;               S2_ONEVENT
;==============================================
;
;         queue up event requests
;
;  online/offline return immediately if the conditon is met.
;
devcmd_onevent:
 bug     <"cnet: s2_onevent mask $%08lx",10>,ios2_wireerror(a1)

 movem.l a1/a3/a6,-(sp)
 move.l  io_device(a1),a3            ; a3 = device

; do we know the requested events?
 move.l  ios2_wireerror(a1),d0
 and.l   #~(S2EVENT_ERROR|S2EVENT_TX|S2EVENT_RX|S2EVENT_ONLINE|S2EVENT_OFFLINE|S2EVENT_BUFF|S2EVENT_HARDWARE),d0
 bne     .unknown

 move.l  ios2_wireerror(a1),d0
 and.l   #S2EVENT_ONLINE,d0          ; waiting for online event ?
 beq.s   .noton
 btst    #DDB_ONLINE,dd_flags(a3)    ; currently online ?
 bne.s   .rtn
 bra.s   .queue

.noton:
 move.l  ios2_wireerror(a1),d0
 and.l   #S2EVENT_OFFLINE,d0         ; waiting for offline event ?
 beq.s   .queue
 btst    #DDB_ONLINE,dd_flags(a3)    ; currently offline ?
 bne.s   .queue

.rtn:
 bug     <"is (on|off)line",10>
 move.l  d0,ios2_wireerror(a1)       ; return immediately
;already! clr.b   io_error(a1)
.term:
 bsr     TermIO
 bra.s   .done

.queue:
 bug     <"queued",10>
 bclr    #IOB_QUICK,io_flags(a1)     ; must be queued
 move.l  exec_base(pc),a6
 lea     dd_eventlist(a3),a0
 MYDISABLE
 ADDTAIL                             ; add ioreq to event queue
 MYENABLE

.done:
 movem.l (sp)+,a1/a3/a6
 rts

.unknown:
 bug     <"unknown events requested!",10>
 moveq   #S2WERR_BAD_EVENT,d0
 move.b  #S2ERR_NOT_SUPPORTED,io_error(a1)
 move.l  d0,ios2_wireerror(a1)
 bra.s   .term



;==============================================
;             S2_DEVICEQUERY
;==============================================
;
devcmd_devicequery:
 bug     <"cnet: s2_devicequery",10>

 move.l  a1,-(sp)
 move.l  ios2_statdata(a1),a0    ; a0 = caller's buffer
 move.l  (a0),d1                 ; d1 = buffer size
 move.l  size_supplied(pc),d0
 cmp.l   d0,d1                   ; enough space to store info?
 bhs.s   .get

 clr.l   S2DQ_SIZESUPPLIED(a0)   ; nope!
.bad:
 moveq   #S2WERR_BAD_STATDATA,d0
 move.b  #S2ERR_BAD_ARGUMENT,io_error(a1)
 move.l  d0,ios2_wireerror(a1)
 bra.s   .done

.get:
 lea     S2DQ_SIZESUPPLIED(a0),a1
 lea     size_supplied(pc),a0

 subq.l  #4,d0                   ; skip bytes_available
 bra.s   .copy
.copyloop:
 move.b  (a0)+,(a1)+             ; copy info to caller's buffer
.copy:
 subq.w  #1,d0
 bpl.s   .copyloop

.done:
 move.l  (sp)+,a1
 bra     TermIO



;==============================================
;           S2_GETSTATIONADDRESS
;==============================================
;
devcmd_getstationaddress:
 bug     <"cnet: s2_getstationaddress",10>

 move.l  io_device(a1),a0
 move.l  a1,-(sp)
 lea     dd_stationaddress(a0),a0
 lea     ios2_srcaddr(a1),a1     ; source address = station address
 bsr.s   .makeit
 subq.l  #ETHER_ADDR_SIZE,a0     ; dest address = default address
 bsr.s   .makeit

 move.l  (sp)+,a1
 bra     TermIO

.makeit:
 move.l  (a0)+,(a1)+
 move.w  (a0)+,(a1)+
 clr.w   (a1)+
 clr.l   (a1)+
 clr.l   (a1)+
 rts



;==============================================
;             S2_CONFIGINTERFACE
;==============================================
;
; NOTE: a default station address has already
;       been set by init_nic
;
devcmd_configinterface:
 bug     <"cnet: s2_configinterface",10>

 move.l  io_device(a1),a0
 bset    #DDB_CONFIGURED,dd_flags(a0)   ; already configured ?
 bne     .already

 move.l  a6,-(sp)
 move.l  exec_base(pc),a6
 MYDISABLE

 move.l  ios2_srcaddr(a1),d0
 ble     .badaddr                       ; check for valid address

 lea     dd_stationaddress(a0),a0
 move.l  d0,(a0)
 move.w  ios2_srcaddr+4(a1),4(a0)

 move.b  nic_cr,d1                      ; remember current command
 delay
 move.b  #DSCM_NODMA|DSCM_PG1,nic_cr    ; select bank 1
 delay
 move.b  (a0)+,nic_par0
 delay
 move.b  (a0)+,nic_par1
 delay
 move.b  (a0)+,nic_par2
 delay                                  ; set station address
 move.b  (a0)+,nic_par3
 delay
 move.b  (a0)+,nic_par4
 delay
 move.b  (a0),nic_par5
 delay
 move.b  d1,nic_cr                      ; restore command

.done:
 MYENABLE
 move.l  (sp)+,a6
.termit:
 bra     TermIO

.badaddr:
 bclr    #DDB_CONFIGURED,dd_flags(a0)   ; NOT configured!
 moveq   #S2WERR_SRC_ADDRESS,d0         ; error, source address bad
 move.b  #S2ERR_BAD_ADDRESS,io_error(a1)
 move.l  d0,ios2_wireerror(a1)
 bra.s   .done

.already
 moveq   #S2WERR_IS_CONFIGURED,d0       ; error, already configured
 move.b  #S2ERR_BAD_STATE,io_error(a1)
 move.l  d0,ios2_wireerror(a1)
 bra.s   .termit



;==============================================
;              S2_MULTICAST
;==============================================
;
devcmd_multicast:
 bug     <"cnet: s2_multicast %08lx%04x",10>,ios2_dstaddr(a1),ios2_dstaddr+2(a1)

 btst    #0,ios2_dstaddr(a1)      ; is dest a multicast address ?
 bne     devcmd_write

 move.l  a1,-(sp)
 moveq   #S2EVENT_TX,d0           ; return any TX events
 bsr     DoEvent
 move.l  (sp)+,a1

 moveq   #S2WERR_BAD_MULTICAST,d0 ; wasn't a multicast addr
 move.b  #S2ERR_BAD_ADDRESS,io_error(a1)
 move.l  d0,ios2_wireerror(a1)
 bra     TermIO



;==============================================
;              S2_BROADCAST
;==============================================
;
devcmd_broadcast:
 bug     <"cnet: s2_broadcast",10>

 moveq   #-1,d0
 move.l  d0,ios2_dstaddr+0(a1)    ; dest address = BROADCAST (255)
 move.w  d0,ios2_dstaddr+4(a1)    ; dest address = BROADCAST (255)

 bra     devcmd_write



;============================================
;              S2_TRACKTYPE
;============================================
;
; This function adds a packet type to the
; list of those that are being tracked.
;
devcmd_tracktype:
 bug     <"cnet: s2_tracktype %ld",10>,ios2_packettype(a1)

 movem.l a2/a3/a6,-(sp)
 move.l  exec_base(pc),a6
 move.l  a1,a2                         ; a2 = ioreq
 move.l  io_device(a1),a3
 move.l  ios2_packettype(a2),d0        ; d0 = our packet type

 MYDISABLE                             ; lock access to list
 move.l  dd_tracklist(a3),a0           ; a0 = 1st entry in list
 bra.s   .start

.search:
 cmp.l   ss2_ptype(a0),d0              ; = our packet type ?
 beq.s   .duperr
 move.l  d1,a0
.start:
 move.l  (a0),d1
 bne.s   .search                       ; end of list ?

 moveq   #ss2_sizeof,d0                ; alloc tracking node
 bsr     alloc
 tst.l   d0
 beq.s   .nomem

 move.l  d0,a1                              ; a1 = new entry
 move.l  ios2_packettype(a2),ss2_ptype(a1)  ; set tracking type
 lea     dd_tracklist(a3),a0
 ADDTAIL                                    ; add new tracktype node to list

.done:
 MYENABLE                              ; release lock on lists
 move.l  a2,a1
 movem.l (sp)+,a2/a3/a6
 bra     TermIO


.nomem:
 moveq   #S2WERR_NOT_TRACKED,d0
 move.b  #S2ERR_NO_RESOURCES,io_error(a2)
 bra.s   .error                        ; error, can't allocate memory!

.duperr:
 moveq   #S2WERR_ALREADY_TRACKED,d0
 move.b  #S2ERR_BAD_STATE,io_error(a2)

.error:
 move.l  d0,ios2_wireerror(a2)         ; error, type is tracked already!
 bra.s   .done



;==============================================
;             S2_UNTRACKTYPE
;==============================================
;
; This function removes a packet type from the
; list of those that are being tracked.
;
devcmd_untracktype:
 bug     <"cnet: s2_untracktype %ld",10>,ios2_packettype(a1)

 movem.l a2-a3/a6,-(sp)
 move.l  exec_base(pc),a6
 move.l  a1,a2                  ; a2 = ioreq
 move.l  io_device(a1),a0
 move.l  ios2_packettype(a2),d0 ; d0 = packet type to untrack

 MYDISABLE                      ; lock access to list
 move.l  dd_tracklist(a0),a3    ; a3 = 1st entry in tracking list
 bra.s   .start

.search:
 cmp.l   ss2_ptype(a3),d0       ; found our packet type ?
 beq.s   .found
 move.l  d1,a3                  ; a3 = next entry
.start:
 move.l  (a3),d1
 bne.s   .search                ; end of list?
 bra.s   .error

.found:
 move.l  a3,a1
 REMOVE                         ; remove entry from list
 move.l  a3,a1
 moveq   #ss2_sizeof,d0         ; free entry's memory
 bsr     free

.done:
 MYENABLE                      ; unlock access to list
 move.l  a2,a1
 movem.l (sp)+,a2-a3/a6
 bra     TermIO


.error:
 moveq   #S2WERR_NOT_TRACKED,d0        ; error, no tracktype!
 move.b  #S2ERR_BAD_STATE,io_error(a2)
 move.l  d0,ios2_wireerror(a2)
 bra.s   .done



;==================================================
;               S2_GETTYPESTATS
;==================================================
; This function returns statistics for a specific
; type of packet that is being tracked.
;
devcmd_gettypestats:
 bug     <"cnet: s2_gettypestats",10>

 movem.l a2/a6,-(sp)
 move.l  exec_base(pc),a6
 move.l  a1,a2                       ; a2 = ioreq
 move.l  io_device(a1),a0
 move.l  ios2_packettype(a2),d0      ; d0 = packettype

 MYDISABLE                           ; lock access to list
 move.l  dd_tracklist(a0),a0         ; a0 = 1st entry in tracking list
 bra.s   .start

.loop:
 move.l  d1,a0                       ; a0  = next entry
.start:
 move.l  (a0),d1                     ; end of list ?
 beq.s   .notfound
 cmp.l   ss2_ptype(a0),d0            ; found our track type ?
 bne.s   .loop

; note that both ss2_stats and ios2_statdata are quaranteed to be
; word aligned.
 lea     ss2_stats(a0),a0
 move.l  ios2_statdata(a2),a1
 moveq   #s2pts_size/4,d0
.copystats
 move.l  (a0)+,(a1)+                 ; copy stats to ioreq
 subq.l  #1,d0
 bne.s   .copystats

.done:
 MYENABLE                            ; release lock on list
 move.l  a2,a1
 movem.l (sp)+,a2/a6
 bra     TermIO

.notfound:
 moveq   #S2WERR_NOT_TRACKED,d0
 move.b  #S2ERR_BAD_STATE,io_error(a2)
 move.l  d0,ios2_wireerror(a2)       ; error, couldn't find track type
 bra.s   .done



;==============================================
;            S2_GETSPECIALSTATS
;==============================================
;
devcmd_getspecialstats:
 bug     <"cnet: s2_getspecialstats",10>

 movem.l a1-a2,-(sp)
 move.l  io_device(a1),a2                    ; a2 = unit
 move.l  ios2_statdata(a1),a1
 clr.l   s2ssh_recordcountsupplied(a1)
 move.l  (a1)+,d0                            ; check s2ssh_recordcountmax
 beq.s   .done

 addq.l  #1,(a1)+                            ; s2ssh_recordcountsupplied++
 move.l  a1,a0
 move.l  #S2SS_ETHERNET_BADMULTICAST,(a0)+   ; set type      }
 move.l  dd_badmulticasts(a2),(a0)+          ; set statistic } bad multi
 move.l  #multiname,(a0)+                    ; set name      }
 subq.l  #1,d0
 beq.s   .done                               ; want more ?

 addq.l  #1,-(a1)                            ; s2ssh_recordcountsupplied++
 move.l  #S2SS_ETHERNET_RETRIES,(a0)+        ; set type      }
 move.l  dd_retries(a2),(a0)+                ; set statistic } retries
 move.l  #retryname,(a0)+                    ; set name      }

.done:
 movem.l (sp)+,a1-a2
 bra     TermIO



;==============================================
;           S2_GETGLOBALSTATS
;==============================================
;
devcmd_getglobalstats:
 bug     <"cnet: s2_getglobalstats",10>

 move.l  a1,-(sp)
 move.l  io_device(a1),a0

; note that both dd_devicestats and ios2_statdata are quaranteed to be
; word aligned.
;
 lea     dd_devicestats(a0),a0
 move.l  ios2_statdata(a1),a1
 moveq   #s2ds_size/4,d0
.copy:
 move.l  (a0)+,(a1)+             ; get sana2devicestats
 subq.l  #1,d0
 bne.s   .copy

 move.l  (sp)+,a1
 bra     TermIO



;==========================================================
;                        get_time
;==========================================================
;
;  in: a0 = pointer to timeval struct
;
;   get current time_of_day
;
;   (Laire: see the code sheduling is gone now! :-))
;
;
get_time:
 movem.l a0/a6,-(sp)             ; timeval ptr (a0) is at top of the stack

 ; is the timer.device open already?
 move.l  timereq+io_device(pc),d0
 beq.s   .open_now               ; io_device <> 0 ?
 addq.l  #1,d0
 bne.s   .is_open                ; io_device <> -1 ?

.open_now:
 move.l  exec_base(pc),a6
 lea     timerdevname(pc),a0
 lea     timereq(pc),a1
 moveq   #UNIT_VBLANK,d0
 move.b  #NT_MESSAGE,ln_type(a1)
 moveq   #0,d1
 move.w  #iotv_size,mn_length(a1)
 jsr     _LVOOpenDevice(a6)
 tst.l   d0
 bne.s   .exit                   ; failure, get out...

.is_open:
 lea     timereq(pc),a1
 move.l  io_device(a1),a6
 move.l  (sp),a0                 ; restore timeval ptr from stack
 jsr     _LVOGetSysTime(a6)
.exit:
 movem.l (sp)+,a0/a6
 rts



;==============================================
;              S2_ADDMULTICASTADDRESS
;==============================================
;
devcmd_addmulticast:
 bug     <"cnet: s2_addmulticastaddress %08lx%04x",10>,ios2_srcaddr(a1),ios2_srcaddr+2(a1)

 move.l  ios2_srcaddr+4(a1),-(sp)    ; lower = upper !
 move.l  ios2_srcaddr(a1),-(sp)
 move.l  ios2_srcaddr+4(a1),-(sp)
 move.l  ios2_srcaddr(a1),-(sp)

addmcast:
 movem.l a2-a4/a6,-(sp)
 move.l  a6,a3                       ; a3 = device
 move.l  exec_base(pc),a6
 move.l  a1,a4                       ; a4 = ioreq

 bsr.s   addmcastrange

 move.l  a4,a1
 movem.l (sp)+,a2-a4/a6
 lea     4*4(sp),sp
 bra     TermIO



;==============================================
;              S2_ADDMULTICASTADDRESSES
;==============================================
;
devcmd_addmulticasts:
 bug     <"cnet: s2_addmulticastaddresses %08lx%04x - %08lx%04x",10>,ios2_srcaddr(a1),ios2_srcaddr+2(a1),ios2_dstaddr(a1),ios2_dstaddr+2(a1)

 move.l  ios2_dstaddr+4(a1),-(sp)    ; upper
 move.l  ios2_dstaddr(a1),-(sp)
 move.l  ios2_srcaddr+4(a1),-(sp)    ; lower
 move.l  ios2_srcaddr(a1),-(sp)
 bra.s   addmcast




AMCRO    EQU 4+(4*4)
addmcastrange:
 btst    #0,AMCRO+0+0(sp)            ; is src a multicast address ?
 beq.s   .badmcast
 btst    #0,AMCRO+8+0(sp)            ; is dst a multicast address ?
 beq.s   .badmcast

 lea     dd_multicastssema(a3),a0
 jsr     _LVOObtainSemaphore(a6)     ; lock access to list
 move.l  dd_multicasts(a3),a0        ; a0 = first entry in multicast list
 bra.s   .getmulti

.check:
 move.l  AMCRO+0+0(sp),d0
 cmp.l   mcastar_loweraddr(a0),d0
 bne.s   .next
 move.w  AMCRO+0+4(sp),d0
 cmp.w   mcastar_loweraddr+4(a0),d0
 bne.s   .next
 move.l  AMCRO+8+0(sp),d0            ; is our address in an existing entry ?
 cmp.l   mcastar_upperaddr(a0),d0
 bne.s   .next                       
 move.w  AMCRO+8+4(sp),d0
 cmp.w   mcastar_upperaddr+4(a0),d0
 bne.s   .next

 addq.l  #1,mcastar_count(a0)        ; added same address again
 ifd     debug
 move.l  mcastar_count(a0),d0
 bug     <"cnet: mcast node $%lx count=%ld",10>,a0,d0
 endc
 bra     .unlock

.next:
 move.l  d1,a0                       ; get next node in list
.getmulti:
 move.l  (a0),d1                     ; end of list ?
 bne.s   .check

 moveq   #mcastar_sizeof,d0
 bsr     alloc                       ; allocate memory for multicast entry
 tst.l   d0
 beq.s   .nomem

 move.l  d0,a1                                  ; a1 = new entry
 move.w  #1,mcastar_count+2(a1)                 ; count = 1
 move.l  AMCRO+0+0(sp),mcastar_loweraddr(a1)
 move.w  AMCRO+0+4(sp),mcastar_loweraddr+4(a1)
 move.l  AMCRO+8+0(sp),mcastar_upperaddr(a1)
 move.w  AMCRO+8+4(sp),mcastar_upperaddr+4(a1)
 bug     <"cnet: new mcast node at $%08lx",10>,a1
 lea     dd_multicasts(a3),a0
 ADDTAIL                                        ; add to list
 move.l  a3,a0
 bsr     update_multicasts                      ; update multicasts in nic

.unlock
 lea     dd_multicastssema(a3),a0
 jmp     _LVOReleaseSemaphore(a6)    ; release lock on list


.badmcast:
 moveq   #S2WERR_BAD_MULTICAST,d0
 move.b  #S2ERR_BAD_ADDRESS,io_error(a4)
 move.l  d0,ios2_wireerror(a4)       ; invalid multicast address
 rts


.nomem:
 moveq   #S2WERR_GENERIC_ERROR,d0
 move.b  #S2ERR_NO_RESOURCES,io_error(a4)
 move.l  d0,ios2_wireerror(a4)
 bra.s   .unlock



;==============================================
;              S2_DELMULTICASTADDRESS
;==============================================
;
devcmd_delmulticast:
 bug     <"cnet: s2_delmulticastaddress %08lx%04x",10>,ios2_srcaddr(a1),ios2_srcaddr+2(a1)

 move.l  ios2_srcaddr+4(a1),-(sp)    ; lower = upper !
 move.l  ios2_srcaddr(a1),-(sp)
 move.l  ios2_srcaddr+4(a1),-(sp)
 move.l  ios2_srcaddr(a1),-(sp)

remmcast:
 movem.l a2-a4/a6,-(sp)
 move.l  a6,a3                       ; a3 = device
 move.l  exec_base(pc),a6
 move.l  a1,a4                       ; a4 = ioreq

 bsr.s   delmcastrange

 move.l  a4,a1
 movem.l (sp)+,a2-a4/a6
 lea     4*4(sp),sp
 bra     TermIO



;==============================================
;              S2_DELMULTICASTADDRESSES
;==============================================
;
devcmd_delmulticasts:
 bug     <"cnet: s2_delmulticastaddresses %08lx%04x - %08lx%04x",10>,ios2_srcaddr(a1),ios2_srcaddr+2(a1),ios2_dstaddr(a1),ios2_dstaddr+2(a1)

 move.l  ios2_dstaddr+4(a1),-(sp)    ; upper
 move.l  ios2_dstaddr(a1),-(sp)
 move.l  ios2_srcaddr+4(a1),-(sp)    ; lower
 move.l  ios2_srcaddr(a1),-(sp)
 bra.s   remmcast



DMCRO    EQU 4+(4*4)
delmcastrange:
 lea     dd_multicastssema(a3),a0
 jsr     _LVOObtainSemaphore(a6)     ; lock access to list
 move.l  dd_multicasts(a3),a2        ; a2 = first entry in multicast list
 bra.s   .getmulti

.check:
 move.l  DMCRO+0+0(sp),d0
 cmp.l   mcastar_loweraddr(a2),d0
 bne.s   .next
 move.w  DMCRO+0+4(sp),d0
 cmp.w   mcastar_loweraddr+4(a2),d0
 bne.s   .next
 move.l  DMCRO+8+0(sp),d0            ; is it our address ?
 cmp.l   mcastar_upperaddr(a2),d0
 bne.s   .next
 move.w  DMCRO+8+4(sp),d0
 cmp.w   mcastar_upperaddr+4(a2),d0
 bne.s   .next

 subq.l  #1,mcastar_count(a2)        ; one less count
 bne.s   .done

 bug     <"cnet: mcast entry $%08lx removed",10>,a2
 move.l  a2,a1
 REMOVE                              ; remove entry from list
 move.l  a2,a1
 moveq   #mcastar_sizeof,d0          ; free memory used by entry
 bsr     free

 move.l  a3,a0
 bsr     update_multicasts           ; update multicasts in nic
 bra.s   .done

.next:
 move.l  d1,a2                       ; get next node in list
.getmulti:
 move.l  (a2),d1                     ; end of list ?
 bne.s   .check

 moveq   #S2WERR_BAD_MULTICAST,d0
 move.b  #S2ERR_BAD_STATE,io_error(a4)
 move.l  d0,ios2_wireerror(a4)

.done:
 lea     dd_multicastssema(a3),a0
 jmp     _LVOReleaseSemaphore(a6)    ; release lock on list




;================================================================
;                    Update Multicasts
;================================================================
;
;  calculate multicast hash table and send it to nic
;
;  as of 1.2beta4 multicasts can be a address range, also single
;  addresses are defined with lower = upper.
;
;   update_multicasts(device)
;                       a0
update_multicasts:
 movem.l d2-d4/a2,-(sp)
 move.l  dd_multicasts(a0),a2
 lea     mcastaddr(pc),a0
 clr.l   (a0)+          ; clear all multicast bits
 clr.l   (a0)+

 subq.l  #8,sp          ; temp for address

.next:
 move.l  (a2),d4
 beq.s   .update        ; end of list ?
 move.l  mcastar_loweraddr(a2),(sp)
 move.w  mcastar_loweraddr+4(a2),4(sp)

.hashall:
 moveq   #-1,d0                 ; initial crc seed
 moveq   #6,d1                  ; 6 bytes in ethernet address
 move.l  sp,a0                  ; data to crc

.nextbyte:
 move.b  (a0)+,d3        ; get next data byte
 moveq   #8,d2           ; 8 bits in byte

.nextbit:
 lsl.l   #1,d0           ; crc << 1
 bcc.s   .crcnot1
 lsr.b   #1,d3           ; data >> 1
 bcc.s   .xor
 bra.s   .skipnext
.crcnot1:
 lsr.b   #1,d3           ; data >> 1
 bcc.s   .skipnext
.xor:
 eor.l   #$04c11db7,d0   ; crc = crc XOR polynomial
.skipnext:
 subq.l  #1,d2
 bne.s   .nextbit

 subq.l  #1,d1
 bne.s   .nextbyte

 rol.l   #6,d0
 lea     mcastaddr(pc),a0
 and.w   #%0000000000111111,d0  ; isolate upper 6 bits
 move.w  d0,d1
 and.b   #%00000111,d0          ; d0 = bit in byte
 lsr.w   #3,d1                  ; d1 = byte offset
 bset    d0,0(a0,d1.w)          ; set bit for hashed multicast address


 move.l  mcastar_upperaddr+2(a2),d0
 move.w  mcastar_upperaddr(a2),d1
 cmp.l   2(sp),d0       ; all done with this range ?
 bne.s   .still
 cmp.w   (sp),d1
 beq.s   .donext        ; all done with this range ?
.still:
; increment to next address...
 addq.l  #1,2(sp)
 bne.s   .hashall       ; did we overflow 32bit ?
 addq.w  #1,(sp)        ; 48bit add fix
 bra.s   .hashall

.donext:
 move.l  d4,a2
 bra.s   .next

.update:
 addq.l  #8,sp

 bsr     put_multi      ; update multicast bits in nic

 movem.l (sp)+,d2-d4/a2
 rts




 ifgt    0  ; this stuff inlined

;==============================================================
;                 hash multicast address
;==============================================================
; The nic does not compare multicast addresses directly, but
; instead uses a bitfield of 64 bits - each bit corresponds
; to a 6 bit hash code for the multicast address. If the bit
; is set then any multicast address that matches the hash code
; will be accepted.
;
; The hash code is generated by taking the upper 6 bits of the
; 32 bit crc of the multicast address.
;
;  hash_multi(address)
;               a0
;
hash_multi:
 moveq   #-1,d0                 ; initial crc seed
 moveq   #6,d1                  ; 6 bytes in ethernet address

 bsr.s   calc_crc               ; calculate crc for this multicast address

 rol.l   #6,d0
 lea     mcastaddr(pc),a0
 and.w   #%0000000000111111,d0  ; isolate upper 6 bits
 move.w  d0,d1
 and.b   #%00000111,d0          ; d0 = bit in byte
 lsr.w   #3,d1                  ; d1 = byte offset
 bset    d0,0(a0,d1.w)          ; set bit for hashed multicast address
 rts



;===========================================================================
;                          Calculate 32 bit crc
;===========================================================================
;
; - for each bit: if (crcbit=1 XOR databit=1) then crc=(crc XOR polynomial).
; - polynomial is correct when data bits range 0-x, crc bits range 31-0.
; - crc wraps at 32 bits.
;
;  crc = calc_crc(data, seed, bytelength)
;   d0.l           a0    d0.l     d1.l
;
calc_crc:
 movem.l d2/d3,-(sp)

.nextbyte:
 move.b  (a0)+,d3        ; get next data byte
 moveq   #8,d2           ; 8 bits in byte

.nextbit:
 lsl.l   #1,d0           ; crc << 1
 bcc.s   .crcnot1
 lsr.b   #1,d3           ; data >> 1
 bcc.s   .xor
 bra.s   .next
.crcnot1:
 lsr.b   #1,d3           ; data >> 1
 bcc.s   .next
.xor:
 eor.l   #$04c11db7,d0   ; crc = crc XOR polynomial
.next:
 subq.l  #1,d2
 bne.s   .nextbit

 subq.l  #1,d1
 bne.s   .nextbyte

 movem.l (sp)+,d2/d3
 rts

 endc



;==============================================================
;      Put Multicast address bitfield into NIC registers
;==============================================================
;
put_multi:
 move.l  a6,-(sp)
 move.l  exec_base(pc),a6
 lea     mcastaddr(pc),a0
 MYDISABLE
 delay
 move.b  nic_cr,d0            ; save old command
 delay
 move.b  #DSCM_PG1|DSCM_NODMA|DSCM_STOP,nic_cr ; select page 1
 delay
 move.b  (a0)+,nic_mar0
 delay
 move.b  (a0)+,nic_mar1
 delay
 move.b  (a0)+,nic_mar2
 delay
 move.b  (a0)+,nic_mar3
 delay                        ; put Multicast bits
 move.b  (a0)+,nic_mar4
 delay
 move.b  (a0)+,nic_mar5
 delay
 move.b  (a0)+,nic_mar6
 delay
 move.b  (a0),nic_mar7
 delay
 move.b  d0,nic_cr            ; restore old command
 MYENABLE
 move.l  (sp)+,a6
 rts



;=========================================================
;              commands not supported
;=========================================================
;
devcmd_nosupport:
 bug     <"cnet: Unsupported command %d",10>,io_command-2(a1)

 moveq   #S2WERR_GENERIC_ERROR,d0
 move.b  #S2ERR_NOT_SUPPORTED,io_error(a1)
 move.l  d0,ios2_wireerror(a1)
 bra     TermIO



;==========================================================
;             doevent([device,] events)
;                       a0       d0
;==========================================================
;
; called when an 'important' event occurs
;
DoEvent:
 bug     <"cnet: doevent $%08lx ...">,d0

 movem.l d2-d3/a2/a6,-(sp)
 move.l  exec_base(pc),a6
 move.l  d0,d2
 move.l  me_myself(pc),a0        ; get global pointer to self...
 MYDISABLE                       ; lock eventlist

 move.l  dd_eventlist(a0),a2     ; get first ioreq
 bra.s   .start

.loop:
 move.l  ios2_wireerror(a2),d0
 and.l   d2,d0                   ; ioreq for our events?
 beq.s   .next

 move.l  d0,ios2_wireerror(a2)   ; set the actual events
 move.l  a2,a1
 REMOVE                          ; remove ioreq from list
 move.b  #NT_REPLYMSG,ln_type(a2)
 move.l  a2,a1
 bug     <"cnet: returned eventreq $%lx",10>,a1
 jsr     _LVOReplyMsg(a6)        ; send reply to ioreq owner

.next:
 move.l  d3,a2                   ; next ioreq

.start:
 move.l  (a2),d3                 ; last ioreq ?
 bne.s   .loop

 MYENABLE                        ; unlock eventlist
 bug     <10,"cnet: doevent done",10>
 movem.l (sp)+,d2-d3/a2/a6
 rts



;==================================================================
;        RemoteRead(buffer, nicbuffer, length)
;                     a1       d0.w     d1.w
;==================================================================
;
; Get a copy of data stored in the network card's onboard RAM.
;
;  buffer     = Amiga RAM
;
;  nicbuffer  = 16 bit address in card memory
;
 CNOP 0,4
;RemoteRead:
REMOTEREAD MACRO

 ifd     turboio

 addq.w  #3,d1                   ; bump up count to even value
 and.w   #-4,d1
 delay
 swap    d1
 move.b  nic_cr,d1               ; save old command
 swap    d1
 delay
 move.b  #DSCM_NODMA|DSCM_START,nic_cr ; select bank 0
 delay
 move.b  d1,nic_rbcr0            ;   set count.lo
 ror.w   #8,d1
 delay
 move.b  d1,nic_rbcr1            ;   set count.hi
 delay
 move.b  d0,nic_rsar0            ;   set address.lo
 ror.w   #8,d0
 delay
 move.b  d0,nic_rsar1            ;   set address.hi
 delay
 move.b  #DSCM_RREAD|DSCM_START,nic_cr ; request Remote Read
 ror.w   #8,d1
 move.w  d1,d0
 and.w   #%0000000000111100,d1
 lea     nic_data,a0
 lsr.w   #1,d1
 lsr.w   #6,d0
 neg.w   d1
 jmp     .rr_startread(pc,d1.w)
.rr_dmaread:
 move.l  (a0),(a1)+
 move.l  (a0),(a1)+
 move.l  (a0),(a1)+
 move.l  (a0),(a1)+
 move.l  (a0),(a1)+
 move.l  (a0),(a1)+
 move.l  (a0),(a1)+
 move.l  (a0),(a1)+
 move.l  (a0),(a1)+
 move.l  (a0),(a1)+
 move.l  (a0),(a1)+
 move.l  (a0),(a1)+
 move.l  (a0),(a1)+
 move.l  (a0),(a1)+
 move.l  (a0),(a1)+
 move.l  (a0),(a1)+
.rr_startread:
 subq.w  #1,d0
 bpl.s   .rr_dmaread

 move.b  #DSIS_RDC,nic_isr       ; Remote DMA Complete
 swap    d1
 delay
 move.b  d1,nic_cr               ; restore old command
;rts

 else

 addq.w  #1,d1                   ; bump up count to even value
 and.w   #-2,d1
 delay
 swap    d1
 move.b  nic_cr,d1               ; save old command
 swap    d1
 delay
 move.b  #DSCM_NODMA|DSCM_START,nic_cr ; select bank 0
 delay
 move.b  d1,nic_rbcr0            ;   set count.lo
 ror.w   #8,d1
 delay
 move.b  d1,nic_rbcr1            ;   set count.hi
 delay
 move.b  d0,nic_rsar0            ;   set address.lo
 ror.w   #8,d0
 delay
 move.b  d0,nic_rsar1            ;   set address.hi
 delay
 move.b  #DSCM_RREAD|DSCM_START,nic_cr ; request Remote Read
 ror.w   #8,d1
 move.w  d1,d0
 and.w   #%0000000000011110,d0
 lea     nic_data,a0
 neg.w   d0
 lsr.w   #5,d1
 jmp     .rr_startread(pc,d0.w)
.rr_dmaread:
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
.rr_startread:
 subq.w  #1,d1
 bpl.s   .rr_dmaread

 move.b  #DSIS_RDC,nic_isr       ; Remote DMA Complete
 swap    d1
 delay
 move.b  d1,nic_cr               ; restore old command
;rts

 endc
 ENDM


;=================================================================
;         RemoteWrite( buffer, nicbuffer, count )
;                        a1      d0.w     d1.w
;=================================================================
;
;      Puts data into the network card's onboard RAM
;
;  buffer    = Amiga memory
;
;  nicbuffer = 16 bit address in card RAM
;
;
 CNOP 0,4
;RemoteWrite:
REMOTEWRITE MACRO
 ifd     turboio

 addq.w  #3,d1
 and.w   #-4,d1                     ; bump up count to even value
 swap    d1
 delay
 move.b  nic_cr,d1                  ; save old command
 swap    d1
 delay
 move.b  #DSIS_RDC,nic_isr          ; remote DMA complete
 delay
 move.b  #DSCM_NODMA|DSCM_START,nic_cr ; select bank 0
 delay
 move.b  d0,nic_rsar0               ; set address.lo
 lsr.w   #8,d0
 delay
 move.b  d0,nic_rsar1               ; set address.hi
 delay
 move.b  d1,nic_rbcr0               ; set count.lo
 ror.w   #8,d1
 delay
 move.b  d1,nic_rbcr1               ; set count.hi
 delay
 move.b  #DSCM_START|DSCM_RWRITE,nic_cr ; request remote write
 ror.w   #8,d1

; write the etherpacket header & prepare for the copy loop.
 lea     nic_data,a0
 sub.w   #ether_data+2,d1
 move.l  txbuffer(pc),(a0)
 move.w  d1,d0
 move.l  txbuffer+4(pc),(a0)
 and.w   #%0000000000111100,d1
 move.l  txbuffer+8(pc),(a0)
 lsr.w   #1,d1
 move.w  txbuffer+12(pc),(a0)
 lsr.w   #6,d0
 neg.w   d1
 move.w  (a1)+,(a0)
 jmp     .rw_startwrite(pc,d1.w)
.rw_dmawrite:
 move.l  (a1)+,(a0)
 move.l  (a1)+,(a0)
 move.l  (a1)+,(a0)
 move.l  (a1)+,(a0)
 move.l  (a1)+,(a0)
 move.l  (a1)+,(a0)
 move.l  (a1)+,(a0)
 move.l  (a1)+,(a0)
 move.l  (a1)+,(a0)
 move.l  (a1)+,(a0)
 move.l  (a1)+,(a0)
 move.l  (a1)+,(a0)
 move.l  (a1)+,(a0)
 move.l  (a1)+,(a0)
 move.l  (a1)+,(a0)
 move.l  (a1)+,(a0)
.rw_startwrite:
 subq.w  #1,d0
 bpl.s   .rw_dmawrite

 move.w  #30000,d0                  ; set timeout
.rw_check:
 delay
 move.b  nic_isr,d1                 ; wait for remote DMA complete
 and.b   #DSIS_RDC,d1
 dbne    d0,.rw_check

; d0.w < 0   send timed out
; d0.w >= 0  send succeeded

 delay
 move.b  #DSIS_RDC,nic_isr          ; Remote DMA complete
 swap    d1
 delay
 move.b  d1,nic_cr                  ; restore old command
;rts

 else

 addq.w  #1,d1
 and.w   #-2,d1                     ; bump up count to even value
 swap    d1
 delay
 move.b  nic_cr,d1                  ; save old command
 swap    d1
 delay
 move.b  #DSIS_RDC,nic_isr          ; remote DMA complete
 delay
 move.b  #DSCM_NODMA|DSCM_START,nic_cr ; select bank 0
 delay
 move.b  d0,nic_rsar0               ; set address.lo
 lsr.w   #8,d0
 delay
 move.b  d0,nic_rsar1               ; set address.hi
 delay
 move.b  d1,nic_rbcr0               ; set count.lo
 ror.w   #8,d1
 delay
 move.b  d1,nic_rbcr1               ; set count.hi
 delay
 move.b  #DSCM_START|DSCM_RWRITE,nic_cr ; request remote write

; write the etherpacket header & prepare for the copy loop.
 lea     nic_data,a0
 ror.w   #8,d1
 move.w  txbuffer(pc),(a0)
 sub.w   #ether_data,d1
 move.w  txbuffer+2(pc),(a0)
 move.w  d1,d0
 move.w  txbuffer+4(pc),(a0)
 move.w  txbuffer+6(pc),(a0)
 and.w   #%0000000000011110,d1
 move.w  txbuffer+8(pc),(a0)
 lsr.w   #5,d0
 move.w  txbuffer+10(pc),(a0)
 neg.w   d1
 move.w  txbuffer+12(pc),(a0)
 jmp     .rw_startwrite(pc,d1.w)
.rw_dmawrite:
 move.w  (a1)+,(a0)
 move.w  (a1)+,(a0)
 move.w  (a1)+,(a0)
 move.w  (a1)+,(a0)
 move.w  (a1)+,(a0)
 move.w  (a1)+,(a0)
 move.w  (a1)+,(a0)
 move.w  (a1)+,(a0)
 move.w  (a1)+,(a0)
 move.w  (a1)+,(a0)
 move.w  (a1)+,(a0)
 move.w  (a1)+,(a0)
 move.w  (a1)+,(a0)
 move.w  (a1)+,(a0)
 move.w  (a1)+,(a0)
 move.w  (a1)+,(a0)
.rw_startwrite:
 subq.w  #1,d0
 bpl.s   .rw_dmawrite

 move.w  #30000,d0                  ; set timeout
.rw_check:
 delay
 move.b  nic_isr,d1                 ; wait for remote DMA complete
 and.b   #DSIS_RDC,d1
 dbne    d0,.rw_check

; if d0.w<0 timed out
; if d0.w>=0 succeeded

 delay
 move.b  #DSIS_RDC,nic_isr          ; Remote DMA complete
 swap    d1
 delay
 move.b  d1,nic_cr                  ; restore old command
;rts

 endc
 ENDM



;=========================================================
;                      reset_nic()
;=========================================================
;
reset_nic:
 delay
 move.b  nic_rst,d0                       ; start reset pulse
 delay
 move.b  d0,nic_rst                       ; end reset pulse
 delay
 move.b  #DSCM_NODMA|DSCM_STOP,nic_cr     ; stop controller
;
; wait 1.5mS
;
 move.w   #1500,d0
.loop:
 tst.b    $bfe001       ; wait 1uS
 dbf      d0,.loop

 move.b  #$ff,nic_isr                     ; clear all nic ints
 rts


 ifgt    0  ; this stuff inlined

;======================
;  delay approx 1.5mS
;======================
;
delay1500:
 move.l   d0,-(sp)
 move.w   #1500,d0
.loop:
 tst.b    $bfe001       ; wait 1uS
 dbf      d0,.loop
 move.l   (sp)+,d0
 rts

 endc


;========================================================================
;                           init_nic(device)
;                                      a1
;========================================================================
;
; Set up the network card for online operation. We also get the hardware
; station address from the nic's ROM.
;
init_nic:
 movem.l d2/a5/a6,-(sp)
 move.l  exec_base(pc),a6
 move.l  a1,a5                             ; a5 = device data
 MYDISABLE                                 ; ignore ints while setting up
 btst    #DDB_NICUP,dd_flags(a5)
 bne     .ok                               ; already initialised ?

 move.b  #DSDC_WTS|DSDC_FT1|DSDC_BMS,dd_dcr(a5)  ; Word Xfer, FIFO, Burst
 move.b  #DSRC_AB|DSRC_AM,dd_rcr(a5)       ; accept broadcast & multicast
 btst    #DDB_PROM,dd_flags2(a5)
 beq.s   .notprom                              ; if promiscuous..
 or.b    #DSRC_SEP|DSRC_PRO|DSRC_AR,dd_rcr(a5) ; ..accept all packets
.notprom:

 move.b  #INTMASK,dd_imr(a5)               ; accept useful interrupts
 bsr     reset_nic                         ; reset the controller
 delay
 move.b  nic_cr,d0                         ; get command
 cmp.b   #DSCM_NODMA|DSCM_STOP,d0
 bne     .error1                           ; correct response ?

 delay
 move.b  dd_dcr(a5),nic_dcr                ; set data configuration register
 delay
 move.b  #0,nic_rbcr0                      ; clear remote byte count
 delay
 move.b  #0,nic_rbcr1                      ;         ''
 delay
 move.b  #DSRC_MON,nic_rcr                 ; set rx to monitor mode
 delay
 move.b  #DSTC_LB0,nic_tcr                 ; set tx to loopback mode 1
 delay
 move.b  #(RBUFEND/256)-1,nic_bnry         ; set boundary page
 delay
 move.b  #RBUF/256,nic_pstart              ; set start of rx ring buffer
 delay
 move.b  #RBUFEND/256,nic_pstop            ; set end of rx ring buffer
 delay
 move.b  #$ff,nic_isr                      ; clear all interrupts
 delay
 move.b  #0,nic_imr                        ; no interrupts allowed
 delay
 move.b  nic_rsr,d0
 delay
 move.b  nic_ncr,d0
 delay
 move.b  nic_cntr0,d0                      ; read status registers
 delay
 move.b  nic_cntr1,d0
 delay
 move.b  nic_cntr2,d0
 delay
 move.b  #ETHER_ADDR_SIZE*2,nic_rbcr0      ; byte count low = (words)
 delay
 move.b  #0,nic_rbcr1                      ; byte count high = 0
 delay
 move.b  #0,nic_rsar0                      ; remote start addr low = 0 (ROM)
 delay
 move.b  #0,nic_rsar1                      ; remote start addr high = 0 (ROM)
 delay
 move.b  #DSCM_RREAD,nic_cr                ; start remote read to get
 delay                                     ; station address from ROM
 lea     dd_romstationaddress(a5),a0

 moveq   #ETHER_ADDR_SIZE,d0
.getaddr:
 move.b  nic_data,(a0)+                    ; get ROM station address
 subq.l  #1,d0                             ; NOTE: 'move.b' as ROM is 8 bit
 bne.s   .getaddr                          

 move.w  #30000,d1
.waitloop:
 delay
 move.b  nic_isr,d0
 and.b   #DSIS_RDC,d0                      ; wait for remote DMA complete
 dbne    d1,.waitloop

 tst.w   d1
 bmi     .error2                           ; error if timed out
 delay
 move.b  #DSIS_RDC,nic_isr                 ; clear remote DMA complete int

 lea     dd_romstationaddress(a5),a0
 tst.b   (a0)
 bmi.s   .badaddr                          ; good station address ?

 move.l  2(a0),d0
 beq.s   .badaddr
 cmp.l   #-1,d0
 bne.s   .gotstation
.badaddr:
 bug     <"cnet: bad station address %08lx%04lx, using default",10>,(a0),2(a0)
 lea     default_address(pc),a0
.gotstation:
 bug     <"cnet: station address set to %08lx%04lx",10>,(a0),2(a0)

 lea     dd_stationaddress(a5),a1
 move.l  (a0)+,(a1)+                       ; copy address to device data
 move.w  (a0)+,(a1)+

 lea     dd_stationaddress(a5),a0
 delay
 move.b  #DSCM_NODMA|DSCM_PG1|DSCM_STOP,nic_cr ; select bank 1
 delay
 move.b  (a0)+,nic_par0
 delay
 move.b  (a0)+,nic_par1
 delay
 move.b  (a0)+,nic_par2
 delay                                     ; set station address
 move.b  (a0)+,nic_par3
 delay
 move.b  (a0)+,nic_par4
 delay
 move.b  (a0),nic_par5
 delay
 move.b  #RBUF/256,nic_curr                ; set current page for rx
 move.b  #DSCM_NODMA|DSCM_START,d0
 delay
 move.b  d0,nic_cr                         ; start controller
 delay
 cmp.b   nic_cr,d0
 bne     .error3                           ; command accepted ?

 bsr     put_multi                         ; load multicast registers

 delay
 move.b  dd_rcr(a5),nic_rcr                ; normal rx mode
 delay
 move.b  #0,nic_tcr                        ; loopback mode off
 delay
 move.b  #TBUF/256,nic_tpsr                ; init tx start page
 delay
 move.b  #$ff,nic_isr                      ; clear all interrupts
 delay
 move.b  dd_imr(a5),nic_imr                ; enable nic interrupts

 bset    #DDB_NICUP,dd_flags(a5)           ; nic is initialised

.ok:
 lea     dd_devicestats+s2ds_laststart(a5),a0
 bsr     get_time                          ; record start time_of_day
 bug     <"cnet: init_nic OK",10>
 moveq   #0,d0                             ; return OK

.done:
 MYENABLE                                  ; allow interrupt processing
 movem.l (sp)+,d2/a5/a6
 rts


.error1:
 bug     <"cnet: failed reset, card not compatible?",10>
 bra.s   .bad

.error2:
 bug     <"cnet: DMA timeout getting station address!",10>
 bra     .reset

.error3:
 bug     <"cnet: failed to start controller, card faulty?",10>

.reset:
 bsr     reset_nic                         ; reset nic after malfunction
.bad:

 moveq   #S2EVENT_ERROR|S2EVENT_HARDWARE,d0
 bsr     DoEvent
 moveq   #-1,d0                            ; return error
 bra     .done



;--------------------------------------------------------
;                     kill_nic(device)
;                                a1
;--------------------------------------------------------
;
kill_nic:
 bug     <"cnet: kill_nic",10>

 move.l  a6,-(sp)
 move.l  exec_base(pc),a6
 MYDISABLE

 bclr    #DDB_NICUP,dd_flags(a1)
 beq.s   .done                    ; already killed ?
 bsr     reset_nic                ; reset controller
 delay
 move.b  #DSRC_MON,nic_rcr        ; set rx to monitor mode
 delay
 move.b  #DSTC_LB0,nic_tcr        ; set tx to loopback mode
.done:

 MYENABLE
 move.l  (sp)+,a6
 rts



;========================================================
;                 txintcode(device)
;                             a1
;========================================================
;
;  send packets to network card. packets will be put
;  into the card's onboard 16 bit ram, and then
;  transmitted to the wire.
;
; scratch: d0-d1, a0-a1, and a5 (softint must preserve a6!)
;
txintcode:
 movem.l d2-d4/a2-a3/a6,-(sp)
 move.l  exec_base(pc),a6
 move.l  a1,a5                      ; a5 = device

.next:
 btst    #DDB_TX,dd_flags(a5)       ; quit if tx in progress (status int
 bne.s   .done                      ; will restart us when tx complete)

 lea     dd_writelist(a5),a0
 MYREMHEAD                          ; remove top ioreq
 bne.s   .gotreq                    ; any ioreqs to process?

.done:
 movem.l (sp)+,d2-d4/a2-a3/a6
 moveq   #0,d0
 rts


.gotreq:
 move.l  d0,a3                      ; a3 = ioreq

 lea     txbuffer(pc),a1            ; a1 = our internal packet buffer
 move.l  ios2_buffermanagement(a3),a2
 move.l  ios2_datalength(a3),d4     ; default: raw packet's full length
 move.l  ios2_packettype(a3),d3     ; d3 = packettype (must be in d3 in non-raw too!!!)
 tst.b   io_flags(a3)               ; (test SANA2IOB_RAW) raw packets?
 bmi.s   .send

; ok. it's not raw packet so we need to build etherheader for it.
; first destination and source addresses then packet type. easy!

 lea     ios2_dstaddr(a3),a0        ; dstaddress
 move.l  (a0)+,(a1)+
 move.w  (a0)+,(a1)+
 lea     dd_stationaddress(a5),a0   ; from address
 move.l  (a0)+,(a1)+
 move.w  (a0)+,(a1)+
 move.w  d3,(a1)+                   ; insert packettype into packet
 add.w   #ether_data,d4             ; d4 = length of header + data


.send:
; Do we have the tag for DMA?
 lea     bufman_dmacopyfrombuf32(a2),a2
 move.l  a1,d2                      ; d2 = where to copyfrom
 move.l  (a2)+,d0
 beq.s   .nodma

; Ask for the DMA address
 move.l  d0,a1
 move.l  ios2_data(a3),a0           ; a0=abstract magic cookie ios2_Data
 jsr     (a1)
 tst.l   d0                         ; dma possible?
 move.l  d0,a1
; If dma is possible we now have the direct application buffer
; in a1.. and we can use that directly with RemoteWrite! cool.
 ifd     verbose
 tst.l   d0
 beq.s   .v1_nodma
 bug     <"cnet: using DMA write from $%08lx!",10>,a1
 tst.l   d0
.v1_nodma:
 endc
 bne.s   .copyok                    ; do it with DMA?


.nodma:
; Ohwell, the caller doesn't support DMA transfer, so lets fall
; back to regular copyfrom.

 move.l  d2,a0                      ; a0=to (contiguous)
 move.l  ios2_data(a3),a1           ; a1=from (abstract)
 move.l  (a2),a2                    ; get bufman_copyfrombuf
 move.l  ios2_datalength(a3),d0     ; d0=n
 jsr     (a2)
 tst.l   d0
 beq     .copyfromfail              ; oh fuck.. copyfrombuf failed!

; we can return the ioreq now!
 move.l  a3,a1
 sub.l   a3,a3
 bsr     TermIO                     ; finish IOrequest

 lea     txbuffer+ether_data(pc),a1 ; non-DMA so use txbuffer for copy!!
.copyok:

 moveq   #ETHER_MIN_LEN,d0
 cmp.l   d0,d4
 bge.s   .min                       ; d4 adjusted to legal packet size
 move.l  d0,d4
.min:

 ifd     verbose
 move.l  a1,-(sp)
 moveq   #0,d1
 move.w  txbuffer+ether_type(pc),d1
 bug     <"cnet: wrpkt %08lx%04lx >> %08lx%04lx ">,txbuffer+6(pc),txbuffer+8(pc),txbuffer(pc),txbuffer+2(pc)
 cmp.w   #1500,d1
 bhs.s   .type
 bug     <"(IEEE802.3)len %ld, ">,d1
 bra.s   .bytes
.type:
 bug     <"type %ld, ">,d1
.bytes:
 bug     <"%ld bytes",10>,d4
 moveq   #-ether_data,d0            ; show only the data
 add.l   d4,d0
 bsr     showpkt
 move.l  (sp)+,a1
 endc


; copy the transfer buffer to cards memory

 move.w  #TBUF,d0
 move.w  d4,d1
; a1 = txbuffer (or DMA buffer)
;bsr     RemoteWrite                ; put packet into nic tx buffer
 REMOTEWRITE
 tst.w   d0
 bmi.s   .xmiterr



; now really start the transfer:

 MYDISABLE                          ; disable interrupts during tx setup
 bset    #DDB_TX,dd_flags(a5)       ; set our "buffer full" flag
 delay
 move.b  d4,nic_tbcr0               ; set tx byte count lo
 ror.w   #8,d4
 delay
 move.b  d4,nic_tbcr1               ; set tx byte count hi
 ror.w   #8,d4
 delay
 move.b  #DSCM_NODMA|DSCM_TRANS|DSCM_START,nic_cr ; start tx
 MYENABLE                           ; enable interrupts

; update statistics:

 move.l  dd_tracklist(a5),a0
.getstats:
 move.l  (a0),d2                    ; tracking packettypes ?
 beq.s   .statsdone
 cmp.l   ss2_ptype(a0),d3           ; stats entry for our type ?
 beq.s   .gotstats
 move.l  d2,a0                      ; try next stats entry
 bra.s   .getstats

.gotstats:
 add.l   d4,ss2_stats+s2pts_txbytes(a0)
 addq.l  #1,ss2_stats+s2pts_txpackets(a0)
.statsdone:


.termio:
 move.l  a3,d0                      ; have we replied it already?
 beq     .next                      ; we have! process next ioreq

 move.l  a3,a1
 bsr     TermIO                     ; finish IOrequest
 bra     .next                      ; process next ioreq


.copyfromfail:
; note: doesn't get called if a3=0
 moveq   #S2EVENT_TX|S2EVENT_BUFF,d0
 bsr     DoEvent
 clr.l   ios2_datalength(a3)
 moveq   #S2WERR_BUFF_ERROR,d0
 move.b  #S2ERR_NO_RESOURCES,io_error(a3)
 move.l  d0,ios2_wireerror(a3)
 bra.s   .termio

.xmiterr:
 moveq   #S2EVENT_ERROR|S2EVENT_TX,d0
 bsr     DoEvent
 bra.s   .termio



;============================================================
;                   rxintcode(device)
;                               a1
;============================================================
;
;                service rx interrupts
;
; scratch: d0-d1, a0-a1, and a5 (softint must preserve a6!)
;
rxintcode:
 movem.l d6/d7/a6,-(sp)
 move.l  exec_base(pc),a6
 move.l  a1,a5                             ; a5 = device

.nextpage:

 MYDISABLE
 delay
 moveq   #0,d7
 move.b  #DSCM_NODMA|DSCM_PG1|DSCM_START,nic_cr ; select bank 1
 move.b  nic_curr,d7                       ; d7 = current page
 delay
 move.b  #DSCM_NODMA|DSCM_START,nic_cr     ; select bank 0
 MYENABLE

 moveq   #0,d6
 delay
 move.b  nic_bnry,d6
 addq.w  #1,d6                             ; d6 = next page (boundary+1)
 cmp.w   #RBUFEND/256,d6
 blo.s   .nowrap                           ; end of buffer mem  ?
 moveq   #RBUF/256,d6                      ; wrap around to start
.nowrap:
 cmp.w   d6,d7                             ; current page = next page ?
 beq     .done                             ; if so then nothing to get

 ifgt    0
 move.w  d6,d0
 lea     rx_header(pc),a1                  ; a1 = buffer
 lsl.w   #8,d0                             ; d0 = 16 bit page address
 moveq   #prhdr_sizeof+ether_data,d1
 bsr     RemoteRead                        ; get packet header

 else

; inlined RemoteRead:

 lea     nic_data,a0
 lea     rx_header(pc),a1                  ; a1 = buffer
 delay
 move.b  nic_cr,d1               ; save old command
 delay
 move.b  #DSCM_NODMA|DSCM_START,nic_cr ; select bank 0
 delay
 move.b  #prhdr_sizeof+ether_data,nic_rbcr0 ;   set count.lo
 delay
 move.b  #0,nic_rbcr1            ;   set count.hi
 delay
 move.b  #0,nic_rsar0            ;   set address.lo
 delay
 move.b  d6,nic_rsar1            ;   set address.hi  (d6=16 bit page address hi)
 delay
 move.b  #DSCM_RREAD|DSCM_START,nic_cr ; request Remote Read

 ; transfer prhdr_sizeof+ether_data = 4+14 = 18 = 2*9

 ifd     turboio
 move.l  (a0),(a1)+              ; 4*4 = 16
 move.l  (a0),(a1)+
 move.l  (a0),(a1)+
 move.l  (a0),(a1)+
 else
 move.w  (a0),(a1)+              ; 4*4 = 16
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 move.w  (a0),(a1)+
 endc
 move.w  (a0),(a1)+              ; +2

 move.b  #DSIS_RDC,nic_isr       ; Remote DMA Complete
 delay
 move.b  d1,nic_cr               ; restore old command

 endc

 move.b  rx_header+prhdr_status(pc),d0
 and.b   #DSRS_RPC,d0                      ; complete packet received ?
 bne.s   .goodpacket

 bug     <"cnet: incomplete packet received",10>
 addq.l  #1,dd_devicestats+s2ds_baddata(a5) ; count bad packets received
 bra.s   .next

.goodpacket:
 addq.l  #1,dd_devicestats+s2ds_packetsreceived(a5) ; count packets received

 move.l  a5,a0
 move.w  d6,d0
;lea     rx_header(pc),a1
 bsr.s   readpacket                        ; feed packet into waiting ioreqs

.next:
 moveq   #0,d0
 move.b  rx_header+prhdr_nxtpg(pc),d0      ; get next page number
 move.w  d0,d7
 subq.w  #1,d0                             ; nxtpage-1 = new boundary
 cmp.w   #RBUF/256,d0
 bge.s   .boundary                         ; wrap if before 1st page
 moveq   #(RBUFEND/256)-1,d0
.boundary:
 delay
 move.b  d0,nic_bnry                       ; set new boundary
 bra     .nextpage                         ; back for more

.done:
 delay
 move.b  #DSCM_NODMA|DSCM_START,nic_cr     ; select bank 0
 MYDISABLE
 delay
 move.b  nic_rsr,d0                        ; read rx status
 delay
 move.b  nic_cntr0,d0
 delay
 move.b  nic_cntr1,d0                      ; read counters
 delay
 move.b  nic_cntr2,d0
 or.b    #DSIM_OVWE|DSIM_RXEE|DSIM_PRXE,dd_imr(a5)
 move.b  dd_imr(a5),nic_imr                ; allow rx interrupts
 MYENABLE

 movem.l (sp)+,d6/d7/a6
 moveq   #0,d0
 rts



;==============================================================
;             readpacket( device, [pkthdr], page )
;                           a0     [a1]     d0.w
;==============================================================
;
; get packet from network card and feed it to all different
; CMD_READ queues. Also call packet filter before doing the copy.
; 
; Note that this is a major improvement to previous implementation,
; that only kept one single list of CMD_READs. This finally fixes
; sanamon for example.
;
; Inputs:
;
;       pkthdr = packet header info extracted from nic
;
;       page = 256 byte page in nic RAM that holds packet
;
;
readpacket:
 movem.l d2-d7/a2-a6,-(sp)

 moveq   #0,d6
 move.w  d0,d7                          ; d7 = page
 move.l  a0,a5                          ; a5 = device
 move.b  rx_header+prhdr_sz1(pc),d6
 moveq   #0,d3
 lsl.w   #8,d6
 move.w  rx_header+prhdr_sizeof+ether_type(pc),d3
 move.b  rx_header+prhdr_sz0(pc),d6
 lsl.w   #8,d7                          ; address=page*256
 sub.w   #prhdr_sizeof+ether_data,d6    ; d6 = length of read
 add.w   #prhdr_sizeof+ether_data,d7    ; skip pageheader and etherheader

; dropflag is set to true if at least one protocol stack managed
; to get the packet. If this still is zero after the following loop
; then I try to feed to for pending S2_READORPHAN request, if any.

 moveq   #0,d4                          ; dropflag

 move.l  dd_bufmanlist(a5),a3           ; a3 = first bufman
 bra.s   .start

; scan each CMD_READ callers bufmanlist until we find the ioreq
; that want this type of packet or till end of list.

.nextreq:
 move.l  (a3),d0
 beq.s   .nextbufman                    ; end of this CMD_READ list ?
 cmp.l   ios2_packettype(a3),d3
 beq.s   .iwantyou                      ; does it want our packet ?
 cmp.w   #1500,d3
 bhi.s   .nextnode                      ; accept 802.3 packets
 cmp.l   #1500,ios2_packettype(a3)
 bls.s   .iwantyou
.nextnode:
 move.l  d0,a3
 bra.s   .nextreq

.iwantyou:
; a3 = ioreq
 moveq   #1,d2                          ; use packetfilter
 bsr     .gotreq
; note that we only offer it once per caller!

.nextbufman:
 move.l  d5,a3                          ; a3 = next bufman node in list
.start:
 move.l  (a3),d5
 move.l  bufman_rxqueue(a3),a3          ; get pending CMD_READ's list
 bne.s   .nextreq                       ; end of bufman list ?

; Now we've tried to give the packet to every CMD_READ caller,
; and d4 contains the number of iorequests that were succesfully
; returned. If d4 is 0 at this point no-one wanted this packet.

 tst.l   d4                             
 beq.s   .gotdropped                    ; did oneone want it?

; Okay, at least one CMD_READ caller got the packet, so we're happy
; now and can quit.

.done:
 movem.l (sp)+,d2-d7/a2-a6
 rts


.gotdropped:

; so there were no outstanding CMD_READs or the packet wasn't
; accepted by any of them. Okay, check if we have any pending
; S2_READORPHAN ioreq in list and if we have return this packet
; with it. Note that packet filter must not be used for this
; time!

 addq.l  #1,dd_devicestats+s2ds_unknowntypesreceived(a5)
 move.l  dd_orphanlist(a5),a3           ; get first S2_READORPHAN available
 tst.l   (a3)
 beq.s   .dotrack                       ; got an orphan ioreq ?

; d4 = whatever, not important here
; a3 = ioreq
 moveq   #0,d2                          ; indicate S2_READORPHAN call
 bsr.s   .gotreq

.dotrack:

; Update dropped packet statistics, if requested.

 move.l  dd_tracklist(a5),a0
.getstat:
 move.l  (a0),d0                        ; tracking packettypes ?
 beq.s   .donetrk
 cmp.l   ss2_ptype(a0),d3               ; get tracking entry for this type
 beq.s   .dropped
 move.l  d0,a0                          ; next tracking entry
 bra.s   .getstat

.dropped:
 addq.l  #1,ss2_stats+s2pts_packetsdropped(a0) ; another packet dropped
.donetrk:

 bug     <"cnet: packet type %ld dropped",10>,d3

 moveq   #S2EVENT_ERROR|S2EVENT_RX,d0
 bsr     DoEvent
 bra     .done



.gotreq:

; Here we have a ioreq in a3 that has requested packets of this type.
; 1st we extract the packet from the card unless we have already done
; so. d2 = 0 if this ioreq is S2_READORPHAN call (packetfilter must
; not be used). if d2 = 1 then this is a regular CMD_READ and
; packetfilter must be called.

 tst.w   d7
 beq.s   .gotalready                    ; have we already read it ?

 lea     rxbuffer+ether_data(pc),a1
 move.w  d7,d0                          ; (we only get the user stuff here)
 move.w  d6,d1
;bsr     RemoteRead                     ; get packet user data from nic
 REMOTEREAD

 ifd     verbose
 bug     <"cnet: rdpkt %08lx%04lx < %08lx%04lx, ">,rxbuffer+ether_dest(pc),rxbuffer+ether_dest+2(pc),rxbuffer+ether_src(pc),rxbuffer+ether_src+2(pc)
 cmp.w   #1500,d3
 bhi.s   .type
 bug     <"(IEEE802.3)len %ld, ">,d3
 bra.s   .bytes
.type:
 bug     <"type %ld, ">,d3
.bytes:
 moveq   #ether_data,d0
 add.l   d6,d0
 bug     <"%ld bytes",10>,d0
 lea     rxbuffer+ether_data(pc),a1
 move.l  d6,d0
 bsr     showpkt
 endc


; Update the packet statistics if required.

 move.l  dd_tracklist(a5),a0
.getstats:
 move.l  (a0),d0                        ; tracking packettypes ?
 beq.s   .received
 cmp.l   ss2_ptype(a0),d3               ; find stats entry for our type
 beq.s   .gotstats
 move.l  d0,a0
 bra.s   .getstats
.gotstats:
 add.l   d6,ss2_stats+s2pts_rxbytes(a0) ; update stats
 addq.l  #1,ss2_stats+s2pts_rxpackets(a0)
.received:

 moveq   #0,d7                          ; don't try to read it from hw anymore
.gotalready



; next figure out if this ioreq asked the raw or non-raw data,
; and skip ether_data bytes from the buffer start if it asked
; raw.

 move.l  d6,ios2_datalength(a3)         ; user data size
 lea     rxbuffer+ether_data(pc),a6     ; a6=user data
 tst.b   io_flags(a3)                   ; (test SANA2IOB_RAW)
 bpl.s   .notraw                        ; is etherheader wanted ?
 add.w   #ether_data+4,ios2_datalength+2(a3) ; raw data size
 lea     rxbuffer(pc),a6                ; a6=raw data
.notraw:

; now build up the ios2 structure enough so we can call the packet
; filter.

 lea     ios2_dstaddr(a3),a0
 lea     rxbuffer+ether_dest(pc),a1     ; or: rx_header+prhdr_sizeof+ether_dest
 move.l  (a1)+,(a0)+                    ; extract the dst address
 move.w  (a1)+,(a0)+
 lea     ios2_packettype(a3),a0
 move.l  d3,(a0)+                       ; packet type
 move.l  (a1)+,(a0)+                    ; extract the src address
 move.w  (a1)+,(a0)+

; call the packet filter, if available. real men don't use filters. :-)
; the truth is out there

 tst.l   d2
 move.l  ios2_buffermanagement(a3),a0
 beq.s   .marlboro                      ; S2_READORPHAN must not use filter!
 move.l  bufman_packetfilter(a0),d0
 beq.s   .marlboro                      ; did the caller provide packet filter ?
 move.l  d0,a0                          ; a0=hook
 move.l  a6,a1                          ; a1=data
 move.l  8(a0),a4                       ; h_entry - get hook assembler entry point
 move.l  a3,a2                          ; a2=ios2
 jsr     (a4)
 tst.l   d0
 beq     .ignore
.marlboro: ;-!

; ok. the packet didn't get filtered, set the BCAST and MCAST
; flags according to dstaddr.

 btst    #0,ios2_dstaddr(a3)            ; address = Multicast ?
 beq.s   .nmcast
 cmp.l   #$ffffffff,ios2_dstaddr(a3)
 bne.s   .nbcast                        ; address = Broadcast ?
 cmp.w   #$ffff,ios2_dstaddr+4(a3)
 bne.s   .nbcast
 bset    #SANA2IOB_BCAST,io_flags(a3)   ; set BROADCAST flag in ioreq
 bra.s   .nmcast
.nbcast:
 bset    #SANA2IOB_MCAST,io_flags(a3)   ; set MULTICAST flag in ioreq
.nmcast:

; finally copy the packet data!

 move.l  ios2_data(a3),a0
 move.l  ios2_buffermanagement(a3),a2
 move.l  a6,a1
 move.l  bufman_copytobuf(a2),a2
 move.l  ios2_datalength(a3),d0
 jsr     (a2)                           ; call copytobuf
 tst.l   d0
 beq.s   .copytobuferr

 addq.l  #1,d4                          ; mark that it wasn't dropped!

 ifd     verbose
 bug     <"cnet: copytobuffed packet ior %08lx %08lx%04lx < %08lx%04lx, type %ld, %ld bytes, ">,a3,ios2_dstaddr(a3),ios2_dstaddr+2(a3),ios2_srcaddr(a3),ios2_srcaddr+2(a3),ios2_packettype(a3),ios2_datalength(a3)
 tst.b   io_flags(a3)
 bpl.s   .v1
 bug     <"RAW ">
.v1:
 btst    #SANA2IOB_BCAST,io_flags(a3)
 beq.s   .v2
 bug     <"BCAST ">
.v2:
 btst    #SANA2IOB_MCAST,io_flags(a3)
 beq.s   .v3
 bug     <"MCAST ">
.v3:
 bug     <10>
 endc

.termit:
 move.l  a3,a1
 REMOVE                                 ; pull it off the list
 move.l  a3,a1
 bra     TermIO                         ; IO finished


.ignore:
 bug     <"cnet: packet type %ld for %08lx ior filtered",10>,d3,a3

; the packet got rejected by packet filter!
 rts


.copytobuferr:
; oh fuck.. copytobuf failed!
 bug     <10,"cnet: copytobuf failed!",10>

 moveq   #S2EVENT_RX|S2EVENT_BUFF,d0
 bsr     DoEvent
 clr.l   ios2_datalength(a3)
 moveq   #S2WERR_BUFF_ERROR,d0
 move.b  #S2ERR_NO_RESOURCES,io_error(a3)
 move.l  d0,ios2_wireerror(a3)
 bra.s   .termit




;======================================================================
;                  init_card(device)
;                              a1
;======================================================================
;
;                    Initialise PCMCIA card
;
init_card:
 bug     <"cnet: init_card",10>

 movem.l d3-d6/a3/a6,-(sp)
 move.l  a1,a3                        ; a3 = device
 btst    #DDB_OWNED,dd_flags(a3)      ; do we own the card already?
 bne     .owned

 move.l  dd_cardres(a3),d0
 bne.s   .gotres
 move.l  exec_base(pc),a6
 lea     cardname(pc),a1
 jsr     _LVOOpenResource(a6)         ; open credit card resource
 move.l  d0,dd_cardres(a3)
 beq     .error
.gotres:

 lea     dd_cardhandle(a3),a1

 lea     dd_cardremoved(a3),a0        ; init interrupt for card removed
 move.l  a3,is_data(a0)
 move.l  #card_removed_code,is_code(a0)
 move.l  a0,cah_CardRemoved(a1)

 lea     dd_cardinserted(a3),a0       ; init interrupt for card inserted
 move.l  a3,is_data(a0)
 move.l  #card_inserted_code,is_code(a0)
 move.l  a0,cah_CardInserted(a1)

 lea     dd_cardstatus(a3),a0         ; init interrupt for status change
 move.l  a3,is_data(a0)

 move.l  dd_cardres(a3),a6
 cmp.w   #39,LIB_VERSION(a6)          ; got CARDB_POSTSTATUS (v39+) ?
 blo.s   .oldie

 move.l  #status_int_code_v39,is_code(a0)
 move.b  #CARDF_IFAVAILABLE|CARDF_POSTSTATUS,cah_cardflags(a1)
 bra.s   .was_goldie

.oldie:
 move.l  #status_int_code_v37,is_code(a0)
 move.b  #CARDF_IFAVAILABLE,cah_cardflags(a1)
.was_goldie:
 move.l  a0,cah_CardStatus(a1)

 lea     devicename(pc),a0
 move.l  a0,ln_name(a1)
 move.b  #20,ln_pri(a1)               ; high priority for I/O card

 jsr     _LVOOwnCard(a6)              ; own card (sets up interrupt vectors)
 tst.l   d0
 bne     .error

 or.b    #DDF_CARDIN|DDF_OWNED,dd_flags(a3)     ; card is inserted & owned by us

.owned:
 lea     dd_cardhandle(a3),a1
 lea     tuple(pc),a0
 moveq   #CISTPL_FUNC_ID,d1
 moveq   #40,d0
 move.l  dd_cardres(a3),a6
 jsr     _LVOCopyTuple(a6)            ; read function tuple
 tst.l   d0
 beq     .error

 lea     tuple(pc),a0
 move.b  2(a0),d0
 cmp.b   #CISTPL_FUNCID_NETWORK,d0    ; must be a network card!
 bne     .error

 lea     dd_cardhandle(a3),a1
 lea     tuple(pc),a0
 moveq   #CISTPL_CONF_MAP,d1
 moveq   #40,d0
 move.l  dd_cardres(a3),a6
 jsr     _LVOCopyTuple(a6)            ; read config map tuple
 tst.l   d0
 beq     .error

 lea     tuple(pc),a0
 move.b  2(a0),d0
 and.w   #$03,d0                      ; (number of address bytes)-1
 moveq   #0,d5

.getaddr:
 lsl.l   #8,d5
 move.b  4(a0,d0.w),d5                ; extract config register address
 subq.w  #1,d0
 bpl.s   .getaddr

 and.l   #$0001FFFF,d5                ; limit to attribute memory range
 lea     dd_cardhandle(a3),a1
 moveq   #CISTPL_CONFIG,d1
 moveq   #40,d0
 jsr     _LVOCopyTuple(a6)            ; read 1st config tuple
 tst.l   d0
 beq.s   .error

 lea     dd_cardhandle(a3),a1
 move.l  #CARDF_DISABLE_WP|CARDF_ENABLE_DIGAUDIO,d1
 jsr     _LVOCardMiscControl(a6)      ; enable card I/O functions
 lea     tuple(pc),a0
 move.b  2(a0),d0                     ; extract configuration ID value

.setconfig:
 and.b   #$3f,d0
 lea     AttrMem,a0
 add.l   d5,a0
 move.b  d0,(a0)                      ; put ID into card config register

.ok:
 bug     <"cnet: init_card: OK",10>
 moveq   #0,d0                        ; card is active, return OK

.done:
 movem.l (sp)+,d3-d6/a3/a6
 rts

.error:
 bug     <"cnet: init_card: failed!",10>
 moveq   #S2EVENT_ERROR|S2EVENT_HARDWARE,d0
 bsr     DoEvent
 moveq   #-1,d0                       ; card not available, return error
 bra.s   .done



;=================================================================
;              initialise standard list
;=================================================================
;
;   newlist(list)
;            a0

_newlist:
 NEWLIST a0
 rts


;=================================================================
;              initialise device data structures
;=================================================================
;
;   init_device(device)
;                 a1
;

init_device:

 movem.l a3/a6,-(sp)
 move.l  a1,a3
 bset    #DDB_DEVINIT,dd_flags(a3)     ; already initialised ?
 bne     .done

 move.l  exec_base(pc),a6

 lea     dd_orphanlist(a3),a0          ; New MinList for orphan queue
 bsr     _newlist
 lea     dd_writelist(a3),a0           ; New MinList for write queue
 bsr     _newlist
 lea     dd_eventlist(a3),a0           ; New MinList for event queue
 bsr     _newlist
 lea     dd_bufmanlist(a3),a0          ; New MinList for bufman vectors
 bsr     _newlist
 lea     dd_tracklist(a3),a0           ; New MinList for tracking
 bsr     _newlist
 lea     dd_multicasts(a3),a0          ; New MinList for multicast filters
 bsr     _newlist

 lea     dd_multicastssema(a3),a0
 jsr     _LVOInitSemaphore(a6)

 lea     dd_rxint+ln_type(a3),a1
 lea     rxintname(pc),a0
 move.w  #(NT_INTERRUPT<<8)|16,(a1)+   ; priority 16
 move.l  a0,(a1)+                      ; set up rx swi
 lea     rxintcode(pc),a0
 move.l  a3,(a1)+
 move.l  a0,(a1)+

 lea     dd_txint+ln_type(a3),a1
 lea     txintname(pc),a0
 move.w  #(NT_INTERRUPT<<8)|0,(a1)+    ; priority 0
 move.l  a0,(a1)+                      ; set up tx swi
 lea     txintcode(pc),a0
 move.l  a3,(a1)+
 move.l  a0,(a1)+

.done:
 movem.l (sp)+,a3/a6
 rts



;============================================================
;             PCMCIA status change interrupt
;============================================================
;
;   Occurs whenever a PCMCIA status line changes
;
;   eg. when the network card activates it's interrupt line
;
;
;  entry:   d0 = status change(s)
;           a1 = device
;
;  exit:    d0 must be preserved!
;

STATINT MACRO

 movem.l d2-d6/a2-a4,-(sp)
 move.l  a1,a4                      ; a4 = device
 btst    #DDB_NICUP,dd_flags(a4)    ; is nic working ?
 beq     .done

 move.l  d0,d6                      ; d6 = status changes
 move.l  exec_base(pc),a6

 move.b  nic_cr,d5                  ; save old command
 delay
 move.b  #0,nic_imr                 ; prevent nic interrupts
 bra     .checkint

; interrupt service loop    (d3 = interrupt status)

.intloop:
 btst    #DSIB_ROVRN,d3             ; Overwrite warning enable ?
 beq     .no_overflow
; receiver ring buffer overflowed (eek!)
 addq.l  #1,dd_overflows(a4)
 delay
 move.b  #0,nic_rbcr0
 delay
 move.b  #0,nic_rbcr1               ; reset remote byte count
 delay
 move.b  #DSTC_LB0,nic_tcr
 delay                              ; monitor mode
 move.b  #DSRC_MON,nic_rcr
 delay
 move.b  #DSCM_NODMA|DSCM_START,nic_cr ; try to restart controller
 delay
 move.b  #DSRC_AB,nic_rcr
 delay                              ; normal rx mode
 move.b  #0,nic_tcr
.no_overflow:

 btst    #DSIB_RXE,d3               ; Receive error enable ?
 beq.s   .norxerr
; bug     <"cnet: rx error",10>
 addq.l  #1,dd_devicestats+s2ds_baddata(a4) ; another bad rx packet ?
 delay
 move.b  nic_rsr,d0                 ; read rx status
 delay
 move.b  nic_cntr0,d0
 delay
 move.b  nic_cntr1,d0               ; read counters
 delay
 move.b  nic_cntr2,d0
 bra.s   .rx
.norxerr:

 btst    #DSIB_RX,d3                ; Packet received enable ?
 beq.s   .no_rx

; new packet(s) arrived in receive ring buffer
.rx:
 and.b   #~(DSIM_OVWE|DSIM_RXEE|DSIM_PRXE),dd_imr(a4) ; ignore rx ints
 lea     dd_rxint(a4),a1
 jsr     _LVOCause(a6)              ; to copy packet(s) into waiting ioreq(s)
.no_rx:

 btst    #DSIB_TXE,d3               ; Transmit error enable ?
 bne.s   .tx
 btst    #DSIB_TX,d3                ; Packet transmitted enable ?
 beq     .no_tx

.tx:
; a packet has just been transmitted
 moveq   #0,d0
 delay
 move.b  nic_ncr,d0                 ; read collision count
 bclr    #DDB_TX,dd_flags(a4)       ; buffer now free
 add.l   d0,dd_retries(a4)          ; increment retry count
 addq.l  #1,dd_devicestats+s2ds_packetssent(a4) ; count packets sent
 lea     dd_txint(a4),a1
 jsr     _LVOCause(a6)              ; to transmit next packet
.no_tx:

 btst    #DSIB_CTRS,d3              ; counter overflow ?
 beq.s   .nocounter

; counter overflow
 delay
 move.b  nic_cntr0,d0
 delay
 move.b  nic_cntr1,d0               ; read counters
 delay
 move.b  nic_cntr2,d0
.nocounter:


.checkint:
 delay
 move.b  nic_isr,d3                 ; d3 = nic interrupt status
 delay
 move.b  d3,nic_isr                 ; clear current interrupt bit(s)
 and.b   dd_imr(a4),d3
 bne     .intloop                   ; any valid interrupts ?

 delay
 move.b  d5,nic_cr                  ; restore old command

 ENDM



 CNOP    0,4
status_int_code_v37:

 STATINT

 eor.b   #$2c,d6
 or.b    #$c0,d6
 move.b  d6,$da9000                 ; clear PCMCIA status change bits
 delay
 move.b  dd_imr(a4),nic_imr         ; enable nic interrupts
 moveq   #0,d0                      ; don't clear status bits coz we did

.done:
 movem.l (sp)+,d2-d6/a2-a4
 rts


 CNOP    0,4
status_int_code_v39:

 STATINT

 delay
 move.b  dd_imr(a4),nic_imr         ; enable nic interrupts
 move.l  d6,d0                      ; return original d0

.done:
 movem.l (sp)+,d2-d6/a2-a4
 rts



;============================================================
;             PCMCIA Card Inserted interrupt
;============================================================
;
;   Occurs whenever a PCMCIA card is plugged in
;
; NOTE: card.resource has given us ownership of the card
;
;  entry: a1 = device
;
;  exit:  d0,d1,a0,a1,a5,a6 = scratch
;
card_inserted_code:
 bug     <"cnet: PCMCIA card inserted",10>

 move.l  a1,a5

 tst.w   lib_opencnt(a5)               ; is someone interested about cards ?
 beq.s   .done

 or.b    #DDF_CARDIN|DDF_OWNED,dd_flags(a5) ; card is inserted & we own the card again

 bsr     init_card
 tst.l   d0                            ; attempt to init pcmcia card
 bne.s   .releasecard

 move.l  a5,a1
 bsr     init_nic                      ; attempt to start controller
 tst.l   d0
 beq.s   .good

.releasecard:
 lea     dd_cardhandle(a5),a1
 move.l  dd_cardres(a5),a6
 moveq   #0,d0
 jsr     _LVOReleaseCard(a6)           ; release foreign or bad card
 bclr    #DDB_OWNED,dd_flags(a5)
 bra.s   .done

.good:
 btst    #DDB_OFFLINE,dd_flags(a5)     ; has device been put offline ?
 bne.s   .done
 bset    #DDB_ONLINE,dd_flags(a5)      ; device is now online
 moveq   #S2EVENT_ONLINE,d0
 bra     DoEvent                       ; return ONLINE event

.done:
 rts



;============================================================
;             PCMCIA Card Removed interrupt
;============================================================
;
;   Occurs whenever a PCMCIA card is unplugged
;
;  entry: a1 = device
;
;  exit:  d0,d1,a0,a1,a5,a6 = scratch
;
card_removed_code:
 bug     <"cnet: PCMCIA card removed",10>

 move.l  a1,a5                          ; a5 = device
;and.b   #~(DDF_OWNED|DDF_CARDIN|DDF_ONLINE|DDF_NICUP|DDF_CONFIGURED),dd_flags(a5)
 and.b   #~(DDF_OWNED|DDF_CARDIN|DDF_ONLINE|DDF_NICUP),dd_flags(a5)
 lea     dd_cardhandle(a5),a1
 move.l  dd_cardres(a5),a6
 moveq   #0,d0
 jsr     _LVOReleaseCard(a6)            ; release card

 moveq   #S2EVENT_OFFLINE,d0
 bra     DoEvent                        ; return OFFLINE event



; debugging stuff...

 ifd     verbose
showpkt:
 movem.l a0-a1/d0-d2,-(sp)
 bug     <10>
 moveq   #0,d1

.rows:
 moveq   #15,d2
 lea     .string(pc),a0

.bytes:
 move.b  (a1)+,d1
 cmp.b   #' ',d1
 blo.s   .dot
 cmp.b   #'z',d1
 bhs.s   .dot
 move.b  d1,(a0)+
 bra.s   .ends
.dot:
 move.b  #'.',(a0)+
.ends:
 move.b  #0,(a0)
 bug     <"%02lx ">,d1
 subq.w  #1,d0
 beq.s   .end
 dbf     d2,.bytes
 bra.s   .shows

.endspc:
 bug     <"   ">
.end:
 dbf     d2,.endspc

.shows:
 bug     <"  %s",10>,#.string
 tst.w   d0
 bne.s   .rows

 bug     <10,10>
 movem.l (sp)+,a0-a1/d0-d2
 rts

.string:
 ds.b    16
 dc.b    0
 even
 endc



;============================================================
;        MemPool routines with pre-V39 compatiblity
;============================================================
;
; standard code, snipped from somewhere... ;-)
;

createpool	cmp.w	#39,(LIB_VERSION,a6)
	bcs.b	.createpool
	jmp	(_LVOCreatePool,a6)

.createpool	sub.l	a0,a0
	cmp.l	d2,d1
	bcs.b	.err
	move.l	d0,-(sp)
	moveq	#MEM_BLOCKMASK,d0
	add.l	d0,d1
	not.b	d0
	and.b	d0,d1
	move.l	d1,-(sp)
	moveq	#0,d1
	moveq	#24,d0
	jsr	(_LVOAllocMem,a6)
	move.l	(sp)+,d1
	move.l	(sp)+,a0
	tst.l	d0
	beq.b	.nomem
	exg	d0,a0
	move.l	a0,(8,a0)
	addq.l	#4,a0
	clr.l	(a0)
	move.l	a0,-(a0)
	lea	(12,a0),a1
	move.l	d0,(a1)+
	move.l	d1,(a1)+
	move.l	d2,(a1)+
.err	move.l	a0,d0
.nomem	rts


deletepool	cmp.w	#39,(LIB_VERSION,a6)
	bcs.b	.deletepool
	jmp	(_LVODeletePool,a6)

.deletepool	move.l	a0,d0
	beq.b	.exit

	movem.l	d2/a2,-(sp)
	move.l	(a0),a2
.loop	move.l	(a2),d2
	beq.b	.done
	move.l	a2,a1
	move.l	d2,a2
	move.l	-(a1),d0
	jsr	(_LVOFreeMem,a6)
	bra.b	.loop

.exit	rts

.done	move.l	a2,a1
	subq.l	#4,a1
	movem.l	(sp)+,d2/a2
	moveq	#24,d0
	jmp	(_LVOFreeMem,a6)


;  IN: d0 = memSize
; OUT: d0 = memory, or NULL
	CNOP	0,4
alloc:	lea	poolsema(pc),a0
	jsr	_LVOObtainSemaphore(a6)

	move.l	poolptr(pc),a0
	bsr.s	allocpooled

	lea	poolsema(pc),a0
	jmp	_LVOReleaseSemaphore(a6)


	CNOP	0,4
allocpooled	cmp.w	#39,(LIB_VERSION,a6)
	bcs.b	.allocpooled
	jmp	(_LVOAllocPooled,a6)

.allocpooled	move.l	d0,d1
	beq.b	.exit
	move.l	a0,d0
	beq.b	.exit

	movem.l	d2/d3/a2/a3,-(sp)
	move.l	d1,d2
	move.l	a0,a2
	cmp.l	($0014,a2),d2
	bcc.b	.nofree
.allocouter	move.l	(a2),d3
.allocinner	move.l	d3,a3
	move.l	(a3),d3
	beq.b	.alloc
	move.l	a3,a0
	tst.l	(8,a0)
	beq.b	.alloc
	move.l	d2,d0
	jsr	(_LVOAllocate,a6)
	tst.l	d0
	beq.b	.allocinner
	move.l	d0,a0
	move.l	(12,a2),d0
	btst	#MEMB_CLEAR,d0
	beq.b	.noclr
	move.l	a0,a1
	move.l	d2,d1
	addq.l	#MEM_BLOCKMASK,d1
	lsr.l	#3,d1
	subq.l	#1,d1
	move.w	d1,d0
	swap	d1
.clr	clr.l	(a1)+
	clr.l	(a1)+
	dbf	d0,.clr
	dbf	d1,.clr
.noclr	move.l	a0,d0
.nomem	movem.l	(sp)+,d2/d3/a2/a3
.exit	rts

.nofree	moveq	#8+4,d0
	add.l	d2,d0
	move.l	(12,a2),d1
	bsr	.allocpuddle
	tst.l	d0
	beq.b	.nomem

	move.l	d0,a1
	move.l	a2,a0
	addq.l	#4,a0
	move.l	(4,a0),d0
	move.l	a1,(4,a0)
	exg	d0,a0
	movem.l	d0/a0,(a1)
	move.l	a1,(a0)
	addq.l	#8,a1
	clr.l	(a1)+
	move.l	a1,d0
	bra.b	.nomem

.alloc	move.l	(12,a2),d1
	move.l	($0010,a2),d0
	add.l	#$00000024,d0
	bsr.b	.allocpuddle
	tst.l	d0
	beq.b	.nomem

	move.l	d0,a3
	move.l	d0,a1
	move.l	a2,a0
	move.l	(a0),d0
	move.l	a1,(a0)
	movem.l	d0/a0,(a1)
	move.l	d0,a0
	move.l	a1,(LN_PRED,a0)
	lea	(.poolname,pc),a0
	move.w	#(10<<8)|10,(LN_TYPE,a3)  ; LN_TYPE=NT_MEMORY LN_PRI=10
	move.l	a0,(LN_NAME,a3)
	move.l	(12+2,a2),(MH_ATTRIBUTES,a3)
	lea	(MH_SIZE+4,a3),a0
	move.l	#~MEM_BLOCKMASK,d1
	move.l	a0,d0
	and.l	d0,d1
	move.l	d1,a0
	move.l	($0010,a2),d0
	move.l	a0,(MH_FIRST,a3)
	move.l	a0,(MH_LOWER,a3)
	move.l	d0,(MH_FREE,a3)
	clr.l	(a0)
	move.l	d0,(4,a0)
	add.l	d0,a0
	move.l	a0,(MH_UPPER,a3)
	bra.w	.allocouter

.allocpuddle	addq.l	#4,d0
	move.l	d0,-(sp)
	jsr	(_LVOAllocMem,a6)
	tst.l	d0
	beq.b	.nomem2
	move.l	d0,a0
	move.l	(sp),(a0)+
	move.l	a0,d0
.nomem2	addq.l	#4,sp
	rts

.poolname:
	dc.b	'Pool',0
	CNOP	0,2


;  IN: a1 = memory
;      d0 = memSize
; OUT: -
	CNOP	0,4
free:	lea	poolsema(pc),a0
	jsr	_LVOObtainSemaphore(a6)

	move.l	poolptr(pc),a0
	bsr.s	freepooled

	lea	poolsema(pc),a0
	jmp	_LVOReleaseSemaphore(a6)


	CNOP	0,4
freepooled	cmp.w	#39,(LIB_VERSION,a6)
	bcs.b	.freepooled
	jmp	(_LVOFreePooled,a6)

.freepooled	move.l	a0,d1
	beq.b	.exit
	move.l	a1,d1
	beq.b	.exit
	cmp.l	($0014,a0),d0
	bcc.b	.big_free
	move.l	a3,-(sp)
	move.l	(a0),d1
.findmem	move.l	d1,a3
	move.l	(a3),d1
	beq.b	.nomem
	tst.l	(LN_TYPE,a3)
	beq.b	.nomem
	cmp.l	(MH_LOWER,a3),a1
	bcs.b	.findmem
	cmp.l	(MH_UPPER,a3),a1
	bcc.b	.findmem
	move.l	a3,a0
	jsr	(_LVODeallocate,a6)
	move.l	(LN_PRED,a3),a1
	move.l	(LN_PRED,a1),d1
	beq.b	.nope
	move.l	d1,a0
	move.l	a3,(a0)
	move.l	a0,(LN_PRED,a3)
	move.l	a3,(LN_PRED,a1)
	move.l	(a3),a0
	move.l	a0,(a1)
	move.l	a1,(LN_PRED,a0)
	move.l	a1,(a3)
.nope	move.l	a3,a1
	move.l	(sp)+,a3
	move.l	(MH_SIZE,a1),d0
	add.l	(MH_LOWER,a1),d0
	sub.l	(MH_UPPER,a1),d0
	beq.b	.free
.exit	rts

.big_free	subq.l	#4,a1
	subq.l	#8,a1
.free	move.l	a1,d0
	move.l	(a1)+,a0
	move.l	(a1),a1
	move.l	a0,(a1)
	move.l	a1,(4,a0)
	move.l	d0,a1
	move.l	-(a1),d0
	jmp	(_LVOFreeMem,a6)

.nomem	move.l	(sp),a3
	move.l	d7,(sp)
	move.l	#$0100000F,d7
	jsr	(_LVOAlert,a6)
	move.l	(sp)+,d7
	rts



rxintname:
 dc.b "cnet rx softint",0
txintname:
 dc.b "cnet tx softint",0

cardname:
 dc.b "card.resource",0
timerdevname:
 dc.b "timer.device",0
utilityName:
 dc.b "utility.library",0

multiname:
 dc.b "bad multicasts",0
retryname:
 dc.b "retries",0

DeviceName:
 dc.b    "cnet.device",0


 dc.b    "$VER: "
IDString:
 dc.b    "cnet.device "
 VERSTR
 ifeq    __CPU-68000
 dc.b    " 68000+"
 else
 ifge    __CPU-68020
 dc.b    " 68020+"
 endc
 endc
 ifd     turboio
 dc.b    "/turboio"
 endc
 ifd     debug
 dc.b    "/debug"
 endc
 dc.b    " by Harry ",34,"Piru",34," Sintonen",10,0


; devicequery block

 CNOP 0,2
size_supplied:
 dc.l    S2DQ_SIZE            ; bytes supplied (size of this block)
 dc.l    0                    ; this is type 0
 dc.l    0                    ; this document is level 0
 dc.w    ETHER_ADDR_SIZE*8    ; address size in bits
 dc.l    ETHERPKT_SIZE        ; maximum packet data size
 dc.l    10000000             ; line rate (10 Megabits/sec)
 dc.l    S2WIRETYPE_ETHERNET  ; what the wire is


; default station address to use if the card won't give it to us.

 CNOP 0,2
default_address:
 dc.b    $00,$00,$12,$34,$56,$78 ; replace this with your card's address!


;--------------------------------------------------------
;                      Global data
;--------------------------------------------------------

 CNOP 0,4

exec_base:     dc.l 0   ; local copy of execbase

utility_base:  dc.l 0   ; for walking taglist

me_myself:     dc.l 0   ; pointer to this device

timerdevice:   dc.l 0   ; timer device base

mcastaddr:     ds.b 8   ; multicast address filter bitfield


; ---- Timer I/O Request ----
 CNOP 0,2
timereq:
 dc.l 0           ; ln_head  }         }
 dc.l 0           ; ln_pred  }         }
 dc.b 0           ; ln_type  } mp_node }
 dc.b 0           ; ln_pri   }         } io_message
 dc.l 0           ; ln_name  }         }
 dc.l 0           ; mn_replyport       }
 dc.w 0           ; mn_length          }
 dc.l 0           ;  io_device
 dc.l 0           ;  io_unit
 dc.w 0           ;  io_command
 dc.b 0           ;  io_flags
 dc.b 0           ;  io_error
 dc.l 0           ;  tv_secs
 dc.l 0           ;  tv_micros


tuple:         ds.b 48  ; PCMCIA tuple buffer


; linked with phxlnk so no need for separate bss hunk!
; section buffers,bss

 CNOP 0,8
rx_header:
 ds.b    prhdr_sizeof
rxbuffer:
 ds.b    PKTBUF_SIZE-prhdr_sizeof      ; received packet buffer

 CNOP 0,8
txbuffer:
 ds.b    PKTBUF_SIZE      ; transmit packet buffer


; here for easy & fast access (no need to go thru device ptr)

 CNOP 0,4
poolptr:
 ds.l    1
poolsema:
 ds.b    SS_SIZE


 CNOP 0,2
Endcode:
