laforge has submitted this change. ( 
https://gerrit.osmocom.org/c/osmocom-bb/+/34492?usp=email )

 (

14 is the latest approved patch-set.
No files were changed between the latest approved patch-set and the submitted 
one.
 )Change subject: ASCI: Add group receive and transmit mode support to MM layer
......................................................................

ASCI: Add group receive and transmit mode support to MM layer

Related: OS#5364
Change-Id: I05957182a57423ad947ab200b52f65fde859e110
---
M src/host/layer23/include/osmocom/bb/mobile/gsm48_mm.h
M src/host/layer23/src/mobile/gsm48_mm.c
2 files changed, 710 insertions(+), 78 deletions(-)

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




diff --git a/src/host/layer23/include/osmocom/bb/mobile/gsm48_mm.h 
b/src/host/layer23/include/osmocom/bb/mobile/gsm48_mm.h
index 67abc2f..1f0db78 100644
--- a/src/host/layer23/include/osmocom/bb/mobile/gsm48_mm.h
+++ b/src/host/layer23/include/osmocom/bb/mobile/gsm48_mm.h
@@ -8,8 +8,8 @@
 #define GSM48_MMCC_CLASS               0x100
 #define GSM48_MMSS_CLASS               0x200
 #define GSM48_MMSMS_CLASS              0x300
-#define GSM48_MMBCC_CLASS              0x500
-#define GSM48_MMGCC_CLASS              0x600
+#define GSM48_MMGCC_CLASS              0x500
+#define GSM48_MMBCC_CLASS              0x600
 #define GSM48_MMXX_REL_IND             0x022
 #define GSM48_MMCC_EST_REQ             0x110
 #define GSM48_MMCC_EST_IND             0x112
@@ -66,6 +66,15 @@
 #define GSM48_MMGCC_REEST_REQ          0x560
 #define GSM48_MMGCC_REEST_CNF          0x561
 #define GSM48_MMGCC_ERR_IND            0x572
+#define GSM48_MMGCC_NOTIF_IND          0x582
+#define GSM48_MMGCC_GROUP_REQ          0x590
+#define GSM48_MMGCC_GROUP_CNF          0x591
+#define GSM48_MMGCC_UPLINK_REQ         0x5a0
+#define GSM48_MMGCC_UPLINK_CNF         0x5a1
+#define GSM48_MMGCC_UPLINK_REL_REQ     0x5a8
+#define GSM48_MMGCC_UPLINK_REL_IND     0x5aa
+#define GSM48_MMGCC_UPLINK_FREE_IND    0x5b2
+#define GSM48_MMGCC_UPLINK_BUSY_IND    0x5b6
 #define GSM48_MMBCC_EST_REQ            0x610
 #define GSM48_MMBCC_EST_CNF            0x611
 #define GSM48_MMBCC_REL_REQ            0x620
@@ -77,18 +86,34 @@
 #define GSM48_MMBCC_REEST_REQ          0x660
 #define GSM48_MMBCC_REEST_CNF          0x661
 #define GSM48_MMBCC_ERR_IND            0x672
+#define GSM48_MMBCC_NOTIF_IND          0x682
+#define GSM48_MMBCC_GROUP_REQ          0x690
+#define GSM48_MMBCC_GROUP_CNF          0x691
+#define GSM48_MMBCC_UPLINK_REQ         0x6a0
+#define GSM48_MMBCC_UPLINK_CNF         0x6a1
+#define GSM48_MMBCC_UPLINK_REL_REQ     0x6a8
+#define GSM48_MMBCC_UPLINK_REL_IND     0x6aa
+#define GSM48_MMBCC_UPLINK_FREE_IND    0x6b2
+#define GSM48_MMBCC_UPLINK_BUSY_IND    0x6b6
+

 #define MMXX_ALLOC_SIZE                        256
 #define MMXX_ALLOC_HEADROOM            64

+#define MMXX_NOTIFY_SETUP              0
+#define MMXX_NOTIFY_RELEASE            1
+
 /* MMxx-SAP header */
 struct gsm48_mmxx_hdr {
-       uint16_t        msg_type; /* MMxx_* primitive */
-       uint32_t        ref; /* reference to transaction */
-       uint32_t        transaction_id; /* transaction identifier */
-       uint8_t         sapi; /* sapi */
-       uint8_t         emergency; /* emergency type of call */
-       uint8_t         cause; /* cause used for release */
+       uint16_t                msg_type; /* MMxx_* primitive */
+       uint32_t                ref; /* reference to transaction */
+       uint32_t                transaction_id; /* transaction identifier */
+       uint8_t                 sapi; /* sapi */
+       uint8_t                 emergency; /* emergency type of call */
+       uint8_t                 cause; /* cause used for release */
+       uint8_t                 notify; /* notify ongoing ASCI call */
+       bool                    ch_desc_present; /* notifies channel */
+       struct gsm48_chan_desc  ch_desc; /* group channel */
 } __attribute__((packed));

 /* GSM 6.1.2 */
@@ -237,6 +262,14 @@

        /* sapi 3 */
        int                     sapi3_link;
+
+       /* VGCS additional states */
+       struct {
+               bool                    enabled;                /* We are in 
group/broadcast mode. */
+               bool                    group_call;             /* This is a 
group call, not a broadcast call. */
+               uint32_t                callref;                /* Callref of 
this call. */
+               bool                    normal_service;         /* Service 
state before group transmit mode. */
+       } vgcs;
 };

 /* MM connection entry */
diff --git a/src/host/layer23/src/mobile/gsm48_mm.c 
b/src/host/layer23/src/mobile/gsm48_mm.c
index 8c57785..82e5fad 100644
--- a/src/host/layer23/src/mobile/gsm48_mm.c
+++ b/src/host/layer23/src/mobile/gsm48_mm.c
@@ -60,6 +60,7 @@
 static int gsm48_mm_loc_upd_normal(struct osmocom_ms *ms, struct msgb *msg);
 static int gsm48_mm_loc_upd_periodic(struct osmocom_ms *ms, struct msgb *msg);
 static int gsm48_mm_loc_upd(struct osmocom_ms *ms, struct msgb *msg);
+static int gsm48_mm_group_rel_req(struct osmocom_ms *ms, struct msgb *msg);

 /*
  * notes
@@ -144,6 +145,9 @@
  * During LIMITED SERVICE state: (4.2.2.3)
  *  - reject MM connection except for emergency calls
  *  - perform location update, if new LAI is entered
+ *  - indicate GCC/BCC calls with channel description only
+ *  - reject joining to GCC/BCC calls without channel description
+ *  - accept joining to GCC/BCC calls with channel description
  *
  *
  * The LOCATION UPDATE NEEDED state is entered if:
@@ -176,6 +180,9 @@
  *  - accept MM connection for emergency calls
  *  - trigger location update on any other MM connection
  *  - respond to paging (with IMSI only, because in U2 TMSI is not valid)
+ *  - indicate GCC/BCC calls with channel description only
+ *  - reject joining to GCC/BCC calls without channel description
+ *  - accept joining to GCC/BCC calls with channel description
  *
  *
  * The NORMAL SERVICE state is entered if:
@@ -185,12 +192,52 @@
  *  - and SIM LAI == cell
  *
  * During NORMAL SERVICE state: (4.2.2.1)
- *  - on expirery of T3211 or T3213: Perform location updated
- *  - on expirery of T3212: Perform location updated
+ *  - on expirery of T3211 or T3213: Perform location update
+ *  - on expirery of T3212: Perform location update
  *  - on change of LAI: Perform location update
  *  - perform IMSI detach
  *  - perform MM connections
  *  - respond to paging
+ *  - indicate GCC/BCC calls with and without channel description
+ *  - accept joining to GCC/BCC calls without channel description
+ *    -> The GCC/BCC layer waits for channel description before joining.
+ *  - accept joining to GCC/BCC calls with channel description
+ *
+ *
+ * The RECEIVING GROUP CALL (NORMAL SERVICE) is entered if:
+ *  - the upper layer requests to join to GCC/BCC call
+ *  - and service state is NORMAL SERVICE
+ *
+ * During RECEIVING GROUP CALL (NORMAL SERVICE) state: (4.2.2.7)
+ *  - reject all MM connections
+ *  - indicate notifications and paging to GCC or BCC layer
+ *    -> If supported by RR layer.
+ *  The following events are not be supported here:
+ *  - perform IMSI detach (This is delayed until the call has ended.)
+ *  - on expirery of T3211 or T3213: Perform location update
+ *  - on expirery of T3212: Perform location update
+ *  - accept MM connections
+ *  - on change of LAI: Perform location update
+ *  - accept joining to GCC/BCC calls without channel description
+ *  - accept joining to GCC/BCC calls with channel description
+ *
+ *
+ * The RECEIVING GROUP CALL (LIMITED SERVICE) is entered if:
+ *  - the upper layer requests to join to GCC/BCC call
+ *  - and service state is LIMITED SERVICE or ATTEMPTING TO UPDATE
+ *
+ * During RECEIVING GROUP CALL (LIMITED SERVICE) state: (4.2.2.8)
+ *  - reject all MM connections
+ *  - indicate notifications and paging to GCC or BCC layer
+ *    -> If supported by RR layer.
+ *  The following events are not be supported here:
+ *  - reject MM connection except for emergency calls
+ *  - on expirery of T3212: Perform location updated
+ *  - reject joining to GCC/BCC calls without channel description
+ *  - accept joining to GCC/BCC calls with channel description
+ *
+ *
+ * A group call is only accepted, if there is no other MM connection ongoing.
  *
  *
  * gsm48_mm_set_plmn_search() is used enter PLMN SEARCH or PLMN SEARCH NORMAL
@@ -223,6 +270,33 @@
  *
  * gsm48_mm_cell_selected() is used to select the Service state.
  *
+ *
+ * New primitives are invented for group/broadcast calls. They are not
+ * specified in any recommendation. They are:
+ *
+ * - MMxCC_NOTIF_IND: The MM layer indicates new/updated/ceased calls. This is
+ *   completely independent from the state of the MM layer.
+ * - MMxCC_GROUP_REQ: The GCC/BCC layer requests group channel in receive mode.
+ *   This mode has no MM connection. Speical flags (mm->vgcs*) are used to
+ *   define that mode and store the reference (callref + group|broadcast). This
+ *   reference is used for messages towards GCC/BCC layer. The state is IDLE
+ *   and the sub-state defines group receive mode at normal service or at
+ *   limited service state.
+ * - MMxCC_GROUP_CNF: The MM layer confirms group channel.
+ * - MMxCC_UPLINK_REQ: The GCC/BCC layer requests uplink (group transmit mode).
+ *   The MM layer changes to group transmit mode.
+ * - MMxCC_UPLINK_CNF: The MM layer confirms uplink. (Uplink was granted.)
+ * - MMxCC_UPLINK_REL_REQ: The GCC/BCC layer requests release of uplink.
+ * - MMxCC_UPLINK_REL_IND: The MM layer indicates/confirms release of uplink
+ *
+ * Existing primitives are used with group calls:
+ *
+ * MMxCC_REL_IND: Failed to establish group receive mode.
+ * MMxCC_ERR_IND: Abort received from RR layer in group receive or transmit 
mode.
+ * MMxCC_REL_REQ: Leave group call (receive mode or transmit mode).
+ *
+ * The group call is released at MM layer, if one of the primitives above are
+ * received or transmitted.
  */

 /*
@@ -578,6 +652,8 @@
        { GSM48_MM_EVENT_USER_PLMN_SEL, "MM_EVENT_USER_PLMN_SEL" },
        { GSM48_MM_EVENT_LOST_COVERAGE, "MM_EVENT_LOST_COVERAGE" },
        { GSM48_MM_EVENT_NOTIFICATION,  "MM_EVENT_NOTIFICATION" },
+       { GSM48_MM_EVENT_UPLINK_FREE,   "MM_EVENT_UPLINK_FREE" },
+       { GSM48_MM_EVENT_UPLINK_BUSY,   "MM_EVENT_UPLINK_BUSY" },
        { 0,                            NULL }
 };

@@ -662,6 +738,26 @@
        { GSM48_MMSMS_ERR_IND,          "MMSMS_ERR_IND" },
        { GSM48_MMSMS_PROMPT_IND,       "MMSMS_PROMPT_IND" },
        { GSM48_MMSMS_PROMPT_REJ,       "MMSMS_PROMPT_REJ" },
+       { GSM48_MMGCC_EST_REQ,          "MMGCC_EST_REQ" },
+       { GSM48_MMGCC_EST_CNF,          "MMGCC_EST_CNF" },
+       { GSM48_MMGCC_REL_REQ,          "MMGCC_REL_REQ" },
+       { GSM48_MMGCC_REL_IND,          "MMGCC_REL_IND" },
+       { GSM48_MMGCC_DATA_REQ,         "MMGCC_DATA_REQ" },
+       { GSM48_MMGCC_DATA_IND,         "MMGCC_DATA_IND" },
+       { GSM48_MMGCC_UNIT_DATA_REQ,    "MMGCC_UNIT_DATA_REQ" },
+       { GSM48_MMGCC_UNIT_DATA_IND,    "MMGCC_UNIT_DATA_IND" },
+       { GSM48_MMGCC_REEST_REQ,        "MMBCC_REEST_REQ" },
+       { GSM48_MMGCC_REEST_CNF,        "MMBCC_REEST_CNF" },
+       { GSM48_MMGCC_ERR_IND,          "MMGCC_ERR_IND" },
+       { GSM48_MMGCC_NOTIF_IND,        "MMGCC_NOTIF_IND" },
+       { GSM48_MMGCC_GROUP_REQ,        "MMGCC_GROUP_REQ" },
+       { GSM48_MMGCC_GROUP_CNF,        "MMGCC_GROUP_CNF" },
+       { GSM48_MMGCC_UPLINK_REQ,       "MMGCC_UPLINK_REQ" },
+       { GSM48_MMGCC_UPLINK_CNF,       "MMGCC_UPLINK_CNF" },
+       { GSM48_MMGCC_UPLINK_REL_REQ,   "MMGCC_UPLINK_REL_REQ" },
+       { GSM48_MMGCC_UPLINK_REL_IND,   "MMGCC_UPLINK_REL_CNF" },
+       { GSM48_MMGCC_UPLINK_FREE_IND,  "MMGCC_UPLINK_FREE_IND" },
+       { GSM48_MMGCC_UPLINK_BUSY_IND,  "MMGCC_UPLINK_BUSY_IND" },
        { GSM48_MMBCC_EST_REQ,          "MMBCC_EST_REQ" },
        { GSM48_MMBCC_EST_CNF,          "MMBCC_EST_CNF" },
        { GSM48_MMBCC_REL_REQ,          "MMBCC_REL_REQ" },
@@ -673,17 +769,15 @@
        { GSM48_MMBCC_REEST_REQ,        "MMBCC_REEST_REQ" },
        { GSM48_MMBCC_REEST_CNF,        "MMBCC_REEST_CNF" },
        { GSM48_MMBCC_ERR_IND,          "MMBCC_ERR_IND" },
-       { GSM48_MMGCC_EST_REQ,          "MMGCC_EST_REQ" },
-       { GSM48_MMGCC_EST_CNF,          "MMGCC_EST_CNF" },
-       { GSM48_MMGCC_REL_REQ,          "MMGCC_REL_REQ" },
-       { GSM48_MMGCC_REL_IND,          "MMGCC_REL_IND" },
-       { GSM48_MMGCC_DATA_REQ,         "MMGCC_DATA_REQ" },
-       { GSM48_MMGCC_DATA_IND,         "MMGCC_DATA_IND" },
-       { GSM48_MMGCC_UNIT_DATA_REQ,    "MMGCC_UNIT_DATA_REQ" },
-       { GSM48_MMGCC_UNIT_DATA_IND,    "MMGCC_UNIT_DATA_IND" },
-       { GSM48_MMGCC_REEST_REQ,        "MMGCC_REEST_REQ" },
-       { GSM48_MMGCC_REEST_CNF,        "MMGCC_REEST_CNF" },
-       { GSM48_MMGCC_ERR_IND,          "MMGCC_ERR_IND" },
+       { GSM48_MMBCC_NOTIF_IND,        "MMBCC_NOTIF_IND" },
+       { GSM48_MMBCC_GROUP_REQ,        "MMBCC_GROUP_REQ" },
+       { GSM48_MMBCC_GROUP_CNF,        "MMBCC_GROUP_CNF" },
+       { GSM48_MMBCC_UPLINK_REQ,       "MMBCC_UPLINK_REQ" },
+       { GSM48_MMBCC_UPLINK_CNF,       "MMBCC_UPLINK_CNF" },
+       { GSM48_MMBCC_UPLINK_REL_REQ,   "MMBCC_UPLINK_REL_REQ" },
+       { GSM48_MMBCC_UPLINK_REL_IND,   "MMBCC_UPLINK_REL_CNF" },
+       { GSM48_MMBCC_UPLINK_FREE_IND,  "MMBCC_UPLINK_FREE_IND" },
+       { GSM48_MMBCC_UPLINK_BUSY_IND,  "MMBCC_UPLINK_BUSY_IND" },
        { 0,                            NULL }
 };

@@ -913,8 +1007,8 @@
        "wait for RR connection active",
        "MM idle",
        "wait for additional outgoing MM connection",
-       "MM_CONN_ACTIVE_VGCS",
-       "WAIT_RR_CONN_VGCS",
+       "MM connection active (group transmit mode)",
+       "wait for RR connection (group transmit mode)",
        "location updating pending",
        "IMSI detach pending",
        "RR connection release not allowed"
@@ -930,8 +1024,8 @@
        "location updating needed",
        "PLMN search",
        "PLMN search (normal)",
-       "RX_VGCS_NORMAL",
-       "RX_VGCS_LIMITED"
+       "receiving group call (normal service)",
+       "receiving group call (limiteed service)"
 };

 /* change state from LOCATION UPDATE NEEDED to ATTEMPTING TO UPDATE */
@@ -1003,6 +1097,11 @@
                        l23_vty_ms_notify(ms, "Trying to register with network 
%s, %s...\n",
                                   gsm_get_mcc(plmn->plmn.mcc), 
gsm_get_mnc(&plmn->plmn));
                        break;
+               case GSM48_MM_SST_RX_VGCS_NORMAL:
+               case GSM48_MM_SST_RX_VGCS_LIMITED:
+                       l23_vty_ms_notify(ms, NULL);
+                       l23_vty_ms_notify(ms, "Listening to %s call.\n", 
(mm->vgcs.group_call) ? "group" : "broadcast");
+                       break;
                }
        }

@@ -1126,6 +1225,7 @@
        struct gsm48_mmlayer *mm = &ms->mmlayer;
        struct gsm322_cellsel *cs = &ms->cellsel;
        struct gsm48_sysinfo *s = &cs->sel_si;
+       bool vgcs = mm->vgcs.enabled;

        if (cs->state != GSM322_C3_CAMPED_NORMALLY
         && cs->state != GSM322_C7_CAMPED_ANY_CELL) {
@@ -1176,7 +1276,8 @@
                                GSM48_MM_SST_ATTEMPT_UPDATE);
                else
                        new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
-                               GSM48_MM_SST_NORMAL_SERVICE);
+                                    (vgcs) ? GSM48_MM_SST_RX_VGCS_NORMAL
+                                           : GSM48_MM_SST_NORMAL_SERVICE);

                return 0;
        }
@@ -1188,13 +1289,15 @@
                        /* location update not allowed */
                        LOGP(DMM, LOGL_INFO, "Loc. upd. not allowed PLMN.\n");
                        new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
-                               GSM48_MM_SST_LIMITED_SERVICE);
+                                    (vgcs) ? GSM48_MM_SST_RX_VGCS_LIMITED
+                                           : GSM48_MM_SST_LIMITED_SERVICE);
                } else
                if (gsm322_is_forbidden_la(ms, &cs->sel_cgi.lai)) {
                        /* location update not allowed */
                        LOGP(DMM, LOGL_INFO, "Loc. upd. not allowed LA.\n");
                        new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
-                               GSM48_MM_SST_LIMITED_SERVICE);
+                                    (vgcs) ? GSM48_MM_SST_RX_VGCS_LIMITED
+                                           : GSM48_MM_SST_LIMITED_SERVICE);
                } else
                /* 4.4.4.9 if cell is barred, don't start */
                if ((!subscr->acc_barr && s->cell_barr)
@@ -1202,19 +1305,22 @@
                                        (s->class_barr ^ 0xffff)))) {
                        LOGP(DMM, LOGL_INFO, "Loc. upd. no access.\n");
                        new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
-                               GSM48_MM_SST_LIMITED_SERVICE);
+                                    (vgcs) ? GSM48_MM_SST_RX_VGCS_LIMITED
+                                           : GSM48_MM_SST_LIMITED_SERVICE);
                } else {
                        /* location update allowed */
                        LOGP(DMM, LOGL_INFO, "Loc. upd. allowed.\n");
                        new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
-                               GSM48_MM_SST_LOC_UPD_NEEDED);
+                                    (vgcs) ? GSM48_MM_SST_RX_VGCS_LIMITED
+                                           : GSM48_MM_SST_LIMITED_SERVICE);
                }
        } else {
                /* location update not allowed */
                LOGP(DMM, LOGL_INFO, "We are camping on any cell as returning "
                        "to MM IDLE\n");
                new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
-                       GSM48_MM_SST_LIMITED_SERVICE);
+                            (vgcs) ? GSM48_MM_SST_RX_VGCS_LIMITED
+                                   : GSM48_MM_SST_LIMITED_SERVICE);
        }

        return 0;
