#include #include #include #include #include #include #include #include #include #include #include #include #include /* Active TDMA frame subset for TCH/H in DTX mode (see 3GPP TS 45.008 Section 8.3). * This mapping is used to determine if a L2 block starting at the given TDMA FN * belongs to the SUB set and thus shall always be transmitted in DTX mode. */ static const uint8_t ts45008_dtx_tchh_speech_fn_map[104] = { /* TCH/H(0): 0, 2, 4, 6, 52, 54, 56, 58 */ [0] = 1, /* block { 0, 2, 4, 6} */ [52] = 1, /* block {52, 54, 56, 58} */ /* TCH/H(1): 14, 16, 18, 20, 66, 68, 70, 72 */ [14] = 1, /* block {14, 16, 18, 20} */ [66] = 1, /* block {66, 68, 70, 72} */ }; static const uint8_t ts45008_dtx_tchh_data_fn_map[104] = { /* UL TCH/H(0): 52, 54, 56, 58, 60, 62, 65, 67, 69, 71 */ [52] = 1, /* block {52, 54, 56, 58, 60, 62} */ [60] = 1, /* block {60, 62, 65, 67, 69, 71} */ /* UL TCH/H(1): 70, 72, 74, 76, 79, 81, 83, 85, 87, 89 */ [70] = 1, /* block {70, 72, 74, 76, 79, 81} */ [79] = 1, /* block {79, 81, 83, 85, 87, 89} */ }; /* In cases where we less measurements than we expect we must assume that we * just did not receive the block because it was lost due to bad channel * conditions. We set up a dummy measurement result here that reflects the * worst possible result. In our* calculation we will use this dummy to replace * the missing measurements */ #define MEASUREMENT_DUMMY_BER 10000 /* 100% BER */ #define MEASUREMENT_DUMMY_IRSSI 109 /* noise floor in -dBm */ static const struct bts_ul_meas measurement_dummy = { .ber10k = MEASUREMENT_DUMMY_BER, .ta_offs_256bits = 0, .ci_cb = 0, .is_sub = 0, .inv_rssi = MEASUREMENT_DUMMY_IRSSI }; /* Decide if a given frame number is part of the "-SUB" measurements (true) or not (false) * (this function is only used internally, it is public to call it from unit-tests) */ bool ts45008_83_is_sub(struct gsm_lchan *lchan, uint32_t fn) { uint32_t fn104 = fn % 104; /* See TS 45.008 Sections 8.3 and 8.4 for a detailed descriptions of the rules * implemented here. We implement the logic for both speech and data (CSD). */ /* AMR is special, SID frames may be scheduled dynamically at any time */ if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) return false; switch (lchan->type) { case GSM_LCHAN_TCH_F: switch (lchan->tch_mode) { case GSM48_CMODE_SIGN: /* TCH/F sign: DTX *is* permitted */ case GSM48_CMODE_SPEECH_V1: case GSM48_CMODE_SPEECH_V1_VAMOS: case GSM48_CMODE_SPEECH_EFR: case GSM48_CMODE_SPEECH_V2_VAMOS: /* Active TDMA frame subset for TCH/F: 52, 53, 54, 55, 56, 57, 58, 59. * There is only one *complete* block in this subset starting at FN=52. * Incomplete blocks {... 52, 53, 54, 55} and {56, 57, 58, 59 ...} * contain only 50% of the useful bits (partial SID) and thus ~50% BER. */ if (fn104 == 52) return true; break; case GSM48_CMODE_DATA_12k0: /* TCH/F9.6 */ case GSM48_CMODE_DATA_6k0: /* TCH/F4.8 */ /* FIXME: The RXQUAL_SUB (not RXLEV!) report shall include measurements on * the TDMA frames given in the table of subclause 8.3 only if L2 fill frames * have been received as FACCH/F frames at the corresponding frame positions. */ default: if (lchan->rsl_cmode == RSL_CMOD_SPD_DATA) return fn104 == 52; LOGPLCFN(lchan, fn, DMEAS, LOGL_ERROR, "Unsupported lchan->tch_mode %u\n", lchan->tch_mode); break; } break; case GSM_LCHAN_TCH_H: switch (lchan->tch_mode) { case GSM48_CMODE_SPEECH_V1: case GSM48_CMODE_SPEECH_V1_VAMOS: if (ts45008_dtx_tchh_speech_fn_map[fn104]) return true; break; case GSM48_CMODE_SIGN: /* No DTX allowed; SUB=FULL, therefore measurements at all frame numbers are * SUB */ return true; case GSM48_CMODE_DATA_6k0: /* TCH/H4.8 */ case GSM48_CMODE_DATA_3k6: /* TCH/H2.4 */ /* FIXME: The RXQUAL_SUB (not RXLEV!) report shall include measurements on * the TDMA frames given in the table of subclause 8.3 only if L2 fill frames * have been received as FACCH/H frames at the corresponding frame positions. */ default: if (lchan->rsl_cmode == RSL_CMOD_SPD_DATA) return ts45008_dtx_tchh_data_fn_map[fn104] == 1; LOGPLCFN(lchan, fn, DMEAS, LOGL_ERROR, "Unsupported lchan->tch_mode %u\n", lchan->tch_mode); break; } break; case GSM_LCHAN_SDCCH: /* No DTX allowed; SUB=FULL, therefore measurements at all frame numbers are SUB */ return true; default: break; } return false; } /* Measurement reporting period and mapping of SACCH message block for TCHF * and TCHH chan As per in 3GPP TS 45.008, section 8.4.1. * * Timeslot number (TN) TDMA frame number (FN) modulo 104 * Half rate, Half rate, Reporting SACCH * Full Rate subch.0 subch.1 period Message block * 0 0 and 1 0 to 103 12, 38, 64, 90 * 1 0 and 1 13 to 12 25, 51, 77, 103 * 2 2 and 3 26 to 25 38, 64, 90, 12 * 3 2 and 3 39 to 38 51, 77, 103, 25 * 4 4 and 5 52 to 51 64, 90, 12, 38 * 5 4 and 5 65 to 64 77, 103, 25, 51 * 6 6 and 7 78 to 77 90, 12, 38, 64 * 7 6 and 7 91 to 90 103, 25, 51, 77 * * Note: The array index of the following three lookup tables refes to a * timeslot number. */ static const uint8_t tchf_meas_rep_fn104_by_ts[] = { [0] = 90, [1] = 103, [2] = 12, [3] = 25, [4] = 38, [5] = 51, [6] = 64, [7] = 77, }; static const uint8_t tchh0_meas_rep_fn104_by_ts[] = { [0] = 90, [1] = 90, [2] = 12, [3] = 12, [4] = 38, [5] = 38, [6] = 64, [7] = 64, }; static const uint8_t tchh1_meas_rep_fn104_by_ts[] = { [0] = 103, [1] = 103, [2] = 25, [3] = 25, [4] = 51, [5] = 51, [6] = 77, [7] = 77, }; /* Measurement reporting period for SDCCH8 and SDCCH4 chan * As per in 3GPP TS 45.008, section 8.4.2. * * Logical Chan TDMA frame number * (FN) modulo 102 * * SDCCH/8 12 to 11 * SDCCH/4 37 to 36 * * * Note: The array index of the following three lookup tables refes to a * subslot number. */ /* FN of the first burst whose block completes before reaching fn%102=11 */ static const uint8_t sdcch8_meas_rep_fn102_by_ss[] = { [0] = 66, /* 15(SDCCH), 47(SACCH), 66(SDCCH) */ [1] = 70, /* 19(SDCCH), 51(SACCH), 70(SDCCH) */ [2] = 74, /* 23(SDCCH), 55(SACCH), 74(SDCCH) */ [3] = 78, /* 27(SDCCH), 59(SACCH), 78(SDCCH) */ [4] = 98, /* 31(SDCCH), 98(SACCH), 82(SDCCH) */ [5] = 0, /* 35(SDCCH), 0(SACCH), 86(SDCCH) */ [6] = 4, /* 39(SDCCH), 4(SACCH), 90(SDCCH) */ [7] = 8, /* 43(SDCCH), 8(SACCH), 94(SDCCH) */ }; /* FN of the first burst whose block completes before reaching fn%102=37 */ static const uint8_t sdcch4_meas_rep_fn102_by_ss[] = { [0] = 88, /* 37(SDCCH), 57(SACCH), 88(SDCCH) */ [1] = 92, /* 41(SDCCH), 61(SACCH), 92(SDCCH) */ [2] = 6, /* 6(SACCH), 47(SDCCH), 98(SDCCH) */ [3] = 10 /* 10(SACCH), 0(SDCCH), 51(SDCCH) */ }; /* Note: The reporting of the measurement results is done via the SACCH channel. * The measurement interval is not aligned with the interval in which the * SACCH is transmitted. When we receive the measurement indication with the * SACCH block, the corresponding measurement interval will already have ended * and we will get the results late, but on spot with the beginning of the * next measurement interval. * * For example: We get a measurement indication on FN%104=38 in TS=2. Then we * will have to look at 3GPP TS 45.008, section 8.4.1 (or 3GPP TS 05.02 Clause 7 * Table 1 of 9) what value we need to feed into the lookup tables in order to * detect the measurement period ending. In this example the "real" ending * was on FN%104=12. This is the value we have to look for in * tchf_meas_rep_fn104_by_ts to know that a measurement period has just ended. */ /* See also 3GPP TS 05.02 Clause 7 Table 1 of 9: * Mapping of logical channels onto physical channels (see subclauses 6.3, 6.4, 6.5) */ static uint8_t translate_tch_meas_rep_fn104(uint8_t fn_mod) { switch (fn_mod) { case 25: return 103; case 38: return 12; case 51: return 25; case 64: return 38; case 77: return 51; case 90: return 64; case 103: return 77; case 12: return 90; } /* Invalid / not of interest */ return 0; } /* determine if a measurement period ends at the given frame number * (this function is only used internally, it is public to call it from * unit-tests) */ int is_meas_complete(struct gsm_lchan *lchan, uint32_t fn) { unsigned int fn_mod = -1; const uint8_t *tbl; int rc = 0; enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts); switch (pchan) { case GSM_PCHAN_TCH_F: fn_mod = translate_tch_meas_rep_fn104(fn % 104); if (tchf_meas_rep_fn104_by_ts[lchan->ts->nr] == fn_mod) rc = 1; break; case GSM_PCHAN_TCH_H: fn_mod = translate_tch_meas_rep_fn104(fn % 104); if (lchan->nr == 0) tbl = tchh0_meas_rep_fn104_by_ts; else tbl = tchh1_meas_rep_fn104_by_ts; if (tbl[lchan->ts->nr] == fn_mod) rc = 1; break; case GSM_PCHAN_SDCCH8_SACCH8C: case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: fn_mod = fn % 102; if (sdcch8_meas_rep_fn102_by_ss[lchan->nr] == fn_mod) rc = 1; break; case GSM_PCHAN_CCCH_SDCCH4: case GSM_PCHAN_CCCH_SDCCH4_CBCH: fn_mod = fn % 102; if (sdcch4_meas_rep_fn102_by_ss[lchan->nr] == fn_mod) rc = 1; break; default: rc = 0; break; } if (rc == 1) { LOGPLCFN(lchan, fn, DMEAS, LOGL_DEBUG, "meas period end fn_mod:%d, status:%d, pchan:%s\n", fn_mod, rc, gsm_pchan_name(pchan)); } return rc; } /* determine the measurement interval modulus by a given lchan */ static uint8_t modulus_by_lchan(struct gsm_lchan *lchan) { enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts); switch (pchan) { case GSM_PCHAN_TCH_F: case GSM_PCHAN_TCH_H: return 104; case GSM_PCHAN_SDCCH8_SACCH8C: case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: case GSM_PCHAN_CCCH_SDCCH4: case GSM_PCHAN_CCCH_SDCCH4_CBCH: return 102; default: /* Invalid */ return 1; } } /* receive a L1 uplink measurement from L1 (this function is only used * internally, it is public to call it from unit-tests) */ int lchan_new_ul_meas(struct gsm_lchan *lchan, const struct bts_ul_meas *ulm, uint32_t fn) { uint32_t fn_mod = fn % modulus_by_lchan(lchan); struct bts_ul_meas *dest; if (lchan->state != LCHAN_S_ACTIVE) { LOGPLCFN(lchan, fn, DMEAS, LOGL_NOTICE, "measurement during state: %s, num_ul_meas=%d, fn_mod=%u\n", gsm_lchans_name(lchan->state), lchan->meas.num_ul_meas, fn_mod); } if (lchan->meas.num_ul_meas >= ARRAY_SIZE(lchan->meas.uplink)) { LOGPLCFN(lchan, fn, DMEAS, LOGL_NOTICE, "no space for uplink measurement, num_ul_meas=%d, fn_mod=%u\n", lchan->meas.num_ul_meas, fn_mod); return -ENOSPC; } dest = &lchan->meas.uplink[lchan->meas.num_ul_meas++]; memcpy(dest, ulm, sizeof(*ulm)); /* We expect the lower layers to mark AMR SID_UPDATE frames already as such. * In this function, we only deal with the common logic as per the TS 45.008 tables */ if (!ulm->is_sub) dest->is_sub = ts45008_83_is_sub(lchan, fn); LOGPLCFN(lchan, fn, DMEAS, LOGL_DEBUG, "adding a %s measurement (ber10k=%u, ta_offs=%d, ci_cB=%d, rssi=-%u), num_ul_meas=%d, fn_mod=%u\n", dest->is_sub ? "SUB" : "FULL", ulm->ber10k, ulm->ta_offs_256bits, ulm->ci_cb, ulm->inv_rssi, lchan->meas.num_ul_meas, fn_mod); lchan->meas.last_fn = fn; return 0; } /* input: BER in steps of .01%, i.e. percent/100 */ static uint8_t ber10k_to_rxqual(uint32_t ber10k) { /* Eight levels of Rx quality are defined and are mapped to the * equivalent BER before channel decoding, as per in 3GPP TS 45.008, * secton 8.2.4. * * RxQual: BER Range: * RXQUAL_0 BER < 0,2 % Assumed value = 0,14 % * RXQUAL_1 0,2 % < BER < 0,4 % Assumed value = 0,28 % * RXQUAL_2 0,4 % < BER < 0,8 % Assumed value = 0,57 % * RXQUAL_3 0,8 % < BER < 1,6 % Assumed value = 1,13 % * RXQUAL_4 1,6 % < BER < 3,2 % Assumed value = 2,26 % * RXQUAL_5 3,2 % < BER < 6,4 % Assumed value = 4,53 % * RXQUAL_6 6,4 % < BER < 12,8 % Assumed value = 9,05 % * RXQUAL_7 12,8 % < BER Assumed value = 18,10 % */ 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; } /* Get the number of measurements that we expect for a specific lchan. * (This is a static number that is defined by the specific slot layout of * the channel) */ static unsigned int lchan_meas_num_expected(const struct gsm_lchan *lchan) { enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts); switch (pchan) { case GSM_PCHAN_TCH_F: /* 24 blocks for TCH + 1 for SACCH */ return 25; case GSM_PCHAN_TCH_H: if (lchan->tch_mode == GSM48_CMODE_SIGN) { /* 12 blocks for TCH + 1 for SACCH */ return 13; } else { /* 24 blocks for TCH + 1 for SACCH */ return 25; } case GSM_PCHAN_SDCCH8_SACCH8C: case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: /* 2 for SDCCH + 1 for SACCH */ return 3; case GSM_PCHAN_CCCH_SDCCH4: case GSM_PCHAN_CCCH_SDCCH4_CBCH: /* 2 for SDCCH + 1 for SACCH */ return 3; default: return lchan->meas.num_ul_meas; } } /* In DTX a subset of blocks must always be transmitted * See also: GSM 05.08, chapter 8.3 Aspects of discontinuous transmission (DTX) * Return value N: (N < 0) -- at least N SUB frames expected; * (N > 0) -- exactly N SUB frames expected; * (N == 0) - unknown channel type/mode? */ static int lchan_meas_sub_num_expected(const struct gsm_lchan *lchan) { enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts); switch (pchan) { case GSM_PCHAN_TCH_F: if (lchan->rsl_cmode == RSL_CMOD_SPD_DATA) return 1 + 1; /* 1 x SACCH + 1 x FACCH */ /* else: signalling or speech */ switch (lchan->tch_mode) { case GSM48_CMODE_SIGN: /* TCH/F sign: DTX *is* permitted */ case GSM48_CMODE_SPEECH_V1: /* TCH/FS */ case GSM48_CMODE_SPEECH_V1_VAMOS: case GSM48_CMODE_SPEECH_EFR: /* TCH/EFS */ case GSM48_CMODE_SPEECH_V2_VAMOS: return 1 + 1; /* 1 x SACCH + 1 x TCH */ case GSM48_CMODE_SPEECH_AMR: /* TCH/AFS */ case GSM48_CMODE_SPEECH_V3_VAMOS: case GSM48_CMODE_SPEECH_V4: /* O-TCH/WFS */ case GSM48_CMODE_SPEECH_V5: /* TCH/WFS */ case GSM48_CMODE_SPEECH_V5_VAMOS: default: return -1; /* at least 1 x SACCH + M x TCH (variable) */ } case GSM_PCHAN_TCH_H: if (lchan->rsl_cmode == RSL_CMOD_SPD_DATA) return 1 + 2; /* 1 x SACCH + 2 x FACCH */ /* else: signalling or speech */ switch (lchan->tch_mode) { case GSM48_CMODE_SIGN: /* TCH/H sign: DTX *is not* permitted */ return 1 + 12; /* 1 x SACCH + 12 x TCH */ case GSM48_CMODE_SPEECH_V1: case GSM48_CMODE_SPEECH_V1_VAMOS: return 1 + 2; /* 1 x SACCH + 2 x TCH */ case GSM48_CMODE_SPEECH_AMR: /* TCH/AHS */ case GSM48_CMODE_SPEECH_V3_VAMOS: case GSM48_CMODE_SPEECH_V4: /* O-TCH/WHS */ case GSM48_CMODE_SPEECH_V6: /* O-TCH/AHS */ default: return -1; /* at least 1 x SACCH + M x TCH (variable) */ } case GSM_PCHAN_SDCCH8_SACCH8C: case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: /* no DTX here, all blocks must be present! */ return 3; case GSM_PCHAN_CCCH_SDCCH4: case GSM_PCHAN_CCCH_SDCCH4_CBCH: /* no DTX here, all blocks must be present! */ return 3; default: return 0; } } /* if we clip the TOA value to 12 bits, i.e. toa256=3200, * -> the maximum deviation can be 2*3200 = 6400 * -> the maximum squared deviation can be 6400^2 = 40960000 * -> the maximum sum of squared deviations can be 104*40960000 = 4259840000 * and hence fit into uint32_t * -> once the value is divided by 104, it's again below 40960000 * leaving 6 MSBs of freedom, i.e. we could extend by 64, resulting in 2621440000 * -> as a result, the standard deviation could be communicated with up to six bits * of fractional fixed-point number. */ /* compute Osmocom extended measurements for the given lchan */ static void lchan_meas_compute_extended(struct gsm_lchan *lchan) { unsigned int num_ul_meas; unsigned int num_ul_meas_excess = 0; unsigned int num_ul_meas_expect; /* we assume that lchan_meas_check_compute() has already computed the mean value * and we can compute the min/max/variance/stddev from this */ int i; /* each measurement is an int32_t, so the squared difference value must fit in 32bits */ /* the sum of the squared values (each up to 32bit) can very easily exceed 32 bits */ uint64_t sq_diff_sum = 0; /* In case we do not have any measurement values collected there is no * computation possible. We just skip the whole computation here, the * lchan->meas.flags will not get the LC_UL_M_F_OSMO_EXT_VALID flag set * so no extended measurement results will be reported back via RSL. * this is ok, since we have nothing to report anyway and apart of that * we also just lost the signal (otherwise we would have at least some * measurements). */ if (!lchan->meas.num_ul_meas) return; /* initialize min/max values with their counterpart */ lchan->meas.ext.toa256_min = INT16_MAX; lchan->meas.ext.toa256_max = INT16_MIN; /* Determine the number of measurement values we need to take into the * computation. In this case we only compute over the measurements we * have indeed received. Since this computation is about timing * information it does not make sense to approach missing measurement * samples the TOA with 0. This would bend the average towards 0. What * counts is the average TOA of the properly received blocks so that * the TA logic can make a proper decision. */ num_ul_meas_expect = lchan_meas_num_expected(lchan); if (lchan->meas.num_ul_meas > num_ul_meas_expect) { num_ul_meas = num_ul_meas_expect; num_ul_meas_excess = lchan->meas.num_ul_meas - num_ul_meas_expect; } else num_ul_meas = lchan->meas.num_ul_meas; /* all computations are done on the relative arrival time of the burst, relative to the * beginning of its slot. This is of course excluding the TA value that the MS has already * compensated/pre-empted its transmission */ /* step 1: compute the sum of the squared difference of each value to mean */ for (i = 0; i < num_ul_meas; i++) { const struct bts_ul_meas *m; OSMO_ASSERT(i < lchan->meas.num_ul_meas); m = &lchan->meas.uplink[i+num_ul_meas_excess]; int32_t diff = (int32_t)m->ta_offs_256bits - (int32_t)lchan->meas.ms_toa256; /* diff can now be any value of +65535 to -65535, so we can safely square it, * but only in unsigned math. As squaring looses the sign, we can simply drop * it before squaring, too. */ uint32_t diff_abs = labs(diff); uint32_t diff_squared = diff_abs * diff_abs; sq_diff_sum += diff_squared; /* also use this loop iteration to compute min/max values */ if (m->ta_offs_256bits > lchan->meas.ext.toa256_max) lchan->meas.ext.toa256_max = m->ta_offs_256bits; if (m->ta_offs_256bits < lchan->meas.ext.toa256_min) lchan->meas.ext.toa256_min = m->ta_offs_256bits; } /* step 2: compute the variance (mean of sum of squared differences) */ sq_diff_sum = sq_diff_sum / num_ul_meas; /* as the individual summed values can each not exceed 2^32, and we're * dividing by the number of summands, the resulting value can also not exceed 2^32 */ OSMO_ASSERT(sq_diff_sum <= UINT32_MAX); /* step 3: compute the standard deviation from the variance */ lchan->meas.ext.toa256_std_dev = osmo_isqrt32(sq_diff_sum); lchan->meas.flags |= LC_UL_M_F_OSMO_EXT_VALID; } int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn) { struct gsm_meas_rep_unidir *mru; uint32_t ber_full_sum = 0; uint32_t irssi_full_sum = 0; int32_t ci_full_sum = 0; uint32_t ber_sub_sum = 0; uint32_t irssi_sub_sum = 0; int32_t ci_sub_sum = 0; int32_t ta256b_sum = 0; unsigned int num_meas_sub = 0; unsigned int num_meas_sub_actual = 0; unsigned int num_meas_sub_subst = 0; int num_meas_sub_expect; unsigned int num_ul_meas; unsigned int num_ul_meas_actual = 0; unsigned int num_ul_meas_subst = 0; unsigned int num_ul_meas_expect; unsigned int num_ul_meas_excess = 0; int i; /* if measurement period is not complete, abort */ if (!is_meas_complete(lchan, fn)) return 0; LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, "Calculating measurement results " "for physical channel: %s\n", gsm_pchan_name(ts_pchan(lchan->ts))); /* Note: Some phys will send no measurement indication at all * when a block is lost. Also in DTX mode blocks are left out * intentionally to save energy. It is not necessarly an error * when we get less measurements as we expect. */ num_ul_meas_expect = lchan_meas_num_expected(lchan); num_meas_sub_expect = lchan_meas_sub_num_expected(lchan); if (lchan->meas.num_ul_meas > num_ul_meas_expect) num_ul_meas_excess = lchan->meas.num_ul_meas - num_ul_meas_expect; num_ul_meas = num_ul_meas_expect; LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, "Received %u UL measurements, expected %u\n", lchan->meas.num_ul_meas, num_ul_meas_expect); if (num_ul_meas_excess) LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, "Received %u excess UL measurements\n", num_ul_meas_excess); /* Measurement computation step 1: add up */ for (i = 0; i < num_ul_meas; i++) { const struct bts_ul_meas *m; bool is_sub = false; /* Note: We will always compute over a full measurement, * interval even when not enough measurement samples are in * the buffer. As soon as we run out of measurement values * we continue the calculation using dummy values. This works * well for the BER, since there we can safely assume 100% * since a missing measurement means that the data (block) * is lost as well (some phys do not give us measurement * reports for lost blocks or blocks that are spaced out for * DTX). However, for RSSI and TA this does not work since * there we would distort the calculation if we would replace * them with a made up number. This means for those values we * only compute over the data we have actually received. */ if (i < lchan->meas.num_ul_meas) { m = &lchan->meas.uplink[i + num_ul_meas_excess]; if (m->is_sub) { irssi_sub_sum += m->inv_rssi; ci_sub_sum += m->ci_cb; num_meas_sub_actual++; is_sub = true; } irssi_full_sum += m->inv_rssi; ta256b_sum += m->ta_offs_256bits; ci_full_sum += m->ci_cb; num_ul_meas_actual++; } else { m = &measurement_dummy; /* only if we know the exact number of SUB measurements */ if (num_meas_sub_expect >= 0) { if (num_meas_sub < num_meas_sub_expect) { num_meas_sub_subst++; is_sub = true; } } num_ul_meas_subst++; } ber_full_sum += m->ber10k; if (is_sub) { num_meas_sub++; ber_sub_sum += m->ber10k; } } LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, "Replaced %u measurements with dummy values, " "from which %u were SUB measurements\n", num_ul_meas_subst, num_meas_sub_subst); /* Normally the logic above should make sure that there is * always the exact amount of SUB measurements taken into * account. If not then the logic that decides tags the received * measurements as is_sub works incorrectly. Since the logic * above only adds missing measurements during the calculation * it can not remove excess SUB measurements or add missing SUB * measurements when there is no more room in the interval. */ if (num_meas_sub_expect < 0) { num_meas_sub_expect = -num_meas_sub_expect; LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, "Received UL measurements contain %u SUB measurements, expected at least %d\n", num_meas_sub_actual, num_meas_sub_expect); if (OSMO_UNLIKELY(num_meas_sub < num_meas_sub_expect)) { LOGPLCHAN(lchan, DMEAS, LOGL_ERROR, "Incorrect number of SUB measurements detected! " "(%u vs exp >=%d)\n", num_meas_sub, num_meas_sub_expect); } } else { LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, "Received UL measurements contain %u SUB measurements, expected %d\n", num_meas_sub_actual, num_meas_sub_expect); if (OSMO_UNLIKELY(num_meas_sub != num_meas_sub_expect)) { LOGPLCHAN(lchan, DMEAS, LOGL_ERROR, "Incorrect number of SUB measurements detected! " "(%u vs exp %d)\n", num_meas_sub, num_meas_sub_expect); } } /* Measurement computation step 2: divide */ ber_full_sum = ber_full_sum / num_ul_meas; if (!irssi_full_sum) irssi_full_sum = MEASUREMENT_DUMMY_IRSSI; else irssi_full_sum = irssi_full_sum / num_ul_meas_actual; if (!num_ul_meas_actual) { ta256b_sum = lchan->meas.ms_toa256; ci_full_sum = lchan->meas.ul_ci_cb_full; } else { ta256b_sum = ta256b_sum / (signed)num_ul_meas_actual; ci_full_sum = ci_full_sum / (signed)num_ul_meas_actual; } if (!num_meas_sub) ber_sub_sum = MEASUREMENT_DUMMY_BER; else ber_sub_sum = ber_sub_sum / num_meas_sub; if (!num_meas_sub_actual) { irssi_sub_sum = MEASUREMENT_DUMMY_IRSSI; ci_sub_sum = lchan->meas.ul_ci_cb_sub; } else { irssi_sub_sum = irssi_sub_sum / num_meas_sub_actual; ci_sub_sum = ci_sub_sum / (signed)num_meas_sub_actual; } LOGPLCHAN(lchan, DMEAS, LOGL_INFO, "Computed TA256(% 4d), BER-FULL(%2u.%02u%%), RSSI-FULL(-%3udBm), C/I-FULL(% 4d cB), " "BER-SUB(%2u.%02u%%), RSSI-SUB(-%3udBm), C/I-SUB(% 4d cB)\n", ta256b_sum, ber_full_sum / 100, ber_full_sum % 100, irssi_full_sum, ci_full_sum, ber_sub_sum / 100, ber_sub_sum % 100, irssi_sub_sum, ci_sub_sum); /* store results */ mru = &lchan->meas.ul_res; mru->full.rx_lev = dbm2rxlev((int)irssi_full_sum * -1); mru->sub.rx_lev = dbm2rxlev((int)irssi_sub_sum * -1); mru->full.rx_qual = ber10k_to_rxqual(ber_full_sum); mru->sub.rx_qual = ber10k_to_rxqual(ber_sub_sum); lchan->meas.ms_toa256 = ta256b_sum; lchan->meas.ul_ci_cb_full = ci_full_sum; lchan->meas.ul_ci_cb_sub = ci_sub_sum; LOGPLCHAN(lchan, DMEAS, LOGL_INFO, "UL MEAS RXLEV_FULL(%u), RXLEV_SUB(%u), RXQUAL_FULL(%u), RXQUAL_SUB(%u), " "num_meas_sub(%u), num_ul_meas(%u)\n", mru->full.rx_lev, mru->sub.rx_lev, mru->full.rx_qual, mru->sub.rx_qual, num_meas_sub, num_ul_meas_expect); lchan->meas.flags |= LC_UL_M_F_RES_VALID; lchan_meas_compute_extended(lchan); lchan->meas.num_ul_meas = 0; /* return 1 to indicate that the computation has been done and the next * interval begins. */ return 1; } /* Process a single uplink measurement sample. This function is called from * l1sap.c every time a measurement indication is received. It collects the * measurement samples and automatically detects the end of the measurement * interval. */ int lchan_meas_process_measurement(struct gsm_lchan *lchan, const struct bts_ul_meas *ulm, uint32_t fn) { lchan_new_ul_meas(lchan, ulm, fn); return lchan_meas_check_compute(lchan, fn); } /* Reset all measurement related struct members to their initial values. This * function will be called every time an lchan is activated to ensure the * measurement process starts with a defined state. */ void lchan_meas_reset(struct gsm_lchan *lchan) { memset(&lchan->meas, 0, sizeof(lchan->meas)); lchan->meas.last_fn = LCHAN_FN_DUMMY; } static inline uint8_t ms_to2rsl(const struct gsm_lchan *lchan, uint8_t ta) { return (lchan->ms_t_offs >= 0) ? lchan->ms_t_offs : (lchan->p_offs - ta); } static inline bool ms_to_valid(const struct gsm_lchan *lchan) { return (lchan->ms_t_offs >= 0) || (lchan->p_offs >= 0); } /* Decide if repeated FACCH should be applied or not. If RXQUAL level, that the * MS reports is high enough, FACCH repetition is not needed. */ static void repeated_dl_facch_active_decision(struct gsm_lchan *lchan, const struct gsm48_meas_res *meas_res) { uint8_t upper; uint8_t lower; uint8_t rxqual; bool prev_repeated_dl_facch_active = lchan->rep_acch.dl_facch_active; /* This is an optimization so that we exit as quickly as possible if * there are no FACCH repetition capabilities present. However If the * repeated FACCH capabilities vanish for whatever reason, we must be * sure that FACCH repetition is disabled. */ if (!lchan->rep_acch_cap.dl_facch_cmd && !lchan->rep_acch_cap.dl_facch_all) { lchan->rep_acch.dl_facch_active = false; goto out; } /* Threshold disabled (always on) */ if (lchan->rep_acch_cap.rxqual == 0) { lchan->rep_acch.dl_facch_active = true; goto out; } /* When the MS sets the SRR bit in the UL-SACCH L1 header * (repeated SACCH requested) then it makes sense to enable * FACCH repetition too. */ if (lchan->meas.l1_info.srr_sro) { lchan->rep_acch.dl_facch_active = true; goto out; } /* Parse MS measurement results */ if (meas_res == NULL) goto out; if (!gsm48_meas_res_is_valid(meas_res)) goto out; /* If the RXQUAL level at the MS drops under a certain threshold * we enable FACCH repetition. */ upper = lchan->rep_acch_cap.rxqual; if (upper > 2) lower = lchan->rep_acch_cap.rxqual - 2; else lower = 0; /* When downlink DTX is applied, use RXQUAL-SUB, otherwise use * RXQUAL-FULL. */ if (meas_res->dtx_used) rxqual = meas_res->rxqual_sub; else rxqual = meas_res->rxqual_full; if (rxqual >= upper) lchan->rep_acch.dl_facch_active = true; else if (rxqual <= lower) lchan->rep_acch.dl_facch_active = false; out: if (lchan->rep_acch.dl_facch_active == prev_repeated_dl_facch_active) return; if (lchan->rep_acch.dl_facch_active) LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "DL-FACCH repetition: inactive => active\n"); else LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "DL-FACCH repetition: active => inactive\n"); } static void acch_overpower_active_decision(struct gsm_lchan *lchan, const struct gsm48_meas_res *meas_res) { const bool old = lchan->top_acch_active; uint8_t upper, lower, rxqual; /* ACCH overpower is not allowed => nothing to do */ if (lchan->top_acch_cap.overpower_db == 0) return; /* RxQual threshold is disabled => overpower is always on */ if (lchan->top_acch_cap.rxqual == 0) return; /* If DTx is active on Downlink, use the '-SUB' */ if (meas_res->dtx_used) rxqual = meas_res->rxqual_sub; else /* ... otherwise use the '-FULL' */ rxqual = meas_res->rxqual_full; upper = lchan->top_acch_cap.rxqual; if (upper > 2) lower = upper - 2; else lower = 0; if (rxqual >= upper) lchan->top_acch_active = true; else if (rxqual <= lower) lchan->top_acch_active = false; if (lchan->top_acch_active != old) { LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "Temporary ACCH overpower: %s\n", lchan->top_acch_active ? "inactive => active" : "active => inactive"); } } static bool data_is_rr_meas_rep(const uint8_t *data) { const struct gsm48_hdr *gh = (void *)(data + 5); const uint8_t *lapdm_hdr = (void *)(data + 2); /* LAPDm address field: SAPI=0, C/R=0, EA=1 */ if (lapdm_hdr[0] != 0x01) return false; /* LAPDm control field: U, func=UI */ if (lapdm_hdr[1] != 0x03) return false; /* Protocol discriminator: RR */ if (gh->proto_discr != GSM48_PDISC_RR) return false; switch (gh->msg_type) { case GSM48_MT_RR_EXT_MEAS_REP: case GSM48_MT_RR_MEAS_REP: return true; default: return false; } } /* Called every time a SACCH block is received from lower layers */ void lchan_meas_handle_sacch(struct gsm_lchan *lchan, struct msgb *msg) { const struct gsm48_meas_res *mr = NULL; const struct gsm48_hdr *gh = NULL; int timing_offset, rc; bool dtxu_used = true; /* safe default assumption */ uint8_t ms_pwr; uint8_t ms_ta; int8_t ul_rssi; int16_t ul_ci_cb; uint8_t *l3; unsigned int l3_len; if (msgb_l2len(msg) == GSM_MACBLOCK_LEN) { /* Some brilliant engineer decided that the ordering of * fields on the Um interface is different from the * order of fields in RSL. See 3GPP TS 44.004 (section 7.2) * vs. 3GPP TS 48.058 (section 9.3.10). */ const struct gsm_sacch_l1_hdr *l1h = msgb_l2(msg); lchan->meas.l1_info.ms_pwr = l1h->ms_pwr; lchan->meas.l1_info.fpc_epc = l1h->fpc_epc; lchan->meas.l1_info.srr_sro = l1h->srr_sro; lchan->meas.l1_info.ta = l1h->ta; lchan->meas.flags |= LC_UL_M_F_L1_VALID; /* Check if this is a Measurement Report */ if (data_is_rr_meas_rep(msgb_l2(msg))) { /* Skip both L1 SACCH and LAPDm headers */ msg->l3h = (void *)(msg->l2h + 2 + 3); gh = msgb_l3(msg); } ms_pwr = lchan->meas.l1_info.ms_pwr; ms_ta = lchan->meas.l1_info.ta; } else { lchan->meas.flags &= ~LC_UL_M_F_L1_VALID; ms_pwr = lchan->ms_power_ctrl.current; ms_ta = lchan->ta_ctrl.current; } timing_offset = ms_to_valid(lchan) ? ms_to2rsl(lchan, ms_ta) : -1; l3 = msgb_l3(msg); l3_len = l3 ? msgb_l3len(msg) : 0; rc = rsl_tx_meas_res(lchan, l3, l3_len, timing_offset); if (rc == 0) /* Count successful transmissions */ lchan->meas.res_nr++; /* Run control loops now that we have all the information: */ /* 3GPP TS 45.008 sec 4.2: UL L1 SACCH Header contains TA and * MS_PWR used "for the last burst of the previous SACCH * period". Since MS must use the values provided in DL SACCH * starting at next meas period, the value of the "last burst" * is actually the value used in the entire meas period. Since * it contains info about the previous meas period, we want to * feed the Control Loop with the measurements for the same * period (the previous one), which is stored in lchan->meas(.ul_res): */ if (gh && gh->msg_type == GSM48_MT_RR_MEAS_REP) { mr = (const struct gsm48_meas_res *)gh->data; if (gsm48_meas_res_is_valid(mr)) dtxu_used = mr->dtx_used; } if (dtxu_used) { ul_rssi = rxlev2dbm(lchan->meas.ul_res.sub.rx_lev); ul_ci_cb = lchan->meas.ul_ci_cb_sub; } else { ul_rssi = rxlev2dbm(lchan->meas.ul_res.full.rx_lev); ul_ci_cb = lchan->meas.ul_ci_cb_full; } lchan_ms_ta_ctrl(lchan, ms_ta, lchan->meas.ms_toa256); lchan_ms_pwr_ctrl(lchan, ms_pwr, ul_rssi, ul_ci_cb); if (mr && gsm48_meas_res_is_valid(mr)) { lchan_bs_pwr_ctrl(lchan, mr); acch_overpower_active_decision(lchan, mr); } repeated_dl_facch_active_decision(lchan, mr); /* Reset state for next iteration */ lchan->tch.dtx.dl_active = false; lchan->meas.flags &= ~LC_UL_M_F_OSMO_EXT_VALID; lchan->ms_t_offs = -1; lchan->p_offs = -1; }