pm.c

来自「适合KS8695X」· C语言 代码 · 共 2,244 行 · 第 1/5 页

C
2,244
字号
/* Generic DPMI routines common to 16/32 bit code                          */
/*-------------------------------------------------------------------------*/

#ifndef REALMODE
ulong PMAPI DPMI_mapPhysicalToLinear(ulong physAddr,ulong limit)
{
    PMREGS  r;
    int     i;
    ulong   baseAddr,baseOfs,roundedLimit;

    /* We can't map memory below 1Mb, but the linear address are already
     * mapped 1:1 for this memory anyway so we just return the base address.
     */
    if (physAddr < 0x100000L)
	return physAddr;

    /* Search table of existing mappings to see if we have already mapped
     * a region of memory that will serve this purpose. We do this because
     * DPMI 0.9 does not allow us to free physical memory mappings, and if
     * the mappings get re-used in the program we want to avoid allocating
     * more mappings than necessary.
     */
    for (i = 0; i < numMaps; i++) {
	if (maps[i].physical == physAddr && maps[i].limit == limit)
	    return maps[i].linear;
	}

    /* Find a free slot in our physical memory mapping table */
    for (i = 0; i < numMaps; i++) {
	if (maps[i].limit == 0)
	    break;
	}
    if (i == numMaps) {
	i = numMaps++;
	if (i == MAX_MEMORY_MAPPINGS)
	    return NULL;
	}

    /* Round the physical address to a 4Kb boundary and the limit to a
     * 4Kb-1 boundary before passing the values to DPMI as some extenders
     * will fail the calls unless this is the case. If we round the
     * physical address, then we also add an extra offset into the address
     * that we return.
     */
    baseOfs = physAddr & 4095;
    baseAddr = physAddr & ~4095;
    roundedLimit = ((limit+baseOfs+1+4095) & ~4095)-1;
    r.x.ax = 0x800;
    r.x.bx = baseAddr >> 16;
    r.x.cx = baseAddr & 0xFFFF;
    r.x.si = roundedLimit >> 16;
    r.x.di = roundedLimit & 0xFFFF;
    PM_int386(0x31, &r, &r);
    if (r.x.cflag)
	return 0xFFFFFFFFUL;
    maps[i].physical = physAddr;
    maps[i].limit = limit;
    maps[i].linear = ((ulong)r.x.bx << 16) + r.x.cx + baseOfs;
    return maps[i].linear;
}

int PMAPI DPMI_setSelectorBase(ushort sel,ulong linAddr)
{
    PMREGS  r;

    r.x.ax = 7;                     /* DPMI set selector base address   */
    r.x.bx = sel;
    r.x.cx = linAddr >> 16;
    r.x.dx = linAddr & 0xFFFF;
    PM_int386(0x31, &r, &r);
    if (r.x.cflag)
	return 0;
    return 1;
}

ulong PMAPI DPMI_getSelectorBase(ushort sel)
{
    PMREGS  r;

    r.x.ax = 6;                     /* DPMI get selector base address   */
    r.x.bx = sel;
    PM_int386(0x31, &r, &r);
    return ((ulong)r.x.cx << 16) + r.x.dx;
}

int PMAPI DPMI_setSelectorLimit(ushort sel,ulong limit)
{
    PMREGS  r;

    r.x.ax = 8;                     /* DPMI set selector limit          */
    r.x.bx = sel;
    r.x.cx = limit >> 16;
    r.x.dx = limit & 0xFFFF;
    PM_int386(0x31, &r, &r);
    if (r.x.cflag)
	return 0;
    return 1;
}

uint PMAPI DPMI_createSelector(ulong base,ulong limit)
{
    uint    sel;
    PMREGS  r;

    /* Allocate 1 descriptor */
    r.x.ax = 0;
    r.x.cx = 1;
    PM_int386(0x31, &r, &r);
    if (r.x.cflag) return 0;
    sel = r.x.ax;

    /* Set the descriptor access rights (for a 32 bit page granular
     * segment).
     */
    if (limit >= 0x10000L) {
	r.x.ax = 9;
	r.x.bx = sel;
	r.x.cx = 0x40F3;
	PM_int386(0x31, &r, &r);
	}

    /* Map physical memory and create selector */
    if ((base = DPMI_mapPhysicalToLinear(base,limit)) == 0xFFFFFFFFUL)
	return 0;
    if (!DPMI_setSelectorBase(sel,base))
	return 0;
    if (!DPMI_setSelectorLimit(sel,limit))
	return 0;
    return sel;
}

