Accessing the Harddisk using LBA under DPMI

From Open Watcom

Revision as of 16:21, 30 October 2009; view current revision
←Older revision | Newer revision→
Jump to: navigation, search

Contents

General Information

Chris Giese's DJGPPLBA.C allows one to read and write to their hard disk using Large Block Access (LBA) under DOS. It does this using four simple BIOS calls:

INT 13 - IBM/MS INT 13 Extensions - INSTALLATION CHECK  AH = 41h
INT 13 - IBM/MS INT 13 Extensions - EXTENDED READ       AH = 42h
INT 13 - IBM/MS INT 13 Extensions - EXTENDED WRITE      AH = 43h
INT 13 - DISK                     - RESET DISK SYSTEM   AH = 00h

(headers are from Ralph Brown's Interrupt list)

Unfortunately, it is only for DJGPP. Well, not any more. For comparison purposes, the original source for DJGPPLBA.C is the "LBA replacement for DJGPP biosdisk()" link on this web page:

http://my.execpc.com/CE/AC/geezer/software/

The core routine of DJGPPLBA.C is lba_biosdisk(). It accepts disk parameters:

  1. the DOS drive number
  2. whether you want to read or write to the disk
  3. the lba sector number
  4. the number of sectors to read
  5. and a pointer to a buffer to read or write the sectors to.

It's basic outline is as follows:

  1. confirms the size of a 'transfer buffer'
  2. calls the BIOS installation check (int 0x13, ah=41h)
  3. if the BIOS calls are available,
    1. either reads (int 0x13, ah=42h)
    2. or writes (int 0x13, ah=43h) to the disk
  4. resets the hard disk (calls int 0x13, ah=00h)

Porting to OW1.3


The easiest way to port is from protected mode(PM) DJGPP to PM OW code, and then port the PM OW code to real mode(RM) OW code.

The first problem with porting DJGPPLBA.C to OW, is that OW doesn't have the <dpmi.h> and <go32.h> PM headers. Jumping ahead a bit, for PM OW, the similar functionality is in <i86.h> This DJGPP and OW specific code are separated into their own #ifdef sections.

The next problem is that the structure for the command packet is packed using a method incompatible with OW. It uses '__attribute__((packed));' This is solved typedef'ing the structure. Moving the structure to the top of the program. And, using a packing method compatible with both OW and DJGPP. Both OW and DJGPP have three methods of packing structures. However, only one is common to both. This is the '#pragma pack' directive. DJGPP's GCC requires a special #define to enable this:

#define HANDLE_PRAGMA_PACK_PUSH_POP 1
DJGPP uses
__dpmi_int()
to call the four RM BIOS interrupts.

__dpmi_int() calls the DPMI 0.9 function int 0x31, ah=300h, "Simulate Real Mode Interrupt." The DPMI host handles PM to RM switching, RM to PM switching, calling the RM interrupt, etc. AFAIK, there is no wrapper for this call in OW1.3. So, the method I prefer is to use 'int386x()' with 'regs','sregs' and the DPMI call structure, 'RMI'. There are other methods which work just as well. The DPMI call structure is the stack layout of the arguments pushed by the Intel instruction 'pushad', with ESP marked as 'rsvd'. Basically, you set the 'regs' and 'sregs' to point to 'RMI' and then call int 0x31, ah=300h using 'int386x()'. Once coded, you can easily recycle this code by setting the appropriate RM interrupt values in 'RMI' and changing the called interrupt in 'r.h.bl'

A 'transfer buffer' is some physically mapped memory below 1Mb. DOS and BIOS, being 16-bit, can't access memory above 1Mb or 1M+64k. This is A20 line dependent. Since DOS can't access the data structures in a PM application since they are above 1Mb, a transfer buffer is used to store and return data from DOS & BIOS calls. For DJGPP, this is setup by the DPMI host. AFAIK for OW, this must be done manually. The get_dos_mem() and free_dos_mem() functions create and destroy the 'transfer buffer'. For PM, we must use the DPMI host to get RM memory. For PM, these functions call the DPMI 0.9 functions: int 0x31, ah=100h, "Allocate DOS Memory Block" and int 0x31, ah=101h, "Free DOS Memory Block", respectively. For RM, we could just point the functions to our data, but for 'compatibility' with the PM code, we'll just use malloc() and free().

One thing to notice is that DJGPP and OW use different naming for the elements of the register structures. For generic 32bit,16bit,8bit, DJGPP uses d,w,h, and OW uses x,w,h, respectively.

Porting PM OW to RM OW


For RM, we can setup and call 'int86()' and basically discard everthing to do with the 'RMI' structure. To get the segment and offset of the data structures, we can use the convenient 'FP_SEG' and 'FP_OFF' macros. I don't recall exactly why movedata() is used instead of memcpy(), but it seems to fit the use of 'FP_SEG' and 'FP_OFF'.


Rod Pemberton

Code

#include <stdio.h>  /* printf() */
#include <string.h> /* memset() */
#include <bios.h>   /* _DISK_... */

#ifdef __DJGPP__
    #include <dpmi.h> /* __dpmi_regs, __dpmi_int() */
    #include <go32.h> /* _go32_info_block, __tb, dosmemget(), dosmemput() */

__dpmi_regs r;

#define HANDLE_PRAGMA_PACK_PUSH_POP 1
#endif
/* end __DJGPP__ */

#ifdef __WATCOMC__
    #include <i86.h> /* regs, sregs, int86x() int386x() */
    #include <stdlib.h> /* atexit */

union REGS r;
struct SREGS s;
struct
{
    long EDI,ESI,EBP,rsvd,EBX,EDX,ECX,EAX;
    short flags,ES,DS,FS,GS,IP,CS,SP,SS;
}
RMI;
/* tb used to setup transfer buffer below 1st megabyte (DJGPP __tb) */
/* DOS can only access the first 1Mb of memory */
/* tb used to save return information from DOS calls */
void *tb;
unsigned short sel;  /* sel needed to free allocated memory, don't use */
#endif
/* end __WATCOMC__ */

#define BPS 512 /* bytes per sector for disk */

typedef unsigned char  uint8_t;
typedef unsigned short  uint16_t;
typedef unsigned long  uint32_t;
typedef unsigned long long uint64_t;

#pragma pack(push,1)

typedef struct
{
    uint8_t  packet_len;
    uint8_t  reserved1;
    uint8_t  nsects;
    uint8_t  reserved2;
    uint16_t buf_offset;
    uint16_t buf_segment;
    uint64_t lba;
}
lba_command_packet;
#pragma pack(pop)

void free_dos_mem(void)

#ifdef __WATCOMC__
#define WAT_TB 0x4000
/* setup tb - transfer buffer for dos calls in memory below 1Mb */
void free_dos_mem(void)
{
#ifdef __386__
    r.w.ax=0x0101; /* free dos memory */
    r.w.dx=sel;
    int386(0x31,&r,&r);
#else

    free (tb);
#endif
}

void get_dos_mem(void)

void get_dos_mem(void)
{
#ifdef __386__
    r.w.ax=0x0100; /* allocate dos memory, no __tb in WATCOM */
    r.w.bx=WAT_TB>>4; /* 0400h is 04000h (16384 bytes) in paragraphs (16 bytes) */
    int386(0x31,&r,&r);
    sel = r.w.dx;
    tb = (void *)(r.w.ax<<4);
#else
    tb = malloc (16384);
    if (tb==NULL)
    {
        printf("RM malloc failed.");
        exit(1);
    }
#endif
}
#endif

int lba_biosdisk(unsigned int13_drive_num, int cmd, unsigned long lba, unsigned nsects, void *buf)

int lba_biosdisk(unsigned int13_drive_num, int cmd, unsigned long lba, unsigned nsects, void *buf)
{
    /* RP - this function is large and there were so many #ifdef's that I */
    /* decided to just separate this function into three sections: */
    /*   DJGPP, WATCOM PM, WATCOM RM */
    /* It really needs to be broken down into smaller functions... */
#ifdef __DJGPP__
    /* DJGPP section */
    lba_command_packet lba_cmd_pkt;

    /* INT 13h AH=42h/AH=43h command packet: */
    unsigned tries, err = 0;
    if(cmd != _DISK_READ && cmd != _DISK_WRITE)
       return 0x100;
   /* make sure the DJGPP or WATCOM transfer buffer (in conventional memory) is big enough */
   if(BPS * nsects + sizeof(lba_command_packet) >
            _go32_info_block.size_of_transfer_buffer)
        return 0x100;
    /* make sure drive and BIOS support LBA. Note that Win95 DOS box emulates INT 13h AH=4x if they are not present in the BIOS. */
    r.x.bx = 0x55AA;
    r.h.dl = int13_drive_num;
    r.h.ah = 0x41;
    __dpmi_int(0x13, &r);
    if(r.x.flags & 0x0001) /* carry bit (CY) is set */
        return 0x100;
    /* fill in the INT 13h AH=4xh command packet */
    memset(&lba_cmd_pkt, 0, sizeof(lba_command_packet));
    lba_cmd_pkt.packet_len = sizeof(lba_command_packet);
    lba_cmd_pkt.nsects = nsects;

    /* use start of transfer buffer for data transferred by BIOS disk I/O... */
    lba_cmd_pkt.buf_offset = 0;
    lba_cmd_pkt.buf_segment = __tb >> 4;
    lba_cmd_pkt.lba = lba;

    /* ...use end of transfer buffer for the command packet itself */
    dosmemput(&lba_cmd_pkt, sizeof(lba_command_packet), __tb + BPS * nsects);

    /* fill in registers for INT 13h AH=4xh */
    r.x.ds = (__tb + BPS * nsects) >> 4;
    r.x.si = (__tb + BPS * nsects) & 0x0F;
    r.h.dl = int13_drive_num;
    /* if writing, copy the data to conventional memory now */
    if(cmd == _DISK_WRITE)
        dosmemput(buf, BPS * nsects, __tb);
    /* make 3 attempts */
    for(tries = 3; tries != 0; tries--)
    {
        r.h.ah = (cmd == _DISK_READ) ? 0x42 : 0x43;
        __dpmi_int(0x13, &r);
        err = r.h.ah;
        if((r.x.flags & 0x0001) == 0)
        {
            /* if reading, copy the data from conventional memory now */
            if(cmd == _DISK_READ)
                dosmemget(__tb, BPS * nsects, buf);
            return 0;
        }
        /* reset disk */
        r.h.ah = 0;
        __dpmi_int(0x13, &r);
    }
    return err;
#endif
    /* end DJGPP section */

#ifdef __WATCOMC__
#ifdef __386__
    /* WATCOM PM section */
    lba_command_packet lba_cmd_pkt;

    /* INT 13h AH=42h/AH=43h command packet: */
    unsigned tries, err = 0;

    if(cmd != _DISK_READ && cmd != _DISK_WRITE)
        return 0x100;
    /* make sure the DJGPP or WATCOM transfer buffer (in conventional memory) is big enough */
    if(BPS * nsects + sizeof(lba_command_packet) > WAT_TB) /* 04000h (16384) bytes for WATCOM above */
        return 0x100;

    /* make sure drive and BIOS support LBA. Note that Win95 DOS box emulates INT 13h AH=4x if they are not present in the BIOS. */
    RMI.EBX = 0x55AA;
    RMI.EDX = int13_drive_num;
    RMI.EAX = (0x41<<8);
    r.w.ax=0x0300; /* simulate real mode interrupt */
    r.h.bl=0x13; /* int 0x13 */
    r.h.bh=0;
    r.w.cx=0;
    s.es=FP_SEG(&RMI);
    r.x.edi=FP_OFF(&RMI);
    int386x(0x31, &r, &r, &s);
    if (RMI.flags & 0x0001) /* carry bit (CY) is set */
        return 0x100;

    /* fill in the INT 13h AH=4xh command packet */
    memset(&lba_cmd_pkt, 0, sizeof(lba_command_packet));
    lba_cmd_pkt.packet_len = sizeof(lba_command_packet);
    lba_cmd_pkt.nsects = nsects;

    /* use start of transfer buffer for data transferred by BIOS disk I/O... */
    lba_cmd_pkt.buf_offset = (unsigned long)tb & 0x0F;
    lba_cmd_pkt.buf_segment = (unsigned long)tb >> 4;
    lba_cmd_pkt.lba = lba;

    /* ...use end of transfer buffer for the command packet itself */
    memcpy((char *)tb + BPS * nsects, &lba_cmd_pkt, sizeof(lba_command_packet));

    /* fill in registers for INT 13h AH=4xh */
    RMI.DS = ((unsigned long)tb + BPS * nsects) >> 4;
    RMI.ESI = ((unsigned long)tb + BPS * nsects) & 0x0F;
    RMI.EDX = int13_drive_num;

    /* if writing, copy the data to conventional memory now */
    if(cmd == _DISK_WRITE)
        memcpy(tb, buf, BPS * nsects);

    /* make 3 attempts */
    for(tries = 3; tries != 0; tries--)
    {
        RMI.EAX = (((cmd == _DISK_READ) ? 0x42 : 0x43)<<8);
        r.w.ax=0x0300; /* simulate real mode interrupt */
        r.h.bl=0x13; /* int 0x13 */
        r.h.bh=0;
        r.w.cx=0;
        s.es=FP_SEG(&RMI);
        r.x.edi=FP_OFF(&RMI);
        int386x(0x31, &r, &r, &s);
        err = (RMI.EAX >> 8) & 0x0F;
        if((RMI.flags & 0x0001) == 0)
        {
            /* if reading, copy the data from conventional memory now */
            if(cmd == _DISK_READ)
                memcpy(buf, tb, BPS * nsects);
            return 0;
        }

        /* reset disk */
        RMI.EAX = 0;
        r.w.ax=0x0300; /* simulate real mode interrupt */
        r.h.bl=0x13; /* int 0x13 */
        r.h.bh=0;
        r.w.cx=0;
        s.es=FP_SEG(&RMI);
        r.x.edi=FP_OFF(&RMI);
        int386x(0x31, &r, &r, &s);
    }
    return err;
    /* end WATCOM PM section */
#else
    /* WATCOM RM section */
    lba_command_packet lba_cmd_pkt;

    /* INT 13h AH=42h/AH=43h command packet: */
    unsigned tries, err = 0;

    if(cmd != _DISK_READ && cmd != _DISK_WRITE)
        return 0x100;

    /* make sure the DJGPP or WATCOM transfer buffer (in conventional memory) is big enough */
    if(BPS * nsects + sizeof(lba_command_packet) >
            WAT_TB) /* 04000h (16384) bytes for WATCOM above */
        return 0x100;

    /* make sure drive and BIOS support LBA. Note that Win95 DOS box emulates INT 13h AH=4x if they are not present in the BIOS. */
    r.w.bx = 0x55AA;
    r.h.dl = int13_drive_num;
    r.h.ah = 0x41;
    int86(0x13, &r, &r);
    if (r.w.cflag & 0x0001) /* carry bit (CY) is set */
        return 0x100;

    /* fill in the INT 13h AH=4xh command packet */
    memset(&lba_cmd_pkt, 0, sizeof(lba_command_packet));
    lba_cmd_pkt.packet_len = sizeof(lba_command_packet);
    lba_cmd_pkt.nsects = nsects;

    /* use start of transfer buffer for data transferred by BIOS disk I/O... */
    lba_cmd_pkt.buf_offset = FP_OFF(tb);
    lba_cmd_pkt.buf_segment = FP_SEG(tb);
    lba_cmd_pkt.lba = lba;
    /* ...use end of transfer buffer for the command packet itself */
    movedata(FP_SEG(&lba_cmd_pkt), FP_OFF(&lba_cmd_pkt), FP_SEG((char*)tb + BPS * nsects), FP_OFF((char *)tb + BPS * nsects), sizeof(lba_command_packet));

    /* fill in registers for INT 13h AH=4xh */
    s.ds = FP_SEG((char *)tb + BPS * nsects);
    r.w.si = FP_OFF((char *)tb + BPS * nsects);
    r.h.dl = int13_drive_num;

    /* if writing, copy the data to conventional memory now */
    if(cmd == _DISK_WRITE)
        movedata(FP_SEG(buf), FP_OFF(buf), FP_SEG(tb), FP_OFF(tb), BPS * nsects);

    /* make 3 attempts */
    for(tries = 3; tries != 0; tries--)
    {
        r.h.ah = (cmd == _DISK_READ) ? 0x42 : 0x43;
        int86x(0x13, &r, &r, &s);
        err = r.h.ah;
        if((r.w.cflag & 0x0001) == 0)
        {
            /* if reading, copy the data from conventional memory now */
            if(cmd == _DISK_READ)
                movedata(FP_SEG(tb), FP_OFF(tb), FP_SEG(buf), FP_OFF(buf),  BPS * nsects);
            return 0;
        }
        /* reset disk */
        r.h.ah = 0;
        int86(0x13, &r, &r);
    }
    return err;
    /* end WATCOM RM section */
#endif
#endif
}

Demo Routines

/*****************************************************************************
demo routines
*****************************************************************************/
#define BPERL  16 /* byte/line for dump */
 
void dump(void *data_p, unsigned count)
{
    unsigned char *data = (unsigned char *)data_p;
    unsigned byte1, byte2;

    while(count != 0)
    {
        for(byte1 = 0; byte1 < BPERL; byte1++)
        {
            if(count == 0)
                break;
            printf("%02X ", data[byte1]);
            count--;
        }
        printf("\t");
        for(byte2 = 0; byte2 < byte1; byte2++)
        {
            if(data[byte2] < ' ')
                printf(".");
            else
                printf("%c", data[byte2]);
        }
        printf("\n");
        data += BPERL;
    }
}

/*****************************************************************************
*****************************************************************************/
int main(void)
{
    char boot[BPS], mbr[BPS], *pte; /* partition table entry */
    unsigned i, drive = 0x80;
    unsigned long j;

#ifdef __WATCOMC__

    atexit(free_dos_mem);
    get_dos_mem();
#endif

    /* read sector 0 of drive (the partition table; or MBR)
    int13_drive_num = 0x80 for drive C: */
    if(lba_biosdisk(drive, _DISK_READ, 0, 1, mbr))
    {
        printf("Error reading partition table on drive 0x%02X\n",
               drive);
        return 1;
    }
    printf("Dump of partition table on drive 0x%02X:\n", drive);
    dump(mbr + 446, 64);
    for(i = 0; i < 4; i++)
    {
        pte = mbr + 446 + 16 * i;
        /* skip partition if size == 0 */
        j = *(uint32_t *)(pte + 12);
        if(j == 0)
            continue;
        /* read sector 0 of partition (the boot sector) */
        j = *(uint32_t *)(pte + 8);
        if(lba_biosdisk(drive, _DISK_READ, j, 1, boot))
        {
            printf("Error reading bootsector of drive 0x%02X, "
                   "partition %u\n", drive, i);
            continue;
        }
        printf("Dump of bootsector for drive 0x%02X, "
               "partition %u:\n", drive, i);
        dump(boot, 64);
    }
    return 0;
}

How to compile

  • DJGPP:
    • gcc -O2 -o djgpplba.exe djgpplba.c
  • WATCOM:
    • wcl386/l=dos4g djgpplba.c
    • wcl/l=dos djgpplba.c

Download the Codesnippet

TODO: add link

Tested Compilers

Tested with:

  • OW1.3

(OW1.4 testing in progrss)

Legal information

Authors

LBA version of biosdisk() for DJGPP

Chris Giese <geezer@execpc.com> http://my.execpc.com/~geezer

WATCOM porting and DJGPP restructuring by

Rod Pemberton (Changes: Feb 19, 2005)

Copyright

Release date: Jan 2, 2004

This code is public domain (no copyright). You can do whatever you want with it.
The changes are also public domain (no copyright).

Useful links

Ralph Brown's Interrupt List (HTML Version): http://www.ctyme.com/rbrown.htm

Delorie's Page (DJGPP): http://www.delorie.com/

Personal tools