diff --git a/openair2/COMMON/f1ap_messages_def.h b/openair2/COMMON/f1ap_messages_def.h index 7c18378b2b..14a7faa843 100644 --- a/openair2/COMMON/f1ap_messages_def.h +++ b/openair2/COMMON/f1ap_messages_def.h @@ -65,4 +65,3 @@ MESSAGE_DEF(F1AP_UE_CONTEXT_MODIFICATION_REFUSE, MESSAGE_PRIORITY_MED, f1ap_ue_c /* CU -> DU*/ MESSAGE_DEF(F1AP_PAGING, MESSAGE_PRIORITY_MED, f1ap_paging_t, f1ap_paging) -MESSAGE_DEF(F1AP_PAGING_IND, MESSAGE_PRIORITY_MED, f1ap_paging_ind_t, f1ap_paging_ind) diff --git a/openair2/COMMON/f1ap_messages_types.h b/openair2/COMMON/f1ap_messages_types.h index d2153f93be..5f430d0e39 100644 --- a/openair2/COMMON/f1ap_messages_types.h +++ b/openair2/COMMON/f1ap_messages_types.h @@ -68,7 +68,6 @@ #define F1AP_UE_CONTEXT_RELEASE_CMD(mSGpTR) (mSGpTR)->ittiMsg.f1ap_ue_context_release_cmd #define F1AP_UE_CONTEXT_RELEASE_COMPLETE(mSGpTR) (mSGpTR)->ittiMsg.f1ap_ue_context_release_complete -#define F1AP_PAGING_IND(mSGpTR) (mSGpTR)->ittiMsg.f1ap_paging_ind #define F1AP_PAGING(mSGpTR) (mSGpTR)->ittiMsg.f1ap_paging /* Length of the transport layer address string @@ -633,15 +632,6 @@ typedef struct f1ap_ue_context_rel_cplt_t { uint32_t gNB_DU_ue_id; } f1ap_ue_context_rel_cplt_t; -typedef struct f1ap_paging_ind_s { - uint16_t ueidentityindexvalue; - uint64_t fiveg_s_tmsi; - uint8_t fiveg_s_tmsi_length; - plmn_id_t plmn; - uint64_t nr_cellid; - uint8_t paging_drx; -} f1ap_paging_ind_t; - typedef struct f1ap_paging_cell_item_s { plmn_id_t plmn; uint64_t nr_cellid; diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 4b0acd44d4..47c9102468 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -3523,7 +3523,7 @@ void *rrc_gnb_task(void *args_p) { break; case NGAP_PAGING_IND: - rrc_gNB_process_PAGING_IND(msg_p, instance); + rrc_gNB_process_PAGING_IND(RC.nrrrc[instance], instance, &NGAP_PAGING_IND(msg_p)); break; case NGAP_HANDOVER_REQUEST: diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index d86b77a188..dc9ec9c19a 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -84,6 +84,7 @@ #include "nr_pdcp/nr_pdcp_oai_api.h" #include "oai_asn1.h" #include "openair2/F1AP/f1ap_ids.h" +#include "openair2/F1AP/lib/f1ap_paging.h" #include "openair3/SECU/key_nas_deriver.h" #include "rrc_messages_types.h" #include "s1ap_messages_types.h" @@ -1629,57 +1630,67 @@ int rrc_gNB_process_NGAP_PDUSESSION_RELEASE_COMMAND(ngap_pdusession_release_comm return 0; } -int rrc_gNB_process_PAGING_IND(MessageDef *msg_p, instance_t instance) +/** @brief Handle NGAP Paging Indication from the AMF. + * For each TAI in the message, matches (PLMN + TAC) against the gNB configuration; on match, + * builds an F1AP Paging message and distributes it to all DUs that serve cells in that PLMN. */ +int rrc_gNB_process_PAGING_IND(gNB_RRC_INST *rrc, const instance_t instance, const ngap_paging_ind_t *msg) { - ngap_paging_ind_t *msg = &NGAP_PAGING_IND(msg_p); - for (uint16_t tai_idx = 0; tai_idx < msg->n_tai; tai_idx++) { - nr_tai_t *tai = &msg->tai_list[tai_idx]; - plmn_id_t *p = &tai->plmn; - LOG_I(NR_RRC, - "[gNB %ld] TAI List for Paging: MCC=%03d, MNC=%0*d, TAC=%d\n", + DevAssert(rrc); + DevAssert(msg); + + // Construct 5G-S-TMSI from paging identity components + const fiveg_s_tmsi_t *s_tmsi = &msg->ue_paging_identity.s_tmsi; + const uint64_t fiveg_s_tmsi = nr_construct_5g_s_tmsi(s_tmsi->amf_set_id, s_tmsi->amf_pointer, s_tmsi->m_tmsi); + const nr_rrc_config_t *req = &rrc->configuration; + DevAssert(req->num_plmn > 0); + + LOG_D(NR_RRC, "[gNB %ld] Processing NGAP Paging Indication for %d TAIs\n", instance, msg->n_tai); + + // Paging Attempt Information (TS 38.413 §9.3.1.72, TS 38.300 §9.2.5) + // AMF provides attempt count and intended attempts for paging optimization (area expansion, retransmission logic). + if (msg->paging_attempt_info != NULL) { + LOG_D(NR_RRC, + "[gNB %ld] Paging Attempt Count: %u, Intended Number of Attempts: %u\n", instance, - p->mcc, - p->mnc_digit_length, - p->mnc, - tai->tac); - nr_rrc_config_t *req = &RC.nrrrc[instance]->configuration; - for (uint8_t j = 0; j < req->num_plmn; j++) { - plmn_id_t *req_plmn = &req->plmn[j]; - if (req_plmn->mcc == p->mcc && req_plmn->mnc == p->mnc && req->tac == tai->tac) { - for (uint8_t CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++) { - AssertFatal(false, "to be implemented properly\n"); - if (NODE_IS_CU(RC.nrrrc[instance]->node_type)) { - MessageDef *m = itti_alloc_new_message(TASK_RRC_GNB, 0, F1AP_PAGING_IND); - F1AP_PAGING_IND(m).plmn = *req_plmn; - // Construct 5G-S-TMSI-Part1 from paging identity to find UE - const fiveg_s_tmsi_t *s_tmsi = &msg->ue_paging_identity.s_tmsi; - uint64_t s_tmsi_part1 = nr_construct_5g_s_tmsi_part1(s_tmsi->amf_set_id, s_tmsi->amf_pointer, s_tmsi->m_tmsi); - // Try to find UE by 5G-S-TMSI-Part1 - rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_ue_context_5g_s_tmsi_exist(RC.nrrrc[instance], s_tmsi_part1); - if (ue_context_p == NULL) { - LOG_W(NR_RRC, "UE not found by paging identity\n"); - return -1; - } - // UE found: use its associated cell - gNB_RRC_UE_t *UE = &ue_context_p->ue_context; - nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(RC.nrrrc[instance], UE); - if (cell == NULL) { - LOG_W(NR_RRC, "UE %d (rnti %04x) found by paging identity but has no valid cell\n", UE->rrc_ue_id, UE->rnti); - return -1; - } - F1AP_PAGING_IND(m).nr_cellid = cell->info.cell_id; - F1AP_PAGING_IND(m).ueidentityindexvalue = (uint16_t)(msg->ue_paging_identity.s_tmsi.m_tmsi % 1024); - F1AP_PAGING_IND(m).fiveg_s_tmsi = msg->ue_paging_identity.s_tmsi.m_tmsi; - F1AP_PAGING_IND(m).paging_drx = msg->paging_drx ? (uint8_t)*msg->paging_drx : 0; - LOG_E(F1AP, "ueidentityindexvalue %u fiveg_s_tmsi %ld paging_drx %u\n", F1AP_PAGING_IND (m).ueidentityindexvalue, F1AP_PAGING_IND (m).fiveg_s_tmsi, F1AP_PAGING_IND (m).paging_drx); - itti_send_msg_to_task(TASK_CU_F1, instance, m); - } else { - //rrc_gNB_generate_pcch_msg(NGAP_PAGING_IND(msg_p).ue_paging_identity.s_tmsi.m_tmsi,(uint8_t)NGAP_PAGING_IND(msg_p).paging_drx, instance, CC_id); - } // end of nodetype check - } // end of cc loop - } // end of mcc mnc check - } // end of num_plmn - } // end of tai size + msg->paging_attempt_info->paging_attempt_count, + msg->paging_attempt_info->intended_paging_attempts); + } + + if (msg->n_tai > NGAP_MAX_NO_TAI_PAGING) { + LOG_E(NR_RRC, + "[gNB %ld] Paging message error: n_tai (%d) exceeds NGAP_MAX_NO_TAI_PAGING (%d)\n", + instance, + msg->n_tai, + NGAP_MAX_NO_TAI_PAGING); + return -1; + } + + // Build F1AP paging message once for all TAIs + f1ap_paging_t f1ap_msg = {0}; + f1ap_msg.ue_identity_index_value = s_tmsi->m_tmsi % 1024; + f1ap_msg.identity_type = F1AP_PAGING_IDENTITY_CN_UE; + f1ap_msg.identity.cn_ue_paging_identity = fiveg_s_tmsi; + if (msg->paging_drx != NULL) { + /* Mapping 1:1 between NGAP and F1AP Paging DRX */ + f1ap_msg.drx = malloc_or_fail(sizeof(*f1ap_msg.drx)); + *f1ap_msg.drx = (f1ap_paging_drx_t)*msg->paging_drx; + } + if (msg->paging_priority != NULL) { + /* Mapping 1:1 between NGAP and F1AP Paging Priority */ + f1ap_msg.priority = malloc_or_fail(sizeof(*f1ap_msg.priority)); + *f1ap_msg.priority = (f1ap_paging_priority_t)*msg->paging_priority; + } + if (msg->origin != NULL) { + /* Mapping 1:1 between NGAP and F1AP Paging Origin */ + f1ap_msg.origin = malloc_or_fail(sizeof(*f1ap_msg.origin)); + *f1ap_msg.origin = (f1ap_paging_origin_t)*msg->origin; + } + + // For each DU, collect cells matching any AMF TAI (PLMN+TAC) and send + // one Paging with that cell list. + rrc_send_paging_to_dus(rrc, msg->tai_list, msg->n_tai, &f1ap_msg); + + free_f1ap_paging(&f1ap_msg); return 0; } diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.h b/openair2/RRC/NR/rrc_gNB_NGAP.h index 2530b3a803..6ad438c990 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.h +++ b/openair2/RRC/NR/rrc_gNB_NGAP.h @@ -84,7 +84,7 @@ void rrc_gNB_send_NGAP_PDUSESSION_RELEASE_RESPONSE(gNB_RRC_INST *rrc, gNB_RRC_UE void nr_rrc_pdcp_config_security(gNB_RRC_UE_t *UE, bool enable_ciphering); -int rrc_gNB_process_PAGING_IND(MessageDef *msg_p, instance_t instance); +int rrc_gNB_process_PAGING_IND(gNB_RRC_INST *rrc, const instance_t instance, const ngap_paging_ind_t *msg); void rrc_gNB_send_NGAP_HANDOVER_REQUIRED(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 3381d60ce5..76f1ab1741 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -820,3 +820,71 @@ void trigger_f1_reset(gNB_RRC_INST *rrc, sctp_assoc_t du_assoc_id) }; rrc->mac_rrc.f1_reset(du_assoc_id, &reset); } + +/** @brief Distribute paging to DUs serving cells matching any of the requested TAIs. + * Loops DUs once, collects matching cells per DU, sends one message per DU. + * Each cell is added at most once per DU (cells are unique by cell_id within a DU). + * Per 3GPP TS 23.003, TAI is uniquely identified by (PLMN, TAC) combination. + * @param rrc RRC instance + * @param tais Array of TAIs (PLMN + TAC) to match against + * @param n_tai Number of TAIs in the array (must be > 0) + * @param msg Pre-filled F1AP paging message */ +void rrc_send_paging_to_dus(gNB_RRC_INST *rrc, const nr_tai_t tais[], uint8_t n_tai, f1ap_paging_t *msg) +{ + DevAssert(msg && !msg->cells); + DevAssert(n_tai > 0); + + nr_rrc_du_container_t *du = NULL; + // Loop over DUs once, for each DU, collects matching cells + RB_FOREACH (du, rrc_du_tree, &rrc->dus) { + RETURN_IF_INVALID_ASSOC_ID(du->assoc_id); + + // Stack-allocated cell list for this DU iteration + f1ap_paging_cell_item_t cells[F1AP_MAX_NB_CELLS] = {0}; + msg->cells = cells; + msg->n_cells = 0; + + // For each cell in this DU, check if it matches any of the matching TAIs + FOR_EACH_SEQ_ARR (nr_rrc_cell_container_t **, cell, &du->cells) { + if (msg->n_cells >= F1AP_MAX_NB_CELLS) { + LOG_W(F1AP, + "[gNB] Paging: DU assoc_id %d has more than %d matching cells, skipping next cells\n", + du->assoc_id, + F1AP_MAX_NB_CELLS); + break; + } + + const nr_rrc_cell_info_t *info = &(*cell)->info; + const plmn_id_t *cell_plmn = &info->plmn; + const uint16_t cell_tac = info->tac; + // Check if this cell's TAI (PLMN + TAC) matches any of the matching TAIs + for (uint8_t j = 0; j < n_tai; j++) { + const nr_tai_t *req_tai = &tais[j]; + const plmn_id_t *req_plmn = &req_tai->plmn; + const uint16_t req_tac = req_tai->tac; + if (cell_plmn->mcc == req_plmn->mcc && cell_plmn->mnc == req_plmn->mnc + && cell_plmn->mnc_digit_length == req_plmn->mnc_digit_length && cell_tac == req_tac) { + f1ap_paging_cell_item_t *paging_cell = &msg->cells[msg->n_cells++]; + paging_cell->plmn = *cell_plmn; + paging_cell->nr_cellid = info->cell_id; + break; // Found match, no need to check other TAIs + } + } + } + + // Skip DUs with no matching cells + if (msg->n_cells == 0) + continue; + DevAssert(msg->n_cells <= F1AP_MAX_NB_CELLS); + LOG_I(F1AP, + "[gNB] Paging transfer to DU assoc_id %d: ue_identity_index_value=%u, paging_identity=%lu, n_cells=%u\n", + du->assoc_id, + msg->ue_identity_index_value, + msg->identity.cn_ue_paging_identity, + msg->n_cells); + // Send one message to the DU with all matching cells + rrc->mac_rrc.paging_transfer(du->assoc_id, msg); + } + + msg->cells = NULL; // Stack allocation, no need to free +} diff --git a/openair2/RRC/NR/rrc_gNB_du.h b/openair2/RRC/NR/rrc_gNB_du.h index 8942af9af5..343ab5d189 100644 --- a/openair2/RRC/NR/rrc_gNB_du.h +++ b/openair2/RRC/NR/rrc_gNB_du.h @@ -34,6 +34,9 @@ struct nr_rrc_cell_container_t; struct f1ap_gnb_du_configuration_update_s; struct f1ap_served_cell_info_t; +#include "f1ap_messages_types.h" +#include "ngap_messages_types.h" + void rrc_gNB_process_f1_setup_req(struct f1ap_setup_req_s *req, sctp_assoc_t assoc_id); void rrc_CU_process_f1_lost_connection(struct gNB_RRC_INST_s *rrc, struct f1ap_lost_connection_t *lc, sctp_assoc_t assoc_id); void rrc_gNB_process_f1_du_configuration_update(struct f1ap_gnb_du_configuration_update_s *conf_up, sctp_assoc_t assoc_id); @@ -58,4 +61,6 @@ struct nr_rrc_du_container_t *find_target_du(struct gNB_RRC_INST_s *rrc, sctp_as void trigger_f1_reset(struct gNB_RRC_INST_s *rrc, sctp_assoc_t du_assoc_id); +void rrc_send_paging_to_dus(struct gNB_RRC_INST_s *rrc, const nr_tai_t tais[], uint8_t n_tai, f1ap_paging_t *f1ap_msg); + #endif /* RRC_GNB_DU_H_ */