void PMAPI DPMI_freeSelector(uint sel)
{
    PMREGS  r;

    r.x.ax = 1;
    r.x.bx = sel;
    PM_int386(0x31, &r, &r);
}

int PMAPI DPMI_lockLinearPages(ulong linear,ulong len)
{
    PMREGS  r;

    r.x.ax = 0x600;                     /* DPMI Lock Linear Region      */
    r.x.bx = (linear >> 16);            /* Linear address in BX:CX      */
    r.x.cx = (linear & 0xFFFF);
    r.x.si = (len >> 16);               /* Length in SI:DI              */
    r.x.di = (len & 0xFFFF);
    PM_int386(0x31, &r, &r);
    return (!r.x.cflag);
}

int PMAPI DPMI_unlockLinearPages(ulong linear,ulong len)
{
    PMREGS  r;

    r.x.ax = 0x601;                     /* DPMI Unlock Linear Region    */
    r.x.bx = (linear >> 16);            /* Linear address in BX:CX      */
    r.x.cx = (linear & 0xFFFF);
    r.x.si = (len >> 16);               /* Length in SI:DI              */
    r.x.di = (len & 0xFFFF);
    PM_int386(0x31, &r, &r);
    return (!r.x.cflag);
}

/****************************************************************************
REMARKS:
Adjust the page table caching bits directly. Requires ring 0 access and
only works with DOS4GW and compatible extenders (CauseWay also works since
it has direct support for the ring 0 instructions we need from ring 3). Will
not work in a DOS box, but we call into the ring 0 helper VxD so we should
never get here in a DOS box anyway (assuming the VxD is present). If we
do get here and we are in windows, this code will be skipped.
****************************************************************************/
static void PM_adjustPageTables(
    ulong linear,
    ulong limit,
    ibool isCached)
{
#ifdef  DOS4GW
    int     startPDB,endPDB,iPDB,startPage,endPage,start,end,iPage;
    ulong   andMask,orMask,pageTable,*pPageTable;

    andMask = ~0x18;
    orMask = (isCached) ? 0x00 : 0x18;
    if (_PM_pagingEnabled() == 1 && (PDB = _PM_getPDB()) != 0) {
	if (_PM_haveCauseWay) {
	    /* CauseWay is a little different in the page table handling.
	     * The code that we use for DOS4G/W does not appear to work
	     * with CauseWay correctly as it does not appear to allow us
	     * to map the page tables directly. Instead we can directly
	     * access the page table entries in extended memory where
	     * CauseWay always locates them (starting at 1024*4096*1023)
	     */
	    startPage = (linear >> 12);
	    endPage = ((linear+limit) >> 12);
	    pPageTable = (ulong*)CW_PAGE_TABLE_START;
	    for (iPage = startPage; iPage <= endPage; iPage++)
		pPageTable[iPage] = (pPageTable[iPage] & andMask) | orMask;
	    }
	else {
	    pPDB = (ulong*)DPMI_mapPhysicalToLinear(PDB,0xFFF);
	    if (pPDB) {
		startPDB = (linear >> 22) & 0x3FF;
		startPage = (linear >> 12) & 0x3FF;
		endPDB = ((linear+limit) >> 22) & 0x3FF;
		endPage = ((linear+limit) >> 12) & 0x3FF;
		for (iPDB = startPDB; iPDB <= endPDB; iPDB++) {
		    pageTable = pPDB[iPDB] & ~0xFFF;
		    pPageTable = (ulong*)DPMI_mapPhysicalToLinear(pageTable,0xFFF);
		    start = (iPDB == startPDB) ? startPage : 0;
		    end = (iPDB == endPDB) ? endPage : 0x3FF;
		    for (iPage = start; iPage <= end; iPage++)
			pPageTable[iPage] = (pPageTable[iPage] & andMask) | orMask;
		    }
		}
	    }
	PM_flushTLB();
	}
#endif
}

