I made a new disk image with the command dd if=/dev/zero of=hd.img bs=512 count=$(expr 16 \* 63 \* \( 15 \* 16 + 8 \)) which corresponds to creating a disk that would fill exactly with QEMU's default disk geometry of 16 heads and 63 sectors per track. The number of cylinders is clearly 0xF8. I then verified the image is the expected number of bytes.

The disk was then loaded into qemu with -drive file=hd.img,format=raw,index=0,media=disk

However when probing the disk from inside with interrupt 0x13 function 0x08 I get rather this output:

    DH = 0x0F
    CL = 0x3F
    CH = 0xF6

but I really expect CH to be 0xF7 which is the last cylinder number when there are 0xF8 cylinders.

  • You can find out the answer by doing the following: Create an image of the size which QEMU recognizes as the desired HD configuration, write a unique pattern into each sector using a program run from within QEMU, then look at the image from the host OS to find out which areas of the image were not touched.
    – Leo B.
    Commented Aug 29, 2021 at 2:27
  • 1
    Tangentially, dd if=/dev/null of=hd.img bs=512 count=0 seek=$(( 16 * 63 * 0xf8 )) would have been faster, as it creates a sparse file instead of copying small blocks of zeroes (and avoids spawning a subprocess for the calculation). Commented Aug 29, 2021 at 8:05
  • @Justme It relates to retrocomputing because the 16-bit ROM BIOS and CHS addressing is an old interface, and even a modern implementation thereof is heavily constrained by the design parameters of retro hardware and compatibility with retro software (e.g. the 256 heads bug). Commented Aug 29, 2021 at 14:23

1 Answer 1


QEMU is not at fault here. The discrepancy comes from the BIOS.

When I create a disk image as described in the question, then attach it to a QEMU virtual machine and use the info qtree command in the monitor, this is the geometry I see reported:

            cyls = 248 (0xf8)
            heads = 16 (0x10)
            secs = 63 (0x3f)

The same geometry is reported to the guest with the ATA IDENTIFY command. The reason for the discrepancy is what the BIOS firmware does with that hardware-reported geometry; this can be seen within the source code of SeaBIOS, which is the BIOS implementation used in QEMU. The disk_1308 function contains this code:

// read disk drive parameters
static void noinline
disk_1308(struct bregs *regs, struct drive_s *drive_fl)
    // Get logical geometry from table
    struct chs_s chs = getLCHS(drive_fl);
    u16 nlc=chs.cylinder, nlh=chs.head, nls=chs.sector;
    u8 count;
    if (regs->dl < EXTSTART_HD) {
        // Floppy
        /* [...] */
    } else if (regs->dl < EXTSTART_CD) {
        // Hard drive
        count = GET_BDA(hdcount);
        nlc--;  // last sector reserved
    } else {
        /* [...] */

    /* [...] */

    regs->al = 0;
    regs->ch = nlc & 0xff;
    regs->cl = ((nlc >> 2) & 0xc0) | (nls & 0x3f);
    regs->dh = nlh;
    disk_ret(regs, DISK_RET_SUCCESS);
    regs->dl = count;

The interrupt 0x13 service 8 handler reads off the logical geometry from an internal data structure. Since the call is supposed to return the maximum CHS coordinates, the numbers of cylinders and heads are decremented by one, as those coordinates are zero-based. If the block device is a hard disk, the maximum cylinder number is decremented again. The latter is accompanied by a rather laconic comment: ‘last sector reserved’. What could that mean?

Ralf Brown’s entry on that service warns us that:

the maximum cylinder number reported in CX is usually two less than the total cylinder count reported in the fixed disk parameter table (see INT 41h,INT 46h) because early hard disks used the last cylinder for testing purposes; however, on some Zenith machines, the maximum cylinder number reportedly is three less than the count in the fixed disk parameter table.

for BIOSes which reserve the last cylinder for testing purposes, the cylinder count is automatically decremented

So apparently there is some historical precedent for this: some BIOSes would reserve the last cylinder of the drive for their own purposes, and as such, hide its existence from programs querying the drive geometry. This agrees with SeaBIOS’s behaviour: if we look up the geometry in the FDPT, we can see it contains the true number of cylinders.

The key word here seems to be ‘usually’, which implies this behaviour is optional, at the BIOS’s discretion. Nevertheless, it seems to have been pretty common indeed: nearly every BIOS firmware I tested under PCem does in fact reserve the last cylinder, including IBM’s; the only exception being Award BIOS v4.60PGA of a FIC VA-503+ motherboard from 2001, which is pretty late in PC history. All the others I checked report one fewer cylinder than there were configured in the emulator.

The Large Disk HOWTO elaborates a little more on what the reservation was used for and what effects it had on reporting disk size:

Many old IBM PS/2 systems used disks with a defect map written to the end of the disk. (Bit 0x20 in the control word of the disk parameter table is set.) Therefore, FDISK would not use the last cylinder. Just to be sure, the BIOS often already reports the size of the disk as one cylinder smaller than reality, and that may mean that two cylinders are lost. Newer BIOSes have several disk size reporting functions, where internally one calls the other. When both subtract 1 for this reserved cylinder and also FDISK does so, then one may lose three cylinders. These days all of this is irrelevant, but this may provide an explanation if one observes that different utilities have slightly different opinions about the disk size.

It does not seem that SeaBIOS actually makes use of that reservation anywhere though, so there is no reason service 8 should not report the true (emulated) geometry. As such, it is not unreasonable to consider it a bug after all. While there might conceivably be software in the wild that relies on this, by blindly attempting to undo the last-cylinder reservation, I think the probability of such being found is rather small. (I would expect software interested in the true disk geometry to consult the FDPT instead anyway.)

  • It wouldn’t even be the only such case: <review.coreboot.org/plugins/gitiles/seabios/+/…> appears to blindly ape what <fd.lod.bz/rbil/interrup/video/1000.html> says about the AL value at return in certain Phoenix/AMI BIOSes, even though this isn’t even consistent with all the other video BIOSes, and modern implementations just preserve register state. Commented Aug 29, 2021 at 14:02
  • So I'm considering this a bug because it maxes no sense to reserve a cylinder for testing purposes on a virtual machine.
    – Joshua
    Commented Aug 29, 2021 at 14:18
  • @Joshua Though it’s primarily used with QEMU, SeaBIOS does target physical machines as well. Commented Aug 29, 2021 at 14:29
  • 1
    I actually have software that blindly assumes it; testing purposes might not be correct. The software moves the heads to the last cluster just before shutdown (it also knows the way-beyond-last-cluster number on really old disks to move the heads completely off the platters) before shipping the disks. Again, completely irrelevant to a virtual machine.
    – Joshua
    Commented Aug 29, 2021 at 14:38
  • @Joshua Now that I tested this under PCem, I am having second thoughts as to how warranted this behaviour is. I wonder if it persisted in later BIOSes just for the sake of ‘tradition’/the precautionary principle, or because some IDE hard drives still internally implemented parking the head by resting it on the last (actually user-visible) cylinder… Commented Aug 30, 2021 at 19:08

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .