/**************************************************************************** * YABEL BIOS Emulator * * Copyright (c) 2008 Pattrick Hueper <phueper@hueper.net> * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ #include <x86emu/x86emu.h> #include "../x86emu/prim_ops.h" #include <string.h> #include "biosemu.h" #include "pmm.h" #include "debug.h" #include "device.h" /* this struct is used to remember which PMM spaces * have been assigned. MAX_PMM_AREAS defines how many * PMM areas we can assign. * All areas are assigned in PMM_CONV_SEGMENT */ typedef struct { u32 handle; /* handle that is returned to PMM caller */ u32 offset; /* in PMM_CONV_SEGMENT */ u32 length; /* length of this area */ } pmm_allocation_t; #define MAX_PMM_AREAS 10 /* array to store the above structs */ static pmm_allocation_t pmm_allocation_array[MAX_PMM_AREAS]; /* index into pmm_allocation_array */ static u32 curr_pmm_allocation_index = 0; /* This function is used to setup the PMM struct in virtual memory * at a certain offset, the length of the PMM struct is returned */ u8 pmm_setup(u16 segment, u16 offset) { /* setup the PMM structure */ pmm_information_t *pis = (pmm_information_t *) (M.mem_base + (((u32) segment) << 4) + offset); memset(pis, 0, sizeof(pmm_information_t)); /* set signature to $PMM */ pis->signature[0] = '$'; pis->signature[1] = 'P'; pis->signature[2] = 'M'; pis->signature[3] = 'M'; /* revision as specified */ pis->struct_rev = 0x01; /* internal length, excluding code */ pis->length = ((void *)&(pis->code) - (void *)&(pis->signature)); /* the code to be executed, pointed to by entry_point_offset */ pis->code[0] = 0xCD; /* INT */ pis->code[1] = PMM_INT_NUM; /* my selfdefined PMM INT number */ pis->code[2] = 0xCB; /* RETF */ /* set the entry_point_offset, it should point to pis->code, segment is the segment of * this struct. Since pis->length is the length of the struct excluding code, offset+pis->length * points to the code... it's that simple ;-) */ out32le(&(pis->entry_point_offset), (u32) segment << 16 | (u32) (offset + pis->length)); /* checksum calculation */ u8 i; u8 checksum = 0; for (i = 0; i < pis->length; i++) { checksum += *(((u8 *) pis) + i); } pis->checksum = ((u8) 0) - checksum; CHECK_DBG(DEBUG_PMM) { DEBUG_PRINTF_PMM("PMM Structure:\n"); dump((void *)pis, sizeof(pmm_information_t)); } return sizeof(pmm_information_t); } /* handle the selfdefined interrupt, this is executed, when the PMM Entry Point * is executed, it must handle all PMM requests */ void pmm_handleInt(void) { u32 rval = 0; u16 function, flags; u32 handle, length; u32 i, j; u32 buffer; /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * according to the PMM Spec "the flags and all registers, except DX and AX * are preserved across calls to PMM" * so we save M.x86 and in :exit label we restore it, however, this means that no * returns must be used in this function, any exit must use goto exit! * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ X86EMU_regs backup_regs = M.x86; pop_long(); /* pop the return address, this is already saved in INT handler, we don't need to remember this. */ function = pop_word(); switch (function) { case 0: /* function pmmAllocate */ length = pop_long(); length *= 16; /* length is passed in "paragraphs" of 16 bytes each */ handle = pop_long(); flags = pop_word(); DEBUG_PRINTF_PMM ("%s: pmmAllocate: Length: %x, Handle: %x, Flags: %x\n", __func__, length, handle, flags); if ((flags & 0x1) != 0) { /* request to allocate in conventional memory */ if (curr_pmm_allocation_index >= MAX_PMM_AREAS) { printf ("%s: pmmAllocate: Maximum Number of allocatable areas reached (%d), cannot allocate more memory!\n", __func__, MAX_PMM_AREAS); rval = 0; goto exit; } /* some ROMs seem to be confused by offset 0, so lets start at 0x100 */ u32 next_offset = 0x100; pmm_allocation_t *pmm_alloc = &(pmm_allocation_array[curr_pmm_allocation_index]); if (curr_pmm_allocation_index != 0) { /* we have already allocated... get the new next_offset * from the previous pmm_allocation_t */ next_offset = pmm_allocation_array [curr_pmm_allocation_index - 1].offset + pmm_allocation_array [curr_pmm_allocation_index - 1].length; } DEBUG_PRINTF_PMM("%s: next_offset: 0x%x\n", __func__, next_offset); if (length == 0) { /* largest possible block size requested, we have on segment * to allocate, so largest possible is segment size (0xFFFF) * minus next_offset */ rval = 0xFFFF - next_offset; goto exit; } u32 align = 0; if (((flags & 0x4) != 0) && (length > 0)) { /* align to least significant bit set in length param */ u8 lsb = 0; while (((length >> lsb) & 0x1) == 0) { lsb++; } align = 1 << lsb; } /* always align at least to paragraph (16byte) boundary * hm... since the length is always in paragraphs, we cannot * align outside of paragraphs anyway... so this check might * be unnecessary...*/ if (align < 0x10) { align = 0x10; } DEBUG_PRINTF_PMM("%s: align: 0x%x\n", __func__, align); if ((next_offset & (align - 1)) != 0) { /* not yet aligned... align! */ next_offset += align; next_offset &= ~(align - 1); } if ((next_offset + length) > 0xFFFF) { rval = 0; printf ("%s: pmmAllocate: Not enough memory available for allocation!\n", __func__); goto exit; } curr_pmm_allocation_index++; /* remember the values in pmm_allocation_array */ pmm_alloc->handle = handle; pmm_alloc->offset = next_offset; pmm_alloc->length = length; /* return the 32bit "physical" address, i.e. combination of segment and offset */ rval = ((u32) (PMM_CONV_SEGMENT << 16)) | next_offset; DEBUG_PRINTF_PMM ("%s: pmmAllocate: allocated memory at %x\n", __func__, rval); } else { rval = 0; printf ("%s: pmmAllocate: allocation in extended memory not supported!\n", __func__); } goto exit; case 1: /* function pmmFind */ handle = pop_long(); /* the handle to lookup */ DEBUG_PRINTF_PMM("%s: pmmFind: Handle: %x\n", __func__, handle); i = 0; for (i = 0; i < curr_pmm_allocation_index; i++) { if (pmm_allocation_array[i].handle == handle) { DEBUG_PRINTF_PMM ("%s: pmmFind: found allocated memory at %x\n", __func__, rval); /* return the 32bit "physical" address, i.e. combination of segment and offset */ rval = ((u32) (PMM_CONV_SEGMENT << 16)) | pmm_allocation_array[i].offset; } } if (rval == 0) { DEBUG_PRINTF_PMM ("%s: pmmFind: handle (%x) not found!\n", __func__, handle); } goto exit; case 2: /* function pmmDeallocate */ buffer = pop_long(); /* since argument is the address of the PMM block (including the segment, * we need to remove the segment to get the offset */ buffer = buffer ^ ((u32) PMM_CONV_SEGMENT << 16); DEBUG_PRINTF_PMM("%s: pmmDeallocate: PMM segment offset: %x\n", __func__, buffer); i = 0; /* rval = 0 means we deallocated the buffer, so set it to 1 in case we don't find it and * thus cannot deallocate */ rval = 1; for (i = 0; i < curr_pmm_allocation_index; i++) { DEBUG_PRINTF_PMM("%d: %x\n", i, pmm_allocation_array[i].handle); if (pmm_allocation_array[i].offset == buffer) { /* we found the requested buffer, rval = 0 */ rval = 0; DEBUG_PRINTF_PMM ("%s: pmmDeallocate: found allocated memory at index: %d\n", __func__, i); /* copy the remaining elements in pmm_allocation_array one position up */ j = i; for (; j < curr_pmm_allocation_index; j++) { pmm_allocation_array[j] = pmm_allocation_array[j + 1]; } /* move curr_pmm_allocation_index one up, too */ curr_pmm_allocation_index--; /* finally clean last element */ pmm_allocation_array[curr_pmm_allocation_index]. handle = 0; pmm_allocation_array[curr_pmm_allocation_index]. offset = 0; pmm_allocation_array[curr_pmm_allocation_index]. length = 0; break; } } if (rval != 0) { DEBUG_PRINTF_PMM ("%s: pmmDeallocate: offset (%x) not found, cannot deallocate!\n", __func__, buffer); } goto exit; default: /* invalid/unimplemented function */ printf("%s: invalid PMM function (0x%04x) called!\n", __func__, function); /* PMM spec says if function is invalid, return 0xFFFFFFFF */ rval = 0xFFFFFFFF; goto exit; } exit: /* exit handler of this function, restore registers, put return value in DX:AX */ M.x86 = backup_regs; M.x86.R_DX = (u16) ((rval >> 16) & 0xFFFF); M.x86.R_AX = (u16) (rval & 0xFFFF); CHECK_DBG(DEBUG_PMM) { DEBUG_PRINTF_PMM("%s: dump of pmm_allocation_array:\n", __func__); for (i = 0; i < MAX_PMM_AREAS; i++) { DEBUG_PRINTF_PMM ("%d:\n\thandle: %x\n\toffset: %x\n\tlength: %x\n", i, pmm_allocation_array[i].handle, pmm_allocation_array[i].offset, pmm_allocation_array[i].length); } } return; } /* This function tests the pmm_handleInt() function above. */ void pmm_test(void) { u32 handle, length, addr; u16 function, flags; /*-------------------- Test simple allocation/find/deallocation ----------------------------- */ function = 0; /* pmmAllocate */ handle = 0xdeadbeef; length = 16; /* in 16byte paragraphs, so we allocate 256 bytes... */ flags = 0x1; /* conventional memory, unaligned */ /* setup stack for call to pmm_handleInt() */ push_word(flags); push_long(handle); push_long(length); push_word(function); push_long(0); /* This is the return address for the ABI, unused in this implementation */ pmm_handleInt(); addr = ((u32) M.x86.R_DX << 16) | M.x86.R_AX; DEBUG_PRINTF_PMM("%s: allocated memory at: %04x:%04x\n", __func__, M.x86.R_DX, M.x86.R_AX); function = 1; /* pmmFind */ push_long(handle); push_word(function); push_long(0); /* This is the return address for the ABI, unused in this implementation */ pmm_handleInt(); DEBUG_PRINTF_PMM("%s: found memory at: %04x:%04x (expected: %08x)\n", __func__, M.x86.R_DX, M.x86.R_AX, addr); function = 2; /* pmmDeallocate */ push_long(addr); push_word(function); push_long(0); /* This is the return address for the ABI, unused in this implementation */ pmm_handleInt(); DEBUG_PRINTF_PMM ("%s: freed memory rval: %04x:%04x (expected: 0000:0000)\n", __func__, M.x86.R_DX, M.x86.R_AX); /*-------------------- Test aligned allocation/deallocation ----------------------------- */ function = 0; /* pmmAllocate */ handle = 0xdeadbeef; length = 257; /* in 16byte paragraphs, so we allocate 4KB + 16 bytes... */ flags = 0x1; /* conventional memory, unaligned */ /* setup stack for call to pmm_handleInt() */ push_word(flags); push_long(handle); push_long(length); push_word(function); push_long(0); /* This is the return address for the ABI, unused in this implementation */ pmm_handleInt(); addr = ((u32) M.x86.R_DX << 16) | M.x86.R_AX; DEBUG_PRINTF_PMM("%s: allocated memory at: %04x:%04x\n", __func__, M.x86.R_DX, M.x86.R_AX); function = 0; /* pmmAllocate */ handle = 0xf00d4b0b; length = 128; /* in 16byte paragraphs, so we allocate 2KB... */ flags = 0x5; /* conventional memory, aligned */ /* setup stack for call to pmm_handleInt() */ push_word(flags); push_long(handle); push_long(length); push_word(function); push_long(0); /* This is the return address for the ABI, unused in this implementation */ pmm_handleInt(); /* the address should be aligned to 0x800, so probably it is at offset 0x1800... */ addr = ((u32) M.x86.R_DX << 16) | M.x86.R_AX; DEBUG_PRINTF_PMM("%s: allocated memory at: %04x:%04x\n", __func__, M.x86.R_DX, M.x86.R_AX); function = 1; /* pmmFind */ push_long(handle); push_word(function); push_long(0); /* This is the return address for the ABI, unused in this implementation */ pmm_handleInt(); addr = ((u32) M.x86.R_DX << 16) | M.x86.R_AX; function = 2; /* pmmDeallocate */ push_long(addr); push_word(function); push_long(0); /* This is the return address for the ABI, unused in this implementation */ pmm_handleInt(); DEBUG_PRINTF_PMM ("%s: freed memory rval: %04x:%04x (expected: 0000:0000)\n", __func__, M.x86.R_DX, M.x86.R_AX); handle = 0xdeadbeef; function = 1; /* pmmFind */ push_long(handle); push_word(function); push_long(0); /* This is the return address for the ABI, unused in this implementation */ pmm_handleInt(); addr = ((u32) M.x86.R_DX << 16) | M.x86.R_AX; function = 2; /* pmmDeallocate */ push_long(addr); push_word(function); push_long(0); /* This is the return address for the ABI, unused in this implementation */ pmm_handleInt(); DEBUG_PRINTF_PMM ("%s: freed memory rval: %04x:%04x (expected: 0000:0000)\n", __func__, M.x86.R_DX, M.x86.R_AX); /*-------------------- Test out of memory allocation ----------------------------- */ function = 0; /* pmmAllocate */ handle = 0xdeadbeef; length = 0; /* length zero means, give me the largest possible block */ flags = 0x1; /* conventional memory, unaligned */ /* setup stack for call to pmm_handleInt() */ push_word(flags); push_long(handle); push_long(length); push_word(function); push_long(0); /* This is the return address for the ABI, unused in this implementation */ pmm_handleInt(); length = ((u32) M.x86.R_DX << 16) | M.x86.R_AX; length /= 16; /* length in paragraphs */ DEBUG_PRINTF_PMM("%s: largest possible length: %08x\n", __func__, length); function = 0; /* pmmAllocate */ flags = 0x1; /* conventional memory, aligned */ /* setup stack for call to pmm_handleInt() */ push_word(flags); push_long(handle); push_long(length); push_word(function); push_long(0); /* This is the return address for the ABI, unused in this implementation */ pmm_handleInt(); addr = ((u32) M.x86.R_DX << 16) | M.x86.R_AX; DEBUG_PRINTF_PMM("%s: allocated memory at: %04x:%04x\n", __func__, M.x86.R_DX, M.x86.R_AX); function = 0; /* pmmAllocate */ length = 1; handle = 0xf00d4b0b; flags = 0x1; /* conventional memory, aligned */ /* setup stack for call to pmm_handleInt() */ push_word(flags); push_long(handle); push_long(length); push_word(function); push_long(0); /* This is the return address for the ABI, unused in this implementation */ pmm_handleInt(); /* this should fail, so 0x0 should be returned */ addr = ((u32) M.x86.R_DX << 16) | M.x86.R_AX; DEBUG_PRINTF_PMM ("%s: allocated memory at: %04x:%04x expected: 0000:0000\n", __func__, M.x86.R_DX, M.x86.R_AX); handle = 0xdeadbeef; function = 1; /* pmmFind */ push_long(handle); push_word(function); push_long(0); /* This is the return address for the ABI, unused in this implementation */ pmm_handleInt(); addr = ((u32) M.x86.R_DX << 16) | M.x86.R_AX; function = 2; /* pmmDeallocate */ push_long(addr); push_word(function); push_long(0); /* This is the return address for the ABI, unused in this implementation */ pmm_handleInt(); DEBUG_PRINTF_PMM ("%s: freed memory rval: %04x:%04x (expected: 0000:0000)\n", __func__, M.x86.R_DX, M.x86.R_AX); }