1
0
mirror of https://github.com/livingcomputermuseum/IFS.git synced 2026-03-10 04:44:19 +00:00

Adding built-in support for Ken Shirriff's BeagleBone-based Alto Ethernet Interface.

Adding revised version of MicrocodeBootRequest, to support booting Dolphin and Dorado hardware.
This commit is contained in:
Josh Dersch
2023-09-30 22:45:05 -07:00
parent 86ee8f8844
commit 4d992b1bd7
16 changed files with 1088 additions and 342 deletions

View File

@@ -10,11 +10,9 @@ CopyDiskRoot = c:\ifs\copydisk
BootRoot = c:\ifs\boot
MailRoot = c:\ifs\mail
# InterfaceType defines the type of interface for local networking,
# (either RAW or UDP)
# and must be the same as other devices or software on the network that
# wishes to talk to this server.
InterfaceType = raw
# InterfaceType defines the type of interface(s) to use for local networking,
# (one or more of RAW, UDP, or 3MBIT)
InterfaceTypes = raw
# The name of the network interface to use for local networking.
# This is the name reported by "ipconfig"
@@ -25,6 +23,9 @@ InterfaceName = Ethernet
# (gateway ports are specified in networks.txt)
UDPPort = 42424
# Whether to run IFS services or just bridge interfaces
RunIFSServices = true
# Defines the address for this IFS server.
# An entry for this IFS server's network must be
# present in networks.txt

View File

@@ -83,9 +83,9 @@ namespace IFS
}
/// <summary>
/// The type of interface (UDP or RAW) to communicate over
/// The type of interface(s) (UDP, RAW, or 3mbit) to communicate over
/// </summary>
public static readonly string InterfaceType;
public static readonly string InterfaceTypes;
/// <summary>
/// The name of the network interface to use
@@ -97,6 +97,11 @@ namespace IFS
/// </summary>
public static readonly int UDPPort;
/// <summary>
/// Whether to run IFS Services or just bridge interfaces.
/// </summary>
public static readonly bool RunIFSServices;
/// <summary>
/// The network that this server lives on
/// </summary>
@@ -208,6 +213,13 @@ namespace IFS
{
switch (field.FieldType.Name)
{
case "Boolean":
{
bool b = bool.Parse(value);
field.SetValue(null, b);
}
break;
case "Int32":
{
int v = int.Parse(value);
@@ -248,7 +260,7 @@ namespace IFS
"ifs.cfg line {0}: Unknown configuration parameter '{1}'.", lineNumber, parameter);
}
}
}
}
}
}
}

View File

@@ -15,20 +15,12 @@
along with IFS. If not, see <http://www.gnu.org/licenses/>.
*/
using IFS.Boot;
using IFS.CopyDisk;
using IFS.FTP;
using IFS.Gateway;
using IFS.IfsConsole;
using IFS.Transport;
using PcapDotNet.Core;
using PcapDotNet.Core.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;
namespace IFS
{
@@ -36,9 +28,9 @@ namespace IFS
{
static void Main(string[] args)
{
PrintHerald();
PrintHerald();
RegisterInterface();
RegisterInterfaces();
// This runs forever, or until the user tells us to exit.
RunCommandPrompt();
@@ -51,61 +43,69 @@ namespace IFS
private static void PrintHerald()
{
Console.WriteLine("LCM+L IFS v0.3, 11/17/2020.");
Console.WriteLine("(c) 2015-2020 Living Computers: Museum+Labs.");
Console.WriteLine("Bug reports to joshd@livingcomputers.org");
Console.WriteLine($"LCM+L IFS {typeof(Entrypoint).Assembly.GetName().Version}, 9/30/2023");
Console.WriteLine("(c) 2015-2020 Living Computers: Museum+Labs, 2020-2023 Josh Dersch");
Console.WriteLine();
Console.WriteLine();
}
}
private static void RegisterInterface()
private static void RegisterInterfaces()
{
bool bFound = false;
switch (Configuration.InterfaceType.ToLowerInvariant())
string[] selectedInterfaces = Configuration.InterfaceTypes.ToLowerInvariant().Split(new char[] { ' ', '\t', ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string ifaceName in selectedInterfaces)
{
case "udp":
// Find matching network interface
{
NetworkInterface[] ifaces = NetworkInterface.GetAllNetworkInterfaces();
foreach(NetworkInterface iface in ifaces)
switch (ifaceName)
{
case "udp":
// Find matching network interface
{
if (iface.Name.ToLowerInvariant() == Configuration.InterfaceName.ToLowerInvariant())
NetworkInterface[] ifaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface iface in ifaces)
{
Router.Instance.RegisterUDPInterface(iface);
bFound = true;
break;
if (iface.Name.ToLowerInvariant() == Configuration.InterfaceName.ToLowerInvariant())
{
Router.Instance.RegisterUDPInterface(iface);
bFound = true;
break;
}
}
}
}
break;
break;
case "raw":
// Find matching RAW interface
{
foreach (LivePacketDevice device in LivePacketDevice.AllLocalMachine)
case "raw":
// Find matching RAW interface
{
if (device.GetNetworkInterface() != null &&
device.GetNetworkInterface().Name.ToLowerInvariant() == Configuration.InterfaceName.ToLowerInvariant())
foreach (LivePacketDevice device in LivePacketDevice.AllLocalMachine)
{
Router.Instance.RegisterRAWInterface(device);
bFound = true;
break;
if (device.GetNetworkInterface() != null &&
device.GetNetworkInterface().Name.ToLowerInvariant() == Configuration.InterfaceName.ToLowerInvariant())
{
Router.Instance.RegisterRAWInterface(device);
bFound = true;
break;
}
}
}
}
break;
}
break;
default:
case "3mbit":
Router.Instance.RegisterBeagleBoneInterface();
break;
default:
throw new InvalidConfigurationException(
String.Format("The specified interface type ({0}) is invalid.", Configuration.InterfaceTypes));
}
// Not found.
if (!bFound)
{
throw new InvalidConfigurationException(
String.Format("The specified interface type ({0}) is invalid.", Configuration.InterfaceType));
}
// Not found.
if (!bFound)
{
throw new InvalidConfigurationException(
String.Format("The specified network interface ({0}) is invalid or unusable by IFS.", Configuration.InterfaceName));
String.Format("The specified network interface ({0}) is invalid or unusable by IFS.", Configuration.InterfaceName));
}
}
}

View File

