gsm0508: add functions to calculate beginning of a block

The calculation of the beginning of a block for TCH/F, TCH/H and FACCH
can be challenging since those channels are affected by the diagonal
interleaving of the TCH channels. However, GSM 05.02 Section 7 Table 1
of 5 specifies how the blocks are distributed over the TDMA frame
interval. Lets add a mapping function that is based on that table

Related: OS#3803
Change-Id: I3d71c66f8c401f5afbad9b1c86c24580dab9e0ce
diff --git a/src/gsm/gsm0502.c b/src/gsm/gsm0502.c
index 53259a4..1a71e61 100644
--- a/src/gsm/gsm0502.c
+++ b/src/gsm/gsm0502.c
@@ -27,6 +27,9 @@
 #include <osmocom/gsm/gsm0502.h>
 #include <osmocom/gsm/gsm48.h>
 #include <osmocom/gsm/rsl.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/logging.h>
+#include <inttypes.h>
 
 unsigned int
 gsm0502_calc_paging_group(struct gsm48_control_channel_descr *chan_desc, uint64_t imsi)
@@ -44,3 +47,157 @@
 
 	return group;
 }
+
+/* Clause 7 Table 1 of 5 Mapping of logical channels onto physical channels */
+#define TCH_REPEAT_LENGTH 13
+#define FACCH_F_REPEAT_LENGTH 13
+#define FACCH_H_REPEAT_LENGTH 26
+
+static const uint8_t gsm0502_tch_f_traffic_block_map[3][8] = {
+	{0, 1, 2, 3, 4, 5, 6, 7},
+	{4, 5, 6, 7, 8, 9, 10, 11},
+	{8, 9, 10, 11, 0, 1, 2, 3}
+};
+
+static const uint8_t gsm0502_tch_h0_traffic_block_map[3][4] = {
+	{0, 2, 4, 6},
+	{4, 6, 8, 10},
+	{8, 10, 0, 2}
+};
+
+static const uint8_t gsm0502_tch_h1_traffic_block_map[3][4] = {
+	{1, 3, 5, 7},
+	{5, 7, 9, 11},
+	{9, 11, 1, 3}
+};
+
+static const uint8_t gsm0502_tch_f_facch_block_map[3][8] = {
+	{0, 1, 2, 3, 4, 5, 6, 7},
+	{4, 5, 6, 7, 8, 9, 10, 11},
+	{8, 9, 10, 11, 0, 1, 2, 3}
+};
+
+static const uint8_t gsm0502_tch_h0_facch_block_map[3][6] = {
+	{0, 2, 4, 6, 8, 10},
+	{8, 10, 13, 15, 17, 19},
+	{17, 19, 21, 23, 0, 2}
+};
+
+static const uint8_t gsm0502_tch_h1_facch_block_map[3][6] = {
+	{1, 3, 5, 7, 9, 11},
+	{9, 11, 14, 16, 18, 20},
+	{18, 20, 22, 24, 1, 3}
+};
+
+/* Struct to describe a remapping function for block frame nbumbers. The member
+ * blockend describes the ending of a block for which we want to determine the
+ * beginning frame number. The member distance describes the value we need to
+ * subtract from the blockend frame number in order to get the beginning of the
+ * the block. The member cycle describes the Repeat length in TDMA frames we
+ * are dealing with. For traffic channels this is always 13, for control
+ * channels it is different. The member len simply defines amount of
+ * blockendings and distances we store in the remap table */
+struct fn_remap_table {
+	unsigned int cycle;
+	unsigned int len;
+	uint8_t blockend[8];
+	uint8_t distance[8];
+};
+
+/* Memory to hold the remap tables we will automatically generate on startup */
+static struct fn_remap_table tch_f_remap_table;
+static struct fn_remap_table tch_h0_remap_table;
+static struct fn_remap_table tch_h1_remap_table;
+static struct fn_remap_table facch_f_remap_table;
+static struct fn_remap_table facch_h0_remap_table;
+static struct fn_remap_table facch_h1_remap_table;
+static struct fn_remap_table *fn_remap_table_ptr[FN_REMAP_MAX];
+
+/* Generate a remap table from a given block map. A block map lists the block
+ * layout as defined in GSM 05.02, Clause 7 Table 1 of 5, one block per row.
+ * Parameters:
+ *   table: name of the remap table to output
+ *   map: traffic block map input
+ *   rows: length of the traffic block map
+ *   cols: witdh of the traffic block map
+ *   repeat: repeat length in TDMA frames (cycle) */
+#define fn_remap_table_from_traffic_block_map(table, map, rows, cols, repeat) \
+	for(i=0;i<rows;i++) { \
+		table.blockend[i] = map[i][cols-1]; \
+		if(map[i][0] <= map[i][cols-1]) \
+			table.distance[i] = map[i][cols-1] - map[i][0]; \
+		else \
+			table.distance[i] = repeat - map[i][0] + map[i][cols-1]; \
+	} \
+	table.cycle = repeat; \
+	table.len = rows;
+
+/* Automatically generate fn remap tables on startupmake */
+static __attribute__ ((constructor))
+void fn_remap_tables_build(void)
+{
+	/* Required by macro */
+	unsigned int i;
+
+	/* Generate tables */
+	fn_remap_table_from_traffic_block_map(tch_f_remap_table,
+					      gsm0502_tch_f_traffic_block_map, 3, 8,
+					      TCH_REPEAT_LENGTH);
+	fn_remap_table_from_traffic_block_map(tch_h0_remap_table,
+					      gsm0502_tch_h0_traffic_block_map, 3, 4,
+					      TCH_REPEAT_LENGTH);
+	fn_remap_table_from_traffic_block_map(tch_h1_remap_table,
+					      gsm0502_tch_h1_traffic_block_map, 3, 4,
+					      TCH_REPEAT_LENGTH);
+	fn_remap_table_from_traffic_block_map(facch_f_remap_table,
+					      gsm0502_tch_f_facch_block_map, 3, 8,
+					      FACCH_F_REPEAT_LENGTH);
+	fn_remap_table_from_traffic_block_map(facch_h0_remap_table,
+					      gsm0502_tch_h0_facch_block_map, 3, 6,
+					      FACCH_H_REPEAT_LENGTH);
+	fn_remap_table_from_traffic_block_map(facch_h1_remap_table,
+					      gsm0502_tch_h1_facch_block_map, 3, 6,
+					      FACCH_H_REPEAT_LENGTH);
+
+	fn_remap_table_ptr[FN_REMAP_TCH_F] = &tch_f_remap_table;
+	fn_remap_table_ptr[FN_REMAP_TCH_H0] = &tch_h0_remap_table;
+	fn_remap_table_ptr[FN_REMAP_TCH_H1] = &tch_h1_remap_table;
+	fn_remap_table_ptr[FN_REMAP_FACCH_F] = &facch_f_remap_table;
+	fn_remap_table_ptr[FN_REMAP_FACCH_H0] = &facch_h0_remap_table;
+	fn_remap_table_ptr[FN_REMAP_FACCH_H1] = &facch_h1_remap_table;
+}
+
+/*! Calculate the frame number of the beginning of a block.
+ *  \param[in] fn frame number of the block ending.
+ *  \param[in] channel channel type (see also enum fn_remap_channel).
+ *  \returns frame number of the beginning of the block or input frame number if
+ *           remapping was not possible. */
+uint32_t gsm0502_fn_remap(uint32_t fn, enum gsm0502_fn_remap_channel channel)
+{
+	uint8_t fn_cycle;
+	uint8_t i;
+	int sub = -1;
+	uint32_t fn_map;
+	struct fn_remap_table *table;
+
+	OSMO_ASSERT(channel < ARRAY_SIZE(fn_remap_table_ptr));
+        table = fn_remap_table_ptr[(uint8_t)channel];
+
+	fn_cycle = fn % table->cycle;
+
+	for (i = 0; i < table->len; i++) {
+		if (table->blockend[i] == fn_cycle) {
+			sub = table->distance[i];
+			break;
+		}
+	}
+
+	if (sub == -1) {
+		LOGP(DLGLOBAL, LOGL_ERROR, "could not remap frame number!, fn=%"PRIu32"\n", fn);
+		return fn;
+	}
+
+	fn_map = (fn + GSM_MAX_FN - sub) % GSM_MAX_FN;
+
+	return fn_map;
+}