Raw File
virtualWire.c
/*
 Copyright 2020-2021 Ronald S. Burkey <info@sandroid.org>

 This file is part of yaAGC.

 yaAGC is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 yaAGC is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with yaAGC; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 In addition, as a special exception, Ronald S. Burkey gives permission to
 link the code of this program with the Orbiter SDK library (or with
 modified versions of the Orbiter SDK library that use the same license as
 the Orbiter SDK library), and distribute linked combinations including
 the two. You must obey the GNU General Public License in all respects for
 all of the code used other than the Orbiter SDK library. If you modify
 this file, you may extend this exception to your version of the file,
 but you are not obligated to do so. If you do not wish to do so, delete
 this exception statement from your version.

 Filename:	virtualWire.c
 Purpose:	Portable functions (*NIX and Win32) for working with sockets
 for connecting yaLVDC to peripherals by "virtual wires".
 Compiler:	GNU gcc.
 Contact:	Ron Burkey <info@sandroid.org>
 Reference:	http://www.ibiblio.org/apollo/LVDC.html
 Mods:		2020-05-06 RSB	Began adapting/simplifying from the yaAGC
                                file agc_utilities.c.
                2021-03-22 RSB  Changes related to compiling for Mac.
                                (MSG_NOSIGNAL #define'd, and all
                                #ifdef unix (or similar) extended to
                                apple.)  Thanks to Ludo Visser for the
                                change.
 */

#include <stdio.h>
#include <string.h>
#ifndef WIN32
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#else
#include <windows.h>
#include <winsock2.h>
#endif

#if (defined(__APPLE__) && defined(__MACH__)) || WIN32
#define MSG_NOSIGNAL 0
#endif

/////////////////////////////////////////////////////////////////////////////////
// Portable functions (*NIX and Win32) for working with sockets.
// LVDC/PTC specific functions.  Just delete them if repurposing this material
// for some other program.

#include "yaLVDC.h"

