summaryrefslogtreecommitdiff
path: root/Kernel
diff options
context:
space:
mode:
authorHarley Travis <harleytravis123@outlook.com>2026-05-27 18:04:13 -0500
committerHarley Travis <harleytravis123@outlook.com>2026-05-27 18:04:13 -0500
commit32673d5872a2c77c2cc70a14a1e50cd440bc0180 (patch)
tree568fb469e295bbb69d166765d4712c21f77568fc /Kernel
parentb223d23c0165fccf014a4e8c0ed1a21425be7100 (diff)
downloadtempleos-floppy-driver-32673d5872a2c77c2cc70a14a1e50cd440bc0180.tar.gz
Refactor source code and create an "installer"
I have moved some source files into subdirectories for ease of installation. I have also added an installer script to automatically place driver files into their respective system directories.
Diffstat (limited to 'Kernel')
-rw-r--r--Kernel/KFloppy.HC482
-rw-r--r--Kernel/KFloppyA.HH137
-rw-r--r--Kernel/KFloppyB.HH25
-rw-r--r--Kernel/KFloppyDMA.HC7
4 files changed, 651 insertions, 0 deletions
diff --git a/Kernel/KFloppy.HC b/Kernel/KFloppy.HC
new file mode 100644
index 0000000..16b6bd3
--- /dev/null
+++ b/Kernel/KFloppy.HC
@@ -0,0 +1,482 @@
+/*
+ New, (hopefully) Less Messy Floppy Driver
+ Copyright (C) 2025-2026 Harley Travis <yoshi128k@gmail.com>.
+ This software (including source code) is licensed under the BSD Zero Clause
+ License. See the Copying.TXT file for details.
+*/
+
+Bool FDCIrq = FALSE;
+
+U0 FDCDMAInit(U16 len)
+{
+ U64 buf_lo, buf_hi, page, cnt_lo, cnt_hi;
+
+ buf_lo = &FDC_DMA & 0xFF;
+ buf_hi = &FDC_DMA >> 8;
+ page = &FDC_DMA >> 16;
+ cnt_lo = (len - 1) & 0xFF;
+ cnt_hi = (len - 1) >> 8;
+
+ OutU8(0x0A, 6); // mask ch 0 and 2
+ Sleep(1);
+ OutU8(0x0C, -1); // reset flip flop
+ Sleep(1);
+ OutU8(0x04, buf_lo); // buf low byte
+ Sleep(1);
+ OutU8(0x04, buf_hi); // buf high byte
+ Sleep(1);
+ OutU8(0x0C, -1); // reset flip flop again
+ Sleep(1);
+ OutU8(0x05, cnt_lo); // cntr low byte
+ Sleep(1);
+ OutU8(0x05, cnt_hi); // cntr high byte
+ Sleep(1);
+ OutU8(0x81, 0); // page
+ Sleep(1);
+ OutU8(0x0A, 2); // unmask ch 0 and 2
+ Sleep(1);
+}
+
+U0 FDCDMAPrepWrite()
+{
+ OutU8(0x0A, 6); // mask ch 0 and 2
+ Sleep(1);
+ OutU8(0x0B, 0x5A); // single xfer, addr inc, auto init, write, ch 2
+ Sleep(1);
+ OutU8(0x0A, 2); // unmask ch 0 and 2
+ Sleep(1);
+}
+
+U0 FDCDMAPrepRead()
+{
+ OutU8(0x0A, 6); // mask ch 0 and 2
+ Sleep(1);
+ OutU8(0x0B, 0x56); // single xfer, addr inc, auto init, read, ch 2
+ Sleep(1);
+ OutU8(0x0A, 2); // unmask ch 0 and 2
+ Sleep(1);
+}
+
+interrupt U0 FDCIrqHandler() {
+ OutU8(0x20, 0x20); // EOI
+ FDCIrq = TRUE;
+}
+
+U16 fdc_base = 0x03F0;
+
+static U8 *fd_types[8] = {
+ "none",
+ "360kB 5.25\"",
+ "1.2MB 5.25\"",
+ "720kB 3.5\"",
+
+ "1.44MB 3.5\"",
+ "2.88MB 3.5\"",
+ "unknown type",
+ "unknown type"
+};
+
+U0 CMOSGetFloppyDrives()
+{
+ OutU8(0x70, 0x10);
+ Sleep(1);
+
+ U64 drives = InU8(0x71);
+
+ AdamLog("Floppy Drive 0: %s\n", fd_types[drives >> 4]);
+ AdamLog("Floppy Drive 1: %s\n", fd_types[drives & 0xF]);
+}
+
+U0 FDCSendCmd(U16 base, U8 cmd)
+{
+ // Send a command to the floppy controller
+
+ // 60 sec timeout
+ U64 i;
+ for (i = 0; i < 600; i++) {
+ Sleep(10);
+ if (0x80 & InU8(base + FDC_MSR_DSR)) {
+ OutU8(base + FDC_DATA, cmd);
+ Sleep(1);
+ return;
+ }
+ }
+ AdamErr("FDC Command TimeOut");
+}
+
+U8 FDCReadData(U16 base)
+{
+ // Read data from the floppy controller
+
+ // 60 sec timeout
+ U64 i;
+ for (i=0;i<600;i++) {
+ Sleep(10);
+ if (0x80 & InU8(base + FDC_MSR_DSR)) {
+ return InU8(base + FDC_DATA);
+ }
+ }
+ AdamErr("FDC Read TimeOut");
+}
+
+U0 FDCCheckInt(U16 base, U8 *st0, U8 *cyl)
+{
+ FDCSendCmd(base, FDC_SENSE_INTR);
+
+ *st0 = FDCReadData(base);
+ *cyl = FDCReadData(base);
+}
+
+I16 fdc_mtr_ticks = 0;
+U8 fdc_mtr_state = FDC_MOTOR_OFF;
+
+U0 FDCMotorOff(U16 base)
+{
+ OutU8(base + FDC_DOR, 0x0C);
+ fdc_mtr_state = FDC_MOTOR_OFF;
+}
+
+U0 FDCMotorPwrOffTimer()
+{
+ // You're supposed to run this function as a separate CTask. It will exit
+ // when the motor exits the "wait" state (either because it has turned off
+ // or because something needs it to be on).
+ // The driver only spawns this when the motor is moved from "on" to "wait".
+ while (fdc_mtr_state == FDC_MOTOR_WAIT) {
+ // Sanity check for if something wants the motor on at the last second
+ if (fdc_mtr_state == FDC_MOTOR_ON) break;
+
+ Sleep(500);
+
+ fdc_mtr_ticks -= 50;
+ if (fdc_mtr_ticks <= 0) {
+ FDCMotorOff(fdc_base);
+ }
+ Yield; // Give control back to the scheduler
+ }
+}
+
+U0 FDCMotorCtrl(U16 base, Bool onoff)
+{
+ U64 prev_mtr_state;
+ if (onoff) {
+ if (!fdc_mtr_state) {
+ // Turn on motor
+ OutU8(base + FDC_DOR, 0x1C);
+ Sleep(500); // Wait 500 ms to allow drive to spin up
+ }
+ fdc_mtr_state = FDC_MOTOR_ON;
+ } else {
+ prev_mtr_state = fdc_mtr_state;
+ if (fdc_mtr_state == FDC_MOTOR_WAIT) {
+ AdamLog("FDC: Motor PowerOff Already Pending\n");
+ }
+ fdc_mtr_ticks = 300; // 3 sec timeout before motor turns off
+ fdc_mtr_state = FDC_MOTOR_WAIT;
+ // Only spawn the timer task of the motor is currently on.
+ // Infinitely-looping tasks peg the CPU!
+ if (prev_mtr_state != FDC_MOTOR_WAIT)
+ Spawn(&FDCMotorPwrOffTimer,,"FDCMotorPwrOffTimer");
+ }
+}
+
+I8 FDCRecalibrate(U16 base)
+{
+ FDCIrq = FALSE;
+
+ U64 i;
+ U8 st0, cyl;
+
+ FDCMotorCtrl(base, FDC_MOTOR_ON);
+
+ for (i=0;i<10;i++) {
+ FDCSendCmd(base, FDC_RECALIBRATE);
+ FDCSendCmd(base, 0);
+
+ // Wait for an interrupt, then get the status
+ while (!FDCIrq) Yield;
+ FDCCheckInt(base, &st0, &cyl);
+ AdamLog("ST0: %d Cyl: %d\n",st0,cyl);
+
+ if (st0 & 0xC0) {
+ static U8 * status[4] = {0, "error", "invalid", "drive"};
+ AdamLog("Calibration Status: %s\n", status[st0 >> 6]);
+ }
+
+ if (!cyl) {
+ FDCMotorCtrl(base, FDC_MOTOR_OFF);
+ return 0;
+ }
+ }
+
+ AdamErr("FDC ReCalibrate TimeOut");
+ FDCMotorCtrl(base, FDC_MOTOR_OFF);
+ return -1;
+}
+
+I8 FDCReset(U16 base)
+{
+ FDCIrq = FALSE;
+
+ OutU8(base + FDC_DOR, 0x00);
+ Sleep(1);
+ OutU8(base + FDC_DOR, 0x0C);
+ Sleep(1);
+
+ while (!FDCIrq) Yield;
+
+ // Ignore this
+ U64 i, lck;
+ U8 st0, cyl;
+ for (i=0;i<4;i++) {
+ FDCCheckInt(base, &st0, &cyl);
+ }
+
+ // Set xfer rate to 500kbps
+ OutU8(base + FDC_CCR_DIR, 0x00);
+ Sleep(1);
+
+ // Set the mechanical params and disable DMA (Terry didn't use it)
+ FDCSendCmd(base, FDC_SPECIFY);
+ FDCSendCmd(base, 0b10000000); // SRT = 8 (8 ms), HUT = 0 (256 ms)
+ FDCSendCmd(base, 0b00011110); // HLT = 15 (30 ms), NDMA = 0 (DMA enabled)
+
+ // Configure the FIFO
+ FDCSendCmd(base, FDC_CONFIGURE);
+ FDCSendCmd(base, 0x00); // 1st param is a 0
+ // 2nd param:
+ // Implied seek on, FIFO on, Drive polling disabled, threshold = 12
+ FDCSendCmd(base, 0b01011011);
+ FDCSendCmd(base, 0x00); // 3rd param: write precomp = 0
+
+ // Lock the configuration
+ FDCSendCmd(base, 0x94);
+ lck = FDCReadData(base); // Result: lock status
+
+ // contingency
+ if (FDCRecalibrate(base)) return -1;
+
+ return 0;
+}
+
+I8 FDCSeek(U16 base, U8 cyli, U8 head)
+{
+ U64 i;
+ U8 st0, cyl;
+ FDCIrq = FALSE;
+
+ FDCMotorCtrl(base, FDC_MOTOR_ON);
+
+ for (i=0;i<10;i++) {
+ // Attempt to move to given cyl
+ // 1: X X X X X HD D1 D0
+ // 2: Cyl No
+ FDCSendCmd(base, FDC_SEEK);
+ FDCSendCmd(base, head<<2);
+ FDCSendCmd(base, cyli);
+
+ while (!FDCIrq) Yield;
+ FDCCheckInt(base, &st0, &cyl);
+ AdamLog("ST0: %d Cyl: %d\n",st0,cyl);
+
+ if (st0 & 0xC0) {
+ static U8 * status[4] = {0, "error", "invalid", "drive"};
+ AdamLog("Seek Status: %s\n", status[st0 >> 6]);
+ }
+
+ if (cyl == cyli) {
+ FDCMotorCtrl(base, FDC_MOTOR_OFF);
+ return 0;
+ }
+ }
+
+ AdamErr("FDC Seek TimeOut");
+ FDCMotorCtrl(base, FDC_MOTOR_OFF);
+ return -1;
+}
+
+U0 FDCRead(U16 base, U8 cyl, U8 head, U8 sect, U8 trklen)
+{
+ // Read data from disk (single-track)
+ static U8 flags;
+ flags = 0x40; // Multi-track off, MFM modulation
+ static U64 cmd;
+ cmd = FDC_READ_DATA | flags;
+ FDCIrq = FALSE;
+
+ U64 st0, st1, st2, rcyl, rhd, rsect, rsectsize;
+
+ FDCMotorCtrl(base, FDC_MOTOR_ON);
+
+ AdamLog("Cmd: %d\nParams: %d %d %d %d %d %d %d %d\n",cmd,head<<2,cyl,head,sect,2,trklen,0x1B,0xFF);
+
+ // Prepare the DMA controller
+ AdamLog("Preparing DMA\n");
+ FDCDMAInit(512*(trklen - sect + 1));
+ FDCDMAPrepRead();
+
+ FDCSendCmd(base, cmd);
+ FDCSendCmd(base, head << 2); // Drive 0, specified head
+ FDCSendCmd(base, cyl);
+ FDCSendCmd(base, head);
+ FDCSendCmd(base, sect);
+ FDCSendCmd(base, 2); // Sector size = 2 = 512 bytes
+ FDCSendCmd(base, trklen); // Track Length/Max Sector No.
+ FDCSendCmd(base, 0x1B); // GAP3 Length = 27 (3.5")
+ FDCSendCmd(base, 0xFF); // Data Length (irrelevant)
+
+ // Wait for xfer to complete
+ while (!FDCIrq) Yield;
+
+ st0 = FDCReadData(base);
+ st1 = FDCReadData(base);
+ st2 = FDCReadData(base);
+ rcyl = FDCReadData(base);
+ rhd = FDCReadData(base);
+ rsect = FDCReadData(base);
+ rsectsize = FDCReadData(base);
+
+ AdamLog("Read Results:\nST0: %d\nST1: %d\nST2: %d\nCyl: %d\nHead: %d\nSect: %d\nSect Size: %d\n",st0,st1,st2,rcyl,rhd,rsect,rsectsize);
+
+ FDCMotorCtrl(base, FDC_MOTOR_OFF);
+}
+
+
+U0 FDCReadMulti(U16 base, U8 cyl, U8 head, U8 sect, U8 trklen)
+{
+ // Read data from disk (multi-track)
+ static U8 flags;
+ flags = 0xC0; // Multi-track on, MFM modulation
+ static U64 cmd;
+ cmd = FDC_READ_DATA | flags;
+ FDCIrq = FALSE;
+
+ U64 st0, st1, st2, rcyl, rhd, rsect, rsectsize;
+
+ FDCMotorCtrl(base, FDC_MOTOR_ON);
+
+ AdamLog("Cmd: %d\nParams: %d %d %d %d %d %d %d %d\n",cmd,head<<2,cyl,head,sect,2,trklen,0x1B,0xFF);
+
+ // Prepare the DMA controller
+ AdamLog("Preparing DMA\n");
+ FDCDMAInit(512*(trklen - sect + 1)*2);
+ FDCDMAPrepRead();
+
+ FDCSendCmd(base, cmd);
+ FDCSendCmd(base, head << 2); // Drive 0, specified head
+ FDCSendCmd(base, cyl);
+ FDCSendCmd(base, head);
+ FDCSendCmd(base, sect);
+ FDCSendCmd(base, 2); // Sector size = 2 = 512 bytes
+ FDCSendCmd(base, trklen); // Track Length/Max Sector No.
+ FDCSendCmd(base, 0x1B); // GAP3 Length = 27 (3.5")
+ FDCSendCmd(base, 0xFF); // Data Length (irrelevant)
+
+ // Wait for the xfer to complete
+ while (!FDCIrq) Yield;
+
+ st0 = FDCReadData(base);
+ st1 = FDCReadData(base);
+ st2 = FDCReadData(base);
+ rcyl = FDCReadData(base);
+ rhd = FDCReadData(base);
+ rsect = FDCReadData(base);
+ rsectsize = FDCReadData(base);
+
+ AdamLog("Read Results:\nST0: %d\nST1: %d\nST2: %d\nCyl: %d\nHead: %d\nSect: %d\nSect Size: %d\n",st0,st1,st2,rcyl,rhd,rsect,rsectsize);
+
+ FDCMotorCtrl(base, FDC_MOTOR_OFF);
+}
+
+U0 FDCWrite(U16 base, U8 cyl, U8 head, U8 sect, U8 trklen)
+{
+ // Write data to disk (single-track)
+ static U8 flags;
+ flags = 0x40; // Multi-track off, MFM modulation
+ static U64 cmd;
+ cmd = FDC_WRITE_DATA | flags;
+ FDCIrq = FALSE;
+
+ U64 st0, st1, st2, rcyl, rhd, rsect, rsectsize;
+
+ FDCMotorCtrl(base, FDC_MOTOR_ON);
+
+ AdamLog("Cmd: %d\nParams: %d %d %d %d %d %d %d %d\n",cmd,head<<2,cyl,head,sect,2,trklen,0x1B,0xFF);
+
+ // Prepare the DMA controller
+ AdamLog("Preparing DMA\n");
+ FDCDMAInit(512*(trklen - sect + 1));
+ FDCDMAPrepWrite();
+
+ FDCSendCmd(base, cmd);
+ FDCSendCmd(base, head << 2); // Drive 0, specified head
+ FDCSendCmd(base, cyl);
+ FDCSendCmd(base, head);
+ FDCSendCmd(base, sect);
+ FDCSendCmd(base, 2); // Sector size = 2 = 512 bytes
+ FDCSendCmd(base, trklen); // Track Length/Max Sector No.
+ FDCSendCmd(base, 0x1B); // GAP3 Length = 27 (3.5")
+ FDCSendCmd(base, 0xFF); // Data Length (irrelevant)
+
+ // Wait for xfer to complete
+ while (!FDCIrq) Yield;
+
+ st0 = FDCReadData(base);
+ st1 = FDCReadData(base);
+ st2 = FDCReadData(base);
+ rcyl = FDCReadData(base);
+ rhd = FDCReadData(base);
+ rsect = FDCReadData(base);
+ rsectsize = FDCReadData(base);
+
+ AdamLog("Read Results:\nST0: %d\nST1: %d\nST2: %d\nCyl: %d\nHead: %d\nSect: %d\nSect Size: %d\n",st0,st1,st2,rcyl,rhd,rsect,rsectsize);
+
+ FDCMotorCtrl(base, FDC_MOTOR_OFF);
+}
+
+
+U0 FDCWriteMulti(U16 base, U8 cyl, U8 head, U8 sect, U8 trklen)
+{
+ // Write data to disk (multi-track)
+ static U8 flags;
+ flags = 0xC0; // Multi-track on, MFM modulation
+ static U64 cmd;
+ cmd = FDC_WRITE_DATA | flags;
+ FDCIrq = FALSE;
+
+ U64 st0, st1, st2, rcyl, rhd, rsect, rsectsize;
+
+ FDCMotorCtrl(base, FDC_MOTOR_ON);
+
+ AdamLog("Cmd: %d\nParams: %d %d %d %d %d %d %d %d\n",cmd,head<<2,cyl,head,sect,2,trklen,0x1B,0xFF);
+
+ // Prepare the DMA controller
+ AdamLog("Preparing DMA\n");
+ FDCDMAInit(512*(trklen - sect + 1)*2);
+ FDCDMAPrepWrite();
+
+ FDCSendCmd(base, cmd);
+ FDCSendCmd(base, head << 2); // Drive 0, specified head
+ FDCSendCmd(base, cyl);
+ FDCSendCmd(base, head);
+ FDCSendCmd(base, sect);
+ FDCSendCmd(base, 2); // Sector size = 2 = 512 bytes
+ FDCSendCmd(base, trklen); // Track Length/Max Sector No.
+ FDCSendCmd(base, 0x1B); // GAP3 Length = 27 (3.5")
+ FDCSendCmd(base, 0xFF); // Data Length (irrelevant)
+
+ // Wait for the xfer to complete
+ while (!FDCIrq) Yield;
+
+ st0 = FDCReadData(base);
+ st1 = FDCReadData(base);
+ st2 = FDCReadData(base);
+ rcyl = FDCReadData(base);
+ rhd = FDCReadData(base);
+ rsect = FDCReadData(base);
+ rsectsize = FDCReadData(base);
+
+ AdamLog("Read Results:\nST0: %d\nST1: %d\nST2: %d\nCyl: %d\nHead: %d\nSect: %d\nSect Size: %d\n",st0,st1,st2,rcyl,rhd,rsect,rsectsize);
+
+ FDCMotorCtrl(base, FDC_MOTOR_OFF);
+}
diff --git a/Kernel/KFloppyA.HH b/Kernel/KFloppyA.HH
new file mode 100644
index 0000000..e7065b6
--- /dev/null
+++ b/Kernel/KFloppyA.HH
@@ -0,0 +1,137 @@
+/*
+ Copyright (C) 2025-2026 Harley Travis <yoshi128k@gmail.com>.
+ This software (including source code) is licensed under the BSD Zero Clause
+ License. See the Copying.TXT file for details.
+*/
+
+// FDC uses IRQ6 = int 26h
+#define I_FDC 0x26
+#define IRQ_FDC 6
+
+// FDC regs (primary ctrlr)
+#define FDC_STAT_A 0x0
+#define FDC_STAT_B 0x1
+#define FDC_DOR 0x2
+#define FDC_TAPE 0x3
+#define FDC_MSR_DSR 0x4
+#define FDC_DATA 0x5
+#define FDC_CCR_DIR 0x7
+
+// FDC cmds
+#define FDC_READ_TRACK 0x02
+#define FDC_SPECIFY 0x03
+#define FDC_SENSE_STAT 0x04
+#define FDC_WRITE_DATA 0x05
+#define FDC_READ_DATA 0x06
+#define FDC_RECALIBRATE 0x07
+#define FDC_SENSE_INTR 0x08
+#define FDC_WRITE_DEL 0x09
+#define FDC_READ_ID 0x0A
+#define FDC_READ_DEL 0x0C
+#define FDC_FMT_TRACK 0x0D
+#define FDC_DUMP_REG 0x0E
+#define FDC_SEEK 0x0F
+#define FDC_VERSION 0x10
+#define FDC_SCAN_EQL 0x11
+#define FDC_PERP_MODE 0x12
+#define FDC_CONFIGURE 0x13
+#define FDC_LOCK 0x14
+#define FDC_VERIFY 0x16
+#define FDC_SCAN_LO_EQL 0x19
+#define FDC_SCAN_HI_EQL 0x1D
+
+// Motor stuff
+#define FDC_MOTOR_OFF 0
+#define FDC_MOTOR_ON 1
+#define FDC_MOTOR_WAIT 2
+
+// Read/write directions
+#define FDC_DIR_READ 0
+#define FDC_DIR_WRITE 1
+
+// Digital Output Register (DOR) flags
+
+// DOR motor control flags: 1 = on, 0 = off
+#define FDC_DOR_MOTD 0x80 // Drive D
+#define FDC_DOR_MOTC 0x40 // Drive C
+#define FDC_DOR_MOTB 0x20 // Drive B
+#define FDC_DOR_MOTA 0x10 // Drive A
+
+// other DOR flags
+#define FDC_DOR_DMA 0x08 // DMA2/IRQ6 On/Off
+#define FDC_DOR_REST 0x04 // Controller Reset: this is active low;
+ // 1 = normal, 0 = reset
+
+// DOR drive select (DOR & 0x03): selects drive 0-3
+
+// Main Status Register (MSR) flags
+#define FDC_MSR_RQM 0x80 // Main Request: 1 = ready, 0 = not ready
+#define FDC_MSR_DIO 0x40 // Data In/Out: 1 = FDC -> PC, 0 = PC -> FDC
+#define FDC_MSR_NDMA 0x20 // Non-DMA: 1 = DMA off, 0 = DMA on
+#define FDC_MSR_BUSY 0x10 // Device busy: self-explanatory
+
+// MSR drive seek flags: 1 = seeking, 0 = idle
+#define FDC_MSR_ACTD 0x08 // Drive D
+#define FDC_MSR_ACTC 0x04 // Drive C
+#define FDC_MSR_ACTB 0x02 // Drive B
+#define FDC_MSR_ACTA 0x01 // Drive A
+
+// Status Register 0 (ST0) flags
+
+/* ST0 interrupt code (ST0 & 0xC0):
+ 00 = normal termination
+ 01 = abnormal termination
+ 10 = invalid cmd
+ 11 = abnormal termination by poilling (drive became not ready)
+*/
+
+#define FDC_ST0_SEEK_END 0x20 // Seek End: seek/calibration completed
+#define FDC_ST0_UNIT_CHK 0x10 // Unit Check: drive encountered a fault
+ // or recalibration failed
+#define FDC_ST0_NOT_RDY 0x08 // Not Ready: self-explanatory
+#define FDC_ST0_HEAD 0x04 // Active Head
+
+// ST0 unit select (ST0 & 0x03): same fmt as DOR drive select
+
+// Status Register 1 (ST1) flags
+#define FDC_ST1_END_CYL 0x80 // End of Cylinder:
+ // set when sector count > sectors on track
+// 0x40 is unused
+#define FDC_ST1_DATA_ERR 0x20 // Data Error: set when an error is
+ // detected in a sector's data or ID fields
+#define FDC_ST1_TIMEOUT 0x10 // Timeout: set when a data overrun or
+ // underrun occurs (i.e. the system is not
+ // reading data fast enough)
+// 0x08 is unused
+#define FDC_ST1_NO_DATA 0x04 // No Data: set when either a sector cannot
+ // be read, or an ID cannot be successfully
+ // read, or if the sector sequence cannot
+ // be determined
+#define FDC_ST1_NO_WRITE 0x02 // Not Writable: set when attempting to
+ // write to a write-protected disk
+#define FDC_ST1_NO_ID 0x01 // No Addr. Mark: set when an ID/(deleted)
+ // data address mark cannot be found.
+
+// Status Register 2 (ST2) flags
+// 0x80 is unused
+#define FDC_ST2_DEL_ADDR_MRK 0x40 // Deleted Addr. Mark: set when a deleted
+ // DAM or valid DAM is detected during RS
+ // or RDS, respectively
+#define FDC_ST2_CRC_ERR 0x20 // CRC Error: self explanatory
+#define FDC_ST2_WRONG_CYL 0x10 // Wrong cyl.: set when the ctrlr cyl does
+ // not match that in the ID addr mrk
+// 0x08 and 0x04 are unused
+#define FDC_ST2_BAD_CYL 0x02 // Bad Cylinder: set when track addr in
+ // sector does not match that on the ctrlr
+ // and equals 0xFF (meaning it is bad)
+#define FDC_ST2_MISSING_DATA 0x01 // Missing Data: set when no DAM or DDAM
+ // can be found
+
+// Status Register 3 (ST3) flags
+// 0x80 is unused
+#define FDC_ST3_W_PROT 0x40 // Write Protected: self explanatory
+// 0x20 is unused
+#define FDC_ST3_TRK_0 0x10 // Track 0: ditto
+// 0x08 is unused
+#define FDC_ST3_HEAD_ADDR 0x04 // Head Address: ditto
+// 0x02 and 0x01 are drive select
diff --git a/Kernel/KFloppyB.HH b/Kernel/KFloppyB.HH
new file mode 100644
index 0000000..76f6859
--- /dev/null
+++ b/Kernel/KFloppyB.HH
@@ -0,0 +1,25 @@
+/*
+ Copyright (C) 2025-2026 Harley Travis <yoshi128k@gmail.com>.
+ This software (including source code) is licensed under the BSD Zero Clause
+ License. See the Copying.TXT file for details.
+*/
+
+// headers for floppy driver
+public extern Bool FDCIrq;
+public extern U16 fdc_base;
+public extern U0 CMOSGetFloppyDrives();
+public extern U0 FDCSendCmd(U16 base, U8 cmd);
+public extern U8 FDCReadData(U16 base);
+public extern U0 FDCCheckInt(U16 base, U8 *st0, U8 *cyl);
+public extern I16 fdc_mtr_ticks;
+public extern U8 fdc_mtr_state;
+public extern U0 FDCMotorOff(U16 base);
+public extern U0 FDCMotorPwrOffTimer();
+public extern U0 FDCMotorCtrl(U16 base, Bool onoff);
+public extern I8 FDCRecalibrate(U16 base);
+public extern I8 FDCReset(U16 base);
+public extern I8 FDCSeek(U16 base, U8 cyli, U8 head);
+public extern U0 FDCRead(U16 base, U8 cyl, U8 head, U8 sect, U8 trklen);
+public extern U0 FDCReadMulti(U16 base, U8 cyl, U8 head, U8 sect, U8 trklen);
+public extern U0 FDCWrite(U16 base, U8 cyl, U8 head, U8 sect, U8 trklen);
+public extern U0 FDCWriteMulti(U16 base, U8 cyl, U8 head, U8 sect, U8 trklen);
diff --git a/Kernel/KFloppyDMA.HC b/Kernel/KFloppyDMA.HC
new file mode 100644
index 0000000..12f4953
--- /dev/null
+++ b/Kernel/KFloppyDMA.HC
@@ -0,0 +1,7 @@
+/*
+ Copyright (C) 2025-2026 Harley Travis <yoshi128k@gmail.com>.
+ This software (including source code) is licensed under the BSD Zero Clause
+ License. See the Copying.TXT file for details.
+*/
+
+U8 FDC_DMA[0x4800]; // Floppy DMA buffer