@@ -28,7 +28,7 @@ using System.Threading;
namespace IFS.Gateway
{
public delegate void RoutePupCallback(PUP pup, bool route);
public delegate void ReceivedPacketCallback(MemoryStream packetStream, IPacketInterface receivingInterface);
/// <summary>
/// Implements gateway services, routing PUPs intended for other networks to
@@ -50,6 +50,7 @@ namespace IFS.Gateway
{
_localProtocolDispatcher = new PUPProtocolDispatcher();
_routingTable = new RoutingTable();
_packetInterfaces = new List<IPacketInterface>();
//
// Look up our own network in the table and get our port.
@@ -96,7 +97,11 @@ namespace IFS.Gateway
public void Shutdown()
{
_localProtocolDispatcher.Shutdown();
_pupPacketInterface.Shutdown();
foreach (IPacketInterface iface in _packetInterfaces)
{
iface.Shutdown();
}
if (_gatewayUdpClient != null)
{
@@ -109,19 +114,22 @@ namespace IFS.Gateway
public void RegisterRAWInterface(LivePacketDevice iface)
{
Ethernet enet = new Ethernet(iface);
_pupPacketInterface = enet;
_rawPacketInterface = enet;
_pupPacketInterface.RegisterRouterCallback(RouteIncomingLocalPacket);
_packetInterfaces.Add(enet);
enet.RegisterRouterCallback(HandleIncomingPacket);
}
public void RegisterUDPInterface(NetworkInterface iface)
{
UDPEncapsulation udp = new UDPEncapsulation(iface);
_packetInterfaces.Add(udp);
udp.RegisterRouterCallback(HandleIncomingPacket);
}
_pupPacketInterface = udp;
_rawPacketInterface = udp;
_pupPacketInterface.RegisterRouterCallback(RouteIncomingLocalPacket);
public void RegisterBeagleBoneInterface()
{
Ether3MbitInterface bbInterface = new Ether3MbitInterface();
_packetInterfaces.Add(bbInterface);
bbInterface.RegisterRouterCallback(HandleIncomingPacket);
}
/// <summary>
@@ -142,9 +150,8 @@ namespace IFS.Gateway
/// <param name="frameType"></param>
public void Send(byte[] data, byte source, byte destination, ushort frameType)
{
if (_rawPacketInterface != null)
{
_rawPacketInterface.Send(data, source, destination, frameType);
foreach(IPacketInterface iface in _packetInterfaces) {
iface.Send(data, source, destination, frameType);
}
}
@@ -163,7 +170,10 @@ namespace IFS.Gateway
if (p.DestinationPort.Network == 0 ||
p.DestinationPort.Network == DirectoryServices.Instance.LocalNetwork)
{
_pupPacketInterface.Send(p);
foreach (IPacketInterface iface in _packetInterfaces)
{
iface.Send(p);
}
}
else
{
@@ -204,6 +214,57 @@ namespace IFS.Gateway
}
}
/// <summary>
/// Handles an encapsulated 3mbit frame incoming from the receiver.
/// </summary>
/// <param name="packetStream"></param>
private void HandleIncomingPacket(MemoryStream packetStream, IPacketInterface receivingInterface)
{
// Read the length prefix (in words), convert to bytes.
// Subtract off 2 words for the ethernet header
int length = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte())) * 2 - 4;
// Read the address (1st word of 3mbit packet)
byte destination = (byte)packetStream.ReadByte();
byte source = (byte)packetStream.ReadByte();
// Read the type and switch on it
int etherType3mbit = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte()));
//
// Ensure this is a packet we're interested in.
//
if (Configuration.RunIFSServices && // We're servicing packets
etherType3mbit == PupPacketBuilder.PupFrameType && // it's a PUP
(destination == DirectoryServices.Instance.LocalHost || // for us, or...
destination == 0)) // broadcast
{
try
{
PUP pup = new PUP(packetStream, length);
RouteIncomingLocalPacket(pup, destination != 0);
}
catch (Exception e)
{
// An error occurred, log it.
Log.Write(LogType.Error, LogComponent.PUP, "Error handling PUP: {0}", e.Message);
}
}
else if (!Configuration.RunIFSServices)
{
// Bridge the packet through all registered interfaces other than the one it came in on
foreach (IPacketInterface iface in _packetInterfaces)
{
if (iface != receivingInterface)
{
packetStream.Seek(0, SeekOrigin.Begin);
Console.WriteLine("Sending to {0}", iface);
iface.Send(packetStream);
}
}
}
}
private void RoutePacketExternally(PUP p)
{
RoutingTableEntry destinationNetworkEntry = _routingTable.GetAddressForNetworkNumber(p.DestinationPort.Network);
@@ -270,7 +331,10 @@ namespace IFS.Gateway
if (p.DestinationPort.Host == DirectoryServices.Instance.LocalHostAddress.Host || // us specifically
p.DestinationPort.Host == 0) // broadcast
{
_localProtocolDispatcher.ReceivePUP(p);
if (Configuration.RunIFSServices)
{
_localProtocolDispatcher.ReceivePUP(p);
}
}
//
@@ -279,7 +343,10 @@ namespace IFS.Gateway
if (p.DestinationPort.Host != DirectoryServices.Instance.LocalHostAddress.Host || // not us
p.DestinationPort.Host == 0) // broadcast
{
_pupPacketInterface.Send(p);
foreach (IPacketInterface iface in _packetInterfaces)
{
iface.Send(p);
}
}
}
else
@@ -395,14 +462,9 @@ namespace IFS.Gateway
}
/// <summary>
/// Our interface to a facility that can transmit/receive PUPs
/// The various interfaces we use to send and receive 3mbit ethernet packets, encapsulated or otherwise.
/// </summary>
private IPupPacketInterface _pupPacketInterface;
/// <summary>
/// Our interface to a facility that can transmit raw Ethernet frames
/// </summary>
private IRawPacketInterface _rawPacketInterface;
private List<IPacketInterface> _packetInterfaces;
/// <summary>
/// Our UdpClient for sending PUPs to external networks.

View File

@@ -124,11 +124,13 @@
<Compile Include="MiscServicesProtocol.cs" />
<Compile Include="Serializer.cs" />
<Compile Include="SocketIDGenerator.cs" />
<Compile Include="Transport\3MbitAdapter.cs" />
<Compile Include="Transport\Ethernet.cs" />
<Compile Include="PUPProtocolBase.cs" />
<Compile Include="PUP.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PUPProtocolDispatcher.cs" />
<Compile Include="Transport\PacketBuilder.cs" />
<Compile Include="Transport\PacketInterface.cs" />
<Compile Include="Transport\UDP.cs" />
</ItemGroup>
@@ -151,6 +153,12 @@
<Content Include="Conf\hosts.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="etherdata.bin">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="ethertext.bin">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View File

@@ -43,8 +43,9 @@ namespace IFS.Logging
UDP = 0x800,
Mail = 0x1000,
Routing = 0x2000,
E3Mbit = 0x4000,
Configuration = 0x4000,
Configuration = 0x8000,
All = 0x7fffffff
}

View File

