mirror of
https://gitlab.eurecom.fr/oai/openairinterface5g.git
synced 2026-07-13 04:30:28 +00:00
QoS handling: add Dynamic5QI QoS support/validation, split QoS characteristics by 5QI type
Model non-dynamic vs dynamic 5QI characteristics explicitly and propagate the new layout through NGAP decode and RRC bearer/QoS handling. Changes: - Define `non_dynamic_5qi_t`/`dynamic_5qi_t`, PER/PDB bounds, and embed a `qos_characteristics` union in `pdusession_level_qos_parameter_t` - Populate the new QoS structures in `fill_qos()`, including optional allocations for Dynamic 5QI `fiveQI` and NonDynamic `priorityLevelQos` - Map QoS params to F1AP with `nr_rrc_get_f1_qos_flow_param()` and add range validation for dynamic priority/PDB/PER and non-dynamic 5QI - Populate E1 QoS characteristics from the new layout and update QoS modify handling to manage optional pointer fields (`openair2/RRC/NR/rrc_gNB_NGAP.c`) - Derive a numeric 5QI via `get_qos_fiveqi()`, handle missing-5QI dynamic flows conservatively, and extend dedicated-DRB decisions to fall back to dynamic characteristics - Add a 5QI range assert in F1AP QoS encoding and extend bearer tests with a Dynamic 5QI flow Signed-off-by: Guido Casati <guido.casati@openairinterface.org>
This commit is contained in:
@@ -90,11 +90,56 @@ typedef uint8_t qos_priority_level_t;
|
||||
#define MIN_QOS_PRIORITY_LEVEL 1 // highest priority
|
||||
#define MAX_QOS_PRIORITY_LEVEL 127 // lowest priority
|
||||
|
||||
/* Packet Delay Budget (PDB) - 3GPP TS 23.501 §5.7.3.4
|
||||
* Upper bound for packet delay between UE and UPF N6 termination point (0..1023 ms).
|
||||
* Used for scheduling and link layer configuration (e.g. HARQ target operating points).
|
||||
* For Delay-critical GBR flows, packets delayed more than PDB are counted as lost. */
|
||||
#define MIN_PACKET_DELAY_BUDGET 0
|
||||
#define MAX_PACKET_DELAY_BUDGET 1023
|
||||
|
||||
/* Packet Error Rate (PER) - 3GPP TS 23.501 §5.7.3.5
|
||||
* Upper bound for rate of non-congestion related packet losses (0..9 for scalar/exponent).
|
||||
* Used for link layer protocol configuration (e.g. RLC, HARQ).
|
||||
* PER = Scalar * 10^(-Exponent) */
|
||||
#define MIN_PACKET_ERROR_RATE_SCALAR 0
|
||||
#define MAX_PACKET_ERROR_RATE_SCALAR 9
|
||||
#define MIN_PACKET_ERROR_RATE_EXPONENT 0
|
||||
#define MAX_PACKET_ERROR_RATE_EXPONENT 9
|
||||
|
||||
typedef struct {
|
||||
// Packet Error Rate Scalar (0..9)
|
||||
uint8_t scalar;
|
||||
// Packet Error Rate Exponent (0..9)
|
||||
uint8_t exponent;
|
||||
} qos_per_t;
|
||||
|
||||
/** QoS Characteristics for a standardized or
|
||||
* pre-configured 5QI for downlink and uplink*/
|
||||
typedef struct {
|
||||
uint16_t fiveQI;
|
||||
qos_priority_level_t *qos_priority;
|
||||
} non_dynamic_5qi_t;
|
||||
|
||||
/** QoS Characteristics for a Non-standardised or
|
||||
* not pre-configured 5QI for downlink and uplink. */
|
||||
typedef struct {
|
||||
uint16_t *fiveQI;
|
||||
qos_priority_level_t qos_priority;
|
||||
// Packet Delay Budget (0..1023 ms)
|
||||
int packet_delay_budget;
|
||||
// Packet Error Rate (0..9 for scalar/exponent)
|
||||
qos_per_t per;
|
||||
} dynamic_5qi_t;
|
||||
|
||||
typedef struct pdusession_level_qos_parameter_s {
|
||||
uint8_t qfi;
|
||||
uint64_t fiveQI;
|
||||
qos_priority_level_t qos_priority;
|
||||
// QoS Characteristics
|
||||
fiveQI_t fiveQI_type;
|
||||
union {
|
||||
non_dynamic_5qi_t non_dynamic;
|
||||
dynamic_5qi_t dynamic;
|
||||
} qos_characteristics;
|
||||
// NG-RAN Allocation and Retention Priority
|
||||
qos_arp_t arp;
|
||||
} pdusession_level_qos_parameter_t;
|
||||
|
||||
|
||||
@@ -386,6 +386,7 @@ static F1AP_QoSFlowLevelQoSParameters_t encode_qos_flow_param(const f1ap_qos_flo
|
||||
if (p->qos_type == NON_DYNAMIC) {
|
||||
f1ap.qoS_Characteristics.present = F1AP_QoS_Characteristics_PR_non_Dynamic_5QI;
|
||||
asn1cCalloc(f1ap.qoS_Characteristics.choice.non_Dynamic_5QI, nd);
|
||||
DevAssert(p->nondyn.fiveQI >= MIN_FIVEQI && p->nondyn.fiveQI <= MAX_FIVEQI);
|
||||
nd->fiveQI = p->nondyn.fiveQI;
|
||||
} else {
|
||||
DevAssert(p->qos_type == DYNAMIC);
|
||||
|
||||
@@ -88,7 +88,6 @@ byte_array_t rrc_gNB_encode_RRCReconfiguration(gNB_RRC_UE_t *UE, nr_rrc_reconfig
|
||||
|
||||
nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, uint8_t srb_reest_bitmap, bool drb_reestablish);
|
||||
|
||||
f1ap_qos_flow_param_t get_qos_char_from_qos_flow_param(const pdusession_level_qos_parameter_t *qos_param);
|
||||
void openair_rrc_gNB_configuration(gNB_RRC_INST *rrc, nr_rrc_config_t *configuration);
|
||||
NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish);
|
||||
NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish, bool do_integrity, bool do_ciphering);
|
||||
|
||||
@@ -2887,21 +2887,44 @@ unsigned int mask_flip(unsigned int x) {
|
||||
return((((x>>8) + (x<<8))&0xffff)>>6);
|
||||
}
|
||||
|
||||
/* \bref return F1AP QoS characteristics based on Qos flow parameters */
|
||||
f1ap_qos_flow_param_t get_qos_char_from_qos_flow_param(const pdusession_level_qos_parameter_t *qos_param)
|
||||
/** @brief Get F1AP QoS flow parameters from PDU session QoS parameters
|
||||
* @param qos_param PDU session level QoS parameters from NGAP
|
||||
* @return F1AP QoS flow parameters */
|
||||
static f1ap_qos_flow_param_t nr_rrc_get_f1_qos_flow_param(const pdusession_level_qos_parameter_t *qos_param)
|
||||
{
|
||||
f1ap_qos_flow_param_t qos_char = {0};
|
||||
if (qos_param->fiveQI_type == DYNAMIC) {
|
||||
|
||||
if (qos_param->fiveQI_type == DYNAMIC) { // Dynamic 5QI
|
||||
const dynamic_5qi_t *dyn = &qos_param->qos_characteristics.dynamic;
|
||||
qos_char.qos_type = DYNAMIC;
|
||||
qos_char.dyn.prio = qos_param->qos_priority;
|
||||
qos_char.dyn.pdb = 100; // TODO
|
||||
qos_char.dyn.per.scalar = 10; // TODO
|
||||
qos_char.dyn.per.exponent = 100; // TODO
|
||||
} else {
|
||||
// Mandatory for Dynamic5QI (range 1..127)
|
||||
DevAssert(dyn->qos_priority >= MIN_QOS_PRIORITY_LEVEL && dyn->qos_priority <= MAX_QOS_PRIORITY_LEVEL);
|
||||
qos_char.dyn.prio = dyn->qos_priority;
|
||||
|
||||
// Packet Delay Budget (0..1023)
|
||||
DevAssert(dyn->packet_delay_budget >= MIN_PACKET_DELAY_BUDGET && dyn->packet_delay_budget <= MAX_PACKET_DELAY_BUDGET);
|
||||
qos_char.dyn.pdb = dyn->packet_delay_budget;
|
||||
|
||||
// Packet Error Rate (0..9 for scalar/exponent)
|
||||
const qos_per_t *per = &dyn->per;
|
||||
DevAssert(per->scalar >= MIN_PACKET_ERROR_RATE_SCALAR && per->scalar <= MAX_PACKET_ERROR_RATE_SCALAR);
|
||||
DevAssert(per->exponent >= MIN_PACKET_ERROR_RATE_EXPONENT && per->exponent <= MAX_PACKET_ERROR_RATE_EXPONENT);
|
||||
qos_char.dyn.per.scalar = per->scalar;
|
||||
qos_char.dyn.per.exponent = per->exponent;
|
||||
} else { // Non-Dynamic 5QI
|
||||
const non_dynamic_5qi_t *non_dyn = &qos_param->qos_characteristics.non_dynamic;
|
||||
qos_char.qos_type = NON_DYNAMIC;
|
||||
qos_char.nondyn.fiveQI = qos_param->fiveQI;
|
||||
// 5QI (0..255)
|
||||
const uint16_t five_qi = non_dyn->fiveQI;
|
||||
DevAssert(five_qi >= MIN_FIVEQI && five_qi <= MAX_FIVEQI);
|
||||
qos_char.nondyn.fiveQI = five_qi;
|
||||
// Note: F1AP non-dynamic 5QI doesn't have priority field
|
||||
}
|
||||
|
||||
// Allocation and Retention Priority (ARP)
|
||||
const qos_arp_t *a = &qos_param->arp;
|
||||
// ARP Priority Level (1..15)
|
||||
DevAssert(a->priority_level >= MIN_QOS_ARP_PRIORITY_LEVEL && a->priority_level <= MAX_QOS_ARP_PRIORITY_LEVEL);
|
||||
qos_char.arp.prio = a->priority_level;
|
||||
qos_char.arp.preempt_cap =
|
||||
a->pre_emp_capability == PEC_MAY_TRIGGER_PREEMPTION ? MAY_TRIGGER_PREEMPTION : SHALL_NOT_TRIGGER_PREEMPTION;
|
||||
@@ -2953,7 +2976,7 @@ static f1ap_drb_info_nr_t fill_f1_drb_info_nr(pdusession_t *pdu, const uint8_t *
|
||||
/* QoS flows Flows Mapped to DRB Item */
|
||||
f1ap_drb_flows_mapped_t *flow = &drb_info.flows[i];
|
||||
flow->qfi = qfi;
|
||||
flow->param = get_qos_char_from_qos_flow_param(&qos_param->qos);
|
||||
flow->param = nr_rrc_get_f1_qos_flow_param(&qos_param->qos);
|
||||
// Find flow with highest ARP priority (lowest ARP priority_level value)
|
||||
// (lower ARP priority value = higher priority for admission/preemption)
|
||||
if (highest_priority_flow == NULL || flow->param.arp.prio < highest_arp) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <netinet/sctp.h>
|
||||
#include <openair3/ocp-gtpu/gtp_itf.h>
|
||||
#include <stdbool.h>
|
||||
#include "common/utils/utils.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -285,12 +286,22 @@ static qos_flow_to_setup_t fill_e1_qos_flow_to_setup(const pdusession_level_qos_
|
||||
// QoS Characteristics
|
||||
qos_characteristics_t *qos_characteristics = &qos_flow.qos_params.qos_characteristics;
|
||||
if (qos->fiveQI_type == NON_DYNAMIC) {
|
||||
qos_characteristics->non_dynamic.fiveqi = qos->fiveQI;
|
||||
qos_characteristics->non_dynamic.qos_priority_level = qos->qos_priority;
|
||||
const non_dynamic_5qi_t *non_dynamic = &qos->qos_characteristics.non_dynamic;
|
||||
typeof(qos_characteristics->non_dynamic) *out_non_dynamic = &qos_characteristics->non_dynamic;
|
||||
out_non_dynamic->fiveqi = non_dynamic->fiveQI;
|
||||
if (non_dynamic->qos_priority) {
|
||||
out_non_dynamic->qos_priority_level = *non_dynamic->qos_priority;
|
||||
}
|
||||
} else {
|
||||
qos_characteristics->dynamic.fiveqi = qos->fiveQI;
|
||||
qos_characteristics->dynamic.qos_priority_level = qos->qos_priority;
|
||||
// NOTE: missing packet error rate and delay budget
|
||||
const dynamic_5qi_t *dynamic = &qos->qos_characteristics.dynamic;
|
||||
typeof(qos_characteristics->dynamic) *out_dynamic = &qos_characteristics->dynamic;
|
||||
if (dynamic->fiveQI != NULL) {
|
||||
out_dynamic->fiveqi = *dynamic->fiveQI;
|
||||
}
|
||||
out_dynamic->qos_priority_level = dynamic->qos_priority;
|
||||
out_dynamic->packet_delay_budget = dynamic->packet_delay_budget;
|
||||
out_dynamic->packet_error_rate.per_scalar = dynamic->per.scalar;
|
||||
out_dynamic->packet_error_rate.per_exponent = dynamic->per.exponent;
|
||||
}
|
||||
qos_characteristics->qos_type = qos->fiveQI_type;
|
||||
|
||||
@@ -943,19 +954,50 @@ static void nr_rrc_update_qos(seq_arr_t *list, const int nb_qos, const pdusessio
|
||||
}
|
||||
|
||||
// Validate 5QI value
|
||||
if (!is_5qi_standardized(q_in->fiveQI)) {
|
||||
const non_dynamic_5qi_t *in_non_dynamic = &q_in->qos_characteristics.non_dynamic;
|
||||
const dynamic_5qi_t *in_dynamic = &q_in->qos_characteristics.dynamic;
|
||||
if (q_in->fiveQI_type == NON_DYNAMIC && !is_5qi_standardized(in_non_dynamic->fiveQI)) {
|
||||
LOG_W(NR_RRC,
|
||||
"QoS flow QFI=%d: 5QI %lu is not a standardized value (1-9, 65-90). Skipping QoS flow.\n",
|
||||
"QoS flow QFI=%d: 5QI %u is not a standardized value (1-9, 65-90). Skipping QoS flow.\n",
|
||||
q_in->qfi,
|
||||
q_in->fiveQI);
|
||||
in_non_dynamic->fiveQI);
|
||||
continue;
|
||||
}
|
||||
|
||||
nr_rrc_qos_t *qos = find_qos(list, q_in->qfi);
|
||||
if (qos) {
|
||||
pdusession_level_qos_parameter_t *dst_qos = &qos->qos;
|
||||
AssertFatal(qos->qos.qfi == q_in->qfi, "QoS Flow to modify must match existing one");
|
||||
LOG_I(NR_RRC, "Updating QoS for QFI=%d\n", q_in->qfi);
|
||||
qos->qos = *q_in;
|
||||
DevAssert(dst_qos->fiveQI_type == q_in->fiveQI_type);
|
||||
|
||||
dst_qos->qfi = q_in->qfi;
|
||||
dst_qos->arp = q_in->arp;
|
||||
|
||||
if (q_in->fiveQI_type == DYNAMIC) {
|
||||
dynamic_5qi_t *out_dyn = &dst_qos->qos_characteristics.dynamic;
|
||||
out_dyn->qos_priority = in_dynamic->qos_priority;
|
||||
out_dyn->packet_delay_budget = in_dynamic->packet_delay_budget;
|
||||
out_dyn->per = in_dynamic->per;
|
||||
if (in_dynamic->fiveQI == NULL) {
|
||||
free_and_zero(out_dyn->fiveQI);
|
||||
} else {
|
||||
if (out_dyn->fiveQI == NULL)
|
||||
out_dyn->fiveQI = calloc_or_fail(1, sizeof(*out_dyn->fiveQI));
|
||||
*out_dyn->fiveQI = *in_dynamic->fiveQI;
|
||||
}
|
||||
} else { /* NON_DYNAMIC */
|
||||
non_dynamic_5qi_t *out_non_dyn = &dst_qos->qos_characteristics.non_dynamic;
|
||||
out_non_dyn->fiveQI = in_non_dynamic->fiveQI;
|
||||
|
||||
if (in_non_dynamic->qos_priority == NULL) {
|
||||
free_and_zero(out_non_dyn->qos_priority);
|
||||
} else {
|
||||
if (out_non_dyn->qos_priority == NULL)
|
||||
out_non_dyn->qos_priority = calloc_or_fail(1, sizeof(*out_non_dyn->qos_priority));
|
||||
*out_non_dyn->qos_priority = *in_non_dynamic->qos_priority;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG_E(NR_RRC, "Failed to update QoS for QFI=%d: QoS flow not found\n", q_in->qfi);
|
||||
}
|
||||
|
||||
@@ -11,11 +11,20 @@
|
||||
#include "assertions.h"
|
||||
#include "common/platform_constants.h"
|
||||
#include "common/utils/T/T.h"
|
||||
#include "common/utils/utils.h"
|
||||
#include "ngap_messages_types.h"
|
||||
#include "oai_asn1.h"
|
||||
#include "openair2/LAYER2/nr_pdcp/nr_pdcp_asn1_utils.h"
|
||||
#include "common/utils/alg/find.h"
|
||||
|
||||
#define NO_FIVEQI UINT16_MAX
|
||||
/* Implementation policy: dedicated-DRB heuristic for Dynamic 5QI without a numeric 5QI.
|
||||
* These thresholds are not specified by 3GPP but reflect a conservative mapping choice. */
|
||||
#define DYN5QI_DEDICATED_DRB_PDB_THRESHOLD_MS 50
|
||||
#define DYN5QI_DEDICATED_DRB_PER_SCALAR_MAX 1
|
||||
#define DYN5QI_DEDICATED_DRB_PER_EXPONENT_MIN 6
|
||||
#define DYN5QI_DEDICATED_DRB_QOS_PRIORITY_MAX 10
|
||||
|
||||
static bool eq_qfi(const void *vval, const void *vit)
|
||||
{
|
||||
const int id = *(const int *)vval;
|
||||
@@ -23,6 +32,19 @@ static bool eq_qfi(const void *vval, const void *vit)
|
||||
return elem->qos.qfi == id;
|
||||
}
|
||||
|
||||
static uint16_t get_qos_fiveqi(const pdusession_level_qos_parameter_t *qos)
|
||||
{
|
||||
if (qos->fiveQI_type == NON_DYNAMIC) {
|
||||
DevAssert(qos->qos_characteristics.non_dynamic.fiveQI <= MAX_STANDARDIZED_FIVEQI);
|
||||
return qos->qos_characteristics.non_dynamic.fiveQI;
|
||||
}
|
||||
if (qos->qos_characteristics.dynamic.fiveQI != NULL) {
|
||||
DevAssert(*qos->qos_characteristics.dynamic.fiveQI <= MAX_FIVEQI);
|
||||
return *qos->qos_characteristics.dynamic.fiveQI;
|
||||
}
|
||||
return NO_FIVEQI;
|
||||
}
|
||||
|
||||
/** @brief Retrieves mapped QoS in UE context for the input @param qfi
|
||||
* @return pointer to the found QoS, NULL if not found */
|
||||
nr_rrc_qos_t *find_qos(seq_arr_t *seq, int qfi)
|
||||
@@ -63,12 +85,27 @@ nr_rrc_qos_t *add_qos(seq_arr_t *qos, const pdusession_level_qos_parameter_t *in
|
||||
}
|
||||
|
||||
// Validate 5QI value (TS 23.501 allows dynamically assigned 5QIs)
|
||||
if (in->fiveQI_type == NON_DYNAMIC && !is_5qi_standardized(in->fiveQI)) {
|
||||
LOG_W(NR_RRC, "QoS flow QFI=%d: 5QI %ld is not a standardized value. Skipping QoS flow.\n", in->qfi, in->fiveQI);
|
||||
if (in->fiveQI_type == NON_DYNAMIC && !is_5qi_standardized(in->qos_characteristics.non_dynamic.fiveQI)) {
|
||||
LOG_W(NR_RRC,
|
||||
"QoS flow QFI=%d: 5QI %d is not a standardized value. Skipping QoS flow.\n",
|
||||
in->qfi,
|
||||
in->qos_characteristics.non_dynamic.fiveQI);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
nr_rrc_qos_t item = {.qos = *in};
|
||||
const dynamic_5qi_t *in_dyn = &in->qos_characteristics.dynamic;
|
||||
dynamic_5qi_t *out_dyn = &item.qos.qos_characteristics.dynamic;
|
||||
if (in->fiveQI_type == DYNAMIC && in_dyn->fiveQI != NULL) {
|
||||
out_dyn->fiveQI = calloc_or_fail(1, sizeof(*out_dyn->fiveQI));
|
||||
*out_dyn->fiveQI = *in_dyn->fiveQI;
|
||||
}
|
||||
const non_dynamic_5qi_t *in_non_dyn = &in->qos_characteristics.non_dynamic;
|
||||
non_dynamic_5qi_t *out_non_dyn = &item.qos.qos_characteristics.non_dynamic;
|
||||
if (in->fiveQI_type == NON_DYNAMIC && in_non_dyn->qos_priority != NULL) {
|
||||
out_non_dyn->qos_priority = calloc_or_fail(1, sizeof(*out_non_dyn->qos_priority));
|
||||
*out_non_dyn->qos_priority = *in_non_dyn->qos_priority;
|
||||
}
|
||||
seq_arr_push_back(qos, &item, sizeof(nr_rrc_qos_t));
|
||||
|
||||
// Double check successful add
|
||||
@@ -82,8 +119,11 @@ nr_rrc_qos_t *add_qos(seq_arr_t *qos, const pdusession_level_qos_parameter_t *in
|
||||
/** @brief Free QoS flows list items */
|
||||
static void free_qos(void *ptr)
|
||||
{
|
||||
/*nothing to do*/
|
||||
UNUSED(ptr);
|
||||
nr_rrc_qos_t *q = ptr;
|
||||
if (q->qos.fiveQI_type == DYNAMIC)
|
||||
free_and_zero(q->qos.qos_characteristics.dynamic.fiveQI);
|
||||
if (q->qos.fiveQI_type == NON_DYNAMIC)
|
||||
free_and_zero(q->qos.qos_characteristics.non_dynamic.qos_priority);
|
||||
}
|
||||
|
||||
/** @brief Free QoS flows list */
|
||||
@@ -401,22 +441,40 @@ bool nr_rrc_is_non_gbr_fiveqi(uint16_t five_qi)
|
||||
// Design-specific aggregate cap to prevent over-multiplexing when mixing resource types on the same DRB
|
||||
#define MAX_QOS_FLOWS_PER_DRB_TOTAL 5
|
||||
|
||||
/** @brief Check if 5QI requires dedicated DRB based on QoS characteristics
|
||||
* Delay-critical GBR (5QI 82-90) and high priority and low-PER services
|
||||
* (e.g. mission-critical and live streaming flows) get dedicated DRBs. */
|
||||
static bool nr_rrc_qos_dedicated_drb(const uint8_t five_qi)
|
||||
/** @brief Decide whether a QoS flow should get a dedicated DRB.
|
||||
*
|
||||
* - If a numeric 5QI is available (Non-Dynamic 5QI, or Dynamic 5QI with 5QI present), we apply
|
||||
* the implementation's 5QI-based isolation policy. In particular, Delay-Critical GBR (5QI 82-90)
|
||||
* and selected high priority / low-PER services are isolated on dedicated DRBs.
|
||||
*
|
||||
* - For Dynamic 5QI where the numeric 5QI is absent, 3GPP allows relying on the signaled QoS
|
||||
* characteristics. We use a conservative heuristic based on priority, PDB and PER thresholds */
|
||||
static bool nr_rrc_qos_dedicated_drb(const pdusession_level_qos_parameter_t *qos)
|
||||
{
|
||||
DevAssert(five_qi <= MAX_FIVEQI);
|
||||
// Delay-critical GBR (5QI 82-90) get dedicated DRBs
|
||||
qos_resource_type_t type = five_qi_resource_type[five_qi];
|
||||
|
||||
if (type == QOS_RESOURCE_TYPE_DC_GBR) {
|
||||
return true;
|
||||
const uint16_t five_qi = get_qos_fiveqi(qos);
|
||||
if (five_qi != NO_FIVEQI) {
|
||||
if (!is_5qi_standardized(five_qi)) {
|
||||
return true; // Dynamic non-standardized 5QI: conservative behavior to prevent unintended multiplexing
|
||||
}
|
||||
// Delay-critical GBR (5QI 82-90) get dedicated DRBs
|
||||
qos_resource_type_t type = five_qi_resource_type[five_qi];
|
||||
if (type == QOS_RESOURCE_TYPE_DC_GBR)
|
||||
return true;
|
||||
// High priority and low-PER services get dedicated DRBs
|
||||
return (five_qi == 4 || five_qi == 6 || five_qi == 7 || five_qi == 8 || five_qi == 9 || five_qi == 10 || five_qi == 70
|
||||
|| five_qi == 80 || (five_qi >= 71 && five_qi <= 73));
|
||||
}
|
||||
// High priority and low-PER services get dedicated DRBs
|
||||
return (five_qi == 4 || five_qi == 6 || five_qi == 7 || five_qi == 8
|
||||
|| five_qi == 9 || five_qi == 10 || five_qi == 70
|
||||
|| five_qi == 80 || (five_qi >= 71 && five_qi <= 73));
|
||||
|
||||
// Dynamic 5QI without a numeric 5QI: decide based on signaled characteristics.
|
||||
DevAssert(qos->fiveQI_type == DYNAMIC);
|
||||
const dynamic_5qi_t *dyn = &qos->qos_characteristics.dynamic;
|
||||
const qos_per_t *per = &dyn->per;
|
||||
/* Conservative policy for low-latency / high-reliability / high-priority flows. */
|
||||
const bool low_latency = dyn->packet_delay_budget <= DYN5QI_DEDICATED_DRB_PDB_THRESHOLD_MS;
|
||||
const bool high_reliability =
|
||||
(per->scalar <= DYN5QI_DEDICATED_DRB_PER_SCALAR_MAX && per->exponent >= DYN5QI_DEDICATED_DRB_PER_EXPONENT_MIN);
|
||||
const bool high_priority = dyn->qos_priority <= DYN5QI_DEDICATED_DRB_QOS_PRIORITY_MAX;
|
||||
return low_latency || high_reliability || high_priority;
|
||||
}
|
||||
|
||||
/** @brief Count QoS flows mapped to a specific DRB, grouped by resource type.
|
||||
@@ -443,8 +501,14 @@ static void nr_rrc_count_qos_flows_by_type(const seq_arr_t *qos_flows,
|
||||
// Check if the QoS flow is mapped to the given DRB ID
|
||||
if (qos->drb_id == drb_id) {
|
||||
// Increment the counter depending on the resource type of the QoS flow
|
||||
const uint8_t five_qi = qos->qos.fiveQI;
|
||||
DevAssert(five_qi <= MAX_FIVEQI);
|
||||
const uint16_t five_qi = get_qos_fiveqi(&qos->qos);
|
||||
if (five_qi == NO_FIVEQI || !is_5qi_standardized(five_qi)) {
|
||||
/* Dynamic 5QI may omit the numeric 5QI; in that case we can't classify by 5QI-derived resource type.
|
||||
* Count conservatively to avoid reusing/multiplexing such flows on existing DRBs. */
|
||||
(*dc_gbr_count)++;
|
||||
continue;
|
||||
}
|
||||
DevAssert(five_qi <= MAX_STANDARDIZED_FIVEQI);
|
||||
qos_resource_type_t type = five_qi_resource_type[five_qi];
|
||||
switch (type) {
|
||||
case QOS_RESOURCE_TYPE_DC_GBR:
|
||||
@@ -457,7 +521,7 @@ static void nr_rrc_count_qos_flows_by_type(const seq_arr_t *qos_flows,
|
||||
(*non_gbr_count)++;
|
||||
break;
|
||||
default:
|
||||
LOG_W(NR_RRC, "Unknown resource type for 5QI %lu\n", qos->qos.fiveQI);
|
||||
LOG_W(NR_RRC, "Unknown resource type for 5QI %u\n", five_qi);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -470,13 +534,16 @@ int nr_rrc_find_suitable_drb_for_qos(gNB_RRC_UE_t *UE,
|
||||
const pdusession_level_qos_parameter_t *qos_params,
|
||||
const seq_arr_t *flows)
|
||||
{
|
||||
const uint8_t five_qi = qos_params->fiveQI;
|
||||
DevAssert(five_qi <= MAX_FIVEQI);
|
||||
const uint16_t five_qi = get_qos_fiveqi(qos_params);
|
||||
if (five_qi == NO_FIVEQI || !is_5qi_standardized(five_qi)) {
|
||||
DevAssert(qos_params->fiveQI_type == DYNAMIC);
|
||||
return -1; // Dynamic 5QI without standardized numeric value: no table-based classification.
|
||||
}
|
||||
qos_resource_type_t type = five_qi_resource_type[five_qi];
|
||||
|
||||
/* Dedicated flows (incl. DC-GBR 5QI 82-90): never reuse an existing DRB. Per-DRB DC-GBR
|
||||
* multiplexing limit is one dedicated DRB each. */
|
||||
if (nr_rrc_qos_dedicated_drb(qos_params->fiveQI) || type == QOS_RESOURCE_TYPE_DC_GBR) {
|
||||
if (nr_rrc_qos_dedicated_drb(qos_params) || type == QOS_RESOURCE_TYPE_DC_GBR) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -500,7 +567,7 @@ int nr_rrc_find_suitable_drb_for_qos(gNB_RRC_UE_t *UE,
|
||||
FOR_EACH_SEQ_ARR(nr_rrc_qos_t *, existing_qos, flows) {
|
||||
if (existing_qos->drb_id != drb->drb_id)
|
||||
continue;
|
||||
if (nr_rrc_qos_dedicated_drb(existing_qos->qos.fiveQI)) {
|
||||
if (nr_rrc_qos_dedicated_drb(&existing_qos->qos)) {
|
||||
has_dedicated_flow_on_drb = true;
|
||||
break;
|
||||
}
|
||||
@@ -521,9 +588,9 @@ int nr_rrc_find_suitable_drb_for_qos(gNB_RRC_UE_t *UE,
|
||||
|
||||
if (has_capacity && total_flows_on_drb < MAX_QOS_FLOWS_PER_DRB_TOTAL) {
|
||||
LOG_I(NR_RRC,
|
||||
"Found suitable DRB %d to multiplex 5QI %ld (QFI %d) - current flows: GBR=%d, Non-GBR=%d\n",
|
||||
"Found suitable DRB %d to multiplex 5QI %d (QFI %d) - current flows: GBR=%d, Non-GBR=%d\n",
|
||||
drb->drb_id,
|
||||
qos_params->fiveQI,
|
||||
five_qi,
|
||||
qos_params->qfi,
|
||||
gbr_count,
|
||||
non_gbr_count);
|
||||
@@ -550,6 +617,8 @@ bool nr_rrc_assign_drb_to_qos_flow(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const pd
|
||||
DevAssert(session);
|
||||
DevAssert(qos);
|
||||
|
||||
const uint16_t five_qi = get_qos_fiveqi(&qos->qos);
|
||||
|
||||
// First, try to find an existing suitable DRB
|
||||
int drb_id = nr_rrc_find_suitable_drb_for_qos(UE, session->pdusession_id, &qos->qos, &session->qos);
|
||||
|
||||
@@ -562,7 +631,7 @@ bool nr_rrc_assign_drb_to_qos_flow(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const pd
|
||||
// Store the assigned DRB ID
|
||||
drb_id = rrc_drb->drb_id;
|
||||
is_new_drb = true;
|
||||
LOG_I(NR_RRC, "UE %d: created new DRB %d for QFI %d (5QI %lu)\n", UE->rrc_ue_id, drb_id, qos->qos.qfi, qos->qos.fiveQI);
|
||||
LOG_I(NR_RRC, "UE %d: created new DRB %d for QFI %d (5QI %d)\n", UE->rrc_ue_id, drb_id, qos->qos.qfi, five_qi);
|
||||
}
|
||||
|
||||
// Assign the DRB ID to the QoS flow
|
||||
@@ -570,11 +639,11 @@ bool nr_rrc_assign_drb_to_qos_flow(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const pd
|
||||
qos->drb_id = drb_id;
|
||||
|
||||
LOG_I(NR_RRC,
|
||||
"UE %d: assigned DRB %d to QFI %d (5QI %lu) in PDU session %d\n",
|
||||
"UE %d: assigned DRB %d to QFI %d (5QI %d) in PDU session %d\n",
|
||||
UE->rrc_ue_id,
|
||||
drb_id,
|
||||
qos->qos.qfi,
|
||||
qos->qos.fiveQI,
|
||||
five_qi,
|
||||
session->pdusession_id);
|
||||
|
||||
return is_new_drb;
|
||||
@@ -599,8 +668,11 @@ void nr_rrc_add_bearers(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, pdusession_t
|
||||
nr_rrc_assign_drb_to_qos_flow(rrc, UE, session, qos);
|
||||
/* TS 23.501 §5.7.2.7: the QoS Flow associated with the default QoS rule should be a Non-GBR QoS Flow
|
||||
* from the standardized value range. Therefore, we prefer the DRB mapped from the first Non-GBR QoS flow here. */
|
||||
DevAssert(qos->qos.fiveQI <= MAX_FIVEQI);
|
||||
if (qos->qos.fiveQI_type == NON_DYNAMIC && nr_rrc_is_non_gbr_fiveqi(qos->qos.fiveQI) && !default_set) {
|
||||
const uint16_t five_qi = get_qos_fiveqi(&qos->qos);
|
||||
if (five_qi == NO_FIVEQI)
|
||||
continue; // Dynamic 5QI: not eligible for default QoS rule selection here.
|
||||
DevAssert(five_qi <= MAX_FIVEQI);
|
||||
if (qos->qos.fiveQI_type == NON_DYNAMIC && nr_rrc_is_non_gbr_fiveqi(five_qi) && !default_set) {
|
||||
session->sdap_config.default_drb = qos->drb_id;
|
||||
default_set = true;
|
||||
}
|
||||
|
||||
@@ -126,7 +126,14 @@ static void test_rrc_qos(void)
|
||||
LOG_A(NR_RRC, "Created PDU Session %d for QoS test\n", session_id);
|
||||
|
||||
const int qfi = 4;
|
||||
const pdusession_level_qos_parameter_t param = { .fiveQI = 9, .fiveQI_type = NON_DYNAMIC, .qfi = qfi };
|
||||
const pdusession_level_qos_parameter_t param = {
|
||||
.qfi = qfi,
|
||||
.fiveQI_type = NON_DYNAMIC,
|
||||
.qos_characteristics.non_dynamic =
|
||||
{
|
||||
.fiveQI = 9,
|
||||
},
|
||||
};
|
||||
LOG_A(NR_RRC, "Adding QoS flow with QFI %d\n", qfi);
|
||||
nr_rrc_qos_t *added = add_qos(&in.qos, ¶m);
|
||||
AssertFatal(added, "add_qos failed");
|
||||
@@ -136,6 +143,30 @@ static void test_rrc_qos(void)
|
||||
AssertFatal(found && found == added, "find_qos failed");
|
||||
LOG_A(NR_RRC, "QoS flow find test passed\n");
|
||||
|
||||
const int dyn_qfi = 5;
|
||||
uint16_t dyn_fiveqi = 7;
|
||||
const pdusession_level_qos_parameter_t dyn_param = {
|
||||
.qfi = dyn_qfi,
|
||||
.fiveQI_type = DYNAMIC,
|
||||
.qos_characteristics.dynamic =
|
||||
{
|
||||
.fiveQI = &dyn_fiveqi,
|
||||
.qos_priority = 10,
|
||||
.packet_delay_budget = 20,
|
||||
.per = {.scalar = 1, .exponent = 6},
|
||||
},
|
||||
};
|
||||
LOG_A(NR_RRC, "Adding Dynamic QoS flow with QFI %d\n", dyn_qfi);
|
||||
nr_rrc_qos_t *dyn_added = add_qos(&in.qos, &dyn_param);
|
||||
AssertFatal(dyn_added, "add_qos (dynamic) failed");
|
||||
|
||||
nr_rrc_qos_t *dyn_found = find_qos(&in.qos, dyn_qfi);
|
||||
AssertFatal(dyn_found && dyn_found == dyn_added, "find_qos (dynamic) failed");
|
||||
AssertFatal(dyn_found->qos.fiveQI_type == DYNAMIC, "dynamic flow type mismatch");
|
||||
AssertFatal(dyn_found->qos.qos_characteristics.dynamic.fiveQI != NULL, "dynamic fiveQI missing");
|
||||
AssertFatal(*dyn_found->qos.qos_characteristics.dynamic.fiveQI == dyn_fiveqi, "dynamic fiveQI mismatch");
|
||||
LOG_A(NR_RRC, "Dynamic QoS flow add/find test passed\n");
|
||||
|
||||
seq_arr_free(&pduSessions, free_pdusession);
|
||||
}
|
||||
|
||||
|
||||
@@ -190,12 +190,40 @@ pdusession_level_qos_parameter_t fill_qos(uint8_t qfi, const NGAP_QosFlowLevelQo
|
||||
AssertFatal(qosChar != NULL, "QoS characteristics are NULL\n");
|
||||
if (qosChar->present == NGAP_QosCharacteristics_PR_nonDynamic5QI) {
|
||||
AssertFatal(qosChar->choice.nonDynamic5QI != NULL, "nonDynamic5QI is NULL\n");
|
||||
const NGAP_NonDynamic5QIDescriptor_t *nonDyn = qosChar->choice.nonDynamic5QI;
|
||||
non_dynamic_5qi_t *out_non_dyn = &out.qos_characteristics.non_dynamic;
|
||||
out.fiveQI_type = NON_DYNAMIC;
|
||||
out.fiveQI = qosChar->choice.nonDynamic5QI->fiveQI;
|
||||
/** For NonDynamic5QI, fiveQI is mandatory and indicates standardized or pre-configured 5QI
|
||||
* (5G QoS characteristics are not signaled, they are derived from the 5QI value) */
|
||||
out_non_dyn->fiveQI = nonDyn->fiveQI;
|
||||
// Extract priorityLevelQos if present (optional for NonDynamic5QI)
|
||||
if (nonDyn->priorityLevelQos != NULL) {
|
||||
out_non_dyn->qos_priority = calloc_or_fail(1, sizeof(*out_non_dyn->qos_priority));
|
||||
*out_non_dyn->qos_priority = *nonDyn->priorityLevelQos;
|
||||
DevAssert(*out_non_dyn->qos_priority >= MIN_QOS_PRIORITY_LEVEL && *out_non_dyn->qos_priority <= MAX_QOS_PRIORITY_LEVEL);
|
||||
}
|
||||
} else if (qosChar->present == NGAP_QosCharacteristics_PR_dynamic5QI) {
|
||||
AssertFatal(qosChar->choice.dynamic5QI != NULL, "dynamic5QI is NULL\n");
|
||||
const NGAP_Dynamic5QIDescriptor_t *dyn = qosChar->choice.dynamic5QI;
|
||||
dynamic_5qi_t *out_dyn = &out.qos_characteristics.dynamic;
|
||||
out.fiveQI_type = DYNAMIC;
|
||||
out.fiveQI = *qosChar->choice.dynamic5QI->fiveQI;
|
||||
/** Extract fiveQI if present (optional for Dynamic5QI)
|
||||
* For Dynamic5QI, fiveQI is optional and indicates dynamically assigned 5QI
|
||||
* (5G QoS characteristics are signaled as part of the QoS profile) */
|
||||
if (dyn->fiveQI != NULL) {
|
||||
out_dyn->fiveQI = calloc_or_fail(1, sizeof(*out_dyn->fiveQI));
|
||||
*out_dyn->fiveQI = *dyn->fiveQI;
|
||||
}
|
||||
// Extract priorityLevelQos (mandatory)
|
||||
out_dyn->qos_priority = dyn->priorityLevelQos;
|
||||
DevAssert(out_dyn->qos_priority >= MIN_QOS_PRIORITY_LEVEL && out_dyn->qos_priority <= MAX_QOS_PRIORITY_LEVEL);
|
||||
/** Extract packetDelayBudget (mandatory)
|
||||
* Note: Per 3GPP TS 38.413 §9.3.1.18, this IE is ignored if Extended Packet Delay Budget
|
||||
* is present in iE_Extensions. Extended Packet Delay Budget parsing is not yet implemented. */
|
||||
out_dyn->packet_delay_budget = dyn->packetDelayBudget;
|
||||
// Extract packetErrorRate (mandatory)
|
||||
out_dyn->per.scalar = dyn->packetErrorRate.pERScalar;
|
||||
out_dyn->per.exponent = dyn->packetErrorRate.pERExponent;
|
||||
} else {
|
||||
AssertFatal(0, "Unsupported QoS Characteristics present value: %d\n", qosChar->present);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user