/*
 * Format for yaLVDC-compatible "virtual wire" packets.
 *
 * The format distinguishes between packets containing "data" and packets
 * containing a "mask".  If the packet carries all 26 bits of data (as
 * packets output by the server do), then there's no need for a mask; all
 * 26 data bits are valid.  However, for packets sent by peripherals to the
 * server (the CPU emulator) it's possible for one peripheral device to
 * supply some of the 26 bits of the port, while other peripherals supply
 * other of the bits.  In that case, the peripheral should precede such
 * a data packet (which will still be the full length) by an otherwise-
 * similar mask packet.  The payload of the mask packet has a 1 wherever
 * the payload in the following data packet will be valid, and a 0 wherever
 * the data packet will be invalid.
 *
 * Each packet consists
 * of 6 data bytes, formatted as follows:
 *
 *  1st byte:   D7      1
 *              D6      1 if the message is a mask, 0 if it's data.
 *              D5-D3   I/O type:       000     PIO
 *                                      001     CIO
 *                                      010     PRS
 *                                      011     Interrupt
 *                                      100     Command or status from panel
 *                                      101     Command or status to panel
 *                                      110     (reserved)
 *                                      111     PING
 *              D2-D0   Unique Source ID.  (000 is the server; i.e., the CPU.)
 *                      The port numbers used are the base port number plus
 *                      the ID.  Thus if the server were port number 19653,
 *                      then ID=1 would be port 19694, ... , ID=7 would be
 *                      port 19660.
 *              Note that if all fields are 0xFF, it could be used as
 *              a harmless 1-byte PING message.  I don't know why I'd
 *              want to use that, necessarily, but at least I'm allowing
 *              for it.
 *
 *   2nd byte   D7      0
 *              D6-D0   Least-significant 7 bits of the channel number
 *
 *   3rd byte   D7      0
 *              D6      Most-significant bit of the channel number
 *              D5      Next-most-significant bit of the channel number
 *              D4-D0   Most-significant 5 bits of the 26-bit data/mask
 *
 *   4th byte   D7      0
 *              D6-D0   Next-most-significant 7 bits of the 26-bit data/mask.
 *
 *   5th byte   D7      0
 *              D6-D0   Next-most-significant 7 bits of the 26-bit data/mask.
 *
 *   6th byte   D7      0
 *              D6-D0   Least-significant 7 bits of the 26-bit data/mask.
 *
 * Commands/status from/to the PTC front panel (I/O type 100 or 101) are, of course,
 * my own invention, and thus are not documented in any of the original Apollo-era
 * documentation, since in the physical PTC they were not implemented with i/o ports.
 * (Except, of course, for data conveyed already by existing PIO or
 * CIO commands.)  So here is the documentation:
 *
 * Type 100 (from panel emulation to CPU emulation):
 *      Channel 000:    Pause at current instruction.  What that means is that the
 *                      main CPU emulation loop will continue cycling normally,
 *                      except that runOneInstruction() is not being executed
 *                      and the cycle/instruction counts are not updated.  This
 *                      means that the yaLVDC debugger interface *won't* appear,
 *                      and virtual-wire transactions will continue to occur.
 *                      The packet payload is ignored.
 *      Channel 001:    Release the instruction pause (from prior channel 000) and
 *                      proceed executing normally.  The packet payload is ignored.
 *      Channel 002:    Set a data-address comparison pattern.  The packet payload
 *                      is the bit pattern against which to compare data addresses.
 *                      The most-significant 13 bits are formatted like a HOP
 *                      constant (containing only the DM and DS fields),
 *                      while the least-significant 13 bits are formatted like
 *                      an instruction (opcode + operand), because those are the 4
 *                      fields specified by the controls on the MLDD panel.
 *      Channel 003:    Set an instruction-address comparison pattern.  The packet
 *                      is formatted like a HOP constant, and provides the payload.
 *                      Only the IM, IS, S, and LOC fields are significant.
 *      Channel 004:    A data value for saving to the current data address (set by
 *                      channel 002 above).  The payload is the 26 bits
 *                      of a pattern for comparison to data.
 *      Channel 005:    Settings from the DISPLAY MODE area of the MLDD, and a
 *                      few other controls whose states we'd like preserved
 *                      if it should happen that the front-panel emulation is
 *                      exited and then restarted during a test procedure:
 *                      Bits D2,D1,D0   0 = PROG-CYCLE REPEAT
 *                                      1 = PROG-CYCLE SINGLE STEP
 *                                      2 = PROG-CYCLE ADR HOLD
 *                                      3 = DISPLAY SINGLE
 *                                      4 = DISPLAY REPEAT
 *                      Bit D3          0 = ADDRESS COMPARE INS
 *                                      1 = ADDRESS COMPARE DATA
 *                      Bits D5,D4      0 = NONE
 *                                      1 = A13 IA
 *                                      2 = A13 DATA
 *                                      3 = TRS
 *                      Bit D6          REPEAT (MLDD MEMORY LOADER area)
 *                      Bit D7          CST (PD PROGRAM CONTROL area)
 *                      Bit D8          MAN CST (PD PROGRAM CONTROL area)
 *                      Bit D9          ML (1) vs DD (0) (TRMC MODE)
 *                      Bit D10         ACC DISPLAY ENABLE (CE ACCUMULATOR area)
 *                      Bit D11         MEM ADD REG (PD DATA area)
 *      Channel 600:    Set the data-comparison mode.  The payload is ignored.
 *      Channel 601:    Set the instruction-comparison mode.  The payload is ignored.
 *      Channel 602:    Inhibit interrupts.  The payload is the 16-bit mask (aligned
 *                      at the least-significant bit) of interrupts to inhibit.
 *      Channel 603:    Step a single instruction.
 *      Channel 604:    Reset the CPU.
 *      Channel 605:    Request status.
 *      Channel 606:    Width of typewriter carriage, in characters.
 *      Channel 607:    Width of typewriter tab stop, in characters.
 *
 * Type 101 (from CPU emulation to panel emulation):
 *      Channel 000:    CPU is paused.
 *      Channel 001:    CPU is running.
 *      Channel 002:    Current data address (same format as i/o type 100 channel 002).
 *      Channel 003:    Current instruction address (same format as i/o type 100 channel 003).
 *      Channel 004:    Current data value.
 *      Channel 005:    Current DISPLAY MODE settings (same format as i/o type 100 channel 005).
 *
 *      Channel 600:    Current accumulator value.
 *      Channel 601:    Current data from the data-address comparison pattern.
 *      Channel 602:    Current data-address comparison pattern.
 *      Channel 603:    Current instruction-address comparison pattern.
 *      Channel 604:    Last PROG REG A configuration received by CPU.
 *      Channel 605:    Last PROG REG B configuration received by CPU.
 */

int ServerBaseSocket = -1;
int PortNum = 19653;
typedef struct
{
  int Listener;
  int disabled;
} Listener_t;
Listener_t Listeners[MAX_LISTENERS];
int NumListeners = 0;
#define MAX_INPACKET_SIZE 1800
uint8_t inPackets[MAX_LISTENERS][MAX_INPACKET_SIZE];
int inPacketSizes[MAX_LISTENERS] =
  { 0 };

static int newConnect = 0;
void
connectCheck(void)
{
  int i, j;
  char *reassigned = "";
  for (j = 0; j < NumListeners; j++)
    if (Listeners[j].disabled)
      break;
  if (j < MAX_LISTENERS)
    {
      i = accept(ServerBaseSocket, NULL, NULL);
      if (i > 0)
        {
          UnblockSocket(i);
          Listeners[j].disabled = 0;
          Listeners[j].Listener = i;
          inPacketSizes[j] = 0;
          if (j < NumListeners)
            {
              reassigned = " (reassigned)";
            }
          else
            {
              NumListeners++;
            }
          printf("\nConnected to peripheral #%d%s on handle %d.\n", j,
              reassigned, i);
          newConnect = 1;
        }
      else if (i == -1 && errno != EAGAIN)
        {
          printf("\nVirtual wire (accept) error: %s\n", strerror(errno));
        }
    }
}

