/* Interface handler for Sysmocom L1 */ /* (C) 2011 by Harald Welte * (C) 2014 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 Affero General Public License as published by * the Free Software Foundation; either version 3 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 Affero General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "femtobts.h" #include "l1_if.h" #include "l1_transp.h" #include "hw_misc.h" #include "misc/sysmobts_par.h" #include "eeprom.h" #include "utils.h" extern int pcu_direct; #define MIN_QUAL_RACH 5.0f /* at least 5 dB C/I */ #define MIN_QUAL_NORM -0.5f /* at least -1 dB C/I */ /* mapping from femtobts L1 SAPI to GSMTAP channel type */ static const uint8_t l1sapi2gsmtap_cht[GsmL1_Sapi_NUM] = { [GsmL1_Sapi_Idle] = 255, [GsmL1_Sapi_Fcch] = 255, [GsmL1_Sapi_Sch] = 255, [GsmL1_Sapi_Sacch] = GSMTAP_CHANNEL_SDCCH | GSMTAP_CHANNEL_ACCH, [GsmL1_Sapi_Sdcch] = GSMTAP_CHANNEL_SDCCH, [GsmL1_Sapi_Bcch] = GSMTAP_CHANNEL_BCCH, [GsmL1_Sapi_Pch] = GSMTAP_CHANNEL_PCH, [GsmL1_Sapi_Agch] = GSMTAP_CHANNEL_AGCH, [GsmL1_Sapi_Cbch] = GSMTAP_CHANNEL_CBCH51, [GsmL1_Sapi_Rach] = GSMTAP_CHANNEL_RACH, [GsmL1_Sapi_TchF] = 255, [GsmL1_Sapi_FacchF] = GSMTAP_CHANNEL_TCH_F, [GsmL1_Sapi_TchH] = 255, [GsmL1_Sapi_FacchH] = GSMTAP_CHANNEL_TCH_H, [GsmL1_Sapi_Nch] = GSMTAP_CHANNEL_CCCH, [GsmL1_Sapi_Pdtch] = GSMTAP_CHANNEL_PACCH, [GsmL1_Sapi_Pacch] = GSMTAP_CHANNEL_PACCH, [GsmL1_Sapi_Pbcch] = 255, [GsmL1_Sapi_Pagch] = 255, [GsmL1_Sapi_Ppch] = 255, [GsmL1_Sapi_Pnch] = 255, [GsmL1_Sapi_Ptcch] = GSMTAP_CHANNEL_PTCCH, [GsmL1_Sapi_Prach] = 255, }; static void tx_to_gsmtap(struct femtol1_hdl *fl1h, struct msgb *msg) { struct gsm_bts_trx *trx = fl1h->priv; GsmL1_Prim_t *l1p = msgb_l1prim(msg); GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq; if (fl1h->gsmtap) { uint8_t ss, chan_type; if (data_req->subCh == 0x1f) ss = 0; else ss = data_req->subCh; if (!(fl1h->gsmtap_sapi_mask & (1 << data_req->sapi))) return; chan_type = l1sapi2gsmtap_cht[data_req->sapi]; if (chan_type == 255) return; gsmtap_send(fl1h->gsmtap, trx->arfcn, data_req->u8Tn, chan_type, ss, data_req->u32Fn, 0, 0, data_req->msgUnitParam.u8Buffer, data_req->msgUnitParam.u8Size); } } static void ul_to_gsmtap(struct femtol1_hdl *fl1h, struct msgb *msg) { struct gsm_bts_trx *trx = fl1h->priv; GsmL1_Prim_t *l1p = msgb_l1prim(msg); GsmL1_PhDataInd_t *data_ind = &l1p->u.phDataInd; int skip = 0; if (fl1h->gsmtap) { uint8_t ss, chan_type; if (data_ind->subCh == 0x1f) ss = 0; else ss = data_ind->subCh; if (!(fl1h->gsmtap_sapi_mask & (1 << data_ind->sapi))) return; chan_type = l1sapi2gsmtap_cht[data_ind->sapi]; if (chan_type == 255) return; if (chan_type == GSMTAP_CHANNEL_PACCH || chan_type == GSMTAP_CHANNEL_PDCH) { if (data_ind->msgUnitParam.u8Buffer[0] != GsmL1_PdtchPlType_Full) return; skip = 1; } gsmtap_send(fl1h->gsmtap, trx->arfcn | GSMTAP_ARFCN_F_UPLINK, data_ind->u8Tn, chan_type, ss, data_ind->u32Fn, data_ind->measParam.fRssi, data_ind->measParam.fLinkQuality, data_ind->msgUnitParam.u8Buffer + skip, data_ind->msgUnitParam.u8Size - skip); } } struct wait_l1_conf { struct llist_head list; /* internal linked list */ struct osmo_timer_list timer; /* timer for L1 timeout */ unsigned int conf_prim_id; /* primitive we expect in response */ unsigned int is_sys_prim; /* is this a system (1) or L1 (0) primitive */ l1if_compl_cb *cb; void *cb_data; }; static void release_wlc(struct wait_l1_conf *wlc) { osmo_timer_del(&wlc->timer); talloc_free(wlc); } static void l1if_req_timeout(void *data) { struct wait_l1_conf *wlc = data; if (wlc->is_sys_prim) LOGP(DL1C, LOGL_FATAL, "Timeout waiting for SYS primitive %s\n", get_value_string(femtobts_sysprim_names, wlc->conf_prim_id)); else LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n", get_value_string(femtobts_l1prim_names, wlc->conf_prim_id)); exit(23); } static int _l1if_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg, int is_system_prim, l1if_compl_cb *cb, void *data) { struct wait_l1_conf *wlc; struct osmo_wqueue *wqueue; unsigned int timeout_secs; /* allocate new wsc and store reference to mutex and conf_id */ wlc = talloc_zero(fl1h, struct wait_l1_conf); wlc->cb = cb; wlc->cb_data = data; /* Make sure we actually have received a REQUEST type primitive */ if (is_system_prim == 0) { GsmL1_Prim_t *l1p = msgb_l1prim(msg); LOGP(DL1P, LOGL_INFO, "Tx L1 prim %s\n", get_value_string(femtobts_l1prim_names, l1p->id)); if (femtobts_l1prim_type[l1p->id] != L1P_T_REQ) { LOGP(DL1C, LOGL_ERROR, "L1 Prim %s is not a Request!\n", get_value_string(femtobts_l1prim_names, l1p->id)); talloc_free(wlc); return -EINVAL; } wlc->is_sys_prim = 0; wlc->conf_prim_id = femtobts_l1prim_req2conf[l1p->id]; wqueue = &fl1h->write_q[MQ_L1_WRITE]; timeout_secs = 30; } else { SuperFemto_Prim_t *sysp = msgb_sysprim(msg); LOGP(DL1C, LOGL_INFO, "Tx SYS prim %s\n", get_value_string(femtobts_sysprim_names, sysp->id)); if (femtobts_sysprim_type[sysp->id] != L1P_T_REQ) { LOGP(DL1C, LOGL_ERROR, "SYS Prim %s is not a Request!\n", get_value_string(femtobts_sysprim_names, sysp->id)); talloc_free(wlc); return -EINVAL; } wlc->is_sys_prim = 1; wlc->conf_prim_id = femtobts_sysprim_req2conf[sysp->id]; wqueue = &fl1h->write_q[MQ_SYS_WRITE]; timeout_secs = 30; } /* enqueue the message in the queue and add wsc to list */ if (osmo_wqueue_enqueue(wqueue, msg) != 0) { /* So we will get a timeout but the log message might help */ LOGP(DL1C, LOGL_ERROR, "Write queue for %s full. dropping msg.\n", is_system_prim ? "system primitive" : "gsm"); msgb_free(msg); } llist_add(&wlc->list, &fl1h->wlc_list); /* schedule a timer for timeout_secs seconds. If DSP fails to respond, we terminate */ wlc->timer.data = wlc; wlc->timer.cb = l1if_req_timeout; osmo_timer_schedule(&wlc->timer, timeout_secs, 0); return 0; } /* send a request primitive to the L1 and schedule completion call-back */ int l1if_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg, l1if_compl_cb *cb, void *data) { return _l1if_req_compl(fl1h, msg, 1, cb, data); } int l1if_gsm_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg, l1if_compl_cb *cb, void *data) { return _l1if_req_compl(fl1h, msg, 0, cb, data); } /* allocate a msgb containing a GsmL1_Prim_t */ struct msgb *l1p_msgb_alloc(void) { struct msgb *msg = msgb_alloc(sizeof(GsmL1_Prim_t), "l1_prim"); if (msg) msg->l1h = msgb_put(msg, sizeof(GsmL1_Prim_t)); return msg; } /* allocate a msgb containing a SuperFemto_Prim_t */ struct msgb *sysp_msgb_alloc(void) { struct msgb *msg = msgb_alloc(sizeof(SuperFemto_Prim_t), "sys_prim"); if (msg) msg->l1h = msgb_put(msg, sizeof(SuperFemto_Prim_t)); return msg; } static GsmL1_PhDataReq_t * data_req_from_rts_ind(GsmL1_Prim_t *l1p, const GsmL1_PhReadyToSendInd_t *rts_ind) { GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq; l1p->id = GsmL1_PrimId_PhDataReq; /* copy fields from PH-RSS.ind */ data_req->hLayer1 = rts_ind->hLayer1; data_req->u8Tn = rts_ind->u8Tn; data_req->u32Fn = rts_ind->u32Fn; data_req->sapi = rts_ind->sapi; data_req->subCh = rts_ind->subCh; data_req->u8BlockNbr = rts_ind->u8BlockNbr; return data_req; } static GsmL1_PhEmptyFrameReq_t * empty_req_from_rts_ind(GsmL1_Prim_t *l1p, const GsmL1_PhReadyToSendInd_t *rts_ind) { GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq; l1p->id = GsmL1_PrimId_PhEmptyFrameReq; empty_req->hLayer1 = rts_ind->hLayer1; empty_req->u8Tn = rts_ind->u8Tn; empty_req->u32Fn = rts_ind->u32Fn; empty_req->sapi = rts_ind->sapi; empty_req->subCh = rts_ind->subCh; empty_req->u8BlockNbr = rts_ind->u8BlockNbr; return empty_req; } /* obtain a ptr to the lapdm_channel for a given hLayer2 */ static struct lapdm_channel * get_lapdm_chan_by_hl2(struct gsm_bts_trx *trx, uint32_t hLayer2) { struct gsm_lchan *lchan; lchan = l1if_hLayer_to_lchan(trx, hLayer2); if (!lchan) return NULL; return &lchan->lapdm_ch; } /* check if the message is a GSM48_MT_RR_CIPH_M_CMD, and if yes, enable * uni-directional de-cryption on the uplink. We need this ugly layering * violation as we have no way of passing down L3 metadata (RSL CIPHERING CMD) * to this point in L1 */ static int check_for_ciph_cmd(struct femtol1_hdl *fl1h, struct msgb *msg, struct gsm_lchan *lchan) { uint8_t n_s; /* only do this if we are in the right state */ switch (lchan->ciph_state) { case LCHAN_CIPH_NONE: case LCHAN_CIPH_RX_REQ: break; default: return 0; } /* First byte (Address Field) of LAPDm header) */ if (msg->data[0] != 0x03) return 0; /* First byte (protocol discriminator) of RR */ if ((msg->data[3] & 0xF) != GSM48_PDISC_RR) return 0; /* 2nd byte (msg type) of RR */ if ((msg->data[4] & 0x3F) != GSM48_MT_RR_CIPH_M_CMD) return 0; /* Remember N(S) + 1 to find the first ciphered frame */ n_s = (msg->data[1] >> 1) & 0x7; lchan->ciph_ns = (n_s + 1) % 8; lchan->ciph_state = LCHAN_CIPH_RX_REQ; l1if_set_ciphering(fl1h, lchan, 0); return 1; } static inline void check_for_first_ciphrd(struct femtol1_hdl *fl1h, GsmL1_MsgUnitParam_t *msgUnitParam, struct gsm_lchan *lchan) { uint8_t n_s; /* if this is the first valid message after enabling Rx * decryption, we have to enable Tx encryption */ if (lchan->ciph_state != LCHAN_CIPH_RX_CONF) return; /* HACK: check if it's an I frame, in order to * ignore some still buffered/queued UI frames received * before decryption was enabled */ if (msgUnitParam->u8Buffer[0] != 0x01) return; if ((msgUnitParam->u8Buffer[1] & 0x01) != 0) return; n_s = msgUnitParam->u8Buffer[1] >> 5; if (lchan->ciph_ns != n_s) return; lchan->ciph_state = LCHAN_CIPH_TXRX_REQ; l1if_set_ciphering(fl1h, lchan, 1); } /* public helpers for the test */ int bts_check_for_ciph_cmd(struct femtol1_hdl *fl1h, struct msgb *msg, struct gsm_lchan *lchan) { return check_for_ciph_cmd(fl1h, msg, lchan); } void bts_check_for_first_ciphrd(struct femtol1_hdl *fl1h, GsmL1_MsgUnitParam_t *msgUnitParam, struct gsm_lchan *lchan) { return check_for_first_ciphrd(fl1h, msgUnitParam, lchan); } static const uint8_t fill_frame[GSM_MACBLOCK_LEN] = { 0x03, 0x03, 0x01, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B }; static int handle_ph_readytosend_ind(struct femtol1_hdl *fl1, GsmL1_PhReadyToSendInd_t *rts_ind) { struct gsm_bts_trx *trx = fl1->priv; struct gsm_bts *bts = trx->bts; struct msgb *resp_msg; GsmL1_PhDataReq_t *data_req; GsmL1_MsgUnitParam_t *msu_param; struct lapdm_entity *le; struct gsm_lchan *lchan; struct gsm_time g_time; uint32_t t3p; uint8_t *si; struct osmo_phsap_prim pp; int rc; gsm_fn2gsmtime(&g_time, rts_ind->u32Fn); DEBUGP(DL1P, "Rx PH-RTS.ind %02u/%02u/%02u SAPI=%s\n", g_time.t1, g_time.t2, g_time.t3, get_value_string(femtobts_l1sapi_names, rts_ind->sapi)); /* In case of TCH downlink trasnmission, we already have a l1 * primitive msgb pre-allocated and pre-formatted in the * dl_tch_queue. All we need to do is to pull it off the queue * and transmit it */ switch (rts_ind->sapi) { case GsmL1_Sapi_TchF: case GsmL1_Sapi_TchH: /* resolve the L2 entity using rts_ind->hLayer2 */ lchan = l1if_hLayer_to_lchan(trx, rts_ind->hLayer2); if (!lchan) break; if (!lchan->loopback && lchan->abis_ip.rtp_socket) { osmo_rtp_socket_poll(lchan->abis_ip.rtp_socket); /* FIXME: we _assume_ that we never miss TDMA * frames and that we always get to this point * for every to-be-transmitted voice frame. A * better solution would be to compute * rx_user_ts based on how many TDMA frames have * elapsed since the last call */ lchan->abis_ip.rtp_socket->rx_user_ts += GSM_RTP_DURATION; } /* get a msgb from the dl_tx_queue */ resp_msg = msgb_dequeue(&lchan->dl_tch_queue); /* if there is none, try to generate empty TCH frame * like AMR SID_BAD */ if (!resp_msg) { LOGP(DL1C, LOGL_DEBUG, "%s DL TCH Tx queue underrun\n", gsm_lchan_name(lchan)); resp_msg = gen_empty_tch_msg(lchan); /* if there really is none, break here and send empty */ if (!resp_msg) break; } /* fill header */ data_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind); /* actually transmit it */ goto tx; break; case GsmL1_Sapi_Pdtch: case GsmL1_Sapi_Pacch: return pcu_tx_rts_req(&trx->ts[rts_ind->u8Tn], 0, rts_ind->u32Fn, rts_ind->u16Arfcn, rts_ind->u8BlockNbr); case GsmL1_Sapi_Ptcch: return pcu_tx_rts_req(&trx->ts[rts_ind->u8Tn], 1, rts_ind->u32Fn, rts_ind->u16Arfcn, rts_ind->u8BlockNbr); default: break; } /* in all other cases, we need to allocate a new PH-DATA.ind * primitive msgb and start to fill it */ resp_msg = l1p_msgb_alloc(); data_req = data_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind); msu_param = &data_req->msgUnitParam; /* set default size */ msu_param->u8Size = GSM_MACBLOCK_LEN; switch (rts_ind->sapi) { case GsmL1_Sapi_Sch: /* compute T3prime */ t3p = (g_time.t3 - 1) / 10; /* fill SCH burst with data */ msu_param->u8Size = 4; msu_param->u8Buffer[0] = (bts->bsic << 2) | (g_time.t1 >> 9); msu_param->u8Buffer[1] = (g_time.t1 >> 1); msu_param->u8Buffer[2] = (g_time.t1 << 7) | (g_time.t2 << 2) | (t3p >> 1); msu_param->u8Buffer[3] = (t3p & 1); break; case GsmL1_Sapi_Bcch: /* get them from bts->si_buf[] */ si = bts_sysinfo_get(bts, &g_time); if (si) memcpy(msu_param->u8Buffer, si, GSM_MACBLOCK_LEN); else memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN); break; case GsmL1_Sapi_Sacch: /* resolve the L2 entity using rts_ind->hLayer2 */ lchan = l1if_hLayer_to_lchan(trx, rts_ind->hLayer2); le = &lchan->lapdm_ch.lapdm_acch; /* * if the DSP is taking care of power control, * then this value will be overridden. */ msu_param->u8Buffer[0] = lchan->ms_power_ctrl.current; msu_param->u8Buffer[1] = lchan->rqd_ta; rc = lapdm_phsap_dequeue_prim(le, &pp); if (rc < 0) { /* No SACCH data from LAPDM pending, send SACCH filling */ uint8_t *si = lchan_sacch_get(lchan); if (si) { /* +2 to not overwrite the ms_power/ta values */ memcpy(msu_param->u8Buffer+2, si, GSM_MACBLOCK_LEN-2); } else { /* +2 to not overwrite the ms_power/ta values */ memcpy(msu_param->u8Buffer+2, fill_frame, GSM_MACBLOCK_LEN-2); } } else { /* +2 to not overwrite the ms_power/ta values */ memcpy(msu_param->u8Buffer+2, pp.oph.msg->data + 2, GSM_MACBLOCK_LEN-2); msgb_free(pp.oph.msg); } break; case GsmL1_Sapi_Sdcch: /* resolve the L2 entity using rts_ind->hLayer2 */ lchan = l1if_hLayer_to_lchan(trx, rts_ind->hLayer2); le = &lchan->lapdm_ch.lapdm_dcch; rc = lapdm_phsap_dequeue_prim(le, &pp); if (rc < 0) memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN); else { memcpy(msu_param->u8Buffer, pp.oph.msg->data, GSM_MACBLOCK_LEN); /* check if it is a RR CIPH MODE CMD. if yes, enable RX ciphering */ check_for_ciph_cmd(fl1, pp.oph.msg, lchan); msgb_free(pp.oph.msg); } break; case GsmL1_Sapi_Agch: case GsmL1_Sapi_Pch: rc = bts_ccch_copy_msg(bts, msu_param->u8Buffer, &g_time, rts_ind->sapi == GsmL1_Sapi_Agch); if (rc <= 0) memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN); break; case GsmL1_Sapi_TchF: case GsmL1_Sapi_TchH: /* only hit in case we have a RTP underflow, as real TCH * frames are handled way above */ goto empty_frame; break; case GsmL1_Sapi_FacchF: case GsmL1_Sapi_FacchH: /* resolve the L2 entity using rts_ind->hLayer2 */ lchan = l1if_hLayer_to_lchan(trx, rts_ind->hLayer2); le = &lchan->lapdm_ch.lapdm_dcch; rc = lapdm_phsap_dequeue_prim(le, &pp); if (rc < 0) goto empty_frame; else { memcpy(msu_param->u8Buffer, pp.oph.msg->data, GSM_MACBLOCK_LEN); /* check if it is a RR CIPH MODE CMD. if yes, enable RX ciphering */ check_for_ciph_cmd(fl1, pp.oph.msg, lchan); msgb_free(pp.oph.msg); } break; case GsmL1_Sapi_Prach: goto empty_frame; break; case GsmL1_Sapi_Cbch: /* get them from bts->si_buf[] */ bts_cbch_get(bts, msu_param->u8Buffer, &g_time); break; default: memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN); break; } tx: tx_to_gsmtap(fl1, resp_msg); /* transmit */ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], resp_msg) != 0) { LOGP(DL1C, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n"); msgb_free(resp_msg); } return 0; empty_frame: /* in case we decide to send an empty frame... */ empty_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind); goto tx; } static int handle_mph_time_ind(struct femtol1_hdl *fl1, GsmL1_MphTimeInd_t *time_ind) { struct gsm_bts_trx *trx = fl1->priv; struct gsm_bts *bts = trx->bts; struct gsm_bts_role_bts *btsb = bts->role; int frames_expired = time_ind->u32Fn - fl1->gsm_time.fn; /* update time on PCU interface */ pcu_tx_time_ind(time_ind->u32Fn); /* Update our data structures with the current GSM time */ gsm_fn2gsmtime(&fl1->gsm_time, time_ind->u32Fn); /* check if the measurement period of some lchan has ended * and pre-compute the respective measurement */ trx_meas_check_compute(fl1->priv, time_ind->u32Fn -1); /* increment the primitive count for the alive timer */ fl1->alive_prim_cnt++; /* increment number of RACH slots that have passed by since the * last time indication */ if (trx == bts->c0) { unsigned int num_rach_per_frame; /* 27 / 51 taken from TS 05.01 Figure 3 */ if (bts->c0->ts[0].pchan == GSM_PCHAN_CCCH_SDCCH4) num_rach_per_frame = 27; else num_rach_per_frame = 51; btsb->load.rach.total += frames_expired * num_rach_per_frame; } return 0; } /* determine LAPDm entity inside LAPDm channel for given L1 sapi */ static struct lapdm_entity *le_by_l1_sapi(struct lapdm_channel *lc, GsmL1_Sapi_t sapi) { switch (sapi) { case GsmL1_Sapi_Sacch: return &lc->lapdm_acch; default: return &lc->lapdm_dcch; } } static uint8_t gen_link_id(GsmL1_Sapi_t l1_sapi, uint8_t lapdm_sapi) { uint8_t c_bits = 0; if (l1_sapi == GsmL1_Sapi_Sacch) c_bits = 0x40; return c_bits | (lapdm_sapi & 7); } static void dump_meas_res(int ll, GsmL1_MeasParam_t *m) { LOGPC(DL1C, ll, ", Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, " "BER %-3.2f, Timing %d\n", m->fRssi, m->fLinkQuality, m->fBer, m->i16BurstTiming); } static int process_meas_res(struct gsm_lchan *lchan, GsmL1_MeasParam_t *m) { struct bts_ul_meas ulm; /* in the GPRS case we are not interested in measurement * processing. The PCU will take care of it */ if (lchan->type == GSM_LCHAN_PDTCH) return 0; ulm.ta_offs_qbits = m->i16BurstTiming; ulm.ber10k = (unsigned int) (m->fBer * 100); ulm.inv_rssi = (uint8_t) (m->fRssi * -1); return lchan_new_ul_meas(lchan, &ulm); } /* process radio link timeout counter S */ static void radio_link_timeout(struct gsm_lchan *lchan, int bad_frame) { struct gsm_bts_role_bts *btsb = lchan->ts->trx->bts->role; /* if link loss criterion already reached */ if (lchan->s == 0) { DEBUGP(DMEAS, "%s radio link counter S already 0.\n", gsm_lchan_name(lchan)); return; } if (bad_frame) { /* count down radio link counter S */ lchan->s--; DEBUGP(DMEAS, "%s counting down radio link counter S=%d\n", gsm_lchan_name(lchan), lchan->s); if (lchan->s == 0) rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL); return; } if (lchan->s < btsb->radio_link_timeout) { /* count up radio link counter S */ lchan->s += 2; if (lchan->s > btsb->radio_link_timeout) lchan->s = btsb->radio_link_timeout; DEBUGP(DMEAS, "%s counting up radio link counter S=%d\n", gsm_lchan_name(lchan), lchan->s); } } static int handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_ind, struct msgb *l1p_msg) { struct gsm_bts_trx *trx = fl1->priv; struct osmo_phsap_prim pp; struct gsm_lchan *lchan; struct lapdm_entity *le; struct msgb *msg; int rc = 0; ul_to_gsmtap(fl1, l1p_msg); lchan = l1if_hLayer_to_lchan(fl1->priv, data_ind->hLayer2); if (!lchan) { LOGP(DL1C, LOGL_ERROR, "unable to resolve lchan by hLayer2 for 0x%x\n", data_ind->hLayer2); return -ENODEV; } process_meas_res(lchan, &data_ind->measParam); if (data_ind->measParam.fLinkQuality < fl1->min_qual_norm && data_ind->msgUnitParam.u8Size != 0) return 0; DEBUGP(DL1C, "Rx PH-DATA.ind %s (hL2 %08x): %s", get_value_string(femtobts_l1sapi_names, data_ind->sapi), data_ind->hLayer2, osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size)); dump_meas_res(LOGL_DEBUG, &data_ind->measParam); if (lchan->ho.active == HANDOVER_WAIT_FRAME) handover_frame(lchan); switch (data_ind->sapi) { case GsmL1_Sapi_Sacch: radio_link_timeout(lchan, (data_ind->msgUnitParam.u8Size == 0)); if (data_ind->msgUnitParam.u8Size == 0) break; /* save the SACCH L1 header in the lchan struct for RSL MEAS RES */ if (data_ind->msgUnitParam.u8Size < 2) { LOGP(DL1C, LOGL_NOTICE, "SACCH with size %u<2 !?!\n", data_ind->msgUnitParam.u8Size); break; } /* * Handle power control */ l1if_ms_pwr_ctrl(lchan, fl1->ul_power_target, data_ind->msgUnitParam.u8Buffer[0] & 0x1f, data_ind->measParam.fRssi); /* Some brilliant engineer decided that the ordering of * fields on the Um interface is different from the * order of fields in RLS. See TS 04.04 (Chapter 7.2) * vs. TS 08.58 (Chapter 9.3.10). */ lchan->meas.l1_info[0] = data_ind->msgUnitParam.u8Buffer[0] << 3; lchan->meas.l1_info[0] |= ((data_ind->msgUnitParam.u8Buffer[0] >> 5) & 1) << 2; lchan->meas.l1_info[1] = data_ind->msgUnitParam.u8Buffer[1]; lchan->meas.flags |= LC_UL_M_F_L1_VALID; /* fall-through */ case GsmL1_Sapi_Sdcch: case GsmL1_Sapi_FacchF: case GsmL1_Sapi_FacchH: /* Check and Re-check for the SACCH */ if (data_ind->msgUnitParam.u8Size == 0) { LOGP(DL1C, LOGL_NOTICE, "%s %s data is null.\n", gsm_lchan_name(lchan), get_value_string(femtobts_l1sapi_names, data_ind->sapi)); break; } check_for_first_ciphrd(fl1, &data_ind->msgUnitParam, lchan); /* SDCCH, SACCH and FACCH all go to LAPDm */ le = le_by_l1_sapi(&lchan->lapdm_ch, data_ind->sapi); /* allocate and fill LAPDm primitive */ msg = msgb_alloc_headroom(128, 64, "PH-DATA.ind"); osmo_prim_init(&pp.oph, SAP_GSM_PH, PRIM_PH_DATA, PRIM_OP_INDICATION, msg); /* copy over actual MAC block */ msg->l2h = msgb_put(msg, data_ind->msgUnitParam.u8Size); memcpy(msg->l2h, data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size); /* LAPDm requires those... */ pp.u.data.chan_nr = gsm_lchan2chan_nr(lchan); pp.u.data.link_id = gen_link_id(data_ind->sapi, 0); /* feed into the LAPDm code of libosmogsm */ rc = lapdm_phsap_up(&pp.oph, le); break; case GsmL1_Sapi_TchF: case GsmL1_Sapi_TchH: /* TCH speech frame handling */ rc = l1if_tch_rx(lchan, l1p_msg); break; case GsmL1_Sapi_Pdtch: case GsmL1_Sapi_Pacch: /* drop incomplete UL block */ if (!data_ind->msgUnitParam.u8Size || data_ind->msgUnitParam.u8Buffer[0] != GsmL1_PdtchPlType_Full) break; /* PDTCH / PACCH frame handling */ rc = pcu_tx_data_ind(&trx->ts[data_ind->u8Tn], 0, data_ind->u32Fn, data_ind->u16Arfcn, data_ind->u8BlockNbr, data_ind->msgUnitParam.u8Buffer + 1, data_ind->msgUnitParam.u8Size - 1, (int8_t) (data_ind->measParam.fRssi)); break; case GsmL1_Sapi_Ptcch: /* PTCCH frame handling */ rc = pcu_tx_data_ind(&trx->ts[data_ind->u8Tn], 1, data_ind->u32Fn, data_ind->u16Arfcn, data_ind->u8BlockNbr, data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size, (int8_t) (data_ind->measParam.fRssi)); break; case GsmL1_Sapi_Idle: /* nothing to send */ break; default: LOGP(DL1C, LOGL_NOTICE, "Rx PH-DATA.ind for unknown L1 SAPI %s\n", get_value_string(femtobts_l1sapi_names, data_ind->sapi)); break; } return rc; } static int check_acc_delay(GsmL1_PhRaInd_t *ra_ind, struct gsm_bts_role_bts *btsb, uint8_t *acc_delay) { if (ra_ind->measParam.i16BurstTiming < 0) *acc_delay = 0; else *acc_delay = ra_ind->measParam.i16BurstTiming >> 2; return *acc_delay <= btsb->max_ta; } static int handle_handover(struct gsm_lchan *lchan, struct gsm_bts_role_bts *btsb, GsmL1_PhRaInd_t *ra_ind) { uint8_t acc_delay; if (!check_acc_delay(ra_ind, btsb, &acc_delay)) { LOGP(DHO, LOGL_INFO, "%s ignoring RACH request %u > max_ta(%u)\n", gsm_lchan_name(lchan), acc_delay, btsb->max_ta); return 0; } handover_rach(lchan, ra_ind->msgUnitParam.u8Buffer[0], acc_delay); return 0; } static int handle_ph_ra_ind(struct femtol1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind) { struct gsm_bts_trx *trx = fl1->priv; struct gsm_bts *bts = trx->bts; struct gsm_bts_role_bts *btsb = bts->role; struct gsm_lchan *lchan; struct osmo_phsap_prim pp; struct lapdm_channel *lc; uint8_t acc_delay; /* increment number of busy RACH slots, if required */ if (trx == bts->c0 && ra_ind->measParam.fRssi >= btsb->load.rach.busy_thresh) btsb->load.rach.busy++; if (ra_ind->measParam.fLinkQuality < fl1->min_qual_rach) return 0; /* * Check if this is a handover */ lchan = l1if_hLayer_to_lchan(trx, ra_ind->hLayer2); if (lchan && lchan->ho.active == HANDOVER_ENABLED) return handle_handover(lchan, btsb, ra_ind); /* increment number of RACH slots with valid RACH burst */ if (trx == bts->c0) btsb->load.rach.access++; DEBUGP(DL1C, "Rx PH-RA.ind"); dump_meas_res(LOGL_DEBUG, &ra_ind->measParam); lc = get_lapdm_chan_by_hl2(fl1->priv, ra_ind->hLayer2); if (!lc) { LOGP(DL1C, LOGL_ERROR, "unable to resolve LAPD channel by hLayer2\n"); return -ENODEV; } /* check for under/overflow / sign */ if (!check_acc_delay(ra_ind, btsb, &acc_delay)) { LOGP(DL1C, LOGL_INFO, "ignoring RACH request %u > max_ta(%u)\n", acc_delay, btsb->max_ta); return 0; } /* check for packet access */ if (trx == bts->c0 && (ra_ind->msgUnitParam.u8Buffer[0] & 0xf0) == 0x70) { LOGP(DL1C, LOGL_INFO, "RACH for packet access\n"); return pcu_tx_rach_ind(bts, ra_ind->measParam.i16BurstTiming, ra_ind->msgUnitParam.u8Buffer[0], ra_ind->u32Fn); } osmo_prim_init(&pp.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, NULL); pp.u.rach_ind.ra = ra_ind->msgUnitParam.u8Buffer[0]; pp.u.rach_ind.fn = ra_ind->u32Fn; pp.u.rach_ind.acc_delay = acc_delay; return lapdm_phsap_up(&pp.oph, &lc->lapdm_dcch); } /* handle any random indication from the L1 */ static int l1if_handle_ind(struct femtol1_hdl *fl1, struct msgb *msg) { GsmL1_Prim_t *l1p = msgb_l1prim(msg); int rc = 0; switch (l1p->id) { case GsmL1_PrimId_MphTimeInd: rc = handle_mph_time_ind(fl1, &l1p->u.mphTimeInd); break; case GsmL1_PrimId_MphSyncInd: break; case GsmL1_PrimId_PhConnectInd: break; case GsmL1_PrimId_PhReadyToSendInd: rc = handle_ph_readytosend_ind(fl1, &l1p->u.phReadyToSendInd); break; case GsmL1_PrimId_PhDataInd: rc = handle_ph_data_ind(fl1, &l1p->u.phDataInd, msg); break; case GsmL1_PrimId_PhRaInd: rc = handle_ph_ra_ind(fl1, &l1p->u.phRaInd); break; default: break; } /* Special return value '1' means: do not free */ if (rc != 1) msgb_free(msg); return rc; } static inline int is_prim_compat(GsmL1_Prim_t *l1p, struct wait_l1_conf *wlc) { /* the limitation here is that we cannot have multiple callers * sending the same primitive */ if (wlc->is_sys_prim != 0) return 0; if (l1p->id != wlc->conf_prim_id) return 0; return 1; } int l1if_handle_l1prim(int wq, struct femtol1_hdl *fl1h, struct msgb *msg) { GsmL1_Prim_t *l1p = msgb_l1prim(msg); struct wait_l1_conf *wlc; int rc; switch (l1p->id) { case GsmL1_PrimId_MphTimeInd: /* silent, don't clog the log file */ break; default: LOGP(DL1P, LOGL_DEBUG, "Rx L1 prim %s on queue %d\n", get_value_string(femtobts_l1prim_names, l1p->id), wq); } /* check if this is a resposne to a sync-waiting request */ llist_for_each_entry(wlc, &fl1h->wlc_list, list) { if (is_prim_compat(l1p, wlc)) { llist_del(&wlc->list); if (wlc->cb) rc = wlc->cb(fl1h->priv, msg, wlc->cb_data); else { rc = 0; msgb_free(msg); } release_wlc(wlc); return rc; } } /* if we reach here, it is not a Conf for a pending Req */ return l1if_handle_ind(fl1h, msg); } int l1if_handle_sysprim(struct femtol1_hdl *fl1h, struct msgb *msg) { SuperFemto_Prim_t *sysp = msgb_sysprim(msg); struct wait_l1_conf *wlc; int rc; LOGP(DL1P, LOGL_DEBUG, "Rx SYS prim %s\n", get_value_string(femtobts_sysprim_names, sysp->id)); /* check if this is a resposne to a sync-waiting request */ llist_for_each_entry(wlc, &fl1h->wlc_list, list) { /* the limitation here is that we cannot have multiple callers * sending the same primitive */ if (wlc->is_sys_prim && sysp->id == wlc->conf_prim_id) { llist_del(&wlc->list); if (wlc->cb) rc = wlc->cb(fl1h->priv, msg, wlc->cb_data); else { rc = 0; msgb_free(msg); } release_wlc(wlc); return rc; } } /* if we reach here, it is not a Conf for a pending Req */ return l1if_handle_ind(fl1h, msg); } #if 0 /* called by RSL if the BCCH SI has been modified */ int sysinfo_has_changed(struct gsm_bts *bts, int si) { /* FIXME: Determine BS_AG_BLKS_RES and * * set cfgParams.u.agch.u8NbrOfAgch * * determine implications on paging */ /* FIXME: Check for Extended BCCH presence */ /* FIXME: Check for CCCH_CONF */ /* FIXME: Check for BS_PA_MFRMS: update paging */ return 0; } #endif static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { SuperFemto_Prim_t *sysp = msgb_sysprim(resp); GsmL1_Status_t status; int on = 0; unsigned int i; if (sysp->id == SuperFemto_PrimId_ActivateRfCnf) on = 1; if (on) status = sysp->u.activateRfCnf.status; else status = sysp->u.deactivateRfCnf.status; LOGP(DL1C, LOGL_INFO, "Rx RF-%sACT.conf (status=%s)\n", on ? "" : "DE", get_value_string(femtobts_l1status_names, status)); if (on) { if (status != GsmL1_Status_Success) { LOGP(DL1C, LOGL_FATAL, "RF-ACT.conf with status %s\n", get_value_string(femtobts_l1status_names, status)); bts_shutdown(trx->bts, "RF-ACT failure"); } else bts_update_status(BTS_STATUS_RF_ACTIVE, 1); /* signal availability */ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); oml_mo_tx_sw_act_rep(&trx->mo); oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); for (i = 0; i < ARRAY_SIZE(trx->ts); i++) oml_mo_state_chg(&trx->ts[i].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY); } else { bts_update_status(BTS_STATUS_RF_ACTIVE, 0); oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); } msgb_free(resp); return 0; } static int get_clk_cal(struct femtol1_hdl *hdl) { #ifdef FEMTOBTS_API_VERSION return hdl->clk_cal; #else switch (hdl->clk_src) { case SuperFemto_ClkSrcId_Ocxo: case SuperFemto_ClkSrcId_Tcxo: /* only for those on-board clocks it makes sense to use * the calibration value */ return hdl->clk_cal; default: /* external clocks like GPS are taken 1:1 without any * modification by a local calibration value */ LOGP(DL1C, LOGL_INFO, "Ignoring Clock Calibration for " "selected %s clock\n", get_value_string(femtobts_clksrc_names, hdl->clk_src)); return 0; } #endif } /* * RevC was the last HW revision without an external * attenuator. Check for that. */ static int has_external_atten(struct femtol1_hdl *hdl) { /* older version doesn't have an attenuator */ return hdl->hw_info.ver_major > 2; } /* activate or de-activate the entire RF-Frontend */ int l1if_activate_rf(struct femtol1_hdl *hdl, int on) { struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); struct gsm_bts_trx *trx = hdl->priv; if (on) { sysp->id = SuperFemto_PrimId_ActivateRfReq; #ifdef HW_SYSMOBTS_V1 sysp->u.activateRfReq.u12ClkVc = get_clk_cal(hdl); #else #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(0,2,0) sysp->u.activateRfReq.timing.u8TimSrc = 1; /* Master */ #endif /* 0.2.0 */ sysp->u.activateRfReq.msgq.u8UseTchMsgq = 0; sysp->u.activateRfReq.msgq.u8UsePdtchMsgq = pcu_direct; /* Use clock from OCXO or whatever source is configured */ #if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,1,0) sysp->u.activateRfReq.rfTrx.u8ClkSrc = hdl->clk_src; #else sysp->u.activateRfReq.rfTrx.clkSrc = hdl->clk_src; #endif /* 2.1.0 */ sysp->u.activateRfReq.rfTrx.iClkCor = get_clk_cal(hdl); #if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0) #if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,1,0) sysp->u.activateRfReq.rfRx.u8ClkSrc = hdl->clk_src; #else sysp->u.activateRfReq.rfRx.clkSrc = hdl->clk_src; #endif /* 2.1.0 */ sysp->u.activateRfReq.rfRx.iClkCor = get_clk_cal(hdl); #endif /* API 2.4.0 */ #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,2,0) if (has_external_atten(hdl)) { LOGP(DL1C, LOGL_INFO, "Using external attenuator.\n"); sysp->u.activateRfReq.rfTrx.u8UseExtAtten = 1; sysp->u.activateRfReq.rfTrx.fMaxTxPower = (float) get_p_trxout_target_mdBm(trx, 0) / 1000; } #endif /* 2.2.0 */ #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,8,1) /* maximum cell size in quarter-bits, 90 == 12.456 km */ sysp->u.activateRfReq.u8MaxCellSize = 90; #endif #endif /* !HW_SYSMOBTS_V1 */ } else { sysp->id = SuperFemto_PrimId_DeactivateRfReq; } return l1if_req_compl(hdl, msg, activate_rf_compl_cb, NULL); } #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0) static void mute_handle_ts(struct gsm_bts_trx_ts *ts, int is_muted) { int i; for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) { struct gsm_lchan *lchan = &ts->lchan[i]; if (!is_muted) continue; if (lchan->state != LCHAN_S_ACTIVE) continue; /* skip channels that might be active for another reason */ if (lchan->type == GSM_LCHAN_CCCH) continue; if (lchan->type == GSM_LCHAN_PDTCH) continue; if (lchan->s <= 0) continue; lchan->s = 0; rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL); } } static int mute_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); SuperFemto_Prim_t *sysp = msgb_sysprim(resp); GsmL1_Status_t status; status = sysp->u.muteRfCnf.status; if (status != GsmL1_Status_Success) { LOGP(DL1C, LOGL_ERROR, "Rx RF-MUTE.conf with status %s\n", get_value_string(femtobts_l1status_names, status)); oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 0); } else { int i; LOGP(DL1C, LOGL_INFO, "Rx RF-MUTE.conf with status=%s\n", get_value_string(femtobts_l1status_names, status)); bts_update_status(BTS_STATUS_RF_MUTE, fl1h->last_rf_mute[0]); oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 1); osmo_static_assert( ARRAY_SIZE(trx->ts) >= ARRAY_SIZE(fl1h->last_rf_mute), ts_array_size); for (i = 0; i < ARRAY_SIZE(fl1h->last_rf_mute); ++i) mute_handle_ts(&trx->ts[i], fl1h->last_rf_mute[i]); } msgb_free(resp); return 0; } #endif /* mute/unmute RF time slots */ int l1if_mute_rf(struct femtol1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb) { struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); LOGP(DL1C, LOGL_INFO, "Tx RF-MUTE.req (%d, %d, %d, %d, %d, %d, %d, %d)\n", mute[0], mute[1], mute[2], mute[3], mute[4], mute[5], mute[6], mute[7] ); #if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(3,6,0) LOGP(DL1C, LOGL_ERROR, "RF-MUTE.req not supported by SuperFemto\n"); msgb_free(msg); return -ENOTSUP; #else sysp->id = SuperFemto_PrimId_MuteRfReq; memcpy(sysp->u.muteRfReq.u8Mute, mute, sizeof(sysp->u.muteRfReq.u8Mute)); /* save for later use */ memcpy(hdl->last_rf_mute, mute, sizeof(hdl->last_rf_mute)); return l1if_req_compl(hdl, msg, cb ? cb : mute_rf_compl_cb, NULL); #endif /* < 3.6.0 */ } /* call-back on arrival of DSP+FPGA version + band capability */ static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { SuperFemto_Prim_t *sysp = msgb_sysprim(resp); SuperFemto_SystemInfoCnf_t *sic = &sysp->u.systemInfoCnf; struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); int rc; fl1h->hw_info.dsp_version[0] = sic->dspVersion.major; fl1h->hw_info.dsp_version[1] = sic->dspVersion.minor; fl1h->hw_info.dsp_version[2] = sic->dspVersion.build; fl1h->hw_info.fpga_version[0] = sic->fpgaVersion.major; fl1h->hw_info.fpga_version[1] = sic->fpgaVersion.minor; fl1h->hw_info.fpga_version[2] = sic->fpgaVersion.build; #ifndef HW_SYSMOBTS_V1 fl1h->hw_info.ver_major = sic->boardVersion.rev; fl1h->hw_info.ver_minor = sic->boardVersion.option; #endif LOGP(DL1C, LOGL_INFO, "DSP v%u.%u.%u, FPGA v%u.%u.%u\nn", sic->dspVersion.major, sic->dspVersion.minor, sic->dspVersion.build, sic->fpgaVersion.major, sic->fpgaVersion.minor, sic->fpgaVersion.build); #ifdef HW_SYSMOBTS_V1 if (sic->rfBand.gsm850) fl1h->hw_info.band_support |= GSM_BAND_850; if (sic->rfBand.gsm900) fl1h->hw_info.band_support |= GSM_BAND_900; if (sic->rfBand.dcs1800) fl1h->hw_info.band_support |= GSM_BAND_1800; if (sic->rfBand.pcs1900) fl1h->hw_info.band_support |= GSM_BAND_1900; #endif if (!(fl1h->hw_info.band_support & trx->bts->band)) LOGP(DL1C, LOGL_FATAL, "BTS band %s not supported by hw\n", gsm_band_name(trx->bts->band)); /* Request the activation */ l1if_activate_rf(fl1h, 1); #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0) /* load calibration tables (if we know their path) */ rc = calib_load(fl1h); if (rc < 0) LOGP(DL1C, LOGL_ERROR, "Operating without calibration; " "unable to load tables!\n"); #else LOGP(DL1C, LOGL_NOTICE, "Operating without calibration " "as software was compiled against old header files\n"); #endif msgb_free(resp); /* FIXME: clock related */ return 0; } /* request DSP+FPGA code versions + band capability */ static int l1if_get_info(struct femtol1_hdl *hdl) { struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); sysp->id = SuperFemto_PrimId_SystemInfoReq; return l1if_req_compl(hdl, msg, info_compl_cb, NULL); } static int reset_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); SuperFemto_Prim_t *sysp = msgb_sysprim(resp); GsmL1_Status_t status = sysp->u.layer1ResetCnf.status; LOGP(DL1C, LOGL_NOTICE, "Rx L1-RESET.conf (status=%s)\n", get_value_string(femtobts_l1status_names, status)); msgb_free(resp); /* If we're coming out of reset .. */ if (status != GsmL1_Status_Success) { LOGP(DL1C, LOGL_FATAL, "L1-RESET.conf with status %s\n", get_value_string(femtobts_l1status_names, status)); bts_shutdown(trx->bts, "L1-RESET failure"); } /* as we cannot get the current DSP trace flags, we simply * set them to zero (or whatever dsp_trace_f has been initialized to */ l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f); /* obtain version information on DSP/FPGA and band capabilities */ l1if_get_info(fl1h); return 0; } int l1if_reset(struct femtol1_hdl *hdl) { struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); sysp->id = SuperFemto_PrimId_Layer1ResetReq; return l1if_req_compl(hdl, msg, reset_compl_cb, NULL); } /* set the trace flags within the DSP */ int l1if_set_trace_flags(struct femtol1_hdl *hdl, uint32_t flags) { struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); LOGP(DL1C, LOGL_INFO, "Tx SET-TRACE-FLAGS.req (0x%08x)\n", flags); sysp->id = SuperFemto_PrimId_SetTraceFlagsReq; sysp->u.setTraceFlagsReq.u32Tf = flags; hdl->dsp_trace_f = flags; /* There is no confirmation we could wait for */ if (osmo_wqueue_enqueue(&hdl->write_q[MQ_SYS_WRITE], msg) != 0) { LOGP(DL1C, LOGL_ERROR, "MQ_SYS_WRITE queue full. Dropping msg\n"); msgb_free(msg); return -EAGAIN; } return 0; } /* send packet data request to L1 */ int l1if_pdch_req(struct gsm_bts_trx_ts *ts, int is_ptcch, uint32_t fn, uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len) { struct gsm_bts_trx *trx = ts->trx; struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); struct msgb *msg; GsmL1_Prim_t *l1p; GsmL1_PhDataReq_t *data_req; GsmL1_MsgUnitParam_t *msu_param; struct gsm_time g_time; gsm_fn2gsmtime(&g_time, fn); DEBUGP(DL1P, "TX packet data %02u/%02u/%02u is_ptcch=%d trx=%d ts=%d " "block_nr=%d, arfcn=%d, len=%d\n", g_time.t1, g_time.t2, g_time.t3, is_ptcch, ts->trx->nr, ts->nr, block_nr, arfcn, len); msg = l1p_msgb_alloc(); l1p = msgb_l1prim(msg); l1p->id = GsmL1_PrimId_PhDataReq; data_req = &l1p->u.phDataReq; data_req->hLayer1 = fl1h->hLayer1; data_req->sapi = (is_ptcch) ? GsmL1_Sapi_Ptcch : GsmL1_Sapi_Pdtch; data_req->subCh = GsmL1_SubCh_NA; data_req->u8BlockNbr = block_nr; data_req->u8Tn = ts->nr; data_req->u32Fn = fn; msu_param = &data_req->msgUnitParam; msu_param->u8Size = len; memcpy(msu_param->u8Buffer, data, len); tx_to_gsmtap(fl1h, msg); /* transmit */ if (osmo_wqueue_enqueue(&fl1h->write_q[MQ_L1_WRITE], msg) != 0) { LOGP(DL1P, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n"); msgb_free(msg); } return 0; } /* get those femtol1_hdl.hw_info elements that sre in EEPROM */ static int get_hwinfo_eeprom(struct femtol1_hdl *fl1h) { eeprom_SysInfo_t sysinfo; int val, rc; rc = sysmobts_par_get_int(SYSMOBTS_PAR_MODEL_NR, &val); if (rc < 0) return rc; fl1h->hw_info.model_nr = val; rc = sysmobts_par_get_int(SYSMOBTS_PAR_MODEL_FLAGS, &val); if (rc < 0) return rc; fl1h->hw_info.model_flags = val; rc = sysmobts_par_get_int(SYSMOBTS_PAR_TRX_NR, &val); if (rc < 0) return rc; fl1h->hw_info.trx_nr = val; rc = eeprom_ReadSysInfo(&sysinfo); if (rc != EEPROM_SUCCESS) { /* some early units don't yet have the EEPROM * information structure */ LOGP(DL1C, LOGL_ERROR, "Unable to read band support " "from EEPROM, assuming all bands\n"); fl1h->hw_info.band_support = GSM_BAND_850 | GSM_BAND_900 | GSM_BAND_1800 | GSM_BAND_1900; return 0; } if (sysinfo.u8GSM850) fl1h->hw_info.band_support |= GSM_BAND_850; if (sysinfo.u8GSM900) fl1h->hw_info.band_support |= GSM_BAND_900; if (sysinfo.u8DCS1800) fl1h->hw_info.band_support |= GSM_BAND_1800; if (sysinfo.u8PCS1900) fl1h->hw_info.band_support |= GSM_BAND_1900; return 0; } struct femtol1_hdl *l1if_open(void *priv) { struct femtol1_hdl *fl1h; int rc; #ifndef HW_SYSMOBTS_V1 LOGP(DL1C, LOGL_INFO, "sysmoBTSv2 L1IF compiled against API headers " "v%u.%u.%u\n", SUPERFEMTO_API_VERSION >> 16, (SUPERFEMTO_API_VERSION >> 8) & 0xff, SUPERFEMTO_API_VERSION & 0xff); #else LOGP(DL1C, LOGL_INFO, "sysmoBTSv1 L1IF compiled against API headers " "v%u.%u.%u\n", FEMTOBTS_API_VERSION >> 16, (FEMTOBTS_API_VERSION >> 8) & 0xff, FEMTOBTS_API_VERSION & 0xff); #endif fl1h = talloc_zero(priv, struct femtol1_hdl); if (!fl1h) return NULL; INIT_LLIST_HEAD(&fl1h->wlc_list); fl1h->priv = priv; fl1h->clk_cal = 0; fl1h->clk_use_eeprom = 1; fl1h->ul_power_target = -75; /* dBm default */ fl1h->min_qual_rach = MIN_QUAL_RACH; fl1h->min_qual_norm = MIN_QUAL_NORM; get_hwinfo_eeprom(fl1h); #if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0) if (fl1h->hw_info.model_nr == 2050) { /* On the sysmoBTS 2050, we don't have an OCXO but * start with the TCXO and will sync it with the PPS * of the GPS in case there is a fix. */ fl1h->clk_src = SuperFemto_ClkSrcId_Tcxo; LOGP(DL1C, LOGL_INFO, "Clock source defaulting to GPS 1PPS " "on sysmoBTS 2050\n"); } else { /* default clock source: OCXO */ fl1h->clk_src = SuperFemto_ClkSrcId_Ocxo; } #else fl1h->clk_src = SF_CLKSRC_OCXO; #endif rc = l1if_transport_open(MQ_SYS_WRITE, fl1h); if (rc < 0) { talloc_free(fl1h); return NULL; } rc = l1if_transport_open(MQ_L1_WRITE, fl1h); if (rc < 0) { l1if_transport_close(MQ_SYS_WRITE, fl1h); talloc_free(fl1h); return NULL; } fl1h->gsmtap = gsmtap_source_init("localhost", GSMTAP_UDP_PORT, 1); if (fl1h->gsmtap) gsmtap_source_add_sink(fl1h->gsmtap); return fl1h; } int l1if_close(struct femtol1_hdl *fl1h) { l1if_transport_close(MQ_L1_WRITE, fl1h); l1if_transport_close(MQ_SYS_WRITE, fl1h); return 0; } #ifdef HW_SYSMOBTS_V1 int l1if_rf_clock_info_reset(struct femtol1_hdl *fl1h) { LOGP(DL1C, LOGL_ERROR, "RfClock calibration not supported on v1 hw.\n"); return -ENOTSUP; } int l1if_rf_clock_info_correct(struct femtol1_hdl *fl1h) { LOGP(DL1C, LOGL_ERROR, "RfClock calibration not supported on v1 hw.\n"); return -ENOTSUP; } #else static int clock_reset_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { msgb_free(resp); return 0; } static int clock_setup_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { SuperFemto_Prim_t *sysp = msgb_sysprim(resp); if (sysp->u.rfClockSetupCnf.status != GsmL1_Status_Success) LOGP(DL1C, LOGL_ERROR, "Rx RfClockSetupConf failed with: %d\n", sysp->u.rfClockSetupCnf.status); msgb_free(resp); return 0; } static int clock_correct_info_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); SuperFemto_Prim_t *sysp = msgb_sysprim(resp); LOGP(DL1C, LOGL_NOTICE, "RfClockInfo iClkCor=%d/clkSrc=%s Err=%d/ErrRes=%d/clkSrc=%s\n", sysp->u.rfClockInfoCnf.rfTrx.iClkCor, get_value_string(femtobts_clksrc_names, sysp->u.rfClockInfoCnf.rfTrx.clkSrc), sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr, sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes, get_value_string(femtobts_clksrc_names, sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc)); if (sysp->u.rfClockInfoCnf.rfTrx.clkSrc == SuperFemto_ClkSrcId_GpsPps) { LOGP(DL1C, LOGL_ERROR, "Calibrating GPS against GPS doesn not make sense.\n"); msgb_free(resp); return -1; } if (sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc == SuperFemto_ClkSrcId_None) { LOGP(DL1C, LOGL_ERROR, "No reference clock set. Please reset first.\n"); msgb_free(resp); return -1; } if (sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes == 0) { LOGP(DL1C, LOGL_ERROR, "Couldn't determine the clock difference.\n"); msgb_free(resp); return -1; } fl1h->clk_cal = sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr; fl1h->clk_use_eeprom = 0; msgb_free(resp); /* * Let's reset the counter and this will lead to applying the * new calibration. */ l1if_rf_clock_info_reset(fl1h); return 0; } int l1if_rf_clock_info_reset(struct femtol1_hdl *fl1h) { struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); /* Set GPS/PPS as reference */ sysp->id = SuperFemto_PrimId_RfClockSetupReq; sysp->u.rfClockSetupReq.rfTrx.iClkCor = get_clk_cal(fl1h); sysp->u.rfClockSetupReq.rfTrx.clkSrc = fl1h->clk_src; sysp->u.rfClockSetupReq.rfTrxClkCal.clkSrc = SuperFemto_ClkSrcId_GpsPps; l1if_req_compl(fl1h, msg, clock_setup_cb, NULL); /* Reset the error counters */ msg = sysp_msgb_alloc(); sysp = msgb_sysprim(msg); sysp->id = SuperFemto_PrimId_RfClockInfoReq; sysp->u.rfClockInfoReq.u8RstClkCal = 1; return l1if_req_compl(fl1h, msg, clock_reset_cb, NULL); } int l1if_rf_clock_info_correct(struct femtol1_hdl *fl1h) { struct msgb *msg = sysp_msgb_alloc(); SuperFemto_Prim_t *sysp = msgb_sysprim(msg); sysp->id = SuperFemto_PrimId_RfClockInfoReq; sysp->u.rfClockInfoReq.u8RstClkCal = 0; return l1if_req_compl(fl1h, msg, clock_correct_info_cb, NULL); } #endif /* * Check if manual power control is needed * Check if fixed power was selected * Check if the MS is already using our level if not * the value is bogus.. * TODO: Add a timeout.. e.g. if the ms is not capable of reaching * the value we have set. */ inline int l1if_ms_pwr_ctrl(struct gsm_lchan *lchan, const int ul_power_target, const uint8_t ms_power, const float rxLevel) { float rx; int cur_dBm, new_dBm, new_pwr; const enum gsm_band band = lchan->ts->trx->bts->band; if (!trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx)) return 0; if (lchan->ms_power_ctrl.fixed) return 0; /* The phone hasn't reached the power level yet */ if (lchan->ms_power_ctrl.current != ms_power) return 0; /* * What is the difference between what we want and received? * Ignore a margin that is within the range of measurement * and MS output issues. */ rx = ul_power_target - rxLevel; if (rx >= 0 && rx < 1.5f) return 0; if (rx < 0 && rx > -1.5f) return 0; /* We don't really care about the truncation of int + float */ cur_dBm = ms_pwr_dbm(band, ms_power); new_dBm = cur_dBm + rx; /* Clamp negative values and do it depending on the band */ if (new_dBm < 0) new_dBm = 0; switch (band) { case GSM_BAND_1800: /* If MS_TX_PWR_MAX_CCH is set the values 29, * 30, 31 are not used. Avoid specifying a dBm * that would lead to these power levels. The * phone might not be able to reach them. */ if (new_dBm > 30) new_dBm = 30; break; default: break; } new_pwr = ms_pwr_ctl_lvl(band, new_dBm); if (lchan->ms_power_ctrl.current != new_pwr) { lchan->ms_power_ctrl.current = new_pwr; bts_model_adjst_ms_pwr(lchan); return 1; } return 0; }