pespin has submitted this change. ( 
https://gerrit.osmocom.org/c/libosmo-gprs/+/31286 )

Change subject: rlcmac: Introduce DL TBF creation through PCH ImmAss
......................................................................

rlcmac: Introduce DL TBF creation through PCH ImmAss

This patch only introduces the gprs_rlcmac_dl_tbf subclass and
allocates/frees it based on PCH ImmAss, and adds initial paths for
received blocks. It also provides a unit test to showcase the scenario.

Change-Id: I7f98e3456ef35d80becdad3481afeb771457b0ef
---
M include/osmocom/gprs/rlcmac/Makefile.am
M include/osmocom/gprs/rlcmac/coding_scheme.h
M include/osmocom/gprs/rlcmac/gre.h
M include/osmocom/gprs/rlcmac/rlcmac.h
M include/osmocom/gprs/rlcmac/rlcmac_private.h
A include/osmocom/gprs/rlcmac/tbf_dl.h
A include/osmocom/gprs/rlcmac/tbf_dl_fsm.h
M include/osmocom/gprs/rlcmac/types_private.h
M src/rlcmac/Makefile.am
M src/rlcmac/coding_scheme.c
M src/rlcmac/gre.c
M src/rlcmac/misc.c
M src/rlcmac/rlcmac.c
M src/rlcmac/rlcmac_prim.c
A src/rlcmac/tbf_dl.c
A src/rlcmac/tbf_dl_fsm.c
M tests/rlcmac/Makefile.am
M tests/rlcmac/rlcmac_prim_test.c
M tests/rlcmac/rlcmac_prim_test.err
M tests/rlcmac/rlcmac_prim_test.ok
20 files changed, 722 insertions(+), 8 deletions(-)

Approvals:
  laforge: Looks good to me, but someone else must approve
  Jenkins Builder: Verified
  pespin: Looks good to me, approved




diff --git a/include/osmocom/gprs/rlcmac/Makefile.am 
b/include/osmocom/gprs/rlcmac/Makefile.am
index 3f04ee3..e91f342 100644
--- a/include/osmocom/gprs/rlcmac/Makefile.am
+++ b/include/osmocom/gprs/rlcmac/Makefile.am
@@ -10,6 +10,8 @@
        rlcmac_private.h \
        sched.h \
        tbf.h \
+       tbf_dl.h \
+       tbf_dl_fsm.h \
        tbf_ul.h \
        tbf_ul_fsm.h \
        tbf_ul_ass_fsm.h \
diff --git a/include/osmocom/gprs/rlcmac/coding_scheme.h 
b/include/osmocom/gprs/rlcmac/coding_scheme.h
index 31692a5..5b71e73 100644
--- a/include/osmocom/gprs/rlcmac/coding_scheme.h
+++ b/include/osmocom/gprs/rlcmac/coding_scheme.h
@@ -61,6 +61,7 @@
 uint8_t gprs_rlcmac_mcs_chan_code(enum gprs_rlcmac_coding_scheme cs);

 enum gprs_rlcmac_coding_scheme gprs_rlcmac_mcs_get_by_size_ul(unsigned size);
+enum gprs_rlcmac_coding_scheme gprs_rlcmac_mcs_get_by_size_dl(unsigned size);
 enum gprs_rlcmac_coding_scheme gprs_rlcmac_mcs_get_gprs_by_num(unsigned num);
 enum gprs_rlcmac_coding_scheme gprs_rlcmac_mcs_get_egprs_by_num(unsigned num);
 bool gprs_rlcmac_mcs_is_valid(enum gprs_rlcmac_coding_scheme cs);
diff --git a/include/osmocom/gprs/rlcmac/gre.h 
b/include/osmocom/gprs/rlcmac/gre.h
index ea50ddb..ca5f773 100644
--- a/include/osmocom/gprs/rlcmac/gre.h
+++ b/include/osmocom/gprs/rlcmac/gre.h
@@ -4,6 +4,7 @@
 #include <osmocom/gprs/rlcmac/rlcmac.h>
 #include <osmocom/gprs/rlcmac/llc_queue.h>

+struct gprs_rlcmac_dl_tbf;
 struct gprs_rlcmac_ul_tbf;

 struct gprs_rlcmac_entity {
@@ -12,6 +13,7 @@

        struct gprs_rlcmac_llc_queue *llc_queue;

+       struct gprs_rlcmac_dl_tbf *dl_tbf;
        struct gprs_rlcmac_ul_tbf *ul_tbf;
 };

diff --git a/include/osmocom/gprs/rlcmac/rlcmac.h 
b/include/osmocom/gprs/rlcmac/rlcmac.h
index 2de5687..64a2073 100644
--- a/include/osmocom/gprs/rlcmac/rlcmac.h
+++ b/include/osmocom/gprs/rlcmac/rlcmac.h
@@ -18,6 +18,7 @@
 enum osmo_gprs_rlcmac_log_cat {
        OSMO_GPRS_RLCMAC_LOGC_RLCMAC,
        OSMO_GPRS_RLCMAC_LOGC_TBFUL,
+       OSMO_GPRS_RLCMAC_LOGC_TBFDL,
        _OSMO_GPRS_RLCMAC_LOGC_MAX,
 };

diff --git a/include/osmocom/gprs/rlcmac/rlcmac_private.h 
b/include/osmocom/gprs/rlcmac/rlcmac_private.h
index f35b69a..36c634c 100644
--- a/include/osmocom/gprs/rlcmac/rlcmac_private.h
+++ b/include/osmocom/gprs/rlcmac/rlcmac_private.h
@@ -28,6 +28,16 @@
        struct gprs_rlcmac_ul_tbf_allocation_ts ts[8];
 };

+struct gprs_rlcmac_dl_tbf_allocation_ts {
+       bool allocated;
+};
+
+struct gprs_rlcmac_dl_tbf_allocation {
+       uint8_t dl_tfi;
+       uint8_t num_ts; /* number of allocated TS */
+       struct gprs_rlcmac_dl_tbf_allocation_ts ts[8];
+};
+
 extern int g_rlcmac_log_cat[_OSMO_GPRS_RLCMAC_LOGC_MAX];

 #define LOGRLCMAC(lvl, fmt, args...) 
LOGP(g_rlcmac_log_cat[OSMO_GPRS_RLCMAC_LOGC_RLCMAC], lvl, fmt, ## args)
@@ -56,13 +66,17 @@
        struct llist_head gre_list; /* contains (struct 
gprs_rlcmac_entity)->entry */

        uint8_t next_ul_tbf_nr;
+       uint8_t next_dl_tbf_nr;
 };

 extern struct gprs_rlcmac_ctx *g_ctx;

 /* rlcmac.c */
 struct gprs_rlcmac_entity *gprs_rlcmac_find_entity_by_tlli(uint32_t tlli);
+struct gprs_rlcmac_dl_tbf *gprs_rlcmac_find_dl_tbf_by_tfi(uint8_t dl_tfi);
 int gprs_rlcmac_handle_ccch_imm_ass(const struct gsm48_imm_ass *ia);
