aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bts-sysmo/l1_if.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bts-sysmo/l1_if.c')
-rw-r--r--src/osmo-bts-sysmo/l1_if.c622
1 files changed, 622 insertions, 0 deletions
diff --git a/src/osmo-bts-sysmo/l1_if.c b/src/osmo-bts-sysmo/l1_if.c
new file mode 100644
index 00000000..8f4b80f6
--- /dev/null
+++ b/src/osmo-bts-sysmo/l1_if.c
@@ -0,0 +1,622 @@
+/* Interface handler for Sysmocom L1 */
+
+/* (C) 2011 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 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/lapdm.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/paging.h>
+
+#include <sysmocom/femtobts/femtobts.h>
+#include <sysmocom/femtobts/gsml1prim.h>
+#include <sysmocom/femtobts/gsml1const.h>
+#include <sysmocom/femtobts/gsml1types.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+#include "l1_transp.h"
+
+/* FIXME: make threshold configurable */
+#define MIN_QUAL_RACH -1.0f /* at least -1 dB C/I */
+#define MIN_QUAL_NORM -1.0f /* at least -1 dB C/I */
+
+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);
+}
+
+/* send a request primitive to the L1 and schedule completion call-back */
+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(DL1C, LOGL_DEBUG, "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 = 10;
+ } else {
+ FemtoBts_Prim_t *sysp = msgb_sysprim(msg);
+
+ LOGP(DL1C, LOGL_DEBUG, "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 */
+ osmo_wqueue_enqueue(wqueue, msg);
+ llist_add(&wlc->list, &fl1h->wlc_list);
+
+ /* schedule a timer for 10 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;
+}
+
+/* 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 FemtoBts_Prim_t */
+struct msgb *sysp_msgb_alloc(void)
+{
+ struct msgb *msg = msgb_alloc(sizeof(FemtoBts_Prim_t), "sys_prim");
+
+ if (msg)
+ msg->l1h = msgb_put(msg, sizeof(FemtoBts_Prim_t));
+
+ return msg;
+}
+
+/* prepare a PH-DATA.req primitive in response to a PH-RTS.ind */
+static struct msgb *alloc_ph_data_req(GsmL1_PhReadyToSendInd_t *rts_ind)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ GsmL1_Prim_t *l1p = msgb_l1prim(msg);
+ 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 msg;
+}
+
+/* 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_hLayer2_to_lchan(trx, hLayer2);
+ if (!lchan)
+ return NULL;
+
+ return &lchan->lapdm_ch;
+}
+
+
+static const uint8_t fill_frame[GSM_MACBLOCK_LEN] = {
+ 0x01, 0x2B, 0x2B, 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 msgb *resp_msg = alloc_ph_data_req(rts_ind);
+ struct gsm_bts_trx *trx = fl1->priv;
+ struct gsm_bts *bts = trx->bts;
+ struct gsm_bts_role_bts *btsb = bts->role;
+ GsmL1_Prim_t *l1p = msgb_l1prim(resp_msg);
+ GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq;
+ GsmL1_MsgUnitParam_t *msu_param = &data_req->msgUnitParam;
+ struct lapdm_channel *lc;
+ 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));
+
+ /* copy over parameters from PH-RTS.ind into PH-DATA.req */
+ 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;
+ 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_hLayer2_to_lchan(trx, rts_ind->hLayer2);
+ le = &lchan->lapdm_ch.lapdm_acch;
+ 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, &g_time);
+ if (si) {
+ /* The +2 is empty space where the DSP inserts the L1 hdr */
+ memcpy(msu_param->u8Buffer+2, si, GSM_MACBLOCK_LEN-2);
+ } else
+ memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN);
+ } else {
+ /* The +2 is empty space where the DSP inserts the L1 hdr */
+ memcpy(msu_param->u8Buffer+2, pp.oph.msg->data, GSM_MACBLOCK_LEN-2);
+ msgb_free(pp.oph.msg);
+ }
+ DEBUGP(DL1C, "Sending SACCH payload bytes: %s\n",
+ osmo_hexdump(msu_param->u8Buffer, GSM_MACBLOCK_LEN));
+ break;
+ case GsmL1_Sapi_Sdcch:
+ /* resolve the L2 entity using rts_ind->hLayer2 */
+ lc = get_lapdm_chan_by_hl2(trx, rts_ind->hLayer2);
+ le = &lc->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);
+ msgb_free(pp.oph.msg);
+ }
+ break;
+ case GsmL1_Sapi_Agch:
+ /* special queue of messages from IMM ASS CMD */
+ {
+ struct msgb *msg = bts_agch_dequeue(bts);
+ if (!msg)
+ memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN);
+ else {
+ DEBUGP(DL1C, "Sending AGCH payload %u bytes: %s\n",
+ msg->len, osmo_hexdump(msg->data, msg->len));
+ memcpy(msu_param->u8Buffer, msg->data, msg->len);
+ msgb_free(msg);
+ }
+ }
+ break;
+ case GsmL1_Sapi_Pch:
+ rc = paging_gen_msg(btsb->paging_state, msu_param->u8Buffer, &g_time);
+ /* FIXME: special paging logic */
+ break;
+ case GsmL1_Sapi_TchF:
+ case GsmL1_Sapi_FacchF:
+ /* we should never receive a request here */
+ default:
+ memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN);
+ break;
+ }
+ /* transmit */
+ osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], resp_msg);
+
+ return 0;
+}
+
+static int handle_mph_time_ind(struct femtol1_hdl *fl1,
+ GsmL1_MphTimeInd_t *time_ind)
+{
+ /* Update our data structures with the current GSM time */
+ gsm_fn2gsmtime(&fl1->gsm_time, time_ind->u32Fn);
+
+ 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(GsmL1_MeasParam_t *m)
+{
+ DEBUGPC(DL1C, ", 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 handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_ind)
+{
+ struct osmo_phsap_prim pp;
+ struct gsm_lchan *lchan;
+ struct lapdm_entity *le;
+ struct msgb *msg;
+#warning Properly determine the SAPI here
+ uint8_t lapdm_sapi = 0;
+
+ if (data_ind->measParam.fLinkQuality < MIN_QUAL_NORM)
+ return 0;
+
+ DEBUGP(DL1C, "Rx PH-DATA.ind (hLayer2 = 0x%08x)", data_ind->hLayer2);
+ dump_meas_res(&data_ind->measParam);
+
+ lchan = l1if_hLayer2_to_lchan(fl1->priv, data_ind->hLayer2);
+ if (!lchan) {
+ LOGP(DL1C, LOGL_ERROR, "unable to resolve lchan by hLayer2\n");
+ return -ENODEV;
+ }
+
+ le = le_by_l1_sapi(&lchan->lapdm_ch, data_ind->sapi);
+
+ msg = msgb_alloc_headroom(128, 64, "PH-DATA.ind");
+
+ osmo_prim_init(&pp.oph, SAP_GSM_PH, PRIM_PH_DATA,
+ PRIM_OP_INDICATION, msg);
+
+ msg->l2h = msgb_put(msg, data_ind->msgUnitParam.u8Size);
+ memcpy(msg->l2h, data_ind->msgUnitParam.u8Buffer,
+ data_ind->msgUnitParam.u8Size);
+
+ /* FIXME: LAPDm shouldn't really need those... */
+ pp.u.data.chan_nr = gsm_lchan2chan_nr(lchan);
+ pp.u.data.link_id = gen_link_id(data_ind->sapi, lapdm_sapi);
+
+ return lapdm_phsap_up(&pp.oph, le);
+}
+
+
+static int handle_ph_ra_ind(struct femtol1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind)
+{
+ struct osmo_phsap_prim pp;
+ struct lapdm_channel *lc;
+
+ if (ra_ind->measParam.fLinkQuality < MIN_QUAL_RACH)
+ return 0;
+
+ DEBUGP(DL1C, "Rx PH-RA.ind");
+ dump_meas_res(&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;
+ }
+
+ 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;
+ /* FIXME: check for under/overflow / sign */
+ if (ra_ind->measParam.i16BurstTiming <= 0 ||
+ ra_ind->measParam.i16BurstTiming > 63 * 4)
+ pp.u.rach_ind.acc_delay = 0;
+ else
+ pp.u.rach_ind.acc_delay = ra_ind->measParam.i16BurstTiming >> 2;
+
+ 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);
+ break;
+ case GsmL1_PrimId_PhRaInd:
+ rc = handle_ph_ra_ind(fl1, &l1p->u.phRaInd);
+ break;
+ default:
+ break;
+ }
+
+ msgb_free(msg);
+ return rc;
+}
+
+int l1if_handle_l1prim(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\n",
+ get_value_string(femtobts_l1prim_names, l1p->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 == 0 && l1p->id == wlc->conf_prim_id) {
+ llist_del(&wlc->list);
+ rc = wlc->cb(msg, wlc->cb_data);
+ 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)
+{
+ FemtoBts_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);
+ rc = wlc->cb(msg, wlc->cb_data);
+ 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 msgb *resp, void *data)
+{
+ FemtoBts_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status;
+ int on = 0;
+
+ if (sysp->id == FemtoBts_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));
+
+ talloc_free(resp);
+
+ return 0;
+}
+
+int l1if_activate_rf(struct femtol1_hdl *hdl, int on)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ FemtoBts_Prim_t *sysp = msgb_sysprim(msg);
+
+ if (on) {
+ sysp->id = FemtoBts_PrimId_ActivateRfReq;
+ sysp->u.activateRfReq.u12ClkVc = 0xFFFF;
+ } else {
+ sysp->id = FemtoBts_PrimId_DeactivateRfReq;
+ }
+
+ return l1if_req_compl(hdl, msg, 1, activate_rf_compl_cb, hdl);
+}
+
+static int reset_compl_cb(struct msgb *resp, void *data)
+{
+ struct femtol1_hdl *fl1h = data;
+ FemtoBts_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));
+
+ talloc_free(resp);
+
+ /* If we're coming out of reset .. */
+ if (status == GsmL1_Status_Success)
+ l1if_activate_rf(fl1h, 1);
+
+ return 0;
+}
+
+int l1if_reset(struct femtol1_hdl *hdl)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ FemtoBts_Prim_t *sysp = msgb_sysprim(msg);
+ sysp->id = FemtoBts_PrimId_Layer1ResetReq;
+
+ return l1if_req_compl(hdl, msg, 1, reset_compl_cb, hdl);
+}
+
+struct femtol1_hdl *l1if_open(void *priv)
+{
+ struct femtol1_hdl *fl1h;
+ int rc;
+
+ fl1h = talloc_zero(priv, struct femtol1_hdl);
+ if (!fl1h)
+ return NULL;
+ INIT_LLIST_HEAD(&fl1h->wlc_list);
+
+ fl1h->priv = priv;
+
+ rc = l1if_transport_open(fl1h);
+ if (rc < 0) {
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ return fl1h;
+}
+
+int l1if_close(struct femtol1_hdl *fl1h)
+{
+ return l1if_transport_close(fl1h);
+}