/*
LDDPMI.C -- undocumented DOS call from DPMI

Revised substantially from the version in UNDOCUMENTED DOS, pp. 74-80

This is a DOS program that starts out in real mode, and uses DPMI
to switch into protected mode.  In addition to using DPMI, it also
uses the protected-mode INT 21h services of the Windows 3.0 Enhanced
mode DOS extender (that's why calls to printf, etc. work below,
even after we've switched into protected mode).

For information on using DPMI in a Windows program, see the article
"The Programming Challenge of Windows Protected Mode" by Andrew
Schulman, _PC Magazine_, 25 June 1991.

Works with Microsoft C 6.0 (or higher) and Borland C++ 2.0 (or higher)
(Some of the _asm convolutions were needed for Borland C++)

sample output:
    in protected mode
    Real mode DOS List Of Lists = 028E:0026
    Protected DOS List Of Lists = 00AD:0026
    LASTDRIVE=E
        
Microsoft C 6.0 (or higher): cl -AS lddpmi.c     
Borland C++ 2.0 (or higher): bcc -ms lddpmi.c
*/

#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <assert.h>
#include <dos.h>

#ifdef __TURBOC__
#pragma inline
#define _dos_allocmem(x,y)      (allocmem(x, y) != -1)
#endif

#define ABSADDR(seg, ofs) \
    ((((unsigned long) seg) << 4) + ((ofs) & 0xFFFF))

#pragma pack(1)

typedef struct {
    unsigned long edi, esi, ebp, reserved, ebx, edx, ecx, eax;
    unsigned flags, es, ds, fs, gs, ip, cs, sp, ss;
    } RMODE_CALL;
    
typedef struct {
    unsigned char accessed   : 1;
    unsigned char read_write : 1;
    unsigned char conf_exp   : 1;
    unsigned char code       : 1;
    unsigned char xsystem    : 1;
    unsigned char dpl        : 2;
    unsigned char present    : 1;
    } ACCESS;
    
/* structure of a protected-mode descriptor */  
typedef struct {
    unsigned limit, addr_lo;
    unsigned char addr_hi;
    ACCESS access;
    unsigned char reserved, addr_xhi;
    } DESCRIPTOR;   
    
typedef enum { FALSE, TRUE } BOOL;

BOOL dpmi_rmode_intr(unsigned intno, unsigned flags, 
    unsigned copywords, RMODE_CALL far *rmode_call);

void dos_exit(unsigned char err)
{ 
    _asm mov al, err
    _asm mov ah, 04ch
    _asm int 21h
}

void fail(char *s)       { puts(s); dos_exit(1); }

/* Determines if DPMI is present and, if so, switches into
   protected mode */
BOOL dpmi_init(void)
{
    void (far *dpmi)();
    unsigned hostdata_seg, hostdata_para, dpmi_flags;
    
    _asm {
        mov ax, 1687h           // test for DPMI presence
        int 2Fh
        and ax, ax
        jnz nodpmi              // if (AX == 0) DPMI is present
        mov dpmi_flags, bx
        mov hostdata_para, si   // paras for DPMI host private data
        mov dpmi, di
        mov dpmi+2, es          // DPMI protected-mode switch entry point
        jmp short gotdpmi
        }
nodpmi:
    return FALSE;
gotdpmi:
    if (_dos_allocmem(hostdata_para, &hostdata_seg) != 0)
        fail("can't allocate memory");
    
    /* enter protected mode */
    _asm {
        mov ax, hostdata_seg
        mov es, ax
        mov ax, dpmi_flags
        }
    (*dpmi)();
        
    return TRUE;
}

/* Performs a real-mode interrupt from protected mode */
BOOL dpmi_rmode_intr(unsigned intno, unsigned flags, 
    unsigned copywords, RMODE_CALL far *rmode_call)
{
    if (flags) intno |= 0x100;
    _asm {
        push di
        push bx
        push cx
        mov ax, 0300h       // simulate real-mode interrupt
        mov bx, intno       // interrupt number, flags
        mov cx, copywords;  // words to copy from pmode to rmode stack
        les di, rmode_call  // ES:DI = address of rmode call struct
        int 31h             // call DPMI
        jc error
        mov ax, 1           // return TRUE
        jmp short done
        }
error:  
        _asm mov ax, 0           // return FALSE
done:   
        _asm pop cx
        _asm pop bx
        _asm pop di
}

/* Allocates a single protected-mode LDT selector */
unsigned dpmi_sel(void)
{
    _asm {
        mov ax, 0           // Allocate LDT Descriptors
        mov cx, 1           // allocate just one
        int 31h             // call DPMI
        jc err
        jmp short done      // AX holds new LDT selector
        }
err:    
        _asm mov ax, 0      // failed
done:;   
}

