/* * Copyright (C) 2010 Piotr JaroszyƄski * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ FILE_LICENCE(GPL2_OR_LATER); #include /** @file * * iPXE user memory allocation API for linux * */ #include #include #include /** Special address returned for empty allocations */ #define NOWHERE ((void *)-1) /** Poison to make the metadata more unique */ #define POISON 0xa5a5a5a5 #define min(a,b) (((a)<(b))?(a):(b)) /** Metadata stored at the beginning of all allocations */ struct metadata { unsigned poison; size_t size; }; #define SIZE_MD (sizeof(struct metadata)) /** Simple realloc which passes most of the work to mmap(), mremap() and munmap() */ static void * linux_realloc(void *ptr, size_t size) { struct metadata md = {0, 0}; struct metadata * mdptr = NULL; DBG2("linux_realloc(%p, %zd)\n", ptr, size); /* Check whether we have a valid pointer */ if (ptr != NULL && ptr != NOWHERE) { mdptr = ptr - SIZE_MD; VALGRIND_MAKE_MEM_DEFINED(mdptr, SIZE_MD); md = *mdptr; VALGRIND_MAKE_MEM_NOACCESS(mdptr, SIZE_MD); /* Check for poison in the metadata */ if (md.poison != POISON) { DBG("linux_realloc bad poison: 0x%x (expected 0x%x)\n", md.poison, POISON); return NULL; } } else { /* Handle NOWHERE as NULL */ ptr = NULL; } /* * At this point, ptr is either NULL or pointing to a region allocated by us. * In the latter case mdptr is pointing to a valid metadata, otherwise it is NULL. */ /* Handle deallocation or allocation of size 0 */ if (size == 0) { if (mdptr) { if (linux_munmap(mdptr, md.size)) DBG("linux_realloc munmap failed: %s\n", linux_strerror(linux_errno)); VALGRIND_FREELIKE_BLOCK(ptr, sizeof(*mdptr)); } return NOWHERE; } if (ptr) { char *vbits = NULL; if (RUNNING_ON_VALGRIND > 0) vbits = linux_realloc(NULL, min(size, md.size)); /* prevent an unused variable warning when building w/o valgrind support */ #ifndef NVALGRIND VALGRIND_GET_VBITS(ptr, vbits, min(size, md.size)); #endif VALGRIND_FREELIKE_BLOCK(ptr, SIZE_MD); mdptr = linux_mremap(mdptr, md.size + SIZE_MD, size + SIZE_MD, MREMAP_MAYMOVE); if (mdptr == MAP_FAILED) { DBG("linux_realloc mremap failed: %s\n", linux_strerror(linux_errno)); return NULL; } ptr = ((void *)mdptr) + SIZE_MD; VALGRIND_MALLOCLIKE_BLOCK(ptr, size, SIZE_MD, 0); /* prevent an unused variable warning when building w/o valgrind support */ #ifndef NVALGRIND VALGRIND_SET_VBITS(ptr, vbits, min(size, md.size)); #endif if (RUNNING_ON_VALGRIND > 0) linux_realloc(vbits, 0); } else { mdptr = linux_mmap(NULL, size + SIZE_MD, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (mdptr == MAP_FAILED) { DBG("linux_realloc mmap failed: %s\n", linux_strerror(linux_errno)); return NULL; } ptr = ((void *)mdptr) + SIZE_MD; VALGRIND_MALLOCLIKE_BLOCK(ptr, size, SIZE_MD, 0); } /* Update the metadata */ VALGRIND_MAKE_MEM_DEFINED(mdptr, SIZE_MD); mdptr->poison = POISON; mdptr->size = size; VALGRIND_MAKE_MEM_NOACCESS(mdptr, SIZE_MD); // VALGRIND_MALLOCLIKE_BLOCK ignores redzones currently, make our own VALGRIND_MAKE_MEM_NOACCESS(ptr + size, SIZE_MD); return ptr; } /** * Reallocate external memory * * @v old_ptr Memory previously allocated by umalloc(), or UNULL * @v new_size Requested size * @ret new_ptr Allocated memory, or UNULL * * Calling realloc() with a new size of zero is a valid way to free a * memory block. */ static userptr_t linux_urealloc(userptr_t old_ptr, size_t new_size) { return (userptr_t)linux_realloc((void *)old_ptr, new_size); } PROVIDE_UMALLOC(linux, urealloc, linux_urealloc);