简体   繁体   中英

Problem with BIOS INT 13H (Read Sectors From Drive)

Description:

In my efforts to create a simple standalone program I have written a simple boot loader in the first sector. Its purpose is to load the program into memory. For this purpose I am using INT 13h with AH=2. The code is:

disk_load:
  push dx           ; Store DX on stack so later we can recall how many sectors were requested to be read,
                    ; even if it is altered in the meantime.
  mov ah, 0x02        ; BIOS read sector.
  mov al, dh          ; Read DH sectors.
  mov ch, 0x00        ; Select cylinder 0.
  mov dh, 0x00        ; Select head 0.
  mov cl, 0x02        ; Start reading from second sector (i.e. after the boot sector).
  int 0x13            ; BIOS interrupt.
                      ;  <!----here
  pop dx
  ret

load_software:
  mov bx, 0x7e0
  mov es, bx
  xor bx, bx
  mov dh, 66
  mov dl, [BOOT_DRIVE]
  call disk_load

I was exercising everything in VirtualBox 5.2.8 and it worked perfectly well. Moving everything to a second machine that has VirtualBox 6.0.14 fails the experiment. The interrupt finishes with CF set, indicating a failure.

Reading the excellent answer in Boot loader doesn't jump to kernel code I have fixed the potential problem of unspecified DS value that could cause problems. If I stop and dump the CPU status just before the int 0x13 call I get a consistent state on both VirtualBoxes:

00:00:05.930849 eax=00000280 ebx=00007e00 ecx=00000002 edx=00000000 esi=00000000 edi=0000fff0

00:00:05.930857 eip=00007cc8 esp=00007bf9 ebp=00007bff iopl=0 nv up ei pl nz na po nc

00:00:05.930864 cs={0000 base=0000000000000000 limit=0000ffff flags=0000009b} dr0=00000000 dr1=00000000

00:00:05.930877 ds={0000 base=0000000000000000 limit=0000ffff flags=00000093} dr2=00000000 dr3=00000000

00:00:05.930884 es={0000 base=0000000000000000 limit=0000ffff flags=00000093} dr4=00000000 dr5=00000000

00:00:05.930891 fs={0000 base=0000000000000000 limit=0000ffff flags=00000093} dr6=ffff0ff0 dr7=00000400

00:00:05.930898 gs={0000 base=0000000000000000 limit=0000ffff flags=00000093} cr0=00000010 cr2=00000000

00:00:05.930904 ss={0000 base=0000000000000000 limit=0000ffff flags=00000093} cr3=00000000 cr4=00000000

00:00:05.930910 gdtr=00000000000fe89f:0047 idtr=0000000000000000:ffff eflags=00200246

Parsing all the values I can only conclude that all the input parameters to the interrupt are set up properly. The state after the dump has CF set and an error code:

00:00:08.984877 eax=00000900 ebx=00000000 ecx=00000002 edx=00000000 esi=00000000 edi=0000fff0

00:00:08.984887 eip=00007cca esp=00007bf9 ebp=00007bff iopl=0 nv up ei pl nz na po cy

00:00:08.984896 cs={0000 base=0000000000000000 limit=0000ffff flags=0000009b} dr0=00000000 dr1=00000000

00:00:08.984909 ds={0000 base=0000000000000000 limit=0000ffff flags=00000093} dr2=00000000 dr3=00000000

00:00:08.984917 es={07e0 base=0000000000007e00 limit=0000ffff flags=00000093} dr4=00000000 dr5=00000000

00:00:08.984925 fs={0000 base=0000000000000000 limit=0000ffff flags=00000093} dr6=ffff0ff0 dr7=00000400

00:00:08.984934 gs={0000 base=0000000000000000 limit=0000ffff flags=00000093} cr0=00000010 cr2=00000000

00:00:08.984941 ss={0000 base=0000000000000000 limit=0000ffff flags=00000093} cr3=00000000 cr4=00000000

00:00:08.984948 gdtr=00000000000fe89f:0047 idtr=0000000000000000:ffff eflags=00200247

Noting the error code AH=9 data boundary error (attempted DMA across 64K boundary or >80h sectors) lead me to https://en.wikipedia.org/wiki/INT_13H where this statement is made:

Addressing of Buffer should guarantee that the complete buffer is inside the given segment, ie ( BX + size_of_buffer ) <= 10000h.

This would explain my initial problems so I made another fix to set the es=0x7e0 and bx=0 . This is the state of the code that is displayed above. However even this code fails with the state described above.

Further testing shows that I can successfully read up to 65 sectors but 66 or more fails. Being it a weird number I calculated the end of the 65th sector: 0xffff. So the problem becomes a little bit more confusing.

Questions:

Should my es=0x7e0 and bx=0 solution avoid the segment crossing (as I understand it should)?

If it does, why does it seem to be the problem in crossing the linear address?

Or can one cross the segment, but not the 0xffff marker in linear address?

Thank you for your help.

The problem is that you've "attempted DMA across a 64K boundary". It's the fact that your destination buffer spans from physical address 0x07E00 to 0x17E00, which crosses the 64K boundary at 0x10000. (This has nothing to do with segment boundaries, so it doesn't matter what segment and offset values you use to arrive at a 0x07E00 physical address.)

The reason for this has to do how the original IBM PC was designed on the cheap. Instead of using a 16-bit 8086 with a 16-bit bus and 16-bit support chips, they used a cheaper 16-bit 8088 CPU with an 8-bit bus that could be used with cheaper 8-bit support chips. In particular the DMA controller they chose is only capable of addressing 64K of memory, the typical addressing limit for 8-bit CPUs the DMA controller was designed for. They made it work by having a separate chip which provides the upper four bits of DMA addresses, allowing 8088's full 1024K address space to be addressed. (On the IBM PC AT this was extended to provide the upper eight bits, so that the entire 80286 16M address space could be accessed.)

Unfortunately this means the upper four bits the DMA address are fixed during a DMA operation, which effectively divides the 1024K address space into sixteen 64K pages. Any attempt to perform a DMA operation that crosses from one page to the next, across a 64K boundary, will wrap around to the beginning of the page instead. While the BIOS could work around this problem by dividing the read into two separate reads, one for each 64K page, it simply returns an error.

Note this is generally only a problem with floppy disk accesses as hard disk interfaces generally don't use the IBM PC DMA controller.

Since potential boundary problems like this also exist when crossing track and cylinder boundaries, the best way to work around this issue is to just read one sector in a time, like Jester and Michael Petch said in comments. As a simple work around you can just move your buffer so that it starts at physical address 0x10000 but you may find on real world systems you're limited to reading to just the remaining sectors on the track.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM