-- File: SimpleNSIOEthernetImpl.mesa - last edit:
-- HGM                 24-Oct-85 17:54:26, Remove SLOW Dove "bug fix"
-- AOF                 16-Oct-85 20:45:58
-- Copyright (C) 1983, 1985  by Xerox Corporation. All rights reserved. 

-- This module implements SimpleNSIO on the Ethernet (NOT the EthernetOne). It supplies level one and level zero encapsulation.

-- NOTE: The module SimpleNSIOEthernetOneImpl is a near-copy of this one. IF YOU MAKE A CHANGE HERE, it probably should be made to it too. SimplePUPIOEthernetOneImpl is another, somewhat less-similar module.

DIRECTORY
  BootChannel USING [Result],
  Checksum USING [ComputeChecksum],
  Device USING [Type],
  DeviceTypes USING [ethernet],
  DriverTypes USING [Encapsulation, EthernetCRC, minWordsPerEthernetPacket],
  Environment USING [Base, bytesPerWord, first64K],
  EthernetFace USING [
    AddCleanup, controlBlockSize, DeviceHandle, GetNextDevice, GetPacketLength,
    GetStatus, globalStateSize, nullDeviceHandle, QueueInput, QueueOutput,
    RemoveCleanup, TurnOff, TurnOn, GlobalStatePtr],
  GermOps USING [GermWorldError],
  Inline USING [LowHalf],
  NSTypes USING [BufferBody, bytesPerIDPHeader, maxIDPWordsPerPacket, PacketType],
  PilotMP USING [cGermERROR],
  ProcessorFace USING [processorID],
  ResidentHeap USING [FreeNode, MakeNode],
  SimpleNSIO USING [
    ByteCount, cantHandleDevice, Cleanups, timedOutBytes, timedOutResult],
  SpecialSystem USING [
    HostNumber, NetworkNumber, nullNetworkNumber, SocketNumber],
  System USING [
    GetClockPulses, Microseconds, MicrosecondsToPulses, NetworkAddress,
    nullHostNumber, nullNetworkNumber, nullSocketNumber, Pulses, SocketNumber];

