aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2016-07-15 01:03:32 +0200
committerNeels Hofmeyr <neels@hofmeyr.de>2016-07-25 22:13:44 +0200
commit21ffe07894ae66346b3073eb0a170a977805b8e7 (patch)
treeffb30abe4ff1ebc8d7fd7734e1739a41c4750f47
parent6e656748b7fa8b7e00d610eaaf4de84113b67a0b (diff)
dyn TS: common TCH/F_TCH/H_PDCH implementation
common/l1sap: For dyn TS, the BSC will issue RSL Chan Activ requests with a non-standard chan_nr. While the rest of the code now understands that, the L1 phy will not. Translate to standard PDCH (== TCH/F). common/oml: use dyn TS' current pchan mode for lchans config. common/pcu_sock: detect desired PDCH mode of dyn TS. common/rsl: implement reconnection chain of a TS for changing its pchan: * rsl_rx_chan_activ(): ** Add dyn_pchan_from_chan_nr() to derive the requested pchan from the RSL chan_nr IE. ** Notice the need for a pchan change and invoke dyn_ts_l1_reconnect() (s.b.) ** Make Chan Mode IE presence optional, because the non-standard PDCH activation message is simpler and does not require it. ** Do PDCH activation via PCU. * Add dyn_ts_l1_reconnect(): store state and disconnect the L1 channel; then wait for cb_ts_disconnected(). * Add osmo_dyn_ts_disconnected() to cb_ts_disconnected(): verify state and connect with the new pchan type; then wait for cb_ts_connected(). * Add osmo_dyn_ts_connected() to cb_ts_connected(), which re-issues the cached chan activation message from before disconnecting the L1 channel. * Also send an rf chan rel/act ack for dyn TS upon PDCH de/act via PCU. * Add dyn_ts_pdch_release(): on channel release of a dyn TS in PDCH mode, release via the PCU. Call from rsl_rx_rd_chan_rel(). Change-Id: I463bb6b4e57674f091c3badba9257374961c52c7
-rw-r--r--src/common/l1sap.c5
-rw-r--r--src/common/oml.c9
-rw-r--r--src/common/pcu_sock.c8
-rw-r--r--src/common/rsl.c225
4 files changed, 235 insertions, 12 deletions
diff --git a/src/common/l1sap.c b/src/common/l1sap.c
index 23bc66e..0bbdb9e 100644
--- a/src/common/l1sap.c
+++ b/src/common/l1sap.c
@@ -1078,6 +1078,11 @@ static int l1sap_chan_act_dact_modify(struct gsm_bts_trx *trx, uint8_t chan_nr,
{
struct osmo_phsap_prim l1sap;
+ /* The caller may pass a non-standard RSL_CHAN_OSMO_PDCH, which the L1
+ * doesn't understand. Use the normal TCH/F cbits instead. */
+ if ((chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_OSMO_PDCH)
+ chan_nr = RSL_CHAN_Bm_ACCHs | (chan_nr & ~RSL_CHAN_NR_MASK);
+
memset(&l1sap, 0, sizeof(l1sap));
osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_REQUEST,
NULL);
diff --git a/src/common/oml.c b/src/common/oml.c
index 8970858..690a81d 100644
--- a/src/common/oml.c
+++ b/src/common/oml.c
@@ -694,6 +694,15 @@ static int conf_lchans(struct gsm_bts_trx_ts *ts)
pchan = ts->flags & TS_F_PDCH_ACTIVE? GSM_PCHAN_PDCH
: GSM_PCHAN_TCH_F;
+ /* Osmocom RSL CHAN ACT style dyn TS */
+ if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ pchan = ts->dyn.pchan_is;
+
+ /* If the dyn TS doesn't have a pchan yet, do nothing. */
+ if (pchan == GSM_PCHAN_NONE)
+ return 0;
+ }
+
return conf_lchans_as_pchan(ts, pchan);
}
diff --git a/src/common/pcu_sock.c b/src/common/pcu_sock.c
index 845e5bb..d40b19b 100644
--- a/src/common/pcu_sock.c
+++ b/src/common/pcu_sock.c
@@ -110,6 +110,14 @@ static bool ts_should_be_pdch(struct gsm_bts_trx_ts *ts) {
else
return (ts->flags & TS_F_PDCH_ACT_PENDING);
}
+ if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ /*
+ * When we're busy de-/activating the PDCH, we first set
+ * ts->dyn.pchan_want, tell the PCU about it and wait for a
+ * response. So only care about dyn.pchan_want here.
+ */
+ return ts->dyn.pchan_want == GSM_PCHAN_PDCH;
+ }
return false;
}
diff --git a/src/common/rsl.c b/src/common/rsl.c
index 69ed108..490ae28 100644
--- a/src/common/rsl.c
+++ b/src/common/rsl.c
@@ -502,7 +502,15 @@ int rsl_tx_rf_rel_ack(struct gsm_lchan *lchan)
struct msgb *msg;
uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
- if (lchan->rel_act_kind != LCHAN_REL_ACT_RSL) {
+ /*
+ * Normally, PDCH deactivation via PCU does not ack back to the BSC.
+ * But for GSM_PCHAN_TCH_F_TCH_H_PDCH, send a non-standard rel ack for
+ * LCHAN_REL_ACT_PCU, since the rel req came from RSL initially.
+ */
+ if (lchan->rel_act_kind != LCHAN_REL_ACT_RSL
+ && !(lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+ && lchan->ts->dyn.pchan_is == GSM_PCHAN_PDCH
+ && lchan->rel_act_kind == LCHAN_REL_ACT_PCU)) {
LOGP(DRSL, LOGL_NOTICE, "%s not sending REL ACK\n",
gsm_lchan_name(lchan));
return 0;
@@ -534,7 +542,15 @@ int rsl_tx_chan_act_ack(struct gsm_lchan *lchan)
uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
uint8_t ie[2];
- if (lchan->rel_act_kind != LCHAN_REL_ACT_RSL) {
+ /*
+ * Normally, PDCH activation via PCU does not ack back to the BSC.
+ * But for GSM_PCHAN_TCH_F_TCH_H_PDCH, send a non-standard act ack for
+ * LCHAN_REL_ACT_PCU, since the act req came from RSL initially.
+ */
+ if (lchan->rel_act_kind != LCHAN_REL_ACT_RSL
+ && !(lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+ && lchan->ts->dyn.pchan_is == GSM_PCHAN_PDCH
+ && lchan->rel_act_kind == LCHAN_REL_ACT_PCU)) {
LOGP(DRSL, LOGL_NOTICE, "%s not sending CHAN ACT ACK\n",
gsm_lchan_name(lchan));
return 0;
@@ -703,11 +719,60 @@ static int encr_info2lchan(struct gsm_lchan *lchan,
return 0;
}
+/*!
+ * Store the CHAN_ACTIV msg, connect the L1 timeslot in the proper type and
+ * then invoke rsl_rx_chan_activ() with msg.
+ */
+static int dyn_ts_l1_reconnect(struct gsm_bts_trx_ts *ts, struct msgb *msg)
+{
+ DEBUGP(DRSL, "%s dyn_ts_l1_reconnect\n", gsm_ts_and_pchan_name(ts));
+
+ switch (ts->dyn.pchan_want) {
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_TCH_H:
+ case GSM_PCHAN_PDCH:
+ break;
+ default:
+ LOGP(DRSL, LOGL_ERROR,
+ "%s Cannot reconnect as pchan %s\n",
+ gsm_ts_and_pchan_name(ts),
+ gsm_pchan_name(ts->dyn.pchan_want));
+ return -EINVAL;
+ }
+
+ /* We will feed this back to rsl_rx_chan_activ() later */
+ ts->dyn.pending_chan_activ = msg;
+
+ /* Disconnect, continue connecting from cb_ts_disconnected(). */
+ DEBUGP(DRSL, "%s Disconnect\n", gsm_ts_and_pchan_name(ts));
+ return bts_model_ts_disconnect(ts);
+}
+
+static enum gsm_phys_chan_config dyn_pchan_from_chan_nr(uint8_t chan_nr)
+{
+ uint8_t cbits = chan_nr & RSL_CHAN_NR_MASK;
+ switch (cbits) {
+ case RSL_CHAN_Bm_ACCHs:
+ return GSM_PCHAN_TCH_F;
+ case RSL_CHAN_Lm_ACCHs:
+ case (RSL_CHAN_Lm_ACCHs + RSL_CHAN_NR_1):
+ return GSM_PCHAN_TCH_H;
+ case RSL_CHAN_OSMO_PDCH:
+ return GSM_PCHAN_PDCH;
+ default:
+ LOGP(DRSL, LOGL_ERROR,
+ "chan nr 0x%x not covered by dyn_pchan_from_chan_nr()\n",
+ chan_nr);
+ return GSM_PCHAN_UNKNOWN;
+ }
+}
+
/* 8.4.1 CHANnel ACTIVation is received */
static int rsl_rx_chan_activ(struct msgb *msg)
{
struct abis_rsl_dchan_hdr *dch = msgb_l2(msg);
struct gsm_lchan *lchan = msg->lchan;
+ struct gsm_bts_trx_ts *ts = lchan->ts;
struct rsl_ie_chan_mode *cm;
struct tlv_parsed tp;
uint8_t type;
@@ -720,6 +785,25 @@ static int rsl_rx_chan_activ(struct msgb *msg)
return rsl_tx_chan_act_nack(lchan, RSL_ERR_EQUIPMENT_FAIL);
}
+ if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ ts->dyn.pchan_want = dyn_pchan_from_chan_nr(dch->chan_nr);
+ DEBUGP(DRSL, "%s rx chan activ\n", gsm_ts_and_pchan_name(ts));
+
+ if (ts->dyn.pchan_is != ts->dyn.pchan_want) {
+ /*
+ * The phy has the timeslot connected in a different
+ * mode than this activation needs it to be.
+ * Re-connect, then come back to rsl_rx_chan_activ().
+ */
+ rc = dyn_ts_l1_reconnect(ts, msg);
+ if (rc)
+ return rsl_tx_chan_act_nack(lchan,
+ RSL_ERR_NORMAL_UNSPEC);
+ /* indicate that the msgb should not be freed. */
+ return 1;
+ }
+ }
+
/* Initialize channel defaults */
lchan->ms_power = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, 0);
lchan->ms_power_ctrl.current = lchan->ms_power;
@@ -735,12 +819,14 @@ static int rsl_rx_chan_activ(struct msgb *msg)
type = *TLVP_VAL(&tp, RSL_IE_ACT_TYPE);
/* 9.3.6 Channel Mode */
- if (!TLVP_PRESENT(&tp, RSL_IE_CHAN_MODE)) {
- LOGP(DRSL, LOGL_NOTICE, "missing Channel Mode\n");
- return rsl_tx_chan_act_nack(lchan, RSL_ERR_MAND_IE_ERROR);
+ if (type != RSL_ACT_OSMO_PDCH) {
+ if (!TLVP_PRESENT(&tp, RSL_IE_CHAN_MODE)) {
+ LOGP(DRSL, LOGL_NOTICE, "missing Channel Mode\n");
+ return rsl_tx_chan_act_nack(lchan, RSL_ERR_MAND_IE_ERROR);
+ }
+ cm = (struct rsl_ie_chan_mode *) TLVP_VAL(&tp, RSL_IE_CHAN_MODE);
+ lchan_tchmode_from_cmode(lchan, cm);
}
- cm = (struct rsl_ie_chan_mode *) TLVP_VAL(&tp, RSL_IE_CHAN_MODE);
- lchan_tchmode_from_cmode(lchan, cm);
/* 9.3.7 Encryption Information */
if (TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO)) {
@@ -839,8 +925,33 @@ static int rsl_rx_chan_activ(struct msgb *msg)
LOGP(DRSL, LOGL_INFO, " chan_nr=0x%02x type=0x%02x mode=0x%02x\n",
dch->chan_nr, type, lchan->tch_mode);
- /* actually activate the channel in the BTS */
+ /* Connecting PDCH on dyn TS goes via PCU instead. */
+ if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+ && ts->dyn.pchan_want == GSM_PCHAN_PDCH) {
+ /*
+ * pcu_tx_info_ind() will pick up the ts->dyn.pchan_want. If
+ * the PCU is not connected yet, ignore for now; the PCU will
+ * catch up (and send the RSL ack) once it connects.
+ */
+ if (pcu_connected()) {
+ DEBUGP(DRSL, "%s Activate via PCU\n", gsm_ts_and_pchan_name(ts));
+ rc = pcu_tx_info_ind();
+ }
+ else {
+ DEBUGP(DRSL, "%s Activate via PCU when PCU connects\n",
+ gsm_ts_and_pchan_name(ts));
+ rc = 0;
+ }
+ if (rc)
+ return rsl_tx_error_report(msg->trx,
+ RSL_ERR_NORMAL_UNSPEC);
+ return 0;
+ }
+
+ /* Remember to send an RSL ACK once the lchan is active */
lchan->rel_act_kind = LCHAN_REL_ACT_RSL;
+
+ /* actually activate the channel in the BTS */
rc = l1sap_chan_act(lchan->ts->trx, dch->chan_nr, &tp);
if (rc < 0)
return rsl_tx_chan_act_nack(lchan, -rc);
@@ -848,6 +959,32 @@ static int rsl_rx_chan_activ(struct msgb *msg)
return 0;
}
+static int dyn_ts_pdch_release(struct gsm_lchan *lchan)
+{
+ struct gsm_bts_trx_ts *ts = lchan->ts;
+
+ if (ts->dyn.pchan_is != ts->dyn.pchan_want) {
+ LOGP(DRSL, LOGL_ERROR, "%s: PDCH release requested but already"
+ " in switchover\n", gsm_ts_and_pchan_name(ts));
+ return -EINVAL;
+ }
+
+ /*
+ * Indicate PDCH Disconnect in dyn_pdch.want, let pcu_tx_info_ind()
+ * pick it up and wait for PCU to disable the channel.
+ */
+ ts->dyn.pchan_want = GSM_PCHAN_NONE;
+
+ if (!pcu_connected()) {
+ /* PCU not connected yet. Just record the new type and done,
+ * the PCU will pick it up once connected. */
+ ts->dyn.pchan_is = GSM_PCHAN_NONE;
+ return 0;
+ }
+
+ return pcu_tx_info_ind();
+}
+
/* 8.4.14 RF CHANnel RELease is received */
static int rsl_rx_rf_chan_rel(struct gsm_lchan *lchan, uint8_t chan_nr)
{
@@ -862,6 +999,12 @@ static int rsl_rx_rf_chan_rel(struct gsm_lchan *lchan, uint8_t chan_nr)
handover_reset(lchan);
lchan->rel_act_kind = LCHAN_REL_ACT_RSL;
+
+ /* Dynamic channel in PDCH mode is released via PCU */
+ if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+ && lchan->ts->dyn.pchan_is == GSM_PCHAN_PDCH)
+ return dyn_ts_pdch_release(lchan);
+
l1sap_chan_rel(lchan->ts->trx, chan_nr);
lapdm_channel_exit(&lchan->lapdm_ch);
@@ -1766,10 +1909,40 @@ error_nack:
ipacc_dyn_pdch_complete(ts, rc);
}
+static void osmo_dyn_ts_disconnected(struct gsm_bts_trx_ts *ts)
+{
+ DEBUGP(DRSL, "%s Disconnected\n", gsm_ts_and_pchan_name(ts));
+ ts->dyn.pchan_is = GSM_PCHAN_NONE;
+
+ switch (ts->dyn.pchan_want) {
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_TCH_H:
+ case GSM_PCHAN_PDCH:
+ break;
+ default:
+ LOGP(DRSL, LOGL_ERROR,
+ "%s Dyn TS disconnected, but invalid desired pchan",
+ gsm_ts_and_pchan_name(ts));
+ ts->dyn.pchan_want = GSM_PCHAN_NONE;
+ /* TODO: how would this recover? */
+ return;
+ }
+
+ conf_lchans_as_pchan(ts, ts->dyn.pchan_want);
+ DEBUGP(DRSL, "%s Connect\n", gsm_ts_and_pchan_name(ts));
+ bts_model_ts_connect(ts, ts->dyn.pchan_want);
+}
+
void cb_ts_disconnected(struct gsm_bts_trx_ts *ts)
{
- if (ts->pchan == GSM_PCHAN_TCH_F_PDCH)
- ipacc_dyn_pdch_ts_disconnected(ts);
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_PDCH:
+ return ipacc_dyn_pdch_ts_disconnected(ts);
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ return osmo_dyn_ts_disconnected(ts);
+ default:
+ return;
+ }
}
static void ipacc_dyn_pdch_ts_connected(struct gsm_bts_trx_ts *ts)
@@ -1823,10 +1996,38 @@ static void ipacc_dyn_pdch_ts_connected(struct gsm_bts_trx_ts *ts)
}
}
+static void osmo_dyn_ts_connected(struct gsm_bts_trx_ts *ts)
+{
+ int rc;
+ struct msgb *msg = ts->dyn.pending_chan_activ;
+ ts->dyn.pending_chan_activ = NULL;
+
+ if (!msg) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s TS re-connected, but no chan activ msg pending\n",
+ gsm_ts_and_pchan_name(ts));
+ return;
+ }
+
+ ts->dyn.pchan_is = ts->dyn.pchan_want;
+ DEBUGP(DRSL, "%s Connected\n", gsm_ts_and_pchan_name(ts));
+
+ /* continue where we left off before re-connecting the TS. */
+ rc = rsl_rx_chan_activ(msg);
+ if (rc != 1)
+ msgb_free(msg);
+}
+
void cb_ts_connected(struct gsm_bts_trx_ts *ts)
{
- if (ts->pchan == GSM_PCHAN_TCH_F_PDCH)
- ipacc_dyn_pdch_ts_connected(ts);
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_PDCH:
+ return ipacc_dyn_pdch_ts_connected(ts);
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ return osmo_dyn_ts_connected(ts);
+ default:
+ return;
+ }
}
void ipacc_dyn_pdch_complete(struct gsm_bts_trx_ts *ts, int rc)