l1: Store measurement values sent by the MS

This commit extends the pcu_l1_meas structure by MS side measurement
values which are transmitted by PACKET DOWNLINK ACK/NACK and
PACKET RESOURCE REQUEST messages. The encoded values are remapped to
dB respectively % values. The values are stored in the corresponding
MS object (if there is one).

Note that the values are store as (rounded) integers, so some
different encodings are mapped to the same decoded value.

Sponsored-by: On-Waves ehf
diff --git a/src/bts.cpp b/src/bts.cpp
index f94799b..5fafd84 100644
--- a/src/bts.cpp
+++ b/src/bts.cpp
@@ -790,11 +790,62 @@
 		"at no request\n");
 }
 
+static void get_rx_qual_meas(struct pcu_l1_meas *meas, uint8_t rx_qual_enc)
+{
+	static const int16_t rx_qual_map[] = {
+		0, /* 0,14 % */
+		0, /* 0,28 % */
+		1, /* 0,57 % */
+		1, /* 1,13 % */
+		2, /* 2,26 % */
+		5, /* 4,53 % */
+		9, /* 9,05 % */
+		18, /* 18,10 % */
+	};
+
+	meas->set_ms_rx_qual(rx_qual_map[
+		OSMO_MIN(rx_qual_enc, ARRAY_SIZE(rx_qual_map)-1)
+		]);
+}
+
+static void get_meas(struct pcu_l1_meas *meas,
+	const Packet_Resource_Request_t *qr)
+{
+	unsigned i;
+
+	meas->set_ms_c_value(qr->C_VALUE);
+	if (qr->Exist_SIGN_VAR)
+		meas->set_ms_sign_var((qr->SIGN_VAR + 2) / 4); /* SIGN_VAR * 0.25 dB */
+
+	for (i = 0; i < OSMO_MIN(ARRAY_SIZE(qr->Slot), ARRAY_SIZE(meas->ts)); i++)
+	{
+		if (qr->Slot[i].Exist)
+			meas->set_ms_i_level(i, -2 * qr->Slot[i].I_LEVEL);
+	}
+}
+
+static void get_meas(struct pcu_l1_meas *meas,
+	const Channel_Quality_Report_t *qr)
+{
+	unsigned i;
+
+	get_rx_qual_meas(meas, qr->RXQUAL);
+	meas->set_ms_c_value(qr->C_VALUE);
+	meas->set_ms_sign_var((qr->SIGN_VAR + 2) / 4); /* SIGN_VAR * 0.25 dB */
+
+	for (i = 0; i < OSMO_MIN(ARRAY_SIZE(qr->Slot), ARRAY_SIZE(meas->ts)); i++)
+	{
+		if (qr->Slot[i].Exist)
+			meas->set_ms_i_level(i, -2 * qr->Slot[i].I_LEVEL_TN);
+	}
+}
+
 void gprs_rlcmac_pdch::rcv_control_dl_ack_nack(Packet_Downlink_Ack_Nack_t *ack_nack, uint32_t fn)
 {
 	int8_t tfi = 0; /* must be signed */
 	struct gprs_rlcmac_dl_tbf *tbf;
 	int rc;
+	struct pcu_l1_meas meas;
 
 	tfi = ack_nack->DOWNLINK_TFI;
 	tbf = bts()->dl_tbf_by_poll_fn(fn, trx_no(), ts_no);
@@ -841,6 +892,11 @@
 		/* schedule uplink assignment */
 		tbf->ul_ass_state = GPRS_RLCMAC_UL_ASS_SEND_ASS;
 	}
+	/* get measurements */
+	if (tbf->ms()) {
+		get_meas(&meas, &ack_nack->Channel_Quality_Report);
+		tbf->ms()->update_l1_meas(&meas);
+	}
 }
 
 void gprs_rlcmac_pdch::rcv_resource_request(Packet_Resource_Request_t *request, uint32_t fn)
