mirror of
https://git.rwth-aachen.de/acs/public/villas/node/
synced 2025-03-09 00:00:00 +01:00
Merge branch 'feature/blockram' into 'develop'
BRAM support + MemoryAllocator rework See merge request acs/public/villas/VILLASfpga-code!7
This commit is contained in:
commit
bd6d337442
14 changed files with 474 additions and 109 deletions
|
@ -127,8 +127,11 @@ public:
|
|||
virtual void dump();
|
||||
|
||||
protected:
|
||||
/// Key-type for accessing maps addressTranslations and slaveAddressSpaces
|
||||
using MemoryBlockName = std::string;
|
||||
|
||||
/// Each IP can declare via this function which memory blocks it requires
|
||||
virtual std::list<std::string>
|
||||
virtual std::list<MemoryBlockName>
|
||||
getMemoryBlocks() const
|
||||
{ return {}; }
|
||||
|
||||
|
@ -177,11 +180,15 @@ public:
|
|||
|
||||
protected:
|
||||
uintptr_t
|
||||
getBaseAddr(const std::string& block) const
|
||||
getBaseAddr(const MemoryBlockName& block) const
|
||||
{ return getLocalAddr(block, 0); }
|
||||
|
||||
uintptr_t
|
||||
getLocalAddr(const std::string& block, uintptr_t address) const;
|
||||
getLocalAddr(const MemoryBlockName& block, uintptr_t address) const;
|
||||
|
||||
MemoryManager::AddressSpaceId
|
||||
getAddressSpaceId(const MemoryBlockName& block) const
|
||||
{ return slaveAddressSpaces.at(block); }
|
||||
|
||||
InterruptController*
|
||||
getInterruptController(const std::string& interruptName) const;
|
||||
|
@ -206,7 +213,10 @@ protected:
|
|||
std::map<std::string, IrqPort> irqs;
|
||||
|
||||
/// Cached translations from the process address space to each memory block
|
||||
std::map<std::string, MemoryTranslation> addressTranslations;
|
||||
std::map<MemoryBlockName, MemoryTranslation> addressTranslations;
|
||||
|
||||
/// Lookup for IP's slave address spaces (= memory blocks)
|
||||
std::map<MemoryBlockName, MemoryManager::AddressSpaceId> slaveAddressSpaces;
|
||||
|
||||
/// AXI bus master interfaces to access memory somewhere
|
||||
std::map<std::string, MemoryManager::AddressSpaceId> busMasterInterfaces;
|
||||
|
|
87
fpga/include/villas/fpga/ips/bram.hpp
Normal file
87
fpga/include/villas/fpga/ips/bram.hpp
Normal file
|
@ -0,0 +1,87 @@
|
|||
/** Block-Raam related helper functions
|
||||
* *
|
||||
* @author Daniel Krebs <github@daniel-krebs.net>
|
||||
* @copyright 2018, Daniel Krebs
|
||||
* @license GNU General Public License (version 3)
|
||||
*
|
||||
* VILLASfpga
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
/** @addtogroup fpga VILLASfpga
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "memory.hpp"
|
||||
#include "fpga/ip.hpp"
|
||||
|
||||
namespace villas {
|
||||
namespace fpga {
|
||||
namespace ip {
|
||||
|
||||
|
||||
class Bram : public IpCore
|
||||
{
|
||||
friend class BramFactory;
|
||||
public:
|
||||
|
||||
bool init();
|
||||
|
||||
LinearAllocator&
|
||||
getAllocator()
|
||||
{ return *allocator; }
|
||||
|
||||
private:
|
||||
static constexpr const char* memoryBlock = "Mem0";
|
||||
std::list<MemoryBlockName> getMemoryBlocks() const
|
||||
{ return { memoryBlock }; }
|
||||
|
||||
size_t size;
|
||||
std::unique_ptr<LinearAllocator> allocator;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class BramFactory : public IpCoreFactory {
|
||||
public:
|
||||
|
||||
BramFactory() :
|
||||
IpCoreFactory(getName())
|
||||
{}
|
||||
|
||||
bool configureJson(IpCore& ip, json_t *json_ip);
|
||||
|
||||
IpCore* create()
|
||||
{ return new Bram; }
|
||||
|
||||
std::string
|
||||
getName() const
|
||||
{ return "Bram"; }
|
||||
|
||||
std::string
|
||||
getDescription() const
|
||||
{ return "Block RAM"; }
|
||||
|
||||
Vlnv getCompatibleVlnv() const
|
||||
{ return {"xilinx.com:ip:axi_bram_ctrl:"}; }
|
||||
};
|
||||
|
||||
} // namespace ip
|
||||
} // namespace fpga
|
||||
} // namespace villas
|
||||
|
||||
/** @} */
|
|
@ -84,7 +84,7 @@ private:
|
|||
// optional Scatter-Gather interface to access descriptors
|
||||
static constexpr char sgInterface[] = "M_AXI_SG";
|
||||
|
||||
std::list<std::string> getMemoryBlocks() const
|
||||
std::list<MemoryBlockName> getMemoryBlocks() const
|
||||
{ return { registerMemory }; }
|
||||
|
||||
XAxiDma xDma;
|
||||
|
|
|
@ -54,7 +54,7 @@ private:
|
|||
static constexpr char axi4Memory[] = "Mem1";
|
||||
static constexpr char irqName[] = "interrupt";
|
||||
|
||||
std::list<std::string> getMemoryBlocks() const
|
||||
std::list<MemoryBlockName> getMemoryBlocks() const
|
||||
{ return { registerMemory, axi4Memory }; }
|
||||
|
||||
XLlFifo xFifo;
|
||||
|
|
|
@ -64,7 +64,7 @@ private:
|
|||
|
||||
static constexpr char registerMemory[] = "Reg";
|
||||
|
||||
std::list<std::string> getMemoryBlocks() const
|
||||
std::list<MemoryBlockName> getMemoryBlocks() const
|
||||
{ return { registerMemory }; }
|
||||
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ private:
|
|||
|
||||
static constexpr char registerMemory[] = "Reg";
|
||||
|
||||
std::list<std::string> getMemoryBlocks() const
|
||||
std::list<MemoryBlockName> getMemoryBlocks() const
|
||||
{ return { registerMemory }; }
|
||||
|
||||
struct Path {
|
||||
|
|
|
@ -63,7 +63,7 @@ public:
|
|||
|
||||
private:
|
||||
|
||||
std::list<std::string> getMemoryBlocks() const
|
||||
std::list<MemoryBlockName> getMemoryBlocks() const
|
||||
{ return { registerMemory }; }
|
||||
|
||||
static constexpr char irqName[] = "generateout0";
|
||||
|
|
|
@ -8,115 +8,221 @@
|
|||
|
||||
namespace villas {
|
||||
|
||||
/**
|
||||
* @brief Basic memory block backed by an address space in the memory graph
|
||||
*
|
||||
* This is a generic representation of a chunk of memory in the system. It can
|
||||
* reside anywhere and represent different types of memory.
|
||||
*/
|
||||
class MemoryBlock {
|
||||
protected:
|
||||
MemoryBlock(MemoryManager::AddressSpaceId addrSpaceId, size_t size) :
|
||||
addrSpaceId(addrSpaceId), size(size) {}
|
||||
|
||||
public:
|
||||
using deallocator_fn = std::function<void(MemoryBlock*)>;
|
||||
|
||||
MemoryBlock(size_t offset, size_t size, MemoryManager::AddressSpaceId addrSpaceId) :
|
||||
offset(offset), size(size), addrSpaceId(addrSpaceId) {}
|
||||
|
||||
MemoryManager::AddressSpaceId getAddrSpaceId() const
|
||||
{ return addrSpaceId; }
|
||||
|
||||
size_t getSize() const
|
||||
{ return size; }
|
||||
|
||||
private:
|
||||
MemoryManager::AddressSpaceId addrSpaceId;
|
||||
size_t size;
|
||||
size_t getOffset() const
|
||||
{ return offset; }
|
||||
|
||||
protected:
|
||||
size_t offset; ///< Offset (or address) inside address space
|
||||
size_t size; ///< Size in bytes of this block
|
||||
MemoryManager::AddressSpaceId addrSpaceId; ///< Identifier in memory graph
|
||||
};
|
||||
|
||||
|
||||
class MemoryAllocator {
|
||||
};
|
||||
|
||||
|
||||
class HostRam : public MemoryAllocator {
|
||||
/**
|
||||
* @brief Wrapper for a MemoryBlock to access the underlying memory directly
|
||||
*
|
||||
* The underlying memory block has to be accessible for the current process,
|
||||
* that means it has to be mapped accordingly and registered to the global
|
||||
* memory graph.
|
||||
* Furthermore, this wrapper can be owning the memory block when initialized
|
||||
* with a moved unique pointer. Otherwise, it just stores a reference to the
|
||||
* memory block and it's the users responsibility to take care that the memory
|
||||
* block is valid.
|
||||
*/
|
||||
template<typename T>
|
||||
class MemoryAccessor {
|
||||
public:
|
||||
using Type = T;
|
||||
|
||||
// take ownership of the MemoryBlock
|
||||
MemoryAccessor(std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn> mem) :
|
||||
translation(MemoryManager::get().getTranslationFromProcess(mem->getAddrSpaceId())),
|
||||
memoryBlock(std::move(mem)) {}
|
||||
|
||||
// just act as an accessor, do not take ownership of MemoryBlock
|
||||
MemoryAccessor(const MemoryBlock& mem) :
|
||||
translation(MemoryManager::get().getTranslationFromProcess(mem.getAddrSpaceId())) {}
|
||||
|
||||
|
||||
T& operator*() const {
|
||||
return *reinterpret_cast<T*>(translation.getLocalAddr(0));
|
||||
}
|
||||
|
||||
T& operator[](size_t idx) const {
|
||||
const size_t offset = sizeof(T) * idx;
|
||||
return *reinterpret_cast<T*>(translation.getLocalAddr(offset));
|
||||
}
|
||||
|
||||
T* operator&() const {
|
||||
return reinterpret_cast<T*>(translation.getLocalAddr(0));
|
||||
}
|
||||
|
||||
T* operator->() const {
|
||||
return reinterpret_cast<T*>(translation.getLocalAddr(0));
|
||||
}
|
||||
|
||||
const MemoryBlock&
|
||||
getMemoryBlock() const
|
||||
{ if(not memoryBlock) throw std::bad_alloc(); else return *memoryBlock; }
|
||||
|
||||
private:
|
||||
/// cached memory translation for fast access
|
||||
MemoryTranslation translation;
|
||||
|
||||
/// take the unique pointer in case user wants this class to have ownership
|
||||
std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn> memoryBlock;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Base memory allocator
|
||||
*
|
||||
* Note the usage of CRTP idiom here to access methods of derived allocators.
|
||||
* The concept is explained here at [1].
|
||||
*
|
||||
* [1] https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
|
||||
*/
|
||||
template<typename DerivedAllocator>
|
||||
class BaseAllocator {
|
||||
public:
|
||||
/// memoryAddrSpaceId: memory that is managed by this allocator
|
||||
BaseAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId) :
|
||||
memoryAddrSpaceId(memoryAddrSpaceId)
|
||||
{
|
||||
// CRTP
|
||||
derivedAlloc = static_cast<DerivedAllocator*>(this);
|
||||
logger = loggerGetOrCreate(derivedAlloc->getName());
|
||||
|
||||
// default deallocation callback
|
||||
free = [&](MemoryBlock* mem) {
|
||||
logger->warn("no free callback defined for addr space {}, not freeing",
|
||||
mem->getAddrSpaceId());
|
||||
};
|
||||
}
|
||||
|
||||
virtual std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
|
||||
allocateBlock(size_t size) = 0;
|
||||
|
||||
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>
|
||||
MemoryAccessor<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);
|
||||
const size_t size = num * sizeof(T);
|
||||
auto mem = allocateBlock(size);
|
||||
return MemoryAccessor<T>(std::move(mem));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static inline bool
|
||||
free(const MemoryBlockHostRam<T>& block)
|
||||
protected:
|
||||
void insertMemoryBlock(const MemoryBlock& mem)
|
||||
{
|
||||
// TODO: remove address space from memory manager
|
||||
// TODO: how to prevent use after free?
|
||||
return HostRam::free(block.addr, block.size);
|
||||
auto& mm = MemoryManager::get();
|
||||
mm.createMapping(mem.getOffset(), 0, mem.getSize(),
|
||||
derivedAlloc->getName(),
|
||||
memoryAddrSpaceId,
|
||||
mem.getAddrSpaceId());
|
||||
}
|
||||
|
||||
void removeMemoryBlock(const MemoryBlock& mem)
|
||||
{
|
||||
// this will also remove any mapping to and from the memory block
|
||||
auto& mm = MemoryManager::get();
|
||||
mm.removeAddressSpace(mem.getAddrSpaceId());
|
||||
}
|
||||
|
||||
MemoryManager::AddressSpaceId getAddrSpaceId() const
|
||||
{ return memoryAddrSpaceId; }
|
||||
|
||||
protected:
|
||||
MemoryBlock::deallocator_fn free;
|
||||
SpdLogger logger;
|
||||
|
||||
private:
|
||||
static void*
|
||||
allocate(size_t length, int flags = 0);
|
||||
MemoryManager::AddressSpaceId memoryAddrSpaceId;
|
||||
DerivedAllocator* derivedAlloc;
|
||||
};
|
||||
|
||||
static bool
|
||||
free(void*, size_t length);
|
||||
|
||||
/**
|
||||
* @brief Linear memory allocator
|
||||
*
|
||||
* This is the simplest kind of allocator. The idea is to keep a pointer at the
|
||||
* first memory address of your memory chunk and move it every time an
|
||||
* allocation is done. Due to its simplicity, this allocator doesn't allow
|
||||
* specific positions of memory to be freed. Usually, all memory is freed
|
||||
* together.
|
||||
*/
|
||||
class LinearAllocator : public BaseAllocator<LinearAllocator> {
|
||||
public:
|
||||
LinearAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId,
|
||||
size_t memorySize,
|
||||
size_t internalOffset = 0);
|
||||
|
||||
size_t getAvailableMemory() const
|
||||
{ return memorySize - nextFreeAddress; }
|
||||
|
||||
std::string getName() const;
|
||||
|
||||
std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
|
||||
allocateBlock(size_t size);
|
||||
|
||||
private:
|
||||
static constexpr size_t alignBytes = sizeof(uintptr_t);
|
||||
static constexpr size_t alignMask = alignBytes - 1;
|
||||
|
||||
size_t getAlignmentPadding(uintptr_t addr) const
|
||||
{ return (alignBytes - (addr & alignMask)) & alignMask; }
|
||||
|
||||
private:
|
||||
size_t nextFreeAddress; ///< next chunk will be allocated here
|
||||
size_t memorySize; ///< total size of managed memory
|
||||
size_t internalOffset; ///< offset in address space (usually 0)
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Wrapper around mmap() to create villas memory blocks
|
||||
*
|
||||
* This class simply wraps around mmap() and munmap() to allocate memory in the
|
||||
* host memory via the OS.
|
||||
*/
|
||||
class HostRam {
|
||||
public:
|
||||
class HostRamAllocator : public BaseAllocator<HostRamAllocator> {
|
||||
public:
|
||||
HostRamAllocator();
|
||||
|
||||
std::string getName() const
|
||||
{ return "HostRamAlloc"; }
|
||||
|
||||
std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
|
||||
allocateBlock(size_t size);
|
||||
};
|
||||
|
||||
static HostRamAllocator&
|
||||
getAllocator()
|
||||
{ return allocator; }
|
||||
|
||||
private:
|
||||
static HostRamAllocator allocator;
|
||||
};
|
||||
|
||||
} // namespace villas
|
||||
|
|
|
@ -77,7 +77,7 @@ private:
|
|||
|
||||
// ... and no copying or assigning
|
||||
MemoryManager(const MemoryManager&) = delete;
|
||||
MemoryManager& operator=(const MemoryManager&) = delete ;
|
||||
MemoryManager& operator=(const MemoryManager&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Custom edge in memory graph representing a memory mapping
|
||||
|
@ -153,13 +153,17 @@ public:
|
|||
{ return getOrCreateAddressSpace("villas-fpga"); }
|
||||
|
||||
AddressSpaceId
|
||||
getProcessAddressSpaceMemoryBlock(const std::string& memoryBlock)
|
||||
getProcessAddressSpaceMemoryBlock(const std::string& memoryBlock)
|
||||
{ return getOrCreateAddressSpace(getSlaveAddrSpaceName("villas-fpga", memoryBlock)); }
|
||||
|
||||
|
||||
AddressSpaceId
|
||||
getOrCreateAddressSpace(std::string name);
|
||||
|
||||
void
|
||||
removeAddressSpace(AddressSpaceId addrSpaceId)
|
||||
{ memoryGraph.removeVertex(addrSpaceId); }
|
||||
|
||||
/// Create a default mapping
|
||||
MappingId
|
||||
createMapping(uintptr_t src, uintptr_t dest, size_t size,
|
||||
|
|
|
@ -10,6 +10,7 @@ set(SOURCES
|
|||
ips/intc.cpp
|
||||
ips/pcie.cpp
|
||||
ips/dma.cpp
|
||||
ips/bram.cpp
|
||||
|
||||
kernel/kernel.c
|
||||
kernel/pci.c
|
||||
|
|
|
@ -281,6 +281,9 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips)
|
|||
const auto addrSpaceId =
|
||||
MemoryManager::get().findAddressSpace(addrSpaceName);
|
||||
|
||||
// ... and save it in IP
|
||||
ip->slaveAddressSpaces.emplace(memoryBlock, addrSpaceId);
|
||||
|
||||
// get the translation to the address space
|
||||
const auto& translation =
|
||||
MemoryManager::get().getTranslationFromProcess(addrSpaceId);
|
||||
|
@ -341,7 +344,7 @@ IpCoreFactory::lookup(const Vlnv &vlnv)
|
|||
|
||||
|
||||
uintptr_t
|
||||
IpCore::getLocalAddr(const std::string& block, uintptr_t address) const
|
||||
IpCore::getLocalAddr(const MemoryBlockName& block, uintptr_t address) const
|
||||
{
|
||||
// throws exception if block not present
|
||||
auto& translation = addressTranslations.at(block);
|
||||
|
|
32
fpga/lib/ips/bram.cpp
Normal file
32
fpga/lib/ips/bram.cpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
#include "fpga/ips/bram.hpp"
|
||||
|
||||
namespace villas {
|
||||
namespace fpga {
|
||||
namespace ip {
|
||||
|
||||
static BramFactory factory;
|
||||
|
||||
bool
|
||||
BramFactory::configureJson(IpCore& ip, json_t* json_ip)
|
||||
{
|
||||
auto& bram = reinterpret_cast<Bram&>(ip);
|
||||
|
||||
if(json_unpack(json_ip, "{ s: i }", "size", &bram.size) != 0) {
|
||||
getLogger()->error("Cannot parse 'size'");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bram::init()
|
||||
{
|
||||
allocator = std::make_unique<LinearAllocator>
|
||||
(getAddressSpaceId(memoryBlock), this->size, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ip
|
||||
} // namespace fpga
|
||||
} // namespace villas
|
|
@ -5,20 +5,135 @@
|
|||
|
||||
namespace villas {
|
||||
|
||||
bool
|
||||
HostRam::free(void* addr, size_t length)
|
||||
std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
|
||||
HostRam::HostRamAllocator::allocateBlock(size_t size)
|
||||
{
|
||||
return munmap(addr, length) == 0;
|
||||
/* Align to next bigger page size chunk */
|
||||
if (size & size_t(0xFFF)) {
|
||||
size += size_t(0x1000);
|
||||
size &= size_t(~0xFFF);
|
||||
}
|
||||
|
||||
const int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT;
|
||||
const int mmap_protection = PROT_READ | PROT_WRITE;
|
||||
|
||||
const void* addr = mmap(nullptr, size, mmap_protection, mmap_flags, 0, 0);
|
||||
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());
|
||||
|
||||
const auto localAddr = reinterpret_cast<uintptr_t>(addr);
|
||||
std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
|
||||
mem(new MemoryBlock(localAddr, size, blockAddrSpaceId), this->free);
|
||||
|
||||
insertMemoryBlock(*mem);
|
||||
|
||||
return mem;
|
||||
}
|
||||
|
||||
|
||||
void*
|
||||
HostRam::allocate(size_t length, int flags)
|
||||
LinearAllocator::LinearAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId,
|
||||
size_t memorySize,
|
||||
size_t internalOffset) :
|
||||
BaseAllocator(memoryAddrSpaceId),
|
||||
nextFreeAddress(0),
|
||||
memorySize(memorySize),
|
||||
internalOffset(internalOffset)
|
||||
{
|
||||
const int mmap_flags = flags | MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT;
|
||||
const int mmap_protection = PROT_READ | PROT_WRITE;
|
||||
// make sure to start at aligned offset, reduce size in case we need padding
|
||||
if(const size_t paddingBytes = getAlignmentPadding(internalOffset)) {
|
||||
assert(paddingBytes < memorySize);
|
||||
|
||||
return mmap(nullptr, length, mmap_protection, mmap_flags, 0, 0);
|
||||
internalOffset += paddingBytes;
|
||||
memorySize -= paddingBytes;
|
||||
}
|
||||
|
||||
// deallocation callback
|
||||
free = [&](MemoryBlock* mem) {
|
||||
logger->debug("freeing {:#x} bytes at local addr {:#x} (addr space {})",
|
||||
mem->getSize(), mem->getOffset(), mem->getAddrSpaceId());
|
||||
logger->warn("free() not implemented");
|
||||
logger->debug("available memory: {:#x} bytes", getAvailableMemory());
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
std::string
|
||||
LinearAllocator::getName() const
|
||||
{
|
||||
std::stringstream name;
|
||||
name << "LinearAlloc" << getAddrSpaceId()
|
||||
<< "@0x" << std::hex << internalOffset;
|
||||
return name.str();
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
|
||||
LinearAllocator::allocateBlock(size_t size)
|
||||
{
|
||||
if(size > getAvailableMemory()) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
// assign address
|
||||
const uintptr_t localAddr = nextFreeAddress + internalOffset;
|
||||
|
||||
// reserve memory
|
||||
nextFreeAddress += size;
|
||||
|
||||
// make sure it is aligned
|
||||
if(const size_t paddingBytes = getAlignmentPadding(nextFreeAddress)) {
|
||||
nextFreeAddress += paddingBytes;
|
||||
|
||||
// if next free address is outside this block due to padding, cap it
|
||||
nextFreeAddress = std::min(nextFreeAddress, memorySize);
|
||||
}
|
||||
|
||||
|
||||
auto& mm = MemoryManager::get();
|
||||
|
||||
// assemble name for this block
|
||||
std::stringstream blockName;
|
||||
blockName << std::showbase << std::hex << localAddr;
|
||||
|
||||
// create address space
|
||||
auto addrSpaceName = mm.getSlaveAddrSpaceName(getName(), blockName.str());
|
||||
auto addrSpaceId = mm.getOrCreateAddressSpace(addrSpaceName);
|
||||
|
||||
logger->debug("Allocated {:#x} bytes for {}, {:#x} bytes remaining",
|
||||
size, addrSpaceId, getAvailableMemory());
|
||||
|
||||
std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
|
||||
mem(new MemoryBlock(localAddr, size, addrSpaceId), this->free);
|
||||
|
||||
// mount block into the memory graph
|
||||
insertMemoryBlock(*mem);
|
||||
|
||||
|
||||
return mem;
|
||||
}
|
||||
|
||||
|
||||
HostRam::HostRamAllocator
|
||||
HostRam::allocator;
|
||||
|
||||
HostRam::HostRamAllocator::HostRamAllocator() :
|
||||
BaseAllocator(MemoryManager::get().getProcessAddressSpace())
|
||||
{
|
||||
free = [&](MemoryBlock* mem) {
|
||||
if(::munmap(reinterpret_cast<void*>(mem->getOffset()), mem->getSize()) != 0) {
|
||||
logger->warn("munmap() failed for {:#x} of size {:#x}",
|
||||
mem->getOffset(), mem->getSize());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace villas
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <villas/log.hpp>
|
||||
#include <villas/fpga/card.hpp>
|
||||
#include <villas/fpga/ips/dma.hpp>
|
||||
#include <villas/fpga/ips/bram.hpp>
|
||||
|
||||
#include <villas/utils.h>
|
||||
|
||||
|
@ -36,20 +37,26 @@ Test(fpga, dma, .description = "DMA")
|
|||
continue;
|
||||
}
|
||||
|
||||
// find a block RAM IP to write to
|
||||
auto bramIp = state.cards.front()->lookupIp(villas::fpga::Vlnv("xilinx.com:ip:axi_bram_ctrl:"));
|
||||
auto bram = reinterpret_cast<villas::fpga::ip::Bram*>(bramIp);
|
||||
cr_assert_not_null(bram, "Couldn't find BRAM");
|
||||
|
||||
// Simple DMA can only transfer up to 4 kb due to PCIe page size burst
|
||||
// limitation
|
||||
size_t len = 4 * (1 << 10);
|
||||
|
||||
/* Allocate memory to use with DMA */
|
||||
auto src = villas::HostRam::allocate<char>(len);
|
||||
auto dst = villas::HostRam::allocate<char>(len);
|
||||
auto src = villas::HostRam::getAllocator().allocate<char>(len);
|
||||
auto dst = bram->getAllocator().allocate<char>(len);
|
||||
|
||||
/* Get new random data */
|
||||
const size_t lenRandom = read_random(&src, len);
|
||||
cr_assert(len == lenRandom, "Failed to get random data");
|
||||
|
||||
/* Start transfer */
|
||||
cr_assert(dma.pingPong(src, dst, len), "DMA ping pong failed");
|
||||
cr_assert(dma.pingPong(src.getMemoryBlock(), dst.getMemoryBlock(), len),
|
||||
"DMA ping pong failed");
|
||||
|
||||
/* Compare data */
|
||||
cr_assert(memcmp(&src, &dst, len) == 0, "Data not equal");
|
||||
|
|
Loading…
Add table
Reference in a new issue