Working on disks

This commit is contained in:
Zellyn Hunter 2013-04-15 17:43:23 -07:00
parent a3eced201d
commit 8f3ce4748b
9 changed files with 697 additions and 33 deletions

130
cards/disk.go Normal file
View File

@ -0,0 +1,130 @@
package cards
import (
"fmt"
"github.com/zellyn/goapple2/disk"
)
type Disk interface {
Read() byte
Write(byte)
SetHalfTrack(byte)
HalfTrack() byte
SetVolume(byte)
Volume() byte
Writeable() bool
}
type DiskCard struct {
rom [256]byte
cm CardManager
slot byte
slotbit byte
disks [2]Disk
active int
phases byte
}
func NewDiskCard(rom []byte, slot byte, cm CardManager) (*DiskCard, error) {
if len(rom) != 256 {
return nil, fmt.Errorf("Wrong size ROM: expected 256, got %d", len(rom))
}
dc := &DiskCard{
cm: cm,
slot: slot,
slotbit: 1 << slot,
disks: [2]Disk{disk.NewDummy(disk.DEFAULT_VOLUME), disk.NewDummy(disk.DEFAULT_VOLUME)},
}
copy(dc.rom[:], rom)
return dc, nil
}
func (dc *DiskCard) String() string {
return fmt.Sprintf("Disk Card (slot %d)", dc.slot)
}
func (dc *DiskCard) Slot() byte {
return dc.slot
}
func (dc *DiskCard) ROMDisabled() {
// Disk card doesn't have a $C(8-F)xx ROM
}
func (dc *DiskCard) handlePhase(phase byte, onOff bool) {
phaseBit := byte(1 << phase)
if onOff {
dc.phases |= phaseBit
} else {
dc.phases &^= phaseBit
}
disk := dc.disks[dc.active]
newTrack := int(disk.HalfTrack())
switch dc.phases {
case 1:
newTrack = (newTrack + 1) / 4 * 4
case 2:
newTrack = (newTrack/4)*4 + 1
case 4:
if newTrack != 0 {
newTrack = (newTrack-1)/4*4 + 2
}
case 8:
if newTrack < 2 {
newTrack = 0
} else {
newTrack = (newTrack-2)/4*4 + 3
}
default:
return
}
if newTrack < 0 {
newTrack = 0
}
if newTrack > 68 {
newTrack = 68
}
if disk.HalfTrack() != byte(newTrack) {
disk.SetHalfTrack(byte(newTrack))
}
}
func (dc *DiskCard) handleAccess(address byte) {
if address < 8 {
phase := address / 2
onOff := (address & 1) == 1
dc.handlePhase(phase, onOff)
return
}
}
func (dc *DiskCard) Read16(address byte) byte {
dc.handleAccess(address)
return dc.cm.EmptyRead()
}
func (dc *DiskCard) Write16(address byte, value byte) {
dc.handleAccess(address)
}
func (dc *DiskCard) Read(address uint16) byte {
panic(fmt.Sprintf("%s got read to $%04X", dc.String(), address))
}
func (dc *DiskCard) Write(address uint16, value byte) {
panic(fmt.Sprintf("%s got write to $%04X", dc.String(), address))
}
func (dc *DiskCard) Read256(address byte) byte {
return dc.rom[address]
}
func (dc *DiskCard) Write256(address byte, value byte) {
// Firmware is ROM: do nothing
}
func (dc *DiskCard) LoadDisk(d Disk, which int) {
dc.disks[which] = d
}

171
disk/convert.go Normal file
View File