BOOL dpmi_set_descriptor(unsigned pmodesel, DESCRIPTOR far *d)
{
    _asm {
        push di
        push bx
        mov ax, 000ch       // Set Descriptor
        mov bx, pmodesel    // protected mode selector
        les di, d           // descriptor
        int 31h             // call DPMI
        jc error
        mov ax, 1           // return TRUE
        jmp short done
        }
error:  
        _asm mov ax, 0      // return FALSE
done:   
        _asm pop di
        _asm pop bx
}

BOOL dpmi_get_descriptor(unsigned pmodesel, DESCRIPTOR far *d)
{
    _asm {
        push di
        mov ax, 000bh       // Get Descriptor
        mov bx, word ptr pmodesel    // protected mode selector
        les di, dword ptr d // descriptor
        int 31h             // call DPMI
        jc error
        mov ax, 1           // return TRUE
        jmp short done
        }
error:  
        _asm xor ax, ax     // return FALSE
done:
        _asm pop di
}

BOOL dpmi_sel_free(unsigned pmodesel)
{
    _asm {
        mov ax, 0001h       // Free LDT Descriptor
        mov bx, pmodesel    // selector to free
        int 31h             // call DPMI
        jc error
        mov ax, 1           // return TRUE
        jmp short done
        }
error:  
        _asm mov ax, 0           // return FALSE
done:;
}

void far *get_doslist(void)
{
    _asm {
        xor bx, bx
        mov es, bx
        mov ah, 52h
        int 21h
        mov dx, es
        mov ax, bx
        }
}

main()
{
    DESCRIPTOR d;
    RMODE_CALL r;
    void far *fp;
    char far *doslist = (char far *) 0;
    unsigned long addr;
    unsigned pmodesel;
    unsigned offset, lastdrv_ofs, lastdrv;

    /* program requires small model! */
    assert((sizeof(void*) == 2) && (sizeof(void (*)()) == 2));
    
    assert(sizeof(ACCESS) == 1);
    assert(sizeof(DESCRIPTOR) == 8);
    
    /* Determine if DPMI present and, if so, switch to protected mode */  
    if (dpmi_init())
        puts("now in protected mode");
    else
        fail("DPMI not present");   
    
    /* Call INT 21h AH=52h (Get DOS List Of Lists) */
    memset(&r, 0, sizeof(RMODE_CALL));
    r.eax = 0x5200;
    if (! dpmi_rmode_intr(0x21, 0, 0, &r))
        fail("DPMI rmode intr failed");
    FP_SEG(doslist) = r.es;
    FP_OFF(doslist) = r.ebx;
    printf("Real mode DOS List Of Lists = %Fp\r\n", doslist);
    
    /* doslist now holds a real-mode address: in order to address it
       in protected mode, allocate an LDT descriptor and set its 
       contents; when done, deallocate the LDT descriptor
    */
    if (! (pmodesel = dpmi_sel()))
        fail("DPMI can't alloc pmode selector");
    
    /* set size of segment */
    d.limit = 0xFFFF;
    
    /* set base address of segment */
    addr = ABSADDR(r.es, 0);
    d.addr_lo = addr & 0xFFFF;
    d.addr_hi = addr >> 16;
    d.addr_xhi = 0;             /* IMPORTANT! */
    
    /* set access-rights of segment */
    d.access.accessed = 0;      /* never been used */
    d.access.read_write = 1;    /* read-write */
    d.access.conf_exp = 0;      /* not a stack */
    d.access.code = 0;          /* data */
    d.access.xsystem = 1;       /* not system descriptor */
    fp = (void far *) main;
    d.access.dpl = FP_SEG(fp) & 3;  /* protection level */
    d.access.present = 1;       /* it's present in memory */
    d.reserved = 0;

    if (! dpmi_set_descriptor(pmodesel, &d))
        fail("DPMI can't set descriptor");
    
    FP_SEG(doslist) = pmodesel; /* convert to protected-mode address */
    FP_OFF(doslist) = r.ebx;
    printf("Protected mode DOS List Of Lists = %Fp\r\n", doslist);
    
    /* now have protected-mode selector to DOS List of Lists */
    /* Get LASTDRIVE number, print LASTDRIVE letter */
    lastdrv = doslist[_osmajor==3 && _osminor==0 ? 0x1b : 0x21];
    printf("LASTDRIVE=%c\r\n", 'A' - 1 + lastdrv);
    
    if (! dpmi_sel_free(pmodesel))
        fail("DPMI can't free selector");
    
    /* in protected mode, flush output and quit */
    fflush(stdout);
    dos_exit(0);

dpmifail:
    fail("DPMI failure");
}