@@ -853,6 +909,7 @@
 		uint32_t tlli = request->ID.u.TLLI;
 		uint8_t ms_class = 0;
 		uint8_t ta = 0;
+		struct pcu_l1_meas meas;
 
 		GprsMs *ms = bts()->ms_by_tlli(tlli);
 		/* Keep the ms, even if it gets idle temporarily */
@@ -907,6 +964,12 @@
 		ul_tbf->control_ts = ts_no;
 		/* schedule uplink assignment */
 		ul_tbf->ul_ass_state = GPRS_RLCMAC_UL_ASS_SEND_ASS;
+
+		/* get measurements */
+		if (ul_tbf->ms()) {
+			get_meas(&meas, request);
+			ul_tbf->ms()->update_l1_meas(&meas);
+		}
 		return;
 	}
 
@@ -933,7 +996,6 @@
 			"RX: [PCU <- BTS] %s FIXME: Packet resource request\n",
 			tbf_name(ul_tbf));
 	}
-
 }
 
 void gprs_rlcmac_pdch::rcv_measurement_report(Packet_Measurement_Report_t *report, uint32_t fn)
diff --git a/src/gprs_ms.cpp b/src/gprs_ms.cpp
index cc21171..fe560e8 100644
--- a/src/gprs_ms.cpp
+++ b/src/gprs_ms.cpp
@@ -421,6 +421,7 @@
 {
 	struct gprs_rlcmac_bts *bts_data;
 	uint8_t max_cs_ul = 4;
+	unsigned i;
 
 	OSMO_ASSERT(m_bts != NULL);
 	bts_data = m_bts->bts_data();
@@ -464,4 +465,20 @@
 		m_l1_meas.set_ber(meas->ber);
 	if (meas->have_link_qual)
 		m_l1_meas.set_link_qual(meas->link_qual);
+
+	if (meas->have_ms_rx_qual)
+		m_l1_meas.set_ms_rx_qual(meas->ms_rx_qual);
+	if (meas->have_ms_c_value)
+		m_l1_meas.set_ms_c_value(meas->ms_c_value);
+	if (meas->have_ms_sign_var)
+		m_l1_meas.set_ms_sign_var(meas->ms_sign_var);
+
+	if (meas->have_ms_i_level) {
+		for (i = 0; i < ARRAY_SIZE(meas->ts); ++i) {
+			if (meas->ts[i].have_ms_i_level)
+				m_l1_meas.set_ms_i_level(i, meas->ts[i].ms_i_level);
+			else
+				m_l1_meas.ts[i].have_ms_i_level = 0;
+		}
+	}
 }
diff --git a/src/pcu_l1_if.h b/src/pcu_l1_if.h
index 88c8399..59b9cba 100644
--- a/src/pcu_l1_if.h
+++ b/src/pcu_l1_if.h
@@ -37,16 +37,42 @@
  * L1 Measurement values
  */
 