+int gprs_rlcmac_handle_gprs_dl_block(const struct osmo_gprs_rlcmac_prim 
*rlcmac_prim,
+                                    enum gprs_rlcmac_coding_scheme cs);

 /* rlcmac_prim.c */
 int gprs_rlcmac_prim_call_up_cb(struct osmo_gprs_rlcmac_prim *rlcmac_prim);
diff --git a/include/osmocom/gprs/rlcmac/tbf_dl.h 
b/include/osmocom/gprs/rlcmac/tbf_dl.h
new file mode 100644
index 0000000..56b0e8c
--- /dev/null
+++ b/include/osmocom/gprs/rlcmac/tbf_dl.h
@@ -0,0 +1,52 @@
+/* Downlink TBF, 3GPP TS 44.060 */
+#pragma once
+
+#include <inttypes.h>
+
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/gprs/rlcmac/tbf.h>
+#include <osmocom/gprs/rlcmac/tbf_dl_fsm.h>
+#include <osmocom/gprs/rlcmac/coding_scheme.h>
+#include <osmocom/gprs/rlcmac/sched.h>
+#include <osmocom/gprs/rlcmac/rlcmac_private.h>
+
+struct gprs_rlcmac_dl_tbf {
+       struct gprs_rlcmac_tbf tbf;
+       struct gprs_rlcmac_tbf_dl_fsm_ctx state_fsm;
+
+       /* Current TS/TFI/USF allocated by the PCU: */
+       struct gprs_rlcmac_dl_tbf_allocation cur_alloc;
+};
+
+struct gprs_rlcmac_dl_tbf *gprs_rlcmac_dl_tbf_alloc(struct gprs_rlcmac_entity 
*gre);
+void gprs_rlcmac_dl_tbf_free(struct gprs_rlcmac_dl_tbf *dl_tbf);
+
+int gprs_rlcmac_dl_tbf_configure_l1ctl(struct gprs_rlcmac_dl_tbf *dl_tbf);
+
+static inline struct gprs_rlcmac_tbf *dl_tbf_as_tbf(struct gprs_rlcmac_dl_tbf 
*dl_tbf)
+{
+       return &dl_tbf->tbf;
+}
+
+static inline const struct gprs_rlcmac_tbf *dl_tbf_as_tbf_const(const struct 
gprs_rlcmac_dl_tbf *dl_tbf)
+{
+       return &dl_tbf->tbf;
+}
+
+static inline struct gprs_rlcmac_dl_tbf *tbf_as_dl_tbf(struct gprs_rlcmac_tbf 
*tbf)
+{
+       OSMO_ASSERT(tbf->direction == GPRS_RLCMAC_TBF_DIR_DL);
+       return (struct gprs_rlcmac_dl_tbf *)tbf;
+}
+
+static inline const struct gprs_rlcmac_dl_tbf *tbf_as_dl_tbf_const(struct 
gprs_rlcmac_tbf *tbf)
+{
+       OSMO_ASSERT(tbf->direction == GPRS_RLCMAC_TBF_DIR_DL);
+       return (const struct gprs_rlcmac_dl_tbf *)tbf;
+}
+
+#define LOGPTBFDL(dl_tbf, lvl, fmt, args...) \
+       LOGP(g_rlcmac_log_cat[OSMO_GPRS_RLCMAC_LOGC_TBFUL], lvl, "TBF(DL:NR-%" 
PRIu8 ":TLLI-%08x) " fmt, \
+       (dl_tbf)->tbf.nr, (dl_tbf)->tbf.gre->tlli, \
+       ## args)
diff --git a/include/osmocom/gprs/rlcmac/tbf_dl_fsm.h 
b/include/osmocom/gprs/rlcmac/tbf_dl_fsm.h
new file mode 100644
index 0000000..5458b68
--- /dev/null
+++ b/include/osmocom/gprs/rlcmac/tbf_dl_fsm.h
@@ -0,0 +1,35 @@
+/* Uplink TBF, 3GPP TS 44.060 */
+#pragma once
+
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/gprs/rlcmac/rlcmac_private.h>
+
+struct gprs_rlcmac_dl_tbf;
+
+enum gprs_rlcmac_tbf_dl_fsm_states {
+       GPRS_RLCMAC_TBF_DL_ST_NEW = 0,  /* new created TBF */
+       GPRS_RLCMAC_TBF_DL_ST_FLOW,     /* RLC/MAC flow, resource needed */
+       GPRS_RLCMAC_TBF_DL_ST_FINISHED, /* flow finished, wait for release */
+};
+
+struct gprs_rlcmac_tbf_dl_fsm_ctx {
+       struct osmo_fsm_inst *fi;
+       union { /* back pointer. union used to easily access superclass from 
ctx */
+               struct gprs_rlcmac_tbf *tbf;
+               struct gprs_rlcmac_dl_tbf *dl_tbf;
+       };
+};
+
+enum tbf_dl_fsm_event {
+       GPRS_RLCMAC_TBF_DL_EV_LAST_DL_DATA_RECVD,
+       GPRS_RLCMAC_TBF_UL_EV_DL_ASS_COMPL,
+};
+
+int gprs_rlcmac_tbf_dl_fsm_init(void);
+void gprs_rlcmac_tbf_dl_fsm_set_log_cat(int logcat);
+
+int gprs_rlcmac_tbf_dl_fsm_constructor(struct gprs_rlcmac_dl_tbf *dl_tbf);
+void gprs_rlcmac_tbf_dl_fsm_destructor(struct gprs_rlcmac_dl_tbf *dl_tbf);
+
+enum gprs_rlcmac_tbf_dl_fsm_states gprs_rlcmac_tbf_dl_state(const struct 
gprs_rlcmac_dl_tbf *dl_tbf);
diff --git a/include/osmocom/gprs/rlcmac/types_private.h 
b/include/osmocom/gprs/rlcmac/types_private.h
index 10852cf..8450d08 100644
--- a/include/osmocom/gprs/rlcmac/types_private.h
+++ b/include/osmocom/gprs/rlcmac/types_private.h
@@ -3,6 +3,14 @@

 #include <osmocom/gprs/rlcmac/types.h>

+/* TS 44.060 Section 10.4.7 Table 10.4.7.1: Payload Type field */
+enum gprs_rlcmac_payload_type {
+       GPRS_RLCMAC_PT_DATA_BLOCK = 0x0,
+       GPRS_RLCMAC_PT_CONTROL_BLOCK = 0x1,
+       GPRS_RLCMAC_PT_CONTROL_BLOCK_OPT = 0x2,
+       GPRS_RLCMAC_PT_RESERVED = 0x3
+};
+
 /* TS 44.060 Table 11.2.16.2 "ACCESS_TYPE" */
 enum gprs_rlcmac_access_type {
        GPRS_RLCMAC_ACCESS_TYPE_2PHASE_ACC_REQ = 0, /* Two Phase Access Request 
*/
diff --git a/src/rlcmac/Makefile.am b/src/rlcmac/Makefile.am
index 6fca2b0..0612eaa 100644
--- a/src/rlcmac/Makefile.am
+++ b/src/rlcmac/Makefile.am
@@ -39,6 +39,8 @@
        rlcmac_prim.c \
        sched.c \
        tbf.c \
+       tbf_dl.c \
+       tbf_dl_fsm.c \
        tbf_ul.c \
        tbf_ul_fsm.c \
        tbf_ul_ass_fsm.c \
diff --git a/src/rlcmac/coding_scheme.c b/src/rlcmac/coding_scheme.c
index 889e0ff..350cb0b 100644
--- a/src/rlcmac/coding_scheme.c
+++ b/src/rlcmac/coding_scheme.c
@@ -144,6 +144,27 @@
        }
 }

