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
© 2016 - 2026 Red Hat, Inc.