osmocom-bb/src/host/layer23/src/gsm48_mm.c

4088 lines
109 KiB
C
Raw Normal View History

/*
* (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>
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;
2010-06-05 19:46:10 +00:00
buf[2] = 0xf0;
break;
}
2010-06-05 19:46:10 +00:00
/* 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
*/
static const char *gsm48_mm_state_names[] = {
"NULL",
"undefined 1",
"undefined 2",
"LOC_UPD_INIT",
"undefined 4",
"WAIT_OUT_MM_CONN",
"MM_CONN_ACTIVE",
"IMSI_DETACH_INIT",
"PROCESS_CM_SERV_P",
"WAIT_NETWORK_CMD",
"LOC_UPD_REJ",
"undefined 11",
"undefined 12",
"WAIT_RR_CONN_LUPD",
"WAIT_RR_CONN_MM_CON",
"WAIT_RR_CONN_IMSI_D",
"undefined 16",
"WAIT_REEST",
"WAIT_RR_ACTIVE",
"MM_IDLE",
"WAIT_ADD_OUT_MM_CON",
"MM_CONN_ACTIVE_VGCS",
"WAIT_RR_CONN_VGCS",
"LOC_UPD_PEND",
"IMSI_DETACH_PEND",
"RR_CONN_RELEASE_NA"
};
static const char *gsm48_mm_substate_names[] = {
"NULL",
"NORMAL_SERVICE",
"ATTEMPT_UPDATE",
"LIMITED_SERVICE",
"NO_IMSI",
"NO_CELL_AVAIL",
"LOC_UPD_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 %03d MNC %02d LAC 0x%04x) "
"!= LAI in SIM (MCC %03d MNC %02d LAC 0x%04x).\n",
cs->sel_mcc, cs->sel_mnc, cs->sel_lac,
subscr->lai_mcc, 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);
llist_del(&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 = 1;
/* new MM connection state */
static void new_conn_state(struct gsm48_mm_conn *conn, int state)
{
LOGP(DMM, LOGL_INFO, "(ref %d) 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;
/* check if request can be fulfilled */
if (!subscr->sim_valid) {
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);
}
2010-06-05 19:46:10 +00:00
LOGP(DMM, LOGL_INFO, "IDENTITY REQUEST (mi_type %d)\n", mi_type);
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");
/* power off when IMSI is detached */
if (mm->power_off) {
l23_app_exit(ms);
printf("Power off!\n");
exit (0);
}
/* 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;
/* 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);
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),
2010-06-05 19:46:10 +00:00
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),
2010-06-05 19:46:10 +00:00
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 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;
}
/* 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 %03d, MNC %02d "
"LAC 0x%04x)\n", mm->lupd_mcc, 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 %03d mnc %02d "
"lac 0x%04x)\n", subscr->lai_mcc, 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;
2010-06-05 19:46:10 +00:00
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);
/* 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;
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 msgb *nmsg;
struct gsm48_hdr *ngh;
2010-06-12 16:14:28 +00:00
struct gsm48_service_request *nsr; /* NOTE: includes MI length */
uint8_t *cm2lv;
uint8_t buf[11];
LOGP(DMM, LOGL_INFO, "CM SERVICE REQUEST\n");
nmsg = gsm48_l3_msgb_alloc();
if (!nmsg)
return -ENOMEM;
ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh));
2010-06-12 16:14:28 +00:00
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 ? */
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 */
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;
}
/* current MM idle state
* (implies MM IDLE state, otherwise this function is not called)
*/
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;
/* Some day 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;
}
/* 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;
}
#warning HACK: always request SDCCH for test
cause = RR_EST_CAUSE_LOC_UPD;
/* 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;
}
2010-06-12 16:14:28 +00:00
/* 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;
/* this conversion is not of any standard */
switch(rrh->cause) {
case RR_REL_CAUSE_NOT_AUTHORIZED:
case RR_REL_CAUSE_EMERGENCY_ONLY:
cause = 21;
break;
case RR_REL_CAUSE_TRY_LATER:
cause = 34;
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) {
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)
{
/* 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;
2010-04-29 18:46:11 +00:00
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)
2010-04-29 18:46:11 +00:00
LOGP(DMM, LOGL_INFO, "-> substate %s\n",
gsm48_mm_substate_names[mm->substate]);
/* 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},
{SBIT(GSM48_MM_ST_WAIT_RR_CONN_IMSI_D), /* 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 */
{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;
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]);
/* 9.2.19 */
if (msg_type == GSM48_MT_MM_NULL)
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);
}
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;
}