+/* Same as UL. Sizes only change in EGPRS2 blocks, which we don't support */
+enum gprs_rlcmac_coding_scheme gprs_rlcmac_mcs_get_by_size_dl(unsigned size)
+{
+       switch (size) {
+       case 23: return GPRS_RLCMAC_CS_1;
+       case 27: return GPRS_RLCMAC_MCS_1;
+       case 33: return GPRS_RLCMAC_MCS_2;
+       case 34: return GPRS_RLCMAC_CS_2;
+       case 40: return GPRS_RLCMAC_CS_3;
+       case 42: return GPRS_RLCMAC_MCS_3;
+       case 49: return GPRS_RLCMAC_MCS_4;
+       case 54: return GPRS_RLCMAC_CS_4;
+       case 61: return GPRS_RLCMAC_MCS_5;
+       case 79: return GPRS_RLCMAC_MCS_6;
+       case 119: return GPRS_RLCMAC_MCS_7;
+       case 143: return GPRS_RLCMAC_MCS_8;
+       case 155: return GPRS_RLCMAC_MCS_9;
+       default: return GPRS_RLCMAC_CS_UNKNOWN;
+       }
+}
+
 enum gprs_rlcmac_coding_scheme gprs_rlcmac_mcs_get_gprs_by_num(unsigned num)
 {
        if (num < 1 || num > 4)
diff --git a/src/rlcmac/gre.c b/src/rlcmac/gre.c
index 3d8ba08..40cd585 100644
--- a/src/rlcmac/gre.c
+++ b/src/rlcmac/gre.c
@@ -24,6 +24,7 @@
 #include <osmocom/gprs/rlcmac/rlcmac.h>
 #include <osmocom/gprs/rlcmac/rlcmac_prim.h>
 #include <osmocom/gprs/rlcmac/rlcmac_private.h>
+#include <osmocom/gprs/rlcmac/tbf_dl.h>
 #include <osmocom/gprs/rlcmac/tbf_ul_fsm.h>
 #include <osmocom/gprs/rlcmac/tbf_ul.h>
 #include <osmocom/gprs/rlcmac/gre.h>
@@ -58,6 +59,7 @@
        if (!gre)
                return;

+       gprs_rlcmac_dl_tbf_free(gre->dl_tbf);
        gprs_rlcmac_ul_tbf_free(gre->ul_tbf);
        gprs_rlcmac_llc_queue_free(gre->llc_queue);
        llist_del(&gre->entry);
diff --git a/src/rlcmac/misc.c b/src/rlcmac/misc.c
index 4edc2b9..7317bd1 100644
--- a/src/rlcmac/misc.c
+++ b/src/rlcmac/misc.c
@@ -18,6 +18,7 @@
 #include <osmocom/core/utils.h>
 #include <osmocom/core/logging.h>
 #include <osmocom/gprs/rlcmac/rlcmac.h>
+#include <osmocom/gprs/rlcmac/tbf_dl_fsm.h>
 #include <osmocom/gprs/rlcmac/tbf_ul_fsm.h>
 #include <osmocom/gprs/rlcmac/tbf_ul_ass_fsm.h>

@@ -30,8 +31,15 @@
        OSMO_ASSERT(logc < _OSMO_GPRS_RLCMAC_LOGC_MAX);
        g_rlcmac_log_cat[logc] = logc_num;

-       if (logc == OSMO_GPRS_RLCMAC_LOGC_TBFUL) {
+       switch (logc) {
+       case OSMO_GPRS_RLCMAC_LOGC_TBFUL:
                gprs_rlcmac_tbf_ul_fsm_set_log_cat(logc_num);
                gprs_rlcmac_tbf_ul_ass_fsm_set_log_cat(logc_num);
+               break;
+       case OSMO_GPRS_RLCMAC_LOGC_TBFDL:
+               gprs_rlcmac_tbf_dl_fsm_set_log_cat(logc_num);
+               break;
+       default:
+               break;
        }
 }
diff --git a/src/rlcmac/rlcmac.c b/src/rlcmac/rlcmac.c
index 3310950..19d9944 100644
--- a/src/rlcmac/rlcmac.c
+++ b/src/rlcmac/rlcmac.c
@@ -29,8 +29,11 @@
 #include <osmocom/gprs/rlcmac/tbf_ul_fsm.h>
 #include <osmocom/gprs/rlcmac/tbf_ul_ass_fsm.h>
 #include <osmocom/gprs/rlcmac/gre.h>
+#include <osmocom/gprs/rlcmac/tbf_dl.h>
 #include <osmocom/gprs/rlcmac/tbf_ul.h>
 #include <osmocom/gprs/rlcmac/csn1_defs.h>
+#include <osmocom/gprs/rlcmac/rlc.h>
+#include <osmocom/gprs/rlcmac/types_private.h>

 #define GPRS_CODEL_SLOW_INTERVAL_MS 4000

@@ -73,6 +76,11 @@
        osmo_tdefs_reset(g_ctx->T_defs);

        if (first_init) {
+               rc = gprs_rlcmac_tbf_dl_fsm_init();
+               if (rc != 0) {
+                       TALLOC_FREE(g_ctx);
+                       return rc;
+               }
                rc = gprs_rlcmac_tbf_ul_fsm_init();
                if (rc != 0) {
                        TALLOC_FREE(g_ctx);
@@ -114,6 +122,20 @@
        return NULL;
 }

+struct gprs_rlcmac_dl_tbf *gprs_rlcmac_find_dl_tbf_by_tfi(uint8_t dl_tfi)
+{
+       struct gprs_rlcmac_entity *gre;
+
+       llist_for_each_entry(gre, &g_ctx->gre_list, entry) {
+               if (!gre->dl_tbf)
+                       continue;
+               if (gre->dl_tbf->cur_alloc.dl_tfi != dl_tfi)
+                       continue;
+               return gre->dl_tbf;
+       }
+       return NULL;
+}
+
 static int gprs_rlcmac_handle_ccch_imm_ass_ul_tbf(uint8_t ts_nr, const struct 
gsm48_imm_ass *ia, const IA_RestOctets_t *iaro)
 {
        int rc = -ENOENT;
@@ -139,6 +161,40 @@
        return rc;
 }

+static int gprs_rlcmac_handle_ccch_imm_ass_dl_tbf(uint8_t ts_nr, const struct 
gsm48_imm_ass *ia, const IA_RestOctets_t *iaro)
+{
+       int rc;
+       struct gprs_rlcmac_entity *gre;
+       struct gprs_rlcmac_dl_tbf *dl_tbf;
+       const Packet_Downlink_ImmAssignment_t *pkdlass;
+
+       if (iaro->UnionType == 1) {
+               /* TODO */
+               return -ENOENT;
+       }
+
+       pkdlass = 
&iaro->u.hh.u.UplinkDownlinkAssignment.ul_dl.Packet_Downlink_ImmAssignment;
+
+       gre = gprs_rlcmac_find_entity_by_tlli(pkdlass->TLLI);
+       if (!gre) {
+               LOGRLCMAC(LOGL_NOTICE, "Got IMM_ASS (DL_TBF) for unknown 
TLLI=0x%08x\n", pkdlass->TLLI);
+               return -ENOENT;
+       }
+
+       LOGGRE(gre, LOGL_INFO, "Got PCH IMM_ASS (DL_TBF): DL_TFI=%u TS=%u\n",
+              pkdlass->TFI_ASSIGNMENT, ts_nr);
+       dl_tbf = gprs_rlcmac_dl_tbf_alloc(gre);
+       dl_tbf->cur_alloc.dl_tfi = pkdlass->TFI_ASSIGNMENT;
+       dl_tbf->cur_alloc.ts[ts_nr].allocated = true;
+
+       /* replace old DL TBF with new one: */
+       gprs_rlcmac_dl_tbf_free(gre->dl_tbf);
+       gre->dl_tbf = dl_tbf;
+
+       rc = osmo_fsm_inst_dispatch(dl_tbf->state_fsm.fi, 
GPRS_RLCMAC_TBF_UL_EV_DL_ASS_COMPL, NULL);
+       return rc;
+}
+
 int gprs_rlcmac_handle_ccch_imm_ass(const struct gsm48_imm_ass *ia)
 {
        int rc;
@@ -171,7 +227,7 @@
                        rc = gprs_rlcmac_handle_ccch_imm_ass_ul_tbf(ch_ts, ia, 
&iaro);
                        break;
                case 1: /* iaro.u.ll.lh0x.MultiBlock_PktDlAss.* 
(IA_MultiBlock_PktDlAss_t) */
-                       /* TODO: Alloc DL TBF */
+                       rc = gprs_rlcmac_handle_ccch_imm_ass_dl_tbf(ch_ts, ia, 
&iaro);
                        break;
                }
                /* TODO: iaro.u.lh.AdditionsR13.* (IA_AdditionsR13_t) */
@@ -204,7 +260,7 @@
                                }
                                break;
                        case 1: /* 
iaro.u.hh.u.UplinkDownlinkAssignment.ul_dl.Packet_Downlink_ImmAssignment* 
(Packet_Downlink_ImmAssignment_t) */
-                               /* TODO: Alloc DL TBF */
+                               rc = 
gprs_rlcmac_handle_ccch_imm_ass_dl_tbf(ch_ts, ia, &iaro);
                                break;
                        }
                        break;
@@ -216,3 +272,71 @@

        return rc;
 }
