! New 2MHz-ether handler for "interim" Mouse, GDMR, Dec 1987.... ! This version has fewer autonomous processes ! This handler runs the station in "raw" mode. It speaks the "standard" ! protocol (type 01/81), providing a port-like interface, and in addition ! allows the use of other protocol-types (for IP, ARP and the like). ! Note that we have two interrupt handlers: one for the station itself and ! one for the clock (IPL 4 and 6) -- this allows us to deal with user timeouts ! and old-protocol ACK-timeouts. %externalstring(47) copyright %alias "GDMR_(C)_2MEG" = %c "Copyright (C) 1987 George D.M. Ross" %constinteger ether packet size = 768; ! Includes header %constinteger RX buffers = 64; ! 2^n %constinteger reTX lives = 6 %constinteger old protocol slots = 48 %constinteger first station = 16_10 %constinteger last station = 16_7F %constinteger last registered = 16 %constinteger inbound handler size = 16384 %constinteger inbound handler priority = 7 %constinteger old timeout handler size = 16384 %constinteger old timeout handler priority = 6 %option "-nonstandard-low-nocheck-nodiag-noline" %include "Moose:Mouse.Inc" %include "GDMR_E:2Meg.Inc" %include "GDMR_H:Lights.Inc" %constinteger lights type = 16_80 %constinteger lights sending = 16_40 %constinteger lights outbound = 16_20 %constinteger lights old outbound = 16_10 %constinteger lights old reTX = 16_04 {16_08 %constinteger lights inbound = 16_20 %constinteger lights old inbound = 16_10 %constinteger lights clear = 16_F8 !! %include "INet:Dump.Inc" %externalroutinespec FS insert(%string(31) what, %integer value) %systemintegerfnspec global heap get(%integer size) %systemintegerfnspec free store %externalroutinespec phex(%integer i) %externalroutinespec phex2(%integer i) %ownrecord(semaphore fm) disaster = 0 ! Clock definitions and interrupt handler %constinteger clock priority = 6 %owninteger timestamp = 0 %ownrecord(semaphore fm) clock semaphore = 0 %routine start ticking %ownrecord(interrupt handler fm) handler = 0 %label clock handler, no setup interrupt handler(handler, addr(clock handler)) add interrupt handler(handler, clock priority) !! printstring("Ether module's clock handler inserted"); newline %return clock handler: *move.l timestamp, D0 { Every clock interrupt (or, at least, every *addq.l #1, D0 { level-6 interrupt) we bump the decisecond *move.l D0, timestamp { timestamp. If it is even (every 1/5 second) *btst.l #0, D0 { we signal the timeout process's semaphore: *bne no { (not this time round, ->) int signal semaphore(clock semaphore) no: return from interrupt *move.l D0, D0; ! HMD-proof it %end ! Ether station definitions @16_7FFFC %readonly %volatile %byte status @16_7FFFC %writeonly %byte command @16_7FFFD %writeonly %byte data @16_7FFFF %writeonly %byte control %constinteger DTX = 16_30 %constinteger RDY = 16_10 %constinteger STX = 16_20 %constinteger ETX = 16_0B %constinteger ACK = 16_C0 %constinteger ESA = 16_05 %constinteger SSA = 16_07 %constinteger RAW = 16_02 %constinteger int bit = 7, int mask = 1 << int bit %constinteger rst bit = 6, rst mask = 1 << rst bit %constinteger td bit = 3, td mask = 1 << td bit %constinteger rd bit = 2, rd mask = 1 << rd bit %constinteger rc bit = 1, rc mask = 1 << rc bit %constinteger rcd bit = 0, rcd mask = 1 << rcd bit %constinteger enable station interrupts = rc mask ! rd mask %constinteger disable station interrupts = 0 %constinteger station interrupt level = 4 %constinteger station interrupts off = 16_0400; ! For SR ! Statistics %owninteger station address = 0 %owninteger buffer low water = 16_7FFFFFFF %owninteger pended DTX = 0 %owninteger unrecognised control = 0 %owninteger inbound packets = 0 %owninteger inbound bytes = 0; ! Includes header (14 bytes) %owninteger outbound packets = 0 %owninteger outbound bytes = 0; ! Includes header (14 bytes) %owninteger dud destinations = 0 %owninteger dud types = 0 %owninteger old inbound packets = 0; ! Data packets, not ACKs %owninteger old outbound packets = 0; ! Data packets, not ACKs %owninteger old inbound no takers = 0 %owninteger old retransmits = 0 %owninteger old ACK timeouts = 0 %owninteger old user timeouts = 0 %owninteger old defines = 0 %owninteger old redefines = 0 %owninteger old undefines = 0 %owninteger old reads = 0 %owninteger old writes = 0 ! Buffer pool management %recordformat ether header fm(%byte ds, dp, dz0, dz1, dz2, dz3, %byte ss, sp, sz0, sz1, sz2, sz3, %byte type, seq) %constinteger old data type = 16_01 %constinteger old ACK type = 16_81 %recordformat RX buffer fm(%record(RX buffer fm)%name next, %integer bytes, (%bytearray data(1 : ether packet size) %or %c %record(ether header fm) header)) %ownrecord(RX buffer fm)%name RX free list == nil %ownrecord(RX buffer fm)%name inbound queue == nil %ownrecord(semaphore fm) inbound packet arrived = 0 %owninteger buffers remaining { for information only } = 0 %routine setup RX free list %record(RX buffer fm)%name x %integer i %for i = 1, 1, RX buffers %cycle x == record(global heap get(4 + 4 + ether packet size + 4)) x_next == RX free list RX free list == x %repeat buffers remaining = RX buffers %end %routine release RX buffer(%record(RX buffer fm)%name buffer) %integer old SR !! printstring("Release RX buffer "); phex(addr(buffer)); newline buffer low water = buffers remaining %if buffers remaining < buffer low water old SR = or to SR(station interrupts off) %if RX free list == nil %then buffers remaining = 1 %c %else buffers remaining = buffers remaining + 1 buffer_next == RX free list RX free list == buffer old SR = set SR(old SR) %end %ownbytename TX buffer1 == nil, TX buffer2 == nil %owninteger TX bytes1 = 0, TX bytes2 = 0 %ownrecord(semaphore fm) TX done = 0 ! Station interrupt handler %routine setup station handler %ownrecord(interrupt handler fm) station interrupt handler = 0 %owninteger old SR, RX buffer, pending DTX = -1 %label interrupt, check for control, control arrived, done %label DTX arrived, DTX 0, DTX 1, DTX 2, DTX d, DTX 3, DTX b %label RDY arrived, RDY 0, RDY 1, RDY 2, RDY 3, RDY 4, RDY 5 %label STX arrived, STX 0, STX 1, STX c %label ACK arrived, ACK 0 %label SSA arrived, SSA w old SR = or to SR(station interrupts off) command = rst mask; ! Ints off, reset the station command = 0 setup interrupt handler(station interrupt handler, addr(interrupt)) add interrupt handler(station interrupt handler, station interrupt level) command = enable station interrupts %while status & td mask = 0 %cycle; %repeat control = RAW %while status & td mask = 0 %cycle; %repeat control = ESA; ! Kick it into life old SR = set SR(old SR) %while station address = 0 %cycle; %repeat !! printstring("2MHz handler initialised, station address is ") !! phex2(station address); newline %return interrupt: ! The interupt handler proper. We enable interrupts for received data ! and control characters (cleared by reading the appropriate register). ! The following may be received: ! DTX: something has arrived from the wire ! STX: the packet we asked for is coming up next ! ETX: our packet has just finished (this won't cause an interrupt ! as we're in a busy-wait loop at the time reading the packet) ! RDY: the station is ready for our (TX) packet ! ACK: can be ignored! ! SSA: the station address is coming up next. ! Since we're in "raw" mode the "port" part of these will always be zero. ! Note, however, that it will be present and must be explicitly handled. ! If it's anything we don't recognise we log it and ignore it. We have ! reasonable control over the receipt of everything except DTX, which ! is driven by packets arriving off the wire. Since we're only geared ! up to handle one receive buffer at a time we tally the "extra" DTXs ! for future processing. ! ! Note: data is transferred in a busy-wait loop in the interrupt handler. ! This should result in less overhead than interrupt-per-character would. ! Any data interrupts are assumed to be caused by the station misbehaving: ! the character is read (to clear the condition) and thrown away. ! First off, read the status register to see if the interrupt was for us. ! If it wasn't we just return; if it was we fetch the control character ! (or read and throw away the data character). *btst.b #int bit, status { Ether station interrupting? *bne check for control { Yes, -> see what it was return from interrupt check for control: *btst.b #rc bit, status { Was it a control character? *bne control arrived { Yes, -> process it *move.b data, D0 { Must have been data, so read it.... {} D0 = D0 & 255; put long(D0); put sym(' ') return from interrupt { .... and ignore it. control arrived: *move.b control, D1 *cmp.b #DTX, D1 *beq DTX arrived *cmp.b #RDY, D1 *beq RDY arrived *cmp.b #STX, D1 *beq STX arrived *cmp.b #ACK, D1 *beq ACK arrived *cmp.b #SSA, D1 *beq SSA arrived ! Something unknown {} D1 = D1 & 255 {} put sym(NL); put sym('^'); put long(D1); put sym(' ') *addq.l #1, unrecognised control ! And fall through.... done: command = enable station interrupts return from interrupt DTX arrived: DTX 0:*btst.b #rd bit, status { 2-byte protocol, look for the *beq DTX 0 { second (port) byte and *move.b data, D0 { throw it away ! A DTX has arrived. Is there one pending already? *addq.l #1, pending DTX { Bump pending tally *beq DTX b { Nothing else waiting, -> process it *addq.l #1, pended DTX { Tally it -> done { Must wait meantime DTX b:! No DTX pending. Claim a buffer. If there isn't one then ask ! the station to drop the packet. Otherwise, send a RDY. *move.l RX free list, A0 { First on the free list *move.l A0, D0 { Anything on free list? *beq DTX d { None, -> drop *move.l (A0), RX free list { Unlink it *move A0, RX buffer { Note it *subq.l #1, buffers remaining DTX 1:*btst.b #td bit, status { TX empty? *beq DTX 1 *move.b #RDY, control { Tell station we're ready DTX 2:*btst.b #td bit, status { TX empty? *beq DTX 2 *move.b #0, data { Null port byte -> done DTX d:*btst.b #td bit, status { TX empty? *beq DTX d *move.b #ACK, control { Tell station to throw it away DTX 3:*btst.b #td bit, status { TX empty? *beq DTX 3 *move.b #0, data { Null port byte !! put sym('!') -> done STX arrived: STX 0:*btst.b #rd bit, status { 2-byte protocol, look for the *beq STX 0 { second (port) byte and *move.b data, D0 { throw it away ! The STX has arrived. Data will be arriving next. Read it in and ! tally it. When the ETX arrives put the buffer on the arrived ! queue and signal the inbound handler's semaphore. *move.l RX buffer, A0 { Previously-allocated buffer *lea 8(A0), A1 { Start of data area *clr.l D1 { Zap tally STX 1:*btst.b #rcd bit, status { Anything arrived? *beq STX 1 *btst.b #rd bit, status { Control (ETX) or data? *beq STX c *move.b data, D0 { Read the character *move.b D0, (A1)+ { Save it *addq.l #1, D1 { Bump tally -> STX 1 { Round for the next one STX c:*move.b control, D0 { Must have been the ETX?? *move.l D1, 4(A0) { Note packet size *move.l inbound queue, A1 { Tail of stuff waiting *move.l A1, (A0) { Link it on *move.l A0, inbound queue { Link us in int signal semaphore(inbound packet arrived) *subq.l #1, pending DTX { Note packet has been done *bge DTX b { More still to do, -> get buffer RX buffer = 0 { Nothing pending, zap for safety -> done RDY arrived: RDY 0:*btst.b #rd bit, status { 2-byte protocol, look for the *beq RDY 0 { second (port) byte and *move.b data, D0 { throw it away ! This must have been in response to our DTX. The TX buffer address ! and length will already have been set up, so all we have to do is ! hand it on to the station, bracketed by STX/ETX. Since we're in raw ! mode the packet must be at least 13 bytes long, so we don't have to ! worry about dealing with awkward empty ones.... RDY 1:*btst.b #td bit, status { TX empty? *beq RDY 1 *move.b #STX, control { Tell station we're ready RDY 2:*btst.b #td bit, status { TX empty? *beq RDY 2 *move.b #0, data { Null port byte ! We can, optionally, concatenate two buffers together to avoid copying. ! Set up and send the first one. *move.l TX bytes1, D0 { Number of bytes to send *subq.l #1, D0 { (for dbra) *move.l TX buffer1, A0 { Address of first byte to go RDY 3:*btst.b #td bit, status { TX empty? *beq RDY 3 *move.b (A0)+, data { Next data byte to go *dbra D0, RDY 3 { Any more? ! Now for the second buffer.... *move.l TX bytes2, D0 { Number of bytes to send *ble RDY 5 { None in second part *subq.l #1, D0 { (for dbra) *move.l TX buffer2, A0 { Address of first byte to go RDY 4:*btst.b #td bit, status { TX empty? *beq RDY 4 *move.b (A0)+, data { Next data byte to go *dbra D0, RDY 4 { Any more? RDY 5:*btst.b #td bit, status { TX empty? *beq RDY 5 *move.b #ETX, control { Tell station we're done int signal semaphore(TX done) -> done ACK arrived: ! This is just in to keep the host/station protocol the same for raw ! mode as it is for cooked mode. We just read the port byte and ! ignore it all.... ACK 0:*btst.b #rd bit, status { 2-byte protocol, look for the *beq ACK 0 { second (port) byte and *move.b data, D0 { throw it away -> done SSA arrived: ! The next data character will be the station address SSA w:*btst.b #rd bit, status *beq SSA w *lea station address, A0 *move.b data, 3(A0) -> done %end ! Inbound packet handler %recordformat icb fm(%integer active, %record(RX buffer fm)%name pending packets, %record(ether request fm)%name pending reads) %ownrecord(icb fm)%array icbs(2 : last registered) = 0(*) %ownrecord(semaphore fm) icb semaphore = 0 %routine copy out(%record(RX buffer fm)%name from, %record(ether request fm)%name to) %integer f, t %label L to_bytes = from_bytes - 14 %if to_bytes > 0 %start ! Beware null packets f = addr(from_data(15)) t = addr(to_buffer) D0 = to_bytes - 1; A0 = f; A1 = t L: *move.b (A0)+, (A1)+ *dbra D0, L %finish to_ra = from_header_ss to_la = from_header_ds to_type = from_header_type %end %routinespec old inbound handler(%record(RX buffer fm)%name buffer) %routine inbound packet handler %record(RX buffer fm)%name buffer, last %record(icb fm)%name icb %record(ether request fm)%name rr %integer old SR, i, n %record(poa fm)%name P %on 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 %start ! Last-chance disaster-trap P == POA printstring("2Meg (inbound): unexpected event "); write(P_event, 0) space; write(P_event sub, 0); space; phex(P_event extra) space; printstring(P_event message) printstring(" at or about PC "); phex(P_event PC) newline %for i = 0, 1, 15 %cycle phex(P_event r(i)); space newline %if i & 7 = 7 %repeat semaphore wait(disaster) %finish open input(3, ":N"); select input(3) open output(3, ":T"); select output(3) !! printstring("Inbound handler: "); write(free store, 0); newline %cycle !L! lights and B(\ lights inbound) semaphore wait(inbound packet arrived) !L! lights or B(lights inbound) %cycle last == nil old SR = or to SR(station interrupts off) old SR = set SR(old SR) %and %exit %if inbound queue == nil buffer == inbound queue last == buffer %and buffer == buffer_next %while buffer_next ## nil %if last == nil %then inbound queue == nil %c %else last_next == nil old SR = set SR(old SR) ! We've unlinked the first pending packet. Deal with it. inbound packets = inbound packets + 1 inbound bytes = inbound bytes + buffer_bytes %if 0 # buffer_header_ds # station address %start ! Not for us !! printstring("Dud destination received: ") !! n = buffer_bytes; n = 18 %unless 0 <= n < 18 !! phex2(buffer_data(i)) %and space %for i = 1, 1, n !! newline release RX buffer(buffer) dud destinations = dud destinations + 1 %else %if buffer_header_type = old data type %c %or buffer_header_type = old ACK type ! Hand the buffer to the old-style protocol handler. Let it ! free the buffer when it's finished old inbound handler(buffer) %else %if 2 <= buffer_header_type <= last registered !! write(buffer_bytes, 0); printstring(" inbound:"); newline !! dump(buffer_data(1), buffer_bytes) semaphore wait(icb semaphore) icb == icbs(buffer_header_type) %if icb_active = 0 %start !! printstring("Inactive type "); phex2(buffer_header_type) !! printstring(" received: ") !! n = buffer_bytes; n = 18 %unless 0 <= n < 18 !! phex2(buffer_data(i)) %and space %for i = 1, 1, n !! newline release RX buffer(buffer) dud types = dud types + 1 %else %if icb_pending reads == nil %start ! Nothing waiting, so put it on the end of the list !! printstring("Nothing waiting, put at end"); newline buffer_next == nil %if icb_pending packets == nil %start icb_pending packets == buffer %else last == icb_pending packets last == last_next %while last_next ## nil last_next == buffer %finish %else ! There's a read request already waiting, so complete it !! printstring("Complete pending"); newline rr == icb_pending reads icb_pending reads == rr_next copy out(buffer, rr) release RX buffer(buffer) rr_status = ether success send message(rr, rr_system part_reply, nil) %c %if rr_system part_reply ## nil %finish %finish signal semaphore(icb semaphore) %else !! printstring("Unknown type "); phex2(buffer_header_type) !! printstring(" received: ") !! n = buffer_bytes; n = 18 %unless 0 <= n < 18 !! phex2(buffer_data(i)) %and space %for i = 1, 1, n !! newline release RX buffer(buffer) dud types = dud types + 1 %finish %repeat %repeat %end ! Outbound packet handler %ownrecord(semaphore fm) outbound semaphore = 0 %routine outbound packet handler(%bytename buffer1, %integer bytes1, %bytename buffer2, %integer bytes2) %record(RX buffer fm)%name loop %integer i, old SR %bytename b !L! lights or A(lights outbound) %if bytes1 >= 13 %start outbound packets = outbound packets + 1 outbound bytes = outbound bytes + bytes1 outbound bytes = outbound bytes + bytes2 %if bytes2 > 0 !! write(bytes1, 0) !! printstring(" + ") %and write(bytes2, 0) %if bytes2 > 0 !! printstring(" to send: ") !! b == buffer1 !! %for i = 1, 1, bytes1 %cycle !! phex2(b); space !! b == b [1] !! %repeat !! newline !! %if bytes2 > 0 %start !! b == buffer2 !! %for i = 1, 1, bytes2 %cycle !! phex2(b); space !! b == b [1] !! %repeat !! newline !! %finish !! b == buffer1 !! %if b [12] = 6 %start !! write(bytes1, 0); printstring(" + ") !! write(bytes2, 0); printstring(" out:"); newline !! dump(buffer1, bytes1); dump(buffer2, bytes2) !! %finish %if buffer1 = station address %start ! Destination = us, loopback. First grab a RX buffer. !! printstring("2Meg loopback"); newline old SR = or to SR(station interrupts off) loop == RX free list RX free list == loop_next %if loop ## nil old SR = set SR(old SR) ! If we've got a buffer, copy the two data segments and ! then enqueue the buffer on the inbound queue. %if loop ## nil %start b == loop_data(1); loop_bytes = 0 %while bytes1 > 0 %cycle b = buffer1 b == b [1]; buffer1 == buffer1 [1] loop_bytes = loop_bytes + 1 bytes1 = bytes1 - 1 %repeat %while bytes2 > 0 %cycle b = buffer2 b == b [1]; buffer2 == buffer2 [1] loop_bytes = loop_bytes + 1 bytes2 = bytes2 - 1 %repeat old SR = or to SR(station interrupts off) loop_next == inbound queue inbound queue == loop old SR = set SR(old SR) signal semaphore(inbound packet arrived) %finish; ! Else just drop the packet.... %else ! Grab semaphore, set up pointers for the transfer semaphore wait(outbound semaphore) TX buffer1 == buffer1; TX bytes1 = bytes1 TX buffer2 == buffer2; TX bytes2 = bytes2 ! Interrrupts off and kick the station !L! lights or A(lights sending) old SR = or to SR(station interrupts off) %while status & td mask = 0 %cycle; %repeat control = DTX %while status & td mask = 0 %cycle; %repeat data = 0 ! Interrupts on again and wait for the packet to go old SR = set SR(old SR) semaphore wait(TX done) !L! lights and A(\lights sending) signal semaphore(outbound semaphore) %finish %else printstring("Dud short TX request: "); write(bytes1, 0) printstring(" + ") %and write(bytes2, 0) %if bytes2 > 0 newline %finish !L! lights and A(\ lights outbound) %end ! Old-style protocol handler. ! Protocol control blocks are accessed in one of two ways: from the user side ! they are accessed directly by indexing into the table, while from the ether ! side they are accessed indirectly via a table of chained blocks, one slot ! for each remote address. "Port 0" is slightly special-cased to set up the ! destination station and port and to register the existence of a recipient. ! Mostly it is just the same as the VC ports. %recordformat old protocol fm(%record(old protocol fm)%name next, %record(ether header fm) header, %integer reTX lives, next reTX, reTX interval, %integer last seq, last address, %record(RX buffer fm)%name pending packets, %record(ether request fm)%name pending reads, %record(ether request fm)%name pending writes) %ownrecord(old protocol fm)%array opcb(0 : old protocol slots) = 0(*) %ownrecord(old protocol fm)%namearray opsl(first station - 1 : last station) == nil(*) %ownrecord(semaphore fm) old protocol semaphore = 0 %record(old protocol fm)%map find inbound opcb(%record(ether header fm)%name h) %record(old protocol fm)%name p %result == nil %unless first station <= h_ss <= last station p == opsl(h_ss) !! printstring("Find inbound first at "); phex(addr(p)); newline %while p ## nil %cycle ! Do source/destinatiaon ports match? Source address for each ! pcb is implied by the list it's on. %result == p %if p_header_dp = h_sp %and p_header_sp = h_dp p == p_next !! printstring("Next at "); phex(addr(p)); newline %repeat %result == nil %end %routine copy out old style(%record(RX buffer fm)%name from, %record(ether request fm)%name to) %integer f, t %label L to_bytes = from_bytes - 14 %if to_bytes > 0 %start ! Beware null packets f = addr(from_data(15)) t = addr(to_buffer) D0 = to_bytes - 1; A0 = f; A1 = t L: *move.b (A0)+, (A1)+ *dbra D0, L %finish to_ra = from_header_ss to_rp = from_header_sp %end %routine do old write(%record(old protocol fm)%name p, %integer setup) !! printstring("Do old write: "); phex(addr(p)) !! write(setup, 1); write(p_pending writes_bytes, 1); newline !L!lights or A(lights old outbound) %if setup # 0 %start p_reTX lives = reTX lives p_reTX interval = 1 p_next reTX = timestamp + 3 %finish %if p == opcb(0) %start ! Port 0, copy in ds and dp p_header_ds = p_pending writes_ra p_header_dp = p_pending writes_rp %finish outbound packet handler(p_header_ds, 14, p_pending writes_buffer, p_pending writes_bytes) old outbound packets = old outbound packets + 1 !L!lights and A(\ lights old outbound) %end %routine old inbound handler(%record(RX buffer fm)%name buffer) %ownrecord(ether header fm) ACK = 0 %record(RX buffer fm)%name RX pending %record(ether request fm)%name request pending %record(old protocol fm)%name p %record(poa fm)%name PP %integer i, n %on 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 %start ! Last-chance disaster-trap PP == POA printstring("2Meg (old in): unexpected event "); write(PP_event, 0) space; write(PP_event sub, 0); space; phex(PP_event extra) space; printstring(PP_event message) printstring(" at or about PC "); phex(PP_event PC) newline %for i = 0, 1, 15 %cycle phex(PP_event r(i)); space newline %if i & 7 = 7 %repeat semaphore wait(disaster) %finish !L! lights or B(lights old inbound) %if buffer_header_ds = 0 %start ! Drop broadcasts release RX buffer(buffer) !L! lights and B(\ lights old inbound) %return %finish semaphore wait(old protocol semaphore) !! printstring("Old inbound at "); phex(addr(buffer)) !! printstring(" for "); write(buffer_header_dp, 0) !! printstring(", type "); phex2(buffer_header_type); newline %if buffer_header_dp = 0 %then p == opsl(first station - 1) %c %else p == find inbound opcb(buffer_header) %if p ## nil %start ! Connection is known. Is this inbound data or an ACK for ! something we've sent? %if buffer_header_type = old data type %start ! Data packet has arrived old inbound packets = old inbound packets + 1 !! printstring("Data arrived from "); phex2(buffer_header_ss) !! print symbol('.'); phex2(buffer_header_sp) !! printstring(", seq "); phex2(buffer_header_seq) !! printstring(", pcb at "); phex(addr(p)) !! newline ! First ACK the packet. !ACK = 0 ACK_ss = station address; ! Filled in by station anyway... ACK_sp = buffer_header_dp ACK_ds = buffer_header_ss ACK_dp = buffer_header_sp ACK_type = old ACK type ACK_seq = buffer_header_seq outbound packet handler(ACK_ds, 14, nil, 0) ! Now if this packet's sequence number is new then we forward ! the data to the user or queue the buffer if there isn't ! a pending read. %if buffer_header_seq # p_last seq %c %or buffer_header_ss # p_last address %start p_last seq = buffer_header_seq p_last address = buffer_header_ss %if p_pending reads ## nil %start ! There's a user request waiting, so complete it. !! printstring("Pending user request"); newline request pending == p_pending reads p_pending reads == request pending_next copy out old style(buffer, request pending) request pending_status = ether success send message(request pending, request pending_system part_reply, nil) %c %if request pending_system part_reply ## nil %else ! No user-request waiting, so put this on the pending queue !! printstring("No pending user request"); newline buffer_next == nil %if p_pending packets == nil %start ! Queue empty p_pending packets == buffer %else ! Something else waiting RX pending == p_pending packets RX pending == RX pending_next %while RX pending_next ## nil RX pending_next == buffer %finish ! Now avoid dropping through, as if we did we would ! be releasing our newly-enqueued RX buffer.... signal semaphore(old protocol semaphore) !L! lights and B(\ lights old inbound) %return %finish %finish %else %if buffer_header_type = old ACK type ! ACK packet has arrived !! printstring("ACK arrived from "); phex2(buffer_header_ss) !! print symbol('.'); phex2(buffer_header_sp) !! printstring(", seq "); phex2(buffer_header_seq) !! printstring(", expecting "); phex2(p_header_seq) !! newline %if buffer_header_seq = p_header_seq %c %and buffer_header_ss = p_header_ds %start !! printstring("Expected ACK"); newline ! It's an ACK for the last thing we sent. First bump our ! send sequence number (avoiding 0, as cooked stations ! treat it as special). p_header_seq = (p_header_seq + 1) & 255 p_header_seq = 1 %if p_header_seq = 0 ! Now complete the first request on the pending write ! queue. %if p_pending writes ## nil %start !! printstring("Completing pending write"); newline request pending == p_pending writes p_pending writes == request pending_next request pending_status = ether success send message(request pending, request pending_system part_reply, nil) %c %if request pending_system part_reply ## nil do old write(p, 1) %if p_pending writes ## nil; ! Kick next !! %else !! printstring("Spurious ACK from "); phex2(buffer_header_ss) !! print symbol('.'); phex2(buffer_header_sp) !! printstring(", seq "); phex2(buffer_header_seq) !! newline %finish !! %else !! printstring("Unexpected ACK: expecting ") !! phex2(p_header_seq); printstring(" got ") !! phex2(buffer_header_seq); newline %finish !! %else !! ! Something bogus on the queue !! printstring("Something bogus arrived from ") !! phex2(buffer_header_ss) !! print symbol('.'); phex2(buffer_header_sp) !! printstring(", type "); phex2(buffer_header_type) !! printstring(", seq "); phex2(buffer_header_seq) !! newline %finish %else ! Else ignore it !! printstring("No takers for ") !! phex2(buffer_header_ss) !! print symbol('.'); phex2(buffer_header_sp) !! printstring(" -> "); phex2(buffer_header_ds) !! print symbol('.'); phex2(buffer_header_dp) !! printstring(", type "); phex2(buffer_header_type) !! printstring(", seq "); phex2(buffer_header_seq) !! newline old inbound no takers = old inbound no takers + 1 %finish signal semaphore(old protocol semaphore) release RX buffer(buffer) !L! lights and B(\ lights old inbound) %end %routine old style ether request(%record(ether request fm)%name m) %record(old protocol fm)%name p, q %record(ether request fm)%name pw %record(RX buffer fm)%name RX buffer %integer i, j !! printstring("Ether request type "); phex2(m_code) !! printstring(", context "); write(m_context, 0); newline %if m_code = ether old define %start !! printstring("Define: RA "); phex2(m_ra) !! print symbol('.'); phex2(m_rp); newline old defines = old defines + 1 semaphore wait(old protocol semaphore) %unless first station <= m_ra <= last station %start m_status = ether dud destination !! printstring("Define dud destination"); newline %else m_context = 0 %for i = 1, 1, old protocol slots %cycle p == opcb(i) m_context = i %and %exit %if p_header_type = 0 %repeat %if m_context # 0 %start !! printstring("Free slot at "); write(m_context, 0); newline ! A free slot. Link it in. p = 0 %if opsl(m_ra) == nil %start ! Station list was empty !! printstring("Only one on list"); newline opsl(m_ra) == p p_next == nil %else ! Put on (head of) station list !! printstring("Link in front of ") !! phex(addr(opsl(m_ra))); newline p_next == opsl(m_ra) opsl(m_ra) == p %finish ! Find the lowest free local port j = 0 q == opsl(m_ra) %while q ## nil %cycle j = j ! (1 << q_header_sp) q == q_next %repeat %for i = 1, 1, 31 %cycle %if j & (1 << i) = 0 %start ! Found one free !! printstring("Assigning local port ") !! write(i, 0); newline p_header_sp = i m_lp = i %exit %finish ! Assume for now that this will always succeed! %repeat p_header_ss = station address p_header_ds = m_ra p_header_dp = m_rp p_header_type = old data type p_header_seq = timestamp & 255 p_header_seq = 1 %if p_header_seq = 0; ! Avoid 0!! p_last seq = -1 m_status = ether success !! printstring("Done, context "); write(m_context, 0) !! printstring(", LP "); phex2(m_lp); newline %else ! Must have been last slot, and in use. m_status = ether no free slots %finish %finish signal semaphore(old protocol semaphore) %else %if m_code = ether old redefine old redefines = old redefines + 1 semaphore wait(old protocol semaphore) %unless 0 < m_context <= old protocol slots %start m_status = ether dud context %else p == opcb(m_context) %if p_header_type = 0 %start ! Not in use m_status = ether dud context %else ! Change the port number of our peer. Remember to reset ! our send and receive sequence numbers. p_header_dp = m_rp p_header_seq = timestamp & 255 p_header_seq = 1 %if p_header_seq = 0; ! Avoid 0!! p_last seq = -1 m_status = ether success %finish %finish signal semaphore(old protocol semaphore) %else %if m_code = ether old undefine old undefines = old undefines + 1 semaphore wait(old protocol semaphore) %unless 0 < m_context <= old protocol slots %start m_status = ether dud context %else p == opcb(m_context) %if p_header_type = 0 %start ! Not in use m_status = ether dud context %else ! Clear down this slot. We have to release any pending receive ! buffers and return any pending user requests with an error ! status. Finally, we have to unlink the pcb from the chain ! of per-station blocks. ! First off, get rid of any pending RX buffers, so that they ! can be reused by the interrupt handler. %while p_pending packets ## nil %cycle RX buffer == p_pending packets p_pending packets == RX buffer_next release RX buffer(RX buffer) %repeat ! Pending read and write requests next... Complete these ! in the order in which they were queued, in case it matters ! to the caller. %while p_pending reads ## nil %cycle pw == p_pending reads p_pending reads == pw_next pw_status = ether operation aborted send message(pw, pw_system part_reply, nil) %c %if pw_system part_reply ## nil %repeat %while p_pending writes ## nil %cycle pw == p_pending writes p_pending writes == pw_next pw_status = ether operation aborted send message(pw, pw_system part_reply, nil) %c %if pw_system part_reply ## nil %repeat ! Finally, unlink us from the per-station chain i = p_header_ds %if first station <= i <= last station %start %if opsl(i) == p %start ! We're the first on the chain opsl(i) == p_next %else ! We're not first in the chain, so follow it down... q == opsl(i) q == q_next %while q_next ## nil %and q_next ## p %if q == nil %start printstring("Slow PCB "); write(m_context, 0) printstring(" not in chain for "); phex2(i) newline %else q_next == p_next %finish %finish %else printstring("Dud ether address "); phex2(i) printstring(" in slow PCB "); write(m_context, 0) newline %finish ! Finally, clear it down just to be sure p = 0 m_status = ether success %finish %finish signal semaphore(old protocol semaphore) %else %if m_code = ether old read old reads = old reads + 1 m_timeout = m_timeout + timestamp %if m_timeout > 0 semaphore wait(old protocol semaphore) %unless 0 <= m_context <= old protocol slots %start m_status = ether dud context %else p == opcb(m_context) %if p_header_type = 0 %start ! Undefined m_status = ether dud context %else ! There are two possible cases here: either there is a ! packet waiting for someone to read it, or there are ! no packets waiting and instead a queue of (zero or more) ! read requests waiting. %if p_pending packets == nil %start ! No pending packets. Enqueue our read on the end of ! the wait queue. m_next == nil %if p_pending reads == nil %start p_pending reads == m %else pw == p_pending reads pw == pw_next %while pw_next ## nil pw_next == m %finish signal semaphore(old protocol semaphore) %return; ! Inbound process will complete it. %else ! A packet awaits a reader. By implication, there ! will be no read requests pending..... RX buffer == p_pending packets p_pending packets == RX buffer_next copy out old style(RX buffer, m) release RX buffer(RX buffer) m_status = ether success %finish %finish %finish signal semaphore(old protocol semaphore) %else %if m_code = ether old write old writes = old writes + 1 %unless 0 <= m_context <= old protocol slots %start m_status = ether dud context %else %unless 0 < m_bytes <= ether packet size - 14 m_status = ether operation undefined %else semaphore wait(old protocol semaphore) p == opcb(m_context) %if p_header_type = 0 %start ! Undefined m_status = ether dud context %else m_next == nil %if p_pending writes == nil %start p_pending writes == m do old write(p, 1) %else ! Something already going, so just enqueue this one pw == p_pending writes pw == pw_next %while pw_next ## nil pw_next == m %finish signal semaphore(old protocol semaphore) %return; ! Inbound process will complete it... %finish signal semaphore(old protocol semaphore) %finish %else %if m_code = ether old register0 old defines = old defines + 1 semaphore wait(old protocol semaphore) %if opsl(first station - 1) == nil %start ! Not yet defined p == opcb(0) p = 0 p_header_ss = station address p_header_type = old data type p_header_seq = timestamp & 255 p_header_seq = 1 %if p_header_seq = 0; ! Avoid 0!! p_last seq = -1 ! ds & dp filled in on per-TX basis opsl(first station - 1) == p m_status = ether success %else ! Already registered m_status = ether operation undefined %finish signal semaphore(old protocol semaphore) %else %if m_code = ether old unregister0 old undefines = old undefines + 1 semaphore wait(old protocol semaphore) %if opsl(first station - 1) == nil %start ! Undefined m_status = ether operation undefined %else opsl(first station - 1) == nil ! First off, get rid of any pending RX buffers, so that they ! can be reused by the interrupt handler. %while p_pending packets ## nil %cycle RX buffer == p_pending packets p_pending packets == RX buffer_next release RX buffer(RX buffer) %repeat ! Pending read and write requests next... Complete these ! in the order in which they were queued, in case it matters ! to the caller. %while p_pending reads ## nil %cycle pw == p_pending reads p_pending reads == pw_next pw_status = ether operation aborted send message(pw, pw_system part_reply, nil) %c %if pw_system part_reply ## nil %repeat %while p_pending writes ## nil %cycle pw == p_pending writes p_pending writes == pw_next pw_status = ether operation aborted send message(pw, pw_system part_reply, nil) %c %if pw_system part_reply ## nil %repeat m_status = ether success %finish signal semaphore(old protocol semaphore) %else m_status = ether operation undefined %finish send message(m, m_system part_reply, nil) %if m_system part_reply ## nil %end %routine old timeout handler %record(old protocol fm)%name p %record(ether request fm)%name r, lr, n %integer i %record(poa fm)%name PP %on 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 %start ! Last-chance disaster-trap PP == POA printstring("2Meg (timeout): unexpected event "); write(PP_event, 0) space; write(PP_event sub, 0); space; phex(PP_event extra) space; printstring(PP_event message) printstring(" at or about PC "); phex(PP_event PC) newline %for i = 0, 1, 15 %cycle phex(PP_event r(i)); space newline %if i & 7 = 7 %repeat semaphore wait(disaster) %finish open input(3, ":N"); select input(3) open output(3, ":T"); select output(3) !! printstring("Old timeout handler: "); write(free store, 0); newline %cycle semaphore wait(clock semaphore) !%continue %unless timestamp & 15 = 0 ! Each time we are wakened we scan the pcb table looking for ! user timeouts and retransmit timeouts expiring. semaphore wait(old protocol semaphore) %for i = first station - 1, 1, last station %cycle p == opsl(i) %while p ## nil %cycle %if P_header_type # 0 %start !! write(timestamp, 0); printstring(" station "); phex2(i) !! printstring(", PW "); phex(addr(p_pending writes)) !! printstring(", lives "); write(p_reTX lives, 0) !! printstring(", next "); write(p_next reTX, 0) !! printstring(", interval "); write(p_reTX interval, 0) !! newline ! In use. First retransmit any pending write requests. %if p_pending writes ## nil %c %and p_next reTX <= timestamp %start ! First pending write's timeout has expired. p_reTX lives = p_reTX lives - 1 %if p_reTX lives <= 0 %start ! Not ACKed, return an error to user. old ACK timeouts = old ACK timeouts + 1 !! phex(addr(r)); printstring(" not ACKed"); newline r == p_pending writes p_pending writes == r_next; ! Unlink it r_status = ether timeout send message(r, r_system part_reply, nil) %c %if r_system part_reply ## nil ! Transmit the next on the queue, if any do old write(p, 1) %if p_pending writes ## nil; ! Kick next %else !! printstring("ReTX "); phex(addr(p)); newline old retransmits = old retransmits + 1 p_reTX interval = p_reTX interval << 1 p_next reTX = timestamp + p_reTX interval !L! ! lights or A(lights old reTX) do old write(p, 0) !L! ! lights and A(\ lights old reTX) %finish %finish ! Now check for expired read timeouts. lr == nil; r == p_pending reads %while r ## nil %cycle %if 0 < r_timeout <= timestamp %start ! This one's specified timeout has expired. Unlink ! it and send back an error message old user timeouts = old user timeouts + 1 !! phex(addr(r)); printstring(" user timeout"); newline n == r_next; ! Note the next one before we send this back. r_status = ether timeout send message(r, r_system part_reply, nil) %c %if r_system part_reply ## nil ! Now set up for the next one r == n %if lr == nil %then p_pending reads == r %c %else lr_next == r %else ! No timeout specified, or not expired yet. On ! to the next one. lr == r r == r_next %finish %repeat %finish p == p_next %repeat %repeat opcb(0)_last seq = -1 %if timestamp & 63 = 0; ! Nasty frig for cooked stations signal semaphore(old protocol semaphore) %repeat %end ! General protocol handler %routine ether request(%record(ether request fm)%name m) %record(RX buffer fm)%name RX %record(ether request fm)%name r, last r %record(icb fm)%name icb %record(ether header fm) header !! printstring("Ether request: "); phex2(m_code) !! space; phex(m_tag); space; phex2(m_type) !! space; write(m_bytes, 0); space; phex2(m_ra); newline %unless 2 <= m_type <= last registered %start m_status = ether dud context send message(m, m_system part_reply, nil) %c %if m_system part_reply ## nil %finish icb == icbs(m_type) semaphore wait(icb semaphore) %if m_code = ether read %start %if icb_active = 0 %start signal semaphore(icb semaphore) m_status = ether dud context send message(m, m_system part_reply, nil) %c %if m_system part_reply ## nil %return %finish %if icb_pending packets == nil %start ! Nothing waiting, we'll have to queue !! printstring("Read: must queue"); newline m_next == nil %if icb_pending reads == nil %start icb_pending reads == m %else last r == icb_pending reads last r == last r_next %while last r_next ## nil last r_next == m %finish signal semaphore(icb semaphore) %else ! Take the first pending packet !! printstring("Read: take first"); newline RX == icb_pending packets icb_pending packets == RX_next signal semaphore(icb semaphore) copy out(RX, m) release RX buffer(RX) m_status = ether success send message(m, m_system part_reply, nil) %c %if m_system part_reply ## nil %finish %else %if m_code = ether write %unless 0 < m_bytes <= ether packet size - 14 %start signal semaphore(icb semaphore) m_status = ether operation undefined send message(m, m_system part_reply, nil) %c %if m_system part_reply ## nil %return %finish %if icb_active = 0 %start signal semaphore(icb semaphore) m_status = ether dud context send message(m, m_system part_reply, nil) %c %if m_system part_reply ## nil %return %finish signal semaphore(icb semaphore) header = 0 header_ds = m_ra header_dp = 255 header_ss = station address header_sp = 255 header_type = m_type header_seq = 255 outbound packet handler(header_ds, 14, m_buffer, m_bytes) m_status = ether success send message(m, m_system part_reply, nil) %if m_system part_reply ## nil %else %if m_code = ether register %if icb_active # 0 %start signal semaphore(icb semaphore) m_status = ether dud context send message(m, m_system part_reply, nil) %c %if m_system part_reply ## nil %return %finish icb_active = 1 signal semaphore(icb semaphore) m_status = ether success send message(m, m_system part_reply, nil) %if m_system part_reply ## nil %else %if m_code = ether unregister %if icb_active = 0 %start signal semaphore(icb semaphore) m_status = ether dud context send message(m, m_system part_reply, nil) %c %if m_system part_reply ## nil %return %finish ! Release all pending packets %while icb_pending packets ## nil %cycle RX == icb_pending packets icb_pending packets == RX_next release RX buffer(RX) %repeat ! Return any pending read requests in the order they were queued %while icb_pending reads ## nil %cycle r == icb_pending reads icb_pending reads == r_next r_status = ether operation aborted send message(r, r_system part_reply, nil) %c %if r_system part_reply ## nil %repeat ! Mark inactive and release the icb icb_active = 0 signal semaphore(icb semaphore) ! Finally send the request back with a success code m_status = ether success send message(m, m_system part_reply, nil) %if m_system part_reply ## nil %else signal semaphore(icb semaphore) m_status = ether operation undefined send message(m, m_system part_reply, nil) %if m_system part_reply ## nil %finish %end ! Main program -- take user requests and call one or other of the old-style ! or general protocol handlers %begin %record(process fm)%name p %ownrecord(mailbox fm) mailbox = 0 %ownrecord(semaphore fm) semaphore = 0 %record(ether request fm)%name m %label start inbound handler, start old timeout handler %record(poa fm)%name PP %integer i %on 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 %start ! Last-chance disaster-trap PP == POA printstring("2Meg (requests): unexpected event "); write(PP_event, 0) space; write(PP_event sub, 0); space; phex(PP_event extra) space; printstring(PP_event message) printstring(" at or about PC "); phex(PP_event PC) newline %for i = 0, 1, 15 %cycle phex(PP_event r(i)); space newline %if i & 7 = 7 %repeat semaphore wait(disaster) %finish open input(2, ":N"); select input(2) open output(2, ":T"); select output(2) setup semaphore(disaster) setup semaphore(clock semaphore) start ticking setup semaphore(inbound packet arrived) setup semaphore(TX done) setup semaphore(icb semaphore) signal semaphore(icb semaphore) setup semaphore(old protocol semaphore) signal semaphore(old protocol semaphore) setup semaphore(outbound semaphore) signal semaphore(outbound semaphore) setup semaphore(semaphore) setup mailbox(mailbox, semaphore) setup RX free list setup station handler p == create process(inbound handler size, addr(start inbound handler), inbound handler priority, nil) p == create process(old timeout handler size, addr(start old timeout handler), old timeout handler priority, nil) FS insert(ether mailbox name, addr(mailbox)) FS insert("SLOW_ETHER_ADDRESS", station address); ! NB: not pointer {} printstring("Slow ether: "); write(free store, 0) {} printstring(" free, station ") {} phex2(station address); newline !L! lights and B(\ lights clear) !L! lights and A(\ lights clear) %cycle m == receive message(mailbox) %if m_code & ether old operation # 0 %start !L! lights or B(lights type) old style ether request(m) !L! lights and B(\ lights type) %else %if m_code & ether operation # 0 !L! lights or A(lights type) ether request(m) !L! lights and A(\ lights type) %else %if m_code = ether stats m_stats_station address = station address m_stats_buffer low water = buffer low water m_stats_pended DTX = pended DTX m_stats_unrecognised control = unrecognised control m_stats_inbound packets = inbound packets m_stats_inbound bytes = inbound bytes m_stats_outbound packets = outbound packets m_stats_outbound bytes = outbound bytes m_stats_dud destinations = dud destinations m_stats_dud types = dud types m_stats_old inbound packets = old inbound packets m_stats_old outbound packets = old outbound packets m_stats_old inbound no takers = old inbound no takers m_stats_old retransmits = old retransmits m_stats_old ACK timeouts = old ACK timeouts m_stats_old user timeouts = old user timeouts m_stats_old defines = old defines m_stats_old redefines = old redefines m_stats_old undefines = old undefines m_stats_old reads = old reads m_stats_old writes = old writes m_status = ether success send message(m, m_system part_reply, nil) %c %if m_system part_reply ## nil %else %if m_code = ether station address m_context = station address m_status = ether success send message(m, m_system part_reply, nil) %c %if m_system part_reply ## nil %else m_status = ether operation undefined send message(m, m_system part_reply, nil) %c %if m_system part_reply ## nil %finish %repeat start inbound handler: inbound packet handler start old timeout handler: old timeout handler %end %of %program