// Add a 6-byte chunk to the output packet. First set outPacketSize=0; then
// call formatPacket() up to MAX_CHUNKS_PER_PACKET times.  Then send()
// outPacket[].
#define MAX_CHUNKS_PER_PACKET 32
static int outPacketSize = 0;
static uint8_t outPacket[6 * MAX_CHUNKS_PER_PACKET];
void
formatPacket(int ioType, int channel, int payload, int isMask)
{
  int id = 0;
  outPacket[outPacketSize++] = (isMask ? 0300 : 0200) | ((ioType << 3) & 0070)
      | (id & 0007);
  outPacket[outPacketSize++] = channel & 0177;
  outPacket[outPacketSize++] = ((channel & 0600) >> 2)
      | ((payload >> 21) & 0037);
  outPacket[outPacketSize++] = (payload >> 14) & 0177;
  outPacket[outPacketSize++] = (payload >> 7) & 0177;
  outPacket[outPacketSize++] = payload & 0177;
}

// Compute parity of a 6-bit value.  The value returned is suitable for direct
// insertion into an interrupt latch after a PRS instruction.
int
oddParity6(int value)
{
  value ^= (value >> 1) ^ (value >> 2) ^ (value >> 3) ^ (value >> 4) ^ (value >> 5);
  value = 1 & ~value;
  if (value)
    return (0004000000 >> 1);
  else
    return (0000000000 >> 1);
}

// Once the server system has been activated, call this function once after
// emulation of each LVDC/PTC instruction to take care of any pending
// virtual-wire actions.  Returns 0 on success, non-zero on error.
typedef struct
{
  int valid;
  int source;
  int ioType;
  int channel;
  int mask;
} pendingMask_t;
pendingMask_t pendingMasks[MAX_LISTENERS] =
  {
    { 0 } };
