INF: Accessing Physical Memory in Windows 3.0 and 3.1
PSS ID Number: Q105643
Article last modified on 05-31-1994

3.00 3.10

WINDOWS


----------------------------------------------------------------------
The information in this article applies to:

 - Microsoft Windows Software Development Kit (SDK) for Windows,
   versions 3.0 and 3.1
----------------------------------------------------------------------

SUMMARY
=======

Some applications need to access memory at a particular physical address
fixed in the address space of the microprocessor. Such applications
typically interact directly with memory-mapped hardware device interface
cards, or may need to read the computer's CMOS settings. This article
outlines two methods to access physical memory in Windows protected mode
(standard mode or 386 enhanced mode).

MORE INFORMATION
================

Method 1: Exported Selectors (Preferred)
----------------------------------------

The Windows 3.0 and 3.1 Kernels export several selectors that should
be used by applications that require access to physical memory located
below the 1 megabyte (MB) boundary. The exported selectors are:

   __0000h, __0040h, __A000h, __B000h, __B800h, __C000h, __D000h,
   __E000h, and __F000h

To use one of these selectors, place it onto a segment register and
access the memory or create a long pointer. Here are examples using
the Microsoft Macro Assembler (MASM) and Microsoft C:

   In ASM:       extern  __0040h
                 ...
                 mov ax, __0040h
                 mov es, ax

   In C:         extern   WORD _0040h;
                 LPSTR    lpBIOSDataArea;
                 ...
                 /* Note the & and single underscore */
                 lpBIOSDataArea = (LPSTR)MAKELONG(0, &_0040h);

   In C++:       extern "C" WORD _0040h;
                 LPSTR    lpBIOSDataArea;
                 ...
                 /* Note the & and single underscore */
                 lpBIOSDataArea = (LPSTR)MAKELONG(0, &_0040h);

Each of these data selectors is limited to accessing 64 kilobytes (K).
An attempt to read or write to data beyond the limit causes a general
protection (GP) fault (evidenced by an unrecoverable application
error--UAE) in protected mode. Performing segment arithmetic with
these selectors also causes a GP fault. Finally, do not free these
selectors once they are no longer needed.
Method 1 is always recommended and should be used unless the exported
selectors do not provide access to the necessary area of physical
memory.

Method 2: Selector Synthesis
----------------------------

Selector synthesis should be used when an application needs to access
physical memory that is not addressable with the selectors exported
from the Windows Kernel. This method involves allocating a new
selector and initializing the associated descriptor with the
appropriate values. The functions to do this are provided through the
Windows Kernel and the DOS Protected Mode Interface (DPMI) server
(which is a part of Windows). The required functions include the
following:

Function                 Description
--------                 -----------

Map Physical To Linear   (DPMI: Interrupt 31h, AX=0800h) Maps a 32-bit
                         physical address to a 32-bit linear address.
                         In 386 enhanced mode this function is
                         required because because linear addresses
                         are different from physical addresses and
                         the selector functions can work only with
                         linear addresses.
                         In standard mode, this function is not
                         required because linear addresses are the
                         same as physical addresses.

AllocSelector(WORD wSelector)

                         (Kernel)  Allocates a new selector or
                         array of tiled selectors and copies the
                         attributes of wSelector to the new
                         selector(s). If the limit of wSelector is
                         less than or equal to 64K, then only one
                         selector is allocated. If the limit of
                         wSelector is larger than 64K, an array of
                         tiled selectors is allocated such that
                         each selector points to one 64K portion of
                         the limit of wSelector.

FreeSelector(WORD wSelector)

                         (Kernel) Frees either a single selector
                         or an array of tiled selectors depending
                         on the limit of wSelector. Frees one
                         selector for each 64K portion of the
                         limit of wSelector. The selector, or
                         array of tiled selectors being freed
                         must have been allocated previously by
                         AllocSelector. Furthermore, the limit
                         of wSelector must be the same as the
                         selector used as a parameter to the call
                         to AllocSelector.

SetSelectorBase(WORD wSelector, DWORD dwBase)

                         (Kernel) Stores the starting linear
                         address of the desired region in the
                         descriptor of wSelector.

SetSelectorLimit(WORD wSelector, DWORD dwLimit)

                         (Kernel) Stores the length of the desired
                         region in the descriptor of wSelector.

GetSelectorLimit(WORD wSelector)

                         (Kernel) Retrieves the length of the
                         region pointed to by wSelector. This
                         length comes from wSelector's descriptor.

Caveats:

1. These routines do not inform the Windows memory manager that a
   particular block of memory is in use. It is the responsibility of
   the caller to ensure that the area of memory will not be accessed
   or freed by some other process in the system.

2. Synthesized selectors that alias a memory object allocated by
   Windows will not be updated if the memory object is moved. To
   make sure that the memory object will not be moved, call
   GlobalFix() on it before synthesizing a selector that aliases it.
   Note that if the synthesized selector points to memory provided
   by a physical device, there is no need to call GlobalFix() because
the device's memory was not allocated by Windows.

3. Allocating large numbers of selectors is discouraged because
selectors are a limited resource.

4. Allocating a selector does not actually allocate any memory: it
   merely creates a pointer that can be used to access existing
   memory (that was previously allocated or is provided by a
   memory-mapped hardware device). Do not confuse allocating a
   selector with allocating memory.

The following code fragment has been written using the inline assembly
feature of the Microsoft C and C++ compilers. This code illustrates
the technique of selector synthesis.

