mirror of
https://git.rwth-aachen.de/acs/public/villas/node/
synced 2025-03-09 00:00:00 +01:00
lib/memory: implement memory handling with allocators and blocks
This commit is 2/2 of a series of patches and not working on its own.
This commit is contained in:
parent
b01a50184c
commit
60882f1086
8 changed files with 267 additions and 19 deletions
|
@ -38,6 +38,7 @@
|
|||
#include "kernel/vfio.hpp"
|
||||
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include "plugin.hpp"
|
||||
|
@ -46,6 +47,7 @@
|
|||
#include "config.h"
|
||||
|
||||
#include "memory_manager.hpp"
|
||||
#include "memory.hpp"
|
||||
|
||||
#define PCI_FILTER_DEFAULT_FPGA { \
|
||||
.id = { \
|
||||
|
@ -70,6 +72,7 @@ public:
|
|||
friend PCIeCardFactory;
|
||||
|
||||
PCIeCard() : filter(PCI_FILTER_DEFAULT_FPGA) {}
|
||||
~PCIeCard();
|
||||
|
||||
bool init();
|
||||
bool stop() { return true; }
|
||||
|
@ -80,12 +83,19 @@ public:
|
|||
ip::IpCore* lookupIp(const std::string& name) const;
|
||||
ip::IpCore* lookupIp(const Vlnv& vlnv) const;
|
||||
|
||||
bool
|
||||
mapMemoryBlock(const MemoryBlock& block);
|
||||
|
||||
private:
|
||||
/// Cache a set of already mapped memory blocks
|
||||
std::set<MemoryManager::AddressSpaceId> memoryBlocksMapped;
|
||||
|
||||
public: // TODO: make this private
|
||||
ip::IpCoreList ips; ///< IPs located on this FPGA card
|
||||
|
||||
bool do_reset; /**< Reset VILLASfpga during startup? */
|
||||
int affinity; /**< Affinity for MSI interrupts */
|
||||
|
||||
|
||||
std::string name; /**< The name of the FPGA card */
|
||||
|
||||
struct pci *pci;
|
||||
|
@ -97,17 +107,19 @@ public:
|
|||
/// The VFIO device that represents this card
|
||||
VfioDevice* vfioDevice;
|
||||
|
||||
/// Slave address space ID to access the PCIe address space from the FPGA
|
||||
MemoryManager::AddressSpaceId addrSpaceIdDeviceToHost;
|
||||
|
||||
/// Address space identifier of the master address space of this FPGA card.
|
||||
/// This will be used for address resolution of all IPs on this card.
|
||||
MemoryManager::AddressSpaceId addrSpaceId;
|
||||
|
||||
size_t maplen;
|
||||
size_t dmalen;
|
||||
MemoryManager::AddressSpaceId addrSpaceIdHostToDevice;
|
||||
|
||||
protected:
|
||||
SpdLogger
|
||||
getLogger() const
|
||||
{ return loggerGetOrCreate(name); }
|
||||
|
||||
SpdLogger logger;
|
||||
};
|
||||
|
||||
using CardList = std::list<std::unique_ptr<PCIeCard>>;
|
||||
|
|
|
@ -51,6 +51,7 @@ public:
|
|||
|
||||
private:
|
||||
static constexpr char axiInterface[] = "M_AXI";
|
||||
static constexpr char pcieMemory[] = "BAR0";
|
||||
};
|
||||
|
||||
|
||||
|
|
122
fpga/include/villas/memory.hpp
Normal file
122
fpga/include/villas/memory.hpp
Normal file
|
@ -0,0 +1,122 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "log.hpp"
|
||||
#include "memory_manager.hpp"
|
||||
|
||||
namespace villas {
|
||||
|
||||
class MemoryBlock {
|
||||
protected:
|
||||
MemoryBlock(MemoryManager::AddressSpaceId addrSpaceId, size_t size) :
|
||||
addrSpaceId(addrSpaceId), size(size) {}
|
||||
|
||||
public:
|
||||
MemoryManager::AddressSpaceId getAddrSpaceId() const
|
||||
{ return addrSpaceId; }
|
||||
|
||||
size_t getSize() const
|
||||
{ return size; }
|
||||
|
||||
private:
|
||||
MemoryManager::AddressSpaceId addrSpaceId;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
|
||||
class MemoryAllocator {
|
||||
};
|
||||
|
||||
|
||||
class HostRam : public MemoryAllocator {
|
||||
public:
|
||||
|
||||
template<typename T>
|
||||
class MemoryBlockHostRam : public MemoryBlock {
|
||||
friend class HostRam;
|
||||
private:
|
||||
MemoryBlockHostRam(void* addr, size_t size, MemoryManager::AddressSpaceId foreignAddrSpaceId) :
|
||||
MemoryBlock(foreignAddrSpaceId, size),
|
||||
addr(addr),
|
||||
translation(MemoryManager::get().getTranslationFromProcess(foreignAddrSpaceId))
|
||||
{}
|
||||
public:
|
||||
using Type = T;
|
||||
|
||||
MemoryBlockHostRam() = delete;
|
||||
|
||||
T& operator*() {
|
||||
return *reinterpret_cast<T*>(translation.getLocalAddr(0));
|
||||
}
|
||||
|
||||
T& operator [](int idx) {
|
||||
const size_t offset = sizeof(T) * idx;
|
||||
return *reinterpret_cast<T*>(translation.getLocalAddr(offset));
|
||||
}
|
||||
|
||||
T* operator &() const {
|
||||
return reinterpret_cast<T*>(translation.getLocalAddr(0));
|
||||
}
|
||||
|
||||
private:
|
||||
// addr needed for freeing later
|
||||
void* addr;
|
||||
|
||||
// cached memory translation for fast access
|
||||
MemoryTranslation translation;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
static MemoryBlockHostRam<T>
|
||||
allocate(size_t num)
|
||||
{
|
||||
/* Align to next bigger page size chunk */
|
||||
size_t length = num * sizeof(T);
|
||||
if (length & size_t(0xFFF)) {
|
||||
length += size_t(0x1000);
|
||||
length &= size_t(~0xFFF);
|
||||
}
|
||||
|
||||
void* const addr = HostRam::allocate(length);
|
||||
if(addr == nullptr) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
auto& mm = MemoryManager::get();
|
||||
|
||||
// assemble name for this block
|
||||
std::stringstream name;
|
||||
name << std::showbase << std::hex << reinterpret_cast<uintptr_t>(addr);
|
||||
|
||||
auto blockAddrSpaceId = mm.getProcessAddressSpaceMemoryBlock(name.str());
|
||||
|
||||
// create mapping from VA space of process to this new block
|
||||
mm.createMapping(reinterpret_cast<uintptr_t>(addr), 0, length,
|
||||
"VA",
|
||||
mm.getProcessAddressSpace(),
|
||||
blockAddrSpaceId);
|
||||
|
||||
// create object and corresponding address space in memory manager
|
||||
return MemoryBlockHostRam<T>(addr, length, blockAddrSpaceId);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static inline bool
|
||||
free(const MemoryBlockHostRam<T>& block)
|
||||
{
|
||||
// TODO: remove address space from memory manager
|
||||
// TODO: how to prevent use after free?
|
||||
return HostRam::free(block.addr, block.size);
|
||||
}
|
||||
|
||||
private:
|
||||
static void*
|
||||
allocate(size_t length, int flags = 0);
|
||||
|
||||
static bool
|
||||
free(void*, size_t length);
|
||||
};
|
||||
|
||||
} // namespace villas
|
|
@ -3,6 +3,7 @@
|
|||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "log.hpp"
|
||||
#include "directed_graph.hpp"
|
||||
|
@ -34,6 +35,10 @@ public:
|
|||
uintptr_t
|
||||
getForeignAddr(uintptr_t addrInLocalAddrSpace) const;
|
||||
|
||||
size_t
|
||||
getSize() const
|
||||
{ return size; }
|
||||
|
||||
friend std::ostream&
|
||||
operator<< (std::ostream& stream, const MemoryTranslation& translation)
|
||||
{
|
||||
|
@ -87,9 +92,9 @@ private:
|
|||
* the destination address space, where the mapping points to. Often, #dest
|
||||
* will be zero for mappings to hardware, but consider the example when
|
||||
* mapping FPGA to application memory:
|
||||
* The application allocates a block 1kB at address
|
||||
* 0x843001000 in its address space. The mapping would then have a #dest
|
||||
* address of 0x843001000 and a #size of 1024.
|
||||
* The application allocates a block 1kB at address 0x843001000 in its
|
||||
* address space. The mapping would then have a #dest address of 0x843001000
|
||||
* and a #size of 1024.
|
||||
*/
|
||||
class Mapping : public graph::Edge {
|
||||
public:
|
||||
|
@ -147,6 +152,11 @@ public:
|
|||
getProcessAddressSpace()
|
||||
{ return getOrCreateAddressSpace("villas-fpga"); }
|
||||
|
||||
AddressSpaceId
|
||||
getProcessAddressSpaceMemoryBlock(const std::string& memoryBlock)
|
||||
{ return getOrCreateAddressSpace(getSlaveAddrSpaceName("villas-fpga", memoryBlock)); }
|
||||
|
||||
|
||||
AddressSpaceId
|
||||
getOrCreateAddressSpace(std::string name);
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ set(SOURCES
|
|||
plugin.cpp
|
||||
utils.cpp
|
||||
memory_manager.cpp
|
||||
memory.cpp
|
||||
)
|
||||
|
||||
include(FindPkgConfig)
|
||||
|
|
|
@ -109,13 +109,33 @@ PCIeCardFactory::make(json_t *json, struct pci* pci, std::shared_ptr<VfioContain
|
|||
return cards;
|
||||
}
|
||||
|
||||
fpga::PCIeCard*
|
||||
fpga::PCIeCardFactory::create()
|
||||
|
||||
PCIeCard*
|
||||
PCIeCardFactory::create()
|
||||
{
|
||||
return new fpga::PCIeCard;
|
||||
}
|
||||
|
||||
|
||||
PCIeCard::~PCIeCard()
|
||||
{
|
||||
auto& mm = MemoryManager::get();
|
||||
|
||||
// unmap all memory blocks
|
||||
for(auto& mappedMemoryBlock : memoryBlocksMapped) {
|
||||
auto translation = mm.getTranslation(addrSpaceIdDeviceToHost,
|
||||
mappedMemoryBlock);
|
||||
|
||||
const uintptr_t iova = translation.getLocalAddr(0);
|
||||
const size_t size = translation.getSize();
|
||||
|
||||
logger->debug("Unmap block {} at IOVA {:#x} of size {:#x}",
|
||||
mappedMemoryBlock, iova, size);
|
||||
vfioContainer->memoryUnmap(iova, size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ip::IpCore*
|
||||
PCIeCard::lookupIp(const std::string& name) const
|
||||
{
|
||||
|
@ -124,9 +144,11 @@ PCIeCard::lookupIp(const std::string& name) const
|
|||
return ip.get();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
ip::IpCore*
|
||||
PCIeCard::lookupIp(const Vlnv& vlnv) const
|
||||
{
|
||||
|
@ -135,17 +157,58 @@ PCIeCard::lookupIp(const Vlnv& vlnv) const
|
|||
return ip.get();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
PCIeCard::mapMemoryBlock(const MemoryBlock& block)
|
||||
{
|
||||
auto& mm = MemoryManager::get();
|
||||
const auto& addrSpaceId = block.getAddrSpaceId();
|
||||
|
||||
if(memoryBlocksMapped.find(addrSpaceId) != memoryBlocksMapped.end()) {
|
||||
// block already mapped
|
||||
return true;
|
||||
} else {
|
||||
logger->debug("Create VFIO mapping for {}", addrSpaceId);
|
||||
}
|
||||
|
||||
|
||||
auto translationFromProcess = mm.getTranslationFromProcess(addrSpaceId);
|
||||
uintptr_t processBaseAddr = translationFromProcess.getLocalAddr(0);
|
||||
uintptr_t iovaAddr = vfioContainer->memoryMap(processBaseAddr,
|
||||
UINTPTR_MAX,
|
||||
block.getSize());
|
||||
|
||||
if(iovaAddr == UINTPTR_MAX) {
|
||||
logger->error("Cannot map memory at {:#x} of size {:#x}",
|
||||
processBaseAddr, block.getSize());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
mm.createMapping(iovaAddr, 0, block.getSize(),
|
||||
"vfio",
|
||||
this->addrSpaceIdDeviceToHost,
|
||||
addrSpaceId);
|
||||
|
||||
// remember that this block has already been mapped for later
|
||||
memoryBlocksMapped.insert(addrSpaceId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
fpga::PCIeCard::init()
|
||||
{
|
||||
int ret;
|
||||
struct pci_device *pdev;
|
||||
|
||||
auto logger = getLogger();
|
||||
auto& mm = MemoryManager::get();
|
||||
logger = getLogger();
|
||||
|
||||
logger->info("Initializing FPGA card {}", name);
|
||||
|
||||
|
@ -181,17 +244,18 @@ fpga::PCIeCard::init()
|
|||
/* Link mapped BAR0 to global memory graph */
|
||||
|
||||
// get the address space of the current application
|
||||
auto villasAddrSpace = MemoryManager::get().getProcessAddressSpace();
|
||||
const auto villasAddrSpace = mm.getProcessAddressSpace();
|
||||
|
||||
// get the address space for the PCIe proxy we use with VFIO
|
||||
const auto cardPCIeAddrSpaceName = mm.getMasterAddrSpaceName(name, "PCIe");
|
||||
|
||||
// create a new address space for this FPGA card
|
||||
this->addrSpaceId = MemoryManager::get().getOrCreateAddressSpace(name);
|
||||
|
||||
addrSpaceIdHostToDevice = mm.getOrCreateAddressSpace(cardPCIeAddrSpaceName);
|
||||
|
||||
// create a mapping from our address space to the FPGA card via vfio
|
||||
MemoryManager::get().createMapping(reinterpret_cast<uintptr_t>(bar0_mapped),
|
||||
mm.createMapping(reinterpret_cast<uintptr_t>(bar0_mapped),
|
||||
0, bar0_size, "VFIO_map",
|
||||
villasAddrSpace, this->addrSpaceId);
|
||||
|
||||
villasAddrSpace, addrSpaceIdHostToDevice);
|
||||
|
||||
|
||||
/* Reset system? */
|
||||
|
|
|
@ -38,6 +38,8 @@ static AxiPciExpressBridgeFactory factory;
|
|||
bool
|
||||
AxiPciExpressBridge::init()
|
||||
{
|
||||
auto& mm = MemoryManager::get();
|
||||
|
||||
// Throw an exception if the is no bus master interface and thus no
|
||||
// address space we can use for translation -> error
|
||||
const MemoryManager::AddressSpaceId myAddrSpaceid =
|
||||
|
@ -47,7 +49,19 @@ AxiPciExpressBridge::init()
|
|||
// point to all other IPs in the FPGA, because Vivado will generate a
|
||||
// memory view for this bridge that can see all others.
|
||||
MemoryManager::get().createMapping(0x00, 0x00, SIZE_MAX, "PCIeBridge",
|
||||
card->addrSpaceId, myAddrSpaceid);
|
||||
card->addrSpaceIdHostToDevice, myAddrSpaceid);
|
||||
|
||||
|
||||
/* Make PCIe (IOVA) address space available to FPGA via BAR0 */
|
||||
|
||||
// IPs that can access this address space will know it via their memory view
|
||||
const auto addrSpaceNameDeviceToHost =
|
||||
mm.getSlaveAddrSpaceName(getInstanceName(), pcieMemory);
|
||||
|
||||
// save ID in card so we can create mappings later when needed (e.g. when
|
||||
// allocating DMA memory in host RAM)
|
||||
card->addrSpaceIdDeviceToHost =
|
||||
mm.getOrCreateAddressSpace(addrSpaceNameDeviceToHost);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
24
fpga/lib/memory.cpp
Normal file
24
fpga/lib/memory.cpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "memory.hpp"
|
||||
|
||||
namespace villas {
|
||||
|
||||
bool
|
||||
HostRam::free(void* addr, size_t length)
|
||||
{
|
||||
return munmap(addr, length) == 0;
|
||||
}
|
||||
|
||||
|
||||
void*
|
||||
HostRam::allocate(size_t length, int flags)
|
||||
{
|
||||
const int mmap_flags = flags | MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT;
|
||||
const int mmap_protection = PROT_READ | PROT_WRITE;
|
||||
|
||||
return mmap(nullptr, length, mmap_protection, mmap_flags, 0, 0);
|
||||
}
|
||||
|
||||
} // namespace villas
|
Loading…
Add table
Reference in a new issue