www.delorie.com/archives/browse.cgi   search  
Mail Archives: djgpp/1996/09/18/05:03:32

Date: Wed, 18 Sep 96 09:32:04 BST
Message-Id: <9609180832.AA26846@leopard.proteon.com>
From: Neil Jarvis <Neil DOT Jarvis AT proteon DOT com>
To: dsmith AT cybernet DOT com
Cc: djgpp AT delorie DOT com
In-Reply-To: <323EB4A0.22CA@cybernet.com> (message from Derek Smith on Tue, 17
Sep 1996 10:24:32 -0400)
Subject: Re: Physical Memory Addresses
Reply-To: Neil DOT Jarvis AT proteon DOT com
Mime-Version: 1.0 (generated by tm-edit 7.43)

>>>>> "Derek" == Derek Smith <dsmith AT cybernet DOT com> writes:

    Derek> I am trying to get the physical address of a block of
    Derek> memory returned by doing a malloc (or some other memory
    Derek> allocation function) to pass to the DMA controller.  I
    Derek> cannot figure how to obtain this physical address.  Can
    Derek> anyone provide some insight for me.

malloc() returns you a virtual address, (you already worked that out),
and the physical address may or may not be in memory. Remember that
the data could be paged out to disk. Try and get the DMA to access the
data from there! However, all is not lost, so read on.


* Requirement to allocate small amounts of memory for the DMA

The trick here is to allocate some DOS memory (which you can easily
get the physical address of), and then map this to a "virtual" area
that you malloc(). The downside is that the amount of memory you can
allocate is limited by the amount of DOS memory you can actually
get. If the amount required is small (~200k), then there should be no
problems with this approach.  Here is some example code to make it
work, it has three interface functions:

allocBuffer(size) returns a virtual address (an unsigned char *), for
an area of DOS memory. The size *must* be page aligned - a multiple of
0x1000.

freeBuffer(pointer) frees the memory you allocated with allocBuffer()

virtToPhys(pointer) return the physical address of the DOS memory, or
0 if it fails. You pass in a pointer to any data within the allocated
region.

Note that given a virtual address, it is not possible to calculate the
physical address. Instead the routines record the physical addresses
allocated in a table. When you call virtToPhys(), it does a
lookup. This example code only allows MAX_HANDLES (10) regions to be
allocated at any one time.

If you do not need mulitple regions, allocBuffer() could be modified
to return both the virtual and physical addresses at the same
time. Left as an exercise for the reader.

#include <stdio.h>

#include <dos.h>
#include <pc.h>
#include <crt0.h>
#include <dpmi.h>
#include <go32.h>
#include <sys/farptr.h>
#include <errno.h>

#define MAX_HANDLES 10
typedef struct allocBufferS
{
   _go32_dpmi_seginfo seginfo;
   unsigned char *linearAddr;
   unsigned long physicalAddr;
   unsigned int regionSize;
} allocBufferS;
allocBufferS handles[MAX_HANDLES];
int nextHandle = 0;

allocBufferS *findHandle(unsigned char *linear)
{
   int handle = 0;

   while (handle < nextHandle)
      if (((unsigned long) linear >= 
           (unsigned long) handles[handle].linearAddr) &&
	  ((unsigned long) linear <= 
           (((unsigned long) handles[handle].linearAddr) + 
            handles[handle].regionSize)))
	 return &handles[handle];
      else
	 handle++;
   return NULL;
}

/*S Convert virtual to physical address
 */
unsigned long virtToPhys(void *virtualPtr)
{
   allocBufferS *handle = findHandle(virtualPtr);

   if (handle)
      return handle->physicalAddr + 
             ((unsigned long) virtualPtr - (unsigned long) handle->linearAddr);
   else
      return 0L;
}

/*S Allocate a buffer
 */
