Program Listing for File dpdk_wrappers.h
↰ Return to documentation for file (utils/dpdk_wrappers.h
)
#pragma once
#include <expected.h>
#include <rte_branch_prediction.h>
#include <rte_eal.h>
#include <rte_errno.h>
#include <rte_lcore.h>
#include <rte_malloc.h>
#include <rte_mbuf.h>
#include <rte_mempool.h>
#include <rte_ring.h>
#include <spdlog/spdlog.h>
#include <algorithm>
#include <array>
#include <cassert>
#include <exception>
#include <iostream>
#include <iterator>
#include <optional>
#include <span>
struct EALGuard {
static tl::expected<EALGuard, int> init(int argc, char **argv) {
auto ret = rte_eal_init(argc, argv);
if (ret == -1)
return tl::unexpected{rte_errno};
return EALGuard{};
}
~EALGuard() {
if (_valid)
rte_eal_cleanup();
}
// This type is just a guard, copying is not allowed.
EALGuard(const EALGuard &) = delete;
EALGuard &operator=(const EALGuard &) = delete;
// Move constructor is required for init function.
EALGuard(EALGuard &&other) : _valid(true) {
other._valid = false;
}
EALGuard &operator=(EALGuard &&other) {
_valid = true;
other._valid = false;
return *this;
}
private:
EALGuard() { }
bool _valid = true;
};
template <typename T>
class RteAllocator {
public:
using value_type = T;
RteAllocator() = default;
T *allocate(std::size_t n) {
return static_cast<T *>(rte_malloc("RteAllocator", n * sizeof(T), alignof(T)));
}
void deallocate(T *p, std::size_t) noexcept {
rte_free(p);
}
};
template <typename Elem>
class RTEPktMbuf;
enum class MbufType {
Raw,
Pkt
};
template <typename Elem, MbufType type = MbufType::Raw>
class RTEMempool {
public:
static tl::expected<RTEMempool, int> init(const std::string &name, size_t num_elems,
size_t cache_size, size_t private_size, unsigned int flags,
unsigned int socket_id = rte_socket_id())
requires(type == MbufType::Raw)
{
auto ptr = rte_mempool_create(name.c_str(), num_elems, sizeof(Elem), cache_size,
private_size, nullptr, nullptr, nullptr, nullptr, socket_id, flags);
if (!ptr)
return tl::unexpected{rte_errno};
return RTEMempool{ptr};
}
static tl::expected<RTEMempool, int> init(const std::string &name, size_t pool_size,
size_t cache_size, size_t priv_size, int socket_id = rte_socket_id())
requires(type == MbufType::Pkt)
{
auto ptr = rte_pktmbuf_pool_create(name.c_str(), pool_size, cache_size, priv_size,
sizeof(Elem) + RTE_PKTMBUF_HEADROOM, socket_id);
if (!ptr)
return tl::unexpected{rte_errno};
return RTEMempool{ptr};
}
// Delete copy constructors.
RTEMempool(const RTEMempool &) = delete;
RTEMempool &operator=(const RTEMempool &) = delete;
RTEMempool(RTEMempool &&other) {
ptr_ = other.ptr_;
other.ptr_ = nullptr;
}
RTEMempool &operator=(RTEMempool &&other) {
std::swap(ptr_, other.ptr_);
return *this;
}
~RTEMempool() {
// Nothing to do if nullptr
if (ptr_ == nullptr)
return;
rte_mempool_free(ptr_);
ptr_ = nullptr;
}
size_t count() const {
return rte_mempool_in_use_count(ptr_);
}
size_t capacity() const {
return rte_mempool_avail_count(ptr_);
}
rte_mempool *get() {
return ptr_;
}
private:
RTEMempool(rte_mempool *ptr) : ptr_(ptr) { }
rte_mempool *ptr_;
};
template <typename Elem>
struct RTEMbuf : public rte_mbuf {
template <typename GetElem = Elem, size_t Off = 0>
requires(sizeof(GetElem) + Off <= sizeof(Elem))
GetElem &data() {
GetElem *elem = rte_pktmbuf_mtod_offset(this, GetElem *, Off);
return *elem;
}
template <typename GetElem = Elem, size_t Off = 0>
requires(sizeof(GetElem) + Off <= sizeof(Elem))
const GetElem &data() const {
GetElem *elem = rte_pktmbuf_mtod_offset(this, GetElem *, Off);
return *elem;
}
};
namespace _detail {
template <typename Elem, MbufType type>
struct MbufElementHelper;
template <typename Elem>
struct MbufElementHelper<Elem, MbufType::Pkt> {
MbufElementHelper() : ptr_(nullptr) { }
MbufElementHelper(RTEMbuf<Elem> *ptr, rte_mempool *) : ptr_(ptr) { }
RTEMbuf<Elem> *ptr_;
};
template <typename Elem>
struct MbufElementHelper<Elem, MbufType::Raw> {
MbufElementHelper() : ptr_(nullptr), mempool_(nullptr) { }
MbufElementHelper(Elem *ptr, rte_mempool *mempool) : ptr_(ptr), mempool_(mempool) { }
Elem *ptr_;
rte_mempool *mempool_;
};
} // namespace _detail
template <typename Elem, MbufType type = MbufType::Raw>
class RTEMbufElement : _detail::MbufElementHelper<Elem, type> {
public:
using value_type =
typename std::conditional<type == MbufType::Raw, Elem, RTEMbuf<Elem>>::type;
using pointer = value_type *;
using double_pointer = pointer *;
using reference = value_type &;
using const_reference = std::add_const_t<reference>;
static tl::expected<RTEMbufElement, int> init(RTEMempool<Elem, type> &mempool) {
pointer ptr;
int res = Alloc(mempool.get(), &ptr);
if (res)
return tl::unexpected(res);
RTEMbufElement ret{mempool.get(), ptr};
Elem &elem = ret.get_data();
new (&elem) Elem();
return ret;
}
static tl::expected<RTEMbufElement, int> init(RTEMempool<Elem, type> &mempool,
Elem &&elem) {
pointer ptr;
int res = Alloc(mempool.get(), &ptr);
if (res)
return tl::unexpected(res);
RTEMbufElement ret{mempool.get(), ptr};
Elem &new_elem = ret.get_data();
new (&new_elem) Elem(std::move(elem));
return ret;
}
~RTEMbufElement() {
if (this->ptr_) {
Elem &elem = get_data();
elem.~Elem();
}
Free();
}
// Disallow copying
RTEMbufElement(const RTEMbufElement &) = delete;
RTEMbufElement &operator=(const RTEMbufElement &) = delete;
RTEMbufElement(RTEMbufElement &&other) {
this->ptr_ = other.ptr_;
other.ptr_ = nullptr;
if constexpr (type == MbufType::Raw)
this->mempool_ = other.mempool_;
// Leave other mempool the same
}
RTEMbufElement &operator=(RTEMbufElement &&other) {
std::swap(this->ptr_, other.ptr_);
if constexpr (type == MbufType::Raw)
std::swap(this->mempool_, other.mempool_);
return *this;
}
// Check if RTEMbufElement contiains data
operator bool() const {
return static_cast<bool>(this->ptr_);
}
void release() {
this->ptr_ = nullptr;
}
reference get() {
return *this->ptr_;
}
const_reference get() const {
return *this->ptr_;
}
Elem &get_data()
requires(type == MbufType::Raw)
{
return *this->ptr_;
}
Elem &get_data()
requires(type == MbufType::Pkt)
{
return (*this->ptr_).data();
}
const Elem &get_data() const
requires(type == MbufType::Raw)
{
return *this->ptr_;
}
const Elem &get_data() const
requires(type == MbufType::Pkt)
{
return (*this->ptr_).data();
}
private:
using helper_struct = _detail::MbufElementHelper<Elem, type>;
template <typename U, size_t N, MbufType t>
friend class RTEMbufArray;
template <typename U, MbufType t>
friend class RTERing;
RTEMbufElement(rte_mempool *mempool, pointer ptr) : helper_struct{ptr, mempool} { }
static int Alloc(rte_mempool *mempool, double_pointer ptr)
requires(type == MbufType::Raw)
{
return rte_mempool_get(mempool, (void **) ptr);
}
static int Alloc(rte_mempool *mempool, double_pointer ptr)
requires(type == MbufType::Pkt)
{
*ptr = (RTEMbuf<Elem> *) rte_pktmbuf_alloc(mempool);
return *ptr ? 0 : ENOENT;
}
void Free()
requires(type == MbufType::Raw)
{
if (this->ptr_)
rte_mempool_put(this->mempool_, (void *) this->ptr_);
}
void Free()
requires(type == MbufType::Pkt)
{
rte_pktmbuf_free(static_cast<rte_mbuf *>(this->ptr_));
}
};
template <typename Elem, size_t N, MbufType type = MbufType::Raw>
class RTEMbufArray {
public:
using value_type =
typename std::conditional<type == MbufType::Raw, Elem, RTEMbuf<Elem>>::type;
using owning_value_type = RTEMbufElement<Elem, type>;
using pointer = value_type *;
using const_pointer = std::add_const_t<pointer>;
using double_pointer = pointer *;
using reference = value_type &;
using const_reference = std::add_const_t<reference>;
template <typename T>
requires(sizeof(T) >= sizeof(Elem))
static tl::expected<RTEMbufArray, int> init(RTEMempool<T, type> &mempool,
const size_t count, const Elem &value) {
std::array<pointer, N> tmp;
int res = AllocBulk(count, &tmp.front(), mempool.get());
if (res)
return tl::unexpected(res);
RTEMbufArray ret{mempool.get(),
std::span<pointer>(tmp.begin(), tmp.begin() + count)};
for (size_t i = 0; i < count; i++) {
Elem &elem = ret.get_data(i);
new (&elem) Elem{value};
}
return ret;
}
template <typename T>
requires(sizeof(T) >= sizeof(Elem))
static tl::expected<RTEMbufArray, int> init(RTEMempool<T, type> &mempool,
const size_t count) {
std::array<pointer, N> tmp;
int res = AllocBulk(count, &tmp.front(), mempool.get());
if (res)
return tl::unexpected(res);
RTEMbufArray ret{mempool.get(),
std::span<pointer>(tmp.begin(), tmp.begin() + count)};
for (size_t i = 0; i < ret.size_; i++) {
Elem &elem = ret.get_data(i);
new (&elem) Elem();
}
return ret;
}
template <typename T>
requires(sizeof(T) >= sizeof(Elem))
static tl::expected<RTEMbufArray, int> init(RTEMempool<T, type> &mempool) {
return RTEMbufArray{mempool.get(), {}};
}
// For interacting with C API's
RTEMbufArray(std::span<pointer> span)
requires(type == MbufType::Pkt)
: mempool_(nullptr) // Mempool cannot be passed in this constructor
{
assert(span.size() <= N);
std::copy(span.begin(), span.end(), ptrs.begin());
size_ = span.size();
}
~RTEMbufArray() {
for (size_t i = 0; i < size_; i++) {
Elem &elem = get_data(i);
elem.~Elem();
}
FreeAll();
std::fill(ptrs.begin(), ptrs.end(), nullptr);
size_ = 0;
}
// USE WITH CAUTION -- releases ownership of all elements
void release() {
size_ = 0;
std::fill(ptrs.begin(), ptrs.end(), nullptr);
}
// Disallow copying
RTEMbufArray(const RTEMbufArray &) = delete;
RTEMbufArray &operator=(const RTEMbufArray &) = delete;
RTEMbufArray(RTEMbufArray &&other) : ptrs() {
size_ = other.size_;
mempool_ = other.mempool_;
other.size_ = 0;
// Leave other mempool the same
std::copy(other.ptrs.begin(), other.ptrs.end(), ptrs.begin());
std::fill(other.ptrs.begin(), other.ptrs.end(), nullptr);
}
RTEMbufArray &operator=(RTEMbufArray &&other) {
if constexpr (type == MbufType::Raw)
assert(mempool_ == other.mempool_);
std::swap(ptrs, other.ptrs);
std::swap(size_, other.size_);
return *this;
}
[[nodiscard]] RTEMbufArray insert(RTEMbufArray &&other) {
if constexpr (type == MbufType::Raw)
assert(mempool_ == other.mempool_);
const size_t capacity = N - size_;
const size_t a_size = std::min(capacity, other.size_);
const size_t b_size = other.size_ - a_size;
std::copy(other.data(), other.data() + a_size, data() + size_);
size_ += a_size;
auto ret = RTEMbufArray(mempool_, std::span(other.data() + a_size, b_size));
other.release();
return ret;
}
std::pair<RTEMbufArray, RTEMbufArray> split(size_t index) {
const size_t a_size = std::min(index, size_);
const size_t b_size = size_ - a_size;
auto ret = std::make_pair(RTEMbufArray{mempool_, std::span(ptrs.begin(), a_size)},
RTEMbufArray{mempool_, std::span(ptrs.begin() + a_size, b_size)});
release();
return ret;
}
[[nodiscard]] size_t FromSpan(const std::span<pointer> span) {
auto to_move = std::min(span.size(), N);
for (size_t i = 0; i < to_move; i++)
ptrs[i] = span[i];
size_ = to_move;
return to_move;
}
size_t size() const {
return size_;
}
size_t free_cnt() const {
return N - size_;
}
constexpr size_t capacity() const {
return N;
}
double_pointer data() {
return &ptrs[0];
}
reference operator[](size_t i) {
return *ptrs[i];
}
const_reference operator[](size_t i) const {
return *ptrs[i];
}
Elem &get_data(size_t i)
requires(type == MbufType::Raw)
{
return (*this)[i];
}
Elem &get_data(size_t i)
requires(type == MbufType::Pkt)
{
return (*this)[i].data();
}
const Elem &get_data(size_t i) const
requires(type == MbufType::Raw)
{
return (*this)[i];
}
const Elem &get_data(size_t i) const
requires(type == MbufType::Pkt)
{
return (*this)[i].data();
}
template <typename ValueType, typename ItCategory>
struct BaseIterator;
using Iterator = BaseIterator<value_type, std::forward_iterator_tag>;
using ConstIterator = BaseIterator<const value_type, std::forward_iterator_tag>;
static_assert(std::forward_iterator<Iterator>);
static_assert(std::forward_iterator<ConstIterator>);
Iterator begin() {
return Iterator(ptrs.begin());
}
Iterator end() {
return Iterator(ptrs.begin() + size_);
}
// TODO: less ugly conversion??
ConstIterator begin() const {
return ConstIterator((const value_type **) (ptrs.begin()));
}
ConstIterator end() const {
return ConstIterator((const value_type **) (ptrs.begin() + size_));
}
reference front() {
return *begin();
}
reference back() {
return *end();
}
const_reference front() const {
return *begin();
}
const_reference back() const {
return *end();
}
std::optional<owning_value_type> pop() {
if (size_ == 0)
return std::nullopt;
return owning_value_type{mempool_, ptrs[--size_]};
}
[[nodiscard]] std::optional<owning_value_type> push(owning_value_type &&elem) {
if (size_ >= N)
return elem;
ptrs[size_++] = elem.ptr_;
elem.release();
return std::nullopt;
}
private:
template <typename, MbufType>
friend class RTERing;
static int AllocBulk(size_t count, double_pointer ptrs, rte_mempool *mempool)
requires(type == MbufType::Raw)
{
// Only allocate elements when count > 0
return count ? rte_mempool_get_bulk(mempool, (void **) ptrs, count) : 0;
}
static int AllocBulk(size_t count, double_pointer ptrs, rte_mempool *mempool)
requires(type == MbufType::Pkt)
{
// Only allocate elements when count > 0
return count ? rte_pktmbuf_alloc_bulk(mempool, (rte_mbuf **) ptrs, count) : 0;
}
void FreeAll()
requires(type == MbufType::Raw)
{
if (mempool_)
rte_mempool_put_bulk(mempool_, (void **) &ptrs[0], size_);
else if (size_)
spdlog::warn("mbuf array has elements to free without mempool!");
}
void FreeAll()
requires(type == MbufType::Pkt)
{
rte_pktmbuf_free_bulk((rte_mbuf **) &ptrs[0], size_);
}
RTEMbufArray(rte_mempool *mempool, std::span<pointer> span)
: ptrs{}, size_(span.size()), mempool_(mempool) {
assert(span.size() <= N);
std::copy(span.begin(), span.end(), ptrs.begin());
}
std::array<pointer, N> ptrs;
size_t size_;
rte_mempool *mempool_;
};
template <typename Elem, size_t N, MbufType type>
template <typename value_type_, typename iterator_category_>
struct RTEMbufArray<Elem, N, type>::BaseIterator {
using iterator_category = iterator_category_;
using difference_type = std::ptrdiff_t;
using value_type = value_type_;
using pointer = value_type *;
using double_pointer = pointer *;
using reference = value_type &;
explicit BaseIterator() : ptrs_(nullptr) { }
BaseIterator(double_pointer ptrs_) : ptrs_(ptrs_) { }
reference operator*() const {
return **ptrs_;
}
pointer operator->() const {
return *ptrs_;
}
BaseIterator &operator++() {
ptrs_++;
return *this;
}
BaseIterator operator++(int) {
BaseIterator tmp = *this;
ptrs_++;
return tmp;
}
friend bool operator==(const BaseIterator &a, const BaseIterator &b) {
return a.ptrs_ == b.ptrs_;
};
friend bool operator!=(const BaseIterator &a, const BaseIterator &b) {
return a.ptrs_ != b.ptrs_;
};
private:
double_pointer ptrs_;
};
template <typename Elem, MbufType type = MbufType::Raw>
class RTERing {
public:
using value_type =
typename std::conditional<type == MbufType::Raw, Elem, RTEMbuf<Elem>>::type;
using owning_value_type = RTEMbufElement<Elem, type>;
using pointer = value_type *;
using const_pointer = std::add_const_t<pointer>;
using double_pointer = pointer *;
using reference = value_type &;
using const_reference = std::add_const_t<reference>;
static tl::expected<RTERing, int> init(const std::string &name,
RTEMempool<Elem, type> &mempool, size_t num_elems, unsigned int flags,
int socket_id = rte_socket_id())
requires(type == MbufType::Raw)
{
rte_ring *ptr = rte_ring_create(name.c_str(), num_elems, socket_id, flags);
if (!ptr)
return tl::unexpected(rte_errno);
return RTERing(ptr, mempool.get());
}
static tl::expected<RTERing, int> init(const std::string &name, size_t num_elems,
unsigned int flags, int socket_id = rte_socket_id())
requires(type == MbufType::Pkt)
{
rte_ring *ptr = rte_ring_create(name.c_str(), num_elems, socket_id, flags);
if (!ptr)
return tl::unexpected(-1);
return RTERing(ptr, nullptr);
}
~RTERing() {
if (!ptr_)
return;
while (dequeue())
;
rte_ring_free(ptr_);
ptr_ = nullptr;
}
[[nodiscard]] std::optional<owning_value_type> enqueue(owning_value_type &&elem) {
void *to_enqueue = &elem.get();
int res = rte_ring_enqueue(ptr_, to_enqueue);
// If enqueue was unsuccessful just return the element
// back
if (unlikely(res))
return elem;
elem.release();
return std::nullopt;
}
template <size_t N>
[[nodiscard]] RTEMbufArray<Elem, N, type> enqueue_burst(
RTEMbufArray<Elem, N, type> &&array) {
if constexpr (type == MbufType::Raw)
assert(mempool_ == array.mempool_);
auto ptr = reinterpret_cast<void **>(array.data());
auto res = rte_ring_enqueue_burst(ptr_, ptr, array.size(), nullptr);
auto [enqueued, avail] = array.split(res);
enqueued.release();
return std::move(avail);
}
std::optional<owning_value_type> dequeue() {
pointer elem = nullptr;
int res = rte_ring_dequeue(ptr_, reinterpret_cast<void **>(&elem));
if (unlikely(res))
return std::nullopt;
return owning_value_type{mempool_, elem};
}
template <size_t N>
RTEMbufArray<Elem, N, type> dequeue_burst(size_t num = N) {
std::array<typename RTEMbufArray<Elem, N, type>::pointer, N> ptrs;
auto ptr = reinterpret_cast<void **>(ptrs.data());
auto res = rte_ring_dequeue_burst(ptr_, ptr, num, nullptr);
return RTEMbufArray<Elem, N, type>{mempool_, std::span{ptrs.begin(), res}};
}
size_t count() const {
return rte_ring_count(ptr_);
}
size_t capacity() const {
return rte_ring_get_capacity(ptr_);
}
size_t free_count() const {
return rte_ring_free_count(ptr_);
}
bool empty() const {
return rte_ring_empty(ptr_);
}
// Delete copy constructors.
RTERing(const RTERing &) = delete;
RTERing &operator=(const RTERing &) = delete;
RTERing(RTERing &&other) {
ptr_ = other.ptr_;
mempool_ = other.mempool_;
other.ptr_ = nullptr;
other.mempool_ = nullptr;
}
RTERing &operator=(RTERing &&other) {
std::swap(ptr_, other.ptr_);
std::swap(mempool_, other.mempool_);
return *this;
}
private:
RTERing(rte_ring *ptr, rte_mempool *mempool) : ptr_(ptr), mempool_(mempool) { }
rte_ring *ptr_;
rte_mempool *mempool_;
};