@@ -24,8 +24,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace IFS
{
@@ -71,7 +70,7 @@ namespace IFS
{
public MiscServicesProtocol()
{
}
/// <summary>
@@ -139,8 +138,8 @@ namespace IFS
//
DateTime currentTime = DateTime.Now;
byte[] timeString = Helpers.StringToArray(currentTime.ToString("dd-MMM-yy HH:mm:ss"));
byte[] timeString = Helpers.StringToArray(currentTime.ToString("dd-MMM-yy HH:mm:ss"));
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.SourcePort.Socket);
PUP response = new PUP(PupType.StringTimeReply, p.ID, p.SourcePort, localPort, timeString);
@@ -148,7 +147,7 @@ namespace IFS
}
private void SendAltoTimeReply(PUP p)
{
{
// So the Alto epoch is 1/1/1901. For the time being to keep things simple we're assuming
// GMT and no DST at all. TODO: make this take into account our TZ, etc.
//
@@ -167,8 +166,8 @@ namespace IFS
// The epoch for .NET is 1/1/0001 at 12 midnight and is counted in 100-ns intervals.
// Some conversion is needed, is what I'm saying.
DateTime altoEpoch = new DateTime(1901, 1, 1);
DateTime altoEpoch = new DateTime(1901, 1, 1);
TimeSpan timeSinceAltoEpoch = new TimeSpan(currentTime.Ticks - altoEpoch.Ticks);
UInt32 altoTime = (UInt32)timeSinceAltoEpoch.TotalSeconds;
@@ -178,9 +177,9 @@ namespace IFS
time.DateTime = altoTime;
time.TimeZone = 0; // Hardcoded to GMT
time.DSTStart = 366; // DST not specified yet
time.DSTEnd = 366;
time.DSTEnd = 366;
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
// Response must contain our network number; this is used to tell clients what network they're on if they don't already know.
PUPPort remotePort = new PUPPort(DirectoryServices.Instance.LocalNetwork, p.SourcePort.Host, p.SourcePort.Socket);
@@ -218,7 +217,7 @@ namespace IFS
// We have a result, pack the name into the response.
// NOTE: This is *not* a BCPL string, just the raw characters.
byte[] interNetworkName = Helpers.StringToArray(hostName);
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, p.DestinationPort.Socket);
PUP lookupReply = new PUP(PupType.AddressLookupResponse, p.ID, p.SourcePort, localPort, interNetworkName);
@@ -239,8 +238,8 @@ namespace IFS
{
//
// For the request PUP:
// A string consisting of an inter-network name expression.
// NOTE: This is *not* a BCPL string, just the raw characters.
// A string consisting of an inter-network name expression.
// NOTE: This is *not* a BCPL string, just the raw characters.
//
// Response:
// One or more 6-byte blocks containing the address(es) corresponding to the
@@ -315,7 +314,7 @@ namespace IFS
List<BootFileEntry> bootFiles = BootServer.EnumerateBootFiles();
foreach(BootFileEntry entry in bootFiles)
foreach (BootFileEntry entry in bootFiles)
{
BootDirectoryBlock block;
block.FileNumber = entry.BootNumber;
@@ -323,7 +322,7 @@ namespace IFS
block.FileName = new BCPLString(entry.Filename);
byte[] serialized = Serializer.Serialize(block);
//
// If this block fits into the current PUP, add it to the stream, otherwise send off the current PUP
// and start a new one.
@@ -375,7 +374,7 @@ namespace IFS
int passwordOffset = (userName.Length % 2) == 0 ? userName.Length : userName.Length + 1;
string password = Helpers.MesaArrayToString(p.Contents, passwordOffset + 4);
UserToken token = Authentication.Authenticate(userName, password);
if (token == null)
@@ -411,7 +410,7 @@ namespace IFS
// If mailbox name has a host/registry appended, we will strip it off.
// TODO: probably should validate host...
//
mailboxName = Authentication.GetUserNameFromFullName(mailboxName);
mailboxName = Authentication.GetUserNameFromFullName(mailboxName);
IEnumerable<string> mailList = MailManager.EnumerateMail(mailboxName);
@@ -433,31 +432,39 @@ namespace IFS
private void SendMicrocodeResponse(PUP p)
{
//
// TODO; validate that this is a request for V1 of the protocol (I don't think there was ever another version...)
//
//
// The request PUP contains the file number in the lower-order 16-bits of the pup ID.
// Assuming the number is a valid bootfile, we start sending it to the client's port via EFTP.
//
ushort fileNumber = (ushort)p.ID;
ushort version = (ushort)(p.ID >> 16);
Log.Write(LogType.Verbose, LogComponent.MiscServices, "Microcode request is for file {0}.", fileNumber);
Log.Write(LogType.Verbose, LogComponent.MiscServices, "Microcode request (version {0}) is for file {1}.", version, Helpers.ToOctal(fileNumber));
FileStream microcodeFile = BootServer.GetStreamForNumber(fileNumber);
if (microcodeFile == null)
{
Log.Write(LogType.Warning, LogComponent.MiscServices, "Microcode file {0} does not exist or could not be opened.", fileNumber);
Log.Write(LogType.Warning, LogComponent.MiscServices, "Microcode file {0} does not exist or could not be opened.", Helpers.ToOctal(fileNumber));
}
else
{
// Send the file. The MicrocodeReply protocol is extremely simple:
// Send the file asynchronously. The MicrocodeReply protocol is extremely simple:
// Just send a sequence of MicrocodeReply PUPs containing the microcode data,
// there are no acks or flow control of any kind.
Log.Write(LogType.Warning, LogComponent.MiscServices, "Sending microcode file {0}.", fileNumber);
SendMicrocodeFile(p.SourcePort, microcodeFile);
ThreadPool.QueueUserWorkItem((ctx) =>
{
Log.Write(LogType.Warning, LogComponent.MiscServices, "Sending microcode file {0} ('{1}').", Helpers.ToOctal(fileNumber), microcodeFile.Name);
SendMicrocodeFile(p.SourcePort, microcodeFile, fileNumber == 0x100 /* test for Initial.eb */);
}, null);
}
}
private void SendMicrocodeFile(PUPPort sourcePort, Stream microcodeFile)
private void SendMicrocodeFile(PUPPort sourcePort, Stream microcodeFile, bool sendEmptyPacket)
{
//
// "For version 1 of the protocol, a server willing to supply the data simply sends a sequence of packets
@@ -467,25 +474,48 @@ namespace IFS
// acknowledgments. This protocol is used by Dolphins and Dorados.
// Currently, the version 1 servers send packets containing 3 * n words of data. This constraint is imposed by the
// Rev L Dolphin EPROM microcode. Id like to remove this restriction if I get a chance, so please dont take
// advantage of it unless you need to.The Rev L Dolphin EPROM also requires the second word of the source
// advantage of it unless you need to. The Rev L Dolphin EPROM also requires the second word of the source
// socket to be 4. / HGM May - 80."
//
// TODO: this should happen in a worker thread.
//
// Skip the first 256 header words in the microcode file.
microcodeFile.Seek(512, SeekOrigin.Begin);
//
// We send 192 words of data per PUP (3 * 64) in an attempt to make the Dolphin happy.
// We send 258 words of data per PUP (3 * 86) in an attempt to make the Dolphin happy.
// This is what the original Xerox IFS code did.
// We space these out a bit to give the D-machine time to keep up, we're much much faster than they are.
//
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, 4);
PUPPort localPort = new PUPPort(DirectoryServices.Instance.LocalHostAddress, SocketIDGenerator.GetNextSocketID() << 16 | 0x4);
byte[] buffer = new byte[384];
bool done = false;
uint id = 0;
//
// Send an empty packet to start the transfer. The prom boot microcode will explicitly ignore this.
// Note that this is not documented in the (meager) protocol docs, nor does the BCPL IFS code
// appear to actually send such a packet, at least not explicitly.
//
// Further:
// D0 Initial's E3Boot doesn't like the empty packet, and assumes it means the end of the microcode reply; it then
// tries to load a 0-length microcode file into CS and falls over.
// The below hacks around it (it only sends the empty packet when the Initial microcode file is requested).
// I'm unsure if there's a subtle bug in our IFS code here or elsewhere or a subtle bug in PARC's IFS code; it does kind of seem
// like the microcode is working around a weird issue but those folks were a lot smarter than I.
// Addendum 7/28/23:
// After reset, The real D0 seems to occasionally complete the first-stage (Initial) boot without the extra empty packet being sent.
// I wonder if there's a hardware glitch the boot microcode is working around.
// Additionally: the Dorado boot ucode source makes no mention of ignoring an empty packet, nor does the code implement such behavior.
//
if (sendEmptyPacket)
{
Router.Instance.SendPup(new PUP(PupType.MicrocodeReply, 0x10000, sourcePort, localPort, new byte[] { }));
}
uint checksum = 0;
while (!done)
{
byte[] buffer = new byte[258 * 2]; // 258 words, as the original IFS did
int read = microcodeFile.Read(buffer, 0, buffer.Length);
if (read < buffer.Length)
@@ -495,12 +525,23 @@ namespace IFS
if (read > 0)
{
PUP microcodeReply = new PUP(PupType.MicrocodeReply, (id | 0x00010000), sourcePort, localPort, buffer);
// Send ONLY the bytes we read.
byte[] packetBuffer = new byte[read];
Array.Copy(buffer, packetBuffer, read);
PUP microcodeReply = new PUP(PupType.MicrocodeReply, (id | 0x10000), sourcePort, localPort, buffer);
Router.Instance.SendPup(microcodeReply);
Log.Write(LogType.Warning, LogComponent.MiscServices, "Sequence {0} Sent {1} bytes of microcode file", id, read);
for (int i = 0; i < read; i += 2)
{
checksum += (uint)(packetBuffer[i + 1] | (packetBuffer[i] << 8));
}
}
// Pause a bit to give the D0 time to breathe.
System.Threading.Thread.Sleep(5);
// TODO: make this configurable?
System.Threading.Thread.Sleep(10);
id++;
}
@@ -508,10 +549,9 @@ namespace IFS
//
// Send an empty packet to conclude the transfer.
//
PUP endReply = new PUP(PupType.MicrocodeReply, (id | 0x00010000), sourcePort, localPort, new byte[] { });
Router.Instance.SendPup(endReply);
Router.Instance.SendPup(new PUP(PupType.MicrocodeReply, (id | 0x10000), sourcePort, localPort, new byte[] { }));
Log.Write(LogType.Warning, LogComponent.MiscServices, "Microcode file sent.");
Log.Write(LogType.Warning, LogComponent.MiscServices, "Microcode file sent. Checksum {0:x4}", (checksum & 0xffff));
}

View File

@@ -27,7 +27,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Living Computers: Museum+Labs")]
[assembly: AssemblyProduct("IFS")]
[assembly: AssemblyCopyright("Copyright © LCM+L 2015-2020")]
[assembly: AssemblyCopyright("Copyright © LCM+L 2015-2020, Josh Dersch 2020-2023")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -49,5 +49,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.3.0.0")]
[assembly: AssemblyFileVersion("1.3.0.0")]
[assembly: AssemblyVersion("1.4.0.0")]
[assembly: AssemblyFileVersion("1.4.0.0")]

View File

@@ -0,0 +1,655 @@
/*
This file is part of IFS.
IFS is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
IFS 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with IFS. If not, see <http://www.gnu.org/licenses/>.
*/
using IFS.Gateway;
using IFS.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
namespace IFS.Transport
{
/// <summary>
/// This provides a packet interface implementation that talks to Ken Shirriff's 3mbit interface on the BeagleBone.
/// See https://github.com/shirriff/alto-ethernet-interface for the original code. This class effectively replaces
/// the "gateway" C program and talks directly to the PRUs on the beaglebone to exchange packets with the hardware.
/// The PRU data and code files etherdata.bin and ethertext.bin are used to load the PRU with the appropriate
/// 3mbit driver code; these are included with this project and must be placed alongside IFS.exe in order to be
/// found and loaded.
///
/// This code is more or less a direct port of Ken's code over to C#, with a bit of cleanup to make it more palatable
/// for C# coding styles. Though it's still pretty rough.
/// </summary>
public class Ether3MbitInterface : IPacketInterface
{
public Ether3MbitInterface()
{
try
{
InitializePRU();
StartReceiver();
}
catch (Exception e)
{
Log.Write(LogType.Error, LogComponent.E3Mbit, "Failed to initialize the BeagleBone 3Mbit Interface. Error: {0}", e.Message);
return;
}
}
public void RegisterRouterCallback(ReceivedPacketCallback callback)
{
_routerCallback = callback;
}
public void Send(PUP p)
{
byte[] frameData = PupPacketBuilder.BuildEthernetFrameFromPup(p);
SendToNetworkInterface(frameData);
}
public void Send(byte[] data, byte source, byte destination, ushort frameType)
{
byte[] frameData = PupPacketBuilder.BuildEthernetFrameFromRawData(data, source, destination, frameType);
SendToNetworkInterface(frameData);
}
public void Send(MemoryStream encapsulatedPacketStream)
{
byte[] encapsulatedFrameData = encapsulatedPacketStream.ToArray();
// Skip the first two bytes (encapsulated length info). This is annoying.
byte[] frameData = new byte[encapsulatedFrameData.Length - 2];
Array.Copy(encapsulatedFrameData, 2, frameData, 0, frameData.Length);
SendToNetworkInterface(frameData);
}
public void Shutdown()
{
}
private void InitializePRU()
{
Log.Write(LogType.Normal, LogComponent.E3Mbit, "PRU Initialization started.");
PRU.prussdrv_init();
if (PRU.prussdrv_open(PRU.PRU_EVTOUT_0) == -1)
{
throw new InvalidOperationException("Unable to open PRU.");
}
PRU.tpruss_intc_initdata initData;
initData.sysevts_enabled = new byte[]{ PRU.PRU0_PRU1_INTERRUPT, PRU.PRU1_PRU0_INTERRUPT, PRU.PRU0_ARM_INTERRUPT, PRU.PRU1_ARM_INTERRUPT, PRU.ARM_PRU0_INTERRUPT, PRU.ARM_PRU1_INTERRUPT, 15, 0xff };
initData.sysevt_to_channel_map = new PRU.tsysevt_to_channel_map[]
{
new PRU.tsysevt_to_channel_map(PRU.PRU0_PRU1_INTERRUPT, PRU.CHANNEL1),
new PRU.tsysevt_to_channel_map(PRU.PRU1_PRU0_INTERRUPT, PRU.CHANNEL0),
new PRU.tsysevt_to_channel_map(PRU.PRU0_ARM_INTERRUPT, PRU.CHANNEL2),
new PRU.tsysevt_to_channel_map(PRU.PRU1_ARM_INTERRUPT, PRU.CHANNEL3),
new PRU.tsysevt_to_channel_map(PRU.ARM_PRU0_INTERRUPT, PRU.CHANNEL0),
new PRU.tsysevt_to_channel_map(PRU.ARM_PRU1_INTERRUPT, PRU.CHANNEL1),
new PRU.tsysevt_to_channel_map(15, PRU.CHANNEL0),
new PRU.tsysevt_to_channel_map(-1, -1),
};
initData.channel_to_host_map = new PRU.tchannel_to_host_map[]
{
new PRU.tchannel_to_host_map(PRU.CHANNEL0, PRU.PRU0),
new PRU.tchannel_to_host_map(PRU.CHANNEL1, PRU.PRU1),
new PRU.tchannel_to_host_map(PRU.CHANNEL2, PRU.PRU_EVTOUT0),
new PRU.tchannel_to_host_map(PRU.CHANNEL3, PRU.PRU_EVTOUT1),
new PRU.tchannel_to_host_map(-1, -1),
};
initData.host_enable_bitmask = PRU.PRU0_HOSTEN_MASK | PRU.PRU1_HOSTEN_MASK | PRU.PRU_EVTOUT0_HOSTEN_MASK | PRU.PRU_EVTOUT1_HOSTEN_MASK;
PRU.prussdrv_pruintc_init(ref initData);
if (PRU.prussdrv_load_datafile(0, "etherdata.bin") < 0)
{
throw new InvalidOperationException("Unable to load PRU data file 'etherdata.bin'.");
}
if (PRU.prussdrv_exec_program(0, "ethertext.bin") < 0)
{
throw new InvalidOperationException("Unable to load and exec PRU program file 'ethertext.bin'.");
}
if (PRU.prussdrv_map_prumem(PRU.PRUSS0_PRU0_DATARAM, out _sharedPruMemory) < 0)
{
throw new InvalidOperationException("Unable to map PRU shared memory.");
}
Log.Write(LogType.Verbose, LogComponent.E3Mbit, "Shared PRU memory at 0x{0:x}", _sharedPruMemory.ToInt64());
// Initialize PRU control block:
PruInterfaceControlBlock cb;
cb.r_owner = OWNER_PRU;
cb.r_buf = R_PTR_OFFSET;
cb.r_max_length = MAX_SIZE;
cb.r_received_length = 0;
cb.r_status = 0;
cb.r_truncated = 0;
cb.w_owner = OWNER_ARM;
cb.w_buf = W_PTR_OFFSET;
cb.w_length = 0;
cb.w_status = 0;
SetInterfaceControlBlock(cb);
Log.Write(LogType.Normal, LogComponent.E3Mbit, "PRU Initialization completed.");
}
private void StartReceiver()
{
ThreadPool.QueueUserWorkItem((ctx) =>
{
Log.Write(LogType.Normal, LogComponent.E3Mbit, "Starting receiver thread.");
ReceiveWorker();
}, null);
}
/// <summary>
/// Worker thread function. Waits for incoming packets on the 3mbit network and handles them
/// when they arrive.
/// </summary>
private void ReceiveWorker()
{
while(true)
{
// Wait for the next wakeup from the PRU
PRU.prussdrv_pru_wait_event(PRU.PRU_EVTOUT_0);
// Clear it
PRU.prussdrv_pru_clear_event(PRU.PRU_EVTOUT_0, PRU.PRU0_ARM_INTERRUPT);
if (HostOwnsReadBuffer())
{
// PRU gave us a read packet from the 3mbit Ether, handle it.
ReceiveFromNetworkInterface();
}
}
}
//
// The following functions read and write the control block located in Host/PRU shared memory.
//
private void SetInterfaceControlBlock(PruInterfaceControlBlock controlBlock)
{
Marshal.StructureToPtr(controlBlock, _sharedPruMemory, false);
}
private PruInterfaceControlBlock GetInterfaceControlBlock()
{
return (PruInterfaceControlBlock)Marshal.PtrToStructure(_sharedPruMemory, typeof(PruInterfaceControlBlock));
}
private bool HostOwnsReadBuffer()
{
// r_owner is at offset + 0
return Marshal.ReadInt32(_sharedPruMemory) == OWNER_ARM;
}
private bool HostOwnsWriteBuffer()
{
// w_owner is at offset + 24
return Marshal.ReadInt32(new IntPtr(_sharedPruMemory.ToInt64() + 24)) == OWNER_ARM;
}
private void SetReadBufferOwner(UInt32 owner)
{
// r_owner is at offset + 0
Marshal.WriteInt32(_sharedPruMemory, (int)owner);
}
private void SetWriteBufferOwner(UInt32 owner)
{
// w_owner is at offset + 24
Marshal.WriteInt32(new IntPtr(_sharedPruMemory.ToInt64() + 24), (int)owner);
}
private void SetWriteBufferLength(UInt32 length)
{
// w_length is at offset + 28
Marshal.WriteInt32(new IntPtr(_sharedPruMemory.ToInt64() + 28), (int)length);
}
/// <summary>
/// Pulls data received from the 3mbit interface and passes it to the router.
/// </summary>
private void ReceiveFromNetworkInterface()
{
PruInterfaceControlBlock cb = GetInterfaceControlBlock();
if (cb.r_truncated != 0)
{
Log.Write(LogType.Warning, LogComponent.E3Mbit, "Truncated packet recieved.");
cb.r_truncated = 0;
SetInterfaceControlBlock(cb);
SetReadBufferOwner(OWNER_PRU);
return;
}
if (cb.r_status != STATUS_INPUT_COMPLETE)
{
Log.Write(LogType.Warning, LogComponent.E3Mbit, "Bad PRU status 0x{0:x}", cb.r_status);
SetReadBufferOwner(OWNER_PRU);
return;
}
int receivedDataLength = (int)cb.r_received_length;
if (receivedDataLength > MAX_SIZE)
{
Log.Write(LogType.Warning, LogComponent.E3Mbit, "Received data too long (0x{0:x} bytes)", receivedDataLength);
SetReadBufferOwner(OWNER_PRU);
return;
}
if (receivedDataLength == 0)
{
Log.Write(LogType.Warning, LogComponent.E3Mbit, "Received 0 bytes of duration data. Ignoring packet.");
SetReadBufferOwner(OWNER_PRU);
return;
}
// Grab the received data from the shared PRU memory:
byte[] durationBuffer = new byte[receivedDataLength];
Marshal.Copy(new IntPtr(_sharedPruMemory.ToInt64() + R_PTR_OFFSET), durationBuffer, 0, receivedDataLength);
// Ready for next packet
SetReadBufferOwner(OWNER_PRU);
byte[] decodedPacket = DecodeDurationBuffer(durationBuffer);
if (decodedPacket == null)
{
Log.Write(LogType.Warning, LogComponent.E3Mbit, "Received bad packet.");
return;
}
// Prepend packet length for our internal encapsulation (annoying since we're just going to strip it off again...)
byte[] encapsulatedPacket = new byte[decodedPacket.Length + 2];
Array.Copy(decodedPacket, 0, encapsulatedPacket, 2, decodedPacket.Length);
int encapsulatedLength = decodedPacket.Length / 2 + 2;
encapsulatedPacket[0] = (byte)(encapsulatedLength >> 8);
encapsulatedPacket[1] = (byte)encapsulatedLength;
MemoryStream packetStream = new MemoryStream(encapsulatedPacket);
_routerCallback(packetStream, this);
Log.Write(LogType.Verbose, LogComponent.E3Mbit, "Received packet (0x{0:x} bytes), sent to router.", receivedDataLength);
}
/// <summary>
/// Sends data to the 3mbit interface.
/// </summary>
private void SendToNetworkInterface(byte[] data)
{
if (!HostOwnsWriteBuffer())
{
// Shouldn't happen
Log.Write(LogType.Error, LogComponent.E3Mbit, "SendToNetworkInterface called when PRU is not ready.");
return;
}
ushort crcVal = CalculateCRC(data, data.Length);
// Construct a new buffer with space for the CRC
byte[] fullPacket = new byte[data.Length + 2];
Array.Copy(data, fullPacket, data.Length);
fullPacket[fullPacket.Length - 2] = (byte)(crcVal >> 8);
fullPacket[fullPacket.Length - 1] = (byte)(crcVal);
// Copy the buffer to the shared PRU memory.
Marshal.Copy(fullPacket, 0, new IntPtr(_sharedPruMemory.ToInt64() + W_PTR_OFFSET), fullPacket.Length);
SetWriteBufferLength((uint)fullPacket.Length);
// Signal PRU to send the data in the write buffer.
SetWriteBufferOwner(OWNER_PRU);
Log.Write(LogType.Verbose, LogComponent.E3Mbit, "Packet sent to 3mbit interface.");
}
/// <summary>
/// Decodes bit timings into packet data. Returns null if issues were found with the data.
/// </summary>
/// <param name="durationBuf"></param>
/// <returns></returns>
byte[] DecodeDurationBuffer(byte[] durationBuf) {
Log.Write(LogType.Verbose, LogComponent.E3Mbit, $"Decoding duration buffer of length {durationBuf.Length}.");
List<byte> byteBuffer = new List<byte>();
bool[] bitBuf = new bool[8 * PUP.MAX_PUP_SIZE];
const int RECV_WIDTH = 2; // Recv values are in units of 2 ns (to fit in byte)
// Convert timings in durationBuf into high/low vector in bitBuf
// bitBuf holds values like 1, 0, 0, 1, 0, 1, indicating if the input
// was high or low during that time interval.
// A Manchester-encoded data bit consists of two values in bitBuf.
int offset1; // Offset into timing vector
int offset2 = 0; // Offset into bit vector
bool value = true; // Current high/low value
for (offset1 = 0; offset1 < durationBuf.Length; offset1++)
{
int width = durationBuf[offset1] * RECV_WIDTH;
if (width < 120)
{
Log.Write(LogType.Error, LogComponent.E3Mbit, $"Bad width {width} at {offset1} of {durationBuf.Length}");
return null;
}
else if (width < 230)
{
value = !value;
bitBuf[offset2++] = value;
}
else if (width < 280)
{
Log.Write(LogType.Error, LogComponent.E3Mbit, $"Bad width {width} at {offset1} of {durationBuf.Length}");
return null;
}
else if (width < 400)
{
value = !value;
bitBuf[offset2++] = value;
bitBuf[offset2++] = value;
}
else
{
Log.Write(LogType.Error, LogComponent.E3Mbit, $"Bad width {width} at {offset1} of {durationBuf.Length}");
return null;
}
}
// Convert bit pairs in bitBuf to bytes in byteBuffer
byte b = 0;
int i;
if ((offset2 % 2) == 0)
{
// For a 0 bit, the last 1 signal gets combined with the no-signal state and lost.
// So add it back.
bitBuf[offset2] = true;
offset2 += 1;
}
Log.Write(LogType.Verbose, LogComponent.E3Mbit, $"Offset2 is {offset2}.");
// Start at 1 to skip sync
for (i = 1; i < offset2; i += 2)
{
if (bitBuf[i] == bitBuf[i + 1])
{
Log.Write(LogType.Error, LogComponent.E3Mbit, $"Bad bit sequence at {i} of {offset2}: {bitBuf[i]}, {bitBuf[i+1]}");
b = (byte)(b << 1);
}
else
{
b = (byte)((b << 1) | (bitBuf[i] ? 1 : 0));
}
if ((i % 16) == 15)
{
byteBuffer.Add(b);
b = 0;
}
}
if ((offset2 % 16) != 1)
{
Log.Write(LogType.Error, LogComponent.E3Mbit, $"Bad offset2: {offset2}");
return null;
}
// Check the Ethernet CRC
byte[] byteArray = byteBuffer.ToArray();
ushort crcVal = CalculateCRC(byteArray, byteArray.Length - 2);
ushort readCrcVal = (ushort)((byteBuffer[byteBuffer.Count - 2] << 8) | byteBuffer[byteBuffer.Count - 1]);
if (crcVal != readCrcVal)
{
Log.Write(LogType.Error, LogComponent.E3Mbit, "Bad CRC, {0:x} vs {1:x}", crcVal, readCrcVal);
return null;
}
return byteArray;
}
// Generate CRC-16 for 3Mbit Ethernet
// buf is sequence of words stored big-endian.
ushort CalculateCRC(byte[] buf, int lengthInBytes)
{
ushort crc = 0x8005; // Due to the sync bit
for (int index = 0; index < lengthInBytes; index++)
{
ushort data = (ushort)(buf[index] << 8);
for (int i = 0; i < 8; i++)
{
ushort xorFeedback = (ushort)((crc ^ data) & 0x8000); // Test upper bit
crc = (ushort)(crc << 1);
data = (ushort)(data << 1);
if (xorFeedback != 0)
{
crc ^= 0x8005; // CRC-16 polynomial constant
}
}
}
return crc;
}
IntPtr _sharedPruMemory;
private ReceivedPacketCallback _routerCallback;
// Interface between host and PRU
// The idea is there are two buffers: r_ and w_.
// Ownership is passed back and forth between the PRU and the ARM processor.
// The PRU sends a signal whenever it gives a buffer back to the ARM.
// "in" and "out" below are from the perspective of the PRU.
//
// This struct is here more for convenience of debugging than actual use in C#
// since it's not really possible to map a C# object directly to volatile memory
// in a way that I feel good about using.
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct PruInterfaceControlBlock
{
public UInt32 r_owner; // in
public UInt32 r_max_length; // in, bytes
public UInt32 r_received_length; // out, bytes
public UInt32 r_buf; // in (pointer offset)
public UInt32 r_truncated; // out, boolean
public UInt32 r_status; // out
public UInt32 w_owner; // in
public UInt32 w_length; // bytes, in (buffer length)
public UInt32 w_buf; // in (pointer offset)
public UInt32 w_status; // out
};
const uint STATUS_INPUT_COMPLETE = (0 << 8);
const uint STATUS_OUTPUT_COMPLETE = (1 << 8);
const uint STATUS_INPUT_OVERRUN = (2 << 8);
const uint STATUS_SOFTWARE_RESET = (5 << 8); // Internal only
const uint STATUS_TRUNCATED = 36; // Not part of real interface
const uint STATUS_TIMING_ERROR = 32; // Not part of real interface
const uint STATUS_BIT_COLLISION = 16;
const uint STATUS_BIT_CRC_BAD = 8; // unused
const uint STATUS_BIT_ICMD = 4; // unused
const uint STATUS_BIT_OCMD = 2; // unused
const uint STATUS_BIT_INCOMPLETE = 1; // Not byte boundary
const uint COMMAND_NONE = 0;
const uint COMMAND_SEND = 1;
const uint COMMAND_RECV = 2;
const uint COMMAND_HALT = 3;
const uint OWNER_ARM = 1;
const uint OWNER_PRU = 2;
const uint W_PTR_OFFSET = 0x400;
const uint R_PTR_OFFSET = 0x10000;
const uint MAX_SIZE = 12 * 1024;
}
/// <summary>
/// Provides constants, structs, and functions needed to P/Invoke into prussdrv lib calls.
/// </summary>
public static class PRU
{
public const int NUM_PRU_HOSTIRQS = 8;
public const int NUM_PRU_HOSTS = 10;
public const int NUM_PRU_CHANNELS = 10;
public const int NUM_PRU_SYS_EVTS = 64;
public const uint PRUSS0_PRU0_DATARAM = 0;
public const uint PRUSS0_PRU1_DATARAM = 1;
public const uint PRUSS0_PRU0_IRAM = 2;
public const uint PRUSS0_PRU1_IRAM = 3;
public const uint PRUSS_V1 = 1; // AM18XX
public const uint PRUSS_V2 = 2; // AM33XX
//Available in AM33xx series - begin
public const uint PRUSS0_SHARED_DATARAM = 4;
public const uint PRUSS0_CFG = 5;
public const uint PRUSS0_UART = 6;
public const uint PRUSS0_IEP = 7;
public const uint PRUSS0_ECAP = 8;
public const uint PRUSS0_MII_RT = 9;
public const uint PRUSS0_MDIO = 10;
//Available in AM33xx series - end
public const uint PRU_EVTOUT_0 = 0;
public const uint PRU_EVTOUT_1 = 1;
public const uint PRU_EVTOUT_2 = 2;
public const uint PRU_EVTOUT_3 = 3;
public const uint PRU_EVTOUT_4 = 4;
public const uint PRU_EVTOUT_5 = 5;
public const uint PRU_EVTOUT_6 = 6;
public const uint PRU_EVTOUT_7 = 7;
public const byte PRU0_PRU1_INTERRUPT = 17;
public const byte PRU1_PRU0_INTERRUPT = 18;
public const byte PRU0_ARM_INTERRUPT = 19;
public const byte PRU1_ARM_INTERRUPT = 20;
public const byte ARM_PRU0_INTERRUPT = 21;
public const byte ARM_PRU1_INTERRUPT = 22;
public const byte CHANNEL0 = 0;
public const byte CHANNEL1 = 1;
public const byte CHANNEL2 = 2;
public const byte CHANNEL3 = 3;
public const byte CHANNEL4 = 4;
public const byte CHANNEL5 = 5;
public const byte CHANNEL6 = 6;
public const byte CHANNEL7 = 7;
public const byte CHANNEL8 = 8;
public const byte CHANNEL9 = 9;
public const byte PRU0 = 0;
public const byte PRU1 = 1;
public const byte PRU_EVTOUT0 = 2;
public const byte PRU_EVTOUT1 = 3;
public const byte PRU_EVTOUT2 = 4;
public const byte PRU_EVTOUT3 = 5;
public const byte PRU_EVTOUT4 = 6;
public const byte PRU_EVTOUT5 = 7;
public const byte PRU_EVTOUT6 = 8;
public const byte PRU_EVTOUT7 = 9;
public const uint PRU0_HOSTEN_MASK = 0x0001;
public const uint PRU1_HOSTEN_MASK = 0x0002;
public const uint PRU_EVTOUT0_HOSTEN_MASK = 0x0004;
public const uint PRU_EVTOUT1_HOSTEN_MASK = 0x0008;
public const uint PRU_EVTOUT2_HOSTEN_MASK = 0x0010;
public const uint PRU_EVTOUT3_HOSTEN_MASK = 0x0020;
public const uint PRU_EVTOUT4_HOSTEN_MASK = 0x0040;
public const uint PRU_EVTOUT5_HOSTEN_MASK = 0x0080;
public const uint PRU_EVTOUT6_HOSTEN_MASK = 0x0100;
public const uint PRU_EVTOUT7_HOSTEN_MASK = 0x0200;
public struct tsysevt_to_channel_map
{
public tsysevt_to_channel_map(short s, short c)
{
sysevt = s;
channel = c;
}
public short sysevt;
public short channel;
}
public struct tchannel_to_host_map
{
public tchannel_to_host_map(short c, short h)
{
channel = c;
host = h;
}
public short channel;
public short host;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct tpruss_intc_initdata
{
//Enabled SYSEVTs - Range:0..63
//{-1} indicates end of list
[MarshalAs(UnmanagedType.ByValArray, SizeConst = NUM_PRU_SYS_EVTS)]
public byte[] sysevts_enabled;
//SysEvt to Channel map. SYSEVTs - Range:0..63 Channels -Range: 0..9
//{-1, -1} indicates end of list
[MarshalAs(UnmanagedType.ByValArray, SizeConst = NUM_PRU_SYS_EVTS)]
public tsysevt_to_channel_map[] sysevt_to_channel_map;
//Channel to Host map.Channels -Range: 0..9 HOSTs - Range:0..9
//{-1, -1} indicates end of list
[MarshalAs(UnmanagedType.ByValArray, SizeConst = NUM_PRU_CHANNELS)]
public tchannel_to_host_map[] channel_to_host_map;
//10-bit mask - Enable Host0-Host9 {Host0/1:PRU0/1, Host2..9 : PRUEVT_OUT0..7}
public UInt32 host_enable_bitmask;
}
[DllImport("prussdrv")]
public static extern int prussdrv_init();
[DllImport("prussdrv")]
public static extern int prussdrv_open(UInt32 host_interrupt);
[DllImport("prussdrv")]
public static extern int prussdrv_pruintc_init(ref tpruss_intc_initdata prussintc_init_data);
[DllImport("prussdrv")]
public static extern int prussdrv_load_datafile(int prunum, [MarshalAs(UnmanagedType.LPStr)] string filename);
[DllImport("prussdrv")]
public static extern int prussdrv_exec_program(int prunum, [MarshalAs(UnmanagedType.LPStr)] string filename);
[DllImport("prussdrv")]
public static extern int prussdrv_map_prumem(UInt32 pru_ram_id, out IntPtr address);
[DllImport("prussdrv")]
public static extern int prussdrv_pru_wait_event(UInt32 host_interrupt);
[DllImport("prussdrv")]
public static extern int prussdrv_pru_clear_event(UInt32 host_interrupt, UInt32 sysevent);
}
}

View File

@@ -16,11 +16,6 @@
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using PcapDotNet.Base;
using PcapDotNet.Core;
using PcapDotNet.Core.Extensions;
@@ -28,7 +23,6 @@ using PcapDotNet.Packets;
using PcapDotNet.Packets.Ethernet;
using IFS.Logging;
using System.IO;
using System.Net.NetworkInformation;
using System.Threading;
using IFS.Gateway;
@@ -41,14 +35,14 @@ namespace IFS.Transport
/// Ethernet packets are broadcast. See comments in UDP.cs for the reasoning behind this.
///
/// </summary>
public class Ethernet : IPupPacketInterface, IRawPacketInterface
public class Ethernet : IPacketInterface
{
public Ethernet(LivePacketDevice iface)
{
_interface = iface;
_interface = iface;
}
public void RegisterRouterCallback(RoutePupCallback callback)
public void RegisterRouterCallback(ReceivedPacketCallback callback)
{
_routerCallback = callback;
@@ -63,7 +57,7 @@ namespace IFS.Transport
public void Shutdown()
{
_routerCallback = null;
_communicator.Break();
_communicator.Break();
}
public void Send(PUP p)
@@ -71,75 +65,26 @@ namespace IFS.Transport
//
// Write PUP to ethernet:
//
// Build the outgoing data; this is:
// 1st word: length of data following
// 2nd word: 3mbit destination / source bytes
// 3rd word: frame type (PUP)
byte[] encapsulatedFrame = new byte[6 + p.RawData.Length];
// 3mbit Packet length
encapsulatedFrame[0] = (byte)((p.RawData.Length / 2 + 2) >> 8);
encapsulatedFrame[1] = (byte)(p.RawData.Length / 2 + 2);
// addressing
encapsulatedFrame[2] = p.DestinationPort.Host;
encapsulatedFrame[3] = p.SourcePort.Host;
// frame type
encapsulatedFrame[4] = (byte)(_pupFrameType >> 8);
encapsulatedFrame[5] = (byte)_pupFrameType;
// Actual data
p.RawData.CopyTo(encapsulatedFrame, 6);
MacAddress destinationMac = new MacAddress(_10mbitBroadcast);
// Build the outgoing packet; place the source/dest addresses, type field and the PUP data.
EthernetLayer ethernetLayer = new EthernetLayer
{
Source = _interface.GetMacAddress(),
Destination = destinationMac,
EtherType = (EthernetType)_3mbitFrameType,
};
PayloadLayer payloadLayer = new PayloadLayer
{
Data = new Datagram(encapsulatedFrame),
};
PacketBuilder builder = new PacketBuilder(ethernetLayer, payloadLayer);
// Send it over the 'net!
_communicator.SendPacket(builder.Build(DateTime.Now));
byte[] encapsulatedFrame = PupPacketBuilder.BuildEncapsulatedEthernetFrameFromPup(p);
SendFrame(encapsulatedFrame);
}
public void Send(byte[] data, byte source, byte destination, ushort frameType)
{
// Build the outgoing data; this is:
// 1st word: length of data following
// 2nd word: 3mbit destination / source bytes
// 3rd word: frame type (PUP)
byte[] encapsulatedFrame = new byte[6 + data.Length];
byte[] encapsulatedFrame = PupPacketBuilder.BuildEncapsulatedEthernetFrameFromRawData(data, source, destination, frameType);
SendFrame(encapsulatedFrame);
}
// 3mbit Packet length
encapsulatedFrame[0] = (byte)((data.Length / 2 + 2) >> 8);
encapsulatedFrame[1] = (byte)(data.Length / 2 + 2);
public void Send(MemoryStream encapsulatedFrameStream)
{
SendFrame(encapsulatedFrameStream.ToArray());
}
// addressing
encapsulatedFrame[2] = destination;
encapsulatedFrame[3] = source;
private void SendFrame(byte[] encapsulatedFrame)
{
MacAddress destinationMac = new MacAddress(_10mbitBroadcast);
// frame type
encapsulatedFrame[4] = (byte)(frameType >> 8);
encapsulatedFrame[5] = (byte)frameType;
// Actual data
data.CopyTo(encapsulatedFrame, 6);
MacAddress destinationMac = new MacAddress(_10mbitBroadcast);
// Build the outgoing packet; place the source/dest addresses, type field and the PUP data.
// Build the outgoing packet; place the source/dest addresses, type field and the PUP data.
EthernetLayer ethernetLayer = new EthernetLayer
{
Source = _interface.GetMacAddress(),
@@ -160,56 +105,19 @@ namespace IFS.Transport
private void ReceiveCallback(Packet p)
{
//
// Filter out encapsulated 3mbit frames and look for PUPs, forward them on.
//
if ((int)p.Ethernet.EtherType == _3mbitFrameType)
{
Log.Write(LogType.Verbose, LogComponent.Ethernet, "3mbit pup received.");
MemoryStream packetStream = p.Ethernet.Payload.ToMemoryStream();
// Read the length prefix (in words), convert to bytes.
// Subtract off 2 words for the ethernet header
int length = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte())) * 2 - 4;
// Read the address (1st word of 3mbit packet)
byte destination = (byte)packetStream.ReadByte();
byte source = (byte)packetStream.ReadByte();
// Read the type and switch on it
int etherType3mbit = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte()));
//
// Ensure this is a packet we're interested in.
//
if (etherType3mbit == _pupFrameType && // it's a PUP
(destination == DirectoryServices.Instance.LocalHost || // for us, or...
destination == 0)) // broadcast
{
try
{
PUP pup = new PUP(packetStream, length);
_routerCallback(pup, destination != 0);
}
catch(Exception e)
{
// An error occurred, log it.
Log.Write(LogType.Error, LogComponent.PUP, "Error handling PUP: {0}", e.Message);
}
}
else
{
Log.Write(LogType.Warning, LogComponent.Ethernet, "3mbit packet is not a PUP, dropping");
}
_routerCallback(packetStream, this);
}
else
{
// Not a PUP, Discard the packet. We will not log this, so as to keep noise down.
// Not an encapsulated 3mbit frame, Discard the packet. We will not log this, so as to keep noise down.
// Log.Write(LogType.Verbose, LogComponent.Ethernet, "Not a PUP (type 0x{0:x}. Dropping.", p.Ethernet.EtherType);
}
}
}
private void Open(bool promiscuous, int timeout)
{
@@ -231,13 +139,10 @@ namespace IFS.Transport
private LivePacketDevice _interface;
private PacketCommunicator _communicator;
private RoutePupCallback _routerCallback;
private ReceivedPacketCallback _routerCallback;
// Constants
// The ethertype used in the encapsulated 3mbit frame
private readonly ushort _pupFrameType = 512;
// The type used for 3mbit frames encapsulated in 10mb frames
private readonly int _3mbitFrameType = 0xbeef; // easy to identify, ostensibly unused by anything of any import

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IFS.Transport
{
/// <summary>
/// Helper functions for building Ethernet frames in a variety of ways.
/// </summary>
public static class PupPacketBuilder
{
// The ethertype used in the encapsulated 3mbit frame
public static readonly ushort PupFrameType = 512;
public static byte[] BuildEncapsulatedEthernetFrameFromPup(PUP p)
{
return BuildEncapsulatedEthernetFrameFromRawData(p.RawData, p.SourcePort.Host, p.DestinationPort.Host, PupFrameType);
}
public static byte[] BuildEncapsulatedEthernetFrameFromRawData(byte[] data, byte source, byte destination, ushort frameType)
{
// Build the outgoing data; this is:
// 1st word: length of data following
// 2nd word: 3mbit destination / source bytes
// 3rd word: frame type
byte[] newFrame = new byte[6 + data.Length];
// 3mbit Packet length
newFrame[0] = (byte)((data.Length / 2 + 2) >> 8);
newFrame[1] = (byte)(data.Length / 2 + 2);
// addressing
newFrame[2] = destination;
newFrame[3] = source;
// frame type
newFrame[4] = (byte)(frameType >> 8);
newFrame[5] = (byte)frameType;
// Actual data
data.CopyTo(newFrame, 6);
return newFrame;
}
public static byte[] BuildEthernetFrameFromPup(PUP p)
{
return BuildEthernetFrameFromRawData(p.RawData, p.SourcePort.Host, p.DestinationPort.Host, PupFrameType);
}
public static byte[] BuildEthernetFrameFromRawData(byte[] data, byte source, byte destination, ushort frameType)
{
// Build the full raw frame data; this is:
// 2nd word: 3mbit destination / source bytes
// 3rd word: frame type
byte[] newFrame = new byte[4 + data.Length];
// addressing
newFrame[0] = destination;
newFrame[1] = source;
// frame type
newFrame[2] = (byte)(frameType >> 8);
newFrame[3] = (byte)frameType;
// Actual data
data.CopyTo(newFrame, 4);
return newFrame;
}
}
}

View File

@@ -16,12 +16,7 @@
*/
using IFS.Gateway;
using PcapDotNet.Packets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace IFS.Transport
{
@@ -41,7 +36,7 @@ namespace IFS.Transport
/// Registers a callback (into the router) to be invoked on receipt of a PUP.
/// </summary>
/// <param name="callback"></param>
void RegisterRouterCallback(RoutePupCallback callback);
void RegisterRouterCallback(ReceivedPacketCallback callback);
/// <summary>
/// Shuts down the interface.
@@ -69,5 +64,18 @@ namespace IFS.Transport
/// <param name="destination"></param>
/// <param name="frameType"></param>
void Send(byte[] data, byte source, byte destination, ushort frameType);
/// <summary>
/// Sends the specified data over the transport.
/// </summary>
/// <param name="stream"></param>
void Send(MemoryStream encapsulatedFrameStream);
}
public interface IPacketInterface : IPupPacketInterface, IRawPacketInterface
{
}
}

View File

@@ -62,7 +62,7 @@ namespace IFS.Transport
/// and go with the broadcast implementation.
///
/// </summary>
public class UDPEncapsulation : IPupPacketInterface, IRawPacketInterface
public class UDPEncapsulation : IPacketInterface
{
public UDPEncapsulation(NetworkInterface iface)
{
@@ -78,7 +78,7 @@ namespace IFS.Transport
// Grab the broadcast address for the interface so that we know what broadcast address to use
// for our UDP datagrams.
//
IPInterfaceProperties props = iface.GetIPProperties();
IPInterfaceProperties props = iface.GetIPProperties();
foreach (UnicastIPAddressInformation unicast in props.UnicastAddresses)
{
@@ -117,11 +117,11 @@ namespace IFS.Transport
/// Registers a gateway to handle incoming PUPs.
/// </summary>
/// <param name="callback"></param>
public void RegisterRouterCallback(RoutePupCallback callback)
public void RegisterRouterCallback(ReceivedPacketCallback callback)
{
_routerCallback = callback;
// Now that we have a callback we can start receiving stuff.
// Now that we have a callback we can start receiving stuff.
BeginReceive();
}
@@ -136,37 +136,16 @@ namespace IFS.Transport
//
// Write PUP to UDP:
//
// For now, no actual routing (Gateway not implemented yet), everything is on the same 'net.
// Just send a broadcast UDP with the encapsulated frame inside of it.
//
//
byte[] encapsulatedFrame = PupPacketBuilder.BuildEncapsulatedEthernetFrameFromPup(p);
// Build the outgoing data; this is:
// 1st word: length of data following
// 2nd word: 3mbit destination / source bytes
// 3rd word: frame type (PUP)
byte[] encapsulatedFrame = new byte[6 + p.RawData.Length];
// 3mbit Packet length
encapsulatedFrame[0] = (byte)((p.RawData.Length / 2 + 2) >> 8);
encapsulatedFrame[1] = (byte)(p.RawData.Length / 2 + 2);
// addressing
encapsulatedFrame[2] = p.DestinationPort.Host;
encapsulatedFrame[3] = p.SourcePort.Host;
// frame type
encapsulatedFrame[4] = (byte)(_pupFrameType >> 8);
encapsulatedFrame[5] = (byte)_pupFrameType;
// Actual data
p.RawData.CopyTo(encapsulatedFrame, 6);
// Send as UDP broadcast.
_udpClient.Send(encapsulatedFrame, encapsulatedFrame.Length, _broadcastEndpoint);
// Send as UDP broadcast.
_udpClient.Send(encapsulatedFrame, encapsulatedFrame.Length, _broadcastEndpoint);
}
/// <summary>
/// Sends an array of bytes over the ethernet as a 3mbit packet encapsulated in a 10mbit packet.
/// Sends an array of bytes over the network as a 3mbit packet encapsulated in a UDP datagram.
/// </summary>
/// <param name="packet"></param>
/// <param name="hostId"></param>
@@ -176,66 +155,26 @@ namespace IFS.Transport
// 1st word: length of data following
// 2nd word: 3mbit destination / source bytes
// 3rd word: frame type (PUP)
byte[] encapsulatedFrame = new byte[6 + data.Length];
byte[] encapsulatedFrame = PupPacketBuilder.BuildEncapsulatedEthernetFrameFromRawData(data, source, destination, frameType);
// 3mbit Packet length
encapsulatedFrame[0] = (byte)((data.Length / 2 + 2) >> 8);
encapsulatedFrame[1] = (byte)(data.Length / 2 + 2);
// Send as UDP broadcast.
_udpClient.Send(encapsulatedFrame, encapsulatedFrame.Length, _broadcastEndpoint);
}
// addressing
encapsulatedFrame[2] = destination;
encapsulatedFrame[3] = source;
// frame type
encapsulatedFrame[4] = (byte)(frameType >> 8);
encapsulatedFrame[5] = (byte)frameType;
// Actual data
data.CopyTo(encapsulatedFrame, 6);
// Send as UDP broadcast.
_udpClient.Send(encapsulatedFrame, encapsulatedFrame.Length, _broadcastEndpoint);
/// <summary>
/// Sends a stream of bytes over the network as a 3mbit packet encapsulated in a UDP datagram.
/// </summary>
/// <param name="encapsulatedPacketStream"></param>
public void Send(MemoryStream encapsulatedPacketStream)
{
// Send as UDP broadcast.
byte[] buf = encapsulatedPacketStream.ToArray();
_udpClient.Send(buf, buf.Length, _broadcastEndpoint);
}
private void Receive(MemoryStream packetStream)
{
//
// Look for PUPs, forward them on.
//
// Read the length prefix (in words), convert to bytes.
// Subtract off 2 words for the ethernet header
int length = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte())) * 2 - 4;
// Read the address (1st word of 3mbit packet)
byte destination = (byte)packetStream.ReadByte();
byte source = (byte)packetStream.ReadByte();
// Read the type and switch on it
int etherType3mbit = ((packetStream.ReadByte() << 8) | (packetStream.ReadByte()));
//
// Ensure this is a packet we're interested in.
//
if (etherType3mbit == _pupFrameType && // it's a PUP
(destination == DirectoryServices.Instance.LocalHost || // for us, or...
destination == 0)) // broadcast
{
try
{
PUP pup = new PUP(packetStream, length);
_routerCallback(pup, destination != 0);
}
catch(Exception e)
{
// An error occurred, log it.
Log.Write(LogType.Error, LogComponent.PUP, "Error handling PUP: {0}", e.Message);
}
}
else
{
Log.Write(LogType.Warning, LogComponent.Ethernet, "UDP packet is not a PUP, dropping");
}
_routerCallback(packetStream, this);
}
/// <summary>
@@ -258,7 +197,7 @@ namespace IFS.Transport
// properly.)
Log.Write(LogComponent.UDP, "UDP Receiver thread started.");
IPEndPoint groupEndPoint = new IPEndPoint(IPAddress.Any, Configuration.UDPPort);
IPEndPoint groupEndPoint = new IPEndPoint(IPAddress.Any, Configuration.UDPPort);
while (true)
{
@@ -285,12 +224,9 @@ namespace IFS.Transport
}
return new IPAddress(broadcastAddress);
}
// The ethertype used in the encapsulated 3mbit frame
private readonly ushort _pupFrameType = 512;
}
private RoutePupCallback _routerCallback;
private ReceivedPacketCallback _routerCallback;
// Thread used for receive
private Thread _receiveThread;