+
+static int gprs_rlcmac_handle_gprs_dl_ctrl_block(const struct 
osmo_gprs_rlcmac_prim *rlcmac_prim)
+{
+       struct bitvec *bv;
+       RlcMacDownlink_t *dl_ctrl_block;
+       size_t max_len = gprs_rlcmac_mcs_max_bytes_dl(GPRS_RLCMAC_CS_1);
+       int rc;
+
+       bv = bitvec_alloc(max_len, g_ctx);
+       OSMO_ASSERT(bv);
+       bitvec_unpack(bv, rlcmac_prim->l1ctl.pdch_data_ind.data);
+
+       dl_ctrl_block = (RlcMacDownlink_t *)talloc_zero(g_ctx, 
RlcMacDownlink_t);
+       OSMO_ASSERT(dl_ctrl_block);
+       rc = osmo_gprs_rlcmac_decode_downlink(bv, dl_ctrl_block);
+       if (rc < 0) {
+               LOGRLCMAC(LOGL_NOTICE, "Failed decoding dl ctrl block: %s\n",
+                         osmo_hexdump(rlcmac_prim->l1ctl.pdch_data_ind.data,
+                                      
rlcmac_prim->l1ctl.pdch_data_ind.data_len));
+               goto free_ret;
+       }
+
+       LOGRLCMAC(LOGL_NOTICE, "TODO: handle decoded dl ctrl block!\n");
+
+free_ret:
+       talloc_free(dl_ctrl_block);
+       bitvec_free(bv);
+       return rc;
+}
+
+static int gprs_rlcmac_handle_gprs_dl_data_block(const struct 
osmo_gprs_rlcmac_prim *rlcmac_prim)
+{
+       const struct gprs_rlcmac_rlc_dl_data_header *data_hdr = (const struct 
gprs_rlcmac_rlc_dl_data_header *)rlcmac_prim->l1ctl.pdch_data_ind.data;
+       struct gprs_rlcmac_dl_tbf *dl_tbf;
+
+       dl_tbf = gprs_rlcmac_find_dl_tbf_by_tfi(data_hdr->tfi);
+       if (!dl_tbf) {
+               LOGPTBFDL(dl_tbf, LOGL_INFO, "Rx DL data for unknown 
dl_tfi=%u\n", data_hdr->tfi);
+               return -ENOENT;
+       }
+       LOGPTBFDL(dl_tbf, LOGL_DEBUG, "Rx new DL data\n");
+       return 0;
+}
+
+int gprs_rlcmac_handle_gprs_dl_block(const struct osmo_gprs_rlcmac_prim 
*rlcmac_prim,
+                                         enum gprs_rlcmac_coding_scheme cs)
+{
+       const struct gprs_rlcmac_rlc_dl_data_header *data_hdr = (const struct 
gprs_rlcmac_rlc_dl_data_header *)rlcmac_prim->l1ctl.pdch_data_ind.data;
+       /* Check block content (data vs ctrl) based on Payload Type: TS 44.060 
10.4.7 */
+       switch ((enum gprs_rlcmac_payload_type)data_hdr->pt) {
+       case GPRS_RLCMAC_PT_DATA_BLOCK:
+               /* "Contains an RLC data block" */
+               return gprs_rlcmac_handle_gprs_dl_data_block(rlcmac_prim);
+       case GPRS_RLCMAC_PT_CONTROL_BLOCK:
+               /* "Contains an RLC/MAC control block that does not include the 
optional octets of the RLC/MAC
+                * control header" */
+               return gprs_rlcmac_handle_gprs_dl_ctrl_block(rlcmac_prim);
+       case GPRS_RLCMAC_PT_CONTROL_BLOCK_OPT:
+               /* Contains an RLC/MAC control block that includes the optional 
first octet of the RLC/MAC
+                * control header" */
+               return gprs_rlcmac_handle_gprs_dl_ctrl_block(rlcmac_prim);
+       case GPRS_RLCMAC_PT_RESERVED: /* Reserved. In this version of the 
protocol, the mobile station shall ignore all fields of the
+                * RLC/MAC block except for the USF field */
+               return 0;
+       default:
+               OSMO_ASSERT(0);
+       }
+}
diff --git a/src/rlcmac/rlcmac_prim.c b/src/rlcmac/rlcmac_prim.c
index 954f954..1707578 100644
--- a/src/rlcmac/rlcmac_prim.c
+++ b/src/rlcmac/rlcmac_prim.c
@@ -37,6 +37,7 @@
 #include <osmocom/gprs/rlcmac/rlcmac_prim.h>
 #include <osmocom/gprs/rlcmac/rlcmac_private.h>
 #include <osmocom/gprs/rlcmac/gre.h>
+#include <osmocom/gprs/rlcmac/tbf_dl.h>
 #include <osmocom/gprs/rlcmac/tbf_ul.h>
 #include <osmocom/gprs/rlcmac/tbf_ul_ass_fsm.h>
 
@@ -466,9 +467,27 @@

 static int rlcmac_prim_handle_l1ctl_pdch_data_ind(struct osmo_gprs_rlcmac_prim 
*rlcmac_prim)
 {
-       int rc = gprs_rlcmac_prim_handle_unsupported(rlcmac_prim);
-       rc = 1; /* msg owned (freed) */
-       return rc;
+       enum gprs_rlcmac_coding_scheme cs = 
gprs_rlcmac_mcs_get_by_size_dl(rlcmac_prim->l1ctl.pdch_data_ind.data_len);
+
+       if (cs == GPRS_RLCMAC_CS_UNKNOWN) {
+               LOGRLCMAC(LOGL_ERROR, "Dropping DL data block with invalid 
length %u: %s\n",
+                         rlcmac_prim->l1ctl.pdch_data_ind.data_len,
+                         osmo_hexdump(rlcmac_prim->l1ctl.pdch_data_ind.data,
+                                      
rlcmac_prim->l1ctl.pdch_data_ind.data_len));
+               return -EINVAL;
+       }
+
+       if (gprs_rlcmac_mcs_is_gprs(cs))
+               return gprs_rlcmac_handle_gprs_dl_block(rlcmac_prim, cs);
+
+       if (gprs_rlcmac_mcs_is_edge(cs)) {
+               LOGRLCMAC(LOGL_NOTICE, "RX EGPRS DL data block NOT 
SUPPORTED\n");
+               return -ENOTSUP;
+       }
+
+       /* Should never be reached. */
+       OSMO_ASSERT(0);
+       return -EINVAL;
 }

 static int rlcmac_prim_handle_l1ctl_ccch_data_ind(struct osmo_gprs_rlcmac_prim 
*rlcmac_prim)
diff --git a/src/rlcmac/tbf_dl.c b/src/rlcmac/tbf_dl.c
new file mode 100644
index 0000000..ae6e7ac
--- /dev/null
+++ b/src/rlcmac/tbf_dl.c
@@ -0,0 +1,86 @@
+/* Downlink TBF as per 3GPP TS 44.064 */
+/*
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <[email protected]>
+ *
+ * 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 Affero 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 <osmocom/core/logging.h>
+
+#include <osmocom/gprs/rlcmac/tbf_dl.h>
+#include <osmocom/gprs/rlcmac/gre.h>
+
+struct gprs_rlcmac_dl_tbf *gprs_rlcmac_dl_tbf_alloc(struct gprs_rlcmac_entity 
*gre)
+{
+       struct gprs_rlcmac_dl_tbf *dl_tbf;
+       int rc;
+
+       dl_tbf = talloc_zero(gre, struct gprs_rlcmac_dl_tbf);
+       if (!dl_tbf)
+               return NULL;
+
+       gprs_rlcmac_tbf_constructor(dl_tbf_as_tbf(dl_tbf), 
GPRS_RLCMAC_TBF_DIR_DL, gre);
+
+       rc = gprs_rlcmac_tbf_dl_fsm_constructor(dl_tbf);
+       if (rc < 0)
+               goto err_tbf_destruct;
+
+       dl_tbf->tbf.nr = g_ctx->next_dl_tbf_nr++;
+
+       return dl_tbf;
+err_tbf_destruct:
+       gprs_rlcmac_tbf_destructor(dl_tbf_as_tbf(dl_tbf));
+       talloc_free(dl_tbf);
+       return NULL;
+}
+
+void gprs_rlcmac_dl_tbf_free(struct gprs_rlcmac_dl_tbf *dl_tbf)
+{
+       if (!dl_tbf)
+               return;
+
+       //gprs_rlcmac_tbf_dl_fsm_destructor(dl_tbf);
+
+       gprs_rlcmac_tbf_destructor(dl_tbf_as_tbf(dl_tbf));
+       talloc_free(dl_tbf);
+}
+
+
+static uint8_t dl_tbf_dl_slotmask(struct gprs_rlcmac_dl_tbf *dl_tbf)
+{
+       uint8_t i;
+       uint8_t dl_slotmask = 0;
+
+       for (i = 0; i < 8; i++) {
+               if (dl_tbf->cur_alloc.ts[i].allocated)
+                       dl_slotmask |= (1 << i);
+       }
+
+       return dl_slotmask;
+}
+
+int gprs_rlcmac_dl_tbf_configure_l1ctl(struct gprs_rlcmac_dl_tbf *dl_tbf)
+{
+       struct osmo_gprs_rlcmac_prim *rlcmac_prim;
+       uint8_t dl_slotmask = dl_tbf_dl_slotmask(dl_tbf);
+
+        LOGPTBFDL(dl_tbf, LOGL_INFO, "Send L1CTL-CF_DL_TBF.req 
dl_slotmask=0x%02x dl_tfi=%u\n",
+                  dl_slotmask, dl_tbf->cur_alloc.dl_tfi);
+       rlcmac_prim = 
gprs_rlcmac_prim_alloc_l1ctl_cfg_dl_tbf_req(dl_tbf->tbf.nr,
+                                                                 dl_slotmask,
+                                                                 
dl_tbf->cur_alloc.dl_tfi);
+       return gprs_rlcmac_prim_call_down_cb(rlcmac_prim);
+}
diff --git a/src/rlcmac/tbf_dl_fsm.c b/src/rlcmac/tbf_dl_fsm.c
new file mode 100644
index 0000000..e4a4cf2
--- /dev/null
+++ b/src/rlcmac/tbf_dl_fsm.c
@@ -0,0 +1,164 @@
+/* TBF as per 3GPP TS 44.064 */
+/*
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <[email protected]>
+ *
+ * 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 Affero 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 <talloc.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/gprs/rlcmac/tbf_dl_fsm.h>
+#include <osmocom/gprs/rlcmac/tbf_dl.h>
+#include <osmocom/gprs/rlcmac/gre.h>
+
+#define X(s) (1 << (s))
+
+static const struct value_string tbf_dl_fsm_event_names[] = {
+       { GPRS_RLCMAC_TBF_DL_EV_LAST_DL_DATA_RECVD,     "LAST_DL_DATA_RECVD" },
+       { GPRS_RLCMAC_TBF_UL_EV_DL_ASS_COMPL,           "DL_ASS_COMPL" },
+       { 0, NULL }
+};
+
+static const struct osmo_tdef_state_timeout tbf_dl_fsm_timeouts[32] = {
+       [GPRS_RLCMAC_TBF_DL_ST_NEW] = { },
+       [GPRS_RLCMAC_TBF_DL_ST_FLOW] = { },
+       [GPRS_RLCMAC_TBF_DL_ST_FINISHED] = { },
+};
+
+/* Transition to a state, using the T timer defined in tbf_fsm_timeouts.
+ * The actual timeout value is in turn obtained from conn->T_defs.
+ * Assumes local variable fi exists. */
+ #define tbf_dl_fsm_state_chg(fi, NEXT_STATE) \
+       osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, \
+                                    tbf_dl_fsm_timeouts, \
+                                    g_ctx->T_defs, \
+                                    -1)
+
+static void st_new(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+       struct gprs_rlcmac_tbf_dl_fsm_ctx *ctx = (struct 
gprs_rlcmac_tbf_dl_fsm_ctx *)fi->priv;
+       switch (event) {
+       case GPRS_RLCMAC_TBF_UL_EV_DL_ASS_COMPL:
+               /* Configure DL TBF on the lower MAC side: */
+               gprs_rlcmac_dl_tbf_configure_l1ctl(ctx->dl_tbf);
+               tbf_dl_fsm_state_chg(fi, GPRS_RLCMAC_TBF_DL_ST_FLOW);
+               break;
+       default:
+               OSMO_ASSERT(0);
+       }
+}
+
+static void st_flow(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+       //struct gprs_rlcmac_tbf_dl_fsm_ctx *ctx = (struct 
gprs_rlcmac_tbf_dl_fsm_ctx *)fi->priv;
+       switch (event) {
+       case GPRS_RLCMAC_TBF_DL_EV_LAST_DL_DATA_RECVD:
+               tbf_dl_fsm_state_chg(fi, GPRS_RLCMAC_TBF_DL_ST_FINISHED);
+               break;
+       default:
+               OSMO_ASSERT(0);
+       }
+}
+
+static void st_finished(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+       //struct gprs_rlcmac_tbf_dl_fsm_ctx *ctx = (struct 
gprs_rlcmac_tbf_dl_fsm_ctx *)fi->priv;
+       switch (event) {
+       default:
+               OSMO_ASSERT(0);
+       }
+}
+
+static struct osmo_fsm_state tbf_dl_fsm_states[] = {
+       [GPRS_RLCMAC_TBF_DL_ST_NEW] = {
+               .in_event_mask =
+                       X(GPRS_RLCMAC_TBF_UL_EV_DL_ASS_COMPL),
+               .out_state_mask =
+                       X(GPRS_RLCMAC_TBF_DL_ST_FLOW),
+               .name = "NEW",
+               .action = st_new,
+       },
+       [GPRS_RLCMAC_TBF_DL_ST_FLOW] = {
+               .in_event_mask =
+                       X(GPRS_RLCMAC_TBF_DL_EV_LAST_DL_DATA_RECVD),
+               .out_state_mask =
+                       X(GPRS_RLCMAC_TBF_DL_ST_FINISHED),
+               .name = "FLOW",
+               .action = st_flow,
+       },
+       [GPRS_RLCMAC_TBF_DL_ST_FINISHED] = {
+               .in_event_mask = 0,
+               .out_state_mask = 0,
+               .name = "FINISHED",
+               .action = st_finished,
+       },
+};
+
+static int tbf_dl_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+       //struct gprs_rlcmac_tbf_dl_fsm_ctx *ctx = (struct 
gprs_rlcmac_tbf_dl_fsm_ctx *)fi->priv;
+       switch (fi->T) {
+       default:
+               OSMO_ASSERT(0);
+       }
+       return 0;
+}
+
+static struct osmo_fsm tbf_dl_fsm = {
+       .name = "DL_TBF",
+       .states = tbf_dl_fsm_states,
+       .num_states = ARRAY_SIZE(tbf_dl_fsm_states),
+       .timer_cb = tbf_dl_fsm_timer_cb,
+       .log_subsys = DLGLOBAL, /* updated dynamically through 
gprs_rlcmac_tbf_dl_fsm_set_log_cat() */
+       .event_names = tbf_dl_fsm_event_names,
+};
+
+int gprs_rlcmac_tbf_dl_fsm_init(void)
+{
+       return osmo_fsm_register(&tbf_dl_fsm);
+}
+
+void gprs_rlcmac_tbf_dl_fsm_set_log_cat(int logcat)
+{
+       tbf_dl_fsm.log_subsys = logcat;
+}
+
+int gprs_rlcmac_tbf_dl_fsm_constructor(struct gprs_rlcmac_dl_tbf *dl_tbf)
+{
+       struct gprs_rlcmac_tbf_dl_fsm_ctx *ctx = &dl_tbf->state_fsm;
+       ctx->dl_tbf = dl_tbf;
+       ctx->fi = osmo_fsm_inst_alloc(&tbf_dl_fsm, dl_tbf, ctx, LOGL_INFO, 
NULL);
+       if (!ctx->fi)
+               return -ENODATA;
+
+       return 0;
+}
+
+void gprs_rlcmac_tbf_dl_fsm_destructor(struct gprs_rlcmac_dl_tbf *dl_tbf)
+{
+       struct gprs_rlcmac_tbf_dl_fsm_ctx *ctx = &dl_tbf->state_fsm;
+       osmo_fsm_inst_free(ctx->fi);
+       ctx->fi = NULL;
+}
+
+enum gprs_rlcmac_tbf_dl_fsm_states gprs_rlcmac_tbf_dl_state(const struct 
gprs_rlcmac_dl_tbf *dl_tbf)
+{
+       const struct gprs_rlcmac_tbf_dl_fsm_ctx *ctx = &dl_tbf->state_fsm;
+       return ctx->fi->state;
+}
diff --git a/tests/rlcmac/Makefile.am b/tests/rlcmac/Makefile.am
index 949aed6..8d19656 100644
--- a/tests/rlcmac/Makefile.am
+++ b/tests/rlcmac/Makefile.am
@@ -1,5 +1,6 @@
 AM_CFLAGS = \
        -Wall \
