.. _program_listing_file_include_embers_helpers_xlator.h: Program Listing for File xlator.h ================================= |exhale_lsh| :ref:`Return to documentation for file ` (``include/embers/helpers/xlator.h``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp /* Copyright © 2020 Advanced Micro Devices, Inc. All rights reserved */ #ifndef XLATOR_H #define XLATOR_H #include #include #include #include #include #include #include #include namespace embers { class Xlator { struct Translation { uintptr_t pa; bool ok; }; private: const uintptr_t pagesize_; const pid_t pid_; const uid_t euid_; std::unordered_map xlate_cache_; mutable std::shared_mutex mutex_; // RW-lock to protect the xlate_cache_ // The layout of each entry in a process' virtual pages as defined in // `/proc/[pid]/pagemap` - see `man 5 proc` or consult the online manpages: // https://man7.org/linux/man-pages/man5/proc.5.html. We only care about if // a particular entry is present and its page frame number (pfn). More info // is available in the entry. union pagemap_entry { uint64_t val; struct { uint64_t pfn : 55; // 54:0 uint64_t other_bits : 8; // 55:62 uint64_t present : 1; // 63 }; }; pagemap_entry get_pagemap_entry(std::ifstream &pagemap_file, const uintptr_t va) const { pagemap_entry pm_ent; pm_ent.present = false; pm_ent.pfn = 0xdead; // Calculate the vpn (virtual page number) from va (virtual address) uint64_t vpn = va / pagesize_; // Calculate the offset into pagemap file using vpn to find our entry std::streampos offset = vpn * sizeof(pm_ent); // Lookup and return pagemap_entry pagemap_file.seekg(offset); if (pagemap_file.tellg() != offset) { std::cerr << "WARN: pos after seek: " << std::to_string(pagemap_file.tellg()) << "\n"; } pagemap_file.read(reinterpret_cast(&pm_ent), sizeof(pm_ent)); // Ensure entry is marked "Present in RAM" if (!pm_ent.present) { std::cerr << "WARN: page not in ram!" << "\n"; return pm_ent; } return pm_ent; } Translation translate(uintptr_t va) { std::ifstream pagemap_file; pagemap_file.rdbuf()->pubsetbuf(0, 0); // Prevents reading more bytes than requested pagemap_file.open(std::string("/proc/") + std::to_string(pid_) + std::string("/pagemap")); const auto ent = get_pagemap_entry(pagemap_file, va); if (!ent.present) { return Translation{.pa = 0, .ok = false}; } // Calculate the `pa` (physical address) using the pagemap entry's // pfn, our system's `pagesize_`, and the va (for the byte index into the // page in the resultant physical address). uint64_t pa = (ent.pfn * pagesize_) | (va % pagesize_); { std::unique_lock lock(mutex_); // Wr-lock // Update translation cache xlate_cache_[va & ~(pagesize_ - 1)] = pa & ~(pagesize_ - 1); } return Translation{.pa = pa, .ok = true}; } public: Xlator() : pagesize_(sysconf(_SC_PAGE_SIZE)), pid_(getpid()), euid_(geteuid()) {} ~Xlator() {} void ClearCache() { std::unique_lock lock(mutex_); // Wr-lock xlate_cache_.clear(); } // Translation via the pagemap requires 'root' permissions bool CanTranslate() { return euid_ == 0; } // @note // IMPORTANT: Page mappings can change arbitrarily at runtime due to swap // and kernel mm mechanics (copy-on-write, etc). To ensure the // most 'stable' (but no guarantees!) page mapping: // // * Prefer using pinned pages // * For READ-ONLY pages: read an address from the page first // * For WRITE-ONLY pages: write an address in the page first // // Caller can load the VA content into a volatile variable // prior to calling VAtoPA to help ensure the page mapping // will exist so that a valid PA can be returned. // Translation VAtoPA(const uintptr_t va, const bool bypass_xlate_cache = false) { if (!CanTranslate()) { return Translation{.pa = 0, .ok = false}; } if (bypass_xlate_cache) { return translate(va); } { std::shared_lock lock(mutex_); // Rd-lock const auto it = xlate_cache_.find(va & ~(pagesize_ - 1)); if (it != xlate_cache_.end()) { return Translation{.pa = it->second | (va % pagesize_), .ok = true}; } } return translate(va); } }; } // namespace embers #endif // XLATOR_H