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:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
27
Contralto/Configuration.cs
Normal file
27
Contralto/Configuration.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
BIN
Contralto/Disk/tdisk8.dsk
Normal file
Binary file not shown.
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user