• Please review our updated Terms and Rules here

need help with HDD reading BIOS calls

Mike Chambers

Veteran Member
Joined
Sep 2, 2006
Messages
2,621
yes, it's in that program i posted the screen shots of for drive imaging to FTP. this is why it hasn't been posted yet. it worked good on the XT, but then i moved it over to a 286 with an IDE hard drive. on that system, my int 13h calls resulted in getting all zero data about the drive... like it would report 0 cyls, 0 sectors, 0 heads.

is there something else i need to do on a system like that? like i said it works great on the XT with the MFM hard drive. i've tried calling resets for the drive and controller before i did it, but i had no luck still.

do you guys want me to post the code?
 
then i moved it over to a 286 with an IDE hard drive. on that system, my int 13h calls resulted in getting all zero data about the drive... like it would report 0 cyls, 0 sectors, 0 heads.

you mean your INT 13, fn 8 (get drive parameters) is returning 0's for the size of the drive? My interrupt list says that you should have ES and DI registers cleared to 0 when calling this routine, because there are lots of BIOS bugs.

If that doesn't work, issuing an identify drive command to an IDE device is a piece of cake. I can send you a pile of code that does it.
 
here's the part that detects the hard drive info. it's pretty sloppy, sorry. and i'm sure what i'm having it do with the data is all wrong, this is the first time i've ever tried INT 13h calls.

Code:
PRINT "Select a source drive:"
PRINT "1. First floppy (drive A:)"
PRINT "2. Second floppy (drive B:)"
PRINT "3. Hard drive 0"
PRINT "4. Hard drive 1"
PRINT "5. Exit to DOS"
PRINT
PRINT "Choose an option: ";
LOCATE , , 1
DO
        key$ = INKEY$
        SELECT CASE key$
                CASE "1"
                        srcdrv = 0
                        EXIT DO
                CASE "2"
                        srcdrv = 1
                        EXIT DO
                CASE "3"
                        srcdrv = &H80
                        EXIT DO
                CASE "4"
                        srcdrv = &H81
                        EXIT DO
                CASE "5"
                        PRINT "5"
                        END
                CASE ELSE
        END SELECT
LOOP
PRINT key$
PRINT
PRINT "Resetting drive " + HEX$(srcdrv) + "h... ";
diskCall.ax = 0
diskCall.dx = MakeReg(0, srcdrv)
CALL interruptx(&H13, diskCall, diskReturn)
PRINT "OK!"

PRINT "Getting drive info from drive " + HEX$(srcdrv) + "h..."
diskCall.ax = MakeReg(&H8, 0)
diskCall.dx = MakeReg(0, srcdrv)
CALL interruptx(&H13, diskCall, diskReturn)

DIM cr AS LONG
DIM tmp AS LONG
DIM sentkb AS LONG
bn$ = ""
tmp = diskReturn.cx
cr = 32768
FOR num = 1 TO 16
        IF tmp - cr >= 0 THEN
                tmp = tmp - cr
                bn$ = bn$ + "1"
        ELSE
                bn$ = bn$ + "0"
        END IF
        cr = cr / 2
NEXT num
cy$ = LEFT$(bn$, 10)
diskSpecs.Cylinders = 0
cr = 1
FOR num = 10 TO 1 STEP -1
        IF MID$(cy$, num, 1) = "1" THEN diskSpecs.Cylinders = diskSpecs.Cylinders + cr
        cr = cr * 2
NEXT num
diskSpecs.Cylinders = diskSpecs.Cylinders * 2
se$ = RIGHT$(bn$, 6)
cr = 1
diskSpec.Sectors = 0
FOR num = 6 TO 1 STEP -1
        IF MID$(se$, num, 1) = "1" THEN diskSpecs.Sectors = diskSpecs.Sectors + cr
        cr = cr * 2
NEXT num
diskSpecs.Sides = HighByte(diskReturn.dx)