@ -0,0 +1,171 @@
package disk
import (
"fmt"
)
// Dos33writeTable is the list of valid DOS 3.3 bytes.
// See [UtA2 9-27 Disk Data Formats].
var Dos33writeTable = [64]byte{
0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
}
// Dos33LogicalToPhysicalSectorMap maps logical sector numbers to physical ones.
// See [UtA2 9-42 - Read Routines].
var Dos33LogicalToPhysicalSectorMap = []byte{
0x00, 0x0D, 0x0B, 0x09, 0x07, 0x05, 0x03, 0x01,
0x0E, 0x0C, 0x0A, 0x08, 0x06, 0x04, 0x02, 0x0F,
}
// Dos33PhysicalToLogicalSectorMap maps physical sector numbers to logical ones.
// See [UtA2 9-42 - Read Routines].
var Dos33PhysicalToLogicalSectorMap = []byte{
0x00, 0x07, 0x0E, 0x06, 0x0D, 0x05, 0x0C, 0x04,
0x0B, 0x03, 0x0A, 0x02, 0x09, 0x01, 0x08, 0x0F,
}
// ProDosLogicalToPhysicalSectorMap maps logical sector numbers to pysical ones.
// See [UtA2e 9-43 - Sectors vs. Blocks].
var ProDosLogicalToPhysicalSectorMap = []byte{
0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E,
0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
}
// ProDosPhysicalToLogicalSectorMap maps physical sector numbers to logical ones.
// See [UtA2e 9-43 - Sectors vs. Blocks].
var ProDosPhysicalToLogicalSectorMap = []byte{
0x00, 0x08, 0x01, 0x09, 0x02, 0x0A, 0x03, 0x0B,
0x04, 0x0C, 0x05, 0x0D, 0x06, 0x0E, 0x07, 0x0F,
}
// PreNybble converts from 256 8-bit bytes to 342 6-bit "nybbles".
// The equivalent DOS routine, PRE.NYBBLE, would put the 256 MSB-6's into one buffer,
// and the 86 LSB-2's into a separate one, but we place the 86 after the 256.
// See http://www.txbobsc.com/aal/1981/aal8106.html#a5
func PreNybble(source []byte) (target [342]byte) {
if len(source) != 256 {
panic(fmt.Sprintf("PreNybble expects 256 bytes as input, got %d", len(source)))
}
y := 2
for i := 0; i < 3; i++ {
for x := 0; x < 86; x++ {
// DEY
y = (y + 0xff) & 0xff
a := source[y]
target[y] = a >> 2
target[x+256] = (target[x+256]<<2 + a&2>>1 + a&1<<1) & 0x3F
}
}
return target
}
// PostNybble converts from 342 6-bit "nybbles to 255 8-bit bytes.
// The equivalent DOS routine, POST.NYBBLE, would read the 256 MSB-6's from one buffer,
// and the 86 LSB-2's from a separate one, but we place the 86 after the 256.
// See http://www.txbobsc.com/aal/1981/aal8106.html#a5
func PostNybble(source []byte) (target [256]byte) {
if len(source) != 342 {
panic(fmt.Sprintf("PostNybble expects 342 bytes as input, got %d", len(source)))
}
x := 0
for y := 0; y < 256; y++ {
// DEX
x = (x + 85) % 86
a := source[256+x]
source[256+x] = a >> 2
target[y] = source[y]<<2 + a&2>>1 + a&1<<1
}
return target
}
// appendSyncs appends the given number of sync bytes to the slice, and returns the resulting slice.
func appendSyncs(target []byte, count int) []byte {
for i := 0; i < count; i++ {
target = append(target, 0xFF)
}
return target
}
// append44 appends bytes using the 4-4 encoded format used for volume, track, sector, checksum.
func append44(target []byte, b byte) []byte {
return append(target, 0xAA|b<<1, 0xAA|b)
}
// appendAddress appends the encoded sector address to the slice, and returns the resulting slice.
func appendAddress(target []byte, t, s, v byte) []byte {
target = append(target, 0xD5, 0xAA, 0x96)
target = append44(target, v)
target = append44(target, t)
target = append44(target, s)
target = append44(target, v^t^s)
target = append(target, 0xDE, 0xAA, 0xEB)
return target
}
// appendData appends the encoded sector data to the slice, and returns the resulting slice.
func appendData(target, source []byte) []byte {
target = append(target, 0xD5, 0xAA, 0xAD)
nybbles := PreNybble(source)
checksum := byte(0)
for i := 341; i >= 256; i-- {
target = append(target, Dos33writeTable[nybbles[i]^checksum])
checksum = nybbles[i]
}
for i := 0; i < 256; i++ {
target = append(target, Dos33writeTable[nybbles[i]^checksum])
checksum = nybbles[i]
}
target = append(target, Dos33writeTable[checksum])
target = append(target, 0xDE, 0xAA, 0xEB)
return target
}
// dosToNybbleSector appends a single 256-byte DOS sector in .nyb format, and returns the slice.
func dosToNybbleSector(target, source []byte, t, s, v byte, preSync, intraSync int) []byte {
if len(source) != 256 {
panic(fmt.Sprintf("dosToNybbleSector expects 256 bytes, got %d", len(source)))
}
target = appendSyncs(target, preSync)
target = appendAddress(target, t, s, v)
target = appendSyncs(target, intraSync)
target = appendData(target, source)
return target
}
// dos16ToNybbleTrack converts a 16-sector dos image of a track to a nybblized track.
func dos16ToNybbleTrack(source []byte, t, v byte, preSync, intraSync int, sectorOrder []byte) []byte {
trackSync := NYBBLE_TRACK_BYTES - 16*(preSync+NYBBLE_ADDRESS_BYTES+intraSync+NYBBLE_DATA_BYTES)
target := make([]byte, 0, NYBBLE_TRACK_BYTES)
target = appendSyncs(target, trackSync)
for s := byte(0); s < 16; s++ {
start := 256 * int(sectorOrder[s])
target = dosToNybbleSector(target, source[start:start+256], t, s, v, preSync, intraSync)
}
if len(target) != NYBBLE_TRACK_BYTES {
panic(fmt.Sprintf("Tracks should be %d bytes, go %d", NYBBLE_TRACK_BYTES, len(target)))
}
return target
}
func dos16ToNybbleTracks(source []byte, v byte, preSync, intraSync int, sectorOrder []byte) (tracks [][]byte, err error) {
if preSync+intraSync > MAX_PRE_INTRA_SYNC {
return nil, fmt.Errorf("preSync(%d) + intraSync(%d) cannot be more than %d bytes",
preSync, intraSync, MAX_PRE_INTRA_SYNC)
}
for t := byte(0); t < 35; t++ {
start := DOS_TRACK_BYTES * int(t)
trackBytes := source[start : start+DOS_TRACK_BYTES]
track := dos16ToNybbleTrack(trackBytes, t, v, preSync, intraSync, sectorOrder)
tracks = append(tracks, track)
}
return tracks, nil
}