static int needStatus = 0;
int printerOctalMode = 0;
static const int octalPATN[] = {
    0120300000>>1, 0130300000>>1, 0120300000>>1, 0130300000>>1, 0160300000>>1, 0170300000>>1, 0160300000>>1, 0170300000>>1
};
int
pendingVirtualWireActivity(void /* int id, int mask */)
{
  int i, j, received, ioType, payload, channel;
  // For a general-purpose function, the following would be a
  // function argument, but for an LVDC server they always have the
  // same values, so it would be kind of pointless for them to be anything
  // other than constants.
  int mask;

  retryStatus: ;
  received = 0;
  ioType = -1;
  mask = 0377777777;
  outPacketSize = 0;
  // Format the output packet.
  if (needStatus || newConnect || panelPause == 2 || panelPause == 4)
    {
      uint16_t instruction = 0;
      int data = -1, hop = -1, hopd = -1, dataReadback = -1, datapat = -1,
          inspat = -1;
      int displayModePayload = -1, progRegA = -1, progRegB = -1;
      hopStructure_t hs =
        { 0 };
      if (needStatus != 3 && needStatus != 4 && needStatus != 5)
        hop = state.hop;
      if (hop != -1 && !parseHopConstant(hop, &hs))
        if (!fetchInstruction(hs.im, hs.dm, hs.s, hs.loc, &instruction,
            &instructionFromDataMemory))
          {
            int opcode = instruction & 0x0F;
            int a9 = (instruction >> 4) & 1;
            int a81 = (instruction >> 5) & 0xFF;
            hopd = (state.hop & 0377760000) | instruction;
            if ((ptc && (opcode == 01 || opcode == 05)) || opcode == 04
                || opcode == 010 || opcode == 012 || opcode == 014
                || opcode == 016)
              {
                // These are operators whose operands are not variables.
              }
            else if (needStatus != 2)
              {
                if (fetchData(hs.dm, a9, hs.ds, a81, &data,
                    &dataFromInstructionMemory))
                  data = 0;
              }
          }
      if (needStatus == 3)
        datapat = panelPatternDataAddress;
      if (needStatus == 4)
        inspat = panelPatternInstructionAddress;
      if (needStatus == 2 || newConnect || needStatus == 5)
        {
          if (needStatus == 2 || newConnect)
            {
              inspat = panelPatternInstructionAddress;
              datapat = panelPatternDataAddress;
              displayModePayload = panelDisplayModePlus;
              progRegA = state.cio[0214];
              progRegB = state.cio[0220];
            }
          dataReadback = -1;
          if (panelPatternDataAddress != -1)
            {
              if (fetchData(panelPatternDM, 0, panelPatternDS, panelPatternDLOC,
                  &dataReadback, &dataFromInstructionMemory))
                dataReadback = 0;
            }
        }
      formatPacket(5, (panelPause == 0 || panelPause == 4) ? 001 : 000, 0, 0);
      if (hop != -1)
        formatPacket(5, 003, state.hop, 0);
      if (hopd != -1)
        formatPacket(5, 002, hopd, 0);
      if (data != -1)
        formatPacket(5, 004, data, 0);
      if (displayModePayload != -1)
        formatPacket(5, 005, displayModePayload, 0);
      formatPacket(5, 0600, state.acc, 0);
      if (progRegA != -1)
        formatPacket(5, 0604, progRegA, 0);
      if (progRegB != -1)
        formatPacket(5, 0605, progRegB, 0);
      if (inspat != -1)
        formatPacket(5, 0603, inspat, 0);
      if (datapat != -1)
        formatPacket(5, 0602, datapat, 0);
      if (dataReadback != -1)
        formatPacket(5, 0601, dataReadback, 0);
      needStatus = 0;
      newConnect = 0;
    }
  // Take care of any virtual-wire outputs needed.  The changes (triggered by
  // the last LVDC/PTC instruction executed) have stuck the necessary info in
  // the global "state" structure.  Note that any given instruction can flag
  // at _most_ one of state.pioChange, .cioChange, or .prsChange.  Moreover,
  // packets are guaranteed to be delivered in the same order generated (by
  // any given source), so there's no possibility of getting anything
  // out of order here.
  if (state.pioChange != -1)
    {
      ioType = 0;
      channel = state.pioChange;
      payload = state.pio[channel];
      state.pioChange = -1;
    }
  else if (state.cioChange != -1)
    {
      ioType = 1;
      channel = state.cioChange;
      payload = state.cio[channel];
      state.cioChange = -1;
      // If the CIO was something that should have made the typewriter, plotter,
      // or printer busy, we fake that up right here, since otherwise there
      // wouldn't be enough time to allow the PTC front-panel emulation to
      // report back that the device wasn't busy.
      if (channel == 0144 || channel == 0150)
        {
          state.bbPlotter = 2;
          state.busyCountPlotter = PERIPHERAL_BUSY_CYCLES;
        }
      else if (channel == 0160)
        {
          state.bbPrinter = 1;
          state.busyCountPrinter = PERIPHERAL_BUSY_CYCLES;
          state.cio[0154] = (payload & 0374000000) | (0002100000 >> 1) | oddParity6((payload >> 20) & 077);
          state.lastWasPrinter = 1;
        }
      else if (channel == 0164)
        {
          printerOctalMode = 1;
          state.cio264Buffer = 0;
          state.cio[0264] = 0;
          state.lastWasPrinter = 1;
        }
      else if (channel == 0170)
        {
          printerOctalMode = 0;
          state.cio264Buffer = 0;
          state.cio[0264] = 0;
          state.lastWasPrinter = 1;
        }
      else if (channel == 0120 || channel == 0124 || channel == 0130 || channel == 0134)
        {
          state.bbTypewriter = 4;
          state.lastWasPrinter = 0;
          if (typewriterCharsInLine >= typewriterMargin)
            {
              dPrintoutsTypewriter("VW HIT MARGIN or RETURN");
              state.busyCountTypewriter = CARRIAGE_RETURN_BUSY_CYCLES;
              typewriterCharsInLine = 0;
            }
          else
            {
              state.busyCountTypewriter = PERIPHERAL_BUSY_CYCLES;
              dPrintoutsTypewriter("VW CIO 120/124/130/134");
            }
        }
    }
  else if (state.prsChange != -1)
    {
      int i;

      ioType = 2;
      channel = state.prsChange;
      payload = state.prs[channel];
      state.prsChange = -1;
      state.bbPrinter = 1;
      state.prsParityDelayCount = 0;

      state.cio[0154] = 0;
      if (channel == 0774) // Group mark.
        {
          state.busyCountPrinter = MEDIUM_BUSY_CYCLES;
          state.cio[0154] |= 0000700000 >> 1;
          state.prsDelayedParity[1] = oddParity6(000);
          state.prsDelayedParity[2] = oddParity6(000);
          state.prsDelayedParity[3] = oddParity6(000);
        }
      else if (printerOctalMode)
        {
          state.busyCountPrinter = PERIPHERAL_BUSY_CYCLES;
          for (i = 0; i <= 24; i += 3)
            {
              int octal = ((payload << i) & 0340000000) >> 23;
              state.cio[0154] |= octalPATN[octal];
              if (i == 3)
                state.prsDelayedParity[1] = oddParity6(octal);
              else if (i == 18)
                {
                  if (state.inhibit250)
                    state.prsDelayedParity[2] = oddParity6(octal);
                  else
                    {
                      // I have no rationale for this whatever.  I was
                      // sure it should be oddParity6(octal), *regardless*
                      // of the state of inhibit250.  It undoubtedly means
                      // that my whole rationale for how to compute the
                      // PRS check parity is wrong ... perhaps it will be
                      // figured out and corrected later.  Adding this is
                      // purely an ad hoc measure to get test L34P9 in the
                      // PAST program to pass.
                      state.prsDelayedParity[2] = oddParity6(000);
                    }
                }
              else if (i == 24)
                {
                  if (state.inhibit250)
                    state.prsDelayedParity[3] = oddParity6(octal);
                  else
                    state.prsDelayedParity[3] = oddParity6(000);
                }
            }
        }
      else // BCD mode
        {
          state.busyCountPrinter = PERIPHERAL_BUSY_CYCLES;
          for (i = 0; i <= 18; i += 6)
            {
              int ba8421 = (payload << i) & (0770000000 >> 1);
              state.cio[0154] |= ba8421;
              state.cio[0154] |= 0000300000 >> 1;
              if (i == 6)
                state.prsDelayedParity[1] = oddParity6(ba8421 >> 20);
              else if (i == 18)
                {
                  state.prsDelayedParity[2] = oddParity6(ba8421 >> 20);
                  state.prsDelayedParity[3] = oddParity6(ba8421 >> 20);
                }
            }
        }
    }
  if (ioType >= 0)
    {
      if ((mask & 0377777777) != 0377777777)
        formatPacket(ioType, channel, mask, 1);
      formatPacket(ioType, channel, payload, 0);
    }

  // Take care of any network stuff needed.
  connectCheck();
  // Receive data.
  for (i = 0; i < NumListeners; i++)
    if (!Listeners[i].disabled)
      {
        j = recv(Listeners[i].Listener, &inPackets[i][inPacketSizes[i]],
        MAX_INPACKET_SIZE - inPacketSizes[i], 0);
        if (j == -1 && errno != EAGAIN)
          {
            printf("Peripheral handle #%d error message (by recv: %s).\n", i,
                strerror(errno));
            if (errno == ENOTCONN)
              {
                printf("Disconnected socket #%d, handle %d.\n", i,
                    Listeners[i].Listener);
                Listeners[i].disabled = 1;
              }
          }
        else if (j > 0)
          {
            received += j;
            inPacketSizes[i] += j;
          }
      }
  // Send data.  I suppose I should check that the entire packet is sent,
  // and do something about it if not, but I'm not sure what.  I'll
  // come back to that later.
  for (i = 0; i < NumListeners; i++)
    if (!Listeners[i].disabled)
      {
        j = send(Listeners[i].Listener, outPacket, outPacketSize, MSG_NOSIGNAL);
        if (j == -1)
          {
            printf("Peripheral handle #%d error message (by send: %s).\n", i,
                strerror(errno));
            if (errno == ENOTCONN || errno == EPIPE)
              {
                printf("Disconnected socket #%d, handle %d.\n", i,
                    Listeners[i].Listener);
                Listeners[i].disabled = 1;
              }
          }
        else if (j >= 0 && j < outPacketSize)
          printf("Message to peripheral handle #%d incomplete.\n", i);
      }
  // The following is just a little trick:  a delay to allow the UI
  // some time to take care of its business after receiving the data.
  // Undoubtedly there are better ways of handling this.
  if (outPacketSize)
    {
      void
      sleepMilliseconds(unsigned Milliseconds);
      sleepMilliseconds(5);
    }

  // Parse the received data.  All we have to do is to read any
  // inputs and stick them in the global "state" structure, where
  // the LVDC emulation will see them at some point.  By the way,
  // note that while there could be buffered output data even if
  // received==0, previous iterations will have insured that there
  // can't be enough buffered yet to form complete packets.
  if (received != 0)
    {
      //printf("Received %d bytes.\n", received);
      for (i = 0; i < NumListeners; i++)
        {
          int size, offsetIntoPacket, offsetIntoBuffer, offset;
          int source, isMask, ioType, channel, value, firstByte;
          if (Listeners[i].disabled)
            continue;
          size = inPacketSizes[i];
          offsetIntoBuffer = 0;
          offset = 0;
          retry: ;
          offsetIntoPacket = 0;
          for (; offset < size; offset++)
            {
              uint8_t currentByte;
              currentByte = inPackets[i][offset];
              //printf("%02X %03o\n", currentByte, currentByte);
              // Check for corruption.
              firstByte = currentByte & 0x80;
              if (!firstByte && !offsetIntoPacket)
                {
                  printf("Corrupt input packet.\n");
                  offset++;
                  // Mark corrupted stuff for removal from buffer.
                  offsetIntoBuffer = offset;
                  goto retry;
                }
              else if (firstByte && offsetIntoPacket)
                {
                  printf("Corrupt input packet.\n");
                  // Mark corrupted stuff for removal from buffer.
                  offsetIntoBuffer = offset;
                  goto retry;
                }
              switch (offsetIntoPacket)
                {
              case 0:
                offsetIntoPacket++;
                isMask = 0x40 & currentByte;
                ioType = 0x07 & (currentByte >> 3);
                source = 0x07 & currentByte;
                break;
              case 1:
                offsetIntoPacket++;
                channel = 0x7F & currentByte;
                break;
              case 2:
                offsetIntoPacket++;
                channel |= ((currentByte << 2) & 0x180);
                value = (currentByte & 0x1F) << 21;
                break;
              case 3:
                offsetIntoPacket++;
                value |= currentByte << 14;
                break;
              case 4:
                offsetIntoPacket++;
                value |= currentByte << 7;
                break;
              case 5:
                offsetIntoPacket = 0;
                value |= currentByte;
                // If we've gotten to here, then it means that the packet has
                // been completely parsed, and we can mark everything so-far
                // processed for removal from the input buffer.
                offsetIntoBuffer = offset + 1;
                // If this is a mask, we can't do anything with it immediately,
                // and so set it aside for later.  Otherwise, apply the pending
                // mask (if any) to the data and update the i/o buffers in
                // the state structure.
                if (isMask)
                  {
                    pendingMasks[i].valid = 1;
                    pendingMasks[i].source = source;
                    pendingMasks[i].ioType = ioType;
                    pendingMasks[i].channel = channel;
                    pendingMasks[i].mask = value;
                  }
                else
                  {
                    int mask = 0377777777;
                    if (pendingMasks[i].valid)
                      {
                        if (pendingMasks[i].source != source)
                          printf("Input mask does not match source.\n");
                        else if (pendingMasks[i].ioType != ioType)
                          printf("Input mask does not match i/o type.\n");
                        else if (pendingMasks[i].channel != channel)
                          printf("Input mask does not match channel.\n");
                        else
                          mask = pendingMasks[i].mask;
                        pendingMasks[i].valid = 0;
                      }
                    switch (ioType)
                      {
                    case 0: // PIO
                      if (channel < 0 || channel > 0777)
                        printf("Input PIO channel out of range.\n");
                      printf(
                          "PIO-%03o changed from %09o to %09o with mask %09o.\n",
                          channel, state.pio[channel], value, mask);
                      state.pio[channel] = (state.pio[channel] & ~mask)
                          | (value & mask);
                      break;
                    case 1: // CIO
                      if (channel < 0 || channel > 0777)
                        printf("Input CIO channel out of range.\n");
                      printf(
                          "CIO-%03o changed from %09o to %09o with mask %09o.\n",
                          channel, state.cio[channel], value, mask);
                      state.cio[channel] = (state.pio[channel] & ~mask)
                          | (value & mask);
                      break;
                    case 2: // PRS
                      printf(
                          "PRS data received from peripheral, which is not allowed.\n");
                      break;
                    case 3: // INT
                      printf(
                          "INT data received from peripheral, which is not yet implemented.\n");
                      break;
                    case 4: // Commands directly from PTC panel emulation
                      if (channel == 0)
                        {
                          printf("PTC panel commands halt.\n");
                          panelPause = 2;
                        }
                      else if (channel == 1)
                        {
                          printf("PTC panel commands resumption.\n");
                          panelPause = 4;
                        }
                      else if (channel == 2)
                        {
                          int residual, opcode;
                          panelPatternDM = (value >> 17) & 1;
                          panelPatternDS = (value >> 20) & 017;
                          residual = (value >> 4) & 1;
                          panelPatternDLOC = (value >> 5) & 0377;
                          opcode = value & 017;
                          printf(
                              "PTC panel new DATA ADDRESS %o-%02o, OPCODE=%02o, OPERAND=%03o.\n",
                              panelPatternDM, panelPatternDS, opcode,
                              (residual << 8) | panelPatternDLOC);
                          panelPatternDataAddress = value;
                          if (residual)
                            {
                              if (ptc)
                                panelPatternDM = 0;
                              panelPatternDS = 017;
                            }
                          needStatus = 3;
                        }
                      else if (channel == 3)
                        {
                          printf(
                              "PTC panel new INSTRUCTION ADDRESS %o-%02o-%o-%03o.\n",
                              (value >> 25) & 1, (value >> 2) & 017,
                              (value >> 6) & 1, (value >> 7) & 0377);
                          panelPatternInstructionAddress = value;
                          needStatus = 4;
                        }
                      else if (channel == 4)
                        {
                          printf("PTC new DATA %09o.\n", value);
                          panelPatternDataValue = value;
                          state.core[panelPatternDM][panelPatternDS][2][panelPatternDLOC] =
                              value;
                          state.core[panelPatternDM][panelPatternDS][0][panelPatternDLOC] =
                              -1;
                          state.core[panelPatternDM][panelPatternDS][1][panelPatternDLOC] =
                              -1;
                          needStatus = 5;
                        }
                      else if (channel == 5)
                        {
                          /*
                           char *dsStrings[] =
                           { "NONE", "A13 IA", "A13 DATA", "TRS" };
                           char *acStrings[] =
                           { "INS", "DATA" };
                           char *mcStrings[] =
                           { "PROG-CYCLE REPEAT", "PROG-CYCLE SINGLE-STEP",
                           "PROG-CYCLE ADR-HOLD", "DISPLAY SINGLE",
                           "DISPLAY REPEAT" };
                           */
                          panelDisplayModePlus = value;
                          panelModeControl = value & 7;
                          panelAddressCompare = (value >> 3) & 1;
                          panelDisplaySelect = (value >> 4) & 3;
                          if (panelModeControl >= 3)
                            panelPause = 0;
                          /*
                           printf(
                           "PTC panel:  DISPLAY SELECT = %s, ADDRESS COMPARE = %s, MODE CONTROL = %s\n",
                           dsStrings[panelDisplaySelect],
                           acStrings[panelAddressCompare],
                           mcStrings[panelModeControl]);
                           */
                        }
                      else if (channel == 0603)
                        {
                          printf("PTC panel requesting single step.\n");
                          panelPause = 1;
                        }
                      else if (channel == 0604)
                        {
                          printf("PTC panel requesting reset.\n");
                          //state.restart = 1;
                          state.hop = 0;
                          panelPause = 0;
                        }
                      else if (channel == 0605)
                        {
                          printf("PTC panel requesting status.\n");
                          needStatus = 1;
                        }
                      else if (channel == 0606)
                        {
                          printf("PTC typewriter carriage width %d.\n", value);
                          typewriterMargin = value;
                        }
                      else if (channel == 0607)
                        {
                          printf("PTC typewriter tab width %d.\n", value);
                          typewriterTabStop = value;
                        }
                      else
                        printf("Command %03o/%09o received from PTC panel\n",
                            channel, value);
                      break;
                    case 5: // Status sent directly from CPU to PTC panel emulation.
                      printf(
                          "Illegal status info received ... should be output only.\n");
                      break;
                    default:
                      printf("Unrecognized input i/o type.\n");
                      break;
                      }
                  }
                break;
              default:
                break;
                }
            }
          // Everything from the input buffer than cat be processed has
          // been.  So removed that stuff from the buffer, leaving everything
          // not yet processed.
          if (offsetIntoBuffer > 0)
            {
              size = size - offsetIntoBuffer;
              if (size > 0)
                memcpy(inPackets[i], &inPackets[i][offsetIntoBuffer], size);
              inPacketSizes[i] = size;
            }
        }
    }

  // If we've gotten to this point and needStatus is non-zero, due to something
  // received from the panel or (I suppose) other peripherals that requires
  // informing the panel of the changed status.  We want to take care of this
  // right away rather than sitting through another potential instruction cycle.
  if (needStatus)
    goto retryStatus;
  return (0);
}

