// // 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 { 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 diskImage = DiskImage(diskPath: "/Users/luigi/apple2/master.dsk") init(slot: Int, romPath: String) { slotNumber = slot romManager = ROMManager(path: romPath, atAddress: 0x0, size: 256) 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) } //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) 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) { currentTrack -= 1 } print("Drive now on track \(currentTrack)") updateCurrentTrackDisplay(drive: softswitches.DriveSelect) } else if(motorPhase == .Phase3) { motorPhase = .Phase0 if(currentTrack % 2 == 1 && currentTrack < 34) { currentTrack += 1 } print("Drive now on track \(currentTrack)") updateCurrentTrackDisplay(drive: softswitches.DriveSelect) } 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 } print("Drive now on track \(currentTrack)") updateCurrentTrackDisplay(drive: softswitches.DriveSelect) } else if(motorPhase == .Phase1) { motorPhase = .Phase2 if(currentTrack % 2 == 0 && currentTrack < 34) { currentTrack += 1; } print("Drive now on track \(currentTrack)") updateCurrentTrackDisplay(drive: softswitches.DriveSelect) } 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) 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) 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() print("Drive 1 Motor is on") } else { NotificationCenter.default.post(name: DiskII.N_Drive2MotorOn, object: nil) motor1OffTimer?.invalidate() print("Drive 2 Motor is on") } case 10: softswitches.DriveSelect = false print("Drive 1 selected") case 11: softswitches.DriveSelect = true print("Drive 2 selected") case 12: softswitches.Q6 = false if(softswitches.Q7 == false && byte == nil) { //in read mode and a read was requested. get the next byte let trk = CPU.sharedInstance.memoryInterface.readByte(offset: 0xB7EC, bypassOverrides: true) let sec = CPU.sharedInstance.memoryInterface.readByte(offset: 0xB7ED, bypassOverrides: true) let mode = CPU.sharedInstance.memoryInterface.readByte(offset: 0xB7F4, bypassOverrides: true) if(trk == 2 && sec == 4 && mode == 1) { _ = 1 } let modeString: String switch (mode) { case 0: modeString = "seeking" case 1: modeString = "reading" case 2: modeString = "writing" case 4: modeString = "formatting" default: modeString = "???" } print("Reading byte \(mediaPosition) of track \(currentTrack). DOS is \(modeString) T\(trk) S\(sec).") return readByteOfTrack(track: currentTrack, advance: softswitches.MotorPowered ? 1 : 0) } case 13: //WRITE PROTECT SENSE MODE softswitches.Q6 = true case 14: //READ MODE softswitches.Q7 = false case 15: softswitches.Q7 = true default: print("Unknown command? This can't happen.") } return 0x00 } func readByteOfTrack(track: Int, advance: Int) -> UInt8 { let trackData = diskImage.encodedTracks[track] let result = trackData[mediaPosition] //Advance the drive to the next byte mediaPosition = (mediaPosition + advance) % trackData.count return result } func updateCurrentTrackDisplay(drive: Bool) { if(drive == false) { NotificationCenter.default.post(name: DiskII.N_Drive1TrackChanged, object: currentTrack) } else { NotificationCenter.default.post(name: DiskII.N_Drive2TrackChanged, object: currentTrack) } } 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 print("Drive 1 Motor is now off") } @objc func disableDrive2Motor() { softswitches.MotorPowered = false print("Drive 2 Motor is now off") } }