diff options
-rw-r--r-- | src/host/layer23/include/osmocom/bb/mobile/Makefile.am | 2 | ||||
-rw-r--r-- | src/host/layer23/include/osmocom/bb/mobile/gsm44068_gcc_bcc.h | 43 | ||||
-rw-r--r-- | src/host/layer23/include/osmocom/bb/mobile/transaction.h | 21 | ||||
-rw-r--r-- | src/host/layer23/src/mobile/Makefile.am | 1 | ||||
-rw-r--r-- | src/host/layer23/src/mobile/app_mobile.c | 3 | ||||
-rw-r--r-- | src/host/layer23/src/mobile/gsm411_sms.c | 9 | ||||
-rw-r--r-- | src/host/layer23/src/mobile/gsm44068_gcc_bcc.c | 1951 | ||||
-rw-r--r-- | src/host/layer23/src/mobile/gsm480_ss.c | 7 | ||||
-rw-r--r-- | src/host/layer23/src/mobile/gsm48_cc.c | 7 | ||||
-rw-r--r-- | src/host/layer23/src/mobile/gsm48_mm.c | 13 | ||||
-rw-r--r-- | src/host/layer23/src/mobile/transaction.c | 5 |
11 files changed, 2059 insertions, 3 deletions
diff --git a/src/host/layer23/include/osmocom/bb/mobile/Makefile.am b/src/host/layer23/include/osmocom/bb/mobile/Makefile.am index 14694bb2..534b8659 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/Makefile.am +++ b/src/host/layer23/include/osmocom/bb/mobile/Makefile.am @@ -1,4 +1,4 @@ noinst_HEADERS = gsm322.h gsm480_ss.h gsm411_sms.h gsm48_cc.h gsm48_mm.h \ - gsm48_rr.h mncc.h \ + gsm48_rr.h mncc.h gsm44068_gcc_bcc.h \ transaction.h vty.h mncc_sock.h mncc_ms.h primitives.h \ app_mobile.h voice.h gapk_io.h diff --git a/src/host/layer23/include/osmocom/bb/mobile/gsm44068_gcc_bcc.h b/src/host/layer23/include/osmocom/bb/mobile/gsm44068_gcc_bcc.h new file mode 100644 index 00000000..e9889b32 --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/mobile/gsm44068_gcc_bcc.h @@ -0,0 +1,43 @@ +/* VGCS/VBS call control */ +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * Author: Andreas Eversberg + * + * 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/>. + */ +#pragma once + +#define GSM44068_ALLOC_SIZE 2048 +#define GSM44068_ALLOC_HEADROOM 256 + +static inline struct msgb *gsm44068_msgb_alloc_name(const char *name) +{ + return msgb_alloc_headroom(GSM44068_ALLOC_SIZE, GSM44068_ALLOC_HEADROOM, name); +} + +int gsm44068_gcc_init(struct osmocom_ms *ms); +int gsm44068_gcc_exit(struct osmocom_ms *ms); +int gsm44068_rcv_gcc_bcc(struct osmocom_ms *ms, struct msgb *msg); +int gsm44068_rcv_mm_idle(struct osmocom_ms *ms); +struct gsm_trans *trans_find_ongoing_gcc_bcc(struct osmocom_ms *ms); +int gcc_bcc_call(struct osmocom_ms *ms, uint8_t protocol, const char *number); +int gcc_leave(struct osmocom_ms *ms); +int gcc_bcc_hangup(struct osmocom_ms *ms); +int gcc_talk(struct osmocom_ms *ms); +int gcc_listen(struct osmocom_ms *ms); +int gsm44068_dump_calls(struct osmocom_ms *ms, void (*print)(void *, const char *, ...), void *priv); diff --git a/src/host/layer23/include/osmocom/bb/mobile/transaction.h b/src/host/layer23/include/osmocom/bb/mobile/transaction.h index 1c998cdd..103ae4e7 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/transaction.h +++ b/src/host/layer23/include/osmocom/bb/mobile/transaction.h @@ -27,7 +27,6 @@ struct gsm_trans { union { struct { - /* current call state */ int state; @@ -55,6 +54,26 @@ struct gsm_trans { struct gsm_sms *sms; } sms; + struct { + /* VGCS/VBS state machine */ + struct osmo_fsm_inst *fi; + + /* Call State (See Table 9.3 of TS 144.068) */ + uint8_t call_state; + + /* State attributes (See Table 9.7 of TS 144.068) */ + uint8_t d_att, u_att, comm, orig; + + /* Channel description last received via notification */ + bool ch_desc_present; + struct gsm48_chan_desc ch_desc; + + /* Flag to store termination request from upper layer. */ + bool termination; + + /* Flag to tell the state machine that call changes from separate link to group receive mode. */ + bool receive_after_sl; + } gcc; }; }; diff --git a/src/host/layer23/src/mobile/Makefile.am b/src/host/layer23/src/mobile/Makefile.am index 55df6677..7b05f659 100644 --- a/src/host/layer23/src/mobile/Makefile.am +++ b/src/host/layer23/src/mobile/Makefile.am @@ -23,6 +23,7 @@ libmobile_a_SOURCES = \ gsm480_ss.c \ gsm411_sms.c \ gsm48_cc.c \ + gsm44068_gcc_bcc.c \ gsm48_mm.c \ gsm48_rr.c \ gsm414.c \ diff --git a/src/host/layer23/src/mobile/app_mobile.c b/src/host/layer23/src/mobile/app_mobile.c index 9ce1ad2b..b08976c2 100644 --- a/src/host/layer23/src/mobile/app_mobile.c +++ b/src/host/layer23/src/mobile/app_mobile.c @@ -35,6 +35,7 @@ #include <osmocom/bb/mobile/gsm480_ss.h> #include <osmocom/bb/mobile/gsm48_mm.h> #include <osmocom/bb/mobile/gsm48_cc.h> +#include <osmocom/bb/mobile/gsm44068_gcc_bcc.h> #include <osmocom/bb/mobile/gsm411_sms.h> #include <osmocom/bb/mobile/gsm322.h> #include <osmocom/bb/mobile/vty.h> @@ -212,6 +213,7 @@ int mobile_exit(struct osmocom_ms *ms, int force) gsm48_cc_exit(ms); gsm480_ss_exit(ms); gsm411_sms_exit(ms); + gsm44068_gcc_exit(ms); gsm_sim_exit(ms); lapdm_channel_exit(&ms->lapdm_channel); @@ -253,6 +255,7 @@ static int mobile_init(struct osmocom_ms *ms) gsm48_cc_init(ms); gsm480_ss_init(ms); gsm411_sms_init(ms); + gsm44068_gcc_init(ms); gsm_voice_init(ms); gsm_subscr_init(ms); gsm48_rr_init(ms); diff --git a/src/host/layer23/src/mobile/gsm411_sms.c b/src/host/layer23/src/mobile/gsm411_sms.c index a5454b72..a21133b9 100644 --- a/src/host/layer23/src/mobile/gsm411_sms.c +++ b/src/host/layer23/src/mobile/gsm411_sms.c @@ -34,6 +34,7 @@ #include <osmocom/bb/mobile/mncc.h> #include <osmocom/bb/mobile/transaction.h> #include <osmocom/bb/mobile/gsm411_sms.h> +#include <osmocom/bb/mobile/gsm44068_gcc_bcc.h> #include <osmocom/gsm/gsm0411_utils.h> #include <osmocom/core/talloc.h> #include <osmocom/bb/mobile/vty.h> @@ -651,6 +652,14 @@ int gsm411_tx_sms_submit(struct osmocom_ms *ms, const char *sms_sca, return -EIO; } + /* ASCI call does not allow other transactions */ + if (trans_find_ongoing_gcc_bcc(ms)) { + LOGP(DLSMS, LOGL_ERROR, "Phone is busy doing ASCI call\n"); + gsm411_sms_report(ms, sms, GSM411_RP_CAUSE_MO_TEMP_FAIL); + sms_free(sms); + return -EIO; + } + /* allocate transaction with dummy reference */ transaction_id = trans_assign_trans_id(ms, GSM48_PDISC_SMS, 0); if (transaction_id < 0) { diff --git a/src/host/layer23/src/mobile/gsm44068_gcc_bcc.c b/src/host/layer23/src/mobile/gsm44068_gcc_bcc.c new file mode 100644 index 00000000..fa0e7bdb --- /dev/null +++ b/src/host/layer23/src/mobile/gsm44068_gcc_bcc.c @@ -0,0 +1,1951 @@ +/* Handle VGCS/VBCS calls. (Voice Group/Broadcast Call Service). */ +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * Author: Andreas Eversberg + * + * 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/>. + */ + +/* Notes on the state machine: + * + * The state machine is different from the diagram depicted in the specs. + * This is because there are some messages missing and some state transitions + * are different or not shown. + * + * A call that has no channel is answered without joining the group channel. + * If it comes available, the establishment is performed and the U4 is entered. + * + * Uplink control is not described in the diagram. Talking/listening is + * requested by user and can be rejected by MM layer, if talking is not + * allowed. + * + * We can be sure that there is no other MM connection while doing VGCS call + * establishment: MMxx-EST-REQ is only accpepted, if there is no MM connection. + * We block calls from user, if there is some other transaction, which is not + * in state U3. Also we accept incoming indications any time and create + * transactions that go to state U3. + * + * If the upper layer or lower layer requests another call/SMS/SS while VGCS + * call is ongoing, this may cause undefined behaviour. + * + */ + +#include <stdint.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm48.h> +#include <osmocom/gsm/protocol/gsm_44_068.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/fsm.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/ms.h> +#include <osmocom/bb/mobile/mncc.h> +#include <osmocom/bb/mobile/transaction.h> +#include <osmocom/bb/mobile/gsm44068_gcc_bcc.h> +#include <osmocom/bb/mobile/voice.h> +#include <osmocom/bb/mobile/vty.h> +#include <l1ctl_proto.h> + +#define S(x) (1 << (x)) + +#define LOG_GCC(trans, level, fmt, args...) \ + LOGP(((trans)->protocol == GSM48_PDISC_GROUP_CC) ? DGCC : DBCC, level, \ + ((trans)->protocol == GSM48_PDISC_GROUP_CC) ? ("VGCS callref %u: " fmt) : ("VBS callref %u: " fmt), \ + (trans)->callref, ##args) +#define LOG_GCC_PR(protocol, ref, level, fmt, args...) \ + LOGP((protocol == GSM48_PDISC_GROUP_CC) ? DGCC : DBCC, level, \ + (protocol == GSM48_PDISC_GROUP_CC) ? ("VGCS callref %u: " fmt) : ("VBS callref %u: " fmt), \ + ref, ##args) + +/* + * init + */ + +static struct osmo_fsm vgcs_gcc_fsm; +static struct osmo_fsm vgcs_bcc_fsm; + +int gsm44068_gcc_init(struct osmocom_ms *ms) +{ + OSMO_ASSERT(osmo_fsm_register(&vgcs_gcc_fsm) == 0); + OSMO_ASSERT(osmo_fsm_register(&vgcs_bcc_fsm) == 0); + + LOGP(DGCC, LOGL_INFO, "init GCC/BCC\n"); + + return 0; +} + +int gsm44068_gcc_exit(struct osmocom_ms *ms) +{ + struct gsm_trans *trans, *trans2; + + LOGP(DGCC, LOGL_INFO, "exit GCC/BCC processes for %s\n", ms->name); + + llist_for_each_entry_safe(trans, trans2, &ms->trans_list, entry) { + if (trans->protocol == GSM48_PDISC_GROUP_CC || trans->protocol == GSM48_PDISC_BCAST_CC) { + LOG_GCC(trans, LOGL_NOTICE, "Free pendig CC-transaction.\n"); + trans_free(trans); + } + } + + return 0; +} + + +/* + * messages + */ + +/* TS 44.068 Chapter 6.1.2.1 */ +enum vgcs_gcc_fsm_states { + VGCS_GCC_ST_U0_NULL = 0, + VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING, + VGCS_GCC_ST_U1_GROUP_CALL_INITIATED, + VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE, /* sepeate link */ + VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE, /* wait for receive mode */ + VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, /* receive mode / U6 @ BCC */ + VGCS_GCC_ST_U2ws_GROUP_CALL_ACTIVE, /* wait for send and receive mode */ + VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE, /* send and receive mode */ + VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE, /* no channel */ + VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, + VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST, + VGCS_GCC_ST_U5_TERMINATION_REQUESTED, +}; + +/* TS 44.068 Figure 6.1 (additional events added) */ +enum vgcs_gcc_fsm_event { + VGCS_GCC_EV_SETUP_REQ, /* calling user initiates call */ + VGCS_GCC_EV_TERM_REQ, /* calling user requests termination */ + VGCS_GCC_EV_MM_EST_CNF, /* MM connection established */ + VGCS_GCC_EV_MM_EST_REJ, /* MM connection failed */ + VGCS_GCC_EV_DI_TERMINATION, /* network acknowledges termination */ + VGCS_GCC_EV_DI_TERM_REJECT, /* network rejects termination */ + VGCS_GCC_EV_DI_CONNECT, /* network indicates connect */ + VGCS_GCC_EV_TIMEOUT, /* several timeout events */ + VGCS_GCC_EV_SETUP_IND, /* notification of ongoing call received */ + VGCS_GCC_EV_JOIN_GC_REQ, /* user wants to join ongoing call */ + VGCS_GCC_EV_JOIN_GC_CNF, /* MM confirms joining ongoing call */ + VGCS_GCC_EV_ABORT_REQ, /* user rejects or leaves call */ + VGCS_GCC_EV_ABORT_IND, /* MM indicates call gone / channel released or failed */ + VGCS_GCC_EV_TALK_REQ, /* user wants to talk */ + VGCS_GCC_EV_TALK_CNF, /* MM confirms talk */ + VGCS_GCC_EV_TALK_REJ, /* MM rejects talk */ + VGCS_GCC_EV_LISTEN_REQ, /* user wants to listen */ + VGCS_GCC_EV_LISTEN_CNF, /* MM confirms listen */ + VGCS_GCC_EV_MM_IDLE, /* MM layer becomes ready for new channel */ + VGCS_GCC_EV_UPLINK_FREE, /* MM layer indicates free uplink in group receive mode */ + VGCS_GCC_EV_UPLINK_BUSY, /* MM layer indicates busy uplink in group receive mode */ +}; + +static const struct value_string vgcs_gcc_fsm_event_names[] = { + OSMO_VALUE_STRING(VGCS_GCC_EV_SETUP_REQ), + OSMO_VALUE_STRING(VGCS_GCC_EV_TERM_REQ), + OSMO_VALUE_STRING(VGCS_GCC_EV_MM_EST_CNF), + OSMO_VALUE_STRING(VGCS_GCC_EV_MM_EST_REJ), + OSMO_VALUE_STRING(VGCS_GCC_EV_DI_TERMINATION), + OSMO_VALUE_STRING(VGCS_GCC_EV_DI_TERM_REJECT), + OSMO_VALUE_STRING(VGCS_GCC_EV_DI_CONNECT), + OSMO_VALUE_STRING(VGCS_GCC_EV_TIMEOUT), + OSMO_VALUE_STRING(VGCS_GCC_EV_SETUP_IND), + OSMO_VALUE_STRING(VGCS_GCC_EV_JOIN_GC_REQ), + OSMO_VALUE_STRING(VGCS_GCC_EV_JOIN_GC_CNF), + OSMO_VALUE_STRING(VGCS_GCC_EV_ABORT_REQ), + OSMO_VALUE_STRING(VGCS_GCC_EV_ABORT_IND), + OSMO_VALUE_STRING(VGCS_GCC_EV_TALK_REQ), + OSMO_VALUE_STRING(VGCS_GCC_EV_TALK_CNF), + OSMO_VALUE_STRING(VGCS_GCC_EV_TALK_REJ), + OSMO_VALUE_STRING(VGCS_GCC_EV_LISTEN_REQ), + OSMO_VALUE_STRING(VGCS_GCC_EV_LISTEN_CNF), + OSMO_VALUE_STRING(VGCS_GCC_EV_MM_IDLE), + OSMO_VALUE_STRING(VGCS_GCC_EV_UPLINK_FREE), + OSMO_VALUE_STRING(VGCS_GCC_EV_UPLINK_BUSY), + { } +}; + +/*! return string representation of GCC/BCC Message Type */ +static const char *gsm44068_gcc_msg_name(uint8_t msg_type) +{ + return get_value_string(osmo_gsm44068_msg_type_names, msg_type); +} + +#define TFU(param) ((param < 0) ? "unchanged" : ((param) ? "T" : "F")) + +/* Set state attributes and check if they are consistent with the current state. */ +static int set_state_attributes(struct gsm_trans *trans, int d_att, int u_att, int comm, int orig, int call_state) +{ + bool orig_t = false, comm_t = false; + + LOG_GCC(trans, LOGL_DEBUG, "Setting state attributes: D-ATT = %s, U-ATT = %s, COMM = %s, ORIG = %s.\n", + TFU(d_att), TFU(u_att), TFU(comm), TFU(orig)); + + /* Control Speaker. */ + if (d_att >= 0 && trans->gcc.d_att != d_att) { + LOG_GCC(trans, LOGL_DEBUG, "Switching Speaker to %d\n", d_att); + gsm48_rr_audio_mode(trans->ms, AUDIO_TX_MICROPHONE | (d_att * AUDIO_RX_SPEAKER)); + } + + if (d_att >= 0) + trans->gcc.d_att = d_att; + if (u_att >= 0) + trans->gcc.u_att = u_att; + if (comm >= 0) + trans->gcc.comm = comm; + if (orig >= 0) + trans->gcc.orig = orig; + if (call_state >= 0) + trans->gcc.call_state = call_state; + + switch (trans->gcc.fi->state) { + case VGCS_GCC_ST_U3_GROUP_CALL_PRESENT: + case VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST: + orig_t = orig; + comm_t = comm; + break; + case VGCS_GCC_ST_U0_NULL: + case VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE: + case VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE: + comm_t = comm; + break; + } + + if (orig_t) + LOG_GCC(trans, LOGL_ERROR, "ORIG = T is inconsistent with states U3 and U4. Please fix!"); + + if (comm_t) + LOG_GCC(trans, LOGL_ERROR, + "COMM = T is inconsistent with states U0, U3, U4, U2nc and U2r. Please fix!"); + + return (orig_t || comm_t) ? -EINVAL : 0; +} + +static void vgcs_vty_notify(struct gsm_trans *trans, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); + +static void vgcs_vty_notify(struct gsm_trans *trans, const char *fmt, ...) +{ + struct osmocom_ms *ms = trans->ms; + char buffer[1000]; + va_list args; + + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer) - 1, fmt, args); + buffer[sizeof(buffer) - 1] = '\0'; + va_end(args); + + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "%s call %d: %s", (trans->protocol == GSM48_PDISC_GROUP_CC) ? "Group" : "Broadcast", + trans->callref, buffer); +} + +/* + * messages + */ + +/* Send MMxx-GROUP-REQ to MM. */ +static int vgcs_group_req(struct gsm_trans *trans) +{ + struct msgb *nmsg; + struct gsm48_mmxx_hdr *nmmh; + uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_GROUP_REQ : GSM48_MMBCC_GROUP_REQ; + + LOG_GCC(trans, LOGL_INFO, "Sending %s.\n", get_mmxx_name(msg_type)); + + OSMO_ASSERT(trans->gcc.ch_desc_present); + + nmsg = gsm48_mmxx_msgb_alloc(msg_type, trans->callref, trans->transaction_id, 0); + if (!nmsg) + return -ENOMEM; + nmmh = (struct gsm48_mmxx_hdr *) nmsg->data; + nmmh->ch_desc_present = trans->gcc.ch_desc_present; + memcpy(&nmmh->ch_desc, &trans->gcc.ch_desc, sizeof(nmmh->ch_desc)); + + return gsm48_mmxx_downmsg(trans->ms, nmsg); +} + +/* Send MMxx-EST-REQ to MM. */ +static int vgcs_est_req(struct gsm_trans *trans) +{ + struct msgb *nmsg; + uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_EST_REQ : GSM48_MMBCC_EST_REQ; + + LOG_GCC(trans, LOGL_INFO, "Sending %s.\n", get_mmxx_name(msg_type)); + + nmsg = gsm48_mmxx_msgb_alloc(msg_type, trans->callref, trans->transaction_id, 0); + if (!nmsg) + return -ENOMEM; + + return gsm48_mmxx_downmsg(trans->ms, nmsg); +} + +/* Push message and send MMxx-DATA-REQ to MM. */ +static int vgcs_data_req(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_mmxx_hdr *mmh; + uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_DATA_REQ : GSM48_MMBCC_DATA_REQ; + + /* push RR header */ + msg->l3h = msg->data; + msgb_push(msg, sizeof(struct gsm48_mmxx_hdr)); + mmh = (struct gsm48_mmxx_hdr *)msg->data; + mmh->msg_type = msg_type; + mmh->ref = trans->callref; + mmh->transaction_id = trans->transaction_id; + mmh->sapi = 0; + + /* send message to MM */ + return gsm48_mmxx_downmsg(trans->ms, msg); +} + +/* Send MMxx-REL-REQ to MM. */ +static int vgcs_rel_req(struct gsm_trans *trans) +{ + struct msgb *nmsg; + uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_REL_REQ : GSM48_MMBCC_REL_REQ; + + LOG_GCC(trans, LOGL_INFO, "Sending %s.\n", get_mmxx_name(msg_type)); + + nmsg = gsm48_mmxx_msgb_alloc(msg_type, trans->callref, trans->transaction_id, 0); + if (!nmsg) + return -ENOMEM; + + return gsm48_mmxx_downmsg(trans->ms, nmsg); +} + +/* Send MMxx-UPLINK-REQ to MM. */ +static int vgcs_uplink_req(struct gsm_trans *trans) +{ + struct msgb *nmsg; + uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_UPLINK_REQ : GSM48_MMBCC_UPLINK_REQ; + + LOG_GCC(trans, LOGL_INFO, "Sending %s.\n", get_mmxx_name(msg_type)); + + nmsg = gsm48_mmxx_msgb_alloc(msg_type, trans->callref, trans->transaction_id, 0); + if (!nmsg) + return -ENOMEM; + + return gsm48_mmxx_downmsg(trans->ms, nmsg); +} + +/* Send MMxx-UPLINK-REL-REQ to MM. */ +static int vgcs_uplink_rel_req(struct gsm_trans *trans) +{ + struct msgb *nmsg; + uint16_t msg_type = (trans->protocol == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_UPLINK_REL_REQ + : GSM48_MMBCC_UPLINK_REL_REQ; + + LOG_GCC(trans, LOGL_INFO, "Sending %s.\n", get_mmxx_name(msg_type)); + + nmsg = gsm48_mmxx_msgb_alloc(msg_type, trans->callref, trans->transaction_id, 0); + if (!nmsg) + return -ENOMEM; + + return gsm48_mmxx_downmsg(trans->ms, nmsg); +} + +static void _add_callref_ie(struct msgb *msg, uint32_t callref, bool with_prio, uint8_t prio) +{ + uint32_t ie; + + ie = callref << 5; + if (with_prio) + ie |= 0x10 | (prio << 1); + msgb_put_u32(msg, ie); +} + +static void _add_user_user_ie(struct msgb *msg, uint8_t user_pdisc, uint8_t *user, uint8_t user_len) +{ + uint8_t *ie; + + ie = msgb_put(msg, user_len + 2); + *ie++ = GSM48_IE_USER_USER; + *ie++ = user_len; + memcpy(ie, user, user_len); +} + +#define GSM44068_IE_CALL_STATE 0xA0 +#define GSM44068_IE_STATE_ATTRS 0xB0 +#define GSM44068_IE_TALKER_PRIO 0xc0 + +static void _add_call_state_ie(struct msgb *msg, uint8_t call_state) +{ + msgb_put_u8(msg, GSM44068_IE_CALL_STATE | call_state); +} + +static void _add_state_attrs_ie(struct msgb *msg, uint8_t da, uint8_t ua, uint8_t comm, uint8_t oi) +{ + msgb_put_u8(msg, GSM44068_IE_STATE_ATTRS | (da << 3) | (ua << 2) | (comm << 1) | oi); +} + +static void _add_talker_prio_ie(struct msgb *msg, uint8_t talker_prio) +{ + msgb_put_u8(msg, GSM44068_IE_TALKER_PRIO | talker_prio); +} + +static void _add_cause_ie(struct msgb *msg, uint8_t cause, uint8_t *diag, uint8_t diag_len) +{ + uint8_t *ie; + + ie = msgb_put(msg, diag_len + 2); + *ie++ = diag_len + 1; + *ie++ = 0x80 | cause; + if (diag_len && diag) + memcpy(ie, diag, diag_len); +} + +static int _msg_too_short(struct gsm_trans *trans) +{ + LOG_GCC(trans, LOGL_ERROR, "MSG too short.\n"); + return -EINVAL; +} + +static int _ie_invalid(struct gsm_trans *trans) +{ + LOG_GCC(trans, LOGL_ERROR, "IE invalid.\n"); + return -EINVAL; +} + +/* 3GPP TS 44.068 Clause 8.4 */ +static int gsm44068_rx_set_parameter(struct gsm_trans *trans, struct msgb *msg, + uint8_t *da, uint8_t *ua, uint8_t *comm, uint8_t *oi) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int remaining_len = msgb_l3len(msg) - sizeof(*gh); + uint8_t *ie = gh->data; + + /* State attributes */ + if (remaining_len < 1) + return _msg_too_short(trans); + if (da) + *da = (ie[0] >> 3) & 0x1; + if (ua) + *ua = (ie[0] >> 2) & 0x1; + if (comm) + *comm = (ie[0] >> 1) & 0x1; + if (oi) + *oi = ie[0] & 0x1; + ie += 1; + + return 0; +} + +/* 3GPP TS 44.068 Clause 8.5 */ +static int gsm44068_tx_setup(struct gsm_trans *trans, uint32_t callref, bool with_prio, uint8_t prio, + uint8_t user_pdisc, uint8_t *user, uint8_t user_len, + bool with_talker_prio, uint8_t talker_prio) +{ + struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX SETUP"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + LOG_GCC(trans, LOGL_INFO, "Sending SETUP.\n"); + + gh->proto_discr = trans->protocol | (trans->transaction_id << 4); + gh->msg_type = OSMO_GSM44068_MSGT_SETUP; + _add_callref_ie(msg, callref, with_prio, prio); + if (user_len && user) + _add_user_user_ie(msg, user_pdisc, user, user_len); + if (with_talker_prio) + _add_talker_prio_ie(msg, talker_prio); + + return vgcs_data_req(trans, msg); +} + +/* 3GPP TS 44.068 Clause 8.6 */ +static int gsm44068_tx_status(struct gsm_trans *trans, uint8_t cause, uint8_t *diag, uint8_t diag_len, + bool with_call_state, uint8_t call_state, bool with_state_attrs, + uint8_t da, uint8_t ua, uint8_t comm, uint8_t oi) +{ + struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX STATUS"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + LOG_GCC(trans, LOGL_INFO, "Sending STATUS.\n"); + + gh->proto_discr = trans->protocol | (trans->transaction_id << 4); + gh->msg_type = OSMO_GSM44068_MSGT_STATUS; + _add_cause_ie(msg, cause, diag, diag_len); + if (with_call_state) + _add_call_state_ie(msg, call_state); + if (with_state_attrs) + _add_state_attrs_ie(msg, da, ua, comm, oi); + + return vgcs_data_req(trans, msg); +} + +/* 3GPP TS 44.068 Clause 8.7 and 8.8 */ +static int gsm44068_rx_termination(struct gsm_trans *trans, struct msgb *msg, uint8_t *cause, uint8_t *diag, uint8_t *diag_len) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int remaining_len = msgb_l3len(msg) - sizeof(*gh); + uint8_t *ie = gh->data; + uint8_t ie_len; + + /* Cause */ + if (remaining_len < 2 || ie[0] < remaining_len - 2) + return _msg_too_short(trans); + ie_len = ie[0]; + if (remaining_len < ie_len + 1) + return _msg_too_short(trans); + if (ie_len < 1) + return _ie_invalid(trans); + if (cause) + *cause = ie[1] & 0x7f; + if (diag && diag_len) { + *diag_len = ie_len - 1; + if (*diag_len) + memcpy(diag, ie + 2, ie_len - 1); + } + remaining_len -= ie_len + 1; + ie += ie_len + 1; + + return 0; +} + +/* 3GPP TS 44.068 Clause 8.9 */ +static int gsm44068_tx_termination_request(struct gsm_trans *trans, uint32_t callref, bool with_prio, uint8_t prio, + bool with_talker_prio, uint8_t talker_prio) +{ + struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX TERMINATION REQUEST"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + LOG_GCC(trans, LOGL_INFO, "Sending TERMINATION REQUEST.\n"); + + gh->proto_discr = trans->protocol | (trans->transaction_id << 4); + gh->msg_type = OSMO_GSM44068_MSGT_TERMINATION_REQUEST; + _add_callref_ie(msg, callref, with_prio, prio); + if (with_talker_prio) + _add_talker_prio_ie(msg, talker_prio); + + return vgcs_data_req(trans, msg); +} + +/* 3GPP TS 44.018 Clause 9.1.48 */ +static int gsm44068_tx_uplink_release(struct gsm_trans *trans, uint8_t cause) +{ + struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX UPLINK RELEASE"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + struct gsm48_uplink_release *ur = (struct gsm48_uplink_release *) msgb_put(msg, sizeof(*ur)); + + LOG_GCC(trans, LOGL_INFO, "UPLINK RELEASE (cause #%d)\n", cause); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_UPLINK_RELEASE; + ur->rr_cause = cause; + + return vgcs_data_req(trans, msg); +} + +/* + * GCC/BCC state machine + * + * For reference see Figure 6.1 of TS 44.068. + * + * Note: There are some events that are not depicted in the state diagram: + * + * "L: ABORT-IND" indicates closing/failing of radio channel. + * "H: TALK-REQ" request talking on the channel. + * "L: TALK-CNF" confirms talker. + * "L: TALK-REJ" rejects talker. + * "H: LISTEN-REQ" request listening. + * "L: LISTEN-CNF" confirms listening. + * + */ + +/* Table 6.1 of TS 44.068 */ +#define T_NO_CHANNEL 3 +#define T_MM_EST 7 +#define T_TERM 10 +#define T_CONN_REQ 10 + +static void vgcs_gcc_fsm_u0_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 0, 0, 0, 0, OSMO_GSM44068_CSTATE_U0); +} + +static void vgcs_gcc_fsm_u0_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + + switch (event) { + case VGCS_GCC_EV_SETUP_REQ: + /* The calling user initiates a new call. */ + LOG_GCC(trans, LOGL_INFO, "Received call from user.\n"); + /* Change to MM CONNECTION PENDING state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING, 0, 0); + /* Send EST-REQ to MM layer. */ + vgcs_est_req(trans); + break; + case VGCS_GCC_EV_SETUP_IND: + /* New call notification. */ + LOG_GCC(trans, LOGL_INFO, "Received call from network.\n"); + /* Change to GROUP CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0); + /* Notify call at VTY. */ + vgcs_vty_notify(trans, "Incoming call\n"); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u0p_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 0, 0, 0, 1, OSMO_GSM44068_CSTATE_U0p); + + /* Start timer */ + osmo_timer_schedule(&fi->timer, T_MM_EST, 0); +} + +static void vgcs_gcc_fsm_u0p_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + uint8_t *cause = data; + uint8_t transaction_id; + + switch (event) { + case VGCS_GCC_EV_TERM_REQ: + /* The user terminates the call. */ + LOG_GCC(trans, LOGL_INFO, "User terminates the call.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Free transaction. MM confirmation/rejection is handled without transaction also. */ + trans_free(trans); + break; + case VGCS_GCC_EV_MM_EST_REJ: + /* The MM layer rejects the call. */ + LOG_GCC(trans, LOGL_INFO, "Call was rejected by MM layer.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify reject at VTY. */ + vgcs_vty_notify(trans, "Rejected (cause %d)\n", *cause); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_MM_EST_CNF: + /* The MM connection was confirmed. */ + LOG_GCC(trans, LOGL_INFO, "Call was confirmed by MM layer.\n"); + /* Change to GROUP CALL INITIATED state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U1_GROUP_CALL_INITIATED, 0, 0); + /* Choose transaction ID. */ + transaction_id = trans_assign_trans_id(trans->ms, trans->protocol, 0); + if (transaction_id < 0) { + /* No free transaction ID. */ + trans_free(trans); + return; + } + trans->transaction_id = transaction_id; + /* Send SETUP towards network. */ + gsm44068_tx_setup(trans, trans->callref, false, 0, false, NULL, 0, false, 0); + break; + case VGCS_GCC_EV_TIMEOUT: + /* Establishment of MM layer timed out. */ + LOG_GCC(trans, LOGL_INFO, "MM layer timed out.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Free transaction. MM confirmation/rejection is handled without transaction also. */ + trans_free(trans); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u1_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 0, 0, 1, 1, OSMO_GSM44068_CSTATE_U1); +} + +static void vgcs_gcc_fsm_u1_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + uint8_t *cause = data; + + switch (event) { + case VGCS_GCC_EV_DI_CONNECT: + /* Received CONNECT from network. */ + LOG_GCC(trans, LOGL_INFO, "Call was accepted by network.\n"); + /* Change to GROUP CALL ACTIVE (separate link) state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE, 0, 0); + /* Notify connect at VTY. */ + vgcs_vty_notify(trans, "Connect\n"); + break; + case VGCS_GCC_EV_DI_TERMINATION: + /* Received TERMINATION from network. */ + LOG_GCC(trans, LOGL_INFO, "Call was terminated by network (cause %d).\n", *cause); + /* Chane to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Terminated (cause %d)\n", *cause); + /* Release MM connection. */ + vgcs_rel_req(trans); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_TERM_REQ: + /* The user terminates the call. */ + LOG_GCC(trans, LOGL_INFO, "User terminates the call.\n"); + /* Change to TERMINATION REQUESTED state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U5_TERMINATION_REQUESTED, 0, 0); + /* Send TERMINATION REQUEST towards network. */ + gsm44068_tx_termination_request(trans, trans->callref, false, 0, false, 0); + break; + case VGCS_GCC_EV_ABORT_IND: + /* Radio link was released or failed. */ + LOG_GCC(trans, LOGL_INFO, "Got release from MM layer.\n"); + /* Chane to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released (cause %d)\n", *cause); + /* Release MM connection. */ + vgcs_rel_req(trans); + /* Free transaction. */ + trans_free(trans); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u2sl_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 1, 1, 1, 1, OSMO_GSM44068_CSTATE_U2sl_U2); +} + +static void vgcs_gcc_fsm_u2sl_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + uint8_t *cause = data; + + switch (event) { + case VGCS_GCC_EV_DI_TERMINATION: + /* Received TERMINATION from network. */ + LOG_GCC(trans, LOGL_INFO, "Call was terminated by network (cause %d).\n", *cause); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Terminated (cause %d)\n", *cause); + /* Don't release MM connection, this is done from network side using CHANNEL RELEASE. */ + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_TERM_REQ: + /* The user terminates the call. */ + LOG_GCC(trans, LOGL_INFO, "User terminates the call.\n"); + /* Change to TERMINATION REQUESTED state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U5_TERMINATION_REQUESTED, 0, 0); + /* Send TERMINATION REQUEST towards network. */ + gsm44068_tx_termination_request(trans, trans->callref, false, 0, false, 0); + break; + case VGCS_GCC_EV_ABORT_IND: + /* Radio link was released or failed. */ + LOG_GCC(trans, LOGL_INFO, "Got release from MM layer.\n"); + /* Chane to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released (cause %d)\n", *cause); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_LISTEN_REQ: + /* The user wants to release dedicated link and join the group channel as listener. */ + LOG_GCC(trans, LOGL_INFO, "User releases uplink on dedicated channel.\n"); + /* Change state to ACTIVE (wait receive). */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE, 0, 0); + /* Set flag that we change to group receive mode after separate link. */ + trans->gcc.receive_after_sl = true; + /* Request release of the uplink. */ + gsm44068_tx_uplink_release(trans, GSM48_RR_CAUSE_LEAVE_GROUP_CA); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u2wr_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 1, 0, 1, -1, OSMO_GSM44068_CSTATE_U2wr_U6); +} + +static void vgcs_gcc_fsm_u2wr_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + uint8_t *cause = data; + + switch (event) { + case VGCS_GCC_EV_LISTEN_CNF: + /* The MM layer confirms uplink release. */ + LOG_GCC(trans, LOGL_INFO, "Uplink is now released.\n"); + /* Change to CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, 0, 0); + /* Notify answer at VTY. */ + vgcs_vty_notify(trans, "Listening\n"); + break; + case VGCS_GCC_EV_TERM_REQ: + /* The calling subscriber wants to terminate the call. */ + LOG_GCC(trans, LOGL_INFO, "User wants to terminate. Setting termination flag.\n"); + trans->gcc.termination = true; + break; + case VGCS_GCC_EV_ABORT_REQ: + /* User aborts group channel. */ + LOG_GCC(trans, LOGL_INFO, "User leaves the group channel.\n"); + /* Change to GROUP CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0); + /* Reset flag after we changed to group receive mode after separate link. */ + trans->gcc.receive_after_sl = false; + /* Notify leaving at VTY. */ + vgcs_vty_notify(trans, "Call left\n"); + /* Release MM connection. (Leave call.) */ + vgcs_rel_req(trans); + break; + case VGCS_GCC_EV_ABORT_IND: + /* The MM layer released the group channel. */ + LOG_GCC(trans, LOGL_INFO, "Release/failure received from MM layer.\n"); + if (trans->gcc.receive_after_sl) { + LOG_GCC(trans, LOGL_INFO, "Ignoring release, because we released separate link.\n"); + break; + } + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released (cause %d)\n", *cause); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_MM_IDLE: + if (!trans->gcc.receive_after_sl) + break; + /* If no channel is available (no notification received), enter the U2nc state. */ + if (!trans->gcc.ch_desc_present) { + /* Change state to ACTIVE (no channel). */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE, 0, 0); + /* Notify answer at VTY. */ + vgcs_vty_notify(trans, "Listen (no channel yet)\n"); + break; + } + /* The MM layer indicates that the phone is ready to request group channel. */ + LOG_GCC(trans, LOGL_INFO, "MM is now idle, we can request group channel.\n"); + /* Send GROUP-REQ to MM layer. */ + vgcs_group_req(trans); + break; + case VGCS_GCC_EV_JOIN_GC_CNF: + /* Reset flag after we changed to group receive mode after separate link. */ + trans->gcc.receive_after_sl = false; + /* The MM layer confirms group channel. */ + LOG_GCC(trans, LOGL_INFO, "Joined group call after releasing separate link.\n"); + /* Change to CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, 0, 0); + /* Notify answer at VTY. */ + vgcs_vty_notify(trans, "Listening\n"); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u2r_u6_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 1, 0, 0, -1, (trans->protocol == GSM48_PDISC_GROUP_CC) ? + OSMO_GSM44068_CSTATE_U2r : OSMO_GSM44068_CSTATE_U2wr_U6); + + /* There is a pending termination request, request uplink. */ + if (trans->gcc.termination) + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TALK_REQ, NULL); +} + +static void vgcs_gcc_fsm_u2r_u6_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + uint8_t *cause = data; + + switch (event) { + case VGCS_GCC_EV_TERM_REQ: + /* The calling subscriber wants to terminate the call. */ + LOG_GCC(trans, LOGL_INFO, "User wants to terminate. Setting termination flag.\n"); + trans->gcc.termination = true; + /* fall-thru */ + case VGCS_GCC_EV_TALK_REQ: + /* The user wants to talk. */ + LOG_GCC(trans, LOGL_INFO, "User wants to talk on the uplink.\n"); + /* Change to GROUP CALL ACTIVE (wait send) state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2ws_GROUP_CALL_ACTIVE, 0, 0); + /* Request group transmit mode from MM layer. */ + vgcs_uplink_req(trans); + break; + case VGCS_GCC_EV_ABORT_REQ: + /* User aborts group channel. */ + LOG_GCC(trans, LOGL_INFO, "User leaves the group channel.\n"); + /* Change to GROUP CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0); + /* Notify leaving at VTY. */ + vgcs_vty_notify(trans, "Call left\n"); + /* Release MM connection. (Leave call.) */ + vgcs_rel_req(trans); + break; + case VGCS_GCC_EV_ABORT_IND: + /* The MM layer released the group channel. */ + LOG_GCC(trans, LOGL_INFO, "Release/failure received from MM layer.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released (cause %d)\n", *cause); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_UPLINK_FREE: + /* The MM layer indicates that the uplink is free. */ + LOG_GCC(trans, LOGL_INFO, "Uplink free indication received from MM layer.\n"); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Uplink free => You may talk\n"); + break; + case VGCS_GCC_EV_UPLINK_BUSY: + /* The MM layer indicates that the uplink is busy. */ + LOG_GCC(trans, LOGL_INFO, "Uplink busy indication received from MM layer.\n"); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Uplink busy => You cannot talk\n"); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u2ws_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 1, 1, 0, -1, OSMO_GSM44068_CSTATE_U2ws); +} + +static void vgcs_gcc_fsm_u2ws_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + uint8_t *cause = data; + + switch (event) { + case VGCS_GCC_EV_TALK_CNF: + /* Uplink was granted. */ + LOG_GCC(trans, LOGL_INFO, "Uplink established, user can talk now.\n"); + /* Change to GROUP CALL ACTIVE (send and receive) state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Talking\n"); + break; + case VGCS_GCC_EV_TALK_REJ: + /* Uplink was rejected. */ + LOG_GCC(trans, LOGL_INFO, "Uplink rejected, user cannot talk.\n"); + /* Clear termination flag, if set. */ + trans->gcc.termination = false; + /* Change to GROUP CALL ACTIVE (receive) state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Talking rejected (cause %d)\n", *cause); + break; + case VGCS_GCC_EV_TERM_REQ: + /* The calling subscriber wants to terminate the call. */ + LOG_GCC(trans, LOGL_INFO, "User wants to terminate. Setting termination flag.\n"); + trans->gcc.termination = true; + break; + case VGCS_GCC_EV_ABORT_REQ: + /* User aborts group channel. */ + LOG_GCC(trans, LOGL_INFO, "User leaves the group channel.\n"); + /* Change to GROUP CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0); + /* Notify leaving at VTY. */ + vgcs_vty_notify(trans, "Call left\n"); + /* Release MM connection. (Leave call.) */ + vgcs_rel_req(trans); + break; + case VGCS_GCC_EV_ABORT_IND: + /* The MM layer released the group channel. */ + LOG_GCC(trans, LOGL_INFO, "Release/failure received from MM layer.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released (cause %d)\n", *cause); + /* Free transaction. */ + trans_free(trans); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u2sr_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 1, 1, 1, -1, OSMO_GSM44068_CSTATE_U2sr); + + /* There is a pending termination request, request uplink. */ + if (trans->gcc.termination) + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TERM_REQ, NULL); +} + +static void vgcs_gcc_fsm_u2sr_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + uint8_t *cause = data; + + switch (event) { + case VGCS_GCC_EV_DI_TERMINATION: + /* Received TERMINATION from network. */ + LOG_GCC(trans, LOGL_INFO, "Call was terminated by network (cause %d).\n", *cause); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Terminated (cause %d)\n", *cause); + /* Don't release MM connection, this is done from network side using CHANNEL RELEASE. */ + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_TALK_REJ: + /* Uplink was rejected by network. (Reject after granting access.) */ + LOG_GCC(trans, LOGL_INFO, "Uplink rejected, user cannot talk.\n"); + /* Change to GROUP CALL ACTIVE (receive) state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Talking rejected (cause %d)\n", *cause); + break; + case VGCS_GCC_EV_LISTEN_REQ: + /* The user wants to release the uplink and become a listener. */ + LOG_GCC(trans, LOGL_INFO, "User wants to release the uplink.\n"); + /* Change to GROUP CALL ACTIVE (wait receive) state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE, 0, 0); + /* Request group receive mode from MM layer. */ + vgcs_uplink_rel_req(trans); + break; + case VGCS_GCC_EV_TERM_REQ: + /* The user terminates the call. */ + LOG_GCC(trans, LOGL_INFO, "User terminates the call.\n"); + /* Change to TERMINATION REQUESTED state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U5_TERMINATION_REQUESTED, 0, 0); + /* Send TERMINATION REQUEST towards network. */ + gsm44068_tx_termination_request(trans, trans->callref, false, 0, false, 0); + break; + case VGCS_GCC_EV_ABORT_REQ: + /* User aborts group channel. */ + LOG_GCC(trans, LOGL_INFO, "User leaves the group channel.\n"); + /* Change to GROUP CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0); + /* Notify leaving at VTY. */ + vgcs_vty_notify(trans, "Call left\n"); + /* Release MM connection. (Leave call.) */ + vgcs_rel_req(trans); + break; + case VGCS_GCC_EV_ABORT_IND: + /* The MM layer released the group channel. */ + LOG_GCC(trans, LOGL_INFO, "Release/failure received from MM layer.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released (cause %d)\n", *cause); + /* Free transaction. */ + trans_free(trans); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u2nc_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 1, 1, 0, -1, OSMO_GSM44068_CSTATE_U2nc); + + /* Start timer */ + osmo_timer_schedule(&fi->timer, T_NO_CHANNEL, 0); +} + +static void vgcs_gcc_fsm_u2nc_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + + switch (event) { + case VGCS_GCC_EV_SETUP_IND: + /* Channel becomes available, now join the group call. */ + LOG_GCC(trans, LOGL_INFO, "Radio channel becomes available.\n"); + /* Change to CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST, 0, 0); + /* Send GROUP-REQ to MM layer. */ + vgcs_group_req(trans); + break; + case VGCS_GCC_EV_TERM_REQ: + /* The calling subscriber wants to terminate the call. */ + LOG_GCC(trans, LOGL_INFO, "User wants to terminate. Setting termination flag.\n"); + trans->gcc.termination = true; + break; + case VGCS_GCC_EV_ABORT_REQ: + /* User aborts group channel. */ + LOG_GCC(trans, LOGL_INFO, "User leaves the group channel, that is not yet established.\n"); + /* Change to GROUP CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U3_GROUP_CALL_PRESENT, 0, 0); + break; + case VGCS_GCC_EV_ABORT_IND: + /* The MM layer indicates that group channel is gone. */ + LOG_GCC(trans, LOGL_INFO, "Group call notification is gone.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released\n"); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_TIMEOUT: + /* Group channel did not become availblet. */ + LOG_GCC(trans, LOGL_INFO, "Timeout waiting for group channel.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Timeout\n"); + /* Free transaction. */ + trans_free(trans); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u3_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 0, 0, 0, 0, OSMO_GSM44068_CSTATE_U3); +} + +static void vgcs_gcc_fsm_u3_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + + switch (event) { + case VGCS_GCC_EV_JOIN_GC_REQ: + /* Join (answer) incoming group call. */ + LOG_GCC(trans, LOGL_INFO, "Join call.\n"); + /* If no channel is available, enter the U2nc state. */ + if (!trans->gcc.ch_desc_present) { + /* Change state to ACTIVE (no channel). */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE, 0, 0); + /* Notify answer at VTY. */ + vgcs_vty_notify(trans, "Answer (no channel yet)\n"); + break; + } + /* Change to CALL PRESENT state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST, 0, 0); + /* Send GROUP-REQ to MM layer. */ + vgcs_group_req(trans); + break; + case VGCS_GCC_EV_ABORT_REQ: + /* User rejects group call. */ + LOG_GCC(trans, LOGL_INFO, "User rejects group call.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_ABORT_IND: + /* The notified call is gone. */ + LOG_GCC(trans, LOGL_INFO, "Received call from network is gone.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. (No cause, because notification is gone.) */ + vgcs_vty_notify(trans, "Released\n"); + /* Free transaction. */ + trans_free(trans); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u4_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, 0, 0, 0, 0, OSMO_GSM44068_CSTATE_U4); + + /* Start timer */ + osmo_timer_schedule(&fi->timer, T_CONN_REQ, 0); +} + +static void vgcs_gcc_fsm_u4_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + + switch (event) { + case VGCS_GCC_EV_JOIN_GC_CNF: + /* The MM layer confirms the group receive mode. (We have a channel.) */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE, 0, 0); + /* Notify answer at VTY. */ + vgcs_vty_notify(trans, "Answer\n"); + break; + case VGCS_GCC_EV_ABORT_REQ: + /* User aborts group channel. */ + LOG_GCC(trans, LOGL_INFO, "User aborts group channel.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_ABORT_IND: + /* The notified call is gone. */ + LOG_GCC(trans, LOGL_INFO, "Received call from network is gone.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. (No cause, because notification is gone.) */ + vgcs_vty_notify(trans, "Released\n"); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_TIMEOUT: + /* Group channel timed out. */ + LOG_GCC(trans, LOGL_INFO, "Timeout waiting for group channel.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Timeout\n"); + /* Free transaction. */ + trans_free(trans); + break; + default: + OSMO_ASSERT(0); + } +} + +static void vgcs_gcc_fsm_u5_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_trans *trans = fi->priv; + + set_state_attributes(trans, -1, -1, 1, 1, OSMO_GSM44068_CSTATE_U5); + + /* Start timer */ + osmo_timer_schedule(&fi->timer, T_TERM, 0); +} + +static void vgcs_gcc_fsm_u5_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + uint8_t *cause = data; + + switch (event) { + case VGCS_GCC_EV_DI_TERMINATION: + /* The network confirm the termination. */ + LOG_GCC(trans, LOGL_INFO, "Termination confirmend by network.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Terminated (cause %d)\n", *cause); + /* Don't release MM connection, this is done from network side using CHANNEL RELEASE. */ + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_DI_TERM_REJECT: + /* Termination was rejected. */ + LOG_GCC(trans, LOGL_INFO, "Termination rejected (cause %d), releasing MM connection.\n", *cause); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Termination rejected (cause %d), Call left\n", *cause); + /* Release MM connection. (Leave call.) + * We must release here, because we can have a dedicated MM connection or joined a group channel. + * We cannot go back, because we don't know what state we had before U5 state was entered. + * Do we really need to go back or just leave the call? */ + vgcs_rel_req(trans); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_ABORT_IND: + /* The notified call is gone. */ + LOG_GCC(trans, LOGL_INFO, "Received call from network is gone.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Released (cause %d)\n", *cause); + /* Free transaction. */ + trans_free(trans); + break; + case VGCS_GCC_EV_TIMEOUT: + /* Termination timed out. */ + LOG_GCC(trans, LOGL_INFO, "Timeout waiting for termination.\n"); + /* Change to NULL state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_U0_NULL, 0, 0); + /* Notify termination at VTY. */ + vgcs_vty_notify(trans, "Timeout\n"); + /* Release MM connection. (Leave call.) */ + vgcs_rel_req(trans); + /* Free transaction. */ + trans_free(trans); + break; + default: + OSMO_ASSERT(0); + } +} + +static int vgcs_gcc_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + return osmo_fsm_inst_dispatch(fi, VGCS_GCC_EV_TIMEOUT, NULL); +} + +static const struct osmo_fsm_state vgcs_gcc_fsm_states[] = { + [VGCS_GCC_ST_U0_NULL] = { + .name = "NULL (U0)", + .in_event_mask = S(VGCS_GCC_EV_SETUP_REQ) | + S(VGCS_GCC_EV_SETUP_IND), + .out_state_mask = S(VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING) | + S(VGCS_GCC_ST_U1_GROUP_CALL_INITIATED) | + S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT), + .onenter = vgcs_gcc_fsm_u0_onenter, + .action = vgcs_gcc_fsm_u0_action, + }, + [VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING] = { + .name = "MM CONNECTION PENDING (U0.p)", + .in_event_mask = S(VGCS_GCC_EV_TERM_REQ) | + S(VGCS_GCC_EV_MM_EST_REJ) | + S(VGCS_GCC_EV_MM_EST_CNF) | + S(VGCS_GCC_EV_TIMEOUT), + .out_state_mask = S(VGCS_GCC_ST_U0_NULL) | + S(VGCS_GCC_ST_U1_GROUP_CALL_INITIATED), + .onenter = vgcs_gcc_fsm_u0p_onenter, + .action = vgcs_gcc_fsm_u0p_action, + }, + [VGCS_GCC_ST_U1_GROUP_CALL_INITIATED] = { + .name = "GROUP CALL INITIATED (U1)", + .in_event_mask = S(VGCS_GCC_EV_DI_CONNECT) | + S(VGCS_GCC_EV_DI_TERMINATION) | + S(VGCS_GCC_EV_TERM_REQ) | + S(VGCS_GCC_EV_ABORT_IND), + .out_state_mask = S(VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U5_TERMINATION_REQUESTED) | + S(VGCS_GCC_ST_U0_NULL), + .onenter = vgcs_gcc_fsm_u1_onenter, + .action = vgcs_gcc_fsm_u1_action, + }, + [VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE] = { + .name = "GROUP CALL ACTIVE separate link (U2sl)", + .in_event_mask = S(VGCS_GCC_EV_DI_TERMINATION) | + S(VGCS_GCC_EV_TERM_REQ) | + S(VGCS_GCC_EV_ABORT_IND) | + S(VGCS_GCC_EV_LISTEN_REQ), + .out_state_mask = S(VGCS_GCC_ST_U0_NULL) | + S(VGCS_GCC_ST_U5_TERMINATION_REQUESTED) | + S(VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE), + .onenter = vgcs_gcc_fsm_u2sl_onenter, + .action = vgcs_gcc_fsm_u2sl_action, + }, + [VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE] = { + .name = "GROUP CALL ACTIVE wait for receive mode (U2wr)", + .in_event_mask = S(VGCS_GCC_EV_LISTEN_CNF) | + S(VGCS_GCC_EV_TERM_REQ) | + S(VGCS_GCC_EV_ABORT_REQ) | + S(VGCS_GCC_EV_ABORT_IND) | + S(VGCS_GCC_EV_MM_IDLE) | + S(VGCS_GCC_EV_JOIN_GC_CNF), + .out_state_mask = S(VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) | + S(VGCS_GCC_ST_U0_NULL), + .onenter = vgcs_gcc_fsm_u2wr_onenter, + .action = vgcs_gcc_fsm_u2wr_action, + }, + [VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE] = { + .name = "GROUP CALL ACTIVE receive mode (U2r or U6 @ BCC)", + .in_event_mask = S(VGCS_GCC_EV_TALK_REQ) | + S(VGCS_GCC_EV_TERM_REQ) | + S(VGCS_GCC_EV_ABORT_REQ) | + S(VGCS_GCC_EV_ABORT_IND) | + S(VGCS_GCC_EV_UPLINK_FREE) | + S(VGCS_GCC_EV_UPLINK_BUSY), + .out_state_mask = S(VGCS_GCC_ST_U2ws_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) | + S(VGCS_GCC_ST_U0_NULL), + .onenter = vgcs_gcc_fsm_u2r_u6_onenter, + .action = vgcs_gcc_fsm_u2r_u6_action, + }, + [VGCS_GCC_ST_U2ws_GROUP_CALL_ACTIVE] = { + .name = "GROUP CALL ACTIVE wait for send+receive mode (U2ws)", + .in_event_mask = S(VGCS_GCC_EV_TALK_CNF) | + S(VGCS_GCC_EV_TALK_REJ) | + S(VGCS_GCC_EV_TERM_REQ) | + S(VGCS_GCC_EV_ABORT_REQ) | + S(VGCS_GCC_EV_ABORT_IND), + .out_state_mask = S(VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) | + S(VGCS_GCC_ST_U0_NULL), + .onenter = vgcs_gcc_fsm_u2ws_onenter, + .action = vgcs_gcc_fsm_u2ws_action, + }, + [VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE] = { + .name = "GROUP CALL ACTIVE send+receive mode (U2sr)", + .in_event_mask = S(VGCS_GCC_EV_DI_TERMINATION) | + S(VGCS_GCC_EV_LISTEN_REQ) | + S(VGCS_GCC_EV_TALK_REJ) | + S(VGCS_GCC_EV_TERM_REQ) | + S(VGCS_GCC_EV_ABORT_REQ) | + S(VGCS_GCC_EV_ABORT_IND), + .out_state_mask = S(VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) | + S(VGCS_GCC_ST_U0_NULL), + .onenter = vgcs_gcc_fsm_u2sr_onenter, + .action = vgcs_gcc_fsm_u2sr_action, + }, + [VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE] = { + .name = "GROUP CALL ACTIVE no channel (U2nc)", + .in_event_mask = S(VGCS_GCC_EV_SETUP_IND) | + S(VGCS_GCC_EV_TERM_REQ) | + S(VGCS_GCC_EV_ABORT_REQ) | + S(VGCS_GCC_EV_ABORT_IND) | + S(VGCS_GCC_EV_TIMEOUT), + .out_state_mask = S(VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST) | + S(VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) | + S(VGCS_GCC_ST_U0_NULL), + .onenter = vgcs_gcc_fsm_u2nc_onenter, + .action = vgcs_gcc_fsm_u2nc_action, + }, + [VGCS_GCC_ST_U3_GROUP_CALL_PRESENT] = { + .name = "GROUP CALL PRESENT (U3)", + .in_event_mask = S(VGCS_GCC_EV_JOIN_GC_REQ) | + S(VGCS_GCC_EV_ABORT_REQ) | + S(VGCS_GCC_EV_ABORT_IND), + .out_state_mask = S(VGCS_GCC_ST_U0_NULL) | + S(VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST) | + S(VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE), + .onenter = vgcs_gcc_fsm_u3_onenter, + .action = vgcs_gcc_fsm_u3_action, + }, + [VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST] = { + .name = "GROUP CALL CONNECTION REQUEST (U4)", + .in_event_mask = S(VGCS_GCC_EV_JOIN_GC_CNF) | + S(VGCS_GCC_EV_ABORT_REQ) | + S(VGCS_GCC_EV_ABORT_IND) | + S(VGCS_GCC_EV_TIMEOUT), + .out_state_mask = S(VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) | + S(VGCS_GCC_ST_U0_NULL), + .onenter = vgcs_gcc_fsm_u4_onenter, + .action = vgcs_gcc_fsm_u4_action, + }, + [VGCS_GCC_ST_U5_TERMINATION_REQUESTED] = { + .name = "TERMINATION REQUESTED (U5)", + .in_event_mask = S(VGCS_GCC_EV_DI_TERMINATION) | + S(VGCS_GCC_EV_DI_TERM_REJECT) | + S(VGCS_GCC_EV_ABORT_IND) | + S(VGCS_GCC_EV_TIMEOUT), + .out_state_mask = S(VGCS_GCC_ST_U0_NULL), + .onenter = vgcs_gcc_fsm_u5_onenter, + .action = vgcs_gcc_fsm_u5_action, + }, +}; + +static struct osmo_fsm vgcs_gcc_fsm = { + .name = "gcc", + .states = vgcs_gcc_fsm_states, + .num_states = ARRAY_SIZE(vgcs_gcc_fsm_states), + .log_subsys = DGCC, + .event_names = vgcs_gcc_fsm_event_names, + .timer_cb = vgcs_gcc_fsm_timer_cb, +}; + +static struct osmo_fsm vgcs_bcc_fsm = { + .name = "bcc", + .states = vgcs_gcc_fsm_states, + .num_states = ARRAY_SIZE(vgcs_gcc_fsm_states), + .log_subsys = DBCC, + .event_names = vgcs_gcc_fsm_event_names, + .timer_cb = vgcs_gcc_fsm_timer_cb, +}; + +static const char *gsm44068_gcc_state_name(struct osmo_fsm_inst *fi) +{ + return vgcs_gcc_fsm_states[fi->state].name; +} + +/* + * transaction + */ + +/* Create transaction together with state machine and set initail states. */ +static struct gsm_trans *trans_alloc_vgcs(struct osmocom_ms *ms, uint8_t pdisc, uint8_t transaction_id, + uint32_t callref) +{ + struct gsm_trans *trans; + + trans = trans_alloc(ms, pdisc, 0xff, callref); + if (trans < 0) + return NULL; + trans->gcc.fi = osmo_fsm_inst_alloc((pdisc == GSM48_PDISC_GROUP_CC) ? &vgcs_gcc_fsm : &vgcs_bcc_fsm, + trans, trans, LOGL_DEBUG, NULL); + if (!trans->gcc.fi) { + trans_free(trans); + return NULL; + } + + LOG_GCC(trans, LOGL_DEBUG, "Created transaction for callref %d, pdisc %d, transaction_id 0x%x\n", + callref, pdisc, transaction_id); + + set_state_attributes(trans, 0, 0, 0, 0, OSMO_GSM44068_CSTATE_U0); + + return trans; +} + +/* Call Control Specific transaction release. + * gets called by trans_free, DO NOT CALL YOURSELF! + */ +void _gsm44068_gcc_bcc_trans_free(struct gsm_trans *trans) +{ + if (!trans->gcc.fi) + return; + + /* Free state machine. */ + osmo_fsm_inst_free(trans->gcc.fi); +} + +/* Find ongoing call. (The call must not be present.) */ +struct gsm_trans *trans_find_ongoing_gcc_bcc(struct osmocom_ms *ms) +{ + struct gsm_trans *trans; + + llist_for_each_entry(trans, &ms->trans_list, entry) { + if (trans->protocol != GSM48_PDISC_GROUP_CC && trans->protocol != GSM48_PDISC_BCAST_CC) + continue; + if (trans->gcc.fi->state != VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) + return trans; + } + + return NULL; +} + +/* + * messages from upper/lower layers + */ + +static int gsm44068_gcc_data_ind(struct gsm_trans *trans, struct msgb *msg, uint8_t transaction_id) +{ + struct osmocom_ms *ms = trans->ms; + struct gsm48_hdr *gh = msgb_l3(msg); + int msg_type = gh->msg_type & 0xbf; + int rc = 0; + uint8_t cause = 0; + uint8_t d_att, u_att, comm, orig; + + /* pull the MMCC header */ + msgb_pull(msg, sizeof(struct gsm48_mmxx_hdr)); + + /* Use transaction ID from message. If we join a group call, we don't have it until now. */ + trans->transaction_id = transaction_id; + + LOG_GCC(trans, LOGL_INFO, "(ms %s) Received '%s' in state %s\n", ms->name, gsm44068_gcc_msg_name(msg_type), + gsm44068_gcc_state_name(trans->gcc.fi)); + + switch (msg_type) { + case OSMO_GSM44068_MSGT_CONNECT: + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_DI_CONNECT, NULL); + break; + case OSMO_GSM44068_MSGT_TERMINATION: + rc = gsm44068_rx_termination(trans, msg, &cause, NULL, NULL); + if (rc < 0) + break; + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_DI_TERMINATION, &cause); + break; + case OSMO_GSM44068_MSGT_TERMINATION_REJECT: + rc = gsm44068_rx_termination(trans, msg, &cause, NULL, NULL); + if (rc < 0) + break; + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_DI_TERM_REJECT, &cause); + break; + case OSMO_GSM44068_MSGT_GET_STATUS: + /* Note: The message via UNIT DATA is not supported. */ + gsm44068_tx_status(trans, OSMO_GSM44068_CAUSE_RESPONSE_TO_GET_STATUS, NULL, 0, + true, trans->gcc.call_state, + true, trans->gcc.d_att, trans->gcc.u_att, trans->gcc.comm, trans->gcc.orig); + break; + case OSMO_GSM44068_MSGT_SET_PARAMETER: + rc = gsm44068_rx_set_parameter(trans, msg, &d_att, &u_att, &comm, &orig); + if (rc >= 0) + set_state_attributes(trans, d_att, u_att, comm, orig, -1); + break; + default: + LOG_GCC(trans, LOGL_NOTICE, "Message '%s' not supported.\n", gsm44068_gcc_msg_name(msg_type)); + rc = -EINVAL; + } + + return rc; +} + +/* receive message from MM layer */ +int gsm44068_rcv_gcc_bcc(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_settings *set = &ms->settings; + struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; + int msg_type = mmh->msg_type; + uint8_t pdisc; + struct gsm_trans *trans; + int rc = 0; + uint8_t cause; + + /* Check for message class and get protocol type. */ + switch ((msg_type & GSM48_MMXX_MASK)) { + case GSM48_MMGCC_CLASS: + pdisc = GSM48_PDISC_GROUP_CC; + if (!set->vgcs) { + LOGP(DGCC, LOGL_ERROR, "Ignoring message '%s', because VGCS is not supported!\n", + get_mmxx_name(msg_type)); + return -ENOTSUP; + } + break; + case GSM48_MMBCC_CLASS: + pdisc = GSM48_PDISC_BCAST_CC; + if (!set->vbs) { + LOGP(DGCC, LOGL_ERROR, "Ignoring message '%s', because VBS is not supported!\n", + get_mmxx_name(msg_type)); + return -ENOTSUP; + } + break; + default: + LOGP(DGCC, LOGL_ERROR, "Message class not allowed, please fix!\n"); + return -EINVAL; + } + + trans = trans_find_by_callref(ms, pdisc, mmh->ref); + if (!trans) { + LOG_GCC_PR(pdisc, mmh->ref, LOGL_INFO, "(ms %s) Received '%s' without transaction.\n", + ms->name, get_mmxx_name(msg_type)); + + if (msg_type == GSM48_MMGCC_REL_IND || msg_type == GSM48_MMBCC_REL_IND) { + /* If we got the MMxx-EST-REJ after we aborted the call, we ignore. */ + return 0; + } + if (msg_type == GSM48_MMGCC_EST_CNF || msg_type == GSM48_MMBCC_EST_CNF || + msg_type == GSM48_MMGCC_GROUP_CNF || msg_type == GSM48_MMBCC_GROUP_CNF) { + struct msgb *nmsg; + LOG_GCC_PR(pdisc, mmh->ref, LOGL_ERROR, "Received confirm for GCC/BCC call, " + "but there is no transaction, releasing.\n"); + /* If we got the MMxx-EST-CNF after we aborted the call, we release. */ + msg_type = (pdisc == GSM48_PDISC_GROUP_CC) ? GSM48_MMGCC_REL_REQ : GSM48_MMBCC_REL_REQ; + nmsg = gsm48_mmxx_msgb_alloc(msg_type, mmh->ref, mmh->transaction_id, 0); + if (!nmsg) + return -ENOMEM; + LOG_GCC_PR(pdisc, mmh->ref, LOGL_INFO, "Sending %s\n", get_mmxx_name(msg_type)); + gsm48_mmxx_downmsg(ms, nmsg); + return 0; + + } else if (msg_type == GSM48_MMGCC_NOTIF_IND || msg_type == GSM48_MMBCC_NOTIF_IND) { + /* Notification gone but no transaction. */ + if (mmh->notify != MMXX_NOTIFY_SETUP) + return 0; + /* Incoming notification, creation transaction. */ + trans = trans_alloc_vgcs(ms, pdisc, 0xff, mmh->ref); + if (trans < 0) + return -ENOMEM; + } else { + LOG_GCC_PR(pdisc, mmh->ref, LOGL_ERROR, "Received GCC/BCC message for unknown transaction.\n"); + return -ENOENT; + } + } + + LOG_GCC(trans, LOGL_INFO, "(ms %s) Received '%s' in state %s\n", ms->name, get_mmxx_name(msg_type), + gsm44068_gcc_state_name(trans->gcc.fi)); + + switch (msg_type) { + case GSM48_MMGCC_EST_CNF: + case GSM48_MMBCC_EST_CNF: + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_MM_EST_CNF, NULL); + break; + case GSM48_MMGCC_ERR_IND: + case GSM48_MMBCC_ERR_IND: + case GSM48_MMGCC_REL_IND: + case GSM48_MMBCC_REL_IND: + /* If MM fails or is rejected during U0.p state, this is a MM-EST-REJ. */ + if (trans->gcc.fi->state == VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING) + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_MM_EST_REJ, &mmh->cause); + else + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_ABORT_IND, &mmh->cause); + break; + case GSM48_MMGCC_DATA_IND: + case GSM48_MMBCC_DATA_IND: + rc = gsm44068_gcc_data_ind(trans, msg, mmh->transaction_id); + break; + case GSM48_MMGCC_NOTIF_IND: + case GSM48_MMBCC_NOTIF_IND: + switch (mmh->notify) { + case MMXX_NOTIFY_SETUP: + /* Store channel description. */ + trans->gcc.ch_desc_present = mmh->ch_desc_present; + memcpy(&trans->gcc.ch_desc, &mmh->ch_desc, sizeof(trans->gcc.ch_desc)); + if (trans->gcc.fi->state == VGCS_GCC_ST_U0_NULL) { + /* In null state this indication is a SETUP-IND. */ + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_SETUP_IND, NULL); + } else if (trans->gcc.fi->state == VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE && mmh->ch_desc_present) { + /* In U2nc state with a channel info, is a SETUP-IND (with updated mode). */ + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_SETUP_IND, NULL); + } + break; + case MMXX_NOTIFY_RELEASE: + if (trans->gcc.fi->state == VGCS_GCC_ST_U3_GROUP_CALL_PRESENT || + trans->gcc.fi->state == VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST || + trans->gcc.fi->state == VGCS_GCC_ST_U2nc_GROUP_CALL_ACTIVE) { + /* If notification is gone, abort pending received call. */ + cause = GSM48_CC_CAUSE_NORM_CALL_CLEAR; + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_ABORT_IND, &cause); + } + break; + } + break; + case GSM48_MMGCC_GROUP_CNF: + case GSM48_MMBCC_GROUP_CNF: + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_JOIN_GC_CNF, NULL); + break; + case GSM48_MMGCC_UPLINK_CNF: + case GSM48_MMBCC_UPLINK_CNF: + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TALK_CNF, NULL); + break; + case GSM48_MMGCC_UPLINK_REL_IND: + case GSM48_MMBCC_UPLINK_REL_IND: + if (trans->gcc.fi->state == VGCS_GCC_ST_U2ws_GROUP_CALL_ACTIVE || + trans->gcc.fi->state == VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE) { + /* We are waiting to send, so this is a reject. */ + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TALK_REJ, &mmh->cause); + } else if (trans->gcc.fi->state == VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE) { + /* We are waiting to receive, so this is a confirm. */ + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_LISTEN_CNF, &mmh->cause); + } + break; + case GSM48_MMGCC_UPLINK_FREE_IND: + case GSM48_MMBCC_UPLINK_FREE_IND: + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_UPLINK_FREE, NULL); + break; + case GSM48_MMGCC_UPLINK_BUSY_IND: + case GSM48_MMBCC_UPLINK_BUSY_IND: + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_UPLINK_BUSY, NULL); + break; + default: + LOG_GCC(trans, LOGL_NOTICE, "Message '%s' unhandled.\n", get_mmxx_name(msg_type)); + rc = -ENOTSUP; + } + + return rc; +} + +/* Special function to receive IDLE state of MM layer. This is required to request a channel after dedicated mode. */ +int gsm44068_rcv_mm_idle(struct osmocom_ms *ms) +{ + struct gsm_trans *trans; + + trans = trans_find_ongoing_gcc_bcc(ms); + if (!trans) + return -ENOENT; + if (trans->gcc.fi->state == VGCS_GCC_ST_U2wr_GROUP_CALL_ACTIVE && trans->gcc.receive_after_sl) { + /* Finally the MM layer is IDLE. */ + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_MM_IDLE, NULL); + return 0; + } + return -EINVAL; +} + +/* Setup or join a VGC/VBC. */ +int gcc_bcc_call(struct osmocom_ms *ms, uint8_t protocol, const char *number) +{ + uint32_t callref = strtoul(number, NULL, 0); + struct gsm_trans *trans; + int i; + + if (strlen(number) > 8) { +inval: + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Invalid group '%s'\n", number); + return -EINVAL; + } + + for (i = 0; i < strlen(number); i++) { + if (number[i] < '0' || number[i] > '9') + goto inval; + } + + if (callref == 0) + goto inval; + + /* Reject if there is already an ongoing call. */ + trans = trans_find_ongoing_gcc_bcc(ms); + if (trans) { + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Cannot call/join, we are busy\n"); + return -EBUSY; + } + + /* Find call that matches the given protocol+cellref. */ + trans = trans_find_by_callref(ms, protocol, callref); + if (trans) { + /* Answer incoming call. */ + if (trans->gcc.fi->state == VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) { + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_JOIN_GC_REQ, NULL); + return 0; + } + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "Call already established\n"); + return -EEXIST; + } + + /* Create new transaction. ORIG will be set when entering U2sl state. */ + trans = trans_alloc_vgcs(ms, protocol, 0xff, callref); + if (trans < 0) + return -ENOMEM; + + /* Setup new call. */ + return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_SETUP_REQ, NULL); +} + +/* Leave a VGC. (If we are the originator, we do not terminate.) */ +int gcc_leave(struct osmocom_ms *ms) +{ + struct gsm_trans *trans; + + /* Reject if there is no call. */ + trans = trans_find_ongoing_gcc_bcc(ms); + if (!trans) { + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "No Call\n"); + return -EINVAL; + } + + if (trans->gcc.fi->state == VGCS_GCC_ST_U0p_MM_CONNECTION_PENDING || + trans->gcc.fi->state == VGCS_GCC_ST_U1_GROUP_CALL_INITIATED || + trans->gcc.fi->state == VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE || + trans->gcc.fi->state == VGCS_GCC_ST_U5_TERMINATION_REQUESTED) { + LOG_GCC(trans, LOGL_NOTICE, "Cannot leave (abort), in this state.\n"); + return -EINVAL; + } + + /* Send ABORT-REQ to leave the call. */ + return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_ABORT_REQ, NULL); +} + +/* Hangup VGC/VBC. (If we are the originator, we terminate the call.) */ +int gcc_bcc_hangup(struct osmocom_ms *ms) +{ + struct gsm_trans *trans; + + /* Reject if there is no call. */ + trans = trans_find_ongoing_gcc_bcc(ms); + if (!trans) { + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "No Call\n"); + return -EINVAL; + } + + if (trans->gcc.orig) { + if (trans->gcc.fi->state == VGCS_GCC_ST_U3_GROUP_CALL_PRESENT || + trans->gcc.fi->state == VGCS_GCC_ST_U4_GROUP_CALL_CONN_REQUEST || + trans->gcc.fi->state == VGCS_GCC_ST_U5_TERMINATION_REQUESTED) { + LOG_GCC(trans, LOGL_NOTICE, "Cannot hangup (terminate) in this state.\n"); + return -EINVAL; + } + /* Send TERM-REQ to leave the call. */ + return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TERM_REQ, NULL); + } + + if (trans->gcc.fi->state == VGCS_GCC_ST_U5_TERMINATION_REQUESTED) { + LOG_GCC(trans, LOGL_NOTICE, "Cannot hangup (abort) in this state.\n"); + return -EINVAL; + } + /* Send ABORT-REQ to leave the call. */ + return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_ABORT_REQ, NULL); +} + +int gcc_talk(struct osmocom_ms *ms) +{ + struct gsm_trans *trans; + + /* Reject if there is no call. */ + trans = trans_find_ongoing_gcc_bcc(ms); + if (!trans) { + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "No Call\n"); + return -EINVAL; + } + + if (trans->protocol != GSM48_PDISC_GROUP_CC) { + LOG_GCC(trans, LOGL_NOTICE, "Cannot talk, the ongoing call is not a group call.\n"); + vgcs_vty_notify(trans, "Not a group call\n"); + return -EIO; + } + + if (trans->gcc.fi->state != VGCS_GCC_ST_U2r_U6_GROUP_CALL_ACTIVE) { + LOG_GCC(trans, LOGL_NOTICE, "Cannot talk, we are not listening.\n"); + return -EINVAL; + } + + /* Send TALK-REQ to become talker. */ + return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TALK_REQ, NULL); +} + +int gcc_listen(struct osmocom_ms *ms) +{ + struct gsm_trans *trans; + + /* Reject if there is no call. */ + trans = trans_find_ongoing_gcc_bcc(ms); + if (!trans) { + l23_vty_ms_notify(ms, NULL); + l23_vty_ms_notify(ms, "No Call\n"); + return -EINVAL; + } + + if (trans->protocol != GSM48_PDISC_GROUP_CC) { + LOG_GCC(trans, LOGL_NOTICE, "Cannot become listener, the ongoing call is not a group call.\n"); + vgcs_vty_notify(trans, "Not a group call\n"); + return -EIO; + } + + if (trans->gcc.fi->state != VGCS_GCC_ST_U2sl_GROUP_CALL_ACTIVE && + trans->gcc.fi->state != VGCS_GCC_ST_U2sr_GROUP_CALL_ACTIVE) { + LOG_GCC(trans, LOGL_NOTICE, "Cannot listen, we are not talking.\n"); + return -EINVAL; + } + + /* Send LISTEN-REQ to become listener. */ + return osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_LISTEN_REQ, NULL); +} + +int gsm44068_dump_calls(struct osmocom_ms *ms, void (*print)(void *, const char *, ...), void *priv) +{ + struct gsm_trans *trans; + + llist_for_each_entry(trans, &ms->trans_list, entry) { + if (trans->protocol != GSM48_PDISC_GROUP_CC && trans->protocol != GSM48_PDISC_BCAST_CC) + continue; + print(priv, "Voice %s call '%d' is %s.\n", + (trans->protocol == GSM48_PDISC_GROUP_CC) ? "group" : "broadcast", trans->callref, + (trans->gcc.fi->state == VGCS_GCC_ST_U3_GROUP_CALL_PRESENT) ? "incoming" : "active"); + } + + return 0; +} diff --git a/src/host/layer23/src/mobile/gsm480_ss.c b/src/host/layer23/src/mobile/gsm480_ss.c index bf996674..4ed83545 100644 --- a/src/host/layer23/src/mobile/gsm480_ss.c +++ b/src/host/layer23/src/mobile/gsm480_ss.c @@ -28,6 +28,7 @@ #include <osmocom/bb/mobile/mncc.h> #include <osmocom/bb/mobile/transaction.h> #include <osmocom/bb/mobile/gsm480_ss.h> +#include <osmocom/bb/mobile/gsm44068_gcc_bcc.h> #include <osmocom/core/talloc.h> #include <osmocom/bb/mobile/vty.h> #include <osmocom/gsm/protocol/gsm_04_80.h> @@ -599,6 +600,12 @@ int ss_send(struct osmocom_ms *ms, const char *code, int new_trans) return -EIO; } + /* ASCI call does not allow other transactions */ + if (trans_find_ongoing_gcc_bcc(ms)) { + gsm480_ss_result(ms, "<ongoing ASCI call>", 0); + return -EBUSY; + } + /* allocate transaction with dummy reference */ transaction_id = trans_assign_trans_id(ms, GSM48_PDISC_NC_SS, 0); diff --git a/src/host/layer23/src/mobile/gsm48_cc.c b/src/host/layer23/src/mobile/gsm48_cc.c index 190eb916..9691c68c 100644 --- a/src/host/layer23/src/mobile/gsm48_cc.c +++ b/src/host/layer23/src/mobile/gsm48_cc.c @@ -32,6 +32,7 @@ #include <osmocom/bb/mobile/mncc.h> #include <osmocom/bb/mobile/transaction.h> #include <osmocom/bb/mobile/gsm48_cc.h> +#include <osmocom/bb/mobile/gsm44068_gcc_bcc.h> #include <osmocom/bb/mobile/voice.h> #include <l1ctl_proto.h> @@ -1934,6 +1935,12 @@ int mncc_tx_to_cc(void *inst, int msg_type, void *arg) return -EBUSY; } + /* ASCI call does not allow other transactions */ + if (trans_find_ongoing_gcc_bcc(ms)) { + LOGP(DCC, LOGL_NOTICE, "Phone is busy doing ASCI call\n"); + return -EBUSY; + } + data->msg_type = msg_type; /* Find callref */ diff --git a/src/host/layer23/src/mobile/gsm48_mm.c b/src/host/layer23/src/mobile/gsm48_mm.c index 82e5fade..092a984d 100644 --- a/src/host/layer23/src/mobile/gsm48_mm.c +++ b/src/host/layer23/src/mobile/gsm48_mm.c @@ -38,6 +38,7 @@ #include <osmocom/bb/mobile/gsm48_cc.h> #include <osmocom/bb/mobile/gsm480_ss.h> #include <osmocom/bb/mobile/gsm411_sms.h> +#include <osmocom/bb/mobile/gsm44068_gcc_bcc.h> #include <osmocom/bb/mobile/app_mobile.h> #include <osmocom/bb/mobile/primitives.h> #include <osmocom/bb/mobile/vty.h> @@ -905,6 +906,7 @@ int gsm48_mmxx_dequeue(struct osmocom_ms *ms) break; case GSM48_MMGCC_CLASS: case GSM48_MMBCC_CLASS: + gsm44068_rcv_gcc_bcc(ms, msg); break; } msgb_free(msg); @@ -1169,6 +1171,15 @@ static void new_mm_state(struct gsm48_mmlayer *mm, int state, int substate) /* must exit, because this function can be called recursively */ return; } + + /* Tell the group call that we are ready for a new channel activation. */ + if (state == GSM48_MM_ST_MM_IDLE + && (substate == GSM48_MM_SST_NORMAL_SERVICE + || substate == GSM48_MM_SST_ATTEMPT_UPDATE)) { + gsm44068_rcv_mm_idle(ms); + /* must exit, because this function can be called recursively */ + return; + } } /* return PLMN SEARCH or PLMN SEARCH NORMAL state */ @@ -4751,7 +4762,7 @@ forward_msg: case GSM48_PDISC_GROUP_CC: case GSM48_PDISC_BCAST_CC: - rc = -ENOTSUP; + rc = gsm44068_rcv_gcc_bcc(ms, msg); msgb_free(msg); return rc; diff --git a/src/host/layer23/src/mobile/transaction.c b/src/host/layer23/src/mobile/transaction.c index 712a8eef..3cc25fdc 100644 --- a/src/host/layer23/src/mobile/transaction.c +++ b/src/host/layer23/src/mobile/transaction.c @@ -30,6 +30,7 @@ void _gsm48_cc_trans_free(struct gsm_trans *trans); void _gsm480_ss_trans_free(struct gsm_trans *trans); void _gsm411_sms_trans_free(struct gsm_trans *trans); +void _gsm44068_gcc_bcc_trans_free(struct gsm_trans *trans); struct gsm_trans *trans_find_by_id(struct osmocom_ms *ms, uint8_t proto, uint8_t trans_id) @@ -93,6 +94,10 @@ void trans_free(struct gsm_trans *trans) case GSM48_PDISC_SMS: _gsm411_sms_trans_free(trans); break; + case GSM48_PDISC_GROUP_CC: + case GSM48_PDISC_BCAST_CC: + _gsm44068_gcc_bcc_trans_free(trans); + break; } DEBUGP(DCC, "ms %s frees transaction (mem %p)\n", trans->ms->name, |