@@ -1241,6 +1347,7 @@
        struct gsm322_cellsel *cs = &ms->cellsel;
        struct gsm48_sysinfo *s = &cs->sel_si;
        struct gsm_settings *set = &ms->settings;
+       bool vgcs = mm->vgcs.enabled;

        /* no SIM is inserted */
        if (!subscr->sim_valid) {
@@ -1260,7 +1367,8 @@

                        LOGP(DMM, LOGL_INFO, "Valid in location area.\n");
                        new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
-                               GSM48_MM_SST_NORMAL_SERVICE);
+                                    (vgcs) ? GSM48_MM_SST_RX_VGCS_NORMAL
+                                           : GSM48_MM_SST_NORMAL_SERVICE);

                        /* send message to PLMN search process */
                        nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_SUCCESS);
@@ -1275,7 +1383,8 @@

                        LOGP(DMM, LOGL_INFO, "Attachment not required.\n");
                        new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
-                               GSM48_MM_SST_NORMAL_SERVICE);
+                                    (vgcs) ? GSM48_MM_SST_RX_VGCS_NORMAL
+                                           : GSM48_MM_SST_NORMAL_SERVICE);

                        /* send message to PLMN search process */
                        nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_SUCCESS);
@@ -1297,7 +1406,8 @@

                LOGP(DMM, LOGL_INFO, "Selected cell is forbidden.\n");
                new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
-                       GSM48_MM_SST_LIMITED_SERVICE);
+                            (vgcs) ? GSM48_MM_SST_RX_VGCS_LIMITED
+                                   : GSM48_MM_SST_LIMITED_SERVICE);

                /* send message to PLMN search process */
                nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_FAILED);
@@ -1318,7 +1428,8 @@

                LOGP(DMM, LOGL_INFO, "Selected cell not found.\n");
                new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
-                       GSM48_MM_SST_LIMITED_SERVICE);
+                            (vgcs) ? GSM48_MM_SST_RX_VGCS_LIMITED
+                                   : GSM48_MM_SST_LIMITED_SERVICE);

                /* send message to PLMN search process */
                nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_FAILED);
@@ -2029,6 +2140,32 @@
        return gsm48_mm_imsi_detach_sent(ms, msg);
 }

+/* Detach during VGCS. Queue and return idle. */
+static int gsm48_mm_imsi_detach_vgcs(struct osmocom_ms *ms, struct msgb *msg)
+{
+       struct gsm48_mmlayer *mm = &ms->mmlayer;
+       struct msgb *nmsg;
+       struct gsm48_mmxx_hdr *nmmh;
+       int msg_type;
+
+       /* remember to detach later */
+       mm->delay_detach = 1;
+
+       /* Release group call. */
+       gsm48_mm_group_rel_req(ms, msg);
+
+       /* Release message to GCC/BCC layer */
+       msg_type = (mm->vgcs.group_call) ? GSM48_MMGCC_REL_IND : 
GSM48_MMBCC_REL_IND;
+       nmsg = gsm48_mmxx_msgb_alloc(msg_type, mm->vgcs.callref, 0xff, 0);
+       if (!nmsg)
+               return -ENOMEM;
+       nmmh = (struct gsm48_mmxx_hdr *)nmsg->data;
+       nmmh->cause = GSM48_CC_CAUSE_NORMAL_UNSPEC;
+
+       gsm48_mmxx_upmsg(ms, nmsg);
+       return 0;
+}
+
 /* ignore ongoing IMSI detach */
 static int gsm48_mm_imsi_detach_ignore(struct osmocom_ms *ms, struct msgb *msg)
 {
@@ -2145,6 +2282,8 @@
        if (mm->state == GSM48_MM_ST_MM_IDLE
         && (mm->substate == GSM48_MM_SST_NO_CELL_AVAIL
          || mm->substate == GSM48_MM_SST_LIMITED_SERVICE
+         || mm->substate == GSM48_MM_SST_RX_VGCS_NORMAL
+         || mm->substate == GSM48_MM_SST_RX_VGCS_LIMITED
          || mm->substate == GSM48_MM_SST_PLMN_SEARCH
          || mm->substate == GSM48_MM_SST_PLMN_SEARCH_NORMAL))
                return 0;
@@ -2276,6 +2415,7 @@
        struct gsm_subscriber *subscr = &ms->subscr;
        struct gsm322_cellsel *cs = &ms->cellsel;
        struct gsm48_sysinfo *s = &cs->sel_si;
+       bool vgcs = mm->vgcs.enabled;
        struct msgb *nmsg;

        /* in case we already have a location update going on */
@@ -2315,7 +2455,8 @@

                /* go straight to normal service state */
                new_mm_state(mm, GSM48_MM_ST_MM_IDLE,
-                               GSM48_MM_SST_NORMAL_SERVICE);
+                            (vgcs) ? GSM48_MM_SST_RX_VGCS_NORMAL
+                                   : GSM48_MM_SST_NORMAL_SERVICE);

 #if 0
                /* don't send message, if we got not triggered by PLMN search */
@@ -3397,6 +3538,7 @@
 {
        struct gsm48_mmlayer *mm = &ms->mmlayer;
        struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *)msg->data;
+       uint32_t msg_type = rrh->msg_type;
        int cause;

        /* stop RR release timer */
@@ -3416,11 +3558,13 @@
                cause = 47;
        }

+       LOGP(DMM, LOGL_INFO, "Aborting connection with cause %d\n", cause);
+
        /* stop MM connection timer */
        stop_mm_t3230(mm);

        /* release all connections */
-       gsm48_mm_release_mm_conn(ms, 1, cause, 1, 0);
+       gsm48_mm_release_mm_conn(ms, 1, cause, (msg_type == 
GSM48_RR_ABORT_IND), 0);

        /* return to MM IDLE */
        return gsm48_mm_return_idle(ms, NULL);
@@ -3464,27 +3608,32 @@
        struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data;
        struct gsm48_mm_conn *conn;
        int msg_type = mmh->msg_type;
+       uint8_t sapi;

-       /* get connection, if not exist (anymore), release */
-       conn = mm_conn_by_ref_and_class(mm, mmh->ref, (mmh->msg_type & 
GSM48_MMXX_MASK));
-       if (!conn) {
-               LOGP(DMM, LOGL_INFO, "MMXX_DATA_REQ with unknown (already "
-                       "released) ref=%x, sending MMXX_REL_IND\n", mmh->ref);
-               mmh->msg_type = GSM48_MMXX_REL_IND | (msg_type & 
GSM48_MMXX_MASK);
-               mmh->cause = 31;
+       if (mm->state == GSM48_MM_ST_MM_CONN_ACTIVE_VGCS) {
+               /* Group transmit mode has no MM connection. */
+               sapi = 0;
+       } else {
+               /* get connection, if not exist (anymore), release */
+               conn = mm_conn_by_ref_and_class(mm, mmh->ref, (mmh->msg_type & 
GSM48_MMXX_MASK));
+               if (!conn) {
+                       LOGP(DMM, LOGL_INFO, "MMXX_DATA_REQ with unknown 
(already "
+                               "released) ref=%x, sending MMXX_REL_IND\n", 
mmh->ref);
+                       mmh->msg_type = GSM48_MMXX_REL_IND | (msg_type & 
GSM48_MMXX_MASK);
+                       mmh->cause = 31;

-               /* mirror message with REL_IND + cause */
-               return gsm48_mmxx_upmsg(ms, msg);
+                       /* mirror message with REL_IND + cause */
+                       return gsm48_mmxx_upmsg(ms, msg);
+               }
+               /* set SAPI, if upper layer does not do it correctly */
+               sapi = conn->sapi;
        }

-       /* set SAPI, if upper layer does not do it correctly */
-       mmh->sapi = conn->sapi;
-
        /* pull MM header */
        msgb_pull(msg, sizeof(struct gsm48_mmxx_hdr));

        /* push RR header and send down */
-       return gsm48_mm_to_rr(ms, msg, GSM48_RR_DATA_REQ, conn->sapi, 0);
+       return gsm48_mm_to_rr(ms, msg, GSM48_RR_DATA_REQ, sapi, 0);
 }

 /* release of MM connection (active state) */
@@ -3591,6 +3740,336 @@
        return gsm48_mm_return_idle(ms, NULL);
 }