+struct pcu_l1_meas_ts {
+	unsigned have_ms_i_level:1;
+
+	int16_t ms_i_level; /* I_LEVEL in dB */
+
+#ifdef __cplusplus
+	pcu_l1_meas_ts& set_ms_i_level(int16_t v) {
+		ms_i_level = v; have_ms_i_level = 1; return *this;
+	}
+
+	pcu_l1_meas_ts() :
+		have_ms_i_level(0)
+	{}
+#endif
+};
+
 struct pcu_l1_meas {
 	unsigned have_rssi:1;
 	unsigned have_ber:1;
 	unsigned have_bto:1;
 	unsigned have_link_qual:1;
+	unsigned have_ms_rx_qual:1;
+	unsigned have_ms_c_value:1;
+	unsigned have_ms_sign_var:1;
+	unsigned have_ms_i_level:1;
 
 	int8_t rssi; /* RSSI in dBm */
 	uint8_t ber; /* Bit error rate in % */
 	int16_t bto; /* Burst timing offset in quarter bits */
-	int16_t link_qual; /* Link quality in db */
+	int16_t link_qual; /* Link quality in dB */
+	int16_t ms_rx_qual; /* MS RXQUAL value in % */
+	int16_t ms_c_value; /* C value in dB */
+	int16_t ms_sign_var; /* SIGN_VAR in dB */
+
+	struct pcu_l1_meas_ts ts[8];
+
 #ifdef __cplusplus
 	pcu_l1_meas& set_rssi(int8_t v) { rssi = v; have_rssi = 1; return *this;}
 	pcu_l1_meas& set_ber(uint8_t v) { ber = v; have_ber = 1; return *this;}
@@ -54,11 +80,27 @@
 	pcu_l1_meas& set_link_qual(int16_t v) {
 		link_qual = v; have_link_qual = 1; return *this;
 	}
+	pcu_l1_meas& set_ms_rx_qual(int16_t v) {
+		ms_rx_qual = v; have_ms_rx_qual = 1; return *this;
+	}
+	pcu_l1_meas& set_ms_c_value(int16_t v) {
+		ms_c_value = v; have_ms_c_value = 1; return *this;
+	}
+	pcu_l1_meas& set_ms_sign_var(int16_t v) {
+		ms_sign_var = v; have_ms_sign_var = 1; return *this;
+	}
+	pcu_l1_meas& set_ms_i_level(size_t idx, int16_t v) {
+		ts[idx].set_ms_i_level(v); have_ms_i_level = 1; return *this;
+	}
 	pcu_l1_meas() :
 		have_rssi(0),
 		have_ber(0),
 		have_bto(0),
-		have_link_qual(0)
+		have_link_qual(0),
+		have_ms_rx_qual(0),
+		have_ms_c_value(0),
+		have_ms_sign_var(0),
+		have_ms_i_level(0)
 	{}
 #endif
 };
diff --git a/src/pcu_vty_functions.cpp b/src/pcu_vty_functions.cpp
index bf4843f..4f54e8e 100644
--- a/src/pcu_vty_functions.cpp
+++ b/src/pcu_vty_functions.cpp
@@ -58,6 +58,8 @@
 
 static int show_ms(struct vty *vty, GprsMs *ms)
 {
+	unsigned i;
+
 	vty_out(vty, "MS TLLI=%08x, IMSI=%s%s", ms->tlli(), ms->imsi(), VTY_NEWLINE);
 	vty_out(vty, "  Timing advance (TA):    %d%s", ms->ta(), VTY_NEWLINE);
 	vty_out(vty, "  Coding scheme uplink:   CS-%d%s", ms->current_cs_ul(),
@@ -79,6 +81,20 @@
 	if (ms->l1_meas()->have_bto)
 		vty_out(vty, "  Burst timing offset:    %d/4 bit%s",
 			ms->l1_meas()->bto, VTY_NEWLINE);
+	if (ms->l1_meas()->have_ms_rx_qual)
+		vty_out(vty, "  MS RX quality:          %d %%%s",
+			ms->l1_meas()->ms_rx_qual, VTY_NEWLINE);
+	if (ms->l1_meas()->have_ms_c_value)
+		vty_out(vty, "  MS C value:             %d dB%s",
+			ms->l1_meas()->ms_c_value, VTY_NEWLINE);
+	if (ms->l1_meas()->have_ms_sign_var)
+		vty_out(vty, "  MS SIGN variance:       %d dB%s",
+			ms->l1_meas()->ms_sign_var, VTY_NEWLINE);
+	for (i = 0; i < ARRAY_SIZE(ms->l1_meas()->ts); ++i) {
+		if (ms->l1_meas()->ts[i].have_ms_i_level)
+			vty_out(vty, "  MS I level (slot %d):    %d dB%s",
+				i, ms->l1_meas()->ts[i].ms_i_level, VTY_NEWLINE);
+	}
 	if (ms->ul_tbf())
 		vty_out(vty, "  Uplink TBF:             TFI=%d, state=%s%s",
 			ms->ul_tbf()->tfi(),