]> mj.ucw.cz Git - pciutils.git/blob - lib/physmem-djgpp.c
libpci: Add DJGPP physmem support for PCIe ECAM access
[pciutils.git] / lib / physmem-djgpp.c
1 /*
2  *      The PCI Library -- Physical memory mapping for DJGPP
3  *
4  *      Copyright (c) 2023 Pali Rohár <pali@kernel.org>
5  *
6  *      Can be freely distributed and used under the terms of the GNU GPL v2+
7  *
8  *      SPDX-License-Identifier: GPL-2.0-or-later
9  */
10
11 #include "internal.h"
12 #include "physmem.h"
13
14 #include <errno.h>
15 #include <stdlib.h>
16 #include <stdio.h> /* for __DJGPP__ and __DJGPP_MINOR__, available since DJGPP v2.02 and defined indirectly via sys/version.h */
17 #include <string.h> /* for ffs() */
18 #include <malloc.h> /* for memalign() */
19
20 #include <dpmi.h>
21 #include <crt0.h> /* for _crt0_startup_flags, __djgpp_memory_handle_list, __djgpp_memory_handle_size and __djgpp_memory_handle() */
22 #include <sys/nearptr.h> /* for __djgpp_conventional_base, __djgpp_nearptr_enable() and __djgpp_nearptr_disable() */
23
24 #ifndef EOVERFLOW
25 #define EOVERFLOW 40 /* defined since DJGPP v2.04 */
26 #endif
27
28 /*
29  * For using __djgpp_conventional_base it is needed to ensure that Unix-like
30  * sbrk algorithm is not active (by setting _CRT0_FLAG_NONMOVE_SBRK startup flag)
31  * and avoiding to call functions like system, spawn*, or exec*.
32  */
33 int _crt0_startup_flags = _CRT0_FLAG_NONMOVE_SBRK;
34
35 static void *
36 aligned_alloc(size_t alignment, size_t size)
37 {
38   /*
39    * Unfortunately DJGPP prior to 2.6 has broken memalign() function,
40    * so for older DJGPP versions use malloc() with manual aligning.
41    */
42 #if !defined(__DJGPP__) || __DJGPP__ < 2 || (__DJGPP__ == 2 && __DJGPP_MINOR__ < 6)
43   void *ptr_alloc, *ptr_aligned;
44
45   if (alignment < 8)
46     alignment = 8;
47
48   ptr_alloc = malloc(size + alignment);
49   if (!ptr_alloc)
50     return NULL;
51
52   ptr_aligned = (void *)(((unsigned long)ptr_alloc & ~(alignment-1)) + alignment);
53
54   /*
55    * Store original pointer from malloc() before our aligned pointer.
56    * DJGPP malloc()'ed ptr_alloc is aligned to 8 bytes, our ptr_alloc is
57    * aligned at least to 8 bytes, so we have always 4 bytes of free space
58    * before memory where is pointing ptr_alloc.
59    */
60   *((unsigned long *)ptr_aligned-1) = (unsigned long)ptr_alloc;
61
62   return ptr_aligned;
63 #else
64   return memalign(alignment, size);
65 #endif
66 }
67
68 static void
69 aligned_free(void *ptr)
70 {
71 #if !defined(__DJGPP__) || __DJGPP__ < 2 || (__DJGPP__ == 2 && __DJGPP_MINOR__ < 6)
72   /* Take original pointer returned by malloc() for releasing memory. */
73   ptr = (void *)*((unsigned long *)ptr-1);
74 #endif
75   free(ptr);
76 }
77
78 static int
79 find_sbrk_memory_handle(void *ptr, unsigned long max_length UNUSED /*pre-v2.04*/, unsigned long pagesize UNUSED /*pre-v2.04*/, const __djgpp_sbrk_handle **sh, unsigned long *sh_size)
80 {
81   /*
82    * Find a DJGPP's sbrk memory handle which belongs to the ptr address pointer
83    * and detects size of this memory handle. DJGPP since v2.04 has arrays
84    * __djgpp_memory_handle_list[] and __djgpp_memory_handle_size[] with sbrk
85    * ranges which can be simple traversed. Older DJGPP versions have only
86    * __djgpp_memory_handle() function which returns information to which handle
87    * passed pointer belongs. So finding the size of the memory handle for DJGPP
88    * pre-v2.04 version is slower, its time complexity is O(N^2).
89    */
90 #if !defined(__DJGPP__) || __DJGPP__ < 2 || (__DJGPP__ == 2 && __DJGPP_MINOR__ < 4)
91
92   const __djgpp_sbrk_handle *sh2;
93   unsigned long end_offset;
94
95   *sh = __djgpp_memory_handle((unsigned long)ptr);
96
97   for (end_offset = max_length-1; end_offset != 0; end_offset = end_offset > pagesize ? end_offset - pagesize : 0)
98     {
99       sh2 = __djgpp_memory_handle((unsigned long)ptr + end_offset);
100       if (!*sh || !sh2)
101         {
102           /*
103            * If sh or sh2 is NULL then it is probably a memory corruption in
104            * DJGPP's __djgpp_memory_handle_list[] structure.
105            */
106           return 0;
107         }
108       if ((*sh)->handle == sh2->handle)
109         break;
110     }
111
112   if (end_offset == 0)
113     {
114       /*
115        * If end page of the sh handle was not found then it is probably a memory
116        * corruption in DJGPP's __djgpp_memory_handle_list[] structure.
117        */
118       return 0;
119     }
120
121   *sh_size = (unsigned long)ptr + end_offset+1 - (*sh)->address;
122   return 1;
123
124 #else
125
126   size_t i;
127
128   for (i = 0; i < sizeof(__djgpp_memory_handle_list)/sizeof(__djgpp_memory_handle_list[0]) && (i == 0 || __djgpp_memory_handle_list[i].address != 0); i++)
129     {
130       if ((unsigned long)ptr >= __djgpp_memory_handle_list[i].address &&
131           (unsigned long)ptr < __djgpp_memory_handle_list[i].address + __djgpp_memory_handle_size[i])
132         break;
133     }
134
135   if ((i != 0 && __djgpp_memory_handle_list[i].address == 0) || __djgpp_memory_handle_size[i] == 0)
136     {
137       /*
138        * If address range was not found in __djgpp_memory_handle_list[]
139        * then it is probably memory corruption in this list.
140        */
141       return 0;
142     }
143
144   *sh = &__djgpp_memory_handle_list[i];
145   *sh_size = __djgpp_memory_handle_size[i];
146   return 1;
147
148 #endif
149 }
150
151 static int
152 set_and_get_page_attributes(__dpmi_meminfo *mi, short *attributes)
153 {
154   unsigned long size;
155   int error;
156   size_t i;
157
158   /* __dpmi_set_page_attributes modifies mi.size */
159   size = mi->size;
160   if (__dpmi_set_page_attributes(mi, attributes) != 0)
161     {
162       error = __dpmi_error;
163       free(attributes);
164       switch (error)
165         {
166         case 0x0507: /* Unsupported function (returned by DPMI 0.9 host, error number is same as DPMI function number) */
167         case 0x8001: /* Unsupported function (returned by DPMI 1.0 host) */
168           errno = ENOSYS;
169           break;
170         case 0x8010: /* Resource unavailable (DPMI host cannot allocate internal resources to complete an operation) */
171         case 0x8013: /* Physical memory unavailable */
172         case 0x8014: /* Backing store unavailable */
173           errno = ENOMEM;
174           break;
175         case 0x8002: /* Invalid state (page in wrong state for request) */
176         case 0x8021: /* Invalid value (illegal request in bits 0-2 of one or more page attribute words) */
177         case 0x8023: /* Invalid handle (in ESI) */
178         case 0x8025: /* Invalid linear address (specified range is not within specified block) */
179           errno = EINVAL;
180           break;
181         default: /* Other unspecified error */
182           errno = EACCES;
183           break;
184         }
185       return -1;
186     }
187   mi->size = size;
188
189   /* Cleanup output buffer. */
190   for (i = 0; i < mi->size; i++)
191     attributes[i] = 0;
192
193   if (__dpmi_get_page_attributes(mi, attributes) != 0)
194     {
195       error = __dpmi_error;
196       free(attributes);
197       switch (error)
198         {
199         case 0x0506: /* Unsupported function (returned by DPMI 0.9 host, error number is same as DPMI function number) */
200         case 0x8001: /* Unsupported function (returned by DPMI 1.0 host) */
201           errno = ENOSYS;
202           break;
203         case 0x8010: /* Resource unavailable (DPMI host cannot allocate internal resources to complete an operation) */
204           errno = ENOMEM;
205           break;
206         case 0x8023: /* Invalid handle (in ESI) */
207         case 0x8025: /* Invalid linear address (specified range is not within specified block) */
208           errno = EINVAL;
209           break;
210         default: /* Other unspecified error */
211           errno = EACCES;
212           break;
213         }
214       return -1;
215     }
216
217   return 0;
218 }
219
220 void
221 physmem_init_config(struct pci_access *a)
222 {
223   pci_define_param(a, "devmem.path", "auto", "DJGPP physical memory access method: auto, devmap, physmap");
224 }
225
226 int
227 physmem_access(struct pci_access *a UNUSED, int w UNUSED)
228 {
229   return 0;
230 }
231
232 #define PHYSMEM_DEVICE_MAPPING ((struct physmem *)1)
233 #define PHYSMEM_PHYSADDR_MAPPING ((struct physmem *)2)
234
235 static int fat_ds_count;
236
237 struct physmem *
238 physmem_open(struct pci_access *a, int w UNUSED)
239 {
240   const char *devmem = pci_get_param(a, "devmem.path");
241   __dpmi_version_ret version;
242   char vendor[128];
243   int capabilities;
244   int try_devmap;
245   int try_physmap;
246   int ret;
247
248   if (strcmp(devmem, "auto") == 0)
249     {
250       try_devmap = 1;
251       try_physmap = 1;
252     }
253   else if (strcmp(devmem, "devmap") == 0)
254     {
255       try_devmap = 1;
256       try_physmap = 0;
257     }
258   else if (strcmp(devmem, "physmap") == 0)
259     {
260       try_devmap = 0;
261       try_physmap = 1;
262     }
263   else
264     {
265       try_devmap = 0;
266       try_physmap = 0;
267     }
268
269   ret = __dpmi_get_version(&version);
270   if (ret != 0)
271     a->debug("detected unknown DPMI host...");
272   else
273     {
274       /*
275        * Call DPMI 1.0 function __dpmi_get_capabilities() for detecting if DPMI
276        * host supports Device mapping. Some DPMI 0.9 hosts like Windows's NTVDM
277        * do not support this function, so does not fill capabilities and vendor
278        * buffer, but returns success. Detect this kind of failure by checking
279        * if AX register (low 16-bits of capabilities variable) was not modified
280        * and contains the number of called DPMI function (0x0401).
281        */
282       vendor[0] = vendor[1] = vendor[2] = 0;
283       ret = __dpmi_get_capabilities(&capabilities, vendor);
284       if (ret == 0 && (capabilities & 0xffff) == 0x0401)
285         ret = -1;
286
287       if (ret == 0)
288         a->debug("detected DPMI %u.%02u host %.126s %u.%u with flags 0x%x and capabilities 0x%x...",
289                   (unsigned)version.major, (unsigned)version.minor, vendor+2,
290                   (unsigned)(unsigned char)vendor[0], (unsigned)(unsigned char)vendor[1],
291                   (unsigned)version.flags, capabilities);
292       else
293         a->debug("detected DPMI %u.%02u host with flags 0x%x...",
294                   (unsigned)version.major, (unsigned)version.minor, (unsigned)version.flags);
295     }
296
297   /*
298    * If device mapping was selected then use __dpmi_map_device_in_memory_block()
299    * for physical memory mapping. Does not have to be supported by DPMI 0.9 host.
300    * Device mapping is supported when capability bit 2 is set.
301    */
302   if (try_devmap)
303     {
304       if (ret == 0 && (capabilities & (1<<2)))
305         {
306           a->debug("using physical memory access via Device Mapping...");
307           return PHYSMEM_DEVICE_MAPPING;
308         }
309       a->debug("DPMI Device Mapping not supported...");
310     }
311
312   /*
313    * If device mapping was not tried or not supported by DPMI host then fallback
314    * to __dpmi_physical_address_mapping(). But this requires Fat DS descriptor,
315    * meaning to increase DS descriptor limit to 4 GB, which does not have to be
316    * supported by some DPMI hosts.
317    */
318   if (try_physmap)
319     {
320       if (fat_ds_count != 0 || __djgpp_nearptr_enable())
321         {
322           fat_ds_count++;
323           a->debug("using physical memory access via Physical Address Mapping...");
324           return PHYSMEM_PHYSADDR_MAPPING;
325         }
326
327       /*
328        * DJGPP prior to 2.6 has semi-broken __djgpp_nearptr_enable() function.
329        * On failure it may let DS descriptor limit in semi-broken state. So for
330        * older DJGPP versions call __djgpp_nearptr_disable() which fixes it.
331        */
332 #if !defined(__DJGPP__) || __DJGPP__ < 2 || (__DJGPP__ == 2 && __DJGPP_MINOR__ < 6)
333       __djgpp_nearptr_disable();
334 #endif
335       a->debug("DPMI Physical Address Mapping not usable because Fat DS descriptor not supported...");
336     }
337
338   /*
339    * Otherwise we do not have access to physical memory mapping. Theoretically
340    * it could be possible to use __dpmi_physical_address_mapping() and then
341    * create new segment where mapped linear address would be available, but this
342    * would require to access memory in newly created segment via far pointers,
343    * which is not only mess in the native 32-bit application but also these far
344    * pointers are not supported by gcc. If DPMI host does not allow us to change
345    * DS descriptor limit to 4 GB then it is mostly due to security reasons and
346    * probably does not allow access to physical memory mapping. This applies
347    * for non-DOS OS systems with integrated DPMI hosts like in Windows NT NTVDM
348    * or older version of Linux dosemu.
349    */
350   a->debug("physical memory access not allowed...");
351   errno = EACCES;
352   return NULL;
353 }
354
355 void
356 physmem_close(struct physmem *physmem)
357 {
358   /* Disable 4 GB limit on DS descriptor if it was the last user. */
359   if (physmem == PHYSMEM_PHYSADDR_MAPPING)
360     {
361       fat_ds_count--;
362       if (fat_ds_count == 0)
363         __djgpp_nearptr_disable();
364     }
365 }
366
367 long
368 physmem_get_pagesize(struct physmem *physmem UNUSED)
369 {
370   static unsigned long pagesize;
371   if (!pagesize)
372     {
373       if (__dpmi_get_page_size(&pagesize) != 0)
374         pagesize = 0;
375       if (pagesize & (pagesize-1))
376         pagesize = 0;
377       if (!pagesize)
378         pagesize = 4096; /* Fallback value, the most commonly used on x86. */
379     }
380   return pagesize;
381 }
382
383 void *
384 physmem_map(struct physmem *physmem, u64 addr, size_t length, int w)
385 {
386   long pagesize = physmem_get_pagesize(physmem);
387   unsigned pagesize_shift = ffs(pagesize)-1;
388   const __djgpp_sbrk_handle *sh;
389   unsigned long sh_size;
390   unsigned long size;
391   __dpmi_meminfo mi;
392   short *attributes;
393   short one_pg_attr;
394   size_t offset;
395   int error;
396   void *ptr;
397   size_t i;
398
399   /* Align length to page size. */
400   if (length & (pagesize-1))
401     length = (length & ~(pagesize-1)) + pagesize;
402
403   /* Mapping of physical memory above 4 GB is not possible. */
404   if (addr >= 0xffffffffUL || addr + length > 0xffffffffUL)
405     {
406       errno = EOVERFLOW;
407       return (void *)-1;
408     }
409
410   if (physmem == PHYSMEM_DEVICE_MAPPING)
411     {
412       /*
413        * __dpmi_map_device_in_memory_block() maps physical memory to any
414        * page-aligned linear address for which we have DPMI memory handle. But
415        * DPMI host does not have to support mapping of memory below 1 MB which
416        * lies in RAM, and is not device memory.
417        *
418        * __djgpp_map_physical_memory() function is DJGPP wrapper around
419        * __dpmi_map_device_in_memory_block() which properly handles memory
420        * range that span multiple DPMI memory handles. It is common that
421        * DJGPP sbrk() or malloc() allocator returns continuous memory range
422        * which is backed by two or more DPMI memory handles which represents
423        * consecutive memory ranges without any gap.
424        *
425        * __dpmi_map_conventional_memory_in_memory_block() aliases memory range
426        * specified by page-aligned linear address to another page-aligned linear
427        * address. This can be used for mapping memory below 1 MB which lies in
428        * RAM and for which cannot be used __dpmi_map_device_in_memory_block().
429        * This function calls takes (virtual) linear address as opposite of the
430        * __dpmi_map_device_in_memory_block() which takes physical address.
431        *
432        * Unfortunately __djgpp_map_physical_memory() internally calls only
433        * __djgpp_map_physical_memory() function and does not return information
434        * for which memory range the call failed. So it cannot be used for
435        * generic memory mapping requests.
436        *
437        * Also it does not return usefull errno. And even in the latest released
438        * DJGPP version v2.5 this function has suboptimal implementation. Its
439        * time complexity is O(N^2) (where N is number of pages).
440        *
441        * So do not use __djgpp_map_physical_memory() function and instead write
442        * own logic handling virtual memory ranges which spans multiple DPMI
443        * memory handles and manually calls __dpmi_map_device_in_memory_block()
444        * or __dpmi_map_conventional_memory_in_memory_block() per every handle.
445        *
446        * We can easily access only linear addresses in our DS segment which
447        * is managed by DJGPP sbrk allocator. So allocate page-aligned range
448        * by aligned_alloc() (our wrapper around malloc()/memalign()) and then
449        * for every subrange which is backed by different DPMI memory handle
450        * call appropriate mapping function which correctly calculated offset
451        * and length to have continuous representation of physical memory range.
452        *
453        * This approach has disadvantage that for each mapping it is required
454        * to reserve and allocate committed memory in RAM with the size of the
455        * mapping itself. This has negative impact for mappings of large sizes.
456        * Unfortunately this is the only way because DJGPP sbrk allocator does
457        * not have any (public) function for directly allocating uncommitted
458        * memory which is not backed by the RAM. Even if DJGPP sbrk code is
459        * extended for this functionality, the corresponding DPMI function
460        * __dpmi_allocate_linear_memory() is DPMI 1.0 function and not widely
461        * supported by DPMI hosts, even the default DJGPP's CWSDPMI does not
462        * support it.
463        */
464
465       ptr = aligned_alloc(pagesize, length);
466       if (!ptr)
467         {
468           errno = ENOMEM;
469           return (void *)-1;
470         }
471
472       for (offset = 0; offset < length; offset += (mi.size << pagesize_shift))
473         {
474           /*
475            * Find a memory handle with its size which belongs to the pointer
476            * address ptr+offset. Base address and size of the memory handle
477            * must be page aligned for memory mapping support.
478            */
479           if (!find_sbrk_memory_handle(ptr + offset, length - offset, pagesize, &sh, &sh_size) ||
480               (sh->address & (pagesize-1)) || (sh_size & (pagesize-1)))
481             {
482               /*
483                * Failure detected. If we have some partial mapping, try to undo
484                * it via physmem_unmap() which also release ptr. If we do not
485                * have partial mapping, just release ptr.
486                */
487               if (offset != 0)
488                 physmem_unmap(physmem, ptr, offset);
489               else
490                 aligned_free(ptr);
491               errno = EINVAL;
492               return (void *)-1;
493             }
494
495           mi.handle = sh->handle;
496           mi.address = (unsigned long)ptr + offset - sh->address;
497           mi.size = (length - offset) >> pagesize_shift;
498           if (mi.size > ((sh_size - mi.address) >> pagesize_shift))
499             mi.size = (sh_size - mi.address) >> pagesize_shift;
500           if (__dpmi_map_device_in_memory_block(&mi, addr + offset) != 0)
501             {
502               /*
503                * __dpmi_map_device_in_memory_block() may fail for memory range
504                * which belongs to non-device memory below 1 MB. DPMI host in
505                * this case returns DPMI error code 0x8003 (System integrity -
506                * invalid device address). For example this is behavior of DPMI
507                * host HX HDPMI32, which strictly differs between non-device and
508                * device memory. If the physical memory range belongs to the
509                * non-device conventional memory and DPMI host uses 1:1 mappings
510                * for memory below 1 MB then we can try to alias range of linear
511                * address below 1 MB to DJGPP's accessible linear address range.
512                * For this aliasing of linear (not the physical) memory address
513                * ranges below 1 MB boundary is there an additional DPMI 1.0
514                * function __dpmi_map_conventional_memory_in_memory_block().
515                * But DPMI host does not have to support it. HDPMI32 supports it.
516                * If the memory range crosses 1 MB boundary then call it only for
517                * the subrange of memory which below 1 MB boundary and let the
518                * remaining subrange for the next iteration of the outer loop.
519                * Because the remaining memory range is above 1 MB limit, only
520                * the __dpmi_map_device_in_memory_block() would be used. This
521                * approach makes continues linear range of the mapped memory.
522                */
523               if (__dpmi_error == 0x8003 && addr + offset < 1*1024*1024UL)
524                 {
525                   /* reuse mi */
526                   if (addr + offset + (mi.size << pagesize_shift) > 1*1024*1024UL)
527                     mi.size = (1*1024*1024UL - addr - offset) >> pagesize_shift;
528                   if (__dpmi_map_conventional_memory_in_memory_block(&mi, addr + offset) != 0)
529                     {
530                       /*
531                        * Save __dpmi_error because any DJGPP function may change
532                        * it. If we have some partial mapping, try to undo it via
533                        * physmem_unmap() which also release ptr. If we do not
534                        * have partial mapping, just release ptr.
535                        */
536                       error = __dpmi_error;
537                       if (offset != 0)
538                         physmem_unmap(physmem, ptr, offset);
539                       else
540                         aligned_free(ptr);
541                       switch (error)
542                         {
543                         case 0x0509: /* Unsupported function (returned by DPMI 0.9 host, error number is same as DPMI function number) */
544                         case 0x8001: /* Unsupported function (returned by DPMI 1.0 host) */
545                           /*
546                            * Conventional Memory Mapping is not supported.
547                            * Device Mapping is supported, but DPMI host rejected
548                            * Device Mapping request. So reports same errno value
549                            * as from the failed Device Mapping switch case,
550                            * which is ENXIO (because __dpmi_error == 0x8003).
551                            */
552                           errno = ENXIO;
553                           break;
554                         case 0x8003: /* System integrity (invalid conventional memory address) */
555                           errno = ENXIO;
556                           break;
557                         case 0x8010: /* Resource unavailable (DPMI host cannot allocate internal resources to complete an operation) */
558                           errno = ENOMEM;
559                           break;
560                         case 0x8023: /* Invalid handle (in ESI) */
561                         case 0x8025: /* Invalid linear address (specified range is not within specified block, or EBX/EDX is not page aligned) */
562                           errno = EINVAL;
563                           break;
564                         default: /* Other unspecified error */
565                           errno = EACCES;
566                           break;
567                         }
568                       return (void *)-1;
569                     }
570                 }
571               else
572                 {
573                   /*
574                    * Save __dpmi_error because any DJGPP function may change
575                    * it. If we have some partial mapping, try to undo it via
576                    * physmem_unmap() which also release ptr. If we do not
577                    * have partial mapping, just release ptr.
578                    */
579                   error = __dpmi_error;
580                   if (offset != 0)
581                     physmem_unmap(physmem, ptr, offset);
582                   else
583                     aligned_free(ptr);
584                   switch (error)
585                     {
586                     case 0x0508: /* Unsupported function (returned by DPMI 0.9 host, error number is same as DPMI function number) */
587                     case 0x8001: /* Unsupported function (returned by DPMI 1.0 host) */
588                       errno = ENOSYS;
589                       break;
590                     case 0x8003: /* System integrity (invalid device address) */
591                       errno = ENXIO;
592                       break;
593                     case 0x8010: /* Resource unavailable (DPMI host cannot allocate internal resources to complete an operation) */
594                       errno = ENOMEM;
595                       break;
596                     case 0x8023: /* Invalid handle (in ESI) */
597                     case 0x8025: /* Invalid linear address (specified range is not within specified block or EBX/EDX is not page-aligned) */
598                       errno = EINVAL;
599                       break;
600                     default: /* Other unspecified error */
601                       errno = EACCES;
602                       break;
603                     }
604                   return (void *)-1;
605                 }
606             }
607
608           /*
609            * For read-only mapping try to change page attributes with not changing
610            * page type (3) and setting read-only access (bit 3 unset). Ignore any
611            * failure as this function requires DPMI 1.0 host and so it does not have
612            * to be supported by other DPMI 0.9 hosts. Note that by default newly
613            * created mapping has read/write access and so we can use it also for
614            * mappings which were requested as read-only too.
615            */
616           if (!w)
617             {
618               attributes = malloc(mi.size * sizeof(*attributes));
619               if (attributes)
620                 {
621                   /* reuse mi */
622                   for (i = 0; i < mi.size; i++)
623                     attributes[i] = (0<<3) | 3;
624
625                   /* __dpmi_set_page_attributes modifies mi.size */
626                   size = mi.size;
627                   __dpmi_set_page_attributes(&mi, attributes);
628                   mi.size = size;
629
630                   free(attributes);
631                 }
632             }
633         }
634
635       return ptr;
636     }
637   else if (physmem == PHYSMEM_PHYSADDR_MAPPING)
638     {
639       /*
640        * __dpmi_physical_address_mapping() is DPMI 0.9 function and so does not
641        * require device mapping support. But DPMI hosts often allow to used it
642        * only for memory above 1 MB and also we do not have control where DPMI
643        * host maps physical memory. Because this is DPMI 0.9 function, error
644        * code on failure does not have to be provided. If DPMI host does not
645        * provide error code then in __dpmi_error variable is stored the called
646        * DPMI function number (0x0800 is for Physical Address Mapping).
647        * Error codes are provided only by DPMI 1.0 hosts.
648        */
649
650       mi.address = addr;
651       mi.size = length;
652       if (__dpmi_physical_address_mapping(&mi) != 0)
653         {
654           /*
655            * __dpmi_physical_address_mapping() may fail for memory range which
656            * starts below 1 MB. DPMI 1.0 host in this case returns DPMI error
657            * code 0x8021 (Invalid value - address is below 1 MB boundary).
658            * DPMI 0.9 host does not provide error code, so __dpmi_error contains
659            * value 0x0800. For example this is behavior of the default DJGPP's
660            * DPMI host CWSDPMI and also of Windows 3.x DPMI host. On the other
661            * hand DPMI host HX HDPMI32 or Windows 9x DPMI host allow requests
662            * for memory ranges below 1 MB and do not fail.
663            */
664           if ((__dpmi_error == 0x0800 || __dpmi_error == 0x8021) && addr < 1*1024*1024UL)
665             {
666               /*
667                * Expects that conventional memory below 1 MB is always 1:1
668                * mapped. On non-paging DPMI hosts it is always truth and paging
669                * DPMI hosts should do it too or at least provide mapping with
670                * compatible or emulated content for compatibility with existing
671                * DOS applications. So check that requested range is below 1 MB.
672                */
673               if (addr + length > 1*1024*1024UL)
674                 {
675                   errno = ENXIO;
676                   return (void *)-1;
677                 }
678
679               /*
680                * Simulate successful __dpmi_physical_address_mapping() call by
681                * setting the 1:1 mapped address.
682                */
683               mi.address = addr;
684             }
685           else
686             {
687               switch (__dpmi_error)
688                 {
689                 case 0x0800: /* Error code was not provided (returned by DPMI 0.9 host, error number is same as DPMI function number) */
690                   errno = EACCES;
691                   break;
692                 case 0x8003: /* System integrity (DPMI host memory region) */
693                 case 0x8021: /* Invalid value (address is below 1 MB boundary) */
694                   errno = ENXIO;
695                   break;
696                 case 0x8010: /* Resource unavailable (DPMI host cannot allocate internal resources to complete an operation) */
697                   errno = ENOMEM;
698                   break;
699                 default: /* Other unspecified error */
700                   errno = EACCES;
701                   break;
702                 }
703               return (void *)-1;
704             }
705         }
706
707       /*
708        * Function returns linear address of the mapping. On non-paging DPMI
709        * hosts it does nothing and just returns same passed physical address.
710        * With DS descriptor limit set to 4 GB (set by __djgpp_nearptr_enable())
711        * we have direct access to any linear address. Direct access to specified
712        * linear address is from the __djgpp_conventional_base offset. Note that
713        * this is always read/write access, and there is no way to make access
714        * just read-only.
715        */
716       ptr = (void *)(mi.address + __djgpp_conventional_base);
717
718       /*
719        * DJGPP CRT code on paging DPMI hosts enables NULL pointer protection by
720        * disabling access to the zero page. If we are running on DPMI host which
721        * does 1:1 mapping and we were asked for physical address range mapping
722        * which includes also our zero page, then we have to disable NULL pointer
723        * protection to allow access to that mapped page. Detect this by checking
724        * that our zero page [0, pagesize-1] does not conflict with the returned
725        * address range [ptr, ptr+length] (note that length is already multiply
726        * of pagesize) and change page attributes to committed page type (1) and
727        * set read/write access (bit 3 set). Ignore any failure as this function
728        * requires DPMI 1.0 host and so it does not have to be supported by other
729        * DPMI 0.9 hosts. In this case DJGPP CRT code did not enable NULL pointer
730        * protection and so zero page can be normally accessed.
731        */
732       if ((unsigned long)ptr - 1 > (unsigned long)ptr - 1 + length)
733         {
734           mi.handle = __djgpp_memory_handle_list[0].handle;
735           mi.address = 0;
736           mi.size = 1; /* number of pages */
737           one_pg_attr = (1<<3) | 1;
738           /* __dpmi_set_page_attributes modifies mi.size */
739           __dpmi_set_page_attributes(&mi, &one_pg_attr);
740         }
741
742       return ptr;
743     }
744
745   /* invalid physmem parameter */
746   errno = EBADF;
747   return (void *)-1;
748 }
749
750 int
751 physmem_unmap(struct physmem *physmem, void *ptr, size_t length)
752 {
753   long pagesize = physmem_get_pagesize(physmem);
754   unsigned pagesize_shift = ffs(pagesize)-1;
755   const __djgpp_sbrk_handle *sh;
756   unsigned long sh_size;
757   __dpmi_meminfo mi;
758   short *attributes;
759   size_t offset;
760   size_t i;
761
762   /* Align length to page size. */
763   if (length & (pagesize-1))
764     length = (length & ~(pagesize-1)) + pagesize;
765
766   if (physmem == PHYSMEM_DEVICE_MAPPING)
767     {
768       /*
769        * Memory mapped by __dpmi_map_conventional_memory_in_memory_block() or by
770        * __dpmi_map_device_in_memory_block() can be unmapped by changing page
771        * attributes back to the what allocator use: page type to committed (1),
772        * access to read/write (bit 3 set) and not setting initial page access
773        * and dirty bits (bit 4 unset).
774        *
775        * There is a DJGPP function __djgpp_set_page_attributes() which sets page
776        * attributes for the memory range specified by ptr pointer, but it has
777        * same disadvantages as __djgpp_map_physical_memory() function (see
778        * comment in map functionality). So use __dpmi_set_page_attributes()
779        * instead.
780        *
781        * If changing page attributes fails then do not return memory back to the
782        * malloc pool because it is still mapped to physical memory and cannot be
783        * used by allocator for general purpose anymore.
784        *
785        * Some DPMI hosts like HDPMI pre-v3.22 (part of HX pre-v2.22) or DPMIONE
786        * do not support changing page type directly from mapped to committed.
787        * But they support changing it indirectly: first from mapped to uncommitted
788        * and then from uncommitted to committed. So if direct change from mapped
789        * to committed fails then try workaround via indirect change.
790        */
791
792       static int do_indirect_change = 0;
793
794       for (offset = 0; offset < length; offset += (mi.size << pagesize_shift))
795         {
796           /*
797            * Find a memory handle with its size which belongs to the pointer
798            * address ptr+offset. Base address and size of the memory handle
799            * must be page aligned for changing page attributes.
800            */
801           if (!find_sbrk_memory_handle(ptr + offset, length - offset, pagesize, &sh, &sh_size) ||
802               (sh->address & (pagesize-1)) || (sh_size & (pagesize-1)))
803             {
804               errno = EINVAL;
805               return -1;
806             }
807
808           mi.handle = sh->handle;
809           mi.address = (unsigned long)ptr + offset - sh->address;
810           mi.size = (length - offset) >> pagesize_shift;
811           if (mi.size > ((sh_size - mi.address) >> pagesize_shift))
812             mi.size = (sh_size - mi.address) >> pagesize_shift;
813
814           attributes = malloc(mi.size * sizeof(*attributes));
815           if (!attributes)
816             {
817               errno = ENOMEM;
818               return -1;
819             }
820
821 retry_via_indirect_change:
822           if (do_indirect_change)
823             {
824               for (i = 0; i < mi.size; i++)
825                 attributes[i] = (0<<4) | (0<<3) | 0; /* 0 = page type uncommitted */
826
827               if (set_and_get_page_attributes(&mi, attributes) != 0)
828                 return -1;
829
830               for (i = 0; i < mi.size; i++)
831                 {
832                   /* Check that every page type is uncommitted (0). */
833                   if ((attributes[i] & 0x7) != 0)
834                     {
835                       free(attributes);
836                       errno = EACCES;
837                       return -1;
838                     }
839                 }
840             }
841
842           for (i = 0; i < mi.size; i++)
843             attributes[i] = (0<<4) | (1<<3) | 1; /* 1 = page type committed */
844
845           if (set_and_get_page_attributes(&mi, attributes) != 0)
846             return -1;
847
848           for (i = 0; i < mi.size; i++)
849             {
850               /* Check that every page type is committed (1) and has read/write access (bit 3 set). */
851               if (((attributes[i] & 0x7) != 1) || !(attributes[i] & (1<<3)))
852                 {
853                   if (!do_indirect_change)
854                     {
855                       /*
856                        * Some DPMI hosts do not support changing page type
857                        * from mapped to committed but for such change request
858                        * do not report any error. Try following workaround:
859                        * Change page type indirectly. First change page type
860                        * from mapped to uncommitted and then to committed.
861                        */
862                       do_indirect_change = 1;
863                       goto retry_via_indirect_change;
864                     }
865                   free(attributes);
866                   errno = EACCES;
867                   return -1;
868                 }
869             }
870
871           free(attributes);
872         }
873
874       /*
875        * Now we are sure that ptr is backed by committed memory which can be
876        * returned back to the DJGPP sbrk pool.
877        */
878       aligned_free(ptr);
879       return 0;
880     }
881   else if (physmem == PHYSMEM_PHYSADDR_MAPPING)
882     {
883       /*
884        * Physical address mapping done by __dpmi_physical_address_mapping() can
885        * be unmapped only by __dpmi_free_physical_address_mapping() function.
886        * This function takes linear address of the mapped region. Direct access
887        * pointer refers to linear address from the __djgpp_conventional_base
888        * offset. On non-paging DPMI hosts, physical memory cannot be unmapped at
889        * all because whole physical memory is always available and so this
890        * function either fails or does nothing. Moreover this unmapping function
891        * requires DPMI 1.0 host as opposite of the mapping function which is
892        * available also in DPMI 0.9. It means that DPMI 0.9 hosts do not provide
893        * ability to unmap already mapped physical addresses. This DPMI unmapping
894        * function is not commonly supported by DPMI hosts, even the default
895        * DJGPP's CWSDPMI does not support it. But few alternative DPMI host like
896        * PMODE/DJ, WDOSX, HDPMI32 or DPMIONE support it. So expects failure from
897        * this function call, in most cases it is not possible to unmap physical
898        * memory which was previously mapped by __dpmi_physical_address_mapping().
899        */
900       mi.address = (unsigned long)ptr - __djgpp_conventional_base;
901       if (__dpmi_free_physical_address_mapping(&mi) != 0)
902         {
903           /*
904            * Do not report error when DPMI function failed with error code
905            * 0x8025 (invalid linear address) and linear address is below 1 MB.
906            * First 1 MB of memory space should stay always mapped.
907            */
908           if (__dpmi_error != 0x8025 || mi.address >= 1*1024*1024UL)
909             {
910               switch (__dpmi_error)
911                 {
912                 case 0x0801: /* Unsupported function (returned by DPMI 0.9 host, error number is same as DPMI function number) */
913                 case 0x8001: /* Unsupported function (returned by DPMI 1.0 host) */
914                   errno = ENOSYS;
915                   break;
916                 case 0x8010: /* Resource unavailable (DPMI host cannot allocate internal resources to complete an operation) */
917                   errno = ENOMEM;
918                   break;
919                 case 0x8025: /* Invalid linear address */
920                   errno = EINVAL;
921                   break;
922                 default: /* Other unspecified error */
923                   errno = EACCES;
924                   break;
925                 }
926               return -1;
927             }
928         }
929
930       return 0;
931     }
932
933   /* invalid physmem parameter */
934   errno = EBADF;
935   return -1;
936 }