SimpleNSIOEthernetImpl: PROGRAM
  IMPORTS
    Checksum, EthernetFace, GermOps, Inline, ProcessorFace, ResidentHeap, System
  EXPORTS SimpleNSIO, System
  SHARES GermOps =
  BEGIN
  
  -- PARAMETERS:
  
  sendTimeOut: System.Microseconds = 3000000;

  -- TYPES AND VARIABLES:

  HostNumber: PUBLIC --System-- TYPE = SpecialSystem.HostNumber;
  NetworkNumber: PUBLIC --System-- TYPE = SpecialSystem.NetworkNumber;
  SocketNumber: PUBLIC --System-- TYPE = SpecialSystem.SocketNumber;

  NetworkAddress: TYPE = System.NetworkAddress;

  ByteCount: TYPE = SimpleNSIO.ByteCount;
  
  -- Level zero Ethernet packet format:
  LevelZeroPacket: TYPE = MACHINE DEPENDENT RECORD [
    encapsulation(0): ethernet DriverTypes.Encapsulation,
    levelOnePacket(7): NSTypes.BufferBody];
  bufferAlign: CARDINAL = 3;  -- align encapsulation to this boundary..
  bufferMod: CARDINAL = 4;  -- .. modulo this.

  slop: CARDINAL = 12;  -- Why this? see Footnote (1) beneath LOG.
  bufferSize: CARDINAL =
    SIZE[ethernet DriverTypes.Encapsulation] +
    NSTypes.maxIDPWordsPerPacket +
    DriverTypes.EthernetCRC.SIZE +
    slop;
  buffer: LONG POINTER TO LevelZeroPacket;

  longIocb: LONG POINTER ← NIL;
  globalStorage: EthernetFace.GlobalStatePtr;
  device: EthernetFace.DeviceHandle ← EthernetFace.nullDeviceHandle;

  returnData: RECORD [
    returnTo, returnedFrom: NetworkAddress, returnVia: HostNumber];


  --==============================
  -- Public Procedures
  --==============================

  GetRawBufferSize: PUBLIC PROCEDURE RETURNS [rawBufferSize: CARDINAL] = {
    RETURN[bufferSize + bufferMod-1]};

  Initialize: PUBLIC PROC [
    deviceType: Device.Type, deviceOrdinal: CARDINAL, rawBuffer: LONG POINTER,
    cleanups: SimpleNSIO.Cleanups]
    RETURNS [
      result: BootChannel.Result, bufferBody: LONG POINTER TO NSTypes.BufferBody] =
    BEGIN
    IF device # EthernetFace.nullDeviceHandle THEN Error[];  -- already initialized
    IF deviceType # DeviceTypes.ethernet THEN
      RETURN[SimpleNSIO.cantHandleDevice, NIL];
    THROUGH [0..deviceOrdinal] DO
      IF (device ← EthernetFace.GetNextDevice[device]) =
        EthernetFace.nullDeviceHandle THEN
	RETURN[SimpleNSIO.cantHandleDevice, NIL];
      ENDLOOP;
    longIocb ← @Environment.first64K[
      ResidentHeap.MakeNode[EthernetFace.controlBlockSize, a4].node];
    buffer ← LOOPHOLE[((LOOPHOLE[rawBuffer, LONG CARDINAL] + bufferMod-1)/bufferMod)*bufferMod
      + bufferAlign];
    IF EthernetFace.globalStateSize # 0 THEN
      [globalStorage, ] ← ResidentHeap.MakeNode[EthernetFace.globalStateSize, a4];
    IF cleanups = doCleanup THEN EthernetFace.AddCleanup[device];
    EtherTurnOn[];
    RETURN[[ok[]], @buffer.levelOnePacket];
    END;  --Initialize--

  Finalize: PUBLIC PROC [cleanups: SimpleNSIO.Cleanups] =
    BEGIN
    IF device = EthernetFace.nullDeviceHandle THEN Error[];  -- not initialized
    EthernetFace.TurnOff[device];
    IF cleanups = doCleanup THEN EthernetFace.RemoveCleanup[device];
    device ← EthernetFace.nullDeviceHandle;
    [] ← ResidentHeap.FreeNode[Inline.LowHalf[longIocb]];
    longIocb ← NIL;
    IF EthernetFace.globalStateSize # 0 THEN
      [] ← ResidentHeap.FreeNode[globalStorage];
    END;

  ReceivePacket: PUBLIC PROC [
    getFrom: NetworkAddress, mySocket: System.SocketNumber,
    timeout: System.Microseconds]
    RETURNS [
      result: BootChannel.Result, dataBytes: ByteCount, type: NSTypes.PacketType,
      source, destination: LONG POINTER TO NetworkAddress] =
    BEGIN
    startTime: System.Pulses = System.GetClockPulses[];
    IF device = EthernetFace.nullDeviceHandle THEN Error[];  -- not initialized
    DO  --UNTIL packet arrives on requested socket--
      EthernetFace.QueueInput[
        device, @buffer.encapsulation, bufferSize, longIocb];
      UNTIL EthernetFace.GetStatus[longIocb] # pending DO
        IF TimeExpired[timeout: timeout, startTime: startTime] THEN
          BEGIN
          EthernetFace.TurnOff[device];  -- abort the input.
          EtherTurnOn[];
          RETURN[
	    dataBytes: SimpleNSIO.timedOutBytes,
	    type: TRASH, source: TRASH, destination: TRASH,
	    result: SimpleNSIO.timedOutResult];
          END;
        ENDLOOP;
      IF EthernetFace.GetStatus[longIocb] ~= ok THEN LOOP;
      IF buffer.encapsulation.ethernetType # ns THEN LOOP;
      IF ~TestChecksum[@buffer.levelOnePacket] THEN LOOP;
      IF buffer.levelOnePacket.destination.host ~= ProcessorFace.processorID THEN LOOP;
      IF getFrom.net ~= System.nullNetworkNumber
        AND getFrom.net ~= buffer.levelOnePacket.source.net THEN LOOP;
      IF getFrom.host ~= System.nullHostNumber
        AND getFrom.host ~= buffer.levelOnePacket.source.host THEN LOOP;
      IF getFrom.socket ~= System.nullSocketNumber
        AND getFrom.socket ~= buffer.levelOnePacket.source.socket THEN LOOP;
      IF mySocket ~= System.nullSocketNumber
        AND mySocket ~= buffer.levelOnePacket.destination.socket THEN LOOP;
      IF EthernetFace.GetPacketLength[longIocb] <
        LevelZeroWordsFromLevelOneBytes[
          buffer.levelOnePacket.pktLength] THEN LOOP;
      IF buffer.levelOnePacket.pktLength >= NSTypes.bytesPerIDPHeader THEN EXIT;
      ENDLOOP;
    returnData.returnTo ← buffer.levelOnePacket.source;
    returnData.returnedFrom ← buffer.levelOnePacket.destination;
    returnData.returnVia ← buffer.encapsulation.ethernetSource;
    RETURN[
      dataBytes: buffer.levelOnePacket.pktLength - NSTypes.bytesPerIDPHeader,
      type: buffer.levelOnePacket.packetType,
      source: @buffer.levelOnePacket.source,
      destination: @buffer.levelOnePacket.destination,
      result: [ok[]]];
    END;  --ReceivePacket--

  ReturnPacket: PUBLIC PROC [dataBytes: ByteCount, type: NSTypes.PacketType]
    RETURNS [result: BootChannel.Result] =
    BEGIN
    IF device = EthernetFace.nullDeviceHandle THEN Error[];  -- not initialized
    buffer.encapsulation.ethernetDest ← returnData.returnVia;
    buffer.levelOnePacket.source ← returnData.returnedFrom;
    buffer.levelOnePacket.destination ← returnData.returnTo;
    RETURN SendBuffer[dataBytes, type];
    END;

  SendPacket: PUBLIC PROC [
    dataBytes: ByteCount, type: NSTypes.PacketType, sourceSocket: SocketNumber,
    dest: NetworkAddress]
    RETURNS [result: BootChannel.Result] =
    BEGIN
    IF device = EthernetFace.nullDeviceHandle THEN Error[];  -- not initialized;
    buffer.encapsulation.ethernetDest ← dest.host;
    buffer.levelOnePacket.source ← [
      net: SpecialSystem.nullNetworkNumber, host: ProcessorFace.processorID,
      socket: sourceSocket];
    buffer.levelOnePacket.destination ← dest;
    RETURN SendBuffer[dataBytes, type];
    END;

  --==============================
  -- Private Procedures
  --==============================

  Error: PROC = {GermOps.GermWorldError[PilotMP.cGermERROR]};

  SendBuffer: PROC [dataBytes: ByteCount, type: NSTypes.PacketType]
    RETURNS [result: BootChannel.Result] =
    -- Sends buffer; doesn't return till it is sent (but times out if can't send).
    BEGIN
    startTime: System.Pulses;
    buffer.levelOnePacket.pktLength ← dataBytes + NSTypes.bytesPerIDPHeader;
    buffer.levelOnePacket.transportControl ← [
      trace: FALSE, filler: 0, hopCount: 0];
    buffer.levelOnePacket.packetType ← type;
    buffer.encapsulation.ethernetSource ← [ProcessorFace.processorID];
    buffer.encapsulation.ethernetType ← ns;
    SetChecksum[@buffer.levelOnePacket];
    EthernetFace.QueueOutput[
      device, @buffer.encapsulation,
      LevelZeroWordsFromLevelOneBytes[buffer.levelOnePacket.pktLength],
      longIocb];
