diff --git a/Contralto.sln b/Contralto.sln index 88d78a0..12925ba 100644 --- a/Contralto.sln +++ b/Contralto.sln @@ -12,18 +12,24 @@ Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Debug|Any CPU.Build.0 = Debug|Any CPU {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Debug|x64.ActiveCfg = Debug|x64 {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Debug|x64.Build.0 = Debug|x64 + {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Debug|x86.ActiveCfg = Debug|x86 + {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Debug|x86.Build.0 = Debug|x86 {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Release|Any CPU.ActiveCfg = Release|Any CPU {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Release|Any CPU.Build.0 = Release|Any CPU {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Release|x64.ActiveCfg = Release|x64 {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Release|x64.Build.0 = Release|x64 + {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Release|x86.ActiveCfg = Release|x86 + {CC6D96B3-8099-4497-8AD8-B0795A3353EA}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Contralto/AltoWindow.cs b/Contralto/AltoWindow.cs index 98d3989..6bc9635 100644 --- a/Contralto/AltoWindow.cs +++ b/Contralto/AltoWindow.cs @@ -214,6 +214,10 @@ namespace Contralto // TODO: invoke the debugger when an error is hit //OnDebuggerShowClick(null, null); SystemStatusLabel.Text = _systemErrorText; + + Console.WriteLine("Execution error: {0} - {1}", e.Message, e.StackTrace); + + System.Diagnostics.Debugger.Break(); } // @@ -367,7 +371,7 @@ namespace Contralto protected override bool ProcessKeyEventArgs(ref Message m) { // Grab the scancode from the message - int scanCode = ((int)m.LParam >> 16) & 0x1ff; + int scanCode = (int)((m.LParam.ToInt64() >> 16) & 0x1ff); bool down = false; const int WM_KEYDOWN = 0x100; diff --git a/Contralto/CPU/Tasks/EthernetTask.cs b/Contralto/CPU/Tasks/EthernetTask.cs index 07bf69a..4dcd34c 100644 --- a/Contralto/CPU/Tasks/EthernetTask.cs +++ b/Contralto/CPU/Tasks/EthernetTask.cs @@ -28,7 +28,7 @@ namespace Contralto.CPU // The resulting [Countdown] wakeup is cleared when the Ether task next runs. _ethernetController.CountdownWakeup = false; _wakeup = false; - } + } return base.ExecuteInstruction(instruction); } @@ -49,15 +49,26 @@ namespace Contralto.CPU } } + protected override void ExecuteSpecialFunction1Early(MicroInstruction instruction) + { + EthernetF1 ef1 = (EthernetF1)instruction.F1; + switch (ef1) + { + case EthernetF1.EILFCT: + // Early: Input Look Function. Gates the contents of the FIFO to BUS[0-15] but does + // not increment the read pointer. + _busData &= _ethernetController.ReadInputFifo(true /* do not increment read pointer */); + break; + } + } + protected override void ExecuteSpecialFunction1(MicroInstruction instruction) { EthernetF1 ef1 = (EthernetF1)instruction.F1; switch(ef1) { case EthernetF1.EILFCT: - // Input Look Function. Gates the contents of the FIFO to BUS[0-15] but does - // not increment the read pointer. - _busData &= _ethernetController.ReadInputFifo(true /* do not increment read pointer */); + // Nothing; handled in Early handler. break; case EthernetF1.EPFCT: @@ -106,7 +117,7 @@ namespace Contralto.CPU break; case EthernetF2.ERBFCT: - // Reset Branch Funct ion. This command dispatch function merges the ICMD + // Reset Branch Function. This command dispatch function merges the ICMD // and OCMD flip flops, into NEXT[6-7]. These flip flops are the means of // communication between the emulator task and the Ethernet task. The // emulator task sets them from BUS[14-15] with the STARTF function, causing @@ -148,7 +159,7 @@ namespace Contralto.CPU // empty. if (!_ethernetController.FIFOEmpty) { - Log.Write(LogComponent.EthernetController, "ECBFCT: FIFO empty"); + Log.Write(LogComponent.EthernetController, "ECBFCT: FIFO not empty"); _nextModifier |= 0x4; } break; diff --git a/Contralto/CPU/Tasks/Task.cs b/Contralto/CPU/Tasks/Task.cs index 02a2d92..d4932a2 100644 --- a/Contralto/CPU/Tasks/Task.cs +++ b/Contralto/CPU/Tasks/Task.cs @@ -227,7 +227,7 @@ namespace Contralto.CPU // // Let F1s that need to modify bus data before the ALU runs do their thing - // (this is just used by the emulator RSNF...) + // (this is used by the emulator RSNF and Ethernet EILFCT) // ExecuteSpecialFunction1Early(instruction); diff --git a/Contralto/Configuration.cs b/Contralto/Configuration.cs new file mode 100644 index 0000000..40a6b5e --- /dev/null +++ b/Contralto/Configuration.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Contralto +{ + /// + /// Encapsulates user-configurable settings. To be enhanced. + /// + public class Configuration + { + static Configuration() + { + // Initialize things to defaults + HostAddress = 0x22; + + } + + public static string Drive0Image; + public static string Drive1Image; + public static byte HostAddress; + public static string HostEthernetInterfaceName; + } + +} diff --git a/Contralto/Contralto.csproj b/Contralto/Contralto.csproj index 17e1b44..de51889 100644 --- a/Contralto/Contralto.csproj +++ b/Contralto/Contralto.csproj @@ -51,6 +51,26 @@ MinimumRecommendedRules.ruleset true + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + true + pcap\PcapDotNet.Base.dll @@ -88,6 +108,7 @@ AltoWindow.cs + @@ -178,6 +199,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -305,6 +329,42 @@ + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + diff --git a/Contralto/Debugger.cs b/Contralto/Debugger.cs index 9d7d765..73f0401 100644 --- a/Contralto/Debugger.cs +++ b/Contralto/Debugger.cs @@ -589,7 +589,7 @@ namespace Contralto break; case "EN": - Task = TaskType.Emulator; + Task = TaskType.Ethernet; break; case "MR": diff --git a/Contralto/Disk/tdisk8.dsk b/Contralto/Disk/tdisk8.dsk new file mode 100644 index 0000000..ee0cf5e Binary files /dev/null and b/Contralto/Disk/tdisk8.dsk differ diff --git a/Contralto/IO/EthernetController.cs b/Contralto/IO/EthernetController.cs index 9f51610..891e26e 100644 --- a/Contralto/IO/EthernetController.cs +++ b/Contralto/IO/EthernetController.cs @@ -15,25 +15,36 @@ namespace Contralto.IO { _system = system; - // TODO: make this configurable - _ethernetAddress = 0x22; + _receiverLock = new System.Threading.ReaderWriterLockSlim(); _fifo = new Queue(); Reset(); - _fifoWakeupEvent = new Event(_fifoTransmitDuration, null, FIFOCallback); + _fifoTransmitWakeupEvent = new Event(_fifoTransmitDuration, null, OutputFifoCallback); + _fifoReceiveWakeupEvent = new Event(_fifoReceiveDuration, null, InputFifoCallback); - _hostEthernet = new HostEthernet(null); + // Attach real Ethernet device if user has specified one, otherwise leave unattached; output data + // will go into a bit-bucket. + if (!String.IsNullOrEmpty(Configuration.HostEthernetInterfaceName)) + { + _hostEthernet = new HostEthernet(Configuration.HostEthernetInterfaceName); + _hostEthernet.RegisterReceiveCallback(OnHostPacketReceived); + } + + // More words than the Alto will ever send. + _outputData = new ushort[4096]; } public void Reset() - { - ResetInterface(); + { + _pollEvent = null; + + ResetInterface(); } public byte Address { - get { return _ethernetAddress; } + get { return Configuration.HostAddress; } } /// @@ -82,13 +93,14 @@ namespace Contralto.IO public void ResetInterface() { + _receiverLock.EnterWriteLock(); // Latch status before resetting _status = (ushort)( (0xffc0) | // bits always set (_dataLate ? 0x00 : 0x20) | (_collision ? 0x00 : 0x10) | (_crcBad ? 0x00 : 0x08) | - ((~_ioCmd & 0x3) << 1) | + ((~0 & 0x3) << 1) | // TODO: we're clearing the IOCMD bits here early -- validate why this works. (_incomplete ? 0x00 : 0x01)); _ioCmd = 0; @@ -99,13 +111,18 @@ namespace Contralto.IO _crcBad = false; _incomplete = false; _fifo.Clear(); - + _incomingPacket = null; + _incomingPacketLength = 0; + _inGone = false; + //_packetReady = false; + if (_system.CPU != null) { _system.CPU.BlockTask(TaskType.Ethernet); } Log.Write(LogComponent.EthernetController, "Interface reset."); + _receiverLock.ExitWriteLock(); } public ushort ReadInputFifo(bool lookOnly) @@ -116,14 +133,42 @@ namespace Contralto.IO return 0; } + ushort read = 0; + if (lookOnly) { - return _fifo.Peek(); + Log.Write(LogComponent.EthernetController, "Peek into FIFO, returning {0} (length {1})", Conversion.ToOctal(_fifo.Peek()), _fifo.Count); + read = _fifo.Peek(); } else { - return _fifo.Dequeue(); - } + read = _fifo.Dequeue(); + Log.Write(LogComponent.EthernetController, "Read from FIFO, returning {0} (length now {1})", Conversion.ToOctal(read), _fifo.Count); + + if (_fifo.Count < 2) + { + if (_inGone) + { + // + // Receiver is done and we're down to the last word (the checksum) + // which never gets pulled from the FIFO. + // clear IBUSY to indicate to the microcode that we've finished. + // + _iBusy = false; + _system.CPU.WakeupTask(TaskType.Ethernet); + } + else + { + // + // Still more data, but we block the Ethernet task until it is put + // into the FIFO. + // + _system.CPU.BlockTask(TaskType.Ethernet); + } + } + } + + return read; } public void WriteOutputFifo(ushort data) @@ -137,7 +182,7 @@ namespace Contralto.IO _fifo.Enqueue(data); // If the FIFO is full, start transmitting and clear Wakeups - if (_fifo.Count == 16) + if (_fifo.Count == 15) { if (_oBusy) { @@ -192,12 +237,12 @@ namespace Contralto.IO private void TransmitFIFO(bool end) { // Schedule a callback to pick up the data and shuffle it out the host interface. - _fifoWakeupEvent.Context = end; - _fifoWakeupEvent.TimestampNsec = _fifoTransmitDuration; - _system.Scheduler.Schedule(_fifoWakeupEvent); + _fifoTransmitWakeupEvent.Context = end; + _fifoTransmitWakeupEvent.TimestampNsec = _fifoTransmitDuration; + _system.Scheduler.Schedule(_fifoTransmitWakeupEvent); } - private void FIFOCallback(ulong timeNsec, ulong skewNsec, object context) + private void OutputFifoCallback(ulong timeNsec, ulong skewNsec, object context) { bool end = (bool)context; @@ -205,7 +250,7 @@ namespace Contralto.IO { // If OBUSY is no longer set then the interface was reset before // we got to run; abandon this operation. - Log.Write(LogComponent.EthernetController, "FIFO callback after reset, abandoning."); + Log.Write(LogComponent.EthernetController, "FIFO callback after reset, abandoning output."); return; } @@ -231,25 +276,63 @@ namespace Contralto.IO _system.CPU.WakeupTask(TaskType.Ethernet); // And actually tell the host ethernet interface to send the data. - _hostEthernet.Send(_outputData, _outputIndex); + if (_hostEthernet != null) + { + _hostEthernet.Send(_outputData, _outputIndex); + } + _outputIndex = 0; } } private void InitializeReceiver() { - // TODO: pull next packet off host ethernet interface that's destined for the Alto, and start the - // process of putting into the FIFO and generating wakeups for the microcode. - _receiverWaiting = true; + // " Sets the IBusy flip flop in the interface..." + // "...restarting the receiver... causes [the controller] to ignore the current packet and hunt + // for the beginning of the next packet." + + // + // So, two things: + // 1) Cancel any pending input packets + // 2) Start listening for more packets if we weren't already doing so. + // + _receiverLock.EnterWriteLock(); + if (_iBusy) + { + Log.Write(LogComponent.EthernetController, "Receiver initializing, dropping current activity."); + _system.Scheduler.CancelEvent(_fifoReceiveWakeupEvent); + _incomingPacket = null; + _incomingPacketLength = 0; + } + + _iBusy = true; + _receiverLock.ExitWriteLock(); + + _system.CPU.BlockTask(TaskType.Ethernet); + + Log.Write(LogComponent.EthernetController, "Receiver initialized."); + + // + // TODO: + // This hack is ugly and it wants to die. Ethernet packets come in asynchronously from another thread. + // The scheduler is not thread-safe (and making it so incurs a serious performance penalty) so the receiver + // thread cannot post an event to wake up the rcv FIFO. Instead we poll periodically and start processing + // new packets if one has arrived. + // + if (_pollEvent == null) + { + _pollEvent = new Event(_pollPeriod, null, PacketPoll); + _system.Scheduler.Schedule(_pollEvent); + } } /// /// Invoked when the host ethernet interface receives a packet destined for us. - /// TODO: determine the best behavior here; we could queue up a number of packets and let + /// TODO: determine the best behavior here; we could queue up a number of incoming packets and let /// the emulated interface pull them off one by one, or we could only save one packet and discard /// any that arrive while the emulated interface is processing the current one. /// - /// The latter is probably more faithful to the intent, but the former might be more useful on + /// The latter is probably more faithful to the original intent, but the former might be more useful on /// busy Ethernets (though the bottom-level filter implemented by HostEthernet might take care /// of that already.) /// @@ -258,14 +341,138 @@ namespace Contralto.IO /// private void OnHostPacketReceived(MemoryStream data) { - if (_incomingPacket == null) + _receiverLock.EnterWriteLock(); + _packetReady = true; + _nextPacket = data; + _receiverLock.ExitWriteLock(); + } + + private void PacketPoll(ulong timeNsec, ulong skewNsec, object context) + { + _receiverLock.EnterUpgradeableReadLock(); + if (_packetReady) { - _incomingPacket = data; + // Schedule the next word of data. + Console.WriteLine("**** hack *****"); + + if (_iBusy && _incomingPacket == null) + { + _incomingPacket = _nextPacket; + + // Read the packet length (in words) (first word of the packet). Convert to bytes. + // + _incomingPacketLength = ((_incomingPacket.ReadByte()) | (_incomingPacket.ReadByte() << 8)) * 2; + + // Sanity check: + if (_incomingPacketLength > _incomingPacket.Length - 2) + { + throw new InvalidOperationException("Invalid 3mbit packet length header."); + } + + Log.Write(LogComponent.EthernetController, "Accepting incoming packet (length {0}).", _incomingPacketLength); + + // From uCode: + // "Interface will generate a data wakeup when the first word of the next + // "packet arrives, ignoring any packet currently passing." + // + // Read the first word, place it in the fifo and wake up the ethernet task. + // + //ushort nextWord = (ushort)((_incomingPacket.ReadByte()) | (_incomingPacket.ReadByte() << 8)); + //_fifo.Enqueue(nextWord); + //_incomingPacketLength -= 2; + //_system.CPU.WakeupTask(TaskType.Ethernet); + + // Wake up the FIFO + _fifoReceiveWakeupEvent.TimestampNsec = _fifoReceiveDuration; + _system.Scheduler.Schedule(_fifoReceiveWakeupEvent); + } + else + { + // Drop, we're either already busy with a packet or we're not listening right now. + Log.Write(LogComponent.EthernetController, "Dropping incoming packet; controller is currently busy or not active (ibusy {0}, packet {1})", _iBusy, _incomingPacket != null); + } + + _receiverLock.EnterWriteLock(); + _packetReady = false; + _nextPacket = null; + _receiverLock.ExitWriteLock(); + } + _receiverLock.ExitUpgradeableReadLock(); + + // Do it again. + _pollEvent.TimestampNsec = _pollPeriod; + _system.Scheduler.Schedule(_pollEvent); + } + + private void InputFifoCallback(ulong timeNsec, ulong skewNsec, object context) + { + _receiverLock.EnterUpgradeableReadLock(); + if (!_iBusy || _inGone) + { + // If IBUSY is no longer set then the interface was reset before + // we got to run; abandon this operation. + Log.Write(LogComponent.EthernetController, "FIFO callback after reset, abandoning input."); + _incomingPacket = null; + _incomingPacketLength = 0; + _receiverLock.ExitUpgradeableReadLock(); + return; + } + + if (_fifo.Count >= 16) + { + _fifoReceiveWakeupEvent.TimestampNsec = _fifoReceiveDuration - skewNsec; + _system.Scheduler.Schedule(_fifoReceiveWakeupEvent); + + Log.Write(LogComponent.EthernetController, "Input FIFO full? Scheduling next wakeup."); + _receiverLock.ExitUpgradeableReadLock(); + return; + } + + Log.Write(LogComponent.EthernetController, "Processing word from input packet ({0} bytes left in input, {1} words in FIFO.)", _incomingPacketLength, _fifo.Count); + + if (_incomingPacketLength >= 2) + { + // Stuff 1 word into the FIFO, if we run out of data to send then we clear _iBusy. + ushort nextWord = (ushort)((_incomingPacket.ReadByte()) | (_incomingPacket.ReadByte() << 8)); + _fifo.Enqueue(nextWord); + + _incomingPacketLength -= 2; + } + else if (_incomingPacketLength == 1) + { + // should never happen + throw new InvalidOperationException("Packet length not multiple of 2 on receive."); + } + + // All out of data? Finish the receive operation. + if (_incomingPacketLength == 0) + { + _receiverLock.EnterWriteLock(); + _inGone = true; + _incomingPacket = null; + _receiverLock.ExitWriteLock(); + + // Wakeup for end of data. + _system.CPU.WakeupTask(TaskType.Ethernet); + + Log.Write(LogComponent.EthernetController, "Receive complete."); } else { - // Drop. + // Schedule the next wakeup. + _fifoReceiveWakeupEvent.TimestampNsec = _fifoReceiveDuration - skewNsec; + _system.Scheduler.Schedule(_fifoReceiveWakeupEvent); + + Log.Write(LogComponent.EthernetController, "Scheduling next wakeup."); } + + // Wake up the Ethernet task to process this data + if (_fifo.Count >= 2) + { + _system.CPU.WakeupTask(TaskType.Ethernet); + } + + _receiverLock.ExitUpgradeableReadLock(); } private Queue _fifo; @@ -277,16 +484,27 @@ namespace Contralto.IO private bool _crcBad; private bool _incomplete; private ushort _status; - - private byte _ethernetAddress; + private bool _countdownWakeup; private bool _oBusy; - private bool _iBusy; + private bool _iBusy; + private bool _inGone; // FIFO scheduling + + // Transmit: private ulong _fifoTransmitDuration = 87075; // ~87000 nsec to transmit 16 words at 3mbit, assuming no collision - private Event _fifoWakeupEvent; + private Event _fifoTransmitWakeupEvent; + + // Receive: + private ulong _fifoReceiveDuration = 5400; // ~5400 nsec to receive 1 word at 3mbit + private Event _fifoReceiveWakeupEvent; + + // Polling (hack) + private ulong _pollPeriod = 23000; + private Event _pollEvent; + private bool _packetReady; // The actual connection to a real Ethernet device on the host HostEthernet _hostEthernet; @@ -295,8 +513,11 @@ namespace Contralto.IO ushort[] _outputData; int _outputIndex; - // Incoming data - MemoryStream _incomingPacket; + // Incoming data and locking + private MemoryStream _incomingPacket; + private MemoryStream _nextPacket; + private int _incomingPacketLength; + private System.Threading.ReaderWriterLockSlim _receiverLock; private AltoSystem _system; } diff --git a/Contralto/IO/HostEthernet.cs b/Contralto/IO/HostEthernet.cs index d139fe7..16bc28c 100644 --- a/Contralto/IO/HostEthernet.cs +++ b/Contralto/IO/HostEthernet.cs @@ -10,6 +10,8 @@ using PcapDotNet.Core.Extensions; using PcapDotNet.Packets; using PcapDotNet.Packets.Ethernet; using System.IO; +using Contralto.Logging; +using System.Threading; namespace Contralto.IO { @@ -55,12 +57,29 @@ namespace Contralto.IO AttachInterface(iface); } + public HostEthernet(string name) + { + // Find the specified device by name + List interfaces = EthernetInterface.EnumerateDevices(); + + foreach (EthernetInterface i in interfaces) + { + if (name == i.Name) + { + AttachInterface(i); + return; + } + } + + throw new InvalidOperationException("Specified ethernet interface does not exist."); + } + public void RegisterReceiveCallback(ReceivePacketDelegate callback) { _callback = callback; // Now that we have a callback we can start receiving stuff. - Open(false /* not promiscuous */, int.MaxValue); + Open(true /* promiscuous */, int.MaxValue); BeginReceive(); } @@ -78,17 +97,37 @@ namespace Contralto.IO throw new InvalidOperationException("Raw packet data must contain at least two bytes for addressing."); } + + // + // Outgoing packet contains 2 extra words (4 bytes): + // - prepended packet length (one word) + // - appended checksum (one word) + byte[] packetBytes = new byte[length * 2 + 4]; + + // + // First two bytes include the length of the 3mbit packet (including checksum); since 10mbit packets have a minimum length of 46 + // bytes, and 3mbit packets have no minimum length this is necessary so the receiver can pull out the + // correct amount of data. + // + packetBytes[0] = (byte)(length + 1); + packetBytes[1] = (byte)((length + 1) >> 8); + // // Do this annoying dance to stuff the ushorts into bytes because this is C#. // - byte[] packetBytes = new byte[length * 2]; - for (int i = 0; i < length; i++) { - packetBytes[i * 2] = (byte)(packet[i] >> 8); - packetBytes[i * 2 + 1] = (byte)packet[i]; + packetBytes[i * 2 + 2] = (byte)(packet[i]); + packetBytes[i * 2 + 3] = (byte)(packet[i] >> 8); } + // + // Append the checksum. + // TODO: actually calculate it. + // + packetBytes[length * 2 + 2] = 0xbe; + packetBytes[length * 2 + 3] = 0xef; + // // Grab the source and destination host addresses from the packet we're sending // and build 10mbit versions. @@ -96,6 +135,11 @@ namespace Contralto.IO byte destinationHost = packetBytes[0]; byte sourceHost = packetBytes[1]; + Log.Write(LogComponent.HostEthernet, "Sending packet; source {0} destination {1}, length {2} words.", + Conversion.ToOctal(sourceHost), + Conversion.ToOctal(destinationHost), + length); + MacAddress destinationMac = new MacAddress((UInt48)(_10mbitMACPrefix | destinationHost)); MacAddress sourceMac = new MacAddress((UInt48)(_10mbitMACPrefix | sourceHost)); @@ -115,7 +159,9 @@ namespace Contralto.IO PacketBuilder builder = new PacketBuilder(ethernetLayer, payloadLayer); // Send it over the 'net! - _communicator.SendPacket(builder.Build(DateTime.Now)); + _communicator.SendPacket(builder.Build(DateTime.Now)); + + Log.Write(LogComponent.HostEthernet, "Encapsulated 3mbit packet sent."); } private void ReceiveCallback(Packet p) @@ -126,10 +172,11 @@ namespace Contralto.IO if ((int)p.Ethernet.EtherType == _3mbitFrameType && (p.Ethernet.Destination.ToValue() & 0xffffffffff00) == _10mbitMACPrefix ) { + Log.Write(LogComponent.HostEthernet, "Received encapsulated 3mbit packet."); _callback(p.Ethernet.Payload.ToMemoryStream()); } else - { + { // Not for us, discard the packet. } } @@ -152,11 +199,18 @@ namespace Contralto.IO { throw new InvalidOperationException("Requested interface not found."); } + + Log.Write(LogComponent.HostEthernet, "Attached to host interface {0}", iface.Name); } private void Open(bool promiscuous, int timeout) { - _communicator = _interface.Open(0xffff, promiscuous ? PacketDeviceOpenAttributes.Promiscuous : PacketDeviceOpenAttributes.None, timeout); + _communicator = _interface.Open(65536, promiscuous ? PacketDeviceOpenAttributes.Promiscuous | PacketDeviceOpenAttributes.NoCaptureLocal : PacketDeviceOpenAttributes.NoCaptureLocal, timeout); + + // Set this to 1 so we'll get packets as soon as they arrive, no buffering. + _communicator.SetKernelMinimumBytesToCopy(1); + + Log.Write(LogComponent.HostEthernet, "Host interface opened and receiving packets."); } /// @@ -164,13 +218,28 @@ namespace Contralto.IO /// private void BeginReceive() { - _communicator.ReceivePackets(-1, ReceiveCallback); + // Kick off receive thread. + _receiveThread = new Thread(ReceiveThread); + _receiveThread.Start(); } + private void ReceiveThread() + { + // Just call ReceivePackets, that's it. This will never return. + // (probably need to make this more elegant so we can tear down the thread + // properly.) + Log.Write(LogComponent.HostEthernet, "Receiver thread started."); + _communicator.ReceivePackets(-1, ReceiveCallback); + } + private LivePacketDevice _interface; private PacketCommunicator _communicator; private ReceivePacketDelegate _callback; + + // Thread used for receive + private Thread _receiveThread; + private const int _3mbitFrameType = 0xbeef; // easy to identify, ostensibly unused by anything of any import /// diff --git a/Contralto/Logging/Log.cs b/Contralto/Logging/Log.cs index 4357653..ca878a4 100644 --- a/Contralto/Logging/Log.cs +++ b/Contralto/Logging/Log.cs @@ -25,6 +25,7 @@ namespace Contralto.Logging EthernetController = 0x400, EthernetTask = 0x800, TaskSwitch = 0x1000, + HostEthernet = 0x2000, Debug = 0x40000000, All = 0x7fffffff @@ -52,7 +53,7 @@ namespace Contralto.Logging static Log() { // TODO: make configurable - _components = LogComponent.CPU; // LogComponent.DiskController | LogComponent.DiskSectorTask | LogComponent.Debug | LogComponent.CPU; // LogComponent.EthernetController; // | LogComponent.Microcode | LogComponent.Memory | LogComponent.CPU; + _components = LogComponent.HostEthernet | LogComponent.EthernetController; // LogComponent.DiskController | LogComponent.DiskSectorTask | LogComponent.Debug | LogComponent.CPU; // LogComponent.EthernetController; // | LogComponent.Microcode | LogComponent.Memory | LogComponent.CPU; _type = LogType.Normal | LogType.Warning | LogType.Error | LogType.Verbose; _logStream = new StreamWriter("log.txt"); diff --git a/Contralto/Program.cs b/Contralto/Program.cs index dba263f..dd18c94 100644 --- a/Contralto/Program.cs +++ b/Contralto/Program.cs @@ -1,5 +1,7 @@ using Contralto.CPU; +using Contralto.IO; using System; +using System.Collections.Generic; namespace Contralto { @@ -8,11 +10,21 @@ namespace Contralto [STAThread] static void Main(string[] args) { + // Handle command-line args + PrintHerald(); + ParseCommandLine(args); + + AltoSystem system = new AltoSystem(); - if (args.Length > 0) + if (!String.IsNullOrEmpty(Configuration.Drive0Image)) { - system.LoadDrive(0, args[0]); + system.LoadDrive(0, Configuration.Drive0Image); + } + + if (!String.IsNullOrEmpty(Configuration.Drive1Image)) + { + system.LoadDrive(1, Configuration.Drive1Image); } AltoWindow mainWindow = new AltoWindow(); @@ -30,5 +42,90 @@ namespace Contralto } + + private static void PrintHerald() + { + Console.WriteLine("ContrAlto v0.1 (c) 2015, 2016 Living Computer Museum."); + Console.WriteLine("Bug reports to joshd@livingcomputermuseum.org"); + Console.WriteLine(); + } + + private static void ParseCommandLine(string[] args) + { + // At the moment, options start with a "-" and are one of: + // "-hostaddress
" : specifies ethernet host address in octal (1-377) + // "-hostinterface " : specifies the name of the host ethernet interface to use + // "-listinterfaces" : lists ethernet interfaces known by pcap + // "-drive0 " : attaches disk image to drive 0 + // "-drive1 " : attaches disk image to drive 1 + + int index = 0; + + // TODO: this parsing needs to be made not terrible. + while(index < args.Length) + { + switch (args[index++].ToLower()) + { + case "-hostaddress": + if (index < args.Length) + { + Configuration.HostAddress = Convert.ToByte(args[index++], 8); + } + else + { + PrintUsage(); + } + break; + + case "-hostinterface": + if (index < args.Length) + { + Configuration.HostEthernetInterfaceName = args[index++]; + } + else + { + PrintUsage(); + } + break; + + case "-listinterfaces": + List interfaces = EthernetInterface.EnumerateDevices(); + + foreach (EthernetInterface i in interfaces) + { + Console.WriteLine("Name: '{0}'\n Description: '{1}'\n MAC '{2}'", i.Name, i.Description, i.MacAddress); + } + break; + + case "-drive0": + if (index < args.Length) + { + Configuration.Drive0Image = args[index++]; + } + else + { + PrintUsage(); + } + break; + + case "-drive1": + if (index < args.Length) + { + Configuration.Drive1Image = args[index++]; + } + else + { + PrintUsage(); + } + break; + } + } + } + + private static void PrintUsage() + { + // TODO: make more useful. + Console.WriteLine("Something is wrong, try again."); + } } }