void * PMAPI DPMI_mapPhysicalAddr(ulong base,ulong limit,ibool isCached)
{
    PMSREGS     sregs;
    ulong       linAddr;
    ulong       DSBaseAddr;

    /* Get the base address for the default DS selector */
    PM_segread(&sregs);
    DSBaseAddr = DPMI_getSelectorBase(sregs.ds);
    if ((base < 0x100000) && (DSBaseAddr == 0)) {
	/* DS is zero based, so we can directly access the first 1Mb of
	 * system memory (like under DOS4GW).
	 */
	return (void*)base;
	}

    /* Map the memory to a linear address using DPMI function 0x800 */
    if ((linAddr = DPMI_mapPhysicalToLinear(base,limit)) == 0xFFFFFFFF) {
	if (base >= 0x100000)
	    return NULL;
	/* If the linear address mapping fails but we are trying to
	 * map an area in the first 1Mb of system memory, then we must
	 * be running under a Windows or OS/2 DOS box. Under these
	 * environments we can use the segment wrap around as a fallback
	 * measure, as this does work properly.
	 */
	linAddr = base;
	}

    /* Now expand the default DS selector to 4Gb so we can access it */
    if (!DPMI_setSelectorLimit(sregs.ds,0xFFFFFFFFUL))
	return NULL;

    /* Finally enable caching for the page tables that we just mapped in,
     * since DOS4GW and PMODE/W create the page table entries without
     * caching enabled which hurts the performance of the linear framebuffer
     * as it disables write combining on Pentium Pro and above processors.
     *
     * For those processors cache disabling is better handled through the
     * MTRR registers anyway (we can write combine a region but disable
     * caching) so that MMIO register regions do not screw up.
     */
    if (DSBaseAddr == 0)
	PM_adjustPageTables(linAddr,limit,isCached);

    /* Now return the base address of the memory into the default DS */
    return (void*)(linAddr - DSBaseAddr);
}

#if defined(PM386)

/* Some DOS extender implementations do not directly support calling a
 * real mode procedure from protected mode. However we can simulate what
 * we need temporarily hooking the INT 6Ah vector with a small real mode
 * stub that will call our real mode code for us.
 */

static uchar int6AHandler[] = {
    0x00,0x00,0x00,0x00,        /*  __PMODE_callReal variable           */
    0xFB,                       /*  sti                                 */
    0x2E,0xFF,0x1E,0x00,0x00,   /*  call    [cs:__PMODE_callReal]       */
    0xCF,                       /*  iretf                               */
    };
static uchar *crPtr = NULL; /* Pointer to of int 6A handler         */
static uint crRSeg,crROff;  /* Real mode seg:offset of handler      */

void PMAPI PM_callRealMode(uint seg,uint off, RMREGS *in,
    RMSREGS *sregs)
{
    uchar   *p;
    uint    oldSeg,oldOff;

    if (!crPtr) {
	/* Allocate and copy the memory block only once */
	crPtr = PM_allocRealSeg(sizeof(int6AHandler), &crRSeg, &crROff);
	memcpy(crPtr,int6AHandler,sizeof(int6AHandler));
	}
    PM_setWord(crPtr,off);              /* Plug in address to call  */
    PM_setWord(crPtr+2,seg);
    p = PM_mapRealPointer(0,0x6A * 4);
    oldOff = PM_getWord(p);             /* Save old handler address */
    oldSeg = PM_getWord(p+2);
    PM_setWord(p,crROff+4);             /* Hook 6A handler          */
    PM_setWord(p+2,crRSeg);
    PM_int86x(0x6A, in, in, sregs);     /* Call real mode code      */
    PM_setWord(p,oldOff);               /* Restore old handler      */
    PM_setWord(p+2,oldSeg);
}

#endif  /* PM386 */

#endif  /* !REALMODE */

