问题
I know that you need the INT 13H extended functions to access drives over 8GB in size. This question refers to standard INT 13H, function 02H.
I also know the old 504MB hard drive limit was a result of: 1024 cylinders x 16 heads x 63 sectors x 512 bytes = 528,482,304 bytes.
But was this hard drive limitation caused by Int 13h itself? Is there any particular reason the head number was limited to 16 when there is an entire byte of space (dh) for the head number? Obviously later on the standard was changed to allow head numbers up to 255 (which caused the 8GB limit)
The reason i'm asking is because i'm having trouble reading a sector that lies quite a ways into a hard disk. It lies over 3 gigabytes into the disk.
Its exact C/H/S offset is: Cylinder 485, Head 147, sector 47
And the code i'm using to attempt to read it is as follows:
mov bx, ds
mov es, bx ;es takes ds
lea bx, secBuff ;bx takes the offset of secBuff, a 512 byte buffer
mov ah, 2 ;function 2, read sectors
mov dl, 80h ;source drive set to master drive
mov ch, 0e5h ;lower 8 bits of cylinder number
mov dh, 93h ;head number
mov cl, 6fh ;upper 2 bits of cylinder number, 6 bit sector number
int 13h ;read the sector into ds:secBuff
I know for a fact that the boot sector of the second partition lies at this C/H/S, i've quadruple checked using a disk editing program, but instead of loading the boot sector into secBuff, it just gets filled with zeros.
INT 13H returns an error code into AH after execution. The code it returns is 00h, which means that as far as it's concerned, the load was successful.
Can INT 13H handle head numbers greater than 16, or is it simply unable to access sectors that lie beyond the first 504MB, or maybe even 2GB of the drive?
回答1:
(This post answers the question as asked. I haven't checked to see if absurdly long comment thread added any pertinent information that should have been incorporated into the question.)
Whether the INT 13h CHS BIOS functions are capable of addressing sectors located on heads numbered 16 or higher depends on the BIOS. The oldest BIOS implementations don't provide any translation of CHS values, passing the cylinder, head and sector numbers directly through the drive interface unmodified. These BIOS implementations don't support drives with more than 16 heads because the standard IBM PC AT controller, the WD-1003, only supported 16 heads. Since the IDE CHS interface is backwards compatible with the WD-1003, this limit also applies to any IDE drive that (only) supports CHS addressing.
Newer BIOSes will do some sort of translation, but what translation used hasn't been consistent. Modern BIOSes will convert the CHS values passed through the INT 13h into an LBA address using the emulated geometry reported by the BIOS and (if the drive doesn't support LBA addressing) back into CHS values using the geometry reported by the drive. However other translation schemes have been used (eg. using the upper two bits of the head value to extend the cylinder value.) Also even when the now standard CHS/LBA/CHS translation used, different BIOS implementations can use different emulated geometries for the same drive.
If your BIOS doesn't use the modern CHS/LBA/CHS translation then you'll need to figure out what translation it does use. If your BIOS does use it and you've moved the drive between computers (or even potentially between controllers on the same PC) that use different emulated geometries then any CHS values stored in on the drive (eg. in the partition table, or FAT BPB) are no longer valid and you'll have to either ignore them or figure out how translate them. LBA values stored on the disk won't normally cause a problem as these remain the same regardless of emulated geometry.
There's a comprehensive document titled How It Works -- CHS Translation by Hale Landis that describes how older BIOSes perform CHS translation in more detail than I've given above. In particular it describes 10 different BIOS types that may help identify what translation scheme your computer might be using. Note that this document is pretty old, so much of what it talks about actual operating systems doing is out of date.
回答2:
When the ATA/IDE interface was developed, internally they supported a maximum of 16 heads. Although the BIOS routines support up to 2552 heads the underlying interface had a maximum of 16. Until different types of translation schemes were adopted (See Ross Ridge's answer and the link he provided) ATA/IDE drives (via the BIOS) were limited to 1024 Cylinders1, 16 heads, 63 sectors with older BIOSes.
In response to this question:
Can INT 13H handle head numbers greater than 16, or is it simply unable to access sectors that lie beyond the first 504MB, or maybe even 2GB of the drive?
The answer is yes, the BIOS can but whether the underlying hardware like ATA/IDE etc supports it is another story. Different types of mechanisms have developed to allow a greater number of heads with ATA/IDE drives via CHS translations done in the BIOSes basic Int 13h disk operations. One mechanism is to lie to the user and tell them an ATA/IDE disk has more heads than it supports and then translation is done behind the scenes by the BIOS. BIOS might report a drive with 16 heads actually has a maximum of 128. The BIOS knows this and divides the heads by 8 (128/16=8) before sending it to the drive controller but at the same time multiplies the number of cylinders by 8. Although the BIOS supports a maximum of 1024 cylinders it doesn't mean the BIOS can't talk to the physical medium using larger values (up to 65536 cylinders supported by ATA/IDE).
However the partition table was generated in the Original Poster's question, the drive geometry used to compute the Cylinder/Head/Sector (CHS) values in the partition table likely are not what the BIOS is using to do translations. You end up reading the wrong sector from the disk with Int 13h/ah=2
In this case the likely solution is to make a call to Int 13/ah=8 to get the drive geometry used for the CHS translation.
DISK - GET DRIVE PARAMETERS (PC,XT286,CONV,PS,ESDI,SCSI)
AH = 08h DL = drive (bit 7 set for hard disk) ES:DI = 0000h:0000h to guard against BIOS bugs Return: CF set on error AH = status (07h) (see #00234) CF clear if successful AH = 00h AL = 00h on at least some BIOSes BL = drive type (AT/PS2 floppies only) (see #00242) CH = low eight bits of maximum cylinder number CL = maximum sector number (bits 5-0) high two bits of maximum cylinder number (bits 7-6) DH = maximum head number DL = number of drives ES:DI -> drive parameter table (floppies only)
You'll need to set DL to the drive number and zero out ES and DI. The 2 components we care about are the lower 6 bits of CL which hold the maximum sector number (1 based) and DH which is the maximum head number. Since the head number is 0 based you'll need to add 1 to it.
As an example you could store them to memory locations that are word size with the bytes returned for both values.
The trickier part is to use this information to compute a 32-bit LBA number and convert it to a CHS tuple. I previously wrote a Stackoverflow Answer regarding the calculation that needs to be performed:
C = (LBA ÷ SPT) ÷ HPC H = (LBA ÷ SPT) mod HPC S = (LBA mod SPT) + 1
Unfortunately the code in the previous Stackoverflow Answer was designed with floppy disk geometries in mind where the division is simpler. Part of the equation needs to be reworked so that we can divide a 32-bit number by a 16-bit number and get a 32-bit quotient and 16-bit remainder. If you want this to run on an 8086 we can't use the 32-bit instructions and registers. I used a variation of the Extended Precision Division in the book The Art of Assembly and amended the lba_to_chs
translation code to be:
; Global variables that need to be set before lba_to_chs can be called
; One can use int 13h/ah=8 to retrieve this data
SectorsPerTrack: dw 0 ; Drive geomtry and drive info used by lba_to_chs
NumberOfHeads: dw 0
boot_device: db 0x80 ; Should be filled in with real drive #
; Function: lba_to_chs
; Description: Translate a 32-bit Logical block address (LBA)
; to CHS (Cylinder, Head, Sector).
;
; Resources: http://www.ctyme.com/intr/rb-0607.htm
; https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion
; https://stackoverflow.com/q/45434899/3857942
; https://stackoverflow.com/q/47118827/3857942
; http://www.oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_9/CH09-4.html
;
; Sector = (LBA mod SPT) + 1
; Head = (LBA / SPT) mod HEADS
; Cylinder = (LBA / SPT) / HEADS
;
; Inputs: SI = Lower 16-bits of LBA
; DI = Upper 16-bits of LBA
; DI:SI = 32 bit LBA number
;
; Outputs: DL = Boot Drive Number
; DH = Head
; CH = Cylinder (lower 8 bits of 10-bit cylinder)
; CL = Sector/Cylinder
; Upper 2 bits of 10-bit Cylinders in upper 2 bits of CL
; Sector in lower 6 bits of CL
;
; Notes: Output registers match expectation of Int 13h/AH=2 inputs
; This routine should work on an 8086 processor.
lba_to_chs:
push bx ; Save temporary registers
push ax
xor dx, dx ; Set up 32-bit by 16-bit DIV to determine high order
mov ax, di ; of Quotient (HOQ), DX:AX = (0x0000:DI)
div word [SectorsPerTrack] ; 32-bit by 16-bit DIV : LBA / SPT Part 1 (HOQ)
mov bx, ax ; Save high order of Quotient (HOQ)
mov ax, si ; Do division to compute low order of Quotient (LOQ)
div word [SectorsPerTrack] ; 32-bit by 16-bit DIV : LBA / SPT Part 2 (LOQ)
mov cl, dl ; CL = S = LBA mod SPT
inc cl ; CL = S = (LBA mod SPT) + 1
mov dx, bx ; Move saved HOQ to DX (Upper 16-bits of DX:AX)
div word [NumberOfHeads] ; 32-bit by 16-bit DIV : (LBA / SPT) / HEADS
mov dh, dl ; DH = H = (LBA / SPT) mod HEADS
mov dl, [boot_device] ; boot device, not necessary to set but convenient
mov ch, al ; CH = C(lower 8 bits) = (LBA / SPT) / HEADS
ror ah, 1 ; Rotate upper 2 bits of 10-bit Cylinder
ror ah, 1 ; into top of AH
and ah, 0xC0 ; set lower 6 bits to 0
or cl, ah ; Place upper 2 bits of 10-bit Cylinder
; Into the upper 2 bits of the sector number
pop ax ; Restore temporary registers
pop bx
ret
The Original Poster mentioned using 8086. To be strictly compliant with the 8086 instruction set I used:
ror ah, 1 ; Rotate upper 2 bits of 10-bit Cylinder
ror ah, 1 ; into top of AH
and ah, 0xC0 ; set lower 6 bits to 0
On an 80186 or later this code can be written as this to get the same desired effect (with the exception of the flags being set which I don't care about):
shl ah, 6
The immediate form of shifting was not available for any value above 1 on the Intel 8086. Alternatively one could have used 6 separate shl ah, 1
instructions.
This is a simple example that could be modified to be used inside a real bootloader or converted to be used as an MS-DOS program:
; Sample bootloader that demonstrates lba_to_chs
; Assemble with: nasm -f bin boot.asm -o boot.bin
org 0x7c00
start:
xor ax, ax
mov ds, ax
mov es, ax ; ES zeroed for int 13h/ah=8 to avoid bug
mov ss, ax
mov sp, 0x7c00
cld
mov [boot_device], dl ; Save the boot drive needed by lba_to_chs
mov ah, 0x08
xor di, di ; DI (and ES) Zeroed to avoid bug
int 0x13 ; Get drive Paramaters
inc dh ; Number of head is 0 based, add 1
mov [NumberOfHeads], dh ; Save the Number of Heads for use by lba_to_chs
and cl, 0x3f ; Lower 6 bits of CL are the maximum sector number
mov [SectorsPerTrack], cl ; Save the sectors per track for use by lba_to_chs
; This is sample code. For the original posters question this wcould be read
; from the 32-bit LBA field from the Partition table
mov si, [LBAtoRead] ; Read lower 16-bits of 32-bit LBA
mov di, [LBAtoRead+2] ; Read higher 16-bits of 32-bit LBA
call lba_to_chs ; Convert 32 bit LBA in SI:DI to CHS values
; DX and CX now set with CHS parameters and boot drive number.
; Can be used by int 13h/ah=2 to do read etc
; Insert other useful code here that sues the CHS values.
; We are finished, so halt
cli
hlt
LBAtoRead: dd 0x00770800 ; 32 Bit LBA in memory as test. This should be the LBA
; Number of Original Posters partition.
; Global variables that need to be set before lba_to_chs can be called
; One can use int 13h/ah=8 to retrieve this data
SectorsPerTrack: dw 0 ; Drive geomtry and drive info used by lba_to_chs
NumberOfHeads: dw 0
boot_device: db 0x00
lba_to_chs:
push bx ; Save temporary registers
push ax
xor dx, dx ; Set up 32-bit by 16-bit DIV to determine high order
mov ax, di ; of Quotient (HOQ), DX:AX = (0x0000:DI)
div word [SectorsPerTrack] ; 32-bit by 16-bit DIV : LBA / SPT Part 1 (HOQ)
mov bx, ax ; Save high order of Quotient (HOQ)
mov ax, si ; Do division to compute low order of Quotient (LOQ)
div word [SectorsPerTrack] ; 32-bit by 16-bit DIV : LBA / SPT Part 2 (LOQ)
mov cl, dl ; CL = S = LBA mod SPT
inc cl ; CL = S = (LBA mod SPT) + 1
mov dx, bx ; Move saved HOQ to DX (Upper 16-bits of DX:AX)
div word [NumberOfHeads] ; 32-bit by 16-bit DIV : (LBA / SPT) / HEADS
mov dh, dl ; DH = H = (LBA / SPT) mod HEADS
mov dl, [boot_device] ; boot device, not necessary to set but convenient
mov ch, al ; CH = C(lower 8 bits) = (LBA / SPT) / HEADS
ror ah, 1 ; Rotate upper 2 bits of 10-bit Cylinder
ror ah, 1 ; into top of AH
and ah, 0xC0 ; set lower 6 bits to 0
or cl, ah ; Place upper 2 bits of 10-bit Cylinder
; Into the upper 2 bits of the sector number
pop ax ; Restore temporary registers
pop bx
ret
TIMES 510-($-$$) db 0x00
dw 0xaa55
This is just sample code that demonstrates lba_to_chs
. Rather than use the variable LBAtoRead
for the 32-bit LBA Number, the original poster should retrieve it from the partition table in the MBR. That is left as a simple exercise.
Footnotes
- 1Internally the original ATA/IDE interface supported 16-bit cylinders (65536), but the BIOS interface had support for a maximum of 10 bits for the cylinder number. The BIOS interface prevented any more than 1024 cylinders. Similarly ATA/IDE supported 256 sectors but the BIOS interface limited it to 63. Internally ATA/IDE could address 65536*16*256 sectors (137438953472 bytes or ~137 GB). The lowest common denominator for BIOSes that don't perform any special translation and the ATA/IDE interface combined for the original CHS limits of 1024/16/63 for ATA drives.
- 28 bits would normally allow 256 heads (0-255) however the 255 head limit became one necessitated by compatibility because of a bug in IBM-DOS and PC-DOS through version 7.10.
来源:https://stackoverflow.com/questions/47118827/is-int13h-non-extended-capable-of-accessing-drives-with-more-than-16-heads-per