From 7d06c78b37599a6e5ad13d002ea17b1eff26177f Mon Sep 17 00:00:00 2001 From: Vadim Yanitskiy Date: Mon, 13 Aug 2018 04:48:14 +0700 Subject: [PATCH] trxcon/scheduler: add TCH/H channel support Change-Id: Ibb2a0850692c5ff86b13b820af10b12085589e67 --- src/host/trxcon/sched_lchan_desc.c | 11 +- src/host/trxcon/sched_lchan_tchh.c | 319 +++++++++++++++++++++++++++++ src/host/trxcon/sched_trx.c | 2 +- src/host/trxcon/sched_trx.h | 6 +- 4 files changed, 331 insertions(+), 7 deletions(-) diff --git a/src/host/trxcon/sched_lchan_desc.c b/src/host/trxcon/sched_lchan_desc.c index 37d12730e..4cac43940 100644 --- a/src/host/trxcon/sched_lchan_desc.c +++ b/src/host/trxcon/sched_lchan_desc.c @@ -27,10 +27,7 @@ /* TODO: implement */ #define tx_pdtch_fn NULL -#define tx_tchh_fn NULL - #define rx_pdtch_fn NULL -#define rx_tchh_fn NULL /* Forward declaration of handlers */ int rx_data_fn(struct trx_instance *trx, struct trx_ts *ts, @@ -54,6 +51,14 @@ int rx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts, int tx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts, struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid); +int rx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts, + struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid, + sbit_t *bits, int8_t rssi, int16_t toa256); + +int tx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts, + struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid); + + const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = { { TRXC_IDLE, "IDLE", diff --git a/src/host/trxcon/sched_lchan_tchh.c b/src/host/trxcon/sched_lchan_tchh.c index 316f995b6..7fb2809d7 100644 --- a/src/host/trxcon/sched_lchan_tchh.c +++ b/src/host/trxcon/sched_lchan_tchh.c @@ -3,6 +3,7 @@ * TDMA scheduler: handlers for DL / UL bursts on logical channels * * (C) 2018 by Vadim Yanitskiy + * (C) 2018 by Harald Welte * * All Rights Reserved * @@ -23,11 +24,27 @@ */ #include +#include #include #include +#include +#include +#include + +#include +#include + +#include +#include + +#include "l1ctl_proto.h" #include "scheduler.h" #include "sched_trx.h" +#include "logging.h" +#include "trx_if.h" +#include "trxcon.h" +#include "l1ctl.h" static const uint8_t tch_h0_traffic_block_map[3][4] = { /* B0(0,2,4,6), B1(4,6,8,10), B2(8,10,0,2) */ @@ -181,3 +198,305 @@ uint32_t sched_tchh_block_dl_first_fn(enum trx_lchan_type chan, /* Couldn't calculate the first fn, return the last */ return last_fn; } + +int rx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts, + struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid, + sbit_t *bits, int8_t rssi, int16_t toa256) +{ + const struct trx_lchan_desc *lchan_desc; + int n_errors = -1, n_bits_total, rc; + sbit_t *buffer, *offset; + uint8_t l2[128], *mask; + size_t l2_len; + + /* Set up pointers */ + lchan_desc = &trx_lchan_desc[lchan->type]; + mask = &lchan->rx_burst_mask; + buffer = lchan->rx_bursts; + + LOGP(DSCHD, LOGL_DEBUG, "Traffic received on %s: fn=%u ts=%u bid=%u\n", + lchan_desc->name, fn, ts->index, bid); + + if (*mask == 0x00) { + /* Align to the first burst */ + if (bid > 0) + return 0; + + /* Align reception of the first FACCH/H frame */ + if (lchan->tch_mode == GSM48_CMODE_SIGN) { + if (!sched_tchh_facch_start(lchan->type, fn, 0)) + return 0; + } else { /* or TCH/H traffic frame */ + if (!sched_tchh_traffic_start(lchan->type, fn, 0)) + return 0; + } + } + + /* Update mask */ + *mask |= (1 << bid); + + /** + * FIXME: properly update measurements + * + * Since TCH/H channel is using block-diagonal interleaving, + * a single burst may carry 57 bits of one encoded frame, + * and 57 bits of another. This should be taken into account. + */ + lchan->meas.rssi_sum += rssi; + lchan->meas.toa256_sum += toa256; + lchan->meas.rssi_num++; + lchan->meas.toa256_num++; + + /* Copy burst to the end of buffer of 6 bursts */ + offset = buffer + bid * 116 + 464; + memcpy(offset, bits + 3, 58); + memcpy(offset + 58, bits + 87, 58); + + /* Wait until the second burst */ + if (bid != 1) + return 0; + + /* Wait for complete set of bursts */ + if (lchan->tch_mode == GSM48_CMODE_SIGN) { + /* FACCH/H is interleaved over 6 bursts */ + if ((*mask & 0x3f) != 0x3f) + goto bfi_shift; + } else { + /* Traffic is interleaved over 4 bursts */ + if ((*mask & 0x0f) != 0x0f) + goto bfi_shift; + } + + /* Skip decoding attempt in case of FACCH/H */ + if (lchan->dl_ongoing_facch) { + lchan->dl_ongoing_facch = false; + goto bfi_shift; /* 2/2 BFI */ + } + + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + case GSM48_CMODE_SPEECH_V1: /* HR */ + rc = gsm0503_tch_hr_decode(l2, buffer, + !sched_tchh_facch_end(lchan->type, fn, 0), + &n_errors, &n_bits_total); + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + /** + * TODO: AMR requires a dedicated loop, + * which will be implemented later... + */ + LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet\n"); + return -ENOTSUP; + default: + LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode); + return -EINVAL; + } + + /* Shift buffer by 4 bursts for interleaving */ + memcpy(buffer, buffer + 232, 232); + memcpy(buffer + 232, buffer + 464, 232); + + /* Shift burst mask */ + *mask = *mask << 2; + + /* Check decoding result */ + if (rc < 4) { + LOGP(DSCHD, LOGL_ERROR, "Received bad TCH frame ending at " + "fn=%u on %s (rc=%d)\n", fn, lchan_desc->name, rc); + + /* Send BFI */ + goto bfi; + } else if (rc == GSM_MACBLOCK_LEN) { + /* Skip decoding of the next 2 stolen bursts */ + lchan->dl_ongoing_facch = true; + + /* Calculate TDMA frame number of the first burst */ + lchan->rx_first_fn = sched_tchh_block_dl_first_fn(lchan->type, + fn, true); /* FACCH/H */ + + /* FACCH/H received, forward to the higher layers */ + sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN, + n_errors, false, false); + + /* 1/2 BFI */ + goto bfi; + } else { + /* A good TCH frame received */ + l2_len = rc; + } + + /* Calculate TDMA frame number of the first burst */ + lchan->rx_first_fn = sched_tchh_block_dl_first_fn(lchan->type, + fn, false); /* TCH/H */ + + /* Send a traffic frame to the higher layers */ + return sched_send_dt_ind(trx, ts, lchan, l2, l2_len, + n_errors, false, true); + +bfi_shift: + /* Shift buffer */ + memcpy(buffer, buffer + 232, 232); + memcpy(buffer + 232, buffer + 464, 232); + + /* Shift burst mask */ + *mask = *mask << 2; + +bfi: + /* Didn't try to decode */ + if (n_errors < 0) + n_errors = 116 * 2; + + /* Calculate TDMA frame number of the first burst */ + lchan->rx_first_fn = sched_tchh_block_dl_first_fn(lchan->type, + fn, false); /* TCH/H */ + + /* BFI is not applicable in signalling mode */ + if (lchan->tch_mode == GSM48_CMODE_SIGN) + return sched_send_dt_ind(trx, ts, lchan, NULL, 0, + n_errors, true, false); + + /* Bad frame indication */ + l2_len = sched_bad_frame_ind(l2, lchan); + + /* Send a BFI frame to the higher layers */ + return sched_send_dt_ind(trx, ts, lchan, l2, l2_len, + n_errors, true, true); +} + +int tx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts, + struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid) +{ + const struct trx_lchan_desc *lchan_desc; + ubit_t burst[GSM_BURST_LEN]; + ubit_t *buffer, *offset; + const uint8_t *tsc; + uint8_t *mask; + size_t l2_len; + int rc; + + /* Set up pointers */ + lchan_desc = &trx_lchan_desc[lchan->type]; + mask = &lchan->tx_burst_mask; + buffer = lchan->tx_bursts; + + if (bid > 0) { + /* Align to the first burst */ + if (*mask == 0x00) + return 0; + goto send_burst; + } + + if (*mask == 0x00) { + /* Align transmission of the first FACCH/H frame */ + if (lchan->tch_mode == GSM48_CMODE_SIGN) + if (!sched_tchh_facch_start(lchan->type, fn, 1)) + return 0; + } + + /* Shift buffer by 2 bursts back for interleaving */ + memcpy(buffer, buffer + 232, 232); + + /* Also shift TX burst mask */ + *mask = *mask << 2; + + /* If FACCH/H blocks are still pending */ + if (lchan->ul_facch_blocks > 2) { + memcpy(buffer + 232, buffer + 464, 232); + goto send_burst; + } + + /* Check the current TCH mode */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + case GSM48_CMODE_SPEECH_V1: /* HR */ + l2_len = GSM_HR_BYTES + 1; + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + /** + * TODO: AMR requires a dedicated loop, + * which will be implemented later... + */ + LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet, " + "dropping frame...\n"); + + /* Forget this primitive */ + sched_prim_drop(lchan); + return -ENOTSUP; + default: + LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u, " + "dropping frame...\n", lchan->tch_mode); + + /* Forget this primitive */ + sched_prim_drop(lchan); + return -EINVAL; + } + + /* Determine payload length */ + if (PRIM_IS_FACCH(lchan->prim)) { + l2_len = GSM_MACBLOCK_LEN; /* FACCH */ + } else if (lchan->prim->payload_len != l2_len) { + LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu " + "(expected %zu for TCH or %u for FACCH), so dropping...\n", + lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN); + + /* Forget this primitive */ + sched_prim_drop(lchan); + return -EINVAL; + } + + /* Encode the payload */ + rc = gsm0503_tch_hr_encode(buffer, lchan->prim->payload, l2_len); + if (rc) { + LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload\n"); + + /* Forget this primitive */ + sched_prim_drop(lchan); + return -EINVAL; + } + + /* A FACCH/H frame occupies 6 bursts */ + if (PRIM_IS_FACCH(lchan->prim)) + lchan->ul_facch_blocks = 6; + +send_burst: + /* Determine which burst should be sent */ + offset = buffer + bid * 116; + + /* Update mask */ + *mask |= (1 << bid); + + /* Choose proper TSC */ + tsc = sched_nb_training_bits[trx->tsc]; + + /* Compose a new burst */ + memset(burst, 0, 3); /* TB */ + memcpy(burst + 3, offset, 58); /* Payload 1/2 */ + memcpy(burst + 61, tsc, 26); /* TSC */ + memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */ + memset(burst + 145, 0, 3); /* TB */ + + LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n", + lchan_desc->name, fn, ts->index, bid); + + /* Forward burst to transceiver */ + sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst); + + /* In case of a FACCH/H frame, one block less */ + if (lchan->ul_facch_blocks) + lchan->ul_facch_blocks--; + + if ((*mask & 0x0f) == 0x0f) { + /** + * If no more FACCH/H blocks pending, + * confirm data / traffic sending + */ + if (!lchan->ul_facch_blocks) + sched_send_dt_conf(trx, ts, lchan, fn, + PRIM_IS_TCH(lchan->prim)); + + /* Forget processed primitive */ + sched_prim_drop(lchan); + } + + return 0; +} diff --git a/src/host/trxcon/sched_trx.c b/src/host/trxcon/sched_trx.c index e20da39f9..023764dc7 100644 --- a/src/host/trxcon/sched_trx.c +++ b/src/host/trxcon/sched_trx.c @@ -463,7 +463,7 @@ static void sched_trx_reset_lchan(struct trx_lchan_state *lchan) /* TCH specific variables */ if (CHAN_IS_TCH(lchan->type)) { lchan->dl_ongoing_facch = 0; - lchan->ul_ongoing_facch = 0; + lchan->ul_facch_blocks = 0; lchan->tch_mode = GSM48_CMODE_SIGN; diff --git a/src/host/trxcon/sched_trx.h b/src/host/trxcon/sched_trx.h index 2c8b4d8f2..08e24894b 100644 --- a/src/host/trxcon/sched_trx.h +++ b/src/host/trxcon/sched_trx.h @@ -171,9 +171,9 @@ struct trx_lchan_state { uint8_t tch_mode; /*! \brief FACCH/H on downlink */ - uint8_t dl_ongoing_facch; - /*! \brief FACCH/H on uplink */ - uint8_t ul_ongoing_facch; + bool dl_ongoing_facch; + /*! \brief pending FACCH/H blocks on Uplink */ + uint8_t ul_facch_blocks; struct { /*! \brief Number of RSSI values */