! APM Front Bus Intel 82586 Ethernet driver ! RWT August 1989 ! Byte sex ! The interface is wired for BYTE-FIDELITY, i.e. values which appear in ! consecutive byte locations as viewed by the 82586 do so as viewed by the ! CPU as well. Since 82586 and 68010 have different byte sex this implies ! that values which are treated as 2-byte words have to have their bytes ! transposed in software, likewise for 4-byte words (which, incidentally, ! are invariably 24-bit addresses in which only 17 bits are relevant). ! This swapping is performed by the functions SWAP2 and SWAP4, respectively. ! Manipulation of pointers to control blocks using the 82586's method of ! using offsets with respect to a fixed base are performed using the ! functions OFFSET OF ITEM and ITEM AT OFFSET, which already contain the ! necessary calls to the SWAP functions. ! Memory layout ! The board contains 128 kbytes of dual ported memory, which appears at ! 040000:05FFFF in the CPU address space. Address consistency between 82586 ! and CPU is achieved by ignoring the 82586's seven high-order address bits. ! For example, the "System Configuration Pointer" record, which the 82586 ! assumes is at FFFFF6, is actually at 05FFF6. The CA (Channel Attention) ! signal to the 82586 is generated as a side effect of the CPU reading from ! any of the 16 highest-addressed bytes of the shared memory (i.e.at 05FFFX). ! NOTE: BASE is in the middle of the memory, because of the 64k segmentation. ! All descriptors are kept in the upper half, data buffers are in the lower ! half and spill over into the upper. It is arranged that the number of ! buffers reserved for receiving is large enough to occupy more than the lower ! half of memory, as a result all the transmit buffers are accessible using ! an offset from the same base as all the descriptors. In fact, the transmit ! buffers are kept adjacent to their descriptors, which in turn are adjacent ! to their command blocks. To avoid overheads involved in buffer chaining, ! all buffers are large enough to hold the maximum size frame (1500 bytes plus ! 14 in case the mode is selected in which addresses and type are in the ! buffers instead of the descriptors). There is enough space for 64 receive and ! 20 transmit buffers including their respective descriptors. Having transmit ! buffers adjacent to their command and buffer descriptors means that no extra ! space needs to be found for data for the configure and address setup commands. ! The maximum number of multicast addresses that can be squeezed into such an ! enlarged command block (MCMAX below) is about 255, more than adequate. %option "-low-nons-nodiag-nocheck-nostack" *temp d0-d4/a0-a3 %constinteger buffersize=1514, rbuffers=64, cbuffers=20 %constinteger mcmax=(buffersize+16)//6*6 %constinteger null=16_ffff {The "offset" of NIL} %recordformat scp fm {System Configuration Pointer} - (%byte sysbus, *, {Data bus width: 1 for 8-bit bus, 0 for 16-bit bus} %integer *, iscp ad {address of ISCP (see below)} ) %recordformat iscp fm {Intermediate System Control Pointer} - (%byte busy, *, {cleared by 82586 after reading SCB ad} %half scb offset, {Address of %integer scb base {SCB (see below)} ) %recordformat scb fm {System Control Block} - (%half status, command, cbl offset, rfa offset, crc errs, aln errs, rsc errs, ovr errs) %recordformat buffer fm (%bytearray b(1:buffersize)) %recordformat buffer desc fm (%half count, next offset, %integer buffer ad, %half size) %recordformat frame desc fm (%half status, command, link offset, desc offset, %bytearray dest, source(1:6), %half type) %recordformat command fm (%half status, command, link offset, ({Dump} %half dump buffer offset %or- {TDR} %half tdr time %or- {MCA Setup} %half mc count, %bytearray mc data(1:mcmax) %or- {Configure} %byte co count, %bytearray co data(1:11) %or- {IA Setup} %bytearray ia(1:6) %or- {Transmit} %half desc offset, %bytearray dest(1:6), %half type, %half count, next offset, %integer buffer ad, %bytearray buffer(1:buffersize))) ! Only those incoming packets in which the TYPE field matches one of several ! pre-determined ranges of values are accepted, the rest are ignored. ! The interrupt handler determines whether to return an incoming frame buffer ! to the free list or to store it in one of the RANGE records below. ! The user-callable predicate ETHER FRAME RECEIVED extract a stored frame from ! these range records and makes it the current (accessible by user) frame. ! Prior to such extraction the previous "current" frame is returned to the ! free list. %recordformat range fm (%record(range fm)%name next, %half mintype,maxtype,head,tail) @16_1070 {i.e. level 4} %integer interrupt vector %constinteger base=16_50000 {in middle of memory space} @(base-16_10000) - %record(buffer fm)%array buffer(1:rbuffers), %record(buffer desc fm)%array rbd(1:rbuffers), %record(frame desc fm)%array rfd(1:rbuffers), %record(command fm)%array com(1:cbuffers), %integer old interrupt vector,intcount,sa4,sa5, %half cbl head, cbl tail, pool head, pool tail, rfa head, rfa tail, rb head, rb tail @(base+16_FFF6) %record (scp fm) scp @(base+16_FFEE) %record (iscp fm) iscp @(base+16_FFDE) %record (scb fm) scb {Status/Command constants in SCB ** pre-swapped **} %constinteger - smask = 16_00F0, cx = 16_0080, fr = 16_0040, cnr = 16_0020, rnr = 16_0010, cmask = 16_0007, cidle = 16_0000, csuspended = 16_0001, cready = 16_0002, rmask = 16_7000, ridle = 16_0000, rsuspended = 16_1000, rnoresources = 16_2000, rready = 16_4000, cstart = 16_0001, cresume = 16_0002, csuspend = 16_0003, cabort = 16_0004, rstart = 16_1000, rresume = 16_2000, rsuspend = 16_3000, rabort = 16_4000, reset = 16_8000 {Command/status bits in descriptors ** pre-swapped **} %constinteger - done = 16_80, busy = 16_40, ok = 16_20, aborted = 16_10, el = 16_80, suspend = 16_40, interrupt = 16_20, eof = 16_80 {in RBD COUNT field} {Command codes ** pre-swapped **} %constinteger - ia setup = 16_0100, configure = 16_0200, multicast = 16_0300, transmit = 16_0400, tdr = 16_0500, dump = 16_0600, diagnose = 16_0700 %integerfn swap2(%register(d0)%integer a) *rol.w #8,a; *and.l #16_ffff,a %end %integerfn swap4(%register(d0)%integer a) *rol.w #8,a; *swap a; *rol.w #8,a %end %integerfn offset of item(%register(a0)%name x) %register(d1)%integer a %result = null %if x==nil a = addr(x)-base %result = 1 %unless a&65535=a %result = swap2(a) %end %record(*)%map item at offset(%register(d0)%integer x) %result == nil %if x=null %result == record(swap2(x)+base) %end %routine issue command(%register(d0)%half x) ! Wait for SCB command word to clear, then copy X into it and waggle CA. d1 = scb_command %until d1=0 scb_command = x x = scp_sysbus %end %routine mask interrupts d0 = 16_0400; *trap #0 %end %routine unmask interrupts d0 = 0; *trap #0 %end ! The 0 is personalised according to the APM's 2MHz ether address %ownbytearray default local address(1:6) = 'F','r','e','d','-',0 %ownbytearray actual local address(1:6) ! SET MODES parameters %externalbyte ether fifolim = 8, ether savbf = 0, ether atloc = 0, ether intloop = 0, ether extloop = 0, ether prom = 0 ! Access to current transmit frame %externalbytearrayname ether transmit dest(1:6) %externalbytearrayname ether transmit data(1:buffersize) %externalhalf ether transmit type %externalhalf ether transmit size = 0 ! Access to current receive frame %externalbytearrayname ether receive dest(1:6) %externalbytearrayname ether receive source(1:6) %externalbytearrayname ether receive data(1:buffersize) %externalhalf ether receive size = 0 %externalhalf ether receive type = 0 %externalhalf ether receive status = 0 ! Statistics %externalinteger commands executed = 0, commands failed = 0, frames received = 0, frames lost = 0, frames discarded = 0, extra buffers = 0, buffer low water = rbuffers, desc low water = rbuffers, tx restarts = 0, rx restarts = 0, crc errs = 0, aln errs = 0, rsc errs = 0, ovr errs = 0 %owninteger buffercount = rbuffers, desccount = rbuffers %ownrecord(range fm)%name range list == nil %ownrecord(command fm)%name current command == nil %ownrecord(frame desc fm)%name current frame == nil %record(range fm)%map find range(%register(d0)%half type) ! Locate the range record, if any, for which TYPE is in range. %register(a0)%record(range fm)%name r == range list %cycle %result == r %if r==nil %result == r %if r_mintype<=type<=r_maxtype r == r_next %repeat %end %routine discard frame(%record(frame desc fm)%name f) ! (Called from either interrupt handler or user program) ! Return frame F (and any buffer descriptors hung off it) to the free list %record(buffer desc fm)%name b,lb %record(frame desc fm)%name lf %half off %returnif f==nil b == item at offset(f_desc offset) %unless b==nil %start off = offset of item(b) %cycle b_size = swap2(buffersize)!el lb == item at offset(rb tail) %if lb==nil %start rb head = off %else lb_nextoffset = off lb_size = swap2(buffersize) %finish rb tail = off buffercount = buffercount+1 %exitif b_count&eof#0 b_count = 0 off = b_nextoffset; b == item at offset(off) %repeat b_count = 0 b_nextoffset = null %finish off = offset of item(f) f_desc offset = null f_command = el f_status = 0 lf == item at offset(rfa tail) %if lf==nil %start rfa head = off %else lf_link offset = off lf_command = 0 %finish rfa tail = off %if scb_status&rmask=rnoresources - %and rbhead#rbtail %and rfahead#rfatail %start f == item at offset(rfa head) f == item at offset(f_link offset) %while f##nil %and f_status#0 %unless f==nil %start rx restarts = rx restarts+1 f_desc offset = rbhead scb_rfaoffset = offset of item(f); issue command(rstart) %finish %finish desccount = desccount+1 %end %routine store received frames ! (Called only from interrupt handler) Deal with incoming frames, either ! store them in the appropriate range records or discard them. %record(frame desc fm)%name f %record(buffer desc fm)%name b %record(range fm)%name r %half off %cycle f == item at offset(rfa head) %exitif f==nil %exitif f_status&done=0 frames received = frames received+1 b == item at offset(f_desc offset) %unless b==nil %start %cycle rb head = b_next offset rb tail = null %andexitif rb head = null buffercount = buffercount-1 %exitif b_count&eof#0 extrabuffers = extrabuffers+1 b == item at offset(rb head) %repeat bufferlowwater = buffercount %if buffercount=mcmax currentcommand_mcdata(currentcommand_mccount) = a(i) %repeat %finish %end %externalroutine ether local address(%bytearrayname a(1:6)) ! Change hats, i.e. specify a new local DTE address. acquire command buffer currentcommand_command = ia setup currentcommand_ia = a submit command actual local address = a %end %externalroutine ether set modes ! Set non-standard modes as specified in the global parameter variables ! ETHERFIFOLIM, ETHERATLOC, ETHERSAVBF, ETHERINTLOOP, ETHEREXTLOOP, ETHERPROM. acquire command buffer currentcommand_command = configure ether fifolim = ether fifolim&15 ether atloc = 8 %unless ether atloc=0 ether savbf = 128 %unless ether savbf=0 ether intloop = 64 %unless ether intloop=0 ether extloop = 128 %unless ether extloop=0 ether prom = 1 %unless ether prom=0 currentcommand_cocount = 11 currentcommand_codata(1) = ether fifolim currentcommand_codata(2) = ether savbf currentcommand_codata(3) = ether extloop ! ether intloop ! ether atloc ! 16_26 currentcommand_codata(4) = 0 currentcommand_codata(5) = 96 currentcommand_codata(6) = 0 currentcommand_codata(7) = 16_f2 currentcommand_codata(8) = ether prom currentcommand_codata(9) = 0 currentcommand_codata(10) = 64 submit command %end %externalroutine ether startup(%bytearrayname local address(1:6)) ! Initialise everything as from after hardware reset. %label interrupt handler %owninteger zero=0 %integer i %record(command fm)%name c %record(frame desc fm)%name f %record(buffer desc fm)%name b @(base-16_10000) %integerarray mem(1:32768) mem(i) = d7 %for i = 1,1,32768; ! Set dual ported memory to "unassigned" pool head = null; pool tail = null cbl head = null; cbl tail = null rfa head = null; rfa tail = null rb head = null; rb tail = null intcount = 0 old interrupt vector = interrupt vector sa4 = a4; sa5 = a5 interrupt vector = addr(interrupt handler) %for i = 1,1,rbuffers %cycle f == rfd(i) f_status = 0 f_desc offset = null b == rbd(i) b_count = 0 b_buffer ad = swap4(addr(buffer(i))) %if i=rbuffers %start f_link offset = null f_command = el b_next offset = null b_size = swap2(buffersize)!el rfa tail = offset of item(f) rb tail = offset of item(b) %else %if i=1 %start rfa head = offset of item(f) f_desc offset = offset of item(b) %finish f_link offset = offset of item(f[1]) f_command = 0 b_next offset = offset of item(b[1]) b_size = swap2(buffersize) %finish %repeat %for i = 1,1,cbuffers %cycle c == com(i) %if i=cbuffers %start c_link offset = null pooltail = offset of item(c) %else %if i=1 %start poolhead = offset of item(c) %finish c_link offset = offset of item(c[1]) %finish %repeat scb = 0 iscp_scbbase = swap4(base) iscp_scboffset = offset of item(scb) iscp_busy = 1 scp_iscpad = swap4(addr(iscp)) scp_sysbus = zero issuecommand(0) default local address(6) = byte(16_3fa8) {2 MHz ether address} local address == default local address %if local address==nil actual local address = local address ether local address(local address) ether set modes mask interrupts scb_rfa offset = rfa head; issuecommand(rstart) unmask interrupts ether receive dest == actual local address %return interrupt handler: %register(d3)%integer status *movem.l d0-d4/a0-a5,-(sp) a4 = sa4; a5 = sa5 status = scb_status&smask %if status#0 %start intcount = intcount+1 issue command(status) recycle executed command descriptors store received frames crc errs = swap2(scb_crc errs) aln errs = swap2(scb_aln errs) rsc errs = swap2(scb_rsc errs) ovr errs = swap2(scb_ovr errs) %finish *movem.l (sp)+,d0-d4/a0-a5 *move.l old interrupt vector,-(sp) *rts %end %externalroutine ether shutdown ! Similar to 82586 hardware reset. ! Also expunge range list and remove interrupt handler. %record(range fm)%name r issuecommand(reset) %cycle; %repeatuntil scb_command=0 interrupt vector = old interrupt vector %cycle r == range list; %exitif r==nil range list == r_next; dispose(r) %repeat %end %externalroutine ether accept type range(%half mintype,maxtype) ! Add a type range to the list of types to be accepted by the ! receiver interrupt handler. %record(rangefm)%name r r == new(r) r_mintype = mintype; r_maxtype = maxtype; r_head = null; r_tail = null r_next == range list; range list == r %end %externalroutine ether ignore type range(%half mintype,maxtype) ! Remove a range list previously added. Both min and max must match. ! Then throw away any pending frames stored there. %record(rangefm)%name prev==nil, r==range list %while r##nil %cycle %if r_mintype=mintype %and r_maxtype=maxtype %start %if prev==nil %then range list == r_next %else prev_next == r_next find frame(r) %until currentframe==nil dispose(r); %exit %finish prev == r; r == r_next %repeat %end %externalpredicate ether frame received(%half type) ! The first range record found for which TYPE is in range is examined. ! If no frame is stored there, return FALSE. Otherwise remove the first ! frame, make it the current frame, and return TRUE. Note that the type ! of that frame will not necessarily be the same as the type parameter ! specified here, the parameter is used only to identify a range. find frame(find range(type)) %falseif currentframe==nil %true %end