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.");
+ }
}
}