diff options
Diffstat (limited to 'src/shared/l1gprs.c')
-rw-r--r-- | src/shared/l1gprs.c | 831 |
1 files changed, 831 insertions, 0 deletions
diff --git a/src/shared/l1gprs.c b/src/shared/l1gprs.c new file mode 100644 index 00000000..2bf759d5 --- /dev/null +++ b/src/shared/l1gprs.c @@ -0,0 +1,831 @@ +/* + * l1gprs - GPRS layer 1 implementation + * + * (C) 2022-2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de> + * + * 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. + * + */ + +#include <errno.h> +#include <stdint.h> +#include <stdbool.h> + +#include <arpa/inet.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/msgb.h> + +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/protocol/gsm_44_060.h> +#include <osmocom/gsm/gsm0502.h> + +#include <osmocom/bb/l1ctl_proto.h> +#include <osmocom/bb/l1gprs.h> + +#define LOGP_GPRS(gprs, level, fmt, args...) \ + LOGP(l1gprs_log_cat, level, "%s" fmt, \ + (gprs)->log_prefix, ## args) + +#define LOGP_PDCH(pdch, level, fmt, args...) \ + LOGP_GPRS((pdch)->gprs, level, "(PDCH-%u) " fmt, \ + (pdch)->tn, ## args) + +#define LOG_TBF_CFG_REQ_FMT "tbf_ref=%u, slotmask=0x%02x, start_fn=%u" +#define LOG_TBF_CFG_REQ_ARGS(req) \ + (req)->tbf_ref, (req)->slotmask, ntohl((req)->start_fn) + +#define LOG_TBF_FMT "%cL-TBF#%03d(slotmask=0x%02x)" +#define LOG_TBF_ARGS(tbf) \ + (tbf)->uplink ? 'U' : 'D', (tbf)->tbf_ref, (tbf)->slotmask + +#define TDMA_FN_INVALID 0xffffffff + +static int l1gprs_log_cat = DLGLOBAL; + +enum gprs_rlcmac_block_type { + GPRS_RLCMAC_DATA_BLOCK = 0x00, + GPRS_RLCMAC_CONTROL_BLOCK = 0x01, + GPRS_RLCMAC_CONTROL_BLOCK_OPT = 0x02, + GPRS_RLCMAC_RESERVED = 0x03, +}; + +static struct l1gprs_tbf_pending_req *l1gprs_tbf_pending_req_alloc(void *talloc_ctx, + bool uplink, uint8_t tbf_ref, + uint8_t slotmask, uint32_t start_fn) +{ + struct l1gprs_tbf_pending_req *preq; + + preq = talloc(talloc_ctx, struct l1gprs_tbf_pending_req); + OSMO_ASSERT(preq != NULL); + + preq->uplink = uplink; + preq->tbf_ref = tbf_ref; + preq->slotmask = slotmask; + preq->start_fn = start_fn; + + return preq; +} + +static void l1gprs_tbf_pending_req_free(struct l1gprs_tbf_pending_req *preq) +{ + if (preq == NULL) + return; + llist_del(&preq->list); + talloc_free(preq); +} + + +static struct l1gprs_tbf *l1gprs_tbf_alloc(void *talloc_ctx, + bool uplink, uint8_t tbf_ref, + uint8_t slotmask) +{ + struct l1gprs_tbf *tbf; + + tbf = talloc(talloc_ctx, struct l1gprs_tbf); + OSMO_ASSERT(tbf != NULL); + + tbf->uplink = uplink; + tbf->tbf_ref = tbf_ref; + tbf->slotmask = slotmask; + + return tbf; +} + +static void l1gprs_tbf_free(struct l1gprs_tbf *tbf) +{ + if (tbf == NULL) + return; + llist_del(&tbf->list); + talloc_free(tbf); +} + +static struct l1gprs_tbf *_l1gprs_find_tbf(const struct llist_head *tbf_list, + bool uplink, uint8_t tbf_ref) +{ + struct l1gprs_tbf *tbf; + + llist_for_each_entry(tbf, tbf_list, list) { + if (tbf->uplink != uplink) + continue; + if (tbf->tbf_ref != tbf_ref) + continue; + return tbf; + } + + return NULL; +} + +static struct l1gprs_tbf *l1gprs_find_tbf(struct l1gprs_state *gprs, + bool uplink, uint8_t tbf_ref) +{ + struct l1gprs_tbf *tbf; + + if ((tbf = _l1gprs_find_tbf(&gprs->tbf_list, uplink, tbf_ref)) != NULL) + return tbf; + return NULL; +} + +static void l1gprs_register_tbf(struct l1gprs_state *gprs, + struct l1gprs_tbf *tbf) +{ + OSMO_ASSERT(tbf->slotmask != 0x00); + + /* Update the PDCH states */ + for (unsigned int tn = 0; tn < ARRAY_SIZE(gprs->pdch); tn++) { + struct l1gprs_pdch *pdch = &gprs->pdch[tn]; + + if (~tbf->slotmask & (1 << pdch->tn)) + continue; + + if (tbf->uplink) { + pdch->ul_tbf_count++; + } else { + pdch->dl_tbf_count++; + pdch->dl_tfi_mask |= (1 << tbf->dl_tfi); + } + + LOGP_PDCH(pdch, LOGL_DEBUG, + "Linked " LOG_TBF_FMT "\n", + LOG_TBF_ARGS(tbf)); + + /* If just got first use: */ + if (l1gprs_pdch_use_count(pdch) == 1) { + if (gprs->pdch_changed_cb) + gprs->pdch_changed_cb(pdch, true); + } + } + + llist_add_tail(&tbf->list, &gprs->tbf_list); + + LOGP_GPRS(gprs, LOGL_INFO, + LOG_TBF_FMT " is registered as active\n", + LOG_TBF_ARGS(tbf)); +} + +static void l1gprs_update_tbf(struct l1gprs_state *gprs, struct l1gprs_tbf *tbf, uint8_t slotmask) +{ + OSMO_ASSERT(tbf->slotmask != 0x00); + OSMO_ASSERT(slotmask != 0x00); + + if (tbf->slotmask == slotmask) + return; /* No change at all, skip */ + + /* Update the PDCH states */ + for (unsigned int tn = 0; tn < ARRAY_SIZE(gprs->pdch); tn++) { + struct l1gprs_pdch *pdch = &gprs->pdch[tn]; + + if ((tbf->slotmask & (1 << tn)) == (slotmask & (1 << tn))) + continue; /* No change, skip */ + + if (tbf->slotmask & (1 << tn)) { + /* slot previously set, remove it */ + if (tbf->uplink) { + OSMO_ASSERT(pdch->ul_tbf_count > 0); + pdch->ul_tbf_count--; + } else { + OSMO_ASSERT(pdch->dl_tbf_count > 0); + pdch->dl_tbf_count--; + pdch->dl_tfi_mask &= ~(1 << tbf->dl_tfi); + } + LOGP_PDCH(pdch, LOGL_DEBUG, "Unlinked " LOG_TBF_FMT "\n", + LOG_TBF_ARGS(tbf)); + /* If not more in use: */ + if (l1gprs_pdch_use_count(pdch) == 0) { + if (gprs->pdch_changed_cb) + gprs->pdch_changed_cb(pdch, false); + } + } else { + /* Slot was not set, add it */ + if (tbf->uplink) { + pdch->ul_tbf_count++; + } else { + pdch->dl_tbf_count++; + pdch->dl_tfi_mask |= (1 << tbf->dl_tfi); + } + LOGP_PDCH(pdch, LOGL_DEBUG, "Linked " LOG_TBF_FMT "\n", + LOG_TBF_ARGS(tbf)); + /* If just got first use: */ + if (l1gprs_pdch_use_count(pdch) == 1) { + if (gprs->pdch_changed_cb) + gprs->pdch_changed_cb(pdch, true); + } + } + } + + LOGP_GPRS(gprs, LOGL_INFO, + LOG_TBF_FMT " slotmask updated 0x%02x -> 0x%02x\n", + LOG_TBF_ARGS(tbf), tbf->slotmask, slotmask); + + tbf->slotmask = slotmask; +} + +static void l1gprs_unregister_tbf(struct l1gprs_state *gprs, struct l1gprs_tbf *tbf) +{ + OSMO_ASSERT(tbf->slotmask != 0x00); + + /* Update the PDCH states */ + for (unsigned int tn = 0; tn < ARRAY_SIZE(gprs->pdch); tn++) { + struct l1gprs_pdch *pdch = &gprs->pdch[tn]; + + if (~tbf->slotmask & (1 << pdch->tn)) + continue; + + if (tbf->uplink) { + OSMO_ASSERT(pdch->ul_tbf_count > 0); + pdch->ul_tbf_count--; + } else { + OSMO_ASSERT(pdch->dl_tbf_count > 0); + pdch->dl_tbf_count--; + pdch->dl_tfi_mask &= ~(1 << tbf->dl_tfi); + } + + LOGP_PDCH(pdch, LOGL_DEBUG, + "Unlinked " LOG_TBF_FMT "\n", + LOG_TBF_ARGS(tbf)); + + /* If not more in use: */ + if (l1gprs_pdch_use_count(pdch) == 0) { + if (gprs->pdch_changed_cb) + gprs->pdch_changed_cb(pdch, false); + } + } + + LOGP_GPRS(gprs, LOGL_INFO, + LOG_TBF_FMT " is unregistered and free()d\n", + LOG_TBF_ARGS(tbf)); + + l1gprs_tbf_free(tbf); +} + +static void l1gprs_add_tbf_pending_req(struct l1gprs_state *gprs, struct l1gprs_tbf_pending_req *preq) +{ + OSMO_ASSERT(preq->slotmask != 0x00); + + /* Update the PDCH states */ + for (unsigned int tn = 0; tn < ARRAY_SIZE(gprs->pdch); tn++) { + struct l1gprs_pdch *pdch = &gprs->pdch[tn]; + + if (~preq->slotmask & (1 << pdch->tn)) + continue; + + if (preq->uplink) { + pdch->pending_ul_tbf_count++; + } else { + pdch->pending_dl_tbf_count++; + /* We don't care about DL_TFI here, we don't want to activate it */ + } + + LOGP_PDCH(pdch, LOGL_DEBUG, + "Linked " LOG_TBF_FMT "\n", + LOG_TBF_ARGS(preq)); + /* If just got first use: */ + if (l1gprs_pdch_use_count(pdch) == 1) { + if (gprs->pdch_changed_cb) + gprs->pdch_changed_cb(pdch, true); + } + } + + llist_add_tail(&preq->list, &gprs->tbf_list_pending); + + LOGP_GPRS(gprs, LOGL_INFO, + LOG_TBF_FMT " is added as pending (fn=%u)\n", + LOG_TBF_ARGS(preq), preq->start_fn); +} + +static void l1gprs_remove_tbf_pending_req(struct l1gprs_state *gprs, struct l1gprs_tbf_pending_req *preq) +{ + + OSMO_ASSERT(preq->slotmask != 0x00); + + /* Update the PDCH states */ + for (unsigned int tn = 0; tn < ARRAY_SIZE(gprs->pdch); tn++) { + struct l1gprs_pdch *pdch = &gprs->pdch[tn]; + + if (~preq->slotmask & (1 << pdch->tn)) + continue; + + if (preq->uplink) { + OSMO_ASSERT(pdch->pending_ul_tbf_count > 0); + pdch->pending_ul_tbf_count--; + } else { + OSMO_ASSERT(pdch->pending_dl_tbf_count > 0); + pdch->pending_dl_tbf_count--; + /* We don't care about DL_TFI here, we didn't activate them in first place */ + } + + LOGP_PDCH(pdch, LOGL_DEBUG, + "Unlinked " LOG_TBF_FMT "\n", + LOG_TBF_ARGS(preq)); + /* Note: not calling gprs->pdch_changed_cb since no real + * activate / deactivate change can occur on lower layers as a + * consequence of moving a PDCH from pending to active, hence + * avoid triggering one active=false event here and immediately + * afterwards the opposite event when adding it as active: */ + } + + llist_del(&preq->list); + + LOGP_GPRS(gprs, LOGL_INFO, + LOG_TBF_FMT " is removed as pending (fn=%u)\n", + LOG_TBF_ARGS(preq), preq->start_fn); +} + +/* Check the list of pending TBFs and move those with expired Fn to the active list */ +static void l1gprs_check_pending_tbfs(struct l1gprs_state *gprs, uint32_t fn) +{ + struct l1gprs_tbf_pending_req *preq, *tmp; + struct l1gprs_tbf *tbf; + + llist_for_each_entry_safe(preq, tmp, &gprs->tbf_list_pending, list) { + if (gsm0502_fncmp(fn, preq->start_fn) < 0) + continue; + + LOGP_GPRS(gprs, LOGL_INFO, + LOG_TBF_FMT " becomes active (current_fn=%u, start_fn=%u)\n", + LOG_TBF_ARGS(preq), fn, preq->start_fn); + + l1gprs_remove_tbf_pending_req(gprs, preq); + + /* If this tbf already exists in the main list, simply update its timeslot: */ + tbf = _l1gprs_find_tbf(&gprs->tbf_list, preq->uplink, preq->tbf_ref); + if (tbf) { + l1gprs_update_tbf(gprs, tbf, preq->slotmask); + tbf->dl_tfi = preq->dl_tfi; + } else { + tbf = l1gprs_tbf_alloc(gprs, preq->uplink, preq->tbf_ref, preq->slotmask); + tbf->dl_tfi = preq->dl_tfi; + l1gprs_register_tbf(gprs, tbf); + } + talloc_free(preq); + } +} + +#define L1GPRS_L1CTL_MSGB_SIZE 256 +#define L1GPRS_L1CTL_MSGB_HEADROOM 32 + +static struct msgb *l1gprs_l1ctl_msgb_alloc(uint8_t msg_type) +{ + struct l1ctl_hdr *l1h; + struct msgb *msg; + + msg = msgb_alloc_headroom(L1GPRS_L1CTL_MSGB_SIZE, + L1GPRS_L1CTL_MSGB_HEADROOM, + "l1gprs_l1ctl_msg"); + if (msg == NULL) + return NULL; + + msg->l1h = msgb_put(msg, sizeof(*l1h)); + l1h = (struct l1ctl_hdr *)msg->l1h; + l1h->msg_type = msg_type; + + return msg; +} + +static bool l1gprs_pdch_filter_dl_block(const struct l1gprs_pdch *pdch, + const uint8_t *data) +{ + enum gprs_rlcmac_block_type block_type = data[0] >> 6; + uint8_t dl_tfi; + + switch (block_type) { + case GPRS_RLCMAC_DATA_BLOCK: + /* see 3GPP TS 44.060, section 10.2.1 */ + dl_tfi = (data[1] >> 1) & 0x1f; + break; + case GPRS_RLCMAC_CONTROL_BLOCK_OPT: + /* see 3GPP TS 44.060, section 10.3.1 */ + dl_tfi = (data[2] >> 1) & 0x1f; + break; + case GPRS_RLCMAC_CONTROL_BLOCK: + /* no optional octets */ + return true; + default: + LOGP_PDCH(pdch, LOGL_NOTICE, + "Rx Downlink block with unknown payload (0x%0x)\n", + block_type); + return false; + } + + if (pdch->dl_tfi_mask & (1 << dl_tfi)) + return true; + return false; +} + +/////////////////////////////////////////////////////////////////////////////////// + +void l1gprs_logging_init(int logc) +{ + l1gprs_log_cat = logc; +} + +struct l1gprs_state *l1gprs_state_alloc(void *ctx, const char *log_prefix, void *priv) +{ + struct l1gprs_state *gprs; + + gprs = talloc_zero(ctx, struct l1gprs_state); + if (gprs == NULL) + return NULL; + + for (unsigned int tn = 0; tn < ARRAY_SIZE(gprs->pdch); tn++) { + struct l1gprs_pdch *pdch = &gprs->pdch[tn]; + + pdch->tn = tn; + pdch->gprs = gprs; + } + + INIT_LLIST_HEAD(&gprs->tbf_list); + INIT_LLIST_HEAD(&gprs->tbf_list_pending); + + if (log_prefix == NULL) + gprs->log_prefix = talloc_asprintf(gprs, "l1gprs[0x%p]: ", gprs); + else + gprs->log_prefix = talloc_strdup(gprs, log_prefix); + gprs->priv = priv; + + return gprs; +} + +void l1gprs_state_free(struct l1gprs_state *gprs) +{ + if (gprs == NULL) + return; + + while (!llist_empty(&gprs->tbf_list)) { + struct l1gprs_tbf *tbf; + + tbf = llist_first_entry(&gprs->tbf_list, struct l1gprs_tbf, list); + LOGP_GPRS(gprs, LOGL_DEBUG, + "%s(): " LOG_TBF_FMT " is free()d\n", + __func__, LOG_TBF_ARGS(tbf)); + l1gprs_tbf_free(tbf); + } + + while (!llist_empty(&gprs->tbf_list_pending)) { + struct l1gprs_tbf_pending_req *preq; + + preq = llist_first_entry(&gprs->tbf_list_pending, struct l1gprs_tbf_pending_req, list); + LOGP_GPRS(gprs, LOGL_DEBUG, + "%s(): " LOG_TBF_FMT " is free()d\n", + __func__, LOG_TBF_ARGS(preq)); + l1gprs_tbf_pending_req_free(preq); + } + + talloc_free(gprs->log_prefix); + talloc_free(gprs); +} + +void l1gprs_state_set_pdch_changed_cb(struct l1gprs_state *gprs, l1gprs_pdch_changed_t pdch_changed_cb) +{ + gprs->pdch_changed_cb = pdch_changed_cb; +} + +int l1gprs_handle_ul_tbf_cfg_req(struct l1gprs_state *gprs, const struct msgb *msg) +{ + const struct l1ctl_gprs_ul_tbf_cfg_req *req = (void *)msg->l1h; + struct l1gprs_tbf *tbf = NULL; + + OSMO_ASSERT(req != NULL); + + if (msgb_l1len(msg) < sizeof(*req)) { + LOGP_GPRS(gprs, LOGL_ERROR, + "Rx malformed Uplink TBF config (len=%u < %zu)\n", + msgb_l1len(msg), sizeof(*req)); + return -EINVAL; + } + + LOGP_GPRS(gprs, LOGL_INFO, + "Rx UL TBF config: " LOG_TBF_CFG_REQ_FMT "\n", + LOG_TBF_CFG_REQ_ARGS(req)); + + if (req->slotmask != 0x00) { + uint32_t start_fn = ntohl(req->start_fn); + if (start_fn != TDMA_FN_INVALID) { + /* Create a temporary tbf and keep it in a separate + * list. It will be moved/merged into the main list at + * start_fn time. */ + struct l1gprs_tbf_pending_req *preq; + preq = l1gprs_tbf_pending_req_alloc(gprs, true, req->tbf_ref, + req->slotmask, start_fn); + l1gprs_add_tbf_pending_req(gprs, preq); + return 0; + } + + tbf = l1gprs_find_tbf(gprs, true, req->tbf_ref); + if (tbf) { + l1gprs_update_tbf(gprs, tbf, req->slotmask); + } else { + tbf = l1gprs_tbf_alloc(gprs, true, req->tbf_ref, req->slotmask); + l1gprs_register_tbf(gprs, tbf); + } + } else { + tbf = l1gprs_find_tbf(gprs, true, req->tbf_ref); + if (tbf == NULL) { + LOGP_GPRS(gprs, LOGL_ERROR, "%s(): " LOG_TBF_FMT " not found\n", + __func__, 'U', req->tbf_ref, req->slotmask); + return -ENOENT; + } + l1gprs_unregister_tbf(gprs, tbf); + } + + return 0; +} + +int l1gprs_handle_dl_tbf_cfg_req(struct l1gprs_state *gprs, const struct msgb *msg) +{ + const struct l1ctl_gprs_dl_tbf_cfg_req *req = (void *)msg->l1h; + struct l1gprs_tbf *tbf = NULL; + + OSMO_ASSERT(req != NULL); + + if (msgb_l1len(msg) < sizeof(*req)) { + LOGP_GPRS(gprs, LOGL_ERROR, + "Rx malformed Downlink TBF config (len=%u < %zu)\n", + msgb_l1len(msg), sizeof(*req)); + return -EINVAL; + } + + LOGP_GPRS(gprs, LOGL_INFO, + "Rx DL TBF config: " LOG_TBF_CFG_REQ_FMT ", dl_tfi=%u\n", + LOG_TBF_CFG_REQ_ARGS(req), req->dl_tfi); + + if (req->dl_tfi > 31) { + LOGP_GPRS(gprs, LOGL_ERROR, + "Invalid DL TFI %u (shall be in range 0..31)\n", + req->dl_tfi); + return -EINVAL; + } + + if (req->slotmask != 0x00) { + uint32_t start_fn = ntohl(req->start_fn); + if (start_fn != TDMA_FN_INVALID) { + /* Create a temporary tbf and keep it in a separate + * list. It will be moved/merged into the main list at + * start_fn time. */ + struct l1gprs_tbf_pending_req *preq; + preq = l1gprs_tbf_pending_req_alloc(gprs, false, req->tbf_ref, + req->slotmask, start_fn); + preq->dl_tfi = req->dl_tfi; + l1gprs_add_tbf_pending_req(gprs, preq); + return 0; + } + + tbf = l1gprs_find_tbf(gprs, false, req->tbf_ref); + if (tbf) { + l1gprs_update_tbf(gprs, tbf, req->slotmask); + } else { + tbf = l1gprs_tbf_alloc(gprs, false, req->tbf_ref, + req->slotmask); + tbf->dl_tfi = req->dl_tfi; + l1gprs_register_tbf(gprs, tbf); + } + } else { + tbf = l1gprs_find_tbf(gprs, false, req->tbf_ref); + if (tbf == NULL) { + LOGP_GPRS(gprs, LOGL_ERROR, "%s(): " LOG_TBF_FMT " not found\n", + __func__, 'D', req->tbf_ref, req->slotmask); + return -ENOENT; + } + l1gprs_unregister_tbf(gprs, tbf); + } + + return 0; +} + +int l1gprs_handle_ul_block_req(struct l1gprs_state *gprs, + struct l1gprs_prim_ul_block_req *req, + const struct msgb *msg) +{ + const struct l1ctl_gprs_ul_block_req *l1br = (void *)msg->l1h; + const struct l1gprs_pdch *pdch = NULL; + size_t data_len; + uint32_t fn; + + OSMO_ASSERT(l1br != NULL); + + if (OSMO_UNLIKELY(msgb_l1len(msg) < sizeof(*l1br))) { + LOGP_GPRS(gprs, LOGL_ERROR, + "Rx malformed UL BLOCK.req (len=%u < %zu)\n", + msgb_l1len(msg), sizeof(*l1br)); + return -EINVAL; + } + fn = ntohl(l1br->hdr.fn); + if (OSMO_UNLIKELY(l1br->hdr.tn >= ARRAY_SIZE(gprs->pdch))) { + LOGP_GPRS(gprs, LOGL_ERROR, + "Rx malformed UL BLOCK.req (fn=%u, tn=%u)\n", + fn, l1br->hdr.tn); + return -EINVAL; + } + + pdch = &gprs->pdch[l1br->hdr.tn]; + data_len = msgb_l1len(msg) - sizeof(*l1br); + + LOGP_PDCH(pdch, LOGL_DEBUG, + "Rx UL BLOCK.req (fn=%u, len=%zu): %s\n", + fn, data_len, osmo_hexdump(l1br->data, data_len)); + + if ((pdch->ul_tbf_count == 0) && (pdch->dl_tbf_count == 0)) { + LOGP_PDCH(pdch, LOGL_ERROR, + "Rx UL BLOCK.req (fn=%u, len=%zu), but this PDCH has no configured TBFs\n", + fn, data_len); + return -EINVAL; + } + + *req = (struct l1gprs_prim_ul_block_req) { + .hdr = { + .fn = fn, + .tn = l1br->hdr.tn, + }, + .data = &l1br->data[0], + .data_len = data_len, + }; + + return 0; +} + +struct msgb *l1gprs_handle_ul_block_cnf(struct l1gprs_state *gprs, + uint32_t fn, uint8_t tn, + const uint8_t *data, + size_t data_len) +{ + const struct l1gprs_pdch *pdch = NULL; + struct l1ctl_gprs_ul_block_cnf *l1bc; + struct msgb *msg; + + OSMO_ASSERT(tn < ARRAY_SIZE(gprs->pdch)); + pdch = &gprs->pdch[tn]; + + LOGP_PDCH(pdch, LOGL_DEBUG, "Rx UL BLOCK.cnf (fn=%u)\n", fn); + + if (pdch->ul_tbf_count + pdch->dl_tbf_count == 0) { + LOGP_PDCH(pdch, LOGL_ERROR, + "Rx UL BLOCK.cnf (fn=%u), but this PDCH has no active TBFs\n", + fn); + return NULL; + } + + msg = l1gprs_l1ctl_msgb_alloc(L1CTL_GPRS_UL_BLOCK_CNF); + if (OSMO_UNLIKELY(msg == NULL)) { + LOGP_GPRS(gprs, LOGL_ERROR, "l1gprs_l1ctl_msgb_alloc() failed\n"); + return NULL; + } + + l1bc = (void *)msgb_put(msg, sizeof(*l1bc)); + *l1bc = (struct l1ctl_gprs_ul_block_cnf) { + .fn = htonl(fn), + .tn = tn, + }; + + if (data != NULL && data_len > 0) + memcpy(msgb_put(msg, data_len), data, data_len); + + return msg; +} + +/* Check if a Downlink block is a PTCCH/D (see 3GPP TS 45.002, table 6) */ +#define BLOCK_IND_IS_PTCCH(ind) \ + (((ind)->hdr.fn % 104) == 12) + +struct msgb *l1gprs_handle_dl_block_ind(struct l1gprs_state *gprs, + const struct l1gprs_prim_dl_block_ind *ind, + uint8_t *usf) +{ + const struct l1gprs_pdch *pdch = NULL; + struct l1ctl_gprs_dl_block_ind *l1bi; + enum osmo_gprs_cs cs; + struct msgb *msg; + + if (OSMO_UNLIKELY(ind->hdr.tn >= ARRAY_SIZE(gprs->pdch))) { + LOGP_GPRS(gprs, LOGL_ERROR, + "Rx malformed DL BLOCK.ind (tn=%u)\n", + ind->hdr.tn); + return NULL; + } + + pdch = &gprs->pdch[ind->hdr.tn]; + + LOGP_PDCH(pdch, LOGL_DEBUG, + "Rx DL BLOCK.ind (%s, fn=%u, len=%zu): %s\n", + BLOCK_IND_IS_PTCCH(ind) ? "PTCCH" : "PDTCH", + ind->hdr.fn, ind->data_len, osmo_hexdump(ind->data, ind->data_len)); + + l1gprs_check_pending_tbfs(gprs, ind->hdr.fn); + + if (pdch->ul_tbf_count + pdch->dl_tbf_count == 0) { + if (pdch->pending_ul_tbf_count + pdch->pending_dl_tbf_count > 0) + LOGP_PDCH(pdch, LOGL_DEBUG, + "Rx DL BLOCK.ind (fn=%u), but this PDCH has no active TBFs yet\n", + ind->hdr.fn); + else + LOGP_PDCH(pdch, LOGL_ERROR, + "Rx DL BLOCK.ind (fn=%u), but this PDCH has no configured TBFs\n", + ind->hdr.fn); + return NULL; + } + + msg = l1gprs_l1ctl_msgb_alloc(L1CTL_GPRS_DL_BLOCK_IND); + if (OSMO_UNLIKELY(msg == NULL)) { + LOGP_GPRS(gprs, LOGL_ERROR, "l1gprs_l1ctl_msgb_alloc() failed\n"); + return NULL; + } + + l1bi = (void *)msgb_put(msg, sizeof(*l1bi)); + *l1bi = (struct l1ctl_gprs_dl_block_ind) { + .hdr = { + .fn = htonl(ind->hdr.fn), + .tn = ind->hdr.tn, + }, + .meas = { + .ber10k = htons(ind->meas.ber10k), + .ci_cb = htons(ind->meas.ci_cb), + .rx_lev = ind->meas.rx_lev, + }, + .usf = 0xff, + }; + + if (ind->data_len == 0) + return msg; + if (BLOCK_IND_IS_PTCCH(ind)) { + memcpy(msgb_put(msg, ind->data_len), ind->data, ind->data_len); + return msg; + } + + cs = osmo_gprs_dl_cs_by_block_bytes(ind->data_len); + switch (cs) { + case OSMO_GPRS_CS1: + case OSMO_GPRS_CS2: + case OSMO_GPRS_CS3: + case OSMO_GPRS_CS4: + l1bi->usf = ind->data[0] & 0x07; + *usf = l1bi->usf; + /* Determine whether to include the payload or not */ + if (l1gprs_pdch_filter_dl_block(pdch, ind->data)) + memcpy(msgb_put(msg, ind->data_len), ind->data, ind->data_len); + break; + case OSMO_GPRS_CS_NONE: + LOGP_PDCH(pdch, LOGL_ERROR, + "Failed to determine Coding Scheme (len=%zu)\n", ind->data_len); + break; + default: + LOGP_PDCH(pdch, LOGL_NOTICE, "Coding Scheme %d is not supported\n", cs); + break; + } + + return msg; +} + +struct msgb *l1gprs_handle_rts_ind(struct l1gprs_state *gprs, uint32_t fn, uint8_t tn, uint8_t usf) +{ + const struct l1gprs_pdch *pdch = NULL; + struct l1ctl_gprs_rts_ind *l1bi; + struct msgb *msg; + + OSMO_ASSERT(tn < ARRAY_SIZE(gprs->pdch)); + pdch = &gprs->pdch[tn]; + + LOGP_PDCH(pdch, LOGL_DEBUG, + "Rx RTS.ind (PDTCH, fn=%u, usf=%u)\n", + fn, usf); + + l1gprs_check_pending_tbfs(gprs, fn); + + if (pdch->ul_tbf_count + pdch->dl_tbf_count == 0) { + if (pdch->pending_ul_tbf_count + pdch->pending_dl_tbf_count > 0) + LOGP_PDCH(pdch, LOGL_DEBUG, + "Rx RTS.ind (fn=%u, usf=%u), but this PDCH has no active TBFs yet\n", + fn, usf); + else + LOGP_PDCH(pdch, LOGL_ERROR, + "Rx RTS.ind (fn=%u, usf=%u), but this PDCH has no configured TBFs\n", + fn, usf); + return NULL; + } + + msg = l1gprs_l1ctl_msgb_alloc(L1CTL_GPRS_RTS_IND); + if (OSMO_UNLIKELY(msg == NULL)) { + LOGP_GPRS(gprs, LOGL_ERROR, "l1gprs_l1ctl_msgb_alloc() failed\n"); + return NULL; + } + + l1bi = (void *)msgb_put(msg, sizeof(*l1bi)); + *l1bi = (struct l1ctl_gprs_rts_ind) { + .fn = htonl(fn), + .tn = tn, + .usf = usf, + }; + + return msg; +} |