+       $(LIBOSMOGSM_CFLAGS) \
        $(LIBOSMOCORE_CFLAGS) \
        -I$(top_srcdir)/include/ \
        $(NULL)
@@ -24,10 +25,12 @@
        $(NULL)

 # Common LDADD entries
+# libosmo-gprs-rlcmac.a is used below to access non-exported private symbols 
used in the test:
 LDADD = \
-       $(LIBOSMOCORE_LIBS) \
+       $(top_builddir)/src/rlcmac/.libs/libosmo-gprs-rlcmac.a \
        $(top_builddir)/src/csn1/libosmo-csn1.la \
-       $(top_builddir)/src/rlcmac/libosmo-gprs-rlcmac.la \
+       $(LIBOSMOGSM_LIBS) \
+       $(LIBOSMOCORE_LIBS) \
        $(NULL)

 csn1_ts_44_018_test_SOURCES = csn1_ts_44_018_test.c
diff --git a/tests/rlcmac/rlcmac_prim_test.c b/tests/rlcmac/rlcmac_prim_test.c
index fa864f9..00f1740 100644
--- a/tests/rlcmac/rlcmac_prim_test.c
+++ b/tests/rlcmac/rlcmac_prim_test.c
@@ -24,6 +24,7 @@

 #include <osmocom/gprs/rlcmac/rlcmac.h>
 #include <osmocom/gprs/rlcmac/gre.h>
