From 898abdc906fec4f7adc0ee46b3abbad2d2845759 Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Tue, 12 Jan 2016 17:29:42 -0800 Subject: [PATCH] Minor fixes for debugger interface and disassembler; started hooking Ethernet interface up to real host Ethernet (because MazeWar.) --- Contralto/CPU/Shifter.cs | 6 + Contralto/CPU/Tasks/EmulatorTask.cs | 5 +- Contralto/CPU/Tasks/Task.cs | 5 + Contralto/CPU/UCodeDisassembler.cs | 14 +-- Contralto/CPU/UCodeMemory.cs | 7 +- Contralto/Contralto.csproj | 13 ++ Contralto/Debugger.Designer.cs | 4 +- Contralto/Debugger.cs | 101 ++++++++++----- Contralto/IO/EthernetController.cs | 47 ++++++- Contralto/IO/HostEthernet.cs | 183 ++++++++++++++++++++++++++++ Contralto/Memory/MemoryBus.cs | 3 +- 11 files changed, 339 insertions(+), 49 deletions(-) create mode 100644 Contralto/IO/HostEthernet.cs diff --git a/Contralto/CPU/Shifter.cs b/Contralto/CPU/Shifter.cs index 9178e6c..122f98b 100644 --- a/Contralto/CPU/Shifter.cs +++ b/Contralto/CPU/Shifter.cs @@ -185,6 +185,12 @@ namespace Contralto.CPU int c = (_output & 0x1) << 15; _output = (ushort)((_output >> 1) | c); } + + if (_dns) + { + // Should never happen + throw new InvalidOperationException("DNS on Rotate Right, not possible."); + } break; default: diff --git a/Contralto/CPU/Tasks/EmulatorTask.cs b/Contralto/CPU/Tasks/EmulatorTask.cs index 44c33e6..e61cfcd 100644 --- a/Contralto/CPU/Tasks/EmulatorTask.cs +++ b/Contralto/CPU/Tasks/EmulatorTask.cs @@ -117,7 +117,7 @@ namespace Contralto.CPU // Dispatch to the appropriate device. // The Ethernet controller is the only common device that is documented // to have used STARTF, so we'll just go there directly; if other - // hardware is discovered we'll put together a more flexible dispatch. + // hardware is discovered to be worth emulating we'll put together a more flexible dispatch. // if (_busData < 4) { @@ -186,9 +186,6 @@ namespace Contralto.CPU // // "...DNS also addresses R from (3-IR[3 - 4])..." // - - // TODO: is S reg select impacted by this? - //_srSelect = _rSelect = (_rSelect & 0xfffc) | ((((uint)_cpu._ir & 0x1800) >> 11) ^ 3); break; diff --git a/Contralto/CPU/Tasks/Task.cs b/Contralto/CPU/Tasks/Task.cs index 3fa1ada..02a2d92 100644 --- a/Contralto/CPU/Tasks/Task.cs +++ b/Contralto/CPU/Tasks/Task.cs @@ -27,6 +27,11 @@ namespace Contralto.CPU get { return (int)_taskType; } } + public TaskType TaskType + { + get { return _taskType; } + } + public bool Wakeup { get { return _wakeup; } diff --git a/Contralto/CPU/UCodeDisassembler.cs b/Contralto/CPU/UCodeDisassembler.cs index eafafa4..037cb82 100644 --- a/Contralto/CPU/UCodeDisassembler.cs +++ b/Contralto/CPU/UCodeDisassembler.cs @@ -38,12 +38,12 @@ namespace Contralto.CPU break; case BusSource.LoadR: - source = "0 "; + source = "L "; loadR = true; break; case BusSource.None: - source = "177777 "; + source = "177777(no source) "; break; case BusSource.TaskSpecific1: @@ -289,7 +289,7 @@ namespace Contralto.CPU default: loadS = false; - return String.Format("BS{0}", Conversion.ToOctal((int)instruction.BS)); + return String.Format("BS {0}", Conversion.ToOctal((int)instruction.BS)); } } @@ -301,7 +301,7 @@ namespace Contralto.CPU return DisassembleEmulatorSpecialFunction1(instruction); default: - return String.Format("F1{0}", Conversion.ToOctal((int)instruction.F1)); + return String.Format("F1 {0}", Conversion.ToOctal((int)instruction.F1)); } } @@ -313,7 +313,7 @@ namespace Contralto.CPU return DisassembleEmulatorSpecialFunction2(instruction); default: - return String.Format("F2{0}", Conversion.ToOctal((int)instruction.F2)); + return String.Format("F2 {0}", Conversion.ToOctal((int)instruction.F2)); } } @@ -367,7 +367,7 @@ namespace Contralto.CPU return "STARTF "; default: - return String.Format("F1{0}", Conversion.ToOctal((int)ef1)); + return String.Format("F1 {0}", Conversion.ToOctal((int)ef1)); } } @@ -400,7 +400,7 @@ namespace Contralto.CPU return "IDISP "; default: - return String.Format("F2{0}", Conversion.ToOctal((int)ef2)); + return String.Format("F2 {0}", Conversion.ToOctal((int)ef2)); } } } diff --git a/Contralto/CPU/UCodeMemory.cs b/Contralto/CPU/UCodeMemory.cs index c8ff2ac..744f9ef 100644 --- a/Contralto/CPU/UCodeMemory.cs +++ b/Contralto/CPU/UCodeMemory.cs @@ -93,10 +93,9 @@ namespace Contralto.CPU get { return _uCodeRam; } } - public static MicrocodeBank Bank - { - // Just return the Bank for the Emulator task for now - get { return _microcodeBank[(int)TaskType.Emulator]; } + public static MicrocodeBank GetBank(TaskType task) + { + return _microcodeBank[(int)task]; } public static void LoadControlRAMAddress(ushort address) diff --git a/Contralto/Contralto.csproj b/Contralto/Contralto.csproj index 24dac31..17e1b44 100644 --- a/Contralto/Contralto.csproj +++ b/Contralto/Contralto.csproj @@ -52,6 +52,18 @@ true + + pcap\PcapDotNet.Base.dll + + + pcap\PcapDotNet.Core.dll + + + pcap\PcapDotNet.Core.Extensions.dll + + + pcap\PcapDotNet.Packets.dll + @@ -108,6 +120,7 @@ + diff --git a/Contralto/Debugger.Designer.cs b/Contralto/Debugger.Designer.cs index b18af4a..10c0996 100644 --- a/Contralto/Debugger.Designer.cs +++ b/Contralto/Debugger.Designer.cs @@ -214,7 +214,7 @@ this._rom0SourceViewer.Size = new System.Drawing.Size(582, 554); this._rom0SourceViewer.TabIndex = 1; this._rom0SourceViewer.TabStop = false; - this._rom0SourceViewer.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.SourceViewCellClick); + this._rom0SourceViewer.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.Rom0SourceViewCellClick); // // Breakpoint // @@ -314,6 +314,7 @@ this._rom1SourceViewer.Size = new System.Drawing.Size(582, 545); this._rom1SourceViewer.TabIndex = 2; this._rom1SourceViewer.TabStop = false; + this._rom1SourceViewer.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.Rom1SourceViewCellClick); // // dataGridViewCheckBoxColumn1 // @@ -413,6 +414,7 @@ this._ram0SourceViewer.Size = new System.Drawing.Size(582, 545); this._ram0SourceViewer.TabIndex = 2; this._ram0SourceViewer.TabStop = false; + this._ram0SourceViewer.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.Ram0SourceViewCellClick); // // dataGridViewCheckBoxColumn2 // diff --git a/Contralto/Debugger.cs b/Contralto/Debugger.cs index 046d996..9d7d765 100644 --- a/Contralto/Debugger.cs +++ b/Contralto/Debugger.cs @@ -25,7 +25,7 @@ namespace Contralto { _system = system; _controller = controller; - _microcodeBreakpointEnabled = new bool[1024]; + _microcodeBreakpointEnabled = new bool[3,1024]; _novaBreakpointEnabled = new bool[65536]; _controller.StepCallback += OnExecutionStep; @@ -143,22 +143,24 @@ namespace Contralto // // Select active tab based on current UCode bank - switch (UCodeMemory.Bank) + MicrocodeBank bank = UCodeMemory.GetBank(_system.CPU.CurrentTask.TaskType); + + switch (bank) { case MicrocodeBank.ROM0: - SourceTabs.TabIndex = 0; + SourceTabs.SelectedIndex = 0; break; case MicrocodeBank.ROM1: - SourceTabs.TabIndex = 1; + SourceTabs.SelectedIndex = 1; break; case MicrocodeBank.RAM0: - SourceTabs.TabIndex = 2; + SourceTabs.SelectedIndex = 2; break; } - RefreshMicrocodeDisassembly(); + RefreshMicrocodeDisassembly(_system.CPU.CurrentTask.MPC); // Highlight the nova memory location corresponding to the emulator PC. // TODO: this should be configurable @@ -197,25 +199,23 @@ namespace Contralto this.BringToFront(); } - private void RefreshMicrocodeDisassembly() + private void RefreshMicrocodeDisassembly(ushort address) { // Update non-ROM code listings, depending on the currently active tab switch (SourceTabs.SelectedIndex) { case 0: // Find the right source line and highlight it. - HighlightMicrocodeSourceLine(_rom0SourceViewer, _system.CPU.CurrentTask.MPC); + HighlightMicrocodeSourceLine(_rom0SourceViewer, address); break; - case 1: - SourceTabs.TabIndex = 1; - HighlightMicrocodeSourceLine(_rom1SourceViewer, _system.CPU.CurrentTask.MPC); + case 1: + HighlightMicrocodeSourceLine(_rom1SourceViewer, address); break; - case 2: - SourceTabs.TabIndex = 2; - RefreshMicrocodeDisassembly(MicrocodeBank.RAM0); - HighlightMicrocodeSourceLine(_ram0SourceViewer, _system.CPU.CurrentTask.MPC); + case 2: + UpdateMicrocodeDisassembly(MicrocodeBank.RAM0); + HighlightMicrocodeSourceLine(_ram0SourceViewer, address); break; } } @@ -272,21 +272,60 @@ namespace Contralto /// /// /// - private void SourceViewCellClick(object sender, DataGridViewCellEventArgs e) + private void Rom0SourceViewCellClick(object sender, DataGridViewCellEventArgs e) + { + // Check for breakpoint column click. + if (e.ColumnIndex == 0) + { + SetBreakpointFromCellClick(MicrocodeBank.ROM0, e.RowIndex); + } + } + + private void Rom1SourceViewCellClick(object sender, DataGridViewCellEventArgs e) { // Check for breakpoint column click. if (e.ColumnIndex == 0) { - // See if this is a source line, if so check/uncheck the box - // and set/unset a breakpoint for the line - if (_rom0SourceViewer.Rows[e.RowIndex].Tag != null) - { - bool value = (bool)_rom0SourceViewer.Rows[e.RowIndex].Cells[0].Value; - _rom0SourceViewer.Rows[e.RowIndex].Cells[0].Value = !value; + SetBreakpointFromCellClick(MicrocodeBank.ROM1, e.RowIndex); + } + } - ModifyMicrocodeBreakpoint((UInt16)_rom0SourceViewer.Rows[e.RowIndex].Tag, !value); - } + private void Ram0SourceViewCellClick(object sender, DataGridViewCellEventArgs e) + { + // Check for breakpoint column click. + if (e.ColumnIndex == 0) + { + SetBreakpointFromCellClick(MicrocodeBank.RAM0, e.RowIndex); + } + } + private void SetBreakpointFromCellClick(MicrocodeBank bank, int index) + { + DataGridView view = null; + + switch(bank) + { + case MicrocodeBank.ROM0: + view = _rom0SourceViewer; + break; + + case MicrocodeBank.ROM1: + view = _rom1SourceViewer; + break; + + case MicrocodeBank.RAM0: + view = _ram0SourceViewer; + break; + } + + // See if this is a source line, if so check/uncheck the box + // and set/unset a breakpoint for the line + if (view.Rows[index].Tag != null) + { + bool value = (bool)view.Rows[index].Cells[0].Value; + view.Rows[index].Cells[0].Value = !value; + + ModifyMicrocodeBreakpoint(bank, (UInt16)view.Rows[index].Tag, !value); } } @@ -303,7 +342,7 @@ namespace Contralto } } - private void RefreshMicrocodeDisassembly(MicrocodeBank bank) + private void UpdateMicrocodeDisassembly(MicrocodeBank bank) { DataGridView view = null; uint[] uCode = null; @@ -414,9 +453,9 @@ namespace Contralto } - private void ModifyMicrocodeBreakpoint(UInt16 address, bool set) + private void ModifyMicrocodeBreakpoint(MicrocodeBank bank, UInt16 address, bool set) { - _microcodeBreakpointEnabled[address] = set; + _microcodeBreakpointEnabled[(int)bank,address] = set; } private bool GetNovaBreakpoint(UInt16 address) @@ -629,7 +668,7 @@ namespace Contralto private void OnTabChanged(object sender, EventArgs e) { - RefreshMicrocodeDisassembly(); + RefreshMicrocodeDisassembly(_system.CPU.CurrentTask.MPC); } private void Debugger_Load(object sender, EventArgs e) @@ -647,7 +686,7 @@ namespace Contralto UInt16 address = Convert.ToUInt16(JumpToAddress.Text, 8); // find the source address that matches this, if any. - HighlightMicrocodeSourceLine(_rom0SourceViewer, address); + RefreshMicrocodeDisassembly(address); } catch { @@ -768,7 +807,7 @@ namespace Contralto case ExecutionType.NextNovaInstruction: // See if we need to stop here if (_execAbort || // The Stop button was hit - _microcodeBreakpointEnabled[_system.CPU.CurrentTask.MPC] || // A microcode breakpoint was hit + _microcodeBreakpointEnabled[(int)UCodeMemory.GetBank(_system.CPU.CurrentTask.TaskType),_system.CPU.CurrentTask.MPC] || // A microcode breakpoint was hit (_execType == ExecutionType.NextTask && _system.CPU.NextTask != null && _system.CPU.NextTask != _system.CPU.CurrentTask) || // The next task was switched to @@ -914,7 +953,7 @@ namespace Contralto // Microcode Debugger breakpoints; one entry per address since we only need // to worry about a 10 bit address space, this is fast and uses little memory. - private bool[] _microcodeBreakpointEnabled; + private bool[,] _microcodeBreakpointEnabled; // Nova Debugger breakpoints; same as above private bool[] _novaBreakpointEnabled; diff --git a/Contralto/IO/EthernetController.cs b/Contralto/IO/EthernetController.cs index 7e670c8..9f51610 100644 --- a/Contralto/IO/EthernetController.cs +++ b/Contralto/IO/EthernetController.cs @@ -2,6 +2,7 @@ using Contralto.Logging; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -21,6 +22,8 @@ namespace Contralto.IO Reset(); _fifoWakeupEvent = new Event(_fifoTransmitDuration, null, FIFOCallback); + + _hostEthernet = new HostEthernet(null); } public void Reset() @@ -208,7 +211,9 @@ namespace Contralto.IO Log.Write(LogComponent.EthernetController, "Sending {0} words from fifo.", _fifo.Count); - // TODO: actually transmit data in FIFO rather than bit-bucketing it. + // Copy FIFO to host ethernet output buffer + _fifo.CopyTo(_outputData, _outputIndex); + _outputIndex += _fifo.Count; _fifo.Clear(); if (!end) @@ -224,6 +229,10 @@ namespace Contralto.IO // Wakeup at end of transmission. ("OUTGONE Post wakeup.") _system.CPU.WakeupTask(TaskType.Ethernet); + + // And actually tell the host ethernet interface to send the data. + _hostEthernet.Send(_outputData, _outputIndex); + _outputIndex = 0; } } @@ -231,6 +240,32 @@ namespace Contralto.IO { // 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; + } + + /// + /// 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 + /// 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 + /// busy Ethernets (though the bottom-level filter implemented by HostEthernet might take care + /// of that already.) + /// + /// For now, we just accept one at a time. + /// + /// + private void OnHostPacketReceived(MemoryStream data) + { + if (_incomingPacket == null) + { + _incomingPacket = data; + } + else + { + // Drop. + } } private Queue _fifo; @@ -253,6 +288,16 @@ namespace Contralto.IO private ulong _fifoTransmitDuration = 87075; // ~87000 nsec to transmit 16 words at 3mbit, assuming no collision private Event _fifoWakeupEvent; + // The actual connection to a real Ethernet device on the host + HostEthernet _hostEthernet; + + // Buffer to hold outgoing data to the host ethernet + ushort[] _outputData; + int _outputIndex; + + // Incoming data + MemoryStream _incomingPacket; + private AltoSystem _system; } } diff --git a/Contralto/IO/HostEthernet.cs b/Contralto/IO/HostEthernet.cs new file mode 100644 index 0000000..d139fe7 --- /dev/null +++ b/Contralto/IO/HostEthernet.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using PcapDotNet.Base; +using PcapDotNet.Core; +using PcapDotNet.Core.Extensions; +using PcapDotNet.Packets; +using PcapDotNet.Packets.Ethernet; +using System.IO; + +namespace Contralto.IO +{ + + public struct EthernetInterface + { + public EthernetInterface(string name, string description, MacAddress macAddress) + { + Name = name; + Description = description; + MacAddress = macAddress; + } + + public static List EnumerateDevices() + { + List interfaces = new List(); + + foreach (LivePacketDevice device in LivePacketDevice.AllLocalMachine) + { + interfaces.Add(new EthernetInterface(device.Name, device.Description, device.GetMacAddress())); + } + + return interfaces; + } + + public string Name; + public string Description; + public MacAddress MacAddress; + } + + public delegate void ReceivePacketDelegate(MemoryStream data); + + /// + /// Implements the logic for encapsulating a 3mbit ethernet packet into a 10mb packet and sending it over an actual + /// interface controlled by the host operating system. + /// + /// This uses PCap.NET to do the dirty work. + /// + public class HostEthernet + { + public HostEthernet(EthernetInterface iface) + { + AttachInterface(iface); + } + + public void RegisterReceiveCallback(ReceivePacketDelegate callback) + { + _callback = callback; + + // Now that we have a callback we can start receiving stuff. + Open(false /* not promiscuous */, int.MaxValue); + BeginReceive(); + } + + + /// + /// Sends an array of bytes over the ethernet as a 3mbit packet encapsulated in a 10mbit packet. + /// + /// + /// + public void Send(ushort[] packet, int length) + { + // Sanity check. + if (length < 1) + { + throw new InvalidOperationException("Raw packet data must contain at least two bytes for addressing."); + } + + // + // 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]; + } + + // + // Grab the source and destination host addresses from the packet we're sending + // and build 10mbit versions. + // + byte destinationHost = packetBytes[0]; + byte sourceHost = packetBytes[1]; + + MacAddress destinationMac = new MacAddress((UInt48)(_10mbitMACPrefix | destinationHost)); + MacAddress sourceMac = new MacAddress((UInt48)(_10mbitMACPrefix | sourceHost)); + + // Build the outgoing packet; place the source/dest addresses, type field and the raw data. + EthernetLayer ethernetLayer = new EthernetLayer + { + Source = sourceMac, + Destination = destinationMac, + EtherType = (EthernetType)_3mbitFrameType, + }; + + PayloadLayer payloadLayer = new PayloadLayer + { + Data = new Datagram(packetBytes), + }; + + PacketBuilder builder = new PacketBuilder(ethernetLayer, payloadLayer); + + // Send it over the 'net! + _communicator.SendPacket(builder.Build(DateTime.Now)); + } + + private void ReceiveCallback(Packet p) + { + // + // Filter out packets intended for the emulator, forward them on, drop everything else. + // + if ((int)p.Ethernet.EtherType == _3mbitFrameType && + (p.Ethernet.Destination.ToValue() & 0xffffffffff00) == _10mbitMACPrefix ) + { + _callback(p.Ethernet.Payload.ToMemoryStream()); + } + else + { + // Not for us, discard the packet. + } + } + + private void AttachInterface(EthernetInterface iface) + { + _interface = null; + + // Find the specified device by name + foreach (LivePacketDevice device in LivePacketDevice.AllLocalMachine) + { + if (device.Name == iface.Name && device.GetMacAddress() == iface.MacAddress) + { + _interface = device; + break; + } + } + + if (_interface == null) + { + throw new InvalidOperationException("Requested interface not found."); + } + } + + private void Open(bool promiscuous, int timeout) + { + _communicator = _interface.Open(0xffff, promiscuous ? PacketDeviceOpenAttributes.Promiscuous : PacketDeviceOpenAttributes.None, timeout); + } + + /// + /// Begin receiving packets, forever. + /// + private void BeginReceive() + { + _communicator.ReceivePackets(-1, ReceiveCallback); + } + + private LivePacketDevice _interface; + private PacketCommunicator _communicator; + private ReceivePacketDelegate _callback; + + private const int _3mbitFrameType = 0xbeef; // easy to identify, ostensibly unused by anything of any import + + /// + /// On output, these bytes are prepended to the Alto's 3mbit (1 byte) address to form a full + /// 6 byte Ethernet MAC. + /// On input, ethernet frames are checked for this prefix + /// + private UInt48 _10mbitMACPrefix = 0x0000aa010200; // 00-00-AA is the old Xerox vendor code, used just to be cute. + } +} diff --git a/Contralto/Memory/MemoryBus.cs b/Contralto/Memory/MemoryBus.cs index 39e1c6f..f1d4c5a 100644 --- a/Contralto/Memory/MemoryBus.cs +++ b/Contralto/Memory/MemoryBus.cs @@ -257,10 +257,11 @@ namespace Contralto.Memory case 3: // debug + /* if (_memoryAddress == 0xfc90 || _memoryAddress == 0xfc91 || _memoryAddress == 0x151) // 176220 -- status word for disk { Logging.Log.Write(Logging.LogComponent.Debug, "++> Task {0} wrote {1} to {3} (was {2}).", _task, Conversion.ToOctal(data), Conversion.ToOctal(DebugReadWord(_task, _memoryAddress)), Conversion.ToOctal(_memoryAddress)); - } + }*/ _memoryData = data; // Only really necessary to show in debugger // Start of doubleword write: