Fdisk Outputting Absurd CHS Values – Troubleshooting

fdiskmbrpartitioning

Take the following fdisk input:

o # Create DOS/MBR partition table.
n # Create new partition.
    p # Partition type
    1 # Partition ID
    2048 # Starting sector
    +4M # Ending sector
t # Assign said partition to a FAT12 filesystem.
    1 # FAT12 filesystem.
a # Mark said partition as bootable.
w # Write partition table.

Now, let's take a look at the MBR, specifically of the partition entry (partition entry starts at 0x80 near the end of the first line).

000001b0: 0000 0000 0000 0000 4a2f 9087 0000 8020  ........J/.....
000001c0: 2100 01a2 2200 0008 0000 0020 0000 0000  !..."...... ....
000001d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000001e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000001f0: 0000 0000 0000 0000 0000 0000 0000 55aa  ..............U.

According to the layout of the MBR portion entries (given here and here), the values we can glean the CHS tuple from are 0x20, 0x21, 0x00.

0x20 is rather simple: it equates to the 32nd head.

0x21 contains both the starting sector and the starting cylinder. 0x21 -> 0b00100001, giving 33 as the sector, and the 8th and the 9th bits of the cylinder resulting in 0 (0b00).

Combining the cylinder bits of 0b00 (from the 0x21) and 0b00000000, we get 0b0000000000 as the cylinder. All in all, we end up with a starting head of 32, a starting sector of 33, and a starting cylinder of 0.

When I try and load this CHS via BIOS interrupts, it complains that the CHS is invalid (I can read other sectors just fine). I assume this is because the head is 32; after all, why would you have a 16-platter hard disk?

tl;dr

Fdisk outputs MBR with CHS values.

My question is if I'm misunderstanding how the CHS value is encoded into the partition entry or if this is a quirk of fdisk.

Best Answer

According to fdisk itself, the C/H/S values are indeed supposed to be:

Command (m for help): x

Expert command (m for help): p

Disk test.disk: 64 MiB, 67108864 bytes, 131072 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xd771f127

Device     Boot Start   End Sectors Id Type  Start-C/H/S End-C/H/S Attrs
test.disk1 *     2048 10239    8192  1 FAT12     0/32/33  0/162/34    80

fdisk doesn't try to match the actual disk geometry – this has long been impossible within the C/H/S limits, so all it does is compute some values that would give the correct result when converted back to LBA.

Code from util-linux include/pt-mbr.h:

static inline void
dos_partition_sync_chs(struct dos_partition *p,
                       unsigned long long int part_offset,
                       unsigned int geom_sectors,
                       unsigned int geom_heads)
{
        unsigned long long int start = part_offset + dos_partition_get_start(p);
        unsigned long long int stop = start + dos_partition_get_size(p) - 1;
        unsigned int spc = geom_heads * geom_sectors;

        if (start / spc > 1023)
                start = spc * 1024 - 1;
        if (stop / spc > 1023)
                stop = spc * 1024 - 1;

        p->bc = (start / spc) & 0xff;
        p->bh = (start / geom_sectors) % geom_heads;
        p->bs = ((start % geom_sectors + 1) & 0x3f) |
                (((start / spc) >> 2) & 0xc0);

        p->ec = (stop / spc) & 0xff;
        p->eh = (stop / geom_sectors) % geom_heads;
        p->es = ((stop % geom_sectors + 1) & 0x3f) |
                (((stop / spc) >> 2) & 0xc0);
}

Comment from util-linux libfdisk/src/dos.c:

 /*
  * Conversion from C/H/S to LBA is defined by formula:
  *   LBA = (c * N_heads + h) * N_sectors + (s - 1)
  * Let o to be:
  *   o = LBA - (s - 1)
  * Then formula can be expressed as:
  *   o = (c * N_heads + h) * N_sectors
  */

why would you have a 16-platter hard disk?

You pretend to have a 16-platter hard disk in order to represent more disk space than the C/H/S system could normally represent. With only 64ki cylinders × 255 sectors × 2 heads per platter, all you can reach is ~8 GB per platter and obviously disks have slightly outgrown that by now.

The OS or firmware doesn't control each head individually, it just passes the numbers to the disk via ATA commands and gets data back, so it doesn't really care whether the disk somehow has 255 heads or not. And neither does the disk itself, anymore – the first thing disks now do is use a formula to convert the C/H/S values to linear LBA addresses (assuming the disk was given C/H/S and not LBA to begin with).

So even before BIOSes and OSes switched to dealing directly with LBA, it was already common to use "absurd" C/H/S values in order to access more disk space that the format would normally allow.

All current operating systems just ignore the C/H/S fields in MBRs and deal with LBA exclusively, and apparently that's what "OnTrack Disk Manager" used to do in MS-DOS. (Also, as far as I know, SCSI disks never supported C/H/S to begin with, they were always LBA-only.) I'm somewhat sure that just about any BIOS you'll find these days has native support for LBA via "extended INT 13h" (as Google tells me).

Related Question