summaryrefslogtreecommitdiffstats
path: root/src/host
diff options
context:
space:
mode:
authorAndreas Eversberg2010-04-25 18:28:24 +0200
committerAndreas Eversberg2010-04-25 18:28:24 +0200
commitf1278ad9da228145925fd613d28c082dbf2806d6 (patch)
tree7c8268c920f57e05911dfb8ed0e86442bbd8fabd /src/host
parentRemoved the code from gsm48-andreas. (diff)
downloadosmocom-f1278ad9da228145925fd613d28c082dbf2806d6.tar.gz
osmocom-f1278ad9da228145925fd613d28c082dbf2806d6.tar.xz
osmocom-f1278ad9da228145925fd613d28c082dbf2806d6.zip
Added new application "mobile" to layer23.
This is the current status of the layer 3 protocols. Everything compiles, radio ressource layer is partly complete. Everything is untested, so don't expect that it runs and does something usefull. The next step for me is running and debugging it. Here is the list of files that are added / modified: new file: ../include/osmocom/gsm322.h new file: ../include/osmocom/gsm48_cc.h new file: ../include/osmocom/gsm48_mm.h new file: ../include/osmocom/gsm48_rr.h modified: ../include/osmocom/l23_app.h modified: ../include/osmocom/logging.h new file: ../include/osmocom/mncc.h modified: ../include/osmocom/osmocom_data.h new file: ../include/osmocom/subscriber.h new file: ../include/osmocom/support.h new file: ../include/osmocom/sysinfo.h new file: ../include/osmocom/transaction.h modified: Makefile.am new file: app_mobile.c new file: gsm322.c new file: gsm48_cc.c new file: gsm48_mm.c new file: gsm48_rr.c modified: main.c new file: mnccms.c new file: subscriber.c new file: support.c new file: sysinfo.c new file: transaction.c
Diffstat (limited to 'src/host')
-rwxr-xr-xsrc/host/layer23/include/osmocom/gsm322.h175
-rw-r--r--src/host/layer23/include/osmocom/gsm48_cc.h18
-rw-r--r--src/host/layer23/include/osmocom/gsm48_mm.h218
-rw-r--r--src/host/layer23/include/osmocom/gsm48_rr.h146
-rw-r--r--src/host/layer23/include/osmocom/l23_app.h3
-rw-r--r--src/host/layer23/include/osmocom/logging.h2
-rw-r--r--src/host/layer23/include/osmocom/mncc.h154
-rw-r--r--src/host/layer23/include/osmocom/osmocom_data.h24
-rw-r--r--src/host/layer23/include/osmocom/subscriber.h73
-rw-r--r--src/host/layer23/include/osmocom/support.h80
-rw-r--r--src/host/layer23/include/osmocom/sysinfo.h104
-rw-r--r--src/host/layer23/include/osmocom/transaction.h71
-rw-r--r--src/host/layer23/src/Makefile.am9
-rw-r--r--src/host/layer23/src/app_mobile.c115
-rwxr-xr-xsrc/host/layer23/src/gsm322.c2648
-rw-r--r--src/host/layer23/src/gsm48_cc.c1975
-rw-r--r--src/host/layer23/src/gsm48_mm.c3400
-rw-r--r--src/host/layer23/src/gsm48_rr.c3594
-rw-r--r--src/host/layer23/src/main.c6
-rw-r--r--src/host/layer23/src/mnccms.c60
-rw-r--r--src/host/layer23/src/subscriber.c147
-rw-r--r--src/host/layer23/src/support.c106
-rw-r--r--src/host/layer23/src/sysinfo.c33
-rw-r--r--src/host/layer23/src/transaction.c138
24 files changed, 13290 insertions, 9 deletions
diff --git a/src/host/layer23/include/osmocom/gsm322.h b/src/host/layer23/include/osmocom/gsm322.h
new file mode 100755
index 0000000..7ab45a0
--- /dev/null
+++ b/src/host/layer23/include/osmocom/gsm322.h
@@ -0,0 +1,175 @@
+#ifndef _GSM322_H
+#define _GSM322_H
+
+/* 4.3.1.1 List of states for PLMN slection process (automatic mode) */
+#define GSM322_A0_NULL 0
+#define GSM322_A1_TRYING_RPLMN 1
+#define GSM322_A2_ON_PLMN 2
+#define GSM322_A3_TRYING_PLMN 3
+#define GSM322_A4_WAIT_FOR_PLMN 4
+#define GSM322_A5_HPLMN_SEARCH 5
+#define GSM322_A6_NO_SIM 6
+
+/* 4.3.1.2 List of states for PLMN slection process (manual mode) */
+#define GSM322_M0_NULL 0
+#define GSM322_M1_TRYING_RPLMN 1
+#define GSM322_M2_ON_PLMN 2
+#define GSM322_M3_NOT_ON_PLMN 3
+#define GSM322_M4_TRYING_PLMN 4
+#define GSM322_M5_NO_SIM 5
+
+/* 4.3.2 List of states for cell selection process */
+#define GSM322_C0_NULL 0
+#define GSM322_C1_NORMAL_CELL_SEL 1
+#define GSM322_C2_STORED_CELL_SEL 2
+#define GSM322_C3_CAMPED_NORMALLY 3
+#define GSM322_C4_NORMAL_CELL_RESEL 4
+#define GSM322_C5_CHOOSE_CELL 5
+#define GSM322_C6_ANY_CELL_SEL 6
+#define GSM322_C7_CAMPED_ANY_CELL 7
+#define GSM322_C8_ANY_CELL_RESEL 8
+#define GSM322_C9_CHOOSE_ANY_CELL 9
+#define GSM322_HPLMN_SEARCH 10
+
+/* GSM 03.22 events */
+#define GSM322_EVENT_SWITCH_ON 1
+#define GSM322_EVENT_SWITCH_OFF 2
+#define GSM322_EVENT_SIM_INSERT 3
+#define GSM322_EVENT_SIM_REMOVE 4
+#define GSM322_EVENT_REG_SUCCESS 5
+#define GSM322_EVENT_REG_FAILED 6
+#define GSM322_EVENT_ROAMING_NA 7
+#define GSM322_EVENT_INVALID_SIM 8
+#define GSM322_EVENT_NEW_PLMN 9
+#define GSM322_EVENT_ON_PLMN 10
+#define GSM322_EVENT_HPLMN_SEARCH 11
+#define GSM322_EVENT_HPLMN_FOUND 12
+#define GSM322_EVENT_HPLMN_NOT_FOUND 13
+#define GSM322_EVENT_USER_RESEL 14
+#define GSM322_EVENT_PLMN_AVAIL 15
+#define GSM322_EVENT_CHOSE_PLMN 16
+#define GSM322_EVENT_SEL_MANUAL 17
+#define GSM322_EVENT_SEL_AUTO 18
+#define GSM322_EVENT_CELL_FOUND 19
+#define GSM322_EVENT_NO_CELL_FOUND 20
+#define GSM322_EVENT_LEAVE_IDLE 21
+#define GSM322_EVENT_RET_IDLE 22
+#define GSM322_EVENT_CELL_RESEL 23
+#define GSM322_EVENT_SYSINFO 24
+
+enum {
+ PLMN_MODE_MANUAL,
+ PLMN_MODE_AUTO
+};
+
+/* node for each PLMN */
+struct gsm322_plmn_list {
+ struct llist_head entry;
+ uint16_t mcc, mnc;
+ int8_t rxlev_db; /* rx level in real dB */
+ uint8_t cause; /* cause value, if PLMN is not allowed */
+};
+
+/* node for each forbidden LA */
+struct gsm322_la_list {
+ struct llist_head entry;
+ uint16_t mcc, mnc, lac;
+ uint8_t cause;
+};
+
+/* node for each BA-List */
+struct gsm322_ba_list {
+ struct llist_head entry;
+ uint16_t mcc, mnc;
+ /* Band allocation for 1024 frequencies.
+ * First bit of first index is frequency 0.
+ */
+ uint8_t freq[128];
+};
+
+#define GSM322_CS_FLAG_SUPPORT 0x01 /* frequency is supported by radio */
+#define GSM322_CS_FLAG_BA 0x02 /* frequency is part of the current ba */
+#define GSM322_CS_FLAG_POWER 0x04 /* frequency was power scanned */
+#define GSM322_CS_FLAG_SIGNAL 0x08 /* valid signal detected */
+#define GSM322_CS_FLAG_SYSINFO 0x10 /* complete sysinfo received */
+#define GSM322_CS_FLAG_BARRED 0x20 /* cell is barred */
+#define GSM322_CS_FLAG_FORBIDD 0x40 /* cell in list of forbidden LAs */
+#define GSM322_CS_FLAG_TEMP_AA 0x80 /* if temporary available and allowable */
+
+/* Cell selection list */
+struct gsm322_cs_list {
+ uint8_t flags; /* see GSM322_CS_FLAG_* */
+ int8_t rxlev_db; /* rx level in real dB */
+ int8_t min_db; /* minimum level to enter cell */
+ int8_t max_pwr; /* maximum power to access cell */
+ uint16_t class_barr; /* barred classes */
+ uint16_t mcc, mnc, lac; /* received mcc, mnc, lac */
+};
+
+/* PLMN search process */
+struct gsm322_plmn {
+ struct osmocom_ms *ms;
+ int state; /* GSM322_Ax_* or GSM322_Mx_* */
+ int mode; /* PLMN_MODE_* */
+
+ struct llist_head event_queue; /* event messages */
+ struct llist_head sorted_plmn; /* list of sorted PLMN */
+ struct llist_head forbidden_la; /* forbidden LAs */
+
+ struct timer_list timer;
+
+ int plmn_curr; /* current index in sorted_plmn */
+ uint16_t mcc, mnc; /* current network selected */
+};
+
+/* Cell selection process */
+struct gsm322_cellsel {
+ struct osmocom_ms *ms;
+ int state; /* GSM322_Cx_* */
+
+ uint16_t arfcn; /* current tuned idle mode arfcn */
+
+ struct llist_head event_queue; /* event messages */
+ struct llist_head ba_list; /* BCCH Allocation per PLMN */
+
+ struct timer_list timer;
+
+ uint16_t mcc, mnc; /* current network to search for */
+ struct gsm322_cs_list list[1024]; /* cell selection list per freq. */
+
+ uint8_t powerscan; /* currently scanning for power */
+ uint32_t scan_state; /* special state of current scan */
+};
+
+/* GSM 03.22 message */
+struct gsm322_msg {
+ int msg_type;
+ uint16_t mcc, mnc;
+ uint8_t sysinfo; /* system information type */
+ uint8_t reject; /* location update reject cause */
+};
+
+#define GSM322_ALLOC_SIZE sizeof(struct gsm322_msg)
+#define GSM322_ALLOC_HEADROOM 0
+
+int gsm322_init(struct osmocom_ms *ms);
+int gsm322_exit(struct osmocom_ms *ms);
+struct msgb *gsm322_msgb_alloc(int msg_type);
+int gsm322_plmn_sendmsg(struct osmocom_ms *ms, struct msgb *msg);
+int gsm322_cs_sendmsg(struct osmocom_ms *ms, struct msgb *msg);
+int gsm322_c_event(struct osmocom_ms *ms, struct msgb *msg);
+int gsm322_plmn_dequeue(struct osmocom_ms *ms);
+int gsm322_cs_dequeue(struct osmocom_ms *ms);
+int gsm322_add_forbidden_plmn(struct osmocom_ms *ms, uint16_t mcc,
+ uint16_t mnc, uint8_t cause);
+int gsm322_add_forbidden_la(struct osmocom_ms *ms, uint16_t mcc,
+ uint16_t mnc, uint16_t lac, uint8_t cause);
+int gsm322_del_forbidden_plmn(struct osmocom_ms *ms, uint16_t mcc,
+ uint16_t mnc);
+int gsm322_del_forbidden_la(struct osmocom_ms *ms, uint16_t mcc,
+ uint16_t mnc, uint16_t lac);
+int gsm322_is_forbidden_plmn(struct osmocom_ms *ms, uint16_t mcc, uint16_t mnc);
+int gsm322_is_forbidden_la(struct osmocom_ms *ms, uint16_t mcc, uint16_t mnc,
+ uint16_t lac);
+
+#endif /* _GSM322_H */
diff --git a/src/host/layer23/include/osmocom/gsm48_cc.h b/src/host/layer23/include/osmocom/gsm48_cc.h
new file mode 100644
index 0000000..d6ea575
--- /dev/null
+++ b/src/host/layer23/include/osmocom/gsm48_cc.h
@@ -0,0 +1,18 @@
+#ifndef _GSM48_CC_H
+#define _GSM48_CC_H
+
+struct gsm48_cclayer {
+ struct osmocom_ms *ms;
+
+ struct llist_head mncc_upqueue;
+ int (*mncc_recv)(struct osmocom_ms *, int, void *);
+};
+
+int gsm48_cc_init(struct osmocom_ms *ms);
+int gsm48_cc_exit(struct osmocom_ms *ms);
+int gsm48_rcv_cc(struct osmocom_ms *ms, struct msgb *msg);
+int mncc_dequeue(struct osmocom_ms *ms);
+int mncc_send(struct osmocom_ms *ms, int msg_type, void *arg);
+
+#endif /* _GSM48_CC_H */
+
diff --git a/src/host/layer23/include/osmocom/gsm48_mm.h b/src/host/layer23/include/osmocom/gsm48_mm.h
new file mode 100644
index 0000000..de723a1
--- /dev/null
+++ b/src/host/layer23/include/osmocom/gsm48_mm.h
@@ -0,0 +1,218 @@
+#ifndef _GSM48_MM_H
+#define _GSM48_MM_H
+
+/* GSM 04.07 9.2.2 */
+#define GSM48_MMXX_MASK 0xf00
+#define GSM48_MMCC_CLASS 0x100
+#define GSM48_MMSS_CLASS 0x200
+#define GSM48_MMSMS_CLASS 0x300
+#define GSM48_MMCC_EST_REQ 0x110
+#define GSM48_MMCC_EST_IND 0x112
+#define GSM48_MMCC_EST_CNF 0x111
+#define GSM48_MMCC_REL_REQ 0x120
+#define GSM48_MMCC_REL_IND 0x122
+#define GSM48_MMCC_DATA_REQ 0x130
+#define GSM48_MMCC_DATA_IND 0x132
+#define GSM48_MMCC_UNIT_DATA_REQ 0x140
+#define GSM48_MMCC_UNIT_DATA_IND 0x142
+#define GSM48_MMCC_SYNC_IND 0x152
+#define GSM48_MMCC_REEST_REQ 0x160
+#define GSM48_MMCC_REEST_CNF 0x161
+#define GSM48_MMCC_ERR_IND 0x172
+#define GSM48_MMCC_PROMPT_IND 0x182
+#define GSM48_MMCC_PROMPT_REJ 0x184
+#define GSM48_MMSS_EST_REQ 0x210
+#define GSM48_MMSS_EST_IND 0x212
+#define GSM48_MMSS_EST_CNF 0x211
+#define GSM48_MMSS_REL_REQ 0x220
+#define GSM48_MMSS_REL_IND 0x222
+#define GSM48_MMSS_DATA_REQ 0x230
+#define GSM48_MMSS_DATA_IND 0x232
+#define GSM48_MMSS_UNIT_DATA_REQ 0x240
+#define GSM48_MMSS_UNIT_DATA_IND 0x242
+#define GSM48_MMSS_REEST_REQ 0x260
+#define GSM48_MMSS_REEST_CNF 0x261
+#define GSM48_MMSS_ERR_IND 0x272
+#define GSM48_MMSS_PROMPT_IND 0x282
+#define GSM48_MMSS_PROMPT_REJ 0x284
+#define GSM48_MMSMS_EST_REQ 0x310
+#define GSM48_MMSMS_EST_IND 0x312
+#define GSM48_MMSMS_EST_CNF 0x311
+#define GSM48_MMSMS_REL_REQ 0x320
+#define GSM48_MMSMS_REL_IND 0x322
+#define GSM48_MMSMS_DATA_REQ 0x330
+#define GSM48_MMSMS_DATA_IND 0x332
+#define GSM48_MMSMS_UNIT_DATA_REQ 0x340
+#define GSM48_MMSMS_UNIT_DATA_IND 0x342
+#define GSM48_MMSMS_REEST_REQ 0x360
+#define GSM48_MMSMS_REEST_CNF 0x361
+#define GSM48_MMSMS_ERR_IND 0x372
+#define GSM48_MMSMS_PROMPT_IND 0x382
+#define GSM48_MMSMS_PROMPT_REJ 0x384
+
+#define MMXX_ALLOC_SIZE 256
+#define MMXX_ALLOC_HEADROOM 64
+
+/* MMxx-SAP header */
+struct gsm48_mmxx_hdr {
+ int msg_type; /* MMxx_* primitive */
+ uint32_t ref; /* reference to transaction */
+ uint32_t transaction_id; /* transaction identifier */
+ uint8_t emergency; /* emergency type of call */
+ uint8_t cause; /* cause used for release */
+};
+
+/* GSM 6.1.2 */
+#define GSM48_MMR_REG_REQ 0x01
+#define GSM48_MMR_REG_CNF 0x02
+#define GSM48_MMR_NREG_REQ 0x03
+#define GSM48_MMR_NREG_IND 0x04
+
+/* MMR-SAP header */
+struct gsm48_mmr {
+ int msg_type;
+
+ uint8_t cause;
+};
+
+/* GSM 04.07 9.2.1 */
+#define GSM48_MMXX_ST_IDLE 0
+#define GSM48_MMXX_ST_CONN_PEND 1
+#define GSM48_MMXX_ST_DEDICATED 2
+#define GSM48_MMXX_ST_CONN_SUSP 3
+#define GSM48_MMXX_ST_REESTPEND 4
+
+/* GSM 04.08 4.1.2.1 */
+#define GSM48_MM_ST_NULL 0
+#define GSM48_MM_ST_LOC_UPD_INIT 3
+#define GSM48_MM_ST_WAIT_OUT_MM_CONN 5
+#define GSM48_MM_ST_MM_CONN_ACTIVE 6
+#define GSM48_MM_ST_IMSI_DETACH_INIT 7
+#define GSM48_MM_ST_PROCESS_CM_SERV_P 8
+#define GSM48_MM_ST_WAIT_NETWORK_CMD 9
+#define GSM48_MM_ST_LOC_UPD_REJ 10
+#define GSM48_MM_ST_WAIT_RR_CONN_LUPD 13
+#define GSM48_MM_ST_WAIT_RR_CONN_MM_CON 14
+#define GSM48_MM_ST_WAIT_RR_CONN_IMSI_D 15
+#define GSM48_MM_ST_WAIT_REEST 17
+#define GSM48_MM_ST_WAIT_RR_ACTIVE 18
+#define GSM48_MM_ST_MM_IDLE 19
+#define GSM48_MM_ST_WAIT_ADD_OUT_MM_CON 20
+#define GSM48_MM_ST_MM_CONN_ACTIVE_VGCS 21
+#define GSM48_MM_ST_WAIT_RR_CONN_VGCS 22
+#define GSM48_MM_ST_LOC_UPD_PEND 23
+#define GSM48_MM_ST_IMSI_DETACH_PEND 24
+#define GSM48_MM_ST_RR_CONN_RELEASE_NA 25
+
+/* GSM 04.08 4.1.2.1 */
+#define GSM48_MM_SST_NORMAL_SERVICE 1
+#define GSM48_MM_SST_ATTEMPT_UPDATE 2
+#define GSM48_MM_SST_LIMITED_SERVICE 3
+#define GSM48_MM_SST_NO_IMSI 4
+#define GSM48_MM_SST_NO_CELL_AVAIL 5
+#define GSM48_MM_SST_LOC_UPD_NEEDED 6
+#define GSM48_MM_SST_PLMN_SEARCH 7
+#define GSM48_MM_SST_PLMN_SEARCH_NORMAL 8
+#define GSM48_MM_SST_RX_VGCS_NORMAL 9
+#define GSM48_MM_SST_RX_VGCS_LIMITED 10
+
+/* MM events */
+#define GSM48_MM_EVENT_NEW_LAI 1
+#define GSM48_MM_EVENT_TIMEOUT_T3210 2
+#define GSM48_MM_EVENT_TIMEOUT_T3211 3
+#define GSM48_MM_EVENT_TIMEOUT_T3212 4
+#define GSM48_MM_EVENT_TIMEOUT_T3213 5
+#define GSM48_MM_EVENT_TIMEOUT_T3220 7
+#define GSM48_MM_EVENT_TIMEOUT_T3230 8
+#define GSM48_MM_EVENT_TIMEOUT_T3240 9
+#define GSM48_MM_EVENT_IMSI_DETACH 10
+#define GSM48_MM_EVENT_POWER_OFF 11
+#define GSM48_MM_EVENT_PAGING 12
+#define GSM48_MM_EVENT_AUTH_RESPONSE 13
+#define GSM48_MM_EVENT_SYSINFO 14
+
+/* message for MM events */
+struct gsm48_mm_event {
+ uint32_t msg_type;
+
+ uint8_t sres[4];
+};
+
+/* GSM 04.08 MM timers */
+#define GSM_T3210_MS 20, 0
+#define GSM_T3211_MS 15, 0
+/* T3212 is given by SYSTEM INFORMATION */
+#define GSM_T3213_MS 4, 0
+#define GSM_T3220_MS 5, 0
+#define GSM_T3230_MS 15, 0
+#define GSM_T3240_MS 10, 0
+#define GSM_T3241_MS 300, 0
+
+/* MM sublayer instance */
+struct gsm48_mmlayer {
+ struct osmocom_ms *ms;
+ int state;
+ int substate;
+
+ /* queue for RR-SAP, MMxx-SAP, MMR-SAP, events message upwards */
+ struct llist_head rr_upqueue;
+ struct llist_head mmxx_upqueue;
+ struct llist_head mmr_downqueue;
+ struct llist_head event_queue;
+
+ /* timers */
+ struct timer_list t3210, t3211, t3212, t3213;
+ struct timer_list t3220, t3230, t3240;
+ int t3212_value;
+
+ /* list of MM connections */
+ struct llist_head mm_conn;
+
+ /* network name */
+ char name_short[32];
+ char name_long[32];
+
+ /* location update */
+ uint8_t lupd_pending; /* current pending loc. upd. */
+ uint8_t lupd_type; /* current coded type */
+ uint8_t lupd_attempt; /* attempt counter */
+ uint8_t lupd_ra_failure;/* random access failed */
+ uint8_t lupd_rej_cause; /* cause of last reject */
+
+ /* imsi detach */
+ uint8_t delay_detach; /* do detach when possible */
+
+ /* other */
+ int mr_substate; /* rem most recent substate */
+};
+
+/* MM connection entry */
+struct gsm48_mm_conn {
+ struct llist_head list;
+ struct gsm48_mmlayer *mm;
+
+ /* ref and type form a unique tupple */
+ uint32_t ref; /* reference to trans */
+ uint8_t protocol;
+ uint8_t transaction_id;
+
+ int state;
+};
+
+int gsm48_mm_init(struct osmocom_ms *ms);
+int gsm48_mm_exit(struct osmocom_ms *ms);
+struct msgb *gsm48_mmr_msgb_alloc(int msg_type);
+struct msgb *gsm48_mmevent_msgb_alloc(int msg_type);
+int gsm48_mmevent_msg(struct osmocom_ms *ms, struct msgb *msg);
+int gsm48_mmr_downmsg(struct osmocom_ms *ms, struct msgb *msg);
+int gsm48_rr_dequeue(struct osmocom_ms *ms);
+int gsm48_mmxx_dequeue(struct osmocom_ms *ms);
+int gsm48_mmr_dequeue(struct osmocom_ms *ms);
+int gsm48_mmevent_dequeue(struct osmocom_ms *ms);
+int gsm48_mmxx_downmsg(struct osmocom_ms *ms, struct msgb *msg);
+struct msgb *gsm48_mmxx_msgb_alloc(int msg_type, uint32_t ref,
+ uint8_t transaction_id);
+const char *get_mmr_name(int value);
+const char *get_mmxx_name(int value);
+
+#endif /* _GSM48_MM_H */
diff --git a/src/host/layer23/include/osmocom/gsm48_rr.h b/src/host/layer23/include/osmocom/gsm48_rr.h
new file mode 100644
index 0000000..9285f58
--- /dev/null
+++ b/src/host/layer23/include/osmocom/gsm48_rr.h
@@ -0,0 +1,146 @@
+#ifndef _GSM48_RR_H
+#define _GSM48_RR_H
+
+#include "osmocore/protocol/gsm_04_08.h"
+
+/* GSM 04.07 9.1.2 */
+#define GSM48_RR_EST_REQ 0x10
+#define GSM48_RR_EST_IND 0x12
+#define GSM48_RR_EST_CNF 0x11
+#define GSM48_RR_REL_IND 0x22
+#define GSM48_RR_SYNC_IND 0x32
+#define GSM48_RR_DATA_REQ 0x40
+#define GSM48_RR_DATA_IND 0x42
+#define GSM48_RR_UNIT_DATA_IND 0x52
+#define GSM48_RR_ABORT_REQ 0x60
+#define GSM48_RR_ABORT_IND 0x62
+#define GSM48_RR_ACT_REQ 0x70
+
+#define RR_EST_CAUSE_EMERGENCY 1
+#define RR_EST_CAUSE_REESTAB_TCH_F 2
+#define RR_EST_CAUSE_REESTAB_TCH_H 3
+#define RR_EST_CAUSE_REESTAB_2_TCH_H 4
+#define RR_EST_CAUSE_ANS_PAG_ANY 5
+#define RR_EST_CAUSE_ANS_PAG_SDCCH 6
+#define RR_EST_CAUSE_ANS_PAG_TCH_F 7
+#define RR_EST_CAUSE_ANS_PAG_TCH_ANY 8
+#define RR_EST_CAUSE_ORIG_TCHF 9
+#define RR_EST_CAUSE_LOC_UPD 12
+#define RR_EST_CAUSE_OTHER_SDCCH 13
+
+#define RR_REL_CAUSE_UNDEFINED 0
+#define RR_REL_CAUSE_NOT_AUTHORIZED 1
+#define RR_REL_CAUSE_RA_FAILURE 2
+#define RR_REL_CAUSE_T3122 3
+#define RR_REL_CAUSE_TRY_LATER 4
+#define RR_REL_CAUSE_EMERGENCY_ONLY 5
+
+#define L3_ALLOC_SIZE 256
+#define L3_ALLOC_HEADROOM 64
+
+#define RR_ALLOC_SIZE 256
+#define RR_ALLOC_HEADROOM 64
+
+/* GSM 04.08 RR-SAP header */
+struct gsm48_rr_hdr {
+ uint32_t msg_type; /* RR-* primitive */
+ uint8_t cause;
+};
+
+/* GSM 04.07 9.1.1 */
+#define GSM48_RR_ST_IDLE 0
+#define GSM48_RR_ST_CONN_PEND 1
+#define GSM48_RR_ST_DEDICATED 2
+
+/* channel description */
+struct gsm48_rr_cd {
+ uint8_t tsc;
+ uint8_t h; /* using hopping */
+ uint16_t arfcn;
+ uint8_t maio;
+ uint8_t hsn;
+ uint8_t chan_nr; /* type, slot, sub slot */
+ uint8_t link_id;
+ uint8_t ta; /* timing advance */
+ uint8_t mob_alloc_lv[9]; /* len + up to 64 bits */
+ uint8_t start_t1, start_t2, start_t3; /* start. time */
+};
+
+/* measurements */
+struct gsm48_rr_meas {
+ uint8_t rxlev_full;
+ uint8_t rxlev_sub;
+ uint8_t rxqual_full;
+ uint8_t rxqual_sub;
+ uint8_t dtx;
+ uint8_t ba;
+ uint8_t meas_valid;
+ uint8_t ncell_na;
+ uint8_t count;
+ uint8_t rxlev_nc[6];
+ uint8_t bsic_nc[6];
+ uint8_t bcch_f_nc[6];
+};
+
+/* RR sublayer instance */
+struct gsm48_rrlayer {
+ struct osmocom_ms *ms;
+ int state;
+
+ /* queue for RSL-SAP message upwards */
+ struct llist_head rsl_upqueue;
+
+ /* queue for messages while RR connection is built up */
+ struct llist_head downqueue;
+
+ /* timers */
+ struct timer_list t3122;
+ struct timer_list t3124;
+ struct timer_list t3126;
+ int t3126_value;
+#ifndef TODO
+ struct timer_list temp_rach_ti; /* temporary timer */
+#endif
+
+ /* states if RR-EST-REQ was used */
+ int rr_est_req;
+ struct msgb *rr_est_msg;
+
+ /* channel request states */
+ uint8_t wait_assign; /* waiting for assignment state */
+ uint8_t n_chan_req; /* number left, incl. current */
+ uint8_t chan_req_val; /* current request value */
+ uint8_t chan_req_mask; /* mask of random bits */
+ int16_t cr_hist[3];
+ /* cr_hist must be signed and greater 8 bit, -1 = no value */
+
+ /* current channel descriptions */
+ uint16_t arfcn; /* dedicated mode only */
+ struct gsm48_rr_cd cd_now;
+
+ /* current cipering */
+ uint8_t cipher_on;
+ uint8_t cipher_type; /* 10.5.2.9 */
+
+ /* special states when changing channel */
+ int hando_susp_state;
+ int assign_susp_state;
+ int resume_last_state;
+ struct gsm48_rr_cd cd_last;
+
+ /* measurements */
+ struct gsm48_rr_meas meas;
+};
+
+const char *get_rr_name(int value);
+extern int gsm48_rr_init(struct osmocom_ms *ms);
+extern int gsm48_rr_exit(struct osmocom_ms *ms);
+int gsm48_rsl_dequeue(struct osmocom_ms *ms);
+int gsm48_rr_downmsg(struct osmocom_ms *ms, struct msgb *msg);
+struct msgb *gsm48_l3_msgb_alloc(void);
+struct msgb *gsm48_rr_msgb_alloc(int msg_type);
+int gsm48_decode_lai(struct gsm48_loc_area_id *lai, uint16_t *mcc,
+ uint16_t *mnc, uint16_t *lac);
+int gsm48_rr_enc_cm2(struct osmocom_ms *ms, struct gsm48_classmark2 *cm);
+
+#endif /* _GSM48_RR_H */
diff --git a/src/host/layer23/include/osmocom/l23_app.h b/src/host/layer23/include/osmocom/l23_app.h
index 6c246b3..bade9fc 100644
--- a/src/host/layer23/include/osmocom/l23_app.h
+++ b/src/host/layer23/include/osmocom/l23_app.h
@@ -3,6 +3,7 @@
/* initialization, called once when starting the app, before entering
* select loop */
-int l23_app_init(struct osmocom_ms *ms);
+extern int l23_app_init(struct osmocom_ms *ms);
+extern int (*l23_app_work) (struct osmocom_ms *ms);
#endif /* _L23_APP_H */
diff --git a/src/host/layer23/include/osmocom/logging.h b/src/host/layer23/include/osmocom/logging.h
index e1bb583..e90d542 100644
--- a/src/host/layer23/include/osmocom/logging.h
+++ b/src/host/layer23/include/osmocom/logging.h
@@ -7,6 +7,8 @@
enum {
DRSL,
DRR,
+ DPLMN,
+ DCS,
DMM,
DCC,
DSMS,
diff --git a/src/host/layer23/include/osmocom/mncc.h b/src/host/layer23/include/osmocom/mncc.h
new file mode 100644
index 0000000..e53d246
--- /dev/null
+++ b/src/host/layer23/include/osmocom/mncc.h
@@ -0,0 +1,154 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 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.
+ *
+ */
+
+#ifndef _MNCC_H
+#define _MNCC_H
+
+#include <osmocore/mncc.h>
+
+#define MNCC_SETUP_REQ 0x0101
+#define MNCC_SETUP_IND 0x0102
+#define MNCC_SETUP_RSP 0x0103
+#define MNCC_SETUP_CNF 0x0104
+#define MNCC_SETUP_COMPL_REQ 0x0105
+#define MNCC_SETUP_COMPL_IND 0x0106
+/* MNCC_REJ_* is perfomed via MNCC_REL_* */
+#define MNCC_CALL_CONF_IND 0x0107
+#define MNCC_CALL_PROC_REQ 0x0108
+#define MNCC_PROGRESS_REQ 0x0109
+#define MNCC_ALERT_REQ 0x010a
+#define MNCC_ALERT_IND 0x010b
+#define MNCC_NOTIFY_REQ 0x010c
+#define MNCC_NOTIFY_IND 0x010d
+#define MNCC_DISC_REQ 0x010e
+#define MNCC_DISC_IND 0x010f
+#define MNCC_REL_REQ 0x0110
+#define MNCC_REL_IND 0x0111
+#define MNCC_REL_CNF 0x0112
+#define MNCC_FACILITY_REQ 0x0113
+#define MNCC_FACILITY_IND 0x0114
+#define MNCC_START_DTMF_IND 0x0115
+#define MNCC_START_DTMF_RSP 0x0116
+#define MNCC_START_DTMF_REJ 0x0117
+#define MNCC_STOP_DTMF_IND 0x0118
+#define MNCC_STOP_DTMF_RSP 0x0119
+#define MNCC_MODIFY_REQ 0x011a
+#define MNCC_MODIFY_IND 0x011b
+#define MNCC_MODIFY_RSP 0x011c
+#define MNCC_MODIFY_CNF 0x011d
+#define MNCC_MODIFY_REJ 0x011e
+#define MNCC_HOLD_IND 0x011f
+#define MNCC_HOLD_CNF 0x0120
+#define MNCC_HOLD_REJ 0x0121
+#define MNCC_RETRIEVE_IND 0x0122
+#define MNCC_RETRIEVE_CNF 0x0123
+#define MNCC_RETRIEVE_REJ 0x0124
+#define MNCC_USERINFO_REQ 0x0125
+#define MNCC_USERINFO_IND 0x0126
+#define MNCC_REJ_REQ 0x0127
+#define MNCC_REJ_IND 0x0128
+#define MNCC_PROGRESS_IND 0x0129
+#define MNCC_CALL_PROC_IND 0x012a
+#define MNCC_CALL_CONF_REQ 0x012b
+#define MNCC_START_DTMF_REQ 0x012c
+#define MNCC_STOP_DTMF_REQ 0x012d
+#define MNCC_HOLD_REQ 0x012e
+#define MNCC_RETRIEVE_REQ 0x012f
+
+#define MNCC_BRIDGE 0x0200
+#define MNCC_FRAME_RECV 0x0201
+#define MNCC_FRAME_DROP 0x0202
+#define MNCC_LCHAN_MODIFY 0x0203
+
+#define GSM_TCHF_FRAME 0x0300
+#define GSM_TCHF_FRAME_EFR 0x0301
+
+#define GSM_MAX_FACILITY 128
+#define GSM_MAX_SSVERSION 128
+#define GSM_MAX_USERUSER 128
+
+#define MNCC_F_BEARER_CAP 0x0001
+#define MNCC_F_CALLED 0x0002
+#define MNCC_F_CALLING 0x0004
+#define MNCC_F_REDIRECTING 0x0008
+#define MNCC_F_CONNECTED 0x0010
+#define MNCC_F_CAUSE 0x0020
+#define MNCC_F_USERUSER 0x0040
+#define MNCC_F_PROGRESS 0x0080
+#define MNCC_F_EMERGENCY 0x0100
+#define MNCC_F_FACILITY 0x0200
+#define MNCC_F_SSVERSION 0x0400
+#define MNCC_F_CCCAP 0x0800
+#define MNCC_F_KEYPAD 0x1000
+#define MNCC_F_SIGNAL 0x2000
+
+struct gsm_mncc {
+ /* context based information */
+ u_int32_t msg_type;
+ u_int32_t callref;
+
+ /* which fields are present */
+ u_int32_t fields;
+
+ /* data derived informations (MNCC_F_ based) */
+ struct gsm_mncc_bearer_cap bearer_cap;
+ struct gsm_mncc_number called;
+ struct gsm_mncc_number calling;
+ struct gsm_mncc_number redirecting;
+ struct gsm_mncc_number connected;
+ struct gsm_mncc_cause cause;
+ struct gsm_mncc_progress progress;
+ struct gsm_mncc_useruser useruser;
+ struct gsm_mncc_facility facility;
+ struct gsm_mncc_cccap cccap;
+ struct gsm_mncc_ssversion ssversion;
+ struct {
+ int sup;
+ int inv;
+ } clir;
+ int signal;
+
+ /* data derived information, not MNCC_F based */
+ int keypad;
+ int more;
+ int notify; /* 0..127 */
+ int emergency;
+ char imsi[16];
+
+ unsigned char lchan_mode;
+};
+
+struct gsm_data_frame {
+ u_int32_t msg_type;
+ u_int32_t callref;
+ unsigned char data[0];
+};
+
+const char *get_mncc_name(int value);
+int mncc_recv(struct osmocom_ms *ms, int msg_type, void *arg);
+void mncc_set_cause(struct gsm_mncc *data, int loc, int val);
+
+#endif
+
diff --git a/src/host/layer23/include/osmocom/osmocom_data.h b/src/host/layer23/include/osmocom/osmocom_data.h
index 18c3c3c..62f97a3 100644
--- a/src/host/layer23/include/osmocom/osmocom_data.h
+++ b/src/host/layer23/include/osmocom/osmocom_data.h
@@ -5,10 +5,17 @@
#include <osmocore/gsm_utils.h>
#include <osmocore/write_queue.h>
-#include <osmocom/lapdm.h>
-
struct osmocom_ms;
+#include <osmocom/support.h>
+#include <osmocom/subscriber.h>
+#include <osmocom/lapdm.h>
+#include <osmocom/gsm48_rr.h>
+#include <osmocom/gsm322.h>
+#include <osmocom/gsm48_mm.h>
+#include <osmocom/sysinfo.h>
+#include <osmocom/gsm48_cc.h>
+
/* A layer2 entity */
struct osmol2_entity {
struct lapdm_entity lapdm_dcch;
@@ -20,10 +27,21 @@ struct osmol2_entity {
struct osmocom_ms {
char name[32];
struct write_queue wq;
- enum gsm_band band;
uint16_t test_arfcn;
+ struct gsm_support support;
+
+ struct gsm_subscriber subscr;
+
struct osmol2_entity l2_entity;
+
+ struct gsm48_rrlayer rrlayer;
+ struct gsm322_plmn plmn;
+ struct gsm322_cellsel cellsel;
+ struct gsm48_sysinfo sysinfo;
+ struct gsm48_mmlayer mmlayer;
+ struct gsm48_cclayer cclayer;
+ struct llist_head trans_list;
};
enum osmobb_sig_subsys {
diff --git a/src/host/layer23/include/osmocom/subscriber.h b/src/host/layer23/include/osmocom/subscriber.h
new file mode 100644
index 0000000..7b6c6e3
--- /dev/null
+++ b/src/host/layer23/include/osmocom/subscriber.h
@@ -0,0 +1,73 @@
+#ifndef _SUBSCRIBER_H
+#define _SUBSCRIBER_H
+
+/* GSM 04.08 4.1.2.2 SIM update status */
+#define GSM_SIM_U0_NULL 0
+#define GSM_SIM_U1_UPDATED 1
+#define GSM_SIM_U2_NOT_UPDATED 2
+#define GSM_SIM_U3_ROAMING_NA 3
+
+struct gsm_sub_plmn_list {
+ struct llist_head entry;
+ uint16_t mcc, mnc;
+};
+
+struct gsm_sub_plmn_na {
+ struct llist_head entry;
+ uint16_t mcc, mnc;
+ uint8_t cause;
+};
+
+#define GSM_IMSI_LENGTH 16
+
+struct gsm_subscriber {
+ struct osmocom_ms *ms;
+
+ /* status */
+ uint8_t sim_valid; /* sim inserted and valid */
+ uint8_t ustate; /* update status */
+ uint8_t sim_att; /* attached state */
+
+ /* LAI */
+ uint8_t lai_valid;
+ uint16_t lai_mcc, lai_mnc, lai_lac;
+
+ /* IMSI */
+ uint16_t mcc, mnc;
+ char imsi[GSM_IMSI_LENGTH];
+
+ /* TMSI */
+ uint8_t tmsi_valid;
+ uint32_t tmsi;
+
+ /* key */
+ uint8_t key_seq; /* ciphering key sequence number */
+ uint8_t key[32]; /* up to 256 bit */
+
+ /* other */
+ struct llist_head plmn_list; /* PLMN Selector field */
+ struct llist_head plmn_na; /* not allowed PLMNs */
+ uint8_t t6m_hplmn; /* timer for hplmn search */
+
+ /* special things */
+ uint8_t always_search_hplmn;
+ /* search hplmn in other countries also (for test cards) */
+ char sim_name[32]; /* name to load/save sim */
+
+ /* PLMN last registered */
+ uint8_t plmn_valid;
+ uint16_t plmn_mcc, plmn_mnc;
+
+ /* our access */
+ uint8_t acc_barr; /* if we may access, if cell barred */
+ uint16_t acc_class; /* bitmask of what we may access */
+};
+
+int gsm_subscr_init(struct osmocom_ms *ms);
+int gsm_subscr_exit(struct osmocom_ms *ms);
+int gsm_subscr_testcard(struct osmocom_ms *ms, int mcc, int mnc, char *msin);
+int gsm_subscr_remove(struct osmocom_ms *ms);
+void new_sim_ustate(struct gsm_subscriber *subscr, int state);
+
+#endif /* _SUBSCRIBER_H */
+
diff --git a/src/host/layer23/include/osmocom/support.h b/src/host/layer23/include/osmocom/support.h
new file mode 100644
index 0000000..155c369
--- /dev/null
+++ b/src/host/layer23/include/osmocom/support.h
@@ -0,0 +1,80 @@
+#ifndef _SUPPORT_H
+#define _SUPPORT_H
+
+#define GSM_CIPHER_A5_1 0
+#define GSM_CIPHER_A5_2 1
+#define GSM_CIPHER_A5_3 2
+#define GSM_CIPHER_A5_4 3
+#define GSM_CIPHER_A5_5 4
+#define GSM_CIPHER_A5_6 5
+#define GSM_CIPHER_A5_7 6
+#define GSM_CIPHER_RESERVED 7
+
+struct gsm_support {
+ /* rf power capability */
+ uint8_t pwr_lev_900; /* and < 900 */
+ uint8_t pwr_lev_1800; /* DCS and PCS */
+ /* controlled early classmark sending */
+ uint8_t es_ind;
+ /* revision level */
+ uint8_t rev_lev;
+ /* support of VGCS */
+ uint8_t vgcs;
+ /* support of VBS */
+ uint8_t vbs;
+ /* support of SMS */
+ uint8_t sms_ptp;
+ /* screening indicator */
+ uint8_t ss_ind;
+ /* pseudo synchronised capability */
+ uint8_t ps_cap;
+ /* CM service prompt */
+ uint8_t cmsp;
+ /* solsa support */
+ uint8_t solsa;
+ /* location service support */
+ uint8_t lcsva;
+ /* codec supprot */
+ uint8_t a5_1;
+ uint8_t a5_2;
+ uint8_t a5_3;
+ uint8_t a5_4;
+ uint8_t a5_5;
+ uint8_t a5_6;
+ uint8_t a5_7;
+ /* radio support */
+ uint8_t p_gsm;
+ uint8_t e_gsm;
+ uint8_t r_gsm;
+ uint8_t r_capa;
+ uint8_t low_capa;
+ uint8_t dcs_1800;
+ uint8_t dcs_capa;
+ uint8_t freq_map[128];
+ /* multi slot support */
+ uint8_t ms_sup;
+ /* ucs2 treatment */
+ uint8_t ucs2_treat;
+ /* support extended measurements */
+ uint8_t ext_meas;
+ /* support switched measurement capability */
+ uint8_t meas_cap;
+ uint8_t sms_val;
+ uint8_t sm_val;
+ /* positioning method capability */
+ uint8_t loc_serv;
+ uint8_t e_otd_ass;
+ uint8_t e_otd_based;
+ uint8_t gps_ass;
+ uint8_t gps_based;
+ uint8_t gps_conv;
+
+ /* IMEI */
+ char imei[15];
+ char imeisv[17];
+};
+
+void gsm_support_init(struct osmocom_ms *ms);
+
+#endif /* _SUPPORT_H */
+
diff --git a/src/host/layer23/include/osmocom/sysinfo.h b/src/host/layer23/include/osmocom/sysinfo.h
new file mode 100644
index 0000000..6dfc7b1
--- /dev/null
+++ b/src/host/layer23/include/osmocom/sysinfo.h
@@ -0,0 +1,104 @@
+#ifndef _SYSINFO_H
+#define _SYSINFO_H
+
+/* collection of system information of the current cell */
+
+/* frequency mask flags of frequency type */
+#define FREQ_TYPE_SERV 0x01 /* frequency of the serving cell */
+#define FREQ_TYPE_HOPP 0x02 /* frequency used for channel hopping */
+#define FREQ_TYPE_NCELL 0x1c /* frequency of the neighbor cell */
+#define FREQ_TYPE_NCELL_2 0x04 /* sub channel of SI 2 */
+#define FREQ_TYPE_NCELL_2bis 0x08 /* sub channel of SI 2bis */
+#define FREQ_TYPE_NCELL_2ter 0x10 /* sub channel of SI 2ter */
+#define FREQ_TYPE_REP 0xe0 /* frequency to be reported */
+#define FREQ_TYPE_REP_5 0x20 /* sub channel of SI 5 */
+#define FREQ_TYPE_REP_5bis 0x40 /* sub channel of SI 5bis */
+#define FREQ_TYPE_REP_5ter 0x80 /* sub channel of SI 5ter */
+
+/* structure of one frequency */
+struct gsm_sysinfo_freq {
+ /* if the frequency included in the sysinfo */
+ uint8_t mask;
+};
+
+/* structure of all received system informations */
+struct gsm48_sysinfo {
+ /* flags of available information */
+ uint8_t si1, si2, si2bis, si2ter, si3,
+ si4, si5, si5bis, si5ter, si6;
+
+ /* memory maps to simply detect change in system info messages */
+ uint8_t si1_msg[23];
+ uint8_t si2_msg[23];
+ uint8_t si2b_msg[23];
+ uint8_t si2t_msg[23];
+ uint8_t si3_msg[23];
+ uint8_t si4_msg[23];
+ uint8_t si5_msg[18];
+ uint8_t si5b_msg[18];
+ uint8_t si5t_msg[18];
+ uint8_t si6_msg[18];
+
+ struct gsm_sysinfo_freq freq[1024]; /* all frequencies */
+ uint16_t hopping[64]; /* hopping arfcn */
+ uint8_t hopp_len;
+
+ /* serving cell */
+ uint16_t cell_id;
+ uint16_t mcc, mnc, lac; /* LAI */
+ uint8_t max_retrans; /* decoded */
+ uint8_t tx_integer; /* decoded */
+ uint8_t reest_denied; /* 1 = denied */
+ uint8_t cell_barr; /* 1 = barred */
+ uint16_t class_barr; /* bit 10 is emergency */
+ uint16_t cell_identity;
+ /* cell selection */
+ int8_t ms_txpwr_max_ccch;
+ int8_t cell_resel_hyst_db;
+ int8_t rxlev_acc_min_db;
+ uint8_t neci;
+ uint8_t acs;
+ /* bcch options */
+ uint8_t bcch_radio_link_timeout;
+ uint8_t bcch_dtx;
+ uint8_t bcch_pwrc;
+ /* sacch options */
+ uint8_t sacch_radio_link_timeout;
+ uint8_t sacch_dtx;
+ uint8_t sacch_pwrc;
+ /* control channel */
+ uint8_t ccch_conf;
+ uint8_t bs_ag_blks_res;
+ uint8_t att_allowed;
+ uint8_t pag_mf_periods;
+ int32_t t3212; /* real value in seconds */
+ /* channel description */
+ uint8_t tsc;
+ uint8_t h; /* using hopping */
+ uint16_t arfcn;
+ uint8_t maio;
+ uint8_t hsn;
+ uint8_t chan_nr; /* type, slot, sub slot */
+
+ /* neighbor cell */
+ uint8_t nb_ext_ind_si2;
+ uint8_t nb_ba_ind_si2;
+ uint8_t nb_ext_ind_si2bis;
+ uint8_t nb_ba_ind_si2bis;
+ uint8_t nb_multi_rep_si2ter; /* see GSM 05.08 8.4.3 */
+ uint8_t nb_ext_ind_si5;
+ uint8_t nb_ba_ind_si5;
+ uint8_t nb_ext_ind_si5bis;
+ uint8_t nb_ba_ind_si5bis;
+ uint8_t nb_multi_rep_si5ter;
+ uint8_t nb_ncc_permitted;
+ uint8_t nb_max_retrans; /* decoded */
+ uint8_t nb_tx_integer; /* decoded */
+ uint8_t nb_reest_denied; /* 1 = denied */
+ uint8_t nb_cell_barr; /* 1 = barred */
+ uint16_t nb_class_barr; /* bit 10 is emergency */
+};
+
+void gsm48_sysinfo_init(struct osmocom_ms *ms);
+
+#endif /* _SYSINFO_H */
diff --git a/src/host/layer23/include/osmocom/transaction.h b/src/host/layer23/include/osmocom/transaction.h
new file mode 100644
index 0000000..4be82c1
--- /dev/null
+++ b/src/host/layer23/include/osmocom/transaction.h
@@ -0,0 +1,71 @@
+#ifndef _TRANSACT_H
+#define _TRANSACT_H
+
+#include <osmocore/linuxlist.h>
+
+/* One transaction */
+struct gsm_trans {
+ /* Entry in list of all transactions */
+ struct llist_head entry;
+
+ /* The protocol within which we live */
+ uint8_t protocol;
+
+ /* The current transaction ID */
+ uint8_t transaction_id;
+
+ /* To whom we belong */
+ struct osmocom_ms *ms;
+
+ /* reference from MNCC or other application */
+ uint32_t callref;
+
+ /* if traffic channel receive was requested */
+ int tch_recv;
+
+ union {
+ struct {
+
+ /* current call state */
+ int state;
+
+ /* most recent progress indicator */
+ uint8_t prog_ind;
+
+ /* current timer and message queue */
+ int Tcurrent; /* current CC timer */
+ int T308_second; /* used to send release again */
+ struct timer_list timer;
+ struct gsm_mncc msg; /* stores setup/disconnect/release message */
+ } cc;
+#if 0
+ struct {
+ uint8_t link_id; /* RSL Link ID to be used for this trans */
+ int is_mt; /* is this a MO (0) or MT (1) transfer */
+ enum gsm411_cp_state cp_state;
+ struct timer_list cp_timer;
+
+ enum gsm411_rp_state rp_state;
+
+ struct gsm_sms *sms;
+ } sms;
+#endif
+ };
+};
+
+
+
+struct gsm_trans *trans_find_by_id(struct osmocom_ms *ms,
+ uint8_t proto, uint8_t trans_id);
+struct gsm_trans *trans_find_by_callref(struct osmocom_ms *ms,
+ uint32_t callref);
+
+struct gsm_trans *trans_alloc(struct osmocom_ms *ms,
+ uint8_t protocol, uint8_t trans_id,
+ uint32_t callref);
+void trans_free(struct gsm_trans *trans);
+
+int trans_assign_trans_id(struct osmocom_ms *ms,
+ uint8_t protocol, uint8_t ti_flag);
+
+#endif
diff --git a/src/host/layer23/src/Makefile.am b/src/host/layer23/src/Makefile.am
index 2c47d50..ff984be 100644
--- a/src/host/layer23/src/Makefile.am
+++ b/src/host/layer23/src/Makefile.am
@@ -4,9 +4,11 @@ AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS)
noinst_LIBRARIES = liblayer23.a
liblayer23_a_SOURCES = l1ctl.c gsmtap_util.c lapdm.c rslms.c \
- layer3.c logging.c bcch_scan.c
+ layer3.c logging.c bcch_scan.c \
+ gsm48_cc.c transaction.c gsm48_mm.c gsm48_rr.c \
+ gsm322.c support.c subscriber.c sysinfo.c mnccms.c
-bin_PROGRAMS = bcch_scan layer23 echo_test
+bin_PROGRAMS = bcch_scan layer23 echo_test mobile
bcch_scan_SOURCES = main.c app_bcch_scan.c
bcch_scan_LDADD = liblayer23.a $(LIBOSMOCORE_LIBS)
@@ -16,3 +18,6 @@ layer23_LDADD = liblayer23.a $(LIBOSMOCORE_LIBS)
echo_test_SOURCES = main.c app_echo_test.c
echo_test_LDADD = liblayer23.a $(LIBOSMOCORE_LIBS)
+
+mobile_SOURCES = main.c app_mobile.c
+mobile_LDADD = liblayer23.a $(LIBOSMOCORE_LIBS)
diff --git a/src/host/layer23/src/app_mobile.c b/src/host/layer23/src/app_mobile.c
new file mode 100644
index 0000000..2e229a0
--- /dev/null
+++ b/src/host/layer23/src/app_mobile.c
@@ -0,0 +1,115 @@
+/* "Application" code of the layer2/3 stack */
+
+/* (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (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 <errno.h>
+
+#include <osmocom/osmocom_data.h>
+#include <osmocom/l1ctl.h>
+#include <osmocom/l23_app.h>
+#include <osmocom/gsm48_rr.h>
+#include <osmocom/sysinfo.h>
+#include <osmocom/lapdm.h>
+#include <osmocom/gsmtap_util.h>
+#include <osmocom/logging.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+#include <osmocore/signal.h>
+
+extern int (*app_work) (struct osmocom_ms *ms);
+int mncc_recv_dummy(struct osmocom_ms *ms, int msg_type, void *arg);
+
+int mobile_work(struct osmocom_ms *ms)
+{
+ int work = 0, w;
+
+ do {
+ w = 0;
+ w |= gsm48_rsl_dequeue(ms);
+ w |= gsm48_rr_dequeue(ms);
+ w |= gsm48_mmxx_dequeue(ms);
+ w |= gsm48_mmr_dequeue(ms);
+ w |= gsm48_mmevent_dequeue(ms);
+ w |= gsm322_plmn_dequeue(ms);
+ w |= gsm322_cs_dequeue(ms);
+ w |= mncc_dequeue(ms);
+ if (w)
+ work = 1;
+ } while (w);
+ return work;
+}
+
+static int signal_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct osmocom_ms *ms;
+ struct msgb *nmsg;
+
+ if (subsys != SS_L1CTL)
+ return 0;
+
+ switch (signal) {
+ case S_L1CTL_RESET:
+ ms = signal_data;
+ gsm_subscr_testcard(ms, 1, 1, "0000000000");
+ /* start PLMN + cell selection process */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_SWITCH_ON);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_SWITCH_ON);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_cs_sendmsg(ms, nmsg);
+ }
+ return 0;
+}
+
+int l23_app_init(struct osmocom_ms *ms)
+{
+ gsm_support_init(ms);
+ gsm_subscr_init(ms);
+ gsm48_sysinfo_init(ms);
+ gsm48_rr_init(ms);
+ gsm48_mm_init(ms);
+ gsm48_cc_init(ms);
+ INIT_LLIST_HEAD(&ms->trans_list);
+ ms->cclayer.mncc_recv = mncc_recv_dummy;
+ gsm322_init(ms);
+ l23_app_work = mobile_work;
+ return register_signal_handler(SS_L1CTL, &signal_cb, NULL);
+}
+
+/* TODO handle this */
+int l23_app_exit(struct osmocom_ms *ms)
+{
+ gsm322_exit(ms);
+ gsm48_cc_exit(ms);
+ gsm48_mm_exit(ms);
+ gsm48_rr_exit(ms);
+ gsm_subscr_exit(ms);
+ return 0;
+}
+
diff --git a/src/host/layer23/src/gsm322.c b/src/host/layer23/src/gsm322.c
new file mode 100755
index 0000000..034a759
--- /dev/null
+++ b/src/host/layer23/src/gsm322.c
@@ -0,0 +1,2648 @@
+/*
+ * (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 <osmocore/msgb.h>
+#include <osmocore/talloc.h>
+#include <osmocore/utils.h>
+#include <osmocore/gsm48.h>
+
+#include <osmocom/logging.h>
+#include <osmocom/l1ctl.h>
+#include <osmocom/file.h>
+#include <osmocom/osmocom_data.h>
+
+extern void *l23_ctx;
+
+static void gsm322_cs_timeout(void *arg);
+static int gsm322_cs_select(struct osmocom_ms *ms, int any);
+static int gsm322_m_switch_on(struct osmocom_ms *ms, struct msgb *msg);
+
+/*
+ * notes
+ */
+
+/* Cell selection process
+ *
+ * The process depends on states and events (finites state machine).
+ *
+ * During states of cell selection or cell re-selection, the search for a cell
+ * is performed in two steps:
+ *
+ * 1. Measurement of received level of all relevant frequencies (rx-lev)
+ *
+ * 2. Receive system information messages of all relevant frequencies
+ *
+ * During this process, the results are stored in a list of all frequencies.
+ * This list is checked whenever a cell is selected. It depends on the results
+ * if the cell is 'suitable' and 'allowable' to 'camp' on.
+ *
+ * This list is also used to generate a list of available networks.
+ */
+
+/* PLMN selection process
+ *
+ * The PLMN (Public Land Mobile Network = Operator's Network) has two different
+ * search processes:
+ *
+ * 1. Automatic search
+ *
+ * 2. Manual search
+ *
+ * The process depends on states and events (finites state machine).
+ *
+ */
+
+/* File format of BA list:
+ *
+ * uint16_t mcc
+ * uint16_t mcc
+ * uint8_t freq[128];
+ * where frequency 0 is bit 0 of first byte
+ *
+ * If not end-of-file, the next BA list is stored.
+ */
+
+/* List of lists:
+ *
+ * * subscr->plmn_list
+ *
+ * The "PLMN Selector list" stores prefered networks to select during PLMN
+ * search process. This list is also stored in the SIM.
+ *
+ * * subscr->plmn_na
+ *
+ * The "forbidden PLMNs" list stores all networks that rejected us. The stored
+ * network will not be used when searching PLMN automatically. This list is
+ * also stored din the SIM.
+ *
+ * * plmn->forbidden_la
+ *
+ * The "forbidden LAs for roaming" list stores all location areas where roaming
+ * was not allowed.
+ *
+ * * cs->list[1024]
+ *
+ * This list stores measurements and cell informations during cell selection
+ * process. It can be used to speed up repeated cell selection.
+ *
+ * * cs->ba_list
+ *
+ * This list stores a map of frequencies used for a PLMN. If this lists exists
+ * for a PLMN, it helps to speedup cell scan process.
+ *
+ * * plmn->sorted_plmn
+ *
+ * This list is generated whenever a PLMN search is started and a list of PLMNs
+ * is required. It consists of home PLMN, PLMN Selector list, and PLMNs found
+ * during scan process.
+ */
+
+/*
+ * event messages
+ */
+
+static const struct value_string gsm322_event_names[] = {
+ { GSM322_EVENT_SWITCH_ON, "EVENT_SWITCH_ON" },
+ { GSM322_EVENT_SWITCH_OFF, "EVENT_SWITCH_OFF" },
+ { GSM322_EVENT_SIM_INSERT, "EVENT_SIM_INSERT" },
+ { GSM322_EVENT_SIM_REMOVE, "EVENT_SIM_REMOVE" },
+ { GSM322_EVENT_REG_FAILED, "EVENT_REG_FAILED" },
+ { GSM322_EVENT_ROAMING_NA, "EVENT_ROAMING_NA" },
+ { GSM322_EVENT_INVALID_SIM, "EVENT_INVALID_SIM" },
+ { GSM322_EVENT_REG_SUCCESS, "EVENT_REG_SUCCESS" },
+ { GSM322_EVENT_NEW_PLMN, "EVENT_NEW_PLMN" },
+ { GSM322_EVENT_ON_PLMN, "EVENT_ON_PLMN" },
+ { GSM322_EVENT_HPLMN_SEARCH, "EVENT_HPLMN_SEARCH" },
+ { GSM322_EVENT_HPLMN_FOUND, "EVENT_HPLMN_FOUND" },
+ { GSM322_EVENT_HPLMN_NOT_FOUND, "EVENT_HPLMN_NOT_FOUND" },
+ { GSM322_EVENT_USER_RESEL, "EVENT_USER_RESEL" },
+ { GSM322_EVENT_PLMN_AVAIL, "EVENT_PLMN_AVAIL" },
+ { GSM322_EVENT_CHOSE_PLMN, "EVENT_CHOSE_PLMN" },
+ { GSM322_EVENT_SEL_MANUAL, "EVENT_SEL_MANUAL" },
+ { GSM322_EVENT_SEL_AUTO, "EVENT_SEL_AUTO" },
+ { GSM322_EVENT_CELL_FOUND, "EVENT_CELL_FOUND" },
+ { GSM322_EVENT_NO_CELL_FOUND, "EVENT_NO_CELL_FOUND" },
+ { GSM322_EVENT_LEAVE_IDLE, "EVENT_LEAVE_IDLE" },
+ { GSM322_EVENT_RET_IDLE, "EVENT_RET_IDLE" },
+ { GSM322_EVENT_CELL_RESEL, "EVENT_CELL_RESEL" },
+ { GSM322_EVENT_SYSINFO, "EVENT_SYSINFO" },
+ { 0, NULL }
+};
+
+const char *get_event_name(int value)
+{
+ return get_value_string(gsm322_event_names, value);
+}
+
+
+/* allocate a 03.22 event message */
+struct msgb *gsm322_msgb_alloc(int msg_type)
+{
+ struct msgb *msg;
+ struct gsm322_msg *gm;
+
+ msg = msgb_alloc_headroom(sizeof(*gm), 0, "GSM 03.22 event");
+ if (!msg)
+ return NULL;
+
+ gm = (struct gsm322_msg *)msgb_put(msg, sizeof(*gm));
+ gm->msg_type = msg_type;
+
+ return msg;
+}
+
+/* queue PLMN selection message */
+int gsm322_plmn_sendmsg(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+
+ msgb_enqueue(&plmn->event_queue, msg);
+
+ return 0;
+}
+
+/* queue cell selection message */
+int gsm322_cs_sendmsg(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+
+ msgb_enqueue(&cs->event_queue, msg);
+
+ return 0;
+}
+
+/*
+ * support
+ */
+
+/* del forbidden PLMN */
+int gsm322_del_forbidden_plmn(struct osmocom_ms *ms, uint16_t mcc,
+ uint16_t mnc)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm_sub_plmn_na *na;
+
+ llist_for_each_entry(na, &subscr->plmn_na, entry) {
+ if (na->mcc == mcc && na->mnc == mnc) {
+ LOGP(DPLMN, LOGL_INFO, "Delete from list of forbidden "
+ "PLMNs (mcc=%03d, mnc=%02d)\n", mcc, mnc);
+ llist_del(&na->entry);
+ talloc_free(na);
+#ifdef TODO
+ update plmn not allowed list on sim
+#endif
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+/* add forbidden PLMN */
+int gsm322_add_forbidden_plmn(struct osmocom_ms *ms, uint16_t mcc,
+ uint16_t mnc, uint8_t cause)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm_sub_plmn_na *na;
+
+ /* don't add Home PLMN */
+ if (subscr->sim_valid && mcc == subscr->mcc && mnc == subscr->mnc)
+ return -EINVAL;
+
+ LOGP(DPLMN, LOGL_INFO, "Add to list of forbidden PLMNs "
+ "(mcc=%03d, mnc=%02d)\n", mcc, mnc);
+ na = talloc_zero(l23_ctx, struct gsm_sub_plmn_na);
+ if (!na)
+ return -ENOMEM;
+ na->mcc = mcc;
+ na->mnc = mnc;
+ na->cause = cause;
+ llist_add_tail(&na->entry, &subscr->plmn_na);
+
+#ifdef TODO
+ update plmn not allowed list on sim
+#endif
+
+ return 0;
+}
+
+/* search forbidden PLMN */
+int gsm322_is_forbidden_plmn(struct osmocom_ms *ms, uint16_t mcc, uint16_t mnc)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm_sub_plmn_na *na;
+
+ llist_for_each_entry(na, &subscr->plmn_na, entry) {
+ if (na->mcc == mcc && na->mnc == mnc)
+ return 1;
+ }
+
+ return 0;
+}
+
+/* del forbidden LA */
+int gsm322_del_forbidden_la(struct osmocom_ms *ms, uint16_t mcc,
+ uint16_t mnc, uint16_t lac)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm322_la_list *la;
+
+ llist_for_each_entry(la, &plmn->forbidden_la, entry) {
+ if (la->mcc == mcc && la->mnc == mnc && la->lac == lac) {
+ LOGP(DPLMN, LOGL_INFO, "Delete from list of forbidden "
+ "LAs (mcc=%03d, mnc=%02d, lac=%04x)\n",
+ mcc, mnc, lac);
+ llist_del(&la->entry);
+ talloc_free(la);
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+/* add forbidden LA */
+int gsm322_add_forbidden_la(struct osmocom_ms *ms, uint16_t mcc,
+ uint16_t mnc, uint16_t lac, uint8_t cause)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm322_la_list *la;
+
+ LOGP(DPLMN, LOGL_INFO, "Add to list of forbidden LAs "
+ "(mcc=%03d, mnc=%02d, lac=%04x)\n", mcc, mnc, lac);
+ la = talloc_zero(l23_ctx, struct gsm322_la_list);
+ if (!la)
+ return -ENOMEM;
+ la->mcc = mcc;
+ la->mnc = mnc;
+ la->lac = lac;
+ la->cause = cause;
+ llist_add_tail(&la->entry, &plmn->forbidden_la);
+
+ return 0;
+}
+
+/* search forbidden LA */
+int gsm322_is_forbidden_la(struct osmocom_ms *ms, uint16_t mcc, uint16_t mnc,
+ uint16_t lac)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm322_la_list *la;
+
+ llist_for_each_entry(la, &plmn->forbidden_la, entry) {
+ if (la->mcc == mcc && la->mnc == mnc && la->lac == lac)
+ return 1;
+ }
+
+ return 0;
+}
+
+/* search for PLMN in all BA lists */
+static struct gsm322_ba_list *gsm322_find_ba_list(struct gsm322_cellsel *cs,
+ uint16_t mcc, uint16_t mnc)
+{
+ struct gsm322_ba_list *ba, *ba_found = NULL;
+
+ /* search for BA list */
+ llist_for_each_entry(ba, &cs->ba_list, entry) {
+ if (ba->mcc == mcc
+ && ba->mnc == mnc) {
+ ba_found = ba;
+ break;
+ }
+ }
+
+ return ba_found;
+}
+
+/*
+ * timer
+ */
+
+/*plmn search timer event */
+static void plmn_timer_timeout(void *arg)
+{
+ struct gsm322_plmn *plmn = arg;
+ struct msgb *nmsg;
+
+ LOGP(DPLMN, LOGL_INFO, "HPLMN search timer has fired.\n");
+
+ /* indicate PLMN selection T timeout */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_HPLMN_SEARCH);
+ if (!nmsg)
+ return;
+ gsm322_plmn_sendmsg(plmn->ms, nmsg);
+}
+
+/* start plmn search timer */
+static void start_plmn_timer(struct gsm322_plmn *plmn, int secs)
+{
+ LOGP(DPLMN, LOGL_INFO, "Starting HPLMN search timer with %d minutes.\n",
+ secs / 60);
+ plmn->timer.cb = plmn_timer_timeout;
+ plmn->timer.data = plmn;
+ bsc_schedule_timer(&plmn->timer, secs, 0);
+}
+
+/* stop plmn search timer */
+static void stop_plmn_timer(struct gsm322_plmn *plmn)
+{
+ if (bsc_timer_pending(&plmn->timer)) {
+ LOGP(DPLMN, LOGL_INFO, "Stopping pending timer.\n");
+ bsc_del_timer(&plmn->timer);
+ }
+}
+
+/* start cell selection timer */
+static void start_cs_timer(struct gsm322_cellsel *cs, int sec, int micro)
+{
+ LOGP(DCS, LOGL_INFO, "Starting CS timer with %d seconds.\n", sec);
+ cs->timer.cb = gsm322_cs_timeout;
+ cs->timer.data = cs;
+ bsc_schedule_timer(&cs->timer, sec, micro);
+}
+
+/* stop cell selection timer */
+static void stop_cs_timer(struct gsm322_cellsel *cs)
+{
+ if (bsc_timer_pending(&cs->timer)) {
+ LOGP(DCS, LOGL_INFO, "stopping pending CS timer.\n");
+ bsc_del_timer(&cs->timer);
+ }
+}
+
+/*
+ * state change
+ */
+
+static const char *plmn_a_state_names[] = {
+ "A0_NULL",
+ "A1_TRYING_RPLMN",
+ "A2_ON_PLMN",
+ "A3_TRYING_PLMN",
+ "A4_WAIT_FOR_PLMN",
+ "A5_HPLMN",
+ "A6_NO_SIM"
+};
+
+static const char *plmn_m_state_names[] = {
+ "M1_NULL",
+ "M1_TRYING_RPLMN",
+ "M2_ON_PLMN",
+ "M3_NOT_ON_PLMN",
+ "M4_TRYING_PLMN",
+ "M5_NO_SIM"
+};
+
+static const char *cs_state_names[] = {
+ "C0_NULL",
+ "C1_NORMAL_CELL_SEL",
+ "C2_STORED_CELL_SEL",
+ "C3_CAMPED_NORMALLY",
+ "C4_NORMAL_CELL_RESEL",
+ "C5_CHOOSE_CELL",
+ "C6_ANY_CELL_SEL",
+ "C7_CAMPED_ANY_CELL",
+ "C8_ANY_CELL_RESEL",
+ "C9_CHOOSE_ANY_CELL",
+ "HPLMN_SEARCH"
+};
+
+
+/* new automatic PLMN search state */
+static void new_a_state(struct gsm322_plmn *plmn, int state)
+{
+ if (plmn->mode != PLMN_MODE_AUTO) {
+ LOGP(DPLMN, LOGL_FATAL, "not in auto mode, please fix!\n");
+ return;
+ }
+
+ stop_plmn_timer(plmn);
+
+ if (state < 0 || state >= (sizeof(plmn_a_state_names) / sizeof(char *)))
+ return;
+
+ LOGP(DPLMN, LOGL_INFO, "new state %s -> %s\n",
+ plmn_a_state_names[plmn->state], plmn_a_state_names[state]);
+
+ plmn->state = state;
+}
+
+/* new manual PLMN search state */
+static void new_m_state(struct gsm322_plmn *plmn, int state)
+{
+ if (plmn->mode != PLMN_MODE_MANUAL) {
+ LOGP(DPLMN, LOGL_FATAL, "not in manual mode, please fix!\n");
+ return;
+ }
+
+ if (state < 0 || state >= (sizeof(plmn_m_state_names) / sizeof(char *)))
+ return;
+
+ LOGP(DPLMN, LOGL_INFO, "new state %s -> %s\n",
+ plmn_m_state_names[plmn->state], plmn_m_state_names[state]);
+
+ plmn->state = state;
+}
+
+/* new Cell selection state */
+static void new_c_state(struct gsm322_cellsel *cs, int state)
+{
+ if (state < 0 || state >= (sizeof(cs_state_names) / sizeof(char *)))
+ return;
+
+ LOGP(DCS, LOGL_INFO, "new state %s -> %s\n",
+ cs_state_names[cs->state], cs_state_names[state]);
+
+ /* stop cell selection timer, if running */
+ stop_cs_timer(cs);
+
+ /* stop scanning of power measurement */
+#ifdef TODO
+ if (cs->powerscan)
+ stop power scanning
+#endif
+
+ cs->state = state;
+}
+
+/*
+ * list of PLMNs
+ */
+
+/* 4.4.3 create sorted list of PLMN
+ *
+ * the source of entries are
+ *
+ * - HPLMN
+ * - entries found in the SIM's PLMN Selector list
+ * - scanned PLMNs above -85 dB (random order)
+ * - scanned PLMNs below or equal -85 (by received level)
+ *
+ * NOTE:
+ *
+ * The list only includes networks found at last scan.
+ *
+ * The list always contains HPLMN if available, even if not used by PLMN
+ * search process at some conditions.
+ *
+ * The list contains all PLMNs even if not allowed, so entries have to be
+ * removed when selecting from the list. (In case we use manual cell selection,
+ * we need to provide non-allowed networks also.)
+ */
+static int gsm322_sort_list(struct osmocom_ms *ms)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm_sub_plmn_list *sim_entry;
+ struct gsm_sub_plmn_na *na_entry;
+ struct llist_head temp_list;
+ struct gsm322_plmn_list *temp, *found;
+ struct llist_head *lh, *lh2;
+ int i, entries, move;
+ int8_t search_db = 0;
+
+ /* flush list */
+ llist_for_each_safe(lh, lh2, &plmn->sorted_plmn) {
+ llist_del(lh);
+ talloc_free(lh);
+ }
+
+ /* Create a temporary list of all networks */
+ INIT_LLIST_HEAD(&temp_list);
+ for (i = 0; i <= 1023; i++) {
+ if (!(cs->list[i].flags & GSM322_CS_FLAG_TEMP_AA))
+ continue;
+
+ /* search if network has multiple cells */
+ found = NULL;
+ llist_for_each_entry(temp, &temp_list, entry) {
+ if (temp->mcc == cs->list[i].mcc
+ && temp->mnc == cs->list[i].mnc)
+ found = temp;
+ break;
+ }
+ /* update or create */
+ if (found) {
+ if (cs->list[i].rxlev_db > found->rxlev_db)
+ found->rxlev_db = cs->list[i].rxlev_db;
+ } else {
+ temp = talloc_zero(l23_ctx, struct gsm322_plmn_list);
+ if (!temp)
+ return -ENOMEM;
+ temp->mcc = cs->list[i].mcc;
+ temp->mnc = cs->list[i].mnc;
+ temp->rxlev_db = cs->list[i].rxlev_db;
+ llist_add_tail(&temp->entry, &temp_list);
+ }
+ }
+
+ /* move Home PLMN, if in list */
+ if (subscr->sim_valid) {
+ found = NULL;
+ llist_for_each_entry(temp, &temp_list, entry) {
+ if (temp->mcc == subscr->mcc
+ && temp->mnc == subscr->mnc) {
+ found = temp;
+ break;
+ }
+ }
+ if (found) {
+ llist_del(&found->entry);
+ llist_add_tail(&found->entry, &plmn->sorted_plmn);
+ }
+ }
+
+ /* move entries if in SIM's PLMN Selector list */
+ llist_for_each_entry(sim_entry, &subscr->plmn_list, entry) {
+ found = NULL;
+ llist_for_each_entry(temp, &temp_list, entry) {
+ if (temp->mcc == sim_entry->mcc
+ && temp->mnc == sim_entry->mnc) {
+ found = temp;
+ break;
+ }
+ }
+ if (found) {
+ llist_del(&found->entry);
+ llist_add_tail(&found->entry, &plmn->sorted_plmn);
+ }
+ }
+
+ /* move PLMN above -85 dBm in random order */
+ entries = 0;
+ llist_for_each_entry(temp, &temp_list, entry) {
+ if (temp->rxlev_db > -85)
+ entries++;
+ }
+ while(entries) {
+ move = random() % entries;
+ i = 0;
+ llist_for_each_entry(temp, &temp_list, entry) {
+ if (temp->rxlev_db > -85) {
+ if (i == move) {
+ llist_del(&temp->entry);
+ llist_add_tail(&temp->entry,
+ &plmn->sorted_plmn);
+ break;
+ }
+ i++;
+ }
+ }
+ entries--;
+ }
+
+ /* move ohter PLMN in decreasing order */
+ while(1) {
+ found = NULL;
+ llist_for_each_entry(temp, &temp_list, entry) {
+ if (!found
+ || temp->rxlev_db > search_db) {
+ search_db = temp->rxlev_db;
+ found = temp;
+ }
+ }
+ if (!found)
+ break;
+ llist_del(&found->entry);
+ llist_add_tail(&found->entry, &plmn->sorted_plmn);
+ }
+
+ /* mark forbidden PLMNs, if in list of forbidden networks */
+ i = 0;
+ llist_for_each_entry(temp, &plmn->sorted_plmn, entry) {
+ llist_for_each_entry(na_entry, &subscr->plmn_na, entry) {
+ if (temp->mcc == na_entry->mcc
+ && temp->mnc == na_entry->mnc) {
+ temp->cause = na_entry->cause;
+ break;
+ }
+ }
+ LOGP(DPLMN, LOGL_INFO, "Crating Sorted PLMN list. "
+ "(%02d: mcc=%03d mnc=%02d allowed=%s rx-lev=%d)\n",
+ i, temp->mcc, temp->mnc, (temp->cause) ? "no ":"yes",
+ temp->rxlev_db);
+ i++;
+ }
+
+ return 0;
+}
+
+/*
+ * handler for automatic search
+ */
+
+/* go On PLMN state */
+static int gsm322_a_go_on_plmn(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm_subscriber *subscr = &ms->subscr;
+
+ /* set last registered PLMN */
+ subscr->plmn_valid = 1;
+ subscr->plmn_mcc = plmn->mcc;
+ subscr->plmn_mnc = plmn->mnc;
+#ifdef TODO
+ store on sim
+#endif
+
+ new_a_state(plmn, GSM322_A2_ON_PLMN);
+
+ /* start timer, if on VPLMN of home country OR special case */
+ if ((plmn->mcc == subscr->mcc && plmn->mcc != subscr->mnc)
+ || (subscr->always_search_hplmn && (plmn->mcc != subscr->mnc
+ || plmn->mcc != subscr->mnc))) {
+ if (subscr->sim_valid && subscr->t6m_hplmn)
+ start_plmn_timer(plmn, subscr->t6m_hplmn * 360);
+ else
+ start_plmn_timer(plmn, 30 * 360);
+ } else
+ stop_plmn_timer(plmn);
+
+ return 0;
+}
+
+/* indicate selected PLMN */
+static int gsm322_a_indicate_selected(struct osmocom_ms *ms, struct msgb *msg)
+{
+#ifdef TODO
+ indicate selected plmn to user
+#endif
+
+ return gsm322_a_go_on_plmn(ms, msg);
+}
+
+/* no (more) PLMN in list */
+static int gsm322_a_no_more_plmn(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct msgb *nmsg;
+ int found;
+
+ /* any PLMN available */
+ found = gsm322_cs_select(ms, 0);
+
+ /* if no PLMN in list */
+ if (found < 0) {
+ if (subscr->plmn_valid) {
+ LOGP(DPLMN, LOGL_INFO, "Select RPLMN.\n");
+ plmn->mcc = subscr->plmn_mcc;
+ plmn->mnc = subscr->plmn_mnc;
+ } else {
+ LOGP(DPLMN, LOGL_INFO, "Select HPLMN.\n");
+ plmn->mcc = subscr->mcc;
+ plmn->mnc = subscr->mnc;
+ }
+
+ new_a_state(plmn, GSM322_A4_WAIT_FOR_PLMN);
+
+ /* we must forward this, otherwhise "Any cell selection"
+ * will not start automatically.
+ */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_NO_CELL_FOUND);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_cs_sendmsg(ms, nmsg);
+
+ return 0;
+ }
+
+ /* select first PLMN in list */
+ plmn->mcc = cs->list[found].mcc;
+ plmn->mnc = cs->list[found].mnc;
+
+ LOGP(DPLMN, LOGL_INFO, "PLMN available (mcc=%03d mnc=%02d)\n",
+ plmn->mcc, plmn->mnc);
+
+ /* indicate New PLMN */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_cs_sendmsg(ms, nmsg);
+
+ /* go On PLMN */
+ return gsm322_a_indicate_selected(ms, msg);
+}
+
+/* select first PLMN in list */
+static int gsm322_a_sel_first_plmn(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct msgb *nmsg;
+ struct gsm322_plmn_list *plmn_entry;
+ struct gsm322_plmn_list *plmn_first = NULL;
+ int i;
+
+ /* generate list */
+ gsm322_sort_list(ms);
+
+ /* select first entry */
+ i = 0;
+ llist_for_each_entry(plmn_entry, &plmn->sorted_plmn, entry) {
+ /* if RPLMN is HPLMN, we skip that */
+ if (plmn->state == GSM322_A1_TRYING_RPLMN
+ && plmn_entry->mcc == plmn->mcc
+ && plmn_entry->mnc == plmn->mnc) {
+ i++;
+ continue;
+ }
+ /* select first allowed network */
+ if (!plmn_entry->cause) {
+ plmn_first = plmn_entry;
+ break;
+ }
+ i++;
+ }
+ plmn->plmn_curr = i;
+
+ /* if no PLMN in list */
+ if (plmn_first) {
+ LOGP(DPLMN, LOGL_INFO, "No PLMN in list.\n");
+ gsm322_a_no_more_plmn(ms, msg);
+
+ return 0;
+ }
+
+ LOGP(DPLMN, LOGL_INFO, "Selecting PLMN from list. (%02d: mcc=%03d "
+ "mnc=%02d\n", plmn->plmn_curr, plmn_first->mcc,
+ plmn_first->mnc);
+
+ /* set current network */
+ plmn->mcc = plmn_first->mcc;
+ plmn->mnc = plmn_first->mnc;
+
+ new_a_state(plmn, GSM322_A3_TRYING_PLMN);
+
+ /* indicate New PLMN */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_cs_sendmsg(ms, nmsg);
+
+ return 0;
+}
+
+/* select next PLMN in list */
+static int gsm322_a_sel_next_plmn(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct msgb *nmsg;
+ struct gsm322_plmn_list *plmn_entry;
+ struct gsm322_plmn_list *plmn_next = NULL;
+ int i, ii;
+
+ /* select next entry from list */
+ i = 0;
+ ii = plmn->plmn_curr + 1;
+ llist_for_each_entry(plmn_entry, &plmn->sorted_plmn, entry) {
+ /* skip previously selected networks */
+ if (i < ii) {
+ i++;
+ continue;
+ }
+ /* select next allowed network */
+ if (!plmn_entry->cause) {
+ plmn_next = plmn_entry;
+ break;
+ }
+ i++;
+ }
+ plmn->plmn_curr = i;
+
+ /* if no more PLMN in list */
+ if (!plmn_next) {
+ LOGP(DPLMN, LOGL_INFO, "No more PLMN in list.\n");
+ gsm322_a_no_more_plmn(ms, msg);
+ return 0;
+ }
+
+
+ LOGP(DPLMN, LOGL_INFO, "Selecting PLMN from list. (%02d: mcc=%03d "
+ "mnc=%02d\n", plmn->plmn_curr, plmn_next->mcc,
+ plmn_next->mnc);
+
+ /* set next network */
+ plmn->mcc = plmn_next->mcc;
+ plmn->mnc = plmn_next->mnc;
+
+ new_a_state(plmn, GSM322_A3_TRYING_PLMN);
+
+ /* indicate New PLMN */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_cs_sendmsg(ms, nmsg);
+
+ return 0;
+}
+
+/* User re-selection event */
+static int gsm322_a_user_reselection(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm322_plmn_list *plmn_entry;
+ struct gsm322_plmn_list *plmn_found = NULL;
+
+ /* search current PLMN in list */
+ llist_for_each_entry(plmn_entry, &plmn->sorted_plmn, entry) {
+ if (plmn_entry->mcc == plmn->mcc
+ && plmn_entry->mnc == plmn->mnc)
+ plmn_found = plmn_entry;
+ break;
+ }
+
+ /* abort if list is empty */
+ if (!plmn_found) {
+ LOGP(DPLMN, LOGL_INFO, "Selected PLMN not in list, strange!\n");
+ return 0;
+ }
+
+ LOGP(DPLMN, LOGL_INFO, "Movin selected PLMN to the bottom of the list "
+ "and restarting PLMN search process.\n");
+
+ /* move entry to end of list */
+ llist_del(&plmn_found->entry);
+ llist_add_tail(&plmn_found->entry, &plmn->sorted_plmn);
+
+ /* select first PLMN in list */
+ return gsm322_a_sel_first_plmn(ms, msg);
+}
+
+/* PLMN becomes available */
+static int gsm322_a_plmn_avail(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
+
+ if (subscr->plmn_valid && subscr->plmn_mcc == gm->mcc
+ && subscr->plmn_mnc == gm->mnc) {
+ /* go On PLMN */
+ plmn->mcc = gm->mcc;
+ plmn->mnc = gm->mnc;
+ LOGP(DPLMN, LOGL_INFO, "HPLMN became available.\n");
+ return gsm322_a_go_on_plmn(ms, msg);
+ } else {
+ /* select first PLMN in list */
+ LOGP(DPLMN, LOGL_INFO, "PLMN became available, start PLMN "
+ "search process.\n");
+ return gsm322_a_sel_first_plmn(ms, msg);
+ }
+}
+
+/* loss of radio coverage */
+static int gsm322_a_loss_of_radio(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ int found;
+
+ /* any PLMN available */
+ found = gsm322_cs_select(ms, 0);
+
+ /* if PLMN in list */
+ if (found >= 0) {
+ LOGP(DPLMN, LOGL_INFO, "PLMN available (mcc=%03d mnc=%02d)\n",
+ cs->list[found].mcc, cs->list[found].mnc);
+ return gsm322_a_sel_first_plmn(ms, msg);
+ }
+
+ LOGP(DPLMN, LOGL_INFO, "PLMN not available.\n");
+
+ plmn->mcc = plmn->mnc = 0;
+
+ new_a_state(plmn, GSM322_A4_WAIT_FOR_PLMN);
+
+ return 0;
+}
+
+/* MS is switched on OR SIM is inserted OR removed */
+static int gsm322_a_switch_on(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct msgb *nmsg;
+
+ if (!subscr->sim_valid) {
+ LOGP(DPLMN, LOGL_INFO, "Switch on without SIM.\n");
+ new_a_state(plmn, GSM322_A6_NO_SIM);
+
+ return 0;
+ }
+
+ /* if there is a registered PLMN */
+ if (subscr->plmn_valid) {
+ /* select the registered PLMN */
+ plmn->mcc = subscr->plmn_mcc;
+ plmn->mnc = subscr->plmn_mnc;
+
+ LOGP(DPLMN, LOGL_INFO, "Use RPLMN (mcc=%03d mnc=%02d)\n",
+ plmn->mcc, plmn->mnc);
+
+ new_a_state(plmn, GSM322_A1_TRYING_RPLMN);
+
+ /* indicate New PLMN */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_cs_sendmsg(ms, nmsg);
+
+ return 0;
+ }
+
+ /* select first PLMN in list */
+ return gsm322_a_sel_first_plmn(ms, msg);
+}
+
+/* MS is switched off */
+static int gsm322_a_switch_off(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+
+ new_a_state(plmn, GSM322_A0_NULL);
+
+ return 0;
+}
+
+/* SIM is removed */
+static int gsm322_a_sim_removed(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct msgb *nmsg;
+
+ /* indicate SIM remove to cell selection process */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_SIM_REMOVE);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_cs_sendmsg(ms, nmsg);
+
+ return gsm322_a_switch_on(ms, msg);
+}
+
+/* location update response: "Roaming not allowed" */
+static int gsm322_a_roaming_na(struct osmocom_ms *ms, struct msgb *msg)
+{
+ /* store in list of forbidden LAs is done in gsm48* */
+
+ return gsm322_a_sel_first_plmn(ms, msg);
+}
+
+/* On VPLMN of home country and timeout occurs */
+static int gsm322_a_hplmn_search(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct msgb *nmsg;
+
+ /* try again later, if not idle */
+ if (cs->state != GSM322_C3_CAMPED_NORMALLY) {
+ LOGP(DPLMN, LOGL_INFO, "Not camping normal, wait some more.\n");
+ start_plmn_timer(plmn, 60);
+
+ return 0;
+ }
+
+ new_a_state(plmn, GSM322_A5_HPLMN_SEARCH);
+
+ /* initiate search at cell selection */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_HPLMN_SEARCH);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_cs_sendmsg(ms, nmsg);
+
+ return 0;
+}
+
+/* manual mode selected */
+static int gsm322_a_sel_manual(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+
+ /* restart state machine */
+ gsm322_a_switch_off(ms, msg);
+ plmn->mode = PLMN_MODE_MANUAL;
+ gsm322_m_switch_on(ms, msg);
+
+ return 0;
+}
+
+/*
+ * handler for manual search
+ */
+
+/* go Not on PLMN state */
+static int gsm322_m_go_not_on_plmn(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+
+ new_m_state(plmn, GSM322_M3_NOT_ON_PLMN);
+
+ return 0;
+}
+
+/* display PLMNs and to Not on PLMN */
+static int gsm322_m_display_plmns(struct osmocom_ms *ms, struct msgb *msg)
+{
+ /* generate list */
+ gsm322_sort_list(ms);
+
+#ifdef TODO
+ display PLMNs to user
+#endif
+
+ /* go Not on PLMN state */
+ return gsm322_m_go_not_on_plmn(ms, msg);
+}
+
+/* MS is switched on OR SIM is inserted OR removed */
+static int gsm322_m_switch_on(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm322_plmn *plmn = &ms->plmn;
+
+ if (!subscr->sim_valid) {
+ LOGP(DPLMN, LOGL_INFO, "Switch on without SIM.\n");
+ new_m_state(plmn, GSM322_M5_NO_SIM);
+
+ return 0;
+ }
+
+ /* if there is a registered PLMN */
+ if (subscr->plmn_valid) {
+ /* select the registered PLMN */
+ plmn->mcc = subscr->plmn_mcc;
+ plmn->mnc = subscr->plmn_mnc;
+
+ LOGP(DPLMN, LOGL_INFO, "Use RPLMN (mcc=%03d mnc=%02d)\n",
+ plmn->mcc, plmn->mnc);
+
+ new_m_state(plmn, GSM322_M1_TRYING_RPLMN);
+
+ return 0;
+ }
+
+ /* display PLMNs */
+ return gsm322_m_display_plmns(ms, msg);
+}
+
+/* MS is switched off */
+static int gsm322_m_switch_off(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+
+ stop_plmn_timer(plmn);
+
+ new_m_state(plmn, GSM322_M0_NULL);
+
+ return 0;
+}
+
+/* SIM is removed */
+static int gsm322_m_sim_removed(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct msgb *nmsg;
+
+ stop_plmn_timer(plmn);
+
+ /* indicate SIM remove to cell selection process */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_SIM_REMOVE);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_cs_sendmsg(ms, nmsg);
+
+ return gsm322_m_switch_on(ms, msg);
+}
+
+/* go to On PLMN state */
+static int gsm322_m_go_on_plmn(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm_subscriber *subscr = &ms->subscr;
+
+ /* if selected PLMN is in list of forbidden PLMNs */
+ gsm322_del_forbidden_plmn(ms, plmn->mcc, plmn->mnc);
+
+ /* set last registered PLMN */
+ subscr->plmn_valid = 1;
+ subscr->plmn_mcc = plmn->mcc;
+ subscr->plmn_mnc = plmn->mnc;
+#ifdef TODO
+ store on sim
+#endif
+
+ new_m_state(plmn, GSM322_M2_ON_PLMN);
+
+ return 0;
+}
+
+/* indicate selected PLMN */
+static int gsm322_m_indicate_selected(struct osmocom_ms *ms, struct msgb *msg)
+{
+#ifdef TODO
+ indicate selected plmn to user
+#endif
+
+ return gsm322_m_go_on_plmn(ms, msg);
+}
+
+/* previously selected PLMN becomes available again */
+static int gsm322_m_plmn_avail(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+
+ new_m_state(plmn, GSM322_M1_TRYING_RPLMN);
+
+ return 0;
+}
+
+/* the user has selected given PLMN */
+static int gsm322_m_choose_plmn(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
+
+ /* use user selection */
+ plmn->mcc = gm->mcc;
+ plmn->mnc = gm->mnc;
+
+ LOGP(DPLMN, LOGL_INFO, "User selects PLMN. (mcc=%03d mnc=%02d)\n",
+ plmn->mcc, plmn->mnc);
+
+ new_m_state(plmn, GSM322_M4_TRYING_PLMN);
+
+ return 0;
+}
+
+/* auto mode selected */
+static int gsm322_m_sel_auto(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+
+ /* restart state machine */
+ gsm322_m_switch_off(ms, msg);
+ plmn->mode = PLMN_MODE_AUTO;
+ gsm322_a_switch_on(ms, msg);
+
+ return 0;
+}
+
+/* if no cell is found in other states than in *_TRYING_* states */
+static int gsm322_am_no_cell_found(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct msgb *nmsg;
+
+ /* Tell cell selection process to handle "no cell found". */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_NO_CELL_FOUND);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_cs_sendmsg(ms, nmsg);
+
+ return 0;
+}
+
+/*
+ * cell scanning process
+ */
+
+/* select a suitable and allowable cell */
+static int gsm322_cs_select(struct osmocom_ms *ms, int any)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ int i, found = -1, power = 0;
+ uint8_t flags, mask;
+ uint16_t acc_class;
+
+ /* set out access class depending on the cell selection type */
+ if (any) {
+ acc_class = subscr->acc_class | 0x0400; /* add emergency */
+ LOGP(DCS, LOGL_INFO, "Using access class with Emergency "
+ "class.\n");
+ } else {
+ acc_class = subscr->acc_class & 0xfbff; /* remove emergency */
+ LOGP(DCS, LOGL_INFO, "Using access class without Emergency "
+ "class\n");
+ }
+
+ /* flags to match */
+ mask = GSM322_CS_FLAG_SUPPORT | GSM322_CS_FLAG_POWER
+ | GSM322_CS_FLAG_SIGNAL | GSM322_CS_FLAG_SYSINFO;
+ if (cs->state == GSM322_C2_STORED_CELL_SEL)
+ mask |= GSM322_CS_FLAG_BA;
+ flags = mask; /* all masked flags are requied */
+
+ /* loop through all scanned frequencies and select cell */
+ for (i = 0; i <= 1023; i++) {
+ cs->list[i].flags &= ~GSM322_CS_FLAG_TEMP_AA;
+
+ /* channel has no informations for us */
+ if ((cs->list[i].flags & mask) != flags) {
+ continue;
+ }
+
+ /* check C1 criteria not fullfilled */
+ // TODO: C1 is also dependant on power class and max power
+ if (cs->list[i].rxlev_db < cs->list[i].min_db) {
+ LOGP(DCS, LOGL_INFO, "Skip frequency %d: C1 criteria "
+ "not met. (rxlev=%d < min=%d)\n", i,
+ cs->list[i].rxlev_db, cs->list[i].min_db);
+ continue;
+ }
+
+ /* if cell is barred and we don't override */
+ if (!subscr->acc_barr
+ && (cs->list[i].flags & GSM322_CS_FLAG_BARRED)) {
+ LOGP(DCS, LOGL_INFO, "Skip frequency %d: Cell is "
+ "barred.\n", i);
+ continue;
+ }
+
+ /* if cell is in list of forbidden LAs */
+ if (!subscr->acc_barr
+ && (cs->list[i].flags & GSM322_CS_FLAG_FORBIDD)) {
+ LOGP(DCS, LOGL_INFO, "Skip frequency %d: Cell is in "
+ "list of forbidden LAs. (mcc=%03d mnc=%02d "
+ "lai=%04x)\n", i, cs->list[i].mcc,
+ cs->list[i].mnc, cs->list[i].lac);
+ continue;
+ }
+
+ /* if we have no access to the cell and we don't override */
+ if (!subscr->acc_barr
+ && !(acc_class & (cs->list[i].class_barr ^ 0xffff))) {
+ LOGP(DCS, LOGL_INFO, "Skip frequency %d: Class is "
+ "barred for out access. (access=%04x "
+ "barred=%04x)\n", i, acc_class,
+ cs->list[i].class_barr);
+ continue;
+ }
+
+ /* store temporary available and allowable flag */
+ cs->list[i].flags |= GSM322_CS_FLAG_TEMP_AA;
+
+ /* if we search a specific PLMN, but it does not match */
+ if (!any && (cs->mcc != cs->list[i].mcc
+ || cs->mnc != cs->list[i].mnc)) {
+ LOGP(DCS, LOGL_INFO, "Skip frequency %d: PLMN of cell "
+ "does not match target PLMN. (mcc=%03d "
+ "mnc=%02d)\n", i, cs->list[i].mcc,
+ cs->list[i].mnc);
+ continue;
+ }
+
+ LOGP(DCS, LOGL_INFO, "Cell frequency %d: Cell found, (rxlev=%d "
+ "mcc=%03d mnc=%02d lac=%04x)\n", i,
+ cs->list[i].rxlev_db, cs->list[i].mcc, cs->list[i].mnc,
+ cs->list[i].lac);
+
+ /* find highest power cell */
+ if (found < 0 || cs->list[i].rxlev_db > power) {
+ power = cs->list[i].rxlev_db;
+ found = i;
+ }
+ }
+
+ if (found >= 0)
+ LOGP(DCS, LOGL_INFO, "Cell frequency %d selected.\n", found);
+
+ return found;
+}
+
+/* tune to first/next unscanned frequency and search for PLMN */
+static int gsm322_cs_scan(struct osmocom_ms *ms)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ int i;
+ uint8_t mask, flags;
+ uint32_t max = 0, weight = cs->scan_state;
+
+ if (rr->state != GSM48_RR_ST_IDLE) {
+ LOGP(DCS, LOGL_FATAL, "This must only happen in IDLE mode, "
+ "please fix!\n");
+ return -EINVAL;
+ }
+
+ /* special prositive case for HPLMN search */
+ if (cs->state == GSM322_HPLMN_SEARCH && s->mcc == subscr->mcc
+ && s->mnc == subscr->mnc) {
+ struct msgb *nmsg;
+
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_HPLMN_FOUND);
+ LOGP(DCS, LOGL_INFO, "HPLMN cell available.\n");
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+
+ return 0;
+ }
+
+ /* search for strongest unscanned cell */
+ mask = GSM322_CS_FLAG_SUPPORT | GSM322_CS_FLAG_POWER
+ | GSM322_CS_FLAG_SIGNAL;
+ if (cs->state == GSM322_C2_STORED_CELL_SEL)
+ mask |= GSM322_CS_FLAG_BA;
+ flags = mask; /* all masked flags are requied */
+ for (i = 0; i <= 1023; i++) {
+ if ((cs->list[i].flags & mask) == flags) {
+ /* weight depends on the power level
+ * if it is the same, it depends on arfcn
+ */
+ weight = cs->list[i].rxlev_db + 128;
+ weight = (weight << 16) | i;
+ if (weight >= cs->scan_state)
+ continue;
+ if (weight > max)
+ max = weight;
+ }
+ }
+ cs->scan_state = weight;
+
+ /* special negative case for HPLMN search */
+ if (cs->state == GSM322_HPLMN_SEARCH && !weight) {
+ struct msgb *nmsg;
+
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_HPLMN_NOT_FOUND);
+ LOGP(DCS, LOGL_INFO, "No HPLMN cell available.\n");
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+
+ /* re-tune back to current VPLMN */
+ l1ctl_tx_ccch_req(ms, cs->arfcn);
+
+ return 0;
+ }
+
+ /* if all frequencies have been searched */
+ if (!weight) {
+ struct msgb *nmsg;
+ int found, any;
+
+ LOGP(DCS, LOGL_INFO, "All frequencies scanned.\n");
+
+ /* just see, if we search for any cell */
+ if (cs->state == GSM322_C6_ANY_CELL_SEL
+ || cs->state == GSM322_C8_ANY_CELL_RESEL
+ || cs->state == GSM322_C9_CHOOSE_ANY_CELL)
+ any = 1;
+
+ found = gsm322_cs_select(ms, any);
+
+ /* if found */
+ if (found >= 0) {
+ LOGP(DCS, LOGL_INFO, "Tune to frequency %d.\n", found);
+ /* tune */
+ cs->arfcn = found;
+ l1ctl_tx_ccch_req(ms, cs->arfcn);
+
+ /* Clear system information. */
+ gsm48_sysinfo_init(ms);
+
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_CELL_FOUND);
+ } else {
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_NO_CELL_FOUND);
+ LOGP(DCS, LOGL_INFO, "No cell available.\n");
+ }
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+
+ return 0;
+ }
+
+ /* NOTE: We might already have system information from previous
+ * scan. But we need recent informations, so we scan again!
+ */
+
+ /* Tune to frequency for a while, to receive broadcasts. */
+ cs->arfcn = weight & 1023;
+ LOGP(DCS, LOGL_INFO, "Scanning frequency %d.\n", cs->arfcn);
+ l1ctl_tx_ccch_req(ms, cs->arfcn);
+
+ /* Clear system information. */
+ gsm48_sysinfo_init(ms);
+
+ /* set timer for reading BCCH */
+ start_cs_timer(cs, 4, 0); // TODO: timer depends on BCCH config
+
+ return 0;
+}
+
+/* check if cell is now suitable and allowable */
+static int gsm322_cs_store(struct osmocom_ms *ms)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+ int i = cs->scan_state & 1023;
+
+ if (cs->state != GSM322_C2_STORED_CELL_SEL
+ && cs->state != GSM322_C1_NORMAL_CELL_SEL
+ && cs->state != GSM322_C6_ANY_CELL_SEL
+ && cs->state != GSM322_C4_NORMAL_CELL_RESEL
+ && cs->state != GSM322_C8_ANY_CELL_RESEL
+ && cs->state != GSM322_C5_CHOOSE_CELL
+ && cs->state != GSM322_C9_CHOOSE_ANY_CELL) {
+ LOGP(DCS, LOGL_FATAL, "This must only happen during cell "
+ "(re-)selection, please fix!\n");
+ return -EINVAL;
+ }
+
+ /* store sysinfo */
+ cs->list[i].flags |= GSM322_CS_FLAG_SYSINFO;
+ if (s->cell_barr)
+ cs->list[i].flags |= GSM322_CS_FLAG_BARRED;
+ else
+ cs->list[i].flags &= ~GSM322_CS_FLAG_BARRED;
+ cs->list[i].min_db = s->rxlev_acc_min_db;
+ cs->list[i].class_barr = s->class_barr;
+ cs->list[i].max_pwr = s->ms_txpwr_max_ccch;
+
+ /* store selected network */
+ if (s->mcc) {
+ cs->list[i].mcc = s->mcc;
+ cs->list[i].mnc = s->mnc;
+ cs->list[i].lac = s->lac;
+
+ if (gsm322_is_forbidden_la(ms, s->mcc, s->mnc, s->lac))
+ cs->list[i].flags |= GSM322_CS_FLAG_FORBIDD;
+ else
+ cs->list[i].flags &= ~GSM322_CS_FLAG_FORBIDD;
+ }
+
+ LOGP(DCS, LOGL_INFO, "Scan frequency %d: Cell found. (rxlev=%d "
+ "mcc=%03d mnc=%02d lac=%04x)\n", i, cs->list[i].rxlev_db,
+ cs->list[i].mcc, cs->list[i].mnc, cs->list[i].lac);
+
+ /* tune to next cell */
+ return gsm322_cs_scan(ms);
+}
+
+/* process system information when returing to idle mode */
+struct gsm322_ba_list *gsm322_cs_sysinfo_sacch(struct osmocom_ms *ms)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+ struct gsm322_ba_list *ba = NULL;
+ int i;
+ uint8_t freq[128];
+
+ /* collect system information received during dedicated mode */
+ if (s->si5
+ && (!s->nb_ext_ind_si5
+ || (s->si5bis && s->nb_ext_ind_si5 && !s->nb_ext_ind_si5bis)
+ || (s->si5bis && s->si5ter && s->nb_ext_ind_si5
+ && s->nb_ext_ind_si5bis))) {
+ /* find or create ba list */
+ ba = gsm322_find_ba_list(cs, s->mcc, s->mnc);
+ if (!ba) {
+ ba = talloc_zero(l23_ctx, struct gsm322_ba_list);
+ if (!ba)
+ return NULL;
+ ba->mcc = s->mcc;
+ ba->mnc = s->mnc;
+ llist_add_tail(&ba->entry, &cs->ba_list);
+ }
+ /* update (add) ba list */
+ memcpy(freq, ba->freq, sizeof(freq));
+ for (i = 0; i <= 1023; i++) {
+ if ((s->freq[i].mask & FREQ_TYPE_REP))
+ freq[i >> 3] |= (1 << (i & 7));
+ }
+ if (!!memcmp(freq, ba->freq, sizeof(freq))) {
+ LOGP(DCS, LOGL_INFO, "New BA list (mcc=%d mnc=%d).\n",
+ ba->mcc, ba->mnc);
+ memcpy(ba->freq, freq, sizeof(freq));
+ }
+ }
+
+ return ba;
+}
+
+/* process system information during camping on a cell */
+static int gsm322_c_camp_sysinfo_bcch(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
+ struct msgb *nmsg;
+
+ if (gm->sysinfo == GSM48_MT_RR_SYSINFO_1) {
+ /* check if cell becomes barred */
+ if (!subscr->acc_barr && s->cell_barr) {
+ LOGP(DCS, LOGL_INFO, "Cell becomes barred.\n");
+ trigger_resel:
+ /* mark cell as unscanned */
+ cs->list[cs->arfcn].flags &= ~GSM322_CS_FLAG_SYSINFO;
+ /* trigger reselection event */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_CELL_RESEL);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_cs_sendmsg(ms, nmsg);
+
+ return 0;
+ }
+ /* check if cell access becomes barred */
+ if (!((subscr->acc_class & 0xfbff)
+ & (cs->list[cs->arfcn].class_barr ^ 0xffff))) {
+ LOGP(DCS, LOGL_INFO, "Cell access becomes barred.\n");
+ goto trigger_resel;
+ }
+ }
+
+ return 0;
+}
+
+/* process system information during channel scanning */
+static int gsm322_c_scan_sysinfo_bcch(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+ struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
+ struct gsm322_ba_list *ba;
+ int i;
+ uint8_t freq[128];
+
+ if (rr->state != GSM48_RR_ST_IDLE) {
+ LOGP(DCS, LOGL_FATAL, "This must only happen in IDLE mode, "
+ "please fix!\n");
+ return -EINVAL;
+ }
+
+ /* no sysinfo if we are not done with power scan */
+ if (cs->powerscan) {
+ LOGP(DCS, LOGL_INFO, "Ignoring sysinfo during power scan.\n");
+ return -EINVAL;
+ }
+
+ /* Store BA if we have full system info about cells and neigbor cells.
+ * Depending on the extended bit in the channel description,
+ * we require more or less system informations about neighbor cells
+ */
+ if (s->mcc
+ && s->mnc
+ && (gm->sysinfo == GSM48_MT_RR_SYSINFO_1
+ || gm->sysinfo == GSM48_MT_RR_SYSINFO_2
+ || gm->sysinfo == GSM48_MT_RR_SYSINFO_2bis
+ || gm->sysinfo == GSM48_MT_RR_SYSINFO_2ter)
+ && s->si1
+ && s->si2
+ && (!s->nb_ext_ind_si2
+ || (s->si2bis && s->nb_ext_ind_si2 && !s->nb_ext_ind_si2bis)
+ || (s->si2bis && s->si2ter && s->nb_ext_ind_si2
+ && s->nb_ext_ind_si2bis))) {
+ /* find or create ba list */
+ ba = gsm322_find_ba_list(cs, s->mcc, s->mnc);
+ if (!ba) {
+ ba = talloc_zero(l23_ctx, struct gsm322_ba_list);
+ if (!ba)
+ return -ENOMEM;
+ ba->mcc = s->mcc;
+ ba->mnc = s->mnc;
+ llist_add_tail(&ba->entry, &cs->ba_list);
+ }
+ /* update ba list */
+ memset(freq, 0, sizeof(freq));
+ freq[cs->arfcn >> 3] |= (1 << (cs->arfcn & 7));
+ for (i = 0; i <= 1023; i++) {
+ if ((s->freq[i].mask &
+ (FREQ_TYPE_SERV | FREQ_TYPE_NCELL)))
+ freq[i >> 3] |= (1 << (i & 7));
+ }
+ if (!!memcmp(freq, ba->freq, sizeof(freq))) {
+ LOGP(DCS, LOGL_INFO, "New BA list (mcc=%d mnc=%d).\n",
+ ba->mcc, ba->mnc);
+ memcpy(ba->freq, freq, sizeof(freq));
+ }
+ }
+
+ /* all relevant system informations received */
+ if (s->si1 && s->si2 && s->si3
+ && (!s->nb_ext_ind_si2
+ || (s->si2bis && s->nb_ext_ind_si2 && !s->nb_ext_ind_si2bis)
+ || (s->si2bis && s->si2ter && s->nb_ext_ind_si2
+ && s->nb_ext_ind_si2bis))) {
+ /* stop timer */
+ stop_cs_timer(cs);
+
+ /* store sysinfo and continue scan */
+ return gsm322_cs_store(ms);
+ }
+
+ /* wait for more sysinfo or timeout */
+ return 0;
+}
+
+static void gsm322_cs_timeout(void *arg)
+{
+ struct gsm322_cellsel *cs = arg;
+ struct osmocom_ms *ms = cs->ms;
+ int i = cs->scan_state & 1023;
+
+ LOGP(DCS, LOGL_INFO, "Cell selection timer has fired.\n");
+ LOGP(DCS, LOGL_INFO, "Scan frequency %d: Cell not found. (rxlev=%d)\n",
+ i, cs->list[i].rxlev_db);
+
+ /* remove system information */
+ cs->list[i].flags &= ~GSM322_CS_FLAG_SYSINFO;
+
+ /* tune to next cell */
+ gsm322_cs_scan(ms);
+
+ return;
+}
+
+/*
+ * power scan process
+ */
+
+/* search for block of unscanned freqeuncies and start scanning */
+static int gsm322_cs_powerscan(struct osmocom_ms *ms)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ int i, s = -1, e;
+ uint8_t mask, flags;
+
+ if (rr->state != GSM48_RR_ST_IDLE) {
+ LOGP(DCS, LOGL_FATAL, "This must only happen in IDLE mode, "
+ "please fix!\n");
+ return -EINVAL;
+ }
+
+ again:
+
+ /* search for first frequency to scan */
+ mask = GSM322_CS_FLAG_SUPPORT | GSM322_CS_FLAG_POWER;
+ flags = GSM322_CS_FLAG_SUPPORT;
+ if (cs->state == GSM322_C2_STORED_CELL_SEL) {
+ mask |= GSM322_CS_FLAG_BA;
+ flags |= GSM322_CS_FLAG_BA;
+ }
+ for (i = 0; i <= 1023; i++) {
+ if ((cs->list[i].flags & mask) == flags) {
+ s = e = i;
+ break;
+ }
+ }
+
+ /* if there is no more frequency, we can tune to that cell */
+ if (s < 0) {
+ int found = 0;
+
+ /* stop power level scanning */
+ cs->powerscan = 0;
+
+ /* check if not signal is found */
+ for (i = 0; i <= 1023; i++) {
+ if ((cs->list[i].flags & GSM322_CS_FLAG_SIGNAL))
+ found++;
+ }
+ if (!found) {
+ struct msgb *nmsg;
+
+ LOGP(DCS, LOGL_INFO, "Found no frequency.\n");
+ /* on normal cell selection, start over */
+ if (cs->state == GSM322_C1_NORMAL_CELL_SEL) {
+ for (i = 0; i <= 1023; i++) {
+ /* clear flag that this was scanned */
+ cs->list[i].flags &=
+ ~(GSM322_CS_FLAG_POWER
+ | GSM322_CS_FLAG_SIGNAL
+ | GSM322_CS_FLAG_SYSINFO);
+ }
+ goto again;
+ }
+ /* on other cell selection, indicate "no cell found" */
+ /* NOTE: PLMN search process handles it.
+ * If not handled there, CS process gets indicated.
+ * If we would continue to process CS, then we might get
+ * our list of scanned cells disturbed.
+ */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_NO_CELL_FOUND);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+
+ return 0;
+ }
+ LOGP(DCS, LOGL_INFO, "Found %d frequencies.\n", found);
+ cs->scan_state = 0xffffffff; /* higher than high */
+ return gsm322_cs_scan(ms);
+ }
+
+ /* search last frequency to scan (en block) */
+ e = i;
+ for (i = s + 1; i <= 1023; i++) {
+ if ((cs->list[i].flags & mask) == flags)
+ e = i;
+ else
+ break;
+ }
+
+ LOGP(DCS, LOGL_INFO, "Scanning frequecies. (%d..%d)\n", s, e);
+
+#ifdef TODO
+ start scan on radio interface
+
+ cs->powerscan = 1;
+
+ also stop on state change
+
+#else
+ printf("scan not supported... exitting\n");
+ exit(-1);
+#endif
+}
+
+#ifdef TODO
+{
+
+ LOGP(DCS, LOGL_INFO, "Found frequency %d.\n", i);
+
+ /* set power scan flag */
+ cs->list[i].flags |= GSM322_CS_FLAG_POWER;
+ if (...)
+ cs->list[i].flags |= GSM322_CS_FLAG_SIGNAL;
+}
+#endif
+
+/*
+ * handler for cell selection process
+ */
+
+/* start HPLMN search */
+static int gsm322_c_hplmn_search(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ int i;
+
+ new_c_state(cs, GSM322_HPLMN_SEARCH);
+
+ /* mark all frequencies except our own BA to be scanned */
+ for (i = 0; i <= 1023; i++) {
+ if ((cs->list[i].flags & GSM322_CS_FLAG_SYSINFO)
+ && !(cs->list[i].flags & GSM322_CS_FLAG_BA))
+ cs->list[i].flags &= ~(GSM322_CS_FLAG_POWER
+ | GSM322_CS_FLAG_SIGNAL
+ | GSM322_CS_FLAG_SYSINFO);
+ }
+
+ /* start power scan */
+ return gsm322_cs_powerscan(ms);
+}
+
+/* start stored cell selection */
+static int gsm322_c_stored_cell_sel(struct osmocom_ms *ms, struct gsm322_ba_list *ba)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ int i;
+
+ new_c_state(cs, GSM322_C2_STORED_CELL_SEL);
+
+ /* flag all frequencies that are in current band allocation */
+ for (i = 0; i <= 1023; i++) {
+ if ((ba->freq[i >> 3] & (1 << (i & 7))))
+ cs->list[i].flags |= GSM322_CS_FLAG_BA;
+ else
+ cs->list[i].flags &= ~GSM322_CS_FLAG_BA;
+ }
+
+#ifdef TODO
+ remove this:
+#else
+ /* use hacked frequency */
+ cs->list[ms->test_arfcn].flags |= GSM322_CS_FLAG_POWER;
+ cs->list[ms->test_arfcn].flags |= GSM322_CS_FLAG_SIGNAL;
+ cs->list[ms->test_arfcn].rxlev_db = -40;
+#endif
+
+ /* start power scan */
+ return gsm322_cs_powerscan(ms);
+}
+
+/* start noraml cell selection */
+static int gsm322_c_normal_cell_sel(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ int i;
+
+ /* except for stored cell selection state, we weed to rescan ?? */
+ if (cs->state != GSM322_C2_STORED_CELL_SEL) {
+ for (i = 0; i <= 1023; i++)
+ cs->list[i].flags &= ~(GSM322_CS_FLAG_POWER
+ | GSM322_CS_FLAG_SIGNAL
+ | GSM322_CS_FLAG_SYSINFO);
+ }
+
+ new_c_state(cs, GSM322_C1_NORMAL_CELL_SEL);
+
+#ifdef TODO
+ remove this:
+#else
+ /* use hacked frequency */
+ cs->list[ms->test_arfcn].flags |= GSM322_CS_FLAG_POWER;
+ cs->list[ms->test_arfcn].flags |= GSM322_CS_FLAG_SIGNAL;
+ cs->list[ms->test_arfcn].rxlev_db = -40;
+#endif
+
+ /* start power scan */
+ return gsm322_cs_powerscan(ms);
+}
+
+/* start any cell selection */
+static int gsm322_c_any_cell_sel(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ int i;
+
+ /* in case we already tried any cell selection, power scan again */
+ if (cs->state == GSM322_C6_ANY_CELL_SEL) {
+ for (i = 0; i <= 1023; i++)
+ cs->list[i].flags &= ~(GSM322_CS_FLAG_POWER
+ | GSM322_CS_FLAG_SIGNAL
+ | GSM322_CS_FLAG_SYSINFO);
+ } else {
+ new_c_state(cs, GSM322_C6_ANY_CELL_SEL);
+ }
+
+ cs->mcc = cs->mnc = 0;
+
+#ifdef TODO
+ remove this:
+#else
+ /* use hacked frequency */
+ cs->list[ms->test_arfcn].flags |= GSM322_CS_FLAG_POWER;
+ cs->list[ms->test_arfcn].flags |= GSM322_CS_FLAG_SIGNAL;
+ cs->list[ms->test_arfcn].rxlev_db = -40;
+#endif
+
+ /* start power scan */
+ return gsm322_cs_powerscan(ms);
+}
+
+/* start noraml cell re-selection */
+static int gsm322_c_normal_cell_resel(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+
+ new_c_state(cs, GSM322_C4_NORMAL_CELL_RESEL);
+
+ /* NOTE: We keep our scan info we have so far.
+ * This may cause a skip in power scan. */
+
+#ifdef TODO
+ remove this:
+#else
+ /* use hacked frequency */
+ cs->list[ms->test_arfcn].flags |= GSM322_CS_FLAG_POWER;
+ cs->list[ms->test_arfcn].flags |= GSM322_CS_FLAG_SIGNAL;
+ cs->list[ms->test_arfcn].rxlev_db = -40;
+#endif
+
+ /* start power scan */
+ return gsm322_cs_powerscan(ms);
+}
+
+/* start any cell re-selection */
+static int gsm322_c_any_cell_resel(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+
+ new_c_state(cs, GSM322_C8_ANY_CELL_RESEL);
+
+ /* NOTE: We keep our scan info we have so far.
+ * This may cause a skip in power scan. */
+
+#ifdef TODO
+ remove this:
+#else
+ /* use hacked frequency */
+ cs->list[ms->test_arfcn].flags |= GSM322_CS_FLAG_POWER;
+ cs->list[ms->test_arfcn].flags |= GSM322_CS_FLAG_SIGNAL;
+ cs->list[ms->test_arfcn].rxlev_db = -40;
+#endif
+
+ /* start power scan */
+ return gsm322_cs_powerscan(ms);
+}
+
+/* create temporary ba range with given frequency ranges */
+struct gsm322_ba_list *gsm322_cs_ba_range(struct osmocom_ms *ms,
+ uint32_t *range, uint8_t ranges)
+{
+ static struct gsm322_ba_list ba;
+ uint16_t lower, higher;
+
+ memset(&ba, 0, sizeof(ba));
+
+ while(ranges--) {
+ lower = *range & 1023;
+ higher = (*range >> 16) & 1023;
+ range++;
+ LOGP(DCS, LOGL_INFO, "Use BA range: %d..%d\n", lower, higher);
+ /* GSM 05.08 6.3 */
+ while (1) {
+ ba.freq[lower >> 3] |= 1 << (lower & 7);
+ if (lower == higher)
+ break;
+ lower = (lower + 1) & 1023;
+ }
+ }
+
+ return &ba;
+}
+
+/* common part of gsm322_c_choose_cell and gsm322_c_choose_any_cell */
+static int gsm322_cs_choose(struct osmocom_ms *ms)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm322_ba_list *ba = NULL;
+ int i;
+
+#ifdef TODO
+ if (message->ranges)
+ ba = gsm322_cs_ba_range(ms, message->range, message->ranges);
+ else {
+ LOGP(DCS, LOGL_INFO, "No BA range(s), try sysinfo.\n");
+#endif
+ /* get and update BA of last received sysinfo 5* */
+ ba = gsm322_cs_sysinfo_sacch(ms);
+#ifdef TODO
+ }
+#endif
+
+ if (!ba) {
+ struct msgb *nmsg;
+
+ LOGP(DCS, LOGL_INFO, "No BA list.\n");
+
+ /* tell CS to start over */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_NO_CELL_FOUND);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_cs_sendmsg(ms, nmsg);
+ }
+
+ /* flag all frequencies that are in current band allocation */
+ for (i = 0; i <= 1023; i++) {
+ if (cs->state == GSM322_C5_CHOOSE_CELL) {
+ if ((ba->freq[i >> 3] & (1 << (i & 7))))
+ cs->list[i].flags |= GSM322_CS_FLAG_BA;
+ else
+ cs->list[i].flags &= ~GSM322_CS_FLAG_BA;
+ }
+ cs->list[i].flags &= ~(GSM322_CS_FLAG_POWER
+ | GSM322_CS_FLAG_SIGNAL
+ | GSM322_CS_FLAG_SYSINFO);
+ }
+#ifdef TODO
+ remove this:
+#else
+ /* use hacked frequency */
+ cs->list[ms->test_arfcn].flags |= GSM322_CS_FLAG_POWER;
+ cs->list[ms->test_arfcn].flags |= GSM322_CS_FLAG_SIGNAL;
+ cs->list[ms->test_arfcn].rxlev_db = -40;
+#endif
+
+ /* start power scan */
+ return gsm322_cs_powerscan(ms);
+}
+
+/* start 'Choose cell' after returning to idle mode */
+static int gsm322_c_choose_cell(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+
+ new_c_state(cs, GSM322_C5_CHOOSE_CELL);
+
+ return gsm322_cs_choose(ms);
+}
+
+/* start 'Choose any cell' after returning to idle mode */
+static int gsm322_c_choose_any_cell(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+
+ new_c_state(cs, GSM322_C9_CHOOSE_ANY_CELL);
+
+ return gsm322_cs_choose(ms);
+}
+
+/* a new PLMN is selected by PLMN search process */
+static int gsm322_c_new_plmn(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm322_ba_list *ba;
+
+ cs->mcc = plmn->mcc;
+ cs->mnc = plmn->mnc;
+
+ /* search for BA list */
+ ba = gsm322_find_ba_list(cs, plmn->mcc, plmn->mnc);
+
+ if (ba) {
+ LOGP(DCS, LOGL_INFO, "Start stored cell selection.\n");
+ return gsm322_c_stored_cell_sel(ms, ba);
+ } else {
+ LOGP(DCS, LOGL_INFO, "Start normal cell selection.\n");
+ return gsm322_c_normal_cell_sel(ms, msg);
+ }
+}
+
+/* a suitable cell was found, so we camp normally */
+static int gsm322_c_camp_normally(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct msgb *nmsg;
+
+ nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_NEW_LAI);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm48_mmevent_msg(ms, nmsg);
+
+ new_c_state(cs, GSM322_C3_CAMPED_NORMALLY);
+
+ return 0;
+}
+
+/* a not suitable cell was found, so we camp on any cell */
+static int gsm322_c_camp_any_cell(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+
+ new_c_state(cs, GSM322_C7_CAMPED_ANY_CELL);
+
+ return 0;
+}
+
+/* go connected mode */
+static int gsm322_c_conn_mode_1(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+
+ /* stop camping process */
+
+ /* be sure to go to current camping frequency on return */
+ LOGP(DCS, LOGL_INFO, "Going to camping frequency %d.\n", cs->arfcn);
+ l1ctl_tx_ccch_req(ms, cs->arfcn);
+
+ return 0;
+}
+
+static int gsm322_c_conn_mode_2(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+
+ /* stop camping process */
+
+ /* be sure to go to current camping frequency on return */
+ LOGP(DCS, LOGL_INFO, "Going to camping frequency %d.\n", cs->arfcn);
+ l1ctl_tx_ccch_req(ms, cs->arfcn);
+
+ return 0;
+}
+
+/* switch on */
+static int gsm322_c_switch_on(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+
+ /* if no SIM is is MS */
+ if (!subscr->sim_valid)
+ LOGP(DCS, LOGL_INFO, "Switch on without SIM.\n");
+ return gsm322_c_any_cell_sel(ms, msg);
+
+ /* stay in NULL state until PLMN is selected */
+
+ return 0;
+}
+
+/*
+ * state machines
+ */
+
+/* state machine for automatic PLMN selection events */
+static struct plmnastatelist {
+ uint32_t states;
+ int type;
+ int (*rout) (struct osmocom_ms *ms, struct msgb *msg);
+} plmnastatelist[] = {
+ {SBIT(GSM322_A0_NULL),
+ GSM322_EVENT_SWITCH_ON, gsm322_a_switch_on},
+ {ALL_STATES,
+ GSM322_EVENT_SWITCH_OFF, gsm322_a_switch_off},
+ {SBIT(GSM322_A6_NO_SIM),
+ GSM322_EVENT_SIM_INSERT, gsm322_a_switch_on},
+ {ALL_STATES,
+ GSM322_EVENT_SIM_REMOVE, gsm322_a_sim_removed},
+ {ALL_STATES,
+ GSM322_EVENT_INVALID_SIM, gsm322_a_sim_removed},
+ {SBIT(GSM322_A1_TRYING_RPLMN),
+ GSM322_EVENT_REG_FAILED, gsm322_a_sel_first_plmn},
+ {SBIT(GSM322_A1_TRYING_RPLMN),
+ GSM322_EVENT_NO_CELL_FOUND, gsm322_a_sel_first_plmn},
+ {SBIT(GSM322_A1_TRYING_RPLMN) | SBIT(GSM322_A3_TRYING_PLMN),
+ GSM322_EVENT_REG_SUCCESS, gsm322_a_indicate_selected},
+ {SBIT(GSM322_A2_ON_PLMN),
+ GSM322_EVENT_ROAMING_NA, gsm322_a_roaming_na},
+ {SBIT(GSM322_A2_ON_PLMN),
+ GSM322_EVENT_HPLMN_SEARCH, gsm322_a_hplmn_search},
+ {SBIT(GSM322_A2_ON_PLMN),
+ GSM322_EVENT_NO_CELL_FOUND, gsm322_a_loss_of_radio},
+ {SBIT(GSM322_A2_ON_PLMN),
+ GSM322_EVENT_USER_RESEL, gsm322_a_user_reselection},
+ {SBIT(GSM322_A3_TRYING_PLMN),
+ GSM322_EVENT_REG_FAILED, gsm322_a_sel_next_plmn},
+ {SBIT(GSM322_A3_TRYING_PLMN),
+ GSM322_EVENT_NO_CELL_FOUND, gsm322_a_sel_next_plmn},
+ {SBIT(GSM322_A5_HPLMN_SEARCH),
+ GSM322_EVENT_HPLMN_FOUND, gsm322_a_sel_first_plmn},
+ {SBIT(GSM322_A5_HPLMN_SEARCH),
+ GSM322_EVENT_HPLMN_NOT_FOUND, gsm322_a_go_on_plmn},
+ {SBIT(GSM322_A4_WAIT_FOR_PLMN),
+ GSM322_EVENT_PLMN_AVAIL, gsm322_a_plmn_avail},
+ {ALL_STATES,
+ GSM322_EVENT_SEL_MANUAL, gsm322_a_sel_manual},
+ {ALL_STATES,
+ GSM322_EVENT_NO_CELL_FOUND, gsm322_am_no_cell_found},
+};
+
+#define PLMNASLLEN \
+ (sizeof(plmnastatelist) / sizeof(struct plmnastatelist))
+
+static int gsm322_a_event(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
+ int msg_type = gm->msg_type;
+ int rc;
+ int i;
+
+ LOGP(DPLMN, LOGL_INFO, "(ms %s) Event '%s' for automatic PLMN "
+ "selection in state %s\n", ms->name, get_event_name(msg_type),
+ plmn_a_state_names[plmn->state]);
+ /* find function for current state and message */
+ for (i = 0; i < PLMNASLLEN; i++)
+ if ((msg_type == plmnastatelist[i].type)
+ && ((1 << plmn->state) & plmnastatelist[i].states))
+ break;
+ if (i == PLMNASLLEN) {
+ LOGP(DPLMN, LOGL_NOTICE, "Event unhandled at this state.\n");
+ return 0;
+ }
+
+ rc = plmnastatelist[i].rout(ms, msg);
+
+ return rc;
+}
+
+/* state machine for manual PLMN selection events */
+static struct plmnmstatelist {
+ uint32_t states;
+ int type;
+ int (*rout) (struct osmocom_ms *ms, struct msgb *msg);
+} plmnmstatelist[] = {
+ {SBIT(GSM322_M0_NULL),
+ GSM322_EVENT_SWITCH_ON, gsm322_m_switch_on},
+ {ALL_STATES,
+ GSM322_EVENT_SWITCH_OFF, gsm322_m_switch_off},
+ {SBIT(GSM322_M5_NO_SIM),
+ GSM322_EVENT_SIM_INSERT, gsm322_m_switch_on},
+ {ALL_STATES,
+ GSM322_EVENT_SIM_REMOVE, gsm322_m_sim_removed},
+ {SBIT(GSM322_M1_TRYING_RPLMN),
+ GSM322_EVENT_REG_FAILED, gsm322_m_display_plmns},
+ {SBIT(GSM322_M1_TRYING_RPLMN),
+ GSM322_EVENT_NO_CELL_FOUND, gsm322_m_display_plmns},
+ {SBIT(GSM322_M1_TRYING_RPLMN),
+ GSM322_EVENT_REG_SUCCESS, gsm322_m_indicate_selected},
+ {SBIT(GSM322_M2_ON_PLMN),
+ GSM322_EVENT_ROAMING_NA, gsm322_m_display_plmns},
+ {SBIT(GSM322_M1_TRYING_RPLMN) | SBIT(GSM322_M2_ON_PLMN) |
+ SBIT(GSM322_M4_TRYING_PLMN),
+ GSM322_EVENT_INVALID_SIM, gsm322_m_sim_removed},
+ {SBIT(GSM322_M2_ON_PLMN),
+ GSM322_EVENT_USER_RESEL, gsm322_m_display_plmns},
+ {SBIT(GSM322_M3_NOT_ON_PLMN),
+ GSM322_EVENT_PLMN_AVAIL, gsm322_m_plmn_avail},
+ {SBIT(GSM322_M3_NOT_ON_PLMN),
+ GSM322_EVENT_CHOSE_PLMN, gsm322_m_choose_plmn},
+ {SBIT(GSM322_M4_TRYING_PLMN),
+ GSM322_EVENT_REG_SUCCESS, gsm322_m_go_on_plmn},
+ {SBIT(GSM322_M4_TRYING_PLMN),
+ GSM322_EVENT_REG_FAILED, gsm322_m_go_not_on_plmn},
+ {SBIT(GSM322_M4_TRYING_PLMN),
+ GSM322_EVENT_NO_CELL_FOUND, gsm322_m_go_not_on_plmn},
+ {ALL_STATES,
+ GSM322_EVENT_SEL_AUTO, gsm322_m_sel_auto},
+ {ALL_STATES,
+ GSM322_EVENT_NO_CELL_FOUND, gsm322_am_no_cell_found},
+};
+
+#define PLMNMSLLEN \
+ (sizeof(plmnmstatelist) / sizeof(struct plmnmstatelist))
+
+static int gsm322_m_event(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
+ int msg_type = gm->msg_type;
+ int rc;
+ int i;
+
+ LOGP(DPLMN, LOGL_INFO, "(ms %s) Event '%s' for manual PLMN selection "
+ "in state %s\n", ms->name, get_event_name(msg_type),
+ plmn_m_state_names[plmn->state]);
+ /* find function for current state and message */
+ for (i = 0; i < PLMNMSLLEN; i++)
+ if ((msg_type == plmnmstatelist[i].type)
+ && ((1 << plmn->state) & plmnmstatelist[i].states))
+ break;
+ if (i == PLMNMSLLEN) {
+ LOGP(DPLMN, LOGL_NOTICE, "Event unhandled at this state.\n");
+ return 0;
+ }
+
+ rc = plmnmstatelist[i].rout(ms, msg);
+
+ return rc;
+}
+
+/* dequeue GSM 03.22 PLMN events */
+int gsm322_plmn_dequeue(struct osmocom_ms *ms)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct msgb *msg;
+ int work = 0;
+
+ while ((msg = msgb_dequeue(&plmn->event_queue))) {
+ /* send event to PLMN select process */
+ if (plmn->mode == PLMN_MODE_AUTO)
+ gsm322_a_event(ms, msg);
+ else
+ gsm322_m_event(ms, msg);
+ msgb_free(msg);
+ work = 1; /* work done */
+ }
+
+ return work;
+}
+
+/* state machine for channel selection events */
+static struct cellselstatelist {
+ uint32_t states;
+ int type;
+ int (*rout) (struct osmocom_ms *ms, struct msgb *msg);
+} cellselstatelist[] = {
+ {ALL_STATES,
+ GSM322_EVENT_SWITCH_ON, gsm322_c_switch_on},
+ {ALL_STATES,
+ GSM322_EVENT_SIM_REMOVE, gsm322_c_any_cell_sel},
+ {ALL_STATES,
+ GSM322_EVENT_NEW_PLMN, gsm322_c_new_plmn},
+ {SBIT(GSM322_C1_NORMAL_CELL_SEL) | SBIT(GSM322_C2_STORED_CELL_SEL) |
+ SBIT(GSM322_C4_NORMAL_CELL_RESEL) | SBIT(GSM322_C5_CHOOSE_CELL),
+ GSM322_EVENT_CELL_FOUND, gsm322_c_camp_normally},
+ {SBIT(GSM322_C9_CHOOSE_ANY_CELL) | SBIT(GSM322_C6_ANY_CELL_SEL) |
+ SBIT(GSM322_C4_NORMAL_CELL_RESEL),
+ GSM322_EVENT_CELL_FOUND, gsm322_c_camp_any_cell},
+ {SBIT(GSM322_C1_NORMAL_CELL_SEL) | SBIT(GSM322_C6_ANY_CELL_SEL) |
+ SBIT(GSM322_C9_CHOOSE_ANY_CELL) | SBIT(GSM322_C8_ANY_CELL_RESEL),
+ GSM322_EVENT_NO_CELL_FOUND, gsm322_c_any_cell_sel},
+ {SBIT(GSM322_C2_STORED_CELL_SEL) | SBIT(GSM322_C5_CHOOSE_CELL) |
+ SBIT(GSM322_C4_NORMAL_CELL_RESEL),
+ GSM322_EVENT_NO_CELL_FOUND, gsm322_c_normal_cell_sel},
+ {SBIT(GSM322_C3_CAMPED_NORMALLY),
+ GSM322_EVENT_LEAVE_IDLE, gsm322_c_conn_mode_1},
+ {SBIT(GSM322_C7_CAMPED_ANY_CELL),
+ GSM322_EVENT_LEAVE_IDLE, gsm322_c_conn_mode_2},
+ {SBIT(GSM322_C3_CAMPED_NORMALLY),
+ GSM322_EVENT_RET_IDLE, gsm322_c_choose_cell},
+ {SBIT(GSM322_C7_CAMPED_ANY_CELL),
+ GSM322_EVENT_RET_IDLE, gsm322_c_choose_any_cell},
+ {SBIT(GSM322_C3_CAMPED_NORMALLY),
+ GSM322_EVENT_CELL_RESEL, gsm322_c_normal_cell_resel},
+ {SBIT(GSM322_C7_CAMPED_ANY_CELL),
+ GSM322_EVENT_CELL_RESEL, gsm322_c_any_cell_resel},
+ {SBIT(GSM322_C7_CAMPED_ANY_CELL),
+ GSM322_EVENT_CELL_FOUND, gsm322_c_normal_cell_sel},
+ {SBIT(GSM322_C1_NORMAL_CELL_SEL) | SBIT(GSM322_C2_STORED_CELL_SEL) |
+ SBIT(GSM322_C4_NORMAL_CELL_RESEL) | SBIT(GSM322_C5_CHOOSE_CELL) |
+ SBIT(GSM322_C9_CHOOSE_ANY_CELL) | SBIT(GSM322_C8_ANY_CELL_RESEL),
+ GSM322_EVENT_SYSINFO, gsm322_c_scan_sysinfo_bcch},
+ {SBIT(GSM322_C3_CAMPED_NORMALLY),
+ GSM322_EVENT_SYSINFO, gsm322_c_camp_sysinfo_bcch},
+ {SBIT(GSM322_C3_CAMPED_NORMALLY),
+ GSM322_EVENT_HPLMN_SEARCH, gsm322_c_hplmn_search}
+};
+
+#define CELLSELSLLEN \
+ (sizeof(cellselstatelist) / sizeof(struct cellselstatelist))
+
+int gsm322_c_event(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
+ int msg_type = gm->msg_type;
+ int rc;
+ int i;
+
+ LOGP(DCS, LOGL_INFO, "(ms %s) Event '%s' for Cell selection in state "
+ "%s\n", ms->name, get_event_name(msg_type),
+ cs_state_names[cs->state]);
+ /* find function for current state and message */
+ for (i = 0; i < CELLSELSLLEN; i++)
+ if ((msg_type == cellselstatelist[i].type)
+ && ((1 << cs->state) & cellselstatelist[i].states))
+ break;
+ if (i == CELLSELSLLEN) {
+ LOGP(DPLMN, LOGL_NOTICE, "Event unhandled at this state.\n");
+ return 0;
+ }
+
+ rc = cellselstatelist[i].rout(ms, msg);
+
+ return rc;
+}
+
+/* dequeue GSM 03.22 cell selection events */
+int gsm322_cs_dequeue(struct osmocom_ms *ms)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct msgb *msg;
+ int work = 0;
+
+ while ((msg = msgb_dequeue(&cs->event_queue))) {
+ /* send event to cell selection process */
+ gsm322_c_event(ms, msg);
+ msgb_free(msg);
+ work = 1; /* work done */
+ }
+
+ return work;
+}
+
+/*
+ * dump lists
+ */
+
+int gsm322_dump_sorted_plmn(struct osmocom_ms *ms)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm322_plmn_list *temp;
+
+ printf("MCC |MNC |allowed|rx-lev\n");
+ printf("-------+-------+-------+-------\n");
+ llist_for_each_entry(temp, &plmn->sorted_plmn, entry) {
+ printf("%03d |%02d |%s |%d\n", temp->mcc, temp->mnc,
+ (temp->cause) ? "no ":"yes", temp->rxlev_db);
+ }
+
+ return 0;
+}
+
+int gsm322_dump_cs_list(struct osmocom_ms *ms)
+{
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ int i, j;
+
+ printf("rx-lev |MCC |MNC |forb.LA|barred,0123456789abcdef|"
+ "min-db |max-pwr\n"
+ "-------+-------+-------+-------+-----------------------+"
+ "-------+-------\n");
+ for (i = 0; i <= 1023; i++) {
+ if (!(cs->list[i].flags & GSM322_CS_FLAG_SIGNAL))
+ continue;
+ printf("%4d |", cs->list[i].rxlev_db);
+ if ((cs->list[i].flags & GSM322_CS_FLAG_SYSINFO)) {
+ printf("%03d |%02d |", cs->list[i].mcc,
+ cs->list[i].mnc);
+ if ((cs->list[i].flags & GSM322_CS_FLAG_FORBIDD))
+ printf("yes |");
+ else
+ printf("no |");
+ if ((cs->list[i].flags & GSM322_CS_FLAG_BARRED))
+ printf("yes ");
+ else
+ printf("no ");
+ for (j = 0; j < 16; j++) {
+ if ((cs->list[i].class_barr & (1 << j)))
+ printf("*");
+ else
+ printf(" ");
+ }
+ printf("|%4d |%4d", cs->list[i].min_db,
+ cs->list[i].max_pwr);
+ } else
+ printf("- |- | |"
+ "- |-");
+ }
+
+ return 0;
+}
+
+int gsm322_dump_sim_plmn(struct osmocom_ms *ms)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm_sub_plmn_list *temp;
+
+ printf("MCC |MNC\n");
+ printf("-------+-------\n");
+ llist_for_each_entry(temp, &subscr->plmn_list, entry)
+ printf("%03d |%02d\n", temp->mcc, temp->mnc);
+
+ return 0;
+}
+
+int gsm322_dump_forbidden_plmn(struct osmocom_ms *ms)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm_sub_plmn_na *temp;
+
+ printf("MCC |MNC |cause\n");
+ printf("-------+-------+-------\n");
+ llist_for_each_entry(temp, &subscr->plmn_na, entry)
+ printf("%03d |%02d |#%d\n", temp->mcc, temp->mnc,
+ temp->cause);
+
+ return 0;
+}
+
+int gsm322_dump_forbidden_la(struct osmocom_ms *ms)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm322_la_list *temp;
+
+ printf("MCC |MNC |LAC |cause\n");
+ printf("-------+-------+-------+-------\n");
+ llist_for_each_entry(temp, &plmn->forbidden_la, entry)
+ printf("%03d |%02d |0x%04x |#%d\n", temp->mcc, temp->mnc,
+ temp->lac, temp->cause);
+
+ return 0;
+}
+
+/*
+ * initialization
+ */
+
+int gsm322_init(struct osmocom_ms *ms)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ OSMOCOM_FILE *fp;
+ char suffix[] = ".ba";
+ char filename[sizeof(ms->name) + strlen(suffix) + 1];
+// int i;
+ struct gsm322_ba_list *ba;
+ uint8_t buf[4];
+
+ LOGP(DPLMN, LOGL_INFO, "init PLMN process\n");
+ LOGP(DCS, LOGL_INFO, "init Cell Selection process\n");
+
+ memset(plmn, 0, sizeof(*plmn));
+ memset(cs, 0, sizeof(*cs));
+ plmn->ms = ms;
+ cs->ms = ms;
+
+ /* set initial state */
+ plmn->state = 0;
+ cs->state = 0;
+ plmn->mode = PLMN_MODE_AUTO;
+
+ /* init lists */
+ INIT_LLIST_HEAD(&plmn->event_queue);
+ INIT_LLIST_HEAD(&cs->event_queue);
+ INIT_LLIST_HEAD(&plmn->sorted_plmn);
+ INIT_LLIST_HEAD(&plmn->forbidden_la);
+ INIT_LLIST_HEAD(&cs->ba_list);
+
+#ifdef TODO
+ /* set supported frequencies in cell selection list */
+ for (i = 0; i <= 1023; i++)
+ if ((s->freq_map[i >> 3] & (1 << (i & 7))))
+ cs->list[i].flags |= GSM322_CS_FLAG_SUPPORT;
+#else
+ /* set cell selection list to given test frequency only */
+ cs->list[ms->test_arfcn].flags |= GSM322_CS_FLAG_SUPPORT;
+#endif
+
+ /* read BA list */
+ strcpy(filename, ms->name);
+ strcat(filename, suffix);
+ fp = osmocom_fopen(filename, "r");
+ if (fp) {
+ while(!feof(fp)) {
+ ba = talloc_zero(l23_ctx, struct gsm322_ba_list);
+ if (!ba)
+ return -ENOMEM;
+ osmocom_fread(buf, 4, 1, fp);
+ ba->mcc = (buf[0] << 8) | buf[1];
+ ba->mnc = (buf[2] << 8) | buf[3];
+ osmocom_fread(ba->freq, sizeof(ba->freq), 1, fp);
+ llist_add_tail(&ba->entry, &cs->ba_list);
+ LOGP(DPLMN, LOGL_INFO, "Read stored BA list (mcc=%d "
+ "mnc=%d)\n", ba->mcc, ba->mnc);
+ }
+ osmocom_fclose(fp);
+ } else
+ LOGP(DCS, LOGL_NOTICE, "No stored BA list\n");
+
+ return 0;
+}
+
+int gsm322_exit(struct osmocom_ms *ms)
+{
+ struct gsm322_plmn *plmn = &ms->plmn;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct llist_head *lh, *lh2;
+ struct msgb *msg;
+ OSMOCOM_FILE *fp;
+ char suffix[] = ".ba";
+ char filename[sizeof(ms->name) + strlen(suffix) + 1];
+ struct gsm322_ba_list *ba;
+ uint8_t buf[4];
+
+ LOGP(DPLMN, LOGL_INFO, "exit PLMN process\n");
+ LOGP(DCS, LOGL_INFO, "exit Cell Selection process\n");
+
+ /* stop cell selection process (if any) */
+ new_c_state(cs, GSM322_C0_NULL);
+
+ /* stop timers */
+ stop_cs_timer(cs);
+ stop_plmn_timer(plmn);
+
+ /* store BA list */
+ strcpy(filename, ms->name);
+ strcat(filename, suffix);
+ fp = osmocom_fopen(filename, "w");
+ if (fp) {
+ llist_for_each_entry(ba, &cs->ba_list, entry) {
+ buf[0] = ba->mcc >> 8;
+ buf[1] = ba->mcc | 0xff;
+ buf[2] = ba->mnc >> 8;
+ buf[3] = ba->mnc | 0xff;
+ osmocom_fwrite(buf, 4, 1, fp);
+ osmocom_fwrite(ba->freq, sizeof(ba->freq), 1, fp);
+ LOGP(DPLMN, LOGL_INFO, "Write stored BA list (mcc=%d "
+ "mnc=%d)\n", ba->mcc, ba->mnc);
+ }
+ osmocom_fclose(fp);
+ } else
+ LOGP(DCS, LOGL_ERROR, "Failed to write BA list\n");
+
+ /* free lists */
+ while ((msg = msgb_dequeue(&plmn->event_queue)))
+ msgb_free(msg);
+ while ((msg = msgb_dequeue(&cs->event_queue)))
+ msgb_free(msg);
+ llist_for_each_safe(lh, lh2, &plmn->sorted_plmn) {
+ llist_del(lh);
+ talloc_free(lh);
+ }
+ llist_for_each_safe(lh, lh2, &plmn->forbidden_la) {
+ llist_del(lh);
+ talloc_free(lh);
+ }
+ llist_for_each_safe(lh, lh2, &cs->ba_list) {
+ llist_del(lh);
+ talloc_free(lh);
+ }
+ return 0;
+}
+
+
diff --git a/src/host/layer23/src/gsm48_cc.c b/src/host/layer23/src/gsm48_cc.c
new file mode 100644
index 0000000..e79c51a
--- /dev/null
+++ b/src/host/layer23/src/gsm48_cc.c
@@ -0,0 +1,1975 @@
+/*
+ * (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 <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/mncc.h>
+#include <osmocom/transaction.h>
+#include <osmocom/gsm48_cc.h>
+
+extern void *l23_ctx;
+
+static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg);
+static int gsm48_rel_null_free(struct gsm_trans *trans);
+int mncc_release_ind(struct osmocom_ms *ms, struct gsm_trans *trans,
+ u_int32_t callref, int location, int value);
+static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg);
+static int gsm48_cc_tx_connect_ack(struct gsm_trans *trans, void *arg);
+
+/*
+ * init
+ */
+
+int gsm48_cc_init(struct osmocom_ms *ms)
+{
+ struct gsm48_cclayer *cc = &ms->cclayer;
+
+ cc->ms = ms;
+
+ INIT_LLIST_HEAD(&cc->mncc_upqueue);
+
+ return 0;
+}
+
+int gsm48_cc_exit(struct osmocom_ms *ms)
+{
+ struct gsm48_cclayer *cc = &ms->cclayer;
+ struct gsm_trans *trans, *trans2;
+ struct msgb *msg;
+
+ llist_for_each_entry_safe(trans, trans2, &ms->trans_list, entry) {
+ if (trans->protocol == GSM48_PDISC_CC)
+ trans_free(trans);
+ }
+
+ while ((msg = msgb_dequeue(&cc->mncc_upqueue)))
+ msgb_free(msg);
+
+ return 0;
+}
+
+/*
+ * messages
+ */
+
+/* names of MNCC-SAP */
+static const struct value_string gsm_mncc_names[] = {
+ { MNCC_SETUP_REQ, "MNCC_SETUP_REQ" },
+ { MNCC_SETUP_IND, "MNCC_SETUP_IND" },
+ { MNCC_SETUP_RSP, "MNCC_SETUP_RSP" },
+ { MNCC_SETUP_CNF, "MNCC_SETUP_CNF" },
+ { MNCC_SETUP_COMPL_REQ, "MNCC_SETUP_COMPL_REQ" },
+ { MNCC_SETUP_COMPL_IND, "MNCC_SETUP_COMPL_IND" },
+ { MNCC_CALL_CONF_IND, "MNCC_CALL_CONF_IND" },
+ { MNCC_CALL_PROC_REQ, "MNCC_CALL_PROC_REQ" },
+ { MNCC_PROGRESS_REQ, "MNCC_PROGRESS_REQ" },
+ { MNCC_ALERT_REQ, "MNCC_ALERT_REQ" },
+ { MNCC_ALERT_IND, "MNCC_ALERT_IND" },
+ { MNCC_NOTIFY_REQ, "MNCC_NOTIFY_REQ" },
+ { MNCC_NOTIFY_IND, "MNCC_NOTIFY_IND" },
+ { MNCC_DISC_REQ, "MNCC_DISC_REQ" },
+ { MNCC_DISC_IND, "MNCC_DISC_IND" },
+ { MNCC_REL_REQ, "MNCC_REL_REQ" },
+ { MNCC_REL_IND, "MNCC_REL_IND" },
+ { MNCC_REL_CNF, "MNCC_REL_CNF" },
+ { MNCC_FACILITY_REQ, "MNCC_FACILITY_REQ" },
+ { MNCC_FACILITY_IND, "MNCC_FACILITY_IND" },
+ { MNCC_START_DTMF_IND, "MNCC_START_DTMF_IND" },
+ { MNCC_START_DTMF_RSP, "MNCC_START_DTMF_RSP" },
+ { MNCC_START_DTMF_REJ, "MNCC_START_DTMF_REJ" },
+ { MNCC_STOP_DTMF_IND, "MNCC_STOP_DTMF_IND" },
+ { MNCC_STOP_DTMF_RSP, "MNCC_STOP_DTMF_RSP" },
+ { MNCC_MODIFY_REQ, "MNCC_MODIFY_REQ" },
+ { MNCC_MODIFY_IND, "MNCC_MODIFY_IND" },
+ { MNCC_MODIFY_RSP, "MNCC_MODIFY_RSP" },
+ { MNCC_MODIFY_CNF, "MNCC_MODIFY_CNF" },
+ { MNCC_MODIFY_REJ, "MNCC_MODIFY_REJ" },
+ { MNCC_HOLD_IND, "MNCC_HOLD_IND" },
+ { MNCC_HOLD_CNF, "MNCC_HOLD_CNF" },
+ { MNCC_HOLD_REJ, "MNCC_HOLD_REJ" },
+ { MNCC_RETRIEVE_IND, "MNCC_RETRIEVE_IND" },
+ { MNCC_RETRIEVE_CNF, "MNCC_RETRIEVE_CNF" },
+ { MNCC_RETRIEVE_REJ, "MNCC_RETRIEVE_REJ" },
+ { MNCC_USERINFO_REQ, "MNCC_USERINFO_REQ" },
+ { MNCC_USERINFO_IND, "MNCC_USERINFO_IND" },
+ { MNCC_REJ_REQ, "MNCC_REJ_REQ" },
+ { MNCC_REJ_IND, "MNCC_REJ_IND" },
+ { MNCC_PROGRESS_IND, "MNCC_PROGRESS_IND" },
+ { MNCC_CALL_PROC_IND, "MNCC_CALL_PROC_IND" },
+ { MNCC_CALL_CONF_REQ, "MNCC_CALL_CONF_REQ" },
+ { MNCC_START_DTMF_REQ, "MNCC_START_DTMF_REQ" },
+ { MNCC_STOP_DTMF_REQ, "MNCC_STOP_DTMF_REQ" },
+ { MNCC_HOLD_REQ, "MNCC_HOLD_REQ " },
+ { MNCC_RETRIEVE_REQ, "MNCC_RETRIEVE_REQ" },
+ { 0, NULL }
+};
+
+const char *get_mncc_name(int value)
+{
+ return get_value_string(gsm_mncc_names, value);
+}
+
+/* push MMCC header and send to MM */
+static int gsm48_cc_to_mm(struct msgb *msg, struct gsm_trans *trans,
+ int msg_type)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct gsm48_mmxx_hdr *mmh;
+ int emergency = 0;
+
+ /* indicate emergency setup to MM layer */
+ if (gh->msg_type == GSM48_MT_CC_EMERG_SETUP)
+ emergency = 1;
+
+ /* push RR header */
+ msgb_push(msg, sizeof(struct gsm48_mmxx_hdr));
+ mmh = (struct gsm48_mmxx_hdr *)msg->data;
+ mmh->msg_type = msg_type;
+ mmh->ref = trans->callref;
+ mmh->transaction_id = trans->transaction_id;
+ mmh->emergency = emergency;
+
+ /* send message to MM */
+ LOGP(DCC, LOGL_INFO, "Sending '%s' using %s\n",
+ gsm48_cc_msg_name(gh->msg_type), get_mmxx_name(msg_type));
+ return gsm48_mmxx_downmsg(trans->ms, msg);
+}
+
+/* enqueue message to application (MNCC-SAP) */
+static int mncc_recvmsg(struct osmocom_ms *ms, struct gsm_trans *trans,
+ int msg_type, struct gsm_mncc *mncc)
+{
+ struct gsm48_cclayer *cc = &ms->cclayer;
+ struct msgb *msg;
+
+ if (trans)
+ LOGP(DCC, LOGL_INFO, "(ms %s ti %x) Sending '%s' to MNCC.\n",
+ ms->name, trans->transaction_id,
+ get_mncc_name(msg_type));
+ else
+ LOGP(DCC, LOGL_INFO, "(ms %s ti -) Sending '%s' to MNCC.\n",
+ ms->name, get_mncc_name(msg_type));
+
+ mncc->msg_type = msg_type;
+
+ msg = msgb_alloc(sizeof(struct gsm_mncc), "MNCC");
+ if (!msg)
+ return -ENOMEM;
+ memcpy(msg->data, mncc, sizeof(struct gsm_mncc));
+ msgb_enqueue(&cc->mncc_upqueue, msg);
+
+ return 0;
+}
+
+/* dequeue messages to layer 4 */
+int mncc_dequeue(struct osmocom_ms *ms)
+{
+ struct gsm48_cclayer *cc = &ms->cclayer;
+ struct gsm_mncc *mncc;
+ struct msgb *msg;
+ int work = 0;
+
+ while ((msg = msgb_dequeue(&cc->mncc_upqueue))) {
+ mncc = (struct gsm_mncc *)msg->data;
+ if (cc->mncc_recv)
+ cc->mncc_recv(ms, mncc->msg_type, mncc);
+ work = 1; /* work done */
+ talloc_free(msg);
+ }
+
+ return work;
+}
+
+
+/*
+ * state transition
+ */
+
+static void new_cc_state(struct gsm_trans *trans, int state)
+{
+ if (state > 31 || state < 0)
+ return;
+
+ DEBUGP(DCC, "new state %s -> %s\n",
+ gsm48_cc_state_name(trans->cc.state),
+ gsm48_cc_state_name(state));
+
+ trans->cc.state = state;
+}
+
+/*
+ * timers
+ */
+
+/* timeout events of all timers */
+static void gsm48_cc_timeout(void *arg)
+{
+ struct gsm_trans *trans = arg;
+ struct osmocom_ms *ms = trans->ms;
+ int disconnect = 0, release = 0, abort = 1;
+ int mo_cause = GSM48_CC_CAUSE_RECOVERY_TIMER;
+ int mo_location = GSM48_CAUSE_LOC_USER;
+ int l4_cause = GSM48_CC_CAUSE_NORMAL_UNSPEC;
+ int l4_location = GSM48_CAUSE_LOC_USER;
+ struct gsm_mncc mo_rel, l4_rel;
+
+ memset(&mo_rel, 0, sizeof(struct gsm_mncc));
+ mo_rel.callref = trans->callref;
+ memset(&l4_rel, 0, sizeof(struct gsm_mncc));
+ l4_rel.callref = trans->callref;
+
+ LOGP(DCC, LOGL_INFO, "Timer T%x has fired.\n", trans->cc.Tcurrent);
+
+ switch(trans->cc.Tcurrent) {
+ case 0x303:
+ /* abort if connection is not already esablished */
+ if (trans->cc.state == GSM_CSTATE_MM_CONNECTION_PEND)
+ abort = 1;
+ else
+ release = 1;
+ l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+ break;
+ case 0x305:
+ release = 1;
+ mo_cause = trans->cc.msg.cause.value;
+ mo_location = trans->cc.msg.cause.location;
+ break;
+ case 0x308:
+ if (!trans->cc.T308_second) {
+ /* restart T308 a second time */
+ gsm48_cc_tx_release(trans, &trans->cc.msg);
+ trans->cc.T308_second = 1;
+ break; /* stay in release state */
+ }
+ /* release MM conn, got NULL state, free trans */
+ gsm48_rel_null_free(trans);
+
+ return;
+ case 0x310:
+ disconnect = 1;
+ l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+ break;
+ case 0x313:
+ disconnect = 1;
+ /* unknown, did not find it in the specs */
+ break;
+ default:
+ release = 1;
+ }
+
+ if ((release || abort) && trans->callref) {
+ /* process release towards layer 4 */
+ mncc_release_ind(trans->ms, trans, trans->callref,
+ l4_location, l4_cause);
+ trans->callref = 0;
+ }
+
+ if (disconnect && trans->callref) {
+ /* process disconnect towards layer 4 */
+ mncc_set_cause(&l4_rel, l4_location, l4_cause);
+ mncc_recvmsg(trans->ms, trans, MNCC_DISC_IND, &l4_rel);
+ }
+
+ /* process disconnect towards mobile station */
+ if (disconnect || release || abort) {
+ mncc_set_cause(&mo_rel, mo_location, mo_cause);
+ mo_rel.cause.diag[0] =
+ ((trans->cc.Tcurrent & 0xf00) >> 8) + '0';
+ mo_rel.cause.diag[1] =
+ ((trans->cc.Tcurrent & 0x0f0) >> 4) + '0';
+ mo_rel.cause.diag[2] = (trans->cc.Tcurrent & 0x00f) + '0';
+ mo_rel.cause.diag_len = 3;
+
+ if (disconnect)
+ gsm48_cc_tx_disconnect(trans, &mo_rel);
+ if (release)
+ gsm48_cc_tx_release(trans, &mo_rel);
+ if (abort) {
+ struct msgb *nmsg;
+
+ /* abort MM connection */
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_REL_REQ,
+ trans->callref, trans->transaction_id);
+ if (!nmsg)
+ return;
+ LOGP(DCC, LOGL_INFO, "Sending MMCC_REL_REQ\n");
+ gsm48_mmxx_downmsg(ms, nmsg);
+
+ new_cc_state(trans, GSM_CSTATE_NULL);
+
+ /* free trans (impies no callref) */
+ trans_free(trans);
+ }
+ }
+}
+
+/* start various timers */
+static void gsm48_start_cc_timer(struct gsm_trans *trans, int current,
+ int sec, int micro)
+{
+ LOGP(DCC, LOGL_INFO, "starting timer T%x with %d seconds\n", current,
+ sec);
+ trans->cc.timer.cb = gsm48_cc_timeout;
+ trans->cc.timer.data = trans;
+ bsc_schedule_timer(&trans->cc.timer, sec, micro);
+ trans->cc.Tcurrent = current;
+}
+
+/* stop various timers */
+static void gsm48_stop_cc_timer(struct gsm_trans *trans)
+{
+ if (bsc_timer_pending(&trans->cc.timer)) {
+ LOGP(DCC, LOGL_INFO, "stopping pending timer T%x\n",
+ trans->cc.Tcurrent);
+ bsc_del_timer(&trans->cc.timer);
+ trans->cc.Tcurrent = 0;
+ }
+}
+
+/*
+ * process handlers (misc)
+ */
+
+/* Call Control Specific transaction release.
+ * gets called by trans_free, DO NOT CALL YOURSELF!
+ */
+void _gsm48_cc_trans_free(struct gsm_trans *trans)
+{
+ gsm48_stop_cc_timer(trans);
+
+ /* send release to L4, if callref still exists */
+ if (trans->callref) {
+ /* Ressource unavailable */
+ mncc_release_ind(trans->ms, trans, trans->callref,
+ GSM48_CAUSE_LOC_USER,
+ GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+ }
+ if (trans->cc.state != GSM_CSTATE_NULL)
+ new_cc_state(trans, GSM_CSTATE_NULL);
+}
+
+/* release MM connection, got NULL state, free transaction */
+static int gsm48_rel_null_free(struct gsm_trans *trans)
+{
+ struct msgb *nmsg;
+
+ /* release MM connection */
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_REL_REQ, trans->callref,
+ trans->transaction_id);
+ if (!nmsg)
+ return -ENOMEM;
+ LOGP(DCC, LOGL_INFO, "Sending MMCC_REL_REQ\n");
+ gsm48_mmxx_downmsg(trans->ms, nmsg);
+
+ new_cc_state(trans, GSM_CSTATE_NULL);
+
+ trans->callref = 0;
+ trans_free(trans);
+
+ return 0;
+}
+
+void mncc_set_cause(struct gsm_mncc *data, int loc, int val)
+{
+ data->fields |= MNCC_F_CAUSE;
+ data->cause.location = loc;
+ data->cause.value = val;
+}
+
+/* send release indication to upper layer */
+int mncc_release_ind(struct osmocom_ms *ms, struct gsm_trans *trans,
+ u_int32_t callref, int location, int value)
+{
+ struct gsm_mncc rel;
+
+ memset(&rel, 0, sizeof(rel));
+ rel.callref = callref;
+ mncc_set_cause(&rel, location, value);
+ return mncc_recvmsg(ms, trans, MNCC_REL_IND, &rel);
+}
+
+/* sending status message in response to unknown message */
+static int gsm48_cc_tx_status(struct gsm_trans *trans, int cause)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+ uint8_t *cause_ie, *call_state_ie;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_STATUS;
+
+ cause_ie = msgb_put(nmsg, 3);
+ cause_ie[0] = 2;
+ cause_ie[1] = GSM48_CAUSE_CS_GSM | GSM48_CAUSE_LOC_USER;
+ cause_ie[2] = 0x80 | cause;
+
+ call_state_ie = msgb_put(nmsg, 1);
+ call_state_ie[0] = 0xc0 | trans->cc.state;
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* reply status enquiry */
+static int gsm48_cc_rx_status_enq(struct gsm_trans *trans, struct msgb *msg)
+{
+ return gsm48_cc_tx_status(trans, GSM48_CC_CAUSE_RESP_STATUS_INQ);
+}
+
+/*
+ * process handlers (mobile originating call establish)
+ */
+
+/* on SETUP request from L4, init MM connection */
+static int gsm48_cc_init_mm(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *nmsg;
+ struct gsm_mncc *data = arg;
+ struct gsm48_mmxx_hdr *nmmh;
+
+ /* store setup message */
+ memcpy(&trans->cc.msg, data, sizeof(struct gsm_mncc));
+
+ new_cc_state(trans, GSM_CSTATE_MM_CONNECTION_PEND);
+
+ /* establish MM connection */
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_EST_REQ, trans->callref,
+ trans->transaction_id);
+ if (!nmsg)
+ return -ENOMEM;
+ nmmh = (struct gsm48_mmxx_hdr *) nmsg->data;
+ if (data->emergency)
+ nmmh->emergency = 1;
+ LOGP(DCC, LOGL_INFO, "Sending MMCC_EST_REQ\n");
+ return gsm48_mmxx_downmsg(trans->ms, nmsg);
+}
+
+/* abort connection prior SETUP */
+static int gsm48_cc_abort_mm(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *nmsg;
+
+ /* abort MM connection */
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_REL_REQ, trans->callref,
+ trans->transaction_id);
+ if (!nmsg)
+ return -ENOMEM;
+ LOGP(DCC, LOGL_INFO, "Sending MMCC_REL_REQ\n");
+ gsm48_mmxx_downmsg(trans->ms, nmsg);
+
+ new_cc_state(trans, GSM_CSTATE_NULL);
+
+ trans->callref = 0;
+ trans_free(trans);
+
+ return 0;
+}
+
+/* setup message from upper layer */
+static int gsm48_cc_tx_setup(struct gsm_trans *trans)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+ struct gsm_mncc *setup = &trans->cc.msg;
+ int rc, transaction_id;
+ uint8_t *ie;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ /* transaction id must not be assigned */
+ if (trans->transaction_id != 0xff) { /* unasssigned */
+ LOGP(DCC, LOGL_NOTICE, "TX Setup with assigned transaction. "
+ "This is not allowed!\n");
+ /* Temporarily out of order */
+ rc = mncc_release_ind(trans->ms, trans, trans->callref,
+ GSM48_CAUSE_LOC_USER,
+ GSM48_CC_CAUSE_NORMAL_UNSPEC);
+ trans->callref = 0;
+ trans_free(trans);
+ return rc;
+ }
+
+ /* Get free transaction_id */
+ transaction_id = trans_assign_trans_id(trans->ms, GSM48_PDISC_CC, 0);
+ if (transaction_id < 0) {
+ /* no free transaction ID */
+ rc = mncc_release_ind(trans->ms, trans, trans->callref,
+ GSM48_CAUSE_LOC_USER,
+ GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+ trans->callref = 0;
+ trans_free(trans);
+ return rc;
+ }
+ trans->transaction_id = transaction_id;
+
+ gh->msg_type = (setup->emergency) ? GSM48_MT_CC_EMERG_SETUP :
+ GSM48_MT_CC_SETUP;
+
+ /* actually we have to start it when CM SERVICE REQUEST has been sent,
+ * but there is no primitive for that defined. i think it is ok to
+ * do it here rather than inventing MMCC-NOTIFY-IND.
+ */
+ gsm48_start_cc_timer(trans, 0x303, GSM48_T303_MS);
+
+ if (!setup->emergency) {
+ /* bearer capability */
+ gsm48_encode_bearer_cap(nmsg, 0, &setup->bearer_cap);
+ /* facility */
+ if (setup->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(nmsg, 0, &setup->facility);
+ /* called party BCD number */
+ if (setup->fields & MNCC_F_CALLED)
+ gsm48_encode_called(nmsg, &setup->called);
+ /* user-user */
+ if (setup->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(nmsg, 0, &setup->useruser);
+ /* ss version */
+ if (setup->fields & MNCC_F_SSVERSION)
+ gsm48_encode_ssversion(nmsg, &setup->ssversion);
+ /* CLIR suppression */
+ if (setup->clir.sup) {
+ ie = msgb_put(nmsg, 1);
+ ie[0] = GSM48_IE_CLIR_SUPP;
+ }
+ /* CLIR invocation */
+ if (setup->clir.inv) {
+ ie = msgb_put(nmsg, 1);
+ ie[0] = GSM48_IE_CLIR_INVOC;
+ }
+ /* cc cap */
+ if (setup->fields & MNCC_F_CCCAP)
+ gsm48_encode_cccap(nmsg, &setup->cccap);
+ }
+
+ /* actually MM CONNECTION PENDING */
+ new_cc_state(trans, GSM_CSTATE_INITIATED);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* progress is received from lower layer */
+static int gsm48_cc_rx_progress(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc progress;
+
+ memset(&progress, 0, sizeof(struct gsm_mncc));
+ progress.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len,
+ GSM48_IE_PROGR_IND, 0);
+ /* progress */
+ if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) {
+ progress.fields |= MNCC_F_PROGRESS;
+ gsm48_decode_progress(&progress.progress,
+ TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1);
+ /* store last progress indicator */
+ trans->cc.prog_ind = progress.progress.descr;
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ progress.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&progress.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_PROGRESS_IND, &progress);
+}
+
+/* call proceeding is received from lower layer */
+static int gsm48_cc_rx_call_proceeding(struct gsm_trans *trans,
+ struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc call_proc;
+
+ gsm48_stop_cc_timer(trans);
+
+ memset(&call_proc, 0, sizeof(struct gsm_mncc));
+ call_proc.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+#if 0
+ /* repeat */
+ if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_CIR))
+ call_conf.repeat = 1;
+ if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_SEQ))
+ call_conf.repeat = 2;
+#endif
+ /* bearer capability */
+ if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+ call_proc.fields |= MNCC_F_BEARER_CAP;
+ gsm48_decode_bearer_cap(&call_proc.bearer_cap,
+ TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+ }
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ call_proc.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&call_proc.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+
+ /* progress */
+ if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) {
+ call_proc.fields |= MNCC_F_PROGRESS;
+ gsm48_decode_progress(&call_proc.progress,
+ TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1);
+ /* store last progress indicator */
+ trans->cc.prog_ind = call_proc.progress.descr;
+ }
+
+ /* start T310, if last progress indicator was 1 or 2 or 64 */
+ if (trans->cc.prog_ind == 1
+ || trans->cc.prog_ind == 2
+ || trans->cc.prog_ind == 64)
+ gsm48_start_cc_timer(trans, 0x310, GSM48_T310_MS);
+
+ new_cc_state(trans, GSM_CSTATE_MO_CALL_PROC);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_CALL_PROC_IND,
+ &call_proc);
+}
+
+/* alerting is received by the lower layer */
+static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc alerting;
+
+ gsm48_stop_cc_timer(trans);
+ /* no T301 in MS call control */
+
+ memset(&alerting, 0, sizeof(struct gsm_mncc));
+ alerting.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ alerting.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&alerting.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+
+ /* progress */
+ if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) {
+ alerting.fields |= MNCC_F_PROGRESS;
+ gsm48_decode_progress(&alerting.progress,
+ TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1);
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ alerting.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&alerting.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+
+ new_cc_state(trans, GSM_CSTATE_CALL_DELIVERED);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_ALERT_IND,
+ &alerting);
+}
+
+/* connect is received from lower layer */
+static int gsm48_cc_rx_connect(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc connect;
+
+ gsm48_stop_cc_timer(trans);
+
+ memset(&connect, 0, sizeof(struct gsm_mncc));
+ connect.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ connect.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&connect.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+ /* connected */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CONN_BCD)) {
+ connect.fields |= MNCC_F_CONNECTED;
+ gsm48_decode_connected(&connect.connected,
+ TLVP_VAL(&tp, GSM48_IE_CONN_BCD)-1);
+ }
+ /* progress */
+ if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) {
+ connect.fields |= MNCC_F_PROGRESS;
+ gsm48_decode_progress(&connect.progress,
+ TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1);
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ connect.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&connect.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+
+ /* ACTIVE state is set during this: */
+ gsm48_cc_tx_connect_ack(trans, NULL);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_SETUP_CNF, &connect);
+}
+
+/* connect ack message from upper layer */
+static int gsm48_cc_tx_connect_ack(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_CONNECT_ACK;
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/*
+ * process handlers (mobile terminating call establish)
+ */
+
+/* setup is received from lower layer */
+static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc setup;
+
+ memset(&setup, 0, sizeof(struct gsm_mncc));
+ setup.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+
+ /* bearer capability */
+ if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+ setup.fields |= MNCC_F_BEARER_CAP;
+ gsm48_decode_bearer_cap(&setup.bearer_cap,
+ TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+ }
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ setup.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&setup.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+ /* progress */
+ if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) {
+ setup.fields |= MNCC_F_PROGRESS;
+ gsm48_decode_progress(&setup.progress,
+ TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1);
+ }
+ /* signal */
+ if (TLVP_PRESENT(&tp, GSM48_IE_SIGNAL)) {
+ setup.fields |= MNCC_F_SIGNAL;
+ gsm48_decode_signal(&setup.signal,
+ TLVP_VAL(&tp, GSM48_IE_SIGNAL)-1);
+ }
+ /* calling party bcd number */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CALLING_BCD)) {
+ setup.fields |= MNCC_F_CALLING;
+ gsm48_decode_calling(&setup.calling,
+ TLVP_VAL(&tp, GSM48_IE_CALLING_BCD)-1);
+ }
+ /* called party bcd number */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD)) {
+ setup.fields |= MNCC_F_CALLED;
+ gsm48_decode_called(&setup.called,
+ TLVP_VAL(&tp, GSM48_IE_CALLED_BCD)-1);
+ }
+ /* redirecting party bcd number */
+ if (TLVP_PRESENT(&tp, GSM48_IE_REDIR_BCD)) {
+ setup.fields |= MNCC_F_REDIRECTING;
+ gsm48_decode_redirecting(&setup.redirecting,
+ TLVP_VAL(&tp, GSM48_IE_REDIR_BCD)-1);
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ setup.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&setup.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+
+ new_cc_state(trans, GSM_CSTATE_CALL_PRESENT);
+
+ /* indicate setup to MNCC */
+ mncc_recvmsg(trans->ms, trans, MNCC_SETUP_IND, &setup);
+
+ return 0;
+}
+
+/* call conf message from upper layer */
+static int gsm48_cc_tx_call_conf(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *confirm = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_CALL_CONF;
+
+ new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF);
+
+ /* bearer capability */
+ if (confirm->fields & MNCC_F_BEARER_CAP)
+ gsm48_encode_bearer_cap(nmsg, 0, &confirm->bearer_cap);
+ /* cause */
+ if (confirm->fields & MNCC_F_CAUSE)
+ gsm48_encode_cause(nmsg, 0, &confirm->cause);
+ /* cc cap */
+ if (confirm->fields & MNCC_F_CCCAP)
+ gsm48_encode_cccap(nmsg, &confirm->cccap);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* alerting message from upper layer */
+static int gsm48_cc_tx_alerting(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *alerting = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_ALERTING;
+
+ /* facility */
+ if (alerting->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(nmsg, 0, &alerting->facility);
+ /* user-user */
+ if (alerting->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(nmsg, 0, &alerting->useruser);
+ /* ss version */
+ if (alerting->fields & MNCC_F_SSVERSION)
+ gsm48_encode_ssversion(nmsg, &alerting->ssversion);
+
+ new_cc_state(trans, GSM_CSTATE_CALL_RECEIVED);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* connect message from upper layer */
+static int gsm48_cc_tx_connect(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *connect = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_CONNECT;
+
+ gsm48_stop_cc_timer(trans);
+ gsm48_start_cc_timer(trans, 0x313, GSM48_T313_MS);
+
+ /* facility */
+ if (connect->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(nmsg, 0, &connect->facility);
+ /* user-user */
+ if (connect->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(nmsg, 0, &connect->useruser);
+ /* ss version */
+ if (connect->fields & MNCC_F_SSVERSION)
+ gsm48_encode_ssversion(nmsg, &connect->ssversion);
+
+ new_cc_state(trans, GSM_CSTATE_CONNECT_REQUEST);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* connect ack is received from lower layer */
+static int gsm48_cc_rx_connect_ack(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm_mncc connect_ack;
+
+ gsm48_stop_cc_timer(trans);
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+ memset(&connect_ack, 0, sizeof(struct gsm_mncc));
+ connect_ack.callref = trans->callref;
+ return mncc_recvmsg(trans->ms, trans, MNCC_SETUP_COMPL_IND,
+ &connect_ack);
+}
+
+/*
+ * process handlers (during active state)
+ */
+
+/* notify message from upper layer */
+static int gsm48_cc_tx_notify(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *notify = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_NOTIFY;
+
+ /* notify */
+ gsm48_encode_notify(nmsg, notify->notify);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* notify is received from lower layer */
+static int gsm48_cc_rx_notify(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct gsm_mncc notify;
+
+ memset(&notify, 0, sizeof(struct gsm_mncc));
+ notify.callref = trans->callref;
+ /* notify */
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of notify message error.\n");
+ return -EINVAL;
+ }
+ gsm48_decode_notify(&notify.notify, gh->data);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_NOTIFY_IND, &notify);
+}
+
+/* start dtmf message from upper layer */
+static int gsm48_cc_tx_start_dtmf(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *dtmf = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_START_DTMF;
+
+ /* keypad */
+ gsm48_encode_keypad(nmsg, dtmf->keypad);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* start dtmf ack is received from lower layer */
+static int gsm48_cc_rx_start_dtmf_ack(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc dtmf;
+
+ memset(&dtmf, 0, sizeof(struct gsm_mncc));
+ dtmf.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ /* keypad facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_KPD_FACILITY)) {
+ dtmf.fields |= MNCC_F_KEYPAD;
+ gsm48_decode_keypad(&dtmf.keypad,
+ TLVP_VAL(&tp, GSM48_IE_KPD_FACILITY)-1);
+ }
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_START_DTMF_RSP, &dtmf);
+}
+
+/* start dtmf rej is received from lower layer */
+static int gsm48_cc_rx_start_dtmf_rej(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct gsm_mncc dtmf;
+
+ memset(&dtmf, 0, sizeof(struct gsm_mncc));
+ dtmf.callref = trans->callref;
+ /* cause */
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of dtmf reject message "
+ "error.\n");
+ return -EINVAL;
+ }
+ gsm48_decode_cause(&dtmf.cause, gh->data);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_START_DTMF_REJ, &dtmf);
+}
+
+/* stop dtmf message from upper layer */
+static int gsm48_cc_tx_stop_dtmf(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_STOP_DTMF;
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* stop dtmf ack is received from lower layer */
+static int gsm48_cc_rx_stop_dtmf_ack(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc dtmf;
+
+ memset(&dtmf, 0, sizeof(struct gsm_mncc));
+ dtmf.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_STOP_DTMF_RSP, &dtmf);
+}
+
+/* hold message from upper layer */
+static int gsm48_cc_tx_hold(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_HOLD;
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* hold ack is received from lower layer */
+static int gsm48_cc_rx_hold_ack(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm_mncc hold;
+
+ memset(&hold, 0, sizeof(struct gsm_mncc));
+ hold.callref = trans->callref;
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_HOLD_CNF, &hold);
+}
+
+/* hold rej is received from lower layer */
+static int gsm48_cc_rx_hold_rej(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct gsm_mncc hold;
+
+ memset(&hold, 0, sizeof(struct gsm_mncc));
+ hold.callref = trans->callref;
+ /* cause */
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of hold reject message "
+ "error.\n");
+ return -EINVAL;
+ }
+ gsm48_decode_cause(&hold.cause, gh->data);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_HOLD_REJ, &hold);
+}
+
+/* retrieve message from upper layer */
+static int gsm48_cc_tx_retrieve(struct gsm_trans *trans, void *arg)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_RETR;
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* retrieve ack is received from lower layer */
+static int gsm48_cc_rx_retrieve_ack(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm_mncc retrieve;
+
+ memset(&retrieve, 0, sizeof(struct gsm_mncc));
+ retrieve.callref = trans->callref;
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_RETRIEVE_CNF, &retrieve);
+}
+
+/* retrieve rej is received from lower layer */
+static int gsm48_cc_rx_retrieve_rej(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct gsm_mncc retrieve;
+
+ memset(&retrieve, 0, sizeof(struct gsm_mncc));
+ retrieve.callref = trans->callref;
+ /* cause */
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of retrieve reject message "
+ "error.\n");
+ return -EINVAL;
+ }
+ gsm48_decode_cause(&retrieve.cause, gh->data);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_RETRIEVE_REJ, &retrieve);
+}
+
+/* facility message from upper layer or from timer event */
+static int gsm48_cc_tx_facility(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *fac = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_FACILITY;
+
+ /* facility */
+ gsm48_encode_facility(nmsg, 1, &fac->facility);
+ /* ss version */
+ if (fac->fields & MNCC_F_SSVERSION)
+ gsm48_encode_ssversion(nmsg, &fac->ssversion);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* facility is received from lower layer */
+static int gsm48_cc_rx_facility(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct gsm_mncc fac;
+
+ memset(&fac, 0, sizeof(struct gsm_mncc));
+ fac.callref = trans->callref;
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of facility message "
+ "error.\n");
+ return -EINVAL;
+ }
+ /* facility */
+ gsm48_decode_facility(&fac.facility, gh->data);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_FACILITY_IND, &fac);
+}
+
+/* user info message from upper layer or from timer event */
+static int gsm48_cc_tx_userinfo(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *user = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_USER_INFO;
+
+ /* user-user */
+ if (user->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(nmsg, 1, &user->useruser);
+ /* more data */
+ if (user->more)
+ gsm48_encode_more(nmsg);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* user info is received from lower layer */
+static int gsm48_cc_rx_userinfo(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc user;
+
+ memset(&user, 0, sizeof(struct gsm_mncc));
+ user.callref = trans->callref;
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of userinfo message "
+ "error.\n");
+ return -EINVAL;
+ }
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len,
+ GSM48_IE_USER_USER, 0);
+ /* user-user */
+ gsm48_decode_useruser(&user.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ /* more data */
+ if (TLVP_PRESENT(&tp, GSM48_IE_MORE_DATA))
+ user.more = 1;
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_USERINFO_IND, &user);
+}
+
+/* modify message from upper layer or from timer event */
+static int gsm48_cc_tx_modify(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *modify = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_MODIFY;
+
+ gsm48_start_cc_timer(trans, 0x323, GSM48_T323_MS);
+
+ /* bearer capability */
+ gsm48_encode_bearer_cap(nmsg, 1, &modify->bearer_cap);
+
+ new_cc_state(trans, GSM_CSTATE_MO_TERM_MODIFY);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* modify complete is received from lower layer */
+static int gsm48_cc_rx_modify_complete(struct gsm_trans *trans,
+ struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct gsm_mncc modify;
+
+ gsm48_stop_cc_timer(trans);
+
+ memset(&modify, 0, sizeof(struct gsm_mncc));
+ modify.callref = trans->callref;
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of modify complete message "
+ "error.\n");
+ return -EINVAL;
+ }
+ /* bearer capability */
+ gsm48_decode_bearer_cap(&modify.bearer_cap, gh->data);
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_MODIFY_CNF, &modify);
+}
+
+/* modify reject is received from lower layer */
+static int gsm48_cc_rx_modify_reject(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc modify;
+
+ gsm48_stop_cc_timer(trans);
+
+ memset(&modify, 0, sizeof(struct gsm_mncc));
+ modify.callref = trans->callref;
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of modify reject message "
+ "error.\n");
+ return -EINVAL;
+ }
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len,
+ GSM48_IE_BEARER_CAP, GSM48_IE_CAUSE);
+ /* bearer capability */
+ if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+ modify.fields |= MNCC_F_BEARER_CAP;
+ gsm48_decode_bearer_cap(&modify.bearer_cap,
+ TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+ }
+ /* cause */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+ modify.fields |= MNCC_F_CAUSE;
+ gsm48_decode_cause(&modify.cause,
+ TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+ }
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_MODIFY_REJ, &modify);
+}
+
+/* modify is received from lower layer */
+static int gsm48_cc_rx_modify(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct gsm_mncc modify;
+
+ memset(&modify, 0, sizeof(struct gsm_mncc));
+ modify.callref = trans->callref;
+ if (payload_len < 1) {
+ LOGP(DCC, LOGL_NOTICE, "Short read of modify message error.\n");
+ return -EINVAL;
+ }
+ /* bearer capability */
+ gsm48_decode_bearer_cap(&modify.bearer_cap, gh->data);
+
+ new_cc_state(trans, GSM_CSTATE_MO_ORIG_MODIFY);
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_MODIFY_IND, &modify);
+}
+
+/* modify complete message from upper layer or from timer event */
+static int gsm48_cc_tx_modify_complete(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *modify = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_MODIFY_COMPL;
+
+ /* bearer capability */
+ gsm48_encode_bearer_cap(nmsg, 1, &modify->bearer_cap);
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* modify reject message from upper layer or from timer event */
+static int gsm48_cc_tx_modify_reject(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *modify = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_MODIFY_REJECT;
+
+ /* bearer capability */
+ gsm48_encode_bearer_cap(nmsg, 1, &modify->bearer_cap);
+ /* cause */
+ gsm48_encode_cause(nmsg, 1, &modify->cause);
+
+ new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/*
+ * process handlers (call clearing)
+ */
+
+static struct gsm_mncc_cause default_cause = {
+ .location = GSM48_CAUSE_LOC_PRN_S_LU,
+ .coding = 0,
+ .rec = 0,
+ .rec_val = 0,
+ .value = GSM48_CC_CAUSE_NORMAL_UNSPEC,
+ .diag_len = 0,
+ .diag = { 0 },
+};
+
+/* disconnect message from upper layer or from timer event */
+static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *disc = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_DISCONNECT;
+
+ gsm48_stop_cc_timer(trans);
+ gsm48_start_cc_timer(trans, 0x305, GSM48_T305_MS);
+
+ /* cause */
+ if (disc->fields & MNCC_F_CAUSE)
+ gsm48_encode_cause(nmsg, 1, &disc->cause);
+ else
+ gsm48_encode_cause(nmsg, 1, &default_cause);
+
+ /* facility */
+ if (disc->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(nmsg, 0, &disc->facility);
+ /* progress */
+ if (disc->fields & MNCC_F_PROGRESS)
+ gsm48_encode_progress(nmsg, 0, &disc->progress);
+ /* user-user */
+ if (disc->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(nmsg, 0, &disc->useruser);
+ /* ss version */
+ if (disc->fields & MNCC_F_SSVERSION)
+ gsm48_encode_ssversion(nmsg, &disc->ssversion);
+
+ new_cc_state(trans, GSM_CSTATE_DISCONNECT_REQ);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* release message from upper layer or from timer event */
+static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *rel = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_RELEASE;
+
+ trans->callref = 0;
+
+ gsm48_stop_cc_timer(trans);
+ gsm48_start_cc_timer(trans, 0x308, GSM48_T308_MS);
+
+ /* cause */
+ if (rel->fields & MNCC_F_CAUSE)
+ gsm48_encode_cause(nmsg, 0, &rel->cause);
+ /* facility */
+ if (rel->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(nmsg, 0, &rel->facility);
+ /* user-user */
+ if (rel->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(nmsg, 0, &rel->useruser);
+ /* ss version */
+ if (rel->fields & MNCC_F_SSVERSION)
+ gsm48_encode_ssversion(nmsg, &rel->ssversion);
+
+ trans->cc.T308_second = 0;
+ memcpy(&trans->cc.msg, rel, sizeof(struct gsm_mncc));
+
+ if (trans->cc.state != GSM_CSTATE_RELEASE_REQ)
+ new_cc_state(trans, GSM_CSTATE_RELEASE_REQ);
+
+ return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ);
+}
+
+/* reject message from upper layer or from timer event */
+static int gsm48_cc_tx_release_compl(struct gsm_trans *trans, void *arg)
+{
+ struct gsm_mncc *rel = arg;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->msg_type = GSM48_MT_CC_RELEASE_COMPL;
+
+ trans->callref = 0;
+
+ gsm48_stop_cc_timer(trans);
+
+ /* cause */
+ if (rel->fields & MNCC_F_CAUSE)
+ gsm48_encode_cause(nmsg, 0, &rel->cause);
+ /* facility */
+ if (rel->fields & MNCC_F_FACILITY)
+ gsm48_encode_facility(nmsg, 0, &rel->facility);
+ /* user-user */
+ if (rel->fields & MNCC_F_USERUSER)
+ gsm48_encode_useruser(nmsg, 0, &rel->useruser);
+ /* ss version */
+ if (rel->fields & MNCC_F_SSVERSION)
+ gsm48_encode_ssversion(nmsg, &rel->ssversion);
+
+
+ /* release MM conn, got NULL state, free trans */
+ return gsm48_rel_null_free(trans);
+}
+
+/* disconnect is received from lower layer */
+static int gsm48_cc_rx_disconnect(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc disc;
+
+ gsm48_stop_cc_timer(trans);
+
+ new_cc_state(trans, GSM_CSTATE_DISCONNECT_IND);
+
+ memset(&disc, 0, sizeof(struct gsm_mncc));
+ disc.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len,
+ GSM48_IE_CAUSE, 0);
+ /* cause */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+ disc.fields |= MNCC_F_CAUSE;
+ gsm48_decode_cause(&disc.cause,
+ TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+ }
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ disc.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&disc.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ disc.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&disc.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+
+ /* store disconnect cause for T305 expiry */
+ memcpy(&trans->cc.msg, &disc, sizeof(struct gsm_mncc));
+
+ return mncc_recvmsg(trans->ms, trans, MNCC_DISC_IND, &disc);
+}
+
+/* release is received from lower layer */
+static int gsm48_cc_rx_release(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc rel;
+
+ gsm48_stop_cc_timer(trans);
+
+ memset(&rel, 0, sizeof(struct gsm_mncc));
+ rel.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ /* cause */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+ rel.fields |= MNCC_F_CAUSE;
+ gsm48_decode_cause(&rel.cause,
+ TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+ }
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ rel.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&rel.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ rel.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&rel.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+
+ if (trans->cc.state == GSM_CSTATE_RELEASE_REQ) {
+ /* release collision 5.4.5 */
+ mncc_recvmsg(trans->ms, trans, MNCC_REL_CNF, &rel);
+ } else {
+ struct gsm_mncc rel2;
+
+ /* forward cause only */
+ memcpy(&rel2, &rel, sizeof(struct gsm_mncc));
+ rel2.fields = MNCC_F_CAUSE;
+ gsm48_cc_tx_release_compl(trans, &rel);
+
+ /* release indication */
+ mncc_recvmsg(trans->ms, trans, MNCC_REL_IND, &rel);
+ }
+
+ /* release MM conn, got NULL state, free trans */
+ return gsm48_rel_null_free(trans);
+}
+
+/* release complete is received from lower layer */
+static int gsm48_cc_rx_release_compl(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+ struct gsm_mncc rel;
+
+ gsm48_stop_cc_timer(trans);
+
+ memset(&rel, 0, sizeof(struct gsm_mncc));
+ rel.callref = trans->callref;
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ /* cause */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+ rel.fields |= MNCC_F_CAUSE;
+ gsm48_decode_cause(&rel.cause,
+ TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+ }
+ /* facility */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+ rel.fields |= MNCC_F_FACILITY;
+ gsm48_decode_facility(&rel.facility,
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+ /* user-user */
+ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+ rel.fields |= MNCC_F_USERUSER;
+ gsm48_decode_useruser(&rel.useruser,
+ TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+ }
+
+ if (trans->callref) {
+ switch (trans->cc.state) {
+ case GSM_CSTATE_CALL_PRESENT:
+ mncc_recvmsg(trans->ms, trans,
+ MNCC_REJ_IND, &rel);
+ break;
+ case GSM_CSTATE_RELEASE_REQ:
+ mncc_recvmsg(trans->ms, trans,
+ MNCC_REL_CNF, &rel);
+ break;
+ default:
+ mncc_recvmsg(trans->ms, trans,
+ MNCC_REL_IND, &rel);
+ }
+ }
+
+ /* release MM conn, got NULL state, free trans */
+ return gsm48_rel_null_free(trans);
+}
+
+/*
+ * state machines
+ */
+
+/* state trasitions for MNCC messages (upper layer) */
+static struct downstate {
+ u_int32_t states;
+ int type;
+ int (*rout) (struct gsm_trans *trans, void *arg);
+} downstatelist[] = {
+ /* mobile originating call establishment */
+ {SBIT(GSM_CSTATE_NULL), /* 5.2.1 */
+ MNCC_SETUP_REQ, gsm48_cc_init_mm},
+ {SBIT(GSM_CSTATE_MM_CONNECTION_PEND), /* 5.2.1 */
+ MNCC_REL_REQ, gsm48_cc_abort_mm},
+ /* mobile terminating call establishment */
+ {SBIT(GSM_CSTATE_CALL_PRESENT), /* 5.2.2.3.1 */
+ MNCC_CALL_CONF_REQ, gsm48_cc_tx_call_conf},
+ {SBIT(GSM_CSTATE_MO_TERM_CALL_CONF), /* 5.2.2.3.2 */
+ MNCC_ALERT_REQ, gsm48_cc_tx_alerting},
+ {SBIT(GSM_CSTATE_MO_TERM_CALL_CONF) |
+ SBIT(GSM_CSTATE_CALL_RECEIVED), /* 5.2.2.5 */
+ MNCC_SETUP_RSP, gsm48_cc_tx_connect},
+ /* signalling during call */
+ {SBIT(GSM_CSTATE_ACTIVE), /* 5.3.1 */
+ MNCC_NOTIFY_REQ, gsm48_cc_tx_notify},
+ {ALL_STATES, /* 5.5.7.1 */
+ MNCC_START_DTMF_REQ, gsm48_cc_tx_start_dtmf},
+ {ALL_STATES, /* 5.5.7.3 */
+ MNCC_STOP_DTMF_REQ, gsm48_cc_tx_stop_dtmf},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ MNCC_HOLD_REQ, gsm48_cc_tx_hold},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ MNCC_RETRIEVE_REQ, gsm48_cc_tx_retrieve},
+ {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ),
+ MNCC_FACILITY_REQ, gsm48_cc_tx_facility},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ MNCC_USERINFO_REQ, gsm48_cc_tx_userinfo},
+ /* clearing */
+ {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_DISCONNECT_IND) -
+ SBIT(GSM_CSTATE_RELEASE_REQ) -
+ SBIT(GSM_CSTATE_DISCONNECT_REQ), /* 5.4.3.1 */
+ MNCC_DISC_REQ, gsm48_cc_tx_disconnect},
+ {SBIT(GSM_CSTATE_INITIATED),
+ MNCC_REJ_REQ, gsm48_cc_tx_release_compl},
+ {ALL_STATES - SBIT(GSM_CSTATE_NULL) -
+ SBIT(GSM_CSTATE_RELEASE_REQ), /* ??? */
+ MNCC_REL_REQ, gsm48_cc_tx_release},
+ /* modify */
+ {SBIT(GSM_CSTATE_ACTIVE),
+ MNCC_MODIFY_REQ, gsm48_cc_tx_modify},
+ {SBIT(GSM_CSTATE_MO_ORIG_MODIFY),
+ MNCC_MODIFY_RSP, gsm48_cc_tx_modify_complete},
+ {SBIT(GSM_CSTATE_MO_ORIG_MODIFY),
+ MNCC_MODIFY_REJ, gsm48_cc_tx_modify_reject},
+};
+
+#define DOWNSLLEN \
+ (sizeof(downstatelist) / sizeof(struct downstate))
+
+int mncc_send(struct osmocom_ms *ms, int msg_type, void *arg)
+{
+ struct gsm_mncc *data = arg;
+ struct gsm_trans *trans;
+ int i, rc;
+
+ /* Find callref */
+ trans = trans_find_by_callref(ms, data->callref);
+
+ if (!trans) {
+ /* check for SETUP message */
+ if (msg_type != MNCC_SETUP_REQ) {
+ /* Invalid call reference */
+ return mncc_release_ind(ms, NULL, data->callref,
+ GSM48_CAUSE_LOC_USER,
+ GSM48_CC_CAUSE_INVAL_TRANS_ID);
+ }
+ if (data->callref < 0x80000000) {
+ LOGP(DCC, LOGL_FATAL, "MNCC ref wrong.\n");
+ return mncc_release_ind(ms, NULL, data->callref,
+ GSM48_CAUSE_LOC_USER,
+ GSM48_CC_CAUSE_INVAL_TRANS_ID);
+ }
+
+ /* Create transaction */
+ trans = trans_alloc(ms, GSM48_PDISC_CC, 0xff, data->callref);
+ if (!trans) {
+ /* No memory or whatever */
+ return mncc_release_ind(ms, NULL, data->callref,
+ GSM48_CAUSE_LOC_USER,
+ GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+ }
+ }
+
+ /* Find function for current state and message */
+ for (i = 0; i < DOWNSLLEN; i++)
+ if ((msg_type == downstatelist[i].type)
+ && ((1 << trans->cc.state) & downstatelist[i].states))
+ break;
+ if (i == DOWNSLLEN) {
+ LOGP(DCC, LOGL_NOTICE, "Message unhandled at this "
+ "state.\n");
+ return 0;
+ }
+
+ rc = downstatelist[i].rout(trans, arg);
+
+ return rc;
+}
+
+/* state trasitions for call control messages (lower layer) */
+static struct datastate {
+ u_int32_t states;
+ int type;
+ int (*rout) (struct gsm_trans *trans, struct msgb *msg);
+} datastatelist[] = {
+ /* mobile originating call establishment */
+ {SBIT(GSM_CSTATE_INITIATED), /* 5.2.1.3 */
+ GSM48_MT_CC_CALL_PROC, gsm48_cc_rx_call_proceeding},
+ {SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC) |
+ SBIT(GSM_CSTATE_CALL_DELIVERED), /* 5.2.1.4.1 */
+ MNCC_PROGRESS_REQ, gsm48_cc_rx_progress},
+ {SBIT(GSM_CSTATE_INITIATED) |
+ SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.5 */
+ GSM48_MT_CC_ALERTING, gsm48_cc_rx_alerting},
+ {SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC) |
+ SBIT(GSM_CSTATE_CALL_DELIVERED), /* 5.2.1.6 */
+ GSM48_MT_CC_CONNECT, gsm48_cc_rx_connect},
+ /* mobile terminating call establishment */
+ {SBIT(GSM_CSTATE_NULL), /* 5.2.2.1 */
+ GSM48_MT_CC_SETUP, gsm48_cc_rx_setup},
+ {SBIT(GSM_CSTATE_CALL_PRESENT), /* 5.2.2.6 */
+ GSM48_MT_CC_CONNECT_ACK, gsm48_cc_rx_connect_ack},
+ /* signalling during call */
+ {SBIT(GSM_CSTATE_ACTIVE), /* 5.3.1 */
+ GSM48_MT_CC_NOTIFY, gsm48_cc_rx_notify},
+ {ALL_STATES, /* 8.4 */
+ GSM48_MT_CC_STATUS_ENQ, gsm48_cc_rx_status_enq},
+ {ALL_STATES, /* 5.5.7.2 */
+ GSM48_MT_CC_START_DTMF_ACK, gsm48_cc_rx_start_dtmf_ack},
+ {ALL_STATES, /* 5.5.7.2 */
+ GSM48_MT_CC_START_DTMF_REJ, gsm48_cc_rx_start_dtmf_rej},
+ {ALL_STATES, /* 5.5.7.4 */
+ GSM48_MT_CC_STOP_DTMF_ACK, gsm48_cc_rx_stop_dtmf_ack},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ GSM48_MT_CC_HOLD_ACK, gsm48_cc_rx_hold_ack},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ GSM48_MT_CC_HOLD_REJ, gsm48_cc_rx_hold_rej},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ GSM48_MT_CC_RETR_ACK, gsm48_cc_rx_retrieve_ack},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ GSM48_MT_CC_RETR_REJ, gsm48_cc_rx_retrieve_rej},
+ {ALL_STATES - SBIT(GSM_CSTATE_NULL),
+ GSM48_MT_CC_FACILITY, gsm48_cc_rx_facility},
+ {SBIT(GSM_CSTATE_ACTIVE),
+ GSM48_MT_CC_USER_INFO, gsm48_cc_rx_userinfo},
+ /* clearing */
+ {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ) -
+ SBIT(GSM_CSTATE_DISCONNECT_IND), /* 5.4.4.1.1 */
+ GSM48_MT_CC_DISCONNECT, gsm48_cc_rx_disconnect},
+ {ALL_STATES - SBIT(GSM_CSTATE_NULL), /* 5.4.3.3 & 5.4.5!!!*/
+ GSM48_MT_CC_RELEASE, gsm48_cc_rx_release},
+ {ALL_STATES, /* 5.4.4.1.3 */
+ GSM48_MT_CC_RELEASE_COMPL, gsm48_cc_rx_release_compl},
+ /* modify */
+ {SBIT(GSM_CSTATE_ACTIVE),
+ GSM48_MT_CC_MODIFY, gsm48_cc_rx_modify},
+ {SBIT(GSM_CSTATE_MO_TERM_MODIFY),
+ GSM48_MT_CC_MODIFY_COMPL, gsm48_cc_rx_modify_complete},
+ {SBIT(GSM_CSTATE_MO_TERM_MODIFY),
+ GSM48_MT_CC_MODIFY_REJECT, gsm48_cc_rx_modify_reject},
+};
+
+#define DATASLLEN \
+ (sizeof(datastatelist) / sizeof(struct datastate))
+
+static int gsm48_cc_data_ind(struct gsm_trans *trans, struct msgb *msg)
+{
+ struct osmocom_ms *ms = trans->ms;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ int msg_type = gh->msg_type & 0xbf;
+ uint8_t transaction_id = ((gh->proto_discr & 0xf0) ^ 0x80) >> 4;
+ /* flip */
+ int msg_supported = 0; /* determine, if message is supported at all */
+ int i, rc;
+
+ /* set transaction ID, if not already */
+ trans->transaction_id = transaction_id;
+
+ /* pull the MMCC header */
+ msgb_pull(msg, sizeof(struct gsm48_mmxx_hdr));
+
+ LOGP(DCC, LOGL_INFO, "(ms %s) Received '%s' in CC state %s\n", ms->name,
+ gsm48_cc_msg_name(msg_type),
+ gsm48_cc_state_name(trans->cc.state));
+
+ /* find function for current state and message */
+ for (i = 0; i < DATASLLEN; i++) {
+ if (msg_type == datastatelist[i].type)
+ msg_supported = 1;
+ if ((msg_type == datastatelist[i].type)
+ && ((1 << trans->cc.state) & datastatelist[i].states))
+ break;
+ }
+ if (i == DATASLLEN) {
+ if (msg_supported) {
+ LOGP(DCC, LOGL_NOTICE, "Message unhandled at this "
+ "state.\n");
+ return gsm48_cc_tx_status(trans,
+ GSM48_REJECT_MSG_TYPE_NOT_COMPATIBLE);
+ } else {
+ LOGP(DCC, LOGL_NOTICE, "Message not supported.\n");
+ return gsm48_cc_tx_status(trans,
+ GSM48_REJECT_MSG_TYPE_NOT_IMPLEMENTED);
+ }
+ }
+
+ rc = datastatelist[i].rout(trans, msg);
+
+ return rc;
+}
+
+/* receive message from MM layer */
+int gsm48_rcv_cc(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 gsm_trans *trans;
+ int rc = 0;
+
+ trans = trans_find_by_callref(ms, mmh->ref);
+ if (!trans) {
+ trans = trans_alloc(ms, GSM48_PDISC_CC, mmh->transaction_id,
+ mmh->ref);
+ if (!trans)
+ return -ENOMEM;
+ }
+
+ switch (msg_type) {
+ case GSM48_MMCC_EST_IND:
+ /* data included */
+ rc = gsm48_cc_data_ind(trans, msg);
+ break;
+ case GSM48_MMCC_EST_CNF:
+ /* send setup after confirm */
+ if (trans->cc.state == GSM_CSTATE_MM_CONNECTION_PEND)
+ rc = gsm48_cc_tx_setup(trans);
+ else
+ LOGP(DCC, LOGL_ERROR, "Oops, MMCC-EST-CONF in state "
+ "%d?\n", trans->cc.state);
+ break;
+ case GSM48_MMCC_ERR_IND: /* no supporting re-establishment */
+ case GSM48_MMCC_REL_IND:
+ /* release L4, release transaction */
+ mncc_release_ind(trans->ms, trans, trans->callref,
+ GSM48_CAUSE_LOC_USER, GSM48_CC_CAUSE_NORMAL_UNSPEC);
+ trans->callref = 0;
+ trans_free(trans);
+ break;
+ case GSM48_MMCC_DATA_IND:
+ rc = gsm48_cc_data_ind(trans, msg);
+ break;
+ case GSM48_MMCC_UNIT_DATA_IND:
+ break;
+ case GSM48_MMCC_SYNC_IND:
+ break;
+ default:
+ LOGP(DCC, LOGL_NOTICE, "Message unhandled.\n");
+ rc = -ENOTSUP;
+ }
+
+ return rc;
+}
+
diff --git a/src/host/layer23/src/gsm48_mm.c b/src/host/layer23/src/gsm48_mm.c
new file mode 100644
index 0000000..9e711e8
--- /dev/null
+++ b/src/host/layer23/src/gsm48_mm.c
@@ -0,0 +1,3400 @@
+/*
+ * (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>
+
+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);
+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);
+
+/*
+ * 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.
+ *
+ */
+
+/*
+ * 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(struct msgb *msg, struct osmocom_ms *ms, uint8_t mi_type)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm_support *sup = &ms->support;
+ u_int8_t buf[11];
+ u_int8_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, sup->imeisv);
+ break;
+ case GSM_MI_TYPE_IMEISV:
+ gsm48_generate_mid_from_imsi(buf, sup->imeisv);
+ break;
+ case GSM_MI_TYPE_NONE:
+ default:
+ buf[0] = GSM48_IE_MOBILE_ID;
+ buf[1] = 1;
+ buf[2] = 0xf0 | GSM_MI_TYPE_NONE;
+ break;
+ }
+ /* 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 msgb *msg, uint8_t rev_lev, uint8_t es_ind,
+ uint8_t a5_1, uint8_t pwr_lev)
+{
+ struct gsm48_classmark1 cm;
+
+ 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;
+ msgb_v_put(msg, *((uint8_t *)&cm));
+
+ return 0;
+}
+
+/*
+ * timers
+ */
+
+static void timeout_mm_t3210(void *arg)
+{
+ struct gsm48_mmlayer *mm = arg;
+
+ LOGP(DRR, 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(DRR, 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(DRR, 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(DRR, 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(DRR, 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(DRR, 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(DRR, 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(DRR, 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(DRR, 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(DRR, 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(DRR, 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(DRR, 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(DRR, 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(DRR, 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(DRR, 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(DRR, 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(DRR, 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(DRR, 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(DRR, 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(DRR, 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(DRR, 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_NEW_LAI, "MM_EVENT_NEW_LAI" },
+ { 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_POWER_OFF, "MM_EVENT_POWER_OFF" },
+ { GSM48_MM_EVENT_PAGING, "MM_EVENT_PAGING" },
+ { GSM48_MM_EVENT_AUTH_RESPONSE, "MM_EVENT_AUTH_RESPONSE" },
+ { GSM48_MM_EVENT_SYSINFO, "MM_EVENT_SYSINFO" },
+ { 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[] = {
+ "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"
+};
+
+/* 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)
+{
+ LOGP(DMM, LOGL_INFO, "(ms %s) new state %s", mm->ms->name,
+ gsm48_mm_state_names[mm->state]);
+ if (mm->state == GSM48_MM_ST_MM_IDLE)
+ LOGP(DMM, LOGL_INFO, " substate %s",
+ gsm48_mm_substate_names[mm->substate]);
+ LOGP(DMM, LOGL_INFO, "-> %s", gsm48_mm_state_names[state]);
+ if (state == GSM48_MM_ST_MM_IDLE)
+ LOGP(DMM, LOGL_INFO, " substate %s",
+ gsm48_mm_substate_names[substate]);
+ LOGP(DMM, LOGL_INFO, "\n");
+
+ /* 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 (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 (mm->state == GSM48_MM_ST_MM_IDLE
+ && (mm->substate == GSM48_MM_SST_NORMAL_SERVICE
+ || mm->substate == GSM48_MM_SST_ATTEMPT_UPDATE)) {
+ if (!bsc_timer_pending(&mm->t3212))
+ start_mm_t3212(mm, mm->t3212_value);
+ }
+
+}
+
+/* 4.2.3 when returning to MM IDLE state, this function is called */
+static int gsm48_mm_return_idle(struct osmocom_ms *ms)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+
+ /* no sim present */
+ if (!subscr->sim_valid) {
+ LOGP(DMM, LOGL_INFO, "SIM invalid as returning to IDLE");
+
+ /* 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;
+ }
+
+ /* no cell found */
+ if (cs->state != GSM322_C3_CAMPED_NORMALLY
+ && cs->state != GSM322_C7_CAMPED_ANY_CELL) {
+ LOGP(DMM, LOGL_INFO, "No cell found as returning to IDLE");
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_PLMN_SEARCH);
+
+ return 0;
+ }
+
+ /* 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 "
+ "IDLE");
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_PLMN_SEARCH);
+
+ return 0;
+ }
+
+ /* selected cell equals the registered LAI */
+ if (subscr->lai_valid && cs->state == GSM322_C3_CAMPED_NORMALLY
+ && cs->list[cs->arfcn].mcc == subscr->lai_mcc
+ && cs->list[cs->arfcn].mnc == subscr->lai_mnc
+ && cs->list[cs->arfcn].lac == subscr->lai_lac) {
+ LOGP(DMM, LOGL_INFO, "We are in registered LAI as returning "
+ "to IDLE");
+ /* 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 "
+ "IDLE");
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
+ GSM48_MM_SST_LOC_UPD_NEEDED);
+ } else {
+ LOGP(DMM, LOGL_INFO, "We are camping on any cell as returning "
+ "to IDLE");
+ new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
+ GSM48_MM_SST_LIMITED_SERVICE);
+ }
+
+ 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;
+
+ /* 4.2.1.1 */
+ mm->state = GSM48_MM_ST_MM_IDLE;
+ mm->substate = GSM48_MM_SST_PLMN_SEARCH;
+
+ /* 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;
+
+ /* 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", conn->ref,
+ gsm48_mmxx_state_names[conn->mm->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)", 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");
+
+ 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");
+ else
+ LOGP(DMM, LOGL_INFO, "Release pending MM Connections");
+
+ /* 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 *)
+ msgb_put(nmsg, sizeof(*nmmh));
+ 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)", 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 assigned.\n", 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;
+}
+
+/* 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);
+#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);
+
+ /* return to MM IDLE / No SIM */
+ gsm48_mm_return_idle(ms);
+
+ }
+
+ 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);
+ }
+
+ 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;
+
+ 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(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;
+
+ 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->arfcn >= 512 && rr->arfcn <= 885)
+ pwr_lev = sup->pwr_lev_1800;
+ else
+ pwr_lev = sup->pwr_lev_900;
+ gsm48_encode_classmark1(nmsg, sup->rev_lev, sup->es_ind, sup->a5_1,
+ pwr_lev);
+ /* MI */
+ if (subscr->tmsi_valid) /* have TMSI ? */
+ gsm48_encode_mi(nmsg, ms, GSM_MI_TYPE_TMSI);
+ else
+ gsm48_encode_mi(nmsg, ms, GSM_MI_TYPE_IMSI);
+
+ /* push RR header and send down */
+ return gsm48_mm_to_rr(ms, nmsg, rr_prim, RR_EST_CAUSE_OTHER_SDCCH);
+}
+
+/* detach has ended */
+static int gsm48_mm_imsi_detach_end(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct msgb *nmsg;
+
+ LOGP(DMM, LOGL_INFO, "IMSI has been detached.\n");
+
+ /* stop IMSI detach timer (if running) */
+ stop_mm_t3220(mm);
+
+ /* update SIM */
+#ifdef TODO
+ sim: store BA list
+ sim: what else?:
+#endif
+
+ /* SIM invalid */
+ subscr->sim_valid = 0;
+
+ /* send SIM remove event to gsm322 */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_SIM_REMOVE);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_SIM_REMOVE);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_cs_sendmsg(ms, nmsg);
+
+ /* return to MM IDLE / No SIM */
+ return gsm48_mm_return_idle(ms);
+}
+
+/* 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->sysinfo;
+
+ /* we may silently finish IMSI detach */
+ if (!s->att_allowed || !subscr->sim_att) {
+ 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->sysinfo;
+
+ /* 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->sim_att) {
+ 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
+
+ /* return to MM IDLE / No SIM */
+ gsm48_mm_return_idle(ms);
+ }
+
+ return 0;
+}
+
+/* 4.3.6.2 MM INFORMATION is received */
+static int gsm48_mm_rx_info(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ struct tlv_parsed tp;
+
+ if (payload_len < 0) {
+ LOGP(DMM, LOGL_NOTICE, "Short read of MM INFORMATION message "
+ "error.\n");
+ return -EINVAL;
+ }
+ tlv_parse(&tp, &gsm48_mm_att_tlvdef, gh->data, payload_len, 0, 0);
+
+ /* long name */
+ if (TLVP_PRESENT(&tp, GSM48_IE_NAME_LONG)) {
+ decode_network_name(mm->name_long, sizeof(mm->name_long),
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+ /* short name */
+ if (TLVP_PRESENT(&tp, GSM48_IE_NAME_SHORT)) {
+ decode_network_name(mm->name_short, sizeof(mm->name_short),
+ TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+ }
+
+ return 0;
+}
+
+/*
+ * process handlers for Location Update + IMSI attach (MM specific procedures)
+ */
+
+/* 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->sysinfo;
+
+ /* 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;
+ }
+
+ /* stop timer if not required anymore */
+ if (s->si3 && !s->t3212 && bsc_timer_pending(&mm->t3212))
+ stop_mm_t3212(mm);
+
+ 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 = &ms->sysinfo;
+ struct gsm_subscriber *subscr = &ms->subscr;
+
+ /* (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");
+ mm->lupd_pending = 0;
+ return 0;
+ }
+
+ /* if LAI is forbidden, don't start */
+ if (gsm322_is_forbidden_plmn(ms, cs->mcc, cs->mnc)) {
+ LOGP(DMM, LOGL_INFO, "Loc. upd. not allowed PLMN.\n");
+ mm->lupd_pending = 0;
+ return 0;
+ }
+ if (gsm322_is_forbidden_la(ms, cs->list[cs->arfcn].mcc,
+ cs->list[cs->arfcn].mnc, cs->list[cs->arfcn].lac)) {
+ LOGP(DMM, LOGL_INFO, "Loc. upd. not allowed LA.\n");
+ mm->lupd_pending = 0;
+ return 0;
+ }
+
+ /* 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");
+ mm->lupd_pending = 0;
+ return 0;
+ }
+
+ 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 = &ms->sysinfo;
+
+ /* 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;
+ }
+
+ /* 4.4.3 is attachment required? */
+ if (subscr->ustate == GSM_SIM_U1_UPDATED
+ && cs->state == GSM322_C3_CAMPED_NORMALLY
+ && cs->list[cs->arfcn].mcc == subscr->lai_mcc
+ && cs->list[cs->arfcn].mnc == subscr->lai_mnc
+ && cs->list[cs->arfcn].lac == subscr->lai_lac
+ && !subscr->sim_att
+ && s->att_allowed) {
+ /* do location update for IMSI attach */
+ mm->lupd_type = 2;
+ } else {
+ /* do normal location update */
+ 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 normal location update */
+ mm->lupd_type = 1;
+ mm->lupd_pending = 1;
+ mm->lupd_ra_failure = 0;
+
+ return gsm48_mm_loc_upd(ms, msg);
+}
+
+/* 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;
+ uint8_t pwr_lev;
+
+ 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) - 1);
+
+ 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->arfcn >= 512 && rr->arfcn <= 885)
+ pwr_lev = sup->pwr_lev_1800;
+ else
+ pwr_lev = sup->pwr_lev_900;
+ gsm48_encode_classmark1(nmsg, sup->rev_lev, sup->es_ind, sup->a5_1,
+ pwr_lev);
+ /* MI */
+ if (subscr->tmsi_valid) /* have TMSI ? */
+ gsm48_encode_mi(nmsg, ms, GSM_MI_TYPE_TMSI);
+ else
+ gsm48_encode_mi(nmsg, ms, GSM_MI_TYPE_IMSI);
+
+ 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 */
+ if (mm->lupd_type == 2)
+ subscr->sim_att = 1;
+
+ /* set the status in the sim to updated */
+ new_sim_ustate(subscr, GSM_SIM_U1_UPDATED);
+#ifdef TODO
+ sim: apply update state
+#endif
+
+ 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;
+
+ mi = TLVP_VAL(&tp, GSM48_IE_FACILITY)-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\n",
+ 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
+ }
+
+ /* 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);
+ 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);
+ 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:
+ case GSM48_REJECT_ILLEGAL_MS:
+ case GSM48_REJECT_ILLEGAL_ME:
+ break;
+ case GSM48_REJECT_PLMN_NOT_ALLOWED:
+ gsm322_add_forbidden_plmn(ms, subscr->lai_mcc,
+ subscr->lai_mnc, mm->lupd_rej_cause);
+ break;
+ case GSM48_REJECT_LOC_NOT_ALLOWED:
+ case GSM48_REJECT_ROAMING_NOT_ALLOWED:
+ gsm322_add_forbidden_la(ms, subscr->lai_mcc, subscr->lai_mnc,
+ subscr->lai_lac, mm->lupd_rej_cause);
+ break;
+ default:
+ /* 4.4.4.9 continue with failure handling */
+ return gsm48_mm_loc_upd_failed(ms);
+ }
+
+ /* return to IDLE, case 13 is also handled there */
+ return gsm48_mm_return_idle(ms);
+}
+
+/* delay a location update */
+static int gsm48_mm_loc_upd_delay(struct osmocom_ms *ms, struct msgb *msg)
+{
+ /* 4.2.2 in case we are not idle, periodic update is started when
+ * becomming idle. (Because the timer expired.)
+ */
+ 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 gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+
+ /* stop location update timer, if running */
+ stop_mm_t3210(mm);
+
+ if (subscr->ustate == GSM_SIM_U1_UPDATED
+ && cs->state == GSM322_C3_CAMPED_NORMALLY
+ && cs->list[cs->arfcn].mcc == subscr->lai_mcc
+ && cs->list[cs->arfcn].mnc == subscr->lai_mnc
+ && cs->list[cs->arfcn].lac == subscr->lai_lac
+ && mm->lupd_attempt < 4) {
+ LOGP(DMM, LOGL_INFO, "Loc. upd. failed, retry #%d\n",
+ mm->lupd_attempt);
+
+ /* start update retry timer */
+ start_mm_t3211(mm);
+
+ /* return to MM IDLE */
+ return gsm48_mm_return_idle(ms);
+ }
+ 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 */
+ if (mm->lupd_attempt < 4)
+ start_mm_t3211(mm);
+
+ /* return to MM IDLE */
+ return gsm48_mm_return_idle(ms);
+}
+
+/* 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;
+
+ 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 */
+ mm->lupd_ra_failure = 0;
+
+ /* continue with failure handling */
+ return gsm48_mm_loc_upd_failed(ms);
+}
+
+/* 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);
+}
+
+/*
+ * 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;
+ struct gsm48_service_request *nsr;
+ uint8_t *cm2lv;
+
+ 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));
+ nsr = (struct gsm48_service_request *)msgb_put(nmsg, sizeof(*nsr) - 1);
+ 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(nmsg, ms, GSM_MI_TYPE_IMEI);
+ else if (subscr->tmsi_valid) /* have TMSI ? */
+ gsm48_encode_mi(nmsg, ms, GSM_MI_TYPE_TMSI);
+ else
+ gsm48_encode_mi(nmsg, ms, GSM_MI_TYPE_IMSI);
+ /* 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
+ * (implicitly IDLE, 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_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;
+ }
+
+ /* 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_REQ, mmh->ref,
+ mmh->transaction_id);
+ break;
+ case GSM48_MMSS_EST_REQ:
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSS_REL_REQ, mmh->ref,
+ mmh->transaction_id);
+ break;
+ case GSM48_MMSMS_EST_REQ:
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSMS_REL_REQ, mmh->ref,
+ mmh->transaction_id);
+ break;
+ }
+ if (!nmsg)
+ return -ENOMEM;
+ nmmh = (struct gsm48_mmxx_hdr *)nmsg->data;
+ nmmh->cause = 17;
+ gsm48_mmxx_upmsg(ms, nmsg);
+
+ return 0;
+}
+
+/* accepting pending connection, got dedicated mode
+ *
+ * this function is called:
+ * - when ciphering command is received
+ * - when cm service is accepted
+ */
+static int gsm48_mm_conn_go_dedic(struct osmocom_ms *ms)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_mm_conn *conn, *conn_found = NULL;
+ struct msgb *nmsg;
+ struct gsm48_mmxx_hdr *nmmh;
+
+ /* the first and only pending connection is the recent requested */
+ llist_for_each_entry(conn, &mm->mm_conn, list) {
+ if (conn->state == GSM48_MMXX_ST_CONN_PEND) {
+ conn_found = conn;
+ break;
+ }
+ }
+
+ /* if no pending connection (anymore) */
+ if (!conn_found) {
+ LOGP(DMM, LOGL_INFO, "No pending MM Connection.\n");
+
+ return 0;
+ }
+
+ new_conn_state(conn, GSM48_MMXX_ST_DEDICATED);
+
+ /* send establishment confirm */
+ nmsg = NULL;
+ switch(conn_found->protocol) {
+ case GSM48_PDISC_CC:
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_EST_CNF, conn->ref,
+ conn->transaction_id);
+ break;
+ case GSM48_PDISC_NC_SS:
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSS_EST_CNF, conn->ref,
+ conn->transaction_id);
+ break;
+ case GSM48_PDISC_SMS:
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSMS_EST_CNF, conn->ref,
+ conn->transaction_id);
+ break;
+ }
+ if (!nmsg)
+ return -ENOMEM;
+ nmmh = (struct gsm48_mmxx_hdr *)nmsg->data;
+ nmmh->cause = 17;
+ gsm48_mmxx_upmsg(ms, nmsg);
+
+ return 0;
+}
+
+/* a RR-SYNC-IND is received during MM connection establishment */
+static int gsm48_mm_sync_ind_wait(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ /* stop MM connection timer */
+ stop_mm_t3230(mm);
+
+ return gsm48_mm_conn_go_dedic(ms);
+}
+
+/* a RR-SYNC-IND is received during MM connection active */
+static int gsm48_mm_sync_ind_active(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_mm_conn *conn;
+ struct msgb *nmsg;
+ struct gsm48_mmxx_hdr *nmmh;
+
+ /* stop MM connection timer */
+ stop_mm_t3230(mm);
+
+ /* broadcast all MMCC connection(s) */
+ llist_for_each_entry(conn, &mm->mm_conn, list) {
+ /* send MMCC-SYNC-IND */
+ nmsg = NULL;
+ switch(conn->protocol) {
+ case GSM48_PDISC_CC:
+ nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_SYNC_IND,
+ conn->ref, conn->transaction_id);
+ break;
+ }
+ if (!nmsg)
+ continue; /* skip if not of CC type */
+ nmmh = (struct gsm48_mmxx_hdr *)nmsg->data;
+ nmmh->cause = 17;
+ /* copy L3 message */
+ nmsg->l3h = msgb_put(nmsg, msgb_l3len(msg));
+ memcpy(nmsg->l3h, msg->l3h, msgb_l3len(msg));
+ gsm48_mmxx_upmsg(ms, nmsg);
+ }
+
+ return 0;
+}
+
+/* 4.5.1.2 RR abort 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;
+
+ /* stop MM connection timer */
+ stop_mm_t3230(mm);
+
+ /* release all connections */
+ gsm48_mm_release_mm_conn(ms, 1, 16, 1);
+
+ /* return to MM IDLE */
+ return gsm48_mm_return_idle(ms);
+}
+
+/* 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);
+
+ /* return to MM IDLE / No SIM */
+ gsm48_mm_return_idle(ms);
+
+ return 0;
+}
+
+/*
+ * other processes
+ */
+
+/* RR is released in other states */
+static int gsm48_mm_rel_other(struct osmocom_ms *ms, struct msgb *msg)
+{
+ return gsm48_mm_return_idle(ms);
+}
+
+/*
+ * 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 */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE),
+ GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr},
+ /* 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.4 PLMN search */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH),
+ GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr},
+ /* 4.5.1.1 MM Connection (EST) */
+ {SBIT(GSM48_MM_ST_RR_CONN_RELEASE_NA), ALL_STATES,
+ GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_first},
+ {SBIT(GSM48_MM_ST_RR_CONN_RELEASE_NA), ALL_STATES,
+ GSM48_MMSS_EST_REQ, gsm48_mm_init_mm_first},
+ {SBIT(GSM48_MM_ST_RR_CONN_RELEASE_NA), ALL_STATES,
+ GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_first},
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES,
+ GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_more},
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES,
+ GSM48_MMSS_EST_REQ, gsm48_mm_init_mm_more},
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES,
+ GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_more},
+ {SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES,
+ GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_wait},
+ {SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES,
+ GSM48_MMSS_EST_REQ, gsm48_mm_init_mm_wait},
+ {SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES,
+ GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_wait},
+ {ALL_STATES, ALL_STATES,
+ GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_reject},
+ {ALL_STATES, ALL_STATES,
+ GSM48_MMSS_EST_REQ, gsm48_mm_init_mm_reject},
+ {ALL_STATES, ALL_STATES,
+ GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_reject},
+ /* 4.5.2.1 MM Connection (DATA) */
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) |
+ SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES,
+ GSM48_MMCC_DATA_REQ, gsm48_mm_data},
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) |
+ SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES,
+ GSM48_MMSS_DATA_REQ, gsm48_mm_data},
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) |
+ SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES,
+ GSM48_MMSMS_DATA_REQ, gsm48_mm_data},
+ /* 4.5.2.1 MM Connection (REL) */
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES,
+ GSM48_MMCC_REL_REQ, gsm48_mm_release_active},
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES,
+ GSM48_MMSS_REL_REQ, gsm48_mm_release_active},
+ {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES,
+ GSM48_MMSMS_REL_REQ, gsm48_mm_release_active},
+ {SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES,
+ GSM48_MMCC_REL_REQ, gsm48_mm_release_wait_add},
+ {SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES,
+ GSM48_MMSS_REL_REQ, gsm48_mm_release_wait_add},
+ {SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES,
+ GSM48_MMSMS_REL_REQ, gsm48_mm_release_wait_add},
+ {SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN), ALL_STATES,
+ GSM48_MMCC_REL_REQ, gsm48_mm_release_wait_active},
+ {SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN), ALL_STATES,
+ GSM48_MMSS_REL_REQ, gsm48_mm_release_wait_active},
+ {SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN), ALL_STATES,
+ GSM48_MMSMS_REL_REQ, gsm48_mm_release_wait_active},
+ {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON), ALL_STATES,
+ GSM48_MMCC_REL_REQ, gsm48_mm_release_wait_rr},
+ {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON), ALL_STATES,
+ GSM48_MMSS_REL_REQ, gsm48_mm_release_wait_rr},
+ {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON), ALL_STATES,
+ GSM48_MMSMS_REL_REQ, gsm48_mm_release_wait_rr},
+};
+
+#define DOWNSLLEN \
+ (sizeof(downstatelist) / sizeof(struct downstate))
+
+int gsm48_mmxx_downmsg(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+ struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data;
+ int msg_type = mmh->msg_type;
+ struct gsm48_mm_conn *conn;
+ int i, rc;
+
+ /* keep up to date with the transaction ID */
+ conn = mm_conn_by_ref(mm, mmh->ref);
+ if (conn)
+ conn->transaction_id = mmh->transaction_id;
+
+ LOGP(DMM, LOGL_INFO, "(ms %s) Received '%s' event in state %s",
+ ms->name, get_mmxx_name(msg_type),
+ gsm48_mm_state_names[mm->state]);
+ if (mm->state == GSM48_MM_ST_MM_IDLE)
+ LOGP(DMM, LOGL_INFO, " substate %s",
+ gsm48_mm_substate_names[mm->substate]);
+ LOGP(DMM, LOGL_INFO, "\n");
+
+ /* 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},
+ {GSM48_MM_ST_LOC_UPD_INIT, /* 4.4.4.6 */
+ GSM48_MT_MM_LOC_UPD_ACCEPT, gsm48_mm_rx_loc_upd_acc},
+ {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;
+
+ /* 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;
+ 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;
+ }
+ 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(DRR, LOGL_NOTICE, "Protocol type 0x%02x unsupported.\n",
+ pdisc);
+ msgb_free(msg);
+ return gsm48_mm_tx_mm_status(ms,
+ GSM48_REJECT_MSG_TYPE_NOT_IMPLEMENTED);
+ }
+
+ LOGP(DMM, LOGL_INFO, "(ms %s) Received '%s' in MM state %s\n", ms->name,
+ get_mm_name(msg_type), gsm48_mm_state_names[mm->state]);
+
+ stop_mm_t3212(mm); /* 4.4.2 */
+
+ /* 11.2 re-start pending RR release timer */
+ if (bsc_timer_pending(&mm->t3240)) {
+ stop_mm_t3240(mm);
+ start_mm_t3240(mm);
+ }
+
+ /* find function for current state and message */
+ for (i = 0; i < MMDATASLLEN; i++) {
+ if (msg_type == mmdatastatelist[i].type)
+ msg_supported = 1;
+ if ((msg_type == mmdatastatelist[i].type)
+ && ((1 << mm->state) & mmdatastatelist[i].states))
+ break;
+ }
+ if (i == MMDATASLLEN) {
+ msgb_free(msg);
+ if (msg_supported) {
+ LOGP(DMM, LOGL_NOTICE, "Message unhandled at this "
+ "state.\n");
+ return gsm48_mm_tx_mm_status(ms,
+ GSM48_REJECT_MSG_TYPE_NOT_COMPATIBLE);
+ } else {
+ LOGP(DMM, LOGL_NOTICE, "Message not supported.\n");
+ return gsm48_mm_tx_mm_status(ms,
+ GSM48_REJECT_MSG_TYPE_NOT_IMPLEMENTED);
+ }
+ }
+
+ rc = mmdatastatelist[i].rout(ms, msg);
+
+ msgb_free(msg);
+
+ return rc;
+}
+
+/* state trasitions for mobile management events */
+static struct eventstate {
+ uint32_t states;
+ uint32_t substates;
+ int type;
+ int (*rout) (struct osmocom_ms *ms, struct msgb *msg);
+} eventstatelist[] = {
+ /* 4.2.2.1 Normal service */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE),
+ GSM48_MM_EVENT_NEW_LAI, gsm48_mm_loc_upd_normal},
+ {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 */
+ {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_NEW_LAI, gsm48_mm_loc_upd_normal},
+ {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_NEW_LAI, gsm48_mm_loc_upd_normal},
+ /* 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_TIMEOUT_T3211, gsm48_mm_loc_upd},
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL),
+ GSM48_MM_EVENT_TIMEOUT_T3213, gsm48_mm_loc_upd},
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL),
+ GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_delay},
+ {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.4 PLMN search */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH),
+ GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_delay},
+ /* No cell available */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NO_CELL_AVAIL),
+ GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_delay},
+ /* IMSI detach in other cases */
+ {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NO_IMSI), /* no SIM */
+ GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_end},
+ {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 - SBIT(GSM48_MM_ST_MM_IDLE), ALL_STATES,
+ GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_delay},
+ {ALL_STATES - SBIT(GSM48_MM_ST_MM_IDLE), ALL_STATES,
+ GSM48_MM_EVENT_TIMEOUT_T3210, gsm48_mm_loc_upd_timeout},
+ /* 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;
+
+ LOGP(DMM, LOGL_INFO, "(ms %s) Received '%s' event in state %s",
+ ms->name, get_mmevent_name(msg_type),
+ gsm48_mm_state_names[mm->state]);
+ if (mm->state == GSM48_MM_ST_MM_IDLE)
+ LOGP(DMM, LOGL_INFO, " substate %s",
+ gsm48_mm_substate_names[mm->substate]);
+ LOGP(DMM, LOGL_INFO, "\n");
+
+ /* 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 msgb *nmsg;
+
+ /* schedule insertion of sim */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_SIM_INSERT);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+
+ return 0;
+}
+
+/* trigger detach of sim card */
+static int gsm48_mmr_nreg_req(struct osmocom_ms *ms)
+{
+ struct msgb *nmsg;
+
+ /* schedule removal of sim */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_SIM_REMOVE);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_plmn_sendmsg(ms, nmsg);
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_SIM_REMOVE);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_cs_sendmsg(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", 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;
+}
+
+
diff --git a/src/host/layer23/src/gsm48_rr.c b/src/host/layer23/src/gsm48_rr.c
new file mode 100644
index 0000000..f4ed2fc
--- /dev/null
+++ b/src/host/layer23/src/gsm48_rr.c
@@ -0,0 +1,3594 @@
+/*
+ * (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.
+ *
+ */
+
+/* Very short description of some of the procedures:
+ *
+ * A radio ressource request causes sendig a channel request on RACH.
+ * After receiving of an immediate assignment the link will be establised.
+ * After the link is established, the dedicated mode is entered and confirmed.
+ *
+ * A Paging request also triggers the channel request as above...
+ * After the link is established, the dedicated mode is entered and indicated.
+ *
+ * During dedicated mode, messages are transferred.
+ *
+ * When an assignment command or a handover command is received, the current
+ * link is released. After release, the new channel is activated and the
+ * link is established again. After link is establised, pending messages from
+ * radio ressource are sent.
+ *
+ * When the assignment or handover fails, the old channel is activate and the
+ * link is established again. Also pending messages are sent.
+ *
+ */
+
+#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/rsl.h>
+#include <osmocore/gsm48.h>
+#include <osmocore/bitvec.h>
+
+#include <osmocom/logging.h>
+#include <osmocom/osmocom_data.h>
+
+static int gsm48_rcv_rsl(struct osmocom_ms *ms, struct msgb *msg);
+static int gsm48_rr_dl_est(struct osmocom_ms *ms);
+
+/*
+ * support
+ */
+
+#define MIN(a, b) ((a < b) ? a : b)
+
+int gsm48_decode_lai(struct gsm48_loc_area_id *lai, uint16_t *mcc,
+ uint16_t *mnc, uint16_t *lac)
+{
+ *mcc = bcd2char(lai->digits[0] & 0x0f) * 100
+ + bcd2char(lai->digits[0] >> 4) * 10
+ + bcd2char(lai->digits[1] & 0x0f);
+ *mnc = bcd2char(lai->digits[1] >> 4) * 100
+ + bcd2char(lai->digits[2] & 0x0f) * 10
+ + bcd2char(lai->digits[2] >> 4);
+ *lac = ntohs(lai->lac);
+
+ return 0;
+}
+
+static int gsm48_encode_chan_h0(struct gsm48_chan_desc *cd, uint8_t tsc,
+ uint16_t arfcn)
+{
+ cd->h0.tsc = tsc;
+ cd->h0.h = 0;
+ cd->h0.arfcn_low = arfcn & 0xff;
+ cd->h0.arfcn_high = arfcn >> 8;
+
+ return 0;
+}
+
+static int gsm48_encode_chan_h1(struct gsm48_chan_desc *cd, uint8_t tsc,
+ uint8_t maio, uint8_t hsn)
+{
+ cd->h1.tsc = tsc;
+ cd->h1.h = 1;
+ cd->h1.maio_low = maio & 0x03;
+ cd->h1.maio_high = maio >> 2;
+ cd->h1.hsn = hsn;
+
+ return 0;
+}
+
+
+static int gsm48_decode_chan_h0(struct gsm48_chan_desc *cd, uint8_t *tsc,
+ uint16_t *arfcn)
+{
+ *tsc = cd->h0.tsc;
+ *arfcn = cd->h0.arfcn_low | (cd->h0.arfcn_high << 8);
+
+ return 0;
+}
+
+static int gsm48_decode_chan_h1(struct gsm48_chan_desc *cd, uint8_t *tsc,
+ uint8_t *maio, uint8_t *hsn)
+{
+ *tsc = cd->h1.tsc;
+ *maio = cd->h1.maio_low | (cd->h1.maio_high << 2);
+ *hsn = cd->h1.hsn;
+
+ return 0;
+}
+
+/* 10.5.2.38 decode Starting time IE */
+static int gsm48_decode_start_time(struct gsm48_rr_cd *cd,
+ struct gsm48_start_time *st)
+{
+ cd->start_t1 = st->t1;
+ cd->start_t2 = st->t2;
+ cd->start_t3 = (st->t3_high << 3) | st->t3_low;
+
+ return 0;
+}
+
+/*
+ * state transition
+ */
+
+static const char *gsm48_rr_state_names[] = {
+ "IDLE",
+ "CONN PEND",
+ "DEDICATED",
+};
+
+static void new_rr_state(struct gsm48_rrlayer *rr, int state)
+{
+ if (state < 0 || state >=
+ (sizeof(gsm48_rr_state_names) / sizeof(char *)))
+ return;
+
+ if (state == GSM48_RR_ST_IDLE) {
+ struct msgb *msg, *nmsg;
+
+ /* free establish message, if any */
+ rr->rr_est_req = 0;
+ if (rr->rr_est_msg) {
+ msgb_free(rr->rr_est_msg);
+ rr->rr_est_msg = NULL;
+ }
+ /* free all pending messages */
+ while((msg = msgb_dequeue(&rr->downqueue)))
+ msgb_free(msg);
+ /* clear all descriptions of last channel */
+ memset(&rr->cd_now, 0, sizeof(rr->cd_now));
+ /* reset cipering */
+ rr->cipher_on = 0;
+ /* tell cell selection process to return to idle mode
+ * NOTE: this must be sent unbuffered, because it will
+ * leave camping state, so it locks against subsequent
+ * establishment of dedicated channel, before the
+ * cell selection process returned to camping state
+ * again.
+ */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_LEAVE_IDLE);
+ if (!nmsg)
+ return;
+ gsm322_c_event(rr->ms, nmsg);
+ msgb_free(nmsg);
+ }
+
+ LOGP(DRR, LOGL_INFO, "new state %s -> %s\n",
+ gsm48_rr_state_names[rr->state], gsm48_rr_state_names[state]);
+
+ rr->state = state;
+}
+
+/*
+ * messages
+ */
+
+/* names of RR-SAP */
+static const struct value_string gsm48_rr_msg_names[] = {
+ { GSM48_RR_EST_REQ, "RR_EST_REQ" },
+ { GSM48_RR_EST_IND, "RR_EST_IND" },
+ { GSM48_RR_EST_CNF, "RR_EST_CNF" },
+ { GSM48_RR_REL_IND, "RR_REL_IND" },
+ { GSM48_RR_SYNC_IND, "RR_SYNC_IND" },
+ { GSM48_RR_DATA_REQ, "RR_DATA_REQ" },
+ { GSM48_RR_DATA_IND, "RR_DATA_IND" },
+ { GSM48_RR_UNIT_DATA_IND, "RR_UNIT_DATA_IND" },
+ { GSM48_RR_ABORT_REQ, "RR_ABORT_REQ" },
+ { GSM48_RR_ABORT_IND, "RR_ABORT_IND" },
+ { GSM48_RR_ACT_REQ, "RR_ACT_REQ" },
+ { 0, NULL }
+};
+
+const char *get_rr_name(int value)
+{
+ return get_value_string(gsm48_rr_msg_names, value);
+}
+
+/* allocate GSM 04.08 layer 3 message */
+struct msgb *gsm48_l3_msgb_alloc(void)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(L3_ALLOC_SIZE+L3_ALLOC_HEADROOM,
+ L3_ALLOC_HEADROOM, "GSM 04.08 L3");
+ if (!msg)
+ return NULL;
+ msg->l3h = msg->data;
+
+ return msg;
+}
+
+/* allocate GSM 04.08 message (RR-SAP) */
+struct msgb *gsm48_rr_msgb_alloc(int msg_type)
+{
+ struct msgb *msg;
+ struct gsm48_rr_hdr *rrh;
+
+ msg = msgb_alloc_headroom(RR_ALLOC_SIZE+RR_ALLOC_HEADROOM,
+ RR_ALLOC_HEADROOM, "GSM 04.08 RR");
+ if (!msg)
+ return NULL;
+
+ rrh = (struct gsm48_rr_hdr *) msgb_put(msg, sizeof(*rrh));
+ rrh->msg_type = msg_type;
+
+ return msg;
+}
+
+/* queue message (RR-SAP) */
+int gsm48_rr_upmsg(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_mmlayer *mm = &ms->mmlayer;
+
+ msgb_enqueue(&mm->rr_upqueue, msg);
+
+ return 0;
+}
+
+/* push rsl header and send (RSL-SAP) */
+static int gsm48_send_rsl(struct osmocom_ms *ms, uint8_t msg_type,
+ struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+
+ rsl_rll_push_l3(msg, msg_type, rr->cd_now.chan_nr,
+ rr->cd_now.link_id, 1);
+
+ return rslms_recvmsg(msg, ms);
+}
+
+/* enqueue messages (RSL-SAP) */
+static int gsm48_rx_rll(struct msgb *msg, struct osmocom_ms *ms)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+
+ msgb_enqueue(&rr->rsl_upqueue, msg);
+
+ return 0;
+}
+
+/* input function that L2 calls when sending messages up to L3 */
+static int gsm48_rx_rsl(struct msgb *msg, struct osmocom_ms *ms)
+{
+ struct abis_rsl_common_hdr *rslh = msgb_l2(msg);
+ int rc = 0;
+
+ switch (rslh->msg_discr & 0xfe) {
+ case ABIS_RSL_MDISC_RLL:
+ rc = gsm48_rx_rll(msg, ms);
+ break;
+ default:
+ /* FIXME: implement this */
+ LOGP(DRSL, LOGL_NOTICE, "unknown RSLms msg_discr 0x%02x\n",
+ rslh->msg_discr);
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+/* dequeue messages (RSL-SAP) */
+int gsm48_rsl_dequeue(struct osmocom_ms *ms)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct msgb *msg;
+ int work = 0;
+
+ while ((msg = msgb_dequeue(&rr->rsl_upqueue))) {
+ /* msg is freed there */
+ gsm48_rcv_rsl(ms, msg);
+ work = 1; /* work done */
+ }
+
+ return work;
+}
+
+/*
+ * timers handling
+ */
+
+static void timeout_rr_t3122(void *arg)
+{
+ LOGP(DRR, LOGL_INFO, "timer T3122 has fired\n");
+}
+
+static void timeout_rr_t3126(void *arg)
+{
+ struct gsm48_rrlayer *rr = arg;
+ struct osmocom_ms *ms = rr->ms;
+
+ LOGP(DRR, LOGL_INFO, "timer T3126 has fired\n");
+ if (rr->rr_est_req) {
+ struct msgb *msg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND);
+ struct gsm48_rr_hdr *rrh;
+
+ if (!msg)
+ return;
+ rrh = (struct gsm48_rr_hdr *)msg->data;
+ rrh->cause = RR_REL_CAUSE_RA_FAILURE;
+ gsm48_rr_upmsg(ms, msg);
+ }
+
+ new_rr_state(rr, GSM48_RR_ST_IDLE);
+}
+
+static void start_rr_t3122(struct gsm48_rrlayer *rr, int sec, int micro)
+{
+ LOGP(DRR, LOGL_INFO, "starting T3122 with %d seconds\n", sec);
+ rr->t3122.cb = timeout_rr_t3122;
+ rr->t3122.data = rr;
+ bsc_schedule_timer(&rr->t3122, sec, micro);
+}
+
+static void start_rr_t3126(struct gsm48_rrlayer *rr, int sec, int micro)
+{
+ LOGP(DRR, LOGL_INFO, "starting T3126 with %d seconds\n", sec);
+ rr->t3126.cb = timeout_rr_t3126;
+ rr->t3126.data = rr;
+ bsc_schedule_timer(&rr->t3126, sec, micro);
+}
+
+static void stop_rr_t3122(struct gsm48_rrlayer *rr)
+{
+ if (bsc_timer_pending(&rr->t3122)) {
+ LOGP(DRR, LOGL_INFO, "stopping pending timer T3122\n");
+ bsc_del_timer(&rr->t3122);
+ }
+}
+
+static void stop_rr_t3126(struct gsm48_rrlayer *rr)
+{
+ if (bsc_timer_pending(&rr->t3126)) {
+ LOGP(DRR, LOGL_INFO, "stopping pending timer T3126\n");
+ bsc_del_timer(&rr->t3126);
+ }
+}
+
+/*
+ * status
+ */
+
+/* send rr status request */
+static int gsm48_rr_tx_rr_status(struct osmocom_ms *ms, uint8_t cause)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+ struct gsm48_rr_status *st;
+
+ LOGP(DRR, LOGL_INFO, "RR STATUS (cause #%d)\n", cause);
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+ st = (struct gsm48_rr_status *) msgb_put(nmsg, sizeof(*st));
+
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_CIPH_M_COMPL;
+
+ /* rr cause */
+ st->rr_cause = cause;
+
+ return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg);
+}
+
+/*
+ * ciphering
+ */
+
+/* send chiperhing mode complete */
+static int gsm48_rr_tx_cip_mode_cpl(struct osmocom_ms *ms, uint8_t cr)
+{
+ struct gsm_support *sup = &ms->support;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+ uint8_t buf[11], *tlv;
+
+ LOGP(DRR, LOGL_INFO, "CIPHERING MODE COMPLETE (cr %d)\n", cr);
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_CIPH_M_COMPL;
+
+ /* MI */
+ if (cr) {
+ gsm48_generate_mid_from_imsi(buf, sup->imeisv);
+ tlv = msgb_put(nmsg, 2 + buf[1]);
+ memcpy(tlv, buf, 2 + buf[1]);
+ }
+
+ return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg);
+}
+
+/* receive ciphering mode command */
+static int gsm48_rr_rx_cip_mode_cmd(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct gsm_support *sup = &ms->support;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct gsm48_cip_mode_cmd *cm = (struct gsm48_cip_mode_cmd *)gh->data;
+ int payload_len = msgb_l3len(msg) - sizeof(*gh) - sizeof(*cm);
+ uint8_t sc, alg_id, cr;
+
+ if (payload_len < 0) {
+ LOGP(DRR, LOGL_NOTICE, "Short read of CIPHERING MODE COMMAND "
+ "message.\n");
+ return gsm48_rr_tx_rr_status(ms,
+ GSM48_RR_CAUSE_PROT_ERROR_UNSPC);
+ }
+
+ /* cipher mode setting */
+ sc = cm->sc;
+ alg_id = cm->alg_id;
+ /* cipher mode response */
+ cr = cm->cr;
+
+ if (sc)
+ LOGP(DRR, LOGL_INFO, "CIPHERING MODE COMMAND (sc=%u, cr=%u)",
+ sc, cr);
+ else
+ LOGP(DRR, LOGL_INFO, "CIPHERING MODE COMMAND (sc=%u, "
+ "algo=A5/%d cr=%u)", sc, alg_id + 1, cr);
+
+ /* 3.4.7.2 */
+ if (rr->cipher_on && sc) {
+ LOGP(DRR, LOGL_INFO, "cipering already applied.\n");
+ return gsm48_rr_tx_rr_status(ms,
+ GSM48_RR_CAUSE_PROT_ERROR_UNSPC);
+ }
+
+ /* check if we actually support this cipher */
+ if ((alg_id == GSM_CIPHER_A5_1 && !sup->a5_1)
+ || (alg_id == GSM_CIPHER_A5_2 && !sup->a5_2)
+ || (alg_id == GSM_CIPHER_A5_3 && !sup->a5_3)
+ || (alg_id == GSM_CIPHER_A5_4 && !sup->a5_4)
+ || (alg_id == GSM_CIPHER_A5_5 && !sup->a5_5)
+ || (alg_id == GSM_CIPHER_A5_6 && !sup->a5_6)
+ || (alg_id == GSM_CIPHER_A5_7 && !sup->a5_7))
+ return gsm48_rr_tx_rr_status(ms,
+ GSM48_RR_CAUSE_CHAN_MODE_UNACCT);
+
+ /* change to ciphering */
+#ifdef TODO
+ rsl command to activate ciperhing
+#endif
+ rr->cipher_on = sc, rr->cipher_type = alg_id;
+
+ /* response */
+ return gsm48_rr_tx_cip_mode_cpl(ms, cr);
+}
+
+/*
+ * classmark
+ */
+
+/* Encode "Classmark 3" (10.5.1.7) */
+static int gsm48_rr_enc_cm3(struct osmocom_ms *ms, uint8_t *buf, uint8_t *len)
+{
+ struct gsm_support *sup = &ms->support;
+ struct bitvec bv;
+
+ memset(&bv, 0, sizeof(bv));
+ bv.data = buf;
+ bv.data_len = 12;
+
+ /* spare bit */
+ bitvec_set_bit(&bv, 0);
+ /* band 3 supported */
+ if (sup->dcs_1800)
+ bitvec_set_bit(&bv, ONE);
+ else
+ bitvec_set_bit(&bv, ZERO);
+ /* band 2 supported */
+ if (sup->e_gsm || sup->r_gsm)
+ bitvec_set_bit(&bv, ONE);
+ else
+ bitvec_set_bit(&bv, ZERO);
+ /* band 1 supported */
+ if (sup->p_gsm && !(sup->e_gsm || sup->r_gsm))
+ bitvec_set_bit(&bv, ONE);
+ else
+ bitvec_set_bit(&bv, ZERO);
+ /* a5 bits */
+ if (sup->a5_7)
+ bitvec_set_bit(&bv, ONE);
+ else
+ bitvec_set_bit(&bv, ZERO);
+ if (sup->a5_6)
+ bitvec_set_bit(&bv, ONE);
+ else
+ bitvec_set_bit(&bv, ZERO);
+ if (sup->a5_5)
+ bitvec_set_bit(&bv, ONE);
+ else
+ bitvec_set_bit(&bv, ZERO);
+ if (sup->a5_4)
+ bitvec_set_bit(&bv, ONE);
+ else
+ bitvec_set_bit(&bv, ZERO);
+ /* radio capability */
+ if (sup->dcs_1800 && !sup->p_gsm && !(sup->e_gsm || sup->r_gsm)) {
+ /* dcs only */
+ bitvec_set_uint(&bv, 0, 4);
+ bitvec_set_uint(&bv, sup->dcs_capa, 4);
+ } else
+ if (sup->dcs_1800 && (sup->p_gsm || (sup->e_gsm || sup->r_gsm))) {
+ /* dcs */
+ bitvec_set_uint(&bv, sup->dcs_capa, 4);
+ /* low band */
+ bitvec_set_uint(&bv, sup->low_capa, 4);
+ } else {
+ /* low band only */
+ bitvec_set_uint(&bv, 0, 4);
+ bitvec_set_uint(&bv, sup->low_capa, 4);
+ }
+ /* r support */
+ if (sup->r_gsm) {
+ bitvec_set_bit(&bv, ONE);
+ bitvec_set_uint(&bv, sup->r_capa, 3);
+ } else {
+ bitvec_set_bit(&bv, ZERO);
+ }
+ /* multi slot support */
+ if (sup->ms_sup) {
+ bitvec_set_bit(&bv, ONE);
+ bitvec_set_uint(&bv, sup->ms_sup, 5);
+ } else {
+ bitvec_set_bit(&bv, ZERO);
+ }
+ /* ucs2 treatment */
+ if (sup->ucs2_treat) {
+ bitvec_set_bit(&bv, ONE);
+ } else {
+ bitvec_set_bit(&bv, ZERO);
+ }
+ /* support extended measurements */
+ if (sup->ext_meas) {
+ bitvec_set_bit(&bv, ONE);
+ } else {
+ bitvec_set_bit(&bv, ZERO);
+ }
+ /* support measurement capability */
+ if (sup->meas_cap) {
+ bitvec_set_bit(&bv, ONE);
+ bitvec_set_uint(&bv, sup->sms_val, 4);
+ bitvec_set_uint(&bv, sup->sm_val, 4);
+ } else {
+ bitvec_set_bit(&bv, ZERO);
+ }
+ /* positioning method capability */
+ if (sup->loc_serv) {
+ bitvec_set_bit(&bv, ONE);
+ bitvec_set_bit(&bv, sup->e_otd_ass == 1);
+ bitvec_set_bit(&bv, sup->e_otd_based == 1);
+ bitvec_set_bit(&bv, sup->gps_ass == 1);
+ bitvec_set_bit(&bv, sup->gps_based == 1);
+ bitvec_set_bit(&bv, sup->gps_conv == 1);
+ } else {
+ bitvec_set_bit(&bv, ZERO);
+ }
+
+ /* partitial bytes will be completed */
+ *len = (bv.cur_bit + 7) >> 3;
+ bitvec_spare_padding(&bv, (*len * 8) - 1);
+
+ return 0;
+}
+
+/* encode classmark 2 */
+int gsm48_rr_enc_cm2(struct osmocom_ms *ms, struct gsm48_classmark2 *cm)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct gsm_support *sup = &ms->support;
+
+ if (rr->arfcn >= 512 && rr->arfcn <= 885)
+ cm->pwr_lev = sup->pwr_lev_1800;
+ else
+ cm->pwr_lev = sup->pwr_lev_900;
+ cm->a5_1 = sup->a5_1;
+ cm->es_ind = sup->es_ind;
+ cm->rev_lev = sup->rev_lev;
+ cm->fc = (sup->r_gsm || sup->e_gsm);
+ cm->vgcs = sup->vgcs;
+ cm->vbs = sup->vbs;
+ cm->sm_cap = sup->sms_ptp;
+ cm->ss_scr = sup->ss_ind;
+ cm->ps_cap = sup->ps_cap;
+ cm->a5_2 = sup->a5_2;
+ cm->a5_3 = sup->a5_3;
+ cm->cmsp = sup->cmsp;
+ cm->solsa = sup->solsa;
+ cm->lcsva_cap = sup->lcsva;
+
+ return 0;
+}
+
+/* send classmark change */
+static int gsm48_rr_tx_cm_change(struct osmocom_ms *ms)
+{
+ struct gsm_support *sup = &ms->support;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+ struct gsm48_cm_change *cc;
+ uint8_t cm3[14], *tlv;
+
+ LOGP(DRR, LOGL_INFO, "CLASSMARK CHANGE\n");
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+ cc = (struct gsm48_cm_change *) msgb_put(nmsg, sizeof(*cc));
+
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_CLSM_CHG;
+
+ /* classmark 2 */
+ cc->cm2_len = sizeof(cc->cm2);
+ gsm48_rr_enc_cm2(ms, &cc->cm2);
+
+ /* classmark 3 */
+ if (sup->dcs_1800 || sup->e_gsm || sup->r_gsm
+ || sup->a5_7 || sup->a5_6 || sup->a5_5 || sup->a5_4
+ || sup->ms_sup
+ || sup->ucs2_treat
+ || sup->ext_meas || sup->meas_cap
+ || sup->loc_serv) {
+ cc->cm2.cm3 = 1;
+ cm3[0] = GSM48_IE_CLASSMARK3;
+ gsm48_rr_enc_cm3(ms, cm3 + 2, &cm3[1]);
+ tlv = msgb_put(nmsg, 2 + cm3[1]);
+ memcpy(tlv, cm3, 2 + cm3[1]);
+ }
+
+ return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg);
+}
+
+/* receiving classmark enquiry */
+static int gsm48_rr_rx_cm_enq(struct osmocom_ms *ms, struct msgb *msg)
+{
+ /* send classmark */
+ return gsm48_rr_tx_cm_change(ms);
+}
+
+/*
+ * random access
+ */
+
+/* temporary timer until we have time control over channnel request */
+#define RSL_MT_CHAN_CNF 0x19
+#include <osmocom/l1ctl.h>
+static void temp_rach_to(void *arg)
+{
+ struct gsm48_rrlayer *rr = arg;
+ struct osmocom_ms *ms = rr->ms;
+ struct msgb *msg = msgb_alloc_headroom(23+10, 10, "LAPDm RR");
+ struct abis_rsl_rll_hdr *rllh = (struct abis_rsl_rll_hdr *) msgb_put(msg, sizeof(*rllh));
+
+ rllh->c.msg_type = RSL_MT_CHAN_CNF;
+ gsm48_rcv_rsl(ms, msg);
+
+ return;
+}
+
+/* send channel request burst message */
+static int gsm48_rr_tx_chan_req(struct osmocom_ms *ms, int cause, int paging)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+ struct msgb *nmsg;
+ struct l1ctl_info_ul *nul;
+ struct l1ctl_rach_req *nra;
+ struct gsm48_rr_hdr *nrrh;
+ uint8_t chan_req, chan_req_val, chan_req_mask;
+ int t;
+
+ /* ignore paging, if not camping */
+ if (paging && cs->state != GSM322_C3_CAMPED_NORMALLY) {
+ return -EBUSY;
+ }
+
+ /* tell cell selection process to leave idle mode
+ * NOTE: this must be sent unbuffered, because the state may not
+ * change until idle mode is left
+ */
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_LEAVE_IDLE);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm322_c_event(ms, nmsg);
+ msgb_free(nmsg);
+
+ /* 3.3.1.1.2 */
+ new_rr_state(rr, GSM48_RR_ST_CONN_PEND);
+
+ /* number of retransmissions (without first transmission) */
+ rr->n_chan_req = s->max_retrans;
+
+ /* generate CHAN REQ (9.1.8) */
+ switch (cause) {
+ case RR_EST_CAUSE_EMERGENCY:
+ /* 101xxxxx */
+ chan_req_mask = 0x1f;
+ chan_req_val = 0xa0;
+ LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (Emergency call)\n",
+ chan_req_val);
+ break;
+ case RR_EST_CAUSE_REESTAB_TCH_F:
+ chan_req_mask = 0x1f;
+ chan_req_val = 0xc0;
+ LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (re-establish "
+ "TCH/F)\n", chan_req_val);
+ break;
+ case RR_EST_CAUSE_REESTAB_TCH_H:
+ if (s->neci) {
+ chan_req_mask = 0x03;
+ chan_req_val = 0x68;
+ LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x "
+ "(re-establish TCH/H with NECI)\n",
+ chan_req_val);
+ } else {
+ chan_req_mask = 0x1f;
+ chan_req_val = 0xc0;
+ LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x "
+ "(re-establish TCH/H no NECI)\n", chan_req_val);
+ }
+ break;
+ case RR_EST_CAUSE_REESTAB_2_TCH_H:
+ if (s->neci) {
+ chan_req_mask = 0x03;
+ chan_req_val = 0x6c;
+ LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x "
+ "(re-establish TCH/H+TCH/H with NECI)\n",
+ chan_req_val);
+ } else {
+ chan_req_mask = 0x1f;
+ chan_req_val = 0xc0;
+ LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x "
+ "(re-establish TCH/H+TCH/H no NECI)\n",
+ chan_req_val);
+ }
+ break;
+ case RR_EST_CAUSE_ANS_PAG_ANY:
+ chan_req_mask = 0x1f;
+ chan_req_val = 0x80;
+ LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (PAGING "
+ "Any channel)\n", chan_req_val);
+ break;
+ case RR_EST_CAUSE_ANS_PAG_SDCCH:
+ chan_req_mask = 0x0f;
+ chan_req_val = 0x10;
+ LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (PAGING SDCCH)\n",
+ chan_req_val);
+ break;
+ case RR_EST_CAUSE_ANS_PAG_TCH_F:
+ /* ms supports no dual rate */
+ chan_req_mask = 0x1f;
+ chan_req_val = 0x80;
+ LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (PAGING TCH/F)\n",
+ chan_req_val);
+ break;
+ case RR_EST_CAUSE_ANS_PAG_TCH_ANY:
+ /* ms supports no dual rate */
+ chan_req_mask = 0x1f;
+ chan_req_val = 0x80;
+ LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (PAGING TCH/H or "
+ "TCH/F)\n", chan_req_val);
+ break;
+ case RR_EST_CAUSE_ORIG_TCHF:
+ /* ms supports no dual rate */
+ chan_req_mask = 0x1f;
+ chan_req_val = 0xe0;
+ LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (Orig TCH/F)\n",
+ chan_req_val);
+ break;
+ case RR_EST_CAUSE_LOC_UPD:
+ if (s->neci) {
+ chan_req_mask = 0x0f;
+ chan_req_val = 0x00;
+ LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (Location "
+ "Update with NECI)\n", chan_req_val);
+ } else {
+ chan_req_mask = 0x1f;
+ chan_req_val = 0x00;
+ LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (Location "
+ "Update no NECI)\n", chan_req_val);
+ }
+ break;
+ case RR_EST_CAUSE_OTHER_SDCCH:
+ if (s->neci) {
+ chan_req_mask = 0x0f;
+ chan_req_val = 0x01;
+ LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (OHTER "
+ "with NECI)\n", chan_req_val);
+ } else {
+ chan_req_mask = 0x1f;
+ chan_req_val = 0xe0;
+ LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (OTHER "
+ "no NECI)\n", chan_req_val);
+ }
+ break;
+ default:
+ if (!rr->rr_est_req) /* no request from MM */
+ return -EINVAL;
+
+ LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: with unknown "
+ "establishment cause: %d\n", cause);
+ nmsg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND);
+ if (!nmsg)
+ return -ENOMEM;
+ nrrh = (struct gsm48_rr_hdr *)nmsg->data;
+ nrrh->cause = RR_REL_CAUSE_UNDEFINED;
+ gsm48_rr_upmsg(ms, nmsg);
+ new_rr_state(rr, GSM48_RR_ST_IDLE);
+ return -EINVAL;
+ }
+
+ rr->wait_assign = 1;
+
+ /* create and send RACH msg */
+#ifdef TODO
+ nmsg = gsm48_rsl_msgb_alloc();
+#else
+ nmsg = msgb_alloc_headroom(64, 48, "RAND_ACC");
+ struct l1ctl_hdr *l1h;
+ nmsg->l1h = msgb_put(nmsg, sizeof(*l1h));
+ l1h = (struct l1ctl_hdr *) nmsg->l1h;
+ l1h->msg_type = L1CTL_RACH_REQ;
+ if (!nmsg)
+ return -ENOMEM;
+ nul = (struct l1ctl_info_ul *) msgb_put(nmsg, sizeof(*nul));
+#endif
+ nra = (struct l1ctl_rach_req *) msgb_put(nmsg, sizeof(*nra));
+
+ chan_req = random();
+ chan_req &= chan_req_mask;
+ chan_req |= chan_req_val;
+ nra->ra = chan_req;
+ t = s->tx_integer;
+ if (t < 8)
+ t = 8;
+#ifdef TODO
+ at this point we require chan req to be sent at a given delay
+ also we require a confirm from radio part
+ nra->delay = random() % t;
+#else
+ rr->temp_rach_ti.cb = temp_rach_to;
+ rr->temp_rach_ti.data = rr;
+ bsc_schedule_timer(&rr->temp_rach_ti, 0, 900000);
+#endif
+
+ /* store */
+ rr->chan_req_val = chan_req_val;
+ rr->chan_req_mask = chan_req_mask;
+ rr->cr_hist[3] = -1;
+ rr->cr_hist[2] = -1;
+ rr->cr_hist[1] = chan_req;
+
+#ifdef TODO
+ add layer 1 conrols to RSL...
+ return gsm48_send_rsl(ms, RSL_MT_CHAN_REQ, nmsg);
+#else
+ return osmo_send_l1(ms, nmsg);
+#endif
+}
+
+/* send next channel request in conn pend state */
+static int gsm48_rr_rand_acc_cnf(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct gsm48_sysinfo *sys = &ms->sysinfo;
+ struct msgb *nmsg;
+ struct l1ctl_info_ul *nul;
+ struct l1ctl_rach_req *nra;
+ int s;
+ uint8_t chan_req;
+
+ LOGP(DRR, LOGL_INFO, "RANDOM ACCESS confirm (requests left %d)\n",
+ rr->n_chan_req);
+
+ if (!rr->n_chan_req) {
+ LOGP(DRR, LOGL_INFO, "Done with sending RANDOM ACCESS "
+ "bursts\n");
+ if (!bsc_timer_pending(&rr->t3126))
+ start_rr_t3126(rr, 5, 0); /* TODO improve! */
+ return 0;
+ }
+ rr->n_chan_req--;
+
+ /* table 3.1 */
+ switch(sys->tx_integer) {
+ case 3: case 8: case 14: case 50:
+ if (sys->ccch_conf != 1) /* not combined CCCH */
+ s = 55;
+ else
+ s = 41;
+ case 4: case 9: case 16:
+ if (sys->ccch_conf != 1)
+ s = 76;
+ else
+ s = 52;
+ case 5: case 10: case 20:
+ if (sys->ccch_conf != 1)
+ s = 109;
+ else
+ s = 58;
+ case 6: case 11: case 25:
+ if (sys->ccch_conf != 1)
+ s = 163;
+ else
+ s = 86;
+ default:
+ if (sys->ccch_conf != 1)
+ s = 217;
+ else
+ s = 115;
+ }
+
+ /* resend chan_req with new randiom */
+#ifdef TODO
+ nmsg = gsm48_rsl_msgb_alloc();
+#else
+ nmsg = msgb_alloc_headroom(64, 48, "RAND_ACC");
+ struct l1ctl_hdr *l1h;
+ nmsg->l1h = msgb_put(nmsg, sizeof(*l1h));
+ l1h = (struct l1ctl_hdr *) nmsg->l1h;
+ l1h->msg_type = L1CTL_RACH_REQ;
+ if (!nmsg)
+ return -ENOMEM;
+ nul = (struct l1ctl_info_ul *) msgb_put(nmsg, sizeof(*nul));
+#endif
+ nra = (struct l1ctl_rach_req *) msgb_put(nmsg, sizeof(*nra));
+ chan_req = random();
+ chan_req &= rr->chan_req_mask;
+ chan_req |= rr->chan_req_val;
+ nra->ra = chan_req;
+#ifdef TODO
+ at this point we require chan req to be sent at a given delay
+ also we require a confirm from radio part
+ nra->delay = (random() % sys->tx_integer) + s;
+
+ LOGP(DRR, LOGL_INFO, "RANDOM ACCESS (ra 0x%02x delay %d)\n", nra->ra,
+ nra->delay);
+#else
+ rr->temp_rach_ti.cb = temp_rach_to;
+ rr->temp_rach_ti.data = rr;
+ bsc_schedule_timer(&rr->temp_rach_ti, 0, 900000);
+
+ LOGP(DRR, LOGL_INFO, "RANDOM ACCESS (ra 0x%02x)\n", nra->ra);
+#endif
+
+ /* shift history and store */
+ rr->cr_hist[3] = rr->cr_hist[2];
+ rr->cr_hist[2] = rr->cr_hist[1];
+ rr->cr_hist[1] = chan_req;
+
+#ifdef TODO
+ add layer 1 conrols to RSL...
+ return gsm48_send_rsl(ms, RSL_MT_CHAN_REQ, nmsg);
+#else
+ return osmo_send_l1(ms, nmsg);
+#endif
+}
+
+/*
+ * system information
+ */
+
+/* decode "Cell Channel Description" (10.5.2.1b) and other frequency lists */
+static int gsm48_decode_freq_list(struct gsm_support *sup,
+ struct gsm_sysinfo_freq *f, uint8_t *cd, uint8_t len, uint8_t mask,
+ uint8_t frqt)
+{
+ int i;
+
+ /* NOTES:
+ *
+ * The Range format uses "SMOD" computation.
+ * e.g. "n SMOD m" equals "((n - 1) % m) + 1"
+ * A cascade of multiple SMOD computations is simpified:
+ * "(n SMOD m) SMOD o" equals "(((n - 1) % m) % o) + 1"
+ *
+ * The Range format uses 16 octets of data in SYSTEM INFORMATION.
+ * When used in dedicated messages, the length can be less.
+ * In this case the ranges are decoded for all frequencies that
+ * fit in the block of given length.
+ */
+
+ /* tabula rasa */
+ for (i = 0; i < 1024; i++)
+ f[i].mask &= ~frqt;
+
+ /* 00..XXX. */
+ if ((cd[0] & 0xc0 & mask) == 0x00) {
+ /* Bit map 0 format */
+ if (len < 16)
+ return -EINVAL;
+ for (i = 1; i <= 124; i++)
+ if ((cd[15 - ((i-1) >> 3)] & (1 << ((i-1) & 7))))
+ f[i].mask |= frqt;
+
+ return 0;
+ }
+
+ /* only Bit map 0 format for P-GSM */
+ if (sup->p_gsm && !sup->e_gsm && !sup->r_gsm && !sup->dcs_1800)
+ return 0;
+
+ /* 10..0XX. */
+ if ((cd[0] & 0xc8 & mask) == 0x80) {
+ /* Range 1024 format */
+ uint16_t w[17]; /* 1..16 */
+ struct gsm48_range_1024 *r = (struct gsm48_range_1024 *)cd;
+
+ if (len < 2)
+ return -EINVAL;
+ memset(w, 0, sizeof(w));
+ if (r->f0)
+ f[0].mask |= frqt;
+ w[1] = (r->w1_hi << 8) | r->w1_lo;
+ if (len >= 4)
+ w[2] = (r->w2_hi << 1) | r->w2_lo;
+ if (len >= 5)
+ w[3] = (r->w3_hi << 2) | r->w3_lo;
+ if (len >= 6)
+ w[4] = (r->w4_hi << 2) | r->w4_lo;
+ if (len >= 7)
+ w[5] = (r->w5_hi << 2) | r->w5_lo;
+ if (len >= 8)
+ w[6] = (r->w6_hi << 2) | r->w6_lo;
+ if (len >= 9)
+ w[7] = (r->w7_hi << 2) | r->w7_lo;
+ if (len >= 10)
+ w[8] = (r->w8_hi << 1) | r->w8_lo;
+ if (len >= 10)
+ w[9] = r->w9;
+ if (len >= 11)
+ w[10] = r->w10;
+ if (len >= 12)
+ w[11] = (r->w11_hi << 6) | r->w11_lo;
+ if (len >= 13)
+ w[12] = (r->w12_hi << 5) | r->w12_lo;
+ if (len >= 14)
+ w[13] = (r->w13_hi << 4) | r->w13_lo;
+ if (len >= 15)
+ w[14] = (r->w14_hi << 3) | r->w14_lo;
+ if (len >= 16)
+ w[15] = (r->w15_hi << 2) | r->w15_lo;
+ if (len >= 16)
+ w[16] = r->w16;
+ if (w[1])
+ f[w[1]].mask |= frqt;
+ if (w[2])
+ f[((w[1] - 512 + w[2] - 1) % 1023) + 1].mask |= frqt;
+ if (w[3])
+ f[((w[1] + w[3] - 1) % 1023) + 1].mask |= frqt;
+ if (w[4])
+ f[((w[1] - 512 + ((w[2] - 256 + w[4] - 1) % 511)) % 1023) + 1].mask |= frqt;
+ if (w[5])
+ f[((w[1] + ((w[3] - 256 - w[5] - 1) % 511)) % 1023) + 1].mask |= frqt;
+ if (w[6])
+ f[((w[1] - 512 + ((w[2] + w[6] - 1) % 511)) % 1023) + 1].mask |= frqt;
+ if (w[7])
+ f[((w[1] + ((w[3] + w[7] - 1) % 511)) % 1023) + 1].mask |= frqt;
+ if (w[8])
+ f[((w[1] - 512 + ((w[2] - 256 + ((w[4] - 128 + w[8] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt;
+ if (w[9])
+ f[((w[1] + ((w[3] - 256 + ((w[5] - 128 + w[9] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt;
+ if (w[10])
+ f[((w[1] - 512 + ((w[2] + ((w[6] - 128 + w[10] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt;
+ if (w[11])
+ f[((w[1] + ((w[3] + ((w[7] - 128 + w[11] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt;
+ if (w[12])
+ f[((w[1] - 512 + ((w[2] - 256 + ((w[4] + w[12] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt;
+ if (w[13])
+ f[((w[1] + ((w[3] - 256 + ((w[5] + w[13] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt;
+ if (w[14])
+ f[((w[1] - 512 + ((w[2] + ((w[6] + w[14] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt;
+ if (w[15])
+ f[((w[1] + ((w[3] + ((w[7] + w[15] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt;
+ if (w[16])
+ f[((w[1] - 512 + ((w[2] - 256 + ((w[4] - 128 + ((w[8] - 64 + w[16] - 1) % 127)) % 255)) % 511)) % 1023) + 1].mask |= frqt;
+
+ return 0;
+ }
+ /* 10..100. */
+ if ((cd[0] & 0xce & mask) == 0x88) {
+ /* Range 512 format */
+ uint16_t w[18]; /* 1..17 */
+ struct gsm48_range_512 *r = (struct gsm48_range_512 *)cd;
+
+ if (len < 4)
+ return -EINVAL;
+ memset(w, 0, sizeof(w));
+ w[0] = (r->orig_arfcn_hi << 9) || (r->orig_arfcn_mid << 1) || r->orig_arfcn_lo;
+ w[1] = (r->w1_hi << 2) || r->w1_lo;
+ if (len >= 5)
+ w[2] = (r->w2_hi << 2) || r->w2_lo;
+ if (len >= 6)
+ w[3] = (r->w3_hi << 2) || r->w3_lo;
+ if (len >= 7)
+ w[4] = (r->w4_hi << 1) || r->w4_lo;
+ if (len >= 7)
+ w[5] = r->w5;
+ if (len >= 8)
+ w[6] = r->w6;
+ if (len >= 9)
+ w[7] = (r->w7_hi << 6) || r->w7_lo;
+ if (len >= 10)
+ w[8] = (r->w8_hi << 4) || r->w8_lo;
+ if (len >= 11)
+ w[9] = (r->w9_hi << 2) || r->w9_lo;
+ if (len >= 11)
+ w[10] = r->w10;
+ if (len >= 12)
+ w[11] = r->w11;
+ if (len >= 13)
+ w[12] = (r->w12_hi << 4) || r->w12_lo;
+ if (len >= 14)
+ w[13] = (r->w13_hi << 2) || r->w13_lo;
+ if (len >= 14)
+ w[14] = r->w14;
+ if (len >= 15)
+ w[15] = r->w15;
+ if (len >= 16)
+ w[16] = (r->w16_hi << 3) || r->w16_lo;
+ if (len >= 16)
+ w[17] = r->w17;
+ if (w[0])
+ f[w[0]].mask |= frqt;
+ if (w[1])
+ f[(w[0] + w[1]) % 1024].mask |= frqt;
+ if (w[2])
+ f[(w[0] + ((w[1] - 256 + w[2] - 1) % 511) + 1) % 1024].mask |= frqt;
+ if (w[3])
+ f[(w[0] + ((w[1] + w[3] - 1) % 511) + 1) % 1024].mask |= frqt;
+ if (w[4])
+ f[(w[0] + ((w[1] - 256 + ((w[2] - 128 + w[4] - 1) % 255)) % 511) + 1) % 1024].mask |= frqt;
+ if (w[5])
+ f[(w[0] + ((w[1] + ((w[3] - 128 + w[5] - 1) % 255)) % 511) + 1) % 1024].mask |= frqt;
+ if (w[6])
+ f[(w[0] + ((w[1] - 256 + ((w[2] + w[6] - 1) % 255)) % 511) + 1) % 1024].mask |= frqt;
+ if (w[7])
+ f[(w[0] + ((w[1] + ((w[3] + w[7] - 1) % 255)) % 511) + 1) % 1024].mask |= frqt;
+ if (w[8])
+ f[(w[0] + ((w[1] - 256 + ((w[2] - 128 + ((w[4] - 64 + w[8] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
+ if (w[9])
+ f[(w[0] + ((w[1] + ((w[3] - 128 + ((w[5] - 64 + w[9] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
+ if (w[10])
+ f[(w[0] + ((w[1] - 256 + ((w[2] + ((w[6] - 64 + w[10] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
+ if (w[11])
+ f[(w[0] + ((w[1] + ((w[3] + ((w[7] - 64 + w[11] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
+ if (w[12])
+ f[(w[0] + ((w[1] - 256 + ((w[2] - 128 + ((w[4] + w[12] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
+ if (w[13])
+ f[(w[0] + ((w[1] + ((w[3] - 128 + ((w[5] + w[13] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
+ if (w[14])
+ f[(w[0] + ((w[1] - 256 + ((w[2] + ((w[6] + w[14] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
+ if (w[15])
+ f[(w[0] + ((w[1] + ((w[3] + ((w[7] + w[15] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
+ if (w[16])
+ f[(w[0] + ((w[1] - 256 + ((w[2] - 128 + ((w[4] - 64 + ((w[8] - 32 + w[16] - 1) % 63)) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
+ if (w[17])
+ f[(w[0] + ((w[1] + ((w[3] - 128 + ((w[5] - 64 + ((w[9] - 32 + w[17] - 1) % 63)) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
+
+ return 0;
+ }
+ /* 10..101. */
+ if ((cd[0] & 0xce & mask) == 0x8a) {
+ /* Range 256 format */
+ uint16_t w[22]; /* 1..21 */
+ struct gsm48_range_256 *r = (struct gsm48_range_256 *)cd;
+
+ if (len < 4)
+ return -EINVAL;
+ memset(w, 0, sizeof(w));
+ w[0] = (r->orig_arfcn_hi << 9) || (r->orig_arfcn_mid << 1) || r->orig_arfcn_lo;
+ w[1] = (r->w1_hi << 1) || r->w1_lo;
+ if (len >= 4)
+ w[2] = r->w2;
+ if (len >= 5)
+ w[3] = r->w3;
+ if (len >= 6)
+ w[4] = (r->w4_hi << 5) || r->w4_lo;
+ if (len >= 7)
+ w[5] = (r->w5_hi << 3) || r->w5_lo;
+ if (len >= 8)
+ w[6] = (r->w6_hi << 1) || r->w6_lo;
+ if (len >= 8)
+ w[7] = r->w7;
+ if (len >= 9)
+ w[8] = (r->w8_hi << 4) || r->w8_lo;
+ if (len >= 10)
+ w[9] = (r->w9_hi << 1) || r->w9_lo;
+ if (len >= 10)
+ w[10] = r->w10;
+ if (len >= 11)
+ w[11] = (r->w11_hi << 3) || r->w11_lo;
+ if (len >= 11)
+ w[12] = r->w12;
+ if (len >= 12)
+ w[13] = r->w13;
+ if (len >= 13)
+ w[14] = r->w15;
+ if (len >= 13)
+ w[15] = (r->w14_hi << 2) || r->w14_lo;
+ if (len >= 14)
+ w[16] = (r->w16_hi << 3) || r->w16_lo;
+ if (len >= 14)
+ w[17] = r->w17;
+ if (len >= 15)
+ w[18] = r->w19;
+ if (len >= 15)
+ w[19] = (r->w18_hi << 3) || r->w18_lo;
+ if (len >= 16)
+ w[20] = (r->w20_hi << 3) || r->w20_lo;
+ if (len >= 16)
+ w[21] = r->w21;
+ if (w[0])
+ f[w[0]].mask |= frqt;
+ if (w[1])
+ f[(w[0] + w[1]) % 1024].mask |= frqt;
+ if (w[2])
+ f[(w[0] + ((w[1] - 128 + w[2] - 1) % 255) + 1) % 1024].mask |= frqt;
+ if (w[3])
+ f[(w[0] + ((w[1] + w[3] - 1) % 255) + 1) % 1024].mask |= frqt;
+ if (w[4])
+ f[(w[0] + ((w[1] - 128 + ((w[2] - 64 + w[4] - 1) % 127)) % 255) + 1) % 1024].mask |= frqt;
+ if (w[5])
+ f[(w[0] + ((w[1] + ((w[3] - 64 + w[5] - 1) % 127)) % 255) + 1) % 1024].mask |= frqt;
+ if (w[6])
+ f[(w[0] + ((w[1] - 128 + ((w[2] + w[6] - 1) % 127)) % 255) + 1) % 1024].mask |= frqt;
+ if (w[7])
+ f[(w[0] + ((w[1] + ((w[3] + w[7] - 1) % 127)) % 255) + 1) % 1024].mask |= frqt;
+ if (w[8])
+ f[(w[0] + ((w[1] - 128 + ((w[2] - 64 + ((w[4] - 32 + w[8] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
+ if (w[9])
+ f[(w[0] + ((w[1] + ((w[3] - 64 + ((w[5] - 32 + w[9] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
+ if (w[10])
+ f[(w[0] + ((w[1] - 128 + ((w[2] + ((w[6] - 32 + w[10] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
+ if (w[11])
+ f[(w[0] + ((w[1] + ((w[3] + ((w[7] - 32 + w[11] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
+ if (w[12])
+ f[(w[0] + ((w[1] - 128 + ((w[2] - 64 + ((w[4] + w[12] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
+ if (w[13])
+ f[(w[0] + ((w[1] + ((w[3] - 64 + ((w[5] + w[13] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
+ if (w[14])
+ f[(w[0] + ((w[1] - 128 + ((w[2] + ((w[6] + w[14] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
+ if (w[15])
+ f[(w[0] + ((w[1] + ((w[3] + ((w[7] + w[15] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
+ if (w[16])
+ f[(w[0] + ((w[1] - 128 + ((w[2] - 64 + ((w[4] - 32 + ((w[8] - 16 + w[16] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
+ if (w[17])
+ f[(w[0] + ((w[1] + ((w[3] - 64 + ((w[5] - 32 + ((w[9] - 16 + w[17] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
+ if (w[18])
+ f[(w[0] + ((w[1] - 128 + ((w[2] + ((w[6] - 32 + ((w[10] - 16 + w[18] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
+ if (w[19])
+ f[(w[0] + ((w[1] + ((w[3] + ((w[7] - 32 + ((w[11] - 16 + w[19] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
+ if (w[20])
+ f[(w[0] + ((w[1] - 128 + ((w[2] - 64 + ((w[4] + ((w[12] - 16 + w[20] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
+ if (w[21])
+ f[(w[0] + ((w[1] + ((w[3] - 64 + ((w[5] + ((w[13] - 16 + w[21] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
+
+ return 0;
+ }
+ /* 10..110. */
+ if ((cd[0] & 0xce & mask) == 0x8c) {
+ /* Range 128 format */
+ uint16_t w[29]; /* 1..28 */
+ struct gsm48_range_128 *r = (struct gsm48_range_128 *)cd;
+
+ if (len < 3)
+ return -EINVAL;
+ memset(w, 0, sizeof(w));
+ w[0] = (r->orig_arfcn_hi << 9) || (r->orig_arfcn_mid << 1) || r->orig_arfcn_lo;
+ w[1] = r->w1;
+ if (len >= 4)
+ w[2] = r->w2;
+ if (len >= 5)
+ w[3] = (r->w3_hi << 4) || r->w3_lo;
+ if (len >= 6)
+ w[4] = (r->w4_hi << 1) || r->w4_lo;
+ if (len >= 6)
+ w[5] = r->w5;
+ if (len >= 7)
+ w[6] = (r->w6_hi << 3) || r->w6_lo;
+ if (len >= 7)
+ w[7] = r->w7;
+ if (len >= 8)
+ w[8] = r->w8;
+ if (len >= 8)
+ w[9] = r->w9;
+ if (len >= 9)
+ w[10] = r->w10;
+ if (len >= 9)
+ w[11] = r->w11;
+ if (len >= 10)
+ w[12] = r->w12;
+ if (len >= 10)
+ w[13] = r->w13;
+ if (len >= 11)
+ w[14] = r->w14;
+ if (len >= 11)
+ w[15] = r->w15;
+ if (len >= 12)
+ w[16] = r->w16;
+ if (len >= 12)
+ w[17] = r->w17;
+ if (len >= 13)
+ w[18] = (r->w18_hi << 1) || r->w18_lo;
+ if (len >= 13)
+ w[19] = r->w19;
+ if (len >= 13)
+ w[20] = r->w20;
+ if (len >= 14)
+ w[21] = (r->w21_hi << 2) || r->w21_lo;
+ if (len >= 14)
+ w[22] = r->w22;
+ if (len >= 14)
+ w[23] = r->w23;
+ if (len >= 15)
+ w[24] = r->w24;
+ if (len >= 15)
+ w[25] = r->w25;
+ if (len >= 16)
+ w[26] = (r->w26_hi << 1) || r->w26_lo;
+ if (len >= 16)
+ w[27] = r->w27;
+ if (len >= 16)
+ w[28] = r->w28;
+ if (w[0])
+ f[w[0]].mask |= frqt;
+ if (w[1])
+ f[(w[0] + w[1]) % 1024].mask |= frqt;
+ if (w[2])
+ f[(w[0] + ((w[1] - 64 + w[2] - 1) % 127) + 1) % 1024].mask |= frqt;
+ if (w[3])
+ f[(w[0] + ((w[1] + w[3] - 1) % 127) + 1) % 1024].mask |= frqt;
+ if (w[4])
+ f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + w[4] - 1) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[5])
+ f[(w[0] + ((w[1] + ((w[3] - 32 + w[5] - 1) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[6])
+ f[(w[0] + ((w[1] - 64 + ((w[2] + w[6] - 1) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[7])
+ f[(w[0] + ((w[1] + ((w[3] + w[7] - 1) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[8])
+ f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4] - 16 + w[8] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[9])
+ f[(w[0] + ((w[1] + ((w[3] - 32 + ((w[5] - 16 + w[9] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[10])
+ f[(w[0] + ((w[1] - 64 + ((w[2] + ((w[6] - 16 + w[10] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[11])
+ f[(w[0] + ((w[1] + ((w[3] + ((w[7] - 16 + w[11] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[12])
+ f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4] + w[12] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[13])
+ f[(w[0] + ((w[1] + ((w[3] - 32 + ((w[5] + w[13] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[14])
+ f[(w[0] + ((w[1] - 64 + ((w[2] + ((w[6] + w[14] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[15])
+ f[(w[0] + ((w[1] + ((w[3] + ((w[7] + w[15] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[16])
+ f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4] - 16 + ((w[8] - 8 + w[16] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[17])
+ f[(w[0] + ((w[1] + ((w[3] - 32 + ((w[5] - 16 + ((w[9] - 8 + w[17] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[18])
+ f[(w[0] + ((w[1] - 64 + ((w[2] + ((w[6] - 16 + ((w[10] - 8 + w[18] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[19])
+ f[(w[0] + ((w[1] + ((w[3] + ((w[7] - 16 + ((w[11] - 8 + w[19] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[20])
+ f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4] + ((w[12] - 8 + w[20] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[21])
+ f[(w[0] + ((w[1] + ((w[3] - 32 + ((w[5] + ((w[13] - 8 + w[21] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[22])
+ f[(w[0] + ((w[1] - 64 + ((w[2] + ((w[6] + ((w[14] - 8 + w[22] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[23])
+ f[(w[0] + ((w[1] + ((w[3] + ((w[7] + ((w[15] - 8 + w[23] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[24])
+ f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4] - 16 + ((w[8] + w[24] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[25])
+ f[(w[0] + ((w[1] + ((w[3] - 32 + ((w[5] - 16 + ((w[9] + w[25] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[26])
+ f[(w[0] + ((w[1] - 64 + ((w[2] + ((w[6] - 16 + ((w[10] + w[26] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[27])
+ f[(w[0] + ((w[1] + ((w[3] + ((w[7] - 16 + ((w[11] + w[27] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+ if (w[28])
+ f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4] + ((w[12] + w[28] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
+
+ return 0;
+ }
+ /* 10..111. */
+ if ((cd[0] & 0xce & mask) == 0x8e) {
+ /* Variable bitmap format (can be any length >= 3) */
+ uint16_t orig = 0;
+ struct gsm48_var_bit *r = (struct gsm48_var_bit *)cd;
+
+ if (len < 3)
+ return -EINVAL;
+ orig = (r->orig_arfcn_hi << 9) || (r->orig_arfcn_mid << 1) || r->orig_arfcn_lo;
+ f[orig].mask |= frqt;
+ for (i = 1; 2 + (i >> 3) < len; i++)
+ if ((cd[2 + (i >> 3)] & (0x80 >> (i & 7))))
+ f[(orig + 1) % 1024].mask |= frqt;
+
+ return 0;
+ }
+
+ return 0;
+}
+
+/* decode "Cell Selection Parameters" (10.5.2.4) */
+static int gsm48_decode_cell_sel_param(struct gsm48_sysinfo *s,
+ struct gsm48_cell_sel_par *cs)
+{
+#ifdef TODO
+ convert ms_txpwr_max_ccch dependant on the current frequenc and support
+ to the right powe level
+#endif
+ s->ms_txpwr_max_ccch = cs->ms_txpwr_max_ccch;
+ s->cell_resel_hyst_db = cs->cell_resel_hyst * 2;
+ s->rxlev_acc_min_db = cs->rxlev_acc_min - 110;
+ s->neci = cs->neci;
+ s->acs = cs->acs;
+
+ return 0;
+}
+
+/* decode "Cell Options (BCCH)" (10.5.2.3) */
+static int gsm48_decode_cellopt_bcch(struct gsm48_sysinfo *s,
+ struct gsm48_cell_options *co)
+{
+ s->bcch_radio_link_timeout = (co->radio_link_timeout + 1) * 4;
+ s->bcch_dtx = co->dtx;
+ s->bcch_pwrc = co->pwrc;
+
+ return 0;
+}
+
+/* decode "Cell Options (SACCH)" (10.5.2.3a) */
+static int gsm48_decode_cellopt_sacch(struct gsm48_sysinfo *s,
+ struct gsm48_cell_options *co)
+{
+ s->sacch_radio_link_timeout = (co->radio_link_timeout + 1) * 4;
+ s->sacch_dtx = co->dtx;
+ s->sacch_pwrc = co->pwrc;
+
+ return 0;
+}
+
+/* decode "Cell Channel Description" (10.5.2.11) */
+static int gsm48_decode_ccd(struct gsm48_sysinfo *s,
+ struct gsm48_control_channel_descr *cc)
+{
+ s->ccch_conf = cc->ccch_conf;
+ s->bs_ag_blks_res = cc->bs_ag_blks_res;
+ s->att_allowed = cc->att;
+ s->pag_mf_periods = cc->bs_pa_mfrms + 2;
+ s->t3212 = cc->t3212 * 360; /* convert deci-hours to seconds */
+
+ return 0;
+}
+
+/* decode "Mobile Allocation" (10.5.2.21) */
+static int gsm48_decode_mobile_alloc(struct gsm48_sysinfo *s,
+ uint8_t *ma, uint8_t len)
+{
+ int i, j = 0;
+ uint16_t f[len << 3];
+
+ /* not more than 64 hopping indexes allowed in IE */
+ if (len > 8)
+ return -EINVAL;
+
+ /* tabula rasa */
+ s->hopp_len = 0;
+ for (i = 0; i < 1024; i++)
+ s->freq[i].mask &= ~FREQ_TYPE_HOPP;
+
+ /* generating list of all frequencies (1..1023,0) */
+ for (i = 1; i <= 1024; i++) {
+ if ((s->freq[i & 1023].mask & FREQ_TYPE_SERV)) {
+ f[j++] = i & 1023;
+ if (j == (len << 3))
+ break;
+ }
+ }
+
+ /* fill hopping table with frequency index given by IE
+ * and set hopping type bits
+ */
+ for (i = 0; i < (len << 3); i++) {
+ /* if bit is set, this frequency index is used for hopping */
+ if ((ma[len - 1 - (i >> 3)] & (1 << (i & 7)))) {
+ /* index higher than entries in list ? */
+ if (i >= j) {
+ LOGP(DRR, LOGL_NOTICE, "Mobile Allocation "
+ "hopping index %d exceeds maximum "
+ "number of cell frequencies. (%d)\n",
+ i + 1, j);
+ break;
+ }
+ s->hopping[s->hopp_len++] = f[i];
+ s->freq[f[i]].mask |= FREQ_TYPE_HOPP;
+ }
+ }
+
+ return 0;
+}
+
+/* Rach Control decode tables */
+static uint8_t gsm48_max_retrans[4] = {
+ 1, 2, 4, 7
+};
+static uint8_t gsm48_tx_integer[16] = {
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 25, 32, 50
+};
+
+/* decode "RACH Control Parameter" (10.5.2.29) */
+static int gsm48_decode_rach_ctl_param(struct gsm48_sysinfo *s,
+ struct gsm48_rach_control *rc)
+{
+ s->reest_denied = rc->re;
+ s->cell_barr = rc->cell_bar;
+ s->tx_integer = gsm48_tx_integer[rc->tx_integer];
+ s->max_retrans = gsm48_max_retrans[rc->max_trans];
+ s->class_barr = (rc->t2 << 8) | rc->t3;
+
+ return 0;
+}
+static int gsm48_decode_rach_ctl_neigh(struct gsm48_sysinfo *s,
+ struct gsm48_rach_control *rc)
+{
+ s->nb_reest_denied = rc->re;
+ s->nb_cell_barr = rc->cell_bar;
+ s->nb_tx_integer = gsm48_tx_integer[rc->tx_integer];
+ s->nb_max_retrans = gsm48_max_retrans[rc->max_trans];
+ s->nb_class_barr = (rc->t2 << 8) | rc->t3;
+
+ return 0;
+}
+
+/* decode "SI 1 Rest Octets" (10.5.2.32) */
+static int gsm48_decode_si1_rest(struct gsm48_sysinfo *s, uint8_t *si,
+ uint8_t len)
+{
+ return 0;
+}
+
+/* decode "SI 3 Rest Octets" (10.5.2.34) */
+static int gsm48_decode_si3_rest(struct gsm48_sysinfo *s, uint8_t *si,
+ uint8_t len)
+{
+ return 0;
+}
+
+/* decode "SI 4 Rest Octets" (10.5.2.35) */
+static int gsm48_decode_si4_rest(struct gsm48_sysinfo *s, uint8_t *si,
+ uint8_t len)
+{
+ return 0;
+}
+
+/* decode "SI 6 Rest Octets" (10.5.2.35a) */
+static int gsm48_decode_si6_rest(struct gsm48_sysinfo *s, uint8_t *si,
+ uint8_t len)
+{
+ return 0;
+}
+
+/* send sysinfo event to other layers */
+static int gsm48_send_sysinfo(struct osmocom_ms *ms, uint8_t type)
+{
+ struct msgb *nmsg;
+ struct gsm322_msg *em;
+
+ nmsg = gsm322_msgb_alloc(GSM322_EVENT_SYSINFO);
+ if (!nmsg)
+ return -ENOMEM;
+ em = (struct gsm322_msg *) nmsg->data;
+ em->sysinfo = type;
+ gsm322_cs_sendmsg(ms, nmsg);
+
+ /* send timer info to location update process */
+ nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_SYSINFO);
+ if (!nmsg)
+ return -ENOMEM;
+ gsm48_mmevent_msg(ms, nmsg);
+
+ return 0;
+}
+
+/* receive "SYSTEM INFORMATION 1" message (9.1.31) */
+static int gsm48_rr_rx_sysinfo1(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_system_information_type_1 *si = msgb_l3(msg);
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+ int payload_len = msgb_l3len(msg) - sizeof(*si);
+
+ if (payload_len < 0) {
+ LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 1 "
+ "message.\n");
+ return -EINVAL;
+ }
+
+ if (!!memcmp(si, s->si1_msg, MIN(msgb_l3len(msg), sizeof(s->si1_msg))))
+ return -0;
+ memcpy(s->si1_msg, si, MIN(msgb_l3len(msg), sizeof(s->si1_msg)));
+
+ LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 1\n");
+
+ /* Cell Channel Description */
+ gsm48_decode_freq_list(&ms->support, s->freq,
+ si->cell_channel_description,
+ sizeof(si->cell_channel_description), 0xce, FREQ_TYPE_SERV);
+ /* RACH Control Parameter */
+ gsm48_decode_rach_ctl_param(s, &si->rach_control);
+ /* SI 1 Rest Octets */
+ if (payload_len)
+ gsm48_decode_si1_rest(s, si->rest_octets, payload_len);
+
+ s->si1 = 1;
+
+ return gsm48_send_sysinfo(ms, si->header.system_information);
+}
+
+/* receive "SYSTEM INFORMATION 2" message (9.1.32) */
+static int gsm48_rr_rx_sysinfo2(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_system_information_type_2 *si = msgb_l3(msg);
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+ int payload_len = msgb_l3len(msg) - sizeof(*si);
+
+ if (payload_len < 0) {
+ LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 2 "
+ "message.\n");
+ return -EINVAL;
+ }
+
+ if (!!memcmp(si, s->si2_msg, MIN(msgb_l3len(msg), sizeof(s->si2_msg))))
+ return -0;
+ memcpy(s->si2_msg, si, MIN(msgb_l3len(msg), sizeof(s->si2_msg)));
+
+ LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 2\n");
+
+ /* Neighbor Cell Description */
+ s->nb_ext_ind_si2 = (si->bcch_frequency_list[0] >> 6) & 1;
+ s->nb_ba_ind_si2 = (si->bcch_frequency_list[0] >> 5) & 1;
+ gsm48_decode_freq_list(&ms->support, s->freq, si->bcch_frequency_list,
+ sizeof(si->bcch_frequency_list), 0xce, FREQ_TYPE_NCELL_2);
+ /* NCC Permitted */
+ s->nb_ncc_permitted = si->ncc_permitted;
+ /* RACH Control Parameter */
+ gsm48_decode_rach_ctl_neigh(s, &si->rach_control);
+
+ s->si2 = 1;
+
+ return gsm48_send_sysinfo(ms, si->header.system_information);
+}
+
+/* receive "SYSTEM INFORMATION 2bis" message (9.1.33) */
+static int gsm48_rr_rx_sysinfo2bis(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_system_information_type_2bis *si = msgb_l3(msg);
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+ int payload_len = msgb_l3len(msg) - sizeof(*si);
+
+ if (payload_len < 0) {
+ LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 2bis "
+ "message.\n");
+ return -EINVAL;
+ }
+
+ if (!!memcmp(si, s->si2b_msg, MIN(msgb_l3len(msg),
+ sizeof(s->si2b_msg))))
+ return -0;
+ memcpy(s->si2b_msg, si, MIN(msgb_l3len(msg), sizeof(s->si2b_msg)));
+
+ LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 2bis\n");
+
+ /* Neighbor Cell Description */
+ s->nb_ext_ind_si2bis = (si->bcch_frequency_list[0] >> 6) & 1;
+ s->nb_ba_ind_si2bis = (si->bcch_frequency_list[0] >> 5) & 1;
+ gsm48_decode_freq_list(&ms->support, s->freq,
+ si->bcch_frequency_list,
+ sizeof(si->bcch_frequency_list), 0x8e,
+ FREQ_TYPE_NCELL_2bis);
+ /* RACH Control Parameter */
+ gsm48_decode_rach_ctl_neigh(s, &si->rach_control);
+
+ s->si2bis = 1;
+
+ return gsm48_send_sysinfo(ms, si->header.system_information);
+}
+
+/* receive "SYSTEM INFORMATION 2ter" message (9.1.34) */
+static int gsm48_rr_rx_sysinfo2ter(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_system_information_type_2ter *si = msgb_l3(msg);
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+ int payload_len = msgb_l3len(msg) - sizeof(*si);
+
+ if (payload_len < 0) {
+ LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 2ter "
+ "message.\n");
+ return -EINVAL;
+ }
+
+ if (!!memcmp(si, s->si2t_msg, MIN(msgb_l3len(msg),
+ sizeof(s->si2t_msg))))
+ return -0;
+ memcpy(s->si2t_msg, si, MIN(msgb_l3len(msg), sizeof(s->si2t_msg)));
+
+ LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 2ter\n");
+
+ /* Neighbor Cell Description 2 */
+ s->nb_multi_rep_si2ter = (si->ext_bcch_frequency_list[0] >> 6) & 3;
+ gsm48_decode_freq_list(&ms->support, s->freq,
+ si->ext_bcch_frequency_list,
+ sizeof(si->ext_bcch_frequency_list), 0x8e,
+ FREQ_TYPE_NCELL_2ter);
+
+ s->si2ter = 1;
+
+ return gsm48_send_sysinfo(ms, si->header.system_information);
+}
+
+/* receive "SYSTEM INFORMATION 3" message (9.1.35) */
+static int gsm48_rr_rx_sysinfo3(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_system_information_type_3 *si = msgb_l3(msg);
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+ int payload_len = msgb_l3len(msg) - sizeof(*si);
+
+ if (payload_len < 0) {
+ LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 3 "
+ "message.\n");
+ return -EINVAL;
+ }
+
+ if (!!memcmp(si, s->si3_msg, MIN(msgb_l3len(msg), sizeof(s->si3_msg))))
+ return -0;
+ memcpy(s->si3_msg, si, MIN(msgb_l3len(msg), sizeof(s->si3_msg)));
+
+ /* Cell Identity */
+ s->cell_id = ntohl(si->cell_identity);
+ /* LAI */
+ gsm48_decode_lai(&si->lai, &s->mcc, &s->mnc, &s->lac);
+ /* Control Channel Description */
+ gsm48_decode_ccd(s, &si->control_channel_desc);
+ /* Cell Options (BCCH) */
+ gsm48_decode_cellopt_bcch(s, &si->cell_options);
+ /* Cell Selection Parameters */
+ gsm48_decode_cell_sel_param(s, &si->cell_sel_par);
+ /* RACH Control Parameter */
+ gsm48_decode_rach_ctl_param(s, &si->rach_control);
+ /* SI 3 Rest Octets */
+ if (payload_len >= 4)
+ gsm48_decode_si3_rest(s, si->rest_octets, payload_len);
+
+ LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 3 (mcc %03d mnc %02d "
+ "lac 0x%04x)\n", s->mcc, s->mnc, s->lac);
+
+ s->si3 = 1;
+
+ return gsm48_send_sysinfo(ms, si->header.system_information);
+}
+
+/* receive "SYSTEM INFORMATION 4" message (9.1.36) */
+static int gsm48_rr_rx_sysinfo4(struct osmocom_ms *ms, struct msgb *msg)
+{
+ /* NOTE: pseudo length is not in this structure, so we skip */
+ struct gsm48_system_information_type_4 *si = msgb_l3(msg) + 1;
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+ int payload_len = msgb_l3len(msg) - sizeof(*si) - 1;
+ uint8_t *data = si->data;
+ struct gsm48_chan_desc *cd;
+
+ if (payload_len < 0) {
+ short_read:
+ LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 4 "
+ "message.\n");
+ return -EINVAL;
+ }
+
+ if (!!memcmp(si, s->si4_msg, MIN(msgb_l3len(msg), sizeof(s->si4_msg))))
+ return -0;
+ memcpy(s->si4_msg, si, MIN(msgb_l3len(msg), sizeof(s->si4_msg)));
+
+ /* LAI */
+ gsm48_decode_lai(&si->lai, &s->mcc, &s->mnc, &s->lac);
+ /* Cell Selection Parameters */
+ gsm48_decode_cell_sel_param(s, &si->cell_sel_par);
+ /* RACH Control Parameter */
+ gsm48_decode_rach_ctl_param(s, &si->rach_control);
+ /* CBCH Channel Description */
+ if (payload_len >= 1 && data[0] == GSM48_IE_CBCH_CHAN_DESC) {
+ if (payload_len < 4)
+ goto short_read;
+ cd = (struct gsm48_chan_desc *) (data + 1);
+ if (cd->h0.h) {
+ s->h = 1;
+ gsm48_decode_chan_h1(cd, &s->tsc, &s->maio, &s->hsn);
+ } else {
+ s->h = 0;
+ gsm48_decode_chan_h0(cd, &s->tsc, &s->arfcn);
+ }
+ payload_len -= 4;
+ data += 4;
+ }
+ /* CBCH Mobile Allocation */
+ if (payload_len >= 1 && data[0] == GSM48_IE_CBCH_MOB_AL) {
+ if (payload_len < 1 || payload_len < 2 + data[1])
+ goto short_read;
+ gsm48_decode_mobile_alloc(s, data + 2, si->data[1]);
+ payload_len -= 2 + data[1];
+ data += 2 + data[1];
+ }
+ /* SI 4 Rest Octets */
+ if (payload_len > 0)
+ gsm48_decode_si4_rest(s, data, payload_len);
+
+ LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 4 (mcc %03d mnc %02d "
+ "lac 0x%04x)\n", s->mcc, s->mnc, s->lac);
+
+ s->si4 = 1;
+
+ return gsm48_send_sysinfo(ms, si->header.system_information);
+}
+
+/* receive "SYSTEM INFORMATION 5" message (9.1.37) */
+static int gsm48_rr_rx_sysinfo5(struct osmocom_ms *ms, struct msgb *msg)
+{
+ /* NOTE: pseudo length is not in this structure, so we skip */
+ struct gsm48_system_information_type_5 *si = msgb_l3(msg) + 1;
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+ int payload_len = msgb_l3len(msg) - sizeof(*si) - 1;
+
+ if (payload_len < 0) {
+ LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 5 "
+ "message.\n");
+ return -EINVAL;
+ }
+
+ if (!!memcmp(si, s->si5_msg, MIN(msgb_l3len(msg), sizeof(s->si5_msg))))
+ return -0;
+ memcpy(s->si5_msg, si, MIN(msgb_l3len(msg), sizeof(s->si5_msg)));
+
+ LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 5\n");
+
+ /* Neighbor Cell Description */
+ s->nb_ext_ind_si5 = (si->bcch_frequency_list[0] >> 6) & 1;
+ s->nb_ba_ind_si5 = (si->bcch_frequency_list[0] >> 5) & 1;
+ gsm48_decode_freq_list(&ms->support, s->freq, si->bcch_frequency_list,
+ sizeof(si->bcch_frequency_list), 0xce, FREQ_TYPE_REP_5);
+
+ s->si5 = 1;
+
+ return gsm48_send_sysinfo(ms, si->system_information);
+}
+
+/* receive "SYSTEM INFORMATION 5bis" message (9.1.38) */
+static int gsm48_rr_rx_sysinfo5bis(struct osmocom_ms *ms, struct msgb *msg)
+{
+ /* NOTE: pseudo length is not in this structure, so we skip */
+ struct gsm48_system_information_type_5bis *si = msgb_l3(msg) + 1;
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+ int payload_len = msgb_l3len(msg) - sizeof(*si) - 1;
+
+ if (payload_len < 0) {
+ LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 5bis "
+ "message.\n");
+ return -EINVAL;
+ }
+
+ LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 5bis\n");
+
+ if (!!memcmp(si, s->si5b_msg, MIN(msgb_l3len(msg),
+ sizeof(s->si5b_msg))))
+ return -0;
+ memcpy(s->si5b_msg, si, MIN(msgb_l3len(msg), sizeof(s->si5b_msg)));
+
+ /* Neighbor Cell Description */
+ s->nb_ext_ind_si5bis = (si->bcch_frequency_list[0] >> 6) & 1;
+ s->nb_ba_ind_si5bis = (si->bcch_frequency_list[0] >> 5) & 1;
+ gsm48_decode_freq_list(&ms->support, s->freq, si->bcch_frequency_list,
+ sizeof(si->bcch_frequency_list), 0xce, FREQ_TYPE_REP_5bis);
+
+ s->si5bis = 1;
+
+ return gsm48_send_sysinfo(ms, si->system_information);
+}
+
+/* receive "SYSTEM INFORMATION 5ter" message (9.1.39) */
+static int gsm48_rr_rx_sysinfo5ter(struct osmocom_ms *ms, struct msgb *msg)
+{
+ /* NOTE: pseudo length is not in this structure, so we skip */
+ struct gsm48_system_information_type_5ter *si = msgb_l3(msg) + 1;
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+ int payload_len = msgb_l3len(msg) - sizeof(*si) - 1;
+
+ if (payload_len < 0) {
+ LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 5ter "
+ "message.\n");
+ return -EINVAL;
+ }
+
+ LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 5ter\n");
+
+ if (!!memcmp(si, s->si5t_msg, MIN(msgb_l3len(msg),
+ sizeof(s->si5t_msg))))
+ return -0;
+ memcpy(s->si5t_msg, si, MIN(msgb_l3len(msg), sizeof(s->si5t_msg)));
+
+ /* Neighbor Cell Description */
+ gsm48_decode_freq_list(&ms->support, s->freq, si->bcch_frequency_list,
+ sizeof(si->bcch_frequency_list), 0xce, FREQ_TYPE_REP_5ter);
+
+ s->si5ter = 1;
+
+ return gsm48_send_sysinfo(ms, si->system_information);
+}
+
+/* receive "SYSTEM INFORMATION 6" message (9.1.39) */
+static int gsm48_rr_rx_sysinfo6(struct osmocom_ms *ms, struct msgb *msg)
+{
+ /* NOTE: pseudo length is not in this structure, so we skip */
+ struct gsm48_system_information_type_6 *si = msgb_l3(msg) + 1;
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+ int payload_len = msgb_l3len(msg) - sizeof(*si) - 1;
+
+ if (payload_len < 0) {
+ LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 6 "
+ "message.\n");
+ return -EINVAL;
+ }
+
+ if (!!memcmp(si, s->si6_msg, MIN(msgb_l3len(msg), sizeof(s->si6_msg))))
+ return -0;
+ memcpy(s->si6_msg, si, MIN(msgb_l3len(msg), sizeof(s->si6_msg)));
+
+ /* Cell Identity */
+ if (s->si6 && s->cell_identity != ntohl(si->cell_identity))
+ LOGP(DRR, LOGL_INFO, "Cell ID on SI 6 differs from previous "
+ "read.\n");
+ s->cell_identity = ntohl(si->cell_identity);
+ /* LAI */
+ gsm48_decode_lai(&si->lai, &s->mcc, &s->mnc, &s->lac);
+ /* Cell Options (SACCH) */
+ gsm48_decode_cellopt_sacch(s, &si->cell_options);
+ /* NCC Permitted */
+ s->nb_ncc_permitted = si->ncc_permitted;
+ /* SI 6 Rest Octets */
+ if (payload_len >= 4)
+ gsm48_decode_si6_rest(s, si->rest_octets, payload_len);
+
+ LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 6 (mcc %03d mnc %02d "
+ "lac 0x%04x)\n", s->mcc, s->mnc, s->lac);
+
+ s->si6 = 1;
+
+ return gsm48_send_sysinfo(ms, si->system_information);
+}
+
+/*
+ * paging
+ */
+
+/* paging channel request */
+static int gsm48_rr_chan2cause[4] = {
+ RR_EST_CAUSE_ANS_PAG_ANY,
+ RR_EST_CAUSE_ANS_PAG_SDCCH,
+ RR_EST_CAUSE_ANS_PAG_TCH_F,
+ RR_EST_CAUSE_ANS_PAG_TCH_ANY
+};
+
+/* given LV of mobile identity is checked agains ms */
+static int gsm_match_mi(struct osmocom_ms *ms, uint8_t *mi)
+{
+ char imsi[16];
+ uint32_t tmsi;
+ uint8_t mi_type;
+
+ if (mi[0] < 1)
+ return 0;
+ mi_type = mi[1] & GSM_MI_TYPE_MASK;
+ switch (mi_type) {
+ case GSM_MI_TYPE_TMSI:
+ if (mi[0] < 5)
+ return 0;
+ memcpy(&tmsi, mi+2, 4);
+ if (ms->subscr.tmsi == ntohl(tmsi)
+ && ms->subscr.tmsi_valid) {
+ LOGP(DRR, LOGL_INFO, "TMSI %08x matches\n", ntohl(tmsi));
+
+ return 1;
+ } else
+ LOGP(DRR, LOGL_INFO, "TMSI %08x (not for us)\n",
+ ntohl(tmsi));
+ break;
+ case GSM_MI_TYPE_IMSI:
+ gsm48_mi_to_string(imsi, sizeof(imsi), mi + 1, mi[0]);
+ if (!strcmp(imsi, ms->subscr.imsi)) {
+ LOGP(DRR, LOGL_INFO, "IMSI %s matches\n", imsi);
+
+ return 1;
+ } else
+ LOGP(DRR, LOGL_INFO, "IMSI %s (not for us)\n", imsi);
+ break;
+ default:
+ LOGP(DRR, LOGL_NOTICE, "Paging with unsupported MI type %d.\n",
+ mi_type);
+ }
+
+ return 0;
+}
+
+/* 9.1.22 PAGING REQUEST 1 message received */
+static int gsm48_rr_rx_pag_req_1(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm48_paging1 *pa = msgb_l3(msg);
+ int payload_len = msgb_l3len(msg) - sizeof(*pa);
+ int chan_1, chan_2;
+ uint8_t *mi;
+
+ /* 3.3.1.1.2: ignore paging while not camping on a cell */
+ if (rr->state != GSM48_RR_ST_IDLE
+ && cs->state != GSM322_C3_CAMPED_NORMALLY) {
+ LOGP(DRR, LOGL_INFO, "PAGING ignored, we are not camping "
+ "normally.\n");
+ return 0;
+ }
+
+ if (payload_len < 2) {
+ short_read:
+ LOGP(DRR, LOGL_NOTICE, "Short read of PAGING REQUEST 1 "
+ "message.\n");
+ return -EINVAL;
+ }
+
+ /* channel needed */
+ chan_1 = pa->cneed1;
+ chan_2 = pa->cneed2;
+ /* first MI */
+ mi = pa->data + 1;
+ if (payload_len < mi[0] + 1)
+ goto short_read;
+ if (gsm_match_mi(ms, mi) > 0)
+ return gsm48_rr_tx_chan_req(ms, gsm48_rr_chan2cause[chan_1], 1);
+ /* second MI */
+ payload_len -= mi[0] + 1;
+ mi = pa->data + mi[0] + 1;
+ if (payload_len < 2)
+ return 0;
+ if (mi[0] != GSM48_IE_MOBILE_ID)
+ return 0;
+ if (payload_len < mi[1] + 2)
+ goto short_read;
+ if (gsm_match_mi(ms, mi + 1) > 0)
+ return gsm48_rr_tx_chan_req(ms, gsm48_rr_chan2cause[chan_2], 1);
+
+ return 0;
+}
+
+/* 9.1.23 PAGING REQUEST 2 message received */
+static int gsm48_rr_rx_pag_req_2(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm48_paging2 *pa = msgb_l3(msg);
+ int payload_len = msgb_l3len(msg) - sizeof(*pa);
+ uint8_t *mi;
+ int chan_1, chan_2, chan_3;
+
+ /* 3.3.1.1.2: ignore paging while not camping on a cell */
+ if (rr->state != GSM48_RR_ST_IDLE
+ && cs->state != GSM322_C3_CAMPED_NORMALLY) {
+ LOGP(DRR, LOGL_INFO, "PAGING ignored, we are not camping "
+ "normally.\n");
+ return 0;
+ }
+
+ if (payload_len < 0) {
+ short_read:
+ LOGP(DRR, LOGL_NOTICE, "Short read of PAGING REQUEST 2 "
+ "message .\n");
+ return -EINVAL;
+ }
+
+ /* channel needed */
+ chan_1 = pa->cneed1;
+ chan_2 = pa->cneed2;
+ /* first MI */
+ if (ms->subscr.tmsi == ntohl(pa->tmsi1)
+ && ms->subscr.tmsi_valid)
+ return gsm48_rr_tx_chan_req(ms, gsm48_rr_chan2cause[chan_1], 1);
+ /* second MI */
+ if (ms->subscr.tmsi == ntohl(pa->tmsi2)
+ && ms->subscr.tmsi_valid)
+ return gsm48_rr_tx_chan_req(ms, gsm48_rr_chan2cause[chan_2], 1);
+ /* third MI */
+ mi = pa->data;
+ if (payload_len < 2)
+ return 0;
+ if (mi[0] != GSM48_IE_MOBILE_ID)
+ return 0;
+ if (payload_len < mi[1] + 2 + 1) /* must include "channel needed" */
+ goto short_read;
+ chan_3 = mi[mi[1] + 2] & 0x03; /* channel needed */
+ if (gsm_match_mi(ms, mi + 1) > 0)
+ return gsm48_rr_tx_chan_req(ms, gsm48_rr_chan2cause[chan_3], 1);
+
+ return 0;
+}
+
+/* 9.1.24 PAGING REQUEST 3 message received */
+static int gsm48_rr_rx_pag_req_3(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm48_paging3 *pa = msgb_l3(msg);
+ int payload_len = msgb_l3len(msg) - sizeof(*pa);
+ int chan_1, chan_2, chan_3, chan_4;
+
+ /* 3.3.1.1.2: ignore paging while not camping on a cell */
+ if (rr->state != GSM48_RR_ST_IDLE
+ && cs->state != GSM322_C3_CAMPED_NORMALLY) {
+ LOGP(DRR, LOGL_INFO, "PAGING ignored, we are not camping "
+ "normally.\n");
+ return 0;
+ }
+
+ if (payload_len < 0) { /* must include "channel needed", part of *pa */
+ LOGP(DRR, LOGL_NOTICE, "Short read of PAGING REQUEST 3 "
+ "message .\n");
+ return -EINVAL;
+ }
+
+ /* channel needed */
+ chan_1 = pa->cneed1;
+ chan_2 = pa->cneed2;
+ chan_3 = pa->cneed3;
+ chan_4 = pa->cneed4;
+ /* first MI */
+ if (ms->subscr.tmsi == ntohl(pa->tmsi1)
+ && ms->subscr.tmsi_valid)
+ return gsm48_rr_tx_chan_req(ms, gsm48_rr_chan2cause[chan_1], 1);
+ /* second MI */
+ if (ms->subscr.tmsi == ntohl(pa->tmsi2)
+ && ms->subscr.tmsi_valid)
+ return gsm48_rr_tx_chan_req(ms, gsm48_rr_chan2cause[chan_2], 1);
+ /* thrid MI */
+ if (ms->subscr.tmsi == ntohl(pa->tmsi3)
+ && ms->subscr.tmsi_valid)
+ return gsm48_rr_tx_chan_req(ms, gsm48_rr_chan2cause[chan_3], 1);
+ /* fourth MI */
+ if (ms->subscr.tmsi == ntohl(pa->tmsi4)
+ && ms->subscr.tmsi_valid)
+ return gsm48_rr_tx_chan_req(ms, gsm48_rr_chan2cause[chan_4], 1);
+
+ return 0;
+}
+
+/*
+ * (immediate) assignment
+ */
+
+/* match request reference agains request history */
+static int gsm48_match_ra(struct osmocom_ms *ms, struct gsm48_req_ref *ref)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ if (rr->cr_hist[i] >= 0
+ && ref->ra == rr->cr_hist[i]) {
+ LOGP(DRR, LOGL_INFO, "request %02x matches\n", ref->ra);
+ // todo: match timeslot
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* 9.1.3 sending ASSIGNMENT COMPLETE */
+static int gsm48_rr_tx_ass_cpl(struct osmocom_ms *ms, uint8_t cause)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+ struct gsm48_ass_cpl *ac;
+
+ LOGP(DRR, LOGL_INFO, "ASSIGNMENT COMPLETE (cause #%d)\n", cause);
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+ ac = (struct gsm48_ass_cpl *) msgb_put(nmsg, sizeof(*ac));
+
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_ASS_COMPL;
+
+ /* RR_CAUSE */
+ ac->rr_cause = cause;
+
+ return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg);
+}
+
+/* 9.1.4 sending ASSIGNMENT FAILURE */
+static int gsm48_rr_tx_ass_fail(struct osmocom_ms *ms, uint8_t cause)
+{
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+ struct gsm48_ass_fail *af;
+
+ LOGP(DRR, LOGL_INFO, "ASSIGNMENT FAILURE (cause #%d)\n", cause);
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+ af = (struct gsm48_ass_fail *) msgb_put(nmsg, sizeof(*af));
+
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_ASS_COMPL;
+
+ /* RR_CAUSE */
+ af->rr_cause = cause;
+
+ return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg);
+}
+
+/* 9.1.18 IMMEDIATE ASSIGNMENT is received */
+static int gsm48_rr_rx_imm_ass(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct gsm48_imm_ass *ia = msgb_l3(msg);
+ int ma_len = msgb_l3len(msg) - sizeof(*ia);
+ uint8_t ch_type, ch_subch, ch_ts;
+ struct gsm48_rr_cd cd;
+ uint8_t *st, st_len;
+
+ memset(&cd, 0, sizeof(cd));
+
+ if (ma_len < 0 /* mobile allocation IE must be included */
+ || ia->mob_alloc_len > ma_len) { /* short read of IE */
+ LOGP(DRR, LOGL_NOTICE, "Short read of IMMEDIATE ASSIGNMENT "
+ "message.\n");
+ return -EINVAL;
+ }
+ if (ia->mob_alloc_len > 8) {
+ LOGP(DRR, LOGL_NOTICE, "Moble allocation in IMMEDIATE "
+ "ASSIGNMENT too large.\n");
+ return -EINVAL;
+ }
+
+ /* starting time */
+ st_len = ma_len - ia->mob_alloc_len;
+ st = ia->mob_alloc + ia->mob_alloc_len;
+ if (st_len >= 3 && st[0] == GSM48_IE_START_TIME)
+ gsm48_decode_start_time(&cd, (struct gsm48_start_time *)(st+1));
+
+ /* decode channel description */
+ rsl_dec_chan_nr(ia->chan_desc.chan_nr, &ch_type, &ch_subch, &ch_ts);
+ if (ia->chan_desc.h0.h) {
+ cd.h = 1;
+ gsm48_decode_chan_h1(&ia->chan_desc, &cd.tsc, &cd.maio,
+ &cd.hsn);
+ LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT (ra=0x%02x, "
+ "chan_nr=0x%02x, MAIO=%u, HSN=%u, TS=%u, SS=%u, "
+ "TSC=%u) ", ia->req_ref.ra, ia->chan_desc.chan_nr,
+ cd.maio, cd.hsn, ch_ts, ch_subch, cd.tsc);
+ } else {
+ cd.h = 0;
+ gsm48_decode_chan_h0(&ia->chan_desc, &cd.tsc, &cd.arfcn);
+ LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT (ra=0x%02x, "
+ "chan_nr=0x%02x, ARFCN=%u, TS=%u, SS=%u, TSC=%u) ",
+ ia->req_ref.ra, ia->chan_desc.chan_nr, cd.arfcn,
+ ch_ts, ch_subch, cd.tsc);
+ }
+
+ /* 3.3.1.1.2: ignore assignment while idle */
+ if (rr->state != GSM48_RR_ST_CONN_PEND || !rr->wait_assign) {
+ LOGP(DRR, LOGL_INFO, "no previous request\n");
+ return 0;
+ }
+
+ /* request ref */
+ if (gsm48_match_ra(ms, &ia->req_ref)) {
+ /* channel description */
+ memcpy(&rr->cd_now, &cd, sizeof(rr->cd_now));
+ /* timing advance */
+ rr->cd_now.ta = ia->timing_advance;
+ /* mobile allocation */
+ memcpy(&rr->cd_now.mob_alloc_lv, &ia->mob_alloc_len,
+ ia->mob_alloc_len + 1);
+ rr->wait_assign = 0;
+ return gsm48_rr_dl_est(ms);
+ }
+ LOGP(DRR, LOGL_INFO, "does not match our request\n");
+
+ return 0;
+}
+
+/* 9.1.19 IMMEDIATE ASSIGNMENT EXTENDED is received */
+static int gsm48_rr_rx_imm_ass_ext(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct gsm48_imm_ass_ext *ia = msgb_l3(msg);
+ int ma_len = msgb_l3len(msg) - sizeof(*ia);
+ uint8_t ch_type, ch_subch, ch_ts;
+ struct gsm48_rr_cd cd1, cd2;
+ uint8_t *st, st_len;
+
+ memset(&cd1, 0, sizeof(cd1));
+ memset(&cd2, 0, sizeof(cd2));
+
+ if (ma_len < 0 /* mobile allocation IE must be included */
+ || ia->mob_alloc_len > ma_len) { /* short read of IE */
+ LOGP(DRR, LOGL_NOTICE, "Short read of IMMEDIATE ASSIGNMENT "
+ "EXTENDED message.\n");
+ return -EINVAL;
+ }
+ if (ia->mob_alloc_len > 4) {
+ LOGP(DRR, LOGL_NOTICE, "Moble allocation in IMMEDIATE "
+ "ASSIGNMENT EXTENDED too large.\n");
+ return -EINVAL;
+ }
+
+ /* starting time */
+ st_len = ma_len - ia->mob_alloc_len;
+ st = ia->mob_alloc + ia->mob_alloc_len;
+ if (st_len >= 3 && st[0] == GSM48_IE_START_TIME) {
+ gsm48_decode_start_time(&cd1,
+ (struct gsm48_start_time *)(st+1));
+ memcpy(&cd2, &cd1, sizeof(cd2));
+ }
+
+ /* decode channel description */
+ LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT ");
+ rsl_dec_chan_nr(ia->chan_desc1.chan_nr, &ch_type, &ch_subch, &ch_ts);
+ if (ia->chan_desc1.h0.h) {
+ cd1.h = 1;
+ gsm48_decode_chan_h1(&ia->chan_desc1, &cd1.tsc, &cd1.maio,
+ &cd1.hsn);
+ LOGP(DRR, LOGL_INFO, "1(ra=0x%02x, "
+ "chan_nr=0x%02x, MAIO=%u, HSN=%u, TS=%u, SS=%u, "
+ "TSC=%u) ", ia->req_ref1.ra, ia->chan_desc1.chan_nr,
+ cd1.maio, cd1.hsn, ch_ts, ch_subch, cd1.tsc);
+ } else {
+ cd1.h = 0;
+ gsm48_decode_chan_h0(&ia->chan_desc1, &cd1.tsc, &cd1.arfcn);
+ LOGP(DRR, LOGL_INFO, "1(ra=0x%02x, "
+ "chan_nr=0x%02x, ARFCN=%u, TS=%u, SS=%u, TSC=%u) ",
+ ia->req_ref1.ra, ia->chan_desc1.chan_nr, cd1.arfcn,
+ ch_ts, ch_subch, cd1.tsc);
+ }
+ rsl_dec_chan_nr(ia->chan_desc2.chan_nr, &ch_type, &ch_subch, &ch_ts);
+ if (ia->chan_desc2.h0.h) {
+ cd2.h = 1;
+ gsm48_decode_chan_h1(&ia->chan_desc2, &cd1.tsc, &cd1.maio,
+ &cd1.hsn);
+ LOGP(DRR, LOGL_INFO, "2(ra=0x%02x, "
+ "chan_nr=0x%02x, MAIO=%u, HSN=%u, TS=%u, SS=%u, "
+ "TSC=%u) ", ia->req_ref2.ra, ia->chan_desc2.chan_nr,
+ cd2.maio, cd2.hsn, ch_ts, ch_subch, cd2.tsc);
+ } else {
+ cd2.h = 0;
+ gsm48_decode_chan_h0(&ia->chan_desc2, &cd2.tsc, &cd2.arfcn);
+ LOGP(DRR, LOGL_INFO, "2(ra=0x%02x, "
+ "chan_nr=0x%02x, ARFCN=%u, TS=%u, SS=%u, TSC=%u) ",
+ ia->req_ref2.ra, ia->chan_desc2.chan_nr, cd2.arfcn,
+ ch_ts, ch_subch, cd2.tsc);
+ }
+
+ /* 3.3.1.1.2: ignore assignment while idle */
+ if (rr->state != GSM48_RR_ST_CONN_PEND || !rr->wait_assign) {
+ LOGP(DRR, LOGL_INFO, "no previous request\n");
+ return 0;
+ }
+
+ /* request ref 1 */
+ if (gsm48_match_ra(ms, &ia->req_ref1)) {
+ /* channel description */
+ memcpy(&rr->cd_now, &cd1, sizeof(rr->cd_now));
+ /* timing advance */
+ rr->cd_now.ta = ia->timing_advance1;
+ /* mobile allocation */
+ memcpy(&rr->cd_now.mob_alloc_lv, &ia->mob_alloc_len,
+ ia->mob_alloc_len + 1);
+ rr->wait_assign = 0;
+ return gsm48_rr_dl_est(ms);
+ }
+ /* request ref 1 */
+ if (gsm48_match_ra(ms, &ia->req_ref2)) {
+ /* channel description */
+ memcpy(&rr->cd_now, &cd2, sizeof(rr->cd_now));
+ /* timing advance */
+ rr->cd_now.ta = ia->timing_advance2;
+ /* mobile allocation */
+ memcpy(&rr->cd_now.mob_alloc_lv, &ia->mob_alloc_len,
+ ia->mob_alloc_len + 1);
+ rr->wait_assign = 0;
+ return gsm48_rr_dl_est(ms);
+ }
+ LOGP(DRR, LOGL_INFO, "does not match our request\n");
+
+ return 0;
+}
+
+/* 9.1.20 IMMEDIATE ASSIGNMENT REJECT is received */
+static int gsm48_rr_rx_imm_ass_rej(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct gsm48_imm_ass_rej *ia = msgb_l3(msg);
+ int payload_len = msgb_l3len(msg) - sizeof(*ia);
+ int i;
+ struct gsm48_req_ref *req_ref;
+ uint8_t t3122_value;
+
+ /* 3.3.1.1.2: ignore assignment while idle */
+ if (rr->state != GSM48_RR_ST_CONN_PEND || !rr->wait_assign)
+ return 0;
+
+ if (payload_len < 0) {
+ LOGP(DRR, LOGL_NOTICE, "Short read of IMMEDIATE ASSIGNMENT "
+ "REJECT message.\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < 4; i++) {
+ /* request reference */
+ req_ref = (struct gsm48_req_ref *)
+ (((uint8_t *)&ia->req_ref1) + i * 4);
+ LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT REJECT "
+ "(ref 0x%02x)\n", req_ref->ra);
+ if (gsm48_match_ra(ms, req_ref)) {
+ /* wait indication */
+ t3122_value = *(((uint8_t *)&ia->wait_ind1) + i * 4);
+ if (t3122_value)
+ start_rr_t3122(rr, t3122_value, 0);
+ /* start timer 3126 if not already */
+ if (!bsc_timer_pending(&rr->t3126))
+ start_rr_t3126(rr, 5, 0); /* TODO improve! */
+ /* stop assignmnet requests */
+ rr->n_chan_req = 0;
+
+ /* wait until timer 3126 expires, then release
+ * or wait for channel assignment */
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+/* 9.1.1 ADDITIONAL ASSIGMENT is received */
+static int gsm48_rr_rx_add_ass(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct gsm48_add_ass *aa = (struct gsm48_add_ass *)gh->data;
+ int payload_len = msgb_l3len(msg) - sizeof(*gh) - sizeof(*aa);
+ struct tlv_parsed tp;
+
+ if (payload_len < 0) {
+ LOGP(DRR, LOGL_NOTICE, "Short read of ADDITIONAL ASSIGNMENT "
+ "message.\n");
+ return gsm48_rr_tx_rr_status(ms,
+ GSM48_RR_CAUSE_PROT_ERROR_UNSPC);
+ }
+ tlv_parse(&tp, &gsm48_rr_att_tlvdef, aa->data, payload_len, 0, 0);
+
+ return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_PROT_ERROR_UNSPC);
+}
+
+/*
+ * measturement reports
+ */
+
+static int gsm48_rr_tx_meas_rep(struct osmocom_ms *ms)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct gsm48_rr_meas *meas = &rr->meas;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+ struct gsm48_meas_res *mr;
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+ mr = (struct gsm48_meas_res *) msgb_put(nmsg, sizeof(*mr));
+
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_MEAS_REP;
+
+ /* measurement results */
+ mr->rxlev_full = meas->rxlev_full;
+ mr->rxlev_sub = meas->rxlev_sub;
+ mr->rxqual_full = meas->rxqual_full;
+ mr->rxqual_sub = meas->rxqual_sub;
+ mr->dtx_used = meas->dtx;
+ mr->ba_used = meas->ba;
+ mr->meas_valid = meas->meas_valid;
+ if (meas->ncell_na) {
+ /* no results for serving cells */
+ mr->no_nc_n_hi = 1;
+ mr->no_nc_n_lo = 3;
+ } else {
+ mr->no_nc_n_hi = meas->count >> 2;
+ mr->no_nc_n_lo = meas->count & 3;
+ }
+ mr->rxlev_nc1 = meas->rxlev_nc[0];
+ mr->rxlev_nc2_hi = meas->rxlev_nc[1] >> 1;
+ mr->rxlev_nc2_lo = meas->rxlev_nc[1] & 1;
+ mr->rxlev_nc3_hi = meas->rxlev_nc[2] >> 2;
+ mr->rxlev_nc3_lo = meas->rxlev_nc[2] & 3;
+ mr->rxlev_nc4_hi = meas->rxlev_nc[3] >> 3;
+ mr->rxlev_nc4_lo = meas->rxlev_nc[3] & 7;
+ mr->rxlev_nc5_hi = meas->rxlev_nc[4] >> 4;
+ mr->rxlev_nc5_lo = meas->rxlev_nc[4] & 15;
+ mr->rxlev_nc6_hi = meas->rxlev_nc[5] >> 5;
+ mr->rxlev_nc6_lo = meas->rxlev_nc[5] & 31;
+ mr->bsic_nc1_hi = meas->bsic_nc[0] >> 3;
+ mr->bsic_nc1_lo = meas->bsic_nc[0] & 7;
+ mr->bsic_nc2_hi = meas->bsic_nc[1] >> 4;
+ mr->bsic_nc2_lo = meas->bsic_nc[1] & 15;
+ mr->bsic_nc3_hi = meas->bsic_nc[2] >> 5;
+ mr->bsic_nc3_lo = meas->bsic_nc[2] & 31;
+ mr->bsic_nc4 = meas->bsic_nc[3];
+ mr->bsic_nc5 = meas->bsic_nc[4];
+ mr->bsic_nc6 = meas->bsic_nc[5];
+ mr->bcch_f_nc1 = meas->bcch_f_nc[0];
+ mr->bcch_f_nc2 = meas->bcch_f_nc[1];
+ mr->bcch_f_nc3 = meas->bcch_f_nc[2];
+ mr->bcch_f_nc4 = meas->bcch_f_nc[3];
+ mr->bcch_f_nc5_hi = meas->bcch_f_nc[4] >> 1;
+ mr->bcch_f_nc5_lo = meas->bcch_f_nc[4] & 1;
+ mr->bcch_f_nc6_hi = meas->bcch_f_nc[5] >> 2;
+ mr->bcch_f_nc6_lo = meas->bcch_f_nc[5] & 3;
+
+ return gsm48_send_rsl(ms, RSL_MT_UNIT_DATA_REQ, nmsg);
+}
+
+/*
+ * link establishment and release
+ */
+
+/* activate link and send establish request */
+static int gsm48_rr_dl_est(struct osmocom_ms *ms)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct msgb *nmsg;
+ struct gsm48_hdr *gh;
+ struct gsm48_pag_rsp *pr;
+ uint8_t mi[11];
+
+ /* 3.3.1.1.3.1 */
+ stop_rr_t3126(rr);
+
+ /* flush pending RACH requests */
+#ifdef TODO
+ rr->n_chan_req = 0; // just to be safe
+ nmsg = msgb_alloc_headroom(20, 16, "RAND_FLUSH");
+ if (!nmsg)
+ return -ENOMEM;
+ gsm48_send_rsl(ms, RSL_MT_RAND_ACC_FLSH, msg);
+#else
+ if (bsc_timer_pending(&rr->temp_rach_ti))
+ bsc_del_timer(&rr->temp_rach_ti);
+#endif
+
+ /* send DL_EST_REQ */
+ if (rr->rr_est_msg) {
+ /* use queued message */
+ nmsg = rr->rr_est_msg;
+ rr->rr_est_msg = 0;
+ LOGP(DRR, LOGL_INFO, "sending establish message\n");
+ } else {
+ /* create paging response */
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh));
+ pr = (struct gsm48_pag_rsp *) msgb_put(nmsg, sizeof(*pr));
+ /* key sequence */
+ pr->key_seq = subscr->key_seq;
+ /* classmark 2 */
+ pr->cm2_len = sizeof(pr->cm2);
+ gsm48_rr_enc_cm2(ms, &pr->cm2);
+ /* mobile identity */
+ if (ms->subscr.tmsi_valid) {
+ gsm48_generate_mid_from_tmsi(mi, subscr->tmsi);
+ LOGP(DRR, LOGL_INFO, "sending paging response with "
+ "TMSI\n");
+ } else if (subscr->imsi[0]) {
+ gsm48_generate_mid_from_imsi(mi, subscr->imsi);
+ LOGP(DRR, LOGL_INFO, "sending paging response with "
+ "IMSI\n");
+ } else {
+ mi[1] = 1;
+ mi[2] = 0xf0 | GSM_MI_TYPE_NONE;
+ LOGP(DRR, LOGL_INFO, "sending paging response without "
+ "TMSI/IMSI\n");
+ }
+ msgb_put(nmsg, 1 + mi[1]);
+ memcpy(pr->data, mi + 1, 1 + mi[1]);
+ }
+
+ /* activate channel */
+#ifdef TODO
+ RSL_MT_ to activate channel with all the cd_now informations
+#else
+ rr->arfcn = rr->cd_now.chan_nr;
+ tx_ph_dm_est_req(ms, rr->cd_now.arfcn, rr->arfcn);
+#endif
+
+ /* start establishmnet */
+ return gsm48_send_rsl(ms, RSL_MT_EST_REQ, nmsg);
+}
+
+/* the link is established */
+static int gsm48_rr_estab_cnf(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct msgb *nmsg;
+
+ /* if MM has releases before confirm, we start release */
+ if (rr->state == GSM48_RR_ST_IDLE) {
+ LOGP(DRR, LOGL_INFO, "MM already released RR.\n");
+ /* release message */
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ /* start release */
+ return gsm48_send_rsl(ms, RSL_MT_REL_REQ, nmsg);
+ }
+
+ /* 3.3.1.1.4 */
+ new_rr_state(rr, GSM48_RR_ST_DEDICATED);
+
+ /* send confirm to upper layer */
+ nmsg = gsm48_rr_msgb_alloc(
+ (rr->rr_est_req) ? GSM48_RR_EST_CNF : GSM48_RR_EST_IND);
+ if (!nmsg)
+ return -ENOMEM;
+ return gsm48_rr_upmsg(ms, nmsg);
+}
+
+/* the link is released */
+static int gsm48_rr_rel_cnf(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+
+ /* deactivate channel */
+ LOGP(DRR, LOGL_INFO, "deactivating channel (arfcn %d)\n", rr->arfcn);
+#ifdef TODO
+ release and give new arfcn
+ tx_ph_dm_rel_req(ms, arfcn, rr->chan_desc.chan_desc.chan_nr);
+#else
+ l1ctl_tx_ccch_req(ms, rr->arfcn);
+#endif
+
+ /* do nothing, because we aleady IDLE
+ * or we received the rel cnf of the last connection
+ * while already requesting a new one (CONN PEND)
+ */
+
+ return 0;
+}
+
+/*
+ * radio ressource requests
+ */
+
+/* establish request for dedicated mode */
+static int gsm48_rr_est_req(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct gsm322_cellsel *cs = &ms->cellsel;
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *) msg->data;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ uint8_t cause;
+ struct msgb *nmsg;
+ struct gsm48_rr_hdr *nrrh;
+ uint16_t acc_class;
+
+ /* 3.3.1.1.3.2 */
+ if (bsc_timer_pending(&rr->t3122)) {
+ if (rrh->cause != RR_EST_CAUSE_EMERGENCY) {
+ LOGP(DRR, LOGL_INFO, "T3122 running, rejecting!\n");
+ cause = RR_REL_CAUSE_T3122;
+ reject:
+ nmsg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND);
+ if (!nmsg)
+ return -ENOMEM;
+ nrrh = (struct gsm48_rr_hdr *)nmsg->data;
+ nrrh->cause = cause;
+ return gsm48_rr_upmsg(ms, nmsg);
+ }
+ LOGP(DRR, LOGL_INFO, "T3122 running, but emergency call\n");
+ stop_rr_t3122(rr);
+ }
+
+ /* check if camping */
+ if (cs->state != GSM322_C3_CAMPED_NORMALLY
+ && rrh->cause != RR_EST_CAUSE_EMERGENCY) {
+ LOGP(DRR, LOGL_INFO, "Not camping normally, rejecting!\n");
+ cause = RR_REL_CAUSE_EMERGENCY_ONLY;
+ goto reject;
+ }
+ if (cs->state != GSM322_C3_CAMPED_NORMALLY
+ && cs->state != GSM322_C7_CAMPED_ANY_CELL) {
+ LOGP(DRR, LOGL_INFO, "Not camping, rejecting!\n");
+ cause = RR_REL_CAUSE_TRY_LATER;
+ goto reject;
+ }
+
+ /* check for relevant informations */
+ if (!s->si3) {
+ LOGP(DRR, LOGL_INFO, "Not enough SI, rejecting!\n");
+ cause = RR_REL_CAUSE_TRY_LATER;
+ goto reject;
+ }
+
+ /* 3.3.1.1.1 */
+ if (!subscr->acc_barr && s->cell_barr) {
+ LOGP(DRR, LOGL_INFO, "Cell barred, rejecting!\n");
+ cause = RR_REL_CAUSE_NOT_AUTHORIZED;
+ goto reject;
+ }
+ if (rrh->cause == RR_EST_CAUSE_EMERGENCY)
+ acc_class = subscr->acc_class | 0x0400;
+ else
+ acc_class = subscr->acc_class & 0xfbff;
+ if (!subscr->acc_barr && !(acc_class & (s->class_barr ^ 0xffff))) {
+ LOGP(DRR, LOGL_INFO, "Cell barred for our access class (access "
+ "%04x barred %04x)!\n", acc_class, s->class_barr);
+ cause = RR_REL_CAUSE_NOT_AUTHORIZED;
+ goto reject;
+ }
+
+ /* requested by RR */
+ rr->rr_est_req = 1;
+
+ /* clone and store REQUEST message */
+ if (!gh) {
+ LOGP(DRR, LOGL_ERROR, "Error, missing l3 message\n");
+ return -EINVAL;
+ }
+ rr->rr_est_msg = msgb_alloc_headroom(256, 16, "EST_REQ");
+ if (!rr->rr_est_msg)
+ return -ENOMEM;
+ memcpy(msgb_put(rr->rr_est_msg, msgb_l3len(msg)),
+ msgb_l3(msg), msgb_l3len(msg));
+
+ /* request channel */
+ return gsm48_rr_tx_chan_req(ms, rrh->cause, 0);
+}
+
+/* send all queued messages down to layer 2 */
+static int gsm48_rr_dequeue_down(struct osmocom_ms *ms)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct msgb *msg;
+
+ while((msg = msgb_dequeue(&rr->downqueue))) {
+ LOGP(DRR, LOGL_INFO, "Sending queued message.\n");
+ gsm48_send_rsl(ms, RSL_MT_DATA_REQ, msg);
+ }
+
+ return 0;
+}
+
+/* 3.4.2 transfer data in dedicated mode */
+static int gsm48_rr_data_req(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+
+ if (rr->state != GSM48_RR_ST_DEDICATED) {
+ msgb_free(msg);
+ return -EINVAL;
+ }
+
+ /* pull RR header */
+ msgb_pull(msg, sizeof(struct gsm48_rr_hdr));
+
+ /* queue message, during handover or assignment procedure */
+ if (rr->hando_susp_state || rr->assign_susp_state) {
+ LOGP(DRR, LOGL_INFO, "Queueing message during suspend.\n");
+ msgb_enqueue(&rr->downqueue, msg);
+ return 0;
+ }
+
+ /* forward message */
+ return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, msg);
+}
+
+/*
+ * data indications from data link
+ */
+
+/* 3.4.2 data from layer 2 to RR and upper layer*/
+static int gsm48_rr_data_ind(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct gsm48_rr_hdr *rrh;
+ uint8_t pdisc = gh->proto_discr & 0x0f;
+
+ if (pdisc == GSM48_PDISC_RR) {
+ int rc = -EINVAL;
+ uint8_t skip_ind = (gh->proto_discr & 0xf0) >> 4;
+
+ /* ignore if skip indicator is not B'0000' */
+ if (skip_ind)
+ return 0;
+
+ switch(gh->msg_type) {
+ case GSM48_MT_RR_ADD_ASS:
+ rc = gsm48_rr_rx_add_ass(ms, msg);
+ break;
+#if 0
+ case GSM48_MT_RR_ASS_CMD:
+ rc = gsm48_rr_rx_ass_cmd(ms, msg);
+ break;
+ case GSM48_MT_RR_CIP_MODE_CMD:
+ rc = gsm48_rr_rx_cip_mode_cmd(ms, msg);
+ break;
+#endif
+ case GSM48_MT_RR_CLSM_ENQ:
+ rc = gsm48_rr_rx_cm_enq(ms, msg);
+ break;
+#if 0
+ case GSM48_MT_RR_HANDO_CMD:
+ rc = gsm48_rr_rx_hando_cmd(ms, msg);
+ break;
+ case GSM48_MT_RR_FREQ_REDEF:
+ rc = gsm48_rr_rx_freq_redef(ms, msg);
+ break;
+#endif
+ default:
+ LOGP(DRR, LOGL_NOTICE, "Message type 0x%02x unknown.\n",
+ gh->msg_type);
+
+ /* status message */
+ gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_MSG_TYPE_N);
+ }
+
+ msgb_free(msg);
+ return rc;
+ }
+
+ /* pull off RSL header up to L3 message */
+ msgb_pull(msg, (long)msgb_l3(msg) - (long)msg->data);
+
+ /* push RR header */
+ msgb_push(msg, sizeof(struct gsm48_rr_hdr));
+ rrh = (struct gsm48_rr_hdr *)msg->data;
+ rrh->msg_type = GSM48_RR_DATA_IND;
+
+ return gsm48_rr_upmsg(ms, msg);
+}
+
+/* unit data from layer 2 to RR layer */
+static int gsm48_rr_unit_data_ind(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+
+ switch (gh->msg_type) {
+ case GSM48_MT_RR_SYSINFO_1:
+ return gsm48_rr_rx_sysinfo1(ms, msg);
+ case GSM48_MT_RR_SYSINFO_2:
+ return gsm48_rr_rx_sysinfo2(ms, msg);
+ case GSM48_MT_RR_SYSINFO_2bis:
+ return gsm48_rr_rx_sysinfo2bis(ms, msg);
+ case GSM48_MT_RR_SYSINFO_2ter:
+ return gsm48_rr_rx_sysinfo2ter(ms, msg);
+ case GSM48_MT_RR_SYSINFO_3:
+ return gsm48_rr_rx_sysinfo3(ms, msg);
+ case GSM48_MT_RR_SYSINFO_4:
+ return gsm48_rr_rx_sysinfo4(ms, msg);
+ case GSM48_MT_RR_SYSINFO_5:
+#warning todo bcch or sacch
+ return gsm48_rr_rx_sysinfo5(ms, msg);
+ case GSM48_MT_RR_SYSINFO_5bis:
+ return gsm48_rr_rx_sysinfo5bis(ms, msg);
+ case GSM48_MT_RR_SYSINFO_5ter:
+ return gsm48_rr_rx_sysinfo5ter(ms, msg);
+ case GSM48_MT_RR_SYSINFO_6:
+ return gsm48_rr_rx_sysinfo6(ms, msg);
+ case GSM48_MT_RR_PAG_REQ_1:
+ return gsm48_rr_rx_pag_req_1(ms, msg);
+ case GSM48_MT_RR_PAG_REQ_2:
+ return gsm48_rr_rx_pag_req_2(ms, msg);
+ case GSM48_MT_RR_PAG_REQ_3:
+ return gsm48_rr_rx_pag_req_3(ms, msg);
+ case GSM48_MT_RR_IMM_ASS:
+ return gsm48_rr_rx_imm_ass(ms, msg);
+ case GSM48_MT_RR_IMM_ASS_EXT:
+ return gsm48_rr_rx_imm_ass_ext(ms, msg);
+ case GSM48_MT_RR_IMM_ASS_REJ:
+ return gsm48_rr_rx_imm_ass_rej(ms, msg);
+ default:
+ LOGP(DRR, LOGL_NOTICE, "Message type 0x%02x unknown.\n",
+ gh->msg_type);
+ return -EINVAL;
+ }
+}
+
+/*
+ * state machines
+ */
+
+/* state trasitions for link layer messages (lower layer) */
+static struct dldatastate {
+ uint32_t states;
+ int type;
+ const char *type_name;
+ int (*rout) (struct osmocom_ms *ms, struct msgb *msg);
+} dldatastatelist[] = {
+ {SBIT(GSM48_RR_ST_IDLE) | SBIT(GSM48_RR_ST_CONN_PEND),
+ RSL_MT_UNIT_DATA_IND, "UNIT_DATA_IND", gsm48_rr_unit_data_ind},
+ {SBIT(GSM48_RR_ST_DEDICATED), /* 3.4.2 */
+ RSL_MT_DATA_IND, "DATA_IND", gsm48_rr_data_ind},
+ {SBIT(GSM48_RR_ST_IDLE) | SBIT(GSM48_RR_ST_CONN_PEND),
+ RSL_MT_EST_CONF, "EST_CONF", gsm48_rr_estab_cnf},
+#if 0
+ {SBIT(GSM48_RR_ST_DEDICATED),
+ RSL_MT_EST_CONF, "EST_CONF", gsm48_rr_estab_cnf_dedicated},
+ {SBIT(GSM_RRSTATE),
+ RSL_MT_CONNECT_CNF, "CONNECT_CNF", gsm48_rr_connect_cnf},
+ {SBIT(GSM_RRSTATE),
+ RSL_MT_RELEASE_IND, "REL_IND", gsm48_rr_rel_ind},
+#endif
+ {SBIT(GSM48_RR_ST_IDLE) | SBIT(GSM48_RR_ST_CONN_PEND),
+ RSL_MT_REL_CONF, "REL_CONF", gsm48_rr_rel_cnf},
+#if 0
+ {SBIT(GSM48_RR_ST_DEDICATED),
+ RSL_MT_REL_CONF, "REL_CONF", gsm48_rr_rel_cnf_dedicated},
+#endif
+ {SBIT(GSM48_RR_ST_CONN_PEND), /* 3.3.1.1.2 */
+ RSL_MT_CHAN_CNF, "CHAN_CNF", gsm48_rr_rand_acc_cnf},
+#if 0
+ {SBIT(GSM48_RR_ST_DEDICATED),
+ RSL_MT_CHAN_CNF, "CHAN_CNF", gsm48_rr_rand_acc_cnf_dedicated},
+ {SBIT(GSM_RRSTATE),
+ RSL_MT_MDL_ERROR_IND, "MDL_ERROR_IND", gsm48_rr_mdl_error_ind},
+#endif
+};
+
+#define DLDATASLLEN \
+ (sizeof(dldatastatelist) / sizeof(struct dldatastate))
+
+static int gsm48_rcv_rsl(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+ int msg_type = rllh->c.msg_type;
+ int i;
+ int rc;
+
+ /* find function for current state and message */
+ for (i = 0; i < DLDATASLLEN; i++)
+ if ((msg_type == dldatastatelist[i].type)
+ && ((1 << rr->state) & dldatastatelist[i].states))
+ break;
+ if (i == DLDATASLLEN) {
+ LOGP(DRR, LOGL_NOTICE, "RSL message 0x%02x unhandled at state "
+ "%s.\n", msg_type, gsm48_rr_state_names[rr->state]);
+ msgb_free(msg);
+ return 0;
+ }
+ LOGP(DRR, LOGL_INFO, "(ms %s) Received 'RSL_MT_%s' from RSL in state "
+ "%s\n", ms->name, dldatastatelist[i].type_name,
+ gsm48_rr_state_names[rr->state]);
+
+ rc = dldatastatelist[i].rout(ms, msg);
+
+ /* free msgb unless it is forwarded */
+ if (dldatastatelist[i].rout != gsm48_rr_data_ind)
+ msgb_free(msg);
+
+ return rc;
+}
+
+/* state trasitions for RR-SAP messages from up */
+static struct rrdownstate {
+ uint32_t states;
+ int type;
+ int (*rout) (struct osmocom_ms *ms, struct msgb *msg);
+} rrdownstatelist[] = {
+ {SBIT(GSM48_RR_ST_IDLE), /* 3.3.1.1 */
+ GSM48_RR_EST_REQ, gsm48_rr_est_req},
+ {SBIT(GSM48_RR_ST_DEDICATED), /* 3.4.2 */
+ GSM48_RR_DATA_REQ, gsm48_rr_data_req},
+#if 0
+ {SBIT(GSM48_RR_ST_CONN_PEND) | SBIT(GSM48_RR_ST_DEDICATED),
+ GSM48_RR_ABORT_REQ, gsm48_rr_abort_req},
+ {SBIT(GSM48_RR_ST_DEDICATED),
+ GSM48_RR_ACT_REQ, gsm48_rr_act_req},
+#endif
+};
+
+#define RRDOWNSLLEN \
+ (sizeof(rrdownstatelist) / sizeof(struct rrdownstate))
+
+int gsm48_rr_downmsg(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *) msg->data;
+ int msg_type = rrh->msg_type;
+ int i;
+ int rc;
+
+ LOGP(DRR, LOGL_INFO, "(ms %s) Message '%s' received in state %s\n",
+ ms->name, get_rr_name(msg_type),
+ gsm48_rr_state_names[rr->state]);
+
+ /* find function for current state and message */
+ for (i = 0; i < RRDOWNSLLEN; i++)
+ if ((msg_type == rrdownstatelist[i].type)
+ && ((1 << rr->state) & rrdownstatelist[i].states))
+ break;
+ if (i == RRDOWNSLLEN) {
+ LOGP(DRR, LOGL_NOTICE, "Message unhandled at this state.\n");
+ msgb_free(msg);
+ return 0;
+ }
+
+ rc = rrdownstatelist[i].rout(ms, msg);
+
+ /* free msgb uless it is forwarded */
+ if (rrdownstatelist[i].rout != gsm48_rr_data_req)
+ msgb_free(msg);
+
+ return rc;
+}
+
+/*
+ * init/exit
+ */
+
+int gsm48_rr_init(struct osmocom_ms *ms)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+
+ memset(rr, 0, sizeof(*rr));
+ rr->ms = ms;
+
+ INIT_LLIST_HEAD(&rr->rsl_upqueue);
+ INIT_LLIST_HEAD(&rr->downqueue);
+ /* downqueue is handled here, so don't add_work */
+
+ osmol2_register_handler(ms, &gsm48_rx_rsl);
+
+ return 0;
+}
+
+int gsm48_rr_exit(struct osmocom_ms *ms)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct msgb *msg;
+
+ /* flush queues */
+ while ((msg = msgb_dequeue(&rr->rsl_upqueue)))
+ msgb_free(msg);
+ while ((msg = msgb_dequeue(&rr->downqueue)))
+ msgb_free(msg);
+
+ if (rr->rr_est_msg) {
+ msgb_free(rr->rr_est_msg);
+ rr->rr_est_msg = NULL;
+ }
+
+ stop_rr_t3122(rr);
+ stop_rr_t3126(rr);
+
+ return 0;
+}
+
+#if 0
+
+the process above is complete
+------------------------------------------------------------------------------
+incomplete
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+todo:
+
+add support structure
+initialize support structure
+
+queue messages (rslms_data_req) if channel changes
+
+flush rach msg in all cases: during sending, after its done, and when aborted
+stop timers on abort
+debugging. (wenn dies todo erledigt ist, bitte in den anderen code moven)
+wird beim abbruch immer der gepufferte cm-service-request entfernt, auch beim verschicken?:
+measurement reports
+todo rr_sync_ind when receiving ciph, re ass, channel mode modify
+
+todo change procedures, release procedure
+
+during procedures, like "channel assignment" or "handover", rr requests must be queued
+they must be dequeued when complete
+they queue must be flushed when rr fails
+
+#include <osmocore/protocol/gsm_04_08.h>
+#include <osmocore/msgb.h>
+#include <osmocore/utils.h>
+#include <osmocore/gsm48.h>
+
+static int gsm48_rr_abort_req(struct osmocom_ms *ms, struct gsm48_rr *rrmsg)
+{
+ struct gsm48_rrlayer *rr = ms->rrlayer;
+ stop_rr_t3126(rr);
+ if (rr->state == GSM48_RR_ST_DEDICATED) {
+ struct gsm_dl dlmsg;
+
+ memset(&dlmsg, 0, sizeof(dlmsg));
+ return gsm48_send_rsl(ms, RSL_MT_REL_REQ, nmsg);
+ }
+ new_rr_state(rr, GSM48_RR_ST_IDLE);
+}
+
+static int gsm48_rr_act_req(struct osmocom_ms *ms, struct gsm48_rr *rrmsg)
+{
+}
+
+
+}
+
+/* memcopy of LV of given IE from tlv_parsed structure */
+static int tlv_copy(void *dest, int dest_len, struct tlv_parsed *tp, uint8_t ie)
+{
+ uint8_t *lv = dest;
+ uint8_t len;
+
+ if (dest_len < 1)
+ return -EINVAL;
+ lv[0] = 0;
+
+ if (!TLVP_PRESENT(tp, ie))
+ return 0;
+
+ len = TLVP_LEN(tp, ie);
+ if (len < 1)
+ return 0;
+ if (len + 1 > dest_len)
+ return -ENOMEM;
+
+ memcpy(dest, TLVP_VAL(tp, ie) - 1, len + 1);
+ return 0;
+}
+
+static int gsm48_rr_rx_ass_cmd(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = ms->rrlayer;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct gsm48_ass_cmd *ac = (struct gsm48_ass_cmd *)gh->data;
+ int payload_len = msgb_l3len(msg) - sizeof(*gh) - sizeof(*ac);
+ struct tlv_parsed tp;
+ struct gsm48_rr_chan_desc cd;
+ struct msgb *nmsg;
+
+ memset(&cd, 0, sizeof(cd));
+
+ if (payload_len < 0) {
+ LOGP(DRR, LOGL_NOTICE, "Short read of ASSIGNMENT COMMAND message.\n");
+ return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_PROT_ERROR_UNSPC);
+ }
+ tlv_parse(&tp, &gsm48_rr_att_tlvdef, ac->data, payload_len, 0, 0);
+
+ /* channel description */
+ memcpy(&cd.chan_desc, &ac->chan_desc, sizeof(chan_desc));
+ /* power command */
+ cd.power_command = ac->power_command;
+ /* frequency list, after timer */
+ tlv_copy(&cd.fl, sizeof(fl_after), &tp, GSM48_IE_FRQLIST_AFTER);
+ /* cell channel description */
+ tlv_copy(&cd.ccd, sizeof(ccd), &tp, GSM48_IE_CELL_CH_DESC);
+ /* multislot allocation */
+ tlv_copy(&cd.multia, sizeof(ma), &tp, GSM48_IE_MSLOT_DESC);
+ /* channel mode */
+ tlv_copy(&cd.chanmode, sizeof(chanmode), &tp, GSM48_IE_CHANMODE_1);
+ /* mobile allocation, after time */
+ tlv_copy(&cd.moba_after, sizeof(moba_after), &tp, GSM48_IE_MOB_AL_AFTER);
+ /* starting time */
+ tlv_copy(&cd.start, sizeof(start), &tp, GSM_IE_START_TIME);
+ /* frequency list, before time */
+ tlv_copy(&cd.fl_before, sizeof(fl_before), &tp, GSM48_IE_FRQLIST_BEFORE);
+ /* channel description, before time */
+ tlv_copy(&cd.chan_desc_before, sizeof(cd_before), &tp, GSM48_IE_CHDES_1_BEFORE);
+ /* frequency channel sequence, before time */
+ tlv_copy(&cd.fcs_before, sizeof(fcs_before), &tp, GSM48_IE_FRQSEQ_BEFORE);
+ /* mobile allocation, before time */
+ tlv_copy(&cd.moba_before, sizeof(moba_before), &tp, GSM48_IE_MOB_AL_BEFORE);
+ /* cipher mode setting */
+ if (TLVP_PRESENT(&tp, GSM48_IE_CIP_MODE_SET))
+ cd.cipher = *TLVP_VAL(&tp, GSM48_IE_CIP_MODE_SET);
+ else
+ cd.cipher = 0;
+
+ if (no CA) {
+ LOGP(DRR, LOGL_INFO, "No current cell allocation available.\n");
+ return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_NO_CELL_ALLOC_A);
+ }
+
+ if (not supported) {
+ LOGP(DRR, LOGL_INFO, "New channel is not supported.\n");
+ return gsm48_rr_tx_rr_status(ms, RR_CAUSE_CHAN_MODE_UNACCEPT);
+ }
+
+ if (freq not supported) {
+ LOGP(DRR, LOGL_INFO, "New frequency is not supported.\n");
+ return gsm48_rr_tx_rr_status(ms, RR_CAUSE_FREQ_NOT_IMPLEMENTED);
+ }
+
+ /* store current channel descriptions, to return in case of failure */
+ memcpy(&rr->chan_last, &rr->chan_desc, sizeof(*cd));
+ /* copy new description */
+ memcpy(&rr->chan_desc, cd, sizeof(cd));
+
+ /* start suspension of current link */
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gsm48_send_rsl(ms, RSL_MT_SUSP_REQ, msg);
+
+ /* change into special assignment suspension state */
+ rr->assign_susp_state = 1;
+ rr->resume_last_state = 0;
+
+ return 0;
+}
+
+/* decode "BA Range" (10.5.2.1a) */
+static int gsm48_decode_ba_range(uint8_t *ba, uint8_t, ba_len, uint32_t *range,
+ uint8_t *ranges, int max_ranges)
+{
+ /* ba = pointer to IE without IE type and length octets
+ * ba_len = number of octets
+ * range = pointer to store decoded range
+ * ranges = number of ranges decoded
+ * max_ranges = maximum number of decoded ranges that can be stored
+ */
+ uint16_t lower, higher;
+ int i, n, required_octets;
+
+ /* find out how much ba ranges will be decoded */
+ n = *ba++;
+ ba_len --;
+ required_octets = 5 * (n >> 1) + 3 * (n & 1);
+ if (required_octets > n) {
+ *ranges = 0;
+ return -EINVAL;
+ }
+ if (max_ranges > n)
+ n = max_ranges;
+
+ /* decode ranges */
+ for (i = 0; i < n; i++) {
+ if (!(i & 1)) {
+ /* decode even range number */
+ lower = *ba++ << 2;
+ lower |= (*ba >> 6);
+ higher = (*ba++ & 0x3f) << 4;
+ higher |= *ba >> 4;
+ } else {
+ lower = (*ba++ & 0x0f) << 6;
+ lower |= *ba >> 2;
+ higher = (*ba++ & 0x03) << 8;
+ higher |= *ba++;
+ /* decode odd range number */
+ }
+ *range++ = (higher << 16) | lower;
+ }
+ *ranges = n;
+
+ return 0;
+}
+
+
+/* decode "Cell Description" (10.5.2.2) */
+static int gsm48_decode_cell_desc(struct gsm48_cell_desc *cd, uint16_t *arfcn, uint8_t *ncc uint8_t *bcc)
+{
+ *arfcn = (cd->bcch_hi << 8) + cd->bcch_lo;
+ *ncc = cd->ncc;
+ *bcc = cd->bcc;
+}
+
+/* decode "Power Command" (10.5.2.28) and (10.5.2.28a) */
+static int gsm48_decode_power_cmd_acc(struct gsm48_power_cmd *pc, uint8_t *power_level uint8_t *atc)
+{
+ *power_level = pc->power_level;
+ if (atc) /* only in case of 10.5.2.28a */
+ *atc = pc->atc;
+}
+
+/* decode "Synchronization Indication" (10.5.2.39) */
+static int gsm48_decode_power_cmd_acc(struct gsm48_rrlayer *rr, struct gsm48_rr_sync_ind *si)
+{
+ rr->ho_sync_ind = si->si;
+ rr->ho_rot = si->rot;
+ rr->ho_nci = si->nci;
+}
+
+/* receiving HANDOVER COMMAND message (9.1.15) */
+static int gsm48_rr_rx_hando_cmd(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = ms->rrlayer;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct gsm48_ho_cmd *ho = (struct gsm48_ho_cmd *)gh->data;
+ int payload_len = msgb_l3len(msg) - sizeof(*gh) - wirklich sizeof(*ho);
+ struct tlv_parsed tp;
+ struct gsm48_rr_chan_desc cd;
+ struct msgb *nmsg;
+
+ memset(&cd, 0, sizeof(cd));
+
+ if (payload_len < 0) {
+ LOGP(DRR, LOGL_NOTICE, "Short read of HANDOVER COMMAND message.\n");
+ return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_PROT_ERROR_UNSPC);
+ }
+ tlv_parse(&tp, &gsm48_rr_att_tlvdef, ho->data, payload_len, 0, 0);
+
+ /* decode Cell Description */
+ gsm_decode_cell_desc(&ho->cell_desc, &cd.bcch_arfcn, &cd.ncc, &cd.bcc);
+ /* Channel Description */
+ memcpy(&rr->chan_desc.chan_desc, ho->chan_desc, 3);
+ /* Handover Reference */
+ rr->hando_ref = ho->ho_ref;
+ /* Power Command and access type */
+ gsm_decode_power_cmd_acc((struct gsm48_power_cmd *)&ho->power_command,
+ &cd.power_level, cd.atc);
+ /* Synchronization Indication */
+ if (TLVP_PRESENT(&tp, GSM48_IE_SYNC_IND))
+ gsm48_decode_sync_ind(rr,
+ TLVP_VAL(&tp, GSM48_IE_MOBILE_ALLOC)-1, &cd);
+ /* Frequency Sort List */
+ if (TLVP_PRESENT(&tp, GSM48_IE_FREQ_SHORT_LIST))
+ gsm48_decode_freq_list(&ms->support, s->freq,
+ TLVP_VAL(&tp, GSM48_IE_MOBILE_ALLOC),
+ *(TLVP_VAL(&tp, GSM48_IE_MOBILE_ALLOC)-1),
+ 0xce, FREQ_TYPE_SERV);
+
+
+today: more IE parsing
+
+ /* store current channel descriptions, to return in case of failure */
+ memcpy(&rr->chan_last, &rr->chan_desc, sizeof(*cd));
+ /* copy new description */
+ memcpy(&rr->chan_desc, cd, sizeof(cd));
+
+ /* start suspension of current link */
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ gsm48_send_rsl(ms, RSL_MT_SUSP_REQ, msg);
+
+ /* change into special handover suspension state */
+ rr->hando_susp_state = 1;
+ rr->resume_last_state = 0;
+
+ return 0;
+}
+
+static int gsm48_rr_estab_cnf_dedicated(struct osmocom_ms *ms, struct msgb *msg)
+{
+ if (rr->hando_susp_state || rr->assign_susp_state) {
+ if (rr->resume_last_state) {
+ rr->resume_last_state = 0;
+ gsm48_rr_tx_ass_cpl(ms, GSM48_RR_CAUSE_NORMAL);
+ } else {
+ gsm48_rr_tx_ass_fail(ms, RR_CAUSE_PROTO_ERR_UNSPEC);
+ }
+ /* transmit queued frames during ho / ass transition */
+ gsm48_rr_dequeue_down(ms);
+ }
+
+ return 0;
+}
+
+static int gsm48_rr_connect_cnf(struct osmocom_ms *ms, struct msgbl *msg)
+{
+}
+
+static int gsm48_rr_rel_ind(struct osmocom_ms *ms, struct msgb *msg)
+{
+}
+
+static int gsm48_rr_rel_cnf_dedicated(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = ms->rrlayer;
+ struct msgb *nmsg;
+
+ if (rr->hando_susp_state || rr->assign_susp_state) {
+ struct msgb *msg;
+
+ /* change radio to new channel */
+ rr->arfcn = rr->chan_desc.chan_desc.chan_nr;
+ tx_ph_dm_est_req(ms, arfcn, rr->arfcn);
+
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ /* send DL-ESTABLISH REQUEST */
+ gsm48_send_rsl(ms, RSL_MT_EST_REQ, nmsg);
+
+ }
+ if (rr->hando_susp_state) {
+ gsm48_rr_tx_hando_access(ms);
+ rr->hando_acc_left = 3;
+ }
+ return 0;
+}
+
+static int gsm48_rr_mdl_error_ind(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = ms->rrlayer;
+ struct msgb *nmsg;
+ struct gsm_rr_hdr *nrrh;
+
+ if (rr->hando_susp_state || rr->assign_susp_state) {
+ if (!rr->resume_last_state) {
+ rr->resume_last_state = 1;
+
+ /* get old channel description */
+ memcpy(&rr->chan_desc, &rr->chan_last, sizeof(*cd));
+
+ /* change radio to old channel */
+ rr->arfcn = rr->chan_desc.chan_desc.chan_nr;
+ tx_ph_dm_est_req(ms, arfcn, rr->arfcn);
+
+ /* re-establish old link */
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ return gsm48_send_rsl(ms, RSL_MT_EST_REQ, nmsg);
+ }
+ rr->resume_last_state = 0;
+ }
+
+ /* deactivate channel */
+ tx_ph_dm_rel_req(ms, arfcn, rr->chan_desc.chan_desc.chan_nr);
+
+ /* send abort ind to upper layer */
+ nmsg = gsm48_mm_msgb_alloc();
+
+ if (!msg)
+ return -ENOMEM;
+ nrrh = (struct gsm_mm_hdr *)nmsg->data;
+ nrrh->msg_type = RR_ABORT_IND;
+ nrrh->cause = GSM_MM_CAUSE_LINK_FAILURE;
+ return gsm48_rr_upmsg(ms, msg);
+}
+
+static void timeout_rr_t3124(void *arg)
+{
+ struct gsm48_rrlayer *rr = arg;
+ struct msgb *nmsg;
+
+ /* stop sending more access bursts when timer expired */
+ hando_acc_left = 0;
+
+ /* get old channel description */
+ memcpy(&rr->chan_desc, &rr->chan_last, sizeof(*cd));
+
+ /* change radio to old channel */
+ rr->arfcn = rr->chan_desc.chan_desc.chan_nr;
+ tx_ph_dm_est_req(ms, arfcn, rr->arfcn);
+
+ /* re-establish old link */
+ nmsg = gsm48_l3_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ return gsm48_send_rsl(ms, RSL_MT_EST_REQ, nmsg);
+
+ todo
+}
+
+/* send HANDOVER ACCESS burst (9.1.14) */
+static int gsm48_rr_tx_hando_access(struct osmocom_ms *ms)
+{
+ nmsg = msgb_alloc_headroom(20, 16, "HAND_ACCESS");
+ if (!nmsg)
+ return -ENOMEM;
+ *msgb_put(nmsg, 1) = rr->hando_ref;
+ todo burst
+ return gsm48_send_rsl(ms, RSL_MT_RAND_ACC_REQ, nmsg);
+}
+
+/* send next channel request in dedicated state */
+static int gsm48_rr_rand_acc_cnf_dedicated(struct osmocom_ms *ms, struct msgb *msg)
+{
+ struct gsm48_rrlayer *rr = &ms->rrlayer;
+ struct msgb *nmsg;
+ int s;
+
+ if (!rr->hando_susp_state) {
+ LOGP(DRR, LOGL_NOTICE, "Random acces confirm, but not in handover state.\n");
+ return 0;
+ }
+
+ /* send up to four handover access bursts */
+ if (rr->hando_acc_left) {
+ rr->hando_acc_left--;
+ gsm48_rr_tx_hando_access(ms);
+ return;
+ }
+
+ /* start timer for sending next HANDOVER ACCESS bursts afterwards */
+ if (!bsc_timer_pending(&rr->t3124)) {
+ if (allocated channel is SDCCH)
+ start_rr_t3124(rr, GSM_T3124_675);
+ else
+ start_rr_t3124(rr, GSM_T3124_320);
+ if (!rr->n_chan_req) {
+ start_rr_t3126(rr, 5, 0); /* TODO improve! */
+ return 0;
+ }
+ rr->n_chan_req--;
+
+ /* wait for PHYSICAL INFORMATION message or T3124 timeout */
+ return 0;
+
+}
+
+#endif
+
+
diff --git a/src/host/layer23/src/main.c b/src/host/layer23/src/main.c
index 8e726e9..09edfc3 100644
--- a/src/host/layer23/src/main.c
+++ b/src/host/layer23/src/main.c
@@ -131,9 +131,9 @@ int osmo_send_l1(struct osmocom_ms *ms, struct msgb *msg)
return 0;
}
-static void print_usage()
+static void print_usage(const char *app)
{
- printf("Usage: ./layer2\n");
+ printf("Usage: %s\n", app);
}
static void print_help()
@@ -166,7 +166,7 @@ static void handle_options(int argc, char **argv)
switch (c) {
case 'h':
- print_usage();
+ print_usage(argv[0]);
print_help();
exit(0);
break;
diff --git a/src/host/layer23/src/mnccms.c b/src/host/layer23/src/mnccms.c
new file mode 100644
index 0000000..d2ee8a5
--- /dev/null
+++ b/src/host/layer23/src/mnccms.c
@@ -0,0 +1,60 @@
+/*
+ * (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 <osmocore/talloc.h>
+
+#include <osmocom/logging.h>
+#include <osmocom/osmocom_data.h>
+#include <osmocom/mncc.h>
+
+/*
+ * support functions
+ */
+
+void mncc_set_cause(struct gsm_mncc *data, int loc, int val);
+
+/*
+ * MNCCms dummy application
+ */
+
+/* this is a minimal implementation as required by GSM 04.08 */
+int mncc_recv_dummy(struct osmocom_ms *ms, int msg_type, void *arg)
+{
+ struct gsm_mncc *data = arg;
+ int callref = data->callref;
+ struct gsm_mncc rel;
+
+ /* reject, as we don't support Calls */
+ memset(&rel, 0, sizeof(struct gsm_mncc));
+ rel.callref = callref;
+ mncc_set_cause(&rel, GSM48_CAUSE_LOC_USER,
+ GSM48_CC_CAUSE_INCOMPAT_DEST);
+
+ return mncc_send(ms, MNCC_REL_REQ, &rel);
+}
+
+
diff --git a/src/host/layer23/src/subscriber.c b/src/host/layer23/src/subscriber.c
new file mode 100644
index 0000000..a988944
--- /dev/null
+++ b/src/host/layer23/src/subscriber.c
@@ -0,0 +1,147 @@
+/*
+ * (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 <string.h>
+#include <osmocore/talloc.h>
+
+#include <osmocom/logging.h>
+#include <osmocom/osmocom_data.h>
+
+
+int gsm_subscr_init(struct osmocom_ms *ms)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+
+ memset(subscr, 0, sizeof(*subscr));
+ subscr->ms = ms;
+
+ /* set key invalid */
+ subscr->key_seq = 7;
+
+ /* init lists */
+ INIT_LLIST_HEAD(&subscr->plmn_list);
+ INIT_LLIST_HEAD(&subscr->plmn_na);
+
+ return 0;
+}
+
+int gsm_subscr_exit(struct osmocom_ms *ms)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct llist_head *lh, *lh2;
+
+ /* flush lists */
+ llist_for_each_safe(lh, lh2, &subscr->plmn_list) {
+ llist_del(lh);
+ talloc_free(lh);
+ }
+ llist_for_each_safe(lh, lh2, &subscr->plmn_na) {
+ llist_del(lh);
+ talloc_free(lh);
+ }
+
+ return 0;
+}
+
+/* Attach test card, no sim must be present */
+int gsm_subscr_testcard(struct osmocom_ms *ms, int mcc, int mnc, char *msin)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct msgb *msg;
+
+ if (subscr->sim_valid) {
+ LOGP(DMM, LOGL_ERROR, "Cannot insert card, until current card "
+ "is detached.\n");
+ return -EBUSY;
+ }
+
+ if (strlen(msin) != 10) {
+ LOGP(DMM, LOGL_ERROR, "MSIN '%s' Error.\n", msin);
+ return -EINVAL;
+ }
+
+ /* reset subscriber */
+ gsm_subscr_exit(ms);
+ gsm_subscr_init(ms);
+
+ sprintf(subscr->sim_name, "test");
+ // TODO: load / save SIM to file system
+ subscr->sim_valid = 1;
+ subscr->ustate = GSM_SIM_U2_NOT_UPDATED;
+ subscr->acc_barr = 1; /* we may access any barred cell */
+ subscr->acc_class = 0xfbff; /* we have any access class */
+ subscr->mcc = mcc;
+ subscr->mnc = mnc;
+ subscr->always_search_hplmn = 1;
+ subscr->t6m_hplmn = 1; /* try to find home network every 6 min */
+ snprintf(subscr->imsi, 15, "%03d%02d%s", mcc, mnc, msin);
+
+ LOGP(DMM, LOGL_INFO, "(ms %s) Inserting test card (mnc=%d mnc=%d "
+ "imsi=%s)\n", ms->name, mcc, mnc, subscr->imsi);
+
+ /* insert card */
+ msg = gsm48_mmr_msgb_alloc(GSM48_MMR_REG_REQ);
+ if (!msg)
+ return -ENOMEM;
+ gsm48_mmr_downmsg(ms, msg);
+
+ return 0;
+}
+
+/* Detach card */
+int gsm_subscr_remove(struct osmocom_ms *ms)
+{
+ struct gsm_subscriber *subscr = &ms->subscr;
+ struct msgb *msg;
+
+ if (!subscr->sim_valid) {
+ LOGP(DMM, LOGL_ERROR, "Cannot remove card, no card present\n");
+ return -EINVAL;
+ }
+
+ /* remove card */
+ msg = gsm48_mmr_msgb_alloc(GSM48_MMR_NREG_REQ);
+ if (!msg)
+ return -ENOMEM;
+ gsm48_mmr_downmsg(ms, msg);
+
+ return 0;
+}
+
+static const char *subscr_ustate_names[] = {
+ "U0_NULL",
+ "U1_UPDATED",
+ "U2_NOT_UPDATED",
+ "U3_ROAMING_NA"
+};
+
+/* change to new U state */
+void new_sim_ustate(struct gsm_subscriber *subscr, int state)
+{
+ LOGP(DMM, LOGL_INFO, "(ms %s) new state %s -> %s\n", subscr->ms->name,
+ subscr_ustate_names[subscr->ustate],
+ subscr_ustate_names[state]);
+
+ subscr->ustate = state;
+}
+
diff --git a/src/host/layer23/src/support.c b/src/host/layer23/src/support.c
new file mode 100644
index 0000000..1fb5223
--- /dev/null
+++ b/src/host/layer23/src/support.c
@@ -0,0 +1,106 @@
+/*
+ * (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 <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <osmocom/osmocom_data.h>
+
+void gsm_support_init(struct osmocom_ms *ms)
+{
+ struct gsm_support *s = &ms->support;
+ int i;
+
+ memset(s, 0, sizeof(*s));
+
+ /* rf power capability */
+ s->pwr_lev_900 = 3; /* CLASS 4: Handheld 2W */
+ s->pwr_lev_1800 = 0; /* CLASS 1: Handheld 1W */
+ /* controlled early classmark sending */
+ s->es_ind = 0; /* no */
+ /* revision level */
+ s->rev_lev = 1; /* phase 2 mobile station */
+ /* support of VGCS */
+ s->vgcs = 0; /* no */
+ /* support of VBS */
+ s->vbs = 0; /* no */
+ /* support of SMS */
+ s->sms_ptp = 1; /* yes */
+ /* screening indicator */
+ s->ss_ind = 1; /* phase 2 error handling */
+ /* pseudo synchronised capability */
+ s->ps_cap = 0; /* no */
+ /* CM service prompt */
+ s->cmsp = 0; /* no */
+ /* solsa support */
+ s->solsa = 0; /* no */
+ /* location service support */
+ s->lcsva = 0; /* no */
+ s->loc_serv = 0; /* no */
+ /* codec supprot */
+ s->a5_1 = 0; /* currently not */
+ s->a5_2 = 0;
+ s->a5_3 = 0;
+ s->a5_4 = 0;
+ s->a5_5 = 0;
+ s->a5_6 = 0;
+ s->a5_7 = 0;
+ /* radio support */
+ s->p_gsm = 1; /* gsm 900 */
+ s->e_gsm = 0;
+ s->r_gsm = 0;
+ s->r_capa = 0;
+ s->low_capa = 4; /* p,e,r power class */
+ s->dcs_1800 = 1;
+ /* set supported frequencies */
+ if (s->e_gsm || s->r_gsm)
+ s->freq_map[0] |= 1;
+ if (s->p_gsm || s->e_gsm || s->r_gsm)
+ for(i = 1; i <= 124; i++)
+ s->freq_map[i >> 3] |= (1 << (i & 7));
+ if (s->dcs_1800)
+ for(i = 512; i <= 885; i++)
+ s->freq_map[i >> 3] |= (1 << (i & 7));
+ if (s->e_gsm)
+ for(i = 975; i <= 1023; i++)
+ s->freq_map[i >> 3] |= (1 << (i & 7));
+ if (s->r_gsm)
+ for(i = 955; i <= 1023; i++)
+ s->freq_map[i >> 3] |= (1 << (i & 7));
+ s->dcs_capa = 1; /* dcs power class */
+ /* multi slot support */
+ s->ms_sup = 0; /* no */
+ /* ucs2 treatment */
+ s->ucs2_treat = 0; /* default */
+ /* support extended measurements */
+ s->ext_meas = 0; /* no */
+ /* support switched measurement capability */
+ s->meas_cap = 0; /* no */
+ //s->sms_val = ;
+ //s->sm_val = ;
+
+ /* IMEI */
+ sprintf(s->imei, "000000000000000");
+ sprintf(s->imeisv, "0000000000000000");
+}
+
+
diff --git a/src/host/layer23/src/sysinfo.c b/src/host/layer23/src/sysinfo.c
new file mode 100644
index 0000000..b3d3a41
--- /dev/null
+++ b/src/host/layer23/src/sysinfo.c
@@ -0,0 +1,33 @@
+/*
+ * (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 <string.h>
+
+#include <osmocom/osmocom_data.h>
+
+void gsm48_sysinfo_init(struct osmocom_ms *ms)
+{
+ struct gsm48_sysinfo *s = &ms->sysinfo;
+
+ memset(s, 0, sizeof(*s));
+};
+
diff --git a/src/host/layer23/src/transaction.c b/src/host/layer23/src/transaction.c
new file mode 100644
index 0000000..8fca909
--- /dev/null
+++ b/src/host/layer23/src/transaction.c
@@ -0,0 +1,138 @@
+/* GSM 04.07 Transaction handling */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * 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 <osmocore/talloc.h>
+#include <osmocore/timer.h>
+#include <osmocore/msgb.h>
+
+#include <osmocom/osmocom_data.h>
+#include <osmocom/mncc.h>
+#include <osmocom/transaction.h>
+#include <osmocom/logging.h>
+
+extern void *l23_ctx;
+
+void _gsm48_cc_trans_free(struct gsm_trans *trans);
+
+struct gsm_trans *trans_find_by_id(struct osmocom_ms *ms,
+ uint8_t proto, uint8_t trans_id)
+{
+ struct gsm_trans *trans;
+
+ llist_for_each_entry(trans, &ms->trans_list, entry) {
+ if (trans->protocol == proto &&
+ trans->transaction_id == trans_id)
+ return trans;
+ }
+ return NULL;
+}
+
+struct gsm_trans *trans_find_by_callref(struct osmocom_ms *ms,
+ uint32_t callref)
+{
+ struct gsm_trans *trans;
+
+ llist_for_each_entry(trans, &ms->trans_list, entry) {
+ if (trans->callref == callref)
+ return trans;
+ }
+ return NULL;
+}
+
+struct gsm_trans *trans_alloc(struct osmocom_ms *ms,
+ uint8_t protocol, uint8_t trans_id,
+ uint32_t callref)
+{
+ struct gsm_trans *trans;
+
+ DEBUGP(DCC, "ms %s\n", ms->name);
+
+ trans = talloc_zero(l23_ctx, struct gsm_trans);
+ if (!trans)
+ return NULL;
+
+ trans->ms = ms;
+
+ trans->protocol = protocol;
+ trans->transaction_id = trans_id;
+ trans->callref = callref;
+
+ llist_add_tail(&trans->entry, &ms->trans_list);
+
+ return trans;
+}
+
+void trans_free(struct gsm_trans *trans)
+{
+ switch (trans->protocol) {
+ case GSM48_PDISC_CC:
+ _gsm48_cc_trans_free(trans);
+ break;
+#if 0
+ case GSM48_PDISC_SS:
+ _gsm411_ss_trans_free(trans);
+ break;
+ case GSM48_PDISC_SMS:
+ _gsm411_sms_trans_free(trans);
+ break;
+#endif
+ }
+
+ llist_del(&trans->entry);
+
+ talloc_free(trans);
+}
+
+/* allocate an unused transaction ID
+ * in the given protocol using the ti_flag specified */
+int trans_assign_trans_id(struct osmocom_ms *ms,
+ uint8_t protocol, uint8_t ti_flag)
+{
+ struct gsm_trans *trans;
+ unsigned int used_tid_bitmask = 0;
+ int i, j, h;
+
+ if (ti_flag)
+ ti_flag = 0x8;
+
+ /* generate bitmask of already-used TIDs for this (proto) */
+ llist_for_each_entry(trans, &ms->trans_list, entry) {
+ if (trans->protocol != protocol ||
+ trans->transaction_id == 0xff)
+ continue;
+ used_tid_bitmask |= (1 << trans->transaction_id);
+ }
+
+ /* find a new one, trying to go in a 'circular' pattern */
+ for (h = 6; h > 0; h--)
+ if (used_tid_bitmask & (1 << (h | ti_flag)))
+ break;
+ for (i = 0; i < 7; i++) {
+ j = ((h + i) % 7) | ti_flag;
+ if ((used_tid_bitmask & (1 << j)) == 0)
+ return j;
+ }
+
+ return -1;
+}
+