+/* The RR indicates notification of ongoing VGCS/VBS calls. */
+static int gsm48_mm_notification(struct osmocom_ms *ms, struct msgb *msg)
+{
+       struct gsm48_mm_event *mme = (struct gsm48_mm_event *)msg->data;
+       uint32_t ref = osmo_load32be(mme->notification.gcr) >> 5;
+       uint16_t msg_type = (mme->notification.gcr[3] & 0x10) ? 
GSM48_MMGCC_NOTIF_IND : GSM48_MMBCC_NOTIF_IND;
+       struct gsm48_mmxx_hdr *mmh;
+       struct msgb *nmsg;
+
+       /* Notification message to GCC/BCC layer */
+       nmsg = gsm48_mmxx_msgb_alloc(msg_type, ref, 0xff, 0);
+       if (!nmsg)
+               return -ENOMEM;
+       mmh = (struct gsm48_mmxx_hdr *)nmsg->data;
+       mmh->notify = (mme->notification.gone) ? MMXX_NOTIFY_RELEASE : 
MMXX_NOTIFY_SETUP;
+       mmh->ch_desc_present = mme->notification.ch_desc_present;
+       memcpy(&mmh->ch_desc, &mme->notification.ch_desc, sizeof(mmh->ch_desc));
+
+       gsm48_mmxx_upmsg(ms, nmsg);
+       return 0;
+}
+
+/* The RR indicates uplink busy. */
+static int gsm48_mm_uplink_free(struct osmocom_ms *ms, struct msgb *msg)
+{
+       struct gsm48_mmlayer *mm = &ms->mmlayer;
+       struct gsm48_mm_event *mme = (struct gsm48_mm_event *)msg->data;
+       struct msgb *nmsg;
+       uint16_t msg_type;
+
+       if (mm->vgcs.group_call)
+               msg_type = (mme->msg_type == GSM48_MM_EVENT_UPLINK_BUSY) ? 
GSM48_MMGCC_UPLINK_BUSY_IND
+                                                                        : 
GSM48_MMGCC_UPLINK_FREE_IND;
+       else
+               msg_type = (mme->msg_type == GSM48_MM_EVENT_UPLINK_BUSY) ? 
GSM48_MMBCC_UPLINK_BUSY_IND
+                                                                        : 
GSM48_MMBCC_UPLINK_FREE_IND;
+
+       LOGP(DMM, LOGL_INFO, "Update uplink free/busy state in group receive 
mode.\n");
+
+       /* Notification message to GCC/BCC layer */
+       nmsg = gsm48_mmxx_msgb_alloc(msg_type, mm->vgcs.callref, 0xff, 0);
+       if (!nmsg)
+               return -ENOMEM;
+
+       gsm48_mmxx_upmsg(ms, nmsg);
+       return 0;
+}
+
+/* Join VGCS/VBS call as listener. */
+static int gsm48_mm_group_req(struct osmocom_ms *ms, struct msgb *msg)
+{
+       struct gsm48_mmlayer *mm = &ms->mmlayer;
+       struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data;
+       struct msgb *nmsg;
+
+       LOGP(DMM, LOGL_INFO, "Request for joining a group call, trying to 
establish group receive mode.\n");
+
+       /* Store infos about group/broadcast call. */
+       mm->vgcs.enabled = true;
+       mm->vgcs.group_call = (mmh->msg_type == GSM48_MMGCC_GROUP_REQ);
+       mm->vgcs.callref = mmh->ref;
+       mm->vgcs.normal_service = (mm->substate == GSM48_MM_SST_NORMAL_SERVICE);
+
+       /* Change to VGCS substate. */
+       new_mm_state(mm, GSM48_MM_ST_MM_IDLE, (mm->substate == 
GSM48_MM_SST_NORMAL_SERVICE)
+                                             ? GSM48_MM_SST_RX_VGCS_NORMAL : 
GSM48_MM_SST_RX_VGCS_LIMITED);
+
+       /* Group recevie mode request to RR layer */
+       nmsg = gsm48_l3_msgb_alloc();
+       if (!nmsg)
+               return -ENOMEM;
+
+       /* Add channel description. */
+       memcpy(msgb_put(nmsg, sizeof(mmh->ch_desc)), &mmh->ch_desc, 
sizeof(mmh->ch_desc));
+
+       /* Push RR header and send to RR layer. */
+       return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_GROUP_REQ, 0, 0);
+}
+
+/* Joining VGCS/VBS call is not allowed in other states. */
+static int gsm48_mm_group_reject(struct osmocom_ms *ms, struct msgb *msg)
+{
+       struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data;
+       uint16_t msg_type;
+       struct msgb *nmsg;
+       struct gsm48_mmxx_hdr *nmmh;
+
+       LOGP(DMM, LOGL_NOTICE, "Joining group call rejected in current 
state.\n");
+
+       msg_type = (mmh->msg_type == GSM48_MMGCC_GROUP_REQ) ? 
GSM48_MMGCC_REL_IND : GSM48_MMBCC_REL_IND;
+
+       /* Release message to GCC/BCC layer */
+       nmsg = gsm48_mmxx_msgb_alloc(msg_type, mmh->ref, mmh->transaction_id, 
mmh->sapi);
+       if (!nmsg)
+               return -ENOMEM;
+       nmmh = (struct gsm48_mmxx_hdr *)nmsg->data;
+       nmmh->cause = GSM48_CC_CAUSE_CALL_REJECTED;
+
+       gsm48_mmxx_upmsg(ms, nmsg);
+       return 0;
+}
+
+/* RR layer confirms group call. */
+static int gsm48_mm_group_cnf(struct osmocom_ms *ms, struct msgb *msg)
+{
+       struct gsm48_mmlayer *mm = &ms->mmlayer;
+       uint16_t msg_type;
+       struct msgb *nmsg;
+
+       LOGP(DMM, LOGL_NOTICE, "RR confirms group call.\n");
+
+       msg_type = (mm->vgcs.group_call) ? GSM48_MMGCC_GROUP_CNF : 
GSM48_MMBCC_GROUP_CNF;
+
+       /* Uplink confirm message to GCC/BCC layer */
+       nmsg = gsm48_mmxx_msgb_alloc(msg_type, mm->vgcs.callref, 0xff, 0);
+       if (!nmsg)
+               return -ENOMEM;
+
+
+       gsm48_mmxx_upmsg(ms, nmsg);
+       return 0;
+}
+
+/* RR layer releases group call channel. */
+static int gsm48_mm_group_rel_ind(struct osmocom_ms *ms, struct msgb *msg)
+{
+       struct gsm48_mmlayer *mm = &ms->mmlayer;
+       struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *)msg->data;
+       uint16_t msg_type;
+       struct msgb *nmsg;
+       struct gsm48_mmxx_hdr *nmmh;
+
+       LOGP(DMM, LOGL_NOTICE, "RR released or rejected group call channel.\n");
+
+       /* Disable group mode. */
+       mm->vgcs.enabled = false;
+
+       /* Change mode back to normal or limited service. */
+       if (mm->substate == GSM48_MM_SST_RX_VGCS_LIMITED)
+               mm->substate = GSM48_MM_SST_LIMITED_SERVICE;
+       if (mm->substate == GSM48_MM_SST_RX_VGCS_NORMAL)
+               mm->substate = GSM48_MM_SST_NORMAL_SERVICE;
+
+       /* Return IDLE, if not already. Also select the sub-state to use. */
+       gsm48_mm_return_idle(ms, NULL);
+
+       /* Release message to GCC/BCC layer */
+       msg_type = (mm->vgcs.group_call) ? GSM48_MMGCC_REL_IND : 
GSM48_MMBCC_REL_IND;
+       nmsg = gsm48_mmxx_msgb_alloc(msg_type, mm->vgcs.callref, 0xff, 0);
+       if (!nmsg)
+               return -ENOMEM;
+       nmmh = (struct gsm48_mmxx_hdr *)nmsg->data;
+       switch (rrh->cause) {
+       case RR_REL_CAUSE_TRY_LATER:
+               /* Joining not yet possible */
+               nmmh->cause = GSM48_CC_CAUSE_TEMP_FAILURE;
+               break;
+       case RR_REL_CAUSE_LOST_SIGNAL:
+               /* Lower layer failed. */
+               nmmh->cause = GSM48_CC_CAUSE_TEMP_FAILURE;
+               break;
+       case RR_REL_CAUSE_NORMAL:
+               /* Channel was released by network. */
+               nmmh->cause = GSM48_CC_CAUSE_NORM_CALL_CLEAR;
+               break;
+       default:
+               nmmh->cause = GSM48_CC_CAUSE_NORMAL_UNSPEC;
+               break;
+       }
+
+       gsm48_mmxx_upmsg(ms, nmsg);
+       return 0;
+}
+
+/* Upper layer releases group call. */
+static int gsm48_mm_group_rel_req(struct osmocom_ms *ms, struct msgb *msg)
+{
+       struct gsm48_mmlayer *mm = &ms->mmlayer;
+       struct msgb *nmsg;
+
+       LOGP(DMM, LOGL_INFO, "Request to release group call in receive or 
transmit mode.\n");
+
+       /* Disable group mode. */
+       mm->vgcs.enabled = false;
+
+       /* Change mode back to normal or limited service. */
+       if (mm->substate == GSM48_MM_SST_RX_VGCS_LIMITED)
+               mm->substate = GSM48_MM_SST_LIMITED_SERVICE;
+       if (mm->substate == GSM48_MM_SST_RX_VGCS_NORMAL)
+               mm->substate = GSM48_MM_SST_NORMAL_SERVICE;
+
+       /* We are already IDLE. Also select the sub-state to use. */
+       gsm48_mm_return_idle(ms, NULL);
+
+       nmsg = gsm48_l3_msgb_alloc();
+       if (!nmsg)
+               return -ENOMEM;
+
+       /* Push RR header and send to RR layer. */
+       return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_GROUP_REL_REQ, 0, 0);
+}
+
+/* Upper layer requests uplink. */
+static int gsm48_mm_uplink_req(struct osmocom_ms *ms, struct msgb *msg)
+{
+       struct gsm48_mmlayer *mm = &ms->mmlayer;
+       struct msgb *nmsg;
+
+       LOGP(DMM, LOGL_INFO, "Request for uplink, trying to establish group 
transmit mode.\n");
+
+       /* Go into uplink pending state. */
+       new_mm_state(mm, GSM48_MM_ST_WAIT_RR_CONN_VGCS, 0);
+
+       nmsg = gsm48_l3_msgb_alloc();
+       if (!nmsg)
+               return -ENOMEM;
+
+       /* Push RR header and send to RR layer. */
+       return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_UPLINK_REQ, 0, 0);
+}
+
+/* Uplink not allowed in this state. */
+static int gsm48_mm_uplink_reject(struct osmocom_ms *ms, struct msgb *msg)
+{
+       struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data;
+       uint16_t msg_type;
+       struct msgb *nmsg;
+       struct gsm48_mmxx_hdr *nmmh;
+
+       LOGP(DMM, LOGL_NOTICE, "Request for uplink rejected in current 
state.\n");
+
+       msg_type = (mmh->msg_type == GSM48_MMGCC_UPLINK_REQ) ? 
GSM48_MMGCC_UPLINK_REL_IND : GSM48_MMBCC_UPLINK_REL_IND;
+
+       /* Uplink release message to GCC/BCC layer */
+       nmsg = gsm48_mmxx_msgb_alloc(msg_type, mmh->ref, mmh->transaction_id, 
mmh->sapi);
+       if (!nmsg)
+               return -ENOMEM;
+       nmmh = (struct gsm48_mmxx_hdr *)nmsg->data;
+       nmmh->cause = GSM48_CC_CAUSE_CALL_REJECTED;
+
+       gsm48_mmxx_upmsg(ms, nmsg);
+       return 0;
+}
+
+/* RR layer confirms uplink. */
+static int gsm48_mm_uplink_cnf(struct osmocom_ms *ms, struct msgb *msg)
+{
+       struct gsm48_mmlayer *mm = &ms->mmlayer;
+       uint16_t msg_type;
+       struct msgb *nmsg;
+
+       LOGP(DMM, LOGL_NOTICE, "RR confirms uplink.\n");
+
+       /* Go into group transmit state. */
+       new_mm_state(mm, GSM48_MM_ST_MM_CONN_ACTIVE_VGCS, 0);
+
+       msg_type = (mm->vgcs.group_call) ? GSM48_MMGCC_UPLINK_CNF : 
GSM48_MMBCC_UPLINK_CNF;
+
+       /* Uplink confirm message to GCC/BCC layer */
+       nmsg = gsm48_mmxx_msgb_alloc(msg_type, mm->vgcs.callref, 0xff, 0);
+       if (!nmsg)
+               return -ENOMEM;
+
+       gsm48_mmxx_upmsg(ms, nmsg);
+       return 0;
+}
+
+/* RR layer releases/rejects uplink. */
+static int gsm48_mm_uplink_rel_ind(struct osmocom_ms *ms, struct msgb *msg)
+{
+       struct gsm48_mmlayer *mm = &ms->mmlayer;
+       struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *)msg->data;
+       uint16_t msg_type;
+       struct msgb *nmsg;
+       struct gsm48_mmxx_hdr *nmmh;
+
+       LOGP(DMM, LOGL_NOTICE, "RR released or rejected uplink.\n");
+
+       /* Change to VGCS substate. */
+       new_mm_state(mm, GSM48_MM_ST_MM_IDLE, (mm->vgcs.normal_service) ? 
GSM48_MM_SST_RX_VGCS_NORMAL
+                                                                       : 
GSM48_MM_SST_RX_VGCS_LIMITED);
+
+       msg_type = (mm->vgcs.group_call) ? GSM48_MMGCC_UPLINK_REL_IND : 
GSM48_MMBCC_UPLINK_REL_IND;
+
+       /* Uplink reject message to GCC/BCC layer */
+       nmsg = gsm48_mmxx_msgb_alloc(msg_type, mm->vgcs.callref, 0xff, 0);
+       if (!nmsg)
+               return -ENOMEM;
+       nmmh = (struct gsm48_mmxx_hdr *)nmsg->data;
+       switch (rrh->cause) {
+       case RR_REL_CAUSE_UPLINK_REJECTED:
+               /* Access to uplink was rejected by network or when not in 
group receive mode. */
+               nmmh->cause = GSM48_CC_CAUSE_CALL_REJECTED;
+               break;
+       case RR_REL_CAUSE_UPLINK_BUSY:
+               /* Uplink was busy and did not become free. */
+               nmmh->cause = GSM48_CC_CAUSE_USER_BUSY;
+               break;
+       case RR_REL_CAUSE_LINK_FAILURE:
+               /* Access to uplink failed. */
+               nmmh->cause = GSM48_CC_CAUSE_TEMP_FAILURE;
+               break;
+       case RR_REL_CAUSE_NORMAL:
+               /* Uplink was released by message. */
+               nmmh->cause = GSM48_CC_CAUSE_NORM_CALL_CLEAR;
+               break;
+       default:
+               nmmh->cause = GSM48_CC_CAUSE_NORMAL_UNSPEC;
+               break;
+       }
+
+       gsm48_mmxx_upmsg(ms, nmsg);
+       return 0;
+}
+
+/* Upper layer releases uplink. */
+static int gsm48_mm_uplink_rel_req(struct osmocom_ms *ms, struct msgb *msg)
+{
+       struct msgb *nmsg;
+
+       LOGP(DMM, LOGL_INFO, "Request to release uplink, leaving group transmit 
mode.\n");
+
+       nmsg = gsm48_l3_msgb_alloc();
+       if (!nmsg)
+               return -ENOMEM;
+
+       /* Push RR header and send to RR layer. */
+       return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_UPLINK_REL_REQ, 0, 0);
+}
+
 /*
  * other processes
  */
@@ -3675,15 +4154,33 @@
        {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE),
         GSM48_MMBCC_EST_REQ, gsm48_mm_init_mm_no_rr},