Sample Code
-----------

   DWORD MapPhysicalToLinear(DWORD, DWORD);
   WORD  SynthSelector(DWORD, DWORD);
   LPSTR GetSelectorPointer(WORD);

      /* These prototypes are needed only for the Windows     */
      /* Software Development Kit (SDK) version 3.0.          */
      /* They are not needed in the Windows 3.1 SDK, because  */
      /* they are included in WINDOWS.H.                      */

   int   FAR PASCAL SetSelectorBase(WORD, DWORD);
   int   FAR PASCAL SetSelectorLimit(WORD, DWORD);
   DWORD FAR PASCAL GetSelectorLimit(WORD);

   LPSTR lpPhys;            // Could be declared a huge ptr instead.
   DWORD dwPhysical, dwLinear, dwLength;
   WORD  wSelector;
   WORD  segment, offset;   // These variables are necessary only
                            // if the target memory is addressed
                            // with a "real-mode" style SEG:OFFSET
                            // pointer.

   /*--------------------------------------------------------------*/
   /*        Sample Code to Synthesize a Selector                  */
   /*--------------------------------------------------------------*/
   /* Create a linear address.  The method to do this depends upon */
   /* where the memory is located.  Both methods are shown below,  */
   /* but only *one* must be used.                                 */
      /* If the selector will point to memory below 1 MB, create a  */
      /* linear address as follows (yes, this really is a linear   */
      /* address):                                                 */
   dwLinear = ((DWORD)segment << 4L) + offset;
      /* Otherwise, if the selector will point to memory above 1 MB,*/
      /* dwPhysical should contain the 32-bit physical address.    */
      /* Call DPMI to convert dwPhysical to a linear address.      */
      /* Note that you must pass the physical address and the      */
      /* length (limit) to DPMI.                                   */
   dwPhysical = 0xC00000;        // This is physical 12 MB address,
                                 // (for example purposes only).
   dwLinear = MapPhysicalToLinear(dwPhysical, dwLength);
   if (!dwLinear)
      {
      // error...
      }
      /* Now that dwLinear contains the linear address, it's time  */
      /* to allocate the selector and then the far pointer from    */
      /* the selector. For memory regions larger than 64K, a huge  */
      /* pointer should be created instead of a far pointer.       */
   wSelector = SynthSelector(dwLinear, dwLength); // Create selector.
   lpPhys = GetSelectorPointer(wSelector);        // Make a pointer.

      /* Use the pointer lpPhys to access memory...                */
      /* Free the selector when finished with it.  Don't need to   */
      /* make sure the selector's limit is less than 64K because   */
      /* SynthSelector creates an array of tiled selectors and we  */
      /* need to free them all.                                    */

   FreeSelector(wSelector);
   /* Rest of program...                                           */
   /*--------------------------------------------------------------*/
   /*  This function is a shell for DPMI Map Physical To Linear.   */
   /*  Returns 0 if it failed or the physical address is below     */
   /*  1 MB. Returns the linear address if DPMI call succeeded.     */
   /*--------------------------------------------------------------*/

   DWORD MapPhysicalToLinear(DWORD dwPhysical, DWORD dwLength)
   {
      DWORD dwLinear = 0L;          // In case memory below 1 MB, we
                                    // don't want to return garbage.
      if (dwPhysical >= 0x100000L)  // Use only if above 1 MB.
         {
         _asm {
              push    di
              push    si
              mov     bx, WORD PTR [dwPhysical+2] ; Load arguments.
              mov     cx, WORD PTR [dwPhysical]
              mov     si, WORD PTR [dwLength+2]
              mov     di, WORD PTR [dwLength]
              mov     ax, 800h
              int     31h                         ; Issue DPMI call.
              jc      short error_return
              mov     dx, bx
              mov     ax, cx
              jmp     short fine_return
      error_return:
              xor     ax, ax
              mov     dx, ax
      fine_return:
              mov     WORD PTR [dwLinear+2], dx   ; Return value.
              mov     WORD PTR [dwLinear], ax
              pop     si
              pop     di
              }
         }
      return dwLinear;
   }

   /*--------------------------------------------------------------*/
   /*   This function will allocate and initialize a selector.     */
   /*--------------------------------------------------------------*/

   WORD SynthSelector(DWORD dwLinearAddress, DWORD dwLength)
   {
      WORD tempSelector, selector = NULL;
        /* Allocate *one* temporary selector by using value of DS  */
        /* (DS contains selector of app's or DLL's DGROUP, which   */
        /* is less than 64K.) Then set the selector's base        */
        /* address and limit to the real values, which may be      */
        /* larger than 64K. Because the memory must be accessed by  */
        /* 16-bit code, it is necessary to allocate an array of    */
        /* tiled selectors. The temporary selector is used to     */
        /* force AllocSelector() to allocate an array with the     */
        /* proper number of tiled selectors each with the proper   */
        /* base and limit. Then, we free the single temporary      */
        /* selector.                                               */

      _asm {
           push     ds
           call     AllocSelector
           mov      tempSelector, ax
           }

      if (tempSelector)   /* AllocSelector returns NULL on error. */
         {
         SetSelectorBase(tempSelector, dwLinearAddress);
         SetSelectorLimit(tempSelector, dwLength);
         selector = AllocSelector(tempSelector);
         SetSelectorLimit(tempSelector, 100L);
         FreeSelector(tempSelector);
         }
      return selector;
   }

   /*--------------------------------------------------------------*/
   /*   This function builds a pointer to the memory referenced    */
   /*   by the selector.                                           */
   /*--------------------------------------------------------------*/

   LPSTR GetSelectorPointer(WORD selector)

   {
      return (LPSTR)MAKELONG(0, selector);
   }

Additional reference words: 3.00 3.10
KBCategory: Prg
KBSubcategory: KrMm

=============================================================================
Copyright Microsoft Corporation 1994.
