aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIvan Kluchnikov <kluchnikovi@gmail.com>2017-08-23 18:09:50 +0300
committerNeels Hofmeyr <neels@hofmeyr.de>2018-01-12 00:55:33 +0100
commitf7f4dc5e3b0dd61b8322946597147baef5d0464b (patch)
treef5346708f3d275c025de1d9f45eb757e0167db93
parentf44fefe0ff3452f43a627e33e127d34a490d0d6d (diff)
handover: Implement proper handover procedure handling at any stage of the callneels/fairwaves_ho_orig
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.c217
5 files changed, 238 insertions, 36 deletions
diff --git a/openbsc/include/openbsc/bsc_api.h b/openbsc/include/openbsc/bsc_api.h
index 3a93119..baacbed 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 ac573c4..542b261 100644
--- a/openbsc/include/openbsc/gsm_data.h
+++ b/openbsc/include/openbsc/gsm_data.h
@@ -138,6 +138,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 8a4c85f..71e82d0 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 af4e801..b7085c3 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 e5402d0..84338d7 100644
--- a/openbsc/src/libmsc/gsm_04_08.c
+++ b/openbsc/src/libmsc/gsm_04_08.c
@@ -147,6 +147,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);
@@ -1749,11 +1758,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;
}
@@ -1821,8 +1827,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 */
@@ -1851,7 +1859,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");
@@ -2017,6 +2024,13 @@ static int tch_recv_mncc(struct gsm_network *net, uint32_t callref, int enable)
LOGP(DCC, LOGL_ERROR, "Error: RTP proxy is disabled\n");
return -EINVAL;
}
+ /* RTP bridge handling */
+ /* TODO: this is from fairwaves/master-rebase branch. It's not entirely clear which case
+ * this is catching and why. Find out and explain. */
+ if (lchan->conn && net->mncc_state) {
+ LOGP(DCC, LOGL_NOTICE, "XXXXXXXXXX tch_recv_mncc(): if (lchan->conn && net->mncc_state) return 0\n");
+ return 0;
+ }
/* In case, we don't have a RTP socket to the BTS yet, the BTS
* will not be connected to our RTP proxy and the socket will
* not be assigned to the application interface. This method
@@ -3325,6 +3339,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;
@@ -3374,6 +3423,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);
}
@@ -3420,23 +3472,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;
- trans = tmp;
- break;
- }
+ trans = trans_find_by_lchan(lchan);
if (!trans) {
LOGP(DMNCC, LOGL_ERROR, "%s IPA abis signal but no transaction.\n",
@@ -3459,7 +3509,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));
@@ -3471,6 +3521,134 @@ static int tch_rtp_signal(struct gsm_lchan *lchan, int signal)
return 0;
}
+static void ho_queue_clean(struct gsm_subscriber_connection *conn)
+{
+ struct msgb *msg;
+ while (!llist_empty(&conn->ho_queue)) {
+ msg = msgb_dequeue(&conn->ho_queue);
+ msgb_free(msg);
+ }
+}
+
+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(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;
+ }
+
+ ho_resumption(new_lchan, trans);
+ return 0;
+}
+
+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);
+ }
+
+ 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;
+}
+
+/* some other part of the code sends us a signal */
+static int handle_lchan_signal(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct lchan_signal_data *lchan_data;
+ struct gsm_lchan *lchan;
+
+ lchan_data = signal_data;
+ switch (subsys) {
+ case SS_LCHAN:
+ lchan = lchan_data->lchan;
+ if (!lchan->conn)
+ return 0;
+ switch (signal) {
+ case S_LCHAN_HANDOVER_DETECT:
+ return ho_detect(lchan);
+ case S_LCHAN_HANDOVER_COMPL:
+ return ho_complete(lchan);
+ case S_LCHAN_HANDOVER_FAIL:
+ return ho_fail(lchan);
+ }
+ break;
+ }
+
+ return 0;
+}
static struct downstate {
uint32_t states;
@@ -3853,6 +4031,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, "