mirror of
https://github.com/romychs/Ocean-240.2-Emulator.git
synced 2026-04-21 11:03:21 +03:00
460 lines
11 KiB
Go
460 lines
11 KiB
Go
package fdc
|
||
|
||
/**
|
||
* Floppy drive controller, based on
|
||
* MB8877, К1818ВГ93
|
||
*
|
||
* By Romych, 2025.03.05
|
||
*/
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/binary"
|
||
"errors"
|
||
"okemu/config"
|
||
"os"
|
||
"slices"
|
||
"strconv"
|
||
|
||
"github.com/howeyc/crc16"
|
||
log "github.com/sirupsen/logrus"
|
||
)
|
||
|
||
// Floppy parameters
|
||
const (
|
||
FloppyB = 0
|
||
FloppyC = 1
|
||
|
||
TotalDrives = 2
|
||
FloppySizeK = 720
|
||
SectorSize = 512
|
||
SideCount = 2
|
||
SectorPerTrack = 9
|
||
|
||
SizeInSectors = FloppySizeK * 1024 / SectorSize
|
||
TracksCount = SizeInSectors / SideCount / SectorPerTrack
|
||
SectorsPerSide = SizeInSectors / SideCount
|
||
|
||
TrackHeaderSize = 146
|
||
TrackSectorSize = 626
|
||
TrackFooterSize = 256 * 3
|
||
TrackBufferSize = TrackHeaderSize + TrackSectorSize*SectorPerTrack + TrackFooterSize
|
||
)
|
||
|
||
// FDC Commands
|
||
const (
|
||
CmdRestore = 0x0
|
||
CmdSeek = 0x1
|
||
CmdStep = 0x2
|
||
CmdStepIn = 0x5
|
||
CmdStepOut = 0x7
|
||
CmdReadSector = 0x8
|
||
CmdReadSectorMulti = 0x9
|
||
CmdWriteSector = 0xa
|
||
CmdReadAddress = 0xc
|
||
CmdWriteTrack = 0xf
|
||
CmdNoCommand = 0xff
|
||
)
|
||
|
||
var interleave = []byte{1, 8, 6, 4, 2, 9, 7, 5, 3}
|
||
|
||
const (
|
||
StatusTR0 = 0x04 // TR0 - Head at track 0
|
||
StatusRNF = 0x10 // RNF - Record not found
|
||
// StatusSeekError = 0x10 // Sector out of disk
|
||
StatusHeadLoaded = 0x20 // Head on disk
|
||
)
|
||
|
||
type SectorType []byte
|
||
|
||
type FloppyDriveController struct {
|
||
// Floppy controller port
|
||
sideNo byte
|
||
ddEn byte
|
||
init byte
|
||
drive byte
|
||
mot1 byte
|
||
mot0 byte
|
||
intRq byte
|
||
motSt byte
|
||
sectorNo byte
|
||
trackNo byte
|
||
drq byte
|
||
// FloppyStorage B and C
|
||
sectors [TotalDrives][SizeInSectors]SectorType
|
||
data byte
|
||
status byte
|
||
lastCmd byte
|
||
//curSector *SectorType
|
||
bytePtr uint16
|
||
trackBuffer []byte
|
||
floppyFile []string
|
||
config *config.OkEmuConfig
|
||
}
|
||
|
||
type FloppyDriveControllerInterface interface {
|
||
SetFloppy()
|
||
Floppy() byte
|
||
SetCmd(value byte)
|
||
Status() byte
|
||
SetTrack(value byte)
|
||
SetSector(value byte)
|
||
SetData(value byte)
|
||
Data() byte
|
||
Drq() byte
|
||
SaveFloppy(drive byte)
|
||
GetSectorNo() uint16
|
||
Track() byte
|
||
Sector() byte
|
||
}
|
||
|
||
//var slicer = []uint16{1, 8, 6, 4, 2, 9, 7, 5, 3}
|
||
|
||
func getSectorNo(side byte, track byte, sector byte) uint16 {
|
||
//return (uint16(track)*18 + uint16(side)*9 + slicer[sector-1] - 1)
|
||
return uint16(side)*SectorsPerSide + uint16(track)*SectorPerTrack + uint16(sector) - 1
|
||
}
|
||
|
||
func (f *FloppyDriveController) GetSectorNo() uint16 {
|
||
return getSectorNo(f.sideNo, f.trackNo, f.sectorNo)
|
||
}
|
||
|
||
func (f *FloppyDriveController) SetFloppy(val byte) {
|
||
// WR: 5-SSEL, 4-#DDEN, 3-INIT, 2-DRSEL, 1-MOT1, 0-MOT0
|
||
f.sideNo = val >> 5 & 0x01
|
||
f.ddEn = val >> 4 & 0x01
|
||
f.init = val >> 3 & 0x01
|
||
f.drive = (^val) >> 2 & 0x01
|
||
f.mot1 = val >> 1 & 0x01
|
||
f.mot0 = val & 0x01
|
||
}
|
||
|
||
func (f *FloppyDriveController) GetFloppy() byte {
|
||
// RD: 7-MOTST, 6-SSEL, 5,4-x , 3-DRSEL, 2-MOT1, 1-MOT0, 0-INT
|
||
floppy := f.intRq | (f.mot0 << 1) | (f.mot1 << 2) | ((^f.drive & 1) << 3) | (f.sideNo << 6) | (f.motSt << 7)
|
||
return floppy
|
||
}
|
||
|
||
var crcTable *crc16.Table
|
||
|
||
func init() {
|
||
crcTable = crc16.MakeTable(0xffff)
|
||
}
|
||
|
||
func (f *FloppyDriveController) SetCmd(value byte) {
|
||
f.lastCmd = value >> 4
|
||
switch f.lastCmd {
|
||
case CmdRestore:
|
||
log.Trace("CMD Restore (seek trackNo 0)")
|
||
f.trackNo = 0
|
||
f.status = StatusTR0 | StatusHeadLoaded // TR0 & Head loaded
|
||
case CmdSeek:
|
||
log.Tracef("CMD Seek %x", value&0xf)
|
||
f.status = StatusHeadLoaded
|
||
f.trackNo = f.data
|
||
case CmdStep:
|
||
log.Tracef("CMD Step %x", value&0xf)
|
||
f.status = StatusHeadLoaded
|
||
f.trackNo = f.data
|
||
case CmdStepIn:
|
||
log.Tracef("CMD StepIn (Next track) %x", value&0xf)
|
||
f.status = StatusHeadLoaded
|
||
if f.trackNo < TracksCount {
|
||
f.trackNo++
|
||
}
|
||
case CmdStepOut:
|
||
log.Tracef("CMD StepOut (Previous track) %x", value&0xf)
|
||
f.status = StatusHeadLoaded
|
||
if f.trackNo > 0 {
|
||
f.trackNo--
|
||
}
|
||
case CmdReadSector:
|
||
sectorNo := f.GetSectorNo()
|
||
log.Tracef("CMD Read single sectorNo: %d", sectorNo)
|
||
if sectorNo < SizeInSectors {
|
||
f.trackBuffer = slices.Clone(f.sectors[f.drive][sectorNo])
|
||
f.drq = 1
|
||
f.status = 0x00
|
||
} else {
|
||
f.drq = 0
|
||
f.status = StatusRNF
|
||
}
|
||
case CmdReadSectorMulti:
|
||
sectorNo := f.GetSectorNo()
|
||
f.trackBuffer = []byte{}
|
||
for c := 0; c < SectorPerTrack; c++ {
|
||
f.trackBuffer = slices.Concat(f.trackBuffer, f.sectors[f.drive][sectorNo])
|
||
sectorNo++
|
||
}
|
||
f.drq = 1
|
||
f.status = 0x0
|
||
case CmdWriteSector:
|
||
sectorNo := f.GetSectorNo()
|
||
log.Tracef("CMD Write Sector %d", sectorNo)
|
||
if sectorNo < SizeInSectors {
|
||
f.bytePtr = 0
|
||
f.drq = 1
|
||
f.status = 0x0
|
||
f.trackBuffer = []byte{}
|
||
} else {
|
||
f.drq = 0
|
||
f.status = StatusRNF
|
||
}
|
||
case CmdReadAddress:
|
||
log.Tracef("CMD ReadAddress %d", value)
|
||
f.trackBuffer = []byte{f.trackNo, f.sideNo, f.sectorNo, 2}
|
||
|
||
checksum := crc16.Checksum(f.trackBuffer, crcTable)
|
||
f.trackBuffer = append(f.trackBuffer, byte(checksum))
|
||
f.trackBuffer = append(f.trackBuffer, byte(checksum>>8))
|
||
|
||
f.drq = 1
|
||
f.status = 0x0
|
||
case CmdWriteTrack:
|
||
log.Tracef("CMD Write Track %x", f.trackNo)
|
||
f.drq = 1
|
||
f.status = 0x00
|
||
f.trackBuffer = []byte{}
|
||
default:
|
||
log.Errorf("Unknown CMD: %x VAL: %x", f.lastCmd, value&0xf)
|
||
}
|
||
}
|
||
|
||
func (f *FloppyDriveController) Status() byte {
|
||
return f.status
|
||
}
|
||
|
||
func (f *FloppyDriveController) SetTrackNo(value byte) {
|
||
//log.Tracef("FDC Track: %d", value)
|
||
if value > TracksCount {
|
||
f.status |= 0x10 /// RNF
|
||
log.Errorf("Track %d not found!", value)
|
||
} else {
|
||
f.trackNo = value
|
||
}
|
||
}
|
||
|
||
func (f *FloppyDriveController) SetSectorNo(value byte) {
|
||
//log.Tracef("FDC Sector: %d", value)
|
||
if value > SectorPerTrack {
|
||
f.status |= 0x10
|
||
log.Errorf("Record not found %d!", value)
|
||
} else {
|
||
f.sectorNo = value
|
||
}
|
||
}
|
||
|
||
func (f *FloppyDriveController) SetData(value byte) {
|
||
//log.Tracef("FCD Data: %d", value)
|
||
if f.lastCmd == CmdWriteTrack {
|
||
if len(f.trackBuffer) < TrackBufferSize {
|
||
f.trackBuffer = append(f.trackBuffer, value)
|
||
f.drq = 1
|
||
f.status = 0x00
|
||
} else {
|
||
//f.dump()
|
||
f.writeTrack()
|
||
f.drq = 0
|
||
f.status = 0x00
|
||
f.lastCmd = CmdNoCommand
|
||
}
|
||
} else if f.lastCmd == CmdWriteSector {
|
||
if len(f.trackBuffer) < SectorSize {
|
||
f.trackBuffer = append(f.trackBuffer, value)
|
||
if len(f.trackBuffer) == SectorSize {
|
||
f.drq = 0
|
||
} else {
|
||
f.drq = 1
|
||
}
|
||
}
|
||
if len(f.trackBuffer) == SectorSize {
|
||
f.drq = 0
|
||
f.sectors[f.drive][f.GetSectorNo()] = slices.Clone(f.trackBuffer)
|
||
f.lastCmd = CmdNoCommand
|
||
}
|
||
}
|
||
f.data = value
|
||
}
|
||
|
||
const SectorInfoSize = 626
|
||
const SectorInfoOffset = 0x0092
|
||
const TrackNoOffset = 0x0010
|
||
const SideNoOffset = 0x0011
|
||
const SectorNoOffset = 0x0012
|
||
const SectorLengthOffset = 0x0013
|
||
const SectorDataOffset = 0x003b
|
||
|
||
var SectorLengths = []int{128, 256, 512, 1024}
|
||
|
||
func (f *FloppyDriveController) writeTrack() {
|
||
// skip header
|
||
ptr := SectorInfoOffset
|
||
// repeat for every sector on track
|
||
for sec := 0; sec < SectorPerTrack; sec++ {
|
||
// get info from header
|
||
trackNo := f.trackBuffer[ptr+TrackNoOffset]
|
||
sideNo := f.trackBuffer[ptr+SideNoOffset]
|
||
sectorNo := f.trackBuffer[ptr+SectorNoOffset]
|
||
sectorLength := SectorLengths[f.trackBuffer[ptr+SectorLengthOffset]&0x03]
|
||
// get sector data
|
||
sectorData := f.trackBuffer[ptr+SectorDataOffset : ptr+SectorDataOffset+sectorLength]
|
||
absSector := getSectorNo(sideNo, trackNo, sectorNo)
|
||
log.Debugf("Write Drive: %d; side:%d; T: %d S: %d Len: %d Data: [%X..%X]; Abs sector: %d", f.drive, sideNo, trackNo, sectorNo, len(sectorData), sectorData[0], sectorData[len(sectorData)-1], absSector)
|
||
// write data to sector buffer
|
||
f.sectors[f.drive][absSector] = slices.Clone(sectorData)
|
||
// shift pointer to next sector info block
|
||
ptr += SectorInfoSize
|
||
}
|
||
}
|
||
|
||
func (f *FloppyDriveController) Data() byte {
|
||
switch f.lastCmd {
|
||
case CmdReadSector, CmdReadSectorMulti, CmdReadAddress:
|
||
if len(f.trackBuffer) > 0 {
|
||
f.drq = 1
|
||
f.data = f.trackBuffer[0]
|
||
f.trackBuffer = f.trackBuffer[1:]
|
||
}
|
||
if len(f.trackBuffer) == 0 {
|
||
f.drq = 0
|
||
f.status = 0
|
||
f.lastCmd = CmdNoCommand
|
||
}
|
||
default:
|
||
f.data = 0xff
|
||
}
|
||
return f.data
|
||
}
|
||
|
||
func (f *FloppyDriveController) Drq() byte {
|
||
return f.drq
|
||
}
|
||
|
||
func (f *FloppyDriveController) LoadFloppy(drive byte) error {
|
||
if drive < TotalDrives {
|
||
return loadFloppy(&f.sectors[drive], f.floppyFile[drive])
|
||
}
|
||
return errors.New("DriveNo " + strconv.Itoa(int(drive)) + " out of range")
|
||
}
|
||
|
||
func (f *FloppyDriveController) SaveFloppy(drive byte) error {
|
||
if drive < TotalDrives {
|
||
return saveFloppy(&f.sectors[drive], f.floppyFile[drive])
|
||
}
|
||
return errors.New("DriveNo " + strconv.Itoa(int(drive)) + " out of range")
|
||
}
|
||
|
||
func NewFDC(conf *config.OkEmuConfig) *FloppyDriveController {
|
||
sec := [2][SizeInSectors]SectorType{}
|
||
// for each drive
|
||
for d := 0; d < TotalDrives; d++ {
|
||
// for each sector
|
||
for i := 0; i < SizeInSectors; i++ {
|
||
sec[d][i] = bytes.Repeat([]byte{0xe5}, SectorSize)
|
||
}
|
||
}
|
||
return &FloppyDriveController{
|
||
sideNo: 0,
|
||
ddEn: 0,
|
||
init: 0,
|
||
drive: 0,
|
||
mot1: 0,
|
||
mot0: 0,
|
||
intRq: 0,
|
||
motSt: 0,
|
||
drq: 0,
|
||
lastCmd: 0xff,
|
||
sectors: sec,
|
||
bytePtr: 0xffff,
|
||
floppyFile: []string{conf.FDC.FloppyB, conf.FDC.FloppyC},
|
||
}
|
||
}
|
||
|
||
func (f *FloppyDriveController) dump() {
|
||
log.Trace("Dump Buffer content.")
|
||
file, err := os.Create("track-" + strconv.Itoa(int(f.trackNo)) + ".dat")
|
||
if err != nil {
|
||
log.Error(err)
|
||
return
|
||
}
|
||
defer func(file *os.File) {
|
||
err := file.Close()
|
||
if err != nil {
|
||
log.Error(err)
|
||
}
|
||
}(file)
|
||
|
||
err = binary.Write(file, binary.LittleEndian, f.trackBuffer)
|
||
if err != nil {
|
||
log.Error("Save track content failed:", err)
|
||
}
|
||
|
||
}
|
||
|
||
func (f *FloppyDriveController) Track() byte {
|
||
return f.trackNo
|
||
}
|
||
|
||
func (f *FloppyDriveController) Sector() byte {
|
||
return f.sectorNo
|
||
}
|
||
|
||
// loadFloppy load floppy image to sector buffer from file
|
||
func loadFloppy(sectors *[SizeInSectors]SectorType, fileName string) error {
|
||
log.Debugf("Load Floppy content from file %s.", fileName)
|
||
file, err := os.Open(fileName)
|
||
if err != nil {
|
||
log.Error(err)
|
||
return err
|
||
}
|
||
|
||
defer func(file *os.File) {
|
||
err := file.Close()
|
||
if err != nil {
|
||
log.Error(err)
|
||
}
|
||
}(file)
|
||
|
||
for sector := 0; sector < SizeInSectors; sector++ {
|
||
var n int
|
||
n, err = file.Read(sectors[sector])
|
||
if n != SectorSize {
|
||
log.Error("Load floppy error, sector size: %d <> %d", n, SectorSize)
|
||
}
|
||
if err != nil {
|
||
log.Error("Load floppy content failed:", err)
|
||
return err
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// saveFloppy Save specified sectors to file with name fileName
|
||
func saveFloppy(sectors *[SizeInSectors]SectorType, fileName string) error {
|
||
log.Debugf("Save Floppy to file %s.", fileName)
|
||
file, err := os.Create(fileName)
|
||
if err != nil {
|
||
log.Error(err)
|
||
return err
|
||
}
|
||
defer func(file *os.File) {
|
||
err := file.Close()
|
||
if err != nil {
|
||
log.Error(err)
|
||
}
|
||
}(file)
|
||
|
||
for sector := 0; sector < SizeInSectors; sector++ {
|
||
var n int
|
||
n, err = file.Write(sectors[sector])
|
||
if n != SectorSize {
|
||
log.Errorf("Save floppy error, sector %d size: %d <> %d", sector, n, SectorSize)
|
||
}
|
||
if err != nil {
|
||
log.Error("Save floppy content failed:", err)
|
||
return err
|
||
}
|
||
}
|
||
return nil
|
||
}
|