Psuedo code (version: Serial ATA AHCI BIOS, Version iSrc 1.20_E.0019 07092009):
Function Create TDPT for drive
Read partition table with INT13 0x201
If read fails, or 0xAA55 signature isn't present, goto Calculate
Get head,sectors from FIRST Partition table entry, Ending CHS values
heads = head+1 (because of 255 limit in partition table)
For each partition entry
If BOOTABLE (entry[0] == 0x80) goto UsePartition
EndFor
For each partition entry
if (entry[0] == 0) and (entry[4] != 0) goto UsePartition
EndFor
Calculate
Call CalculateCHS - using DPT and physical size
if no need for translation, return GOOD
Goto CreateTDPT with cylinders, heads, sectors
UsePartition:
Read first sector of partition with INT13 0x4200
If the word at offset 0x1A is less than 0x100,
and the word at offset 0x18 is less than 0x40
then
set heads to the byte at offset 0x1A
set sectors to the byte at offset 0x18
fi
tracksize = heads * sectors
if tracksize == 0, Goto Calculate
DWORD size = (DPT[heads]*DPT[sectors])*DPT[cylinders]
WORD cylinders = size / tracksize <--- Bad!
-- if the result is greater than 65536, a divide overflow occurs
-- which isn't handled by the BIOSes
if (cylinders > 1024) cylinders = 1024if ((heads == DPT[heads]) && (sectors == DPT[sectors])) return GOOD
CreateTDPT:
WORD at DPT[8] = DPT[0] - Save original cylinders
BYTE at DPT[10] = DPT[2] - Save original heads
BYTE at DPT[7] = DPT[3] - Save original sectors
WORD at DPT[0] = cylinders
BYTE at DPT[2] = heads
BYTE at DPT[3] = sectors
BYTE at DPT[5] = 8 if heads greater than 8, otherwise 0
BYTE at DPT[4] = 0xA0
BYTE at DPT[15] = SUM( DPT[0] .. DPT[14] )
So, what goes wrong? When it breaks, it starts with bad values from the partition table, and tries to fix it with values from the boot parameter block, if it finds "valid" numbers there for heads and sectors (that is, less than or equal to 0xFF and 0x3F, respectively). When these values aren't right, due to full disk encryption, an operating system other than Microsoft Windows, or malicious intent:
- It uses the ending head/sector of the first partition to size the translation layer.
- Windows 7 with 100MB partition results in unexpected values for INT13, FUNCTION=8 (eg, 0x13 heads).
- It stores those values into the Translated Device Parameter Table.. and then some other code comes along and uses those values. While I can't find where those values are causing the exception, anything doing C/H/S translation will be unhappy.
Looking back at version Serial ATA AHCI BIOS, Version iSrc 1.20E (Gigabyte Desktop Motherboard), I found that it doesn't read from the BPB at all. I speculate the extra read of the NTFS boot-sector was to workaround a problem on Insyde BIOS. This version can be crashed if the two bytes in the partition table are small enough and will hang with error code 23. Award BIOS will function OK with the other unexpected values, but Insyde BIOS will still crash if it sees them.
Finally, one HP system with an Insyde BIOS has the latest(?) 'fixed' version (Serial ATA AHCI BIOS, Version iSrc 1.20_E.0024 12212009), which reads from both the partition table and the BPB, also adding still more checks. Unfortunately, it seems as though someone messed up and added a further bug, as it doesn't actually use any of the values it reads, but rather discards them all.
New and improved UsePartition (Serial ATA AHCI BIOS, Version iSrc 1.20_E.0024 12212009):
Read first sector of partition with INT13 0x4200
if the word at offset 0x1FE is not equal 0xAA55
and the byte at offset 0 is not equal 0xEB
and the word at offset 0x1A is less than 0x100
then
set heads to the byte at offset 0x1A
fi
if (tracks == 0) or ((sectors & 0x3F) == 0) Goto Calculate
-- New bug: Since sectors can be at most 0x3F from partition table
-- the newer version ALWAYS goes off to Calculate the CHS
if (sectors & 0xC0) == 0) Goto Calculate
tracksize = heads * sectors
if tracksize == 0, Goto Calculate
DWORD size = (DPT[heads]*DPT[sectors])*DPT[cylinders]
WORD cylinders = size / tracksize <-- Uber dangerous
-- if the result is greater than 65536, a divide overflow occurs
-- which isn't handled by the BIOSes.
if (cylinders > 1024) cylinders = 1024
if ((heads == DPT[heads]) && (sectors == DPT[sectors])) return GOOD
Goto CreateTDPT
A year later and neither Acer nor Gigabyte are providing fixed BIOSes.
1Expected Final TDPT Values from a 60GB SSD:
WORD Logical Cylinders 0x400
BYTE Heads 0xFF
BYTE Sectors 0x3F
BYTE Signature 0xA0
BYTE HeadsAbove8Flag 0x08
BYTE Ignored 0x00
BYTE Physical Sectors 0x3F
WORD Physical Cylinders 0x3FFF
BYTE Physical Heads 0x10
BYTE Ignored[4] 0x0
BYTE Checksum 0x89