BIN
PUP/etherdata.bin Normal file

Binary file not shown.

BIN
PUP/ethertext.bin Normal file

Binary file not shown.

View File

@@ -1,4 +1,4 @@
Readme.txt for IFS v1.2:
Readme.txt for IFS v1.4:
1. Introduction and Overview
============================
@@ -11,8 +11,10 @@ into being during the Alto's lifetime, so the IFS was a permanent fixture of
the network environment at PARC during the heyday of the Alto.
The LCM+L's IFS implementation is an implementation of this protocol suite
that runs on a modern PC, and is designed to work with the ContrAlto Alto
emulator over either Raw Ethernet packets or UDP broadcasts.
that runs on a modern PC. It is designed to work with the ContrAlto Alto
emulator over either Raw Ethernet packets or UDP broadcasts, as well as with
real hardware when using the BeagleBone-based Alto Ethernet Interface available at
https://github.com/shirriff/alto-ethernet-interface.
It provides the following IFS services:
@@ -84,8 +86,12 @@ Directory configuration:
(User mail folders are placed in this directory.)
Interface configuration:
- InterfaceType: "RAW" or "UDP". Specifies the transport to use for
communication.
- InterfaceTypes: Any combination of "RAW" "UDP" or "3MBIT". Specifies
the transports to use for communication:
- RAW: Raw Ethernet frames
- UDP: UDP Datagram
- 3MBIT: The Beaglebone-based Alto Ethernet Interface
- InterfaceName: The name of the host network adapter to use for
communication.
@@ -104,6 +110,12 @@ Debugging configuration:
DirectoryServices, PUP, FTP, BreathOfLife, EFTP,
BootServer, UDP, Mail, Configuration, or All
Misc:
- RunIFSServices: Whether to run the full suite of IFS services or just
perform basic bridging (this is mostly useful when
working with real hardware via the Alto Interface Hardware
at https://github.com/shirriff/alto-ethernet-interface.)
2.2 hosts.txt:
--------------
@@ -398,9 +410,9 @@ The following documents may be useful in actually using the Alto-land client
tools (FTP, CopyDisk, mail, etc) to communicate with the IFS server:
The Alto User's Handbook:
http://bitsavers.org/pdf/xerox/alto/Alto_Users_Handbook_Sep79.pdf
http://bitsavers.org/pdf/xerox/alto/Alto_Users_Handbook_Sep79.pdf
Alto Subsystems:
http://bitsavers.org/pdf/xerox/alto/memos_1981/Alto_Subsystems_May81.pdf
http://bitsavers.org/pdf/xerox/alto/memos_1981/Alto_Subsystems_May81.pdf
The following specifications were used to implement the IFS protocol suite:
@@ -414,16 +426,48 @@ Misc Services:
http://xeroxalto.computerhistory.org/_cd8_/pup/.miscservices.press!1.pdf
8.0 Packet-Level Protocol
8.0 Using With Real Hardware
============================
IFS can be used to talk with real hardware that has a 3mbit Ethernet interface
(Altos, Dolphins, Dorados, PERQs, and early Sun hardware).
This is accomplished by using the BeagleBone Alto Ethernet Interface developed by
Ken Sherriff and documented here: https://github.com/shirriff/alto-ethernet-interface.
Ken provides his own modification of this IFS code to use with his hardware,
but as of V1.4, this IFS implementation provides all the functionality of his
software, making his fork unnecessary. All that is necessary is to place the
release binaries on the BeagleBone and ensure that IFS.exe is running (you must
disable any earlier versions of IFS.exe and the 'gateway' program before doing so.)
Use of the BeagleBone Alto Ethernet Interface is enabled by specifying "3MBIT" as one
of the InterfaceTypes options in Ifs.cfg (see section 2.1).
When running in this configuration, by default IFS will run just as it does on desktop PCs and
will provide network services directly to the system it is connected to.
8.1 Bridging Mode
-----------------
Additionally, by setting the "RunIFSServices" parameter to false, IFS will run in
a mode where it does not provide any IFS services, but instead bridges interfaces together:
Packets received from a real Alto will be sent out over UDP and RAW interfaces (if configured)
and similarly, packets coming in over UDP and RAW interfaces will be sent to the real Alto.
This allows the IFS software to let real hardware directly communicate with other 3mbit devices on
your local network (real or emulated) with a minimum of effort. The expectation in this
mode is that some other system on the network (a PC or a server somewhere) will be providing
IFS services, not the IFS running on the BeagleBone.
9.0 Packet-Level Protocol
=========================
IFS (and ContrAlto) use a very simple encapsulation for transmitting 3mbit
Ethernet packets over modern transports. An encapsulated packet consists of
two fields:
- Packet Length (2 bytes): Length (in 16-bit words) of the 3mbit Packet
Data field (see below)
- Packet Data (N bytes): The 3mbit packet, including 3mbit Ethernet header
but excluding the checksum word.
- Packet Length (2 bytes): Length (in 16-bit words) of the 3mbit Packet
Data field (see below)
- Packet Data (N bytes): The 3mbit packet, including 3mbit Ethernet header
but excluding the checksum word.
All words are stored in big-endian format.
@@ -437,7 +481,7 @@ As discussed in Section 5.0, all packets are broadcast. The technical reasons
for this are documented in the source code; see Transport\UDP.cs for details.
9.0 Thanks
10.0 Thanks
==========
This project would not have been possible without the conservation efforts of