mirror of https://github.com/pvnis/srsRAN_4G.git
Merge branch 'next' into agpl_next
# Conflicts: # lib/include/srsran/adt/adt_utils.hmaster
commit
a047e13479
@ -1,61 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-2021 Software Radio Systems Limited
|
||||
*
|
||||
* This file is part of srsLTE.
|
||||
*
|
||||
* srsLTE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* srsLTE 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 Affero General Public License for more details.
|
||||
*
|
||||
* A copy of the GNU Affero General Public License can be found in
|
||||
* the LICENSE file in the top-level directory of this distribution
|
||||
* and at http://www.gnu.org/licenses/.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SRSRAN_ADT_UTILS_H
|
||||
#define SRSRAN_ADT_UTILS_H
|
||||
|
||||
#ifdef __EXCEPTIONS
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#define EXCEPTIONS_ENABLED 1
|
||||
|
||||
namespace srsran {
|
||||
|
||||
class bad_type_access : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
explicit bad_type_access(const std::string& what_arg) : runtime_error(what_arg) {}
|
||||
explicit bad_type_access(const char* what_arg) : runtime_error(what_arg) {}
|
||||
};
|
||||
|
||||
#define THROW_BAD_ACCESS(msg) throw bad_type_access(msg)
|
||||
|
||||
} // namespace srsran
|
||||
|
||||
#else
|
||||
|
||||
#define EXCEPTIONS_ENABLED 0
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace srsran {
|
||||
|
||||
#define THROW_BAD_ACCESS(msg) \
|
||||
std::fprintf(stderr, "ERROR: exception thrown with %s", msg); \
|
||||
std::abort()
|
||||
|
||||
} // namespace srsran
|
||||
|
||||
#endif
|
||||
|
||||
#endif // SRSRAN_ADT_UTILS_H
|
@ -0,0 +1,135 @@
|
||||
/**
|
||||
*
|
||||
* \section COPYRIGHT
|
||||
*
|
||||
* Copyright 2013-2021 Software Radio Systems Limited
|
||||
*
|
||||
* By using this file, you agree to the terms and conditions set
|
||||
* forth in the LICENSE file which can be found at the top level of
|
||||
* the distribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SRSRAN_OPTIONAL_H
|
||||
#define SRSRAN_OPTIONAL_H
|
||||
|
||||
#include "detail/type_storage.h"
|
||||
#include "srsran/common/srsran_assert.h"
|
||||
|
||||
namespace srsran {
|
||||
|
||||
template <typename T>
|
||||
class optional
|
||||
{
|
||||
public:
|
||||
optional() : has_val_(false) {}
|
||||
optional(const T& t) : has_val_(true) { storage.emplace(t); }
|
||||
optional(T&& t) : has_val_(true) { storage.emplace(std::move(t)); }
|
||||
optional(const optional<T>& other) : has_val_(other.has_value())
|
||||
{
|
||||
if (other.has_value()) {
|
||||
storage.copy_ctor(other.storage);
|
||||
}
|
||||
}
|
||||
optional(optional<T>&& other) noexcept : has_val_(other.has_value())
|
||||
{
|
||||
if (other.has_value()) {
|
||||
storage.move_ctor(std::move(other.storage));
|
||||
}
|
||||
}
|
||||
optional& operator=(const optional<T>& other)
|
||||
{
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
copy_if_present_helper(storage, other.storage, has_value(), other.has_value());
|
||||
has_val_ = other.has_value();
|
||||
return *this;
|
||||
}
|
||||
optional& operator=(optional<T>&& other) noexcept
|
||||
{
|
||||
move_if_present_helper(storage, other.storage, has_value(), other.has_value());
|
||||
has_val_ = other.has_value();
|
||||
return *this;
|
||||
}
|
||||
~optional()
|
||||
{
|
||||
if (has_value()) {
|
||||
storage.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
bool has_value() const { return has_val_; }
|
||||
explicit operator bool() const { return has_value(); }
|
||||
|
||||
T* operator->() { return &value(); }
|
||||
const T* operator->() const { return &value(); }
|
||||
T& operator*() { return value(); }
|
||||
const T& operator*() const { return value(); }
|
||||
T& value()
|
||||
{
|
||||
srsran_assert(has_val_, "Invalid optional<T> access");
|
||||
return storage.get();
|
||||
}
|
||||
const T& value() const
|
||||
{
|
||||
srsran_assert(has_val_, "Invalid optional<T> access");
|
||||
return storage.get();
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void emplace(Args&&... args)
|
||||
{
|
||||
if (has_value()) {
|
||||
storage.destroy();
|
||||
}
|
||||
storage.emplace(std::forward<Args>(args)...);
|
||||
has_val_ = true;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
if (has_value()) {
|
||||
storage.destroy();
|
||||
has_val_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool has_val_;
|
||||
detail::type_storage<T> storage;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
bool operator==(const optional<T>& lhs, const optional<T>& rhs)
|
||||
{
|
||||
return lhs.has_value() == rhs.has_value() and (not lhs.has_value() or lhs.value() == rhs.value());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool operator==(const optional<T>& lhs, const T& rhs)
|
||||
{
|
||||
return lhs.has_value() and lhs.value() == rhs;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool operator!=(const optional<T>& lhs, const optional<T>& rhs)
|
||||
{
|
||||
return not(lhs == rhs);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool operator!=(const optional<T>& lhs, const T& rhs)
|
||||
{
|
||||
return not(lhs == rhs);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool operator<(const optional<T>& lhs, const optional<T>& rhs)
|
||||
{
|
||||
return rhs.has_value() and ((lhs.has_value() and lhs.value() < rhs.value()) or (not lhs.has_value()));
|
||||
}
|
||||
|
||||
} // namespace srsran
|
||||
|
||||
#endif // SRSRAN_OPTIONAL_H
|
@ -0,0 +1,199 @@
|
||||
/**
|
||||
*
|
||||
* \section COPYRIGHT
|
||||
*
|
||||
* Copyright 2013-2021 Software Radio Systems Limited
|
||||
*
|
||||
* By using this file, you agree to the terms and conditions set
|
||||
* forth in the LICENSE file which can be found at the top level of
|
||||
* the distribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SRSRAN_FIXED_SIZE_POOL_H
|
||||
#define SRSRAN_FIXED_SIZE_POOL_H
|
||||
|
||||
#include "memblock_cache.h"
|
||||
#include "srsran/adt/circular_buffer.h"
|
||||
#include <thread>
|
||||
|
||||
namespace srsran {
|
||||
|
||||
/**
|
||||
* Concurrent fixed size memory pool made of blocks of equal size
|
||||
* Each worker keeps a separate thread-local memory block cache that it uses for fast allocation/deallocation.
|
||||
* When this cache gets depleted, the worker tries to obtain blocks from a central memory block cache.
|
||||
* When accessing a thread local cache, no locks are required.
|
||||
* Since there is no stealing of blocks between workers, it is possible that a worker can't allocate while another
|
||||
* worker still has blocks in its own cache. To minimize the impact of this event, an upper bound is place on a worker
|
||||
* thread cache size. Once a worker reaches that upper bound, it sends half of its stored blocks to the central cache.
|
||||
* Note: Taking into account the usage of thread_local, this class is made a singleton
|
||||
* Note2: No considerations were made regarding false sharing between threads. It is assumed that the blocks are big
|
||||
* enough to fill a cache line.
|
||||
* @tparam NofObjects number of objects in the pool
|
||||
* @tparam ObjSize object size
|
||||
*/
|
||||
template <size_t ObjSize, bool DebugSanitizeAddress = false>
|
||||
class concurrent_fixed_memory_pool
|
||||
{
|
||||
static_assert(ObjSize > 256, "This pool is particularly designed for large objects.");
|
||||
using pool_type = concurrent_fixed_memory_pool<ObjSize, DebugSanitizeAddress>;
|
||||
|
||||
struct obj_storage_t {
|
||||
typename std::aligned_storage<ObjSize, alignof(detail::max_alignment_t)>::type buffer;
|
||||
};
|
||||
|
||||
const static size_t batch_steal_size = 16;
|
||||
|
||||
// ctor only accessible from singleton get_instance()
|
||||
explicit concurrent_fixed_memory_pool(size_t nof_objects_)
|
||||
{
|
||||
srsran_assert(nof_objects_ > batch_steal_size, "A positive pool size must be provided");
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
allocated_blocks.resize(nof_objects_);
|
||||
for (std::unique_ptr<obj_storage_t>& b : allocated_blocks) {
|
||||
b.reset(new obj_storage_t());
|
||||
srsran_assert(b.get() != nullptr, "Failed to instantiate fixed memory pool");
|
||||
central_mem_cache.push(static_cast<void*>(b.get()));
|
||||
}
|
||||
local_growth_thres = allocated_blocks.size() / 16;
|
||||
local_growth_thres = local_growth_thres < batch_steal_size ? batch_steal_size : local_growth_thres;
|
||||
}
|
||||
|
||||
public:
|
||||
const static size_t BLOCK_SIZE = ObjSize;
|
||||
|
||||
concurrent_fixed_memory_pool(const concurrent_fixed_memory_pool&) = delete;
|
||||
concurrent_fixed_memory_pool(concurrent_fixed_memory_pool&&) = delete;
|
||||
concurrent_fixed_memory_pool& operator=(const concurrent_fixed_memory_pool&) = delete;
|
||||
concurrent_fixed_memory_pool& operator=(concurrent_fixed_memory_pool&&) = delete;
|
||||
|
||||
~concurrent_fixed_memory_pool()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
allocated_blocks.clear();
|
||||
}
|
||||
|
||||
static concurrent_fixed_memory_pool<ObjSize, DebugSanitizeAddress>* get_instance(size_t size = 4096)
|
||||
{
|
||||
static concurrent_fixed_memory_pool<ObjSize, DebugSanitizeAddress> pool(size);
|
||||
return &pool;
|
||||
}
|
||||
|
||||
size_t size() { return allocated_blocks.size(); }
|
||||
|
||||
void* allocate_node(size_t sz)
|
||||
{
|
||||
srsran_assert(sz <= ObjSize, "Allocated node size=%zd exceeds max object size=%zd", sz, ObjSize);
|
||||
worker_ctxt* worker_ctxt = get_worker_cache();
|
||||
|
||||
void* node = worker_ctxt->cache.try_pop();
|
||||
if (node == nullptr) {
|
||||
// fill the thread local cache enough for this and next allocations
|
||||
std::array<void*, batch_steal_size> popped_blocks;
|
||||
size_t n = central_mem_cache.try_pop(popped_blocks);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
new (popped_blocks[i]) obj_storage_t();
|
||||
worker_ctxt->cache.push(static_cast<void*>(popped_blocks[i]));
|
||||
}
|
||||
node = worker_ctxt->cache.try_pop();
|
||||
}
|
||||
|
||||
#ifdef SRSRAN_BUFFER_POOL_LOG_ENABLED
|
||||
if (node == nullptr) {
|
||||
print_error("Error allocating buffer in pool of ObjSize=%zd", ObjSize);
|
||||
}
|
||||
#endif
|
||||
return node;
|
||||
}
|
||||
|
||||
void deallocate_node(void* p)
|
||||
{
|
||||
srsran_assert(p != nullptr, "Deallocated nodes must have valid address");
|
||||
worker_ctxt* worker_ctxt = get_worker_cache();
|
||||
obj_storage_t* block_ptr = static_cast<obj_storage_t*>(p);
|
||||
|
||||
if (DebugSanitizeAddress) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
srsran_assert(std::any_of(allocated_blocks.begin(),
|
||||
allocated_blocks.end(),
|
||||
[block_ptr](const std::unique_ptr<obj_storage_t>& b) { return b.get() == block_ptr; }),
|
||||
"Error deallocating block with address 0x%lx",
|
||||
(long unsigned)block_ptr);
|
||||
}
|
||||
|
||||
// push to local memory block cache
|
||||
worker_ctxt->cache.push(static_cast<void*>(p));
|
||||
|
||||
if (worker_ctxt->cache.size() >= local_growth_thres) {
|
||||
// if local cache reached max capacity, send half of the blocks to central cache
|
||||
central_mem_cache.steal_blocks(worker_ctxt->cache, worker_ctxt->cache.size() / 2);
|
||||
}
|
||||
}
|
||||
|
||||
void enable_logger(bool enabled)
|
||||
{
|
||||
if (enabled) {
|
||||
logger = &srslog::fetch_basic_logger("POOL");
|
||||
logger->set_level(srslog::basic_levels::debug);
|
||||
} else {
|
||||
logger = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void print_all_buffers()
|
||||
{
|
||||
auto* worker = get_worker_cache();
|
||||
size_t tot_blocks = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
tot_blocks = allocated_blocks.size();
|
||||
}
|
||||
printf("There are %zd/%zd buffers in shared block container. This thread contains %zd in its local cache\n",
|
||||
central_mem_cache.size(),
|
||||
tot_blocks,
|
||||
worker->cache.size());
|
||||
}
|
||||
|
||||
private:
|
||||
struct worker_ctxt {
|
||||
std::thread::id id;
|
||||
memblock_cache cache;
|
||||
|
||||
worker_ctxt() : id(std::this_thread::get_id()) {}
|
||||
~worker_ctxt()
|
||||
{
|
||||
mutexed_memblock_cache& central_cache = pool_type::get_instance()->central_mem_cache;
|
||||
central_cache.steal_blocks(cache, cache.size());
|
||||
}
|
||||
};
|
||||
|
||||
worker_ctxt* get_worker_cache()
|
||||
{
|
||||
thread_local worker_ctxt worker_cache;
|
||||
return &worker_cache;
|
||||
}
|
||||
|
||||
/// Formats and prints the input string and arguments into the configured output stream.
|
||||
template <typename... Args>
|
||||
void print_error(const char* str, Args&&... args)
|
||||
{
|
||||
if (logger != nullptr) {
|
||||
logger->error(str, std::forward<Args>(args)...);
|
||||
} else {
|
||||
fmt::printf(std::string(str) + "\n", std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
size_t local_growth_thres = 0;
|
||||
srslog::basic_logger* logger = nullptr;
|
||||
|
||||
mutexed_memblock_cache central_mem_cache;
|
||||
std::mutex mutex;
|
||||
std::vector<std::unique_ptr<obj_storage_t> > allocated_blocks;
|
||||
};
|
||||
|
||||
} // namespace srsran
|
||||
|
||||
#endif // SRSRAN_FIXED_SIZE_POOL_H
|
@ -0,0 +1,159 @@
|
||||
/**
|
||||
*
|
||||
* \section COPYRIGHT
|
||||
*
|
||||
* Copyright 2013-2021 Software Radio Systems Limited
|
||||
*
|
||||
* By using this file, you agree to the terms and conditions set
|
||||
* forth in the LICENSE file which can be found at the top level of
|
||||
* the distribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SRSRAN_MEMBLOCK_CACHE_H
|
||||
#define SRSRAN_MEMBLOCK_CACHE_H
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace srsran {
|
||||
|
||||
/// Stores provided mem blocks in a stack in an non-owning manner. Not thread-safe
|
||||
class memblock_cache
|
||||
{
|
||||
struct node {
|
||||
node* prev;
|
||||
explicit node(node* prev_) : prev(prev_) {}
|
||||
};
|
||||
|
||||
public:
|
||||
constexpr static size_t min_memblock_size() { return sizeof(node); }
|
||||
|
||||
memblock_cache() = default;
|
||||
|
||||
memblock_cache(const memblock_cache&) = delete;
|
||||
|
||||
memblock_cache(memblock_cache&& other) noexcept : head(other.head) { other.head = nullptr; }
|
||||
|
||||
memblock_cache& operator=(const memblock_cache&) = delete;
|
||||
|
||||
memblock_cache& operator=(memblock_cache&& other) noexcept
|
||||
{
|
||||
head = other.head;
|
||||
other.head = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void push(void* block) noexcept
|
||||
{
|
||||
// printf("head: %ld\n", (long)head);
|
||||
node* next = ::new (block) node(head);
|
||||
head = next;
|
||||
count++;
|
||||
}
|
||||
|
||||
uint8_t* try_pop() noexcept
|
||||
{
|
||||
if (is_empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
node* last_head = head;
|
||||
head = head->prev;
|
||||
count--;
|
||||
return (uint8_t*)last_head;
|
||||
}
|
||||
|
||||
bool is_empty() const { return head == nullptr; }
|
||||
|
||||
size_t size() const { return count; }
|
||||
|
||||
void clear() { head = nullptr; }
|
||||
|
||||
private:
|
||||
node* head = nullptr;
|
||||
size_t count = 0;
|
||||
};
|
||||
|
||||
/// memblock stack that mutexes pushing/popping
|
||||
class mutexed_memblock_cache
|
||||
{
|
||||
public:
|
||||
mutexed_memblock_cache() = default;
|
||||
|
||||
mutexed_memblock_cache(const mutexed_memblock_cache&) = delete;
|
||||
|
||||
mutexed_memblock_cache(mutexed_memblock_cache&& other) noexcept
|
||||
{
|
||||
std::unique_lock<std::mutex> lk1(other.mutex, std::defer_lock);
|
||||
std::unique_lock<std::mutex> lk2(mutex, std::defer_lock);
|
||||
std::lock(lk1, lk2);
|
||||
stack = std::move(other.stack);
|
||||
}
|
||||
|
||||
mutexed_memblock_cache& operator=(const mutexed_memblock_cache&) = delete;
|
||||
|
||||
mutexed_memblock_cache& operator=(mutexed_memblock_cache&& other) noexcept
|
||||
{
|
||||
std::unique_lock<std::mutex> lk1(other.mutex, std::defer_lock);
|
||||
std::unique_lock<std::mutex> lk2(mutex, std::defer_lock);
|
||||
std::lock(lk1, lk2);
|
||||
stack = std::move(other.stack);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void push(void* block) noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
stack.push(block);
|
||||
}
|
||||
|
||||
void steal_blocks(memblock_cache& other, size_t max_n) noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
for (size_t i = 0; i < max_n and not other.is_empty(); ++i) {
|
||||
stack.push(other.try_pop());
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t* try_pop() noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
uint8_t* block = stack.try_pop();
|
||||
return block;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
size_t try_pop(std::array<void*, N>& result) noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
size_t i = 0;
|
||||
for (; i < N; ++i) {
|
||||
result[i] = stack.try_pop();
|
||||
if (result[i] == nullptr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
bool is_empty() const noexcept { return stack.is_empty(); }
|
||||
|
||||
size_t size() const noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
return stack.size();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
stack.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
memblock_cache stack;
|
||||
mutable std::mutex mutex;
|
||||
};
|
||||
|
||||
} // namespace srsran
|
||||
|
||||
#endif // SRSRAN_MEMBLOCK_CACHE_H
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
*
|
||||
* \section COPYRIGHT
|
||||
*
|
||||
* Copyright 2013-2021 Software Radio Systems Limited
|
||||
*
|
||||
* By using this file, you agree to the terms and conditions set
|
||||
* forth in the LICENSE file which can be found at the top level of
|
||||
* the distribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "srsran/adt/optional.h"
|
||||
#include "srsran/common/test_common.h"
|
||||
|
||||
using namespace srsran;
|
||||
|
||||
void test_optional_int()
|
||||
{
|
||||
optional<int> opt, opt2(5);
|
||||
TESTASSERT(not opt.has_value() and opt2.has_value());
|
||||
TESTASSERT(not static_cast<bool>(opt) and static_cast<bool>(opt2));
|
||||
TESTASSERT(opt2.value() == 5 and *opt2 == 5);
|
||||
|
||||
opt = 4;
|
||||
TESTASSERT(opt.has_value());
|
||||
TESTASSERT(opt != opt2);
|
||||
opt2 = 4;
|
||||
TESTASSERT(opt == opt2);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
test_optional_int();
|
||||
}
|
@ -0,0 +1,269 @@
|
||||
/**
|
||||
*
|
||||
* \section COPYRIGHT
|
||||
*
|
||||
* Copyright 2013-2021 Software Radio Systems Limited
|
||||
*
|
||||
* By using this file, you agree to the terms and conditions set
|
||||
* forth in the LICENSE file which can be found at the top level of
|
||||
* the distribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "srsenb/hdr/stack/upper/s1ap.h"
|
||||
#include "srsenb/test/common/dummy_classes.h"
|
||||
#include "srsran/common/network_utils.h"
|
||||
#include "srsran/common/test_common.h"
|
||||
|
||||
using namespace srsenb;
|
||||
|
||||
class stack_dummy : public srsenb::stack_interface_s1ap_lte
|
||||
{
|
||||
public:
|
||||
void add_mme_socket(int fd) {}
|
||||
void remove_mme_socket(int fd) {}
|
||||
};
|
||||
|
||||
struct mme_dummy {
|
||||
mme_dummy(const char* addr_str_, int port_) : addr_str(addr_str_), port(port_)
|
||||
{
|
||||
srsran::net_utils::set_sockaddr(&mme_sockaddr, addr_str, port);
|
||||
{
|
||||
using namespace srsran::net_utils;
|
||||
fd = open_socket(addr_family::ipv4, socket_type::seqpacket, protocol_type::SCTP);
|
||||
TESTASSERT(fd > 0);
|
||||
TESTASSERT(bind_addr(fd, mme_sockaddr));
|
||||
}
|
||||
|
||||
srsran_assert(listen(fd, SOMAXCONN) == 0, "Failed to listen to incoming SCTP connections");
|
||||
}
|
||||
|
||||
~mme_dummy()
|
||||
{
|
||||
if (fd > 0) {
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
srsran::unique_byte_buffer_t read_msg(sockaddr_in* sockfrom = nullptr)
|
||||
{
|
||||
srsran::unique_byte_buffer_t pdu = srsran::make_byte_buffer();
|
||||
sockaddr_in from = {};
|
||||
socklen_t fromlen = sizeof(from);
|
||||
sctp_sndrcvinfo sri = {};
|
||||
int flags = 0;
|
||||
ssize_t n_recv = sctp_recvmsg(fd, pdu->msg, pdu->get_tailroom(), (struct sockaddr*)&from, &fromlen, &sri, &flags);
|
||||
if (n_recv > 0) {
|
||||
if (sockfrom != nullptr) {
|
||||
*sockfrom = from;
|
||||
}
|
||||
pdu->N_bytes = n_recv;
|
||||
}
|
||||
return pdu;
|
||||
}
|
||||
|
||||
const char* addr_str;
|
||||
int port;
|
||||
struct sockaddr_in mme_sockaddr = {};
|
||||
int fd;
|
||||
srsran::unique_byte_buffer_t last_sdu;
|
||||
};
|
||||
|
||||
struct rrc_tester : public rrc_dummy {
|
||||
void modify_erabs(uint16_t rnti,
|
||||
const asn1::s1ap::erab_modify_request_s& msg,
|
||||
std::vector<uint16_t>* erabs_modified,
|
||||
std::vector<uint16_t>* erabs_failed_to_modify) override
|
||||
{
|
||||
*erabs_modified = next_erabs_modified;
|
||||
*erabs_failed_to_modify = next_erabs_failed_to_modify;
|
||||
}
|
||||
void release_ue(uint16_t rnti) override { last_released_rnti = rnti; }
|
||||
|
||||
uint16_t last_released_rnti = SRSRAN_INVALID_RNTI;
|
||||
std::vector<uint16_t> next_erabs_modified, next_erabs_failed_to_modify;
|
||||
};
|
||||
|
||||
void run_s1_setup(s1ap& s1ap_obj, mme_dummy& mme)
|
||||
{
|
||||
asn1::s1ap::s1ap_pdu_c s1ap_pdu;
|
||||
|
||||
// eNB -> MME: S1 Setup Request
|
||||
srsran::unique_byte_buffer_t sdu = mme.read_msg();
|
||||
TESTASSERT(sdu->N_bytes > 0);
|
||||
asn1::cbit_ref cbref(sdu->msg, sdu->N_bytes);
|
||||
TESTASSERT(s1ap_pdu.unpack(cbref) == asn1::SRSASN_SUCCESS);
|
||||
TESTASSERT(s1ap_pdu.type().value == asn1::s1ap::s1ap_pdu_c::types_opts::init_msg);
|
||||
TESTASSERT(s1ap_pdu.init_msg().proc_code == ASN1_S1AP_ID_S1_SETUP);
|
||||
|
||||
// MME -> eNB: S1 Setup Response
|
||||
sockaddr_in mme_addr = {};
|
||||
sctp_sndrcvinfo rcvinfo = {};
|
||||
int flags = 0;
|
||||
uint8_t s1_setup_resp[] = {0x20, 0x11, 0x00, 0x25, 0x00, 0x00, 0x03, 0x00, 0x3d, 0x40, 0x0a, 0x03, 0x80, 0x73,
|
||||
0x72, 0x73, 0x6d, 0x6d, 0x65, 0x30, 0x31, 0x00, 0x69, 0x00, 0x0b, 0x00, 0x00, 0x00,
|
||||
0xf1, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00, 0x1a, 0x00, 0x57, 0x40, 0x01, 0xff};
|
||||
memcpy(sdu->msg, s1_setup_resp, sizeof(s1_setup_resp));
|
||||
sdu->N_bytes = sizeof(s1_setup_resp);
|
||||
TESTASSERT(s1ap_obj.handle_mme_rx_msg(std::move(sdu), mme_addr, rcvinfo, flags));
|
||||
}
|
||||
|
||||
void add_rnti(s1ap& s1ap_obj, mme_dummy& mme)
|
||||
{
|
||||
asn1::s1ap::s1ap_pdu_c s1ap_pdu;
|
||||
|
||||
// New UE
|
||||
uint8_t nas_msg[] = {0x00, 0x1a, 0x00, 0x21, 0x20, 0x17, 0x82, 0xa8, 0x64, 0x46, 0x04, 0x07, 0x41,
|
||||
0x01, 0x0b, 0xf6, 0x00, 0xf1, 0x10, 0x00, 0x01, 0x1a, 0x5e, 0xa4, 0x54, 0x47,
|
||||
0x02, 0xf0, 0x70, 0x00, 0x04, 0x02, 0x01, 0xd0, 0x11, 0x91, 0xe0};
|
||||
|
||||
srsran::unique_byte_buffer_t sdu = srsran::make_byte_buffer();
|
||||
memcpy(sdu->msg, nas_msg, sizeof(nas_msg));
|
||||
sdu->N_bytes = sizeof(nas_msg);
|
||||
s1ap_obj.initial_ue(0x46, 0, asn1::s1ap::rrc_establishment_cause_opts::mo_data, std::move(sdu));
|
||||
sdu = mme.read_msg();
|
||||
TESTASSERT(sdu->N_bytes > 0);
|
||||
asn1::cbit_ref cbref{sdu->msg, sdu->N_bytes};
|
||||
TESTASSERT(s1ap_pdu.unpack(cbref) == SRSRAN_SUCCESS);
|
||||
TESTASSERT(s1ap_pdu.type().value == asn1::s1ap::s1ap_pdu_c::types_opts::init_msg);
|
||||
TESTASSERT(s1ap_pdu.init_msg().proc_code == ASN1_S1AP_ID_INIT_UE_MSG);
|
||||
|
||||
// InitialContextSetupRequest (skip all NAS exchange)
|
||||
sockaddr_in mme_addr = {};
|
||||
sctp_sndrcvinfo rcvinfo = {};
|
||||
int flags = 0;
|
||||
uint8_t icsr_msg[] = {
|
||||
0x00, 0x09, 0x00, 0x80, 0xac, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x08, 0x00, 0x02,
|
||||
0x00, 0x01, 0x00, 0x42, 0x00, 0x0a, 0x18, 0x3b, 0x9a, 0xca, 0x00, 0x60, 0x3b, 0x9a, 0xca, 0x00, 0x00, 0x18,
|
||||
0x00, 0x5e, 0x00, 0x00, 0x34, 0x00, 0x59, 0x45, 0x00, 0x09, 0x3c, 0x0f, 0x80, 0x7f, 0x00, 0x01, 0x64, 0x00,
|
||||
0x00, 0x00, 0x01, 0x4a, 0x27, 0x9b, 0x6d, 0xe9, 0x42, 0x01, 0x07, 0x42, 0x01, 0x3e, 0x06, 0x00, 0x00, 0xf1,
|
||||
0x10, 0x00, 0x07, 0x00, 0x1d, 0x52, 0x01, 0xc1, 0x01, 0x09, 0x07, 0x06, 0x73, 0x72, 0x73, 0x61, 0x70, 0x6e,
|
||||
0x05, 0x01, 0xc0, 0xa8, 0x0a, 0x02, 0x27, 0x08, 0x80, 0x00, 0x0d, 0x04, 0x08, 0x08, 0x08, 0x08, 0x50, 0x0b,
|
||||
0xf6, 0x00, 0xf1, 0x10, 0x00, 0x01, 0x1a, 0x32, 0xdd, 0x59, 0x35, 0x13, 0x00, 0xf1, 0x10, 0x00, 0x01, 0x23,
|
||||
0x05, 0xf4, 0x32, 0xdd, 0x59, 0x35, 0x00, 0x6b, 0x00, 0x05, 0x18, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x49, 0x00,
|
||||
0x20, 0x84, 0xa4, 0xea, 0x15, 0x55, 0xb3, 0xe0, 0xf4, 0x55, 0xbe, 0x1f, 0x41, 0x52, 0x92, 0xfc, 0x04, 0xd8,
|
||||
0x02, 0x38, 0x0d, 0xe0, 0x81, 0x29, 0xe1, 0xaa, 0xd7, 0xc4, 0x7b, 0x12, 0x95, 0x72, 0xbe};
|
||||
sdu = srsran::make_byte_buffer();
|
||||
memcpy(sdu->msg, icsr_msg, sizeof(icsr_msg));
|
||||
sdu->N_bytes = sizeof(icsr_msg);
|
||||
TESTASSERT(s1ap_obj.handle_mme_rx_msg(std::move(sdu), mme_addr, rcvinfo, flags));
|
||||
|
||||
// InitialContextSetupResponse
|
||||
uint8_t icsresp[] = {0x20, 0x09, 0x00, 0x22, 0x00, 0x00, 0x03, 0x00, 0x00, 0x40, 0x02, 0x00, 0x01,
|
||||
0x00, 0x08, 0x40, 0x02, 0x00, 0x01, 0x00, 0x33, 0x40, 0x0f, 0x00, 0x00, 0x32,
|
||||
0x40, 0x0a, 0x0a, 0x1f, 0x7f, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01};
|
||||
cbref = asn1::cbit_ref(icsresp, sizeof(icsresp));
|
||||
TESTASSERT(s1ap_pdu.unpack(cbref) == SRSRAN_SUCCESS);
|
||||
s1ap_obj.ue_ctxt_setup_complete(0x46, s1ap_pdu.successful_outcome().value.init_context_setup_resp());
|
||||
sdu = mme.read_msg();
|
||||
TESTASSERT(sdu->N_bytes > 0);
|
||||
cbref = asn1::cbit_ref{sdu->msg, sdu->N_bytes};
|
||||
TESTASSERT(s1ap_pdu.unpack(cbref) == SRSRAN_SUCCESS);
|
||||
TESTASSERT(s1ap_pdu.type().value == asn1::s1ap::s1ap_pdu_c::types_opts::successful_outcome);
|
||||
TESTASSERT(s1ap_pdu.successful_outcome().proc_code == ASN1_S1AP_ID_INIT_CONTEXT_SETUP);
|
||||
}
|
||||
|
||||
enum class test_event { success, wrong_erabid_mod, wrong_mme_s1ap_id };
|
||||
|
||||
void test_s1ap_erab_setup(test_event event)
|
||||
{
|
||||
srsran::task_scheduler task_sched;
|
||||
srslog::basic_logger& logger = srslog::fetch_basic_logger("S1AP");
|
||||
s1ap s1ap_obj(&task_sched, logger);
|
||||
rrc_tester rrc;
|
||||
stack_dummy stack;
|
||||
asn1::s1ap::s1ap_pdu_c s1ap_pdu;
|
||||
srsran::unique_byte_buffer_t sdu;
|
||||
|
||||
const char* mme_addr_str = "127.0.0.1";
|
||||
const uint32_t MME_PORT = 36412;
|
||||
mme_dummy mme(mme_addr_str, MME_PORT);
|
||||
|
||||
s1ap_args_t args = {};
|
||||
args.cell_id = 0x01;
|
||||
args.enb_id = 0x19B;
|
||||
args.mcc = 907;
|
||||
args.mnc = 70;
|
||||
args.s1c_bind_addr = "127.0.0.100";
|
||||
args.tac = 7;
|
||||
args.gtp_bind_addr = "127.0.0.100";
|
||||
args.mme_addr = mme_addr_str;
|
||||
args.enb_name = "srsenb01";
|
||||
|
||||
TESTASSERT(s1ap_obj.init(args, &rrc, &stack) == SRSRAN_SUCCESS);
|
||||
|
||||
run_s1_setup(s1ap_obj, mme);
|
||||
add_rnti(s1ap_obj, mme);
|
||||
|
||||
// E-RAB Modify Request
|
||||
sockaddr_in mme_addr = {};
|
||||
sctp_sndrcvinfo rcvinfo = {};
|
||||
int flags = 0;
|
||||
uint8_t mod_req_msg[] = {0x00, 0x06, 0x00, 0x1E, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00,
|
||||
0x01, 0x00, 0x08, 0x00, 0x02, 0x00, 0x01, 0x00, 0x1E, 0x00, 0x0B, 0x00,
|
||||
0x00, 0x24, 0x00, 0x06, 0x0A, 0x00, 0x09, 0x3C, 0x01, 0x00};
|
||||
// 00 06 00 1E 00 00 03 00 00 00 02 00 01 00 08 00 02 00 01 00 1E 00 0B 00 00 24 00 06 0A 00 09 3C 01 00
|
||||
if (event == test_event::wrong_erabid_mod) {
|
||||
mod_req_msg[sizeof(mod_req_msg) - 6] = 0x0C; // E-RAB id = 6
|
||||
rrc.next_erabs_failed_to_modify.push_back(6);
|
||||
} else if (event == test_event::wrong_mme_s1ap_id) {
|
||||
mod_req_msg[12] = 0x02; // MME-UE-S1AP-ID = 2
|
||||
} else {
|
||||
rrc.next_erabs_modified.push_back(5);
|
||||
}
|
||||
sdu = srsran::make_byte_buffer();
|
||||
memcpy(sdu->msg, mod_req_msg, sizeof(mod_req_msg));
|
||||
sdu->N_bytes = sizeof(mod_req_msg);
|
||||
TESTASSERT(rrc.last_released_rnti == SRSRAN_INVALID_RNTI);
|
||||
TESTASSERT(s1ap_obj.handle_mme_rx_msg(std::move(sdu), mme_addr, rcvinfo, flags));
|
||||
sdu = mme.read_msg();
|
||||
TESTASSERT(sdu->N_bytes > 0);
|
||||
asn1::cbit_ref cbref{sdu->msg, sdu->N_bytes};
|
||||
TESTASSERT(s1ap_pdu.unpack(cbref) == SRSRAN_SUCCESS);
|
||||
|
||||
if (event == test_event::wrong_mme_s1ap_id) {
|
||||
// See TS 36.413, Section 10.6 - Handling of AP ID
|
||||
TESTASSERT(s1ap_pdu.type().value == asn1::s1ap::s1ap_pdu_c::types_opts::init_msg);
|
||||
TESTASSERT(s1ap_pdu.init_msg().proc_code == ASN1_S1AP_ID_ERROR_IND);
|
||||
auto& protocol_ies = s1ap_pdu.init_msg().value.error_ind().protocol_ies;
|
||||
TESTASSERT(protocol_ies.mme_ue_s1ap_id_present and protocol_ies.mme_ue_s1ap_id.value.value == 2);
|
||||
TESTASSERT(protocol_ies.enb_ue_s1ap_id_present and protocol_ies.enb_ue_s1ap_id.value.value == 1);
|
||||
TESTASSERT(rrc.last_released_rnti == 0x46);
|
||||
return;
|
||||
}
|
||||
|
||||
TESTASSERT(s1ap_pdu.type().value == asn1::s1ap::s1ap_pdu_c::types_opts::successful_outcome);
|
||||
TESTASSERT(s1ap_pdu.successful_outcome().proc_code == ASN1_S1AP_ID_ERAB_MODIFY);
|
||||
auto& protocol_ies = s1ap_pdu.successful_outcome().value.erab_modify_resp().protocol_ies;
|
||||
if (event == test_event::wrong_erabid_mod) {
|
||||
TESTASSERT(not protocol_ies.erab_modify_list_bearer_mod_res_present);
|
||||
TESTASSERT(protocol_ies.erab_failed_to_modify_list_present);
|
||||
TESTASSERT(protocol_ies.erab_failed_to_modify_list.value.size() == 1);
|
||||
auto& erab_item = protocol_ies.erab_failed_to_modify_list.value[0].value.erab_item();
|
||||
TESTASSERT(erab_item.erab_id == 6);
|
||||
TESTASSERT(erab_item.cause.type().value == asn1::s1ap::cause_c::types_opts::radio_network);
|
||||
TESTASSERT(erab_item.cause.radio_network().value == asn1::s1ap::cause_radio_network_opts::unknown_erab_id);
|
||||
return;
|
||||
}
|
||||
|
||||
TESTASSERT(protocol_ies.erab_modify_list_bearer_mod_res_present);
|
||||
TESTASSERT(not protocol_ies.erab_failed_to_modify_list_present);
|
||||
TESTASSERT(protocol_ies.erab_modify_list_bearer_mod_res.value.size() == 1);
|
||||
auto& erab_item = protocol_ies.erab_modify_list_bearer_mod_res.value[0].value.erab_modify_item_bearer_mod_res();
|
||||
TESTASSERT(erab_item.erab_id == 5);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
// Setup logging.
|
||||
auto& logger = srslog::fetch_basic_logger("S1AP");
|
||||
logger.set_level(srslog::basic_levels::debug);
|
||||
logger.set_hex_dump_max_size(-1);
|
||||
|
||||
// Start the log backend.
|
||||
srsran::test_init(argc, argv);
|
||||
|
||||
test_s1ap_erab_setup(test_event::success);
|
||||
test_s1ap_erab_setup(test_event::wrong_erabid_mod);
|
||||
test_s1ap_erab_setup(test_event::wrong_mme_s1ap_id);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
*
|
||||
* \section COPYRIGHT
|
||||
*
|
||||
* Copyright 2013-2021 Software Radio Systems Limited
|
||||
*
|
||||
* By using this file, you agree to the terms and conditions set
|
||||
* forth in the LICENSE file which can be found at the top level of
|
||||
* the distribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SRSUE_MAC_COMMON_H
|
||||
#define SRSUE_MAC_COMMON_H
|
||||
|
||||
/**
|
||||
* @brief Common definitions/interfaces between LTE/NR MAC components
|
||||
*
|
||||
* @remark: So far only the trigger types are identical. The BSR report type and LCID mapping is implemented in RAT
|
||||
* specialications.
|
||||
*/
|
||||
namespace srsue {
|
||||
|
||||
// BSR trigger are common between LTE and NR
|
||||
typedef enum { NONE, REGULAR, PADDING, PERIODIC } bsr_trigger_type_t;
|
||||
char* bsr_trigger_type_tostring(bsr_trigger_type_t type);
|
||||
|
||||
} // namespace srsue
|
||||
|
||||
#endif // SRSUE_MAC_COMMON_H
|
@ -0,0 +1,102 @@
|
||||
/**
|
||||
*
|
||||
* \section COPYRIGHT
|
||||
*
|
||||
* Copyright 2013-2021 Software Radio Systems Limited
|
||||
*
|
||||
* By using this file, you agree to the terms and conditions set
|
||||
* forth in the LICENSE file which can be found at the top level of
|
||||
* the distribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SRSUE_MUX_BASE_H
|
||||
#define SRSUE_MUX_BASE_H
|
||||
|
||||
#include "srsran/interfaces/mac_interface_types.h"
|
||||
|
||||
namespace srsue {
|
||||
|
||||
/**
|
||||
* @brief Common base class for UE MUX unit for 4G and 5G RAT
|
||||
*
|
||||
*/
|
||||
class mux_base
|
||||
{
|
||||
public:
|
||||
int setup_lcid(const srsran::logical_channel_config_t& config)
|
||||
{
|
||||
if (has_logical_channel(config.lcid)) {
|
||||
// update settings
|
||||
for (auto& channel : logical_channels) {
|
||||
if (channel.lcid == config.lcid) {
|
||||
channel = config;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// warn user if there is another LCID with same prio
|
||||
for (auto& channel : logical_channels) {
|
||||
if (channel.priority == config.priority && channel.lcid != config.lcid) {
|
||||
srslog::fetch_basic_logger("MAC").error("LCID %d and %d have same priority.", channel.lcid, config.lcid);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// add new entry
|
||||
logical_channels.push_back(config);
|
||||
}
|
||||
|
||||
// sort according to priority (increasing is lower priority)
|
||||
std::sort(logical_channels.begin(), logical_channels.end(), priority_compare);
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
void print_logical_channel_state(const std::string& info)
|
||||
{
|
||||
std::string logline = info;
|
||||
|
||||
for (auto& channel : logical_channels) {
|
||||
logline += "\n";
|
||||
logline += "- lcid=";
|
||||
logline += std::to_string(channel.lcid);
|
||||
logline += ", lcg=";
|
||||
logline += std::to_string(channel.lcg);
|
||||
logline += ", prio=";
|
||||
logline += std::to_string(channel.priority);
|
||||
logline += ", Bj=";
|
||||
logline += std::to_string(channel.Bj);
|
||||
logline += ", PBR=";
|
||||
logline += std::to_string(channel.PBR);
|
||||
logline += ", BSD=";
|
||||
logline += std::to_string(channel.BSD);
|
||||
logline += ", buffer_len=";
|
||||
logline += std::to_string(channel.buffer_len);
|
||||
logline += ", sched_len=";
|
||||
logline += std::to_string(channel.sched_len);
|
||||
}
|
||||
srslog::fetch_basic_logger("MAC").debug("%s", logline.c_str());
|
||||
}
|
||||
|
||||
protected:
|
||||
static bool priority_compare(const srsran::logical_channel_config_t& u1, const srsran::logical_channel_config_t& u2)
|
||||
{
|
||||
return u1.priority <= u2.priority;
|
||||
}
|
||||
|
||||
bool has_logical_channel(const uint32_t& lcid)
|
||||
{
|
||||
for (auto& channel : logical_channels) {
|
||||
if (channel.lcid == lcid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<srsran::logical_channel_config_t> logical_channels;
|
||||
};
|
||||
|
||||
} // namespace srsue
|
||||
|
||||
#endif // SRSUE_MUX_BASE_H
|
@ -0,0 +1,128 @@
|
||||
/**
|
||||
*
|
||||
* \section COPYRIGHT
|
||||
*
|
||||
* Copyright 2013-2021 Software Radio Systems Limited
|
||||
*
|
||||
* By using this file, you agree to the terms and conditions set
|
||||
* forth in the LICENSE file which can be found at the top level of
|
||||
* the distribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SRSUE_PROC_BSR_NR_H
|
||||
#define SRSUE_PROC_BSR_NR_H
|
||||
|
||||
#include <map>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "proc_sr_nr.h"
|
||||
#include "srsran/common/task_scheduler.h"
|
||||
#include "srsran/srslog/srslog.h"
|
||||
#include "srsue/hdr/stack/mac_common/mac_common.h"
|
||||
|
||||
/* Buffer status report procedure */
|
||||
|
||||
namespace srsue {
|
||||
|
||||
class rlc_interface_mac;
|
||||
|
||||
// BSR interface for MUX
|
||||
class bsr_interface_mux_nr
|
||||
{
|
||||
public:
|
||||
// TS 38.321 Sec 6.1.3.1
|
||||
typedef enum { SHORT_BSR, LONG_BSR, SHORT_TRUNC_BSR, LONG_TRUNC_BSR } bsr_format_nr_t;
|
||||
|
||||
// FIXME: this will be replaced
|
||||
typedef struct {
|
||||
bsr_format_nr_t format;
|
||||
uint32_t buff_size[4];
|
||||
} bsr_t;
|
||||
|
||||
/// MUX calls BSR to let it generate a padding BSR if there is space in PDU.
|
||||
virtual bool generate_padding_bsr(uint32_t nof_padding_bytes, bsr_t* bsr) = 0;
|
||||
};
|
||||
|
||||
class mux_interface_bsr_nr
|
||||
{
|
||||
public:
|
||||
/// Inform MUX unit to that a BSR needs to be generated in the next UL transmission.
|
||||
virtual void generate_bsr_mac_ce() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief BSR procedure for NR according to 3GPP TS 38.321 version 15.3.0
|
||||
*
|
||||
* @remark: So far only class scelleton.
|
||||
*/
|
||||
class proc_bsr_nr : public srsran::timer_callback, public bsr_interface_mux_nr
|
||||
{
|
||||
public:
|
||||
explicit proc_bsr_nr(srslog::basic_logger& logger) : logger(logger) {}
|
||||
int init(proc_sr_nr* sr_proc,
|
||||
mux_interface_bsr_nr* mux_,
|
||||
rlc_interface_mac* rlc,
|
||||
srsran::ext_task_sched_handle* task_sched_);
|
||||
void step(uint32_t tti);
|
||||
void reset();
|
||||
int set_config(const srsran::bsr_cfg_nr_t& bsr_cfg);
|
||||
|
||||
int setup_lcid(uint32_t lcid, uint32_t lcg, uint32_t priority);
|
||||
void timer_expired(uint32_t timer_id);
|
||||
uint32_t get_buffer_state();
|
||||
|
||||
/// Called by MAC when an UL grant is received
|
||||
void new_grant_ul(uint32_t grant_size);
|
||||
|
||||
// bool need_to_send_bsr();
|
||||
bool generate_padding_bsr(uint32_t nof_padding_bytes, bsr_t* bsr);
|
||||
void update_bsr_tti_end(const bsr_t* bsr);
|
||||
|
||||
private:
|
||||
const static int QUEUE_STATUS_PERIOD_MS = 1000;
|
||||
|
||||
std::mutex mutex;
|
||||
|
||||
srsran::ext_task_sched_handle* task_sched = nullptr;
|
||||
srslog::basic_logger& logger;
|
||||
rlc_interface_mac* rlc = nullptr;
|
||||
mux_interface_bsr_nr* mux = nullptr;
|
||||
proc_sr_nr* sr = nullptr;
|
||||
|
||||
srsran::bsr_cfg_nr_t bsr_cfg = {};
|
||||
|
||||
bool initiated = false;
|
||||
|
||||
const static int MAX_NOF_LCG = 8;
|
||||
|
||||
typedef struct {
|
||||
int priority;
|
||||
uint32_t old_buffer;
|
||||
uint32_t new_buffer;
|
||||
} lcid_t;
|
||||
|
||||
std::map<uint32_t, lcid_t> lcgs[MAX_NOF_LCG]; // groups LCID in LCG
|
||||
|
||||
bsr_trigger_type_t triggered_bsr_type = NONE;
|
||||
|
||||
void print_state();
|
||||
void set_trigger(bsr_trigger_type_t new_trigger);
|
||||
void update_new_data();
|
||||
void update_old_buffer();
|
||||
bool check_highest_channel();
|
||||
bool check_new_data();
|
||||
bool check_any_channel();
|
||||
uint32_t get_buffer_state_lcg(uint32_t lcg);
|
||||
bool generate_bsr(bsr_t* bsr, uint32_t nof_padding_bytes);
|
||||
|
||||
uint32_t find_max_priority_lcg_with_data();
|
||||
|
||||
srsran::timer_handler::unique_timer timer_periodic;
|
||||
srsran::timer_handler::unique_timer timer_retx;
|
||||
srsran::timer_handler::unique_timer timer_queue_status_print;
|
||||
};
|
||||
|
||||
} // namespace srsue
|
||||
|
||||
#endif // SRSUE_PROC_BSR_NR_H
|
@ -0,0 +1,10 @@
|
||||
#
|
||||
# Copyright 2013-2021 Software Radio Systems Limited
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set
|
||||
# forth in the LICENSE file which can be found at the top level of
|
||||
# the distribution.
|
||||
#
|
||||
|
||||
set(SOURCES mac_common.cc)
|
||||
add_library(srsue_mac_common STATIC ${SOURCES})
|
@ -0,0 +1,32 @@
|
||||
/**
|
||||
*
|
||||
* \section COPYRIGHT
|
||||
*
|
||||
* Copyright 2013-2021 Software Radio Systems Limited
|
||||
*
|
||||
* By using this file, you agree to the terms and conditions set
|
||||
* forth in the LICENSE file which can be found at the top level of
|
||||
* the distribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "srsue/hdr/stack/mac_common/mac_common.h"
|
||||
|
||||
namespace srsue {
|
||||
|
||||
char* bsr_trigger_type_tostring(bsr_trigger_type_t type)
|
||||
{
|
||||
switch (type) {
|
||||
case bsr_trigger_type_t::NONE:
|
||||
return (char*)"none";
|
||||
case bsr_trigger_type_t::REGULAR:
|
||||
return (char*)"Regular";
|
||||
case bsr_trigger_type_t::PADDING:
|
||||
return (char*)"Padding";
|
||||
case bsr_trigger_type_t::PERIODIC:
|
||||
return (char*)"Periodic";
|
||||
}
|
||||
return (char*)"unknown";
|
||||
}
|
||||
|
||||
} // namespace srsue
|
@ -0,0 +1,322 @@
|
||||
/**
|
||||
*
|
||||
* \section COPYRIGHT
|
||||
*
|
||||
* Copyright 2013-2021 Software Radio Systems Limited
|
||||
*
|
||||
* By using this file, you agree to the terms and conditions set
|
||||
* forth in the LICENSE file which can be found at the top level of
|
||||
* the distribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "srsue/hdr/stack/mac_nr/proc_bsr_nr.h"
|
||||
#include "srsran/interfaces/ue_rlc_interfaces.h"
|
||||
#include "srsran/mac/mac_sch_pdu_nr.h"
|
||||
|
||||
namespace srsue {
|
||||
|
||||
int32_t proc_bsr_nr::init(proc_sr_nr* sr_,
|
||||
mux_interface_bsr_nr* mux_,
|
||||
rlc_interface_mac* rlc_,
|
||||
srsran::ext_task_sched_handle* task_sched_)
|
||||
{
|
||||
rlc = rlc_;
|
||||
mux = mux_;
|
||||
sr = sr_;
|
||||
task_sched = task_sched_;
|
||||
|
||||
timer_periodic = task_sched->get_unique_timer();
|
||||
timer_retx = task_sched->get_unique_timer();
|
||||
timer_queue_status_print = task_sched->get_unique_timer();
|
||||
|
||||
reset();
|
||||
|
||||
// Print periodically the LCID queue status
|
||||
auto queue_status_print_task = [this](uint32_t tid) {
|
||||
print_state();
|
||||
timer_queue_status_print.run();
|
||||
};
|
||||
timer_queue_status_print.set(QUEUE_STATUS_PERIOD_MS, queue_status_print_task);
|
||||
timer_queue_status_print.run();
|
||||
|
||||
initiated = true;
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
void proc_bsr_nr::print_state()
|
||||
{
|
||||
char str[128];
|
||||
str[0] = '\0';
|
||||
int n = 0;
|
||||
for (auto& lcg : lcgs) {
|
||||
for (auto& iter : lcg) {
|
||||
n = srsran_print_check(str, 128, n, "%d: %d ", iter.first, iter.second.old_buffer);
|
||||
}
|
||||
}
|
||||
logger.info(
|
||||
"BSR: triggered_bsr_type=%s, LCID QUEUE status: %s", bsr_trigger_type_tostring(triggered_bsr_type), str);
|
||||
}
|
||||
|
||||
void proc_bsr_nr::set_trigger(bsr_trigger_type_t new_trigger)
|
||||
{
|
||||
triggered_bsr_type = new_trigger;
|
||||
|
||||
// Trigger SR always when Regular BSR is triggered in the current TTI. Will be cancelled if a grant is received
|
||||
if (triggered_bsr_type == REGULAR) {
|
||||
logger.debug("BSR: Triggering SR procedure");
|
||||
sr->start();
|
||||
}
|
||||
}
|
||||
|
||||
void proc_bsr_nr::reset()
|
||||
{
|
||||
timer_periodic.stop();
|
||||
timer_retx.stop();
|
||||
|
||||
triggered_bsr_type = NONE;
|
||||
}
|
||||
|
||||
int proc_bsr_nr::set_config(const srsran::bsr_cfg_nr_t& bsr_cfg_)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
|
||||
bsr_cfg = bsr_cfg_;
|
||||
|
||||
if (bsr_cfg_.periodic_timer > 0) {
|
||||
timer_periodic.set(bsr_cfg_.periodic_timer, [this](uint32_t tid) { timer_expired(tid); });
|
||||
logger.info("BSR: Configured timer periodic %d ms", bsr_cfg_.periodic_timer);
|
||||
}
|
||||
if (bsr_cfg_.retx_timer > 0) {
|
||||
timer_retx.set(bsr_cfg_.retx_timer, [this](uint32_t tid) { timer_expired(tid); });
|
||||
logger.info("BSR: Configured timer reTX %d ms", bsr_cfg_.retx_timer);
|
||||
}
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
/* Process Periodic BSR */
|
||||
void proc_bsr_nr::timer_expired(uint32_t timer_id)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
|
||||
// periodicBSR-Timer
|
||||
if (timer_id == timer_periodic.id()) {
|
||||
if (triggered_bsr_type == NONE) {
|
||||
set_trigger(PERIODIC);
|
||||
logger.debug("BSR: Triggering Periodic BSR");
|
||||
}
|
||||
// retxBSR-Timer
|
||||
} else if (timer_id == timer_retx.id()) {
|
||||
// Enable reTx of SR only if periodic timer is not infinity
|
||||
logger.debug("BSR: Timer BSR reTX expired, periodic=%d, channel=%d", bsr_cfg.periodic_timer, check_any_channel());
|
||||
// Triger Regular BSR if UE has available data for transmission on any channel
|
||||
if (check_any_channel()) {
|
||||
set_trigger(REGULAR);
|
||||
logger.debug("BSR: Triggering BSR reTX");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t proc_bsr_nr::get_buffer_state()
|
||||
{
|
||||
uint32_t buffer = 0;
|
||||
for (int i = 0; i < MAX_NOF_LCG; i++) {
|
||||
buffer += get_buffer_state_lcg(i);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Checks if data is available for a channel with higher priority than others
|
||||
bool proc_bsr_nr::check_highest_channel()
|
||||
{
|
||||
// TODO: move 4G implementation to base class or rewrite
|
||||
for (int i = 0; i < MAX_NOF_LCG; i++) {
|
||||
for (std::map<uint32_t, lcid_t>::iterator iter = lcgs[i].begin(); iter != lcgs[i].end(); ++iter) {
|
||||
// If new data available
|
||||
if (iter->second.new_buffer > iter->second.old_buffer) {
|
||||
// Check if this LCID has higher priority than any other LCID ("belong to any LCG") for which data is already
|
||||
// available for transmission
|
||||
bool is_max_priority = true;
|
||||
for (int j = 0; j < MAX_NOF_LCG; j++) {
|
||||
for (std::map<uint32_t, lcid_t>::iterator iter2 = lcgs[j].begin(); iter2 != lcgs[j].end(); ++iter2) {
|
||||
// No max prio LCG if prio isn't higher or LCID already had buffered data
|
||||
if (iter2->second.priority <= iter->second.priority && (iter2->second.old_buffer > 0)) {
|
||||
is_max_priority = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_max_priority) {
|
||||
logger.debug("BSR: New data for lcid=%d with maximum priority in lcg=%d", iter->first, i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool proc_bsr_nr::check_any_channel()
|
||||
{
|
||||
// TODO: move 4G implementation to base class or rewrite
|
||||
for (int i = 0; i < MAX_NOF_LCG; i++) {
|
||||
if (get_buffer_state_lcg(i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Checks if only one logical channel has data avaiable for Tx
|
||||
bool proc_bsr_nr::check_new_data()
|
||||
{
|
||||
// TODO: move 4G implementation to base class or rewrite
|
||||
for (int i = 0; i < MAX_NOF_LCG; i++) {
|
||||
// If there was no data available in any LCID belonging to this LCG
|
||||
if (get_buffer_state_lcg(i) == 0) {
|
||||
for (std::map<uint32_t, lcid_t>::iterator iter = lcgs[i].begin(); iter != lcgs[i].end(); ++iter) {
|
||||
if (iter->second.new_buffer > 0) {
|
||||
logger.debug("BSR: New data available for lcid=%d", iter->first);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void proc_bsr_nr::update_new_data()
|
||||
{
|
||||
// TODO: move 4G implementation to base class or rewrite
|
||||
for (int i = 0; i < MAX_NOF_LCG; i++) {
|
||||
for (std::map<uint32_t, lcid_t>::iterator iter = lcgs[i].begin(); iter != lcgs[i].end(); ++iter) {
|
||||
iter->second.new_buffer = rlc->get_buffer_state(iter->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void proc_bsr_nr::update_old_buffer()
|
||||
{
|
||||
// TODO: move 4G implementation to base class or rewrite
|
||||
for (int i = 0; i < MAX_NOF_LCG; i++) {
|
||||
for (std::map<uint32_t, lcid_t>::iterator iter = lcgs[i].begin(); iter != lcgs[i].end(); ++iter) {
|
||||
iter->second.old_buffer = iter->second.new_buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t proc_bsr_nr::get_buffer_state_lcg(uint32_t lcg)
|
||||
{
|
||||
// TODO: move 4G implementation to base class or rewrite
|
||||
uint32_t n = 0;
|
||||
for (std::map<uint32_t, lcid_t>::iterator iter = lcgs[lcg].begin(); iter != lcgs[lcg].end(); ++iter) {
|
||||
n += iter->second.old_buffer;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// Generate BSR
|
||||
bool proc_bsr_nr::generate_bsr(bsr_t* bsr, uint32_t pdu_space)
|
||||
{
|
||||
// TODO: add BSR generation
|
||||
bool send_bsr = false;
|
||||
return send_bsr;
|
||||
}
|
||||
|
||||
// Called by MAC every TTI
|
||||
// Checks if Regular BSR must be assembled, as defined in 5.4.5
|
||||
// Padding BSR is assembled when called by mux_unit when UL dci is received
|
||||
// Periodic BSR is triggered by the expiration of the timers
|
||||
void proc_bsr_nr::step(uint32_t tti)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
|
||||
if (not initiated) {
|
||||
return;
|
||||
}
|
||||
|
||||
update_new_data();
|
||||
|
||||
// Regular BSR triggered if new data arrives or channel with high priority has new data
|
||||
if (check_new_data() || check_highest_channel()) {
|
||||
logger.debug("BSR: Triggering Regular BSR tti=%d", tti);
|
||||
set_trigger(REGULAR);
|
||||
}
|
||||
|
||||
update_old_buffer();
|
||||
}
|
||||
|
||||
void proc_bsr_nr::new_grant_ul(uint32_t grant_size)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
if (triggered_bsr_type != NONE) {
|
||||
// inform MUX we need to generate a BSR
|
||||
mux->generate_bsr_mac_ce();
|
||||
}
|
||||
|
||||
// TODO: restart retxBSR-Timer
|
||||
}
|
||||
|
||||
// This function is called by MUX only if Regular BSR has not been triggered before
|
||||
bool proc_bsr_nr::generate_padding_bsr(uint32_t nof_padding_bytes, bsr_t* bsr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
|
||||
// TODO: get correct values from mac_sch_pdu_nr
|
||||
const uint32_t SBSR_CE_SUBHEADER_LEN = 1;
|
||||
const uint32_t LBSR_CE_SUBHEADER_LEN = 1;
|
||||
// if the number of padding bits is equal to or larger than the size of the Short BSR plus its subheader but smaller
|
||||
// than the size of the Long BSR plus its subheader
|
||||
if (nof_padding_bytes >= SBSR_CE_SUBHEADER_LEN + srsran::mac_sch_subpdu_nr::sizeof_ce(SHORT_BSR, true) &&
|
||||
nof_padding_bytes <= LBSR_CE_SUBHEADER_LEN + srsran::mac_sch_subpdu_nr::sizeof_ce(LONG_BSR, true)) {
|
||||
// generate padding BSR
|
||||
set_trigger(PADDING);
|
||||
generate_bsr(bsr, nof_padding_bytes);
|
||||
set_trigger(NONE);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int proc_bsr_nr::setup_lcid(uint32_t lcid, uint32_t new_lcg, uint32_t priority)
|
||||
{
|
||||
// TODO: move 4G implementation to base class
|
||||
if (new_lcg > MAX_NOF_LCG) {
|
||||
logger.error("BSR: Invalid lcg=%d for lcid=%d", new_lcg, lcid);
|
||||
return SRSRAN_ERROR;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
|
||||
// First see if it already exists and eliminate it
|
||||
for (int i = 0; i < MAX_NOF_LCG; i++) {
|
||||
if (lcgs[i].count(lcid)) {
|
||||
lcgs[i].erase(lcid);
|
||||
}
|
||||
}
|
||||
// Now add it
|
||||
lcgs[new_lcg][lcid].priority = priority;
|
||||
lcgs[new_lcg][lcid].old_buffer = 0;
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
uint32_t proc_bsr_nr::find_max_priority_lcg_with_data()
|
||||
{
|
||||
// TODO: move 4G implementation to base class or rewrite
|
||||
int32_t max_prio = 99;
|
||||
uint32_t max_idx = 0;
|
||||
for (int i = 0; i < MAX_NOF_LCG; i++) {
|
||||
for (std::map<uint32_t, lcid_t>::iterator iter = lcgs[i].begin(); iter != lcgs[i].end(); ++iter) {
|
||||
if (iter->second.priority < max_prio && iter->second.old_buffer > 0) {
|
||||
max_prio = iter->second.priority;
|
||||
max_idx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return max_idx;
|
||||
}
|
||||
|
||||
} // namespace srsue
|
@ -0,0 +1,251 @@
|
||||
/**
|
||||
*
|
||||
* \section COPYRIGHT
|
||||
*
|
||||
* Copyright 2013-2021 Software Radio Systems Limited
|
||||
*
|
||||
* By using this file, you agree to the terms and conditions set
|
||||
* forth in the LICENSE file which can be found at the top level of
|
||||
* the distribution.
|
||||
*
|
||||
*/
|
||||
#include "srsran/common/buffer_pool.h"
|
||||
#include "srsran/common/common.h"
|
||||
#include "srsran/common/test_common.h"
|
||||
#include "srsran/test/ue_test_interfaces.h"
|
||||
#include "srsue/hdr/stack/mac_nr/mac_nr.h"
|
||||
|
||||
using namespace srsue;
|
||||
|
||||
#define HAVE_PCAP 0
|
||||
#define UE_ID 0
|
||||
|
||||
static std::unique_ptr<srsran::mac_pcap> pcap_handle = nullptr;
|
||||
|
||||
class dummy_phy : public phy_interface_mac_nr
|
||||
{
|
||||
public:
|
||||
dummy_phy() {}
|
||||
void send_prach(const uint32_t prach_occasion_,
|
||||
const int preamble_index_,
|
||||
const float preamble_received_target_power_,
|
||||
const float ta_base_sec_ = 0.0f) override
|
||||
{
|
||||
prach_occasion = prach_occasion_;
|
||||
preamble_index = preamble_index_;
|
||||
preamble_received_target_power = preamble_received_target_power_;
|
||||
}
|
||||
int tx_request(const tx_request_t& request) override { return 0; }
|
||||
int set_ul_grant(std::array<uint8_t, SRSRAN_RAR_UL_GRANT_NBITS>, uint16_t rnti, srsran_rnti_type_t rnti_type) override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void get_last_send_prach(uint32_t* prach_occasion_, uint32_t* preamble_index_, int* preamble_received_target_power_)
|
||||
{
|
||||
*prach_occasion_ = prach_occasion;
|
||||
*preamble_index_ = preamble_index;
|
||||
*preamble_received_target_power_ = preamble_received_target_power;
|
||||
}
|
||||
void sr_send(uint32_t sr_id) override {}
|
||||
|
||||
private:
|
||||
uint32_t prach_occasion = 0;
|
||||
uint32_t preamble_index = 0;
|
||||
int preamble_received_target_power = 0;
|
||||
};
|
||||
|
||||
class rrc_dummy : public rrc_interface_mac
|
||||
{
|
||||
public:
|
||||
rrc_dummy() {}
|
||||
virtual void ra_completed() {}
|
||||
virtual void ra_problem() {}
|
||||
virtual void release_pucch_srs() {}
|
||||
};
|
||||
|
||||
class stack_dummy : public stack_test_dummy
|
||||
{
|
||||
public:
|
||||
void init(mac_nr* mac_, phy_interface_mac_nr* phy_)
|
||||
{
|
||||
mac_h = mac_;
|
||||
phy_h = phy_;
|
||||
}
|
||||
void run_tti(uint32_t tti)
|
||||
{
|
||||
mac_h->run_tti(tti);
|
||||
// flush all events
|
||||
stack_test_dummy::run_tti();
|
||||
}
|
||||
|
||||
private:
|
||||
phy_interface_mac_nr* phy_h = nullptr;
|
||||
mac_nr* mac_h = nullptr;
|
||||
};
|
||||
|
||||
// TODO: refactor to common test dummy components
|
||||
class rlc_dummy : public srsue::rlc_dummy_interface
|
||||
{
|
||||
public:
|
||||
rlc_dummy() : received_bytes(0) {}
|
||||
bool has_data_locked(const uint32_t lcid) final { return ul_queues[lcid] > 0; }
|
||||
uint32_t get_buffer_state(const uint32_t lcid) final { return ul_queues[lcid]; }
|
||||
int read_pdu(uint32_t lcid, uint8_t* payload, uint32_t nof_bytes) final
|
||||
{
|
||||
if (!read_enable || nof_bytes < read_min) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (read_len > 0 && read_len < (int32_t)nof_bytes) {
|
||||
nof_bytes = read_len;
|
||||
}
|
||||
|
||||
uint32_t len = SRSRAN_MIN(ul_queues[lcid], nof_bytes);
|
||||
|
||||
// set payload bytes to LCID so we can check later if the scheduling was correct
|
||||
memset(payload, lcid > 0 ? lcid : 0xf, len);
|
||||
|
||||
// remove from UL queue
|
||||
ul_queues[lcid] -= len;
|
||||
|
||||
return len;
|
||||
};
|
||||
void write_pdu(uint32_t lcid, uint8_t* payload, uint32_t nof_bytes) final
|
||||
{
|
||||
logger.debug(payload, nof_bytes, "Received %d B on LCID %d", nof_bytes, lcid);
|
||||
received_bytes += nof_bytes;
|
||||
}
|
||||
|
||||
void write_sdu(uint32_t lcid, uint32_t nof_bytes) { ul_queues[lcid] += nof_bytes; }
|
||||
uint32_t get_received_bytes() { return received_bytes; }
|
||||
|
||||
void disable_read() { read_enable = false; }
|
||||
void set_read_len(uint32_t len) { read_len = len; }
|
||||
void set_read_min(uint32_t len) { read_min = len; }
|
||||
void reset_queues()
|
||||
{
|
||||
for (auto& q : ul_queues) {
|
||||
q.second = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool read_enable = true;
|
||||
int32_t read_len = -1; // read all
|
||||
uint32_t read_min = 0; // minimum "grant size" for read_pdu() to return data
|
||||
uint32_t received_bytes;
|
||||
srslog::basic_logger& logger = srslog::fetch_basic_logger("RLC");
|
||||
// UL queues where key is LCID and value the queue length
|
||||
std::map<uint32_t, uint32_t> ul_queues;
|
||||
};
|
||||
|
||||
// TODO: Add test
|
||||
int msg3_test()
|
||||
{
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
// Basic PDU generation test
|
||||
int mac_nr_ul_logical_channel_prioritization_test1()
|
||||
{
|
||||
// PDU layout (20B in total)
|
||||
// - 2 B MAC subheader for SCH LCID=4
|
||||
// - 10 B sduPDU
|
||||
// - 1 B subheader padding
|
||||
// - 7 B padding
|
||||
const uint8_t tv[] = {0x04, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x04, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
// dummy layers
|
||||
dummy_phy phy;
|
||||
rlc_dummy rlc;
|
||||
rrc_dummy rrc;
|
||||
stack_dummy stack;
|
||||
|
||||
// the actual MAC
|
||||
mac_nr mac(&stack.task_sched);
|
||||
|
||||
mac_nr_args_t args = {};
|
||||
mac.init(args, &phy, &rlc, &rrc);
|
||||
|
||||
stack.init(&mac, &phy);
|
||||
const uint16_t crnti = 0x1001;
|
||||
|
||||
// generate config (default DRB2 config for EN-DC)
|
||||
std::vector<srsran::logical_channel_config_t> lcids;
|
||||
srsran::logical_channel_config_t config = {};
|
||||
config.lcid = 4;
|
||||
config.lcg = 6;
|
||||
config.PBR = 0;
|
||||
config.BSD = 1000; // 1000ms
|
||||
config.priority = 11;
|
||||
lcids.push_back(config);
|
||||
|
||||
// setup LCIDs in MAC
|
||||
for (auto& channel : lcids) {
|
||||
mac.setup_lcid(channel);
|
||||
}
|
||||
|
||||
srsran::bsr_cfg_nr_t bsr_cfg = {};
|
||||
bsr_cfg.periodic_timer = 20;
|
||||
bsr_cfg.retx_timer = 320;
|
||||
TESTASSERT(mac.set_config(bsr_cfg) == SRSRAN_SUCCESS);
|
||||
|
||||
// write dummy data to DRB2
|
||||
rlc.write_sdu(4, 10);
|
||||
|
||||
// run TTI to setup Bj, BSR should be generated
|
||||
stack.run_tti(0);
|
||||
usleep(100);
|
||||
|
||||
// create UL action and grant and read MAC PDU
|
||||
{
|
||||
mac_interface_phy_nr::tb_action_ul_t ul_action = {};
|
||||
mac_interface_phy_nr::mac_nr_grant_ul_t mac_grant = {};
|
||||
|
||||
mac_grant.rnti = crnti; // make sure MAC picks it up as valid UL grant
|
||||
mac_grant.pid = 0;
|
||||
mac_grant.rnti = 0x1001;
|
||||
mac_grant.tti = 0;
|
||||
mac_grant.tbs = 20;
|
||||
int cc_idx = 0;
|
||||
|
||||
// Send grant to MAC and get action for this TB, 0x
|
||||
mac.new_grant_ul(cc_idx, mac_grant, &ul_action);
|
||||
|
||||
// print generated PDU
|
||||
srslog::fetch_basic_logger("MAC").info(
|
||||
ul_action.tb.payload->msg, mac_grant.tbs, "Generated PDU (%d B)", mac_grant.tbs);
|
||||
#if HAVE_PCAP
|
||||
pcap_handle->write_ul_crnti_nr(
|
||||
ul_action.tb.payload->msg, mac_grant.tbs, mac_grant.rnti, UE_ID, mac_grant.pid, mac_grant.tti);
|
||||
#endif
|
||||
|
||||
TESTASSERT(memcmp(ul_action.tb.payload->msg, tv, sizeof(tv)) == 0);
|
||||
}
|
||||
|
||||
// make sure MAC PDU thread picks up before stopping
|
||||
stack.run_tti(0);
|
||||
mac.stop();
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
#if HAVE_PCAP
|
||||
pcap_handle = std::unique_ptr<srsran::mac_pcap>(new srsran::mac_pcap());
|
||||
pcap_handle->open("mac_test_nr.pcap");
|
||||
#endif
|
||||
|
||||
auto& mac_logger = srslog::fetch_basic_logger("MAC");
|
||||
mac_logger.set_level(srslog::basic_levels::debug);
|
||||
mac_logger.set_hex_dump_max_size(-1);
|
||||
srslog::init();
|
||||
|
||||
TESTASSERT(msg3_test() == SRSRAN_SUCCESS);
|
||||
TESTASSERT(mac_nr_ul_logical_channel_prioritization_test1() == SRSRAN_SUCCESS);
|
||||
|
||||
return SRSRAN_SUCCESS;
|
||||
}
|
Loading…
Reference in New Issue