From 4d992b1bd769dec8d82730f5f376ca5f9a17b63c Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Sat, 30 Sep 2023 22:45:05 -0700 Subject: [PATCH] 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. --- PUP/Conf/ifs.cfg | 11 +- PUP/Configuration.cs | 18 +- PUP/Entrypoint.cs | 96 ++--- PUP/Gateway/Router.cs | 106 +++-- PUP/IFS.csproj | 8 + PUP/Logging/Log.cs | 3 +- PUP/MiscServicesProtocol.cs | 108 +++-- PUP/Properties/AssemblyInfo.cs | 6 +- PUP/Transport/3MbitAdapter.cs | 655 +++++++++++++++++++++++++++++++ PUP/Transport/Ethernet.cs | 137 +------ PUP/Transport/PacketBuilder.cs | 74 ++++ PUP/Transport/PacketInterface.cs | 22 +- PUP/Transport/UDP.cs | 116 ++---- PUP/etherdata.bin | Bin 0 -> 8192 bytes PUP/ethertext.bin | Bin 0 -> 8192 bytes PUP/readme.txt | 70 +++- 16 files changed, 1088 insertions(+), 342 deletions(-) create mode 100644 PUP/Transport/3MbitAdapter.cs create mode 100644 PUP/Transport/PacketBuilder.cs create mode 100644 PUP/etherdata.bin create mode 100644 PUP/ethertext.bin diff --git a/PUP/Conf/ifs.cfg b/PUP/Conf/ifs.cfg index a890586..5395c09 100644 --- a/PUP/Conf/ifs.cfg +++ b/PUP/Conf/ifs.cfg @@ -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 diff --git a/PUP/Configuration.cs b/PUP/Configuration.cs index 5507be8..ab4bf2e 100644 --- a/PUP/Configuration.cs +++ b/PUP/Configuration.cs @@ -83,9 +83,9 @@ namespace IFS } /// - /// The type of interface (UDP or RAW) to communicate over + /// The type of interface(s) (UDP, RAW, or 3mbit) to communicate over /// - public static readonly string InterfaceType; + public static readonly string InterfaceTypes; /// /// The name of the network interface to use @@ -97,6 +97,11 @@ namespace IFS /// public static readonly int UDPPort; + /// + /// Whether to run IFS Services or just bridge interfaces. + /// + public static readonly bool RunIFSServices; + /// /// The network that this server lives on /// @@ -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); } } - } + } } } } diff --git a/PUP/Entrypoint.cs b/PUP/Entrypoint.cs index 76abeaf..92acde4 100644 --- a/PUP/Entrypoint.cs +++ b/PUP/Entrypoint.cs @@ -15,20 +15,12 @@ along with IFS. If not, see . */ -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)); + } } } diff --git a/PUP/Gateway/Router.cs b/PUP/Gateway/Router.cs index cf7133d..a56d3ed 100644 --- a/PUP/Gateway/Router.cs +++ b/PUP/Gateway/Router.cs @@ -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); /// /// 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(); // // 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); } /// @@ -142,9 +150,8 @@ namespace IFS.Gateway /// 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 } } + /// + /// Handles an encapsulated 3mbit frame incoming from the receiver. + /// + /// + 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 } /// - /// 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. /// - private IPupPacketInterface _pupPacketInterface; - - /// - /// Our interface to a facility that can transmit raw Ethernet frames - /// - private IRawPacketInterface _rawPacketInterface; + private List _packetInterfaces; /// /// Our UdpClient for sending PUPs to external networks. diff --git a/PUP/IFS.csproj b/PUP/IFS.csproj index 6918367..9ecae83 100644 --- a/PUP/IFS.csproj +++ b/PUP/IFS.csproj @@ -124,11 +124,13 @@ + + @@ -151,6 +153,12 @@ PreserveNewest + + Always + + + Always + diff --git a/PUP/Logging/Log.cs b/PUP/Logging/Log.cs index ea44625..c4e4606 100644 --- a/PUP/Logging/Log.cs +++ b/PUP/Logging/Log.cs @@ -43,8 +43,9 @@ namespace IFS.Logging UDP = 0x800, Mail = 0x1000, Routing = 0x2000, + E3Mbit = 0x4000, - Configuration = 0x4000, + Configuration = 0x8000, All = 0x7fffffff } diff --git a/PUP/MiscServicesProtocol.cs b/PUP/MiscServicesProtocol.cs index 5698802..3621a73 100644 --- a/PUP/MiscServicesProtocol.cs +++ b/PUP/MiscServicesProtocol.cs @@ -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() { - + } /// @@ -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 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 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. I’d like to remove this restriction if I get a chance, so please don’t 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)); } diff --git a/PUP/Properties/AssemblyInfo.cs b/PUP/Properties/AssemblyInfo.cs index b9cca8a..2963d6f 100644 --- a/PUP/Properties/AssemblyInfo.cs +++ b/PUP/Properties/AssemblyInfo.cs @@ -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")] diff --git a/PUP/Transport/3MbitAdapter.cs b/PUP/Transport/3MbitAdapter.cs new file mode 100644 index 0000000..2a6304b --- /dev/null +++ b/PUP/Transport/3MbitAdapter.cs @@ -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 . +*/ + + +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 +{ + /// + /// 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. + /// + 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); + } + + /// + /// Worker thread function. Waits for incoming packets on the 3mbit network and handles them + /// when they arrive. + /// + 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); + } + + /// + /// Pulls data received from the 3mbit interface and passes it to the router. + /// + 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); + } + + /// + /// Sends data to the 3mbit interface. + /// + 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."); + } + + /// + /// Decodes bit timings into packet data. Returns null if issues were found with the data. + /// + /// + /// + byte[] DecodeDurationBuffer(byte[] durationBuf) { + + Log.Write(LogType.Verbose, LogComponent.E3Mbit, $"Decoding duration buffer of length {durationBuf.Length}."); + + List byteBuffer = new List(); + 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; + } + + /// + /// Provides constants, structs, and functions needed to P/Invoke into prussdrv lib calls. + /// + 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); + } +} diff --git a/PUP/Transport/Ethernet.cs b/PUP/Transport/Ethernet.cs index 074a4b8..63a2bc9 100644 --- a/PUP/Transport/Ethernet.cs +++ b/PUP/Transport/Ethernet.cs @@ -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. /// /// - 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 diff --git a/PUP/Transport/PacketBuilder.cs b/PUP/Transport/PacketBuilder.cs new file mode 100644 index 0000000..1944adb --- /dev/null +++ b/PUP/Transport/PacketBuilder.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IFS.Transport +{ + /// + /// Helper functions for building Ethernet frames in a variety of ways. + /// + 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; + } + } +} diff --git a/PUP/Transport/PacketInterface.cs b/PUP/Transport/PacketInterface.cs index 382ea1e..7489ba9 100644 --- a/PUP/Transport/PacketInterface.cs +++ b/PUP/Transport/PacketInterface.cs @@ -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. /// /// - void RegisterRouterCallback(RoutePupCallback callback); + void RegisterRouterCallback(ReceivedPacketCallback callback); /// /// Shuts down the interface. @@ -69,5 +64,18 @@ namespace IFS.Transport /// /// void Send(byte[] data, byte source, byte destination, ushort frameType); + + /// + /// Sends the specified data over the transport. + /// + /// + void Send(MemoryStream encapsulatedFrameStream); } + + public interface IPacketInterface : IPupPacketInterface, IRawPacketInterface + { + + } + + } diff --git a/PUP/Transport/UDP.cs b/PUP/Transport/UDP.cs index 31a1a8e..25866db 100644 --- a/PUP/Transport/UDP.cs +++ b/PUP/Transport/UDP.cs @@ -62,7 +62,7 @@ namespace IFS.Transport /// and go with the broadcast implementation. /// /// - 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. /// /// - 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); } /// - /// 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. /// /// /// @@ -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); + /// + /// Sends a stream of bytes over the network as a 3mbit packet encapsulated in a UDP datagram. + /// + /// + 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); } /// @@ -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; diff --git a/PUP/etherdata.bin b/PUP/etherdata.bin new file mode 100644 index 0000000000000000000000000000000000000000..6d17cf9d15fb9f4a2358a2d079f3b8c755d005fa GIT binary patch literal 8192 zcmeIu0Sy2E0K%a6Pi+o2h(KY$fB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM GyblZ@00031 literal 0 HcmV?d00001 diff --git a/PUP/ethertext.bin b/PUP/ethertext.bin new file mode 100644 index 0000000000000000000000000000000000000000..f2fc4ee316c1568e36d5726e2d029c51a66a4301 GIT binary patch literal 8192 zcmeH`zpE2L5P)ZsJ4tvkS>b7(`qZ3R3xB=DJ1i`O2wGT(q_9x1un>HR1Pcob;jXZa z=BY_KD_j4Dl~h{z6hQ==j9`0kqQ_O9b0{K{l$LYN+&4G7+4(YS0Za(We3?7$%!T%B zpK9Auk!zVmfq6&ok2RR>GMGnH8?_+$K)EL{H$#ecQM|{xJn#PCfd^fKlCfbj3sfLI&rL4>Ix9H!JASp%~B^#)yf{qrCFxS zG4zX>f;Zs3rTH1sdTW)7sMKVl)NKm`3ner10b^@*-Y)iE#aC1pRe|n+a&v^i^I{L0 z(XqdlTuV!Xh~i}|;(GFXURw#|2VWO=;AwXi0YDdbX))I`hiRVa%t^BX=3}0lahRL5 zp6Xf(o6G7KbF}Xc-5 zu%CXPdIXrYD9R%S#DEwO17bi7hygJm2E>3E5CdXB42S_SAO^&M7!U(uKn#chF(3xS Iz<)OI3x4m>qW}N^ literal 0 HcmV?d00001 diff --git a/PUP/readme.txt b/PUP/readme.txt index 957f477..f3c4002 100644 --- a/PUP/readme.txt +++ b/PUP/readme.txt @@ -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