+#include <osmocom/gprs/rlcmac/rlc.h>

 static void *tall_ctx = NULL;

@@ -118,6 +119,64 @@
        0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b
 };

+/*
+GSM CCCH - Immediate Assignment
+       L2 Pseudo Length
+               0010 11.. = L2 Pseudo Length value: 11
+       .... 0110 = Protocol discriminator: Radio Resources Management messages 
(0x6)
+               .... 0110 = Protocol discriminator: Radio Resources Management 
messages (0x6)
+               0000 .... = Skip Indicator: No indication of selected PLMN (0)
+       Message Type: Immediate Assignment
+       Page Mode
+               .... 0000 = Page Mode: Normal paging (0)
+       Dedicated mode or TBF
+               0011 .... = Dedicated mode or TBF: This message assigns a 
downlink TBF to the mobile station identified in the IA Rest Octets IE (3)
+       Packet Channel Description
+               0000 1... = Channel Type: 1
+               .... .111 = Timeslot: 7
+               111. .... = Training Sequence: 7
+               .... .0.. = Spare: 0x00
+               .... ..11  0110 0111 = Single channel ARFCN: 871
+       Request Reference
+               Random Access Information (RA): 125
+               1000 0... = T1': 16
+               .... .000 000. .... = T3: 0
+               ...0 0000 = T2: 0
+               [RFN: 21216]
+       Timing Advance
+               Timing advance value: 28
+       Mobile Allocation
+               Length: 0
+       IA Rest Octets
+               H... .... = First Discriminator Bit: High
+               .H.. .... = Second Discriminator Bit: High
+               ..0. .... = Discriminator Bit: Packet Assignment
+               ...1 .... = Discriminator Bit: Packet Downlink Assignment
+               Packet Downlink Assignment
+                       .... 0000  0000 0000  0000 0000  0000 0000  0001 .... = 
TLLI: 0x00000001
+                       .... 1... = TFI Assignment (etc): Present
+                       .... .000  00.. .... = TFI_Assignment: 0
+                       ..0. .... = RLC_Mode: RLC acknowledged mode
+                       ...0 .... = Alpha: Not Present
+                       .... 0000  0... .... = Gamma: 0 dB (0)
+                       .0.. .... = Polling: no action is required from MS
+                       ..0. .... = TA_Valid: the timing advance value is not 
valid
+                       ...0 .... = Timing Advance Index: Not Present
+                       .... 0... = TBF Starting Time: Not Present
+                       .... .0.. = P0: Not Present
+                       .... ..L. = Additions in R99: Not Present
+                       .... ...L = Additions in Rel-6: Not Present
+                       L... .... = Additions in Rel-7: Not Present
+               .L.. .... = Additions in Rel-10: Not Present
+               ..L. .... = Additions in Rel-13: Not Present
+               Padding Bits: default padding
+*/
+static uint8_t ccch_imm_ass_pkt_dl_tbf[] = {
+       0x2d, 0x06, 0x3f, 0x30, 0x0f, 0xe3, 0x67, 0x7d, 0x80, 0x00,
+       0x1c, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x18, 0x00, 0x03,
+       0x2b, 0x2b, 0x2b, 0x2b
+};
+
 static int test_rlcmac_prim_up_cb(struct osmo_gprs_rlcmac_prim *rlcmac_prim, 
void *user_data)
 {
        const char *pdu_name = osmo_gprs_rlcmac_prim_name(rlcmac_prim);
@@ -162,6 +221,12 @@
                               rlcmac_prim->l1ctl.cfg_ul_tbf_req.ul_tbf_nr,
                               rlcmac_prim->l1ctl.cfg_ul_tbf_req.ul_slotmask);
                        break;