+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE),
+        GSM48_MMBCC_GROUP_REQ, gsm48_mm_group_req},
+
+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE),
+        GSM48_MMGCC_GROUP_REQ, gsm48_mm_group_req},
+
        /* 4.2.2.2 Attempt to update / Loc. Upd. needed */
        {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE) |
                                SBIT(GSM48_MM_SST_LOC_UPD_NEEDED),
         GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr}, /* emergency only */

+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE),
+        GSM48_MMBCC_GROUP_REQ, gsm48_mm_group_req},
+
+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE),
+        GSM48_MMGCC_GROUP_REQ, gsm48_mm_group_req},
+
        /* 4.2.2.3 Limited service */
        {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE),
         GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr},

+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE),
+        GSM48_MMBCC_GROUP_REQ, gsm48_mm_group_req},
+
+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE),
+        GSM48_MMGCC_GROUP_REQ, gsm48_mm_group_req},
+
        /* 4.2.2.4 No IMSI */
        {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NO_IMSI),
         GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr},
@@ -3708,6 +4205,32 @@
        {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH),
         GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr},

+       /* 4.2.2.7 Receiving group call, normal service */
+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_NORMAL),
+        GSM48_MMGCC_REL_REQ, gsm48_mm_group_rel_req},
+
+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_NORMAL),
+        GSM48_MMBCC_REL_REQ, gsm48_mm_group_rel_req},
+
+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_NORMAL),
+        GSM48_MMGCC_UPLINK_REQ, gsm48_mm_uplink_req},
+
+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_NORMAL),
+        GSM48_MMBCC_UPLINK_REQ, gsm48_mm_uplink_req},
+
+       /* 4.2.2.8 Receiving group call, limited service */
+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_LIMITED),
+        GSM48_MMGCC_REL_REQ, gsm48_mm_group_rel_req},
+
+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_LIMITED),
+        GSM48_MMBCC_REL_REQ, gsm48_mm_group_rel_req},
+
+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_LIMITED),
+        GSM48_MMGCC_UPLINK_REQ, gsm48_mm_uplink_reject},
+
+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_LIMITED),
+        GSM48_MMBCC_UPLINK_REQ, gsm48_mm_uplink_reject},
+
        /* 4.5.1.1 MM Connection (EST) */
        {SBIT(GSM48_MM_ST_RR_CONN_RELEASE_NA), ALL_STATES,
         GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_first},
