/** * \brief Linux emulation code * \author Sebastian Sumpf * \date 2013-08-28 */ /* * Copyright (C) 2013 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU General Public License version 2. */ #include #include #include #include #include #include extern "C" { #include } /* * VM-area to reserve for back-end allocator */ enum { VM_RESERVATION = 24 * 1024 * 1024 }; /* * TODO: fine tune these */ unsigned long totalram_pages = VM_RESERVATION / PAGE_SIZE; unsigned long num_physpages = totalram_pages; namespace Genode { template class Slab_backend_alloc; typedef Slab_backend_alloc Backend_alloc; class Slab_alloc; } /** * Back-end allocator for Genode's slab allocator */ template class Genode::Slab_backend_alloc : public Genode::Allocator, public Genode::Rm_connection { private: enum { BLOCK_SIZE = 1024 * 1024, /* 1 MB */ ELEMENTS = VM_SIZE / BLOCK_SIZE, /* MAX number of dataspaces in VM */ }; addr_t _base; /* virt. base address */ bool _cached; /* non-/cached RAM */ Ram_dataspace_capability _ds_cap[ELEMENTS]; /* dataspaces to put in VM */ addr_t _ds_phys[ELEMENTS]; /* physical bases of dataspaces */ int _index; /* current index in ds_cap */ Allocator_avl _range; /* manage allocations */ bool _alloc_block() { if (_index == ELEMENTS) { PERR("Slab-backend exhausted!"); return false; } try { _ds_cap[_index] = Genode::env()->ram_session()->alloc(BLOCK_SIZE, _cached); /* attach at index * BLOCK_SIZE */ Rm_connection::attach_at(_ds_cap[_index], _index * BLOCK_SIZE, BLOCK_SIZE, 0); /* lookup phys. address */ _ds_phys[_index] = Dataspace_client(_ds_cap[_index]).phys_addr(); } catch (...) { return false; } /* return base + offset in VM area */ addr_t block_base = _base + (_index * BLOCK_SIZE); ++_index; _range.add_range(block_base, BLOCK_SIZE); return true; } public: Slab_backend_alloc(bool cached) : Rm_connection(0, VM_SIZE), _cached(cached), _index(0), _range(env()->heap()) { /* reserver attach us, anywere */ _base = env()->rm_session()->attach(dataspace()); } /** * Allocate */ bool alloc(size_t size, void **out_addr) { bool done = _range.alloc(size, out_addr); if (done) return done; done = _alloc_block(); if (!done) { PERR("Backend allocator exhausted\n"); return false; } return _range.alloc(size, out_addr); } void free(void *addr, size_t /* size */) { } size_t overhead(size_t size) { return 0; } /** * Return phys address for given virtual addr. */ addr_t phys_addr(addr_t addr) { if (addr < _base || addr >= (_base + VM_SIZE)) return ~0UL; int index = (addr - _base) / BLOCK_SIZE; /* physical base of dataspace */ addr_t phys = _ds_phys[index]; if (!phys) return ~0UL; /* add offset */ phys += (addr - _base - (index * BLOCK_SIZE)); return phys; } addr_t start() const { return _base; } addr_t end() const { return _base + VM_SIZE - 1; } }; /** * Slab allocator using our back-end allocator */ class Genode::Slab_alloc : public Genode::Slab { private: size_t _calculate_block_size(size_t object_size) { size_t block_size = 8 * (object_size + sizeof(Slab_entry)) + sizeof(Slab_block); return align_addr(block_size, 12); } public: Slab_alloc(size_t object_size, Backend_alloc *allocator) : Slab(object_size, _calculate_block_size(object_size), 0, allocator) { } inline addr_t alloc() { addr_t result; return (Slab::alloc(slab_size(), (void **)&result) ? result : 0); } }; /** * Memory interface used used for Linux emulation */ class Malloc { private: enum { SLAB_START_LOG2 = 3, /* 8 B */ SLAB_STOP_LOG2 = 16, /* 64 KB */ NUM_SLABS = (SLAB_STOP_LOG2 - SLAB_START_LOG2) + 1, }; typedef Genode::addr_t addr_t; typedef Genode::Slab_alloc Slab_alloc; typedef Genode::Slab_backend_alloc Slab_backend_alloc; Slab_backend_alloc *_back_allocator; Slab_alloc *_allocator[NUM_SLABS]; bool _cached; /* cached or un-cached memory */ addr_t _start; /* VM region of this allocator */ addr_t _end; /** * Set 'value' at 'addr' */ void _set_at(addr_t addr, addr_t value) { *((addr_t *)addr) = value; } /** * Retrieve slab index belonging to given address */ unsigned _slab_index(Genode::addr_t **addr) { using namespace Genode; /* get index */ addr_t index = *(*addr - 1); /* * If index large, we use aligned memory, retrieve beginning of slab entry * and read index from there */ if (index > 32) { *addr = (addr_t *)*(*addr - 1); index = *(*addr - 1); } return index; } public: Malloc(Slab_backend_alloc *alloc, bool cached) : _back_allocator(alloc), _cached(cached), _start(alloc->start()), _end(alloc->end()) { /* init slab allocators */ for (unsigned i = SLAB_START_LOG2; i <= SLAB_STOP_LOG2; i++) _allocator[i - SLAB_START_LOG2] = new (Genode::env()->heap()) Slab_alloc(1U << i, alloc); } /** * Alloc in slabs */ void *alloc(Genode::size_t size, int align = 0, Genode::addr_t *phys = 0) { using namespace Genode; /* += slab index + aligment size */ size += sizeof(addr_t) + (align > 2 ? (1 << align) : 0); int msb = Genode::log2(size); if (size > (1U << msb)) msb++; if (size < (1U << SLAB_START_LOG2)) msb = SLAB_STOP_LOG2; if (msb > SLAB_STOP_LOG2) { PERR("Slab too large %u requested %zu cached %d", 1U << msb, size, _cached); return 0; } addr_t addr = _allocator[msb - SLAB_START_LOG2]->alloc(); if (!addr) { PERR("Failed to get slab for %u", 1 << msb); return 0; } _set_at(addr, msb - SLAB_START_LOG2); addr += sizeof(addr_t); if (align > 2) { /* save */ addr_t ptr = addr; addr_t align_val = (1U << align); addr_t align_mask = align_val - 1; /* align */ addr = (addr + align_val) & ~align_mask; /* write start address before aligned address */ _set_at(addr - sizeof(addr_t), ptr); } if (phys) *phys = _back_allocator->phys_addr(addr); return (addr_t *)addr; } void free(void const *a) { using namespace Genode; addr_t *addr = (addr_t *)a; unsigned nr = _slab_index(&addr); _allocator[nr]->free((void *)(addr - 1)); } size_t slab_size(void const *a) { using namespace Genode; addr_t *addr =(addr_t *)a; unsigned nr = _slab_index(&addr); size_t size = 1 << (SLAB_START_LOG2 + nr); size -= (addr_t)a - (addr_t)(addr - 1); return size; } Genode::addr_t phys_addr(void *a) { return _back_allocator->phys_addr((addr_t)a); } /** * Belongs given address to this allocator */ bool inside(addr_t const addr) const { return (addr > _start) && (addr <= _end); } /** * Cached memory allocator */ static Malloc *mem() { static Slab_backend_alloc _b(true); static Malloc _m(&_b, true); return &_m; } }; /************************************* ** Memory allocation, linux/slab.h ** *************************************/ void *kmalloc(size_t size, gfp_t flags) { void *addr = Malloc::mem()->alloc(size); unsigned long a = (unsigned long)addr; if (a & 0x3) PERR("Unaligned kmalloc %lx", a); // PDBG("Kmalloc: [%lx-%lx) from %p", a, a + size, __builtin_return_address(0)); return addr; } void *kzalloc(size_t size, gfp_t flags) { void *addr = kmalloc(size, flags); if (addr) Genode::memset(addr, 0, size); //PDBG("Kmalloc: [%lx-%lx) from %p", (unsigned)addr, (unsigned)addr + size, __builtin_return_address(0)); return addr; } void *kcalloc(size_t n, size_t size, gfp_t flags) { if (size != 0 && n > ~0UL / size) return 0; return kzalloc(n * size, flags); } void kfree(const void *p) { if (Malloc::mem()->inside((Genode::addr_t)p)) Malloc::mem()->free(p); else PWRN("%p is not within our memory range called from %p", p, __builtin_return_address(0)); } void *kmalloc_node_track_caller(size_t size, gfp_t flags, int node) { return kmalloc(size, 0); } void *kzalloc_node(size_t size, gfp_t flags, int node) { return kzalloc(size, 0); } size_t ksize(const void *p) { if (!(Malloc::mem()->inside((Genode::addr_t)p))) { PDBG("%p not in slab allocator", p); return 0; } size_t size = Malloc::mem()->slab_size(p); return size; } /******************** ** linux/string.h ** ********************/ char *strcpy(char *to, const char *from) { char *save = to; for (; (*to = *from); ++from, ++to); return(save); } char *strchr(const char *p, int ch) { char c; c = ch; for (;; ++p) { if (*p == c) return ((char *)p); if (*p == '\0') break; } return 0; } char *strnchr(const char *p, size_t count, int ch) { char c; c = ch; for (; count; ++p, count--) { if (*p == c) return ((char *)p); if (*p == '\0') break; } return 0; } size_t strnlen(const char *s, size_t maxlen) { size_t c; for (c = 0; c < maxlen; c++) if (!s[c]) return c; return maxlen; } size_t strlen(const char *s) { return Genode::strlen(s); } int strcmp(const char *s1, const char *s2) { return Genode::strcmp(s1, s2); } int strncmp(const char *s1, const char *s2, size_t len) { return Genode::strcmp(s1, s2, len); } int memcmp(const void *p0, const void *p1, size_t size) { return Genode::memcmp(p0, p1, size); } int snprintf(char *str, size_t size, const char *format, ...) { va_list list; va_start(list, format); Genode::String_console sc(str, size); sc.vprintf(format, list); va_end(list); return sc.len(); } size_t strlcpy(char *dest, const char *src, size_t size) { size_t ret = strlen(src); if (size) { size_t len = (ret >= size) ? size - 1 : ret; Genode::memcpy(dest, src, len); dest[len] = '\0'; } return ret; } void *kmemdup(const void *src, size_t len, gfp_t gfp) { void *ptr = kmalloc(len, gfp); Genode::memcpy(ptr, src, len); return ptr; } void *genode_memcpy(void *d, const void *s, size_t n) { return Genode::memcpy(d, s, n); } /****************** ** linux/log2.h ** ******************/ unsigned long ilog2(unsigned long n) { return Genode::log2(n); } /******************* ** linux/sched.h ** *******************/ extern "C" void __wait_event() { Genode::Signal s = Net::Env::receiver()->wait_for_signal(); static_cast(s.context())->dispatch(s.num()); } long schedule_timeout_uninterruptible(signed long timeout) { return schedule_timeout(timeout); } signed long schedule_timeout(signed long timeout) { long start = jiffies; __wait_event(); timeout -= jiffies - start; return timeout < 0 ? 0 : timeout; } void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p) { __wait_event(); } /****************** ** linux/time.h ** ******************/ unsigned long get_seconds(void) { return jiffies / HZ; } /***************** ** linux/gfp.h ** *****************/ class Avl_page : public Genode::Avl_node { private: Genode::addr_t _addr; Genode::size_t _size; struct page *_page; public: Avl_page(Genode::size_t size) : _size(size) { _addr =(Genode::addr_t)kmalloc(size, 0); if (!_addr) throw -1; _page = (struct page *) kzalloc(sizeof(struct page), 0); if (!_page) { kfree((void *)_addr); throw -2; } _page->addr = (void *)_addr; atomic_set(&_page->_count, 1); dde_kit_log(DEBUG_SLAB, "alloc page: %p addr: %lx-%lx", _page, _addr, _addr + size); } virtual ~Avl_page() { kfree((void *)_addr); kfree((void *)_page); } struct page* page() { return _page; } bool higher(Avl_page *c) { return c->_addr > _addr; } Avl_page *find_by_address(Genode::addr_t addr) { if (addr >= _addr && addr < _addr + _size) return this; bool side = addr > _addr; Avl_page *c = Avl_node::child(side); return c ? c->find_by_address(addr) : 0; } }; static Genode::Avl_tree tree; struct page *alloc_pages(gfp_t gfp_mask, unsigned int order) { Avl_page *p; try { p = (Avl_page *)new (Genode::env()->heap()) Avl_page(PAGE_SIZE << order); tree.insert(p); } catch (...) { return 0; } return p->page(); } /**************** ** linux/mm.h ** ****************/ struct page *virt_to_head_page(const void *x) { Avl_page *p = tree.first()->find_by_address((Genode::addr_t)x); dde_kit_log(DEBUG_SLAB, "virt_to_head_page: %p page %p\n", x,p ? p->page() : 0); return p ? p->page() : 0; } void put_page(struct page *page) { if (!atomic_dec_and_test(&page->_count)) return; dde_kit_log(DEBUG_SLAB, "put_page: %p", page); Avl_page *p = tree.first()->find_by_address((Genode::addr_t)page->addr); tree.remove(p); destroy(Genode::env()->heap(), p); }