/****************************************************************************
REMARKS:
Allocates a block of locked, physically contiguous memory. The memory
may be required to be below the 16Meg boundary.
****************************************************************************/
void * PMAPI PM_allocLockedMem(
    uint size,
    ulong *physAddr,
    ibool contiguous,
    ibool below16Meg)
{
    uchar           *p,*roundedP;
    uint            r_seg,r_off;
    uint            roundedSize = (size + 4 + 0xFFF) & ~0xFFF;
    PM_lockHandle   lh; /* Unused in DOS */
#ifndef REALMODE
    VXD_regs        regs;

    /* If we have connected to our helper VxD in a Windows DOS box, use the
     * helper VxD services to allocate the memory that we need.
     */
    if (VXD_version) {
	memset(&regs,0,sizeof(regs));
	regs.eax = API_NUM(PMHELP_ALLOCLOCKED);
	regs.ebx = size;
	regs.ecx = (ulong)physAddr;
	regs.edx = contiguous | (below16Meg << 8);
	_PM_VxDCall(&regs,_PM_VXD_off,_PM_VXD_sel);
	return (void*)regs.eax;
	}

    /* If the memory is not contiguous, we simply need to allocate it
     * using regular memory allocation services, and lock it down
     * in memory.
     *
     * For contiguous memory blocks, the only way to guarantee contiguous physical
     * memory addresses under DOS is to allocate the memory below the
     * 1Meg boundary as real mode memory.
     *
     * Note that we must page align the memory block, and we also must
     * keep track of the non-aligned pointer so we can properly free
     * it later. Hence we actually allocate 4 bytes more than the
     * size rounded up to the next 4K boundary.
     */
    if (!contiguous)
	p = PM_malloc(roundedSize);
    else
#endif
	p = PM_allocRealSeg(roundedSize,&r_seg,&r_off);
    if (p == NULL)
	return NULL;
    roundedP = (void*)(((ulong)p + 0xFFF) & ~0xFFF);
    *((ulong*)(roundedP + size)) = (ulong)p;
    PM_lockDataPages(roundedP,size,&lh);
    if ((*physAddr = PM_getPhysicalAddr(roundedP)) == 0xFFFFFFFF) {
	PM_freeLockedMem(roundedP,size,contiguous);
	return NULL;
	}

    /* Disable caching for the memory since it is probably a DMA buffer */
#ifndef REALMODE
    PM_adjustPageTables((ulong)roundedP,size-1,false);
#endif
    return roundedP;
}

/****************************************************************************
REMARKS:
Free a block of locked memory.
****************************************************************************/
void PMAPI PM_freeLockedMem(void *p,uint size,ibool contiguous)
{
#ifndef REALMODE
    VXD_regs        regs;
    PM_lockHandle   lh; /* Unused in DOS */

    if (!p)
	return;
    if (VXD_version) {
	memset(&regs,0,sizeof(regs));
	regs.eax = API_NUM(PMHELP_FREELOCKED);
	regs.ebx = (ulong)p;
	regs.ecx = size;
	regs.edx = contiguous;
	_PM_VxDCall(&regs,_PM_VXD_off,_PM_VXD_sel);
	return;
	}
    PM_unlockDataPages(p,size,&lh);
    if (!contiguous)
	free(*((void**)((uchar*)p + size)));
    else
#endif
	PM_freeRealSeg(*((void**)((char*)p + size)));
}

#ifndef REALMODE
/****************************************************************************
REMARKS:
Allocates a new block of pages for the page block manager.
****************************************************************************/
static pageblock *PM_addNewPageBlock(void)
{
    int         i,size;
    pageblock   *newBlock;
    char        *p,*next;

    /* Allocate memory for the new page block, and add to head of list */
    size = PAGES_PER_BLOCK * PM_PAGE_SIZE + (PM_PAGE_SIZE-1) + sizeof(pageblock);
    if ((newBlock = PM_malloc(size)) == NULL)
	return NULL;
    newBlock->prev = NULL;
    newBlock->next = pageBlocks;
    if (pageBlocks)
	pageBlocks->prev = newBlock;
    pageBlocks = newBlock;

    /* Initialise the page aligned free list for the page block */
    newBlock->freeCount = PAGES_PER_BLOCK;
    newBlock->freeList = p = (char*)(((ulong)(newBlock + 1) + (PM_PAGE_SIZE-1)) & ~(PM_PAGE_SIZE-1));
    newBlock->freeListStart = newBlock->freeList;
    newBlock->freeListEnd = p + (PAGES_PER_BLOCK-1) * PM_PAGE_SIZE;
    for (i = 0; i < PAGES_PER_BLOCK; i++,p = next)
	FREELIST_NEXT(p) = next = p + PM_PAGE_SIZE;
    FREELIST_NEXT(p - PM_PAGE_SIZE) = NULL;
    return newBlock;
}
#endif

/****************************************************************************
REMARKS:
Allocates a page aligned and page sized block of memory
****************************************************************************/
void * PMAPI PM_allocPage(

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?