unsigned char *allocBuffer(unsigned int size)
{
   unsigned int pages = (size / 0x1000) + 2;
   unsigned int paragraphs = pages * (0x1000 / 0x10);

   /* Size must be paged aligned */
   if (size & 0xfff)
      return NULL;
   
   /* Any more handles ? */ 
   if ((nextHandle + 1) >= MAX_HANDLES)
      return NULL;

   handles[nextHandle].seginfo.size = paragraphs;
   if (_go32_dpmi_allocate_dos_memory(&handles[nextHandle].seginfo) != -1)
   {
      unsigned long pageAligned = ((handles[nextHandle].seginfo.rm_segment<<4)+
                                   0xfff) & ~0xfff;
	 
      if ((handles[nextHandle].linearAddr = calloc(1, size)) != NULL)
      {
         if (__djgpp_map_physical_memory(handles[nextHandle].linearAddr, 
                                         size, pageAligned) != -1)
         {
            handles[nextHandle].regionSize = size;
            handles[nextHandle].physicalAddr = pageAligned;
            return handles[nextHandle++].linearAddr;
         }
      }
	 
      /* We failed... */
      _go32_dpmi_free_dos_memory(&handles[nextHandle].seginfo);
   }
   return NULL;
}

void freeBuffer(unsigned char *buffer)
{
   allocBufferS *handle = findHandle(buffer);

   if (handle)
   {
      free(handle->linearAddr);
       _go32_dpmi_free_dos_memory(&handle->seginfo);
   }
}



* Requirement to allocate large amounts of memory for the DMA

Things get a lot trickier when you need a lot of memory. But here is a
solution. The virtToPhys() routine shown below can be used to
calculate the physical address of any malloced address.

NOTE: You must run your program with protecton level 0, i.e. use
      CWSDPR0.EXE or PMODETSR.EXE as your DPMI servers. This disables
      virtual memory, and prevents malloced data from being swapped to
      disk.

MORE NOTES: the _my_cr3() routine will cause a general protection
      violation if the code is not executed at protection level 0 This
      means that you cannot run this code from within GDB - e.g. no
      debugging.....

#include <dos.h>
#include <pc.h>
#include <crt0.h>
#include <dpmi.h>
#include <go32.h>
#include <sys/farptr.h>

/* Just to make me feel happier, I use the following. May not be needed. */
int _crt0_startup_flags = (_CRT0_FLAG_FILL_SBRK_MEMORY |
			   _CRT0_FLAG_FILL_DEADBEEF    |
			   _CRT0_FLAG_NONMOVE_SBRK     |
			   _CRT0_FLAG_LOCK_MEMORY      );

static __inline__ unsigned long _my_cr3(void)
{
   unsigned long result;
   __asm__("mov %%cr3,%0" : "=r" (result));
   return result;
}

/*S Convert virtual to physical address
 */
unsigned long virtToPhys(void *virtualPtr)
{
   unsigned long DSLinearAddr;

   if (__dpmi_get_segment_base_address(_go32_my_ds(), &DSLinearAddr) != -1)
   {
      unsigned long pde, pdeAddr, pte, pteAddr, linear;
	 
      /* Calculate linear address */
      linear = (unsigned long) virtualPtr + DSLinearAddr;
	 
      /* Calculate Page Directory Entry address */
      pdeAddr = _my_cr3() + ((linear & 0xffc00000L) >> 20);
	 
      /* Read Page Directory Entry */
      pde = _farpeekl(_dos_ds, pdeAddr);
	 
      /* Calculate Page Table Entry address */
      pteAddr = (pde & 0xfffff000L) + ((linear & 0x003ff000L) >> 10);
	 
      /* Read Page Table Entry */
      pte = _farpeekl(_dos_ds, pteAddr);
	 
      /* Calculate and return Page Frame address */
      return (pte & 0xfffff000L) + (linear & 0x00000fffL);
   }
   else
      return 0L;
}

This has been a long message, but I hope it helps. My current project
had a requirement to allocate between 4 and 8Mb of memory, which must
be accessible to DMA controllers. Therefore I used the second method.
To get around the no debug problem, I run my program in a special
debug mode, where the amount of memory allocated is a lot smaller
(about 200-300k). This automagically enables the first allocation
method, and I can run the program from the debugger. Not the best
solution, but it keeps me happy :-)

BTW, if anyone can tell me how to read the CPU's CR3 register when you
are not a protection level 0, I would love you here from you! I'm
pretty sure it is impossible without hacking the DPMI server to give
you the value through a new (non-DPMI) interface call.

-- Neil Jarvis, Proteon International R&D, York, UK. (Neil DOT Jarvis AT proteon DOT com)
--    Club sandwiches, not seals!

- Raw text -


  webmaster     delorie software   privacy  
  Copyright © 2019   by DJ Delorie     Updated Jul 2019