14
disk/disk.go Normal file
View File

@ -0,0 +1,14 @@
package disk
const (
DOS_DISK_BYTES = 143360 // 35 tracks * 16 sectors * 256 bytes
DOS_TRACK_BYTES = DOS_DISK_BYTES / 35
NYBBLE_DISK_BYTES = 232960
NYBBLE_TRACK_BYTES = NYBBLE_DISK_BYTES / 35
NYBBLE_ADDRESS_BYTES = 14
NYBBLE_DATA_BYTES = 349
DEFAULT_VOLUME = 254
DEFAULT_PRESYNC = 20
DEFAULT_INTRASYNC = 8
MAX_PRE_INTRA_SYNC = (NYBBLE_TRACK_BYTES / 16) - NYBBLE_ADDRESS_BYTES - NYBBLE_DATA_BYTES
)

32
disk/disk_test.go Normal file
View File

@ -0,0 +1,32 @@
package disk
import (
"testing"
)
func TestPreNybble(t *testing.T) {
var source [256]byte
for i := range source {
source[i] = byte(i)
}
target := PreNybble(source[:])
for i := range target {
if target[i] > 0x3F {
t.Errorf("target[%d] too large: 0x%02X", i, target[i])
}
}
}
func TestPrePostNybble(t *testing.T) {
var source [256]byte
for i := range source {
source[i] = byte(i)
}
target := PreNybble(source[:])
sourceCheck := PostNybble(target[:])
for i := range source {
if source[i] != sourceCheck[i] {
t.Errorf("source, sourceCheck differ at %d: 0x%02X != 0x%02X", i, source[i], sourceCheck[i])
}
}
}

