summaryrefslogtreecommitdiffstats
path: root/src/host/layer23/src/mobile/gsm48_mm.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/host/layer23/src/mobile/gsm48_mm.c')
-rw-r--r--src/host/layer23/src/mobile/gsm48_mm.c4147
1 files changed, 4147 insertions, 0 deletions
diff --git a/src/host/layer23/src/mobile/gsm48_mm.c b/src/host/layer23/src/mobile/gsm48_mm.c
new file mode 100644
index 00000000..82d09a7e
--- /dev/null
+++ b/src/host/layer23/src/mobile/gsm48_mm.c
@@ -0,0 +1,4147 @@
+/*
+ * (C) 2010 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/utils.h>
+#include <osmocore/gsm48.h>
+#include <osmocore/talloc.h>
+
+#include <osmocom/logging.h>
+#include <osmocom/osmocom_data.h>
+#include <osmocom/gsm48_cc.h>
+#include <osmocom/l23_app.h>
+#include <osmocom/networks.h>
+#include <osmocom/l1ctl.h>
+
+extern void *l23_ctx;
+
+void mm_conn_free(struct gsm48_mm_conn *conn);
+static int gsm48_rcv_rr(struct osmocom_ms *ms, struct msgb *msg);
+static int gsm48_rcv_mmr(struct osmocom_ms *ms, struct msgb *msg);
+static int gsm48_mm_ev(struct osmocom_ms *ms, int msg_type, struct msgb *msg);
+static int gsm48_mm_tx_id_rsp(struct osmocom_ms *ms, uint8_t mi_type);
+static int gsm48_mm_tx_loc_upd_req(struct osmocom_ms *ms);
+static int gsm48_mm_loc_upd_failed(struct osmocom_ms *ms, struct msgb *msg);
+static int gsm48_mm_conn_go_dedic(struct osmocom_ms *ms);
+static int gsm48_mm_init_mm_reject(struct osmocom_ms *ms, struct msgb *msg);
+static int gsm48_mm_data_ind(struct osmocom_ms *ms, struct msgb *msg);
+static void new_mm_state(struct gsm48_mmlayer *mm, int state, int substate);
+static int gsm48_mm_loc_upd_normal(struct osmocom_ms *ms, struct msgb *msg);
+static int gsm48_mm_loc_upd_periodic(struct osmocom_ms *ms, struct msgb *msg);
+static int gsm48_mm_loc_upd(struct osmocom_ms *ms, struct msgb *msg);
+
+/*
+ * notes
+ */
+
+/*
+ * Notes on IMSI detach procedure:
+ *
+ * At the end of the procedure, the state of MM, RR, cell selection: No SIM.
+ *
+ * In MM IDLE state, cell available: RR is establised, IMSI detach specific
+ * procedure is performed.
+ *
+ * In MM IDLE state, no cell: State is silently changed to No SIM.
+ *
+ * During any MM connection state, or Wait for network command: All MM
+ * connections (if any) are released locally, and IMSI detach specific
+ * procedure is performed.
+ *
+ * During IMSI detach processing: Request of IMSI detach is ignored.
+ *
+ * Any other state: The special 'delay_detach' flag is set only. If set, at any
+ * state transition we will clear the flag and restart the procedure again.
+ *
+ * The procedure is not spec conform, but always succeeds.
+ *
+ */
+
+/* Notes on Service states:
+ *
+ * There are two PLMN search states:
+ *
+ * - PLMN SEARCH NORMAL
+ * - PLMN SEARCH
+ *
+ * They are entered, if: (4.2.1.2)
+ * - ME is switched on
+ * - SIM is inserted
+ * - user has asked PLMN selection in certain Service states
+ * - coverage is lost in certain Service states
+ * - roaming is denied
+ * - (optionally see 4.2.1.2)
+ *
+ * PLMN SEARCH NORMAL state is then entered, if all these conditions are met:
+ * - SIM is valid
+ * - SIM state is U1
+ * - SIM LAI valid
+ * - cell selected
+ * - cell == SIM LAI
+ *
+ * Otherwhise PLMN SEARCH is entered.
+ *
+ * During PLMN SEARCH NORMAL state: (4.2.2.5)
+ * - on expirery of T3211 or T3213: Perform location update, when back
+ * to NORMAL SERVICE state.
+ * - on expirery of T3212: Perform periodic location update, when back
+ * to NORMAL SERVICE state.
+ * - perform IMSI detach
+ * - perform MM connections
+ * - respond to paging (if possible)
+ *
+ * During PLMN SEARCH state: (4.2.2.6)
+ * - reject MM connection except for emergency calls
+ *
+ *
+ * The NO CELL AVAILABLE state is entered, if:
+ * - no cell found during PLMN search
+ *
+ * During NO CELL AVAILABLE state:
+ * - reject any MM connection
+ *
+ *
+ * The NO IMSI state is entered if:
+ * - SIM is invalid
+ * - and cell is selected during PLMN SEARCH states
+ *
+ * During NO IMSO state: (4.2.2.4)
+ * - reject MM connection except for emergency calls
+ *
+ *
+ * The LIMITED SERVICE state is entered if:
+ * - SIM is valid
+ * - and SIM state is U3
+ * - and cell is selected
+ *
+ * During LIMITED SERVICE state: (4.2.2.3)
+ * - reject MM connection except for emergency calls
+ * - perform location update, if new LAI is entered
+ *
+ *
+ * The LOCATION UPDATE NEEDED state is entered if:
+ * - SIM is valid
+ * - and location update must be performed for any reason
+ *
+ * During LOCATION UPDATE NEEDED state:
+ * - reject MM connection except for emergency calls
+ *
+ * This state is left if location update is possible and directly enter
+ * state ATTEMPTING TO UPDATE and trigger location update.
+ * The function gsm48_mm_loc_upd_possible() is used to check this on state
+ * change.
+ *
+ *
+ * The ATTEMPTING TO UPDATE state is entered if:
+ * - SIM is valid
+ * - and SIM state is U2
+ * - and cell is selected
+ *
+ * During ATTEMPTING TO UPDATE state: (4.2.2.2)
+ * - on expirery of T3211 or T3213: Perform location updated
+ * - on expirery of T3212: Perform location updated
+ * - on change of LAI: Perform location update
+ * - (abnormal cases unsupported)
+ * - accept MM connection for emergency calls
+ * - trigger location update on any other MM connection
+ * - respond to paging (with IMSI only, because in U2 TMSI is not valid)
+ *
+ *
+ * The NORMAL SERVICE state is entered if:
+ * - SIM is valid
+ * - and SIM state is U1
+ * - and cell is selected
+ * - and SIM LAI == cell
+ *
+ * During NORMAL SERVICE state: (4.2.2.1)
+ * - on expirery of T3211 or T3213: Perform location updated
+ * - on expirery of T3212: Perform location updated
+ * - on change of LAI: Perform location update
+ * - perform IMSI detach
+ * - perform MM connections
+ * - respond to paging
+ *
+ *
+ * gsm48_mm_set_plmn_search() is used enter PLMN SEARCH or PLMN SEARCH NORMAL
+ * state. Depending on the conditions above, the appropiate state is selected.
+ *
+ *
+ * gsm48_mm_return_idle() is used to select the Service state when returning
+ * to MM IDLE state after cell reselection.
+ *
+ *
+ * If cell selection process indicates NO_CELL_FOUND:
+ *
+ * - NO CELL AVAILABLE state is entered, if not already.
+ *
+ * gsm48_mm_no_cell_found() is used to select the Service state.
+ *
+ *
+ * If cell selection process indicates CELL_SELECTED:
+ *
+ * - NO IMSI state is entered, if no SIM valid.
+ * - Otherwise NORMAL SERVICES state is entered, if
+ * SIM state is U1, SIM LAI == cell, IMSI is attached, T3212 not expired.
+ * - Otherwise NORMAL SERVICES state is entered, if
+ * SIM state is U1, SIM LAI == cell, attach not required, T3212 not expired.
+ * - Otherwise LIMITED SERVICE state is entered, if
+ * CS mode is automatic, cell is forbidden PLMN or forbidden LA.
+ * - Otherwise LIMITED SERVICE state is entered, if
+ * CS mode is manual, cell is not the selected one.
+ * - Otherwise LOCATION UPDATE NEEDED state is entered.
+ *
+ * gsm48_mm_cell_selected() is used to select the Service state.
+ *
+ */
+
+/*
+ * support functions
+ */
+
+/* decode network name */
+static int decode_network_name(char *name, int name_len,
+ const uint8_t *lv)
+{
+ uint8_t in_len = lv[0];
+ int length, padding;
+
+ name[0] = '\0';
+ if (in_len < 1)
+ return -EINVAL;
+
+ /* must be CB encoded */
+ if ((lv[1] & 0x70) != 0x00)
+ return -ENOTSUP;
+
+ padding = lv[1] & 0x03;
+ length = ((in_len - 1) * 8 - padding) / 7;
+ if (length <= 0)
+ return 0;
+ if (length >= name_len)
+ length = name_len - 1;
+ gsm_7bit_decode(name, lv + 2, length);
+ name[length] = '\0';
+
+ return length;
+}
+
+/* encode 'mobile identity' */
+int gsm48_encode_mi(uint8_t *buf, struct msgb *msg, struct osmocom_ms *ms,
+ uint8_t mi_type)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm_settings *set = &ms->settings;
+ uint8_t *ie;
+
+ switch(mi_type) {
+ case GSM_MI_TYPE_TMSI:
+ gsm48_generate_mid_from_tmsi(buf, subscr->tmsi);
+ break;
+ case GSM_MI_TYPE_IMSI:
+ gsm48_generate_mid_from_imsi(buf, subscr->imsi);
+ break;
+ case GSM_MI_TYPE_IMEI:
+ gsm48_generate_mid_from_imsi(buf, set->imei);
+ break;
+ case GSM_MI_TYPE_IMEISV:
+ gsm48_generate_mid_from_imsi(buf, set->imeisv);
+ break;
+ case GSM_MI_TYPE_NONE:
+ default:
+ buf[0] = GSM48_IE_MOBILE_ID;
+ buf[1] = 1;
+ buf[2] = 0xf0;
+ break;
+ }
+ /* alter MI type */
+ buf[2] = (buf[2] & ~GSM_MI_TYPE_MASK) | mi_type;
+
+ if (msg) {
+ /* MI as LV */
+ ie = msgb_put(msg, 1 + buf[1]);
+ memcpy(ie, buf + 1, 1 + buf[1]);
+ }
+
+ return 0;
+}
+
+/* encode 'classmark 1' */
+int gsm48_encode_classmark1(struct gsm48_classmark1 *cm, uint8_t rev_lev,
+ uint8_t es_ind, uint8_t a5_1, uint8_t pwr_lev)
+{
+ memset(cm, 0, sizeof(*cm));
+ cm->rev_lev = rev_lev;
+ cm->es_ind = es_ind;
+ cm->a5_1 = a5_1;
+ cm->pwr_lev = pwr_lev;
+
+ return 0;
+}
+
+/*
+ * timers
+ */
+
+static void timeout_mm_t3210(void *arg)
+{
+ struct gsm48_mmlayer *mm = arg;
+
+ LOGP(DMM, LOGL_INFO, "timer T3210 (loc. upd. timeout) has fired\n");
+ gsm48_mm_ev(mm->ms, GSM48_MM_EVENT_TIMEOUT_T3210, NULL);
+}
+
+static void timeout_mm_t3211(void *arg)
+{
+ struct gsm48_mmlayer *mm = arg;
+
+ LOGP(DSUM, LOGL_INFO, "Location update retry\n");
+ LOGP(DMM, LOGL_INFO, "timer T3211 (loc. upd. retry delay) has fired\n");
+ gsm48_mm_ev(mm->ms, GSM48_MM_EVENT_TIMEOUT_T3211, NULL);
+}
+
+static void timeout_mm_t3212(void *arg)
+{
+ struct gsm48_mmlayer *mm = arg;
+
+ LOGP(DSUM, LOGL_INFO, "Periodic location update\n");
+ LOGP(DMM, LOGL_INFO, "timer T3212 (periodic loc. upd. delay) has "
+ "fired\n");
+
+ /* reset attempt counter when attempting to update (4.4.4.5) */
+ if (mm->state == GSM48_MM_ST_MM_IDLE
+ && mm->substate == GSM48_MM_SST_ATTEMPT_UPDATE)
+ mm->lupd_attempt = 0;
+
+ gsm48_mm_ev(mm->ms, GSM48_MM_EVENT_TIMEOUT_T3212, NULL);
+}
+
+static void timeout_mm_t3213(void *arg)
+{
+ struct gsm48_mmlayer *mm = arg;
+
+ LOGP(DSUM, LOGL_INFO, "Location update retry\n");
+ LOGP(DMM, LOGL_INFO, "timer T3213 (delay after RA failure) has "
+ "fired\n");
+ gsm48_mm_ev(mm->ms, GSM48_MM_EVENT_TIMEOUT_T3213, NULL);
+}
+
+static void timeout_mm_t3230(void *arg)
+{
+ struct gsm48_mmlayer *mm = arg;
+
+ LOGP(DMM, LOGL_INFO, "timer T3230 (MM connection timeout) has "
+ "fired\n");
+ gsm48_mm_ev(mm->ms, GSM48_MM_EVENT_TIMEOUT_T3230, NULL);
+}
+
+static void timeout_mm_t3220(void *arg)
+{
+ struct gsm48_mmlayer *mm = arg;
+
+ LOGP(DMM, LOGL_INFO, "timer T3220 (IMSI detach keepalive) has "
+ "fired\n");
+ gsm48_mm_ev(mm->ms, GSM48_MM_EVENT_TIMEOUT_T3220, NULL);
+}
+
+static void timeout_mm_t3240(void *arg)
+{
+ struct gsm48_mmlayer *mm = arg;
+
+ LOGP(DMM, LOGL_INFO, "timer T3240 (RR release timeout) has fired\n");
+ gsm48_mm_ev(mm->ms, GSM48_MM_EVENT_TIMEOUT_T3240, NULL);
+}
+
+static void start_mm_t3210(struct gsm48_mmlayer *mm)
+{
+ LOGP(DMM, LOGL_INFO, "starting T3210 (loc. upd. timeout) with %d.%d "
+ "seconds\n", GSM_T3210_MS);
+ mm->t3210.cb = timeout_mm_t3210;
+ mm->t3210.data = mm;
+ bsc_schedule_timer(&mm->t3210, GSM_T3210_MS);
+}
+
+static void start_mm_t3211(struct gsm48_mmlayer *mm)
+{
+ LOGP(DMM, LOGL_INFO, "starting T3211 (loc. upd. retry delay) with "
+ "%d.%d seconds\n", GSM_T3211_MS);
+ mm->t3211.cb = timeout_mm_t3211;
+ mm->t3211.data = mm;
+ bsc_schedule_timer(&mm->t3211, GSM_T3211_MS);
+}
+
+static void start_mm_t3212(struct gsm48_mmlayer *mm, int sec)
+{
+ /* don't start, if is not available */
+ if (!sec)
+ return;
+
+ LOGP(DMM, LOGL_INFO, "starting T3212 (periodic loc. upd. delay) with "
+ "%d seconds\n", sec);
+ mm->t3212.cb = timeout_mm_t3212;
+ mm->t3212.data = mm;
+ bsc_schedule_timer(&mm->t3212, sec, 0);
+}
+
+static void start_mm_t3213(struct gsm48_mmlayer *mm)
+{
+ LOGP(DMM, LOGL_INFO, "starting T3213 (delay after RA failure) with "
+ "%d.%d seconds\n", GSM_T3213_MS);
+ mm->t3213.cb = timeout_mm_t3213;
+ mm->t3213.data = mm;
+ bsc_schedule_timer(&mm->t3213, GSM_T3213_MS);
+}
+
+static void start_mm_t3220(struct gsm48_mmlayer *mm)
+{
+ LOGP(DMM, LOGL_INFO, "starting T3220 (IMSI detach keepalive) with "
+ "%d.%d seconds\n", GSM_T3220_MS);
+ mm->t3220.cb = timeout_mm_t3220;
+ mm->t3220.data = mm;
+ bsc_schedule_timer(&mm->t3220, GSM_T3220_MS);
+}
+
+static void start_mm_t3230(struct gsm48_mmlayer *mm)
+{
+ LOGP(DMM, LOGL_INFO, "starting T3230 (MM connection timeout) with "
+ "%d.%d seconds\n", GSM_T3230_MS);
+ mm->t3230.cb = timeout_mm_t3230;
+ mm->t3230.data = mm;
+ bsc_schedule_timer(&mm->t3230, GSM_T3230_MS);
+}
+
+static void start_mm_t3240(struct gsm48_mmlayer *mm)
+{
+ LOGP(DMM, LOGL_INFO, "starting T3240 (RR release timeout) with %d.%d "
+ "seconds\n", GSM_T3240_MS);
+ mm->t3240.cb = timeout_mm_t3240;
+ mm->t3240.data = mm;
+ bsc_schedule_timer(&mm->t3240, GSM_T3240_MS);
+}
+
+static void stop_mm_t3210(struct gsm48_mmlayer *mm)
+{
+ if (bsc_timer_pending(&mm->t3210)) {
+ LOGP(DMM, LOGL_INFO, "stopping pending (loc. upd. timeout) "
+ "timer T3210\n");
+ bsc_del_timer(&mm->t3210);
+ }
+}
+
+static void stop_mm_t3211(struct gsm48_mmlayer *mm)
+{
+ if (bsc_timer_pending(&mm->t3211)) {
+ LOGP(DMM, LOGL_INFO, "stopping pending (loc. upd. retry "
+ "delay) timer T3211\n");
+ bsc_del_timer(&mm->t3211);
+ }
+}
+
+static void stop_mm_t3212(struct gsm48_mmlayer *mm)
+{
+ if (bsc_timer_pending(&mm->t3212)) {
+ LOGP(DMM, LOGL_INFO, "stopping pending (periodic loc. upd. "
+ "delay) timer T3212\n");
+ bsc_del_timer(&mm->t3212);
+ }
+}
+
+static void stop_mm_t3213(struct gsm48_mmlayer *mm)
+{
+ if (bsc_timer_pending(&mm->t3213)) {
+ LOGP(DMM, LOGL_INFO, "stopping pending (delay after RA "
+ "failure) timer T3213\n");
+ bsc_del_timer(&mm->t3213);
+ }
+}
+
+static void stop_mm_t3220(struct gsm48_mmlayer *mm)
+{
+ if (bsc_timer_pending(&mm->t3220)) {
+ LOGP(DMM, LOGL_INFO, "stopping pending (IMSI detach keepalive) "
+ "timer T3220\n");
+ bsc_del_timer(&mm->t3220);
+ }
+}
+
+static void stop_mm_t3230(struct gsm48_mmlayer *mm)
+{
+ if (bsc_timer_pending(&mm->t3230)) {
+ LOGP(DMM, LOGL_INFO, "stopping pending (MM connection timeout) "
+ "timer T3230\n");
+ bsc_del_timer(&mm->t3230);
+ }
+}
+
+static void stop_mm_t3240(struct gsm48_mmlayer *mm)
+{
+ if (bsc_timer_pending(&mm->t3240)) {
+ LOGP(DMM, LOGL_INFO, "stopping pending (RR release timeout) "
+ "timer T3240\n");
+ bsc_del_timer(&mm->t3240);
+ }
+}
+
+static void stop_mm_t3241(struct gsm48_mmlayer *mm)
+{
+ /* not implemented, not required */
+}
+
+/*
+ * messages
+ */
+
+/* names of MM events */
+static const struct value_string gsm48_mmevent_names[] = {
+ { GSM48_MM_EVENT_CELL_SELECTED, "MM_EVENT_CELL_SELECTED" },
+ { GSM48_MM_EVENT_NO_CELL_FOUND, "MM_EVENT_NO_CELL_FOUND" },
+ { GSM48_MM_EVENT_TIMEOUT_T3210, "MM_EVENT_TIMEOUT_T3210" },
+ { GSM48_MM_EVENT_TIMEOUT_T3211, "MM_EVENT_TIMEOUT_T3211" },
+ { GSM48_MM_EVENT_TIMEOUT_T3212, "MM_EVENT_TIMEOUT_T3212" },
+ { GSM48_MM_EVENT_TIMEOUT_T3213, "MM_EVENT_TIMEOUT_T3213" },
+ { GSM48_MM_EVENT_TIMEOUT_T3220, "MM_EVENT_TIMEOUT_T3220" },
+ { GSM48_MM_EVENT_TIMEOUT_T3230, "MM_EVENT_TIMEOUT_T3230" },
+ { GSM48_MM_EVENT_TIMEOUT_T3240, "MM_EVENT_TIMEOUT_T3240" },
+ { GSM48_MM_EVENT_IMSI_DETACH, "MM_EVENT_IMSI_DETACH" },
+ { GSM48_MM_EVENT_PAGING, "MM_EVENT_PAGING" },
+ { GSM48_MM_EVENT_AUTH_RESPONSE, "MM_EVENT_AUTH_RESPONSE" },
+ { GSM48_MM_EVENT_SYSINFO, "MM_EVENT_SYSINFO" },
+ { GSM48_MM_EVENT_USER_PLMN_SEL, "MM_EVENT_USER_PLMN_SEL" },
+ { 0, NULL }
+};
+
+const char *get_mmevent_name(int value)
+{
+ return get_value_string(gsm48_mmevent_names, value);
+}
+
+/* names of MM-SAP */
+static const struct value_string gsm48_mm_msg_names[] = {
+ { GSM48_MT_MM_IMSI_DETACH_IND, "MT_MM_IMSI_DETACH_IND" },
+ { GSM48_MT_MM_LOC_UPD_ACCEPT, "MT_MM_LOC_UPD_ACCEPT" },
+ { GSM48_MT_MM_LOC_UPD_REJECT, "MT_MM_LOC_UPD_REJECT" },
+ { GSM48_MT_MM_LOC_UPD_REQUEST, "MT_MM_LOC_UPD_REQUEST" },
+ { GSM48_MT_MM_AUTH_REJ, "MT_MM_AUTH_REJ" },
+ { GSM48_MT_MM_AUTH_REQ, "MT_MM_AUTH_REQ" },
+ { GSM48_MT_MM_AUTH_RESP, "MT_MM_AUTH_RESP" },
+ { GSM48_MT_MM_ID_REQ, "MT_MM_ID_REQ" },
+ { GSM48_MT_MM_ID_RESP, "MT_MM_ID_RESP" },
+ { GSM48_MT_MM_TMSI_REALL_CMD, "MT_MM_TMSI_REALL_CMD" },
+ { GSM48_MT_MM_TMSI_REALL_COMPL, "MT_MM_TMSI_REALL_COMPL" },
+ { GSM48_MT_MM_CM_SERV_ACC, "MT_MM_CM_SERV_ACC" },
+ { GSM48_MT_MM_CM_SERV_REJ, "MT_MM_CM_SERV_REJ" },
+ { GSM48_MT_MM_CM_SERV_ABORT, "MT_MM_CM_SERV_ABORT" },
+ { GSM48_MT_MM_CM_SERV_REQ, "MT_MM_CM_SERV_REQ" },
+ { GSM48_MT_MM_CM_SERV_PROMPT, "MT_MM_CM_SERV_PROMPT" },
+ { GSM48_MT_MM_CM_REEST_REQ, "MT_MM_CM_REEST_REQ" },
+ { GSM48_MT_MM_ABORT, "MT_MM_ABORT" },
+ { GSM48_MT_MM_NULL, "MT_MM_NULL" },
+ { GSM48_MT_MM_STATUS, "MT_MM_STATUS" },
+ { GSM48_MT_MM_INFO, "MT_MM_INFO" },
+ { 0, NULL }
+};
+
+const char *get_mm_name(int value)
+{
+ return get_value_string(gsm48_mm_msg_names, value);
+}
+
+/* names of MMxx-SAP */
+static const struct value_string gsm48_mmxx_msg_names[] = {
+ { GSM48_MMCC_EST_REQ, "MMCC_EST_REQ" },
+ { GSM48_MMCC_EST_IND, "MMCC_EST_IND" },
+ { GSM48_MMCC_EST_CNF, "MMCC_EST_CNF" },
+ { GSM48_MMCC_REL_REQ, "MMCC_REL_REQ" },
+ { GSM48_MMCC_REL_IND, "MMCC_REL_IND" },
+ { GSM48_MMCC_DATA_REQ, "MMCC_DATA_REQ" },
+ { GSM48_MMCC_DATA_IND, "MMCC_DATA_IND" },
+ { GSM48_MMCC_UNIT_DATA_REQ, "MMCC_UNIT_DATA_REQ" },
+ { GSM48_MMCC_UNIT_DATA_IND, "MMCC_UNIT_DATA_IND" },
+ { GSM48_MMCC_SYNC_IND, "MMCC_SYNC_IND" },
+ { GSM48_MMCC_REEST_REQ, "MMCC_REEST_REQ" },
+ { GSM48_MMCC_REEST_CNF, "MMCC_REEST_CNF" },
+ { GSM48_MMCC_ERR_IND, "MMCC_ERR_IND" },
+ { GSM48_MMCC_PROMPT_IND, "MMCC_PROMPT_IND" },
+ { GSM48_MMCC_PROMPT_REJ, "MMCC_PROMPT_REJ" },
+ { GSM48_MMSS_EST_REQ, "MMSS_EST_REQ" },
+ { GSM48_MMSS_EST_IND, "MMSS_EST_IND" },
+ { GSM48_MMSS_EST_CNF, "MMSS_EST_CNF" },
+ { GSM48_MMSS_REL_REQ, "MMSS_REL_REQ" },
+ { GSM48_MMSS_REL_IND, "MMSS_REL_IND" },
+ { GSM48_MMSS_DATA_REQ, "MMSS_DATA_REQ" },
+ { GSM48_MMSS_DATA_IND, "MMSS_DATA_IND" },
+ { GSM48_MMSS_UNIT_DATA_REQ, "MMSS_UNIT_DATA_REQ" },
+ { GSM48_MMSS_UNIT_DATA_IND, "MMSS_UNIT_DATA_IND" },
+ { GSM48_MMSS_REEST_REQ, "MMSS_REEST_REQ" },
+ { GSM48_MMSS_REEST_CNF, "MMSS_REEST_CNF" },
+ { GSM48_MMSS_ERR_IND, "MMSS_ERR_IND" },
+ { GSM48_MMSS_PROMPT_IND, "MMSS_PROMPT_IND" },
+ { GSM48_MMSS_PROMPT_REJ, "MMSS_PROMPT_REJ" },
+ { GSM48_MMSMS_EST_REQ, "MMSMS_EST_REQ" },
+ { GSM48_MMSMS_EST_IND, "MMSMS_EST_IND" },
+ { GSM48_MMSMS_EST_CNF, "MMSMS_EST_CNF" },
+ { GSM48_MMSMS_REL_REQ, "MMSMS_REL_REQ" },
+ { GSM48_MMSMS_REL_IND, "MMSMS_REL_IND" },
+ { GSM48_MMSMS_DATA_REQ, "MMSMS_DATA_REQ" },
+ { GSM48_MMSMS_DATA_IND, "MMSMS_DATA_IND" },
+ { GSM48_MMSMS_UNIT_DATA_REQ, "MMSMS_UNIT_DATA_REQ" },
+ { GSM48_MMSMS_UNIT_DATA_IND, "MMSMS_UNIT_DATA_IND" },
+ { GSM48_MMSMS_REEST_REQ, "MMSMS_REEST_REQ" },
+ { GSM48_MMSMS_REEST_CNF, "MMSMS_REEST_CNF" },
+ { GSM48_MMSMS_ERR_IND, "MMSMS_ERR_IND" },
+ { GSM48_MMSMS_PROMPT_IND, "MMSMS_PROMPT_IND" },
+ { GSM48_MMSMS_PROMPT_REJ, "MMSMS_PROMPT_REJ" },
+ { 0, NULL }
+};
+
+const char *get_mmxx_name(int value)
+{
+ return get_value_string(gsm48_mmxx_msg_names, value);
+}
+
+/* names of MMR-SAP */
+static const struct value_string gsm48_mmr_msg_names[] = {
+ { GSM48_MMR_REG_REQ, "MMR_REG_REQ" },
+ { GSM48_MMR_REG_CNF, "MMR_REG_CNF" },
+ { GSM48_MMR_NREG_REQ, "MMR_NREG_REQ" },
+ { GSM48_MMR_NREG_IND, "MMR_NREG_IND" },
+ { 0, NULL }
+};
+
+const char *get_mmr_name(int value)
+{
+ return get_value_string(gsm48_mmr_msg_names, value);
+}
+
+/* allocate GSM 04.08 message (MMxx-SAP) */
+struct msgb *gsm48_mmxx_msgb_alloc(int msg_type, uint32_t ref,
+ uint8_t transaction_id)
+{
+ struct msgb *msg;
+ struct gsm48_mmxx_hdr *mmh;
+
+ msg = msgb_alloc_headroom(MMXX_ALLOC_SIZE+MMXX_ALLOC_HEADROOM,
+ MMXX_ALLOC_HEADROOM, "GSM 04.08 MMxx");
+ if (!msg)
+ return NULL;
+
+ mmh = (struct gsm48_mmxx_hdr *)msgb_put(msg, sizeof(*mmh));
+ mmh->msg_type = msg_type;
+ mmh->ref = ref;
+ mmh->transaction_id = transaction_id;
+
+ return msg;
+}
+
+/* allocate MM event message */
+struct msgb *gsm48_mmevent_msgb_alloc(int msg_type)
+{
+ struct msgb *msg;
+ struct gsm48_mm_event *mme;
+
+ msg = msgb_alloc_headroom(sizeof(*mme), 0, "GSM 04.08 MM event");
+ if (!msg)
+ return NULL;
+
+ mme = (struct gsm48_mm_event *)msgb_put(msg, sizeof(*mme));
+ mme->msg_type = msg_type;
+
+ return msg;
+}
+
+/* allocate MMR message */
+struct msgb *gsm48_mmr_msgb_alloc(int msg_type)
+{
+ struct msgb *msg;
+ struct gsm48_mmr *mmr;
+
+ msg = msgb_alloc_headroom(sizeof(*mmr), 0, "GSM 04.08 MMR");
+ if (!msg)
+ return NULL;
+
+ mmr = (struct gsm48_mmr *)msgb_put(msg, sizeof(*mmr));
+ mmr->msg_type = msg_type;
+
+ return msg;
+}
+
+/* queue message (MMxx-SAP) */
+int gsm48_mmxx_upmsg(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ msgb_enqueue(&mm->mmxx_upqueue, msg);
+
+ return 0;
+}
+
+/* queue message (MMR-SAP) */
+int gsm48_mmr_downmsg(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ msgb_enqueue(&mm->mmr_downqueue, msg);
+
+ return 0;
+}
+
+/* queue MM event message */
+int gsm48_mmevent_msg(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ msgb_enqueue(&mm->event_queue, msg);
+
+ return 0;
+}
+
+/* dequeue messages (MMxx-SAP) */
+int gsm48_mmxx_dequeue(struct osmocom_ms *ms)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct msgb *msg;
+ struct gsm48_mmxx_hdr *mmh;
+ int work = 0;
+
+ while ((msg = msgb_dequeue(&mm->mmxx_upqueue))) {
+ mmh = (struct gsm48_mmxx_hdr *) msg->data;
+ switch (mmh->msg_type & GSM48_MMXX_MASK) {
+ case GSM48_MMCC_CLASS:
+ gsm48_rcv_cc(ms, msg);
+ break;
+#if 0
+ case GSM48_MMSS_CLASS:
+ gsm48_rcv_ss(ms, msg);
+ break;
+ case GSM48_MMSMS_CLASS:
+ gsm48_rcv_sms(ms, msg);
+ break;
+#endif
+ }
+ msgb_free(msg);
+ work = 1; /* work done */
+ }
+
+ return work;
+}
+
+/* dequeue messages (MMR-SAP) */
+int gsm48_mmr_dequeue(struct osmocom_ms *ms)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct msgb *msg;
+ struct gsm48_mmr *mmr;
+ int work = 0;
+
+ while ((msg = msgb_dequeue(&mm->mmr_downqueue))) {
+ mmr = (struct gsm48_mmr *) msg->data;
+ gsm48_rcv_mmr(ms, msg);
+ msgb_free(msg);
+ work = 1; /* work done */
+ }
+
+ return work;
+}
+
+/* dequeue messages (RR-SAP) */
+int gsm48_rr_dequeue(struct osmocom_ms *ms)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct msgb *msg;
+ int work = 0;
+
+ while ((msg = msgb_dequeue(&mm->rr_upqueue))) {
+ /* msg is freed there */
+ gsm48_rcv_rr(ms, msg);
+ work = 1; /* work done */
+ }
+
+ return work;
+}
+
+/* dequeue MM event messages */
+int gsm48_mmevent_dequeue(struct osmocom_ms *ms)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_mm_event *mme;
+ struct msgb *msg;
+ int work = 0;
+
+ while ((msg = msgb_dequeue(&mm->event_queue))) {
+ mme = (struct gsm48_mm_event *) msg->data;
+ gsm48_mm_ev(ms, mme->msg_type, msg);
+ msgb_free(msg);
+ work = 1; /* work done */
+ }
+
+ return work;
+}
+
+/* push RR header and send to RR */
+static int gsm48_mm_to_rr(struct osmocom_ms *ms, struct msgb *msg,
+ int msg_type, uint8_t cause)
+{
+ struct gsm48_rr_hdr *rrh;
+
+ /* push RR header */
+ msgb_push(msg, sizeof(struct gsm48_rr_hdr));
+ rrh = (struct gsm48_rr_hdr *) msg->data;
+ rrh->msg_type = msg_type;
+ rrh->cause = cause;
+
+ /* send message to RR */
+ return gsm48_rr_downmsg(ms, msg);
+}
+
+/*
+ * state transition
+ */
+
+const char *gsm48_mm_state_names[] = {
+ "NULL",
+ "undefined 1",
+ "undefined 2",
+ "location updating initiated",
+ "undefined 4",
+ "wait for outgoing MM connection",
+ "MM connection active",
+ "IMSI detach initiated",
+ "process CM service prompt",
+ "wait for network command",
+ "location updating reject",
+ "undefined 11",
+ "undefined 12",
+ "wait for RR connection (location updating)",
+ "wait for RR connection (MM connection)",
+ "wait for RR connection (IMSI detach)",
+ "undefined 16",
+ "wait for re-establishment",
+ "wait for RR connection active",
+ "MM idle",
+ "wait for additional outgoing MM connection",
+ "MM_CONN_ACTIVE_VGCS",
+ "WAIT_RR_CONN_VGCS",
+ "location updating pending",
+ "IMSI detach pending",
+ "RR connection release not allowed"
+};
+
+const char *gsm48_mm_substate_names[] = {
+ "NULL",
+ "normal service",
+ "attempting to update",
+ "limited service",
+ "no IMSI",
+ "no cell available",
+ "location updating needed",
+ "PLMN search",
+ "PLMN search (normal)",
+ "RX_VGCS_NORMAL",
+ "RX_VGCS_LIMITED"
+};
+
+/* change state from LOCATION UPDATE NEEDED to ATTEMPTING TO UPDATE */
+static int gsm48_mm_loc_upd_possible(struct gsm48_mmlayer *mm)
+{
+ // TODO: check if really possible
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_ATTEMPT_UPDATE);
+ return gsm48_mm_loc_upd_normal(mm->ms, NULL);
+}
+
+/* Set new MM state, also new substate in case of MM IDLE state. */
+static void new_mm_state(struct gsm48_mmlayer *mm, int state, int substate)
+{
+ /* IDLE -> IDLE */
+ if (mm->state == GSM48_MM_ST_MM_IDLE && state == mm->state)
+ LOGP(DMM, LOGL_INFO, "new MM IDLE state %s -> %s\n",
+ gsm48_mm_substate_names[mm->substate],
+ gsm48_mm_substate_names[substate]);
+ /* IDLE -> non-IDLE */
+ else if (mm->state == GSM48_MM_ST_MM_IDLE)
+ LOGP(DMM, LOGL_INFO, "new state MM IDLE, %s -> %s\n",
+ gsm48_mm_substate_names[mm->substate],
+ gsm48_mm_state_names[state]);
+ /* non-IDLE -> IDLE */
+ else if (state == GSM48_MM_ST_MM_IDLE)
+ LOGP(DMM, LOGL_INFO, "new state %s -> MM IDLE, %s\n",
+ gsm48_mm_state_names[mm->state],
+ gsm48_mm_substate_names[substate]);
+ /* non-IDLE -> non-IDLE */
+ else
+ LOGP(DMM, LOGL_INFO, "new state %s -> %s\n",
+ gsm48_mm_state_names[mm->state],
+ gsm48_mm_state_names[state]);
+
+ /* remember most recent substate */
+ if (mm->state == GSM48_MM_ST_MM_IDLE)
+ mm->mr_substate = mm->substate;
+
+ mm->state = state;
+ mm->substate = substate;
+
+ /* resend detach event, if flag is set */
+ if (state == GSM48_MM_ST_MM_IDLE && mm->delay_detach) {
+ struct msgb *nmsg;
+
+ mm->delay_detach = 0;
+
+ nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_IMSI_DETACH);
+ if (!nmsg)
+ return;
+ gsm48_mmevent_msg(mm->ms, nmsg);
+ }
+
+ /* 4.4.2 start T3212 in MM IDLE mode if not started or has expired */
+ if (state == GSM48_MM_ST_MM_IDLE
+ && (substate == GSM48_MM_SST_NORMAL_SERVICE
+ || substate == GSM48_MM_SST_ATTEMPT_UPDATE)) {
+ /* start periodic location update timer */
+ if (!bsc_timer_pending(&mm->t3212))
+ start_mm_t3212(mm, mm->t3212_value);
+ /* perform pending location update */
+ if (mm->lupd_retry) {
+ LOGP(DMM, LOGL_INFO, "Loc. upd. pending (type %d)\n",
+ mm->lupd_type);
+ mm->lupd_retry = 0;
+ gsm48_mm_loc_upd(mm->ms, NULL);
+ /* must exit, because this function can be called
+ * recursively
+ */
+ return;
+ }
+ if (mm->lupd_periodic) {
+ struct gsm48_sysinfo *s = &mm->ms->cellsel.sel_si;
+
+ LOGP(DMM, LOGL_INFO, "Periodic loc. upd. pending "
+ "(type %d)\n", mm->lupd_type);
+ mm->lupd_periodic = 0;
+ if (s->t3212)
+ gsm48_mm_loc_upd_periodic(mm->ms, NULL);
+ else
+ LOGP(DMM, LOGL_INFO, "but not requred\n");
+ /* must exit, because this function can be called
+ * recursively
+ */
+ return;
+ }
+ }
+
+ /* check if location update is possible */
+ if (state == GSM48_MM_ST_MM_IDLE
+ && substate == GSM48_MM_SST_LOC_UPD_NEEDED) {
+ gsm48_mm_loc_upd_possible(mm);
+ /* must exit, because this function can be called recursively */
+ return;
+ }
+}
+
+/* return PLMN SEARCH or PLMN SEARCH NORMAL state */
+static int gsm48_mm_set_plmn_search(struct osmocom_ms *ms)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+
+ /* SIM not inserted */
+ if (!subscr->sim_valid) {
+ LOGP(DMM, LOGL_INFO, "Selecting PLMN SEARCH state, because "
+ "no SIM.\n");
+ return GSM48_MM_SST_PLMN_SEARCH;
+ }
+
+ /* SIM not updated */
+ if (subscr->ustate != GSM_SIM_U1_UPDATED) {
+ LOGP(DMM, LOGL_INFO, "Selecting PLMN SEARCH state, because "
+ "SIM not updated.\n");
+ return GSM48_MM_SST_PLMN_SEARCH;
+ }
+ if (!subscr->lai_valid) {
+ LOGP(DMM, LOGL_INFO, "Selecting PLMN SEARCH state, because "
+ "LAI in SIM not valid.\n");
+ return GSM48_MM_SST_PLMN_SEARCH;
+ }
+
+ /* no cell selected */
+ if (!cs->selected) {
+ LOGP(DMM, LOGL_INFO, "Selecting PLMN SEARCH state, because "
+ "no cell selected.\n");
+ return GSM48_MM_SST_PLMN_SEARCH;
+ }
+
+ /* selected cell's LAI not equal to LAI stored on the sim */
+ if (cs->sel_mcc != subscr->lai_mcc
+ || cs->sel_mnc != subscr->lai_mnc
+ || cs->sel_lac != subscr->lai_lac) {
+ LOGP(DMM, LOGL_INFO, "Selecting PLMN SEARCH state, because "
+ "LAI of selected cell (MCC %s MNC %s LAC 0x%04x) "
+ "!= LAI in SIM (MCC %s MNC %s LAC 0x%04x).\n",
+ gsm_print_mcc(cs->sel_mcc), gsm_print_mnc(cs->sel_mnc),
+ cs->sel_lac, gsm_print_mcc(subscr->lai_mcc),
+ gsm_print_mnc(subscr->lai_mnc), subscr->lai_lac);
+ return GSM48_MM_SST_PLMN_SEARCH;
+ }
+
+ /* SIM is updated in this LA */
+ LOGP(DMM, LOGL_INFO, "Selecting PLMN SEARCH NORMAL state.\n");
+ return GSM48_MM_SST_PLMN_SEARCH_NORMAL;
+}
+
+/* 4.2.3 when returning to MM IDLE state, this function is called */
+static int gsm48_mm_return_idle(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+
+ /* 4.4.4.9 start T3211 when RR is released */
+ if (mm->start_t3211) {
+ LOGP(DMM, LOGL_INFO, "Starting T3211 after RR release.\n");
+ mm->start_t3211 = 0;
+ start_mm_t3211(mm);
+ }
+
+ /* return from location update with "Roaming not allowed" */
+ if (mm->state == GSM48_MM_ST_LOC_UPD_REJ && mm->lupd_rej_cause == 13) {
+ LOGP(DMM, LOGL_INFO, "Roaming not allowed as returning to "
+ "MM IDLE\n");
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
+ gsm48_mm_set_plmn_search(ms));
+
+ return 0;
+ }
+
+ /* no SIM present or invalid */
+ if (!subscr->sim_valid) {
+ LOGP(DMM, LOGL_INFO, "SIM invalid as returning to MM IDLE\n");
+
+ /* stop periodic location updating */
+ mm->lupd_pending = 0;
+ stop_mm_t3212(mm); /* 4.4.2 */
+
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_NO_IMSI);
+
+ return 0;
+ }
+
+ /* selected cell equals the registered LAI */
+ if (subscr->lai_valid
+ && cs->sel_mcc == subscr->lai_mcc
+ && cs->sel_mnc == subscr->lai_mnc
+ && cs->sel_lac == subscr->lai_lac) {
+ LOGP(DMM, LOGL_INFO, "We are in registered LAI as returning "
+ "to MM IDLE\n");
+ /* if SIM not updated (abnormal case as described in 4.4.4.9) */
+ if (subscr->ustate != GSM_SIM_U1_UPDATED)
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
+ GSM48_MM_SST_ATTEMPT_UPDATE);
+ else
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
+ GSM48_MM_SST_NORMAL_SERVICE);
+
+ return 0;
+ }
+
+ /* location update allowed */
+ if (cs->state == GSM322_C3_CAMPED_NORMALLY) {
+ LOGP(DMM, LOGL_INFO, "We are camping normally as returning to "
+ "MM IDLE\n");
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
+ GSM48_MM_SST_LOC_UPD_NEEDED);
+ } else { /* location update not allowed */
+ LOGP(DMM, LOGL_INFO, "We are camping on any cell as returning "
+ "to MM IDLE\n");
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
+ GSM48_MM_SST_LIMITED_SERVICE);
+ }
+
+ return 0;
+}
+
+/* 4.2.1.1 Service state PLMN SEARCH (NORMAL) is left if no cell found */
+static int gsm48_mm_no_cell_found(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_NO_CELL_AVAIL);
+
+ return 0;
+}
+
+/* 4.2.1.1 Service state PLMN SEARCH (NORMAL) / NO CELL AVAILABLE is left
+ * if cell selected
+ */
+static int gsm48_mm_cell_selected(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm48_sysinfo *s = &cs->sel_si;
+ struct gsm_settings *set = &ms->settings;
+
+ /* no SIM is inserted */
+ if (!subscr->sim_valid) {
+ LOGP(DMM, LOGL_INFO, "SIM invalid as cell is selected.\n");
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_NO_IMSI);
+
+ return 0;
+ }
+
+ /* SIM not updated in this LA */
+ if (subscr->ustate == GSM_SIM_U1_UPDATED
+ && subscr->lai_valid
+ && cs->sel_mcc == subscr->lai_mcc
+ && cs->sel_mnc == subscr->lai_mnc
+ && cs->sel_lac == subscr->lai_lac
+ && !mm->lupd_periodic) {
+ if (subscr->imsi_attached) {
+ struct msgb *nmsg;
+
+ LOGP(DMM, LOGL_INFO, "Valid in location area.\n");
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
+ GSM48_MM_SST_NORMAL_SERVICE);
+
+ /* send message to PLMN search process */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_SUCCESS);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+
+ return 0;
+ }
+ if (!s->att_allowed) {
+ struct msgb *nmsg;
+
+ LOGP(DMM, LOGL_INFO, "Attachment not required.\n");
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
+ GSM48_MM_SST_NORMAL_SERVICE);
+
+ /* send message to PLMN search process */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_SUCCESS);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+
+ return 0;
+ }
+ /* else, continue */
+ }
+
+ /* PLMN mode auto and selected cell is forbidden */
+ if (set->plmn_mode == PLMN_MODE_AUTO
+ && (gsm_subscr_is_forbidden_plmn(subscr, cs->sel_mcc, cs->sel_mnc)
+ || gsm322_is_forbidden_la(ms, cs->sel_mcc, cs->sel_mnc,
+ cs->sel_lac))) {
+ struct msgb *nmsg;
+
+ LOGP(DMM, LOGL_INFO, "Selected cell is forbidden.\n");
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
+ GSM48_MM_SST_LIMITED_SERVICE);
+
+ /* send message to PLMN search process */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_ROAMING_NA);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+
+ return 0;
+ }
+
+ /* PLMN mode manual and selected cell not selected PLMN */
+ if (set->plmn_mode == PLMN_MODE_MANUAL
+ && (plmn->mcc != cs->sel_mcc
+ || plmn->mnc != cs->sel_mnc)) {
+ struct msgb *nmsg;
+
+ LOGP(DMM, LOGL_INFO, "Selected cell not found.\n");
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
+ GSM48_MM_SST_LIMITED_SERVICE);
+
+ /* send message to PLMN search process */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_FAILED);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+
+ return 0;
+ }
+
+ /* other cases */
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_LOC_UPD_NEEDED);
+
+ return 0;
+}
+
+/* 4.2.1.2 Service state PLMN SEARCH (NORMAL) is entered */
+static int gsm48_mm_plmn_search(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE, gsm48_mm_set_plmn_search(ms));
+
+ return 0;
+}
+
+/*
+ * init and exit
+ */
+
+/* initialize Mobility Management process */
+int gsm48_mm_init(struct osmocom_ms *ms)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ memset(mm, 0, sizeof(*mm));
+ mm->ms = ms;
+
+ LOGP(DMM, LOGL_INFO, "init Mobility Management process\n");
+
+ /* 4.2.1.1 */
+ mm->state = GSM48_MM_ST_MM_IDLE;
+ mm->substate = gsm48_mm_set_plmn_search(ms);
+
+ /* init lists */
+ INIT_LLIST_HEAD(&mm->mm_conn);
+ INIT_LLIST_HEAD(&mm->rr_upqueue);
+ INIT_LLIST_HEAD(&mm->mmxx_upqueue);
+ INIT_LLIST_HEAD(&mm->mmr_downqueue);
+ INIT_LLIST_HEAD(&mm->event_queue);
+
+ return 0;
+}
+
+/* exit MM process */
+int gsm48_mm_exit(struct osmocom_ms *ms)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_mm_conn *conn;
+ struct msgb *msg;
+
+ LOGP(DMM, LOGL_INFO, "exit Mobility Management process\n");
+
+ /* flush lists */
+ while (!llist_empty(&mm->mm_conn)) {
+ conn = llist_entry(mm->mm_conn.next,
+ struct gsm48_mm_conn, list);
+ mm_conn_free(conn);
+ }
+ while ((msg = msgb_dequeue(&mm->rr_upqueue)))
+ msgb_free(msg);
+ while ((msg = msgb_dequeue(&mm->mmxx_upqueue)))
+ msgb_free(msg);
+ while ((msg = msgb_dequeue(&mm->mmr_downqueue)))
+ msgb_free(msg);
+ while ((msg = msgb_dequeue(&mm->event_queue)))
+ msgb_free(msg);
+
+ /* stop timers */
+ stop_mm_t3210(mm);
+ stop_mm_t3211(mm);
+ stop_mm_t3212(mm);
+ stop_mm_t3213(mm);
+ stop_mm_t3220(mm);
+ stop_mm_t3230(mm);
+ stop_mm_t3240(mm);
+
+ return 0;
+}
+
+/*
+ * MM connection management
+ */
+
+static const char *gsm48_mmxx_state_names[] = {
+ "IDLE",
+ "CONN_PEND",
+ "DEDICATED",
+ "CONN_SUSP",
+ "REESTPEND"
+};
+
+uint32_t mm_conn_new_ref = 0x80000001;
+
+/* new MM connection state */
+static void new_conn_state(struct gsm48_mm_conn *conn, int state)
+{
+ LOGP(DMM, LOGL_INFO, "(ref %x) new state %s -> %s\n", conn->ref,
+ gsm48_mmxx_state_names[conn->state],
+ gsm48_mmxx_state_names[state]);
+ conn->state = state;
+}
+
+/* find MM connection by protocol+ID */
+struct gsm48_mm_conn *mm_conn_by_id(struct gsm48_mmlayer *mm,
+ uint8_t proto, uint8_t transaction_id)
+{
+ struct gsm48_mm_conn *conn;
+
+ llist_for_each_entry(conn, &mm->mm_conn, list) {
+ if (conn->protocol == proto &&
+ conn->transaction_id == transaction_id)
+ return conn;
+ }
+ return NULL;
+}
+
+/* find MM connection by reference */
+struct gsm48_mm_conn *mm_conn_by_ref(struct gsm48_mmlayer *mm,
+ uint32_t ref)
+{
+ struct gsm48_mm_conn *conn;
+
+ llist_for_each_entry(conn, &mm->mm_conn, list) {
+ if (conn->ref == ref)
+ return conn;
+ }
+ return NULL;
+}
+
+/* create MM connection instance */
+static struct gsm48_mm_conn* mm_conn_new(struct gsm48_mmlayer *mm,
+ int proto, uint8_t transaction_id, uint32_t ref)
+{
+ struct gsm48_mm_conn *conn = talloc_zero(l23_ctx, struct gsm48_mm_conn);
+
+ if (!conn)
+ return NULL;
+
+ LOGP(DMM, LOGL_INFO, "New MM Connection (proto 0x%02x trans_id %d "
+ "ref %d)\n", proto, transaction_id, ref);
+
+ conn->mm = mm;
+ conn->state = GSM48_MMXX_ST_IDLE;
+ conn->transaction_id = transaction_id;
+ conn->protocol = proto;
+ conn->ref = ref;
+
+ llist_add(&conn->list, &mm->mm_conn);
+
+ return conn;
+}
+
+/* destroy MM connection instance */
+void mm_conn_free(struct gsm48_mm_conn *conn)
+{
+ LOGP(DMM, LOGL_INFO, "Freeing MM Connection\n");
+
+ new_conn_state(conn, GSM48_MMXX_ST_IDLE);
+
+ llist_del(&conn->list);
+
+ talloc_free(conn);
+}
+
+/* support function to release pending/all ongoing MM connections */
+static int gsm48_mm_release_mm_conn(struct osmocom_ms *ms, int abort_any,
+ uint8_t cause, int error)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_mm_conn *conn, *conn2;
+ struct msgb *nmsg;
+ struct gsm48_mmxx_hdr *nmmh;
+
+ if (abort_any)
+ LOGP(DMM, LOGL_INFO, "Release any MM Connection\n");
+ else
+ LOGP(DMM, LOGL_INFO, "Release pending MM Connections\n");
+
+ /* release MM connection(s) */
+ llist_for_each_entry_safe(conn, conn2, &mm->mm_conn, list) {
+ /* abort any OR the pending connection */
+ if (abort_any || conn->state == GSM48_MMXX_ST_CONN_PEND) {
+ /* send MMxx-REL-IND */
+ nmsg = NULL;
+ switch(conn->protocol) {
+ case GSM48_PDISC_CC:
+ nmsg = gsm48_mmxx_msgb_alloc(
+ error ? GSM48_MMCC_ERR_IND
+ : GSM48_MMCC_REL_IND, conn->ref,
+ conn->transaction_id);
+ break;
+ case GSM48_PDISC_NC_SS:
+ nmsg = gsm48_mmxx_msgb_alloc(
+ error ? GSM48_MMSS_ERR_IND
+ : GSM48_MMSS_REL_IND, conn->ref,
+ conn->transaction_id);
+ break;
+ case GSM48_PDISC_SMS:
+ nmsg = gsm48_mmxx_msgb_alloc(
+ error ? GSM48_MMSMS_ERR_IND
+ : GSM48_MMSMS_REL_IND, conn->ref,
+ conn->transaction_id);
+ break;
+ }
+ if (!nmsg) {
+ /* this should not happen */
+ mm_conn_free(conn);
+ continue; /* skip if not of CC type */
+ }
+ nmmh = (struct gsm48_mmxx_hdr *)nmsg->data;
+ nmmh->cause = cause;
+ gsm48_mmxx_upmsg(ms, nmsg);
+
+ mm_conn_free(conn);
+ }
+ }
+ return 0;
+}
+
+/*
+ * process handlers (Common procedures)
+ */
+
+/* sending MM STATUS message */
+static int gsm48_mm_tx_mm_status(struct osmocom_ms *ms, uint8_t cause)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *ngh;
+ uint8_t *reject_cause;
+
+ LOGP(DMM, LOGL_INFO, "MM STATUS (cause #%d)\n", cause);
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh));
+ reject_cause = msgb_put(nmsg, 1);
+
+ ngh->proto_discr = GSM48_PDISC_MM;
+ ngh->msg_type = GSM48_MT_MM_STATUS;
+ *reject_cause = cause;
+
+ /* push RR header and send down */
+ return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_DATA_REQ, 0);
+}
+
+/* 4.3.1.2 sending TMSI REALLOCATION COMPLETE message */
+static int gsm48_mm_tx_tmsi_reall_cpl(struct osmocom_ms *ms)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *ngh;
+
+ LOGP(DMM, LOGL_INFO, "TMSI REALLOCATION COMPLETE\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh));
+
+ ngh->proto_discr = GSM48_PDISC_MM;
+ ngh->msg_type = GSM48_MT_MM_TMSI_REALL_COMPL;
+
+ /* push RR header and send down */
+ return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_DATA_REQ, 0);
+}
+
+/* 4.3.1 TMSI REALLOCATION COMMAND is received */
+static int gsm48_mm_rx_tmsi_realloc_cmd(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct gsm48_loc_area_id *lai = (struct gsm48_loc_area_id *) gh->data;
+ uint8_t mi_type, *mi;
+ uint32_t tmsi;
+
+ if (payload_len < sizeof(struct gsm48_loc_area_id) + 2) {
+ short_read:
+ LOGP(DMM, LOGL_NOTICE, "Short read of TMSI REALLOCATION "
+ "COMMAND message error.\n");
+ return -EINVAL;
+ }
+ /* LAI */
+ gsm48_decode_lai(lai, &subscr->lai_mcc, &subscr->lai_mnc,
+ &subscr->lai_lac);
+ /* MI */
+ mi = gh->data + sizeof(struct gsm48_loc_area_id);
+ mi_type = mi[1] & GSM_MI_TYPE_MASK;
+ switch (mi_type) {
+ case GSM_MI_TYPE_TMSI:
+ if (payload_len + sizeof(struct gsm48_loc_area_id) < 6
+ || mi[0] < 5)
+ goto short_read;
+ memcpy(&tmsi, mi+2, 4);
+ subscr->tmsi = ntohl(tmsi);
+ subscr->tmsi_valid = 1;
+ LOGP(DMM, LOGL_INFO, "TMSI 0x%08x (%u) assigned.\n",
+ subscr->tmsi, subscr->tmsi);
+ gsm48_mm_tx_tmsi_reall_cpl(ms);
+ break;
+ case GSM_MI_TYPE_IMSI:
+ subscr->tmsi_valid = 0;
+ LOGP(DMM, LOGL_INFO, "TMSI removed.\n");
+ gsm48_mm_tx_tmsi_reall_cpl(ms);
+ break;
+ default:
+ LOGP(DMM, LOGL_NOTICE, "TMSI reallocation with unknown MI "
+ "type %d.\n", mi_type);
+ gsm48_mm_tx_mm_status(ms, GSM48_REJECT_INCORRECT_MESSAGE);
+
+ return 0; /* don't store in SIM */
+ }
+
+#ifdef TODO
+ store / remove from sim
+#endif
+
+ return 0;
+}
+
+#ifndef TODO
+static int gsm48_mm_tx_auth_rsp(struct osmocom_ms *ms, struct msgb *msg);
+#endif
+
+/* 4.3.2.2 AUTHENTICATION REQUEST is received */
+static int gsm48_mm_rx_auth_req(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct gsm48_auth_req *ar = (struct gsm48_auth_req *) gh->data;
+
+ if (payload_len < sizeof(struct gsm48_auth_req)) {
+ LOGP(DMM, LOGL_NOTICE, "Short read of AUTHENTICATION REQUEST "
+ "message error.\n");
+ return -EINVAL;
+ }
+
+ /* SIM is not available */
+ if (!subscr->sim_valid) {
+ LOGP(DMM, LOGL_INFO, "AUTHENTICATION REQUEST without SIM\n");
+ return gsm48_mm_tx_mm_status(ms,
+ GSM48_REJECT_MSG_NOT_COMPATIBLE);
+ }
+
+ LOGP(DMM, LOGL_INFO, "AUTHENTICATION REQUEST (seq %d)\n", ar->key_seq);
+
+ /* key_seq and random */
+#ifdef TODO
+ new key to sim:
+ (..., ar->key_seq, ar->rand);
+#else
+ /* Fake response */
+ struct msgb *nmsg;
+ struct gsm48_mm_event *nmme;
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ nmme = (struct gsm48_mm_event *)msgb_put(nmsg, sizeof(*nmme));
+ *((uint32_t *)nmme->sres) = 0x12345678;
+ gsm48_mm_tx_auth_rsp(ms, nmsg);
+ msgb_free(nmsg);
+#endif
+
+ /* wait for auth response event from SIM */
+ return 0;
+}
+
+/* 4.3.2.2 sending AUTHENTICATION RESPONSE */
+static int gsm48_mm_tx_auth_rsp(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mm_event *mme = (struct gsm48_mm_event *) msg->data;
+ struct msgb *nmsg;
+ struct gsm48_hdr *ngh;
+ uint8_t *sres;
+
+ LOGP(DMM, LOGL_INFO, "AUTHENTICATION RESPONSE\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh));
+
+ ngh->proto_discr = GSM48_PDISC_MM;
+ ngh->msg_type = GSM48_MT_MM_AUTH_RESP;
+
+ /* SRES */
+ sres = msgb_put(nmsg, 4);
+ memcpy(sres, mme->sres, 4);
+
+ /* push RR header and send down */
+ return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_DATA_REQ, 0);
+}
+
+/* 4.3.2.5 AUTHENTICATION REJECT is received */
+static int gsm48_mm_rx_auth_rej(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ LOGP(DMM, LOGL_INFO, "AUTHENTICATION REJECT\n");
+
+ stop_mm_t3212(mm); /* 4.4.2 */
+
+ /* SIM invalid */
+ subscr->sim_valid = 0;
+
+ /* TMSI and LAI invalid */
+ subscr->lai_valid = 0;
+ subscr->tmsi_valid = 0;
+
+ /* key is invalid */
+ subscr->key_seq = 7;
+
+ /* update status */
+ new_sim_ustate(subscr, GSM_SIM_U3_ROAMING_NA);
+
+#ifdef TODO
+ sim: delete tmsi, lai
+ sim: delete key seq number
+ sim: set update status
+#endif
+
+ /* abort IMSI detach procedure */
+ if (mm->state == GSM48_MM_ST_IMSI_DETACH_INIT) {
+ struct msgb *nmsg;
+ struct gsm48_rr_hdr *nrrh;
+
+ /* abort RR connection */
+ nmsg = gsm48_rr_msgb_alloc(GSM48_RR_ABORT_REQ);
+ if (!nmsg)
+ return -ENOMEM;
+ nrrh = (struct gsm48_rr_hdr *) msgb_put(nmsg, sizeof(*nrrh));
+ nrrh->cause = GSM48_RR_CAUSE_NORMAL;
+ gsm48_rr_downmsg(ms, nmsg);
+
+ /* CS process will trigger: return to MM IDLE / No SIM */
+ return 0;
+ }
+
+ return 0;
+}
+
+/* 4.3.3.1 IDENTITY REQUEST is received */
+static int gsm48_mm_rx_id_req(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ uint8_t mi_type;
+
+ if (payload_len < 1) {
+ LOGP(DMM, LOGL_NOTICE, "Short read of IDENTITY REQUEST message "
+ "error.\n");
+ return -EINVAL;
+ }
+
+ /* id type */
+ mi_type = *gh->data;
+ LOGP(DMM, LOGL_INFO, "IDENTITY REQUEST (mi_type %d)\n", mi_type);
+
+ /* check if request can be fulfilled */
+ if (!subscr->sim_valid && mi_type != GSM_MI_TYPE_IMEI
+ && mi_type != GSM_MI_TYPE_IMEISV) {
+ LOGP(DMM, LOGL_INFO, "IDENTITY REQUEST without SIM\n");
+ return gsm48_mm_tx_mm_status(ms,
+ GSM48_REJECT_MSG_NOT_COMPATIBLE);
+ }
+ if (mi_type == GSM_MI_TYPE_TMSI && !subscr->tmsi_valid) {
+ LOGP(DMM, LOGL_INFO, "IDENTITY REQUEST of TMSI, but we have no "
+ "TMSI\n");
+ return gsm48_mm_tx_mm_status(ms,
+ GSM48_REJECT_MSG_NOT_COMPATIBLE);
+ }
+
+ return gsm48_mm_tx_id_rsp(ms, mi_type);
+}
+
+/* send IDENTITY RESPONSE message */
+static int gsm48_mm_tx_id_rsp(struct osmocom_ms *ms, uint8_t mi_type)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *ngh;
+ uint8_t buf[11];
+
+ LOGP(DMM, LOGL_INFO, "IDENTITY RESPONSE\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh));
+
+ ngh->proto_discr = GSM48_PDISC_MM;
+ ngh->msg_type = GSM48_MT_MM_ID_RESP;
+
+ /* MI */
+ gsm48_encode_mi(buf, nmsg, ms, mi_type);
+
+ /* push RR header and send down */
+ return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_DATA_REQ, 0);
+}
+
+/* 4.3.4.1 sending IMSI DETACH INDICATION message */
+static int gsm48_mm_tx_imsi_detach(struct osmocom_ms *ms, int rr_prim)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm_support *sup = &ms->support;
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct msgb *nmsg;
+ struct gsm48_hdr *ngh;
+ uint8_t pwr_lev;
+ uint8_t buf[11];
+ struct gsm48_classmark1 cm;
+
+
+ LOGP(DMM, LOGL_INFO, "IMSI DETACH INDICATION\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh));
+
+ ngh->proto_discr = GSM48_PDISC_MM;
+ ngh->msg_type = GSM48_MT_MM_IMSI_DETACH_IND;
+
+ /* classmark 1 */
+ if (rr->cd_now.arfcn >= 512 && rr->cd_now.arfcn <= 885)
+ pwr_lev = sup->pwr_lev_1800;
+ else
+ pwr_lev = sup->pwr_lev_900;
+ gsm48_encode_classmark1(&cm, sup->rev_lev, sup->es_ind, sup->a5_1,
+ pwr_lev);
+ msgb_v_put(nmsg, *((uint8_t *)&cm));
+ /* MI */
+ if (subscr->tmsi_valid) /* have TMSI ? */
+ gsm48_encode_mi(buf, nmsg, ms, GSM_MI_TYPE_TMSI);
+ else
+ gsm48_encode_mi(buf, nmsg, ms, GSM_MI_TYPE_IMSI);
+
+ /* push RR header and send down */
+ return gsm48_mm_to_rr(ms, nmsg, rr_prim, RR_EST_CAUSE_OTHER_SDCCH);
+}
+
+/* detach has ended */
+static int gsm48_mm_imsi_detach_end(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct msgb *nmsg;
+
+ LOGP(DMM, LOGL_INFO, "IMSI has been detached.\n");
+
+ /* stop IMSI detach timer (if running) */
+ stop_mm_t3220(mm);
+
+ /* update SIM */
+#ifdef TODO
+ sim: store BA list
+ sim: what else?:
+#endif
+
+ /* SIM invalid */
+ subscr->sim_valid = 0;
+
+ /* wait for RR idle and then power off when IMSI is detached */
+ if (mm->power_off) {
+ if (mm->state == GSM48_MM_ST_MM_IDLE) {
+ l23_app_exit(ms);
+ exit(0);
+ }
+ mm->power_off_idle = 1;
+
+ return 0;
+ }
+
+ /* send SIM remove event to gsm322 */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_SIM_REMOVE);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+
+ /* CS process will trigger return to MM IDLE / No SIM */
+ return 0;
+}
+
+/* start an IMSI detach in MM IDLE */
+static int gsm48_mm_imsi_detach_start(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_sysinfo *s = &ms->cellsel.sel_si;
+
+ /* we may silently finish IMSI detach */
+ if (!s->att_allowed || !subscr->imsi_attached) {
+ LOGP(DMM, LOGL_INFO, "IMSI detach not required.\n");
+
+ return gsm48_mm_imsi_detach_end(ms, msg);
+ }
+ LOGP(DMM, LOGL_INFO, "IMSI detach started (MM IDLE)\n");
+
+ new_mm_state(mm, GSM48_MM_ST_WAIT_RR_CONN_IMSI_D, 0);
+
+ /* establish RR and send IMSI detach */
+ return gsm48_mm_tx_imsi_detach(ms, GSM48_RR_EST_REQ);
+}
+
+/* IMSI detach has been sent, wait for RR release */
+static int gsm48_mm_imsi_detach_sent(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ /* start T3220 (4.3.4.1) */
+ start_mm_t3220(mm);
+
+ LOGP(DMM, LOGL_INFO, "IMSI detach started (Wait for RR release)\n");
+
+ new_mm_state(mm, GSM48_MM_ST_IMSI_DETACH_INIT, 0);
+
+ return 0;
+}
+
+/* release MM connection and proceed with IMSI detach */
+static int gsm48_mm_imsi_detach_release(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_sysinfo *s = &ms->cellsel.sel_si;
+
+ /* stop MM connection timer */
+ stop_mm_t3230(mm);
+
+ /* release all connections */
+ gsm48_mm_release_mm_conn(ms, 1, 16, 0);
+
+ /* wait for release of RR */
+ if (!s->att_allowed || !subscr->imsi_attached) {
+ LOGP(DMM, LOGL_INFO, "IMSI detach not required.\n");
+ new_mm_state(mm, GSM48_MM_ST_WAIT_NETWORK_CMD, 0);
+
+ /* power off */
+ if (mm->power_off) {
+ l23_app_exit(ms);
+ exit(0);
+ }
+
+ return 0;
+ }
+
+ /* send IMSI detach */
+ gsm48_mm_tx_imsi_detach(ms, GSM48_RR_DATA_REQ);
+
+ /* go to sent state */
+ return gsm48_mm_imsi_detach_sent(ms, msg);
+}
+
+/* ignore ongoing IMSI detach */
+static int gsm48_mm_imsi_detach_ignore(struct osmocom_ms *ms, struct msgb *msg)
+{
+ return 0;
+}
+
+/* delay until state change (and then retry) */
+static int gsm48_mm_imsi_detach_delay(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ LOGP(DMM, LOGL_INFO, "IMSI detach delayed.\n");
+
+ /* remember to detach later */
+ mm->delay_detach = 1;
+
+ return 0;
+}
+
+/* 4.3.5.2 ABORT is received */
+static int gsm48_mm_rx_abort(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ uint8_t reject_cause;
+
+ if (payload_len < 1) {
+ LOGP(DMM, LOGL_NOTICE, "Short read of ABORT message error.\n");
+ return -EINVAL;
+ }
+
+ reject_cause = *gh->data;
+
+ if (llist_empty(&mm->mm_conn)) {
+ LOGP(DMM, LOGL_NOTICE, "ABORT (cause #%d) while no MM "
+ "connection is established.\n", reject_cause);
+ return gsm48_mm_tx_mm_status(ms,
+ GSM48_REJECT_MSG_NOT_COMPATIBLE);
+ } else {
+ LOGP(DMM, LOGL_NOTICE, "ABORT (cause #%d) while MM connection "
+ "is established.\n", reject_cause);
+ /* stop MM connection timer */
+ stop_mm_t3230(mm);
+
+ gsm48_mm_release_mm_conn(ms, 1, 16, 0);
+ }
+
+ if (reject_cause == GSM48_REJECT_ILLEGAL_ME) {
+ /* SIM invalid */
+ subscr->sim_valid = 0;
+
+ /* TMSI and LAI invalid */
+ subscr->lai_valid = 0;
+ subscr->tmsi_valid = 0;
+
+ /* key is invalid */
+ subscr->key_seq = 7;
+
+ /* update status */
+ new_sim_ustate(subscr, GSM_SIM_U3_ROAMING_NA);
+
+#ifdef TODO
+ sim: delete tmsi, lai
+ sim: delete key seq number
+ sim: apply update state
+#endif
+
+ /* CS process will trigger: return to MM IDLE / No SIM */
+ return 0;
+ }
+
+ return 0;
+}
+
+/* 4.3.6.2 MM INFORMATION is received */
+static int gsm48_mm_rx_info(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+
+ if (payload_len < 0) {
+ LOGP(DMM, LOGL_NOTICE, "Short read of MM INFORMATION message "
+ "error.\n");
+ return -EINVAL;
+ }
+ tlv_parse(&tp, &gsm48_mm_att_tlvdef, gh->data, payload_len, 0, 0);
+
+ /* long name */
+ if (TLVP_PRESENT(&tp, GSM48_IE_NAME_LONG)) {
+ decode_network_name(mm->name_long, sizeof(mm->name_long),
+ TLVP_VAL(&tp, GSM48_IE_NAME_LONG)-1);
+ }
+ /* short name */
+ if (TLVP_PRESENT(&tp, GSM48_IE_NAME_SHORT)) {
+ decode_network_name(mm->name_short, sizeof(mm->name_short),
+ TLVP_VAL(&tp, GSM48_IE_NAME_SHORT)-1);
+ }
+
+ return 0;
+}
+
+/*
+ * process handlers for Location Update + IMSI attach (MM specific procedures)
+ */
+
+/* 4.4.2 received sysinfo change event */
+static int gsm48_mm_sysinfo(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_sysinfo *s = &ms->cellsel.sel_si;
+
+ /* t3212 not changed in these states */
+ if (mm->state == GSM48_MM_ST_MM_IDLE
+ && (mm->substate == GSM48_MM_SST_NO_CELL_AVAIL
+ || mm->substate == GSM48_MM_SST_LIMITED_SERVICE
+ || mm->substate == GSM48_MM_SST_PLMN_SEARCH
+ || mm->substate == GSM48_MM_SST_PLMN_SEARCH_NORMAL))
+ return 0;
+
+ /* new periodic location update timer timeout */
+ if (s->t3212 && s->t3212 != mm->t3212_value) {
+ if (bsc_timer_pending(&mm->t3212)) {
+ int t;
+ struct timeval current_time;
+
+ /* get rest time */
+ gettimeofday(&current_time, NULL);
+ t = mm->t3212.timeout.tv_sec - current_time.tv_sec;
+ if (t < 0)
+ t = 0;
+ LOGP(DMM, LOGL_INFO, "New T3212 while timer is running "
+ "(value %d rest %d)\n", s->t3212, t);
+
+ /* rest time modulo given value */
+ mm->t3212.timeout.tv_sec = current_time.tv_sec
+ + (t % s->t3212);
+ } else {
+ uint32_t rand = random();
+
+ LOGP(DMM, LOGL_INFO, "New T3212 while timer is not "
+ "running (value %d)\n", s->t3212);
+
+ /* value between 0 and given value */
+ start_mm_t3212(mm, rand % (s->t3212 + 1));
+ }
+ mm->t3212_value = s->t3212;
+ }
+
+ return 0;
+}
+
+/* 4.4.4.1 (re)start location update
+ *
+ * this function is called by
+ * - normal location update
+ * - periodic location update
+ * - imsi attach (normal loc. upd. function)
+ * - retry timers (T3211 and T3213)
+ */
+static int gsm48_mm_loc_upd(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm48_sysinfo *s = &cs->sel_si;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm_settings *set = &ms->settings;
+ struct msgb *nmsg;
+ int msg_type;
+
+ /* (re)start only if we still require location update */
+ if (!mm->lupd_pending) {
+ LOGP(DMM, LOGL_INFO, "No loc. upd. pending.\n");
+ return 0;
+ }
+
+ /* must camp normally */
+ if (cs->state != GSM322_C3_CAMPED_NORMALLY) {
+ LOGP(DMM, LOGL_INFO, "Loc. upd. not camping normally.\n");
+ msg_type = GSM322_EVENT_REG_FAILED;
+ stop:
+ LOGP(DSUM, LOGL_INFO, "Location update not possible\n");
+ mm->lupd_pending = 0;
+ /* send message to PLMN search process */
+ nmsg = gsm322_msgb_alloc(msg_type);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+ return 0;
+ }
+
+ /* deny network, if disabled */
+ if (set->no_lupd) {
+ LOGP(DMM, LOGL_INFO, "Loc. upd. disabled, adding "
+ "forbidden PLMN.\n");
+ gsm_subscr_add_forbidden_plmn(subscr, cs->sel_mcc,
+ cs->sel_mnc, GSM48_REJECT_PLMN_NOT_ALLOWED);
+ msg_type = GSM322_EVENT_ROAMING_NA;
+ goto stop;
+ }
+
+ /* if LAI is forbidden, don't start */
+ if (gsm_subscr_is_forbidden_plmn(subscr, cs->sel_mcc, cs->sel_mnc)) {
+ LOGP(DMM, LOGL_INFO, "Loc. upd. not allowed PLMN.\n");
+ msg_type = GSM322_EVENT_ROAMING_NA;
+ goto stop;
+ }
+ if (gsm322_is_forbidden_la(ms, cs->sel_mcc,
+ cs->sel_mnc, cs->sel_lac)) {
+ LOGP(DMM, LOGL_INFO, "Loc. upd. not allowed LA.\n");
+ msg_type = GSM322_EVENT_ROAMING_NA;
+ goto stop;
+ }
+
+ /* 4.4.4.9 if cell is barred, don't start */
+ if ((!subscr->acc_barr && s->cell_barr)
+ || (!subscr->acc_barr && !((subscr->acc_class & 0xfbff) &
+ (s->class_barr ^ 0xffff)))) {
+ LOGP(DMM, LOGL_INFO, "Loc. upd. no access.\n");
+ msg_type = GSM322_EVENT_ROAMING_NA;
+ goto stop;
+ }
+
+ mm->lupd_mcc = cs->sel_mcc;
+ mm->lupd_mnc = cs->sel_mnc;
+ mm->lupd_lac = cs->sel_lac;
+
+ LOGP(DSUM, LOGL_INFO, "Perform location update (MCC %s, MNC %s "
+ "LAC 0x%04x)\n", gsm_print_mcc(mm->lupd_mcc),
+ gsm_print_mnc(mm->lupd_mnc), mm->lupd_lac);
+
+ return gsm48_mm_tx_loc_upd_req(ms);
+}
+
+/* initiate a normal location update / imsi attach */
+static int gsm48_mm_loc_upd_normal(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm48_sysinfo *s = &cs->sel_si;
+ struct msgb *nmsg;
+
+ /* in case we already have a location update going on */
+ if (mm->lupd_pending) {
+ LOGP(DMM, LOGL_INFO, "Loc. upd. already pending.\n");
+
+ return -EBUSY;
+ }
+
+ /* no location update, if limited service */
+ if (cs->state != GSM322_C3_CAMPED_NORMALLY) {
+ LOGP(DMM, LOGL_INFO, "Loc. upd. not allowed.\n");
+
+ /* send message to PLMN search process */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_FAILED);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+
+ return 0;
+ }
+
+ /* if location update is not required */
+ if (subscr->ustate == GSM_SIM_U1_UPDATED
+ && cs->selected
+ && cs->sel_mcc == subscr->lai_mcc
+ && cs->sel_mnc == subscr->lai_mnc
+ && cs->sel_lac == subscr->lai_lac
+ && (subscr->imsi_attached
+ || !s->att_allowed)) {
+ LOGP(DMM, LOGL_INFO, "Loc. upd. not required.\n");
+ subscr->imsi_attached = 1;
+
+ /* send message to PLMN search process */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_SUCCESS);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+
+ return 0;
+ }
+
+ /* 4.4.3 is attachment required? */
+ if (subscr->ustate == GSM_SIM_U1_UPDATED
+ && cs->selected
+ && cs->sel_mcc == subscr->lai_mcc
+ && cs->sel_mnc == subscr->lai_mnc
+ && cs->sel_lac == subscr->lai_lac
+ && !subscr->imsi_attached
+ && s->att_allowed) {
+ /* do location update for IMSI attach */
+ LOGP(DMM, LOGL_INFO, "Do Loc. upd. for IMSI attach.\n");
+ mm->lupd_type = 2;
+ } else {
+ /* do normal location update */
+ LOGP(DMM, LOGL_INFO, "Do normal Loc. upd.\n");
+ mm->lupd_type = 0;
+ }
+
+ /* start location update */
+ mm->lupd_attempt = 0;
+ mm->lupd_pending = 1;
+ mm->lupd_ra_failure = 0;
+
+ return gsm48_mm_loc_upd(ms, msg);
+}
+
+/* initiate a periodic location update */
+static int gsm48_mm_loc_upd_periodic(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ /* in case we already have a location update going on */
+ if (mm->lupd_pending) {
+ LOGP(DMM, LOGL_INFO, "Loc. upd. already pending.\n");
+ return -EBUSY;
+ }
+
+ /* start periodic location update */
+ mm->lupd_type = 1;
+ mm->lupd_pending = 1;
+ mm->lupd_ra_failure = 0;
+
+ return gsm48_mm_loc_upd(ms, msg);
+}
+
+/* ignore location update */
+static int gsm48_mm_loc_upd_ignore(struct osmocom_ms *ms, struct msgb *msg)
+{
+ return 0;
+}
+
+/* 9.2.15 send LOCATION UPDATING REQUEST message */
+static int gsm48_mm_tx_loc_upd_req(struct osmocom_ms *ms)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm_support *sup = &ms->support;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct msgb *nmsg;
+ struct gsm48_hdr *ngh;
+ struct gsm48_loc_upd_req *nlu; /* NOTE: mi_len is part of struct */
+ uint8_t pwr_lev;
+ uint8_t buf[11];
+
+ LOGP(DMM, LOGL_INFO, "LOCATION UPDATING REQUEST\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh));
+ nlu = (struct gsm48_loc_upd_req *)msgb_put(nmsg, sizeof(*nlu));
+
+ ngh->proto_discr = GSM48_PDISC_MM;
+ ngh->msg_type = GSM48_MT_MM_LOC_UPD_REQUEST;
+
+ /* location updating type */
+ nlu->type = mm->lupd_type;
+ /* cipering key */
+ nlu->key_seq = subscr->key_seq;
+ /* LAI (use last SIM stored LAI) */
+ gsm48_generate_lai(&nlu->lai,
+ subscr->lai_mcc, subscr->lai_mnc, subscr->lai_lac);
+ /* classmark 1 */
+ if (rr->cd_now.arfcn >= 512 && rr->cd_now.arfcn <= 885)
+ pwr_lev = sup->pwr_lev_1800;
+ else
+ pwr_lev = sup->pwr_lev_900;
+ gsm48_encode_classmark1(&nlu->classmark1, sup->rev_lev, sup->es_ind,
+ sup->a5_1, pwr_lev);
+ /* MI */
+ if (subscr->tmsi_valid) /* have TMSI ? */
+ gsm48_encode_mi(buf, NULL, ms, GSM_MI_TYPE_TMSI);
+ else
+ gsm48_encode_mi(buf, NULL, ms, GSM_MI_TYPE_IMSI);
+ msgb_put(nmsg, buf[1]); /* length is part of nlu */
+ memcpy(&nlu->mi_len, buf + 1, 1 + buf[1]);
+
+ new_mm_state(mm, GSM48_MM_ST_WAIT_RR_CONN_LUPD, 0);
+
+ /* push RR header and send down */
+ return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_EST_REQ, RR_EST_CAUSE_LOC_UPD);
+}
+
+/* 4.4.4.1 RR is esablised during location update */
+static int gsm48_mm_est_loc_upd(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ /* start location update timer */
+ start_mm_t3210(mm);
+
+ new_mm_state(mm, GSM48_MM_ST_LOC_UPD_INIT, 0);
+
+ return 0;
+}
+
+/* 4.4.4.6 LOCATION UPDATING ACCEPT is received */
+static int gsm48_mm_rx_loc_upd_acc(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct gsm48_loc_area_id *lai = (struct gsm48_loc_area_id *) gh->data;
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct msgb *nmsg;
+
+ if (payload_len < sizeof(struct gsm48_loc_area_id)) {
+ short_read:
+ LOGP(DMM, LOGL_NOTICE, "Short read of LOCATION UPDATING ACCEPT "
+ "message error.\n");
+ return -EINVAL;
+ }
+ tlv_parse(&tp, &gsm48_mm_att_tlvdef,
+ gh->data + sizeof(struct gsm48_loc_area_id),
+ payload_len - sizeof(struct gsm48_loc_area_id), 0, 0);
+
+ /* update has finished */
+ mm->lupd_pending = 0;
+
+ /* RA was successfull */
+ mm->lupd_ra_failure = 0;
+
+ /* stop periodic location updating timer */
+ stop_mm_t3212(mm); /* 4.4.2 */
+
+ /* LAI */
+ subscr->lai_valid = 1;
+ gsm48_decode_lai(lai, &subscr->lai_mcc, &subscr->lai_mnc,
+ &subscr->lai_lac);
+
+ /* stop location update timer */
+ stop_mm_t3210(mm);
+
+ /* reset attempt counter */
+ mm->lupd_attempt = 0;
+
+ /* mark SIM as attached */
+ subscr->imsi_attached = 1;
+
+ /* set the status in the sim to updated */
+ new_sim_ustate(subscr, GSM_SIM_U1_UPDATED);
+#ifdef TODO
+ sim: apply update state
+#endif
+
+ /* set last registered PLMN */
+ subscr->plmn_valid = 1;
+ subscr->plmn_mcc = subscr->lai_mcc;
+ subscr->plmn_mnc = subscr->lai_mnc;
+#ifdef TODO
+ sim: store plmn
+#endif
+
+ LOGP(DSUM, LOGL_INFO, "Location update accepted\n");
+ LOGP(DMM, LOGL_INFO, "LOCATION UPDATING ACCEPT (mcc %s mnc %s "
+ "lac 0x%04x)\n", gsm_print_mcc(subscr->lai_mcc),
+ gsm_print_mnc(subscr->lai_mnc), subscr->lai_lac);
+
+ /* remove LA from forbidden list */
+ gsm322_del_forbidden_la(ms, subscr->lai_mcc, subscr->lai_mnc,
+ subscr->lai_lac);
+
+ /* MI */
+ if (TLVP_PRESENT(&tp, GSM48_IE_MOBILE_ID)) {
+ const uint8_t *mi;
+ uint8_t mi_type;
+ uint32_t tmsi;
+
+ mi = TLVP_VAL(&tp, GSM48_IE_MOBILE_ID)-1;
+ if (mi[0] < 1)
+ goto short_read;
+ mi_type = mi[1] & GSM_MI_TYPE_MASK;
+ switch (mi_type) {
+ case GSM_MI_TYPE_TMSI:
+ if (payload_len + sizeof(struct gsm48_loc_area_id) < 6
+ || mi[0] < 5)
+ goto short_read;
+ memcpy(&tmsi, mi+2, 4);
+ subscr->tmsi = ntohl(tmsi);
+ subscr->tmsi_valid = 1;
+ LOGP(DMM, LOGL_INFO, "got TMSI 0x%08x (%u)\n",
+ subscr->tmsi, subscr->tmsi);
+#ifdef TODO
+ sim: store tmsi
+#endif
+ break;
+ case GSM_MI_TYPE_IMSI:
+ LOGP(DMM, LOGL_INFO, "TMSI removed\n");
+ subscr->tmsi_valid = 0;
+#ifdef TODO
+ sim: delete tmsi
+#endif
+ /* send TMSI REALLOCATION COMPLETE */
+ gsm48_mm_tx_tmsi_reall_cpl(ms);
+ break;
+ default:
+ LOGP(DMM, LOGL_NOTICE, "TMSI reallocation with unknown "
+ "MI type %d.\n", mi_type);
+ }
+ }
+
+ /* send message to PLMN search process */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_SUCCESS);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+
+ /* follow on proceed */
+ if (TLVP_PRESENT(&tp, GSM48_IE_MOBILE_ID))
+ LOGP(DMM, LOGL_NOTICE, "follow-on proceed not supported.\n");
+
+ /* start RR release timer */
+ start_mm_t3240(mm);
+
+ new_mm_state(mm, GSM48_MM_ST_WAIT_NETWORK_CMD, 0);
+
+ return 0;
+}
+
+/* 4.4.4.7 LOCATION UPDATING REJECT is received */
+static int gsm48_mm_rx_loc_upd_rej(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+
+ if (payload_len < 1) {
+ LOGP(DMM, LOGL_NOTICE, "Short read of LOCATION UPDATING REJECT "
+ "message error.\n");
+ return -EINVAL;
+ }
+
+ /* RA was successfull */
+ mm->lupd_ra_failure = 0;
+
+ /* stop periodic location updating timer */
+ stop_mm_t3212(mm); /* 4.4.2 */
+
+ /* stop location update timer */
+ stop_mm_t3210(mm);
+
+ /* store until RR is released */
+ mm->lupd_rej_cause = *gh->data;
+
+ /* start RR release timer */
+ start_mm_t3240(mm);
+
+ new_mm_state(mm, GSM48_MM_ST_LOC_UPD_REJ, 0);
+
+ return 0;
+}
+
+/* 4.4.4.7 RR is released after location update reject */
+static int gsm48_mm_rel_loc_upd_rej(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct msgb *nmsg;
+ struct gsm322_msg *ngm;
+
+ LOGP(DMM, LOGL_INFO, "Loc. upd. rejected (cause %d)\n",
+ mm->lupd_rej_cause);
+
+ /* stop RR release timer */
+ stop_mm_t3240(mm);
+
+ /* new status */
+ switch (mm->lupd_rej_cause) {
+ case GSM48_REJECT_IMSI_UNKNOWN_IN_HLR:
+ case GSM48_REJECT_ILLEGAL_MS:
+ case GSM48_REJECT_ILLEGAL_ME:
+ /* reset attempt counter */
+ mm->lupd_attempt = 0;
+
+ /* SIM invalid */
+ subscr->sim_valid = 0;
+
+ // fall through
+ case GSM48_REJECT_PLMN_NOT_ALLOWED:
+ case GSM48_REJECT_LOC_NOT_ALLOWED:
+ case GSM48_REJECT_ROAMING_NOT_ALLOWED:
+ /* TMSI and LAI invalid */
+ subscr->lai_valid = 0;
+ subscr->tmsi_valid = 0;
+
+ /* key is invalid */
+ subscr->key_seq = 7;
+
+ /* update status */
+ new_sim_ustate(subscr, GSM_SIM_U3_ROAMING_NA);
+#ifdef TODO
+ sim: delete tmsi, lai
+ sim: delete key seq number
+ sim: apply update state
+#endif
+ /* update has finished */
+ mm->lupd_pending = 0;
+ }
+
+ /* send event to PLMN search process */
+ switch(mm->lupd_rej_cause) {
+ case GSM48_REJECT_ROAMING_NOT_ALLOWED:
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_ROAMING_NA);
+ break;
+ case GSM48_REJECT_IMSI_UNKNOWN_IN_HLR:
+ case GSM48_REJECT_ILLEGAL_MS:
+ case GSM48_REJECT_ILLEGAL_ME:
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_INVALID_SIM);
+ break;
+ default:
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_FAILED);
+ }
+ if (!nmsg)
+ return -ENOMEM;
+ ngm = (struct gsm322_msg *)nmsg->data;
+ ngm->reject = mm->lupd_rej_cause;
+ gsm322_plmn_sendmsg(ms, nmsg);
+
+ /* forbidden list */
+ switch (mm->lupd_rej_cause) {
+ case GSM48_REJECT_IMSI_UNKNOWN_IN_HLR:
+ LOGP(DSUM, LOGL_INFO, "Location update failed (IMSI unknown "
+ "in HLR)\n");
+ break;
+ case GSM48_REJECT_ILLEGAL_MS:
+ LOGP(DSUM, LOGL_INFO, "Location update failed (Illegal MS)\n");
+ break;
+ case GSM48_REJECT_ILLEGAL_ME:
+ LOGP(DSUM, LOGL_INFO, "Location update failed (Illegal ME)\n");
+ break;
+ case GSM48_REJECT_PLMN_NOT_ALLOWED:
+ gsm_subscr_add_forbidden_plmn(subscr, mm->lupd_mcc,
+ mm->lupd_mnc, mm->lupd_rej_cause);
+ LOGP(DSUM, LOGL_INFO, "Location update failed (PLMN not "
+ "allowed)\n");
+ break;
+ case GSM48_REJECT_LOC_NOT_ALLOWED:
+ case GSM48_REJECT_ROAMING_NOT_ALLOWED:
+ gsm322_add_forbidden_la(ms, mm->lupd_mcc, mm->lupd_mnc,
+ mm->lupd_lac, mm->lupd_rej_cause);
+ LOGP(DSUM, LOGL_INFO, "Location update failed (LAI not "
+ "allowed)\n");
+ break;
+ default:
+ /* 4.4.4.9 continue with failure handling */
+ return gsm48_mm_loc_upd_failed(ms, NULL);
+ }
+
+ /* CS proc triggers: return to IDLE, case 13 is also handled there */
+ return 0;
+}
+
+/* 4.2.2 delay a location update */
+static int gsm48_mm_loc_upd_delay_per(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ LOGP(DMM, LOGL_INFO, "Schedule a pending periodic loc. upd.\n");
+ mm->lupd_periodic = 1;
+
+ return 0;
+}
+
+/* delay a location update retry */
+static int gsm48_mm_loc_upd_delay_retry(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ LOGP(DMM, LOGL_INFO, "Schedule a pending periodic loc. upd.\n");
+ mm->lupd_retry = 1;
+
+ return 0;
+}
+
+/* process failues as described in the lower part of 4.4.4.9 */
+static int gsm48_mm_loc_upd_failed(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm_subscriber *subscr = &ms->subscr;
+
+ LOGP(DSUM, LOGL_INFO, "Location update failed\n");
+
+ /* stop location update timer, if running */
+ stop_mm_t3210(mm);
+
+ if (subscr->ustate == GSM_SIM_U1_UPDATED
+ && mm->lupd_mcc == subscr->lai_mcc
+ && mm->lupd_mnc == subscr->lai_mnc
+ && mm->lupd_lac == subscr->lai_lac) {
+ if (mm->lupd_attempt < 4) {
+ LOGP(DSUM, LOGL_INFO, "Try location update later\n");
+ LOGP(DMM, LOGL_INFO, "Loc. upd. failed, retry #%d\n",
+ mm->lupd_attempt);
+
+ /* start update retry timer */
+ start_mm_t3211(mm);
+
+ /* CS process will trigger: return to MM IDLE */
+ return 0;
+ } else
+ LOGP(DMM, LOGL_INFO, "Loc. upd. failed too often.\n");
+ }
+
+ /* TMSI and LAI invalid */
+ subscr->lai_valid = 0;
+ subscr->tmsi_valid = 0;
+
+ /* key is invalid */
+ subscr->key_seq = 7;
+
+ /* update status */
+ new_sim_ustate(subscr, GSM_SIM_U2_NOT_UPDATED);
+
+#ifdef TODO
+ sim: delete tmsi, lai
+ sim: delete key seq number
+ sim: set update status
+#endif
+
+ /* start update retry timer (RR connection is released) */
+ if (mm->lupd_attempt < 4) {
+ mm->start_t3211 = 1;
+ LOGP(DSUM, LOGL_INFO, "Try location update later\n");
+ }
+
+ /* CS process will trigger: return to MM IDLE */
+ return 0;
+}
+
+/* abort a location update due to radio failure or release */
+static int gsm48_mm_rel_loc_upd_abort(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *)msg->data;
+
+ /* stop RR release timer */
+ stop_mm_t3240(mm);
+
+ if (rrh->msg_type == GSM48_RR_REL_IND) {
+ LOGP(DMM, LOGL_INFO, "RR link released after loc. upd.\n");
+
+ /* continue with failure handling */
+ return gsm48_mm_loc_upd_failed(ms, NULL);
+ }
+
+ LOGP(DMM, LOGL_INFO, "Loc. upd. aborted by radio (cause #%d)\n",
+ rrh->cause);
+
+ /* random access failure, but not two successive failures */
+ if (rrh->cause == RR_REL_CAUSE_RA_FAILURE && !mm->lupd_ra_failure) {
+ mm->lupd_ra_failure = 1;
+
+ /* start RA failure timer */
+ start_mm_t3213(mm);
+
+ return 0;
+ }
+
+ /* RA was successfull or sent twice */
+ mm->lupd_ra_failure = 0;
+
+ /* continue with failure handling */
+ return gsm48_mm_loc_upd_failed(ms, NULL);
+}
+
+/* location update has timed out */
+static int gsm48_mm_loc_upd_timeout(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct msgb *nmsg;
+ struct gsm48_rr_hdr *nrrh;
+
+ /* abort RR connection */
+ nmsg = gsm48_rr_msgb_alloc(GSM48_RR_ABORT_REQ);
+ if (!nmsg)
+ return -ENOMEM;
+ nrrh = (struct gsm48_rr_hdr *) msgb_put(nmsg, sizeof(*nrrh));
+ nrrh->cause = GSM48_RR_CAUSE_ABNORMAL_TIMER;
+ gsm48_rr_downmsg(ms, nmsg);
+
+ /* continue with failure handling */
+ return gsm48_mm_loc_upd_failed(ms, NULL);
+}
+
+/*
+ * process handlers for MM connections
+ */
+
+/* cm reestablish request message from upper layer */
+static int gsm48_mm_tx_cm_serv_req(struct osmocom_ms *ms, int rr_prim,
+ uint8_t cause, uint8_t cm_serv)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm_settings *set = &ms->settings;
+ struct msgb *nmsg;
+ struct gsm48_hdr *ngh;
+ struct gsm48_service_request *nsr; /* NOTE: includes MI length */
+ uint8_t *cm2lv;
+ uint8_t buf[11];
+
+ LOGP(DMM, LOGL_INFO, "CM SERVICE REQUEST (cause %d)\n", cause);
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh));
+ nsr = (struct gsm48_service_request *)msgb_put(nmsg, sizeof(*nsr));
+ cm2lv = (uint8_t *)&nsr->classmark;
+
+ ngh->proto_discr = GSM48_PDISC_MM;
+ ngh->msg_type = GSM48_MT_MM_CM_SERV_REQ;
+
+ /* type and key */
+ nsr->cm_service_type = cm_serv;
+ nsr->cipher_key_seq = subscr->key_seq;
+ /* classmark 2 */
+ cm2lv[0] = sizeof(struct gsm48_classmark2);
+ gsm48_rr_enc_cm2(ms, (struct gsm48_classmark2 *)(cm2lv + 1));
+ /* MI */
+ if (!subscr->sim_valid) { /* have no SIM ? */
+ if (set->emergency_imsi[0])
+ gsm48_generate_mid_from_imsi(buf,
+ set->emergency_imsi);
+ else
+ gsm48_encode_mi(buf, NULL, ms, GSM_MI_TYPE_IMEI);
+ } else if (subscr->tmsi_valid) /* have TMSI ? */
+ gsm48_encode_mi(buf, NULL, ms, GSM_MI_TYPE_TMSI);
+ else
+ gsm48_encode_mi(buf, NULL, ms, GSM_MI_TYPE_IMSI);
+ msgb_put(nmsg, buf[1]); /* length is part of nsr */
+ memcpy(&nsr->mi_len, buf + 1, 1 + buf[1]);
+ /* prio is optional for eMLPP */
+
+ /* push RR header and send down */
+ return gsm48_mm_to_rr(ms, nmsg, rr_prim, cause);
+}
+
+/* cm service abort message from upper layer
+ * NOTE: T3240 is started by the calling function
+ */
+static int gsm48_mm_tx_cm_service_abort(struct osmocom_ms *ms)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *ngh;
+
+ LOGP(DMM, LOGL_INFO, "CM SERVICE ABORT\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh));
+
+ ngh->proto_discr = GSM48_PDISC_MM;
+ ngh->msg_type = GSM48_MT_MM_CM_SERV_ABORT;
+
+ /* push RR header and send down */
+ return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_DATA_REQ, 0);
+}
+
+/* cm service acknowledge is received from lower layer */
+static int gsm48_mm_rx_cm_service_acc(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ /* stop MM connection timer */
+ stop_mm_t3230(mm);
+
+ new_mm_state(mm, GSM48_MM_ST_MM_CONN_ACTIVE, 0);
+
+ return gsm48_mm_conn_go_dedic(ms);
+}
+
+/* 9.2.6 CM SERVICE REJECT message received */
+static int gsm48_mm_rx_cm_service_rej(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ uint8_t abort_any = 0;
+ uint8_t reject_cause;
+
+ if (payload_len < 1) {
+ LOGP(DMM, LOGL_NOTICE, "Short read of cm service reject "
+ "message error.\n");
+ return -EINVAL;
+ }
+
+ /* reject cause */
+ reject_cause = *gh->data;
+
+ LOGP(DMM, LOGL_INFO, "CM SERVICE REJECT (cause %d)\n", reject_cause);
+
+ /* stop MM connection timer */
+ stop_mm_t3230(mm);
+
+ /* selection action on cause value */
+ switch (reject_cause) {
+ case GSM48_REJECT_IMSI_UNKNOWN_IN_VLR:
+ case GSM48_REJECT_ILLEGAL_ME:
+ abort_any = 1;
+
+ /* TMSI and LAI invalid */
+ subscr->lai_valid = 0;
+ subscr->tmsi_valid = 0;
+
+ /* key is invalid */
+ subscr->key_seq = 7;
+
+ /* update status */
+ new_sim_ustate(subscr, GSM_SIM_U2_NOT_UPDATED);
+
+#ifdef TODO
+ sim: delete tmsi, lai
+ sim: delete key seq number
+ sim: set update status
+#endif
+
+ /* change to WAIT_NETWORK_CMD state impied by abort_any == 1 */
+
+ if (reject_cause == GSM48_REJECT_ILLEGAL_ME)
+ subscr->sim_valid = 0;
+
+ break;
+ default:
+ /* state implied by the number of remaining connections */
+ ;
+ }
+
+ /* release MM connection(s) */
+ gsm48_mm_release_mm_conn(ms, abort_any, 16, 0);
+
+ /* state depends on the existance of remaining MM connections */
+ if (llist_empty(&mm->mm_conn))
+ new_mm_state(mm, GSM48_MM_ST_WAIT_NETWORK_CMD, 0);
+ else
+ new_mm_state(mm, GSM48_MM_ST_MM_CONN_ACTIVE, 0);
+
+ return 0;
+}
+
+/* initiate an MM connection 4.5.1.1
+ *
+ * this function is called when:
+ * - no RR connection exists
+ * - an RR connection exists, but this is the first MM connection
+ * - an RR connection exists, and there are already MM connection(s)
+ */
+static int gsm48_mm_init_mm(struct osmocom_ms *ms, struct msgb *msg,
+ int rr_prim)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data;
+ int msg_type = mmh->msg_type;
+ int emergency = 0;
+ uint8_t cause = 0, cm_serv = 0, proto = 0;
+ struct msgb *nmsg;
+ struct gsm48_mmxx_hdr *nmmh;
+ struct gsm48_mm_conn *conn, *conn_found = NULL;
+
+ /* reset loc. upd. counter on CM service request */
+ mm->lupd_attempt = 0;
+
+ /* find if there is already a pending connection */
+ llist_for_each_entry(conn, &mm->mm_conn, list) {
+ if (conn->state == GSM48_MMXX_ST_CONN_PEND) {
+ conn_found = conn;
+ break;
+ }
+ }
+
+ /* if pending connection */
+ if (conn_found) {
+ LOGP(DMM, LOGL_INFO, "Init MM Connection, but already have "
+ "pending MM Connection.\n");
+ cause = 17;
+ reject:
+ nmsg = NULL;
+ switch(msg_type) {
+ case GSM48_MMCC_EST_REQ:
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_REL_IND,
+ mmh->ref, mmh->transaction_id);
+ break;
+ case GSM48_MMSS_EST_REQ:
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSS_REL_IND,
+ mmh->ref, mmh->transaction_id);
+ break;
+ case GSM48_MMSMS_EST_REQ:
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSMS_REL_IND,
+ mmh->ref, mmh->transaction_id);
+ break;
+ }
+ if (!nmsg)
+ return -ENOMEM;
+ nmmh = (struct gsm48_mmxx_hdr *)nmsg->data;
+ nmmh->cause = cause;
+ gsm48_mmxx_upmsg(ms, nmsg);
+
+ return -EBUSY;
+ }
+ /* in case of an emergency setup */
+ if (msg_type == GSM48_MMCC_EST_REQ && mmh->emergency)
+ emergency = 1;
+
+ /* if sim is not updated */
+ if (!emergency && subscr->ustate != GSM_SIM_U1_UPDATED) {
+ LOGP(DMM, LOGL_INFO, "Init MM Connection, but SIM not "
+ "updated.\n");
+ cause = 21;
+ goto reject;
+ }
+
+ if (mm->state == GSM48_MM_ST_MM_IDLE) {
+ /* current MM idle state */
+ switch (mm->substate) {
+ case GSM48_MM_SST_NORMAL_SERVICE:
+ case GSM48_MM_SST_PLMN_SEARCH_NORMAL:
+ LOGP(DMM, LOGL_INFO, "Init MM Connection.\n");
+ break; /* allow when normal */
+ case GSM48_MM_SST_LOC_UPD_NEEDED:
+ case GSM48_MM_SST_ATTEMPT_UPDATE:
+ /* store mm request if attempting to update */
+ if (!emergency) {
+ LOGP(DMM, LOGL_INFO, "Init MM Connection, but "
+ "attempting to update.\n");
+ cause = 21;
+ goto reject;
+ /* TODO: implement delay and start loc upd. */
+ }
+ break;
+ default:
+ /* reject if not emergency */
+ if (!emergency) {
+ LOGP(DMM, LOGL_INFO, "Init MM Connection, not "
+ "in normal state.\n");
+ cause = 21;
+ goto reject;
+ }
+ break;
+ }
+ } else
+ LOGP(DMM, LOGL_INFO, "Init another MM Connection.\n");
+
+ /* set cause, service, proto */
+ switch(msg_type) {
+ case GSM48_MMCC_EST_REQ:
+ if (emergency) {
+ cause = RR_EST_CAUSE_EMERGENCY;
+ cm_serv = GSM48_CMSERV_EMERGENCY;
+ } else {
+ cause = RR_EST_CAUSE_ORIG_TCHF;
+ cm_serv = GSM48_CMSERV_MO_CALL_PACKET;
+ }
+ proto = GSM48_PDISC_CC;
+ break;
+ case GSM48_MMSS_EST_REQ:
+ cause = RR_EST_CAUSE_OTHER_SDCCH;
+ cm_serv = GSM48_CMSERV_SUP_SERV;
+ proto = GSM48_PDISC_NC_SS;
+ break;
+ case GSM48_MMSMS_EST_REQ:
+ cause = RR_EST_CAUSE_OTHER_SDCCH;
+ cm_serv = GSM48_CMSERV_SMS;
+ proto = GSM48_PDISC_SMS;
+ break;
+ }
+
+ /* create MM connection instance */
+ conn = mm_conn_new(mm, proto, mmh->transaction_id, mmh->ref);
+ if (!conn)
+ return -ENOMEM;
+
+ new_conn_state(conn, GSM48_MMXX_ST_CONN_PEND);
+
+ /* send CM SERVICE REQUEST */
+ if (rr_prim)
+ return gsm48_mm_tx_cm_serv_req(ms, rr_prim, cause, cm_serv);
+ else
+ return 0;
+}
+
+/* 4.5.1.1 a) MM connection request triggers RR connection */
+static int gsm48_mm_init_mm_no_rr(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ int rc;
+
+ /* start MM connection by requesting RR connection */
+ rc = gsm48_mm_init_mm(ms, msg, GSM48_RR_EST_REQ);
+ if (rc)
+ return rc;
+
+ new_mm_state(mm, GSM48_MM_ST_WAIT_RR_CONN_MM_CON, 0);
+
+ return 0;
+}
+
+/* 4.5.1.1 a) RR is esablised during mm connection, wait for CM accepted */
+static int gsm48_mm_est_mm_con(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ /* 4.5.1.7 if there is no more MM connection */
+ if (llist_empty(&mm->mm_conn)) {
+ LOGP(DMM, LOGL_INFO, "MM Connection, are already gone.\n");
+
+ /* start RR release timer */
+ start_mm_t3240(mm);
+
+ new_mm_state(mm, GSM48_MM_ST_WAIT_NETWORK_CMD, 0);
+
+ /* send abort */
+ return gsm48_mm_tx_cm_service_abort(ms);
+ }
+
+ /* start MM connection timer */
+ start_mm_t3230(mm);
+
+ new_mm_state(mm, GSM48_MM_ST_WAIT_OUT_MM_CONN, 0);
+
+ return 0;
+}
+
+/* 4.5.1.1 b) MM connection request on existing RR connection */
+static int gsm48_mm_init_mm_first(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ int rc;
+
+ /* start MM connection by sending data */
+ rc = gsm48_mm_init_mm(ms, msg, GSM48_RR_DATA_REQ);
+ if (rc)
+ return rc;
+
+ /* stop "RR connection release not allowed" timer */
+ stop_mm_t3241(mm);
+
+ /* start MM connection timer */
+ start_mm_t3230(mm);
+
+ new_mm_state(mm, GSM48_MM_ST_WAIT_OUT_MM_CONN, 0);
+
+ return 0;
+}
+
+/* 4.5.1.1 b) another MM connection request on existing RR connection */
+static int gsm48_mm_init_mm_more(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ int rc;
+
+ /* start MM connection by sending data */
+ rc = gsm48_mm_init_mm(ms, msg, GSM48_RR_DATA_REQ);
+ if (rc)
+ return rc;
+
+ /* start MM connection timer */
+ start_mm_t3230(mm);
+
+ new_mm_state(mm, GSM48_MM_ST_WAIT_ADD_OUT_MM_CON, 0);
+
+ return 0;
+}
+
+/* 4.5.1.1 b) delay on WAIT FOR NETWORK COMMAND state */
+static int gsm48_mm_init_mm_wait(struct osmocom_ms *ms, struct msgb *msg)
+{
+ /* reject */
+ gsm48_mm_init_mm_reject(ms, msg);
+#if 0
+ this requires handling when leaving this state...
+
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ int rc;
+
+ /* just create the MM connection in pending state */
+ rc = gsm48_mm_init_mm(ms, msg, 0);
+ if (rc)
+ return rc;
+
+ /* start MM connection timer */
+ start_mm_t3230(mm);
+
+ new_mm_state(mm, GSM48_MM_ST_WAIT_ADD_OUT_MM_CON, 0);
+#endif
+
+ return 0;
+}
+
+/* initiate an mm connection other cases */
+static int gsm48_mm_init_mm_reject(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data;
+ int msg_type = mmh->msg_type;
+ struct msgb *nmsg;
+ struct gsm48_mmxx_hdr *nmmh;
+
+ /* reject */
+ nmsg = NULL;
+ switch(msg_type) {
+ case GSM48_MMCC_EST_REQ:
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_REL_IND, mmh->ref,
+ mmh->transaction_id);
+ break;
+ case GSM48_MMSS_EST_REQ:
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSS_REL_IND, mmh->ref,
+ mmh->transaction_id);
+ break;
+ case GSM48_MMSMS_EST_REQ:
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSMS_REL_IND, mmh->ref,
+ mmh->transaction_id);
+ break;
+ }
+ if (!nmsg)
+ return -ENOMEM;
+ nmmh = (struct gsm48_mmxx_hdr *)nmsg->data;
+ nmmh->cause = 17;
+ gsm48_mmxx_upmsg(ms, nmsg);
+
+ return 0;
+}
+
+/* accepting pending connection, got dedicated mode
+ *
+ * this function is called:
+ * - when ciphering command is received
+ * - when cm service is accepted
+ */
+static int gsm48_mm_conn_go_dedic(struct osmocom_ms *ms)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_mm_conn *conn, *conn_found = NULL;
+ struct msgb *nmsg;
+ struct gsm48_mmxx_hdr *nmmh;
+
+ /* the first and only pending connection is the recent requested */
+ llist_for_each_entry(conn, &mm->mm_conn, list) {
+ if (conn->state == GSM48_MMXX_ST_CONN_PEND) {
+ conn_found = conn;
+ break;
+ }
+ }
+
+ /* if no pending connection (anymore) */
+ if (!conn_found) {
+ LOGP(DMM, LOGL_INFO, "No pending MM Connection.\n");
+
+ return 0;
+ }
+
+ new_conn_state(conn, GSM48_MMXX_ST_DEDICATED);
+
+ /* send establishment confirm */
+ nmsg = NULL;
+ switch(conn_found->protocol) {
+ case GSM48_PDISC_CC:
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_EST_CNF, conn->ref,
+ conn->transaction_id);
+ break;
+ case GSM48_PDISC_NC_SS:
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSS_EST_CNF, conn->ref,
+ conn->transaction_id);
+ break;
+ case GSM48_PDISC_SMS:
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSMS_EST_CNF, conn->ref,
+ conn->transaction_id);
+ break;
+ }
+ if (!nmsg)
+ return -ENOMEM;
+ nmmh = (struct gsm48_mmxx_hdr *)nmsg->data;
+ nmmh->cause = 17;
+ gsm48_mmxx_upmsg(ms, nmsg);
+
+ return 0;
+}
+
+/* a RR-SYNC-IND is received during MM connection establishment */
+static int gsm48_mm_sync_ind_wait(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ /* stop MM connection timer */
+ stop_mm_t3230(mm);
+
+ return gsm48_mm_conn_go_dedic(ms);
+}
+
+/* a RR-SYNC-IND is received during MM connection active */
+static int gsm48_mm_sync_ind_active(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_mm_conn *conn;
+ struct msgb *nmsg;
+ struct gsm48_mmxx_hdr *nmmh;
+
+ /* stop MM connection timer */
+ stop_mm_t3230(mm);
+
+ /* broadcast all MMCC connection(s) */
+ llist_for_each_entry(conn, &mm->mm_conn, list) {
+ /* send MMCC-SYNC-IND */
+ nmsg = NULL;
+ switch(conn->protocol) {
+ case GSM48_PDISC_CC:
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_SYNC_IND,
+ conn->ref, conn->transaction_id);
+ break;
+ }
+ if (!nmsg)
+ continue; /* skip if not of CC type */
+ nmmh = (struct gsm48_mmxx_hdr *)nmsg->data;
+ nmmh->cause = 17;
+ /* copy L3 message */
+ nmsg->l3h = msgb_put(nmsg, msgb_l3len(msg));
+ memcpy(nmsg->l3h, msg->l3h, msgb_l3len(msg));
+ gsm48_mmxx_upmsg(ms, nmsg);
+ }
+
+ return 0;
+}
+
+/* 4.5.1.2 RR abort/release is received during MM connection establishment */
+static int gsm48_mm_abort_mm_con(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *)msg->data;
+ int cause;
+
+ /* stop RR release timer */
+ stop_mm_t3240(mm);
+
+ /* this conversion is not of any standard */
+ switch(rrh->cause) {
+ case RR_REL_CAUSE_NOT_AUTHORIZED:
+ case RR_REL_CAUSE_EMERGENCY_ONLY:
+ case RR_REL_CAUSE_TRY_LATER:
+ cause = 21;
+ break;
+ case RR_REL_CAUSE_NORMAL:
+ cause = 16;
+ break;
+ default:
+ cause = 47;
+ }
+
+ /* stop MM connection timer */
+ stop_mm_t3230(mm);
+
+ /* release all connections */
+ gsm48_mm_release_mm_conn(ms, 1, cause, 1);
+
+ /* no RR connection, so we return to MM IDLE */
+ if (mm->state == GSM48_MM_ST_WAIT_RR_CONN_MM_CON)
+ return gsm48_mm_return_idle(ms, NULL);
+
+ /* CS process will trigger: return to MM IDLE */
+ return 0;
+}
+
+/* 4.5.1.2 timeout is received during MM connection establishment */
+static int gsm48_mm_timeout_mm_con(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ /* release pending connection */
+ gsm48_mm_release_mm_conn(ms, 0, 102, 0);
+
+ /* state depends on the existance of remaining MM connections */
+ if (llist_empty(&mm->mm_conn)) {
+ /* start RR release timer */
+ start_mm_t3240(mm);
+
+ new_mm_state(mm, GSM48_MM_ST_WAIT_NETWORK_CMD, 0);
+ } else
+ new_mm_state(mm, GSM48_MM_ST_MM_CONN_ACTIVE, 0);
+
+ return 0;
+}
+
+/* respond to paging */
+static int gsm48_mm_est(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ new_mm_state(mm, GSM48_MM_ST_WAIT_NETWORK_CMD, 0);
+
+ return 0;
+}
+
+/* send CM data */
+static int gsm48_mm_data(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data;
+ struct gsm48_mm_conn *conn;
+ int msg_type = mmh->msg_type;
+
+ /* get connection, if not exist (anymore), release */
+ conn = mm_conn_by_ref(mm, mmh->ref);
+ if (!conn) {
+ LOGP(DMM, LOGL_INFO, "MMXX_DATA_REQ with unknown (already "
+ "released) ref=%x, sending MMXX_REL_IND\n", mmh->ref);
+ switch(msg_type & GSM48_MMXX_MASK) {
+ case GSM48_MMCC_CLASS:
+ mmh->msg_type = GSM48_MMCC_REL_IND;
+ break;
+ case GSM48_MMSS_CLASS:
+ mmh->msg_type = GSM48_MMSS_REL_IND;
+ break;
+ case GSM48_MMSMS_CLASS:
+ mmh->msg_type = GSM48_MMSMS_REL_IND;
+ break;
+ }
+ mmh->cause = 31;
+
+ /* mirror message with REL_IND + cause */
+ return gsm48_mmxx_upmsg(ms, msg);
+ }
+
+ /* pull MM header */
+ msgb_pull(msg, sizeof(struct gsm48_mmxx_hdr));
+
+ /* push RR header and send down */
+ return gsm48_mm_to_rr(ms, msg, GSM48_RR_DATA_REQ, 0);
+}
+
+/* release of MM connection (active state) */
+static int gsm48_mm_release_active(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data;
+ struct gsm48_mm_conn *conn;
+
+ /* get connection, if not exist (anymore), release */
+ conn = mm_conn_by_ref(mm, mmh->ref);
+ if (conn)
+ mm_conn_free(conn);
+
+ /* state depends on the existance of remaining MM connections */
+ if (llist_empty(&mm->mm_conn)) {
+ /* start RR release timer */
+ start_mm_t3240(mm);
+
+ new_mm_state(mm, GSM48_MM_ST_WAIT_NETWORK_CMD, 0);
+ } else
+ new_mm_state(mm, GSM48_MM_ST_MM_CONN_ACTIVE, 0);
+
+ return 0;
+}
+
+/* release of MM connection (wait for additional state) */
+static int gsm48_mm_release_wait_add(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data;
+ struct gsm48_mm_conn *conn;
+
+ /* get connection, if not exist (anymore), release */
+ conn = mm_conn_by_ref(mm, mmh->ref);
+ if (conn)
+ mm_conn_free(conn);
+
+ return 0;
+}
+
+/* release of MM connection (wait for active state) */
+static int gsm48_mm_release_wait_active(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data;
+ struct gsm48_mm_conn *conn;
+
+ /* get connection, if not exist (anymore), release */
+ conn = mm_conn_by_ref(mm, mmh->ref);
+ if (conn)
+ mm_conn_free(conn);
+
+ /* 4.5.1.7 if there is no MM connection during wait for active state */
+ if (llist_empty(&mm->mm_conn)) {
+ LOGP(DMM, LOGL_INFO, "No MM Connection during 'wait for "
+ "active' state.\n");
+
+ /* start RR release timer */
+ start_mm_t3240(mm);
+
+ new_mm_state(mm, GSM48_MM_ST_WAIT_NETWORK_CMD, 0);
+
+ /* send abort */
+ return gsm48_mm_tx_cm_service_abort(ms);
+ }
+
+ return 0;
+}
+
+/* release of MM connection (wait for RR state) */
+static int gsm48_mm_release_wait_rr(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data;
+ struct gsm48_mm_conn *conn;
+
+ /* get connection, if not exist (anymore), release */
+ conn = mm_conn_by_ref(mm, mmh->ref);
+ if (conn)
+ mm_conn_free(conn);
+
+ /* later, if RR connection is established, the CM SERIVE ABORT
+ * message will be sent
+ */
+ return 0;
+}
+
+/* abort RR connection (due to T3240) */
+static int gsm48_mm_abort_rr(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct msgb *nmsg;
+ struct gsm48_rr_hdr *nrrh;
+
+ /* send abort to RR */
+ nmsg = gsm48_rr_msgb_alloc(GSM48_RR_ABORT_REQ);
+ if (!nmsg)
+ return -ENOMEM;
+ nrrh = (struct gsm48_rr_hdr *) msgb_put(nmsg, sizeof(*nrrh));
+ nrrh->cause = GSM48_RR_CAUSE_ABNORMAL_TIMER;
+ gsm48_rr_downmsg(ms, nmsg);
+
+ /* CS process will trigger: return to MM IDLE / No SIM */
+ return 0;
+}
+
+/*
+ * other processes
+ */
+
+/* RR is released in other states */
+static int gsm48_mm_rel_other(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ /* stop RR release timer (if running) */
+ stop_mm_t3240(mm);
+
+ /* CS process will trigger: return to MM IDLE */
+ return 0;
+}
+
+/*
+ * state machines
+ */
+
+/* state trasitions for MMxx-SAP messages from upper layers */
+static struct downstate {
+ uint32_t states;
+ uint32_t substates;
+ int type;
+ int (*rout) (struct osmocom_ms *ms, struct msgb *msg);
+} downstatelist[] = {
+ /* 4.2.2.1 Normal service */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE),
+ GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr},
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE),
+ GSM48_MMSS_EST_REQ, gsm48_mm_init_mm_no_rr},
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE),
+ GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_no_rr},
+
+ /* 4.2.2.2 Attempt to update / Loc. Upd. needed */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE) |
+ SBIT(GSM48_MM_SST_LOC_UPD_NEEDED),
+ GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr}, /* emergency only */
+
+ /* 4.2.2.3 Limited service */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE),
+ GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr},
+
+ /* 4.2.2.4 No IMSI */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NO_IMSI),
+ GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr},
+
+ /* 4.2.2.5 PLMN search, normal service */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL),
+ GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr},
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL),
+ GSM48_MMSS_EST_REQ, gsm48_mm_init_mm_no_rr},
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL),
+ GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_no_rr},
+
+ /* 4.2.2.6 PLMN search */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH),
+ GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr},
+
+ /* 4.5.1.1 MM Connection (EST) */
+ {SBIT(GSM48_MM_ST_RR_CONN_RELEASE_NA), ALL_STATES,
+ GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_first},
+
+ {SBIT(GSM48_MM_ST_RR_CONN_RELEASE_NA), ALL_STATES,
+ GSM48_MMSS_EST_REQ, gsm48_mm_init_mm_first},
+
+ {SBIT(GSM48_MM_ST_RR_CONN_RELEASE_NA), ALL_STATES,
+ GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_first},
+
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES,
+ GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_more},
+
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES,
+ GSM48_MMSS_EST_REQ, gsm48_mm_init_mm_more},
+
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES,
+ GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_more},
+
+ {SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES,
+ GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_wait},
+
+ {SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES,
+ GSM48_MMSS_EST_REQ, gsm48_mm_init_mm_wait},
+
+ {SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES,
+ GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_wait},
+
+ {ALL_STATES, ALL_STATES,
+ GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_reject},
+
+ {ALL_STATES, ALL_STATES,
+ GSM48_MMSS_EST_REQ, gsm48_mm_init_mm_reject},
+
+ {ALL_STATES, ALL_STATES,
+ GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_reject},
+
+ /* 4.5.2.1 MM Connection (DATA) */
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) |
+ SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES,
+ GSM48_MMCC_DATA_REQ, gsm48_mm_data},
+
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) |
+ SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES,
+ GSM48_MMSS_DATA_REQ, gsm48_mm_data},
+
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) |
+ SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES,
+ GSM48_MMSMS_DATA_REQ, gsm48_mm_data},
+
+ /* 4.5.2.1 MM Connection (REL) */
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES,
+ GSM48_MMCC_REL_REQ, gsm48_mm_release_active},
+
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES,
+ GSM48_MMSS_REL_REQ, gsm48_mm_release_active},
+
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES,
+ GSM48_MMSMS_REL_REQ, gsm48_mm_release_active},
+
+ {SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES,
+ GSM48_MMCC_REL_REQ, gsm48_mm_release_wait_add},
+
+ {SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES,
+ GSM48_MMSS_REL_REQ, gsm48_mm_release_wait_add},
+
+ {SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES,
+ GSM48_MMSMS_REL_REQ, gsm48_mm_release_wait_add},
+
+ {SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN), ALL_STATES,
+ GSM48_MMCC_REL_REQ, gsm48_mm_release_wait_active},
+
+ {SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN), ALL_STATES,
+ GSM48_MMSS_REL_REQ, gsm48_mm_release_wait_active},
+
+ {SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN), ALL_STATES,
+ GSM48_MMSMS_REL_REQ, gsm48_mm_release_wait_active},
+
+ {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON), ALL_STATES,
+ GSM48_MMCC_REL_REQ, gsm48_mm_release_wait_rr},
+
+ {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON), ALL_STATES,
+ GSM48_MMSS_REL_REQ, gsm48_mm_release_wait_rr},
+
+ {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON), ALL_STATES,
+ GSM48_MMSMS_REL_REQ, gsm48_mm_release_wait_rr},
+};
+
+#define DOWNSLLEN \
+ (sizeof(downstatelist) / sizeof(struct downstate))
+
+int gsm48_mmxx_downmsg(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data;
+ int msg_type = mmh->msg_type;
+ struct gsm48_mm_conn *conn;
+ int i, rc;
+
+ /* keep up to date with the transaction ID */
+ conn = mm_conn_by_ref(mm, mmh->ref);
+ if (conn)
+ conn->transaction_id = mmh->transaction_id;
+
+ LOGP(DMM, LOGL_INFO, "(ms %s) Received '%s' event in state %s\n",
+ ms->name, get_mmxx_name(msg_type),
+ gsm48_mm_state_names[mm->state]);
+ if (mm->state == GSM48_MM_ST_MM_IDLE)
+ LOGP(DMM, LOGL_INFO, "-> substate %s\n",
+ gsm48_mm_substate_names[mm->substate]);
+ LOGP(DMM, LOGL_INFO, "-> callref %x, transaction_id %d\n",
+ mmh->ref, mmh->transaction_id);
+
+ /* Find function for current state and message */
+ for (i = 0; i < DOWNSLLEN; i++)
+ if ((msg_type == downstatelist[i].type)
+ && ((1 << mm->state) & downstatelist[i].states)
+ && ((1 << mm->substate) & downstatelist[i].substates))
+ break;
+ if (i == DOWNSLLEN) {
+ LOGP(DMM, LOGL_NOTICE, "Message unhandled at this state.\n");
+ msgb_free(msg);
+ return 0;
+ }
+
+ rc = downstatelist[i].rout(ms, msg);
+
+ if (downstatelist[i].rout != gsm48_mm_data)
+ msgb_free(msg);
+
+ return rc;
+}
+
+/* state trasitions for radio ressource messages (lower layer) */
+static struct rrdatastate {
+ uint32_t states;
+ int type;
+ int (*rout) (struct osmocom_ms *ms, struct msgb *msg);
+} rrdatastatelist[] = {
+ /* paging */
+ {SBIT(GSM48_MM_ST_MM_IDLE),
+ GSM48_RR_EST_IND, gsm48_mm_est},
+
+ /* imsi detach */
+ {SBIT(GSM48_MM_ST_WAIT_RR_CONN_IMSI_D), /* 4.3.4.4 */
+ GSM48_RR_EST_CNF, gsm48_mm_imsi_detach_sent},
+
+ {SBIT(GSM48_MM_ST_WAIT_RR_CONN_IMSI_D), /* 4.3.4.4 (unsuc.) */
+ GSM48_RR_REL_IND, gsm48_mm_imsi_detach_end},
+ /* also this may happen if SABM is ackwnowledged with DISC */
+
+ {SBIT(GSM48_MM_ST_WAIT_RR_CONN_IMSI_D), /* 4.3.4.4 (lost) */
+ GSM48_RR_ABORT_IND, gsm48_mm_imsi_detach_end},
+
+ {SBIT(GSM48_MM_ST_IMSI_DETACH_INIT), /* 4.3.4.4 (unsuc.) */
+ GSM48_RR_REL_IND, gsm48_mm_imsi_detach_end},
+
+ {SBIT(GSM48_MM_ST_IMSI_DETACH_INIT), /* 4.3.4.4 (lost) */
+ GSM48_RR_ABORT_IND, gsm48_mm_imsi_detach_end},
+
+ /* location update */
+ {SBIT(GSM48_MM_ST_WAIT_RR_CONN_LUPD), /* 4.4.4.1 */
+ GSM48_RR_EST_CNF, gsm48_mm_est_loc_upd},
+
+ {SBIT(GSM48_MM_ST_LOC_UPD_INIT) |
+ SBIT(GSM48_MM_ST_WAIT_RR_CONN_LUPD), /* 4.4.4.9 */
+ GSM48_RR_REL_IND, gsm48_mm_rel_loc_upd_abort},
+
+ {SBIT(GSM48_MM_ST_LOC_UPD_INIT) |
+ SBIT(GSM48_MM_ST_WAIT_RR_CONN_LUPD), /* 4.4.4.9 */
+ GSM48_RR_ABORT_IND, gsm48_mm_rel_loc_upd_abort},
+
+ {SBIT(GSM48_MM_ST_LOC_UPD_REJ), /* 4.4.4.7 */
+ GSM48_RR_REL_IND, gsm48_mm_rel_loc_upd_rej},
+
+ {SBIT(GSM48_MM_ST_LOC_UPD_REJ), /* 4.4.4.7 */
+ GSM48_RR_ABORT_IND, gsm48_mm_rel_loc_upd_rej},
+
+ /* MM connection (EST) */
+ {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON), /* 4.5.1.1 */
+ GSM48_RR_EST_CNF, gsm48_mm_est_mm_con},
+
+ /* MM connection (DATA) */
+ {ALL_STATES,
+ GSM48_RR_DATA_IND, gsm48_mm_data_ind},
+
+ /* MM connection (SYNC) */
+ {SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN) |
+ SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), /* 4.5.1.1 */
+ GSM48_RR_SYNC_IND, gsm48_mm_sync_ind_wait},
+
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE),
+ GSM48_RR_SYNC_IND, gsm48_mm_sync_ind_active},
+
+ /* MM connection (REL/ABORT) */
+ {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON) |
+ SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN) |
+ SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), /* 4.5.1.2 */
+ GSM48_RR_REL_IND, gsm48_mm_abort_mm_con},
+
+ {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON) |
+ SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN) |
+ SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), /* 4.5.1.2 */
+ GSM48_RR_ABORT_IND, gsm48_mm_abort_mm_con},
+
+ /* MM connection (REL/ABORT with re-establishment possibility) */
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), /* not supported */
+ GSM48_RR_REL_IND, gsm48_mm_abort_mm_con},
+
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) |
+ SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), /* not supported */
+ GSM48_RR_ABORT_IND, gsm48_mm_abort_mm_con},
+
+ /* other (also wait for network command) */
+ {ALL_STATES,
+ GSM48_RR_REL_IND, gsm48_mm_rel_other},
+
+ {ALL_STATES,
+ GSM48_RR_ABORT_IND, gsm48_mm_rel_other},
+};
+
+#define RRDATASLLEN \
+ (sizeof(rrdatastatelist) / sizeof(struct rrdatastate))
+
+static int gsm48_rcv_rr(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *)msg->data;
+ int msg_type = rrh->msg_type;
+ int i, rc;
+
+ LOGP(DMM, LOGL_INFO, "(ms %s) Received '%s' from RR in state %s\n",
+ ms->name, get_rr_name(msg_type),
+ gsm48_mm_state_names[mm->state]);
+
+ /* find function for current state and message */
+ for (i = 0; i < RRDATASLLEN; i++)
+ if ((msg_type == rrdatastatelist[i].type)
+ && ((1 << mm->state) & rrdatastatelist[i].states))
+ break;
+ if (i == RRDATASLLEN) {
+ LOGP(DMM, LOGL_NOTICE, "Message unhandled at this state.\n");
+ msgb_free(msg);
+ return 0;
+ }
+
+ rc = rrdatastatelist[i].rout(ms, msg);
+
+ if (rrdatastatelist[i].rout != gsm48_mm_data_ind)
+ msgb_free(msg);
+
+ return rc;
+}
+
+/* state trasitions for mobile managemnt messages (lower layer) */
+static struct mmdatastate {
+ uint32_t states;
+ int type;
+ int (*rout) (struct osmocom_ms *ms, struct msgb *msg);
+} mmdatastatelist[] = {
+ {ALL_STATES, /* 4.3.1.2 */
+ GSM48_MT_MM_TMSI_REALL_CMD, gsm48_mm_rx_tmsi_realloc_cmd},
+
+ {ALL_STATES, /* 4.3.2.2 */
+ GSM48_MT_MM_AUTH_REQ, gsm48_mm_rx_auth_req},
+
+ {ALL_STATES, /* 4.3.2.5 */
+ GSM48_MT_MM_AUTH_REJ, gsm48_mm_rx_auth_rej},
+
+ {ALL_STATES, /* 4.3.3.2 */
+ GSM48_MT_MM_ID_REQ, gsm48_mm_rx_id_req},
+
+ {ALL_STATES, /* 4.3.5.2 */
+ GSM48_MT_MM_ABORT, gsm48_mm_rx_abort},
+
+ {ALL_STATES, /* 4.3.6.2 */
+ GSM48_MT_MM_INFO, gsm48_mm_rx_info},
+
+ {SBIT(GSM48_MM_ST_LOC_UPD_INIT), /* 4.4.4.6 */
+ GSM48_MT_MM_LOC_UPD_ACCEPT, gsm48_mm_rx_loc_upd_acc},
+
+ {SBIT(GSM48_MM_ST_LOC_UPD_INIT), /* 4.4.4.7 */
+ GSM48_MT_MM_LOC_UPD_REJECT, gsm48_mm_rx_loc_upd_rej},
+
+ {ALL_STATES, /* 4.5.1.1 */
+ GSM48_MT_MM_CM_SERV_ACC, gsm48_mm_rx_cm_service_acc},
+
+ {ALL_STATES, /* 4.5.1.1 */
+ GSM48_MT_MM_CM_SERV_REJ, gsm48_mm_rx_cm_service_rej},
+};
+
+#define MMDATASLLEN \
+ (sizeof(mmdatastatelist) / sizeof(struct mmdatastate))
+
+static int gsm48_mm_data_ind(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ uint8_t pdisc = gh->proto_discr & 0x0f;
+ uint8_t msg_type = gh->msg_type & 0xbf;
+ struct gsm48_mmxx_hdr *mmh;
+ int msg_supported = 0; /* determine, if message is supported at all */
+ int rr_prim = -1, rr_est = -1; /* no prim set */
+ uint8_t skip_ind;
+ int i, rc;
+
+ /* 9.2.19 */
+ if (msg_type == GSM48_MT_MM_NULL)
+ return 0;
+
+ if (mm->state == GSM48_MM_ST_IMSI_DETACH_INIT) {
+ LOGP(DMM, LOGL_NOTICE, "DATA IND ignored during IMSI "
+ "detach.\n");
+ return 0;
+ }
+ /* pull the RR header */
+ msgb_pull(msg, sizeof(struct gsm48_rr_hdr));
+
+ /* create transaction (if not exists) and push message */
+ switch (pdisc) {
+ case GSM48_PDISC_CC:
+ rr_prim = GSM48_MMCC_DATA_IND;
+ rr_est = GSM48_MMCC_EST_IND;
+ break;
+#if 0
+ case GSM48_PDISC_NC_SS:
+ rr_prim = GSM48_MMSS_DATA_IND;
+ rr_est = GSM48_MMSS_EST_IND;
+ break;
+ case GSM48_PDISC_SMS:
+ rr_prim = GSM48_MMSMS_DATA_IND;
+ rr_est = GSM48_MMSMS_EST_IND;
+ break;
+#endif
+ }
+ if (rr_prim != -1) {
+ uint8_t transaction_id = ((gh->proto_discr & 0xf0) ^ 0x80) >> 4;
+ /* flip */
+ struct gsm48_mm_conn *conn;
+
+ /* find transaction, if any */
+ conn = mm_conn_by_id(mm, pdisc, transaction_id);
+
+ /* create MM connection instance */
+ if (!conn) {
+ conn = mm_conn_new(mm, pdisc, transaction_id,
+ mm_conn_new_ref++);
+ rr_prim = rr_est;
+ }
+ if (!conn)
+ return -ENOMEM;
+
+ /* push new header */
+ msgb_push(msg, sizeof(struct gsm48_mmxx_hdr));
+ mmh = (struct gsm48_mmxx_hdr *)msg->data;
+ mmh->msg_type = rr_prim;
+ mmh->ref = conn->ref;
+
+ /* go MM CONN ACTIVE state */
+ if (mm->state == GSM48_MM_ST_WAIT_NETWORK_CMD
+ || mm->state == GSM48_MM_ST_RR_CONN_RELEASE_NA) {
+ /* stop RR release timer */
+ stop_mm_t3240(mm);
+
+ /* stop "RR connection release not allowed" timer */
+ stop_mm_t3241(mm);
+
+ new_mm_state(mm, GSM48_MM_ST_MM_CONN_ACTIVE, 0);
+ }
+ }
+
+ /* forward message */
+ switch (pdisc) {
+ case GSM48_PDISC_MM:
+ skip_ind = (gh->proto_discr & 0xf0) >> 4;
+
+ /* ignore if skip indicator is not B'0000' */
+ if (skip_ind)
+ return 0;
+ break; /* follow the selection proceedure below */
+
+ case GSM48_PDISC_CC:
+ return gsm48_rcv_cc(ms, msg);
+
+#if 0
+ case GSM48_PDISC_NC_SS:
+ return gsm48_rcv_ss(ms, msg);
+
+ case GSM48_PDISC_SMS:
+ return gsm48_rcv_sms(ms, msg);
+#endif
+
+ default:
+ LOGP(DMM, LOGL_NOTICE, "Protocol type 0x%02x unsupported.\n",
+ pdisc);
+ msgb_free(msg);
+ return gsm48_mm_tx_mm_status(ms,
+ GSM48_REJECT_MSG_TYPE_NOT_IMPLEMENTED);
+ }
+
+ LOGP(DMM, LOGL_INFO, "(ms %s) Received '%s' in MM state %s\n", ms->name,
+ get_mm_name(msg_type), gsm48_mm_state_names[mm->state]);
+
+ stop_mm_t3212(mm); /* 4.4.2 */
+
+ /* 11.2 re-start pending RR release timer */
+ if (bsc_timer_pending(&mm->t3240)) {
+ stop_mm_t3240(mm);
+ start_mm_t3240(mm);
+ }
+
+ /* find function for current state and message */
+ for (i = 0; i < MMDATASLLEN; i++) {
+ if (msg_type == mmdatastatelist[i].type)
+ msg_supported = 1;
+ if ((msg_type == mmdatastatelist[i].type)
+ && ((1 << mm->state) & mmdatastatelist[i].states))
+ break;
+ }
+ if (i == MMDATASLLEN) {
+ msgb_free(msg);
+ if (msg_supported) {
+ LOGP(DMM, LOGL_NOTICE, "Message unhandled at this "
+ "state.\n");
+ return gsm48_mm_tx_mm_status(ms,
+ GSM48_REJECT_MSG_TYPE_NOT_COMPATIBLE);
+ } else {
+ LOGP(DMM, LOGL_NOTICE, "Message not supported.\n");
+ return gsm48_mm_tx_mm_status(ms,
+ GSM48_REJECT_MSG_TYPE_NOT_IMPLEMENTED);
+ }
+ }
+
+ rc = mmdatastatelist[i].rout(ms, msg);
+
+ msgb_free(msg);
+
+ return rc;
+}
+
+/* state trasitions for mobile management events */
+static struct eventstate {
+ uint32_t states;
+ uint32_t substates;
+ int type;
+ int (*rout) (struct osmocom_ms *ms, struct msgb *msg);
+} eventstatelist[] = {
+ /* 4.2.3 return to MM IDLE */
+ {ALL_STATES - SBIT(GSM48_MM_ST_MM_IDLE), ALL_STATES,
+ GSM48_MM_EVENT_NO_CELL_FOUND, gsm48_mm_no_cell_found},
+
+ {ALL_STATES - SBIT(GSM48_MM_ST_MM_IDLE), ALL_STATES,
+ GSM48_MM_EVENT_CELL_SELECTED, gsm48_mm_return_idle},
+
+ /* 4.2.2.1 Normal service */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE),
+ GSM48_MM_EVENT_NO_CELL_FOUND, gsm48_mm_plmn_search}, /* 4.2.1.2 */
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE),
+ GSM48_MM_EVENT_CELL_SELECTED, gsm48_mm_loc_upd_normal}, /* change */
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE),
+ GSM48_MM_EVENT_TIMEOUT_T3211, gsm48_mm_loc_upd},
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE),
+ GSM48_MM_EVENT_TIMEOUT_T3213, gsm48_mm_loc_upd},
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE),
+ GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_periodic},
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE),
+ GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_start},
+
+ /* 4.2.2.2 Attempt to update / Loc. upd. needed */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE) |
+ SBIT(GSM48_MM_SST_LOC_UPD_NEEDED),
+ GSM48_MM_EVENT_USER_PLMN_SEL, gsm48_mm_plmn_search}, /* 4.2.1.2 */
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE) |
+ SBIT(GSM48_MM_SST_LOC_UPD_NEEDED),
+ GSM48_MM_EVENT_NO_CELL_FOUND, gsm48_mm_plmn_search}, /* 4.2.1.2 */
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE) |
+ SBIT(GSM48_MM_SST_LOC_UPD_NEEDED),
+ GSM48_MM_EVENT_CELL_SELECTED, gsm48_mm_loc_upd_normal}, /* change */
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE),
+ GSM48_MM_EVENT_TIMEOUT_T3211, gsm48_mm_loc_upd},
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE),
+ GSM48_MM_EVENT_TIMEOUT_T3213, gsm48_mm_loc_upd},
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE),
+ GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_periodic},
+
+ /* 4.2.2.3 Limited service */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE),
+ GSM48_MM_EVENT_USER_PLMN_SEL, gsm48_mm_plmn_search}, /* 4.2.1.2 */
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE),
+ GSM48_MM_EVENT_NO_CELL_FOUND, gsm48_mm_plmn_search}, /* 4.2.1.2 */
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE),
+ GSM48_MM_EVENT_CELL_SELECTED, gsm48_mm_loc_upd_normal}, /* if allow. */
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE),
+ GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_delay_per}, /* 4.4.2 */
+
+ /* 4.2.2.4 No IMSI */
+ /* 4.2.2.5 PLMN search, normal service */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL),
+ GSM48_MM_EVENT_NO_CELL_FOUND, gsm48_mm_no_cell_found}, /* 4.2.1.1 */
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL),
+ GSM48_MM_EVENT_CELL_SELECTED, gsm48_mm_cell_selected}, /* 4.2.1.1 */
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL),
+ GSM48_MM_EVENT_TIMEOUT_T3211, gsm48_mm_loc_upd_delay_retry},
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL),
+ GSM48_MM_EVENT_TIMEOUT_T3213, gsm48_mm_loc_upd_delay_retry},
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL),
+ GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_delay_per}, /* 4.4.2 */
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL),
+ GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_start},
+
+ /* 4.2.2.6 PLMN search */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH),
+ GSM48_MM_EVENT_NO_CELL_FOUND, gsm48_mm_no_cell_found}, /* 4.2.1.1 */
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH),
+ GSM48_MM_EVENT_CELL_SELECTED, gsm48_mm_cell_selected}, /* 4.2.1.1 */
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH),
+ GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_delay_per}, /* 4.4.2 */
+
+ /* No cell available */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NO_CELL_AVAIL),
+ GSM48_MM_EVENT_CELL_SELECTED, gsm48_mm_cell_selected}, /* 4.2.1.1 */
+
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NO_CELL_AVAIL),
+ GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_delay_per}, /* 4.4.2 */
+
+ /* IMSI detach in other cases */
+ {SBIT(GSM48_MM_ST_MM_IDLE), ALL_STATES, /* silently detach */
+ GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_end},
+
+ {SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN) |
+ SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) |
+ SBIT(GSM48_MM_ST_PROCESS_CM_SERV_P) |
+ SBIT(GSM48_MM_ST_WAIT_REEST) |
+ SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON) |
+ SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS) |
+ SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES, /* we can release */
+ GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_release},
+
+ {SBIT(GSM48_MM_ST_WAIT_RR_CONN_IMSI_D) |
+ SBIT(GSM48_MM_ST_IMSI_DETACH_INIT) |
+ SBIT(GSM48_MM_ST_IMSI_DETACH_PEND), ALL_STATES, /* ignore */
+ GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_ignore},
+
+ {ALL_STATES, ALL_STATES,
+ GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_delay},
+
+ {GSM48_MM_ST_IMSI_DETACH_INIT, ALL_STATES,
+ GSM48_MM_EVENT_TIMEOUT_T3220, gsm48_mm_imsi_detach_end},
+
+ /* location update in other cases */
+ {ALL_STATES, ALL_STATES,
+ GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_ignore},
+
+ {ALL_STATES - SBIT(GSM48_MM_ST_MM_IDLE), ALL_STATES,
+ GSM48_MM_EVENT_TIMEOUT_T3210, gsm48_mm_loc_upd_timeout},
+
+ {ALL_STATES - SBIT(GSM48_MM_ST_MM_IDLE), ALL_STATES,
+ GSM48_MM_EVENT_TIMEOUT_T3213, gsm48_mm_loc_upd_failed},
+ /* 4.4.4.9 c) (but without retry) */
+
+ /* SYSINFO event */
+ {ALL_STATES, ALL_STATES,
+ GSM48_MM_EVENT_SYSINFO, gsm48_mm_sysinfo},
+
+ /* T3240 timed out */
+ {SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD) |
+ SBIT(GSM48_MM_ST_LOC_UPD_REJ), ALL_STATES, /* 4.4.4.8 */
+ GSM48_MM_EVENT_TIMEOUT_T3240, gsm48_mm_abort_rr},
+
+ /* T3230 timed out */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE),
+ GSM48_MM_EVENT_TIMEOUT_T3230, gsm48_mm_timeout_mm_con},
+
+ /* SIM reports SRES */
+ {ALL_STATES, ALL_STATES, /* 4.3.2.2 */
+ GSM48_MM_EVENT_AUTH_RESPONSE, gsm48_mm_tx_auth_rsp},
+
+#if 0
+ /* change in classmark is reported */
+ {ALL_STATES, ALL_STATES,
+ GSM48_MM_EVENT_CLASSMARK_CHG, gsm48_mm_classm_chg},
+#endif
+};
+
+#define EVENTSLLEN \
+ (sizeof(eventstatelist) / sizeof(struct eventstate))
+
+static int gsm48_mm_ev(struct osmocom_ms *ms, int msg_type, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ int i, rc;
+
+ if (mm->state == GSM48_MM_ST_MM_IDLE)
+ LOGP(DMM, LOGL_INFO, "(ms %s) Received '%s' event in state "
+ "MM IDLE, %s\n", ms->name, get_mmevent_name(msg_type),
+ gsm48_mm_substate_names[mm->substate]);
+ else
+ LOGP(DMM, LOGL_INFO, "(ms %s) Received '%s' event in state "
+ "%s\n", ms->name, get_mmevent_name(msg_type),
+ gsm48_mm_state_names[mm->state]);
+
+ /* Find function for current state and message */
+ for (i = 0; i < EVENTSLLEN; i++)
+ if ((msg_type == eventstatelist[i].type)
+ && ((1 << mm->state) & eventstatelist[i].states)
+ && ((1 << mm->substate) & eventstatelist[i].substates))
+ break;
+ if (i == EVENTSLLEN) {
+ LOGP(DMM, LOGL_NOTICE, "Message unhandled at this state.\n");
+ return 0;
+ }
+
+ rc = eventstatelist[i].rout(ms, msg);
+
+ return rc;
+}
+
+/*
+ * MM Register (SIM insert and remove)
+ */
+
+/* register new SIM card and trigger attach */
+static int gsm48_mmr_reg_req(struct osmocom_ms *ms)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct msgb *nmsg;
+
+ /* schedule insertion of SIM */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_SIM_INSERT);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+
+ /* 4.2.1.2 SIM is inserted in state NO IMSI */
+ if (mm->state == GSM48_MM_ST_MM_IDLE
+ && mm->substate == GSM48_MM_SST_NO_IMSI)
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
+ gsm48_mm_set_plmn_search(ms));
+
+ return 0;
+}
+
+/* trigger detach of sim card */
+static int gsm48_mmr_nreg_req(struct osmocom_ms *ms)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct msgb *nmsg;
+
+ nmsg = gsm322_msgb_alloc(GSM48_MM_EVENT_IMSI_DETACH);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm48_mmevent_msg(mm->ms, nmsg);
+
+ return 0;
+}
+
+static int gsm48_rcv_mmr(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmr *mmr = (struct gsm48_mmr *)msg->data;
+ int msg_type = mmr->msg_type;
+ int rc = 0;
+
+ LOGP(DMM, LOGL_INFO, "(ms %s) Received '%s' event\n", ms->name,
+ get_mmr_name(msg_type));
+ switch(msg_type) {
+ case GSM48_MMR_REG_REQ:
+ rc = gsm48_mmr_reg_req(ms);
+ break;
+ case GSM48_MMR_NREG_REQ:
+ rc = gsm48_mmr_nreg_req(ms);
+ break;
+ default:
+ LOGP(DMM, LOGL_NOTICE, "Message unhandled.\n");
+ }
+
+ return rc;
+}
+
+