/* GPRS LLC LLGM SAP as per 3GPP TS 44.064 7.2.1 */ /* * (C) 2022 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 const struct value_string osmo_gprs_llc_llgmm_prim_type_names[] = { { OSMO_GPRS_LLC_LLGMM_ASSIGN, "ASSIGN" }, { OSMO_GPRS_LLC_LLGMM_RESET, "RESET" }, { OSMO_GPRS_LLC_LLGMM_TRIGGER, "TRIGGER" }, { OSMO_GPRS_LLC_LLGMM_SUSPEND, "SUSPEND" }, { OSMO_GPRS_LLC_LLGMM_RESUME, "RESUME" }, { OSMO_GPRS_LLC_LLGMM_PAGE, "PAGE" }, { OSMO_GPRS_LLC_LLGMM_IOV, "IOV" }, { OSMO_GPRS_LLC_LLGMM_STATUS, "STATUS" }, { OSMO_GPRS_LLC_LLGMM_PSHO, "PSHO" }, { OSMO_GPRS_LLC_LLGMM_ASSIGN_UP, "ASSIGN-USERPLANE" }, { 0, NULL } }; /* Generate XID message that will cause the GMM to reset */ static int gprs_llc_lle_generate_xid_for_gmm_reset(struct gprs_llc_lle *lle, uint8_t *bytes, int bytes_len, uint32_t iov_ui) { struct gprs_llc_xid_field *fields; const unsigned int fields_len = 2; fields = (struct gprs_llc_xid_field *)talloc_zero_size(lle->llme, sizeof(*fields) * fields_len); OSMO_ASSERT(fields); /* First XID component must be RESET */ fields[0].type = OSMO_GPRS_LLC_XID_T_RESET; fields[1].type = OSMO_GPRS_LLC_XID_T_IOV_UI; fields[1].val = iov_ui; talloc_free(lle->xid); lle->xid = fields; lle->xid_len = fields_len; return gprs_llc_xid_encode(bytes, bytes_len, fields, fields_len); } /******************************** * Primitive allocation: ********************************/ static inline struct osmo_gprs_llc_prim *llc_prim_llgmm_alloc(enum osmo_gprs_llc_llgmm_prim_type type, enum osmo_prim_operation operation, unsigned int l3_len) { return gprs_llc_prim_alloc(OSMO_GPRS_LLC_SAP_LLGMM, type, operation, l3_len); } /* 7.2.1.1 LLGMM-ASSIGN.req (MS/SGSN):*/ struct osmo_gprs_llc_prim *osmo_gprs_llc_prim_alloc_llgmm_assign_req(uint32_t tlli) { struct osmo_gprs_llc_prim *llc_prim; llc_prim = llc_prim_llgmm_alloc(OSMO_GPRS_LLC_LLGMM_ASSIGN, PRIM_OP_REQUEST, 0); llc_prim->llgmm.tlli = tlli; return llc_prim; } /* 7.2.1.2 LLGMM-RESET.req (SGSN):*/ struct osmo_gprs_llc_prim *osmo_gprs_llc_prim_alloc_llgmm_reset_req(uint32_t tlli) { struct osmo_gprs_llc_prim *llc_prim; llc_prim = llc_prim_llgmm_alloc(OSMO_GPRS_LLC_LLGMM_RESET, PRIM_OP_REQUEST, 0); llc_prim->llgmm.tlli = tlli; return llc_prim; } /* 7.2.1.2 LLGMM-RESET.cnf (SGSN): */ struct osmo_gprs_llc_prim *osmo_gprs_llc_prim_alloc_llgmm_reset_cnf(uint32_t tlli) { struct osmo_gprs_llc_prim *llc_prim; llc_prim = llc_prim_llgmm_alloc(OSMO_GPRS_LLC_LLGMM_RESET, PRIM_OP_CONFIRM, 0); llc_prim->llgmm.tlli = tlli; return llc_prim; } /* 7.2.1.3 LLGMM-TRIGGER.req (MS): */ struct osmo_gprs_llc_prim *osmo_gprs_llc_prim_alloc_llgmm_trigger_req(uint32_t tlli, uint8_t cause) { struct osmo_gprs_llc_prim *llc_prim; llc_prim = llc_prim_llgmm_alloc(OSMO_GPRS_LLC_LLGMM_TRIGGER, PRIM_OP_REQUEST, 0); llc_prim->llgmm.tlli = tlli; llc_prim->llgmm.trigger_req.cause = cause; return llc_prim; } /* 7.2.1.4 LLGMM-SUSPEND.req (MS/SGSN): */ struct osmo_gprs_llc_prim *osmo_gprs_llc_prim_alloc_llgmm_suspend_req(uint32_t tlli) { struct osmo_gprs_llc_prim *llc_prim; llc_prim = llc_prim_llgmm_alloc(OSMO_GPRS_LLC_LLGMM_SUSPEND, PRIM_OP_REQUEST, 0); llc_prim->llgmm.tlli = tlli; return llc_prim; } /* 7.2.1.5 LLGMM-RESUME.req (MS/SGSN):*/ struct osmo_gprs_llc_prim *osmo_gprs_llc_prim_alloc_llgmm_resume_req(uint32_t tlli) { struct osmo_gprs_llc_prim *llc_prim; llc_prim = llc_prim_llgmm_alloc(OSMO_GPRS_LLC_LLGMM_RESUME, PRIM_OP_REQUEST, 0); llc_prim->llgmm.tlli = tlli; return llc_prim; } /******************************** * Handling from upper layers: ********************************/ /* 7.2.1.1 LLGMM-ASSIGN.req (MS/SGSN):*/ static int llc_prim_handle_llgmm_assign_req(struct osmo_gprs_llc_prim *llc_prim) { uint32_t old_tlli = llc_prim->llgmm.tlli; uint32_t new_tlli = llc_prim->llgmm.assign_req.tlli_new; int rc = 0; bool free = false; struct gprs_llc_llme *llme; unsigned int i; if (old_tlli == TLLI_UNASSIGNED && new_tlli == TLLI_UNASSIGNED) { LOGLLC(LOGL_NOTICE, "Rx %s: Wrong input: Both old and new TLLI are unassigned!\n", osmo_gprs_llc_prim_name(llc_prim)); rc = -EINVAL; goto ret_free; } llme = gprs_llc_find_llme_by_tlli(old_tlli != TLLI_UNASSIGNED ? old_tlli : new_tlli); if (!llme) { LOGLLC(LOGL_NOTICE, "Rx %s: Unknown TLLI 0x%08x\n", osmo_gprs_llc_prim_name(llc_prim), old_tlli); rc = -ENOKEY; goto ret_free; } LOGLLME(llme, LOGL_NOTICE, "LLGM Assign pre (0x%08x => 0x%08x)\n", old_tlli, new_tlli); if (old_tlli == TLLI_UNASSIGNED && new_tlli != TLLI_UNASSIGNED) { /* TLLI Assignment 8.3.1 */ /* New TLLI shall be assigned and used when (re)transmitting LLC frames */ /* If old TLLI != TLLI_UNASSIGNED was assigned to LLME, then TLLI * old is unassigned. Only TLLI new shall be accepted when * received from peer. */ if (llme->old_tlli != TLLI_UNASSIGNED) { llme->old_tlli = TLLI_UNASSIGNED; llme->tlli = new_tlli; } else { /* If TLLI old == TLLI_UNASSIGNED was assigned to LLME, then this is * TLLI assignmemt according to 8.3.1 */ llme->old_tlli = TLLI_UNASSIGNED; llme->state = OSMO_GPRS_LLC_LLMS_ASSIGNED; /* 8.5.3.1 For all LLE's */ for (i = 0; i < ARRAY_SIZE(llme->lle); i++) { struct gprs_llc_lle *l = &llme->lle[i]; l->state = OSMO_GPRS_LLC_LLES_ASSIGNED_ADM; if (llme->tlli != new_tlli) { /* Only reset state if the new tlli is really changing. * During GMM attachment, the TLLI is already known and * used by LLC (LLME allocated on the fly), and hence it * is expected to keep using previous state with the * same TLLI from here onwards. */ l->vu_send = l->vu_recv = 0; l->retrans_ctr = 0; } /* FIXME Set parameters according to table 9 */ } llme->tlli = new_tlli; } } else if (old_tlli != TLLI_UNASSIGNED && new_tlli != TLLI_UNASSIGNED) { /* TLLI Change 8.3.2 */ /* Both TLLI Old and TLLI New are assigned; use New when * (re)transmitting. Accept both Old and New on Rx */ llme->old_tlli = old_tlli; llme->tlli = new_tlli; llme->state = OSMO_GPRS_LLC_LLMS_ASSIGNED; /* Inform SNDCP about the TLLI change so it can update it too: */ if (old_tlli != new_tlli) gprs_llc_llme_submit_prim_ll_assign_ind(old_tlli, new_tlli); } else if (old_tlli != TLLI_UNASSIGNED && new_tlli == TLLI_UNASSIGNED) { /* TLLI Unassignment 8.3.3) */ llme->tlli = llme->old_tlli = TLLI_UNASSIGNED; llme->state = OSMO_GPRS_LLC_LLMS_ASSIGNED; for (i = 0; i < ARRAY_SIZE(llme->lle); i++) { struct gprs_llc_lle *l = &llme->lle[i]; l->state = OSMO_GPRS_LLC_LLES_UNASSIGNED; } free = true; } LOGLLME(llme, LOGL_NOTICE, "LLGM Assign post (0x%08x => 0x%08x)\n", old_tlli, new_tlli); if (free) gprs_llc_llme_free(llme); ret_free: msgb_free(llc_prim->oph.msg); return rc; } /* 7.2.1.2 LLGMM-RESET.req (SGSN):*/ static int llc_prim_handle_llgmm_reset_req(struct osmo_gprs_llc_prim *llc_prim) { struct gprs_llc_lle *lle; uint8_t xid_bytes[1024]; int xid_bytes_len; int rc = 0; struct gprs_llc_llme *llme = gprs_llc_find_llme_by_tlli(llc_prim->llgmm.tlli); if (!llme) { LOGLLC(LOGL_NOTICE, "Rx %s: Unknown TLLI 0x%08x\n", osmo_gprs_llc_prim_name(llc_prim), llc_prim->llgmm.tlli); rc = -ENOKEY; goto ret_free; } LOGLLME(llme, LOGL_INFO, "%s\n", osmo_gprs_llc_prim_name(llc_prim)); lle = gprs_llc_llme_get_lle(llme, OSMO_GPRS_LLC_SAPI_GMM); rc = osmo_get_rand_id((uint8_t *) &llme->iov_ui, 4); if (rc < 0) { LOGLLME(llme, LOGL_ERROR, "osmo_get_rand_id() failed for LLC XID reset: %s\n", strerror(-rc)); goto ret_free; } /* Generate XID message */ xid_bytes_len = gprs_llc_lle_generate_xid_for_gmm_reset(lle, xid_bytes, sizeof(xid_bytes), llme->iov_ui); if (xid_bytes_len < 0) { rc = -EINVAL; goto ret_free; } /* Reset some of the LLC parameters. See GSM 04.64, 8.5.3.1 */ lle->vu_recv = 0; lle->vu_send = 0; lle->oc_ui_send = 0; lle->oc_ui_recv = 0; rc = gprs_llc_lle_tx_xid(lle, xid_bytes, xid_bytes_len, true); ret_free: msgb_free(llc_prim->oph.msg); return rc; } /* 7.2.1.3 LLGMM-TRIGGER.req (SGSN):*/ static int llc_prim_handle_llgmm_trigger_req(struct osmo_gprs_llc_prim *llc_prim) { struct gprs_llc_lle *lle; int rc; struct gprs_llc_llme *llme = gprs_llc_find_llme_by_tlli(llc_prim->llgmm.tlli); const uint8_t radio_prio = 1; /* Use highest prio for GMM messages, TS 24.008 10.5.7.2 */ if (!llme) { LOGLLC(LOGL_NOTICE, "Rx %s: Unknown TLLI 0x%08x\n", osmo_gprs_llc_prim_name(llc_prim), llc_prim->llgmm.tlli); rc = -ENOKEY; goto ret_free; } LOGLLME(llme, LOGL_INFO, "%s\n", osmo_gprs_llc_prim_name(llc_prim)); lle = gprs_llc_llme_get_lle(llme, OSMO_GPRS_LLC_SAPI_GMM); /* "If there is a frame waiting to be transmitted in the MS, then this * frame shall be transmitted on the corresponding SAPI or optionally a * UI frame with no information field shall be transmitted on any SAPI. * Otherwise if Cause indicates Cell Update and if Cell Notification is * indicated by the SGSN (see 3GPP TS 24.008 [8a]), then a NULL frame * with P=0 shall be transmitted on any SAPI." */ switch (llc_prim->llgmm.trigger_req.cause) { case OSMO_GPRS_LLC_LLGM_TRIGGER_CELL_NOTIFICATION: rc = gprs_llc_lle_tx_null(lle); break; case OSMO_GPRS_LLC_LLGM_TRIGGER_CELL_UPDATE: case OSMO_GPRS_LLC_LLGM_TRIGGER_PAGE_RESP: default: rc = gprs_llc_lle_tx_ui(lle, NULL, 0, false, radio_prio); break; } ret_free: msgb_free(llc_prim->oph.msg); return rc; } /* 7.2.1.4 LLGMM-SUSPEND.req (MS/SGSN):*/ static int llc_prim_handle_llgmm_suspend_req(struct osmo_gprs_llc_prim *llc_prim) { int rc = 0; struct gprs_llc_llme *llme = gprs_llc_find_llme_by_tlli(llc_prim->llgmm.tlli); if (!llme) { LOGLLC(LOGL_NOTICE, "Rx %s: Unknown TLLI 0x%08x\n", osmo_gprs_llc_prim_name(llc_prim), llc_prim->llgmm.tlli); rc = -ENOKEY; goto ret_free; } LOGLLME(llme, LOGL_INFO, "%s\n", osmo_gprs_llc_prim_name(llc_prim)); llme->suspended = true; ret_free: msgb_free(llc_prim->oph.msg); return rc; } /* 7.2.1.5 LLGMM-RESUME.req (MS/SGSN):*/ static int llc_prim_handle_llgmm_resume_req(struct osmo_gprs_llc_prim *llc_prim) { int rc = 0; struct gprs_llc_llme *llme = gprs_llc_find_llme_by_tlli(llc_prim->llgmm.tlli); if (!llme) { LOGLLC(LOGL_NOTICE, "Rx %s: Unknown TLLI 0x%08x\n", osmo_gprs_llc_prim_name(llc_prim), llc_prim->llgmm.tlli); rc = -ENOKEY; goto ret_free; } LOGLLME(llme, LOGL_INFO, "%s\n", osmo_gprs_llc_prim_name(llc_prim)); llme->suspended = false; ret_free: msgb_free(llc_prim->oph.msg); return rc; } /* LLC higher layers push LLC primitive down to LLC layer: */ int gprs_llc_prim_llgmm_upper_down(struct osmo_gprs_llc_prim *llc_prim) { int rc; switch (OSMO_PRIM_HDR(&llc_prim->oph)) { case OSMO_PRIM(OSMO_GPRS_LLC_LLGMM_ASSIGN, PRIM_OP_REQUEST): OSMO_ASSERT(g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_MS || g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN); rc = llc_prim_handle_llgmm_assign_req(llc_prim); break; case OSMO_PRIM(OSMO_GPRS_LLC_LLGMM_RESET, PRIM_OP_REQUEST): OSMO_ASSERT(g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN); rc = llc_prim_handle_llgmm_reset_req(llc_prim); break; case OSMO_PRIM(OSMO_GPRS_LLC_LLGMM_TRIGGER, PRIM_OP_REQUEST): OSMO_ASSERT(g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_MS); rc = llc_prim_handle_llgmm_trigger_req(llc_prim); break; case OSMO_PRIM(OSMO_GPRS_LLC_LLGMM_SUSPEND, PRIM_OP_REQUEST): OSMO_ASSERT(g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_MS || g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN); rc = llc_prim_handle_llgmm_suspend_req(llc_prim); break; case OSMO_PRIM(OSMO_GPRS_LLC_LLGMM_RESUME, PRIM_OP_REQUEST): OSMO_ASSERT(g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_MS || g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN); rc = llc_prim_handle_llgmm_resume_req(llc_prim); break; case OSMO_PRIM(OSMO_GPRS_LLC_LLGMM_IOV, PRIM_OP_REQUEST): OSMO_ASSERT(g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN); rc = gprs_llc_prim_handle_unsupported(llc_prim); break; case OSMO_PRIM(OSMO_GPRS_LLC_LLGMM_PSHO, PRIM_OP_REQUEST): OSMO_ASSERT(g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_MS || g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN); rc = gprs_llc_prim_handle_unsupported(llc_prim); break; case OSMO_PRIM(OSMO_GPRS_LLC_LLGMM_ASSIGN_UP, PRIM_OP_REQUEST): OSMO_ASSERT(g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_MS || g_llc_ctx->location == OSMO_GPRS_LLC_LOCATION_SGSN); rc = gprs_llc_prim_handle_unsupported(llc_prim); break; default: rc = -ENOTSUP; msgb_free(llc_prim->oph.msg); } return rc; }