1
0
mirror of https://github.com/livingcomputermuseum/ContrAlto.git synced 2026-01-28 12:48:48 +00:00

Ethernet! A really hacky version of Ethernet. Can play MazeWar, but still needs work.

This commit is contained in:
Josh Dersch
2016-01-15 14:44:24 -08:00
parent 09dc2cd3fc
commit 8463ef19d0
12 changed files with 550 additions and 54 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Contralto
{
/// <summary>
/// Encapsulates user-configurable settings. To be enhanced.
/// </summary>
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;
}
}

View File

@@ -51,6 +51,26 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="PcapDotNet.Base">
<HintPath>pcap\PcapDotNet.Base.dll</HintPath>
@@ -88,6 +108,7 @@
<Compile Include="AltoWindow.Designer.cs">
<DependentUpon>AltoWindow.cs</DependentUpon>
</Compile>
<Compile Include="Configuration.cs" />
<Compile Include="CPU\ALU.cs" />
<Compile Include="CPU\ConstantMemory.cs" />
<Compile Include="CPU\CPU.cs" />
@@ -178,6 +199,9 @@
<None Include="Disk\tdisk4.dsk">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Disk\tdisk8.dsk">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Disk\xmsmall.dsk">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@@ -305,6 +329,42 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Content Include="pcap\PcapDotNet.Base.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="pcap\PcapDotNet.Base.pdb">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="pcap\PcapDotNet.Base.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="pcap\PcapDotNet.Core.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="pcap\PcapDotNet.Core.Extensions.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="pcap\PcapDotNet.Core.Extensions.pdb">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="pcap\PcapDotNet.Core.Extensions.XML">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="pcap\PcapDotNet.Core.pdb">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="pcap\PcapDotNet.Core.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="pcap\PcapDotNet.Packets.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="pcap\PcapDotNet.Packets.pdb">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="pcap\PcapDotNet.Packets.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<EmbeddedResource Include="Alto.ico" />
<Content Include="Notes.txt" />
</ItemGroup>

View File

@@ -589,7 +589,7 @@ namespace Contralto
break;
case "EN":
Task = TaskType.Emulator;
Task = TaskType.Ethernet;
break;
case "MR":

BIN
Contralto/Disk/tdisk8.dsk Normal file

Binary file not shown.

View File

@@ -15,25 +15,36 @@ namespace Contralto.IO
{
_system = system;
// TODO: make this configurable
_ethernetAddress = 0x22;
_receiverLock = new System.Threading.ReaderWriterLockSlim();
_fifo = new Queue<ushort>();
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; }
}
/// <summary>
@@ -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);
}
}
/// <summary>
/// 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
/// <param name="data"></param>
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<ushort> _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;
}

View File

@@ -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<EthernetInterface> 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.");
}
/// <summary>
@@ -164,13 +218,28 @@ namespace Contralto.IO
/// </summary>
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
/// <summary>

View File

@@ -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");

View File

@@ -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 <address>" : specifies ethernet host address in octal (1-377)
// "-hostinterface <name>" : specifies the name of the host ethernet interface to use
// "-listinterfaces" : lists ethernet interfaces known by pcap
// "-drive0 <image>" : attaches disk image to drive 0
// "-drive1 <image>" : 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<EthernetInterface> 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.");
}
}
}