summaryrefslogtreecommitdiffstats
path: root/src/host/layer23/src/common/l1ctl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/host/layer23/src/common/l1ctl.c')
-rw-r--r--src/host/layer23/src/common/l1ctl.c370
1 files changed, 314 insertions, 56 deletions
diff --git a/src/host/layer23/src/common/l1ctl.c b/src/host/layer23/src/common/l1ctl.c
index 5d6d9c0c..94979103 100644
--- a/src/host/layer23/src/common/l1ctl.c
+++ b/src/host/layer23/src/common/l1ctl.c
@@ -15,10 +15,6 @@
* 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 <stdio.h>
@@ -41,14 +37,63 @@
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/protocol/gsm_08_58.h>
#include <osmocom/gsm/rsl.h>
+#include <osmocom/gsm/lapdm.h>
+#include <osmocom/gsm/gsm0502.h>
#include <osmocom/bb/common/l1ctl.h>
+#include <osmocom/bb/common/l23_app.h>
#include <osmocom/bb/common/osmocom_data.h>
+#include <osmocom/bb/common/ms.h>
#include <osmocom/bb/common/l1l2_interface.h>
-#include <osmocom/gsm/lapdm.h>
#include <osmocom/bb/common/logging.h>
-extern struct gsmtap_inst *gsmtap_inst;
+/* determine the CCCH block number based on the frame number */
+static unsigned int fn2ccch_block(uint32_t fn)
+{
+ int rc = gsm0502_fn2ccch_block(fn);
+ /* if FN is negative, we were called for something that's not CCCH! */
+ OSMO_ASSERT(rc >= 0);
+ return rc;
+}
+
+static uint8_t chantype_rsl2gsmtap_ext(uint8_t rsl_chantype, uint8_t link_id, uint32_t fn, uint8_t num_agch)
+{
+ uint8_t ret = chantype_rsl2gsmtap2(rsl_chantype, link_id, false);
+ if (ret != GSMTAP_CHANNEL_PCH)
+ return ret;
+
+ if (fn2ccch_block(fn) >= num_agch)
+ return GSMTAP_CHANNEL_PCH;
+ return GSMTAP_CHANNEL_AGCH;
+}
+
+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
+};
+
+/* Paging Request 1 with "no identity" content, i.e. empty/dummy paging */
+static const uint8_t paging_fill[GSM_MACBLOCK_LEN] = {
+ 0x15, 0x06, 0x21, 0x00, 0x01, 0xf0, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b };
+
+static bool is_fill_frame(uint8_t chan_type, const uint8_t *data)
+{
+ switch (chan_type) {
+ case GSMTAP_CHANNEL_AGCH:
+ if (!memcmp(data, fill_frame, GSM_MACBLOCK_LEN))
+ return true;
+ break;
+ case GSMTAP_CHANNEL_PCH:
+ if (!memcmp(data, paging_fill, GSM_MACBLOCK_LEN))
+ return true;
+ break;
+ /* don't use 'default' case here as the above only conditionally return true */
+ }
+ return false;
+}
static struct msgb *osmo_l1_alloc(uint8_t msg_type)
{
@@ -63,7 +108,7 @@ static struct msgb *osmo_l1_alloc(uint8_t msg_type)
msg->l1h = msgb_put(msg, sizeof(*l1h));
l1h = (struct l1ctl_hdr *) msg->l1h;
l1h->msg_type = msg_type;
-
+
return msg;
}
@@ -145,6 +190,7 @@ static int rx_ph_data_ind(struct osmocom_ms *ms, struct msgb *msg)
struct rx_meas_stat *meas = &ms->meas;
uint8_t chan_type, chan_ts, chan_ss;
uint8_t gsmtap_chan_type;
+ uint8_t bs_ag_blks_res;
struct gsm_time tm;
if (msgb_l1len(msg) < sizeof(*dl)) {
@@ -159,7 +205,13 @@ static int rx_ph_data_ind(struct osmocom_ms *ms, struct msgb *msg)
ccch = (struct l1ctl_data_ind *) msg->l2h;
gsm_fn2gsmtime(&tm, ntohl(dl->frame_nr));
- rsl_dec_chan_nr(dl->chan_nr, &chan_type, &chan_ss, &chan_ts);
+ if (rsl_dec_chan_nr(dl->chan_nr, &chan_type, &chan_ss, &chan_ts) != 0) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n",
+ __func__, dl->chan_nr);
+ return -EINVAL;
+ }
+
DEBUGP(DL1C, "%s (%.4u/%.2u/%.2u) %d dBm: %s\n",
rsl_chan_nr_str(dl->chan_nr), tm.t1, tm.t2, tm.t3,
(int)dl->rx_level-110,
@@ -229,11 +281,31 @@ static int rx_ph_data_ind(struct osmocom_ms *ms, struct msgb *msg)
return 0;
}
- /* send CCCH data via GSMTAP */
- gsmtap_chan_type = chantype_rsl2gsmtap(chan_type, dl->link_id);
- gsmtap_send(gsmtap_inst, ntohs(dl->band_arfcn), chan_ts,
- gsmtap_chan_type, chan_ss, tm.fn, dl->rx_level-110,
- dl->snr, ccch->data, sizeof(ccch->data));
+ /* May not be initialized in some applications (e.g. ccch_scan) */
+ if (ms->cellsel.si != NULL)
+ bs_ag_blks_res = ms->cellsel.si->bs_ag_blks_res;
+ else /* fall-back to 1 (this is what OsmoBTS does) */
+ bs_ag_blks_res = 1;
+
+ gsmtap_chan_type = chantype_rsl2gsmtap_ext(chan_type, dl->link_id, tm.fn, bs_ag_blks_res);
+ /* don't log fill frames via GSMTAP; they serve no purpose other than
+ * to clog up your logs */
+ if (!is_fill_frame(gsmtap_chan_type, ccch->data)) {
+ /* send CCCH data via GSMTAP */
+ gsmtap_send(l23_cfg.gsmtap.inst, ntohs(dl->band_arfcn), chan_ts,
+ gsmtap_chan_type, chan_ss, tm.fn, dl->rx_level-110,
+ dl->snr, ccch->data, sizeof(ccch->data));
+ }
+
+ /* Do not pass PDCH and CBCH frames to LAPDm */
+ switch (chan_type) {
+ case RSL_CHAN_OSMO_PDCH:
+ case RSL_CHAN_OSMO_CBCH4:
+ case RSL_CHAN_OSMO_CBCH8:
+ /* TODO: pass directly to l23 application */
+ msgb_free(msg);
+ return 0;
+ }
/* determine LAPDm entity based on SACCH or not */
if (dl->link_id & 0x40)
@@ -245,6 +317,7 @@ static int rx_ph_data_ind(struct osmocom_ms *ms, struct msgb *msg)
PRIM_OP_INDICATION, msg);
pp.u.data.chan_nr = dl->chan_nr;
pp.u.data.link_id = dl->link_id;
+ pp.u.data.fn = tm.fn;
/* send it up into LAPDm */
return lapdm_phsap_up(&pp.oph, le);
@@ -284,7 +357,6 @@ int l1ctl_tx_data_req(struct osmocom_ms *ms, struct msgb *msg,
struct l1ctl_hdr *l1h;
struct l1ctl_info_ul *l1i_ul;
uint8_t chan_type, chan_ts, chan_ss;
- uint8_t gsmtap_chan_type;
DEBUGP(DL1C, "(%s)\n", osmo_hexdump(msg->l2h, msgb_l2len(msg)));
@@ -296,10 +368,16 @@ int l1ctl_tx_data_req(struct osmocom_ms *ms, struct msgb *msg,
}
/* send copy via GSMTAP */
- rsl_dec_chan_nr(chan_nr, &chan_type, &chan_ss, &chan_ts);
- gsmtap_chan_type = chantype_rsl2gsmtap(chan_type, link_id);
- gsmtap_send(gsmtap_inst, 0|0x4000, chan_ts, gsmtap_chan_type,
- chan_ss, 0, 127, 255, msg->l2h, msgb_l2len(msg));
+ if (rsl_dec_chan_nr(chan_nr, &chan_type, &chan_ss, &chan_ts) == 0) {
+ uint8_t gsmtap_chan_type = chantype_rsl2gsmtap2(chan_type, link_id, false);
+ gsmtap_send(l23_cfg.gsmtap.inst, ms->rrlayer.cd_now.arfcn | GSMTAP_ARFCN_F_UPLINK,
+ chan_ts, gsmtap_chan_type, chan_ss, 0, 127, 0,
+ msg->l2h, msgb_l2len(msg));
+ } else {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s(): rsl_dec_chan_nr(chan_nr=0x%02x) failed\n",
+ __func__, chan_nr);
+ }
/* prepend uplink info header */
l1i_ul = (struct l1ctl_info_ul *) msgb_push(msg, sizeof(*l1i_ul));
@@ -365,8 +443,8 @@ int l1ctl_tx_ccch_mode_req(struct osmocom_ms *ms, uint8_t ccch_mode)
}
/* Transmit L1CTL_TCH_MODE_REQ */
-int l1ctl_tx_tch_mode_req(struct osmocom_ms *ms, uint8_t tch_mode,
- uint8_t audio_mode)
+int l1ctl_tx_tch_mode_req(struct osmocom_ms *ms, uint8_t tch_mode, uint8_t audio_mode, uint8_t tch_flags,
+ uint8_t tch_loop_mode)
{
struct msgb *msg;
struct l1ctl_tch_mode_req *req;
@@ -380,6 +458,9 @@ int l1ctl_tx_tch_mode_req(struct osmocom_ms *ms, uint8_t tch_mode,
req = (struct l1ctl_tch_mode_req *) msgb_put(msg, sizeof(*req));
req->tch_mode = tch_mode;
req->audio_mode = audio_mode;
+ req->tch_flags = tch_flags;
+ req->tch_loop_mode = tch_loop_mode;
+ /* TODO: Set AMR codec in req if req->tch_mode==GSM48_CMODE_SPEECH_AMR */
return osmo_send_l1(ms, msg);
}
@@ -431,8 +512,9 @@ int l1ctl_tx_crypto_req(struct osmocom_ms *ms, uint8_t chan_nr,
}
/* Transmit L1CTL_RACH_REQ */
-int l1ctl_tx_rach_req(struct osmocom_ms *ms, uint8_t ra, uint16_t offset,
- uint8_t combined)
+int l1ctl_tx_rach_req(struct osmocom_ms *ms,
+ uint8_t chan_nr, uint8_t link_id,
+ uint8_t ra, uint16_t offset, uint8_t combined, uint8_t uic)
{
struct msgb *msg;
struct l1ctl_info_ul *ul;
@@ -442,20 +524,22 @@ int l1ctl_tx_rach_req(struct osmocom_ms *ms, uint8_t ra, uint16_t offset,
if (!msg)
return -1;
- DEBUGP(DL1C, "RACH Req. offset=%d combined=%d\n", offset, combined);
+ DEBUGP(DL1C, "RACH Req. offset=%d combined=%d uic=0x%02x\n", offset, combined, uic);
ul = (struct l1ctl_info_ul *) msgb_put(msg, sizeof(*ul));
+ ul->chan_nr = chan_nr;
+ ul->link_id = link_id;
req = (struct l1ctl_rach_req *) msgb_put(msg, sizeof(*req));
req->ra = ra;
req->offset = htons(offset);
req->combined = combined;
+ req->uic = uic;
return osmo_send_l1(ms, msg);
}
/* Transmit L1CTL_DM_EST_REQ */
-int l1ctl_tx_dm_est_req_h0(struct osmocom_ms *ms, uint16_t band_arfcn,
- uint8_t chan_nr, uint8_t tsc, uint8_t tch_mode,
- uint8_t audio_mode)
+int l1ctl_tx_dm_est_req_h0(struct osmocom_ms *ms, uint16_t band_arfcn, uint8_t chan_nr, uint8_t tsc, uint8_t tch_mode,
+ uint8_t audio_mode, uint8_t tch_flags)
{
struct msgb *msg;
struct l1ctl_info_ul *ul;
@@ -478,14 +562,13 @@ int l1ctl_tx_dm_est_req_h0(struct osmocom_ms *ms, uint16_t band_arfcn,
req->h0.band_arfcn = htons(band_arfcn);
req->tch_mode = tch_mode;
req->audio_mode = audio_mode;
+ req->tch_flags = tch_flags;
return osmo_send_l1(ms, msg);
}
-int l1ctl_tx_dm_est_req_h1(struct osmocom_ms *ms, uint8_t maio, uint8_t hsn,
- uint16_t *ma, uint8_t ma_len,
- uint8_t chan_nr, uint8_t tsc, uint8_t tch_mode,
- uint8_t audio_mode)
+int l1ctl_tx_dm_est_req_h1(struct osmocom_ms *ms, uint8_t maio, uint8_t hsn, uint16_t *ma, uint8_t ma_len,
+ uint8_t chan_nr, uint8_t tsc, uint8_t tch_mode, uint8_t audio_mode, uint8_t tch_flags)
{
struct msgb *msg;
struct l1ctl_info_ul *ul;
@@ -513,6 +596,7 @@ int l1ctl_tx_dm_est_req_h1(struct osmocom_ms *ms, uint8_t maio, uint8_t hsn,
req->h1.ma[i] = htons(ma[i]);
req->tch_mode = tch_mode;
req->audio_mode = audio_mode;
+ req->tch_flags = tch_flags;
return osmo_send_l1(ms, msg);
}
@@ -630,13 +714,10 @@ int l1ctl_tx_sim_req(struct osmocom_ms *ms, uint8_t *data, uint16_t length)
/* just forward the SIM response to the SIM handler */
static int rx_l1_sim_conf(struct osmocom_ms *ms, struct msgb *msg)
{
- uint16_t len = msgb_l2len(msg);
- uint8_t *data = msg->data;
-
- LOGP(DL1C, LOGL_INFO, "SIM %s\n", osmo_hexdump(data, len));
+ LOGP(DL1C, LOGL_INFO, "SIM %s\n", msgb_hexdump_l1(msg));
sim_apdu_resp(ms, msg);
-
+
return 0;
}
@@ -751,6 +832,7 @@ static int rx_l1_tch_mode_conf(struct osmocom_ms *ms, struct msgb *msg)
mc.tch_mode = conf->tch_mode;
mc.audio_mode = conf->audio_mode;
+ mc.tch_flags = conf->tch_flags;
mc.ms = ms;
osmo_signal_dispatch(SS_L1CTL, S_L1CTL_TCH_MODE_CONF, &mc);
@@ -762,7 +844,6 @@ static int rx_l1_traffic_ind(struct osmocom_ms *ms, struct msgb *msg)
{
struct l1ctl_info_dl *dl;
struct l1ctl_traffic_ind *ti;
- size_t frame_len;
uint8_t *frame;
if (msgb_l1len(msg) < sizeof(*dl)) {
@@ -779,11 +860,8 @@ static int rx_l1_traffic_ind(struct osmocom_ms *ms, struct msgb *msg)
msg->l2h = dl->payload;
msg->l3h = frame;
- /* Calculate the frame length */
- frame_len = msgb_l3len(msg);
-
- DEBUGP(DL1C, "TRAFFIC IND len=%zu (%s)\n", frame_len,
- osmo_hexdump(frame, frame_len));
+ LOGP(DL1C, LOGL_DEBUG, "Rx TRAFFIC.ind (fn=%u, chan_nr=0x%02x, len=%u): %s\n",
+ ntohl(dl->frame_nr), dl->chan_nr, msgb_l3len(msg), msgb_hexdump_l3(msg));
/* distribute or drop */
if (ms->l1_entity.l1_traffic_ind)
@@ -800,7 +878,6 @@ int l1ctl_tx_traffic_req(struct osmocom_ms *ms, struct msgb *msg,
struct l1ctl_hdr *l1h;
struct l1ctl_info_ul *l1i_ul;
struct l1ctl_traffic_req *tr;
- size_t frame_len;
uint8_t *frame;
/* Header handling */
@@ -808,20 +885,8 @@ int l1ctl_tx_traffic_req(struct osmocom_ms *ms, struct msgb *msg,
frame = (uint8_t *) tr->data;
msg->l3h = frame;
- /* Calculate the frame length */
- frame_len = msgb_l3len(msg);
-
- DEBUGP(DL1C, "TRAFFIC REQ len=%zu (%s)\n", frame_len,
- osmo_hexdump(frame, frame_len));
-
- if ((frame[0] >> 4) != 0xd) {
- LOGP(DL1C, LOGL_ERROR, "Traffic Request has incorrect magic "
- "(%u != 0xd)\n", frame[0] >> 4);
- msgb_free(msg);
- return -EINVAL;
- }
-
-// printf("TX %s\n", osmo_hexdump(frame, frame_len));
+ LOGP(DL1C, LOGL_DEBUG, "Tx TRAFFIC.req (chan_nr=0x%02x, len=%u): %s\n",
+ chan_nr, msgb_l3len(msg), msgb_hexdump_l3(msg));
/* prepend uplink info header */
l1i_ul = (struct l1ctl_info_ul *) msgb_push(msg, sizeof(*l1i_ul));
@@ -884,6 +949,190 @@ static int rx_l1_neigh_pm_ind(struct osmocom_ms *ms, struct msgb *msg)
return 0;
}
+/* Receive L1CTL_GPRS_UL_BLOCK_CNF */
+static int rx_l1_gprs_ul_block_cnf(struct osmocom_ms *ms, struct msgb *msg)
+{
+ const struct l1ctl_gprs_ul_block_cnf *cnf = (void *)msg->l1h;
+
+ if (msgb_l1len(msg) < sizeof(*cnf)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Rx malformed GPRS UL BLOCK.cnf (len=%u < %zu)\n",
+ msgb_l1len(msg), sizeof(*cnf));
+ return -EINVAL;
+ }
+ if (OSMO_UNLIKELY(cnf->tn >= 8)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Rx malformed GPRS UL BLOCK.cnf (tn=%u)\n", cnf->tn);
+ return -EINVAL;
+ }
+
+ msg->l2h = (void *)&cnf->data[0];
+
+ DEBUGP(DL1C, "Rx GPRS UL BLOCK.cnf (fn=%u, tn=%u, len=%u): %s\n",
+ ntohl(cnf->fn), cnf->tn, msgb_l2len(msg), msgb_hexdump_l2(msg));
+
+ /* distribute or drop */
+ if (ms->l1_entity.l1_gprs_ul_block_cnf)
+ return ms->l1_entity.l1_gprs_ul_block_cnf(ms, msg);
+
+ msgb_free(msg);
+ return 0;
+}
+
+/* Receive L1CTL_GPRS_DL_BLOCK_IND */
+static int rx_l1_gprs_dl_block_ind(struct osmocom_ms *ms, struct msgb *msg)
+{
+ const struct l1ctl_gprs_dl_block_ind *ind = (void *)msg->l1h;
+ uint8_t gsmtap_chan;
+ uint32_t fn;
+
+ if (msgb_l1len(msg) < sizeof(*ind)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Rx malformed GPRS DL BLOCK.ind (len=%u < %zu)\n",
+ msgb_l1len(msg), sizeof(*ind));
+ return -EINVAL;
+ }
+ if (OSMO_UNLIKELY(ind->hdr.tn >= 8)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Rx malformed GPRS DL BLOCK.ind (tn=%u)\n",
+ ind->hdr.tn);
+ return -EINVAL;
+ }
+
+ msg->l2h = (void *)&ind->data[0];
+
+ fn = ntohl(ind->hdr.fn);
+ if ((fn % 104) == 12)
+ gsmtap_chan = GSMTAP_CHANNEL_PTCCH;
+ else
+ gsmtap_chan = GSMTAP_CHANNEL_PDTCH;
+
+ gsmtap_send(l23_cfg.gsmtap.inst,
+ ms->rrlayer.cd_now.arfcn,
+ ind->hdr.tn, gsmtap_chan, 0, fn,
+ rxlev2dbm(ind->meas.rx_lev), 0,
+ msgb_l2(msg), msgb_l2len(msg));
+
+ DEBUGP(DL1C, "Rx GPRS DL BLOCK.ind (fn=%u, tn=%u, len=%u): %s\n",
+ fn, ind->hdr.tn, msgb_l2len(msg), msgb_hexdump_l2(msg));
+
+ /* distribute or drop */
+ if (ms->l1_entity.l1_gprs_dl_block_ind)
+ return ms->l1_entity.l1_gprs_dl_block_ind(ms, msg);
+
+ msgb_free(msg);
+ return 0;
+}
+
+/* Receive L1CTL_GPRS_RTS_IND */
+static int rx_l1_gprs_rts_ind(struct osmocom_ms *ms, struct msgb *msg)
+{
+ const struct l1ctl_gprs_rts_ind *ind = (void *)msg->l1h;
+
+ if (msgb_l1len(msg) < sizeof(*ind)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Rx malformed GPRS RTS.ind (len=%u < %zu)\n",
+ msgb_l1len(msg), sizeof(*ind));
+ return -EINVAL;
+ }
+ if (OSMO_UNLIKELY(ind->tn >= 8)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Rx malformed GPRS RTS.ind (tn=%u)\n",
+ ind->tn);
+ return -EINVAL;
+ }
+
+ DEBUGP(DL1C, "Rx GPRS RTS.ind (fn=%u, tn=%u, usf=%u)\n",
+ ntohl(ind->fn), ind->tn, ind->usf);
+
+ /* distribute or drop */
+ if (ms->l1_entity.l1_gprs_rts_ind)
+ return ms->l1_entity.l1_gprs_rts_ind(ms, msg);
+
+ msgb_free(msg);
+ return 0;
+}
+
+/* Transmit L1CTL_GPRS_UL_BLOCK_REQ */
+int l1ctl_tx_gprs_ul_block_req(struct osmocom_ms *ms, uint32_t fn, uint8_t tn,
+ const uint8_t *data, size_t data_len)
+{
+ struct l1ctl_gprs_ul_block_req *req;
+ struct msgb *msg;
+
+ msg = osmo_l1_alloc(L1CTL_GPRS_UL_BLOCK_REQ);
+ if (!msg)
+ return -ENOMEM;
+
+ req = (void *)msgb_put(msg, sizeof(*req));
+ req->hdr.fn = htonl(fn);
+ req->hdr.tn = tn;
+ if (data_len > 0)
+ memcpy(msgb_put(msg, data_len), data, data_len);
+
+ DEBUGP(DL1C, "Tx GPRS UL block (fn=%u, tn=%u, len=%zu): %s\n",
+ fn, tn, data_len, osmo_hexdump(data, data_len));
+
+ gsmtap_send(l23_cfg.gsmtap.inst,
+ ms->rrlayer.cd_now.arfcn | GSMTAP_ARFCN_F_UPLINK,
+ tn, GSMTAP_CHANNEL_PDTCH, 0, fn, 127, 0,
+ data, data_len);
+
+ return osmo_send_l1(ms, msg);
+}
+
+/* Transmit L1CTL_GPRS_UL_TBF_CFG_REQ */
+int l1ctl_tx_gprs_ul_tbf_cfg_req(struct osmocom_ms *ms, uint8_t tbf_ref,
+ uint8_t slotmask, uint32_t start_fn)
+{
+ struct l1ctl_gprs_ul_tbf_cfg_req *req;
+ struct msgb *msg;
+
+ msg = osmo_l1_alloc(L1CTL_GPRS_UL_TBF_CFG_REQ);
+ if (!msg)
+ return -ENOMEM;
+
+ req = (void *)msgb_put(msg, sizeof(*req));
+ *req = (struct l1ctl_gprs_ul_tbf_cfg_req) {
+ .tbf_ref = tbf_ref,
+ .slotmask = slotmask,
+ .start_fn = htonl(start_fn),
+ };
+
+ DEBUGP(DL1C, "Tx GPRS UL TBF CFG: "
+ "tbf_ref=%u, slotmask=0x%02x, start_fn=%u\n",
+ tbf_ref, slotmask, start_fn);
+
+ return osmo_send_l1(ms, msg);
+}
+
+/* Transmit L1CTL_GPRS_DL_TBF_CFG_REQ */
+int l1ctl_tx_gprs_dl_tbf_cfg_req(struct osmocom_ms *ms, uint8_t tbf_ref,
+ uint8_t slotmask, uint32_t start_fn,
+ uint8_t dl_tfi)
+{
+ struct l1ctl_gprs_dl_tbf_cfg_req *req;
+ struct msgb *msg;
+
+ msg = osmo_l1_alloc(L1CTL_GPRS_DL_TBF_CFG_REQ);
+ if (!msg)
+ return -ENOMEM;
+
+ req = (void *)msgb_put(msg, sizeof(*req));
+ *req = (struct l1ctl_gprs_dl_tbf_cfg_req) {
+ .tbf_ref = tbf_ref,
+ .slotmask = slotmask,
+ .start_fn = htonl(start_fn),
+ .dl_tfi = dl_tfi,
+ };
+
+ DEBUGP(DL1C, "Tx GPRS DL TBF CFG: "
+ "tbf_ref=%u, slotmask=0x%02x, start_fn=%u, dl_tfi=%u)\n",
+ tbf_ref, slotmask, start_fn, dl_tfi);
+
+ return osmo_send_l1(ms, msg);
+}
+
/* Receive incoming data from L1 using L1CTL format */
int l1ctl_recv(struct osmocom_ms *ms, struct msgb *msg)
{
@@ -952,6 +1201,15 @@ int l1ctl_recv(struct osmocom_ms *ms, struct msgb *msg)
case L1CTL_TRAFFIC_CONF:
msgb_free(msg);
break;
+ case L1CTL_GPRS_UL_BLOCK_CNF:
+ rc = rx_l1_gprs_ul_block_cnf(ms, msg);
+ break;
+ case L1CTL_GPRS_DL_BLOCK_IND:
+ rc = rx_l1_gprs_dl_block_ind(ms, msg);
+ break;
+ case L1CTL_GPRS_RTS_IND:
+ rc = rx_l1_gprs_rts_ind(ms, msg);
+ break;
default:
LOGP(DL1C, LOGL_ERROR, "Unknown MSG: %u\n", hdr->msg_type);
msgb_free(msg);