diff options
Diffstat (limited to 'src/target/firmware/layer1')
23 files changed, 5208 insertions, 0 deletions
diff --git a/src/target/firmware/layer1/Makefile b/src/target/firmware/layer1/Makefile new file mode 100644 index 00000000..455a444a --- /dev/null +++ b/src/target/firmware/layer1/Makefile @@ -0,0 +1,9 @@ + +LIBRARIES+=layer1 +layer1_DIR=layer1 +layer1_SRCS=avg.c agc.c afc.c toa.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_tch.c + diff --git a/src/target/firmware/layer1/afc.c b/src/target/firmware/layer1/afc.c new file mode 100644 index 00000000..a51a1071 --- /dev/null +++ b/src/target/firmware/layer1/afc.c @@ -0,0 +1,130 @@ +/* AFC (Automatic Frequency Correction) Implementation */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdint.h> +#include <stdio.h> + +#include <debug.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <layer1/afc.h> +#include <layer1/avg.h> +#include <calypso/dsp.h> + +#define AFC_INITIAL_DAC_VALUE -700 + +/* Over how many TDMA frames do we want to average? (this may change in dedicated mode) */ +#define AFC_PERIOD 40 +/* How many of our measurements have to be valid? */ +#define AFC_MIN_MUN_VALID 8 + +/* The actual AFC code */ + +struct afc_state { + struct running_avg ravg; /* running average */ + int16_t dac_value; /* current DAC output value */ + uint16_t arfcn; +}; + +static void afc_ravg_output(struct running_avg *ravg, int32_t avg); + +static struct afc_state afc_state = { + .ravg = { + .outfn = &afc_ravg_output, + .period = AFC_PERIOD, + .min_valid = AFC_MIN_MUN_VALID, + }, + .dac_value = AFC_INITIAL_DAC_VALUE, +}; + +/* The AFC DAC in the ABB has to be configured as follows: + * DAC = 1MHz / 947MHz * FreqErr(Hz) / AFCslop(ppm/LSB) + * where: + * 947 MHz is the center of EGSM + * AFCslope is coded F1.15, thus a normalization factor of 2^15 applies + */ + +#define AFC_NORM_FACTOR_GSM ((1<<15) / 947) +#define AFC_NORM_FACTOR_DCS ((1<<15) / 1894) + +/* we assume 8.769ppb per LSB, equals 0.008769 * 32768 == 287 */ +//#define AFC_SLOPE 320 +#define AFC_SLOPE 287 + +/* The DSP can measure the frequency error in the following ranges: + * FB_MODE0: +/- 20 kHz + * FB_MODE1: +/- 4 kHz + * Sync Burst: +/- 1 kHz + * Normal Burst: +/- 400 Hz + */ + +/* Update the AFC with a frequency error, bypassing averaging */ +void afc_correct(int16_t freq_error, uint16_t arfcn) +{ + int32_t afc_norm_factor; + int16_t delta; + + switch (gsm_arfcn2band(arfcn)) { + case GSM_BAND_900: + case GSM_BAND_850: + afc_norm_factor = AFC_NORM_FACTOR_GSM; + break; + default: + afc_norm_factor = AFC_NORM_FACTOR_DCS; + } + + delta = (int16_t) ((afc_norm_factor * (int32_t)freq_error) / AFC_SLOPE); + printd("afc_correct(error=%dHz, arfcn=%u): delta=%d, afc_dac(old=%d,new=%d)\n", + freq_error, arfcn, delta, afc_state.dac_value, afc_state.dac_value+delta); + afc_state.dac_value += delta; + + /* The AFC DAC has only 13 bits */ + if (afc_state.dac_value > 4095) + afc_state.dac_value = 4095; + else if (afc_state.dac_value < -4096) + afc_state.dac_value = -4096; +} + +void afc_reset(void) +{ + afc_state.dac_value = AFC_INITIAL_DAC_VALUE; +} + +void afc_input(int32_t freq_error, uint16_t arfcn, int valid) +{ + afc_state.arfcn = arfcn; + runavg_input(&afc_state.ravg, freq_error, valid); + runavg_check_output(&afc_state.ravg); +} + +/* callback function for runavg */ +static void afc_ravg_output(struct running_avg *ravg, int32_t avg) +{ + afc_correct(avg, afc_state.arfcn); +} + +/* Update DSP with new AFC DAC value to be used for next TDMA frame */ +void afc_load_dsp(void) +{ + dsp_api.db_w->d_afc = afc_state.dac_value; + dsp_api.db_w->d_ctrl_abb |= (1 << B_AFC); +} diff --git a/src/target/firmware/layer1/agc.c b/src/target/firmware/layer1/agc.c new file mode 100644 index 00000000..b72a6e74 --- /dev/null +++ b/src/target/firmware/layer1/agc.c @@ -0,0 +1,62 @@ +/* AFC (Automatic Gain Control) Implementation */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdint.h> +#include <stdio.h> + +#include <osmocom/gsm/gsm_utils.h> +#include <debug.h> +#include <rffe.h> + +#include <layer1/agc.h> +#include <calypso/dsp.h> + +/* compute the input level present at the antenna based on a baseband + * power measurement of the DSP at baseband */ +int16_t agc_inp_dbm8_by_pm(int16_t pm) +{ + /* pm is in 1/8 dBm at baseband */ + int16_t total_gain_dbm8; + + /* compute total current gain */ + total_gain_dbm8 = (system_inherent_gain + rffe_get_gain()) * 8; + + /* subtract gain from power measurement at baseband level */ + return pm - total_gain_dbm8; +} + +uint8_t agc_il_by_dbm8(int16_t dbm8) +{ + uint16_t il; + + /* convert from 1/8 dBm to l1c format: [220..0] in -1/2dBm unit */ + if (dbm8 >= 0) + il = 0; + else + il = -dbm8; + + /* saturate */ + if (il > 4 * 255) + il = 4 * 255; + + return (uint8_t)(il >> 2); +} diff --git a/src/target/firmware/layer1/apc.c b/src/target/firmware/layer1/apc.c new file mode 100644 index 00000000..480c7607 --- /dev/null +++ b/src/target/firmware/layer1/apc.c @@ -0,0 +1,57 @@ +/* APC (Automatic Power Control) Implementation */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <errno.h> + +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <layer1/apc.h> + +/* calibration table defined in board file */ +extern const int16_t dbm2apc_gsm900[]; +extern const int dbm2apc_gsm900_max; + + +/* determine the AUXAPC value by the Tx Power Level */ +int16_t apc_tx_dbm2auxapc(enum gsm_band band, int8_t dbm) +{ + if (dbm < 0) + return -ERANGE; + + /* FIXME: offsets for different bands! */ + if (dbm > dbm2apc_gsm900_max) + dbm = dbm2apc_gsm900_max; + + return dbm2apc_gsm900[dbm]; +} + +/* determine the AUXAPC value by the Tx Power Level */ +int16_t apc_tx_pwrlvl2auxapc(enum gsm_band band, uint8_t lvl) +{ + /* convert tx power level to dBm */ + int dbm = ms_pwr_dbm(band, lvl); + if (dbm < 0) + return dbm; + + return apc_tx_dbm2auxapc(band, dbm); +} diff --git a/src/target/firmware/layer1/async.c b/src/target/firmware/layer1/async.c new file mode 100644 index 00000000..cb2a2a8c --- /dev/null +++ b/src/target/firmware/layer1/async.c @@ -0,0 +1,159 @@ +/* Asynchronous part of GSM Layer 1 */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdint.h> + +#include <debug.h> +#include <arm.h> +#include <asm/system.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> + +#include <layer1/sync.h> +#include <layer1/async.h> +#include <layer1/mframe_sched.h> +#include <layer1/sched_gsmtime.h> +#include <layer1/l23_api.h> +#include <calypso/l1_environment.h> + +extern const struct tdma_sched_item rach_sched_set_ul[]; + +/* safely enable a message into the L1S TX queue */ +void l1a_txq_msgb_enq(struct llist_head *queue, struct msgb *msg) +{ + unsigned long flags; + + local_firq_save(flags); + msgb_enqueue(queue, msg); + local_irq_restore(flags); +} + +void l1a_meas_msgb_set(struct msgb *msg) +{ + unsigned long flags; + + local_firq_save(flags); + if (l1s.tx_meas) + msgb_free(l1s.tx_meas); + l1s.tx_meas = msg; + local_irq_restore(flags); +} + +/* safely count messages in the L1S TX queue */ +int l1a_txq_msgb_count(struct llist_head *queue) +{ + unsigned long flags; + int num = 0; + struct llist_head *le; + + local_firq_save(flags); + llist_for_each(le, queue) + num++; + local_irq_restore(flags); + + return num; +} + +/* safely flush all pending msgb */ +void l1a_txq_msgb_flush(struct llist_head *queue) +{ + struct msgb *msg; + unsigned long flags; + + local_firq_save(flags); + while ((msg = msgb_dequeue(queue))) + msgb_free(msg); + local_irq_restore(flags); +} + +/* Enable a repeating multiframe task */ +void l1a_mftask_enable(enum mframe_task task) +{ + /* we don't need locking here as L1S only reads mframe.tasks */ + mframe_enable(task); +} + +/* Disable a repeating multiframe task */ +void l1a_mftask_disable(enum mframe_task task) +{ + /* we don't need locking here as L1S only reads mframe.tasks */ + mframe_disable(task); +} + +/* Set the mask for repeating multiframe tasks */ +void l1a_mftask_set(uint32_t tasks) +{ + /* we don't need locking here as L1S only reads mframe.tasks */ + mframe_set(tasks); +} + +/* Set TCH mode */ +uint8_t l1a_tch_mode_set(uint8_t mode) +{ + switch (mode) { + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + l1s.tch_mode = mode; + break; + default: + l1s.tch_mode = GSM48_CMODE_SIGN; + } + + return l1s.tch_mode; +} + +/* Set Audio routing mode */ +uint8_t l1a_audio_mode_set(uint8_t mode) +{ + l1s.audio_mode = mode; + return mode; +} + +/* Initialize asynchronous part of Layer1 */ +void l1a_init(void) +{ + l1a_l23api_init(); +} + +/* Execute pending L1A completions */ +void l1a_compl_execute(void) +{ + unsigned long flags; + unsigned int scheduled; + unsigned int i; + + /* get and reset the currently scheduled tasks */ + local_firq_save(flags); + scheduled = l1s.scheduled_compl; + l1s.scheduled_compl = 0; + local_irq_restore(flags); + + /* Iterate over list of scheduled completions, call their + * respective completion handler */ + for (i = 0; i < 32; i++) { + if (!(scheduled & (1 << i))) + continue; + /* call completion function */ + l1s.completion[i](i); + } +} diff --git a/src/target/firmware/layer1/avg.c b/src/target/firmware/layer1/avg.c new file mode 100644 index 00000000..a4bf565b --- /dev/null +++ b/src/target/firmware/layer1/avg.c @@ -0,0 +1,57 @@ +/* Averaging Implementation */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdint.h> + +#include <layer1/avg.h> + +/* input a new sample into the averaging process */ +void runavg_input(struct running_avg *ravg, int32_t val, int valid) +{ + ravg->num_samples++; + if (valid) { + ravg->acc_val += val; + ravg->num_samples_valid++; + } +} + +/* check if sufficient samples have been obtained, and call outfn() */ +int runavg_check_output(struct running_avg *ravg) +{ + if (ravg->num_samples < ravg->period) + return 0; + + if (ravg->num_samples_valid >= ravg->min_valid) { + int32_t avg = ravg->acc_val / ravg->num_samples_valid; + + ravg->outfn(ravg, avg); + + ravg->num_samples = ravg->num_samples_valid = 0; + ravg->acc_val = 0; + + return 1; + } + + return 0; +} + + diff --git a/src/target/firmware/layer1/init.c b/src/target/firmware/layer1/init.c new file mode 100644 index 00000000..e7fde232 --- /dev/null +++ b/src/target/firmware/layer1/init.c @@ -0,0 +1,73 @@ +/* OsmocomBB Layer1 initialization */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdint.h> +#include <stdio.h> + +#include <rffe.h> +#include <rf/trf6151.h> +#include <abb/twl3025.h> +#include <calypso/tpu.h> +#include <calypso/tsp.h> +#include <calypso/dsp.h> +#include <calypso/irq.h> + +#include <layer1/sync.h> +#include <layer1/async.h> +#include <layer1/l23_api.h> + +void layer1_init(void) +{ +#ifndef CONFIG_TX_ENABLE + printf("\n\nTHIS FIRMWARE WAS COMPILED WITHOUT TX SUPPORT!!!\n\n"); +#endif + + /* initialize asynchronous part of L1 */ + l1a_init(); + /* initialize TDMA Frame IRQ driven synchronous L1 */ + l1s_init(); + /* power up the DSP */ + dsp_power_on(); + + /* Initialize TPU, TSP and TRF drivers */ + tpu_init(); + tsp_init(); + + rffe_init(); + +#if 0 /* only if RX TPU window is disabled! */ + /* Put TWL3025 in downlink mode (includes calibration) */ + twl3025_downlink(1, 1000); +#endif + + /* issue the TRF and TWL initialization sequence */ + tpu_enq_sleep(); + tpu_enable(1); + tpu_wait_idle(); + + /* Disable RTC interrupt as it causes lost TDMA frames */ + irq_disable(IRQ_RTC_TIMER); + + /* inform l2 and upwards that we are ready for orders */ + l1ctl_tx_reset(L1CTL_RESET_IND, L1CTL_RES_T_BOOT); +} diff --git a/src/target/firmware/layer1/l23_api.c b/src/target/firmware/layer1/l23_api.c new file mode 100644 index 00000000..ae39e634 --- /dev/null +++ b/src/target/firmware/layer1/l23_api.c @@ -0,0 +1,690 @@ +/* Synchronous part of GSM Layer 1: API to Layer2+ */ + +/* (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org> + * + * 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. + * + */ + +#define DEBUG + +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include <debug.h> +#include <byteorder.h> + +#include <asm/system.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <comm/sercomm.h> + +#include <layer1/sync.h> +#include <layer1/async.h> +#include <layer1/mframe_sched.h> +#include <layer1/prim.h> +#include <layer1/tpu_window.h> +#include <layer1/sched_gsmtime.h> + +#include <abb/twl3025.h> +#include <rf/trf6151.h> +#include <calypso/sim.h> +#include <calypso/dsp.h> + +#include <l1ctl_proto.h> + +/* the size we will allocate struct msgb* for HDLC */ +#define L3_MSG_HEAD 4 +#define L3_MSG_DATA 200 +#define L3_MSG_SIZE (L3_MSG_HEAD + sizeof(struct l1ctl_hdr) + L3_MSG_DATA) + +void (*l1a_l23_tx_cb)(struct msgb *msg) = NULL; + +void l1_queue_for_l2(struct msgb *msg) +{ + if (l1a_l23_tx_cb) { + l1a_l23_tx_cb(msg); + return; + } + /* forward via serial for now */ + sercomm_sendmsg(SC_DLCI_L1A_L23, msg); +} + +enum mf_type { + MFNONE, + MF51, + MF26ODD, + MF26EVEN +}; +static uint32_t chan_nr2mf_task_mask(uint8_t chan_nr, uint8_t neigh_mode) +{ + uint8_t cbits = chan_nr >> 3; + uint8_t tn = chan_nr & 0x7; + uint8_t lch_idx; + enum mframe_task master_task = 0; + uint32_t neigh_task = 0; + enum mf_type multiframe; + + if (cbits == 0x01) { + lch_idx = 0; + master_task = (tn & 1) ? MF_TASK_TCH_F_ODD : MF_TASK_TCH_F_EVEN; + multiframe = (tn & 1) ? MF26ODD : MF26EVEN; + } else if ((cbits & 0x1e) == 0x02) { + lch_idx = cbits & 0x1; + master_task = MF_TASK_TCH_H_0 + lch_idx; + } else if ((cbits & 0x1c) == 0x04) { + lch_idx = cbits & 0x3; + master_task = MF_TASK_SDCCH4_0 + lch_idx; + multiframe = MF51; + } else if ((cbits & 0x18) == 0x08) { + lch_idx = cbits & 0x7; + master_task = MF_TASK_SDCCH8_0 + lch_idx; + multiframe = MF51; +#if 0 + } else if (cbits == 0x10) { + /* FIXME: when to do extended BCCH? */ + master_task = MF_TASK_BCCH_NORM; + } else if (cbits == 0x11 || cbits == 0x12) { + /* FIXME: how to decide CCCH norm/extd? */ + master_task = MF_TASK_BCCH_CCCH; +#endif + } + switch (neigh_mode) { + case NEIGH_MODE_PM: + switch (multiframe) { + case MF51: + neigh_task = (1 << MF_TASK_NEIGH_PM51); + break; + case MF26EVEN: + neigh_task = (1 << MF_TASK_NEIGH_PM26E); + break; + case MF26ODD: + neigh_task = (1 << MF_TASK_NEIGH_PM26O); + break; + } + break; + } + return (1 << master_task) | neigh_task; +} + +static int chan_nr2dchan_type(uint8_t chan_nr) +{ + uint8_t cbits = chan_nr >> 3; + + if (cbits == 0x01) { + return GSM_DCHAN_TCH_F; + } else if ((cbits & 0x1e) == 0x02) { + return GSM_DCHAN_TCH_H; + } else if ((cbits & 0x1c) == 0x04) { + return GSM_DCHAN_SDCCH_4; + } else if ((cbits & 0x18) == 0x08) { + return GSM_DCHAN_SDCCH_8; + } + return GSM_DCHAN_UNKNOWN; +} + +static int chan_nr_is_tch(uint8_t chan_nr) +{ + return ((chan_nr >> 3) == 0x01 || /* TCH/F */ + ((chan_nr >> 3) & 0x1e) == 0x02); /* TCH/H */ +} + +static void audio_set_enabled(uint8_t tch_mode, uint8_t audio_mode) +{ + if (tch_mode == GSM48_CMODE_SIGN) { + twl3025_unit_enable(TWL3025_UNIT_VUL, 0); + twl3025_unit_enable(TWL3025_UNIT_VDL, 0); + } else { + twl3025_unit_enable(TWL3025_UNIT_VUL, + !!(audio_mode & AUDIO_TX_MICROPHONE)); + twl3025_unit_enable(TWL3025_UNIT_VDL, + !!(audio_mode & AUDIO_RX_SPEAKER)); + } +} + +struct msgb *l1ctl_msgb_alloc(uint8_t msg_type) +{ + struct msgb *msg; + struct l1ctl_hdr *l1h; + + msg = msgb_alloc_headroom(L3_MSG_SIZE, L3_MSG_HEAD, "l1ctl"); + if (!msg) { + while (1) { + puts("OOPS. Out of buffers...\n"); + } + + return NULL; + } + l1h = (struct l1ctl_hdr *) msgb_put(msg, sizeof(*l1h)); + l1h->msg_type = msg_type; + l1h->flags = 0; + + msg->l1h = (uint8_t *)l1h; + + return msg; +} + +struct msgb *l1_create_l2_msg(int msg_type, uint32_t fn, uint16_t snr, + uint16_t arfcn) +{ + struct l1ctl_info_dl *dl; + struct msgb *msg = l1ctl_msgb_alloc(msg_type); + + dl = (struct l1ctl_info_dl *) msgb_put(msg, sizeof(*dl)); + dl->frame_nr = htonl(fn); + dl->snr = snr; + dl->band_arfcn = htons(arfcn); + + return msg; +} + +/* receive a L1CTL_FBSB_REQ from L23 */ +static void l1ctl_rx_fbsb_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; + struct l1ctl_fbsb_req *sync_req = (struct l1ctl_fbsb_req *) l1h->data; + + if (sizeof(*sync_req) > msg->len) { + printf("Short sync msg. %u\n", msg->len); + return; + } + + printd("L1CTL_FBSB_REQ (arfcn=%u, flags=0x%x)\n", + ntohs(sync_req->band_arfcn), sync_req->flags); + + /* reset scheduler and hardware */ + l1s_reset(); + + /* pre-set the CCCH mode */ + l1s.serving_cell.ccch_mode = sync_req->ccch_mode; + + printd("Starting FCCH Recognition\n"); + l1s_fbsb_req(1, sync_req); +} + +/* receive a L1CTL_DM_EST_REQ from L23 */ +static void l1ctl_rx_dm_est_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; + struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data; + struct l1ctl_dm_est_req *est_req = (struct l1ctl_dm_est_req *) ul->payload; + + printd("L1CTL_DM_EST_REQ (arfcn=%u, chan_nr=0x%02x, tsc=%u)\n", + ntohs(est_req->h0.band_arfcn), ul->chan_nr, est_req->tsc); + + /* disable neighbour cell measurement of C0 TS 0 */ + mframe_disable(MF_TASK_NEIGH_PM51_C0T0); + + /* configure dedicated channel state */ + l1s.dedicated.type = chan_nr2dchan_type(ul->chan_nr); + l1s.dedicated.tsc = est_req->tsc; + l1s.dedicated.tn = ul->chan_nr & 0x7; + l1s.dedicated.h = est_req->h; + + if (est_req->h) { + int i; + l1s.dedicated.h1.hsn = est_req->h1.hsn; + l1s.dedicated.h1.maio = est_req->h1.maio; + l1s.dedicated.h1.n = est_req->h1.n; + for (i=0; i<est_req->h1.n; i++) + l1s.dedicated.h1.ma[i] = ntohs(est_req->h1.ma[i]); + } else { + l1s.dedicated.h0.arfcn = ntohs(est_req->h0.band_arfcn); + } + + /* TCH config */ + if (chan_nr_is_tch(ul->chan_nr)) { + /* Mode */ + l1a_tch_mode_set(est_req->tch_mode); + l1a_audio_mode_set(est_req->audio_mode); + + /* Sync */ + l1s.tch_sync = 1; /* can be set without locking */ + + /* Audio path */ + audio_set_enabled(est_req->tch_mode, est_req->audio_mode); + } + + /* figure out which MF tasks to enable */ + l1a_mftask_set(chan_nr2mf_task_mask(ul->chan_nr, NEIGH_MODE_PM)); +} + +/* receive a L1CTL_DM_FREQ_REQ from L23 */ +static void l1ctl_rx_dm_freq_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; + struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data; + struct l1ctl_dm_freq_req *freq_req = + (struct l1ctl_dm_freq_req *) ul->payload; + + printd("L1CTL_DM_FREQ_REQ (arfcn=%u, tsc=%u)\n", + ntohs(freq_req->h0.band_arfcn), freq_req->tsc); + + /* configure dedicated channel state */ + l1s.dedicated.st_tsc = freq_req->tsc; + l1s.dedicated.st_h = freq_req->h; + + if (freq_req->h) { + int i; + l1s.dedicated.st_h1.hsn = freq_req->h1.hsn; + l1s.dedicated.st_h1.maio = freq_req->h1.maio; + l1s.dedicated.st_h1.n = freq_req->h1.n; + for (i=0; i<freq_req->h1.n; i++) + l1s.dedicated.st_h1.ma[i] = ntohs(freq_req->h1.ma[i]); + } else { + l1s.dedicated.st_h0.arfcn = ntohs(freq_req->h0.band_arfcn); + } + + l1a_freq_req(ntohs(freq_req->fn)); +} + +/* receive a L1CTL_CRYPTO_REQ from L23 */ +static void l1ctl_rx_crypto_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; + struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data; + struct l1ctl_crypto_req *cr = (struct l1ctl_crypto_req *) ul->payload; + uint8_t key_len = msg->len - sizeof(*l1h) - sizeof(*ul) - sizeof(*cr); + + printd("L1CTL_CRYPTO_REQ (algo=A5/%u, len=%u)\n", cr->algo, key_len); + + if (cr->algo && key_len != 8) { + printd("L1CTL_CRYPTO_REQ -> Invalid key\n"); + return; + } + + dsp_load_ciph_param(cr->algo, cr->key); +} + +/* receive a L1CTL_DM_REL_REQ from L23 */ +static void l1ctl_rx_dm_rel_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; + struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data; + + printd("L1CTL_DM_REL_REQ\n"); + l1a_mftask_set(0); + l1s.dedicated.type = GSM_DCHAN_NONE; + l1a_txq_msgb_flush(&l1s.tx_queue[L1S_CHAN_MAIN]); + l1a_txq_msgb_flush(&l1s.tx_queue[L1S_CHAN_SACCH]); + l1a_txq_msgb_flush(&l1s.tx_queue[L1S_CHAN_TRAFFIC]); + l1a_meas_msgb_set(NULL); + dsp_load_ciph_param(0, NULL); + l1a_tch_mode_set(GSM48_CMODE_SIGN); + audio_set_enabled(GSM48_CMODE_SIGN, 0); + l1s.neigh_pm.n = 0; +} + +/* receive a L1CTL_PARAM_REQ from L23 */ +static void l1ctl_rx_param_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; + struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data; + struct l1ctl_par_req *par_req = (struct l1ctl_par_req *) ul->payload; + + printd("L1CTL_PARAM_REQ (ta=%d, tx_power=%d)\n", par_req->ta, + par_req->tx_power); + + l1s.ta = par_req->ta; + l1s.tx_power = par_req->tx_power; +} + +/* receive a L1CTL_RACH_REQ from L23 */ +static void l1ctl_rx_rach_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; + struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data; + struct l1ctl_rach_req *rach_req = (struct l1ctl_rach_req *) ul->payload; + + printd("L1CTL_RACH_REQ (ra=0x%02x, offset=%d combined=%d)\n", + rach_req->ra, ntohs(rach_req->offset), rach_req->combined); + + l1a_rach_req(ntohs(rach_req->offset), rach_req->combined, + rach_req->ra); +} + +/* receive a L1CTL_DATA_REQ from L23 */ +static void l1ctl_rx_data_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; + struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data; + struct l1ctl_data_ind *data_ind = (struct l1ctl_data_ind *) ul->payload; + struct llist_head *tx_queue; + + printd("L1CTL_DATA_REQ (link_id=0x%02x)\n", ul->link_id); + + msg->l3h = data_ind->data; + if (ul->link_id & 0x40) { + struct gsm48_hdr *gh = (struct gsm48_hdr *)(data_ind->data + 5); + if (gh->proto_discr == GSM48_PDISC_RR + && gh->msg_type == GSM48_MT_RR_MEAS_REP) { + printd("updating measurement report\n"); + l1a_meas_msgb_set(msg); + return; + } + tx_queue = &l1s.tx_queue[L1S_CHAN_SACCH]; + } else + tx_queue = &l1s.tx_queue[L1S_CHAN_MAIN]; + + printd("ul=%p, ul->payload=%p, data_ind=%p, data_ind->data=%p l3h=%p\n", + ul, ul->payload, data_ind, data_ind->data, msg->l3h); + + l1a_txq_msgb_enq(tx_queue, msg); +} + +/* receive a L1CTL_PM_REQ from L23 */ +static void l1ctl_rx_pm_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; + struct l1ctl_pm_req *pm_req = (struct l1ctl_pm_req *) l1h->data; + + switch (pm_req->type) { + case 1: + l1s.pm.mode = 1; + l1s.pm.range.arfcn_start = + ntohs(pm_req->range.band_arfcn_from); + l1s.pm.range.arfcn_next = + ntohs(pm_req->range.band_arfcn_from); + l1s.pm.range.arfcn_end = + ntohs(pm_req->range.band_arfcn_to); + printf("L1CTL_PM_REQ start=%u end=%u\n", + l1s.pm.range.arfcn_start, l1s.pm.range.arfcn_end); + break; + } + l1s_reset_hw(); /* must reset, otherwise measurement results are delayed */ + l1s_pm_test(1, l1s.pm.range.arfcn_next); +} + +/* Transmit a L1CTL_RESET_IND or L1CTL_RESET_CONF */ +void l1ctl_tx_reset(uint8_t msg_type, uint8_t reset_type) +{ + struct msgb *msg = l1ctl_msgb_alloc(msg_type); + struct l1ctl_reset *reset_resp; + reset_resp = (struct l1ctl_reset *) + msgb_put(msg, sizeof(*reset_resp)); + reset_resp->type = reset_type; + + l1_queue_for_l2(msg); +} + +/* receive a L1CTL_RESET_REQ from L23 */ +static void l1ctl_rx_reset_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; + struct l1ctl_reset *reset_req = + (struct l1ctl_reset *) l1h->data; + + switch (reset_req->type) { + case L1CTL_RES_T_FULL: + printf("L1CTL_RESET_REQ: FULL!\n"); + l1s_reset(); + l1s_reset_hw(); + audio_set_enabled(GSM48_CMODE_SIGN, 0); + l1ctl_tx_reset(L1CTL_RESET_CONF, reset_req->type); + break; + case L1CTL_RES_T_SCHED: + printf("L1CTL_RESET_REQ: SCHED!\n"); + l1ctl_tx_reset(L1CTL_RESET_CONF, reset_req->type); + sched_gsmtime_reset(); + break; + default: + printf("unknown L1CTL_RESET_REQ type\n"); + break; + } +} + +/* Transmit a L1CTL_CCCH_MODE_CONF */ +static void l1ctl_tx_ccch_mode_conf(uint8_t ccch_mode) +{ + struct msgb *msg = l1ctl_msgb_alloc(L1CTL_CCCH_MODE_CONF); + struct l1ctl_ccch_mode_conf *mode_conf; + mode_conf = (struct l1ctl_ccch_mode_conf *) + msgb_put(msg, sizeof(*mode_conf)); + mode_conf->ccch_mode = ccch_mode; + + l1_queue_for_l2(msg); +} + +/* receive a L1CTL_CCCH_MODE_REQ from L23 */ +static void l1ctl_rx_ccch_mode_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; + struct l1ctl_ccch_mode_req *ccch_mode_req = + (struct l1ctl_ccch_mode_req *) l1h->data; + uint8_t ccch_mode = ccch_mode_req->ccch_mode; + + /* pre-set the CCCH mode */ + l1s.serving_cell.ccch_mode = ccch_mode; + + /* Update task */ + mframe_disable(MF_TASK_CCCH_COMB); + mframe_disable(MF_TASK_CCCH); + + if (ccch_mode == CCCH_MODE_COMBINED) + mframe_enable(MF_TASK_CCCH_COMB); + else if (ccch_mode == CCCH_MODE_NON_COMBINED) + mframe_enable(MF_TASK_CCCH); + + l1ctl_tx_ccch_mode_conf(ccch_mode); +} + +/* Transmit a L1CTL_TCH_MODE_CONF */ +static void l1ctl_tx_tch_mode_conf(uint8_t tch_mode, uint8_t audio_mode) +{ + struct msgb *msg = l1ctl_msgb_alloc(L1CTL_TCH_MODE_CONF); + struct l1ctl_tch_mode_conf *mode_conf; + mode_conf = (struct l1ctl_tch_mode_conf *) + msgb_put(msg, sizeof(*mode_conf)); + mode_conf->tch_mode = tch_mode; + mode_conf->audio_mode = audio_mode; + + l1_queue_for_l2(msg); +} + +/* receive a L1CTL_TCH_MODE_REQ from L23 */ +static void l1ctl_rx_tch_mode_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; + struct l1ctl_tch_mode_req *tch_mode_req = + (struct l1ctl_tch_mode_req *) l1h->data; + uint8_t tch_mode = tch_mode_req->tch_mode; + uint8_t audio_mode = tch_mode_req->audio_mode; + + printd("L1CTL_TCH_MODE_REQ (tch_mode=0x%02x audio_mode=0x%02x)\n", + tch_mode, audio_mode); + tch_mode = l1a_tch_mode_set(tch_mode); + audio_mode = l1a_audio_mode_set(audio_mode); + + audio_set_enabled(tch_mode, audio_mode); + + l1s.tch_sync = 1; /* Needed for audio to work */ + + l1ctl_tx_tch_mode_conf(tch_mode, audio_mode); +} + +/* receive a L1CTL_NEIGH_PM_REQ from L23 */ +static void l1ctl_rx_neigh_pm_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; + struct l1ctl_neigh_pm_req *pm_req = + (struct l1ctl_neigh_pm_req *) l1h->data; + int i; + + /* reset list in order to prevent race condition */ + l1s.neigh_pm.n = 0; /* atomic */ + l1s.neigh_pm.second = 0; + /* now reset pointer and fill list */ + l1s.neigh_pm.pos = 0; + l1s.neigh_pm.running = 0; + for (i = 0; i < pm_req->n; i++) { + l1s.neigh_pm.band_arfcn[i] = ntohs(pm_req->band_arfcn[i]); + l1s.neigh_pm.tn[i] = pm_req->tn[i]; + } + printf("L1CTL_NEIGH_PM_REQ new list with %u entries\n", pm_req->n); + l1s.neigh_pm.n = pm_req->n; /* atomic */ + + /* on C0 enable PM on frame 51 */ + if (l1s.dedicated.type == GSM_DCHAN_NONE) + mframe_enable(MF_TASK_NEIGH_PM51_C0T0); +} + +/* receive a L1CTL_TRAFFIC_REQ from L23 */ +static void l1ctl_rx_traffic_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; + struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data; + struct l1ctl_traffic_req *tr = (struct l1ctl_traffic_req *) ul->payload; + int num = 0; + + /* printd("L1CTL_TRAFFIC_REQ\n"); */ /* Very verbose, can overwelm serial */ + + msg->l2h = tr->data; + + num = l1a_txq_msgb_count(&l1s.tx_queue[L1S_CHAN_TRAFFIC]); + if (num >= 4) { + printd("dropping traffic frame\n"); + msgb_free(msg); + return; + } + + l1a_txq_msgb_enq(&l1s.tx_queue[L1S_CHAN_TRAFFIC], msg); +} + +static void l1ctl_sim_req(struct msgb *msg) +{ + uint16_t len = msg->len - sizeof(struct l1ctl_hdr); + uint8_t *data = msg->data + sizeof(struct l1ctl_hdr); + +#if 1 /* for debugging only */ + { + int i; + printf("SIM Request (%u): ", len); + for (i = 0; i < len; i++) + printf("%02x ", data[i]); + puts("\n"); + } +#endif + + sim_apdu(len, data); +} + +static struct llist_head l23_rx_queue = LLIST_HEAD_INIT(l23_rx_queue); + +/* callback from SERCOMM when L2 sends a message to L1 */ +void l1a_l23_rx(uint8_t dlci, struct msgb *msg) +{ + unsigned long flags; + + local_firq_save(flags); + msgb_enqueue(&l23_rx_queue, msg); + local_irq_restore(flags); +} + +void l1a_l23_handler(void) +{ + struct msgb *msg; + struct l1ctl_hdr *l1h; + unsigned long flags; + + local_firq_save(flags); + msg = msgb_dequeue(&l23_rx_queue); + local_irq_restore(flags); + if (!msg) + return; + + l1h = (struct l1ctl_hdr *) msg->data; + +#if 0 + { + int i; + printf("l1a_l23_rx_cb (%u): ", msg->len); + for (i = 0; i < msg->len; i++) + printf("%02x ", msg->data[i]); + puts("\n"); + } +#endif + + msg->l1h = msg->data; + + if (sizeof(*l1h) > msg->len) { + printf("l1a_l23_cb: Short message. %u\n", msg->len); + goto exit_msgbfree; + } + + switch (l1h->msg_type) { + case L1CTL_FBSB_REQ: + l1ctl_rx_fbsb_req(msg); + break; + case L1CTL_DM_EST_REQ: + l1ctl_rx_dm_est_req(msg); + break; + case L1CTL_DM_REL_REQ: + l1ctl_rx_dm_rel_req(msg); + break; + case L1CTL_PARAM_REQ: + l1ctl_rx_param_req(msg); + break; + case L1CTL_DM_FREQ_REQ: + l1ctl_rx_dm_freq_req(msg); + break; + case L1CTL_CRYPTO_REQ: + l1ctl_rx_crypto_req(msg); + break; + case L1CTL_RACH_REQ: + l1ctl_rx_rach_req(msg); + break; + case L1CTL_DATA_REQ: + l1ctl_rx_data_req(msg); + /* we have to keep the msgb, not free it! */ + goto exit_nofree; + case L1CTL_PM_REQ: + l1ctl_rx_pm_req(msg); + break; + case L1CTL_RESET_REQ: + l1ctl_rx_reset_req(msg); + break; + case L1CTL_CCCH_MODE_REQ: + l1ctl_rx_ccch_mode_req(msg); + break; + case L1CTL_TCH_MODE_REQ: + l1ctl_rx_tch_mode_req(msg); + break; + case L1CTL_NEIGH_PM_REQ: + l1ctl_rx_neigh_pm_req(msg); + break; + case L1CTL_TRAFFIC_REQ: + l1ctl_rx_traffic_req(msg); + /* we have to keep the msgb, not free it! */ + goto exit_nofree; + case L1CTL_SIM_REQ: + l1ctl_sim_req(msg); + break; + } + +exit_msgbfree: + msgb_free(msg); +exit_nofree: + return; +} + +void l1a_l23api_init(void) +{ + sercomm_register_rx_cb(SC_DLCI_L1A_L23, l1a_l23_rx); +} + diff --git a/src/target/firmware/layer1/mframe_sched.c b/src/target/firmware/layer1/mframe_sched.c new file mode 100644 index 00000000..f3a6b433 --- /dev/null +++ b/src/target/firmware/layer1/mframe_sched.c @@ -0,0 +1,494 @@ +/* GSM Multiframe Scheduler Implementation (on top of TDMA sched) */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdint.h> +#include <stdio.h> +#include <string.h> + +#include <debug.h> + +#include <osmocom/gsm/gsm_utils.h> + +#include <layer1/prim.h> +#include <layer1/sync.h> +#include <layer1/tdma_sched.h> +#include <layer1/mframe_sched.h> + +/* A multiframe operation which can be scheduled for a multiframe */ +struct mframe_sched_item { + /* The TDMA scheduler item that shall be scheduled */ + const struct tdma_sched_item *sched_set; + /* Which modulo shall be used on the frame number */ + uint16_t modulo; + /* At which number inside the modulo shall we be scheduled */ + uint16_t frame_nr; + /* bit-mask of flags */ + uint16_t flags; +}; + +/* FIXME: properly clean this up */ +#define NB_QUAD_DL nb_sched_set +#define NB_QUAD_FH_DL NB_QUAD_DL +#define NB_QUAD_UL nb_sched_set_ul +#define NB_QUAD_FH_UL NB_QUAD_UL +#define NEIGH_PM neigh_pm_sched_set + +/* BCCH Normal */ +static const struct mframe_sched_item mf_bcch_norm[] = { + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 2 }, + { .sched_set = NULL } +}; + +/* BCCH Extended */ +static const struct mframe_sched_item mf_bcch_ext[] = { + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 6 }, + { .sched_set = NULL } +}; + +/* Full CCCH in a pure BCCH + CCCH C0T0 */ +static const struct mframe_sched_item mf_ccch[] = { + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 6 }, + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 12 }, + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 16 }, + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 22 }, + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 26 }, + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 32 }, + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 36 }, + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 42 }, + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 46 }, + { .sched_set = NULL } +}; + +/* Full CCCH in a combined CCCH on C0T0 */ +static const struct mframe_sched_item mf_ccch_comb[] = { + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 6 }, + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 12 }, + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 16 }, + { .sched_set = NULL } +}; + +/* SDCCH/4 in a combined CCCH on C0T0, cannot be FH */ +static const struct mframe_sched_item mf_sdcch4_0[] = { + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 22 }, + { .sched_set = NB_QUAD_UL, .modulo = 51, .frame_nr = 22+15 }, + { .sched_set = NB_QUAD_DL, .modulo = 2*51, .frame_nr = 42, + .flags = MF_F_SACCH }, + { .sched_set = NB_QUAD_UL, .modulo = 2*51, .frame_nr = 42+15, + .flags = MF_F_SACCH }, + { .sched_set = NULL } +}; +static const struct mframe_sched_item mf_sdcch4_1[] = { + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 26 }, + { .sched_set = NB_QUAD_UL, .modulo = 51, .frame_nr = 26+15 }, + { .sched_set = NB_QUAD_DL, .modulo = 2*51, .frame_nr = 46, + .flags = MF_F_SACCH }, + { .sched_set = NB_QUAD_UL, .modulo = 2*51, .frame_nr = 46+15, + .flags = MF_F_SACCH }, + { .sched_set = NULL } +}; +static const struct mframe_sched_item mf_sdcch4_2[] = { + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 32 }, + { .sched_set = NB_QUAD_UL, .modulo = 51, .frame_nr = 32+15 }, + { .sched_set = NB_QUAD_DL, .modulo = 2*51, .frame_nr = 51+42, + .flags = MF_F_SACCH }, + { .sched_set = NB_QUAD_UL, .modulo = 2*51, .frame_nr = 51+42+15, + .flags = MF_F_SACCH }, + { .sched_set = NULL } +}; +static const struct mframe_sched_item mf_sdcch4_3[] = { + { .sched_set = NB_QUAD_DL, .modulo = 51, .frame_nr = 36 }, + { .sched_set = NB_QUAD_UL, .modulo = 51, .frame_nr = 36+15 }, + { .sched_set = NB_QUAD_DL, .modulo = 2*51, .frame_nr = 51+46, + .flags = MF_F_SACCH }, + { .sched_set = NB_QUAD_UL, .modulo = 2*51, .frame_nr = 51+46+15, + .flags = MF_F_SACCH }, + { .sched_set = NULL } +}; + +/* SDCCH/8, can be frequency hopping (FH) */ +static const struct mframe_sched_item mf_sdcch8_0[] = { + { .sched_set = NB_QUAD_FH_DL, .modulo = 51, .frame_nr = 0 }, + { .sched_set = NB_QUAD_FH_UL, .modulo = 51, .frame_nr = 0+15 }, + { .sched_set = NB_QUAD_FH_DL, .modulo = 2*51, .frame_nr = 32, + .flags = MF_F_SACCH }, + { .sched_set = NB_QUAD_FH_UL, .modulo = 2*51, .frame_nr = 32+15, + .flags = MF_F_SACCH }, + { .sched_set = NULL } +}; +static const struct mframe_sched_item mf_sdcch8_1[] = { + { .sched_set = NB_QUAD_FH_DL, .modulo = 51, .frame_nr = 4 }, + { .sched_set = NB_QUAD_FH_UL, .modulo = 51, .frame_nr = 4+15 }, + { .sched_set = NB_QUAD_FH_DL, .modulo = 2*51, .frame_nr = 36, + .flags = MF_F_SACCH }, + { .sched_set = NB_QUAD_FH_UL, .modulo = 2*51, .frame_nr = 36+15, + .flags = MF_F_SACCH }, + { .sched_set = NULL } +}; +static const struct mframe_sched_item mf_sdcch8_2[] = { + { .sched_set = NB_QUAD_FH_DL, .modulo = 51, .frame_nr = 8 }, + { .sched_set = NB_QUAD_FH_UL, .modulo = 51, .frame_nr = 8+15 }, + { .sched_set = NB_QUAD_FH_DL, .modulo = 2*51, .frame_nr = 40, + .flags = MF_F_SACCH }, + { .sched_set = NB_QUAD_FH_UL, .modulo = 2*51, .frame_nr = 40+15, + .flags = MF_F_SACCH }, + { .sched_set = NULL } +}; +static const struct mframe_sched_item mf_sdcch8_3[] = { + { .sched_set = NB_QUAD_FH_DL, .modulo = 51, .frame_nr = 12 }, + { .sched_set = NB_QUAD_FH_UL, .modulo = 51, .frame_nr = 12+15 }, + { .sched_set = NB_QUAD_FH_DL, .modulo = 2*51, .frame_nr = 44, + .flags = MF_F_SACCH }, + { .sched_set = NB_QUAD_FH_UL, .modulo = 2*51, .frame_nr = 44+15, + .flags = MF_F_SACCH }, + { .sched_set = NULL } +}; +static const struct mframe_sched_item mf_sdcch8_4[] = { + { .sched_set = NB_QUAD_FH_DL, .modulo = 51, .frame_nr = 16 }, + { .sched_set = NB_QUAD_FH_UL, .modulo = 51, .frame_nr = 16+15 }, + { .sched_set = NB_QUAD_FH_DL, .modulo = 2*51, .frame_nr = 51+32, + .flags = MF_F_SACCH }, + { .sched_set = NB_QUAD_FH_UL, .modulo = 2*51, .frame_nr = 51+32+15, + .flags = MF_F_SACCH }, + { .sched_set = NULL } +}; +static const struct mframe_sched_item mf_sdcch8_5[] = { + { .sched_set = NB_QUAD_FH_DL, .modulo = 51, .frame_nr = 20 }, + { .sched_set = NB_QUAD_FH_UL, .modulo = 51, .frame_nr = 20+15 }, + { .sched_set = NB_QUAD_FH_DL, .modulo = 2*51, .frame_nr = 51+36, + .flags = MF_F_SACCH }, + { .sched_set = NB_QUAD_FH_UL, .modulo = 2*51, .frame_nr = 51+36+15, + .flags = MF_F_SACCH }, + { .sched_set = NULL } +}; +static const struct mframe_sched_item mf_sdcch8_6[] = { + { .sched_set = NB_QUAD_FH_DL, .modulo = 51, .frame_nr = 24 }, + { .sched_set = NB_QUAD_FH_UL, .modulo = 51, .frame_nr = 24+15 }, + { .sched_set = NB_QUAD_FH_DL, .modulo = 2*51, .frame_nr = 51+40, + .flags = MF_F_SACCH }, + { .sched_set = NB_QUAD_FH_UL, .modulo = 2*51, .frame_nr = 51+40+15, + .flags = MF_F_SACCH }, + { .sched_set = NULL } +}; +static const struct mframe_sched_item mf_sdcch8_7[] = { + { .sched_set = NB_QUAD_FH_DL, .modulo = 51, .frame_nr = 28 }, + { .sched_set = NB_QUAD_FH_UL, .modulo = 51, .frame_nr = 28+15 }, + { .sched_set = NB_QUAD_FH_DL, .modulo = 2*51, .frame_nr = 51+44, + .flags = MF_F_SACCH }, + { .sched_set = NB_QUAD_FH_UL, .modulo = 2*51, .frame_nr = 51+44+15, + .flags = MF_F_SACCH }, + { .sched_set = NULL } +}; + +/* Measurement for MF 51 C0 */ +static const struct mframe_sched_item mf_neigh_pm51_c0t0[] = { + { .sched_set = NEIGH_PM , .modulo = 51, .frame_nr = 0 }, + { .sched_set = NEIGH_PM , .modulo = 51, .frame_nr = 10 }, + { .sched_set = NEIGH_PM , .modulo = 51, .frame_nr = 20 }, + { .sched_set = NEIGH_PM , .modulo = 51, .frame_nr = 30 }, + { .sched_set = NEIGH_PM , .modulo = 51, .frame_nr = 40 }, + { .sched_set = NULL } +}; + +/* Measurement for MF 51 */ +static const struct mframe_sched_item mf_neigh_pm51[] = { + { .sched_set = NEIGH_PM , .modulo = 51, .frame_nr = 50 }, + { .sched_set = NULL } +}; + +/* TCH */ +#define TCH tch_sched_set +#define TCH_A tch_a_sched_set +#define TCH_D tch_d_sched_set + +static const struct mframe_sched_item mf_tch_f_even[] = { + { .sched_set = TCH, .modulo = 13, .frame_nr = 0 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 1 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 2 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 3 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 4 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 5 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 6 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 7 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 8 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 9 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 10 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 11 }, + { .sched_set = TCH_A, .modulo = 26, .frame_nr = 12, + .flags = MF_F_SACCH }, + { .sched_set = NULL } +}; + +static const struct mframe_sched_item mf_tch_f_odd[] = { + { .sched_set = TCH, .modulo = 13, .frame_nr = 0 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 1 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 2 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 3 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 4 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 5 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 6 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 7 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 8 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 9 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 10 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 11 }, + { .sched_set = TCH_A, .modulo = 26, .frame_nr = 25, + .flags = MF_F_SACCH }, + { .sched_set = NULL } +}; + +static const struct mframe_sched_item mf_tch_h_0[] = { + { .sched_set = TCH, .modulo = 13, .frame_nr = 0 }, + { .sched_set = TCH_D, .modulo = 13, .frame_nr = 1 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 2 }, + { .sched_set = TCH_D, .modulo = 13, .frame_nr = 3 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 4 }, + { .sched_set = TCH_D, .modulo = 13, .frame_nr = 5 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 6 }, + { .sched_set = TCH_D, .modulo = 13, .frame_nr = 7 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 8 }, + { .sched_set = TCH_D, .modulo = 13, .frame_nr = 9 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 10 }, + { .sched_set = TCH_D, .modulo = 13, .frame_nr = 11 }, + { .sched_set = TCH_A, .modulo = 26, .frame_nr = 12, + .flags = MF_F_SACCH }, + { .sched_set = NULL } +}; + +static const struct mframe_sched_item mf_tch_h_1[] = { + { .sched_set = TCH_D, .modulo = 13, .frame_nr = 0 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 1 }, + { .sched_set = TCH_D, .modulo = 13, .frame_nr = 2 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 3 }, + { .sched_set = TCH_D, .modulo = 13, .frame_nr = 4 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 5 }, + { .sched_set = TCH_D, .modulo = 13, .frame_nr = 6 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 7 }, + { .sched_set = TCH_D, .modulo = 13, .frame_nr = 8 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 9 }, + { .sched_set = TCH_D, .modulo = 13, .frame_nr = 10 }, + { .sched_set = TCH, .modulo = 13, .frame_nr = 11 }, + { .sched_set = TCH_A, .modulo = 26, .frame_nr = 25, + .flags = MF_F_SACCH }, + { .sched_set = NULL } +}; + +/* Measurement for MF 26 */ +static const struct mframe_sched_item mf_neigh_pm26_even[] = { + { .sched_set = NEIGH_PM , .modulo = 26, .frame_nr = 25 }, + { .sched_set = NULL } +}; +static const struct mframe_sched_item mf_neigh_pm26_odd[] = { + { .sched_set = NEIGH_PM , .modulo = 26, .frame_nr = 12 }, + { .sched_set = NULL } +}; + +/* Test TX */ +static const struct mframe_sched_item mf_tx_all_nb[] = { + { .sched_set = NB_QUAD_FH_UL, .modulo = 4, .frame_nr = 0 }, + { .sched_set = NULL } +}; + +static const struct mframe_sched_item *sched_set_for_task[32] = { + [MF_TASK_BCCH_NORM] = mf_bcch_norm, + [MF_TASK_BCCH_EXT] = mf_bcch_ext, + [MF_TASK_CCCH] = mf_ccch, + [MF_TASK_CCCH_COMB] = mf_ccch_comb, + + [MF_TASK_SDCCH4_0] = mf_sdcch4_0, + [MF_TASK_SDCCH4_1] = mf_sdcch4_1, + [MF_TASK_SDCCH4_2] = mf_sdcch4_2, + [MF_TASK_SDCCH4_3] = mf_sdcch4_3, + + [MF_TASK_SDCCH8_0] = mf_sdcch8_0, + [MF_TASK_SDCCH8_1] = mf_sdcch8_1, + [MF_TASK_SDCCH8_2] = mf_sdcch8_2, + [MF_TASK_SDCCH8_3] = mf_sdcch8_3, + [MF_TASK_SDCCH8_4] = mf_sdcch8_4, + [MF_TASK_SDCCH8_5] = mf_sdcch8_5, + [MF_TASK_SDCCH8_6] = mf_sdcch8_6, + [MF_TASK_SDCCH8_7] = mf_sdcch8_7, + + [MF_TASK_TCH_F_EVEN] = mf_tch_f_even, + [MF_TASK_TCH_F_ODD] = mf_tch_f_odd, + [MF_TASK_TCH_H_0] = mf_tch_h_0, + [MF_TASK_TCH_H_1] = mf_tch_h_1, + + [MF_TASK_NEIGH_PM51_C0T0] = mf_neigh_pm51_c0t0, + [MF_TASK_NEIGH_PM51] = mf_neigh_pm51, + [MF_TASK_NEIGH_PM26E] = mf_neigh_pm26_even, + [MF_TASK_NEIGH_PM26O] = mf_neigh_pm26_odd, + + [MF_TASK_UL_ALL_NB] = mf_tx_all_nb, +}; + +/* encodes a channel number according to 08.58 Chapter 9.3.1 */ +uint8_t mframe_task2chan_nr(enum mframe_task mft, uint8_t ts) +{ + uint8_t cbits; + + switch (mft) { + case MF_TASK_BCCH_NORM: + case MF_TASK_BCCH_EXT: + cbits = 0x10; + break; + case MF_TASK_CCCH: + case MF_TASK_CCCH_COMB: + cbits = 0x12; + break; + case MF_TASK_SDCCH4_0: + cbits = 0x04 + 0; + break; + case MF_TASK_SDCCH4_1: + cbits = 0x04 + 1; + break; + case MF_TASK_SDCCH4_2: + cbits = 0x04 + 2; + break; + case MF_TASK_SDCCH4_3: + cbits = 0x04 + 3; + break; + case MF_TASK_SDCCH8_0: + cbits = 0x08 + 0; + break; + case MF_TASK_SDCCH8_1: + cbits = 0x08 + 1; + break; + case MF_TASK_SDCCH8_2: + cbits = 0x08 + 2; + break; + case MF_TASK_SDCCH8_3: + cbits = 0x08 + 3; + break; + case MF_TASK_SDCCH8_4: + cbits = 0x08 + 4; + break; + case MF_TASK_SDCCH8_5: + cbits = 0x08 + 5; + break; + case MF_TASK_SDCCH8_6: + cbits = 0x08 + 6; + break; + case MF_TASK_SDCCH8_7: + cbits = 0x08 + 7; + break; + case MF_TASK_TCH_F_EVEN: + case MF_TASK_TCH_F_ODD: + cbits = 0x01; + break; + case MF_TASK_TCH_H_0: + cbits = 0x02 + 0; + break; + case MF_TASK_TCH_H_1: + cbits = 0x02 + 1; + break; + case MF_TASK_UL_ALL_NB: + /* ERROR: cannot express as channel number */ + cbits = 0; + break; + } + + return (cbits << 3) | (ts & 0x7); +} + +/* how many TDMA frame ticks should we schedule events ahead? */ +#define SCHEDULE_AHEAD 2 + +/* how long do we need to tell the DSP in advance what we want to do? */ +#define SCHEDULE_LATENCY 1 + +/* (test and) schedule one particular sched_item_set by means of the TDMA scheduler */ +static void mframe_schedule_set(enum mframe_task task_id) +{ + const struct mframe_sched_item *set = sched_set_for_task[task_id]; + const struct mframe_sched_item *si; + + for (si = set; si->sched_set != NULL; si++) { + unsigned int trigger = si->frame_nr % si->modulo; + unsigned int current = (l1s.current_time.fn + SCHEDULE_AHEAD) % si->modulo; + if (current == trigger) { + uint32_t fn; + int rv; + + /* Schedule the set */ + /* FIXME: what to do with SACCH Flag etc? */ + rv = tdma_schedule_set(SCHEDULE_AHEAD-SCHEDULE_LATENCY, + si->sched_set, task_id | (si->flags<<8)); + + /* Compute the next safe time to queue a DSP command */ + fn = l1s.current_time.fn; + ADD_MODULO(fn, rv - 2, GSM_MAX_FN); /* -2 = worst case last dsp command */ + if ((fn > l1s.mframe_sched.safe_fn) || + (l1s.mframe_sched.safe_fn >= GSM_MAX_FN)) + l1s.mframe_sched.safe_fn = fn; + } + } +} + +/* Enable a specific task */ +void mframe_enable(enum mframe_task task_id) +{ + l1s.mframe_sched.tasks_tgt |= (1 << task_id); +} + +/* Disable a specific task */ +void mframe_disable(enum mframe_task task_id) +{ + l1s.mframe_sched.tasks_tgt &= ~(1 << task_id); +} + +/* Replace the current active set by the new one */ +void mframe_set(uint32_t tasks) +{ + l1s.mframe_sched.tasks_tgt = tasks; +} + +/* Schedule mframe_sched_items according to current MF TASK list */ +void mframe_schedule(void) +{ + unsigned int i; + int fn_diff; + + /* Try to enable/disable task to meet target bitmap */ + fn_diff = l1s.mframe_sched.safe_fn - l1s.current_time.fn; + if ((fn_diff <= 0) || (fn_diff >= (GSM_MAX_FN>>1)) || + (l1s.mframe_sched.safe_fn >= GSM_MAX_FN)) + /* If nothing is in the way, enable new tasks */ + l1s.mframe_sched.tasks = l1s.mframe_sched.tasks_tgt; + else + /* Else, Disable only */ + l1s.mframe_sched.tasks &= l1s.mframe_sched.tasks_tgt; + + /* Schedule any active pending set */ + for (i = 0; i < 32; i++) { + if (l1s.mframe_sched.tasks & (1 << i)) + mframe_schedule_set(i); + } +} + +/* reset the scheduler, disabling all tasks */ +void mframe_reset(void) +{ + l1s.mframe_sched.tasks = 0; + l1s.mframe_sched.tasks_tgt = 0; + l1s.mframe_sched.safe_fn = -1UL; /* Force safe */ +} + diff --git a/src/target/firmware/layer1/prim_fbsb.c b/src/target/firmware/layer1/prim_fbsb.c new file mode 100644 index 00000000..e849240b --- /dev/null +++ b/src/target/firmware/layer1/prim_fbsb.c @@ -0,0 +1,575 @@ +/* Layer 1 - FCCH and SCH burst handling */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> + +#include <defines.h> +#include <debug.h> +#include <memory.h> +#include <byteorder.h> +#include <rffe.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/core/msgb.h> +#include <calypso/dsp_api.h> +#include <calypso/irq.h> +#include <calypso/tpu.h> +#include <calypso/tsp.h> +#include <calypso/dsp.h> +#include <calypso/timer.h> +#include <comm/sercomm.h> + +#include <layer1/sync.h> +#include <layer1/afc.h> +#include <layer1/toa.h> +#include <layer1/tdma_sched.h> +#include <layer1/mframe_sched.h> +#include <layer1/tpu_window.h> +#include <layer1/l23_api.h> +#include <layer1/agc.h> + +#include <l1ctl_proto.h> + +#define FB0_RETRY_COUNT 3 +#define AFC_RETRY_COUNT 30 + +extern uint16_t rf_arfcn; // TODO + +struct mon_state { + uint32_t fnr_report; /* frame number when DSP reported it */ + int attempt; /* which attempt was this ? */ + + int16_t toa; + uint16_t pm; + uint16_t angle; + uint16_t snr; + + /* computed values */ + int16_t freq_diff; + + /* Sync Burst (SB) */ + uint8_t bsic; + struct gsm_time time; +}; + +struct l1a_fb_state { + struct mon_state mon; + struct l1ctl_fbsb_req req; + int16_t initial_freq_err; + uint8_t fb_retries; + uint8_t afc_retries; +}; + +static struct l1a_fb_state fbs; +static struct mon_state *last_fb = &fbs.mon; + +static void dump_mon_state(struct mon_state *fb) +{ +#if 0 + printf("(%u:%u): TOA=%5u, Power=%4ddBm, Angle=%5dHz, " + "SNR=%04x(%d.%u) OFFSET=%u SYNCHRO=%u\n", + fb->fnr_report, fb->attempt, fb->toa, + agc_inp_dbm8_by_pm(fb->pm)/8, ANGLE_TO_FREQ(fb->angle), + fb->snr, l1s_snr_int(fb->snr), l1s_snr_fract(fb->snr), + tpu_get_offset(), tpu_get_synchro()); +#else + printf("(%u:%u): TOA=%5u, Power=%4ddBm, Angle=%5dHz\n", + fb->fnr_report, fb->attempt, fb->toa, + agc_inp_dbm8_by_pm(fb->pm)/8, ANGLE_TO_FREQ(fb->angle)); +#endif +} + +static int l1ctl_fbsb_resp(uint8_t res) +{ + struct msgb *msg; + struct l1ctl_fbsb_conf *resp; + + msg = l1_create_l2_msg(L1CTL_FBSB_CONF, fbs.mon.time.fn, + l1s_snr_int(fbs.mon.snr), + fbs.req.band_arfcn); + if (!msg) + return -ENOMEM; + + resp = (struct l1ctl_fbsb_conf *) msgb_put(msg, sizeof(*resp)); + resp->initial_freq_err = htons(fbs.initial_freq_err); + resp->result = res; + resp->bsic = fbs.mon.bsic; + + /* no need to set BSIC, as it is never used here */ + l1_queue_for_l2(msg); + + return 0; +} + +/* SCH Burst Detection ********************************************************/ + +/* determine the GSM time and BSIC from a Sync Burst */ +static uint8_t l1s_decode_sb(struct gsm_time *time, uint32_t sb) +{ + uint8_t bsic = (sb >> 2) & 0x3f; + uint8_t t3p; + + memset(time, 0, sizeof(*time)); + + /* TS 05.02 Chapter 3.3.2.2.1 SCH Frame Numbers */ + time->t1 = ((sb >> 23) & 1) | ((sb >> 7) & 0x1fe) | ((sb << 9) & 0x600); + time->t2 = (sb >> 18) & 0x1f; + t3p = ((sb >> 24) & 1) | ((sb >> 15) & 6); + time->t3 = t3p*10 + 1; + + /* TS 05.02 Chapter 4.3.3 TDMA frame number */ + time->fn = gsm_gsmtime2fn(time); + + time->tc = (time->fn / 51) % 8; + + return bsic; +} + +static void read_sb_result(struct mon_state *st, int attempt) +{ + st->toa = dsp_api.db_r->a_serv_demod[D_TOA]; + st->pm = dsp_api.db_r->a_serv_demod[D_PM]>>3; + st->angle = dsp_api.db_r->a_serv_demod[D_ANGLE]; + st->snr = dsp_api.db_r->a_serv_demod[D_SNR]; + + st->freq_diff = ANGLE_TO_FREQ(st->angle); + st->fnr_report = l1s.current_time.fn; + st->attempt = attempt; + + dump_mon_state(st); + + if (st->snr > AFC_SNR_THRESHOLD) + afc_input(st->freq_diff, rf_arfcn, 1); + else + afc_input(st->freq_diff, rf_arfcn, 0); + + dsp_api.r_page_used = 1; +} + +/* Note: When we get the SB response, it is 2 TDMA frames after the SB + * actually happened, as it is a "C W W R" task */ +#define SB2_LATENCY 2 + +static int l1s_sbdet_resp(__unused uint8_t p1, uint8_t attempt, + __unused uint16_t p3) +{ + uint32_t sb; + int qbits, fn_offset; + struct l1_cell_info *cinfo = &l1s.serving_cell; + int fnr_delta, bits_delta; + struct l1ctl_sync_new_ccch_resp *l1; + struct msgb *msg; + + putchart('s'); + + if (dsp_api.db_r->a_sch[0] & (1<<B_SCH_CRC)) { + /* mark READ page as being used */ + dsp_api.r_page_used = 1; + + /* after 2nd attempt, we failed */ + if (attempt == 2) { + last_fb->attempt = 13; + l1s_compl_sched(L1_COMPL_FB); + } + + /* after 1st attempt, we simply wait for 2nd */ + return 0; + } + + printf("SB%d ", attempt); + read_sb_result(last_fb, attempt); + + sb = dsp_api.db_r->a_sch[3] | dsp_api.db_r->a_sch[4] << 16; + fbs.mon.bsic = l1s_decode_sb(&fbs.mon.time, sb); + printf("=> SB 0x%08x: BSIC=%u ", sb, fbs.mon.bsic); + l1s_time_dump(&fbs.mon.time); + + l1s.serving_cell.bsic = fbs.mon.bsic; + + /* calculate synchronisation value (TODO: only complete for qbits) */ + last_fb->toa -= 23; + qbits = last_fb->toa * 4; + fn_offset = l1s.current_time.fn; // TODO + + if (qbits > QBITS_PER_TDMA) { + qbits -= QBITS_PER_TDMA; + fn_offset -= 1; + } else if (qbits < 0) { + qbits += QBITS_PER_TDMA; + fn_offset += 1; + } + + fnr_delta = last_fb->fnr_report - attempt; + bits_delta = fnr_delta * BITS_PER_TDMA; + + cinfo->fn_offset = fnr_delta; + cinfo->time_alignment = qbits; + cinfo->arfcn = rf_arfcn; + + if (last_fb->toa > bits_delta) + printf("=> DSP reports SB in bit that is %d bits in the " + "future?!?\n", last_fb->toa - bits_delta); + else + printf(" qbits=%u\n", qbits); + + synchronize_tdma(&l1s.serving_cell); + + /* if we have recived a SYNC burst, update our local GSM time */ + gsm_fn2gsmtime(&l1s.current_time, fbs.mon.time.fn + SB2_LATENCY); + /* compute next time from new current time */ + l1s.next_time = l1s.current_time; + l1s_time_inc(&l1s.next_time, 1); + + /* If we call tdma_sched_reset(), which is only needed if there + * are further l1s_sbdet_resp() scheduled, we will bring + * dsp_api.db_r and dsp_api.db_w out of sync because we changed + * dsp_api.db_w for l1s_sbdet_cmd() and canceled + * l1s_sbdet_resp() which would change dsp_api.db_r. The DSP + * however expects dsp_api.db_w and dsp_api.db_r to be in sync + * (either "0 - 0" or "1 - 1"). So we have to bring dsp_api.db_w + * and dsp_api.db_r into sync again, otherwise NB reading will + * complain. We probably don't need the Abort command and could + * just bring dsp_api.db_w and dsp_api.db_r into sync. */ + if (attempt != 2) { + tdma_sched_reset(); + l1s_dsp_abort(); + } + + l1s_reset_hw(); + /* enable the MF Task for BCCH reading */ + mframe_enable(MF_TASK_BCCH_NORM); + if (l1s.serving_cell.ccch_mode == CCCH_MODE_COMBINED) + mframe_enable(MF_TASK_CCCH_COMB); + else if (l1s.serving_cell.ccch_mode == CCCH_MODE_NON_COMBINED) + mframe_enable(MF_TASK_CCCH); + + l1s_compl_sched(L1_COMPL_FB); + + return 0; +} + +static int l1s_sbdet_cmd(__unused uint8_t p1, __unused uint8_t p2, + __unused uint16_t p3) +{ + putchart('S'); + + fbs.mon.bsic = 0; + fbs.mon.time.fn = 0; + + dsp_api.db_w->d_task_md = SB_DSP_TASK; + dsp_api.ndb->d_fb_mode = 0; /* wideband search */ + + /* Program TPU */ + l1s_rx_win_ctrl(rf_arfcn, L1_RXWIN_SB, 0); + + return 0; +} + +/* This is how it is done by the TSM30 */ +static const struct tdma_sched_item sb_sched_set[] = { + SCHED_ITEM_DT(l1s_sbdet_cmd, 0, 0, 1), SCHED_END_FRAME(), + SCHED_ITEM_DT(l1s_sbdet_cmd, 0, 0, 2), SCHED_END_FRAME(), + SCHED_END_FRAME(), + SCHED_ITEM(l1s_sbdet_resp, -4, 0, 1), SCHED_END_FRAME(), + SCHED_ITEM(l1s_sbdet_resp, -4, 0, 2), SCHED_END_FRAME(), + SCHED_END_SET() +}; + +void l1s_sb_test(uint8_t base_fn) +{ + tdma_schedule_set(base_fn, sb_sched_set, 0); +} +/* FCCH Burst *****************************************************************/ + +static int read_fb_result(struct mon_state *st, int attempt) +{ + st->toa = dsp_api.ndb->a_sync_demod[D_TOA]; + st->pm = dsp_api.ndb->a_sync_demod[D_PM]>>3; + st->angle = dsp_api.ndb->a_sync_demod[D_ANGLE]; + st->snr = dsp_api.ndb->a_sync_demod[D_SNR]; + + //last_fb->angle = clip_int16(last_fb->angle, AFC_MAX_ANGLE); + st->freq_diff = ANGLE_TO_FREQ(last_fb->angle); + st->fnr_report = l1s.current_time.fn; + st->attempt = attempt; + + dump_mon_state(st); + + dsp_api.ndb->d_fb_det = 0; + dsp_api.ndb->a_sync_demod[D_TOA] = 0; /* TSM30 does it (really needed ?) */ + + /* Update AFC with current frequency offset */ + afc_correct(st->freq_diff, rf_arfcn); + + //tpu_dsp_frameirq_enable(); + return 1; +} + +static void fbinfo2cellinfo(struct l1_cell_info *cinfo, + const struct mon_state *mon) +{ + int ntdma, qbits, fn_offset, fnr_delta, bits_delta; + + /* FIXME: where did this magic 23 come from? */ + last_fb->toa -= 23; + + if (last_fb->toa < 0) { + qbits = (last_fb->toa + BITS_PER_TDMA) * 4; + ntdma = -1; + } else { + ntdma = (last_fb->toa) / BITS_PER_TDMA; + qbits = (last_fb->toa - ntdma * BITS_PER_TDMA) * 4; + } + + fn_offset = l1s.current_time.fn - last_fb->attempt + ntdma; + fnr_delta = last_fb->fnr_report - last_fb->attempt; + bits_delta = fnr_delta * BITS_PER_TDMA; + + cinfo->fn_offset = fnr_delta; + cinfo->time_alignment = qbits; + cinfo->arfcn = rf_arfcn; + + if (last_fb->toa > bits_delta) + printf("=> DSP reports FB in bit that is %d bits in " + "the future?!?\n", last_fb->toa - bits_delta); + else { + int fb_fnr = (last_fb->fnr_report - last_fb->attempt) + + last_fb->toa/BITS_PER_TDMA; + printf("=>FB @ FNR %u fn_offset=%d qbits=%u\n", + fb_fnr, fn_offset, qbits); + } +} + +/* scheduler callback to issue a FB detection task to the DSP */ +static int l1s_fbdet_cmd(__unused uint8_t p1, __unused uint8_t p2, + uint16_t fb_mode) +{ + if (fb_mode == 0) { + putchart('F'); + } else { + putchart('V'); + } + + l1s.fb.mode = fb_mode; + + /* Tell the RF frontend to set the gain appropriately */ + rffe_compute_gain(-85, CAL_DSP_TGT_BB_LVL); + + /* Program DSP */ + dsp_api.db_w->d_task_md = FB_DSP_TASK; /* maybe with I/Q swap? */ + dsp_api.ndb->d_fb_mode = fb_mode; + + /* Program TPU */ + l1s_rx_win_ctrl(fbs.req.band_arfcn, L1_RXWIN_FB, 0); + + return 0; +} + +#if 0 +#define FB0_SNR_THRESH 2000 +#define FB1_SNR_THRESH 3000 +#else +#define FB0_SNR_THRESH 0 +#define FB1_SNR_THRESH 0 +#endif + +static const struct tdma_sched_item fb_sched_set[]; + +/* scheduler callback to check for a FB detection response */ +static int l1s_fbdet_resp(__unused uint8_t p1, uint8_t attempt, + uint16_t fb_mode) +{ + putchart('f'); + + if (!dsp_api.ndb->d_fb_det) { + /* we did not detect a FB */ + + /* attempt < 12, do nothing */ + if (attempt < 12) + return 0; + + /* attempt >= 12, we simply don't find one */ + + /* If we don't reset here, we get DSP DMA errors */ + tdma_sched_reset(); + + if (fbs.fb_retries < FB0_RETRY_COUNT) { + /* retry once more */ + tdma_schedule_set(1, fb_sched_set, 0); + fbs.fb_retries++; + } else { + last_fb->attempt = 13; + l1s_compl_sched(L1_COMPL_FB); + } + + return 0; + } + + /* We found a frequency burst, reset everything */ + l1s_reset_hw(); + + printf("FB%u ", dsp_api.ndb->d_fb_mode); + read_fb_result(last_fb, attempt); + + /* if this is the first success, save freq err */ + if (!fbs.initial_freq_err) + fbs.initial_freq_err = last_fb->freq_diff; + + /* If we don't reset here, we get DSP DMA errors */ + tdma_sched_reset(); + + /* Immediately schedule further TDMA tasklets, if requested. Doing + * this directly from L1S means we can do this quickly without any + * additional delays */ + if (fb_mode == 0) { + if (fbs.req.flags & L1CTL_FBSB_F_FB1) { + /* If we don't reset here, we get DSP DMA errors */ + tdma_sched_reset(); + /* FIXME: don't only use the last but an average */ + if (abs(last_fb->freq_diff) < fbs.req.freq_err_thresh1 && + last_fb->snr > FB0_SNR_THRESH) { + /* continue with FB1 task in DSP */ + tdma_schedule_set(1, fb_sched_set, 1); + } else { + if (fbs.afc_retries < AFC_RETRY_COUNT) { + tdma_schedule_set(1, fb_sched_set, 0); + fbs.afc_retries++; + } else { + /* Abort */ + last_fb->attempt = 13; + l1s_compl_sched(L1_COMPL_FB); + } + } + } else + l1s_compl_sched(L1_COMPL_FB); + } else if (fb_mode == 1) { + if (fbs.req.flags & L1CTL_FBSB_F_SB) { + + int ntdma, qbits; + /* FIXME: where did this magic 23 come from? */ + last_fb->toa -= 23; + + if (last_fb->toa < 0) { + qbits = (last_fb->toa + BITS_PER_TDMA) * 4; + ntdma = -1; + } else { + ntdma = (last_fb->toa) / BITS_PER_TDMA; + qbits = (last_fb->toa - ntdma * BITS_PER_TDMA) * 4; + } + + + int fn_offset = l1s.current_time.fn - last_fb->attempt + ntdma; + int delay = fn_offset + 11 - l1s.current_time.fn - 1; + printf(" fn_offset=%d (fn=%u + attempt=%u + ntdma = %d)\n", + fn_offset, l1s.current_time.fn, last_fb->attempt, ntdma); + printf(" delay=%d (fn_offset=%d + 11 - fn=%u - 1\n", delay, + fn_offset, l1s.current_time.fn); + printf(" scheduling next FB/SB detection task with delay %u\n", delay); + if (abs(last_fb->freq_diff) < fbs.req.freq_err_thresh2 && + last_fb->snr > FB1_SNR_THRESH) { + /* synchronize before reading SB */ + fbinfo2cellinfo(&l1s.serving_cell, last_fb); + synchronize_tdma(&l1s.serving_cell); + tdma_schedule_set(delay, sb_sched_set, 0); + } else + tdma_schedule_set(delay, fb_sched_set, 1); + } else + l1s_compl_sched(L1_COMPL_FB); + } + + return 0; +} + +/* FB detection */ +static const struct tdma_sched_item fb_sched_set[] = { + SCHED_ITEM_DT(l1s_fbdet_cmd, 0, 0, 0), SCHED_END_FRAME(), + SCHED_END_FRAME(), + SCHED_ITEM(l1s_fbdet_resp, -4, 0, 1), SCHED_END_FRAME(), + SCHED_ITEM(l1s_fbdet_resp, -4, 0, 2), SCHED_END_FRAME(), + SCHED_ITEM(l1s_fbdet_resp, -4, 0, 3), SCHED_END_FRAME(), + SCHED_ITEM(l1s_fbdet_resp, -4, 0, 4), SCHED_END_FRAME(), + SCHED_ITEM(l1s_fbdet_resp, -4, 0, 5), SCHED_END_FRAME(), + SCHED_ITEM(l1s_fbdet_resp, -4, 0, 6), SCHED_END_FRAME(), + SCHED_ITEM(l1s_fbdet_resp, -4, 0, 7), SCHED_END_FRAME(), + SCHED_ITEM(l1s_fbdet_resp, -4, 0, 8), SCHED_END_FRAME(), + SCHED_ITEM(l1s_fbdet_resp, -4, 0, 9), SCHED_END_FRAME(), + SCHED_ITEM(l1s_fbdet_resp, -4, 0, 10), SCHED_END_FRAME(), + SCHED_ITEM(l1s_fbdet_resp, -4, 0, 11), SCHED_END_FRAME(), + SCHED_ITEM(l1s_fbdet_resp, -4, 0, 12), SCHED_END_FRAME(), + SCHED_END_SET() +}; + +/* Asynchronous completion handler for FB detection */ +static void l1a_fb_compl(__unused enum l1_compl c) +{ + struct l1_cell_info *cinfo = &l1s.serving_cell; + + if (last_fb->attempt >= 13) { + /* FB detection failed, signal this via L1CTL */ + return l1ctl_fbsb_resp(255); + } + + /* FIME: use l1s.neigh_cell[fbs.cinfo_idx] */ + fbinfo2cellinfo(&l1s.serving_cell, last_fb); + + /* send FBSB_CONF success message via L1CTL */ + l1ctl_fbsb_resp(0); +} + +void l1s_fbsb_req(uint8_t base_fn, struct l1ctl_fbsb_req *req) +{ + /* copy + endian convert request data */ + fbs.req.band_arfcn = ntohs(req->band_arfcn); + fbs.req.timeout = ntohs(req->timeout); + fbs.req.freq_err_thresh1 = ntohs(req->freq_err_thresh1); + fbs.req.freq_err_thresh2 = ntohs(req->freq_err_thresh2); + fbs.req.num_freqerr_avg = req->num_freqerr_avg; + fbs.req.flags = req->flags; + fbs.req.sync_info_idx = req->sync_info_idx; + + /* clear initial frequency error */ + fbs.initial_freq_err = 0; + fbs.fb_retries = 0; + fbs.afc_retries = 0; + + /* Make sure we start at a 'center' AFCDAC output value */ + afc_reset(); + + /* Reset the TOA loop counters */ + toa_reset(); + + if (fbs.req.flags & L1CTL_FBSB_F_FB0) + tdma_schedule_set(base_fn, fb_sched_set, 0); + else if (fbs.req.flags & L1CTL_FBSB_F_FB1) + tdma_schedule_set(base_fn, fb_sched_set, 0); + else if (fbs.req.flags & L1CTL_FBSB_F_SB) + tdma_schedule_set(base_fn, sb_sched_set, 0); + +} + +static __attribute__ ((constructor)) void l1s_prim_fbsb_init(void) +{ + l1s.completion[L1_COMPL_FB] = &l1a_fb_compl; +} diff --git a/src/target/firmware/layer1/prim_freq.c b/src/target/firmware/layer1/prim_freq.c new file mode 100644 index 00000000..ca6dc9e4 --- /dev/null +++ b/src/target/firmware/layer1/prim_freq.c @@ -0,0 +1,113 @@ +/* Layer 1 Frequency redefinition at "starting time" */ + +/* (C) 2010 by Andreas Eversverg <jolly@eversberg.eu> + * + * 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 <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <defines.h> +#include <debug.h> +#include <memory.h> +#include <byteorder.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/core/msgb.h> +#include <calypso/dsp_api.h> +#include <calypso/irq.h> +#include <calypso/tpu.h> +#include <calypso/tsp.h> +#include <calypso/dsp.h> +#include <calypso/timer.h> +#include <comm/sercomm.h> +#include <asm/system.h> + +#include <layer1/sync.h> +#include <layer1/async.h> +#include <layer1/tdma_sched.h> +#include <layer1/tpu_window.h> +#include <layer1/l23_api.h> +#include <layer1/sched_gsmtime.h> + +#include <l1ctl_proto.h> + +struct { + uint32_t fn; + uint16_t band_arfcn; +} last_rach; + +/* if the "starting time" is reached, use frequencies "after time" */ +static int l1s_freq_cmd(__unused uint8_t p1, __unused uint8_t p2, __unused uint16_t p3) +{ + putchart('F'); + + printf("Reached starting time, altering frequency set\n"); + + l1s.dedicated.tsc = l1s.dedicated.st_tsc; + l1s.dedicated.h = l1s.dedicated.st_h; + if (l1s.dedicated.h) + memcpy(&l1s.dedicated.h1, &l1s.dedicated.st_h1, + sizeof(l1s.dedicated.h1)); + else + memcpy(&l1s.dedicated.h0, &l1s.dedicated.st_h0, + sizeof(l1s.dedicated.h0)); + + return 0; +} + +/* sched set for frequency change */ +const struct tdma_sched_item freq_sched_set[] = { + SCHED_ITEM(l1s_freq_cmd, -3, 1, 0), + SCHED_END_SET() +}; + +/* request a frequency change at the given frame number + * Note: The fn_sched parameter must be in range 0..42431. */ +void l1a_freq_req(uint32_t fn_sched) +{ + int32_t diff; + unsigned long flags; + + /* We must check here, if the time already elapsed. + * This is required, because we may have an undefined delay between + * layer 1 and layer 3. + */ + diff = fn_sched - (l1s.current_time.fn % 42432); + if (diff < 0) + diff += 42432; + /* note: 5 is used to give scheduler some time */ + if (diff == 5 || diff >= 32024) { + l1s_freq_cmd(0, 0, 0); + return; + } + + /* calculate (full range) frame number */ + fn_sched = l1s.current_time.fn + diff; + if (fn_sched >= GSM_MAX_FN) + fn_sched -= GSM_MAX_FN; + printf("Scheduling frequency change at fn=%u, currently fn=%u\n", + fn_sched, l1s.current_time.fn); + + local_firq_save(flags); + sched_gsmtime(freq_sched_set, fn_sched, 0); + local_irq_restore(flags); +} + diff --git a/src/target/firmware/layer1/prim_pm.c b/src/target/firmware/layer1/prim_pm.c new file mode 100644 index 00000000..1630600a --- /dev/null +++ b/src/target/firmware/layer1/prim_pm.c @@ -0,0 +1,241 @@ +/* Layer 1 Power Measurement */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <defines.h> +#include <debug.h> +#include <memory.h> +#include <byteorder.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/core/msgb.h> +#include <calypso/dsp_api.h> +#include <calypso/irq.h> +#include <calypso/tpu.h> +#include <calypso/tsp.h> +#include <calypso/dsp.h> +#include <calypso/timer.h> +#include <comm/sercomm.h> +#include <asm/system.h> + +#include <layer1/sync.h> +#include <layer1/agc.h> +#include <layer1/tdma_sched.h> +#include <layer1/tpu_window.h> +#include <layer1/l23_api.h> +#include <layer1/prim.h> +#include <rffe.h> + +#include <l1ctl_proto.h> + +static void l1ddsp_meas_read(uint8_t nbmeas, uint16_t *pm) +{ + uint8_t i; + + for (i = 0; i < nbmeas; i++) + pm[i] = (uint16_t) ((dsp_api.db_r->a_pm[i] & 0xffff) >> 3); + dsp_api.r_page_used = 1; +} + +/* scheduler callback to issue a power measurement task to the DSP */ +static int l1s_pm_cmd(uint8_t num_meas, + __unused uint8_t p2, uint16_t arfcn) +{ + putchart('P'); + + dsp_api.db_w->d_task_md = num_meas; /* number of measurements */ + dsp_api.ndb->d_fb_mode = 0; /* wideband search */ + + /* Tell the RF frontend to set the gain appropriately */ + rffe_compute_gain(-85, CAL_DSP_TGT_BB_LVL); + + /* Program TPU */ + /* FIXME: RXWIN_PW needs to set up multiple times in case + * num_meas > 1 */ + l1s_rx_win_ctrl(arfcn, L1_RXWIN_PW, 0); + //l1s_rx_win_ctrl(arfcn, L1_RXWIN_NB); + + return 0; +} + +/* scheduler callback to read power measurement resposnse from the DSP */ +static int l1s_pm_resp(uint8_t num_meas, __unused uint8_t p2, + uint16_t arfcn) +{ + struct l1ctl_pm_conf *pmr; + uint16_t pm_level[2]; + + putchart('p'); + + l1ddsp_meas_read(num_meas, pm_level); + + printf("PM MEAS: ARFCN=%u, %-4d dBm at baseband, %-4d dBm at RF\n", + arfcn, pm_level[0]/8, agc_inp_dbm8_by_pm(pm_level[0])/8); + + printd("PM MEAS: %-4d dBm, %-4d dBm ARFCN=%u\n", + agc_inp_dbm8_by_pm(pm_level[0])/8, + agc_inp_dbm8_by_pm(pm_level[1])/8, arfcn); + + if (!l1s.pm.msg) + l1s.pm.msg = l1ctl_msgb_alloc(L1CTL_PM_CONF); + + if (msgb_tailroom(l1s.pm.msg) < sizeof(*pmr)) { + /* flush current msgb */ + l1_queue_for_l2(l1s.pm.msg); + /* allocate a new msgb and initialize header */ + l1s.pm.msg = l1ctl_msgb_alloc(L1CTL_PM_CONF); + } + + pmr = msgb_put(l1s.pm.msg, sizeof(*pmr)); + pmr->band_arfcn = htons(arfcn); + /* FIXME: do this as RxLev rather than DBM8 ? */ + pmr->pm[0] = dbm2rxlev(agc_inp_dbm8_by_pm(pm_level[0])/8); + if (num_meas > 1) + pmr->pm[1] = dbm2rxlev(agc_inp_dbm8_by_pm(pm_level[1])/8); + else + pmr->pm[1] = 0; + + if (l1s.pm.mode == 1) { + if (l1s.pm.range.arfcn_next != l1s.pm.range.arfcn_end) { + /* schedule PM for next ARFCN in range */ + l1s.pm.range.arfcn_next = + (l1s.pm.range.arfcn_next+1) & 0xfbff; + l1s_pm_test(1, l1s.pm.range.arfcn_next); + } else { + /* we have finished, flush the msgb to L2 */ + struct l1ctl_hdr *l1h = l1s.pm.msg->l1h; + l1h->flags |= L1CTL_F_DONE; + l1_queue_for_l2(l1s.pm.msg); + l1s.pm.msg = NULL; + } + } + + return 0; +} + +static const struct tdma_sched_item pm_sched_set[] = { + SCHED_ITEM_DT(l1s_pm_cmd, 0, 1, 0), SCHED_END_FRAME(), + SCHED_END_FRAME(), + SCHED_ITEM(l1s_pm_resp, -4, 1, 0), SCHED_END_FRAME(), + SCHED_END_SET() +}; + +/* Schedule a power measurement test */ +void l1s_pm_test(uint8_t base_fn, uint16_t arfcn) +{ + unsigned long flags; + + printd("l1s_pm_test(%u, %u)\n", base_fn, arfcn); + + local_firq_save(flags); + tdma_schedule_set(base_fn, pm_sched_set, arfcn); + local_irq_restore(flags); +} + +/* + * perform measurements of neighbour cells + */ + +/* scheduler callback to issue a power measurement task to the DSP */ +static int l1s_neigh_pm_cmd(uint8_t num_meas, + __unused uint8_t p2, __unused uint16_t p3) +{ + uint8_t last_gain = rffe_get_gain(); + + dsp_api.db_w->d_task_md = num_meas; /* number of measurements */ +// dsp_api.ndb->d_fb_mode = 0; /* wideband search */ + + /* Tell the RF frontend to set the gain appropriately (keep last) */ + rffe_compute_gain(-85, CAL_DSP_TGT_BB_LVL); + + /* Program TPU */ + /* FIXME: RXWIN_PW needs to set up multiple times in case + * num_meas > 1 */ + /* do measurement dummy, in case l1s.neigh_pm.n == 0 */ + l1s_rx_win_ctrl((l1s.neigh_pm.n) ? + l1s.neigh_pm.band_arfcn[l1s.neigh_pm.pos] : 0, + L1_RXWIN_PW, l1s.neigh_pm.tn[l1s.neigh_pm.pos]); + + /* restore last gain */ + rffe_set_gain(last_gain); + + l1s.neigh_pm.running = 1; + + return 0; +} + +/* scheduler callback to read power measurement resposnse from the DSP */ +static int l1s_neigh_pm_resp(__unused uint8_t p1, __unused uint8_t p2, + __unused uint16_t p3) +{ + uint16_t dbm; + uint8_t level; + + dsp_api.r_page_used = 1; + + if (l1s.neigh_pm.n == 0 || !l1s.neigh_pm.running) + goto out; + + dbm = (uint16_t) ((dsp_api.db_r->a_pm[0] & 0xffff) >> 3); + level = dbm2rxlev(agc_inp_dbm8_by_pm(dbm)/8); + + l1s.neigh_pm.level[l1s.neigh_pm.pos] = level; + + if (++l1s.neigh_pm.pos >= l1s.neigh_pm.n) { + struct msgb *msg; + struct l1ctl_neigh_pm_ind *mi; + int i; + + l1s.neigh_pm.pos = 0; + /* return result */ + msg = l1ctl_msgb_alloc(L1CTL_NEIGH_PM_IND); + for (i = 0; i < l1s.neigh_pm.n; i++) { + if (msgb_tailroom(msg) < (int) sizeof(*mi)) { + l1_queue_for_l2(msg); + msg = l1ctl_msgb_alloc(L1CTL_NEIGH_PM_IND); + } + mi = (struct l1ctl_neigh_pm_ind *) + msgb_put(msg, sizeof(*mi)); + mi->band_arfcn = htons(l1s.neigh_pm.band_arfcn[i]); + mi->tn = l1s.neigh_pm.tn[i]; + mi->pm[0] = l1s.neigh_pm.level[i]; + mi->pm[1] = 0; + } + l1_queue_for_l2(msg); + } + +out: + l1s.neigh_pm.running = 0; + + return 0; +} + +const struct tdma_sched_item neigh_pm_sched_set[] = { + SCHED_ITEM_DT(l1s_neigh_pm_cmd, 0, 1, 0), SCHED_END_FRAME(), + SCHED_END_FRAME(), + SCHED_ITEM(l1s_neigh_pm_resp, -4, 1, 0), SCHED_END_FRAME(), + SCHED_END_SET() +}; + diff --git a/src/target/firmware/layer1/prim_rach.c b/src/target/firmware/layer1/prim_rach.c new file mode 100644 index 00000000..27e89abb --- /dev/null +++ b/src/target/firmware/layer1/prim_rach.c @@ -0,0 +1,160 @@ +/* Layer 1 Random Access Channel Burst */ + +/* (C) 2010 by Dieter Spaar <spaar@mirider.augusta.de> + * (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <defines.h> +#include <debug.h> +#include <memory.h> +#include <byteorder.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/core/msgb.h> +#include <calypso/dsp_api.h> +#include <calypso/irq.h> +#include <calypso/tpu.h> +#include <calypso/tsp.h> +#include <calypso/dsp.h> +#include <calypso/timer.h> +#include <comm/sercomm.h> +#include <asm/system.h> + +#include <layer1/sync.h> +#include <layer1/async.h> +#include <layer1/tdma_sched.h> +#include <layer1/tpu_window.h> +#include <layer1/l23_api.h> +#include <layer1/sched_gsmtime.h> + +#include <l1ctl_proto.h> + +struct { + uint32_t fn; + uint16_t band_arfcn; +} last_rach; + +/* p1: type of operation (0: one NB, 1: one RACH burst, 2: four NB */ +static int l1s_tx_rach_cmd(__unused uint8_t p1, __unused uint8_t p2, __unused uint16_t p3) +{ + int i; + uint16_t *info_ptr; + uint8_t data[2]; + + putchart('T'); + + l1s_tx_apc_helper(l1s.serving_cell.arfcn); + + data[0] = l1s.serving_cell.bsic << 2; + data[1] = l1s.rach.ra; + + info_ptr = &dsp_api.ndb->d_rach; + info_ptr[0] = ((uint16_t)(data[0])) | ((uint16_t)(data[1])<<8); + + dsp_api.db_w->d_task_ra = RACH_DSP_TASK; + + l1s_tx_win_ctrl(l1s.serving_cell.arfcn | ARFCN_UPLINK, L1_TXWIN_AB, 0, 3); + + return 0; +} + +/* p1: type of operation (0: one NB, 1: one RACH burst, 2: four NB */ +static int l1s_tx_rach_resp(__unused uint8_t p1, __unused uint8_t burst_id, + __unused uint16_t p3) +{ + putchart('t'); + + dsp_api.r_page_used = 1; + + /* schedule a confirmation back indicating the GSM time at which + * the RACH burst was transmitted to the BTS */ + last_rach.fn = l1s.current_time.fn - 1; + last_rach.band_arfcn = l1s.serving_cell.arfcn; + l1s_compl_sched(L1_COMPL_RACH); + + return 0; +} + +/* sched sets for uplink */ +const struct tdma_sched_item rach_sched_set_ul[] = { + SCHED_ITEM_DT(l1s_tx_rach_cmd, 3, 1, 0), SCHED_END_FRAME(), + SCHED_END_FRAME(), + SCHED_ITEM(l1s_tx_rach_resp, -4, 1, 0), SCHED_END_FRAME(), + SCHED_END_SET() +}; + +/* Asynchronous completion handler for FB detection */ +static void l1a_rach_compl(__unused enum l1_compl c) +{ + struct msgb *msg; + + msg = l1_create_l2_msg(L1CTL_RACH_CONF, last_rach.fn, 0, + last_rach.band_arfcn); + l1_queue_for_l2(msg); +} + +static uint8_t t3_to_rach_comb[51] = { + 0, 0, 0, 0, + 0, 1, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 25, 25, 25, 25, 25, 25, 25, + 25, 26, + 27, 27, 27, 27}; +static uint8_t rach_to_t3_comb[27] = { + 4, 5, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 45, 46}; + +/* request a RACH request at the next multiframe T3 = fn51 */ +void l1a_rach_req(uint16_t offset, uint8_t combined, uint8_t ra) +{ + uint32_t fn_sched; + unsigned long flags; + + offset += 3; + + local_firq_save(flags); + if (combined) { + /* add elapsed RACH slots to offset */ + offset += t3_to_rach_comb[l1s.current_time.t3]; + /* offset is the number of RACH slots in the future */ + fn_sched = l1s.current_time.fn - l1s.current_time.t3; + fn_sched += offset / 27 * 51; + fn_sched += rach_to_t3_comb[offset % 27]; + } else + fn_sched = l1s.current_time.fn + offset; + l1s.rach.ra = ra; + sched_gsmtime(rach_sched_set_ul, fn_sched, 0); + local_irq_restore(flags); + + memset(&last_rach, 0, sizeof(last_rach)); +} + +static __attribute__ ((constructor)) void prim_rach_init(void) +{ + l1s.completion[L1_COMPL_RACH] = &l1a_rach_compl; +} diff --git a/src/target/firmware/layer1/prim_rx_nb.c b/src/target/firmware/layer1/prim_rx_nb.c new file mode 100644 index 00000000..ade23a01 --- /dev/null +++ b/src/target/firmware/layer1/prim_rx_nb.c @@ -0,0 +1,217 @@ +/* Layer 1 - Receiving Normal Bursts */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <defines.h> +#include <debug.h> +#include <memory.h> +#include <byteorder.h> +#include <rffe.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/core/msgb.h> +#include <calypso/dsp_api.h> +#include <calypso/irq.h> +#include <calypso/tpu.h> +#include <calypso/tsp.h> +#include <calypso/dsp.h> +#include <calypso/timer.h> +#include <comm/sercomm.h> + +#include <layer1/sync.h> +#include <layer1/afc.h> +#include <layer1/toa.h> +#include <layer1/tdma_sched.h> +#include <layer1/mframe_sched.h> +#include <layer1/tpu_window.h> +#include <layer1/l23_api.h> +#include <layer1/rfch.h> +#include <layer1/prim.h> +#include <layer1/agc.h> + +#include <l1ctl_proto.h> + +struct l1s_rxnb_state { + struct l1s_meas_hdr meas[4]; + + struct msgb *msg; + struct l1ctl_info_dl *dl; + struct l1ctl_data_ind *di; +}; + +static struct l1s_rxnb_state rxnb; + +static int l1s_nb_resp(__unused uint8_t p1, uint8_t burst_id, uint16_t p3) +{ + struct gsm_time rx_time; + uint8_t mf_task_id = p3 & 0xff; + uint8_t mf_task_flags = p3 >> 8; + uint16_t rf_arfcn; + uint8_t tsc, tn; + + putchart('n'); + + /* just for debugging, d_task_d should not be 0 */ + if (dsp_api.db_r->d_task_d == 0) { + puts("EMPTY\n"); + return 0; + } + + /* DSP burst ID needs to correspond with what we expect */ + if (dsp_api.db_r->d_burst_d != burst_id) { + printf("BURST ID %u!=%u\n", dsp_api.db_r->d_burst_d, burst_id); + return 0; + } + + /* get radio parameters for _this_ burst */ + gsm_fn2gsmtime(&rx_time, l1s.current_time.fn - 1); + rfch_get_params(&rx_time, &rf_arfcn, &tsc, &tn); + + /* collect measurements */ + rxnb.meas[burst_id].toa_qbit = dsp_api.db_r->a_serv_demod[D_TOA]; + rxnb.meas[burst_id].pm_dbm8 = + agc_inp_dbm8_by_pm(dsp_api.db_r->a_serv_demod[D_PM] >> 3); + rxnb.meas[burst_id].freq_err = + ANGLE_TO_FREQ(dsp_api.db_r->a_serv_demod[D_ANGLE]); + rxnb.meas[burst_id].snr = dsp_api.db_r->a_serv_demod[D_SNR]; + + /* feed computed frequency error into AFC loop */ + if (rxnb.meas[burst_id].snr > AFC_SNR_THRESHOLD) + afc_input(rxnb.meas[burst_id].freq_err, rf_arfcn, 1); + else + afc_input(rxnb.meas[burst_id].freq_err, rf_arfcn, 0); + + /* feed computed TOA into TA loop */ + toa_input(rxnb.meas[burst_id].toa_qbit << 2, rxnb.meas[burst_id].snr); + + /* Tell the RF frontend to set the gain appropriately */ + rffe_compute_gain(rxnb.meas[burst_id].pm_dbm8/8, CAL_DSP_TGT_BB_LVL); + + /* 4th burst, get frame data */ + if (dsp_api.db_r->d_burst_d == 3) { + uint8_t i; + uint16_t num_biterr; + uint32_t avg_snr = 0; + int32_t avg_dbm8 = 0; + + /* Get radio parameters for the first burst */ + gsm_fn2gsmtime(&rx_time, l1s.current_time.fn - 4); + rfch_get_params(&rx_time, &rf_arfcn, &tsc, &tn); + + /* Set Channel Number depending on MFrame Task ID */ + rxnb.dl->chan_nr = mframe_task2chan_nr(mf_task_id, tn); + + /* Set SACCH indication in Link IDentifier */ + if (mf_task_flags & MF_F_SACCH) + rxnb.dl->link_id = 0x40; + else + rxnb.dl->link_id = 0x00; + + rxnb.dl->band_arfcn = htons(rf_arfcn); + + rxnb.dl->frame_nr = htonl(rx_time.fn); + + /* compute average snr and rx level */ + for (i = 0; i < 4; ++i) { + avg_snr += rxnb.meas[i].snr; + avg_dbm8 += rxnb.meas[i].pm_dbm8; + } + rxnb.dl->snr = avg_snr / 4; + rxnb.dl->rx_level = dbm2rxlev(avg_dbm8 / (8*4)); + + num_biterr = dsp_api.ndb->a_cd[2] & 0xffff; + if (num_biterr > 0xff) + rxnb.dl->num_biterr = 0xff; + else + rxnb.dl->num_biterr = num_biterr; + + rxnb.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(rxnb.dl->rx_level); + + /* copy actual data, skipping the information block [0,1,2] */ + dsp_memcpy_from_api(rxnb.di->data, &dsp_api.ndb->a_cd[3], 23, 0); + + l1_queue_for_l2(rxnb.msg); + rxnb.msg = NULL; rxnb.dl = NULL; rxnb.di = NULL; + + /* clear downlink task */ + dsp_api.db_w->d_task_d = 0; + } + + /* mark READ page as being used */ + dsp_api.r_page_used = 1; + + return 0; +} + +static int l1s_nb_cmd(__unused uint8_t p1, uint8_t burst_id, + __unused uint16_t p3) +{ + uint16_t arfcn; + uint8_t tsc, tn; + + putchart('N'); + + if (burst_id == 1) { + /* allocate message only at 2nd burst in case of + * consecutive/overlapping normal burst RX tasks */ + /* FIXME: we actually want all allocation out of L1S! */ + if (rxnb.msg) { + /* Can happen when resetting ... */ + printf("nb_cmd(0) and rxnb.msg != NULL\n"); + msgb_free(rxnb.msg); + } + /* allocate msgb as needed. FIXME: from L1A ?? */ + rxnb.msg = l1ctl_msgb_alloc(L1CTL_DATA_IND); + if (!rxnb.msg) + printf("nb_cmd(0): unable to allocate msgb\n"); + rxnb.dl = (struct l1ctl_info_dl *) msgb_put(rxnb.msg, sizeof(*rxnb.dl)); + rxnb.di = (struct l1ctl_data_ind *) msgb_put(rxnb.msg, sizeof(*rxnb.di)); + } + + rfch_get_params(&l1s.next_time, &arfcn, &tsc, &tn); + + /* DDL_DSP_TASK, four normal bursts */ + dsp_load_tch_param(&l1s.next_time, + SIG_ONLY_MODE, SDCCH_4, 0, 0, 0, tn); + + dsp_load_rx_task(ALLC_DSP_TASK, burst_id, tsc); + + l1s_rx_win_ctrl(arfcn, L1_RXWIN_NB, 0); + + return 0; +} + +const struct tdma_sched_item nb_sched_set[] = { + SCHED_ITEM_DT(l1s_nb_cmd, 0, 0, 0), SCHED_END_FRAME(), + SCHED_ITEM_DT(l1s_nb_cmd, 0, 0, 1), SCHED_END_FRAME(), + SCHED_ITEM(l1s_nb_resp, -4, 0, 0), SCHED_ITEM_DT(l1s_nb_cmd, 0, 0, 2), SCHED_END_FRAME(), + SCHED_ITEM(l1s_nb_resp, -4, 0, 1), SCHED_ITEM_DT(l1s_nb_cmd, 0, 0, 3), SCHED_END_FRAME(), + SCHED_ITEM(l1s_nb_resp, -4, 0, 2), SCHED_END_FRAME(), + SCHED_ITEM(l1s_nb_resp, -4, 0, 3), SCHED_END_FRAME(), + SCHED_END_SET() +}; diff --git a/src/target/firmware/layer1/prim_tch.c b/src/target/firmware/layer1/prim_tch.c new file mode 100644 index 00000000..a26c58ea --- /dev/null +++ b/src/target/firmware/layer1/prim_tch.c @@ -0,0 +1,752 @@ +/* Layer 1 - TCH */ + +/* (C) 2010 by Dieter Spaar <spaar@mirider.augusta.de> + * (C) 2010 by Sylvain Munaut <tnt@246tnt.com> + * + * 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 <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <defines.h> +#include <debug.h> +#include <memory.h> +#include <byteorder.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/core/msgb.h> +#include <calypso/dsp_api.h> +#include <calypso/irq.h> +#include <calypso/tpu.h> +#include <calypso/tsp.h> +#include <calypso/dsp.h> +#include <calypso/timer.h> +#include <comm/sercomm.h> + +#include <rffe.h> +#include <layer1/sync.h> +#include <layer1/afc.h> +#include <layer1/agc.h> +#include <layer1/toa.h> +#include <layer1/tdma_sched.h> +#include <layer1/mframe_sched.h> +#include <layer1/tpu_window.h> +#include <layer1/l23_api.h> +#include <layer1/rfch.h> +#include <layer1/prim.h> + +#include <l1ctl_proto.h> + + +/* 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 ... + */ + +#define TX_TYPE_SACCH (1<<0) +#define TX_TYPE_FACCH (1<<1) +#define TX_TYPE_TRAFFIC (1<<2) + +static uint16_t last_tx_tch_fn; +static uint16_t last_tx_tch_type; + +static void l1a_tx_tch_compl(__unused enum l1_compl c) +{ + struct msgb *msg; + + if (last_tx_tch_type & (TX_TYPE_SACCH | TX_TYPE_FACCH)) { + msg = l1_create_l2_msg(L1CTL_DATA_CONF, last_tx_tch_fn, 0, 0); + l1_queue_for_l2(msg); + } + + if (last_tx_tch_type & TX_TYPE_TRAFFIC) { + msg = l1_create_l2_msg(L1CTL_TRAFFIC_CONF, last_tx_tch_fn, 0, 0); + l1_queue_for_l2(msg); + } + + last_tx_tch_type = 0; +} + +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); + + /* feed computed TOA into TA loop */ + toa_input(rx_tch.meas[meas_id].toa_qbit << 2, rx_tch.meas[meas_id].snr); + + /* Tell the RF frontend to set the gain appropriately */ + rffe_compute_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<<B_BLUD))) { + struct msgb *msg; + struct l1ctl_info_dl *dl; + struct l1ctl_data_ind *di; + uint16_t num_biterr; + uint32_t avg_snr = 0; + int32_t avg_dbm8 = 0; + int i, n; + + /* Allocate msgb */ + /* FIXME: we actually want all allocation out of L1S! */ + msg = l1ctl_msgb_alloc(L1CTL_DATA_IND); + if(!msg) { + printf("TCH FACCH: unable to allocate msgb\n"); + goto skip_rx_facch; + } + + dl = (struct l1ctl_info_dl *) msgb_put(msg, sizeof(*dl)); + di = (struct l1ctl_data_ind *) msgb_put(msg, sizeof(*di)); + + /* Fill DL header (should be about the first burst ... here is the last) */ + dl->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; i<n; i++) { + int j = (meas_id + FACCH_MEAS_HIST - i) % FACCH_MEAS_HIST; + avg_snr += rx_tch.meas[j].snr; + avg_dbm8 += rx_tch.meas[j].pm_dbm8; + } + + dl->snr = avg_snr / n; + dl->rx_level = dbm2rxlev(avg_dbm8 / (8*n)); + + /* 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_rx_facch: + /* Reset A_FD header (needed by DSP) */ + /* B_FIRE1 =1, B_FIRE0 =0 , BLUD =0 */ + dsp_api.ndb->a_fd[0] = (1<<B_FIRE1); + dsp_api.ndb->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<<B_BLUD)) { + /* Send the data to upper layers (if interested and good frame) */ + if ((l1s.audio_mode & AUDIO_RX_TRAFFIC_IND) && + !(dsp_api.ndb->a_dd_0[0] & (1<<B_BFI))) { + struct msgb *msg; + struct l1ctl_info_dl *dl; + struct l1ctl_traffic_ind *ti; + + /* Allocate msgb */ + /* FIXME: we actually want all allocation out of L1S! */ + msg = l1ctl_msgb_alloc(L1CTL_TRAFFIC_IND); + if(!msg) { + printf("TCH traffic: unable to allocate msgb\n"); + goto skip_rx_traffic; + } + + dl = (struct l1ctl_info_dl *) msgb_put(msg, sizeof(*dl)); + ti = (struct l1ctl_traffic_ind *) msgb_put(msg, sizeof(*ti)); + + /* Copy actual data, skipping the information block [0,1,2] */ + dsp_memcpy_from_api(ti->data, &traffic_buf[3], 33, 1); + + /* Give message to up layer */ + l1_queue_for_l2(msg); + } + + skip_rx_traffic: + /* Reset traffic buffer header in NDB (needed by DSP) */ + traffic_buf[0] = 0; + traffic_buf[2] = 0xffff; + } + } + + /* mark READ page as being used */ + dsp_api.r_page_used = 1; + + return 0; +} + +static int l1s_tch_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, tch_mode; + uint32_t fn_report; + uint8_t sync = 0; + static int icnt; + int facch_tx_now, traffic_tx_now; + + /* 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, &tch_mode); + + /* Sync & FACCH delay */ + if (l1s.tch_sync) { + l1s.tch_sync = 0; + sync = 1; + icnt = 0; + } else if (icnt <= 26) + icnt++; + + /* Load FACCH data if we start a new burst */ + /* (the DSP wants the data on the CMD of the burst _preceding_ the + * first burst) */ + if (tch_f_hn) { + /* FACCH/F: B0(0...7),B1(4...11),B2(8...11,0...3) */ + facch_tx_now = ((l1s.next_time.fn % 13) % 4) == 3; + } else { + /* FAACH/H: See GSM 05.02 Clause 7 Table 1of9 */ + uint8_t t2_norm = l1s.next_time.t2 - tch_sub; + facch_tx_now = (t2_norm == 23) || + (t2_norm == 6) || + (t2_norm == 15); + } + + if (facch_tx_now) { + uint16_t *info_ptr = dsp_api.ndb->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; + last_tx_tch_type |= TX_TYPE_FACCH; + l1s_compl_sched(L1_COMPL_TX_TCH); + } + + /* Free msg now that we're done with it */ + if (msg) + msgb_free(msg); + } + + /* Traffic now ? */ + if (tch_f_hn) { + /* TCH/F: B0(0...7),B1(4...11),B2(8...11,0...3) (mod 13)*/ + traffic_tx_now = ((l1s.next_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_tx_now = (((l1s.next_time.fn - tch_sub + 13) % 13) % 4) == 2; + } + + if (traffic_tx_now) { + volatile uint16_t *traffic_buf; + struct msgb *msg; + const uint8_t *data; + + /* Reset play mode */ + dsp_api.ndb->d_tch_mode &= ~B_PLAY_UL; + + /* Check l1s audio mode */ + if (!(l1s.audio_mode & AUDIO_TX_TRAFFIC_REQ)) + goto skip_tx_traffic; + + /* Traffic buffer = !tch_sub */ + traffic_buf = tch_sub ? dsp_api.ndb->a_du_0 : dsp_api.ndb->a_du_1; + + /* Pull Traffic data (if any) */ + msg = msgb_dequeue(&l1s.tx_queue[L1S_CHAN_TRAFFIC]); + + /* Copy actual data, skipping the information block [0,1,2] */ + if (msg) { + data = msg->l2h; + dsp_memcpy_to_api(&traffic_buf[3], data, 33, 1); + + traffic_buf[0] = (1 << B_BLUD); /* 1st word: Set B_BLU bit. */ + traffic_buf[1] = 0; /* 2nd word: cleared. */ + traffic_buf[2] = 0; /* 3nd word: cleared. */ + } + + if (msg) + dsp_api.ndb->d_tch_mode |= B_PLAY_UL; + + /* Indicate completion (FIXME: early but easier this way for now) */ + if (msg) { + last_tx_tch_fn = l1s.next_time.fn; + last_tx_tch_type |= TX_TYPE_TRAFFIC; + l1s_compl_sched(L1_COMPL_TX_TCH); + } + + /* Free msg now that we're done with it */ + if (msg) + msgb_free(msg); + } +skip_tx_traffic: + + /* 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 | ARFCN_UPLINK, 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/H: Dummy + * ------------------------------------------------------------------------- */ + +/* This task is needed to perform some operation in the DSP when there is + * no data to be exchanged */ + +static int l1s_tch_d_resp(__unused uint8_t p1, __unused uint8_t p2, uint16_t p3) +{ + /* mark READ page as being used */ + dsp_api.r_page_used = 1; + + return 0; +} + +static int l1s_tch_d_cmd(__unused uint8_t p1, __unused uint8_t p2, uint16_t p3) +{ + uint8_t mf_task_id = p3 & 0xff; + uint8_t chan_nr; + uint8_t tsc, tn; + uint8_t tch_f_hn, tch_sub, tch_mode; + uint32_t fn_report; + + /* Get/compute various parameters */ + rfch_get_params(&l1s.next_time, NULL, &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, &tch_mode); + + /* Configure DSP */ + dsp_load_tch_param( + &l1s.next_time, + tch_mode, tch_f_hn ? TCH_F : TCH_H, tch_sub, + 0, 0, tn + ); + + dsp_load_rx_task(TCHD_DSP_TASK, 0, tsc); /* burst_id unused for TCH */ + dsp_load_tx_task(TCHD_DSP_TASK, 0, tsc); /* burst_id unused for TCH */ + + return 0; +} + +const struct tdma_sched_item tch_d_sched_set[] = { + SCHED_ITEM_DT(l1s_tch_d_cmd, 0, 0, 0), SCHED_END_FRAME(), + SCHED_END_FRAME(), + SCHED_ITEM(l1s_tch_d_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); + + /* feed computed TOA into TA loop */ + toa_input(rx_tch_a.meas[burst_id].toa_qbit << 2, rx_tch_a.meas[burst_id].snr); + + /* Tell the RF frontend to set the gain appropriately */ + rffe_compute_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<<B_BLUD))) { + unsigned int i; + uint16_t num_biterr; + uint32_t avg_snr = 0; + int32_t avg_dbm8 = 0; + + /* Average SNR & RX level + error & crc status */ + for (i=0; i<4; i++) { + avg_snr += rx_tch_a.meas[i].snr; + avg_dbm8 += rx_tch_a.meas[i].pm_dbm8; + } + rx_tch_a.dl->snr = avg_snr / 4; + rx_tch_a.dl->rx_level = dbm2rxlev(avg_dbm8 / (8*4)); + + 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<<B_FIRE1); + dsp_api.ndb->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, tch_mode; + 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, &tch_mode); + 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; + last_tx_tch_type |= TX_TYPE_SACCH; + 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, + tch_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 | ARFCN_UPLINK, 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() +}; diff --git a/src/target/firmware/layer1/prim_tx_nb.c b/src/target/firmware/layer1/prim_tx_nb.c new file mode 100644 index 00000000..df13c757 --- /dev/null +++ b/src/target/firmware/layer1/prim_tx_nb.c @@ -0,0 +1,173 @@ +/* Layer 1 - Transmit Normal Burst */ + +/* (C) 2010 by Dieter Spaar <spaar@mirider.augusta.de> + * (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <defines.h> +#include <debug.h> +#include <memory.h> +#include <byteorder.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/core/msgb.h> +#include <calypso/dsp_api.h> +#include <calypso/irq.h> +#include <calypso/tpu.h> +#include <calypso/tsp.h> +#include <calypso/dsp.h> +#include <calypso/timer.h> +#include <comm/sercomm.h> + +#include <layer1/sync.h> +#include <layer1/agc.h> +#include <layer1/tdma_sched.h> +#include <layer1/mframe_sched.h> +#include <layer1/tpu_window.h> +#include <layer1/l23_api.h> +#include <layer1/rfch.h> +#include <layer1/prim.h> + +#include <l1ctl_proto.h> + + +static uint32_t last_txnb_fn; + +/* p1: type of operation (0: one NB, 1: one RACH burst, 2: four NB) */ +static int l1s_tx_resp(__unused uint8_t p1, __unused uint8_t burst_id, + __unused uint16_t p3) +{ + putchart('t'); + + dsp_api.r_page_used = 1; + + if (burst_id == 3) { + last_txnb_fn = l1s.current_time.fn - 4; + l1s_compl_sched(L1_COMPL_TX_NB); + } + + return 0; +} + +/* p1: type of operation (0: one NB, 1: one RACH burst, 2: four NB) */ +static int l1s_tx_cmd(uint8_t p1, uint8_t burst_id, uint16_t p3) +{ + uint16_t arfcn; + uint8_t tsc, tn; + uint8_t mf_task_id = p3 & 0xff; + uint8_t mf_task_flags = p3 >> 8; + + putchart('T'); + + /* before sending first of the four bursts, copy data to API ram */ + if (burst_id == 0) { + uint16_t *info_ptr = dsp_api.ndb->a_cu; + struct msgb *msg; + const uint8_t *data; + + /* distinguish between DCCH and ACCH */ + if (mf_task_flags & MF_F_SACCH) { + msg = msgb_dequeue(&l1s.tx_queue[L1S_CHAN_SACCH]); + data = msg ? msg->l3h : pu_get_meas_frame(); + } else { + msg = msgb_dequeue(&l1s.tx_queue[L1S_CHAN_MAIN]); + data = msg ? msg->l3h : pu_get_idle_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; // 3rd word: cleared. + + /* Copy the actual data after the header */ + dsp_memcpy_to_api(&info_ptr[3], data, 23, 0); + + if (msg) + msgb_free(msg); + } + + rfch_get_params(&l1s.next_time, &arfcn, &tsc, &tn); + + l1s_tx_apc_helper(arfcn); + + if (p1 == 0) + /* DUL_DSP_TASK, one normal burst */ + dsp_load_tch_param(&l1s.next_time, + SIG_ONLY_MODE, INVALID_CHANNEL, 0, 0, 0, tn); + + else if (p1 == 2) + /* DUL_DSP_TASK, four normal bursts */ + dsp_load_tch_param(&l1s.next_time, + SIG_ONLY_MODE, SDCCH_4, 0, 0, 0, tn); + + dsp_load_tx_task(DUL_DSP_TASK, burst_id, tsc); + + l1s_tx_win_ctrl(arfcn | ARFCN_UPLINK, L1_TXWIN_NB, 0, 3); + + return 0; +} + +/* Asynchronous completion handler for NB transmit */ +static void l1a_tx_nb_compl(__unused enum l1_compl c) +{ + struct msgb *msg; + + msg = l1_create_l2_msg(L1CTL_DATA_CONF, last_txnb_fn, 0, 0); + l1_queue_for_l2(msg); +} + +void l1s_tx_test(uint8_t base_fn, uint8_t type) +{ + printf("Starting TX %d\n", type); + + if (type == 0) {// one normal burst + tdma_schedule(base_fn, &l1s_tx_cmd, 0, 0, 0, 3); + tdma_schedule(base_fn + 2, &l1s_tx_resp, 0, 0, 0, 3); + } else if (type == 2) { // four normal bursts + tdma_schedule(base_fn, &l1s_tx_cmd, 2, 0, 0, 3); + tdma_schedule(base_fn + 1, &l1s_tx_cmd, 2, 1, 0, 3); + tdma_schedule(base_fn + 2, &l1s_tx_resp, 2, 0, 0, 3); + tdma_schedule(base_fn + 2, &l1s_tx_cmd, 2, 2, 0, 3); + tdma_schedule(base_fn + 3, &l1s_tx_resp, 2, 1, 0, 3); + tdma_schedule(base_fn + 3, &l1s_tx_cmd, 2, 3, 0, 3); + tdma_schedule(base_fn + 4, &l1s_tx_resp, 2, 2, 0, 3); + tdma_schedule(base_fn + 5, &l1s_tx_resp, 2, 3, 0, 3); + } +} + +/* sched sets for uplink */ +const struct tdma_sched_item nb_sched_set_ul[] = { + SCHED_ITEM_DT(l1s_tx_cmd, 3, 2, 0), SCHED_END_FRAME(), + SCHED_ITEM_DT(l1s_tx_cmd, 3, 2, 1), SCHED_END_FRAME(), + SCHED_ITEM(l1s_tx_resp, -4, 2, 0), SCHED_ITEM_DT(l1s_tx_cmd, 3, 2, 2), SCHED_END_FRAME(), + SCHED_ITEM(l1s_tx_resp, -4, 2, 1), SCHED_ITEM_DT(l1s_tx_cmd, 3, 2, 3), SCHED_END_FRAME(), + SCHED_ITEM(l1s_tx_resp, -4, 2, 2), SCHED_END_FRAME(), + SCHED_ITEM(l1s_tx_resp, -4, 2, 3), SCHED_END_FRAME(), + SCHED_END_SET() +}; + +static __attribute__ ((constructor)) void prim_tx_nb_init(void) +{ + l1s.completion[L1_COMPL_TX_NB] = &l1a_tx_nb_compl; +} diff --git a/src/target/firmware/layer1/prim_utils.c b/src/target/firmware/layer1/prim_utils.c new file mode 100644 index 00000000..c85da717 --- /dev/null +++ b/src/target/firmware/layer1/prim_utils.c @@ -0,0 +1,74 @@ +/* Layer 1 Various primitive utilities */ + +/* (C) 2010 by Sylvain Munaut <tnt@246tNt.com> + * + * 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 <stdint.h> + +#include <osmocom/core/msgb.h> +#include <layer1/sync.h> + + +static const uint8_t ubUui[23] = { + /* dummy lapdm header */ + 0x01, 0x03, 0x01, + + /* fill bytes */ + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b +}; + +static uint8_t ubMeas[23] = { + /* L1 SAACH pseudo-header */ + 0x0f, 0x00, + + /* lapdm header */ + 0x01, 0x03, 0x49, + + /* Measurement report */ + 0x06, 0x15, 0x36, 0x36, 0x01, 0xC0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 +}; + + +const uint8_t *pu_get_idle_frame(void) +{ + return ubUui; +} + +void pu_update_rx_level(uint8_t rx_level) +{ + ubMeas[7] = ubMeas[8] = rx_level; +} + +const uint8_t *pu_get_meas_frame(void) +{ + if (l1s.tx_meas) { + return l1s.tx_meas->l3h; + } else { + /* Update L1 SAACH pseudo-header */ + ubMeas[0] = l1s.tx_power; + ubMeas[1] = l1s.ta; + + return ubMeas; + } +} diff --git a/src/target/firmware/layer1/rfch.c b/src/target/firmware/layer1/rfch.c new file mode 100644 index 00000000..d0818d04 --- /dev/null +++ b/src/target/firmware/layer1/rfch.c @@ -0,0 +1,152 @@ +/* RF Channel utilities */ + +/* (C) 2010 by Sylvain Munaut <tnt@246tNt.com> + * + * 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 <stdint.h> + +#include <osmocom/gsm/gsm_utils.h> + +#include <layer1/sync.h> + + +/* + * Hopping sequence generation + * + * The algorithm is explained in GSM 05.02 Section 6.2.3 + * + * if HSN = 0 (cyclic hopping) then: + * MAI, integer (0 .. N-1) : + * MAI = (FN + MAIO) modulo N + * + * else: + * M, integer (0 .. 152) : + * M = T2 + RNTABLE((HSN xor T1R) + T3) + * + * S, integer (0 .. N-1) : + * M' = M modulo (2 ^ NBIN) + * T' = T3 modulo (2 ^ NBIN) + * + * if M' < N then: + * S = M' + * else: + * S = (M'+T') modulo N + * + * MAI, integer (0 .. N-1) : + * MAI = (S + MAIO) modulo N + */ + +static uint8_t rn_table[114] = { + 48, 98, 63, 1, 36, 95, 78, 102, 94, 73, + 0, 64, 25, 81, 76, 59, 124, 23, 104, 100, + 101, 47, 118, 85, 18, 56, 96, 86, 54, 2, + 80, 34, 127, 13, 6, 89, 57, 103, 12, 74, + 55, 111, 75, 38, 109, 71, 112, 29, 11, 88, + 87, 19, 3, 68, 110, 26, 33, 31, 8, 45, + 82, 58, 40, 107, 32, 5, 106, 92, 62, 67, + 77, 108, 122, 37, 60, 66, 121, 42, 51, 126, + 117, 114, 4, 90, 43, 52, 53, 113, 120, 72, + 16, 49, 7, 79, 119, 61, 22, 84, 9, 97, + 91, 15, 21, 24, 46, 39, 93, 105, 65, 70, + 125, 99, 17, 123, +}; + + +static int pow_nbin_mask(int n) +{ + int x; + x = (n ) | + (n >> 1) | + (n >> 2) | + (n >> 3) | + (n >> 4) | + (n >> 5) | + (n >> 6); + return x; +} + +static int16_t rfch_hop_seq_gen(struct gsm_time *t, + uint8_t hsn, uint8_t maio, + uint8_t n, uint16_t *arfcn_tbl) +{ + int mai; + + if (!hsn) { + /* cyclic hopping */ + mai = (t->fn + maio) % n; + } else { + /* pseudo random hopping */ + int m, mp, tp, s, pnm; + + pnm = pow_nbin_mask(n); + + m = t->t2 + rn_table[(hsn ^ (t->t1 & 63)) + t->t3]; + mp = m & pnm; + + if (mp < n) + s = mp; + else { + tp = t->t3 & pnm; + s = (mp + tp) % n; + } + + mai = (s + maio) % n; + } + + return arfcn_tbl ? arfcn_tbl[mai] : mai; +} + + +/* RF Channel parameters */ +void rfch_get_params(struct gsm_time *t, + uint16_t *arfcn_p, uint8_t *tsc_p, uint8_t *tn_p) +{ + if (l1s.dedicated.type == GSM_DCHAN_NONE) { + /* Serving cell only */ + if (arfcn_p) + *arfcn_p = l1s.serving_cell.arfcn; + + if (tsc_p) + *tsc_p = l1s.serving_cell.bsic & 0x7; + + if (tn_p) + *tn_p = 0; + } else { + /* Dedicated channel */ + if (arfcn_p) { + if (l1s.dedicated.h) { + *arfcn_p = rfch_hop_seq_gen(t, + l1s.dedicated.h1.hsn, + l1s.dedicated.h1.maio, + l1s.dedicated.h1.n, + l1s.dedicated.h1.ma); + } else { + *arfcn_p = l1s.dedicated.h0.arfcn; + } + } + + if (tsc_p) + *tsc_p = l1s.dedicated.tsc; + + if (tn_p) + *tn_p = l1s.dedicated.tn; + } +} + diff --git a/src/target/firmware/layer1/sched_gsmtime.c b/src/target/firmware/layer1/sched_gsmtime.c new file mode 100644 index 00000000..01e22ca3 --- /dev/null +++ b/src/target/firmware/layer1/sched_gsmtime.c @@ -0,0 +1,119 @@ +/* GSM-Time One-shot Event Scheduler Implementation (on top of TDMA sched) */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdint.h> +#include <errno.h> + +#include <debug.h> +#include <osmocom/core/linuxlist.h> + +#include <layer1/tdma_sched.h> +#include <layer1/sched_gsmtime.h> + +static struct sched_gsmtime_event sched_gsmtime_events[16]; +static LLIST_HEAD(active_evts); +static LLIST_HEAD(inactive_evts); + +/* Scheduling of a tdma_sched_item list one-shot at a given GSM time */ +int sched_gsmtime(const struct tdma_sched_item *si, uint32_t fn, uint16_t p3) +{ + struct llist_head *lh; + struct sched_gsmtime_event *evt, *cur; + + printd("sched_gsmtime(si=%p, fn=%u)\n", si, fn); + + /* obtain a free/inactive event structure */ + if (llist_empty(&inactive_evts)) + return -EBUSY; + lh = inactive_evts.next; + llist_del(lh); + evt = llist_entry(lh, struct sched_gsmtime_event, list); + + evt->fn = fn; + evt->si = si; + evt->p3 = p3; + + /* do a sorted insert into the list, i.e. insert the new + * event _before_ the first entry that has a higher fn */ + llist_for_each_entry(cur, &active_evts, list) { + if (cur->fn > evt->fn) { + llist_add_tail(lh, &cur->list); + return 0; + } + } + + /* if we reach here, active_evts is empty _OR_ new event + * is after all the other events: append at end of list */ + llist_add_tail(lh, &active_evts); + + return 0; +} + +/* how many TDMA frame ticks should we schedule events ahead? */ +#define SCHEDULE_AHEAD 2 + +/* how long do we need to tell the DSP in advance what we want to do? */ +#define SCHEDULE_LATENCY 1 + +/* execute all GSMTIME one-shot events pending for 'fn' */ +int sched_gsmtime_execute(uint32_t fn) +{ + struct sched_gsmtime_event *evt, *evt2; + int num = 0; + + llist_for_each_entry_safe(evt, evt2, &active_evts, list) { + if (evt->fn == fn + SCHEDULE_AHEAD) { + printd("sched_gsmtime_execute(time=%u): fn=%u si=%p\n", fn, evt->fn, evt->si); + tdma_schedule_set(SCHEDULE_AHEAD-SCHEDULE_LATENCY, + evt->si, evt->p3); + llist_del(&evt->list); + /* put event back in list of inactive (free) events */ + llist_add(&evt->list, &inactive_evts); + num++; + } if (evt->fn > fn + SCHEDULE_AHEAD) { + /* break the loop as our list is ordered */ + break; + } + } + return num; +} + +void sched_gsmtime_init(void) +{ + unsigned int i; + + printd("sched_gsmtime_init()\n"); + + for (i = 0; i < ARRAY_SIZE(sched_gsmtime_events); i++) + llist_add(&sched_gsmtime_events[i].list, &inactive_evts); +} + +void sched_gsmtime_reset(void) +{ + struct sched_gsmtime_event *evt, *evt2; + + llist_for_each_entry_safe(evt, evt2, &active_evts, list) { + llist_del(&evt->list); + /* put event back in list of inactive (free) events */ + llist_add(&evt->list, &inactive_evts); + } +} diff --git a/src/target/firmware/layer1/sync.c b/src/target/firmware/layer1/sync.c new file mode 100644 index 00000000..36f42975 --- /dev/null +++ b/src/target/firmware/layer1/sync.c @@ -0,0 +1,402 @@ +/* Synchronous part of GSM Layer 1 */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * (C) 2010 by Dieter Spaar <spaar@mirider.augusta.de> + * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org> + * + * 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 <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <defines.h> +#include <debug.h> +#include <memory.h> +#include <byteorder.h> +#include <asm/system.h> + +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/core/msgb.h> +#include <calypso/dsp_api.h> +#include <calypso/irq.h> +#include <calypso/tpu.h> +#include <calypso/tsp.h> +#include <calypso/dsp.h> +#include <calypso/timer.h> +#include <comm/sercomm.h> + +#include <abb/twl3025.h> + +//#define DEBUG_EVERY_TDMA + +#include <layer1/sync.h> +#include <layer1/afc.h> +#include <layer1/agc.h> +#include <layer1/apc.h> +#include <layer1/tdma_sched.h> +#include <layer1/mframe_sched.h> +#include <layer1/sched_gsmtime.h> +#include <layer1/tpu_window.h> +#include <layer1/l23_api.h> + +#include <l1ctl_proto.h> + +struct l1s_state l1s; + +void l1s_time_inc(struct gsm_time *time, uint32_t delta_fn) +{ + ADD_MODULO(time->fn, delta_fn, GSM_MAX_FN); + + if (delta_fn == 1) { + ADD_MODULO(time->t2, 1, 26); + ADD_MODULO(time->t3, 1, 51); + + /* if the new frame number is a multiple of 51 */ + if (time->t3 == 0) { + ADD_MODULO(time->tc, 1, 8); + + /* if new FN is multiple of 51 and 26 */ + if (time->t2 == 0) + ADD_MODULO(time->t1, 1, 2048); + } + } else + gsm_fn2gsmtime(time, time->fn); +} + +void l1s_time_dump(const struct gsm_time *time) +{ + printf("fn=%lu(%u/%2u/%2u)", time->fn, time->t1, time->t2, time->t3); +} + +/* clip a signed 16bit value at a certain limit */ +int16_t clip_int16(int16_t angle, int16_t clip_at) +{ + if (angle > clip_at) + angle = clip_at; + else if (angle < -clip_at) + angle = -clip_at; + + return angle; +} + +int16_t l1s_snr_int(uint16_t snr) +{ + return snr >> 10; +} + +uint16_t l1s_snr_fract(uint16_t snr) +{ + uint32_t fract = snr & 0x3ff; + fract = fract * 1000 / (2 << 10); + + return fract & 0xffff; +} + +#define AFC_MAX_ANGLE 328 /* 0.01 radian in fx1.15 */ + +/* synchronize the L1S to a new timebase (typically a new cell */ +void synchronize_tdma(struct l1_cell_info *cinfo) +{ + int32_t fn_offset; + uint32_t tpu_shift = cinfo->time_alignment; + + /* NB detection only works if the TOA of the SB + * is within 0...8. We have to add 75 to get an SB TOA of 4. */ + tpu_shift += 75; + + tpu_shift = (l1s.tpu_offset + tpu_shift) % QBITS_PER_TDMA; + + fn_offset = cinfo->fn_offset - 1; + + /* if we're already very close to the end of the TPU frame, the + * next interrupt will basically occur now and we need to + * compensate */ + if (tpu_shift < SWITCH_TIME) + fn_offset++; + +#if 0 /* probably wrong as we already added "offset" and "shift" above */ + /* increment the TPU quarter-bit offset */ + l1s.tpu_offset = (l1s.tpu_offset + tpu_shift) % TPU_RANGE; +#else + l1s.tpu_offset = tpu_shift; +#endif + + puts("Synchronize_TDMA\n"); + /* request the TPU to adjust the SYNCHRO and OFFSET registers */ + tpu_enq_at(SWITCH_TIME); + tpu_enq_sync(l1s.tpu_offset); +#if 0 + /* FIXME: properly end the TPU window at the emd of l1_sync() */ + tpu_end_scenario(); +#endif + + /* Change the current time to reflect the new value */ + l1s_time_inc(&l1s.current_time, fn_offset); + l1s.next_time = l1s.current_time; + l1s_time_inc(&l1s.next_time, 1); + + /* The serving cell now no longer has a frame or bit offset */ + cinfo->fn_offset = 0; + cinfo->time_alignment = 0; +} + +void l1s_reset_hw(void) +{ + dsp_api.w_page = 0; + dsp_api.r_page = 0; + dsp_api.r_page_used = 0; + dsp_api.db_w = (T_DB_MCU_TO_DSP *) BASE_API_W_PAGE_0; + dsp_api.db_r = (T_DB_DSP_TO_MCU *) BASE_API_R_PAGE_0; + dsp_api.ndb->d_dsp_page = 0; + + /* we have to really reset the TPU, otherwise FB detection + * somtimes returns wrong TOA values. */ + tpu_reset(1); + tpu_reset(0); + tpu_rewind(); + tpu_enq_wait(5); /* really needed ? */ + tpu_enq_sync(l1s.tpu_offset); + tpu_end_scenario(); +} + +/* Lost TDMA interrupt detection. This works by starting a hardware timer + * that is clocked by the same master clock source (VCTCXO). We expect + * 1875 timer ticks in the duration of a TDMA frame (5000 qbits / 1250 bits) */ + +/* Timer for detecting lost IRQ */ +#define TIMER_TICKS_PER_TDMA 1875 +#define TIMER_TICK_JITTER 1 + +static int last_timestamp; + +static inline void check_lost_frame(void) +{ + int diff, timestamp = hwtimer_read(1); + + if (last_timestamp < timestamp) + last_timestamp += (4*TIMER_TICKS_PER_TDMA); + + diff = last_timestamp - timestamp; + + /* allow for a bit of jitter */ + if (diff < TIMER_TICKS_PER_TDMA - TIMER_TICK_JITTER || + diff > TIMER_TICKS_PER_TDMA + TIMER_TICK_JITTER) + printf("LOST %d!\n", diff); + + last_timestamp = timestamp; +} + +/* schedule a completion */ +void l1s_compl_sched(enum l1_compl c) +{ + unsigned long flags; + + local_firq_save(flags); + l1s.scheduled_compl |= (1 << c); + local_irq_restore(flags); +} + +/* main routine for synchronous part of layer 1, called by frame interrupt + * generated by TPU once every TDMA frame */ +static void l1_sync(void) +{ + uint16_t sched_flags; + + putchart('+'); + + check_lost_frame(); + + /* Increment Time */ + l1s.current_time = l1s.next_time; + l1s_time_inc(&l1s.next_time, 1); + //l1s_time_dump(&l1s.current_time); putchar(' '); + + dsp_api.frame_ctr++; + dsp_api.r_page_used = 0; + + /* Update pointers */ + if (dsp_api.w_page == 0) + dsp_api.db_w = (T_DB_MCU_TO_DSP *) BASE_API_W_PAGE_0; + else + dsp_api.db_w = (T_DB_MCU_TO_DSP *) BASE_API_W_PAGE_1; + + if (dsp_api.r_page == 0) + dsp_api.db_r = (T_DB_DSP_TO_MCU *) BASE_API_R_PAGE_0; + else + dsp_api.db_r = (T_DB_DSP_TO_MCU *) BASE_API_R_PAGE_1; + + /* Reset MCU->DSP page */ + dsp_api_memset((uint16_t *) dsp_api.db_w, sizeof(*dsp_api.db_w)); + + /* Update AFC */ + afc_load_dsp(); + + if (dsp_api.ndb->d_error_status) { + printf("DSP Error Status: %u\n", dsp_api.ndb->d_error_status); + dsp_api.ndb->d_error_status = 0; + } + + /* execute the sched_items that have been scheduled for this + * TDMA frame (including setup/cleanup steps) */ + sched_flags = tdma_sched_flag_scan(); + + if (sched_flags & TDMA_IFLG_TPU) + l1s_win_init(); + + tdma_sched_execute(); + + if (dsp_api.r_page_used) { + /* clear and switch the read page */ + dsp_api_memset((uint16_t *) dsp_api.db_r, + sizeof(*dsp_api.db_r)); + + /* TSM30 does it (really needed ?): + * Set crc result as "SB not found". */ + dsp_api.db_r->a_sch[0] = (1<<B_SCH_CRC); /* B_SCH_CRC =1, BLUD =0 */ + + dsp_api.r_page ^= 1; + } + + if (sched_flags & TDMA_IFLG_DSP) + dsp_end_scenario(); + + if (sched_flags & TDMA_IFLG_TPU) + tpu_end_scenario(); + + /* schedule new / upcoming TDMA items */ + mframe_schedule(); + /* schedule new / upcoming one-shot events */ + sched_gsmtime_execute(l1s.current_time.fn); + + tdma_sched_advance(); +} + +/* ABORT command ********************************************************/ + +static int l1s_abort_cmd(__unused uint8_t p1, __unused uint8_t p2, + __unused uint16_t p3) +{ + putchart('A'); + + /* similar to l1s_reset_hw() without touching the TPU */ + + dsp_api.w_page = 0; + dsp_api.r_page = 0; + dsp_api.r_page_used = 0; + dsp_api.db_w = (T_DB_MCU_TO_DSP *) BASE_API_W_PAGE_0; + dsp_api.db_r = (T_DB_DSP_TO_MCU *) BASE_API_R_PAGE_0; + + /* Reset task commands. */ + dsp_api.db_w->d_task_d = NO_DSP_TASK; /* Init. RX task to NO TASK */ + dsp_api.db_w->d_task_u = NO_DSP_TASK; /* Init. TX task to NO TASK */ + dsp_api.db_w->d_task_ra = NO_DSP_TASK; /* Init. RA task to NO TASK */ + dsp_api.db_w->d_task_md = NO_DSP_TASK; /* Init. MONITORING task to NO TASK */ + dsp_api.ndb->d_dsp_page = 0; + + /* Set "b_abort" to TRUE, dsp will reset current and pending tasks */ + dsp_api.db_w->d_ctrl_system |= (1 << B_TASK_ABORT); + return 0; +} + +void l1s_dsp_abort(void) +{ + /* abort right now */ + tdma_schedule(0, &l1s_abort_cmd, 0, 0, 0, 10); +} + +void l1s_tx_apc_helper(uint16_t arfcn) +{ + int16_t auxapc; + enum gsm_band band; + int i; + + /* Get DAC setting */ + band = gsm_arfcn2band(arfcn); + auxapc = apc_tx_pwrlvl2auxapc(band, l1s.tx_power); + + /* Load the ApcOffset into the DSP */ + #define MY_OFFSET 4 + dsp_api.ndb->d_apcoff = ABB_VAL(APCOFF, (1 << 6) | MY_OFFSET) | 1; /* 2x slope for the GTA-02 ramp */ + + /* Load the TX Power into the DSP */ + /* + If the power is too low (below 0 dBm) the ramp is not OK, + especially for GSM-1800. However an MS does not send below + 0dBm anyway. + */ + dsp_api.db_w->d_power_ctl = ABB_VAL(AUXAPC, auxapc); + + /* Update the ramp according to the PCL */ + for (i = 0; i < 16; i++) + dsp_api.ndb->a_ramp[i] = ABB_VAL(APCRAM, twl3025_default_ramp[i]); + + /* The Ramp Table is sent to ABB only once after RF init routine called */ + dsp_api.db_w->d_ctrl_abb |= (1 << B_RAMP) | (1 << B_BULRAMPDEL); +} + +/* Interrupt handler */ +static void frame_irq(__unused enum irq_nr nr) +{ + l1_sync(); +} + +/* reset the layer1 as part of synchronizing to a new cell */ +void l1s_reset(void) +{ + /* Reset state */ + l1s.fb.mode = 0; + l1s.tx_power = 7; /* initial power reset */ + + /* Leave dedicated mode */ + l1s.dedicated.type = GSM_DCHAN_NONE; + + /* reset scheduler and hardware */ + sched_gsmtime_reset(); + mframe_reset(); + tdma_sched_reset(); + l1s_dsp_abort(); + + /* Cipher off */ + dsp_load_ciph_param(0, NULL); +} + +void l1s_init(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(l1s.tx_queue); i++) + INIT_LLIST_HEAD(&l1s.tx_queue[i]); + l1s.tx_meas = NULL; + + sched_gsmtime_init(); + + /* register FRAME interrupt as FIQ so it can interrupt normal IRQs */ + irq_register_handler(IRQ_TPU_FRAME, &frame_irq); + irq_config(IRQ_TPU_FRAME, 1, 1, 0); + irq_enable(IRQ_TPU_FRAME); + + /* configure timer 1 to be auto-reload and have a prescale of 12 (13MHz/12 == qbit clock) */ + hwtimer_enable(1, 1); + hwtimer_load(1, (1875*4)-1); + hwtimer_config(1, 0, 1); + hwtimer_enable(1, 1); +} + diff --git a/src/target/firmware/layer1/tdma_sched.c b/src/target/firmware/layer1/tdma_sched.c new file mode 100644 index 00000000..88129922 --- /dev/null +++ b/src/target/firmware/layer1/tdma_sched.c @@ -0,0 +1,244 @@ +/* TDMA Scheduler Implementation */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdint.h> +#include <stdio.h> +#include <string.h> + +#include <defines.h> +#include <debug.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <layer1/tdma_sched.h> +#include <layer1/sync.h> + +#include <calypso/dsp.h> + +/* dummy function to mark end of set */ +int tdma_end_set(__unused uint8_t p1, __unused uint8_t p2, + __unused uint16_t p3) +{ + return 0; +} + +static uint8_t wrap_bucket(uint8_t offset) +{ + uint16_t bucket; + + bucket = (l1s.tdma_sched.cur_bucket + offset) + % ARRAY_SIZE(l1s.tdma_sched.bucket); + + return bucket; +} + +/* Schedule an item at 'frame_offset' TDMA frames in the future */ +int tdma_schedule(uint8_t frame_offset, tdma_sched_cb *cb, + uint8_t p1, uint8_t p2, uint16_t p3, int16_t prio) +{ + struct tdma_scheduler *sched = &l1s.tdma_sched; + uint8_t bucket_nr = wrap_bucket(frame_offset); + struct tdma_sched_bucket *bucket = &sched->bucket[bucket_nr]; + struct tdma_sched_item *sched_item; + + if (bucket->num_items >= ARRAY_SIZE(bucket->item)) { + puts("tdma_schedule bucket overflow\n"); + return -1; + } + + sched_item = &bucket->item[bucket->num_items++]; + + sched_item->cb = cb; + sched_item->p1 = p1; + sched_item->p2 = p2; + sched_item->p3 = p3; + sched_item->prio = prio; + + return 0; +} + +/* Schedule a set of items starting from 'frame_offset' TDMA frames in the future */ +int tdma_schedule_set(uint8_t frame_offset, const struct tdma_sched_item *item_set, uint16_t p3) +{ + struct tdma_scheduler *sched = &l1s.tdma_sched; + uint8_t bucket_nr = wrap_bucket(frame_offset); + int i, j; + + for (i = 0, j = 0; 1; i++) { + const struct tdma_sched_item *sched_item = &item_set[i]; + struct tdma_sched_bucket *bucket = &sched->bucket[bucket_nr]; + + if (sched_item->cb == &tdma_end_set) { + /* end of scheduler set, return */ + break; + } + + if (sched_item->cb == NULL) { + /* advance to next bucket (== TDMA frame) */ + bucket_nr = wrap_bucket(++frame_offset); + j++; + continue; + } + /* check for bucket overflow */ + if (bucket->num_items >= ARRAY_SIZE(bucket->item)) { + puts("tdma_schedule bucket overflow\n"); + return -1; + } + /* copy the item from the set into the current bucket item position */ + memcpy(&bucket->item[bucket->num_items], sched_item, sizeof(*sched_item)); + bucket->item[bucket->num_items].p3 = p3; + bucket->num_items++; + } + + return j; +} + +/* Advance TDMA scheduler to the next bucket */ +void tdma_sched_advance(void) +{ + struct tdma_scheduler *sched = &l1s.tdma_sched; + uint8_t next_bucket; + + /* advance to the next bucket */ + next_bucket = wrap_bucket(1); + sched->cur_bucket = next_bucket; +} + +/* Scan current frame scheduled items for flags */ +uint16_t tdma_sched_flag_scan(void) +{ + struct tdma_scheduler *sched = &l1s.tdma_sched; + struct tdma_sched_bucket *bucket; + int i; + uint16_t flags = 0; + + /* determine current bucket */ + bucket = &sched->bucket[sched->cur_bucket]; + + /* iterate over items in this bucket and call callback function */ + for (i=0; i<bucket->num_items; i++) { + struct tdma_sched_item *item = &bucket->item[i]; + flags |= item->flags; + } + + return flags; +} + +/* Sort a bucket entries by priority */ +static void _tdma_sched_bucket_sort(struct tdma_sched_bucket *bucket, int *seq) +{ + int i, j, k; + struct tdma_sched_item *item_i, *item_j; + + /* initial sequence */ + /* we need all the items because some call back may schedule + * new call backs 'on the fly' */ + for (i=0; i<TDMASCHED_NUM_CB; i++) + seq[i] = i; + + /* iterate over items in this bucket and sort them */ + for (i=0; i<bucket->num_items; i++) + { + item_i = &bucket->item[seq[i]]; + + for (j=i+1; j<bucket->num_items; j++) + { + item_j = &bucket->item[seq[j]]; + + if (item_i->prio > item_j->prio) + { + item_i = item_j; + k = seq[i]; + seq[i] = seq[j]; + seq[j] = k; + } + } + } +} + +/* Execute pre-scheduled events for current frame */ +int tdma_sched_execute(void) +{ + struct tdma_scheduler *sched = &l1s.tdma_sched; + struct tdma_sched_bucket *bucket; + int i, num_events = 0; + int seq[TDMASCHED_NUM_CB]; + + /* determine current bucket */ + bucket = &sched->bucket[sched->cur_bucket]; + + /* get sequence in priority order */ + _tdma_sched_bucket_sort(bucket, seq); + + /* iterate over items in this bucket and call callback function */ + for (i = 0; i < bucket->num_items; i++) { + struct tdma_sched_item *item = &bucket->item[seq[i]]; + int rc; + + num_events++; + + rc = item->cb(item->p1, item->p2, item->p3); + if (rc < 0) { + printf("Error %d during processing of item %u of bucket %u\n", + rc, i, sched->cur_bucket); + return rc; + } + /* if the cb() we just called has scheduled more items for the + * current TDMA, bucket->num_items will have increased and we + * will simply continue to execute them as intended. Priorities + * won't work though ! */ + } + + /* clear/reset the bucket */ + bucket->num_items = 0; + + /* return number of items that we called */ + return num_events; +} + +void tdma_sched_reset(void) +{ + struct tdma_scheduler *sched = &l1s.tdma_sched; + unsigned int bucket_nr; + + for (bucket_nr = 0; bucket_nr < ARRAY_SIZE(sched->bucket); bucket_nr++) { + struct tdma_sched_bucket *bucket = &sched->bucket[bucket_nr]; + /* current bucket will be reset by iteration code above! */ + if (bucket_nr != sched->cur_bucket) + bucket->num_items = 0; + } + + /* Don't reset cur_bucket, as it would upset the bucket iteration code + * in tdma_sched_execute() */ +} + +void tdma_sched_dump(void) +{ + unsigned int i; + + printf("\n(%2u)", l1s.tdma_sched.cur_bucket); + for (i = 0; i < ARRAY_SIZE(l1s.tdma_sched.bucket); i++) { + int bucket_nr = wrap_bucket(i); + struct tdma_sched_bucket *bucket = &l1s.tdma_sched.bucket[bucket_nr]; + printf("%u:", bucket->num_items); + } + putchar('\n'); +} diff --git a/src/target/firmware/layer1/toa.c b/src/target/firmware/layer1/toa.c new file mode 100644 index 00000000..7d80d952 --- /dev/null +++ b/src/target/firmware/layer1/toa.c @@ -0,0 +1,80 @@ +/* AFC (Automatic Frequency Correction) Implementation */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * (C) 2011 by Andreas Eversberg <jolly@eversberg.eu> + * + * 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 <stdint.h> +#include <stdio.h> + +#include <debug.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <layer1/toa.h> +#include <layer1/avg.h> +#include <layer1/sync.h> + +/* Over how many TDMA frames do we want to average? */ +#define TOA_PERIOD 250 +/* How many of our measurements have to be valid? */ +#define TOA_MIN_MUN_VALID 125 + +// FIXME: +#define TOA_SNR_THRESHOLD 2560 /* 2.5 dB in fx6.10 */ + +struct toa_state { + struct running_avg ravg; /* running average */ +}; + + +static void toa_ravg_output(struct running_avg *ravg, int32_t avg); + +static struct toa_state toa_state = { + .ravg = { + .outfn = &toa_ravg_output, + .period = TOA_PERIOD, + .min_valid = TOA_MIN_MUN_VALID, + }, +}; + +void toa_input(int32_t offset, uint32_t snr) +{ + int valid = 1; + + if (snr < TOA_SNR_THRESHOLD || offset < 0 || offset >31) + valid = 0; + runavg_input(&toa_state.ravg, offset, valid); + runavg_check_output(&toa_state.ravg); +} + +void toa_reset(void) +{ + toa_state.ravg.num_samples = toa_state.ravg.num_samples_valid = 0; + toa_state.ravg.acc_val = 0; +} + +/* callback function for runavg */ +static void toa_ravg_output(struct running_avg *ravg, int32_t avg) +{ + if (avg != 16) { + printf("TOA AVG is not 16 qbits, correcting (got %ld)\n", avg); + l1s.tpu_offset_correction = avg - 16; + } +} diff --git a/src/target/firmware/layer1/tpu_window.c b/src/target/firmware/layer1/tpu_window.c new file mode 100644 index 00000000..f4e76c16 --- /dev/null +++ b/src/target/firmware/layer1/tpu_window.c @@ -0,0 +1,175 @@ +/* TPU window control routines for Layer 1 */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdint.h> +#include <debug.h> +#include <defines.h> +#include <stdio.h> + +#include <rffe.h> +#include <calypso/tpu.h> +#include <calypso/tsp.h> +#include <abb/twl3025.h> +#include <rf/trf6151.h> + +#include <layer1/sync.h> +#include <layer1/tpu_window.h> +#include <layer1/rfch.h> + +/* all units in GSM quarter-bits (923.1ns) */ +#define L1_TDMA_LENGTH_Q 5000 +#define L1_BURST_LENGTH_Q 625 /* L1_TDMA_LENGTH_Q/8 */ + +#define L1_NB_MARGIN_Q (3 * 4) +#define L1_SB_MARGIN_Q (23 * 4) +#define L1_TAIL_DURATION_Q (3 * 4) + +/* Sample length as required by the Calypso DSP */ +#define L1_NB_DURATION_Q (L1_BURST_LENGTH_Q + 2 * L1_NB_MARGIN_Q - L1_TAIL_DURATION_Q) +#define L1_SB_DURATION_Q (L1_BURST_LENGTH_Q + 2 * L1_SB_MARGIN_Q - L1_TAIL_DURATION_Q) +#define L1_FB_DURATION_Q (11 * L1_TDMA_LENGTH_Q + 2057) /* more than 11 full slots */ +#define L1_FB26_DURATION_Q (L1_TDMA_LENGTH_Q + 798) +#define L1_PW_DURATION_Q 289 + +#define DSP_SETUP_TIME 66 + +static const uint16_t rx_burst_duration[_NUM_L1_RXWIN] = { + [L1_RXWIN_PW] = L1_PW_DURATION_Q, + [L1_RXWIN_FB] = L1_FB_DURATION_Q, + [L1_RXWIN_SB] = L1_SB_DURATION_Q, + [L1_RXWIN_NB] = L1_NB_DURATION_Q, +}; + +#define L1_TX_NB_DURATION_Q 626 +#define L1_TX_AB_DURATION_Q 386 + +static const uint16_t tx_burst_duration[_NUM_L1_TXWIN] = { + [L1_TXWIN_NB] = L1_TX_NB_DURATION_Q, + [L1_TXWIN_AB] = L1_TX_AB_DURATION_Q, +}; + + +static int _win_setup(__unused uint8_t p1, __unused uint8_t p2, __unused uint16_t p3) +{ + uint8_t tn; + + rfch_get_params(&l1s.next_time, NULL, NULL, &tn); + + l1s.tpu_offset = (5000 + l1s.tpu_offset + l1s.tpu_offset_correction) % 5000; + l1s.tpu_offset_correction = 0; + + tpu_enq_at(4740); + tpu_enq_sync((5000 + l1s.tpu_offset + (L1_BURST_LENGTH_Q * tn)) % 5000); + + return 0; +} + +static int _win_cleanup(__unused uint8_t p1, __unused uint8_t p2, __unused uint16_t p3) +{ + uint8_t tn; + + rfch_get_params(&l1s.next_time, NULL, NULL, &tn); + + /* restore offset */ + tpu_enq_offset((5000 + l1s.tpu_offset + (L1_BURST_LENGTH_Q * tn)) % 5000); + + return 0; +} + +void l1s_win_init(void) +{ + tdma_schedule(0, _win_setup, 0, 0, 0, -2); + tdma_schedule(0, _win_cleanup, 0, 0, 0, 9); +} + +void l1s_rx_win_ctrl(uint16_t arfcn, enum l1_rxwin_type wtype, uint8_t tn_ofs) +{ + int16_t start; + int32_t stop; /* prevent overflow of int16_t in L1_RXWIN_FB */ + + /* TN offset & TA adjust */ + start = DSP_SETUP_TIME; + start += L1_BURST_LENGTH_Q * tn_ofs; + + stop = start + rx_burst_duration[wtype] - 1; + + /* window open for TRF6151 */ + /* FIXME: why do we need the magic value 100 ? */ + rffe_mode(gsm_arfcn2band(arfcn), 0); + trf6151_rx_window(start - 100, arfcn); + + /* Window open for ABB */ + twl3025_downlink(1, start); + + /* Delay 11 full TDMA frames */ + if (wtype == L1_RXWIN_FB) { + uint8_t i; + for (i = 0; i < 11; i++) + tpu_enq_at(0); + + stop -= 11 * L1_TDMA_LENGTH_Q; + } + + /* Window close for ABB */ + twl3025_downlink(0, stop & 0xffff); + + /* window close for TRF6151 */ + trf6151_set_mode(TRF6151_IDLE); +} + +void l1s_tx_win_ctrl(uint16_t arfcn, enum l1_txwin_type wtype, uint8_t pwr, uint8_t tn_ofs) +{ + uint16_t offset; + + /* TN offset & TA adjust */ + offset = 28; /* ("+ 32" gives a TA of 1) */ + offset += L1_BURST_LENGTH_Q * tn_ofs; + offset -= l1s.ta << 2; + +#ifdef CONFIG_TX_ENABLE + /* window open for TRF6151 */ + trf6151_tx_window(offset, arfcn); +#endif + + /* Window open for ABB */ + twl3025_uplink(1, offset); + +#ifdef CONFIG_TX_ENABLE + /* Window open for RFFE */ + rffe_mode(gsm_arfcn2band(arfcn), 1); +#endif + + /* Window close for ABB */ + twl3025_uplink(0, tx_burst_duration[wtype] + offset + 2); // TODO: "+ 2" + + /* window close for TRF6151 */ + trf6151_set_mode(TRF6151_IDLE); + + /* Window close for RFFE */ + rffe_mode(gsm_arfcn2band(arfcn), 0); +} + +void tpu_end_scenario(void) +{ + tpu_enq_sleep(); + tpu_enable(1); +} |