+               case OSMO_PRIM(OSMO_GPRS_RLCMAC_L1CTL_CFG_DL_TBF, 
PRIM_OP_REQUEST):
+                       printf("%s(): Rx %s dl_tbf_nr=%u dl_slotmask=0x%02x 
dl_tfi=%u\n", __func__, pdu_name,
+                              rlcmac_prim->l1ctl.cfg_dl_tbf_req.dl_tbf_nr,
+                              rlcmac_prim->l1ctl.cfg_dl_tbf_req.dl_slotmask,
+                              rlcmac_prim->l1ctl.cfg_dl_tbf_req.dl_tfi);
+                       break;
                default:
                        printf("%s(): Rx %s\n", __func__, pdu_name);
                }
@@ -173,6 +238,37 @@
        return 0;
 }

+static const uint8_t llc_dummy_command[] = {
+       0x43, 0xc0, 0x01, 0x2b, 0x2b, 0x2b
+};
+
+static struct msgb *create_dl_data_block(uint8_t dl_tfi, uint8_t usf, enum 
gprs_rlcmac_coding_scheme cs, uint8_t bsn, bool fbi)
+{
+       struct msgb *msg = msgb_alloc(128, __func__);
+       struct gprs_rlcmac_rlc_dl_data_header *hdr;
+       struct gprs_rlcmac_rlc_li_field *lime;
+
+       hdr = (struct gprs_rlcmac_rlc_dl_data_header *)msgb_put(msg, 
gprs_rlcmac_mcs_size_dl(cs));
+       hdr->pt = 0; /* RLC/MAC block contains an RLC data block */
+       hdr->rrbp = 0;
+       hdr->s_p = 0;
+       hdr->usf = usf;
+       hdr->pr = 0;
+       hdr->tfi = dl_tfi;
+       hdr->fbi = fbi ? 1 : 0;
+       hdr->tfi = dl_tfi;
+       hdr->bsn = bsn;
+       hdr->e = 0;
+       lime = &hdr->lime[0];
+       lime->li = sizeof(llc_dummy_command);
+       lime->m = 0;
+       lime->e = 1;
+       msg->l3h = &lime->ll_pdu[0];
+       memset(msg->l3h, 0x2b, msgb_l3len(msg));
+       memcpy(msg->l3h, llc_dummy_command, sizeof(llc_dummy_command));
+       return msg;
+}
+
 void prepare_test(void)
 {
        int rc;
@@ -183,6 +279,12 @@
        osmo_gprs_rlcmac_prim_set_down_cb(test_rlcmac_prim_down_cb, NULL);
 }

