floppy_prep() recalibrates and then calls floppy_media_sense(), which
issues READ ID once per data rate. A drive with no medium spins no
diskette and therefore produces no index pulses, so a real controller
never completes READ ID and every attempt waits out FLOPPY_IRQ_TIMEOUT.
Booting with an empty drive, or an INT 13h read of an empty A:, costs
about ten seconds.
The disk change line answers the same question immediately. It latches
when the medium is removed and is cleared only by a step pulse with a
diskette in the drive. A recalibrate leaves the head on cylinder 0 and
need not step at all, so seek to cylinder 1 and read DIR again. Linux's
floppy driver has done exactly this since 2.0 and never issues READ ID.
Only the 1.2MB 5.25" drive and the 3.5" drives have the change line
(CMOS types 2..5); on the older 5.25" types the pin is not driven and
reads back as a permanent "changed". Restrict the probe to those types,
and report any uncertainty as "medium present", so that the code can only
ever fall back to the current behaviour.
Measured with qemu-system-x86_64, "-machine pc -device floppy,
drive-type=144" and no medium, INT 13h AH=02h on drive A:
before 0.11 s, AH = 0x20 (controller failure)
after 0.11 s, AH = 0xC0 (no media)
QEMU completes READ ID successfully on an empty drive, which hides the
timeout; a fix for that is on qemu-devel [1]. With it applied, so that
READ ID never completes as on real hardware, the same read takes 10.12 s
before this change and 0.11 s after it. Booting from a diskette is
unaffected, including the cold start where the change line is set for a
freshly inserted medium.
[1] https://lore.kernel.org/qemu-devel/20260710113134.43012-1-christian@quante.one/
Signed-off-by: Christian Quante <christian@quante.one>
---
Notes for reviewers, not for the commit message:
How the numbers were measured: a 512-byte boot sector issues INT 13h
AH=02h on drive A: and then exits the VM through isa-debug-exit, so the
time is what the guest waits, not what the harness waits. The drive is
"-device floppy,drive-type=144" with no medium. Both BIOS images were
built from master (c2a33ad9), which has not touched src/hw/floppy.c since
rel-1.17.0.
What motivated this: OS/2 2.11 cannot detect that a diskette has been
removed, because QEMU answers READ ID on an empty drive. With the QEMU
side fixed so that READ ID never completes, OS/2's driver issues one
READ ID, waits 2.55 s, resets the controller and correctly reports "no
diskette" -- but SeaBIOS then spends 10 s in floppy_media_sense() on
every boot. This patch removes that cost. The two changes are useful
independently: on today's unmodified QEMU this one already turns
AH = 0x20 (controller failure) into AH = 0xC0 (no media).
Tested: boot from a diskette; INT 13h read of a present diskette; cold
start with a freshly inserted medium, where the change line is set and a
naive probe would report the drive as empty; and an empty drive. Also
exercised through PC-DOS 7, IBM DOS 5.02, Windows for Workgroups 3.11
and OS/2 2.11.
Not tested on real hardware -- I have none with a floppy drive. The
gating on CMOS types 2..5 and the "when in doubt, say present" fallback
are meant to make that safe, but a second opinion on drives without a
change line would be welcome.
One open question: DISK_RET_EMEDIA (0xC0) is what floppy_media_sense()
already returns when it exhausts the data rates, so I kept it. A real
AT BIOS times the command out and returns 0x80 (timeout), which DOS
renders as "Not ready reading drive A" rather than "General failure".
If 0x80 is the better answer here I am happy to change it.
src/hw/floppy.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/src/hw/floppy.c b/src/hw/floppy.c
index 9e6647d4..5bdd6541 100644
--- a/src/hw/floppy.c
+++ b/src/hw/floppy.c
@@ -27,6 +27,9 @@
#define PORT_FD_DATA 0x03f5
#define PORT_FD_DIR 0x03f7
+// Digital Input Register (read)
+#define FLOPPY_DIR_DSKCHG 0x80
+
#define FLOPPY_SIZE_CODE 0x02 // 512 byte sectors
#define FLOPPY_DATALEN 0xff // Not used - because size code is 0x02
#define FLOPPY_MOTOR_TICKS 37 // ~2 seconds
@@ -442,6 +445,48 @@ floppy_drive_readid(u8 floppyid, u8 data_rate, u8 head)
return 0;
}
+// Only 1.2MB 5.25" and the 3.5" drives have a disk change line. On the older
+// 5.25" types the pin is not driven and reads back as a permanent "changed".
+static int
+floppy_has_changeline(u8 ftype)
+{
+ return ftype >= 2 && ftype <= 5;
+}
+
+// Determine whether a diskette is present, without waiting for a command to
+// time out. The disk change line latches when the medium is removed and is
+// cleared only by a step pulse with a diskette in the drive. A recalibrate
+// leaves the head on cylinder 0 and need not step at all, so step to cylinder
+// 1 and look again. Returns 0 only when the drive is known to be empty; any
+// uncertainty is reported as "present", leaving the caller to find out.
+static int
+floppy_media_present(struct drive_s *drive_gf, u8 floppyid)
+{
+ if (!floppy_has_changeline(GET_GLOBALFLAT(drive_gf->floppy_type)))
+ return 1;
+
+ // The caller has just recalibrated this drive, so it is selected in the
+ // DOR and the DIR reflects its change line.
+ if (!(inb(PORT_FD_DIR) & FLOPPY_DIR_DSKCHG))
+ return 1;
+
+ u8 param[2];
+ param[0] = floppyid;
+ param[1] = 1;
+ int ret = floppy_drive_pio(floppyid, FC_SEEK, param);
+ if (ret)
+ return 1;
+ int present = !(inb(PORT_FD_DIR) & FLOPPY_DIR_DSKCHG);
+
+ // Put the head back on cylinder 0 and fix up the BDA.
+ ret = floppy_drive_recal(floppyid);
+ if (ret)
+ return 1;
+
+ dprintf(2, "Floppy_media_present %d: %d\n", floppyid, present);
+ return present;
+}
+
static int
floppy_media_sense(struct drive_s *drive_gf)
{
@@ -498,6 +543,12 @@ floppy_prep(struct drive_s *drive_gf, u8 cylinder)
if (ret)
return ret;
+ // An empty drive gives no index pulses, so READ ID would never
+ // complete and media sensing would wait out its timeout once per
+ // data rate. Ask the disk change line instead.
+ if (!floppy_media_present(drive_gf, floppyid))
+ return DISK_RET_EMEDIA;
+
// Sense media.
ret = floppy_media_sense(drive_gf);
if (ret)
--
2.53.0
_______________________________________________
SeaBIOS mailing list -- seabios@seabios.org
To unsubscribe send an email to seabios-leave@seabios.org
Kevin, thanks again -- both points fixed, and the 0x0C/0xC0 transposition
you dug up is now in 3/3 where it belongs.
2/3 A failed seek is passed up rather than reported as success, and the
head goes back to cylinder 0 once a medium is found, so the READ ID
in floppy_media_sense() runs on the same track it always did. You
were right to flag that -- it was a behaviour change I had waved
through with a comment instead of fixing.
3/3 Unchanged apart from the commit message, which now credits where
0xC0 came from.
Changes since v2:
- floppy_check_media() passes a seek error up (was: masked as success)
- it seeks back to cylinder 0 when a medium is present
- commit message of 2/3 no longer claims the head is simply left behind
- commit message of 2/3 says where the ten-second figure comes from
- commit message of 3/3 carries the Bochs 0x0C history
A clarification on the timing, because v2 conflated two things that are not
the same. The ten-second wait is a real-hardware effect and does not appear
under QEMU at all: QEMU completes READ ID on an empty drive (a bug of its
own), and the fix on qemu-devel makes the command fail immediately rather
than never complete -- so neither of them shows the wait. The figure comes
from a further change that makes QEMU leave READ ID unfinished the way
hardware does. That one is local and not proposed upstream; it could not be
until this series is in place, or every access to an empty drive under QEMU
would cost ten seconds.
INT 13h AH=02h on an empty A:, SeaBIOS 1.17.0, each commit built and measured
on its own:
QEMU made to behave like hardware (READ ID never completes)
upstream 10.13 s AH = 0xC0
1/3 floppy_drive_seek() 10.13 s AH = 0xC0 (no functional change)
2/3 disk change probe 0.11 s AH = 0xC0
3/3 not ready 0.11 s AH = 0x80
QEMU as it stands today (READ ID succeeds on an empty drive)
upstream 0.11 s AH = 0x20
1/3 0.11 s AH = 0x20
2/3 0.11 s AH = 0xC0
3/3 0.11 s AH = 0x80
So on stock QEMU the series saves no time -- there is no wait to save -- but
it still turns "controller failure" into "not ready". The wait it removes is
the one on real hardware.
The extra seek back to cylinder 0 costs no measurable time. A diskette in
the drive is still read, and booting from one -- including the cold start
where the change line is set for a freshly inserted medium -- is unchanged.
Still not verified against real hardware; I have none with a floppy drive.
Christian Quante (3):
floppy: add floppy_drive_seek()
floppy: detect an empty drive via the disk change line
floppy: report an empty drive as not ready
src/hw/floppy.c | 77 ++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 70 insertions(+), 7 deletions(-)
--
2.53.0
_______________________________________________
SeaBIOS mailing list -- seabios@seabios.org
To unsubscribe send an email to seabios-leave@seabios.org
On Tue, Jul 14, 2026 at 06:41:48PM +0200, Christian Quante wrote: > Kevin, thanks again -- both points fixed, and the 0x0C/0xC0 transposition > you dug up is now in 3/3 where it belongs. > > 2/3 A failed seek is passed up rather than reported as success, and the > head goes back to cylinder 0 once a medium is found, so the READ ID > in floppy_media_sense() runs on the same track it always did. You > were right to flag that -- it was a behaviour change I had waved > through with a comment instead of fixing. > > 3/3 Unchanged apart from the commit message, which now credits where > 0xC0 came from. > > Changes since v2: > - floppy_check_media() passes a seek error up (was: masked as success) > - it seeks back to cylinder 0 when a medium is present > - commit message of 2/3 no longer claims the head is simply left behind > - commit message of 2/3 says where the ten-second figure comes from > - commit message of 3/3 carries the Bochs 0x0C history Thanks. I committed this series. -Kevin _______________________________________________ SeaBIOS mailing list -- seabios@seabios.org To unsubscribe send an email to seabios-leave@seabios.org
On Tue, Jul 14, 2026 at 06:41:48PM +0200, Christian Quante wrote: > Kevin, thanks again -- both points fixed, and the 0x0C/0xC0 transposition > you dug up is now in 3/3 where it belongs. > > 2/3 A failed seek is passed up rather than reported as success, and the > head goes back to cylinder 0 once a medium is found, so the READ ID > in floppy_media_sense() runs on the same track it always did. You > were right to flag that -- it was a behaviour change I had waved > through with a comment instead of fixing. > > 3/3 Unchanged apart from the commit message, which now credits where > 0xC0 came from. > > Changes since v2: > - floppy_check_media() passes a seek error up (was: masked as success) > - it seeks back to cylinder 0 when a medium is present > - commit message of 2/3 no longer claims the head is simply left behind > - commit message of 2/3 says where the ten-second figure comes from > - commit message of 3/3 carries the Bochs 0x0C history > > A clarification on the timing, because v2 conflated two things that are not > the same. The ten-second wait is a real-hardware effect and does not appear > under QEMU at all: QEMU completes READ ID on an empty drive (a bug of its > own), and the fix on qemu-devel makes the command fail immediately rather > than never complete -- so neither of them shows the wait. The figure comes > from a further change that makes QEMU leave READ ID unfinished the way > hardware does. That one is local and not proposed upstream; it could not be > until this series is in place, or every access to an empty drive under QEMU > would cost ten seconds. > > INT 13h AH=02h on an empty A:, SeaBIOS 1.17.0, each commit built and measured > on its own: > > QEMU made to behave like hardware (READ ID never completes) > upstream 10.13 s AH = 0xC0 > 1/3 floppy_drive_seek() 10.13 s AH = 0xC0 (no functional change) > 2/3 disk change probe 0.11 s AH = 0xC0 > 3/3 not ready 0.11 s AH = 0x80 > > QEMU as it stands today (READ ID succeeds on an empty drive) > upstream 0.11 s AH = 0x20 > 1/3 0.11 s AH = 0x20 > 2/3 0.11 s AH = 0xC0 > 3/3 0.11 s AH = 0x80 > > So on stock QEMU the series saves no time -- there is no wait to save -- but > it still turns "controller failure" into "not ready". The wait it removes is > the one on real hardware. > > The extra seek back to cylinder 0 costs no measurable time. A diskette in > the drive is still read, and booting from one -- including the cold start > where the change line is set for a freshly inserted medium -- is unchanged. > > Still not verified against real hardware; I have none with a floppy drive. Thanks. It seems fine to me. I'll give a few days to see if there are further comments and otherwise look to commit. It'll be difficult to get testing on real floppy hardware given how rare they are. I don't think we need to wait for that. Cheers, -Kevin _______________________________________________ SeaBIOS mailing list -- seabios@seabios.org To unsubscribe send an email to seabios-leave@seabios.org
Wrap the SEEK command and the BDA bookkeeping that goes with it, the way
floppy_drive_recal() already does for RECALIBRATE. No functional change;
the next patch gains a second caller.
Signed-off-by: Christian Quante <christian@quante.one>
---
src/hw/floppy.c | 24 +++++++++++++++++-------
1 file changed, 17 insertions(+), 7 deletions(-)
diff --git a/src/hw/floppy.c b/src/hw/floppy.c
index 9e6647d4..8f053fe3 100644
--- a/src/hw/floppy.c
+++ b/src/hw/floppy.c
@@ -416,6 +416,21 @@ floppy_drive_recal(u8 floppyid)
return DISK_RET_SUCCESS;
}
+static int
+floppy_drive_seek(u8 floppyid, u8 cylinder)
+{
+ dprintf(2, "Floppy_drive_seek %d to %d\n", floppyid, cylinder);
+ u8 param[2];
+ param[0] = floppyid;
+ param[1] = cylinder;
+ int ret = floppy_drive_pio(floppyid, FC_SEEK, param);
+ if (ret)
+ return ret;
+
+ SET_BDA(floppy_track[floppyid], cylinder);
+ return DISK_RET_SUCCESS;
+}
+
static int
floppy_drive_specify(void)
{
@@ -511,15 +526,10 @@ floppy_prep(struct drive_s *drive_gf, u8 cylinder)
}
// Seek to cylinder if needed.
- u8 lastcyl = GET_BDA(floppy_track[floppyid]);
- if (cylinder != lastcyl) {
- u8 param[2];
- param[0] = floppyid;
- param[1] = cylinder;
- int ret = floppy_drive_pio(floppyid, FC_SEEK, param);
+ if (cylinder != GET_BDA(floppy_track[floppyid])) {
+ int ret = floppy_drive_seek(floppyid, cylinder);
if (ret)
return ret;
- SET_BDA(floppy_track[floppyid], cylinder);
}
return DISK_RET_SUCCESS;
--
2.53.0
_______________________________________________
SeaBIOS mailing list -- seabios@seabios.org
To unsubscribe send an email to seabios-leave@seabios.org
floppy_prep() recalibrates and then calls floppy_media_sense(), which
issues READ ID once per data rate. A drive with no medium spins no
diskette and therefore produces no index pulses, so a real controller
never completes READ ID and every attempt waits out FLOPPY_IRQ_TIMEOUT.
Booting with an empty drive, or an INT 13h read of an empty A:, costs
about ten seconds.
The disk change line answers the same question immediately. It latches
when the medium is removed and is cleared only by a step pulse with a
diskette in the drive. A recalibrate leaves the head on cylinder 0 and
need not step at all, so seek to cylinder 1, read DIR again, and seek back
if a medium turns out to be present, so that media sensing reads the track
it always did. Linux's floppy driver has done exactly this since 2.0 and
never issues READ ID.
Only the 1.2MB 5.25" drive and the 3.5" drives have the change line
(CMOS types 2..5); on the older 5.25" types the pin is not driven and
reads back as a permanent "changed". Restrict the probe to those types,
so that the code can only ever fall back to the current behaviour.
The error code is unchanged: floppy_media_sense() already reports
DISK_RET_EMEDIA once it has exhausted the data rates, so an empty drive
still answers DISK_RET_EMEDIA -- just without the wait.
The wait is a real-hardware effect and cannot be reproduced on QEMU as it
stands: QEMU completes READ ID on an empty drive and hands back a made-up
sector ID, so media sensing never blocks. That is a bug of its own, and a
fix is on qemu-devel [1] -- but it makes READ ID fail immediately rather
than never complete, so it does not bring the wait back either. To measure
what this patch removes I made QEMU leave the command unfinished the way
hardware does. That change is local and not proposed upstream: it would
need this series in place first, or every access to an empty drive under
QEMU would cost ten seconds.
INT 13h AH=02h on an empty A:, "-machine pc -device floppy,drive-type=144":
QEMU made to behave like hardware 10.13 s -> 0.11 s
QEMU as it stands today 0.11 s -> 0.11 s
So on stock QEMU this patch neither costs nor saves time; what it changes
there is the error code, which 3/3 is about. Booting from a diskette is
unaffected, including the cold start where the change line is set for a
freshly inserted medium.
[1] https://lore.kernel.org/qemu-devel/20260710113134.43012-1-christian@quante.one/
Signed-off-by: Christian Quante <christian@quante.one>
---
src/hw/floppy.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
diff --git a/src/hw/floppy.c b/src/hw/floppy.c
index 8f053fe3..0469a791 100644
--- a/src/hw/floppy.c
+++ b/src/hw/floppy.c
@@ -27,6 +27,9 @@
#define PORT_FD_DATA 0x03f5
#define PORT_FD_DIR 0x03f7
+// Digital Input Register (read)
+#define FLOPPY_DIR_DSKCHG 0x80
+
#define FLOPPY_SIZE_CODE 0x02 // 512 byte sectors
#define FLOPPY_DATALEN 0xff // Not used - because size code is 0x02
#define FLOPPY_MOTOR_TICKS 37 // ~2 seconds
@@ -457,6 +460,49 @@ floppy_drive_readid(u8 floppyid, u8 data_rate, u8 head)
return 0;
}
+// Only the 1.2MB 5.25" drive and the 3.5" drives have a disk change line. On
+// the older 5.25" types the pin is not driven and reads back as a permanent
+// "changed".
+static int
+floppy_has_changeline(u8 ftype)
+{
+ return ftype >= 2 && ftype <= 5;
+}
+
+// Determine whether a diskette is present, without waiting for a command to
+// time out. The disk change line latches when the medium is removed and is
+// cleared only by a step pulse with a diskette in the drive. The caller has
+// just recalibrated, which leaves the head on cylinder 0 and need not step at
+// all, so step to cylinder 1 and look again.
+//
+// Report DISK_RET_EMEDIA only when the drive is known to be empty -- an older
+// drive without a change line is reported as present, so this can only ever
+// fall back to the previous behaviour.
+static int
+floppy_check_media(struct drive_s *drive_gf, u8 floppyid)
+{
+ if (!floppy_has_changeline(GET_GLOBALFLAT(drive_gf->floppy_type)))
+ return DISK_RET_SUCCESS;
+
+ // The caller has just recalibrated this drive, so it is selected in the
+ // DOR and the DIR reflects its change line.
+ if (!(inb(PORT_FD_DIR) & FLOPPY_DIR_DSKCHG))
+ return DISK_RET_SUCCESS;
+
+ int ret = floppy_drive_seek(floppyid, 1);
+ if (ret)
+ return ret;
+
+ if (inb(PORT_FD_DIR) & FLOPPY_DIR_DSKCHG) {
+ dprintf(2, "Floppy_check_media %d: no medium\n", floppyid);
+ return DISK_RET_EMEDIA;
+ }
+
+ // A medium is present. Put the head back on cylinder 0, where the caller
+ // left it, so that media sensing reads the track it always did.
+ return floppy_drive_seek(floppyid, 0);
+}
+
static int
floppy_media_sense(struct drive_s *drive_gf)
{
@@ -513,6 +559,13 @@ floppy_prep(struct drive_s *drive_gf, u8 cylinder)
if (ret)
return ret;
+ // An empty drive gives no index pulses, so the READ ID that
+ // floppy_media_sense() issues would never complete and would wait out
+ // its timeout once per data rate. Ask the disk change line instead.
+ ret = floppy_check_media(drive_gf, floppyid);
+ if (ret)
+ return ret;
+
// Sense media.
ret = floppy_media_sense(drive_gf);
if (ret)
--
2.53.0
_______________________________________________
SeaBIOS mailing list -- seabios@seabios.org
To unsubscribe send an email to seabios-leave@seabios.org
An empty drive and an unreadable diskette are different failures, and
until the previous patch SeaBIOS could not tell them apart: both ended up
in floppy_media_sense() exhausting the data rates and returning
DISK_RET_EMEDIA (0xC0). Now that the disk change line identifies an absent
medium, report it as DISK_RET_ETIMEOUT (0x80), which the INT 13h tables
give as "time out, drive not ready". floppy_media_sense() keeps
DISK_RET_EMEDIA for a medium it cannot read.
Kevin O'Connor traced where 0xC0 came from: Bochs BIOS answers 0x0C here,
"media type not found", and the value was transposed when the code was
brought over at the initial checkin f076a3ee. Ralf Brown's interrupt list
gives 0x0C as "unsupported track or invalid media" -- so 0xC0 has never
been an INT 13h status code at all. It does not appear in the classic
tables, and guests treat it accordingly.
Measured on an empty drive, with SeaBIOS patched to return each code in
turn. The guests here are German-language, so their messages are quoted as
they appear, with the English sense in brackets:
PC-DOS 7, "dir a:"
0x80 "Nicht bereit beim Lesen von Laufwerk A"
[not ready reading drive A]
0xC0 "Allgemeiner Fehler beim Lesen von Laufwerk A"
[general failure reading drive A]
0xAA same message as 0xC0
0x99 same message as 0xC0 <- invented, not a real code
0x06 no message at all; DOS retries forever
Windows for Workgroups 3.11, File Manager, drive A:
0x80 "In Laufwerk A ist kein Datenträger eingelegt. Legen Sie einen
Datenträger ein, und versuchen Sie es erneut." [Wiederholen]
[there is no disk in drive A; insert one and try again]
0xC0 "Der Datenträger in Laufwerk A ist nicht formatiert. Möchten
Sie, daß der Datenträger jetzt formatiert wird?" [Ja] [Nein]
[the disk in drive A is not formatted; format it now?]
DOS gives 0xC0 the same message as the invented code 0x99, which is to say
it does not recognise it. Windows does recognise it, and offers to format
a diskette that is not there. Both are correct with 0x80.
OS/2 2.11 and Linux are unaffected: both drive the controller through their
own drivers rather than INT 13h.
Not verified against real hardware -- I have none with a floppy drive.
Signed-off-by: Christian Quante <christian@quante.one>
---
src/hw/floppy.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/hw/floppy.c b/src/hw/floppy.c
index 0469a791..ebed54f1 100644
--- a/src/hw/floppy.c
+++ b/src/hw/floppy.c
@@ -475,7 +475,7 @@ floppy_has_changeline(u8 ftype)
// just recalibrated, which leaves the head on cylinder 0 and need not step at
// all, so step to cylinder 1 and look again.
//
-// Report DISK_RET_EMEDIA only when the drive is known to be empty -- an older
+// Report a missing medium only when the drive is known to be empty -- an older
// drive without a change line is reported as present, so this can only ever
// fall back to the previous behaviour.
static int
@@ -495,7 +495,7 @@ floppy_check_media(struct drive_s *drive_gf, u8 floppyid)
if (inb(PORT_FD_DIR) & FLOPPY_DIR_DSKCHG) {
dprintf(2, "Floppy_check_media %d: no medium\n", floppyid);
- return DISK_RET_EMEDIA;
+ return DISK_RET_ETIMEOUT;
}
// A medium is present. Put the head back on cylinder 0, where the caller
--
2.53.0
_______________________________________________
SeaBIOS mailing list -- seabios@seabios.org
To unsubscribe send an email to seabios-leave@seabios.org
Kevin, thanks for the review. All three points addressed, plus the error
code question, which turned out to deserve its own patch as you suggested.
1/3 floppy_drive_seek(), as you asked -- it also removes the reason for
the second recalibrate you asked about. The seek in my v1 went
straight to floppy_drive_pio() and left floppy_track[] in the BDA
stale; the recalibrate was there to repair that, not because the
head needed to move. With the BDA kept correct the head can simply
stay on cylinder 1, and floppy_prep() seeks where it wants anyway.
2/3 The probe itself, now returning a status code rather than a bool.
Behaviour is unchanged from today apart from the wait: an empty
drive still answers DISK_RET_EMEDIA, just in 0.11 s instead of 10 s.
3/3 The error code, separate as you preferred.
On 3/3 -- you asked whether I am sure a traditional BIOS answers differently.
I was not, and said so; here is what I found since.
0xC0 does not appear in the classic INT 13h status tables at all (helppc,
the Wikipedia table for AH=01h, techhelp). It has been in SeaBIOS since the
initial checkin f076a3ee, without a comment or a source. Bochs BIOS, which
SeaBIOS descends from, answers 0x80 here ("SET_AH(0x80); // drive not ready
(timeout)") and has no READ ID media sensing at all -- that arrived with your
e7c5a7ef in 2013, and DISK_RET_EMEDIA came along with it.
Guests agree. Building SeaBIOS to return each code in turn on an empty drive.
My guests are German-language, so their messages are quoted as they appear,
with the English sense in brackets:
PC-DOS 7, "dir a:"
0x80 "Nicht bereit beim Lesen von Laufwerk A"
[not ready reading drive A]
0xC0 "Allgemeiner Fehler beim Lesen von Laufwerk A"
[general failure reading drive A]
0xAA same message as 0xC0
0x99 same message as 0xC0 <- an invented code
0x06 no message at all; DOS retries forever
Windows for Workgroups 3.11, File Manager, drive A:
0x80 "In Laufwerk A ist kein Datenträger eingelegt. Legen Sie einen
Datenträger ein, und versuchen Sie es erneut." [Wiederholen]
[there is no disk in drive A; insert one and try again]
0xC0 "Der Datenträger in Laufwerk A ist nicht formatiert. Möchten
Sie, daß der Datenträger jetzt formatiert wird?" [Ja] [Nein]
[the disk in drive A is not formatted; format it now?]
DOS gives 0xC0 the same message as the invented 0x99, so it does not
recognise the code. Windows does recognise it -- and offers to format a
diskette that is not in the drive.
The two failures are distinct, and 2/3 is what makes them distinguishable in
the first place: an absent medium is now known as such, so it can be reported
as "not ready", while floppy_media_sense() keeps DISK_RET_EMEDIA for a medium
it cannot read.
Tested: empty drive; diskette present; cold start with a freshly inserted
medium, where the change line is set and a naive probe would call the drive
empty; boot from a diskette; and the guests PC-DOS 7, IBM DOS 5.02, Windows
for Workgroups 3.11 and OS/2 2.11. Each commit builds and was measured
separately -- 1/3 changes nothing, 2/3 takes an INT 13h read of an empty A:
from 10.12 s to 0.11 s, 3/3 changes the code it answers with.
Still not verified against real hardware; I have none with a floppy drive.
The gating on CMOS types 2..5 and the "when in doubt, say present" fallback
are meant to keep that safe, but a second opinion on drives without a change
line would be welcome.
Christian Quante (3):
floppy: add floppy_drive_seek()
floppy: detect an empty drive via the disk change line
floppy: report an empty drive as not ready
src/hw/floppy.c | 75 ++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 68 insertions(+), 7 deletions(-)
--
2.53.0
_______________________________________________
SeaBIOS mailing list -- seabios@seabios.org
To unsubscribe send an email to seabios-leave@seabios.org
On Mon, Jul 13, 2026 at 09:35:26PM +0200, Christian Quante wrote:
> Kevin, thanks for the review. All three points addressed, plus the error
> code question, which turned out to deserve its own patch as you suggested.
>
> 1/3 floppy_drive_seek(), as you asked -- it also removes the reason for
> the second recalibrate you asked about. The seek in my v1 went
> straight to floppy_drive_pio() and left floppy_track[] in the BDA
> stale; the recalibrate was there to repair that, not because the
> head needed to move. With the BDA kept correct the head can simply
> stay on cylinder 1, and floppy_prep() seeks where it wants anyway.
>
> 2/3 The probe itself, now returning a status code rather than a bool.
> Behaviour is unchanged from today apart from the wait: an empty
> drive still answers DISK_RET_EMEDIA, just in 0.11 s instead of 10 s.
>
> 3/3 The error code, separate as you preferred.
>
> On 3/3 -- you asked whether I am sure a traditional BIOS answers differently.
> I was not, and said so; here is what I found since.
>
> 0xC0 does not appear in the classic INT 13h status tables at all (helppc,
> the Wikipedia table for AH=01h, techhelp). It has been in SeaBIOS since the
> initial checkin f076a3ee, without a comment or a source. Bochs BIOS, which
> SeaBIOS descends from, answers 0x80 here ("SET_AH(0x80); // drive not ready
> (timeout)") and has no READ ID media sensing at all -- that arrived with your
> e7c5a7ef in 2013, and DISK_RET_EMEDIA came along with it.
Ah, it looks like the code in f076a3ee descends from this code in
bochs bios:
```
// see if drive exists
if (floppy_drive_exists(drive) == 0) {
SET_AH(0x80); // not responding
set_diskette_ret_status(0x80);
SET_AL(0); // no sectors read
SET_CF(); // error occurred
return;
}
// see if media in drive, and type is known
if (floppy_media_known(drive) == 0) {
if (floppy_media_sense(drive) == 0) {
SET_AH(0x0C); // Media type not found
set_diskette_ret_status(0x0C);
SET_AL(0); // no sectors read
SET_CF(); // error occurred
return;
}
}
```
It seems I goofed in the translation - should have been 0x0c, but I
incorrectly translated it to 0xc0..
Ralph Brown's interrupt list agrees:
```
(Table 00234)
Values for disk operation status:
00h successful completion
...
0Ch unsupported track or invalid media
```
So, long story short, I agree the error should be corrected.
Thanks,
-Kevin
_______________________________________________
SeaBIOS mailing list -- seabios@seabios.org
To unsubscribe send an email to seabios-leave@seabios.org
On Mon, Jul 13, 2026 at 08:05:12PM +0000, Kevin O'Connor via SeaBIOS wrote:
> On Mon, Jul 13, 2026 at 09:35:26PM +0200, Christian Quante wrote:
> > Kevin, thanks for the review. All three points addressed, plus the error
> > code question, which turned out to deserve its own patch as you suggested.
> >
> > 1/3 floppy_drive_seek(), as you asked -- it also removes the reason for
> > the second recalibrate you asked about. The seek in my v1 went
> > straight to floppy_drive_pio() and left floppy_track[] in the BDA
> > stale; the recalibrate was there to repair that, not because the
> > head needed to move. With the BDA kept correct the head can simply
> > stay on cylinder 1, and floppy_prep() seeks where it wants anyway.
> >
> > 2/3 The probe itself, now returning a status code rather than a bool.
> > Behaviour is unchanged from today apart from the wait: an empty
> > drive still answers DISK_RET_EMEDIA, just in 0.11 s instead of 10 s.
> >
> > 3/3 The error code, separate as you preferred.
> >
> > On 3/3 -- you asked whether I am sure a traditional BIOS answers differently.
> > I was not, and said so; here is what I found since.
> >
> > 0xC0 does not appear in the classic INT 13h status tables at all (helppc,
> > the Wikipedia table for AH=01h, techhelp). It has been in SeaBIOS since the
> > initial checkin f076a3ee, without a comment or a source. Bochs BIOS, which
> > SeaBIOS descends from, answers 0x80 here ("SET_AH(0x80); // drive not ready
> > (timeout)") and has no READ ID media sensing at all -- that arrived with your
> > e7c5a7ef in 2013, and DISK_RET_EMEDIA came along with it.
Just for future reference, the e7c5a7ef changes in 2013 appear to stem
from this email:
https://mail.coreboot.org/archives/list/seabios@seabios.org/thread/SGQPBJUCMPKUTC7TLCBFLXWEIS4R2VSE/
-Kevin
_______________________________________________
SeaBIOS mailing list -- seabios@seabios.org
To unsubscribe send an email to seabios-leave@seabios.org
Wrap the SEEK command and the BDA bookkeeping that goes with it, the way
floppy_drive_recal() already does for RECALIBRATE. No functional change;
the next patch gains a second caller.
Signed-off-by: Christian Quante <christian@quante.one>
---
src/hw/floppy.c | 24 +++++++++++++++++-------
1 file changed, 17 insertions(+), 7 deletions(-)
diff --git a/src/hw/floppy.c b/src/hw/floppy.c
index 9e6647d4..8f053fe3 100644
--- a/src/hw/floppy.c
+++ b/src/hw/floppy.c
@@ -416,6 +416,21 @@ floppy_drive_recal(u8 floppyid)
return DISK_RET_SUCCESS;
}
+static int
+floppy_drive_seek(u8 floppyid, u8 cylinder)
+{
+ dprintf(2, "Floppy_drive_seek %d to %d\n", floppyid, cylinder);
+ u8 param[2];
+ param[0] = floppyid;
+ param[1] = cylinder;
+ int ret = floppy_drive_pio(floppyid, FC_SEEK, param);
+ if (ret)
+ return ret;
+
+ SET_BDA(floppy_track[floppyid], cylinder);
+ return DISK_RET_SUCCESS;
+}
+
static int
floppy_drive_specify(void)
{
@@ -511,15 +526,10 @@ floppy_prep(struct drive_s *drive_gf, u8 cylinder)
}
// Seek to cylinder if needed.
- u8 lastcyl = GET_BDA(floppy_track[floppyid]);
- if (cylinder != lastcyl) {
- u8 param[2];
- param[0] = floppyid;
- param[1] = cylinder;
- int ret = floppy_drive_pio(floppyid, FC_SEEK, param);
+ if (cylinder != GET_BDA(floppy_track[floppyid])) {
+ int ret = floppy_drive_seek(floppyid, cylinder);
if (ret)
return ret;
- SET_BDA(floppy_track[floppyid], cylinder);
}
return DISK_RET_SUCCESS;
--
2.53.0
_______________________________________________
SeaBIOS mailing list -- seabios@seabios.org
To unsubscribe send an email to seabios-leave@seabios.org
floppy_prep() recalibrates and then calls floppy_media_sense(), which
issues READ ID once per data rate. A drive with no medium spins no
diskette and therefore produces no index pulses, so a real controller
never completes READ ID and every attempt waits out FLOPPY_IRQ_TIMEOUT.
Booting with an empty drive, or an INT 13h read of an empty A:, costs
about ten seconds.
The disk change line answers the same question immediately. It latches
when the medium is removed and is cleared only by a step pulse with a
diskette in the drive. A recalibrate leaves the head on cylinder 0 and
need not step at all, so seek to cylinder 1 and read DIR again. Linux's
floppy driver has done exactly this since 2.0 and never issues READ ID.
Only the 1.2MB 5.25" drive and the 3.5" drives have the change line
(CMOS types 2..5); on the older 5.25" types the pin is not driven and
reads back as a permanent "changed". Restrict the probe to those types,
and report any uncertainty as success, so that the code can only ever
fall back to the current behaviour.
The error code is unchanged: floppy_media_sense() already reports
DISK_RET_EMEDIA once it has exhausted the data rates, so an empty drive
still answers DISK_RET_EMEDIA -- just without the wait.
Measured with qemu-system-x86_64, "-machine pc -device floppy,
drive-type=144" and no medium, INT 13h AH=02h on drive A:
before 0.11 s, AH = 0x20 (controller failure)
after 0.11 s, AH = 0xC0 (no media)
QEMU completes READ ID successfully on an empty drive, which hides the
timeout; a fix for that is on qemu-devel [1]. With it applied, so that
READ ID never completes as on real hardware, the same read takes 10.12 s
before this change and 0.11 s after it. Booting from a diskette is
unaffected, including the cold start where the change line is set for a
freshly inserted medium.
[1] https://lore.kernel.org/qemu-devel/20260710113134.43012-1-christian@quante.one/
Signed-off-by: Christian Quante <christian@quante.one>
---
src/hw/floppy.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/src/hw/floppy.c b/src/hw/floppy.c
index 8f053fe3..20033fa3 100644
--- a/src/hw/floppy.c
+++ b/src/hw/floppy.c
@@ -27,6 +27,9 @@
#define PORT_FD_DATA 0x03f5
#define PORT_FD_DIR 0x03f7
+// Digital Input Register (read)
+#define FLOPPY_DIR_DSKCHG 0x80
+
#define FLOPPY_SIZE_CODE 0x02 // 512 byte sectors
#define FLOPPY_DATALEN 0xff // Not used - because size code is 0x02
#define FLOPPY_MOTOR_TICKS 37 // ~2 seconds
@@ -457,6 +460,47 @@ floppy_drive_readid(u8 floppyid, u8 data_rate, u8 head)
return 0;
}
+// Only the 1.2MB 5.25" drive and the 3.5" drives have a disk change line. On
+// the older 5.25" types the pin is not driven and reads back as a permanent
+// "changed".
+static int
+floppy_has_changeline(u8 ftype)
+{
+ return ftype >= 2 && ftype <= 5;
+}
+
+// Determine whether a diskette is present, without waiting for a command to
+// time out. The disk change line latches when the medium is removed and is
+// cleared only by a step pulse with a diskette in the drive. The caller has
+// just recalibrated, which leaves the head on cylinder 0 and need not step at
+// all, so step to cylinder 1 and look again. The head is left there; the
+// caller seeks to the cylinder it wants anyway.
+//
+// Report DISK_RET_EMEDIA only when the drive is known to be empty -- any
+// uncertainty is reported as success, so this can only ever fall back to the
+// previous behaviour.
+static int
+floppy_check_media(struct drive_s *drive_gf, u8 floppyid)
+{
+ if (!floppy_has_changeline(GET_GLOBALFLAT(drive_gf->floppy_type)))
+ return DISK_RET_SUCCESS;
+
+ // The caller has just recalibrated this drive, so it is selected in the
+ // DOR and the DIR reflects its change line.
+ if (!(inb(PORT_FD_DIR) & FLOPPY_DIR_DSKCHG))
+ return DISK_RET_SUCCESS;
+
+ int ret = floppy_drive_seek(floppyid, 1);
+ if (ret)
+ return DISK_RET_SUCCESS;
+
+ if (!(inb(PORT_FD_DIR) & FLOPPY_DIR_DSKCHG))
+ return DISK_RET_SUCCESS;
+
+ dprintf(2, "Floppy_check_media %d: no medium\n", floppyid);
+ return DISK_RET_EMEDIA;
+}
+
static int
floppy_media_sense(struct drive_s *drive_gf)
{
@@ -513,6 +557,13 @@ floppy_prep(struct drive_s *drive_gf, u8 cylinder)
if (ret)
return ret;
+ // An empty drive gives no index pulses, so the READ ID that
+ // floppy_media_sense() issues would never complete and would wait out
+ // its timeout once per data rate. Ask the disk change line instead.
+ ret = floppy_check_media(drive_gf, floppyid);
+ if (ret)
+ return ret;
+
// Sense media.
ret = floppy_media_sense(drive_gf);
if (ret)
--
2.53.0
_______________________________________________
SeaBIOS mailing list -- seabios@seabios.org
To unsubscribe send an email to seabios-leave@seabios.org
On Mon, Jul 13, 2026 at 09:35:28PM +0200, Christian Quante wrote:
> floppy_prep() recalibrates and then calls floppy_media_sense(), which
> issues READ ID once per data rate. A drive with no medium spins no
> diskette and therefore produces no index pulses, so a real controller
> never completes READ ID and every attempt waits out FLOPPY_IRQ_TIMEOUT.
> Booting with an empty drive, or an INT 13h read of an empty A:, costs
> about ten seconds.
>
> The disk change line answers the same question immediately. It latches
> when the medium is removed and is cleared only by a step pulse with a
> diskette in the drive. A recalibrate leaves the head on cylinder 0 and
> need not step at all, so seek to cylinder 1 and read DIR again. Linux's
> floppy driver has done exactly this since 2.0 and never issues READ ID.
>
> Only the 1.2MB 5.25" drive and the 3.5" drives have the change line
> (CMOS types 2..5); on the older 5.25" types the pin is not driven and
> reads back as a permanent "changed". Restrict the probe to those types,
> and report any uncertainty as success, so that the code can only ever
> fall back to the current behaviour.
>
> The error code is unchanged: floppy_media_sense() already reports
> DISK_RET_EMEDIA once it has exhausted the data rates, so an empty drive
> still answers DISK_RET_EMEDIA -- just without the wait.
>
> Measured with qemu-system-x86_64, "-machine pc -device floppy,
> drive-type=144" and no medium, INT 13h AH=02h on drive A:
>
> before 0.11 s, AH = 0x20 (controller failure)
> after 0.11 s, AH = 0xC0 (no media)
>
> QEMU completes READ ID successfully on an empty drive, which hides the
> timeout; a fix for that is on qemu-devel [1]. With it applied, so that
> READ ID never completes as on real hardware, the same read takes 10.12 s
> before this change and 0.11 s after it. Booting from a diskette is
> unaffected, including the cold start where the change line is set for a
> freshly inserted medium.
>
> [1] https://lore.kernel.org/qemu-devel/20260710113134.43012-1-christian@quante.one/
>
> Signed-off-by: Christian Quante <christian@quante.one>
> ---
> src/hw/floppy.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 51 insertions(+)
>
> diff --git a/src/hw/floppy.c b/src/hw/floppy.c
> index 8f053fe3..20033fa3 100644
> --- a/src/hw/floppy.c
> +++ b/src/hw/floppy.c
> @@ -27,6 +27,9 @@
> #define PORT_FD_DATA 0x03f5
> #define PORT_FD_DIR 0x03f7
>
> +// Digital Input Register (read)
> +#define FLOPPY_DIR_DSKCHG 0x80
> +
> #define FLOPPY_SIZE_CODE 0x02 // 512 byte sectors
> #define FLOPPY_DATALEN 0xff // Not used - because size code is 0x02
> #define FLOPPY_MOTOR_TICKS 37 // ~2 seconds
> @@ -457,6 +460,47 @@ floppy_drive_readid(u8 floppyid, u8 data_rate, u8 head)
> return 0;
> }
>
> +// Only the 1.2MB 5.25" drive and the 3.5" drives have a disk change line. On
> +// the older 5.25" types the pin is not driven and reads back as a permanent
> +// "changed".
> +static int
> +floppy_has_changeline(u8 ftype)
> +{
> + return ftype >= 2 && ftype <= 5;
> +}
> +
> +// Determine whether a diskette is present, without waiting for a command to
> +// time out. The disk change line latches when the medium is removed and is
> +// cleared only by a step pulse with a diskette in the drive. The caller has
> +// just recalibrated, which leaves the head on cylinder 0 and need not step at
> +// all, so step to cylinder 1 and look again. The head is left there; the
> +// caller seeks to the cylinder it wants anyway.
> +//
> +// Report DISK_RET_EMEDIA only when the drive is known to be empty -- any
> +// uncertainty is reported as success, so this can only ever fall back to the
> +// previous behaviour.
> +static int
> +floppy_check_media(struct drive_s *drive_gf, u8 floppyid)
> +{
> + if (!floppy_has_changeline(GET_GLOBALFLAT(drive_gf->floppy_type)))
> + return DISK_RET_SUCCESS;
> +
> + // The caller has just recalibrated this drive, so it is selected in the
> + // DOR and the DIR reflects its change line.
> + if (!(inb(PORT_FD_DIR) & FLOPPY_DIR_DSKCHG))
> + return DISK_RET_SUCCESS;
> +
> + int ret = floppy_drive_seek(floppyid, 1);
> + if (ret)
> + return DISK_RET_SUCCESS;
If floppy_drive_seek() returns an error then I think that error should
be passed up to the caller.
> +
> + if (!(inb(PORT_FD_DIR) & FLOPPY_DIR_DSKCHG))
> + return DISK_RET_SUCCESS;
> +
> + dprintf(2, "Floppy_check_media %d: no medium\n", floppyid);
> + return DISK_RET_EMEDIA;
> +}
> +
> static int
> floppy_media_sense(struct drive_s *drive_gf)
> {
> @@ -513,6 +557,13 @@ floppy_prep(struct drive_s *drive_gf, u8 cylinder)
> if (ret)
> return ret;
>
> + // An empty drive gives no index pulses, so the READ ID that
> + // floppy_media_sense() issues would never complete and would wait out
> + // its timeout once per data rate. Ask the disk change line instead.
> + ret = floppy_check_media(drive_gf, floppyid);
> + if (ret)
> + return ret;
> +
> // Sense media.
> ret = floppy_media_sense(drive_gf);
> if (ret)
FYI, this would be a change in behavior for the following FC_READID
command - it could now run with the head at cylinder 1 instead of
cylinder 0. I do not know if that important or not, but I wanted to
make sure there is awareness of it.
Thanks,
-Kevin
_______________________________________________
SeaBIOS mailing list -- seabios@seabios.org
To unsubscribe send an email to seabios-leave@seabios.org
An empty drive and an unreadable diskette are different failures, and
until the previous patch SeaBIOS could not tell them apart: both ended
up in floppy_media_sense() exhausting the data rates and returning
DISK_RET_EMEDIA (0xC0). Now that the disk change line identifies an
absent medium, report it as DISK_RET_ETIMEOUT (0x80), the code the
INT 13h documentation gives for "time out, drive not ready".
floppy_media_sense() keeps DISK_RET_EMEDIA for a medium it cannot read.
0xC0 does not appear in the classic INT 13h status tables at all, and
guests treat it accordingly. Measured on an empty drive, with SeaBIOS
patched to return each code in turn. The guests here are German-language,
so their messages are quoted as they appear, with the English sense in
brackets:
PC-DOS 7, "dir a:"
0x80 "Nicht bereit beim Lesen von Laufwerk A"
[not ready reading drive A]
0xC0 "Allgemeiner Fehler beim Lesen von Laufwerk A"
[general failure reading drive A]
0xAA same message as 0xC0
0x99 same message as 0xC0 <- invented, not a real code
0x06 no message at all; DOS retries forever
Windows for Workgroups 3.11, File Manager, drive A:
0x80 "In Laufwerk A ist kein Datenträger eingelegt. Legen Sie einen
Datenträger ein, und versuchen Sie es erneut." [Wiederholen]
[there is no disk in drive A; insert one and try again]
0xC0 "Der Datenträger in Laufwerk A ist nicht formatiert. Möchten
Sie, daß der Datenträger jetzt formatiert wird?" [Ja] [Nein]
[the disk in drive A is not formatted; format it now?]
DOS gives 0xC0 the same message as the invented code 0x99, which is to
say it does not recognise it. Windows does recognise it, and offers to
format a diskette that is not there. Both are correct with 0x80.
Bochs BIOS, from which SeaBIOS descends, returns 0x80 here as well
("SET_AH(0x80); // drive not ready (timeout)"). It has no READ ID media
sensing; that arrived in SeaBIOS with commit e7c5a7ef, and DISK_RET_EMEDIA
came with it.
OS/2 2.11 and Linux are unaffected: both drive the controller through
their own drivers rather than INT 13h.
Not verified against real hardware -- I have none with a floppy drive.
Signed-off-by: Christian Quante <christian@quante.one>
---
src/hw/floppy.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/hw/floppy.c b/src/hw/floppy.c
index 20033fa3..d7249df4 100644
--- a/src/hw/floppy.c
+++ b/src/hw/floppy.c
@@ -498,7 +498,7 @@ floppy_check_media(struct drive_s *drive_gf, u8 floppyid)
return DISK_RET_SUCCESS;
dprintf(2, "Floppy_check_media %d: no medium\n", floppyid);
- return DISK_RET_EMEDIA;
+ return DISK_RET_ETIMEOUT;
}
static int
--
2.53.0
_______________________________________________
SeaBIOS mailing list -- seabios@seabios.org
To unsubscribe send an email to seabios-leave@seabios.org
On Fri, Jul 10, 2026 at 03:18:53PM +0200, Christian Quante wrote: > floppy_prep() recalibrates and then calls floppy_media_sense(), which > issues READ ID once per data rate. A drive with no medium spins no > diskette and therefore produces no index pulses, so a real controller > never completes READ ID and every attempt waits out FLOPPY_IRQ_TIMEOUT. > Booting with an empty drive, or an INT 13h read of an empty A:, costs > about ten seconds. > > The disk change line answers the same question immediately. It latches > when the medium is removed and is cleared only by a step pulse with a > diskette in the drive. A recalibrate leaves the head on cylinder 0 and > need not step at all, so seek to cylinder 1 and read DIR again. Linux's > floppy driver has done exactly this since 2.0 and never issues READ ID. > > Only the 1.2MB 5.25" drive and the 3.5" drives have the change line > (CMOS types 2..5); on the older 5.25" types the pin is not driven and > reads back as a permanent "changed". Restrict the probe to those types, > and report any uncertainty as "medium present", so that the code can only > ever fall back to the current behaviour. > > Measured with qemu-system-x86_64, "-machine pc -device floppy, > drive-type=144" and no medium, INT 13h AH=02h on drive A: > > before 0.11 s, AH = 0x20 (controller failure) > after 0.11 s, AH = 0xC0 (no media) > > QEMU completes READ ID successfully on an empty drive, which hides the > timeout; a fix for that is on qemu-devel [1]. With it applied, so that > READ ID never completes as on real hardware, the same read takes 10.12 s > before this change and 0.11 s after it. Booting from a diskette is > unaffected, including the cold start where the change line is set for a > freshly inserted medium. > > [1] https://lore.kernel.org/qemu-devel/20260710113134.43012-1-christian@quante.one/ > > Signed-off-by: Christian Quante <christian@quante.one> Thanks. I looked through the code changes. In general it seems fine to me. I have a few minor comments/questions. Why does the code call floppy_drive_recal() a second time? It seems a little odd to recalibrate immediately after recalibrating.. As a minor thing, I think it would be preferable if floppy_media_present() returned a status code like most of the other floppy functions do (that is, return DISK_RET_SUCCESS if media detected or unknown, and return DISK_RET_EMEDIA if known to be not present). As a minor thing, I think it would be worthwhile to introduce a new floppy_drive_seek() function (including setting the BDA) and convert both code sites to use the new function. [...] > One open question: DISK_RET_EMEDIA (0xC0) is what floppy_media_sense() > already returns when it exhausts the data rates, so I kept it. A real > AT BIOS times the command out and returns 0x80 (timeout), which DOS > renders as "Not ready reading drive A" rather than "General failure". > If 0x80 is the better answer here I am happy to change it. If you are sure a traditional BIOS returns a different error code then I think it would make sense to change SeaBIOS as well. It would be preferable if it was in a separate commit (in the unlikely case it causes a regression). Thanks again, -Kevin _______________________________________________ SeaBIOS mailing list -- seabios@seabios.org To unsubscribe send an email to seabios-leave@seabios.org
On Fri, Jul 10, 2026 at 03:18:53PM +0200, Christian Quante wrote:
> floppy_prep() recalibrates and then calls floppy_media_sense(), which
> issues READ ID once per data rate. A drive with no medium spins no
> diskette and therefore produces no index pulses, so a real controller
> never completes READ ID and every attempt waits out FLOPPY_IRQ_TIMEOUT.
> Booting with an empty drive, or an INT 13h read of an empty A:, costs
> about ten seconds.
>
> The disk change line answers the same question immediately. It latches
> when the medium is removed and is cleared only by a step pulse with a
> diskette in the drive. A recalibrate leaves the head on cylinder 0 and
> need not step at all, so seek to cylinder 1 and read DIR again. Linux's
> floppy driver has done exactly this since 2.0 and never issues READ ID.
>
> Only the 1.2MB 5.25" drive and the 3.5" drives have the change line
> (CMOS types 2..5); on the older 5.25" types the pin is not driven and
> reads back as a permanent "changed". Restrict the probe to those types,
> and report any uncertainty as "medium present", so that the code can only
> ever fall back to the current behaviour.
>
> Measured with qemu-system-x86_64, "-machine pc -device floppy,
> drive-type=144" and no medium, INT 13h AH=02h on drive A:
>
> before 0.11 s, AH = 0x20 (controller failure)
> after 0.11 s, AH = 0xC0 (no media)
>
> QEMU completes READ ID successfully on an empty drive, which hides the
> timeout; a fix for that is on qemu-devel [1]. With it applied, so that
> READ ID never completes as on real hardware, the same read takes 10.12 s
> before this change and 0.11 s after it. Booting from a diskette is
> unaffected, including the cold start where the change line is set for a
> freshly inserted medium.
>
> [1] https://lore.kernel.org/qemu-devel/20260710113134.43012-1-christian@quante.one/
Thanks Christian. I must admit I'm not an expert on floppy drive
hardware. Most of the SeaBIOS floppy code was a port of the Bochs
bios code.
The last person who worked on this was Nikolay, and if I recall
correctly the code was able to run on real hardware.
Maybe Nikolay could add some comments to this proposed seabios change?
-Kevin
>
> Signed-off-by: Christian Quante <christian@quante.one>
> ---
>
> Notes for reviewers, not for the commit message:
>
> How the numbers were measured: a 512-byte boot sector issues INT 13h
> AH=02h on drive A: and then exits the VM through isa-debug-exit, so the
> time is what the guest waits, not what the harness waits. The drive is
> "-device floppy,drive-type=144" with no medium. Both BIOS images were
> built from master (c2a33ad9), which has not touched src/hw/floppy.c since
> rel-1.17.0.
>
> What motivated this: OS/2 2.11 cannot detect that a diskette has been
> removed, because QEMU answers READ ID on an empty drive. With the QEMU
> side fixed so that READ ID never completes, OS/2's driver issues one
> READ ID, waits 2.55 s, resets the controller and correctly reports "no
> diskette" -- but SeaBIOS then spends 10 s in floppy_media_sense() on
> every boot. This patch removes that cost. The two changes are useful
> independently: on today's unmodified QEMU this one already turns
> AH = 0x20 (controller failure) into AH = 0xC0 (no media).
>
> Tested: boot from a diskette; INT 13h read of a present diskette; cold
> start with a freshly inserted medium, where the change line is set and a
> naive probe would report the drive as empty; and an empty drive. Also
> exercised through PC-DOS 7, IBM DOS 5.02, Windows for Workgroups 3.11
> and OS/2 2.11.
>
> Not tested on real hardware -- I have none with a floppy drive. The
> gating on CMOS types 2..5 and the "when in doubt, say present" fallback
> are meant to make that safe, but a second opinion on drives without a
> change line would be welcome.
>
> One open question: DISK_RET_EMEDIA (0xC0) is what floppy_media_sense()
> already returns when it exhausts the data rates, so I kept it. A real
> AT BIOS times the command out and returns 0x80 (timeout), which DOS
> renders as "Not ready reading drive A" rather than "General failure".
> If 0x80 is the better answer here I am happy to change it.
>
> src/hw/floppy.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 51 insertions(+)
>
> diff --git a/src/hw/floppy.c b/src/hw/floppy.c
> index 9e6647d4..5bdd6541 100644
> --- a/src/hw/floppy.c
> +++ b/src/hw/floppy.c
> @@ -27,6 +27,9 @@
> #define PORT_FD_DATA 0x03f5
> #define PORT_FD_DIR 0x03f7
>
> +// Digital Input Register (read)
> +#define FLOPPY_DIR_DSKCHG 0x80
> +
> #define FLOPPY_SIZE_CODE 0x02 // 512 byte sectors
> #define FLOPPY_DATALEN 0xff // Not used - because size code is 0x02
> #define FLOPPY_MOTOR_TICKS 37 // ~2 seconds
> @@ -442,6 +445,48 @@ floppy_drive_readid(u8 floppyid, u8 data_rate, u8 head)
> return 0;
> }
>
> +// Only 1.2MB 5.25" and the 3.5" drives have a disk change line. On the older
> +// 5.25" types the pin is not driven and reads back as a permanent "changed".
> +static int
> +floppy_has_changeline(u8 ftype)
> +{
> + return ftype >= 2 && ftype <= 5;
> +}
> +
> +// Determine whether a diskette is present, without waiting for a command to
> +// time out. The disk change line latches when the medium is removed and is
> +// cleared only by a step pulse with a diskette in the drive. A recalibrate
> +// leaves the head on cylinder 0 and need not step at all, so step to cylinder
> +// 1 and look again. Returns 0 only when the drive is known to be empty; any
> +// uncertainty is reported as "present", leaving the caller to find out.
> +static int
> +floppy_media_present(struct drive_s *drive_gf, u8 floppyid)
> +{
> + if (!floppy_has_changeline(GET_GLOBALFLAT(drive_gf->floppy_type)))
> + return 1;
> +
> + // The caller has just recalibrated this drive, so it is selected in the
> + // DOR and the DIR reflects its change line.
> + if (!(inb(PORT_FD_DIR) & FLOPPY_DIR_DSKCHG))
> + return 1;
> +
> + u8 param[2];
> + param[0] = floppyid;
> + param[1] = 1;
> + int ret = floppy_drive_pio(floppyid, FC_SEEK, param);
> + if (ret)
> + return 1;
> + int present = !(inb(PORT_FD_DIR) & FLOPPY_DIR_DSKCHG);
> +
> + // Put the head back on cylinder 0 and fix up the BDA.
> + ret = floppy_drive_recal(floppyid);
> + if (ret)
> + return 1;
> +
> + dprintf(2, "Floppy_media_present %d: %d\n", floppyid, present);
> + return present;
> +}
> +
> static int
> floppy_media_sense(struct drive_s *drive_gf)
> {
> @@ -498,6 +543,12 @@ floppy_prep(struct drive_s *drive_gf, u8 cylinder)
> if (ret)
> return ret;
>
> + // An empty drive gives no index pulses, so READ ID would never
> + // complete and media sensing would wait out its timeout once per
> + // data rate. Ask the disk change line instead.
> + if (!floppy_media_present(drive_gf, floppyid))
> + return DISK_RET_EMEDIA;
> +
> // Sense media.
> ret = floppy_media_sense(drive_gf);
> if (ret)
> --
> 2.53.0
>
_______________________________________________
SeaBIOS mailing list -- seabios@seabios.org
To unsubscribe send an email to seabios-leave@seabios.org
On 7/11/26 3:20 AM, Kevin O'Connor wrote:
> On Fri, Jul 10, 2026 at 03:18:53PM +0200, Christian Quante wrote:
>> floppy_prep() recalibrates and then calls floppy_media_sense(), which
>> issues READ ID once per data rate. A drive with no medium spins no
>> diskette and therefore produces no index pulses, so a real controller
>> never completes READ ID and every attempt waits out FLOPPY_IRQ_TIMEOUT.
>> Booting with an empty drive, or an INT 13h read of an empty A:, costs
>> about ten seconds.
>>
>> The disk change line answers the same question immediately. It latches
>> when the medium is removed and is cleared only by a step pulse with a
>> diskette in the drive. A recalibrate leaves the head on cylinder 0 and
>> need not step at all, so seek to cylinder 1 and read DIR again. Linux's
>> floppy driver has done exactly this since 2.0 and never issues READ ID.
>>
>> Only the 1.2MB 5.25" drive and the 3.5" drives have the change line
>> (CMOS types 2..5); on the older 5.25" types the pin is not driven and
>> reads back as a permanent "changed". Restrict the probe to those types,
>> and report any uncertainty as "medium present", so that the code can only
>> ever fall back to the current behaviour.
>>
>> Measured with qemu-system-x86_64, "-machine pc -device floppy,
>> drive-type=144" and no medium, INT 13h AH=02h on drive A:
>>
>> before 0.11 s, AH = 0x20 (controller failure)
>> after 0.11 s, AH = 0xC0 (no media)
>>
>> QEMU completes READ ID successfully on an empty drive, which hides the
>> timeout; a fix for that is on qemu-devel [1]. With it applied, so that
>> READ ID never completes as on real hardware, the same read takes 10.12 s
>> before this change and 0.11 s after it. Booting from a diskette is
>> unaffected, including the cold start where the change line is set for a
>> freshly inserted medium.
>>
>> [1] https://lore.kernel.org/qemu-devel/20260710113134.43012-1-christian@quante.one/
> Thanks Christian. I must admit I'm not an expert on floppy drive
> hardware. Most of the SeaBIOS floppy code was a port of the Bochs
> bios code.
>
> The last person who worked on this was Nikolay, and if I recall
> correctly the code was able to run on real hardware.
>
> Maybe Nikolay could add some comments to this proposed seabios change?
Unfortunately, the motherboard with the FDC controller that I was using
with SeaBIOS died (probably due to bad electrolytic capacitors), so I
can no longer test with a real floppy. :(
I remember there was indeed a bug with the BIOS not reporting the
correct error when there is no diskette present in the drive. Instead of
"Not ready error reading drive A:" in DOS, I got "General failure
reading drive A:" or something of that sort.
I still have floppies and a working motherboard with a floppy controller
(ASRock 939N68PV-GLAN), so I can test it in theory, but there's no
coreboot support for the motherboard I have, so I need to port coreboot
first, which will be fun to do, but it'll take time. :(
Is there any way to test the SeaBIOS code without coreboot, in other
words, boot with the proprietary AMI BIOS and then load SeaBIOS into
shadow RAM with some DOS utility and then boot again?
Nikolay
>
> -Kevin
>
>
>
>> Signed-off-by: Christian Quante <christian@quante.one>
>> ---
>>
>> Notes for reviewers, not for the commit message:
>>
>> How the numbers were measured: a 512-byte boot sector issues INT 13h
>> AH=02h on drive A: and then exits the VM through isa-debug-exit, so the
>> time is what the guest waits, not what the harness waits. The drive is
>> "-device floppy,drive-type=144" with no medium. Both BIOS images were
>> built from master (c2a33ad9), which has not touched src/hw/floppy.c since
>> rel-1.17.0.
>>
>> What motivated this: OS/2 2.11 cannot detect that a diskette has been
>> removed, because QEMU answers READ ID on an empty drive. With the QEMU
>> side fixed so that READ ID never completes, OS/2's driver issues one
>> READ ID, waits 2.55 s, resets the controller and correctly reports "no
>> diskette" -- but SeaBIOS then spends 10 s in floppy_media_sense() on
>> every boot. This patch removes that cost. The two changes are useful
>> independently: on today's unmodified QEMU this one already turns
>> AH = 0x20 (controller failure) into AH = 0xC0 (no media).
>>
>> Tested: boot from a diskette; INT 13h read of a present diskette; cold
>> start with a freshly inserted medium, where the change line is set and a
>> naive probe would report the drive as empty; and an empty drive. Also
>> exercised through PC-DOS 7, IBM DOS 5.02, Windows for Workgroups 3.11
>> and OS/2 2.11.
>>
>> Not tested on real hardware -- I have none with a floppy drive. The
>> gating on CMOS types 2..5 and the "when in doubt, say present" fallback
>> are meant to make that safe, but a second opinion on drives without a
>> change line would be welcome.
>>
>> One open question: DISK_RET_EMEDIA (0xC0) is what floppy_media_sense()
>> already returns when it exhausts the data rates, so I kept it. A real
>> AT BIOS times the command out and returns 0x80 (timeout), which DOS
>> renders as "Not ready reading drive A" rather than "General failure".
>> If 0x80 is the better answer here I am happy to change it.
>>
>> src/hw/floppy.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++
>> 1 file changed, 51 insertions(+)
>>
>> diff --git a/src/hw/floppy.c b/src/hw/floppy.c
>> index 9e6647d4..5bdd6541 100644
>> --- a/src/hw/floppy.c
>> +++ b/src/hw/floppy.c
>> @@ -27,6 +27,9 @@
>> #define PORT_FD_DATA 0x03f5
>> #define PORT_FD_DIR 0x03f7
>>
>> +// Digital Input Register (read)
>> +#define FLOPPY_DIR_DSKCHG 0x80
>> +
>> #define FLOPPY_SIZE_CODE 0x02 // 512 byte sectors
>> #define FLOPPY_DATALEN 0xff // Not used - because size code is 0x02
>> #define FLOPPY_MOTOR_TICKS 37 // ~2 seconds
>> @@ -442,6 +445,48 @@ floppy_drive_readid(u8 floppyid, u8 data_rate, u8 head)
>> return 0;
>> }
>>
>> +// Only 1.2MB 5.25" and the 3.5" drives have a disk change line. On the older
>> +// 5.25" types the pin is not driven and reads back as a permanent "changed".
>> +static int
>> +floppy_has_changeline(u8 ftype)
>> +{
>> + return ftype >= 2 && ftype <= 5;
>> +}
>> +
>> +// Determine whether a diskette is present, without waiting for a command to
>> +// time out. The disk change line latches when the medium is removed and is
>> +// cleared only by a step pulse with a diskette in the drive. A recalibrate
>> +// leaves the head on cylinder 0 and need not step at all, so step to cylinder
>> +// 1 and look again. Returns 0 only when the drive is known to be empty; any
>> +// uncertainty is reported as "present", leaving the caller to find out.
>> +static int
>> +floppy_media_present(struct drive_s *drive_gf, u8 floppyid)
>> +{
>> + if (!floppy_has_changeline(GET_GLOBALFLAT(drive_gf->floppy_type)))
>> + return 1;
>> +
>> + // The caller has just recalibrated this drive, so it is selected in the
>> + // DOR and the DIR reflects its change line.
>> + if (!(inb(PORT_FD_DIR) & FLOPPY_DIR_DSKCHG))
>> + return 1;
>> +
>> + u8 param[2];
>> + param[0] = floppyid;
>> + param[1] = 1;
>> + int ret = floppy_drive_pio(floppyid, FC_SEEK, param);
>> + if (ret)
>> + return 1;
>> + int present = !(inb(PORT_FD_DIR) & FLOPPY_DIR_DSKCHG);
>> +
>> + // Put the head back on cylinder 0 and fix up the BDA.
>> + ret = floppy_drive_recal(floppyid);
>> + if (ret)
>> + return 1;
>> +
>> + dprintf(2, "Floppy_media_present %d: %d\n", floppyid, present);
>> + return present;
>> +}
>> +
>> static int
>> floppy_media_sense(struct drive_s *drive_gf)
>> {
>> @@ -498,6 +543,12 @@ floppy_prep(struct drive_s *drive_gf, u8 cylinder)
>> if (ret)
>> return ret;
>>
>> + // An empty drive gives no index pulses, so READ ID would never
>> + // complete and media sensing would wait out its timeout once per
>> + // data rate. Ask the disk change line instead.
>> + if (!floppy_media_present(drive_gf, floppyid))
>> + return DISK_RET_EMEDIA;
>> +
>> // Sense media.
>> ret = floppy_media_sense(drive_gf);
>> if (ret)
>> --
>> 2.53.0
>>
_______________________________________________
SeaBIOS mailing list -- seabios@seabios.org
To unsubscribe send an email to seabios-leave@seabios.org
On Sat, Jul 11, 2026 at 05:45:43PM +0300, Nikolay Nikolov wrote: > On 7/11/26 3:20 AM, Kevin O'Connor wrote: > > The last person who worked on this was Nikolay, and if I recall > > correctly the code was able to run on real hardware. > > > > Maybe Nikolay could add some comments to this proposed seabios change? [...] > Is there any way to test the SeaBIOS code without coreboot, in other > words, boot with the proprietary AMI BIOS and then load SeaBIOS into > shadow RAM with some DOS utility and then boot again? Thanks for following up. Alas, I'm not aware of a way to do that. I'm sure one could write a dos program to talk directly with the floppy controller, but I suspect that would be a lot of work. Cheers, -Kevin _______________________________________________ SeaBIOS mailing list -- seabios@seabios.org To unsubscribe send an email to seabios-leave@seabios.org
© 2016 - 2026 Red Hat, Inc.