aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIvan Kluchnikov <kluchnikovi@gmail.com>2017-08-23 18:09:50 +0300
committerIvan Kluchnikov <kluchnikovi@gmail.com>2017-08-23 18:09:50 +0300
commit9507a7a1ea627e07370c9d264816bb190b3b91b8 (patch)
tree72c648684d99433f742e4c35c128e09a04ea9a71
parent8623c372ccd739b073c405bf475be3c3f0aab5e3 (diff)
handover: Implement proper handover procedure handling at any stage of the call
Enhancements for each stage of handover procedure should be implemented in order to support handover at any stage of the call. For these purposes new in_handover state and ho_queue for call control messages was introduced for gsm_subscriber_connection. Stage 1: HO-Command is sent to MS gsm_subscriber_connection state should be changed to in_handover=1. In this state all transmission of signalling layer messages (except RR messages needed for handover procedure) should be suspended until resuming is indicated. All call control messages for connection received from network side should be buffered in ho_queue. All call control messages for connection received from MS side should be ignored. Channel mode modification procedures should be also suspended. Stage 2: HO-Detect is received from MS Audio path should be switched on network side. Stage 3-1: HO-Complete is received from MS Resumption procedure after successful handover should be performed: - gsm_subscriber_connection state should be changed to normal (in_handover=0). - all buffered call control messages (ho_queue) should be sent to MS on new lchan. - suspended channel mode modification procedures should be performed on new lchan. Stage 3-2: HO-Fail is received from MS Resumption procedure after failed handover should be performed: - gsm_subscriber_connection state should be changed to normal (in_handover=0). - all buffered call control messages (ho_queue) should be sent to MS on old lchan. - suspended channel mode modification procedures should be performed on old lchan. Stage 3-3: T3103 expired: Handover has failed without HO-Complete or HO-Fail Resumption procedure should not be performed in case of T3103 expired: - gsm_subscriber_connection state should be changed to normal (in_handover=0). - all buffered call control messages (ho_queue) should be cleaned without sending them to MS. - suspended channel mode modification procedures should not be performed. Change-Id: Icb9b5c35ef0c894af2ea762e539f1a9216447fb7
-rw-r--r--openbsc/include/openbsc/bsc_api.h1
-rw-r--r--openbsc/include/openbsc/gsm_data.h2
-rw-r--r--openbsc/src/libbsc/bsc_api.c19
-rw-r--r--openbsc/src/libbsc/handover_logic.c35
-rw-r--r--openbsc/src/libmsc/gsm_04_08.c224
5 files changed, 199 insertions, 82 deletions
diff --git a/openbsc/include/openbsc/bsc_api.h b/openbsc/include/openbsc/bsc_api.h
index 3a9311991..baacbeda3 100644
--- a/openbsc/include/openbsc/bsc_api.h
+++ b/openbsc/include/openbsc/bsc_api.h
@@ -51,5 +51,6 @@ int gsm0808_cipher_mode(struct gsm_subscriber_connection *conn, int cipher,
int gsm0808_page(struct gsm_bts *bts, unsigned int page_group,
unsigned int mi_len, uint8_t *mi, int chan_type);
int gsm0808_clear(struct gsm_subscriber_connection *conn);
+int gsm0808_ho_clear(struct gsm_subscriber_connection *conn);
#endif
diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h
index 7418d727c..d27757c68 100644
--- a/openbsc/include/openbsc/gsm_data.h
+++ b/openbsc/include/openbsc/gsm_data.h
@@ -140,6 +140,8 @@ struct gsm_subscriber_connection {
struct gsm_network *network;
int in_release;
+ int in_handover;
+ struct llist_head ho_queue;
struct gsm_lchan *lchan; /* BSC */
struct gsm_lchan *ho_lchan; /* BSC */
struct gsm_bts *bts; /* BSC */
diff --git a/openbsc/src/libbsc/bsc_api.c b/openbsc/src/libbsc/bsc_api.c
index 8a4c85ff3..71e82d033 100644
--- a/openbsc/src/libbsc/bsc_api.c
+++ b/openbsc/src/libbsc/bsc_api.c
@@ -253,11 +253,14 @@ struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_lchan *lcha
conn->bts = lchan->ts->trx->bts;
lchan->conn = conn;
llist_add_tail(&conn->entry, &net->subscr_conns);
+ INIT_LLIST_HEAD(&conn->ho_queue);
return conn;
}
void bsc_subscr_con_free(struct gsm_subscriber_connection *conn)
{
+ struct msgb *msg;
+
if (!conn)
return;
@@ -283,6 +286,11 @@ void bsc_subscr_con_free(struct gsm_subscriber_connection *conn)
conn->secondary_lchan->conn = NULL;
}
+ while (!llist_empty(&conn->ho_queue)) {
+ msg = msgb_dequeue(&conn->ho_queue);
+ msgb_free(msg);
+ }
+
llist_del(&conn->entry);
talloc_free(conn);
}
@@ -747,6 +755,17 @@ int gsm0808_clear(struct gsm_subscriber_connection *conn)
return 0;
}
+/*
+ * Release handover RF Channel.
+ */
+int gsm0808_ho_clear(struct gsm_subscriber_connection *conn)
+{
+ if (conn->ho_lchan)
+ bsc_clear_handover(conn, 1);
+
+ return 0;
+}
+
static void send_sapi_reject(struct gsm_subscriber_connection *conn, int link_id)
{
struct bsc_api *api;
diff --git a/openbsc/src/libbsc/handover_logic.c b/openbsc/src/libbsc/handover_logic.c
index af4e8013f..b7085c349 100644
--- a/openbsc/src/libbsc/handover_logic.c
+++ b/openbsc/src/libbsc/handover_logic.c
@@ -186,10 +186,17 @@ static void ho_T3103_cb(void *_ho)
{
struct bsc_handover *ho = _ho;
struct gsm_network *net = ho->new_lchan->ts->trx->bts->network;
+ struct msgb *msg;
DEBUGP(DHO, "HO T3103 expired\n");
rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_TIMEOUT]);
+ ho->new_lchan->conn->in_handover = 0;
+ while (!llist_empty(&ho->new_lchan->conn->ho_queue)) {
+ msg = msgb_dequeue(&ho->new_lchan->conn->ho_queue);
+ msgb_free(msg);
+ }
+
ho->new_lchan->conn->ho_lchan = NULL;
ho->new_lchan->conn = NULL;
lchan_release(ho->new_lchan, 0, RSL_REL_LOCAL_END);
@@ -214,6 +221,8 @@ static int ho_chan_activ_ack(struct gsm_lchan *new_lchan)
gsm48_send_ho_cmd(ho->old_lchan, new_lchan, 0, ho->ho_ref);
+ new_lchan->conn->in_handover = 1;
+
/* start T3103. We can continue either with T3103 expiration,
* 04.08 HANDOVER COMPLETE or 04.08 HANDOVER FAIL */
ho->T3103.cb = ho_T3103_cb;
@@ -221,7 +230,8 @@ static int ho_chan_activ_ack(struct gsm_lchan *new_lchan)
osmo_timer_schedule(&ho->T3103, 10, 0);
/* create a RTP connection */
- if (is_ipaccess_bts(new_lchan->ts->trx->bts))
+ if (is_ipaccess_bts(new_lchan->ts->trx->bts) &&
+ new_lchan->tch_mode != GSM48_CMODE_SIGN)
rsl_ipacc_crcx(new_lchan);
return 0;
@@ -273,6 +283,11 @@ static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan)
if (is_e1_bts(new_lchan->conn->bts))
switch_trau_mux(ho->old_lchan, new_lchan);
+ if (ho->old_lchan->conn->mncc_rtp_connect_pending) {
+ new_lchan->abis_ip.connect_port = ho->old_lchan->abis_ip.connect_port;
+ new_lchan->abis_ip.connect_ip = ho->old_lchan->abis_ip.connect_ip;
+ }
+
/* Replace the ho lchan with the primary one */
if (ho->old_lchan != new_lchan->conn->lchan)
LOGP(DHO, LOGL_ERROR, "Primary lchan changed during handover.\n");
@@ -295,27 +310,9 @@ static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan)
static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan)
{
struct gsm_network *net = old_lchan->ts->trx->bts->network;
- struct bsc_handover *ho;
- struct gsm_lchan *new_lchan;
-
- ho = bsc_ho_by_old_lchan(old_lchan);
- if (!ho) {
- LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
- return -ENODEV;
- }
rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_FAILED]);
- new_lchan = ho->new_lchan;
-
- /* release the channel and forget about it */
- ho->new_lchan->conn->ho_lchan = NULL;
- ho->new_lchan->conn = NULL;
- handover_free(ho);
-
- lchan_release(new_lchan, 0, RSL_REL_LOCAL_END);
-
-
return 0;
}
diff --git a/openbsc/src/libmsc/gsm_04_08.c b/openbsc/src/libmsc/gsm_04_08.c
index 79d094170..8c2db5ac6 100644
--- a/openbsc/src/libmsc/gsm_04_08.c
+++ b/openbsc/src/libmsc/gsm_04_08.c
@@ -148,6 +148,15 @@ static int gsm48_conn_sendmsg(struct msgb *msg, struct gsm_subscriber_connection
sign_link->trx->bts->nr,
sign_link->trx->nr, msg->lchan->ts->nr,
gh->proto_discr, gh->msg_type);
+
+ if (conn->in_handover) {
+ msgb_enqueue(&conn->ho_queue, msg);
+ DEBUGP(DCC, "(bts %d trx %d ts %d) Suspend message sending to MS, "
+ "active HO procedure.\n",
+ sign_link->trx->bts->nr,
+ sign_link->trx->nr, msg->lchan->ts->nr);
+ return 0;
+ }
}
return gsm0808_submit_dtap(conn, msg, 0, 0);
@@ -1781,11 +1790,8 @@ static int switch_for_handover(struct gsm_lchan *old_lchan,
struct rtp_socket *old_rs, *new_rs, *other_rs;
/* Ask the new socket to send to the already known port. */
- if (new_lchan->conn->mncc_rtp_bridge) {
- LOGP(DHO, LOGL_DEBUG, "Forwarding RTP\n");
- rsl_ipacc_mdcx(new_lchan,
- old_lchan->abis_ip.connect_ip,
- old_lchan->abis_ip.connect_port, 0);
+ if (new_lchan->ts->trx->bts->network->mncc_state) {
+ /* Audio path should be switched after receiving ho detect message.*/
return 0;
}
@@ -1853,8 +1859,10 @@ static int handle_abisip_signal(unsigned int subsys, unsigned int signal,
if (subsys != SS_ABISIP)
return 0;
+ net = lchan->ts->trx->bts->network;
+
/* RTP bridge handling */
- if (lchan->conn && lchan->conn->mncc_rtp_bridge)
+ if (lchan->conn && net->mncc_state)
return tch_rtp_signal(lchan, signal);
/* in case we use direct BTS-to-BTS RTP */
@@ -1883,7 +1891,6 @@ static int handle_abisip_signal(unsigned int subsys, unsigned int signal,
/* check if any transactions on this lchan still have
* a tch_recv_mncc request pending */
- net = lchan->ts->trx->bts->network;
llist_for_each_entry(trans, &net->trans_list, entry) {
if (trans->conn && trans->conn->lchan == lchan && trans->tch_recv) {
DEBUGP(DCC, "pending tch_recv_mncc request\n");
@@ -2050,7 +2057,7 @@ static int tch_recv_mncc(struct gsm_network *net, uint32_t callref, int enable)
return -EINVAL;
}
/* RTP bridge handling */
- if (lchan->conn && lchan->conn->mncc_rtp_bridge) {
+ if (lchan->conn && net->mncc_state) {
return 0;
}
/* In case, we don't have a RTP socket to the BTS yet, the BTS
@@ -3362,6 +3369,41 @@ static void mncc_recv_rtp_err(struct gsm_network *net, uint32_t callref, int cmd
return mncc_recv_rtp(net, callref, cmd, 0, 0, 0, 0);
}
+static void mncc_recv_rtp_modify(struct gsm_lchan *lchan, uint32_t callref)
+{
+ int msg_type;
+ struct gsm_network *net = lchan->ts->trx->bts->network;
+
+ LOGP(DMNCC, LOGL_NOTICE, "%s sending pending RTP modify ind.\n",
+ gsm_lchan_name(lchan));
+
+ switch (lchan->abis_ip.rtp_payload) {
+ case RTP_PT_GSM_FULL:
+ msg_type = GSM_TCHF_FRAME;
+ break;
+ case RTP_PT_GSM_EFR:
+ msg_type = GSM_TCHF_FRAME_EFR;
+ break;
+ case RTP_PT_GSM_HALF:
+ msg_type = GSM_TCHH_FRAME;
+ break;
+ case RTP_PT_AMR:
+ msg_type = GSM_TCH_FRAME_AMR;
+ break;
+ default:
+ LOGP(DMNCC, LOGL_ERROR, "%s unknown payload type %d\n",
+ gsm_lchan_name(lchan), lchan->abis_ip.rtp_payload);
+ msg_type = 0;
+ break;
+ }
+
+ mncc_recv_rtp(net, callref, MNCC_RTP_MODIFY,
+ lchan->abis_ip.bound_ip,
+ lchan->abis_ip.bound_port,
+ lchan->abis_ip.rtp_payload,
+ msg_type);
+}
+
static int tch_rtp_create(struct gsm_network *net, uint32_t callref)
{
struct gsm_bts *bts;
@@ -3411,6 +3453,9 @@ static int tch_rtp_create(struct gsm_network *net, uint32_t callref)
LOGP(DMNCC, LOGL_DEBUG, "RTP create: codec=%s, chan_type=%s\n",
get_value_string(gsm48_chan_mode_names, m),
get_value_string(gsm_chan_t_names, lchan->type));
+ if (trans->conn->in_handover) {
+ return 0;
+ }
return gsm0808_assign_req(trans->conn, m,
lchan->type != GSM_LCHAN_TCH_H);
}
@@ -3457,25 +3502,21 @@ static int tch_rtp_connect(struct gsm_network *net, void *arg)
* same package!
*/
trans->conn->mncc_rtp_connect_pending = 1;
+ if (trans->conn->in_handover) {
+ lchan->abis_ip.connect_port = rtp->port;
+ lchan->abis_ip.connect_ip = rtp->ip;
+ return 0;
+ }
return rsl_ipacc_mdcx(lchan, rtp->ip, rtp->port, 0);
}
static int tch_rtp_signal(struct gsm_lchan *lchan, int signal)
{
struct gsm_network *net;
- struct gsm_trans *tmp, *trans = NULL;
+ struct gsm_trans *trans;
net = lchan->ts->trx->bts->network;
- llist_for_each_entry(tmp, &net->trans_list, entry) {
- if (!tmp->conn)
- continue;
- if (tmp->conn->lchan != lchan && tmp->conn->ho_lchan != lchan)
- continue;
- if (!tmp->tch_recv)
- continue;
- trans = tmp;
- break;
- }
+ trans = trans_find_by_lchan(lchan);
if (!trans) {
LOGP(DMNCC, LOGL_ERROR, "%s IPA abis signal but no transaction.\n",
@@ -3498,7 +3539,7 @@ static int tch_rtp_signal(struct gsm_lchan *lchan, int signal)
maybe_switch_for_handover(lchan);
break;
case S_ABISIP_MDCX_ACK:
- if (lchan->conn->mncc_rtp_connect_pending) {
+ if (lchan->conn->mncc_rtp_connect_pending && !lchan->conn->in_handover) {
lchan->conn->mncc_rtp_connect_pending = 0;
LOGP(DMNCC, LOGL_NOTICE, "%s sending pending RTP connect ind.\n",
gsm_lchan_name(lchan));
@@ -3510,58 +3551,105 @@ static int tch_rtp_signal(struct gsm_lchan *lchan, int signal)
return 0;
}
-static int ho_detect(struct gsm_lchan *lchan)
+static void ho_queue_clean(struct gsm_subscriber_connection *conn)
{
- struct gsm_network *net;
- struct gsm_trans *tmp, *trans = NULL;
+ struct msgb *msg;
+ while (!llist_empty(&conn->ho_queue)) {
+ msg = msgb_dequeue(&conn->ho_queue);
+ msgb_free(msg);
+ }
+}
- net = lchan->ts->trx->bts->network;
- llist_for_each_entry(tmp, &net->trans_list, entry) {
- if (!tmp->conn)
- continue;
- if (tmp->conn->lchan != lchan && tmp->conn->ho_lchan != lchan)
- continue;
- if (!tmp->tch_recv)
- continue;
- trans = tmp;
- break;
+static void ho_resumption(struct gsm_lchan *lchan, struct gsm_trans *trans)
+{
+ struct msgb *msg;
+ enum gsm48_chan_mode m;
+
+ while (!llist_empty(&lchan->conn->ho_queue)) {
+ msg = msgb_dequeue(&lchan->conn->ho_queue);
+ gsm48_conn_sendmsg(msg, lchan->conn, trans);
}
+ if (trans->conn->mncc_rtp_create_pending &&
+ lchan->tch_mode == GSM48_CMODE_SIGN) {
+ m = mncc_codec_for_mode(lchan->type);
+ gsm0808_assign_req(lchan->conn, m, lchan->type != GSM_LCHAN_TCH_H);
+ }
+
+ if (trans->conn->mncc_rtp_connect_pending) {
+ rsl_ipacc_mdcx(lchan, lchan->abis_ip.connect_ip, lchan->abis_ip.connect_port, 0);
+ }
+}
+
+static int ho_complete(struct gsm_lchan *new_lchan)
+{
+ struct gsm_trans *trans;
+
+ new_lchan->conn->in_handover = 0;
+ trans = trans_find_by_lchan(new_lchan);
if (!trans) {
- LOGP(DMNCC, LOGL_ERROR, "%s lchan signal but no transaction.\n",
- gsm_lchan_name(lchan));
+ LOGP(DHO, LOGL_ERROR, "%s HO detected, but no transaction for new_lchan.\n",
+ gsm_lchan_name(new_lchan));
+ ho_queue_clean(new_lchan->conn);
return 0;
}
- LOGP(DMNCC, LOGL_NOTICE, "%s sending pending RTP modify ind.\n",
- gsm_lchan_name(lchan));
+ ho_resumption(new_lchan, trans);
+ return 0;
+}
- int msg_type;
- switch (lchan->abis_ip.rtp_payload) {
- case RTP_PT_GSM_FULL:
- msg_type = GSM_TCHF_FRAME;
- break;
- case RTP_PT_GSM_EFR:
- msg_type = GSM_TCHF_FRAME_EFR;
- break;
- case RTP_PT_GSM_HALF:
- msg_type = GSM_TCHH_FRAME;
- break;
- case RTP_PT_AMR:
- msg_type = GSM_TCH_FRAME_AMR;
- break;
- default:
- LOGP(DMNCC, LOGL_ERROR, "%s unknown payload type %d\n",
- gsm_lchan_name(lchan), lchan->abis_ip.rtp_payload);
- msg_type = 0;
- break;
+static int ho_fail(struct gsm_lchan *old_lchan)
+{
+ struct gsm_trans *trans;
+
+ old_lchan->conn->in_handover = 0;
+ trans = trans_find_by_lchan(old_lchan);
+ if (trans)
+ ho_resumption(old_lchan, trans);
+ else {
+ LOGP(DHO, LOGL_ERROR, "%s HO fail, but no transaction for old_lchan.\n",
+ gsm_lchan_name(old_lchan));
+ ho_queue_clean(old_lchan->conn);
}
- mncc_recv_rtp(net, trans->callref, MNCC_RTP_MODIFY,
- lchan->abis_ip.bound_ip,
- lchan->abis_ip.bound_port,
- lchan->abis_ip.rtp_payload,
- msg_type);
+ gsm0808_ho_clear(old_lchan->conn);
+ return 0;
+}
+
+static int ho_detect(struct gsm_lchan *new_lchan)
+{
+ struct gsm_trans *trans;
+ struct gsm_lchan *old_lchan;
+
+ trans = trans_find_by_lchan(new_lchan);
+
+ if (!trans) {
+ LOGP(DHO, LOGL_ERROR, "%s HO detected, but no transaction for new_lchan"
+ " with enabled tch_recv.\n",
+ gsm_lchan_name(new_lchan));
+ return 0;
+ }
+
+ if (!new_lchan->conn->mncc_rtp_bridge) {
+ LOGP(DHO, LOGL_ERROR, "%s HO detected, but connection not in mncc_rtp_bridge mode.\n",
+ gsm_lchan_name(new_lchan));
+ return 0;
+ }
+
+ old_lchan = bsc_handover_pending(new_lchan);
+ if (!old_lchan) {
+ LOGP(DHO, LOGL_ERROR, "%s HO detected, but no old_lchan for handover.\n",
+ gsm_lchan_name(new_lchan));
+ return 0;
+ }
+
+ LOGP(DHO, LOGL_DEBUG, "HO detected, forwarding RTP\n");
+ rsl_ipacc_mdcx(new_lchan,
+ old_lchan->abis_ip.connect_ip,
+ old_lchan->abis_ip.connect_port, 0);
+
+ mncc_recv_rtp_modify(new_lchan, trans->callref);
+
return 0;
}
@@ -3576,10 +3664,15 @@ static int handle_lchan_signal(unsigned int subsys, unsigned int signal,
switch (subsys) {
case SS_LCHAN:
lchan = lchan_data->lchan;
+ if (!lchan->conn)
+ return 0;
switch (signal) {
case S_LCHAN_HANDOVER_DETECT:
- if (lchan->conn && lchan->conn->mncc_rtp_bridge)
- return ho_detect(lchan);
+ return ho_detect(lchan);
+ case S_LCHAN_HANDOVER_COMPL:
+ return ho_complete(lchan);
+ case S_LCHAN_HANDOVER_FAIL:
+ return ho_fail(lchan);
}
break;
}
@@ -3968,6 +4061,11 @@ static int gsm0408_rcv_cc(struct gsm_subscriber_connection *conn, struct msgb *m
gsm48_cc_msg_name(msg_type), trans?(trans->cc.state):0,
gsm48_cc_state_name(trans?(trans->cc.state):0));
+ if (conn->in_handover) {
+ DEBUGP(DCC, "Message unhandled, handover procedure.\n");
+ return 0;
+ }
+
/* Create transaction */
if (!trans) {
DEBUGP(DCC, "Unknown transaction ID %x, "