+void cleanup_test(void)
+{
+       /* Reinit the RLCMAC layer so that data generated during the test is 
freed within the test context: */
+       osmo_gprs_rlcmac_init(OSMO_GPRS_RLCMAC_LOCATION_MS);
+}
+
 static void test_ul_tbf_attach(void)
 {
        struct osmo_gprs_rlcmac_prim *rlcmac_prim;
@@ -210,7 +312,46 @@
        rc = osmo_gprs_rlcmac_prim_lower_up(rlcmac_prim);

        OSMO_ASSERT(rc == 0);
+
        printf("=== %s end ===\n", __func__);
+       cleanup_test();
+}
+
+/* PCU allocates a DL TBF through PCH ImmAss for MS (when in packet-idle) */
+static void test_dl_tbf_ccch_assign(void)
+{
+       struct osmo_gprs_rlcmac_prim *rlcmac_prim;
+       int rc;
+       struct msgb *dl_data_msg;
+
+       printf("=== %s start ===\n", __func__);
+       prepare_test();
+       uint32_t tlli = 0x0000001;
+       uint8_t ts_nr = 7;
+       uint8_t usf = 0;
+       uint32_t rts_fn = 4;
+       uint8_t dl_tfi = 0;
+
+       /* Notify RLCMAC about our TLLI */
+       rlcmac_prim = osmo_gprs_rlcmac_prim_alloc_gmmrr_assign_req(tlli);
+       rc = osmo_gprs_rlcmac_prim_upper_down(rlcmac_prim);
+
+       OSMO_ASSERT(sizeof(ccch_imm_ass_pkt_dl_tbf) == GSM_MACBLOCK_LEN);
+       rlcmac_prim = osmo_gprs_rlcmac_prim_alloc_l1ctl_ccch_data_ind(0, 
ccch_imm_ass_pkt_dl_tbf);
+       rc = osmo_gprs_rlcmac_prim_lower_up(rlcmac_prim);
+       OSMO_ASSERT(rc == 0);
+
+       /* Transmit some DL LLC data MS<-PCU */
+       dl_data_msg = create_dl_data_block(dl_tfi, usf, GPRS_RLCMAC_CS_1, 0, 1);
+       rlcmac_prim = osmo_gprs_rlcmac_prim_alloc_l1ctl_pdch_data_ind(ts_nr, 
rts_fn, 0, 0, 0,
+                                                                     
msgb_data(dl_data_msg),
+                                                                     
msgb_length(dl_data_msg));
+       rc = osmo_gprs_rlcmac_prim_lower_up(rlcmac_prim);
+       OSMO_ASSERT(rc == 0);
+       msgb_free(dl_data_msg);
+
+       printf("=== %s end ===\n", __func__);
+       cleanup_test();
 }

 static const struct log_info_cat test_log_categories[] = { };
@@ -234,6 +375,7 @@
        log_set_use_color(osmo_stderr_target, 0);

        test_ul_tbf_attach();
+       test_dl_tbf_ccch_assign();

        talloc_free(tall_ctx);
 }
diff --git a/tests/rlcmac/rlcmac_prim_test.err 
b/tests/rlcmac/rlcmac_prim_test.err
index e6d6b30..89a970c 100644
--- a/tests/rlcmac/rlcmac_prim_test.err
+++ b/tests/rlcmac/rlcmac_prim_test.err
@@ -25,3 +25,15 @@
 DLGLOBAL DEBUG TBF(UL:NR-0:TLLI-00002342) Copying 1 RLC blocks, 1 BSNs
 DLGLOBAL DEBUG TBF(UL:NR-0:TLLI-00002342) Copying data unit 0 (BSN 0)
 DLGLOBAL DEBUG TBF(UL:NR-0:TLLI-00002342) msg block (BSN 0, CS-2): 3c 00 01 01 
c0 00 08 01 01 d5 71 00 00 08 29 26 24 00 00 00 00 71 62 f2 24 6c 84 44 04 11 
e5 10 00 00
+DLGLOBAL INFO UL_TBF_ASS{IDLE}: Deallocated
+DLGLOBAL INFO UL_TBF{FLOW}: Deallocated
+DLGLOBAL INFO Rx from upper layers: GMMRR-ASSIGN.request
+DLGLOBAL INFO GMMRR-ASSIGN.req: creating new entity TLLI=0x00000001
+DLGLOBAL INFO Rx from lower layers: L1CTL-CCCH_DATA.indication
+DLGLOBAL INFO GRE(00000001) Got PCH IMM_ASS (DL_TBF): DL_TFI=0 TS=7
+DLGLOBAL INFO DL_TBF{NEW}: Allocated
+DLGLOBAL INFO DL_TBF{NEW}: Received Event DL_ASS_COMPL
+DLGLOBAL INFO TBF(DL:NR-0:TLLI-00000001) Send L1CTL-CF_DL_TBF.req 
dl_slotmask=0x80 dl_tfi=0
+DLGLOBAL INFO DL_TBF{NEW}: state_chg to FLOW
+DLGLOBAL INFO Rx from lower layers: L1CTL-PDCH_DATA.indication
+DLGLOBAL DEBUG TBF(DL:NR-0:TLLI-00000001) Rx new DL data
diff --git a/tests/rlcmac/rlcmac_prim_test.ok b/tests/rlcmac/rlcmac_prim_test.ok
index 6e0db40..808ce21 100644
--- a/tests/rlcmac/rlcmac_prim_test.ok
+++ b/tests/rlcmac/rlcmac_prim_test.ok
@@ -3,3 +3,6 @@
 test_rlcmac_prim_down_cb(): Rx L1CTL-CFG_UL_TBF.request ul_tbf_nr=0 
ul_slotmask=0x80
 test_rlcmac_prim_down_cb(): Rx L1CTL-PDCH_DATA.request fn=4 ts=7 data_len=34 
data=[3c 00 01 01 c0 00 08 01 01 d5 71 00 00 08 29 26 24 00 00 00 00 71 62 f2 
24 6c 84 44 04 11 e5 10 00 00 ]
 === test_ul_tbf_attach end ===
+=== test_dl_tbf_ccch_assign start ===
+test_rlcmac_prim_down_cb(): Rx L1CTL-CFG_DL_TBF.request dl_tbf_nr=0 
dl_slotmask=0x80 dl_tfi=0
+=== test_dl_tbf_ccch_assign end ===

--
To view, visit https://gerrit.osmocom.org/c/libosmo-gprs/+/31286
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings

Gerrit-Project: libosmo-gprs
Gerrit-Branch: master
Gerrit-Change-Id: I7f98e3456ef35d80becdad3481afeb771457b0ef
Gerrit-Change-Number: 31286
Gerrit-PatchSet: 10
Gerrit-Owner: pespin <[email protected]>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <[email protected]>
Gerrit-Reviewer: pespin <[email protected]>
Gerrit-CC: msuraev <[email protected]>
Gerrit-CC: neels <[email protected]>
Gerrit-MessageType: merged

Reply via email to