/* Synchronous part of GSM Layer 1 */ /* (C) 2010 by Harald Welte * (C) 2010 by Dieter Spaar * (C) 2010 by Holger Hans Peter Freyther * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define DEBUG_EVERY_TDMA /* A debug macro to print every TDMA frame */ #ifdef DEBUG_EVERY_TDMA #define putchart(x) putchar(x) #else #define putchart(x) #endif struct l1s_state l1s; static l1s_cb_t l1s_cb = NULL; void l1s_set_handler(l1s_cb_t cb) { l1s_cb = cb; } #define ADD_MODULO(sum, delta, modulo) do { \ if ((sum += delta) >= modulo) \ sum -= modulo; \ } while (0) #define GSM_MAX_FN (26*51*2048) static 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); } static void l1s_time_dump(const struct gsm_time *time) { printf("fn=%u(%u/%2u/%2u)", time->fn, time->t1, time->t2, time->t3); } /* 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 int last_task_fnr; extern uint16_t rf_arfcn; // TODO /* 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; } 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; } /* Convert an angle in fx1.15 notatinon into Hz */ #define BITFREQ_DIV_2PI 43104 /* 270kHz / 2 * pi */ #define BITFREQ_DIV_PI 86208 /* 270kHz / pi */ #define ANG2FREQ_SCALING (2<<15) /* 2^15 scaling factor for fx1.15 */ #define ANGLE_TO_FREQ(angle) ((int16_t)angle * BITFREQ_DIV_PI / ANG2FREQ_SCALING) #define AFC_MAX_ANGLE 328 /* 0.01 radian in fx1.15 */ #define AFC_SNR_THRESHOLD 2560 /* 2.5 dB in fx6.10 */ #define BITS_PER_TDMA 1250 #define QBITS_PER_TDMA (BITS_PER_TDMA * 4) /* 5000 */ #define TPU_RANGE QBITS_PER_TDMA #define SWITCH_TIME (TPU_RANGE-10) /* synchronize the L1S to a new timebase (typically a new cell */ static 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; } static 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_offset(l1s.tpu_offset); tpu_end_scenario(); } 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; }; 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 ", fb->fnr_report, fb->attempt, fb->toa, agc_inp_dbm8_by_pm(fb->pm)/8, ANGLE_TO_FREQ(fb->angle)); #endif } static struct mon_state _last_fb, *last_fb = &_last_fb; static int read_fb_result(int attempt) { last_fb->toa = dsp_api.ndb->a_sync_demod[D_TOA]; last_fb->pm = dsp_api.ndb->a_sync_demod[D_PM]>>3; last_fb->angle = dsp_api.ndb->a_sync_demod[D_ANGLE]; last_fb->snr = dsp_api.ndb->a_sync_demod[D_SNR]; //last_fb->angle = clip_int16(last_fb->angle, AFC_MAX_ANGLE); last_fb->freq_diff = ANGLE_TO_FREQ(last_fb->angle); last_fb->fnr_report = l1s.current_time.fn; last_fb->attempt = attempt; dump_mon_state(last_fb); 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(last_fb->freq_diff, rf_arfcn); //tpu_dsp_frameirq_enable(); return 1; } static void read_sb_result(int attempt) { last_fb->toa = dsp_api.db_r->a_serv_demod[D_TOA]; last_fb->pm = dsp_api.db_r->a_serv_demod[D_PM]>>3; last_fb->angle = dsp_api.db_r->a_serv_demod[D_ANGLE]; last_fb->snr = dsp_api.db_r->a_serv_demod[D_SNR]; last_fb->freq_diff = ANGLE_TO_FREQ(last_fb->angle); last_fb->fnr_report = l1s.current_time.fn; last_fb->attempt = attempt; dump_mon_state(last_fb); if (last_fb->snr > AFC_SNR_THRESHOLD) afc_input(last_fb->freq_diff, rf_arfcn, 1); else afc_input(last_fb->freq_diff, rf_arfcn, 0); dsp_api.r_page_used = 1; } #define TIMER_TICKS_PER_TDMA 1875 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; if (diff != 1875) printf("LOST!\n"); last_timestamp = timestamp; } /* main routine for synchronous part of layer 1, called by frame interrupt * generated by TPU once every TDMA frame */ static void l1_sync(void) { 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 */ 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<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); } /* FCCH Burst *****************************************************************/ /* scheduler callback to issue a FB detection task to the DSP */ static int l1s_fbdet_cmd(__unused uint8_t p1, __unused uint8_t fb_mode, __unused uint16_t p3) { if (fb_mode == 0) { putchart('F'); } else { putchart('V'); } /* 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; dsp_end_scenario(); last_task_fnr = dsp_api.frame_ctr; /* Program TPU */ l1s_rx_win_ctrl(rf_arfcn, L1_RXWIN_FB); tpu_end_scenario(); return 0; } /* scheduler callback to check for a FB detection response */ static int l1s_fbdet_resp(__unused uint8_t p1, uint8_t attempt, __unused uint16_t p3) { int ntdma, qbits, fn_offset; putchart('f'); if (!dsp_api.ndb->d_fb_det) { /* we did not detect a FB, fall back to mode 0! */ if (attempt == 12) { /* If we don't reset here, we get DSP DMA errors */ tdma_sched_reset(); /* if we are already synchronized initially, * code below has set l1s.fb.mode to 1 and * we switch to the more narrow mode 1 */ l1s_fb_test(1, l1s.fb.mode); } return 0; } printf("FB%u ", dsp_api.ndb->d_fb_mode); read_fb_result(attempt); 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 - attempt + ntdma; int fnr_delta = last_fb->fnr_report - attempt; int bits_delta = fnr_delta * BITS_PER_TDMA; struct l1_cell_info *cinfo = &l1s.serving_cell; 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_task_fnr + last_fb->toa/BITS_PER_TDMA; printf("=>FB @ FNR %u fn_offset=%d qbits=%u\n", fb_fnr, fn_offset, qbits); } } if (dsp_api.frame_ctr > 500 && l1s.fb.mode == 0) { /* Don't synchronize_tdma() yet, it does probably not work * reliable due to the TPU reset) */ l1s_reset_hw(); tdma_sched_reset(); /* We've done more than 500 rounds of FB detection, so * the AGC should be synchronized and we switch to the * more narrow FB detection mode 1 */ l1s.fb.mode = 1; } else { /* We found a frequency burst, reset everything and start next task */ l1s_reset_hw(); tdma_sched_reset(); } #if 1 /* restart a SB or new FB detection task */ if (dsp_api.frame_ctr > 1000 && l1s.fb.mode == 1 && abs(last_fb->freq_diff) < 1000) { int delay; /* synchronize before reading SB */ synchronize_tdma(&l1s.serving_cell); delay = fn_offset + 11 - l1s.current_time.fn - 1; dsp_api.ndb->d_fb_det = 0; dsp_api.ndb->a_sync_demod[D_TOA] = 0; /* TSM30 does it (really needed ?) */ l1s.fb.mode = 0; l1s_sb_test(delay); } else #endif { /* If we don't reset here, we get DSP DMA errors */ tdma_sched_reset(); /* use FB_MODE_1 if we are within certain limits */ if (abs(last_fb->freq_diff < 2000)) l1s_fb_test(fn_offset + 10 - l1s.current_time.fn - 1, 1); else l1s_fb_test(fn_offset + 10 - l1s.current_time.fn - 1, 0); } return 0; } /* we don't really use this because we need to configure the fb_mode! */ static const struct tdma_sched_item fb_sched_set[] = { SCHED_ITEM(l1s_fbdet_cmd, 0, 0), SCHED_END_FRAME(), SCHED_END_FRAME(), SCHED_ITEM(l1s_fbdet_resp, 0, 1), SCHED_END_FRAME(), SCHED_ITEM(l1s_fbdet_resp, 0, 2), SCHED_END_FRAME(), SCHED_ITEM(l1s_fbdet_resp, 0, 3), SCHED_END_FRAME(), SCHED_ITEM(l1s_fbdet_resp, 0, 4), SCHED_END_FRAME(), SCHED_ITEM(l1s_fbdet_resp, 0, 5), SCHED_END_FRAME(), SCHED_ITEM(l1s_fbdet_resp, 0, 6), SCHED_END_FRAME(), SCHED_ITEM(l1s_fbdet_resp, 0, 7), SCHED_END_FRAME(), SCHED_ITEM(l1s_fbdet_resp, 0, 8), SCHED_END_FRAME(), SCHED_ITEM(l1s_fbdet_resp, 0, 9), SCHED_END_FRAME(), SCHED_ITEM(l1s_fbdet_resp, 0, 10), SCHED_END_FRAME(), SCHED_ITEM(l1s_fbdet_resp, 0, 11), SCHED_END_FRAME(), SCHED_ITEM(l1s_fbdet_resp, 0, 12), SCHED_END_FRAME(), SCHED_END_SET() }; void l1s_fb_test(uint8_t base_fn, uint8_t fb_mode) { #if 1 int i; /* schedule the FB detection command */ tdma_schedule(base_fn, &l1s_fbdet_cmd, 0, fb_mode, 0); /* schedule 12 attempts to read the result */ for (i = 1; i <= 12; i++) { uint8_t fn = base_fn + 1 + i; tdma_schedule(fn, &l1s_fbdet_resp, 0, i, 0); } #else /* use the new scheduler 'set' and simply schedule the whole set */ /* WARNING: we cannot set FB_MODE_1 this way !!! */ tdma_schedule_set(base_fn, fb_sched_set, 0); #endif } /* SCH Burst Detection ********************************************************/ static int sb_once = 0; static uint8_t sb_cnt; /* 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 extern const struct tdma_sched_item rach_sched_set_ul[]; static int l1s_sbdet_resp(__unused uint8_t p1, uint8_t attempt, __unused uint16_t p3) { uint32_t sb; uint8_t bsic; static struct gsm_time sb_time; 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<a_sch[3] | dsp_api.db_r->a_sch[4] << 16; bsic = l1s_decode_sb(&sb_time, sb); printf("=> SB 0x%08x: BSIC=%u ", sb, bsic); l1s_time_dump(&sb_time); l1s.serving_cell.bsic = 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); if (sb_cnt > 5 && sb_once == 0) { synchronize_tdma(&l1s.serving_cell); sb_once = 1; } /* if we have recived a SYNC burst, update our local GSM time */ gsm_fn2gsmtime(&l1s.current_time, sb_time.fn + SB2_LATENCY); /* compute next time from new current time */ l1s.next_time = l1s.current_time; l1s_time_inc(&l1s.next_time, 1); /* place it in the queue for the layer2 */ msg = l1_create_l2_msg(L1CTL_NEW_CCCH_RESP, sb_time.fn, last_fb->snr, rf_arfcn); l1 = (struct l1ctl_sync_new_ccch_resp *) msgb_put(msg, sizeof(*l1)); l1->bsic = bsic; l1_queue_for_l2(msg); #if 0 tdma_sched_reset(); #else /* 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(); } #endif if (sb_cnt > 10 && sb_time.t3 == 41) { l1s_reset_hw(); /* enable the MF Task for BCCH reading */ l1s.mf_tasks |= (1 << MF_TASK_BCCH_NORM); l1s.mf_tasks |= (1 << MF_TASK_CCCH_COMB); } else { /* We have just seen a SCH burst, we know the next one is not in * less than 7 TDMA frames from now */ l1s_sb_test(7); } return 0; } static int l1s_sbdet_cmd(__unused uint8_t p1, __unused uint8_t p2, __unused uint16_t p3) { putchart('S'); dsp_api.db_w->d_task_md = SB_DSP_TASK; dsp_api.ndb->d_fb_mode = 0; /* wideband search */ dsp_end_scenario(); last_task_fnr = dsp_api.frame_ctr; /* Program TPU */ l1s_rx_win_ctrl(rf_arfcn, L1_RXWIN_SB); tpu_end_scenario(); return 0; } void l1s_sb_test(uint8_t base_fn) { #if 1 /* This is how it is done by the TSM30 */ tdma_schedule(base_fn, &l1s_sbdet_cmd, 0, 1, 0); tdma_schedule(base_fn + 1, &l1s_sbdet_cmd, 0, 2, 0); tdma_schedule(base_fn + 3, &l1s_sbdet_resp, 0, 1, 0); tdma_schedule(base_fn + 4, &l1s_sbdet_resp, 0, 2, 0); #else tdma_schedule(base_fn, &l1s_sbdet_cmd, 0, 1, 0); tdma_schedule(base_fn + 1, &l1s_sbdet_resp, 0, 1, 0); tdma_schedule(base_fn + 2, &l1s_sbdet_resp, 0, 2, 0); #endif } /* Power Measurement **********************************************************/ /* scheduler callback to issue a power measurement task to the DSP */ static int l1s_pm_cmd(__unused uint8_t p1, __unused uint8_t p2, uint16_t arfcn) { putchart('P'); dsp_api.db_w->d_task_md = 2; dsp_api.ndb->d_fb_mode = 0; /* wideband search */ dsp_end_scenario(); last_task_fnr = dsp_api.frame_ctr; /* Program TPU */ //l1s_rx_win_ctrl(arfcn, L1_RXWIN_PW); l1s_rx_win_ctrl(arfcn, L1_RXWIN_NB); tpu_end_scenario(); return 0; } /* scheduler callback to read power measurement resposnse from the DSP */ static int l1s_pm_resp(__unused uint8_t p1, __unused uint8_t p2, uint16_t arfcn) { struct l1ctl_pm_resp *pmr; uint16_t pm_level[2]; struct l1_signal sig; putchart('p'); l1ddsp_meas_read(2, pm_level); printf("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); /* build and deliver signal */ sig.signum = L1_SIG_PM; sig.arfcn = arfcn; sig.pm.dbm8[0] = agc_inp_dbm8_by_pm(pm_level[0]); sig.pm.dbm8[1] = agc_inp_dbm8_by_pm(pm_level[1]); if (l1s_cb) l1s_cb(&sig); if (!l1s.pm.msg) l1s.pm.msg = l1ctl_msgb_alloc(L1CTL_PM_RESP); 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_RESP); } 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); pmr->pm[1] = dbm2rxlev(agc_inp_dbm8_by_pm(pm_level[1])/8); 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_test(1, l1s.pm.range.arfcn_next); 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; } void l1s_pm_test(uint8_t base_fn, uint16_t arfcn) { printf("l1s_pm_test(%u, %u)\n", base_fn, arfcn); tdma_schedule(base_fn, &l1s_pm_cmd, 0, 0, arfcn); tdma_schedule(base_fn + 2, &l1s_pm_resp, 0, 0, arfcn); } /* Normal Burst ***************************************************************/ static int l1s_nb_resp(__unused uint8_t p1, uint8_t burst_id, uint16_t p3) { static struct l1_signal _nb_sig, *sig = &_nb_sig; uint8_t mf_task_id = p3 & 0xff; uint8_t mf_task_flags = p3 >> 8; struct msgb *msg; 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 corespond 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; } sig->nb.meas[burst_id].toa_qbit = dsp_api.db_r->a_serv_demod[D_TOA]; sig->nb.meas[burst_id].pm_dbm8 = dsp_api.db_r->a_serv_demod[D_PM] >> 3; sig->nb.meas[burst_id].freq_err = ANGLE_TO_FREQ(dsp_api.db_r->a_serv_demod[D_ANGLE]); sig->nb.meas[burst_id].snr = dsp_api.db_r->a_serv_demod[D_SNR]; /* feed computed frequency error into AFC loop */ if (sig->nb.meas[burst_id].snr > AFC_SNR_THRESHOLD) afc_input(sig->nb.meas[burst_id].freq_err, rf_arfcn, 1); else afc_input(sig->nb.meas[burst_id].freq_err, rf_arfcn, 0); /* 4th burst, get frame data */ if (dsp_api.db_r->d_burst_d == 3) { struct l1ctl_hdr *l1h; struct l1ctl_info_dl *dl; struct l1ctl_data_ind *di; uint32_t avg_snr = 0; int32_t avg_dbm8 = 0; uint8_t i, j; sig->signum = L1_SIG_NB; sig->nb.num_biterr = dsp_api.ndb->a_cd[2] & 0xffff; sig->nb.crc = ((dsp_api.ndb->a_cd[0] & 0xffff) & ((1 << B_FIRE1) | (1 << B_FIRE0))) >> B_FIRE0; sig->nb.fire = ((dsp_api.ndb->a_cd[0] & 0xffff) & (1 << B_FIRE1)) >> B_FIRE1; /* copy actual data, skipping the information block [0,1,2] */ for (j = 0,i = 3; i < 15; i++) { sig->nb.frame[j++] = dsp_api.ndb->a_cd[i] & 0xFF; sig->nb.frame[j++] = (dsp_api.ndb->a_cd[i] >> 8) & 0xFF; } /* actually issue the signal */ if (l1s_cb) l1s_cb(sig); /* place it in the queue for the layer2 */ msg = l1_create_l2_msg(L1CTL_DATA_IND, l1s.current_time.fn-4, last_fb->snr, rf_arfcn); l1h = (struct l1ctl_hdr *) msg->l1h; dl = (struct l1ctl_info_dl *) l1h->data; di = (struct l1ctl_data_ind *) msgb_put(msg, sizeof(*di)); /* Set Channel Number depending on MFrame Task ID */ dl->chan_nr = mframe_task2chan_nr(mf_task_id, 0); /* FIXME: TS */ /* Set SACCH indication in Link IDentifier */ if (mf_task_flags & MF_F_SACCH) dl->link_id = 0x40; else dl->link_id = 0x00; /* compute average snr and rx level */ for (i = 0; i < 4; ++i) { avg_snr += sig->nb.meas[i].snr; avg_dbm8 += sig->nb.meas[i].pm_dbm8; } dl->snr = avg_snr / 4; dl->rx_level = (avg_dbm8 / (8*4)) + 110; /* copy the actual payload data */ for (i = 0; i < 23; ++i) di->data[i] = sig->nb.frame[i]; l1_queue_for_l2(msg); /* 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) { uint8_t tsc = l1s.serving_cell.bsic & 0x7; putchart('N'); dsp_load_rx_task(ALLC_DSP_TASK, burst_id, tsc); dsp_end_scenario(); l1s_rx_win_ctrl(rf_arfcn, L1_RXWIN_NB); tpu_end_scenario(); return 0; } const struct tdma_sched_item nb_sched_set[] = { SCHED_ITEM(l1s_nb_cmd, 0, 0), SCHED_END_FRAME(), SCHED_ITEM(l1s_nb_cmd, 0, 1), SCHED_END_FRAME(), SCHED_ITEM(l1s_nb_resp, 0, 0), SCHED_ITEM(l1s_nb_cmd, 0, 2), SCHED_END_FRAME(), SCHED_ITEM(l1s_nb_resp, 0, 1), SCHED_ITEM(l1s_nb_cmd, 0, 3), SCHED_END_FRAME(), SCHED_ITEM(l1s_nb_resp, 0, 2), SCHED_END_FRAME(), SCHED_ITEM(l1s_nb_resp, 0, 3), SCHED_END_FRAME(), SCHED_END_SET() }; /* Transmit Burst *************************************************************/ const uint8_t ubUui[23] = { 0x01, 0x03, 0x01, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b }; /* 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; return 0; } /* Channel type definitions for DEDICATED mode */ #define INVALID_CHANNEL 0 #define TCH_F 1 #define TCH_H 2 #define SDCCH_4 3 #define SDCCH_8 4 /* Channel mode definitions for DEDICATED mode */ #define SIG_ONLY_MODE 0 // signalling only #define TCH_FS_MODE 1 // speech full rate #define TCH_HS_MODE 2 // speech half rate #define TCH_96_MODE 3 // data 9,6 kb/s #define TCH_48F_MODE 4 // data 4,8 kb/s full rate #define TCH_48H_MODE 5 // data 4,8 kb/s half rate #define TCH_24F_MODE 6 // data 2,4 kb/s full rate #define TCH_24H_MODE 7 // data 2,4 kb/s half rate #define TCH_EFR_MODE 8 // enhanced full rate #define TCH_144_MODE 9 // data 14,4 kb/s half rate static uint8_t loc_cnt = 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) { int i; uint8_t tsc; uint8_t mf_task_id = p3 & 0xff; uint8_t mf_task_flags = p3 >> 8; putchart('T'); /* 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, 0xC0); /* 2 dBm pulse with offset 4 (GSM-1800) */ /* 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); if (p1 == 0) /* DUL_DSP_TASK, one normal burst */ dsp_load_tch_param(0, SIG_ONLY_MODE, INVALID_CHANNEL, 0, 0, 0); else if (p1 == 2) /* DUL_DSP_TASK, four normal bursts */ dsp_load_tch_param(0, SIG_ONLY_MODE, SDCCH_4, 0, 0, 0); /* before sending first of the four bursts, copy data to API ram */ if (burst_id == 0) { if (p1 == 0 || p1 == 2) { // DUL_DSP_TASK uint16_t *info_ptr = dsp_api.ndb->a_cu; struct llist_head *tx_queue; struct msgb *msg; const uint8_t *data; uint8_t j; /* distinguish between DCCH and ACCH */ if (mf_task_flags & MF_F_SACCH) { puts("SACCH queue "); tx_queue = &l1s.tx_queue[L1S_CHAN_SACCH]; } else { puts("SDCCH queue "); tx_queue = &l1s.tx_queue[L1S_CHAN_MAIN]; } msg = msgb_dequeue(tx_queue); /* If the TX queue is empty, send idle pattern */ if (!msg) { puts("TX idle pattern\n"); data = ubUui; } else { puts("TX uplink msg\n"); data = msg->l3h; } /* 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 first 22 bytes in the first 11 words after header. */ for (i=0, j=(3+0); j<(3+11); j++) { info_ptr[j] = ((uint16_t)(data[i])) | ((uint16_t)(data[i+1]) << 8); printf("%02x %02x ", data[i], data[i+1]); i += 2; } /* Copy last UWORD8 (23rd) in the 12th word after header. */ info_ptr[14] = data[22]; printf("%02x\n", data[22]); if (msg) msgb_free(msg); } else if (p1 == 1) { // RACH_DSP_TASK uint16_t *info_ptr; uint8_t data[2]; data[0] = l1s.serving_cell.bsic << 2; data[1] = 0x00 + loc_cnt; // channel request, location update, loc_cnt as random reference loc_cnt++; if (loc_cnt > 16) loc_cnt = 0; info_ptr = &dsp_api.ndb->d_rach; info_ptr[0] = ((uint16_t)(data[0])) | ((uint16_t)(data[1])<<8); } } if (p1 == 0 || p1 == 2) { tsc = 7; // !!!!! nanoBTS configuration for SDCCH 0 !!!!!!!! dsp_load_tx_task(DUL_DSP_TASK, burst_id, tsc); dsp_end_scenario(); l1s_tx_win_ctrl(rf_arfcn, L1_TXWIN_NB, 0); tpu_end_scenario(); } else if (p1 == 1) { dsp_api.db_w->d_task_ra = RACH_DSP_TASK; dsp_end_scenario(); l1s_tx_win_ctrl(rf_arfcn, L1_TXWIN_AB, 0); tpu_end_scenario(); } return 0; } 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); tdma_schedule(base_fn + 2, &l1s_tx_resp, 0, 0, 0); } else if (type == 1) { // one RACH burst tdma_schedule(base_fn, &l1s_tx_cmd, 1, 0, 0); tdma_schedule(base_fn + 2, &l1s_tx_resp, 1, 0, 0); } else if (type == 2) { // four normal burst tdma_schedule(base_fn, &l1s_tx_cmd, 2, 0, 0); tdma_schedule(base_fn + 1, &l1s_tx_cmd, 2, 1, 0); tdma_schedule(base_fn + 2, &l1s_tx_resp, 2, 0, 0); tdma_schedule(base_fn + 2, &l1s_tx_cmd, 2, 2, 0); tdma_schedule(base_fn + 3, &l1s_tx_resp, 2, 1, 0); tdma_schedule(base_fn + 3, &l1s_tx_cmd, 2, 3, 0); tdma_schedule(base_fn + 4, &l1s_tx_resp, 2, 2, 0); tdma_schedule(base_fn + 5, &l1s_tx_resp, 2, 3, 0); } } /* sched sets for uplink */ const struct tdma_sched_item rach_sched_set_ul[] = { SCHED_ITEM(l1s_tx_cmd, 1, 0), SCHED_END_FRAME(), SCHED_END_FRAME(), SCHED_ITEM(l1s_tx_resp, 1, 0), SCHED_END_FRAME(), SCHED_END_SET() }; const struct tdma_sched_item nb_sched_set_ul[] = { SCHED_ITEM(l1s_tx_cmd, 2, 0), SCHED_END_FRAME(), SCHED_ITEM(l1s_tx_cmd, 2, 1), SCHED_END_FRAME(), SCHED_ITEM(l1s_tx_resp, 2, 0), SCHED_ITEM(l1s_tx_cmd, 2, 2), SCHED_END_FRAME(), SCHED_ITEM(l1s_tx_resp, 2, 1), SCHED_ITEM(l1s_tx_cmd, 2, 3), SCHED_END_FRAME(), SCHED_ITEM(l1s_tx_resp, 2, 2), SCHED_END_FRAME(), SCHED_ITEM(l1s_tx_resp, 2, 3), SCHED_END_FRAME(), SCHED_END_SET() }; /* 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) { l1s.fb.mode = 0; sb_once = 0; /* reset scheduler and hardware */ l1s.mf_tasks = 0; tdma_sched_reset(); l1s_dsp_abort(); } void l1s_init(void) { unsigned int i; for (i = 0; i < ARRAY_SIZE(l1s.tx_queue); i++) INIT_LLIST_HEAD(&l1s.tx_queue[i]); 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); }