aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Welte <laforge@gnumonks.org>2011-06-29 10:36:07 +0200
committerHarald Welte <laforge@gnumonks.org>2011-06-29 10:36:07 +0200
commit6818881d72cdde04eca4b1923a805190acde493c (patch)
tree02a808116ca27e88637e8880a02261b69138fbe3
parentc5e01c8bd463e42ea3bbe4142941f43c548f14c7 (diff)
implement baseic uplink measurement processing + reporting
* gather measurements from each PH-DATA.ind * check every TDMA frame about meas period expiration * compute averages after period expired * put MS DL MEAS REP into RSL MEAS RES messages, include UL meas bugs: * L3 INFO content seems to have some offset * is_sub is not set anywhere * measurement periods might have up/downlink offset
-rw-r--r--include/osmo-bts/Makefile.am3
-rw-r--r--include/osmo-bts/measurement.h13
-rw-r--r--src/common/Makefile.am3
-rw-r--r--src/common/logging.c4
-rw-r--r--src/common/measurement.c235
-rw-r--r--src/common/rsl.c101
-rw-r--r--src/osmo-bts-sysmo/l1_if.c35
7 files changed, 377 insertions, 17 deletions
diff --git a/include/osmo-bts/Makefile.am b/include/osmo-bts/Makefile.am
index 64711f8..4f8753d 100644
--- a/include/osmo-bts/Makefile.am
+++ b/include/osmo-bts/Makefile.am
@@ -1 +1,2 @@
-noinst_HEADERS = abis.h bts.h bts_model.h gsm_data.h logging.h oml.h paging.h rsl.h rtp.h signal.h vty.h
+noinst_HEADERS = abis.h bts.h bts_model.h gsm_data.h logging.h measurement.h \
+ oml.h paging.h rsl.h rtp.h signal.h vty.h
diff --git a/include/osmo-bts/measurement.h b/include/osmo-bts/measurement.h
new file mode 100644
index 0000000..5ad6795
--- /dev/null
+++ b/include/osmo-bts/measurement.h
@@ -0,0 +1,13 @@
+#ifndef OSMO_BTS_MEAS_H
+#define OSMO_BTS_MEAS_H
+
+int lchan_new_ul_meas(struct gsm_lchan *lchan, struct bts_ul_meas *ulm);
+
+int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn);
+int ts_meas_check_compute(struct gsm_bts_trx_ts *ts, uint32_t fn);
+int trx_meas_check_compute(struct gsm_bts_trx *trx, uint32_t fn);
+
+/* build the 3 byte RSL uplinke measurement IE content */
+int lchan_build_rsl_ul_meas(struct gsm_lchan *, uint8_t *buf);
+
+#endif
diff --git a/src/common/Makefile.am b/src/common/Makefile.am
index 70b9495..c8530c4 100644
--- a/src/common/Makefile.am
+++ b/src/common/Makefile.am
@@ -3,5 +3,6 @@ AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS)
LDADD = $(LIBOSMOCORE_LIBS)
noinst_LIBRARIES = libbts.a
-libbts_a_SOURCES = gsm_data_shared.c sysinfo.c logging.c abis.c oml.c bts.c rsl.c vty.c paging.c
+libbts_a_SOURCES = gsm_data_shared.c sysinfo.c logging.c abis.c oml.c bts.c \
+ rsl.c vty.c paging.c measurement.c
#support.c rtp.c
diff --git a/src/common/logging.c b/src/common/logging.c
index 362e30c..a7df228 100644
--- a/src/common/logging.c
+++ b/src/common/logging.c
@@ -58,7 +58,7 @@ static const struct log_info_cat bts_log_info_cat[] = {
[DMEAS] = {
.name = "DMEAS",
.description = "Radio Measurement Processing",
- .enabled = 0, .loglevel = LOGL_NOTICE,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DPAG] = {
.name = "DPAG",
@@ -107,7 +107,7 @@ const struct log_info bts_log_info = {
.num_cat = ARRAY_SIZE(bts_log_info_cat),
};
-#define DEFAULT_MASK "DL1C:DOML:DRSL:DPAG"
+#define DEFAULT_MASK "DL1C:DOML:DRSL:DPAG:DMEAS"
int bts_log_init(const char *category_mask)
{
diff --git a/src/common/measurement.c b/src/common/measurement.c
new file mode 100644
index 0000000..c20db46
--- /dev/null
+++ b/src/common/measurement.c
@@ -0,0 +1,235 @@
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/measurement.h>
+
+/* TS 05.08, Chapter 8.4.1 */
+/* measurement period ends at fn % 104 == ? */
+static const uint8_t tchf_meas_rep_fn104[] = {
+ [0] = 103,
+ [1] = 12,
+ [2] = 25,
+ [3] = 38,
+ [4] = 51,
+ [5] = 64,
+ [6] = 77,
+ [7] = 90,
+};
+static const uint8_t tchh0_meas_rep_fn104[] = {
+ [0] = 103,
+ [1] = 103,
+ [2] = 25,
+ [3] = 25,
+ [4] = 51,
+ [5] = 51,
+ [6] = 77,
+ [7] = 77,
+};
+static const uint8_t tchh1_meas_rep_fn104[] = {
+ [0] = 12,
+ [1] = 12,
+ [2] = 38,
+ [3] = 38,
+ [4] = 64,
+ [5] = 64,
+ [6] = 90,
+ [7] = 90,
+};
+
+/* determine if a measurement period ends at the given frame number */
+static int is_meas_complete(enum gsm_phys_chan_config pchan, unsigned int ts,
+ unsigned int subch, uint32_t fn)
+{
+ unsigned int fn_mod;
+ const uint8_t *tbl;
+ int rc = 0;
+
+ if (ts >= 8)
+ return -EINVAL;
+ if (pchan >= _GSM_PCHAN_MAX)
+ return -EINVAL;
+
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ fn_mod = fn % 104;
+ if (tchf_meas_rep_fn104[ts] == fn_mod)
+ rc = 1;
+ break;
+ case GSM_PCHAN_TCH_H:
+ fn_mod = fn % 104;
+ if (subch == 0)
+ tbl = tchh0_meas_rep_fn104;
+ else
+ tbl = tchh1_meas_rep_fn104;
+ if (tbl[ts] == fn_mod)
+ rc = 1;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ fn_mod = fn % 102;
+ if (fn_mod == 11)
+ rc = 1;
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ fn_mod = fn % 102;
+ if (fn_mod == 36)
+ rc = 1;
+ break;
+ default:
+ rc = 0;
+ break;
+ }
+ return rc;
+}
+
+/* receive a L1 uplink measurement from L1 */
+int lchan_new_ul_meas(struct gsm_lchan *lchan, struct bts_ul_meas *ulm)
+{
+ DEBUGP(DMEAS, "%s adding measurement, num_ul_meas=%d\n",
+ gsm_lchan_name(lchan), lchan->meas.num_ul_meas);
+
+ if (lchan->meas.num_ul_meas >= ARRAY_SIZE(lchan->meas.uplink)) {
+ LOGP(DMEAS, LOGL_NOTICE, "%s no space for uplink measurement\n",
+ gsm_lchan_name(lchan));
+ return -ENOSPC;
+ }
+
+ memcpy(&lchan->meas.uplink[lchan->meas.num_ul_meas++], ulm,
+ sizeof(*ulm));
+
+ return 0;
+}
+
+/* input: BER in steps of .01%, i.e. percent/100 */
+static uint8_t ber10k_to_rxqual(uint32_t ber10k)
+{
+ /* 05.08 / 8.2.4 */
+ if (ber10k < 20)
+ return 0;
+ if (ber10k < 40)
+ return 1;
+ if (ber10k < 80)
+ return 2;
+ if (ber10k < 160)
+ return 3;
+ if (ber10k < 320)
+ return 4;
+ if (ber10k < 640)
+ return 5;
+ if (ber10k < 1280)
+ return 6;
+ return 7;
+}
+
+int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn)
+{
+ uint32_t ber_full_sum = 0;
+ uint32_t irssi_full_sum = 0;
+ uint32_t ber_sub_sum = 0;
+ uint32_t irssi_sub_sum = 0;
+ int32_t taqb_sum = 0;
+ unsigned int num_meas_sub = 0;
+ int i;
+
+ /* if measurement period is not complete, abort */
+ if (!is_meas_complete(lchan->ts->pchan, lchan->ts->nr,
+ lchan->nr, fn))
+ return 0;
+
+ /* if there are no measurements, skip computation */
+ if (lchan->meas.num_ul_meas == 0)
+ return 0;
+
+ /* compute the actual measurements */
+
+ /* step 1: add up */
+ for (i = 0; i < lchan->meas.num_ul_meas; i++) {
+ struct bts_ul_meas *m = &lchan->meas.uplink[i];
+
+ ber_full_sum += m->ber10k;
+ irssi_full_sum += m->inv_rssi;
+ taqb_sum += m->ta_offs_qbits;
+
+ if (m->is_sub) {
+ num_meas_sub++;
+ ber_sub_sum += m->ber10k;
+ irssi_sub_sum += m->inv_rssi;
+ }
+ }
+
+ /* step 2: divide */
+ ber_full_sum = ber_full_sum / lchan->meas.num_ul_meas;
+ irssi_full_sum = irssi_full_sum / lchan->meas.num_ul_meas;
+ taqb_sum = taqb_sum / lchan->meas.num_ul_meas;
+ ber_sub_sum = ber_sub_sum / num_meas_sub;
+ irssi_sub_sum = irssi_sub_sum / num_meas_sub;
+
+ DEBUGP(DMEAS, "%s Computed TA(% 4uqb) BER-FULL(%2u.%02u%%), RSSI-FULL(-%3udBm), "
+ "BER-SUB(%2u.%02u%%), RSSI-SUB(-%3udBm)", gsm_lchan_name(lchan),
+ taqb_sum, ber_full_sum/100,
+ ber_full_sum%100, irssi_full_sum, ber_sub_sum/100, ber_sub_sum%100,
+ irssi_sub_sum);
+
+ /* store results */
+ lchan->meas.res.rxlev_full = dbm2rxlev((int)irssi_full_sum * -1);
+ lchan->meas.res.rxlev_sub = dbm2rxlev((int)irssi_sub_sum * -1);
+ lchan->meas.res.rxqual_full = ber10k_to_rxqual(ber_full_sum);
+ lchan->meas.res.rxqual_sub = ber10k_to_rxqual(ber_sub_sum);
+
+ lchan->meas.flags |= LC_UL_M_F_RES_VALID;
+ lchan->meas.num_ul_meas = 0;
+
+ /* send a signal indicating computation is complete */
+
+ return 1;
+}
+
+/* build the 3 byte RSL uplinke measurement IE content */
+int lchan_build_rsl_ul_meas(struct gsm_lchan *lchan, uint8_t *buf)
+{
+ buf[0] = (lchan->meas.res.rxlev_full & 0x3f); /* FIXME: DTXu support */
+ buf[1] = (lchan->meas.res.rxlev_sub & 0x3f);
+ buf[2] = ((lchan->meas.res.rxqual_full & 7) << 3) |
+ (lchan->meas.res.rxqual_sub & 7);
+
+ return 3;
+}
+
+int ts_meas_check_compute(struct gsm_bts_trx_ts *ts, uint32_t fn)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) {
+ struct gsm_lchan *lchan = &ts->lchan[i];
+
+ if (lchan->state != LCHAN_S_ACTIVE)
+ continue;
+
+ switch (lchan->type) {
+ case GSM_LCHAN_SDCCH:
+ case GSM_LCHAN_TCH_F:
+ case GSM_LCHAN_TCH_H:
+ lchan_meas_check_compute(lchan, fn);
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+/* needs to be called once every TDMA frame ! */
+int trx_meas_check_compute(struct gsm_bts_trx *trx, uint32_t fn)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ ts_meas_check_compute(ts, fn);
+ }
+ return 0;
+}
diff --git a/src/common/rsl.c b/src/common/rsl.c
index e6ced26..c9885fd 100644
--- a/src/common/rsl.c
+++ b/src/common/rsl.c
@@ -39,6 +39,7 @@
#include <osmo-bts/oml.h>
#include <osmo-bts/signal.h>
#include <osmo-bts/bts_model.h>
+#include <osmo-bts/measurement.h>
static int rsl_tx_error_report(struct gsm_bts_trx *trx, uint8_t cause);
@@ -251,7 +252,7 @@ static int rsl_rx_bcch_info(struct gsm_bts_trx *trx, struct msgb *msg)
LOGP(DRSL, LOGL_INFO, " Rx RSL BCCH INFO (SI%s)\n",
get_value_string(osmo_sitype_strs, osmo_si));
} else if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) {
- uint8_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO);
+ uint16_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO);
if (len > sizeof(sysinfo_buf_t))
len = sizeof(sysinfo_buf_t);
bts->si_valid |= (1 << osmo_si);
@@ -359,7 +360,7 @@ static int rsl_rx_sacch_fill(struct gsm_bts_trx *trx, struct msgb *msg)
return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT);
}
if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) {
- uint8_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO);
+ uint16_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO);
/* We have to pre-fix with the two-byte LAPDM UI header */
if (len > sizeof(sysinfo_buf_t)-2)
len = sizeof(sysinfo_buf_t)-2;
@@ -437,6 +438,9 @@ int rsl_tx_chan_act_ack(struct gsm_lchan *lchan, struct gsm_time *gtime)
rsl_dch_push_hdr(msg, RSL_MT_CHAN_ACTIV_ACK, chan_nr);
msg->trx = lchan->ts->trx;
+ /* since activation was successful, do some lchan initialization */
+ lchan->meas.res_nr = 0;
+
return abis_rsl_sendmsg(msg);
}
@@ -605,7 +609,7 @@ 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, mode);
/* actually activate the channel in the BTS */
- return bts_model_rsl_chan_act(msg->lchan, &tp);
+ return bts_model_rsl_chan_act(msg->lchan, &tp);
}
/* 8.4.14 RF CHANnel RELease is received */
@@ -651,7 +655,7 @@ static int rsl_rx_sacch_inf_mod(struct msgb *msg)
return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT);
}
if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) {
- uint8_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO);
+ uint16_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO);
/* We have to pre-fix with the two-byte LAPDM UI header */
if (len > sizeof(sysinfo_buf_t)-2)
len = sizeof(sysinfo_buf_t)-2;
@@ -851,18 +855,99 @@ static int rsl_rx_rll(struct gsm_bts_trx *trx, struct msgb *msg)
return lapdm_rslms_recvmsg(msg, &lchan->lapdm_ch);
}
+static inline int rsl_link_id_is_sacch(uint8_t link_id)
+{
+ if (link_id >> 6 == 1)
+ return 1;
+ else
+ return 0;
+}
+
+static int rslms_is_meas_rep(struct msgb *msg)
+{
+ struct abis_rsl_common_hdr *rh = msgb_l2(msg);
+ struct abis_rsl_rll_hdr *rllh;
+ struct gsm48_hdr *gh;
+
+ if ((rh->msg_discr & 0xfe) != ABIS_RSL_MDISC_RLL) {
+ DEBUGP(DRSL, "msg_disc 0x%x != RLL\n", rh->msg_discr);
+ return 0;
+ }
+
+ if (rh->msg_type != RSL_MT_UNIT_DATA_IND) {
+ DEBUGP(DRSL, "msg_type 0x%x != UNIT_DATA_IND\n", rh->msg_type);
+ return 0;
+ }
+#if 0
+ rllh = msgb_l2(msg);
+ if (rsl_link_id_is_sacch(rllh->link_id) == 0)
+ return 0;
+ gh = msgb_l3(msg);
+ if (gh->proto_discr != GSM48_PDISC_RR)
+ return 0;
+
+ if (gh->msg_type != GSM48_MT_RR_MEAS_REP)
+ return 0;
+#endif
+ return 1;
+}
+
+/* 8.4.8 MEASUREMENT RESult */
+static int rsl_tx_meas_res(struct gsm_lchan *lchan, uint8_t *l3, int l3_len)
+{
+ struct msgb *msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+
+ LOGP(DRSL, LOGL_NOTICE, "(%s) Tx MEAS RES\n", gsm_lchan_name(lchan));
+
+ msgb_tv_put(msg, RSL_IE_MEAS_RES_NR, lchan->meas.res_nr++);
+ if (lchan->meas.flags & LC_UL_M_F_RES_VALID) {
+ uint8_t meas_res[16];
+ int ie_len = lchan_build_rsl_ul_meas(lchan, meas_res);
+ if (ie_len >= 3) {
+ msgb_tlv_put(msg, RSL_IE_UPLINK_MEAS, ie_len, meas_res);
+ lchan->meas.flags &= ~LC_UL_M_F_RES_VALID;
+ }
+ }
+ msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->meas.bts_tx_pwr);
+ if (lchan->meas.flags & LC_UL_M_F_L1_VALID) {
+ msgb_tv_fixed_put(msg, RSL_IE_L1_INFO, 2, lchan->meas.l1_info);
+ lchan->meas.flags &= ~LC_UL_M_F_L1_VALID;
+ }
+ msgb_tl16v_put(msg, RSL_IE_L3_INFO, l3_len, l3);
+ //msgb_tv_put(msg, RSL_IE_MS_TIMING_OFFSET, FIXME);
+
+ rsl_dch_push_hdr(msg, RSL_MT_MEAS_RES, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_rsl_sendmsg(msg);
+}
+
/* call-back for LAPDm code, called when it wants to send msgs UP */
int lapdm_rll_tx_cb(struct msgb *msg, struct lapdm_entity *le, void *ctx)
{
struct gsm_lchan *lchan = ctx;
struct abis_rsl_common_hdr *rh = msgb_l2(msg);
- LOGP(DRSL, LOGL_INFO, "%s Fwd RLL msg %s from LAPDm to A-bis\n",
- gsm_lchan_name(lchan), rsl_msg_name(rh->msg_type));
-
msg->trx = lchan->ts->trx;
- return abis_rsl_sendmsg(msg);
+ /* check if this is a measurement report from SACCH which needs special
+ * processing before forwarding */
+ if (rslms_is_meas_rep(msg)) {
+ int rc;
+
+ LOGP(DRSL, LOGL_INFO, "%s Handing RLL msg %s from LAPDm to MEAS REP\n",
+ gsm_lchan_name(lchan), rsl_msg_name(rh->msg_type));
+
+ rc = rsl_tx_meas_res(lchan, msgb_l3(msg), msgb_l3len(msg));
+ msgb_free(msg);
+ return rc;
+ } else {
+ LOGP(DRSL, LOGL_INFO, "%s Fwd RLL msg %s from LAPDm to A-bis\n",
+ gsm_lchan_name(lchan), rsl_msg_name(rh->msg_type));
+
+ return abis_rsl_sendmsg(msg);
+ }
}
static int rsl_rx_cchan(struct gsm_bts_trx *trx, struct msgb *msg)
diff --git a/src/osmo-bts-sysmo/l1_if.c b/src/osmo-bts-sysmo/l1_if.c
index c01919f..fd09ed9 100644
--- a/src/osmo-bts-sysmo/l1_if.c
+++ b/src/osmo-bts-sysmo/l1_if.c
@@ -39,6 +39,7 @@
#include <osmo-bts/bts.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/paging.h>
+#include <osmo-bts/measurement.h>
#include <sysmocom/femtobts/femtobts.h>
#include <sysmocom/femtobts/gsml1prim.h>
@@ -326,6 +327,10 @@ static int handle_mph_time_ind(struct femtol1_hdl *fl1,
/* Update our data structures with the current GSM time */
gsm_fn2gsmtime(&fl1->gsm_time, time_ind->u32Fn);
+ /* check if the measurement period of some lchan has ended
+ * and pre-compute the respective measurement */
+ trx_meas_check_compute(fl1->priv, time_ind->u32Fn -1);
+
return 0;
}
@@ -357,13 +362,31 @@ static void dump_meas_res(GsmL1_MeasParam_t *m)
m->fBer, m->i16BurstTiming);
}
+static int process_meas_res(struct gsm_lchan *lchan, GsmL1_MeasParam_t *m)
+{
+ struct bts_ul_meas ulm;
+
+ ulm.ta_offs_qbits = m->i16BurstTiming;
+ ulm.ber10k = (unsigned int) (m->fBer * 100);
+ ulm.inv_rssi = (uint8_t) (m->fRssi * -1);
+
+ return lchan_new_ul_meas(lchan, &ulm);
+}
+
static int handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_ind)
{
struct osmo_phsap_prim pp;
struct gsm_lchan *lchan;
struct lapdm_entity *le;
struct msgb *msg;
- uint8_t lapdm_sapi = 0;
+
+ lchan = l1if_hLayer2_to_lchan(fl1->priv, data_ind->hLayer2);
+ if (!lchan) {
+ LOGP(DL1C, LOGL_ERROR, "unable to resolve lchan by hLayer2\n");
+ return -ENODEV;
+ }
+
+ process_meas_res(lchan, &data_ind->measParam);
if (data_ind->measParam.fLinkQuality < MIN_QUAL_NORM)
return 0;
@@ -371,10 +394,12 @@ static int handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_i
DEBUGP(DL1C, "Rx PH-DATA.ind (hLayer2 = 0x%08x)", data_ind->hLayer2);
dump_meas_res(&data_ind->measParam);
- lchan = l1if_hLayer2_to_lchan(fl1->priv, data_ind->hLayer2);
- if (!lchan) {
- LOGP(DL1C, LOGL_ERROR, "unable to resolve lchan by hLayer2\n");
- return -ENODEV;
+ /* save the SACCH L1 header in the lchan struct for RSL MEAS RES */
+ if (data_ind->sapi == GsmL1_Sapi_Sacch &&
+ data_ind->msgUnitParam.u8Size >= 2) {
+ lchan->meas.l1_info[0] = data_ind->msgUnitParam.u8Buffer[0];
+ lchan->meas.l1_info[1] = data_ind->msgUnitParam.u8Buffer[1];
+ lchan->meas.flags |= LC_UL_M_F_L1_VALID;
}
le = le_by_l1_sapi(&lchan->lapdm_ch, data_ind->sapi);