summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPau Espin Pedrol <pespin@sysmocom.de>2023-05-03 20:46:50 +0200
committerPau Espin Pedrol <pespin@sysmocom.de>2023-05-04 12:03:52 +0200
commitbbfb569b8ec650060c97d64c5063cd35e851e4d5 (patch)
tree8ef8c49198c842084c8f68f592fe2a2b967f016a
parentbb5e13ca23e242dd9b70962065c8c01fbb4c3d7a (diff)
layer23: Introduce apn_fsm
This allows further control on the state of the APNs, as well as a step further towards administering them through VTY. Change-Id: I2cc732dfb020d31ab89025e7e22276b819dcb24a
-rw-r--r--src/host/layer23/include/osmocom/bb/common/Makefile.am1
-rw-r--r--src/host/layer23/include/osmocom/bb/common/apn.h2
-rw-r--r--src/host/layer23/include/osmocom/bb/common/apn_fsm.h29
-rw-r--r--src/host/layer23/include/osmocom/bb/common/settings.h1
-rw-r--r--src/host/layer23/src/common/Makefile.am1
-rw-r--r--src/host/layer23/src/common/apn.c16
-rw-r--r--src/host/layer23/src/common/apn_fsm.c245
-rw-r--r--src/host/layer23/src/common/settings.c11
-rw-r--r--src/host/layer23/src/modem/gmm.c13
-rw-r--r--src/host/layer23/src/modem/sm.c16
-rw-r--r--src/host/layer23/src/modem/vty.c14
11 files changed, 334 insertions, 15 deletions
diff --git a/src/host/layer23/include/osmocom/bb/common/Makefile.am b/src/host/layer23/include/osmocom/bb/common/Makefile.am
index 28caf78d..7c0fa972 100644
--- a/src/host/layer23/include/osmocom/bb/common/Makefile.am
+++ b/src/host/layer23/include/osmocom/bb/common/Makefile.am
@@ -1,5 +1,6 @@
noinst_HEADERS = \
apn.h \
+ apn_fsm.h \
l1ctl.h \
l1l2_interface.h \
l23_app.h \
diff --git a/src/host/layer23/include/osmocom/bb/common/apn.h b/src/host/layer23/include/osmocom/bb/common/apn.h
index 94784ef7..538b31dd 100644
--- a/src/host/layer23/include/osmocom/bb/common/apn.h
+++ b/src/host/layer23/include/osmocom/bb/common/apn.h
@@ -20,6 +20,7 @@
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/select.h>
#include <osmocom/core/tun.h>
+#include <osmocom/bb/common/apn_fsm.h>
struct osmocom_ms;
@@ -50,6 +51,7 @@ struct osmobb_apn {
bool tx_gpdu_seq;
} cfg;
struct osmo_tundev *tun;
+ struct apn_fsm_ctx fsm;
};
struct osmobb_apn *apn_alloc(struct osmocom_ms *ms, const char *name);
diff --git a/src/host/layer23/include/osmocom/bb/common/apn_fsm.h b/src/host/layer23/include/osmocom/bb/common/apn_fsm.h
new file mode 100644
index 00000000..890267c9
--- /dev/null
+++ b/src/host/layer23/include/osmocom/bb/common/apn_fsm.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <osmocom/core/fsm.h>
+
+struct osmobb_apn;
+
+enum apn_fsm_states {
+ APN_ST_DISABLED,
+ APN_ST_INACTIVE,
+ APN_ST_ACTIVATING,
+ APN_ST_ACTIVE,
+};
+
+enum apn_fsm_events {
+ APN_EV_GPRS_ALLOWED, /* data: bool *allowed */
+ APN_EV_GMM_ATTACHED,
+ APN_EV_GMM_DETACHED,
+ APN_EV_RX_SM_ACT_PDP_CTX_REJ, /* data: enum gsm48_gsm_cause *cause */
+ APN_EV_RX_SM_ACT_PDP_CTX_ACC,
+ APN_EV_RX_SM_DEACT_PDP_CTX_ACC,
+};
+
+struct apn_fsm_ctx {
+ struct osmo_fsm_inst *fi;
+ struct osmobb_apn *apn;
+};
+
+int apn_fsm_ctx_init(struct apn_fsm_ctx *ctx, struct osmobb_apn *apn);
+void apn_fsm_ctx_release(struct apn_fsm_ctx *ctx);
diff --git a/src/host/layer23/include/osmocom/bb/common/settings.h b/src/host/layer23/include/osmocom/bb/common/settings.h
index 88ce280a..38faa4bb 100644
--- a/src/host/layer23/include/osmocom/bb/common/settings.h
+++ b/src/host/layer23/include/osmocom/bb/common/settings.h
@@ -205,6 +205,7 @@ struct gprs_settings {
int gprs_settings_init(struct osmocom_ms *ms);
int gprs_settings_fi(struct osmocom_ms *ms);
struct osmobb_apn *ms_find_apn_by_name(struct osmocom_ms *ms, const char *apn_name);
+int ms_dispatch_all_apn(struct osmocom_ms *ms, uint32_t event, void *data);
extern char *layer2_socket_path;
diff --git a/src/host/layer23/src/common/Makefile.am b/src/host/layer23/src/common/Makefile.am
index 60af3b83..8f5aebaa 100644
--- a/src/host/layer23/src/common/Makefile.am
+++ b/src/host/layer23/src/common/Makefile.am
@@ -16,6 +16,7 @@ AM_CFLAGS = \
noinst_LIBRARIES = liblayer23.a
liblayer23_a_SOURCES = \
apn.c \
+ apn_fsm.c \
gps.c \
l1ctl.c \
l1l2_interface.c \
diff --git a/src/host/layer23/src/common/apn.c b/src/host/layer23/src/common/apn.c
index 636246a9..b9b032c9 100644
--- a/src/host/layer23/src/common/apn.c
+++ b/src/host/layer23/src/common/apn.c
@@ -35,25 +35,33 @@ struct osmobb_apn *apn_alloc(struct osmocom_ms *ms, const char *name)
if (!apn)
return NULL;
+ if (apn_fsm_ctx_init(&apn->fsm, apn) != 0)
+ goto ret_free;
+
talloc_set_name(apn, "apn_%s", name);
apn->cfg.name = talloc_strdup(apn, name);
apn->cfg.shutdown = true;
apn->cfg.tx_gpdu_seq = true;
apn->tun = osmo_tundev_alloc(apn, name);
- if (!apn->tun) {
- talloc_free(apn);
- return NULL;
- }
+ if (!apn->tun)
+ goto ret_free_fsm;
osmo_tundev_set_priv_data(apn->tun, apn);
apn->ms = ms;
llist_add_tail(&apn->list, &ms->gprs.apn_list);
return apn;
+
+ret_free_fsm:
+ apn_fsm_ctx_release(&apn->fsm);
+ret_free:
+ talloc_free(apn);
+ return NULL;
}
void apn_free(struct osmobb_apn *apn)
{
+ apn_fsm_ctx_release(&apn->fsm);
llist_del(&apn->list);
osmo_tundev_free(apn->tun);
talloc_free(apn);
diff --git a/src/host/layer23/src/common/apn_fsm.c b/src/host/layer23/src/common/apn_fsm.c
new file mode 100644
index 00000000..edaac48a
--- /dev/null
+++ b/src/host/layer23/src/common/apn_fsm.c
@@ -0,0 +1,245 @@
+/* Lifecycle of an APN */
+/*
+ * (C) 2023 by sysmocom - s.m.f.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 <errno.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/bb/common/logging.h>
+#include <osmocom/bb/common/apn_fsm.h>
+#include <osmocom/bb/common/apn.h>
+#define X(s) (1 << (s))
+
+static struct osmo_tdef T_defs_apn[] = {
+ { .T=1, .default_val=30, .desc = "Activating timeout (s)" },
+ { 0 } /* empty item at the end */
+};
+
+static const struct osmo_tdef_state_timeout apn_fsm_timeouts[32] = {
+ [APN_ST_DISABLED] = {},
+ [APN_ST_INACTIVE] = {},
+ [APN_ST_ACTIVATING] = { .T=1 },
+ [APN_ST_ACTIVE] = {},
+};
+
+#define apn_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, apn_fsm_timeouts, T_defs_apn, -1)
+
+static void st_apn_disabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct apn_fsm_ctx *ctx = (struct apn_fsm_ctx *)fi->priv;
+
+ apn_stop(ctx->apn);
+}
+
+static void st_apn_disabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case APN_EV_GPRS_ALLOWED:
+ if (*((bool *)data) == true)
+ apn_fsm_state_chg(fi, APN_ST_INACTIVE);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_apn_inactive_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct apn_fsm_ctx *ctx = (struct apn_fsm_ctx *)fi->priv;
+
+ int rc = apn_start(ctx->apn);
+ if (rc < 0)
+ apn_fsm_state_chg(fi, APN_ST_DISABLED);
+
+ /* FIXME: Here once we find a way to store whether the ms object is GMM
+ attached, we can transition directly to ACTIVATING. */
+}
+
+static void st_apn_inactive(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case APN_EV_GPRS_ALLOWED:
+ if (*((bool *)data) == false)
+ apn_fsm_state_chg(fi, APN_ST_DISABLED);
+ break;
+ case APN_EV_GMM_ATTACHED:
+ apn_fsm_state_chg(fi, APN_ST_ACTIVATING);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_apn_activating_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ /* FIXME: We could send SMREG-PDP_ACT.req from here. Right now that's done by the app. */
+}
+
+static void st_apn_activating(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case APN_EV_GPRS_ALLOWED:
+ /* TODO: Tx PDP DEACT ACC */
+ apn_fsm_state_chg(fi, APN_ST_DISABLED);
+ break;
+ case APN_EV_GMM_DETACHED:
+ apn_fsm_state_chg(fi, APN_ST_INACTIVE);
+ break;
+ case APN_EV_RX_SM_ACT_PDP_CTX_REJ:
+ apn_fsm_state_chg(fi, APN_ST_INACTIVE);
+ break;
+ case APN_EV_RX_SM_ACT_PDP_CTX_ACC:
+ apn_fsm_state_chg(fi, APN_ST_ACTIVE);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_apn_active_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct apn_fsm_ctx *ctx = (struct apn_fsm_ctx *)fi->priv;
+ struct osmo_netdev *netdev;
+
+ netdev = osmo_tundev_get_netdev(ctx->apn->tun);
+ osmo_netdev_ifupdown(netdev, true);
+}
+
+static void st_apn_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case APN_EV_GPRS_ALLOWED:
+ /* TODO: Tx PDP DEACT ACC */
+ apn_fsm_state_chg(fi, APN_ST_DISABLED);
+ break;
+ case APN_EV_GMM_DETACHED:
+ apn_fsm_state_chg(fi, APN_ST_INACTIVE);
+ break;
+ case APN_EV_RX_SM_DEACT_PDP_CTX_ACC:
+ apn_fsm_state_chg(fi, APN_ST_INACTIVE);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static int apn_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ switch (fi->T) {
+ case 1:
+ apn_fsm_state_chg(fi, APN_ST_INACTIVE);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ return 0;
+}
+
+static struct osmo_fsm_state apn_fsm_states[] = {
+ [APN_ST_DISABLED] = {
+ .in_event_mask =
+ X(APN_EV_GPRS_ALLOWED),
+ .out_state_mask =
+ X(APN_ST_INACTIVE),
+ .name = "DISABLED",
+ .onenter = st_apn_disabled_on_enter,
+ .action = st_apn_disabled,
+ },
+ [APN_ST_INACTIVE] = {
+ .in_event_mask =
+ X(APN_EV_GPRS_ALLOWED) |
+ X(APN_EV_GMM_ATTACHED),
+ .out_state_mask =
+ X(APN_ST_ACTIVATING),
+ .name = "INACTIVE",
+ .onenter = st_apn_inactive_on_enter,
+ .action = st_apn_inactive,
+ },
+ [APN_ST_ACTIVATING] = {
+ .in_event_mask =
+ X(APN_EV_GPRS_ALLOWED) |
+ X(APN_EV_GMM_DETACHED) |
+ X(APN_EV_RX_SM_ACT_PDP_CTX_REJ) |
+ X(APN_EV_RX_SM_ACT_PDP_CTX_ACC),
+ .out_state_mask =
+ X(APN_ST_DISABLED) |
+ X(APN_ST_INACTIVE) |
+ X(APN_ST_ACTIVE),
+ .name = "ACTIVATING",
+ .onenter = st_apn_activating_on_enter,
+ .action = st_apn_activating,
+ },
+ [APN_ST_ACTIVE] = {
+ .in_event_mask =
+ X(APN_EV_GPRS_ALLOWED) |
+ X(APN_EV_GMM_DETACHED)|
+ X(APN_EV_RX_SM_DEACT_PDP_CTX_ACC),
+ .out_state_mask =
+ X(APN_ST_DISABLED) |
+ X(APN_ST_INACTIVE),
+ .name = "ACTIVE",
+ .onenter = st_apn_active_on_enter,
+ .action = st_apn_active,
+ },
+};
+
+const struct value_string apn_fsm_event_names[] = {
+ { APN_EV_GPRS_ALLOWED, "GPRS_ALLOWED" },
+ { APN_EV_GMM_ATTACHED, "GMM_ATTACHED" },
+ { APN_EV_GMM_DETACHED, "GMM_DETACHED" },
+ { APN_EV_RX_SM_ACT_PDP_CTX_REJ, "ACT_PDP_CTX_REJ" },
+ { APN_EV_RX_SM_ACT_PDP_CTX_ACC, "ACT_PDP_CTX_ACC" },
+ { APN_EV_RX_SM_DEACT_PDP_CTX_ACC, "DEACT_PDP_CTX_ACC" },
+ { 0, NULL }
+};
+
+struct osmo_fsm apn_fsm = {
+ .name = "APN",
+ .states = apn_fsm_states,
+ .num_states = ARRAY_SIZE(apn_fsm_states),
+ .timer_cb = apn_fsm_timer_cb,
+ .event_names = apn_fsm_event_names,
+ .log_subsys = DTUN,
+ .timer_cb = apn_fsm_timer_cb,
+};
+
+int apn_fsm_ctx_init(struct apn_fsm_ctx *ctx, struct osmobb_apn *apn)
+{
+ ctx->apn = apn;
+ ctx->fi = osmo_fsm_inst_alloc(&apn_fsm, apn, ctx, LOGL_INFO, NULL);
+ if (!ctx->fi)
+ return -ENODATA;
+
+ return 0;
+}
+
+void apn_fsm_ctx_release(struct apn_fsm_ctx *ctx)
+{
+ osmo_fsm_inst_free(ctx->fi);
+}
+
+static __attribute__((constructor)) void apn_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&apn_fsm) == 0);
+ osmo_tdefs_reset(T_defs_apn);
+}
diff --git a/src/host/layer23/src/common/settings.c b/src/host/layer23/src/common/settings.c
index 565da961..986d551b 100644
--- a/src/host/layer23/src/common/settings.c
+++ b/src/host/layer23/src/common/settings.c
@@ -252,3 +252,14 @@ struct osmobb_apn *ms_find_apn_by_name(struct osmocom_ms *ms, const char *apn_na
}
return NULL;
}
+
+int ms_dispatch_all_apn(struct osmocom_ms *ms, uint32_t event, void *data)
+{
+ struct gprs_settings *set = &ms->gprs;
+ int rc = 0;
+ struct osmobb_apn *apn;
+
+ llist_for_each_entry(apn, &set->apn_list, list)
+ rc |= osmo_fsm_inst_dispatch(apn->fsm.fi, event, data);
+ return rc;
+}
diff --git a/src/host/layer23/src/modem/gmm.c b/src/host/layer23/src/modem/gmm.c
index f3a936aa..b95f15a2 100644
--- a/src/host/layer23/src/modem/gmm.c
+++ b/src/host/layer23/src/modem/gmm.c
@@ -64,10 +64,12 @@ static int modem_gmm_prim_up_cb(struct osmo_gprs_gmm_prim *gmm_prim, void *user_
ms->subscr.ptmsi = gmm_prim->gmmreg.attach_cnf.acc.allocated_ptmsi;
app_data.modem_state = MODEM_ST_ATTACHED;
/* Activate APN if not yet already: */
- apn = llist_first_entry_or_null(&ms->gprs.apn_list, struct osmobb_apn, list);
- if (!apn || apn->cfg.shutdown)
- break;
- modem_sm_smreg_pdp_act_req(ms, apn);
+ llist_for_each_entry(apn, &ms->gprs.apn_list, list) {
+ if (apn->fsm.fi->state != APN_ST_INACTIVE)
+ continue;
+ osmo_fsm_inst_dispatch(apn->fsm.fi, APN_EV_GMM_ATTACHED, NULL);
+ modem_sm_smreg_pdp_act_req(ms, apn);
+ }
} else {
uint8_t cause = gmm_prim->gmmreg.attach_cnf.rej.cause;
LOGP(DGMM, LOGL_ERROR, "%s(): Rx %s: Attach rejected, cause=%u (%s)\n",
@@ -94,6 +96,9 @@ static int modem_gmm_prim_up_cb(struct osmo_gprs_gmm_prim *gmm_prim, void *user_
break;
case OSMO_PRIM(OSMO_GPRS_GMM_GMMREG_DETACH, PRIM_OP_CONFIRM):
case OSMO_PRIM(OSMO_GPRS_GMM_GMMREG_DETACH, PRIM_OP_INDICATION):
+ LOGP(DGMM, LOGL_NOTICE, "%s(): Rx %s\n", __func__, pdu_name);
+ ms_dispatch_all_apn(ms, APN_EV_GMM_DETACHED, NULL);
+ break;
default:
LOGP(DGMM, LOGL_ERROR, "%s(): Rx %s UNIMPLEMENTED\n", __func__, pdu_name);
break;
diff --git a/src/host/layer23/src/modem/sm.c b/src/host/layer23/src/modem/sm.c
index 56b223f5..aac2b52a 100644
--- a/src/host/layer23/src/modem/sm.c
+++ b/src/host/layer23/src/modem/sm.c
@@ -50,24 +50,28 @@
static int modem_sm_handle_pdp_act_cnf(struct osmocom_ms *ms, struct osmo_gprs_sm_prim *sm_prim)
{
const char *pdu_name = osmo_gprs_sm_prim_name(sm_prim);
- struct osmobb_apn *apn = llist_first_entry_or_null(&ms->gprs.apn_list, struct osmobb_apn, list);
+ struct osmobb_apn *apn = NULL, *apn_it;
struct osmo_netdev *netdev;
char buf_addr[INET6_ADDRSTRLEN];
char buf_addr2[INET6_ADDRSTRLEN];
int rc;
+ llist_for_each_entry(apn_it, &ms->gprs.apn_list, list) {
+ if (apn_it->fsm.fi->state == APN_ST_ACTIVATING) {
+ apn = apn_it;
+ break;
+ }
+ }
+
if (!apn) {
LOGP(DSM, LOGL_ERROR, "Rx %s but have no APN!\n", pdu_name);
return -ENOENT;
}
- if (apn->cfg.shutdown) {
- LOGPAPN(LOGL_ERROR, apn, "Rx %s but APN is administratively shutdown!\n", pdu_name);
- return -ENOENT;
- }
if (!sm_prim->smreg.pdp_act_cnf.accepted) {
LOGPAPN(LOGL_ERROR, apn, "Rx %s: Activate PDP failed! cause '%s'\n", pdu_name,
get_value_string(gsm48_gsm_cause_names, sm_prim->smreg.pdp_act_cnf.rej.cause));
+ osmo_fsm_inst_dispatch(apn->fsm.fi, APN_EV_RX_SM_ACT_PDP_CTX_REJ, NULL);
/* TODO: maybe retry ? */
return 0;
}
@@ -117,6 +121,8 @@ static int modem_sm_handle_pdp_act_cnf(struct osmocom_ms *ms, struct osmo_gprs_s
/* TODO: Handle PCO */
/* TODO: Handle QoS */
+
+ osmo_fsm_inst_dispatch(apn->fsm.fi, APN_EV_RX_SM_ACT_PDP_CTX_ACC, NULL);
return rc;
}
diff --git a/src/host/layer23/src/modem/vty.c b/src/host/layer23/src/modem/vty.c
index 33891d46..77f0885f 100644
--- a/src/host/layer23/src/modem/vty.c
+++ b/src/host/layer23/src/modem/vty.c
@@ -257,6 +257,7 @@ DEFUN(cfg_ms_no_apn, cfg_ms_no_apn_cmd, "no apn APN_NAME",
{
struct osmocom_ms *ms = vty->index;
struct osmobb_apn *apn;
+ bool on = false;
apn = ms_find_apn_by_name(ms, argv[0]);
if (!apn) {
@@ -264,6 +265,9 @@ DEFUN(cfg_ms_no_apn, cfg_ms_no_apn_cmd, "no apn APN_NAME",
return CMD_WARNING;
}
+ /* Disable APN before getting rid of it. */
+ osmo_fsm_inst_dispatch(apn->fsm.fi, APN_EV_GPRS_ALLOWED, &on);
+
apn_free(apn);
return CMD_SUCCESS;
@@ -326,9 +330,12 @@ DEFUN(cfg_apn_shutdown, cfg_apn_shutdown_cmd,
"Put the APN in administrative shut-down\n")
{
struct osmobb_apn *apn = (struct osmobb_apn *) vty->index;
+ bool on = false;
+ int rc;
if (!apn->cfg.shutdown) {
- if (apn_stop(apn)) {
+ rc = osmo_fsm_inst_dispatch(apn->fsm.fi, APN_EV_GPRS_ALLOWED, &on);
+ if (rc < 0) {
vty_out(vty, "%% Failed to Stop APN%s", VTY_NEWLINE);
return CMD_WARNING;
}
@@ -343,13 +350,16 @@ DEFUN(cfg_apn_no_shutdown, cfg_apn_no_shutdown_cmd,
NO_STR "Remove the APN from administrative shut-down\n")
{
struct osmobb_apn *apn = (struct osmobb_apn *) vty->index;
+ bool on = true;
+ int rc;
if (apn->cfg.shutdown) {
if (!apn->cfg.dev_name) {
vty_out(vty, "%% Failed to start APN, tun-device is not configured%s", VTY_NEWLINE);
return CMD_WARNING;
}
- if (apn_start(apn) < 0) {
+ rc = osmo_fsm_inst_dispatch(apn->fsm.fi, APN_EV_GPRS_ALLOWED, &on);
+ if (rc < 0) {
vty_out(vty, "%% Failed to start APN, check log for details%s", VTY_NEWLINE);
return CMD_WARNING;
}