Support for disk writes in memory. Not persisted to host disk
This commit is contained in:
parent
8233b99e43
commit
3b07b77ea2
|
@ -7,9 +7,14 @@ import (
|
||||||
|
|
||||||
/*
|
/*
|
||||||
https://applesaucefdc.com/woz/reference2/
|
https://applesaucefdc.com/woz/reference2/
|
||||||
|
|
||||||
Good explanation of the softswitches and the phases:
|
Good explanation of the softswitches and the phases:
|
||||||
http://yesterbits.com/media/pubs/AppleOrchard/articles/disk-ii-part-1-1983-apr.pdf
|
http://yesterbits.com/media/pubs/AppleOrchard/articles/disk-ii-part-1-1983-apr.pdf
|
||||||
|
|
||||||
|
"IMW Floppy Disk I/O Controller info" (https://www.brutaldeluxe.fr/documentation/iwm/apple2_IWM_INFO_19840510.pdf)
|
||||||
|
|
||||||
|
"Understanfing the Apple II, chapter 9"
|
||||||
|
|
||||||
35 tracks, 16 sectors, 256 bytes
|
35 tracks, 16 sectors, 256 bytes
|
||||||
NIB: 35 tracks 6656 bytes, 232960 bytes
|
NIB: 35 tracks 6656 bytes, 232960 bytes
|
||||||
|
|
||||||
|
@ -18,138 +23,167 @@ const maxHalfTrack = 68
|
||||||
|
|
||||||
type cardDisk2 struct {
|
type cardDisk2 struct {
|
||||||
cardBase
|
cardBase
|
||||||
selected int // Only 0 and 1 supported
|
selected int // q5, Only 0 and 1 supported
|
||||||
drive [2]cardDisk2Drive
|
drive [2]cardDisk2Drive
|
||||||
|
|
||||||
|
dataLatch uint8
|
||||||
|
q6 bool
|
||||||
|
q7 bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type cardDisk2Drive struct {
|
type cardDisk2Drive struct {
|
||||||
diskette *diskette16sector
|
diskette *diskette16sector
|
||||||
currentPhase int
|
currentPhase int
|
||||||
power bool
|
power bool // q4
|
||||||
writeMode bool
|
|
||||||
halfTrack int
|
halfTrack int
|
||||||
position int
|
position int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cardDisk2) assign(a *Apple2, slot int) {
|
const (
|
||||||
// Phase control soft switches
|
diskBitCycle = 4 // There is a dataLatch bit transferred every 4 cycles
|
||||||
// Lazy emulation. It only checks for phases on and move the head
|
diskLatchReadCycles = 7 // Loaded data is available for a little more than 7ns
|
||||||
// up or down depending on the previous phase.
|
diskWriteByteCycle = 32 // Load data to write every 32 cycles
|
||||||
for i := 0; i < 4; i++ {
|
diskWriteSelfSyncCycle = 40 // Save $FF every 40 cycles. Self sync is 10 bits: 1111 1111 00
|
||||||
func(phase int) {
|
diskMotorStartMs = 150 // Time with the disk spinning to get full speed
|
||||||
c.ssr[phase<<1] = func(_ *ioC0Page) uint8 {
|
|
||||||
//fmt.Printf("DISKII: Phase %v off\n", phase)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
c.ssr[(phase<<1)+1] = func(_ *ioC0Page) uint8 {
|
|
||||||
//fmt.Printf("DISKII: Phase %v on\n", phase)
|
|
||||||
halfTrack := c.drive[c.selected].halfTrack
|
|
||||||
delta := (phase - c.drive[c.selected].currentPhase + 4) % 4
|
|
||||||
switch delta {
|
|
||||||
case 1: // Up
|
|
||||||
halfTrack++
|
|
||||||
case 2: // Illegal, let's say up
|
|
||||||
halfTrack++
|
|
||||||
case 3: // Down
|
|
||||||
halfTrack--
|
|
||||||
case 0: // No chamge
|
|
||||||
}
|
|
||||||
|
|
||||||
if halfTrack > maxHalfTrack {
|
)
|
||||||
halfTrack = maxHalfTrack
|
|
||||||
} else if halfTrack < 0 {
|
func (c *cardDisk2) assign(a *Apple2, slot int) {
|
||||||
halfTrack = 0
|
// Q1, Q2, Q3 and Q4 phase control soft switches,
|
||||||
}
|
for i := 0; i < 4; i++ {
|
||||||
c.drive[c.selected].halfTrack = halfTrack
|
phase := i
|
||||||
c.drive[c.selected].currentPhase = phase
|
c.ssr[phase<<1] = func(_ *ioC0Page) uint8 {
|
||||||
//fmt.Printf("DISKII: Current halftrack is %v\n", halfTrack)
|
return c.dataLatch // All even addresses return the last dataLatch
|
||||||
return 0
|
}
|
||||||
|
c.ssr[(phase<<1)+1] = func(_ *ioC0Page) uint8 {
|
||||||
|
// Move the head up or down depending on the previous phase.
|
||||||
|
drive := &c.drive[c.selected]
|
||||||
|
delta := (phase - drive.currentPhase + 4) % 4
|
||||||
|
switch delta {
|
||||||
|
case 1: // Up
|
||||||
|
drive.halfTrack++
|
||||||
|
case 2: // Illegal, let's say up
|
||||||
|
drive.halfTrack++
|
||||||
|
case 3: // Down
|
||||||
|
drive.halfTrack--
|
||||||
|
case 0: // No chamge
|
||||||
}
|
}
|
||||||
}(i)
|
|
||||||
|
// Don't go over the limits
|
||||||
|
if drive.halfTrack > maxHalfTrack {
|
||||||
|
drive.halfTrack = maxHalfTrack
|
||||||
|
} else if drive.halfTrack < 0 {
|
||||||
|
drive.halfTrack = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
drive.currentPhase = phase
|
||||||
|
//fmt.Printf("DISKII: Current halftrack is %v\n", drive.halfTrack)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Other soft switches
|
// Q4, power switch
|
||||||
c.ssr[0x8] = func(_ *ioC0Page) uint8 {
|
c.ssr[0x8] = func(_ *ioC0Page) uint8 {
|
||||||
if c.drive[c.selected].power {
|
if c.drive[c.selected].power {
|
||||||
c.drive[c.selected].power = false
|
c.drive[c.selected].power = false
|
||||||
c.a.releaseFastMode()
|
c.a.releaseFastMode()
|
||||||
//fmt.Printf("DISKII: Disk %v is off for %v\n", c.selected, x)
|
|
||||||
}
|
}
|
||||||
return 0
|
return c.dataLatch
|
||||||
}
|
}
|
||||||
c.ssr[0x9] = func(_ *ioC0Page) uint8 {
|
c.ssr[0x9] = func(_ *ioC0Page) uint8 {
|
||||||
if !c.drive[c.selected].power {
|
if !c.drive[c.selected].power {
|
||||||
c.drive[c.selected].power = true
|
c.drive[c.selected].power = true
|
||||||
c.a.requestFastMode()
|
c.a.requestFastMode()
|
||||||
//fmt.Printf("DISKII: Disk %v is on\n", c.selected)
|
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Q5, drive selecion
|
||||||
c.ssr[0xA] = func(_ *ioC0Page) uint8 {
|
c.ssr[0xA] = func(_ *ioC0Page) uint8 {
|
||||||
c.selected = 0
|
c.selected = 0
|
||||||
//fmt.Printf("DISKII: Disk %v selected\n", c.selected)
|
return c.dataLatch
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
c.ssr[0xB] = func(_ *ioC0Page) uint8 {
|
c.ssr[0xB] = func(_ *ioC0Page) uint8 {
|
||||||
c.selected = 1
|
c.selected = 1
|
||||||
//fmt.Printf("DISKII: Disk %v selected\n", c.selected)
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Q6L
|
// Q6, Q7
|
||||||
c.ssr[0xC] = func(_ *ioC0Page) uint8 {
|
for i := 0xC; i <= 0xF; i++ {
|
||||||
//fmt.Printf("DISKII: Reading\n")
|
iCopy := i
|
||||||
drive := &c.drive[c.selected]
|
c.ssr[iCopy] = func(_ *ioC0Page) uint8 {
|
||||||
if drive.diskette == nil {
|
return c.softSwitchQ6Q7(iCopy, 0)
|
||||||
return 0xff
|
}
|
||||||
|
c.ssw[iCopy] = func(_ *ioC0Page, value uint8) {
|
||||||
|
c.softSwitchQ6Q7(iCopy, value)
|
||||||
}
|
}
|
||||||
track := drive.halfTrack / 2
|
|
||||||
value, newPosition := drive.diskette.read(track, drive.position)
|
|
||||||
drive.position = newPosition
|
|
||||||
//fmt.Printf("DISKII: Reading value 0x%02v from track %v, position %v\n", value, track, drive.position)
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
c.ssw[0xC] = func(_ *ioC0Page, value uint8) {
|
|
||||||
//fmt.Printf("DISKII: Writing the value 0x%02x\n", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Q6H
|
|
||||||
c.ssr[0xD] = func(_ *ioC0Page) uint8 {
|
|
||||||
c.drive[c.selected].writeMode = false
|
|
||||||
//fmt.Printf("DISKII: Sense write protection\n")
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Q7L
|
|
||||||
c.ssr[0xE] = func(_ *ioC0Page) uint8 {
|
|
||||||
c.drive[c.selected].writeMode = false
|
|
||||||
//fmt.Printf("DISKII: Set read mode\n")
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Q7H
|
|
||||||
c.ssr[0xF] = func(_ *ioC0Page) uint8 {
|
|
||||||
c.drive[c.selected].writeMode = true
|
|
||||||
//fmt.Printf("DISKII: Set write mode\n")
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.cardBase.assign(a, slot)
|
c.cardBase.assign(a, slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *cardDisk2) softSwitchQ6Q7(index int, in uint8) uint8 {
|
||||||
|
switch index {
|
||||||
|
case 0xC: // Q6L
|
||||||
|
c.q6 = false
|
||||||
|
case 0xD: // Q6H
|
||||||
|
c.q6 = true
|
||||||
|
case 0xE: // Q/L
|
||||||
|
c.q7 = false
|
||||||
|
case 0xF: // Q7H
|
||||||
|
c.q7 = true
|
||||||
|
}
|
||||||
|
|
||||||
|
c.processQ6Q7(in)
|
||||||
|
if index&1 == 0 {
|
||||||
|
// All even addresses return the last dataLatch
|
||||||
|
return c.dataLatch
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cardDisk2) processQ6Q7(in uint8) {
|
||||||
|
d := &c.drive[c.selected]
|
||||||
|
if d.diskette == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !c.q6 {
|
||||||
|
if !c.q7 { // Q6L-Q7L: Read
|
||||||
|
track := d.halfTrack / 2
|
||||||
|
c.dataLatch, d.position = d.diskette.read(track, d.position)
|
||||||
|
} else { // Q6L-Q7H: Write the dataLatch value to disk. Shift data out
|
||||||
|
track := d.halfTrack / 2
|
||||||
|
d.position = d.diskette.write(track, d.position, c.dataLatch)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !c.q7 { // Q6H-Q7L: Sense write protect / prewrite state
|
||||||
|
// Bit 7 of the control status register means write protected
|
||||||
|
c.dataLatch = 0 // Never write protected
|
||||||
|
} else { // Q6H-Q7H: Load data into the controller
|
||||||
|
c.dataLatch = in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (d *cardDisk2Drive) insertDiskette(dt *diskette16sector) {
|
func (d *cardDisk2Drive) insertDiskette(dt *diskette16sector) {
|
||||||
d.diskette = dt
|
d.diskette = dt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cardDisk2) save(w io.Writer) {
|
func (c *cardDisk2) save(w io.Writer) {
|
||||||
binary.Write(w, binary.BigEndian, c.selected)
|
binary.Write(w, binary.BigEndian, c.selected)
|
||||||
|
binary.Write(w, binary.BigEndian, c.dataLatch)
|
||||||
|
binary.Write(w, binary.BigEndian, c.q6)
|
||||||
|
binary.Write(w, binary.BigEndian, c.q7)
|
||||||
c.drive[0].save(w)
|
c.drive[0].save(w)
|
||||||
c.drive[1].save(w)
|
c.drive[1].save(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cardDisk2) load(r io.Reader) {
|
func (c *cardDisk2) load(r io.Reader) {
|
||||||
binary.Read(r, binary.BigEndian, &c.selected)
|
binary.Read(r, binary.BigEndian, &c.selected)
|
||||||
|
binary.Read(r, binary.BigEndian, &c.dataLatch)
|
||||||
|
binary.Read(r, binary.BigEndian, &c.q6)
|
||||||
|
binary.Read(r, binary.BigEndian, &c.q7)
|
||||||
c.drive[0].load(r)
|
c.drive[0].load(r)
|
||||||
c.drive[1].load(r)
|
c.drive[1].load(r)
|
||||||
}
|
}
|
||||||
|
@ -157,7 +191,6 @@ func (c *cardDisk2) load(r io.Reader) {
|
||||||
func (d *cardDisk2Drive) save(w io.Writer) {
|
func (d *cardDisk2Drive) save(w io.Writer) {
|
||||||
binary.Write(w, binary.BigEndian, d.currentPhase)
|
binary.Write(w, binary.BigEndian, d.currentPhase)
|
||||||
binary.Write(w, binary.BigEndian, d.power)
|
binary.Write(w, binary.BigEndian, d.power)
|
||||||
binary.Write(w, binary.BigEndian, d.writeMode)
|
|
||||||
binary.Write(w, binary.BigEndian, d.halfTrack)
|
binary.Write(w, binary.BigEndian, d.halfTrack)
|
||||||
binary.Write(w, binary.BigEndian, d.position)
|
binary.Write(w, binary.BigEndian, d.position)
|
||||||
}
|
}
|
||||||
|
@ -165,7 +198,6 @@ func (d *cardDisk2Drive) save(w io.Writer) {
|
||||||
func (d *cardDisk2Drive) load(r io.Reader) {
|
func (d *cardDisk2Drive) load(r io.Reader) {
|
||||||
binary.Read(r, binary.BigEndian, &d.currentPhase)
|
binary.Read(r, binary.BigEndian, &d.currentPhase)
|
||||||
binary.Read(r, binary.BigEndian, &d.power)
|
binary.Read(r, binary.BigEndian, &d.power)
|
||||||
binary.Read(r, binary.BigEndian, &d.writeMode)
|
|
||||||
binary.Read(r, binary.BigEndian, &d.halfTrack)
|
binary.Read(r, binary.BigEndian, &d.halfTrack)
|
||||||
binary.Read(r, binary.BigEndian, &d.position)
|
binary.Read(r, binary.BigEndian, &d.position)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Info from:
|
/*
|
||||||
"Beneath Apple DOS" https://fabiensanglard.net/fd_proxy/prince_of_persia/Beneath%20Apple%20DOS.pdf
|
See:
|
||||||
https://github.com/TomHarte/CLK/wiki/Apple-GCR-disk-encoding
|
"Beneath Apple DOS" https://fabiensanglard.net/fd_proxy/prince_of_persia/Beneath%20Apple%20DOS.pdf
|
||||||
|
https://github.com/TomHarte/CLK/wiki/Apple-GCR-disk-encoding
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -31,6 +32,11 @@ func (d *diskette16sector) read(track int, position int) (value uint8, newPositi
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *diskette16sector) write(track int, position int, value uint8) int {
|
||||||
|
d.track[track][position] = value
|
||||||
|
return (position + 1) % nibBytesPerTrack
|
||||||
|
}
|
||||||
|
|
||||||
func loadDisquette(filename string) *diskette16sector {
|
func loadDisquette(filename string) *diskette16sector {
|
||||||
var d diskette16sector
|
var d diskette16sector
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue