-- RouterImpl.mesa (last edited by: BLyon on: March 21, 1981 11:53 AM) -- Function: The implementation module for the Pilot OISCP Router switch. DIRECTORY BufferDefs USING [OisBuffer], Checksums USING [SetChecksum, TestChecksum], CommunicationInternal USING [], CommFlags USING [doDebug, doStats, doStorms], CommUtilDefs USING [CopyLong], DriverDefs USING [ ChangeNumberOfInputBuffers, DriverXmitStatus, GetDeviceChain, Glitch, MaybeGetFreeOisBuffer, Network, PutOnGlobalDoneQueue, RouterObject, SetOisRouter], Inline USING [LowHalf], OISCP USING [ allHostIDs, CreditReceiveOisBuffer, EnqueueOis, MaybeGetFreeReceiveOisBufferFromPool, unknownNetID, unknownSocketID], OISCPConstants USING [routingInformationSocket], OISCPTypes USING [ bytesPerPktHeader, bytesPerPktText, maxBytesPerPkt, OISErrorCode, TransPortControl], Process USING [Yield], Router USING [ AddNetwork, FindNetworkAndTransmit, FindDestinationRelativeNetID, ForwardPacket, RemoveNetwork, RoutingInformationPacket, RoutingTableOn, RoutingTableOff, SocketTable, StateChanged, XmitStatus], SocketInternal USING [SocketHandle], SpecialCommunication USING [RoutersFunction], SpecialSystem USING [ broadcastHostNumber, GetProcessorID, HostNumber, NetworkAddress, nullNetworkNumber, NetworkNumber, SocketNumber], StatsDefs USING [StatIncr], System USING [GetClockPulses]; RouterImpl: MONITOR LOCKS socketRouterLock IMPORTS BufferDefs, Checksums, CommUtilDefs, DriverDefs, Inline, OISCP, Process, Router, SpecialSystem, StatsDefs, System EXPORTS BufferDefs, CommunicationInternal, OISCP, Router SHARES BufferDefs = BEGIN OPEN DriverDefs, OISCP, OISCPTypes, SocketInternal, StatsDefs; -- Many of these variables must eventually live in outerspace so that multiple MDSs -- access the same router variables. The modules in the primary MDS will have the -- proceses and will perform the initialization of the "globals", while the others will not. -- The lock covers both SocketImpl and RouterImpl, specifically the socket objects, -- the socket table, and spare socket ID counter. These will all live in outerspace. -- The monitor locks some variables on the global frame too. They can be MDS specific. -- EXPORTed TYPEs Network: PUBLIC TYPE = DriverDefs.Network; -- switches and variables that don't change during execution unless for diagnostics primaryMDS: PUBLIC BOOLEAN ← TRUE; -- we are in the primary MDS. checkIt: PUBLIC BOOLEAN ← TRUE; -- checksums on for everybody driverLoopback: BOOLEAN ← FALSE; -- loopback in router stormy: BOOLEAN ← FALSE; -- storms for debugging are not on routersFunction: PUBLIC SpecialCommunication.RoutersFunction ← vanillaRouting; myHostID: SpecialSystem.HostNumber; -- processor ID of this system element initialSpareSocketID: SpecialSystem.SocketNumber = [1001]; initialTransportControl: OISCPTypes.TransPortControl = [trace: FALSE, filler: 0, hopCount: 0]; -- oiscp router object for the dispatcher. There will only be one for all MDSs, but it -- can live on all global frame instances or we can put it in hyperspace and have the -- dispatcher access it via a long pointer. The former is preferable. oiscpRouter: DriverDefs.RouterObject ← [input: LOOPHOLE[ReceivePacket], broadcast: LOOPHOLE[SendBroadcastPacketToCorrectNet], addNetwork: Router.AddNetwork, removeNetwork: Router.RemoveNetwork, stateChanged: Router.StateChanged]; -- monitor protected data. -- parameters for killing packets for debugging lightning: INTEGER ← 30; bolt: INTEGER ← 10; -- I think only the following need be in outerspace, accesible via a pointer since these -- will be touched by multiple MDS processes via their copy of RouterImpl and SocketImpl. socketRouterLock: PUBLIC MONITORLOCK; -- lock for the router and all the sockets. -- Some day the socketIDs in use will be remembered across wrap around, and maybe -- the rebooting of Pilot. spareSocketID: SpecialSystem.SocketNumber; -- socket table socketTable: Router.SocketTable; -- various Glitches generated by the router IllegalOisPktLength: ERROR = CODE; -- Cool Procedures -- This procedure assigns a temporary socket number in an SpecialSystem.NetworkAddress. -- Some day the active socket numbers in use will be kept around, so that on wrap -- around an unused number will be assigned. When the system element is connected to -- more than one network, this procedure must return a list of SpecialSystem.NetworkAddress. AssignOisAddress: PUBLIC ENTRY PROCEDURE RETURNS [localAddr: SpecialSystem.NetworkAddress] = BEGIN localAddr ← [net: Router.FindDestinationRelativeNetID[SpecialSystem.nullNetworkNumber], host: myHostID, socket: spareSocketID]; IF (spareSocketID ← [spareSocketID + 1]) = SpecialSystem.SocketNumber[0] THEN spareSocketID ← initialSpareSocketID; END; -- AssignOisAddress -- This procedure is just like the previous one except that the network number is relative -- to the destination network. That is, we pick that one of our locally connected -- networks that is the best way to get to destNet, with the hope that it is the best -- way to get from destNet to us. AssignDestinationRelativeOisAddress: PUBLIC ENTRY PROCEDURE [destNet: SpecialSystem.NetworkNumber] RETURNS [localAddr: SpecialSystem.NetworkAddress] = BEGIN localAddr ← [net: Router.FindDestinationRelativeNetID[destNet], host: myHostID, socket: spareSocketID]; IF (spareSocketID ← [spareSocketID + 1]) = SpecialSystem.SocketNumber[0] THEN spareSocketID ← initialSpareSocketID; END; -- AssignDestinationRelativeOisAddress -- This procedure tells the OISCP Router about a new socket. AddSocket: PUBLIC ENTRY PROCEDURE [sH: SocketHandle] = BEGIN MaybeIncreaseDriversBuffers: PROCEDURE = INLINE BEGIN previousSH: SocketHandle ← socketTable.first; UNTIL previousSH = NIL DO IF previousSH.pool.total > 0 THEN RETURN; previousSH ← previousSH.next; ENDLOOP; DriverDefs.ChangeNumberOfInputBuffers[TRUE]; -- TRUE => increase END; -- add new socket to the head of the table IF sH.pool.total > 0 THEN MaybeIncreaseDriversBuffers[]; sH.next ← socketTable.first; socketTable.first ← sH; socketTable.length ← socketTable.length + 1; END; -- AddSocket -- This procedure removes a socket from the OISCP Router's tables. RemoveSocket: PUBLIC ENTRY PROCEDURE [sH: SocketHandle] = BEGIN MaybeDecreaseDriversBuffers: PROCEDURE = INLINE BEGIN previousSH ← socketTable.first; UNTIL previousSH = NIL DO IF previousSH.pool.total > 0 THEN RETURN; previousSH ← previousSH.next; ENDLOOP; DriverDefs.ChangeNumberOfInputBuffers[FALSE]; -- FALSE => decrease END; previousSH: SocketHandle; IF socketTable.first = sH THEN socketTable.first ← sH.next ELSE BEGIN previousSH ← socketTable.first; UNTIL previousSH = NIL DO IF previousSH.next = sH THEN BEGIN previousSH.next ← sH.next; EXIT; END; previousSH ← previousSH.next; ENDLOOP; END; socketTable.length ← socketTable.length - 1; IF sH.pool.total > 0 THEN MaybeDecreaseDriversBuffers[]; END; -- RemoveSocket -- This is not an entry procedure because we change it only for debugging and can -- live with a race condition. SetOisStormy: PUBLIC PROCEDURE [new: BOOLEAN] = BEGIN stormy ← new; END; -- SetOisStormy -- This is not an entry procedure because we change it only for debugging and can -- live with a race condition. SetOisCheckit: PUBLIC PROCEDURE [new: BOOLEAN] = BEGIN checkIt ← new; END; -- SetOisCheckIt -- This is not an entry procedure because we change it only for debugging and can -- live with a race condition. SetOisDriverLoopback: PUBLIC PROCEDURE [new: BOOLEAN] = BEGIN driverLoopback ← new; END; -- SetOisDriverLoopback -- This procedure returns the processor ID of this machine. FindMyHostID: PUBLIC PROCEDURE RETURNS [SpecialSystem.HostNumber] = BEGIN RETURN[myHostID]; END; -- FindMyHostID GetOisPacketTextLength: PUBLIC PROCEDURE [b: BufferDefs.OisBuffer] RETURNS [CARDINAL] = BEGIN RETURN[b.ois.pktLength - bytesPerPktHeader]; END; -- GetOisPacketTextLength SetOisPacketTextLength: PUBLIC PROCEDURE [b: BufferDefs.OisBuffer, len: CARDINAL] = BEGIN IF len IN [0..bytesPerPktText] THEN b.ois.pktLength ← len + bytesPerPktHeader ELSE IF CommFlags.doDebug THEN Glitch[IllegalOisPktLength]; END; -- SetOisPacketTextLength SetOisPacketLength: PUBLIC PROCEDURE [b: BufferDefs.OisBuffer, len: CARDINAL] = BEGIN IF len IN [bytesPerPktHeader..maxBytesPerPkt] THEN b.ois.pktLength ← len ELSE IF CommFlags.doDebug THEN Glitch[IllegalOisPktLength]; END; -- SetOisPacketLength -- Hot Procedures -- This procedure transmits a packet over a locally connected network. -- The procedure assumes that all fields of the buffer have been filled in appropriately, -- except the encapsulation and buffer length which depend on the network. If the -- packet is destined for a local socket and we do NOT want to do loopback at the -- driver or at the network, then it gets looped back here. Broadcast -- packets are NOT delivered to the source socket. -- Packets are no longer copied into system buffers. -- The caller owns the buffer. The buffer is asynchronously sent and retruned to the -- caller process by the dispatcher using b.requeueProcedure. SendPacket: PUBLIC PROCEDURE [b: BufferDefs.OisBuffer] = BEGIN IF CommFlags.doStorms AND stormy AND PacketHit[] THEN BEGIN DriverDefs.PutOnGlobalDoneQueue[b]; RETURN; -- for debugging only END; IF b.ois.destination.host = myHostID AND NOT driverLoopback THEN -- packet is for a local socket; broadcast packets are not delivered locally BEGIN IF NOT DeliveredToLocalSocket[b, TRUE] THEN -- TRUE -> copy this buffer BEGIN nullNetwork: Network = NIL; -- because of EXPORTed TYPEs bug -- no socket IF CommFlags.doStats THEN StatIncr[statJunkOisForUsNoLocalSocket]; b.network ← nullNetwork; SendErrorPacket[b, noSocketOisErrorCode, 0]; END; -- we requeue here because we shorted the drivers and dispatcher. -- we Yield here to prevent local communication from using all of the cycles. DriverDefs.PutOnGlobalDoneQueue[b]; Process.Yield[]; END ELSE -- packet is for a remote machine BEGIN -- If the destination net is inaccessible then we will have suffered the overhead of -- computing a checksum, but we don't expect inaccessible nets that often. -- set the checksum if appropriate b.ois.transCntlAndPktTp.transportControl ← initialTransportControl; IF checkIt THEN Checksums.SetChecksum[b] ELSE b.ois.checksum ← 177777B; [] ← Router.FindNetworkAndTransmit[b]; END; END; -- SendPacket -- This procedure is called by the Dispatcher when it sees a valid ois packet in a system -- buffer. The procedure checks the checksum field and then routes the packet to a -- local socket. If this is an internetwork router, then the packets are forwarded over -- a suitable network. We now own this packet. ReceivePacket: PUBLIC PROCEDURE [b: BufferDefs.OisBuffer] = BEGIN badChecksum: BOOLEAN; incomingNet: Network; destHost: SpecialSystem.HostNumber ← b.ois.destination.host; broadcastPacket: BOOLEAN ← FALSE; IF b.ois.pktLength < OISCPTypes.bytesPerPktHeader THEN BEGIN IF CommFlags.doStats THEN StatIncr[statOisDiscarded]; DriverDefs.PutOnGlobalDoneQueue[b]; -- done with this packet RETURN; END; IF (badChecksum ← IF checkIt THEN NOT Checksums.TestChecksum[b] ELSE FALSE) THEN -- hardware says ok, but bad end-to-end software checksum. BEGIN IF CommFlags.doStats THEN BEGIN StatIncr[statReceivedBadOisChecksum]; StatIncr[statOisDiscarded]; END; DriverDefs.PutOnGlobalDoneQueue[b]; -- done with this packet RETURN; END; -- packet is in good shape, route it. IF CommFlags.doStorms AND stormy AND PacketHit[] THEN BEGIN DriverDefs.PutOnGlobalDoneQueue[b]; RETURN; END; -- for debugging only incomingNet ← b.network; IF destHost = myHostID OR ((broadcastPacket←(destHost = allHostIDs)) AND (incomingNet.netNumber = b.ois.destination.net) OR (b.ois.destination.net = SpecialSystem.nullNetworkNumber)) THEN BEGIN -- incomming packet for us (we may have broadcast it) IF NOT DeliveredToLocalSocket[b, FALSE] THEN -- FALSE -> do not copy this buffer, but deliver it BEGIN -- routing information socket is part of router in another module IF b.ois.destination.socket = OISCPConstants.routingInformationSocket THEN Router.RoutingInformationPacket[b] -- we still own this packet! ELSE BEGIN -- packet for unknown socket IF NOT broadcastPacket THEN BEGIN SendErrorPacket[b, noSocketOisErrorCode, 0]; IF CommFlags.doStats THEN StatIncr[statJunkOisForUsNoLocalSocket]; END ELSE IF CommFlags.doStats THEN StatIncr[statJunkBroadcastOis] END; DriverDefs.PutOnGlobalDoneQueue[b]; -- done with this packet END; END ELSE SELECT TRUE FROM routersFunction=interNetworkRouting => BEGIN Router.ForwardPacket[b]; -- dispatcher returns to system pool END; b.ois.destination.socket=OISCPConstants.routingInformationSocket => BEGIN Router.RoutingInformationPacket[b]; -- we still own this packet! DriverDefs.PutOnGlobalDoneQueue[b]; END; ENDCASE => BEGIN IF CommFlags.doStats THEN StatIncr[statOisDiscarded]; DriverDefs.PutOnGlobalDoneQueue[b]; -- we got the packet when we shouldn't have! END; END; -- ReceivePacket -- Hot, except should really not exist; therefor cold? -- This procedure attempts to hit a packet with a bolt of lightening, and if it succeeds, -- then it returns true else false. The caller dispenses with the buffer. PacketHit: ENTRY PROCEDURE RETURNS [BOOLEAN] = BEGIN IF (lightning ← lightning + 1) > bolt OR lightning < 0 THEN BEGIN IF lightning > bolt THEN BEGIN IF bolt > 100 THEN BEGIN randLong: LONG CARDINAL ← System.GetClockPulses[]; rand: CARDINAL ← Inline.LowHalf[randLong]; lightning ← -INTEGER[rand MOD 20B]; bolt ← 10; END ELSE BEGIN lightning ← 0; bolt ← bolt + 1; END; END; IF CommFlags.doStats THEN StatIncr[statZappedP]; RETURN[TRUE]; END ELSE RETURN[FALSE]; END; -- PacketHit -- This procedure finds a local socket object to deliver the packet to, and if it succeeds, -- then it returns true else false. Caller retains ownership of buffer if useCopy is true -- or return is false; caller loses buffer ownership if NOT useCopy and return is true. DeliveredToLocalSocket: ENTRY PROCEDURE [b: BufferDefs.OisBuffer, useCopy: BOOLEAN] RETURNS [BOOLEAN] = BEGIN ENABLE UNWIND => NULL; destSocket: SpecialSystem.SocketNumber ← b.ois.destination.socket; sH, prevSH: SocketHandle; -- find the correct socket for this packet FOR sH ← (prevSH ← socketTable.first), sH.next UNTIL sH = NIL DO IF sH.localAddr.socket = destSocket THEN BEGIN IF useCopy THEN EnqueueCopyOfNewInput[sH, b] ELSE EnqueueNewInput[sH, b]; -- do some dynamic socket ordering before returning; rearange the list only if -- sH was not in the first two entries of the list. IF prevSH # socketTable.first THEN BEGIN prevSH.next ← sH.next; -- this removes sH from the list sH.next ← socketTable.first; socketTable.first ← sH; -- this puts sH at head of list END; RETURN[TRUE]; END; prevSH ← sH; ENDLOOP; RETURN[FALSE]; END; -- DeliveredToLocalSocket -- This procedure enqueues a new input packet at the socket object. -- The incoming buffer is from a network driver and NOT a local socket. -- The caller of this routine relinquishes ownership of this buffer. EnqueueNewInput: INTERNAL PROCEDURE [sH: SocketHandle, b: BufferDefs.OisBuffer] = INLINE BEGIN IF (sH.channelState = aborted) OR NOT (OISCP.CreditReceiveOisBuffer[sH.pool, b]) THEN BEGIN IF CommFlags.doStats THEN StatsDefs.StatIncr[statOisInputQueueOverflow]; DriverDefs.PutOnGlobalDoneQueue[b]; RETURN; END; b.status ← LOOPHOLE[Router.XmitStatus[goodCompletion]]; EnqueueOis[@sH.completedUserGetQueue, b]; BROADCAST sH.newUserInput; END; -- EnqueueNewInput -- This procedure enqueues a new input packet at the socket object. -- The incoming buffer is only from a local socket. -- The caller of this routine retains ownership of this buffer. EnqueueCopyOfNewInput: INTERNAL PROCEDURE [ sH: SocketHandle, b: BufferDefs.OisBuffer] = INLINE BEGIN getBuffer: BufferDefs.OisBuffer ← IF sH.channelState = aborted THEN NIL ELSE MaybeGetFreeReceiveOisBufferFromPool[sH.pool]; -- Copy b into the first OisBuffer on the pendingGetQueue, if there is one. IF (getBuffer # NIL) THEN BEGIN getBuffer.status ← LOOPHOLE[Router.XmitStatus[goodCompletion]]; -- assume always good -- getBuffer is full sized OisBuffer, therefore no need to check length CommUtilDefs.CopyLong[ from: @b.ois.checksum, nwords: (b.ois.pktLength + 1)/2, to: @getBuffer.ois.checksum]; EnqueueOis[@sH.completedUserGetQueue, getBuffer]; BROADCAST sH.newUserInput; END ELSE IF CommFlags.doStats THEN StatsDefs.StatIncr[statOisInputQueueOverflow]; -- funny name END; -- EnqueueNewLocalInput -- This procedure causes a broadcast packet to be sent over all networks. BroadcastThisPacket: PUBLIC PROCEDURE [b: BufferDefs.OisBuffer] = BEGIN network: Network; IF CommFlags.doStats THEN StatIncr[statOisBroadcast]; b.allNets ← TRUE; -- this is where it gets turned on b.ois.destination.host ← SpecialSystem.broadcastHostNumber; b.network ← network ← DriverDefs.GetDeviceChain[]; IF network = NIL THEN BEGIN DriverDefs.PutOnGlobalDoneQueue[b]; IF CommFlags.doStats THEN StatsDefs.StatIncr[statOisSentNowhere]; RETURN; END; SendBroadcastPacketToCorrectNet[b]; END; -- BroadcastThisPacket -- This procedure causes a broadcast packet to be sent out over the right network. SendBroadcastPacketToCorrectNet: PUBLIC PROCEDURE [b: BufferDefs.OisBuffer] = BEGIN network: Network ← b.network; IF ~network.alive OR (b.bypassZeroNet AND network.netNumber = [0, 0]) THEN BEGIN DriverDefs.PutOnGlobalDoneQueue[b]; RETURN; END; -- goes (slowly) around in circles b.ois.destination.net ← b.ois.source.net ← network.netNumber; b.ois.source.host ← myHostID; IF checkIt THEN Checksums.SetChecksum[b] ELSE b.ois.checksum ← 177777B; LOOPHOLE[b.status, DriverDefs.DriverXmitStatus] ← goodCompletion; network.encapsulateOis[b, SpecialSystem.broadcastHostNumber]; network.sendBuffer[b]; END; -- SendBroadcastPacketToCorrectNet -- This procedure generates and sends an error packet. All or most of the -- offending packet is copied into the the error packet. The caller of this -- Procedure still owns offendingPkt. SendErrorPacket: PUBLIC PROCEDURE [offendingPkt: BufferDefs.OisBuffer, errCode: OISCPTypes.OISErrorCode, errParm: CARDINAL] = BEGIN net: Network = offendingPkt.network; b: BufferDefs.OisBuffer; offenseLen: CARDINAL; IF offendingPkt.ois.transCntlAndPktTp.packetType=error THEN RETURN; -- don't send errors about errors IF (b ← DriverDefs.MaybeGetFreeOisBuffer[])=NIL THEN RETURN; -- give up! b.ois.destination ← offendingPkt.ois.source; b.ois.source ← [ IF net=NIL THEN OISCP.unknownNetID ELSE net.netNumber, myHostID, OISCP.unknownSocketID]; b.ois.transCntlAndPktTp ← [initialTransportControl, error]; b.ois.errorType ← errCode; b.ois.errorParameter ← errParm; offenseLen ← MIN[offendingPkt.ois.pktLength, OISCPTypes.bytesPerPktText-4]; -- four is for errorType and errorParameter. CommUtilDefs.CopyLong[@offendingPkt.ois, (offenseLen+1)/2, @b.ois.errorBody]; SetOisPacketTextLength[b, offenseLen+4]; SendPacket[b]; END; -- SendErrorPacket --Cold Procedures -- This procedure turns the router on. The Communication software is written with the -- idea of eventually turning the code on and off during execution, and so we should -- get the latest values of netID and hostID when being turned back on. OisRouterOn: PUBLIC PROCEDURE = BEGIN OisRouterActivate[]; Router.RoutingTableOn[]; DriverDefs.SetOisRouter[@oiscpRouter]; END; -- OisRouterOn OisRouterActivate: ENTRY PROCEDURE = INLINE BEGIN myHostID ← SpecialSystem.GetProcessorID[]; IF primaryMDS THEN socketTable ← [length: 0, first: NIL]; END; -- OisRouterActivate OisRouterOff: PUBLIC PROCEDURE = BEGIN DriverDefs.SetOisRouter[NIL]; OisRouterDeactivate[]; Router.RoutingTableOff[]; END; -- OisRouterOff OisRouterDeactivate: ENTRY PROCEDURE = INLINE BEGIN -- cleanup the socket table. --IF primaryMDS THEN END; -- OisRouterDeactivate --Cold -- initialization IF primaryMDS THEN spareSocketID ← initialSpareSocketID; END. -- RouterImpl module. LOG Time: January 19, 1980 4:05 PM By: Dalal Action: Split OISCPRouter into two. Time: January 21, 1980 6:07 PM By: Dalal Action: one lock for SocketImpl and RouterImpl. Time: March 13, 1980 4:55 PM By: BLyon Action: modified SendPacket. Time: March 18, 1980 4:09 PM By: BLyon Action: Modified EnqueueNewInput where it gets an input buffer. Time: May 12, 1980 6:38 PM By: BLyon Action: Put checksum into microcode (switched order parameters too). Time: May 16, 1980 10:07 AM PM By: BLyon Action: Removed all ShortenPointer to allow multiple MDS. Time: June 30, 1980 1:02 PM By: BLyon Action: Checkit init to FALSE instead of TRUE.. Time: July 22, 1980 11:03 AM By: BLyon Action: Checkit changed back to TRUE; checksums stuff put in seperate modules; we now receive out own broadcasts. Time: August 1, 1980 1:29 PM By: BLyon Action: replaced internetRouter by routersFunction. Time: September 13, 1980 6:15 PM By: HGM Action: Add StateChanged. Time: September 18, 1980 3:34 PM By: BLyon Action: AssignOisAddress puts unknownNetID in network field rather than primaryNetID. Time: February 24, 1981 3:25 PM By: BLyon Action: put extra clause in ReceivePacket so that an INR would forward a broadcast instead of eating it with a local socket.