35
disk/dummy.go Normal file
View File

@ -0,0 +1,35 @@
package disk
type Dummy byte
func (v Dummy) Read() byte {
return 0xFF
}
func (v Dummy) Write(b byte) {
// pass
}
func (v Dummy) SetHalfTrack(t byte) {
// pass
}
func (v Dummy) HalfTrack() byte {
return 0
}
func (v Dummy) SetVolume(byte) {
// pass
}
func (v Dummy) Volume() byte {
return byte(v)
}
func (v Dummy) Writeable() bool {
return false
}
func NewDummy(v byte) Dummy {
return Dummy(v)
}

84
disk/nybble.go Normal file
View File

@ -0,0 +1,84 @@
package disk
import (
"fmt"
"io/ioutil"
"strings"
)
type Nybble struct {
Tracks [][]byte
volume byte
halfTrack byte
position int
writeable bool
}
func NewNybble() *Nybble {
nd := Nybble{
volume: DEFAULT_VOLUME,
}
return &nd
}
func (disk *Nybble) LoadDosDisk(filename string) error {
var sectorOrder []byte
switch {
case strings.HasSuffix(filename, ".dsk"):
sectorOrder = Dos33PhysicalToLogicalSectorMap
case strings.HasSuffix(filename, ".do"):
sectorOrder = Dos33PhysicalToLogicalSectorMap
case strings.HasSuffix(filename, ".po"):
sectorOrder = ProDosPhysicalToLogicalSectorMap
default:
return fmt.Errorf("Unknown suffix (not .dsk, .do, or .po): %s", filename)
}
bytes, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
if len(bytes) != DOS_DISK_BYTES {
return fmt.Errorf("Disk images should be %d bytes, got %d: %s", DOS_DISK_BYTES, len(bytes), filename)
}
tracks, err := dos16ToNybbleTracks(bytes, disk.Volume(), DEFAULT_PRESYNC, DEFAULT_INTRASYNC, sectorOrder)
if err != nil {
return err
}
disk.Tracks = tracks
return nil
}
func (disk *Nybble) Read() byte {
track := disk.Tracks[disk.halfTrack/2]
disk.position = (disk.position + 1) % len(track)
return track[disk.position]
}
func (disk *Nybble) Write(b byte) {
track := disk.Tracks[disk.halfTrack/2]
disk.position = (disk.position + 1) % len(track)
if disk.writeable {
track[disk.position] = b
}
}
func (disk *Nybble) SetHalfTrack(halfTrack byte) {
disk.halfTrack = halfTrack
}
func (disk *Nybble) HalfTrack() byte {
return disk.halfTrack
}
func (disk *Nybble) SetVolume(volume byte) {
disk.volume = volume
}
func (disk *Nybble) Volume() byte {
return disk.volume
}
func (disk *Nybble) Writeable() bool {
return disk.writeable
}

View File

