-- File: BufferMgr.mesa
--   Edit by: BLyon on: March 19, 1981  12:37 PM
--   Edit by: HGM on: March 17, 1981  6:32 PM
--   Edit by: Garlick on: January 26, 1981  1:52 PM

DIRECTORY
  BufferDefs USING [
    defaultDataWordsPerSystemBuffer, BufferAccessHandle, BufferAccessObject,
    BufferFunction, BufferType, Buffer, PupBuffer, OisBuffer, SppBuffer,
    BufferObject, Queue, QueueObject, wordsPerNonVarientBufferOverhead],
  CommFlags USING [doDebug, doStats],
  CommUtilDefs USING [
    AllocateBuffers, FreeBuffers, LockBuffers, UnlockBuffers, AllocateIocbs,
    FreeIocbs, GetReturnFrame, MaybeShorten],
  DriverDefs USING [Glitch, GetGiantVector, Network],
  DriverTypes USING [bufferSeal, bufferPoolSeal, queueSeal],
  Heap USING [FreeNode, MakeNode],
  Inline USING [BITAND, LowHalf],
  OISCP USING [],
  OISCPTypes USING [BufferBody, wordPerPktHeader, wordsPerLevel2SppHeader],
  Process USING [InitializeCondition, MsecToTicks],
  PupDefs USING [],
  StatsDefs USING [StatBump, StatIncr],
  System USING [GetClockPulses, Pulses, PulsesToMicroseconds];

