diff --git a/Contralto/AltoSystem.cs b/Contralto/AltoSystem.cs index 58d523d..4b39ea2 100644 --- a/Contralto/AltoSystem.cs +++ b/Contralto/AltoSystem.cs @@ -41,7 +41,7 @@ namespace Contralto _keyboard = new Keyboard(); _diskController = new DiskController(this); _displayController = new DisplayController(this); - _mouse = new Mouse(); + _mouseAndKeyset = new MouseAndKeyset(); _ethernetController = new EthernetController(this); _orbitController = new OrbitController(this); _audioDAC = new AudioDAC(this); @@ -53,7 +53,7 @@ namespace Contralto // Attach memory-mapped devices to the bus _memBus.AddDevice(_mem); _memBus.AddDevice(_keyboard); - _memBus.AddDevice(_mouse); + _memBus.AddDevice(_mouseAndKeyset); _memBus.AddDevice(_audioDAC); _memBus.AddDevice(_organKeyboard); @@ -71,7 +71,7 @@ namespace Contralto _diskController.Reset(); _displayController.Reset(); _keyboard.Reset(); - _mouse.Reset(); + _mouseAndKeyset.Reset(); _cpu.Reset(); _ethernetController.Reset(); _orbitController.Reset(); @@ -231,11 +231,11 @@ namespace Contralto if (newImage) { - newPack = InMemoryDiskPack.CreateEmpty(geometry, path); + newPack = FileBackedDiskPack.CreateEmpty(geometry, path); } else { - newPack = InMemoryDiskPack.Load(geometry, path); + newPack = FileBackedDiskPack.Load(geometry, path); } _tridentController.Drives[drive].LoadPack(newPack); @@ -301,9 +301,9 @@ namespace Contralto get { return _keyboard; } } - public Mouse Mouse + public MouseAndKeyset MouseAndKeyset { - get { return _mouse; } + get { return _mouseAndKeyset; } } public EthernetController EthernetController @@ -330,7 +330,7 @@ namespace Contralto private MemoryBus _memBus; private Memory.Memory _mem; private Keyboard _keyboard; - private Mouse _mouse; + private MouseAndKeyset _mouseAndKeyset; private DiskController _diskController; private DisplayController _displayController; private EthernetController _ethernetController; diff --git a/Contralto/CPU/Tasks/Task.cs b/Contralto/CPU/Tasks/Task.cs index cdde852..a809fd4 100644 --- a/Contralto/CPU/Tasks/Task.cs +++ b/Contralto/CPU/Tasks/Task.cs @@ -211,7 +211,7 @@ namespace Contralto.CPU case BusSource.ReadMouse: // "BUS[12-15]<-MOUSE; BUS[0-13]<- -1" // (Note -- BUS[0-13] appears to be a typo, and should likely be BUS[0-11]). - _busData = (ushort)(_cpu._system.Mouse.PollMouseBits() | 0xfff0); + _busData = (ushort)(_cpu._system.MouseAndKeyset.PollMouseBits() | 0xfff0); break; case BusSource.ReadDisp: diff --git a/Contralto/Contralto.csproj b/Contralto/Contralto.csproj index d02af50..b6fc8e0 100644 --- a/Contralto/Contralto.csproj +++ b/Contralto/Contralto.csproj @@ -206,7 +206,7 @@ - + diff --git a/Contralto/IO/HostEthernetEncapsulation.cs b/Contralto/IO/HostEthernetEncapsulation.cs index 6774621..493e7b0 100644 --- a/Contralto/IO/HostEthernetEncapsulation.cs +++ b/Contralto/IO/HostEthernetEncapsulation.cs @@ -196,19 +196,36 @@ namespace Contralto.IO // if (e.Packet.LinkLayerType == LinkLayers.Ethernet) { - EthernetPacket packet = (EthernetPacket)Packet.ParsePacket(LinkLayers.Ethernet, e.Packet.Data); - - UpdateSourceAddress(); - - if ((int)packet.Type == _3mbitFrameType && // encapsulated 3mbit frames - (!packet.SourceHwAddress.Equals(_10mbitSourceAddress))) // and not sent by this emulator + // + // We wrap this in a try/catch; on occasion Packet.ParsePacket fails due to a bug + // in the PacketDotNet library. + // + EthernetPacket packet = null; + try { - Log.Write(LogComponent.HostNetworkInterface, "Received encapsulated 3mbit packet."); - _callback(new System.IO.MemoryStream(packet.PayloadData)); + packet = (EthernetPacket)Packet.ParsePacket(LinkLayers.Ethernet, e.Packet.Data); } - else + catch (Exception ex) { - // Not for us, discard the packet. + // Just eat this, log a message. + Log.Write(LogType.Error, LogComponent.HostNetworkInterface, "Failed to parse 3mbit packet. Exception {0}", ex.Message); + packet = null; + } + + if (packet != null) + { + UpdateSourceAddress(); + + if ((int)packet.Type == _3mbitFrameType && // encapsulated 3mbit frames + (!packet.SourceHwAddress.Equals(_10mbitSourceAddress))) // and not sent by this emulator + { + Log.Write(LogComponent.HostNetworkInterface, "Received encapsulated 3mbit packet."); + _callback(new System.IO.MemoryStream(packet.PayloadData)); + } + else + { + // Not for us, discard the packet. + } } } } diff --git a/Contralto/IO/Mouse.cs b/Contralto/IO/MouseAndKeyset.cs similarity index 82% rename from Contralto/IO/Mouse.cs rename to Contralto/IO/MouseAndKeyset.cs index 0993fbc..cda100d 100644 --- a/Contralto/IO/Mouse.cs +++ b/Contralto/IO/MouseAndKeyset.cs @@ -30,13 +30,28 @@ namespace Contralto.IO Right = 0x2, Left = 0x4, } - - /// - /// Implements the hardware for the standard Alto mouse. - /// - public class Mouse : IMemoryMappedDevice + + [Flags] + public enum AltoKeysetKey { - public Mouse() + None = 0x00, + Keyset0 = 0x80, // left-most (bit 8) + Keyset1 = 0x40, + Keyset2 = 0x20, + Keyset3 = 0x10, + Keyset4 = 0x08, // right-most (bit 12) + } + + /// + /// Implements the hardware for the standard Alto mouse + /// and the Keyset, because both share the same memory-mapped + /// address. When the Diablo printer is finally emulated, + /// I'll have to revisit this scheme because it ALSO shares + /// the same address and that's just silly. + /// + public class MouseAndKeyset : IMemoryMappedDevice + { + public MouseAndKeyset() { _lock = new ReaderWriterLockSlim(); Reset(); @@ -44,12 +59,13 @@ namespace Contralto.IO public void Reset() { - + _keyset = 0; + _buttons = AltoMouseButton.None; } public ushort Read(int address, TaskType task, bool extendedMemoryReference) { - return (ushort)(~_buttons); + return (ushort)~((int)_buttons | (int)_keyset); } public void Load(int address, ushort data, TaskType task, bool extendedMemoryReference) @@ -81,6 +97,16 @@ namespace Contralto.IO _buttons ^= button; } + public void KeysetDown(AltoKeysetKey key) + { + _keyset |= key; + } + + public void KeysetUp(AltoKeysetKey key) + { + _keyset ^= key; + } + /// /// Gets the bits read by the "<-MOUSE" special function, and moves /// the pointer one step closer to its final destination (if it has moved at all). @@ -177,6 +203,9 @@ namespace Contralto.IO // Mouse buttons: AltoMouseButton _buttons; + // Keyset switches: + AltoKeysetKey _keyset; + /// /// Where the mouse is currently reported to be /// diff --git a/Contralto/IO/TridentController.cs b/Contralto/IO/TridentController.cs index 1007bc8..050a5c4 100644 --- a/Contralto/IO/TridentController.cs +++ b/Contralto/IO/TridentController.cs @@ -60,7 +60,6 @@ namespace Contralto.IO _seekIncomplete = false; _headOverflow = false; _deviceCheck = false; - _notSelected = false; _sectorOverflow = false; _outputLate = false; _inputLate = false; @@ -72,8 +71,9 @@ namespace Contralto.IO _sector = 0; _checkDone = false; - _empty = false; + _waitForEmpty = false; _pauseOutputProcessing = false; + _commandState = CommandState.Command; _readState = ReadState.Idle; _writeState = WriteState.Idle; @@ -96,7 +96,6 @@ namespace Contralto.IO _seekIncomplete = false; _headOverflow = false; _deviceCheck = false; - _notSelected = false; _sectorOverflow = false; _outputLate = false; _inputLate = false; @@ -104,11 +103,11 @@ namespace Contralto.IO _readOnly = false; _offset = false; + _commandState = CommandState.Command; _writeState = WriteState.Idle; _readState = ReadState.Idle; - _empty = false; - //_pauseOutputProcessing = false; + _waitForEmpty = false; ClearInputFIFO(); } @@ -128,7 +127,6 @@ namespace Contralto.IO System.Windows.Forms.MessageBox.Show(String.Format("Unable to save Trident disk {0}'s contents. Error {1}. Any changes have been lost.", drive, e.Message), "Disk save error"); } } - } public TridentDrive[] Drives @@ -148,21 +146,20 @@ namespace Contralto.IO _runEnable = false; _system.CPU.BlockTask(TaskType.TridentInput); _system.CPU.BlockTask(TaskType.TridentOutput); - - //ClearOutputFIFO(); - //ClearInputFIFO(); } if ((value & 0x20) != 0) { _runEnable = true; + // // "Issuing an SIO with bit 10 set will wake up the microcode // once and thus may report status in the absence of sector // pulses from the disk." // So do that now. - // Based on a reading of the microcode and schematics it looks like the + // Based on a reading of the microcode and schematics it looks like only the // Write (Output) task is woken up here. + // // // Clear error flags. @@ -171,20 +168,13 @@ namespace Contralto.IO _outputLate = false; _sectorOverflow = false; - // _empty = false; - // _pauseOutputProcessing = false; - // - // Clear the output FIFO + // Clear the FIFOs + // ClearOutputFIFO(); ClearInputFIFO(); - - //_system.CPU.WakeupTask(TaskType.TridentInput); + _system.CPU.WakeupTask(TaskType.TridentOutput); - - //_system.CPU.BlockTask(TaskType.TridentInput); - //_system.CPU.BlockTask(TaskType.TridentOutput); - } Log.Write(LogComponent.TridentController, "Trident STARTF {0}", Conversion.ToOctal(value)); @@ -207,7 +197,7 @@ namespace Contralto.IO set { - Log.Write(LogComponent.TridentController, "------> Trident KDTA queued {0}", Conversion.ToOctal(value)); + Log.Write(LogComponent.TridentController, "Trident KDTA queued {0}", Conversion.ToOctal(value)); WriteOutputFifo(value); } } @@ -248,7 +238,7 @@ namespace Contralto.IO (_seekIncomplete ? 0x8000 : 0) | (_headOverflow ? 0x4000 : 0) | (_deviceCheck ? 0x2000 : 0) | - (_notSelected ? 0x1000 : 0) | + (NotSelected() ? 0x1000 : 0) | (NotOnline() ? 0x0800 : 0) | (NotReady() ? 0x0400 : 0) | (_sectorOverflow ? 0x0200 : 0) | @@ -266,10 +256,10 @@ namespace Contralto.IO public void TagInstruction(ushort tag) { - Log.Write(LogComponent.TridentController, "------> Trident tag instruction queued {0}", Conversion.ToOctal(tag)); + Log.Write(LogComponent.TridentController, "Trident tag instruction {0} queued", Conversion.ToOctal(tag)); // - // Add tag to the output FIFO, with bit 16 set, identifying it as a tag. + // Add tag to the output FIFO with bit 16 set, identifying it as a tag. // WriteOutputFifo(0x10000 | tag); } @@ -280,31 +270,36 @@ namespace Contralto.IO if (_outputFifo.Count > 0) { _system.CPU.BlockTask(TaskType.TridentOutput); - _empty = true; + _waitForEmpty = true; Log.Write(LogComponent.TridentController, "Trident output task blocked until Output FIFO emptied."); } } private bool NotReady() { - return SelectedDrive.NotReady | - !SelectedDrive.IsLoaded | - _selectedDrive > 7; + return SelectedDrive.NotReady | // The drive is not ready (i.e. seeking) + !SelectedDrive.IsLoaded | // No pack is loaded + _selectedDrive > 7; // The drive doesn't exist } private bool NotOnline() { + // + // A drive is considered online if (a) it exists, and (b) it has a pack loaded. + // return _selectedDrive < 8 ? !SelectedDrive.IsLoaded : true; } + private bool NotSelected() + { + // + // Only drives 0-7 exist and are running. + // + return _selectedDrive > 7; + } + private void WriteOutputFifo(int value) { - if (_outputFifo.Count > 16) - { - Log.Write(LogType.Error, LogComponent.TridentController, "Output FIFO full, dropping word."); - return; - } - EnqueueOutputFIFO(value); // @@ -319,342 +314,375 @@ namespace Contralto.IO } private void OutputFifoCallback(ulong timeNsec, ulong skewNsec, object context) - { - ProcessCommandOutput(); - } - - private void ProcessCommandOutput() { - if (_outputFifo.Count == 0) + switch (_commandState) { - if (_writeState == WriteState.Writing) - { - // - // We have run out of data on a write. - // - _outputLate = true; - - Log.Write(LogComponent.TridentController, "Output FIFO underflowed."); - } - - return; - } - - if (_readState != ReadState.Reading) - { - int fifoWord = DequeueOutputFIFO(); - - if ((fifoWord & 0x10000) != 0) - { - // - // This is a Tag word; process it accordingly. - // - Log.Write(LogComponent.TridentController, "Tag word {0} pulled from Output FIFO.", Conversion.ToOctal((ushort)fifoWord)); - - // - // From the TRICON schematic (page 16): - // Bit 3 is the Enable bit, which enables selection of one of four commands - // to the drive, specified in bits 1 and 2: - // (and as usual, these are in the backwards Nova/Alto bit ordering scheme) - // Bits 1 and 2 decode to: - // 0 0 - Control - // 0 1 - Set Head - // 1 0 - Set Cylinder - // 1 1 - Set Drive - // - // Head, Cylinder, Drive are straightforward -- the lower bits of the tag - // word contain the data for the command. - // - // The Control bits are described in the Trident T300 Theory of Operations manual, - // Page 3-13 and are the lower 10 bits of the tag command: - // - // 0 - Strobe Late : Skews read data detection 4ns late for attempted read-error recovery. - // 1 - Strobe Early : Same as above, but early. - // 2 - Write : Turns on circuits to write data, - // 3 - Read : Turns on read circuits an resets Attention interrupts. - // 4 - Address Mark : Commands an address mark to be generated, if writing; or - // enables the address mark detector, if reading. - // 5 - Reset Head Register : Resets HAR to 0 - // 6 - Device Check Reset : Resets most types of Device Check errors unless an error - // condition is still present. - // 7 - Head Select : Tuns on the head-selection circuits. Head select must be active 5 - // or 15 microseconds before Write or Read is commanded, respectively. - // 8 - Rezero : Repositions the heads to cylinder 000, selects Head Address 0, and resets - // some types of Device Checks. - // 9 - Head Advance : Increases Head Address count by one. - // - // - // Bit 0 of the Tag word, if set, tells the controller to hold off Output FIFO processing - // until the next sector pulse. - // - if ((fifoWord & 0x8000) != 0) + case CommandState.Command: + + if (_outputFifo.Count > 0) { - _pauseOutputProcessing = true; - Log.Write(LogComponent.TridentController, "Output FIFO processing paused until next sector pulse."); + int fifoWord = DequeueOutputFIFO(); + + if ((fifoWord & 0x10000) != 0) + { + // + // This is a Tag word; process it accordingly. + // + Log.Write(LogComponent.TridentController, "Tag word {0} pulled from Output FIFO.", Conversion.ToOctal((ushort)fifoWord)); + ProcessTagWord(fifoWord); + } + else + { + // + // This is a data word, unexpected in this state. + // + Log.Write(LogType.Error, LogComponent.TridentController, "Unexepected Data word {0} in Command state.", Conversion.ToOctal((ushort)fifoWord)); + } + + // + // Schedule next FIFO wakeup if there are more words left to be processed. + // + RescheduleOutputEvent(); } - // See if the enable bit (3) is set, in which case this is a command to the drive + break; + + case CommandState.Read: // - if ((fifoWord & 0x1000) != 0) + // Once a Read operation has commenced, we do not process the output FIFO via this callback, instead we let + // the Input callback dequeue words as necessary to do Compare operations with incoming data. + // + if (_outputFifo.Count > 0 && _readState != ReadState.Reading) + { + int fifoWord = DequeueOutputFIFO(); + + if ((fifoWord & 0x10000) != 0) + { + // + // This is a Tag word, unexpected in this state. + // + Log.Write(LogComponent.TridentController, "Unexpected tag word {0} in Read state.", Conversion.ToOctal((ushort)fifoWord)); + } + else + { + switch (_readState) + { + case ReadState.Initialized: + _readWordCount = fifoWord - 1; + _readState = ReadState.Reading; + + Log.Write(LogType.Verbose, LogComponent.TridentController, "Read initializing {0} words (max) to read.", _readWordCount); + break; + + default: + throw new InvalidOperationException( + String.Format("Unexpected ReadState {0} in OutputFIFOCallback.", _readState)); + + } + } + + // + // Schedule next FIFO wakeup if there are more words left to be processed. + // + RescheduleOutputEvent(); + } + + break; + + case CommandState.Write: + if (_outputFifo.Count == 0) { // - // Switch on the specific command - switch ((fifoWord & 0x6000) >> 13) - { - case 0: // Control - Log.Write(LogComponent.TridentController, "Control word."); + // We have run out of data on a write. + // + _outputLate = true; - if ((fifoWord & 0x0001) != 0) // 9 - Head advance - { - if (!SelectedDrive.IsLoaded) - { - _deviceCheck = true; - } - else - { - if (SelectedDrive.Head + 1 >= SelectedDrive.Pack.Geometry.Heads) - { - _headOverflow = true; - _deviceCheck = true; - - Log.Write(LogComponent.TridentController, "Head {0} is out of range on Head Advance.", SelectedDrive.Head + 1); - } - else - { - SelectedDrive.Head++; - Log.Write(LogComponent.TridentController, "Control: Head Advance. Head is now {0}", SelectedDrive.Head); - } - } - } - - if ((fifoWord & 0x0002) != 0) // 8 - Rezero - { - _deviceCheck = false; - SelectedDrive.Head = 0; - - InitSeek(0); - - Log.Write(LogComponent.TridentController, "Control: Rezero."); - } - - if ((fifoWord & 0x0004) != 0) // 7 - Head select - { - Log.Write(LogComponent.TridentController, "Control: Head Select."); - - if (!SelectedDrive.IsLoaded) - { - _deviceCheck = true; - } - - // TODO: technically this needs to be active before a write or read is selected. Do I care? - } - - if ((fifoWord & 0x0008) != 0) // 6 - Device Check Reset - { - Log.Write(LogComponent.TridentController, "Control: Device Check Reset."); - _deviceCheck = false; - } - - if ((fifoWord & 0x0010) != 0) // 5 - Reset Head Register - { - Log.Write(LogComponent.TridentController, "Control: Reset Head Register."); - SelectedDrive.Head = 0; - } - - if ((fifoWord & 0x0020) != 0) // 4 - Address Mark - { - Log.Write(LogComponent.TridentController, "Control: Address mark."); - - // Not much to do here, emulation-wise. - } - - if ((fifoWord & 0x0040) != 0) // 3 - Read - { - Log.Write(LogComponent.TridentController, "Control: Read."); - // - // Commence reading -- start reading a word at a time into the input FIFO, - // Waking up the Input task as necessary. - // - if (NotReady()) - { - _deviceCheck = true; - } - else - { - InitRead(); - } - } - - if ((fifoWord & 0x0080) != 0) // 2 - Write - { - Log.Write(LogComponent.TridentController, "Control: Write."); - - // - // Commence writing -- start pulling a word at a time out of the output FIFO, - // Waking up the Output task as necessary. - // - if (NotReady()) - { - _deviceCheck = true; - } - else - { - InitWrite(); - } - } - - if ((fifoWord & 0x0100) != 0) // 1 - Strobe Early - { - Log.Write(LogComponent.TridentController, "Control: Strobe Early."); - - // Not going to emulate this, as fun as it sounds. - } - - if ((fifoWord & 0x0200) != 0) // 0 - Strobe Late - { - Log.Write(LogComponent.TridentController, "Control: Strobe Late."); - - // Not going to emulate this either. - } - - break; - - case 1: // Set Head - int head = fifoWord & 0x1f; // low 5 bits - Log.Write(LogComponent.TridentController, "Command is Set Head {0}", head); - - if (!SelectedDrive.IsLoaded) - { - _deviceCheck = true; - } - else - { - if (head >= SelectedDrive.Pack.Geometry.Heads) - { - _headOverflow = true; - _deviceCheck = true; - - Log.Write(LogComponent.TridentController, "Head {0} is out of range.", head); - } - else - { - SelectedDrive.Head = head; - } - } - break; - - case 2: // Set Cylinder - int cyl = fifoWord & 0x3ff; // low 10 bits - Log.Write(LogComponent.TridentController, "Command is Set Cylinder {0}.", cyl); - - if (NotReady()) - { - _deviceCheck = true; - } - else - { - InitSeek(cyl); - } - break; - - case 3: // Set Drive - // We take all four drive-select bits even though only 8 drives are actually supported. - // The high bit is used by many trident utilities to select an invalid drive to test for - // the presence of the 8-drive multiplexer. - _selectedDrive = fifoWord & 0xf; - - if (_selectedDrive > 7) - { - _system.CPU.BlockTask(TaskType.TridentOutput); - _notSelected = true; - } - else - { - _notSelected = false; - } - - Log.Write(LogComponent.TridentController, "Command is Set Drive {0}", _selectedDrive); - break; - } + Log.Write(LogComponent.TridentController, "Output FIFO underflowed."); } else { + int fifoWord = DequeueOutputFIFO(); + + if ((fifoWord & 0x10000) != 0) + { + // + // This is a Tag word, unexpected in this state. + // + Log.Write(LogComponent.TridentController, "Unexpected tag word {0} in Write state.", Conversion.ToOctal((ushort)fifoWord)); + } + else + { + switch (_writeState) + { + case WriteState.Initialized: + // This word should be the MAX number of words to check. + _writeWordCount = fifoWord - 1; + _writeState = WriteState.WaitingForStartBit; + Log.Write(LogComponent.TridentController, "Write initializing {0} words (max) to write.", _writeWordCount); + break; + + case WriteState.WaitingForStartBit: + // See if we got the "1" bit indicating the start of the output data to be written to disk. + if (fifoWord == 1) + { + Log.Write(LogComponent.TridentController, "Start bit recognized, commencing write operation."); + _writeState = WriteState.Writing; + } + break; + + case WriteState.Writing: + ProcessDiskWrite(fifoWord); + break; + + default: + throw new InvalidOperationException( + String.Format("Unexpected WriteState {0} in OutputFIFOCallback.", _writeState)); + } + } + // - // There are two values we expect to see with the enable bit off: - // 0 is sent to reset the tag bus (to the drive) to zeros, we simply ignore it. - // 2 is sent because the drive expects the Rezero signal to be held for a time - // after the Control signal is dropped. We will commence a Rezero operation - // if we get one here. + // Schedule next FIFO wakeup if there are more words left to be processed. // - if (fifoWord == 2) - { - Log.Write(LogComponent.TridentController, "Rezero started."); - } - else if (fifoWord != 0) - { - Log.Write(LogComponent.TridentController, "Unexpected Tag word without enable bit set: {0}", Conversion.ToOctal(fifoWord)); - } + RescheduleOutputEvent(); + } - } - else - { - // - // This is a data word. - // - Log.Write(LogComponent.TridentController, "Data word {0} pulled from Output FIFO.", Conversion.ToOctal((ushort)fifoWord)); - - if (_writeState != WriteState.Idle) - { - switch (_writeState) - { - case WriteState.Initialized: - // This word should be the MAX number of words to check. - _writeWordCount = fifoWord - 1; - _writeState = WriteState.WaitingForStartBit; - Log.Write(LogComponent.TridentController, "Write initializing {0} words (max) to write.", _writeWordCount); - break; - - case WriteState.WaitingForStartBit: - if (fifoWord == 1) - { - Log.Write(LogComponent.TridentController, "Start bit recognized, commencing write operation."); - _writeState = WriteState.Writing; - } - break; - - case WriteState.Writing: - ProcessDiskWrite(fifoWord); - break; - } - } - - if (_readState != ReadState.Idle) - { - switch (_readState) - { - case ReadState.Initialized: - _readWordCount = fifoWord - 1; - _readState = ReadState.Reading; - - Log.Write(LogType.Verbose, LogComponent.TridentController, "Read initializing {0} words (max) to read.", _readWordCount); - break; - - default: - throw new InvalidOperationException("Unexpected ReadState in ProcessCommandOutput."); - - } - } - } + break; } + } + private void RescheduleOutputEvent() + { if (!_pauseOutputProcessing && _outputFifo.Count > 0) { - // - // Schedule next FIFO wakeup. - _outputFifoEvent.TimestampNsec = _outputFifoDuration; + _outputFifoEvent.TimestampNsec = _writeState == WriteState.Writing ? _writeWordDuration : _outputFifoDuration; _system.Scheduler.Schedule(_outputFifoEvent); } } + private void ProcessTagWord(int tagWord) + { + // + // From the TRICON schematic (page 16): + // Bit 3 is the Enable bit, which enables selection of one of four commands + // to the drive, specified in bits 1 and 2: + // (and as usual, these are in the backwards Nova/Alto bit ordering scheme) + // Bits 1 and 2 decode to: + // 0 0 - Control + // 0 1 - Set Head + // 1 0 - Set Cylinder + // 1 1 - Set Drive + // + // Head, Cylinder, Drive are straightforward -- the lower bits of the tag + // word contain the data for the command. + // + // The Control bits are described in the Trident T300 Theory of Operations manual, + // Page 3-13 and are the lower 10 bits of the tag command: + // + // 0 - Strobe Late : Skews read data detection 4ns late for attempted read-error recovery. + // 1 - Strobe Early : Same as above, but early. + // 2 - Write : Turns on circuits to write data, + // 3 - Read : Turns on read circuits and resets Attention interrupts. + // 4 - Address Mark : Commands an address mark to be generated, if writing; or + // enables the address mark detector, if reading. + // 5 - Reset Head Register : Resets HAR to 0 + // 6 - Device Check Reset : Resets most types of Device Check errors unless an error + // condition is still present. + // 7 - Head Select : Tuns on the head-selection circuits. Head select must be active 5 + // or 15 microseconds before Write or Read is commanded, respectively. + // 8 - Rezero : Repositions the heads to cylinder 000, selects Head Address 0, and resets + // some types of Device Checks. + // 9 - Head Advance : Increases Head Address count by one. + // + // + // Bit 0 of the Tag word, if set, tells the controller to hold off Output FIFO processing + // until the next sector pulse. + // + if ((tagWord & 0x8000) != 0) + { + _pauseOutputProcessing = true; + Log.Write(LogComponent.TridentController, "Output FIFO processing paused until next sector pulse."); + } + + // + // See if the enable bit (3) is set, in which case this is a command to the drive. + // + if ((tagWord & 0x1000) != 0) + { + // + // Switch on the specific command + switch ((TagCommand)((tagWord & 0x6000) >> 13)) + { + case TagCommand.Control: + Log.Write(LogComponent.TridentController, "Control word."); + ControlFlags control = (ControlFlags)tagWord; + + if ((control & ControlFlags.HeadAdvance) != 0) + { + if (!SelectedDrive.IsLoaded) + { + _deviceCheck = true; + } + else + { + if (SelectedDrive.Head + 1 >= SelectedDrive.Pack.Geometry.Heads) + { + _headOverflow = true; + _deviceCheck = true; + + Log.Write(LogComponent.TridentController, "Head {0} is out of range on Head Advance.", SelectedDrive.Head + 1); + } + else + { + SelectedDrive.Head++; + Log.Write(LogComponent.TridentController, "Control: Head Advance. Head is now {0}", SelectedDrive.Head); + } + } + } + + if ((control & ControlFlags.Rezero) != 0) + { + _deviceCheck = false; + SelectedDrive.Head = 0; + + InitSeek(0); + + Log.Write(LogComponent.TridentController, "Control: Rezero."); + } + + if ((control & ControlFlags.HeadSelect) != 0) + { + Log.Write(LogComponent.TridentController, "Control: Head Select."); + + if (!SelectedDrive.IsLoaded) + { + _deviceCheck = true; + } + + // TODO: technically this needs to be active before a write or read is selected. Do I care? + } + + if ((control & ControlFlags.DeviceCheckReset) != 0) + { + Log.Write(LogComponent.TridentController, "Control: Device Check Reset."); + _deviceCheck = false; + } + + if ((control & ControlFlags.ResetHeadRegister) != 0) + { + Log.Write(LogComponent.TridentController, "Control: Reset Head Register."); + SelectedDrive.Head = 0; + } + + if ((control & ControlFlags.AddressMark) != 0) + { + Log.Write(LogComponent.TridentController, "Control: Address mark."); + + // Not much to do here, emulation-wise. + } + + if ((control & ControlFlags.Read) != 0) + { + Log.Write(LogComponent.TridentController, "Control: Read."); + // + // Commence reading -- start reading a word at a time into the input FIFO, + // Waking up the Input task as necessary. + // + if (NotReady()) + { + _deviceCheck = true; + } + else + { + InitRead(); + } + } + + if ((control & ControlFlags.Write) != 0) + { + Log.Write(LogComponent.TridentController, "Control: Write."); + + // + // Commence writing -- start pulling a word at a time out of the output FIFO, + // Waking up the Output task as necessary. + // + if (NotReady()) + { + _deviceCheck = true; + } + else + { + InitWrite(); + } + } + + if ((control & ControlFlags.StrobeEarly) != 0) + { + Log.Write(LogComponent.TridentController, "Control: Strobe Early."); + + // Not going to emulate this, as fun as it sounds. + } + + if ((control & ControlFlags.StrobeLate) != 0) + { + Log.Write(LogComponent.TridentController, "Control: Strobe Late."); + + // Not going to emulate this either. + } + + break; + + case TagCommand.SetHead: + int head = tagWord & 0x1f; // low 5 bits + Log.Write(LogComponent.TridentController, "Command is Set Head {0}", head); + + if (!SelectedDrive.IsLoaded) + { + _deviceCheck = true; + } + else + { + if (head >= SelectedDrive.Pack.Geometry.Heads) + { + _headOverflow = true; + _deviceCheck = true; + + Log.Write(LogComponent.TridentController, "Head {0} is out of range.", head); + } + else + { + SelectedDrive.Head = head; + } + } + break; + + case TagCommand.SetCylinder: + int cyl = tagWord & 0x3ff; // low 10 bits + Log.Write(LogComponent.TridentController, "Command is Set Cylinder {0}.", cyl); + + if (NotReady()) + { + _deviceCheck = true; + } + else + { + InitSeek(cyl); + } + break; + + case TagCommand.SetDrive: + // + // We take all four drive-select bits even though only 8 drives are actually supported. + // The high bit is used by many trident utilities to select an invalid drive to test for + // the presence of the 8-drive multiplexer. + // (In the absence of the multiplexer, selecting any drive selects drive 0.) + // + _selectedDrive = tagWord & 0xf; + + Log.Write(LogComponent.TridentController, "Command is Set Drive {0}", _selectedDrive); + break; + } + } + } + private void InitSeek(int destCylinder) { _deviceCheck = !SelectedDrive.Seek(destCylinder); @@ -665,20 +693,18 @@ namespace Contralto.IO if (_readState == ReadState.Idle) { _readState = ReadState.Initialized; + _commandState = CommandState.Read; _checkedWordCount = 0; _readIndex = 0; _readWordCount = 0; _checkDone = false; - // Clear the FIFO: TODO check schematics. - // ClearInputFIFO(); - - _readWordEvent.TimestampNsec = _readWordDuration * 10; + _readWordEvent.TimestampNsec = _readWordDuration; _system.Scheduler.Schedule(_readWordEvent); } else { - // Unexpected, throw for now. + // Unexpected behavior. throw new InvalidOperationException("Unexpected Read command while read active."); } } @@ -768,10 +794,16 @@ namespace Contralto.IO { Log.Write(LogComponent.TridentController, "CHS {0}/{1}/{2} {3} read from drive {4} complete.", SelectedDrive.Cylinder, SelectedDrive.Head, _sector, _sectorBlock, _selectedDrive); _readState = ReadState.Idle; + _commandState = CommandState.Command; _sectorBlock++; + + // + // Kick the output event if necessary. + // + RescheduleOutputEvent(); } - } + } private void InitWrite() { @@ -779,6 +811,7 @@ namespace Contralto.IO { Log.Write(LogComponent.TridentController, "Write primed, waiting for start bit."); _writeState = WriteState.Initialized; + _commandState = CommandState.Write; _writeIndex = 0; } else @@ -822,13 +855,14 @@ namespace Contralto.IO throw new InvalidOperationException(String.Format("Unexpected Sector Block of {0} on write.", _sectorBlock)); } - _writeIndex++; + _writeIndex++; _writeWordCount--; if (_writeWordCount <= 0) { - Console.WriteLine( "CHS {0}/{1}/{2} {3} write to drive {4} complete. {5} words written.", SelectedDrive.Cylinder, SelectedDrive.Head, _sector, _sectorBlock, _selectedDrive, _writeIndex); + Log.Write(LogType.Verbose, LogComponent.TridentController, "CHS {0}/{1}/{2} {3} write to drive {4} complete. {5} words written.", SelectedDrive.Cylinder, SelectedDrive.Head, _sector, _sectorBlock, _selectedDrive, _writeIndex); _writeState = WriteState.Idle; + _commandState = CommandState.Command; _writeIndex = 0; // Move to the next block @@ -838,7 +872,8 @@ namespace Contralto.IO private void SectorCallback(ulong timeNsec, ulong skewNsec, object context) { - // Move to the next sector. + // Move to the next sector if the controller is running + // and the disk is ready. if (_runEnable && !NotReady()) { _sector = (_sector + 1) % 9; @@ -847,7 +882,7 @@ namespace Contralto.IO // Reset to the first block (header) in the sector. _sectorBlock = SectorBlock.Header; - // Wake up the Output task + // Wake up the Output task _system.CPU.WakeupTask(TaskType.TridentOutput); } else @@ -857,7 +892,7 @@ namespace Contralto.IO } // - // Schedule the next sector pulse + // Schedule the next (potential) sector pulse // _sectorEvent.TimestampNsec = _sectorDuration - skewNsec; _system.Scheduler.Schedule(_sectorEvent); @@ -984,12 +1019,12 @@ namespace Contralto.IO Log.Write(LogComponent.TridentController, "Output FIFO dequeued, queue depth is now {0}", _outputFifo.Count); } - if (_empty) + if (_waitForEmpty) { // Only wake up the Output task when the output FIFO goes completely empty. if (_outputFifo.Count == 0) { - _empty = false; + _waitForEmpty = false; _system.CPU.WakeupTask(TaskType.TridentOutput); Log.Write(LogComponent.TridentController, "Output FIFO emptied, waking Output task."); } @@ -1019,7 +1054,6 @@ namespace Contralto.IO private bool _seekIncomplete; private bool _headOverflow; private bool _deviceCheck; - private bool _notSelected; private bool _sectorOverflow; private bool _outputLate; private bool _inputLate; @@ -1033,15 +1067,24 @@ namespace Contralto.IO /// /// Drive timings /// - private static ulong _sectorDuration = (ulong)((16.66666 / 9.0) * Conversion.MsecToNsec); // time in nsec for one sector -- 9 sectors, 16.66ms per rotation - private Event _sectorEvent; + private static ulong _sectorDuration = (ulong)((16.67 / 9.0) * Conversion.MsecToNsec); // time in nsec for one sector -- 9 sectors, 16.66ms per rotation + private Event _sectorEvent; + // + // Command state + // + private CommandState _commandState; + + // // Output FIFO. This is actually 17-bits wide (the 17th bit is the Tag bit). + // private Queue _outputFifo; private Event _outputFifoEvent; private bool _pauseOutputProcessing; - private bool _empty; - private static ulong _outputFifoDuration = (ulong)(_sectorDuration / 2048.0); // time in nsec for one word -- 1120 words per sector. + private bool _waitForEmpty; + private static ulong _outputFifoDuration = (ulong)(_sectorDuration / 2240.0); // time to pull one word from fifo during command processing -- this is made up at the moment. + private static ulong _writeWordDuration = (ulong)(_sectorDuration / 2240.0); // time in nsec for one word time on disk -- 1120 words per sector. + // TODO: when a write isn't in process, how is the output fifo clocked? // Input FIFO. private Queue _inputFifo; @@ -1049,20 +1092,49 @@ namespace Contralto.IO // // Read timings // - private static ulong _readWordDuration = (ulong)(_sectorDuration / 1120.0); // time in nsec for one word -- 1120 words per sector. + private static ulong _readWordDuration = (ulong)(_sectorDuration / 1120.0); // time in nsec for one word time on disk -- 1120 words per sector. private Event _readWordEvent; private int _readIndex; - private int _readWordCount; + private int _readWordCount; private bool _checkDone; private int _checkedWordCount; // - // Write timings + // Write data // - private static ulong _writeWordDuration = (ulong)(_sectorDuration / 1120.0); // time in nsec for one word -- 1120 words per sector. private int _writeIndex; private int _writeWordCount; + private enum TagCommand + { + Control = 0, + SetHead = 1, + SetCylinder = 2, + SetDrive = 3, + } + + [Flags] + private enum ControlFlags + { + HeadAdvance = 0x001, // 9 + Rezero = 0x002, + HeadSelect = 0x004, + DeviceCheckReset = 0x008, + ResetHeadRegister = 0x010, + AddressMark = 0x020, + Read = 0x040, + Write = 0x080, + StrobeEarly = 0x100, + StrobeLate = 0x200, // 0 + } + + private enum CommandState + { + Command, + Read, + Write + } + private enum WriteState { Idle, @@ -1076,7 +1148,6 @@ namespace Contralto.IO { Idle, Initialized, - WaitingForStartBit, Reading, } private ReadState _readState; diff --git a/Contralto/Properties/AssemblyInfo.cs b/Contralto/Properties/AssemblyInfo.cs index bcb06e9..95db7f9 100644 --- a/Contralto/Properties/AssemblyInfo.cs +++ b/Contralto/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.1")] -[assembly: AssemblyFileVersion("1.2.1.0")] +[assembly: AssemblyVersion("1.2.2")] +[assembly: AssemblyFileVersion("1.2.2.0")] diff --git a/Contralto/SdlUI/SdlAltoWindow.cs b/Contralto/SdlUI/SdlAltoWindow.cs index cb9edb7..6017f28 100644 --- a/Contralto/SdlUI/SdlAltoWindow.cs +++ b/Contralto/SdlUI/SdlAltoWindow.cs @@ -313,7 +313,7 @@ namespace Contralto.SdlUI if (dx != 0 || dy != 0) { - _system.Mouse.MouseMove(dx, dy); + _system.MouseAndKeyset.MouseMove(dx, dy); // Don't handle the very next Mouse Move event (which will just be the motion we caused in the // below line...) @@ -337,6 +337,11 @@ namespace Contralto.SdlUI { _system.Keyboard.KeyDown(_keyMap[code]); } + + if (_keysetMap.ContainsKey(code)) + { + _system.MouseAndKeyset.KeysetDown(_keysetMap[code]); + } } private void KeyUp(SDL.SDL_Keycode code) @@ -359,6 +364,11 @@ namespace Contralto.SdlUI { _system.Keyboard.KeyUp(_keyMap[code]); } + + if (_keysetMap.ContainsKey(code)) + { + _system.MouseAndKeyset.KeysetUp(_keysetMap[code]); + } } private void MouseDown(byte button, int x, int y) @@ -383,7 +393,7 @@ namespace Contralto.SdlUI if (altoButton != AltoMouseButton.None) { - _system.Mouse.MouseDown(altoButton); + _system.MouseAndKeyset.MouseDown(altoButton); } } @@ -398,7 +408,7 @@ namespace Contralto.SdlUI if (altoButton != AltoMouseButton.None) { - _system.Mouse.MouseUp(altoButton); + _system.MouseAndKeyset.MouseUp(altoButton); } } @@ -559,6 +569,14 @@ namespace Contralto.SdlUI _keyMap.Add(SDL.SDL_Keycode.SDLK_LEFTBRACKET, AltoKey.LBracket); _keyMap.Add(SDL.SDL_Keycode.SDLK_RIGHTBRACKET, AltoKey.RBracket); _keyMap.Add(SDL.SDL_Keycode.SDLK_DOWN, AltoKey.LF); + + _keysetMap = new Dictionary(); + // Map the 5 keyset keys to F5-F9. + _keysetMap.Add(SDL.SDL_Keycode.SDLK_F5, AltoKeysetKey.Keyset0); + _keysetMap.Add(SDL.SDL_Keycode.SDLK_F6, AltoKeysetKey.Keyset1); + _keysetMap.Add(SDL.SDL_Keycode.SDLK_F7, AltoKeysetKey.Keyset2); + _keysetMap.Add(SDL.SDL_Keycode.SDLK_F8, AltoKeysetKey.Keyset3); + _keysetMap.Add(SDL.SDL_Keycode.SDLK_F9, AltoKeysetKey.Keyset4); } private static AltoMouseButton GetMouseButton(byte button) @@ -620,6 +638,11 @@ namespace Contralto.SdlUI // private Dictionary _keyMap; + // + // Keymap data + // + private Dictionary _keysetMap; + // // Mouse data // diff --git a/Contralto/SdlUI/SdlConsole.cs b/Contralto/SdlUI/SdlConsole.cs index cdd1f2b..c67a408 100644 --- a/Contralto/SdlUI/SdlConsole.cs +++ b/Contralto/SdlUI/SdlConsole.cs @@ -209,6 +209,21 @@ namespace Contralto.SdlUI return CommandResult.Normal; } + [DebuggerFunction("new disk", "Creates and loads a new image for the specified drive.", "")] + private CommandResult NewDisk(ushort drive, string path) + { + if (drive > 1) + { + throw new InvalidOperationException("Drive specification out of range."); + } + + // Unload the current pack. + _system.LoadDiabloDrive(drive, path, true); + Console.WriteLine("Drive {0} created and loaded.", drive); + + return CommandResult.Normal; + } + [DebuggerFunction("show disk", "Displays the contents of the specified drive.", "")] private CommandResult ShowDisk(ushort drive) { @@ -262,6 +277,21 @@ namespace Contralto.SdlUI return CommandResult.Normal; } + [DebuggerFunction("new trident", "Creates and loads a new image for the specified drive.", "")] + private CommandResult NewTrident(ushort drive, string path) + { + if (drive > 7) + { + throw new InvalidOperationException("Drive specification out of range."); + } + + // Unload the current pack. + _system.LoadTridentDrive(drive, path, true); + Console.WriteLine("Trident {0} created and loaded.", drive); + + return CommandResult.Normal; + } + [DebuggerFunction("show trident", "Displays the contents of the specified trident drive.", "")] private CommandResult ShowTrident(ushort drive) { diff --git a/Contralto/UI/AltoWindow.cs b/Contralto/UI/AltoWindow.cs index 4459231..c1f8e5e 100644 --- a/Contralto/UI/AltoWindow.cs +++ b/Contralto/UI/AltoWindow.cs @@ -734,6 +734,11 @@ namespace Contralto { _system.Keyboard.KeyDown(_keyMap[e.KeyCode]); } + + if (_keysetMap.ContainsKey(e.KeyCode)) + { + _system.MouseAndKeyset.KeysetDown(_keysetMap[e.KeyCode]); + } if (e.Alt) { @@ -753,8 +758,13 @@ namespace Contralto if (_keyMap.ContainsKey(e.KeyCode)) { _system.Keyboard.KeyUp(_keyMap[e.KeyCode]); - } - + } + + if (_keysetMap.ContainsKey(e.KeyCode)) + { + _system.MouseAndKeyset.KeysetUp(_keysetMap[e.KeyCode]); + } + if (e.Alt) { ReleaseMouse(); @@ -789,7 +799,7 @@ namespace Contralto if (dx != 0 || dy != 0) { - _system.Mouse.MouseMove(dx, dy); + _system.MouseAndKeyset.MouseMove(dx, dy); // Don't handle the very next Mouse Move event (which will just be the motion we caused in the // below line...) @@ -830,7 +840,7 @@ namespace Contralto break; } - _system.Mouse.MouseDown(button); + _system.MouseAndKeyset.MouseDown(button); } @@ -863,7 +873,7 @@ namespace Contralto break; } - _system.Mouse.MouseUp(button); + _system.MouseAndKeyset.MouseUp(button); } private void CaptureMouse() @@ -873,7 +883,7 @@ namespace Contralto // - Keep the mouse within our window bounds (so it doesn't escape our window, hence "capture"). // - Put some text in the Status area telling people how to leave... _mouseCaptured = true; - ShowCursor(false); + ShowCursor(false); CaptureStatusLabel.Text = "Alto Mouse/Keyboard captured. Press Alt to release."; } @@ -881,7 +891,7 @@ namespace Contralto private void ReleaseMouse() { _mouseCaptured = false; - ShowCursor(true); + ShowCursor(true); CaptureStatusLabel.Text = "Click on display to capture Alto Mouse/Keyboard."; } @@ -1079,7 +1089,16 @@ namespace Contralto _keyMap.Add(Keys.OemSemicolon, AltoKey.Semicolon); _keyMap.Add(Keys.OemOpenBrackets, AltoKey.LBracket); _keyMap.Add(Keys.OemCloseBrackets, AltoKey.RBracket); - _keyMap.Add(Keys.Down, AltoKey.LF); + _keyMap.Add(Keys.Down, AltoKey.LF); + + _keysetMap = new Dictionary(); + + // Map the 5 keyset keys to F5-F9. + _keysetMap.Add(Keys.F5, AltoKeysetKey.Keyset0); + _keysetMap.Add(Keys.F6, AltoKeysetKey.Keyset1); + _keysetMap.Add(Keys.F7, AltoKeysetKey.Keyset2); + _keysetMap.Add(Keys.F8, AltoKeysetKey.Keyset3); + _keysetMap.Add(Keys.F9, AltoKeysetKey.Keyset4); } private ImageCodecInfo GetEncoderForFormat(ImageFormat format) @@ -1158,6 +1177,9 @@ namespace Contralto // Keyboard mapping from windows vkeys to Alto keys private Dictionary _keyMap; + // Keyset mapping from windows vkeys to Keyset keys + private Dictionary _keysetMap; + // Mouse capture state private bool _mouseCaptured; private bool _currentCursorState; diff --git a/Contralto/readme-mono.txt b/Contralto/readme-mono.txt index 4a62f6d..fc86c0f 100644 --- a/Contralto/readme-mono.txt +++ b/Contralto/readme-mono.txt @@ -19,15 +19,18 @@ ContrAlto currently emulates the following Alto hardware: - Two Diablo Model 31 or 44 drives - Ethernet (encapsulated in UDP datagrams on the host machine) - Standard Keyboard/Mouse/Video + - Alto Keyset (5-key chording keyboard) - Audio DAC (used with the Smalltalk Music System) - The Orbit raster hardware, Dover Raster Output Scanner and Dover print engine, which provides 384dpi print output (currently PDF only) + - The Trident Disk Controller (TriCon) and up to eight T-80 or T-300 + drives 1.2 What's Not -------------- -At this time, ContrAlto does not support more exotic hardware such as Trident -disks, printers or audio using the utility port, or the keyset input device. +At this time, ContrAlto does not support more exotic hardware such as printers +or audio using the utility port. The Audio DAC is technically emulated, but output is not connected to an audio device on non-Windows platforms at this time. @@ -143,8 +146,16 @@ Blank-Bottom F3 DEL Del LOCK F4 +3.1.3 Keyset +------------ -3.1.3 Disk Packs +A 5-key chording keyboard referred to as the "keyset" was a standard peripheral +in the early days of the Alto. (It never caught on.) The 5 keys on the keyset +are mapped to F5-F9 on your keyboard, with F5 corresponding to the leftmost key +and F9 corresponding to the rightmost. + + +3.1.4 Disk Packs ---------------- A real Alto uses large 14" disk packs for disk storage, each containing @@ -153,8 +164,8 @@ data. ContrAlto uses files, referred to as "disk images" or just "images" that contain a bit-for-bit copy of these original packs. These are a lot easier to use with a modern PC. -Disk images can be loaded and unloaded via the "Load Disk" command. (See Section -5 for details on this and other commands.) +Disk images can be loaded and unloaded via the "Load Disk" and "Unload Disk" +commands. (See Section 5 for details on this and other commands.) If you modify the contents of a loaded disk (for example creating new files or deleting existing ones) the changes will be written back out to the disk image @@ -166,14 +177,30 @@ ContrAlto does not come with any disk images, however an assortment of Alto programs can be found on Bitsavers.org, at http://www.bitsavers.org/bits/Xerox/Alto/disk_images/. Images include: -AllGames.dsk - A collection of games and toys for the Alto -Bcpl.dsk - A set of BCPL development tools -Diags.dsk - Diagnostic tools -NonProg.dsk - The "Non-Programmer's Disk," containing Bravo -Xmsmall.dsk - Smalltalk-76 +AllGames.dsk - A collection of games and toys for the Alto +chm/Bcpl.dsk - A set of BCPL development tools +chm/Diags.dsk - Diagnostic tools +chm/Bravox.dsk - The BravoX word processing environment +chm/Xmsmall.dsk - Smalltalk-76 -3.1.4 Startup, Reset and Shutdown +3.1.5 Trident Disk Packs +------------------------ + +Some Altos were used as file or print servers and needed greater storage +capacity than the Diablo drives could provide. These Altos used a special +controller, referred to as the Trident (or TriCon) which could control up to +eight Century (later Xerox) T-80 or T-300 drives with a capacity of 80 or +300 megabytes, respectively. + +ContrAlto can emulate a Trident controller and up to eight T-80 or T-300 drives +(in any combination.) Like the Diablo, the contents of these disk packs are +stored in image files. These are loaded, unloaded, or created using the +"Load Trident," "Unload Trident" commands. (See Section 5 for +details on this and other commands.) + + +3.1.5 Startup, Reset and Shutdown --------------------------------- The system can be started at any time by using the "Start" command, though @@ -372,11 +399,25 @@ Start With Keyboard Net Boot - Starts the emulated Alto with the keyboard ethern either in the configuration file or by the Set Keyboard Net Boot File command. -Load Disk - Loads the specified drive (0 or 1) with the requested disk image. +Load Disk - Loads the specified Diablo drive (0 or 1) with the requested disk image. -Unload Disk - Unloads the specified drive (0 or 1). Changes to disk contents are saved. +Unload Disk - Unloads the specified Diablo drive (0 or 1). Changes to disk contents are saved. -Show Disk - Displays the currently loaded image for the specified drive (0 or 1). +Create Disk - Creates a new (empty) disk image and loads the specified Diablo drive with it. + +Show Disk - Displays the currently loaded image for the specified Diablo drive (0 or 1). + +Load Trident - Loads the specified Trident drive (0 through 7) with the requested + disk image. + +Unload Trident - Unloads the specified Trident drive (0 through 7). Changes to disk + contents are saved. + +Create Trident - Creates a new (empty) disk image and loads the specified Trident drive with it. + Specifying a file extension of ".dsk80" will create a new T-80 disk; an extension + of ".dsk300" will create a new T-300 disk. + +Show Trident - Displays the currently loaded image for the specified drive (0 through 7). Show System Type - Displays the Alto system type as configured by the configuration file. @@ -424,8 +465,13 @@ specifying the file to be net booted. At the moment, the following issues are known and being worked on. If you find an issue not listed here, see section 7.0 to report a new bug. +- TriEx reports a status of "00000" randomly when doing read operations from + Trident disks. TFU, and IFS work correctly. + - Audio is not available on Unix / OS X. +- Fullscreen video is not yet implemented on Unix / OS X. + 7.0 Reporting Bugs ================== @@ -475,6 +521,15 @@ PDF generation is provided by the iTextSharp library, see: https://github.com/it 10.0 Change History =================== +V1.2.2 +------ +- Initial support for the Trident controller and associated T-80 and T-300 + drives. +- Added support for the Alto Keyset. (Finally.) +- Fixed bug in XM bank register soft-reset behavior. IFS now runs. +- Fixed issue with ethernet encapsulation that caused the emulator to receive + its own packets. + V1.2.1 ------ - Completed implementation of Orbit, Dover ROS and Dover print engine. diff --git a/Contralto/readme.txt b/Contralto/readme.txt index f36e80e..5e2860c 100644 --- a/Contralto/readme.txt +++ b/Contralto/readme.txt @@ -18,17 +18,20 @@ ContrAlto currently emulates the following Alto hardware: - 256KW of main memory (in 64KW banks) - Two Diablo Model 31 or 44 drives - Ethernet (encapsulated in either UDP datagrams or raw Ethernet frames - on the host machine) + on the host machine) - Standard Keyboard/Mouse/Video + - Alto Keyset (5-key chording keyboard) - Audio DAC (used with the Smalltalk Music System) - The Orbit raster hardware, Dover Raster Output Scanner and Dover print engine, which provides 384dpi print output (currently PDF only) + - The Trident Disk Controller (TriCon) and up to eight T-80 or T-300 + drives 1.2 What's Not -------------- -At this time, ContrAlto does not support more exotic hardware such as Trident -disks, printers or audio using the utility port or the keyset input device. +At this time, ContrAlto does not support more exotic hardware such as printers +or audio using the utility port. 2.0 Requirements @@ -138,8 +141,17 @@ DEL Del LOCK F4 -3.1.3 Disk Packs ----------------- +3.1.3 Keyset +------------ + +A 5-key chording keyboard referred to as the "keyset" was a standard peripheral +in the early days of the Alto. (It never caught on.) The 5 keys on the keyset +are mapped to F5-F9 on your keyboard, with F5 corresponding to the leftmost key +and F9 corresponding to the rightmost. + + +3.1.4 Diablo Disk Packs +----------------------- A real Alto uses large 14" disk packs for disk storage, each containing approximately 2.5 megabytes (for Diablo 31) or 5 megabytes (for Diablo 44) of @@ -147,7 +159,7 @@ data. ContrAlto uses files, referred to as "disk images" or just "images" that contain a bit-for-bit copy of these original packs. These are a lot easier to use with a modern PC. -Disk images can be loaded and unloaded via the "System->Drive 0" and +Disk images can be loaded, unloaded and created via the "System->Drive 0" and System->Drive 1" menus. A file dialog will be presented showing possible disk images in the current directory. @@ -161,14 +173,29 @@ ContrAlto does not come with any disk images, however an assortment of Alto programs can be found on Bitsavers.org, at http://www.bitsavers.org/bits/Xerox/Alto/disk_images/. Images include: -AllGames.dsk - A collection of games and toys for the Alto -Bcpl.dsk - A set of BCPL development tools -Diags.dsk - Diagnostic tools -NonProg.dsk - The "Non-Programmer's Disk," containing Bravo -Xmsmall.dsk - Smalltalk-76 +AllGames.dsk - A collection of games and toys for the Alto +chm/Bcpl.dsk - A set of BCPL development tools +chm/Diags.dsk - Diagnostic tools +chm/Bravox.dsk - The BravoX word processing environment +chm/Xmsmall.dsk - Smalltalk-76 -3.1.4 Startup, Reset and Shutdown +3.1.5 Trident Disk Packs +------------------------ + +Some Altos were used as file or print servers and needed greater storage +capacity than the Diablo drives could provide. These Altos used a special +controller, referred to as the Trident (or TriCon) which could control up to +eight Century (later Xerox) T-80 or T-300 drives with a capacity of 80 or +300 megabytes, respectively. + +ContrAlto can emulate a Trident controller and up to eight T-80 or T-300 drives +(in any combination.) Like the Diablo, the contents of these disk packs are +stored in image files. These are loaded, unloaded, or created using the +System->Trident Drives->Drive N menus. + + +3.1.6 Startup, Reset and Shutdown --------------------------------- The system can be started at any time by using the "System->Start" menu, though @@ -465,8 +492,8 @@ Reserved Memory: 6.0 Known Issues ================ -At the moment, there are no major known issues. If you encounter any, please -see the next section! +- TriEx reports a status of "00000" randomly when doing read operations from + Trident disks. TFU and IFS work correctly. 7.0 Reporting Bugs @@ -518,6 +545,15 @@ PDF generation is provided by the iTextSharp library, see: https://github.com/it 10.0 Change History =================== +V1.2.2 +------ +- Initial support for the Trident controller and associated T-80 and T-300 + drives. +- Added support for the Alto Keyset. (Finally.) +- Fixed bug in XM bank register soft-reset behavior. IFS now runs. +- Fixed issue with ethernet encapsulation that caused the emulator to receive + its own packets. + V1.2.1 ------ - Completed implementation of Orbit, Dover ROS and Dover print engine. diff --git a/ContraltoSetup/Product.wxs b/ContraltoSetup/Product.wxs index ba787cc..3ad993f 100644 --- a/ContraltoSetup/Product.wxs +++ b/ContraltoSetup/Product.wxs @@ -17,18 +17,18 @@ --> - + + Minimum="1.2.1" IncludeMinimum="yes" + Maximum="1.2.1" IncludeMaximum="yes" /> + Minimum="1.2.1" IncludeMinimum="no" /> - +