Date: Wed, 18 Sep 96 09:32:04 BST Message-Id: <9609180832.AA26846@leopard.proteon.com> From: Neil Jarvis 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) Content-Type: text/plain; charset=US-ASCII >>>>> "Derek" == Derek Smith 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 #include #include #include #include #include #include #include #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 #include #include #include #include #include /* 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!