aboutsummaryrefslogtreecommitdiffstats
path: root/src/common/measurement.c
blob: b58698720e11248f517c9ffbfaf114c0eccaf574 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
#include <stdint.h>
#include <errno.h>

#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/core/utils.h>

#include <osmo-bts/gsm_data.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/measurement.h>
#include <osmo-bts/scheduler.h>
#include <osmo-bts/rsl.h>
#include <osmo-bts/ta_control.h>

/* Tables as per TS 45.008 Section 8.3 */
static const uint8_t ts45008_83_tch_f[] = { 52, 53, 54, 55, 56, 57, 58, 59 };
static const uint8_t ts45008_83_tch_hs0[] = { 0, 2, 4, 6, 52, 54, 56, 58 };
static const uint8_t ts45008_83_tch_hs1[] = { 14, 16, 18, 20, 66, 68, 70, 72 };

/* 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,
	.c_i = 0,
	.is_sub = 0,
	.inv_rssi = MEASUREMENT_DUMMY_IRSSI
};

/* find out if an array contains a given key as element */
#define ARRAY_CONTAINS(arr, val) array_contains(arr, ARRAY_SIZE(arr), val)
static bool array_contains(const uint8_t *arr, unsigned int len, uint8_t val) {
	int i;
	for (i = 0; i < len; i++) {
		if (arr[i] == val)
			return true;
	}
	return false;
}