/////////////////////////////////////////////////////////////////////////////////
// Portable functions (*NIX and Win32) for working with sockets.

// Used for socket-operation error codes.
int virtualWireErrorCodes = 0;

/*
 The usage is this:

 1. Servers:  Create a socket, suitable for clients to connect to,
 with EstablishSocket().  Call accept(,NULL,NULL) to listen for a new
 client; the function will return whether or not there is a new
 listener, with either -1 (no new client) or else the new socket
 number (>=0); the first parameter is the socket number created by
 EstablishSocket().   The socket should be unblocked with UnblockSocket().

 2. Clients:  Connect to a server with CallSocket().  The function will
 return whether or not the connection succeeded, with either -1
 (failure) or else with the connection socket number.

 3. Either the client or server can then proceed to perform i/o using
 send() or recv() to all connected servers or clients.

 Server example:

 int ServerBaseSocket;
 #define MAX_LISTENERS 5
 int ListeningSockets[MAX_LISTENERS], NumListeners = 0;
 int PortNum = ... something ...;
 int i, j;

 ServerBaseSocket = EstablishSocket (PortNum, MAX_LISTENERS);
 if (ServerBaseSocket == -1)
 ... unrecoverable error ...
 // Main activity loop
 for (;;)
 {
 ...
 // Periodically do this:
 if (NumListeners < MAX_LISTENERS)
 {
 i = accept (ServerBaseSocket, NULL, NULL);
 if (i != -1)
 {
 UnblockSocket (i);
 Listeners[NumListeners++] = i;
 }
 }
 ...
 // Receive data.
 for (i = 0; i < NumListeners; i++)
 ... receive data from listener i using recv() ...
 ...
 // Send data.
 for (i = 0; i < NumListeners; i++)
 ... transmit data to listener i using send() ...
 ...
 }

 Client Example:

 int ConnectionSocket = -1;

 // Main activity loop.
 for (;;)
 {
 ...
 // Try for a connection.
 if (ConnectionSocket == -1)
 ConnectionSocket = CallSocket (Hostname, Portnum);
 ...
 // Perform i/o with send/recv:
 if (ConnectionSocket != -1)
 ... send/recv ...
 ...
 }

 These examples don't illustrate what to do in case of broken
 connections.  (I don't actually KNOW what to do.)

 */

