/* * \brief Associate page-table and frame selectors with virtual addresses * \author Norman Feske * \date 2015-05-04 */ /* * Copyright (C) 2015-2017 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. */ #ifndef _CORE__INCLUDE__PAGE_TABLE_REGISTRY_H_ #define _CORE__INCLUDE__PAGE_TABLE_REGISTRY_H_ /* Genode includes */ #include #include #include /* core includes */ #include #include namespace Genode { class Page_table_registry; } class Genode::Page_table_registry { public: class Lookup_failed : Exception { }; class Mapping_cache_full : Exception { }; private: /* * XXX use AVL tree (with virtual address as key) instead of list */ class Page_table : public List::Element { public: struct Entry : List::Element { addr_t const addr; unsigned const sel; Entry(addr_t addr, unsigned sel) : addr(addr), sel(sel) { } }; addr_t const addr; private: List _entries; static addr_t _page_frame_base(addr_t addr) { return addr & get_page_mask(); } bool _entry_exists(addr_t addr) const { for (Entry const *e = _entries.first(); e; e = e->next()) { if (_page_frame_base(e->addr) == _page_frame_base(addr)) return true; } return false; } public: class Lookup_failed : Exception { }; Page_table(addr_t addr) : addr(addr) { } Entry *first() { return _entries.first(); } Entry &lookup(addr_t addr) { for (Entry *e = _entries.first(); e; e = e->next()) { if (_page_frame_base(e->addr) == _page_frame_base(addr)) return *e; } throw Lookup_failed(); } void insert_entry(Allocator &entry_alloc, addr_t addr, unsigned sel) { if (_entry_exists(addr)) { warning("trying to insert page frame for ", Hex(addr), " twice"); return; } try { _entries.insert(new (entry_alloc) Entry(addr, sel)); } catch (Genode::Allocator::Out_of_memory) { throw Mapping_cache_full(); } } void remove_entry(Allocator &entry_alloc, addr_t addr) { try { Entry &entry = lookup(addr); _entries.remove(&entry); destroy(entry_alloc, &entry); } catch (Lookup_failed) { } } void flush_all(Allocator &entry_alloc) { for (; Entry *entry = _entries.first();) { _entries.remove(entry); destroy(entry_alloc, entry); } } }; /** * Allocator operating on a static memory pool * * \param ELEM element type * \param MAX maximum number of elements * * The size of a single ELEM must be a multiple of sizeof(long). */ template class Static_allocator : public Allocator { private: Bit_allocator _used; struct Elem_space { long space[sizeof(ELEM)/sizeof(long)]; }; Elem_space _elements[MAX]; public: class Alloc_failed { }; bool alloc(size_t size, void **out_addr) override { *out_addr = nullptr; if (size > sizeof(Elem_space)) { error("unexpected allocation size of ", size); return false; } try { *out_addr = &_elements[_used.alloc()]; } catch (typename Bit_allocator::Out_of_indices) { return false; } return true; } size_t overhead(size_t) const override { return 0; } void free(void *ptr, size_t) override { Elem_space *elem = reinterpret_cast(ptr); unsigned const index = elem - &_elements[0]; _used.free(index); } bool need_size_for_free() const { return false; } }; Static_allocator _page_table_alloc; Static_allocator _page_table_entry_alloc; List _page_tables; static addr_t _page_table_base(addr_t addr) { return addr & ~(4*1024*1024 - 1); } bool _page_table_exists(addr_t addr) const { for (Page_table const *pt = _page_tables.first(); pt; pt = pt->next()) { if (_page_table_base(pt->addr) == _page_table_base(addr)) return true; } return false; } Page_table &_lookup(addr_t addr) { for (Page_table *pt = _page_tables.first(); pt; pt = pt->next()) { if (_page_table_base(pt->addr) == _page_table_base(addr)) return *pt; } warning(__func__, ": page-table lookup failed ", Hex(addr)); throw Lookup_failed(); } public: /** * Constructor * * \param md_alloc backing store allocator for metadata * * XXX The md_alloc argument is currently unused as we dimension * MAX_PAGE_TABLES and MAX_PAGE_TABLE_ENTRIES statically. */ Page_table_registry(Allocator &md_alloc) { } ~Page_table_registry() { if (_page_tables.first()) error("still entries in page table registry in destruction"); } /** * Register page table * * \param addr virtual address * \param sel page-table selector */ void insert_page_table(addr_t addr, Cap_sel sel) { /* XXX sel is unused */ if (_page_table_exists(addr)) { warning("attempt to insert page table for ", Hex(addr), " twice"); return; } _page_tables.insert(new (_page_table_alloc) Page_table(addr)); } bool has_page_table_at(addr_t addr) const { return _page_table_exists(addr); } /** * Register page table entry * * \param addr virtual address * \param sel page frame selector * * \throw Lookup_failed no page table for given address */ void insert_page_table_entry(addr_t addr, unsigned sel) { _lookup(addr).insert_entry(_page_table_entry_alloc, addr, sel); } /** * Discard the information about the given virtual address */ void forget_page_table_entry(addr_t addr) { try { Page_table &page_table = _lookup(addr); page_table.remove_entry(_page_table_entry_alloc, addr); } catch (...) { } } void flush_cache() { for (Page_table *pt = _page_tables.first(); pt; pt = pt->next()) pt->flush_all(_page_table_entry_alloc); } /** * Apply functor 'fn' to selector of specified virtual address * * \param addr virtual address * * The functor is called with the selector of the page table entry * (the copy of the phys frame selector) as argument. */ template void apply(addr_t addr, FN const &fn) { try { Page_table &page_table = _lookup(addr); Page_table::Entry &entry = page_table.lookup(addr); fn(entry.sel); } catch (...) { } } template void apply_to_and_destruct_all(FN const &fn) { for (Page_table *pt; (pt = _page_tables.first());) { Page_table::Entry *entry = pt->first(); for (; entry; entry = entry->next()) fn(entry->sel); pt->flush_all(_page_table_entry_alloc); _page_tables.remove(pt); destroy(_page_table_alloc, pt); } } }; #endif /* _CORE__INCLUDE__PAGE_TABLE_REGISTRY_H_ */