IF FALSE THEN
    THROUGH [0..1000) DO ENDLOOP;  --we haven't figured out why this helps
    startTime ← System.GetClockPulses[];
    DO  --until sent or timed out
      IF EthernetFace.GetStatus[longIocb] # pending THEN EXIT;
      IF TimeExpired[timeout: sendTimeOut, startTime: startTime] THEN {
        EthernetFace.TurnOff[device]; EtherTurnOn[]; EXIT};
      ENDLOOP;
    RETURN[[ok[]]];
    END;  --SendBuffer--

  SetChecksum: PROC [b: LONG POINTER TO NSTypes.BufferBody] = {
    b.checksum ← Checksum.ComputeChecksum[
      0, (b.pktLength + 1 - 2)/2, @b.pktLength]};

  TestChecksum: PROC [b: LONG POINTER TO NSTypes.BufferBody] RETURNS [BOOLEAN] = {
    RETURN[
      b.checksum = Checksum.ComputeChecksum[
        0, (b.pktLength + 1 - 2)/2, @b.pktLength] OR b.checksum = 177777B]};

  TimeExpired: PROC [timeout, startTime: System.Microseconds] RETURNS [BOOLEAN] =
    BEGIN
    IF timeout = LAST[System.Microseconds] THEN RETURN[FALSE]
    ELSE
      RETURN[
        (System.GetClockPulses[] - startTime) > System.MicrosecondsToPulses[
          timeout]];
    END;

  EtherTurnOn: PROC = {EthernetFace.TurnOn[device, [ProcessorFace.processorID], 0, 0, globalStorage]};

  LevelZeroWordsFromLevelOneBytes: PROC [bodyBytes: CARDINAL]
    RETURNS [wds: CARDINAL] =
    BEGIN
    RETURN[MAX[
      DriverTypes.minWordsPerEthernetPacket, 
      SIZE[ethernet DriverTypes.Encapsulation] +
        (bodyBytes + Environment.bytesPerWord - 1)/Environment.bytesPerWord]];
    END;


  -- MAIN body:
  --IF EthernetFace.globalStateSize # 0 THEN Error[];

  END.
  
  