//----------------------------------------------------------------------
// The following are the only two socket functions that internally differ
// between *nix and Win32.
// Initialize the socket system.  Return 0 on success.
static int SocketSystemInitialized = 0;
int
InitializeSocketSystem(void)
{
  if (SocketSystemInitialized)
    return (0);
  SocketSystemInitialized = 1;
#if defined(unix) || (defined(__APPLE__) && defined(__MACH__))
  return (0);
#else
  WSADATA wsaData;
  return (WSAStartup (MAKEWORD (2, 0), &wsaData));
#endif
}

// Set an existing socket to be non-blocking.
void
UnblockSocket(int SocketNum)
{
#if defined(unix) || (defined(__APPLE__) && defined(__MACH__))
  fcntl(SocketNum, F_SETFL, O_NONBLOCK);
#else
  unsigned long nonBlock = 1;
  ioctlsocket (SocketNum, FIONBIO, &nonBlock);
#endif
}

//----------------------------------------------------------------------
// Function for creating a socket.  Copied from
// http://world.std.com/~jimf/papers/sockets/sockets.html, and then
// modified somewhat for my own purposes.  The parameters:
//
//      portnum         The port on which we're going to listen.
//      MaxClients      Max number of queued connections.  (5 is good.)
//
// Returns -1 on error, or the new socket number (>=0) if successful.

