/* GPRS Radio Resource SAP as per: * 3GPP TS 44.060 4.3 * 3GPP TS 24.007 9.3 * 3GPP TS 44.064 7.2.3 */ /* * (C) 2023 by sysmocom - s.f.m.c. GmbH * * All Rights Reserved * * This program 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define RLCMAC_MSGB_HEADROOM 0 const struct value_string osmo_gprs_rlcmac_prim_sap_names[] = { { OSMO_GPRS_RLCMAC_SAP_GRR, "GRR" }, { OSMO_GPRS_RLCMAC_SAP_GMMRR, "GMMRR" }, { OSMO_GPRS_RLCMAC_SAP_L1CTL, "L1CTL" }, { 0, NULL } }; const struct value_string osmo_gprs_rlcmac_grr_prim_type_names[] = { { OSMO_GPRS_RLCMAC_GRR_DATA, "DATA" }, { OSMO_GPRS_RLCMAC_GRR_UNITDATA, "UNITDATA" }, { 0, NULL } }; const struct value_string osmo_gprs_rlcmac_gmmrr_prim_type_names[] = { { OSMO_GPRS_RLCMAC_GMMRR_ASSIGN, "ASSIGN" }, { OSMO_GPRS_RLCMAC_GMMRR_PAGE, "PAGE" }, { OSMO_GPRS_RLCMAC_GMMRR_LLC_TRANSMITTED, "LLC_TRANSMITTED" }, { 0, NULL } }; const struct value_string osmo_gprs_rlcmac_l1ctl_prim_type_names[] = { { OSMO_GPRS_RLCMAC_L1CTL_RACH, "RACH" }, { OSMO_GPRS_RLCMAC_L1CTL_CCCH_DATA, "CCCH_DATA" }, { OSMO_GPRS_RLCMAC_L1CTL_PDCH_DATA, "PDCH_DATA" }, { OSMO_GPRS_RLCMAC_L1CTL_PDCH_RTS, "PDCH_RTS" }, { OSMO_GPRS_RLCMAC_L1CTL_CFG_UL_TBF, "CFG_UL_TBF" }, { OSMO_GPRS_RLCMAC_L1CTL_CFG_DL_TBF, "CFG_DL_TBF" }, { OSMO_GPRS_RLCMAC_L1CTL_PDCH_ESTABLISH, "PDCH_ESTABLISH" }, { OSMO_GPRS_RLCMAC_L1CTL_PDCH_RELEASE, "PDCH_RELEASE" }, { OSMO_GPRS_RLCMAC_L1CTL_CCCH_READY, "CCCH_READY" }, { 0, NULL } }; const char *osmo_gprs_rlcmac_prim_name(const struct osmo_gprs_rlcmac_prim *rlcmac_prim) { static char name_buf[256]; const char *sap = osmo_gprs_rlcmac_prim_sap_name(rlcmac_prim->oph.sap); const char *op = get_value_string(osmo_prim_op_names, rlcmac_prim->oph.operation); const char *type; switch (rlcmac_prim->oph.sap) { case OSMO_GPRS_RLCMAC_SAP_GRR: type = osmo_gprs_rlcmac_grr_prim_type_name(rlcmac_prim->oph.primitive); break; case OSMO_GPRS_RLCMAC_SAP_GMMRR: type = osmo_gprs_rlcmac_gmmrr_prim_type_name(rlcmac_prim->oph.primitive); break; case OSMO_GPRS_RLCMAC_SAP_L1CTL: type = osmo_gprs_rlcmac_l1ctl_prim_type_name(rlcmac_prim->oph.primitive); break; default: type = "unsupported-rlcmac-sap"; } snprintf(name_buf, sizeof(name_buf), "%s-%s.%s", sap, type, op); return name_buf; } static int rlcmac_up_cb_dummy(struct osmo_gprs_rlcmac_prim *rlcmac_prim, void *user_data) { LOGRLCMAC(LOGL_INFO, "rlcmac_up_cb_dummy(%s)\n", osmo_gprs_rlcmac_prim_name(rlcmac_prim)); return 0; } static int rlcmac_down_cb_dummy(struct osmo_gprs_rlcmac_prim *rlcmac_prim, void *user_data) { LOGRLCMAC(LOGL_INFO, "rlcmac_down_cb_dummy(%s)\n", osmo_gprs_rlcmac_prim_name(rlcmac_prim)); return 0; } /* Set callback used by LLC layer to push primitives to higher layers in protocol stack */ void osmo_gprs_rlcmac_prim_set_up_cb(osmo_gprs_rlcmac_prim_up_cb up_cb, void *up_user_data) { g_rlcmac_ctx->rlcmac_up_cb = up_cb; g_rlcmac_ctx->rlcmac_up_cb_user_data = up_user_data; } /* Set callback used by LLC layer to push primitives to lower layers in protocol stack */ void osmo_gprs_rlcmac_prim_set_down_cb(osmo_gprs_rlcmac_prim_down_cb down_cb, void *down_user_data) { g_rlcmac_ctx->rlcmac_down_cb = down_cb; g_rlcmac_ctx->rlcmac_down_cb_user_data = down_user_data; } /******************************** * Primitive allocation: ********************************/ /* allocate a msgb containing a struct osmo_gprs_rlcmac_prim + optional l3 data */ static struct msgb *gprs_rlcmac_prim_msgb_alloc(unsigned int l3_len) { const int headroom = RLCMAC_MSGB_HEADROOM; const int size = headroom + sizeof(struct osmo_gprs_rlcmac_prim) + l3_len; struct msgb *msg = msgb_alloc_headroom(size, headroom, "rlcmac_prim"); if (!msg) return NULL; msg->l1h = msgb_put(msg, sizeof(struct osmo_gprs_rlcmac_prim)); return msg; } static struct osmo_gprs_rlcmac_prim *gprs_rlcmac_prim_alloc(enum osmo_gprs_rlcmac_prim_sap sap, unsigned int type, enum osmo_prim_operation operation, unsigned int l3_len) { struct msgb *msg = gprs_rlcmac_prim_msgb_alloc(l3_len); struct osmo_gprs_rlcmac_prim *rlcmac_prim = msgb_rlcmac_prim(msg); osmo_prim_init(&rlcmac_prim->oph, sap, type, operation, msg); return rlcmac_prim; } static inline struct osmo_gprs_rlcmac_prim *rlcmac_prim_grr_alloc(enum osmo_gprs_rlcmac_grr_prim_type type, enum osmo_prim_operation operation, unsigned int l3_len) { return gprs_rlcmac_prim_alloc(OSMO_GPRS_RLCMAC_SAP_GRR, type, operation, l3_len); } static inline struct osmo_gprs_rlcmac_prim *rlcmac_prim_gmmrr_alloc(enum osmo_gprs_rlcmac_gmmrr_prim_type type, enum osmo_prim_operation operation, unsigned int l3_len) { return gprs_rlcmac_prim_alloc(OSMO_GPRS_RLCMAC_SAP_GMMRR, type, operation, l3_len); } static inline struct osmo_gprs_rlcmac_prim *rlcmac_prim_l1ctl_alloc(enum osmo_gprs_rlcmac_l1ctl_prim_type type, enum osmo_prim_operation operation, unsigned int l3_len) { return gprs_rlcmac_prim_alloc(OSMO_GPRS_RLCMAC_SAP_L1CTL, type, operation, l3_len); } /* 3GPP TS 44.064 7.2.3.2 GRR-UNITDATA.ind (MS):*/ struct osmo_gprs_rlcmac_prim *gprs_rlcmac_prim_alloc_grr_unitdata_ind( uint32_t tlli, uint8_t *ll_pdu, size_t ll_pdu_len) { struct osmo_gprs_rlcmac_prim *rlcmac_prim; rlcmac_prim = rlcmac_prim_grr_alloc(OSMO_GPRS_RLCMAC_GRR_UNITDATA, PRIM_OP_INDICATION, ll_pdu_len); rlcmac_prim->grr.tlli = tlli; rlcmac_prim->grr.ll_pdu = ll_pdu; rlcmac_prim->grr.ll_pdu_len = ll_pdu_len; return rlcmac_prim; } /* 3GPP TS 44.064 7.2.3.2 GRR-UL-UNITDATA.req (MS):*/ struct osmo_gprs_rlcmac_prim *osmo_gprs_rlcmac_prim_alloc_grr_unitdata_req( uint32_t tlli, uint8_t *ll_pdu, size_t ll_pdu_len) { struct osmo_gprs_rlcmac_prim *rlcmac_prim; rlcmac_prim = rlcmac_prim_grr_alloc(OSMO_GPRS_RLCMAC_GRR_UNITDATA, PRIM_OP_REQUEST, ll_pdu_len); rlcmac_prim->grr.tlli = tlli; rlcmac_prim->grr.ll_pdu = ll_pdu; rlcmac_prim->grr.ll_pdu_len = ll_pdu_len; return rlcmac_prim; } /* 3GPP TS 24.007 9.3.2.1 GMMRR-ASSIGN-REQ:*/ struct osmo_gprs_rlcmac_prim *osmo_gprs_rlcmac_prim_alloc_gmmrr_assign_req(uint32_t old_tlli, uint32_t new_tlli) { struct osmo_gprs_rlcmac_prim *rlcmac_prim; rlcmac_prim = rlcmac_prim_gmmrr_alloc(OSMO_GPRS_RLCMAC_GMMRR_ASSIGN, PRIM_OP_REQUEST, 0); rlcmac_prim->gmmrr.tlli = old_tlli; rlcmac_prim->gmmrr.assign_req.new_tlli = new_tlli; return rlcmac_prim; } /* 3GPP TS 24.007 9.3.2.2 GMMRR-PAGE-IND:*/ struct osmo_gprs_rlcmac_prim *gprs_rlcmac_prim_alloc_gmmrr_page_ind(uint32_t tlli) { struct osmo_gprs_rlcmac_prim *rlcmac_prim; rlcmac_prim = rlcmac_prim_gmmrr_alloc(OSMO_GPRS_RLCMAC_GMMRR_PAGE, PRIM_OP_INDICATION, 0); rlcmac_prim->gmmrr.tlli = tlli; return rlcmac_prim; } /* TS 24.008 4.7.2.1.1: indication towards GMM that an LLC frame other than LLC * NULL frame has been transmitted on the radio interface */ struct osmo_gprs_rlcmac_prim *gprs_rlcmac_prim_alloc_gmmrr_llc_transmitted_ind(uint32_t tlli) { struct osmo_gprs_rlcmac_prim *rlcmac_prim; rlcmac_prim = rlcmac_prim_gmmrr_alloc(OSMO_GPRS_RLCMAC_GMMRR_LLC_TRANSMITTED, PRIM_OP_INDICATION, 0); rlcmac_prim->gmmrr.tlli = tlli; return rlcmac_prim; } /* L1CTL-RACH.req (8bit) */ struct osmo_gprs_rlcmac_prim *gprs_rlcmac_prim_alloc_l1ctl_rach8_req(uint8_t ra) { struct osmo_gprs_rlcmac_prim *rlcmac_prim; rlcmac_prim = rlcmac_prim_l1ctl_alloc(OSMO_GPRS_RLCMAC_L1CTL_RACH, PRIM_OP_REQUEST, 0); rlcmac_prim->l1ctl.rach_req.is_11bit = false; rlcmac_prim->l1ctl.rach_req.ra = ra; return rlcmac_prim; } /* L1CTL-RACH.req (11bit) */ struct osmo_gprs_rlcmac_prim *gprs_rlcmac_prim_alloc_l1ctl_rach11_req(uint16_t ra11, uint8_t synch_seq) { struct osmo_gprs_rlcmac_prim *rlcmac_prim; rlcmac_prim = rlcmac_prim_l1ctl_alloc(OSMO_GPRS_RLCMAC_L1CTL_RACH, PRIM_OP_REQUEST, 0); rlcmac_prim->l1ctl.rach_req.is_11bit = true; rlcmac_prim->l1ctl.rach_req.ra11 = ra11; rlcmac_prim->l1ctl.rach_req.synch_seq = synch_seq; return rlcmac_prim; } /* L1CTL-CCCH_DATA.ind */ struct osmo_gprs_rlcmac_prim *osmo_gprs_rlcmac_prim_alloc_l1ctl_ccch_data_ind(uint32_t fn, uint8_t *data) { struct osmo_gprs_rlcmac_prim *rlcmac_prim; rlcmac_prim = rlcmac_prim_l1ctl_alloc(OSMO_GPRS_RLCMAC_L1CTL_CCCH_DATA, PRIM_OP_INDICATION, 0); rlcmac_prim->l1ctl.ccch_data_ind.fn = fn; rlcmac_prim->l1ctl.ccch_data_ind.data = data; return rlcmac_prim; } /* L1CTL-PDCH_DATA.req */ struct osmo_gprs_rlcmac_prim *gprs_rlcmac_prim_alloc_l1ctl_pdch_data_req(uint8_t ts_nr, uint32_t fn, uint8_t *data, uint8_t data_len) { struct osmo_gprs_rlcmac_prim *rlcmac_prim; rlcmac_prim = rlcmac_prim_l1ctl_alloc(OSMO_GPRS_RLCMAC_L1CTL_PDCH_DATA, PRIM_OP_REQUEST, data_len); rlcmac_prim->l1ctl.pdch_data_req.fn = fn; rlcmac_prim->l1ctl.pdch_data_req.ts_nr = ts_nr; rlcmac_prim->l1ctl.pdch_data_req.data_len = data_len; rlcmac_prim->l1ctl.pdch_data_req.data = data; return rlcmac_prim; } /* L1CTL-PDCH_DATA.cnf */ struct osmo_gprs_rlcmac_prim *osmo_gprs_rlcmac_prim_alloc_l1ctl_pdch_data_cnf(uint8_t ts_nr, uint32_t fn, uint8_t *data, uint8_t data_len) { struct osmo_gprs_rlcmac_prim *rlcmac_prim; rlcmac_prim = rlcmac_prim_l1ctl_alloc(OSMO_GPRS_RLCMAC_L1CTL_PDCH_DATA, PRIM_OP_CONFIRM, 0); rlcmac_prim->l1ctl.pdch_data_cnf.fn = fn; rlcmac_prim->l1ctl.pdch_data_cnf.ts_nr = ts_nr; rlcmac_prim->l1ctl.pdch_data_ind.data_len = data_len; rlcmac_prim->l1ctl.pdch_data_ind.data = data; return rlcmac_prim; } /* L1CTL-PDCH_DATA.ind */ struct osmo_gprs_rlcmac_prim *osmo_gprs_rlcmac_prim_alloc_l1ctl_pdch_data_ind(uint8_t ts_nr, uint32_t fn, uint8_t rx_lev, uint16_t ber10k, int16_t ci_cb, uint8_t *data, uint8_t data_len) { struct osmo_gprs_rlcmac_prim *rlcmac_prim; rlcmac_prim = rlcmac_prim_l1ctl_alloc(OSMO_GPRS_RLCMAC_L1CTL_PDCH_DATA, PRIM_OP_INDICATION, data_len); rlcmac_prim->l1ctl.pdch_data_ind.fn = fn; rlcmac_prim->l1ctl.pdch_data_ind.ts_nr = ts_nr; rlcmac_prim->l1ctl.pdch_data_ind.rx_lev = rx_lev; rlcmac_prim->l1ctl.pdch_data_ind.ber10k = ber10k; rlcmac_prim->l1ctl.pdch_data_ind.ci_cb = ci_cb; rlcmac_prim->l1ctl.pdch_data_ind.data_len = data_len; rlcmac_prim->l1ctl.pdch_data_ind.data = data; return rlcmac_prim; } /* L1CTL-PDCH_RTS.ind */ struct osmo_gprs_rlcmac_prim *osmo_gprs_rlcmac_prim_alloc_l1ctl_pdch_rts_ind(uint8_t ts_nr, uint32_t fn, uint8_t usf) { struct osmo_gprs_rlcmac_prim *rlcmac_prim; rlcmac_prim = rlcmac_prim_l1ctl_alloc(OSMO_GPRS_RLCMAC_L1CTL_PDCH_RTS, PRIM_OP_INDICATION, 0); rlcmac_prim->l1ctl.pdch_rts_ind.fn = fn; rlcmac_prim->l1ctl.pdch_rts_ind.ts_nr = ts_nr; rlcmac_prim->l1ctl.pdch_rts_ind.usf = usf; return rlcmac_prim; } /* L1CTL-CFG_DL_TBF.req */ struct osmo_gprs_rlcmac_prim *gprs_rlcmac_prim_alloc_l1ctl_cfg_dl_tbf_req(uint8_t tbf_nr, uint8_t slotmask, uint8_t dl_tfi) { struct osmo_gprs_rlcmac_prim *rlcmac_prim; rlcmac_prim = rlcmac_prim_l1ctl_alloc(OSMO_GPRS_RLCMAC_L1CTL_CFG_DL_TBF, PRIM_OP_REQUEST, 0); rlcmac_prim->l1ctl.cfg_dl_tbf_req.dl_tbf_nr = tbf_nr; rlcmac_prim->l1ctl.cfg_dl_tbf_req.dl_slotmask = slotmask; rlcmac_prim->l1ctl.cfg_dl_tbf_req.start_fn = UINT32_MAX; /* invalid */ rlcmac_prim->l1ctl.cfg_dl_tbf_req.dl_tfi = dl_tfi; return rlcmac_prim; } /* L1CTL-CFG_UL_TBF.req */ struct osmo_gprs_rlcmac_prim *gprs_rlcmac_prim_alloc_l1ctl_cfg_ul_tbf_req(uint8_t ul_tbf_nr, uint8_t ul_slotmask) { struct osmo_gprs_rlcmac_prim *rlcmac_prim; rlcmac_prim = rlcmac_prim_l1ctl_alloc(OSMO_GPRS_RLCMAC_L1CTL_CFG_UL_TBF, PRIM_OP_REQUEST, 0); rlcmac_prim->l1ctl.cfg_ul_tbf_req.ul_tbf_nr = ul_tbf_nr; rlcmac_prim->l1ctl.cfg_ul_tbf_req.ul_slotmask = ul_slotmask; rlcmac_prim->l1ctl.cfg_ul_tbf_req.start_fn = UINT32_MAX; /* invalid */ return rlcmac_prim; } /* L1CTL-PDCH_ESTABLISH.req */ struct osmo_gprs_rlcmac_prim *gprs_rlcmac_prim_alloc_l1ctl_pdch_est_req(uint8_t ts_nr, uint8_t tsc, uint8_t ta) { struct osmo_gprs_rlcmac_prim *rlcmac_prim; rlcmac_prim = rlcmac_prim_l1ctl_alloc(OSMO_GPRS_RLCMAC_L1CTL_PDCH_ESTABLISH, PRIM_OP_REQUEST, 0); rlcmac_prim->l1ctl.pdch_est_req.ts_nr = ts_nr; rlcmac_prim->l1ctl.pdch_est_req.tsc = tsc; rlcmac_prim->l1ctl.pdch_est_req.ta = ta; return rlcmac_prim; } /* L1CTL-PDCH_RELEASE.req */ struct osmo_gprs_rlcmac_prim *gprs_rlcmac_prim_alloc_l1ctl_pdch_rel_req(void) { struct osmo_gprs_rlcmac_prim *rlcmac_prim; rlcmac_prim = rlcmac_prim_l1ctl_alloc(OSMO_GPRS_RLCMAC_L1CTL_PDCH_RELEASE, PRIM_OP_REQUEST, 0); return rlcmac_prim; } /* L1CTL-CCCH_READY.ind */ struct osmo_gprs_rlcmac_prim *osmo_gprs_rlcmac_prim_alloc_l1ctl_ccch_ready_ind(void) { struct osmo_gprs_rlcmac_prim *rlcmac_prim; rlcmac_prim = rlcmac_prim_l1ctl_alloc(OSMO_GPRS_RLCMAC_L1CTL_CCCH_READY, PRIM_OP_INDICATION, 0); return rlcmac_prim; } int gprs_rlcmac_prim_handle_unsupported(struct osmo_gprs_rlcmac_prim *rlcmac_prim) { LOGRLCMAC(LOGL_ERROR, "Unsupported rlcmac_prim! %s\n", osmo_gprs_rlcmac_prim_name(rlcmac_prim)); msgb_free(rlcmac_prim->oph.msg); return -ENOTSUP; } /******************************** * Handling from/to upper layers: ********************************/ static int rlcmac_prim_handle_grr_data_req(struct osmo_gprs_rlcmac_prim *rlcmac_prim) { int rc = gprs_rlcmac_prim_handle_unsupported(rlcmac_prim); return rc; } static int rlcmac_prim_handle_grr_unitdata_req(struct osmo_gprs_rlcmac_prim *rlcmac_prim) { struct gprs_rlcmac_entity *gre; enum gprs_rlcmac_radio_priority radio_prio; int rc; gre = gprs_rlcmac_find_entity_by_tlli(rlcmac_prim->grr.tlli); if (!gre) { LOGRLCMAC(LOGL_INFO, "TLLI=0x%08x not found, creating entity on the fly\n", rlcmac_prim->grr.tlli); gre = gprs_rlcmac_entity_alloc(rlcmac_prim->grr.tlli); } OSMO_ASSERT(gre); /* Expected values are integers 1..4, trim to known expected radio priorities (3GPP TS 24.008) 10.5.7.2 */ if (OSMO_UNLIKELY(rlcmac_prim->grr.unitdata_req.radio_prio < 1)) { LOGGRE(gre, LOGL_NOTICE, "Rx UNITDATA.req with unexpected radio_prio=%u not in range (1..4)\n", rlcmac_prim->grr.unitdata_req.radio_prio); radio_prio = GPRS_RLCMAC_RADIO_PRIORITY_1; } else if (OSMO_UNLIKELY(rlcmac_prim->grr.unitdata_req.radio_prio > 4)) { LOGGRE(gre, LOGL_NOTICE, "Rx UNITDATA.req with unexpected radio_prio=%u not in range (1..4)\n", rlcmac_prim->grr.unitdata_req.radio_prio); radio_prio = GPRS_RLCMAC_RADIO_PRIORITY_4; } else { radio_prio = (enum gprs_rlcmac_radio_priority)(rlcmac_prim->grr.unitdata_req.radio_prio - 1); } rc = gprs_rlcmac_entity_llc_enqueue(gre, rlcmac_prim->grr.ll_pdu, rlcmac_prim->grr.ll_pdu_len, rlcmac_prim->grr.unitdata_req.sapi, radio_prio); msgb_free(rlcmac_prim->oph.msg); return rc; } int gprs_rlcmac_prim_call_up_cb(struct osmo_gprs_rlcmac_prim *rlcmac_prim) { int rc; if (g_rlcmac_ctx->rlcmac_up_cb) rc = g_rlcmac_ctx->rlcmac_up_cb(rlcmac_prim, g_rlcmac_ctx->rlcmac_up_cb_user_data); else rc = rlcmac_up_cb_dummy(rlcmac_prim, g_rlcmac_ctx->rlcmac_up_cb_user_data); /* Special return value '1' means: do not free */ if (rc != 1) msgb_free(rlcmac_prim->oph.msg); else rc = 0; return rc; } static int gprs_rlcmac_prim_grr_upper_down(struct osmo_gprs_rlcmac_prim *rlcmac_prim) { int rc; switch (OSMO_PRIM_HDR(&rlcmac_prim->oph)) { case OSMO_PRIM(OSMO_GPRS_RLCMAC_GRR_DATA, PRIM_OP_REQUEST): rc = rlcmac_prim_handle_grr_data_req(rlcmac_prim); break; case OSMO_PRIM(OSMO_GPRS_RLCMAC_GRR_UNITDATA, PRIM_OP_REQUEST): rc = rlcmac_prim_handle_grr_unitdata_req(rlcmac_prim); break; default: rc = -ENOTSUP; } return rc; } static int rlcmac_prim_handle_gmmrr_assign_req(struct osmo_gprs_rlcmac_prim *rlcmac_prim) { struct gprs_rlcmac_entity *gre; uint32_t old_tlli = rlcmac_prim->gmmrr.tlli; uint32_t new_tlli = rlcmac_prim->gmmrr.assign_req.new_tlli; int rc = 0; if (old_tlli == GPRS_RLCMAC_TLLI_UNASSIGNED) { /* Case "create" */ if (new_tlli == GPRS_RLCMAC_TLLI_UNASSIGNED) { LOGRLCMAC(LOGL_ERROR, "GMMRR-ASSIGN.req: both old and new TLLIs are unassigned\n"); rc = -EINVAL; goto free_ret; } if ((gre = gprs_rlcmac_find_entity_by_tlli(new_tlli))) { LOGRLCMAC(LOGL_ERROR, "GMMRR-ASSIGN.req: GRE with new TLLI=0x%08x already exists\n", new_tlli); rc = -EINVAL; goto free_ret; } LOGRLCMAC(LOGL_INFO, "GMMRR-ASSIGN.req: creating new entity TLLI=0x%08x\n", new_tlli); gre = gprs_rlcmac_entity_alloc(new_tlli); OSMO_ASSERT(gre); } else if (new_tlli == GPRS_RLCMAC_TLLI_UNASSIGNED) { /* Case "destroy" */ gre = gprs_rlcmac_find_entity_by_tlli(old_tlli); if (!gre) { LOGRLCMAC(LOGL_ERROR, "GMMRR-ASSIGN.req: GRE with TLLI=0x%08x not found\n", old_tlli); rc = -ENOENT; goto free_ret; } gre->old_tlli = old_tlli; gre->tlli = GPRS_RLCMAC_TLLI_UNASSIGNED; gprs_rlcmac_entity_free(gre); gre = NULL; goto free_ret; } else { /* Case "update", both old_tlli and new_tlli are valid */ gre = gprs_rlcmac_find_entity_by_tlli(old_tlli); if (!gre) { LOGRLCMAC(LOGL_ERROR, "GMMRR-ASSIGN.req: GRE with TLLI=0x%08x not found\n", old_tlli); rc = -ENOENT; goto free_ret; } LOGGRE(gre, LOGL_INFO, "Update TLLI 0x%08x -> 0x%08x\n", old_tlli, new_tlli); gre->old_tlli = old_tlli; gre->tlli = new_tlli; } /* cache/update knowledge about this GMME's PTMSI and IMSI. It will be * needed later on to match paging requests: */ gre->ptmsi = rlcmac_prim->gmmrr.assign_req.ptmsi; OSMO_STRLCPY_ARRAY(gre->imsi, rlcmac_prim->gmmrr.assign_req.imsi); free_ret: msgb_free(rlcmac_prim->oph.msg); return rc; } static int gprs_rlcmac_prim_gmmrr_upper_down(struct osmo_gprs_rlcmac_prim *rlcmac_prim) { int rc; switch (OSMO_PRIM_HDR(&rlcmac_prim->oph)) { case OSMO_PRIM(OSMO_GPRS_RLCMAC_GMMRR_ASSIGN, PRIM_OP_REQUEST): rc = rlcmac_prim_handle_gmmrr_assign_req(rlcmac_prim); break; default: rc = -ENOTSUP; } return rc; } /* RLC/MAC higher layers (RLCMAC) push GRR/GMMRR primitive down to RLC/MAC layer: */ int osmo_gprs_rlcmac_prim_upper_down(struct osmo_gprs_rlcmac_prim *rlcmac_prim) { int rc; LOGRLCMAC(LOGL_INFO, "Rx from upper layers: %s\n", osmo_gprs_rlcmac_prim_name(rlcmac_prim)); switch (rlcmac_prim->oph.sap) { case OSMO_GPRS_RLCMAC_SAP_GRR: rc = gprs_rlcmac_prim_grr_upper_down(rlcmac_prim); break; case OSMO_GPRS_RLCMAC_SAP_GMMRR: rc = gprs_rlcmac_prim_gmmrr_upper_down(rlcmac_prim); break; default: rc = gprs_rlcmac_prim_handle_unsupported(rlcmac_prim); } return rc; } /******************************** * Handling from/to lower layers: ********************************/ int gprs_rlcmac_prim_call_down_cb(struct osmo_gprs_rlcmac_prim *rlcmac_prim) { int rc; LOGRLCMAC(LOGL_DEBUG, "Tx to lower layers: %s\n", osmo_gprs_rlcmac_prim_name(rlcmac_prim)); if (g_rlcmac_ctx->rlcmac_down_cb) rc = g_rlcmac_ctx->rlcmac_down_cb(rlcmac_prim, g_rlcmac_ctx->rlcmac_down_cb_user_data); else rc = rlcmac_down_cb_dummy(rlcmac_prim, g_rlcmac_ctx->rlcmac_down_cb_user_data); /* Special return value '1' means: do not free */ if (rc != 1) msgb_free(rlcmac_prim->oph.msg); else rc = 0; return rc; } static int rlcmac_prim_handle_l1ctl_pdch_rts_ind(struct osmo_gprs_rlcmac_prim *rlcmac_prim) { int rc; struct gprs_rlcmac_rts_block_ind bi = { .ts = rlcmac_prim->l1ctl.pdch_rts_ind.ts_nr, .fn = rlcmac_prim->l1ctl.pdch_rts_ind.fn, .usf = rlcmac_prim->l1ctl.pdch_rts_ind.usf, }; LOGRLCMAC(LOGL_DEBUG, "Rx RTS.ind (fn=%u, ts=%u, usf=%u)\n", bi.fn, bi.ts, bi.usf); rc = gprs_rlcmac_rcv_rts_block(&bi); return rc; } static int rlcmac_prim_handle_l1ctl_pdch_data_ind(struct osmo_gprs_rlcmac_prim *rlcmac_prim) { enum gprs_rlcmac_coding_scheme cs; /* ignore empty DATA.ind */ if (OSMO_UNLIKELY(rlcmac_prim->l1ctl.pdch_data_ind.data_len == 0)) { LOGRLCMAC(LOGL_DEBUG, "Dropping DL data block with length 0\n"); return 0; } cs = gprs_rlcmac_mcs_get_by_size_dl(rlcmac_prim->l1ctl.pdch_data_ind.data_len); if (cs == GPRS_RLCMAC_CS_UNKNOWN) { LOGRLCMAC(LOGL_ERROR, "Dropping DL data block with invalid length %u: %s\n", rlcmac_prim->l1ctl.pdch_data_ind.data_len, osmo_hexdump(rlcmac_prim->l1ctl.pdch_data_ind.data, rlcmac_prim->l1ctl.pdch_data_ind.data_len)); return -EINVAL; } /* TODO: handle PTCCH/D (Packet Timing Control CHannel) blocks */ if ((rlcmac_prim->l1ctl.pdch_data_ind.fn % 104) == 12) { LOGRLCMAC(LOGL_DEBUG, "Dropping PTCCH/D block (not implemented)\n"); return 0; } if (gprs_rlcmac_mcs_is_gprs(cs)) return gprs_rlcmac_handle_gprs_dl_block(rlcmac_prim, cs); if (gprs_rlcmac_mcs_is_edge(cs)) { LOGRLCMAC(LOGL_NOTICE, "RX EGPRS DL data block NOT SUPPORTED\n"); return -ENOTSUP; } /* Should never be reached. */ OSMO_ASSERT(0); return -EINVAL; } static int rlcmac_prim_handle_l1ctl_ccch_data_ind(struct osmo_gprs_rlcmac_prim *rlcmac_prim) { int rc; switch (rlcmac_prim->l1ctl.ccch_data_ind.data[2]) { case GSM48_MT_RR_IMM_ASS: rc = gprs_rlcmac_handle_ccch_imm_ass((struct gsm48_imm_ass *)rlcmac_prim->l1ctl.ccch_data_ind.data, rlcmac_prim->l1ctl.ccch_data_ind.fn); break; case GSM48_MT_RR_PAG_REQ_1: rc = gprs_rlcmac_handle_ccch_pag_req1((const struct gsm48_paging1 *)rlcmac_prim->l1ctl.ccch_data_ind.data); break; case GSM48_MT_RR_PAG_REQ_2: rc = gprs_rlcmac_handle_ccch_pag_req2((const struct gsm48_paging2 *)rlcmac_prim->l1ctl.ccch_data_ind.data); break; case GSM48_MT_RR_SYSINFO_13: rc = gprs_rlcmac_handle_bcch_si13((struct gsm48_system_information_type_13 *)rlcmac_prim->l1ctl.ccch_data_ind.data); break; default: rc = -ENOTSUP; } return rc; } static int rlcmac_prim_handle_l1ctl_pdch_data_cnf(struct osmo_gprs_rlcmac_prim *rlcmac_prim) { struct gprs_rlcmac_entity *gre; #if 0 /* TODO: enable once we have originating req data in primitive coming from lower layers. */ /* 3GPP TS 44.060 10.3.2 Uplink RLC/MAC control block: */ enum osmo_gprs_rlcmac_ul_msg_type msg_type; if (rlcmac_prim->l1ctl.pdch_data_cnf.data_len < 1) return -EINVAL; msg_type = (rlcmac_prim->l1ctl.pdch_data_cnf.data & 0xC0) >> 2; if (msg_type != OSMO_GPRS_RLCMAC_UL_MSGT_PACKET_CONTROL_ACK) return -EINVAL; #endif llist_for_each_entry(gre, &g_rlcmac_ctx->gre_list, entry) { if (!gre->ul_tbf) continue; if (!gprs_rlcmac_ul_tbf_waiting_pkt_ctrl_ack_tx_confirmation(gre->ul_tbf, rlcmac_prim->l1ctl.pdch_data_cnf.fn, rlcmac_prim->l1ctl.pdch_data_cnf.ts_nr)) continue; osmo_fsm_inst_dispatch(gre->ul_tbf->state_fsm.fi, GPRS_RLCMAC_TBF_UL_EV_TX_COMPL_PKT_CTRL_ACK, NULL); /* gre->ul_tbf is NULL here. */ break; } return 0; } static int rlcmac_prim_handle_l1ctl_ccch_ready_ind(struct osmo_gprs_rlcmac_prim *rlcmac_prim) { struct gprs_rlcmac_entity *gre; /* Lower layers are synced to CCCH, check if some MS was waiting for * that condition to start packet-access-procedure (see * _defer_pkt_idle_timer_cb) */ llist_for_each_entry(gre, &g_rlcmac_ctx->gre_list, entry) gprs_rlcmac_entity_start_ul_tbf_pkt_acc_proc_if_needed(gre); return 0; } static int gprs_rlcmac_prim_l1ctl_lower_up(struct osmo_gprs_rlcmac_prim *rlcmac_prim) { int rc; switch (OSMO_PRIM_HDR(&rlcmac_prim->oph)) { case OSMO_PRIM(OSMO_GPRS_RLCMAC_L1CTL_PDCH_RTS, PRIM_OP_INDICATION): rc = rlcmac_prim_handle_l1ctl_pdch_rts_ind(rlcmac_prim); break; case OSMO_PRIM(OSMO_GPRS_RLCMAC_L1CTL_PDCH_DATA, PRIM_OP_INDICATION): rc = rlcmac_prim_handle_l1ctl_pdch_data_ind(rlcmac_prim); break; case OSMO_PRIM(OSMO_GPRS_RLCMAC_L1CTL_PDCH_DATA, PRIM_OP_CONFIRM): rc = rlcmac_prim_handle_l1ctl_pdch_data_cnf(rlcmac_prim); break; case OSMO_PRIM(OSMO_GPRS_RLCMAC_L1CTL_CCCH_DATA, PRIM_OP_INDICATION): rc = rlcmac_prim_handle_l1ctl_ccch_data_ind(rlcmac_prim); break; case OSMO_PRIM(OSMO_GPRS_RLCMAC_L1CTL_CCCH_READY, PRIM_OP_INDICATION): rc = rlcmac_prim_handle_l1ctl_ccch_ready_ind(rlcmac_prim); break; default: rc = -ENOTSUP; } return rc; } int osmo_gprs_rlcmac_prim_lower_up(struct osmo_gprs_rlcmac_prim *rlcmac_prim) { OSMO_ASSERT(g_rlcmac_ctx); OSMO_ASSERT(rlcmac_prim); struct msgb *msg = rlcmac_prim->oph.msg; int rc; LOGRLCMAC(LOGL_DEBUG, "Rx from lower layers: %s\n", osmo_gprs_rlcmac_prim_name(rlcmac_prim)); switch (rlcmac_prim->oph.sap) { case OSMO_GPRS_RLCMAC_SAP_L1CTL: rc = gprs_rlcmac_prim_l1ctl_lower_up(rlcmac_prim); break; default: rc = -EINVAL; } /* Special return value '1' means: do not free */ if (rc != 1) msgb_free(msg); else rc = 0; return rc; }