From: Pali Rohár Date: Mon, 8 May 2023 19:22:59 +0000 (+0200) Subject: libpci: Add DJGPP physmem support for PCIe ECAM access X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=a9df1d1baccbafcd1f4bd7622dd20d3ea684fb75;p=pciutils.git libpci: Add DJGPP physmem support for PCIe ECAM access It requires either Device Mapping support on DPMI host or Physical Address Mapping support together with support for changing DS descriptor limit to maximal size 4 GB which enables address wrapping and so access to addresses below the process base address. --- diff --git a/lib/Makefile b/lib/Makefile index 33698db..84e25b5 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -83,7 +83,9 @@ endif ifdef PCI_USE_PHYSMEM ifndef PCI_OS_WINDOWS -ifndef PCI_OS_DJGPP +ifdef PCI_OS_DJGPP +OBJS += physmem-djgpp +else OBJS += physmem-posix endif endif diff --git a/lib/configure b/lib/configure index d02160b..a6baeea 100755 --- a/lib/configure +++ b/lib/configure @@ -167,8 +167,12 @@ case $sys in echo >>$c '#define PCI_HAVE_PM_INTEL_CONF' ;; djgpp) - echo_n " i386-ports" + echo_n " i386-ports mem-ports ecam" echo >>$c '#define PCI_HAVE_PM_INTEL_CONF' + echo >>$c '#define PCI_HAVE_PM_MMIO_CONF' + echo >>$c '#define PCI_HAVE_PM_ECAM' + echo >>$c '#define PCI_PATH_ACPI_MCFG ""' + echo >>$c '#define PCI_PATH_EFI_SYSTAB ""' EXEEXT=.exe ;; cygwin|windows) diff --git a/lib/physmem-djgpp.c b/lib/physmem-djgpp.c new file mode 100644 index 0000000..4215069 --- /dev/null +++ b/lib/physmem-djgpp.c @@ -0,0 +1,936 @@ +/* + * The PCI Library -- Physical memory mapping for DJGPP + * + * Copyright (c) 2023 Pali Rohár + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "internal.h" +#include "physmem.h" + +#include +#include +#include /* for __DJGPP__ and __DJGPP_MINOR__, available since DJGPP v2.02 and defined indirectly via sys/version.h */ +#include /* for ffs() */ +#include /* for memalign() */ + +#include +#include /* for _crt0_startup_flags, __djgpp_memory_handle_list, __djgpp_memory_handle_size and __djgpp_memory_handle() */ +#include /* for __djgpp_conventional_base, __djgpp_nearptr_enable() and __djgpp_nearptr_disable() */ + +#ifndef EOVERFLOW +#define EOVERFLOW 40 /* defined since DJGPP v2.04 */ +#endif + +/* + * For using __djgpp_conventional_base it is needed to ensure that Unix-like + * sbrk algorithm is not active (by setting _CRT0_FLAG_NONMOVE_SBRK startup flag) + * and avoiding to call functions like system, spawn*, or exec*. + */ +int _crt0_startup_flags = _CRT0_FLAG_NONMOVE_SBRK; + +static void * +aligned_alloc(size_t alignment, size_t size) +{ + /* + * Unfortunately DJGPP prior to 2.6 has broken memalign() function, + * so for older DJGPP versions use malloc() with manual aligning. + */ +#if !defined(__DJGPP__) || __DJGPP__ < 2 || (__DJGPP__ == 2 && __DJGPP_MINOR__ < 6) + void *ptr_alloc, *ptr_aligned; + + if (alignment < 8) + alignment = 8; + + ptr_alloc = malloc(size + alignment); + if (!ptr_alloc) + return NULL; + + ptr_aligned = (void *)(((unsigned long)ptr_alloc & ~(alignment-1)) + alignment); + + /* + * Store original pointer from malloc() before our aligned pointer. + * DJGPP malloc()'ed ptr_alloc is aligned to 8 bytes, our ptr_alloc is + * aligned at least to 8 bytes, so we have always 4 bytes of free space + * before memory where is pointing ptr_alloc. + */ + *((unsigned long *)ptr_aligned-1) = (unsigned long)ptr_alloc; + + return ptr_aligned; +#else + return memalign(alignment, size); +#endif +} + +static void +aligned_free(void *ptr) +{ +#if !defined(__DJGPP__) || __DJGPP__ < 2 || (__DJGPP__ == 2 && __DJGPP_MINOR__ < 6) + /* Take original pointer returned by malloc() for releasing memory. */ + ptr = (void *)*((unsigned long *)ptr-1); +#endif + free(ptr); +} + +static int +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) +{ + /* + * Find a DJGPP's sbrk memory handle which belongs to the ptr address pointer + * and detects size of this memory handle. DJGPP since v2.04 has arrays + * __djgpp_memory_handle_list[] and __djgpp_memory_handle_size[] with sbrk + * ranges which can be simple traversed. Older DJGPP versions have only + * __djgpp_memory_handle() function which returns information to which handle + * passed pointer belongs. So finding the size of the memory handle for DJGPP + * pre-v2.04 version is slower, its time complexity is O(N^2). + */ +#if !defined(__DJGPP__) || __DJGPP__ < 2 || (__DJGPP__ == 2 && __DJGPP_MINOR__ < 4) + + const __djgpp_sbrk_handle *sh2; + unsigned long end_offset; + + *sh = __djgpp_memory_handle((unsigned long)ptr); + + for (end_offset = max_length-1; end_offset != 0; end_offset = end_offset > pagesize ? end_offset - pagesize : 0) + { + sh2 = __djgpp_memory_handle((unsigned long)ptr + end_offset); + if (!*sh || !sh2) + { + /* + * If sh or sh2 is NULL then it is probably a memory corruption in + * DJGPP's __djgpp_memory_handle_list[] structure. + */ + return 0; + } + if ((*sh)->handle == sh2->handle) + break; + } + + if (end_offset == 0) + { + /* + * If end page of the sh handle was not found then it is probably a memory + * corruption in DJGPP's __djgpp_memory_handle_list[] structure. + */ + return 0; + } + + *sh_size = (unsigned long)ptr + end_offset+1 - (*sh)->address; + return 1; + +#else + + size_t i; + + 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++) + { + if ((unsigned long)ptr >= __djgpp_memory_handle_list[i].address && + (unsigned long)ptr < __djgpp_memory_handle_list[i].address + __djgpp_memory_handle_size[i]) + break; + } + + if ((i != 0 && __djgpp_memory_handle_list[i].address == 0) || __djgpp_memory_handle_size[i] == 0) + { + /* + * If address range was not found in __djgpp_memory_handle_list[] + * then it is probably memory corruption in this list. + */ + return 0; + } + + *sh = &__djgpp_memory_handle_list[i]; + *sh_size = __djgpp_memory_handle_size[i]; + return 1; + +#endif +} + +static int +set_and_get_page_attributes(__dpmi_meminfo *mi, short *attributes) +{ + unsigned long size; + int error; + size_t i; + + /* __dpmi_set_page_attributes modifies mi.size */ + size = mi->size; + if (__dpmi_set_page_attributes(mi, attributes) != 0) + { + error = __dpmi_error; + free(attributes); + switch (error) + { + case 0x0507: /* Unsupported function (returned by DPMI 0.9 host, error number is same as DPMI function number) */ + case 0x8001: /* Unsupported function (returned by DPMI 1.0 host) */ + errno = ENOSYS; + break; + case 0x8010: /* Resource unavailable (DPMI host cannot allocate internal resources to complete an operation) */ + case 0x8013: /* Physical memory unavailable */ + case 0x8014: /* Backing store unavailable */ + errno = ENOMEM; + break; + case 0x8002: /* Invalid state (page in wrong state for request) */ + case 0x8021: /* Invalid value (illegal request in bits 0-2 of one or more page attribute words) */ + case 0x8023: /* Invalid handle (in ESI) */ + case 0x8025: /* Invalid linear address (specified range is not within specified block) */ + errno = EINVAL; + break; + default: /* Other unspecified error */ + errno = EACCES; + break; + } + return -1; + } + mi->size = size; + + /* Cleanup output buffer. */ + for (i = 0; i < mi->size; i++) + attributes[i] = 0; + + if (__dpmi_get_page_attributes(mi, attributes) != 0) + { + error = __dpmi_error; + free(attributes); + switch (error) + { + case 0x0506: /* Unsupported function (returned by DPMI 0.9 host, error number is same as DPMI function number) */ + case 0x8001: /* Unsupported function (returned by DPMI 1.0 host) */ + errno = ENOSYS; + break; + case 0x8010: /* Resource unavailable (DPMI host cannot allocate internal resources to complete an operation) */ + errno = ENOMEM; + break; + case 0x8023: /* Invalid handle (in ESI) */ + case 0x8025: /* Invalid linear address (specified range is not within specified block) */ + errno = EINVAL; + break; + default: /* Other unspecified error */ + errno = EACCES; + break; + } + return -1; + } + + return 0; +} + +void +physmem_init_config(struct pci_access *a) +{ + pci_define_param(a, "devmem.path", "auto", "DJGPP physical memory access method: auto, devmap, physmap"); +} + +int +physmem_access(struct pci_access *a UNUSED, int w UNUSED) +{ + return 0; +} + +#define PHYSMEM_DEVICE_MAPPING ((struct physmem *)1) +#define PHYSMEM_PHYSADDR_MAPPING ((struct physmem *)2) + +static int fat_ds_count; + +struct physmem * +physmem_open(struct pci_access *a, int w UNUSED) +{ + const char *devmem = pci_get_param(a, "devmem.path"); + __dpmi_version_ret version; + char vendor[128]; + int capabilities; + int try_devmap; + int try_physmap; + int ret; + + if (strcmp(devmem, "auto") == 0) + { + try_devmap = 1; + try_physmap = 1; + } + else if (strcmp(devmem, "devmap") == 0) + { + try_devmap = 1; + try_physmap = 0; + } + else if (strcmp(devmem, "physmap") == 0) + { + try_devmap = 0; + try_physmap = 1; + } + else + { + try_devmap = 0; + try_physmap = 0; + } + + ret = __dpmi_get_version(&version); + if (ret != 0) + a->debug("detected unknown DPMI host..."); + else + { + /* + * Call DPMI 1.0 function __dpmi_get_capabilities() for detecting if DPMI + * host supports Device mapping. Some DPMI 0.9 hosts like Windows's NTVDM + * do not support this function, so does not fill capabilities and vendor + * buffer, but returns success. Detect this kind of failure by checking + * if AX register (low 16-bits of capabilities variable) was not modified + * and contains the number of called DPMI function (0x0401). + */ + vendor[0] = vendor[1] = vendor[2] = 0; + ret = __dpmi_get_capabilities(&capabilities, vendor); + if (ret == 0 && (capabilities & 0xffff) == 0x0401) + ret = -1; + + if (ret == 0) + a->debug("detected DPMI %u.%02u host %.126s %u.%u with flags 0x%x and capabilities 0x%x...", + (unsigned)version.major, (unsigned)version.minor, vendor+2, + (unsigned)(unsigned char)vendor[0], (unsigned)(unsigned char)vendor[1], + (unsigned)version.flags, capabilities); + else + a->debug("detected DPMI %u.%02u host with flags 0x%x...", + (unsigned)version.major, (unsigned)version.minor, (unsigned)version.flags); + } + + /* + * If device mapping was selected then use __dpmi_map_device_in_memory_block() + * for physical memory mapping. Does not have to be supported by DPMI 0.9 host. + * Device mapping is supported when capability bit 2 is set. + */ + if (try_devmap) + { + if (ret == 0 && (capabilities & (1<<2))) + { + a->debug("using physical memory access via Device Mapping..."); + return PHYSMEM_DEVICE_MAPPING; + } + a->debug("DPMI Device Mapping not supported..."); + } + + /* + * If device mapping was not tried or not supported by DPMI host then fallback + * to __dpmi_physical_address_mapping(). But this requires Fat DS descriptor, + * meaning to increase DS descriptor limit to 4 GB, which does not have to be + * supported by some DPMI hosts. + */ + if (try_physmap) + { + if (fat_ds_count != 0 || __djgpp_nearptr_enable()) + { + fat_ds_count++; + a->debug("using physical memory access via Physical Address Mapping..."); + return PHYSMEM_PHYSADDR_MAPPING; + } + + /* + * DJGPP prior to 2.6 has semi-broken __djgpp_nearptr_enable() function. + * On failure it may let DS descriptor limit in semi-broken state. So for + * older DJGPP versions call __djgpp_nearptr_disable() which fixes it. + */ +#if !defined(__DJGPP__) || __DJGPP__ < 2 || (__DJGPP__ == 2 && __DJGPP_MINOR__ < 6) + __djgpp_nearptr_disable(); +#endif + a->debug("DPMI Physical Address Mapping not usable because Fat DS descriptor not supported..."); + } + + /* + * Otherwise we do not have access to physical memory mapping. Theoretically + * it could be possible to use __dpmi_physical_address_mapping() and then + * create new segment where mapped linear address would be available, but this + * would require to access memory in newly created segment via far pointers, + * which is not only mess in the native 32-bit application but also these far + * pointers are not supported by gcc. If DPMI host does not allow us to change + * DS descriptor limit to 4 GB then it is mostly due to security reasons and + * probably does not allow access to physical memory mapping. This applies + * for non-DOS OS systems with integrated DPMI hosts like in Windows NT NTVDM + * or older version of Linux dosemu. + */ + a->debug("physical memory access not allowed..."); + errno = EACCES; + return NULL; +} + +void +physmem_close(struct physmem *physmem) +{ + /* Disable 4 GB limit on DS descriptor if it was the last user. */ + if (physmem == PHYSMEM_PHYSADDR_MAPPING) + { + fat_ds_count--; + if (fat_ds_count == 0) + __djgpp_nearptr_disable(); + } +} + +long +physmem_get_pagesize(struct physmem *physmem UNUSED) +{ + static unsigned long pagesize; + if (!pagesize) + { + if (__dpmi_get_page_size(&pagesize) != 0) + pagesize = 0; + if (pagesize & (pagesize-1)) + pagesize = 0; + if (!pagesize) + pagesize = 4096; /* Fallback value, the most commonly used on x86. */ + } + return pagesize; +} + +void * +physmem_map(struct physmem *physmem, u64 addr, size_t length, int w) +{ + long pagesize = physmem_get_pagesize(physmem); + unsigned pagesize_shift = ffs(pagesize)-1; + const __djgpp_sbrk_handle *sh; + unsigned long sh_size; + unsigned long size; + __dpmi_meminfo mi; + short *attributes; + short one_pg_attr; + size_t offset; + int error; + void *ptr; + size_t i; + + /* Align length to page size. */ + if (length & (pagesize-1)) + length = (length & ~(pagesize-1)) + pagesize; + + /* Mapping of physical memory above 4 GB is not possible. */ + if (addr >= 0xffffffffUL || addr + length > 0xffffffffUL) + { + errno = EOVERFLOW; + return (void *)-1; + } + + if (physmem == PHYSMEM_DEVICE_MAPPING) + { + /* + * __dpmi_map_device_in_memory_block() maps physical memory to any + * page-aligned linear address for which we have DPMI memory handle. But + * DPMI host does not have to support mapping of memory below 1 MB which + * lies in RAM, and is not device memory. + * + * __djgpp_map_physical_memory() function is DJGPP wrapper around + * __dpmi_map_device_in_memory_block() which properly handles memory + * range that span multiple DPMI memory handles. It is common that + * DJGPP sbrk() or malloc() allocator returns continuous memory range + * which is backed by two or more DPMI memory handles which represents + * consecutive memory ranges without any gap. + * + * __dpmi_map_conventional_memory_in_memory_block() aliases memory range + * specified by page-aligned linear address to another page-aligned linear + * address. This can be used for mapping memory below 1 MB which lies in + * RAM and for which cannot be used __dpmi_map_device_in_memory_block(). + * This function calls takes (virtual) linear address as opposite of the + * __dpmi_map_device_in_memory_block() which takes physical address. + * + * Unfortunately __djgpp_map_physical_memory() internally calls only + * __djgpp_map_physical_memory() function and does not return information + * for which memory range the call failed. So it cannot be used for + * generic memory mapping requests. + * + * Also it does not return usefull errno. And even in the latest released + * DJGPP version v2.5 this function has suboptimal implementation. Its + * time complexity is O(N^2) (where N is number of pages). + * + * So do not use __djgpp_map_physical_memory() function and instead write + * own logic handling virtual memory ranges which spans multiple DPMI + * memory handles and manually calls __dpmi_map_device_in_memory_block() + * or __dpmi_map_conventional_memory_in_memory_block() per every handle. + * + * We can easily access only linear addresses in our DS segment which + * is managed by DJGPP sbrk allocator. So allocate page-aligned range + * by aligned_alloc() (our wrapper around malloc()/memalign()) and then + * for every subrange which is backed by different DPMI memory handle + * call appropriate mapping function which correctly calculated offset + * and length to have continuous representation of physical memory range. + * + * This approach has disadvantage that for each mapping it is required + * to reserve and allocate committed memory in RAM with the size of the + * mapping itself. This has negative impact for mappings of large sizes. + * Unfortunately this is the only way because DJGPP sbrk allocator does + * not have any (public) function for directly allocating uncommitted + * memory which is not backed by the RAM. Even if DJGPP sbrk code is + * extended for this functionality, the corresponding DPMI function + * __dpmi_allocate_linear_memory() is DPMI 1.0 function and not widely + * supported by DPMI hosts, even the default DJGPP's CWSDPMI does not + * support it. + */ + + ptr = aligned_alloc(pagesize, length); + if (!ptr) + { + errno = ENOMEM; + return (void *)-1; + } + + for (offset = 0; offset < length; offset += (mi.size << pagesize_shift)) + { + /* + * Find a memory handle with its size which belongs to the pointer + * address ptr+offset. Base address and size of the memory handle + * must be page aligned for memory mapping support. + */ + if (!find_sbrk_memory_handle(ptr + offset, length - offset, pagesize, &sh, &sh_size) || + (sh->address & (pagesize-1)) || (sh_size & (pagesize-1))) + { + /* + * Failure detected. If we have some partial mapping, try to undo + * it via physmem_unmap() which also release ptr. If we do not + * have partial mapping, just release ptr. + */ + if (offset != 0) + physmem_unmap(physmem, ptr, offset); + else + aligned_free(ptr); + errno = EINVAL; + return (void *)-1; + } + + mi.handle = sh->handle; + mi.address = (unsigned long)ptr + offset - sh->address; + mi.size = (length - offset) >> pagesize_shift; + if (mi.size > ((sh_size - mi.address) >> pagesize_shift)) + mi.size = (sh_size - mi.address) >> pagesize_shift; + if (__dpmi_map_device_in_memory_block(&mi, addr + offset) != 0) + { + /* + * __dpmi_map_device_in_memory_block() may fail for memory range + * which belongs to non-device memory below 1 MB. DPMI host in + * this case returns DPMI error code 0x8003 (System integrity - + * invalid device address). For example this is behavior of DPMI + * host HX HDPMI32, which strictly differs between non-device and + * device memory. If the physical memory range belongs to the + * non-device conventional memory and DPMI host uses 1:1 mappings + * for memory below 1 MB then we can try to alias range of linear + * address below 1 MB to DJGPP's accessible linear address range. + * For this aliasing of linear (not the physical) memory address + * ranges below 1 MB boundary is there an additional DPMI 1.0 + * function __dpmi_map_conventional_memory_in_memory_block(). + * But DPMI host does not have to support it. HDPMI32 supports it. + * If the memory range crosses 1 MB boundary then call it only for + * the subrange of memory which below 1 MB boundary and let the + * remaining subrange for the next iteration of the outer loop. + * Because the remaining memory range is above 1 MB limit, only + * the __dpmi_map_device_in_memory_block() would be used. This + * approach makes continues linear range of the mapped memory. + */ + if (__dpmi_error == 0x8003 && addr + offset < 1*1024*1024UL) + { + /* reuse mi */ + if (addr + offset + (mi.size << pagesize_shift) > 1*1024*1024UL) + mi.size = (1*1024*1024UL - addr - offset) >> pagesize_shift; + if (__dpmi_map_conventional_memory_in_memory_block(&mi, addr + offset) != 0) + { + /* + * Save __dpmi_error because any DJGPP function may change + * it. If we have some partial mapping, try to undo it via + * physmem_unmap() which also release ptr. If we do not + * have partial mapping, just release ptr. + */ + error = __dpmi_error; + if (offset != 0) + physmem_unmap(physmem, ptr, offset); + else + aligned_free(ptr); + switch (error) + { + case 0x0509: /* Unsupported function (returned by DPMI 0.9 host, error number is same as DPMI function number) */ + case 0x8001: /* Unsupported function (returned by DPMI 1.0 host) */ + /* + * Conventional Memory Mapping is not supported. + * Device Mapping is supported, but DPMI host rejected + * Device Mapping request. So reports same errno value + * as from the failed Device Mapping switch case, + * which is ENXIO (because __dpmi_error == 0x8003). + */ + errno = ENXIO; + break; + case 0x8003: /* System integrity (invalid conventional memory address) */ + errno = ENXIO; + break; + case 0x8010: /* Resource unavailable (DPMI host cannot allocate internal resources to complete an operation) */ + errno = ENOMEM; + break; + case 0x8023: /* Invalid handle (in ESI) */ + case 0x8025: /* Invalid linear address (specified range is not within specified block, or EBX/EDX is not page aligned) */ + errno = EINVAL; + break; + default: /* Other unspecified error */ + errno = EACCES; + break; + } + return (void *)-1; + } + } + else + { + /* + * Save __dpmi_error because any DJGPP function may change + * it. If we have some partial mapping, try to undo it via + * physmem_unmap() which also release ptr. If we do not + * have partial mapping, just release ptr. + */ + error = __dpmi_error; + if (offset != 0) + physmem_unmap(physmem, ptr, offset); + else + aligned_free(ptr); + switch (error) + { + case 0x0508: /* Unsupported function (returned by DPMI 0.9 host, error number is same as DPMI function number) */ + case 0x8001: /* Unsupported function (returned by DPMI 1.0 host) */ + errno = ENOSYS; + break; + case 0x8003: /* System integrity (invalid device address) */ + errno = ENXIO; + break; + case 0x8010: /* Resource unavailable (DPMI host cannot allocate internal resources to complete an operation) */ + errno = ENOMEM; + break; + case 0x8023: /* Invalid handle (in ESI) */ + case 0x8025: /* Invalid linear address (specified range is not within specified block or EBX/EDX is not page-aligned) */ + errno = EINVAL; + break; + default: /* Other unspecified error */ + errno = EACCES; + break; + } + return (void *)-1; + } + } + + /* + * For read-only mapping try to change page attributes with not changing + * page type (3) and setting read-only access (bit 3 unset). Ignore any + * failure as this function requires DPMI 1.0 host and so it does not have + * to be supported by other DPMI 0.9 hosts. Note that by default newly + * created mapping has read/write access and so we can use it also for + * mappings which were requested as read-only too. + */ + if (!w) + { + attributes = malloc(mi.size * sizeof(*attributes)); + if (attributes) + { + /* reuse mi */ + for (i = 0; i < mi.size; i++) + attributes[i] = (0<<3) | 3; + + /* __dpmi_set_page_attributes modifies mi.size */ + size = mi.size; + __dpmi_set_page_attributes(&mi, attributes); + mi.size = size; + + free(attributes); + } + } + } + + return ptr; + } + else if (physmem == PHYSMEM_PHYSADDR_MAPPING) + { + /* + * __dpmi_physical_address_mapping() is DPMI 0.9 function and so does not + * require device mapping support. But DPMI hosts often allow to used it + * only for memory above 1 MB and also we do not have control where DPMI + * host maps physical memory. Because this is DPMI 0.9 function, error + * code on failure does not have to be provided. If DPMI host does not + * provide error code then in __dpmi_error variable is stored the called + * DPMI function number (0x0800 is for Physical Address Mapping). + * Error codes are provided only by DPMI 1.0 hosts. + */ + + mi.address = addr; + mi.size = length; + if (__dpmi_physical_address_mapping(&mi) != 0) + { + /* + * __dpmi_physical_address_mapping() may fail for memory range which + * starts below 1 MB. DPMI 1.0 host in this case returns DPMI error + * code 0x8021 (Invalid value - address is below 1 MB boundary). + * DPMI 0.9 host does not provide error code, so __dpmi_error contains + * value 0x0800. For example this is behavior of the default DJGPP's + * DPMI host CWSDPMI and also of Windows 3.x DPMI host. On the other + * hand DPMI host HX HDPMI32 or Windows 9x DPMI host allow requests + * for memory ranges below 1 MB and do not fail. + */ + if ((__dpmi_error == 0x0800 || __dpmi_error == 0x8021) && addr < 1*1024*1024UL) + { + /* + * Expects that conventional memory below 1 MB is always 1:1 + * mapped. On non-paging DPMI hosts it is always truth and paging + * DPMI hosts should do it too or at least provide mapping with + * compatible or emulated content for compatibility with existing + * DOS applications. So check that requested range is below 1 MB. + */ + if (addr + length > 1*1024*1024UL) + { + errno = ENXIO; + return (void *)-1; + } + + /* + * Simulate successful __dpmi_physical_address_mapping() call by + * setting the 1:1 mapped address. + */ + mi.address = addr; + } + else + { + switch (__dpmi_error) + { + case 0x0800: /* Error code was not provided (returned by DPMI 0.9 host, error number is same as DPMI function number) */ + errno = EACCES; + break; + case 0x8003: /* System integrity (DPMI host memory region) */ + case 0x8021: /* Invalid value (address is below 1 MB boundary) */ + errno = ENXIO; + break; + case 0x8010: /* Resource unavailable (DPMI host cannot allocate internal resources to complete an operation) */ + errno = ENOMEM; + break; + default: /* Other unspecified error */ + errno = EACCES; + break; + } + return (void *)-1; + } + } + + /* + * Function returns linear address of the mapping. On non-paging DPMI + * hosts it does nothing and just returns same passed physical address. + * With DS descriptor limit set to 4 GB (set by __djgpp_nearptr_enable()) + * we have direct access to any linear address. Direct access to specified + * linear address is from the __djgpp_conventional_base offset. Note that + * this is always read/write access, and there is no way to make access + * just read-only. + */ + ptr = (void *)(mi.address + __djgpp_conventional_base); + + /* + * DJGPP CRT code on paging DPMI hosts enables NULL pointer protection by + * disabling access to the zero page. If we are running on DPMI host which + * does 1:1 mapping and we were asked for physical address range mapping + * which includes also our zero page, then we have to disable NULL pointer + * protection to allow access to that mapped page. Detect this by checking + * that our zero page [0, pagesize-1] does not conflict with the returned + * address range [ptr, ptr+length] (note that length is already multiply + * of pagesize) and change page attributes to committed page type (1) and + * set read/write access (bit 3 set). Ignore any failure as this function + * requires DPMI 1.0 host and so it does not have to be supported by other + * DPMI 0.9 hosts. In this case DJGPP CRT code did not enable NULL pointer + * protection and so zero page can be normally accessed. + */ + if ((unsigned long)ptr - 1 > (unsigned long)ptr - 1 + length) + { + mi.handle = __djgpp_memory_handle_list[0].handle; + mi.address = 0; + mi.size = 1; /* number of pages */ + one_pg_attr = (1<<3) | 1; + /* __dpmi_set_page_attributes modifies mi.size */ + __dpmi_set_page_attributes(&mi, &one_pg_attr); + } + + return ptr; + } + + /* invalid physmem parameter */ + errno = EBADF; + return (void *)-1; +} + +int +physmem_unmap(struct physmem *physmem, void *ptr, size_t length) +{ + long pagesize = physmem_get_pagesize(physmem); + unsigned pagesize_shift = ffs(pagesize)-1; + const __djgpp_sbrk_handle *sh; + unsigned long sh_size; + __dpmi_meminfo mi; + short *attributes; + size_t offset; + size_t i; + + /* Align length to page size. */ + if (length & (pagesize-1)) + length = (length & ~(pagesize-1)) + pagesize; + + if (physmem == PHYSMEM_DEVICE_MAPPING) + { + /* + * Memory mapped by __dpmi_map_conventional_memory_in_memory_block() or by + * __dpmi_map_device_in_memory_block() can be unmapped by changing page + * attributes back to the what allocator use: page type to committed (1), + * access to read/write (bit 3 set) and not setting initial page access + * and dirty bits (bit 4 unset). + * + * There is a DJGPP function __djgpp_set_page_attributes() which sets page + * attributes for the memory range specified by ptr pointer, but it has + * same disadvantages as __djgpp_map_physical_memory() function (see + * comment in map functionality). So use __dpmi_set_page_attributes() + * instead. + * + * If changing page attributes fails then do not return memory back to the + * malloc pool because it is still mapped to physical memory and cannot be + * used by allocator for general purpose anymore. + * + * Some DPMI hosts like HDPMI pre-v3.22 (part of HX pre-v2.22) or DPMIONE + * do not support changing page type directly from mapped to committed. + * But they support changing it indirectly: first from mapped to uncommitted + * and then from uncommitted to committed. So if direct change from mapped + * to committed fails then try workaround via indirect change. + */ + + static int do_indirect_change = 0; + + for (offset = 0; offset < length; offset += (mi.size << pagesize_shift)) + { + /* + * Find a memory handle with its size which belongs to the pointer + * address ptr+offset. Base address and size of the memory handle + * must be page aligned for changing page attributes. + */ + if (!find_sbrk_memory_handle(ptr + offset, length - offset, pagesize, &sh, &sh_size) || + (sh->address & (pagesize-1)) || (sh_size & (pagesize-1))) + { + errno = EINVAL; + return -1; + } + + mi.handle = sh->handle; + mi.address = (unsigned long)ptr + offset - sh->address; + mi.size = (length - offset) >> pagesize_shift; + if (mi.size > ((sh_size - mi.address) >> pagesize_shift)) + mi.size = (sh_size - mi.address) >> pagesize_shift; + + attributes = malloc(mi.size * sizeof(*attributes)); + if (!attributes) + { + errno = ENOMEM; + return -1; + } + +retry_via_indirect_change: + if (do_indirect_change) + { + for (i = 0; i < mi.size; i++) + attributes[i] = (0<<4) | (0<<3) | 0; /* 0 = page type uncommitted */ + + if (set_and_get_page_attributes(&mi, attributes) != 0) + return -1; + + for (i = 0; i < mi.size; i++) + { + /* Check that every page type is uncommitted (0). */ + if ((attributes[i] & 0x7) != 0) + { + free(attributes); + errno = EACCES; + return -1; + } + } + } + + for (i = 0; i < mi.size; i++) + attributes[i] = (0<<4) | (1<<3) | 1; /* 1 = page type committed */ + + if (set_and_get_page_attributes(&mi, attributes) != 0) + return -1; + + for (i = 0; i < mi.size; i++) + { + /* Check that every page type is committed (1) and has read/write access (bit 3 set). */ + if (((attributes[i] & 0x7) != 1) || !(attributes[i] & (1<<3))) + { + if (!do_indirect_change) + { + /* + * Some DPMI hosts do not support changing page type + * from mapped to committed but for such change request + * do not report any error. Try following workaround: + * Change page type indirectly. First change page type + * from mapped to uncommitted and then to committed. + */ + do_indirect_change = 1; + goto retry_via_indirect_change; + } + free(attributes); + errno = EACCES; + return -1; + } + } + + free(attributes); + } + + /* + * Now we are sure that ptr is backed by committed memory which can be + * returned back to the DJGPP sbrk pool. + */ + aligned_free(ptr); + return 0; + } + else if (physmem == PHYSMEM_PHYSADDR_MAPPING) + { + /* + * Physical address mapping done by __dpmi_physical_address_mapping() can + * be unmapped only by __dpmi_free_physical_address_mapping() function. + * This function takes linear address of the mapped region. Direct access + * pointer refers to linear address from the __djgpp_conventional_base + * offset. On non-paging DPMI hosts, physical memory cannot be unmapped at + * all because whole physical memory is always available and so this + * function either fails or does nothing. Moreover this unmapping function + * requires DPMI 1.0 host as opposite of the mapping function which is + * available also in DPMI 0.9. It means that DPMI 0.9 hosts do not provide + * ability to unmap already mapped physical addresses. This DPMI unmapping + * function is not commonly supported by DPMI hosts, even the default + * DJGPP's CWSDPMI does not support it. But few alternative DPMI host like + * PMODE/DJ, WDOSX, HDPMI32 or DPMIONE support it. So expects failure from + * this function call, in most cases it is not possible to unmap physical + * memory which was previously mapped by __dpmi_physical_address_mapping(). + */ + mi.address = (unsigned long)ptr - __djgpp_conventional_base; + if (__dpmi_free_physical_address_mapping(&mi) != 0) + { + /* + * Do not report error when DPMI function failed with error code + * 0x8025 (invalid linear address) and linear address is below 1 MB. + * First 1 MB of memory space should stay always mapped. + */ + if (__dpmi_error != 0x8025 || mi.address >= 1*1024*1024UL) + { + switch (__dpmi_error) + { + case 0x0801: /* Unsupported function (returned by DPMI 0.9 host, error number is same as DPMI function number) */ + case 0x8001: /* Unsupported function (returned by DPMI 1.0 host) */ + errno = ENOSYS; + break; + case 0x8010: /* Resource unavailable (DPMI host cannot allocate internal resources to complete an operation) */ + errno = ENOMEM; + break; + case 0x8025: /* Invalid linear address */ + errno = EINVAL; + break; + default: /* Other unspecified error */ + errno = EACCES; + break; + } + return -1; + } + } + + return 0; + } + + /* invalid physmem parameter */ + errno = EBADF; + return -1; +}