#define MAXHOSTNAME 256
int
EstablishSocket(unsigned short portnum, int MaxClients)
{
  char myname[MAXHOSTNAME + 1];
  int s, i;
  struct sockaddr_in sa;
  struct hostent *hp;

  InitializeSocketSystem();

  memset(&sa, 0, sizeof(struct sockaddr_in)); /* clear our address */
  gethostname(myname, MAXHOSTNAME); /* who are we? */
  hp = gethostbyname(myname); /* get our address info */
  if (hp == NULL) /* we don't exist !? */
    {
      char s[32];
      switch (h_errno)
        {
      case HOST_NOT_FOUND:
        strcpy(s, "Host not found");
        break;
      case NO_ADDRESS:
        strcpy(s, "No address");
        break;
        //case NO_DATA: strcpy(s, "No data"); break;
      case NO_RECOVERY:
        strcpy(s, "No recovery");
        break;
      case TRY_AGAIN:
        strcpy(s, "Try again");
        break;
      default:
        sprintf(s, "Error %d", h_errno);
        break;
        }
      fprintf(stderr, "gethostbyname (\"%s\" %d) reports %s\n", myname, portnum,
          s);
      virtualWireErrorCodes = 0x101;
      return (-1);
    }
  sa.sin_family = hp->h_addrtype; /* this is our host address */
  sa.sin_port = htons(portnum); /* this is our port number */
  if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) /* create socket */
    {
      virtualWireErrorCodes = 0x102;
      return (-1);
    }

  // Make sure to clean up after any previous disconnects of the
  // port.  Otherwise there would be a timeout until we could
  // reuse the port.
  i = 1;
  setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *) &i, sizeof(int));

  if (bind(s, (struct sockaddr *) &sa, sizeof(struct sockaddr_in)) < 0)
    {
#if defined(unix) || (defined(__APPLE__) && defined(__MACH__))
      close(s);
#else
      closesocket (s);
#endif
      virtualWireErrorCodes = 0x103;
      return (-1); /* bind address to socket */
    }
  listen(s, MaxClients); /* max # of queued connects */
  // Don't want to wait when there's no incoming data.
  UnblockSocket(s);
  return (s);
}