LOG

23-Oct-81 17:04:29   FXH       Created file.
24-Nov-81 11:10:38   FXH       Merged in use of ResidentHeap.
10-Dec-81 11:43:21   AWL      
   MicrosecondsToPulses now in System.  Fixes for network address related things
   now being defined in System.
10-Dec-81 17:11:11   AWL         Export types to System from SpecialSystem.
20-Apr-83 13:10:33   WDK     
   OISCP => NS. Renamed module MiniOISCPDriverImpl.mesa => SimpleNSIOEthernetImpl. Now returns all nonfatal errors to caller. Make compatible with new SimpleNSIO. Now will talk to specified ethernet. Get constants from DriverTypes. Buffer passed in by client.
20-Jul-83  9:58:49   AWL      
   NSTypes.bytesPerPktHeader => NSTypes.bytesPerSppHeader,
   NSTypes.maxBytesPerPkt => NSTypes.maxDataBytesPerSpp.
 9-Aug-83 17:05:49   WDK     
   Previous change was wrong. Should be IDP, not SPP. Use new DriverTypes.EthernetCRC.
 8-Aug-85  9:36:25   AOF
   Process globalStorage when Faces says size is # 0 (Daybreak).
16-Oct-85 20:44:44   AOF
   Add a small delay loop after queue output to the ethernet face.  We don't know why this works, but it covers problems in both the DLion and Daybreak machines.     
   
  
  FOOTNOTE
  added at time of Copyriting  8-Oct-84 18:18:35:
  (1) "-- Hal says we need this. We should ask why." where the global constant ~slop~ is declared.