/* GMM states in the MS, 3GPP TS 24.008 ยง 4.1.3.1 */ /* * (C) 2023 by sysmocom - s.f.m.c. GmbH * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ * * Author: Pau Espin Pedrol * * 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 #define X(s) (1 << (s)) static const struct osmo_tdef_state_timeout gmm_ms_fsm_timeouts[32] = { [GPRS_GMM_MS_ST_NULL] = {}, [GPRS_GMM_MS_ST_DEREGISTERED] = {}, [GPRS_GMM_MS_ST_REGISTERED_INITIATED] = { .T = 3310 }, [GPRS_GMM_MS_ST_REGISTERED] = {}, [GPRS_GMM_MS_ST_DEREGISTERED_INITIATED] = {}, [GPRS_GMM_MS_ST_RAU_INITIATED] = { .T = 3330 }, [GPRS_GMM_MS_ST_SR_INITIATED] = {}, }; #define gmm_ms_fsm_state_chg(fi, NEXT_STATE) \ osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, gmm_ms_fsm_timeouts, g_gmm_ctx->T_defs, -1) static int reinit_attach_proc(struct gprs_gmm_ms_fsm_ctx *ctx) { unsigned long val_sec; OSMO_ASSERT(ctx->fi->state == GPRS_GMM_MS_ST_REGISTERED_INITIATED); /* Rearm T3310 */ OSMO_ASSERT(ctx->fi->T == 3310); val_sec = osmo_tdef_get(g_gmm_ctx->T_defs, ctx->fi->T, OSMO_TDEF_S, -1); osmo_timer_schedule(&ctx->fi->timer, val_sec, 0); return gprs_gmm_tx_att_req(ctx->gmme, ctx->attach.type, ctx->attach.with_imsi); } static int reinit_rau_proc(struct gprs_gmm_ms_fsm_ctx *ctx) { unsigned long val_sec; OSMO_ASSERT(ctx->fi->state == GPRS_GMM_MS_ST_RAU_INITIATED); /* Rearm T3330 */ OSMO_ASSERT(ctx->fi->T == 3330); val_sec = osmo_tdef_get(g_gmm_ctx->T_defs, ctx->fi->T, OSMO_TDEF_S, -1); osmo_timer_schedule(&ctx->fi->timer, val_sec, 0); return gprs_gmm_tx_rau_req(ctx->gmme, ctx->rau.type); } static void st_gmm_ms_null_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct gprs_gmm_ms_fsm_ctx *ctx = (struct gprs_gmm_ms_fsm_ctx *)fi->priv; gprs_gmm_gmme_t3316_stop(ctx->gmme); } static void st_gmm_ms_null(struct osmo_fsm_inst *fi, uint32_t event, void *data) { switch (event) { case GPRS_GMM_MS_EV_ENABLE_GPRS_MODE: gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_DEREGISTERED); break; default: OSMO_ASSERT(0); } } static void st_gmm_ms_deregistered_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct gprs_gmm_ms_fsm_ctx *ctx = (struct gprs_gmm_ms_fsm_ctx *)fi->priv; memset(&ctx->attach, 0, sizeof(ctx->attach)); gprs_gmm_gmme_t3312_stop(ctx->gmme); gprs_gmm_gmme_t3316_stop(ctx->gmme); /* TS 24.007 9.5.1.4: informs SM layer that the MS has been GPRS detached, e.g. by timer expiry */ if (prev_state != GPRS_GMM_MS_ST_NULL && ctx->gmme->sess_id != GPRS_GMM_SESS_ID_UNASSIGNED) gprs_gmm_submit_gmmsm_release_ind(ctx->gmme); } static void st_gmm_ms_deregistered(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gprs_gmm_ms_fsm_ctx *ctx = (struct gprs_gmm_ms_fsm_ctx *)fi->priv; int rc; switch (event) { case GPRS_GMM_MS_EV_DISABLE_GPRS_MODE: gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_NULL); break; case GPRS_GMM_MS_EV_ATTACH_REQUESTED: rc = gprs_gmm_tx_att_req(ctx->gmme, ctx->attach.type, ctx->attach.with_imsi); if (rc < 0) return; gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_REGISTERED_INITIATED); break; default: OSMO_ASSERT(0); } } static void st_gmm_ms_registered_initiated(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gprs_gmm_ms_fsm_ctx *ctx = (struct gprs_gmm_ms_fsm_ctx *)fi->priv; uint8_t cause = GMM_CAUSE_MAC_FAIL; int rc; struct gprs_gmm_ms_fsm_attach_ctx att; switch (event) { case GPRS_GMM_MS_EV_ATTACH_REQUESTED: /* Upper layers request us to retry attaching: */ reinit_attach_proc(ctx); break; case GPRS_GMM_MS_EV_ATTACH_REJECTED: cause = *(uint8_t *)data; /* fall-through */ case GPRS_GMM_MS_EV_LOW_LVL_FAIL: /* Update state before announcing event to users. Moving to * Deregistered reset attach ctx, hence do a tmp copy here: */ memcpy(&att, &ctx->attach, sizeof(att)); gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_DEREGISTERED); if (att.explicit_att) { /* Submit GMMREG-ATTACH-REJ as per TS 24.007 Annex C.1 */ rc = gprs_gmm_submit_gmmreg_attach_cnf(ctx->gmme, false, cause); if (rc < 0) return; } if (att.implicit_att) { /* Submit GMMSM-ESTABLISH-CNF as per TS 24.007 Annex C.3 */ rc = gprs_gmm_submit_gmmsm_establish_cnf(ctx->gmme, false, cause); if (rc < 0) return; } break; case GPRS_GMM_MS_EV_ATTACH_ACCEPTED: /* Update state before announcing event to users. */ gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_REGISTERED); if (ctx->attach.explicit_att) { /* Submit GMMREG-ATTACH-CNF as per TS 24.007 Annex C.1 */ rc = gprs_gmm_submit_gmmreg_attach_cnf(ctx->gmme, true, 0); if (rc < 0) return; } if (ctx->attach.implicit_att) { /* Submit GMMSM-ESTABLISH-CNF as per TS 24.007 Annex C.3 */ rc = gprs_gmm_submit_gmmsm_establish_cnf(ctx->gmme, true, 0); if (rc < 0) return; } break; case GPRS_GMM_MS_EV_DETACH_REQUESTED: gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_DEREGISTERED); break; default: OSMO_ASSERT(0); } } static void st_gmm_ms_registered_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct gprs_gmm_ms_fsm_ctx *ctx = (struct gprs_gmm_ms_fsm_ctx *)fi->priv; ctx->attach.req_attempts = 0; } static void st_gmm_ms_registered(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gprs_gmm_ms_fsm_ctx *ctx = (struct gprs_gmm_ms_fsm_ctx *)fi->priv; struct osmo_gprs_llc_prim *llc_prim_tx; int rc; switch (event) { case GPRS_GMM_MS_EV_SR_REQUESTED: gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_REGISTERED); break; case GPRS_GMM_MS_EV_RAU_REQUESTED: /* TS 24.007 C.15: submit LLGMM-SUSPEND-REQ */ llc_prim_tx = osmo_gprs_llc_prim_alloc_llgmm_suspend_req(ctx->gmme->tlli); OSMO_ASSERT(llc_prim_tx); rc = gprs_gmm_prim_call_llc_down_cb(llc_prim_tx); /* Transmit RAU Requested to SGSN: */ rc = gprs_gmm_tx_rau_req(ctx->gmme, ctx->rau.type); if (rc < 0) return; gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_RAU_INITIATED); break; case GPRS_GMM_MS_EV_DETACH_REQUESTED: gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_DEREGISTERED_INITIATED); break; default: OSMO_ASSERT(0); } } static void st_gmm_ms_deregistered_initiated(struct osmo_fsm_inst *fi, uint32_t event, void *data) { switch (event) { case GPRS_GMM_MS_EV_DETACH_ACCEPTED: case GPRS_GMM_MS_EV_LOW_LVL_FAIL: gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_DEREGISTERED); break; case GPRS_GMM_MS_EV_RAU_ACCEPTED: gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_REGISTERED); break; default: OSMO_ASSERT(0); } } static void st_gmm_ms_rau_initiated(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gprs_gmm_ms_fsm_ctx *ctx = (struct gprs_gmm_ms_fsm_ctx *)fi->priv; struct osmo_gprs_llc_prim *llc_prim_tx; enum gsm48_gmm_cause cause; switch (event) { case GPRS_GMM_MS_EV_LOW_LVL_FAIL: gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_REGISTERED); break; case GPRS_GMM_MS_EV_RAU_REJECTED: cause = *(uint8_t *)data; /* 3GPP TS 24.008 Figure 4.1b */ switch (cause) { case GMM_CAUSE_ROAMING_NOTALLOWED: case GMM_CAUSE_NO_SUIT_CELL_IN_LA: case GMM_CAUSE_NOT_AUTH_FOR_CSG: gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_REGISTERED); break; default: gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_DEREGISTERED); break; } break; case GPRS_GMM_MS_EV_RAU_ACCEPTED: gprs_gmm_gmme_t3316_stop(ctx->gmme); /* TS 24.007 C.15: submit LLGM-RESUME-REQ */ llc_prim_tx = osmo_gprs_llc_prim_alloc_llgmm_resume_req(ctx->gmme->tlli); OSMO_ASSERT(llc_prim_tx); gprs_gmm_prim_call_llc_down_cb(llc_prim_tx); gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_REGISTERED); break; default: OSMO_ASSERT(0); } } static void st_gmm_ms_sr_initiated(struct osmo_fsm_inst *fi, uint32_t event, void *data) { switch (event) { case GPRS_GMM_MS_EV_LOW_LVL_FAIL: gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_REGISTERED); break; case GPRS_GMM_MS_EV_SR_REJECTED: gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_REGISTERED); break; case GPRS_GMM_MS_EV_SR_ACCEPTED: gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_REGISTERED); break; default: OSMO_ASSERT(0); } } void gmm_ms_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) { switch (event) { case GPRS_GMM_MS_EV_DETACH_REQUESTED_POWEROFF: gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_DEREGISTERED); break; default: OSMO_ASSERT(0); } } static struct osmo_fsm_state gmm_ms_fsm_states[] = { [GPRS_GMM_MS_ST_NULL] = { .in_event_mask = X(GPRS_GMM_MS_EV_ENABLE_GPRS_MODE), .out_state_mask = X(GPRS_GMM_MS_ST_DEREGISTERED), .name = "Null", .onenter = st_gmm_ms_null_on_enter, .action = st_gmm_ms_null, }, [GPRS_GMM_MS_ST_DEREGISTERED] = { .in_event_mask = X(GPRS_GMM_MS_EV_DISABLE_GPRS_MODE) | X(GPRS_GMM_MS_EV_ATTACH_REQUESTED), .out_state_mask = X(GPRS_GMM_MS_ST_NULL) | X(GPRS_GMM_MS_ST_REGISTERED_INITIATED)| X(GPRS_GMM_MS_ST_DEREGISTERED), .name = "Deregistered", .onenter = st_gmm_ms_deregistered_on_enter, .action = st_gmm_ms_deregistered, }, [GPRS_GMM_MS_ST_REGISTERED_INITIATED] = { .in_event_mask = X(GPRS_GMM_MS_EV_ATTACH_REJECTED) | X(GPRS_GMM_MS_EV_LOW_LVL_FAIL) | X(GPRS_GMM_MS_EV_ATTACH_REQUESTED) | X(GPRS_GMM_MS_EV_ATTACH_ACCEPTED) | X(GPRS_GMM_MS_EV_DETACH_REQUESTED), .out_state_mask = X(GPRS_GMM_MS_ST_DEREGISTERED) | X(GPRS_GMM_MS_ST_REGISTERED), .name = "RegisteredInitiated", .action = st_gmm_ms_registered_initiated, }, [GPRS_GMM_MS_ST_REGISTERED] = { .in_event_mask = X(GPRS_GMM_MS_EV_SR_REQUESTED) | X(GPRS_GMM_MS_EV_RAU_REQUESTED) | X(GPRS_GMM_MS_EV_DETACH_REQUESTED), .out_state_mask = X(GPRS_GMM_MS_ST_REGISTERED) | X(GPRS_GMM_MS_ST_RAU_INITIATED) | X(GPRS_GMM_MS_ST_DEREGISTERED_INITIATED) | X(GPRS_GMM_MS_ST_DEREGISTERED), .name = "Registered", .onenter = st_gmm_ms_registered_on_enter, .action = st_gmm_ms_registered, }, [GPRS_GMM_MS_ST_DEREGISTERED_INITIATED] = { .in_event_mask = X(GPRS_GMM_MS_EV_DETACH_ACCEPTED) | X(GPRS_GMM_MS_EV_LOW_LVL_FAIL) | X(GPRS_GMM_MS_EV_RAU_ACCEPTED), .out_state_mask = X(GPRS_GMM_MS_ST_REGISTERED) | X(GPRS_GMM_MS_ST_DEREGISTERED), .name = "DeregisteredInitiated", .action = st_gmm_ms_deregistered_initiated, }, [GPRS_GMM_MS_ST_RAU_INITIATED] = { .in_event_mask = X(GPRS_GMM_MS_EV_LOW_LVL_FAIL) | X(GPRS_GMM_MS_EV_RAU_REJECTED) | X(GPRS_GMM_MS_EV_RAU_ACCEPTED), .out_state_mask = X(GPRS_GMM_MS_ST_REGISTERED) | X(GPRS_GMM_MS_ST_DEREGISTERED), .name = "RAUInitidated", .action = st_gmm_ms_rau_initiated, }, [GPRS_GMM_MS_ST_SR_INITIATED] = { .in_event_mask = X(GPRS_GMM_MS_EV_LOW_LVL_FAIL) | X(GPRS_GMM_MS_EV_SR_REJECTED) | X(GPRS_GMM_MS_EV_SR_ACCEPTED), .out_state_mask = X(GPRS_GMM_MS_ST_REGISTERED) | X(GPRS_GMM_MS_ST_DEREGISTERED), .name = "SRInitiated", .action = st_gmm_ms_sr_initiated, }, }; const struct value_string gmm_ms_fsm_event_names[] = { { GPRS_GMM_MS_EV_ENABLE_GPRS_MODE, "ENABLE_GPRS_MODE" }, { GPRS_GMM_MS_EV_DISABLE_GPRS_MODE, "DIOSABLE_GPRS_MODE" }, { GPRS_GMM_MS_EV_ATTACH_REQUESTED, "ATTACH_REQUESTED" }, { GPRS_GMM_MS_EV_ATTACH_REJECTED, "ATTACH_REJECTED" }, { GPRS_GMM_MS_EV_ATTACH_ACCEPTED, "ATTACH_ACCEPTED" }, { GPRS_GMM_MS_EV_DETACH_REQUESTED, "DETACH_REQUESTED" }, { GPRS_GMM_MS_EV_DETACH_REQUESTED_POWEROFF, "DETACH_REQUESTED_POWEROFF" }, { GPRS_GMM_MS_EV_DETACH_ACCEPTED, "DETACH_ACCEPTED" }, { GPRS_GMM_MS_EV_SR_REQUESTED, "SR_REQUESTED" }, { GPRS_GMM_MS_EV_SR_REJECTED, "SR_REJECTED" }, { GPRS_GMM_MS_EV_SR_ACCEPTED, "SR_ACCEPTED" }, { GPRS_GMM_MS_EV_RAU_REQUESTED, "RAU_REQUESTED" }, { GPRS_GMM_MS_EV_RAU_REJECTED, "RAU_REJECTED" }, { GPRS_GMM_MS_EV_RAU_ACCEPTED, "RAU_ACCEPTED" }, { GPRS_GMM_MS_EV_LOW_LVL_FAIL, "LOW_LVL_FAIL" }, { 0, NULL } }; int gmm_ms_fsm_timer_cb(struct osmo_fsm_inst *fi) { struct gprs_gmm_ms_fsm_ctx *ctx = (struct gprs_gmm_ms_fsm_ctx *)fi->priv; switch (fi->T) { case 3310: /* TS 24.008 clause 4.7.3.1.5 c) */ ctx->attach.req_attempts++; LOGPFSML(ctx->fi, LOGL_INFO, "T3310 timeout attempts=%u\n", ctx->attach.req_attempts); OSMO_ASSERT(fi->state == GPRS_GMM_MS_ST_REGISTERED_INITIATED); if (ctx->attach.req_attempts == 4) { /* "On the fifth expiry of timer T3310, the MS shall * abort the GPRS attach procedure" */ LOGPFSML(ctx->fi, LOGL_NOTICE, "GPRS attach procedure failure (T3310 timeout attempts=%u)\n", ctx->attach.req_attempts); osmo_fsm_inst_dispatch(ctx->fi, GPRS_GMM_MS_EV_LOW_LVL_FAIL, NULL); return 0; } reinit_attach_proc(ctx); break; case 3330: /* TS 24.008 clause 4.7.5.1.5 c) */ ctx->rau.req_attempts++; LOGPFSML(ctx->fi, LOGL_INFO, "T3330 timeout attempts=%u\n", ctx->rau.req_attempts); OSMO_ASSERT(fi->state == GPRS_GMM_MS_ST_RAU_INITIATED); if (ctx->rau.req_attempts == 4) { /* "On the fifth expiry of timer T3330, the MS shall * abort the GPRS attach procedure" */ LOGPFSML(ctx->fi, LOGL_NOTICE, "GPRS RAU procedure failure (T3330 timeout attempts=%u)\n", ctx->rau.req_attempts); osmo_fsm_inst_dispatch(ctx->fi, GPRS_GMM_MS_EV_LOW_LVL_FAIL, NULL); return 0; } reinit_rau_proc(ctx); break; default: OSMO_ASSERT(0); } return 0; } struct osmo_fsm gmm_ms_fsm = { .name = "GMM_MS", .states = gmm_ms_fsm_states, .num_states = ARRAY_SIZE(gmm_ms_fsm_states), .event_names = gmm_ms_fsm_event_names, .allstate_event_mask = X(GPRS_GMM_MS_EV_DETACH_REQUESTED_POWEROFF), .allstate_action = gmm_ms_fsm_allstate_action, .log_subsys = DLGLOBAL, /* updated dynamically through gprs_gmm_ms_fsm_set_log_cat() */ .timer_cb = gmm_ms_fsm_timer_cb, }; int gprs_gmm_ms_fsm_init(void) { return osmo_fsm_register(&gmm_ms_fsm); } void gprs_gmm_ms_fsm_set_log_cat(int logcat) { gmm_ms_fsm.log_subsys = logcat; } int gprs_gmm_ms_fsm_ctx_init(struct gprs_gmm_ms_fsm_ctx *ctx, struct gprs_gmm_entity *gmme) { ctx->gmme = gmme; ctx->fi = osmo_fsm_inst_alloc(&gmm_ms_fsm, gmme, ctx, LOGL_INFO, NULL); if (!ctx->fi) return -ENODATA; /* Transition to state GMM-DEREGISTERED right away if GPRS is enabled: */ if (g_gmm_ctx->gprs_enabled) osmo_fsm_inst_dispatch(ctx->fi, GPRS_GMM_MS_EV_ENABLE_GPRS_MODE, NULL); return 0; } void gprs_gmm_ms_fsm_ctx_release(struct gprs_gmm_ms_fsm_ctx *ctx) { osmo_fsm_inst_free(ctx->fi); } int gprs_gmm_ms_fsm_ctx_request_attach(struct gprs_gmm_ms_fsm_ctx *ctx, enum osmo_gprs_gmm_attach_type attach_type, bool attach_with_imsi, bool explicit_attach) { int rc; ctx->attach.type = attach_type; ctx->attach.with_imsi = attach_with_imsi; if (explicit_attach) ctx->attach.explicit_att = true; else ctx->attach.implicit_att = true; rc = osmo_fsm_inst_dispatch(ctx->fi, GPRS_GMM_MS_EV_ATTACH_REQUESTED, NULL); return rc; } int gprs_gmm_ms_fsm_ctx_request_detach(struct gprs_gmm_ms_fsm_ctx *ctx, enum osmo_gprs_gmm_detach_ms_type detach_type, enum osmo_gprs_gmm_detach_poweroff_type poweroff_type) { int rc; ctx->detach.type = detach_type; ctx->detach.poweroff_type = poweroff_type; switch (poweroff_type) { case OSMO_GPRS_GMM_DETACH_POWEROFF_TYPE_NORMAL: /* C.3 MS initiated DETACH, GPRS only */ rc = osmo_fsm_inst_dispatch(ctx->fi, GPRS_GMM_MS_EV_DETACH_REQUESTED, NULL); break; case OSMO_GPRS_GMM_DETACH_POWEROFF_TYPE_POWEROFF: /* C.4 POWER-OFF DETACH, GPRS only */ rc = osmo_fsm_inst_dispatch(ctx->fi, GPRS_GMM_MS_EV_DETACH_REQUESTED_POWEROFF, NULL); break; default: OSMO_ASSERT(0); } rc = gprs_gmm_tx_detach_req(ctx->gmme, detach_type, poweroff_type); return rc; } int gprs_gmm_ms_fsm_ctx_request_rau(struct gprs_gmm_ms_fsm_ctx *ctx, enum gprs_gmm_upd_type rau_type) { int rc = 0; ctx->rau.type = rau_type; rc = osmo_fsm_inst_dispatch(ctx->fi, GPRS_GMM_MS_EV_RAU_REQUESTED, NULL); return rc; }