//----------------------------------------------------------------------
// Client connection to server via socket.
// http://world.std.com/~jimf/papers/sockets/sockets.html.
// The hostname is the name of the server, either resolvable by DNS,
// or else a dotted IP number.  (The latter fails on Win32.)
// The portnum is the port-number on which the server listens.

int
CallSocket(char *hostname, unsigned short portnum)
{
  struct sockaddr_in sa;
  struct hostent *hp;
  //int a;
  int s;

  InitializeSocketSystem();

  if ((hp = gethostbyname(hostname)) == NULL)
    {
      /* do we know the host's */
      //errno= ECONNREFUSED; /* address? */
      virtualWireErrorCodes = 0x301;
      return (-1); /* no */
    }

  memset(&sa, 0, sizeof(sa));
  memcpy((char *) &sa.sin_addr, hp->h_addr, hp->h_length); /* set address */
  sa.sin_family = hp->h_addrtype;
  sa.sin_port = htons((u_short) portnum);
  if ((s = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0) /* get socket */
    {
      virtualWireErrorCodes = 0x302;
      return (-1);
    }
  if (connect(s, (struct sockaddr *) &sa, sizeof(sa)) < 0)
    {
      /* connect */
#if defined(unix) || (defined(__APPLE__) && defined(__MACH__))
      close(s);
#else
      closesocket (s);
#endif
      virtualWireErrorCodes = 0x303;
      return (-1);
    }
  UnblockSocket(s);
  return (s);
}
back to top