/* 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 only implement the logic for Voice, not CSD */

	switch (lchan->type) {
	case GSM_LCHAN_TCH_F:
		switch (lchan->tch_mode) {
		case GSM48_CMODE_SPEECH_V1:
		case GSM48_CMODE_SPEECH_EFR:
			if (trx_sched_is_sacch_fn(lchan->ts, fn, true))
				return true;
			if (ARRAY_CONTAINS(ts45008_83_tch_f, fn104))
				return true;
			break;
		case GSM48_CMODE_SPEECH_AMR:
			if (trx_sched_is_sacch_fn(lchan->ts, fn, true))
				return true;
			break;
		case GSM48_CMODE_SIGN:
			/* No DTX allowed; SUB=FULL, therefore measurements at all frame numbers are
			 * SUB */
			return true;
		default:
			LOGPFN(DMEAS, LOGL_ERROR, fn, "%s: Unsupported lchan->tch_mode %u\n",
				gsm_lchan_name(lchan), lchan->tch_mode);
			break;
		}
		break;
	case GSM_LCHAN_TCH_H:
		switch (lchan->tch_mode) {
		case GSM48_CMODE_SPEECH_V1:
			if (trx_sched_is_sacch_fn(lchan->ts, fn, true))
				return true;
			switch (lchan->nr) {
			case 0:
				if (ARRAY_CONTAINS(ts45008_83_tch_hs0, fn104))
					return true;
				break;
			case 1:
				if (ARRAY_CONTAINS(ts45008_83_tch_hs1, fn104))
					return true;
				break;
			default:
				OSMO_ASSERT(0);
			}
			break;
		case GSM48_CMODE_SPEECH_AMR:
			if (trx_sched_is_sacch_fn(lchan->ts, fn, true))
				return true;
			break;
		case GSM48_CMODE_SIGN:
			/* No DTX allowed; SUB=FULL, therefore measurements at all frame numbers are
			 * SUB */
			return true;
		default:
			LOGPFN(DMEAS, LOGL_ERROR, fn, "%s: Unsupported lchan->tch_mode %u\n",
				gsm_lchan_name(lchan), 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) {
		DEBUGP(DMEAS,
		       "%s meas period end fn:%u, fn_mod:%i, status:%d, pchan:%s\n",
		       gsm_lchan_name(lchan), fn, 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) {
		LOGPFN(DMEAS, LOGL_NOTICE, fn,
		       "%s measurement during state: %s, num_ul_meas=%d, fn_mod=%u\n",
		       gsm_lchan_name(lchan), gsm_lchans_name(lchan->state),
		       lchan->meas.num_ul_meas, fn_mod);
	}

	if (lchan->meas.num_ul_meas >= ARRAY_SIZE(lchan->meas.uplink)) {
		LOGPFN(DMEAS, LOGL_NOTICE, fn,
		       "%s no space for uplink measurement, num_ul_meas=%d, fn_mod=%u\n",
		       gsm_lchan_name(lchan), 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);

	DEBUGPFN(DMEAS, fn, "%s adding measurement (ber10k=%u, ta_offs=%d, ci=%0.2f, is_sub=%u, rssi=-%u), num_ul_meas=%d, fn_mod=%u\n",
		 gsm_lchan_name(lchan), ulm->ber10k, ulm->ta_offs_256bits,
		 ulm->c_i, dest->is_sub, 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) */
static unsigned int lchan_meas_sub_num_expected(const struct gsm_lchan *lchan)
{
	enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts);

	/* AMR is using a more elaborated model with a dymanic amount of
	 * DTX blocks so this function is not applicable to determine the
	 * amount of expected SUB Measurements when AMR is used */
	OSMO_ASSERT(lchan->tch_mode != GSM48_CMODE_SPEECH_AMR);

	switch (pchan) {
	case GSM_PCHAN_TCH_F:
		if (lchan->tch_mode == GSM48_CMODE_SIGN) {
			/* 1 block SACCH, 24 blocks TCH (see note 1) */
			return 25;
		} else {
			/* 1 block SACCH, 2 blocks TCH */
			return 3;
		}
	case GSM_PCHAN_TCH_H:
		if (lchan->tch_mode == GSM48_CMODE_SIGN) {
			/* 1 block SACCH, 12 blocks TCH (see ynote 1) */
			return 13;
		} else {
			/* 1 block SACCH, 4 blocks TCH */
			return 5;
		}
	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;
	}

	/* Note 1: In signalling mode all blocks count as SUB blocks. */
}

/* 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;
	uint32_t ber_sub_sum = 0;
	uint32_t irssi_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;
	unsigned 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);

	if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR)
		num_meas_sub_expect = lchan_meas_sub_num_expected(lchan);
	else {
		/* When AMR is used, we expect at least one SUB frame, since
		 * the SACCH will always be SUB frame. There may occur more
		 * SUB frames but since DTX periods in AMR are dynamic, we
		 * can not know how much exactly. */
		num_meas_sub_expect = 1;
	}

	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;
				num_meas_sub_actual++;
				is_sub = true;
			}
			irssi_full_sum += m->inv_rssi;
			ta256b_sum += m->ta_offs_256bits;

			num_ul_meas_actual++;
		} else {
			m = &measurement_dummy;

			/* For AMR the amount of SUB frames is defined by the
			 * the occurrence of DTX periods, which are dynamically
			 * negotiated in AMR, so we can not know if and how many
			 * SUB frames are missing. */
			if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR) {
				if (num_meas_sub <= i) {
					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;
		}
	}

	if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR) {
		LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG,
			  "Received UL measurements contain %u SUB measurements, expected %u\n",
			  num_meas_sub_actual, num_meas_sub_expect);
	} else {
		LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG,
			  "Received UL measurements contain %u SUB measurements, expected at least %u\n",
			  num_meas_sub_actual, num_meas_sub_expect);
	}

	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 (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR) {
		if (num_meas_sub != num_meas_sub_expect) {
			LOGPLCHAN(lchan, DMEAS, LOGL_ERROR,
				  "Incorrect number of SUB measurements detected! "
				  "(%u vs exp %u)\n", num_meas_sub, num_meas_sub_expect);
		}
	} else {
		if (num_meas_sub < num_meas_sub_expect) {
			LOGPLCHAN(lchan, DMEAS, LOGL_ERROR,
				  "Incorrect number of SUB measurements detected! "
				  "(%u vs exp >=%u)\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;
	else
		ta256b_sum = ta256b_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;
	else
		irssi_sub_sum = irssi_sub_sum / num_meas_sub_actual;

	LOGPLCHAN(lchan, DMEAS, LOGL_INFO,
		  "Computed TA256(% 4d) BER-FULL(%2u.%02u%%), RSSI-FULL(-%3udBm), "
		  "BER-SUB(%2u.%02u%%), RSSI-SUB(-%3udBm)\n",
		  ta256b_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 */
	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;

	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);

	/* Compute new ta_req value. This has to be done here since the value
	 * in lchan->meas.num_ul_meas together with lchan->meas.ms_toa256
	 * is needed for the computation. */
	lchan_ms_ta_ctrl(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;
}