From 76bfbc819397d15764c4fcb7b578f53f5bef7c37 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Sun, 19 Sep 2010 15:49:48 +0200 Subject: [PATCH] target/fw/layer1: Add initial version of TCH primitives The initial bringup is mainly Dieter Spaar's work. I took the logic and rewrote it, adapting to later scheduler changes and adding support for several other things (tch_mode, initial HR support, various cleanup, ...). Initially-Written-by: Dieter Spaar Signed-off-by: Sylvain Munaut --- src/target/firmware/include/layer1/prim.h | 3 + src/target/firmware/include/layer1/sync.h | 4 +- src/target/firmware/layer1/Makefile | 2 +- src/target/firmware/layer1/prim_tch.c | 598 ++++++++++++++++++++++ 4 files changed, 605 insertions(+), 2 deletions(-) create mode 100644 src/target/firmware/layer1/prim_tch.c diff --git a/src/target/firmware/include/layer1/prim.h b/src/target/firmware/include/layer1/prim.h index e9823de..c8e49a6 100644 --- a/src/target/firmware/include/layer1/prim.h +++ b/src/target/firmware/include/layer1/prim.h @@ -26,4 +26,7 @@ void l1a_rach_req(uint8_t fn51, uint8_t mf_off, uint8_t ra); extern const struct tdma_sched_item nb_sched_set[]; extern const struct tdma_sched_item nb_sched_set_ul[]; +extern const struct tdma_sched_item tch_sched_set[]; +extern const struct tdma_sched_item tch_a_sched_set[]; + #endif /* _L1_PRIM_H */ diff --git a/src/target/firmware/include/layer1/sync.h b/src/target/firmware/include/layer1/sync.h index f60966f..06ce6a0 100644 --- a/src/target/firmware/include/layer1/sync.h +++ b/src/target/firmware/include/layer1/sync.h @@ -34,6 +34,7 @@ enum l1_compl { L1_COMPL_FB, L1_COMPL_RACH, L1_COMPL_TX_NB, + L1_COMPL_TX_TCH, }; typedef void l1_compl_cb(enum l1_compl c); @@ -75,8 +76,9 @@ struct l1s_state { int8_t ta; uint8_t tx_power; - /* TCH mode */ + /* TCH */ uint8_t tch_mode; + uint8_t tch_sync; /* Transmit queues of pending packets for main DCCH and ACCH */ struct llist_head tx_queue[_NUM_L1S_CHAN]; diff --git a/src/target/firmware/layer1/Makefile b/src/target/firmware/layer1/Makefile index d6bcbff..4f834ea 100644 --- a/src/target/firmware/layer1/Makefile +++ b/src/target/firmware/layer1/Makefile @@ -5,5 +5,5 @@ layer1_SRCS=avg.c agc.c afc.c sync.c tdma_sched.c tpu_window.c init.c l23_api.c mframe_sched.c sched_gsmtime.c async.c rfch.c apc.c layer1_SRCS += prim_pm.c prim_rach.c prim_tx_nb.c prim_rx_nb.c prim_fbsb.c \ - prim_freq.c prim_utils.c + prim_freq.c prim_utils.c prim_tch.c diff --git a/src/target/firmware/layer1/prim_tch.c b/src/target/firmware/layer1/prim_tch.c new file mode 100644 index 0000000..4a3b363 --- /dev/null +++ b/src/target/firmware/layer1/prim_tch.c @@ -0,0 +1,598 @@ +/* Layer 1 - TCH */ + +/* (C) 2010 by Dieter Spaar + * (C) 2010 by Sylvain Munaut + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +/* This computes various parameters both for the DSP and for + * our logic. Not all are used all the time, but it's easier + * to build all in one place */ +static void tch_get_params(struct gsm_time *time, uint8_t chan_nr, + uint32_t *fn_report, uint8_t *tch_f_hn, + uint8_t *tch_sub, uint8_t *tch_mode) +{ + uint8_t tn = chan_nr & 0x07; + uint8_t cbits = chan_nr >> 3; + + *tch_f_hn = (cbits & 2) ? 0 : 1; + + if (*tch_f_hn) { + *fn_report = (time->fn - (tn * 13) + 104) % 104; + *tch_sub = 0; + } else { + uint8_t chan_sub = cbits & 1; + uint8_t tn_report = (tn & ~1) | chan_sub; + *fn_report = (time->fn - (tn_report * 13) + 104) % 104; + *tch_sub = chan_sub; + } + + if (tch_mode) { + switch (l1s.tch_mode) { + case GSM48_CMODE_SPEECH_V1: + *tch_mode = *tch_f_hn ? TCH_FS_MODE : TCH_HS_MODE; + break; + case GSM48_CMODE_SPEECH_EFR: + *tch_mode = *tch_f_hn ? TCH_EFR_MODE : SIG_ONLY_MODE; + break; + default: + *tch_mode = SIG_ONLY_MODE; + } + } +} + + +/* ------------------------------------------------------------------------- + * Shared completion handler + * ------------------------------------------------------------------------- */ + +/* + * FIXME We really need a better way to handle completion, where we can + * pass arguments and such ... + * + * Right now, we just 'hope' it gets processed before the next one ... + */ + +static uint16_t last_tx_tch_fn; + +static void l1a_tx_tch_compl(__unused enum l1_compl c) +{ + struct msgb *msg; + + msg = l1_create_l2_msg(L1CTL_DATA_CONF, last_tx_tch_fn, 0, 0); + l1_queue_for_l2(msg); +} + +static __attribute__ ((constructor)) void prim_tch_init(void) +{ + l1s.completion[L1_COMPL_TX_TCH] = &l1a_tx_tch_compl; +} + + +/* ------------------------------------------------------------------------- + * TCH: Voice & FACCH + * ------------------------------------------------------------------------- */ + +/* + * Voice and FACCH data are spread in various ways depending on a lot of + * factors. Trying to handle that with the mframe scheduler is just a mess, + * so we schedule it burst by burst and handle the complex logic inside the + * primitive task code itself. + */ + + +#define FACCH_MEAS_HIST 8 /* Up to 8 bursts history */ +struct l1s_rx_tch_state { + struct l1s_meas_hdr meas[FACCH_MEAS_HIST]; +}; + +static struct l1s_rx_tch_state rx_tch; + + +static int l1s_tch_resp(__unused uint8_t p1, __unused uint8_t p2, uint16_t p3) +{ + static uint8_t meas_id = 0; + uint8_t mf_task_id = p3 & 0xff; + struct gsm_time rx_time; + uint8_t chan_nr; + uint16_t arfcn; + uint8_t tsc, tn; + uint8_t tch_f_hn, tch_sub; + uint32_t fn_report; + int facch_rx_now, traffic_rx_now; + + /* Get/compute various parameters */ + gsm_fn2gsmtime(&rx_time, (l1s.current_time.fn - 1 + GSM_MAX_FN) % GSM_MAX_FN); + rfch_get_params(&rx_time, &arfcn, &tsc, &tn); + chan_nr = mframe_task2chan_nr(mf_task_id, tn); + tch_get_params(&rx_time, chan_nr, &fn_report, &tch_f_hn, &tch_sub, NULL); + + meas_id = (meas_id + 1) % FACCH_MEAS_HIST; /* absolute value doesn't matter */ + + /* Collect measurements */ + rx_tch.meas[meas_id].toa_qbit = dsp_api.db_r->a_serv_demod[D_TOA]; + rx_tch.meas[meas_id].pm_dbm8 = + agc_inp_dbm8_by_pm(dsp_api.db_r->a_serv_demod[D_PM] >> 3); + rx_tch.meas[meas_id].freq_err = + ANGLE_TO_FREQ(dsp_api.db_r->a_serv_demod[D_ANGLE]); + rx_tch.meas[meas_id].snr = dsp_api.db_r->a_serv_demod[D_SNR]; + + /* feed computed frequency error into AFC loop */ + if (rx_tch.meas[meas_id].snr > AFC_SNR_THRESHOLD) + afc_input(rx_tch.meas[meas_id].freq_err, arfcn, 1); + else + afc_input(rx_tch.meas[meas_id].freq_err, arfcn, 0); + + /* Tell the RF frontend to set the gain appropriately */ + rffe_set_gain(rx_tch.meas[meas_id].pm_dbm8 / 8, CAL_DSP_TGT_BB_LVL); + + /* FACCH Block end ? */ + if (tch_f_hn) { + /* FACCH/F: B0(0...7),B1(4...11),B2(8...11,0...3) (mod 13) */ + facch_rx_now = ((rx_time.fn % 13) % 4) == 3; + } else { + /* FAACH/H: See GSM 05.02 Clause 7 Table 1of9 */ + uint8_t t2_norm = rx_time.t2 - tch_sub; + facch_rx_now = (t2_norm == 15) || + (t2_norm == 23) || + (t2_norm == 6); + } + + if (facch_rx_now && (dsp_api.ndb->a_fd[0] & (1<chan_nr = chan_nr; + dl->link_id = 0x00; /* FACCH */ + dl->band_arfcn = htons(arfcn); + dl->frame_nr = htonl(rx_time.fn); + + /* Average SNR & RX level */ + n = tch_f_hn ? 8 : 6; + for (i=0; isnr = avg_snr / n; + dl->rx_level = (avg_dbm8 / (8*n)) + 110; + + /* Errors & CRC status */ + num_biterr = dsp_api.ndb->a_fd[2] & 0xffff; + if (num_biterr > 0xff) + dl->num_biterr = 0xff; + else + dl->num_biterr = num_biterr; + + dl->fire_crc = ((dsp_api.ndb->a_fd[0] & 0xffff) & ((1 << B_FIRE1) | (1 << B_FIRE0))) >> B_FIRE0; + + /* Update rx level for pm report */ + pu_update_rx_level(dl->rx_level); + + /* Copy actual data, skipping the information block [0,1,2] */ + dsp_memcpy_from_api(di->data, &dsp_api.ndb->a_fd[3], 23, 0); + + /* Give message to up layer */ + l1_queue_for_l2(msg); + + skip: + /* Reset A_FD header (needed by DSP) */ + /* B_FIRE1 =1, B_FIRE0 =0 , BLUD =0 */ + dsp_api.ndb->a_fd[0] = (1<a_fd[2] = 0xffff; + + /* Reset A_DD_0 header in NDB (needed by DSP) */ + dsp_api.ndb->a_dd_0[0] = 0; + dsp_api.ndb->a_dd_0[2] = 0xffff; + + /* Reset A_DD_1 header in NDB (needed by DSP) */ + dsp_api.ndb->a_dd_1[0] = 0; + dsp_api.ndb->a_dd_1[2] = 0xffff; + } + + /* Traffic now ? */ + if (tch_f_hn) { + /* TCH/F: B0(0...7),B1(4...11),B2(8...11,0...3) (mod 13)*/ + traffic_rx_now = ((rx_time.fn % 13) % 4) == 3; + } else { + /* TCH/H0: B0(0,2,4,6),B1(4,6,8,10),B2(8,10,0,2) (mod 13) */ + /* H1: B0(1,3,5,7),B1(5,7,9,11),B2(9,11,1,3) (mod 13) */ + traffic_rx_now = (((rx_time.fn - tch_sub + 13) % 13) % 4) == 2; + } + + if (traffic_rx_now) { + volatile uint16_t *traffic_buf; + + traffic_buf = tch_sub ? dsp_api.ndb->a_dd_1 : dsp_api.ndb->a_dd_0; + + if (traffic_buf[0] & (1<a_fu; + struct msgb *msg; + const uint8_t *data; + + /* Pull FACCH data (if ready) */ + if (icnt > 26) + msg = msgb_dequeue(&l1s.tx_queue[L1S_CHAN_MAIN]); + else + msg = NULL; + + /* If TX is empty and we're signalling only, use dummy frame */ + if (msg) + data = msg->l3h; + else if (tch_mode == SIG_ONLY_MODE) + data = pu_get_idle_frame(); + else + data = NULL; + + /* Do we really send something ? */ + if (data) { + /* Fill data block header */ + info_ptr[0] = (1 << B_BLUD); /* 1st word: Set B_BLU bit. */ + info_ptr[1] = 0; /* 2nd word: cleared. */ + info_ptr[2] = 0; /* 3nd word: cleared. */ + + /* Copy the actual data after the header */ + dsp_memcpy_to_api(&info_ptr[3], data, 23, 0); + } + + /* Indicate completion (FIXME: early but easier this way for now) */ + if (msg) { + last_tx_tch_fn = l1s.next_time.fn; + l1s_compl_sched(L1_COMPL_TX_TCH); + } + + /* Free msg now that we're done with it */ + if (msg) + msgb_free(msg); + } + + /* Configure DSP for TX/RX */ + l1s_tx_apc_helper(arfcn); + + dsp_load_tch_param( + &l1s.next_time, + tch_mode, tch_f_hn ? TCH_F : TCH_H, tch_sub, + 0, sync, tn + ); + + dsp_load_rx_task(TCHT_DSP_TASK, 0, tsc); /* burst_id unused for TCH */ + l1s_rx_win_ctrl(arfcn, L1_RXWIN_NB, 0); + + dsp_load_tx_task(TCHT_DSP_TASK, 0, tsc); /* burst_id unused for TCH */ + l1s_tx_win_ctrl(arfcn, L1_TXWIN_NB, 0, 3); + + return 0; +} + + +const struct tdma_sched_item tch_sched_set[] = { + SCHED_ITEM_DT(l1s_tch_cmd, 0, 0, 0), SCHED_END_FRAME(), + SCHED_END_FRAME(), + SCHED_ITEM(l1s_tch_resp, 0, 0, -4), SCHED_END_FRAME(), + SCHED_END_SET() +}; + + +/* ------------------------------------------------------------------------- + * TCH: SACCH + * ------------------------------------------------------------------------- */ + +/* + * SACCH data are spread over 4 bursts, however they are so far appart that + * we can't use the normal scheduler to schedule all them at once in a single + * set. + * Therefore, the task code itself decides in which burst it is, if it's the + * start/end, and act appropriately. + */ + + +struct l1s_rx_tch_a_state { + struct l1s_meas_hdr meas[4]; + + struct msgb *msg; + struct l1ctl_info_dl *dl; + struct l1ctl_data_ind *di; +}; + +static struct l1s_rx_tch_a_state rx_tch_a; + + +static int l1s_tch_a_resp(__unused uint8_t p1, __unused uint8_t p2, uint16_t p3) +{ + uint8_t mf_task_id = p3 & 0xff; + struct gsm_time rx_time; + uint8_t chan_nr; + uint16_t arfcn; + uint8_t tsc, tn; + uint8_t tch_f_hn, tch_sub; + uint32_t fn_report; + uint8_t burst_id; + + /* It may happen we've never gone through cmd(0) yet, skip until then */ + if (!rx_tch_a.msg) + goto skip; + + /* Get/compute various parameters */ + gsm_fn2gsmtime(&rx_time, (l1s.current_time.fn - 1 + GSM_MAX_FN) % GSM_MAX_FN); + rfch_get_params(&rx_time, &arfcn, &tsc, &tn); + chan_nr = mframe_task2chan_nr(mf_task_id, tn); + tch_get_params(&rx_time, chan_nr, &fn_report, &tch_f_hn, &tch_sub, NULL); + burst_id = (fn_report - 12) / 26; + + /* Collect measurements */ + rx_tch_a.meas[burst_id].toa_qbit = dsp_api.db_r->a_serv_demod[D_TOA]; + rx_tch_a.meas[burst_id].pm_dbm8 = + agc_inp_dbm8_by_pm(dsp_api.db_r->a_serv_demod[D_PM] >> 3); + rx_tch_a.meas[burst_id].freq_err = + ANGLE_TO_FREQ(dsp_api.db_r->a_serv_demod[D_ANGLE]); + rx_tch_a.meas[burst_id].snr = dsp_api.db_r->a_serv_demod[D_SNR]; + + /* feed computed frequency error into AFC loop */ + if (rx_tch_a.meas[burst_id].snr > AFC_SNR_THRESHOLD) + afc_input(rx_tch_a.meas[burst_id].freq_err, arfcn, 1); + else + afc_input(rx_tch_a.meas[burst_id].freq_err, arfcn, 0); + + /* Tell the RF frontend to set the gain appropriately */ + rffe_set_gain(rx_tch_a.meas[burst_id].pm_dbm8 / 8, CAL_DSP_TGT_BB_LVL); + + /* Last burst, read data & send to the up layer */ + if ((burst_id == 3) && (dsp_api.ndb->a_cd[0] & (1<snr = avg_snr / 4; + rx_tch_a.dl->rx_level = (avg_dbm8 / (8*4)) + 110; + + num_biterr = dsp_api.ndb->a_cd[2]; + if (num_biterr > 0xff) + rx_tch_a.dl->num_biterr = 0xff; + else + rx_tch_a.dl->num_biterr = num_biterr; + + rx_tch_a.dl->fire_crc = ((dsp_api.ndb->a_cd[0] & 0xffff) & ((1 << B_FIRE1) | (1 << B_FIRE0))) >> B_FIRE0; + + /* Update rx level for pm report */ + pu_update_rx_level(rx_tch_a.dl->rx_level); + + /* Copy actual data, skipping the information block [0,1,2] */ + dsp_memcpy_from_api(rx_tch_a.di->data, &dsp_api.ndb->a_cd[3], 23, 0); + + /* Give message to up layer */ + l1_queue_for_l2(rx_tch_a.msg); + rx_tch_a.msg = NULL; rx_tch_a.dl = NULL; rx_tch_a.di = NULL; + + /* Reset header */ + dsp_api.ndb->a_cd[0] = (1<a_cd[2] = 0xffff; + } + +skip: + /* mark READ page as being used */ + dsp_api.r_page_used = 1; + + return 0; +} + +static int l1s_tch_a_cmd(__unused uint8_t p1, __unused uint8_t p2, uint16_t p3) +{ + uint8_t mf_task_id = p3 & 0xff; + uint8_t chan_nr; + uint16_t arfcn; + uint8_t tsc, tn; + uint8_t tch_f_hn, tch_sub; + uint32_t fn_report; + uint8_t burst_id; + + /* Get/compute various parameters */ + rfch_get_params(&l1s.next_time, &arfcn, &tsc, &tn); + chan_nr = mframe_task2chan_nr(mf_task_id, tn); + tch_get_params(&l1s.next_time, chan_nr, &fn_report, &tch_f_hn, &tch_sub, NULL); + burst_id = (fn_report - 12) / 26; + + /* Load SACCH data if we start a new burst */ + if (burst_id == 0) { + uint16_t *info_ptr = dsp_api.ndb->a_cu; + struct msgb *msg; + const uint8_t *data; + + /* If the TX queue is empty, send dummy measurement */ + msg = msgb_dequeue(&l1s.tx_queue[L1S_CHAN_SACCH]); + data = msg ? msg->l3h : pu_get_meas_frame(); + + /* Fill data block header */ + info_ptr[0] = (1 << B_BLUD); /* 1st word: Set B_BLU bit. */ + info_ptr[1] = 0; /* 2nd word: cleared. */ + info_ptr[2] = 0; /* 3nd word: cleared. */ + + /* Copy the actual data after the header */ + dsp_memcpy_to_api(&info_ptr[3], data, 23, 0); + + /* Indicate completion (FIXME: early but easier this way for now) */ + if (msg) { + last_tx_tch_fn = l1s.next_time.fn; + l1s_compl_sched(L1_COMPL_TX_TCH); + } + + /* Free msg now that we're done with it */ + if (msg) + msgb_free(msg); + } + + /* Allocate RX burst */ + if (burst_id == 0) { + /* Clear 'dangling' msgb */ + if (rx_tch_a.msg) { + /* Can happen if the task was shutdown in the middle of + * 4 bursts ... */ + msgb_free(rx_tch_a.msg); + } + + /* Allocate burst */ + /* FIXME: we actually want all allocation out of L1S! */ + rx_tch_a.msg = l1ctl_msgb_alloc(L1CTL_DATA_IND); + if (!rx_tch_a.msg) + printf("tch_a_cmd(0): unable to allocate msgb\n"); + + rx_tch_a.dl = (struct l1ctl_info_dl *) msgb_put(rx_tch_a.msg, sizeof(*rx_tch_a.dl)); + rx_tch_a.di = (struct l1ctl_data_ind *) msgb_put(rx_tch_a.msg, sizeof(*rx_tch_a.di)); + + /* Pre-fill DL header with some info about burst(0) */ + rx_tch_a.dl->chan_nr = chan_nr; + rx_tch_a.dl->link_id = 0x40; /* SACCH */ + rx_tch_a.dl->band_arfcn = htons(arfcn); + rx_tch_a.dl->frame_nr = htonl(l1s.next_time.fn); + } + + /* Configure DSP for TX/RX */ + l1s_tx_apc_helper(arfcn); + + dsp_load_tch_param( + &l1s.next_time, + SIG_ONLY_MODE, tch_f_hn ? TCH_F : TCH_H, tch_sub, + 0, 0, tn + ); + + dsp_load_rx_task(TCHA_DSP_TASK, 0, tsc); /* burst_id unused for TCHA */ + l1s_rx_win_ctrl(arfcn, L1_RXWIN_NB, 0); + + dsp_load_tx_task(TCHA_DSP_TASK, 0, tsc); /* burst_id unused for TCHA */ + l1s_tx_win_ctrl(arfcn, L1_TXWIN_NB, 0, 3); + + return 0; +} + + +const struct tdma_sched_item tch_a_sched_set[] = { + SCHED_ITEM_DT(l1s_tch_a_cmd, 0, 0, 0), SCHED_END_FRAME(), + SCHED_END_FRAME(), + SCHED_ITEM(l1s_tch_a_resp, 0, 0, -4), SCHED_END_FRAME(), + SCHED_END_SET() +}; -- 2.20.1