BufferMgr: MONITOR
  IMPORTS CommUtilDefs, DriverDefs, Heap, Inline, Process, StatsDefs, System
  EXPORTS BufferDefs, DriverDefs, OISCP, PupDefs
  SHARES BufferDefs =
  BEGIN OPEN BufferDefs, DriverDefs;

  -- EXPORTed TYPEs
  Network: PUBLIC TYPE = DriverDefs.Network;

  -- monitor protected data
  accessHandleChainHead: BufferDefs.BufferAccessHandle ← NIL;
  systemAccessHandle: PUBLIC BufferDefs.BufferAccessHandle ← NIL;
  systemFreeQueueNotEmpty: CONDITION;
  systemBufferQueue: Queue;
  systemDataWordsPerBuffer: CARDINAL ← defaultDataWordsPerSystemBuffer;
  systemBufferSize: CARDINAL ← 0;
  -- the raw buffer data length that drivers need to know (includes encapsulation words)
  useCount: CARDINAL ← 0;
  wordsPerIocb: CARDINAL ← 0;
  totalSendAndReceiveBuffers: CARDINAL ← 0;
  totalSendAndReceiveBuffersInUse: CARDINAL ← 0;
  totalReserveBuffers: CARDINAL ← 0;
  inactivePools: CARDINAL ← 0;
  enqueue: PROCEDURE [Queue, Buffer] ← Enqueue;

  -- Buffers and IOCB locations must be rounded up for alignment constraints.
  -- Alto SLA Microcode needs IOCB/LCB to be EVEN.
  -- D0 Ethernet/Xwire Microcode needs IOCB to be Quad word aligned,
  -- and first data word to be (almost?) QUAD word aligned.

  -- size of a buffer without any data
  rawOverhead: CARDINAL = BufferDefs.wordsPerNonVarientBufferOverhead;
  oisOverhead: CARDINAL = rawOverhead + OISCPTypes.wordPerPktHeader;
  sppOisOverhead: CARDINAL = oisOverhead + OISCPTypes.wordsPerLevel2SppHeader;
  -- 1 extra for pup checksum
  pupOverhead: CARDINAL = SIZE[pupWords pup BufferDefs.BufferObject] + 1;
  overhead: CARDINAL = MAX[
    pupOverhead,
    oisOverhead,
    sppOisOverhead];

  -- for the Glitches
  QueueSealBroken: PUBLIC ERROR = CODE;
  PoolSealBroken: PUBLIC ERROR = CODE;
  BufferSealBroken: PUBLIC ERROR = CODE;
  FreeQueueNotInitialized: PUBLIC ERROR = CODE;
  BufferPoolNotInitialized: PUBLIC ERROR = CODE;
  QueueScrambled: PUBLIC ERROR = CODE;
  CantResetWhileActive: PUBLIC ERROR = CODE;
  SystemBufferSizeConfused: PUBLIC ERROR = CODE;
  DontKnowHowToAllocateBuffer: PUBLIC ERROR = CODE;

  -- Cold Procedures

  AdjustBufferSize: PUBLIC ENTRY PROCEDURE [bufferSize: CARDINAL] =
    BEGIN
    IF useCount # 0 THEN Glitch[CantResetWhileActive];
    IF bufferSize # 0 THEN systemDataWordsPerBuffer ← bufferSize;
    systemBufferSize ← 0;
    END;

  GetBufferSize: PUBLIC PROCEDURE RETURNS [bufferSize: CARDINAL] =
    BEGIN RETURN[systemDataWordsPerBuffer]; END;

  GetWordsPerIocb: PUBLIC PROCEDURE RETURNS [CARDINAL] =
    BEGIN RETURN[wordsPerIocb]; END;

  SetWordsPerIocb: PUBLIC PROCEDURE [new: CARDINAL] =
    BEGIN wordsPerIocb ← new; END;

  -- NB: wordsPerBuffer and wordsPerIocb MUST be QuadWord multiples

  MakeBufferPool: PUBLIC ENTRY PROCEDURE [
    total: CARDINAL, -- number of new buffer that will actually be created   
    send: CARDINAL,
    -- number of send type buffer that can be allocated from this pool (could be greater than total)   
    receive: CARDINAL,
    -- number of receive type buffer that can be allocated from this pool (could be greater than total)   
    reserve: CARDINAL,
    -- number of send type buffer that can be allocated from this pool (could be greater than total)   
    forSystemUse: BOOLEAN] -- as opposed to for socket use 
     RETURNS [p: BufferAccessHandle] =
    BEGIN
    b: Buffer;
    x: --SHORT--POINTER TO BufferObject = NIL;
    i: CARDINAL;
    newSystemBufferSize: CARDINAL;
    IF NOT forSystemUse THEN
      BEGIN
      totalSendAndReceiveBuffers ← totalSendAndReceiveBuffers + send + receive;
      END;
    totalReserveBuffers ← totalReserveBuffers + reserve;
    useCount ← useCount + 1;
    p ← Heap.MakeNode[n: SIZE[BufferAccessObject]];
    p.seal ← DriverTypes.bufferPoolSeal;
    p.active ← TRUE;
    p.madeForSystem ← forSystemUse;
    p.filler ← 0;
    p.total ← total;
    p.sendInUse ← p.receiveInUse ← p.recovered ← 0;
    p.send ← send;
    p.receive ← receive;
    p.reserve ← reserve;
    -- insert this accessObject into the linked list of known accessObjects
    p.next ← accessHandleChainHead;
    accessHandleChainHead ← p;
    IF CommFlags.doDebug THEN DriverDefs.GetGiantVector[].firstBufferAccessHandle ← accessHandleChainHead;
    Process.InitializeCondition[
      CommUtilDefs.MaybeShorten[@p.bufferAvailable], Process.MsecToTicks[10000]];
    IF p.total = 0 THEN
      BEGIN
      p.firstBuffer ← LOOPHOLE[p];
      --  kludgy, but lets us do sanity check for nil values
      RETURN;
      END;
    -- 2 for checksum, 4 for end test, 3 for round down
    p.wordsPerBuffer ← systemDataWordsPerBuffer + overhead + 2 + 4 + 3;
    UNTIL Inline.BITAND[p.wordsPerBuffer, 3] = 0 DO
      p.wordsPerBuffer ← p.wordsPerBuffer + 1; ENDLOOP;
    -- systemBufferSize is a little bigger because of the quad word allignment
    newSystemBufferSize ←
      p.wordsPerBuffer - (@x.encapsulation - LOOPHOLE[x, POINTER]);
    IF systemBufferSize = 0 THEN systemBufferSize ← newSystemBufferSize
    ELSE
      IF newSystemBufferSize # systemBufferSize THEN
	Glitch[SystemBufferSizeConfused];
    p.firstBuffer ← CommUtilDefs.AllocateBuffers[p.wordsPerBuffer*total + 3];
    CommUtilDefs.LockBuffers[p.firstBuffer];
    -- This determines the buffer alignment: see DriverTypes.Encapsulation
    UNTIL Inline.BITAND[Inline.LowHalf[@p.firstBuffer.encapsulation], 3] = 3 DO
      p.firstBuffer ← p.firstBuffer + 1; ENDLOOP;
    b ← p.firstBuffer;
    FOR i IN [0..total) DO
      b.iocbChain ← NIL;
      b.allNets ← b.bypassZeroNet ← FALSE;
      b.type ← raw;
      b.pupLength ← b.length ← 0;
      b.pupType ← last;
      b.queue ← NIL;
      b.pool ← p;
      b.next ← NIL;
      IF CommFlags.doDebug THEN b.seal ← DriverTypes.bufferSeal;
      b.requeueProcedure ← ReturnFreeBuffer;
      Enqueue[systemBufferQueue, b];
      b ← b + p.wordsPerBuffer;
      ENDLOOP;
    IF wordsPerIocb # 0 THEN
      BEGIN
      iocb: LONG POINTER;
      iocb ← CommUtilDefs.AllocateIocbs[wordsPerIocb*total + 3];
      b ← p.firstBuffer;
      FOR i IN [0..total) DO
	-- this does NOT align each individual iocb
	b.iocbChain ← iocb;
	iocb ← iocb + wordsPerIocb;
	b ← b + p.wordsPerBuffer;
	ENDLOOP;
      END;
    END;

  DestroyBufferPoolLocked: PROCEDURE [p: BufferAccessHandle] =
    BEGIN
    IF CommFlags.doDebug THEN
      BEGIN
      b: Buffer ← p.firstBuffer;
      FOR i: CARDINAL IN [0..p.total) DO
	IF b.queue # NIL THEN Glitch[QueueScrambled];
	b ← b + p.wordsPerBuffer;
	ENDLOOP;
      END;
    IF p.total # 0 THEN
      BEGIN
      CommUtilDefs.UnlockBuffers[p.firstBuffer];
      IF p.firstBuffer.iocbChain # NIL THEN
	CommUtilDefs.FreeIocbs[p.firstBuffer.iocbChain];
      CommUtilDefs.FreeBuffers[p.firstBuffer];
      END;
    IF NOT p.madeForSystem THEN
      totalSendAndReceiveBuffers ←
	totalSendAndReceiveBuffers - (p.send + p.receive - p.total);
    p.firstBuffer ← NIL;
    -- remove this accessObject from the linked list of known accessObjects
    IF accessHandleChainHead=p THEN
      BEGIN
      accessHandleChainHead ← p.next;
    IF CommFlags.doDebug THEN DriverDefs.GetGiantVector[].firstBufferAccessHandle ← accessHandleChainHead;
      END
    ELSE
      BEGIN
      prev: BufferDefs.BufferAccessHandle ← accessHandleChainHead;
      WHILE (prev#NIL) AND (prev.next#p) DO
        prev ← prev.next;
        ENDLOOP;
      IF prev=NIL THEN ERROR;
      prev.next ← p.next;
      END;
    p.next ← NIL;
    Heap.FreeNode[p: p];
    useCount ← useCount - 1;
    IF (inactivePools ← inactivePools - 1) = 0 AND enqueue =
      EnqueueActiveBuffersOnly THEN
      BEGIN
      IF CommFlags.doStats THEN
	StatsDefs.StatBump[
	  statRecyclingZombieBuffersTime, Inline.LowHalf[
	    System.PulsesToMicroseconds[[System.GetClockPulses[]-startReCyclingTime]]/1000]];
      enqueue ← Enqueue;
      END;
    END;

  startReCyclingTime: System.Pulses;

  FreeBufferPool: PUBLIC ENTRY PROCEDURE [p: BufferAccessHandle] =
    BEGIN
    b: Buffer ← p.firstBuffer;
    IF CommFlags.doDebug AND (useCount = 0 OR p = NIL) THEN Glitch[FreeQueueNotInitialized];
    IF CommFlags.doDebug AND p.seal # DriverTypes.bufferPoolSeal THEN
      Glitch[PoolSealBroken];
    IF CommFlags.doDebug AND p.firstBuffer = NIL THEN Glitch[BufferPoolNotInitialized];
    -- since sends are asynchronous, don't free the pool until they are completed
    IF NOT p.madeForSystem THEN
      BEGIN
      WHILE p.sendInUse>0 DO
        WAIT systemFreeQueueNotEmpty;  -- any short CV will do
        ENDLOOP;
      END;
    p.active ← FALSE;
    inactivePools ← inactivePools + 1;
    totalReserveBuffers ← totalReserveBuffers - p.reserve;
    p.recovered ← 0;
    FOR i: CARDINAL IN [0..p.total) DO
      IF b.queue = systemBufferQueue THEN
	BEGIN
	p.recovered ← p.recovered + 1;
	IF NOT p.madeForSystem THEN
	  totalSendAndReceiveBuffers ← totalSendAndReceiveBuffers - 1;
	IF ExtractFromQueue[systemBufferQueue, b] # b THEN Glitch[QueueScrambled];
	END;
      b ← b + p.wordsPerBuffer;
      ENDLOOP;
    IF p.recovered = p.total THEN
      DestroyBufferPoolLocked[p]  -- all buffers recovered, so destroy pool.
    ELSE
      BEGIN
      IF CommFlags.doStats AND enqueue # EnqueueActiveBuffersOnly THEN
	startReCyclingTime ← System.GetClockPulses[];
      enqueue ← EnqueueActiveBuffersOnly; -- continue trying to recover buffers
      END;
    END;

  EnqueueActiveBuffersOnly: PROCEDURE [q: Queue, b: Buffer] =
    BEGIN
    aH: BufferAccessHandle = b.pool;
    IF aH.active THEN Enqueue[q, b]
    ELSE
      BEGIN
      IF b = NIL OR b.queue # NIL THEN Glitch[QueueScrambled];
      IF CommFlags.doDebug AND q.seal # DriverTypes.queueSeal THEN Glitch[QueueSealBroken];
      IF CommFlags.doDebug AND b.seal # DriverTypes.bufferSeal THEN
	Glitch[BufferSealBroken];
      b.next ← NIL;
      aH.recovered ← aH.recovered + 1;
      IF NOT aH.madeForSystem THEN
	totalSendAndReceiveBuffers ← totalSendAndReceiveBuffers - 1;
      IF aH.recovered = aH.total THEN DestroyBufferPoolLocked[aH];
      END;
    END;

  -- Cool Procedures

  -- This is not an ENTRY procedure because we assume that systemBufferPool can
  -- have its parameters set only once.

  DataWordsPerPupBuffer: PUBLIC PROCEDURE RETURNS [CARDINAL] =
    BEGIN RETURN[systemDataWordsPerBuffer + (overhead - pupOverhead)]; END;

  -- This is not an ENTRY procedure because we assume that systemBufferPool can
  -- have its parameters set only once.

  DataWordsPerRawBuffer: PUBLIC PROCEDURE RETURNS [CARDINAL] =
    BEGIN RETURN[systemDataWordsPerBuffer + (overhead - rawOverhead)]; END;

  -- This is not an ENTRY procedure because we assume that systemBufferPool can
  -- have its parameters set only once.

  DataWordsPerOisBuffer: PUBLIC PROCEDURE RETURNS [CARDINAL] =
    BEGIN
    RETURN[systemDataWordsPerBuffer + (overhead - oisOverhead)];
    END;

  -- This is not an ENTRY procedure because we assume that systemBufferPool can
  -- have its parameters set only once.

  DataWordsPerSppBuffer: PUBLIC PROCEDURE RETURNS [CARDINAL] =
    BEGIN
    RETURN[systemDataWordsPerBuffer + (overhead - sppOisOverhead)];
    END;

  -- Hot Procedures

  SendAndReceiveBuffersInFreeQueue: PROCEDURE RETURNS [CARDINAL] = INLINE
    BEGIN
    RETURN[
      IF totalSendAndReceiveBuffersInUse >= totalSendAndReceiveBuffers THEN 0
      ELSE totalSendAndReceiveBuffers - totalSendAndReceiveBuffersInUse];
    END;

  -- This routine is only used by device drivers to get buffers to read things into.
  -- NB: b.length is setup for the size of the buffer, including ALL of the encapsulation.
  -- All device drivers except for the XWire must fudge things themsleves, since they do
  -- not use all of the encapsulation field.

  GetInputBuffer: PUBLIC ENTRY PROCEDURE [tryToWaitForBuffer: BOOLEAN]
    RETURNS [b: Buffer ← NIL] =
    BEGIN
    IF CommFlags.doDebug AND useCount = 0 THEN Glitch[FreeQueueNotInitialized];
    FOR i: CARDINAL IN [0..2) DO
      -- since the drivers only use the buffers for a short time, no access control
      -- is used in their allocation; a filled buffer will be subject to usual access control
      -- when it is delivered;  therefore if we have a buffer the drivers can have it.
      IF systemBufferQueue.length > 0 THEN
	BEGIN
	b ← Dequeue[systemBufferQueue];
	b.currentOwner ← systemAccessHandle;
	b.bufFunc ← systemUse;
	b.length ← systemBufferSize;
	b.type ← raw;
	IF CommFlags.doDebug THEN b.debug ← CommUtilDefs.GetReturnFrame[].accesslink;
	RETURN;
	END;
      IF (~tryToWaitForBuffer) OR (i > 0) THEN RETURN;
      WAIT systemFreeQueueNotEmpty;
      ENDLOOP;
    END;

  FillInBufferTypeInformation: PROCEDURE [
    b: Buffer, bufType: BufferType, bufFunc: BufferFunction] = INLINE
    BEGIN
    b.bufFunc ← bufFunc;
    SELECT bufType FROM
      oisSpp =>
	BEGIN
	sppBuf: LONG POINTER TO spp OISCPTypes.BufferBody = LOOPHOLE[b+rawOverhead];
	sppBuf.systemPacket ← sppBuf.sendAck ← sppBuf.attention ← sppBuf.endOfMessage ← FALSE;
	bufType ← ois;
	END;
      pup => b.pupType ← data;
      ENDCASE => NULL;
    b.type ← bufType;
    END;

  GetFreeBuffer: PUBLIC ENTRY PROCEDURE [
    bufType: BufferType, aH: BufferAccessHandle, bufFunc: BufferFunction]
    RETURNS [b: Buffer] =
    BEGIN
    ENABLE UNWIND => NULL;
    IF CommFlags.doDebug AND useCount = 0 THEN Glitch[FreeQueueNotInitialized];
    IF CommFlags.doDebug AND aH.seal # DriverTypes.bufferPoolSeal THEN
      Glitch[PoolSealBroken];
    IF CommFlags.doDebug AND aH.firstBuffer = NIL THEN Glitch[BufferPoolNotInitialized];
    IF aH = systemAccessHandle THEN
      BEGIN
      IF CommFlags.doDebug AND bufFunc # systemUse THEN Glitch[DontKnowHowToAllocateBuffer];
      IF CommFlags.doStats AND ~systemBufferQueue.length >
	totalReserveBuffers + SendAndReceiveBuffersInFreeQueue[] THEN
	StatsDefs.StatIncr[statBufferWaits];
      UNTIL systemBufferQueue.length >
	totalReserveBuffers + SendAndReceiveBuffersInFreeQueue[] DO
	WAIT systemFreeQueueNotEmpty; ENDLOOP;
      END
    ELSE
      BEGIN
      sendOrReceiveInUse: LONG POINTER TO CARDINAL;
      sendOrReceive: CARDINAL;
      SELECT bufFunc FROM
	send =>
	  BEGIN sendOrReceiveInUse ← @aH.sendInUse; sendOrReceive ← aH.send; END;
	receive =>
	  BEGIN
	  sendOrReceiveInUse ← @aH.receiveInUse;
	  sendOrReceive ← aH.receive;
	  END;
	ENDCASE => Glitch[DontKnowHowToAllocateBuffer];
      IF CommFlags.doStats AND
	(~sendOrReceiveInUse↑ < sendOrReceive OR systemBufferQueue.length = 0)
	THEN StatsDefs.StatIncr[statBufferWaits];
      UNTIL (sendOrReceiveInUse↑ < sendOrReceive) DO
	WAIT aH.bufferAvailable; ENDLOOP;
      sendOrReceiveInUse↑ ← sendOrReceiveInUse↑ + 1;
      totalSendAndReceiveBuffersInUse ← totalSendAndReceiveBuffersInUse + 1;
      UNTIL (systemBufferQueue.length > 0) DO
	WAIT systemFreeQueueNotEmpty; ENDLOOP;
      END;
    b ← Dequeue[systemBufferQueue];
    IF CommFlags.doDebug AND b = NIL THEN Glitch[QueueScrambled];
    FillInBufferTypeInformation[b, bufType, bufFunc];
    b.currentOwner ← aH;
    IF CommFlags.doDebug THEN b.debug ← CommUtilDefs.GetReturnFrame[].accesslink;
    END;

  -- Get free buffer, but don't wait if there is none
  -- NB: These will return the last buffer, use with caution

  MaybeGetFreeBuffer: PUBLIC ENTRY PROCEDURE [
    bufType: BufferType, aH: BufferAccessHandle, bufFunc: BufferFunction]
    RETURNS [b: Buffer] =
    BEGIN
    IF CommFlags.doDebug AND useCount = 0 THEN Glitch[FreeQueueNotInitialized];
    IF CommFlags.doDebug AND aH.seal # DriverTypes.bufferPoolSeal THEN
      Glitch[PoolSealBroken];
    IF CommFlags.doDebug AND aH.firstBuffer = NIL THEN Glitch[BufferPoolNotInitialized];
    IF aH = systemAccessHandle THEN
      BEGIN
      IF ~systemBufferQueue.length > SendAndReceiveBuffersInFreeQueue[] THEN
	BEGIN
	b ← NIL;
	IF CommFlags.doStats THEN StatsDefs.StatIncr[statBufferWaits];
	RETURN;
	END;
      IF CommFlags.doDebug AND bufFunc # systemUse THEN Glitch[DontKnowHowToAllocateBuffer];
      END
    ELSE
      BEGIN
      sendOrReceiveInUse: LONG POINTER TO CARDINAL;
      sendOrReceive: CARDINAL;
      SELECT bufFunc FROM
	send =>
	  BEGIN sendOrReceiveInUse ← @aH.sendInUse; sendOrReceive ← aH.send; END;
	receive =>
	  BEGIN
	  sendOrReceiveInUse ← @aH.receiveInUse;
	  sendOrReceive ← aH.receive;
	  END;
	ENDCASE => Glitch[DontKnowHowToAllocateBuffer];
      IF (~sendOrReceiveInUse↑ < sendOrReceive) OR (systemBufferQueue.length = 0)
	THEN
	BEGIN
	b ← NIL;
	IF CommFlags.doStats THEN StatsDefs.StatIncr[statBufferWaits];
	RETURN;
	END;
      sendOrReceiveInUse↑ ← sendOrReceiveInUse↑ + 1;
      totalSendAndReceiveBuffersInUse ← totalSendAndReceiveBuffersInUse + 1;
      END;
    IF (b ← Dequeue[systemBufferQueue]) = NIL THEN Glitch[QueueScrambled];
    FillInBufferTypeInformation[b, bufType, bufFunc];
    b.currentOwner ← aH;
    IF CommFlags.doDebug THEN b.debug ← CommUtilDefs.GetReturnFrame[].accesslink;
    END;

  -- credits may be used to avoid copying buffers

  CreditReceiveOisBuffer: PUBLIC ENTRY PROCEDURE [
    aH: BufferAccessHandle, b: OisBuffer] RETURNS [gotCreadit: BOOLEAN] =
    BEGIN
    IF CommFlags.doDebug AND (aH = NIL OR aH.firstBuffer = NIL) THEN
      Glitch[BufferPoolNotInitialized];
    IF CommFlags.doDebug AND aH.seal # DriverTypes.bufferPoolSeal THEN
      Glitch[PoolSealBroken];
    IF (gotCreadit ← aH.receiveInUse < aH.receive) THEN
      BEGIN
      aH.receiveInUse ← aH.receiveInUse + 1;
      totalSendAndReceiveBuffersInUse ← totalSendAndReceiveBuffersInUse + 1;
      b.currentOwner ← aH;
      b.bufFunc ← receive;
      END
    ELSE
      IF CommFlags.doStats THEN StatsDefs.StatIncr[statBufferWaits];
    END;

  NILNetwork: PROCEDURE RETURNS [Network] = INLINE {RETURN[NIL]};

  ReturnFreePupBuffer: PUBLIC PROCEDURE [PupBuffer] = LOOPHOLE[ReturnFreeBuffer];
  ReturnFreeOisBuffer: PUBLIC PROCEDURE [OisBuffer] = LOOPHOLE[ReturnFreeBuffer];
  ReturnFreeSppBuffer: PUBLIC PROCEDURE [SppBuffer] = LOOPHOLE[ReturnFreeBuffer];
  ReturnFreeBuffer: PUBLIC ENTRY PROCEDURE [b: Buffer] =
    BEGIN
    aH: BufferAccessHandle = b.currentOwner;
    IF CommFlags.doDebug AND useCount = 0 THEN Glitch[FreeQueueNotInitialized];
    IF CommFlags.doDebug AND aH.firstBuffer = NIL THEN Glitch[BufferPoolNotInitialized];
    -- Note: we do some "initialization" of things here since there are several ways to get
    -- buffers from the freeQueue.
    b.requeueProcedure ← ReturnFreeBuffer;
    b.network ←  NILNetwork[];  --  because of mokelumne compiler bug
    IF aH # systemAccessHandle THEN
      BEGIN
      SELECT b.bufFunc FROM
	send => aH.sendInUse ← aH.sendInUse - 1;
	receive => aH.receiveInUse ← aH.receiveInUse - 1;
	ENDCASE => Glitch[DontKnowHowToAllocateBuffer];
      totalSendAndReceiveBuffersInUse ← totalSendAndReceiveBuffersInUse - 1;
      BROADCAST aH.bufferAvailable;
      END;
    enqueue[systemBufferQueue, b];
    -- This is ugly, but there isn't any way to make things work without it, if 2
    -- PROCESSes are waiting for buffers.
    BROADCAST systemFreeQueueNotEmpty;
    END;

  BuffersLeft: PUBLIC ENTRY PROCEDURE [aH: BufferAccessHandle]
    RETURNS [left: CARDINAL] =
    BEGIN
    IF CommFlags.doDebug AND useCount = 0 THEN Glitch[FreeQueueNotInitialized];
    IF CommFlags.doDebug AND aH.seal # DriverTypes.bufferPoolSeal THEN
      Glitch[PoolSealBroken];
    IF CommFlags.doDebug AND aH.firstBuffer = NIL THEN Glitch[BufferPoolNotInitialized];
    IF aH = systemAccessHandle THEN
      BEGIN
      reserved: CARDINAL =
	SendAndReceiveBuffersInFreeQueue[] + totalReserveBuffers;
      IF CommFlags.doDebug AND useCount = 0 THEN Glitch[FreeQueueNotInitialized];
      left ← systemBufferQueue.length;
      left ← (IF left > reserved THEN left - reserved ELSE 0);
      END
    ELSE BEGIN left ← aH.send + aH.receive - aH.sendInUse - aH.receiveInUse; END;
    END;

  SendBuffersLeft: PUBLIC ENTRY PROCEDURE [aH: BufferAccessHandle]
    RETURNS [CARDINAL] =
    BEGIN
    IF CommFlags.doDebug AND useCount = 0 THEN Glitch[FreeQueueNotInitialized];
    IF CommFlags.doDebug AND aH.seal # DriverTypes.bufferPoolSeal THEN
      Glitch[PoolSealBroken];
    IF CommFlags.doDebug AND aH.firstBuffer = NIL THEN Glitch[BufferPoolNotInitialized];
    RETURN[aH.send - aH.sendInUse];
    END; -- SendBufferLeftInPool

  ReceiveBuffersLeft: PUBLIC ENTRY PROCEDURE [aH: BufferAccessHandle]
    RETURNS [CARDINAL] =
    BEGIN
    IF CommFlags.doDebug AND useCount = 0 THEN Glitch[FreeQueueNotInitialized];
    IF CommFlags.doDebug AND aH.seal # DriverTypes.bufferPoolSeal THEN
      Glitch[PoolSealBroken];
    IF CommFlags.doDebug AND aH.firstBuffer = NIL THEN Glitch[BufferPoolNotInitialized];
    RETURN[aH.receive - aH.receiveInUse];
    END; -- ReceiveBufferLeftInPool

  EnumerateBuffersInPool: PUBLIC PROCEDURE [
    pool: BufferAccessHandle, proc: PROCEDURE [Buffer]] =
    BEGIN
    b: BufferDefs.Buffer;
    IF CommFlags.doDebug AND useCount = 0 THEN Glitch[FreeQueueNotInitialized];
    b ← pool.firstBuffer;
    IF CommFlags.doDebug AND pool.seal # DriverTypes.bufferPoolSeal THEN
      Glitch[PoolSealBroken];
    FOR i: CARDINAL IN [0..pool.total) DO proc[b]; b ← b + pool.wordsPerBuffer; ENDLOOP;
    END; -- EnumerateBuffersInPool

  -- Get free buffer, but don't wait if there is none
  -- NB: These will return the last buffer, use with caution

  GetFreePupBuffer: PUBLIC PROCEDURE RETURNS [PupBuffer] = 
    BEGIN
    RETURN[
      LOOPHOLE[GetFreeBuffer[pup, systemAccessHandle, systemUse], PupBuffer]];
    END;

  GetClumpOfPupBuffers: PUBLIC ENTRY PROCEDURE [
    q: Queue, n: CARDINAL, wait: BOOLEAN] =
    BEGIN
    ENABLE UNWIND => NULL;
    IF CommFlags.doDebug AND useCount = 0 THEN Glitch[FreeQueueNotInitialized];
    IF CommFlags.doDebug AND q.seal # DriverTypes.queueSeal THEN Glitch[QueueSealBroken];
    UNTIL systemBufferQueue.length >
      n + SendAndReceiveBuffersInFreeQueue[] + totalReserveBuffers DO
      IF ~wait THEN RETURN; WAIT systemFreeQueueNotEmpty; ENDLOOP;
    THROUGH [0..n) DO
      b: PupBuffer ← DequeuePup[systemBufferQueue];
      b.type ← pup;
      b.pupType ← data;
      IF CommFlags.doDebug THEN b.debug ← CommUtilDefs.GetReturnFrame[].accesslink;
      EnqueuePup[q, b];
      ENDLOOP;
    END;

  -- Queue Manipulation Routines

  QueueInitialize: PUBLIC PROCEDURE [q: Queue] =
    BEGIN
    q↑ ← [length: 0, first: NIL, last: NIL, seal: DriverTypes.queueSeal];
    END;

  -- Put all buffers back onto buffer pool's freeQueue

  QueueCleanup: PUBLIC PROCEDURE [q: Queue] =
    BEGIN
    b: Buffer;
    UNTIL (b ← Dequeue[q]) = NIL DO ReturnFreeBuffer[b]; ENDLOOP;
    END;

  ExtractPupFromQueue: PUBLIC PROCEDURE [Queue, PupBuffer] RETURNS [PupBuffer] =
    LOOPHOLE[ExtractFromQueue];
  ExtractOisFromQueue: PUBLIC PROCEDURE [Queue, OisBuffer] RETURNS [OisBuffer] =
    LOOPHOLE[ExtractFromQueue];
  ExtractSppFromQueue: PUBLIC PROCEDURE [Queue, SppBuffer] RETURNS [SppBuffer] =
    LOOPHOLE[ExtractFromQueue];
  ExtractFromQueue: PUBLIC PROCEDURE [q: Queue, b: Buffer] RETURNS [Buffer] =
    BEGIN
    previousB, currentB: Buffer;
    IF q = NIL THEN Glitch[QueueScrambled];
    IF CommFlags.doDebug AND q.seal # DriverTypes.queueSeal THEN Glitch[QueueSealBroken];
    IF CommFlags.doDebug AND b.seal # DriverTypes.bufferSeal THEN Glitch[BufferSealBroken];
    previousB ← NIL;
    currentB ← q.first;
    UNTIL currentB = b DO
      IF currentB = NIL THEN EXIT;
      previousB ← currentB;
      currentB ← currentB.next;
      ENDLOOP;
    IF currentB # NIL THEN
      BEGIN
      -- remove this buffer from the queue
      IF CommFlags.doDebug AND currentB.seal # DriverTypes.bufferSeal THEN
	Glitch[BufferSealBroken];
      IF currentB = q.first THEN q.first ← currentB.next;
      IF currentB = q.last THEN q.last ← previousB;
      IF previousB # NIL THEN previousB.next ← currentB.next;
      q.length ← q.length - 1;
      currentB.queue ← NIL;
      currentB.next ← NIL;
      IF CommFlags.doStats THEN StatsDefs.StatIncr[statXqueue];
      END
    ELSE IF CommFlags.doStats THEN StatsDefs.StatIncr[statXqueueNIL];
    RETURN[currentB];
    END;

  EnqueuePup: PUBLIC PROCEDURE [Queue, PupBuffer] = LOOPHOLE[Enqueue];
  EnqueueOis: PUBLIC PROCEDURE [Queue, OisBuffer] = LOOPHOLE[Enqueue];
  EnqueueSpp: PUBLIC PROCEDURE [Queue, SppBuffer] = LOOPHOLE[Enqueue];
  Enqueue: PUBLIC PROCEDURE [q: Queue, b: Buffer] =
    BEGIN
    IF q = NIL OR b = NIL OR b.queue # NIL THEN Glitch[QueueScrambled];
    IF CommFlags.doDebug AND q.seal # DriverTypes.queueSeal THEN Glitch[QueueSealBroken];
    IF CommFlags.doDebug AND b.seal # DriverTypes.bufferSeal THEN Glitch[BufferSealBroken];
    IF CommFlags.doDebug AND q.length # 0 AND (q.first = NIL OR q.last = NIL) THEN
      Glitch[QueueScrambled];
    IF CommFlags.doDebug AND q.length > 256 THEN Glitch[QueueScrambled];
    b.next ← NIL;
    IF CommFlags.doStats THEN StatsDefs.StatIncr[statEnqueue];
    IF q.first = NIL THEN q.first ← b ELSE q.last↑.next ← b;
    q.last ← b;
    b.queue ← q;
    q.length ← q.length + 1;
    END; -- Enqueue

  DequeuePup: PUBLIC PROCEDURE [Queue] RETURNS [PupBuffer] = LOOPHOLE[Dequeue];
  DequeueOis: PUBLIC PROCEDURE [Queue] RETURNS [OisBuffer] = LOOPHOLE[Dequeue];
  DequeueSpp: PUBLIC PROCEDURE [Queue] RETURNS [SppBuffer] = LOOPHOLE[Dequeue];
  Dequeue: PUBLIC PROCEDURE [q: Queue] RETURNS [b: Buffer] =
    BEGIN
    IF q = NIL THEN Glitch[QueueScrambled];
    IF CommFlags.doDebug AND q.seal # DriverTypes.queueSeal THEN Glitch[QueueSealBroken];
    IF (b ← q.first) = NIL THEN
      BEGIN
      IF CommFlags.doDebug AND q.length # 0 THEN Glitch[QueueScrambled];
      IF CommFlags.doStats THEN StatsDefs.StatIncr[statDequeueNIL];
      RETURN;
      END;
    IF (q.first ← q.first.next) = NIL THEN q.last ← NIL;
    IF CommFlags.doDebug AND q.length > 256 THEN Glitch[QueueScrambled];
    q.length ← q.length - 1;
    IF CommFlags.doStats THEN StatsDefs.StatIncr[statDequeue];
    IF b.queue # q THEN Glitch[QueueScrambled];
    b.queue ← NIL;
    b.next ← NIL;
    IF CommFlags.doDebug AND b.seal # DriverTypes.bufferSeal THEN Glitch[BufferSealBroken];
    IF CommFlags.doDebug AND q.length # 0 AND (q.first = NIL OR q.last = NIL) THEN
      Glitch[QueueScrambled];
    END; -- Dequeue

  -- initialization
  --systemAccessHandle ← MakeBufferPool[total: 0, send: 0, receive: 0, reserve: 0, forSystemUse: TRUE];

  systemBufferQueue ← Heap.MakeNode[n: SIZE[QueueObject]];
  QueueInitialize[systemBufferQueue];
  Process.InitializeCondition[
    CommUtilDefs.MaybeShorten[@systemFreeQueueNotEmpty], Process.MsecToTicks[
    1000]];


  IF CommFlags.doDebug THEN
    BEGIN
    GetGiantVector[].firstBuffer ← NIL;
    GetGiantVector[].freeQueue ← systemBufferQueue;
    END;
  END.