Ocean-240.2-Emulator/okean240/fdc/fdc.go
2026-03-06 22:31:52 +03:00

378 lines
7.9 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 (
"encoding/binary"
"os"
"slices"
"strconv"
log "github.com/sirupsen/logrus"
)
// Floppy parameters
const (
FloppySizeK = 720
SectorSize = 128
SideCount = 2
SectorPerTrack = 36
SizeInSectors = FloppySizeK * 1024 / SectorSize
TracksCount = SizeInSectors / SideCount / SectorPerTrack
SectorsPerSide = SizeInSectors / SideCount
TrackHeaderSize = 146
TrackSectorSize = 626
TrackFooterSize = 256 * 3
TrackBufferSize = TrackHeaderSize + TrackSectorSize*9 + 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
sectors [SizeInSectors]SectorType
data byte
status byte
lastCmd byte
//curSector *SectorType
bytePtr uint16
trackBuffer []byte
}
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 (f *FloppyDriveController) GetSectorNo() uint16 {
return uint16(f.sideNo)*SectorsPerSide + uint16(f.trackNo)*SectorPerTrack + uint16(f.sectorNo) - 1
}
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 << 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.Debug("CMD Restore (seek trackNo 0)")
f.trackNo = 0
f.status = StatusTR0 | StatusHeadLoaded // TR0 & Head loaded
case CmdSeek:
log.Debugf("CMD Seek %x", value&0xf)
f.status = StatusHeadLoaded
f.trackNo = f.data
case CmdStep:
log.Debugf("CMD Step %x", value&0xf)
f.status = StatusHeadLoaded
f.trackNo = f.data
case CmdStepIn:
log.Debugf("CMD StepIn (Next track) %x", value&0xf)
f.status = StatusHeadLoaded
if f.trackNo < TracksCount {
f.trackNo++
}
case CmdStepOut:
log.Debugf("CMD StepOut (Previous track) %x", value&0xf)
f.status = StatusHeadLoaded
if f.trackNo > 0 {
f.trackNo--
}
case CmdReadSector:
sectorNo := f.GetSectorNo()
log.Debugf("CMD Read single sectorNo: %d", sectorNo)
if sectorNo < SizeInSectors {
f.trackBuffer = slices.Clone(f.sectors[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[sectorNo])
sectorNo++
}
f.drq = 1
f.status = 0x0
case CmdWriteSector:
sectorNo := f.GetSectorNo()
log.Debugf("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.Debugf("CMD Write Track %x", f.trackNo)
f.status = 0x00
f.trackBuffer = []byte{}
f.drq = 1
default:
log.Debugf("Unknown CMD: %x VAL: %x", f.lastCmd, value&0xf)
}
}
func (f *FloppyDriveController) Status() byte {
return f.status
}
func (f *FloppyDriveController) SetTrackNo(value byte) {
//log.Debugf("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.Debugf("FDC Sector: %d", value)
if value > SectorPerTrack {
f.status |= 0x10
log.Error("Record not found!")
} else {
f.sectorNo = value
}
}
func (f *FloppyDriveController) SetData(value byte) {
//log.Debugf("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.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.GetSectorNo()] = slices.Clone(f.trackBuffer)
f.lastCmd = CmdNoCommand
}
}
f.data = value
}
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() {
log.Debug("Load Floppy content.")
file, err := os.Open("floppy.okd")
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(f.sectors[sector])
if n != SectorSize {
log.Error("Load floppy error, sector size: %d <> %d", n, SectorSize)
}
// err = binary.Read(file, binary.LittleEndian, f.sectors[sector])
if err != nil {
log.Error("Load floppy content failed:", err)
break
}
}
}
func (f *FloppyDriveController) SaveFloppy() {
log.Debug("Save Floppy content.")
file, err := os.Create("floppy.okd")
if err != nil {
log.Error(err)
return
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
log.Error(err)
}
}(file)
// Write the struct to the file in little-endian byte order
for sector := 0; sector < SizeInSectors; sector++ {
var n int
n, err = file.Write(f.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
}
}
}
func New() *FloppyDriveController {
sec := [SizeInSectors]SectorType{}
for i := 0; i < SizeInSectors; i++ {
sec[i] = make([]byte, SectorSize)
for s := 0; s < 128; s++ {
sec[i][s] = 0xE5
}
}
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,
}
}
func (f *FloppyDriveController) dump() {
log.Debug("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
}
//