DO
        diskSpecs.Capacity = diskSpecs.Cylinders + 1
        diskSpecs.Capacity = diskSpecs.Capacity * diskSpecs.Sectors
        diskSpecs.Capacity = diskSpecs.Capacity * diskSpecs.Sides
        diskSpecs.Capacity = diskSpecs.Capacity * 512
        mb$ = MID$(STR$(FIX(((diskSpecs.Capacity / 1024) / 1024) * 100) / 100), 2)
        PRINT "Cylinders:" + STR$(diskSpecs.Cylinders + 1)
        PRINT "Sectors:" + STR$(diskSpecs.Sectors)
        PRINT "Heads:" + STR$(diskSpecs.Sides + 1)
        PRINT "Capacity:" + STR$(diskSpecs.Capacity) + " bytes (" + mb$ + " MB)"
        PRINT
        PRINT "Is the above information correct? (Y/N): "; : LOCATE , , 1
        okquit = 0
        DO
                key$ = INKEY$
                SELECT CASE LCASE$(key$)
                        CASE "y"
                                PRINT key$
                                okquit = 1
                                EXIT DO
                        CASE "n"
                                PRINT key$
                                PRINT "Please manually specify disk parameters."
                                LINE INPUT "Cylinders: ", cyltmp$
                                LINE INPUT "  Sectors: ", sectmp$
                                LINE INPUT "    Heads: ", headtmp$
                                PRINT
                                diskSpecs.Cylinders = VAL(cyltmp$) - 1
                                diskSpecs.Sectors = VAL(sectmp$)
                                diskSpecs.Sides = VAL(headtmp$) - 1
                                EXIT DO
                END SELECT
        LOOP
LOOP UNTIL okquit = 1
 
you mean your INT 13, fn 8 (get drive parameters) is returning 0's for the size of the drive? My interrupt list says that you should have ES and DI registers cleared to 0 when calling this routine, because there are lots of BIOS bugs.

If that doesn't work, issuing an identify drive command to an IDE device is a piece of cake. I can send you a pile of code that does it.

that's a good idea actually. i am going to try that and see what it does!
 
you want sloppy code? I'll give ya some sloppy code! :)

here's a drive ID program written in quickbasic. this is from 1994, and one of my first forays into doing low level hardware access. I think I spent a week in soft-ice trying to reverse engineer how a program from seagate managed to pull the ID data off a drive. had I known there were ata specs at the time (or where to find them, or understand them) my world would have been a better place.

I couldn't even begin to tell you how this works in basic anymore or what I was thinking when I wrote this.
I full understand the assembly part of it now though, so doing the actual hardware talking is (for me) the easy part.

So, if you can get this to compile, it should ID a drive attached as master on the primary IDE ports, which are 1f0-1f7. It could easily be tweaked to do ID's to all 4 IDE channels.

if you get this to work, we can change it around a bit so you can read the total number of addressable sectors off the drive as well as the serial and model number.

good luck!

Code:
DECLARE SUB driveid (drive!, model$, firmware$, serial$)
CALL driveid(0, model$, firmware$, serial$)
PRINT model$
PRINT firmware$
PRINT serial$

SUB driveid (drive, model$, firmware$, serial$)
CONST drivebytes = 16
DIM p(drivebytes), rt$(60), lt$(60)


p(1) = &H55          'this is our l'il assembly program that does a drive
p(2) = &H8B          'id, and returns values in AX.
p(3) = &HEC
p(4) = &HBA
p(5) = &HF0
p(6) = &H1
p(7) = &HED
p(8) = &H8B
p(9) = &H5E
p(10) = &H6
p(11) = &H89
p(12) = &H7
p(13) = &H5D
p(14) = &HCA
p(15) = &H2
p(16) = &H0


p = VARPTR(p(1))
' Poke the machine-language program into the array.
DEF SEG = VARSEG(p(1))
FOR i = 0 TO drivebytes - 1
   POKE (p + i), p(i + 1)
NEXT i

drive = drive AND 1
drive = drive * 16
OUT &H1F0, drive




start:
IF retry > 3 THEN model$ = "Unknown Model": serial$ = "Unknown": GOTO rsetum
OUT &H1F7, &HEC  'issue drive ID command to HD
FOR n = 1 TO 150: NEXT
FOR r = 1 TO 50
FOR t = 1 TO 160: NEXT
' Execute the program. The program expects a single integer argument.
DEF SEG = VARSEG(p(1))' Change the segment.
CALL Absolute(x%, VARPTR(p(1)))
DEF SEG   ' Restore the segment.
a$ = HEX$(x%)
IF INSTR(a$, "D0") THEN r = r - 1: GOTO nxt
IF LEN(a$) = 3 THEN a$ = "0" + a$
IF LEN(a$) = 2 THEN a$ = "00" + a$
IF LEN(a$) = 1 THEN a$ = "000" + a$
lt$ = CHR$(VAL("&h" + LEFT$(a$, 2)))
rt$ = CHR$(VAL("&h" + RIGHT$(a$, 2)))
rt$(r) = rt$
lt$(r) = lt$
nxt:
NEXT
IF ASC(lt$(29)) < 20 OR ASC(lt$(29)) > 123 THEN retry = retry + 1: GOTO start
IF ASC(rt$(29)) < 20 OR ASC(rt$(29)) > 123 THEN retry = retry + 1: GOTO start
FOR x = 28 TO 47: model$ = model$ + lt$(x) + rt$(x): : NEXT
FOR x = 11 TO 20: serial$ = serial$ + lt$(x) + rt$(x): NEXT
FOR x = 24 TO 27: firmware$ = firmware$ + lt$(x) + rt$(x): NEXT
model$ = LTRIM$(RTRIM$(model$))
firmware$ = LTRIM$(RTRIM$(firmware$))
serial$ = LTRIM$(RTRIM$(serial$))
rsetum:
OUT &H1F7, &H10
END SUB
 