@ -485,3 +485,183 @@ F8 jumper not jumpered: never use F8 ROM on card: use motherboard.
* Questions
** Color
Can a previously rendered dot change colors? Gray?
** Disk
I thought ProDOS only allowed a read syncing leader from $10 to
$1A. But the nybblized disk format seems to have 48 spare bytes for
each sector.
(- 416 14 5 6 1 342) 48
* Disk formats
** dsk/do - DOS 3.3 order
35 tracks * 16 sectors * 256 bytes = 143,360
** po - ProDOS order
** nib
232,960 bytes
(* 35 16 342)
191,520
(* 35 16 (+ 342 74))
232,960 bytes
Extra bytes per track:
1184 (74 PER SECTOR)
https://code.google.com/p/twoapple/source/browse/src/peripheral/diskii.d
ADDR: 13
PROLOGUE: 3 D5,AA,96
VOLUME: 2
TRACK: 2
SECTOR: 2
CHECK: 2
EPILOGUE: 2 DE,AA
DATAFIELD: 349
PROLOGUE: 3 D5,AA,AD
DATA: 343
EPILOGUE: 3 DE,AA,EB
SECTOR:
ADDR: 13
GAP2: 6
DATAFIELD: 349
GAP3: 45
(+ 13 6 349 45)
413
# 48 byte gap, 413 per sector: (+ 48 (* 16 413)) 6656
http://sourceforge.net/p/java-ace/code/285/tree/jace/src/jace/hardware/FloppyDisk.java
- 15 junk bytes
- Address block: 14
- D5,AA,96 - 3
- Volume - 2
- Track - 2
- Sector - 2
- Checksum - 2
- DE,AA,EB - 3
- 4 junk bytes
- Data block 349
- D5,AA,AD 3
- Data: 342
- Checksum: 1
- DE,AA,EB - 3
- 34 junk bytes
(+ 15 14 4 349 34)
416
Writing out:
http://sourceforge.net/p/java-ace/code/285/tree/jace/src/jace/hardware/FloppyDisk.java#l260
- Look for D5,AA,96
- Verify track
- Read sector
- Look for DE,AA
- Look for D5,AA,AD
-
- Look for DE,AA,EB
http://mrob.com/apple2/extract.txt
https://code.google.com/p/openemulator/source/browse/trunk/libdiskimage/DIApple525DiskStorage.cpp#590
- 128 sync bytes for track 0, 20 for others.
- address field
- (+ 3 8 3) 14
- 8 sync bytes
- data field
- (+ 3 86 256 1 3) 349
(+ 108 (* 20 16) (* 16 (+ 14 8 349)))
6364
6656
* Listings
* RWTS listing
http://www.txbobsc.com/aal/1981/aal8109.html#a5
* DOS 3.3 boot sector
http://www.txbobsc.com/aal/1981/aal8108.html#a9
* Volume numbers
DOS 3.3: Default = 254
Prodos: not used. [[https://groups.google.com/d/msg/comp.sys.apple2/YjoOR7gza1w/-2-HcNLLucwJ][1?]]
* Sector orders
** DOS 3.3
[[ftp://ftp.apple.asimov.net/pub/apple_II/documentation/source_code/Apple2DOS33CSourceListing.pdf][Logical to physical sector mapping source]]
Logical to physical
0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
0,D,B,9,7,5,3,1,E,C,A,8,6,4,2,F
Physical to logical:
0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
0,7,E,6,D,5,C,4,B,3,A,2,9,1,8,F
When writing nybblized track 1, we write track 1 in address header, but read from image track sector 7.
logical 4 <==> physical 7
** Prodos
[[http://sourceforge.net/p/java-ace/code/285/tree/jace/src/jace/hardware/FloppyDisk.java#l40][Physical to logical - JACE]]
Logical to Physical:
0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
0,2,4,6,8,A,C,E,1,3,5,7,9,B,D,F
Physical to logical:
0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
0,8,1,9,2,A,3,B,4,C,5,D,6,E,7,F
* Formatting...
_Prodos note_: tolerance of read syncing leader is $10 to $1A, so drive
speed must be close to 300rpm.
Syncing leader: 5-40 FFs
Address
Gap 50
Gap 53
Syncing leader: 5 FFs
Guess 40 FFs.
Write 128 FF40's before Sector 0, 40 FF40s before the others.
Write sectors in order (physical sectors).
Sync count reduced by 2s until it reaches 16, then by ones until it reaches 5.
If track 0 has >15 sync count, the rest of the tracks will have (sync count - 2).
* Byte counts
| | Jace | Twoapple | OpenEmulator-Byte | OpenEmulator-Cycle |
|-------------+------------------------+---------------------+----------------------+-----------------------------|
| Pre-track-0 | | 48 | 108 | (* 108 40) 4320 |
| Pre-sync | 15 | | 20 | (+ (* 19 40) 32) 792 |
| Address | 14 | 13 | 14 | (+ (* 13 32) 16) 432 |
| Intra-sync | 4 | 6 | 8 | (+ (* 7 40) 36) 316 |
| Data | 349 | 349 | (+ 3 86 256 3 1) 349 | (* 349 32) 11168 |
| Post-sync | 34 | 45 | 0 | 0 |
| | (+ 15 14 4 349 34) 416 | (+ 13 6 349 45) 413 | (+ 20 14 8 349) 391 | (+ 792 432 316 11168) 12708 |
| | (* 416 16) 6656 | (* 413 16) 6608 | (* 16 391) 6256 | (* 16 12708) 203328 |
| | 6656 | (+ 6608 48) 6656 | (+ 6256 108) 6364 | (+ 203328 4320) 207648 |
| | | | | (/ 207648 4) 51912 |
tracksize 51024
(/ 51024 8)
6378

View File

@ -56,6 +56,7 @@ func (a2 *Apple2) handleCardRom(address uint16, value byte, write bool) byte {
}
func (a2 *Apple2) handleC00X(address uint16, value byte, write bool) byte {
if address < 0xC080 {
switch address & 0xC0F0 {
// $C00X: Read keyboard
case 0xC000:
@ -90,6 +91,7 @@ func (a2 *Apple2) handleC00X(address uint16, value byte, write bool) byte {
case 0xC057: // HIRES
a2.scanner.SetHires(true)
}
}
if address < 0xC080 {
return a2.EmptyRead()

View File

@ -13,6 +13,7 @@ import (
"github.com/0xe2-0x9a-0x9b/Go-SDL/sdl"
"github.com/zellyn/goapple2"
"github.com/zellyn/goapple2/cards"
"github.com/zellyn/goapple2/disk"
"github.com/zellyn/goapple2/util"
"github.com/zellyn/goapple2/videoscan"
)
@ -33,12 +34,10 @@ const (
func plot(x, y uint, color uint32, screen *sdl.Surface) {
x = x + BORDER_W
y = y + BORDER_H
screen.Lock()
pixels := uintptr(screen.Pixels)
offset := uintptr(y*uint(screen.Pitch) + x*(SCREEN_BPP/8))
addr := pixels + offset
*(*uint32)(unsafe.Pointer(addr)) = color
screen.Unlock()
}
func Init() (screen *sdl.Surface, err error) {
@ -255,7 +254,9 @@ func (s SdlPlotter) Plot(pd videoscan.PlotData) {
}
func (s SdlPlotter) OncePerFrame() {
s.screen.Unlock()
s.screen.Flip()
s.screen.Lock()
s.oncePerFrame()
}
@ -305,6 +306,21 @@ func RunEmulator() {
log.Fatal(err)
}
diskCardRom := util.ReadRomOrDie("../data/roms/Apple Disk II 16 Sector Interface Card ROM P5 - 341-0027.bin")
diskCard, err := cards.NewDiskCard(diskCardRom, 6, a2)
if err != nil {
panic(err)
}
if err := a2.AddCard(diskCard); err != nil {
log.Fatal(err)
}
disk1 := disk.NewNybble()
if err = disk1.LoadDosDisk("../data/disks/chivalry.dsk"); err != nil {
log.Fatal(err)
}
disk1.SetHalfTrack(50)
diskCard.LoadDisk(disk1, 0)
steps := *steplimit
if *cpuprofile != "" {