libosmo-gprs/src/gmm/gmm_ms_fsm.c

523 lines
16 KiB
C

/* GMM states in the MS, 3GPP TS 24.008 § 4.1.3.1 */
/*
* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* SPDX-License-Identifier: AGPL-3.0+
*
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <osmocom/core/tdef.h>
#include <osmocom/gprs/gmm/gmm_ms_fsm.h>
#include <osmocom/gprs/gmm/gmm.h>
#include <osmocom/gprs/gmm/gmm_private.h>
#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
#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 layerthat the MS has been GPRS detached, e.g. by timer expiry */
if (prev_state != GPRS_GMM_MS_ST_NULL)
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;
switch (event) {
case GPRS_GMM_MS_EV_RAU_REJECTED:
// causes #13, #15, #25
gmm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_REGISTERED);
// else
//mm_ms_fsm_state_chg(fi, GPRS_GMM_MS_ST_DEREGISTERED_INITIATED);
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_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_RAU_REJECTED) |
X(GPRS_GMM_MS_EV_RAU_ACCEPTED),
.out_state_mask =
X(GPRS_GMM_MS_ST_REGISTERED) |
X(GPRS_GMM_MS_ST_DEREGISTERED_INITIATED) |
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_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;
}