While the above code is a neat hack :) I would caution against code that works separately for BIOS vs. low-level IDE. For most platforms this utility targets, the BIOS interface should be all that is needed, for mfm/rll/ide/scsi.

When I get home I'll check the code Mike posted to see what's going on.
 
weird.
I compiled mike's code with my quickbasic here at work and trapped int 13 calls with my debugger (s-ice). Ran it on a basic pentium with a < 10G IDE hard drive. (it's all I have handy here)

Anyway, the weird thing is that while int 13 is called twice as expected, but ax, bx, cx and dx were 0 on both calls. So it's resetting the A: drive twice, and upon return from INT 13, all registers are zero.

I then get a string memory corrupt error and it hangs or bails out so hard that it has to reload DOS. I was using dos 6.22

If I intercepted the calls and inserted my own reset to drive C: and get info from drive C: then I received back some actual data in CX and DX. Even after doing this, it still bombed out with a string space corrupt error.

So, my hunch is now that qbasic isn't setting up the interruptx call properly, or the diskCall.ax = MakeReg(&H8, 0) isn't making its way into actually interrupt. It's weird, but I suppose it could also be my build environment or perhaps some compiler option I'm missing?

Mike, if you'd like to post up a compiled version of your code, I'd be happy to give it some debugger time.
 
weird.
I compiled mike's code with my quickbasic here at work and trapped int 13 calls with my debugger (s-ice). Ran it on a basic pentium with a < 10G IDE hard drive. (it's all I have handy here)

Anyway, the weird thing is that while int 13 is called twice as expected, but ax, bx, cx and dx were 0 on both calls. So it's resetting the A: drive twice, and upon return from INT 13, all registers are zero.

I then get a string memory corrupt error and it hangs or bails out so hard that it has to reload DOS. I was using dos 6.22

If I intercepted the calls and inserted my own reset to drive C: and get info from drive C: then I received back some actual data in CX and DX. Even after doing this, it still bombed out with a string space corrupt error.

So, my hunch is now that qbasic isn't setting up the interruptx call properly, or the diskCall.ax = MakeReg(&H8, 0) isn't making its way into actually interrupt. It's weird, but I suppose it could also be my build environment or perhaps some compiler option I'm missing?

Mike, if you'd like to post up a compiled version of your code, I'd be happy to give it some debugger time.

wait are you actually using qbasic.exe that came with DOS, or is it quickbasic 4.x or 7.x? the old qbasic can't handle interrupt calls.
 
wait are you actually using qbasic.exe that came with DOS, or is it quickbasic 4.x or 7.x? the old qbasic can't handle interrupt calls.

Quickbasic Extended 7.1

I had a hard time figuring out which it was-there's no "about" section in the menus, but then found out that if I just run qbx.exe it tells you the version # at the bottom of the screen.
 
Quickbasic Extended 7.1

I had a hard time figuring out which it was-there's no "about" section in the menus, but then found out that if I just run qbx.exe it tells you the version # at the bottom of the screen.

oh okay, that'll work fine. since you're in ver 7.x you'll actually have to start it like this instead: qbx.exe /l qbx.qlb

you're probably going to have to set up a packet driver and ntcpdrv or tcpdrv before you run it. otherwise, odds are it'll lock your system up when it tries to initialize the TCP stack.
 
oh okay, that'll work fine. since you're in ver 7.x you'll actually have to start it like this instead: qbx.exe /l qbx.qlb

yep, that's stock for me. I've always used a batch file to load it up using that additional library or whatever it is.
you're probably going to have to set up a packet driver and ntcpdrv or tcpdrv before you run it. otherwise, odds are it'll lock your system up when it tries to initialize the TCP stack.

eek. that will be significantly more difficult. Could you try compiling just that hard drive snippet you posted and send that to me? Since I built that and it wrecked the int 13 calls, I'd like to see if your build does the same just to eliminate a variable in the debug process.
 
yep, that's stock for me. I've always used a batch file to load it up using that additional library or whatever it is.


eek. that will be significantly more difficult. Could you try compiling just that hard drive snippet you posted and send that to me? Since I built that and it wrecked the int 13 calls, I'd like to see if your build does the same just to eliminate a variable in the debug process.

yup, no problem. i'll go ahead and do that real quick. i'll get it posted here shortly. ty for the help.
 
Back
Top