FruitMachine-Swift/FruitMachine/AppleII/Peripherals/DiskII/DiskII.swift

397 lines
15 KiB
Swift

//
// 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()
}
}