// // DiskII.swift // FruitMachine // // Created by Christopher Rohl on 8/2/17. // Copyright © 2017 Christopher Rohl. All rights reserved. // import Cocoa /* Commands: 0 = PHASE 0 OFF 1 = PHASE 0 ON 2 = PHASE 1 OFF 3 = PHASE 1 ON 4 = PHASE 2 OFF 5 = PHASE 2 ON 6 = PHASE 3 OFF 7 = PHASE 3 ON 8 = TURN MOTOR OFF 9 = TURN MOTOR ON A = SELECT DRIVE 1 B = SELECT DRIVE 2 C = Q6 -> L D = Q6 -> H E = Q7 -> L F = Q7 -> H Q6 Q7 L L READ H L SENSE WRITE PROTECT OR PREWRITE STATE L H WRITE H H WRITE LOAD */ class DiskII: NSObject, Peripheral, HasROM { let debug = false enum MotorPhase { case Phase0 case Phase1 case Phase2 case Phase3 } /* Notifications */ static let N_Drive1MotorOn = NSNotification.Name(rawValue: "Drive1MotorOn") static let N_Drive2MotorOn = NSNotification.Name(rawValue: "Drive2MotorOn") static let N_Drive1MotorOff = NSNotification.Name(rawValue: "Drive1MotorOff") static let N_Drive2MotorOff = NSNotification.Name(rawValue: "Drive2MotorOff") static let N_Drive1TrackChanged = NSNotification.Name(rawValue: "Drive1TrackChanged") static let N_Drive2TrackChanged = NSNotification.Name(rawValue: "Drive2TrackChanged") var motor1OffTimer: Timer? var motor2OffTimer: Timer? /* Softswitches */ struct Softswitches { var Phase0 = false var Phase1 = false var Phase2 = false var Phase3 = false var MotorPowered = false var DriveSelect = false //false = 1, true = 2 var Q6 = false var Q7 = false } let slotNumber: Int let romManager: ROMManager var readMemoryOverride: ReadOverride? = nil var readIOOverride: ReadOverride? = nil var writeIOOverride: WriteOverride? = nil var softswitches = Softswitches() var currentTrack: Int = 0 var mediaPosition: Int = 0 var motorPhase: MotorPhase = .Phase0 var preloadedByte: UInt8 = 0x00 var diskImage: DiskImage? init(slot: Int, romPath: String) { slotNumber = slot romManager = ROMManager(path: romPath, atAddress: 0x0, size: 256) diskImage = nil super.init() readMemoryOverride = ReadOverride(start: UInt16(0xC000 + (0x100 * slotNumber)), end: UInt16(0xC0FF + (0x100 * slotNumber)), readAnyway: false, action: actionReadMemory) readIOOverride = ReadOverride(start: UInt16(0xC080 + (0x10 * slotNumber)), end: UInt16(0xC08F + (0x10 * slotNumber)), readAnyway: false, action: actionDispatchOperation) writeIOOverride = WriteOverride(start: UInt16(0xC080 + (0x10 * slotNumber)), end: UInt16(0xC08F + (0x10 * slotNumber)), writeAnyway: false, action: actionDispatchOperation) } func attachDiskImage(imagePath: String) { let image = DiskImage(diskPath: imagePath) self.diskImage = image print("attached image to DiskII \(self)") } //http://ftp.twaren.net/NetBSD/misc/wrstuden/Apple_PDFs/Software%20control%20of%20IWM.pdf private func actionDispatchOperation(something: AnyObject, address: UInt16, byte: UInt8?) -> UInt8? { let operationNumber = UInt8(address & 0xFF) - UInt8(0x80 & 0xFF) - UInt8(0x10 * slotNumber) if(debug) { print("Disk II command: \(operationNumber)") } //Update the softswitches. switch(operationNumber) { case 0: softswitches.Phase0 = false case 1: softswitches.Phase0 = true if(motorPhase == .Phase1) { motorPhase = .Phase0 //if(currentTrack % 2 == 0 && currentTrack > 0) if(currentTrack > 0) { currentTrack -= 1 } if(debug) { print("Drive now on track \(currentTrack)") } } else if(motorPhase == .Phase3) { motorPhase = .Phase0 //if(currentTrack % 2 == 1 && currentTrack < 34) { if(currentTrack < 34) { currentTrack += 1 } if(debug) { print("Drive now on track \(currentTrack)") } } case 2: softswitches.Phase1 = false case 3: softswitches.Phase1 = true if(motorPhase == .Phase0 || motorPhase == .Phase2) { motorPhase = .Phase1 } case 4: softswitches.Phase2 = false case 5: softswitches.Phase2 = true if(motorPhase == .Phase3) { motorPhase = .Phase2 if(currentTrack % 2 == 1 && currentTrack > 0) { currentTrack -= 1 } if(debug) { print("Drive now on track \(currentTrack)") } } else if(motorPhase == .Phase1) { motorPhase = .Phase2 if(currentTrack % 2 == 0 && currentTrack < 34) { currentTrack += 1; } if(debug) { print("Drive now on track \(currentTrack)") } } case 6: softswitches.Phase3 = false case 7: softswitches.Phase3 = true if(motorPhase == .Phase0 || motorPhase == .Phase2) { motorPhase = .Phase3 } case 8: //softswitches.MotorPowered = false if(softswitches.DriveSelect == false) { NotificationCenter.default.post(name: DiskII.N_Drive1MotorOff, object: nil) motor1OffTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(disableDrive1Motor), userInfo: nil, repeats: false) if(debug) { print("Drive 1 Motor will turn off in 1 second") } } else { NotificationCenter.default.post(name: DiskII.N_Drive2MotorOff, object: nil) motor2OffTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(disableDrive2Motor), userInfo: nil, repeats: false) if(debug) { print("Drive 2 Motor will turn off in 1 second") } } case 9: softswitches.MotorPowered = true if(softswitches.DriveSelect == false) { NotificationCenter.default.post(name: DiskII.N_Drive1MotorOn, object: nil) motor1OffTimer?.invalidate() if(debug) { print("Drive 1 Motor is on") } } else { NotificationCenter.default.post(name: DiskII.N_Drive2MotorOn, object: nil) motor1OffTimer?.invalidate() if(debug) { print("Drive 2 Motor is on") } } case 10: softswitches.DriveSelect = false if(debug) { print("Drive 1 selected") } case 11: softswitches.DriveSelect = true if(debug) { print("Drive 2 selected") } case 12: softswitches.Q6 = false let trk: UInt8 let sec: UInt8 let mode: UInt8 let blkLo: UInt8 let blkHi: UInt8 if(diskImage == nil) { print("No disk inserted, aborting. DiskII \(self)") return 0x00 } if(diskImage?.image is Dos33Image) { trk = CPU.sharedInstance.memoryInterface.readByte(offset: 0xB7EC, bypassOverrides: true) sec = CPU.sharedInstance.memoryInterface.readByte(offset: 0xB7ED, bypassOverrides: true) mode = CPU.sharedInstance.memoryInterface.readByte(offset: 0xB7F4, bypassOverrides: true) blkLo = 0 blkHi = 0 } else if(diskImage?.image is ProdosImage) { trk = CPU.sharedInstance.memoryInterface.readByte(offset: 0x41, bypassOverrides: true) sec = CPU.sharedInstance.memoryInterface.readByte(offset: 0x3D, bypassOverrides: true) mode = CPU.sharedInstance.memoryInterface.readByte(offset: 0x42, bypassOverrides: true) blkLo = CPU.sharedInstance.memoryInterface.readByte(offset: 0x46, bypassOverrides: true) blkHi = CPU.sharedInstance.memoryInterface.readByte(offset: 0x47, bypassOverrides: true) } else { trk = 0 sec = 0 mode = 0 blkLo = 0 blkHi = 0 } if(trk == 2 && sec == 4 && mode == 1) { _ = 1 } let modeString: String switch (mode) { case 0: modeString = "seek" case 1: modeString = "read" case 2: modeString = "write" case 4: modeString = "format" default: modeString = "???" } if(debug) { if(diskImage?.image is Dos33Image) { print("Head is at nibble \(mediaPosition) of track \(currentTrack). DOS is trying to \(modeString) T\(trk) S\(sec).") } else if(diskImage?.image is ProdosImage) { print("Head is at nibble \(mediaPosition) of track \(currentTrack). ProDOS is trying to \(modeString) Block $\(blkHi.asHexString())\(blkLo.asHexString()) (T\(trk) S\(sec)).") if(mode == 1) { let bufLo = CPU.sharedInstance.memoryInterface.readByte(offset: 0x44, bypassOverrides: true) let bufHi = CPU.sharedInstance.memoryInterface.readByte(offset: 0x45, bypassOverrides: true) print("I/O buffer is located at $\(bufHi.asHexString())\(bufLo.asHexString())") } } } updateCurrentTrackSectorDisplay(drive: softswitches.DriveSelect, track: currentTrack, sector: Int(sec)) if(softswitches.Q7 == false && byte == nil) { //in read mode and a read was requested. get the next nibble return readByteOfTrack(track: currentTrack, advance: softswitches.MotorPowered ? 1 : 0) } if(softswitches.Q7 == true && byte == nil) { //in write mode writeNibbleOfTrack(track: currentTrack, advance: softswitches.MotorPowered ? 1 : 0, nibble: preloadedByte) return 0x00 } if(debug) { print("Disk II: Operation failed!") } case 13: //WRITE PROTECT SENSE MODE softswitches.Q6 = true if(byte != nil) { preloadedByte = byte! if(debug) { print("WRITE LOAD: shift register contains \(preloadedByte.asHexString())") } } case 14: if(debug) { print("Disk II: READ STATUS REGISTER") } softswitches.Q7 = false if(diskImage != nil) { return 0x00 | (diskImage!.writeProtect ? 0x80 : 0x00) | (softswitches.MotorPowered ? 0x20 : 0x00) } else { return 0x00 | (softswitches.MotorPowered ? 0x20 : 0x00) } case 15: softswitches.Q7 = true //STA $C if(softswitches.Q6 == true && byte != nil) { preloadedByte = byte! if(debug) { print("WRITE LOAD: shift register contains \(preloadedByte.asHexString())") } } default: if(debug) { print("Unknown command? This can't happen.") } } return 0x00 } func readByteOfTrack(track: Int, advance: Int) -> UInt8 { if(diskImage == nil) { return 0x00 } //No disk inserted, fail. let trackData = diskImage?.encodedTracks[track] if(trackData == nil) { return 0x00 } //No disk inserted, fail. let result = trackData![mediaPosition] //Advance the drive to the next byte mediaPosition = (mediaPosition + advance) % trackData!.count return result } func writeNibbleOfTrack(track: Int, advance: Int, nibble: UInt8) { if(diskImage == nil) { return} //No disk inserted, fail. let trackData = diskImage?.encodedTracks[track] if(trackData == nil) { return } //No disk inserted, fail. if(debug) { print("wrote \(nibble.asHexString()) to disk") } diskImage!.encodedTracks[track][mediaPosition] = nibble mediaPosition = (mediaPosition + advance) % trackData!.count } func updateCurrentTrackSectorDisplay(drive: Bool, track: Int, sector: Int) { if(drive == false) { NotificationCenter.default.post(name: DiskII.N_Drive1TrackChanged, object: (currentTrack, sector)) } else { NotificationCenter.default.post(name: DiskII.N_Drive2TrackChanged, object: (currentTrack, sector)) } } func installOverrides() { CPU.sharedInstance.memoryInterface.read_overrides.append(readMemoryOverride!) CPU.sharedInstance.memoryInterface.read_overrides.append(readIOOverride!) CPU.sharedInstance.memoryInterface.write_overrides.append(writeIOOverride!) } private func actionReadMemory(something: AnyObject, address: UInt16, byte: UInt8?) -> UInt8? { let offset: UInt16 = 0xC000 + UInt16(slotNumber*0x100) let local = address - offset return getMemoryMappedByte(address: local) } private func getMemoryMappedByte(address: UInt16) -> UInt8 { //Disk II just maps its ROM to the memory addressed by the slot. return romManager.ROM[Int(address)] } @objc func disableDrive1Motor() { softswitches.MotorPowered = false if(debug) { print("Drive 1 Motor is now off, saving updated image") } diskImage?.saveDiskImage() } @objc func disableDrive2Motor() { softswitches.MotorPowered = false if(debug) { print("Drive 2 Motor is now off, saving updated image") } diskImage?.saveDiskImage() } }