From nobody Sat Jul 25 20:10:53 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of seabios.org designates 78.46.105.101 as permitted sender) client-ip=78.46.105.101; envelope-from=seabios-bounces@seabios.org; helo=coreboot.org; Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of seabios.org designates 78.46.105.101 as permitted sender) smtp.mailfrom=seabios-bounces@seabios.org Return-Path: Received: from coreboot.org (mail.coreboot.org [78.46.105.101]) by mx.zohomail.com with SMTPS id 1783693311846831.2171227966094; Fri, 10 Jul 2026 07:21:51 -0700 (PDT) Received: from authenticated-user (PRIMARY_HOSTNAME [PUBLIC_IP]) by coreboot.org (Postfix) with ESMTPA id A889D54E1E1; Fri, 10 Jul 2026 14:21:47 +0000 (UTC) Received: from authenticated-user (PRIMARY_HOSTNAME [PUBLIC_IP]) (nx104.node01.secure-mailgate.com [89.22.108.104]) by coreboot.org (Postfix) with ESMTP id BE1AE54E1B5 for ; Fri, 10 Jul 2026 14:21:21 +0000 (UTC) Received: from authenticated-user (PRIMARY_HOSTNAME [PUBLIC_IP]) by node01.secure-mailgate.com with esmtps (TLS1.2) tls TLS_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1wiB8k-005b0e-Pd; Fri, 10 Jul 2026 15:19:40 +0200 Received: from authenticated-user (PRIMARY_HOSTNAME [PUBLIC_IP]) (Authenticated sender: christian@quante.one) by web279.dogado.net (Postfix) with ESMTPSA id CC74F2813EE; Fri, 10 Jul 2026 15:19:37 +0200 (CEST) X-SecureMailgate-Identity: christian@quante.one;web279.dogado.net DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=quante.one; s=cloudpit; t=1783689578; bh=mYmGqxv2lxKznvQPs1kS/XW233pMqrPx+hd9W+5PCbY=; h=From:To:Cc:Subject:Date:From; b=CYpPEa1HI+NmRL0lCRujIYDSXSOYHFyrk49rlGvJg9UiZGUw35JYiZLzweUpa/eKA 1zn4KTFtLxrSQ6HYA/gOowcpgNHBDU6hRVJ/fZnde8JIpLP3r64w8BVN5PA5WP4BZS cCvD5N3skYETiDQs/nNNDRIxDbII6t9a1FpLhZAM= X-SecureMailgate-Identity: christian@quante.one;web279.dogado.net From: Christian Quante To: seabios@seabios.org Date: Fri, 10 Jul 2026 15:18:53 +0200 Message-ID: <20260710131853.59862-1-christian@quante.one> MIME-Version: 1.0 X-PPP-Message-ID: <178368957843.2768073.14927914211030071159@web279.dogado.net> X-PPP-Vhost: quante.one X-SecureMailgate-Domain: web279.dogado.net X-SecureMailgate-Username: 31.47.255.59 X-SecureMailgate-Outgoing-Class: ham X-SecureMailgate-Outgoing-Evidence: SB/global_tokens (0.00435290280548) X-Recommended-Action: accept X-Filter-ID: 9kzQTOBWQUFZTohSKvQbgI7ZDo5ubYELi59AwcWUnuWSyiUBlsxNdy946XzjwJ3aN1sirZdH4nk2 P4GsTcIkkiu2SmbhJN1U9FKs8X3+Nt1HOctZ5X+40a9IXddeG3M38JQYgcgjsac3uQcQdCX4ROfH zJ6mVE7ewsipSVIfs4Z34fi95t6WoFzmdaFHmcMLS5+h1uiHzk/Rfxfh461YLyu3RZI1CD15pCOE 1vk99RPdEorPfEdXy94XZtjluxSTdOtYechJ/ikE1WgRg4cS6K5mANmx2g/2xIniaWLUPf0al0h7 43VOpKNY+4MjijihZK0EMmf3P50Ajf/tIX00Fuu2beRqilmjgQ6jA3UwYgm3QTha208ypFqQwBFb xm4JX7semJdl8Weuy8+tlMSTQwhoy9D5QZ6a84vJal7pM1XhlAOd290vPXa7Loma0jth7qAXzi6H u+vd1PUHqD56sKVpCSy8DvYjml2C5x08HjVOdbycUIjxTi/oScdKqdkqU23mck4kYaL7W/YjuX9F gtaPrsMji/nLJj58tIE9bJtlIqecxOBP1yJf91UNzkSEUDq6mKQ/XAiI42Mzh38yXz3dKxLhoxcm aInYbR5vlqHlDhVAB/0EYD/jnz2OZJv6szlVwj1n2JAvHUSGQgae904HZe+Npbm5PA9zxaDCuu7n enbrzWUw2X62ZQfetBN+Wd6yNxOSUIsmGCJDuSiGSNdj68Lf4FNI5GmjYNknO4WLIFZtYLHBUTps l3R7CReAwrX6eL0Un5Uudpq1owyUV+9GdUvJJyfDkf9EGzbG3sA9tQrhDVxWCGj1JArKPJWkG7EB ouE/Lp65oEK6Kr4PFISAzVaFNHfkjGyFyiRzr7YOhfiboNBv0POUVLQJNXGP/GGMrN0VgEP1fddi hPZrzx1Lw2yMf4vpvhlM+vErQ1gn6IjLLbcvolUaG2pSKCEkiWrFfcvTzpdI3AEG059XQfQRPohi F+SQcSjgcNhW0yN3OnKG9eRDP2/Ogb+c8D2oSvdcBJ1869L2y5ERFHC8NPIK4B7OUVSn0kbL6D23 dfd3aH2fATPqEIe+zbt2NwyT7DYzFfTn4NXnBjEun9DvZ0n9a1rfFCnSMsc+vEwrNstTbe4wBw6S kC2jAENWaINDgXBbKTIlqNpRK89muFbKwBsa9A+nmEd7cRVfyxrpZ1WT35ix5VehBc2cUR742vO7 eUcOGCmJTaorUhtlNRknxw== X-Report-Abuse-To: spam@node04.secure-mailgate.com X-Complaints-To: abuse@node01.secure-mailgate.com X-Spam-Level: **** Message-ID-Hash: K2QGADPOTAEEZRQS5T6PKZLDQUBO62HY X-Message-ID-Hash: K2QGADPOTAEEZRQS5T6PKZLDQUBO62HY X-MailFrom: christian@quante.one X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; header-match-seabios.seabios.org-0; header-match-seabios.seabios.org-1; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header Cc: Gerd Hoffmann , Christian Quante X-Mailman-Version: 3.3.11b1 Precedence: list Subject: [SeaBIOS] [PATCH] floppy: detect an empty drive via the disk change line List-Id: SeaBIOS mailing list Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Content-Transfer-Encoding: quoted-printable Authentication-Results: coreboot.org; auth=pass smtp.auth=mailman@coreboot.org smtp.mailfrom=seabios-bounces@seabios.org X-Spamd-Bar: / X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1783693315105158500 Content-Type: text/plain; charset="utf-8" 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=3D144" and no medium, INT 13h AH=3D02h on drive A: before 0.11 s, AH =3D 0x20 (controller failure) after 0.11 s, AH =3D 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@qua= nte.one/ Signed-off-by: Christian Quante --- Notes for reviewers, not for the commit message: How the numbers were measured: a 512-byte boot sector issues INT 13h AH=3D02h 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=3D144" 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 =3D 0x20 (controller failure) into AH =3D 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 =20 +// 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; } =20 +// Only 1.2MB 5.25" and the 3.5" drives have a disk change line. On the o= lder +// 5.25" types the pin is not driven and reads back as a permanent "change= d". +static int +floppy_has_changeline(u8 ftype) +{ + return ftype >=3D 2 && ftype <=3D 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 recalibra= te +// leaves the head on cylinder 0 and need not step at all, so step to cyli= nder +// 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 t= he + // DOR and the DIR reflects its change line. + if (!(inb(PORT_FD_DIR) & FLOPPY_DIR_DSKCHG)) + return 1; + + u8 param[2]; + param[0] =3D floppyid; + param[1] =3D 1; + int ret =3D floppy_drive_pio(floppyid, FC_SEEK, param); + if (ret) + return 1; + int present =3D !(inb(PORT_FD_DIR) & FLOPPY_DIR_DSKCHG); + + // Put the head back on cylinder 0 and fix up the BDA. + ret =3D 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; =20 + // 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 =3D floppy_media_sense(drive_gf); if (ret) --=20 2.53.0 _______________________________________________ SeaBIOS mailing list -- seabios@seabios.org To unsubscribe send an email to seabios-leave@seabios.org