/* * \brief ARM v7 long physical address space extension page table format * \author Stefan Kalkowski * \date 2014-07-07 */ /* * Copyright (C) 2014-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 _SRC__LIB__HW__SPEC__ARM__LPAE_H_ #define _SRC__LIB__HW__SPEC__ARM__LPAE_H_ #include #include #include #include namespace Hw { enum { SIZE_LOG2_4KB = 12, SIZE_LOG2_16KB = 14, SIZE_LOG2_2MB = 21, SIZE_LOG2_1GB = 30, SIZE_LOG2_4GB = 32, SIZE_LOG2_256GB = 38, }; /** * The stage indicates the kind of translation * * STAGE1 denotes the translation of 32-bit virtual addresses * to a 40-bit "intermediate" or actual physical addresses * STAGE2 denotes the translation of intermediate physical * addresses to actual physical addresses */ enum Stage { STAGE1, STAGE2 }; /** * Translation table base class for long format * * Long translation tables are part of the Large Physical Address Extension * of ARM. They are organized in at most three-level. This class comprises * properties shared between all 3 level of tables. * * \param BLOCK_SIZE_LOG2 virtual address range size in log2 * of a single table entry * \param SIZE_LOG2 virtual address range size in log2 of whole table */ template class Long_translation_table; /** * Translation table third level (leaf in the table tree) * * \param STAGE marks whether it is part of a stage 1 or 2 table */ template class Level_3_translation_table; /** * Translation table first and second level * * \param ENTRY type of the table entries * \param STAGE marks whether it is part of a stage 1 or 2 table * \param SIZE_LOG2 virtual address range size in log2 of whole table */ template class Level_x_translation_table; /** * Trait pattern: helper class to carry information about * the type of a block descriptor (and stage) * through the template forest * * \param TABLE carries information about the table type * \param STAGE marks whether it is part of a stage 1 or 2 table */ template struct Stage_trait; template struct Stage_trait { using Type = typename TABLE::Block_descriptor_stage1; }; template struct Stage_trait { using Type = typename TABLE::Block_descriptor_stage2; }; using Level_3_stage_1_translation_table = Level_3_translation_table; using Level_2_stage_1_translation_table = Level_x_translation_table; using Level_1_stage_1_translation_table = Level_x_translation_table; using Level_3_stage_2_translation_table = Level_3_translation_table; using Level_2_stage_2_translation_table = Level_x_translation_table; using Level_1_stage_2_translation_table = Level_x_translation_table; struct Page_table; using addr_t = Genode::addr_t; using size_t = Genode::size_t; } template class Hw::Long_translation_table { private: bool _aligned(addr_t const a, size_t const alignm_log2) { return a == ((a >> alignm_log2) << alignm_log2); } public: static constexpr size_t SIZE_LOG2 = SZ_LOG2; static constexpr size_t MAX_ENTRIES = 1 << (SIZE_LOG2-BLOCK_SIZE_LOG2); static constexpr size_t BLOCK_SIZE = 1 << BLOCK_SIZE_LOG2; static constexpr size_t BLOCK_MASK = ~((1 << BLOCK_SIZE_LOG2) - 1); static constexpr size_t ALIGNM_LOG2 = SIZE_LOG2_4KB; class Misaligned {}; class Invalid_range {}; class Double_insertion {}; struct Descriptor : Genode::Register<64> { enum Type { INVALID, TABLE, BLOCK }; struct Valid : Bitfield<0, 1> { }; struct Table : Bitfield<1, 1> { }; static Type type(access_t const v) { if (!Valid::get(v)) return INVALID; if (Table::get(v)) return TABLE; return BLOCK; } static bool valid(access_t const v) { return Valid::get(v); } }; struct Block_descriptor_base : Descriptor { struct Shareability : Descriptor::template Bitfield<8, 2> { enum { NON_SHAREABLE = 0, OUTER_SHAREABLE = 2, INNER_SHAREABLE = 3 }; }; struct Access_flag : Descriptor::template Bitfield<10,1> { }; struct Output_address : Descriptor::template Bitfield { }; /** * Indicates that 16 adjacent entries point to contiguous * memory regions */ struct Continguous_hint : Descriptor::template Bitfield<52,1> { }; struct Execute_never : Descriptor::template Bitfield<54,1> { }; }; struct Table_descriptor : Descriptor { struct Next_table : Descriptor::template Bitfield<12, 27> { }; static typename Descriptor::access_t create(void * const pa) { typename Descriptor::access_t oa = (addr_t) pa; return Next_table::masked(oa) | Descriptor::Table::bits(1) | Descriptor::Valid::bits(1); } }; struct Block_descriptor_stage1 : Block_descriptor_base { using Base = Block_descriptor_base; /** * Indicates memory attribute indirection register MAIR */ struct Attribute_index : Base::template Bitfield<2, 3> { enum { UNCACHED = Genode::Cache_attribute::UNCACHED, CACHED = Genode::Cache_attribute::CACHED, DEVICE }; static typename Descriptor::access_t create(Page_flags const &f) { if (f.type == Hw::DEVICE) return Attribute_index::bits(DEVICE); switch (f.cacheable) { case Genode::CACHED: return Attribute_index::bits(CACHED); case Genode::WRITE_COMBINED: case Genode::UNCACHED: return Attribute_index::bits(UNCACHED); } return 0; } }; struct Non_secure : Base::template Bitfield<5, 1> { }; struct Access_permission : Base::template Bitfield<6, 2> { enum { PRIVILEGED_RW, USER_RW, PRIVILEGED_RO, USER_RO }; static typename Descriptor::access_t create(Page_flags const &f) { bool p = f.privileged; if (f.writeable) { if (p) { return Access_permission::bits(PRIVILEGED_RW); } else { return Access_permission::bits(USER_RW); } } else { if (p) { return Access_permission::bits(PRIVILEGED_RO); } else { return Access_permission::bits(USER_RO); } } } }; struct Not_global : Base::template Bitfield<11,1> { }; struct Privileged_execute_never : Base::template Bitfield<53,1> { }; struct Execute_never : Base::template Bitfield<54,1> { }; static typename Descriptor::access_t create(Page_flags const &f, addr_t const pa) { return Access_permission::create(f) | Attribute_index::create(f) | Not_global::bits(!f.global) | Base::Shareability::bits( Base::Shareability::OUTER_SHAREABLE) | Base::Output_address::masked(pa) | Base::Access_flag::bits(1) | Descriptor::Valid::bits(1) | Execute_never::bits(!f.executable); } }; struct Block_descriptor_stage2 : Block_descriptor_base { using Base = Block_descriptor_base; struct Mem_attr : Block_descriptor_base::template Bitfield<2,4>{}; struct Hap : Block_descriptor_base::template Bitfield<6,2>{}; static typename Descriptor::access_t create(Page_flags const &, addr_t const pa) { return Base::Shareability::bits( Base::Shareability::NON_SHAREABLE) | Base::Output_address::masked(pa) | Base::Access_flag::bits(1) | Descriptor::Valid::bits(1) | Mem_attr::bits(0xf) | Hap::bits(0x3); } }; protected: typename Descriptor::access_t _entries[MAX_ENTRIES]; Long_translation_table() { if (!_aligned((addr_t)this, ALIGNM_LOG2)) throw Misaligned(); Genode::memset(&_entries, 0, sizeof(_entries)); } template void _range_op(addr_t vo, addr_t pa, size_t size, FUNC &&func) { for (size_t i = vo >> BLOCK_SIZE_LOG2; size > 0; i = vo >> BLOCK_SIZE_LOG2) { addr_t end = (vo + BLOCK_SIZE) & BLOCK_MASK; size_t sz = Genode::min(size, end-vo); func(vo, pa, sz, _entries[i]); /* check whether we wrap */ if (end < vo) return; size = size - sz; vo += sz; pa += sz; } } public: bool empty() { for (unsigned i = 0; i < MAX_ENTRIES; i++) if (Descriptor::valid(_entries[i])) return false; return true; } } __attribute__((aligned(1 << ALIGNM_LOG2))); template class Hw::Level_3_translation_table : public Hw::Long_translation_table { private: struct Insert_func { Page_flags const & flags; Insert_func(Page_flags const & flags) : flags(flags) { } void operator () (addr_t const vo, addr_t const pa, size_t const size, Descriptor::access_t &desc) { using Base = Long_translation_table; using Block_descriptor = typename Stage_trait::Type; if ((vo & ~BLOCK_MASK) || (pa & ~BLOCK_MASK) || size < BLOCK_SIZE) throw Invalid_range(); Descriptor::access_t blk_desc = Block_descriptor::create(flags, pa) | Descriptor::Table::bits(1); if (Descriptor::valid(desc) && desc != blk_desc) throw Double_insertion(); desc = blk_desc; } }; struct Remove_func { Remove_func() { } void operator () (addr_t /* vo */, addr_t /* pa */, size_t /* size */, Descriptor::access_t &desc) { desc = 0; } }; public: using Allocator = Hw::Page_table_allocator<1 << SIZE_LOG2_4KB>; void insert_translation(addr_t vo, addr_t pa, size_t size, Page_flags const & flags, Allocator &) { _range_op(vo, pa, size, Insert_func(flags)); } void remove_translation(addr_t vo, size_t size, Allocator&) { _range_op(vo, 0, size, Remove_func()); } }; template class Hw::Level_x_translation_table : public Hw::Long_translation_table { public: using Allocator = Hw::Page_table_allocator<1 << SIZE_LOG2_4KB>; private: using Base = Long_translation_table; using Descriptor = typename Base::Descriptor; using Table_descriptor = typename Base::Table_descriptor; using Block_descriptor = typename Stage_trait::Type; template struct Insert_func { Page_flags const & flags; Allocator & alloc; Insert_func(Page_flags const & flags, Allocator & alloc) : flags(flags), alloc(alloc) { } void operator () (addr_t const vo, addr_t const pa, size_t const size, typename Descriptor::access_t &desc) { using Nt = typename Table_descriptor::Next_table; /* can we insert a whole block? */ if (!((vo & ~Base::BLOCK_MASK) || (pa & ~Base::BLOCK_MASK) || size < Base::BLOCK_SIZE)) { typename Descriptor::access_t blk_desc = Block_descriptor::create(flags, pa); if (Descriptor::valid(desc) && desc != blk_desc) throw typename Base::Double_insertion(); desc = blk_desc; return; } /* we need to use a next level table */ switch (Descriptor::type(desc)) { case Descriptor::INVALID: /* no entry */ { /* create and link next level table */ E & table = alloc.construct(); desc = Table_descriptor::create((void*)alloc.phys_addr(table)); } case Descriptor::TABLE: /* table already available */ { /* use allocator to retrieve virt address of table */ E & table = alloc.virt_addr(Nt::masked(desc)); table.insert_translation(vo - (vo & Base::BLOCK_MASK), pa, size, flags, alloc); break; } case Descriptor::BLOCK: /* there is already a block */ { throw typename Base::Double_insertion(); } }; } }; template struct Remove_func { Allocator & alloc; Remove_func(Allocator & alloc) : alloc(alloc) { } void operator () (addr_t const vo, addr_t const /* pa */, size_t const size, typename Descriptor::access_t &desc) { using Nt = typename Table_descriptor::Next_table; switch (Descriptor::type(desc)) { case Descriptor::TABLE: { /* use allocator to retrieve virt address of table */ E & table = alloc.virt_addr(Nt::masked(desc)); table.remove_translation(vo - (vo & Base::BLOCK_MASK), size, alloc); if (!table.empty()) break; alloc.destruct(table); } case Descriptor::BLOCK: case Descriptor::INVALID: desc = 0; } } }; public: static constexpr size_t MIN_PAGE_SIZE_LOG2 = SIZE_LOG2_4KB; static constexpr size_t ALIGNM_LOG2 = SIZE_LOG2_16KB; /** * Insert translations into this table * * \param vo offset of the virtual region represented * by the translation within the virtual * region represented by this table * \param pa base of the physical backing store * \param size size of the translated region * \param flags mapping flags * \param alloc second level translation table allocator */ void insert_translation(addr_t vo, addr_t pa, size_t size, Page_flags const & flags, Allocator & alloc) { this->_range_op(vo, pa, size, Insert_func(flags, alloc)); } /** * Remove translations that overlap with a given virtual region * * \param vo region offset within the tables virtual region * \param size region size * \param alloc second level translation table allocator */ void remove_translation(addr_t vo, size_t size, Allocator & alloc) { this->_range_op(vo, 0, size, Remove_func(alloc)); } }; struct Hw::Page_table : Level_1_stage_1_translation_table { enum { TABLE_LEVEL_X_SIZE_LOG2 = SIZE_LOG2_4KB, TABLE_LEVEL_X_ENTRIES = (1 << SIZE_LOG2_4KB) / sizeof(addr_t), CORE_VM_AREA_SIZE = 1024 * 1024 * 1024, SIZE_1GB = 1 << SIZE_LOG2_1GB, CORE_LEVEL_2_TT_COUNT = ((Genode::uint64_t)CORE_VM_AREA_SIZE + SIZE_1GB - 1) / SIZE_1GB, CORE_TRANS_TABLE_COUNT = CORE_LEVEL_2_TT_COUNT + CORE_LEVEL_2_TT_COUNT * TABLE_LEVEL_X_ENTRIES, }; Page_table() : Level_1_stage_1_translation_table() { } /** * On ARM we do not need to copy top-level kernel entries * because the virtual-memory kernel part is hold in a separate table */ explicit Page_table(Page_table &) : Level_1_stage_1_translation_table() { } }; #endif /* _SRC__LIB__HW__SPEC__ARM__LPAE_H_ */