Ocean-240.2-Emulator/okean240/fdc/fdc.go

427 lines
9.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package fdc
/**
* Floppy drive controller, based on
* MB8877, К1818ВГ93
*
* By Romych, 2025.03.05
*/
import (
"bytes"
"encoding/binary"
"okemu/config"
"os"
"slices"
"strconv"
log "github.com/sirupsen/logrus"
)
// Floppy parameters
const (
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 byte = 0x0
CmdSeek byte = 0x1
CmdStep byte = 0x2
CmdStepIn byte = 0x5
CmdStepOut byte = 0x7
CmdReadSector byte = 0x8
CmdReadSectorMulti byte = 0x9
CmdWriteSector byte = 0xa
CmdWriteTrack byte = 0xf
CmdNoCommand byte = 0xff
)
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
floppyBFile string
floppyCFile string
}
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()
GetSectorNo() uint16
Track() byte
Sector() byte
}
func getSectorNo(side byte, track byte, sector byte) uint16 {
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
}
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 CmdWriteTrack:
log.Tracef("CMD Write Track %x", f.trackNo)
f.status = 0x00
f.trackBuffer = []byte{}
f.drq = 1
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.Error("Track not found!")
} 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:
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() {
loadFloppy(&f.sectors[0], f.floppyBFile)
loadFloppy(&f.sectors[1], f.floppyCFile)
}
func (f *FloppyDriveController) SaveFloppy() {
saveFloppy(&f.sectors[0], f.floppyBFile)
saveFloppy(&f.sectors[1], f.floppyCFile)
}
func New(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,
floppyBFile: conf.FloppyB,
floppyCFile: conf.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) {
log.Debugf("Load Floppy content from file %s.", fileName)
file, err := os.Open(fileName)
if err != nil {
log.Error(err)
return
}
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)
break
}
}
}
func saveFloppy(sectors *[SizeInSectors]SectorType, fileName string) {
log.Debugf("Save Floppy to file %s.", fileName)
file, err := os.Create(fileName)
if err != nil {
log.Error(err)
return
}
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)
break
}
}
}