@@ -3734,10 +4257,7 @@
         GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_more},

        {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES,
-        GSM48_MMGCC_EST_REQ, gsm48_mm_init_mm_more},
-
-       {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES,
-        GSM48_MMBCC_EST_REQ, gsm48_mm_init_mm_more},
+        GSM48_MMGCC_GROUP_REQ, gsm48_mm_group_req},

        {SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES,
         GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_wait},
@@ -3748,12 +4268,7 @@
        {SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES,
         GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_wait},

-       {SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES,
-        GSM48_MMGCC_EST_REQ, gsm48_mm_init_mm_wait},
-
-       {SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES,
-        GSM48_MMBCC_EST_REQ, gsm48_mm_init_mm_wait},
-
+       /* Reject call in other states. */
        {ALL_STATES, ALL_STATES,
         GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_reject},

@@ -3769,6 +4284,12 @@
        {ALL_STATES, ALL_STATES,
         GSM48_MMBCC_EST_REQ, gsm48_mm_init_mm_reject},

+       {ALL_STATES, ALL_STATES,
+        GSM48_MMGCC_GROUP_REQ, gsm48_mm_group_reject},
+
+       {ALL_STATES, ALL_STATES,
+        GSM48_MMBCC_GROUP_REQ, gsm48_mm_group_reject},
+
        /* 4.5.2.1 MM Connection (DATA) */
        {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) |
         SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES,
@@ -3844,6 +4365,29 @@

        {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON), ALL_STATES,
         GSM48_MMBCC_REL_REQ, gsm48_mm_release_wait_rr},
+
+       /* Group transmit mode */
+       {SBIT(GSM48_MM_ST_WAIT_RR_CONN_VGCS) |
+        SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS), ALL_STATES,
+        GSM48_MMGCC_UPLINK_REL_REQ, gsm48_mm_uplink_rel_req},
+
+       {SBIT(GSM48_MM_ST_WAIT_RR_CONN_VGCS) |
+        SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS), ALL_STATES,
+        GSM48_MMBCC_UPLINK_REL_REQ, gsm48_mm_uplink_rel_req},
+
+       {SBIT(GSM48_MM_ST_WAIT_RR_CONN_VGCS) |
+        SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS), ALL_STATES,
+        GSM48_MMGCC_REL_REQ, gsm48_mm_group_rel_req},
+
+       {SBIT(GSM48_MM_ST_WAIT_RR_CONN_VGCS) |
+        SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS), ALL_STATES,
+        GSM48_MMBCC_REL_REQ, gsm48_mm_group_rel_req},
+
+       {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS), ALL_STATES,
+        GSM48_MMGCC_DATA_REQ, gsm48_mm_data},
+
+       {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS), ALL_STATES,
+        GSM48_MMBCC_DATA_REQ, gsm48_mm_data},
 };

 #define DOWNSLLEN \
@@ -3971,6 +4515,23 @@
         SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), /* not supported */
         GSM48_RR_ABORT_IND, gsm48_mm_abort_mm_con},

+       /* Group call */
+       {ALL_STATES,
+        GSM48_RR_GROUP_CNF, gsm48_mm_group_cnf},
+
+       {ALL_STATES,
+        GSM48_RR_UPLINK_CNF, gsm48_mm_uplink_cnf},
+
+       {ALL_STATES,
+        GSM48_RR_GROUP_REL_IND, gsm48_mm_group_rel_ind},
+
+       {ALL_STATES,
+        GSM48_RR_UPLINK_REL_IND, gsm48_mm_uplink_rel_ind},
+
+       {SBIT(GSM48_MM_ST_WAIT_RR_CONN_VGCS) |
+        SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS),
+        GSM48_RR_REL_IND, gsm48_mm_group_rel_ind},
+
        /* other (also wait for network command) */
        {ALL_STATES,
         GSM48_RR_REL_IND, gsm48_mm_rel_other},
@@ -4065,6 +4626,7 @@
        uint8_t pdisc = gh->proto_discr & 0x0f;
        uint8_t msg_type = gh->msg_type & 0xbf;
        uint8_t transaction_id;
+       uint32_t callref;
        struct gsm48_mm_conn *conn;
        struct gsm48_mmxx_hdr *mmh;
        int msg_supported = 0; /* determine, if message is supported at all */
@@ -4087,7 +4649,7 @@
        /* pull the RR header */
        msgb_pull(msg, sizeof(struct gsm48_rr_hdr));

-       /* create transaction (if not exists) and push MM header to message */
+       /* create transaction (if not exists) and push message */
        switch (pdisc) {
        case GSM48_PDISC_CC:
                rr_prim = GSM48_MMCC_DATA_IND;
@@ -4113,34 +4675,39 @@

        transaction_id = ((gh->proto_discr & 0xf0) ^ 0x80) >> 4; /* flip */

-       /* find transaction, if any */
-       conn = mm_conn_by_id(mm, pdisc, transaction_id);
+       if (mm->vgcs.enabled) {
+               /* Ongoing group call. */
+               callref = mm->vgcs.callref;
+       } else {
+               /* find transaction, if any */
+               conn = mm_conn_by_id(mm, pdisc, transaction_id);

-       /* create MM connection instance */
-       if (!conn) {
-               /* if MT calls are not supported with protocol */
-               if (rr_est == -1) {
-                       LOGP(DMM, LOGL_ERROR, "No MO connection for pdisc=%d, "
-                            "transaction_id=%d\n", pdisc, transaction_id);
-                       msgb_free(msg);
-                       return -EINVAL;
+               /* create MM connection instance */
+               if (!conn) {
+                       /* if MT calls are not supported with protocol */
+                       if (rr_est == -1) {
+                               LOGP(DMM, LOGL_ERROR, "No MO connection for 
pdisc=%d, transaction_id=%d\n",
+                                    pdisc, transaction_id);
+                               msgb_free(msg);
+                               return -EINVAL;
+                       }
+                       conn = mm_conn_new(mm, pdisc, transaction_id, sapi, 
mm_conn_new_ref++);
+                       rr_prim = rr_est;
                }
-
-               conn = mm_conn_new(mm, pdisc, transaction_id, sapi, 
mm_conn_new_ref++);
                if (!conn) {
                        msgb_free(msg);
                        return -ENOMEM;
                }
-               rr_prim = rr_est;
+               callref = conn->ref;
        }

        /* push new header */
        msgb_push(msg, sizeof(struct gsm48_mmxx_hdr));
        mmh = (struct gsm48_mmxx_hdr *)msg->data;
        mmh->msg_type = rr_prim;
-       mmh->ref = conn->ref;
-       mmh->transaction_id = conn->transaction_id;
-       mmh->sapi = conn->sapi;
+       mmh->ref = callref;
+       mmh->transaction_id = transaction_id;
+       mmh->sapi = sapi;

        /* go MM CONN ACTIVE state */
        if (mm->state == GSM48_MM_ST_WAIT_NETWORK_CMD ||
@@ -4272,6 +4839,10 @@
        {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE),
         GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_start},

+       /* 4.2.2.7 Receiving Group Call (Normal service) */
+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_NORMAL),
+        GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_vgcs},
+
        /* 4.2.2.2 Attempt to update / Loc. upd. needed */
        {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE) |
                                SBIT(GSM48_MM_SST_LOC_UPD_NEEDED),
@@ -4307,6 +4878,10 @@
        {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE),
         GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_delay_per}, /* 4.4.2 */

+       /* 4.2.2.8 Receiving Group Call (Limited service) */
+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_LIMITED),
+        GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_vgcs},
+
        /* 4.2.2.4 No IMSI */
        /* 4.2.2.5 PLMN search, normal service */
        {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL),
@@ -4342,12 +4917,15 @@
        {SBIT(GSM48_MM_ST_MM_IDLE), ALL_STATES, /* silently detach */
         GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_end},

+       {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS) |
+        SBIT(GSM48_MM_ST_WAIT_RR_CONN_VGCS), ALL_STATES, /* uplink access */
+        GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_vgcs},
+
        {SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN) |
         SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) |
         SBIT(GSM48_MM_ST_PROCESS_CM_SERV_P) |
         SBIT(GSM48_MM_ST_WAIT_REEST) |
         SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON) |
-        SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS) |
         SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES, /* we can release */
         GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_release},

@@ -4402,6 +4980,17 @@
        {ALL_STATES, ALL_STATES,
         GSM48_MM_EVENT_CLASSMARK_CHG, gsm48_mm_classm_chg},
 #endif
+
+       /* Group call notification event */
+       {ALL_STATES, ALL_STATES,
+        GSM48_MM_EVENT_NOTIFICATION, gsm48_mm_notification},
+
+       /* Uplink free/busy while in group receive mode */
+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_NORMAL) | 
SBIT(GSM48_MM_SST_RX_VGCS_LIMITED),
+        GSM48_MM_EVENT_UPLINK_BUSY, gsm48_mm_uplink_free},
+
+       {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_RX_VGCS_NORMAL) | 
SBIT(GSM48_MM_SST_RX_VGCS_LIMITED),
+        GSM48_MM_EVENT_UPLINK_FREE, gsm48_mm_uplink_free},
 };

 #define EVENTSLLEN \

--
To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/34492?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings

Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-Change-Id: I05957182a57423ad947ab200b52f65fde859e110
Gerrit-Change-Number: 34492
Gerrit-PatchSet: 15
Gerrit-Owner: jolly <andr...@eversberg.eu>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <vyanits...@sysmocom.de>
Gerrit-Reviewer: laforge <lafo...@osmocom.org>
Gerrit-Reviewer: pespin <pes...@sysmocom.de>
Gerrit-MessageType: merged

Reply via email to